0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-12 11:19:08 -05:00

Merge bitcoin/bitcoin#28246: wallet: Use CTxDestination in CRecipient instead of just scriptPubKey

ad0c469d98 wallet: Use CTxDestination in CRecipient rather than scriptPubKey (Andrew Chow)
07d3bdf4eb Add PubKeyDestination for P2PK scripts (Andrew Chow)
1a98a51c66 Allow CNoDestination to represent a raw script (Andrew Chow)
8dd067088d Make WitnessUnknown members private (Andrew Chow)

Pull request description:

  For silent payments, we want to provide a `SilentPaymentsDestination` to be used as the recipient, which requires `CRecipient` to use something other than just the `scriptPubKey` as we cannot know the output script for a silent payment prior to transaction creation. `CTxDestination` seems like the obvious place to add a `SilentPaymentsDestination` as it is our internal representation of an address.

  In order to still allow paying to arbitrary scriptPubKeys (e.g. for data carrier outputs, or the user hand crafted a raw transaction that they have given to `fundrawtransaction`), `CNoDestination` is changed to contain raw scripts.

  Additionally, P2PK scripts are now interpreted as a new `PubKeyDestination` rather than `PKHash`. This results in some things that would have given an address for P2PK scripts to no longer do so. This is arguably more correct.

  `ExtractDestination`'s behavior is slightly changed for the above. It now returns `true` for those destinations that have addresses, so P2PK scripts now result in `false`. Even though it returns false for `CNoDestination`, the script will now be included in that `CNoDestination`.

  Builds on #28244

ACKs for top commit:
  josibake:
    ACK ad0c469d98

Tree-SHA512: ef3f8f3c7284779d9806c77c85b21caf910a79a1f7e7f1b51abcc0d7e074f14e00abf30f625a13075e41d94dad6202c10ddff462c0ee74c2ca4aab585b145a52
This commit is contained in:
fanquake 2023-09-19 16:30:09 +00:00
commit 53313c49d6
No known key found for this signature in database
GPG key ID: 2EEB9F5CC09526C1
19 changed files with 160 additions and 90 deletions

View file

@ -54,11 +54,12 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
switch (whichType) { switch (whichType) {
case TxoutType::PUBKEY: { case TxoutType::PUBKEY: {
CPubKey pubKey(vSolutions[0]); CPubKey pubKey(vSolutions[0]);
if (!pubKey.IsValid()) if (!pubKey.IsValid()) {
return false; addressRet = CNoDestination(scriptPubKey);
} else {
addressRet = PKHash(pubKey); addressRet = PubKeyDestination(pubKey);
return true; }
return false;
} }
case TxoutType::PUBKEYHASH: { case TxoutType::PUBKEYHASH: {
addressRet = PKHash(uint160(vSolutions[0])); addressRet = PKHash(uint160(vSolutions[0]));
@ -87,16 +88,13 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
return true; return true;
} }
case TxoutType::WITNESS_UNKNOWN: { case TxoutType::WITNESS_UNKNOWN: {
WitnessUnknown unk; addressRet = WitnessUnknown{vSolutions[0][0], vSolutions[1]};
unk.version = vSolutions[0][0];
std::copy(vSolutions[1].begin(), vSolutions[1].end(), unk.program);
unk.length = vSolutions[1].size();
addressRet = unk;
return true; return true;
} }
case TxoutType::MULTISIG: case TxoutType::MULTISIG:
case TxoutType::NULL_DATA: case TxoutType::NULL_DATA:
case TxoutType::NONSTANDARD: case TxoutType::NONSTANDARD:
addressRet = CNoDestination(scriptPubKey);
return false; return false;
} // no default case, so the compiler can warn about missing cases } // no default case, so the compiler can warn about missing cases
assert(false); assert(false);
@ -108,7 +106,12 @@ class CScriptVisitor
public: public:
CScript operator()(const CNoDestination& dest) const CScript operator()(const CNoDestination& dest) const
{ {
return CScript(); return dest.GetScript();
}
CScript operator()(const PubKeyDestination& dest) const
{
return CScript() << ToByteVector(dest.GetPubKey()) << OP_CHECKSIG;
} }
CScript operator()(const PKHash& keyID) const CScript operator()(const PKHash& keyID) const
@ -138,9 +141,22 @@ public:
CScript operator()(const WitnessUnknown& id) const CScript operator()(const WitnessUnknown& id) const
{ {
return CScript() << CScript::EncodeOP_N(id.version) << std::vector<unsigned char>(id.program, id.program + id.length); return CScript() << CScript::EncodeOP_N(id.GetWitnessVersion()) << id.GetWitnessProgram();
} }
}; };
class ValidDestinationVisitor
{
public:
bool operator()(const CNoDestination& dest) const { return false; }
bool operator()(const PubKeyDestination& dest) const { return false; }
bool operator()(const PKHash& dest) const { return true; }
bool operator()(const ScriptHash& dest) const { return true; }
bool operator()(const WitnessV0KeyHash& dest) const { return true; }
bool operator()(const WitnessV0ScriptHash& dest) const { return true; }
bool operator()(const WitnessV1Taproot& dest) const { return true; }
bool operator()(const WitnessUnknown& dest) const { return true; }
};
} // namespace } // namespace
CScript GetScriptForDestination(const CTxDestination& dest) CScript GetScriptForDestination(const CTxDestination& dest)
@ -149,5 +165,5 @@ CScript GetScriptForDestination(const CTxDestination& dest)
} }
bool IsValidDestination(const CTxDestination& dest) { bool IsValidDestination(const CTxDestination& dest) {
return dest.index() != 0; return std::visit(ValidDestinationVisitor(), dest);
} }

