/***********************************************************************
 * Distributed under the MIT software license, see the accompanying    *
 * file COPYING or https://www.opensource.org/licenses/mit-license.php.*
 ***********************************************************************/

#ifndef SECP256K1_MODULE_CHILLDKG_SIMPLPEDPOP_H
#define SECP256K1_MODULE_CHILLDKG_SIMPLPEDPOP_H

#include "../../../include/secp256k1.h"

#include "util.h"
#include "vss.h"
#include "../../group.h"
#include "../../scalar.h"

/* This file contains the internal SimplPedPop sub-protocol of the ChillDKG
 * module, mirroring chilldkg_ref/simplpedpop.py of the bip-frost-dkg
 * reference implementation. Function decomposition and naming follow the
 * reference so that the two stay diffable.
 *
 * SimplPedPop is a non-interactive Pedersen VSS with a proof of possession
 * (pop) per participant. Messages are byte strings:
 *   pmsg (participant -> coordinator): com (33*t) || pop (64)
 *   cmsg (coordinator -> participants): coms_to_secrets (33*n) ||
 *        sum_coms_to_nonconst_terms (33*(t-1)) || pops (64*n)
 * The SimplPedPop contribution to the equality-check input is
 *   eq_input = u32be(t) || sum_coms (33*t)   [sum_coms is UNTWEAKED]
 * and is extended by the EncPedPop layer. */

/* The message over which a proof of possession is computed is
 * u32be(participant_id) (simplpedpop.py pop_msg); the tag prefix is
 * "BIP DKG/pop message" (POP_MSG_TAG). */

/* Compute a proof of possession (simplpedpop.py pop_prove): a BIP 340
 * signature with tag prefix "BIP DKG/pop message" over u32be(participant_id),
 * created with the given 32-byte secret key and aux_rand. Returns 1 on success
 * and 0 if the secret key is invalid (negligible probability for VSS
 * coefficients). */
static int secp256k1_chilldkg_simplpedpop_pop_prove(const secp256k1_context *ctx, unsigned char *pop64, const unsigned char *seckey32, uint32_t participant_id, const unsigned char *aux_rand32);

/* Verify a proof of possession (simplpedpop.py pop_verify) against the x-only
 * encoding of the commitment to the secret of the claimed participant.
 * Returns 1 if the pop is valid and 0 otherwise. */
static int secp256k1_chilldkg_simplpedpop_pop_verify(const secp256k1_context *ctx, const unsigned char *pop64, const unsigned char *pubkey32, uint32_t participant_id);

/* Participant state (simplpedpop.py ParticipantState). */
typedef struct {
    uint32_t t;
    uint32_t n;
    uint32_t participant_id;
    secp256k1_ge com_to_secret;
} secp256k1_chilldkg_simplpedpop_participant_state;

/* DKG output (simplpedpop.py DKGOutput). secshare32 and the pubshares are the
 * TWEAKED values (after invalid_taproot_commit). The coordinator has no
 * secshare; in its output secshare32 is zero (None in the reference). Only
 * the first n entries of pubshares33 are filled. */
typedef struct {
    unsigned char secshare32[32];
    unsigned char thresh_pk33[33];
    unsigned char pubshares33[SECP256K1_CHILLDKG_MAX_PARTICIPANTS][33];
} secp256k1_chilldkg_simplpedpop_dkg_output;

/* Participant step 1 (simplpedpop.py participant_step1): derive the VSS
 * coefficients from seed32, compute the commitment and the shares for all n
 * participants, and prove possession of the secret (the constant coefficient
 * f(0)) with aux_rand32 as aux_rand.
 *
 * Outputs the state, the participant message msg (33*t + 64 bytes) and the n
 * partial secret shares (the caller must clear them after use). Returns 1 on
 * success and 0 on invalid input (t < 1, t > n, n or t exceeding
 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS, participant_id >= n) or if the
 * seed-derived coefficients overflow the group order or the secret is zero
 * (negligible probability). */
static int secp256k1_chilldkg_simplpedpop_participant_step1(const secp256k1_context *ctx, secp256k1_chilldkg_simplpedpop_participant_state *state, unsigned char *msg, secp256k1_scalar *partial_secshares, const unsigned char *seed32, uint32_t t, uint32_t n, uint32_t participant_id, const unsigned char *aux_rand32);

/* Sum the partial secshares received from all participants
 * (simplpedpop.py participant_step2_prepare_secshare). */
static void secp256k1_chilldkg_simplpedpop_participant_step2_prepare_secshare(secp256k1_scalar *out, const secp256k1_scalar *partial_secshares, size_t n);

