using SQLite.Net.Attributes;
using SQLite.Net.Interop;
using SQLite.Net.Platform.Generic;
+using System;
using System.IO;
using System.Linq;
/// Item creation time
/// </summary>
[Indexed]
- public int nTime { get; set; }
+ public uint nTime { get; set; }
}
/// <summary>
/// <summary>
/// select count(...) as Count from ... where ...
+ /// select number from ... where ...
/// </summary>
- class CountQuery
+ class NumQuery
{
- public int Count { get; set; }
+ public int Num { get; set; }
}
- public class CKeyStore
+ /// <summary>
+ /// Key storage
+ /// </summary>
+ public class CKeyStore : IDisposable
{
+ private bool disposed = false;
private object LockObj = new object();
private SQLiteConnection dbConn = null;
+ private int nKeyPoolSize = 100;
- public CKeyStore(string strDatabasePath="KeyStore.db")
+ /// <summary>
+ /// Initialize new instance of key store.
+ /// </summary>
+ /// <param name="strDatabasePath">Path to database file.</param>
+ /// <param name="KeyPoolSize">Number of reserved keys.</param>
+ public CKeyStore(string strDatabasePath="KeyStore.db", int KeyPoolSize = 100)
{
- bool firstInit = File.Exists(strDatabasePath);
+ bool firstInit = !File.Exists(strDatabasePath);
dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), strDatabasePath);
+ nKeyPoolSize = KeyPoolSize;
- if (!firstInit)
+ if (firstInit)
{
lock(LockObj)
{
dbConn.CreateTable<KeyStorageItem>(CreateFlags.AutoIncPK);
dbConn.CreateTable<ScriptStorageItem>(CreateFlags.AutoIncPK);
- dbConn.BeginTransaction();
-
- // Generate keys
- for (int i = 0; i < 1000; i++)
- {
- var keyPair = new CKeyPair();
-
- var res = dbConn.Insert(new KeyStorageItem()
- {
- KeyID = keyPair.KeyID.hashBytes,
- PublicKey = keyPair.PublicBytes,
- PrivateKey = keyPair.SecretBytes,
- IsCompressed = keyPair.IsCompressed,
- IsUsed = false,
- nTime = Interop.GetTime()
- });
-
- // TODO: Additional initialization
- }
-
- dbConn.Commit();
+ GenerateKeys(nKeyPoolSize);
}
}
}
~CKeyStore()
{
- if (dbConn != null)
+ 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;
+ }
+ }
+
+
+ /// <summary>
+ /// Generate keys and insert them to key store.
+ /// </summary>
+ /// <param name="n"></param>
+ private void GenerateKeys(int n)
+ {
+ lock (LockObj)
{
- dbConn.Close();
- dbConn = null;
+ 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();
}
}
{
var res = dbConn.Insert(new KeyStorageItem()
{
- KeyID = keyPair.KeyID.hashBytes,
- PublicKey = keyPair.PublicBytes,
- PrivateKey = keyPair.SecretBytes,
+ KeyID = keyPair.KeyID,
+ PublicKey = keyPair.PubKey,
+ PrivateKey = keyPair,
IsCompressed = keyPair.IsCompressed,
IsUsed = true,
nTime = Interop.GetTime()
return true;
}
+ /// <summary>
+ /// Check existance of item with provided KeyID
+ /// </summary>
+ /// <param name="scriptID">Hash160 of public key.</param>
+ /// <returns>Checking result</returns>
public bool HaveKey(CKeyID keyID)
{
- var QueryCount = dbConn.Query<CountQuery>("select count([ItemID]) as [Count] from [KeyStorage] where [KeyID] = ?", keyID.hashBytes);
+ var QueryCount = dbConn.Query<NumQuery>("select count([ItemID]) from [KeyStorage] where [KeyID] = ?", (byte[])keyID);
- return QueryCount.First().Count == 1;
+ return QueryCount.First().Num == 1;
}
/// <summary>
/// <returns>Result</returns>
public bool GetKey(CKeyID keyID, out CKeyPair keyPair)
{
- var QueryGet = dbConn.Query<KeyStorageItem>("select * from [KeyStorage] where [KeyID] = ?", keyID.hashBytes);
+ var QueryGet = dbConn.Query<KeyStorageItem>("select * from [KeyStorage] where [KeyID] = ?", (byte[])keyID);
if (QueryGet.Count() == 1)
{
return false;
}
+ /// <summary>
+ /// Add redeem script to script store.
+ /// </summary>
+ /// <param name="script">CScript instance</param>
+ /// <returns>Result</returns>
public bool AddScript(CScript script)
{
lock (LockObj)
{
var res = dbConn.Insert(new ScriptStorageItem()
{
- ScriptID = script.ScriptID.hashBytes,
- ScriptCode = script.Bytes
+ ScriptID = script.ScriptID,
+ ScriptCode = script
});
if (res == 0)
return true;
}
+ /// <summary>
+ /// Check existance of item with provided ScriptID
+ /// </summary>
+ /// <param name="scriptID">Hash160 of script code.</param>
+ /// <returns>Checking result</returns>
public bool HaveScript(CScriptID scriptID)
{
- var QueryGet = dbConn.Query<CountQuery>("select count([ItemID]) from [ScriptStorage] where [ScriptID] = ?", scriptID.hashBytes);
+ var QueryGet = dbConn.Query<NumQuery>("select count([ItemID]) from [ScriptStorage] where [ScriptID] = ?", (byte[])scriptID);
- return QueryGet.First().Count == 1;
+ return QueryGet.First().Num == 1;
}
+ /// <summary>
+ /// Get redeem script from database.
+ /// </summary>
+ /// <param name="scriptID">Script ID, evaluated as Hash160(script code).</param>
+ /// <param name="script">Instance of CScript</param>
+ /// <returns>Result</returns>
public bool GetScript(CScriptID scriptID, out CScript script)
{
- var QueryGet = dbConn.Query<ScriptStorageItem>("select * from [ScriptStorage] where [ScriptID] = ?", scriptID.hashBytes);
+ var QueryGet = dbConn.Query<ScriptStorageItem>("select * from [ScriptStorage] where [ScriptID] = ?", (byte[])scriptID);
if (QueryGet.Count() == 1)
{
return false;
}
+
+ /// <summary>
+ /// SQLite return type for ReserveKey
+ /// </summary>
+ class ReservedKey
+ {
+ public int ItemId { get; set; }
+ public byte[] KeyID { get; set; }
+ }
+
+ /// <summary>
+ /// Reserve key from a list of unused keys.
+ /// </summary>
+ /// <param name="nKeyIndex">Internal index of key</param>
+ /// <returns>CKeyID instance</returns>
+ public CKeyID SelectKey(out int nKeyIndex)
+ {
+ var QueryGet = dbConn.Query<ReservedKey>("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);
+ }
+ }
+
+ /// <summary>
+ /// Mark key as used.
+ /// </summary>
+ /// <param name="nIndex">Internal index.</param>
+ public void MarkUsed(int nIndex)
+ {
+ lock (LockObj)
+ {
+ dbConn.Execute("update [KeyStorage] set [IsUsed] = true where [ItemId] = ?", nIndex);
+ }
+ }
+
+ /// <summary>
+ /// Mark key as unused.
+ /// </summary>
+ /// <param name="nIndex">Internal index.</param>
+ public void MarkUnused(int nIndex)
+ {
+ lock (LockObj)
+ {
+ dbConn.Execute("update [KeyStorage] set [IsUsed] = false where [ItemId] = ?", nIndex);
+ }
+ }
+ /// <summary>
+ /// Regenerate all unused keys.
+ /// </summary>
+ public void ResetPool()
+ {
+ lock (LockObj)
+ {
+ dbConn.Execute("delete from [KeyStorage] where not [IsUsed]");
+ GenerateKeys(nKeyPoolSize);
+ }
+ }
+
+ /// <summary>
+ /// Timestamp of oldest item in keystore.
+ /// </summary>
+ public int OldestTime
+ {
+ get
+ {
+ var QueryTime = dbConn.Query<NumQuery>("select [nTime] from [KeyStorage] where not [IsUsed] order by [ItemId] asc limit 1");
+
+ return QueryTime.First().Num;
+ }
+ }
}
}