Skip to content
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e19129c
chore: gnark update
ivokub Nov 17, 2025
cb05285
chore: implement p256verify glue
ivokub Nov 17, 2025
332f497
fix: enforce public vars
ivokub Nov 18, 2025
7620614
test: add testdata generator
ivokub Nov 18, 2025
119bd71
feat: implement inputfiller
ivokub Nov 18, 2025
dae2651
test: do not write noop test cases
ivokub Nov 18, 2025
d4094d3
test: implement unit tests
ivokub Nov 18, 2025
2aa908b
test: include generated tests
ivokub Nov 18, 2025
1ae0acc
docs: refer to test vector location
ivokub Nov 18, 2025
bdfa261
chore: gnark update
ivokub Nov 19, 2025
0aab08d
docs: circuit size
ivokub Nov 19, 2025
abedf2e
fix: correct instance number calculation
ivokub Nov 19, 2025
81d6377
feat: loat p256 module
ivokub Nov 19, 2025
1cf0482
Merge branch 'feat/bls-glue' into feat/secp256r1
ivokub Nov 19, 2025
03437cf
feat: define p256 input instance count
ivokub Nov 19, 2025
9c19167
fix: ensure assignment is called
ivokub Nov 19, 2025
7b78570
chore: gnark update to master
ivokub Nov 24, 2025
d506919
fix: column names
ivokub Nov 24, 2025
8fa0879
Merge branch 'feat/bls-glue' into feat/secp256r1
ivokub Nov 24, 2025
4fbdc96
chore: connect zkevm p256 module with circuit
ivokub Nov 24, 2025
f6d1ddf
Merge branch 'feat/bls-glue' into feat/secp256r1
ivokub Nov 25, 2025
766211d
feat: implement p256 circuits serialization types in registry
ivokub Nov 25, 2025
984921e
fix: export types for serialization
ivokub Nov 25, 2025
ca69bc9
Merge branch 'feat/bls-glue' into feat/secp256r1
ivokub Nov 25, 2025
798d22c
Merge branch 'feat/bls-glue' into feat/secp256r1
ivokub Nov 25, 2025
20af8d8
fix: correct order of point coordinates in inputfiller
ivokub Nov 27, 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
4 changes: 4 additions & 0 deletions prover/protocol/serialization/implementation_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import (
"github.com/consensys/linea-monorepo/prover/zkevm/prover/hash/packing"
"github.com/consensys/linea-monorepo/prover/zkevm/prover/hash/sha2"
"github.com/consensys/linea-monorepo/prover/zkevm/prover/modexp"
"github.com/consensys/linea-monorepo/prover/zkevm/prover/p256verify"

ded "github.com/consensys/linea-monorepo/prover/zkevm/prover/hash/packing/dedicated"
"github.com/consensys/linea-monorepo/prover/zkevm/prover/hash/packing/dedicated/spaghettifier"
Expand Down Expand Up @@ -179,6 +180,9 @@ func init() {
RegisterImplementation(bls.MultiPointEvalCircuit{})
RegisterImplementation(bls.MultiPointEvalFailureCircuit{})

// P256 verify circuit implementations
RegisterImplementation(p256verify.MultiP256VerifyInstanceCircuit{})

// Dedicated and common types
RegisterImplementation(byte32cmp.MultiLimbCmp{})
RegisterImplementation(byte32cmp.OneLimbCmpCtx{})
Expand Down
4 changes: 4 additions & 0 deletions prover/zkevm/full.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/consensys/linea-monorepo/prover/zkevm/prover/hash/keccak"
"github.com/consensys/linea-monorepo/prover/zkevm/prover/hash/sha2"
"github.com/consensys/linea-monorepo/prover/zkevm/prover/modexp"
"github.com/consensys/linea-monorepo/prover/zkevm/prover/p256verify"
"github.com/consensys/linea-monorepo/prover/zkevm/prover/statemanager"
"github.com/consensys/linea-monorepo/prover/zkevm/prover/statemanager/accumulator"
)
Expand Down Expand Up @@ -250,6 +251,9 @@ func FullZKEVMWithSuite(tl *config.TracesLimits, suite CompilationSuite, cfg *co
NbPointEvalInputInstances: NbInputPerInstanceBLSPointEval,
NbPointEvalFailureInputInstances: NbInputPerInstanceBLSPointEvalFailure,
},
P256Verify: p256verify.Limits{
NbInputInstances: NbInputPerInstanceP256Verify,
},
}

