Skip to content

Commit a60e163

Browse files
committed
Hacking on taproot again, work in progress
1 parent 8eb39fb commit a60e163

21 files changed

+618
-223
lines changed

lib/Bitcoin/Crypto.pm

+11
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ our @EXPORT_OK = qw(
1111
btc_extpub
1212
btc_pub
1313
btc_script
14+
btc_script_tree
1415
btc_transaction
1516
btc_block
1617
btc_utxo
@@ -49,6 +50,12 @@ sub btc_script
4950
return 'Bitcoin::Crypto::Script';
5051
}
5152

53+
sub btc_script_tree
54+
{
55+
require Bitcoin::Crypto::Script::Tree;
56+
return 'Bitcoin::Crypto::Script::Tree';
57+
}
58+
5259
sub btc_transaction
5360
{
5461
require Bitcoin::Crypto::Transaction;
@@ -143,6 +150,10 @@ Loads L<Bitcoin::Crypto::Key::Public>
143150
144151
Loads L<Bitcoin::Crypto::Script>
145152
153+
=head2 btc_script_tree
154+
155+
Loads L<Bitcoin::Crypto::Script::Tree>
156+
146157
=head2 btc_transaction
147158
148159
Loads L<Bitcoin::Crypto::Transaction>

lib/Bitcoin/Crypto/Constants.pm

-3
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@ use constant {
4545
psbt_global_map => 'global',
4646
psbt_input_map => 'in',
4747
psbt_output_map => 'out',
48-
49-
signing_algorithm_ecdsa => 'ecdsa',
50-
signing_algorithm_schnorr => 'schnorr',
5148
};
5249

5350
# These constants are environment-specific and internal only

lib/Bitcoin/Crypto/Exception.pm

+7
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,13 @@ sub as_string
241241
use parent -norequire, 'Bitcoin::Crypto::Exception';
242242
}
243243

244+
{
245+
246+
package Bitcoin::Crypto::Exception::ScriptTree;
247+
248+
use parent -norequire, 'Bitcoin::Crypto::Exception';
249+
}
250+
244251
{
245252

246253
package Bitcoin::Crypto::Exception::ScriptSyntax;

lib/Bitcoin/Crypto/Key/Public.pm

+20-9
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,13 @@ use Bitcoin::Crypto::Base58 qw(encode_base58check);
1313
use Bitcoin::Crypto::Bech32 qw(encode_segwit);
1414
use Bitcoin::Crypto::Types -types;
1515
use Bitcoin::Crypto::Constants;
16-
use Bitcoin::Crypto::Util qw(hash160 get_public_key_compressed taproot_merkle_root);
16+
use Bitcoin::Crypto::Util qw(hash160 get_public_key_compressed);
1717
use Bitcoin::Crypto::Helpers qw(ecc);
1818

1919
use namespace::clean;
2020

2121
extends qw(Bitcoin::Crypto::Key::Base);
2222

23-
has extended 'key_instance' => (
24-
required => 0,
25-
predicate => 1,
26-
);
27-
2823
sub _is_private { 0 }
2924

3025
signature_for get_hash => (
@@ -39,6 +34,18 @@ sub get_hash
3934
return hash160($self->to_serialized);
4035
}
4136

37+
signature_for get_xonly_key => (
38+
method => Object,
39+
positional => [],
40+
);
41+
42+
sub get_xonly_key
43+
{
44+
my ($self) = @_;
45+
46+
return $self->raw_key('public_xonly');
47+
}
48+
4249
sub key_hash
4350
{
4451
my $self = shift;
@@ -76,7 +83,11 @@ sub witness_program
7683
},
7784
(Bitcoin::Crypto::Constants::taproot_witness_version) => sub {
7885
my ($self, $params) = @_;
79-
return shift->taproot_tweaked_key(%$params);
86+
87+
$self = $self->get_taproot_tweaked_key(%$params)
88+
unless $self->taproot;
89+
90+
return $self->get_xonly_key;
8091
},
8192
};
8293

@@ -155,7 +166,7 @@ sub get_segwit_address
155166

156167
signature_for get_taproot_address => (
157168
method => Object,
158-
positional => [Maybe [ArrayRef], {default => undef}],
169+
positional => [Maybe [BitcoinScriptTree], {default => undef}],
159170
);
160171

161172
sub get_taproot_address
@@ -173,7 +184,7 @@ sub get_taproot_address
173184

174185
my $taproot_program = $self->witness_program(
175186
Bitcoin::Crypto::Constants::taproot_witness_version,
176-
defined $script_tree ? {tweak_suffix => taproot_merkle_root($script_tree)} : {}
187+
defined $script_tree ? {tweak_suffix => $script_tree->get_merkle_root} : {}
177188
);
178189

179190
return encode_segwit($self->network->segwit_hrp, $taproot_program->run->stack_serialized);

lib/Bitcoin/Crypto/PSBT/FieldType.pm