View file

@ -14,9 +14,30 @@
#include <algorithm> #include <algorithm>
class CNoDestination { class CNoDestination {
private:
CScript m_script;
public: public:
friend bool operator==(const CNoDestination &a, const CNoDestination &b) { return true; } CNoDestination() = default;
friend bool operator<(const CNoDestination &a, const CNoDestination &b) { return true; } CNoDestination(const CScript& script) : m_script(script) {}
const CScript& GetScript() const { return m_script; }
friend bool operator==(const CNoDestination& a, const CNoDestination& b) { return a.GetScript() == b.GetScript(); }
friend bool operator<(const CNoDestination& a, const CNoDestination& b) { return a.GetScript() < b.GetScript(); }
};
struct PubKeyDestination {
private:
CPubKey m_pubkey;
public:
PubKeyDestination(const CPubKey& pubkey) : m_pubkey(pubkey) {}
const CPubKey& GetPubKey() const LIFETIMEBOUND { return m_pubkey; }
friend bool operator==(const PubKeyDestination& a, const PubKeyDestination& b) { return a.GetPubKey() == b.GetPubKey(); }
friend bool operator<(const PubKeyDestination& a, const PubKeyDestination& b) { return a.GetPubKey() < b.GetPubKey(); }
}; };
struct PKHash : public BaseHash<uint160> struct PKHash : public BaseHash<uint160>
@ -69,45 +90,55 @@ struct WitnessV1Taproot : public XOnlyPubKey
//! CTxDestination subtype to encode any future Witness version //! CTxDestination subtype to encode any future Witness version
struct WitnessUnknown struct WitnessUnknown
{ {
unsigned int version; private:
unsigned int length; unsigned int m_version;
unsigned char program[40]; std::vector<unsigned char> m_program;
public:
WitnessUnknown(unsigned int version, const std::vector<unsigned char>& program) : m_version(version), m_program(program) {}
WitnessUnknown(int version, const std::vector<unsigned char>& program) : m_version(static_cast<unsigned int>(version)), m_program(program) {}
unsigned int GetWitnessVersion() const { return m_version; }
const std::vector<unsigned char>& GetWitnessProgram() const LIFETIMEBOUND { return m_program; }
friend bool operator==(const WitnessUnknown& w1, const WitnessUnknown& w2) { friend bool operator==(const WitnessUnknown& w1, const WitnessUnknown& w2) {
if (w1.version != w2.version) return false; if (w1.GetWitnessVersion() != w2.GetWitnessVersion()) return false;
if (w1.length != w2.length) return false; return w1.GetWitnessProgram() == w2.GetWitnessProgram();
return std::equal(w1.program, w1.program + w1.length, w2.program);
} }
friend bool operator<(const WitnessUnknown& w1, const WitnessUnknown& w2) { friend bool operator<(const WitnessUnknown& w1, const WitnessUnknown& w2) {
if (w1.version < w2.version) return true; if (w1.GetWitnessVersion() < w2.GetWitnessVersion()) return true;
if (w1.version > w2.version) return false; if (w1.GetWitnessVersion() > w2.GetWitnessVersion()) return false;
if (w1.length < w2.length) return true; return w1.GetWitnessProgram() < w2.GetWitnessProgram();
if (w1.length > w2.length) return false;
return std::lexicographical_compare(w1.program, w1.program + w1.length, w2.program, w2.program + w2.length);
} }
}; };
/** /**
* A txout script template with a specific destination. It is either: * A txout script categorized into standard templates.
* * CNoDestination: no destination set * * CNoDestination: Optionally a script, no corresponding address.
* * PKHash: TxoutType::PUBKEYHASH destination (P2PKH) * * PubKeyDestination: TxoutType::PUBKEY (P2PK), no corresponding address
* * ScriptHash: TxoutType::SCRIPTHASH destination (P2SH) * * PKHash: TxoutType::PUBKEYHASH destination (P2PKH address)
* * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH) * * ScriptHash: TxoutType::SCRIPTHASH destination (P2SH address)
* * WitnessV0KeyHash: TxoutType::WITNESS_V0_KEYHASH destination (P2WPKH) * * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH address)
* * WitnessV1Taproot: TxoutType::WITNESS_V1_TAPROOT destination (P2TR) * * WitnessV0KeyHash: TxoutType::WITNESS_V0_KEYHASH destination (P2WPKH address)
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W???) * * WitnessV1Taproot: TxoutType::WITNESS_V1_TAPROOT destination (P2TR address)
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W??? address)
* A CTxDestination is the internal data type encoded in a bitcoin address * A CTxDestination is the internal data type encoded in a bitcoin address
*/ */
using CTxDestination = std::variant<CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown>; using CTxDestination = std::variant<CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown>;
/** Check whether a CTxDestination is a CNoDestination. */ /** Check whether a CTxDestination corresponds to one with an address. */
bool IsValidDestination(const CTxDestination& dest); bool IsValidDestination(const CTxDestination& dest);
/** /**
* Parse a standard scriptPubKey for the destination address. Assigns result to * Parse a scriptPubKey for the destination.
* the addressRet parameter and returns true if successful. Currently only works for P2PK, *
* P2PKH, P2SH, P2WPKH, and P2WSH scripts. * For standard scripts that have addresses (and P2PK as an exception), a corresponding CTxDestination
* is assigned to addressRet.
* For all other scripts. addressRet is assigned as a CNoDestination containing the scriptPubKey.
*
* Returns true for standard destinations with addresses - P2PKH, P2SH, P2WPKH, P2WSH, P2TR and P2W??? scripts.
* Returns false for non-standard destinations and those without addresses - P2PK, bare multisig, null data, and nonstandard scripts.
*/ */
bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet); bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet);

