Security
Defense in depth: failure at any single layer is sufficient to prevent an order from being submitted.
Threat model
What each adversary can and cannot do, as tested in the repository's circuit and contract test suites:
| Adversary | Worst case | Mitigation |
|---|---|---|
| Malicious agent, arbitrary strategy | Nothing — a breaching order is simply unprovable | Circuit soundness (primary invariant) |
| Malicious agent, stale proof replay | Rejected | Epoch + state-root binding in the order commitment |
| Compromised or lagging sequencer | Liveness/privacy issue, not a policy violation | State-root verification (PortfolioMismatchError) — the agent still cannot lie about its portfolio |
| Auction operator censorship | Liveness delay | Owner escape hatch on MandateAccount, independent of the operator |
Defense in depth
- 1
Schema validation
Malformed inputs are rejected before any processing (Zod-validated MCP tool inputs).
- 2
Session-key authorization
The agent must hold a registered key scoped to a specific mandate. The session key has zero authority over funds — no MandateAccount function is callable by it.
- 3
Policy-commitment verification
MandateClient checks the local plaintext policy opens the on-chain registered commitment before ever proving — refusing to prove with a stale or wrong mandate.
- 4
Portfolio-state-root verification
Confirms the sequencer-reported portfolio matches the on-chain committed state root. The agent cannot fabricate a healthier portfolio. The sequencer/operator is trusted to post correct state roots after clearing — state-transition proofs are deferred.
- 5
Zero-knowledge compliance proof
The Noir/UltraHonk circuit cryptographically proves all policy constraints are satisfied simultaneously — whitelist, notional, position, daily loss, and breaker mode.
- 6
On-chain contract verification
The bb-generated HonkVerifier re-verifies the proof on-chain. The five public inputs (policy commitment, state root, order commitment, epoch, breaker bit) are assembled by the contract from registry state, not taken from calldata.
- 7
Batch-auction epoch commitment
An order is committed only within the valid commit window of the current epoch; the order commitment binds the epoch, so a proof cannot be replayed against a different one.
- 8
Owner escape hatch
MandateAccount provides an owner-only withdrawal that depends on nothing — not the agent, sequencer, proof, or auction being alive.
Failure at any single layer is sufficient to prevent an order from being submitted.
Trust assumptions
- The sequencer is trusted for liveness and state updates— it computes and posts post-fill Merkle roots off-chain. State-transition proofs (fraud proofs / validity proofs) are deferred, so a malicious sequencer could stall the system, but the ZK circuit still prevents it from enabling a policy violation: the agent can only prove against the root the sequencer posted, and if that root disagrees with reality, that's a liveness/state bug to fix, not a security hole an agent can exploit for profit.
- The sequencer is trusted for order-flow privacy — the commit–reveal design simulates threshold encryption through a single party. Ring-1 privacy holds only against everyone except the sequencer itself.
- The powers-of-tau / circuit setup underlying UltraHonk is a universal, widely-reused setup — not specific to this project, and not re-audited here.
- Not assumed for fund safety:the sequencer's honesty, or any liveness of the agent, auction, or proof pipeline — the escape hatch below depends on none of them.
Escape hatch
MandateAccount provides an owner-only withdrawal path that depends on nothing — not the agent, the sequencer, a proof, or the auction being alive. This is tested directly: test_account_escapeHatchWorksWithDeadInfrastructure. The session key that authorizes trading has zero authority over this path; no MandateAccount function is callable by it.
Secrets management
Never commit these
- This system is experimental and has not undergone an external security audit.
- It runs entirely on local infrastructure (a local Anvil chain) — there is no public testnet or mainnet deployment.
- All keys and values are test-only. This is not production custody infrastructure.
- Session keys and policy salts are secrets and must never be committed to version control.
- Do not give the same agent unrestricted wallet, swap, transfer, or trading tools alongside mandate_submit_order — proof-gating only works if it is the only execution path.
Concretely, MANDATE_SESSION_KEY and MANDATE_POLICY_SALT (see MCP — client configuration) are the two values that must never appear in version control, logs, or chat transcripts. The local setup script writes them to .local/ with chmod 600 and that directory is git-ignored by default.
Current limitations
- Local Anvil chain only — no public testnet or mainnet deployment.
- No external security audit of the circuit, contracts, or SDK.
- Test-only keys and values throughout; not production custody infrastructure.
- Single, centralized sequencer — no decentralized keyper committee or watchtower network yet.
- State-transition proofs deferred — the operator is trusted to post correct state roots after clearing.
- No cross-chain settlement (ERC-7683), TEE fast lane, or DA-blob publishing yet.