New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Wallet] Add simplest BIP32/deterministic key generation implementation #8035

Merged
merged 4 commits into from Jun 14, 2016
View
@@ -9,6 +9,7 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.12.0**):
* [`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)).
* [`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)).
* [`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)).
* [`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)).
* [`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)).
* [`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)).
* [`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)).
View
@@ -112,6 +112,24 @@ feerate. [BIP 133](https://github.com/bitcoin/bips/blob/master/bip-0133.mediawik
### Wallet
Hierarchical Deterministic Key Generation
-----------------------------------------
Newly created wallets will use hierarchical deterministic key generation
according to BIP32 (keypath m/0'/0'/k').
Existing wallets will still use traditional key generation.
Backups of HD wallets, regardless of when they have been created, can
therefore be used to re-generate all possible private keys, even the
ones which haven't already been generated during the time of the backup.
HD key generation for new wallets can be disabled by `-usehd=0`. Keep in
mind that this flag only has affect on newly created wallets.
You can't disable HD key generation once you have created a HD wallet.
There is no distinction between internal (change) and external keys.
[Pull request](https://github.com/bitcoin/bitcoin/pull/8035/files), [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
### GUI
### Tests
View
@@ -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))

This comment has been minimized.

@instagibbs

instagibbs May 12, 2016

Member

nit: simply calling AddKey should work here nevermind, wrong function

@instagibbs

instagibbs May 12, 2016

Member

nit: simply calling AddKey should work here nevermind, wrong function

This comment has been minimized.

@sipa

sipa Jun 1, 2016

Member

I'd like to know why this comment does not apply.

EDIT: because you need pubkey below anyway, ok!

@sipa

sipa Jun 1, 2016

Member

I'd like to know why this comment does not apply.

EDIT: because you need pubkey below anyway, ok!

throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed");

This comment has been minimized.

@MarcoFalke

MarcoFalke Jul 8, 2016

Member

Nit: Wrong function name

@MarcoFalke

MarcoFalke Jul 8, 2016

Member

Nit: Wrong function name

// 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));

This comment has been minimized.

@MarcoFalke

MarcoFalke Jun 10, 2016

Member

Nit: Might as well call this -createhdwallet or similar. Using a lengthy and more verbose option does not hurt if it is expected to be changed from its default only very rarely.

@MarcoFalke

MarcoFalke Jun 10, 2016

Member

Nit: Might as well call this -createhdwallet or similar. Using a lengthy and more verbose option does not hurt if it is expected to be changed from its default only very rarely.

This comment has been minimized.

@jonasschnelli

jonasschnelli Jun 10, 2016

Member

Agree that -createhdwallet would make more sense.

@jonasschnelli

jonasschnelli Jun 10, 2016

Member

Agree that -createhdwallet would make more sense.

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);
View
@@ -57,6 +57,9 @@ static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 2;
static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000;
static const bool DEFAULT_WALLETBROADCAST = true;
//! if set, all keys will be derived by using BIP32
static const bool DEFAULT_USE_HD_WALLET = true;
extern const char * DEFAULT_WALLET_DAT;
class CBlockIndex;
@@ -574,6 +577,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>);
/* the hd chain data model (external chain counters) */
CHDChain hdChain;
public:
/*
* Main wallet lock.
@@ -887,6 +893,12 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
static bool ParameterInteraction();
bool BackupWallet(const std::string& strDest);
/* 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. */
View
@@ -599,6 +599,16 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
return false;
}
}
else if (strType == "hdchain")

This comment has been minimized.

@paveljanik

paveljanik May 10, 2016

Contributor

Repeating "hdchain" several times reminds me there was some patch redefining these as constants...

@paveljanik

paveljanik May 10, 2016

Contributor

Repeating "hdchain" several times reminds me there was some patch redefining these as constants...

This comment has been minimized.

@laanwj

laanwj May 10, 2016

Member

Which one? We did the redefine-magic-strings-as-constants it in other places, but also for the wallet?

@laanwj

laanwj May 10, 2016

Member

Which one? We did the redefine-magic-strings-as-constants it in other places, but also for the wallet?

This comment has been minimized.

@paveljanik

paveljanik May 10, 2016

Contributor

Yes, because some of them are repeated several times in the file. But maybe I'm remembering some other patch.

@paveljanik

paveljanik May 10, 2016

Contributor

Yes, because some of them are repeated several times in the file. But maybe I'm remembering some other patch.

This comment has been minimized.

@paveljanik

paveljanik May 10, 2016

Contributor

Ah, it probably was #5707.

@paveljanik

paveljanik May 10, 2016

Contributor

Ah, it probably was #5707.

This comment has been minimized.

@jonasschnelli

jonasschnelli May 10, 2016

Member

Yes. I did refactor out these stings into constants serval times. But this should be done in another PR and hopefully after this has been merged.

@jonasschnelli

jonasschnelli May 10, 2016

Member

Yes. I did refactor out these stings into constants serval times. But this should be done in another PR and hopefully after this has been merged.

This comment has been minimized.

@paveljanik

paveljanik May 10, 2016

Contributor

Definitely not for this PR, of course.

@paveljanik

paveljanik May 10, 2016

Contributor

Definitely not for this PR, of course.

{
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);
}
View
@@ -40,6 +40,35 @@ enum DBErrors
DB_NEED_REWRITE
};
/* 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 <typename Stream, typename Operation>
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();
}
};
class CKeyMetadata
{
public:
@@ -134,6 +163,9 @@ class CWalletDB : public CDB
static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys);
static bool Recover(CDBEnv& dbenv, const std::string& filename);
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain);
private:
CWalletDB(const CWalletDB&);
void operator=(const CWalletDB&);
ProTip! Use n and p to navigate between commits in a pull request.