Bringing Zero-Knowledge to 0G





0gzk is a 0G-native toolkit for shipping zero-knowledge proofs on top of 0G.
You write a Circom circuit, package the proving artifacts into a circuit bundle, and store that bundle on 0G Storage as content-addressed data. From there, anyone with the rootHash can pull the same bytes back and run the prover on their own machine or in their browser.
For a non-technical explanation, refer to the pitch deck: https://canva.link/disj812xktm4xnx
0gzk turns a zero-knowledge circuit into a named, content-addressed primitive on the 0G stack. A circuit author compiles a Circom circuit, runs the Groth16 setup, and publishes a self-describing circuit bundle (circuit.wasm + circuit_final.zkey + verification_key.json + verifier.sol + metadata.json) as a single tar.gz to 0G Storage. The returned 32-byte rootHash is committed on-chain in the CircuitRegistry contract on 0G Chain under a human-readable name@version, alongside the canonical vkeyHash and the deployed Groth16 verifier address.
Anyone, whether running a Node script, a browser tab, or the CLI, can then resolve name@version against the registry, fetch the bundle from 0G Storage, verify that what arrived matches what was committed, and run snarkjs.groth16.fullProve locally. The witness (the secret input) never crosses a network boundary. The resulting proof can be verified locally against the same key, or on-chain by calling the registered verifier.sol.
We ship three surfaces, all reading the same registry:
Surface | Package | Purpose |
|---|---|---|
Web prover | Pick a circuit from the live registry, prove in-browser, witness stays in the tab | |
CLI |
|
|
SDK |
| Isomorphic prover ( |
Building a Groth16 ZK feature today means making three trust decisions at deploy time. Nobody writes them down.
Which bytes? Circuit artifacts (WASM and zkey files) are often megabytes. They get dumped on a CDN, on IPFS, or inside the app bundle. The on-chain verifier doesn't authenticate any of those channels.
Which key? The verifying key in your bundle must match the one compiled into verifier.sol. They have to agree byte-for-byte. Otherwise every proof silently fails. There's no standard way to assert this on-chain.
Where does the witness go? "ZK as a service" providers solve (1) and (2) by hosting the prover for you. That means your secret inputs sit in plaintext on their server. It kills the point of zero knowledge.
0gzk closes all three gaps in one content-addressed protocol.
AI agents are about to need ZK badly.
An agent might run inference on private user data. It might sign an action on someone's behalf. It might claim it executed a specific model. In each case, it needs to prove what it did without leaking the inputs.
Today there's no clean path. The model weights live somewhere unverifiable. The verifier doesn't know which circuit produced the proof. And if you outsource proving, the user's private prompt ends up on a third party's GPU.
0G fixes this because it puts the pieces under one roof.
Storage handles circuit and model distribution honestly. Artifacts are too big for L1 calldata. They're too sensitive for a single CDN. 0G Storage is decentralized and content-addressed. Any client (or any agent) can recompute the merkle root from a rootHash and detect tampering. No indexer trust required.
Chain handles the registry and verifier binding. A 0G Storage rootHash is just a bytes32 to any 0G Chain contract. A registry can bind a name directly to its bytes. A snarkjs-generated Solidity verifier runs as-is. No transpilation. No recursion bridge. No second proof system.
One trust domain. Storage durability, registry immutability, and on-chain verification all come from the same operator set. The same consensus. The same explorer.
For AI agents, this means something concrete. A user can run inference locally. They can prove the result against a registered circuit. An on-chain contract can verify it. The model weights, the prompt, and the witness never leave their device.
Other ecosystems would have to stitch this together from three separately governed services. On 0G these are primitives. 0gzk is what falls out when you take that seriously.

0gzk is 0G-native by construction, and both data planes are required:
0G Storage. Every circuit bundle is uploaded as a single tar.gz to 0G Storage and addressed by its 32-byte merkle rootHash. We use @0gfoundation/0g-ts-sdk for chunked uploads, finalization polling, and indexer-fronted downloads. The rootHash is the same primitive on both planes: it stores directly as bytes32 in the registry, with no CID-string parsing and no separate availability oracle.
0G Chain (EVM). CircuitRegistry.sol is deployed on 0G Galileo testnet (chain id 16602) and 0G mainnet (chain id 16661). It maps name@version → (rootHash, vkeyHash, verifier, publisher, publishedAt, metadataURI). Per-circuit verifier.sol contracts (auto-generated by snarkjs) deploy alongside, on the same chain.
Crucially, bundle storage and registry record live under the same consensus. A relying party that trusts 0G's chain to serve the registry record does not have to additionally trust a separately-governed pinning service to still be serving the bundle. Bundle availability and registry correctness fail or recover together.
There are three actors in the system:
Circuit author: Writes and publishes the circuit
Prover: An end user who wants to prove something privately
Relying party: The app or contract that wants to act on the proof.
0gzk gives each of them exactly one job and uses the 0G stack to make sure they don't have to trust each other.
A circuit "bundle" is the entire package needed to prove and verify one circuit, packed into a single tar.gz. It contains five files: circuit.wasm (the witness generator from circom), circuit_final.zkey (the Groth16 proving key), verification_key.json (the small key used to check proofs), verifier.sol (the Solidity verifier ready to deploy on 0G Chain), and metadata.json (a typed schema declaring the circuit's name, version, and every input with its type and visibility).
circuit_bundle/
├── circuit.wasm // WASM witness generator from circom
├── circuit_final.zkey // Groth16 proving key
├── verification_key.json // snarkjs export, canonicalized
├── verifier.sol // Solidity Groth16 verifier (optional)
└── metadata.json // typed input/output schema, semver, curveThe bundle is self-describing on purpose: any client with just the 32-byte hash can fetch it and have everything needed to prove, verify locally, and verify on-chain. There's no separate manifest, no app-side glue. The SDK uses metadata.json to validate inputs before snarkjs runs and to auto-generate the input form on 0gzk.com.

0gzk publish ./circuit_bundle --register does four things:
Pack the five files into a deterministic tar.gz.
Upload to 0G Storage. The storage layer chunks the file, commits it under a binary merkle tree, and returns the 32-byte rootHash. That hash is the bundle's identity.
Deploy the bundle's verifier.sol as a normal contract on 0G Chain.
Register the version on-chain by calling CircuitRegistry.publishVersion(name, version, rootHash, vkeyHash, verifier, metadataURI). From this transaction onward the mapping is immutable.
The first time an author publishes under a new name, the CLI also calls claimName(name) once. Names are first-come-first-served and owned by an EOA.
For each name@version the registry stores six fields. The two that carry the security weight are rootHash (the 0G Storage handle to the bundle) and vkeyHash (the keccak256 of the bundle's canonical verifying key). The other four are the deployed verifier address, the publisher EOA, the publish timestamp, and a human-readable metadata URI.
The trick is the vkeyHash. When a client downloads the bundle from 0G Storage, it recomputes the canonical hash of the verifying key it just unpacked and compares it to what the registry committed. If a malicious indexer ever returns different bytes, the check fails and the bundle is rejected. The bundle and the registry record can't drift apart.

Proving by name from any of the three surfaces (CLI, SDK, web) follows the same path: read the registry record, download the bundle from 0G Storage, check the vkeyHash matches, then run snarkjs.groth16.fullProve in the local process. The witness lives only in that process's memory. What leaves the device is a ~200-byte Groth16 proof and the public signals the circuit was designed to expose. Verification happens off-chain against the same verifying key, or on-chain by calling the registered verifier contract. Either way, the answer is the same.
Then read our documentation at docs.0gzk.com. There are really helpful examples in the documentation that use this GitHub repository as a reference.
If you are interested but don't want to deal with code right now, go to https://0gzk.com/prove?name=age_verification%400.1.0, enter your birth year, current year, and a minimum age to check whether you are older than the threshold or not. The circuit checks if your age is older than the threshold without leaking your birth year. The whole thing happens in your browser.
Founder & Builder: Ozan Andaç
https://www.linkedin.com/in/ozanandac/
Website: 0gzk.com
Github: github.com/0gzk/core
Documentation: docs.0gzk.com
Examples: github.com/0gzk/examples
Pitch Deck: canva.link/disj812xktm4xnx
0gzl CLI: npmjs.com/package/@0gzk/cli
0gzk SDK: npmjs.com/package/@0gzk/sdk
Contract: explorer.0g.ai/mainnet/blockchain/accounts/0xce9f0df51abec7b8cd751067c6d8d3db5e2be64d/transactions
Built an MVP of the whole project during the hackathon
Self-funded