aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/wallet/rpcwallet.cpp34
-rw-r--r--src/wallet/wallet.cpp180
-rw-r--r--src/wallet/wallet.h38
-rw-r--r--src/wallet/walletdb.h8
-rwxr-xr-xtest/functional/fundrawtransaction.py4
-rwxr-xr-xtest/functional/keypool.py41
-rwxr-xr-xtest/functional/wallet-dump.py6
-rwxr-xr-xtest/functional/wallet-hd.py19
8 files changed, 241 insertions, 89 deletions
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 84e7eb60d7..22b1e3522c 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -221,7 +221,7 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
CReserveKey reservekey(pwallet);
CPubKey vchPubKey;
- if (!reservekey.GetReservedKey(vchPubKey))
+ if (!reservekey.GetReservedKey(vchPubKey, true))
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
reservekey.KeepKey();
@@ -1852,7 +1852,7 @@ UniValue gettransaction(const JSONRPCRequest& request)
" \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
" 'send' category of transactions.\n"
" \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
- " 'send' category of transactions.\n"
+ " 'send' category of transactions.\n"
" }\n"
" ,...\n"
" ],\n"
@@ -2418,16 +2418,17 @@ UniValue getwalletinfo(const JSONRPCRequest& request)
"Returns an object containing various wallet state info.\n"
"\nResult:\n"
"{\n"
- " \"walletversion\": xxxxx, (numeric) the wallet version\n"
- " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
- " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
- " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n"
- " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
- " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n"
- " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n"
- " \"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"
- " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
- " \"hdmasterkeyid\": \"<hash160>\" (string) the Hash160 of the HD master pubkey\n"
+ " \"walletversion\": xxxxx, (numeric) the wallet version\n"
+ " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
+ " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
+ " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n"
+ " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
+ " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n"
+ " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n"
+ " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n"
+ " \"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"
+ " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
+ " \"hdmasterkeyid\": \"<hash160>\" (string) the Hash160 of the HD master pubkey\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getwalletinfo", "")
@@ -2437,18 +2438,23 @@ UniValue getwalletinfo(const JSONRPCRequest& request)
LOCK2(cs_main, pwallet->cs_wallet);
UniValue obj(UniValue::VOBJ);
+
+ size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
obj.push_back(Pair("walletversion", pwallet->GetVersion()));
obj.push_back(Pair("balance", ValueFromAmount(pwallet->GetBalance())));
obj.push_back(Pair("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance())));
obj.push_back(Pair("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance())));
obj.push_back(Pair("txcount", (int)pwallet->mapWallet.size()));
obj.push_back(Pair("keypoololdest", pwallet->GetOldestKeyPoolTime()));
- obj.push_back(Pair("keypoolsize", (int)pwallet->GetKeyPoolSize()));
+ obj.push_back(Pair("keypoolsize", (int64_t)kpExternalSize));
+ CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID;
+ if (!masterKeyID.IsNull() && pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
+ obj.push_back(Pair("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize)));
+ }
if (pwallet->IsCrypted()) {
obj.push_back(Pair("unlocked_until", pwallet->nRelockTime));
}
obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK())));
- CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID;
if (!masterKeyID.IsNull())
obj.push_back(Pair("hdmasterkeyid", masterKeyID.GetHex()));
return obj;
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 39e2ab7c1f..e631c86bb2 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -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)
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index ae4321eef8..ccede60097 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -86,6 +86,9 @@ enum WalletFeature
FEATURE_COMPRPUBKEY = 60000, // compressed public keys
FEATURE_HD = 130000, // Hierarchical key derivation after BIP32 (HD Wallet)
+
+ FEATURE_HD_SPLIT = 139900, // Wallet with HD chain split (change outputs will use m/0'/1'/k)
+
FEATURE_LATEST = FEATURE_COMPRPUBKEY // HD is optional, use FEATURE_COMPRPUBKEY as latest version
};
@@ -96,9 +99,10 @@ class CKeyPool
public:
int64_t nTime;
CPubKey vchPubKey;
+ bool fInternal; // for change outputs
CKeyPool();
- CKeyPool(const CPubKey& vchPubKeyIn);
+ CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn);
ADD_SERIALIZE_METHODS;
@@ -109,6 +113,19 @@ public:
READWRITE(nVersion);
READWRITE(nTime);
READWRITE(vchPubKey);
+ if (ser_action.ForRead()) {
+ try {
+ READWRITE(fInternal);
+ }
+ catch (std::ios_base::failure&) {
+ /* flag as external address if we can't read the internal boolean
+ (this will be the case for any wallet before the HD chain split version) */
+ fInternal = false;
+ }
+ }
+ else {
+ READWRITE(fInternal);
+ }
}
};
@@ -647,6 +664,9 @@ private:
/* the HD chain data model (external chain counters) */
CHDChain hdChain;
+ /* HD derive new child key (on internal or external chain) */
+ void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool internal = false);
+
bool fFileBacked;
std::set<int64_t> setKeyPool;
@@ -774,8 +794,7 @@ public:
* keystore implementation
* Generate a new key
*/
- CPubKey GenerateNewKey();
- void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret);
+ CPubKey GenerateNewKey(bool internal = false);
//! Adds a key to the store, and saves it to disk.
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override;
//! Adds a key to the store, without saving it to disk (used by LoadWallet)
@@ -883,11 +902,12 @@ public:
static CAmount GetRequiredFee(unsigned int nTxBytes);
bool NewKeyPool();
+ size_t KeypoolCountExternalKeys();
bool TopUpKeyPool(unsigned int kpSize = 0);
- void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool);
+ void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool internal);
void KeepKey(int64_t nIndex);
void ReturnKey(int64_t nIndex);
- bool GetKeyFromPool(CPubKey &key);
+ bool GetKeyFromPool(CPubKey &key, bool internal = false);
int64_t GetOldestKeyPoolTime();
void GetAllReserveKeys(std::set<CKeyID>& setAddress) const;
@@ -1035,8 +1055,10 @@ public:
/* Generates a new HD master key (will not be activated) */
CPubKey GenerateNewHDMasterKey();
- /* Set the current HD master key (will reset the chain child index counters) */
- bool SetHDMasterKey(const CPubKey& key);
+ /* Set the current HD master key (will reset the chain child index counters)
+ If possibleOldChain is provided, the parameters from the old chain (version)
+ will be preserved. */
+ bool SetHDMasterKey(const CPubKey& key, CHDChain *possibleOldChain = nullptr);
};
/** A key allocated from the key pool. */
@@ -1063,7 +1085,7 @@ public:
}
void ReturnKey();
- bool GetReservedKey(CPubKey &pubkey);
+ bool GetReservedKey(CPubKey &pubkey, bool internal = false);
void KeepKey();
void KeepScript() { KeepKey(); }
};
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 4d7dfb727e..271c1d66c9 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -46,9 +46,12 @@ class CHDChain
{
public:
uint32_t nExternalChainCounter;
+ uint32_t nInternalChainCounter;
CKeyID masterKeyID; //!< master key hash160
- static const int CURRENT_VERSION = 1;
+ static const int VERSION_HD_BASE = 1;
+ static const int VERSION_HD_CHAIN_SPLIT = 2;
+ static const int CURRENT_VERSION = VERSION_HD_CHAIN_SPLIT;
int nVersion;
CHDChain() { SetNull(); }
@@ -59,12 +62,15 @@ public:
READWRITE(this->nVersion);
READWRITE(nExternalChainCounter);
READWRITE(masterKeyID);
+ if (this->nVersion >= VERSION_HD_CHAIN_SPLIT)
+ READWRITE(nInternalChainCounter);
}
void SetNull()
{
nVersion = CHDChain::CURRENT_VERSION;
nExternalChainCounter = 0;
+ nInternalChainCounter = 0;
masterKeyID.SetNull();
}
};
diff --git a/test/functional/fundrawtransaction.py b/test/functional/fundrawtransaction.py
index 3bfc05d37b..b86ea2d877 100755
--- a/test/functional/fundrawtransaction.py
+++ b/test/functional/fundrawtransaction.py
@@ -467,6 +467,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# drain the keypool
self.nodes[1].getnewaddress()
+ self.nodes[1].getrawchangeaddress()
inputs = []
outputs = {self.nodes[0].getnewaddress():1.1}
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
@@ -476,6 +477,7 @@ class RawTransactionsTest(BitcoinTestFramework):
#refill the keypool
self.nodes[1].walletpassphrase("test", 100)
+ self.nodes[1].keypoolrefill(8) #need to refill the keypool to get an internal change address
self.nodes[1].walletlock()
assert_raises_jsonrpc(-13, "walletpassphrase", self.nodes[1].sendtoaddress, self.nodes[0].getnewaddress(), 1.2)
@@ -644,7 +646,7 @@ class RawTransactionsTest(BitcoinTestFramework):
if out['value'] > 1.0:
changeaddress += out['scriptPubKey']['addresses'][0]
assert(changeaddress != "")
- nextaddr = self.nodes[3].getnewaddress()
+ nextaddr = self.nodes[3].getrawchangeaddress()
# frt should not have removed the key from the keypool
assert(changeaddress == nextaddr)
diff --git a/test/functional/keypool.py b/test/functional/keypool.py
index cee58563f0..cb9ab688d1 100755
--- a/test/functional/keypool.py
+++ b/test/functional/keypool.py
@@ -27,28 +27,42 @@ class KeyPoolTest(BitcoinTestFramework):
wallet_info = nodes[0].getwalletinfo()
assert(addr_before_encrypting_data['hdmasterkeyid'] != wallet_info['hdmasterkeyid'])
assert(addr_data['hdmasterkeyid'] == wallet_info['hdmasterkeyid'])
-
assert_raises_jsonrpc(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress)
- # put three new keys in the keypool
+ # put six (plus 2) new keys in the keypool (100% external-, +100% internal-keys, 1 in min)
nodes[0].walletpassphrase('test', 12000)
- nodes[0].keypoolrefill(3)
+ nodes[0].keypoolrefill(6)
nodes[0].walletlock()
+ wi = nodes[0].getwalletinfo()
+ assert_equal(wi['keypoolsize_hd_internal'], 6)
+ assert_equal(wi['keypoolsize'], 6)
- # drain the keys
+ # drain the internal keys
+ nodes[0].getrawchangeaddress()
+ nodes[0].getrawchangeaddress()
+ nodes[0].getrawchangeaddress()
+ nodes[0].getrawchangeaddress()
+ nodes[0].getrawchangeaddress()
+ nodes[0].getrawchangeaddress()
addr = set()
- addr.add(nodes[0].getrawchangeaddress())
- addr.add(nodes[0].getrawchangeaddress())
- addr.add(nodes[0].getrawchangeaddress())
- addr.add(nodes[0].getrawchangeaddress())
- # assert that four unique addresses were returned
- assert(len(addr) == 4)
# the next one should fail
assert_raises_jsonrpc(-12, "Keypool ran out", nodes[0].getrawchangeaddress)
+ # drain the external keys
+ addr.add(nodes[0].getnewaddress())
+ addr.add(nodes[0].getnewaddress())
+ addr.add(nodes[0].getnewaddress())
+ addr.add(nodes[0].getnewaddress())
+ addr.add(nodes[0].getnewaddress())
+ addr.add(nodes[0].getnewaddress())
+ assert(len(addr) == 6)
+ # the next one should fail
+ assert_raises_jsonrpc(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress)
+
# refill keypool with three new addresses
nodes[0].walletpassphrase('test', 1)
nodes[0].keypoolrefill(3)
+
# test walletpassphrase timeout
time.sleep(1.1)
assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0)
@@ -57,9 +71,14 @@ class KeyPoolTest(BitcoinTestFramework):
nodes[0].generate(1)
nodes[0].generate(1)
nodes[0].generate(1)
- nodes[0].generate(1)
assert_raises_jsonrpc(-12, "Keypool ran out", nodes[0].generate, 1)
+ nodes[0].walletpassphrase('test', 100)
+ nodes[0].keypoolrefill(100)
+ wi = nodes[0].getwalletinfo()
+ assert_equal(wi['keypoolsize_hd_internal'], 100)
+ assert_equal(wi['keypoolsize'], 100)
+
def __init__(self):
super().__init__()
self.setup_clean_chain = False
diff --git a/test/functional/wallet-dump.py b/test/functional/wallet-dump.py
index b819b72b75..8876f935a4 100755
--- a/test/functional/wallet-dump.py
+++ b/test/functional/wallet-dump.py
@@ -88,7 +88,7 @@ class WalletDumpTest(BitcoinTestFramework):
read_dump(tmpdir + "/node0/wallet.unencrypted.dump", addrs, None)
assert_equal(found_addr, test_addr_count) # all keys must be in the dump
assert_equal(found_addr_chg, 50) # 50 blocks where mined
- assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one)
+ assert_equal(found_addr_rsv, 90*2) # 90 keys plus 100% internal keys
#encrypt wallet, restart, unlock and dump
self.nodes[0].encryptwallet('test')
@@ -102,8 +102,8 @@ class WalletDumpTest(BitcoinTestFramework):
found_addr, found_addr_chg, found_addr_rsv, hd_master_addr_enc = \
read_dump(tmpdir + "/node0/wallet.encrypted.dump", addrs, hd_master_addr_unenc)
assert_equal(found_addr, test_addr_count)
- assert_equal(found_addr_chg, 90 + 1 + 50) # old reserve keys are marked as change now
- assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one)
+ assert_equal(found_addr_chg, 90*2 + 50) # old reserve keys are marked as change now
+ assert_equal(found_addr_rsv, 90*2)
if __name__ == '__main__':
WalletDumpTest().main ()
diff --git a/test/functional/wallet-hd.py b/test/functional/wallet-hd.py
index c40662dc3d..64a6c92782 100755
--- a/test/functional/wallet-hd.py
+++ b/test/functional/wallet-hd.py
@@ -42,6 +42,11 @@ class WalletHDTest(BitcoinTestFramework):
masterkeyid = self.nodes[1].getwalletinfo()['hdmasterkeyid']
assert_equal(len(masterkeyid), 40)
+ # create an internal key
+ change_addr = self.nodes[1].getrawchangeaddress()
+ change_addrV= self.nodes[1].validateaddress(change_addr);
+ assert_equal(change_addrV["hdkeypath"], "m/0'/1'/0'") #first internal child key
+
# Import a non-HD private key in the HD wallet
non_hd_add = self.nodes[0].getnewaddress()
self.nodes[1].importprivkey(self.nodes[0].dumpprivkey(non_hd_add))
@@ -65,6 +70,11 @@ class WalletHDTest(BitcoinTestFramework):
self.nodes[0].sendtoaddress(non_hd_add, 1)
self.nodes[0].generate(1)
+ # create an internal key (again)
+ change_addr = self.nodes[1].getrawchangeaddress()
+ change_addrV= self.nodes[1].validateaddress(change_addr);
+ assert_equal(change_addrV["hdkeypath"], "m/0'/1'/1'") #second internal child key
+
self.sync_all()
assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1)
@@ -90,6 +100,15 @@ class WalletHDTest(BitcoinTestFramework):
#connect_nodes_bi(self.nodes, 0, 1)
assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1)
+ # send a tx and make sure its using the internal chain for the changeoutput
+ txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1)
+ outs = self.nodes[1].decoderawtransaction(self.nodes[1].gettransaction(txid)['hex'])['vout'];
+ keypath = ""
+ for out in outs:
+ if out['value'] != 1:
+ keypath = self.nodes[1].validateaddress(out['scriptPubKey']['addresses'][0])['hdkeypath']
+
+ assert_equal(keypath[0:7], "m/0'/1'")
if __name__ == '__main__':
WalletHDTest().main ()