// SPDX-License-Identifier: MIT
//
// Self-contained CJS reimplementation of the t-of-n threshold-ECDSA reference
// (Feldman VSS DKG + demonstrator combine) for the Hardhat suite. It mirrors
// sdk-js/src/mpc (the canonical TS reference); having an INDEPENDENT second
// implementation agree with the contract is extra assurance the wire math is right.
//
// Honest note (same as the TS lib): the DKG is non-custodial, but combine() reconstructs
// the secret at the combiner -- fine for a test-vector generator, NOT production signing.

const { secp256k1 } = require("@noble/curves/secp256k1");
const { keccak_256 } = require("@noble/hashes/sha3");
const { mod, invert } = require("@noble/curves/abstract/modular");

const N = secp256k1.CURVE.n;
const N_HALF = N >> 1n;
const Point = secp256k1.ProjectivePoint;

const modN = (x) => mod(x, N);
const invModN = (x) => invert(mod(x, N), N);

function randScalar() {
  return bytesToBigInt(secp256k1.utils.randomPrivateKey());
}
function bytesToBigInt(b) {
  let x = 0n;
  for (const byte of b) x = (x << 8n) | BigInt(byte);
  return x;
}
function bigIntTo32(x) {
  const out = new Uint8Array(32);
  let v = mod(x, 1n << 256n);
  for (let i = 31; i >= 0; i--) {
    out[i] = Number(v & 0xffn);
    v >>= 8n;
  }
  return out;
}
function hex(b) {
  return "0x" + Buffer.from(b).toString("hex");
}
function pointToPubKey64(P) {
  return P.toRawBytes(false).slice(1);
}
function addressFromPoint(P) {
  const h = keccak_256(pointToPubKey64(P));
  return "0x" + Buffer.from(h.slice(12)).toString("hex");
}

function evalPoly(coeffs, x) {
  let acc = 0n;
  let xp = 1n;
  for (const a of coeffs) {
    acc = modN(acc + a * xp);
    xp = modN(xp * x);
  }
  return acc;
}

// Full n-party Feldman VSS DKG (simulated in-process). Returns group key + shares.
function runDkg(n, t) {
  const polys = [];
  const commits = [];
  for (let i = 0; i < n; i++) {
    const c = [];
    for (let k = 0; k < t; k++) c.push(randScalar());
    polys.push(c);
    commits.push(c.map((a) => Point.BASE.multiply(a)));
  }
  // shares
  const shares = [];
  for (let j = 1; j <= n; j++) {
    let xj = 0n;
    for (let d = 0; d < n; d++) xj = modN(xj + evalPoly(polys[d], BigInt(j)));
    shares.push({ index: j, share: xj });
  }
  // aggregate commitment C0 = group point
  let group = null;
  for (let i = 0; i < n; i++) group = group === null ? commits[i][0] : group.add(commits[i][0]);
  return {
    n,
    t,
    groupPoint: group,
    groupPubKey64: "0x" + Buffer.from(pointToPubKey64(group)).toString("hex"),
    groupAddress: addressFromPoint(group),
    shares,
  };
}

function lagrangeAtZero(indices, i) {
  let num = 1n;
  let den = 1n;
  const xi = BigInt(i);
  for (const j of indices) {
    if (j === i) continue;
    const xj = BigInt(j);
    num = modN(num * xj);
    den = modN(den * modN(xj - xi));
  }
  return modN(num * invModN(den));
}

function reconstruct(shares) {
  const idx = shares.map((s) => s.index);
  let x = 0n;
  for (const s of shares) x = modN(x + modN(s.share * lagrangeAtZero(idx, s.index)));
  return x;
}

// Standard ECDSA over a 32-byte digest by scalar priv -> Ethereum (r||s||v) 65 bytes, low-s.
function ecdsaSign(digest32, priv, kFixed) {
  const z = modN(bytesToBigInt(digest32));
  for (let attempt = 0; attempt < 64; attempt++) {
    const k = kFixed !== undefined ? kFixed : randScalar();
    const R = Point.BASE.multiply(k).toAffine();
    const r = modN(R.x);
    if (r === 0n) {
      if (kFixed !== undefined) throw new Error("bad k");
      continue;
    }
    let s = modN(invModN(k) * modN(z + r * modN(priv)));
    if (s === 0n) {
      if (kFixed !== undefined) throw new Error("bad k");
      continue;
    }
    let recovery = Number(R.y & 1n) & 1;
    if (s > N_HALF) {
      s = N - s;
      recovery ^= 1;
    }
    const sig = new Uint8Array(65);
    sig.set(bigIntTo32(r), 0);
    sig.set(bigIntTo32(s), 32);
    sig[64] = 27 + recovery;
    return "0x" + Buffer.from(sig).toString("hex");
  }
  throw new Error("ecdsa failed");
}

// Threshold combine: interpolate the provided shares, sign digest. >=t shares -> group key.
function thresholdSign(digest32, shares, kFixed) {
  const x = reconstruct(shares);
  return ecdsaSign(digest32, x, kFixed);
}

module.exports = {
  N,
  N_HALF,
  Point,
  runDkg,
  thresholdSign,
  reconstruct,
  ecdsaSign,
  lagrangeAtZero,
  bigIntTo32,
  hex,
  addressFromPoint,
};
