diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 2ec6a0acf1..b22e44ef98 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -260,6 +260,7 @@ export default defineConfig({ collapsed: true, autogenerate: { directory: 'cookbook/dexes' } }, + { slug: 'cookbook/zk-proofs-on-tact' }, ], }, { diff --git a/docs/src/content/docs/cookbook/zk-proofs-on-tact.mdx b/docs/src/content/docs/cookbook/zk-proofs-on-tact.mdx new file mode 100644 index 0000000000..c410d41f38 --- /dev/null +++ b/docs/src/content/docs/cookbook/zk-proofs-on-tact.mdx @@ -0,0 +1,296 @@ +--- +title: zkProofs on Tact +description: "A step-by-step guide on integrating zero-knowledge proofs into Tact smart contracts, using zkJetton as an example with hidden balances, Circom circuits, and Groth16 verifiers." +--- + + +## Introduction + +This guide shows how to create, compile, and test Circom circuits and verify **ZK-proofs** in the **TON** blockchain using the **Tact** language and the **zk-SNARK Groth16** protocol. + +It demonstrates how to use the **[zkJetton](https://github.com/zkTokenTip/zkJetton)** repository to create a Jetton token in the **TON** blockchain, where user balances are hidden using homomorphic encryption and zero-knowledge proofs. + +The zkJetton project combines the Jetton standard with ZK-proof verification inside **Tact** smart contracts. `zkJetton` is based on the pipeline **Circom → snarkjs → export-ton-verifier → Tact**, similar to the examples from [zk-ton-example](https://github.com/zkTokenTip/zk-ton-example). + +:::note +This guide is also applicable to circuits written in [Noname](https://github.com/zksecurity/noname), since the `export-ton-verifier` library integrates with `snarkjs`, which in turn supports Noname. + +You can also use [gnark](https://github.com/Consensys/gnark) circuits by importing a verification key compatible with `snarkjs`. +::: + +### Disclaimer + +This repository uses a simplified version of Jetton written in Tact. The code has not been audited, contains potential vulnerabilities, and requires significant improvements. It is implemented solely for educational purposes. + +--- + +## What this guide covers + +- How zkJetton is designed. +- Setting up the environment. +- How to work with Circom. +- Exporting zk-verifiers for Tact. +- Testing zkJetton. + +--- + +## Prerequisites + +- **Node.js** and **npm** installed. +- **circom** and **snarkjs** installed. +- Basic familiarity with TON, Tact, and the Blueprint toolkit. +- Basic knowledge of Jetton and Tact. + +--- + +## Homomorphic encryption + +This project uses additively homomorphic **Paillier** encryption for private balances. It allows performing **addition** operations directly on encrypted data without decryption: + +- $Enc(m_1) \cdot Enc(m_2) \bmod n^2 = Enc(m_1 + m_2)$ + +This aligns perfectly with Jetton logic: deposit/withdrawal = adding/subtracting to the hidden balance. + +--- + +## Project setup + +1. Create a new project with Blueprint. +2. Install libraries for ZK-proof handling: + +```bash +npm install snarkjs @types/snarkjs +``` + +3. Install the verifier export utility for TON: + +```bash +npm install export-ton-verifier@latest +``` + +This tool exports verifier contracts for Tact, FunC, and Tolk. + +Or simply clone the current project: + +```sh +git clone https://github.com/zkTokenTip/zkJetton.git +cd zkJetton +npm install +``` + +--- + +## zkJetton architecture + +The token consists of several contracts: + +1. `ZkJettonMinter` — similar to `JettonMinter`. The main token contract. Allows minting tokens (`Mint`). Inherits from `trait MintVerifier`. +2. `ZkJettonWallet` — similar to `JettonWallet`. Created for each user upon registration. Allows transferring tokens (`ZkJettonTransfer`) and receiving them (`ZkJettonTransferInternal`). Inherits from `trait RegistrationVerifier` and `trait TransferVerifier`. + +--- + +## Circom + +In the `circuits` directory, you will find `circom` circuits that can be compiled as follows: + +```bash +cd circuits + +circom registration.circom --r1cs --wasm --sym --prime bls12381 +circom mint.circom --r1cs --wasm --sym --prime bls12381 +circom transfer.circom --r1cs --wasm --sym --prime bls12381 +``` + +Compilation produces: +- `.r1cs` — circuit constraints (R1CS) +- `.sym` — signal mapping +- `.wasm` — artifact for proof generation + +:::note +`snarkjs` supports both **altbn-128** and **bls12-381** curves. Ethereum uses altbn-128, but TON uses **bls12-381**, which is why this guide uses it. +::: + +### Circuits + +#### Registration circuit +The first step is registering in the token contract and assigning the user keys that will be used for encrypting balances. + +The template for fast modular exponentiation is imported first: + +```circom +include "binpower.circom"; +``` + +This template is then used to encrypt an initial balance of zero, required for proving that the encrypted balance indeed equals zero. + +#### Mint circuit +The second step is minting tokens to the user. + +After registration, the user’s public key and encrypted balance are stored in the token contract. + +The circuit checks that the minted amount is encrypted with the recipient’s public key. This is important because otherwise, when adding the encrypted balance and transfer amount, the decrypted result could be invalid (e.g., instead of 10 tokens, the user might get 10,000). + +#### Transfer circuit +The third step is transferring tokens from one user to another. + +The circuit checks that: +1. The transfer amount does not exceed the decrypted user balance. +2. The encrypted transfer amounts for sender and recipient are correctly encrypted with their respective public keys and are valid. + +--- + +### Trusted setup (Groth16) + +After writing and compiling circuits, the next step is running a simplified trusted setup ceremony. Example for the registration circuit (similar for others): + +```bash +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" +snarkjs powersoftau prepare phase2 pot10_0001.ptau pot10_final.ptau -v +snarkjs groth16 setup registration.r1cs pot10_final.ptau registration_0000.zkey +snarkjs zkey contribute registration_0000.zkey registration_final.zkey --name="1st Contributor Name" -v -e="some random text" + +# export verification key +snarkjs zkey export verificationkey registration_final.zkey verification_key.json +``` + +The parameter (`10`) affects execution time — larger circuits require higher values. + +--- + +## Exporting verifier contracts + +After the trusted setup ceremony, verifier contracts can be exported for Tact: + +```sh +npx export-ton-verifier ./circuits/registration/registration_final.zkey ./contracts/verifiers/verifier_registration.tact --tact +``` + +This generates a contract template that accepts a proof and verifies it: + +```tact +receive(msg: Verify) { + let res = self.groth16Verify(msg.piA, msg.piB, msg.piC, msg.pubInputs); +} +``` + +For quick checks, you can use the `verify` get-method. + +Possible integration approaches: +1. Turn the contract into a `trait` and inherit from it. +2. Extend the generated contract with business logic. +3. Use a two-step flow: + - User → Verifier (proof check) + - Verifier → Main contract (execute logic if verified) + +The example here uses the `trait` approach as the most convenient. + +--- + +## Testing and proof verification + +Testing is split into two stages: +1. Verifier testing (`Verifiers.spec.ts`) +2. Token testing (`zkJetton.spec.ts`) + +Helper functions in the `common` directory simplify proof generation. + +### Preparing for testing +For example, registration: + +```ts +import paillierBigint from 'paillier-bigint'; +import * as snarkjs from 'snarkjs'; +import path from 'path'; + +import { dictFromInputList, groth16CompressProof } from 'export-ton-verifier'; + +const wasmPath = path.join(__dirname, '../../circuits/registration/registration_js', 'registration.wasm'); +const zkeyPath = path.join(__dirname, '../../circuits/registration', 'registration_final.zkey'); +``` + +Imports used: +1. `paillier-bigint` — implementation of the homomorphic cryptosystem. +2. `export-ton-verifier` — helper functions: + - `dictFromInputList` — converts input array into a `Dictionary`. + - `groth16CompressProof` — prepares the proof for sending to the contract. +3. `path` — file path handling. + +### Generating a proof +Proofs can be generated with one line: + +```js +await snarkjs.groth16.fullProve(getRegistrationData(keys), wasmPath, zkeyPath); +``` + +The function `getRegistrationData(keys)` generates input values for proof creation: + +```js +export function getRegistrationData(keys: paillierBigint.KeyPair) { + const balance = initBalance; // 0 + const rand_r = getRandomBigInt(keys.publicKey.n); + const encryptedBalance = keys.publicKey.encrypt(balance, rand_r); + const pubKey = [keys.publicKey.g, rand_r, keys.publicKey.n]; + + return { encryptedBalance, balance, pubKey }; +} +``` + +Which corresponds to the `circom` circuit: + +``` +signal input encryptedBalance; +signal input balance; +// public key: g, rand r, n +signal input pubKey[3]; +``` + +The generated proof is then prepared for contract submission: + +```js +const { proof, publicSignals } = await createRegistrationProof(keys); + +const { pi_a, pi_b, pi_c, pubInputs } = await groth16CompressProof(proof, publicSignals); +``` + +### Proof verification +For testing, proofs can be verified locally: + +```js +const verificationKey = require('../circuits/verification_key.json'); + +const ok = await snarkjs.groth16.verify(verificationKey, publicSignals, proof); +``` + +Or sent to the contract: + +```js +await zkJettonWallet.getVerifyRegistration( + beginCell().storeBuffer(pi_a).endCell().asSlice(), + beginCell().storeBuffer(pi_b).endCell().asSlice(), + beginCell().storeBuffer(pi_c).endCell().asSlice(), + dictFromInputList(pubInputs), +), +``` + +--- + +## Conclusion + +`zkJetton` is a working example of a minimal Jetton token with private balances. +It demonstrates: +- how to integrate zk-proofs into TON, +- how to protect user data, +- how to use Circom circuits with Tact contracts. + +This can serve as a foundation for building private DeFi protocols, DAOs, or payment systems in TON. + +--- + +## Useful links + +- Token repository with hidden balances: [zkJetton](https://github.com/zkTokenTip/zkJetton) +- Example repository: [zk-ton-example](https://github.com/zkTokenTip/zk-ton-example/) +- Verifier export library: [export-ton-verifier](https://github.com/mysteryon88/export-ton-verifier) +- Circom: [docs.circom.io](https://docs.circom.io/) +- SnarkJS: [iden3/snarkjs](https://github.com/iden3/snarkjs) diff --git a/spell/cspell-list.txt b/spell/cspell-list.txt index 2738ff737f..b73d26b895 100644 --- a/spell/cspell-list.txt +++ b/spell/cspell-list.txt @@ -1,6 +1,7 @@ Aksakov Aliaksandr alnum +altbn Ashimine assgn astrojs @@ -10,6 +11,7 @@ backdoor backdoors Bahdanau basechain +binpower bitcode bitstring bitstrings @@ -23,6 +25,8 @@ Cheatsheet cheatsheets Cheatsheets chksgn +Circom +circom cleanall CNFT codegen @@ -30,6 +34,7 @@ compilables Compilables comptime Comptime +cryptosystem CSBT Daniil Danil @@ -66,14 +71,15 @@ funs Georgiy getsimpleforwardfee gettest +Groth guarantor Gutarev hazyone -Héctor hehe heisenbugs hippity Hoppity +Héctor idict Ikko Ilya @@ -111,6 +117,7 @@ llms logomark lordivash lparen +Ludwintor lvalue lvalues Makhnev @@ -144,19 +151,23 @@ Nowarp Offchain omelander Ovchinnikov +Paillier +paillier Parens Petr pgen pinst POSIX postpack +powersoftau prando +ptau quadtree quadtrees RANDU -Reentrancy rangle rawslice +Reentrancy reentrancy renamer replaceget @@ -164,7 +175,6 @@ respecifying rparen rugpull rugpulled -Sánchez sansx Satoshi sctx @@ -177,6 +187,7 @@ shardchains shiki Shvetc skywardboundd +snarkjs Stateinit statinit stdlib @@ -194,6 +205,7 @@ subfolders subtyping subwallet supertypes +Sánchez Tactina tactlang Tarjan @@ -202,6 +214,7 @@ thetonstudio TIMELOCK timeouted Timeouted +Tolk Toncoin Toncoins tonstudio @@ -221,6 +234,7 @@ unixfs untypable varint varuint +verificationkey verytactical viiii Viltrum @@ -234,4 +248,3 @@ xpyctumo xtwitter yeehaw привет -Ludwintor