Skip to content

Commit da70be2

Browse files
authored
feat: zero-knowledge proof guide (#1398)
1 parent 2215f58 commit da70be2

File tree

3 files changed

+332
-24
lines changed

3 files changed

+332
-24
lines changed

contract-dev/zero-knowledge.mdx

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
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+
&params.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

Comments
 (0)