View file

@ -67,16 +67,18 @@ public:
std::string operator()(const WitnessUnknown& id) const std::string operator()(const WitnessUnknown& id) const
{ {
if (id.version < 1 || id.version > 16 || id.length < 2 || id.length > 40) { const std::vector<unsigned char>& program = id.GetWitnessProgram();
if (id.GetWitnessVersion() < 1 || id.GetWitnessVersion() > 16 || program.size() < 2 || program.size() > 40) {
return {}; return {};
} }
std::vector<unsigned char> data = {(unsigned char)id.version}; std::vector<unsigned char> data = {(unsigned char)id.GetWitnessVersion()};
data.reserve(1 + (id.length * 8 + 4) / 5); data.reserve(1 + (program.size() * 8 + 4) / 5);
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.program, id.program + id.length); ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, program.begin(), program.end());
return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data); return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data);
} }
std::string operator()(const CNoDestination& no) const { return {}; } std::string operator()(const CNoDestination& no) const { return {}; }
std::string operator()(const PubKeyDestination& pk) const { return {}; }
}; };
CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, std::string& error_str, std::vector<int>* error_locations) CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, std::string& error_str, std::vector<int>* error_locations)
@ -189,11 +191,7 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
return CNoDestination(); return CNoDestination();
} }
WitnessUnknown unk; return WitnessUnknown{version, data};
unk.version = version;
std::copy(data.begin(), data.end(), unk.program);
unk.length = data.size();
return unk;
} else { } else {
error_str = strprintf("Invalid padding in Bech32 data section"); error_str = strprintf("Invalid padding in Bech32 data section");
return CNoDestination(); return CNoDestination();

View file

@ -280,6 +280,11 @@ static RPCHelpMan deriveaddresses()
for (const CScript& script : scripts) { for (const CScript& script : scripts) {
CTxDestination dest; CTxDestination dest;
if (!ExtractDestination(script, dest)) { if (!ExtractDestination(script, dest)) {
// ExtractDestination no longer returns true for P2PK since it doesn't have a corresponding address
// However combo will output P2PK and should just ignore that script
if (scripts.size() > 1 && std::get_if<PubKeyDestination>(&dest)) {
continue;
}
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor does not have a corresponding address"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor does not have a corresponding address");
} }

View file

@ -253,6 +253,11 @@ public:
return UniValue(UniValue::VOBJ); return UniValue(UniValue::VOBJ);
} }
UniValue operator()(const PubKeyDestination& dest) const
{
return UniValue(UniValue::VOBJ);
}
UniValue operator()(const PKHash& keyID) const UniValue operator()(const PKHash& keyID) const
{ {
UniValue obj(UniValue::VOBJ); UniValue obj(UniValue::VOBJ);
@ -303,8 +308,8 @@ public:
{ {
UniValue obj(UniValue::VOBJ); UniValue obj(UniValue::VOBJ);
obj.pushKV("iswitness", true); obj.pushKV("iswitness", true);
obj.pushKV("witness_version", (int)id.version); obj.pushKV("witness_version", id.GetWitnessVersion());
obj.pushKV("witness_program", HexStr({id.program, id.length})); obj.pushKV("witness_program", HexStr(id.GetWitnessProgram()));
return obj; return obj;
} }
}; };

