Files
bitcoin/src/wallet/external_signer_scriptpubkeyman.cpp
merge-script c6f225c757 Merge bitcoin/bitcoin#28333: wallet: Construct ScriptPubKeyMans with all data rather than loaded progressively
451fdd26a4 test: wallet: Constructing a DSPKM that can't TopUp() throws. (David Gumberg)
32946e0291 wallet: Setup new autogenerated descriptors on construction (Ava Chow)
e20aaff70f wallet: Construct ExternalSignerSPKM with the new descriptor (Ava Chow)
aa4f7823aa wallet: include keys when constructing DescriptorSPKM during import (Ava Chow)
6538f69135 fuzz: Skip adding descriptor to wallet if it cannot be expanded (Ava Chow)
8be5ee554b test: wallet: Check that loading wallet with both unencrypted and encrypted keys fails. (David Gumberg)
80b0c25992 wallet: Load everything into DescSPKM on construction (Ava Chow)
f713fd1725 refactor: wallet: Don't reuse WALLET_BLANK flag for born-encrypted wallets. (David Gumberg)
cd912c4e10 wallet: Consolidate generation setup callers into one function (Ava Chow)
0301c758ea wallet migration, fuzz: Migrate hd seed once (Ava Chow)

Pull request description:

  Instead of constructing ScriptPubKeyMans with no data, and then loading data as we find it, we should gather everything first and then load it all on construction. If there actually is no data and we want to setup generation, then that should also occur in a constructor rather than afterwards.

  This change is only applied to DescriptorScriptPubKeyMan and ExternalSignerScriptPubKeyMan, and should be done for any ScriptPubKeyMans added in the future. I don't think it's really worth it to do this for LegacyScriptPubKeyMan since it would make loading performance worse (or cause layer violations) and it's (supposed to be) going away soon.

ACKs for top commit:
  polespinasa:
    ACK 451fdd26a4
  davidgumberg:
    re crACK 451fdd26a4
  w0xlt:
    ACK 451fdd26a4

Tree-SHA512: 58a889bf7c77d5da78041907a76a1958207f95a19bec8dc4d86d4e4108d256a729e0949c0973f7d447178f78a7fd4268cda71d358cae4dec5a76dc453b5283af
2026-05-28 21:49:59 +02:00

117 lines
4.6 KiB
C++

// Copyright (c) 2020-present 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 <chainparams.h>
#include <common/args.h>
#include <common/system.h>
#include <external_signer.h>
#include <node/types.h>
#include <wallet/external_signer_scriptpubkeyman.h>
#include <iostream>
#include <key_io.h>
#include <memory>
#include <stdexcept>
#include <string>
#include <univalue.h>
#include <utility>
#include <vector>
using common::PSBTError;
namespace wallet {
std::unique_ptr<ExternalSignerScriptPubKeyMan> ExternalSignerScriptPubKeyMan::LoadFromStorage(WalletStorage& storage, WalletDescriptor& descriptor, int64_t keypool_size, const KeyMap& keys, const CryptedKeyMap& ckeys)
{
return std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(storage, descriptor, keypool_size, keys, ckeys));
}
std::unique_ptr<ExternalSignerScriptPubKeyMan> ExternalSignerScriptPubKeyMan::CreateNew(WalletStorage& storage, WalletBatch& batch, int64_t keypool_size, std::unique_ptr<Descriptor> desc)
{
auto spkm = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(storage, keypool_size));
LOCK(spkm->cs_desc_man);
assert(storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
assert(storage.IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
int64_t creation_time = GetTime();
// Make the descriptor
WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0);
spkm->m_wallet_descriptor = w_desc;
// Store the descriptor
if (!batch.WriteDescriptor(spkm->GetID(), spkm->m_wallet_descriptor)) {
throw std::runtime_error(std::string(__func__) + ": writing descriptor failed");
}
// TopUp
spkm->TopUpWithDB(batch);
storage.UnsetBlankWalletFlag(batch);
return spkm;
}
util::Result<ExternalSigner> ExternalSignerScriptPubKeyMan::GetExternalSigner() {
const std::string command = gArgs.GetArg("-signer", "");
if (command == "") return util::Error{Untranslated("restart bitcoind with -signer=<cmd>")};
std::vector<ExternalSigner> signers;
ExternalSigner::Enumerate(command, signers, Params().GetChainTypeString());
if (signers.empty()) return util::Error{Untranslated("No external signers found")};
// TODO: add fingerprint argument instead of failing in case of multiple signers.
if (signers.size() > 1) return util::Error{Untranslated("More than one external signer found. Please connect only one at a time.")};
return signers[0];
}
util::Result<void> ExternalSignerScriptPubKeyMan::DisplayAddress(const CTxDestination& dest, const ExternalSigner &signer) const
{
// TODO: avoid the need to infer a descriptor from inside a descriptor wallet
const CScript& scriptPubKey = GetScriptForDestination(dest);
auto provider = GetSolvingProvider(scriptPubKey);
auto descriptor = InferDescriptor(scriptPubKey, *provider);
const UniValue& result = signer.DisplayAddress(descriptor->ToString());
const UniValue& error = result.find_value("error");
if (error.isStr()) return util::Error{strprintf(_("Signer returned error: %s"), error.getValStr())};
const UniValue& ret_address = result.find_value("address");
if (!ret_address.isStr()) return util::Error{_("Signer did not echo address")};
if (ret_address.getValStr() != EncodeDestination(dest)) {
return util::Error{strprintf(_("Signer echoed unexpected address %s"), ret_address.getValStr())};
}
return util::Result<void>();
}
// If sign is true, transaction must previously have been filled
std::optional<PSBTError> ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, const common::PSBTFillOptions& options, int* n_signed) const
{
if (!options.sign) {
return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, options, n_signed);
}
// Already complete if every input is now signed
bool complete = true;
for (const auto& input : psbt.inputs) {
complete &= PSBTInputSigned(input);
}
if (complete) return {};
auto signer{GetExternalSigner()};
if (!signer) {
LogWarning("%s", util::ErrorString(signer).original);
return PSBTError::EXTERNAL_SIGNER_NOT_FOUND;
}
std::string failure_reason;
if(!signer->SignTransaction(psbt, failure_reason)) {
LogWarning("Failed to sign: %s\n", failure_reason);
return PSBTError::EXTERNAL_SIGNER_FAILED;
}
if (options.finalize) FinalizePSBT(psbt); // This won't work in a multisig setup
return {};
}
} // namespace wallet