-
Notifications
You must be signed in to change notification settings - Fork 419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Perf: Poseidon2 GKR circuit #1410
base: master
Are you sure you want to change the base?
Changes from 37 commits
6025aa5
952ac57
3af41cb
c8d5327
f9d6f56
a4c633e
134e640
35a1045
d7c49f9
909adbf
89de33f
5e00636
63038e6
a8e353f
1b57817
d9abb7d
e137111
8f5e88e
e5fdd2b
a022a5a
5f8eee0
a216fee
37a79fa
2473dea
bdc849e
5032ce6
254dd75
cc342a0
4b9aaf2
ffdba7f
d6fcd5d
5c46d09
3ed26b0
2a94b12
e344adc
c1ecda2
6ea4c1e
7c5f296
7a488cb
1ee4644
12a8dcb
429e64f
dee2881
b1ed2dd
791c5e3
883c46f
836ece8
4f862ce
e2cfc55
f8f8497
e99bfb5
8329f58
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package gkr | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"github.com/consensys/gnark-crypto/ecc" | ||
frBls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" | ||
gkrBls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/gkr" | ||
hint "github.com/consensys/gnark/constraint/solver" | ||
"github.com/consensys/gnark/frontend" | ||
"math/big" | ||
) | ||
|
||
// SolveAll IS A TEST FUNCTION USED ONLY TO DEBUG a GKR circuit | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it is only a test function not supposed to be publicly exposed, then I would rename the package of this file to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean that it's a testing utility, similar to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked at the method documentation and all-caps documentation that its a debug method scared me off. So looking at the method, my understanding is that it computes the GKR circuit directly inside a SNARK, by evaluating the gates in-circuit (instead of computing a GKR proof and then verifying the GKR proof inside a SNARK). In that case the method could be useful, but I'm still not sure if it is good to expose it in a public API. Or if we do, then with better documentation what is actually going on a la: // SolveNative solves the defined circuit directly inside the SNARK circuit. This means that the method does not compute the GKR proof of the circuit and does not embed the GKR proof verifier inside a SNARK.
// Usually this method shouldn't be called, only for exceptional cases:
// * debugging a GKR circuit - GKR prover is only called for actual proving backends and doesn't work with test engine, this method allows to develop the circuits without needing to run the actual solver/prover
// * switching implementation from GKR to SNARK if the number of input instances is small - GKR efficiency appears when the number of input instances is large. This method allows to directly compile the GKR circuit as SNARK circuit to have only a single GKR circuit implementation
func (api *API) NativeSolve(nativeAPI frontend.API) [][]frontend.Variable { |
||
// The output is the values of all variables, across all instances | ||
// TODO find better name | ||
func (api *API) SolveAll(parentApi frontend.API) [][]frontend.Variable { | ||
res := make([][]frontend.Variable, len(api.toStore.Circuit)) | ||
degreeTestedGates := make(map[string]struct{}) | ||
for i, w := range api.toStore.Circuit { // TODO use assignments | ||
res[i] = make([]frontend.Variable, api.nbInstances()) | ||
copy(res[i], api.assignments[i]) | ||
if len(w.Inputs) == 0 { | ||
continue | ||
} | ||
degree := Gates[w.Gate].Degree() | ||
var degreeFr int | ||
if parentApi.Compiler().Field().Cmp(ecc.BLS12_377.ScalarField()) == 0 { | ||
degreeFr = gkrBls12377.Gates[w.Gate].Degree() | ||
} else { | ||
panic("field not yet supported") | ||
} | ||
if degree != degreeFr { | ||
panic(fmt.Errorf("gate \"%s\" degree mismatch: SNARK %d, Raw %d", w.Gate, degree, degreeFr)) | ||
} | ||
} | ||
for instanceI := range api.nbInstances() { | ||
for wireI, w := range api.toStore.Circuit { | ||
if len(w.Dependencies) != 0 && len(w.Inputs) != 0 { | ||
panic(fmt.Errorf("non-input wire %d should not have dependencies", wireI)) | ||
} | ||
for _, dep := range w.Dependencies { | ||
if dep.InputInstance == instanceI { | ||
if dep.OutputInstance >= instanceI { | ||
panic(fmt.Errorf("out of order dependency not yet supported in SolveAll; (wire %d, instance %d) depends on (wire %d, instance %d)", wireI, instanceI, dep.OutputWire, dep.OutputInstance)) | ||
} | ||
if res[wireI][instanceI] != nil { | ||
panic(fmt.Errorf("dependency (wire %d, instance %d) <- (wire %d, instance %d) attempting to override existing value assignment", wireI, instanceI, dep.OutputWire, dep.OutputInstance)) | ||
} | ||
res[wireI][instanceI] = api.assignments[dep.OutputWire][dep.OutputInstance] | ||
} | ||
} | ||
|
||
if res[wireI][instanceI] == nil { // no assignment or dependency | ||
if len(w.Inputs) == 0 { | ||
panic(fmt.Errorf("input wire %d, instance %d has no dependency or explicit assignment", wireI, instanceI)) | ||
} | ||
ins := make([]frontend.Variable, len(w.Inputs)) | ||
for i, in := range w.Inputs { | ||
ins[i] = res[in][instanceI] | ||
} | ||
expectedV, err := parentApi.Compiler().NewHint(frGateHint(w.Gate, degreeTestedGates), 1, ins...) | ||
if err != nil { | ||
panic(err) | ||
} | ||
res[wireI][instanceI] = Gates[w.Gate].Evaluate(parentApi, ins...) | ||
parentApi.AssertIsEqual(expectedV[0], res[wireI][instanceI]) // snark and raw gate evaluations must agree | ||
} | ||
|
||
} | ||
} | ||
return res | ||
} | ||
|
||
func frGateHint(gateName string, degreeTestedGates map[string]struct{}) hint.Hint { | ||
return func(mod *big.Int, ins, outs []*big.Int) error { | ||
if len(outs) != 1 { | ||
return errors.New("gate must have one output") | ||
} // TODO @Tabaie implement other fields | ||
if ecc.BLS12_377.ScalarField().Cmp(mod) == 0 { | ||
|
||
gate := gkrBls12377.Gates[gateName] | ||
if gate == nil { | ||
return fmt.Errorf("gate \"%s\" not found", gateName) | ||
} | ||
if _, ok := degreeTestedGates[gateName]; !ok { | ||
if err := gkrBls12377.TestGateDegree(gate, len(ins)); err != nil { | ||
return fmt.Errorf("gate %s: %w", gateName, err) | ||
} | ||
degreeTestedGates[gateName] = struct{}{} | ||
} | ||
|
||
x := make([]frBls12377.Element, len(ins)) | ||
for i := range ins { | ||
x[i].SetBigInt(ins[i]) | ||
} | ||
y := gate.Evaluate(x...) | ||
y.BigInt(outs[0]) | ||
} else { | ||
return errors.New("field not supported") | ||
} | ||
return nil | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package poseidon2 | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/consensys/gnark/frontend" | ||
"github.com/consensys/gnark/std/hash" | ||
poseidon2 "github.com/consensys/gnark/std/permutation/poseidon2" | ||
) | ||
|
||
// NewMerkleDamgardHasher returns a Poseidon2 hasher using the Merkle-Damgard | ||
// construction with the default parameters. | ||
func NewMerkleDamgardHasher(api frontend.API) (hash.FieldHasher, error) { | ||
f, err := poseidon2.NewPoseidon2(api) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not create poseidon2 hasher: %w", err) | ||
} | ||
return hash.NewMerkleDamgardHasher(api, f, 0), nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package poseidon2 | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/consensys/gnark-crypto/ecc" | ||
"github.com/consensys/gnark-crypto/ecc/bls12-377/fr/poseidon2" | ||
"github.com/consensys/gnark/frontend" | ||
"github.com/consensys/gnark/test" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestPoseidon2Hash(t *testing.T) { | ||
// prepare expected output | ||
h := poseidon2.NewMerkleDamgardHasher() | ||
for i := range 5 { | ||
_, err := h.Write([]byte{byte(i)}) | ||
require.NoError(t, err) | ||
} | ||
res := h.Sum(nil) | ||
|
||
test.SingleFunction(ecc.BLS12_377, func(api frontend.API) []frontend.Variable { | ||
hsh, err := NewMerkleDamgardHasher(api) | ||
require.NoError(t, err) | ||
hsh.Write(0, 1, 2, 3, 4) | ||
return []frontend.Variable{hsh.Sum()} | ||
}, res)(t) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would remove unimplemented function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intention with this was for the GKR API to implement frontend.API so it could be passed to a gadget. I don't think we've used that in the past 2 years, so we can probably delete.