---
# System prepended metadata

title: Analysis of zk-Snark engineering principles based on Gnark
tags: [Public]

---

# 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

![](https://hackmd.io/_uploads/ryyK4gotj.png)

**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?