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

#ifndef SECP256K1_MODULE_CHILLDKG_VSS_IMPL_H
#define SECP256K1_MODULE_CHILLDKG_VSS_IMPL_H

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

#include "util.h"
#include "vss.h"
#include "../../ecmult.h"
#include "../../util.h"

static int secp256k1_chilldkg_vss_gen_coeffs(const secp256k1_hash_ctx *hash_ctx, secp256k1_scalar *coeffs, size_t t, const unsigned char *seed32) {
    unsigned char buf[4];
    unsigned char hash32[32];
    secp256k1_sha256 sha;
    size_t j;
    /* Bitwise (not short-circuiting) so that this does not branch on the
     * secret-derived overflow; the caller declassifies the return value. */
    int ok = 1;

    for (j = 0; j < t; j++) {
        int overflow;
        secp256k1_chilldkg_sha256_tagged_vss_coeffs(hash_ctx, &sha);
        secp256k1_sha256_write(hash_ctx, &sha, seed32, 32);
        secp256k1_write_be32(buf, (uint32_t)j);
        secp256k1_sha256_write(hash_ctx, &sha, buf, sizeof(buf));
        secp256k1_sha256_finalize(hash_ctx, &sha, hash32);
        /* from_bytes_checked in the reference: coefficients that overflow the
         * group order are rejected. */
        secp256k1_scalar_set_b32(&coeffs[j], hash32, &overflow);
        ok &= !overflow;
    }
    secp256k1_sha256_clear(&sha);
    secp256k1_memclear_explicit(buf, sizeof(buf));
    secp256k1_memclear_explicit(hash32, sizeof(hash32));
    return ok;
}

static void secp256k1_chilldkg_vss_poly_eval(secp256k1_scalar *out, const secp256k1_scalar *coeffs, size_t t, const secp256k1_scalar *x) {
    size_t j = t;

    /* Horner's method, iterating over the coefficients from the
     * highest-degree term down to the constant term. */
    secp256k1_scalar_set_int(out, 0);
    while (j > 0) {
        j--;
        secp256k1_scalar_mul(out, out, x);
        secp256k1_scalar_add(out, out, &coeffs[j]);
    }
}

static void secp256k1_chilldkg_vss_secshare_for(secp256k1_scalar *out, const secp256k1_scalar *coeffs, size_t t, uint32_t id) {
    secp256k1_scalar x;

    /* x = id + 1, computed in scalar arithmetic so that id == UINT32_MAX
     * does not overflow. This never computes f(0) because x != 0 for all
     * uint32_t ids. */
    secp256k1_scalar_set_int(&x, id);
    secp256k1_scalar_add(&x, &x, &secp256k1_scalar_one);
    secp256k1_chilldkg_vss_poly_eval(out, coeffs, t, &x);
    secp256k1_scalar_clear(&x);
}

static void secp256k1_chilldkg_vss_commit(const secp256k1_context *ctx, secp256k1_ge *coms, const secp256k1_scalar *coeffs, size_t t) {
    size_t j;

    VERIFY_CHECK(ctx != NULL);
    for (j = 0; j < t; j++) {
        secp256k1_gej comj;
        /* Constant-time; a zero coefficient (negligible probability) results
         * in the point at infinity, matching the reference. */
        secp256k1_ecmult_gen_gej(&ctx->ecmult_gen_ctx, &comj, &coeffs[j]);
        secp256k1_ge_set_gej(&coms[j], &comj);
        /* The commitment is public (it is part of the participant message);
         * declassify it so that the variable-time serialization is allowed
         * to branch on it. */
        secp256k1_declassify(ctx, &coms[j], sizeof(coms[j]));
        secp256k1_gej_clear(&comj);
    }
}

