Bitcoin Core  29.1.0
P2P Digital Currency
test_vectors_musig2_generate.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 import sys
4 import json
5 import textwrap
6 
7 max_pubkeys = 0
8 
9 if 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 
21 def create_init(name):
22  return """
23 static const struct musig_%s_vector musig_%s_vector = {
24 """ % (
25  name,
26  name,
27  )
28 
29 
30 def init_array(key):
31  return textwrap.indent("{ %s },\n" % hexstr_to_intarray(data[key]), 4 * " ")
32 
33 
34 def 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 
43 def init_indices(array):
44  return " %d, { %s }" % (
45  len(array),
46  ", ".join(map(str, array) if len(array) > 0 else "0"),
47  )
48 
49 
50 def 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 
60 def 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 
72 s = (
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 
83 s += """
84 enum 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
97 with 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  """
115 struct 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 += """
124 struct 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 += """
140 struct 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 
165  def comment_to_error(case):
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
189 with 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 += """
200 struct 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  """
218 struct 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 
227  def init_array_maybe(array):
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
251 with 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 += """
264 struct 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 += """
274 struct 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
301 with 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 
308  def filter_msg32(k):
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. */
343 struct 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  """
357 struct 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 += """
370 struct 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 += """
387 struct 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
484 with 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 += """
500 struct 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 += """
520 struct 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
576 with 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. */
595 struct 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 += """
618 struct 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()
655 s += "enum { MUSIG_VECTORS_MAX_PUBKEYS = %d };" % max_pubkeys
656 print(s)