Skip to content

Commit 53313c4

Browse files
committed
Merge bitcoin/bitcoin#28246: wallet: Use CTxDestination in CRecipient instead of just scriptPubKey
ad0c469 wallet: Use CTxDestination in CRecipient rather than scriptPubKey (Andrew Chow) 07d3bdf Add PubKeyDestination for P2PK scripts (Andrew Chow) 1a98a51 Allow CNoDestination to represent a raw script (Andrew Chow) 8dd0670 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 bitcoin/bitcoin@ad0c469 Tree-SHA512: ef3f8f3c7284779d9806c77c85b21caf910a79a1f7e7f1b51abcc0d7e074f14e00abf30f625a13075e41d94dad6202c10ddff462c0ee74c2ca4aab585b145a52
2 parents 737aac8 + ad0c469 commit 53313c4

19 files changed

+160
-90
lines changed

src/addresstype.cpp

Lines changed: 29 additions & 13 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]));
@@ -87,16 +88,13 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
8788
return true;
8889
}
8990
case TxoutType::WITNESS_UNKNOWN: {
90-
WitnessUnknown unk;
91-
unk.version = vSolutions[0][0];
92-
std::copy(vSolutions[1].begin(), vSolutions[1].end(), unk.program);
93-
unk.length = vSolutions[1].size();
94-
addressRet = unk;
91+
addressRet = WitnessUnknown{vSolutions[0][0], vSolutions[1]};
9592
return true;
9693
}
9794
case TxoutType::MULTISIG:
9895
case TxoutType::NULL_DATA:
9996
case TxoutType::NONSTANDARD:
97+
addressRet = CNoDestination(scriptPubKey);
10098
return false;
10199
} // no default case, so the compiler can warn about missing cases
102100
assert(false);
@@ -108,7 +106,12 @@ class CScriptVisitor
108106
public:
109107
CScript operator()(const CNoDestination& dest) const
110108
{
111-
return CScript();
109+
return dest.GetScript();
110+
}
111+
112+
CScript operator()(const PubKeyDestination& dest) const
113+
{
114+
return CScript() << ToByteVector(dest.GetPubKey()) << OP_CHECKSIG;
112115
}
113116

114117
CScript operator()(const PKHash& keyID) const
@@ -138,9 +141,22 @@ class CScriptVisitor
138141

139142
CScript operator()(const WitnessUnknown& id) const
140143
{
141-
return CScript() << CScript::EncodeOP_N(id.version) << std::vector<unsigned char>(id.program, id.program + id.length);
144+
return CScript() << CScript::EncodeOP_N(id.GetWitnessVersion()) << id.GetWitnessProgram();
142145
}
143146
};
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+
};
144160
} // namespace
145161

146162
CScript GetScriptForDestination(const CTxDestination& dest)
@@ -149,5 +165,5 @@ CScript GetScriptForDestination(const CTxDestination& dest)
149165
}
150166

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

src/addresstype.h

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,30 @@
1414
#include <algorithm>
1515

1616
class CNoDestination {
17+
private:
18+
CScript m_script;
19+
20+
public:
21+
CNoDestination() = default;
22+
CNoDestination(const CScript& script) : m_script(script) {}
23+
24+
const CScript& GetScript() const { return m_script; }
25+
26+
friend bool operator==(const CNoDestination& a, const CNoDestination& b) { return a.GetScript() == b.GetScript(); }
27+
friend bool operator<(const CNoDestination& a, const CNoDestination& b) { return a.GetScript() < b.GetScript(); }
28+
};
29+
30+
struct PubKeyDestination {
31+
private:
32+
CPubKey m_pubkey;
33+
1734
public:
18-
friend bool operator==(const CNoDestination &a, const CNoDestination &b) { return true; }
19-
friend bool operator<(const CNoDestination &a, const CNoDestination &b) { return true; }
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(); }
2041
};
2142

