Skip to content

Commit 147d64d

Browse files
committed
Merge bitcoin/bitcoin#25858: psbt: Only include PSBT_OUT_TAP_TREE when the output has a script path
9e386af tests: Test that PSBT_OUT_TAP_TREE is included correctly (Andrew Chow) 30ff25c psbt: Only include m_tap_tree if it has scripts (Andrew Chow) 0577d42 psbt: Change m_tap_tree to store just the tuples (Andrew Chow) 22c051c tests: Test that PSBT_OUT_TAP_TREE is combined correctly (Andrew Chow) 7df6e1b psbt: Fix merging of m_tap_tree (Andrew Chow) 0652dc5 [BugFix]: Do not allow deserializing PSBT with empty PSBT_OUT_TAP_TREE (Jeremy Rubin) Pull request description: PSBT_OUT_TAP_TREE should not be included for outputs that do not have such a tree. This should be disallowed during parsing, as well as prior to serialization when the field is populated during updating. Also added some test cases. Alternative to #25856 ACKs for top commit: instagibbs: ACK bitcoin/bitcoin@9e386af darosior: ACK 9e386af Tree-SHA512: ce5c02a69752d176dbd967c1e8d30129b1905c8f186aeeef034576c1de82059271a1ee846bd040f5be4e66bb77ba711dcf14ac1e597c5707d7e7e2293f6cfefb
2 parents 75cbbfa + 9e386af commit 147d64d

File tree

7 files changed

+58
-28
lines changed

7 files changed

+58
-28
lines changed

