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

#ifndef SECP256K1_MODULE_CHILLDKG_UTIL_IMPL_H
#define SECP256K1_MODULE_CHILLDKG_UTIL_IMPL_H

#include <string.h>

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

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

static void secp256k1_chilldkg_sha256_tagged_params_hash(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
    secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/params_hash", sizeof("BIP DKG/params_hash") - 1);
}

static void secp256k1_chilldkg_sha256_tagged_encpedpop_seed(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
    secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/encpedpop seed", sizeof("BIP DKG/encpedpop seed") - 1);
}

static void secp256k1_chilldkg_sha256_tagged_simplpedpop_aux(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
    secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/simplpedpop aux", sizeof("BIP DKG/simplpedpop aux") - 1);
}

static void secp256k1_chilldkg_sha256_tagged_encpedpop_secnonce(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
    secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/encpedpop secnonce", sizeof("BIP DKG/encpedpop secnonce") - 1);
}

static void secp256k1_chilldkg_sha256_tagged_encpedpop_ecdh(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
    secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/encpedpop ecdh", sizeof("BIP DKG/encpedpop ecdh") - 1);
}

static void secp256k1_chilldkg_sha256_tagged_self_pad(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
    secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/encaps_multi self_pad", sizeof("BIP DKG/encaps_multi self_pad") - 1);
}

static void secp256k1_chilldkg_sha256_tagged_vss_coeffs(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
    secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"BIP DKG/vss coeffs", sizeof("BIP DKG/vss coeffs") - 1);
}

static void secp256k1_chilldkg_sha256_tagged_taptweak(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha) {
    secp256k1_sha256_initialize_tagged(hash_ctx, sha, (const unsigned char *)"TapTweak", sizeof("TapTweak") - 1);
}

static void secp256k1_chilldkg_point_save(unsigned char *out33, const secp256k1_ge *p) {
    if (secp256k1_ge_is_infinity(p)) {
        memset(out33, 0, 33);
        return;
    }
    {
        secp256k1_ge tmp = *p;
        /* Serialization operates on public data (commitments, pubnonces, host
         * public keys), so variable-time normalization is fine. */
        secp256k1_fe_normalize_var(&tmp.x);
        secp256k1_fe_normalize_var(&tmp.y);
        out33[0] = secp256k1_fe_is_odd(&tmp.y) ? 0x03 : 0x02;
        secp256k1_fe_get_b32(&out33[1], &tmp.x);
    }
}

static int secp256k1_chilldkg_point_load(secp256k1_ge *p, const unsigned char *in33) {
    static const unsigned char zeros33[33] = { 0 };
    secp256k1_fe x;

    /* Parsed data comes from protocol messages, i.e., it is public. */
    if (secp256k1_memcmp_var(in33, zeros33, 33) == 0) {
        secp256k1_ge_set_infinity(p);
        return 1;
    }
    if (in33[0] != 0x02 && in33[0] != 0x03) {
        secp256k1_ge_set_infinity(p);
        return 0;
    }
    if (!secp256k1_fe_set_b32_limit(&x, &in33[1])) {
        secp256k1_ge_set_infinity(p);
        return 0;
    }
    if (!secp256k1_ge_set_xo_var(p, &x, in33[0] == 0x03)) {
        secp256k1_ge_set_infinity(p);
        return 0;
    }
    return 1;
}

static void secp256k1_chilldkg_xonly_save(unsigned char *out32, const secp256k1_ge *p) {
    secp256k1_ge tmp = *p;
    VERIFY_CHECK(!secp256k1_ge_is_infinity(p));
    secp256k1_fe_normalize_var(&tmp.x);
    secp256k1_fe_get_b32(out32, &tmp.x);
}

static int secp256k1_chilldkg_xonly_load(secp256k1_ge *p, const unsigned char *in32) {
    secp256k1_fe x;
    if (!secp256k1_fe_set_b32_limit(&x, in32)) {
        return 0;
    }
    return secp256k1_ge_set_xo_var(p, &x, 0);
}