// Initialize the Full zkEVM arithmetization
Expand Down
99 changes: 99 additions & 0 deletions prover/zkevm/prover/p256verify/circuit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package p256verify

import (
"fmt"

"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/std/evmprecompiles"
"github.com/consensys/gnark/std/math/bitslice"
"github.com/consensys/gnark/std/math/emulated"
"github.com/consensys/gnark/std/math/emulated/emparams"
)

const (
nbBits = 128 // for large-field we use 128-bit limbs both for base and scalar fields
nbBytes = nbBits / 8

nbFrLimbs = 2 // P-256 scalar field represented with 2 limbs of 128 bits
nbFpLimbs = 2 // P-256 base field represented with 2 limbs of 128 bits
nbResLimbs = 2

nbG1Limbs = 2 * nbFpLimbs // (Ax, Ay)

nbRows = 3*nbFrLimbs + nbG1Limbs + nbResLimbs // msg
)

type scalarfield = emparams.P256Fr
type basefield = emparams.P256Fp

var fpParams basefield
var frParams scalarfield

type scalarElementWizard struct {
S [nbFrLimbs]frontend.Variable
}

func (c scalarElementWizard) ToElement(api frontend.API, fr *emulated.Field[scalarfield]) *emulated.Element[scalarfield] {
Slimbs := make([]frontend.Variable, frParams.NbLimbs())
Slimbs[2], Slimbs[3] = bitslice.Partition(api, c.S[0], 64, bitslice.WithNbDigits(128))
Slimbs[0], Slimbs[1] = bitslice.Partition(api, c.S[1], 64, bitslice.WithNbDigits(128))
return fr.NewElement(Slimbs)
}

type baseElementWizard struct {
P [nbFpLimbs]frontend.Variable
}

func (c baseElementWizard) ToElement(api frontend.API, fp *emulated.Field[basefield]) *emulated.Element[basefield] {
Plimbs := make([]frontend.Variable, fpParams.NbLimbs())
Plimbs[2], Plimbs[3] = bitslice.Partition(api, c.P[0], 64, bitslice.WithNbDigits(128))
Plimbs[0], Plimbs[1] = bitslice.Partition(api, c.P[1], 64, bitslice.WithNbDigits(128))
return fp.NewElement(Plimbs)
}

type P256VerifyInstance struct {
H scalarElementWizard `gnark:",public"`
R scalarElementWizard `gnark:",public"`
S scalarElementWizard `gnark:",public"`
Qx baseElementWizard `gnark:",public"`
Qy baseElementWizard `gnark:",public"`
Expected [nbResLimbs]frontend.Variable `gnark:",public"`
}

type multiP256VerifyInstanceCircuit struct {
Instances []P256VerifyInstance
}

func (c *multiP256VerifyInstanceCircuit) Define(api frontend.API) error {
scalarApi, err := emulated.NewField[scalarfield](api)
if err != nil {
return fmt.Errorf("new scalar field: %w", err)
}
baseApi, err := emulated.NewField[basefield](api)
if err != nil {
return fmt.Errorf("new base field: %w", err)
}

nbInstances := len(c.Instances)
for i := 0; i < nbInstances; i++ {
h := c.Instances[i].H.ToElement(api, scalarApi)
r := c.Instances[i].R.ToElement(api, scalarApi)
s := c.Instances[i].S.ToElement(api, scalarApi)
qx := c.Instances[i].Qx.ToElement(api, baseApi)
qy := c.Instances[i].Qy.ToElement(api, baseApi)
// the high limb of the result is always zero
api.AssertIsEqual(c.Instances[i].Expected[0], 0)
// the expected result should be boolean
expected := c.Instances[i].Expected[1]
api.AssertIsBoolean(expected)
res := evmprecompiles.P256Verify(api, h, r, s, qx, qy)
api.AssertIsEqual(res, expected)
}
return nil
}

