Skip to content

Commit 3ad69a8

Browse files
authored
instructions on generating Gnark proofs (#578)
1 parent 3c63ddc commit 3ad69a8

2 files changed

Lines changed: 400 additions & 0 deletions

File tree

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
# How to create a Gnark Plonk proof
2+
3+
## Step 1 : Set up your enviroment
4+
5+
- 1 Install Go: Make suere you have Go installed. You can download it from [here](https://go.dev/doc/install)
6+
7+
- 2 Initialize a Go Module: Create a new directory for your project and initializa a Go module
8+
9+
```bash=
10+
mkdir gnark_plonk_circuit
11+
cd gnark_plonk_circuit
12+
go mod init gnark_plonk_circuit
13+
```
14+
15+
- 3 Install Gnark: Add the library to your project
16+
17+
```bash=
18+
go get github.com/consensys/gnark@v0.10.0
19+
```
20+
21+
22+
23+
## Step 2: Import dependencies
24+
25+
```bash=
26+
import (
27+
"fmt"
28+
"log"
29+
"os"
30+
"github.com/consensys/gnark-crypto/ecc"
31+
"github.com/consensys/gnark/backend/plonk"
32+
cs "github.com/consensys/gnark/constraint/bn254"
33+
"github.com/consensys/gnark/frontend"
34+
"github.com/consensys/gnark/test/unsafekzg"
35+
"github.com/consensys/gnark/frontend/cs/scs"
36+
)
37+
```
38+
39+
Here's what each package is used for:
40+
41+
42+
43+
```fmt```: Standard Go library for formatted input/output.
44+
45+
```log```: Standard Go library for event logging.
46+
47+
```os```: Standard Go library for interacting with the operating system.
48+
49+
```path/filepath```: Standard Go library for portable file path manipulation.
50+
51+
```github.com/consensys/gnark-crypto/ecc```: Provides cryptographic operations over elliptic curves.
52+
53+
```github.com/consensys/gnark/backend/plonk``` Gnark backend for the PLONK proving system.
54+
55+
```github.com/consensys/gnark/constraint/bn254```: Provides types and functions to work with constraint systems specifically for the BN254 curve.
56+
57+
```github.com/consensys/gnark/frontend```: Provides the API for defining constraints and creating witness data.
58+
59+
```github.com/consensys/gnark/test/unsafekzg```: Gnark testing utilities for KZG commitments.
60+
61+
```github.com/consensys/gnark/frontend/cs/scs```: Gnark frontend for the SCS (Sparse Constraint System) builder.
62+
63+
64+
## Step 3: Define the circuit
65+
66+
The circuit structure is defined in this case using the equation
67+
68+
$x^3 + x + 5 = y$
69+
70+
71+
```bash=
72+
// CubicCircuit defines a simple circuit
73+
// x**3 + x + 5 == y
74+
type CubicCircuit struct {
75+
X frontend.Variable `gnark:"x"`
76+
Y frontend.Variable `gnark:",public"`
77+
}
78+
```
79+
Here
80+
81+
```CubicCircuit```struct contains the variables ```X``` and ```Y```
82+
83+
```X``` is a secret input, annotated as ```'gnark:"x"'```
84+
85+
```Y``` is a public input, annotated as ```'gnark:",public"'```
86+
87+
## Step 4: Define the circuit constraints:
88+
89+
Establish constraints that the circuit must satisfy. Here you define the logic that relates inputs to outputs, encapsulating the computation:
90+
91+
```bash=
92+
// Define declares the circuit constraints
93+
// x**3 + x + 5 == y
94+
func (circuit *CubicCircuit) Define(api frontend.API) error {
95+
x3 := api.Mul(circuit.X, circuit.X, circuit.X)
96+
api.AssertIsEqual(circuit.Y, api.Add(x3, circuit.X, 5))
97+
return nil
98+
}
99+
```
100+
101+
The ```Define``` method specifies the constraints for the circuit.
102+
103+
```x3 := api.Mul(circuit.X, circuit.X, circuit.X)``` computes X**3
104+
105+
106+
```api.AssertIsEqual(circuit.Y, api.Add(x3, circuit.X, 5)``` asserts that X**3 + X + 5 == Y
107+
108+
There are other options that we migth use like ```ÀssertDifferent``` ```AssertIsLessOrEqual```
109+
110+
## Step 5: Compile the circuit and generate the proof
111+
112+
Detail the steps to compile the circuit, generate a witness, create a proof, and verify it:
113+
114+
115+
we need to specify the directory where the proof, verification key and the public key will be saved
116+
117+
```bash
118+
outputDir := "gnark_plonk_circuit/"
119+
```
120+
121+
To compile the circuit we do
122+
123+
```bash=
124+
var circuit CubicCircuit
125+
// Compile the circuit using scs.NewBuilder
126+
ccs, err := frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &circuit)
127+
if err != nil {
128+
panic("circuit compilation error")
129+
}
130+
```
131+
where
132+
133+
134+
The ```frontend.Compile``` function compiles the circuit using the SCS
135+
constraint system.
136+
137+
```ecc.BN254.ScalarField()``` specifies the scalar field, in this case for the BN254 curve.
138+
139+
```scs.NewBuilder``` is used to build the sparse constraint system.
140+
141+
The we generate the SRS (Structured Reference String)
142+
143+
```bash=
144+
// Generate the SRS and its Lagrange interpolation
145+
r1cs := ccs.(*cs.SparseR1CS)
146+
srs, srsLagrangeInterpolation, err := unsafekzg.NewSRS(r1cs)
147+
if err != nil {
148+
panic("KZG setup error")
149+
}
150+
```
151+
152+
```r1cs := ccs.(*cs.SparseR1CS)``` converts the compiled circuit to a sparse R1CS(Rank-1 Constraint Systems) format required by the SRS generation.
153+
154+
```unsafekzg.NewSRS``` generates the structured reference string (SRS) and its Lagrange interpolation.
155+
156+
157+
Next we need to setup PLONK
158+
159+
```bash=
160+
pk, vk, _ := plonk.Setup(ccs, srs, srsLagrangeInterpolation)
161+
```
162+
163+
```plonk.Setup``` initializes the PLONK proving system with the constraint system, SRS, and its Lagrange interpolation.
164+
This generates the proving key ```pk``` and verification key ```vk```
165+
166+
Then the Witness is created
167+
168+
```bash=
169+
assignment := CubicCircuit{X: 3, Y: 35}
170+
fullWitness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField())
171+
if err != nil {
172+
log.Fatal(err)
173+
}
174+
publicWitness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField(), frontend.PublicOnly())
175+
if err != nil {
176+
log.Fatal(err)
177+
}
178+
```
179+
180+
An assignment to the circuit variables is created: ```X = 3``` and ```Y = 35```.
181+
182+
```frontend.NewWitness``` creates the full witness including all variables.
183+
184+
```frontend.NewWitness``` with ```frontend.PublicOnly()``` creates the public witness including only the public variables.
185+
186+
Generate the Proof:
187+
188+
```bash=
189+
proof, err := plonk.Prove(ccs, pk, fullWitness)
190+
if err != nil {
191+
panic("PLONK proof generation error")
192+
}
193+
```
194+
195+
```plonk.Prove``` generates a proof using the compilated circuit,proving key and full witness
196+
197+
Then to Verify
198+
199+
```bash=
200+
// Verify the proof
201+
err = plonk.Verify(proof, vk, publicWitness)
202+
if err != nil {
203+
panic("PLONK proof not verified")
204+
}
205+
```
206+
207+
```plonk.Verify``` verifies the proof using the compilated circuit,proving key and full witness
208+
209+
Finally we have to serialize and save outputs
210+
211+
```bash=
212+
// Open files for writing the proof, the verification key, and the public witness
213+
proofFile, err := os.Create(outputDir + "plonk.proof")
214+
if err != nil {
215+
panic(err)
216+
}
217+
vkFile, err := os.Create( "plonk.vk")
218+
if err != nil {
219+
panic(err)
220+
}
221+
witnessFile, err := os.Create( "plonk_pub_input.pub")
222+
if err != nil {
223+
panic(err)
224+
}
225+
defer proofFile.Close()
226+
defer vkFile.Close()
227+
defer witnessFile.Close()
228+
// Write the proof to the file
229+
_, err = proof.WriteTo(proofFile)
230+
if err != nil {
231+
panic("could not serialize proof into file")
232+
}
233+
// Write the verification key to the file
234+
_, err = vk.WriteTo(vkFile)
235+
if err != nil {
236+
panic("could not serialize verification key into file")
237+
}
238+
// Write the public witness to the file
239+
_, err = publicWitness.WriteTo(witnessFile)
240+
if err != nil {
241+
panic("could not serialize proof into file")
242+
}
243+
fmt.Println("Proof written into plonk.proof")
244+
fmt.Println("Verification key written into plonk.vk")
245+
fmt.Println("Public witness written into plonk_pub_input.pub")
246+
}
247+
```
248+
249+
Files are created for the proof, verification key, and public witness.
250+
251+
The proof, verification key, and public witness are written to these files.
252+
253+
This ensures that the proof and related data are saved for later use or verification.
254+
255+
The complete code is:
256+
257+
```bash=
258+
package main
259+
import (
260+
"fmt"
261+
"log"
262+
"os"
263+
"github.com/consensys/gnark-crypto/ecc"
264+
"github.com/consensys/gnark/backend/plonk"
265+
cs "github.com/consensys/gnark/constraint/bn254"
266+
"github.com/consensys/gnark/frontend"
267+
"github.com/consensys/gnark/test/unsafekzg"
268+
"github.com/consensys/gnark/frontend/cs/scs"
269+
)
270+
// CubicCircuit defines a simple circuit
271+
// x**3 + x + 5 == y
272+
type CubicCircuit struct {
273+
// struct tags on a variable is optional
274+
// default uses variable name and secret visibility.
275+
X frontend.Variable `gnark:"x"`
276+
Y frontend.Variable `gnark:",public"`
277+
}
278+
// Define declares the circuit constraints
279+
// x**3 + x + 5 == y
280+
func (circuit *CubicCircuit) Define(api frontend.API) error {
281+
x3 := api.Mul(circuit.X, circuit.X, circuit.X)
282+
api.AssertIsEqual(circuit.Y, api.Add(x3, circuit.X, 5))
283+
return nil
284+
}
285+
func main() {
286+
var circuit CubicCircuit
287+
// use scs.NewBuilder instead of r1cs.NewBuilder (groth16)
288+
ccs, err := frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &circuit)
289+
if err != nil {
290+
panic("circuit compilation error")
291+
}
292+
// use unsafekzg.NewSRS to generate the SRS and the Lagrange interpolation of the SRS
293+
// Setup prepares the public data associated to a circuit + public inputs.
294+
// The kzg SRS must be provided in canonical and lagrange form.
295+
// For test purposes, see test/unsafekzg package. With an existing SRS generated through MPC in canonical form,
296+
r1cs := ccs.(*cs.SparseR1CS)
297+
srs, srsLagrangeInterpolation, err := unsafekzg.NewSRS(r1cs)
298+
// srs, err := test.NewKZGSRS(r1cs)
299+
if err != nil {
300+
panic("KZG setup error")
301+
}
302+
// add srsLagrangeInterpolation to the Setup function
303+
pk, vk, _ := plonk.Setup(ccs, srs, srsLagrangeInterpolation)
304+
assignment := CubicCircuit{X: 3, Y: 35}
305+
fullWitness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField())
306+
if err != nil {
307+
log.Fatal(err)
308+
}
309+
publicWitness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField(), frontend.PublicOnly())
310+
if err != nil {
311+
log.Fatal(err)
312+
}
313+
// This proof should be serialized for testing in the operator
314+
proof, err := plonk.Prove(ccs, pk, fullWitness)
315+
if err != nil {
316+
panic("PLONK proof generation error")
317+
}
318+
// The proof is verified before writing it into a file to make sure it is valid.
319+
err = plonk.Verify(proof, vk, publicWitness)
320+
if err != nil {
321+
panic("PLONK proof not verified")
322+
}
323+
// Open files for writing the proof, the verification key and the public witness
324+
proofFile, err := os.Create("plonk.proof")
325+
if err != nil {
326+
panic(err)
327+
}
328+
vkFile, err := os.Create("plonk.vk")
329+
if err != nil {
330+
panic(err)
331+
}
332+
witnessFile, err := os.Create( "plonk_pub_input.pub")
333+
if err != nil {
334+
panic(err)
335+
}
336+
defer proofFile.Close()
337+
defer vkFile.Close()
338+
defer witnessFile.Close()
339+
_, err = proof.WriteTo(proofFile)
340+
if err != nil {
341+
panic("could not serialize proof into file")
342+
}
343+
_, err = vk.WriteTo(vkFile)
344+
if err != nil {
345+
panic("could not serialize verification key into file")
346+
}
347+
_, err = publicWitness.WriteTo(witnessFile)
348+
if err != nil {
349+
panic("could not serialize proof into file")
350+
}
351+
fmt.Println("Proof written into plonk.proof")
352+
fmt.Println("Verification key written into plonk.vk")
353+
fmt.Println("Public witness written into plonk_pub_input.pub")
354+
}
355+
```

0 commit comments

Comments
 (0)