Skip to content

Commit b67a472

Browse files
committed
Merge #8035: [Wallet] Add simplest BIP32/deterministic key generation implementation
afcd77e Detect -usehd mismatches when wallet.dat already exists (Jonas Schnelli) 17c0131 [Docs] Add release notes and bip update for Bip32/HD wallets (Jonas Schnelli) c022e5b [Wallet] use constant for bip32 hardened key limit (Jonas Schnelli) f190251 [Wallet] Add simplest BIP32/deterministic key generation implementation (Jonas Schnelli)
2 parents cca1c8c + afcd77e commit b67a472

File tree

6 files changed

+173
-4
lines changed

6 files changed

+173
-4
lines changed

doc/bips.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.13.0**):
1010
* [`BIP 23`](https://github.com/bitcoin/bips/blob/master/bip-0023.mediawiki): Some extensions to GBT have been implemented since **v0.10.0rc1**, including longpolling and block proposals ([PR #1816](https://github.com/bitcoin/bitcoin/pull/1816)).
1111
* [`BIP 30`](https://github.com/bitcoin/bips/blob/master/bip-0030.mediawiki): The evaluation rules to forbid creating new transactions with the same txid as previous not-fully-spent transactions were implemented since **v0.6.0**, and the rule took effect on *March 15th 2012* ([PR #915](https://github.com/bitcoin/bitcoin/pull/915)).
1212
* [`BIP 31`](https://github.com/bitcoin/bips/blob/master/bip-0031.mediawiki): The 'pong' protocol message (and the protocol version bump to 60001) has been implemented since **v0.6.1** ([PR #1081](https://github.com/bitcoin/bitcoin/pull/1081)).
13+
* [`BIP 32`](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki): Hierarchical Deterministic Wallets has been implemented since **v0.13.0** ([PR #8035](https://github.com/bitcoin/bitcoin/pull/8035)).
1314
* [`BIP 34`](https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki): The rule that requires blocks to contain their height (number) in the coinbase input, and the introduction of version 2 blocks has been implemented since **v0.7.0**. The rule took effect for version 2 blocks as of *block 224413* (March 5th 2013), and version 1 blocks are no longer allowed since *block 227931* (March 25th 2013) ([PR #1526](https://github.com/bitcoin/bitcoin/pull/1526)).
1415
* [`BIP 35`](https://github.com/bitcoin/bips/blob/master/bip-0035.mediawiki): The 'mempool' protocol message (and the protocol version bump to 60002) has been implemented since **v0.7.0** ([PR #1641](https://github.com/bitcoin/bitcoin/pull/1641)).
1516
* [`BIP 37`](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki): The bloom filtering for transaction relaying, partial merkle trees for blocks, and the protocol version bump to 70001 (enabling low-bandwidth SPV clients) has been implemented since **v0.8.0** ([PR #1795](https://github.com/bitcoin/bitcoin/pull/1795)).

doc/release-notes.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,24 @@ feerate. [BIP 133](https://github.com/bitcoin/bips/blob/master/bip-0133.mediawik
119119

120120
### Wallet
121121

122+
Hierarchical Deterministic Key Generation
123+
-----------------------------------------
124+
Newly created wallets will use hierarchical deterministic key generation
125+
according to BIP32 (keypath m/0'/0'/k').
126+
Existing wallets will still use traditional key generation.
127+
128+
Backups of HD wallets, regardless of when they have been created, can
129+
therefore be used to re-generate all possible private keys, even the
130+
ones which haven't already been generated during the time of the backup.
131+
132+
HD key generation for new wallets can be disabled by `-usehd=0`. Keep in
133+
mind that this flag only has affect on newly created wallets.
134+
You can't disable HD key generation once you have created a HD wallet.
135+
136+
There is no distinction between internal (change) and external keys.
137+
138+
[Pull request](https://github.com/bitcoin/bitcoin/pull/8035/files), [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
139+
122140
### GUI
123141

124142
### Tests

src/wallet/wallet.cpp

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE;
4242
bool fSendFreeTransactions = DEFAULT_SEND_FREE_TRANSACTIONS;
4343

4444
const char * DEFAULT_WALLET_DAT = "wallet.dat";
45+
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
4546

4647
/**
4748
* Fees smaller than this (in satoshi) are considered zero fee (for transaction creation)
@@ -91,7 +92,51 @@ CPubKey CWallet::GenerateNewKey()
9192
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
9293

9394
CKey secret;
94-
secret.MakeNewKey(fCompressed);
95+
96+
// Create new metadata
97+
int64_t nCreationTime = GetTime();
98+
CKeyMetadata metadata(nCreationTime);
99+
100+
// use HD key derivation if HD was enabled during wallet creation
101+
if (!hdChain.masterKeyID.IsNull()) {
102+
// for now we use a fixed keypath scheme of m/0'/0'/k
103+
CKey key; //master key seed (256bit)
104+
CExtKey masterKey; //hd master key
105+
CExtKey accountKey; //key at m/0'
106+
CExtKey externalChainChildKey; //key at m/0'/0'
107+
CExtKey childKey; //key at m/0'/0'/<n>'
108+
109+
// try to get the master key
110+
if (!GetKey(hdChain.masterKeyID, key))
111+
throw std::runtime_error("CWallet::GenerateNewKey(): Master key not found");
112+
113+
masterKey.SetMaster(key.begin(), key.size());
114+
115+
// derive m/0'
116+
// use hardened derivation (child keys >= 0x80000000 are hardened after bip32)
117+
masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
118+
119+
// derive m/0'/0'
120+
accountKey.Derive(externalChainChildKey, BIP32_HARDENED_KEY_LIMIT);
121+
122+
// derive child key at next index, skip keys already known to the wallet
123+
do
124+
{
125+
// always derive hardened keys
126+
// childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
127+
// example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
128+
externalChainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
129+
// increment childkey index
130+
hdChain.nExternalChainCounter++;
131+
} while(HaveKey(childKey.key.GetPubKey().GetID()));
132+
secret = childKey.key;
133+
134+
// update the chain model in the database
135+
if (!CWalletDB(strWalletFile).WriteHDChain(hdChain))
136+
throw std::runtime_error("CWallet::GenerateNewKey(): Writing HD chain model failed");
137+
} else {
138+
secret.MakeNewKey(fCompressed);
139+
}
95140

96141
// Compressed public keys were introduced in version 0.6.0
97142
if (fCompressed)
@@ -100,9 +145,7 @@ CPubKey CWallet::GenerateNewKey()
100145
CPubKey pubkey = secret.GetPubKey();
101146
assert(secret.VerifyPubKey(pubkey));
102147

103-
// Create new metadata
104-
int64_t nCreationTime = GetTime();
105-
mapKeyMetadata[pubkey.GetID()] = CKeyMetadata(nCreationTime);
148+
mapKeyMetadata[pubkey.GetID()] = metadata;
106149
if (!nTimeFirstKey || nCreationTime < nTimeFirstKey)
107150
nTimeFirstKey = nCreationTime;
108151

@@ -1121,6 +1164,37 @@ CAmount CWallet::GetChange(const CTransaction& tx) const
11211164
return nChange;
11221165
}
11231166

1167+
bool CWallet::SetHDMasterKey(const CKey& key)
1168+
{
1169+
LOCK(cs_wallet);
1170+
1171+
// store the key as normal "key"/"ckey" object
1172+
// in the database
1173+
// key metadata is not required
1174+
CPubKey pubkey = key.GetPubKey();
1175+
if (!AddKeyPubKey(key, pubkey))
1176+
throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed");
1177+
1178+
// store the keyid (hash160) together with
1179+
// the child index counter in the database
1180+
// as a hdchain object
1181+
CHDChain newHdChain;
1182+
newHdChain.masterKeyID = pubkey.GetID();
1183+
SetHDChain(newHdChain, false);
1184+
1185+
return true;
1186+
}
1187+
1188+
bool CWallet::SetHDChain(const CHDChain& chain, bool memonly)
1189+
{
1190+
LOCK(cs_wallet);
1191+
if (!memonly && !CWalletDB(strWalletFile).WriteHDChain(chain))
1192+
throw runtime_error("AddHDChain(): writing chain failed");
1193+
1194+
hdChain = chain;
1195+
return true;
1196+
}
1197+
11241198
int64_t CWalletTx::GetTxTime() const
11251199
{
11261200
int64_t n = nTimeSmart;
@@ -3135,6 +3209,7 @@ std::string CWallet::GetWalletHelpString(bool showDebug)
31353209
strUsage += HelpMessageOpt("-sendfreetransactions", strprintf(_("Send transactions as zero-fee transactions if possible (default: %u)"), DEFAULT_SEND_FREE_TRANSACTIONS));
31363210
strUsage += HelpMessageOpt("-spendzeroconfchange", strprintf(_("Spend unconfirmed change when sending transactions (default: %u)"), DEFAULT_SPEND_ZEROCONF_CHANGE));
31373211
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));
3212+
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));
31383213
strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup"));
31393214
strUsage += HelpMessageOpt("-wallet=<file>", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT));
31403215
strUsage += HelpMessageOpt("-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %u)"), DEFAULT_WALLETBROADCAST));
@@ -3222,6 +3297,13 @@ bool CWallet::InitLoadWallet()
32223297
if (fFirstRun)
32233298
{
32243299
// Create new keyUser and set as default key
3300+
if (GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET)) {
3301+
// generate a new master key
3302+
CKey key;
3303+
key.MakeNewKey(true);
3304+
if (!walletInstance->SetHDMasterKey(key))
3305+
throw std::runtime_error("CWallet::GenerateNewKey(): Storing master key failed");
3306+
}
32253307
CPubKey newDefaultKey;
32263308
if (walletInstance->GetKeyFromPool(newDefaultKey)) {
32273309
walletInstance->SetDefaultKey(newDefaultKey);
@@ -3231,6 +3313,13 @@ bool CWallet::InitLoadWallet()
32313313

32323314
walletInstance->SetBestChain(chainActive.GetLocator());
32333315
}
3316+
else if (mapArgs.count("-usehd")) {
3317+
bool useHD = GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET);
3318+
if (!walletInstance->hdChain.masterKeyID.IsNull() && !useHD)
3319+
return InitError(strprintf(_("Error loading %s: You can't disable HD on a already existing HD wallet"), walletFile));
3320+
if (walletInstance->hdChain.masterKeyID.IsNull() && useHD)
3321+
return InitError(strprintf(_("Error loading %s: You can't enable HD on a already existing non-HD wallet"), walletFile));
3322+
}
32343323

32353324
LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart);
32363325

src/wallet/wallet.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 2;
5757
static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000;
5858
static const bool DEFAULT_WALLETBROADCAST = true;
5959

60+
//! if set, all keys will be derived by using BIP32
61+
static const bool DEFAULT_USE_HD_WALLET = true;
62+
6063
extern const char * DEFAULT_WALLET_DAT;
6164

6265
class CBlockIndex;
@@ -574,6 +577,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
574577

575578
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>);
576579

580+
/* the hd chain data model (external chain counters) */
581+
CHDChain hdChain;
582+
577583
public:
578584
/*
579585
* Main wallet lock.
@@ -889,6 +895,12 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
889895
static bool ParameterInteraction();
890896

891897
bool BackupWallet(const std::string& strDest);
898+
899+
/* Set the hd chain model (chain child index counters) */
900+
bool SetHDChain(const CHDChain& chain, bool memonly);
901+
902+
/* Set the current hd master key (will reset the chain child index counters) */
903+
bool SetHDMasterKey(const CKey& key);
892904
};
893905

894906
/** A key allocated from the key pool. */

src/wallet/walletdb.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,16 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
599599
return false;
600600
}
601601
}
602+
else if (strType == "hdchain")
603+
{
604+
CHDChain chain;
605+
ssValue >> chain;
606+
if (!pwallet->SetHDChain(chain, true))
607+
{
608+
strErr = "Error reading wallet database: SetHDChain failed";
609+
return false;
610+
}
611+
}
602612
} catch (...)
603613
{
604614
return false;
@@ -1003,3 +1013,10 @@ bool CWalletDB::EraseDestData(const std::string &address, const std::string &key
10031013
nWalletDBUpdated++;
10041014
return Erase(std::make_pair(std::string("destdata"), std::make_pair(address, key)));
10051015
}
1016+
1017+
1018+
bool CWalletDB::WriteHDChain(const CHDChain& chain)
1019+
{
1020+
nWalletDBUpdated++;
1021+
return Write(std::string("hdchain"), chain);
1022+
}

src/wallet/walletdb.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,35 @@ enum DBErrors
4040
DB_NEED_REWRITE
4141
};
4242

43+
/* simple hd chain data model */
44+
class CHDChain
45+
{
46+
public:
47+
uint32_t nExternalChainCounter;
48+
CKeyID masterKeyID; //!< master key hash160
49+
50+
static const int CURRENT_VERSION = 1;
51+
int nVersion;
52+
53+
CHDChain() { SetNull(); }
54+
ADD_SERIALIZE_METHODS;
55+
template <typename Stream, typename Operation>
56+
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion)
57+
{
58+
READWRITE(this->nVersion);
59+
nVersion = this->nVersion;
60+
READWRITE(nExternalChainCounter);
61+
READWRITE(masterKeyID);
62+
}
63+
64+
void SetNull()
65+
{
66+
nVersion = CHDChain::CURRENT_VERSION;
67+
nExternalChainCounter = 0;
68+
masterKeyID.SetNull();
69+
}
70+
};
71+
4372
class CKeyMetadata
4473
{
4574
public:
@@ -134,6 +163,9 @@ class CWalletDB : public CDB
134163
static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys);
135164
static bool Recover(CDBEnv& dbenv, const std::string& filename);
136165

166+
//! write the hdchain model (external chain child index counter)
167+
bool WriteHDChain(const CHDChain& chain);
168+
137169
private:
138170
CWalletDB(const CWalletDB&);
139171
void operator=(const CWalletDB&);

0 commit comments

Comments
 (0)