Bitcoin Core 28.99.0
P2P Digital Currency
test_vectors_musig2_generate.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2
3import sys
4import json
5import textwrap
6
7max_pubkeys = 0
8
9if len(sys.argv) < 2:
10 print(
11 "This script converts BIP MuSig2 test vectors in a given directory to a C file that can be used in the test framework."
12 )
13 print("Usage: %s <dir>" % sys.argv[0])
14 sys.exit(1)
15
16
18 return ", ".join([f"0x{b:02X}" for b in bytes.fromhex(str)])
19
20
21def create_init(name):
22 return """
23static const struct musig_%s_vector musig_%s_vector = {
24""" % (
25 name,
26 name,
27 )
28
29
30def init_array(key):
31 return textwrap.indent("{ %s },\n" % hexstr_to_intarray(data[key]), 4 * " ")
32
33
34def init_arrays(key):
35 s = textwrap.indent("{\n", 4 * " ")
36 s += textwrap.indent(
37 ",\n".join(["{ %s }" % hexstr_to_intarray(x) for x in data[key]]), 8 * " "
38 )
39 s += textwrap.indent("\n},\n", 4 * " ")
40 return s
41
42
43def init_indices(array):
44 return " %d, { %s }" % (
45 len(array),
46 ", ".join(map(str, array) if len(array) > 0 else "0"),
47 )
48
49
50def init_is_xonly(case):
51 if len(case["tweak_indices"]) > 0:
52 return ", ".join(map(lambda x: "1" if x else "0", case["is_xonly"]))
53 return "0"
54
55
57 return hexstr_to_intarray(case["expected"]) if "expected" in case else 0
58
59
60def init_cases(cases, f):
61 s = textwrap.indent("{\n", 4 * " ")
62 for (i, case) in enumerate(cases):
63 s += textwrap.indent("%s\n" % f(case), 8 * " ")
64 s += textwrap.indent("},\n", 4 * " ")
65 return s
66
67
69 return "};\n"
70
71
72s = (
73 """/**
74 * Automatically generated by %s.
75 *
76 * The test vectors for the KeySort function are included in this file. They can
77 * be found in src/modules/extrakeys/tests_impl.h. */
78"""
79 % sys.argv[0]
80)
81
82
83s += """
84enum MUSIG_ERROR {
85 MUSIG_PUBKEY,
86 MUSIG_TWEAK,
87 MUSIG_PUBNONCE,
88 MUSIG_AGGNONCE,
89 MUSIG_SECNONCE,
90 MUSIG_SIG,
91 MUSIG_SIG_VERIFY,
92 MUSIG_OTHER
93};
94"""
95
96# key agg vectors
97with open(sys.argv[1] + "/key_agg_vectors.json", "r") as f:
98 data = json.load(f)
99
100 max_key_indices = max(
101 len(test_case["key_indices"]) for test_case in data["valid_test_cases"]
102 )
103 max_tweak_indices = max(
104 len(test_case["tweak_indices"]) for test_case in data["error_test_cases"]
105 )
106 num_pubkeys = len(data["pubkeys"])
107 max_pubkeys = max(num_pubkeys, max_pubkeys)
108 num_tweaks = len(data["tweaks"])
109 num_valid_cases = len(data["valid_test_cases"])
110 num_error_cases = len(data["error_test_cases"])
111
112 # Add structures for valid and error cases
113 s += (
114 """
115struct musig_key_agg_valid_test_case {
116 size_t key_indices_len;
117 size_t key_indices[%d];
118 unsigned char expected[32];
119};
120"""
121 % max_key_indices
122 )
123 s += """
124struct musig_key_agg_error_test_case {
125 size_t key_indices_len;
126 size_t key_indices[%d];
127 size_t tweak_indices_len;
128 size_t tweak_indices[%d];
129 int is_xonly[%d];
130 enum MUSIG_ERROR error;
131};
132""" % (
133 max_key_indices,
134 max_tweak_indices,
135 max_tweak_indices,
136 )
137
138 # Add structure for entire vector
139 s += """
140struct musig_key_agg_vector {
141 unsigned char pubkeys[%d][33];
142 unsigned char tweaks[%d][32];
143 struct musig_key_agg_valid_test_case valid_case[%d];
144 struct musig_key_agg_error_test_case error_case[%d];
145};
146""" % (
147 num_pubkeys,
148 num_tweaks,
149 num_valid_cases,
150 num_error_cases,
151 )
152
153 s += create_init("key_agg")
154 # Add pubkeys and tweaks to the vector
155 s += init_arrays("pubkeys")
156 s += init_arrays("tweaks")
157
158 # Add valid cases to the vector
159 s += init_cases(
160 data["valid_test_cases"],
161 lambda case: "{ %s, { %s }},"
162 % (init_indices(case["key_indices"]), hexstr_to_intarray(case["expected"])),
163 )
164
166 comment = case["comment"]
167 if "public key" in comment.lower():
168 return "MUSIG_PUBKEY"
169 elif "tweak" in comment.lower():
170 return "MUSIG_TWEAK"
171 else:
172 sys.exit("Unknown error")
173
174 # Add error cases to the vector
175 s += init_cases(
176 data["error_test_cases"],
177 lambda case: "{ %s, %s, { %s }, %s },"
178 % (
179 init_indices(case["key_indices"]),
180 init_indices(case["tweak_indices"]),
181 init_is_xonly(case),
182 comment_to_error(case),
183 ),
184 )
185
186 s += finish_init()
187
188# nonce gen vectors
189with open(sys.argv[1] + "/nonce_gen_vectors.json", "r") as f:
190 data = json.load(f)
191
192 # The MuSig2 implementation only allows messages of length 32
193 data["test_cases"] = list(
194 filter(lambda c: c["msg"] is None or len(c["msg"]) == 64, data["test_cases"])
195 )
196
197 num_tests = len(data["test_cases"])
198
199 s += """
200struct musig_nonce_gen_test_case {
201 unsigned char rand_[32];
202 int has_sk;
203 unsigned char sk[32];
204 unsigned char pk[33];
205 int has_aggpk;
206 unsigned char aggpk[32];
207 int has_msg;
208 unsigned char msg[32];
209 int has_extra_in;
210 unsigned char extra_in[32];
211 unsigned char expected_secnonce[97];
212 unsigned char expected_pubnonce[66];
213};
214"""
215
216 s += (
217 """
218struct musig_nonce_gen_vector {
219 struct musig_nonce_gen_test_case test_case[%d];
220};
221"""
222 % num_tests
223 )
224
225 s += create_init("nonce_gen")
226
228 return "%d , { %s }" % (
229 0 if array is None else 1,
230 hexstr_to_intarray(array) if array is not None else 0,
231 )
232
233 s += init_cases(
234 data["test_cases"],
235 lambda case: "{ { %s }, %s, { %s }, %s, %s, %s, { %s }, { %s } },"
236 % (
237 hexstr_to_intarray(case["rand_"]),
238 init_array_maybe(case["sk"]),
239 hexstr_to_intarray(case["pk"]),
240 init_array_maybe(case["aggpk"]),
241 init_array_maybe(case["msg"]),
242 init_array_maybe(case["extra_in"]),
243 hexstr_to_intarray(case["expected_secnonce"]),
244 hexstr_to_intarray(case["expected_pubnonce"]),
245 ),
246 )
247
248 s += finish_init()
249
250# nonce agg vectors
251with open(sys.argv[1] + "/nonce_agg_vectors.json", "r") as f:
252 data = json.load(f)
253
254 num_pnonces = len(data["pnonces"])
255 num_valid_cases = len(data["valid_test_cases"])
256 num_error_cases = len(data["error_test_cases"])
257
258 pnonce_indices_len = 2
259 for case in data["valid_test_cases"] + data["error_test_cases"]:
260 assert len(case["pnonce_indices"]) == pnonce_indices_len
261
262 # Add structures for valid and error cases
263 s += """
264struct musig_nonce_agg_test_case {
265 size_t pnonce_indices[2];
266 /* if valid case */
267 unsigned char expected[66];
268 /* if error case */
269 int invalid_nonce_idx;
270};
271"""
272 # Add structure for entire vector
273 s += """
274struct musig_nonce_agg_vector {
275 unsigned char pnonces[%d][66];
276 struct musig_nonce_agg_test_case valid_case[%d];
277 struct musig_nonce_agg_test_case error_case[%d];
278};
279""" % (
280 num_pnonces,
281 num_valid_cases,
282 num_error_cases,
283 )
284
285 s += create_init("nonce_agg")
286 s += init_arrays("pnonces")
287
288 for cases in (data["valid_test_cases"], data["error_test_cases"]):
289 s += init_cases(
290 cases,
291 lambda case: "{ { %s }, { %s }, %d },"
292 % (
293 ", ".join(map(str, case["pnonce_indices"])),
295 case["error"]["signer"] if "error" in case else 0,
296 ),
297 )
298 s += finish_init()
299
300# sign/verify vectors
301with open(sys.argv[1] + "/sign_verify_vectors.json", "r") as f:
302 data = json.load(f)
303
304 # The MuSig2 implementation only allows messages of length 32
305 assert list(filter(lambda x: len(x) == 64, data["msgs"]))[0] == data["msgs"][0]
306 data["msgs"] = [data["msgs"][0]]
307
309 return list(filter(lambda x: x["msg_index"] == 0, data[k]))
310
311 data["valid_test_cases"] = filter_msg32("valid_test_cases")
312 data["sign_error_test_cases"] = filter_msg32("sign_error_test_cases")
313 data["verify_error_test_cases"] = filter_msg32("verify_error_test_cases")
314 data["verify_fail_test_cases"] = filter_msg32("verify_fail_test_cases")
315
316 num_pubkeys = len(data["pubkeys"])
317 max_pubkeys = max(num_pubkeys, max_pubkeys)
318 num_secnonces = len(data["secnonces"])
319 num_pubnonces = len(data["pnonces"])
320 num_aggnonces = len(data["aggnonces"])
321 num_msgs = len(data["msgs"])
322 num_valid_cases = len(data["valid_test_cases"])
323 num_sign_error_cases = len(data["sign_error_test_cases"])
324 num_verify_fail_cases = len(data["verify_fail_test_cases"])
325 num_verify_error_cases = len(data["verify_error_test_cases"])
326
327 all_cases = (
328 data["valid_test_cases"]
329 + data["sign_error_test_cases"]
330 + data["verify_error_test_cases"]
331 + data["verify_fail_test_cases"]
332 )
333 max_key_indices = max(len(test_case["key_indices"]) for test_case in all_cases)
334 max_nonce_indices = max(
335 len(test_case["nonce_indices"]) if "nonce_indices" in test_case else 0
336 for test_case in all_cases
337 )
338 # Add structures for valid and error cases
339 s += (
340 """
341/* Omit pubnonces in the test vectors because our partial signature verification
342 * implementation is able to accept the aggnonce directly. */
343struct musig_valid_case {
344 size_t key_indices_len;
345 size_t key_indices[%d];
346 size_t aggnonce_index;
347 size_t msg_index;
348 size_t signer_index;
349 unsigned char expected[32];
350};
351"""
352 % max_key_indices
353 )
354
355 s += (
356 """
357struct musig_sign_error_case {
358 size_t key_indices_len;
359 size_t key_indices[%d];
360 size_t aggnonce_index;
361 size_t msg_index;
362 size_t secnonce_index;
363 enum MUSIG_ERROR error;
364};
365"""
366 % max_key_indices
367 )
368
369 s += """
370struct musig_verify_fail_error_case {
371 unsigned char sig[32];
372 size_t key_indices_len;
373 size_t key_indices[%d];
374 size_t nonce_indices_len;
375 size_t nonce_indices[%d];
376 size_t msg_index;
377 size_t signer_index;
378 enum MUSIG_ERROR error;
379};
380""" % (
381 max_key_indices,
382 max_nonce_indices,
383 )
384
385 # Add structure for entire vector
386 s += """
387struct musig_sign_verify_vector {
388 unsigned char sk[32];
389 unsigned char pubkeys[%d][33];
390 unsigned char secnonces[%d][194];
391 unsigned char pubnonces[%d][194];
392 unsigned char aggnonces[%d][66];
393 unsigned char msgs[%d][32];
394 struct musig_valid_case valid_case[%d];
395 struct musig_sign_error_case sign_error_case[%d];
396 struct musig_verify_fail_error_case verify_fail_case[%d];
397 struct musig_verify_fail_error_case verify_error_case[%d];
398};
399""" % (
400 num_pubkeys,
401 num_secnonces,
402 num_pubnonces,
403 num_aggnonces,
404 num_msgs,
405 num_valid_cases,
406 num_sign_error_cases,
407 num_verify_fail_cases,
408 num_verify_error_cases,
409 )
410
411 s += create_init("sign_verify")
412 s += init_array("sk")
413 s += init_arrays("pubkeys")
414 s += init_arrays("secnonces")
415 s += init_arrays("pnonces")
416 s += init_arrays("aggnonces")
417 s += init_arrays("msgs")
418
419 s += init_cases(
420 data["valid_test_cases"],
421 lambda case: "{ %s, %d, %d, %d, { %s }},"
422 % (
423 init_indices(case["key_indices"]),
424 case["aggnonce_index"],
425 case["msg_index"],
426 case["signer_index"],
428 ),
429 )
430
431 def sign_error(case):
432 comment = case["comment"]
433 if "pubkey" in comment or "public key" in comment:
434 return "MUSIG_PUBKEY"
435 elif "Aggregate nonce" in comment:
436 return "MUSIG_AGGNONCE"
437 elif "Secnonce" in comment:
438 return "MUSIG_SECNONCE"
439 else:
440 sys.exit("Unknown sign error")
441
442 s += init_cases(
443 data["sign_error_test_cases"],
444 lambda case: "{ %s, %d, %d, %d, %s },"
445 % (
446 init_indices(case["key_indices"]),
447 case["aggnonce_index"],
448 case["msg_index"],
449 case["secnonce_index"],
450 sign_error(case),
451 ),
452 )
453
454 def verify_error(case):
455 comment = case["comment"]
456 if "exceeds" in comment:
457 return "MUSIG_SIG"
458 elif "Wrong signer" in comment or "Wrong signature" in comment:
459 return "MUSIG_SIG_VERIFY"
460 elif "pubnonce" in comment:
461 return "MUSIG_PUBNONCE"
462 elif "pubkey" in comment:
463 return "MUSIG_PUBKEY"
464 else:
465 sys.exit("Unknown verify error")
466
467 for cases in ("verify_fail_test_cases", "verify_error_test_cases"):
468 s += init_cases(
469 data[cases],
470 lambda case: "{ { %s }, %s, %s, %d, %d, %s },"
471 % (
472 hexstr_to_intarray(case["sig"]),
473 init_indices(case["key_indices"]),
474 init_indices(case["nonce_indices"]),
475 case["msg_index"],
476 case["signer_index"],
477 verify_error(case),
478 ),
479 )
480
481 s += finish_init()
482
483# tweak vectors
484with open(sys.argv[1] + "/tweak_vectors.json", "r") as f:
485 data = json.load(f)
486
487 num_pubkeys = len(data["pubkeys"])
488 max_pubkeys = max(num_pubkeys, max_pubkeys)
489 num_pubnonces = len(data["pnonces"])
490 num_tweaks = len(data["tweaks"])
491 num_valid_cases = len(data["valid_test_cases"])
492 num_error_cases = len(data["error_test_cases"])
493
494 all_cases = data["valid_test_cases"] + data["error_test_cases"]
495 max_key_indices = max(len(test_case["key_indices"]) for test_case in all_cases)
496 max_tweak_indices = max(len(test_case["tweak_indices"]) for test_case in all_cases)
497 max_nonce_indices = max(len(test_case["nonce_indices"]) for test_case in all_cases)
498 # Add structures for valid and error cases
499 s += """
500struct musig_tweak_case {
501 size_t key_indices_len;
502 size_t key_indices[%d];
503 size_t nonce_indices_len;
504 size_t nonce_indices[%d];
505 size_t tweak_indices_len;
506 size_t tweak_indices[%d];
507 int is_xonly[%d];
508 size_t signer_index;
509 unsigned char expected[32];
510};
511""" % (
512 max_key_indices,
513 max_nonce_indices,
514 max_tweak_indices,
515 max_tweak_indices,
516 )
517
518 # Add structure for entire vector
519 s += """
520struct musig_tweak_vector {
521 unsigned char sk[32];
522 unsigned char secnonce[97];
523 unsigned char aggnonce[66];
524 unsigned char msg[32];
525 unsigned char pubkeys[%d][33];
526 unsigned char pubnonces[%d][194];
527 unsigned char tweaks[%d][32];
528 struct musig_tweak_case valid_case[%d];
529 struct musig_tweak_case error_case[%d];
530};
531""" % (
532 num_pubkeys,
533 num_pubnonces,
534 num_tweaks,
535 num_valid_cases,
536 num_error_cases,
537 )
538 s += create_init("tweak")
539 s += init_array("sk")
540 s += init_array("secnonce")
541 s += init_array("aggnonce")
542 s += init_array("msg")
543 s += init_arrays("pubkeys")
544 s += init_arrays("pnonces")
545 s += init_arrays("tweaks")
546
547 s += init_cases(
548 data["valid_test_cases"],
549 lambda case: "{ %s, %s, %s, { %s }, %d, { %s }},"
550 % (
551 init_indices(case["key_indices"]),
552 init_indices(case["nonce_indices"]),
553 init_indices(case["tweak_indices"]),
554 init_is_xonly(case),
555 case["signer_index"],
557 ),
558 )
559
560 s += init_cases(
561 data["error_test_cases"],
562 lambda case: "{ %s, %s, %s, { %s }, %d, { %s }},"
563 % (
564 init_indices(case["key_indices"]),
565 init_indices(case["nonce_indices"]),
566 init_indices(case["tweak_indices"]),
567 init_is_xonly(case),
568 case["signer_index"],
570 ),
571 )
572
573 s += finish_init()
574
575# sigagg vectors
576with open(sys.argv[1] + "/sig_agg_vectors.json", "r") as f:
577 data = json.load(f)
578
579 num_pubkeys = len(data["pubkeys"])
580 max_pubkeys = max(num_pubkeys, max_pubkeys)
581 num_tweaks = len(data["tweaks"])
582 num_psigs = len(data["psigs"])
583 num_valid_cases = len(data["valid_test_cases"])
584 num_error_cases = len(data["error_test_cases"])
585
586 all_cases = data["valid_test_cases"] + data["error_test_cases"]
587 max_key_indices = max(len(test_case["key_indices"]) for test_case in all_cases)
588 max_tweak_indices = max(len(test_case["tweak_indices"]) for test_case in all_cases)
589 max_psig_indices = max(len(test_case["psig_indices"]) for test_case in all_cases)
590
591 # Add structures for valid and error cases
592 s += """
593/* Omit pubnonces in the test vectors because they're only needed for
594 * implementations that do not directly accept an aggnonce. */
595struct musig_sig_agg_case {
596 size_t key_indices_len;
597 size_t key_indices[%d];
598 size_t tweak_indices_len;
599 size_t tweak_indices[%d];
600 int is_xonly[%d];
601 unsigned char aggnonce[66];
602 size_t psig_indices_len;
603 size_t psig_indices[%d];
604 /* if valid case */
605 unsigned char expected[64];
606 /* if error case */
607 int invalid_sig_idx;
608};
609""" % (
610 max_key_indices,
611 max_tweak_indices,
612 max_tweak_indices,
613 max_psig_indices,
614 )
615
616 # Add structure for entire vector
617 s += """
618struct musig_sig_agg_vector {
619 unsigned char pubkeys[%d][33];
620 unsigned char tweaks[%d][32];
621 unsigned char psigs[%d][32];
622 unsigned char msg[32];
623 struct musig_sig_agg_case valid_case[%d];
624 struct musig_sig_agg_case error_case[%d];
625};
626""" % (
627 num_pubkeys,
628 num_tweaks,
629 num_psigs,
630 num_valid_cases,
631 num_error_cases,
632 )
633
634 s += create_init("sig_agg")
635 s += init_arrays("pubkeys")
636 s += init_arrays("tweaks")
637 s += init_arrays("psigs")
638 s += init_array("msg")
639
640 for cases in (data["valid_test_cases"], data["error_test_cases"]):
641 s += init_cases(
642 cases,
643 lambda case: "{ %s, %s, { %s }, { %s }, %s, { %s }, %d },"
644 % (
645 init_indices(case["key_indices"]),
646 init_indices(case["tweak_indices"]),
647 init_is_xonly(case),
648 hexstr_to_intarray(case["aggnonce"]),
649 init_indices(case["psig_indices"]),
651 case["error"]["signer"] if "error" in case else 0,
652 ),
653 )
654 s += finish_init()
655s += "enum { MUSIG_VECTORS_MAX_PUBKEYS = %d };" % max_pubkeys
656print(s)