Architecture
Concepts · the four layers
ShadowKit is layered so each concern is enforced where it belongs: privacy in the browser, correctness on-chain, execution behind a policy lock. Nothing is “trusted” — every claim is either a zero-knowledge proof, a timelock, or an on-chain check.
The four layers
Section titled “The four layers”- Snapshot & circuit (ZK).
@shadowkit/snapshot-toolbuilds a Poseidon Merkle tree of eligible holders. ThevoteCircom circuit proves membership + a hidden weight and emits a nullifier, producing a Groth16 proof over BLS12-381. - Seal (timelock).
@shadowkit/zk-provertlock-encrypts the(direction, weight)to the drand round at the proposal deadline. The on-chain ciphertext is bound to the proof by its commitment hash (public signal #4). - Governance (on-chain).
GovVaultverifies the proof via theGroth16Verifiercontract, stores the sealed vote, and — after the deadline — re-aggregates the revealed tally and setsApproved/Rejected. - Agent & policy (bounded execution). The
@shadowkit/agentloop pays an x402 data endpoint, asks an LLM for a bounded plan, and submits a swap — which theAgentPolicysmart-account contract authorizes only if it matches the approved action.
Data flow
Section titled “Data flow”Browser (private) Chain (verifiable) Agent (bounded)───────────────── ────────────────── ───────────────buildSnapshot() ── Merkle root ──────────────▶ GovVault.init(root)generateVoteProof() · Groth16 proof (snarkjs + vote.wasm) · tlock seal (direction,weight → round) │ proof + sealed ciphertext ▼ cast_vote(id, proof, pubSignals, sealed) ──▶ Groth16Verifier.verify store SealedVote (no tally) ── after deadline ──revealTally() (tlock decrypt) ── decryptions ─▶ close_and_reveal(id, yesW, noW, decryptions) re-aggregate → Approved? ───▶ Watcher.waitForApproved x402 pay premium-data LLM plan (≤ cap) AgentPolicy.enforce ◀──────── Executor.executeSwap SwapVenue.swap mark_executed (single-shot)The privacy / security split
Section titled “The privacy / security split”The demo runs fully live on testnet because the secrets are partitioned correctly:
- The voter’s witness (secret, weight, direction) never leaves the browser — that is what makes the vote private.
- The agent’s signing key and LLM key live only as Cloudflare Pages Function secrets — they never reach the browser.
Trust model
Section titled “Trust model”- No tally leak before close.
GovVaultexposesvotes_cast(participation) butweighted_yes/weighted_noareNoneuntil the reveal. - No double-vote. The nullifier (
Poseidon(secret, proposalId)) is stored; a repeat is rejected withNullifierUsed. - No replay across proposals. The proof binds
proposalIdas a public signal; a proof for one proposal fails on another (WrongProposalId). - No off-spec execution.
AgentPolicyre-readsGovVaultduring authorization and applies its gates (approved · not-executed · target · asset · cap). See Contracts.