Skip to content

Commit 3f19d3f

Browse files
committed
Allow using bytestrings for leaf ids in trees, default to leaf hash
1 parent a3b0ef1 commit 3f19d3f

13 files changed

Lines changed: 84 additions & 48 deletions

File tree

ex/tx/taproot_script_redeem.pl

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@
1919
my $prev_tx = $psbt->get_field('PSBT_GLOBAL_UNSIGNED_TX')->value;
2020
$prev_tx->update_utxos;
2121

22-
# get tree from psbt, mark the leaf we're spending with an id, and clear tree
23-
# cache (required after manual changes to tree structure)
22+
# get tree from psbt, and also define a leaf_id which is a hash of the script
23+
# leaf we want to spend
2424
my $tree = $psbt->get_field('PSBT_OUT_TAP_TREE', 0)->value;
25-
$tree->tree->[0]{id} = 0;
26-
$tree->clear_tree_cache;
25+
my $leaf_id = [hex => 'cf560b27c9c761ba98e5b79271a283a03e428b676e7c545aa6b82ab8c3b1a4c8'];
2726

2827
# get public key from psbt
2928
my $public_key = $psbt->get_field('PSBT_OUT_TAP_INTERNAL_KEY', 0)->value;
@@ -64,7 +63,7 @@
6463
->sign(
6564
signing_index => 0,
6665
script_tree => $tree,
67-
leaf_id => 0,
66+
leaf_id => $leaf_id,
6867
public_key => $public_key,
6968
)
7069
->add_signature($private_key_1)

lib/Bitcoin/Crypto/Manual.pod

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -740,18 +740,16 @@ contains just a single script.
740740
my $tree = btc_script_tree->new(
741741
tree => [
742742
{
743-
id => 0,
744743
leaf_version => TAPSCRIPT_LEAF_VERSION,
745744
script => $script1,
746745
},
747746
[
748747
{
749-
id => 1,
748+
id => 'interesting leaf',
750749
leaf_version => TAPSCRIPT_LEAF_VERSION,
751750
script => $script2,
752751
},
753752
{
754-
id => 2,
755753
leaf_version => TAPSCRIPT_LEAF_VERSION,
756754
script => $script3,
757755
},
@@ -769,13 +767,15 @@ field can be used to store the entire unhashed script tree in base64 format.
769767
Notice how each leaf in the tree can have an C<id>. This is the way
770768
Bitcoin::Crypto locates the leaf in various places, for example in
771769
L<Bitcoin::Crypto::Util/get_taproot_ext> or in
772-
L<Bitcoin::Crypto::Transaction::Signer/leaf_id>. However, since it is a custom
773-
part of this structure which is not serialized in PSBT, often you have to give
774-
an ID to a structure which is already created. In this case, you can add an
775-
C<id> directly into a structure in L<Bitcoin::Crypto::Script::Tree/tree>, and
776-
then call L<Bitcoin::Crypto::Script::Tree/clear_tree_cache>:
770+
L<Bitcoin::Crypto::Transaction::Signer/leaf_id>. However, it is a custom part
771+
of this structure which is not serialized in PSBT. If a leaf was not marked
772+
with an C<id>, it is automatically given one which is equal to the hash of the
773+
leaf.
777774

778-
$tree->tree->[0]{id} = 0;
775+
A certain leaf in the tree structure may be given an C<id> after the tree was
776+
constructed, but that requires rebuilding the tree cache:
777+
778+
$tree->tree->[0]{id} = 'my_leaf';
779779
$tree->clear_tree_cache;
780780

781781
=head4 Various bits and pieces

lib/Bitcoin/Crypto/Script/Transaction.pm

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ has option 'script_tree' => (
5858
clearer => -hidden,
5959
);
6060

61+
has option 'leaf_id' => (
62+
coerce => ByteStr,
63+
writer => 1,
64+
clearer => -hidden,
65+
);
66+
6167
has option 'sigop_budget' => (
6268
isa => Int,
6369
writer => -hidden,
@@ -73,6 +79,7 @@ sub _clear
7379
$self->set_taproot_ext_flag(0);
7480
$self->_clear_taproot_annex;
7581
$self->_clear_script_tree;
82+
$self->_clear_leaf_id;
7683
$self->_clear_sigop_budget;
7784
}
7885

lib/Bitcoin/Crypto/Script/Tree.pm

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ sub _traverse
4444
state $leaf_type = Dict [
4545
leaf_version => IntMaxBits [8],
4646
script => BitcoinScript,
47-
id => Optional [Int],
47+
id => Optional [ByteStr],
4848
hash => Optional [ByteStr],
4949
];
5050

@@ -59,6 +59,8 @@ sub _traverse
5959
$value->{hash} =
6060
tagged_hash('TapLeaf', join '', pack('C', $value->{leaf_version}), $script_len, $script);
6161
}
62+
63+
$value->{id} //= $value->{hash};
6264
}
6365

6466
$leaf_action->($value, scalar @stack) if defined $leaf_action;
@@ -153,11 +155,16 @@ sub get_merkle_root
153155
return $self->_tree_cache->{root}{hash};
154156
}
155157

