2736d68730f0cc81db80ce53bbe2cb07e73fb8f5
[NovacoinLibrary.git] / Novacoin / CKeyStore.cs
1 \feff/**
2  *  Novacoin classes library
3  *  Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com)
4
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU Affero General Public License as
7  *  published by the Free Software Foundation, either version 3 of the
8  *  License, or (at your option) any later version.
9
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Affero General Public License for more details.
14
15  *  You should have received a copy of the GNU Affero General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19
20 using SQLite.Net;
21 using SQLite.Net.Attributes;
22 using SQLite.Net.Interop;
23 using SQLite.Net.Platform.Generic;
24 using System.IO;
25 using System.Linq;
26
27 namespace Novacoin
28 {
29     /// <summary>
30     /// Key storage item and structure
31     /// </summary>
32     [Table("KeyStorage")]
33     class KeyStorageItem
34     {
35         /// <summary>
36         /// Key item number
37         /// </summary>
38         [PrimaryKey, AutoIncrement]
39         public int ItemId { get; set; }
40
41         /// <summary>
42         /// Hash160 of pubkey
43         /// </summary>
44         public byte[] KeyID { get; set; }
45
46         /// <summary>
47         /// Public key 
48         /// </summary>
49         public byte[] PublicKey { get; set; }
50
51         /// <summary>
52         /// Private key 
53         /// </summary>
54         public byte[] PrivateKey { get; set; }
55
56         /// <summary>
57         /// Compressed key flag
58         /// </summary>
59         public bool IsCompressed { get; set; }
60
61         /// <summary>
62         /// Is this key a part of KeyPool?
63         /// </summary>
64         [Indexed]
65         public bool IsUsed { get; set; }
66
67         /// <summary>
68         /// Item creation time
69         /// </summary>
70         [Indexed]
71         public int nTime { get; set; }
72     }
73
74     /// <summary>
75     /// Script storage item and structure
76     /// </summary>
77     [Table("ScriptStorage")]
78     class ScriptStorageItem
79     {
80         /// <summary>
81         /// Script item number
82         /// </summary>
83         [PrimaryKey, AutoIncrement]
84         public int ItemId { get; set; }
85
86         /// <summary>
87         /// Hash160 of script 
88         /// </summary>
89         public byte[] ScriptID { get; set; }
90
91         /// <summary>
92         /// Script code bytes 
93         /// </summary>
94         public byte[] ScriptCode { get; set; }
95     }
96
97     /// <summary>
98     /// select count(...) as Count from ... where ...
99     /// select number from ... where ...
100     /// </summary>
101     class NumQuery
102     {
103         public int Num { get; set; }
104     }
105
106     /// <summary>
107     /// Key storage
108     /// </summary>
109     public class CKeyStore
110     {
111         private object LockObj = new object();
112         private SQLiteConnection dbConn = null;
113
114         private int nKeyPoolSize = 100;
115
116         /// <summary>
117         /// Initialize new instance of key store.
118         /// </summary>
119         /// <param name="strDatabasePath">Path to database file.</param>
120         /// <param name="KeyPoolSize">Number of reserved keys.</param>
121         public CKeyStore(string strDatabasePath="KeyStore.db", int KeyPoolSize = 100)
122         {
123             bool firstInit = !File.Exists(strDatabasePath);
124
125             dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), strDatabasePath);
126             nKeyPoolSize = KeyPoolSize;
127
128             if (firstInit)
129             {
130                 lock(LockObj)
131                 {
132                     dbConn.CreateTable<KeyStorageItem>(CreateFlags.AutoIncPK);
133                     dbConn.CreateTable<ScriptStorageItem>(CreateFlags.AutoIncPK);
134
135                     GenerateKeys(nKeyPoolSize);
136                 }
137             }
138         }
139
140         ~CKeyStore()
141         {
142             if (dbConn != null)
143             {
144                 dbConn.Close();
145                 dbConn = null;
146             }
147         }
148
149         /// <summary>
150         /// Generate keys and insert them to key store.
151         /// </summary>
152         /// <param name="n"></param>
153         private void GenerateKeys(int n)
154         {
155             lock (LockObj)
156             {
157                 dbConn.BeginTransaction();
158
159                 // Generate keys
160                 for (int i = 0; i < n; i++)
161                 {
162                     var keyPair = new CKeyPair();
163
164                     var res = dbConn.Insert(new KeyStorageItem()
165                     {
166                         KeyID = keyPair.KeyID.hashBytes,
167                         PublicKey = keyPair.PublicBytes,
168                         PrivateKey = keyPair.SecretBytes,
169                         IsCompressed = keyPair.IsCompressed,
170                         IsUsed = false,
171                         nTime = Interop.GetTime()
172                     });
173
174                     // TODO: Additional initialization
175                 }
176
177                 dbConn.Commit();
178             }
179         }
180
181         /// <summary>
182         /// Insert key data into table
183         /// </summary>
184         /// <param name="keyPair">CKeyPair instance</param>
185         /// <returns>Result</returns>
186         public bool AddKey(CKeyPair keyPair)
187         {
188             lock(LockObj)
189             {
190                 var res = dbConn.Insert(new KeyStorageItem()
191                 {
192                     KeyID = keyPair.KeyID.hashBytes,
193                     PublicKey = keyPair.PublicBytes,
194                     PrivateKey = keyPair.SecretBytes,
195                     IsCompressed = keyPair.IsCompressed,
196                     IsUsed = true,
197                     nTime = Interop.GetTime()
198                 });
199
200                 if (res == 0)
201                 {
202                     return false;
203                 }
204             }
205
206             return true;
207         }
208
209         /// <summary>
210         /// Check existance of item with provided KeyID
211         /// </summary>
212         /// <param name="scriptID">Hash160 of public key.</param>
213         /// <returns>Checking result</returns>
214         public bool HaveKey(CKeyID keyID)
215         {
216             var QueryCount = dbConn.Query<NumQuery>("select count([ItemID]) from [KeyStorage] where [KeyID] = ?", keyID.hashBytes);
217
218             return QueryCount.First().Num == 1;
219         }
220
221         /// <summary>
222         /// Get the key pair object.
223         /// </summary>
224         /// <param name="keyID">Hash of public key.</param>
225         /// <param name="keyPair">Instance of CKeyPair or null.</param>
226         /// <returns>Result</returns>
227         public bool GetKey(CKeyID keyID, out CKeyPair keyPair)
228         {
229             var QueryGet = dbConn.Query<KeyStorageItem>("select * from [KeyStorage] where [KeyID] = ?", keyID.hashBytes);
230
231             if (QueryGet.Count() == 1)
232             {
233                 keyPair = new CKeyPair(QueryGet.First().PrivateKey);
234                 return true;
235             }
236
237             keyPair = null;
238             return false;
239         }
240
241         /// <summary>
242         /// Add redeem script to script store.
243         /// </summary>
244         /// <param name="script">CScript instance</param>
245         /// <returns>Result</returns>
246         public bool AddScript(CScript script)
247         {
248             lock (LockObj)
249             {
250                 var res = dbConn.Insert(new ScriptStorageItem()
251                 {
252                     ScriptID = script.ScriptID.hashBytes,
253                     ScriptCode = script.Bytes
254                 });
255
256                 if (res == 0)
257                 {
258                     return false;
259                 }
260             }
261
262             return true;
263         }
264
265         /// <summary>
266         /// Check existance of item with provided ScriptID
267         /// </summary>
268         /// <param name="scriptID">Hash160 of script code.</param>
269         /// <returns>Checking result</returns>
270         public bool HaveScript(CScriptID scriptID)
271         {
272             var QueryGet = dbConn.Query<NumQuery>("select count([ItemID]) from [ScriptStorage] where [ScriptID] = ?", scriptID.hashBytes);
273
274             return QueryGet.First().Num == 1;
275         }
276
277         /// <summary>
278         /// Get redeem script from database.
279         /// </summary>
280         /// <param name="scriptID">Script ID, evaluated as Hash160(script code).</param>
281         /// <param name="script">Instance of CScript</param>
282         /// <returns>Result</returns>
283         public bool GetScript(CScriptID scriptID, out CScript script)
284         {
285             var QueryGet = dbConn.Query<ScriptStorageItem>("select * from [ScriptStorage] where [ScriptID] = ?", scriptID.hashBytes);
286
287             if (QueryGet.Count() == 1)
288             {
289                 script = new CScript(QueryGet.First().ScriptCode);
290                 return true;
291             }
292
293             script = null;
294             return false;
295         }
296
297   
298         /// <summary>
299         /// SQLite return type for ReserveKey
300         /// </summary>                      
301         class ReservedKey
302         {
303             public int ItemId { get; set; }
304             public byte[] KeyID { get; set; }
305         }
306
307         /// <summary>
308         /// Reserve key from a list of unused keys.
309         /// </summary>
310         /// <param name="nKeyIndex">Internal index of key</param>
311         /// <returns>CKeyID instance</returns>
312         public CKeyID SelectKey(out int nKeyIndex)
313         {
314             var QueryGet = dbConn.Query<ReservedKey>("select ItemId, KeyID from [KeyStorage] where not IsUsed order by [nTime] asc limit 1");
315
316             if (QueryGet.Count() == 1)
317             {
318                 var res = QueryGet.First();
319
320                 nKeyIndex = res.ItemId;
321
322                 return new CKeyID(res.KeyID);
323             }
324             else
325             {
326                 // Generate new keys in case if keypool is exhausted.
327
328                 GenerateKeys(nKeyPoolSize);
329
330                 return SelectKey(out nKeyIndex);
331             }
332         }
333
334         /// <summary>
335         /// Mark key as used.
336         /// </summary>
337         /// <param name="nIndex">Internal index.</param>
338         public void MarkUsed(int nIndex)
339         {
340             lock (LockObj)
341             {
342                 dbConn.Execute("update [KeyStorage] set [IsUsed] = true where [ItemId] = ?", nIndex);
343             }
344         }
345
346         /// <summary>
347         /// Mark key as unused.
348         /// </summary>
349         /// <param name="nIndex">Internal index.</param>
350         public void MarkUnused(int nIndex)
351         {
352             lock (LockObj)
353             {
354                 dbConn.Execute("update [KeyStorage] set [IsUsed] = false where [ItemId] = ?", nIndex);
355             }
356         }
357
358         /// <summary>
359         /// Regenerate all unused keys. 
360         /// </summary>
361         public void ResetPool()
362         {
363             lock (LockObj)
364             {
365                 dbConn.Execute("delete from [KeyStorage] where [IsUsed] = false");
366                 GenerateKeys(nKeyPoolSize);
367             }
368         }
369
370         /// <summary>
371         /// Timestamp of oldest item in keystore.
372         /// </summary>
373         public int OldestTime
374         {
375             get
376             {
377                 var QueryTime = dbConn.Query<NumQuery>("select [nTime] from [KeyStorage] where not [IsUsed] order by [ItemId] asc limit 1");
378
379                 return QueryTime.First().Num;
380             }
381         }
382     }
383 }