/* Investigation data captured when participant_step2 fails with
 * SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR
 * (simplpedpop.py ParticipantInvestigationData). Contains secrets (the
 * untweaked secshare); clear after use. */
typedef struct {
    uint32_t n;
    uint32_t participant_id;
    secp256k1_scalar secshare; /* untweaked */
    secp256k1_ge pubshare;     /* untweaked */
} secp256k1_chilldkg_simplpedpop_participant_inv_data;

/* Participant step 2 (simplpedpop.py participant_step2): verify the
 * coordinator message, i.e., check that our own commitment to the secret was
 * echoed correctly, verify the pops of all other participants against the
 * x-only encodings of their commitments to the secrets, and check the tweaked
 * secshare against the tweaked pubshare (the tweak is applied before the share
 * verification). Outputs the DKG output and the SimplPedPop contribution to
 * eq_input (4 + 33*t bytes).
 *
 * cmsg must be exactly 97*n + 33*(t-1) bytes. Returns SECP256K1_CHILLDKG_OK
 * on success; otherwise a fault code (with *fault_index set when applicable):
 *  - FAULTY_COORDINATOR if cmsg is malformed or the echo of our own
 *    commitment to the secret is wrong;
 *  - FAULTY_PARTICIPANT_OR_COORDINATOR(i) if participant i sent the point at
 *    infinity as commitment to their secret or an invalid pop;
 *  - UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR if the tweaked secshare does
 *    not match the tweaked pubshare; in this case, and only in this case,
 *    inv_data (if not NULL) is filled for
 *    secp256k1_chilldkg_simplpedpop_participant_investigate. */
static secp256k1_chilldkg_fault secp256k1_chilldkg_simplpedpop_participant_step2(const secp256k1_context *ctx, secp256k1_chilldkg_simplpedpop_dkg_output *dkg_output, unsigned char *eq_input, uint32_t *fault_index, secp256k1_chilldkg_simplpedpop_participant_inv_data *inv_data, const secp256k1_chilldkg_simplpedpop_participant_state *state, const unsigned char *cmsg, size_t cmsg_len, const secp256k1_scalar *secshare);

/* Determine the faulty party after an
 * UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR fault of participant_step2
 * (simplpedpop.py participant_investigate): check that the n partial
 * pubshares sum to the pubshare, that the n partial secshares sum to the
 * secshare, and that every partial secshare matches its partial pubshare.
 * Returns FAULTY_COORDINATOR, or FAULTY_PARTICIPANT_OR_COORDINATOR(i) with
 * *fault_index = i. If all inputs are consistent (i.e., the function was
 * called even though no fault occurred), returns SECP256K1_CHILLDKG_INVALID_INPUT
 * (the reference raises a RuntimeError in this case). */
static secp256k1_chilldkg_fault secp256k1_chilldkg_simplpedpop_participant_investigate(const secp256k1_context *ctx, uint32_t *fault_index, const secp256k1_chilldkg_simplpedpop_participant_inv_data *inv_data, const secp256k1_ge *partial_pubshares, const secp256k1_scalar *partial_secshares);

/* Assemble the summed VSS commitment (simplpedpop.py assemble_sum_coms):
 * sum_coms[0] = sum of the n coms_to_secrets, sum_coms[j] =
 * sum_coms_to_nonconst_terms[j-1] for j > 0. All inputs are public. */
static void secp256k1_chilldkg_simplpedpop_assemble_sum_coms(secp256k1_ge *sum_coms, const secp256k1_ge *coms_to_secrets, const secp256k1_ge *sum_coms_to_nonconst_terms, size_t n, size_t t);

/* Coordinator step (simplpedpop.py coordinator_step): parse the n participant
 * messages (each 33*t + 64 bytes), echo the per-participant commitments to
 * the secrets, sum the commitments to the non-constant terms, and compute the
 * tweaked DKG output (no secshare) and the SimplPedPop contribution to
 * eq_input (4 + 33*t bytes). The pops are NOT verified here (participants
 * verify them in step 2).
 *
 * Outputs cmsg (97*n + 33*(t-1) bytes). Returns SECP256K1_CHILLDKG_OK on
 * success and SECP256K1_CHILLDKG_FAULTY_PARTICIPANT (with *fault_index) if a
 * participant message is malformed. */
static secp256k1_chilldkg_fault secp256k1_chilldkg_simplpedpop_coordinator_step(const secp256k1_context *ctx, unsigned char *cmsg, secp256k1_chilldkg_simplpedpop_dkg_output *dkg_output, unsigned char *eq_input, uint32_t *fault_index, const unsigned char *const *pmsgs, uint32_t t, uint32_t n);

#endif
