[Wallet] Add simplest BIP32/deterministic key generation implementation #8035
Merged
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
f190251
[Wallet] Add simplest BIP32/deterministic key generation implementation
jonasschnelli c022e5b
[Wallet] use constant for bip32 hardened key limit
jonasschnelli 17c0131
[Docs] Add release notes and bip update for Bip32/HD wallets
jonasschnelli afcd77e
Detect -usehd mismatches when wallet.dat already exists
jonasschnelli | @@ -42,6 +42,7 @@ bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE; | ||
| bool fSendFreeTransactions = DEFAULT_SEND_FREE_TRANSACTIONS; | ||
| const char * DEFAULT_WALLET_DAT = "wallet.dat"; | ||
| +const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; | ||
| /** | ||
| * Fees smaller than this (in satoshi) are considered zero fee (for transaction creation) | ||
| @@ -91,7 +92,51 @@ CPubKey CWallet::GenerateNewKey() | ||
| bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets | ||
| CKey secret; | ||
| - secret.MakeNewKey(fCompressed); | ||
| + | ||
| + // Create new metadata | ||
| + int64_t nCreationTime = GetTime(); | ||
| + CKeyMetadata metadata(nCreationTime); | ||
| + | ||
| + // use HD key derivation if HD was enabled during wallet creation | ||
| + if (!hdChain.masterKeyID.IsNull()) { | ||
| + // for now we use a fixed keypath scheme of m/0'/0'/k | ||
| + CKey key; //master key seed (256bit) | ||
| + CExtKey masterKey; //hd master key | ||
| + CExtKey accountKey; //key at m/0' | ||
| + CExtKey externalChainChildKey; //key at m/0'/0' | ||
| + CExtKey childKey; //key at m/0'/0'/<n>' | ||
| + | ||
| + // try to get the master key | ||
| + if (!GetKey(hdChain.masterKeyID, key)) | ||
| + throw std::runtime_error("CWallet::GenerateNewKey(): Master key not found"); | ||
| + | ||
| + masterKey.SetMaster(key.begin(), key.size()); | ||
| + | ||
| + // derive m/0' | ||
| + // use hardened derivation (child keys >= 0x80000000 are hardened after bip32) | ||
| + masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT); | ||
| + | ||
| + // derive m/0'/0' | ||
| + accountKey.Derive(externalChainChildKey, BIP32_HARDENED_KEY_LIMIT); | ||
| + | ||
| + // derive child key at next index, skip keys already known to the wallet | ||
| + do | ||
| + { | ||
| + // always derive hardened keys | ||
| + // 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); | ||
| + // increment childkey index | ||
| + hdChain.nExternalChainCounter++; | ||
| + } while(HaveKey(childKey.key.GetPubKey().GetID())); | ||
| + secret = childKey.key; | ||
| + | ||
| + // update the chain model in the database | ||
| + if (!CWalletDB(strWalletFile).WriteHDChain(hdChain)) | ||
| + throw std::runtime_error("CWallet::GenerateNewKey(): Writing HD chain model failed"); | ||
| + } else { | ||
| + secret.MakeNewKey(fCompressed); | ||
| + } | ||
| // Compressed public keys were introduced in version 0.6.0 | ||
| if (fCompressed) | ||
| @@ -100,9 +145,7 @@ CPubKey CWallet::GenerateNewKey() | ||
| CPubKey pubkey = secret.GetPubKey(); | ||
| assert(secret.VerifyPubKey(pubkey)); | ||
| - // Create new metadata | ||
| - int64_t nCreationTime = GetTime(); | ||
| - mapKeyMetadata[pubkey.GetID()] = CKeyMetadata(nCreationTime); | ||
| + mapKeyMetadata[pubkey.GetID()] = metadata; | ||
| if (!nTimeFirstKey || nCreationTime < nTimeFirstKey) | ||
| nTimeFirstKey = nCreationTime; | ||
| @@ -1049,6 +1092,37 @@ CAmount CWallet::GetChange(const CTransaction& tx) const | ||
| return nChange; | ||
| } | ||
| +bool CWallet::SetHDMasterKey(const CKey& key) | ||
| +{ | ||
| + LOCK(cs_wallet); | ||
| + | ||
| + // store the key as normal "key"/"ckey" object | ||
| + // in the database | ||
| + // key metadata is not required | ||
| + CPubKey pubkey = key.GetPubKey(); | ||
| + if (!AddKeyPubKey(key, pubkey)) | ||
sipa
Owner
|
||
| + throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed"); | ||
|
|
||
| + | ||
| + // store the keyid (hash160) together with | ||
| + // the child index counter in the database | ||
| + // as a hdchain object | ||
| + CHDChain newHdChain; | ||
| + newHdChain.masterKeyID = pubkey.GetID(); | ||
| + SetHDChain(newHdChain, false); | ||
| + | ||
| + return true; | ||
| +} | ||
| + | ||
| +bool CWallet::SetHDChain(const CHDChain& chain, bool memonly) | ||
| +{ | ||
| + LOCK(cs_wallet); | ||
| + if (!memonly && !CWalletDB(strWalletFile).WriteHDChain(chain)) | ||
| + throw runtime_error("AddHDChain(): writing chain failed"); | ||
| + | ||
| + hdChain = chain; | ||
| + return true; | ||
| +} | ||
| + | ||
| int64_t CWalletTx::GetTxTime() const | ||
| { | ||
| int64_t n = nTimeSmart; | ||
| @@ -3058,6 +3132,7 @@ std::string CWallet::GetWalletHelpString(bool showDebug) | ||
| strUsage += HelpMessageOpt("-sendfreetransactions", strprintf(_("Send transactions as zero-fee transactions if possible (default: %u)"), DEFAULT_SEND_FREE_TRANSACTIONS)); | ||
| strUsage += HelpMessageOpt("-spendzeroconfchange", strprintf(_("Spend unconfirmed change when sending transactions (default: %u)"), DEFAULT_SPEND_ZEROCONF_CHANGE)); | ||
| strUsage += HelpMessageOpt("-txconfirmtarget=<n>", strprintf(_("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)"), DEFAULT_TX_CONFIRM_TARGET)); | ||
| + strUsage += HelpMessageOpt("-usehd", _("Use hierarchical deterministic key generation (HD) after bip32. Only has effect during wallet creation/first start") + " " + strprintf(_("(default: %u)"), DEFAULT_USE_HD_WALLET)); | ||
MarcoFalke
Member
|
||
| strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup")); | ||
| strUsage += HelpMessageOpt("-wallet=<file>", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT)); | ||
| strUsage += HelpMessageOpt("-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %u)"), DEFAULT_WALLETBROADCAST)); | ||
| @@ -3145,6 +3220,13 @@ bool CWallet::InitLoadWallet() | ||
| if (fFirstRun) | ||
| { | ||
| // Create new keyUser and set as default key | ||
| + if (GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET)) { | ||
| + // generate a new master key | ||
| + CKey key; | ||
| + key.MakeNewKey(true); | ||
| + if (!walletInstance->SetHDMasterKey(key)) | ||
| + throw std::runtime_error("CWallet::GenerateNewKey(): Storing master key failed"); | ||
| + } | ||
| CPubKey newDefaultKey; | ||
| if (walletInstance->GetKeyFromPool(newDefaultKey)) { | ||
| walletInstance->SetDefaultKey(newDefaultKey); | ||
| @@ -3154,6 +3236,13 @@ bool CWallet::InitLoadWallet() | ||
| walletInstance->SetBestChain(chainActive.GetLocator()); | ||
| } | ||
| + else if (mapArgs.count("-usehd")) { | ||
| + bool useHD = GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET); | ||
| + if (!walletInstance->hdChain.masterKeyID.IsNull() && !useHD) | ||
| + return InitError(strprintf(_("Error loading %s: You can't disable HD on a already existing HD wallet"), walletFile)); | ||
| + if (walletInstance->hdChain.masterKeyID.IsNull() && useHD) | ||
| + return InitError(strprintf(_("Error loading %s: You can't enable HD on a already existing non-HD wallet"), walletFile)); | ||
| + } | ||
| LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart); | ||
| @@ -599,6 +599,16 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, | ||
| return false; | ||
| } | ||
| } | ||
| + else if (strType == "hdchain") | ||
paveljanik
Contributor
|
||
| + { | ||
| + CHDChain chain; | ||
| + ssValue >> chain; | ||
| + if (!pwallet->SetHDChain(chain, true)) | ||
| + { | ||
| + strErr = "Error reading wallet database: SetHDChain failed"; | ||
| + return false; | ||
| + } | ||
| + } | ||
| } catch (...) | ||
| { | ||
| return false; | ||
| @@ -1003,3 +1013,10 @@ bool CWalletDB::EraseDestData(const std::string &address, const std::string &key | ||
| nWalletDBUpdated++; | ||
| return Erase(std::make_pair(std::string("destdata"), std::make_pair(address, key))); | ||
| } | ||
| + | ||
| + | ||
| +bool CWalletDB::WriteHDChain(const CHDChain& chain) | ||
| +{ | ||
| + nWalletDBUpdated++; | ||
| + return Write(std::string("hdchain"), chain); | ||
| +} | ||
nit: simply calling AddKey should work herenevermind, wrong function