Bitcoin Core
32.1.0
P2P Digital Currency
Toggle main menu visibility
Loading...
Searching...
No Matches
src
secp256k1
tools
tests_silentpayments_generate.py
Go to the documentation of this file.
1
#!/usr/bin/env python3
2
3
"""
4
A script to convert BIP352 test vectors from JSON to a C header.
5
6
Usage:
7
8
./tools/tests_silentpayments_generate.py src/modules/silentpayments/bip352_send_and_receive_test_vectors.json > ./src/modules/silentpayments/vectors.h
9
"""
10
11
import
hashlib
12
import
json
13
import
sys
14
15
NUMS_H = bytes.fromhex(
"50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"
)
16
MAX_INPUTS_PER_TEST_CASE = 3
17
MAX_OUTPUTS_PER_TEST_CASE = 2324
# K_max + 1
18
MAX_RECIPIENT_ENTRIES_PER_TEST_CASE = 4
19
MAX_LABELS_PER_TEST_CASE = 4
20
MAX_PERMUTATIONS_PER_SENDING_TEST_CASE = 12
21
MAX_RECEIVE_SUBTESTS = 2
22
23
def
sha256
(s):
24
return
hashlib.sha256(s).digest()
25
26
def
smallest_outpoint
(outpoints):
27
serialized_outpoints = [bytes.fromhex(txid)[::-1] + n.to_bytes(4,
'little'
)
for
txid, n
in
outpoints]
28
return
sorted(serialized_outpoints)[0]
29
30
def
is_p2tr
(s):
# OP_1 OP_PUSHBYTES_32 <32 bytes>
31
return
(len(s) == 34)
and
(s[0] == 0x51)
and
(s[1] == 0x20)
32
33
def
get_pubkey_from_input
(input_data, pubkey_hex):
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)
37
38
if
is_p2tr
(spk):
# taproot input
39
# Check for NUMS_H in witness (should be skipped)
40
witness = bytes.fromhex(input_data.get(
'txinwitness'
,
''
))
41
# Parse witness stack
42
witness_stack = []
43
num_witness_items = 0
44
if
len(witness) > 0:
45
num_witness_items = witness[0]
46
witness = witness[1:]
47
for
i
in
range(num_witness_items):
48
item_len = witness[0]
49
witness_stack.append(witness[1:item_len+1])
50
witness = witness[item_len+1:]
51
52
# Check for script-path spend with NUMS_H
53
if
len(witness_stack) > 1
and
witness_stack[-1][0] == 0x50:
54
witness_stack.pop()
55
if
len(witness_stack) > 1:
# script-path spend?
56
control_block = witness_stack[-1]
57
internal_key = control_block[1:33]
58
if
internal_key == NUMS_H:
# skip
59
return
b
''
60
61
# Convert to x-only (32 bytes) for taproot
62
if
len(pubkey) == 33:
63
pubkey = pubkey[1:]
# Remove prefix byte
64
return
pubkey
65
else
:
# regular input - use full compressed pubkey (33 bytes)
66
return
pubkey
67
68
def
gen_byte_array
(hex):
69
assert
len(hex) % 2 == 0
70
if
hex ==
""
:
71
return
"{0x00}"
72
s =
',0x'
.join(a + b
for
a, b
in
zip(hex[::2], hex[1::2]))
73
return
"{0x"
+ s +
"}"
74
75
def
maybe_gen_comment
(comment):
76
if
comment:
77
return
f
" /* {comment} */"
78
else
:
79
return
""
80
81
def
gen_key_material
(keys, comment=None, prepend_count=False):
82
assert
len(keys) <= MAX_INPUTS_PER_TEST_CASE
83
out =
""
84
if
prepend_count:
85
out += f
" {len(keys)},\n"
86
out += f
" {{{maybe_gen_comment(comment)}\n"
87
for
k
in
keys:
88
out += f
" {gen_byte_array(k)},\n"
89
if
not
keys:
90
out +=
' "",\n'
91
out +=
" },\n"
92
return
out
93
94
def
gen_recipient_addr_material
(recipients):
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):
102
out +=
" {\n"
103
if
i < len(recipients):
104
# Use the scan_pubkey and spend_pubkey directly from the recipient
105
scan_pubkey = bytes.fromhex(recipients[i][
'scan_pub_key'
])
106
spend_pubkey = bytes.fromhex(recipients[i][
'spend_pub_key'
])
107
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"
111
else
:
112
out +=
' "",\n'
113
out +=
' "",\n'
114
out +=
' 0,\n'
115
out +=
" },\n"
116
out +=
" },\n"
117
return
out
118
119
def
gen_sending_outputs
(output_sets, comment=None, prepend_count=False):
120
assert
len(output_sets) <= MAX_PERMUTATIONS_PER_SENDING_TEST_CASE
121
out =
""
122
if
prepend_count:
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)
128
if
not
output_sets:
129
out +=
gen_outputs
(comment=
None
, outputs=[], prepend_count=
False
, indent=12)
130
out +=
" },\n"
131
return
out
132
133
def
gen_outputs
(outputs, comment=None, prepend_count=False, indent=8):
134
out =
""
135
spaces = indent *
" "
136
if
prepend_count:
137
out += spaces + f
"{len(outputs)},\n"
138
out += f
" {{{maybe_gen_comment(comment)}\n"
139
for
o
in
outputs:
140
out += spaces + f
" {gen_byte_array(o)},\n"
141
if
not
outputs:
142
out += spaces +
' "",\n'
143
out += spaces +
"},\n"
144
return
out
145
146
def
gen_labels
(labels):
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):
151
if
i < len(labels):
152
out += f
"{labels[i]}, "
153
else
:
154
out +=
"0xffffffff, "
155
out +=
"},\n"
156
return
out
157
158
def
gen_preamble
(test_vectors):
159
out =
"/* Note: this file was autogenerated using tests_silentpayments_generate.py. Do not edit. */\n"
160
out += f
"""
161
#include <stddef.h>
162
163
#define MAX_INPUTS_PER_TEST_CASE {MAX_INPUTS_PER_TEST_CASE}
164
#define MAX_OUTPUTS_PER_TEST_CASE {MAX_OUTPUTS_PER_TEST_CASE}
165
#define MAX_RECIPIENT_ENTRIES_PER_TEST_CASE {MAX_RECIPIENT_ENTRIES_PER_TEST_CASE}
166
#define MAX_LABELS_PER_TEST_CASE {MAX_LABELS_PER_TEST_CASE}
167
#define MAX_PERMUTATIONS_PER_SENDING_TEST_CASE {MAX_PERMUTATIONS_PER_SENDING_TEST_CASE}
168
#define MAX_RECEIVE_SUBTESTS {MAX_RECEIVE_SUBTESTS}
169
170
struct bip352_recipient_addressdata {{
171
unsigned char scan_pubkey[33];
172
unsigned char spend_pubkey[33];
173
size_t count;
174
}};
175
176
struct 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];
182
size_t num_labels;
183
unsigned int label_integers[MAX_LABELS_PER_TEST_CASE];
184
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];
191
}};
192
193
struct 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];
202
203
/* Given sender data (pubkeys encoded per output address to send to) */
204
size_t num_outputs;
205
size_t num_recipient_entries;
206
struct bip352_recipient_addressdata recipient_pubkeys[MAX_RECIPIENT_ENTRIES_PER_TEST_CASE];
207
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];
212
213
size_t num_receive_subtests;
214
struct bip352_receive_subtest receive_subtests[MAX_RECEIVE_SUBTESTS];
215
}};
216
"""
217
return
out
218
219
def
gen_test_vectors
(test_vectors):
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):
223
# determine input private and public keys, grouped into plain and taproot/x-only
224
input_plain_seckeys = []
225
input_taproot_seckeys = []
226
input_plain_pubkeys = []
227
input_xonly_pubkeys = []
228
outpoints = []
229
230
# To keep this script simple and the generated test vectors file (vectors.h) small, we take
231
# use of the fact that currently all BIP352 test vectors only have a single sending subtest.
232
# If that ever changes in the future, the following assertion would fail and this script,
233
# its emitted data structures and the test code consuming the generated header would need to
234
# be extended, in a similar way to how it's already done for the receiving subtests.
235
assert
len(test_vector[
'sending'
]) == 1
236
237
pubkey_index = 0
238
input_pubkeys_hex = test_vector[
'sending'
][0][
'expected'
][
'input_pub_keys'
]
239
240
for
vec
in
test_vector[
'sending'
][0][
'given'
][
'vin'
]:
241
outpoints.append((vec[
'txid'
], vec[
'vout'
]))
242
243
if
pubkey_index < len(input_pubkeys_hex):
244
pubkey =
get_pubkey_from_input
(vec, input_pubkeys_hex[pubkey_index])
245
if
len(pubkey) == 33:
# regular input
246
input_plain_seckeys.append(vec[
'private_key'
])
247
input_plain_pubkeys.append(pubkey.hex())
248
pubkey_index += 1
249
elif
len(pubkey) == 32:
# taproot input
250
input_taproot_seckeys.append(vec[
'private_key'
])
251
input_xonly_pubkeys.append(pubkey.hex())
252
pubkey_index += 1
253
# len(pubkey) == 0, it's a NUMS_H input - skip without incrementing
254
255
out += f
" /* ----- {test_vector['comment']} ({test_i + 1}) ----- */\n"
256
out +=
" {\n"
257
258
outpoint_L =
smallest_outpoint
(outpoints).hex()
259
out +=
gen_key_material
(input_plain_seckeys,
"input plain seckeys"
, prepend_count=
True
)
260
out +=
gen_key_material
(input_plain_pubkeys,
"input plain pubkeys"
)
261
out +=
gen_key_material
(input_taproot_seckeys,
"input taproot seckeys"
, prepend_count=
True
)
262
out +=
gen_key_material
(input_xonly_pubkeys,
"input x-only pubkeys"
)
263
out +=
" /* smallest outpoint */\n"
264
out += f
" {gen_byte_array(outpoint_L)},\n"
265
266
# emit recipient pubkeys (address data)
267
out +=
gen_recipient_addr_material
(test_vector[
'sending'
][0][
'given'
][
'recipients'
])
268
# emit recipient outputs
269
out +=
gen_sending_outputs
(test_vector[
'sending'
][0][
'expected'
][
'outputs'
],
"recipient outputs"
, prepend_count=
True
)
270
271
assert
len(test_vector[
'receiving'
]) <= MAX_RECEIVE_SUBTESTS
272
out += f
" {len(test_vector['receiving'])}, /* number of receive subtests */\n"
273
out +=
" {\n"
274
for
recv_test
in
test_vector[
'receiving'
]:
275
out +=
" {\n"
276
# emit recipient scan/spend seckeys
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"
282
283
# emit recipient to-scan outputs, labels and expected-found outputs
284
out +=
gen_outputs
(recv_test_given[
'outputs'
],
"outputs to scan"
, prepend_count=
True
)
285
out +=
gen_labels
(recv_test_given[
'labels'
])
286
full_check =
'outputs'
in
recv_test_expected
287
out += f
" {int(full_check)}, /* full check? */\n"
288
if
full_check:
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"
293
else
:
294
expected_pubkeys = []
295
expected_tweaks = []
296
expected_signatures = []
297
out +=
" /* expected number of outputs */\n"
298
out += f
" {recv_test_expected['n_outputs']},\n"
299
300
out +=
gen_outputs
(expected_pubkeys, prepend_count=full_check)
301
out +=
gen_outputs
(expected_tweaks)
302
out +=
gen_outputs
(expected_signatures)
303
out +=
" },\n"
304
for
_
in
range(len(test_vector[
'receiving'
]), MAX_RECEIVE_SUBTESTS):
305
out +=
' { "", "", 0, {"",}, 0, {0,}, 0, 0, {"",}, {"",}, {"",} }, /* unused receive subtest entry */\n'
306
out +=
" },\n"
307
out +=
" },\n\n"
308
out +=
"};\n"
309
return
out
310
311
def
main
():
312
if
len(sys.argv) != 2:
313
print(__doc__)
314
sys.exit(1)
315
316
filename_input = sys.argv[1]
317
with
open(filename_input)
as
f:
318
test_vectors = json.load(f)
319
320
print(
gen_preamble
(test_vectors))
321
print(
gen_test_vectors
(test_vectors), end=
""
)
322
323
if
__name__ ==
"__main__"
:
324
main
()
sum
volatile double sum
Definition
examples.cpp:10
sha256
Internal SHA-256 implementation.
Definition
sha256.cpp:68
tests_silentpayments_generate.get_pubkey_from_input
get_pubkey_from_input(input_data, pubkey_hex)
Definition
tests_silentpayments_generate.py:33
tests_silentpayments_generate.is_p2tr
is_p2tr(s)
Definition
tests_silentpayments_generate.py:30
tests_silentpayments_generate.main
main()
Definition
tests_silentpayments_generate.py:311
tests_silentpayments_generate.gen_test_vectors
gen_test_vectors(test_vectors)
Definition
tests_silentpayments_generate.py:219
tests_silentpayments_generate.smallest_outpoint
smallest_outpoint(outpoints)
Definition
tests_silentpayments_generate.py:26
tests_silentpayments_generate.maybe_gen_comment
maybe_gen_comment(comment)
Definition
tests_silentpayments_generate.py:75
tests_silentpayments_generate.gen_byte_array
gen_byte_array(hex)
Definition
tests_silentpayments_generate.py:68
tests_silentpayments_generate.gen_labels
gen_labels(labels)
Definition
tests_silentpayments_generate.py:146
tests_silentpayments_generate.gen_preamble
gen_preamble(test_vectors)
Definition
tests_silentpayments_generate.py:158
tests_silentpayments_generate.gen_recipient_addr_material
gen_recipient_addr_material(recipients)
Definition
tests_silentpayments_generate.py:94
tests_silentpayments_generate.gen_sending_outputs
gen_sending_outputs(output_sets, comment=None, prepend_count=False)
Definition
tests_silentpayments_generate.py:119
tests_silentpayments_generate.gen_outputs
gen_outputs(outputs, comment=None, prepend_count=False, indent=8)
Definition
tests_silentpayments_generate.py:133
tests_silentpayments_generate.gen_key_material
gen_key_material(keys, comment=None, prepend_count=False)
Definition
tests_silentpayments_generate.py:81
Generated on
for Bitcoin Core by
1.18.0