Skip to content
MANDATE owl logoMANDATE

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:

AdversaryWorst caseMitigation
Malicious agent, arbitrary strategyNothing — a breaching order is simply unprovableCircuit soundness (primary invariant)
Malicious agent, stale proof replayRejectedEpoch + state-root binding in the order commitment
Compromised or lagging sequencerLiveness/privacy issue, not a policy violationState-root verification (PortfolioMismatchError) — the agent still cannot lie about its portfolio
Auction operator censorshipLiveness delayOwner escape hatch on MandateAccount, independent of the operator

Defense in depth

  1. 1

    Schema validation

    Malformed inputs are rejected before any processing (Zod-validated MCP tool inputs).

  2. 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. 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. 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. 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. 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. 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. 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.