static void secp256k1_chilldkg_vss_pubshare(secp256k1_gej *out, const secp256k1_ge *coms, size_t t, uint32_t id) {
    secp256k1_scalar x, power;
    size_t j;

    /* x = id + 1, computed in scalar arithmetic. All inputs are public, so
     * variable-time algorithms and branches are fine. */
    secp256k1_scalar_set_int(&x, id);
    secp256k1_scalar_add(&x, &x, &secp256k1_scalar_one);

    secp256k1_gej_set_infinity(out);
    secp256k1_scalar_set_int(&power, 1);
    for (j = 0; j < t; j++) {
        if (!secp256k1_ge_is_infinity(&coms[j])) {
            secp256k1_gej term;
            secp256k1_gej_set_ge(&term, &coms[j]);
            secp256k1_ecmult(&term, &term, &power, NULL);
            secp256k1_gej_add_var(out, out, &term, NULL);
        }
        secp256k1_scalar_mul(&power, &power, &x);
    }
    secp256k1_scalar_clear(&x);
    secp256k1_scalar_clear(&power);
}

static void secp256k1_chilldkg_vss_commitment_add(secp256k1_ge *out, const secp256k1_ge *a, const secp256k1_ge *b, size_t t) {
    size_t j;

    for (j = 0; j < t; j++) {
        secp256k1_gej aj, bj, sumj;
        secp256k1_gej_set_ge(&aj, &a[j]);
        secp256k1_gej_set_ge(&bj, &b[j]);
        /* gej_add_var handles operands at infinity. */
        secp256k1_gej_add_var(&sumj, &aj, &bj, NULL);
        secp256k1_ge_set_gej_var(&out[j], &sumj);
    }
}

static int secp256k1_chilldkg_vss_verify_secshare(const secp256k1_context *ctx, const secp256k1_scalar *secshare, const secp256k1_ge *pubshare) {
    secp256k1_ge actual;

    VERIFY_CHECK(ctx != NULL);
    secp256k1_ecmult_gen_ge(&ctx->ecmult_gen_ctx, &actual, secshare);
    /* We can declassify because in honest runs actual = secshare*G is the
     * (public) pubshare itself, and because the verification result is always
     * public: it determines a public fault code. The discrete log of actual
     * (the secret share) is not revealed. */
    secp256k1_declassify(ctx, &actual, sizeof(actual));
    if (secp256k1_ge_is_infinity(&actual) || secp256k1_ge_is_infinity(pubshare)) {
        return secp256k1_ge_is_infinity(&actual) && secp256k1_ge_is_infinity(pubshare);
    }
    return secp256k1_ge_eq_var(&actual, pubshare);
}

static int secp256k1_chilldkg_vss_invalid_taproot_commit(const secp256k1_context *ctx, secp256k1_ge *out_coms, secp256k1_scalar *tweak, secp256k1_ge *pubtweak, const secp256k1_ge *coms, size_t t) {
    const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(ctx);
    unsigned char pk32[32];
    unsigned char hash32[32];
    secp256k1_sha256 sha;
    secp256k1_gej tweakj, com0j;
    int overflow;
    size_t j;

    VERIFY_CHECK(ctx != NULL);
    /* The commitment to the secret must not be infinity (the reference would
     * fail to serialize it to x-only bytes). */
    if (t == 0 || secp256k1_ge_is_infinity(&coms[0])) {
        return 0;
    }

    secp256k1_chilldkg_xonly_save(pk32, &coms[0]);
    secp256k1_chilldkg_sha256_tagged_taptweak(hash_ctx, &sha);
    secp256k1_sha256_write(hash_ctx, &sha, pk32, 32);
    secp256k1_sha256_finalize(hash_ctx, &sha, hash32);
    secp256k1_sha256_clear(&sha);
    /* from_bytes_checked in the reference. The tweak is derived from public
     * data, so everything here may be variable-time. */
    secp256k1_scalar_set_b32(tweak, hash32, &overflow);
    secp256k1_memclear_explicit(hash32, sizeof(hash32));
    if (overflow) {
        return 0;
    }

    /* pubtweak = tweak*G. This is the point at infinity iff tweak is zero
     * (negligible probability), matching the reference. */
    secp256k1_ecmult_gen_gej(&ctx->ecmult_gen_ctx, &tweakj, tweak);
    secp256k1_ge_set_gej_var(pubtweak, &tweakj);

    for (j = 1; j < t; j++) {
        out_coms[j] = coms[j];
    }
    secp256k1_gej_set_ge(&com0j, &coms[0]);
    secp256k1_gej_add_var(&tweakj, &tweakj, &com0j, NULL);
    secp256k1_ge_set_gej_var(&out_coms[0], &tweakj);
    return 1;
}

#endif