View file

@ -186,7 +186,7 @@ FUZZ_TARGET(key, .init = initialize_key)
const CTxDestination tx_destination = GetDestinationForKey(pubkey, output_type); const CTxDestination tx_destination = GetDestinationForKey(pubkey, output_type);
assert(output_type == OutputType::LEGACY); assert(output_type == OutputType::LEGACY);
assert(IsValidDestination(tx_destination)); assert(IsValidDestination(tx_destination));
assert(CTxDestination{PKHash{pubkey}} == tx_destination); assert(PKHash{pubkey} == *std::get_if<PKHash>(&tx_destination));
const CScript script_for_destination = GetScriptForDestination(tx_destination); const CScript script_for_destination = GetScriptForDestination(tx_destination);
assert(script_for_destination.size() == 25); assert(script_for_destination.size() == 25);

View file

@ -149,13 +149,16 @@ FUZZ_TARGET(script, .init = initialize_script)
const CTxDestination tx_destination_2{ConsumeTxDestination(fuzzed_data_provider)}; const CTxDestination tx_destination_2{ConsumeTxDestination(fuzzed_data_provider)};
const std::string encoded_dest{EncodeDestination(tx_destination_1)}; const std::string encoded_dest{EncodeDestination(tx_destination_1)};
const UniValue json_dest{DescribeAddress(tx_destination_1)}; const UniValue json_dest{DescribeAddress(tx_destination_1)};
Assert(tx_destination_1 == DecodeDestination(encoded_dest));
(void)GetKeyForDestination(/*store=*/{}, tx_destination_1); (void)GetKeyForDestination(/*store=*/{}, tx_destination_1);
const CScript dest{GetScriptForDestination(tx_destination_1)}; const CScript dest{GetScriptForDestination(tx_destination_1)};
const bool valid{IsValidDestination(tx_destination_1)}; const bool valid{IsValidDestination(tx_destination_1)};
Assert(dest.empty() != valid);
Assert(valid == IsValidDestinationString(encoded_dest)); if (!std::get_if<PubKeyDestination>(&tx_destination_1)) {
// Only try to round trip non-pubkey destinations since PubKeyDestination has no encoding
Assert(dest.empty() != valid);
Assert(tx_destination_1 == DecodeDestination(encoded_dest));
Assert(valid == IsValidDestinationString(encoded_dest));
}
(void)(tx_destination_1 < tx_destination_2); (void)(tx_destination_1 < tx_destination_2);
if (tx_destination_1 == tx_destination_2) { if (tx_destination_1 == tx_destination_2) {

View file

@ -172,6 +172,15 @@ CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) no
[&] { [&] {
tx_destination = CNoDestination{}; tx_destination = CNoDestination{};
}, },
[&] {
bool compressed = fuzzed_data_provider.ConsumeBool();
CPubKey pk{ConstructPubKeyBytes(
fuzzed_data_provider,
ConsumeFixedLengthByteVector(fuzzed_data_provider, (compressed ? CPubKey::COMPRESSED_SIZE : CPubKey::SIZE)),
compressed
)};
tx_destination = PubKeyDestination{pk};
},
[&] { [&] {
tx_destination = PKHash{ConsumeUInt160(fuzzed_data_provider)}; tx_destination = PKHash{ConsumeUInt160(fuzzed_data_provider)};
}, },
@ -188,15 +197,11 @@ CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) no
tx_destination = WitnessV1Taproot{XOnlyPubKey{ConsumeUInt256(fuzzed_data_provider)}}; tx_destination = WitnessV1Taproot{XOnlyPubKey{ConsumeUInt256(fuzzed_data_provider)}};
}, },
[&] { [&] {
WitnessUnknown witness_unknown{}; std::vector<unsigned char> program{ConsumeRandomLengthByteVector(fuzzed_data_provider, /*max_length=*/40)};
witness_unknown.version = fuzzed_data_provider.ConsumeIntegralInRange(2, 16); if (program.size() < 2) {
std::vector<uint8_t> witness_unknown_program_1{fuzzed_data_provider.ConsumeBytes<uint8_t>(40)}; program = {0, 0};
if (witness_unknown_program_1.size() < 2) {
witness_unknown_program_1 = {0, 0};
} }
witness_unknown.length = witness_unknown_program_1.size(); tx_destination = WitnessUnknown{fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(2, 16), program};
std::copy(witness_unknown_program_1.begin(), witness_unknown_program_1.end(), witness_unknown.program);
tx_destination = witness_unknown;
})}; })};
Assert(call_size == std::variant_size_v<CTxDestination>); Assert(call_size == std::variant_size_v<CTxDestination>);
return tx_destination; return tx_destination;

