diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/rpcdump.cpp | 89 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 52 | ||||
-rw-r--r-- | src/wallet/test/psbt_wallet_tests.cpp | 1 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 136 | ||||
-rw-r--r-- | src/wallet/wallet.h | 14 | ||||
-rw-r--r-- | src/wallet/walletdb.cpp | 17 | ||||
-rw-r--r-- | src/wallet/walletdb.h | 16 |
7 files changed, 240 insertions, 85 deletions
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index ec49efcf22..f38202a2b8 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -13,6 +13,7 @@ #include <script/script.h> #include <script/standard.h> #include <sync.h> +#include <util/bip32.h> #include <util/system.h> #include <util/time.h> #include <validation.h> @@ -850,7 +851,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) } else { file << "change=1"; } - file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].hdKeypath.size() > 0 ? " hdkeypath="+pwallet->mapKeyMetadata[keyid].hdKeypath : "")); + file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].has_key_origin ? " hdkeypath="+WriteHDKeypath(pwallet->mapKeyMetadata[keyid].key_origin.path) : "")); } } file << "\n"; @@ -887,6 +888,7 @@ struct ImportData // Output data std::set<CScript> import_scripts; std::map<CKeyID, bool> used_keys; //!< Import these private keys if available (the value indicates whether if the key is required for solvability) + std::map<CKeyID, KeyOriginInfo> key_origins; }; enum class ScriptContext @@ -965,7 +967,7 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d } } -static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data) +static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys) { UniValue warnings(UniValue::VARR); @@ -1036,6 +1038,7 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key"); } pubkey_map.emplace(pubkey.GetID(), pubkey); + ordered_pubkeys.push_back(pubkey.GetID()); } for (size_t i = 0; i < keys.size(); ++i) { const auto& str = keys[i].get_str(); @@ -1108,13 +1111,13 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP return warnings; } -static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data) +static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys) { UniValue warnings(UniValue::VARR); const std::string& descriptor = data["desc"].get_str(); FlatSigningProvider keys; - auto parsed_desc = Parse(descriptor, keys); + auto parsed_desc = Parse(descriptor, keys, /* require_checksum = */ true); if (!parsed_desc) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor is invalid"); } @@ -1142,21 +1145,24 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); - FlatSigningProvider out_keys; - // Expand all descriptors to get public keys and scripts. // TODO: get private keys from descriptors too for (int i = range_start; i <= range_end; ++i) { + FlatSigningProvider out_keys; std::vector<CScript> scripts_temp; parsed_desc->Expand(i, keys, scripts_temp, out_keys); std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end())); - } + for (const auto& key_pair : out_keys.pubkeys) { + ordered_pubkeys.push_back(key_pair.first); + } - for (const auto& x : out_keys.scripts) { - import_data.import_scripts.emplace(x.second); - } + for (const auto& x : out_keys.scripts) { + import_data.import_scripts.emplace(x.second); + } - std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end())); + std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end())); + import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end()); + } for (size_t i = 0; i < priv_keys.size(); ++i) { const auto& str = priv_keys[i].get_str(); @@ -1205,19 +1211,26 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); } const std::string& label = data.exists("label") ? data["label"].get_str() : ""; + const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false; + + // Add to keypool only works with privkeys disabled + if (add_keypool && !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Keys can only be imported to the keypool when private keys are disabled"); + } ImportData import_data; std::map<CKeyID, CPubKey> pubkey_map; std::map<CKeyID, CKey> privkey_map; std::set<CScript> script_pub_keys; + std::vector<CKeyID> ordered_pubkeys; bool have_solving_data; if (data.exists("scriptPubKey") && data.exists("desc")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Both a descriptor and a scriptPubKey should not be provided."); } else if (data.exists("scriptPubKey")) { - warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data); + warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys); } else if (data.exists("desc")) { - warnings = ProcessImportDescriptor(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data); + warnings = ProcessImportDescriptor(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys); } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "Either a descriptor or scriptPubKey must be provided."); } @@ -1239,27 +1252,40 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con for (const auto& entry : import_data.import_scripts) { if (!pwallet->HaveCScript(CScriptID(entry)) && !pwallet->AddCScript(entry)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet"); + } + } + for (const auto& entry : privkey_map) { + const CKey& key = entry.second; + CPubKey pubkey = key.GetPubKey(); + const CKeyID& id = entry.first; + assert(key.VerifyPubKey(pubkey)); + pwallet->mapKeyMetadata[id].nCreateTime = timestamp; + // If the private key is not present in the wallet, insert it. + if (!pwallet->HaveKey(id) && !pwallet->AddKeyPubKey(key, pubkey)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); + } + pwallet->UpdateTimeFirstKey(timestamp); + } + for (const CKeyID& id : ordered_pubkeys) { + auto entry = pubkey_map.find(id); + if (entry == pubkey_map.end()) { + continue; } - } - for (const auto& entry : privkey_map) { - const CKey& key = entry.second; - CPubKey pubkey = key.GetPubKey(); - const CKeyID& id = entry.first; - assert(key.VerifyPubKey(pubkey)); - pwallet->mapKeyMetadata[id].nCreateTime = timestamp; - // If the private key is not present in the wallet, insert it. - if (!pwallet->HaveKey(id) && !pwallet->AddKeyPubKey(key, pubkey)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); - } - pwallet->UpdateTimeFirstKey(timestamp); - } - for (const auto& entry : pubkey_map) { - const CPubKey& pubkey = entry.second; - const CKeyID& id = entry.first; - CPubKey temp; - if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) { + const CPubKey& pubkey = entry->second; + CPubKey temp; + if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } + const auto& key_orig_it = import_data.key_origins.find(id); + if (key_orig_it != import_data.key_origins.end()) { + pwallet->AddKeyOrigin(pubkey, key_orig_it->second); + } + pwallet->mapKeyMetadata[id].nCreateTime = timestamp; + + // Add to keypool only works with pubkeys + if (add_keypool) { + pwallet->AddKeypoolPubkey(pubkey, internal); + } } for (const CScript& script : script_pub_keys) { @@ -1356,6 +1382,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) {"internal", RPCArg::Type::BOOL, /* default */ "false", "Stating whether matching outputs should be treated as not incoming payments (also known as change)"}, {"watchonly", RPCArg::Type::BOOL, /* default */ "false", "Stating whether matching outputs should be considered watchonly."}, {"label", RPCArg::Type::STR, /* default */ "''", "Label to assign to the address, only allowed with internal=false"}, + {"keypool", RPCArg::Type::BOOL, /* default */ "false", "Stating whether imported public keys should be added to the keypool for when users request new addresses. Only allowed when wallet private keys are disabled"}, }, }, }, diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 4211eeac88..97c6c38be1 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -27,6 +27,7 @@ #include <script/sign.h> #include <shutdown.h> #include <timedata.h> +#include <util/bip32.h> #include <util/system.h> #include <util/moneystr.h> #include <wallet/coincontrol.h> @@ -172,18 +173,12 @@ static UniValue getnewaddress(const JSONRPCRequest& request) }, }.ToString()); - // Belt and suspenders check for disabled private keys - if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); - } - LOCK(pwallet->cs_wallet); if (!pwallet->CanGetAddresses()) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys"); } - // Parse the label first so we don't generate a key if there's an error std::string label; if (!request.params[0].isNull()) @@ -239,11 +234,6 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request) }, }.ToString()); - // Belt and suspenders check for disabled private keys - if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); - } - LOCK(pwallet->cs_wallet); if (!pwallet->CanGetAddresses(true)) { @@ -2418,7 +2408,6 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) " \"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" " \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n" - " \"hdmasterkeyid\": \"<hash160>\" (string, optional) alias for hdseedid retained for backwards-compatibility. Will be removed in V0.18.\n" " \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n" "}\n" }, @@ -2447,7 +2436,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime()); obj.pushKV("keypoolsize", (int64_t)kpExternalSize); CKeyID seed_id = pwallet->GetHDChain().seed_id; - if (!seed_id.IsNull() && pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) { + if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) { obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize)); } if (pwallet->IsCrypted()) { @@ -2456,7 +2445,6 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK())); if (!seed_id.IsNull()) { obj.pushKV("hdseedid", seed_id.GetHex()); - obj.pushKV("hdmasterkeyid", seed_id.GetHex()); } obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); return obj; @@ -2770,7 +2758,8 @@ static UniValue listunspent(const JSONRPCRequest& request) " \"scriptPubKey\" : \"key\", (string) the script key\n" " \"amount\" : x.xxx, (numeric) the transaction output amount in " + CURRENCY_UNIT + "\n" " \"confirmations\" : n, (numeric) The number of confirmations\n" - " \"redeemScript\" : n (string) The redeemScript if scriptPubKey is P2SH\n" + " \"redeemScript\" : \"script\" (string) The redeemScript if scriptPubKey is P2SH\n" + " \"witnessScript\" : \"script\" (string) witnessScript if the scriptPubKey is P2WSH or P2SH-P2WSH\n" " \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n" " \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n" " \"desc\" : xxx, (string, only when solvable) A descriptor for spending this output\n" @@ -2884,6 +2873,28 @@ static UniValue listunspent(const JSONRPCRequest& request) CScript redeemScript; if (pwallet->GetCScript(hash, redeemScript)) { entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())); + // Now check if the redeemScript is actually a P2WSH script + CTxDestination witness_destination; + if (redeemScript.IsPayToWitnessScriptHash()) { + bool extracted = ExtractDestination(redeemScript, witness_destination); + assert(extracted); + // Also return the witness script + const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination); + CScriptID id; + CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); + CScript witnessScript; + if (pwallet->GetCScript(id, witnessScript)) { + entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + } + } + } + } else if (scriptPubKey.IsPayToWitnessScriptHash()) { + const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(address); + CScriptID id; + CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); + CScript witnessScript; + if (pwallet->GetCScript(id, witnessScript)) { + entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); } } } @@ -3139,7 +3150,8 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, {"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "script key"}, - {"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH or P2WSH) redeem script"}, + {"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH) redeem script"}, + {"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2WSH or P2SH-P2WSH) witness script"}, {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount spent"}, }, }, @@ -3660,7 +3672,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request) " \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n" " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n" " \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed\n" - " \"hdmasterkeyid\" : \"<hash160>\" (string, optional) alias for hdseedid maintained for backwards compatibility. Will be removed in V0.18.\n" + " \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The fingperint of the master key.\n" " \"labels\" (object) Array of labels associated with the address.\n" " [\n" " { (json object of label data)\n" @@ -3723,10 +3735,10 @@ UniValue getaddressinfo(const JSONRPCRequest& request) } if (meta) { ret.pushKV("timestamp", meta->nCreateTime); - if (!meta->hdKeypath.empty()) { - ret.pushKV("hdkeypath", meta->hdKeypath); + if (meta->has_key_origin) { + ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path)); ret.pushKV("hdseedid", meta->hd_seed_id.GetHex()); - ret.pushKV("hdmasterkeyid", meta->hd_seed_id.GetHex()); + ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint, meta->key_origin.fingerprint + 4)); } } diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index d1a9741ca1..789e86e21b 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -4,6 +4,7 @@ #include <key_io.h> #include <script/sign.h> +#include <util/bip32.h> #include <util/strencodings.h> #include <wallet/psbtwallet.h> #include <wallet/rpcwallet.h> diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 063015d1d8..388422bec8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -27,6 +27,7 @@ #include <shutdown.h> #include <timedata.h> #include <txmempool.h> +#include <util/bip32.h> #include <util/moneystr.h> #include <wallet/fees.h> @@ -255,16 +256,25 @@ void CWallet::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey if (internal) { chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT); metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.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++; } else { chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.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++; } } while (HaveKey(childKey.key.GetPubKey().GetID())); secret = childKey.key; metadata.hd_seed_id = hdChain.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"); @@ -348,6 +358,54 @@ void CWallet::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata m_script_metadata[script_id] = meta; } +// Writes a keymetadata for a public key. overwrite specifies whether to overwrite an existing metadata for that key if there exists one. +bool CWallet::WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite) +{ + return WalletBatch(*database).WriteKeyMetadata(meta, pubkey, overwrite); +} + +void CWallet::UpgradeKeyMetadata() +{ + AssertLockHeld(cs_wallet); // mapKeyMetadata + if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) { + return; + } + + std::unique_ptr<WalletBatch> batch = MakeUnique<WalletBatch>(*database); + size_t cnt = 0; + for (auto& meta_pair : mapKeyMetadata) { + CKeyMetadata& meta = meta_pair.second; + if (!meta.hd_seed_id.IsNull() && !meta.has_key_origin && meta.hdKeypath != "s") { // If the hdKeypath is "s", that's the seed and it doesn't have a key origin + CKey key; + GetKey(meta.hd_seed_id, key); + CExtKey masterKey; + masterKey.SetSeed(key.begin(), key.size()); + // Add to map + CKeyID master_id = masterKey.key.GetPubKey().GetID(); + std::copy(master_id.begin(), master_id.begin() + 4, meta.key_origin.fingerprint); + if (!ParseHDKeypath(meta.hdKeypath, meta.key_origin.path)) { + throw std::runtime_error("Invalid stored hdKeypath"); + } + meta.has_key_origin = true; + if (meta.nVersion < CKeyMetadata::VERSION_WITH_KEY_ORIGIN) { + meta.nVersion = CKeyMetadata::VERSION_WITH_KEY_ORIGIN; + } + + // Write meta to wallet + CPubKey pubkey; + if (GetPubKey(meta_pair.first, pubkey)) { + batch->WriteKeyMetadata(meta, pubkey, true); + if (++cnt % 1000 == 0) { + // avoid creating overlarge in-memory batches in case the wallet contains large amounts of keys + batch.reset(new WalletBatch(*database)); + } + } + } + } + batch.reset(); //write before setting the flag + SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA); +} + bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) { return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); @@ -446,8 +504,11 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_key return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) continue; // try another master key - if (CCryptoKeyStore::Unlock(_vMasterKey, accept_no_keys)) + if (CCryptoKeyStore::Unlock(_vMasterKey, accept_no_keys)) { + // Now that we've unlocked, upgrade the key metadata + UpgradeKeyMetadata(); return true; + } } } return false; @@ -1407,6 +1468,7 @@ CPubKey CWallet::DeriveNewSeed(const CKey& key) // set the hd keypath to "s" -> Seed, refers the seed to itself metadata.hdKeypath = "s"; + metadata.has_key_origin = false; metadata.hd_seed_id = seed.GetID(); { @@ -2778,8 +2840,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // post-backup change. // Reserve a new key pair from key pool - if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - strFailReason = _("Can't generate a change-address key. Private keys are disabled for this wallet."); + if (!CanGetAddresses(true)) { + strFailReason = _("Can't generate a change-address key. No keys in the internal keypool and can't generate any keys."); return false; } CPubKey vchPubKey; @@ -3388,20 +3450,8 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) internal = true; } - assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys? - int64_t index = ++m_max_keypool_index; - CPubKey pubkey(GenerateNewKey(batch, internal)); - if (!batch.WritePool(index, CKeyPool(pubkey, internal))) { - throw std::runtime_error(std::string(__func__) + ": writing generated key failed"); - } - - if (internal) { - setInternalKeyPool.insert(index); - } else { - setExternalKeyPool.insert(index); - } - m_pool_key_to_index[pubkey.GetID()] = index; + AddKeypoolPubkeyWithDB(pubkey, internal, batch); } if (missingInternal + missingExternal > 0) { WalletLogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size()); @@ -3411,6 +3461,29 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) return true; } +void CWallet::AddKeypoolPubkey(const CPubKey& pubkey, const bool internal) +{ + WalletBatch batch(*database); + AddKeypoolPubkeyWithDB(pubkey, internal, batch); + NotifyCanGetAddressesChanged(); +} + +void CWallet::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch) +{ + LOCK(cs_wallet); + assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys? + int64_t index = ++m_max_keypool_index; + if (!batch.WritePool(index, CKeyPool(pubkey, internal))) { + throw std::runtime_error(std::string(__func__) + ": writing imported pubkey failed"); + } + if (internal) { + setInternalKeyPool.insert(index); + } else { + setExternalKeyPool.insert(index); + } + m_pool_key_to_index[pubkey.GetID()] = index; +} + bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal) { nIndex = -1; @@ -3421,7 +3494,8 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe if (!IsLocked()) TopUpKeyPool(); - bool fReturningInternal = IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT) && fRequestedInternal; + bool fReturningInternal = fRequestedInternal; + fReturningInternal &= (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) || IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); bool use_split_keypool = set_pre_split_keypool.empty(); std::set<int64_t>& setKeyPool = use_split_keypool ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) : set_pre_split_keypool; @@ -3438,7 +3512,8 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe if (!batch.ReadPool(nIndex, keypool)) { throw std::runtime_error(std::string(__func__) + ": read failed"); } - if (!HaveKey(keypool.vchPubKey.GetID())) { + CPubKey pk; + if (!GetPubKey(keypool.vchPubKey.GetID(), pk)) { throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); } // If the key was pre-split keypool, we don't care about what type it is @@ -3492,7 +3567,7 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) { LOCK(cs_wallet); int64_t nIndex; - if (!ReserveKeyFromKeyPool(nIndex, keypool, internal)) { + if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (IsLocked()) return false; WalletBatch batch(*database); result = GenerateNewKey(batch, internal); @@ -4487,18 +4562,21 @@ bool CWallet::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const meta = it->second; } } - if (!meta.hdKeypath.empty()) { - if (!ParseHDKeypath(meta.hdKeypath, info.path)) return false; - // Get the proper master key id - CKey key; - GetKey(meta.hd_seed_id, key); - CExtKey masterKey; - masterKey.SetSeed(key.begin(), key.size()); - // Compute identifier - CKeyID masterid = masterKey.key.GetPubKey().GetID(); - std::copy(masterid.begin(), masterid.begin() + 4, info.fingerprint); + if (meta.has_key_origin) { + std::copy(meta.key_origin.fingerprint, meta.key_origin.fingerprint + 4, info.fingerprint); + info.path = meta.key_origin.path; } else { // Single pubkeys get the master fingerprint of themselves std::copy(keyID.begin(), keyID.begin() + 4, info.fingerprint); } return true; } + +bool CWallet::AddKeyOrigin(const CPubKey& pubkey, const KeyOriginInfo& info) +{ + LOCK(cs_wallet); + std::copy(info.fingerprint, info.fingerprint + 4, mapKeyMetadata[pubkey.GetID()].key_origin.fingerprint); + mapKeyMetadata[pubkey.GetID()].key_origin.path = info.path; + mapKeyMetadata[pubkey.GetID()].has_key_origin = true; + mapKeyMetadata[pubkey.GetID()].hdKeypath = WriteHDKeypath(info.path); + return WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true); +} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 5846ac0f3e..2a5d6caaf8 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -135,6 +135,9 @@ enum WalletFlags : uint64_t { // wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown // unknown wallet flags in the lower section <= (1 << 31) will be tolerated + // Indicates that the metadata has already been upgraded to contain key origins + WALLET_FLAG_KEY_ORIGIN_METADATA = (1ULL << 1), + // will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys) WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32), @@ -151,7 +154,7 @@ enum WalletFlags : uint64_t { WALLET_FLAG_BLANK_WALLET = (1ULL << 33), }; -static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET; +static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET | WALLET_FLAG_KEY_ORIGIN_METADATA; /** A key pool entry */ class CKeyPool @@ -776,6 +779,8 @@ public: // Map from Script ID to key metadata (for watch-only keys). std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_wallet); + bool WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, bool overwrite); + typedef std::map<unsigned int, CMasterKey> MasterKeyMap; MasterKeyMap mapMasterKeys; unsigned int nMasterKeyMaxID = 0; @@ -866,6 +871,8 @@ public: //! Load metadata (used by LoadWallet) void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + //! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo + void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; } void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -994,6 +1001,8 @@ public: bool NewKeyPool(); size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool TopUpKeyPool(unsigned int kpSize = 0); + void AddKeypoolPubkey(const CPubKey& pubkey, const bool internal); + void AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch); /** * Reserves a key from the keypool and sets nIndex to its index @@ -1212,6 +1221,9 @@ public: /** Implement lookup of key origin information through wallet key metadata. */ bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; + + /** Add a KeyOriginInfo to the wallet */ + bool AddKeyOrigin(const CPubKey& pubkey, const KeyOriginInfo& info); }; /** A key allocated from the key pool. */ diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 6e037808e3..2783f83fd6 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -57,9 +57,14 @@ bool WalletBatch::EraseTx(uint256 hash) return EraseIC(std::make_pair(std::string("tx"), hash)); } +bool WalletBatch::WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite) +{ + return WriteIC(std::make_pair(std::string("keymeta"), pubkey), meta, overwrite); +} + bool WalletBatch::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta) { - if (!WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta, false)) { + if (!WriteKeyMetadata(keyMeta, vchPubKey, false)) { return false; } @@ -76,7 +81,7 @@ bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey, const std::vector<unsigned char>& vchCryptedSecret, const CKeyMetadata &keyMeta) { - if (!WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta)) { + if (!WriteKeyMetadata(keyMeta, vchPubKey, true)) { return false; } @@ -529,6 +534,14 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) if (wss.fAnyUnordered) result = pwallet->ReorderTransactions(); + // Upgrade all of the wallet keymetadata to have the hd master key id + // This operation is not atomic, but if it fails, updated entries are still backwards compatible with older software + try { + pwallet->UpgradeKeyMetadata(); + } catch (...) { + result = DBErrors::CORRUPT; + } + return result; } diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 5584407a56..0532a55ff5 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -8,6 +8,7 @@ #include <amount.h> #include <primitives/transaction.h> +#include <script/sign.h> #include <wallet/db.h> #include <key.h> @@ -93,11 +94,14 @@ class CKeyMetadata public: static const int VERSION_BASIC=1; static const int VERSION_WITH_HDDATA=10; - static const int CURRENT_VERSION=VERSION_WITH_HDDATA; + static const int VERSION_WITH_KEY_ORIGIN = 12; + static const int CURRENT_VERSION=VERSION_WITH_KEY_ORIGIN; int nVersion; int64_t nCreateTime; // 0 means unknown - std::string hdKeypath; //optional HD/bip32 keypath + std::string hdKeypath; //optional HD/bip32 keypath. Still used to determine whether a key is a seed. Also kept for backwards compatibility CKeyID hd_seed_id; //id of the HD seed used to derive this key + KeyOriginInfo key_origin; // Key origin info with path and fingerprint + bool has_key_origin = false; //< Whether the key_origin is useful CKeyMetadata() { @@ -120,6 +124,11 @@ public: READWRITE(hdKeypath); READWRITE(hd_seed_id); } + if (this->nVersion >= VERSION_WITH_KEY_ORIGIN) + { + READWRITE(key_origin); + READWRITE(has_key_origin); + } } void SetNull() @@ -128,6 +137,8 @@ public: nCreateTime = 0; hdKeypath.clear(); hd_seed_id.SetNull(); + key_origin.clear(); + has_key_origin = false; } }; @@ -177,6 +188,7 @@ public: bool WriteTx(const CWalletTx& wtx); bool EraseTx(uint256 hash); + bool WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite); bool WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata &keyMeta); bool WriteCryptedKey(const CPubKey& vchPubKey, const std::vector<unsigned char>& vchCryptedSecret, const CKeyMetadata &keyMeta); bool WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey); |