aere-contracts/test/lattice-verifier.test.js
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
Aere Network public source. Everything here can be checked against the live
chain (chain id 2800, https://rpc.aere.network).

Scope note, stated up front rather than buried: consensus on chain 2800 is
classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at
the signature, precompile, account and transport layers. Nothing here makes the
consensus post-quantum, and no document in it should be read as claiming so.
2026-07-20 01:02:37 +03:00

59 lines
2.5 KiB
JavaScript

const { expect } = require("chai");
const { ethers } = require("hardhat");
const Q = 12289, N = 64;
function ringMul(a, b) {
const t = new Array(N).fill(0);
for (let i = 0; i < N; i++) { if (!a[i]) continue; for (let j = 0; j < N; j++) {
const p = (a[i] * b[j]) % Q; const k = i + j;
if (k < N) t[k] = (t[k] + p) % Q; else t[k - N] = (t[k - N] + Q - p) % Q;
} }
return t;
}
const short = () => Array.from({ length: N }, () => (((Math.floor(Math.random() * 7) - 3) % Q) + Q) % Q); // coeffs in [-3,3]
const randPoly = () => Array.from({ length: N }, () => Math.floor(Math.random() * Q));
describe("AereLatticeVerifier (Falcon/Dilithium-family lattice core)", function () {
async function deploy() {
const V = await ethers.getContractFactory("AereLatticeVerifier");
const v = await V.deploy(); await v.waitForDeployment(); return v;
}
it("accepts a valid short-vector lattice signature", async function () {
const v = await deploy();
const s1 = short(), s2 = short(), h = randPoly();
const c = ringMul(s2, h).map((tv, i) => (s1[i] + tv) % Q); // c = s1 + s2*h
expect(await v.verify(h, c, s1, s2, 20000)).to.equal(true);
});
it("rejects a tampered s1 (relation broken)", async function () {
const v = await deploy();
const s1 = short(), s2 = short(), h = randPoly();
const c = ringMul(s2, h).map((tv, i) => (s1[i] + tv) % Q);
const bad = [...s1]; bad[0] = (bad[0] + 1) % Q;
expect(await v.verify(h, c, bad, s2, 20000)).to.equal(false);
});
it("rejects a forgery: the relation holds but (s1,s2) is NOT short", async function () {
const v = await deploy();
const s1 = short(), s2 = short(), h = randPoly();
const c = ringMul(s2, h).map((tv, i) => (s1[i] + tv) % Q);
// attacker picks a different short s2b, solves s1b = c - s2b*h — which is LARGE
const s2b = short();
const tb = ringMul(s2b, h);
const s1b = c.map((cv, i) => ((cv - tb[i]) % Q + Q) % Q);
// relation c - s2b*h == s1b holds by construction, but norm blows the bound
expect(await v.verify(h, c, s1b, s2b, 20000)).to.equal(false);
});
it("ringMul is negacyclic (x^n = -1): x^(n-1) * x = -1", async function () {
const v = await deploy();
const a = new Array(N).fill(0); a[N - 1] = 1; // x^(n-1)
const b = new Array(N).fill(0); b[1] = 1; // x
const t = (await v.ringMul(a, b)).map(Number);
// x^(n-1) * x = x^n = -1 == q-1 at coefficient 0
expect(t[0]).to.equal(Q - 1);
expect(t.slice(1).every(x => x === 0)).to.equal(true);
});
});