Zero-knowledge tests
The Access gate relies on Semaphore v4: its circuits, its proving keys, its verifier contract and its JavaScript packages. The tests in blockchain/test/zk/ cover two different risks:
- Behaviour. The gate and Semaphore do what the access design assumes, checked with real Groth16 proofs, not mocks.
- Supply chain. The proof system that runs is exactly the one that was reviewed: pinned packages, hash-checked circuit files, proving keys that match the verifier, no mock verifier and no stray key files.
Background on the cryptography is in Zero-knowledge proofs.
How the pieces fit
Each box is checked by its own test file and, in CI, by its own step before the full suite runs.
Behaviour tests
The gate
| File | Covers |
|---|---|
AccessGate.test.ts | Constants and wiring, the scope vector (fixtures/access-scope-vector.json), the 20 KB size budget, zero addresses; admission by the bound holder or the issuer, one group per credential with the gate as its only admin, strangers refused (even members of another group), revoked, suspended and paused credentials refused, the 64th member accepted and the 65th refused, removed members still counted; removal rules |
AccessGateProofs.test.ts | A receipt from a fresh account; verifyAccess without spending; the epoch window (previous, current, next; the largest number refused); a new nullifier in the next epoch; wrong scope (other epoch, credential or gate), other chain id, other submitting account; replay of the same proof and of the same member in one epoch; a stranger cannot burn a member's nullifier by calling the library with a copied proof; never more receipts than members; tampered curve points, nullifier and root; a root not in the group; an unserved depth refused before the library is asked; old roots inside and outside the 10 minute window; a removed member; revoked after admission; paused (proving stops, verifying works); unknown credential and no group |
Semaphore as pinned
| File | Covers |
|---|---|
SemaphoreProperties.test.ts | The properties the access design relies on, against the pinned contracts: on-chain root equals the library's; proofs over made-up or different groups refused; tampered points; no proof for a secret at or above the subgroup order; no proof for a removed member against the new tree; all four public signals bound and in the order root, nullifier, hash(message), hash(scope); signals at or above the field modulus answer false (so nullifier plus modulus cannot replay); one nullifier per group whatever the message; nullifier derived from scope and secret only; proofs not bound to a group id (which is why the gate keeps its own ledger per credential); old root lifetimes, a zero duration, retroactive duration changes; depth limits 1 to 32; empty and missing groups; group admin rules; the pinned verifier code hash and the published Poseidon address; reference gas figures (printed, no budget) |
SemaphoreVectors.test.ts | Conformance vectors (fixtures/semaphore-vectors.json): the same identity from a seed, the same root and Merkle path, message and scope encoding, the public signal order accepted by the chain, a different nullifier for another scope |
Provers
| File | Covers |
|---|---|
AccessJourney.test.ts | The Node path end to end: new identity, access code, admission, proof, receipt and removal; the same constants as the deployed gate; proving from a tree with a removed slot; refusing a group deeper than served and a non-member |
AccessCli.test.ts | The npm run access tool: commands and argument errors, a malformed key never echoed, the node address hidden in error text, identity files written once and never overwritten, secrets never printed |
AccessBrowserProver.test.ts | A proof made by the browser prover module (src/lib/zk/) is accepted by the gate |
These tests import src/lib/zk/* and src/services/indexing/* from the app, which is why CI installs the root dependencies before the contract job runs.
Supply chain checks
| File | npm check | What it proves |
|---|---|---|
ZkPins.test.ts | check:zk-pins | Every proof system package (@semaphore-protocol/*, @zk-kit/*, snarkjs and helpers such as poseidon-solidity, ffjavascript) is an exact version that the lock file resolves, with an integrity hash and the npm registry address, in both blockchain/ and the root app; ranges like ^4.14.3, ~4.14.3, 4.x or latest are refused; overrides and resolutions are refused; the lock file entries equal the reviewed snapshots (fixtures/zk-lock-snapshot.json, zk-lock-snapshot-root.json), so even a transitive bump is a reviewed change |
ZkArtifacts.test.ts | zk:artifacts | Circuit files of artifact version 4.13.0 (the one the pinned proof library asks for) are downloaded from https://snark-artifacts.pse.dev/semaphore with a 60 second timeout, no redirects and a 16 MB cap, checked against fixtures/artifact-hashes.json, cached in blockchain/.zk-artifacts atomically, and replaced when a cached file's hash no longer matches; a file with no recorded hash is refused |
ProofCallGuard.test.ts | (test only) | Proof generation is imported only in allowed files and every call passes the pinned .wasm and .zkey paths, so no code path falls back to files fetched at run time |
VerificationKey.test.ts | (helper) | Reads the 14 key words per depth for all 32 depths from the pinned verifier source and from snarkjs keys, in the contract's order |
KeyCheck.test.ts | check:zk-keys | The verification key inside each of the 32 pinned .zkey files equals the key points in the pinned verifier; one changed byte, a swapped depth, a changed constant or a missing key fails and names the depth |
MockVerifierScan.test.ts | check:no-mock-verifier | No verifier whose body is only return true, no self-declared development verifier, no zero verifying key, no verifyProof without a pairing or a delegate; the real Groth16 verifier and the gate pass; mock and test folders are scanned too, because Hardhat compiles every folder under contracts/. CI also runs the check on the compiled ABIs |
DevKeyFiles.test.ts | check:no-dev-keys | No proving key, powers of tau file, constraint system, exported verification key or generated verifier contract is committed, including compressed or backup copies |
SetupReproduction.test.ts | zk:reproduce (helpers) | The helpers of the setup reproduction: pinned circuit commit, include packages, compiler and one hash per powers of tau size; the powers of tau size snarkjs picks; the generated circuit source; reading the contribution count, beacon and circuit hash; comparing with the recorded outcome for every depth |
DeployAccessGate.test.ts, VerifyAccessTargets.test.ts | deploy:access:*, verify:access:sepolia | Deployment wires the library, pinned verifier, Semaphore and gate; reuses the published Poseidon only if its code is right; refuses a verifier with another code hash and a registry with no code; writes the manifest; Etherscan targets in deployment order |
The trusted setup
Semaphore's proving keys come from a trusted setup. npm run zk:reproduce -- --workdir <dir> rebuilds every .zkey from the circuit source and the Perpetual Powers of Tau files, and compares the result with the recorded outcome. It is run by hand, not in CI (it is slow); the test suite checks only its helpers. Step P2-S06 recorded that all 32 keys were reproduced and match the pinned verifier (ADR-0070). That shows the keys are consistent with the public ceremony; it cannot show that any ceremony contributor was honest.
Running the ZK tests
cd blockchain
npm run zk:artifacts # once: download and hash-check the circuit files
npx hardhat test test/zk/AccessGateProofs.test.ts
npm run check:zk-pins && npm run check:no-mock-verifier && npm run check:no-dev-keys
npm run check:zk-keys
Proof-generating tests take a few hundred milliseconds per proof on a laptop.
Open points (as of 2026-10-24)
- The independent cryptography and circuit review (step P2-S09) has not taken place.
- The PSE Semaphore v4 audit report and the ceremony outcome page were not found during P2-S05 and P2-S06; they are listed for the reviewer.
- The gate on Sepolia is verified on Etherscan; a smoke with a real wallet is still open.
Source files
blockchain/test/zk/andblockchain/test/zk/helpers/gate-world.ts,semaphore.tsblockchain/test/zk/fixtures/blockchain/scripts/lib/zk-pins.ts,zk-artifacts.ts,zk-key-check.ts,verification-key.ts,mock-verifier-scan.ts,proof-call-guard.ts,dev-key-files.ts,zk-reproduce.ts,deploy-access-gate.tsblockchain/contracts/zk/,src/lib/zk/