Skip to content

Commit bd13832

Browse files
committed
Refactor script trees - add leaf class
1 parent 2239f07 commit bd13832

14 files changed

Lines changed: 318 additions & 113 deletions

File tree

Changes

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ Revision history for Perl extension Bitcoin::Crypto.
1414
- added unpack_array function
1515
- Bitcoin::Crypto::Transaction::Signer:
1616
- added signature attribute
17+
- Bitcoin::Crypto::Script::Tree:
18+
- added get_leaf method
19+
- added get_leaves method
20+
- deprecated get_tapleaf_script method
21+
- deprecated get_tapleaf_version method
22+
- deprecated get_tapleaf_hash method
23+
- added class Bitcoin::Crypto::Script::Tree::Leaf
1724

1825
[Improvements and behavior changes]
1926
- Script::Tree leaf ids are now bytestrings:

lib/Bitcoin/Crypto/Helpers.pm

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ our @EXPORT_OK = qw(
2727
ensure_length
2828
encode_64bit
2929
decode_64bit
30-
carp_once
3130
parse_formatdesc
3231
ecc
3332
standard_push
@@ -40,16 +39,6 @@ our @EXPORT_OK = qw(
4039
our @CARP_NOT;
4140
my %warned;
4241

43-
sub carp_once
44-
{
45-
my ($msg) = @_;
46-
47-
return if $warned{$msg};
48-
$warned{$msg} = 1;
49-
local @CARP_NOT = ((caller)[0]);
50-
carp($msg);
51-
}
52-
5342
sub pad_hex
5443
{
5544
my ($hex) = @_;

lib/Bitcoin/Crypto/Key/ExtBase.pm

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use warnings;
66
use Mooish::Base -standard;
77
use Scalar::Util qw(blessed);
88
use Types::Common -sigs;
9-
use Carp qw(carp);
109
use List::Util qw(none);
1110

1211
use Bitcoin::Crypto::Key::Private;

lib/Bitcoin/Crypto/Key/ExtPrivate.pm

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use Mooish::Base -standard;
77
use Crypt::Mac::HMAC qw(hmac);
88
use Bitcoin::BIP39 qw(bip39_mnemonic_to_entropy);
99
use Types::Common -sigs;
10-
use Carp qw(carp);
1110

1211
use Bitcoin::Crypto::BIP44;
1312
use Bitcoin::Crypto::Key::ExtPublic;

lib/Bitcoin/Crypto/Key/Public.pm

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use warnings;
55

66
use Mooish::Base -standard;
77
use Types::Common -sigs;
8-
use Carp qw(carp);
98

109
use Bitcoin::Crypto::Script;
1110
use Bitcoin::Crypto::Base58 qw(encode_base58check);

lib/Bitcoin/Crypto/PSBT/FieldType.pm

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -761,21 +761,21 @@ my %types = (
761761
my $tree = ($sig->(@_))[0];
762762

763763
my @list;
764-
my $action = sub {
765-
my ($value, $depth) = @_;
766-
764+
foreach my $leaf (@{$tree->get_leaves}) {
767765
die_no_trace 'tree must have all its leaves unhashed'
768-
unless defined $value->{script};
766+
unless $leaf->has_script && $leaf->has_leaf_version;
767+
768+
# this should always be the case, but check nonetheless
769+
die_no_trace 'tree must have depth set for its leaves'
770+
unless $leaf->has_depth;
769771

770-
my $serialized = $value->{script}->to_serialized;
772+
my $serialized = $leaf->script->to_serialized;
771773

772-
push @list, pack('C', $depth)
773-
. pack('C', $value->{leaf_version})
774+
push @list, pack('C', $leaf->depth)
775+
. pack('C', $leaf->leaf_version)
774776
. pack_compactsize(length $serialized)
775777
. $serialized;
776-
};
777-
778-
$tree->_traverse(undef, $action);
778+
}
779779

780780
return join '', @list;
781781

lib/Bitcoin/Crypto/Script.pm

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use Types::Common -sigs;
88
use Crypt::Digest::SHA256 qw(sha256);
99
use Scalar::Util qw(blessed);
1010
use List::Util qw(any);
11-
use Carp qw(carp);
1211

1312
use Bitcoin::Crypto::Base58 qw(encode_base58check decode_base58check);
1413
use Bitcoin::Crypto::Bech32 qw(encode_segwit decode_segwit get_hrp);

lib/Bitcoin/Crypto/Script/Tree.pm

Lines changed: 72 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ use warnings;
66
use Mooish::Base -standard;
77
use Types::Common -sigs;
88
use List::Util qw(first);
9+
use Scalar::Util qw(blessed);
10+
use Carp qw(carp);
911

1012
use Bitcoin::Crypto::Types -types;
1113
use Bitcoin::Crypto::Exception;
1214
use Bitcoin::Crypto::Util::Internal qw(tagged_hash pack_compactsize has_even_y);
15+
use Bitcoin::Crypto::Script::Tree::Leaf;
1316
use Bitcoin::Crypto::Transaction::ControlBlock;
1417

1518
# recursive structure - a binary tree
@@ -26,7 +29,7 @@ has field '_tree_cache' => (
2629
# script trees
2730
sub _traverse
2831
{
29-
my ($self, $join_action, $leaf_action) = @_;
32+
my ($self, $paths_ref, $leaves_ref) = @_;
3033

3134
my @stack = ({nodes => [@{$self->tree}], results => []});
3235
my $result;
@@ -40,30 +43,14 @@ sub _traverse
4043
push @stack, {nodes => [@$item], results => []};
4144
}
4245
else {
43-
state $precomputed_type = Dict [hash => ByteStr];
44-
state $leaf_type = Dict [
45-
leaf_version => IntMaxBits [8],
46-
script => BitcoinScript,
47-
id => Optional [ByteStr],
48-
hash => Optional [ByteStr],
49-
];
5046

5147
# this value is a leaf which may need calculating
52-
my $value = $precomputed_type->coerce($item);
53-
if (!$precomputed_type->check($value)) {
54-
$value = $leaf_type->assert_coerce($item);
55-
if (!defined $value->{hash}) {
56-
my $script = $value->{script}->to_serialized;
57-
my $script_len = pack_compactsize(length $script);
58-
59-
$value->{hash} =
60-
tagged_hash('TapLeaf', join '', pack('C', $value->{leaf_version}), $script_len, $script);
61-
}
62-
63-
$value->{id} //= $value->{hash};
64-
}
48+
my $value = Bitcoin::Crypto::Script::Tree::Leaf->new(
49+
%{$item},
50+
depth => scalar @stack,
51+
);
6552

66-
$leaf_action->($value, scalar @stack) if defined $leaf_action;
53+
push @{$leaves_ref}, $value;
6754
push @{$stack[-1]{results}}, $value;
6855
}
6956
}
@@ -75,9 +62,21 @@ sub _traverse
7562
@results = reverse @results
7663
if $results[0]{hash} gt $results[1]{hash};
7764

78-
my %data = defined $join_action ? $join_action->(@results, scalar @stack) : ();
65+
my @all_ids;
66+
foreach my $key (keys @results) {
67+
my ($this_one, $other_one) = @results[$key, ($key + 1) % 2];
68+
69+
my $is_leaf = blessed $this_one;
70+
my @ids = $is_leaf ? ($this_one->id) : @{$this_one->{ids}};
71+
push @all_ids, @ids;
72+
73+
foreach my $id (@ids) {
74+
push @{$paths_ref->{$id}}, $other_one->{hash};
75+
}
76+
}
77+
7978
$result = {
80-
%data,
79+
ids => \@all_ids,
8180
hash => tagged_hash('TapBranch', join '', map { $_->{hash} } @results),
8281
};
8382
}
@@ -98,52 +97,17 @@ sub _traverse
9897
return $result;
9998
}
10099

101-
sub _tree_paths_action
102-
{
103-
my ($self) = @_;
104-
105-
my %paths;
106-
my $action = sub {
107-
my ($node1, $node2) = @_;
108-
my @all_ids;
109-
110-
foreach my $info ([$node1, $node2], [$node2, $node1]) {
111-
my ($this_one, $other_one) = @{$info};
112-
next unless defined $this_one->{id};
113-
my @ids = ref $this_one->{id} ? @{$this_one->{id}} : $this_one->{id};
114-
push @all_ids, @ids;
115-
116-
foreach my $id (@ids) {
117-
push @{$paths{$id}}, $other_one->{hash};
118-
}
119-
}
120-
121-
return (
122-
id => \@all_ids,
123-
);
124-
};
125-
126-
return (\%paths, $action);
127-
}
128-
129100
sub _build_tree_cache
130101
{
131102
my ($self) = @_;
132103

133104
my @leaves;
134-
my ($paths, $paths_action) = $self->_tree_paths_action;
135-
136-
my $root = $self->_traverse(
137-
$paths_action,
138-
sub {
139-
my $leaf = shift;
140-
push @leaves, $leaf;
141-
}
142-
);
105+
my %paths;
106+
my $root = $self->_traverse(\%paths, \@leaves);
143107

144108
return {
145109
leaves => \@leaves,
146-
paths => $paths,
110+
paths => \%paths,
147111
root => $root,
148112
};
149113
}
@@ -155,12 +119,12 @@ sub get_merkle_root
155119
return $self->_tree_cache->{root}{hash};
156120
}
157121

158-
signature_for _get_tapleaf => (
122+
signature_for get_leaf => (
159123
method => !!1,
160124
positional => [ByteStr],
161125
);
162126

163-
sub _get_tapleaf
127+
sub get_leaf
164128
{
165129
my ($self, $leaf_id) = @_;
166130

@@ -176,21 +140,27 @@ sub get_tapleaf_script
176140
{
177141
my ($self, $leaf_id) = @_;
178142

179-
return $self->_get_tapleaf($leaf_id)->{script};
143+
carp 'get_tapleaf_script is deprecated - use get_leaf(...)->script instead';
144+
145+
return $self->get_leaf($leaf_id)->script;
180146
}
181147

182148
sub get_tapleaf_hash
183149
{
184150
my ($self, $leaf_id) = @_;
185151

186-
return $self->_get_tapleaf($leaf_id)->{hash};
152+
carp 'get_tapleaf_hash is deprecated - use get_leaf(...)->hash instead';
153+
154+
return $self->get_leaf($leaf_id)->hash;
187155
}
188156

189157
sub get_tapleaf_version
190158
{
191159
my ($self, $leaf_id) = @_;
192160

193-
return $self->_get_tapleaf($leaf_id)->{leaf_version};
161+
carp 'get_tapleaf_version is deprecated - use get_leaf(...)->leaf_version instead';
162+
163+
return $self->get_leaf($leaf_id)->leaf_version;
194164
}
195165

196166
sub get_tree_paths
@@ -200,6 +170,13 @@ sub get_tree_paths
200170
return $self->_tree_cache->{paths};
201171
}
202172

173+
sub get_leaves
174+
{
175+
my ($self) = @_;
176+
177+
return $self->_tree_cache->{leaves};
178+
}
179+
203180
signature_for from_path => (
204181
method => !!1,
205182
positional => [HashRef, ArrayRef [ByteStr]],
@@ -233,7 +210,7 @@ sub get_control_block
233210
my ($self, $leaf_id, $pubkey) = @_;
234211
my $cache = $self->_tree_cache;
235212

236-
my $leaf_version = $self->get_tapleaf_version($leaf_id);
213+
my $leaf_version = $self->get_leaf($leaf_id)->leaf_version;
237214
my $tapkey = $pubkey->get_taproot_output_key($cache->{root}{hash});
238215
my $parity = has_even_y($tapkey);
239216

@@ -285,7 +262,8 @@ trees are used by taproot and are necessary to build custom taproot scripts.
285262
286263
=head2 Tree leaves
287264
288-
Each leaf in the tree is represented with this Perl structure:
265+
Each leaf in the tree is represented with this Perl structure, which is turned
266+
into an instance of L<Bitcoin::Crypto::Script::Tree::Leaf>:
289267
290268
{
291269
id => bytestring (optional),
@@ -367,6 +345,10 @@ disclosing information about a script:
367345
]
368346
]
369347
348+
The structure in C<tree> is considered immutable and will not be changed. All
349+
data coercions will be done in separate structures, which can be cleared using
350+
L</clear_tree_cache> to be updated if the source C<tree> has changed.
351+
370352
=head2 Methods
371353
372354
=head3 new
@@ -398,24 +380,37 @@ could look like this:
398380
Calculates a merkle root of the script tree. Returns a bytestring which is the
399381
root hash of the tree.
400382
383+
=head3 get_leaf
384+
385+
$leaf = $tree->get_leaf($id_or_hash)
386+
387+
Returns a tree leaf - instance of L<Bitcoin::Crypto::Script::Tree::Leaf>. If
388+
the leaf does not exist, an exception is raised.
389+
401390
=head3 get_tapleaf_script
402391
403392
$script = $tree->get_tapleaf_script($leaf_id)
404393
394+
Deprecated - use C<< $tree->get_leaf($leaf_id)->script >> instead.
395+
405396
Returns a tapleaf script of a leaf with given C<$leaf_id>. If such leaf does
406397
not exist, an exception is thrown. Returns a script instance.
407398
408399
=head3 get_tapleaf_version
409400
410401
$int = $tree->get_tapleaf_version($leaf_id)
411402
403+
Deprecated - use C<< $tree->get_leaf($leaf_id)->leaf_version >> instead.
404+
412405
Returns a tapleaf version of a leaf with given C<$leaf_id>. If such leaf does
413406
not exist, an exception is thrown. Returns an integer.
414407
415408
=head3 get_tapleaf_hash
416409
417410
$hash = $tree->get_tapleaf_hash($leaf_id)
418411
412+
Deprecated - use C<< $tree->get_leaf($leaf_id)->hash >> instead.
413+
419414
Calculates a tapleaf hash of a leaf with given C<$leaf_id>. If such leaf does
420415
not exist, an exception is thrown. Returns a bytestring.
421416
@@ -435,6 +430,14 @@ of L<Bitcoin::Crypto::Transaction::ControlBlock>.
435430
Returns a hash reference of paths for each of leaves in the tree which have an
436431
id. Each path is an array reference - same as what L</from_path> takes as input.
437432
433+
=head3 get_leaves
434+
435+
$leaves = $tree->get_leaves()
436+
437+
Returns an array reference of leaves in the hash - each leaf is an instance of
438+
L<Bitcoin::Crypto::Script::Tree::Leaf>. The order of leaves in the array is
439+
strictly determined by the order of leaves in the tree.
440+
438441
=head3 clear_tree_cache
439442
440443
$tree->clear_tree_cache()

0 commit comments

Comments
 (0)