Skip to content

Commit 8e648ee

Browse files
committed
taproot address 1st draft done but address is different from core
1 parent 5fca30f commit 8e648ee

6 files changed

Lines changed: 92 additions & 31 deletions

File tree

TODO

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
12
******
23
0.5.10 is current working copy -- HAVEN'T TAGGED IT YET, will refactor PrivateKey first
34

45

6+
CLEAN UP
7+
-- use sha256 rather than hashlib.sha256
8+
-- will use ecpy lib for ECSchorr (point mul, signing, etc) - use ecpy for ECDSA to remove ecdsa lib
9+
-- internally everything should be handled in bytes -- and converted to hex when displayed
10+
11+
512
IMPROVE
613
- why does bitcoinlib.services.service worked for connecting and proxy didn't for some students? (services probably has some redundancy code)
714

bitcoinutils/keys.py

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
from ecdsa import SigningKey, VerifyingKey, SECP256k1, ellipticcurve, numbertheory
2020
from ecdsa.util import sigencode_string, sigdecode_string, sigencode_der
2121
from sympy.ntheory import sqrt_mod
22+
from ecpy.curves import Curve, Point
2223

2324
from bitcoinutils.constants import NETWORK_WIF_PREFIXES, \
2425
NETWORK_P2PKH_PREFIXES, NETWORK_P2SH_PREFIXES, SIGHASH_ALL, \
2526
P2PKH_ADDRESS, P2SH_ADDRESS, P2WPKH_ADDRESS_V0, P2WSH_ADDRESS_V0, \
2627
P2TR_ADDRESS_V1, NETWORK_SEGWIT_PREFIXES
2728
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
2931
from bitcoinutils.ripemd160 import ripemd160
3032
import bitcoinutils.script
3133
import bitcoinutils.bech32
@@ -59,6 +61,10 @@ class EcdsaParams:
5961
_G = ellipticcurve.Point( _curve, _Gx, _Gy, _order )
6062

6163

64+
class SchnorrParams:
65+
pass
66+
67+
6268

6369
class PrivateKey:
6470
"""Represents an ECDSA private key.
@@ -370,7 +376,6 @@ def _sign_taproot_input(self, tx_digest, sighash=SIGHASH_ALL):
370376
pass
371377

372378
# TODO AAAAA IS PUBLIC KEY size is 32!!!! -- get_public_key needs to be modified appr.
373-
374379
def get_public_key(self):
375380
"""Returns the corresponding PublicKey"""
376381

@@ -483,30 +488,31 @@ def to_hex(self, compressed=True, taproot=False):
483488

484489
key_hex = hexlify(self.key.to_string())
485490

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
497492
#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]
504496
# else:
505-
# key_str = key_hex[:64]
497+
# key_str = b'03' + key_hex[:64]
506498
#else:
507499
# # uncompressed starts with 04
508500
# key_str = b'04' + key_hex
509501

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+
510516
return key_str.decode('utf-8')
511517

512518

@@ -657,16 +663,32 @@ def get_segwit_address(self):
657663
return P2wpkhAddress(witness_program=addr_string_hex)
658664

659665

660-
def get_taproot_address(self):
666+
def get_taproot_address(self, tagged=True):
661667
"""Returns the corresponding P2TR address
662668
663669
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.
665671
"""
666672

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+
670692
return P2trAddress(witness_program=pubkey)
671693

672694

@@ -1065,7 +1087,11 @@ def to_string(self):
10651087
"""
10661088

10671089
# 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') )
10691095
witness_int_array = memoryview(hash_bytes).tolist()
10701096

