diff options
author | Andrew Chow <achow101-github@achow101.com> | 2019-10-07 14:11:34 -0400 |
---|---|---|
committer | Andrew Chow <achow101-github@achow101.com> | 2019-10-25 19:20:24 -0400 |
commit | 6702048f91089d7a565e5ca5f7c8dcd2ca405a85 (patch) | |
tree | 06ce34574f0a51b70967af8cbd6856fb3a7b7199 /src/wallet/scriptpubkeyman.cpp | |
parent | ab053ec6d1e766402f88947d29cd875a285e7280 (diff) |
MOVEONLY: Move key handling code out of wallet to keyman file
Start moving wallet and ismine code to scriptpubkeyman.h, scriptpubkeyman.cpp
The easiest way to review this commit is to run:
git log -p -n1 --color-moved=dimmed_zebra
And check that everything is a move (other than includes and copyrights comments).
This commit is move-only and doesn't change code or affect behavior.
Diffstat (limited to 'src/wallet/scriptpubkeyman.cpp')
-rw-r--r-- | src/wallet/scriptpubkeyman.cpp | 1262 |
1 files changed, 1262 insertions, 0 deletions
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp new file mode 100644 index 0000000000..77ee8f97cb --- /dev/null +++ b/src/wallet/scriptpubkeyman.cpp @@ -0,0 +1,1262 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <key_io.h> +#include <outputtype.h> +#include <script/descriptor.h> +#include <util/bip32.h> +#include <util/strencodings.h> +#include <util/translation.h> +#include <wallet/scriptpubkeyman.h> +#include <wallet/wallet.h> + +bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error) +{ + LOCK(cs_wallet); + error.clear(); + + TopUpKeyPool(); + + // Generate a new key that is added to wallet + CPubKey new_key; + if (!GetKeyFromPool(new_key)) { + error = "Error: Keypool ran out, please call keypoolrefill first"; + return false; + } + LearnRelatedScripts(new_key, type); + dest = GetDestinationForKey(new_key, type); + + SetAddressBook(dest, label, "receive"); + return true; +} + +typedef std::vector<unsigned char> valtype; + +namespace { + +/** + * This is an enum that tracks the execution context of a script, similar to + * SigVersion in script/interpreter. It is separate however because we want to + * distinguish between top-level scriptPubKey execution and P2SH redeemScript + * execution (a distinction that has no impact on consensus rules). + */ +enum class IsMineSigVersion +{ + TOP = 0, //!< scriptPubKey execution + P2SH = 1, //!< P2SH redeemScript + WITNESS_V0 = 2, //!< P2WSH witness script execution +}; + +/** + * This is an internal representation of isminetype + invalidity. + * Its order is significant, as we return the max of all explored + * possibilities. + */ +enum class IsMineResult +{ + NO = 0, //!< Not ours + WATCH_ONLY = 1, //!< Included in watch-only balance + SPENDABLE = 2, //!< Included in all balances + INVALID = 3, //!< Not spendable by anyone (uncompressed pubkey in segwit, P2SH inside P2SH or witness, witness inside witness) +}; + +bool PermitsUncompressed(IsMineSigVersion sigversion) +{ + return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH; +} + +bool HaveKeys(const std::vector<valtype>& pubkeys, const CWallet& keystore) +{ + for (const valtype& pubkey : pubkeys) { + CKeyID keyID = CPubKey(pubkey).GetID(); + if (!keystore.HaveKey(keyID)) return false; + } + return true; +} + +IsMineResult IsMineInner(const CWallet& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion) +{ + IsMineResult ret = IsMineResult::NO; + + std::vector<valtype> vSolutions; + txnouttype whichType = Solver(scriptPubKey, vSolutions); + + CKeyID keyID; + switch (whichType) + { + case TX_NONSTANDARD: + case TX_NULL_DATA: + case TX_WITNESS_UNKNOWN: + break; + case TX_PUBKEY: + keyID = CPubKey(vSolutions[0]).GetID(); + if (!PermitsUncompressed(sigversion) && vSolutions[0].size() != 33) { + return IsMineResult::INVALID; + } + if (keystore.HaveKey(keyID)) { + ret = std::max(ret, IsMineResult::SPENDABLE); + } + break; + case TX_WITNESS_V0_KEYHASH: + { + if (sigversion == IsMineSigVersion::WITNESS_V0) { + // P2WPKH inside P2WSH is invalid. + return IsMineResult::INVALID; + } + if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { + // We do not support bare witness outputs unless the P2SH version of it would be + // acceptable as well. This protects against matching before segwit activates. + // This also applies to the P2WSH case. + break; + } + ret = std::max(ret, IsMineInner(keystore, GetScriptForDestination(PKHash(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0)); + break; + } + case TX_PUBKEYHASH: + keyID = CKeyID(uint160(vSolutions[0])); + if (!PermitsUncompressed(sigversion)) { + CPubKey pubkey; + if (keystore.GetPubKey(keyID, pubkey) && !pubkey.IsCompressed()) { + return IsMineResult::INVALID; + } + } + if (keystore.HaveKey(keyID)) { + ret = std::max(ret, IsMineResult::SPENDABLE); + } + break; + case TX_SCRIPTHASH: + { + if (sigversion != IsMineSigVersion::TOP) { + // P2SH inside P2WSH or P2SH is invalid. + return IsMineResult::INVALID; + } + CScriptID scriptID = CScriptID(uint160(vSolutions[0])); + CScript subscript; + if (keystore.GetCScript(scriptID, subscript)) { + ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::P2SH)); + } + break; + } + case TX_WITNESS_V0_SCRIPTHASH: + { + if (sigversion == IsMineSigVersion::WITNESS_V0) { + // P2WSH inside P2WSH is invalid. + return IsMineResult::INVALID; + } + if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { + break; + } + uint160 hash; + CRIPEMD160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(hash.begin()); + CScriptID scriptID = CScriptID(hash); + CScript subscript; + if (keystore.GetCScript(scriptID, subscript)) { + ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0)); + } + break; + } + + case TX_MULTISIG: + { + // Never treat bare multisig outputs as ours (they can still be made watchonly-though) + if (sigversion == IsMineSigVersion::TOP) { + break; + } + + // Only consider transactions "mine" if we own ALL the + // keys involved. Multi-signature transactions that are + // partially owned (somebody else has a key that can spend + // them) enable spend-out-from-under-you attacks, especially + // in shared-wallet situations. + std::vector<valtype> keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1); + if (!PermitsUncompressed(sigversion)) { + for (size_t i = 0; i < keys.size(); i++) { + if (keys[i].size() != 33) { + return IsMineResult::INVALID; + } + } + } + if (HaveKeys(keys, keystore)) { + ret = std::max(ret, IsMineResult::SPENDABLE); + } + break; + } + } + + if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) { + ret = std::max(ret, IsMineResult::WATCH_ONLY); + } + return ret; +} + +} // namespace + +isminetype IsMine(const CWallet& keystore, const CScript& scriptPubKey) +{ + switch (IsMineInner(keystore, scriptPubKey, IsMineSigVersion::TOP)) { + case IsMineResult::INVALID: + case IsMineResult::NO: + return ISMINE_NO; + case IsMineResult::WATCH_ONLY: + return ISMINE_WATCH_ONLY; + case IsMineResult::SPENDABLE: + return ISMINE_SPENDABLE; + } + assert(false); +} + +bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) +{ + { + LOCK(cs_KeyStore); + if (!SetCrypted()) + return false; + + bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys + bool keyFail = false; + CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); + for (; mi != mapCryptedKeys.end(); ++mi) + { + const CPubKey &vchPubKey = (*mi).second.first; + const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; + CKey key; + if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key)) + { + keyFail = true; + break; + } + keyPass = true; + if (fDecryptionThoroughlyChecked) + break; + } + if (keyPass && keyFail) + { + LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n"); + throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt."); + } + if (keyFail || (!keyPass && !accept_no_keys)) + return false; + vMasterKey = vMasterKeyIn; + fDecryptionThoroughlyChecked = true; + } + NotifyStatusChanged(this); + return true; +} + +bool CWallet::EncryptKeys(CKeyingMaterial& vMasterKeyIn) +{ + LOCK(cs_KeyStore); + if (!mapCryptedKeys.empty() || IsCrypted()) + return false; + + fUseCrypto = true; + for (const KeyMap::value_type& mKey : mapKeys) + { + const CKey &key = mKey.second; + CPubKey vchPubKey = key.GetPubKey(); + CKeyingMaterial vchSecret(key.begin(), key.end()); + std::vector<unsigned char> vchCryptedSecret; + if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) + return false; + if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) + return false; + } + mapKeys.clear(); + return true; +} + +void CWallet::UpgradeKeyMetadata() +{ + AssertLockHeld(cs_wallet); + if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) { + return; + } + + std::unique_ptr<WalletBatch> batch = MakeUnique<WalletBatch>(*database); + 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); + } + } + } + batch.reset(); //write before setting the flag + SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA); +} + +bool CWallet::IsHDEnabled() const +{ + return !hdChain.seed_id.IsNull(); +} + +bool CWallet::CanGetAddresses(bool internal) +{ + LOCK(cs_wallet); + // Check if the keypool has keys + bool keypool_has_keys; + if (internal && CanSupportFeature(FEATURE_HD_SPLIT)) { + keypool_has_keys = setInternalKeyPool.size() > 0; + } else { + keypool_has_keys = KeypoolCountExternalKeys() > 0; + } + // If the keypool doesn't have keys, check if we can generate them + if (!keypool_has_keys) { + return CanGenerateKeys(); + } + return keypool_has_keys; +} + +static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, WalletBatch& batch) { + if (setKeyPool.empty()) { + return GetTime(); + } + + CKeyPool keypool; + int64_t nIndex = *(setKeyPool.begin()); + if (!batch.ReadPool(nIndex, keypool)) { + throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed"); + } + assert(keypool.vchPubKey.IsValid()); + return keypool.nTime; +} + +int64_t CWallet::GetOldestKeyPoolTime() +{ + LOCK(cs_wallet); + + WalletBatch batch(*database); + + // load oldest key from keypool, get time and return + int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, batch); + if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) { + oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, batch), oldestKey); + if (!set_pre_split_keypool.empty()) { + oldestKey = std::max(GetOldestKeyTimeInPool(set_pre_split_keypool, batch), oldestKey); + } + } + + return oldestKey; +} + +size_t CWallet::KeypoolCountExternalKeys() +{ + AssertLockHeld(cs_wallet); + return setExternalKeyPool.size() + set_pre_split_keypool.size(); +} + +/** + * Update wallet first key creation time. This should be called whenever keys + * are added to the wallet, with the oldest key creation time. + */ +void CWallet::UpdateTimeFirstKey(int64_t nCreateTime) +{ + AssertLockHeld(cs_wallet); + if (nCreateTime <= 1) { + // Cannot determine birthday information, so set the wallet birthday to + // the beginning of time. + nTimeFirstKey = 1; + } else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) { + nTimeFirstKey = nCreateTime; + } +} + +bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) +{ + WalletBatch batch(*database); + return CWallet::AddKeyPubKeyWithDB(batch, secret, pubkey); +} + +bool CWallet::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& secret, const CPubKey& pubkey) +{ + AssertLockHeld(cs_wallet); + + // Make sure we aren't adding private keys to private key disabled wallets + assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + + // FillableSigningProvider has no concept of wallet databases, but calls AddCryptedKey + // which is overridden below. To avoid flushes, the database handle is + // tunneled through to it. + bool needsDB = !encrypted_batch; + if (needsDB) { + encrypted_batch = &batch; + } + if (!AddKeyPubKeyInner(secret, pubkey)) { + if (needsDB) encrypted_batch = nullptr; + return false; + } + if (needsDB) encrypted_batch = nullptr; + + // check if we need to remove from watch-only + CScript script; + script = GetScriptForDestination(PKHash(pubkey)); + if (HaveWatchOnly(script)) { + RemoveWatchOnly(script); + } + script = GetScriptForRawPubKey(pubkey); + if (HaveWatchOnly(script)) { + RemoveWatchOnly(script); + } + + if (!IsCrypted()) { + return batch.WriteKey(pubkey, + secret.GetPrivKey(), + mapKeyMetadata[pubkey.GetID()]); + } + UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); + return true; +} + +bool CWallet::LoadCScript(const CScript& redeemScript) +{ + /* A sanity check was added in pull #3843 to avoid adding redeemScripts + * that never can be redeemed. However, old wallets may still contain + * these. Do not add them to the wallet and warn. */ + if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) + { + std::string strAddr = EncodeDestination(ScriptHash(redeemScript)); + WalletLogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); + return true; + } + + return FillableSigningProvider::AddCScript(redeemScript); +} + +void CWallet::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata& meta) +{ + AssertLockHeld(cs_wallet); + UpdateTimeFirstKey(meta.nCreateTime); + mapKeyMetadata[keyID] = meta; +} + +void CWallet::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta) +{ + AssertLockHeld(cs_wallet); + UpdateTimeFirstKey(meta.nCreateTime); + m_script_metadata[script_id] = meta; +} + +bool CWallet::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::AddKeyPubKey(key, pubkey); + } + + if (IsLocked()) { + return false; + } + + std::vector<unsigned char> vchCryptedSecret; + CKeyingMaterial vchSecret(key.begin(), key.end()); + if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) { + return false; + } + + if (!AddCryptedKey(pubkey, vchCryptedSecret)) { + return false; + } + return true; +} + +bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) +{ + return AddCryptedKeyInner(vchPubKey, vchCryptedSecret); +} + +bool CWallet::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) +{ + LOCK(cs_KeyStore); + if (!SetCrypted()) { + return false; + } + + mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); + ImplicitlyLearnRelatedKeyScripts(vchPubKey); + return true; +} + +bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, + const std::vector<unsigned char> &vchCryptedSecret) +{ + if (!AddCryptedKeyInner(vchPubKey, vchCryptedSecret)) + return false; + { + LOCK(cs_wallet); + if (encrypted_batch) + return encrypted_batch->WriteCryptedKey(vchPubKey, + vchCryptedSecret, + mapKeyMetadata[vchPubKey.GetID()]); + else + return WalletBatch(*database).WriteCryptedKey(vchPubKey, + vchCryptedSecret, + mapKeyMetadata[vchPubKey.GetID()]); + } +} + +bool CWallet::HaveWatchOnly(const CScript &dest) const +{ + LOCK(cs_KeyStore); + return setWatchOnly.count(dest) > 0; +} + +bool CWallet::HaveWatchOnly() const +{ + LOCK(cs_KeyStore); + return (!setWatchOnly.empty()); +} + +static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut) +{ + std::vector<std::vector<unsigned char>> solutions; + return Solver(dest, solutions) == TX_PUBKEY && + (pubKeyOut = CPubKey(solutions[0])).IsFullyValid(); +} + +bool CWallet::RemoveWatchOnly(const CScript &dest) +{ + AssertLockHeld(cs_wallet); + { + LOCK(cs_KeyStore); + setWatchOnly.erase(dest); + CPubKey pubKey; + if (ExtractPubKey(dest, pubKey)) { + mapWatchKeys.erase(pubKey.GetID()); + } + // Related CScripts are not removed; having superfluous scripts around is + // harmless (see comment in ImplicitlyLearnRelatedKeyScripts). + } + + if (!HaveWatchOnly()) + NotifyWatchonlyChanged(false); + if (!WalletBatch(*database).EraseWatchOnly(dest)) + return false; + + return true; +} + +bool CWallet::LoadWatchOnly(const CScript &dest) +{ + return AddWatchOnlyInMem(dest); +} + +bool CWallet::AddWatchOnlyInMem(const CScript &dest) +{ + LOCK(cs_KeyStore); + setWatchOnly.insert(dest); + CPubKey pubKey; + if (ExtractPubKey(dest, pubKey)) { + mapWatchKeys[pubKey.GetID()] = pubKey; + ImplicitlyLearnRelatedKeyScripts(pubKey); + } + return true; +} + +bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) +{ + if (!AddWatchOnlyInMem(dest)) + return false; + const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)]; + UpdateTimeFirstKey(meta.nCreateTime); + NotifyWatchonlyChanged(true); + if (batch.WriteWatchOnly(dest, meta)) { + UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); + return true; + } + return false; +} + +bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) +{ + m_script_metadata[CScriptID(dest)].nCreateTime = create_time; + return AddWatchOnlyWithDB(batch, dest); +} + +bool CWallet::AddWatchOnly(const CScript& dest) +{ + WalletBatch batch(*database); + return AddWatchOnlyWithDB(batch, dest); +} + +bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime) +{ + m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime; + return AddWatchOnly(dest); +} + +void CWallet::SetHDChain(const CHDChain& chain, bool memonly) +{ + LOCK(cs_wallet); + if (!memonly && !WalletBatch(*database).WriteHDChain(chain)) + throw std::runtime_error(std::string(__func__) + ": writing chain failed"); + + hdChain = chain; +} + +bool CWallet::HaveKey(const CKeyID &address) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::HaveKey(address); + } + return mapCryptedKeys.count(address) > 0; +} + +bool CWallet::GetKey(const CKeyID &address, CKey& keyOut) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::GetKey(address, keyOut); + } + + CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); + if (mi != mapCryptedKeys.end()) + { + const CPubKey &vchPubKey = (*mi).second.first; + const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; + return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); + } + return false; +} + +bool CWallet::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const +{ + CKeyMetadata meta; + { + LOCK(cs_wallet); + auto it = mapKeyMetadata.find(keyID); + if (it != mapKeyMetadata.end()) { + meta = it->second; + } + } + 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::GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const +{ + LOCK(cs_KeyStore); + WatchKeyMap::const_iterator it = mapWatchKeys.find(address); + if (it != mapWatchKeys.end()) { + pubkey_out = it->second; + return true; + } + return false; +} + +bool CWallet::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + if (!FillableSigningProvider::GetPubKey(address, vchPubKeyOut)) { + return GetWatchPubKey(address, vchPubKeyOut); + } + return true; + } + + CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); + if (mi != mapCryptedKeys.end()) + { + vchPubKeyOut = (*mi).second.first; + return true; + } + // Check for watch-only pubkeys + return GetWatchPubKey(address, vchPubKeyOut); +} + +CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal) +{ + assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + assert(!IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)); + AssertLockHeld(cs_wallet); + bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets + + CKey secret; + + // Create new metadata + int64_t nCreationTime = GetTime(); + CKeyMetadata metadata(nCreationTime); + + // use HD key derivation if HD was enabled during wallet creation and a seed is present + if (IsHDEnabled()) { + DeriveNewChildKey(batch, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false)); + } else { + secret.MakeNewKey(fCompressed); + } + + // Compressed public keys were introduced in version 0.6.0 + if (fCompressed) { + SetMinVersion(FEATURE_COMPRPUBKEY); + } + + CPubKey pubkey = secret.GetPubKey(); + assert(secret.VerifyPubKey(pubkey)); + + mapKeyMetadata[pubkey.GetID()] = metadata; + UpdateTimeFirstKey(nCreationTime); + + if (!AddKeyPubKeyWithDB(batch, secret, pubkey)) { + throw std::runtime_error(std::string(__func__) + ": AddKey failed"); + } + return pubkey; +} + +const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; + +void CWallet::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal) +{ + // for now we use a fixed keypath scheme of m/0'/0'/k + CKey seed; //seed (256bit) + CExtKey masterKey; //hd master key + CExtKey accountKey; //key at m/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 seed + if (!GetKey(hdChain.seed_id, seed)) + throw std::runtime_error(std::string(__func__) + ": seed not found"); + + masterKey.SetSeed(seed.begin(), seed.size()); + + // derive m/0' + // use hardened derivation (child keys >= 0x80000000 are hardened after bip32) + masterKey.Derive(accountKey, 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 + 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"); +} + +void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) +{ + AssertLockHeld(cs_wallet); + if (keypool.m_pre_split) { + set_pre_split_keypool.insert(nIndex); + } else if (keypool.fInternal) { + setInternalKeyPool.insert(nIndex); + } else { + setExternalKeyPool.insert(nIndex); + } + m_max_keypool_index = std::max(m_max_keypool_index, nIndex); + m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex; + + // If no metadata exists yet, create a default with the pool key's + // creation time. Note that this may be overwritten by actually + // stored metadata for that key later, which is fine. + CKeyID keyid = keypool.vchPubKey.GetID(); + if (mapKeyMetadata.count(keyid) == 0) + mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); +} + +bool CWallet::CanGenerateKeys() +{ + // A wallet can generate keys if it has an HD seed (IsHDEnabled) or it is a non-HD wallet (pre FEATURE_HD) + LOCK(cs_wallet); + return IsHDEnabled() || !CanSupportFeature(FEATURE_HD); +} + +CPubKey CWallet::GenerateNewSeed() +{ + assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + CKey key; + key.MakeNewKey(true); + return DeriveNewSeed(key); +} + +CPubKey CWallet::DeriveNewSeed(const CKey& key) +{ + int64_t nCreationTime = GetTime(); + CKeyMetadata metadata(nCreationTime); + + // calculate the seed + CPubKey seed = key.GetPubKey(); + assert(key.VerifyPubKey(seed)); + + // 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(); + + { + LOCK(cs_wallet); + + // mem store the metadata + mapKeyMetadata[seed.GetID()] = metadata; + + // write the key&metadata to the database + if (!AddKeyPubKey(key, seed)) + throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed"); + } + + return seed; +} + +void CWallet::SetHDSeed(const CPubKey& seed) +{ + LOCK(cs_wallet); + // store the keyid (hash160) together with + // the child index counter in the database + // as a hdchain object + CHDChain newHdChain; + newHdChain.nVersion = CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE; + newHdChain.seed_id = seed.GetID(); + SetHDChain(newHdChain, false); + NotifyCanGetAddressesChanged(); + UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); +} + +/** + * Mark old keypool keys as used, + * and generate all new keys + */ +bool CWallet::NewKeyPool() +{ + if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + return false; + } + { + LOCK(cs_wallet); + WalletBatch batch(*database); + + for (const int64_t nIndex : setInternalKeyPool) { + batch.ErasePool(nIndex); + } + setInternalKeyPool.clear(); + + for (const int64_t nIndex : setExternalKeyPool) { + batch.ErasePool(nIndex); + } + setExternalKeyPool.clear(); + + for (const int64_t nIndex : set_pre_split_keypool) { + batch.ErasePool(nIndex); + } + set_pre_split_keypool.clear(); + + m_pool_key_to_index.clear(); + + if (!TopUpKeyPool()) { + return false; + } + WalletLogPrintf("CWallet::NewKeyPool rewrote keypool\n"); + } + return true; +} + +bool CWallet::TopUpKeyPool(unsigned int kpSize) +{ + if (!CanGenerateKeys()) { + return false; + } + { + LOCK(cs_wallet); + + if (IsLocked()) return false; + + // Top up key pool + unsigned int nTargetSize; + if (kpSize > 0) + nTargetSize = kpSize; + else + nTargetSize = std::max(gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0); + + // count amount of available keys (internal, external) + // make sure the keypool of external and internal keys fits the user selected target (-keypool) + int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setExternalKeyPool.size(), (int64_t) 0); + int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setInternalKeyPool.size(), (int64_t) 0); + + if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT)) + { + // don't create extra internal keys + missingInternal = 0; + } + bool internal = false; + WalletBatch batch(*database); + for (int64_t i = missingInternal + missingExternal; i--;) + { + if (i < missingInternal) { + internal = true; + } + + CPubKey pubkey(GenerateNewKey(batch, internal)); + 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()); + } + } + NotifyCanGetAddressesChanged(); + return true; +} + +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; +} + +void CWallet::KeepKey(int64_t nIndex) +{ + // Remove from key pool + WalletBatch batch(*database); + batch.ErasePool(nIndex); + WalletLogPrintf("keypool keep %d\n", nIndex); +} + +void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey) +{ + // Return to key pool + { + LOCK(cs_wallet); + if (fInternal) { + setInternalKeyPool.insert(nIndex); + } else if (!set_pre_split_keypool.empty()) { + set_pre_split_keypool.insert(nIndex); + } else { + setExternalKeyPool.insert(nIndex); + } + m_pool_key_to_index[pubkey.GetID()] = nIndex; + NotifyCanGetAddressesChanged(); + } + WalletLogPrintf("keypool return %d\n", nIndex); +} + +bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) +{ + if (!CanGetAddresses(internal)) { + return false; + } + + CKeyPool keypool; + { + LOCK(cs_wallet); + int64_t nIndex; + if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (IsLocked()) return false; + WalletBatch batch(*database); + result = GenerateNewKey(batch, internal); + return true; + } + KeepKey(nIndex); + result = keypool.vchPubKey; + } + return true; +} + +bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal) +{ + nIndex = -1; + keypool.vchPubKey = CPubKey(); + { + LOCK(cs_wallet); + + TopUpKeyPool(); + + 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; + + // Get the oldest key + if (setKeyPool.empty()) { + return false; + } + + WalletBatch batch(*database); + + auto it = setKeyPool.begin(); + nIndex = *it; + setKeyPool.erase(it); + if (!batch.ReadPool(nIndex, keypool)) { + throw std::runtime_error(std::string(__func__) + ": read failed"); + } + 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 + if (use_split_keypool && keypool.fInternal != fReturningInternal) { + throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified"); + } + if (!keypool.vchPubKey.IsValid()) { + throw std::runtime_error(std::string(__func__) + ": keypool entry invalid"); + } + + m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); + WalletLogPrintf("keypool reserve %d\n", nIndex); + } + NotifyCanGetAddressesChanged(); + return true; +} + +void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type) +{ + if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) { + CTxDestination witdest = WitnessV0KeyHash(key.GetID()); + CScript witprog = GetScriptForDestination(witdest); + // Make sure the resulting program is solvable. + assert(IsSolvable(*this, witprog)); + AddCScript(witprog); + } +} + +void CWallet::LearnAllRelatedScripts(const CPubKey& key) +{ + // OutputType::P2SH_SEGWIT always adds all necessary scripts for all types. + LearnRelatedScripts(key, OutputType::P2SH_SEGWIT); +} + +void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) +{ + AssertLockHeld(cs_wallet); + bool internal = setInternalKeyPool.count(keypool_id); + if (!internal) assert(setExternalKeyPool.count(keypool_id) || set_pre_split_keypool.count(keypool_id)); + std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : (set_pre_split_keypool.empty() ? &setExternalKeyPool : &set_pre_split_keypool); + auto it = setKeyPool->begin(); + + WalletBatch batch(*database); + while (it != std::end(*setKeyPool)) { + const int64_t& index = *(it); + if (index > keypool_id) break; // set*KeyPool is ordered + + CKeyPool keypool; + if (batch.ReadPool(index, keypool)) { //TODO: This should be unnecessary + m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); + } + LearnAllRelatedScripts(keypool.vchPubKey); + batch.ErasePool(index); + WalletLogPrintf("keypool index %d removed\n", index); + it = setKeyPool->erase(it); + } +} + +std::vector<CKeyID> GetAffectedKeys(const CScript& spk, const SigningProvider& provider) +{ + std::vector<CScript> dummy; + FlatSigningProvider out; + InferDescriptor(spk, provider)->Expand(0, DUMMY_SIGNING_PROVIDER, dummy, out); + std::vector<CKeyID> ret; + for (const auto& entry : out.pubkeys) { + ret.push_back(entry.first); + } + return ret; +} + +void CWallet::MarkPreSplitKeys() +{ + WalletBatch batch(*database); + for (auto it = setExternalKeyPool.begin(); it != setExternalKeyPool.end();) { + int64_t index = *it; + CKeyPool keypool; + if (!batch.ReadPool(index, keypool)) { + throw std::runtime_error(std::string(__func__) + ": read keypool entry failed"); + } + keypool.m_pre_split = true; + if (!batch.WritePool(index, keypool)) { + throw std::runtime_error(std::string(__func__) + ": writing modified keypool entry failed"); + } + set_pre_split_keypool.insert(index); + it = setExternalKeyPool.erase(it); + } +} + +bool CWallet::AddCScript(const CScript& redeemScript) +{ + WalletBatch batch(*database); + return AddCScriptWithDB(batch, redeemScript); +} + +bool CWallet::AddCScriptWithDB(WalletBatch& batch, const CScript& redeemScript) +{ + if (!FillableSigningProvider::AddCScript(redeemScript)) + return false; + if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) { + UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); + return true; + } + return false; +} + +bool CWallet::AddKeyOriginWithDB(WalletBatch& batch, 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 batch.WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true); +} + +bool CWallet::ImportScripts(const std::set<CScript> scripts, int64_t timestamp) +{ + WalletBatch batch(*database); + for (const auto& entry : scripts) { + CScriptID id(entry); + if (HaveCScript(id)) { + WalletLogPrintf("Already have script %s, skipping\n", HexStr(entry)); + continue; + } + if (!AddCScriptWithDB(batch, entry)) { + return false; + } + + if (timestamp > 0) { + m_script_metadata[CScriptID(entry)].nCreateTime = timestamp; + } + } + if (timestamp > 0) { + UpdateTimeFirstKey(timestamp); + } + + return true; +} + +bool CWallet::ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) +{ + WalletBatch batch(*database); + for (const auto& entry : privkey_map) { + const CKey& key = entry.second; + CPubKey pubkey = key.GetPubKey(); + const CKeyID& id = entry.first; + assert(key.VerifyPubKey(pubkey)); + // Skip if we already have the key + if (HaveKey(id)) { + WalletLogPrintf("Already have key with pubkey %s, skipping\n", HexStr(pubkey)); + continue; + } + mapKeyMetadata[id].nCreateTime = timestamp; + // If the private key is not present in the wallet, insert it. + if (!AddKeyPubKeyWithDB(batch, key, pubkey)) { + return false; + } + UpdateTimeFirstKey(timestamp); + } + return true; +} + +bool CWallet::ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) +{ + WalletBatch batch(*database); + for (const auto& entry : key_origins) { + AddKeyOriginWithDB(batch, entry.second.first, entry.second.second); + } + for (const CKeyID& id : ordered_pubkeys) { + auto entry = pubkey_map.find(id); + if (entry == pubkey_map.end()) { + continue; + } + const CPubKey& pubkey = entry->second; + CPubKey temp; + if (GetPubKey(id, temp)) { + // Already have pubkey, skipping + WalletLogPrintf("Already have pubkey %s, skipping\n", HexStr(temp)); + continue; + } + if (!AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey), timestamp)) { + return false; + } + mapKeyMetadata[id].nCreateTime = timestamp; + + // Add to keypool only works with pubkeys + if (add_keypool) { + AddKeypoolPubkeyWithDB(pubkey, internal, batch); + NotifyCanGetAddressesChanged(); + } + } + return true; +} + +bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) +{ + WalletBatch batch(*database); + for (const CScript& script : script_pub_keys) { + if (!have_solving_data || !::IsMine(*this, script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated + if (!AddWatchOnlyWithDB(batch, script, timestamp)) { + return false; + } + } + CTxDestination dest; + ExtractDestination(script, dest); + if (apply_label && IsValidDestination(dest)) { + SetAddressBookWithDB(batch, dest, label, "receive"); + } + } + return true; +} + +std::set<CKeyID> CWallet::GetKeys() const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::GetKeys(); + } + std::set<CKeyID> set_address; + for (const auto& mi : mapCryptedKeys) { + set_address.insert(mi.first); + } + return set_address; +} |