Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ee63153
feat: make init hook field-aware
ivokub May 6, 2025
46e41ef
feat: add DynamicFieldParams interface
ivokub May 6, 2025
827ce81
feat: make ValueOf asynchronous for field-aware init
ivokub May 8, 2025
7efdc84
feat: ensure all fields are copied
ivokub May 8, 2025
1a145f6
feat: use effective number of limbs and width
ivokub May 9, 2025
c10acdf
refactor: use NewElement instead of ValueOf everywhere
ivokub May 9, 2025
1d00a5b
feat: use actual number of limbs for GLV hint
ivokub May 9, 2025
4e4d4a5
fix: GLV scalar mul for constant inputs
ivokub May 9, 2025
3edf439
feat: update stats
ivokub May 13, 2025
271e4f8
feat: add field-extension package
ivokub Dec 3, 2024
0f57252
feat: add utility methods to field extension
ivokub Dec 9, 2024
32e2ecc
refactor: use interface instead of type
ivokub May 9, 2025
2390cb6
fix: use correct sign
ivokub May 6, 2025
7eef0a6
feat: perform checks in option
ivokub May 6, 2025
21e93cd
docs: add method documentation
ivokub May 6, 2025
8bfcfbf
chore: use new style iterator
ivokub May 6, 2025
e987ac6
refactor: use big.Int for defining extension
ivokub May 6, 2025
ed75906
fix: do not underflow mul if zero by zero
ivokub May 6, 2025
a63dfd9
Merge branch 'feat/native-field-extension' into feat/widecommitter
ivokub May 16, 2025
5e6e370
feat: add experimental WideCommitter interface
ivokub Dec 3, 2024
6e660ff
feat: implement wide commitment for multicommit
ivokub Dec 3, 2024
f6be38a
test: add WideCommit to test engine
ivokub Dec 3, 2024
96b0d94
refactor: remove unused test engine option
ivokub May 16, 2025
5dce4df
feat: explicitly panic commit for small fields
ivokub May 16, 2025
aaae50a
docs: add comments for logderivarg with widecommit
ivokub May 16, 2025
43261ff
Merge branch 'feat/widecommitter' into feat/emulation-over-smallfield
ivokub May 16, 2025
9eccdf0
Merge branch 'feat/field-aware-init' into feat/emulation-over-smallfield
ivokub May 16, 2025
f83e223
chore: fix typo
ivokub May 16, 2025
1d314a1
feat: initialize mulchecks in field extensioifen small native field
ivokub Dec 9, 2024
7c4f993
chore: remove unused field
ivokub Dec 9, 2024
64b4570
chore: avoid initializing hasher when 1-column logderivarg
ivokub Dec 10, 2024
0f5399c
test: field emulation circuits over small fields
ivokub Nov 25, 2024
4c28460
test: add internal mocked widecommitter for testing
ivokub May 19, 2025
7a1d216
test: use commit or widecommit for tests depending on field
ivokub May 19, 2025
73ad63d
test: wrap builder to mock widecommitter when needed
ivokub May 19, 2025
4884f8a
test: multicommit with widecommitment test
ivokub May 19, 2025
c62f681
test: skip the commit circuit test for consistency check
ivokub May 19, 2025
dbebbe9
Merge branch 'feat/widecommitter' into feat/emulation-over-smallfield
ivokub May 19, 2025
2ef6add
Merge branch 'master' into feat/emulation-over-smallfield
ivokub May 20, 2025
6e6aba7
fix: return at least one value in the mocked range check
ivokub May 20, 2025
a6b7821
chore: do not create temporary variable for type assertion
ivokub May 20, 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
154 changes: 148 additions & 6 deletions internal/smallfields/circuits_test.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
package smallfields_test

import (
"crypto/rand"
"fmt"
"math/big"
"testing"

"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark-crypto/ecc/bn254"
"github.com/consensys/gnark-crypto/field/babybear"
"github.com/consensys/gnark-crypto/field/goldilocks"
"github.com/consensys/gnark-crypto/field/koalabear"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/frontend/cs/r1cs"
"github.com/consensys/gnark/frontend/cs/scs"
"github.com/consensys/gnark/internal/smallfields/tinyfield"
"github.com/consensys/gnark/internal/widecommitter"
"github.com/consensys/gnark/std/algebra/emulated/sw_bn254"
"github.com/consensys/gnark/std/math/emulated"
"github.com/consensys/gnark/std/math/emulated/emparams"
"github.com/consensys/gnark/test"
)

var testSmallField = koalabear.Modulus()

