Skip to content
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

Open
wants to merge 52 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
6025aa5
refactor: power
Tabaie Jan 22, 2025
952ac57
feat: gkr gates
Tabaie Jan 23, 2025
3af41cb
cleanup
Tabaie Jan 23, 2025
c8d5327
feat bls12377 gates for poseidon2
Tabaie Jan 24, 2025
f9d6f56
fix: fr gates
Tabaie Jan 24, 2025
a4c633e
feat: gkr permutations
Tabaie Jan 29, 2025
134e640
fix: no y round keys in partial rounds
Tabaie Jan 29, 2025
35a1045
feat: passing proof
Tabaie Jan 29, 2025
d7c49f9
fix: max
Tabaie Jan 29, 2025
909adbf
refactor: public GkrPermutations
Tabaie Jan 29, 2025
89de33f
fix real round constants
Tabaie Jan 30, 2025
5e00636
feat: merkle damgard and poseidon2
Tabaie Jan 31, 2025
63038e6
fix bad use of h.Permutation, and test
Tabaie Jan 31, 2025
a8e353f
docs: remove incomplete comment
Tabaie Feb 3, 2025
1b57817
test: against gnark-crypto
Tabaie Feb 3, 2025
d9abb7d
remove zero keys
Tabaie Feb 3, 2025
e137111
refactor: move bls12377 code to gnark-crypto
Tabaie Feb 3, 2025
8f5e88e
perf: break up sbox
Tabaie Feb 4, 2025
e5fdd2b
fix broken up s box works
Tabaie Feb 4, 2025
a022a5a
feat: test gate degrees
Tabaie Feb 4, 2025
5f8eee0
chore: share sbox gates
Tabaie Feb 4, 2025
a216fee
refactor: simplify sbox function
Tabaie Feb 4, 2025
37a79fa
chore: integrate gnark-crypto refactors
Tabaie Feb 5, 2025
2473dea
refactor: rename NewHash to NewPermutation
Tabaie Feb 5, 2025
bdc849e
refactor: downstream from gnark-crypto Poseidon2 changes
Tabaie Feb 5, 2025
5032ce6
Merge branch 'master' into feat/poseidon2-hash
Tabaie Feb 11, 2025
254dd75
refactor: align implementation with gnark-crypto
ivokub Feb 12, 2025
cc342a0
refactor: align naming with gnark-crypto
ivokub Feb 12, 2025
4b9aaf2
feat: take default parameters from gnark-crypto
ivokub Feb 12, 2025
ffdba7f
chore: rename file
ivokub Feb 12, 2025
d6fcd5d
refactor: rename constructor to align with gnark-crypto
ivokub Feb 12, 2025
5c46d09
fix: add danger when calling permute for t=2,3 for now
ivokub Feb 13, 2025
3ed26b0
Merge branch 'master' into perf/gkr-poseidon2-breakup-sbox
Tabaie Feb 13, 2025
2a94b12
build: update deps
Tabaie Feb 13, 2025
e344adc
revert: bring back algo_utils.Map
Tabaie Feb 13, 2025
c1ecda2
fix: reflect gate naming changes
Tabaie Feb 13, 2025
6ea4c1e
Merge remote-tracking branch 'origin/feat/poseidon2-hash' into perf/g…
ivokub Feb 13, 2025
7c5f296
chore: PR feedback
Tabaie Feb 16, 2025
7a488cb
docs: poseidon2 GKR
Tabaie Feb 17, 2025
1ee4644
test: SolveInTestEngine
Tabaie Feb 17, 2025
12a8dcb
chore: solidify RegisterGKRGates
Tabaie Feb 17, 2025
429e64f
refactor: break up `finalize`
Tabaie Feb 17, 2025
dee2881
refactor: move var def near use
Tabaie Feb 17, 2025
b1ed2dd
chore: remove gkr API Println
Tabaie Feb 17, 2025
791c5e3
Merge branch 'master' into perf/gkr-poseidon2-breakup-sbox
Tabaie Feb 17, 2025
883c46f
docs: incorporate @ivokub's suggestions in SolveAll doc
Tabaie Feb 17, 2025
836ece8
Merge branch 'master' into perf/gkr-poseidon2-breakup-sbox
Tabaie Feb 17, 2025
4f862ce
build: update gnark-crypto deps
Tabaie Feb 17, 2025
e2cfc55
Merge branch 'master' into perf/gkr-poseidon2-breakup-sbox
Tabaie Feb 18, 2025
f8f8497
chore: remove separated feature
ivokub Feb 19, 2025
e99bfb5
test: use test.CheckCircuit instead of manual Groth16/PLONK
ivokub Feb 19, 2025
8329f58
chore: go fmt
ivokub Feb 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/blang/semver/v4 v4.0.0
github.com/consensys/bavard v0.1.27
github.com/consensys/compress v0.2.5
github.com/consensys/gnark-crypto v0.15.0
github.com/consensys/gnark-crypto v0.16.1-0.20250213054626-7c56ee003026
github.com/fxamacker/cbor/v2 v2.7.0
github.com/google/go-cmp v0.6.0
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ github.com/consensys/bavard v0.1.27 h1:j6hKUrGAy/H+gpNrpLU3I26n1yc+VMGmd6ID5+gAh
github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs=
github.com/consensys/compress v0.2.5 h1:gJr1hKzbOD36JFsF1AN8lfXz1yevnJi1YolffY19Ntk=
github.com/consensys/compress v0.2.5/go.mod h1:pyM+ZXiNUh7/0+AUjUf9RKUM6vSH7T/fsn5LLS0j1Tk=
github.com/consensys/gnark-crypto v0.15.0 h1:OXsWnhheHV59eXIzhL5OIexa/vqTK8wtRYQCtwfMDtY=
github.com/consensys/gnark-crypto v0.15.0/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU=
github.com/consensys/gnark-crypto v0.16.1-0.20250213054626-7c56ee003026 h1:rJe4sZspAP96Sb2NIpEk52x87/CEDQrmHp0NHknGOT0=
github.com/consensys/gnark-crypto v0.16.1-0.20250213054626-7c56ee003026/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
Expand Down
3 changes: 1 addition & 2 deletions std/gkr/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ func (api *API) Mul(i1, i2 constraint.GkrVariable, in ...constraint.GkrVariable)
return api.namedGate2PlusIn("mul", i1, i2, in...)
}

