|
19 | 19 | from ecdsa import SigningKey, VerifyingKey, SECP256k1, ellipticcurve, numbertheory |
20 | 20 | from ecdsa.util import sigencode_string, sigdecode_string, sigencode_der |
21 | 21 | from sympy.ntheory import sqrt_mod |
| 22 | +from ecpy.curves import Curve, Point |
22 | 23 |
|
23 | 24 | from bitcoinutils.constants import NETWORK_WIF_PREFIXES, \ |
24 | 25 | NETWORK_P2PKH_PREFIXES, NETWORK_P2SH_PREFIXES, SIGHASH_ALL, \ |
25 | 26 | P2PKH_ADDRESS, P2SH_ADDRESS, P2WPKH_ADDRESS_V0, P2WSH_ADDRESS_V0, \ |
26 | 27 | P2TR_ADDRESS_V1, NETWORK_SEGWIT_PREFIXES |
27 | 28 | from bitcoinutils.setup import get_network |
28 | | -from bitcoinutils.utils import bytes_from_int, encode_varint, add_magic_prefix |
| 29 | +from bitcoinutils.utils import bytes_from_int, encode_varint, add_magic_prefix, \ |
| 30 | + tagged_hash, hex_str_to_int |
29 | 31 | from bitcoinutils.ripemd160 import ripemd160 |
30 | 32 | import bitcoinutils.script |
31 | 33 | import bitcoinutils.bech32 |
@@ -59,6 +61,10 @@ class EcdsaParams: |
59 | 61 | _G = ellipticcurve.Point( _curve, _Gx, _Gy, _order ) |
60 | 62 |
|
61 | 63 |
|
| 64 | +class SchnorrParams: |
| 65 | + pass |
| 66 | + |
| 67 | + |
62 | 68 |
|
63 | 69 | class PrivateKey: |
64 | 70 | """Represents an ECDSA private key. |
@@ -370,7 +376,6 @@ def _sign_taproot_input(self, tx_digest, sighash=SIGHASH_ALL): |
370 | 376 | pass |
371 | 377 |
|
372 | 378 | # TODO AAAAA IS PUBLIC KEY size is 32!!!! -- get_public_key needs to be modified appr. |
373 | | - |
374 | 379 | def get_public_key(self): |
375 | 380 | """Returns the corresponding PublicKey""" |
376 | 381 |
|
@@ -483,30 +488,31 @@ def to_hex(self, compressed=True, taproot=False): |
483 | 488 |
|
484 | 489 | key_hex = hexlify(self.key.to_string()) |
485 | 490 |
|
486 | | - if compressed: |
487 | | - # check if y is even or odd (02 even, 03 odd) |
488 | | - if int(key_hex[-2:], 16) % 2 == 0: |
489 | | - key_str = b'02' + key_hex[:64] |
490 | | - else: |
491 | | - key_str = b'03' + key_hex[:64] |
492 | | - else: |
493 | | - # uncompressed starts with 04 |
494 | | - key_str = b'04' + key_hex |
495 | | - |
496 | | - # this was added for taproot but will be refactored! |
| 491 | + # OLD w/o taproot |
497 | 492 | #if compressed: |
498 | | - # if not taproot: |
499 | | - # # check if y is even or odd (02 even, 03 odd) |
500 | | - # if int(key_hex[-2:], 16) % 2 == 0: |
501 | | - # key_str = b'02' + key_hex[:64] |
502 | | - # else: |
503 | | - # key_str = b'03' + key_hex[:64] |
| 493 | + # # check if y is even or odd (02 even, 03 odd) |
| 494 | + # if int(key_hex[-2:], 16) % 2 == 0: |
| 495 | + # key_str = b'02' + key_hex[:64] |
504 | 496 | # else: |
505 | | - # key_str = key_hex[:64] |
| 497 | + # key_str = b'03' + key_hex[:64] |
506 | 498 | #else: |
507 | 499 | # # uncompressed starts with 04 |
508 | 500 | # key_str = b'04' + key_hex |
509 | 501 |
|
| 502 | + # this was added for taproot but will be refactored! |
| 503 | + if compressed: |
| 504 | + if not taproot: |
| 505 | + # check if y is even or odd (02 even, 03 odd) |
| 506 | + if int(key_hex[-2:], 16) % 2 == 0: |
| 507 | + key_str = b'02' + key_hex[:64] |
| 508 | + else: |
| 509 | + key_str = b'03' + key_hex[:64] |
| 510 | + else: |
| 511 | + key_str = key_hex[:64] |
| 512 | + else: |
| 513 | + # uncompressed starts with 04 |
| 514 | + key_str = b'04' + key_hex |
| 515 | + |
510 | 516 | return key_str.decode('utf-8') |
511 | 517 |
|
512 | 518 |
|
@@ -657,16 +663,32 @@ def get_segwit_address(self): |
657 | 663 | return P2wpkhAddress(witness_program=addr_string_hex) |
658 | 664 |
|
659 | 665 |
|
660 | | - def get_taproot_address(self): |
| 666 | + def get_taproot_address(self, tagged=True): |
661 | 667 | """Returns the corresponding P2TR address |
662 | 668 |
|
663 | 669 | Only compressed is allowed. Taproot does not hash the public key |
664 | | - so we store it directly. |
| 670 | + so we store it directly. By default tagged_hashes are used. |
665 | 671 | """ |
666 | 672 |
|
667 | | - # Note that in taproot it is not really the hash.. just the compressed |
668 | | - # public key without a prefix! |
669 | | - pubkey = self.to_hex(compressed=True, taproot=True) |
| 673 | + # Tweak public key (BIP340) |
| 674 | + # https://bitcoin.stackexchange.com/a/116391/31844 |
| 675 | + if tagged: |
| 676 | + th = tagged_hash(b'TapTweak', self.key.to_string()) |
| 677 | + th_as_int = hex_str_to_int( th.hexdigest() ) |
| 678 | + |
| 679 | + # compute the tweaked public key Q = P + (t * G) |
| 680 | + curve = Curve.get_curve('secp256k1') |
| 681 | + |
| 682 | + # convert public key bytes to Point |
| 683 | + x = hex_str_to_int( self.key.to_string()[:32].hex() ) |
| 684 | + y = hex_str_to_int( self.key.to_string()[32:].hex() ) |
| 685 | + P = Point(x, y, curve) |
| 686 | + |
| 687 | + Q = P + (th_as_int * curve.generator) |
| 688 | + pubkey = hex(Q.x)[2:] + hex(Q.y)[2:] |
| 689 | + else: |
| 690 | + pubkey = self.to_hex(compressed=True, taproot=True) |
| 691 | + |
670 | 692 | return P2trAddress(witness_program=pubkey) |
671 | 693 |
|
672 | 694 |
|
@@ -1065,7 +1087,11 @@ def to_string(self): |
1065 | 1087 | """ |
1066 | 1088 |
|
1067 | 1089 | # convert hex string witness program to int array (required by bech32 lib) |
1068 | | - hash_bytes = unhexlify( self.witness_program.encode('utf-8') ) |
| 1090 | + # if taproot only the x coordinate is required |
| 1091 | + if self.segwit_num_version == 1: |
| 1092 | + hash_bytes = unhexlify( self.witness_program[:64].encode('utf-8') ) |
| 1093 | + else: |
| 1094 | + hash_bytes = unhexlify( self.witness_program.encode('utf-8') ) |
1069 | 1095 | witness_int_array = memoryview(hash_bytes).tolist() |
1070 | 1096 |
|
1071 | 1097 | return bitcoinutils.bech32.encode(NETWORK_SEGWIT_PREFIXES[get_network()], |
|
0 commit comments