Skip to content

Commit 58da161

Browse files
committed
Merge bitcoin/bitcoin#25877: refactor: Do not use CScript for tapleaf scripts until the tapleaf version is known
dee8943 Abstract out ComputeTapbranchHash (Russell O'Connor) 8e3fc99 Do not use CScript for tapleaf scripts until the tapleaf version is known (Russell O'Connor) Pull request description: While BIP-341 calls the contents of tapleaf a "script", only in the case that the tapleaf version is `0xc0` is this script known to be a tapscript. Otherwise the tapleaf "script" is simply an uninterpreted string of bytes. This PR corrects the issue where the type `CScript` is used prior to the tapleaf version being known to be a tapscript. This prevents `CScript` methods from erroneously being called on non-tapscript data. A second commit abstracts out the TapBranch hash computation in the same manner that the TapLeaf computation is already abstracted. These two abstractions ensure that the TapLeaf and TapBranch tagged hashes are always constructed properly. ACKs for top commit: ajtowns: ACK dee8943 instagibbs: ACK dee8943 achow101: ACK dee8943 sipa: ACK dee8943 aureleoules: reACK dee8943 - I verified that there is no behavior change. Tree-SHA512: 4a1d37f3e9a1890e7f5eadcf65562688cc451389581fe6e2da0feb2368708edacdd95392578d8afff05270d88fc61dce732d83d1063d84d12cf47b5f4633ec7e
2 parents 250598a + dee8943 commit 58da161

File tree

9 files changed

+53
-43
lines changed

9 files changed

+53
-43
lines changed

