Zero-knowledge proofs on TON
Create, compile, and test a simple Circom scheme and verify a ZK-proof using the zk-SNARK Groth16 protocol.
This guide is also applicable to circuits written in the Noname language, since the export-ton-verifier library integrates with snarkjs, which in turn integrates with the Noname language.
The zk-ton-examples repository contains additional examples.
Prerequisites
Initialize a project
Create a new project
npm create ton@latest ZkSimple && cd ZkSimpleInstall dependencies
npm install snarkjs @types/snarkjs export-ton-verifier@latestCreate the Circom circuit
Create the circuit directory
mkdir -p circuits/Multiplier && cd circuits/MultiplierRun the commands from the following steps in the circuits/Multiplier directory, or adjust the paths accordingly if running from the project root.
Implement the circuit
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.
pragma circom 2.2.2;
template Multiplier() {
signal input a;
signal input b;
signal output c;
c <== a*b;
}
component main = Multiplier();Compile the circuit
circom Multiplier.circom --r1cs --wasm --sym --prime bls12381The compiler generates the following files:
Multiplier.r1cs— circuit constraints (R1CS)Multiplier.sym— symbolic signal mapMultiplier_js/Multiplier.wasm— artifact for generating proof
Check the constraints
snarkjs r1cs info Multiplier.r1cs[INFO] snarkJS: Curve: bls12-381
[INFO] snarkJS: # of Wires: 4
[INFO] snarkJS: # of Constraints: 1
[INFO] snarkJS: # of Private Inputs: 2
[INFO] snarkJS: # of Public Inputs: 0
[INFO] snarkJS: # of Labels: 4
[INFO] snarkJS: # of Outputs: 1Initialize trusted setup
The trusted setup is a one-time ceremony that generates the proving and verification keys for a circuit. It is 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.
Choose the "power of tau" parameter (10) as low as possible, because it affects execution time, but not too low, because the more constraints in the scheme, the higher the parameter required.
Execute the first phase
snarkjs powersoftau new bls12-381 10 pot10_0000.ptau -v
snarkjs powersoftau contribute pot10_0000.ptau pot10_0001.ptau --name="First contribution" -v -e="some random text"Execute the second phase
snarkjs powersoftau prepare phase2 pot10_0001.ptau pot10_final.ptau -v
snarkjs groth16 setup Multiplier.r1cs pot10_final.ptau Multiplier_0000.zkey
snarkjs zkey contribute Multiplier_0000.zkey Multiplier_final.zkey --name="1st Contributor" -v -e="some random text"Export the verification key
snarkjs zkey export verificationkey Multiplier_final.zkey verification_key.jsonClean up artifacts
rm pot10_0000.ptau pot10_0001.ptau pot10_final.ptau Multiplier_0000.zkeyRun the commands from the following steps in the project root.
Export the verifier contract
Export the verifier contract
npx export-ton-verifier ./circuits/Multiplier/Multiplier_final.zkey ./contracts/verifier_multiplier.tolkGenerate TypeScript wrappers
These TypeScript wrappers enable type-safe interaction with the verifier contract.
npx export-ton-verifier import-wrapper ./wrappers/Verifier_tolk.ts --groth16 --forceTest and verify the proof
Build the contracts and generate the TypeScript wrappers before running tests.
Import dependencies
import * as snarkjs from 'snarkjs';
import path from 'path';
import { dictFromInputList, groth16CompressProof } from 'export-ton-verifier';
import { Verifier } from '../wrappers/Verifier_tolk';Implement local verification
it("should verify the proof locally", async () => {
const wasmPath = path.join(
__dirname,
'../circuits/Multiplier/Multiplier_js',
'Multiplier.wasm'
);
const zkeyPath = path.join(
__dirname,
'../circuits/Multiplier',
'Multiplier_final.zkey'
);
const verificationKey = require('../circuits/Multiplier/verification_key.json');
const input = { a: '342', b: '1245' };
const {
proof,
publicSignals
} = await snarkjs.groth16.fullProve(input, wasmPath, zkeyPath);
const okLocal = await snarkjs.groth16.verify(verificationKey, publicSignals, proof);
expect(okLocal).toBe(true);
});Implement on-chain verification
it("should verify the proof on-chain", async () => {
const { pi_a, pi_b, pi_c, pubInputs } = await groth16CompressProof(proof, publicSignals);
// Quick check via get-method: verifies the proof locally without changing blockchain state.
expect(await verifier.getVerify({ pi_a, pi_b, pi_c, pubInputs })).toBe(true);
// Send the proof to the contract in a message.
// The contract will run verification; subsequent handling of the result depends on the integration.
await verifier.sendVerify
deployer.getSender(),
{ pi_a, pi_b, pi_c, pubInputs, value: toNano('0.15') }
);
});Other languages
This tutorial follows the path: Circom → snarkjs → export-ton-verifier → TON.
The same workflow applies to other stacks, the key requirement is to obtain a proof and a verification key in the snarkjs format. The idea is always the same: generate proof.json and verification_key.json in the snarkjs format, then perform the verification in TON with the export-ton-verifier.
The zk-ton-examples repository contains templates for Noname, gnark, and Arkworks. It is possible to generate proofs in any of these stacks, then convert them to the snarkjs format and verify them both locally and on-chain.
Two utilities are available that help convert proofs and verification keys in a format compatible with snarkjs: ark-snarkjs and gnark-to-snarkjs.
Rust
Use the Arkworks library to generate the proof and verification key, then convert them into snarkjs format with ark-snarkjs.
Create a new project
cargo init
cargo add ark-bls12-381 ark-ff ark-groth16 ark-r1cs-std ark-relations ark-snark ark-snarkjs ark-std rand@0.8.5The listed packages are core dependencies for most Arkworks circuits. Depending on the specific circuit implementation, additional packages may be required.
Implement the circuit
Implement the circuit logic using Arkworks primitives, similar to a Circom circuit.
Learn how to write constraints in Arkworks by following the Arkworks R1CS tutorial.
The zk-ton-examples repository contains a working example of a simple multiplication circuit.
Compile, generate proof, perform trusted setup
Follow the same workflow as in the Circom section and the trusted setup section, but implement the logic in Rust with Arkworks instead of Circom. The resulting proof and verification key will be in Arkworks format, which can be converted to snarkjs format with ark-snarkjs.
Export the proof and verification key
The following script will create the directory and files automatically.
use ark_snarkjs::{export_proof, export_vk};
use ark_bls12_381::Bls12_381;
let _ = export_proof::<Bls12_381, _>(&proof, &public_inputs, "json/proof.json");
let _ = export_vk::<Bls12_381, _>(
¶ms.vk,
public_inputs.len(),
"json/verification_key.json",
);Export the verifier contract
npx export-ton-verifier ./circuits/Arkworks/MulCircuit/json/verification_key.json ./contracts/verifier_ark.tolkGo
Use the gnark library to generate the proof and verification key, then convert them to the snarkjs format with gnark-to-snarkjs.
Create a new project
mkdir zk-ton-gnark && cd zk-ton-gnark
go mod init zk-ton-gnark
The gnark repository contains an example circuit.
The zk-ton-examples repository contains a working example of a simple cubic circuit.
Install the dependencies
go get github.com/consensys/gnark@latest
go get github.com/mysteryon88/gnark-to-snarkjs@latestExport the proof and verification key
{
proof_out, _ := os.Create("proof.json")
defer proof_out.Close()
_ = gnarktosnarkjs.ExportProof(proof, []string{"35"}, proof_out)
}
{
out, _ := os.Create("verification_key.json")
defer out.Close()
_ = gnarktosnarkjs.ExportVerifyingKey(vk, out)
}Export the verifier contract
npx export-ton-verifier ./circuits/cubic-gnark/verification_key.json ./contracts/verifier_cubic.tolkUseful Links
- The zk-ton-examples repository contains additional examples of zk-SNARK circuits and their integration with TON.
- The export-ton-verifier repository is a library that generates TON-compatible verifier contracts from snarkjs verification keys.
- The snarkjs repository is a JavaScript library for generating and verifying zk-SNARK proofs, as well as performing trusted setup ceremonies.
- The ark-snarkjs repository is a utility that converts Arkworks proofs and verification keys into a format compatible with snarkjs.
- The gnark-to-snarkjs repository is a utility that converts gnark proofs and verification keys into a format compatible with snarkjs.
- The Noname repository is a high-level language for writing zk-SNARK circuits, which can be compiled to a format compatible with snarkjs.
- The gnark repository is a Go library for writing zk-SNARK circuits and generating proofs.
- The Arkworks homepage is a Rust ecosystem for zk-SNARKs, providing libraries for writing circuits, generating proofs, and performing trusted setup ceremonies.
Last updated on