type NativeCircuit struct {
A frontend.Variable `gnark:",public"`
B frontend.Variable `gnark:",secret"`
Expand All @@ -31,7 +39,6 @@ var testCases = []struct {
modulus *big.Int
supportsCompile bool
}{
{"goldilocks", goldilocks.Modulus(), false},
{"tinyfield", tinyfield.Modulus(), true},
{"babybear", babybear.Modulus(), true},
{"koalabear", koalabear.Modulus(), true},
Expand Down Expand Up @@ -59,9 +66,8 @@ func TestNativeCircuitCompileAndSolve(t *testing.T) {
assignment := &NativeCircuit{A: 2, B: 4}
wit, err := frontend.NewWitness(assignment, tc.modulus)
assert.NoError(err)
solution, err := ccs.Solve(wit)
err = ccs.IsSolved(wit)
assert.NoError(err)
_ = solution

}, fmt.Sprintf("ccs=r1cs/field=%s", tc.name))
assert.Run(func(assert *test.Assert) {
Expand All @@ -70,9 +76,145 @@ func TestNativeCircuitCompileAndSolve(t *testing.T) {
assignment := &NativeCircuit{A: 2, B: 4}
wit, err := frontend.NewWitness(assignment, tc.modulus)
assert.NoError(err)
solution, err := ccs.Solve(wit)
err = ccs.IsSolved(wit)
assert.NoError(err)
_ = solution
}, fmt.Sprintf("ccs=scs/field=%s", tc.name))
}
}

type EmulatedCircuit[T emulated.FieldParams] struct {
A emulated.Element[T] `gnark:",public"`
B emulated.Element[T] `gnark:",secret"`
}

func (c *EmulatedCircuit[T]) Define(api frontend.API) error {
f, err := emulated.NewField[T](api)
if err != nil {
return err
}
res := f.Mul(&c.A, &c.A)
f.AssertIsEqual(res, &c.B)
return nil
}

func TestEmulatedCircuit(t *testing.T) {
assert := test.NewAssert(t)

a, err := rand.Int(rand.Reader, emparams.BN254Fp{}.Modulus())
assert.NoError(err)
b := new(big.Int).Mul(a, a)
b.Mod(b, emparams.BN254Fp{}.Modulus())

err = test.IsSolved(&EmulatedCircuit[emparams.BN254Fp]{}, &EmulatedCircuit[emparams.BN254Fp]{A: emulated.ValueOf[emparams.BN254Fp](a), B: emulated.ValueOf[emparams.BN254Fp](b)}, ecc.BN254.ScalarField())
assert.NoError(err)

err = test.IsSolved(&EmulatedCircuit[emparams.BN254Fp]{}, &EmulatedCircuit[emparams.BN254Fp]{A: emulated.ValueOf[emparams.BN254Fp](a), B: emulated.ValueOf[emparams.BN254Fp](b)}, testSmallField)
assert.NoError(err)

// assert that when the compiled doesn't have specific support for small fields (rangechecker and widecommit), then it would fail
err = test.IsSolved(&EmulatedCircuit[emparams.BN254Fp]{}, &EmulatedCircuit[emparams.BN254Fp]{A: emulated.ValueOf[emparams.BN254Fp](a), B: emulated.ValueOf[emparams.BN254Fp](b)}, testSmallField, test.WithNoSmallFieldCompatibility())
assert.Error(err)
}

func TestCompileEmulatedCircuit(t *testing.T) {
assert := test.NewAssert(t)
f := testSmallField

assignment := &EmulatedCircuit[emparams.BN254Fp]{A: emulated.ValueOf[emparams.BN254Fp](2), B: emulated.ValueOf[emparams.BN254Fp](4)}

ccs, err := frontend.CompileU32(f, widecommitter.From(scs.NewBuilder), &EmulatedCircuit[emparams.BN254Fp]{})
assert.NoError(err)

w, err := frontend.NewWitness(assignment, f)
assert.NoError(err)

err = ccs.IsSolved(w)
assert.NoError(err)

ccs2, err := frontend.CompileU32(f, widecommitter.From(r1cs.NewBuilder), &EmulatedCircuit[emparams.BN254Fp]{})
assert.NoError(err)

err = ccs2.IsSolved(w)
assert.NoError(err)

// ensure the compilation fails in case we compile over a small field but we don't have rangechecker and widecommit support
_, err = frontend.CompileU32(f, scs.NewBuilder, &EmulatedCircuit[emparams.BN254Fp]{})
assert.Error(err)
_, err = frontend.CompileU32(f, r1cs.NewBuilder, &EmulatedCircuit[emparams.BN254Fp]{})
assert.Error(err)
}

type PairCircuit struct {
InG1 sw_bn254.G1Affine
InG2 sw_bn254.G2Affine
Res sw_bn254.GTEl
}

func (c *PairCircuit) Define(api frontend.API) error {
pairing, err := sw_bn254.NewPairing(api)
if err != nil {
return fmt.Errorf("new pairing: %w", err)
}
pairing.AssertIsOnG1(&c.InG1)
pairing.AssertIsOnG2(&c.InG2)
res, err := pairing.Pair([]*sw_bn254.G1Affine{&c.InG1}, []*sw_bn254.G2Affine{&c.InG2})
if err != nil {
return fmt.Errorf("pair: %w", err)
}
pairing.AssertIsEqual(res, &c.Res)
return nil
}