src/psbt.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ struct PSBTInput
206206
// Taproot fields
207207
std::vector<unsigned char> m_tap_key_sig;
208208
std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> m_tap_script_sigs;
209-
std::map<std::pair<CScript, int>, std::set<std::vector<unsigned char>, ShortestVectorFirstComparator>> m_tap_scripts;
209+
std::map<std::pair<std::vector<unsigned char>, int>, std::set<std::vector<unsigned char>, ShortestVectorFirstComparator>> m_tap_scripts;
210210
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths;
211211
XOnlyPubKey m_tap_internal_key;
212212
uint256 m_tap_merkle_root;
@@ -621,7 +621,7 @@ struct PSBTInput
621621
}
622622
uint8_t leaf_ver = script_v.back();
623623
script_v.pop_back();
624-
const auto leaf_script = std::make_pair(CScript(script_v.begin(), script_v.end()), (int)leaf_ver);
624+
const auto leaf_script = std::make_pair(script_v, (int)leaf_ver);
625625
m_tap_scripts[leaf_script].insert(std::vector<unsigned char>(key.begin() + 1, key.end()));
626626
break;
627627
}
@@ -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::vector<std::tuple<uint8_t, uint8_t, CScript>> m_tap_tree;
716+
std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> 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;
@@ -864,7 +864,7 @@ struct PSBTOutput
864864
while (!s_tree.empty()) {
865865
uint8_t depth;
866866
uint8_t leaf_ver;
867-
CScript script;
867+
std::vector<unsigned char> script;
868868
s_tree >> depth;
869869
s_tree >> leaf_ver;
870870
s_tree >> script;

src/script/descriptor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1643,7 +1643,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
16431643
for (const auto& [depth, script, leaf_ver] : *tree) {
16441644
std::unique_ptr<DescriptorImpl> subdesc;
16451645
if (leaf_ver == TAPROOT_LEAF_TAPSCRIPT) {
1646-
subdesc = InferScript(script, ParseScriptContext::P2TR, provider);
1646+
subdesc = InferScript(CScript(script.begin(), script.end()), ParseScriptContext::P2TR, provider);
16471647
}
16481648
if (!subdesc) {
16491649
ok = false;

src/script/interpreter.cpp

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1825,9 +1825,20 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
18251825
return true;
18261826
}
18271827

1828-
uint256 ComputeTapleafHash(uint8_t leaf_version, const CScript& script)
1828+
uint256 ComputeTapleafHash(uint8_t leaf_version, Span<const unsigned char> script)
18291829
{
1830-
return (HashWriter{HASHER_TAPLEAF} << leaf_version << script).GetSHA256();
1830+
return (HashWriter{HASHER_TAPLEAF} << leaf_version << CompactSizeWriter(script.size()) << script).GetSHA256();
1831+
}
1832+
1833+
uint256 ComputeTapbranchHash(Span<const unsigned char> a, Span<const unsigned char> b)
1834+
{
1835+
HashWriter ss_branch{HASHER_TAPBRANCH};
1836+
if (std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end())) {
1837+
ss_branch << a << b;
1838+
} else {
1839+
ss_branch << b << a;
1840+
}
1841+
return ss_branch.GetSHA256();
18311842
}
18321843

18331844
uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint256& tapleaf_hash)
@@ -1839,14 +1850,8 @@ uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint25
18391850
const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE;
18401851
uint256 k = tapleaf_hash;
18411852
for (int i = 0; i < path_len; ++i) {
1842-
HashWriter ss_branch{HASHER_TAPBRANCH};
18431853
Span node{Span{control}.subspan(TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i, TAPROOT_CONTROL_NODE_SIZE)};
1844-
if (std::lexicographical_compare(k.begin(), k.end(), node.begin(), node.end())) {
1845-
ss_branch << k << node;
1846-
} else {
1847-
ss_branch << node << k;
1848-
}
1849-
k = ss_branch.GetSHA256();
1854+
k = ComputeTapbranchHash(k, node);
18501855
}
18511856
return k;
18521857
}
@@ -1917,18 +1922,18 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
19171922
} else {
19181923
// Script path spending (stack size is >1 after removing optional annex)
19191924
const valtype& control = SpanPopBack(stack);
1920-
const valtype& script_bytes = SpanPopBack(stack);
1921-
exec_script = CScript(script_bytes.begin(), script_bytes.end());
1925+
const valtype& script = SpanPopBack(stack);
19221926
if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > TAPROOT_CONTROL_MAX_SIZE || ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) {
19231927
return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE);
19241928
}
1925-
execdata.m_tapleaf_hash = ComputeTapleafHash(control[0] & TAPROOT_LEAF_MASK, exec_script);
1929+
execdata.m_tapleaf_hash = ComputeTapleafHash(control[0] & TAPROOT_LEAF_MASK, script);
19261930
if (!VerifyTaprootCommitment(control, program, execdata.m_tapleaf_hash)) {
19271931
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
19281932
}
19291933
execdata.m_tapleaf_hash_init = true;
19301934
if ((control[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) {
19311935
// Tapscript (leaf version 0xc0)
1936+
exec_script = CScript(script.begin(), script.end());
19321937
execdata.m_validation_weight_left = ::GetSerializeSize(witness.stack, PROTOCOL_VERSION) + VALIDATION_WEIGHT_OFFSET;
19331938
execdata.m_validation_weight_left_init = true;
19341939
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::TAPSCRIPT, checker, execdata, serror);

src/script/interpreter.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,10 @@ class DeferringSignatureChecker : public BaseSignatureChecker
333333
};
334334

335335
/** Compute the BIP341 tapleaf hash from leaf version & script. */
336-
uint256 ComputeTapleafHash(uint8_t leaf_version, const CScript& script);
336+
uint256 ComputeTapleafHash(uint8_t leaf_version, Span<const unsigned char> script);
337+
/** Compute the BIP341 tapbranch hash from two branches.
338+
* Spans must be 32 bytes each. */
339+
uint256 ComputeTapbranchHash(Span<const unsigned char> a, Span<const unsigned char> b);
337340
/** Compute the BIP341 taproot script tree Merkle root from control block and leaf hash.
338341
* Requires control block to have valid length (33 + k*32, with k in {0,1,..,128}). */
339342
uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint256& tapleaf_hash);

src/script/sign.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,14 @@ static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, Signatur
169169
return false;
170170
}
171171

172-
static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatureCreator& creator, SignatureData& sigdata, int leaf_version, const CScript& script, std::vector<valtype>& result)
172+
static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatureCreator& creator, SignatureData& sigdata, int leaf_version, Span<const unsigned char> script_bytes, std::vector<valtype>& result)
173173
{
174174
// Only BIP342 tapscript signing is supported for now.
175175
if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false;
176176
SigVersion sigversion = SigVersion::TAPSCRIPT;
177177

178-
uint256 leaf_hash = (HashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256();
178+
uint256 leaf_hash = ComputeTapleafHash(leaf_version, script_bytes);
179+
CScript script = CScript(script_bytes.begin(), script_bytes.end());
179180

180181
// <xonly pubkey> OP_CHECKSIG
181182
if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) {

src/script/standard.cpp

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -370,12 +370,7 @@ bool IsValidDestination(const CTxDestination& dest) {
370370
leaf.merkle_branch.push_back(a.hash);
371371
ret.leaves.emplace_back(std::move(leaf));
372372
}
373-
/* Lexicographically sort a and b's hash, and compute parent hash. */
374-
if (a.hash < b.hash) {
375-
ret.hash = (HashWriter{HASHER_TAPBRANCH} << a.hash << b.hash).GetSHA256();
376-
} else {
377-
ret.hash = (HashWriter{HASHER_TAPBRANCH} << b.hash << a.hash).GetSHA256();
378-
}
373+
ret.hash = ComputeTapbranchHash(a.hash, b.hash);
379374
return ret;
380375
}
381376

@@ -443,14 +438,14 @@ void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth)
443438
return branch.size() == 0 || (branch.size() == 1 && branch[0]);
444439
}
445440