static void secp256k1_chilldkg_pad33(unsigned char *out33, const char *str) {
    size_t len = strlen(str);
    VERIFY_CHECK(len <= 33);
    memcpy(out33, str, len);
    memset(out33 + len, 0, 33 - len);
}

/* Initializes sha with the BIP 340 tagged hash tag tag_prefix || subtag,
 * e.g., "BIP DKG/pop message" || "/nonce". Tag strings are public constants. */
static void secp256k1_chilldkg_schnorrsig_sha256_tagged(const secp256k1_hash_ctx *hash_ctx, secp256k1_sha256 *sha, const char *tag_prefix, const char *subtag) {
    unsigned char tag[64];
    size_t prefix_len = strlen(tag_prefix);
    size_t subtag_len = strlen(subtag);

    VERIFY_CHECK(prefix_len + subtag_len <= sizeof(tag));
    memcpy(tag, tag_prefix, prefix_len);
    memcpy(tag + prefix_len, subtag, subtag_len);
    secp256k1_sha256_initialize_tagged(hash_ctx, sha, tag, prefix_len + subtag_len);
}

/* BIP 340 nonce derivation with parameterized tag prefix, mirroring
 * schnorr_sign in secp256k1lab/bip340.py:
 *   t = seckey32 XOR TH(tag_prefix || "/aux", aux_rand32)
 *   nonce32 = TH(tag_prefix || "/nonce", t || xonly_pk32 || msg) */
static void secp256k1_chilldkg_schnorrsig_nonce(const secp256k1_hash_ctx *hash_ctx, unsigned char *nonce32, const unsigned char *msg, size_t msglen, const unsigned char *seckey32, const unsigned char *xonly_pk32, const unsigned char *aux_rand32, const char *tag_prefix) {
    secp256k1_sha256 sha;
    unsigned char masked_key[32];
    unsigned char rand[32];
    int i;

    secp256k1_chilldkg_schnorrsig_sha256_tagged(hash_ctx, &sha, tag_prefix, "/aux");
    secp256k1_sha256_write(hash_ctx, &sha, aux_rand32, 32);
    secp256k1_sha256_finalize(hash_ctx, &sha, rand);
    for (i = 0; i < 32; i++) {
        masked_key[i] = seckey32[i] ^ rand[i];
    }
    secp256k1_chilldkg_schnorrsig_sha256_tagged(hash_ctx, &sha, tag_prefix, "/nonce");
    secp256k1_sha256_write(hash_ctx, &sha, masked_key, 32);
    secp256k1_sha256_write(hash_ctx, &sha, xonly_pk32, 32);
    secp256k1_sha256_write(hash_ctx, &sha, msg, msglen);
    secp256k1_sha256_finalize(hash_ctx, &sha, nonce32);
    secp256k1_sha256_clear(&sha);
    secp256k1_memclear_explicit(masked_key, sizeof(masked_key));
    secp256k1_memclear_explicit(rand, sizeof(rand));
}

/* BIP 340 challenge hash with parameterized tag prefix:
 *   e = TH(tag_prefix || "/challenge", r32 || pubkey32 || msg) mod n
 * The reduction modulo the group order matches the reference, which reduces
 * the hash output with int_from_bytes(...) % GE.ORDER. */
static void secp256k1_chilldkg_schnorrsig_challenge(const secp256k1_hash_ctx *hash_ctx, secp256k1_scalar *e, const unsigned char *r32, const unsigned char *msg, size_t msglen, const unsigned char *pubkey32, const char *tag_prefix) {
    unsigned char buf[32];
    secp256k1_sha256 sha;

    secp256k1_chilldkg_schnorrsig_sha256_tagged(hash_ctx, &sha, tag_prefix, "/challenge");
    secp256k1_sha256_write(hash_ctx, &sha, r32, 32);
    secp256k1_sha256_write(hash_ctx, &sha, pubkey32, 32);
    secp256k1_sha256_write(hash_ctx, &sha, msg, msglen);
    secp256k1_sha256_finalize(hash_ctx, &sha, buf);
    secp256k1_scalar_set_b32(e, buf, NULL);
}