View file

@ -203,8 +203,8 @@ BOOST_AUTO_TEST_CASE(script_standard_ExtractDestination)
// TxoutType::PUBKEY // TxoutType::PUBKEY
s.clear(); s.clear();
s << ToByteVector(pubkey) << OP_CHECKSIG; s << ToByteVector(pubkey) << OP_CHECKSIG;
BOOST_CHECK(ExtractDestination(s, address)); BOOST_CHECK(!ExtractDestination(s, address));
BOOST_CHECK(std::get<PKHash>(address) == PKHash(pubkey)); BOOST_CHECK(std::get<PubKeyDestination>(address) == PubKeyDestination(pubkey));
// TxoutType::PUBKEYHASH // TxoutType::PUBKEYHASH
s.clear(); s.clear();
@ -249,10 +249,7 @@ BOOST_AUTO_TEST_CASE(script_standard_ExtractDestination)
s.clear(); s.clear();
s << OP_1 << ToByteVector(pubkey); s << OP_1 << ToByteVector(pubkey);
BOOST_CHECK(ExtractDestination(s, address)); BOOST_CHECK(ExtractDestination(s, address));
WitnessUnknown unk; WitnessUnknown unk{1, ToByteVector(pubkey)};
unk.length = 33;
unk.version = 1;
std::copy(pubkey.begin(), pubkey.end(), unk.program);
BOOST_CHECK(std::get<WitnessUnknown>(address) == unk); BOOST_CHECK(std::get<WitnessUnknown>(address) == unk);
} }

View file

@ -47,7 +47,7 @@ MessageVerificationResult MessageVerify(
return MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED; return MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED;
} }
if (!(CTxDestination(PKHash(pubkey)) == destination)) { if (!(PKHash(pubkey) == *std::get_if<PKHash>(&destination))) {
return MessageVerificationResult::ERR_NOT_SIGNED; return MessageVerificationResult::ERR_NOT_SIGNED;
} }

View file

