mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-22 15:04:44 +01:00
Add PubKeyDestination for P2PK scripts
P2PK scripts are not PKHash destinations, they should have their own type. This also results in no longer showing a p2pkh address for p2pk outputs. However for backwards compatibility, ListCoinst will still do this conversion.
This commit is contained in:
parent
1a98a51c66
commit
07d3bdf4eb
11 changed files with 85 additions and 18 deletions
|
@ -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]));
|
||||||
|
@ -108,6 +109,11 @@ public:
|
||||||
return dest.GetScript();
|
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
|
||||||
{
|
{
|
||||||
return CScript() << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
|
return CScript() << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
|
||||||
|
@ -138,6 +144,19 @@ public:
|
||||||
return CScript() << CScript::EncodeOP_N(id.GetWitnessVersion()) << id.GetWitnessProgram();
|
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)
|
||||||
|
@ -146,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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,19 @@ public:
|
||||||
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>
|
||||||
{
|
{
|
||||||
PKHash() : BaseHash() {}
|
PKHash() : BaseHash() {}
|
||||||
|
@ -103,6 +116,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* A txout script categorized into standard templates.
|
* A txout script categorized into standard templates.
|
||||||
* * CNoDestination: Optionally a script, no corresponding address.
|
* * CNoDestination: Optionally a script, no corresponding address.
|
||||||
|
* * PubKeyDestination: TxoutType::PUBKEY (P2PK), no corresponding address
|
||||||
* * PKHash: TxoutType::PUBKEYHASH destination (P2PKH address)
|
* * PKHash: TxoutType::PUBKEYHASH destination (P2PKH address)
|
||||||
* * ScriptHash: TxoutType::SCRIPTHASH destination (P2SH address)
|
* * ScriptHash: TxoutType::SCRIPTHASH destination (P2SH address)
|
||||||
* * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH address)
|
* * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH address)
|
||||||
|
@ -111,9 +125,9 @@ public:
|
||||||
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W??? 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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,8 +137,8 @@ bool IsValidDestination(const CTxDestination& dest);
|
||||||
* is assigned to addressRet.
|
* is assigned to addressRet.
|
||||||
* For all other scripts. addressRet is assigned as a CNoDestination containing the scriptPubKey.
|
* For all other scripts. addressRet is assigned as a CNoDestination containing the scriptPubKey.
|
||||||
*
|
*
|
||||||
* Returns true for standard destinations - P2PK, P2PKH, P2SH, P2WPKH, P2WSH, and P2TR scripts.
|
* Returns true for standard destinations with addresses - P2PKH, P2SH, P2WPKH, P2WSH, P2TR and P2W??? scripts.
|
||||||
* Returns false for non-standard destinations - P2PK with invalid pubkeys, P2W???, bare multisig, null data, and nonstandard 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);
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)};
|
||||||
},
|
},
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -490,8 +490,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue