Skip to content

Commit 02592f4

Browse files
committed
[Wallet] split the keypool in an internal and external part
1 parent a230b05 commit 02592f4

File tree

8 files changed

+180
-76
lines changed

8 files changed

+180
-76
lines changed

src/wallet/rpcwallet.cpp

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
221221

222222
CReserveKey reservekey(pwallet);
223223
CPubKey vchPubKey;
224-
if (!reservekey.GetReservedKey(vchPubKey))
224+
if (!reservekey.GetReservedKey(vchPubKey, true))
225225
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
226226

227227
reservekey.KeepKey();
@@ -2418,16 +2418,17 @@ UniValue getwalletinfo(const JSONRPCRequest& request)
24182418
"Returns an object containing various wallet state info.\n"
24192419
"\nResult:\n"
24202420
"{\n"
2421-
" \"walletversion\": xxxxx, (numeric) the wallet version\n"
2422-
" \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
2423-
" \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
2424-
" \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n"
2425-
" \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
2426-
" \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n"
2427-
" \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n"
2428-
" \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
2429-
" \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
2430-
" \"hdmasterkeyid\": \"<hash160>\" (string) the Hash160 of the HD master pubkey\n"
2421+
" \"walletversion\": xxxxx, (numeric) the wallet version\n"
2422+
" \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
2423+
" \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
2424+
" \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n"
2425+
" \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
2426+
" \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n"
2427+
" \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n"
2428+
" \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (change outputs, 10% of the -keypoolsize target, only appears if HD is enabled)\n"
2429+
" \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
2430+
" \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
2431+
" \"hdmasterkeyid\": \"<hash160>\" (string) the Hash160 of the HD master pubkey\n"
24312432
"}\n"
24322433
"\nExamples:\n"
24332434
+ HelpExampleCli("getwalletinfo", "")
@@ -2437,18 +2438,22 @@ UniValue getwalletinfo(const JSONRPCRequest& request)
24372438
LOCK2(cs_main, pwallet->cs_wallet);
24382439

24392440
UniValue obj(UniValue::VOBJ);
2441+
size_t kpExternalSize = (int)pwallet->KeypoolCountExternalKeys();
24402442
obj.push_back(Pair("walletversion", pwallet->GetVersion()));
24412443
obj.push_back(Pair("balance", ValueFromAmount(pwallet->GetBalance())));
24422444
obj.push_back(Pair("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance())));
24432445
obj.push_back(Pair("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance())));
24442446
obj.push_back(Pair("txcount", (int)pwallet->mapWallet.size()));
24452447
obj.push_back(Pair("keypoololdest", pwallet->GetOldestKeyPoolTime()));
2446-
obj.push_back(Pair("keypoolsize", (int)pwallet->GetKeyPoolSize()));
2448+
obj.push_back(Pair("keypoolsize", (int64_t)kpExternalSize));
2449+
CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID;
2450+
if (!masterKeyID.IsNull()) {
2451+
obj.push_back(Pair("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize)));
2452+
}
24472453
if (pwallet->IsCrypted()) {
24482454
obj.push_back(Pair("unlocked_until", pwallet->nRelockTime));
24492455
}
24502456
obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK())));
2451-
CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID;
24522457
if (!masterKeyID.IsNull())
24532458
obj.push_back(Pair("hdmasterkeyid", masterKeyID.GetHex()));
24542459
return obj;

src/wallet/wallet.cpp

Lines changed: 85 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
8585
return &(it->second);
8686
}
8787

88-
CPubKey CWallet::GenerateNewKey()
88+
CPubKey CWallet::GenerateNewKey(bool internal)
8989
{
9090
AssertLockHeld(cs_wallet); // mapKeyMetadata
9191
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
@@ -98,7 +98,7 @@ CPubKey CWallet::GenerateNewKey()
9898

9999
// use HD key derivation if HD was enabled during wallet creation
100100
if (IsHDEnabled()) {
101-
DeriveNewChildKey(metadata, secret);
101+
DeriveNewChildKey(metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
102102
} else {
103103
secret.MakeNewKey(fCompressed);
104104
}
@@ -118,13 +118,13 @@ CPubKey CWallet::GenerateNewKey()
118118
return pubkey;
119119
}
120120

121-
void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret)
121+
void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool internal)
122122
{
123123
// for now we use a fixed keypath scheme of m/0'/0'/k
124124
CKey key; //master key seed (256bit)
125125
CExtKey masterKey; //hd master key
126126
CExtKey accountKey; //key at m/0'
127-
CExtKey externalChainChildKey; //key at m/0'/0'
127+
CExtKey chainChildKey; //key at m/0'/0' (external) or m/0'/1' (internal)
128128
CExtKey childKey; //key at m/0'/0'/<n>'
129129

130130
// try to get the master key
@@ -137,19 +137,22 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret)
137137
// use hardened derivation (child keys >= 0x80000000 are hardened after bip32)
138138
masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
139139

140-
// derive m/0'/0'
141-
accountKey.Derive(externalChainChildKey, BIP32_HARDENED_KEY_LIMIT);
140+
// derive m/0'/0' (external chain) OR m/0'/1' (internal chain)
141+
accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0));
142142

