// kzg-verifier.test.js
//
// AereKZGVerifier over the EIP-4844 point-evaluation precompile (0x0A).
//
// The local hardhat network (EDR/revm, hardfork cancun) implements the REAL
// KZG point-evaluation precompile with the official ceremony trusted setup,
// so these tests exercise genuine KZG verification, not a mock. The KAT is
// the official go-ethereum precompile test vector (pointEvaluation1), which
// was also confirmed live against AERE chain 2800 via eth_call on 2026-07-10.

const { expect } = require("chai");
const { ethers } = require("hardhat");
const kat = require("./fixtures/kzg-point-evaluation-kat.json");

const PRECOMPILE = "0x000000000000000000000000000000000000000A";
const BLS_MODULUS =
  "52435875175126190479447740508185965837690552500527637822603658699938581184513";

describe("AereKZGVerifier (EIP-4844 point-evaluation)", function () {
  let verifier, user, user2;

  before(async function () {
    [user, user2] = await ethers.getSigners();

    // Sanity: the local EVM must actually have the precompile, otherwise
    // every test below would be meaningless.
    const raw = await ethers.provider.call({ to: PRECOMPILE, data: kat.input });
    expect(raw).to.equal(kat.expected);

    const F = await ethers.getContractFactory("AereKZGVerifier");
    verifier = await F.deploy();
    await verifier.waitForDeployment();
  });

  it("exposes the canonical EIP-4844 constants", async function () {
    expect(await verifier.POINT_EVALUATION()).to.equal(PRECOMPILE);
    expect(await verifier.INPUT_LENGTH()).to.equal(192n);
    expect(await verifier.FIELD_ELEMENTS_PER_BLOB()).to.equal(4096n);
    expect(await verifier.BLS_MODULUS()).to.equal(BigInt(BLS_MODULUS));
  });

  it("checkPointEvaluation returns true for the official KAT", async function () {
    expect(await verifier.checkPointEvaluation(kat.input)).to.equal(true);
  });

  it("verifyPointEvaluation records the KAT and emits the event", async function () {
    await expect(verifier.connect(user).verifyPointEvaluation(kat.input))
      .to.emit(verifier, "PointEvaluationVerified")
      .withArgs(user.address, kat.versionedHash, kat.z, kat.y, 0n);

    expect(await verifier.verificationCount()).to.equal(1n);
    const rec = await verifier.getVerification(0);
    expect(rec.sender).to.equal(user.address);
    expect(rec.versionedHash).to.equal(kat.versionedHash);
    expect(rec.z).to.equal(kat.z);
    expect(rec.y).to.equal(kat.y);
    expect(rec.blockNumber).to.be.gt(0n);
    expect(rec.timestamp).to.be.gt(0n);
    expect(await verifier.verificationCountByHash(kat.versionedHash)).to.equal(1n);
  });

  it("records repeat verifications and tracks the latest per hash", async function () {
    await expect(verifier.connect(user2).verifyPointEvaluation(kat.input))
      .to.emit(verifier, "PointEvaluationVerified")
      .withArgs(user2.address, kat.versionedHash, kat.z, kat.y, 1n);

    expect(await verifier.verificationCount()).to.equal(2n);
    expect(await verifier.verificationCountByHash(kat.versionedHash)).to.equal(2n);

    const latest = await verifier.getLatestVerificationForHash(kat.versionedHash);
    expect(latest.sender).to.equal(user2.address);
  });

  it("rejects a corrupted proof (last byte flipped)", async function () {
    const bad = ethers.getBytes(kat.input);
    bad[bad.length - 1] ^= 0x01;
    await expect(
      verifier.checkPointEvaluation(ethers.hexlify(bad))
    ).to.be.revertedWithCustomError(verifier, "PrecompileCallFailed");
    await expect(
      verifier.verifyPointEvaluation(ethers.hexlify(bad))
    ).to.be.revertedWithCustomError(verifier, "PrecompileCallFailed");
  });

  it("rejects a corrupted commitment", async function () {
    const bad = ethers.getBytes(kat.input);
    bad[100] ^= 0x01; // inside the 48-byte commitment (bytes 96..143)
    await expect(
      verifier.checkPointEvaluation(ethers.hexlify(bad))
    ).to.be.revertedWithCustomError(verifier, "PrecompileCallFailed");
  });

  it("rejects a mismatched versioned hash", async function () {
    const bad = ethers.getBytes(kat.input);
    bad[31] ^= 0x01; // versioned hash no longer sha256(commitment)|0x01
    await expect(
      verifier.checkPointEvaluation(ethers.hexlify(bad))
    ).to.be.revertedWithCustomError(verifier, "PrecompileCallFailed");
  });

  it("rejects a wrong-version versioned hash (0x02 prefix)", async function () {
    const bad = ethers.getBytes(kat.input);
    bad[0] = 0x02;
    await expect(
      verifier.checkPointEvaluation(ethers.hexlify(bad))
    ).to.be.revertedWithCustomError(verifier, "PrecompileCallFailed");
  });

  it("rejects wrong-length input without touching the precompile", async function () {
    await expect(verifier.checkPointEvaluation("0x1234"))
      .to.be.revertedWithCustomError(verifier, "InvalidInputLength")
      .withArgs(2n);
    await expect(verifier.checkPointEvaluation(kat.input + "00"))
      .to.be.revertedWithCustomError(verifier, "InvalidInputLength")
      .withArgs(193n);
  });

  it("rejects an out-of-field z (z >= BLS_MODULUS)", async function () {
    const bad = ethers.getBytes(kat.input);
    // set z (bytes 32..63) to BLS_MODULUS itself, which is out of range
    const mod = ethers.toBeHex(BigInt(BLS_MODULUS), 32);
    ethers.getBytes(mod).forEach((b, i) => { bad[32 + i] = b; });
    await expect(
      verifier.checkPointEvaluation(ethers.hexlify(bad))
    ).to.be.revertedWithCustomError(verifier, "PrecompileCallFailed");
  });

  it("getLatestVerificationForHash reverts for a never-verified hash", async function () {
    await expect(
      verifier.getLatestVerificationForHash(ethers.ZeroHash)
    ).to.be.revertedWith("AereKZGVerifier: hash never verified");
  });

  it("getVerification reverts out of range", async function () {
    await expect(verifier.getVerification(999)).to.be.reverted;
  });
});