158+
signature_for _get_tapleaf => (
159+
method => !!1,
160+
positional => [ByteStr],
161+
);
162+
156163
sub _get_tapleaf
157164
{
158165
my ($self, $leaf_id) = @_;
159166

160-
my $leaf = first { exists $_->{id} && $_->{id} == $leaf_id } @{$self->_tree_cache->{leaves}};
167+
my $leaf = first { $_->{id} eq $leaf_id } @{$self->_tree_cache->{leaves}};
161168
Bitcoin::Crypto::Exception::ScriptTree->raise(
162169
"no such block with id=$leaf_id"
163170
) unless defined $leaf;
@@ -218,7 +225,7 @@ sub from_path
218225

219226
signature_for get_control_block => (
220227
method => !!1,
221-
positional => [Int, InstanceOf ['Bitcoin::Crypto::Key::Public']],
228+
positional => [ByteStr, InstanceOf ['Bitcoin::Crypto::Key::Public']],
222229
);
223230

224231
sub get_control_block
@@ -258,7 +265,7 @@ Bitcoin::Crypto::Script::Tree - BIP341 Script trees
258265
{hash => [hex => 'f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d']},
259266
[
260267
{
261-
id => 1,
268+
id => 'leaf_1',
262269
leaf_version => TAPSCRIPT_LEAF_VERSION,
263270
script => [hex => '20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac']
264271
},
@@ -281,13 +288,14 @@ trees are used by taproot and are necessary to build custom taproot scripts.
281288
Each leaf in the tree is represented with this Perl structure:
282289
283290
{
284-
id => integer (optional),
291+
id => bytestring (optional),
285292
leaf_version => integer,
286293
script => Bitcoin::Crypto::Script instance (or its coercible),
287294
}
288295
289296
Optional C<id> is used to identify the leaf in the tree, which is used in
290-
methods like L</get_control_block>.
297+
methods like L</get_control_block>. If it is not present, it is automatically
298+
assigned as the hash of the leaf.
291299
292300
Currently, C<leaf_version> must be equal to
293301
L<Bitcoin::Crypto::Constants/TAPSCRIPT_LEAF_VERSION>, since other
@@ -328,18 +336,16 @@ Example structure:
328336
# tree with all scripts known
329337
[
330338
{
331-
id => 0,
332339
leaf_version => TAPSCRIPT_LEAF_VERSION,
333340
script => [hex => '2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac']
334341
},
335342
[
336343
{
337-
id => 1,
344+
id => 'interesting leaf',
338345
leaf_version => TAPSCRIPT_LEAF_VERSION,
339346
script => [hex => '20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac']
340347
},
341348
{
342-
id => 2,
343349
leaf_version => TAPSCRIPT_LEAF_VERSION,
344350
script => [hex => '20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac']
345351
}

lib/Bitcoin/Crypto/Tapscript/Opcode.pm

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,10 @@ sub _OP_CHECKSIG
9191
die_no_trace 'no script_tree in script transaction object'
9292
unless $tx->has_script_tree;
9393

94-
# leaf for this script must be defined with id 0 to get a proper hash
9594
$ext = get_taproot_ext(
9695
$ext_flag,
9796
script_tree => $tx->script_tree,
98-
leaf_id => 0,
97+
leaf_id => $tx->leaf_id,
9998
codesep_pos => $runner->codeseparator,
10099
);
101100
}

lib/Bitcoin/Crypto/Transaction.pm

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -585,18 +585,17 @@ sub _verify_script_taproot
585585
return;
586586
}
587587

588-
# TODO: for now, leaf must have id 0 to get recognized by runner (see
589-
# OP_CHECKSIG for tapscript)
590588
my $tree = btc_script_tree->from_path(
591589
{
592-
id => 0,
590+
id => 'spending script',
593591
leaf_version => $leaf_version,
594592
script => $script,
595593
},
596594
$control_block->script_blocks
597595
);
598596

599597
$script_runner->transaction->set_script_tree($tree);
598+
$script_runner->transaction->set_leaf_id('spending script');
600599

601600
my $tweaked = $control_block->public_key->get_taproot_output_key($tree->get_merkle_root);
602601
my $expected_parity = !has_even_y($tweaked);

lib/Bitcoin/Crypto/Transaction/Signer.pm

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,8 +371,9 @@ script path.
371371
372372
=head3 leaf_id
373373
374-
Numeric identifier which marks the leaf in L</script_tree>. Must be passed for
375-
script spends.
374+
Bytestring identifier which marks the leaf in L</script_tree>. If the leaf was
375+
not manually marked using C<id>, a leaf hash must be used instead. Must be
376+
passed for script spends.
376377
377378
=head3 public_key
378379

lib/Bitcoin/Crypto/Transaction/Signer/P2TR.pm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ has option 'script_tree' => (
1818
);
1919

2020
has option 'leaf_id' => (
21-
isa => Int,
21+
coerce => ByteStr,
2222
);
2323

2424
has option 'public_key' => (
@@ -71,6 +71,7 @@ sub _build_runner
7171
if ($self->script_spend) {
7272
$runner->transaction->set_taproot_ext_flag(1);
7373
$runner->transaction->set_script_tree($self->script_tree);
74+
$runner->transaction->set_leaf_id($self->leaf_id);
7475
}
7576

7677
return $runner;

lib/Bitcoin/Crypto/Util.pm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ C<%args> are empty, an empty string is generated.
488488
489489
C<script_tree> - instance of L<Bitcoin::Crypto::Script::Tree> (required)
490490
491-
C<leaf_id> - integer, identifier of C<script_tree> leaf for current context (required)
491+
C<leaf_id> - bytestring, identifier of C<script_tree> leaf for current context (required)
492492
493493
C<codesep_pos> - position of last executed codeseparator, or undef if there was none (optional)
494494

lib/Bitcoin/Crypto/Util/Internal.pm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,11 +448,11 @@ sub get_taproot_ext
448448
elsif ($ext_flag == 1) {
449449
state $type = Dict [
450450
script_tree => BitcoinScriptTree,
451-
leaf_id => Int,
451+
leaf_id => ByteStr,
452452
codesep_pos => Optional [Maybe [PositiveOrZeroInt]],
453453
];
454454

455-
$type->assert_valid(\%args);
455+
$type->assert_coerce(\%args);
456456

457457
# https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#common-signature-message-extension
458458
return $args{script_tree}->get_tapleaf_hash($args{leaf_id})

0 commit comments

Comments
 (0)