#!/usr/bin/env python3
"""Converts the ChillDKG test vectors of the bip-frost-dkg reference
repository into a C header used by the tests in src/modules/chilldkg/tests_impl.h.

Usage: tools/test_vectors_chilldkg_generate.py <dir>

<dir> must contain the vector files of the bip-frost-dkg repository
(https://github.com/BlockstreamResearch/bip-frost-dkg, directory vectors/)
pinned at commit a91896883f85b159415ecf298d5e844879af112d.
"""

import sys
import json
import textwrap

if len(sys.argv) < 2:
    print(__doc__)
    sys.exit(1)

VEC_DIR = sys.argv[1]

FILES = {
    "hostpubkey_gen": "hostpubkey_gen_vectors.json",
    "params_hash": "params_hash_vectors.json",
    "participant_step1": "participant_step1_vectors.json",
    "participant_step2": "participant_step2_vectors.json",
    "participant_finalize": "participant_finalize_vectors.json",
    "participant_investigate": "participant_investigate_vectors.json",
    "coordinator_step1": "coordinator_step1_vectors.json",
    "coordinator_finalize": "coordinator_finalize_vectors.json",
    "coordinator_investigate": "coordinator_investigate_vectors.json",
    "recover": "recover_vectors.json",
}

data = {k: json.load(open(VEC_DIR + "/" + f)) for k, f in FILES.items()}

skipped_cases = []


def hexstr_to_intarray(s):
    # Always emitted inside brace initializers. C90 forbids empty initializer
    # braces, so an empty byte string becomes "0" (the associated length field
    # is 0, so the padding byte is never read).
    return ", ".join([f"0x{b:02X}" for b in bytes.fromhex(s)]) or "0"


def byte_array(hex_str):
    return "{ %s }" % hexstr_to_intarray(hex_str)


def indent(s, level=1):
    return textwrap.indent(s, 4 * level * " ")


def map_error(case, step_name):
    """Maps the reference expectedError of an error case to a
    chilldkg_vec_error code and fault index. Returns None if the case is not
    expressible in the C API (wrong-length or wrong-count inputs; the C API
    uses fixed-size buffers whose lengths derive from the session
    parameters)."""
    err = case["expectedError"]
    etype = err["type"]
    if etype == "ValueError":
        skipped_cases.append(
            "%s tcId %d (%s): not expressible in the C API (wrong input length or count)"
            % (step_name, case["tcId"], case.get("comment", "").strip())
        )
        return None
    fault_index = "UINT32_MAX"
    if "participantId" in err:
        assert etype in ("FaultyParticipantError", "FaultyParticipantOrCoordinatorError", "InvalidHostPubkeyError", "DuplicateHostPubkeyError")
        fault_index = str(err["participantId"])
    if etype in ("HostSeckeyError", "ThresholdOrCountError", "InvalidHostPubkeyError",
                 "DuplicateHostPubkeyError", "RandomnessError", "RecoveryDataError"):
        return "CHILLDKG_VEC_INVALID_INPUT", "UINT32_MAX"
    if etype == "FaultyCoordinatorError":
        return "CHILLDKG_VEC_FAULTY_COORDINATOR", fault_index
    if etype == "FaultyParticipantError":
        return "CHILLDKG_VEC_FAULTY_PARTICIPANT", fault_index
    if etype == "FaultyParticipantOrCoordinatorError":
        return "CHILLDKG_VEC_FAULTY_PARTICIPANT_OR_COORDINATOR", fault_index
    if etype == "UnknownFaultyParticipantOrCoordinatorError":
        return "CHILLDKG_VEC_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR", fault_index
    sys.exit("Unknown error type %s in %s tcId %d" % (etype, step_name, case["tcId"]))


def groups(d):
    return d["testGroups"] if "testGroups" in d else [d]


def all_cases(d):
    for g in groups(d):
        for c in g.get("validTestCases", []) + g.get("errorTestCases", []):
            yield g, c


def get_params(case, group):
    return case.get("params", group.get("params"))


