Skip to content

Commit 8eb39fb

Browse files
committed
Implement lift_x and has_even_y
1 parent 435cab9 commit 8eb39fb

File tree

6 files changed

+77
-9
lines changed

6 files changed

+77
-9
lines changed

Changes

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Revision history for Perl extension Bitcoin::Crypto.
55
- Bitcoin::Crypto::Util::merkle_root function
66
- Bitcoin::Crypto::Util::taproot_merkle_root function
77
- Bitcoin::Crypto::Util::tagged_hash function
8+
- Bitcoin::Crypto::Util::lift_x function
9+
- Bitcoin::Crypto::Util::has_even_y function
810

911
3.001 Tue Sep 24, 2024
1012
[Changes and fixes]

cpanfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ requires 'Mooish::AttributeBuilder' => '1.001';
88
requires 'Type::Tiny' => '2';
99
requires 'List::Util' => '1.33';
1010
requires 'CryptX' => '0.074';
11-
requires 'Bitcoin::Secp256k1' => '0.004';
11+
requires 'Bitcoin::Secp256k1' => '0.008';
1212
requires 'Bitcoin::BIP39' => '0.002';
1313
requires 'namespace::clean' => '0.27';
1414
requires 'Try::Tiny' => 0;

lib/Bitcoin/Crypto/Role/Key.pm

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use Types::Common -sigs, -types;
88

99
use Bitcoin::Crypto::Types -types;
1010
use Bitcoin::Crypto::Constants;
11-
use Bitcoin::Crypto::Util qw(get_key_type tagged_hash);
11+
use Bitcoin::Crypto::Util qw(get_key_type tagged_hash lift_x has_even_y);
1212
use Bitcoin::Crypto::Helpers qw(ensure_length ecc);
1313
use Bitcoin::Crypto::Exception;
1414

@@ -144,15 +144,15 @@ sub taproot_tweaked_key
144144
my $internal = $self->raw_key('private');
145145
my $internal_public = ecc->create_public_key($internal);
146146
$internal = ecc->negate_private_key($internal)
147-
if substr($internal_public, 0, 1) eq "\x03";
147+
unless has_even_y($internal_public);
148148

149149
my $tweak = tagged_hash('TapTweak', ecc->xonly_public_key($internal_public) . ($args->{tweak_suffix} // ''));
150150
return ecc->add_private_key($internal, $tweak);
151151
}
152152
else {
153153
my $internal = $self->raw_key('public_xonly');
154154
my $tweak = tagged_hash('TapTweak', $internal . ($args->{tweak_suffix} // ''));
155-
my $combined = ecc->combine_public_keys(ecc->create_public_key($tweak), "\x02" . $internal);
155+
my $combined = ecc->combine_public_keys(ecc->create_public_key($tweak), lift_x $internal);
156156
return ecc->xonly_public_key($combined);
157157
}
158158
}

lib/Bitcoin/Crypto/Util.pm

+54-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use Try::Tiny;
1414
use Scalar::Util qw(blessed);
1515
use Types::Common -sigs, -types;
1616

17-
use Bitcoin::Crypto::Helpers qw(parse_formatdesc);
17+
use Bitcoin::Crypto::Helpers qw(parse_formatdesc ecc);
1818
use Bitcoin::Crypto::Constants;
1919
use Bitcoin::Crypto::Types -types;
2020
use Bitcoin::Crypto::Exception;
@@ -38,6 +38,8 @@ our @EXPORT_OK = qw(
3838
merkle_root
3939
taproot_merkle_root
4040
tagged_hash
41+
lift_x
42+
has_even_y
4143
);
4244

4345
our %EXPORT_TAGS = (all => [@EXPORT_OK]);
@@ -490,6 +492,38 @@ sub tagged_hash
490492
return sha256($partial . $partial . $message);
491493
}
492494

495+
signature_for lift_x => (
496+
positional => [ByteStr],
497+
);
498+
499+
sub lift_x
500+
{
501+
my ($x) = @_;
502+
503+
my $key = "\x02" . $x;
504+
Bitcoin::Crypto::Exception::KeyCreate->raise(
505+
'invalid xonly public key'
506+
) unless ecc->verify_public_key($key);
507+
508+
return $key;
509+
}
510+
511+
signature_for has_even_y => (
512+
positional => [ByteStr],
513+
);
514+
515+
sub has_even_y
516+
{
517+
my ($key) = @_;
518+
519+
return Bitcoin::Crypto::Exception->trap_into(
520+
sub {
521+
$key = ecc->compress_public_key($key);
522+
return substr($key, 0, 1) eq "\x02";
523+
}
524+
);
525+
}
526+
493527
1;
494528

495529
__END__
@@ -518,6 +552,8 @@ Bitcoin::Crypto::Util - General Bitcoin utilities
518552
merkle_root
519553
taproot_merkle_root
520554
tagged_hash
555+
lift_x
556+
has_even_y
521557
);
522558
523559
=head1 DESCRIPTION
@@ -779,6 +815,23 @@ strings for C<$tag>, but can't detect whether it got it or not. This will only
779815
become a problem if you use non-ascii tag. If there's a possibility of
780816
non-ascii, always use utf8 and set binmodes to get decoded (wide) characters.
781817
818+
=head2 lift_x
819+
820+
$public_key = lift_x $xonly_public_key;
821+
822+
This implements C<lift_x> function defined in BIP340. Returns a compressed ECC
823+
public key with even Y coordinate as a bytestring for a given 32-byte bytestring
824+
C<$xonly_public_key>. Throws an exception if the result is not a valid public
825+
key.
826+
827+
=head2 has_even_y
828+
829+
$even_y = has_even_y $public_key;
830+
831+
This implements C<has_even_y> function defined in BIP340. Returns a boolean for
832+
a given serialized C<$public_key> - a bytestring. Throws an exception if the
833+
argument is not a valid public key.
834+
782835
=head1 SEE ALSO
783836
784837
L<https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki>