func newP256VerifyCircuit(limits *Limits) frontend.Circuit {
return &multiP256VerifyInstanceCircuit{
Instances: make([]P256VerifyInstance, limits.NbInputInstances),
}
}
34 changes: 34 additions & 0 deletions prover/zkevm/prover/p256verify/input_filler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package p256verify

import (
"github.com/consensys/linea-monorepo/prover/maths/field"
"github.com/consensys/linea-monorepo/prover/protocol/dedicated/plonk"
)

const (
input_filler_key = "p256-verify-input-filler"
)

func init() {
plonk.RegisterInputFiller(input_filler_key, inputFiller)
}

func inputFiller(circuitInstance, inputIndex int) field.Element {
datas := []string{
// h
"0", "0",
// r
"0", "1",
// s
"0", "1",
// qx
"0x77037d812deb33a0f4a13945d898c296",
"0x6b17d1f2e12c4247f8bce6e563a440f2",
// qy
"0x2bce33576b315ececbb6406837bf51f5",
"0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e16",
// expected result
"0", "0",
}
return field.NewFromString(datas[inputIndex%nbRows])
}
10 changes: 10 additions & 0 deletions prover/zkevm/prover/p256verify/limits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package p256verify

// Limits defines limits for P256Verify module.
type Limits struct {
// NbInputInstances is the number of P256 input instances per a single
// verification circuit. gnark circuit size approximately 709k constraints
// and in Plonk-in-Wizard (with externalized range checks) approximately
// 183k rows per instance.
NbInputInstances int
}
84 changes: 84 additions & 0 deletions prover/zkevm/prover/p256verify/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package p256verify

import (
"fmt"

"github.com/consensys/linea-monorepo/prover/protocol/dedicated/plonk"
"github.com/consensys/linea-monorepo/prover/protocol/ifaces"
"github.com/consensys/linea-monorepo/prover/protocol/query"
"github.com/consensys/linea-monorepo/prover/protocol/wizard"
)

const (
NAME_P256_VERIFY = "P256_VERIFY"
moduleName = "ecdata"
ROUND_NR = 0
)

func colNameFn(colName string) ifaces.ColID {
return ifaces.ColID(fmt.Sprintf("%s.%s", moduleName, colName))
}

type P256VerifyDataSource struct {
ID ifaces.Column
CS ifaces.Column
Limb ifaces.Column
Index ifaces.Column
IsData ifaces.Column
IsResult ifaces.Column
}

func newP256VerifyDataSource(comp *wizard.CompiledIOP) *P256VerifyDataSource {
return &P256VerifyDataSource{
ID: comp.Columns.GetHandle(colNameFn("ID")),
CS: comp.Columns.GetHandle(colNameFn("CIRCUIT_SELECTOR_P256_VERIFY")),
Limb: comp.Columns.GetHandle(colNameFn("LIMB")),
Index: comp.Columns.GetHandle(colNameFn("INDEX")),
IsData: comp.Columns.GetHandle(colNameFn("IS_P256_VERIFY_DATA")),
IsResult: comp.Columns.GetHandle(colNameFn("IS_P256_VERIFY_RESULT")),
}
}

type P256Verify struct {
*P256VerifyDataSource
P256VerifyGnarkData *plonk.Alignment
*Limits
}

func newP256Verify(_ *wizard.CompiledIOP, limits *Limits, src *P256VerifyDataSource) *P256Verify {
res := &P256Verify{
P256VerifyDataSource: src,
Limits: limits,
}

return res
}

