aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/crypter.h12
-rw-r--r--src/wallet/rpcwallet.cpp5
-rw-r--r--src/wallet/scriptpubkeyman.cpp143
-rw-r--r--src/wallet/scriptpubkeyman.h109
-rw-r--r--src/wallet/test/wallet_tests.cpp16
-rw-r--r--src/wallet/wallet.cpp10
-rw-r--r--src/wallet/wallet.h1
-rw-r--r--src/wallet/walletdb.cpp104
-rw-r--r--src/wallet/walletdb.h40
-rw-r--r--src/wallet/walletutil.h34
10 files changed, 331 insertions, 143 deletions
diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h
index f59c63260e..f2df786e2e 100644
--- a/src/wallet/crypter.h
+++ b/src/wallet/crypter.h
@@ -43,15 +43,9 @@ public:
//! such as the various parameters to scrypt
std::vector<unsigned char> vchOtherDerivationParameters;
- ADD_SERIALIZE_METHODS;
-
- template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action) {
- READWRITE(vchCryptedKey);
- READWRITE(vchSalt);
- READWRITE(nDerivationMethod);
- READWRITE(nDeriveIterations);
- READWRITE(vchOtherDerivationParameters);
+ SERIALIZE_METHODS(CMasterKey, obj)
+ {
+ READWRITE(obj.vchCryptedKey, obj.vchSalt, obj.nDerivationMethod, obj.nDeriveIterations, obj.vchOtherDerivationParameters);
}
CMasterKey()
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 2a57248705..2a9ac189ea 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -2726,6 +2726,7 @@ static UniValue createwallet(const JSONRPCRequest& request)
}
if (!request.params[5].isNull() && request.params[5].get_bool()) {
flags |= WALLET_FLAG_DESCRIPTORS;
+ warnings.emplace_back(Untranslated("Wallet is an experimental descriptor wallet"));
}
bilingual_str error;
@@ -3979,10 +3980,6 @@ UniValue sethdseed(const JSONRPCRequest& request)
LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
- if (pwallet->chain().isInitialBlockDownload()) {
- throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download");
- }
-
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed to a wallet with private keys disabled");
}
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index e4be5045e1..8a2a798644 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -12,6 +12,9 @@
#include <util/translation.h>
#include <wallet/scriptpubkeyman.h>
+//! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details.
+const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
+
bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error)
{
LOCK(cs_KeyStore);
@@ -220,6 +223,7 @@ bool LegacyScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master_key
bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys
bool keyFail = false;
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
+ WalletBatch batch(m_storage.GetDatabase());
for (; mi != mapCryptedKeys.end(); ++mi)
{
const CPubKey &vchPubKey = (*mi).second.first;
@@ -233,6 +237,10 @@ bool LegacyScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master_key
keyPass = true;
if (fDecryptionThoroughlyChecked)
break;
+ else {
+ // Rewrite these encrypted keys with checksums
+ batch.WriteCryptedKey(vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]);
+ }
}
if (keyPass && keyFail)
{
@@ -290,6 +298,43 @@ bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool i
return true;
}
+bool LegacyScriptPubKeyMan::TopUpInactiveHDChain(const CKeyID seed_id, int64_t index, bool internal)
+{
+ LOCK(cs_KeyStore);
+
+ if (m_storage.IsLocked()) return false;
+
+ auto it = m_inactive_hd_chains.find(seed_id);
+ if (it == m_inactive_hd_chains.end()) {
+ return false;
+ }
+
+ CHDChain& chain = it->second;
+
+ // Top up key pool
+ int64_t target_size = std::max(gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 1);
+
+ // "size" of the keypools. Not really the size, actually the difference between index and the chain counter
+ // Since chain counter is 1 based and index is 0 based, one of them needs to be offset by 1.
+ int64_t kp_size = (internal ? chain.nInternalChainCounter : chain.nExternalChainCounter) - (index + 1);
+
+ // make sure the keypool fits the user-selected target (-keypool)
+ int64_t missing = std::max(target_size - kp_size, (int64_t) 0);
+
+ if (missing > 0) {
+ WalletBatch batch(m_storage.GetDatabase());
+ for (int64_t i = missing; i > 0; --i) {
+ GenerateNewKey(batch, chain, internal);
+ }
+ if (internal) {
+ WalletLogPrintf("inactive seed with id %s added %d internal keys\n", HexStr(seed_id), missing);
+ } else {
+ WalletLogPrintf("inactive seed with id %s added %d keys\n", HexStr(seed_id), missing);
+ }
+ }
+ return true;
+}
+
void LegacyScriptPubKeyMan::MarkUnusedAddresses(const CScript& script)
{
LOCK(cs_KeyStore);
@@ -297,13 +342,28 @@ void LegacyScriptPubKeyMan::MarkUnusedAddresses(const CScript& script)
for (const auto& keyid : GetAffectedKeys(script, *this)) {
std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid);
if (mi != m_pool_key_to_index.end()) {
- WalletLogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__);
+ WalletLogPrintf("%s: Detected a used keypool key, mark all keypool keys up to this key as used\n", __func__);
MarkReserveKeysAsUsed(mi->second);
if (!TopUp()) {
WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__);
}
}
+
+ // Find the key's metadata and check if it's seed id (if it has one) is inactive, i.e. it is not the current m_hd_chain seed id.
+ // If so, TopUp the inactive hd chain
+ auto it = mapKeyMetadata.find(keyid);
+ if (it != mapKeyMetadata.end()){
+ CKeyMetadata meta = it->second;
+ if (!meta.hd_seed_id.IsNull() && meta.hd_seed_id != m_hd_chain.seed_id) {
+ bool internal = (meta.key_origin.path[1] & ~BIP32_HARDENED_KEY_LIMIT) != 0;
+ int64_t index = meta.key_origin.path[2] & ~BIP32_HARDENED_KEY_LIMIT;
+
+ if (!TopUpInactiveHDChain(meta.hd_seed_id, index, internal)) {
+ WalletLogPrintf("%s: Adding inactive seed keys failed\n", __func__);
+ }
+ }
+ }
}
}
@@ -357,7 +417,7 @@ bool LegacyScriptPubKeyMan::SetupGeneration(bool force)
bool LegacyScriptPubKeyMan::IsHDEnabled() const
{
- return !hdChain.seed_id.IsNull();
+ return !m_hd_chain.seed_id.IsNull();
}
bool LegacyScriptPubKeyMan::CanGetAddresses(bool internal) const
@@ -713,8 +773,13 @@ bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey& key, const CPubKey &pu
return true;
}
-bool LegacyScriptPubKeyMan::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
+bool LegacyScriptPubKeyMan::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret, bool checksum_valid)
{
+ // Set fDecryptionThoroughlyChecked to false when the checksum is invalid
+ if (!checksum_valid) {
+ fDecryptionThoroughlyChecked = false;
+ }
+
return AddCryptedKeyInner(vchPubKey, vchCryptedSecret);
}
@@ -838,10 +903,27 @@ bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest, int64_t nCreateTim
void LegacyScriptPubKeyMan::SetHDChain(const CHDChain& chain, bool memonly)
{
LOCK(cs_KeyStore);
- if (!memonly && !WalletBatch(m_storage.GetDatabase()).WriteHDChain(chain))
- throw std::runtime_error(std::string(__func__) + ": writing chain failed");
+ // memonly == true means we are loading the wallet file
+ // memonly == false means that the chain is actually being changed
+ if (!memonly) {
+ // Store the new chain
+ if (!WalletBatch(m_storage.GetDatabase()).WriteHDChain(chain)) {
+ throw std::runtime_error(std::string(__func__) + ": writing chain failed");
+ }
+ // When there's an old chain, add it as an inactive chain as we are now rotating hd chains
+ if (!m_hd_chain.seed_id.IsNull()) {
+ AddInactiveHDChain(m_hd_chain);
+ }
+ }
+
+ m_hd_chain = chain;
+}
- hdChain = chain;
+void LegacyScriptPubKeyMan::AddInactiveHDChain(const CHDChain& chain)
+{
+ LOCK(cs_KeyStore);
+ assert(!chain.seed_id.IsNull());
+ m_inactive_hd_chains[chain.seed_id] = chain;
}
bool LegacyScriptPubKeyMan::HaveKey(const CKeyID &address) const
@@ -920,7 +1002,7 @@ bool LegacyScriptPubKeyMan::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyO
return GetWatchPubKey(address, vchPubKeyOut);
}
-CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, bool internal)
+CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, CHDChain& hd_chain, bool internal)
{
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
@@ -935,7 +1017,7 @@ CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, bool internal)
// use HD key derivation if HD was enabled during wallet creation and a seed is present
if (IsHDEnabled()) {
- DeriveNewChildKey(batch, metadata, secret, (m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
+ DeriveNewChildKey(batch, metadata, secret, hd_chain, (m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
} else {
secret.MakeNewKey(fCompressed);
}
@@ -957,9 +1039,7 @@ CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, bool internal)
return pubkey;
}
-const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
-
-void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal)
+void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, CHDChain& hd_chain, bool internal)
{
// for now we use a fixed keypath scheme of m/0'/0'/k
CKey seed; //seed (256bit)
@@ -969,7 +1049,7 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata&
CExtKey childKey; //key at m/0'/0'/<n>'
// try to get the seed
- if (!GetKey(hdChain.seed_id, seed))
+ if (!GetKey(hd_chain.seed_id, seed))
throw std::runtime_error(std::string(__func__) + ": seed not found");
masterKey.SetSeed(seed.begin(), seed.size());
@@ -988,30 +1068,30 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata&
// childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
// example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
if (internal) {
- chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
- metadata.hdKeypath = "m/0'/1'/" + ToString(hdChain.nInternalChainCounter) + "'";
+ chainChildKey.Derive(childKey, hd_chain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
+ metadata.hdKeypath = "m/0'/1'/" + ToString(hd_chain.nInternalChainCounter) + "'";
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT);
- metadata.key_origin.path.push_back(hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
- hdChain.nInternalChainCounter++;
+ metadata.key_origin.path.push_back(hd_chain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
+ hd_chain.nInternalChainCounter++;
}
else {
- chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
- metadata.hdKeypath = "m/0'/0'/" + ToString(hdChain.nExternalChainCounter) + "'";
+ chainChildKey.Derive(childKey, hd_chain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
+ metadata.hdKeypath = "m/0'/0'/" + ToString(hd_chain.nExternalChainCounter) + "'";
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
- metadata.key_origin.path.push_back(hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
- hdChain.nExternalChainCounter++;
+ metadata.key_origin.path.push_back(hd_chain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
+ hd_chain.nExternalChainCounter++;
}
} while (HaveKey(childKey.key.GetPubKey().GetID()));
secret = childKey.key;
- metadata.hd_seed_id = hdChain.seed_id;
+ metadata.hd_seed_id = hd_chain.seed_id;
CKeyID master_id = masterKey.key.GetPubKey().GetID();
std::copy(master_id.begin(), master_id.begin() + 4, metadata.key_origin.fingerprint);
metadata.has_key_origin = true;
// update the chain model in the database
- if (!batch.WriteHDChain(hdChain))
- throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
+ if (hd_chain.seed_id == m_hd_chain.seed_id && !batch.WriteHDChain(hd_chain))
+ throw std::runtime_error(std::string(__func__) + ": writing HD chain model failed");
}
void LegacyScriptPubKeyMan::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)
@@ -1166,7 +1246,7 @@ bool LegacyScriptPubKeyMan::TopUp(unsigned int kpSize)
internal = true;
}
- CPubKey pubkey(GenerateNewKey(batch, internal));
+ CPubKey pubkey(GenerateNewKey(batch, m_hd_chain, internal));
AddKeypoolPubkeyWithDB(pubkey, internal, batch);
}
if (missingInternal + missingExternal > 0) {
@@ -1239,7 +1319,7 @@ bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, const OutputType typ
if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) && !m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
if (m_storage.IsLocked()) return false;
WalletBatch batch(m_storage.GetDatabase());
- result = GenerateNewKey(batch, internal);
+ result = GenerateNewKey(batch, m_hd_chain, internal);
return true;
}
KeepDestination(nIndex, type);
@@ -1497,7 +1577,7 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const
return set_address;
}
-void LegacyScriptPubKeyMan::SetType(OutputType type, bool internal) {}
+void LegacyScriptPubKeyMan::SetInternal(bool internal) {}
bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error)
{
@@ -1509,7 +1589,9 @@ bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDest
{
LOCK(cs_desc_man);
assert(m_wallet_descriptor.descriptor->IsSingleType()); // This is a combo descriptor which should not be an active descriptor
- if (type != m_address_type) {
+ Optional<OutputType> desc_addr_type = m_wallet_descriptor.descriptor->GetOutputType();
+ assert(desc_addr_type);
+ if (type != *desc_addr_type) {
throw std::runtime_error(std::string(__func__) + ": Types are inconsistent");
}
@@ -1777,7 +1859,7 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const
}
}
-bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key)
+bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type)
{
LOCK(cs_desc_man);
assert(m_storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
@@ -1794,7 +1876,7 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_
// Build descriptor string
std::string desc_prefix;
std::string desc_suffix = "/*)";
- switch (m_address_type) {
+ switch (addr_type) {
case OutputType::LEGACY: {
desc_prefix = "pkh(" + xpub + "/44'";
break;
@@ -2076,9 +2158,8 @@ uint256 DescriptorScriptPubKeyMan::GetID() const
return id;
}
-void DescriptorScriptPubKeyMan::SetType(OutputType type, bool internal)
+void DescriptorScriptPubKeyMan::SetInternal(bool internal)
{
- this->m_address_type = type;
this->m_internal = internal;
}
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index 4c002edf2d..d62d30f339 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -18,6 +18,8 @@
#include <boost/signals2/signal.hpp>
+#include <unordered_map>
+
enum class OutputType;
struct bilingual_str;
@@ -110,40 +112,52 @@ public:
CKeyPool();
CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn);
- ADD_SERIALIZE_METHODS;
+ template<typename Stream>
+ void Serialize(Stream& s) const
+ {
+ int nVersion = s.GetVersion();
+ if (!(s.GetType() & SER_GETHASH)) {
+ s << nVersion;
+ }
+ s << nTime << vchPubKey << fInternal << m_pre_split;
+ }
- template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action) {
+ template<typename Stream>
+ void Unserialize(Stream& s)
+ {
int nVersion = s.GetVersion();
- if (!(s.GetType() & SER_GETHASH))
- 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;
- }
- try {
- READWRITE(m_pre_split);
- }
- catch (std::ios_base::failure&) {
- /* flag as postsplit address if we can't read the m_pre_split boolean
- (this will be the case for any wallet that upgrades to HD chain split)*/
- m_pre_split = false;
- }
+ if (!(s.GetType() & SER_GETHASH)) {
+ s >> nVersion;
+ }
+ s >> nTime >> vchPubKey;
+ try {
+ s >> 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);
- READWRITE(m_pre_split);
+ try {
+ s >> m_pre_split;
+ } catch (std::ios_base::failure&) {
+ /* flag as postsplit address if we can't read the m_pre_split boolean
+ (this will be the case for any wallet that upgrades to HD chain split) */
+ m_pre_split = false;
}
}
};
+class KeyIDHasher
+{
+public:
+ KeyIDHasher() {}
+
+ size_t operator()(const CKeyID& id) const
+ {
+ return id.GetUint64(0);
+ }
+};
+
/*
* A class implementing ScriptPubKeyMan manages some (or all) scriptPubKeys used in a wallet.
* It contains the scripts and keys related to the scriptPubKeys it manages.
@@ -224,7 +238,7 @@ public:
virtual uint256 GetID() const { return uint256(); }
- virtual void SetType(OutputType type, bool internal) {}
+ virtual void SetInternal(bool internal) {}
/** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */
template<typename... Params>
@@ -243,7 +257,7 @@ class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProv
{
private:
//! keeps track of whether Unlock has run a thorough check before
- bool fDecryptionThoroughlyChecked = false;
+ bool fDecryptionThoroughlyChecked = true;
using WatchOnlySet = std::set<CScript>;
using WatchKeyMap = std::map<CKeyID, CPubKey>;
@@ -288,10 +302,11 @@ private:
bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info);
/* the HD chain data model (external chain counters) */
- CHDChain hdChain;
+ CHDChain m_hd_chain;
+ std::unordered_map<CKeyID, CHDChain, KeyIDHasher> m_inactive_hd_chains;
/* HD derive new child key (on internal or external chain) */
- void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+ void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
std::set<int64_t> setInternalKeyPool GUARDED_BY(cs_KeyStore);
std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_KeyStore);
@@ -320,6 +335,18 @@ private:
*/
bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal);
+ /**
+ * Like TopUp() but adds keys for inactive HD chains.
+ * Ensures that there are at least -keypool number of keys derived after the given index.
+ *
+ * @param seed_id the CKeyID for the HD seed.
+ * @param index the index to start generating keys from
+ * @param internal whether the internal chain should be used. true for internal chain, false for external chain.
+ *
+ * @return true if seed was found and keys were derived. false if unable to derive seeds
+ */
+ bool TopUpInactiveHDChain(const CKeyID seed_id, int64_t index, bool internal);
+
public:
using ScriptPubKeyMan::ScriptPubKeyMan;
@@ -370,7 +397,7 @@ public:
uint256 GetID() const override;
- void SetType(OutputType type, bool internal) override;
+ void SetInternal(bool internal) override;
// Map from Key ID to key metadata.
std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_KeyStore);
@@ -385,7 +412,7 @@ public:
//! Adds an encrypted key to the store, and saves it to disk.
bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
//! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet)
- bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
+ bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret, bool checksum_valid);
void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
//! Adds a CScript to the store
bool LoadCScript(const CScript& redeemScript);
@@ -393,11 +420,12 @@ public:
void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata);
void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata);
//! Generate a new key
- CPubKey GenerateNewKey(WalletBatch& batch, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+ CPubKey GenerateNewKey(WalletBatch& batch, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
/* Set the HD chain model (chain child index counters) */
void SetHDChain(const CHDChain& chain, bool memonly);
- const CHDChain& GetHDChain() const { return hdChain; }
+ const CHDChain& GetHDChain() const { return m_hd_chain; }
+ void AddInactiveHDChain(const CHDChain& chain);
//! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet)
bool LoadWatchOnly(const CScript &dest);
@@ -497,14 +525,11 @@ private:
PubKeyMap m_map_pubkeys GUARDED_BY(cs_desc_man);
int32_t m_max_cached_index = -1;
- OutputType m_address_type;
bool m_internal = false;
KeyMap m_map_keys GUARDED_BY(cs_desc_man);
CryptedKeyMap m_map_crypted_keys GUARDED_BY(cs_desc_man);
- bool SetCrypted();
-
//! keeps track of whether Unlock has run a thorough check before
bool m_decryption_thoroughly_checked = false;
@@ -524,9 +549,9 @@ public:
: ScriptPubKeyMan(storage),
m_wallet_descriptor(descriptor)
{}
- DescriptorScriptPubKeyMan(WalletStorage& storage, OutputType address_type, bool internal)
+ DescriptorScriptPubKeyMan(WalletStorage& storage, bool internal)
: ScriptPubKeyMan(storage),
- m_address_type(address_type), m_internal(internal)
+ m_internal(internal)
{}
mutable RecursiveMutex cs_desc_man;
@@ -551,7 +576,7 @@ public:
bool IsHDEnabled() const override;
//! Setup descriptors based on the given CExtkey
- bool SetupDescriptorGeneration(const CExtKey& master_key);
+ bool SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type);
bool HavePrivateKeys() const override;
@@ -575,7 +600,7 @@ public:
uint256 GetID() const override;
- void SetType(OutputType type, bool internal) override;
+ void SetInternal(bool internal) override;
void SetCache(const DescriptorCache& cache);
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index d888b8f842..3654420eb2 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -15,6 +15,7 @@
#include <rpc/server.h>
#include <test/util/logging.h>
#include <test/util/setup_common.h>
+#include <util/ref.h>
#include <util/translation.h>
#include <validation.h>
#include <wallet/coincontrol.h>
@@ -117,7 +118,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Prune the older block file.
{
LOCK(cs_main);
- PruneOneBlockFile(oldTip->GetBlockPos().nFile);
+ EnsureChainman(m_node).PruneOneBlockFile(oldTip->GetBlockPos().nFile);
}
UnlinkPrunedFiles({oldTip->GetBlockPos().nFile});
@@ -143,7 +144,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Prune the remaining block file.
{
LOCK(cs_main);
- PruneOneBlockFile(newTip->GetBlockPos().nFile);
+ EnsureChainman(m_node).PruneOneBlockFile(newTip->GetBlockPos().nFile);
}
UnlinkPrunedFiles({newTip->GetBlockPos().nFile});
@@ -180,7 +181,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
// Prune the older block file.
{
LOCK(cs_main);
- PruneOneBlockFile(oldTip->GetBlockPos().nFile);
+ EnsureChainman(m_node).PruneOneBlockFile(oldTip->GetBlockPos().nFile);
}
UnlinkPrunedFiles({oldTip->GetBlockPos().nFile});
@@ -208,7 +209,8 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1);
key.pushKV("internal", UniValue(true));
keys.push_back(key);
- JSONRPCRequest request;
+ util::Ref context;
+ JSONRPCRequest request(context);
request.params.setArray();
request.params.push_back(keys);
@@ -262,7 +264,8 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
AddWallet(wallet);
wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
}
- JSONRPCRequest request;
+ util::Ref context;
+ JSONRPCRequest request(context);
request.params.setArray();
request.params.push_back(backup_file);
@@ -277,7 +280,8 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
LOCK(wallet->cs_wallet);
wallet->SetupLegacyScriptPubKeyMan();
- JSONRPCRequest request;
+ util::Ref context;
+ JSONRPCRequest request(context);
request.params.setArray();
request.params.push_back(backup_file);
AddWallet(wallet);
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 6826782073..7824563254 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1982,10 +1982,6 @@ void CWallet::ResendWalletTransactions()
nNextResend = GetTime() + (12 * 60 * 60) + GetRand(24 * 60 * 60);
if (fFirst) return;
- // Only do it if there's been a new block since last time
- if (m_best_block_time < nLastResend) return;
- nLastResend = GetTime();
-
int submitted_tx_count = 0;
{ // cs_wallet scope
@@ -4357,7 +4353,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
for (bool internal : {false, true}) {
for (OutputType t : OUTPUT_TYPES) {
- auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, t, internal));
+ auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, internal));
if (IsCrypted()) {
if (IsLocked()) {
throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
@@ -4366,7 +4362,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
}
}
- spk_manager->SetupDescriptorGeneration(master_key);
+ spk_manager->SetupDescriptorGeneration(master_key, t);
uint256 id = spk_manager->GetID();
m_spk_managers[id] = std::move(spk_manager);
SetActiveScriptPubKeyMan(id, t, internal);
@@ -4379,7 +4375,7 @@ void CWallet::SetActiveScriptPubKeyMan(uint256 id, OutputType type, bool interna
WalletLogPrintf("Setting spkMan to active: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
auto spk_man = m_spk_managers.at(id).get();
- spk_man->SetType(type, internal);
+ spk_man->SetInternal(internal);
spk_mans[type] = spk_man;
if (!memonly) {
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index fc4cc9495c..29d04a0cba 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -641,7 +641,6 @@ private:
int nWalletMaxVersion GUARDED_BY(cs_wallet) = FEATURE_BASE;
int64_t nNextResend = 0;
- int64_t nLastResend = 0;
bool fBroadcastTransactions = false;
// Local time that the tip block was received. Used to schedule wallet rebroadcasts.
std::atomic<int64_t> m_best_block_time {0};
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 32fc002660..e7adbfea77 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -10,6 +10,7 @@
#include <protocol.h>
#include <serialize.h>
#include <sync.h>
+#include <util/bip32.h>
#include <util/system.h>
#include <util/time.h>
#include <wallet/wallet.h>
@@ -115,8 +116,19 @@ bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey,
return false;
}
- if (!WriteIC(std::make_pair(DBKeys::CRYPTED_KEY, vchPubKey), vchCryptedSecret, false)) {
- return false;
+ // Compute a checksum of the encrypted key
+ uint256 checksum = Hash(vchCryptedSecret.begin(), vchCryptedSecret.end());
+
+ const auto key = std::make_pair(DBKeys::CRYPTED_KEY, vchPubKey);
+ if (!WriteIC(key, std::make_pair(vchCryptedSecret, checksum), false)) {
+ // It may already exist, so try writing just the checksum
+ std::vector<unsigned char> val;
+ if (!m_batch.Read(key, val)) {
+ return false;
+ }
+ if (!WriteIC(key, std::make_pair(val, checksum), true)) {
+ return false;
+ }
}
EraseIC(std::make_pair(DBKeys::KEY, vchPubKey));
return true;
@@ -245,6 +257,7 @@ public:
std::map<uint256, DescriptorCache> m_descriptor_caches;
std::map<std::pair<uint256, CKeyID>, CKey> m_descriptor_keys;
std::map<std::pair<uint256, CKeyID>, std::pair<CPubKey, std::vector<unsigned char>>> m_descriptor_crypt_keys;
+ std::map<uint160, CHDChain> m_hd_chains;
CWalletScanState() {
}
@@ -397,9 +410,21 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
}
std::vector<unsigned char> vchPrivKey;
ssValue >> vchPrivKey;
+
+ // Get the checksum and check it
+ bool checksum_valid = false;
+ if (!ssValue.eof()) {
+ uint256 checksum;
+ ssValue >> checksum;
+ if ((checksum_valid = Hash(vchPrivKey.begin(), vchPrivKey.end()) != checksum)) {
+ strErr = "Error reading wallet database: Crypted key corrupt";
+ return false;
+ }
+ }
+
wss.nCKeys++;
- if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCryptedKey(vchPubKey, vchPrivKey))
+ if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCryptedKey(vchPubKey, vchPrivKey, checksum_valid))
{
strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCryptedKey failed";
return false;
@@ -412,6 +437,65 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
ssValue >> keyMeta;
wss.nKeyMeta++;
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyMetadata(vchPubKey.GetID(), keyMeta);
+
+ // Extract some CHDChain info from this metadata if it has any
+ if (keyMeta.nVersion >= CKeyMetadata::VERSION_WITH_HDDATA && !keyMeta.hd_seed_id.IsNull() && keyMeta.hdKeypath.size() > 0) {
+ // Get the path from the key origin or from the path string
+ // Not applicable when path is "s" as that indicates a seed
+ bool internal = false;
+ uint32_t index = 0;
+ if (keyMeta.hdKeypath != "s") {
+ std::vector<uint32_t> path;
+ if (keyMeta.has_key_origin) {
+ // We have a key origin, so pull it from its path vector
+ path = keyMeta.key_origin.path;
+ } else {
+ // No key origin, have to parse the string
+ if (!ParseHDKeypath(keyMeta.hdKeypath, path)) {
+ strErr = "Error reading wallet database: keymeta with invalid HD keypath";
+ return false;
+ }
+ }
+
+ // Extract the index and internal from the path
+ // Path string is m/0'/k'/i'
+ // Path vector is [0', k', i'] (but as ints OR'd with the hardened bit
+ // k == 0 for external, 1 for internal. i is the index
+ if (path.size() != 3) {
+ strErr = "Error reading wallet database: keymeta found with unexpected path";
+ return false;
+ }
+ if (path[0] != 0x80000000) {
+ strErr = strprintf("Unexpected path index of 0x%08x (expected 0x80000000) for the element at index 0", path[0]);
+ return false;
+ }
+ if (path[1] != 0x80000000 && path[1] != (1 | 0x80000000)) {
+ strErr = strprintf("Unexpected path index of 0x%08x (expected 0x80000000 or 0x80000001) for the element at index 1", path[1]);
+ return false;
+ }
+ if ((path[2] & 0x80000000) == 0) {
+ strErr = strprintf("Unexpected path index of 0x%08x (expected to be greater than or equal to 0x80000000)", path[2]);
+ return false;
+ }
+ internal = path[1] == (1 | 0x80000000);
+ index = path[2] & ~0x80000000;
+ }
+
+ // Insert a new CHDChain, or get the one that already exists
+ auto ins = wss.m_hd_chains.emplace(keyMeta.hd_seed_id, CHDChain());
+ CHDChain& chain = ins.first->second;
+ if (ins.second) {
+ // For new chains, we want to default to VERSION_HD_BASE until we see an internal
+ chain.nVersion = CHDChain::VERSION_HD_BASE;
+ chain.seed_id = keyMeta.hd_seed_id;
+ }
+ if (internal) {
+ chain.nVersion = CHDChain::VERSION_HD_CHAIN_SPLIT;
+ chain.nInternalChainCounter = std::max(chain.nInternalChainCounter, index);
+ } else {
+ chain.nExternalChainCounter = std::max(chain.nExternalChainCounter, index);
+ }
+ }
} else if (strType == DBKeys::WATCHMETA) {
CScript script;
ssKey >> script;
@@ -742,6 +826,20 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
result = DBErrors::CORRUPT;
}
+ // Set the inactive chain
+ if (wss.m_hd_chains.size() > 0) {
+ LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan();
+ if (!legacy_spkm) {
+ pwallet->WalletLogPrintf("Inactive HD Chains found but no Legacy ScriptPubKeyMan\n");
+ return DBErrors::CORRUPT;
+ }
+ for (const auto& chain_pair : wss.m_hd_chains) {
+ if (chain_pair.first != pwallet->GetLegacyScriptPubKeyMan()->GetHDChain().seed_id) {
+ pwallet->GetLegacyScriptPubKeyMan()->AddInactiveHDChain(chain_pair.second);
+ }
+ }
+ }
+
return result;
}
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index bcd1f9303d..b95ed24d12 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -98,15 +98,13 @@ public:
int nVersion;
CHDChain() { SetNull(); }
- ADD_SERIALIZE_METHODS;
- template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action)
+
+ SERIALIZE_METHODS(CHDChain, obj)
{
- READWRITE(this->nVersion);
- READWRITE(nExternalChainCounter);
- READWRITE(seed_id);
- if (this->nVersion >= VERSION_HD_CHAIN_SPLIT)
- READWRITE(nInternalChainCounter);
+ READWRITE(obj.nVersion, obj.nExternalChainCounter, obj.seed_id);
+ if (obj.nVersion >= VERSION_HD_CHAIN_SPLIT) {
+ READWRITE(obj.nInternalChainCounter);
+ }
}
void SetNull()
@@ -116,6 +114,11 @@ public:
nInternalChainCounter = 0;
seed_id.SetNull();
}
+
+ bool operator==(const CHDChain& chain) const
+ {
+ return seed_id == chain.seed_id;
+ }
};
class CKeyMetadata
@@ -142,21 +145,16 @@ public:
nCreateTime = nCreateTime_;
}
- ADD_SERIALIZE_METHODS;
-
- template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action) {
- READWRITE(this->nVersion);
- READWRITE(nCreateTime);
- if (this->nVersion >= VERSION_WITH_HDDATA)
- {
- READWRITE(hdKeypath);
- READWRITE(hd_seed_id);
+ SERIALIZE_METHODS(CKeyMetadata, obj)
+ {
+ READWRITE(obj.nVersion, obj.nCreateTime);
+ if (obj.nVersion >= VERSION_WITH_HDDATA) {
+ READWRITE(obj.hdKeypath, obj.hd_seed_id);
}
- if (this->nVersion >= VERSION_WITH_KEY_ORIGIN)
+ if (obj.nVersion >= VERSION_WITH_KEY_ORIGIN)
{
- READWRITE(key_origin);
- READWRITE(has_key_origin);
+ READWRITE(obj.key_origin);
+ READWRITE(obj.has_key_origin);
}
}
diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h
index 599b1a9f5a..a4e4fda8a1 100644
--- a/src/wallet/walletutil.h
+++ b/src/wallet/walletutil.h
@@ -98,26 +98,22 @@ public:
int32_t next_index = 0; // Position of the next item to generate
DescriptorCache cache;
- ADD_SERIALIZE_METHODS;
-
- template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action) {
- if (ser_action.ForRead()) {
- std::string desc;
- std::string error;
- READWRITE(desc);
- FlatSigningProvider keys;
- descriptor = Parse(desc, keys, error, true);
- if (!descriptor) {
- throw std::ios_base::failure("Invalid descriptor: " + error);
- }
- } else {
- READWRITE(descriptor->ToString());
+ void DeserializeDescriptor(const std::string& str)
+ {
+ std::string error;
+ FlatSigningProvider keys;
+ descriptor = Parse(str, keys, error, true);
+ if (!descriptor) {
+ throw std::ios_base::failure("Invalid descriptor: " + error);
}
- READWRITE(creation_time);
- READWRITE(next_index);
- READWRITE(range_start);
- READWRITE(range_end);
+ }
+
+ SERIALIZE_METHODS(WalletDescriptor, obj)
+ {
+ std::string descriptor_str;
+ SER_WRITE(obj, descriptor_str = obj.descriptor->ToString());
+ READWRITE(descriptor_str, obj.creation_time, obj.next_index, obj.range_start, obj.range_end);
+ SER_READ(obj, obj.DeserializeDescriptor(descriptor_str));
}
WalletDescriptor() {}