@ -257,12 +257,12 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
const auto& txouts = outputs.empty() ? wtx.tx->vout : outputs; const auto& txouts = outputs.empty() ? wtx.tx->vout : outputs;
for (size_t i = 0; i < txouts.size(); ++i) { for (size_t i = 0; i < txouts.size(); ++i) {
const CTxOut& output = txouts.at(i); const CTxOut& output = txouts.at(i);
CTxDestination dest;
ExtractDestination(output.scriptPubKey, dest);
if (reduce_output.has_value() ? reduce_output.value() == i : OutputIsChange(wallet, output)) { if (reduce_output.has_value() ? reduce_output.value() == i : OutputIsChange(wallet, output)) {
CTxDestination change_dest; new_coin_control.destChange = dest;
ExtractDestination(output.scriptPubKey, change_dest);
new_coin_control.destChange = change_dest;
} else { } else {
CRecipient recipient = {output.scriptPubKey, output.nValue, false}; CRecipient recipient = {dest, output.nValue, false};
recipients.push_back(recipient); recipients.push_back(recipient);
} }
new_outputs_value += output.nValue; new_outputs_value += output.nValue;
@ -278,7 +278,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
// Add change as recipient with SFFO flag enabled, so fees are deduced from it. // Add change as recipient with SFFO flag enabled, so fees are deduced from it.
// If the output differs from the original tx output (because the user customized it) a new change output will be created. // If the output differs from the original tx output (because the user customized it) a new change output will be created.
recipients.emplace_back(CRecipient{GetScriptForDestination(new_coin_control.destChange), new_outputs_value, /*fSubtractFeeFromAmount=*/true}); recipients.emplace_back(CRecipient{new_coin_control.destChange, new_outputs_value, /*fSubtractFeeFromAmount=*/true});
new_coin_control.destChange = CNoDestination(); new_coin_control.destChange = CNoDestination();
} }

View file

@ -427,6 +427,7 @@ public:
explicit DescribeWalletAddressVisitor(const SigningProvider* _provider) : provider(_provider) {} explicit DescribeWalletAddressVisitor(const SigningProvider* _provider) : provider(_provider) {}
UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); } UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); }
UniValue operator()(const PubKeyDestination& dest) const { return UniValue(UniValue::VOBJ); }
UniValue operator()(const PKHash& pkhash) const UniValue operator()(const PKHash& pkhash) const
{ {

View file

@ -39,7 +39,6 @@ static void ParseRecipients(const UniValue& address_amounts, const UniValue& sub
} }
destinations.insert(dest); destinations.insert(dest);
CScript script_pub_key = GetScriptForDestination(dest);
CAmount amount = AmountFromValue(address_amounts[i++]); CAmount amount = AmountFromValue(address_amounts[i++]);
bool subtract_fee = false; bool subtract_fee = false;
@ -50,7 +49,7 @@ static void ParseRecipients(const UniValue& address_amounts, const UniValue& sub
} }
} }
CRecipient recipient = {script_pub_key, amount, subtract_fee}; CRecipient recipient = {dest, amount, subtract_fee};
recipients.push_back(recipient); recipients.push_back(recipient);
} }
} }

View file

