From 1c1139f1423a746b40d01ee0ef9c13f02b33b2b7 Mon Sep 17 00:00:00 2001 From: bbrtj Date: Thu, 26 Sep 2024 18:33:13 +0200 Subject: [PATCH] Implement taproot address generation --- cpanfile | 2 +- lib/Bitcoin/Crypto/Key/Public.pm | 9 +++++++-- t/Taproot/BIP86.t | 14 ++++++++++++++ t/Util.t | 6 +++++- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/cpanfile b/cpanfile index dcf00d5..2108057 100644 --- a/cpanfile +++ b/cpanfile @@ -8,7 +8,7 @@ requires 'Mooish::AttributeBuilder' => '1.001'; requires 'Type::Tiny' => '2'; requires 'List::Util' => '1.33'; requires 'CryptX' => '0.074'; -requires 'Bitcoin::Secp256k1' => 0; +requires 'Bitcoin::Secp256k1' => '0.003'; requires 'Bitcoin::BIP39' => '0.002'; requires 'namespace::clean' => '0.27'; requires 'Try::Tiny' => 0; diff --git a/lib/Bitcoin/Crypto/Key/Public.pm b/lib/Bitcoin/Crypto/Key/Public.pm index 8b06d2c..267c3b1 100644 --- a/lib/Bitcoin/Crypto/Key/Public.pm +++ b/lib/Bitcoin/Crypto/Key/Public.pm @@ -13,7 +13,8 @@ use Bitcoin::Crypto::Base58 qw(encode_base58check); use Bitcoin::Crypto::Bech32 qw(encode_segwit); use Bitcoin::Crypto::Types -types; use Bitcoin::Crypto::Constants; -use Bitcoin::Crypto::Util qw(hash160 get_public_key_compressed); +use Bitcoin::Crypto::Util qw(hash160 get_public_key_compressed tagged_hash); +use Bitcoin::Crypto::Helpers qw(ecc); use namespace::clean; @@ -117,7 +118,11 @@ sub witness_program shift->get_hash; }, +Bitcoin::Crypto::Constants::taproot_witness_version => sub { - shift->raw_key('public_taproot'); + my $self = shift; + my $internal = $self->raw_key('public_taproot'); + my $tweaked = tagged_hash($internal, 'TapTweak'); + my $combined = ecc->combine_public_keys(ecc->create_public_key($tweaked), "\02" . $internal); + return substr $combined, 1; }, }; diff --git a/t/Taproot/BIP86.t b/t/Taproot/BIP86.t index 70b7b91..b2dd253 100644 --- a/t/Taproot/BIP86.t +++ b/t/Taproot/BIP86.t @@ -6,6 +6,19 @@ use Bitcoin::Crypto::Util qw(to_format); # https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki#test-vectors my @cases = ( + { + bip44 => { + purpose => Bitcoin::Crypto::Constants::bip44_taproot_purpose, + }, + xprv => + 'xprvA449goEeU9okwCzzZaxiy475EQGQzBkc65su82nXEvcwzfSskb2hAt2WymrjyRL6kpbVTGL3cKtp9herYXSjjQ1j4stsXXiRF7kXkCacK3T', + xpub => + 'xpub6H3W6JmYJXN49h5TfcVjLC3onS6uPeUTTJoVvRC8oG9vsTn2J8LwigLzq5tHbrwAzH9DGo6ThGUdWsqce8dGfwHVBxSbixjDADGGdzF7t2B', + internal_key => 'cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115', + output_key => 'a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c', + scriptPubKey => '5120a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c', + address => 'bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr', + }, { bip44 => { purpose => Bitcoin::Crypto::Constants::bip44_taproot_purpose, @@ -49,6 +62,7 @@ foreach my $case_ind (0 .. $#cases) { is to_format [base58 => $key->get_public_key->to_serialized], $case->{xpub}, 'extended public key ok'; $key = $key->get_basic_key; + #is to_format [hex => $key->get_public_key->to_serialized], $case->{internal_key}, 'private key ok'; is $key->get_public_key->get_address, $case->{address}, 'address ok'; }; diff --git a/t/Util.t b/t/Util.t index 59f7d6c..f681b7f 100644 --- a/t/Util.t +++ b/t/Util.t @@ -296,7 +296,11 @@ subtest 'testing tagged_hash' => sub { my $tag = 'ąść'; #is(tagged_hash($data, $tag), sha256(sha256(encode 'UTF-8', $tag) . sha256(encode 'UTF-8', $tag) . $data), 'tagged_hash ok'); - is(tagged_hash($data, $tag), sha256(sha256(encode 'UTF-8', $tag) . sha256(encode 'UTF-8', $tag) . $data), 'tagged_hash ok'); + is( + tagged_hash($data, $tag), + sha256(sha256(encode 'UTF-8', $tag) . sha256(encode 'UTF-8', $tag) . $data), + 'tagged_hash ok' + ); }; done_testing;