|
| 1 | +--- |
| 2 | +title: "Zero-knowledge proofs on TON" |
| 3 | +sidebarTitle: "Zero-knowledge proofs" |
| 4 | +--- |
| 5 | + |
| 6 | +import { Aside } from '/snippets/aside.jsx'; |
| 7 | + |
| 8 | +This guide shows how to create, compile, and test a simple [Circom](https://docs.circom.io/) scheme and verify a [ZK-proof](https://en.wikipedia.org/wiki/Zero-knowledge_proof) using the [`zk-SNARK`](https://en.wikipedia.org/wiki/Non-interactive_zero-knowledge_proof) [`Groth16`](https://eprint.iacr.org/2016/260.pdf) protocol. |
| 9 | + |
| 10 | +<Aside> |
| 11 | + This guide is also applicable to circuits written in the [Noname](https://github.com/zksecurity/noname) language, since the [`export-ton-verifier`](https://github.com/mysteryon88/export-ton-verifier) library integrates with `snarkjs`, which in turn integrates with the Noname language. |
| 12 | + |
| 13 | + Other examples can be found [here](https://github.com/zk-examples/zk-ton-examples/). |
| 14 | +</Aside> |
| 15 | + |
| 16 | +## Prerequisites |
| 17 | + |
| 18 | +- [Node.js](https://nodejs.org) 18 or a later LTS. |
| 19 | +- [`circom`](https://docs.circom.io/getting-started/installation/#installing-circom) |
| 20 | +- [`snarkjs`](https://docs.circom.io/getting-started/installation/#installing-snarkjs) |
| 21 | +- [Blueprint](/contract-dev/blueprint/overview) |
| 22 | + |
| 23 | +## Project setup |
| 24 | + |
| 25 | +1. Create a new project using Blueprint: |
| 26 | + |
| 27 | + ```bash |
| 28 | + npm create ton@latest ZkSimple |
| 29 | + cd ZkSimple |
| 30 | + ``` |
| 31 | +1. Install libraries for working with ZK-proofs: |
| 32 | + |
| 33 | + ```bash |
| 34 | + npm install snarkjs @types/snarkjs |
| 35 | + ``` |
| 36 | +1. Install the verifier export utility for TON: |
| 37 | + |
| 38 | + ```bash |
| 39 | + npm install export-ton-verifier@latest |
| 40 | + ``` |
| 41 | + |
| 42 | +This utility exports verifier contracts for FunC, Tolk, and Tact. |
| 43 | + |
| 44 | +## Create the Circom circuit |
| 45 | + |
| 46 | +Create the directory `circuits/Multiplier` and the file `Multiplier.circom`: |
| 47 | + |
| 48 | +```bash |
| 49 | +mkdir -p circuits/Multiplier |
| 50 | +cd circuits/Multiplier |
| 51 | +``` |
| 52 | + |
| 53 | +```circom |
| 54 | +pragma circom 2.2.2; |
| 55 | +
|
| 56 | +template Multiplier() { |
| 57 | + signal input a; |
| 58 | + signal input b; |
| 59 | +
|
| 60 | + signal output c; |
| 61 | +
|
| 62 | + c <== a*b; |
| 63 | +} |
| 64 | +
|
| 65 | +component main = Multiplier(); |
| 66 | +``` |
| 67 | + |
| 68 | +This circuit proves knowledge of two numbers `a` and `b`, whose product is equal to the public output `c`, without revealing `a` and `b` themselves. |
| 69 | + |
| 70 | +### Compile |
| 71 | + |
| 72 | +Run in `circuits/Multiplier`: |
| 73 | + |
| 74 | +```bash |
| 75 | +circom Multiplier.circom --r1cs --wasm --sym --prime bls12381 |
| 76 | +``` |
| 77 | + |
| 78 | +After compilation, the following files will appear: |
| 79 | + |
| 80 | +- `Multiplier.r1cs` — circuit constraints (R1CS) |
| 81 | +- `Multiplier.sym` — symbolic signal map |
| 82 | +- `Multiplier.wasm` — artifact for generating proof |
| 83 | + |
| 84 | +Check constraints: |
| 85 | + |
| 86 | +```bash |
| 87 | +snarkjs r1cs info Multiplier.r1cs |
| 88 | +``` |
| 89 | + |
| 90 | +Output example: |
| 91 | + |
| 92 | +``` |
| 93 | +[INFO] snarkJS: Curve: bls12-381 |
| 94 | +[INFO] snarkJS: # of Wires: 4 |
| 95 | +[INFO] snarkJS: # of Constraints: 1 |
| 96 | +[INFO] snarkJS: # of Private Inputs: 2 |
| 97 | +[INFO] snarkJS: # of Public Inputs: 0 |
| 98 | +[INFO] snarkJS: # of Outputs: 1 |
| 99 | +``` |
| 100 | + |
| 101 | +<Aside> |
| 102 | + `snarkjs` supports the [`alt_bn128`](https://eips.ethereum.org/EIPS/eip-196) and [`bls12-381`](https://electriccoin.co/blog/new-snark-curve/) curves. Ethereum uses `alt_bn128`, but `bls12-381` is used for TON, so it is the one chosen in this guide. |
| 103 | +</Aside> |
| 104 | + |
| 105 | +## Trusted setup (`Groth16`) |
| 106 | + |
| 107 | +The **trusted setup** is a one-time ceremony that generates the proving and verification keys for a circuit. It's called "trusted" because if the setup parameters are compromised, proofs could be forged. For production use, participate in a multi-party trusted setup ceremony. For local development and testing, a simplified single-party setup is sufficient. For local tests, perform a simplified trusted setup ceremony. |
| 108 | + |
| 109 | +The "power of tau" parameter (`10`) has to be chosen |
| 110 | + |
| 111 | +- as low as possible, because it affects execution time; |
| 112 | +- high enough, because the more constraints in the scheme, the higher the parameter required. |
| 113 | + |
| 114 | +```bash |
| 115 | +# first phase |
| 116 | +snarkjs powersoftau new bls12-381 10 pot10_0000.ptau -v |
| 117 | +snarkjs powersoftau contribute pot10_0000.ptau pot10_0001.ptau --name="First contribution" -v -e="some random text" |
| 118 | + |
| 119 | +# second phase (depends on the compiled scheme) |
| 120 | +snarkjs powersoftau prepare phase2 pot10_0001.ptau pot10_final.ptau -v |
| 121 | +snarkjs groth16 setup Multiplier.r1cs pot10_final.ptau Multiplier_0000.zkey |
| 122 | +snarkjs zkey contribute Multiplier_0000.zkey Multiplier_final.zkey --name="1st Contributor" -v -e="some random text" |
| 123 | + |
| 124 | +# export verification key |
| 125 | +snarkjs zkey export verificationkey Multiplier_final.zkey verification_key.json |
| 126 | +``` |
| 127 | + |
| 128 | +Clear up unnecessary artifacts: |
| 129 | + |
| 130 | +```sh |
| 131 | +rm pot10_0000.ptau pot10_0001.ptau pot10_final.ptau Multiplier_0000.zkey |
| 132 | +``` |
| 133 | + |
| 134 | +## Export the verifier contract |
| 135 | + |
| 136 | +```bash |
| 137 | +# export FunC contract (default) |
| 138 | +npx export-ton-verifier ./circuits/Multiplier/Multiplier_final.zkey ./contracts/verifier_multiplier.fc |
| 139 | + |
| 140 | +# export Tolk contract |
| 141 | +npx export-ton-verifier ./circuits/Multiplier/Multiplier_final.zkey ./contracts/verifier_multiplier.tolk --tolk |
| 142 | + |
| 143 | +# export Tact contract |
| 144 | +npx export-ton-verifier ./circuits/Multiplier/Multiplier_final.zkey ./contracts/verifier_multiplier.tact --tact |
| 145 | +``` |
| 146 | + |
| 147 | +For FunC and Tolk, wrappers must be generated manually: |
| 148 | + |
| 149 | +```bash |
| 150 | +npx export-ton-verifier import-wrapper ./wrappers/Verifier.ts --force |
| 151 | +``` |
| 152 | + |
| 153 | +This command generates a TypeScript wrapper file that provides type-safe methods to interact with the verifier contract. |
| 154 | + |
| 155 | +## Testing and verification |
| 156 | + |
| 157 | +In `tests/ZkSimple.spec.ts`: |
| 158 | + |
| 159 | +```ts |
| 160 | +import * as snarkjs from 'snarkjs'; |
| 161 | +import path from 'path'; |
| 162 | +import { dictFromInputList, groth16CompressProof } from 'export-ton-verifier'; |
| 163 | + |
| 164 | +// for Tact (After running `npx blueprint build --all`) |
| 165 | +import { Verifier } from '../build/Verifier_tact/tact_Verifier'; |
| 166 | + |
| 167 | +// for FunC and Tolk |
| 168 | +import { Verifier } from '../wrappers/Verifier'; |
| 169 | +``` |
| 170 | + |
| 171 | +Local verification: |
| 172 | + |
| 173 | +```ts |
| 174 | +const wasmPath = path.join(__dirname, '../circuits/Multiplier', 'Multiplier.wasm'); |
| 175 | +const zkeyPath = path.join(__dirname, '../circuits/Multiplier', 'Multiplier_final.zkey'); |
| 176 | +const verificationKey = require('../circuits/Multiplier/verification_key.json'); |
| 177 | + |
| 178 | +const input = { a: '342', b: '1245' }; |
| 179 | + |
| 180 | +const { proof, publicSignals } = await snarkjs.groth16.fullProve(input, wasmPath, zkeyPath); |
| 181 | +const okLocal = await snarkjs.groth16.verify(verificationKey, publicSignals, proof); |
| 182 | +``` |
| 183 | + |
| 184 | +On-chain verification: |
| 185 | + |
| 186 | +```ts |
| 187 | +const { pi_a, pi_b, pi_c, pubInputs } = await groth16CompressProof(proof, publicSignals); |
| 188 | + |
| 189 | +// Quick check via get-method: verifies the proof locally without changing blockchain state. |
| 190 | +expect(await verifier.getVerify({ pi_a, pi_b, pi_c, pubInputs })).toBe(true); |
| 191 | + |
| 192 | +// Send the proof to the contract in a message |
| 193 | +// The contract will run verification; handling the result/flow is up to the developer using this template. |
| 194 | +await verifier.sendVerify(deployer.getSender(), { pi_a, pi_b, pi_c, pubInputs, value: toNano('0.15') }); |
| 195 | +``` |
| 196 | + |
| 197 | +<Aside type="tip"> |
| 198 | + Build the contracts before running tests. For Tact contracts, run `npx blueprint build --all` first. For FunC/Tolk, ensure the wrappers are generated. |
| 199 | +</Aside> |
| 200 | + |
| 201 | +## Other Languages |
| 202 | + |
| 203 | +This tutorial follows the path **Circom → `snarkjs` → `export-ton-verifier` → TON**. |
| 204 | +The same workflow applies to other stacks — the key requirement is to obtain a `proof` and a `verification key` in `snarkjs` format. |
| 205 | + |
| 206 | +In the example repository — [`zk-ton-examples`](https://github.com/zk-examples/zk-ton-examples/) — there are already templates for `noname`, `gnark`, and `arkworks`: proofs can be generated in any of these stacks, then converted into `snarkjs` format and verified both locally and on-chain in the same way. |
| 207 | + |
| 208 | +<Aside> |
| 209 | + Two utilities are available that help convert proofs and verification keys into a format compatible with `snarkjs`: |
| 210 | + |
| 211 | + - [`ark-snarkjs`](https://github.com/mysteryon88/ark-snarkjs): use for exporting from `arkworks` |
| 212 | + - [`gnark-to-snarkjs`](https://github.com/mysteryon88/gnark-to-snarkjs): use for exporting from `gnark` |
| 213 | +</Aside> |
| 214 | + |
| 215 | +The idea is always the same: generate `proof.json` and `verification_key.json` in `snarkjs` format, then use `export-ton-verifier` and perform verification in TON. |
| 216 | + |
| 217 | +### Arkworks (Rust) |
| 218 | + |
| 219 | +Use the `arkworks` library to generate the proof and verification key, then convert them into `snarkjs` format with `ark-snarkjs`. |
| 220 | + |
| 221 | +1. Set up an Arkworks project: |
| 222 | + |
| 223 | +```sh |
| 224 | +cargo init |
| 225 | +cargo add ark-bls12-381 ark-ff ark-groth16 ark-r1cs-std ark-relations ark-snark ark-snarkjs ark-std rand@0.8.5 |
| 226 | +``` |
| 227 | + |
| 228 | +<Aside> |
| 229 | + The packages listed above are the core dependencies needed for most `arkworks` circuits. Depending on the specific circuit implementation, additional packages may be required. |
| 230 | +</Aside> |
| 231 | + |
| 232 | +2. Write the circuit in Rust. Implement the circuit logic using `arkworks` primitives, similar to how a Circom circuit would be written. Learn how to write constraints in `arkworks` by following the [Arkworks R1CS tutorial](https://github.com/arkworks-rs/r1cs-tutorial). A working example of a simple multiplication circuit can be found in the [`zk-ton-examples` repository](https://github.com/zk-examples/zk-ton-examples/tree/main/circuits/Arkworks/MulCircuit). |
| 233 | + |
| 234 | +2. Compile, generate proof, and perform trusted setup following the same workflow as in the Circom section above. |
| 235 | + |
| 236 | +2. Export the proof and verification key to JSON using `ark-snarkjs`: |
| 237 | + |
| 238 | +```rust |
| 239 | +use ark_snarkjs::{export_proof, export_vk}; |
| 240 | +use ark_bls12_381::{Bls12_381, Fr}; |
| 241 | + |
| 242 | +let _ = export_proof::<Bls12_381, _>(&proof, &public_inputs, "json/proof.json"); |
| 243 | +let _ = export_vk::<Bls12_381, _>( |
| 244 | + ¶ms.vk, |
| 245 | + public_inputs.len(), |
| 246 | + "json/verification_key.json", |
| 247 | +); |
| 248 | +``` |
| 249 | + |
| 250 | +The directory and files will be created automatically. |
| 251 | + |
| 252 | +5. Export the verifier contract: |
| 253 | + |
| 254 | +```sh |
| 255 | +# FunC contract |
| 256 | +npx export-ton-verifier ./circuits/Arkworks/MulCircuit/json/verification_key.json ./contracts/verifier_ark.fc |
| 257 | + |
| 258 | +# Tact contract |
| 259 | +npx export-ton-verifier ./circuits/Arkworks/MulCircuit/json/verification_key.json ./contracts/verifier_ark.tact --tact |
| 260 | + |
| 261 | +# Tolk contract |
| 262 | +npx export-ton-verifier ./circuits/Arkworks/MulCircuit/json/verification_key.json ./contracts/verifier_ark.tolk --tolk |
| 263 | +``` |
| 264 | + |
| 265 | +### `gnark` (Go) |
| 266 | + |
| 267 | +Use the `gnark` library to generate the proof and verification key, then convert them into `snarkjs` format with `gnark-to-snarkjs`. |
| 268 | + |
| 269 | +1. Set up a `gnark` project. You can find an example circuit in the [`gnark` repository](https://github.com/Consensys/gnark?tab=readme-ov-file#example). A working example of a cubic circuit can be found in the [`zk-ton-examples` repository](https://github.com/zk-examples/zk-ton-examples/tree/main/circuits/Cubic%20%28gnark%29). |
| 270 | + |
| 271 | +1. Add `gnark-to-snarkjs` as a dependency: |
| 272 | + |
| 273 | +```sh |
| 274 | +go get github.com/mysteryon88/gnark-to-snarkjs@latest |
| 275 | +``` |
| 276 | + |
| 277 | +3. Export the proof and verification key: |
| 278 | + |
| 279 | +```go |
| 280 | +{ |
| 281 | + proof_out, _ := os.Create("proof.json") |
| 282 | + defer proof_out.Close() |
| 283 | + _ = gnarktosnarkjs.ExportProof(proof, []string{"35"}, proof_out) |
| 284 | +} |
| 285 | +{ |
| 286 | + out, _ := os.Create("verification_key.json") |
| 287 | + defer out.Close() |
| 288 | + _ = gnarktosnarkjs.ExportVerifyingKey(vk, out) |
| 289 | +} |
| 290 | +``` |
| 291 | + |
| 292 | +4. Export the verifier contract: |
| 293 | + |
| 294 | +```sh |
| 295 | +# Tact contract |
| 296 | +npx export-ton-verifier ./circuits/cubic-gnark/verification_key.json ./contracts/verifier_cubic.tact --tact |
| 297 | + |
| 298 | +# FunC contract |
| 299 | +npx export-ton-verifier ./circuits/cubic-gnark/verification_key.json ./contracts/verifier_cubic.fc |
| 300 | + |
| 301 | +# Tolk contract |
| 302 | +npx export-ton-verifier ./circuits/cubic-gnark/verification_key.json ./contracts/verifier_cubic.tolk --tolk |
| 303 | +``` |
| 304 | + |
| 305 | +## Conclusion |
| 306 | + |
| 307 | +This guide demonstrates a minimal example: **circuit → trusted setup → verifier export → verification in TON**. This workflow can be extended to support more complex circuits and real-world applications. |
| 308 | + |
| 309 | +## Useful Links |
| 310 | + |
| 311 | +- Example repository: [`zk-ton-examples`](https://github.com/zk-examples/zk-ton-examples/) |
| 312 | +- Verifier export library: [`export-ton-verifier`](https://github.com/mysteryon88/export-ton-verifier) |
| 313 | +- Additional utilities: |
| 314 | + - [`ark-snarkjs`](https://github.com/mysteryon88/ark-snarkjs) |
| 315 | + - [`gnark-to-snarkjs`](https://github.com/mysteryon88/gnark-to-snarkjs) |
| 316 | +- Using ZK-proofs in Tact: [docs.tact](https://docs.tact-lang.org/cookbook/zk-proofs/) |
| 317 | +- Circom: [docs.circom.io](https://docs.circom.io/) |
| 318 | +- Noname: [`zksecurity/noname`](https://github.com/zksecurity/noname) |
| 319 | +- `gnark`: [`consensys/gnark`](https://github.com/Consensys/gnark) |
| 320 | +- Arkworks: [`arkworks`](https://arkworks.rs/) |
| 321 | +- SnarkJS: [`iden3/snarkjs`](https://github.com/iden3/snarkjs) |
0 commit comments