test_vectors_musig2_generate.py raw

   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  
  17  def hexstr_to_intarray(str):
  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  
  56  def init_optional_expected(case):
  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  
  68  def finish_init():
  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"])),
 294                  init_optional_expected(case),
 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"],
 427              init_optional_expected(case),
 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"],
 556              init_optional_expected(case),
 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"],
 569              init_optional_expected(case),
 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"]),
 650                  init_optional_expected(case),
 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)
 657