Skip to content

Commit 90fcac3

Browse files
committed
Add TaprootBuilder class
This class functions as a utility for building taproot outputs, from internal key and script leaves.
1 parent 5f6cc8d commit 90fcac3

File tree

7 files changed

+262
-2
lines changed

7 files changed

+262
-2
lines changed

src/pubkey.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@ XOnlyPubKey::XOnlyPubKey(Span<const unsigned char> bytes)
180180
std::copy(bytes.begin(), bytes.end(), m_keydata.begin());
181181
}
182182

183+
bool XOnlyPubKey::IsFullyValid() const
184+
{
185+
secp256k1_xonly_pubkey pubkey;
186+
return secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &pubkey, m_keydata.data());
187+
}
188+
183189
bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const
184190
{
185191
assert(sigbytes.size() == 64);

src/pubkey.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,11 @@ class XOnlyPubKey
229229
XOnlyPubKey(const XOnlyPubKey&) = default;
230230
XOnlyPubKey& operator=(const XOnlyPubKey&) = default;
231231

232+
/** Determine if this pubkey is fully valid. This is true for approximately 50% of all
233+
* possible 32-byte arrays. If false, VerifySchnorr and CreatePayToContract will always
234+
* fail. */
235+
bool IsFullyValid() const;
236+
232237
/** Construct an x-only pubkey from exactly 32 bytes. */
233238
explicit XOnlyPubKey(Span<const unsigned char> bytes);
234239

src/script/interpreter.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,8 +1484,8 @@ template PrecomputedTransactionData::PrecomputedTransactionData(const CTransacti
14841484
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
14851485

14861486
static const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash");
1487-
static const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
1488-
static const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
1487+
const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
1488+
const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
14891489

14901490
static bool HandleMissingData(MissingDataBehavior mdb)
14911491
{

src/script/interpreter.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#ifndef BITCOIN_SCRIPT_INTERPRETER_H
77
#define BITCOIN_SCRIPT_INTERPRETER_H
88

9+
#include <hash.h>
910
#include <script/script_error.h>
1011
#include <span.h>
1112
#include <primitives/transaction.h>
@@ -218,6 +219,9 @@ static constexpr size_t TAPROOT_CONTROL_NODE_SIZE = 32;
218219
static constexpr size_t TAPROOT_CONTROL_MAX_NODE_COUNT = 128;
219220
static constexpr size_t TAPROOT_CONTROL_MAX_SIZE = TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT;
220221

222+
extern const CHashWriter HASHER_TAPLEAF; //!< Hasher with tag "TapLeaf" pre-fed to it.
223+
extern const CHashWriter HASHER_TAPBRANCH; //!< Hasher with tag "TapBranch" pre-fed to it.
224+
221225
template <class T>
222226
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr);
223227

src/script/standard.cpp

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
#include <script/standard.h>
77

88
#include <crypto/sha256.h>
9+
#include <hash.h>
910
#include <pubkey.h>
11+
#include <script/interpreter.h>
1012
#include <script/script.h>
13+
#include <util/strencodings.h>
1114

1215
#include <string>
1316

@@ -370,3 +373,99 @@ CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys)
370373
bool IsValidDestination(const CTxDestination& dest) {
371374
return dest.index() != 0;
372375
}
376+
377+
/*static*/ TaprootBuilder::NodeInfo TaprootBuilder::Combine(NodeInfo&& a, NodeInfo&& b)
378+
{
379+
NodeInfo ret;
380+
/* Lexicographically sort a and b's hash, and compute parent hash. */
381+
if (a.hash < b.hash) {
382+
ret.hash = (CHashWriter(HASHER_TAPBRANCH) << a.hash << b.hash).GetSHA256();
383+
} else {
384+
ret.hash = (CHashWriter(HASHER_TAPBRANCH) << b.hash << a.hash).GetSHA256();
385+
}
386+
return ret;
387+
}
388+
389+
void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth)
390+
{
391+
assert(depth >= 0 && (size_t)depth <= TAPROOT_CONTROL_MAX_NODE_COUNT);
392+
/* We cannot insert a leaf at a lower depth while a deeper branch is unfinished. Doing
393+
* so would mean the Add() invocations do not correspond to a DFS traversal of a
394+
* binary tree. */
395+
if ((size_t)depth + 1 < m_branch.size()) {
396+
m_valid = false;
397+
return;
398+
}
399+
/* As long as an entry in the branch exists at the specified depth, combine it and propagate up.
400+
* The 'node' variable is overwritten here with the newly combined node. */
401+
while (m_valid && m_branch.size() > (size_t)depth && m_branch[depth].has_value()) {
402+
node = Combine(std::move(node), std::move(*m_branch[depth]));
403+
m_branch.pop_back();
404+
if (depth == 0) m_valid = false; /* Can't propagate further up than the root */
405+
--depth;
406+
}
407+
if (m_valid) {
408+
/* Make sure the branch is big enough to place the new node. */
409+
if (m_branch.size() <= (size_t)depth) m_branch.resize((size_t)depth + 1);
410+
assert(!m_branch[depth].has_value());
411+
m_branch[depth] = std::move(node);
412+
}
413+
}
414+
415+
/*static*/ bool TaprootBuilder::ValidDepths(const std::vector<int>& depths)
416+
{
417+
std::vector<bool> branch;
418+
for (int depth : depths) {
419+
// This inner loop corresponds to effectively the same logic on branch
420+
// as what Insert() performs on the m_branch variable. Instead of
421+
// storing a NodeInfo object, just remember whether or not there is one
422+
// at that depth.
423+
if (depth < 0 || (size_t)depth > TAPROOT_CONTROL_MAX_NODE_COUNT) return false;
424+
if ((size_t)depth + 1 < branch.size()) return false;
425+
while (branch.size() > (size_t)depth && branch[depth]) {
426+
branch.pop_back();
427+
if (depth == 0) return false;
428+
--depth;
429+
}
430+
if (branch.size() <= (size_t)depth) branch.resize((size_t)depth + 1);
431+
assert(!branch[depth]);
432+
branch[depth] = true;
433+
}
434+
// And this check corresponds to the IsComplete() check on m_branch.
435+
return branch.size() == 0 || (branch.size() == 1 && branch[0]);
436+
}
437+
438+
TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_version)
439+
{
440+
assert((leaf_version & ~TAPROOT_LEAF_MASK) == 0);
441+
if (!IsValid()) return *this;
442+
/* Construct NodeInfo object with leaf hash. */
443+
NodeInfo node;
444+
node.hash = (CHashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256();
445+
/* Insert into the branch. */
446+
Insert(std::move(node), depth);
447+
return *this;
448+
}
449+
450+
TaprootBuilder& TaprootBuilder::AddOmitted(int depth, const uint256& hash)
451+
{
452+
if (!IsValid()) return *this;
453+
/* Construct NodeInfo object with the hash directly, and insert it into the branch. */
454+
NodeInfo node;
455+
node.hash = hash;
456+
Insert(std::move(node), depth);
457+
return *this;
458+
}
459+
460+
TaprootBuilder& TaprootBuilder::Finalize(const XOnlyPubKey& internal_key)
461+
{
462+
/* Can only call this function when IsComplete() is true. */
463+
assert(IsComplete());
464+
m_internal_key = internal_key;
465+
auto ret = m_internal_key.CreateTapTweak(m_branch.size() == 0 ? nullptr : &m_branch[0]->hash);
466+
assert(ret.has_value());
467+
std::tie(m_output_key, std::ignore) = *ret;
468+
return *this;
469+
}
470+
471+
WitnessV1Taproot TaprootBuilder::GetOutput() { return WitnessV1Taproot{m_output_key}; }

src/script/standard.h

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,82 @@ CScript GetScriptForRawPubKey(const CPubKey& pubkey);
209209
/** Generate a multisig script. */
210210
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
211211

212+
/** Utility class to construct Taproot outputs from internal key and script tree. */
213+
class TaprootBuilder
214+
{
215+
private:
216+
/** Information associated with a node in the Merkle tree. */
217+
struct NodeInfo
218+
{
219+
/** Merkle hash of this node. */
220+
uint256 hash;
221+
};
222+
/** Whether the builder is in a valid state so far. */
223+
bool m_valid = true;
224+
225+
/** The current state of the builder.
226+
*
227+
* For each level in the tree, one NodeInfo object may be present. m_branch[0]
228+
* is information about the root; further values are for deeper subtrees being
229+
* explored.
230+
*
231+
* For every right branch taken to reach the position we're currently
232+
* working in, there will be a (non-nullopt) entry in m_branch corresponding
233+
* to the left branch at that level.
234+
*
235+
* For example, imagine this tree: - N0 -
236+
* / \
237+
* N1 N2
238+
* / \ / \
239+
* A B C N3
240+
* / \
241+
* D E
242+
*
243+
* Initially, m_branch is empty. After processing leaf A, it would become
244+
* {nullopt, nullopt, A}. When processing leaf B, an entry at level 2 already
245+
* exists, and it would thus be combined with it to produce a level 1 one,
246+
* resulting in {nullopt, N1}. Adding C and D takes us to {nullopt, N1, C}
247+
* and {nullopt, N1, C, D} respectively. When E is processed, it is combined
248+
* with D, and then C, and then N1, to produce the root, resulting in {N0}.
249+
*
250+
* This structure allows processing with just O(log n) overhead if the leaves
251+
* are computed on the fly.
252+
*
253+
* As an invariant, there can never be nullopt entries at the end. There can
254+
* also not be more than 128 entries (as that would mean more than 128 levels
255+
* in the tree). The depth of newly added entries will always be at least
256+
* equal to the current size of m_branch (otherwise it does not correspond
257+
* to a depth-first traversal of a tree). m_branch is only empty if no entries
258+
* have ever be processed. m_branch having length 1 corresponds to being done.
259+
*/
260+
std::vector<std::optional<NodeInfo>> m_branch;
261+
262+
XOnlyPubKey m_internal_key; //!< The internal key, set when finalizing.
263+
XOnlyPubKey m_output_key; //!< The output key, computed when finalizing. */
264+
265+
/** Combine information about a parent Merkle tree node from its child nodes. */
266+
static NodeInfo Combine(NodeInfo&& a, NodeInfo&& b);
267+
/** Insert information about a node at a certain depth, and propagate information up. */
268+
void Insert(NodeInfo&& node, int depth);
269+
270+
public:
271+
/** Add a new script at a certain depth in the tree. Add() operations must be called
272+
* in depth-first traversal order of binary tree. */
273+
TaprootBuilder& Add(int depth, const CScript& script, int leaf_version);
274+
/** Like Add(), but for a Merkle node with a given hash to the tree. */
275+
TaprootBuilder& AddOmitted(int depth, const uint256& hash);
276+
/** Finalize the construction. Can only be called when IsComplete() is true.
277+
internal_key.IsFullyValid() must be true. */
278+
TaprootBuilder& Finalize(const XOnlyPubKey& internal_key);
279+
280+
/** Return true if so far all input was valid. */
281+
bool IsValid() const { return m_valid; }
282+
/** Return whether there were either no leaves, or the leaves form a Huffman tree. */
283+
bool IsComplete() const { return m_valid && (m_branch.size() == 0 || (m_branch.size() == 1 && m_branch[0].has_value())); }
284+
/** Compute scriptPubKey (after Finalize()). */
285+
WitnessV1Taproot GetOutput();
286+
/** Check if a list of depths is legal (will lead to IsComplete()). */
287+
static bool ValidDepths(const std::vector<int>& depths);
288+
};
289+
212290
#endif // BITCOIN_SCRIPT_STANDARD_H

src/test/script_standard_tests.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <key.h>
6+
#include <key_io.h>
67
#include <script/script.h>
78
#include <script/signingprovider.h>
89
#include <script/standard.h>
910
#include <test/util/setup_common.h>
11+
#include <util/strencodings.h>
1012

1113
#include <boost/test/unit_test.hpp>
1214

@@ -378,4 +380,70 @@ BOOST_AUTO_TEST_CASE(script_standard_GetScriptFor_)
378380
BOOST_CHECK(result == expected);
379381
}
380382