143143
// derive child key at next index, skip keys already known to the wallet
144144
do {
145145
// always derive hardened keys
146146
// childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
147147
// example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
148-
externalChainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
149-
metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'";
148+
chainChildKey.Derive(childKey, (internal ? hdChain.nInternalChainCounter : hdChain.nExternalChainCounter) | BIP32_HARDENED_KEY_LIMIT);
149+
metadata.hdKeypath = "m/0'/" + std::string(internal ? "1'/"+ std::to_string(hdChain.nInternalChainCounter) : "0'/" + std::to_string(hdChain.nExternalChainCounter)) + "'";
150150
metadata.hdMasterKeyID = hdChain.masterKeyID;
151151
// increment childkey index
152-
hdChain.nExternalChainCounter++;
152+
if (internal)
153+
hdChain.nInternalChainCounter++;
154+
else
155+
hdChain.nExternalChainCounter++;
153156
} while (HaveKey(childKey.key.GetPubKey().GetID()));
154157
secret = childKey.key;
155158

@@ -799,7 +802,7 @@ bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bFo
799802

800803
// Generate a new key
801804
if (bForceNew) {
802-
if (!GetKeyFromPool(account.vchPubKey))
805+
if (!GetKeyFromPool(account.vchPubKey, false))
803806
return false;
804807

805808
SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive");
@@ -1304,8 +1307,8 @@ bool CWallet::SetHDMasterKey(const CPubKey& pubkey)
13041307
{
13051308
LOCK(cs_wallet);
13061309

1307-
// ensure this wallet.dat can only be opened by clients supporting HD
1308-
SetMinVersion(FEATURE_HD);
1310+
// ensure this wallet.dat can only be opened by clients supporting HD with chain split
1311+
SetMinVersion(FEATURE_HD_SPLIT);
13091312

13101313
// store the keyid (hash160) together with
13111314
// the child index counter in the database
@@ -2445,7 +2448,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
24452448
// Reserve a new key pair from key pool
24462449
CPubKey vchPubKey;
24472450
bool ret;
2448-
ret = reservekey.GetReservedKey(vchPubKey);
2451+
ret = reservekey.GetReservedKey(vchPubKey, true);
24492452
if (!ret)
24502453
{
24512454
strFailReason = _("Keypool ran out, please call keypoolrefill first");
@@ -2899,18 +2902,31 @@ bool CWallet::NewKeyPool()
28992902
if (IsLocked())
29002903
return false;
29012904

2902-
int64_t nKeys = std::max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t)0);
2903-
for (int i = 0; i < nKeys; i++)
2904-
{
2905-
int64_t nIndex = i+1;
2906-
walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey()));
2907-
setKeyPool.insert(nIndex);
2908-
}
2909-
LogPrintf("CWallet::NewKeyPool wrote %d new keys\n", nKeys);
2905+
TopUpKeyPool();
2906+
LogPrintf("CWallet::NewKeyPool rewrote keypool\n");
29102907
}
29112908
return true;
29122909
}
29132910

2911+
size_t CWallet::KeypoolCountExternalKeys()
2912+
{
2913+
AssertLockHeld(cs_wallet); // setKeyPool
2914+
2915+
CWalletDB walletdb(strWalletFile);
2916+
2917+
// count amount of external keys
2918+
size_t amountE = 0;
2919+
for(const int64_t& id : setKeyPool)
2920+
{
2921+
CKeyPool tmpKeypool;
2922+
if (!walletdb.ReadPool(id, tmpKeypool))
2923+
throw std::runtime_error(std::string(__func__) + ": read failed");
2924+
amountE += !tmpKeypool.fInternal;
2925+
}
2926+
2927+
return amountE;
2928+
}
2929+
29142930
bool CWallet::TopUpKeyPool(unsigned int kpSize)
29152931
{
29162932
{
@@ -2919,30 +2935,46 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
29192935
if (IsLocked())
29202936
return false;
29212937

2922-
CWalletDB walletdb(strWalletFile);
2923-
29242938
// Top up key pool
29252939
unsigned int nTargetSize;
29262940
if (kpSize > 0)
29272941
nTargetSize = kpSize;
29282942
else
29292943
nTargetSize = std::max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0);
29302944

2931-
while (setKeyPool.size() < (nTargetSize + 1))
2945+
// count amount of available keys (internal, external)
2946+
// make sure the keypool of external keys fits the user selected target (-keypool)
2947+
// generate +20% internal keys (minimum 2 keys)
2948+
int64_t amountExternal = KeypoolCountExternalKeys();
2949+
int64_t amountInternal = setKeyPool.size() - amountExternal;
2950+
int64_t targetInternal = max((int64_t)ceil(nTargetSize * 0.2), (int64_t) 2);
2951+
int64_t missingExternal = max( (int64_t)(nTargetSize - amountExternal), (int64_t) 0);
2952+
int64_t missingInternal = max(targetInternal - amountInternal, (int64_t) 0);
2953+
2954+
if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT))
2955+
{
2956+
// don't create extra internal keys
2957+
missingInternal = 0;
2958+
}
2959+
bool internal = false;
2960+
CWalletDB walletdb(strWalletFile);
2961+
for (int64_t i = missingInternal + missingExternal; i--;)
29322962
{
29332963
int64_t nEnd = 1;
2964+
if (i < missingInternal)
2965+
internal = true;
29342966
if (!setKeyPool.empty())
29352967
nEnd = *(--setKeyPool.end()) + 1;
2936-
if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey())))
2968+
if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey(internal), internal)))
29372969
throw std::runtime_error(std::string(__func__) + ": writing generated key failed");
29382970
setKeyPool.insert(nEnd);
2939-
LogPrintf("keypool added key %d, size=%u\n", nEnd, setKeyPool.size());
2971+
LogPrintf("keypool added key %d, size=%u, internal=%d\n", nEnd, setKeyPool.size(), internal);
29402972
}
29412973
}
29422974
return true;
29432975
}
29442976

