Agentic anonymization module
Product · not yet on mainnetRootzk
Rootzk lets you move value privately on a public blockchain — deposit, transfer and withdraw so the ledger no longer links your operations to each other or to you — and lets an AI assistant help you do it without ever touching your keys or secrets.
Rootzk is the anonymization strand of the ROOTVIEL ecosystem — the module through which agents reason about and operate privacy under strict confinement. It is the privacy counterpart to Rootchain, which covers agentic security in Web3. Rootzk asks “how can an agent help with private, verifiable value flows without ever holding a secret?”; Rootchain asks “how do agents secure on-chain systems?”.
Status · A built product at protocol freeze 1.10.0, deployed and exercised end-to-end on a public testnet (Sepolia, with DEV proving artifacts) but not yet on mainnet. An internal security audit was performed and its findings fixed; production soundness still depends on a multi-party trusted-setup ceremony (not yet run) and an independent external audit (not yet done). Until both, treat strong claims as UNKNOWN.

The problem
Why private value flows are hard on a public chain
A public blockchain records every transfer — amounts, addresses, timing — permanently. Anyone can follow the money: who paid whom, how much, and when. For many legitimate needs (payroll, treasury moves, donations, or simply not broadcasting your net worth) that permanent public linkage is the problem.
Rootzk provides a shielded pool where funds can be held, transferred and withdrawn so the public ledger no longer links your operations to each other or to you — using zero-knowledge proofs, while you keep custody of your keys. An agent can help you operate it, without ever handling your secrets.
Who it is for
- People and teams who need legitimate on-chain privacy: payroll and treasury, donations, or trading that should not telegraph intent.
- Builders who want a pool with selective, provable compliance — prove a range, or membership in a curated association (“clean”) set — rather than all-or-nothing secrecy.
- Researchers studying how an agentic layer can assist private operations under strict confinement.
See it work
One operation, end to end
Follow one flow before the mechanics — a complete operation, from putting funds in to taking them out.
- 1
Deposit
publicYou move funds into the pool. A one-time 1% protocol entry fee is taken at deposit, so the note commits to the net amount — and that net is what the in-circuit conservation preserves from then on. A commitment that hides your secret becomes a leaf in a public Merkle tree, and from now on you blend into every deposit of the same shape.
- 2
Private transfer
privateInside the pool you pay someone: a 2→2 join-split spends your notes and creates a note for them plus your change. The ledger shows two nullifiers and two new commitments — not amounts, not who paid whom.
- 3
Prove, don’t reveal
privateOptionally you prove facts about your funds — an amount is within a range, or they belong to an approved association set (proof of innocence) — without disclosing the amount or your history.
- 4
Withdraw
publicLater you withdraw to a fresh address by proving you own an unspent leaf under the tree root and publishing a nullifier. The payout is public; the link back to your deposit is not.
The agent decides what to do but never holds the secret that would let it act: opening the note and building the proof happen in a separate prover environment. Verification and the promotion to a settled operation are done on-chain and by deterministic components — not by the agent.
How it works
Notes, commitments, nullifiers — and why each exists
The pool is built from a few interlocking ideas. Each one solves a specific problem; together they let you spend privately without a trusted operator.
A note is a private record of value you own. To put it on-chain without revealing it, you publish a commitment — a hash that binds your secret — which becomes a leaf in a shared Merkle tree. The tree lets you later prove your leaf is in it without pointing to which one.
Spending needs a way to stop you from spending the same note twice — but checking that must not reveal which note you used, or privacy collapses. That is the job of the nullifier: a value derived from the same secret in a different way, published when you spend. The contract records it; a second spend would publish the same nullifier and be rejected. The check is on the nullifier, never on the note — so nothing identifies the leaf you consumed.
The commitment binds secret and nullifier; the published nullifierHash is derived from them in a different order, so the value seen at withdrawal cannot be matched to the one seen at deposit. Unlinkability is real only against an on-chain observer, and only as large as the anonymity set — a separate, measured quantity. Illustrative; a valid proof attests to membership under its assumptions, never that the funds are legitimate.
A double-spend is blocked because the second attempt would publish the same nullifierHash, and the set already holds it — the check is on the nullifier, never on the proof or the leaf, so nothing identifies the note that was spent. Different-order hashing is one ingredient of unlinkability, not the whole of it: the overall guarantee also depends on the anonymity set and on behaviour, covered below.
Transfers and withdrawals
A private transfer moves value between people inside the pool. A 2→2 join-split spends two notes and creates two — one for the receiver, one as change — so value never leaves the shielded domain and no amount is revealed. A withdrawal instead exits to a public address, paying out a public amount while keeping the link to your deposit hidden.
Both rely on an in-circuit conservation rule: the value going in must equal the value coming out, exactly, so no proof can mint funds. This is what makes the pool safe to withdraw from.
A transfer never exits to the public balance, so no amount is revealed — unlike a withdrawal, which pays out a public amount. The sender proves the spend is valid and value-preserving; the receiver can later spend their note the same way.
Conservation (INV-02) is what makes the pool safe to withdraw from: no proof can create value. It was tested adversarially — mint-by-broken-conservation and negative-output attempts were rejected by the circuit.
The agentic layer
What the agent adds — and what it may never do
Rootzk’s own contribution is the layer that lets an agent operate this safely. Its value is in reasoning; its design is in strictly separating that reasoning from anything that can act.
Planning
Turns an intent (“send 1 privately”) into a concrete sequence of capabilities, choosing denominations and routes.
Evaluating alternatives
Compares ways to reach the goal and weighs their privacy and cost, rather than executing the first option.
Privacy analysis
Runs a proposed output through the privacy gate’s checks and prefers outputs that do not stand out.
Capability coordination
Sequences deposit, transfer, disclosure and withdrawal capabilities, and surfaces what is unavailable instead of hiding a gap.
Separation of duties
Rootzk draws a hard line between reasoning and anything that can move value or touch a secret. Five duties are deliberately held by different parties:
| Duty | Who does it | Note |
|---|---|---|
| Reasoning & planning | The agent | Decides what to do — over opaque references only. |
| Secret handling & proof generation | A prover environment, outside the agent | Opens notes and builds proofs; the agent never sees this material. |
| Authorization | Deterministic policy + on-chain gates | Decides whether an action may take effect; the agent cannot self-authorize. |
| Verification | On-chain Groth16 verifiers (deterministic) | Validate every proof; a reasoner cannot vouch for one. |
| Promotion to a settled operation | The chain | Only an on-chain receipt makes an operation real. |
So the agent is forbidden to see a secret, sign, validate cryptography or promote a result to settled — while proving, verifying and promotion are deliberately done by other parties.
What this boundary is, and is not (threat model)
Be precise about how strong this boundary is. The agent-side confinement is enforced by types, modules and tests — a strong boundary against accidental leakage and against a reasoner (including an LLM) steered by its inputs. It is NOT a sandbox against arbitrary native code in the same process; that is not the threat model. The guarantees that hold against a malicious party come from the separate prover environment and from deterministic on-chain verification — not from in-process isolation.
The separation is enforced by typing, not by convention: an agent cannot import the membrane that restricts it, and the context accepts only agent-safe observations. The four prohibitions — no secret, no sign, no crypto-validate, no promote — are each asserted by a test.
Privacy gate
Attacking the output before it is broadcast
Before anything is broadcast, a privacy gate attacks the proposed output with its own de-anonymiser and refuses outputs it can trace.
Inputs
- The proposed public output — nullifiers, commitments, amounts, timing
- The current pool state and anonymity set
- A battery of de-anonymisation heuristics
Checks it runs
- Subset-sum and amount matching
- Temporal correlation and intersection
- Gas and arity fingerprints
- Input-selection and controller-history patterns
Decision
If the output is uniquely traceable or below the anonymity floor, it is rejected; otherwise it is released — with a randomised hold so the gate’s own timing is not a usable signal.
Its limits
The gate is a conservative filter, not a proof of privacy: it can reject what its heuristics catch, but it cannot certify that an accepted output is private. That still depends on the anonymity set and on off-chain behaviour (network, timing, host), which are outside the protocol.
The gate is a conservative filter, not a guarantee: it rejects outputs its own attacker can de-anonymise, but it cannot prove an accepted output is private — that still depends on the anonymity-set size and on what happens off-chain (network, timing, host).
Status & limits
Where the product stands
A built product at protocol freeze 1.10.0, deployed and exercised end-to-end on a public testnet (Sepolia, with DEV proving artifacts) but not yet on mainnet. An internal security audit was performed and its findings fixed; production soundness still depends on a multi-party trusted-setup ceremony (not yet run) and an independent external audit (not yet done). Until both, treat strong claims as UNKNOWN.
Exercised on testnet
The pool is deployed on the Sepolia testnet (DEV artifacts) and run end-to-end: a real deposit → a Groth16 proof verified on-chain → a withdrawal paying the exact amounts → a rejected double-spend (“Nullifier used”). This shows the implementation works; it is not a mainnet-readiness claim — the DEV proving keys are forgeable until the ceremony.
Internal audit — done
An internal security audit was performed and its findings fixed at freeze 1.10.0: the controlled-access gate now covers every value path including join-split and shielded (H-01); Merkle path indices are boolean-constrained in all circuits (M-01); the keystore signer both deploy scripts require is restored (L-04); and guardian-set changes are timelocked once governance is active (M-04). An independent external audit is still pending — production soundness remains UNKNOWN until it, and the ceremony, are done.
Why the trusted setup matters
The zero-knowledge proofs are only sound if the one-time setup that generated the proving keys was run honestly — otherwise whoever holds its secret could forge proofs. A production ceremony, with independent contributors, is the primary thing standing between this product and mainnet. Its full procedure is operational detail, below.
Why an external audit matters
Circuits and contracts fail in subtle ways that reading your own code does not catch. An internal audit was done and its findings fixed; an independent external audit is still a precondition for trusting the system with real value, and it has not yet been done — so production soundness is UNKNOWN.
Trust assumptions
- An honest ceremony (at least one honest contributor)
- An honest owner — until M-of-N governance is activated
- One honest RPC for receipts
- A relayer and an indexer
- circomlib / snarkjs / ethers behaving as specified
Out of the model
- Traffic analysis
- Host compromise
- Legal coercion
- Deep chain reorganisations
- A quantum adversary — BN254 and secp256k1 are not post-quantum
Known gaps
- Proof-of-innocence membership is available but NOT required on the live withdrawal paths (M-03); if PoI gating is a desired invariant, it must be explicitly wired.
- By default the owner can upgrade the Diamond instantly; the non-custodial property is conditional on activating M-of-N, owner-managed governance and the 48h timelock. Guardian-set changes are themselves timelocked once governance is active (M-04), and on the testnet pool 2-of-3 governance is activated — the instant owner cut is disabled.
- The two mainnet blockers remain: the production trusted-setup ceremony has not been run, and an independent external audit (distinct from the internal audit already done) has not been performed — so production soundness is UNKNOWN until both are complete.
- Reproducibility note: the circuit library is tag-pinned rather than a committed submodule (M-05).
Relation to prior work
Rootzk integrates well-known ideas — Tornado-style pools, join-split, proof-of-innocence, stealth addresses. Its own contribution is the agentic layer: the confinement boundary and the privacy gate. It is not equivalent to any deployed system; a feature-level comparison with RAILGUN, with honest classifications, is in the technical reference below.
Reading the guarantee honestly: a valid proof attests to a statement — conservation, membership, a range, an opening — under its circuit and the pending ceremony’s assumptions. It is never evidence that funds are legitimate, that a withdrawal was willing, or that the privacy a user wanted was achieved. PROVEN, NOT PROVEN and UNKNOWN are kept distinct.
Technical reference
Implementation detail
The specifics an implementer or auditor needs — kept out of the explanation above. Expand what you need.
Build, toolchain & architecture layers
- Protocol freeze
- 1.10.0
- Toolchain
- circom 2.1.6 / 2.2.3 · snarkjs 0.7.6 · circomlib 2.0.5 · solc 0.8.20 (via_ir) · Groth16 / BN254
Reasoning layer
Python · ≈33k LOC · standard library only
A read-only-by-default architecture with no third-party runtime dependencies, whose job is to let agents reason about privacy and verification while being structurally unable to see a secret, sign, validate cryptography, or promote a result to a verified fact. The audit catalogue reports it as the most mature and best-verified layer, with 769 secret-confinement tests passing. Those four prohibitions are each asserted by tests, not left to convention.
On-chain protocol
Solidity + circom · EIP-2535 Diamond (11 facets, ≈7.3k LOC) · 12 Groth16 circuits (≈2.2k LOC)
A Tornado-style note pool with amount-carrying notes, a 2→2 join-split, and a persistent shielded domain. Commitments and nullifiers use Poseidon, with an on-chain nullifier set and a Poseidon Merkle tree. Fully implemented at protocol freeze 1.10.0 and exercised end-to-end on the Sepolia testnet (DEV artifacts); not yet on mainnet, with the production trusted-setup ceremony and an independent external audit still pending.
Ceremony / release / freeze tooling
Reproducible freeze fingerprint · independent ceremony verification · release gates
Tooling to govern a future deployment: a reproducible freeze fingerprint, independent verification of the trusted-setup ceremony, and release gates. The machinery exists; a production ceremony has not been run.
Note model — field, hashes and parameters
Let F be the BN254 scalar field (p = 21888242871839275222246405745257275088548364400416034343698204186575808495617). All hashing uses Poseidon (circomlib) — never a bespoke implementation.
- commitment
- Poseidon(secret, nullifier)
- leaf
- Poseidon(commitment, amount) — the leaf carries the amount
- nullifierHash
- Poseidon(nullifier, secret) — the reversed order separates spending from viewing
- secret, nullifier ∈ F are private; amount < 2^128 (constrained in-circuit).
- The Merkle tree is Poseidon, depth 20, zero-leaf keccak256("tornado") mod p, with a rolling history of 100 roots.
- The persistent shielded pool uses three domain separators (DOMAIN_NOTE, DOMAIN_NULL, DOMAIN_OWNER): ownerPk = Poseidon(DOMAIN_OWNER, spendKey); commitment = Poseidon(DOMAIN_NOTE, asset, value, ownerPk, randomness, rho); nullifier = Poseidon(DOMAIN_NULL, rho, spendKey) — so the view key never derives the spend key.
On-chain layer — facets, invariants & governance
| Facet | Functions | Key property | Access control |
|---|---|---|---|
| AmountFacet | depositAmount, batchDepositAmount, withdrawAmount, emergencyWithdrawAmount, *Authorized | Amount-carrying note; conservation withdraw + fee ≤ amount; derived change | nonReentrant, authorization gate, EIP-712 grant variants |
| JoinSplitFacet | joinSplit (join_split_v4 verifier, 11 signals) | 2→2, exact conservation; recipient + relayer bound; msg.sender == relayer | nonReentrant; authorization gate (fixed, finding H-01) |
| ShieldedFacet | shieldedJoinSplit (V2 verifier, 14 signals) | Persistent pool; metadataHash + PROTOCOL_VERSION bound; fee subtracted before net | nonReentrant; authorization gate (fixed, H-01) |
| CoreFacet | shieldedTransferV2 (5 signals) | Value-preserving re-key (newLeaf = Poseidon(newCommitment, amount), same amount) | nonReentrant, authorization gate |
| ComplianceFacet | verifyDisclosureV2 (5 signals), updateAssociationSet | Selective range disclosure; proof of innocence | aspOperator role for the association set |
An EIP-2535 Diamond proxy with AppStorage in slot 0 and LibDiamond in a namespaced slot; the fallback delegatecalls the facet selected by msg.sig.
Legacy deposits/withdrawals, an on-chain stealth spend, batch withdraw, migrateOut, token paths, a decoy-pool gas sponsor, and a removed zkML mock verifier are present only as stubs that revert — withdrawn by design and documented finding by finding: migrate the intent, not the defect.
Invariants verified on-chain
- Double-spend prevented by a set of nullifierHashes + marking under nonReentrant, with checks-effects-interactions order.
- poolId = address(this) and chainId = block.chainid are supplied by the contract (anti-replay across pools and chains).
- Solvency checked excluding amounts owed to the treasury.
All 11 verifiers are real Groth16 (precompiles 0x06/0x07/0x08, checkField over each public signal; no “return true” shortcut).
Governance & upgrade
- DiamondCutFacet
- owner-only, instantaneous diamondCut; disabled once governanceThreshold > 0.
- TimelockCutFacet
- proposal → 48h timelock → M-of-N → execution, with a payload-hash re-check.
- VerifierGovernanceFacet
- the ONLY path to install verifiers: register (captures codehash) → propose → 48h timelock → M-of-N → EXTCODEHASH re-check at execution → install.
- GovernanceFacet
- guardian set and M-of-N threshold; owner-managed (a documented trust boundary).
Zero-knowledge circuits & soundness properties
| Circuit | Constraints | Property | Verifier |
|---|---|---|---|
| withdraw_amount | 13,516 | Withdrawal with change; conservation LessEqThan(130); Num2Bits(128) per value | Groth16VerifierAmount |
| join_split_v1 / v2 | 27,869 / 54,365 | 2→2 and 4→4; exact conservation | (baseline) |
| join_split_v4 | 27,960 | v3 + recipient — closes settlement front-running | JoinSplitVerifierV4 |
| shielded_joinsplit_v1 / v2 | 29,046 / 29,277 | Two-sided conservation; metadataHash + protocolVersion bound (v2) | ShieldedJoinSplitVerifierV2 |
| shielded_transfer | 13,118 | Re-key; same amount ⇒ value-preserving | ShieldedTransferVerifier |
| association_set_membership_v2 | 11,434 | Proof of innocence: commitment in a clean set, bound to the same nullifierHash | AssociationVerifierV2 |
| selective_disclosure_v2 | 12,353 | min ≤ amount ≤ max without revealing amount | SelectiveDisclosureVerifierV2 |
- Conservation / no-mint (INV-02)
- sumIn === sumOut + publicAmount + relayerFee (exact); each value Num2Bits(128). Adversarially tested: mint-by-broken-conservation and negative-output (−v mod p) attempts are rejected by the circuit.
- Context binding (INV-09)
- poolId/chainId (and recipient/relayer) are public signals supplied by the contract; a proof is valid only for the exact signal vector it was generated with — a mutated signal invalidates it (verified).
- Boolean Merkle-index constraint (M-01, fixed)
- Each pathIndices[i] is constrained to {0,1} (isRight·(isRight−1) === 0) across all 12 circuits after the fix; before, 4 circuits only bounded the weighted sum. A membership forgery with a non-boolean index was experimentally refuted.
Off-chain services
Relayer
policy and execution are separated; the private key lives in a Node subprocess environment, never as a Python value; a NonceManager serializes nonce assignment (fixing a legacy nonce race); only an on-chain receipt promotes an operation.
Indexer
reorg-aware, anchored to block hash; LANDED_DESPITE_FAILURE reconciliation; reconstructible from the chain.
Wallet
the seed/phrase never enters Python (scrypt→HKDF derivation and proving happen in a Node bridge); artifacts verified by hash; deniability via two independent notes with independent salts.
API
capability-shaped endpoints; FORBIDDEN_FIELDS rejects secret material and infrastructure identifiers at parse; a 64 KiB limit; an anti-replay window.
Trusted setup & ceremony — operational detail
Status: NOT performed
NOT performed. All artifacts are DEV; REAL proving is rejected by code unless trust_level == PRODUCTION_CANDIDATE. This is the primary cryptographic blocker.
- Policy: a minimum of 3 independent contributors, 5+ preferred — an operational floor, not a cryptographic threshold (an updatable ceremony is secure as long as one honest contributor destroys their contribution).
- freeze fingerprint: SHA-256 over contract + circuit sources, selectors, storage layout, manifest and toolchain, so audited source = ceremony source = deployed source.
- Independent transcript verification (an output_hash → input_hash chain, a final beacon, frozen_circuit_match); a TEST ceremony promotes nothing.
Conditional soundness (informal)
Conditional soundness (informal). Groth16 soundness (INV-03) holds if and only if (a) the circuits express the correct properties, (b) the verifiers are real (verified), and (c) an honest ceremony exists (pending). Without (c), proofs are forgeable by whoever holds the toxic waste.
Feature-level comparison with RAILGUN
A row marked “implemented” means the feature is built — not that it equals RAILGUN’s in production. RAILGUN is deployed, audited and multi-token; Rootzk is not yet on mainnet, with no external audit or production ceremony. Feature presence is not parity. Read the classification, not the ticks — and this is a comparison, never a claim of equivalence.
| Dimension | RAILGUN (reference) | Rootzk | Classification |
|---|---|---|---|
| Shielding / unshielding | Yes, multi-token | Yes — ETH, amount-carrying note | Implemented — not yet on mainnet |
| Private transfers | Yes | ShieldedFacet + shieldedTransferV2 | Implemented |
| Private DeFi | Yes | Absent | Out of scope |
| ZK circuits | Audited Groth16 + ceremony | Real Groth16 — no ceremony, no external audit | Partial (blocking) |
| Commitments / nullifiers | Yes | Poseidon; on-chain set | Implemented & verified |
| Merkle trees | Yes | Poseidon, depth 20, 100 roots | Implemented |
| Broadcasters | Yes | Real relayer, key in a subprocess | Implemented |
| Viewing keys / disclosure | Yes | selective_disclosure_v2, stealth view-key | Implemented |
| Private PoI | Yes | association_set_membership_v2 (not enforced on live withdrawal) | Partial |
| Non-custodial | Yes | Keys never in Python — but owner has instant upgrade by default | Partial / conditional |
| Governance | Multisig / timelock | Diamond + 48h timelock + M-of-N; 2-of-3 active on testnet (M-04) | Partial |
| Security audits | Multiple external | Internal done; external pending | Partial |
The material difference from RAILGUN is less one of design than of maturity — ceremony, external audit and deployment. Rootzk’s most original engineering contribution is the agentic membrane with typed secret confinement and a privacy gate with a built-in attacker; its cryptographic novelty is low (a careful integration of known ideas: Tornado-style pools, join-split, proof-of-innocence, stealth addresses per ERC-5564).
Background — learn the concepts
Rootzk is built on well-studied cryptography. These Academy lessons explain the ideas from first principles — the honest way to understand what the proofs do and do not guarantee.
