const { expect } = require("chai");
const { ethers } = require("hardhat");
const kat = require("./falcon1024_kat0.json");

// Validates the on-chain Falcon-1024 verifier stage-by-stage against the OFFICIAL
// NIST Falcon-1024 KAT (falcon1024-KAT.rsp, SHA-256 036a0bf5...b20d, round-3
// submission), vectors 0 and 1. The oracle arrays (c, h, s2, s1) were produced by
// an INDEPENDENT reference (OpenSSL SHAKE256 + schoolbook negacyclic convolution),
// so agreement here means the contract's assembly-keccak + NTT path matches an
// implementation that shares no code with it.

describe("AereFalcon1024Verifier: NIST KAT", function () {
  let v;
  before(async function () {
    const F = await ethers.getContractFactory("AereFalcon1024Verifier");
    v = await F.deploy();
    await v.waitForDeployment();
  });

  it("SHAKE256 matches the FIPS 202 empty-string KAT", async function () {
    const out = await v.shake256("0x", 32);
    expect(out.toLowerCase()).to.equal(
      "0x46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f"
    );
  });

  it("HashToPoint(nonce||msg) reproduces the reference challenge polynomial c", async function () {
    const c = await v.hashToPoint(kat.nonce, kat.msg);
    const got = c.map((x) => Number(x));
    expect(got).to.deep.equal(kat.c);
  });

  it("decodePublicKey reproduces h and accepts the 1793-byte key", async function () {
    const [h, ok] = await v.decodePublicKey(kat.pk);
    expect(ok).to.equal(true);
    expect(h.map((x) => Number(x))).to.deep.equal(kat.h);
  });

  it("decodeSignature reproduces s2 and consumes the whole compressed blob", async function () {
    const [s2, ok] = await v.decodeSignature(kat.compsig);
    expect(ok).to.equal(true);
    expect(s2.map((x) => Number(x))).to.deep.equal(kat.s2);
  });

  it("verify() ACCEPTS the official NIST Falcon-1024 KAT signature", async function () {
    expect(await v.verify(kat.pk, kat.msg, kat.nonce, kat.compsig)).to.equal(true);
  });

  it("verifySignedMessage() ACCEPTS the raw NIST signed-message blob", async function () {
    expect(await v.verifySignedMessage(kat.pk, kat.sm)).to.equal(true);
  });

  it("REJECTS a tampered signature (flip one bit of compSig)", async function () {
    const bytes = ethers.getBytes(kat.compsig);
    bytes[100] ^= 0x01; // flip a bit deep in the compressed signature
    const tampered = ethers.hexlify(bytes);
    expect(await v.verify(kat.pk, kat.msg, kat.nonce, tampered)).to.equal(false);
  });

  it("REJECTS a tampered message (flip one bit of msg)", async function () {
    const bytes = ethers.getBytes(kat.msg);
    bytes[0] ^= 0x01;
    const tampered = ethers.hexlify(bytes);
    expect(await v.verify(kat.pk, tampered, kat.nonce, kat.compsig)).to.equal(false);
  });

  it("REJECTS a tampered signed-message blob", async function () {
    const bytes = ethers.getBytes(kat.sm);
    bytes[bytes.length - 5] ^= 0x02; // flip a byte inside the signature region
    const tampered = ethers.hexlify(bytes);
    expect(await v.verifySignedMessage(kat.pk, tampered)).to.equal(false);
  });

  it("ACCEPTS a second official NIST KAT vector (count=1, mlen=66)", async function () {
    expect(await v.verifySignedMessage(kat.pk1, kat.sm1)).to.equal(true);
  });

  it("REJECTS KAT vector 1 signature under the WRONG public key", async function () {
    expect(await v.verifySignedMessage(kat.pk, kat.sm1)).to.equal(false);
  });

  it("verifyAndRecord() writes the result on-chain", async function () {
    // ~21M gas; pass an explicit limit (auto-estimation binary-searches under
    // the local per-tx ceiling). AERE mainnet has an unlimited block gas limit.
    const tx = await v.verifyAndRecord(kat.pk, kat.sm, { gasLimit: 40_000_000 });
    const rc = await tx.wait();
    console.log("        gas used by verifyAndRecord:", rc.gasUsed.toString());
    expect(await v.lastResult()).to.equal(true);
    expect(await v.verifyCount()).to.equal(1n);
  });
});