/* Mirrors schnorr_sign in secp256k1lab/bip340.py. The structure follows
 * secp256k1_schnorrsig_sign_internal, which cannot be reused directly because
 * it hardcodes the "BIP0340" tags. */
static int secp256k1_chilldkg_schnorrsig_sign(const secp256k1_context *ctx, unsigned char *sig64, const unsigned char *msg, size_t msglen, const unsigned char *seckey32, const unsigned char *aux_rand32, const char *tag_prefix) {
    const secp256k1_hash_ctx *hash_ctx;
    secp256k1_scalar sk;
    secp256k1_scalar e;
    secp256k1_scalar k;
    secp256k1_ge pk;
    secp256k1_ge r;
    unsigned char nonce32[32] = { 0 };
    unsigned char pk32[32];
    unsigned char seckey[32];
    int overflow;
    int ret = 1;

    VERIFY_CHECK(ctx != NULL);
    ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
    ARG_CHECK(sig64 != NULL);
    ARG_CHECK(msg != NULL || msglen == 0);
    ARG_CHECK(seckey32 != NULL);
    ARG_CHECK(aux_rand32 != NULL);
    ARG_CHECK(tag_prefix != NULL);
    hash_ctx = secp256k1_get_hash_context(ctx);

    secp256k1_scalar_set_b32(&sk, seckey32, &overflow);
    overflow |= secp256k1_scalar_is_zero(&sk);
    /* Branching on the validity of the secret key is fine: whether the
     * caller's secret key is in range 1..n-1 is not secret. */
    secp256k1_declassify(ctx, &overflow, sizeof(overflow));
    if (overflow) {
        memset(sig64, 0, 64);
        secp256k1_scalar_clear(&sk);
        return 0;
    }

    secp256k1_ecmult_gen_ge(&ctx->ecmult_gen_ctx, &pk, &sk);
    /* The public key is not secret, so variable-time normalization and
     * branching on its y parity are fine. */
    secp256k1_declassify(ctx, &pk, sizeof(pk));
    secp256k1_fe_normalize_var(&pk.x);
    secp256k1_fe_normalize_var(&pk.y);
    if (secp256k1_fe_is_odd(&pk.y)) {
        secp256k1_scalar_negate(&sk, &sk);
    }
    secp256k1_scalar_get_b32(seckey, &sk);
    secp256k1_fe_get_b32(pk32, &pk.x);

    secp256k1_chilldkg_schnorrsig_nonce(hash_ctx, nonce32, msg, msglen, seckey, pk32, aux_rand32, tag_prefix);
    /* The reference reduces the nonce hash modulo the group order. */
    secp256k1_scalar_set_b32(&k, nonce32, NULL);
    ret &= !secp256k1_scalar_is_zero(&k);
    secp256k1_scalar_cmov(&k, &secp256k1_scalar_one, !ret);

    secp256k1_ecmult_gen_ge(&ctx->ecmult_gen_ctx, &r, &k);
    /* We declassify r to allow using it as a branch point. This is fine
     * because r is not a secret. */
    secp256k1_declassify(ctx, &r, sizeof(r));
    secp256k1_fe_normalize_var(&r.y);
    if (secp256k1_fe_is_odd(&r.y)) {
        secp256k1_scalar_negate(&k, &k);
    }
    secp256k1_fe_normalize_var(&r.x);
    secp256k1_fe_get_b32(&sig64[0], &r.x);

    secp256k1_chilldkg_schnorrsig_challenge(hash_ctx, &e, &sig64[0], msg, msglen, pk32, tag_prefix);
    secp256k1_scalar_mul(&e, &e, &sk);
    secp256k1_scalar_add(&e, &e, &k);
    secp256k1_scalar_get_b32(&sig64[32], &e);

    secp256k1_memczero(sig64, 64, !ret);
    secp256k1_scalar_clear(&k);
    secp256k1_scalar_clear(&sk);
    secp256k1_scalar_clear(&e);
    secp256k1_ge_clear(&pk);
    secp256k1_ge_clear(&r);
    secp256k1_memclear_explicit(seckey, sizeof(seckey));
    secp256k1_memclear_explicit(nonce32, sizeof(nonce32));

    /* Branching on the return value only leaks whether the derived nonce is
     * zero, which happens with negligible probability (the case of an invalid
     * secret key is declassified above). */
    secp256k1_declassify(ctx, &ret, sizeof(ret));
    return ret;
}

