A zero-knowledge "passport" that lets autonomous AI agents pay — without doxxing their owner or exposing their balance.
Built for the Stellar Hacks: Real-World ZK hackathon. The trust layer that Open-Stellar (and any agent-commerce hub on Stellar) was missing.
▶ Live demo: https://bitcoindefi.github.io/open-stellar-passport/
Generates a real Groth16 proof in your browser and verifies it on the deployed Stellar testnet contract — no wallet, no signup.
Live demo: real in-browser Groth16 proving → real on-chain verification → x402 payment gate. Run it with
cd frontend && npm install && npm run dev.
🎬 Demo video (2:02, ElevenLabs voiceover + synced captions): narrated · silent — built with Remotion in video/, script in docs/VIDEO.md
Not a mock — a real ZK proof was verified and an attestation minted on-chain. Everything here is independently verifiable on stellar.expert:
| Address / tx | Explorer | |
|---|---|---|
| AgentPassportValidator — stateful policy contract | CDNSZUNEWFCGSPWLPDSWTENR2WPHKC34RGZQG7RJA54OPGTZGVVRFYBA |
view ↗ |
| CircomGroth16Verifier — BN254, our circuit's VK baked in | CCMKLYSRUH2HMA4UU6WLXWQXEY6KAH5AWB5BEVMJGNGC5GLGTVROLG4A |
view ↗ |
Mint tx — verify_and_register succeeded, passport event emitted |
226170818ccb4e37817e3929cff328da5d71203c72a219c91325100bde966f90 |
view ↗ |
| Init tx | 099f4243ead8af663eaa4cdd31312cc6b981f91362ab4e320572fc87d925025d |
view ↗ |
What the chain enforces — tested end-to-end:
- ✅ Valid proof →
verify_and_registerSuccess: attestation minted for agent#42at ledger3,146,304,passportevent emitted with the nullifier + spend cap. - 🔁 Replay the same proof →
Error(Contract, #4)=NullifierUsed(stateful anti-replay / anti-Sybil). - ✋ Tamper a public input →
Error(Contract, #5)=InvalidProof(the BN254 pairing check rejects it — soundness). - ❌ Proof against unknown root →
Error(Contract, #6)=UnknownRegistryRoot(only approved roots allowed). - 🔒 Only 4 public inputs ever reach the chain (
registryRoot,nullifierHash,agentId,spendCap). The owner key, balance and Merkle path never leave the browser.
Reproduce it yourself against the live contract:
node scripts/smoke-onchain.mjs # fresh proof verifies · replay rejected · reads
# → SMOKE OK — 4 passed, 0 failedAI agents are starting to pay for things autonomously (x402). To let an agent transact, today you do one of two dangerous things:
- Hand it keys or your full balance → if the agent is compromised, you lose everything (money loss).
- KYC the operator into a central database → another honeypot that gets breached (identity loss — see the 2025 exchange KYC leaks).
And agents get impersonated and Sybil-farmed. There's no way to know an agent is backed by a real, solvent, authorized human without revealing who they are or how much they hold.
Every existing "agent passport" (SelfClaw, risotto-passport, World ID + AgentKit…) lives on EVM/Solana, verifies identity off-chain via an external service, only proves personhood once, and never gates the actual payment with proof-of-funds. None are on Stellar.
The human owner mints an Agent Passport: a ZK credential bound to the agent's Stellar address that proves, in a single Groth16 proof verified on-chain in Soroban:
| Claim | How |
|---|---|
| 🧍 The operator is a verified human/business | Merkle membership in an attested-identity registry (no PII on-chain) |
| 🔒 One identity can't spawn infinite agents | Poseidon2 nullifier bound to the agent id (anti-Sybil / anti-replay) |
| 💰 The operator is solvent for the spend | proof-of-funds: balance ≥ spendCap, balance stays hidden |
A compromised agent can't exceed its proven cap. The owner's identity and real balance never leave the device. An auditor can later be given a view key (selective disclosure — roadmap).
Human → mints agent in trionlabs/stellar-8004 Identity Registry (agent_id)
→ owner generates Groth16 proof CLIENT-SIDE (snarkjs/WASM, secrets never leave)
→ AgentPassportValidator (Soroban) verifies proof on BN254, burns the nullifier
→ writes a "zk-passport" attestation (validator store today; 8004 Validation Registry is the target)
At payment time (x402): settle only if agent has a valid passport AND amount ≤ proven cap.
We reuse:
- NethermindEth/stellar-private-payments (Apache-2.0) — the
circom-groth16-verifierSoroban contract (BN254 native precompile) and the Poseidon2 / Keypair / MerkleProof circom building blocks. - trionlabs/stellar-8004 (MIT) — ERC-8004 Identity / Reputation / Validation registries on Soroban.
The circuit (circuits/agent_passport.circom)
private: privateKey, balance, pathElements[20], pathIndices
public : registryRoot, nullifierHash, agentId, spendCap
proves : publicKey = Poseidon2(privateKey, 0)
MerkleProof(publicKey, path) == registryRoot // personhood
nullifierHash == Poseidon2(privateKey, agentId) // anti-Sybil/replay
balance >= spendCap // proof-of-funds
~9.6k constraints · 4 public inputs · proves in well under a second client-side.
- Phase 0 — circuit compiles, Groth16 trusted setup, proof generated & verified off-chain ✅
- Phase 1 — VK baked into the Soroban
circom-groth16-verifier, deployed to testnet & proof verified ON-CHAIN ✅- Verifier contract:
CCMKLYSRUH2HMA4UU6WLXWQXEY6KAH5AWB5BEVMJGNGC5GLGTVROLG4A(testnet) - Valid proof →
true; tampered public input →InvalidProof(soundness verified)
- Verifier contract:
- Phase 2 —
AgentPassportValidator(stateful policy layer) + TypeScript SDK ✅- Validator contract:
CDNSZUNEWFCGSPWLPDSWTENR2WPHKC34RGZQG7RJA54OPGTZGVVRFYBA(testnet) - Cross-contract calls the verifier, burns the nullifier (anti-replay / anti-Sybil), mints a
zk-passportattestation - Audit views:
list_registry_roots()enumerates the current root allow-list. Spent nullifiers stay event-sourced fromPassportRegisteredevents, withis_nullifier_used(...)available for point checks so the contract avoids an unbounded nullifier list. - On-chain e2e:
verify_and_register→ minted ✅; replay →NullifierUsed✅; tampered input →InvalidProof✅ (5 unit tests run the real proof through the real verifier WASM) @open-stellar/agent-passportSDK: client-side proving (snarkjs) + typed client + theauthorizePaymentx402 gate
- Validator contract:
- Phase 3 — live demo frontend ✅
- Real in-browser Groth16 proving (~1 s) → real on-chain verification (no wallet needed) → x402 payment gate → anti-replay
- Vite + React 19 + Tailwind v4; design system in
design-system/(published to Claude Design) - Run:
cd frontend && npm install && npm run dev
- Selective-disclosure view key + per-payment
amount ≤ capproof (stretch)
The legacy scripts/wsl-*.sh helpers still work for the original WSL layout,
but the portable path is now the repository Makefile.
make check-build-tools
make verify-committed-zk
make build-contractsTooling:
- Node.js + npm install the pinned
snarkjsdependency frompackage-lock.json. - Rust +
rustupbuild and test the Soroban validator crate. circom2.2.x is required only when recompiling.circomsources.- Stellar CLI is required only for release contract artifact parity, deployment, and TypeScript binding generation.
make verify-committed-zk copies the committed browser artifacts from
frontend/public/zk/ into build/, exports the verification key from the
committed zkey, compares it with frontend/public/zk/verification_key.json, and
runs node scripts/smoke.mjs against those copied artifacts.
To recompile the circuit sources:
make build-circuitTo run a fresh local Groth16 setup, provide the powers-of-tau file:
make circuit-setup POT14=/path/to/pot14_final.ptau ZKEY_ENTROPY="local entropy"The resulting zkey is expected to differ from
frontend/public/zk/agent_passport_final.zkey unless the original contribution
transcript and entropy are reused. The committed zkey remains auditable through
the verify-committed-zk target.
make build-contracts runs the validator unit tests, compiles the contract with
the current Rust wasm32v1-none target, and writes the portable Cargo artifact
to build/contracts/agent_passport_validator.cargo.wasm. The raw Cargo output
can differ from the committed optimized deployment artifact, so the target prints
a SHA-256 comparison instead of hiding the difference.
For byte-for-byte release artifact parity, install Stellar CLI and run:
make build-contracts-stellar
make build-verifier-contractThe verifier build target fetches Nethermind's
stellar-private-payments into ignored build/external/, injects the committed
verification key, and writes the built verifier WASM under build/contracts/.
Deployment targets are explicit and require a local Stellar identity name:
make deploy-verifier STELLAR_SOURCE=passport-deployer
make deploy-validator STELLAR_SOURCE=passport-deployer
make gen-bindingsdeploy-validator reads deploy/verifier-contract-id.txt, writes
deploy/validator-contract-id.txt, and initializes the validator. Set
STELLAR_ADMIN to override the admin address; otherwise the address for
STELLAR_SOURCE is used.
# prerequisites: node, circom 2.2.x, snarkjs
npm install
# compile circuit
circom circuits/agent_passport.circom --r1cs --wasm --sym -o build
# trusted setup (downloads powers-of-tau 2^14)
npx snarkjs groth16 setup build/agent_passport.r1cs build/pot14_final.ptau build/agent_passport_0000.zkey
npx snarkjs zkey contribute build/agent_passport_0000.zkey build/agent_passport_final.zkey -e="<entropy>"
npx snarkjs zkey export verificationkey build/agent_passport_final.zkey build/verification_key.json
# end-to-end smoke test: generate + verify a proof
node scripts/smoke.mjs # ==> PROOF VALID: trueMIT. Vendored circuit building blocks under circuits/lib/ are Apache-2.0 (© Nethermind), derived from tornadocash/tornado-nova.
The AgentPassportValidator contract has an admin role with significant power. Specifically, the admin can:
- Change the Verifier: Update the address of the verifier contract used for proof validation. A malicious admin could point to a verifier that always returns
true, bypassing all ZK security guarantees. - Manage Registry Roots: Add or remove approved identity registry roots.
To mitigate these risks:
- Observable Actions: All administrative actions (verifier changes, admin transfers) emit on-chain events for transparency.
- Two-Step Transfer: Admin rights are transferred through a propose-and-accept flow to prevent accidental loss of control.
- Multisig Recommendation: For production deployments, the admin should be a multisig account (e.g., Gnosis Safe on Stellar) or a DAO-controlled contract, rather than a single-signature key.
- Renouncement: The admin role can be renounced if no further upgrades or management are intended.
⚠️ Research prototype for a hackathon. Not audited. Do not use with real funds.
