// =============================================================================
// PQC-authorized intents: Falcon/ML-DSA-signed ERC-7683 cross-chain orders.
//
// Under test:
//   contracts/pqc/AerePQCOrderAuthorizer.sol   (crypto-agility CONSUMER over the registry)
//   contracts/intents/AereSpokePool.sol         (openForPQC + bindPQCKey PQC intent path)
//   contracts/intents/IAerePQCOrder.sol         (PQCGaslessOrder + authorizer interface)
//
// The order is authorized ONLY by a NIST post-quantum signature (Falcon-512/1024,
// ML-DSA-44, SLH-DSA-128s) over the EIP-712 order digest, verified on-chain by AERE's
// live native PQC precompiles routed through the governed AereCryptoRegistry. So the
// intent is quantum-durable end to end, and a Foundation scheme swap migrates the
// accepted scheme with NO pool redeploy (proven below).
//
// TWO verification backends at 0x0AE1..0x0AE4 (Hardhat has no native PQC precompile):
//   A. MockPQCPrecompile — parses the input at the precompiles' SPEC offsets and accepts
//      iff the embedded commitment == keccak256(pk || message). Drives the accept /
//      bad-signature / wrong-scheme / replay / agility LOGIC deterministically. Because
//      the registry builds the SAME wire format the live precompiles parse, a positive
//      result proves correct routing/encoding (NOT the lattice cryptography).
//   B. RealFalcon512PrecompileAdapter — routes the staticcall into the NIST-KAT-proven
//      AereFalcon512Verifier, so a GENUINE @noble/post-quantum Falcon-512 signature over
//      the pool's real order digest fills the order, and the official NIST KAT vector 0
//      is accepted. This proves the real crypto leg.
//
// Run: npx hardhat test test/pqc-intents.test.js
// No em-dashes anywhere in this file.
// =============================================================================

const { expect } = require("chai");
const { ethers, network } = require("hardhat");
const path = require("path");
const { pathToFileURL } = require("url");

const AbiCoder = ethers.AbiCoder.defaultAbiCoder();
const AERE_ORDER_DATA_TYPE = ethers.keccak256(ethers.toUtf8Bytes("AereV3Order"));
const DEST_CHAIN = 1n; // Ethereum-like destination
const Status = { UNKNOWN: 0, ACTIVE: 1, DEPRECATED: 2, REVOKED: 3 };

const PRECOMPILE = {
  1: ethers.getAddress("0x0000000000000000000000000000000000000ae1"),
  2: ethers.getAddress("0x0000000000000000000000000000000000000ae2"),
  3: ethers.getAddress("0x0000000000000000000000000000000000000ae3"),
  4: ethers.getAddress("0x0000000000000000000000000000000000000ae4"),
};
const META = {
  1: { name: "Falcon-512", pkLen: 897, pkHeader: 0x09, esigHeader: 0x29, sigLen: 0, falcon: true },
  2: { name: "Falcon-1024", pkLen: 1793, pkHeader: 0x0a, esigHeader: 0x2a, sigLen: 0, falcon: true },
  3: { name: "ML-DSA-44", pkLen: 1312, esigHeader: 0x00, sigLen: 2420, falcon: false },
  4: { name: "SLH-DSA-128s", pkLen: 32, esigHeader: 0x00, sigLen: 7856, falcon: false },
};

// Deterministic seeded RNG (keccak hash-chain), identical to the hybrid-authorizer suite.
function makeRng(seedText) {
  let seed = ethers.keccak256(ethers.toUtf8Bytes(seedText));
  function bytes(n) {
    const out = new Uint8Array(n);
    let i = 0;
    while (i < n) {
      seed = ethers.keccak256(seed);
      const chunk = ethers.getBytes(seed);
      for (let j = 0; j < chunk.length && i < n; j++, i++) out[i] = chunk[j];
    }
    return out;
  }
  return { bytes };
}

function makePubKey(scheme, rng) {
  const m = META[scheme];
  const raw = rng.bytes(m.pkLen);
  if (m.falcon) raw[0] = m.pkHeader;
  return ethers.hexlify(raw);
}

