Skip to content

Commit 07d3bdf

Browse files
committed
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.
1 parent 1a98a51 commit 07d3bdf

File tree

11 files changed

+85
-18
lines changed

11 files changed

+85
-18
lines changed

src/addresstype.cpp

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,12 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
5454
switch (whichType) {
5555
case TxoutType::PUBKEY: {
5656
CPubKey pubKey(vSolutions[0]);
57-
if (!pubKey.IsValid())
58-
return false;
59-
60-
addressRet = PKHash(pubKey);
61-
return true;
57+
if (!pubKey.IsValid()) {
58+
addressRet = CNoDestination(scriptPubKey);
59+
} else {
60+
addressRet = PubKeyDestination(pubKey);
61+
}
62+
return false;
6263
}
6364
case TxoutType::PUBKEYHASH: {
6465
addressRet = PKHash(uint160(vSolutions[0]));
@@ -108,6 +109,11 @@ class CScriptVisitor
108109
return dest.GetScript();
109110
}
110111

112+
CScript operator()(const PubKeyDestination& dest) const
113+
{
114+
return CScript() << ToByteVector(dest.GetPubKey()) << OP_CHECKSIG;
115+
}
116+
111117
CScript operator()(const PKHash& keyID) const
112118
{
113119
return CScript() << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
@@ -138,6 +144,19 @@ class CScriptVisitor
138144
return CScript() << CScript::EncodeOP_N(id.GetWitnessVersion()) << id.GetWitnessProgram();
139145
}
140146
};
147+
148+
class ValidDestinationVisitor
149+
{
150+
public:
151+
bool operator()(const CNoDestination& dest) const { return false; }
152+
bool operator()(const PubKeyDestination& dest) const { return false; }
153+
bool operator()(const PKHash& dest) const { return true; }
154+
bool operator()(const ScriptHash& dest) const { return true; }
155+
bool operator()(const WitnessV0KeyHash& dest) const { return true; }
156+
bool operator()(const WitnessV0ScriptHash& dest) const { return true; }
157+
bool operator()(const WitnessV1Taproot& dest) const { return true; }
158+
bool operator()(const WitnessUnknown& dest) const { return true; }
159+
};
141160
} // namespace
142161

143162
CScript GetScriptForDestination(const CTxDestination& dest)
@@ -146,5 +165,5 @@ CScript GetScriptForDestination(const CTxDestination& dest)
146165
}
147166

148167
bool IsValidDestination(const CTxDestination& dest) {
149-
return dest.index() != 0;
168+
return std::visit(ValidDestinationVisitor(), dest);
150169
}

src/addresstype.h

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ class CNoDestination {
2727
friend bool operator<(const CNoDestination& a, const CNoDestination& b) { return a.GetScript() < b.GetScript(); }
2828
};
2929

30+
struct PubKeyDestination {
31+
private:
32+
CPubKey m_pubkey;
33+
34+
public:
35+
PubKeyDestination(const CPubKey& pubkey) : m_pubkey(pubkey) {}
36+
37+
const CPubKey& GetPubKey() const LIFETIMEBOUND { return m_pubkey; }
38+
39+
friend bool operator==(const PubKeyDestination& a, const PubKeyDestination& b) { return a.GetPubKey() == b.GetPubKey(); }
40+
friend bool operator<(const PubKeyDestination& a, const PubKeyDestination& b) { return a.GetPubKey() < b.GetPubKey(); }
41+
};
42+
3043
struct PKHash : public BaseHash<uint160>
3144
{
3245
PKHash() : BaseHash() {}
@@ -103,6 +116,7 @@ struct WitnessUnknown
103116
/**
104117
* A txout script categorized into standard templates.
105118
* * CNoDestination: Optionally a script, no corresponding address.
119+
* * PubKeyDestination: TxoutType::PUBKEY (P2PK), no corresponding address
106120
* * PKHash: TxoutType::PUBKEYHASH destination (P2PKH address)
107121
* * ScriptHash: TxoutType::SCRIPTHASH destination (P2SH address)
108122
* * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH address)
@@ -111,9 +125,9 @@ struct WitnessUnknown
111125
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W??? address)
112126
* A CTxDestination is the internal data type encoded in a bitcoin address
113127
*/
114-
using CTxDestination = std::variant<CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown>;
128+
using CTxDestination = std::variant<CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown>;
115129

116-
/** Check whether a CTxDestination is a CNoDestination. */
130+
/** Check whether a CTxDestination corresponds to one with an address. */
117131
bool IsValidDestination(const CTxDestination& dest);
118132

119133
/**
@@ -123,8 +137,8 @@ bool IsValidDestination(const CTxDestination& dest);
123137
* is assigned to addressRet.
124138
* For all other scripts. addressRet is assigned as a CNoDestination containing the scriptPubKey.
125139
*
126-
* Returns true for standard destinations - P2PK, P2PKH, P2SH, P2WPKH, P2WSH, and P2TR scripts.
127-
* Returns false for non-standard destinations - P2PK with invalid pubkeys, P2W???, bare multisig, null data, and nonstandard scripts.
140+
* Returns true for standard destinations with addresses - P2PKH, P2SH, P2WPKH, P2WSH, P2TR and P2W??? scripts.
141+
* Returns false for non-standard destinations and those without addresses - P2PK, bare multisig, null data, and nonstandard scripts.
128142
*/
129143
bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet);
130144

