forked from Consensys/gnark
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: add input packing example (Consensys#1311)
- Loading branch information
Showing
2 changed files
with
218 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Package inputpacking illustrates input packing for reducing public input. | ||
|
||
// Usually in a SNARK circuit there are public and private inputs. The public | ||
// inputs are known to the prover and verifier, while the private inputs are | ||
// known only to the prover. To verify the proof, the verifier needs to provide | ||
// the public inputs as an input to the verification algorithm. | ||
// | ||
// However, there are several drawbacks to this approach: | ||
// 1. The public inputs may not be of a convenient format -- this happens for example when using the non-native arithmetic where we work on limbs. | ||
// 2. The verifier work depends on the number of public inputs -- this is a problem in case of a recursive SNARK verifier, making the recursion more expensive. | ||
// 3. The public input needs to be provided as a calldata to the Solidity verifier, which is expensive. | ||
// | ||
// An alternative approach however is to provide only a hash of the public | ||
// inputs to the verifier. This way, if the verifier computes the hash of the | ||
// inputs on its own, it can be sure that the inputs are correct and we can | ||
// mitigate the issues. | ||
// | ||
// This examples how to use this approach for both native and non-native inputs. We use MiMC hash function. | ||
package inputpacking |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
package inputpacking | ||
|
||
import ( | ||
"crypto/rand" | ||
"fmt" | ||
"math/big" | ||
|
||
fp_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/fp" | ||
fr_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/fr" | ||
cmimc "github.com/consensys/gnark-crypto/ecc/bn254/fr/mimc" | ||
|
||
"github.com/consensys/gnark-crypto/ecc" | ||
"github.com/consensys/gnark/backend/groth16" | ||
"github.com/consensys/gnark/frontend" | ||
"github.com/consensys/gnark/frontend/cs/r1cs" | ||
"github.com/consensys/gnark/std/hash/mimc" | ||
"github.com/consensys/gnark/std/math/emulated" | ||
"github.com/consensys/gnark/std/math/emulated/emparams" | ||
) | ||
|
||
func inCircuitComputation(api frontend.API, input1, input2 frontend.Variable, expected frontend.Variable) { | ||
res := api.Mul(input1, input2) | ||
api.AssertIsEqual(res, expected) | ||
} | ||
|
||
func inCircuitComputationEmulated(api frontend.API, input1, input2 emulated.Element[emulated.BN254Fp], expected emulated.Element[emulated.BN254Fp]) error { | ||
f, err := emulated.NewField[emulated.BN254Fp](api) | ||
if err != nil { | ||
return err | ||
} | ||
res := f.Mul(&input1, &input2) | ||
f.AssertIsEqual(res, &expected) | ||
return nil | ||
} | ||
|
||
// UnpackedCircuit represents a circuit where all public inputs are given as is | ||
type UnpackedCircuit struct { | ||
Input1, Input2 frontend.Variable `gnark:",public"` | ||
EmulatedInput1, EmulatedInput2 emulated.Element[emulated.BN254Fp] `gnark:",public"` | ||
Output frontend.Variable `gnark:",private"` | ||
EmulatedOutput emulated.Element[emulated.BN254Fp] `gnark:",private"` | ||
} | ||
|
||
func (circuit *UnpackedCircuit) Define(api frontend.API) error { | ||
inCircuitComputation(api, circuit.Input1, circuit.Input2, circuit.Output) | ||
return inCircuitComputationEmulated(api, circuit.EmulatedInput1, circuit.EmulatedInput2, circuit.EmulatedOutput) | ||
} | ||
|
||
// PackedCircuit represents a circuit where all public inputs are given as private instead and we provide a hash of them as the only public input. | ||
type PackedCircuit struct { | ||
PublicHash frontend.Variable | ||
|
||
Input1, Input2 frontend.Variable `gnark:",private"` | ||
EmulatedInput1, EmulatedInput2 emulated.Element[emulated.BN254Fp] `gnark:",private"` | ||
Output frontend.Variable `gnark:",private"` | ||
EmulatedOutput emulated.Element[emulated.BN254Fp] `gnark:",private"` | ||
} | ||
|
||
func (circuit *PackedCircuit) Define(api frontend.API) error { | ||
h, err := mimc.NewMiMC(api) | ||
if err != nil { | ||
return err | ||
} | ||
h.Write(circuit.Input1) | ||
h.Write(circuit.Input2) | ||
h.Write(circuit.EmulatedInput1.Limbs...) | ||
h.Write(circuit.EmulatedInput2.Limbs...) | ||
dgst := h.Sum() | ||
api.AssertIsEqual(dgst, circuit.PublicHash) | ||
|
||
inCircuitComputation(api, circuit.Input1, circuit.Input2, circuit.Output) | ||
return inCircuitComputationEmulated(api, circuit.EmulatedInput1, circuit.EmulatedInput2, circuit.EmulatedOutput) | ||
} | ||
|
||
func Example() { | ||
modulusNative := ecc.BN254.ScalarField() | ||
modulusEmulated := ecc.BN254.BaseField() | ||
|
||
// declare inputs | ||
input1, err := rand.Int(rand.Reader, modulusNative) | ||
if err != nil { | ||
panic(err) | ||
} | ||
input2, err := rand.Int(rand.Reader, modulusNative) | ||
if err != nil { | ||
panic(err) | ||
} | ||
emulatedInput1, err := rand.Int(rand.Reader, modulusEmulated) | ||
if err != nil { | ||
panic(err) | ||
} | ||
emulatedInput2, err := rand.Int(rand.Reader, modulusEmulated) | ||
if err != nil { | ||
panic(err) | ||
} | ||
output := new(big.Int).Mul(input1, input2) | ||
output.Mod(output, modulusNative) | ||
emulatedOutput := new(big.Int).Mul(emulatedInput1, emulatedInput2) | ||
emulatedOutput.Mod(emulatedOutput, modulusEmulated) | ||
|
||
// first we run the circuit where public inputs are not packed | ||
assignment := &UnpackedCircuit{ | ||
Input1: input1, | ||
Input2: input2, | ||
EmulatedInput1: emulated.ValueOf[emparams.BN254Fp](emulatedInput1), | ||
EmulatedInput2: emulated.ValueOf[emparams.BN254Fp](emulatedInput2), | ||
Output: output, | ||
EmulatedOutput: emulated.ValueOf[emparams.BN254Fp](emulatedOutput), | ||
} | ||
privWit, err := frontend.NewWitness(assignment, ecc.BN254.ScalarField()) | ||
if err != nil { | ||
panic(err) | ||
} | ||
publicWit, err := frontend.NewWitness(assignment, ecc.BN254.ScalarField(), frontend.PublicOnly()) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
ccs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &UnpackedCircuit{}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
pk, vk, err := groth16.Setup(ccs) | ||
if err != nil { | ||
panic(err) | ||
} | ||
proof, err := groth16.Prove(ccs, pk, privWit) | ||
if err != nil { | ||
panic(err) | ||
} | ||
err = groth16.Verify(proof, vk, publicWit) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// print the number of public inputs when we provide all public inputs. Note that we also count the commitment here. | ||
fmt.Println("unpacked public variables:", ccs.GetNbPublicVariables()) | ||
|
||
// then we run the circuit where public inputs are packed | ||
var buf [fr_bn254.Bytes]byte | ||
var buf2 [fp_bn254.Bytes]byte | ||
h := cmimc.NewMiMC() | ||
input1.FillBytes(buf[:]) | ||
h.Write(buf[:]) | ||
input2.FillBytes(buf[:]) | ||
h.Write(buf[:]) | ||
emulatedInput1.FillBytes(buf2[:]) | ||
h.Write(buf2[24:32]) | ||
h.Write(buf2[16:24]) | ||
h.Write(buf2[8:16]) | ||
h.Write(buf2[0:8]) | ||
emulatedInput2.FillBytes(buf2[:]) | ||
h.Write(buf2[24:32]) | ||
h.Write(buf2[16:24]) | ||
h.Write(buf2[8:16]) | ||
h.Write(buf2[0:8]) | ||
|
||
dgst := h.Sum(nil) | ||
phash := new(big.Int).SetBytes(dgst) | ||
|
||
assignment2 := &PackedCircuit{ | ||
PublicHash: phash, | ||
Input1: input1, | ||
Input2: input2, | ||
EmulatedInput1: emulated.ValueOf[emparams.BN254Fp](emulatedInput1), | ||
EmulatedInput2: emulated.ValueOf[emparams.BN254Fp](emulatedInput2), | ||
Output: output, | ||
EmulatedOutput: emulated.ValueOf[emparams.BN254Fp](emulatedOutput), | ||
} | ||
privWit2, err := frontend.NewWitness(assignment2, ecc.BN254.ScalarField()) | ||
if err != nil { | ||
panic(err) | ||
} | ||
publicWit2, err := frontend.NewWitness(assignment2, ecc.BN254.ScalarField(), frontend.PublicOnly()) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
ccs2, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &PackedCircuit{}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
pk2, vk2, err := groth16.Setup(ccs2) | ||
if err != nil { | ||
panic(err) | ||
} | ||
proof2, err := groth16.Prove(ccs2, pk2, privWit2) | ||
if err != nil { | ||
panic(err) | ||
} | ||
err = groth16.Verify(proof2, vk2, publicWit2) | ||
if err != nil { | ||
panic(err) | ||
} | ||
// print the number of public inputs when we provide only the hash. Note that we also count the commitment here. | ||
fmt.Println("packed public variables:", ccs2.GetNbPublicVariables()) | ||
// output: unpacked public variables: 11 | ||
// packed public variables: 1 | ||
} |