446-
TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_version, bool track)
441+
TaprootBuilder& TaprootBuilder::Add(int depth, Span<const unsigned char> script, int leaf_version, bool track)
447442
{
448443
assert((leaf_version & ~TAPROOT_LEAF_MASK) == 0);
449444
if (!IsValid()) return *this;
450445
/* Construct NodeInfo object with leaf hash and (if track is true) also leaf information. */
451446
NodeInfo node;
452-
node.hash = (HashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256();
453-
if (track) node.leaves.emplace_back(LeafInfo{script, leaf_version, {}});
447+
node.hash = ComputeTapleafHash(leaf_version, script);
448+
if (track) node.leaves.emplace_back(LeafInfo{std::vector<unsigned char>(script.begin(), script.end()), leaf_version, {}});
454449
/* Insert into the branch. */
455450
Insert(std::move(node), depth);
456451
return *this;
@@ -506,13 +501,13 @@ TaprootSpendData TaprootBuilder::GetSpendData() const
506501
return spd;
507502
}
508503

509-
std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output)
504+
std::optional<std::vector<std::tuple<int, std::vector<unsigned char>, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output)
510505
{
511506
// Verify that the output matches the assumed Merkle root and internal key.
512507
auto tweak = spenddata.internal_key.CreateTapTweak(spenddata.merkle_root.IsNull() ? nullptr : &spenddata.merkle_root);
513508
if (!tweak || tweak->first != output) return std::nullopt;
514509
// If the Merkle root is 0, the tree is empty, and we're done.
515-
std::vector<std::tuple<int, CScript, int>> ret;
510+
std::vector<std::tuple<int, std::vector<unsigned char>, int>> ret;
516511
if (spenddata.merkle_root.IsNull()) return ret;
517512

518513
/** Data structure to represent the nodes of the tree we're going to build. */
@@ -523,7 +518,7 @@ std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const
523518
std::unique_ptr<TreeNode> sub[2];
524519
/** If this is known to be a leaf node, a pointer to the (script, leaf_ver) pair.
525520
* nullptr otherwise. */
526-
const std::pair<CScript, int>* leaf = nullptr;
521+
const std::pair<std::vector<unsigned char>, int>* leaf = nullptr;
527522
/** Whether or not this node has been explored (is known to be a leaf, or known to have children). */
528523
bool explored = false;
529524
/** Whether or not this node is an inner node (unknown until explored = true). */
@@ -607,7 +602,7 @@ std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const
607602
node.done = true;
608603
stack.pop_back();
609604
} else if (node.sub[0]->done && !node.sub[1]->done && !node.sub[1]->explored && !node.sub[1]->hash.IsNull() &&
610-
(HashWriter{HASHER_TAPBRANCH} << node.sub[1]->hash << node.sub[1]->hash).GetSHA256() == node.hash) {
605+
ComputeTapbranchHash(node.sub[1]->hash, node.sub[1]->hash) == node.hash) {
611606
// Whenever there are nodes with two identical subtrees under it, we run into a problem:
612607
// the control blocks for the leaves underneath those will be identical as well, and thus
613608
// they will all be matched to the same path in the tree. The result is that at the location
@@ -641,10 +636,10 @@ std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const
641636
return ret;
642637
}
643638

