11#!/usr/bin/env python
22
3- from re import A
43import reference
54import json
65from secp256k1 import *
76from bitcoin_utils import deser_txid , hash160 , COutPoint
87import bip32
98from copy import deepcopy
10- from importlib import reload
119from typing import Tuple , List
12- reload (reference )
13-
14- G = ECKey ().set (1 ).get_pubkey ()
15- sending_test_vectors = []
10+ from itertools import permutations
1611
1712HRP = "sp"
18- #TODO: use clearly mock signatures, e.g. 010203040506...
13+
1914def derive_silent_payment_key_pair (seed : bytes ) -> Tuple [ECKey , ECKey , ECPubKey , ECPubKey ]:
2015 SCAN_KEY = "m/352h/0h/0h/1h/0"
2116 SPEND_KEY = "m/352h/0h/0h/0h/0"
@@ -28,7 +23,6 @@ def derive_silent_payment_key_pair(seed: bytes) -> Tuple[ECKey, ECKey, ECPubKey,
2823
2924 return scan , spend , Scan , Spend
3025
31-
3226def get_key_pair (index , seed = b'deadbeef' , derivation = 'm/0h' ):
3327
3428 master = bip32 .BIP32 .from_seed (seed )
@@ -37,12 +31,6 @@ def get_key_pair(index, seed=b'deadbeef', derivation='m/0h'):
3731
3832 return d , P
3933
40- def add_private_keys (inputs , input_priv_keys ):
41- for x , i in enumerate (inputs ):
42- i ['private_key' ] = input_priv_keys [x ][0 ].get_bytes ().hex ()
43-
44- return inputs
45-
4634def get_p2pkh_scriptsig (pub_key , priv_key ):
4735 msg = hashlib .sha256 (b'message' ).digest ()
4836 sig = priv_key .sign_ecdsa (msg , low_s = False , rfc6979 = True ).hex ()
@@ -74,37 +62,46 @@ def serialize_witness_stack(stack_items):
7462 result += f'{ size :02x} ' + item
7563 return result
7664
77- def new_test_case ():
78- recipient = {
79- "given" : {
80- "vin" : [],
81- "outputs" : [],
82- "key_material" : {
83- "spend_priv_key" : "hex" ,
84- "scan_priv_key" : "hex" ,
85- },
86- "labels" : [],
87- },
88- "expected" : {
89- "addresses" : [],
90- "outputs" : [],
91- }
92- }
93- sender = {
94- "given" : {
95- "vin" : [],
96- "recipients" : []
97- },
98- "expected" : {
99- "outputs" : [],
100- }
101- }
102- test_case = {
103- "comment" : "" ,
104- "sending" : [],
105- "receiving" : [],
106- }
107- return sender , recipient , test_case
65+ def unique_permutations (lst ):
66+ # Generate all permutations of the list
67+ perms = permutations (lst , len (lst ))
68+ unique_perms = set (perms )
69+ unique_perms_list = list (unique_perms )
70+ return unique_perms_list
71+
72+ def create_output_permutations (input_priv_keys : List [Tuple [ECKey , bool ]], input_hash : bytes , recipients : List [str ], hrp = "tsp" ) -> List [List [str ]]:
73+ G = ECKey ().set (1 ).get_pubkey ()
74+ negated_keys = []
75+ for key , is_xonly in input_priv_keys :
76+ k = ECKey ().set (key .get_bytes ())
77+ if is_xonly and k .get_pubkey ().get_y () % 2 != 0 :
78+ k .negate ()
79+ negated_keys .append (k )
80+
81+ a_sum = sum (negated_keys )
82+ all_outputs = []
83+ for permutation in unique_permutations (recipients ):
84+ silent_payment_groups : Dict [ECPubKey , List [ECPubKey ]] = {}
85+ for recipient in permutation :
86+ B_scan , B_m = reference .decode_silent_payment_address (recipient , hrp = hrp )
87+ if B_scan in silent_payment_groups :
88+ silent_payment_groups [B_scan ].append (B_m )
89+ else :
90+ silent_payment_groups [B_scan ] = [B_m ]
91+ outputs = []
92+ for B_scan , B_m_values in silent_payment_groups .items ():
93+ ecdh_shared_secret = input_hash * a_sum * B_scan
94+ k = 0
95+ for B_m in B_m_values :
96+ t_k = TaggedHash ("BIP0352/SharedSecret" , ecdh_shared_secret .get_bytes (False ) + reference .ser_uint32 (k ))
97+ P_km = B_m + t_k * G
98+ outputs .append (P_km .get_bytes ().hex ())
99+ k += 1
100+ all_outputs .append (outputs )
101+ set_of_sets = set ()
102+ for s in all_outputs :
103+ set_of_sets .add (frozenset (s ))
104+ return [sorted (list (fz )) for fz in set_of_sets ]
108105
109106def custom_sort (sublist ):
110107 return tuple (sorted (sublist ))
@@ -160,7 +157,7 @@ def generated_sender(inputs, addrs,):
160157 else :
161158 A_sum = sum (input_pubkeys )
162159 input_hash = reference .get_input_hash ([o .outpoint for o in vins ], A_sum )
163- outputs = reference . create_outputs (input_priv_keys , input_hash , addrs , hrp = HRP )
160+ outputs = create_output_permutations (input_priv_keys , input_hash , addrs , hrp = HRP )
164161 sender ['expected' ]['outputs' ] = sort_list_of_lists (outputs )
165162 return deepcopy (sender )
166163
@@ -247,6 +244,8 @@ def generated_recipient(inputs, output_set: List[str], scan_key: ECKey, spend_ke
247244 recipient ['given' ]['vin' ] = deepcopy (inputs_receiver )
248245 return deepcopy (recipient )
249246
247+ # Test cases
248+
250249def generate_labeled_output_tests ():
251250
252251 test_cases = []
@@ -282,7 +281,6 @@ def generate_labeled_output_tests():
282281 test_cases .append (generated_test_case (inputs , b_scan , b_spend , addresses , labels = label_ints , comment = comments [i ]))
283282 return test_cases
284283
285-
286284def generate_single_output_outpoint_tests ():
287285
288286 outpoint_test_cases = [
@@ -339,7 +337,6 @@ def generate_single_output_outpoint_tests():
339337 test_cases .append (generated_test_case (inputs , b_scan , b_spend , [address ], labels = [], comment = comments [i ]))
340338 return test_cases
341339
342-
343340def generate_multiple_output_tests ():
344341
345342 outpoints = [
@@ -380,78 +377,6 @@ def generate_multiple_output_tests():
380377 ]
381378 return test_cases
382379
383-
384- # In[13]:
385-
386-
387- def generate_paying_to_self_test ():
388-
389- msg = hashlib .sha256 (b'message' ).digest ()
390- aux = hashlib .sha256 (b'random auxiliary data' ).digest ()
391- outpoints = [
392- ("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16" , 0 ),
393- ("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d" , 0 )
394- ]
395-
396- sender_bip32_seed = 'deadbeef'
397- recipient_bip32_seed = 'deadbeef'
398- i1 , I1 = get_key_pair (0 , seed = bytes .fromhex (sender_bip32_seed ))
399- i2 , I2 = get_key_pair (1 )
400- input_priv_keys = [(i1 , False ), (i2 , False )]
401- input_pub_keys = [I1 , I2 ]
402-
403- sender , recipient , test_case = new_test_case ()
404- sender ['given' ]['outpoints' ] = outpoints
405- recipient ['given' ]['outpoints' ] = outpoints
406- sender ['given' ]['input_priv_keys' ].extend ([i1 .get_bytes ().hex (), i2 .get_bytes ().hex ()])
407- recipient ['given' ]['input_pub_keys' ].extend ([I1 .get_bytes (False ).hex (), I2 .get_bytes (False ).hex ()])
408-
409- b_scan , b_spend , B_scan , B_spend = create_silent_payment_key_pair (bytes .fromhex (recipient_bip32_seed ))
410- recipient ['given' ]['bip32_seed' ] = recipient_bip32_seed
411- recipient ['given' ]['scan_priv_key' ] = b_scan .get_bytes ().hex ()
412- recipient ['given' ]['spend_priv_key' ] = b_spend .get_bytes ().hex ()
413- address = reference .encode_silent_payment_address (B_scan , B_spend , hrp = HRP )
414-
415- sender ['given' ]['recipients' ].extend ([address ])
416- recipient ['expected' ]['addresses' ].extend ([address ])
417-
418- A_sum = sum (input_pub_keys )
419- deterministic_nonce = reference .get_input_hash ([COutPoint (deser_txid (o [0 ]), o [1 ]) for o in outpoints ], A_sum )
420- outputs = reference .create_outputs (input_priv_keys , deterministic_nonce , [address ], hrp = HRP )
421- set_of_sets = set ()
422- for output_set in outputs :
423- generated_set = {tuple (d .items ()) for d in output_set }
424- set_of_sets .add (frozenset (generated_set ))
425-
426- sender ['expected' ]['outputs' ] = [[o [1 ][1 ] for o in pubkey_set ] for pubkey_set in set_of_sets ]
427-
428- expected_sets = []
429- for output_set in set_of_sets :
430- tmp = []
431- for t in output_set :
432- o = dict (t )
433-
434- pubkey = ECPubKey ().set (bytes .fromhex (o ['pub_key' ]))
435- full_private_key = b_spend .add (
436- bytes .fromhex (o ['priv_key_tweak' ])
437- )
438- if full_private_key .get_pubkey ().get_y ()% 2 != 0 :
439- full_private_key .negate ()
440-
441- sig = full_private_key .sign_schnorr (msg , aux )
442- assert pubkey .verify_schnorr (sig , msg )
443- o ['signature' ] = sig .hex ()
444- tmp .append (t )
445- expected_sets .append (tmp )
446-
447- recipient ['given' ]['outputs' ] = [o [1 ][1 ] for output_set in set_of_sets for o in output_set ]
448- recipient ['expected' ]['outputs' ] = expected_sets
449- test_case ['sending' ].extend ([sender ])
450- test_case ['receiving' ].extend ([recipient ])
451-
452- return test_case
453-
454-
455380def generate_multiple_outputs_with_labels_tests ():
456381
457382 outpoints = [
@@ -500,7 +425,6 @@ def generate_multiple_outputs_with_labels_tests():
500425
501426 return test_cases
502427
503-
504428def generate_single_output_input_tests ():
505429
506430 outpoints = [
@@ -590,7 +514,6 @@ def generate_single_output_input_tests():
590514
591515 return test_cases
592516
593-
594517def generate_change_tests ():
595518
596519 outpoints = [
@@ -626,71 +549,6 @@ def generate_change_tests():
626549 test_case ['receiving' ].append (generated_recipient (inputs , test_case ['sending' ][0 ]['expected' ]['outputs' ][0 ], scan1 , spend1 , labels = []))
627550 return [test_case ]
628551
629-
630- # In[17]:
631-
632-
633- def generate_unknown_segwit_ver_test ():
634- sender , recipient , test_case = new_test_case ()
635-
636- msg = hashlib .sha256 (b'message' ).digest ()
637- aux = hashlib .sha256 (b'random auxiliary data' ).digest ()
638- outpoints = [
639- ("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16" , 0 ),
640- ("a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d" , 0 ),
641- ]
642- sender_bip32_seed = 'deadbeef'
643- recipient_bip32_seed = 'f00dbabe'
644- scan , spend , Scan , Spend = derive_silent_payment_key_pair (bytes .fromhex (recipient_bip32_seed ))
645- address = reference .encode_silent_payment_address (Scan , Spend , hrp = HRP )
646- addresses = [address ]
647-
648- recipient ['given' ]['key_material' ]['scan_priv_key' ] = scan .get_bytes ().hex ()
649- recipient ['given' ]['key_material' ]['spend_priv_key' ] = spend .get_bytes ().hex ()
650-
651- inputs = []
652- input_priv_keys = []
653- input_pub_keys = []
654-
655- ## included
656- # p2pkh
657- i = len (inputs )
658- priv , pub = get_key_pair (i , seed = bytes .fromhex (sender_bip32_seed ))
659- inputs += [{
660- 'prevout' : list (outpoints [i ]) + [get_p2pkh_scriptsig (pub , priv ), "" ],
661- 'scriptPubKey' : get_p2pkh_scriptPubKey (pub ),
662- }]
663- input_priv_keys += [(priv , False )]
664- input_pub_keys += [pub ]
665-
666- # unknown segwit version
667- i = len (inputs )
668- priv , pub = get_key_pair (i , seed = bytes .fromhex (sender_bip32_seed ))
669- sig = priv .sign_ecdsa (msg , low_s = False , rfc6979 = True ).hex ()
670- inputs += [{
671- 'prevout' : list (outpoints [i ]) + ["" , serialize_witness_stack ([sig , pub .get_bytes (False ).hex ()])],
672- 'scriptPubKey' : "5214" + hash160 (pub .get_bytes (False )),
673- }]
674- input_priv_keys += [(priv , False )]
675- input_pub_keys += [pub ]
676-
677- sender ['given' ]['recipients' ] = addresses
678- sender ['given' ]['inputs' ] = add_private_keys (deepcopy (inputs ), input_priv_keys )
679- # TODO: encode failure of sending explicitly
680- test_case ['sending' ].extend ([sender ])
681-
682- recipient ['given' ]['inputs' ] = inputs
683- # create plausible outputs
684- # a) using all detected keys
685- outputs_a = reference .create_outputs (input_priv_keys , reference .hash_outpoints (outpoints ), addresses , hrp = HRP )
686- # b) using only p2pkh input
687- outputs_b = reference .create_outputs (input_priv_keys [:1 ], reference .hash_outpoints (outpoints ), addresses , hrp = HRP )
688- recipient ['given' ]['outputs' ] = [outputs_a [0 ][0 ], outputs_b [0 ][0 ]]
689- test_case ['receiving' ].extend ([recipient ])
690-
691- test_case ["comment" ] = "Skipped tx: unknown segwit version input"
692- return [test_case ]
693-
694552def generate_taproot_with_nums_point_test ():
695553
696554 outpoints = [
@@ -1042,7 +900,6 @@ def generate_no_outputs_tests():
1042900 return test_cases
1043901
1044902def generate_no_valid_inputs_tests ():
1045- sender , recipient , test_case = new_test_case ()
1046903
1047904 outpoints = [
1048905 ("f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16" , 0 ),
@@ -1088,10 +945,6 @@ def generate_no_valid_inputs_tests():
1088945 regular_p2tr1 = get_key_pair (i , seed = bytes .fromhex (sender_bip32_seed ))[1 ].get_bytes (True ).hex ()
1089946 regular_p2tr2 = get_key_pair (i + 1 , seed = bytes .fromhex (sender_bip32_seed ))[1 ].get_bytes (True ).hex ()
1090947
1091- sender ['expected' ]['outputs' ] = []
1092- recipient ['given' ]['outputs' ] = [regular_p2tr1 , regular_p2tr2 ]
1093- recipient ['expected' ]['outputs' ] = []
1094-
1095948 test_cases = [
1096949 generated_test_case (inputs , scan , spend , addresses , labels = [], comment = "No valid inputs, sender generates no outputs" ),
1097950 ]
0 commit comments