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

fix(ecpair): ensure last non-trivial input pair goes to ML+finalexp circuit #524

Merged
merged 24 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
faa7b80
fix: set inputresult only once
ivokub Jan 8, 2025
2e1b088
fix: copy only actual inputs going to pairing (no trivial pairs)
ivokub Jan 8, 2025
784422b
fix: non-exclusive G2 membership or pairing check
ivokub Jan 8, 2025
60ee51c
fix: enable index consistency check when interleaved G2 membership
ivokub Jan 9, 2025
41f707c
fix: do not project pair IDs and total pairings
ivokub Jan 9, 2025
6ac9b1b
fix: add constraint to check totalpairs correctness
ivokub Jan 10, 2025
fc4e689
test: add IS_RESULT column to test cases and uniform reformat
ivokub Jan 10, 2025
eedb7e5
feat: allow outputting CSV in hex
ivokub Jan 10, 2025
37edba8
feat: add utility method to write the module
ivokub Jan 10, 2025
d163adb
test: check also IS_RESULT in test cases
ivokub Jan 10, 2025
04c609f
test: add test case modules
ivokub Jan 10, 2025
1aaddda
test: add regression test case
ivokub Jan 10, 2025
5a956ba
feat: add testdata generator for ecpair
ivokub Jan 13, 2025
a84deeb
feat: add tests for manual generation of EC edge cases
ivokub Jan 13, 2025
9a09764
fix: accumulator consistency assignment in case of one valid point
ivokub Jan 14, 2025
a5890ab
docs: describe assignment
ivokub Jan 14, 2025
8183cb7
fix: indexing when non-sequential valid points
ivokub Jan 14, 2025
874ec8e
test: run tests on generated data
ivokub Jan 14, 2025
41674db
feat: generate all cases for 1-5 input pairs
ivokub Jan 15, 2025
1f9df02
test: skip long tests
ivokub Jan 15, 2025
325da32
fix: test imports
ivokub Jan 15, 2025
2dc66f3
enable ecpair
gusiri Jan 16, 2025
5150b25
Merge branch 'main' into fix/ecpair-last-finalexp
gusiri Jan 16, 2025
cc93af0
perf: parallelize testcase testing
ivokub Jan 16, 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
13 changes: 12 additions & 1 deletion prover/utils/csvtraces/csvtraces.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type cfg struct {
nbRows int
skipPrePaddingZero bool
filterOn ifaces.Column
inHex bool
}

type Option func(*cfg) error
Expand Down Expand Up @@ -47,6 +48,12 @@ func FilterOn(col ifaces.Column) Option {
}
}

// InHex sets the CSV printer to print the values in hexadecimal
func InHex(c *cfg) error {
c.inHex = true
return nil
}

