mirror of
https://github.com/dashpay/dash.git
synced 2024-12-28 13:32:47 +01:00
9845f8c992
* MOVEONLY: Reorder LegacyScriptPubKeyMan methods Can verify move-only with: git log -p -n1 --color-moved This commit is move-only and doesn't change code or affect behavior. * Refactor: Declare LegacyScriptPubKeyMan methods as virtual This commit does not change behavior. * Refactor: Add new ScriptPubKeyMan virtual methods This commit does not change behavior. * Refactor: Move SetAddressBook call out of LegacyScriptPubKeyMan::GetNewDestination This commit does not change behavior. * Refactor: Move SetWalletFlag out of LegacyScriptPubKeyMan::UpgradeKeyMetadata This commit does not change behavior. * Remove SetWalletFlag from WalletStorage SetWalletFlag is unused. Does not change any behavior * Refactor: Remove UnsetWalletFlag call from LegacyScriptPubKeyMan::SetHDSeed This commit does not change behavior. * refactor: Replace UnsetWalletFlagWithDB with UnsetBlankWalletFlag in ScriptPubKeyMan ScriptPubKeyMan is only using UnsetWalletFlagWithDB to unset the blank wallet flag. Just make that it's own function and not expose the flag writing directly. This does not change behavior. * Refactor: Move SetAddressBookWithDB call out of LegacyScriptPubKeyMan::ImportScriptPubKeys This commit does not change behavior. * Refactor: Move LoadKey LegacyScriptPubKeyMan method definition This commit does not change behavior. * Refactor: Move GetMetadata code out of getaddressinfo Easier to review ignoring whitespace: git log -p -n1 -w This commit does not change behavior. * Refactor: Move MarkUnusedAddresses code out of CWallet::AddToWalletIfInvolvingMe This commit does not change behavior. * Refactor: Move HavePrivateKeys code out of CWallet::CreateWalletFromFile This commit does not change behavior. * Refactor: Move RewriteDB code out of CWallet This commit does not change behavior. * Refactor: Move GetKeypoolSize code out of CWallet This commit does not change behavior. * Refactor: Move nTimeFirstKey accesses out of CWallet This commit does not change behavior. * Re-order methods of scriptpubkeyman for easier backporting changes in future * Fixup for missing cs_wallet lock: ``` wallet/wallet.cpp:4536:41: error: calling function 'GetTimeFirstKey' requires holding mutex 'spk_man->cs_wallet' exclusively [-Werror,-Wthread-safety-analysis] int64_t time = spk_man->GetTimeFirstKey(); ^ wallet/wallet.cpp:4570:106: error: calling function 'GetTimeFirstKey' requires holding mutex 'walletInstance->m_spk_man->cs_wallet' exclusively [-Werror,-Wthread-safety-analysis] walletInstance->WalletLogPrintf("nTimeFirstKey = %u\n", walletInstance->m_spk_man->GetTimeFirstKey()); ``` * Fix 2 locks * more of "refactor: Replace UnsetWalletFlagWithDB with UnsetBlankWalletFlag in ScriptPubKeyMan" * Refactoring GetOldestKeyInPool -> GetOldestKeyTimeInPool, partial bitcoin#10235 Co-authored-by: Andrew Chow <achow101-github@achow101.com> Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
1730 lines
56 KiB
C++
1730 lines
56 KiB
C++
// 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 <chainparams.h>
|
|
#include <coinjoin/client.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 LegacyScriptPubKeyMan::GetNewDestination(CTxDestination& dest, std::string& error)
|
|
{
|
|
error.clear();
|
|
TopUpKeyPool();
|
|
|
|
// Generate a new key that is added to wallet
|
|
CPubKey new_key;
|
|
if (!GetKeyFromPool(new_key, false)) {
|
|
error = "Error: Keypool ran out, please call keypoolrefill first";
|
|
return false;
|
|
}
|
|
//LearnRelatedScripts(new_key);
|
|
dest = new_key.GetID();
|
|
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
|
|
};
|
|
|
|
/**
|
|
* 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 (P2SH inside P2SH)
|
|
};
|
|
|
|
bool PermitsUncompressed(IsMineSigVersion sigversion)
|
|
{
|
|
return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH;
|
|
}
|
|
|
|
bool HaveKeys(const std::vector<valtype>& pubkeys, const LegacyScriptPubKeyMan& keystore)
|
|
{
|
|
for (const valtype& pubkey : pubkeys) {
|
|
CKeyID keyID = CPubKey(pubkey).GetID();
|
|
if (!keystore.HaveKey(keyID)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
IsMineResult IsMineInner(const LegacyScriptPubKeyMan& 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:
|
|
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_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 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_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 LegacyScriptPubKeyMan::IsMine(const CScript& scriptPubKey) const
|
|
{
|
|
switch (IsMineInner(*this, 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);
|
|
}
|
|
|
|
isminetype LegacyScriptPubKeyMan::IsMine(const CTxDestination& dest) const
|
|
{
|
|
CScript script = GetScriptForDestination(dest);
|
|
return IsMine(script);
|
|
}
|
|
|
|
bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool fForMixingOnly, 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) {
|
|
return false;
|
|
}
|
|
if (!keyPass && !accept_no_keys) {
|
|
if (m_spk_man == nullptr) {
|
|
return false;
|
|
} else {
|
|
AssertLockHeld(m_spk_man->cs_KeyStore);
|
|
if (m_spk_man->cryptedHDChain.IsNull()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
vMasterKey = vMasterKeyIn;
|
|
|
|
if (m_spk_man != nullptr) {
|
|
AssertLockHeld(m_spk_man->cs_KeyStore);
|
|
if(!m_spk_man->cryptedHDChain.IsNull()) {
|
|
bool chainPass = false;
|
|
// try to decrypt seed and make sure it matches
|
|
CHDChain hdChainTmp;
|
|
if (m_spk_man->DecryptHDChain(hdChainTmp)) {
|
|
// make sure seed matches this chain
|
|
chainPass = m_spk_man->cryptedHDChain.GetID() == hdChainTmp.GetSeedHash();
|
|
}
|
|
if (!chainPass) {
|
|
vMasterKey.clear();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
fDecryptionThoroughlyChecked = true;
|
|
}
|
|
fOnlyMixingAllowed = fForMixingOnly;
|
|
NotifyStatusChanged(this);
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::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;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::GetReservedDestination(bool internal, int64_t& index, CKeyPool& keypool)
|
|
{
|
|
{
|
|
if (!ReserveKeyFromKeyPool(index, keypool, internal)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void LegacyScriptPubKeyMan::KeepDestination(int64_t index)
|
|
{
|
|
KeepKey(index);
|
|
}
|
|
|
|
void LegacyScriptPubKeyMan::ReturnDestination(int64_t index, bool internal, const CPubKey& pubkey)
|
|
{
|
|
ReturnKey(index, internal, pubkey);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::TopUp(unsigned int size)
|
|
{
|
|
return TopUpKeyPool(size);
|
|
}
|
|
|
|
void LegacyScriptPubKeyMan::MarkUnusedAddresses(WalletBatch &batch, const CScript& script, const uint256& hashBlock)
|
|
{
|
|
AssertLockHeld(cs_wallet);
|
|
// extract addresses and check if they match with an unused keypool key
|
|
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__);
|
|
MarkReserveKeysAsUsed(mi->second);
|
|
|
|
if (!TopUpKeyPool()) {
|
|
WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__);
|
|
}
|
|
}
|
|
if (!hashBlock.IsNull()) {
|
|
int64_t block_time;
|
|
bool found_block = m_wallet.chain().findBlock(hashBlock, nullptr /* block */, &block_time);
|
|
assert(found_block);
|
|
if (mapKeyMetadata[keyid].nCreateTime > block_time) {
|
|
WalletLogPrintf("%s: Found a key which appears to be used earlier than we expected, updating metadata\n", __func__);
|
|
CPubKey vchPubKey;
|
|
bool res = GetPubKey(keyid, vchPubKey);
|
|
assert(res); // this should never fail
|
|
mapKeyMetadata[keyid].nCreateTime = block_time;
|
|
batch.WriteKeyMetadata(mapKeyMetadata[keyid], vchPubKey, true);
|
|
UpdateTimeFirstKey(block_time);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LegacyScriptPubKeyMan::UpgradeKeyMetadata()
|
|
{
|
|
AssertLockHeld(cs_wallet); // mapKeyMetadata
|
|
if (m_storage.IsLocked() || m_storage.IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA) || !IsHDEnabled()) {
|
|
return;
|
|
}
|
|
|
|
CHDChain hdChainCurrent;
|
|
if (!GetHDChain(hdChainCurrent))
|
|
throw std::runtime_error(std::string(__func__) + ": GetHDChain failed");
|
|
if (!DecryptHDChain(hdChainCurrent))
|
|
throw std::runtime_error(std::string(__func__) + ": DecryptHDChain failed");
|
|
|
|
CExtKey masterKey;
|
|
SecureVector vchSeed = hdChainCurrent.GetSeed();
|
|
masterKey.SetSeed(vchSeed.data(), vchSeed.size());
|
|
CKeyID master_id = masterKey.key.GetPubKey().GetID();
|
|
|
|
LOCK(cs_KeyStore);
|
|
std::unique_ptr<WalletBatch> batch = MakeUnique<WalletBatch>(m_storage.GetDatabase());
|
|
size_t cnt = 0;
|
|
for (auto& meta_pair : mapKeyMetadata) {
|
|
const CKeyID& keyid = meta_pair.first;
|
|
CKeyMetadata& meta = meta_pair.second;
|
|
if (!meta.has_key_origin) {
|
|
HDPubKeyMap::const_iterator mi = mapHdPubKeys.find(keyid);
|
|
if (mi == mapHdPubKeys.end()) {
|
|
continue;
|
|
}
|
|
|
|
// Add to map
|
|
std::copy(master_id.begin(), master_id.begin() + 4, meta.key_origin.fingerprint);
|
|
if (!ParseHDKeypath(mi->second.GetKeyPath(), meta.key_origin.path)) {
|
|
throw std::runtime_error("Invalid HD keypath");
|
|
}
|
|
meta.has_key_origin = true;
|
|
if (meta.nVersion < CKeyMetadata::VERSION_WITH_KEY_ORIGIN) {
|
|
meta.nVersion = CKeyMetadata::VERSION_WITH_KEY_ORIGIN;
|
|
}
|
|
|
|
// Write meta to wallet
|
|
batch->WriteKeyMetadata(meta, mi->second.extPubKey.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(m_storage.GetDatabase()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LegacyScriptPubKeyMan::GenerateNewHDChain(const SecureString& secureMnemonic, const SecureString& secureMnemonicPassphrase)
|
|
{
|
|
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
|
|
CHDChain newHdChain;
|
|
|
|
// NOTE: an empty mnemonic means "generate a new one for me"
|
|
// NOTE: default mnemonic passphrase is an empty string
|
|
if (!newHdChain.SetMnemonic(secureMnemonic, secureMnemonicPassphrase, true)) {
|
|
throw std::runtime_error(std::string(__func__) + ": SetMnemonic failed");
|
|
}
|
|
|
|
// add default account
|
|
newHdChain.AddAccount();
|
|
newHdChain.Debug(__func__);
|
|
|
|
if (!SetHDChainSingle(newHdChain, false)) {
|
|
throw std::runtime_error(std::string(__func__) + ": SetHDChainSingle failed");
|
|
}
|
|
|
|
if (!NewKeyPool()) {
|
|
throw std::runtime_error(std::string(__func__) + ": NewKeyPool failed");
|
|
}
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::GenerateNewHDChainEncrypted(const SecureString& secureMnemonic, const SecureString& secureMnemonicPassphrase, const SecureString& secureWalletPassphrase)
|
|
{
|
|
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
|
|
LOCK(cs_wallet);
|
|
|
|
if (!IsCrypted()) {
|
|
return false;
|
|
}
|
|
|
|
CCrypter crypter;
|
|
CKeyingMaterial vMasterKey;
|
|
CHDChain hdChainTmp;
|
|
|
|
// NOTE: an empty mnemonic means "generate a new one for me"
|
|
// NOTE: default mnemonic passphrase is an empty string
|
|
if (!hdChainTmp.SetMnemonic(secureMnemonic, secureMnemonicPassphrase, true)) {
|
|
throw std::runtime_error(std::string(__func__) + ": SetMnemonic failed");
|
|
}
|
|
|
|
// add default account
|
|
hdChainTmp.AddAccount();
|
|
hdChainTmp.Debug(__func__);
|
|
|
|
for (const CWallet::MasterKeyMap::value_type& pMasterKey : m_wallet.mapMasterKeys) {
|
|
if (!crypter.SetKeyFromPassphrase(secureWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) {
|
|
return false;
|
|
}
|
|
// get vMasterKey to encrypt new hdChain
|
|
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) {
|
|
continue; // try another master key
|
|
}
|
|
|
|
bool res = EncryptHDChain(vMasterKey, hdChainTmp);
|
|
assert(res);
|
|
|
|
CHDChain hdChainCrypted;
|
|
res = GetHDChain(hdChainCrypted);
|
|
assert(res);
|
|
|
|
DBG(
|
|
tfm::format(std::cout, "GenerateNewHDChainEncrypted -- current seed: '%s'\n", HexStr(hdChainTmp.GetSeed()));
|
|
tfm::format(std::cout, "GenerateNewHDChainEncrypted -- crypted seed: '%s'\n", HexStr(hdChainCrypted.GetSeed()));
|
|
);
|
|
|
|
// ids should match, seed hashes should not
|
|
assert(hdChainTmp.GetID() == hdChainCrypted.GetID());
|
|
assert(hdChainTmp.GetSeedHash() != hdChainCrypted.GetSeedHash());
|
|
|
|
hdChainCrypted.Debug(__func__);
|
|
|
|
if (SetCryptedHDChainSingle(hdChainCrypted, false)) {
|
|
m_wallet.Lock();
|
|
if (!m_wallet.Unlock(secureWalletPassphrase)) {
|
|
// this should never happen
|
|
throw std::runtime_error(std::string(__func__) + ": Unlock failed");
|
|
}
|
|
if (!NewKeyPool()) {
|
|
throw std::runtime_error(std::string(__func__) + ": NewKeyPool failed");
|
|
}
|
|
m_wallet.Lock();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::SetHDChain(WalletBatch &batch, const CHDChain& chain, bool memonly)
|
|
{
|
|
LOCK(cs_wallet);
|
|
|
|
if (!SetHDChain(chain))
|
|
return false;
|
|
|
|
if (!memonly) {
|
|
if (!batch.WriteHDChain(chain)) {
|
|
throw std::runtime_error(std::string(__func__) + ": WriteHDChain failed");
|
|
}
|
|
|
|
m_storage.UnsetBlankWalletFlag(batch);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::SetCryptedHDChain(WalletBatch &batch, const CHDChain& chain, bool memonly)
|
|
{
|
|
LOCK(cs_wallet);
|
|
|
|
if (!SetCryptedHDChain(chain))
|
|
return false;
|
|
|
|
if (!memonly) {
|
|
if (encrypted_batch) {
|
|
if (!encrypted_batch->WriteCryptedHDChain(chain))
|
|
throw std::runtime_error(std::string(__func__) + ": WriteCryptedHDChain failed");
|
|
} else {
|
|
if (!batch.WriteCryptedHDChain(chain))
|
|
throw std::runtime_error(std::string(__func__) + ": WriteCryptedHDChain failed");
|
|
}
|
|
m_storage.UnsetBlankWalletFlag(batch);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::SetHDChainSingle(const CHDChain& chain, bool memonly)
|
|
{
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
return SetHDChain(batch, chain, memonly);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::SetCryptedHDChainSingle(const CHDChain& chain, bool memonly)
|
|
{
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
return SetCryptedHDChain(batch, chain, memonly);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::GetDecryptedHDChain(CHDChain& hdChainRet)
|
|
{
|
|
LOCK(cs_wallet);
|
|
|
|
CHDChain hdChainTmp;
|
|
if (!GetHDChain(hdChainTmp)) {
|
|
return false;
|
|
}
|
|
|
|
if (!DecryptHDChain(hdChainTmp))
|
|
return false;
|
|
|
|
// make sure seed matches this chain
|
|
if (hdChainTmp.GetID() != hdChainTmp.GetSeedHash())
|
|
return false;
|
|
|
|
hdChainRet = hdChainTmp;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::EncryptHDChain(const CKeyingMaterial& vMasterKeyIn, const CHDChain& chain)
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
// should call EncryptKeys first
|
|
if (!IsCrypted())
|
|
return false;
|
|
|
|
if (!cryptedHDChain.IsNull())
|
|
return true;
|
|
|
|
if (cryptedHDChain.IsCrypted())
|
|
return true;
|
|
|
|
if (hdChain.IsNull() && !chain.IsNull()) {
|
|
// Encrypting a new HDChain for an already encrypted non-HD wallet
|
|
hdChain = chain;
|
|
}
|
|
|
|
// make sure seed matches this chain
|
|
if (hdChain.GetID() != hdChain.GetSeedHash())
|
|
return false;
|
|
|
|
std::vector<unsigned char> vchCryptedSeed;
|
|
if (!EncryptSecret(vMasterKeyIn, hdChain.GetSeed(), hdChain.GetID(), vchCryptedSeed))
|
|
return false;
|
|
|
|
hdChain.Debug(__func__);
|
|
cryptedHDChain = hdChain;
|
|
cryptedHDChain.SetCrypted(true);
|
|
|
|
SecureVector vchSecureCryptedSeed(vchCryptedSeed.begin(), vchCryptedSeed.end());
|
|
if (!cryptedHDChain.SetSeed(vchSecureCryptedSeed, false))
|
|
return false;
|
|
|
|
SecureVector vchMnemonic;
|
|
SecureVector vchMnemonicPassphrase;
|
|
|
|
// it's ok to have no mnemonic if wallet was initialized via hdseed
|
|
if (hdChain.GetMnemonic(vchMnemonic, vchMnemonicPassphrase)) {
|
|
std::vector<unsigned char> vchCryptedMnemonic;
|
|
std::vector<unsigned char> vchCryptedMnemonicPassphrase;
|
|
|
|
if (!vchMnemonic.empty() && !EncryptSecret(vMasterKeyIn, vchMnemonic, hdChain.GetID(), vchCryptedMnemonic))
|
|
return false;
|
|
if (!vchMnemonicPassphrase.empty() && !EncryptSecret(vMasterKeyIn, vchMnemonicPassphrase, hdChain.GetID(), vchCryptedMnemonicPassphrase))
|
|
return false;
|
|
|
|
SecureVector vchSecureCryptedMnemonic(vchCryptedMnemonic.begin(), vchCryptedMnemonic.end());
|
|
SecureVector vchSecureCryptedMnemonicPassphrase(vchCryptedMnemonicPassphrase.begin(), vchCryptedMnemonicPassphrase.end());
|
|
if (!cryptedHDChain.SetMnemonic(vchSecureCryptedMnemonic, vchSecureCryptedMnemonicPassphrase, false))
|
|
return false;
|
|
}
|
|
|
|
if (!hdChain.SetNull())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::DecryptHDChain(CHDChain& hdChainRet) const
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
if (!IsCrypted())
|
|
return true;
|
|
|
|
if (cryptedHDChain.IsNull())
|
|
return false;
|
|
|
|
if (!cryptedHDChain.IsCrypted())
|
|
return false;
|
|
|
|
SecureVector vchSecureSeed;
|
|
SecureVector vchSecureCryptedSeed = cryptedHDChain.GetSeed();
|
|
std::vector<unsigned char> vchCryptedSeed(vchSecureCryptedSeed.begin(), vchSecureCryptedSeed.end());
|
|
if (!DecryptSecret(vMasterKey, vchCryptedSeed, cryptedHDChain.GetID(), vchSecureSeed))
|
|
return false;
|
|
|
|
hdChainRet = cryptedHDChain;
|
|
if (!hdChainRet.SetSeed(vchSecureSeed, false))
|
|
return false;
|
|
|
|
// hash of decrypted seed must match chain id
|
|
if (hdChainRet.GetSeedHash() != cryptedHDChain.GetID())
|
|
return false;
|
|
|
|
SecureVector vchSecureCryptedMnemonic;
|
|
SecureVector vchSecureCryptedMnemonicPassphrase;
|
|
|
|
// it's ok to have no mnemonic if wallet was initialized via hdseed
|
|
if (cryptedHDChain.GetMnemonic(vchSecureCryptedMnemonic, vchSecureCryptedMnemonicPassphrase)) {
|
|
SecureVector vchSecureMnemonic;
|
|
SecureVector vchSecureMnemonicPassphrase;
|
|
|
|
std::vector<unsigned char> vchCryptedMnemonic(vchSecureCryptedMnemonic.begin(), vchSecureCryptedMnemonic.end());
|
|
std::vector<unsigned char> vchCryptedMnemonicPassphrase(vchSecureCryptedMnemonicPassphrase.begin(), vchSecureCryptedMnemonicPassphrase.end());
|
|
|
|
if (!vchCryptedMnemonic.empty() && !DecryptSecret(vMasterKey, vchCryptedMnemonic, cryptedHDChain.GetID(), vchSecureMnemonic))
|
|
return false;
|
|
if (!vchCryptedMnemonicPassphrase.empty() && !DecryptSecret(vMasterKey, vchCryptedMnemonicPassphrase, cryptedHDChain.GetID(), vchSecureMnemonicPassphrase))
|
|
return false;
|
|
|
|
if (!hdChainRet.SetMnemonic(vchSecureMnemonic, vchSecureMnemonicPassphrase, false))
|
|
return false;
|
|
}
|
|
|
|
hdChainRet.SetCrypted(false);
|
|
hdChainRet.Debug(__func__);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::IsHDEnabled() const
|
|
{
|
|
CHDChain hdChainCurrent;
|
|
return GetHDChain(hdChainCurrent);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::CanGetAddresses(bool internal)
|
|
{
|
|
LOCK(cs_wallet);
|
|
// Check if the keypool has keys
|
|
bool keypool_has_keys;
|
|
if (internal && m_storage.CanSupportFeature(FEATURE_HD)) {
|
|
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;
|
|
}
|
|
|
|
|
|
bool LegacyScriptPubKeyMan::HavePrivateKeys() const
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
return !mapKeys.empty() || !mapCryptedKeys.empty();
|
|
}
|
|
|
|
void LegacyScriptPubKeyMan::RewriteDB()
|
|
{
|
|
AssertLockHeld(cs_wallet);
|
|
setInternalKeyPool.clear();
|
|
setExternalKeyPool.clear();
|
|
m_pool_key_to_index.clear();
|
|
// Note: can't top-up keypool here, because wallet is locked.
|
|
// User will be prompted to unlock wallet the next operation
|
|
// that requires a new key.
|
|
}
|
|
|
|
static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, WalletBatch& batch) {
|
|
if (setKeyPool.empty()) {
|
|
// if the keypool is empty, return <NOW>
|
|
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 LegacyScriptPubKeyMan::GetOldestKeyPoolTime()
|
|
{
|
|
LOCK(cs_wallet);
|
|
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, batch);
|
|
|
|
if (IsHDEnabled()) {
|
|
oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, batch), oldestKey);
|
|
}
|
|
return oldestKey;
|
|
}
|
|
|
|
size_t LegacyScriptPubKeyMan::KeypoolCountExternalKeys()
|
|
{
|
|
AssertLockHeld(cs_wallet);
|
|
return setExternalKeyPool.size();
|
|
}
|
|
|
|
size_t LegacyScriptPubKeyMan::KeypoolCountInternalKeys()
|
|
{
|
|
AssertLockHeld(cs_wallet); // setInternalKeyPool
|
|
return setInternalKeyPool.size();
|
|
}
|
|
|
|
unsigned int LegacyScriptPubKeyMan::GetKeyPoolSize() const
|
|
{
|
|
AssertLockHeld(cs_wallet);
|
|
return setInternalKeyPool.size() + setExternalKeyPool.size();
|
|
}
|
|
|
|
int64_t LegacyScriptPubKeyMan::GetTimeFirstKey() const
|
|
{
|
|
AssertLockHeld(cs_wallet);
|
|
return nTimeFirstKey;
|
|
}
|
|
|
|
const CKeyMetadata* LegacyScriptPubKeyMan::GetMetadata(uint160 id) const
|
|
{
|
|
AssertLockHeld(cs_wallet);
|
|
auto it = mapKeyMetadata.find(CKeyID(id));
|
|
if (it != mapKeyMetadata.end()) {
|
|
return &it->second;
|
|
} else {
|
|
auto it2 = m_script_metadata.find(CScriptID(id));
|
|
if (it2 != m_script_metadata.end()) {
|
|
return &it2->second;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Update wallet first key creation time. This should be called whenever keys
|
|
* are added to the wallet, with the oldest key creation time.
|
|
*/
|
|
void LegacyScriptPubKeyMan::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 LegacyScriptPubKeyMan::LoadKey(const CKey& key, const CPubKey &pubkey)
|
|
{
|
|
return AddKeyPubKeyInner(key, pubkey);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
|
|
{
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
|
|
return LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(batch, secret, pubkey);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::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(!m_storage.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(pubkey.GetID());
|
|
if (HaveWatchOnly(script)) {
|
|
RemoveWatchOnly(script);
|
|
}
|
|
script = GetScriptForRawPubKey(pubkey);
|
|
if (HaveWatchOnly(script)) {
|
|
RemoveWatchOnly(script);
|
|
}
|
|
|
|
if (!IsCrypted()) {
|
|
return batch.WriteKey(pubkey,
|
|
secret.GetPrivKey(),
|
|
mapKeyMetadata[pubkey.GetID()]);
|
|
}
|
|
m_storage.UnsetBlankWalletFlag(batch);
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::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(CScriptID(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 LegacyScriptPubKeyMan::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata& meta)
|
|
{
|
|
AssertLockHeld(cs_wallet);
|
|
UpdateTimeFirstKey(meta.nCreateTime);
|
|
mapKeyMetadata[keyID] = meta;
|
|
}
|
|
|
|
void LegacyScriptPubKeyMan::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta)
|
|
{
|
|
AssertLockHeld(cs_wallet);
|
|
UpdateTimeFirstKey(meta.nCreateTime);
|
|
m_script_metadata[script_id] = meta;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey)
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
if (!IsCrypted()) {
|
|
return FillableSigningProvider::AddKeyPubKey(key, pubkey);
|
|
}
|
|
|
|
if (m_storage.IsLocked(true)) {
|
|
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 LegacyScriptPubKeyMan::GetKeyInner(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 LegacyScriptPubKeyMan::GetPubKeyInner(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);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
|
|
{
|
|
return AddCryptedKeyInner(vchPubKey, vchCryptedSecret);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::HaveKeyInner(const CKeyID &address) const
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
if (!IsCrypted()) {
|
|
return FillableSigningProvider::HaveKey(address);
|
|
}
|
|
return mapCryptedKeys.count(address) > 0;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
if (!SetCrypted()) {
|
|
return false;
|
|
}
|
|
|
|
mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret);
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::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(m_storage.GetDatabase()).WriteCryptedKey(vchPubKey,
|
|
vchCryptedSecret,
|
|
mapKeyMetadata[vchPubKey.GetID()]);
|
|
}
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::HaveWatchOnly(const CScript &dest) const
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
return setWatchOnly.count(dest) > 0;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::HaveWatchOnly() const
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
return (!setWatchOnly.empty());
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::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;
|
|
}
|
|
|
|
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 LegacyScriptPubKeyMan::RemoveWatchOnly(const CScript &dest)
|
|
{
|
|
AssertLockHeld(cs_wallet);
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
setWatchOnly.erase(dest);
|
|
CPubKey pubKey;
|
|
if (ExtractPubKey(dest, pubKey)) {
|
|
mapWatchKeys.erase(pubKey.GetID());
|
|
}
|
|
}
|
|
|
|
if (!HaveWatchOnly())
|
|
NotifyWatchonlyChanged(false);
|
|
if (!WalletBatch(m_storage.GetDatabase()).EraseWatchOnly(dest))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::LoadWatchOnly(const CScript &dest)
|
|
{
|
|
return AddWatchOnlyInMem(dest);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::AddWatchOnlyInMem(const CScript &dest)
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
setWatchOnly.insert(dest);
|
|
CPubKey pubKey;
|
|
if (ExtractPubKey(dest, pubKey)) {
|
|
mapWatchKeys[pubKey.GetID()] = pubKey;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::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)) {
|
|
m_storage.UnsetBlankWalletFlag(batch);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time)
|
|
{
|
|
m_script_metadata[CScriptID(dest)].nCreateTime = create_time;
|
|
return AddWatchOnlyWithDB(batch, dest);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest)
|
|
{
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
return AddWatchOnlyWithDB(batch, dest);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest, int64_t nCreateTime)
|
|
{
|
|
m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime;
|
|
return AddWatchOnly(dest);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::SetHDChain(const CHDChain& chain)
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
if (IsCrypted())
|
|
return false;
|
|
|
|
if (chain.IsCrypted())
|
|
return false;
|
|
|
|
hdChain = chain;
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::SetCryptedHDChain(const CHDChain& chain)
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
if (!SetCrypted())
|
|
return false;
|
|
|
|
if (!chain.IsCrypted())
|
|
return false;
|
|
|
|
cryptedHDChain = chain;
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::HaveKey(const CKeyID &address) const
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
if (mapHdPubKeys.count(address) > 0)
|
|
return true;
|
|
return HaveKeyInner(address);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::AddHDPubKey(WalletBatch &batch, const CExtPubKey &extPubKey, bool fInternal)
|
|
{
|
|
AssertLockHeld(cs_wallet);
|
|
|
|
CHDChain hdChainCurrent;
|
|
GetHDChain(hdChainCurrent);
|
|
|
|
CHDPubKey hdPubKey;
|
|
hdPubKey.extPubKey = extPubKey;
|
|
hdPubKey.hdchainID = hdChainCurrent.GetID();
|
|
hdPubKey.nChangeIndex = fInternal ? 1 : 0;
|
|
LoadHDPubKey(hdPubKey);
|
|
|
|
// check if we need to remove from watch-only
|
|
CScript script;
|
|
script = GetScriptForDestination(extPubKey.pubkey.GetID());
|
|
if (HaveWatchOnly(script))
|
|
RemoveWatchOnly(script);
|
|
script = GetScriptForRawPubKey(extPubKey.pubkey);
|
|
if (HaveWatchOnly(script))
|
|
RemoveWatchOnly(script);
|
|
|
|
if (!batch.WriteHDPubKey(hdPubKey, mapKeyMetadata[extPubKey.pubkey.GetID()])) {
|
|
return false;
|
|
}
|
|
m_storage.UnsetBlankWalletFlag(batch);
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::LoadHDPubKey(const CHDPubKey &hdPubKey)
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
mapHdPubKeys[hdPubKey.extPubKey.pubkey.GetID()] = hdPubKey;
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::GetKey(const CKeyID &address, CKey& keyOut) const
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
HDPubKeyMap::const_iterator mi = mapHdPubKeys.find(address);
|
|
if (mi != mapHdPubKeys.end())
|
|
{
|
|
// if the key has been found in mapHdPubKeys, derive it on the fly
|
|
const CHDPubKey &hdPubKey = (*mi).second;
|
|
CHDChain hdChainCurrent;
|
|
if (!GetHDChain(hdChainCurrent))
|
|
throw std::runtime_error(std::string(__func__) + ": GetHDChain failed");
|
|
if (!DecryptHDChain(hdChainCurrent))
|
|
throw std::runtime_error(std::string(__func__) + ": DecryptHDChain failed");
|
|
// make sure seed matches this chain
|
|
if (hdChainCurrent.GetID() != hdChainCurrent.GetSeedHash())
|
|
throw std::runtime_error(std::string(__func__) + ": Wrong HD chain!");
|
|
|
|
CExtKey extkey;
|
|
KeyOriginInfo key_origin_tmp;
|
|
hdChainCurrent.DeriveChildExtKey(hdPubKey.nAccountIndex, hdPubKey.nChangeIndex != 0, hdPubKey.extPubKey.nChild, extkey, key_origin_tmp);
|
|
keyOut = extkey.key;
|
|
|
|
return true;
|
|
}
|
|
else {
|
|
return GetKeyInner(address, keyOut);
|
|
}
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::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 LegacyScriptPubKeyMan::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;
|
|
return WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
HDPubKeyMap::const_iterator mi = mapHdPubKeys.find(address);
|
|
if (mi != mapHdPubKeys.end())
|
|
{
|
|
const CHDPubKey &hdPubKey = (*mi).second;
|
|
vchPubKeyOut = hdPubKey.extPubKey.pubkey;
|
|
return true;
|
|
}
|
|
else
|
|
return GetPubKeyInner(address, vchPubKeyOut);
|
|
}
|
|
|
|
// Writes a keymetadata for a public key. overwrite specifies whether to overwrite an existing metadata for that key if there exists one.
|
|
bool LegacyScriptPubKeyMan::WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite)
|
|
{
|
|
return WalletBatch(m_storage.GetDatabase()).WriteKeyMetadata(meta, pubkey, overwrite);
|
|
}
|
|
|
|
CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, uint32_t nAccountIndex, bool fInternal)
|
|
{
|
|
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
|
|
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
|
|
AssertLockHeld(cs_wallet);
|
|
bool fCompressed = m_storage.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);
|
|
|
|
CPubKey pubkey;
|
|
// use HD key derivation if HD was enabled during wallet creation and a non-null HD chain is present
|
|
if (IsHDEnabled()) {
|
|
DeriveNewChildKey(batch, metadata, secret, nAccountIndex, fInternal);
|
|
pubkey = secret.GetPubKey();
|
|
} else {
|
|
secret.MakeNewKey(fCompressed);
|
|
|
|
// Compressed public keys were introduced in version 0.6.0
|
|
if (fCompressed) {
|
|
m_storage.SetMinVersion(FEATURE_COMPRPUBKEY);
|
|
}
|
|
|
|
pubkey = secret.GetPubKey();
|
|
assert(secret.VerifyPubKey(pubkey));
|
|
|
|
// Create new metadata
|
|
mapKeyMetadata[pubkey.GetID()] = metadata;
|
|
UpdateTimeFirstKey(nCreationTime);
|
|
|
|
if (!AddKeyPubKeyWithDB(batch, secret, pubkey)) {
|
|
throw std::runtime_error(std::string(__func__) + ": AddKey failed");
|
|
}
|
|
}
|
|
return pubkey;
|
|
}
|
|
|
|
void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal)
|
|
{
|
|
CHDChain hdChainTmp;
|
|
if (!GetHDChain(hdChainTmp)) {
|
|
throw std::runtime_error(std::string(__func__) + ": GetHDChain failed");
|
|
}
|
|
|
|
if (!DecryptHDChain(hdChainTmp))
|
|
throw std::runtime_error(std::string(__func__) + ": DecryptHDChain failed");
|
|
// make sure seed matches this chain
|
|
if (hdChainTmp.GetID() != hdChainTmp.GetSeedHash())
|
|
throw std::runtime_error(std::string(__func__) + ": Wrong HD chain!");
|
|
|
|
CHDAccount acc;
|
|
if (!hdChainTmp.GetAccount(nAccountIndex, acc))
|
|
throw std::runtime_error(std::string(__func__) + ": Wrong HD account!");
|
|
|
|
// derive child key at next index, skip keys already known to the wallet
|
|
CExtKey childKey;
|
|
KeyOriginInfo key_origin_tmp;
|
|
uint32_t nChildIndex = fInternal ? acc.nInternalChainCounter : acc.nExternalChainCounter;
|
|
do {
|
|
// NOTE: DeriveChildExtKey updates key_origin, make sure to clear it.
|
|
key_origin_tmp.clear();
|
|
hdChainTmp.DeriveChildExtKey(nAccountIndex, fInternal, nChildIndex, childKey, key_origin_tmp);
|
|
// increment childkey index
|
|
nChildIndex++;
|
|
} while (HaveKey(childKey.key.GetPubKey().GetID()));
|
|
metadata.key_origin = key_origin_tmp;
|
|
assert(!metadata.has_key_origin);
|
|
metadata.has_key_origin = true;
|
|
secretRet = childKey.key;
|
|
|
|
CPubKey pubkey = secretRet.GetPubKey();
|
|
assert(secretRet.VerifyPubKey(pubkey));
|
|
|
|
// store metadata
|
|
mapKeyMetadata[pubkey.GetID()] = metadata;
|
|
UpdateTimeFirstKey(metadata.nCreateTime);
|
|
|
|
// update the chain model in the database
|
|
CHDChain hdChainCurrent;
|
|
GetHDChain(hdChainCurrent);
|
|
|
|
if (fInternal) {
|
|
acc.nInternalChainCounter = nChildIndex;
|
|
}
|
|
else {
|
|
acc.nExternalChainCounter = nChildIndex;
|
|
}
|
|
|
|
if (!hdChainCurrent.SetAccount(nAccountIndex, acc))
|
|
throw std::runtime_error(std::string(__func__) + ": SetAccount failed");
|
|
|
|
if (IsCrypted()) {
|
|
if (!SetCryptedHDChain(batch, hdChainCurrent, false))
|
|
throw std::runtime_error(std::string(__func__) + ": SetCryptedHDChain failed");
|
|
}
|
|
else {
|
|
if (!SetHDChain(batch, hdChainCurrent, false))
|
|
throw std::runtime_error(std::string(__func__) + ": SetHDChain failed");
|
|
}
|
|
|
|
if (!AddHDPubKey(batch, childKey.Neuter(), fInternal))
|
|
throw std::runtime_error(std::string(__func__) + ": AddHDPubKey failed");
|
|
}
|
|
|
|
void LegacyScriptPubKeyMan::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)
|
|
{
|
|
AssertLockHeld(cs_wallet);
|
|
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 LegacyScriptPubKeyMan::CanGenerateKeys()
|
|
{
|
|
LOCK(cs_wallet);
|
|
if (m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) || m_storage.IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Mark old keypool keys as used,
|
|
* and generate all new keys
|
|
*/
|
|
bool LegacyScriptPubKeyMan::NewKeyPool()
|
|
{
|
|
if (m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
|
return false;
|
|
}
|
|
{
|
|
LOCK(cs_wallet);
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
for (const int64_t nIndex : setInternalKeyPool) {
|
|
batch.ErasePool(nIndex);
|
|
}
|
|
setInternalKeyPool.clear();
|
|
for (const int64_t nIndex : setExternalKeyPool) {
|
|
batch.ErasePool(nIndex);
|
|
}
|
|
setExternalKeyPool.clear();
|
|
auto it = coinJoinClientManagers.find(m_wallet.GetName());
|
|
if (it != coinJoinClientManagers.end()) {
|
|
it->second->StopMixing();
|
|
}
|
|
m_wallet.nKeysLeftSinceAutoBackup = 0;
|
|
|
|
m_pool_key_to_index.clear();
|
|
|
|
if (!TopUpKeyPool())
|
|
return false;
|
|
|
|
WalletLogPrintf("LegacyScriptPubKeyMan::NewKeyPool rewrote keypool\n");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::TopUpKeyPool(unsigned int kpSize)
|
|
{
|
|
if (!CanGenerateKeys()) {
|
|
return false;
|
|
}
|
|
{
|
|
LOCK(cs_wallet);
|
|
|
|
if (m_storage.IsLocked(true)) 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 amountExternal = setExternalKeyPool.size();
|
|
int64_t amountInternal = setInternalKeyPool.size();
|
|
int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - amountExternal, (int64_t) 0);
|
|
int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - amountInternal, (int64_t) 0);
|
|
|
|
if (!IsHDEnabled())
|
|
{
|
|
// don't create extra internal keys
|
|
missingInternal = 0;
|
|
} else {
|
|
nTargetSize *= 2;
|
|
}
|
|
bool fInternal = false;
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
for (int64_t i = missingInternal + missingExternal; i--;)
|
|
{
|
|
if (i < missingInternal) {
|
|
fInternal = true;
|
|
}
|
|
|
|
// TODO: implement keypools for all accounts?
|
|
CPubKey pubkey(GenerateNewKey(batch, 0, fInternal));
|
|
AddKeypoolPubkeyWithDB(pubkey, fInternal, batch);
|
|
|
|
if (missingInternal + missingExternal > 0) {
|
|
WalletLogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n",
|
|
missingInternal + missingExternal, missingInternal,
|
|
setInternalKeyPool.size() + setExternalKeyPool.size(), setInternalKeyPool.size());
|
|
}
|
|
|
|
double dProgress = 100.f * m_max_keypool_index / (nTargetSize + 1);
|
|
std::string strMsg = strprintf(_("Loading wallet... (%3.2f %%)").translated, dProgress);
|
|
uiInterface.InitMessage(strMsg);
|
|
}
|
|
}
|
|
NotifyCanGetAddressesChanged();
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
void LegacyScriptPubKeyMan::AddKeypoolPubkey(const CPubKey& pubkey, const bool internal)
|
|
{
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
AddKeypoolPubkeyWithDB(pubkey, internal, batch);
|
|
NotifyCanGetAddressesChanged();
|
|
}
|
|
*/
|
|
|
|
void LegacyScriptPubKeyMan::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 LegacyScriptPubKeyMan::KeepKey(int64_t nIndex)
|
|
{
|
|
// Remove from key pool
|
|
{
|
|
LOCK(cs_wallet);
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
if (batch.ErasePool(nIndex))
|
|
--m_wallet.nKeysLeftSinceAutoBackup;
|
|
if (!nWalletBackups)
|
|
m_wallet.nKeysLeftSinceAutoBackup = 0;
|
|
}
|
|
WalletLogPrintf("keypool keep %d\n", nIndex);
|
|
}
|
|
|
|
void LegacyScriptPubKeyMan::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey)
|
|
{
|
|
// Return to key pool
|
|
{
|
|
LOCK(cs_wallet);
|
|
if (fInternal) {
|
|
setInternalKeyPool.insert(nIndex);
|
|
} else {
|
|
setExternalKeyPool.insert(nIndex);
|
|
}
|
|
m_pool_key_to_index[pubkey.GetID()] = nIndex;
|
|
NotifyCanGetAddressesChanged();
|
|
}
|
|
WalletLogPrintf("keypool return %d\n", nIndex);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, bool internal)
|
|
{
|
|
if (!CanGetAddresses(internal)) {
|
|
return false;
|
|
}
|
|
|
|
CKeyPool keypool;
|
|
{
|
|
LOCK(cs_wallet);
|
|
int64_t nIndex;
|
|
if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) && !m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
|
if (m_storage.IsLocked(true)) return false;
|
|
// TODO: implement keypool for all accouts?
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
result = GenerateNewKey(batch, 0, internal);
|
|
return true;
|
|
}
|
|
KeepKey(nIndex);
|
|
result = keypool.vchPubKey;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal)
|
|
{
|
|
nIndex = -1;
|
|
keypool.vchPubKey = CPubKey();
|
|
{
|
|
LOCK(cs_wallet);
|
|
|
|
TopUpKeyPool();
|
|
|
|
bool fReturningInternal = fRequestedInternal;
|
|
fReturningInternal &= IsHDEnabled() || m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
|
std::set<int64_t>& setKeyPool = fReturningInternal ? setInternalKeyPool : setExternalKeyPool;
|
|
|
|
// Get the oldest key
|
|
if (setKeyPool.empty()) {
|
|
return false;
|
|
}
|
|
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
|
|
nIndex = *setKeyPool.begin();
|
|
setKeyPool.erase(nIndex);
|
|
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 (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 LegacyScriptPubKeyMan::MarkReserveKeysAsUsed(int64_t keypool_id)
|
|
{
|
|
AssertLockHeld(cs_wallet);
|
|
bool internal = setInternalKeyPool.count(keypool_id);
|
|
if (!internal) assert(setExternalKeyPool.count(keypool_id));
|
|
std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : &setExternalKeyPool;
|
|
auto it = setKeyPool->begin();
|
|
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
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());
|
|
}
|
|
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;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::AddCScript(const CScript& redeemScript)
|
|
{
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
return AddCScriptWithDB(batch, redeemScript);
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::AddCScriptWithDB(WalletBatch& batch, const CScript& redeemScript)
|
|
{
|
|
if (!FillableSigningProvider::AddCScript(redeemScript))
|
|
return false;
|
|
if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) {
|
|
m_storage.UnsetBlankWalletFlag(batch);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::ImportScripts(const std::set<CScript> scripts, int64_t timestamp)
|
|
{
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
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 LegacyScriptPubKeyMan::ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp)
|
|
{
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
for (const auto& entry : privkey_map) {
|
|
const CKey& key = entry.second;
|
|
CPubKey pubkey = key.GetPubKey();
|
|
const CKeyID& id = entry.first;
|
|
assert(key.VerifyPubKey(pubkey));
|
|
mapKeyMetadata[id].nCreateTime = timestamp;
|
|
// Skip if we already have the key
|
|
if (HaveKey(id)) {
|
|
WalletLogPrintf("Already have key with pubkey %s, skipping\n", HexStr(pubkey));
|
|
continue;
|
|
}
|
|
// If the private key is not present in the wallet, insert it.
|
|
if (!AddKeyPubKeyWithDB(batch, key, pubkey)) {
|
|
return false;
|
|
}
|
|
UpdateTimeFirstKey(timestamp);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::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(m_storage.GetDatabase());
|
|
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();
|
|
}
|
|
}
|
|
for (const auto& entry : key_origins) {
|
|
AddKeyOrigin(entry.second.first, entry.second.second);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LegacyScriptPubKeyMan::ImportScriptPubKeys(const std::set<CScript>& script_pub_keys, const bool have_solving_data, const int64_t timestamp)
|
|
{
|
|
WalletBatch batch(m_storage.GetDatabase());
|
|
for (const CScript& script : script_pub_keys) {
|
|
if (!have_solving_data || !IsMine(script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated
|
|
if (!AddWatchOnlyWithDB(batch, script, timestamp)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::set<CKeyID> LegacyScriptPubKeyMan::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;
|
|
}
|
|
|
|
|
|
bool LegacyScriptPubKeyMan::GetHDChain(CHDChain& hdChainRet) const
|
|
{
|
|
LOCK(cs_KeyStore);
|
|
if(IsCrypted()) {
|
|
hdChainRet = cryptedHDChain;
|
|
return !cryptedHDChain.IsNull();
|
|
}
|
|
|
|
hdChainRet = hdChain;
|
|
return !hdChain.IsNull();
|
|
}
|
|
|
|
// Temporary CWallet accessors and aliases.
|
|
LegacyScriptPubKeyMan::LegacyScriptPubKeyMan(CWallet& wallet)
|
|
: ScriptPubKeyMan(wallet),
|
|
m_wallet(wallet),
|
|
cs_wallet(wallet.cs_wallet),
|
|
vMasterKey(wallet.vMasterKey),
|
|
fUseCrypto(wallet.fUseCrypto),
|
|
fDecryptionThoroughlyChecked(wallet.fDecryptionThoroughlyChecked) {}
|
|
|
|
bool LegacyScriptPubKeyMan::SetCrypted() { return m_wallet.SetCrypted(); }
|
|
bool LegacyScriptPubKeyMan::IsCrypted() const { return m_wallet.IsCrypted(); }
|
|
void LegacyScriptPubKeyMan::NotifyWatchonlyChanged(bool fHaveWatchOnly) const { return m_wallet.NotifyWatchonlyChanged(fHaveWatchOnly); }
|
|
void LegacyScriptPubKeyMan::NotifyCanGetAddressesChanged() const { return m_wallet.NotifyCanGetAddressesChanged(); }
|
|
template<typename... Params> void LegacyScriptPubKeyMan::WalletLogPrintf(const std::string& fmt, const Params&... parameters) const { return m_wallet.WalletLogPrintf(fmt, parameters...); }
|