77
88import json
99import sys
10- import hashlib
1110from pathlib import Path
12- from typing import Optional , Tuple
1311
14- p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
15- n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
16- G = (
17- 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 ,
18- 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 ,
12+ BIP375_DIR = Path (__file__ ).resolve ().parents [1 ] / "bip-0375"
13+ DEPS_DIR = BIP375_DIR / "deps"
14+ SECP256K1LAB_DIR = DEPS_DIR / "secp256k1lab/src"
15+ for dependency_path in (BIP375_DIR , DEPS_DIR , SECP256K1LAB_DIR ):
16+ sys .path .insert (0 , str (dependency_path ))
17+
18+ from secp256k1lab .bip340 import schnorr_sign
19+ from secp256k1lab .secp256k1 import G , Scalar
20+
21+ from psbt_bip376 import (
22+ BIP376PSBT ,
23+ PSBT_IN_SP_TWEAK ,
24+ get_p2tr_witness_utxo_output_key ,
25+ get_sp_tweak ,
26+ set_tap_key_sig ,
1927)
2028
21- Point = Tuple [int , int ]
22-
23-
24- def tagged_hash (tag : str , msg : bytes ) -> bytes :
25- tag_hash = hashlib .sha256 (tag .encode ("utf-8" )).digest ()
26- return hashlib .sha256 (tag_hash + tag_hash + msg ).digest ()
27-
28-
29- def int_from_bytes (data : bytes ) -> int :
30- return int .from_bytes (data , byteorder = "big" )
31-
32-
33- def bytes_from_int (x : int ) -> bytes :
34- return x .to_bytes (32 , byteorder = "big" )
35-
36-
37- def has_even_y (P : Point ) -> bool :
38- return (P [1 ] % 2 ) == 0
39-
40-
41- def bytes_from_point (P : Point ) -> bytes :
42- return bytes_from_int (P [0 ])
43-
44-
45- def xor_bytes (a : bytes , b : bytes ) -> bytes :
46- return bytes (x ^ y for (x , y ) in zip (a , b ))
47-
48-
49- def lift_x (x_coord : int ) -> Optional [Point ]:
50- if x_coord >= p :
51- return None
52- y_sq = (pow (x_coord , 3 , p ) + 7 ) % p
53- y_coord = pow (y_sq , (p + 1 ) // 4 , p )
54- if pow (y_coord , 2 , p ) != y_sq :
55- return None
56- return (x_coord , y_coord if (y_coord % 2 ) == 0 else p - y_coord )
57-
58-
59- def point_add (P1 : Optional [Point ], P2 : Optional [Point ]) -> Optional [Point ]:
60- if P1 is None :
61- return P2
62- if P2 is None :
63- return P1
64- if (P1 [0 ] == P2 [0 ]) and (P1 [1 ] != P2 [1 ]):
65- return None
66- if P1 == P2 :
67- lam = (3 * P1 [0 ] * P1 [0 ] * pow (2 * P1 [1 ], p - 2 , p )) % p
68- else :
69- lam = ((P2 [1 ] - P1 [1 ]) * pow (P2 [0 ] - P1 [0 ], p - 2 , p )) % p
70- x3 = (lam * lam - P1 [0 ] - P2 [0 ]) % p
71- y3 = (lam * (P1 [0 ] - x3 ) - P1 [1 ]) % p
72- return (x3 , y3 )
73-
74-
75- def point_mul (P : Optional [Point ], scalar : int ) -> Optional [Point ]:
76- R = None
77- for i in range (256 ):
78- if (scalar >> i ) & 1 :
79- R = point_add (R , P )
80- P = point_add (P , P )
81- return R
82-
83-
84- def schnorr_verify (msg : bytes , pubkey : bytes , sig : bytes ) -> bool :
85- if len (pubkey ) != 32 or len (sig ) != 64 :
86- return False
87- P = lift_x (int_from_bytes (pubkey ))
88- r = int_from_bytes (sig [0 :32 ])
89- s = int_from_bytes (sig [32 :64 ])
90- if P is None or r >= p or s >= n :
91- return False
92- e = int_from_bytes (tagged_hash ("BIP0340/challenge" , sig [0 :32 ] + pubkey + msg )) % n
93- R = point_add (point_mul (G , s ), point_mul (P , n - e ))
94- if R is None :
95- return False
96- return has_even_y (R ) and (R [0 ] == r )
97-
98-
99- def schnorr_sign (msg : bytes , seckey : bytes , aux_rand : bytes ) -> bytes :
100- d0 = int_from_bytes (seckey )
101- if not (1 <= d0 <= n - 1 ):
102- raise ValueError ("The secret key must be in the range 1..n-1." )
103- if len (aux_rand ) != 32 :
104- raise ValueError ("aux_rand must be 32 bytes." )
105- P = point_mul (G , d0 )
106- assert P is not None
107- d = d0 if has_even_y (P ) else n - d0
108- t = xor_bytes (bytes_from_int (d ), tagged_hash ("BIP0340/aux" , aux_rand ))
109- k0 = int_from_bytes (tagged_hash ("BIP0340/nonce" , t + bytes_from_point (P ) + msg )) % n
110- if k0 == 0 :
111- raise RuntimeError ("Failure. This happens only with negligible probability." )
112- R = point_mul (G , k0 )
113- assert R is not None
114- k = k0 if has_even_y (R ) else n - k0
115- e = int_from_bytes (tagged_hash ("BIP0340/challenge" , bytes_from_point (R ) + bytes_from_point (P ) + msg )) % n
116- sig = bytes_from_point (R ) + bytes_from_int ((k + e * d ) % n )
117- if not schnorr_verify (msg , bytes_from_point (P ), sig ):
118- raise RuntimeError ("The created signature does not pass verification." )
119- return sig
120-
12129
12230def parse_hex (data : str , expected_len : int , field_name : str ) -> bytes :
12331 raw = bytes .fromhex (data )
@@ -126,29 +34,45 @@ def parse_hex(data: str, expected_len: int, field_name: str) -> bytes:
12634 return raw
12735
12836
129- def derive_signing_key (spend_seckey : bytes , tweak : bytes , output_pubkey : bytes ) -> Tuple [int , int , bool ]:
130- b_spend = int_from_bytes (spend_seckey )
131- if not (1 <= b_spend <= n - 1 ):
37+ def derive_signing_key (
38+ spend_seckey : bytes , tweak : bytes , output_pubkey : bytes
39+ ) -> tuple [Scalar , Scalar , bool ]:
40+ try :
41+ b_spend = Scalar .from_bytes_checked (spend_seckey )
42+ except ValueError as exc :
43+ raise ValueError ("spend key out of range" ) from exc
44+ if b_spend == 0 :
13245 raise ValueError ("spend key out of range" )
13346
134- tweak_int = int_from_bytes (tweak )
135- d_raw = (b_spend + tweak_int ) % n
47+ d_raw = b_spend + Scalar .from_bytes_wrapping (tweak )
13648 if d_raw == 0 :
13749 raise ValueError ("tweaked private key is zero" )
13850
139- Q = point_mul ( G , d_raw )
140- assert Q is not None
141- negated = not has_even_y (Q )
142- d = d_raw if not negated else n - d_raw
51+ Q = d_raw * G
52+ assert not Q . infinity
53+ negated = not Q . has_even_y ()
54+ d = d_raw if not negated else - d_raw
14355
144- Q_even = point_mul ( G , d )
145- assert Q_even is not None
146- if bytes_from_point ( Q_even ) != output_pubkey :
56+ Q_even = d * G
57+ assert not Q_even . infinity
58+ if Q_even . to_bytes_xonly ( ) != output_pubkey :
14759 raise ValueError ("tweaked key does not match output key" )
14860
14961 return d_raw , d , negated
15062
15163
64+ def sign_psbt (psbt_data : str , spend_seckey : bytes , message : bytes , aux_rand : bytes ) -> str :
65+ psbt = BIP376PSBT .from_base64 (psbt_data )
66+ for input_map in psbt .i :
67+ if input_map .get (PSBT_IN_SP_TWEAK ) is None :
68+ continue
69+ tweak = get_sp_tweak (input_map )
70+ output_pubkey = get_p2tr_witness_utxo_output_key (input_map )
71+ _ , d , _ = derive_signing_key (spend_seckey , tweak , output_pubkey )
72+ set_tap_key_sig (input_map , schnorr_sign (message , d .to_bytes (), aux_rand ))
73+ return psbt .to_base64 ()
74+
75+
15276def run_test_vectors (path : Path ) -> bool :
15377 vectors = json .loads (path .read_text (encoding = "utf-8" ))
15478 all_passed = True
@@ -164,18 +88,18 @@ def run_test_vectors(path: Path) -> bool:
16488 print (f"- valid[{ index } ] { description } " )
16589 try :
16690 spend_seckey = parse_hex (given ["spend_seckey" ], 32 , "spend_seckey" )
167- tweak = parse_hex (given ["tweak" ], 32 , "tweak" )
168- output_pubkey = parse_hex (given ["output_pubkey" ], 32 , "output_pubkey" )
16991 message = parse_hex (given ["message" ], 32 , "message" )
17092 aux_rand = parse_hex (given ["aux_rand" ], 32 , "aux_rand" )
17193
172- d_raw , d , negated = derive_signing_key (spend_seckey , tweak , output_pubkey )
173- signature = schnorr_sign (message , bytes_from_int (d ), aux_rand )
174-
175- assert bytes_from_int (d_raw ).hex () == expected ["raw_tweaked_seckey" ]
176- assert negated == expected ["negated" ]
177- assert bytes_from_int (d ).hex () == expected ["final_seckey" ]
178- assert signature .hex () == expected ["signature" ]
94+ if "psbt" in given :
95+ signed_psbt = sign_psbt (given ["psbt" ], spend_seckey , message , aux_rand )
96+ assert signed_psbt == expected ["psbt" ]
97+ else :
98+ tweak = parse_hex (given ["tweak" ], 32 , "tweak" )
99+ output_pubkey = parse_hex (given ["output_pubkey" ], 32 , "output_pubkey" )
100+ _ , d , _ = derive_signing_key (spend_seckey , tweak , output_pubkey )
101+ signature = schnorr_sign (message , d .to_bytes (), aux_rand )
102+ assert signature .hex () == expected ["signature" ]
179103 except Exception as exc :
180104 all_passed = False
181105 print (f" FAILED: { exc } " )
0 commit comments