diff --git a/src/init.cpp b/src/init.cpp index bcbc85bf2..0171a1453 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -410,6 +410,7 @@ std::string HelpMessage(HelpMessageMode mode) 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=", 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)"), true)); strUsage += HelpMessageOpt("-maxtxfee=", strprintf(_("Maximum total fees (in %s) to use in a single wallet transaction; setting this too low may abort large transactions (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE))); strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup")); @@ -1538,6 +1539,14 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // Create new keyUser and set as default key RandAddSeedPerfmon(); + // Create new keyUser and set as default key + if (GetBoolArg("-usehd", true)) { + // generate a new master key + CKey key; + key.MakeNewKey(true); + if (!pwalletMain->SetHDMasterKey(key)) + throw std::runtime_error("CWallet::GenerateNewKey(): Storing master key failed"); + } CPubKey newDefaultKey; if (pwalletMain->GetKeyFromPool(newDefaultKey)) { pwalletMain->SetDefaultKey(newDefaultKey); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 3ae1115a0..255fe8610 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -94,7 +94,48 @@ 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'/' + + // 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, 0 | 0x80000000); + + // derive m/0'/0' + accountKey.Derive(externalChainChildKey, 0 | 0x80000000); + + // derive child key at next index, skip keys already known to the wallet + do + { + externalChainChildKey.Derive(childKey, hdChain.nExternalChainCounter | 0x80000000); + // 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) @@ -104,8 +145,7 @@ CPubKey CWallet::GenerateNewKey() 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; @@ -1333,6 +1373,37 @@ CAmount CWallet::GetChange(const CTxOut& txout) const return (IsChange(txout) ? txout.nValue : 0); } +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)) + 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; +} + bool CWallet::IsMine(const CTransaction& tx) const { BOOST_FOREACH(const CTxOut& txout, tx.vout) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 19cfcc786..1bd0e1f0b 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -85,6 +85,35 @@ enum WalletFeature }; +/* simple hd chain data model */ +class CHDChain +{ +public: + uint32_t nExternalChainCounter; + CKeyID masterKeyID; //!< master key hash160 + + static const int CURRENT_VERSION = 1; + int nVersion; + + CHDChain() { SetNull(); } + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(this->nVersion); + nVersion = this->nVersion; + READWRITE(nExternalChainCounter); + READWRITE(masterKeyID); + } + + void SetNull() + { + nVersion = CHDChain::CURRENT_VERSION; + nExternalChainCounter = 0; + masterKeyID.SetNull(); + } +}; + /** A key pool entry */ class CKeyPool { @@ -509,6 +538,9 @@ private: void SyncMetaData(std::pair); + /* the hd chain data model (external chain counters) */ + CHDChain hdChain; + public: /* * Main wallet lock. @@ -814,6 +846,11 @@ public: bool SelectCoinsForStaking(CAmount& nTargetValue, std::set >& setCoinsRet, CAmount& nValueRet) const; void AvailableCoinsForStaking(std::vector& vCoins) const; uint64_t GetStakeWeight() const; + /* 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) */ + bool SetHDMasterKey(const CKey& key); }; /** A key allocated from the key pool. */ diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index b1b9d0c23..522568faf 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -598,6 +598,16 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, return false; } } + else if (strType == "hdchain") + { + CHDChain chain; + ssValue >> chain; + if (!pwallet->SetHDChain(chain, true)) + { + strErr = "Error reading wallet database: SetHDChain failed"; + return false; + } + } } catch (...) { return false; @@ -998,6 +1008,12 @@ bool CWalletDB::WriteDestData(const std::string &address, const std::string &key return Write(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value); } +bool CWalletDB::WriteHDChain(const CHDChain& chain) +{ + nWalletDBUpdated++; + return Write(std::string("hdchain"), chain); +} + bool CWalletDB::EraseDestData(const std::string &address, const std::string &key) { nWalletDBUpdated++; diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 8da33dead..a213d4c65 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -46,6 +46,8 @@ public: static const int CURRENT_VERSION=1; 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() { @@ -73,6 +75,32 @@ public: } }; +/* simple hd chain data model */ +class CHDChain +{ +public: + uint32_t nExternalChainCounter; + CKeyID masterKeyID; //!< master key hash160 + static const int CURRENT_VERSION = 1; + int nVersion; + CHDChain() { SetNull(); } + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(this->nVersion); + nVersion = this->nVersion; + READWRITE(nExternalChainCounter); + READWRITE(masterKeyID); + } + void SetNull() + { + nVersion = CHDChain::CURRENT_VERSION; + nExternalChainCounter = 0; + masterKeyID.SetNull(); + } +}; + /** Access to the wallet database (wallet.dat) */ class CWalletDB : public CDB { @@ -120,9 +148,9 @@ public: /// Write destination data key,value tuple to database bool WriteDestData(const std::string &address, const std::string &key, const std::string &value); + bool WriteHDChain(const CHDChain& chain); /// Erase destination data tuple from wallet database bool EraseDestData(const std::string &address, const std::string &key); - CAmount GetAccountCreditDebit(const std::string& strAccount); void ListAccountCreditDebit(const std::string& strAccount, std::list& acentries);