Skip to content

Commit c0b7d57

Browse files
authored
Change taproot functions (buidl-bitcoin#153)
* changed name from bip340 to xonly * changing how taproot is done * fixed formatting
1 parent 67dc0de commit c0b7d57

24 files changed

+489
-427
lines changed

buidl/blinding.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def secure_secret_path(depth=4):
1919
8: 248
2020
9: 279
2121
"""
22-
if type(depth) != int:
22+
if not isinstance(depth, int):
2323
raise ValueError(f"depth must be an int: {depth}")
2424
if depth >= 32:
2525
raise ValueError(

buidl/cecc.py

+51-16
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import hmac
33
import secrets
44

5+
from buidl.hash import hash_taptweak
56
from buidl.helper import (
67
big_endian_to_int,
78
encode_base58_checksum,
@@ -86,6 +87,12 @@ def __add__(self, scalar):
8687
raise RuntimeError("libsecp256k1 serialize error")
8788
return self.__class__(usec=bytes(serialized))
8889

90+
def even_point(self):
91+
if self.parity:
92+
return -1 * self
93+
else:
94+
return self
95+
8996
def verify(self, z, sig):
9097
msg = int_to_big_endian(z, 32)
9198
sig_data = sig.cdata()
@@ -134,8 +141,8 @@ def sec(self, compressed=True):
134141
self.usec = bytes(ffi.buffer(serialized, 65))
135142
return self.usec
136143

137-
def bip340(self):
138-
# returns the binary version of BIP340 pubkey
144+
def xonly(self):
145+
# returns the binary version of XONLY pubkey
139146
xonly_key = ffi.new("secp256k1_xonly_pubkey *")
140147
if not lib.secp256k1_xonly_pubkey_from_pubkey(
141148
GLOBAL_CTX, xonly_key, ffi.NULL, self.c
@@ -146,6 +153,24 @@ def bip340(self):
146153
raise RuntimeError("libsecp256k1 xonly serialize error")
147154
return bytes(ffi.buffer(output32, 32))
148155

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+
149174
def hash160(self, compressed=True):
150175
# get the sec
151176
sec = self.sec(compressed)
@@ -172,12 +197,13 @@ def p2sh_p2wpkh_redeem_script(self):
172197
"""Returns the RedeemScript for a p2sh-p2wpkh redemption"""
173198
return self.p2wpkh_script().redeem_script()
174199

175-
def p2tr_script(self):
200+
def p2tr_script(self, merkle_root=b"", tweak=None):
176201
"""Returns the p2tr Script object"""
202+
external_pubkey = self.tweaked_key(merkle_root, tweak)
177203
# avoid circular dependency
178-
from buidl.taproot import TapRoot
204+
from buidl.script import P2TRScriptPubKey
179205

180-
return TapRoot(self).script_pubkey()
206+
return P2TRScriptPubKey(external_pubkey)
181207

182208
def p2pk_tap_script(self):
183209
"""Returns the p2tr Script object"""
@@ -198,9 +224,9 @@ def p2sh_p2wpkh_address(self, network="mainnet"):
198224
"""Returns the p2sh-p2wpkh base58 address string"""
199225
return self.p2wpkh_script().p2sh_address(network)
200226

201-
def p2tr_address(self, network="mainnet"):
227+
def p2tr_address(self, merkle_root=b"", tweak=None, network="mainnet"):
202228
"""Returns the p2tr bech32m address string"""
203-
return self.p2tr_script().address(network)
229+
return self.p2tr_script(merkle_root, tweak).address(network)
204230

205231
def verify_message(self, message, sig):
206232
"""Verify a message in the form of bytes. Assumes that the z
@@ -214,9 +240,9 @@ def verify_message(self, message, sig):
214240

215241
@classmethod
216242
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"""
218244
if len(binary) == 32:
219-
return cls.parse_bip340(binary)
245+
return cls.parse_xonly(binary)
220246
elif len(binary) in (33, 65):
221247
return cls.parse_sec(binary)
222248
else:
@@ -231,7 +257,7 @@ def parse_sec(cls, sec_bin):
231257
return cls(csec=sec_bin)
232258

233259
@classmethod
234-
def parse_bip340(cls, binary):
260+
def parse_xonly(cls, binary):
235261
sec_bin = b"\x02" + binary
236262
return cls(csec=sec_bin)
237263

@@ -348,6 +374,12 @@ def __init__(self, secret, network="mainnet", compressed=True):
348374
def hex(self):
349375
return "{:x}".format(self.secret).zfill(64)
350376

377+
def even_secret(self):
378+
if self.point.parity:
379+
return N - self.secret
380+
else:
381+
return self.secret
382+
351383
def sign(self, z):
352384
# per libsecp256k1 documentation, this helps against side-channel attacks
353385
if not lib.secp256k1_context_randomize(
@@ -435,12 +467,15 @@ def wif(self, compressed=True):
435467
# encode_base58_checksum the whole thing
436468
return encode_base58_checksum(prefix + secret_bytes + suffix)
437469

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__
444479
return self.__class__(new_secret, network=self.network)
445480

446481
@classmethod

buidl/compactfilter.py

-1
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,6 @@ def parse(cls, s):
254254

255255

256256
class GetCFCheckPointMessage:
257-
258257
command = b"getcfcheckpt"
259258
define_network = False
260259

buidl/descriptor.py

-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ def parse_any_key_record(key_record_str):
151151

152152

153153
class P2WSHSortedMulti:
154-
155154
# TODO: make an inheritable base descriptor class that this inherits from
156155

157156
def __init__(

buidl/hd.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,8 @@ def get_private_key(self, purpose, account_num=0, is_external=True, address_num=
332332
return hd_priv.private_key
333333

334334
def _get_address(self, purpose, account_num=0, is_external=True, address_num=0):
335-
"""Returns the proper address among purposes 44', 49' and 84'.
336-
p2pkh for 44', p2sh-p2wpkh for 49' and p2wpkh for 84'."""
337-
# if purpose is not one of 44', 49' or 84', raise ValueError
335+
"""Returns the proper address among purposes 44', 49', 84' and 86'.
336+
p2pkh for 44', p2sh-p2wpkh for 49', p2wpkh for 84', and p2tr for 86'."""
338337
point = self.get_private_key(
339338
purpose=purpose,
340339
account_num=account_num,
@@ -353,6 +352,7 @@ def _get_address(self, purpose, account_num=0, is_external=True, address_num=0):
353352
# if 86', return the p2tr_address
354353
elif purpose == "86'":
355354
return point.p2tr_address(network=self.network)
355+
# if purpose is not one of 44', 49', 84' or 86', raise ValueError
356356
else:
357357
raise ValueError(
358358
f"Cannot create an address without a proper purpose: {purpose}"

buidl/libsec_build.py

100644100755
+6-1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,10 @@
1313

1414
ffi = FFI()
1515
ffi.cdef(source)
16-
ffi.set_source("_libsec", header, libraries=["secp256k1"])
16+
ffi.set_source(
17+
"_libsec",
18+
header,
19+
libraries=["secp256k1"],
20+
include_dirs=["/opt/homebrew/Cellar/libsecp256k1/0.1/include"],
21+
)
1722
ffi.compile(verbose=True)

buidl/mnemonic.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def secure_mnemonic(num_bits=256, extra_entropy=0):
2121
"""
2222
if num_bits not in (128, 160, 192, 224, 256):
2323
raise ValueError(f"Invalid num_bits: {num_bits}")
24-
if type(extra_entropy) is not int:
24+
if not isinstance(extra_entropy, int):
2525
raise TypeError(f"extra_entropy must be an int: {extra_entropy}")
2626
if extra_entropy < 0:
2727
raise ValueError(f"extra_entropy cannot be negative: {extra_entropy}")
@@ -115,9 +115,9 @@ def __init__(self, filename, num_words):
115115
self.lookup[word[:4]] = i
116116

117117
def __getitem__(self, key):
118-
if type(key) == str:
118+
if isinstance(key, str):
119119
return self.lookup[key]
120-
elif type(key) == int:
120+
elif isinstance(key, int):
121121
return self.words[key]
122122
else:
123123
raise KeyError("key needs to be a str or int")

buidl/op.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,7 @@ def op_checksig_schnorr(stack, tx_obj, input_index):
748748
return False
749749
pubkey = stack.pop()
750750
signature = stack.pop()
751-
point = S256Point.parse_bip340(pubkey)
751+
point = S256Point.parse_xonly(pubkey)
752752
if len(signature) == 65:
753753
hash_type = signature[-1]
754754
signature = signature[:-1]
@@ -777,7 +777,7 @@ def op_checksigadd_schnorr(stack, tx_obj, input_index):
777777
pubkey = stack.pop()
778778
n = decode_num(stack.pop())
779779
signature = stack.pop()
780-
point = S256Point.parse_bip340(pubkey)
780+
point = S256Point.parse_xonly(pubkey)
781781
if len(signature) == 65:
782782
hash_type = signature[-1]
783783
signature = signature[:-1]

0 commit comments

Comments
 (0)