+1-6
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,7 @@ my %public_key_serializers = (
192192
return $value->to_serialized;
193193
},
194194
key_deserializer => sub {
195-
my $key = btc_pub->from_serialized(shift);
196-
if (!$key->has_key_instance) {
197-
die 'not a plain EC public key';
198-
}
199-
200-
return $key;
195+
return btc_pub->from_serialized(shift);
201196
},
202197
);
203198

lib/Bitcoin/Crypto/Role/Key.pm

+24-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ has param 'purpose' => (
2525
required => 0,
2626
);
2727

28+
has param 'taproot' => (
29+
isa => Bool,
30+
writer => 1,
31+
default => !!0,
32+
);
33+
2834
with qw(Bitcoin::Crypto::Role::Network);
2935

3036
requires qw(
@@ -51,6 +57,11 @@ sub _validate_key
5157
'private key is not valid'
5258
) unless ecc->verify_private_key(ensure_length $entropy, Bitcoin::Crypto::Constants::key_max_length);
5359
}
60+
else {
61+
Bitcoin::Crypto::Exception::KeyCreate->raise(
62+
'public key is not valid'
63+
) unless ecc->verify_public_key($entropy);
64+
}
5465
}
5566

5667
sub BUILD
@@ -127,7 +138,7 @@ sub raw_key
127138
}
128139
}
129140