type CsvTrace struct {
mapped map[string][]field.Element

Expand Down Expand Up @@ -110,7 +117,11 @@ func FmtCsv(w io.Writer, run *wizard.ProverRuntime, cols []ifaces.Column, option
}

if assignment[c][r].IsUint64() {
fmtVals = append(fmtVals, assignment[c][r].String())
if cfg.inHex {
fmtVals = append(fmtVals, "0x"+assignment[c][r].Text(16))
} else {
fmtVals = append(fmtVals, assignment[c][r].String())
}
continue
}

Expand Down
5 changes: 4 additions & 1 deletion prover/zkevm/prover/ecpair/ecpair.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,12 @@ func newECPair(comp *wizard.CompiledIOP, limits *Limits, ecSource *ECPairSource)
res.csInstanceIDChangeWhenNewInstance(comp)
res.csAccumulatorInit(comp)
res.csAccumulatorConsistency(comp)
res.csTotalPairs(comp)
res.csLastPairToFinalExp(comp)
res.csIndexConsistency(comp)
res.csAccumulatorMask(comp)
// only Unaligned Pairing data or G2 membership data is active at a time
res.csExclusiveUnalignedDatas(comp)
res.csPairingDataOrMembershipActive(comp)
// only to Miller loop or to FinalExp
res.csExclusivePairingCircuitMasks(comp)

Expand Down Expand Up @@ -241,6 +242,7 @@ type UnalignedPairingData struct {
ToMillerLoopCircuitMask ifaces.Column
ToFinalExpCircuitMask ifaces.Column

IsResultOfInstance ifaces.Column
IsFirstLineOfInstance ifaces.Column
IsFirstLineOfPrevAccumulator ifaces.Column
IsFirstLineOfCurrAccumulator ifaces.Column
Expand All @@ -263,6 +265,7 @@ func newUnalignedPairingData(comp *wizard.CompiledIOP, limits *Limits) *Unaligne
IsFirstLineOfInstance: createCol("IS_FIRST_LINE_OF_INSTANCE"),
IsFirstLineOfPrevAccumulator: createCol("IS_FIRST_LINE_OF_PREV_ACC"),
IsFirstLineOfCurrAccumulator: createCol("IS_FIRST_LINE_OF_CURR_ACC"),
IsResultOfInstance: createCol("IS_RESULT"),
IsAccumulatorPrev: createCol("IS_ACCUMULATOR_PREV"),
IsAccumulatorCurr: createCol("IS_ACCUMULATOR_CURR"),
IsAccumulatorInit: createCol("IS_ACCUMULATOR_INIT"),
Expand Down
109 changes: 91 additions & 18 deletions prover/zkevm/prover/ecpair/ecpair_assignment.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,18 @@ func (ec *ECPair) assignPairingData(run *wizard.ProverRuntime) {
dstIndex = common.NewVectorBuilder(ec.UnalignedPairingData.Index)
dstIsFirstPrev = common.NewVectorBuilder(ec.UnalignedPairingData.IsFirstLineOfPrevAccumulator)
dstIsFirstCurr = common.NewVectorBuilder(ec.UnalignedPairingData.IsFirstLineOfCurrAccumulator)
dstIsResult = common.NewVectorBuilder(ec.UnalignedPairingData.IsResultOfInstance)
)

for currPos := 0; currPos < len(srcLimbs); {
// we need to check if the current position is a pairing or not. If not, then skip it.
// we need to check if the current position is a pairing or not. If not,
// then skip it.
//
// this also skips inputs to the current pairing instance which do not
// require passing to the pairing circuit (full-trivial or half-trivial
// inputs). But this is fine as it doesn't change the result as trivial
// result ML is 1. Only non-trivial input pairs affect the pairing check
// result.
if srcIsPairing[currPos].IsZero() {
currPos++
continue
Expand All @@ -100,28 +108,42 @@ func (ec *ECPair) assignPairingData(run *wizard.ProverRuntime) {
// input data. We iterate over the data to get the number of pairing
// inputs.
nbInputs := 1 // we start with 1 because we always have at least one pairing input
// we may have trivial input pairs in-between the non-trivial ones. We
// mark which inputs we are interested in.
actualInputs := []int{0} // we started when isPairing is 1, this means that we have non-trivial input.
for {
if srcIsRes[currPos+nbInputs*(nbG1Limbs+nbG2Limbs)].IsOne() {
break
}
if srcIsPairing[currPos+nbInputs*(nbG1Limbs+nbG2Limbs)].IsOne() {
actualInputs = append(actualInputs, nbInputs)
}
nbInputs++
}
nbActualTotalPairs := len(actualInputs)
// now, we have continous chunk of data that is for the pairing. Prepare it for processing.
pairingInG1 = make([][nbG1Limbs]field.Element, nbInputs)
pairingInG2 = make([][nbG2Limbs]field.Element, nbInputs)
for i := 0; i < nbInputs; i++ {
pairingInG1 = make([][nbG1Limbs]field.Element, nbActualTotalPairs)
pairingInG2 = make([][nbG2Limbs]field.Element, nbActualTotalPairs)
for ii, i := range actualInputs {
for j := 0; j < nbG1Limbs; j++ {
pairingInG1[i][j] = srcLimbs[currPos+i*(nbG1Limbs+nbG2Limbs)+j]
pairingInG1[ii][j] = srcLimbs[currPos+i*(nbG1Limbs+nbG2Limbs)+j]
}
for j := 0; j < nbG2Limbs; j++ {
pairingInG2[i][j] = srcLimbs[currPos+i*(nbG1Limbs+nbG2Limbs)+nbG1Limbs+j]
pairingInG2[ii][j] = srcLimbs[currPos+i*(nbG1Limbs+nbG2Limbs)+nbG1Limbs+j]
}
inputResult[0] = srcLimbs[currPos+(i+1)*(nbG1Limbs+nbG2Limbs)]
inputResult[1] = srcLimbs[currPos+(i+1)*(nbG1Limbs+nbG2Limbs)+1]
}
inputResult[0] = srcLimbs[currPos+nbInputs*(nbG1Limbs+nbG2Limbs)]
inputResult[1] = srcLimbs[currPos+nbInputs*(nbG1Limbs+nbG2Limbs)+1]
limbs := processPairingData(pairingInG1, pairingInG2, inputResult)
instanceId := srcID[currPos]
// processed data has the input limbs, but we have entered the intermediate Gt accumulator values

// generic assignment. We push the static values for the current instance:
// - the limbs (interleaved with accumulator values)
// - indicator for the first row of the whole pairingcheck instance
// - the instance id
// - the index of the current limb
// - activeness of the submodule
for i := 0; i < len(limbs); i++ {
dstLimb.PushField(limbs[i])
if i == 0 {
Expand All @@ -133,15 +155,24 @@ func (ec *ECPair) assignPairingData(run *wizard.ProverRuntime) {
dstIndex.PushInt(i)
dstIsActive.PushOne()
}
for i := 0; i < nbInputs-1; i++ {
// now we push the dynamic values per Miller loop. We send all valid
// pairs except the last to Miller loop (not Miller loop + finalexp!)
// circuit. Keep in mind if there is only a single valid pair then this
// loop is skipped.
for ii := range actualInputs[:len(actualInputs)-1] {
// first we indicate for the accumulator if it is the first one, previous or current
for j := 0; j < nbGtLimbs; j++ {
dstIsComputed.PushOne()
dstIsPulling.PushZero()
if i == 0 {
if ii == 0 {
// we handle first accumulator separately to be able to
// constrain that the initial accumulator value is correct.
dstIsAccInit.PushOne()
dstIsAccPrev.PushZero()
dstIsFirstPrev.PushZero()
} else {
// we're not in the first pair of points, so we indicate
// that the accumulator consistency needs to be checked.
dstIsAccInit.PushZero()
if j == 0 {
dstIsFirstPrev.PushOne()
Expand All @@ -153,6 +184,8 @@ func (ec *ECPair) assignPairingData(run *wizard.ProverRuntime) {
dstIsAccCurr.PushZero()
dstIsFirstCurr.PushZero()
}
// now we push the dynamic values for the actual inputs to the pairing check circuit (coming from the arithmetization).
// essentially we only mark that this limb came directly from arithmetization.
for j := nbGtLimbs; j < nbGtLimbs+nbG1Limbs+nbG2Limbs; j++ {
dstIsPulling.PushOne()
dstIsComputed.PushZero()
Expand All @@ -162,6 +195,7 @@ func (ec *ECPair) assignPairingData(run *wizard.ProverRuntime) {
dstIsFirstPrev.PushZero()
dstIsFirstCurr.PushZero()
}
// finally, we need to indicate that the next limbs are for the current accumulator
for j := nbGtLimbs + nbG1Limbs + nbG2Limbs; j < 2*nbGtLimbs+nbG1Limbs+nbG2Limbs; j++ {
dstIsComputed.PushOne()
dstIsPulling.PushZero()
Expand All @@ -175,54 +209,92 @@ func (ec *ECPair) assignPairingData(run *wizard.ProverRuntime) {
}
dstIsAccCurr.PushOne()
}
// we also set the static values for all limbs in this pairs of points. These are:
// - true mark for miller loop circuit
// - false mark for the ML+finalexp circuit
// - the pair ID
// - the total number of pairs
// - false mark that current limb is for the result
for j := 0; j < nbG1Limbs+nbG2Limbs+2*nbGtLimbs; j++ {
dstToMillerLoop.PushOne()
dstToFinalExp.PushZero()
dstPairId.PushInt(i + 1)
dstTotalPairs.PushInt(nbInputs)
dstPairId.PushInt(ii + 1)
dstTotalPairs.PushInt(nbActualTotalPairs)
dstIsResult.PushZero()
}
}
// we need to handle the final pair of points separately. The
// ML+finalexp circuit does not take the current accumulator as an
// input, but rather the expected pairing check result.
//
// first we set the masks for the accumulator limbs.
for j := 0; j < nbGtLimbs; j++ {
dstIsComputed.PushOne()
dstIsPulling.PushZero()
dstPairId.PushInt(nbInputs)
dstIsAccInit.PushZero()
dstIsAccPrev.PushOne()
dstPairId.PushInt(nbActualTotalPairs)
// handle separately the case when there is only one valid input
// pair. In this case, the first valid pair also includes the
// accumulator initialization.
if nbActualTotalPairs == 1 {
dstIsAccInit.PushOne()
dstIsAccPrev.PushZero()
} else {
dstIsAccInit.PushZero()
dstIsAccPrev.PushOne()
}
dstIsAccCurr.PushZero()
if j == 0 {
dstIsFirstPrev.PushOne()
// handle separately the case when there is only one valid
// input. In this case we don't have the previous accumulator.
if nbActualTotalPairs == 1 {
dstIsFirstPrev.PushZero()
} else {
dstIsFirstPrev.PushOne()
}
} else {
dstIsFirstPrev.PushZero()
}
dstIsFirstCurr.PushZero()
dstIsResult.PushZero()
}
// similarly for the final pair of points, we need to indicate that the
// G1/G2 points come directly from the arithmetization.
for j := nbGtLimbs; j < nbGtLimbs+nbG1Limbs+nbG2Limbs; j++ {
dstIsPulling.PushOne()
dstIsComputed.PushZero()
dstPairId.PushInt(nbInputs)
dstPairId.PushInt(nbActualTotalPairs)
dstIsAccInit.PushZero()
dstIsAccPrev.PushZero()
dstIsAccCurr.PushZero()
dstIsFirstPrev.PushZero()
dstIsFirstCurr.PushZero()
dstIsResult.PushZero()
}
// finally, we need to indicate that the result of the pairing check
// comes directly from the arithmetization. this is a bit explicit loop,
// but it's easier to understand.
for j := nbGtLimbs + nbG1Limbs + nbG2Limbs; j < nbGtLimbs+nbG1Limbs+nbG2Limbs+2; j++ {
dstIsPulling.PushOne()
dstIsComputed.PushZero()
// NB! for the result the Pair ID is 0. This is important to keep in
// mind as we set some constraints based on this.
dstPairId.PushZero()
dstIsAccInit.PushZero()
dstIsAccPrev.PushZero()
dstIsAccCurr.PushZero()
dstIsFirstPrev.PushZero()
dstIsFirstCurr.PushZero()
dstIsResult.PushOne()
}
// finally we set the static masks for the final pair of points.
for j := 0; j < nbG1Limbs+nbG2Limbs+nbGtLimbs+2; j++ {
dstToFinalExp.PushOne()
dstToMillerLoop.PushZero()
dstTotalPairs.PushInt(nbInputs)
dstTotalPairs.PushInt(nbActualTotalPairs)
}
currPos += nbInputs*(nbG1Limbs+nbG2Limbs) + 2
}
// Finally, we pad and assign the assigned data.
dstIsActive.PadAndAssign(run, field.Zero())
dstLimb.PadAndAssign(run, field.Zero())
dstPairId.PadAndAssign(run, field.Zero())
Expand All @@ -239,6 +311,7 @@ func (ec *ECPair) assignPairingData(run *wizard.ProverRuntime) {
dstIndex.PadAndAssign(run, field.Zero())
dstIsFirstPrev.PadAndAssign(run, field.Zero())
dstIsFirstCurr.PadAndAssign(run, field.Zero())
dstIsResult.PadAndAssign(run, field.Zero())
}

func processPairingData(pairingInG1 [][nbG1Limbs]field.Element, pairingInG2 [][nbG2Limbs]field.Element, inputResult [2]field.Element) []field.Element {
Expand Down
40 changes: 32 additions & 8 deletions prover/zkevm/prover/ecpair/ecpair_constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ func (ec *ECPair) csProjections(comp *wizard.CompiledIOP) {
// we project data from the arithmetization correctly to the unaligned part of the circuit
projection.InsertProjection(
comp, ifaces.QueryIDf("%v_PROJECTION_PAIRING", nameECPair),
[]ifaces.Column{ec.ECPairSource.Limb, ec.ECPairSource.AccPairings, ec.ECPairSource.TotalPairings, ec.ECPairSource.ID},
[]ifaces.Column{ec.UnalignedPairingData.Limb, ec.UnalignedPairingData.PairID, ec.UnalignedPairingData.TotalPairs, ec.UnalignedPairingData.InstanceID},
[]ifaces.Column{ec.ECPairSource.Limb, ec.ECPairSource.ID, ec.ECPairSource.IsEcPairingResult},
[]ifaces.Column{ec.UnalignedPairingData.Limb, ec.UnalignedPairingData.InstanceID, ec.UnalignedPairingData.IsResultOfInstance},
ec.ECPairSource.CsEcpairing,
ec.UnalignedPairingData.IsPulling,
)
Expand Down Expand Up @@ -159,6 +159,23 @@ func (ec *ECPair) csAccumulatorConsistency(comp *wizard.CompiledIOP) {
)
}

func (ec *ECPair) csTotalPairs(comp *wizard.CompiledIOP) {
// total pairs corresponds to the number of pairs in the instance. for this
// we check that when the limb corresponds to the result, then the PairID of
// the shifted by two corresponds to the total pairs. the limb corresponds
// to the result when its PairID is 0 (for input pairs the indexing starts
// from 1).
comp.InsertGlobal(
roundNr,
ifaces.QueryIDf("%v_TOTAL_PAIRS", nameECPair),
sym.Mul(
ec.UnalignedPairingData.IsActive,
ec.UnalignedPairingData.IsResultOfInstance,
sym.Sub(ec.UnalignedPairingData.TotalPairs, column.Shift(ec.UnalignedPairingData.PairID, -2)), // we have two limbs for the result, shift by two gets to the input
),
)
}

func (ec *ECPair) csLastPairToFinalExp(comp *wizard.CompiledIOP) {
// if the last pair then the final exp circuit should be active

Expand Down Expand Up @@ -191,7 +208,6 @@ func (ec *ECPair) csIndexConsistency(comp *wizard.CompiledIOP) {
ifaces.QueryIDf("%v_INDEX_INCREMENT", nameECPair),
sym.Mul(
ec.UnalignedPairingData.IsActive,
sym.Sub(1, ec.UnalignedG2MembershipData.IsPulling, ec.UnalignedG2MembershipData.IsComputed), // we dont use index in the G2 membership check
sym.Sub(1, ec.UnalignedPairingData.IsFirstLineOfInstance),
sym.Sub(ec.UnalignedPairingData.Index, column.Shift(ec.UnalignedPairingData.Index, -1), 1),
),
Expand Down Expand Up @@ -276,11 +292,19 @@ func (ec *ECPair) csAccumulatorMask(comp *wizard.CompiledIOP) {
)
}

func (ec *ECPair) csExclusiveUnalignedDatas(comp *wizard.CompiledIOP) {
common.MustBeMutuallyExclusiveBinaryFlags(comp, ec.IsActive, []ifaces.Column{
ec.UnalignedG2MembershipData.ToG2MembershipCircuitMask,
ec.UnalignedPairingData.IsActive,
})
func (ec *ECPair) csPairingDataOrMembershipActive(comp *wizard.CompiledIOP) {
// when module is active, then either pairing data or membership data is
// active. Can also be both.
comp.InsertGlobal(
roundNr,
ifaces.QueryIDf("%v_PAIRING_OR_MEMBERSHIP_ACTIVE", nameECPair),
sym.Add(
ec.IsActive,
sym.Neg(ec.UnalignedPairingData.IsActive),
sym.Neg(ec.UnalignedG2MembershipData.ToG2MembershipCircuitMask),
sym.Mul(ec.UnalignedPairingData.IsActive, ec.UnalignedG2MembershipData.ToG2MembershipCircuitMask),
),
)
}

func (ec *ECPair) csExclusivePairingCircuitMasks(comp *wizard.CompiledIOP) {
Expand Down
Loading
Loading