// A "genuine" mock signature: the precompile accepts iff commitment == keccak256(pk||message),
// where `message` is the 32-byte order digest the registry passes as messageHash.
function genuineMockSig(scheme, pubKey, digest, rng) {
  const m = META[scheme];
  const commitment = ethers.keccak256(ethers.concat([pubKey, digest]));
  if (m.falcon) {
    const nonce = rng.bytes(40);
    const esig = ethers.concat([new Uint8Array([m.esigHeader]), commitment]); // 33 bytes
    return ethers.hexlify(ethers.concat([nonce, esig]));
  }
  const sig = new Uint8Array(m.sigLen);
  sig.set(ethers.getBytes(commitment), 0);
  return ethers.hexlify(sig);
}

function tamperSig(scheme, sigHex) {
  const b = ethers.getBytes(sigHex);
  const idx = META[scheme].falcon ? 41 : 0; // first byte of the embedded commitment
  b[idx] ^= 0x01;
  return ethers.hexlify(b);
}

function encodeOrderData(inputToken, inputAmount, outputToken, outputAmount, recipientB32, destChain) {
  return AbiCoder.encode(
    ["address", "uint256", "address", "uint256", "bytes32", "uint64"],
    [inputToken, inputAmount, outputToken, outputAmount, recipientB32, destChain]
  );
}

async function latestTs() {
  return Number((await ethers.provider.getBlock("latest")).timestamp);
}