10711097
return bitcoinutils.bech32.encode(NETWORK_SEGWIT_PREFIXES[get_network()],

bitcoinutils/utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
# propagated, or distributed except according to the terms contained in the
1010
# LICENSE file.
1111

12+
from hashlib import sha256
1213
from binascii import hexlify, unhexlify
1314
from bitcoinutils.constants import SATOSHIS_PER_BITCOIN
1415

@@ -83,6 +84,7 @@ def vi_to_int(byteint):
8384
return int.from_bytes(byteint[1:1+size][::-1], 'big'), size + 1
8485

8586

87+
# TODO name hex_to_bytes ??
8688
def to_bytes(string, unhexlify=True):
8789
'''
8890
Converts a hex string to bytes
@@ -131,3 +133,25 @@ def add_magic_prefix(message):
131133
return message_magic
132134

133135

136+
def tagged_hash(tag, data):
137+
'''
138+
Tagged hashes ensure that hashes used in one context can not be used in another.
139+
It is used extensively in Taproot
140+
141+
A tagged hash is: SHA256( SHA256("TapTweak") ||
142+
SHA256("TapTweak") ||
143+
data
144+
)
145+
Returns hashlib object (can then use .digest() or hexdigest())
146+
'''
147+
148+
tag_digest = sha256(tag).digest()
149+
return sha256( tag_digest + tag_digest + data )
150+
151+
152+
def hex_str_to_int(hex_str):
153+
'''
154+
Converts a string hexadecimal to a number (starting with 0x)
155+
'''
156+
return int(hex_str, base=16)
157+

examples/keys_taproot_addresses.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@
99
# modified, propagated, or distributed except according to the terms contained
1010
# in the LICENSE file.
1111

12-
1312
from bitcoinutils.setup import setup
1413
from bitcoinutils.script import Script
1514
from bitcoinutils.keys import P2trAddress, PrivateKey, PublicKey
1615

16+
1717
def main():
1818
# always remember to setup the network
1919
setup('testnet')
2020

2121
# could also instantiate from existing WIF key
22-
priv = PrivateKey.from_wif('cVHcBHKydbbdqQtKWm2AdgF7LGrQdHzxa3yaABPa16wb5ZTqc3Np')
22+
#priv = PrivateKey.from_wif('cQoDmhFk8Ei8hmoyDvEnLN4wTWaYPLCvNKBwYkj8t1yaQTmyfQfg')
23+
priv = PrivateKey.from_wif('cSfna7riKJdNU7skpRUx17WYANNsyHTA2FmuzLpFzpp37xpytgob')
2324

2425
# compressed is the default
2526
print("\nPrivate key WIF:", priv.to_wif())
@@ -35,15 +36,16 @@ def main():
3536

3637
# print the address and hash - default is compressed address
3738
print("Native Address:", address.to_string())
38-
taproot_pk = address.to_hash()
39+
taproot_pk = address.to_witness_program()
3940
print("Taproot public key:", taproot_pk)
4041
print("Segwit Version:", address.get_type())
4142

4243
# test to_string
43-
addr2 = P2trAddress.from_hash(taproot_pk)
44+
addr2 = P2trAddress.from_witness_program(taproot_pk)
4445
print("Created P2trAddress from public key and calculate address:")
4546
print("Native Address:", addr2.to_string())
4647

48+
assert(address.to_string() == 'tb1p3yfmr6sc448t2sya7f8n49y9h3gtg26fygg4jtpaem6pvptvc7jq423gxd')
4749

4850
if __name__ == "__main__":
4951
main()

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ base58check>=1.0.2,<2.0
22
ecdsa==0.17.0
33
sympy>=1.2,<2.0
44
python-bitcoinrpc>=1.0,<2.0
5+
ecpy==1.2.5

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
'base58check>=1.0.2,<2.0',
2424
'ecdsa==0.17.0',
2525
'sympy>=1.2,<2.0',
26-
'python-bitcoinrpc>=1.0,<2.0'
26+
'python-bitcoinrpc>=1.0,<2.0',
27+
'ECpy==1.2.5'
2728
],
2829
packages=['bitcoinutils'],
2930
#package_data={

0 commit comments

Comments
 (0)