TrimArray() simplification
[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;
25 using System.IO;
26 using System.Linq;
27
28 namespace Novacoin
29 {
30     /// <summary>
31     /// Key storage item and structure
32     /// </summary>
33     [Table("KeyStorage")]
34     class KeyStorageItem
35     {
36         /// <summary>
37         /// Key item number
38         /// </summary>
39         [PrimaryKey, AutoIncrement]
40         public int ItemId { get; set; }
41
42         /// <summary>
43         /// Hash160 of pubkey
44         /// </summary>
45         public byte[] KeyID { get; set; }
46
47         /// <summary>
48         /// Public key 
49         /// </summary>
50         public byte[] PublicKey { get; set; }
51
52         /// <summary>
53         /// Private key 
54         /// </summary>
55         public byte[] PrivateKey { get; set; }
56
57         /// <summary>
58         /// Compressed key flag
59         /// </summary>
60         public bool IsCompressed { get; set; }
61
62         /// <summary>
63         /// Is this key a part of KeyPool?
64         /// </summary>
65         [Indexed]
66         public bool IsUsed { get; set; }
67
68         /// <summary>
69         /// Item creation time
70         /// </summary>
71         [Indexed]
72         public uint nTime { get; set; }
73     }
74
75     /// <summary>
76     /// Script storage item and structure
77     /// </summary>
78     [Table("ScriptStorage")]
79     class ScriptStorageItem
80     {
81         /// <summary>
82         /// Script item number
83         /// </summary>
84         [PrimaryKey, AutoIncrement]
85         public int ItemId { get; set; }
86
87         /// <summary>
88         /// Hash160 of script 
89         /// </summary>
90         public byte[] ScriptID { get; set; }
91
92         /// <summary>
93         /// Script code bytes 
94         /// </summary>
95         public byte[] ScriptCode { get; set; }
96     }
97
98     /// <summary>
99     /// select count(...) as Count from ... where ...
100     /// select number from ... where ...
101     /// </summary>
102     class NumQuery
103     {
104         public int Num { get; set; }
105     }
106
107     /// <summary>
108     /// Key storage
109     /// </summary>
110     public class CKeyStore : IDisposable
111     {
112         private bool disposed = false;
113         private object LockObj = new object();
114         private SQLiteConnection dbConn = null;
115         private int nKeyPoolSize = 100;
116
117         /// <summary>
118         /// Initialize new instance of key store.
119         /// </summary>
120         /// <param name="strDatabasePath">Path to database file.</param>
121         /// <param name="KeyPoolSize">Number of reserved keys.</param>
122         public CKeyStore(string strDatabasePath="KeyStore.db", int KeyPoolSize = 100)
123         {
124             bool firstInit = !File.Exists(strDatabasePath);
125
126             dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), strDatabasePath);
127             nKeyPoolSize = KeyPoolSize;
128
129             if (firstInit)
130             {
131                 lock(LockObj)
132                 {
133                     dbConn.CreateTable<KeyStorageItem>(CreateFlags.AutoIncPK);
134                     dbConn.CreateTable<ScriptStorageItem>(CreateFlags.AutoIncPK);
135
136                     GenerateKeys(nKeyPoolSize);
137                 }
138             }
139         }
140
141         ~CKeyStore()
142         {
143             Dispose(false);
144         }
145
146         public void Dispose()
147         {
148             Dispose(true);
149             GC.SuppressFinalize(this);
150         }
151
152         protected virtual void Dispose(bool disposing)
153         {
154             if (!disposed)
155             {
156                 if (disposing)
157                 {
158                     // Free other state (managed objects).
159                 }
160
161                 if (dbConn != null)
162                 {
163                     dbConn.Close();
164                     dbConn = null;
165                 }
166
167                 disposed = true;
168             }
169         }
170
171
172         /// <summary>
173         /// Generate keys and insert them to key store.
174         /// </summary>
175         /// <param name="n"></param>
176         private void GenerateKeys(int n)
177         {
178             lock (LockObj)
179             {
180                 dbConn.BeginTransaction();
181
182                 // Generate keys
183                 for (int i = 0; i < n; i++)
184                 {
185                     var keyPair = new CKeyPair();
186
187                     var res = dbConn.Insert(new KeyStorageItem()
188                     {
189                         KeyID = keyPair.KeyID,
190                         PublicKey = keyPair.PubKey,
191                         PrivateKey = keyPair,
192                         IsCompressed = keyPair.IsCompressed,
193                         IsUsed = false,
194                         nTime = Interop.GetTime()
195                     });
196
197                     // TODO: Additional initialization
198                 }
199
200                 dbConn.Commit();
201             }
202         }
203
204         /// <summary>
205         /// Insert key data into table
206         /// </summary>
207         /// <param name="keyPair">CKeyPair instance</param>
208         /// <returns>Result</returns>
209         public bool AddKey(CKeyPair keyPair)
210         {
211             lock(LockObj)
212             {
213                 var res = dbConn.Insert(new KeyStorageItem()
214                 {
215                     KeyID = keyPair.KeyID,
216                     PublicKey = keyPair.PubKey,
217                     PrivateKey = keyPair,
218                     IsCompressed = keyPair.IsCompressed,
219                     IsUsed = true,
220                     nTime = Interop.GetTime()
221                 });
222
223                 if (res == 0)
224                 {
225                     return false;
226                 }
227             }
228
229             return true;
230         }
231
232         /// <summary>
233         /// Check existance of item with provided KeyID
234         /// </summary>
235         /// <param name="scriptID">Hash160 of public key.</param>
236         /// <returns>Checking result</returns>
237         public bool HaveKey(CKeyID keyID)
238         {
239             var QueryCount = dbConn.Query<NumQuery>("select count([ItemID]) from [KeyStorage] where [KeyID] = ?", (byte[])keyID);
240
241             return QueryCount.First().Num == 1;
242         }
243
244         /// <summary>
245         /// Get the key pair object.
246         /// </summary>
247         /// <param name="keyID">Hash of public key.</param>
248         /// <param name="keyPair">Instance of CKeyPair or null.</param>
249         /// <returns>Result</returns>
250         public bool GetKey(CKeyID keyID, out CKeyPair keyPair)
251         {
252             var QueryGet = dbConn.Query<KeyStorageItem>("select * from [KeyStorage] where [KeyID] = ?", (byte[])keyID);
253
254             if (QueryGet.Count() == 1)
255             {
256                 keyPair = new CKeyPair(QueryGet.First().PrivateKey);
257                 return true;
258             }
259
260             keyPair = null;
261             return false;
262         }
263
264         /// <summary>
265         /// Add redeem script to script store.
266         /// </summary>
267         /// <param name="script">CScript instance</param>
268         /// <returns>Result</returns>
269         public bool AddScript(CScript script)
270         {
271             lock (LockObj)
272             {
273                 var res = dbConn.Insert(new ScriptStorageItem()
274                 {
275                     ScriptID = script.ScriptID,
276                     ScriptCode = script
277                 });
278
279                 if (res == 0)
280                 {
281                     return false;
282                 }
283             }
284
285             return true;
286         }
287
288         /// <summary>
289         /// Check existance of item with provided ScriptID
290         /// </summary>
291         /// <param name="scriptID">Hash160 of script code.</param>
292         /// <returns>Checking result</returns>
293         public bool HaveScript(CScriptID scriptID)
294         {
295             var QueryGet = dbConn.Query<NumQuery>("select count([ItemID]) from [ScriptStorage] where [ScriptID] = ?", (byte[])scriptID);
296
297             return QueryGet.First().Num == 1;
298         }
299
300         /// <summary>
301         /// Get redeem script from database.
302         /// </summary>
303         /// <param name="scriptID">Script ID, evaluated as Hash160(script code).</param>
304         /// <param name="script">Instance of CScript</param>
305         /// <returns>Result</returns>
306         public bool GetScript(CScriptID scriptID, out CScript script)
307         {
308             var QueryGet = dbConn.Query<ScriptStorageItem>("select * from [ScriptStorage] where [ScriptID] = ?", (byte[])scriptID);
309
310             if (QueryGet.Count() == 1)
311             {
312                 script = new CScript(QueryGet.First().ScriptCode);
313                 return true;
314             }
315
316             script = null;
317             return false;
318         }
319
320   
321         /// <summary>
322         /// SQLite return type for ReserveKey
323         /// </summary>                      
324         class ReservedKey
325         {
326             public int ItemId { get; set; }
327             public byte[] KeyID { get; set; }
328         }
329
330         /// <summary>
331         /// Reserve key from a list of unused keys.
332         /// </summary>
333         /// <param name="nKeyIndex">Internal index of key</param>
334         /// <returns>CKeyID instance</returns>
335         public CKeyID SelectKey(out int nKeyIndex)
336         {
337             var QueryGet = dbConn.Query<ReservedKey>("select ItemId, KeyID from [KeyStorage] where not [IsUsed] order by [nTime] asc limit 1");
338
339             if (QueryGet.Count() == 1)
340             {
341                 var res = QueryGet.First();
342
343                 nKeyIndex = res.ItemId;
344
345                 return new CKeyID(res.KeyID);
346             }
347             else
348             {
349                 // Generate new keys in case if keypool is exhausted.
350
351                 GenerateKeys(nKeyPoolSize);
352
353                 return SelectKey(out nKeyIndex);
354             }
355         }
356
357         /// <summary>
358         /// Mark key as used.
359         /// </summary>
360         /// <param name="nIndex">Internal index.</param>
361         public void MarkUsed(int nIndex)
362         {
363             lock (LockObj)
364             {
365                 dbConn.Execute("update [KeyStorage] set [IsUsed] = true where [ItemId] = ?", nIndex);
366             }
367         }
368
369         /// <summary>
370         /// Mark key as unused.
371         /// </summary>
372         /// <param name="nIndex">Internal index.</param>
373         public void MarkUnused(int nIndex)
374         {
375             lock (LockObj)
376             {
377                 dbConn.Execute("update [KeyStorage] set [IsUsed] = false where [ItemId] = ?", nIndex);
378             }
379         }
380
381         /// <summary>
382         /// Regenerate all unused keys. 
383         /// </summary>
384         public void ResetPool()
385         {
386             lock (LockObj)
387             {
388                 dbConn.Execute("delete from [KeyStorage] where not [IsUsed]");
389                 GenerateKeys(nKeyPoolSize);
390             }
391         }
392
393         /// <summary>
394         /// Timestamp of oldest item in keystore.
395         /// </summary>
396         public int OldestTime
397         {
398             get
399             {
400                 var QueryTime = dbConn.Query<NumQuery>("select [nTime] from [KeyStorage] where not [IsUsed] order by [ItemId] asc limit 1");
401
402                 return QueryTime.First().Num;
403             }
404         }
405     }
406 }