/** * Novacoin classes library * Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com) * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ using SQLite.Net; using SQLite.Net.Attributes; using SQLite.Net.Interop; using SQLite.Net.Platform.Generic; using System; using System.IO; using System.Linq; namespace Novacoin { /// /// Key storage item and structure /// [Table("KeyStorage")] class KeyStorageItem { /// /// Key item number /// [PrimaryKey, AutoIncrement] public int ItemId { get; set; } /// /// Hash160 of pubkey /// public byte[] KeyID { get; set; } /// /// Public key /// public byte[] PublicKey { get; set; } /// /// Private key /// public byte[] PrivateKey { get; set; } /// /// Compressed key flag /// public bool IsCompressed { get; set; } /// /// Is this key a part of KeyPool? /// [Indexed] public bool IsUsed { get; set; } /// /// Item creation time /// [Indexed] public uint nTime { get; set; } } /// /// Script storage item and structure /// [Table("ScriptStorage")] class ScriptStorageItem { /// /// Script item number /// [PrimaryKey, AutoIncrement] public int ItemId { get; set; } /// /// Hash160 of script /// public byte[] ScriptID { get; set; } /// /// Script code bytes /// public byte[] ScriptCode { get; set; } } /// /// select count(...) as Count from ... where ... /// select number from ... where ... /// class NumQuery { public int Num { get; set; } } /// /// Key storage /// public class CKeyStore : IDisposable { private bool disposed = false; private object LockObj = new object(); private SQLiteConnection dbConn = null; private int nKeyPoolSize = 100; /// /// Initialize new instance of key store. /// /// Path to database file. /// Number of reserved keys. public CKeyStore(string strDatabasePath="KeyStore.db", int KeyPoolSize = 100) { bool firstInit = !File.Exists(strDatabasePath); dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), strDatabasePath); nKeyPoolSize = KeyPoolSize; if (firstInit) { lock(LockObj) { dbConn.CreateTable(CreateFlags.AutoIncPK); dbConn.CreateTable(CreateFlags.AutoIncPK); GenerateKeys(nKeyPoolSize); } } } ~CKeyStore() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { // Free other state (managed objects). } if (dbConn != null) { dbConn.Close(); dbConn = null; } disposed = true; } } /// /// Generate keys and insert them to key store. /// /// private void GenerateKeys(int n) { lock (LockObj) { dbConn.BeginTransaction(); // Generate keys for (int i = 0; i < n; i++) { var keyPair = new CKeyPair(); var res = dbConn.Insert(new KeyStorageItem() { KeyID = keyPair.KeyID, PublicKey = keyPair.PubKey, PrivateKey = keyPair, IsCompressed = keyPair.IsCompressed, IsUsed = false, nTime = Interop.GetTime() }); // TODO: Additional initialization } dbConn.Commit(); } } /// /// Insert key data into table /// /// CKeyPair instance /// Result public bool AddKey(CKeyPair keyPair) { lock(LockObj) { var res = dbConn.Insert(new KeyStorageItem() { KeyID = keyPair.KeyID, PublicKey = keyPair.PubKey, PrivateKey = keyPair, IsCompressed = keyPair.IsCompressed, IsUsed = true, nTime = Interop.GetTime() }); if (res == 0) { return false; } } return true; } /// /// Check existance of item with provided KeyID /// /// Hash160 of public key. /// Checking result public bool HaveKey(CKeyID keyID) { var QueryCount = dbConn.Query("select count([ItemID]) from [KeyStorage] where [KeyID] = ?", (byte[])keyID); return QueryCount.First().Num == 1; } /// /// Get the key pair object. /// /// Hash of public key. /// Instance of CKeyPair or null. /// Result public bool GetKey(CKeyID keyID, out CKeyPair keyPair) { var QueryGet = dbConn.Query("select * from [KeyStorage] where [KeyID] = ?", (byte[])keyID); if (QueryGet.Count() == 1) { keyPair = new CKeyPair(QueryGet.First().PrivateKey); return true; } keyPair = null; return false; } /// /// Add redeem script to script store. /// /// CScript instance /// Result public bool AddScript(CScript script) { lock (LockObj) { var res = dbConn.Insert(new ScriptStorageItem() { ScriptID = script.ScriptID, ScriptCode = script }); if (res == 0) { return false; } } return true; } /// /// Check existance of item with provided ScriptID /// /// Hash160 of script code. /// Checking result public bool HaveScript(CScriptID scriptID) { var QueryGet = dbConn.Query("select count([ItemID]) from [ScriptStorage] where [ScriptID] = ?", (byte[])scriptID); return QueryGet.First().Num == 1; } /// /// Get redeem script from database. /// /// Script ID, evaluated as Hash160(script code). /// Instance of CScript /// Result public bool GetScript(CScriptID scriptID, out CScript script) { var QueryGet = dbConn.Query("select * from [ScriptStorage] where [ScriptID] = ?", (byte[])scriptID); if (QueryGet.Count() == 1) { script = new CScript(QueryGet.First().ScriptCode); return true; } script = null; return false; } /// /// SQLite return type for ReserveKey /// class ReservedKey { public int ItemId { get; set; } public byte[] KeyID { get; set; } } /// /// Reserve key from a list of unused keys. /// /// Internal index of key /// CKeyID instance public CKeyID SelectKey(out int nKeyIndex) { var QueryGet = dbConn.Query("select ItemId, KeyID from [KeyStorage] where not [IsUsed] order by [nTime] asc limit 1"); if (QueryGet.Count() == 1) { var res = QueryGet.First(); nKeyIndex = res.ItemId; return new CKeyID(res.KeyID); } else { // Generate new keys in case if keypool is exhausted. GenerateKeys(nKeyPoolSize); return SelectKey(out nKeyIndex); } } /// /// Mark key as used. /// /// Internal index. public void MarkUsed(int nIndex) { lock (LockObj) { dbConn.Execute("update [KeyStorage] set [IsUsed] = true where [ItemId] = ?", nIndex); } } /// /// Mark key as unused. /// /// Internal index. public void MarkUnused(int nIndex) { lock (LockObj) { dbConn.Execute("update [KeyStorage] set [IsUsed] = false where [ItemId] = ?", nIndex); } } /// /// Regenerate all unused keys. /// public void ResetPool() { lock (LockObj) { dbConn.Execute("delete from [KeyStorage] where not [IsUsed]"); GenerateKeys(nKeyPoolSize); } } /// /// Timestamp of oldest item in keystore. /// public int OldestTime { get { var QueryTime = dbConn.Query("select [nTime] from [KeyStorage] where not [IsUsed] order by [ItemId] asc limit 1"); return QueryTime.First().Num; } } } }