Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
11dbe80
test: add small field test in checkcircuit
ivokub Jan 7, 2026
e143f8c
test: make small field testing optional
ivokub Jan 7, 2026
3bbd565
fix: include build tags
ivokub Jan 7, 2026
bdfca36
Update test/assert_checkcircuit.go
ivokub Jan 7, 2026
2f19e04
Update test/assert_options.go
ivokub Jan 7, 2026
85a2370
feat: add skip small field checks option
ivokub Jan 8, 2026
127ba9e
feat: add no curves option to disable any curve-based check
ivokub Jan 8, 2026
785e5dd
feat: add koalabear extension for internal use
ivokub Jan 8, 2026
d1cfdc8
docs: improve documentation
ivokub Jan 8, 2026
8d297c5
feat: allow initializing elements
ivokub Jan 8, 2026
d6f88c2
test: add koalabear specific tests
ivokub Jan 8, 2026
f165560
refactor: move fieldextension to internal package
ivokub Jan 8, 2026
870daaf
test: use extension 4 in nativecommit test
ivokub Jan 8, 2026
0a9e529
feat: implement logderivative argument over small fields
ivokub Jan 8, 2026
782dd05
fix: ensure all values are filled
ivokub Jan 9, 2026
03492e2
test: complete tests
ivokub Jan 9, 2026
4d5a2ba
test: use MustSetRandom
ivokub Jan 9, 2026
1ed4e0b
feat: implement logderivative argument also for width>1 tables
ivokub Jan 9, 2026
416225d
test: explicit smallfield check even when without smallfield build tag
ivokub Jan 9, 2026
031221a
fix: linter error
ivokub Jan 9, 2026
1394ef3
Merge branch 'master' into feat/internal-koalabear-extension
ivokub Jan 9, 2026
14c9a45
fix: option
ivokub Jan 9, 2026
d8a70e3
Merge branch 'feat/internal-koalabear-extension' into feat/logderivat…
ivokub Jan 9, 2026
6e419ea
fix: option names
ivokub Jan 9, 2026
67ed00c
Merge branch 'master' into feat/logderivative-smallfield
ivokub Jan 11, 2026
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
116 changes: 92 additions & 24 deletions std/internal/logderivarg/logderivarg.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import (

"github.com/consensys/gnark/constraint/solver"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/internal/smallfields"
"github.com/consensys/gnark/std/internal/fieldextension"
"github.com/consensys/gnark/std/internal/mimc"
"github.com/consensys/gnark/std/multicommit"
)
Expand Down Expand Up @@ -115,35 +117,75 @@ func Build(api frontend.API, table Table, queries Table) error {
}
toCommit = append(toCommit, exps...)