// TODO @Tabaie This can be useful
func (api *API) Println(a ...constraint.GkrVariable) {
func (api *API) Println(...constraint.GkrVariable) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would remove unimplemented function.

Copy link
Contributor Author

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.

panic("not implemented")
}
1 change: 1 addition & 0 deletions std/gkr/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ func (c *benchMiMCMerkleTreeCircuit) Define(api frontend.API) error {
return solution.Verify("-20", challenge)
}

// TODO @Tabaie just try using IsSolved instead?
func testGroth16(t *testing.T, circuit, assignment frontend.Circuit) {
cs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, circuit, frontend.WithCompressThreshold(compressThreshold))
require.NoError(t, err)
Expand Down
104 changes: 104 additions & 0 deletions std/gkr/testing.go
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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 gkr_test. Then it won't be publicly exposed and only available in test files which also set package gkr_test. This also requires that this is a test file, i.e. should have a suffix _test.go. So I would recommend renaming it to debugging_test.go.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean that it's a testing utility, similar to the test package. I'll update the comment to better reflect that.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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
}
}
49 changes: 47 additions & 2 deletions std/hash/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
package hash

import (
"errors"
"fmt"
"sync"

"github.com/consensys/gnark/frontend"
Expand Down Expand Up @@ -58,7 +58,7 @@ func GetFieldHasher(name string, api frontend.API) (FieldHasher, error) {
defer lock.RUnlock()
builder, ok := builderRegistry[name]
if !ok {
return nil, errors.New("hash function not found")
return nil, fmt.Errorf("hash function \"%s\" not registered", name)
}
return builder(api)
}
Expand Down Expand Up @@ -87,3 +87,48 @@ type BinaryFixedLengthHasher interface {
// FixedLengthSum returns digest of the first length bytes.
FixedLengthSum(length frontend.Variable) []uints.U8
}

// Compressor is a 2-1 one-way function. It takes two inputs and compresses
// them into one output.
//
// NB! This is lossy compression, meaning that the output is not guaranteed to
// be unique for different inputs. The output is guaranteed to be the same for
// the same inputs.
//
// The Compressor is used in the Merkle-Damgard construction to build a hash
// function.
type Compressor interface {
Compress(frontend.Variable, frontend.Variable) frontend.Variable
}

type merkleDamgardHasher struct {
state frontend.Variable
iv frontend.Variable
f Compressor
api frontend.API
}

// NewMerkleDamgardHasher transforms a 2-1 one-way function into a hash
// initialState is a value whose preimage is not known
func NewMerkleDamgardHasher(api frontend.API, f Compressor, initialState frontend.Variable) FieldHasher {
return &merkleDamgardHasher{
state: initialState,
iv: initialState,
f: f,
api: api,
}
}

func (h *merkleDamgardHasher) Reset() {
h.state = h.iv
}

func (h *merkleDamgardHasher) Write(data ...frontend.Variable) {
for _, d := range data {
h.state = h.f.Compress(h.state, d)
}
}

func (h *merkleDamgardHasher) Sum() frontend.Variable {
return h.state
}
19 changes: 19 additions & 0 deletions std/hash/poseidon2/poseidon2.go
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
}
28 changes: 28 additions & 0 deletions std/hash/poseidon2/poseidon2_test.go
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)
}
Loading