130-
signature_for taproot_tweaked_key => (
141+
signature_for get_taproot_tweaked_key => (
131142
method => Object,
132143
named => [
133144
tweak_suffix => Maybe [ByteStr],
@@ -136,25 +147,33 @@ signature_for taproot_tweaked_key => (
136147
bless => !!0,
137148
);
138149

139-
sub taproot_tweaked_key
150+
sub get_taproot_tweaked_key
140151
{
141152
my ($self, $args) = @_;
142153

154+
my $new_key;
143155
if ($self->_is_private) {
144156
my $internal = $self->raw_key('private');
145157
my $internal_public = ecc->create_public_key($internal);
146158
$internal = ecc->negate_private_key($internal)
147159
unless has_even_y($internal_public);
148160

149161
my $tweak = tagged_hash('TapTweak', ecc->xonly_public_key($internal_public) . ($args->{tweak_suffix} // ''));
150-
return ecc->add_private_key($internal, $tweak);
162+
$new_key = ecc->add_private_key($internal, $tweak);
151163
}
152164
else {
153165
my $internal = $self->raw_key('public_xonly');
154166
my $tweak = tagged_hash('TapTweak', $internal . ($args->{tweak_suffix} // ''));
155-
my $combined = ecc->combine_public_keys(ecc->create_public_key($tweak), lift_x $internal);
156-
return ecc->xonly_public_key($combined);
167+
$new_key = ecc->combine_public_keys(ecc->create_public_key($tweak), lift_x $internal);
157168
}
169+
170+
my $pkg = ref $self;
171+
return $pkg->new(
172+
key_instance => $new_key,
173+
purpose => $self->purpose,
174+
network => $self->network,
175+
taproot => !!1,
176+
);
158177
}
159178

160179
1;

lib/Bitcoin/Crypto/Role/SignVerify.pm

+41-33
Original file line numberDiff line numberDiff line change
@@ -16,52 +16,60 @@ use Moo::Role;
1616

1717
requires qw(
1818
raw_key
19+
taproot
1920
_is_private
2021
);
2122

23+
my %algorithms = (
24+
default => {
25+
digest => \&hash256,
26+
signing_method => sub {
27+
my ($key, $digest) = @_;
28+
29+
return ecc->sign_digest($key->raw_key, $digest);
30+
},
31+
verification_method => sub {
32+
my ($key, $signature, $digest) = @_;
33+
34+
my $normalized = ecc->normalize_signature($signature);
35+
return !!0 if $normalized ne $signature;
36+
return ecc->verify_digest($key->raw_key('public'), $signature, $digest);
37+
},
38+
},
39+
schnorr => {
40+
digest => sub { tagged_hash('TapSighash', shift) },
41+
signing_method => sub {
42+
my ($key, $digest) = @_;
43+
44+
return ecc->sign_digest_schnorr($key->raw_key, $digest);
45+
},
46+
verification_method => sub {
47+
my ($key, $signature, $digest) = @_;
48+
49+
return ecc->verify_digest_schnorr($key->raw_key('public_xonly'), $signature, $digest);
50+
},
51+
},
52+
);
53+
2254
signature_for sign_message => (
2355
method => Object,
24-
head => [ByteStr],
25-
named => [
26-
algorithm => SignatureAlgorithm,
27-
{default => Bitcoin::Crypto::Constants::signing_algorithm_ecdsa},
28-
taproot_tweak_suffix => Maybe [ByteStr],
29-
{default => undef},
30-
],
31-
bless => !!0,
56+
positional => [ByteStr],
3257
);
3358

3459
sub sign_message
3560
{
36-
my ($self, $preimage, $args) = @_;
61+
my ($self, $preimage) = @_;
62+
my $algorithm = $self->taproot ? 'schnorr' : 'default';
3763

3864
Bitcoin::Crypto::Exception::Sign->raise(
3965
'cannot sign a message with a public key'
4066
) unless $self->_is_private;
4167

42-
my %algorithms = (
43-
(Bitcoin::Crypto::Constants::signing_algorithm_ecdsa) => {
44-
digest => \&hash256,
45-
signing_method => sub { ecc->sign_digest(@_) },
46-
raw_key => sub { $self->raw_key },
47-
},
48-
(Bitcoin::Crypto::Constants::signing_algorithm_schnorr) => {
49-
digest => sub { tagged_hash('TapSighash', shift) },
50-
signing_method => sub { ecc->sign_digest_schnorr(@_) },
51-
raw_key => sub {
52-
$self->taproot_tweaked_key(
53-
tweak_suffix => $args->{taproot_tweak_suffix}
54-
);
55-
},
56-
},
57-
);
58-
59-
my $key = $algorithms{$args->{algorithm}}{raw_key}->();
60-
my $digest = $algorithms{$args->{algorithm}}{digest}->($preimage);
68+
my $digest = $algorithms{$algorithm}{digest}->($preimage);
6169

6270
return Bitcoin::Crypto::Exception::Sign->trap_into(
6371
sub {
64-
return $algorithms{$args->{algorithm}}{signing_method}->($key, $digest);
72+
return $algorithms{$algorithm}{signing_method}->($self, $digest);
6573
}
6674
);
6775
}
@@ -94,13 +102,13 @@ signature_for verify_message => (
94102
sub verify_message
95103
{
96104
my ($self, $preimage, $signature) = @_;
97-
my $digest = hash256($preimage);
105+
my $algorithm = $self->taproot ? 'schnorr' : 'default';
106+
107+
my $digest = $algorithms{$algorithm}{digest}->($preimage);
98108

99109
return Bitcoin::Crypto::Exception::Verify->trap_into(
100110
sub {
101-
my $normalized = ecc->normalize_signature($signature);
102-
return !!0 if $normalized ne $signature;
103-
return ecc->verify_digest($self->raw_key('public'), $signature, $digest);
111+
return $algorithms{$algorithm}{verification_method}->($self, $signature, $digest);
104112
}
105113
);
106114
}

lib/Bitcoin/Crypto/Script/Common.pm

+8-8
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ sub _make_WSH
4242
->add('OP_EQUAL');
4343
}
4444

45-
#sub _make_P2TR
46-
#{
47-
# my ($class, $script, $pubkey) = @_;
48-
#
49-
# return $script
50-
# ->push($pubkey)
51-
# ->add('OP_CHECKSIG');
52-
#}
45+
sub _make_TR
46+
{
47+
my ($class, $script, $pubkey) = @_;
48+
49+
return $script
50+
->push($pubkey)
51+
->add('OP_CHECKSIG');
52+
}
5353

5454
sub _get_method
5555
{

lib/Bitcoin/Crypto/Script/Opcode.pm

+18-7
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ use Bitcoin::Crypto qw(btc_pub);
1717
use Bitcoin::Crypto::Constants;
1818
use Bitcoin::Crypto::Exception;
1919
use Bitcoin::Crypto::Types -types;
20-
use Bitcoin::Crypto::Util qw(hash160 hash256 get_public_key_compressed);
20+
use Bitcoin::Crypto::Helpers qw(ecc);
21+
use Bitcoin::Crypto::Util qw(hash160 hash256 get_public_key_compressed lift_x);
2122
use Bitcoin::Crypto::Transaction::Input;
2223

2324
# some private helpers for opcodes
@@ -740,15 +741,25 @@ my %opcodes = (
740741

741742
my $raw_pubkey = pop @$stack;
742743
my $sig = pop @$stack;
743-
my $hashtype = substr $sig, -1, 1, '';
744744

745-
my $digest = $runner->transaction->get_digest($runner->subscript, unpack 'C', $hashtype);
746-
my $pubkey = btc_pub->from_serialized($raw_pubkey);
745+
my $pubkey;
746+
my $hashtype;
747747

748-
script_error('SegWit validation requires compressed public key')
749-
if !$pubkey->compressed && $runner->transaction->is_native_segwit;
748+
if ($runner->transaction->is_taproot) {
749+
$hashtype = length $sig == 65 ? unpack('C', substr $sig, -1, 1, '') : undef;
750+
$pubkey = btc_pub->from_serialized(lift_x $raw_pubkey);
751+
$pubkey->set_taproot(!!1);
752+
}
753+
else {
754+
$hashtype = unpack 'C', substr $sig, -1, 1, '';
755+
$pubkey = btc_pub->from_serialized($raw_pubkey);
756+
757+
script_error('SegWit validation requires compressed public key')
758+
if !$pubkey->compressed && $runner->transaction->is_native_segwit;
759+
}
750760

751-
my $result = $pubkey->verify_message($digest, $sig);
761+
my $preimage = $runner->transaction->get_digest($runner->subscript, $hashtype);
762+
my $result = $pubkey->verify_message($preimage, $sig);
752763
push @$stack, $runner->from_bool($result);
753764
},
754765
},

0 commit comments

Comments
 (0)