t/Taproot/BIP341-script-pub-key.t

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use Test2::V0;
22
use Bitcoin::Crypto qw(btc_pub);
3-
use Bitcoin::Crypto::Util qw(to_format);
3+
use Bitcoin::Crypto::Util qw(to_format lift_x);
44

55
# Data from:
66
# https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#test-vectors
@@ -164,8 +164,7 @@ foreach my $case_ind (0 .. $#cases) {
164164
subtest "should pass case index $case_ind" => sub {
165165
my $case = $cases[$case_ind];
166166

167-
# TODO: not possible to import xonly pubkey at the moment
168-
my $key = btc_pub->from_serialized([hex => "02" . $case->{given}{internal_pubkey}]);
167+
my $key = btc_pub->from_serialized(lift_x [hex => $case->{given}{internal_pubkey}]);
169168
is $key->get_taproot_address($case->{given}{script_tree}), $case->{expected}{bip350_address}, 'address ok';
170169

171170
# TODO: control blocks

t/Util.t

+15-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use Crypt::Digest::SHA256 qw(sha256);
44
use Encode qw(encode);
55

66
use Bitcoin::Crypto::Util qw(:all);
7-
use Bitcoin::Crypto::Helpers; # loads Math::BigInt
7+
use Bitcoin::Crypto::Helpers qw(ecc); # loads Math::BigInt
88
use Bitcoin::Crypto::Key::ExtPrivate;
99

1010
subtest 'testing mnemonic_to_seed' => sub {
@@ -323,5 +323,19 @@ subtest 'testing tagged_hash' => sub {
323323
);
324324
};
325325

326+
subtest 'testing lift_x' => sub {
327+
my $key = '151bb80b24b79955e9e7b50614f354b52bb5362f1147e68870a0e992cdb9eaa4';
328+
my $negated = ecc->negate_public_key(from_format [hex => '03' . $key]);
329+
330+
is lift_x([hex => $key]), $negated, 'result ok';
331+
};
332+
333+
subtest 'has_even_y' => sub {
334+
my $key = '151bb80b24b79955e9e7b50614f354b52bb5362f1147e68870a0e992cdb9eaa4';
335+
336+
ok has_even_y([hex => '02' . $key]), 'even key ok';
337+
ok !has_even_y([hex => '03' . $key]), 'odd key ok';
338+
};
339+
326340
done_testing;
327341

0 commit comments

Comments
 (0)