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