4A script to convert BIP352 test vectors from JSON to a C header.
8 ./tools/tests_silentpayments_generate.py src/modules/silentpayments/bip352_send_and_receive_test_vectors.json > ./src/modules/silentpayments/vectors.h
15NUMS_H = bytes.fromhex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")
16MAX_INPUTS_PER_TEST_CASE = 3
17MAX_OUTPUTS_PER_TEST_CASE = 2324
18MAX_RECIPIENT_ENTRIES_PER_TEST_CASE = 4
19MAX_LABELS_PER_TEST_CASE = 4
20MAX_PERMUTATIONS_PER_SENDING_TEST_CASE = 12
21MAX_RECEIVE_SUBTESTS = 2
24 return hashlib.sha256(s).digest()
27 serialized_outpoints = [bytes.fromhex(txid)[::-1] + n.to_bytes(4,
'little')
for txid, n
in outpoints]
28 return sorted(serialized_outpoints)[0]
31 return (len(s) == 34)
and (s[0] == 0x51)
and (s[1] == 0x20)
34 """Extract the correct pubkey for an input, handling NUMS_H filtering and format conversion"""
35 spk = bytes.fromhex(input_data[
'prevout'][
'scriptPubKey'][
'hex'])
36 pubkey = bytes.fromhex(pubkey_hex)
40 witness = bytes.fromhex(input_data.get(
'txinwitness',
''))
45 num_witness_items = witness[0]
47 for i
in range(num_witness_items):
49 witness_stack.append(witness[1:item_len+1])
50 witness = witness[item_len+1:]
53 if len(witness_stack) > 1
and witness_stack[-1][0] == 0x50:
55 if len(witness_stack) > 1:
56 control_block = witness_stack[-1]
57 internal_key = control_block[1:33]
58 if internal_key == NUMS_H:
69 assert len(hex) % 2 == 0
72 s =
',0x'.join(a + b
for a, b
in zip(hex[::2], hex[1::2]))
73 return "{0x" + s +
"}"
77 return f
" /* {comment} */"
82 assert len(keys) <= MAX_INPUTS_PER_TEST_CASE
85 out += f
" {len(keys)},\n"
86 out += f
" {{{maybe_gen_comment(comment)}\n"
88 out += f
" {gen_byte_array(k)},\n"
95 assert len(recipients) <= MAX_RECIPIENT_ENTRIES_PER_TEST_CASE
96 num_outputs =
sum([r.get(
'count', 1)
for r
in recipients])
97 assert num_outputs <= MAX_OUTPUTS_PER_TEST_CASE
98 out = f
" {num_outputs},\n"
99 out += f
" {len(recipients)},\n"
100 out +=
" { /* recipient pubkey entries (address data) */\n"
101 for i
in range(MAX_RECIPIENT_ENTRIES_PER_TEST_CASE):
103 if i < len(recipients):
105 scan_pubkey = bytes.fromhex(recipients[i][
'scan_pub_key'])
106 spend_pubkey = bytes.fromhex(recipients[i][
'spend_pub_key'])
108 out += f
" {gen_byte_array(scan_pubkey.hex())},\n"
109 out += f
" {gen_byte_array(spend_pubkey.hex())},\n"
110 out += f
" {recipients[i].get('count', 1)},\n"
120 assert len(output_sets) <= MAX_PERMUTATIONS_PER_SENDING_TEST_CASE
123 out += f
" {len(output_sets)},\n"
124 out += f
" {len(output_sets[0])},\n"
125 out += f
" {{{maybe_gen_comment(comment)}\n"
126 for o
in output_sets:
127 out +=
gen_outputs(outputs=o, prepend_count=
False, indent=12)
129 out +=
gen_outputs(comment=
None, outputs=[], prepend_count=
False, indent=12)
133def gen_outputs(outputs, comment=None, prepend_count=False, indent=8):
135 spaces = indent *
" "
137 out += spaces + f
"{len(outputs)},\n"
138 out += f
" {{{maybe_gen_comment(comment)}\n"
140 out += spaces + f
" {gen_byte_array(o)},\n"
142 out += spaces +
' "",\n'
143 out += spaces +
"},\n"
147 assert len(labels) <= MAX_LABELS_PER_TEST_CASE
148 out =
" /* labels */\n"
149 out += f
" {len(labels)}, {{"
150 for i
in range(MAX_LABELS_PER_TEST_CASE):
152 out += f
"{labels[i]}, "
154 out +=
"0xffffffff, "
159 out =
"/* Note: this file was autogenerated using tests_silentpayments_generate.py. Do not edit. */\n"
163#define MAX_INPUTS_PER_TEST_CASE {MAX_INPUTS_PER_TEST_CASE}
170struct bip352_recipient_addressdata {{
171 unsigned char scan_pubkey[33];
172 unsigned char spend_pubkey[33];
176struct bip352_receive_subtest {{
177 /* Given recipient data */
178 unsigned char scan_seckey[32];
179 unsigned char spend_seckey[32];
180 size_t num_to_scan_outputs;
181 unsigned char to_scan_outputs[MAX_OUTPUTS_PER_TEST_CASE][32];
183 unsigned int label_integers[MAX_LABELS_PER_TEST_CASE];
185 /* Expected recipient data */
186 size_t full_check; /* 1..detailed check against tweaks
and signatures, 0..only check found outputs count */
187 size_t num_found_output_pubkeys;
188 unsigned char found_output_pubkeys[MAX_OUTPUTS_PER_TEST_CASE][32];
189 unsigned char found_seckey_tweaks[MAX_OUTPUTS_PER_TEST_CASE][32];
190 unsigned char found_signatures[MAX_OUTPUTS_PER_TEST_CASE][64];
193struct bip352_test_vector {{
194 /* Inputs (private keys / public keys + smallest outpoint) */
195 size_t num_plain_inputs;
196 unsigned char plain_seckeys[MAX_INPUTS_PER_TEST_CASE][32];
197 unsigned char plain_pubkeys[MAX_INPUTS_PER_TEST_CASE][33];
198 size_t num_taproot_inputs;
199 unsigned char taproot_seckeys[MAX_INPUTS_PER_TEST_CASE][32];
200 unsigned char xonly_pubkeys[MAX_INPUTS_PER_TEST_CASE][32];
201 unsigned char outpoint_smallest[36];
203 /* Given sender data (pubkeys encoded per output address to send to) */
205 size_t num_recipient_entries;
206 struct bip352_recipient_addressdata recipient_pubkeys[MAX_RECIPIENT_ENTRIES_PER_TEST_CASE];
208 /* Expected sender data */
209 size_t num_output_sets;
210 size_t num_recipient_outputs;
211 unsigned char recipient_outputs[MAX_PERMUTATIONS_PER_SENDING_TEST_CASE][MAX_OUTPUTS_PER_TEST_CASE][32];
213 size_t num_receive_subtests;
214 struct bip352_receive_subtest receive_subtests[MAX_RECEIVE_SUBTESTS];
220 out = f
"#define SECP256K1_SILENTPAYMENTS_NUMBER_TESTVECTORS {len(test_vectors)}\n\n"
221 out +=
"static const struct bip352_test_vector bip352_test_vectors[SECP256K1_SILENTPAYMENTS_NUMBER_TESTVECTORS] = {\n"
222 for test_i, test_vector
in enumerate(test_vectors):
224 input_plain_seckeys = []
225 input_taproot_seckeys = []
226 input_plain_pubkeys = []
227 input_xonly_pubkeys = []
235 assert len(test_vector[
'sending']) == 1
238 input_pubkeys_hex = test_vector[
'sending'][0][
'expected'][
'input_pub_keys']
240 for vec
in test_vector[
'sending'][0][
'given'][
'vin']:
241 outpoints.append((vec[
'txid'], vec[
'vout']))
243 if pubkey_index < len(input_pubkeys_hex):
245 if len(pubkey) == 33:
246 input_plain_seckeys.append(vec[
'private_key'])
247 input_plain_pubkeys.append(pubkey.hex())
249 elif len(pubkey) == 32:
250 input_taproot_seckeys.append(vec[
'private_key'])
251 input_xonly_pubkeys.append(pubkey.hex())
255 out += f
" /* ----- {test_vector['comment']} ({test_i + 1}) ----- */\n"
259 out +=
gen_key_material(input_plain_seckeys,
"input plain seckeys", prepend_count=
True)
261 out +=
gen_key_material(input_taproot_seckeys,
"input taproot seckeys", prepend_count=
True)
263 out +=
" /* smallest outpoint */\n"
264 out += f
" {gen_byte_array(outpoint_L)},\n"
269 out +=
gen_sending_outputs(test_vector[
'sending'][0][
'expected'][
'outputs'],
"recipient outputs", prepend_count=
True)
271 assert len(test_vector[
'receiving']) <= MAX_RECEIVE_SUBTESTS
272 out += f
" {len(test_vector['receiving'])}, /* number of receive subtests */\n"
274 for recv_test
in test_vector[
'receiving']:
277 recv_test_given = recv_test[
'given']
278 recv_test_expected = recv_test[
'expected']
279 out +=
" /* recipient data (scan and spend seckeys) */\n"
280 out += f
" {gen_byte_array(recv_test_given['key_material']['scan_priv_key'])},\n"
281 out += f
" {gen_byte_array(recv_test_given['key_material']['spend_priv_key'])},\n"
284 out +=
gen_outputs(recv_test_given[
'outputs'],
"outputs to scan", prepend_count=
True)
286 full_check =
'outputs' in recv_test_expected
287 out += f
" {int(full_check)}, /* full check? */\n"
289 expected_pubkeys = [o[
'pub_key']
for o
in recv_test_expected[
'outputs']]
290 expected_tweaks = [o[
'priv_key_tweak']
for o
in recv_test_expected[
'outputs']]
291 expected_signatures = [o[
'signature']
for o
in recv_test_expected[
'outputs']]
292 out +=
" /* expected output data (pubkeys and seckey tweaks) */\n"
294 expected_pubkeys = []
296 expected_signatures = []
297 out +=
" /* expected number of outputs */\n"
298 out += f
" {recv_test_expected['n_outputs']},\n"
300 out +=
gen_outputs(expected_pubkeys, prepend_count=full_check)
304 for _
in range(len(test_vector[
'receiving']), MAX_RECEIVE_SUBTESTS):
305 out +=
' { "", "", 0, {"",}, 0, {0,}, 0, 0, {"",}, {"",}, {"",} }, /* unused receive subtest entry */\n'
312 if len(sys.argv) != 2:
316 filename_input = sys.argv[1]
317 with open(filename_input)
as f:
318 test_vectors = json.load(f)
323if __name__ ==
"__main__":
def gen_test_vectors(test_vectors)
def gen_recipient_addr_material(recipients)
def gen_sending_outputs(output_sets, comment=None, prepend_count=False)
def gen_outputs(outputs, comment=None, prepend_count=False, indent=8)
def gen_key_material(keys, comment=None, prepend_count=False)
def maybe_gen_comment(comment)
def get_pubkey_from_input(input_data, pubkey_hex)
def smallest_outpoint(outpoints)
def gen_preamble(test_vectors)