From 5b95dd2c256dd7ba3808021adc31bb85b41553c8 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Sat, 9 Jul 2016 11:41:01 +0200 Subject: [PATCH 1/6] [Wallet] extend CKeyMetadata with HD keypath --- src/wallet/wallet.cpp | 2 ++ src/wallet/walletdb.h | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a0095ebd9..33e516a7f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -126,6 +126,8 @@ CPubKey CWallet::GenerateNewKey() // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649 externalChainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); + metadata.hdKeypath = "m/0'/0'/"+std::to_string(hdChain.nExternalChainCounter)+"'"; + metadata.hdMasterKeyID = hdChain.masterKeyID; // increment childkey index hdChain.nExternalChainCounter++; } while(HaveKey(childKey.key.GetPubKey().GetID())); diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index d083722dd..eaa406857 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -73,9 +73,13 @@ public: class CKeyMetadata { public: - static const int CURRENT_VERSION=1; + static const int VERSION_BASIC=1; + static const int VERSION_WITH_HDDATA=10; + static const int CURRENT_VERSION=VERSION_WITH_HDDATA; int nVersion; int64_t nCreateTime; // 0 means unknown + std::string hdKeypath; //optional HD/bip32 keypath + CKeyID hdMasterKeyID; //id of the hd masterkey used to derive this key CKeyMetadata() { @@ -85,6 +89,7 @@ public: { nVersion = CKeyMetadata::CURRENT_VERSION; nCreateTime = nCreateTime_; + hdKeypath.clear(); } ADD_SERIALIZE_METHODS; @@ -94,12 +99,18 @@ public: READWRITE(this->nVersion); nVersion = this->nVersion; READWRITE(nCreateTime); + if (this->nVersion >= VERSION_WITH_HDDATA) + { + READWRITE(hdKeypath); + READWRITE(hdMasterKeyID); + } } void SetNull() { nVersion = CKeyMetadata::CURRENT_VERSION; nCreateTime = 0; + hdKeypath.clear(); } }; From b1c7b244e21ba67c38fe3d1a4d1638ca52835ac5 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Sat, 9 Jul 2016 11:41:32 +0200 Subject: [PATCH 2/6] [Wallet] report optional HDKeypath/HDMasterKeyId in validateaddress --- src/rpc/misc.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index f2a29416e..a8c5bcd17 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -166,6 +166,8 @@ UniValue validateaddress(const UniValue& params, bool fHelp) " \"pubkey\" : \"publickeyhex\", (string) The hex value of the raw public key\n" " \"iscompressed\" : true|false, (boolean) If the address is compressed\n" " \"account\" : \"account\" (string) DEPRECATED. The account associated with the address, \"\" is the default account\n" + " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n" + " \"hdmasterkeyid\" : \"\" (string, optional) The Hash160 of the HD master pubkey\n" "}\n" "\nExamples:\n" + HelpExampleCli("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") @@ -200,6 +202,12 @@ UniValue validateaddress(const UniValue& params, bool fHelp) ret.pushKVs(detail); if (pwalletMain && pwalletMain->mapAddressBook.count(dest)) ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest].name)); + CKeyID keyID; + if (pwalletMain && address.GetKeyID(keyID) && pwalletMain->mapKeyMetadata.count(keyID) && !pwalletMain->mapKeyMetadata[keyID].hdKeypath.empty()) + { + ret.push_back(Pair("hdkeypath", pwalletMain->mapKeyMetadata[keyID].hdKeypath)); + ret.push_back(Pair("hdmasterkeyid", pwalletMain->mapKeyMetadata[keyID].hdMasterKeyID.GetHex())); + } #endif } return ret; From 986c2232143e5c2f909f5b27bf74470654faf49c Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Sat, 9 Jul 2016 11:25:02 +0200 Subject: [PATCH 3/6] [Wallet] print hd masterkeyid in getwalletinfo --- src/wallet/rpcwallet.cpp | 4 ++++ src/wallet/wallet.h | 1 + 2 files changed, 5 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 8538f880f..f4625743b 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2269,6 +2269,7 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n" " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" + " \"masterkeyid\": \"\", (string) the Hash160 of the hd master pubkey\n" "}\n" "\nExamples:\n" + HelpExampleCli("getwalletinfo", "") @@ -2288,6 +2289,9 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) if (pwalletMain->IsCrypted()) obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); + CKeyID masterKeyID = pwalletMain->GetHDChain().masterKeyID; + if (!masterKeyID.IsNull()) + obj.push_back(Pair("masterkeyid",masterKeyID.GetHex())); return obj; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 7fc6ce5de..d58cb7b0e 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -901,6 +901,7 @@ public: /* Set the current hd master key (will reset the chain child index counters) */ bool SetHDMasterKey(const CKey& key); + const CHDChain& GetHDChain() { return hdChain; } }; /** A key allocated from the key pool. */ From f70808596fb4108e53c8b433820593b88c4fa81b Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Sat, 9 Jul 2016 12:05:30 +0200 Subject: [PATCH 4/6] [QA] extend wallet-hd test to cover HD metadata --- qa/rpc-tests/wallet-hd.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/qa/rpc-tests/wallet-hd.py b/qa/rpc-tests/wallet-hd.py index 845eec027..f3ae69e10 100755 --- a/qa/rpc-tests/wallet-hd.py +++ b/qa/rpc-tests/wallet-hd.py @@ -30,6 +30,10 @@ class WalletHDTest(BitcoinTestFramework): def run_test (self): tmpdir = self.options.tmpdir + # Make sure we use hd, keep masterkeyid + masterkeyid = self.nodes[1].getwalletinfo()['masterkeyid'] + assert_equal(len(masterkeyid), 40) + # Import a non-HD private key in the HD wallet non_hd_add = self.nodes[0].getnewaddress() self.nodes[1].importprivkey(self.nodes[0].dumpprivkey(non_hd_add)) @@ -45,6 +49,9 @@ class WalletHDTest(BitcoinTestFramework): num_hd_adds = 300 for _ in range(num_hd_adds): hd_add = self.nodes[1].getnewaddress() + hd_info = self.nodes[1].validateaddress(hd_add) + assert_equal(hd_info["hdkeypath"], "m/0'/0'/"+str(_+1)+"'") + assert_equal(hd_info["hdmasterkeyid"], masterkeyid) self.nodes[0].sendtoaddress(hd_add, 1) self.nodes[0].generate(1) self.nodes[0].sendtoaddress(non_hd_add, 1) @@ -64,6 +71,9 @@ class WalletHDTest(BitcoinTestFramework): hd_add_2 = None for _ in range(num_hd_adds): hd_add_2 = self.nodes[1].getnewaddress() + hd_info_2 = self.nodes[1].validateaddress(hd_add_2) + assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/"+str(_+1)+"'") + assert_equal(hd_info_2["hdmasterkeyid"], masterkeyid) assert_equal(hd_add, hd_add_2) # Needs rescan From 68d7682b9f8c0f548e0a9ef03296320b8ae3960d Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 15 Jul 2016 10:33:25 +0200 Subject: [PATCH 5/6] [Wallet] ensure CKeyMetadata.hdMasterKeyID will be cleared during SetNull() --- src/wallet/walletdb.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index eaa406857..42179f228 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -87,9 +87,8 @@ public: } CKeyMetadata(int64_t nCreateTime_) { - nVersion = CKeyMetadata::CURRENT_VERSION; + SetNull(); nCreateTime = nCreateTime_; - hdKeypath.clear(); } ADD_SERIALIZE_METHODS; @@ -111,6 +110,7 @@ public: nVersion = CKeyMetadata::CURRENT_VERSION; nCreateTime = 0; hdKeypath.clear(); + hdMasterKeyID.SetNull(); } }; From 7945088d413819d8cf1772fd25e0f355c84c64d6 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 15 Jul 2016 10:34:08 +0200 Subject: [PATCH 6/6] [Wallet] comsetic non-code changes for the HD feature --- qa/rpc-tests/wallet-hd.py | 4 ++-- src/wallet/rpcwallet.cpp | 4 ++-- src/wallet/wallet.h | 6 +++--- src/wallet/walletdb.h | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/qa/rpc-tests/wallet-hd.py b/qa/rpc-tests/wallet-hd.py index f3ae69e10..c738ee220 100755 --- a/qa/rpc-tests/wallet-hd.py +++ b/qa/rpc-tests/wallet-hd.py @@ -47,10 +47,10 @@ class WalletHDTest(BitcoinTestFramework): self.nodes[0].generate(101) hd_add = None num_hd_adds = 300 - for _ in range(num_hd_adds): + for i in range(num_hd_adds): hd_add = self.nodes[1].getnewaddress() hd_info = self.nodes[1].validateaddress(hd_add) - assert_equal(hd_info["hdkeypath"], "m/0'/0'/"+str(_+1)+"'") + assert_equal(hd_info["hdkeypath"], "m/0'/0'/"+str(i+1)+"'") assert_equal(hd_info["hdmasterkeyid"], masterkeyid) self.nodes[0].sendtoaddress(hd_add, 1) self.nodes[0].generate(1) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index f4625743b..960d193e5 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2269,7 +2269,7 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n" " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" - " \"masterkeyid\": \"\", (string) the Hash160 of the hd master pubkey\n" + " \"masterkeyid\": \"\", (string) the Hash160 of the HD master pubkey\n" "}\n" "\nExamples:\n" + HelpExampleCli("getwalletinfo", "") @@ -2291,7 +2291,7 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); CKeyID masterKeyID = pwalletMain->GetHDChain().masterKeyID; if (!masterKeyID.IsNull()) - obj.push_back(Pair("masterkeyid",masterKeyID.GetHex())); + obj.push_back(Pair("masterkeyid", masterKeyID.GetHex())); return obj; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d58cb7b0e..e9d669a7d 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -577,7 +577,7 @@ private: void SyncMetaData(std::pair); - /* the hd chain data model (external chain counters) */ + /* the HD chain data model (external chain counters) */ CHDChain hdChain; public: @@ -896,10 +896,10 @@ public: bool BackupWallet(const std::string& strDest); - /* Set the hd chain model (chain child index counters) */ + /* Set the HD chain model (chain child index counters) */ bool SetHDChain(const CHDChain& chain, bool memonly); - /* Set the current hd master key (will reset the chain child index counters) */ + /* Set the current HD master key (will reset the chain child index counters) */ bool SetHDMasterKey(const CKey& key); const CHDChain& GetHDChain() { return hdChain; } }; diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 42179f228..5addd5c5c 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -41,7 +41,7 @@ enum DBErrors DB_NEED_REWRITE }; -/* simple hd chain data model */ +/* simple HD chain data model */ class CHDChain { public: @@ -79,7 +79,7 @@ public: int nVersion; int64_t nCreateTime; // 0 means unknown std::string hdKeypath; //optional HD/bip32 keypath - CKeyID hdMasterKeyID; //id of the hd masterkey used to derive this key + CKeyID hdMasterKeyID; //id of the HD masterkey used to derive this key CKeyMetadata() {