upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-08-01 17:53:17 -0300
committerGitHub <noreply@github.com>2026-08-01 17:53:17 -0300
commitce59d8c72c667ce638bd12f37db26d8b3408a5d2 (patch)
tree95c62a0d0cc0a21bc3349264ce0a3c949deb32c3 /src
parent593cf346dbce0a8ce46a13a73a4f78ddd044ab52 (diff)
wallet: derive the key pool from a seed, so a phrase could bring it back (#93)
Adds BIP32 derivation behind the wallet key pool, with no user-facing way to create a seed yet. wallet.dat gains hdmaster/hdchaincode/hdnext; the mnemonic is never stored. TopUpKeyPool derives m/n' when a seed exists and falls back to a random key, with a log line, if derivation fails. New -selftest=wallet-hd, wired into make tests on both platforms.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile3
-rw-r--r--src/db.cpp12
-rw-r--r--src/db.h27
-rw-r--r--src/key.h34
-rw-r--r--src/main.cpp112
-rw-r--r--src/main.h28
-rw-r--r--src/main_gui.cpp18
-rw-r--r--src/makefile.mingw3
-rw-r--r--src/selftest.cpp149
9 files changed, 382 insertions, 4 deletions
diff --git a/src/Makefile b/src/Makefile
index d4b1837..bbdc9e2 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -50,7 +50,7 @@ IMGUI_OBJS = obj/imgui.o obj/imgui_draw.o obj/imgui_tables.o \
50NODE_OBJS = obj/util.o obj/script.o obj/db.o obj/net.o obj/main.o \ 50NODE_OBJS = obj/util.o obj/script.o obj/db.o obj/net.o obj/main.o \
51 obj/market.o obj/sha.o obj/nostr.o obj/randomx_pow.o \ 51 obj/market.o obj/sha.o obj/nostr.o obj/randomx_pow.o \
52 obj/btfaddr.o obj/btfchan.o obj/btfrv.o obj/btftunnel.o obj/rpc.o \ 52 obj/btfaddr.o obj/btfchan.o obj/btfrv.o obj/btftunnel.o obj/rpc.o \
53 obj/selftest.o 53 obj/selftest.o obj/bip32.o
54 54
55GUI_OBJS = obj/gui.o obj/main_gui.o 55GUI_OBJS = obj/gui.o obj/main_gui.o
56 56
@@ -131,3 +131,4 @@ test_bip32: test_bip32.cpp bip32.cpp bip32.h bip39_english.h
131tests: test_bip32 bitflash-node 131tests: test_bip32 bitflash-node
132 ./test_bip32 132 ./test_bip32
133 ./bitflash-node -selftest=wallet-keypool 133 ./bitflash-node -selftest=wallet-keypool
134 ./bitflash-node -selftest=wallet-hd
diff --git a/src/db.cpp b/src/db.cpp
index d482c5a..294920b 100644
--- a/src/db.cpp
+++ b/src/db.cpp
@@ -639,6 +639,18 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet)
639 { 639 {
640 ssValue >> vchDefaultKeyRet; 640 ssValue >> vchDefaultKeyRet;
641 } 641 }
642 else if (strType == "hdmaster")
643 {
644 ssValue >> vchHDMaster;
645 }
646 else if (strType == "hdchaincode")
647 {
648 ssValue >> vchHDChainCode;
649 }
650 else if (strType == "hdnext")
651 {
652 ssValue >> nHDNext;
653 }
642 else if (strType == "pool") 654 else if (strType == "pool")
643 { 655 {
644 int64 nIndex; 656 int64 nIndex;
diff --git a/src/db.h b/src/db.h
index f51047e..01b8583 100644
--- a/src/db.h
+++ b/src/db.h
@@ -386,6 +386,33 @@ public:
386 return Write(string("defaultkey"), vchPubKey); 386 return Write(string("defaultkey"), vchPubKey);
387 } 387 }
388 388
389 // --- Deterministic (BIP32) seed ---------------------------------------
390 //
391 // Stored as the master key and chain code rather than the mnemonic: the
392 // twelve words are the user's to write down, and keeping a copy of them in
393 // the file they are meant to protect defeats the point of having them.
394 //
395 // "hdnext" is the next child index to derive, so the sequence a restore
396 // reproduces is the same one this wallet handed out.
397 bool ReadHDMaster(vector<unsigned char>& vchMasterRet, vector<unsigned char>& vchChainCodeRet)
398 {
399 vchMasterRet.clear();
400 vchChainCodeRet.clear();
401 return Read(string("hdmaster"), vchMasterRet)
402 && Read(string("hdchaincode"), vchChainCodeRet);
403 }
404
405 bool WriteHDMaster(const vector<unsigned char>& vchMaster, const vector<unsigned char>& vchChainCode)
406 {
407 return Write(string("hdmaster"), vchMaster)
408 && Write(string("hdchaincode"), vchChainCode);
409 }
410
411 bool WriteHDNext(unsigned int nNext)
412 {
413 return Write(string("hdnext"), nNext);
414 }
415
389 // Keys generated ahead of time and not yet handed out. The private key of 416 // Keys generated ahead of time and not yet handed out. The private key of
390 // each is already stored under its own "key" record by AddKey; a "pool" 417 // each is already stored under its own "key" record by AddKey; a "pool"
391 // record only says that this one is still unspoken for. 418 // record only says that this one is still unspoken for.
diff --git a/src/key.h b/src/key.h
index 26e1ae2..6cc2a24 100644
--- a/src/key.h
+++ b/src/key.h
@@ -86,6 +86,40 @@ public:
86 return true; 86 return true;
87 } 87 }
88 88
89 // Load a raw 32-byte scalar, the form BIP32 derivation produces.
90 //
91 // SetPrivKey above takes DER and cannot be used for this: handing it a
92 // scalar makes d2i_ECPrivateKey fail, and if the caller ignores the return
93 // value -- as one attempt at this did -- the key is left with no material
94 // at all and the address it produces has no private half. Coins paid there
95 // are gone. Hence a separate entry point rather than a looser SetPrivKey.
96 //
97 // The public half is computed here rather than read from anywhere:
98 // pub = G * secret.
99 bool SetSecret(const std::vector<unsigned char>& vchSecret)
100 {
101 if (vchSecret.size() != 32)
102 return false;
103
104 bool fOk = false;
105 BIGNUM* bn = BN_bin2bn(&vchSecret[0], 32, NULL);
106 EC_POINT* pubkey = NULL;
107 if (bn && EC_KEY_set_private_key(pkey, bn))
108 {
109 const EC_GROUP* group = EC_KEY_get0_group(pkey);
110 pubkey = EC_POINT_new(group);
111 if (pubkey &&
112 EC_POINT_mul(group, pubkey, bn, NULL, NULL, NULL) &&
113 EC_KEY_set_public_key(pkey, pubkey))
114 fOk = true;
115 }
116 if (pubkey)
117 EC_POINT_free(pubkey);
118 if (bn)
119 BN_clear_free(bn); // it held the secret
120 return fOk;
121 }
122
89 CPrivKey GetPrivKey() const 123 CPrivKey GetPrivKey() const
90 { 124 {
91 unsigned int nSize = i2d_ECPrivateKey(pkey, NULL); 125 unsigned int nSize = i2d_ECPrivateKey(pkey, NULL);
diff --git a/src/main.cpp b/src/main.cpp
index 82a98d2..e5754e6 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -14,6 +14,7 @@
14#include <atomic> 14#include <atomic>
15#include <mutex> 15#include <mutex>
16#include <thread> 16#include <thread>
17#include "bip32.h"
17#include "btfaddr.h" 18#include "btfaddr.h"
18#include "btftunnel.h" 19#include "btftunnel.h"
19#ifndef _WIN32 20#ifndef _WIN32
@@ -178,6 +179,66 @@ vector<unsigned char> GenerateNewKey()
178CCriticalSection cs_keyPool; 179CCriticalSection cs_keyPool;
179map<int64, vector<unsigned char> > mapKeyPool; 180map<int64, vector<unsigned char> > mapKeyPool;
180 181
182vector<unsigned char> vchHDMaster;
183vector<unsigned char> vchHDChainCode;
184unsigned int nHDNext = 0;
185
186bool DeriveHDKey(unsigned int nIndex, CKey& keyRet, string& strErrorRet)
187{
188 if (!HaveHDSeed())
189 {
190 strErrorRet = "no seed";
191 return false;
192 }
193
194 bitflash::BIP32PrivateNode parent;
195 parent.privateKey = vchHDMaster;
196 parent.chainCode = vchHDChainCode;
197
198 bitflash::BIP32PrivateNode child;
199 if (!bitflash::BIP32DeriveHardenedChild(parent, nIndex, child, strErrorRet))
200 return false;
201
202 if (!keyRet.SetSecret(child.privateKey))
203 {
204 strErrorRet = "derived scalar is not a usable key";
205 return false;
206 }
207 return true;
208}
209
210bool SetHDSeedFromMnemonic(const string& strMnemonic, string& strErrorRet)
211{
212 string strNormalized;
213 if (!bitflash::BIP39ValidateMnemonic(strMnemonic, strNormalized, strErrorRet))
214 return false;
215
216 vector<unsigned char> vchSeed;
217 if (!bitflash::BIP39MnemonicToSeed(strNormalized, "", vchSeed, strErrorRet))
218 return false;
219
220 bitflash::BIP32PrivateNode master;
221 if (!bitflash::BIP32MasterFromSeed(vchSeed, master, strErrorRet))
222 return false;
223
224 CRITICAL_BLOCK(cs_keyPool)
225 {
226 // Written before the globals change, so a failed write leaves the
227 // wallet on the seed it already had rather than on one that exists
228 // only in memory.
229 if (!CWalletDB().WriteHDMaster(master.privateKey, master.chainCode) ||
230 !CWalletDB().WriteHDNext(0))
231 {
232 strErrorRet = "could not write the seed to wallet.dat";
233 return false;
234 }
235 vchHDMaster = master.privateKey;
236 vchHDChainCode = master.chainCode;
237 nHDNext = 0;
238 }
239 return true;
240}
241
181void TopUpKeyPool() 242void TopUpKeyPool()
182{ 243{
183 CRITICAL_BLOCK(cs_keyPool) 244 CRITICAL_BLOCK(cs_keyPool)
@@ -191,7 +252,30 @@ void TopUpKeyPool()
191 nIndex = (--mapKeyPool.end())->first + 1; 252 nIndex = (--mapKeyPool.end())->first + 1;
192 253
193 CKey key; 254 CKey key;
194 key.MakeNewKey(); 255 if (HaveHDSeed())
256 {
257 // Derived, so a recovery phrase can reproduce this exact key.
258 // A derivation that fails is not a reason to stop filling the
259 // pool -- fall back to a random key, which is what a wallet
260 // without a seed uses anyway. It will not come back from the
261 // phrase, and that is better than a node that cannot mine.
262 string strError;
263 if (DeriveHDKey(nHDNext, key, strError))
264 {
265 nHDNext++;
266 CWalletDB().WriteHDNext(nHDNext);
267 }
268 else
269 {
270 printf("TopUpKeyPool() : derivation at index %u failed (%s), "
271 "falling back to a random key\n", nHDNext, strError.c_str());
272 key.MakeNewKey();
273 }
274 }
275 else
276 {
277 key.MakeNewKey();
278 }
195 // AddKey writes the private key. Order matters: the key is in 279 // AddKey writes the private key. Order matters: the key is in
196 // wallet.dat before anything can point at it, so a crash between 280 // wallet.dat before anything can point at it, so a crash between
197 // the two writes costs an unused key, never a usable one. 281 // the two writes costs an unused key, never a usable one.
@@ -310,6 +394,32 @@ bool AddToWalletIfMine(const CTransaction& tx, const CBlock* pblock)
310 return true; 394 return true;
311} 395}
312 396
397bool CanScanWalletTransactions(string& strErrorRet)
398{
399 strErrorRet.clear();
400
401 if (pindexGenesisBlock == NULL || pindexBest == NULL || hashBestChain == 0 || nBestHeight < 0)
402 {
403 strErrorRet = "The block index is not loaded, so the wallet scan cannot "
404 "prove whether imported or restored keys own coins.";
405 return false;
406 }
407
408 if (nBestHeight == 0)
409 {
410 strErrorRet = "The local chain is only at height 0. A wallet scan would "
411 "inspect only the genesis block and could incorrectly "
412 "report zero transactions. Start the node and let it sync "
413 "before scanning. If this happened after copying a data "
414 "directory, keep wallet.dat safe and rebuild or re-download "
415 "the block data instead of trusting the copied database/ "
416 "environment.";
417 return false;
418 }
419
420 return true;
421}
422
313int ScanForWalletTransactions(CBlockIndex* pindexStart) 423int ScanForWalletTransactions(CBlockIndex* pindexStart)
314{ 424{
315 // Walk the chain looking for transactions that belong to keys this wallet 425 // Walk the chain looking for transactions that belong to keys this wallet
diff --git a/src/main.h b/src/main.h
index 8d9b7e6..a04a4e3 100644
--- a/src/main.h
+++ b/src/main.h
@@ -141,6 +141,33 @@ static const int KEYPOOL_SIZE = 100;
141extern CCriticalSection cs_keyPool; 141extern CCriticalSection cs_keyPool;
142extern map<int64, vector<unsigned char> > mapKeyPool; 142extern map<int64, vector<unsigned char> > mapKeyPool;
143 143
144// --- Deterministic wallet (BIP32) -----------------------------------------
145//
146// When a seed is present the key pool is derived from it -- m/0'/n, hardened,
147// n increasing -- instead of being made of random keys. That is what lets a
148// recovery phrase bring a wallet back: the same twelve words reproduce the same
149// keys in the same order.
150//
151// A wallet without a seed keeps working exactly as before. Nothing here
152// migrates an existing wallet on its own: the seed is created only when the
153// user asks for a phrase and confirms they have written it down, because a
154// phrase generated silently is a backup nobody has.
155//
156// Empty until a seed exists.
157extern vector<unsigned char> vchHDMaster; // 32-byte master private key (IL)
158extern vector<unsigned char> vchHDChainCode; // 32 bytes (IR)
159extern unsigned int nHDNext; // next child index to derive
160inline bool HaveHDSeed() { return vchHDMaster.size() == 32 && vchHDChainCode.size() == 32; }
161
162// Install a seed derived from a mnemonic, replacing any existing one, and reset
163// the derivation counter. Writes to wallet.dat. Returns false and leaves the
164// wallet untouched if the phrase is not valid.
165bool SetHDSeedFromMnemonic(const string& strMnemonic, string& strErrorRet);
166
167// Derive the child at nIndex and return it as a key. Used by the key pool and
168// by restore, which needs to run ahead of the pool.
169bool DeriveHDKey(unsigned int nIndex, CKey& keyRet, string& strErrorRet);
170
144// Fill the pool back up to KEYPOOL_SIZE. Every key it creates is written to 171// Fill the pool back up to KEYPOOL_SIZE. Every key it creates is written to
145// wallet.dat before it is offered to anybody. 172// wallet.dat before it is offered to anybody.
146void TopUpKeyPool(); 173void TopUpKeyPool();
@@ -156,6 +183,7 @@ void ReacceptWalletTransactions();
156// a key this wallet holds. Returns how many were added or updated. Needed by 183// a key this wallet holds. Returns how many were added or updated. Needed by
157// anything that puts a key into the wallet after the fact -- an import, a 184// anything that puts a key into the wallet after the fact -- an import, a
158// restored backup, and later a recovery phrase. 185// restored backup, and later a recovery phrase.
186bool CanScanWalletTransactions(string& strErrorRet);
159int ScanForWalletTransactions(CBlockIndex* pindexStart); 187int ScanForWalletTransactions(CBlockIndex* pindexStart);
160// Returns how many wallet transactions the chain corrected. 188// Returns how many wallet transactions the chain corrected.
161int RescanSpentFlags(); 189int RescanSpentFlags();
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 2bcdb83..aa9c23d 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -323,6 +323,16 @@ int main(int argc, char* argv[])
323 // import having failed. 323 // import having failed.
324 if (nAdded > 0) 324 if (nAdded > 0)
325 { 325 {
326 string strScanError;
327 if (!CanScanWalletTransactions(strScanError))
328 {
329 fprintf(stderr, "Cannot scan for imported coins: %s\n",
330 strScanError.c_str());
331 fprintf(stderr, "The key import was written, but the wallet "
332 "balance was not proven against the chain.\n");
333 DBFlush(true);
334 return 1;
335 }
326 printf("Looking through the chain for coins belonging to the imported key(s)...\n"); 336 printf("Looking through the chain for coins belonging to the imported key(s)...\n");
327 int nFound = ScanForWalletTransactions(pindexGenesisBlock); 337 int nFound = ScanForWalletTransactions(pindexGenesisBlock);
328 printf("Found %d transaction(s). Start the node normally to see the balance.\n", nFound); 338 printf("Found %d transaction(s). Start the node normally to see the balance.\n", nFound);
@@ -339,6 +349,14 @@ int main(int argc, char* argv[])
339 // balance is wrong for this reason. 349 // balance is wrong for this reason.
340 if (arg(argc,argv,"/rescan") || arg(argc,argv,"-rescan")) 350 if (arg(argc,argv,"/rescan") || arg(argc,argv,"-rescan"))
341 { 351 {
352 string strScanError;
353 if (!CanScanWalletTransactions(strScanError))
354 {
355 fprintf(stderr, "Cannot rescan wallet transactions: %s\n",
356 strScanError.c_str());
357 DBFlush(true);
358 return 1;
359 }
342 printf("Rescanning the chain for this wallet's transactions...\n"); 360 printf("Rescanning the chain for this wallet's transactions...\n");
343 int nFound = ScanForWalletTransactions(pindexGenesisBlock); 361 int nFound = ScanForWalletTransactions(pindexGenesisBlock);
344 printf("Rescan done: %d transaction(s) added or updated.\n", nFound); 362 printf("Rescan done: %d transaction(s) added or updated.\n", nFound);
diff --git a/src/makefile.mingw b/src/makefile.mingw
index 4f297d9..07ae034 100644
--- a/src/makefile.mingw
+++ b/src/makefile.mingw
@@ -42,7 +42,7 @@ IMGUI_OBJS = obj/imgui.o obj/imgui_draw.o obj/imgui_tables.o \
42NODE_OBJS = obj/util.o obj/script.o obj/db.o obj/net.o obj/main.o \ 42NODE_OBJS = obj/util.o obj/script.o obj/db.o obj/net.o obj/main.o \
43 obj/market.o obj/sha.o obj/nostr.o obj/randomx_pow.o \ 43 obj/market.o obj/sha.o obj/nostr.o obj/randomx_pow.o \
44 obj/btfaddr.o obj/btfchan.o obj/btfrv.o obj/btftunnel.o obj/rpc.o \ 44 obj/btfaddr.o obj/btfchan.o obj/btfrv.o obj/btftunnel.o obj/rpc.o \
45 obj/selftest.o 45 obj/selftest.o obj/bip32.o
46 46
47GUI_OBJS = obj/gui.o obj/main_gui.o obj/bitflash_res.o 47GUI_OBJS = obj/gui.o obj/main_gui.o obj/bitflash_res.o
48 48
@@ -103,3 +103,4 @@ test_bip32.exe: test_bip32.cpp bip32.cpp bip32.h bip39_english.h
103tests: test_bip32.exe bitflash.exe 103tests: test_bip32.exe bitflash.exe
104 ./test_bip32.exe 104 ./test_bip32.exe
105 ./bitflash.exe -selftest=wallet-keypool 105 ./bitflash.exe -selftest=wallet-keypool
106 ./bitflash.exe -selftest=wallet-hd
diff --git a/src/selftest.cpp b/src/selftest.cpp
index abe1d15..6792ae7 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -314,14 +314,161 @@ static void AttachTerminal()
314#endif 314#endif
315} 315}
316 316
317static int RunWalletHDSelfTest()
318{
319 fflush(stdout);
320 printf("wallet-hd self-test\n");
321
322 std::string tmp;
323 std::string cwd;
324 if (!MakeTempDir(tmp))
325 {
326 printf(" FAIL could not create a temporary data directory\n");
327 return 1;
328 }
329 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
330 {
331 printf(" FAIL could not move into the temporary data directory\n");
332 RemoveTree(tmp);
333 return 1;
334 }
335
336 int nFail = 0;
337 strSetDataDir = tmp;
338 printf(" temp datadir: %s\n", tmp.c_str());
339
340 // Test-vector phrases, so nothing here depends on randomness.
341 const std::string strPhraseA =
342 "abandon abandon abandon abandon abandon abandon "
343 "abandon abandon abandon abandon abandon about";
344 const std::string strPhraseB =
345 "legal winner thank year wave sausage worth useful "
346 "legal winner thank yellow";
347
348 try
349 {
350 if (!LoadWallet())
351 throw std::runtime_error("LoadWallet failed");
352
353 std::string strError;
354 nFail += Check(SetHDSeedFromMnemonic(strPhraseA, strError),
355 "a valid phrase installs a seed") ? 0 : 1;
356 nFail += Check(HaveHDSeed(), "the wallet reports having a seed") ? 0 : 1;
357
358 std::vector<unsigned char> vchBefore = vchHDMaster;
359 nFail += Check(!SetHDSeedFromMnemonic("not a mnemonic at all", strError),
360 "an invalid phrase is refused") ? 0 : 1;
361 nFail += Check(vchHDMaster == vchBefore,
362 "a refused phrase leaves the existing seed alone") ? 0 : 1;
363
364 // The property the whole feature exists for: the same words give back
365 // the same keys, in the same order.
366 std::vector<std::string> first;
367 for (unsigned int i = 0; i < 5; i++)
368 {
369 CKey key;
370 if (!DeriveHDKey(i, key, strError))
371 throw std::runtime_error("derivation failed: " + strError);
372 first.push_back(HexStrLocal(key.GetPubKey()));
373 }
374
375 SetHDSeedFromMnemonic(strPhraseA, strError);
376 std::vector<std::string> again;
377 for (unsigned int i = 0; i < 5; i++)
378 {
379 CKey key;
380 if (!DeriveHDKey(i, key, strError))
381 throw std::runtime_error("derivation failed: " + strError);
382 again.push_back(HexStrLocal(key.GetPubKey()));
383 }
384 nFail += Check(first == again,
385 "the same phrase derives the same keys in the same order") ? 0 : 1;
386
387 SetHDSeedFromMnemonic(strPhraseB, strError);
388 std::vector<std::string> other;
389 for (unsigned int i = 0; i < 5; i++)
390 {
391 CKey key;
392 if (!DeriveHDKey(i, key, strError))
393 throw std::runtime_error("derivation failed: " + strError);
394 other.push_back(HexStrLocal(key.GetPubKey()));
395 }
396 nFail += Check(first != other, "a different phrase derives different keys") ? 0 : 1;
397
398 std::set<std::string> distinct(first.begin(), first.end());
399 nFail += Check(distinct.size() == first.size(),
400 "consecutive indices give distinct keys") ? 0 : 1;
401
402 // With a seed installed the pool must be derived from it, and the
403 // counter must move exactly once per key.
404 SetHDSeedFromMnemonic(strPhraseA, strError);
405 CRITICAL_BLOCK(cs_keyPool)
406 mapKeyPool.clear();
407 unsigned int nNextBefore = nHDNext;
408 TopUpKeyPool();
409
410 int nPool = 0;
411 CRITICAL_BLOCK(cs_keyPool)
412 nPool = (int)mapKeyPool.size();
413 nFail += Check(nPool == KEYPOOL_SIZE, "the derived pool fills") ? 0 : 1;
414 nFail += Check(nHDNext == nNextBefore + (unsigned int)KEYPOOL_SIZE,
415 "the derivation counter advances once per pooled key") ? 0 : 1;
416
417 std::set<std::string> derived;
418 for (unsigned int i = nNextBefore; i < nHDNext; i++)
419 {
420 CKey key;
421 if (!DeriveHDKey(i, key, strError))
422 throw std::runtime_error("derivation failed: " + strError);
423 derived.insert(HexStrLocal(key.GetPubKey()));
424 }
425
426 bool fStored = true;
427 bool fFromSeed = true;
428 CRITICAL_BLOCK(cs_keyPool)
429 {
430 for (map<int64, vector<unsigned char> >::const_iterator mi = mapKeyPool.begin();
431 mi != mapKeyPool.end(); ++mi)
432 {
433 if (!WalletHasPrivateKey(mi->second))
434 fStored = false;
435 if (!derived.count(HexStrLocal(mi->second)))
436 fFromSeed = false;
437 }
438 }
439 nFail += Check(fStored, "every derived pooled key is stored in wallet.dat") ? 0 : 1;
440 nFail += Check(fFromSeed, "every pooled key came from the seed, not from chance") ? 0 : 1;
441 }
442 catch (const std::exception& e)
443 {
444 printf(" FAIL exception: %s\n", e.what());
445 nFail++;
446 }
447 catch (...)
448 {
449 printf(" FAIL unknown exception\n");
450 nFail++;
451 }
452
453 DBFlush(true);
454 SetCurrentDir(cwd);
455 RemoveTree(tmp);
456 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
457 nFail, nFail == 1 ? "" : "s");
458 fflush(stdout);
459 return nFail == 0 ? 0 : 1;
460}
461
317int RunSelfTest(const std::string& name) 462int RunSelfTest(const std::string& name)
318{ 463{
319 AttachTerminal(); 464 AttachTerminal();
320 465
321 if (name == "wallet-keypool") 466 if (name == "wallet-keypool")
322 return RunWalletKeyPoolSelfTest(); 467 return RunWalletKeyPoolSelfTest();
468 if (name == "wallet-hd")
469 return RunWalletHDSelfTest();
323 470
324 printf("Unknown self-test '%s'\n", name.c_str()); 471 printf("Unknown self-test '%s'\n", name.c_str());
325 printf("Known self-tests: wallet-keypool\n"); 472 printf("Known self-tests: wallet-keypool, wallet-hd\n");
326 return 1; 473 return 1;
327} 474}