# Write a zk circuit with gnark in Go

By [E(frog)mily Lin(ea)](https://paragraph.com/@emjlin) · 2024-02-27

---

Get ready - there is a lot of zk math jargon in here which might not be relevant for every audience.

This isn’t quite a beginner’s guide, but also not an advanced guide either. This is a “somewhere in the middle” guide and is here mostly as a fun pet project to get me to learn more a bit about [gnark](https://docs.gnark.consensys.io/), the ZK circuit library that powers [Linea](https://linea.build).

I do want to give a heads up that this post will not explain ZK proofs and how they work. I should probably write something up on that later.

What is gnark
=============

`gnark` is a an open-source zk-SNARK library that offers a [high-level API](https://docs.gnark.consensys.io/HowTo/write/circuit_api) to design [circuits](https://docs.gnark.consensys.io/Concepts/circuits).

As compared to other libraries like [NoirJS](https://noir-lang.org/), which enable developers to build privacy solutions, `gnark` development is primarily geared towards scale and for teams focused on building performance-critical applications such as rollups, bridges, and light-clients! That is, `gnark` is great for specialized circuits, aka running one zkSNARK per program. If you are interested in using `gnark` for privacy preserving dapps, there are Javascript libraries like [Sindri](https://sindri.app/docs/how-to-guides/frameworks/gnark/) that leverage `gnark` circuits. Linea uses `gnark`, but it also powers projects like zkBNB, Celer zkBridge.

Anyways, `gnark` has a few properties that really make it standout.

*   It’s a `go` package, which means there is no domain-specific language (DSL) required. As a result, it can also be integrated with more complex solutions like logging and serialization like any other `go` module. Additionally, devs can debug, document, test and benchmark circuits as well as versioned, unit-tested, and use in CI/CD workflows like any other Go program.
    
*   Our fine friends at `gnark` also built an extensive standard library on top of it to provide a better developer UX for doing more complex cryptographic applications such as hashing, checking signature validity, and merkle trees.
    
*   It can generate proving and verifying keys for both trusted generation and MPC generation.
    
*   It supports multiple elliptic curves and backends (Groth16, PlonK w/KZG).
    
*   It can verify via native verifiers for integrating zk proofs into traditional web services or via Ethereum smart contract verifiers for creating proofs on chain.
    
*   It’s the fastest SNARK library out there and has won an award for being the fastest on mobile.
    

If you didn’t understand most of that, that’s okay! To summarize in a sentence:

`gnark` is a robust go module to help you build lightning fast zkSNARKs for performance-critical applications.

Now that we’ve got a feeling for what `gnark` is, let’s actually just write an extremely simple circuit. You can find the [full Github repo here](https://github.com/emilyJLin95/SimpleCircuitTutorial).

Set up a go project
===================

Before you start, make sure you’ve [installed go using these instructions](https://go.dev/doc/install). Once you’ve done that, we are ready to create a project! Let’s make a directory, initialize a go project, create the file you’ll be writing the circuit in, and install the `gnark` module:

    mkdir circuit_tutorial
    cd circuit_tutorial
    go mod init example/simple_circuit
    touch simple_circuit.go
    go get github.com/consensys/gnark@latest
    

Then, open up your folder in your favorite IDE. I’m using VSCode. Modify `simple_circuit.go`:

    package main
    
    func main() {
    
    }
    

Now that we have all the scaffolding in place, let’s actually write a circuit!

The simplest circuit in the history of all circuits
===================================================

There are really complex things you can do with `gnark`, but in this case, we’ll just be writing a circuit that proves a value has been cubed and increased by 5.

A `gnark` circuit must implement this interface, where `Define` declares the circuits constraints. In our case, we will be asserting that a value Y is 5 + a value X cubed in the `Define` function.

    type Circuit interface {
        // Define declares the circuit's constraints
        Define(api frontend.API) error
    }
    

Before we start writing the circuit, just paste these imports at the top of your file, which you will all need by the time we get done with this tutorial:

    import (
        "github.com/consensys/gnark-crypto/ecc"
        "github.com/consensys/gnark/backend/groth16"
        "github.com/consensys/gnark/frontend"
        "github.com/consensys/gnark/frontend/cs/r1cs"
    )
    

To create a our circuit, we’ll want a private variable X that we will cube and add 5 to, and a public variable Y, which will be the resulting value.

Add this circuit definition to your code:

    type SimpleCircuit struct {
        // struct tags on a variable is optional
        // default uses variable name and secret visibility.
        X frontend.Variable `gnark:"x"`
        Y frontend.Variable `gnark:",public"`
    }
    

The circuit must declare its private and secret inputs as `frontend.Variable`. By default, these variables are secret (`gnark:",secret"`). However, since `Y` is our public variable, we need to indicate as such with `gnark:”,public”`.

Now that we’ve defined a circuit struct, we can actually write our `Define` method!

    // x**3 + x + 5 == y
    func (circuit *SimpleCircuit) Define(api frontend.API) error {
        x3 := api.Mul(circuit.X, circuit.X, circuit.X)
        api.AssertIsEqual(circuit.Y, api.Add(x3, circuit.X, 5))
        return nil
    }
    

Your file thus far should look like this:

    package main
    
    import (
        "github.com/consensys/gnark-crypto/ecc"
        "github.com/consensys/gnark/backend/groth16"
        "github.com/consensys/gnark/frontend"
        "github.com/consensys/gnark/frontend/cs/r1cs"
    )
    
    // SimpleCircuit defines a simple circuit
    // x**3 + x + 5 == y
    type SimpleCircuit struct {
        // struct tags on a variable is optional
        // default uses variable name and secret visibility.
        X frontend.Variable `gnark:"x"`
        Y frontend.Variable `gnark:",public"`
    }
    
    // Define declares the circuit constraints
    // x**3 + x + 5 == y
    func (circuit *SimpleCircuit) Define(api frontend.API) error {
        x3 := api.Mul(circuit.X, circuit.X, circuit.X)
        api.AssertIsEqual(circuit.Y, api.Add(x3, circuit.X, 5))
        return nil
    }
    
    func main() {
    }
    

Run your circuit
================

There are four steps to running a circuit:

1.  Compile
    
2.  Setup
    
3.  Witness generation
    
4.  Verify
    

Paste this into your `main` function:

    // compiles our circuit into a R1CS
    var circuit SimpleCircuit
    ccs, _ := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit)
    
    // groth16 zkSNARK: Setup
    pk, vk, _ := groth16.Setup(ccs)
    
    // witness definition
    assignment := SimpleCircuit{X: 3, Y: 35}
    witness, _ := frontend.NewWitness(&assignment, ecc.BN254.ScalarField())
    publicWitness, _ := witness.Public()
    
    // groth16: Prove & Verify
    proof, _ := groth16.Prove(ccs, pk, witness)
    groth16.Verify(proof, vk, publicWitness)
    

Now, to run it, simply call `go run .`

![](https://storage.googleapis.com/papyrus_images/1a8bc338a85cbf7f384d48f90df89f4d3342b58523ca19ba5dc4a84dfbe73baa.png)

Test your circuit
=================

Ah, yes, indeed you are a great developer, and all great developers write tests! `gnark` makes this easy as well, providing its own testing module to get you on your merry little way. Create a file called `simple_circuit_test.go` and paste in this code:

    package main
    
    import (
        "testing"
        "github.com/consensys/gnark/test"
    )
    
    func TestCubicEquation(t *testing.T) {
        assert := test.NewAssert(t)
    
        var simpleCircuit SimpleCircuit
    
        assert.ProverFailed(&simpleCircuit, &SimpleCircuit{
            X: 42,
            Y: 42,
        })
    
        assert.ProverSucceeded(&simpleCircuit, &SimpleCircuit{
            X: 3,
            Y: 35,
        })
    }
    

Run `go test` to actually run the test and it should pass!

![](https://storage.googleapis.com/papyrus_images/dfa6193717e6fa391d367476efb650648069c7a4daf7b44e68c6d9ff4eb9b94a.png)

Anyways, that’s everything you need to know to build a very simple ZK circuit! You are now clearly up-skilled enough to go build a rollup LOL.

If you want to find more complex circuits or just play around with them in general, `gnark` has a great interactive interface at [play.gnark.io](https://play.gnark.io/).

Feel free to follow @\_emjlin and @lineabuild on Twitter, or @emilylin and @linea on Farcaster to stay up to date with the latest news.

As always, quick plug to join /frog on Farcaster cause it’s a great channel, and if you’re interested in joining the Linea Builders Club, where you can get exclusive access to tech talks, cryptographers, educational programs, and monthly mini-hacks, you can apply [here](https://linea.deform.cc/linea-builders-club) and mention that you were referred through this blog!

---

*Originally published on [E(frog)mily Lin(ea)](https://paragraph.com/@emjlin/write-a-zk-circuit-with-gnark-in-go)*