644-
std::vector<std::tuple<uint8_t, uint8_t, CScript>> TaprootBuilder::GetTreeTuples() const
639+
std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> TaprootBuilder::GetTreeTuples() const
645640
{
646641
assert(IsComplete());
647-
std::vector<std::tuple<uint8_t, uint8_t, CScript>> tuples;
642+
std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> tuples;
648643
if (m_branch.size()) {
649644
const auto& leaves = m_branch[0]->leaves;
650645
for (const auto& leaf : leaves) {

src/script/standard.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ struct TaprootSpendData
217217
* inference can reconstruct the full tree. Within each set, the control
218218
* blocks are sorted by size, so that the signing logic can easily
219219
* prefer the cheapest one. */
220-
std::map<std::pair<CScript, int>, std::set<std::vector<unsigned char>, ShortestVectorFirstComparator>> scripts;
220+
std::map<std::pair<std::vector<unsigned char>, int>, std::set<std::vector<unsigned char>, ShortestVectorFirstComparator>> scripts;
221221
/** Merge other TaprootSpendData (for the same scriptPubKey) into this. */
222222
void Merge(TaprootSpendData other);
223223
};
@@ -229,7 +229,7 @@ class TaprootBuilder
229229
/** Information about a tracked leaf in the Merkle tree. */
230230
struct LeafInfo
231231
{
232-
CScript script; //!< The script.
232+
std::vector<unsigned char> script; //!< The script.
233233
int leaf_version; //!< The leaf version for that script.
234234
std::vector<uint256> merkle_branch; //!< The hashing partners above this leaf.
235235
};
@@ -296,7 +296,7 @@ class TaprootBuilder
296296
/** Add a new script at a certain depth in the tree. Add() operations must be called
297297
* in depth-first traversal order of binary tree. If track is true, it will be included in
298298
* the GetSpendData() output. */
299-
TaprootBuilder& Add(int depth, const CScript& script, int leaf_version, bool track = true);
299+
TaprootBuilder& Add(int depth, Span<const unsigned char> script, int leaf_version, bool track = true);
300300
/** Like Add(), but for a Merkle node with a given hash to the tree. */
301301
TaprootBuilder& AddOmitted(int depth, const uint256& hash);
302302
/** Finalize the construction. Can only be called when IsComplete() is true.
@@ -314,7 +314,7 @@ class TaprootBuilder
314314
/** Compute spending data (after Finalize()). */
315315
TaprootSpendData GetSpendData() const;
316316
/** Returns a vector of tuples representing the depth, leaf version, and script */
317-
std::vector<std::tuple<uint8_t, uint8_t, CScript>> GetTreeTuples() const;
317+
std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> GetTreeTuples() const;
318318
/** Returns true if there are any tapscripts */
319319
bool HasScripts() const { return !m_branch.empty(); }
320320
};
@@ -325,6 +325,6 @@ class TaprootBuilder
325325
* std::nullopt is returned. Otherwise, a vector of (depth, script, leaf_ver) tuples is
326326
* returned, corresponding to a depth-first traversal of the script tree.
327327
*/
328-
std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output);
328+
std::optional<std::vector<std::tuple<int, std::vector<unsigned char>, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output);
329329

330330
#endif // BITCOIN_SCRIPT_STANDARD_H

src/test/script_standard_tests.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -400,12 +400,11 @@ BOOST_AUTO_TEST_CASE(bip341_spk_test_vectors)
400400

401401
for (const auto& vec : vectors.getValues()) {
402402
TaprootBuilder spktest;
403-
std::map<std::pair<CScript, int>, int> scriptposes;
403+
std::map<std::pair<std::vector<unsigned char>, int>, int> scriptposes;
404404
std::function<void (const UniValue&, int)> parse_tree = [&](const UniValue& node, int depth) {
405405
if (node.isNull()) return;
406406
if (node.isObject()) {
407-
auto script_bytes = ParseHex(node["script"].get_str());
408-
CScript script(script_bytes.begin(), script_bytes.end());
407+
auto script = ParseHex(node["script"].get_str());
409408
int idx = node["id"].getInt<int>();
410409
int leaf_version = node["leafVersion"].getInt<int>();
411410
scriptposes[{script, leaf_version}] = idx;

src/test/script_tests.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,7 +1817,14 @@ BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors)
18171817
}
18181818

18191819
}
1820+
}
18201821

1822+
BOOST_AUTO_TEST_CASE(compute_tapbranch)
1823+
{
1824+
uint256 hash1 = uint256S("8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7");
1825+
uint256 hash2 = uint256S("f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a");
1826+
uint256 result = uint256S("a64c5b7b943315f9b805d7a7296bedfcfd08919270a1f7a1466e98f8693d8cd9");
1827+
BOOST_CHECK_EQUAL(ComputeTapbranchHash(hash1, hash2), result);
18211828
}
18221829

18231830
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)