aere-contracts/test/pqc-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

63 lines
2.8 KiB
JavaScript

const { expect } = require("chai");
const { ethers } = require("hardhat");
// Off-chain WOTS+ signer matching AerePQCVerifier exactly.
function chain(x, count) { for (let i = 0; i < count; i++) x = ethers.keccak256(x); return x; }
function digits(mHex) {
const m = ethers.getBytes(mHex); const d = new Array(67); let csum = 0;
for (let i = 0; i < 32; i++) { const b = m[i], hi = b >> 4, lo = b & 0x0f; d[2 * i] = hi; d[2 * i + 1] = lo; csum += (15 - hi) + (15 - lo); }
d[64] = (csum >> 8) & 0x0f; d[65] = (csum >> 4) & 0x0f; d[66] = csum & 0x0f; return d;
}
function keygen() {
const sk = []; for (let i = 0; i < 67; i++) sk.push(ethers.hexlify(ethers.randomBytes(32)));
const pkEnds = sk.map(s => chain(s, 15));
const compressedPK = ethers.keccak256(ethers.concat(pkEnds));
return { sk, compressedPK };
}
function sign(sk, msgHash) { const d = digits(msgHash); return sk.map((s, i) => chain(s, d[i])); }
describe("AerePQCVerifier (WOTS+ post-quantum)", function () {
async function deploy() {
const V = await ethers.getContractFactory("AerePQCVerifier");
const v = await V.deploy(); await v.waitForDeployment(); return v;
}
it("accepts a valid WOTS+ signature", async function () {
const v = await deploy();
const { sk, compressedPK } = keygen();
const msgHash = ethers.keccak256(ethers.toUtf8Bytes("AERE is post-quantum ready"));
const sig = sign(sk, msgHash);
expect(await v.verify(msgHash, sig, compressedPK)).to.equal(true);
// derivePublicKey helper agrees
expect(await v.derivePublicKey(sk)).to.equal(compressedPK);
});
it("rejects a wrong public key", async function () {
const v = await deploy();
const { sk } = keygen();
const msgHash = ethers.keccak256(ethers.toUtf8Bytes("m"));
const sig = sign(sk, msgHash);
expect(await v.verify(msgHash, sig, ethers.keccak256("0xdead"))).to.equal(false);
});
it("rejects a signature reused on a DIFFERENT message (forgery-resistant)", async function () {
const v = await deploy();
const { sk, compressedPK } = keygen();
const m1 = ethers.keccak256(ethers.toUtf8Bytes("original"));
const sig = sign(sk, m1);
const m2 = ethers.keccak256(ethers.toUtf8Bytes("forged"));
expect(await v.verify(m2, sig, compressedPK)).to.equal(false);
});
it("checksum blocks a naive forge (raising a digit without lowering checksum)", async function () {
const v = await deploy();
const { sk, compressedPK } = keygen();
const m = ethers.keccak256(ethers.toUtf8Bytes("x"));
const sig = sign(sk, m);
// advance one message-chain by 1 (attacker can hash forward) — checksum digits stay, so PK mismatch
const tampered = [...sig];
tampered[0] = ethers.keccak256(tampered[0]);
expect(await v.verify(m, tampered, compressedPK)).to.equal(false);
});
});