Skip to content

Commit

Permalink
feat: allow configurable hash-to-field function for Groth16 Solidity …
Browse files Browse the repository at this point in the history
…verifier (#1102)

* fix: set dynamic default Solidity version pragma

* feat: add option for selecting Solidity compatible options

* feat: add configurable hash function to exportSolidity

* feat: prepend default hash function to allow override

* feat: use chosen hash function in Solidity contract

* chore: generate

* test: add test for the options
  • Loading branch information
ivokub authored Sep 5, 2024
1 parent d55adc1 commit d651b89
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 26 deletions.
4 changes: 2 additions & 2 deletions backend/groth16/bn254/solidity.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ contract Verifier {
{{- end }}
publicCommitments[{{$i}}] = uint256(
sha256(
{{ hashFnName }}(
abi.encodePacked(
commitments[{{mul $i 2}}],
commitments[{{sum (mul $i 2) 1}}],
Expand Down Expand Up @@ -713,7 +713,7 @@ contract Verifier {
{{- end }}
publicCommitments[{{$i}}] = uint256(
sha256(
{{ hashFnName }}(
abi.encodePacked(
commitments[{{mul $i 2}}],
commitments[{{sum (mul $i 2) 1}}],
Expand Down
38 changes: 32 additions & 6 deletions backend/groth16/bn254/verify.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions backend/plonk/bn254/verify.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 75 additions & 1 deletion backend/solidity/solidity.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
package solidity

import (
"fmt"
"hash"

"github.com/consensys/gnark/backend"
"golang.org/x/crypto/sha3"
)

// ExportOption defines option for altering the behavior of the prover in
// Prove, ReadAndProve and IsSolved methods. See the descriptions of functions
// returning instances of this type for implemented options.
Expand All @@ -8,13 +16,15 @@ type ExportOption func(*ExportConfig) error
// ExportConfig is the configuration for the prover with the options applied.
type ExportConfig struct {
PragmaVersion string
HashToFieldFn hash.Hash
}

// NewExportConfig returns a default ExportConfig with given export options opts
// applied.
func NewExportConfig(opts ...ExportOption) (ExportConfig, error) {
config := ExportConfig{
PragmaVersion: "0.8.24",
// we set default pragma version to 0.8.0+ to avoid needing to sync Solidity CI all the time
PragmaVersion: "^0.8.0",
}
for _, option := range opts {
if err := option(&config); err != nil {
Expand All @@ -31,3 +41,67 @@ func WithPragmaVersion(version string) ExportOption {
return nil
}
}

// WithHashToFieldFunction changes the hash function used for hashing
// bytes to field. If not set then the default hash function based on RFC 9380
// is used. Used mainly for compatibility between different systems and
// efficient recursion.
func WithHashToFieldFunction(hFunc hash.Hash) ExportOption {
return func(cfg *ExportConfig) error {
cfg.HashToFieldFn = hFunc
return nil
}
}

// WithProverTargetSolidityVerifier returns a prover option that sets all the
// necessary prover options which are suitable for verifying the proofs in the
// Solidity verifier.
//
// For PLONK this is a no-op option as the Solidity verifier is directly
// compatible with the default prover options. Regardless, it is recommended to
// use this option for consistency and possible future changes in the Solidity
// verifier.
//
// For Groth16 this option sets the hash function used for hashing bytes to
// field to [sha3.NewLegacyKeccak256] as the Solidity verifier does not support
// the standard hash-to-field function. We use legacy Keccak256 in Solidity for
// the cheapest gas usage.
func WithProverTargetSolidityVerifier(bid backend.ID) backend.ProverOption {
switch bid {
case backend.GROTH16:
// Solidity verifier does not support standard hash-to-field function.
// Choose efficient one.
return backend.WithProverHashToFieldFunction(sha3.NewLegacyKeccak256())
case backend.PLONK:
// default hash function works for PLONK. We just have to return a no-op option
return func(*backend.ProverConfig) error {
return nil
}
default:
return func(*backend.ProverConfig) error {
return fmt.Errorf("unsupported backend ID: %s", bid)
}
}
}

// WithVerifierTargetSolidityVerifier returns a verifier option that sets all
// the necessary verifier options which are suitable for verifying the proofs
// targeted for the Solidity verifier. See the comments in
// [WithProverTargetSolidityVerifier].
func WithVerifierTargetSolidityVerifier(bid backend.ID) backend.VerifierOption {
switch bid {
case backend.GROTH16:
// Solidity verifier does not support standard hash-to-field function.
// Choose efficient one.
return backend.WithVerifierHashToFieldFunction(sha3.NewLegacyKeccak256())
case backend.PLONK:
// default hash function works for PLONK. We just have to return a no-op option
return func(*backend.VerifierConfig) error {
return nil
}
default:
return func(*backend.VerifierConfig) error {
return fmt.Errorf("unsupported backend ID: %s", bid)
}
}
}
176 changes: 176 additions & 0 deletions backend/solidity/solidity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package solidity_test

import (
"crypto/sha256"
"fmt"
"hash"
"testing"

"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/backend"
"github.com/consensys/gnark/backend/solidity"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/test"
"golang.org/x/crypto/sha3"
)

type noCommitCircuit struct {
A, B, Out frontend.Variable `gnark:",public"`
}

func (c *noCommitCircuit) Define(api frontend.API) error {
res := api.Mul(c.A, c.B)
api.AssertIsEqual(res, c.Out)
return nil
}

type commitCircuit struct {
A, B, Out frontend.Variable `gnark:",public"`
}

func (c *commitCircuit) Define(api frontend.API) error {
res := api.Mul(c.A, c.B)
api.AssertIsEqual(res, c.Out)
cmter, ok := api.(frontend.Committer)
if !ok {
return fmt.Errorf("api does not support commitment")
}
cmt1, err := cmter.Commit(res)
if err != nil {
return err
}
api.AssertIsDifferent(cmt1, res)
return nil
}

type twoCommitCircuit struct {
A, B, Out frontend.Variable `gnark:",public"`
}

func (c *twoCommitCircuit) Define(api frontend.API) error {
res := api.Mul(c.A, c.B)
api.AssertIsEqual(res, c.Out)
cmter, ok := api.(frontend.Committer)
if !ok {
return fmt.Errorf("api does not support commitment")
}
cmt1, err := cmter.Commit(res)
if err != nil {
return err
}
cmt2, err := cmter.Commit(cmt1)
if err != nil {
return err
}
api.AssertIsDifferent(cmt1, cmt2)
return nil
}

func TestNoCommitment(t *testing.T) {
// should succeed both with G16 and PLONK:
assert := test.NewAssert(t)
circuit := &noCommitCircuit{}
assignment := &noCommitCircuit{A: 2, B: 3, Out: 6}
defaultOpts := []test.TestingOption{
test.WithCurves(ecc.BN254),
test.WithValidAssignment(assignment),
}
checkCircuit := func(assert *test.Assert, bid backend.ID) {
opts := append(defaultOpts,
test.WithBackends(bid),
)

assert.CheckCircuit(circuit, opts...)
}
assert.Run(func(assert *test.Assert) {
checkCircuit(assert, backend.GROTH16)
}, "Groth16")
assert.Run(func(assert *test.Assert) {
checkCircuit(assert, backend.PLONK)
}, "PLONK")
}

func TestSingleCommitment(t *testing.T) {
// should succeed both with G16 and PLONK:
// - But for G16 only if the hash-to-field is set to a supported one.
// - but for PLONK only if the hash-to-field is the default one. If not, then it should fail.
assert := test.NewAssert(t)
circuit := &commitCircuit{}
assignment := &commitCircuit{A: 2, B: 3, Out: 6}
defaultOpts := []test.TestingOption{
test.WithCurves(ecc.BN254),
test.WithValidAssignment(assignment),
}
checkCircuit := func(assert *test.Assert, bid backend.ID, newHash func() hash.Hash) {
opts := append(defaultOpts,
test.WithBackends(bid),
test.WithProverOpts(
backend.WithProverHashToFieldFunction(newHash()),
),
test.WithVerifierOpts(
backend.WithVerifierHashToFieldFunction(newHash()),
),
test.WithSolidityExportOptions(solidity.WithHashToFieldFunction(newHash())),
)

assert.CheckCircuit(circuit, opts...)
}
// G16 success with explicitly set options
assert.Run(func(assert *test.Assert) {
checkCircuit(assert, backend.GROTH16, sha256.New)
}, "groth16", "sha256")
assert.Run(func(assert *test.Assert) {
checkCircuit(assert, backend.GROTH16, sha3.NewLegacyKeccak256)
}, "groth16", "keccak256")
// G16 success with using TargetSolidityVerifier
assert.Run(func(assert *test.Assert) {
opts := append(defaultOpts,
test.WithBackends(backend.GROTH16),
test.WithProverOpts(
solidity.WithProverTargetSolidityVerifier(backend.GROTH16),
),
test.WithVerifierOpts(
solidity.WithVerifierTargetSolidityVerifier(backend.GROTH16),
),
)
assert.CheckCircuit(circuit, opts...)
}, "groth16", "targetSolidityVerifier")
// G16 success without any options because we set default options already in
// assert.CheckCircuit if they are not set.
assert.Run(func(assert *test.Assert) {
opts := append(defaultOpts,
test.WithBackends(backend.GROTH16),
)
assert.CheckCircuit(circuit, opts...)
}, "groth16", "no-options")

// PLONK success with default options
assert.Run(func(assert *test.Assert) {
opts := append(defaultOpts,
test.WithBackends(backend.PLONK),
)
assert.CheckCircuit(circuit, opts...)
}, "plonk", "default")
// PLONK success with using TargetSolidityVerifier
assert.Run(func(assert *test.Assert) {
opts := append(defaultOpts,
test.WithBackends(backend.PLONK),
test.WithProverOpts(
solidity.WithProverTargetSolidityVerifier(backend.PLONK),
),
test.WithVerifierOpts(
solidity.WithVerifierTargetSolidityVerifier(backend.PLONK),
),
)
assert.CheckCircuit(circuit, opts...)
}, "plonk", "targetSolidityVerifier")
}

func TestTwoCommitments(t *testing.T) {
// should succeed with PLONK only.
// - but for PLONK only if the hash-to-field is the default one. If not, then it should fail.
assert := test.NewAssert(t)
circuit := &twoCommitCircuit{}
assignment := &twoCommitCircuit{A: 2, B: 3, Out: 6}
assert.CheckCircuit(circuit, test.WithCurves(ecc.BN254), test.WithValidAssignment(assignment), test.WithBackends(backend.PLONK))
}
Loading

0 comments on commit d651b89

Please sign in to comment.