# Analysis of zk-Snark engineering principles based on Gnark
###### tags: `Public`
Author: 0x60018 from [@OrbiterResearch](https://twitter.com/OrbiterResearch)
## 1. How to use Gnark
Example:
```typescript
Hash = mimc(PreImage)
```
*mimc* is the hash algorithm, providing *Hash* and *proof* to make the **verifier** be sure of the **prover** knows the *PreImage*.
### 1.1 Define the circuit
```typescript
// CubicCircuit defines a simple circuit
type CubicCircuit struct {
PreImage frontend.Variable
Hash frontend.Variable `gnark:",public"`
}
// Define declares the circuit constraints
func (circuit *CubicCircuit) Define(api frontend.API) error {
mimc, _ := mimc.NewMiMC(api)
mimc.Write(circuit.PreImage)
api.AssertIsEqual(circuit.Hash, mimc.Sum())
return nil
}
```
Q1: Is there a more general Turing-complete language for writing circuits?
Q2: Is that what the Cairo language does for this purpose?
### 1.2 Compile the circuit and generate R1CS constraints
*Note: In addition to the BN254 curve, there are also curves such as BLS12–381.*
Detailed comparison: https://docs.gnark.consensys.net/en/latest/Concepts/schemes_curves/
The mathematical rationale for generating R1CS constraints is complicated and still needs further research.
```typescript
// Generate constraints using BN254 curves
r1cs, err := frontend.Compile(ecc.BN254, r1cs.NewBuilder, &circuit)
```
### 1.3 Generate ProvingKey, VerifyingKey
```typescript
pk, vk, err := groth16.Setup(r1cs)
```
### 1.4 Generates Solidity file of Verifier using VerifyingKey
```typescript
verifySolidityPath := fmt.Sprintf("..%chardhat%ccontracts%cmimc_groth16.sol", os.PathSeparator, os.PathSeparator, os.PathSeparator)
f, _ := os.OpenFile(verifySolidityPath, os.O_CREATE|os.O_WRONLY, 0666)
defer f.Close()
vk.ExportSolidity(f)
```
### 1.5 Generate proof by applying Prove to PreImage, Hash
```typescript
assignment := &CubicCircuit{PreImage: preImage, Hash: hash}
witness, _ := frontend.NewWitness(assignment, ecc.BN254)
publicWitness, _ := witness.Public()
proof, err := groth16.Prove(r1cs, pk, witness)
```
### 1.6 Save proof and publicWitness
```typescript
// Save publicWitness
publicWitnessPath := fmt.Sprintf("..%chardhat%ctest%cmimc_public_witness.json", os.PathSeparator, os.PathSeparator, os.PathSeparator)
publicWitnessFile, _ := os.OpenFile(publicWitnessPath, os.O_CREATE|os.O_WRONLY, 0666)
defer publicWitnessFile.Close()
publicWitnessJson, _ := publicWitness.MarshalJSON()
publicWitnessFile.Write(publicWitnessJson)
// Save proof
proofPath := fmt.Sprintf("..%chardhat%ctest%cmimc.proof", os.PathSeparator, os.PathSeparator, os.PathSeparator)
proofFile, _ := os.OpenFile(proofPath, os.O_CREATE|os.O_WRONLY, 0666)
defer proofFile.Close()
proof.WriteRawTo(proofFile)
```
### 1.7 Send proof and publicWitness to the contract for verification
```typescript
import { ethers } from "hardhat";
import { Signer, BigNumber, Contract, ContractFactory } from "ethers";
// import "ethers";
import { expect } from "chai";
import fs from "fs-extra";
import path from "path";
describe("mimc", function () {
let accounts: Signer[];
let contractFactory: ContractFactory;
let verifierContract: Contract;
before(async function () {
accounts = await ethers.getSigners();
contractFactory = await ethers.getContractFactory("Verifier");
});
it("deploy", async function () {
verifierContract = await contractFactory.deploy();
});
it("verifyProof", async function () {
const fpSize = 4 * 8;
const inputBuffer = await fs.readFile(
path.resolve(__dirname, "mimc_public_witness.input")
);
const input = JSON.parse(inputBuffer.toString()).Hash;
console.warn(input);
const proofBuffer = await fs.readFile(
path.resolve(__dirname, "mimc.proof")
);
const a: Buffer[] = [];
a[0] = proofBuffer.slice(fpSize * 0, fpSize * 1);
a[1] = proofBuffer.slice(fpSize * 1, fpSize * 2);
const b: Buffer[][] = [[], []];
b[0][0] = proofBuffer.slice(fpSize * 2, fpSize * 3);
b[0][1] = proofBuffer.slice(fpSize * 3, fpSize * 4);
b[1][0] = proofBuffer.slice(fpSize * 4, fpSize * 5);
b[1][1] = proofBuffer.slice(fpSize * 5, fpSize * 6);
const c: Buffer[] = [];
c[0] = proofBuffer.slice(fpSize * 6, fpSize * 7);
c[1] = proofBuffer.slice(fpSize * 7, fpSize * 8);
const resp = await verifierContract.verifyProof(a, b, c, [input]);
expect(resp).to.be.true;
});
});
```
In the above example, we can see that only **proof** and **public input** (Hash value) are submitted off-chain. The contract can verify whether the value of **PreImage** is known off-chain, which can realize privacy proof. But there are still some issues to be resolved:
**Q1**: Split the proof into a, b, and c. The function is to check that the elements in a, b, and c cannot be greater than or equal to PRIME_Q (value: 21888242871839275222246405745257275088696311157297823662689037894645226208583) during contract verification. What is the mathematical principle? Rules for R1CS constraints?
**Q2**: Proof generation in the above example needs to be generated by **go language**, so the proof cannot be generated on the web client. How can this be achieved using **TypeScript**? Can it be imported from gnark or is there another library that implements it? Is there a reference to *Tornado.cash*?
A2: Currently, [snarkjs](https://github.com/iden3/snarkjs) and [zokrates](https://github.com/Zokrates/ZoKrates) use *JavaScript* to implement the *zk-snark* function, and *Tornado.cash* uses *snarkjs*
Q3: How to implement zk-rollup with zk-snark technology? or a merkle tree proof circuit? What is the relationship between merkle and zk-rollup (or no relationship at all?)
A3: *zk-rollup* uses *merkle tree* to store **user amount, public key, nonce value** and other information. The Operator converts the transaction into a state and generates a zk-snark proof to submit to the L1 contract for verification and change the user state (simple version)
## 2. Gnark source code structure
The following structural analysis is based on Gnark v0.7.1

**Q**: The operation functions provided under *frontend => api.go* are limited, and Turing-complete zk programming cannot be provided for the time being. Is **circom** more complete?