/**
* 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;
}
}
}
}