2243
struct PKHash : public BaseHash<uint160>
@@ -69,45 +90,55 @@ struct WitnessV1Taproot : public XOnlyPubKey
6990
//! CTxDestination subtype to encode any future Witness version
7091
struct WitnessUnknown
7192
{
72-
unsigned int version;
73-
unsigned int length;
74-
unsigned char program[40];
93+
private:
94+
unsigned int m_version;
95+
std::vector<unsigned char> m_program;
96+
97+
public:
98+
WitnessUnknown(unsigned int version, const std::vector<unsigned char>& program) : m_version(version), m_program(program) {}
99+
WitnessUnknown(int version, const std::vector<unsigned char>& program) : m_version(static_cast<unsigned int>(version)), m_program(program) {}
100+
101+
unsigned int GetWitnessVersion() const { return m_version; }
102+
const std::vector<unsigned char>& GetWitnessProgram() const LIFETIMEBOUND { return m_program; }
75103

76104
friend bool operator==(const WitnessUnknown& w1, const WitnessUnknown& w2) {
77-
if (w1.version != w2.version) return false;
78-
if (w1.length != w2.length) return false;
79-
return std::equal(w1.program, w1.program + w1.length, w2.program);
105+
if (w1.GetWitnessVersion() != w2.GetWitnessVersion()) return false;
106+
return w1.GetWitnessProgram() == w2.GetWitnessProgram();
80107
}
81108

82109
friend bool operator<(const WitnessUnknown& w1, const WitnessUnknown& w2) {
83-
if (w1.version < w2.version) return true;
84-
if (w1.version > w2.version) return false;
85-
if (w1.length < w2.length) return true;
86-
if (w1.length > w2.length) return false;
87-
return std::lexicographical_compare(w1.program, w1.program + w1.length, w2.program, w2.program + w2.length);
110+
if (w1.GetWitnessVersion() < w2.GetWitnessVersion()) return true;
111+
if (w1.GetWitnessVersion() > w2.GetWitnessVersion()) return false;
112+
return w1.GetWitnessProgram() < w2.GetWitnessProgram();
88113
}
89114
};
90115

91116
/**
92-
* A txout script template with a specific destination. It is either:
93-
* * CNoDestination: no destination set
94-
* * PKHash: TxoutType::PUBKEYHASH destination (P2PKH)
95-
* * ScriptHash: TxoutType::SCRIPTHASH destination (P2SH)
96-
* * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH)
97-
* * WitnessV0KeyHash: TxoutType::WITNESS_V0_KEYHASH destination (P2WPKH)
98-
* * WitnessV1Taproot: TxoutType::WITNESS_V1_TAPROOT destination (P2TR)
99-
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W???)
117+
* A txout script categorized into standard templates.
118+
* * CNoDestination: Optionally a script, no corresponding address.
119+
* * PubKeyDestination: TxoutType::PUBKEY (P2PK), no corresponding address
120+
* * PKHash: TxoutType::PUBKEYHASH destination (P2PKH address)
121+
* * ScriptHash: TxoutType::SCRIPTHASH destination (P2SH address)
122+
* * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH address)
123+
* * WitnessV0KeyHash: TxoutType::WITNESS_V0_KEYHASH destination (P2WPKH address)
124+
* * WitnessV1Taproot: TxoutType::WITNESS_V1_TAPROOT destination (P2TR address)
125+
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W??? address)
100126
* A CTxDestination is the internal data type encoded in a bitcoin address
101127
*/
102-
using CTxDestination = std::variant<CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown>;
128+
using CTxDestination = std::variant<CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown>;
103129

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

107133
/**
108-
* Parse a standard scriptPubKey for the destination address. Assigns result to
109-
* the addressRet parameter and returns true if successful. Currently only works for P2PK,
110-
* P2PKH, P2SH, P2WPKH, and P2WSH scripts.
134+
* Parse a scriptPubKey for the destination.
135+
*
136+
* For standard scripts that have addresses (and P2PK as an exception), a corresponding CTxDestination
137+
* is assigned to addressRet.
138+
* For all other scripts. addressRet is assigned as a CNoDestination containing the scriptPubKey.
139+
*
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.
111142
*/
112143
bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet);
113144

src/key_io.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,18 @@ class DestinationEncoder
6767

6868
std::string operator()(const WitnessUnknown& id) const
6969
{
70-
if (id.version < 1 || id.version > 16 || id.length < 2 || id.length > 40) {
70+
const std::vector<unsigned char>& program = id.GetWitnessProgram();
71+
if (id.GetWitnessVersion() < 1 || id.GetWitnessVersion() > 16 || program.size() < 2 || program.size() > 40) {
7172
return {};
7273
}
73-
std::vector<unsigned char> data = {(unsigned char)id.version};
74-
data.reserve(1 + (id.length * 8 + 4) / 5);
75-
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.program, id.program + id.length);
74+
std::vector<unsigned char> data = {(unsigned char)id.GetWitnessVersion()};
75+
data.reserve(1 + (program.size() * 8 + 4) / 5);
76+
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, program.begin(), program.end());
7677
return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data);
7778
}
7879

