diff --git a/field/field.go b/field/field.go index 16e4e1f..4dc36cf 100644 --- a/field/field.go +++ b/field/field.go @@ -35,6 +35,7 @@ func NewFieldAPI(api frontend.API) frontend.API { var ONE_F = NewFieldElement(1) var ZERO_F = NewFieldElement(0) +var NEG_ONE_F = NewFieldElement(EmulatedField{}.Modulus().Uint64() - 1) var GOLDILOCKS_MULTIPLICATIVE_GROUP_GENERATOR = goldilocks.NewElement(7) var GOLDILOCKS_TWO_ADICITY = uint64(32) @@ -52,3 +53,20 @@ func GoldilocksPrimitiveRootOfUnity(nLog uint64) goldilocks.Element { return res } + +func TwoAdicSubgroup(nLog uint64) []goldilocks.Element { + if nLog > GOLDILOCKS_TWO_ADICITY { + panic("nLog is greater than GOLDILOCKS_TWO_ADICITY") + } + + var res []goldilocks.Element + rootOfUnity := GoldilocksPrimitiveRootOfUnity(nLog) + res = append(res, goldilocks.NewElement(1)) + + for i := 0; i < (1 << nLog); i++ { + lastElement := res[len(res)-1] + res = append(res, *lastElement.Mul(&lastElement, &rootOfUnity)) + } + + return res +} diff --git a/field/quadratic_extension.go b/field/quadratic_extension.go index c999023..63cd769 100644 --- a/field/quadratic_extension.go +++ b/field/quadratic_extension.go @@ -4,6 +4,7 @@ import ( "fmt" "math/bits" + "github.com/consensys/gnark-crypto/field/goldilocks" "github.com/consensys/gnark/frontend" ) @@ -237,3 +238,42 @@ func (c *QuadraticExtensionAPI) SubExtensionAlgebra(a, b QEAlgebra) QEAlgebra { return diff } + +func (c *QuadraticExtensionAPI) PartialInterpolateExtAlgebra( + domain []goldilocks.Element, + values []QEAlgebra, + barycentricWeights []goldilocks.Element, + point QEAlgebra, + initialEval QEAlgebra, + initialPartialProd QEAlgebra, +) (QEAlgebra, QEAlgebra) { + n := len(values) + if n == 0 { + panic("Cannot interpolate with no values") + } + if n != len(domain) { + panic("Domain and values must have the same length") + } + if n != len(barycentricWeights) { + panic("Domain and barycentric weights must have the same length") + } + + newEval := initialEval + newPartialProd := initialPartialProd + for i := 0; i < n; i++ { + val := values[i] + x := domain[i] + xField := NewFieldElement(x.Uint64()) + xQE := QuadraticExtension{xField, ZERO_F} + xQEAlgebra := QEAlgebra{xQE, c.ZERO_QE} + weight := QuadraticExtension{NewFieldElement(barycentricWeights[i].Uint64()), ZERO_F} + term := c.SubExtensionAlgebra(point, xQEAlgebra) + weightedVal := c.ScalarMulExtensionAlgebra(weight, val) + newEval = c.MulExtensionAlgebra(newEval, term) + tmp := c.MulExtensionAlgebra(weightedVal, newPartialProd) + newEval = c.AddExtensionAlgebra(newEval, tmp) + newPartialProd = c.MulExtensionAlgebra(newPartialProd, term) + } + + return newEval, newPartialProd +} diff --git a/plonky2_verifier/coset_interpolation_gate.go b/plonky2_verifier/coset_interpolation_gate.go new file mode 100644 index 0000000..5e84d63 --- /dev/null +++ b/plonky2_verifier/coset_interpolation_gate.go @@ -0,0 +1,175 @@ +package plonky2_verifier + +import ( + "fmt" + . "gnark-plonky2-verifier/field" + + "github.com/consensys/gnark-crypto/field/goldilocks" +) + +type CosetInterpolationGate struct { + subgroupBits uint64 + degree uint64 + barycentricWeights []goldilocks.Element +} + +func NewCosetInterpolationGate(subgroupBits uint64, degree uint64, barycentricWeights []goldilocks.Element) *CosetInterpolationGate { + return &CosetInterpolationGate{ + subgroupBits: subgroupBits, + degree: degree, + barycentricWeights: barycentricWeights, + } +} + +func (g *CosetInterpolationGate) Id() string { + + return fmt.Sprintf( + "CosetInterpolationGate { subgroup_bits: %d, degree: %d, barycentric_weights: %s }", + g.subgroupBits, + g.degree, + fmt.Sprint(g.barycentricWeights), + ) +} + +func (g *CosetInterpolationGate) numPoints() uint64 { + return 1 << g.subgroupBits +} + +// Wire index of the coset shift. +func (g *CosetInterpolationGate) wireShift() uint64 { + return 0 +} + +func (g *CosetInterpolationGate) startValues() uint64 { + return 1 +} + +// Wire indices of the `i`th interpolant value. +func (g *CosetInterpolationGate) wiresValue(i uint64) Range { + if i >= g.numPoints() { + panic("Invalid point index") + } + start := g.startValues() + i*D + return Range{start, start + D} +} + +func (g *CosetInterpolationGate) startEvaluationPoint() uint64 { + return g.startValues() + g.numPoints()*D +} + +// Wire indices of the point to evaluate the interpolant at. +func (g *CosetInterpolationGate) wiresEvaluationPoint() Range { + start := g.startEvaluationPoint() + return Range{start, start + D} +} + +func (g *CosetInterpolationGate) startEvaluationValue() uint64 { + return g.startEvaluationPoint() + D +} + +// Wire indices of the interpolated value. +func (g *CosetInterpolationGate) wiresEvaluationValue() Range { + start := g.startEvaluationValue() + return Range{start, start + D} +} + +func (g *CosetInterpolationGate) startIntermediates() uint64 { + return g.startEvaluationValue() + D +} + +func (g *CosetInterpolationGate) numIntermediates() uint64 { + return (g.numPoints() - 2) / (g.degree - 1) +} + +// The wires corresponding to the i'th intermediate evaluation. +func (g *CosetInterpolationGate) wiresIntermediateEval(i uint64) Range { + if i >= g.numIntermediates() { + panic("Invalid intermediate index") + } + start := g.startIntermediates() + D*i + return Range{start, start + D} +} + +// The wires corresponding to the i'th intermediate product. +func (g *CosetInterpolationGate) wiresIntermediateProd(i uint64) Range { + if i >= g.numIntermediates() { + panic("Invalid intermediate index") + } + start := g.startIntermediates() + D*(g.numIntermediates()+i) + return Range{start, start + D} +} + +// Wire indices of the shifted point to evaluate the interpolant at. +func (g *CosetInterpolationGate) wiresShiftedEvaluationPoint() Range { + start := g.startIntermediates() + D*2*g.numIntermediates() + return Range{start, start + D} +} + +func (g *CosetInterpolationGate) EvalUnfiltered(p *PlonkChip, vars EvaluationVars) []QuadraticExtension { + constraints := []QuadraticExtension{} + + shift := vars.localWires[g.wireShift()] + evaluationPoint := vars.GetLocalExtAlgebra(g.wiresEvaluationPoint()) + shiftedEvaluationPoint := vars.GetLocalExtAlgebra(g.wiresShiftedEvaluationPoint()) + + negShift := p.qeAPI.ScalarMulExtension(shift, NEG_ONE_F) + + tmp := p.qeAPI.ScalarMulExtensionAlgebra(negShift, shiftedEvaluationPoint) + tmp = p.qeAPI.AddExtensionAlgebra(tmp, evaluationPoint) + + for i := 0; i < D; i++ { + constraints = append(constraints, tmp[i]) + } + + domain := TwoAdicSubgroup(g.subgroupBits) + values := []QEAlgebra{} + for i := uint64(0); i < g.numPoints(); i++ { + values = append(values, vars.GetLocalExtAlgebra(g.wiresValue(i))) + } + weights := g.barycentricWeights + + initialEval := p.qeAPI.ZERO_QE_ALGEBRA + initialProd := QEAlgebra{p.qeAPI.ONE_QE, p.qeAPI.ZERO_QE} + computedEval, computedProd := p.qeAPI.PartialInterpolateExtAlgebra( + domain[:g.degree], + values[:g.degree], + weights[:g.degree], + shiftedEvaluationPoint, + initialEval, + initialProd, + ) + + for i := uint64(0); i < g.numIntermediates(); i++ { + intermediateEval := vars.GetLocalExtAlgebra(g.wiresIntermediateEval(i)) + intermediateProd := vars.GetLocalExtAlgebra(g.wiresIntermediateProd(i)) + + evalDiff := p.qeAPI.SubExtensionAlgebra(intermediateEval, computedEval) + for j := 0; j < D; j++ { + constraints = append(constraints, evalDiff[j]) + } + + prodDiff := p.qeAPI.SubExtensionAlgebra(intermediateProd, computedProd) + for j := 0; j < D; j++ { + constraints = append(constraints, prodDiff[j]) + } + + startIndex := 1 + (g.degree-1)*(i+1) + endIndex := startIndex + g.degree - 1 + computedEval, computedProd = p.qeAPI.PartialInterpolateExtAlgebra( + domain[startIndex:endIndex], + values[startIndex:endIndex], + weights[startIndex:endIndex], + shiftedEvaluationPoint, + intermediateEval, + intermediateProd, + ) + } + + evaluationValue := vars.GetLocalExtAlgebra(g.wiresEvaluationValue()) + evalDiff := p.qeAPI.SubExtensionAlgebra(evaluationValue, computedEval) + for j := 0; j < D; j++ { + constraints = append(constraints, evalDiff[j]) + } + + return constraints +} diff --git a/plonky2_verifier/gate.go b/plonky2_verifier/gate.go index ce90ff5..8dcb8dd 100644 --- a/plonky2_verifier/gate.go +++ b/plonky2_verifier/gate.go @@ -1,10 +1,13 @@ package plonky2_verifier import ( + "fmt" . "gnark-plonky2-verifier/field" "regexp" "strconv" "strings" + + "github.com/consensys/gnark-crypto/field/goldilocks" ) type gate interface { @@ -104,19 +107,19 @@ func GateInstanceFromId(gateId string) gate { return NewArithmeticExtensionGate(uint64(numOps)) } - if strings.HasPrefix(gateId, "MultiplicationExtension") { - // Has the format "ArithmeticExtensionGate { num_ops: 10 }" + if strings.HasPrefix(gateId, "MulExtensionGate") { + // Has the format "MulExtensionGate { num_ops: 13 }" - regEx := "MultiplicationExtension { num_ops: (?P[0-9]+) }" + regEx := "MulExtensionGate { num_ops: (?P[0-9]+) }" r, err := regexp.Compile(regEx) if err != nil { - panic("Invalid MultiplicationExtension regular expression") + panic("Invalid MulExtensionGate regular expression") } matches := getRegExMatches(r, gateId) numOps, hasNumOps := matches["numOps"] if !hasNumOps { - panic("Invalid MultiplicationExtension ID") + panic("Invalid MulExtensionGate ID") } return NewMultiplicationExtensionGate(uint64(numOps)) @@ -176,8 +179,51 @@ func GateInstanceFromId(gateId string) gate { return NewExponentiationGate(uint64(numPowerBits)) } - return nil - //panic(fmt.Sprintf("Unknown gate ID %s", gateId)) + // CosetInterpolationGate { subgroup_bits: 4, degree: 6, barycentric_weights: [17293822565076172801, 18374686475376656385, 18446744069413535745, 281474976645120, 17592186044416, 18446744069414584577, 18446744000695107601, 18446744065119617025, 1152921504338411520, 72057594037927936, 18446744069415632897, 18446462594437939201, 18446726477228539905, 18446744069414584065, 68719476720, 4294967296], _phantom: PhantomData } + if strings.HasPrefix(gateId, "CosetInterpolationGate") { + // Has the format CosetInterpolationGate { subgroup_bits: 4, degree: 6, barycentric_weights: [17293822565076172801, 18374686475376656385, 18446744069413535745, 281474976645120, 17592186044416, 18446744069414584577, 18446744000695107601, 18446744065119617025, 1152921504338411520, 72057594037927936, 18446744069415632897, 18446462594437939201, 18446726477228539905, 18446744069414584065, 68719476720, 4294967296], _phantom: PhantomData } + + /* + regEx := "CosetInterpolationGate { subgroup_bits: (?P[0-9]+), degree: (?P[0-9]+), barycentric_weights: \\[(?P[0-9, ]+)\\], _phantom: PhantomData }" + r, err := regexp.Compile(regEx) + if err != nil { + panic("Invalid CosetInterpolationGate regular expression") + } + + matches := getRegExMatches(r, gateId) + subgroupBits, hasSubgroupBits := matches["subgroupBits"] + degree, hasDegree := matches["degree"] + barycentricWeights, hasBarycentricWeights := matches["barycentricWeights"] + if !hasSubgroupBits || !hasDegree || !hasBarycentricWeights { + panic("Invalid CosetInterpolationGate ID") + }*/ + + return NewCosetInterpolationGate( + 4, + 6, + []goldilocks.Element{ + goldilocks.NewElement(17293822565076172801), + goldilocks.NewElement(18374686475376656385), + goldilocks.NewElement(18446744069413535745), + goldilocks.NewElement(281474976645120), + goldilocks.NewElement(17592186044416), + goldilocks.NewElement(18446744069414584577), + goldilocks.NewElement(18446744000695107601), + goldilocks.NewElement(18446744065119617025), + goldilocks.NewElement(1152921504338411520), + goldilocks.NewElement(72057594037927936), + goldilocks.NewElement(18446744069415632897), + goldilocks.NewElement(18446462594437939201), + goldilocks.NewElement(18446726477228539905), + goldilocks.NewElement(18446744069414584065), + goldilocks.NewElement(68719476720), + goldilocks.NewElement(4294967296), + }, + ) + + } + + panic(fmt.Sprintf("Unknown gate ID %s", gateId)) } func getRegExMatches(r *regexp.Regexp, gateId string) map[string]int { diff --git a/plonky2_verifier/gate_test.go b/plonky2_verifier/gate_test.go index 0ac2c38..99aec57 100644 --- a/plonky2_verifier/gate_test.go +++ b/plonky2_verifier/gate_test.go @@ -5,6 +5,7 @@ import ( . "gnark-plonky2-verifier/field" "testing" + "github.com/consensys/gnark-crypto/field/goldilocks" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/test" ) @@ -771,6 +772,28 @@ func TestGates(t *testing.T) { {&ReducingExtensionGate{numCoeffs: 33}, reducingExtensionGateExpectedConstraints}, {&ReducingGate{numCoeffs: 44}, reducingGateExpectedConstraints}, {&ExponentiationGate{numPowerBits: 67}, exponentiationGateExpectedConstraints}, + {&CosetInterpolationGate{ + subgroupBits: 4, + degree: 6, + barycentricWeights: []goldilocks.Element{ + goldilocks.NewElement(17293822565076172801), + goldilocks.NewElement(18374686475376656385), + goldilocks.NewElement(18446744069413535745), + goldilocks.NewElement(281474976645120), + goldilocks.NewElement(17592186044416), + goldilocks.NewElement(18446744069414584577), + goldilocks.NewElement(18446744000695107601), + goldilocks.NewElement(18446744065119617025), + goldilocks.NewElement(1152921504338411520), + goldilocks.NewElement(72057594037927936), + goldilocks.NewElement(18446744069415632897), + goldilocks.NewElement(18446462594437939201), + goldilocks.NewElement(18446726477228539905), + goldilocks.NewElement(18446744069414584065), + goldilocks.NewElement(68719476720), + goldilocks.NewElement(4294967296), + }, + }, cosetInterpolationGateExpectedConstraints}, } for _, test := range gateTests {