func (pv *P256Verify) WithCircuit(comp *wizard.CompiledIOP, options ...query.PlonkOption) *P256Verify {
nbRowsPerCircuit := nbRows * pv.Limits.NbInputInstances
maxNbCircuits := (pv.P256VerifyDataSource.CS.Size() + nbRowsPerCircuit - 1) / nbRowsPerCircuit

toAlign := &plonk.CircuitAlignmentInput{
Name: fmt.Sprintf("%s_ALIGNMENT", NAME_P256_VERIFY),
Round: ROUND_NR,
DataToCircuitMask: pv.P256VerifyDataSource.CS,
DataToCircuit: pv.P256VerifyDataSource.Limb,
Circuit: newP256VerifyCircuit(pv.Limits),
NbCircuitInstances: maxNbCircuits,
PlonkOptions: options,
InputFillerKey: input_filler_key,
}
pv.P256VerifyGnarkData = plonk.DefineAlignment(comp, toAlign)
return pv
}

func (pv *P256Verify) Assign(run *wizard.ProverRuntime) {
if pv.P256VerifyGnarkData != nil {
pv.P256VerifyGnarkData.Assign(run)
}
}

func NewP256VerifyZkEvm(comp *wizard.CompiledIOP, limits *Limits) *P256Verify {
return newP256Verify(comp, limits, newP256VerifyDataSource(comp)).
WithCircuit(comp, query.PlonkRangeCheckOption(16, 6, true))
}
71 changes: 71 additions & 0 deletions prover/zkevm/prover/p256verify/module_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package p256verify

import (
"errors"
"os"
"testing"

"github.com/consensys/linea-monorepo/prover/protocol/compiler/dummy"
"github.com/consensys/linea-monorepo/prover/protocol/query"
"github.com/consensys/linea-monorepo/prover/protocol/wizard"
"github.com/consensys/linea-monorepo/prover/utils/csvtraces"
)

func testP256Verify(t *testing.T, withCircuit bool, path string, limits *Limits) {
f, err := os.Open(path)
if errors.Is(err, os.ErrNotExist) {
t.Fatal("csv file does not exist, please run `go generate` to generate the test data")
}
if err != nil {
t.Fatal("failed to open csv file", err)
}
defer f.Close()
ct, err := csvtraces.NewCsvTrace(f)
if err != nil {
t.Fatal("failed to create csv trace", err)
}
var p256Verify *P256Verify
cmp := wizard.Compile(
func(b *wizard.Builder) {
p256VerifySource := &P256VerifyDataSource{
ID: ct.GetCommit(b, "ID"),
CS: ct.GetCommit(b, "CIRCUIT_SELECTOR_P256_VERIFY"),
Limb: ct.GetCommit(b, "LIMB"),
Index: ct.GetCommit(b, "INDEX"),
IsData: ct.GetCommit(b, "DATA_P256_VERIFY_FLAG"),
IsResult: ct.GetCommit(b, "RSLT_P256_VERIFY_FLAG"),
}
p256Verify = newP256Verify(b.CompiledIOP, limits, p256VerifySource)
if withCircuit {
p256Verify = p256Verify.
WithCircuit(b.CompiledIOP, query.PlonkRangeCheckOption(16, 6, true))
}
},
dummy.Compile,
)

proof := wizard.Prove(cmp,
func(run *wizard.ProverRuntime) {
ct.Assign(run, "ID", "CIRCUIT_SELECTOR_P256_VERIFY", "LIMB", "INDEX", "DATA_P256_VERIFY_FLAG", "RSLT_P256_VERIFY_FLAG")
p256Verify.Assign(run)
})

if err := wizard.Verify(cmp, proof); err != nil {
t.Fatal("proof failed", err)
}
t.Log("proof succeeded")
}

func TestP256VerifyNoCircuit(t *testing.T) {
limits := &Limits{
NbInputInstances: 6,
}
testP256Verify(t, false, "testdata/p256verify_inputs.csv", limits)
}

func TestP256VerifyWithCircuit(t *testing.T) {
limits := &Limits{
NbInputInstances: 6,
}
testP256Verify(t, true, "testdata/p256verify_inputs.csv", limits)
}
4 changes: 4 additions & 0 deletions prover/zkevm/prover/p256verify/serialization_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package p256verify

// Type alias for serialization registration
type MultiP256VerifyInstanceCircuit = multiP256VerifyInstanceCircuit
Loading
Loading