7980
std::string operator()(const CNoDestination& no) const { return {}; }
81+
std::string operator()(const PubKeyDestination& pk) const { return {}; }
8082
};
8183

8284
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
189191
return CNoDestination();
190192
}
191193

192-
WitnessUnknown unk;
193-
unk.version = version;
194-
std::copy(data.begin(), data.end(), unk.program);
195-
unk.length = data.size();
196-
return unk;
194+
return WitnessUnknown{version, data};
197195
} else {
198196
error_str = strprintf("Invalid padding in Bech32 data section");
199197
return CNoDestination();

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: 7 additions & 2 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);
@@ -303,8 +308,8 @@ class DescribeAddressVisitor
303308
{
304309
UniValue obj(UniValue::VOBJ);
305310
obj.pushKV("iswitness", true);
306-
obj.pushKV("witness_version", (int)id.version);
307-
obj.pushKV("witness_program", HexStr({id.program, id.length}));
311+
obj.pushKV("witness_version", id.GetWitnessVersion());
312+
obj.pushKV("witness_program", HexStr(id.GetWitnessProgram()));
308313
return obj;
309314
}
310315
};

src/test/fuzz/key.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ FUZZ_TARGET(key, .init = initialize_key)
186186
const CTxDestination tx_destination = GetDestinationForKey(pubkey, output_type);
187187
assert(output_type == OutputType::LEGACY);
188188
assert(IsValidDestination(tx_destination));
189-
assert(CTxDestination{PKHash{pubkey}} == tx_destination);
189+
assert(PKHash{pubkey} == *std::get_if<PKHash>(&tx_destination));
190190

191191
const CScript script_for_destination = GetScriptForDestination(tx_destination);
192192
assert(script_for_destination.size() == 25);

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: 13 additions & 8 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
},
@@ -188,15 +197,11 @@ CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) no
188197
tx_destination = WitnessV1Taproot{XOnlyPubKey{ConsumeUInt256(fuzzed_data_provider)}};
189198
},
190199
[&] {
191-
WitnessUnknown witness_unknown{};
192-
witness_unknown.version = fuzzed_data_provider.ConsumeIntegralInRange(2, 16);
193-
std::vector<uint8_t> witness_unknown_program_1{fuzzed_data_provider.ConsumeBytes<uint8_t>(40)};
194-
if (witness_unknown_program_1.size() < 2) {
195-
witness_unknown_program_1 = {0, 0};
200+
std::vector<unsigned char> program{ConsumeRandomLengthByteVector(fuzzed_data_provider, /*max_length=*/40)};
201+
if (program.size() < 2) {
202+
program = {0, 0};
196203
}
197-
witness_unknown.length = witness_unknown_program_1.size();
198-
std::copy(witness_unknown_program_1.begin(), witness_unknown_program_1.end(), witness_unknown.program);
199-
tx_destination = witness_unknown;
204+
tx_destination = WitnessUnknown{fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(2, 16), program};
200205
})};
201206
Assert(call_size == std::variant_size_v<CTxDestination>);
202207
return tx_destination;

src/test/script_standard_tests.cpp

Lines changed: 3 additions & 6 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();
@@ -249,10 +249,7 @@ BOOST_AUTO_TEST_CASE(script_standard_ExtractDestination)
249249
s.clear();
250250
s << OP_1 << ToByteVector(pubkey);
251251
BOOST_CHECK(ExtractDestination(s, address));
252-
WitnessUnknown unk;
253-
unk.length = 33;
254-
unk.version = 1;
255-
std::copy(pubkey.begin(), pubkey.end(), unk.program);
252+
WitnessUnknown unk{1, ToByteVector(pubkey)};
256253
BOOST_CHECK(std::get<WitnessUnknown>(address) == unk);
257254
}
258255

src/util/message.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ MessageVerificationResult MessageVerify(
4747
return MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED;
4848
}
4949

50-
if (!(CTxDestination(PKHash(pubkey)) == destination)) {
50+
if (!(PKHash(pubkey) == *std::get_if<PKHash>(&destination))) {
5151
return MessageVerificationResult::ERR_NOT_SIGNED;
5252
}
5353

0 commit comments

Comments
 (0)