/* Mirrors schnorr_verify in secp256k1lab/bip340.py. The structure follows
 * secp256k1_schnorrsig_verify, which cannot be reused directly because it
 * hardcodes the "BIP0340/challenge" tag. */
static int secp256k1_chilldkg_schnorrsig_verify(const secp256k1_context *ctx, const unsigned char *sig64, const unsigned char *msg, size_t msglen, const unsigned char *pubkey32, const char *tag_prefix) {
    secp256k1_scalar s;
    secp256k1_scalar e;
    secp256k1_gej rj;
    secp256k1_ge pk;
    secp256k1_gej pkj;
    secp256k1_fe rx;
    secp256k1_ge r;
    unsigned char buf[32];
    int overflow;

    VERIFY_CHECK(ctx != NULL);
    ARG_CHECK(sig64 != NULL);
    ARG_CHECK(msg != NULL || msglen == 0);
    ARG_CHECK(pubkey32 != NULL);
    ARG_CHECK(tag_prefix != NULL);

    if (!secp256k1_fe_set_b32_limit(&rx, &sig64[0])) {
        return 0;
    }

    secp256k1_scalar_set_b32(&s, &sig64[32], &overflow);
    if (overflow) {
        return 0;
    }

    if (!secp256k1_chilldkg_xonly_load(&pk, pubkey32)) {
        return 0;
    }

    /* Compute e. */
    secp256k1_fe_get_b32(buf, &pk.x);
    secp256k1_chilldkg_schnorrsig_challenge(secp256k1_get_hash_context(ctx), &e, &sig64[0], msg, msglen, buf, tag_prefix);

    /* Compute rj = s*G + (-e)*pkj */
    secp256k1_scalar_negate(&e, &e);
    secp256k1_gej_set_ge(&pkj, &pk);
    secp256k1_ecmult(&rj, &pkj, &e, &s);

    secp256k1_ge_set_gej_var(&r, &rj);
    if (secp256k1_ge_is_infinity(&r)) {
        return 0;
    }

    secp256k1_fe_normalize_var(&r.y);
    return !secp256k1_fe_is_odd(&r.y) &&
           secp256k1_fe_equal(&rx, &r.x);
}

