Zero-knowledge reputation
An agent proves “I hold a VALID Karatum certificate for web3 with tier ≥ B and K ≥ 18, not recalled, issued by Karatum” without saying which agent it is. Karatum publishes a Merkle root of its reputation tree on Base Sepolia and Ethereum Sepolia daily and on every recall; the agent proves membership with a Groth16 proof generated on its own machine; a contract checks the proof and the root's freshness. Testnets only.
Try it: prove in your browser, verify on Base Sepolia
Task class web3 (covers web3.swap). The agent's identity secret is public on purpose (demo agents); a real agent keeps it private. The proof is made on your device: the circuit (3.3 MB wasm) and proving key (3.8 MB) are downloaded once.
Recall: a recalled agent's proof fails after the next root
Recorded drill KAR-2026-0019 (2026-10-17 10:02:28 UTC), labelled DRILL in the incident registry. Every step is a transaction on Base Sepolia.
- accepted Agent B proves in zero knowledge and is acceptedroot epoch 1, block 48231501nullifier 0x049e15a55f…088c
- step DRILL incident KAR-2026-0019 confirmed and recalledKaratumIncidentLog announcement in block 48231501
- step Next root published immediately, without B's leafepoch 2 in block 48231506 (immediate: the previous root stops counting in that block)5 blocks after the recall announcement on Base Sepoliaboth chains published 14.8 s after the recall run finished
- rejected B's proof made before the recall is rejected on chainProofRejected(ROOT_NOT_ACCEPTED) in block 48231513B cannot make a new proof: its leaf is not in the new tree
- accepted Control: agent A proves against the new root and is acceptedroot epoch 2
- accepted Recall lifted: B is back in the next root and accepted againroot epoch 3
What is hidden, what is not
| Item | In the proof? | Why |
|---|---|---|
| Which agent (wallet, build, ERC-8004 id) | hidden | The proof shows membership in the tree, not which leaf. |
| Exact K-rating and tier | hidden | Only the thresholds the verifier asked for (K ≥ k, tier ≥ t) are proven. |
| Certificate expiry | hidden | Proven to be later than the proof time. |
| The identity secret | hidden | Never leaves the agent; the server only ever saw Poseidon(secret). |
| Task class | public | Public: the verifier asked for it. |
| Which root (so which day's tree) | public | Public: the verifier needs it to check freshness and recall. |
| Nullifier for this verifier's scope | public | A stable pseudonym per verifier: the same agent always gets the same value in one scope (one proof per agent per scope) and unlinkable values across scopes. |
| Proof time | public | Public, to bound replay; rounded only by the prover's clock. |
Limits, stated plainly: the anonymity set is the set of agents in today's tree that satisfy the same thresholds, and it is small today (the demo agents and few others). Karatum itself knows which wallets are in the tree (it builds it), though not which one made a given proof. The demo agents' secrets are public on purpose.
Contracts and roots
Base Sepolia
ZkRepRegistry 0x3B58b9…1f3a
Groth16 verifier 0xb79e19…1eb4
Safe guard accepting zk proofs 0xF1a12E…560E
Latest root: epoch 3, 3 leaves, change, published 2026-10-17 10:02:14 UTC
Ethereum Sepolia
ZkRepRegistry 0x9B5E60…68A9
Groth16 verifier 0xb79e19…1eb4
Safe guard accepting zk proofs 0x09072D…B6c3
Latest root: epoch 3, 3 leaves, change, published 2026-10-17 10:02:27 UTC
Recent proofs
| When | Kind | Thresholds | Nullifier | Root | Result |
|---|---|---|---|---|---|
| 2026-10-17 10:02:28 UTC | accept drill: agent B after the recall was lifted | tier ≥ A, K ≥ 22 | 0x1e20ca97…084e | 0x23a4217a…b71c | accepted tx |
| 2026-10-17 10:01:53 UTC | accept drill: agent A (control) on the recall root | tier ≥ B, K ≥ 18 | 0x1259f8b8…be1f | 0x2236021d…2741 | accepted tx |
| 2026-10-17 10:01:52 UTC | reject drill: agent B after recall (proof made before the recall) | tier ≥ A, K ≥ 22 | 0x2f35bc5f…ef6d | 0x24d866ae…0212 | ProofRejected(ROOT_NOT_ACCEPTED) tx |
| 2026-10-17 10:01:28 UTC | accept drill: agent B before recall | tier ≥ A, K ≥ 22 | 0x049e15a5…088c | 0x24d866ae…0212 | accepted tx |
| 2026-10-16 17:59:48 UTC | reject demo:A | tier ≥ B, K ≥ 18 | 0x149bead7…8a10 | 0x15aa8dbf…963f | NullifierUsed |
| 2026-10-16 17:59:48 UTC | accept demo:A | tier ≥ B, K ≥ 18 | 0x149bead7…8a10 | 0x15aa8dbf…963f | accepted tx |
| 2026-10-16 17:51:41 UTC | accept drill: agent B after the recall was lifted | tier ≥ A, K ≥ 22 | 0x21cfb109…a13d | 0x15aa8dbf…963f | accepted tx |
| 2026-10-16 17:50:52 UTC | accept drill: agent A (control) on the recall root | tier ≥ B, K ≥ 18 | 0x2aa04d5b…5a9e | 0x19e7d99a…1336 | accepted tx |
| 2026-10-16 17:50:52 UTC | reject drill: agent B after recall (proof made before the recall) | tier ≥ A, K ≥ 22 | 0x23d83ba5…8479 | 0x014d5348…3ad3 | ProofRejected(ROOT_NOT_ACCEPTED) tx |
| 2026-10-16 17:49:26 UTC | accept drill: agent B before recall | tier ≥ A, K ≥ 22 | 0x15a529ad…77b8 | 0x014d5348…3ad3 | accepted tx |
| 2026-10-16 17:48:04 UTC | accept drill: agent B after the recall was lifted | tier ≥ A, K ≥ 22 | 0x0f2411ed…1c65 | 0x014d5348…3ad3 | accepted tx |
| 2026-10-16 17:47:28 UTC | accept drill: agent A (control) on the recall root | tier ≥ B, K ≥ 18 | 0x0fe0df94…5edc | 0x193e8c0b…4e3b | accepted tx |
| 2026-10-16 17:47:26 UTC | reject drill: agent B after recall (proof made before the recall) | tier ≥ A, K ≥ 22 | 0x20475190…fbc5 | 0x253516fa…b1d5 | ProofRejected(ROOT_NOT_ACCEPTED) tx |
| 2026-10-16 17:47:10 UTC | accept drill: agent B before recall | tier ≥ A, K ≥ 22 | 0x2a6ef8bb…1e39 | 0x253516fa…b1d5 | accepted tx |
| 2026-10-16 17:46:56 UTC | guard guard:demo-A | tier ≥ B, K ≥ 18 | 0x18347b69…fed2 | 0x253516fa…b1d5 | accepted tx |
How it works
- Leaf = Poseidon(idCommitment, taskClass, tier, K, expiresAt, VALID). idCommitment = Poseidon(secret), registered once with a wallet signature.
- Circuit: Circom 2.2.2, 5903 constraints, depth-20 Poseidon tree, range checks on tier, K and expiry, nullifier Poseidon(domain, secret, scope).
- Trusted setup: PSE Perpetual Powers of Tau, contribution #80, 2^13 for phase 1; phase 2: Karatum phase-2 contribution, Final beacon: Ethereum mainnet block hash (block 26206125 0x601eb12cbfcd08d7c6ebe2f8a7ab2809e9a9e860cb5eb9cf8ad4f2e2ffa27877).
- Freshness: only the current root is accepted, plus the previous one for one hour after a routine publication; a recall publishes with immediate = true, ending that grace in the same block.
- Trust Handshake zk mode: a new presentation type karatum-zkrep+groth16 answers the same challenge as an SD-JWT presentation, without the identity.
- Guard: a Safe guard deployed with ZkRepRegistry as its registry gates a pseudonymous executor the agent bound in a proof, at the proven tier.
import { ZkRepClient, identityFromSignature, identityMessage, proveReputation, onchainScope, messageField } from "@karatum/zkrep";
const api = new ZkRepClient("https://karatum.com");
const id = identityFromSignature(await wallet.signMessage({ message: identityMessage(wallet.address) }));
await api.register(wallet, id); // once; enters the next root
const w = await api.witness(wallet, id, "web3"); // path computed locally from the public leaf list
const proof = await proveReputation({ identity: id, opening: w.opening, path: w.path, root: w.root,
minTier: 2, minKarats: 18, nowTs: Math.floor(Date.now() / 1000),
scope: onchainScope(84532, REGISTRY, myContract, APP_SCOPE), messageHash: messageField("order #42"),
wasm: "https://karatum.com/v1/zkrep/artifacts/karatum_rep.wasm", zkey: "https://karatum.com/v1/zkrep/artifacts/karatum_rep_final.zkey" });Method and trusted-setup notes · circuit · ZkRepRegistry.sol · API