2
2
import hmac
3
3
import secrets
4
4
5
+ from buidl .hash import hash_taptweak
5
6
from buidl .helper import (
6
7
big_endian_to_int ,
7
8
encode_base58_checksum ,
@@ -86,6 +87,12 @@ def __add__(self, scalar):
86
87
raise RuntimeError ("libsecp256k1 serialize error" )
87
88
return self .__class__ (usec = bytes (serialized ))
88
89
90
+ def even_point (self ):
91
+ if self .parity :
92
+ return - 1 * self
93
+ else :
94
+ return self
95
+
89
96
def verify (self , z , sig ):
90
97
msg = int_to_big_endian (z , 32 )
91
98
sig_data = sig .cdata ()
@@ -134,8 +141,8 @@ def sec(self, compressed=True):
134
141
self .usec = bytes (ffi .buffer (serialized , 65 ))
135
142
return self .usec
136
143
137
- def bip340 (self ):
138
- # returns the binary version of BIP340 pubkey
144
+ def xonly (self ):
145
+ # returns the binary version of XONLY pubkey
139
146
xonly_key = ffi .new ("secp256k1_xonly_pubkey *" )
140
147
if not lib .secp256k1_xonly_pubkey_from_pubkey (
141
148
GLOBAL_CTX , xonly_key , ffi .NULL , self .c
@@ -146,6 +153,24 @@ def bip340(self):
146
153
raise RuntimeError ("libsecp256k1 xonly serialize error" )
147
154
return bytes (ffi .buffer (output32 , 32 ))
148
155
156
+ def tweak (self , merkle_root = b"" ):
157
+ """returns the tweak for use in p2tr"""
158
+ # take the hash_taptweak of the xonly and the merkle root
159
+ tweak = hash_taptweak (self .xonly () + merkle_root )
160
+ return tweak
161
+
162
+ def tweaked_key (self , merkle_root = b"" , tweak = None ):
163
+ """Creates the tweaked external key for a particular merkle root/tweak."""
164
+ # Get the tweak with the merkle root
165
+ if tweak is None :
166
+ tweak = self .tweak (merkle_root )
167
+ # t is the tweak interpreted as a big endian integer
168
+ t = big_endian_to_int (tweak )
169
+ # Q = P + tG
170
+ external_key = self .even_point () + t
171
+ # return the external key
172
+ return external_key
173
+
149
174
def hash160 (self , compressed = True ):
150
175
# get the sec
151
176
sec = self .sec (compressed )
@@ -172,12 +197,13 @@ def p2sh_p2wpkh_redeem_script(self):
172
197
"""Returns the RedeemScript for a p2sh-p2wpkh redemption"""
173
198
return self .p2wpkh_script ().redeem_script ()
174
199
175
- def p2tr_script (self ):
200
+ def p2tr_script (self , merkle_root = b"" , tweak = None ):
176
201
"""Returns the p2tr Script object"""
202
+ external_pubkey = self .tweaked_key (merkle_root , tweak )
177
203
# avoid circular dependency
178
- from buidl .taproot import TapRoot
204
+ from buidl .script import P2TRScriptPubKey
179
205
180
- return TapRoot ( self ). script_pubkey ( )
206
+ return P2TRScriptPubKey ( external_pubkey )
181
207
182
208
def p2pk_tap_script (self ):
183
209
"""Returns the p2tr Script object"""
@@ -198,9 +224,9 @@ def p2sh_p2wpkh_address(self, network="mainnet"):
198
224
"""Returns the p2sh-p2wpkh base58 address string"""
199
225
return self .p2wpkh_script ().p2sh_address (network )
200
226
201
- def p2tr_address (self , network = "mainnet" ):
227
+ def p2tr_address (self , merkle_root = b"" , tweak = None , network = "mainnet" ):
202
228
"""Returns the p2tr bech32m address string"""
203
- return self .p2tr_script ().address (network )
229
+ return self .p2tr_script (merkle_root , tweak ).address (network )
204
230
205
231
def verify_message (self , message , sig ):
206
232
"""Verify a message in the form of bytes. Assumes that the z
@@ -214,9 +240,9 @@ def verify_message(self, message, sig):
214
240
215
241
@classmethod
216
242
def parse (cls , binary ):
217
- """returns a Point object from a SEC or BIP340 pubkey"""
243
+ """returns a Point object from a SEC or XONLY pubkey"""
218
244
if len (binary ) == 32 :
219
- return cls .parse_bip340 (binary )
245
+ return cls .parse_xonly (binary )
220
246
elif len (binary ) in (33 , 65 ):
221
247
return cls .parse_sec (binary )
222
248
else :
@@ -231,7 +257,7 @@ def parse_sec(cls, sec_bin):
231
257
return cls (csec = sec_bin )
232
258
233
259
@classmethod
234
- def parse_bip340 (cls , binary ):
260
+ def parse_xonly (cls , binary ):
235
261
sec_bin = b"\x02 " + binary
236
262
return cls (csec = sec_bin )
237
263
@@ -348,6 +374,12 @@ def __init__(self, secret, network="mainnet", compressed=True):
348
374
def hex (self ):
349
375
return "{:x}" .format (self .secret ).zfill (64 )
350
376
377
+ def even_secret (self ):
378
+ if self .point .parity :
379
+ return N - self .secret
380
+ else :
381
+ return self .secret
382
+
351
383
def sign (self , z ):
352
384
# per libsecp256k1 documentation, this helps against side-channel attacks
353
385
if not lib .secp256k1_context_randomize (
@@ -435,12 +467,15 @@ def wif(self, compressed=True):
435
467
# encode_base58_checksum the whole thing
436
468
return encode_base58_checksum (prefix + secret_bytes + suffix )
437
469
438
- def tweaked (self , tweak ):
439
- if self .point .parity :
440
- s = N - self .secret
441
- else :
442
- s = self .secret
443
- new_secret = (s + tweak ) % N
470
+ def tweaked_key (self , merkle_root = b"" ):
471
+ e = self .even_secret ()
472
+ # get the tweak from the point's tweak method
473
+ tweak = self .point .tweak (merkle_root )
474
+ # t is the tweak interpreted as big endian
475
+ t = big_endian_to_int (tweak )
476
+ # new secret is the secret plus t (make sure to mod by N)
477
+ new_secret = (e + t ) % N
478
+ # create a new instance of this class using self.__class__
444
479
return self .__class__ (new_secret , network = self .network )
445
480
446
481
@classmethod
0 commit comments