2945-
void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool)
2977+
void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool internal)
29462978
{
29472979
nIndex = -1;
29482980
keypool.vchPubKey = CPubKey();
@@ -2958,14 +2990,24 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool)
29582990

29592991
CWalletDB walletdb(strWalletFile);
29602992

2961-
nIndex = *(setKeyPool.begin());
2962-
setKeyPool.erase(setKeyPool.begin());
2963-
if (!walletdb.ReadPool(nIndex, keypool))
2964-
throw std::runtime_error(std::string(__func__) + ": read failed");
2965-
if (!HaveKey(keypool.vchPubKey.GetID()))
2966-
throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
2967-
assert(keypool.vchPubKey.IsValid());
2968-
LogPrintf("keypool reserve %d\n", nIndex);
2993+
// try to find a key that matches the internal/external filter
2994+
for(const int64_t& id : setKeyPool)
2995+
{
2996+
CKeyPool tmpKeypool;
2997+
if (!walletdb.ReadPool(id, tmpKeypool))
2998+
throw std::runtime_error(std::string(__func__) + ": read failed");
2999+
if (!HaveKey(tmpKeypool.vchPubKey.GetID()))
3000+
throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
3001+
if (!IsHDEnabled() || tmpKeypool.fInternal == internal)
3002+
{
3003+
nIndex = id;
3004+
keypool = tmpKeypool;
3005+
setKeyPool.erase(id);
3006+
assert(keypool.vchPubKey.IsValid());
3007+
LogPrintf("keypool reserve %d\n", nIndex);
3008+
return;
3009+
}
3010+
}
29693011
}
29703012
}
29713013

@@ -2990,17 +3032,17 @@ void CWallet::ReturnKey(int64_t nIndex)
29903032
LogPrintf("keypool return %d\n", nIndex);
29913033
}
29923034

2993-
bool CWallet::GetKeyFromPool(CPubKey& result)
3035+
bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
29943036
{
29953037
int64_t nIndex = 0;
29963038
CKeyPool keypool;
29973039
{
29983040
LOCK(cs_wallet);
2999-
ReserveKeyFromKeyPool(nIndex, keypool);
3041+
ReserveKeyFromKeyPool(nIndex, keypool, internal);
30003042
if (nIndex == -1)
30013043
{
30023044
if (IsLocked()) return false;
3003-
result = GenerateNewKey();
3045+
result = GenerateNewKey(internal);
30043046
return true;
30053047
}
30063048
KeepKey(nIndex);
@@ -3205,12 +3247,12 @@ std::set<CTxDestination> CWallet::GetAccountAddresses(const std::string& strAcco
32053247
return result;
32063248
}
32073249

3208-
bool CReserveKey::GetReservedKey(CPubKey& pubkey)
3250+
bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
32093251
{
32103252
if (nIndex == -1)
32113253
{
32123254
CKeyPool keypool;
3213-
pwallet->ReserveKeyFromKeyPool(nIndex, keypool);
3255+
pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal);
32143256
if (nIndex != -1)
32153257
vchPubKey = keypool.vchPubKey;
32163258
else {
@@ -3629,7 +3671,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
36293671
throw std::runtime_error(std::string(__func__) + ": Storing master key failed");
36303672
}
36313673
CPubKey newDefaultKey;
3632-
if (walletInstance->GetKeyFromPool(newDefaultKey)) {
3674+
if (walletInstance->GetKeyFromPool(newDefaultKey, false)) {
36333675
walletInstance->SetDefaultKey(newDefaultKey);
36343676
if (!walletInstance->SetAddressBook(walletInstance->vchDefaultKey.GetID(), "", "receive")) {
36353677
InitError(_("Cannot write default address") += "\n");
@@ -3890,10 +3932,11 @@ CKeyPool::CKeyPool()
38903932
nTime = GetTime();
38913933
}
38923934

3893-
CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn)
3935+
CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn)
38943936
{
38953937
nTime = GetTime();
38963938
vchPubKey = vchPubKeyIn;
3939+
fInternal = internalIn;
38973940
}
38983941

38993942
CWalletKey::CWalletKey(int64_t nExpires)

0 commit comments

Comments
 (0)