describe("PQC-authorized intents (Falcon/ML-DSA ERC-7683 orders)", function () {
  this.timeout(0);

  let signers, chainId;
  let mockCode;

  before(async function () {
    signers = await ethers.getSigners();
    chainId = (await ethers.provider.getNetwork()).chainId;
    // Deploy the four precompile-format mocks once; capture their runtime code.
    mockCode = {};
    const F = await ethers.getContractFactory("MockPQCPrecompile");
    for (const scheme of [1, 2, 3, 4]) {
      const mock = await F.deploy(scheme);
      await mock.waitForDeployment();
      mockCode[scheme] = await ethers.provider.getCode(await mock.getAddress());
    }
  });

  async function installMocks() {
    for (const scheme of [1, 2, 3, 4]) {
      await network.provider.send("hardhat_setCode", [PRECOMPILE[scheme], mockCode[scheme]]);
    }
  }

  async function deployStack(defaultId = 1) {
    const deployer = signers[0];
    const reg = await (await ethers.getContractFactory("AereCryptoRegistry")).deploy();
    await reg.waitForDeployment();
    await reg.seedLiveSchemes(); // ids 1..5 = Falcon-512/1024, ML-DSA-44, SLH-DSA-128s, SHAKE256

    const auth = await (await ethers.getContractFactory("AerePQCOrderAuthorizer")).deploy(
      await reg.getAddress(),
      defaultId
    );
    await auth.waitForDeployment();

    const ERC = await ethers.getContractFactory("MockERC20Lending");
    const input = await ERC.deploy("USD Coin", "USDC", 6);
    await input.waitForDeployment();
    const waere = await ERC.deploy("Wrapped AERE", "WAERE", 18); // dummy bond token for the constructor
    await waere.waitForDeployment();
    const sink = await (await ethers.getContractFactory("MockFlushSink")).deploy();
    await sink.waitForDeployment();

    const pool = await (await ethers.getContractFactory("contracts/intents/AereSpokePool.sol:AereSpokePool")).deploy(
      await waere.getAddress(),
      await sink.getAddress()
    );
    await pool.waitForDeployment();
    await (await pool.setPQCAuthorizer(await auth.getAddress())).wait();

    return {
      deployer, reg, auth, input, waere, sink, pool,
      poolAddr: await pool.getAddress(),
      inputAddr: await input.getAddress(),
      waereAddr: await waere.getAddress(),
      sinkAddr: await sink.getAddress(),
    };
  }

  // Build the PQCGaslessOrder tuple (struct field order) for the ethers call.
  function buildOrder(ctx, { user, nonce, algorithmId, inputAmount, recipient, life = 3600, openLife = 1800 }) {
    const orderData = encodeOrderData(ctx.inputAddr, inputAmount, ctx.waereAddr, inputAmount, ethers.zeroPadValue(recipient, 32), DEST_CHAIN);
    return {
      tuple: [user, nonce, chainId, ctx._now + openLife, ctx._now + life, algorithmId, AERE_ORDER_DATA_TYPE, orderData],
      orderData,
      inputAmount,
    };
  }

  async function prepUser(ctx, user, pubKey, amount) {
    await (await ctx.input.mint(user.address, amount)).wait();
    await (await ctx.input.connect(user).approve(ctx.poolAddr, amount)).wait();
    await (await ctx.pool.connect(user).bindPQCKey(pubKey)).wait();
  }

  // -------------------------------------------------------------------------
  // A. Mock-precompile LOGIC paths (routing / policy / replay / agility).
  // -------------------------------------------------------------------------
  describe("mock precompile: accept / reject / wrong-scheme / replay", function () {
    let ctx;
    beforeEach(async function () {
      await installMocks();
      ctx = await deployStack(1);
      ctx._now = await latestTs();
    });

    it("valid Falcon-512 order FILLS: locks solver principal, routes fee, bumps nonce", async function () {
      const [_, user, relayer, recipient] = signers;
      const rng = makeRng("valid-falcon");
      const pubKey = makePubKey(1, rng);
      const inputAmount = 1000n * 10n ** 6n;
      await prepUser(ctx, user, pubKey, inputAmount * 2n);

      const { tuple } = buildOrder(ctx, { user: user.address, nonce: 0, algorithmId: 1, inputAmount, recipient: recipient.address });
      const digest = await ctx.auth.orderDigest(ctx.poolAddr, tuple, pubKey);
      const sig = genuineMockSig(1, pubKey, digest, rng);

      const rc = await (await ctx.pool.connect(relayer).openForPQC(tuple, pubKey, sig)).wait();
      const openedId = rc.logs.find((l) => l.fragment && l.fragment.name === "Opened").args.orderId;
      const pqcEv = rc.logs.find((l) => l.fragment && l.fragment.name === "PQCOrderOpened");
      // The PQC event's orderId is the SAME canonical id _openInternal stored.
      expect(pqcEv.args.orderId).to.equal(openedId);
      expect(pqcEv.args.user).to.equal(user.address);
      expect(pqcEv.args.algorithmId).to.equal(1n);
      // The order is really stored and open under that id.
      const stored = await ctx.pool.orders(openedId);
      expect(stored.open).to.equal(true);
      expect(stored.user).to.equal(user.address);

      const fee = (inputAmount * 50n) / 10_000n;
      const solverPortion = inputAmount - fee;
      expect(await ctx.input.balanceOf(ctx.poolAddr)).to.equal(solverPortion);
      expect(await ctx.input.balanceOf(ctx.sinkAddr)).to.equal(fee);
      expect(await ctx.pool.totalLocked(ctx.inputAddr)).to.equal(solverPortion);
      expect(await ctx.pool.pqcNonce(user.address)).to.equal(1n);
    });

    it("valid ML-DSA-44 order FILLS (multi-scheme, explicit algorithmId=3)", async function () {
      const [_, user, relayer, recipient] = signers;
      const rng = makeRng("valid-mldsa");
      const pubKey = makePubKey(3, rng);
      const inputAmount = 500n * 10n ** 6n;
      await prepUser(ctx, user, pubKey, inputAmount);

      const { tuple } = buildOrder(ctx, { user: user.address, nonce: 0, algorithmId: 3, inputAmount, recipient: recipient.address });
      const digest = await ctx.auth.orderDigest(ctx.poolAddr, tuple, pubKey);
      const sig = genuineMockSig(3, pubKey, digest, rng);

      await expect(ctx.pool.connect(relayer).openForPQC(tuple, pubKey, sig)).to.emit(ctx.pool, "PQCOrderOpened");
      expect(await ctx.pool.pqcNonce(user.address)).to.equal(1n);
    });

    it("algorithmId=0 routes to the authorizer's governed default (Falcon-512)", async function () {
      const [_, user, relayer, recipient] = signers;
      const rng = makeRng("default-algo");
      const pubKey = makePubKey(1, rng);
      const inputAmount = 100n * 10n ** 6n;
      await prepUser(ctx, user, pubKey, inputAmount);

      const { tuple } = buildOrder(ctx, { user: user.address, nonce: 0, algorithmId: 0, inputAmount, recipient: recipient.address });
      const digest = await ctx.auth.orderDigest(ctx.poolAddr, tuple, pubKey);
      const sig = genuineMockSig(1, pubKey, digest, rng);
      await expect(ctx.pool.connect(relayer).openForPQC(tuple, pubKey, sig)).to.emit(ctx.pool, "PQCOrderOpened");
    });

    it("BAD SIGNATURE order is REJECTED (tampered commitment)", async function () {
      const [_, user, relayer, recipient] = signers;
      const rng = makeRng("bad-sig");
      const pubKey = makePubKey(1, rng);
      const inputAmount = 100n * 10n ** 6n;
      await prepUser(ctx, user, pubKey, inputAmount);

      const { tuple } = buildOrder(ctx, { user: user.address, nonce: 0, algorithmId: 1, inputAmount, recipient: recipient.address });
      const digest = await ctx.auth.orderDigest(ctx.poolAddr, tuple, pubKey);
      const bad = tamperSig(1, genuineMockSig(1, pubKey, digest, rng));

      await expect(ctx.pool.connect(relayer).openForPQC(tuple, pubKey, bad))
        .to.be.revertedWithCustomError(ctx.pool, "InvalidSignature");
      // Nothing moved: no lock, no nonce bump.
      expect(await ctx.pool.totalLocked(ctx.inputAddr)).to.equal(0n);
      expect(await ctx.pool.pqcNonce(user.address)).to.equal(0n);
    });

    it("WRONG SCHEME is REJECTED: a Falcon key/sig declared under the ML-DSA id", async function () {
      const [_, user, relayer, recipient] = signers;
      const rng = makeRng("wrong-scheme");
      const pubKey = makePubKey(1, rng); // 897-byte Falcon key, bound
      const inputAmount = 100n * 10n ** 6n;
      await prepUser(ctx, user, pubKey, inputAmount);

      // Declare algorithmId = 3 (ML-DSA-44): the registry routes to a verifier expecting a
      // 1312-byte key, so the 897-byte Falcon key fails the length gate -> not authorized.
      const { tuple } = buildOrder(ctx, { user: user.address, nonce: 0, algorithmId: 3, inputAmount, recipient: recipient.address });
      const digest = await ctx.auth.orderDigest(ctx.poolAddr, tuple, pubKey);
      const falconSig = genuineMockSig(1, pubKey, digest, rng);
      await expect(ctx.pool.connect(relayer).openForPQC(tuple, pubKey, falconSig))
        .to.be.revertedWithCustomError(ctx.pool, "InvalidSignature");
    });

    it("WRONG SCHEME is REJECTED: a hash-only scheme id (SHAKE256 id 5) authorizes nothing", async function () {
      const [_, user, relayer, recipient] = signers;
      const rng = makeRng("hash-only");
      const pubKey = makePubKey(1, rng);
      const inputAmount = 100n * 10n ** 6n;
      await prepUser(ctx, user, pubKey, inputAmount);

      const { tuple } = buildOrder(ctx, { user: user.address, nonce: 0, algorithmId: 5, inputAmount, recipient: recipient.address });
      const digest = await ctx.auth.orderDigest(ctx.poolAddr, tuple, pubKey);
      const sig = genuineMockSig(1, pubKey, digest, rng);
      await expect(ctx.pool.connect(relayer).openForPQC(tuple, pubKey, sig))
        .to.be.revertedWithCustomError(ctx.pool, "InvalidSignature");
    });

    it("REPLAY is REJECTED: the same order cannot be opened twice (nonce consumed)", async function () {
      const [_, user, relayer, recipient] = signers;
      const rng = makeRng("replay");
      const pubKey = makePubKey(1, rng);
      const inputAmount = 100n * 10n ** 6n;
      await prepUser(ctx, user, pubKey, inputAmount * 3n);

      const { tuple } = buildOrder(ctx, { user: user.address, nonce: 0, algorithmId: 1, inputAmount, recipient: recipient.address });
      const digest = await ctx.auth.orderDigest(ctx.poolAddr, tuple, pubKey);
      const sig = genuineMockSig(1, pubKey, digest, rng);

      await (await ctx.pool.connect(relayer).openForPQC(tuple, pubKey, sig)).wait();
      expect(await ctx.pool.pqcNonce(user.address)).to.equal(1n);

      // Exact-replay of the captured (order, sig): nonce 0 no longer current.
      await expect(ctx.pool.connect(relayer).openForPQC(tuple, pubKey, sig))
        .to.be.revertedWithCustomError(ctx.pool, "NonceMismatch");

      // Even a freshly-signed order at the stale nonce 0 is rejected.
      const sig2 = genuineMockSig(1, pubKey, digest, makeRng("replay-2"));
      await expect(ctx.pool.connect(relayer).openForPQC(tuple, pubKey, sig2))
        .to.be.revertedWithCustomError(ctx.pool, "NonceMismatch");
    });

    it("REJECTS an unbound funder and a pubkey that does not match the bound commitment", async function () {
      const [_, user, relayer, recipient, stranger] = signers;
      const rng = makeRng("binding");
      const pubKey = makePubKey(1, rng);
      const otherKey = makePubKey(1, makeRng("binding-other"));
      const inputAmount = 100n * 10n ** 6n;

      // Unbound: mint + approve but never bindPQCKey.
      await (await ctx.input.mint(user.address, inputAmount)).wait();
      await (await ctx.input.connect(user).approve(ctx.poolAddr, inputAmount)).wait();
      const { tuple } = buildOrder(ctx, { user: user.address, nonce: 0, algorithmId: 1, inputAmount, recipient: recipient.address });
      const digest = await ctx.auth.orderDigest(ctx.poolAddr, tuple, pubKey);
      const sig = genuineMockSig(1, pubKey, digest, rng);
      await expect(ctx.pool.connect(relayer).openForPQC(tuple, pubKey, sig))
        .to.be.revertedWithCustomError(ctx.pool, "PQCKeyNotBound");

      // Bind pubKey, but present a DIFFERENT key at open -> rejected before any verify.
      await (await ctx.pool.connect(user).bindPQCKey(pubKey)).wait();
      await expect(ctx.pool.connect(relayer).openForPQC(tuple, otherKey, genuineMockSig(1, otherKey, digest, rng)))
        .to.be.revertedWithCustomError(ctx.pool, "PQCKeyNotBound");
    });

    it("REJECTS an order for the wrong chain and after the open deadline", async function () {
      const [_, user, relayer, recipient] = signers;
      const rng = makeRng("deadline");
      const pubKey = makePubKey(1, rng);
      const inputAmount = 100n * 10n ** 6n;
      await prepUser(ctx, user, pubKey, inputAmount);

      // Wrong origin chain id.
      const wrongChain = buildOrder(ctx, { user: user.address, nonce: 0, algorithmId: 1, inputAmount, recipient: recipient.address });
      wrongChain.tuple[2] = chainId + 1n;
      const d1 = await ctx.auth.orderDigest(ctx.poolAddr, wrongChain.tuple, pubKey);
      await expect(ctx.pool.connect(relayer).openForPQC(wrongChain.tuple, pubKey, genuineMockSig(1, pubKey, d1, rng)))
        .to.be.revertedWithCustomError(ctx.pool, "InvalidSignature");

      // Past open deadline.
      const expired = buildOrder(ctx, { user: user.address, nonce: 0, algorithmId: 1, inputAmount, recipient: recipient.address, openLife: -10 });
      const d2 = await ctx.auth.orderDigest(ctx.poolAddr, expired.tuple, pubKey);
      await expect(ctx.pool.connect(relayer).openForPQC(expired.tuple, pubKey, genuineMockSig(1, pubKey, d2, rng)))
        .to.be.revertedWithCustomError(ctx.pool, "DeadlinePassed");
    });

    it("PQC path is disabled when the authorizer is unset", async function () {
      const [_, user, relayer, recipient] = signers;
      const rng = makeRng("unset");
      const pubKey = makePubKey(1, rng);
      const inputAmount = 100n * 10n ** 6n;
      await prepUser(ctx, user, pubKey, inputAmount);
      await (await ctx.pool.setPQCAuthorizer(ethers.ZeroAddress)).wait();

      const { tuple } = buildOrder(ctx, { user: user.address, nonce: 0, algorithmId: 1, inputAmount, recipient: recipient.address });
      const digest = await ctx.auth.orderDigest(ctx.poolAddr, tuple, pubKey);
      await expect(ctx.pool.connect(relayer).openForPQC(tuple, pubKey, genuineMockSig(1, pubKey, digest, rng)))
        .to.be.revertedWithCustomError(ctx.pool, "PQCAuthorizerUnset");
    });
  });

  // -------------------------------------------------------------------------
  // THE HEADLINE: a Foundation scheme swap on the REGISTRY changes the accepted
  // PQC scheme for the SAME requested id, with NO pool or authorizer redeploy.
  // -------------------------------------------------------------------------
  describe("zero-redeploy scheme migration (Falcon-512 -> ML-DSA-44 successor)", function () {
    let ctx;
    beforeEach(async function () {
      await installMocks();
      ctx = await deployStack(1);
      ctx._now = await latestTs();
    });

    it("the same algorithmId=1 stops accepting Falcon-512 and starts accepting the ML-DSA-44 successor", async function () {
      const [_, user, relayer, recipient] = signers;
      const rng = makeRng("swap");
      const inputAmount = 100n * 10n ** 6n;

      // BEFORE: a Falcon-512 key/order under id 1 fills.
      const fkey = makePubKey(1, rng);
      await prepUser(ctx, user, fkey, inputAmount * 4n);
      const beforeOrder = buildOrder(ctx, { user: user.address, nonce: 0, algorithmId: 1, inputAmount, recipient: recipient.address });
      const dBefore = await ctx.auth.orderDigest(ctx.poolAddr, beforeOrder.tuple, fkey);
      await expect(ctx.pool.connect(relayer).openForPQC(beforeOrder.tuple, fkey, genuineMockSig(1, fkey, dBefore, rng)))
        .to.emit(ctx.pool, "PQCOrderOpened");

      // Foundation-governed swap on the REGISTRY (no pool/authorizer redeploy):
      //   1. register an ML-DSA-44-shaped successor (ACTIVE), 2. point id 1 -> it,
      //   3. deprecate id 1.
      const tx = await ctx.reg.addAlgorithm("ML-DSA-44-succ", 3, PRECOMPILE[3], 1312, 2420, 0x00, 351000, true);
      const rc = await tx.wait();
      const newId = Number(rc.logs.map((l) => ctx.reg.interface.parseLog(l)).find((p) => p && p.name === "AlgorithmAdded").args.id);
      await (await ctx.reg.setSuccessor(1, newId)).wait();
      await (await ctx.reg.setStatus(1, Status.DEPRECATED)).wait();

      // AFTER: a Falcon-512 order under the SAME id 1 is now rejected (routes to the
      // ML-DSA successor, whose verifier rejects the 897-byte Falcon key).
      const fkey2 = makePubKey(1, makeRng("swap-falcon2"));
      await (await ctx.pool.connect(user).bindPQCKey(fkey2)).wait();
      const afterFalcon = buildOrder(ctx, { user: user.address, nonce: 1, algorithmId: 1, inputAmount, recipient: recipient.address });
      const dF = await ctx.auth.orderDigest(ctx.poolAddr, afterFalcon.tuple, fkey2);
      await expect(ctx.pool.connect(relayer).openForPQC(afterFalcon.tuple, fkey2, genuineMockSig(1, fkey2, dF, rng)))
        .to.be.revertedWithCustomError(ctx.pool, "InvalidSignature");

      // An ML-DSA-44 order under the SAME requested id 1 now fills.
      const mkey = makePubKey(3, makeRng("swap-mldsa"));
      await (await ctx.pool.connect(user).bindPQCKey(mkey)).wait();
      const afterMldsa = buildOrder(ctx, { user: user.address, nonce: 1, algorithmId: 1, inputAmount, recipient: recipient.address });
      const dM = await ctx.auth.orderDigest(ctx.poolAddr, afterMldsa.tuple, mkey);
      await expect(ctx.pool.connect(relayer).openForPQC(afterMldsa.tuple, mkey, genuineMockSig(3, mkey, dM, rng)))
        .to.emit(ctx.pool, "PQCOrderOpened");
      expect(await ctx.pool.pqcNonce(user.address)).to.equal(2n);
    });
  });

  // -------------------------------------------------------------------------
  // B. REAL Falcon-512 crypto: genuine @noble signatures + the NIST KAT vector,
  //    verified by the KAT-proven AereFalcon512Verifier behind a precompile adapter.
  // -------------------------------------------------------------------------
  describe("real Falcon-512 (KAT-proven verifier, genuine signatures over the order digest)", function () {
    let falcon512, adapterCode, verifier;
    const kat = require("./falcon512_kat0.json");
    const HEAVY = { gasLimit: 500_000_000 };

    before(async function () {
      const nobleFalcon = path.resolve(__dirname, "../../sdk-js/node_modules/@noble/post-quantum/falcon.js");
      ({ falcon512 } = await import(pathToFileURL(nobleFalcon).href));

      verifier = await (await ethers.getContractFactory("AereFalcon512Verifier")).deploy();
      await verifier.waitForDeployment();
      const adapter = await (await ethers.getContractFactory("RealFalcon512PrecompileAdapter")).deploy(await verifier.getAddress());
      await adapter.waitForDeployment();
      adapterCode = await ethers.provider.getCode(await adapter.getAddress());
    });

    // noble detached (0x39 || nonce(40) || compressed) -> AERE envelope (nonce(40) || 0x29 || compressed)
    function realEnvelope(detached) {
      const nonce = detached.slice(1, 41);
      const compressed = detached.slice(41);
      const esig = new Uint8Array(1 + compressed.length);
      esig[0] = 0x29;
      esig.set(compressed, 1);
      return ethers.hexlify(ethers.concat([nonce, esig]));
    }

    async function installRealFalcon() {
      await network.provider.send("hardhat_setCode", [PRECOMPILE[1], adapterCode]);
    }

    it("the precompile adapter accepts the OFFICIAL NIST Falcon-512 KAT vector 0", async function () {
      await installRealFalcon();
      const ret = await ethers.provider.call({ to: PRECOMPILE[1], data: ethers.concat([kat.pk, kat.sm]) });
      expect(BigInt(ret)).to.equal(1n);
    });

    it("a GENUINE Falcon-512 signature over the pool's order digest FILLS the order; a tampered one is rejected", async function () {
      await installRealFalcon();
      const [_, user, relayer, recipient] = signers;
      const ctx = await deployStack(1);
      ctx._now = await latestTs();

      // Falcon keygen wants a 48-byte seed; derive one deterministically.
      const seed = ethers.getBytes(
        ethers.concat([
          ethers.keccak256(ethers.toUtf8Bytes("pqc-order-falcon-seed-a")),
          ethers.keccak256(ethers.toUtf8Bytes("pqc-order-falcon-seed-b")),
        ])
      ).slice(0, 48);
      const kp = falcon512.keygen(seed);
      const pubKey = ethers.hexlify(kp.publicKey);
      expect(kp.publicKey.length).to.equal(897);

      const inputAmount = 2500n * 10n ** 6n;
      await prepUser(ctx, user, pubKey, inputAmount * 2n);

      const { tuple } = buildOrder(ctx, { user: user.address, nonce: 0, algorithmId: 1, inputAmount, recipient: recipient.address });
      const digest = await ctx.auth.orderDigest(ctx.poolAddr, tuple, pubKey);

      // Produce a genuine Falcon-512 signature over the 32-byte order digest and confirm
      // the FULL registry -> adapter -> verifier path accepts it (Falcon signing is
      // randomized; re-sign on the rare estimation miss, as a real client would).
      let envelope;
      for (let attempt = 0; attempt < 6; attempt++) {
        envelope = realEnvelope(falcon512.sign(ethers.getBytes(digest), kp.secretKey));
        if (await ctx.reg.verify(1, pubKey, digest, envelope)) break;
        envelope = null;
      }
      expect(envelope, "could not produce a verifying Falcon-512 leg").to.not.equal(null);

      const feeSolver = inputAmount - (inputAmount * 50n) / 10_000n;
      await (await ctx.pool.connect(relayer).openForPQC(tuple, pubKey, envelope, HEAVY)).wait();
      expect(await ctx.pool.totalLocked(ctx.inputAddr)).to.equal(feeSolver);
      expect(await ctx.pool.pqcNonce(user.address)).to.equal(1n);

      // Tamper one byte deep in the compressed signature -> the real verifier rejects it.
      const tampered = ethers.getBytes(envelope);
      tampered[80] ^= 0x01;
      const next = buildOrder(ctx, { user: user.address, nonce: 1, algorithmId: 1, inputAmount, recipient: recipient.address });
      await expect(ctx.pool.connect(relayer).openForPQC(next.tuple, pubKey, ethers.hexlify(tampered), HEAVY))
        .to.be.revertedWithCustomError(ctx.pool, "InvalidSignature");
      expect(await ctx.pool.pqcNonce(user.address)).to.equal(1n);
    });

    it("SDK order-builder digest matches on-chain byte-for-byte AND an SDK-signed order fills", async function () {
      await installRealFalcon();
      const [_, user, relayer, recipient] = signers;
      const ctx = await deployStack(1);
      ctx._now = await latestTs();

      // Load the built SDK order-builder (ESM dist).
      const sdkPath = path.resolve(__dirname, "../../sdk-js/dist/intents/index.js");
      const sdk = await import(pathToFileURL(sdkPath).href);

      // Falcon-512 keypair via the SDK.
      const seed = ethers.getBytes(
        ethers.concat([
          ethers.keccak256(ethers.toUtf8Bytes("sdk-order-seed-a")),
          ethers.keccak256(ethers.toUtf8Bytes("sdk-order-seed-b")),
        ])
      ).slice(0, 48);
      const kp = sdk.generateOrderKey(sdk.PQC_SCHEME.FALCON512, seed);
      const pubKey = ethers.hexlify(kp.publicKey);
      const inputAmount = 1234n * 10n ** 6n;
      await prepUser(ctx, user, pubKey, inputAmount * 2n);

      const now = await latestTs();
      const signed = sdk.buildAndSignPQCOrder({
        settler: ctx.poolAddr,
        chainId,
        scheme: sdk.PQC_SCHEME.FALCON512,
        publicKey: kp.publicKey,
        secretKey: kp.secretKey,
        params: {
          user: user.address,
          nonce: 0,
          originChainId: chainId,
          openDeadline: now + 1800,
          fillDeadline: now + 3600,
          algorithmId: 1,
          order: {
            inputToken: ctx.inputAddr,
            inputAmount,
            outputToken: ctx.waereAddr,
            outputAmount: inputAmount,
            recipient: recipient.address,
            destinationChainId: DEST_CHAIN,
          },
        },
      });

      // Struct field order for the ethers call.
      const o = signed.order;
      const tuple = [o.user, o.nonce, o.originChainId, o.openDeadline, o.fillDeadline, o.algorithmId, o.orderDataType, o.orderData];

      // (a) The SDK-computed digest equals the on-chain authorizer digest, byte-for-byte.
      const onchainDigest = await ctx.auth.orderDigest(ctx.poolAddr, tuple, pubKey);
      expect(signed.digest).to.equal(onchainDigest);

      // (b) The SDK-built order + genuine Falcon signature fills through the real verifier.
      await expect(ctx.pool.connect(relayer).openForPQC(tuple, signed.pubKey, signed.signature, HEAVY))
        .to.emit(ctx.pool, "PQCOrderOpened");
      const solverPortion = inputAmount - (inputAmount * 50n) / 10_000n;
      expect(await ctx.pool.totalLocked(ctx.inputAddr)).to.equal(solverPortion);
      expect(await ctx.pool.pqcNonce(user.address)).to.equal(1n);
    });
  });
});
