mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-12 15:09:59 +01:00
Merge #9294: Use internal HD chain for change outputs (hd split)
4115af7Fix rebase issue where pwalletMain was used instead of pwallet Ser./Deser. nInternalChainCounter as last element (Jonas Schnelli)9382f04Do not break backward compatibility during wallet encryption (Jonas Schnelli)1df08d1Add assertion for CanSupportFeature(FEATURE_HD_SPLIT) (Jonas Schnelli)cd468d0Define CWallet::DeriveNewChildKey() as private (Jonas Schnelli)ed79e4fOptimize GetOldestKeyPoolTime(), return as soon as we have both oldest keys (Jonas Schnelli)771a304Make sure we set the wallets min version to FEATURE_HD_SPLIT at the very first point (Jonas Schnelli)1b3b5c6Slightly modify fundrawtransaction.py test (change getnewaddress() into getrawchangeaddress()) (Jonas Schnelli)003e197Remove FEATURE_HD_SPLIT bump TODO (Jonas Schnelli)d9638e5Overhaul the internal/external key derive switch (Jonas Schnelli)1090502Fix superfluous cast and code style nits in RPC wallet-hd.py test (Jonas Schnelli)58e1483CKeyPool avoid "catch (...)" in SerializationOp (Jonas Schnelli)e138876Only show keypoolsize_hd_internal if HD split is enabled (Jonas Schnelli)add38d9GetOldestKeyPoolTime: if HD & HD Chain Split is enabled, response max(oldest-internal-key, oldest-external-key) (Jonas Schnelli)dd526c2Don't switch to HD-chain-split during wallet encryption of non HD-chain-split wallets (Jonas Schnelli)79df9dfSwitch to 100% for the HD internal keypool size (Jonas Schnelli)bcafca1Make sure we always generate one keypool key at minimum (Jonas Schnelli)d0a627aFix issue where CDataStream->nVersion was taken a CKeyPool record version (Jonas Schnelli)9af8f00Make sure we hand out keypool keys if HD_SPLIT is not enabled (Jonas Schnelli)469a47bMake sure ReserveKeyFromKeyPool only hands out internal keys if HD_SPLIT is supported (Jonas Schnelli)05a9b49Fix wrong keypool internal size in RPC getwalletinfo help (Jonas Schnelli)01de822Removed redundant IsLocked() check in NewKeyPool() (Jonas Schnelli)d59531dImmediately return setKeyPool's size if HD or HD_SPLIT is disabled or not supported (Jonas Schnelli)02592f4[Wallet] split the keypool in an internal and external part (Jonas Schnelli) Tree-SHA512: 80d355d5e844b48c3163b56c788ab8b5b5285db0ceeb19858a3ef517d5a702afeca21dbae526d7b8fb4101c2a745af1d92bf557c40cf516780f17992bf678c1a
This commit is contained in:
@@ -85,7 +85,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
|
||||
return &(it->second);
|
||||
}
|
||||
|
||||
CPubKey CWallet::GenerateNewKey()
|
||||
CPubKey CWallet::GenerateNewKey(bool internal)
|
||||
{
|
||||
AssertLockHeld(cs_wallet); // mapKeyMetadata
|
||||
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
|
||||
@@ -98,7 +98,7 @@ CPubKey CWallet::GenerateNewKey()
|
||||
|
||||
// use HD key derivation if HD was enabled during wallet creation
|
||||
if (IsHDEnabled()) {
|
||||
DeriveNewChildKey(metadata, secret);
|
||||
DeriveNewChildKey(metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
|
||||
} else {
|
||||
secret.MakeNewKey(fCompressed);
|
||||
}
|
||||
@@ -118,13 +118,13 @@ CPubKey CWallet::GenerateNewKey()
|
||||
return pubkey;
|
||||
}
|
||||
|
||||
void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret)
|
||||
void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool internal)
|
||||
{
|
||||
// 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 chainChildKey; //key at m/0'/0' (external) or m/0'/1' (internal)
|
||||
CExtKey childKey; //key at m/0'/0'/<n>'
|
||||
|
||||
// try to get the master key
|
||||
@@ -137,22 +137,28 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret)
|
||||
// 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 m/0'/0' (external chain) OR m/0'/1' (internal chain)
|
||||
assert(internal ? CanSupportFeature(FEATURE_HD_SPLIT) : true);
|
||||
accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0));
|
||||
|
||||
// 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);
|
||||
metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'";
|
||||
metadata.hdMasterKeyID = hdChain.masterKeyID;
|
||||
// increment childkey index
|
||||
hdChain.nExternalChainCounter++;
|
||||
if (internal) {
|
||||
chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'";
|
||||
hdChain.nInternalChainCounter++;
|
||||
}
|
||||
else {
|
||||
chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'";
|
||||
hdChain.nExternalChainCounter++;
|
||||
}
|
||||
} while (HaveKey(childKey.key.GetPubKey().GetID()));
|
||||
secret = childKey.key;
|
||||
|
||||
metadata.hdMasterKeyID = hdChain.masterKeyID;
|
||||
// update the chain model in the database
|
||||
if (!CWalletDB(strWalletFile).WriteHDChain(hdChain))
|
||||
throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
|
||||
@@ -633,7 +639,9 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
||||
if (IsHDEnabled()) {
|
||||
CKey key;
|
||||
CPubKey masterPubKey = GenerateNewHDMasterKey();
|
||||
if (!SetHDMasterKey(masterPubKey))
|
||||
// preserve the old chains version to not break backward compatibility
|
||||
CHDChain oldChain = GetHDChain();
|
||||
if (!SetHDMasterKey(masterPubKey, &oldChain))
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -799,7 +807,7 @@ bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bFo
|
||||
|
||||
// Generate a new key
|
||||
if (bForceNew) {
|
||||
if (!GetKeyFromPool(account.vchPubKey))
|
||||
if (!GetKeyFromPool(account.vchPubKey, false))
|
||||
return false;
|
||||
|
||||
SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive");
|
||||
@@ -1300,17 +1308,17 @@ CPubKey CWallet::GenerateNewHDMasterKey()
|
||||
return pubkey;
|
||||
}
|
||||
|
||||
bool CWallet::SetHDMasterKey(const CPubKey& pubkey)
|
||||
bool CWallet::SetHDMasterKey(const CPubKey& pubkey, CHDChain *possibleOldChain)
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
|
||||
// ensure this wallet.dat can only be opened by clients supporting HD
|
||||
SetMinVersion(FEATURE_HD);
|
||||
|
||||
// store the keyid (hash160) together with
|
||||
// the child index counter in the database
|
||||
// as a hdchain object
|
||||
CHDChain newHdChain;
|
||||
if (possibleOldChain) {
|
||||
// preserve the old chains version
|
||||
newHdChain.nVersion = possibleOldChain->nVersion;
|
||||
}
|
||||
newHdChain.masterKeyID = pubkey.GetID();
|
||||
SetHDChain(newHdChain, false);
|
||||
|
||||
@@ -2445,7 +2453,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||
// Reserve a new key pair from key pool
|
||||
CPubKey vchPubKey;
|
||||
bool ret;
|
||||
ret = reservekey.GetReservedKey(vchPubKey);
|
||||
ret = reservekey.GetReservedKey(vchPubKey, true);
|
||||
if (!ret)
|
||||
{
|
||||
strFailReason = _("Keypool ran out, please call keypoolrefill first");
|
||||
@@ -2896,21 +2904,37 @@ bool CWallet::NewKeyPool()
|
||||
walletdb.ErasePool(nIndex);
|
||||
setKeyPool.clear();
|
||||
|
||||
if (IsLocked())
|
||||
if (!TopUpKeyPool()) {
|
||||
return false;
|
||||
|
||||
int64_t nKeys = std::max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t)0);
|
||||
for (int i = 0; i < nKeys; i++)
|
||||
{
|
||||
int64_t nIndex = i+1;
|
||||
walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey()));
|
||||
setKeyPool.insert(nIndex);
|
||||
}
|
||||
LogPrintf("CWallet::NewKeyPool wrote %d new keys\n", nKeys);
|
||||
LogPrintf("CWallet::NewKeyPool rewrote keypool\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t CWallet::KeypoolCountExternalKeys()
|
||||
{
|
||||
AssertLockHeld(cs_wallet); // setKeyPool
|
||||
|
||||
// immediately return setKeyPool's size if HD or HD_SPLIT is disabled or not supported
|
||||
if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT))
|
||||
return setKeyPool.size();
|
||||
|
||||
CWalletDB walletdb(strWalletFile);
|
||||
|
||||
// count amount of external keys
|
||||
size_t amountE = 0;
|
||||
for(const int64_t& id : setKeyPool)
|
||||
{
|
||||
CKeyPool tmpKeypool;
|
||||
if (!walletdb.ReadPool(id, tmpKeypool))
|
||||
throw std::runtime_error(std::string(__func__) + ": read failed");
|
||||
amountE += !tmpKeypool.fInternal;
|
||||
}
|
||||
|
||||
return amountE;
|
||||
}
|
||||
|
||||
bool CWallet::TopUpKeyPool(unsigned int kpSize)
|
||||
{
|
||||
{
|
||||
@@ -2919,8 +2943,6 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
|
||||
if (IsLocked())
|
||||
return false;
|
||||
|
||||
CWalletDB walletdb(strWalletFile);
|
||||
|
||||
// Top up key pool
|
||||
unsigned int nTargetSize;
|
||||
if (kpSize > 0)
|
||||
@@ -2928,21 +2950,37 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
|
||||
else
|
||||
nTargetSize = std::max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0);
|
||||
|
||||
while (setKeyPool.size() < (nTargetSize + 1))
|
||||
// count amount of available keys (internal, external)
|
||||
// make sure the keypool of external and internal keys fits the user selected target (-keypool)
|
||||
int64_t amountExternal = KeypoolCountExternalKeys();
|
||||
int64_t amountInternal = setKeyPool.size() - amountExternal;
|
||||
int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - amountExternal, (int64_t) 0);
|
||||
int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - amountInternal, (int64_t) 0);
|
||||
|
||||
if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT))
|
||||
{
|
||||
// don't create extra internal keys
|
||||
missingInternal = 0;
|
||||
}
|
||||
bool internal = false;
|
||||
CWalletDB walletdb(strWalletFile);
|
||||
for (int64_t i = missingInternal + missingExternal; i--;)
|
||||
{
|
||||
int64_t nEnd = 1;
|
||||
if (i < missingInternal)
|
||||
internal = true;
|
||||
if (!setKeyPool.empty())
|
||||
nEnd = *(--setKeyPool.end()) + 1;
|
||||
if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey())))
|
||||
if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey(internal), internal)))
|
||||
throw std::runtime_error(std::string(__func__) + ": writing generated key failed");
|
||||
setKeyPool.insert(nEnd);
|
||||
LogPrintf("keypool added key %d, size=%u\n", nEnd, setKeyPool.size());
|
||||
LogPrintf("keypool added key %d, size=%u, internal=%d\n", nEnd, setKeyPool.size(), internal);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool)
|
||||
void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool internal)
|
||||
{
|
||||
nIndex = -1;
|
||||
keypool.vchPubKey = CPubKey();
|
||||
@@ -2958,14 +2996,24 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool)
|
||||
|
||||
CWalletDB walletdb(strWalletFile);
|
||||
|
||||
nIndex = *(setKeyPool.begin());
|
||||
setKeyPool.erase(setKeyPool.begin());
|
||||
if (!walletdb.ReadPool(nIndex, keypool))
|
||||
throw std::runtime_error(std::string(__func__) + ": read failed");
|
||||
if (!HaveKey(keypool.vchPubKey.GetID()))
|
||||
throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
|
||||
assert(keypool.vchPubKey.IsValid());
|
||||
LogPrintf("keypool reserve %d\n", nIndex);
|
||||
// try to find a key that matches the internal/external filter
|
||||
for(const int64_t& id : setKeyPool)
|
||||
{
|
||||
CKeyPool tmpKeypool;
|
||||
if (!walletdb.ReadPool(id, tmpKeypool))
|
||||
throw std::runtime_error(std::string(__func__) + ": read failed");
|
||||
if (!HaveKey(tmpKeypool.vchPubKey.GetID()))
|
||||
throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
|
||||
if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT) || tmpKeypool.fInternal == internal)
|
||||
{
|
||||
nIndex = id;
|
||||
keypool = tmpKeypool;
|
||||
setKeyPool.erase(id);
|
||||
assert(keypool.vchPubKey.IsValid());
|
||||
LogPrintf("keypool reserve %d\n", nIndex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2990,17 +3038,17 @@ void CWallet::ReturnKey(int64_t nIndex)
|
||||
LogPrintf("keypool return %d\n", nIndex);
|
||||
}
|
||||
|
||||
bool CWallet::GetKeyFromPool(CPubKey& result)
|
||||
bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
|
||||
{
|
||||
int64_t nIndex = 0;
|
||||
CKeyPool keypool;
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
ReserveKeyFromKeyPool(nIndex, keypool);
|
||||
ReserveKeyFromKeyPool(nIndex, keypool, internal);
|
||||
if (nIndex == -1)
|
||||
{
|
||||
if (IsLocked()) return false;
|
||||
result = GenerateNewKey();
|
||||
result = GenerateNewKey(internal);
|
||||
return true;
|
||||
}
|
||||
KeepKey(nIndex);
|
||||
@@ -3017,9 +3065,33 @@ int64_t CWallet::GetOldestKeyPoolTime()
|
||||
if (setKeyPool.empty())
|
||||
return GetTime();
|
||||
|
||||
// load oldest key from keypool, get time and return
|
||||
CKeyPool keypool;
|
||||
CWalletDB walletdb(strWalletFile);
|
||||
|
||||
if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT))
|
||||
{
|
||||
// if HD & HD Chain Split is enabled, response max(oldest-internal-key, oldest-external-key)
|
||||
int64_t now = GetTime();
|
||||
int64_t oldest_external = now, oldest_internal = now;
|
||||
|
||||
for(const int64_t& id : setKeyPool)
|
||||
{
|
||||
if (!walletdb.ReadPool(id, keypool)) {
|
||||
throw std::runtime_error(std::string(__func__) + ": read failed");
|
||||
}
|
||||
if (keypool.fInternal && keypool.nTime < oldest_internal) {
|
||||
oldest_internal = keypool.nTime;
|
||||
}
|
||||
else if (!keypool.fInternal && keypool.nTime < oldest_external) {
|
||||
oldest_external = keypool.nTime;
|
||||
}
|
||||
if (oldest_internal != now && oldest_external != now) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return std::max(oldest_internal, oldest_external);
|
||||
}
|
||||
// load oldest key from keypool, get time and return
|
||||
int64_t nIndex = *(setKeyPool.begin());
|
||||
if (!walletdb.ReadPool(nIndex, keypool))
|
||||
throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed");
|
||||
@@ -3205,12 +3277,12 @@ std::set<CTxDestination> CWallet::GetAccountAddresses(const std::string& strAcco
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CReserveKey::GetReservedKey(CPubKey& pubkey)
|
||||
bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
|
||||
{
|
||||
if (nIndex == -1)
|
||||
{
|
||||
CKeyPool keypool;
|
||||
pwallet->ReserveKeyFromKeyPool(nIndex, keypool);
|
||||
pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal);
|
||||
if (nIndex != -1)
|
||||
vchPubKey = keypool.vchPubKey;
|
||||
else {
|
||||
@@ -3623,13 +3695,17 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
|
||||
{
|
||||
// Create new keyUser and set as default key
|
||||
if (GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) && !walletInstance->IsHDEnabled()) {
|
||||
|
||||
// ensure this wallet.dat can only be opened by clients supporting HD with chain split
|
||||
walletInstance->SetMinVersion(FEATURE_HD_SPLIT);
|
||||
|
||||
// generate a new master key
|
||||
CPubKey masterPubKey = walletInstance->GenerateNewHDMasterKey();
|
||||
if (!walletInstance->SetHDMasterKey(masterPubKey))
|
||||
throw std::runtime_error(std::string(__func__) + ": Storing master key failed");
|
||||
}
|
||||
CPubKey newDefaultKey;
|
||||
if (walletInstance->GetKeyFromPool(newDefaultKey)) {
|
||||
if (walletInstance->GetKeyFromPool(newDefaultKey, false)) {
|
||||
walletInstance->SetDefaultKey(newDefaultKey);
|
||||
if (!walletInstance->SetAddressBook(walletInstance->vchDefaultKey.GetID(), "", "receive")) {
|
||||
InitError(_("Cannot write default address") += "\n");
|
||||
@@ -3888,12 +3964,14 @@ bool CWallet::BackupWallet(const std::string& strDest)
|
||||
CKeyPool::CKeyPool()
|
||||
{
|
||||
nTime = GetTime();
|
||||
fInternal = false;
|
||||
}
|
||||
|
||||
CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn)
|
||||
CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn)
|
||||
{
|
||||
nTime = GetTime();
|
||||
vchPubKey = vchPubKeyIn;
|
||||
fInternal = internalIn;
|
||||
}
|
||||
|
||||
CWalletKey::CWalletKey(int64_t nExpires)
|
||||
|
||||
Reference in New Issue
Block a user