# Compute the shared maximum sizes.
max_n = 0
max_t = 0
max_pmsg1 = 0
max_cmsg1 = 0
max_cmsg2 = 0
max_recovery = 0
max_cinv = 0
max_pmsg1_pool = 0
max_pmsg2_pool = 0
max_cmsg1_pool = 0
max_indices = 0
for name, d in data.items():
    for g, c in all_cases(d):
        p = get_params(c, g)
        if p is not None:
            max_n = max(max_n, len(p["hostpubkeys"]))
            max_t = max(max_t, p["t"])
        for key, cur in (("expectedPmsg1", "pmsg1"), ("cmsg1", "cmsg1"), ("cmsg2", "cmsg2"),
                         ("recoveryData", "recovery"), ("cinvMsg", "cinv")):
            v = c.get(key)
            if isinstance(v, str):
                if cur == "pmsg1":
                    max_pmsg1 = max(max_pmsg1, len(v) // 2)
                elif cur == "cmsg1":
                    max_cmsg1 = max(max_cmsg1, len(v) // 2)
                elif cur == "cmsg2":
                    max_cmsg2 = max(max_cmsg2, len(v) // 2)
                elif cur == "recovery":
                    max_recovery = max(max_recovery, len(v) // 2)
                elif cur == "cinv":
                    max_cinv = max(max_cinv, len(v) // 2)
        if "pmsg1Indices" in c:
            max_indices = max(max_indices, len(c["pmsg1Indices"]))
        if "pmsg2Indices" in c:
            max_indices = max(max_indices, len(c["pmsg2Indices"]))
    for g in groups(d):
        if "pmsg1" in g and isinstance(g["pmsg1"], str):
            max_pmsg1 = max(max_pmsg1, len(g["pmsg1"]) // 2)
        if "cmsg1" in g and isinstance(g["cmsg1"], str):
            max_cmsg1 = max(max_cmsg1, len(g["cmsg1"]) // 2)
        if "pmsgs1" in g:
            max_n = max(max_n, len(g["pmsgs1"]))
            for m in g["pmsgs1"]:
                max_pmsg1 = max(max_pmsg1, len(m) // 2)
        if "pmsg1Pool" in g:
            max_pmsg1_pool = max(max_pmsg1_pool, len(g["pmsg1Pool"]))
            for m in g["pmsg1Pool"]:
                max_pmsg1 = max(max_pmsg1, len(m) // 2)
        if "pmsg2Pool" in g:
            max_pmsg2_pool = max(max_pmsg2_pool, len(g["pmsg2Pool"]))
        if "cmsg1Pool" in g:
            max_cmsg1_pool = max(max_cmsg1_pool, len(g["cmsg1Pool"]))
            for m in g["cmsg1Pool"]:
                max_cmsg1 = max(max_cmsg1, len(m) // 2)
        for c in g.get("validTestCases", []):
            if "expectedCinvMsgs" in c:
                for m in c["expectedCinvMsgs"]:
                    max_cinv = max(max_cinv, len(m) // 2)
            eo = c.get("expectedOutput")
            if eo is not None:
                if "recoveryData" in eo:
                    max_recovery = max(max_recovery, len(eo["recoveryData"]) // 2)
                if "cmsg2" in eo:
                    max_cmsg2 = max(max_cmsg2, len(eo["cmsg2"]) // 2)
        for c in g.get("validTestCases", []) + g.get("errorTestCases", []):
            if isinstance(c.get("recoveryData"), str):
                max_recovery = max(max_recovery, len(c["recoveryData"]) // 2)

s = """/**
 * Automatically generated by %s.
 *
 * The test vectors are from the bip-frost-dkg reference repository
 * https://github.com/BlockstreamResearch/bip-frost-dkg, pinned at commit
 * a91896883f85b159415ecf298d5e844879af112d (BIP version 0.3.0-dev). They are
 * used by the tests in src/modules/chilldkg/tests_impl.h.
 *
 * Regenerate with:
 *   tools/test_vectors_chilldkg_generate.py <dir> > src/modules/chilldkg/vectors.h
 * where <dir> is the vectors/ directory of the reference repository at the
 * commit above. */
#ifndef SECP256K1_MODULE_CHILLDKG_VECTORS_H
#define SECP256K1_MODULE_CHILLDKG_VECTORS_H

#include <stddef.h>
#include <stdint.h>

""" % sys.argv[0]

s += """/* The expected result of an error test case, mapped from the reference
 * exception taxonomy; the values coincide with secp256k1_chilldkg_fault.
 * CHILLDKG_VEC_INVALID_INPUT covers the local input errors (ValueError
 * subclasses that are not protocol faults: HostSeckeyError,
 * ThresholdOrCountError, InvalidHostPubkeyError, DuplicateHostPubkeyError,
 * RandomnessError, RecoveryDataError). */
enum chilldkg_vec_error {
    CHILLDKG_VEC_OK = 0,
    CHILLDKG_VEC_FAULTY_COORDINATOR = 1,
    CHILLDKG_VEC_FAULTY_PARTICIPANT = 2,
    CHILLDKG_VEC_FAULTY_PARTICIPANT_OR_COORDINATOR = 3,
    CHILLDKG_VEC_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR = 4,
    CHILLDKG_VEC_INVALID_INPUT = 5
};

enum {
    CHILLDKG_VEC_MAX_PARTICIPANTS = %d,
    CHILLDKG_VEC_MAX_PMSG1_LEN = %d,
    CHILLDKG_VEC_MAX_CMSG1_LEN = %d,
    CHILLDKG_VEC_MAX_CMSG2_LEN = %d,
    CHILLDKG_VEC_MAX_RECOVERY_LEN = %d,
    CHILLDKG_VEC_MAX_CINV_LEN = %d,
    CHILLDKG_VEC_MAX_PMSG1_POOL = %d,
    CHILLDKG_VEC_MAX_PMSG2_POOL = %d,
    CHILLDKG_VEC_MAX_CMSG1_POOL = %d,
    CHILLDKG_VEC_MAX_INDICES = %d
};

/* Session parameters (hostpubkeys || t). */
struct chilldkg_vec_params {
    uint32_t t;
    size_t n;
    unsigned char hostpubkeys[CHILLDKG_VEC_MAX_PARTICIPANTS][33];
};

/* The DKG output of a participant or the coordinator (has_secshare == 0). */
struct chilldkg_vec_dkg_output {
    int has_secshare;
    unsigned char secshare[32];
    unsigned char thresh_pk[33];
    unsigned char pubshares[CHILLDKG_VEC_MAX_PARTICIPANTS][33];
};
""" % (max_n, max_pmsg1, max_cmsg1, max_cmsg2, max_recovery, max_cinv,
       max_pmsg1_pool, max_pmsg2_pool, max_cmsg1_pool, max_indices)


def init_params(p):
    inner = "\n" + indent(",\n".join(byte_array(h) for h in p["hostpubkeys"]), 2) + "\n"
    return "{ %d, %d, {%s} }" % (p["t"], len(p["hostpubkeys"]), inner)


def init_dkg_output(o):
    return "{ %d, %s, %s, {\n%s\n} }" % (
        0 if o["secshare"] is None else 1,
        byte_array(o["secshare"]) if o["secshare"] is not None else "{ 0 }",
        byte_array(o["threshPk"]),
        indent(",\n".join(byte_array(p) for p in o["pubshares"]), 2),
    )


def case_list(d):
    """Returns the (is_valid, case) list of a vector file (flat per-group
    files) with inexpressible error cases removed."""
    out = []
    for g in groups(d):
        for c in g.get("validTestCases", []):
            out.append((1, c))
        for c in g.get("errorTestCases", []):
            out.append((0, c))
    return out


# hostpubkey_gen vectors
cases = []
for is_valid, c in case_list(data["hostpubkey_gen"]):
    if not is_valid and map_error(c, "hostpubkey_gen") is None:
        continue
    cases.append((is_valid, c))
s += """
struct chilldkg_vec_hostpubkey_gen_case {
    int is_valid;
    unsigned char hostseckey[32];
    unsigned char expected_hostpubkey[33];
};

static const struct chilldkg_vec_hostpubkey_gen_case chilldkg_vec_hostpubkey_gen_cases[%d] = {
""" % len(cases)
for is_valid, c in cases:
    s += indent("{ %d, %s, %s },\n" % (
        is_valid,
        byte_array(c["hostseckey"]),
        byte_array(c.get("expectedHostpubkey") or ""),
    ))
s += "};\n"

# params_hash vectors
cases = []
for is_valid, c in case_list(data["params_hash"]):
    if not is_valid and map_error(c, "params_hash") is None:
        continue
    cases.append((is_valid, c))
s += """
struct chilldkg_vec_params_hash_case {
    int is_valid;
    struct chilldkg_vec_params params;
    unsigned char expected_hash[32];
};

static const struct chilldkg_vec_params_hash_case chilldkg_vec_params_hash_cases[%d] = {
""" % len(cases)
for is_valid, c in cases:
    s += indent("{ %d,\n%s,\n%s },\n" % (
        is_valid,
        indent(init_params(c["params"]) + ",", 1)[:-1],
        byte_array(c.get("expectedParamsHash") or ""),
    ))
s += "};\n"

# participant_step1 vectors
cases = []
for is_valid, c in case_list(data["participant_step1"]):
    if not is_valid and map_error(c, "participant_step1") is None:
        continue
    cases.append((is_valid, c))
s += """
struct chilldkg_vec_step1_case {
    int is_valid;
    unsigned char hostseckey[32];
    struct chilldkg_vec_params params;
    unsigned char random[32];
    unsigned char expected_pmsg1[CHILLDKG_VEC_MAX_PMSG1_LEN];
};

static const struct chilldkg_vec_step1_case chilldkg_vec_step1_cases[%d] = {
""" % len(cases)
for is_valid, c in cases:
    s += indent("{ %d, %s,\n%s,\n%s, %s },\n" % (
        is_valid,
        byte_array(c["hostseckey"]),
        indent(init_params(c["params"]) + ",", 1)[:-1],
        byte_array(c["random"]),
        byte_array(c.get("expectedPmsg1") or ""),
    ))
s += "};\n"

# participant_step2 vectors
s += """
struct chilldkg_vec_step2_group {
    struct chilldkg_vec_params params;
    unsigned char hostseckey[32];
    unsigned char random[32];
    unsigned char aux_rand[32];
    unsigned char pmsg1[CHILLDKG_VEC_MAX_PMSG1_LEN];
};

struct chilldkg_vec_step2_case {
    int is_valid;
    /* hostseckey override (error cases only); has_hostseckey == 0 means the
     * group's hostseckey is used. */
    int has_hostseckey;
    unsigned char hostseckey[32];
    unsigned char cmsg1[CHILLDKG_VEC_MAX_CMSG1_LEN];
    /* Expected result: CHILLDKG_VEC_OK or an enum chilldkg_vec_error value,
     * mapped to secp256k1_chilldkg_fault / return-0 as in the tests. */
    int expected_error;
    uint32_t expected_fault_index;
    unsigned char expected_pmsg2[64];
};

"""
s += "static const struct chilldkg_vec_step2_group chilldkg_vec_step2_groups[%d] = {\n" % len(groups(data["participant_step2"]))
for g in groups(data["participant_step2"]):
    s += indent("{\n%s,\n%s, %s, %s, %s },\n" % (
        indent(init_params(g["params"]) + ",", 1)[:-1],
        byte_array(g["hostseckey"]),
        byte_array(g["random"]),
        byte_array(g["auxRand"]),
        byte_array(g["pmsg1"]),
    ))
s += "};\n"
step2_cases = []
for gi, g in enumerate(groups(data["participant_step2"])):
    for c in g.get("validTestCases", []):
        step2_cases.append((gi, 1, c))
    for c in g.get("errorTestCases", []):
        if map_error(c, "participant_step2") is None:
            continue
        step2_cases.append((gi, 0, c))
s += "static const struct chilldkg_vec_step2_case chilldkg_vec_step2_cases[%d] = {\n" % len(step2_cases)
for gi, is_valid, c in step2_cases:
    if is_valid:
        error_code, fault_index = "CHILLDKG_VEC_OK", "UINT32_MAX"
        expected = byte_array(c["expectedPmsg2"])
    else:
        error_code, fault_index = map_error(c, "participant_step2")
        expected = "{ 0 }"
    has_hsk = 1 if "hostseckey" in c else 0
    s += indent("{ %d, %d, %s,\n%s,\n%s, %s, %s },\n" % (
        is_valid,
        has_hsk,
        byte_array(c.get("hostseckey") or "00" * 32),
        indent(byte_array(c["cmsg1"]) + ",", 1)[:-1],
        error_code, fault_index, expected,
    ))
# Note: the group index is implicit (cases are emitted group by group).
s += "};\n"
s += "static const size_t chilldkg_vec_step2_group_case_counts[%d] = {\n" % len(groups(data["participant_step2"]))
gi = 0
for g in groups(data["participant_step2"]):
    cnt = sum(1 for x in step2_cases if x[0] == gi)
    s += indent("%d,\n" % cnt)
    gi += 1
s += "};\n"

# participant_finalize vectors
s += """
struct chilldkg_vec_finalize_group {
    struct chilldkg_vec_params params;
    unsigned char hostseckey[32];
    unsigned char random[32];
    unsigned char aux_rand[32];
    unsigned char pmsg1[CHILLDKG_VEC_MAX_PMSG1_LEN];
    unsigned char cmsg1[CHILLDKG_VEC_MAX_CMSG1_LEN];
    unsigned char pmsg2[64];
};

struct chilldkg_vec_finalize_case {
    int is_valid;
    unsigned char cmsg2[CHILLDKG_VEC_MAX_CMSG2_LEN];
    /* Expected result: CHILLDKG_VEC_OK or CHILLDKG_VEC_FAULTY_COORDINATOR. */
    int expected_error;
    struct chilldkg_vec_dkg_output expected_output;
    unsigned char expected_recovery[CHILLDKG_VEC_MAX_RECOVERY_LEN];
};

"""
s += "static const struct chilldkg_vec_finalize_group chilldkg_vec_finalize_groups[%d] = {\n" % len(groups(data["participant_finalize"]))
for g in groups(data["participant_finalize"]):
    s += indent("{\n%s,\n%s, %s, %s, %s,\n%s,\n%s },\n" % (
        indent(init_params(g["params"]) + ",", 1)[:-1],
        byte_array(g["hostseckey"]),
        byte_array(g["random"]),
        byte_array(g["auxRand"]),
        byte_array(g["pmsg1"]),
        indent(byte_array(g["cmsg1"]) + ",", 1)[:-1],
        byte_array(g["pmsg2"]),
    ))
s += "};\n"
finalize_cases = []
for gi, g in enumerate(groups(data["participant_finalize"])):
    for c in g.get("validTestCases", []):
        finalize_cases.append((gi, 1, c))
    for c in g.get("errorTestCases", []):
        if map_error(c, "participant_finalize") is None:
            continue
        finalize_cases.append((gi, 0, c))
s += "static const struct chilldkg_vec_finalize_case chilldkg_vec_finalize_cases[%d] = {\n" % len(finalize_cases)
for gi, is_valid, c in finalize_cases:
    if is_valid:
        error_code = "CHILLDKG_VEC_OK"
        eo = init_dkg_output(c["expectedOutput"]["dkgOutput"])
        rec = byte_array(c["expectedOutput"]["recoveryData"])
    else:
        error_code, _ = map_error(c, "participant_finalize")
        eo = "{ 0, { 0 }, { 0 }, { { 0 } } }"
        rec = "{ 0 }"
    s += indent("{ %d,\n%s,\n%s,\n%s,\n%s },\n" % (
        is_valid,
        indent(byte_array(c["cmsg2"]) + ",", 1)[:-1],
        error_code,
        indent(eo + ",", 1)[:-1],
        indent(rec, 1),
    ))
s += "};\n"
s += "static const size_t chilldkg_vec_finalize_group_case_counts[%d] = {\n" % len(groups(data["participant_finalize"]))
for gi in range(len(groups(data["participant_finalize"]))):
    cnt = sum(1 for x in finalize_cases if x[0] == gi)
    s += indent("%d,\n" % cnt)
s += "};\n"

# participant_investigate vectors
s += """
struct chilldkg_vec_investigate_group {
    struct chilldkg_vec_params params;
    unsigned char hostseckey[32];
    unsigned char random[32];
    unsigned char aux_rand[32];
    unsigned char pmsg1[CHILLDKG_VEC_MAX_PMSG1_LEN];
    size_t n_cmsg1;
    unsigned char cmsg1_pool[CHILLDKG_VEC_MAX_CMSG1_POOL][CHILLDKG_VEC_MAX_CMSG1_LEN];
};

struct chilldkg_vec_investigate_case {
    size_t cmsg1_index;
    unsigned char cinv[CHILLDKG_VEC_MAX_CINV_LEN];
    /* Expected result of participant_investigate. */
    int expected_error;
    uint32_t expected_fault_index;
};

"""
s += "static const struct chilldkg_vec_investigate_group chilldkg_vec_investigate_groups[%d] = {\n" % len(groups(data["participant_investigate"]))
for g in groups(data["participant_investigate"]):
    pool = g["cmsg1Pool"]
    inner = "\n" + indent(",\n".join(byte_array(m) for m in pool), 2) + "\n"
    s += indent("{\n%s,\n%s, %s, %s, %s,\n%s, {%s} },\n" % (
        indent(init_params(g["params"]) + ",", 1)[:-1],
        byte_array(g["hostseckey"]),
        byte_array(g["random"]),
        byte_array(g["auxRand"]),
        byte_array(g["pmsg1"]),
        len(pool),
        inner,
    ))
s += "};\n"
investigate_cases = []
for gi, g in enumerate(groups(data["participant_investigate"])):
    for c in g.get("errorTestCases", []):
        investigate_cases.append((gi, c))
s += "static const struct chilldkg_vec_investigate_case chilldkg_vec_investigate_cases[%d] = {\n" % len(investigate_cases)
for gi, c in investigate_cases:
    error_code, fault_index = map_error(c, "participant_investigate")
    s += indent("{ %d,\n%s,\n%s, %s },\n" % (
        c["cmsg1Index"],
        indent(byte_array(c["cinvMsg"]) + ",", 1)[:-1],
        error_code, fault_index,
    ))
s += "};\n"
s += "static const size_t chilldkg_vec_investigate_group_case_counts[%d] = {\n" % len(groups(data["participant_investigate"]))
for gi in range(len(groups(data["participant_investigate"]))):
    cnt = sum(1 for x in investigate_cases if x[0] == gi)
    s += indent("%d,\n" % cnt)
s += "};\n"

# coordinator_step1 vectors
s += """
struct chilldkg_vec_coord_step1_group {
    size_t n_pmsg1_pool;
    unsigned char pmsg1_pool[CHILLDKG_VEC_MAX_PMSG1_POOL][CHILLDKG_VEC_MAX_PMSG1_LEN];
};

struct chilldkg_vec_coord_step1_case {
    int is_valid;
    size_t n_pmsgs1;
    size_t pmsg1_indices[CHILLDKG_VEC_MAX_INDICES];
    struct chilldkg_vec_params params;
    /* Expected result: CHILLDKG_VEC_OK or an enum chilldkg_vec_error value. */
    int expected_error;
    uint32_t expected_fault_index;
    unsigned char expected_cmsg1[CHILLDKG_VEC_MAX_CMSG1_LEN];
};

"""
s += "static const struct chilldkg_vec_coord_step1_group chilldkg_vec_coord_step1_groups[%d] = {\n" % len(groups(data["coordinator_step1"]))
for g in groups(data["coordinator_step1"]):
    pool = g["pmsg1Pool"]
    inner = "\n" + indent(",\n".join(byte_array(m) for m in pool), 2) + "\n"
    s += indent("{ %d, {%s} },\n" % (len(pool), inner))
s += "};\n"
coord_step1_cases = []
for gi, g in enumerate(groups(data["coordinator_step1"])):
    for c in g.get("validTestCases", []):
        coord_step1_cases.append((gi, 1, c))
    for c in g.get("errorTestCases", []):
        if map_error(c, "coordinator_step1") is None:
            continue
        coord_step1_cases.append((gi, 0, c))
s += "static const struct chilldkg_vec_coord_step1_case chilldkg_vec_coord_step1_cases[%d] = {\n" % len(coord_step1_cases)
for gi, is_valid, c in coord_step1_cases:
    if is_valid:
        error_code, fault_index = "CHILLDKG_VEC_OK", "UINT32_MAX"
        expected = byte_array(c["expectedCmsg1"])
    else:
        error_code, fault_index = map_error(c, "coordinator_step1")
        expected = "{ 0 }"
    s += indent("{ %d, %d, { %s },\n%s,\n%s, %s,\n%s },\n" % (
        is_valid,
        len(c["pmsg1Indices"]),
        ", ".join(map(str, c["pmsg1Indices"])),
        indent(init_params(c["params"]) + ",", 1)[:-1],
        error_code, fault_index,
        indent(expected, 1),
    ))
s += "};\n"
s += "static const size_t chilldkg_vec_coord_step1_group_case_counts[%d] = {\n" % len(groups(data["coordinator_step1"]))
for gi in range(len(groups(data["coordinator_step1"]))):
    cnt = sum(1 for x in coord_step1_cases if x[0] == gi)
    s += indent("%d,\n" % cnt)
s += "};\n"

# coordinator_finalize vectors
s += """
struct chilldkg_vec_coord_finalize_group {
    struct chilldkg_vec_params params;
    unsigned char pmsgs1[CHILLDKG_VEC_MAX_PARTICIPANTS][CHILLDKG_VEC_MAX_PMSG1_LEN];
    unsigned char cmsg1[CHILLDKG_VEC_MAX_CMSG1_LEN];
    size_t n_pmsg2_pool;
    unsigned char pmsg2_pool[CHILLDKG_VEC_MAX_PMSG2_POOL][64];
};

struct chilldkg_vec_coord_finalize_case {
    int is_valid;
    size_t n_pmsgs2;
    size_t pmsg2_indices[CHILLDKG_VEC_MAX_INDICES];
    /* Expected result: CHILLDKG_VEC_OK or CHILLDKG_VEC_FAULTY_PARTICIPANT. */
    int expected_error;
    uint32_t expected_fault_index;
    unsigned char expected_cmsg2[CHILLDKG_VEC_MAX_CMSG2_LEN];
    struct chilldkg_vec_dkg_output expected_output;
    unsigned char expected_recovery[CHILLDKG_VEC_MAX_RECOVERY_LEN];
};

"""
s += "static const struct chilldkg_vec_coord_finalize_group chilldkg_vec_coord_finalize_groups[%d] = {\n" % len(groups(data["coordinator_finalize"]))
for g in groups(data["coordinator_finalize"]):
    pool = g["pmsg2Pool"]
    p1_inner = "\n" + indent(",\n".join(byte_array(m) for m in g["pmsgs1"]), 2) + "\n"
    p2_inner = "\n" + indent(",\n".join(byte_array(m) for m in pool), 2) + "\n"
    s += indent("{\n%s,\n{%s},\n%s,\n%s, {%s} },\n" % (
        indent(init_params(g["params"]) + ",", 1)[:-1],
        p1_inner,
        indent(byte_array(g["cmsg1"]) + ",", 1)[:-1],
        len(pool),
        p2_inner,
    ))
s += "};\n"
coord_finalize_cases = []
for gi, g in enumerate(groups(data["coordinator_finalize"])):
    for c in g.get("validTestCases", []):
        coord_finalize_cases.append((gi, 1, c))
    for c in g.get("errorTestCases", []):
        if map_error(c, "coordinator_finalize") is None:
            continue
        coord_finalize_cases.append((gi, 0, c))
s += "static const struct chilldkg_vec_coord_finalize_case chilldkg_vec_coord_finalize_cases[%d] = {\n" % len(coord_finalize_cases)
for gi, is_valid, c in coord_finalize_cases:
    if is_valid:
        error_code, fault_index = "CHILLDKG_VEC_OK", "UINT32_MAX"
        expected_cmsg2 = byte_array(c["expectedOutput"]["cmsg2"])
        eo = init_dkg_output(c["expectedOutput"]["dkgOutput"])
        rec = byte_array(c["expectedOutput"]["recoveryData"])
    else:
        error_code, fault_index = map_error(c, "coordinator_finalize")
        expected_cmsg2 = "{ 0 }"
        eo = "{ 0, { 0 }, { 0 }, { { 0 } } }"
        rec = "{ 0 }"
    s += indent("{ %d, %d, { %s }, %s, %s,\n%s,\n%s,\n%s },\n" % (
        is_valid,
        len(c["pmsg2Indices"]),
        ", ".join(map(str, c["pmsg2Indices"])),
        error_code, fault_index,
        indent(expected_cmsg2 + ",", 1)[:-1],
        indent(eo + ",", 1)[:-1],
        indent(rec, 1),
    ))
s += "};\n"
s += "static const size_t chilldkg_vec_coord_finalize_group_case_counts[%d] = {\n" % len(groups(data["coordinator_finalize"]))
for gi in range(len(groups(data["coordinator_finalize"]))):
    cnt = sum(1 for x in coord_finalize_cases if x[0] == gi)
    s += indent("%d,\n" % cnt)
s += "};\n"

# coordinator_investigate vectors
s += """
struct chilldkg_vec_coord_investigate_group {
    struct chilldkg_vec_params params;
    unsigned char pmsgs1[CHILLDKG_VEC_MAX_PARTICIPANTS][CHILLDKG_VEC_MAX_PMSG1_LEN];
    unsigned char expected_cinv[CHILLDKG_VEC_MAX_PARTICIPANTS][CHILLDKG_VEC_MAX_CINV_LEN];
};

"""
s += "static const struct chilldkg_vec_coord_investigate_group chilldkg_vec_coord_investigate_groups[%d] = {\n" % len(groups(data["coordinator_investigate"]))
for g in groups(data["coordinator_investigate"]):
    p1_inner = "\n" + indent(",\n".join(byte_array(m) for m in g["pmsgs1"]), 2) + "\n"
    cinv_inner = "\n" + indent(",\n".join(byte_array(m) for m in g["validTestCases"][0]["expectedCinvMsgs"]), 2) + "\n"
    s += indent("{\n%s,\n{%s},\n{%s} },\n" % (
        indent(init_params(g["params"]) + ",", 1)[:-1],
        p1_inner,
        cinv_inner,
    ))
s += "};\n"

# recover vectors
s += """
struct chilldkg_vec_recover_case {
    int is_valid;
    /* Coordinator recovery if has_hostseckey == 0. */
    int has_hostseckey;
    unsigned char hostseckey[32];
    size_t recovery_len;
    unsigned char recovery[CHILLDKG_VEC_MAX_RECOVERY_LEN];
    /* Expected result: CHILLDKG_VEC_OK or CHILLDKG_VEC_INVALID_INPUT. */
    int expected_error;
    struct chilldkg_vec_dkg_output expected_output;
    struct chilldkg_vec_params expected_params;
};

"""
recover_cases = []
for c in data["recover"]["validTestCases"]:
    recover_cases.append((1, c))
for c in data["recover"]["errorTestCases"]:
    if map_error(c, "recover") is None:
        continue
    recover_cases.append((0, c))
s += "static const struct chilldkg_vec_recover_case chilldkg_vec_recover_cases[%d] = {\n" % len(recover_cases)
for is_valid, c in recover_cases:
    if is_valid:
        error_code = "CHILLDKG_VEC_OK"
        eo = init_dkg_output(c["expectedOutput"]["dkgOutput"])
        ep = init_params(c["expectedOutput"]["params"])
    else:
        error_code, _ = map_error(c, "recover")
        eo = "{ 0, { 0 }, { 0 }, { { 0 } } }"
        ep = "{ 0, 0, { { 0 } } }"
    s += indent("{ %d, %d, %s, %d,\n%s,\n%s,\n%s,\n%s },\n" % (
        is_valid,
        1 if c["hostseckey"] else 0,
        byte_array(c["hostseckey"] or "00" * 32),
        len(bytes.fromhex(c["recoveryData"])),
        indent(byte_array(c["recoveryData"]) + ",", 1)[:-1],
        error_code,
        indent(eo + ",", 1)[:-1],
        indent(ep, 1),
    ))
s += "};\n"

if skipped_cases:
    s += "\n/* Skipped test cases (not expressible in the C API):\n"
    for case in skipped_cases:
        s += " * - %s\n" % case
    s += " */\n"

s += "\n#endif\n"
print(s)