src/key_io.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class DestinationEncoder
7777
}
7878

7979
std::string operator()(const CNoDestination& no) const { return {}; }
80+
std::string operator()(const PubKeyDestination& pk) const { return {}; }
8081
};
8182

8283
CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, std::string& error_str, std::vector<int>* error_locations)

src/rpc/output_script.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,11 @@ static RPCHelpMan deriveaddresses()
280280
for (const CScript& script : scripts) {
281281
CTxDestination dest;
282282
if (!ExtractDestination(script, dest)) {
283+
// ExtractDestination no longer returns true for P2PK since it doesn't have a corresponding address
284+
// However combo will output P2PK and should just ignore that script
285+
if (scripts.size() > 1 && std::get_if<PubKeyDestination>(&dest)) {
286+
continue;
287+
}
283288
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor does not have a corresponding address");
284289
}
285290

src/rpc/util.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,11 @@ class DescribeAddressVisitor
253253
return UniValue(UniValue::VOBJ);
254254
}
255255

256+
UniValue operator()(const PubKeyDestination& dest) const
257+
{
258+
return UniValue(UniValue::VOBJ);
259+
}
260+
256261
UniValue operator()(const PKHash& keyID) const
257262
{
258263
UniValue obj(UniValue::VOBJ);

src/test/fuzz/script.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,16 @@ FUZZ_TARGET(script, .init = initialize_script)
149149
const CTxDestination tx_destination_2{ConsumeTxDestination(fuzzed_data_provider)};
150150
const std::string encoded_dest{EncodeDestination(tx_destination_1)};
151151
const UniValue json_dest{DescribeAddress(tx_destination_1)};
152-
Assert(tx_destination_1 == DecodeDestination(encoded_dest));
153152
(void)GetKeyForDestination(/*store=*/{}, tx_destination_1);
154153
const CScript dest{GetScriptForDestination(tx_destination_1)};
155154
const bool valid{IsValidDestination(tx_destination_1)};
156-
Assert(dest.empty() != valid);
157155

158-
Assert(valid == IsValidDestinationString(encoded_dest));
156+
if (!std::get_if<PubKeyDestination>(&tx_destination_1)) {
157+
// Only try to round trip non-pubkey destinations since PubKeyDestination has no encoding
158+
Assert(dest.empty() != valid);
159+
Assert(tx_destination_1 == DecodeDestination(encoded_dest));
160+
Assert(valid == IsValidDestinationString(encoded_dest));
161+
}
159162

160163
(void)(tx_destination_1 < tx_destination_2);
161164
if (tx_destination_1 == tx_destination_2) {

src/test/fuzz/util.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,15 @@ CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) no
172172
[&] {
173173
tx_destination = CNoDestination{};
174174
},
175+
[&] {
176+
bool compressed = fuzzed_data_provider.ConsumeBool();
177+
CPubKey pk{ConstructPubKeyBytes(
178+
fuzzed_data_provider,
179+
ConsumeFixedLengthByteVector(fuzzed_data_provider, (compressed ? CPubKey::COMPRESSED_SIZE : CPubKey::SIZE)),
180+
compressed
181+
)};
182+
tx_destination = PubKeyDestination{pk};
183+
},
175184
[&] {
176185
tx_destination = PKHash{ConsumeUInt160(fuzzed_data_provider)};
177186
},

src/test/script_standard_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ BOOST_AUTO_TEST_CASE(script_standard_ExtractDestination)
203203
// TxoutType::PUBKEY
204204
s.clear();
205205
s << ToByteVector(pubkey) << OP_CHECKSIG;
206-
BOOST_CHECK(ExtractDestination(s, address));
207-
BOOST_CHECK(std::get<PKHash>(address) == PKHash(pubkey));
206+
BOOST_CHECK(!ExtractDestination(s, address));
207+
BOOST_CHECK(std::get<PubKeyDestination>(address) == PubKeyDestination(pubkey));
208208

209209
// TxoutType::PUBKEYHASH
210210
s.clear();

src/wallet/rpc/addresses.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ class DescribeWalletAddressVisitor
427427
explicit DescribeWalletAddressVisitor(const SigningProvider* _provider) : provider(_provider) {}
428428

429429
UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); }
430+
UniValue operator()(const PubKeyDestination& dest) const { return UniValue(UniValue::VOBJ); }
430431

431432
UniValue operator()(const PKHash& pkhash) const
432433
{

src/wallet/spend.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,8 +490,15 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
490490
coins_params.skip_locked = false;
491491
for (const COutput& coin : AvailableCoins(wallet, &coin_control, /*feerate=*/std::nullopt, coins_params).All()) {
492492
CTxDestination address;
493-
if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) &&
494-
ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
493+
if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable))) {
494+
if (!ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
495+
// For backwards compatibility, we convert P2PK output scripts into PKHash destinations
496+
if (auto pk_dest = std::get_if<PubKeyDestination>(&address)) {
497+
address = PKHash(pk_dest->GetPubKey());
498+
} else {
499+
continue;
500+
}
501+
}
495502
result[address].emplace_back(coin);
496503
}
497504
}

0 commit comments

Comments
 (0)