src/psbt.cpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,14 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
218218
for (const auto& key_pair : hd_keypaths) {
219219
sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair);
220220
}
221-
if (m_tap_tree.has_value() && m_tap_internal_key.IsFullyValid()) {
222-
TaprootSpendData spenddata = m_tap_tree->GetSpendData();
221+
if (!m_tap_tree.empty() && m_tap_internal_key.IsFullyValid()) {
222+
TaprootBuilder builder;
223+
for (const auto& [depth, leaf_ver, script] : m_tap_tree) {
224+
builder.Add((int)depth, script, (int)leaf_ver, /*track=*/true);
225+
}
226+
assert(builder.IsComplete());
227+
builder.Finalize(m_tap_internal_key);
228+
TaprootSpendData spenddata = builder.GetSpendData();
223229

224230
sigdata.tr_spenddata.internal_key = m_tap_internal_key;
225231
sigdata.tr_spenddata.Merge(spenddata);
@@ -243,8 +249,8 @@ void PSBTOutput::FromSignatureData(const SignatureData& sigdata)
243249
if (!sigdata.tr_spenddata.internal_key.IsNull()) {
244250
m_tap_internal_key = sigdata.tr_spenddata.internal_key;
245251
}
246-
if (sigdata.tr_builder.has_value()) {
247-
m_tap_tree = sigdata.tr_builder;
252+
if (sigdata.tr_builder.has_value() && sigdata.tr_builder->HasScripts()) {
253+
m_tap_tree = sigdata.tr_builder->GetTreeTuples();
248254
}
249255
for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) {
250256
m_tap_bip32_paths.emplace(pubkey, leaf_origin);
@@ -265,7 +271,7 @@ void PSBTOutput::Merge(const PSBTOutput& output)
265271
if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script;
266272
if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script;
267273
if (m_tap_internal_key.IsNull() && !output.m_tap_internal_key.IsNull()) m_tap_internal_key = output.m_tap_internal_key;
268-
if (m_tap_tree.has_value() && !output.m_tap_tree.has_value()) m_tap_tree = output.m_tap_tree;
274+
if (m_tap_tree.empty() && !output.m_tap_tree.empty()) m_tap_tree = output.m_tap_tree;
269275
}
270276
bool PSBTInputSigned(const PSBTInput& input)
271277
{

src/psbt.h

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ struct PSBTOutput
713713
CScript witness_script;
714714
std::map<CPubKey, KeyOriginInfo> hd_keypaths;
715715
XOnlyPubKey m_tap_internal_key;
716-
std::optional<TaprootBuilder> m_tap_tree;
716+
std::vector<std::tuple<uint8_t, uint8_t, CScript>> m_tap_tree;
717717
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths;
718718
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
719719
std::set<PSBTProprietary> m_proprietary;
@@ -754,15 +754,11 @@ struct PSBTOutput
754754
}
755755

756756
// Write taproot tree
757-
if (m_tap_tree.has_value()) {
757+
if (!m_tap_tree.empty()) {
758758
SerializeToVector(s, PSBT_OUT_TAP_TREE);
759759
std::vector<unsigned char> value;
760760
CVectorWriter s_value(s.GetType(), s.GetVersion(), value, 0);
761-
const auto& tuples = m_tap_tree->GetTreeTuples();
762-
for (const auto& tuple : tuples) {
763-
uint8_t depth = std::get<0>(tuple);
764-
uint8_t leaf_ver = std::get<1>(tuple);
765-
CScript script = std::get<2>(tuple);
761+
for (const auto& [depth, leaf_ver, script] : m_tap_tree) {
766762
s_value << depth;
767763
s_value << leaf_ver;
768764
s_value << script;
@@ -858,10 +854,13 @@ struct PSBTOutput
858854
} else if (key.size() != 1) {
859855
throw std::ios_base::failure("Output Taproot tree key is more than one byte type");
860856
}
861-
m_tap_tree.emplace();
862857
std::vector<unsigned char> tree_v;
863858
s >> tree_v;
864859
SpanReader s_tree(s.GetType(), s.GetVersion(), tree_v);
860+
if (s_tree.empty()) {
861+
throw std::ios_base::failure("Output Taproot tree must not be empty");
862+
}
863+
TaprootBuilder builder;
865864
while (!s_tree.empty()) {
866865
uint8_t depth;
867866
uint8_t leaf_ver;
@@ -875,9 +874,10 @@ struct PSBTOutput
875874
if ((leaf_ver & ~TAPROOT_LEAF_MASK) != 0) {
876875
throw std::ios_base::failure("Output Taproot tree has a leaf with an invalid leaf version");
877876
}
878-
m_tap_tree->Add((int)depth, script, (int)leaf_ver, true /* track */);
877+
m_tap_tree.push_back(std::make_tuple(depth, leaf_ver, script));
878+
builder.Add((int)depth, script, (int)leaf_ver, true /* track */);
879879
}
880-
if (!m_tap_tree->IsComplete()) {
880+
if (!builder.IsComplete()) {
881881
throw std::ios_base::failure("Output Taproot tree is malformed");
882882
}
883883
break;
@@ -931,11 +931,6 @@ struct PSBTOutput
931931
}
932932
}
933933

934-
// Finalize m_tap_tree so that all of the computed things are computed
935-
if (m_tap_tree.has_value() && m_tap_tree->IsComplete() && m_tap_internal_key.IsFullyValid()) {
936-
m_tap_tree->Finalize(m_tap_internal_key);
937-
}
938-
939934
if (!found_sep) {
940935
throw std::ios_base::failure("Separator is missing at the end of an output map");
941936
}

src/rpc/rawtransaction.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,13 +1241,9 @@ static RPCHelpMan decodepsbt()
12411241
}
12421242

12431243
// Taproot tree
1244-
if (output.m_tap_tree.has_value()) {
1244+
if (!output.m_tap_tree.empty()) {
12451245
UniValue tree(UniValue::VARR);
1246-
const auto& tuples = output.m_tap_tree->GetTreeTuples();
1247-
for (const auto& tuple : tuples) {
1248-
uint8_t depth = std::get<0>(tuple);
1249-
uint8_t leaf_ver = std::get<1>(tuple);
1250-
CScript script = std::get<2>(tuple);
1246+
for (const auto& [depth, leaf_ver, script] : output.m_tap_tree) {
12511247
UniValue elem(UniValue::VOBJ);
12521248
elem.pushKV("depth", (int)depth);
12531249
elem.pushKV("leaf_ver", (int)leaf_ver);

src/script/standard.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ class TaprootBuilder
315315
TaprootSpendData GetSpendData() const;
316316
/** Returns a vector of tuples representing the depth, leaf version, and script */
317317
std::vector<std::tuple<uint8_t, uint8_t, CScript>> GetTreeTuples() const;
318+
/** Returns true if there are any tapscripts */
319+
bool HasScripts() const { return !m_branch.empty(); }
318320
};
319321

320322
/** Given a TaprootSpendData and the output key, reconstruct its script tree.

test/functional/data/rpc_psbt.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
[
4545
"cHNidP8BAKOro2MDAwMDA5ggCAAA////CQAtAAD+///1AAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAD+///1Zm9ybmV3nWx1Y2vmelLmegAAAAAAAAAAAAAAAAAAAAMKAwMDAwMDAwMDAwMACvMBA3FkAAAAAAAAAAAABAAlAAAAAAAAACEWDQ0zDQ0NDQ0NDQ0NCwEAAH9/f39/fwMAAABNo6P///kAAA==",
4646
"Input Taproot BIP32 keypath has an invalid length"
47+
],
48+
[
49+
"cHNidP8BAIkCAAAAAapfm08b0MipBvW9thL06f8rMbeazW7TIa0W9plHj4WoAAAAAAD9////AoCWmAAAAAAAIlEgC+blBlIP1iijRWxqjw1u9H02sqr7y8fno6/LdnvGqPl895x2AAAAACJRIM5wyjSexMbADl4K+AI1/68zyaDlE7guKvrEDUAjwqU1AAAAAAABASsAlDV3AAAAACJRIDfCpO/CIAqc0JKgBhsCfaPGdyroYtmH+4gQK/Mnn72UIRZGOixxmh9h2gqDIecYHcQHRa8w+Sokc//iDiqXz7uMGRkAHzYIzlYAAIABAACAAAAAgAAAAABhAAAAARcgRjoscZofYdoKgyHnGB3EB0WvMPkqJHP/4g4ql8+7jBkAAQUg1YCB33LpmkGemw3ncz7fcnjhL/bBG/PjH8vpgr2L3cUBBgAhB9WAgd9y6ZpBnpsN53M+33J44S/2wRvz4x/L6YK9i93FGQAfNgjOVgAAgAEAAIAAAACAAAAAAGIAAAAAAQUg9jMNus8cd+GAosBk9wn+pNP9wn7A+jy2Vq0cy+siJ8wBBgAhB/YzDbrPHHfhgKLAZPcJ/qTT/cJ+wPo8tlatHMvrIifMGQAfNgjOVgAAgAEAAIAAAACAAQAAAFEBAAAA",
50+
"Output Taproot tree must not be empty"
4751
]
4852
],
4953
"valid" : [

test/functional/rpc_psbt.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
PSBT_IN_SHA256,
2828
PSBT_IN_HASH160,
2929
PSBT_IN_HASH256,
30+
PSBT_OUT_TAP_TREE,
3031
)
3132
from test_framework.test_framework import BitcoinTestFramework
3233
from test_framework.util import (
@@ -779,9 +780,18 @@ def test_psbt_input_keys(psbt_input, keys):
779780
self.generate(self.nodes[0], 1)
780781
self.nodes[0].importdescriptors([{"desc": descsum_create("tr({})".format(privkey)), "timestamp":"now"}])
781782

782-
psbt = watchonly.sendall([wallet.getnewaddress()])["psbt"]
783+
psbt = watchonly.sendall([wallet.getnewaddress(), addr])["psbt"]
783784
psbt = self.nodes[0].walletprocesspsbt(psbt)["psbt"]
784-
self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"])
785+
txid = self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"])
786+
vout = find_vout_for_address(self.nodes[0], txid, addr)
787+
788+
# Make sure tap tree is in psbt
789+
parsed_psbt = PSBT.from_base64(psbt)
790+
assert_greater_than(len(parsed_psbt.o[vout].map[PSBT_OUT_TAP_TREE]), 0)
791+
assert "taproot_tree" in self.nodes[0].decodepsbt(psbt)["outputs"][vout]
792+
parsed_psbt.make_blank()
793+
comb_psbt = self.nodes[0].combinepsbt([psbt, parsed_psbt.to_base64()])
794+
assert_equal(comb_psbt, psbt)
785795

786796
self.log.info("Test that walletprocesspsbt both updates and signs a non-updated psbt containing Taproot inputs")
787797
addr = self.nodes[0].getnewaddress("", "bech32m")
@@ -793,6 +803,14 @@ def test_psbt_input_keys(psbt_input, keys):
793803
self.nodes[0].sendrawtransaction(rawtx)
794804
self.generate(self.nodes[0], 1)
795805

806+
# Make sure tap tree is not in psbt
807+
parsed_psbt = PSBT.from_base64(psbt)
808+
assert PSBT_OUT_TAP_TREE not in parsed_psbt.o[0].map
809+
assert "taproot_tree" not in self.nodes[0].decodepsbt(psbt)["outputs"][0]
810+
parsed_psbt.make_blank()
811+
comb_psbt = self.nodes[0].combinepsbt([psbt, parsed_psbt.to_base64()])
812+
assert_equal(comb_psbt, psbt)
813+
796814
self.log.info("Test decoding PSBT with per-input preimage types")
797815
# note that the decodepsbt RPC doesn't check whether preimages and hashes match
798816
hash_ripemd160, preimage_ripemd160 = random_bytes(20), random_bytes(50)

test/functional/test_framework/psbt.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,15 @@ def serialize(self):
123123
psbt = [x.serialize() for x in [self.g] + self.i + self.o]
124124
return b"psbt\xff" + b"".join(psbt)
125125

126+
def make_blank(self):
127+
"""
128+
Remove all fields except for PSBT_GLOBAL_UNSIGNED_TX
129+
"""
130+
for m in self.i + self.o:
131+
m.map.clear()
132+
133+
self.g = PSBTMap(map={0: self.g.map[0]})
134+
126135
def to_base64(self):
127136
return base64.b64encode(self.serialize()).decode("utf8")
128137

0 commit comments

Comments
 (0)