Skip to content

Commit 9201dd6

Browse files
committed
update
1 parent 329803b commit 9201dd6

2 files changed

Lines changed: 88 additions & 230 deletions

File tree

‎generate-test-vector.py‎

Lines changed: 45 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
#!/usr/bin/env python
22

3-
from re import A
43
import reference
54
import json
65
from secp256k1 import *
76
from bitcoin_utils import deser_txid, hash160, COutPoint
87
import bip32
98
from copy import deepcopy
10-
from importlib import reload
119
from 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

1712
HRP="sp"
18-
#TODO: use clearly mock signatures, e.g. 010203040506...
13+
1914
def 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-
3226
def 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-
4634
def 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

109106
def 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+
250249
def 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-
286284
def 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-
343340
def 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-
455380
def 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-
504428
def 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-
594517
def 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-
694552
def generate_taproot_with_nums_point_test():
695553

696554
outpoints = [
@@ -1042,7 +900,6 @@ def generate_no_outputs_tests():
1042900
return test_cases
1043901

1044902
def 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

Comments
 (0)