func TestPairTestSolve(t *testing.T) {
assert := test.NewAssert(t)
p, q := randomG1G2Affines()
res, err := bn254.Pair([]bn254.G1Affine{p}, []bn254.G2Affine{q})
assert.NoError(err)
witness := PairCircuit{
InG1: sw_bn254.NewG1Affine(p),
InG2: sw_bn254.NewG2Affine(q),
Res: sw_bn254.NewGTEl(res),
}
err = test.IsSolved(&PairCircuit{}, &witness, testSmallField)
assert.NoError(err)

ccs, err := frontend.CompileU32(testSmallField, widecommitter.From(scs.NewBuilder), &PairCircuit{})
assert.NoError(err)

w, err := frontend.NewWitness(&witness, testSmallField)
assert.NoError(err)

err = ccs.IsSolved(w)
assert.NoError(err)

ccs2, err := frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, &PairCircuit{})
assert.NoError(err)
// we define it again as the field is different
witness = PairCircuit{
InG1: sw_bn254.NewG1Affine(p),
InG2: sw_bn254.NewG2Affine(q),
Res: sw_bn254.NewGTEl(res),
}
w2, err := frontend.NewWitness(&witness, ecc.BLS12_377.ScalarField())
assert.NoError(err)
err = ccs2.IsSolved(w2)
assert.NoError(err)
}

func randomG1G2Affines() (bn254.G1Affine, bn254.G2Affine) {
_, _, G1AffGen, G2AffGen := bn254.Generators()
mod := bn254.ID.ScalarField()
s1, err := rand.Int(rand.Reader, mod)
if err != nil {
panic(err)
}
s2, err := rand.Int(rand.Reader, mod)
if err != nil {
panic(err)
}
var p bn254.G1Affine
p.ScalarMultiplication(&G1AffGen, s1)
var q bn254.G2Affine
q.ScalarMultiplication(&G2AffGen, s2)
return p, q
}
2 changes: 1 addition & 1 deletion internal/widecommitter/widecommitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (w *wrappedBuilder) Compiler() frontend.Compiler {
}

func (w *wrappedBuilder) Check(in frontend.Variable, width int) {
_, err := w.NewHint(mockedRangecheckHint, 0, width, in)
_, err := w.NewHint(mockedRangecheckHint, 1, width, in)
if err != nil {
panic(fmt.Sprintf("failed to check range: %v", err))
}
Expand Down
4 changes: 4 additions & 0 deletions std/internal/logderivarg/logderivarg.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ func Build(api frontend.API, table Table, queries Table) error {
}

func randLinearCoefficients(api frontend.API, nbRow int, commitment frontend.Variable) (rowCoeffs []frontend.Variable, challenge frontend.Variable) {
if nbRow == 1 {
// to avoid initializing the hasher.
return []frontend.Variable{1}, commitment
}
hasher, err := mimc.NewMiMC(api)
if err != nil {
panic(err)
Expand Down
30 changes: 20 additions & 10 deletions std/math/emulated/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import (

"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/internal/kvstore"
"github.com/consensys/gnark/internal/smallfields"
"github.com/consensys/gnark/internal/utils"
"github.com/consensys/gnark/logger"
limbs "github.com/consensys/gnark/std/internal/limbcomposition"
"github.com/consensys/gnark/std/math/fieldextension"
"github.com/consensys/gnark/std/rangecheck"
"github.com/rs/zerolog"
"golang.org/x/exp/constraints"
Expand All @@ -23,6 +25,8 @@ import (
type Field[T FieldParams] struct {
// api is the native API
api frontend.API
// extensionApi is the extension API when we need to perform multiplication checks over the extension field
extensionApi fieldextension.Field

// fParams carries the ring parameters
fParams staticFieldParams[T]
Expand All @@ -32,16 +36,14 @@ type Field[T FieldParams] struct {
maxOfOnce sync.Once

// constants for often used elements n, 0 and 1. Allocated only once
nConstOnce sync.Once
nConst *Element[T]
nprevConstOnce sync.Once
nprevConst *Element[T]
zeroConstOnce sync.Once
zeroConst *Element[T]
oneConstOnce sync.Once
oneConst *Element[T]
shortOneConstOnce sync.Once
shortOneConst *Element[T]
nConstOnce sync.Once
nConst *Element[T]
nprevConstOnce sync.Once
nprevConst *Element[T]
zeroConstOnce sync.Once
zeroConst *Element[T]
oneConstOnce sync.Once
oneConst *Element[T]

log zerolog.Logger

Expand Down Expand Up @@ -70,6 +72,14 @@ func NewField[T FieldParams](native frontend.API) (*Field[T], error) {
checker: rangecheck.New(native),
fParams: newStaticFieldParams[T](native.Compiler().Field()),
}
if smallfields.IsSmallField(native.Compiler().Field()) {
f.log.Debug().Msg("using small native field, multiplication checks will be performed in extension field")
extapi, err := fieldextension.NewExtension(native)
if err != nil {
return nil, fmt.Errorf("extension field: %w", err)
}
f.extensionApi = extapi
}

// ensure prime is correctly set
if f.fParams.IsPrime() {
Expand Down
Loading