static void secp256k1_chilldkg_encpedpop_ecdh(const secp256k1_context *ctx, secp256k1_scalar *out, const secp256k1_scalar *seckey, const secp256k1_ge *their_point, const unsigned char *my_pubkey33, const unsigned char *their_pubkey33, const unsigned char *context, size_t context_len, int sending) {
    const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(ctx);
    secp256k1_gej resj;
    secp256k1_ge res;
    unsigned char x[32];
    unsigned char y[32];
    unsigned char shared[32];
    unsigned char hash32[32];
    secp256k1_sha256 sha;

    VERIFY_CHECK(ctx != NULL);
    VERIFY_CHECK(!secp256k1_scalar_is_zero(seckey));
    VERIFY_CHECK(!secp256k1_ge_is_infinity(their_point));

    /* libsecp256k1-style ECDH: SHA256 of the compressed shared point. */
    secp256k1_ecmult_const(&resj, their_point, seckey);
    secp256k1_ge_set_gej(&res, &resj);
    /* The result cannot be the point at infinity: their_point is not
     * infinity, seckey is nonzero, and the group has prime order. */
    VERIFY_CHECK(!secp256k1_ge_is_infinity(&res));
    secp256k1_fe_normalize(&res.x);
    secp256k1_fe_normalize(&res.y);
    secp256k1_fe_get_b32(x, &res.x);
    secp256k1_fe_get_b32(y, &res.y);
    /* This hash function always succeeds; call it unconditionally (it must
     * not sit inside VERIFY_CHECK, which is compiled out in noverify
     * builds). */
    if (!ecdh_hash_function_sha256_impl(hash_ctx, shared, x, y, NULL)) {
        VERIFY_CHECK(0);
        memset(shared, 0, sizeof(shared));
    }

    secp256k1_chilldkg_sha256_tagged_encpedpop_ecdh(hash_ctx, &sha);
    secp256k1_sha256_write(hash_ctx, &sha, shared, 32);
    /* The sender's pubnonce always comes first in the hash input. */
    if (sending) {
        secp256k1_sha256_write(hash_ctx, &sha, my_pubkey33, 33);
        secp256k1_sha256_write(hash_ctx, &sha, their_pubkey33, 33);
    } else {
        secp256k1_sha256_write(hash_ctx, &sha, their_pubkey33, 33);
        secp256k1_sha256_write(hash_ctx, &sha, my_pubkey33, 33);
    }
    secp256k1_sha256_write(hash_ctx, &sha, context, context_len);
    secp256k1_sha256_finalize(hash_ctx, &sha, hash32);
    secp256k1_sha256_clear(&sha);

    /* Pads are reduced modulo the group order (from_bytes_wrapping in the
     * reference). */
    secp256k1_scalar_set_b32(out, hash32, NULL);

    secp256k1_memclear_explicit(x, sizeof(x));
    secp256k1_memclear_explicit(y, sizeof(y));
    secp256k1_memclear_explicit(shared, sizeof(shared));
    secp256k1_memclear_explicit(hash32, sizeof(hash32));
    secp256k1_ge_clear(&res);
    secp256k1_gej_clear(&resj);
}

static void secp256k1_chilldkg_encpedpop_self_pad(const secp256k1_hash_ctx *hash_ctx, secp256k1_scalar *out, const unsigned char *symkey32, const unsigned char *nonce33, const unsigned char *context, size_t context_len) {
    unsigned char hash32[32];
    secp256k1_sha256 sha;

    secp256k1_chilldkg_sha256_tagged_self_pad(hash_ctx, &sha);
    secp256k1_sha256_write(hash_ctx, &sha, symkey32, 32);
    secp256k1_sha256_write(hash_ctx, &sha, nonce33, 33);
    secp256k1_sha256_write(hash_ctx, &sha, context, context_len);
    secp256k1_sha256_finalize(hash_ctx, &sha, hash32);
    secp256k1_sha256_clear(&sha);

    /* from_bytes_wrapping in the reference. */
    secp256k1_scalar_set_b32(out, hash32, NULL);
    secp256k1_memclear_explicit(hash32, sizeof(hash32));
}

static void secp256k1_chilldkg_params_hash_internal(const secp256k1_hash_ctx *hash_ctx, unsigned char *out32, const unsigned char *hostpubkeys33, size_t n, uint32_t t) {
    unsigned char buf[4];
    secp256k1_sha256 sha;

    secp256k1_chilldkg_sha256_tagged_params_hash(hash_ctx, &sha);
    secp256k1_write_be32(buf, t);
    secp256k1_sha256_write(hash_ctx, &sha, buf, sizeof(buf));
    secp256k1_sha256_write(hash_ctx, &sha, hostpubkeys33, 33 * n);
    secp256k1_sha256_finalize(hash_ctx, &sha, out32);
    secp256k1_sha256_clear(&sha);
}

#endif
