Reported by @dooglus.
Fixes #8383
2080@@ -2081,7 +2081,7 @@ UniValue encryptwallet(const UniValue& params, bool fHelp)
2081 // slack space in .dat files; that is bad if the old data is
2082 // unencrypted private keys. So:
2083 StartShutdown();
2084- return "wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet. The keypool has been flushed, you need to make a new backup.";
2085+ return "wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet. The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.";
625@@ -626,6 +626,16 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
626
627 Lock();
628 Unlock(strWalletPassphrase);
629+
630+ // if we are using HD, replace the HD master key with a new one
631+ if (!hdChain.masterKeyID.IsNull()) {
632+ CKey key;
633+ key.MakeNewKey(true);
634+ return false;
validateaddress
.
11@@ -12,13 +12,23 @@ class KeyPoolTest(BitcoinTestFramework):
12
13 def run_test(self):
14 nodes = self.nodes
15+ addrBeforeEncrypting = nodes[0].getnewaddress()
16+ addrBeforeEncryptingData = nodes[0].validateaddress(addrBeforeEncrypting)
17+ walletInfoOld = nodes[0].getwalletinfo()
18+ assert(addrBeforeEncryptingData['hdmasterkeyid'] == walletInfoOld['masterkeyid'])
So this makes it impossible to encrypt a seed which is already in use and is planned to be used in the future, but I guess for 0.13 this is fair enough.
Concept ACK
625@@ -626,6 +626,15 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
626
627 Lock();
628 Unlock(strWalletPassphrase);
629+
630+ // if we are using HD, replace the HD master key with a new one
631+ if (!hdChain.masterKeyID.IsNull()) {
632+ CKey key;
633+ key.MakeNewKey(true);
634+ if (!SetHDMasterKey(key))
bool SomeFunction()
, where the return value indicates if something bad happens but at the same time this is unused and exceptions are thrown. This should be made consistent.
I think this solution is fine for the moment. The return value of SetHDMasterKey()
is for future extensions and I think its useful even if its not possible to get a false
returned with the current implementation.
The exception will already be caught though the RPC/Server logic (EncryptWallet()
will only be called in the RPC context where we “handle” exceptions).
@pstratem It reads to me as if the old hdchain
key is overwritten by the new one:
0bool CWalletDB::WriteHDChain(const CHDChain& chain)
1{
2 nWalletDBUpdated++;
3 return Write(std::string("hdchain"), chain);
4}
This WriteHDChain() function is used each time a new address is generated:
0 // update the chain model in the database
1 if (!CWalletDB(strWalletFile).WriteHDChain(hdChain))
The original chain could be stored under a different database key if we need to keep it around somewhere.
@pstratem: CWalletDB and BDB is a key value storage as far as I know. Writing the same key at later stage should replace the current value. It may be possible to have the old K/V still stored in the database, though, CWalletDB/BDB needs to make sure the later write operation will result in the laster read/mem-map operation during load wallet.
But I agree we need to keep track of the older/replaced seed. Maybe we should add something to the CKeyMetadata in that case (or at least CAddressBook).
Pushed a commit that…
CKeyMetadata
record together with a HD master key where we set a hdkeypath="m"
for later identifying this key as hd master keyhttps://github.com/bitcoin/bitcoin/pull/8206/files would make use of this. It would show old/rewritten master keys in the dumped wallet.
899@@ -900,8 +900,11 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
900 /* Set the HD chain model (chain child index counters) */
901 bool SetHDChain(const CHDChain& chain, bool memonly);
902
903+ /* Generates a new HD master key (will not be activated) */
904+ CPubKey GenerateNewHDMasterKey(CKey& key);
1185+
1186+ // calculate the pubkey
1187+ CPubKey pubkey = key.GetPubKey();
1188+ assert(key.VerifyPubKey(pubkey));
1189+
1190+ // set the hd keypath to "m" -> Master, refere the masterkeyid to itself
630+ // if we are using HD, replace the HD master key (seed) with a new one
631+ if (!hdChain.masterKeyID.IsNull()) {
632+ CKey key;
633+ CPubKey masterPubKey = GenerateNewHDMasterKey(key);;
634+ if (!SetHDMasterKey(masterPubKey))
635+ return false;
630+ // if we are using HD, replace the HD master key (seed) with a new one
631+ if (!hdChain.masterKeyID.IsNull()) {
632+ CKey key;
633+ CPubKey masterPubKey = GenerateNewHDMasterKey();
634+ if (!SetHDMasterKey(masterPubKey))
635+ return false;
NewKeyPool()
.
I don’t see a better way of handling this case.
I did this:
0$ rm ~/.bitcoin/regtest/wallet.dat
1$ src/bitcoind -regtest -rpcport=1234 -daemon -usehd=1
2Bitcoin server starting
3$ src/bitcoin-cli -regtest -rpcport=1234 dumpwallet "/tmp/dump_pre"
4$ src/bitcoin-cli -regtest -rpcport=1234 encryptwallet "pass"
5wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet. The keypool has been flushed, you need to make a new backup.
6$ src/bitcoind -regtest -rpcport=1234 -daemon -usehd=1
7Bitcoin server starting
8$ src/bitcoin-cli -regtest -rpcport=1234 walletpassphrase "pass" 12345
9$ src/bitcoin-cli -regtest -rpcport=1234 dumpwallet "/tmp/dump_post"
10$ src/bitcoin-cli -regtest -rpcport=1234 stop
11$ diff -du /tmp/dump_pre /tmp/dump_post
Result: http://www.hastebin.com/ajuhahofan.diff
Looks OK to me. The old reserve keys have been demoted to “change”, and there are new reserve keys in the keypool. These keys are different, suggesting a new master key was used to generate them.
The old master key was kept around as “oldhdmaster”:
0-cSoMD7tEsoJNfSS3cL6N3E9gXpuQh7wgs79RDUBE6NjTEWS6Ptda 2016-07-28T10:48:28Z hdmaster=1 # addr=msqHNkZcD6ybT7xdCkquN4esLPLmoRyk8b hdkeypath=m
1+cSoMD7tEsoJNfSS3cL6N3E9gXpuQh7wgs79RDUBE6NjTEWS6Ptda 2016-07-28T10:48:28Z inactivehdmaster=1 # addr=msqHNkZcD6ybT7xdCkquN4esLPLmoRyk8b hdkeypath=m
And a new one was added
0+cRrbkixFuTz1esCbJTz3QqNGyEcPjFCjVyHRTy1JDxKDbHYXn9Qd 2016-07-28T10:48:43Z hdmaster=1 # addr=n3phnt6MDc2xUvKXrf8iW6ZiwtL2i7Rusm hdkeypath=m
The single key that is automatically generated (with empty label) at initialization stayed the same
0 cRj4c74AFYdH9W1vbM3UgnaCeGbwztu3qb11NgDReiudtS2Q1ZAr 2016-07-28T10:48:28Z label= # addr=mwR8SR9sfKk7Q8z99xpdcCUNtccq81iRup hdkeypath=m/0'/0'/0'
For the new reserve keys it starts counting again at m/0'/0'/0'
0+cNRrj6BtSxcXySqU6WgWgGEHMARifd1VXCVECdsXx4ervHE7iS4t 2016-07-28T10:48:43Z reserve=1 # addr=moBwgfzRf8H4ZjweCbQcckuJbMC16jwtD1 hdkeypath=m/0'/0'/0'
I think everything is as expected. Tested ACK.
625@@ -626,6 +626,15 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
626
627 Lock();
628 Unlock(strWalletPassphrase);
629+
630+ // if we are using HD, replace the HD master key (seed) with a new one
631+ if (!hdChain.masterKeyID.IsNull()) {
632+ CKey key;
1198+ // mem store the metadata
1199+ mapKeyMetadata[pubkey.GetID()] = metadata;
1200+
1201+ // write the key&metadata to the database
1202+ if (!AddKeyPubKey(key, pubkey))
1203+ throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed");