icme-preflight
v0.1.0
Published
Verify Preflight receipts and build on-chain attestations.
Maintainers
Readme
icme-preflight
Verify Preflight receipts, and turn one into an on-chain attestation on Arc.
A receipt is a zero-knowledge proof that an AI agent's action was checked against a formal policy by an SMT solver. Verifying one tells you the check happened and what it returned, without revealing the policy or the action values.
Install
npm install icme-preflightNo Rust toolchain. The verifier is the same arkworks implementation the on-chain contract agrees with, compiled to WebAssembly and shipped in the package.
Check your install
import { selfTest } from "icme-preflight";
const { passed, failed } = await selfTest();
console.log(passed, "passed", failed); // 12 passed []That verifies twelve bundled vectors — valid proofs, malformed proofs, wrong-key proofs, and field-boundary cases. The same vectors drive the Solidity and Rust verifiers, so a clean run means your install agrees with the chain on all of them, including the ones that must be rejected.
Verify a receipt
import { verify, bundledVerifyingKey } from "icme-preflight";
const key = await bundledVerifyingKey();
const ok = verify(receipt, key);verify returns false for a well-formed receipt that does not check out, and throws only when
the receipt cannot be read at all. Those are different failures and usually deserve different
handling.
Pass a receipt to a counterparty
import { toHeader, fromHeader } from "icme-preflight";
fetch(url, { headers: { "X-Preflight-Proof": toHeader(receipt) } });About 1.2 KB — inside the 8 KB header limit nginx and Apache default to. The receiving side calls
fromHeader then verify, with no round trip to a chain.
Record it on-chain
import { buildSubmission } from "icme-preflight";
const data = buildSubmission(receipt); // calldata for submitProof
await wallet.sendTransaction({ to: registryAddress, data });Calldata is returned rather than sent, so you keep control of signing, gas, and nonce. Arc pays gas in USDC — get testnet funds from faucet.circle.com, 20 USDC per address every two hours, which covers thousands of attestations.
The registry rejects a proof that does not verify, so an attestation exists only as a side effect of a passing check. It also rejects replays, and rejects a second, contradictory result for the same policy and action rather than recording both.
Signal layout
Don't hardcode it. signalCount(key) reads it from the verifying key. The layout changes when
the proving circuit changes, and reading it means your code doesn't.
Status
The wrap circuit is a placeholder. Receipts verify, submit, and record correctly — every link
in the chain is real — but the proof currently attests to nothing about the underlying policy
check. Placeholder attestations are identifiable on-chain: the circuit constrains vkHash to
0x…504c414345484c44 ("PLACEHLD"), so they can be filtered and can never be mistaken for real
ones.
Browser support covers verify, signalCount, buildSubmission, and the header helpers.
selfTest and bundledVerifyingKey read from disk and are Node-only.
Licence
MIT
Full walkthrough
With a prover running (cargo run --release --bin preflight-prover, or point at a hosted one):
node examples/walkthrough.mjsCheck the install, get a receipt for your own policy, verify it, pass it through a header, tamper with it and watch it fail, then build the on-chain calldata.
