Merge bitcoin/bitcoin#25877: refactor: Do not use CScript for tapleaf scripts until the tapleaf version is known

dee89438b8 Abstract out ComputeTapbranchHash (Russell O'Connor)
8e3fc99427 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 dee89438b8
  instagibbs:
    ACK dee89438b8
  achow101:
    ACK dee89438b8
  sipa:
    ACK dee89438b8
  aureleoules:
    reACK dee89438b8 - I verified that there is no behavior change.

Tree-SHA512: 4a1d37f3e9a1890e7f5eadcf65562688cc451389581fe6e2da0feb2368708edacdd95392578d8afff05270d88fc61dce732d83d1063d84d12cf47b5f4633ec7e
This commit is contained in:
Andrew Chow
2023-01-19 17:42:14 -05:00
9 changed files with 53 additions and 43 deletions

View File

@@ -370,12 +370,7 @@ bool IsValidDestination(const CTxDestination& dest) {
leaf.merkle_branch.push_back(a.hash);
ret.leaves.emplace_back(std::move(leaf));
}
/* Lexicographically sort a and b's hash, and compute parent hash. */
if (a.hash < b.hash) {
ret.hash = (HashWriter{HASHER_TAPBRANCH} << a.hash << b.hash).GetSHA256();
} else {
ret.hash = (HashWriter{HASHER_TAPBRANCH} << b.hash << a.hash).GetSHA256();
}
ret.hash = ComputeTapbranchHash(a.hash, b.hash);
return ret;
}
@@ -443,14 +438,14 @@ void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth)
return branch.size() == 0 || (branch.size() == 1 && branch[0]);
}
TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_version, bool track)
TaprootBuilder& TaprootBuilder::Add(int depth, Span<const unsigned char> script, int leaf_version, bool track)
{
assert((leaf_version & ~TAPROOT_LEAF_MASK) == 0);
if (!IsValid()) return *this;
/* Construct NodeInfo object with leaf hash and (if track is true) also leaf information. */
NodeInfo node;
node.hash = (HashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256();
if (track) node.leaves.emplace_back(LeafInfo{script, leaf_version, {}});
node.hash = ComputeTapleafHash(leaf_version, script);
if (track) node.leaves.emplace_back(LeafInfo{std::vector<unsigned char>(script.begin(), script.end()), leaf_version, {}});
/* Insert into the branch. */
Insert(std::move(node), depth);
return *this;
@@ -506,13 +501,13 @@ TaprootSpendData TaprootBuilder::GetSpendData() const
return spd;
}
std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output)
std::optional<std::vector<std::tuple<int, std::vector<unsigned char>, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output)
{
// Verify that the output matches the assumed Merkle root and internal key.
auto tweak = spenddata.internal_key.CreateTapTweak(spenddata.merkle_root.IsNull() ? nullptr : &spenddata.merkle_root);
if (!tweak || tweak->first != output) return std::nullopt;
// If the Merkle root is 0, the tree is empty, and we're done.
std::vector<std::tuple<int, CScript, int>> ret;
std::vector<std::tuple<int, std::vector<unsigned char>, int>> ret;
if (spenddata.merkle_root.IsNull()) return ret;
/** 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
std::unique_ptr<TreeNode> sub[2];
/** If this is known to be a leaf node, a pointer to the (script, leaf_ver) pair.
* nullptr otherwise. */
const std::pair<CScript, int>* leaf = nullptr;
const std::pair<std::vector<unsigned char>, int>* leaf = nullptr;
/** Whether or not this node has been explored (is known to be a leaf, or known to have children). */
bool explored = false;
/** 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
node.done = true;
stack.pop_back();
} else if (node.sub[0]->done && !node.sub[1]->done && !node.sub[1]->explored && !node.sub[1]->hash.IsNull() &&
(HashWriter{HASHER_TAPBRANCH} << node.sub[1]->hash << node.sub[1]->hash).GetSHA256() == node.hash) {
ComputeTapbranchHash(node.sub[1]->hash, node.sub[1]->hash) == node.hash) {
// Whenever there are nodes with two identical subtrees under it, we run into a problem:
// the control blocks for the leaves underneath those will be identical as well, and thus
// 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
return ret;
}
std::vector<std::tuple<uint8_t, uint8_t, CScript>> TaprootBuilder::GetTreeTuples() const
std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> TaprootBuilder::GetTreeTuples() const
{
assert(IsComplete());
std::vector<std::tuple<uint8_t, uint8_t, CScript>> tuples;
std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> tuples;
if (m_branch.size()) {
const auto& leaves = m_branch[0]->leaves;
for (const auto& leaf : leaves) {