383+
BOOST_AUTO_TEST_CASE(script_standard_taproot_builder)
384+
{
385+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({}), true);
386+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0}), true);
387+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1}), false);
388+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2}), false);
389+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,0}), false);
390+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,1}), false);
391+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,2}), false);
392+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,0}), false);
393+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,1}), true);
394+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,2}), false);
395+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,0}), false);
396+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,1}), false);
397+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,2}), false);
398+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,0,0}), false);
399+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,0,1}), false);
400+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,0,2}), false);
401+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,1,0}), false);
402+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,1,1}), false);
403+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,1,2}), false);
404+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,2,0}), false);
405+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,2,1}), false);
406+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,2,2}), false);
407+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,0,0}), false);
408+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,0,1}), false);
409+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,0,2}), false);
410+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,1,0}), false);
411+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,1,1}), false);
412+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,1,2}), false);
413+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,2,0}), false);
414+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,2,1}), false);
415+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,2,2}), true);
416+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,0,0}), false);
417+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,0,1}), false);
418+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,0,2}), false);
419+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,1,0}), false);
420+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,1,1}), false);
421+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,1,2}), false);
422+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,2,0}), false);
423+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,2,1}), true);
424+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,2,2}), false);
425+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,2,2,3,4,5,6,7,8,9,10,11,12,14,14,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,31,31,31,31,31,31,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,128}), true);
426+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({128,128,127,126,125,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,109,108,107,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1}), true);
427+
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({129,129,128,127,126,125,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,109,108,107,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1}), false);
428+
429+
XOnlyPubKey key_inner{ParseHex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")};
430+
XOnlyPubKey key_1{ParseHex("c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5")};
431+
XOnlyPubKey key_2{ParseHex("f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9")};
432+
CScript script_1 = CScript() << ToByteVector(key_1) << OP_CHECKSIG;
433+
CScript script_2 = CScript() << ToByteVector(key_2) << OP_CHECKSIG;
434+
uint256 hash_3 = uint256S("31fe7061656bea2a36aa60a2f7ef940578049273746935d296426dc0afd86b68");
435+
436+
TaprootBuilder builder;
437+
BOOST_CHECK(builder.IsValid() && builder.IsComplete());
438+
builder.Add(2, script_2, 0xc0);
439+
BOOST_CHECK(builder.IsValid() && !builder.IsComplete());
440+
builder.AddOmitted(2, hash_3);
441+
BOOST_CHECK(builder.IsValid() && !builder.IsComplete());
442+
builder.Add(1, script_1, 0xc0);
443+
BOOST_CHECK(builder.IsValid() && builder.IsComplete());
444+
builder.Finalize(key_inner);
445+
BOOST_CHECK(builder.IsValid() && builder.IsComplete());
446+
BOOST_CHECK_EQUAL(EncodeDestination(builder.GetOutput()), "bc1pj6gaw944fy0xpmzzu45ugqde4rz7mqj5kj0tg8kmr5f0pjq8vnaqgynnge");
447+
}
448+
381449
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)