@ -505,8 +505,15 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
coins_params.skip_locked = false; coins_params.skip_locked = false;
for (const COutput& coin : AvailableCoins(wallet, &coin_control, /*feerate=*/std::nullopt, coins_params).All()) { for (const COutput& coin : AvailableCoins(wallet, &coin_control, /*feerate=*/std::nullopt, coins_params).All()) {
CTxDestination address; CTxDestination address;
if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) && if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable))) {
ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) { if (!ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
// For backwards compatibility, we convert P2PK output scripts into PKHash destinations
if (auto pk_dest = std::get_if<PubKeyDestination>(&address)) {
address = PKHash(pk_dest->GetPubKey());
} else {
continue;
}
}
result[address].emplace_back(coin); result[address].emplace_back(coin);
} }
} }
@ -1071,7 +1078,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
// vouts to the payees // vouts to the payees
for (const auto& recipient : vecSend) for (const auto& recipient : vecSend)
{ {
CTxOut txout(recipient.nAmount, recipient.scriptPubKey); CTxOut txout(recipient.nAmount, GetScriptForDestination(recipient.dest));
// Include the fee cost for outputs. // Include the fee cost for outputs.
coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
@ -1319,7 +1326,9 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet,
// Turn the txout set into a CRecipient vector. // Turn the txout set into a CRecipient vector.
for (size_t idx = 0; idx < tx.vout.size(); idx++) { for (size_t idx = 0; idx < tx.vout.size(); idx++) {
const CTxOut& txOut = tx.vout[idx]; const CTxOut& txOut = tx.vout[idx];
CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, setSubtractFeeFromOutputs.count(idx) == 1}; CTxDestination dest;
ExtractDestination(txOut.scriptPubKey, dest);
CRecipient recipient = {dest, txOut.nValue, setSubtractFeeFromOutputs.count(idx) == 1};
vecSend.push_back(recipient); vecSend.push_back(recipient);
} }

View file

@ -27,7 +27,7 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
// leftover input amount which would have been change to the recipient // leftover input amount which would have been change to the recipient
// instead of the miner. // instead of the miner.
auto check_tx = [&wallet](CAmount leftover_input_amount) { auto check_tx = [&wallet](CAmount leftover_input_amount) {
CRecipient recipient{GetScriptForRawPubKey({}), 50 * COIN - leftover_input_amount, /*subtract_fee=*/true}; CRecipient recipient{PubKeyDestination({}), 50 * COIN - leftover_input_amount, /*subtract_fee=*/true};
constexpr int RANDOM_CHANGE_POSITION = -1; constexpr int RANDOM_CHANGE_POSITION = -1;
CCoinControl coin_control; CCoinControl coin_control;
coin_control.m_feerate.emplace(10000); coin_control.m_feerate.emplace(10000);

View file

@ -645,7 +645,7 @@ void TestCoinsResult(ListCoinsTest& context, OutputType out_type, CAmount amount
{ {
LOCK(context.wallet->cs_wallet); LOCK(context.wallet->cs_wallet);
util::Result<CTxDestination> dest = Assert(context.wallet->GetNewDestination(out_type, "")); util::Result<CTxDestination> dest = Assert(context.wallet->GetNewDestination(out_type, ""));
CWalletTx& wtx = context.AddTx(CRecipient{{GetScriptForDestination(*dest)}, amount, /*fSubtractFeeFromAmount=*/true}); CWalletTx& wtx = context.AddTx(CRecipient{*dest, amount, /*fSubtractFeeFromAmount=*/true});
CoinFilterParams filter; CoinFilterParams filter;
filter.skip_locked = false; filter.skip_locked = false;
CoinsResult available_coins = AvailableCoins(*context.wallet, nullptr, std::nullopt, filter); CoinsResult available_coins = AvailableCoins(*context.wallet, nullptr, std::nullopt, filter);

View file

@ -2213,15 +2213,13 @@ OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& chang
bool any_pkh{false}; bool any_pkh{false};
for (const auto& recipient : vecSend) { for (const auto& recipient : vecSend) {
std::vector<std::vector<uint8_t>> dummy; if (std::get_if<WitnessV1Taproot>(&recipient.dest)) {
const TxoutType type{Solver(recipient.scriptPubKey, dummy)};
if (type == TxoutType::WITNESS_V1_TAPROOT) {
any_tr = true; any_tr = true;
} else if (type == TxoutType::WITNESS_V0_KEYHASH) { } else if (std::get_if<WitnessV0KeyHash>(&recipient.dest)) {
any_wpkh = true; any_wpkh = true;
} else if (type == TxoutType::SCRIPTHASH) { } else if (std::get_if<ScriptHash>(&recipient.dest)) {
any_sh = true; any_sh = true;
} else if (type == TxoutType::PUBKEYHASH) { } else if (std::get_if<PKHash>(&recipient.dest)) {
any_pkh = true; any_pkh = true;
} }
} }

View file

@ -288,7 +288,7 @@ inline std::optional<AddressPurpose> PurposeFromString(std::string_view s)
struct CRecipient struct CRecipient
{ {
CScript scriptPubKey; CTxDestination dest;
CAmount nAmount; CAmount nAmount;
bool fSubtractFeeFromAmount; bool fSubtractFeeFromAmount;
}; };

View file

@ -42,7 +42,10 @@ class DeriveaddressesTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "Range should be greater or equal than 0", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), [-1, 0]) assert_raises_rpc_error(-8, "Range should be greater or equal than 0", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), [-1, 0])
combo_descriptor = descsum_create("combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)") combo_descriptor = descsum_create("combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)")
assert_equal(self.nodes[0].deriveaddresses(combo_descriptor), ["mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", "mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", address, "2NDvEwGfpEqJWfybzpKPHF2XH3jwoQV3D7x"]) assert_equal(self.nodes[0].deriveaddresses(combo_descriptor), ["mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", address, "2NDvEwGfpEqJWfybzpKPHF2XH3jwoQV3D7x"])
# P2PK does not have a valid address
assert_raises_rpc_error(-5, "Descriptor does not have a corresponding address", self.nodes[0].deriveaddresses, descsum_create("pk(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK)"))
# Before #26275, bitcoind would crash when deriveaddresses was # Before #26275, bitcoind would crash when deriveaddresses was
# called with derivation index 2147483647, which is the maximum # called with derivation index 2147483647, which is the maximum