multicommit.WithCommitment(api, func(api frontend.API, commitment frontend.Variable) error {
rowCoeffs, challenge := randLinearCoefficients(api, nbRow, commitment)
var lp frontend.Variable = 0
for i := range table {
tmp := api.DivUnchecked(exps[i], api.Sub(challenge, randLinearCombination(api, rowCoeffs, table[i])))
lp = api.Add(lp, tmp)
}
var rp frontend.Variable = 0
if !smallfields.IsSmallField(api.Compiler().Field()) {
// handle the commitment over large fields directly
multicommit.WithCommitment(api, func(api frontend.API, commitment frontend.Variable) error {
rowCoeffs, challenge := randLinearCoefficients(api, nbRow, commitment)
var lp frontend.Variable = 0
for i := range table {
tmp := api.DivUnchecked(exps[i], api.Sub(challenge, randLinearCombination(api, rowCoeffs, table[i])))
lp = api.Add(lp, tmp)
}
var rp frontend.Variable = 0

toInvert := make([]frontend.Variable, len(queries))
for i := range queries {
toInvert[i] = api.Sub(challenge, randLinearCombination(api, rowCoeffs, queries[i]))
}
toInvert := make([]frontend.Variable, len(queries))
for i := range queries {
toInvert[i] = api.Sub(challenge, randLinearCombination(api, rowCoeffs, queries[i]))
}

if bapi, ok := api.(frontend.BatchInverter); ok {
toInvert = bapi.BatchInvert(toInvert)
} else {
for i := range toInvert {
toInvert[i] = api.Inverse(toInvert[i])
if bapi, ok := api.(frontend.BatchInverter); ok {
toInvert = bapi.BatchInvert(toInvert)
} else {
for i := range toInvert {
toInvert[i] = api.Inverse(toInvert[i])
}
}
}

for i := range queries {
// tmp := api.Inverse(api.Sub(challenge, randLinearCombination(api, rowCoeffs, queries[i])))
rp = api.Add(rp, toInvert[i])
for i := range queries {
// tmp := api.Inverse(api.Sub(challenge, randLinearCombination(api, rowCoeffs, queries[i])))
rp = api.Add(rp, toInvert[i])
}
api.AssertIsEqual(lp, rp)
return nil
}, toCommit...)
} else {
// when the native field is small field, then we need to use WithWideCommitment
extapi, err := fieldextension.NewExtension(api)
if err != nil {
return fmt.Errorf("create field extension: %w", err)
}
api.AssertIsEqual(lp, rp)
return nil
}, toCommit...)
multicommit.WithWideCommitment(api, func(api frontend.API, commitment []frontend.Variable) error {
rowCoeffs, challenge := randLinearCofficientsExt(extapi, nbRow, fieldextension.Element(commitment))
var lp fieldextension.Element
tableEntriesExts := make([]fieldextension.Element, nbRow)
for i := range table {
for j := range tableEntriesExts {
tableEntriesExts[j] = extapi.AsExtensionVariable(table[i][j])
}
tableComb := randLinearCombinationExt(extapi, rowCoeffs, tableEntriesExts)
denom := extapi.Sub(challenge, tableComb)
denom = extapi.Inverse(denom)
Comment thread
ivokub marked this conversation as resolved.
expEntryExt := extapi.AsExtensionVariable(exps[i])
term := extapi.Mul(expEntryExt, denom)
lp = extapi.Add(lp, term)
}

var rp fieldextension.Element
queryEntryExts := make([]fieldextension.Element, nbRow)
for i := range queries {
for j := range queryEntryExts {
queryEntryExts[j] = extapi.AsExtensionVariable(queries[i][j])
}
queryEntryExt := randLinearCombinationExt(extapi, rowCoeffs, queryEntryExts)
denom := extapi.Sub(challenge, queryEntryExt)
denom = extapi.Inverse(denom)
rp = extapi.Add(rp, denom)
}
extapi.AssertIsEqual(lp, rp)
return nil
}, extapi.Degree(), toCommit...)
}

return nil
}

Expand All @@ -166,6 +208,20 @@ func randLinearCoefficients(api frontend.API, nbRow int, commitment frontend.Var
return rowCoeffs, commitment
}

func randLinearCofficientsExt(extapi fieldextension.Field, nbRow int, commitment fieldextension.Element) (rowCoeffs []fieldextension.Element, challenge fieldextension.Element) {
if nbRow == 1 {
// to avoid initializing the hasher.
return []fieldextension.Element{extapi.One()}, commitment
}
// we don't have a hash function over extensions yet. So we use 1, ch, ch^2, ...
rowCoeffs = make([]fieldextension.Element, nbRow)
rowCoeffs[0] = extapi.One()
for i := 1; i < nbRow; i++ {
rowCoeffs[i] = extapi.Mul(rowCoeffs[i-1], commitment)
}
return rowCoeffs, commitment
}

func randLinearCombination(api frontend.API, rowCoeffs []frontend.Variable, row []frontend.Variable) frontend.Variable {
if len(rowCoeffs) != len(row) {
panic("coefficient count mismatch")
Expand All @@ -177,6 +233,18 @@ func randLinearCombination(api frontend.API, rowCoeffs []frontend.Variable, row
return res
}

func randLinearCombinationExt(extapi fieldextension.Field, rowCoeffs []fieldextension.Element, row []fieldextension.Element) fieldextension.Element {
if len(rowCoeffs) != len(row) {
panic("coefficient count mismatch")
}
res := extapi.Zero()
for i := range rowCoeffs {
term := extapi.Mul(rowCoeffs[i], row[i])
res = extapi.Add(res, term)
}
return res
}

func countHint(m *big.Int, inputs []*big.Int, outputs []*big.Int) error {
if len(inputs) <= 2 {
return fmt.Errorf("at least two input required")
Expand Down
2 changes: 1 addition & 1 deletion std/lookup/logderivlookup/logderivlookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestLookup(t *testing.T) {
witness.Expected[i] = new(big.Int).Set(witness.Entries[q.Int64()].(*big.Int))
}

assert.CheckCircuit(&LookupCircuit{}, test.WithValidAssignment(&witness))
assert.CheckCircuit(&LookupCircuit{}, test.WithValidAssignment(&witness), test.WithSmallfieldCheck())
}

type LookupCircuitLarge struct {
Expand Down
9 changes: 1 addition & 8 deletions std/rangecheck/rangecheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,7 @@ func New(api frontend.API) frontend.Rangechecker {
return newCommitRangechecker(api)
}
if _, ok := api.(frontend.WideCommitter); ok {
// native field extension package does not support inversion for now which is required
// for the logderivate argument. However, we use wide committer only for small fields
// where the backend already knows how to range check (and should implement Rangechecker interface).
// So we can just panic here to detect the case when the backend does not implement
// the range checker interface.
//
// See https://github.com/Consensys/gnark/pull/1493
panic("wide committer does not support operations for range checking")
return newCommitRangechecker(api)
}
return plainChecker{api: api}
}
Expand Down
46 changes: 35 additions & 11 deletions std/rangecheck/rangecheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import (
"math/big"
"testing"

"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/frontend/cs/r1cs"
"github.com/consensys/gnark/test"
)

Expand All @@ -28,19 +26,45 @@ func TestCheck(t *testing.T) {
assert := test.NewAssert(t)
var err error
bits := 64
nbVals := 100000
nbVals := 100
bound := new(big.Int).Lsh(big.NewInt(1), uint(bits))
vals := make([]frontend.Variable, nbVals)
for i := range vals {
vals[i], err = rand.Int(rand.Reader, bound)
if err != nil {
t.Fatal(err)
}
assert.NoError(err)
}
witness := CheckCircuit{Vals: vals, bits: bits}
invalidVals := make([]frontend.Variable, nbVals)
for i := range invalidVals {
invalidVals[i], err = rand.Int(rand.Reader, bound)
assert.NoError(err)
invalidVals[i] = new(big.Int).Add(invalidVals[i].(*big.Int), bound)
}
witness := CheckCircuit{Vals: vals}
invalidWitness := CheckCircuit{Vals: invalidVals}
circuit := CheckCircuit{Vals: make([]frontend.Variable, len(vals)), bits: bits}
assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithInvalidAssignment(&invalidWitness), test.WithoutSmallfieldCheck())
}

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

var err error
bits := 20
nbVals := 100
bound := new(big.Int).Lsh(big.NewInt(1), uint(bits))
vals := make([]frontend.Variable, nbVals)
for i := range vals {
vals[i], err = rand.Int(rand.Reader, bound)
assert.NoError(err)
}
invalidVals := make([]frontend.Variable, nbVals)
for i := range invalidVals {
invalidVals[i], err = rand.Int(rand.Reader, bound)
assert.NoError(err)
invalidVals[i] = new(big.Int).Add(invalidVals[i].(*big.Int), bound)
}
witness := CheckCircuit{Vals: vals}
invalidWitness := CheckCircuit{Vals: invalidVals}
circuit := CheckCircuit{Vals: make([]frontend.Variable, len(vals)), bits: bits}
err = test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField())
assert.NoError(err)
_, err = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit, frontend.WithCompressThreshold(100))
assert.NoError(err)
assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithInvalidAssignment(&invalidWitness), test.WithoutCurveChecks(), test.WithSmallfieldCheck())
}
Loading