diff --git a/backend/backend.go b/backend/backend.go index 7ee17d6793..7c427e5825 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -60,6 +60,7 @@ type ProverConfig struct { ChallengeHash hash.Hash KZGFoldingHash hash.Hash Accelerator string + StatisticalZK bool } // NewProverConfig returns a default ProverConfig with given prover options opts @@ -133,6 +134,16 @@ func WithIcicleAcceleration() ProverOption { } } +// WithStatisticalZeroKnowledge ensures that statistical zero knowledgeness is achieved. +// This option makes the prover more memory costly, as there are 3 more size n (size of the circuit) +// allocations. +func WithStatisticalZeroKnowledge() ProverOption { + return func(pc *ProverConfig) error { + pc.StatisticalZK = true + return nil + } +} + // VerifierOption defines option for altering the behavior of the verifier. See // the descriptions of functions returning instances of this type for // implemented options. diff --git a/backend/groth16/bls12-377/verify.go b/backend/groth16/bls12-377/verify.go index b8ac2625b6..e4fbc2d27b 100644 --- a/backend/groth16/bls12-377/verify.go +++ b/backend/groth16/bls12-377/verify.go @@ -19,7 +19,6 @@ package groth16 import ( "errors" "fmt" - "github.com/consensys/gnark/backend/solidity" "io" "time" @@ -30,6 +29,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/pedersen" "github.com/consensys/gnark-crypto/utils" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/solidity" "github.com/consensys/gnark/constraint" "github.com/consensys/gnark/logger" ) diff --git a/backend/groth16/bls12-381/verify.go b/backend/groth16/bls12-381/verify.go index f6a266807e..497009bdf4 100644 --- a/backend/groth16/bls12-381/verify.go +++ b/backend/groth16/bls12-381/verify.go @@ -19,7 +19,6 @@ package groth16 import ( "errors" "fmt" - "github.com/consensys/gnark/backend/solidity" "io" "time" @@ -30,6 +29,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/pedersen" "github.com/consensys/gnark-crypto/utils" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/solidity" "github.com/consensys/gnark/constraint" "github.com/consensys/gnark/logger" ) diff --git a/backend/groth16/bls24-315/verify.go b/backend/groth16/bls24-315/verify.go index 98a21fb52d..b832adec1e 100644 --- a/backend/groth16/bls24-315/verify.go +++ b/backend/groth16/bls24-315/verify.go @@ -19,7 +19,6 @@ package groth16 import ( "errors" "fmt" - "github.com/consensys/gnark/backend/solidity" "io" "time" @@ -30,6 +29,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls24-315/fr/pedersen" "github.com/consensys/gnark-crypto/utils" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/solidity" "github.com/consensys/gnark/constraint" "github.com/consensys/gnark/logger" ) diff --git a/backend/groth16/bls24-317/verify.go b/backend/groth16/bls24-317/verify.go index cbaa84e45a..79d828492b 100644 --- a/backend/groth16/bls24-317/verify.go +++ b/backend/groth16/bls24-317/verify.go @@ -19,7 +19,6 @@ package groth16 import ( "errors" "fmt" - "github.com/consensys/gnark/backend/solidity" "io" "time" @@ -30,6 +29,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls24-317/fr/pedersen" "github.com/consensys/gnark-crypto/utils" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/solidity" "github.com/consensys/gnark/constraint" "github.com/consensys/gnark/logger" ) diff --git a/backend/groth16/bn254/solidity.go b/backend/groth16/bn254/solidity.go index 97337c6507..5ec3e8bcb7 100644 --- a/backend/groth16/bn254/solidity.go +++ b/backend/groth16/bn254/solidity.go @@ -75,49 +75,49 @@ contract Verifier { uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; // Groth16 alpha point in G1 - uint256 constant ALPHA_X = {{.Vk.G1.Alpha.X.String}}; - uint256 constant ALPHA_Y = {{.Vk.G1.Alpha.Y.String}}; + uint256 constant ALPHA_X = {{ (fpstr .Vk.G1.Alpha.X) }}; + uint256 constant ALPHA_Y = {{ (fpstr .Vk.G1.Alpha.Y) }}; // Groth16 beta point in G2 in powers of i - uint256 constant BETA_NEG_X_0 = {{.Vk.G2.Beta.X.A0.String}}; - uint256 constant BETA_NEG_X_1 = {{.Vk.G2.Beta.X.A1.String}}; - uint256 constant BETA_NEG_Y_0 = {{.Vk.G2.Beta.Y.A0.String}}; - uint256 constant BETA_NEG_Y_1 = {{.Vk.G2.Beta.Y.A1.String}}; + uint256 constant BETA_NEG_X_0 = {{ (fpstr .Vk.G2.Beta.X.A0) }}; + uint256 constant BETA_NEG_X_1 = {{ (fpstr .Vk.G2.Beta.X.A1) }}; + uint256 constant BETA_NEG_Y_0 = {{ (fpstr .Vk.G2.Beta.Y.A0) }}; + uint256 constant BETA_NEG_Y_1 = {{ (fpstr .Vk.G2.Beta.Y.A1) }}; // Groth16 gamma point in G2 in powers of i - uint256 constant GAMMA_NEG_X_0 = {{.Vk.G2.Gamma.X.A0.String}}; - uint256 constant GAMMA_NEG_X_1 = {{.Vk.G2.Gamma.X.A1.String}}; - uint256 constant GAMMA_NEG_Y_0 = {{.Vk.G2.Gamma.Y.A0.String}}; - uint256 constant GAMMA_NEG_Y_1 = {{.Vk.G2.Gamma.Y.A1.String}}; + uint256 constant GAMMA_NEG_X_0 = {{ (fpstr .Vk.G2.Gamma.X.A0) }}; + uint256 constant GAMMA_NEG_X_1 = {{ (fpstr .Vk.G2.Gamma.X.A1) }}; + uint256 constant GAMMA_NEG_Y_0 = {{ (fpstr .Vk.G2.Gamma.Y.A0) }}; + uint256 constant GAMMA_NEG_Y_1 = {{ (fpstr .Vk.G2.Gamma.Y.A1) }}; // Groth16 delta point in G2 in powers of i - uint256 constant DELTA_NEG_X_0 = {{.Vk.G2.Delta.X.A0.String}}; - uint256 constant DELTA_NEG_X_1 = {{.Vk.G2.Delta.X.A1.String}}; - uint256 constant DELTA_NEG_Y_0 = {{.Vk.G2.Delta.Y.A0.String}}; - uint256 constant DELTA_NEG_Y_1 = {{.Vk.G2.Delta.Y.A1.String}}; + uint256 constant DELTA_NEG_X_0 = {{ (fpstr .Vk.G2.Delta.X.A0) }}; + uint256 constant DELTA_NEG_X_1 = {{ (fpstr .Vk.G2.Delta.X.A1) }}; + uint256 constant DELTA_NEG_Y_0 = {{ (fpstr .Vk.G2.Delta.Y.A0) }}; + uint256 constant DELTA_NEG_Y_1 = {{ (fpstr .Vk.G2.Delta.Y.A1) }}; {{- if gt $numCommitments 0 }} // Pedersen G point in G2 in powers of i - uint256 constant PEDERSEN_G_X_0 = {{.Vk.CommitmentKey.G.X.A0.String}}; - uint256 constant PEDERSEN_G_X_1 = {{.Vk.CommitmentKey.G.X.A1.String}}; - uint256 constant PEDERSEN_G_Y_0 = {{.Vk.CommitmentKey.G.Y.A0.String}}; - uint256 constant PEDERSEN_G_Y_1 = {{.Vk.CommitmentKey.G.Y.A1.String}}; + uint256 constant PEDERSEN_G_X_0 = {{ (fpstr .Vk.CommitmentKey.G.X.A0) }}; + uint256 constant PEDERSEN_G_X_1 = {{ (fpstr .Vk.CommitmentKey.G.X.A1) }}; + uint256 constant PEDERSEN_G_Y_0 = {{ (fpstr .Vk.CommitmentKey.G.Y.A0) }}; + uint256 constant PEDERSEN_G_Y_1 = {{ (fpstr .Vk.CommitmentKey.G.Y.A1) }}; // Pedersen GRootSigmaNeg point in G2 in powers of i - uint256 constant PEDERSEN_GROOTSIGMANEG_X_0 = {{.Vk.CommitmentKey.GRootSigmaNeg.X.A0.String}}; - uint256 constant PEDERSEN_GROOTSIGMANEG_X_1 = {{.Vk.CommitmentKey.GRootSigmaNeg.X.A1.String}}; - uint256 constant PEDERSEN_GROOTSIGMANEG_Y_0 = {{.Vk.CommitmentKey.GRootSigmaNeg.Y.A0.String}}; - uint256 constant PEDERSEN_GROOTSIGMANEG_Y_1 = {{.Vk.CommitmentKey.GRootSigmaNeg.Y.A1.String}}; + uint256 constant PEDERSEN_GROOTSIGMANEG_X_0 = {{ (fpstr .Vk.CommitmentKey.GRootSigmaNeg.X.A0) }}; + uint256 constant PEDERSEN_GROOTSIGMANEG_X_1 = {{ (fpstr .Vk.CommitmentKey.GRootSigmaNeg.X.A1) }}; + uint256 constant PEDERSEN_GROOTSIGMANEG_Y_0 = {{ (fpstr .Vk.CommitmentKey.GRootSigmaNeg.Y.A0) }}; + uint256 constant PEDERSEN_GROOTSIGMANEG_Y_1 = {{ (fpstr .Vk.CommitmentKey.GRootSigmaNeg.Y.A1) }}; {{- end }} // Constant and public input points {{- $k0 := index .Vk.G1.K 0}} - uint256 constant CONSTANT_X = {{$k0.X.String}}; - uint256 constant CONSTANT_Y = {{$k0.Y.String}}; + uint256 constant CONSTANT_X = {{ (fpstr $k0.X) }}; + uint256 constant CONSTANT_Y = {{ (fpstr $k0.Y) }}; {{- range $i, $ki := .Vk.G1.K }} {{- if gt $i 0 }} - uint256 constant PUB_{{sub $i 1}}_X = {{$ki.X.String}}; - uint256 constant PUB_{{sub $i 1}}_Y = {{$ki.Y.String}}; + uint256 constant PUB_{{sub $i 1}}_X = {{ (fpstr $ki.X) }}; + uint256 constant PUB_{{sub $i 1}}_Y = {{ (fpstr $ki.Y) }}; {{- end }} {{- end }} diff --git a/backend/groth16/bn254/verify.go b/backend/groth16/bn254/verify.go index b2decd60a3..00914c003d 100644 --- a/backend/groth16/bn254/verify.go +++ b/backend/groth16/bn254/verify.go @@ -19,8 +19,9 @@ package groth16 import ( "errors" "fmt" - "github.com/consensys/gnark/backend/solidity" + "github.com/consensys/gnark-crypto/ecc/bn254/fp" "io" + "math/big" "text/template" "time" @@ -31,6 +32,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bn254/fr/pedersen" "github.com/consensys/gnark-crypto/utils" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/solidity" "github.com/consensys/gnark/constraint" "github.com/consensys/gnark/logger" ) @@ -163,6 +165,11 @@ func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.Expor } return out }, + "fpstr": func(x fp.Element) string { + bv := new(big.Int) + x.BigInt(bv) + return bv.String() + }, } log := logger.Logger() diff --git a/backend/groth16/bw6-633/verify.go b/backend/groth16/bw6-633/verify.go index e6d270455d..cb2389f7e3 100644 --- a/backend/groth16/bw6-633/verify.go +++ b/backend/groth16/bw6-633/verify.go @@ -19,7 +19,6 @@ package groth16 import ( "errors" "fmt" - "github.com/consensys/gnark/backend/solidity" "io" "time" @@ -30,6 +29,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bw6-633/fr/pedersen" "github.com/consensys/gnark-crypto/utils" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/solidity" "github.com/consensys/gnark/constraint" "github.com/consensys/gnark/logger" ) diff --git a/backend/groth16/bw6-761/verify.go b/backend/groth16/bw6-761/verify.go index 3b81eaa85b..6204909127 100644 --- a/backend/groth16/bw6-761/verify.go +++ b/backend/groth16/bw6-761/verify.go @@ -19,7 +19,6 @@ package groth16 import ( "errors" "fmt" - "github.com/consensys/gnark/backend/solidity" "io" "time" @@ -30,6 +29,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/pedersen" "github.com/consensys/gnark-crypto/utils" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/solidity" "github.com/consensys/gnark/constraint" "github.com/consensys/gnark/logger" ) diff --git a/backend/groth16/groth16.go b/backend/groth16/groth16.go index 56129c40db..9cdf99eba7 100644 --- a/backend/groth16/groth16.go +++ b/backend/groth16/groth16.go @@ -94,6 +94,10 @@ type ProvingKey interface { type VerifyingKey interface { groth16Object gnarkio.UnsafeReaderFrom + // VerifyingKey are the methods required for generating the Solidity + // verifier contract from the VerifyingKey. This will return an error if not + // supported on the CurveID(). + solidity.VerifyingKey // NbPublicWitness returns number of elements expected in the public witness NbPublicWitness() int @@ -104,10 +108,6 @@ type VerifyingKey interface { // NbG2 returns the number of G2 elements in the VerifyingKey NbG2() int - // ExportSolidity writes a solidity Verifier contract from the VerifyingKey - // this will return an error if not supported on the CurveID() - ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error - IsDifferent(interface{}) bool } diff --git a/backend/plonk/bls12-377/prove.go b/backend/plonk/bls12-377/prove.go index 8dcd0479f0..c5380230ea 100644 --- a/backend/plonk/bls12-377/prove.go +++ b/backend/plonk/bls12-377/prove.go @@ -179,10 +179,11 @@ type instance struct { htfFunc hash.Hash // hash to field function // polynomials - x []*iop.Polynomial // x stores tracks the polynomial we need - bp []*iop.Polynomial // blinding polynomials - h *iop.Polynomial // h is the quotient polynomial - blindedZ []fr.Element // blindedZ is the blinded version of Z + x []*iop.Polynomial // x stores tracks the polynomial we need + bp []*iop.Polynomial // blinding polynomials + h *iop.Polynomial // h is the quotient polynomial + blindedZ []fr.Element // blindedZ is the blinded version of Z + quotientShardsRandomizers [2]fr.Element // random elements for blinding the shards of the quotient linearizedPolynomial []fr.Element linearizedPolynomialDigest kzg.Digest @@ -246,6 +247,12 @@ func newInstance(ctx context.Context, spr *cs.SparseR1CS, pk *ProvingKey, fullWi sizeSystem := uint64(nbConstraints + len(spr.Public)) // len(spr.Public) is for the placeholder constraints s.domain0 = fft.NewDomain(sizeSystem) + // sampling random numbers for blinding the quotient + if opts.StatisticalZK { + s.quotientShardsRandomizers[0].SetRandom() + s.quotientShardsRandomizers[1].SetRandom() + } + // h, the quotient polynomial is of degree 3(n+1)+2, so it's in a 3(n+2) dim vector space, // the domain is the next power of 2 superior to 3(n+2). 4*domainNum is enough in all cases // except when n<6. @@ -320,7 +327,7 @@ func (s *instance) bsb22Hint(_ *big.Int, ins, outs []*big.Int) error { } // solveConstraints computes the evaluation of the polynomials L, R, O -// and sets x[id_L], x[id_R], x[id_O] in canonical form +// and sets x[id_L], x[id_R], x[id_O] in Lagrange form func (s *instance) solveConstraints() error { _solution, err := s.spr.Solve(s.fullWitness, s.opt.SolverOpts...) if err != nil { @@ -529,7 +536,7 @@ func (s *instance) computeQuotient() (err error) { return err } - s.h, err = divideByXMinusOne(numerator, [2]*fft.Domain{s.domain0, s.domain1}) + s.h, err = divideByZH(numerator, [2]*fft.Domain{s.domain0, s.domain1}) if err != nil { return err } @@ -610,17 +617,39 @@ func (s *instance) openZ() (err error) { } func (s *instance) h1() []fr.Element { - h1 := s.h.Coefficients()[:s.domain0.Cardinality+2] + var h1 []fr.Element + if !s.opt.StatisticalZK { + h1 = s.h.Coefficients()[:s.domain0.Cardinality+2] + } else { + h1 = make([]fr.Element, s.domain0.Cardinality+3) + copy(h1, s.h.Coefficients()[:s.domain0.Cardinality+2]) + h1[s.domain0.Cardinality+2].Set(&s.quotientShardsRandomizers[0]) + } return h1 } func (s *instance) h2() []fr.Element { - h2 := s.h.Coefficients()[s.domain0.Cardinality+2 : 2*(s.domain0.Cardinality+2)] + var h2 []fr.Element + if !s.opt.StatisticalZK { + h2 = s.h.Coefficients()[s.domain0.Cardinality+2 : 2*(s.domain0.Cardinality+2)] + } else { + h2 = make([]fr.Element, s.domain0.Cardinality+3) + copy(h2, s.h.Coefficients()[s.domain0.Cardinality+2:2*(s.domain0.Cardinality+2)]) + h2[0].Sub(&h2[0], &s.quotientShardsRandomizers[0]) + h2[s.domain0.Cardinality+2].Set(&s.quotientShardsRandomizers[1]) + } return h2 } func (s *instance) h3() []fr.Element { - h3 := s.h.Coefficients()[2*(s.domain0.Cardinality+2) : 3*(s.domain0.Cardinality+2)] + var h3 []fr.Element + if !s.opt.StatisticalZK { + h3 = s.h.Coefficients()[2*(s.domain0.Cardinality+2) : 3*(s.domain0.Cardinality+2)] + } else { + h3 = make([]fr.Element, s.domain0.Cardinality+2) + copy(h3, s.h.Coefficients()[2*(s.domain0.Cardinality+2):3*(s.domain0.Cardinality+2)]) + h3[0].Sub(&h3[0], &s.quotientShardsRandomizers[1]) + } return h3 } @@ -696,13 +725,6 @@ func (s *instance) computeLinearizedPolynomial() error { func (s *instance) batchOpening() error { - // wait for LRO to be committed (or ctx.Done()) - select { - case <-s.ctx.Done(): - return errContextDone - case <-s.chLRO: - } - // wait for linearizedPolynomial to be computed (or ctx.Done()) select { case <-s.ctx.Done(): @@ -772,7 +794,7 @@ func (s *instance) computeNumerator() (*iop.Polynomial, error) { case <-s.chQk: } - nbBsbGates := (len(s.x) - id_Qci + 1) >> 1 + nbBsbGates := len(s.proof.Bsb22Commitments) gateConstraint := func(u ...fr.Element) fr.Element { @@ -1077,7 +1099,7 @@ func evaluateBlinded(p, bp *iop.Polynomial, zeta fr.Element) fr.Element { return pEvaluatedAtZeta } -// /!\ modifies p's underlying array of coefficients, in particular the size changes +// /!\ modifies the size func getBlindedCoefficients(p, bp *iop.Polynomial) []fr.Element { cp := p.Coefficients() cbp := bp.Coefficients() @@ -1150,10 +1172,10 @@ func commitToQuotient(h1, h2, h3 []fr.Element, proof *Proof, kzgPk kzg.ProvingKe return g.Wait() } -// divideByXMinusOne +// divideByZH // The input must be in LagrangeCoset. // The result is in Canonical Regular. (in place using a) -func divideByXMinusOne(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { +func divideByZH(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { // check that the basis is LagrangeCoset if a.Basis != iop.LagrangeCoset || a.Layout != iop.BitReverse { @@ -1193,7 +1215,7 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { res[0].Exp(domains[1].FrMultiplicativeGen, expo) var t fr.Element - t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + t.Exp(domains[1].Generator, expo) one := fr.One() @@ -1220,6 +1242,8 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { // + α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*(β*s3(X))*Z(μζ) - Z(X)*(l(ζ)+β*id1(ζ)+γ)*(r(ζ)+β*id2(ζ)+γ)*(o(ζ)+β*id3(ζ)+γ)) // + l(ζ)*Ql(X) + l(ζ)r(ζ)*Qm(X) + r(ζ)*Qr(X) + o(ζ)*Qo(X) + Qk(X) + ∑ᵢQcp_(ζ)Pi_(X) // - Z_{H}(ζ)*((H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) +// +// /!\ blindedZCanonical is modified func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, zu fr.Element, qcpZeta, blindedZCanonical []fr.Element, pi2Canonical [][]fr.Element, pk *ProvingKey) []fr.Element { // l(ζ)r(ζ) @@ -1258,25 +1282,26 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, s2.Neg(&s2).Mul(&s2, &alpha) // Z_h(ζ), ζⁿ⁺², L₁(ζ)*α²*Z - var zhZeta, zetaNPlusTwo, alphaSquareLagrangeOne, one, den, frNbElmt fr.Element + var zhZeta, zetaNPlusTwo, alphaSquareLagrangeZero, one, den, frNbElmt fr.Element one.SetOne() nbElmt := int64(s.domain0.Cardinality) - alphaSquareLagrangeOne.Set(&zeta).Exp(alphaSquareLagrangeOne, big.NewInt(nbElmt)) // ζⁿ - zetaNPlusTwo.Mul(&alphaSquareLagrangeOne, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² - alphaSquareLagrangeOne.Sub(&alphaSquareLagrangeOne, &one) // ζⁿ - 1 - zhZeta.Set(&alphaSquareLagrangeOne) // Z_h(ζ) = ζⁿ - 1 + alphaSquareLagrangeZero.Set(&zeta).Exp(alphaSquareLagrangeZero, big.NewInt(nbElmt)) // ζⁿ + zetaNPlusTwo.Mul(&alphaSquareLagrangeZero, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² + alphaSquareLagrangeZero.Sub(&alphaSquareLagrangeZero, &one) // ζⁿ - 1 + zhZeta.Set(&alphaSquareLagrangeZero) // Z_h(ζ) = ζⁿ - 1 frNbElmt.SetUint64(uint64(nbElmt)) - den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) - alphaSquareLagrangeOne.Mul(&alphaSquareLagrangeOne, &den). // L₁ = (ζⁿ - 1)/(ζ-1) - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &s.domain0.CardinalityInv) // α²*L₁(ζ) + den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) + alphaSquareLagrangeZero.Mul(&alphaSquareLagrangeZero, &den). // L₁ = (ζⁿ - 1)/(ζ-1) + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &s.domain0.CardinalityInv) // α²*L₁(ζ) s3canonical := s.trace.S3.Coefficients() s.trace.Qk.ToCanonical(s.domain0).ToRegular() - // the hi are all of the same length + // len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 when Statistical ZK is activated + // len(h1)=len(h2)=len(h3)=len(blindedZCanonical)-1 when Statistical ZK is deactivated h1 := s.h1() h2 := s.h2() h3 := s.h3() @@ -1316,20 +1341,29 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, } } - t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeOne) // α²L₁(ζ)Z(X) - blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) + t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeZero) // α²L₁(ζ)Z(X) + blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) - if i < len(h1) { + // if statistical zeroknowledge is deactivated, len(h1)=len(h2)=len(h3)=len(blindedZ)-1. + // Else len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 + if i < len(h3) { t.Mul(&h3[i], &zetaNPlusTwo). Add(&t, &h2[i]). Mul(&t, &zetaNPlusTwo). - Add(&t, &h1[i]) - t.Mul(&t, &zhZeta) + Add(&t, &h1[i]). + Mul(&t, &zhZeta) blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } else { + if s.opt.StatisticalZK { + t.Mul(&h2[i], &zetaNPlusTwo). + Add(&t, &h1[i]). + Mul(&t, &zhZeta) + blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } } - } }) + return blindedZCanonical } diff --git a/backend/plonk/bls12-377/setup.go b/backend/plonk/bls12-377/setup.go index 0b9158e9a5..933d0e9038 100644 --- a/backend/plonk/bls12-377/setup.go +++ b/backend/plonk/bls12-377/setup.go @@ -18,7 +18,6 @@ package plonk import ( "fmt" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/fft" "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/iop" @@ -96,7 +95,7 @@ func Setup(spr *cs.SparseR1CS, srs, srsLagrange kzg.SRS) (*ProvingKey, *Verifyin // step 0: set the fft domains domain := initFFTDomain(spr) if domain.Cardinality < 2 { - return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", spr.GetNbConstraints()) + return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", len(spr.Public)+spr.GetNbConstraints()) } // check the size of the kzg srs. @@ -154,9 +153,7 @@ func (pk *ProvingKey) VerifyingKey() interface{} { func NewTrace(spr *cs.SparseR1CS, domain *fft.Domain) *Trace { var trace Trace - nbConstraints := spr.GetNbConstraints() - sizeSystem := uint64(nbConstraints + len(spr.Public)) - size := ecc.NextPowerOfTwo(sizeSystem) + size := int(domain.Cardinality) commitmentInfo := spr.CommitmentInfo.(constraint.PlonkCommitments) ql := make([]fr.Element, size) diff --git a/backend/plonk/bls12-377/verify.go b/backend/plonk/bls12-377/verify.go index c3df8fc8c0..9dd652cc0e 100644 --- a/backend/plonk/bls12-377/verify.go +++ b/backend/plonk/bls12-377/verify.go @@ -42,6 +42,7 @@ import ( var ( errAlgebraicRelation = errors.New("algebraic relation does not hold") errInvalidWitness = errors.New("witness length is invalid") + errInvalidPoint = errors.New("point is not on the curve") ) func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { @@ -61,6 +62,32 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac return errInvalidWitness } + // check that the points in the proof are on the curve + for i := 0; i < len(proof.LRO); i++ { + if !proof.LRO[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.Z.IsInSubGroup() { + return errInvalidPoint + } + for i := 0; i < len(proof.H); i++ { + if !proof.H[i].IsInSubGroup() { + return errInvalidPoint + } + } + for i := 0; i < len(proof.Bsb22Commitments); i++ { + if !proof.Bsb22Commitments[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.BatchedProof.H.IsInSubGroup() { + return errInvalidPoint + } + if !proof.ZShiftedOpening.H.IsInSubGroup() { + return errInvalidPoint + } + // transcript to derive the challenge fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") @@ -99,16 +126,16 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac } // evaluation of zhZeta=ζⁿ-1 - var zetaPowerM, zhZeta, lagrangeOne fr.Element + var zetaPowerM, zhZeta, lagrangeZero fr.Element var bExpo big.Int one := fr.One() bExpo.SetUint64(vk.Size) zetaPowerM.Exp(zeta, &bExpo) - zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 - lagrangeOne.Sub(&zeta, &one). // ζ-1 - Inverse(&lagrangeOne). // 1/(ζ-1) - Mul(&lagrangeOne, &zhZeta). // (ζ^n-1)/(ζ-1) - Mul(&lagrangeOne, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) + zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 + lagrangeZero.Sub(&zeta, &one). // ζ-1 + Inverse(&lagrangeZero). // 1/(ζ-1) + Mul(&lagrangeZero, &zhZeta). // (ζ^n-1)/(ζ-1) + Mul(&lagrangeZero, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) // compute PI = ∑_{i> 1 + nbBsbGates := len(s.proof.Bsb22Commitments) gateConstraint := func(u ...fr.Element) fr.Element { @@ -1077,7 +1099,7 @@ func evaluateBlinded(p, bp *iop.Polynomial, zeta fr.Element) fr.Element { return pEvaluatedAtZeta } -// /!\ modifies p's underlying array of coefficients, in particular the size changes +// /!\ modifies the size func getBlindedCoefficients(p, bp *iop.Polynomial) []fr.Element { cp := p.Coefficients() cbp := bp.Coefficients() @@ -1150,10 +1172,10 @@ func commitToQuotient(h1, h2, h3 []fr.Element, proof *Proof, kzgPk kzg.ProvingKe return g.Wait() } -// divideByXMinusOne +// divideByZH // The input must be in LagrangeCoset. // The result is in Canonical Regular. (in place using a) -func divideByXMinusOne(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { +func divideByZH(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { // check that the basis is LagrangeCoset if a.Basis != iop.LagrangeCoset || a.Layout != iop.BitReverse { @@ -1193,7 +1215,7 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { res[0].Exp(domains[1].FrMultiplicativeGen, expo) var t fr.Element - t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + t.Exp(domains[1].Generator, expo) one := fr.One() @@ -1220,6 +1242,8 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { // + α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*(β*s3(X))*Z(μζ) - Z(X)*(l(ζ)+β*id1(ζ)+γ)*(r(ζ)+β*id2(ζ)+γ)*(o(ζ)+β*id3(ζ)+γ)) // + l(ζ)*Ql(X) + l(ζ)r(ζ)*Qm(X) + r(ζ)*Qr(X) + o(ζ)*Qo(X) + Qk(X) + ∑ᵢQcp_(ζ)Pi_(X) // - Z_{H}(ζ)*((H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) +// +// /!\ blindedZCanonical is modified func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, zu fr.Element, qcpZeta, blindedZCanonical []fr.Element, pi2Canonical [][]fr.Element, pk *ProvingKey) []fr.Element { // l(ζ)r(ζ) @@ -1258,25 +1282,26 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, s2.Neg(&s2).Mul(&s2, &alpha) // Z_h(ζ), ζⁿ⁺², L₁(ζ)*α²*Z - var zhZeta, zetaNPlusTwo, alphaSquareLagrangeOne, one, den, frNbElmt fr.Element + var zhZeta, zetaNPlusTwo, alphaSquareLagrangeZero, one, den, frNbElmt fr.Element one.SetOne() nbElmt := int64(s.domain0.Cardinality) - alphaSquareLagrangeOne.Set(&zeta).Exp(alphaSquareLagrangeOne, big.NewInt(nbElmt)) // ζⁿ - zetaNPlusTwo.Mul(&alphaSquareLagrangeOne, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² - alphaSquareLagrangeOne.Sub(&alphaSquareLagrangeOne, &one) // ζⁿ - 1 - zhZeta.Set(&alphaSquareLagrangeOne) // Z_h(ζ) = ζⁿ - 1 + alphaSquareLagrangeZero.Set(&zeta).Exp(alphaSquareLagrangeZero, big.NewInt(nbElmt)) // ζⁿ + zetaNPlusTwo.Mul(&alphaSquareLagrangeZero, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² + alphaSquareLagrangeZero.Sub(&alphaSquareLagrangeZero, &one) // ζⁿ - 1 + zhZeta.Set(&alphaSquareLagrangeZero) // Z_h(ζ) = ζⁿ - 1 frNbElmt.SetUint64(uint64(nbElmt)) - den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) - alphaSquareLagrangeOne.Mul(&alphaSquareLagrangeOne, &den). // L₁ = (ζⁿ - 1)/(ζ-1) - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &s.domain0.CardinalityInv) // α²*L₁(ζ) + den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) + alphaSquareLagrangeZero.Mul(&alphaSquareLagrangeZero, &den). // L₁ = (ζⁿ - 1)/(ζ-1) + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &s.domain0.CardinalityInv) // α²*L₁(ζ) s3canonical := s.trace.S3.Coefficients() s.trace.Qk.ToCanonical(s.domain0).ToRegular() - // the hi are all of the same length + // len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 when Statistical ZK is activated + // len(h1)=len(h2)=len(h3)=len(blindedZCanonical)-1 when Statistical ZK is deactivated h1 := s.h1() h2 := s.h2() h3 := s.h3() @@ -1316,20 +1341,29 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, } } - t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeOne) // α²L₁(ζ)Z(X) - blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) + t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeZero) // α²L₁(ζ)Z(X) + blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) - if i < len(h1) { + // if statistical zeroknowledge is deactivated, len(h1)=len(h2)=len(h3)=len(blindedZ)-1. + // Else len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 + if i < len(h3) { t.Mul(&h3[i], &zetaNPlusTwo). Add(&t, &h2[i]). Mul(&t, &zetaNPlusTwo). - Add(&t, &h1[i]) - t.Mul(&t, &zhZeta) + Add(&t, &h1[i]). + Mul(&t, &zhZeta) blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } else { + if s.opt.StatisticalZK { + t.Mul(&h2[i], &zetaNPlusTwo). + Add(&t, &h1[i]). + Mul(&t, &zhZeta) + blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } } - } }) + return blindedZCanonical } diff --git a/backend/plonk/bls12-381/setup.go b/backend/plonk/bls12-381/setup.go index e0d7e50c06..16bc19bb53 100644 --- a/backend/plonk/bls12-381/setup.go +++ b/backend/plonk/bls12-381/setup.go @@ -18,7 +18,6 @@ package plonk import ( "fmt" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/fft" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/iop" @@ -96,7 +95,7 @@ func Setup(spr *cs.SparseR1CS, srs, srsLagrange kzg.SRS) (*ProvingKey, *Verifyin // step 0: set the fft domains domain := initFFTDomain(spr) if domain.Cardinality < 2 { - return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", spr.GetNbConstraints()) + return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", len(spr.Public)+spr.GetNbConstraints()) } // check the size of the kzg srs. @@ -154,9 +153,7 @@ func (pk *ProvingKey) VerifyingKey() interface{} { func NewTrace(spr *cs.SparseR1CS, domain *fft.Domain) *Trace { var trace Trace - nbConstraints := spr.GetNbConstraints() - sizeSystem := uint64(nbConstraints + len(spr.Public)) - size := ecc.NextPowerOfTwo(sizeSystem) + size := int(domain.Cardinality) commitmentInfo := spr.CommitmentInfo.(constraint.PlonkCommitments) ql := make([]fr.Element, size) diff --git a/backend/plonk/bls12-381/verify.go b/backend/plonk/bls12-381/verify.go index 9fa93ce23a..97006ffa6f 100644 --- a/backend/plonk/bls12-381/verify.go +++ b/backend/plonk/bls12-381/verify.go @@ -42,6 +42,7 @@ import ( var ( errAlgebraicRelation = errors.New("algebraic relation does not hold") errInvalidWitness = errors.New("witness length is invalid") + errInvalidPoint = errors.New("point is not on the curve") ) func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { @@ -61,6 +62,32 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac return errInvalidWitness } + // check that the points in the proof are on the curve + for i := 0; i < len(proof.LRO); i++ { + if !proof.LRO[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.Z.IsInSubGroup() { + return errInvalidPoint + } + for i := 0; i < len(proof.H); i++ { + if !proof.H[i].IsInSubGroup() { + return errInvalidPoint + } + } + for i := 0; i < len(proof.Bsb22Commitments); i++ { + if !proof.Bsb22Commitments[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.BatchedProof.H.IsInSubGroup() { + return errInvalidPoint + } + if !proof.ZShiftedOpening.H.IsInSubGroup() { + return errInvalidPoint + } + // transcript to derive the challenge fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") @@ -99,16 +126,16 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac } // evaluation of zhZeta=ζⁿ-1 - var zetaPowerM, zhZeta, lagrangeOne fr.Element + var zetaPowerM, zhZeta, lagrangeZero fr.Element var bExpo big.Int one := fr.One() bExpo.SetUint64(vk.Size) zetaPowerM.Exp(zeta, &bExpo) - zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 - lagrangeOne.Sub(&zeta, &one). // ζ-1 - Inverse(&lagrangeOne). // 1/(ζ-1) - Mul(&lagrangeOne, &zhZeta). // (ζ^n-1)/(ζ-1) - Mul(&lagrangeOne, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) + zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 + lagrangeZero.Sub(&zeta, &one). // ζ-1 + Inverse(&lagrangeZero). // 1/(ζ-1) + Mul(&lagrangeZero, &zhZeta). // (ζ^n-1)/(ζ-1) + Mul(&lagrangeZero, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) // compute PI = ∑_{i> 1 + nbBsbGates := len(s.proof.Bsb22Commitments) gateConstraint := func(u ...fr.Element) fr.Element { @@ -1077,7 +1099,7 @@ func evaluateBlinded(p, bp *iop.Polynomial, zeta fr.Element) fr.Element { return pEvaluatedAtZeta } -// /!\ modifies p's underlying array of coefficients, in particular the size changes +// /!\ modifies the size func getBlindedCoefficients(p, bp *iop.Polynomial) []fr.Element { cp := p.Coefficients() cbp := bp.Coefficients() @@ -1150,10 +1172,10 @@ func commitToQuotient(h1, h2, h3 []fr.Element, proof *Proof, kzgPk kzg.ProvingKe return g.Wait() } -// divideByXMinusOne +// divideByZH // The input must be in LagrangeCoset. // The result is in Canonical Regular. (in place using a) -func divideByXMinusOne(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { +func divideByZH(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { // check that the basis is LagrangeCoset if a.Basis != iop.LagrangeCoset || a.Layout != iop.BitReverse { @@ -1193,7 +1215,7 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { res[0].Exp(domains[1].FrMultiplicativeGen, expo) var t fr.Element - t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + t.Exp(domains[1].Generator, expo) one := fr.One() @@ -1220,6 +1242,8 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { // + α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*(β*s3(X))*Z(μζ) - Z(X)*(l(ζ)+β*id1(ζ)+γ)*(r(ζ)+β*id2(ζ)+γ)*(o(ζ)+β*id3(ζ)+γ)) // + l(ζ)*Ql(X) + l(ζ)r(ζ)*Qm(X) + r(ζ)*Qr(X) + o(ζ)*Qo(X) + Qk(X) + ∑ᵢQcp_(ζ)Pi_(X) // - Z_{H}(ζ)*((H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) +// +// /!\ blindedZCanonical is modified func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, zu fr.Element, qcpZeta, blindedZCanonical []fr.Element, pi2Canonical [][]fr.Element, pk *ProvingKey) []fr.Element { // l(ζ)r(ζ) @@ -1258,25 +1282,26 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, s2.Neg(&s2).Mul(&s2, &alpha) // Z_h(ζ), ζⁿ⁺², L₁(ζ)*α²*Z - var zhZeta, zetaNPlusTwo, alphaSquareLagrangeOne, one, den, frNbElmt fr.Element + var zhZeta, zetaNPlusTwo, alphaSquareLagrangeZero, one, den, frNbElmt fr.Element one.SetOne() nbElmt := int64(s.domain0.Cardinality) - alphaSquareLagrangeOne.Set(&zeta).Exp(alphaSquareLagrangeOne, big.NewInt(nbElmt)) // ζⁿ - zetaNPlusTwo.Mul(&alphaSquareLagrangeOne, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² - alphaSquareLagrangeOne.Sub(&alphaSquareLagrangeOne, &one) // ζⁿ - 1 - zhZeta.Set(&alphaSquareLagrangeOne) // Z_h(ζ) = ζⁿ - 1 + alphaSquareLagrangeZero.Set(&zeta).Exp(alphaSquareLagrangeZero, big.NewInt(nbElmt)) // ζⁿ + zetaNPlusTwo.Mul(&alphaSquareLagrangeZero, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² + alphaSquareLagrangeZero.Sub(&alphaSquareLagrangeZero, &one) // ζⁿ - 1 + zhZeta.Set(&alphaSquareLagrangeZero) // Z_h(ζ) = ζⁿ - 1 frNbElmt.SetUint64(uint64(nbElmt)) - den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) - alphaSquareLagrangeOne.Mul(&alphaSquareLagrangeOne, &den). // L₁ = (ζⁿ - 1)/(ζ-1) - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &s.domain0.CardinalityInv) // α²*L₁(ζ) + den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) + alphaSquareLagrangeZero.Mul(&alphaSquareLagrangeZero, &den). // L₁ = (ζⁿ - 1)/(ζ-1) + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &s.domain0.CardinalityInv) // α²*L₁(ζ) s3canonical := s.trace.S3.Coefficients() s.trace.Qk.ToCanonical(s.domain0).ToRegular() - // the hi are all of the same length + // len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 when Statistical ZK is activated + // len(h1)=len(h2)=len(h3)=len(blindedZCanonical)-1 when Statistical ZK is deactivated h1 := s.h1() h2 := s.h2() h3 := s.h3() @@ -1316,20 +1341,29 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, } } - t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeOne) // α²L₁(ζ)Z(X) - blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) + t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeZero) // α²L₁(ζ)Z(X) + blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) - if i < len(h1) { + // if statistical zeroknowledge is deactivated, len(h1)=len(h2)=len(h3)=len(blindedZ)-1. + // Else len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 + if i < len(h3) { t.Mul(&h3[i], &zetaNPlusTwo). Add(&t, &h2[i]). Mul(&t, &zetaNPlusTwo). - Add(&t, &h1[i]) - t.Mul(&t, &zhZeta) + Add(&t, &h1[i]). + Mul(&t, &zhZeta) blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } else { + if s.opt.StatisticalZK { + t.Mul(&h2[i], &zetaNPlusTwo). + Add(&t, &h1[i]). + Mul(&t, &zhZeta) + blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } } - } }) + return blindedZCanonical } diff --git a/backend/plonk/bls24-315/setup.go b/backend/plonk/bls24-315/setup.go index 8035bbed8a..7a6a4b5ac9 100644 --- a/backend/plonk/bls24-315/setup.go +++ b/backend/plonk/bls24-315/setup.go @@ -18,7 +18,6 @@ package plonk import ( "fmt" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" "github.com/consensys/gnark-crypto/ecc/bls24-315/fr/fft" "github.com/consensys/gnark-crypto/ecc/bls24-315/fr/iop" @@ -96,7 +95,7 @@ func Setup(spr *cs.SparseR1CS, srs, srsLagrange kzg.SRS) (*ProvingKey, *Verifyin // step 0: set the fft domains domain := initFFTDomain(spr) if domain.Cardinality < 2 { - return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", spr.GetNbConstraints()) + return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", len(spr.Public)+spr.GetNbConstraints()) } // check the size of the kzg srs. @@ -154,9 +153,7 @@ func (pk *ProvingKey) VerifyingKey() interface{} { func NewTrace(spr *cs.SparseR1CS, domain *fft.Domain) *Trace { var trace Trace - nbConstraints := spr.GetNbConstraints() - sizeSystem := uint64(nbConstraints + len(spr.Public)) - size := ecc.NextPowerOfTwo(sizeSystem) + size := int(domain.Cardinality) commitmentInfo := spr.CommitmentInfo.(constraint.PlonkCommitments) ql := make([]fr.Element, size) diff --git a/backend/plonk/bls24-315/verify.go b/backend/plonk/bls24-315/verify.go index 3b1385b347..2a646df122 100644 --- a/backend/plonk/bls24-315/verify.go +++ b/backend/plonk/bls24-315/verify.go @@ -42,6 +42,7 @@ import ( var ( errAlgebraicRelation = errors.New("algebraic relation does not hold") errInvalidWitness = errors.New("witness length is invalid") + errInvalidPoint = errors.New("point is not on the curve") ) func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { @@ -61,6 +62,32 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac return errInvalidWitness } + // check that the points in the proof are on the curve + for i := 0; i < len(proof.LRO); i++ { + if !proof.LRO[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.Z.IsInSubGroup() { + return errInvalidPoint + } + for i := 0; i < len(proof.H); i++ { + if !proof.H[i].IsInSubGroup() { + return errInvalidPoint + } + } + for i := 0; i < len(proof.Bsb22Commitments); i++ { + if !proof.Bsb22Commitments[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.BatchedProof.H.IsInSubGroup() { + return errInvalidPoint + } + if !proof.ZShiftedOpening.H.IsInSubGroup() { + return errInvalidPoint + } + // transcript to derive the challenge fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") @@ -99,16 +126,16 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac } // evaluation of zhZeta=ζⁿ-1 - var zetaPowerM, zhZeta, lagrangeOne fr.Element + var zetaPowerM, zhZeta, lagrangeZero fr.Element var bExpo big.Int one := fr.One() bExpo.SetUint64(vk.Size) zetaPowerM.Exp(zeta, &bExpo) - zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 - lagrangeOne.Sub(&zeta, &one). // ζ-1 - Inverse(&lagrangeOne). // 1/(ζ-1) - Mul(&lagrangeOne, &zhZeta). // (ζ^n-1)/(ζ-1) - Mul(&lagrangeOne, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) + zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 + lagrangeZero.Sub(&zeta, &one). // ζ-1 + Inverse(&lagrangeZero). // 1/(ζ-1) + Mul(&lagrangeZero, &zhZeta). // (ζ^n-1)/(ζ-1) + Mul(&lagrangeZero, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) // compute PI = ∑_{i> 1 + nbBsbGates := len(s.proof.Bsb22Commitments) gateConstraint := func(u ...fr.Element) fr.Element { @@ -1077,7 +1099,7 @@ func evaluateBlinded(p, bp *iop.Polynomial, zeta fr.Element) fr.Element { return pEvaluatedAtZeta } -// /!\ modifies p's underlying array of coefficients, in particular the size changes +// /!\ modifies the size func getBlindedCoefficients(p, bp *iop.Polynomial) []fr.Element { cp := p.Coefficients() cbp := bp.Coefficients() @@ -1150,10 +1172,10 @@ func commitToQuotient(h1, h2, h3 []fr.Element, proof *Proof, kzgPk kzg.ProvingKe return g.Wait() } -// divideByXMinusOne +// divideByZH // The input must be in LagrangeCoset. // The result is in Canonical Regular. (in place using a) -func divideByXMinusOne(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { +func divideByZH(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { // check that the basis is LagrangeCoset if a.Basis != iop.LagrangeCoset || a.Layout != iop.BitReverse { @@ -1193,7 +1215,7 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { res[0].Exp(domains[1].FrMultiplicativeGen, expo) var t fr.Element - t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + t.Exp(domains[1].Generator, expo) one := fr.One() @@ -1220,6 +1242,8 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { // + α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*(β*s3(X))*Z(μζ) - Z(X)*(l(ζ)+β*id1(ζ)+γ)*(r(ζ)+β*id2(ζ)+γ)*(o(ζ)+β*id3(ζ)+γ)) // + l(ζ)*Ql(X) + l(ζ)r(ζ)*Qm(X) + r(ζ)*Qr(X) + o(ζ)*Qo(X) + Qk(X) + ∑ᵢQcp_(ζ)Pi_(X) // - Z_{H}(ζ)*((H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) +// +// /!\ blindedZCanonical is modified func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, zu fr.Element, qcpZeta, blindedZCanonical []fr.Element, pi2Canonical [][]fr.Element, pk *ProvingKey) []fr.Element { // l(ζ)r(ζ) @@ -1258,25 +1282,26 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, s2.Neg(&s2).Mul(&s2, &alpha) // Z_h(ζ), ζⁿ⁺², L₁(ζ)*α²*Z - var zhZeta, zetaNPlusTwo, alphaSquareLagrangeOne, one, den, frNbElmt fr.Element + var zhZeta, zetaNPlusTwo, alphaSquareLagrangeZero, one, den, frNbElmt fr.Element one.SetOne() nbElmt := int64(s.domain0.Cardinality) - alphaSquareLagrangeOne.Set(&zeta).Exp(alphaSquareLagrangeOne, big.NewInt(nbElmt)) // ζⁿ - zetaNPlusTwo.Mul(&alphaSquareLagrangeOne, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² - alphaSquareLagrangeOne.Sub(&alphaSquareLagrangeOne, &one) // ζⁿ - 1 - zhZeta.Set(&alphaSquareLagrangeOne) // Z_h(ζ) = ζⁿ - 1 + alphaSquareLagrangeZero.Set(&zeta).Exp(alphaSquareLagrangeZero, big.NewInt(nbElmt)) // ζⁿ + zetaNPlusTwo.Mul(&alphaSquareLagrangeZero, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² + alphaSquareLagrangeZero.Sub(&alphaSquareLagrangeZero, &one) // ζⁿ - 1 + zhZeta.Set(&alphaSquareLagrangeZero) // Z_h(ζ) = ζⁿ - 1 frNbElmt.SetUint64(uint64(nbElmt)) - den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) - alphaSquareLagrangeOne.Mul(&alphaSquareLagrangeOne, &den). // L₁ = (ζⁿ - 1)/(ζ-1) - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &s.domain0.CardinalityInv) // α²*L₁(ζ) + den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) + alphaSquareLagrangeZero.Mul(&alphaSquareLagrangeZero, &den). // L₁ = (ζⁿ - 1)/(ζ-1) + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &s.domain0.CardinalityInv) // α²*L₁(ζ) s3canonical := s.trace.S3.Coefficients() s.trace.Qk.ToCanonical(s.domain0).ToRegular() - // the hi are all of the same length + // len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 when Statistical ZK is activated + // len(h1)=len(h2)=len(h3)=len(blindedZCanonical)-1 when Statistical ZK is deactivated h1 := s.h1() h2 := s.h2() h3 := s.h3() @@ -1316,20 +1341,29 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, } } - t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeOne) // α²L₁(ζ)Z(X) - blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) + t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeZero) // α²L₁(ζ)Z(X) + blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) - if i < len(h1) { + // if statistical zeroknowledge is deactivated, len(h1)=len(h2)=len(h3)=len(blindedZ)-1. + // Else len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 + if i < len(h3) { t.Mul(&h3[i], &zetaNPlusTwo). Add(&t, &h2[i]). Mul(&t, &zetaNPlusTwo). - Add(&t, &h1[i]) - t.Mul(&t, &zhZeta) + Add(&t, &h1[i]). + Mul(&t, &zhZeta) blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } else { + if s.opt.StatisticalZK { + t.Mul(&h2[i], &zetaNPlusTwo). + Add(&t, &h1[i]). + Mul(&t, &zhZeta) + blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } } - } }) + return blindedZCanonical } diff --git a/backend/plonk/bls24-317/setup.go b/backend/plonk/bls24-317/setup.go index 1359bb5097..212657f6a0 100644 --- a/backend/plonk/bls24-317/setup.go +++ b/backend/plonk/bls24-317/setup.go @@ -18,7 +18,6 @@ package plonk import ( "fmt" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bls24-317/fr" "github.com/consensys/gnark-crypto/ecc/bls24-317/fr/fft" "github.com/consensys/gnark-crypto/ecc/bls24-317/fr/iop" @@ -96,7 +95,7 @@ func Setup(spr *cs.SparseR1CS, srs, srsLagrange kzg.SRS) (*ProvingKey, *Verifyin // step 0: set the fft domains domain := initFFTDomain(spr) if domain.Cardinality < 2 { - return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", spr.GetNbConstraints()) + return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", len(spr.Public)+spr.GetNbConstraints()) } // check the size of the kzg srs. @@ -154,9 +153,7 @@ func (pk *ProvingKey) VerifyingKey() interface{} { func NewTrace(spr *cs.SparseR1CS, domain *fft.Domain) *Trace { var trace Trace - nbConstraints := spr.GetNbConstraints() - sizeSystem := uint64(nbConstraints + len(spr.Public)) - size := ecc.NextPowerOfTwo(sizeSystem) + size := int(domain.Cardinality) commitmentInfo := spr.CommitmentInfo.(constraint.PlonkCommitments) ql := make([]fr.Element, size) diff --git a/backend/plonk/bls24-317/verify.go b/backend/plonk/bls24-317/verify.go index c625bf83d5..a5d7049753 100644 --- a/backend/plonk/bls24-317/verify.go +++ b/backend/plonk/bls24-317/verify.go @@ -42,6 +42,7 @@ import ( var ( errAlgebraicRelation = errors.New("algebraic relation does not hold") errInvalidWitness = errors.New("witness length is invalid") + errInvalidPoint = errors.New("point is not on the curve") ) func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { @@ -61,6 +62,32 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac return errInvalidWitness } + // check that the points in the proof are on the curve + for i := 0; i < len(proof.LRO); i++ { + if !proof.LRO[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.Z.IsInSubGroup() { + return errInvalidPoint + } + for i := 0; i < len(proof.H); i++ { + if !proof.H[i].IsInSubGroup() { + return errInvalidPoint + } + } + for i := 0; i < len(proof.Bsb22Commitments); i++ { + if !proof.Bsb22Commitments[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.BatchedProof.H.IsInSubGroup() { + return errInvalidPoint + } + if !proof.ZShiftedOpening.H.IsInSubGroup() { + return errInvalidPoint + } + // transcript to derive the challenge fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") @@ -99,16 +126,16 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac } // evaluation of zhZeta=ζⁿ-1 - var zetaPowerM, zhZeta, lagrangeOne fr.Element + var zetaPowerM, zhZeta, lagrangeZero fr.Element var bExpo big.Int one := fr.One() bExpo.SetUint64(vk.Size) zetaPowerM.Exp(zeta, &bExpo) - zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 - lagrangeOne.Sub(&zeta, &one). // ζ-1 - Inverse(&lagrangeOne). // 1/(ζ-1) - Mul(&lagrangeOne, &zhZeta). // (ζ^n-1)/(ζ-1) - Mul(&lagrangeOne, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) + zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 + lagrangeZero.Sub(&zeta, &one). // ζ-1 + Inverse(&lagrangeZero). // 1/(ζ-1) + Mul(&lagrangeZero, &zhZeta). // (ζ^n-1)/(ζ-1) + Mul(&lagrangeZero, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) // compute PI = ∑_{i> 1 + nbBsbGates := len(s.proof.Bsb22Commitments) gateConstraint := func(u ...fr.Element) fr.Element { @@ -1077,7 +1099,7 @@ func evaluateBlinded(p, bp *iop.Polynomial, zeta fr.Element) fr.Element { return pEvaluatedAtZeta } -// /!\ modifies p's underlying array of coefficients, in particular the size changes +// /!\ modifies the size func getBlindedCoefficients(p, bp *iop.Polynomial) []fr.Element { cp := p.Coefficients() cbp := bp.Coefficients() @@ -1150,10 +1172,10 @@ func commitToQuotient(h1, h2, h3 []fr.Element, proof *Proof, kzgPk kzg.ProvingKe return g.Wait() } -// divideByXMinusOne +// divideByZH // The input must be in LagrangeCoset. // The result is in Canonical Regular. (in place using a) -func divideByXMinusOne(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { +func divideByZH(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { // check that the basis is LagrangeCoset if a.Basis != iop.LagrangeCoset || a.Layout != iop.BitReverse { @@ -1193,7 +1215,7 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { res[0].Exp(domains[1].FrMultiplicativeGen, expo) var t fr.Element - t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + t.Exp(domains[1].Generator, expo) one := fr.One() @@ -1220,6 +1242,8 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { // + α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*(β*s3(X))*Z(μζ) - Z(X)*(l(ζ)+β*id1(ζ)+γ)*(r(ζ)+β*id2(ζ)+γ)*(o(ζ)+β*id3(ζ)+γ)) // + l(ζ)*Ql(X) + l(ζ)r(ζ)*Qm(X) + r(ζ)*Qr(X) + o(ζ)*Qo(X) + Qk(X) + ∑ᵢQcp_(ζ)Pi_(X) // - Z_{H}(ζ)*((H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) +// +// /!\ blindedZCanonical is modified func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, zu fr.Element, qcpZeta, blindedZCanonical []fr.Element, pi2Canonical [][]fr.Element, pk *ProvingKey) []fr.Element { // l(ζ)r(ζ) @@ -1258,25 +1282,26 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, s2.Neg(&s2).Mul(&s2, &alpha) // Z_h(ζ), ζⁿ⁺², L₁(ζ)*α²*Z - var zhZeta, zetaNPlusTwo, alphaSquareLagrangeOne, one, den, frNbElmt fr.Element + var zhZeta, zetaNPlusTwo, alphaSquareLagrangeZero, one, den, frNbElmt fr.Element one.SetOne() nbElmt := int64(s.domain0.Cardinality) - alphaSquareLagrangeOne.Set(&zeta).Exp(alphaSquareLagrangeOne, big.NewInt(nbElmt)) // ζⁿ - zetaNPlusTwo.Mul(&alphaSquareLagrangeOne, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² - alphaSquareLagrangeOne.Sub(&alphaSquareLagrangeOne, &one) // ζⁿ - 1 - zhZeta.Set(&alphaSquareLagrangeOne) // Z_h(ζ) = ζⁿ - 1 + alphaSquareLagrangeZero.Set(&zeta).Exp(alphaSquareLagrangeZero, big.NewInt(nbElmt)) // ζⁿ + zetaNPlusTwo.Mul(&alphaSquareLagrangeZero, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² + alphaSquareLagrangeZero.Sub(&alphaSquareLagrangeZero, &one) // ζⁿ - 1 + zhZeta.Set(&alphaSquareLagrangeZero) // Z_h(ζ) = ζⁿ - 1 frNbElmt.SetUint64(uint64(nbElmt)) - den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) - alphaSquareLagrangeOne.Mul(&alphaSquareLagrangeOne, &den). // L₁ = (ζⁿ - 1)/(ζ-1) - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &s.domain0.CardinalityInv) // α²*L₁(ζ) + den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) + alphaSquareLagrangeZero.Mul(&alphaSquareLagrangeZero, &den). // L₁ = (ζⁿ - 1)/(ζ-1) + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &s.domain0.CardinalityInv) // α²*L₁(ζ) s3canonical := s.trace.S3.Coefficients() s.trace.Qk.ToCanonical(s.domain0).ToRegular() - // the hi are all of the same length + // len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 when Statistical ZK is activated + // len(h1)=len(h2)=len(h3)=len(blindedZCanonical)-1 when Statistical ZK is deactivated h1 := s.h1() h2 := s.h2() h3 := s.h3() @@ -1316,20 +1341,29 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, } } - t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeOne) // α²L₁(ζ)Z(X) - blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) + t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeZero) // α²L₁(ζ)Z(X) + blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) - if i < len(h1) { + // if statistical zeroknowledge is deactivated, len(h1)=len(h2)=len(h3)=len(blindedZ)-1. + // Else len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 + if i < len(h3) { t.Mul(&h3[i], &zetaNPlusTwo). Add(&t, &h2[i]). Mul(&t, &zetaNPlusTwo). - Add(&t, &h1[i]) - t.Mul(&t, &zhZeta) + Add(&t, &h1[i]). + Mul(&t, &zhZeta) blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } else { + if s.opt.StatisticalZK { + t.Mul(&h2[i], &zetaNPlusTwo). + Add(&t, &h1[i]). + Mul(&t, &zhZeta) + blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } } - } }) + return blindedZCanonical } diff --git a/backend/plonk/bn254/setup.go b/backend/plonk/bn254/setup.go index 5d916034f6..0b01ec1cf0 100644 --- a/backend/plonk/bn254/setup.go +++ b/backend/plonk/bn254/setup.go @@ -18,7 +18,6 @@ package plonk import ( "fmt" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bn254/fr" "github.com/consensys/gnark-crypto/ecc/bn254/fr/fft" "github.com/consensys/gnark-crypto/ecc/bn254/fr/iop" @@ -96,7 +95,7 @@ func Setup(spr *cs.SparseR1CS, srs, srsLagrange kzg.SRS) (*ProvingKey, *Verifyin // step 0: set the fft domains domain := initFFTDomain(spr) if domain.Cardinality < 2 { - return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", spr.GetNbConstraints()) + return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", len(spr.Public)+spr.GetNbConstraints()) } // check the size of the kzg srs. @@ -154,9 +153,7 @@ func (pk *ProvingKey) VerifyingKey() interface{} { func NewTrace(spr *cs.SparseR1CS, domain *fft.Domain) *Trace { var trace Trace - nbConstraints := spr.GetNbConstraints() - sizeSystem := uint64(nbConstraints + len(spr.Public)) - size := ecc.NextPowerOfTwo(sizeSystem) + size := int(domain.Cardinality) commitmentInfo := spr.CommitmentInfo.(constraint.PlonkCommitments) ql := make([]fr.Element, size) diff --git a/backend/plonk/bn254/solidity.go b/backend/plonk/bn254/solidity.go index b4c8361400..29966d952d 100644 --- a/backend/plonk/bn254/solidity.go +++ b/backend/plonk/bn254/solidity.go @@ -67,6 +67,9 @@ contract PlonkVerifier { // ------------------------------------------------ + // size of the proof without call custom gate + uint256 private constant FIXED_PROOF_SIZE = 0x300; + // offset proof {{ $offset := 0 }} uint256 private constant PROOF_L_COM_X = {{ hex $offset }};{{ $offset = add $offset 0x20}} @@ -77,14 +80,14 @@ contract PlonkVerifier { uint256 private constant PROOF_O_COM_Y = {{ hex $offset }};{{ $offset = add $offset 0x20}} // h = h_0 + x^{n+2}h_1 + x^{2(n+2)}h_2 - uint256 private constant PROOF_H_0_X = {{ hex $offset }};{{ $offset = add $offset 0x20}} - uint256 private constant PROOF_H_0_Y = {{ hex $offset }};{{ $offset = add $offset 0x20}} - uint256 private constant PROOF_H_1_X = {{ hex $offset }};{{ $offset = add $offset 0x20}} - uint256 private constant PROOF_H_1_Y = {{ hex $offset }};{{ $offset = add $offset 0x20}} - uint256 private constant PROOF_H_2_X = {{ hex $offset }};{{ $offset = add $offset 0x20}} - uint256 private constant PROOF_H_2_Y = {{ hex $offset }};{{ $offset = add $offset 0x20}} - - // wire values at zeta + uint256 private constant PROOF_H_0_COM_X = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant PROOF_H_0_COM_Y = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant PROOF_H_1_COM_X = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant PROOF_H_1_COM_Y = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant PROOF_H_2_COM_X = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant PROOF_H_2_COM_Y = {{ hex $offset }};{{ $offset = add $offset 0x20}} + + // "evaluations of wire polynomials at zeta uint256 private constant PROOF_L_AT_ZETA = {{ hex $offset }};{{ $offset = add $offset 0x20}} uint256 private constant PROOF_R_AT_ZETA = {{ hex $offset }};{{ $offset = add $offset 0x20}} uint256 private constant PROOF_O_AT_ZETA = {{ hex $offset }};{{ $offset = add $offset 0x20}} @@ -109,9 +112,6 @@ contract PlonkVerifier { uint256 private constant PROOF_OPENING_QCP_AT_ZETA = {{ hex $offset }}; uint256 private constant PROOF_BSB_COMMITMENTS = {{ hex (add $offset (mul (len .Vk.CommitmentConstraintIndexes) 32 ) )}}; - // -> next part of proof is - // [ openings_selector_commits || commitments_wires_commit_api] - // -------- offset state // challenges to check the claimed quotient @@ -127,7 +127,7 @@ contract PlonkVerifier { uint256 private constant STATE_LINEARISED_POLYNOMIAL_Y = {{ hex $offset }};{{ $offset = add $offset 0x20}} uint256 private constant STATE_OPENING_LINEARISED_POLYNOMIAL_ZETA = {{ hex $offset }};{{ $offset = add $offset 0x20}} uint256 private constant STATE_FOLDED_CLAIMED_VALUES = {{ hex $offset }};{{ $offset = add $offset 0x20}} // Folded proof for the opening of H, linearised poly, l, r, o, s_1, s_2, qcp - uint256 private constant STATE_FOLDED_DIGESTS_X = {{ hex $offset }};{{ $offset = add $offset 0x20}} // folded digests of H, linearised poly, l, r, o, s_1, s_2, qcp + uint256 private constant STATE_FOLDED_DIGESTS_X = {{ hex $offset }};{{ $offset = add $offset 0x20}} // linearised poly, l, r, o, s_1, s_2, qcp uint256 private constant STATE_FOLDED_DIGESTS_Y = {{ hex $offset }};{{ $offset = add $offset 0x20}} uint256 private constant STATE_PI = {{ hex $offset }};{{ $offset = add $offset 0x20}} uint256 private constant STATE_ZETA_POWER_N_MINUS_ONE = {{ hex $offset }};{{ $offset = add $offset 0x20}} @@ -157,6 +157,7 @@ contract PlonkVerifier { {{ end }} // -------- precompiles + uint8 private constant SHA2 = 0x2; uint8 private constant MOD_EXP = 0x5; uint8 private constant EC_ADD = 0x6; uint8 private constant EC_MUL = 0x7; @@ -202,7 +203,7 @@ contract PlonkVerifier { mstore(add(mem, STATE_PI), l_pi) compute_alpha_square_lagrange_0() - verify_opening_linearised_polynomial(proof.offset) + compute_opening_linearised_polynomial(proof.offset) fold_h(proof.offset) compute_commitment_linearised_polynomial(proof.offset) compute_gamma_kzg(proof.offset) @@ -222,6 +223,16 @@ contract PlonkVerifier { revert(ptError, 0x64) } + /// Called when an exponentiation mod r fails + function error_mod_exp() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0xc) + mstore(add(ptError, 0x44), "error mod exp") + revert(ptError, 0x64) + } + /// Called when an operation on Bn254 fails /// @dev for instance when calling EcMul on a point not on Bn254. function error_ec_op() { @@ -319,7 +330,7 @@ contract PlonkVerifier { /// Checks if the proof is of the correct size /// @param actual_proof_size size of the proof (not the expected size) function check_proof_size(actual_proof_size) { - let expected_proof_size := add(0x300, mul(VK_NB_CUSTOM_GATES,0x60)) + let expected_proof_size := add(FIXED_PROOF_SIZE, mul(VK_NB_CUSTOM_GATES,0x60)) if iszero(eq(actual_proof_size, expected_proof_size)) { error_proof_size() } @@ -403,30 +414,28 @@ contract PlonkVerifier { let state := mload(0x40) let mPtr := add(state, STATE_LAST_MEM) - // gamma - // gamma in ascii is [0x67,0x61,0x6d, 0x6d, 0x61] - // (same for alpha, beta, zeta) mstore(mPtr, FS_GAMMA) // "gamma" - mstore(add(mPtr, 0x20), VK_S1_COM_X) - mstore(add(mPtr, 0x40), VK_S1_COM_Y) - mstore(add(mPtr, 0x60), VK_S2_COM_X) - mstore(add(mPtr, 0x80), VK_S2_COM_Y) - mstore(add(mPtr, 0xa0), VK_S3_COM_X) - mstore(add(mPtr, 0xc0), VK_S3_COM_Y) - mstore(add(mPtr, 0xe0), VK_QL_COM_X) - mstore(add(mPtr, 0x100), VK_QL_COM_Y) - mstore(add(mPtr, 0x120), VK_QR_COM_X) - mstore(add(mPtr, 0x140), VK_QR_COM_Y) - mstore(add(mPtr, 0x160), VK_QM_COM_X) - mstore(add(mPtr, 0x180), VK_QM_COM_Y) - mstore(add(mPtr, 0x1a0), VK_QO_COM_X) - mstore(add(mPtr, 0x1c0), VK_QO_COM_Y) - mstore(add(mPtr, 0x1e0), VK_QK_COM_X) - mstore(add(mPtr, 0x200), VK_QK_COM_Y) + {{ $offset = 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), VK_S1_COM_X) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_S1_COM_Y) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_S2_COM_X) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_S2_COM_Y) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_S3_COM_X) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_S3_COM_Y) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_QL_COM_X) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_QL_COM_Y) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_QR_COM_X) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_QR_COM_Y) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_QM_COM_X) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_QM_COM_Y) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_QO_COM_X) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_QO_COM_Y) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_QK_COM_X) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_QK_COM_Y) {{ $offset = add $offset 0x20}} {{ range $index, $element := .Vk.CommitmentConstraintIndexes}} - mstore(add(mPtr, {{ hex (add 544 (mul $index 64)) }}), VK_QCP_{{ $index }}_X) - mstore(add(mPtr, {{ hex (add 576 (mul $index 64)) }}), VK_QCP_{{ $index }}_Y) + mstore(add(mPtr, {{ hex $offset }}), VK_QCP_{{ $index }}_X) {{ $offset = add $offset 0x20}} + mstore(add(mPtr, {{ hex $offset }}), VK_QCP_{{ $index }}_Y) {{ $offset = add $offset 0x20}} {{ end }} // public inputs let _mPtr := add(mPtr, {{ hex (add (mul (len .Vk.CommitmentConstraintIndexes) 64) 544) }}) @@ -447,7 +456,7 @@ contract PlonkVerifier { {{ if (gt (len .Vk.CommitmentConstraintIndexes) 0 )}} size := add(size, mul(VK_NB_CUSTOM_GATES, 0x40)) {{ end -}} - let l_success := staticcall(gas(), 0x2, add(mPtr, 0x1b), size, mPtr, 0x20) //0x1b -> 000.."gamma" + let l_success := staticcall(gas(), SHA2, add(mPtr, 0x1b), size, mPtr, 0x20) //0x1b -> 000.."gamma" if iszero(l_success) { error_verify() } @@ -468,7 +477,7 @@ contract PlonkVerifier { // beta mstore(mPtr, FS_BETA) // "beta" mstore(add(mPtr, 0x20), gamma_not_reduced) - let l_success := staticcall(gas(), 0x2, add(mPtr, 0x1c), 0x24, mPtr, 0x20) //0x1b -> 000.."gamma" + let l_success := staticcall(gas(), SHA2, add(mPtr, 0x1c), 0x24, mPtr, 0x20) //0x1b -> 000.."gamma" if iszero(l_success) { error_verify() } @@ -504,7 +513,7 @@ contract PlonkVerifier { {{ end }} // [Z], the commitment to the grand product polynomial calldatacopy(_mPtr, add(aproof, PROOF_GRAND_PRODUCT_COMMITMENT_X), 0x40) - let l_success := staticcall(gas(), 0x2, add(mPtr, 0x1b), full_size, mPtr, 0x20) + let l_success := staticcall(gas(), SHA2, add(mPtr, 0x1b), full_size, mPtr, 0x20) if iszero(l_success) { error_verify() } @@ -526,8 +535,8 @@ contract PlonkVerifier { // zeta mstore(mPtr, FS_ZETA) // "zeta" mstore(add(mPtr, 0x20), alpha_not_reduced) - calldatacopy(add(mPtr, 0x40), add(aproof, PROOF_H_0_X), 0xc0) - let l_success := staticcall(gas(), 0x2, add(mPtr, 0x1c), 0xe4, mPtr, 0x20) + calldatacopy(add(mPtr, 0x40), add(aproof, PROOF_H_0_COM_X), 0xc0) + let l_success := staticcall(gas(), SHA2, add(mPtr, 0x1c), 0xe4, mPtr, 0x20) if iszero(l_success) { error_verify() } @@ -567,24 +576,24 @@ contract PlonkVerifier { /// batch_compute_lagranges_at_z computes [L_0(z), .., L_{n-1}(z)] /// @param z point at which the Lagranges are evaluated /// @param zpnmo ζⁿ-1 - /// @param n number of public inputs (number of Lagranges to compute) + /// @param n_pub number of public inputs (number of Lagranges to compute) /// @param mPtr pointer to which the results are stored - function batch_compute_lagranges_at_z(z, zpnmo, n, mPtr) { + function batch_compute_lagranges_at_z(z, zpnmo, n_pub, mPtr) { let zn := mulmod(zpnmo, VK_INV_DOMAIN_SIZE, R_MOD) // 1/n * (ζⁿ - 1) let _w := 1 let _mPtr := mPtr - for {let i:=0} lt(i,n) {i:=add(i,1)} + for {let i:=0} lt(i,n_pub) {i:=add(i,1)} { mstore(_mPtr, addmod(z,sub(R_MOD, _w), R_MOD)) _w := mulmod(_w, VK_OMEGA, R_MOD) _mPtr := add(_mPtr, 0x20) } - batch_invert(mPtr, n, _mPtr) + batch_invert(mPtr, n_pub, _mPtr) _mPtr := mPtr _w := 1 - for {let i:=0} lt(i,n) {i:=add(i,1)} + for {let i:=0} lt(i,n_pub) {i:=add(i,1)} { mstore(_mPtr, mulmod(mulmod(mload(_mPtr), zn , R_MOD), _w, R_MOD)) _mPtr := add(_mPtr, 0x20) @@ -641,8 +650,10 @@ contract PlonkVerifier { h_fr := hash_fr(calldataload(p), calldataload(add(p, 0x20)), mPtr) ith_lagrange := compute_ith_lagrange_at_z(z, zpnmo, add(nb_public_inputs, VK_INDEX_COMMIT_API_{{ $index }}), mPtr) pi_commit := addmod(pi_commit, mulmod(h_fr, ith_lagrange, R_MOD), R_MOD) + {{ if (lt (inc $index) (len $.Vk.CommitmentConstraintIndexes) )}} p := add(p, 0x40) {{ end }} + {{ end }} } @@ -702,7 +713,7 @@ contract PlonkVerifier { // size domain mstore8(add(mPtr, 0x8e), HASH_FR_SIZE_DOMAIN) - let l_success := staticcall(gas(), 0x2, mPtr, 0x8f, mPtr, 0x20) + let l_success := staticcall(gas(), SHA2, mPtr, 0x8f, mPtr, 0x20) if iszero(l_success) { error_verify() } @@ -726,7 +737,7 @@ contract PlonkVerifier { mstore8(add(mPtr, 0x2b), 0x6b) mstore8(add(mPtr, 0x2c), HASH_FR_SIZE_DOMAIN) // size domain - l_success := staticcall(gas(), 0x2, mPtr, 0x2d, mPtr, 0x20) + l_success := staticcall(gas(), SHA2, mPtr, 0x2d, mPtr, 0x20) if iszero(l_success) { error_verify() } @@ -753,14 +764,14 @@ contract PlonkVerifier { mstore8(add(mPtr, 0x4c), HASH_FR_SIZE_DOMAIN) // size domain let offset := add(mPtr, 0x20) - l_success := staticcall(gas(), 0x2, offset, 0x2d, offset, 0x20) + l_success := staticcall(gas(), SHA2, offset, 0x2d, offset, 0x20) if iszero(l_success) { error_verify() } // at this point we have mPtr = [ b1 || b2] where b1 is on 32byes and b2 in 16bytes. // we interpret it as a big integer mod r in big endian (similar to regular decimal notation) - // the result is then 2**(8*16)*mPtr[32:] + mPtr[32:48] + // the result is then 2**(8*16)*mPtr[:32] + mPtr[32:48] res := mulmod(mload(mPtr), HASH_FR_BB, R_MOD) // <- res = 2**128 * mPtr[:32] let b1 := shr(128, mload(add(mPtr, 0x20))) // b1 <- [0, 0, .., 0 || b2[:16] ] res := addmod(res, b1, R_MOD) @@ -801,7 +812,7 @@ contract PlonkVerifier { // derive a random number. As there is no random generator, we // do an FS like challenge derivation, depending on both digests and - // ζ to ensure that the prover cannot control the random numger. + // ζ to ensure that the prover cannot control the random number. // Note: adding the other point ζω is not needed, as ω is known beforehand. mstore(mPtr, mload(add(state, STATE_FOLDED_DIGESTS_X))) mstore(add(mPtr, 0x20), mload(add(state, STATE_FOLDED_DIGESTS_Y))) @@ -813,7 +824,7 @@ contract PlonkVerifier { mstore(add(mPtr, 0xe0), calldataload(add(aproof, PROOF_OPENING_AT_ZETA_OMEGA_Y))) mstore(add(mPtr, 0x100), mload(add(state, STATE_ZETA))) mstore(add(mPtr, 0x120), mload(add(state, STATE_GAMMA_KZG))) - let random := staticcall(gas(), 0x2, mPtr, 0x140, mPtr, 0x20) + let random := staticcall(gas(), SHA2, mPtr, 0x140, mPtr, 0x20) if iszero(random){ error_random_generation() } @@ -863,17 +874,18 @@ contract PlonkVerifier { mstore(folded_quotients_y, sub(P_MOD, mload(folded_quotients_y))) mstore(mPtr, mload(folded_digests)) - mstore(add(mPtr, 0x20), mload(add(folded_digests, 0x20))) - mstore(add(mPtr, 0x40), G2_SRS_0_X_0) // the 4 lines are the canonical G2 point on BN254 - mstore(add(mPtr, 0x60), G2_SRS_0_X_1) - mstore(add(mPtr, 0x80), G2_SRS_0_Y_0) - mstore(add(mPtr, 0xa0), G2_SRS_0_Y_1) - mstore(add(mPtr, 0xc0), mload(folded_quotients)) - mstore(add(mPtr, 0xe0), mload(add(folded_quotients, 0x20))) - mstore(add(mPtr, 0x100), G2_SRS_1_X_0) - mstore(add(mPtr, 0x120), G2_SRS_1_X_1) - mstore(add(mPtr, 0x140), G2_SRS_1_Y_0) - mstore(add(mPtr, 0x160), G2_SRS_1_Y_1) + {{ $offset = 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), mload(add(folded_digests, 0x20))) {{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), G2_SRS_0_X_0) {{ $offset = add $offset 0x20 }} // the 4 lines are the canonical G2 point on BN254 + mstore(add(mPtr, {{ hex $offset }}), G2_SRS_0_X_1) {{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), G2_SRS_0_Y_0) {{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), G2_SRS_0_Y_1) {{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), mload(folded_quotients)) {{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), mload(add(folded_quotients, 0x20))) {{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), G2_SRS_1_X_0) {{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), G2_SRS_1_X_1) {{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), G2_SRS_1_Y_0) {{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), G2_SRS_1_Y_1) {{ $offset = add $offset 0x20 }} check_pairing_kzg(mPtr) } @@ -894,7 +906,7 @@ contract PlonkVerifier { /// @notice Fold the opening proofs at ζ: /// * at state+state_folded_digest we store: [Linearised_polynomial]+γ[L] + γ²[R] + γ³[O] + γ⁴[S₁] +γ⁵[S₂] + ∑ᵢγ⁵⁺ⁱ[Pi_{i}] - /// * at state+state_folded_claimed_values we store: H(ζ) + γLinearised_polynomial(ζ)+γ²L(ζ) + γ³R(ζ)+ γ⁴O(ζ) + γ⁵S₁(ζ) +γ⁶S₂(ζ) + ∑ᵢγ⁶⁺ⁱPi_{i}(ζ) + /// * at state+state_folded_claimed_values we store: Linearised_polynomial(ζ)+γL(ζ) + γ²R(ζ)+ γ³O(ζ) + γ⁴S₁(ζ) +γ⁵S₂(ζ) + ∑ᵢγ⁵⁺ⁱPi_{i}(ζ) /// @param aproof pointer to the proof /// acc_gamma stores the γⁱ function fold_state(aproof) { @@ -908,11 +920,11 @@ contract PlonkVerifier { let acc_gamma := l_gamma_kzg let state_folded_digests := add(state, STATE_FOLDED_DIGESTS_X) - mstore(add(state, STATE_FOLDED_DIGESTS_X), mload(add(state, STATE_LINEARISED_POLYNOMIAL_X))) + mstore(state_folded_digests, mload(add(state, STATE_LINEARISED_POLYNOMIAL_X))) mstore(add(state, STATE_FOLDED_DIGESTS_Y), mload(add(state, STATE_LINEARISED_POLYNOMIAL_Y))) mstore(add(state, STATE_FOLDED_CLAIMED_VALUES), mload(add(state, STATE_OPENING_LINEARISED_POLYNOMIAL_ZETA))) - point_acc_mul_calldata(add(state, STATE_FOLDED_DIGESTS_X), add(aproof, PROOF_L_COM_X), acc_gamma, mPtr) + point_acc_mul_calldata(state_folded_digests, add(aproof, PROOF_L_COM_X), acc_gamma, mPtr) fr_acc_mul_calldata(add(state, STATE_FOLDED_CLAIMED_VALUES), add(aproof, PROOF_L_AT_ZETA), acc_gamma) acc_gamma := mulmod(acc_gamma, l_gamma_kzg, R_MOD) @@ -1003,9 +1015,9 @@ contract PlonkVerifier { mstore(_mPtr, calldataload(add(aproof, PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA))) let start_input := 0x1b // 00.."gamma" - let size_input := add(0x14, mul(VK_NB_CUSTOM_GATES,3)) // number of 32bytes elmts = 0x17 (zeta+3*6 for the digests+openings) + 3*VK_NB_CUSTOM_GATES (for the commitments of the selectors) + 1 (opening of Z at ζω) + let size_input := add(0x14, mul(VK_NB_CUSTOM_GATES,3)) // number of 32bytes elmts = 0x14 (zeta+3*6 for the digests+openings) + 3*VK_NB_CUSTOM_GATES (for the commitments of the selectors) + 1 (opening of Z at ζω) size_input := add(0x5, mul(size_input, 0x20)) // size in bytes: 15*32 bytes + 5 bytes for gamma - let check_staticcall := staticcall(gas(), 0x2, add(mPtr,start_input), size_input, add(state, STATE_GAMMA_KZG), 0x20) + let check_staticcall := staticcall(gas(), SHA2, add(mPtr,start_input), size_input, add(state, STATE_GAMMA_KZG), 0x20) if iszero(check_staticcall) { error_verify() } @@ -1149,7 +1161,7 @@ contract PlonkVerifier { compute_commitment_linearised_polynomial_ec(aproof, s1, s2) } - /// @notice compute -z_h(ζ)*([H₁] + ζᵐ⁺²[H₂] + ζ²⁽ᵐ⁺²⁾[H₃]) and store the result at + /// @notice compute -z_h(ζ)*([H₁] + ζⁿ⁺²[H₂] + ζ²⁽ⁿ⁺²⁾[H₃]) and store the result at /// state + state_folded_h /// @param aproof pointer to the proof function fold_h(aproof) { @@ -1157,10 +1169,10 @@ contract PlonkVerifier { let n_plus_two := add(VK_DOMAIN_SIZE, 2) let mPtr := add(mload(0x40), STATE_LAST_MEM) let zeta_power_n_plus_two := pow(mload(add(state, STATE_ZETA)), n_plus_two, mPtr) - point_mul_calldata(add(state, STATE_FOLDED_H_X), add(aproof, PROOF_H_2_X), zeta_power_n_plus_two, mPtr) - point_add_calldata(add(state, STATE_FOLDED_H_X), add(state, STATE_FOLDED_H_X), add(aproof, PROOF_H_1_X), mPtr) + point_mul_calldata(add(state, STATE_FOLDED_H_X), add(aproof, PROOF_H_2_COM_X), zeta_power_n_plus_two, mPtr) + point_add_calldata(add(state, STATE_FOLDED_H_X), add(state, STATE_FOLDED_H_X), add(aproof, PROOF_H_1_COM_X), mPtr) point_mul(add(state, STATE_FOLDED_H_X), add(state, STATE_FOLDED_H_X), zeta_power_n_plus_two, mPtr) - point_add_calldata(add(state, STATE_FOLDED_H_X), add(state, STATE_FOLDED_H_X), add(aproof, PROOF_H_0_X), mPtr) + point_add_calldata(add(state, STATE_FOLDED_H_X), add(state, STATE_FOLDED_H_X), add(aproof, PROOF_H_0_COM_X), mPtr) point_mul(add(state, STATE_FOLDED_H_X), add(state, STATE_FOLDED_H_X), mload(add(state, STATE_ZETA_POWER_N_MINUS_ONE)), mPtr) let folded_h_y := mload(add(state, STATE_FOLDED_H_Y)) folded_h_y := sub(P_MOD, folded_h_y) @@ -1170,7 +1182,7 @@ contract PlonkVerifier { /// @notice check that the opening of the linearised polynomial at zeta is equal to /// - [ PI(ζ) - α²*L₁(ζ) + α(l(ζ)+β*s1(ζ)+γ)(r(ζ)+β*s2(ζ)+γ)(o(ζ)+γ)*z(ωζ) ] /// @param aproof pointer to the proof - function verify_opening_linearised_polynomial(aproof) { + function compute_opening_linearised_polynomial(aproof) { let state := mload(0x40) @@ -1290,7 +1302,6 @@ contract PlonkVerifier { /// @param s scalar /// @mPtr free memory function point_acc_mul_calldata(dst, src, s, mPtr) { - let state := mload(0x40) mstore(mPtr, calldataload(src)) mstore(add(mPtr, 0x20), calldataload(add(src, 0x20))) mstore(add(mPtr, 0x40), s) @@ -1325,7 +1336,7 @@ contract PlonkVerifier { mstore(add(mPtr, 0xa0), R_MOD) let check_staticcall := staticcall(gas(),MOD_EXP,mPtr,0xc0,mPtr,0x20) if eq(check_staticcall, 0) { - + error_mod_exp() } res := mload(mPtr) } @@ -1334,7 +1345,7 @@ contract PlonkVerifier { } ` -// MarshalSolidity convert s a proof to a byte array that can be used in a +// MarshalSolidity converts a proof to a byte array that can be used in a // Solidity contract. func (proof *Proof) MarshalSolidity() []byte { @@ -1398,16 +1409,14 @@ func (proof *Proof) MarshalSolidity() []byte { // uint256[] selector_commit_api_at_zeta; // uint256[] wire_committed_commitments; - if len(proof.Bsb22Commitments) > 0 { - for i := 0; i < len(proof.Bsb22Commitments); i++ { - tmp32 = proof.BatchedProof.ClaimedValues[6+i].Bytes() - res = append(res, tmp32[:]...) - } - - for _, bc := range proof.Bsb22Commitments { - tmp64 = bc.RawBytes() - res = append(res, tmp64[:]...) - } + for i := 0; i < len(proof.Bsb22Commitments); i++ { + tmp32 = proof.BatchedProof.ClaimedValues[6+i].Bytes() + res = append(res, tmp32[:]...) + } + + for _, bc := range proof.Bsb22Commitments { + tmp64 = bc.RawBytes() + res = append(res, tmp64[:]...) } return res diff --git a/backend/plonk/bn254/unmarshal.go b/backend/plonk/bn254/unmarshal.go index cc3e7b8add..9554884105 100644 --- a/backend/plonk/bn254/unmarshal.go +++ b/backend/plonk/bn254/unmarshal.go @@ -55,10 +55,8 @@ func UnmarshalSolidity(s []byte, nbCommits int) Proof { proof.ZShiftedOpening.ClaimedValue.SetBytes(s[offset : offset+fr_size]) offset += fr_size - // uint256 quotient_polynomial_at_zeta; - // uint256 linearization_polynomial_at_zeta; - proof.BatchedProof.ClaimedValues[0].SetBytes(s[offset : offset+fr_size]) - offset += fr_size + // we skip the claimed value of the linearised polynomial at zeta as + // it is not in the marshal solidity proof // uint256 opening_at_zeta_proof_x; // uint256 opening_at_zeta_proof_y; @@ -73,7 +71,7 @@ func UnmarshalSolidity(s []byte, nbCommits int) Proof { // uint256[] selector_commit_api_at_zeta; // uint256[] wire_committed_commitments; for i := 0; i < nbCommits; i++ { - proof.BatchedProof.ClaimedValues[7+i].SetBytes(s[offset : offset+fr_size]) + proof.BatchedProof.ClaimedValues[6+i].SetBytes(s[offset : offset+fr_size]) offset += fr_size } diff --git a/backend/plonk/bn254/verify.go b/backend/plonk/bn254/verify.go index 8c4fecbca5..2197228b3f 100644 --- a/backend/plonk/bn254/verify.go +++ b/backend/plonk/bn254/verify.go @@ -42,6 +42,7 @@ import ( var ( errAlgebraicRelation = errors.New("algebraic relation does not hold") errInvalidWitness = errors.New("witness length is invalid") + errInvalidPoint = errors.New("point is not on the curve") ) func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { @@ -61,6 +62,32 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac return errInvalidWitness } + // check that the points in the proof are on the curve + for i := 0; i < len(proof.LRO); i++ { + if !proof.LRO[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.Z.IsInSubGroup() { + return errInvalidPoint + } + for i := 0; i < len(proof.H); i++ { + if !proof.H[i].IsInSubGroup() { + return errInvalidPoint + } + } + for i := 0; i < len(proof.Bsb22Commitments); i++ { + if !proof.Bsb22Commitments[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.BatchedProof.H.IsInSubGroup() { + return errInvalidPoint + } + if !proof.ZShiftedOpening.H.IsInSubGroup() { + return errInvalidPoint + } + // transcript to derive the challenge fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") @@ -99,16 +126,16 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac } // evaluation of zhZeta=ζⁿ-1 - var zetaPowerM, zhZeta, lagrangeOne fr.Element + var zetaPowerM, zhZeta, lagrangeZero fr.Element var bExpo big.Int one := fr.One() bExpo.SetUint64(vk.Size) zetaPowerM.Exp(zeta, &bExpo) - zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 - lagrangeOne.Sub(&zeta, &one). // ζ-1 - Inverse(&lagrangeOne). // 1/(ζ-1) - Mul(&lagrangeOne, &zhZeta). // (ζ^n-1)/(ζ-1) - Mul(&lagrangeOne, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) + zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 + lagrangeZero.Sub(&zeta, &one). // ζ-1 + Inverse(&lagrangeZero). // 1/(ζ-1) + Mul(&lagrangeZero, &zhZeta). // (ζ^n-1)/(ζ-1) + Mul(&lagrangeZero, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) // compute PI = ∑_{i> 1 + nbBsbGates := len(s.proof.Bsb22Commitments) gateConstraint := func(u ...fr.Element) fr.Element { @@ -1077,7 +1099,7 @@ func evaluateBlinded(p, bp *iop.Polynomial, zeta fr.Element) fr.Element { return pEvaluatedAtZeta } -// /!\ modifies p's underlying array of coefficients, in particular the size changes +// /!\ modifies the size func getBlindedCoefficients(p, bp *iop.Polynomial) []fr.Element { cp := p.Coefficients() cbp := bp.Coefficients() @@ -1150,10 +1172,10 @@ func commitToQuotient(h1, h2, h3 []fr.Element, proof *Proof, kzgPk kzg.ProvingKe return g.Wait() } -// divideByXMinusOne +// divideByZH // The input must be in LagrangeCoset. // The result is in Canonical Regular. (in place using a) -func divideByXMinusOne(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { +func divideByZH(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { // check that the basis is LagrangeCoset if a.Basis != iop.LagrangeCoset || a.Layout != iop.BitReverse { @@ -1193,7 +1215,7 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { res[0].Exp(domains[1].FrMultiplicativeGen, expo) var t fr.Element - t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + t.Exp(domains[1].Generator, expo) one := fr.One() @@ -1220,6 +1242,8 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { // + α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*(β*s3(X))*Z(μζ) - Z(X)*(l(ζ)+β*id1(ζ)+γ)*(r(ζ)+β*id2(ζ)+γ)*(o(ζ)+β*id3(ζ)+γ)) // + l(ζ)*Ql(X) + l(ζ)r(ζ)*Qm(X) + r(ζ)*Qr(X) + o(ζ)*Qo(X) + Qk(X) + ∑ᵢQcp_(ζ)Pi_(X) // - Z_{H}(ζ)*((H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) +// +// /!\ blindedZCanonical is modified func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, zu fr.Element, qcpZeta, blindedZCanonical []fr.Element, pi2Canonical [][]fr.Element, pk *ProvingKey) []fr.Element { // l(ζ)r(ζ) @@ -1258,25 +1282,26 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, s2.Neg(&s2).Mul(&s2, &alpha) // Z_h(ζ), ζⁿ⁺², L₁(ζ)*α²*Z - var zhZeta, zetaNPlusTwo, alphaSquareLagrangeOne, one, den, frNbElmt fr.Element + var zhZeta, zetaNPlusTwo, alphaSquareLagrangeZero, one, den, frNbElmt fr.Element one.SetOne() nbElmt := int64(s.domain0.Cardinality) - alphaSquareLagrangeOne.Set(&zeta).Exp(alphaSquareLagrangeOne, big.NewInt(nbElmt)) // ζⁿ - zetaNPlusTwo.Mul(&alphaSquareLagrangeOne, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² - alphaSquareLagrangeOne.Sub(&alphaSquareLagrangeOne, &one) // ζⁿ - 1 - zhZeta.Set(&alphaSquareLagrangeOne) // Z_h(ζ) = ζⁿ - 1 + alphaSquareLagrangeZero.Set(&zeta).Exp(alphaSquareLagrangeZero, big.NewInt(nbElmt)) // ζⁿ + zetaNPlusTwo.Mul(&alphaSquareLagrangeZero, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² + alphaSquareLagrangeZero.Sub(&alphaSquareLagrangeZero, &one) // ζⁿ - 1 + zhZeta.Set(&alphaSquareLagrangeZero) // Z_h(ζ) = ζⁿ - 1 frNbElmt.SetUint64(uint64(nbElmt)) - den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) - alphaSquareLagrangeOne.Mul(&alphaSquareLagrangeOne, &den). // L₁ = (ζⁿ - 1)/(ζ-1) - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &s.domain0.CardinalityInv) // α²*L₁(ζ) + den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) + alphaSquareLagrangeZero.Mul(&alphaSquareLagrangeZero, &den). // L₁ = (ζⁿ - 1)/(ζ-1) + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &s.domain0.CardinalityInv) // α²*L₁(ζ) s3canonical := s.trace.S3.Coefficients() s.trace.Qk.ToCanonical(s.domain0).ToRegular() - // the hi are all of the same length + // len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 when Statistical ZK is activated + // len(h1)=len(h2)=len(h3)=len(blindedZCanonical)-1 when Statistical ZK is deactivated h1 := s.h1() h2 := s.h2() h3 := s.h3() @@ -1316,20 +1341,29 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, } } - t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeOne) // α²L₁(ζ)Z(X) - blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) + t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeZero) // α²L₁(ζ)Z(X) + blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) - if i < len(h1) { + // if statistical zeroknowledge is deactivated, len(h1)=len(h2)=len(h3)=len(blindedZ)-1. + // Else len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 + if i < len(h3) { t.Mul(&h3[i], &zetaNPlusTwo). Add(&t, &h2[i]). Mul(&t, &zetaNPlusTwo). - Add(&t, &h1[i]) - t.Mul(&t, &zhZeta) + Add(&t, &h1[i]). + Mul(&t, &zhZeta) blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } else { + if s.opt.StatisticalZK { + t.Mul(&h2[i], &zetaNPlusTwo). + Add(&t, &h1[i]). + Mul(&t, &zhZeta) + blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } } - } }) + return blindedZCanonical } diff --git a/backend/plonk/bw6-633/setup.go b/backend/plonk/bw6-633/setup.go index 8aa342a41a..03622060e3 100644 --- a/backend/plonk/bw6-633/setup.go +++ b/backend/plonk/bw6-633/setup.go @@ -18,7 +18,6 @@ package plonk import ( "fmt" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bw6-633/fr" "github.com/consensys/gnark-crypto/ecc/bw6-633/fr/fft" "github.com/consensys/gnark-crypto/ecc/bw6-633/fr/iop" @@ -96,7 +95,7 @@ func Setup(spr *cs.SparseR1CS, srs, srsLagrange kzg.SRS) (*ProvingKey, *Verifyin // step 0: set the fft domains domain := initFFTDomain(spr) if domain.Cardinality < 2 { - return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", spr.GetNbConstraints()) + return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", len(spr.Public)+spr.GetNbConstraints()) } // check the size of the kzg srs. @@ -154,9 +153,7 @@ func (pk *ProvingKey) VerifyingKey() interface{} { func NewTrace(spr *cs.SparseR1CS, domain *fft.Domain) *Trace { var trace Trace - nbConstraints := spr.GetNbConstraints() - sizeSystem := uint64(nbConstraints + len(spr.Public)) - size := ecc.NextPowerOfTwo(sizeSystem) + size := int(domain.Cardinality) commitmentInfo := spr.CommitmentInfo.(constraint.PlonkCommitments) ql := make([]fr.Element, size) diff --git a/backend/plonk/bw6-633/verify.go b/backend/plonk/bw6-633/verify.go index 831c11ec18..c9e1f1928e 100644 --- a/backend/plonk/bw6-633/verify.go +++ b/backend/plonk/bw6-633/verify.go @@ -42,6 +42,7 @@ import ( var ( errAlgebraicRelation = errors.New("algebraic relation does not hold") errInvalidWitness = errors.New("witness length is invalid") + errInvalidPoint = errors.New("point is not on the curve") ) func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { @@ -61,6 +62,32 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac return errInvalidWitness } + // check that the points in the proof are on the curve + for i := 0; i < len(proof.LRO); i++ { + if !proof.LRO[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.Z.IsInSubGroup() { + return errInvalidPoint + } + for i := 0; i < len(proof.H); i++ { + if !proof.H[i].IsInSubGroup() { + return errInvalidPoint + } + } + for i := 0; i < len(proof.Bsb22Commitments); i++ { + if !proof.Bsb22Commitments[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.BatchedProof.H.IsInSubGroup() { + return errInvalidPoint + } + if !proof.ZShiftedOpening.H.IsInSubGroup() { + return errInvalidPoint + } + // transcript to derive the challenge fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") @@ -99,16 +126,16 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac } // evaluation of zhZeta=ζⁿ-1 - var zetaPowerM, zhZeta, lagrangeOne fr.Element + var zetaPowerM, zhZeta, lagrangeZero fr.Element var bExpo big.Int one := fr.One() bExpo.SetUint64(vk.Size) zetaPowerM.Exp(zeta, &bExpo) - zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 - lagrangeOne.Sub(&zeta, &one). // ζ-1 - Inverse(&lagrangeOne). // 1/(ζ-1) - Mul(&lagrangeOne, &zhZeta). // (ζ^n-1)/(ζ-1) - Mul(&lagrangeOne, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) + zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 + lagrangeZero.Sub(&zeta, &one). // ζ-1 + Inverse(&lagrangeZero). // 1/(ζ-1) + Mul(&lagrangeZero, &zhZeta). // (ζ^n-1)/(ζ-1) + Mul(&lagrangeZero, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) // compute PI = ∑_{i> 1 + nbBsbGates := len(s.proof.Bsb22Commitments) gateConstraint := func(u ...fr.Element) fr.Element { @@ -1077,7 +1099,7 @@ func evaluateBlinded(p, bp *iop.Polynomial, zeta fr.Element) fr.Element { return pEvaluatedAtZeta } -// /!\ modifies p's underlying array of coefficients, in particular the size changes +// /!\ modifies the size func getBlindedCoefficients(p, bp *iop.Polynomial) []fr.Element { cp := p.Coefficients() cbp := bp.Coefficients() @@ -1150,10 +1172,10 @@ func commitToQuotient(h1, h2, h3 []fr.Element, proof *Proof, kzgPk kzg.ProvingKe return g.Wait() } -// divideByXMinusOne +// divideByZH // The input must be in LagrangeCoset. // The result is in Canonical Regular. (in place using a) -func divideByXMinusOne(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { +func divideByZH(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { // check that the basis is LagrangeCoset if a.Basis != iop.LagrangeCoset || a.Layout != iop.BitReverse { @@ -1193,7 +1215,7 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { res[0].Exp(domains[1].FrMultiplicativeGen, expo) var t fr.Element - t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + t.Exp(domains[1].Generator, expo) one := fr.One() @@ -1220,6 +1242,8 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { // + α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*(β*s3(X))*Z(μζ) - Z(X)*(l(ζ)+β*id1(ζ)+γ)*(r(ζ)+β*id2(ζ)+γ)*(o(ζ)+β*id3(ζ)+γ)) // + l(ζ)*Ql(X) + l(ζ)r(ζ)*Qm(X) + r(ζ)*Qr(X) + o(ζ)*Qo(X) + Qk(X) + ∑ᵢQcp_(ζ)Pi_(X) // - Z_{H}(ζ)*((H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) +// +// /!\ blindedZCanonical is modified func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, zu fr.Element, qcpZeta, blindedZCanonical []fr.Element, pi2Canonical [][]fr.Element, pk *ProvingKey) []fr.Element { // l(ζ)r(ζ) @@ -1258,25 +1282,26 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, s2.Neg(&s2).Mul(&s2, &alpha) // Z_h(ζ), ζⁿ⁺², L₁(ζ)*α²*Z - var zhZeta, zetaNPlusTwo, alphaSquareLagrangeOne, one, den, frNbElmt fr.Element + var zhZeta, zetaNPlusTwo, alphaSquareLagrangeZero, one, den, frNbElmt fr.Element one.SetOne() nbElmt := int64(s.domain0.Cardinality) - alphaSquareLagrangeOne.Set(&zeta).Exp(alphaSquareLagrangeOne, big.NewInt(nbElmt)) // ζⁿ - zetaNPlusTwo.Mul(&alphaSquareLagrangeOne, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² - alphaSquareLagrangeOne.Sub(&alphaSquareLagrangeOne, &one) // ζⁿ - 1 - zhZeta.Set(&alphaSquareLagrangeOne) // Z_h(ζ) = ζⁿ - 1 + alphaSquareLagrangeZero.Set(&zeta).Exp(alphaSquareLagrangeZero, big.NewInt(nbElmt)) // ζⁿ + zetaNPlusTwo.Mul(&alphaSquareLagrangeZero, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² + alphaSquareLagrangeZero.Sub(&alphaSquareLagrangeZero, &one) // ζⁿ - 1 + zhZeta.Set(&alphaSquareLagrangeZero) // Z_h(ζ) = ζⁿ - 1 frNbElmt.SetUint64(uint64(nbElmt)) - den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) - alphaSquareLagrangeOne.Mul(&alphaSquareLagrangeOne, &den). // L₁ = (ζⁿ - 1)/(ζ-1) - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &s.domain0.CardinalityInv) // α²*L₁(ζ) + den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) + alphaSquareLagrangeZero.Mul(&alphaSquareLagrangeZero, &den). // L₁ = (ζⁿ - 1)/(ζ-1) + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &s.domain0.CardinalityInv) // α²*L₁(ζ) s3canonical := s.trace.S3.Coefficients() s.trace.Qk.ToCanonical(s.domain0).ToRegular() - // the hi are all of the same length + // len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 when Statistical ZK is activated + // len(h1)=len(h2)=len(h3)=len(blindedZCanonical)-1 when Statistical ZK is deactivated h1 := s.h1() h2 := s.h2() h3 := s.h3() @@ -1316,20 +1341,29 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, } } - t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeOne) // α²L₁(ζ)Z(X) - blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) + t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeZero) // α²L₁(ζ)Z(X) + blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) - if i < len(h1) { + // if statistical zeroknowledge is deactivated, len(h1)=len(h2)=len(h3)=len(blindedZ)-1. + // Else len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 + if i < len(h3) { t.Mul(&h3[i], &zetaNPlusTwo). Add(&t, &h2[i]). Mul(&t, &zetaNPlusTwo). - Add(&t, &h1[i]) - t.Mul(&t, &zhZeta) + Add(&t, &h1[i]). + Mul(&t, &zhZeta) blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } else { + if s.opt.StatisticalZK { + t.Mul(&h2[i], &zetaNPlusTwo). + Add(&t, &h1[i]). + Mul(&t, &zhZeta) + blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } } - } }) + return blindedZCanonical } diff --git a/backend/plonk/bw6-761/setup.go b/backend/plonk/bw6-761/setup.go index 9764e5a796..b9cd13daed 100644 --- a/backend/plonk/bw6-761/setup.go +++ b/backend/plonk/bw6-761/setup.go @@ -18,7 +18,6 @@ package plonk import ( "fmt" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/fft" "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/iop" @@ -96,7 +95,7 @@ func Setup(spr *cs.SparseR1CS, srs, srsLagrange kzg.SRS) (*ProvingKey, *Verifyin // step 0: set the fft domains domain := initFFTDomain(spr) if domain.Cardinality < 2 { - return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", spr.GetNbConstraints()) + return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", len(spr.Public)+spr.GetNbConstraints()) } // check the size of the kzg srs. @@ -154,9 +153,7 @@ func (pk *ProvingKey) VerifyingKey() interface{} { func NewTrace(spr *cs.SparseR1CS, domain *fft.Domain) *Trace { var trace Trace - nbConstraints := spr.GetNbConstraints() - sizeSystem := uint64(nbConstraints + len(spr.Public)) - size := ecc.NextPowerOfTwo(sizeSystem) + size := int(domain.Cardinality) commitmentInfo := spr.CommitmentInfo.(constraint.PlonkCommitments) ql := make([]fr.Element, size) diff --git a/backend/plonk/bw6-761/verify.go b/backend/plonk/bw6-761/verify.go index c9f25260e5..23522a2dca 100644 --- a/backend/plonk/bw6-761/verify.go +++ b/backend/plonk/bw6-761/verify.go @@ -42,6 +42,7 @@ import ( var ( errAlgebraicRelation = errors.New("algebraic relation does not hold") errInvalidWitness = errors.New("witness length is invalid") + errInvalidPoint = errors.New("point is not on the curve") ) func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { @@ -61,6 +62,32 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac return errInvalidWitness } + // check that the points in the proof are on the curve + for i := 0; i < len(proof.LRO); i++ { + if !proof.LRO[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.Z.IsInSubGroup() { + return errInvalidPoint + } + for i := 0; i < len(proof.H); i++ { + if !proof.H[i].IsInSubGroup() { + return errInvalidPoint + } + } + for i := 0; i < len(proof.Bsb22Commitments); i++ { + if !proof.Bsb22Commitments[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.BatchedProof.H.IsInSubGroup() { + return errInvalidPoint + } + if !proof.ZShiftedOpening.H.IsInSubGroup() { + return errInvalidPoint + } + // transcript to derive the challenge fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") @@ -99,16 +126,16 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac } // evaluation of zhZeta=ζⁿ-1 - var zetaPowerM, zhZeta, lagrangeOne fr.Element + var zetaPowerM, zhZeta, lagrangeZero fr.Element var bExpo big.Int one := fr.One() bExpo.SetUint64(vk.Size) zetaPowerM.Exp(zeta, &bExpo) - zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 - lagrangeOne.Sub(&zeta, &one). // ζ-1 - Inverse(&lagrangeOne). // 1/(ζ-1) - Mul(&lagrangeOne, &zhZeta). // (ζ^n-1)/(ζ-1) - Mul(&lagrangeOne, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) + zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 + lagrangeZero.Sub(&zeta, &one). // ζ-1 + Inverse(&lagrangeZero). // 1/(ζ-1) + Mul(&lagrangeZero, &zhZeta). // (ζ^n-1)/(ζ-1) + Mul(&lagrangeZero, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) // compute PI = ∑_{i> 1 + nbBsbGates := len(s.proof.Bsb22Commitments) gateConstraint := func(u ...fr.Element) fr.Element { @@ -1054,7 +1076,7 @@ func evaluateBlinded(p, bp *iop.Polynomial, zeta fr.Element) fr.Element { return pEvaluatedAtZeta } -// /!\ modifies p's underlying array of coefficients, in particular the size changes +// /!\ modifies the size func getBlindedCoefficients(p, bp *iop.Polynomial) []fr.Element { cp := p.Coefficients() cbp := bp.Coefficients() @@ -1127,10 +1149,10 @@ func commitToQuotient(h1, h2, h3 []fr.Element, proof *Proof, kzgPk kzg.ProvingKe return g.Wait() } -// divideByXMinusOne +// divideByZH // The input must be in LagrangeCoset. // The result is in Canonical Regular. (in place using a) -func divideByXMinusOne(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { +func divideByZH(a *iop.Polynomial, domains [2]*fft.Domain) (*iop.Polynomial, error) { // check that the basis is LagrangeCoset if a.Basis != iop.LagrangeCoset || a.Layout != iop.BitReverse { @@ -1170,7 +1192,7 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { res[0].Exp(domains[1].FrMultiplicativeGen, expo) var t fr.Element - t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + t.Exp(domains[1].Generator, expo) one := fr.One() @@ -1197,6 +1219,8 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { // + α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*(β*s3(X))*Z(μζ) - Z(X)*(l(ζ)+β*id1(ζ)+γ)*(r(ζ)+β*id2(ζ)+γ)*(o(ζ)+β*id3(ζ)+γ)) // + l(ζ)*Ql(X) + l(ζ)r(ζ)*Qm(X) + r(ζ)*Qr(X) + o(ζ)*Qo(X) + Qk(X) + ∑ᵢQcp_(ζ)Pi_(X) // - Z_{H}(ζ)*((H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) +// +// /!\ blindedZCanonical is modified func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, zu fr.Element, qcpZeta, blindedZCanonical []fr.Element, pi2Canonical [][]fr.Element, pk *ProvingKey) []fr.Element { // l(ζ)r(ζ) @@ -1235,25 +1259,26 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, s2.Neg(&s2).Mul(&s2, &alpha) // Z_h(ζ), ζⁿ⁺², L₁(ζ)*α²*Z - var zhZeta, zetaNPlusTwo, alphaSquareLagrangeOne, one, den, frNbElmt fr.Element + var zhZeta, zetaNPlusTwo, alphaSquareLagrangeZero, one, den, frNbElmt fr.Element one.SetOne() nbElmt := int64(s.domain0.Cardinality) - alphaSquareLagrangeOne.Set(&zeta).Exp(alphaSquareLagrangeOne, big.NewInt(nbElmt)) // ζⁿ - zetaNPlusTwo.Mul(&alphaSquareLagrangeOne, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² - alphaSquareLagrangeOne.Sub(&alphaSquareLagrangeOne, &one) // ζⁿ - 1 - zhZeta.Set(&alphaSquareLagrangeOne) // Z_h(ζ) = ζⁿ - 1 + alphaSquareLagrangeZero.Set(&zeta).Exp(alphaSquareLagrangeZero, big.NewInt(nbElmt)) // ζⁿ + zetaNPlusTwo.Mul(&alphaSquareLagrangeZero, &zeta).Mul(&zetaNPlusTwo, &zeta) // ζⁿ⁺² + alphaSquareLagrangeZero.Sub(&alphaSquareLagrangeZero, &one) // ζⁿ - 1 + zhZeta.Set(&alphaSquareLagrangeZero) // Z_h(ζ) = ζⁿ - 1 frNbElmt.SetUint64(uint64(nbElmt)) den.Sub(&zeta, &one).Inverse(&den) // 1/(ζ-1) - alphaSquareLagrangeOne.Mul(&alphaSquareLagrangeOne, &den). // L₁ = (ζⁿ - 1)/(ζ-1) - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &alpha). - Mul(&alphaSquareLagrangeOne, &s.domain0.CardinalityInv) // α²*L₁(ζ) + alphaSquareLagrangeZero.Mul(&alphaSquareLagrangeZero, &den). // L₁ = (ζⁿ - 1)/(ζ-1) + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &alpha). + Mul(&alphaSquareLagrangeZero, &s.domain0.CardinalityInv) // α²*L₁(ζ) s3canonical := s.trace.S3.Coefficients() s.trace.Qk.ToCanonical(s.domain0).ToRegular() - // the hi are all of the same length + // len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 when Statistical ZK is activated + // len(h1)=len(h2)=len(h3)=len(blindedZCanonical)-1 when Statistical ZK is deactivated h1 := s.h1() h2 := s.h2() h3 := s.h3() @@ -1293,20 +1318,29 @@ func (s *instance) innerComputeLinearizedPoly(lZeta, rZeta, oZeta, alpha, beta, } } - t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeOne) // α²L₁(ζ)Z(X) + t0.Mul(&blindedZCanonical[i], &alphaSquareLagrangeZero) // α²L₁(ζ)Z(X) blindedZCanonical[i].Add(&t, &t0) // linPol += α²L₁(ζ)Z(X) - if i < len(h1) { + // if statistical zeroknowledge is deactivated, len(h1)=len(h2)=len(h3)=len(blindedZ)-1. + // Else len(h1)=len(h2)=len(blindedZCanonical)=len(h3)+1 + if i < len(h3) { t.Mul(&h3[i], &zetaNPlusTwo). Add(&t, &h2[i]). Mul(&t, &zetaNPlusTwo). - Add(&t, &h1[i]) - t.Mul(&t, &zhZeta) + Add(&t, &h1[i]). + Mul(&t, &zhZeta) blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } else { + if s.opt.StatisticalZK { + t.Mul(&h2[i], &zetaNPlusTwo). + Add(&t, &h1[i]). + Mul(&t, &zhZeta) + blindedZCanonical[i].Sub(&blindedZCanonical[i], &t) // linPol -= Z_h(ζ)*(H₀(X) + ζᵐ⁺²*H₁(X) + ζ²⁽ᵐ⁺²⁾*H₂(X)) + } } - } }) + return blindedZCanonical } diff --git a/internal/generator/backend/template/zkpschemes/plonk/plonk.setup.go.tmpl b/internal/generator/backend/template/zkpschemes/plonk/plonk.setup.go.tmpl index 8401930fc9..e58f9684eb 100644 --- a/internal/generator/backend/template/zkpschemes/plonk/plonk.setup.go.tmpl +++ b/internal/generator/backend/template/zkpschemes/plonk/plonk.setup.go.tmpl @@ -5,7 +5,6 @@ import ( {{- template "import_backend_cs" . }} "fmt" "github.com/consensys/gnark-crypto/ecc/{{toLower .Curve}}/fr/iop" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark/backend/plonk/internal" "github.com/consensys/gnark/constraint" ) @@ -78,7 +77,7 @@ func Setup(spr *cs.SparseR1CS, srs, srsLagrange kzg.SRS) (*ProvingKey, *Verifyin // step 0: set the fft domains domain := initFFTDomain(spr) if domain.Cardinality < 2 { - return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", spr.GetNbConstraints()) + return nil, nil, fmt.Errorf("circuit has only %d constraints; unsupported by the current implementation", len(spr.Public)+spr.GetNbConstraints()) } // check the size of the kzg srs. @@ -136,9 +135,7 @@ func (pk *ProvingKey) VerifyingKey() interface{} { func NewTrace(spr *cs.SparseR1CS, domain *fft.Domain) *Trace { var trace Trace - nbConstraints := spr.GetNbConstraints() - sizeSystem := uint64(nbConstraints + len(spr.Public)) - size := ecc.NextPowerOfTwo(sizeSystem) + size := int(domain.Cardinality) commitmentInfo := spr.CommitmentInfo.(constraint.PlonkCommitments) ql := make([]fr.Element, size) diff --git a/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl b/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl index 8b06dc097a..7ba4a42a48 100644 --- a/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl +++ b/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl @@ -25,6 +25,7 @@ import ( var ( errAlgebraicRelation = errors.New("algebraic relation does not hold") errInvalidWitness = errors.New("witness length is invalid") + errInvalidPoint = errors.New("point is not on the curve") ) func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { @@ -44,6 +45,32 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac return errInvalidWitness } + // check that the points in the proof are on the curve + for i := 0; i < len(proof.LRO); i++ { + if !proof.LRO[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.Z.IsInSubGroup() { + return errInvalidPoint + } + for i := 0; i < len(proof.H); i++ { + if !proof.H[i].IsInSubGroup() { + return errInvalidPoint + } + } + for i := 0; i < len(proof.Bsb22Commitments); i++ { + if !proof.Bsb22Commitments[i].IsInSubGroup() { + return errInvalidPoint + } + } + if !proof.BatchedProof.H.IsInSubGroup() { + return errInvalidPoint + } + if !proof.ZShiftedOpening.H.IsInSubGroup() { + return errInvalidPoint + } + // transcript to derive the challenge fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") @@ -82,16 +109,16 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac } // evaluation of zhZeta=ζⁿ-1 - var zetaPowerM, zhZeta, lagrangeOne fr.Element + var zetaPowerM, zhZeta, lagrangeZero fr.Element var bExpo big.Int one := fr.One() bExpo.SetUint64(vk.Size) zetaPowerM.Exp(zeta, &bExpo) zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 - lagrangeOne.Sub(&zeta, &one). // ζ-1 - Inverse(&lagrangeOne). // 1/(ζ-1) - Mul(&lagrangeOne, &zhZeta). // (ζ^n-1)/(ζ-1) - Mul(&lagrangeOne, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) + lagrangeZero.Sub(&zeta, &one). // ζ-1 + Inverse(&lagrangeZero). // 1/(ζ-1) + Mul(&lagrangeZero, &zhZeta). // (ζ^n-1)/(ζ-1) + Mul(&lagrangeZero, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) // compute PI = ∑_{i _Q == Q pr.g2.AssertIsEqual(Q, _Q) } +// IsOnG2 returns a boolean indicating if the G2 point is in the subgroup. The +// method assumes that the point is already on the curve. Call +// [Pairing.AssertIsOnTwist] before to ensure point is on the curve. +func (pr Pairing) IsOnG2(Q *G2Affine) frontend.Variable { + // 1 - is Q on curve + isOnCurve := pr.IsOnTwist(Q) + // 2 - is Q in the subgroup + _Q := pr.computeG2ShortVector(Q) + isInSubgroup := pr.g2.IsEqual(Q, _Q) + return pr.api.And(isOnCurve, isInSubgroup) +} + // loopCounter = 6x₀+2 = 29793968203157093288 // // in 2-NAF @@ -365,7 +401,6 @@ func (pr Pairing) millerLoopLines(P []*G1Affine, lines []lineEvaluations) (*GTEl xNegOverY[k] = pr.curveF.Neg(xNegOverY[k]) } - // f_{x₀+1+λ(x₀³-x₀²-x₀),Q}(P), Q is known in advance var prodLines [5]*fields_bn254.E2 res := pr.Ext12.One() @@ -644,12 +679,138 @@ func (pr Pairing) MillerLoopAndMul(P *G1Affine, Q *G2Affine, previous *GTEl) (*G return res, err } -// FinalExponentiationIsOne performs the final exponentiation on e -// and checks that the result in 1 in GT. +// MillerLoopAndFinalExpCheck computes the Miller loop between P and Q, +// multiplies it in 𝔽p¹² by previous and checks that the result lies in the +// same equivalence class as the reduced pairing purported to be 1. This check +// replaces the final exponentiation step in-circuit and follows Section 4 of +// [On Proving Pairings] paper by A. Novakovic and L. Eagen. // // This method is needed for evmprecompiles/ecpair. -func (pr Pairing) FinalExponentiationIsOne(e *GTEl) { - res := pr.finalExponentiation(e, false) - one := pr.One() - pr.AssertIsEqual(res, one) +// +// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf +func (pr Pairing) MillerLoopAndFinalExpCheck(P *G1Affine, Q *G2Affine, previous *GTEl) error { + + // hint the non-residue witness + hint, err := pr.curveF.NewHint(millerLoopAndCheckFinalExpHint, 24, &P.X, &P.Y, &Q.P.X.A0, &Q.P.X.A1, &Q.P.Y.A0, &Q.P.Y.A1, &previous.C0.B0.A0, &previous.C0.B0.A1, &previous.C0.B1.A0, &previous.C0.B1.A1, &previous.C0.B2.A0, &previous.C0.B2.A1, &previous.C1.B0.A0, &previous.C1.B0.A1, &previous.C1.B1.A0, &previous.C1.B1.A1, &previous.C1.B2.A0, &previous.C1.B2.A1) + if err != nil { + // err is non-nil only for invalid number of inputs + panic(err) + } + + residueWitness := fields_bn254.E12{ + C0: fields_bn254.E6{ + B0: fields_bn254.E2{A0: *hint[0], A1: *hint[1]}, + B1: fields_bn254.E2{A0: *hint[2], A1: *hint[3]}, + B2: fields_bn254.E2{A0: *hint[4], A1: *hint[5]}, + }, + C1: fields_bn254.E6{ + B0: fields_bn254.E2{A0: *hint[6], A1: *hint[7]}, + B1: fields_bn254.E2{A0: *hint[8], A1: *hint[9]}, + B2: fields_bn254.E2{A0: *hint[10], A1: *hint[11]}, + }, + } + // constrain cubicNonResiduePower to be in Fp6 + cubicNonResiduePower := fields_bn254.E12{ + C0: fields_bn254.E6{ + B0: fields_bn254.E2{A0: *hint[12], A1: *hint[13]}, + B1: fields_bn254.E2{A0: *hint[14], A1: *hint[15]}, + B2: fields_bn254.E2{A0: *hint[16], A1: *hint[17]}, + }, + C1: (*pr.Ext6.Zero()), + } + + // residueWitnessInv = 1 / residueWitness + residueWitnessInv := pr.Inverse(&residueWitness) + + if Q.Lines == nil { + Qlines := pr.computeLines(&Q.P) + Q.Lines = &Qlines + } + lines := *Q.Lines + + // precomputations + yInv := pr.curveF.Inverse(&P.Y) + xNegOverY := pr.curveF.MulMod(&P.X, yInv) + xNegOverY = pr.curveF.Neg(xNegOverY) + + // init Miller loop accumulator to residueWitnessInv to share the squarings + // of residueWitnessInv^{6x₀+2} + res := residueWitnessInv + + // Compute f_{6x₀+2,Q}(P) + for i := 64; i >= 0; i-- { + res = pr.Square(res) + + switch loopCounter[i] { + case 0: + // ℓ × res + res = pr.MulBy034( + res, + pr.MulByElement(&lines[0][i].R0, xNegOverY), + pr.MulByElement(&lines[0][i].R1, yInv), + ) + case 1: + // multiply by residueWitnessInv when bit=1 + res = pr.Mul(res, residueWitnessInv) + // lines evaluations at P + // and ℓ × ℓ + prodLines := pr.Mul034By034( + pr.MulByElement(&lines[0][i].R0, xNegOverY), + pr.MulByElement(&lines[0][i].R1, yInv), + pr.MulByElement(&lines[1][i].R0, xNegOverY), + pr.MulByElement(&lines[1][i].R1, yInv), + ) + // (ℓ × ℓ) × res + res = pr.MulBy01234(res, prodLines) + case -1: + // multiply by residueWitness when bit=-1 + res = pr.Mul(res, &residueWitness) + // lines evaluations at P + // and ℓ × ℓ + prodLines := pr.Mul034By034( + pr.MulByElement(&lines[0][i].R0, xNegOverY), + pr.MulByElement(&lines[0][i].R1, yInv), + pr.MulByElement(&lines[1][i].R0, xNegOverY), + pr.MulByElement(&lines[1][i].R1, yInv), + ) + // (ℓ × ℓ) × res + res = pr.MulBy01234(res, prodLines) + default: + return nil + } + } + + // Compute ℓ_{[6x₀+2]Q,π(Q)}(P) · ℓ_{[6x₀+2]Q+π(Q),-π²(Q)}(P) + // lines evaluations at P + // and ℓ × ℓ + prodLines := pr.Mul034By034( + pr.MulByElement(&lines[0][65].R0, xNegOverY), + pr.MulByElement(&lines[0][65].R1, yInv), + pr.MulByElement(&lines[1][65].R0, xNegOverY), + pr.MulByElement(&lines[1][65].R1, yInv), + ) + // (ℓ × ℓ) × res + res = pr.MulBy01234(res, prodLines) + + // multiply by previous multi-Miller function + res = pr.Mul(res, previous) + + // Check that res * cubicNonResiduePower * residueWitnessInv^λ' == 1 + // where λ' = q^3 - q^2 + q, with u the BN254 seed + // and residueWitnessInv, cubicNonResiduePower from the hint. + // Note that res is already MillerLoop(P,Q) * residueWitnessInv^{6x₀+2} since + // we initialized the Miller loop accumulator with residueWitnessInv. + t2 := pr.Mul(&cubicNonResiduePower, res) + + t1 := pr.FrobeniusCube(residueWitnessInv) + t0 := pr.FrobeniusSquare(residueWitnessInv) + t1 = pr.DivUnchecked(t1, t0) + t0 = pr.Frobenius(residueWitnessInv) + t1 = pr.Mul(t1, t0) + + t2 = pr.Mul(t2, t1) + + pr.AssertIsEqual(t2, pr.One()) + + return nil } diff --git a/std/algebra/emulated/sw_bn254/pairing_test.go b/std/algebra/emulated/sw_bn254/pairing_test.go index bc41582cde..b6a9a751e2 100644 --- a/std/algebra/emulated/sw_bn254/pairing_test.go +++ b/std/algebra/emulated/sw_bn254/pairing_test.go @@ -63,6 +63,43 @@ func TestFinalExponentiationTestSolve(t *testing.T) { assert.NoError(err) } +type MillerLoopCircuit struct { + InG1 G1Affine + InG2 G2Affine + Res GTEl +} + +func (c *MillerLoopCircuit) Define(api frontend.API) error { + pairing, err := NewPairing(api) + if err != nil { + return fmt.Errorf("new pairing: %w", err) + } + res, err := pairing.MillerLoop([]*G1Affine{&c.InG1}, []*G2Affine{&c.InG2}) + if err != nil { + return fmt.Errorf("pair: %w", err) + } + pairing.AssertIsEqual(res, &c.Res) + return nil +} + +func TestMillerLoopTestSolve(t *testing.T) { + assert := test.NewAssert(t) + p, q := randomG1G2Affines() + lines := bn254.PrecomputeLines(q) + res, err := bn254.MillerLoopFixedQ( + []bn254.G1Affine{p}, + [][2][len(bn254.LoopCounter)]bn254.LineEvaluationAff{lines}, + ) + assert.NoError(err) + witness := MillerLoopCircuit{ + InG1: NewG1Affine(p), + InG2: NewG2Affine(q), + Res: NewGTEl(res), + } + err = test.IsSolved(&MillerLoopCircuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} + type PairCircuit struct { InG1 G1Affine InG2 G2Affine @@ -226,6 +263,114 @@ func TestGroupMembershipSolve(t *testing.T) { assert.NoError(err) } +type IsOnTwistCircuit struct { + Q G2Affine + Expected frontend.Variable +} + +func (c *IsOnTwistCircuit) Define(api frontend.API) error { + pairing, err := NewPairing(api) + if err != nil { + return fmt.Errorf("new pairing: %w", err) + } + res := pairing.IsOnTwist(&c.Q) + api.AssertIsEqual(res, c.Expected) + return nil +} + +func TestIsOnTwistSolve(t *testing.T) { + assert := test.NewAssert(t) + // test for a point not on the twist + var Q bn254.G2Affine + _, err := Q.X.A0.SetString("0x119606e6d3ea97cea4eff54433f5c7dbc026b8d0670ddfbe6441e31225028d31") + assert.NoError(err) + _, err = Q.X.A1.SetString("0x1d3df5be6084324da6333a6ad1367091ca9fbceb70179ec484543a58b8cb5d63") + assert.NoError(err) + _, err = Q.Y.A0.SetString("0x1b9a36ea373fe2c5b713557042ce6deb2907d34e12be595f9bbe84c144de86ef") + assert.NoError(err) + _, err = Q.Y.A1.SetString("0x49fe60975e8c78b7b31a6ed16a338ac8b28cf6a065cfd2ca47e9402882518ba0") + assert.NoError(err) + assert.False(Q.IsOnCurve()) + witness := IsOnTwistCircuit{ + Q: NewG2Affine(Q), + Expected: 0, + } + err = test.IsSolved(&IsOnTwistCircuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + // test for a point on the twist + _, Q = randomG1G2Affines() + assert.True(Q.IsOnCurve()) + witness = IsOnTwistCircuit{ + Q: NewG2Affine(Q), + Expected: 1, + } + err = test.IsSolved(&IsOnTwistCircuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} + +type IsOnG2Circuit struct { + Q G2Affine + Expected frontend.Variable +} + +func (c *IsOnG2Circuit) Define(api frontend.API) error { + pairing, err := NewPairing(api) + if err != nil { + return fmt.Errorf("new pairing: %w", err) + } + res := pairing.IsOnG2(&c.Q) + api.AssertIsEqual(res, c.Expected) + return nil +} + +func TestIsOnG2Solve(t *testing.T) { + assert := test.NewAssert(t) + // test for a point not on the curve + var Q bn254.G2Affine + _, err := Q.X.A0.SetString("0x119606e6d3ea97cea4eff54433f5c7dbc026b8d0670ddfbe6441e31225028d31") + assert.NoError(err) + _, err = Q.X.A1.SetString("0x1d3df5be6084324da6333a6ad1367091ca9fbceb70179ec484543a58b8cb5d63") + assert.NoError(err) + _, err = Q.Y.A0.SetString("0x1b9a36ea373fe2c5b713557042ce6deb2907d34e12be595f9bbe84c144de86ef") + assert.NoError(err) + _, err = Q.Y.A1.SetString("0x49fe60975e8c78b7b31a6ed16a338ac8b28cf6a065cfd2ca47e9402882518ba0") + assert.NoError(err) + assert.False(Q.IsOnCurve()) + witness := IsOnG2Circuit{ + Q: NewG2Affine(Q), + Expected: 0, + } + err = test.IsSolved(&IsOnG2Circuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + // test for a point on curve not in G2 + _, err = Q.X.A0.SetString("0x07192b9fd0e2a32e3e1caa8e59462b757326d48f641924e6a1d00d66478913eb") + assert.NoError(err) + _, err = Q.X.A1.SetString("0x15ce93f1b1c4946dd6cfbb3d287d9c9a1cdedb264bda7aada0844416d8a47a63") + assert.NoError(err) + _, err = Q.Y.A0.SetString("0x0fa65a9b48ba018361ed081e3b9e958451de5d9e8ae0bd251833ebb4b2fafc96") + assert.NoError(err) + _, err = Q.Y.A1.SetString("0x06e1f5e20f68f6dfa8a91a3bea048df66d9eaf56cc7f11215401f7e05027e0c6") + assert.NoError(err) + assert.True(Q.IsOnCurve()) + assert.False(Q.IsInSubGroup()) + witness = IsOnG2Circuit{ + Q: NewG2Affine(Q), + Expected: 0, + } + err = test.IsSolved(&IsOnG2Circuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + // test for a point in G2 + _, Q = randomG1G2Affines() + assert.True(Q.IsOnCurve()) + assert.True(Q.IsInSubGroup()) + witness = IsOnG2Circuit{ + Q: NewG2Affine(Q), + Expected: 1, + } + err = test.IsSolved(&IsOnG2Circuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} + // bench func BenchmarkPairing(b *testing.B) { diff --git a/std/algebra/emulated/sw_bw6761/pairing.go b/std/algebra/emulated/sw_bw6761/pairing.go index b6fb462139..88485288cb 100644 --- a/std/algebra/emulated/sw_bw6761/pairing.go +++ b/std/algebra/emulated/sw_bw6761/pairing.go @@ -141,13 +141,22 @@ func (pr Pairing) Pair(P []*G1Affine, Q []*G2Affine) (*GTEl, error) { // // This function doesn't check that the inputs are in the correct subgroups. func (pr Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { - f, err := pr.Pair(P, Q) + f, err := pr.MillerLoop(P, Q) if err != nil { return err } - one := pr.One() - pr.AssertIsEqual(f, one) + // We perform the easy part of the final exp to push f to the cyclotomic + // subgroup so that FinalExponentiationCheck is carried with optimized + // cyclotomic squaring (e.g. Karabina12345). + // + // f = f^(p³-1)(p+1) + buf := pr.Conjugate(f) + buf = pr.DivUnchecked(buf, f) + f = pr.Frobenius(buf) + f = pr.Mul(f, buf) + + pr.FinalExponentiationCheck(f) return nil } diff --git a/std/algebra/native/sw_bls24315/g1.go b/std/algebra/native/sw_bls24315/g1.go index d65c6217c9..afb8cec716 100644 --- a/std/algebra/native/sw_bls24315/g1.go +++ b/std/algebra/native/sw_bls24315/g1.go @@ -188,21 +188,7 @@ func (P *G1Affine) varScalarMul(api frontend.API, Q G1Affine, s frontend.Variabl // curve. cc := getInnerCurveConfig(api.Compiler().Field()) - // the hints allow to decompose the scalar s into s1 and s2 such that - // s1 + λ * s2 == s mod r, - // where λ is third root of one in 𝔽_r. - sd, err := api.Compiler().NewHint(decomposeScalarG1Simple, 3, s) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - s1, s2 := sd[0], sd[1] - - // s1 + λ * s2 == s mod r - api.AssertIsEqual( - api.Add(s1, api.Mul(s2, cc.lambda)), - api.Add(s, api.Mul(cc.fr, sd[2])), - ) + s1, s2 := callDecomposeScalar(api, s, true) nbits := 127 s1bits := api.ToBinary(s1, nbits) @@ -451,24 +437,8 @@ func (P *G1Affine) jointScalarMul(api frontend.API, Q, R G1Affine, s, t frontend // P = [s]Q + [t]R using Shamir's trick func (P *G1Affine) jointScalarMulUnsafe(api frontend.API, Q, R G1Affine, s, t frontend.Variable) *G1Affine { cc := getInnerCurveConfig(api.Compiler().Field()) - - sd, err := api.Compiler().NewHint(decomposeScalarG1, 3, s) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - s1, s2 := sd[0], sd[1] - - td, err := api.Compiler().NewHint(decomposeScalarG1, 3, t) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - t1, t2 := td[0], td[1] - - api.AssertIsEqual(api.Add(s1, api.Mul(s2, cc.lambda)), api.Add(s, api.Mul(cc.fr, sd[2]))) - api.AssertIsEqual(api.Add(t1, api.Mul(t2, cc.lambda)), api.Add(t, api.Mul(cc.fr, td[2]))) - + s1, s2 := callDecomposeScalar(api, s, false) + t1, t2 := callDecomposeScalar(api, t, false) nbits := cc.lambda.BitLen() + 1 s1bits := api.ToBinary(s1, nbits) diff --git a/std/algebra/native/sw_bls24315/g2.go b/std/algebra/native/sw_bls24315/g2.go index 85aa37cf8f..ed74bec16f 100644 --- a/std/algebra/native/sw_bls24315/g2.go +++ b/std/algebra/native/sw_bls24315/g2.go @@ -201,21 +201,7 @@ func (P *g2AffP) varScalarMul(api frontend.API, Q g2AffP, s frontend.Variable, o // curve. cc := getInnerCurveConfig(api.Compiler().Field()) - // the hints allow to decompose the scalar s into s1 and s2 such that - // s1 + λ * s2 == s mod r, - // where λ is third root of one in 𝔽_r. - sd, err := api.Compiler().NewHint(decomposeScalarG1Simple, 3, s) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - s1, s2 := sd[0], sd[1] - - // s1 + λ * s2 == s mod r, - api.AssertIsEqual( - api.Add(s1, api.Mul(s2, cc.lambda)), - api.Add(s, api.Mul(cc.fr, sd[2])), - ) + s1, s2 := callDecomposeScalar(api, s, true) nbits := 127 s1bits := api.ToBinary(s1, nbits) diff --git a/std/algebra/native/sw_bls24315/hints.go b/std/algebra/native/sw_bls24315/hints.go index 0404212674..d20fac537c 100644 --- a/std/algebra/native/sw_bls24315/hints.go +++ b/std/algebra/native/sw_bls24315/hints.go @@ -6,13 +6,16 @@ import ( "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark/constraint/solver" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/emulated/emparams" ) func GetHints() []solver.Hint { return []solver.Hint{ - decomposeScalarG1, - decomposeScalarG1Simple, - decomposeScalarG2, + decomposeScalar, + decomposeScalarSimple, + decompose, } } @@ -20,63 +23,105 @@ func init() { solver.RegisterHint(GetHints()...) } -func decomposeScalarG1Simple(scalarField *big.Int, inputs []*big.Int, outputs []*big.Int) error { - if len(inputs) != 1 { - return fmt.Errorf("expecting one input") - } - if len(outputs) != 3 { - return fmt.Errorf("expecting three outputs") - } - cc := getInnerCurveConfig(scalarField) - sp := ecc.SplitScalar(inputs[0], cc.glvBasis) - outputs[0].Set(&(sp[0])) - outputs[1].Set(&(sp[1])) - // figure out how many times we have overflowed - outputs[2].Mul(outputs[1], cc.lambda).Add(outputs[2], outputs[0]) - outputs[2].Sub(outputs[2], inputs[0]) - outputs[2].Div(outputs[2], cc.fr) +func decomposeScalarSimple(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { + return emulated.UnwrapHintWithNativeInput(nativeInputs, nativeOutputs, func(nnMod *big.Int, nninputs, nnOutputs []*big.Int) error { + if len(nninputs) != 1 { + return fmt.Errorf("expecting one input") + } + if len(nnOutputs) != 2 { + return fmt.Errorf("expecting two outputs") + } + cc := getInnerCurveConfig(nativeMod) + sp := ecc.SplitScalar(nninputs[0], cc.glvBasis) + nnOutputs[0].Set(&(sp[0])) + nnOutputs[1].Set(&(sp[1])) - return nil + return nil + }) } -func decomposeScalarG1(scalarField *big.Int, inputs []*big.Int, res []*big.Int) error { - cc := getInnerCurveConfig(scalarField) - sp := ecc.SplitScalar(inputs[0], cc.glvBasis) - res[0].Set(&(sp[0])) - res[1].Set(&(sp[1])) - one := big.NewInt(1) - // add (lambda+1, lambda) until scalar compostion is over Fr to ensure that - // the high bits are set in decomposition. - for res[0].Cmp(cc.lambda) < 1 && res[1].Cmp(cc.lambda) < 1 { - res[0].Add(res[0], cc.lambda) - res[0].Add(res[0], one) - res[1].Add(res[1], cc.lambda) - } - // figure out how many times we have overflowed - res[2].Mul(res[1], cc.lambda).Add(res[2], res[0]) - res[2].Sub(res[2], inputs[0]) - res[2].Div(res[2], cc.fr) +func decomposeScalar(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { + return emulated.UnwrapHintWithNativeInput(nativeInputs, nativeOutputs, func(nnMod *big.Int, nninputs, nnOutputs []*big.Int) error { + if len(nninputs) != 1 { + return fmt.Errorf("expecting one input") + } + if len(nnOutputs) != 2 { + return fmt.Errorf("expecting two outputs") + } + cc := getInnerCurveConfig(nativeMod) + sp := ecc.SplitScalar(nninputs[0], cc.glvBasis) + nnOutputs[0].Set(&(sp[0])) + nnOutputs[1].Set(&(sp[1])) + one := big.NewInt(1) + // add (lambda+1, lambda) until scalar compostion is over Fr to ensure that + // the high bits are set in decomposition. + for nnOutputs[0].Cmp(cc.lambda) < 1 && nnOutputs[1].Cmp(cc.lambda) < 1 { + nnOutputs[0].Add(nnOutputs[0], cc.lambda) + nnOutputs[0].Add(nnOutputs[0], one) + nnOutputs[1].Add(nnOutputs[1], cc.lambda) + } - return nil + return nil + }) } -func decomposeScalarG2(scalarField *big.Int, inputs []*big.Int, res []*big.Int) error { - cc := getInnerCurveConfig(scalarField) - sp := ecc.SplitScalar(inputs[0], cc.glvBasis) - res[0].Set(&(sp[0])) - res[1].Set(&(sp[1])) - one := big.NewInt(1) - // add (lambda+1, lambda) until scalar compostion is over Fr to ensure that - // the high bits are set in decomposition. - for res[0].Cmp(cc.lambda) < 1 && res[1].Cmp(cc.lambda) < 1 { - res[0].Add(res[0], cc.lambda) - res[0].Add(res[0], one) - res[1].Add(res[1], cc.lambda) +func callDecomposeScalar(api frontend.API, s frontend.Variable, simple bool) (s1, s2 frontend.Variable) { + var fr emparams.BLS24315Fr + cc := getInnerCurveConfig(api.Compiler().Field()) + sapi, err := emulated.NewField[emparams.BLS24315Fr](api) + if err != nil { + panic(err) + } + var hintFn solver.Hint + if simple { + hintFn = decomposeScalarSimple + } else { + hintFn = decomposeScalar + } + // compute the decomposition using a hint. We have to use the emulated + // version which takes native input and outputs non-native outputs. + // + // the hints allow to decompose the scalar s into s1 and s2 such that + // s1 + λ * s2 == s mod r, + // where λ is third root of one in 𝔽_r. + sd, err := sapi.NewHintWithNativeInput(hintFn, 2, s) + if err != nil { + panic(err) + } + // lambda as nonnative element + lambdaEmu := sapi.NewElement(cc.lambda) + // the scalar as nonnative element. We need to split at 64 bits. + limbs, err := api.NewHint(decompose, int(fr.NbLimbs()), s) + if err != nil { + panic(err) + } + semu := sapi.NewElement(limbs) + // s1 + λ * s2 == s mod r + lhs := sapi.MulNoReduce(sd[1], lambdaEmu) + lhs = sapi.Add(lhs, sd[0]) + + sapi.AssertIsEqual(lhs, semu) + + s1 = 0 + s2 = 0 + b := big.NewInt(1) + for i := range sd[0].Limbs { + s1 = api.Add(s1, api.Mul(sd[0].Limbs[i], b)) + s2 = api.Add(s2, api.Mul(sd[1].Limbs[i], b)) + b.Lsh(b, 64) } - // figure out how many times we have overflowed - res[2].Mul(res[1], cc.lambda).Add(res[2], res[0]) - res[2].Sub(res[2], inputs[0]) - res[2].Div(res[2], cc.fr) + return s1, s2 +} +func decompose(mod *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 1 && len(outputs) != 4 { + return fmt.Errorf("input/output length mismatch") + } + tmp := new(big.Int).Set(inputs[0]) + mask := new(big.Int).SetUint64(^uint64(0)) + for i := 0; i < 4; i++ { + outputs[i].And(tmp, mask) + tmp.Rsh(tmp, 64) + } return nil } diff --git a/std/algebra/native/sw_bls24315/pairing2.go b/std/algebra/native/sw_bls24315/pairing2.go index 3bbbc4d041..643314ef4a 100644 --- a/std/algebra/native/sw_bls24315/pairing2.go +++ b/std/algebra/native/sw_bls24315/pairing2.go @@ -174,16 +174,7 @@ func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar, opts ...algopts } gamma := c.packScalarToVar(scalars[0]) // decompose gamma in the endomorphism eigenvalue basis and bit-decompose the sub-scalars - cc := getInnerCurveConfig(c.api.Compiler().Field()) - sd, err := c.api.Compiler().NewHint(decomposeScalarG1Simple, 3, gamma) - if err != nil { - panic(err) - } - gamma1, gamma2 := sd[0], sd[1] - c.api.AssertIsEqual( - c.api.Add(gamma1, c.api.Mul(gamma2, cc.lambda)), - c.api.Add(gamma, c.api.Mul(cc.fr, sd[2])), - ) + gamma1, gamma2 := callDecomposeScalar(c.api, gamma, true) nbits := 127 gamma1Bits := c.api.ToBinary(gamma1, nbits) gamma2Bits := c.api.ToBinary(gamma2, nbits) diff --git a/std/evmprecompiles/08-bnpairing.go b/std/evmprecompiles/08-bnpairing.go index 9d25cd20ec..b796ee458e 100644 --- a/std/evmprecompiles/08-bnpairing.go +++ b/std/evmprecompiles/08-bnpairing.go @@ -10,12 +10,20 @@ import ( // [ALT_BN128_PAIRING_CHECK]: https://ethereum.github.io/execution-specs/autoapi/ethereum/paris/vm/precompiled_contracts/alt_bn128/index.html#alt-bn128-pairing-check // // To have a fixed-circuit regardless of the number of inputs, we need 2 fixed circuits: -// - A Miller loop of fixed size 1 followed with a multiplication in 𝔽p¹² (MillerLoopAndMul) -// - A final exponentiation followed with an equality check in GT (FinalExponentiationIsOne) +// - MillerLoopAndMul: +// A Miller loop of fixed size 1 followed by a multiplication in 𝔽p¹². +// - MillerLoopAndFinalExpCheck: +// A Miller loop of fixed size 1 followed by a multiplication in 𝔽p¹², and +// a check that the result lies in the same equivalence class as the +// reduced pairing purported to be 1. This check replaces the final +// exponentiation step in-circuit and follows Section 4 of [On Proving +// Pairings] paper by A. Novakovic and L. Eagen. // -// N.B.: This is a sub-optimal routine but defines a fixed circuit regardless -// of the number of inputs. We can extend this routine to handle a 2-by-2 -// logic but we prefer a minimal number of circuits (2). +// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf +// +// N.B.: This is a sub-optimal routine but defines a fixed circuit regardless +// of the number of inputs. We can extend this routine to handle a 2-by-2 +// logic but we prefer a minimal number of circuits (2). func ECPair(api frontend.API, P []*sw_bn254.G1Affine, Q []*sw_bn254.G2Affine) { if len(P) != len(Q) { @@ -37,7 +45,7 @@ func ECPair(api frontend.API, P []*sw_bn254.G1Affine, Q []*sw_bn254.G2Affine) { // 3- Check that ∏ᵢ e(Pᵢ, Qᵢ) == 1 ml := pair.One() - for i := 0; i < n; i++ { + for i := 0; i < n-1; i++ { // fixed circuit 1 ml, err = pair.MillerLoopAndMul(P[i], Q[i], ml) if err != nil { @@ -46,5 +54,5 @@ func ECPair(api frontend.API, P []*sw_bn254.G1Affine, Q []*sw_bn254.G2Affine) { } // fixed circuit 2 - pair.FinalExponentiationIsOne(ml) + pair.MillerLoopAndFinalExpCheck(P[n-1], Q[n-1], ml) } diff --git a/std/hash/mimc/doc.go b/std/hash/mimc/doc.go new file mode 100644 index 0000000000..915b2ad3b1 --- /dev/null +++ b/std/hash/mimc/doc.go @@ -0,0 +1,41 @@ +// Package mimc provides a ZKP-circuit function to compute a MiMC hash. +// +// For the reference implementation of the MiMC hash function, see the +// corresponding package in [gnark-crypto]. +// +// # Length extension attack +// +// The MiMC hash function is vulnerable to a length extension attack. For +// example when we have a hash +// +// h = MiMC(k || m) +// +// and we want to hash a new message +// +// m' = m || m2, +// +// we can compute +// +// h' = MiMC(k || m || m2) +// +// without knowing k by computing +// +// h' = MiMC(h || m2). +// +// This is because the MiMC hash function is a simple iterated cipher, and the +// hash value is the state of the cipher after encrypting the message. +// +// There are several ways to mitigate this attack: +// - use a random key for each hash +// - use a domain separation tag for different use cases: +// h = MiMC(k || tag || m) +// - use the secret input as last input: +// h = MiMC(m || k) +// +// In general, inside a circuit the length-extension attack is not a concern as +// due to the circuit definition the attacker can not append messages to +// existing hash. But the user has to consider the cases when using a secret key +// and MiMC in different contexts. +// +// [gnark-crypto]: https://pkg.go.dev/github.com/consensys/gnark-crypto/hash +package mimc diff --git a/std/hash/mimc/mimc.go b/std/hash/mimc/mimc.go index 210ecdf652..1d6fa4d35c 100644 --- a/std/hash/mimc/mimc.go +++ b/std/hash/mimc/mimc.go @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package mimc provides a ZKP-circuit function to compute a MiMC hash. package mimc import ( @@ -26,7 +25,9 @@ import ( "github.com/consensys/gnark/internal/utils" ) -// MiMC contains the params of the Mimc hash func and the curves on which it is implemented +// MiMC contains the params of the MiMC hash func and the curves on which it is implemented. +// +// NB! See the package documentation for length extension attack consideration. type MiMC struct { params []big.Int // slice containing constants for the encryption rounds id ecc.ID // id needed to know which encryption function to use @@ -35,7 +36,12 @@ type MiMC struct { api frontend.API // underlying constraint system } -// NewMiMC returns a MiMC instance, that can be used in a gnark circuit +// NewMiMC returns a MiMC instance that can be used in a gnark circuit. The +// out-circuit counterpart of this function is provided in [gnark-crypto]. +// +// NB! See the package documentation for length extension attack consideration. +// +// [gnark-crypto]: https://pkg.go.dev/github.com/consensys/gnark-crypto/hash func NewMiMC(api frontend.API) (MiMC, error) { // TODO @gbotrel use field if constructor, ok := newMimc[utils.FieldToCurve(api.Compiler().Field())]; ok { @@ -55,10 +61,10 @@ func (h *MiMC) Reset() { h.h = 0 } -// Sum hash (in r1cs form) using Miyaguchi–Preneel: -// https://en.wikipedia.org/wiki/One-way_compression_function -// The XOR operation is replaced by field addition. -// See github.com/consensys/gnark-crypto for reference implementation. +// Sum hash using [Miyaguchi–Preneel] where the XOR operation is replaced by +// field addition. +// +// [Miyaguchi–Preneel]: https://en.wikipedia.org/wiki/One-way_compression_function func (h *MiMC) Sum() frontend.Variable { //h.Write(data...)s diff --git a/std/math/emulated/field.go b/std/math/emulated/field.go index 6c1f19b04d..96bca2fd79 100644 --- a/std/math/emulated/field.go +++ b/std/math/emulated/field.go @@ -43,7 +43,7 @@ type Field[T FieldParams] struct { log zerolog.Logger - constrainedLimbs map[uint64]struct{} + constrainedLimbs map[[16]byte]struct{} checker frontend.Rangechecker mulChecks []mulCheck[T] @@ -69,7 +69,7 @@ func NewField[T FieldParams](native frontend.API) (*Field[T], error) { f := &Field[T]{ api: native, log: logger.Logger(), - constrainedLimbs: make(map[uint64]struct{}), + constrainedLimbs: make(map[[16]byte]struct{}), checker: rangecheck.New(native), } @@ -216,7 +216,7 @@ func (f *Field[T]) enforceWidthConditional(a *Element[T]) (didConstrain bool) { } continue } - if vv, ok := a.Limbs[i].(interface{ HashCode() uint64 }); ok { + if vv, ok := a.Limbs[i].(interface{ HashCode() [16]byte }); ok { // okay, this is a canonical variable and it has a hashcode. We use // it to see if the limb is already constrained. h := vv.HashCode() diff --git a/std/math/emulated/field_assert.go b/std/math/emulated/field_assert.go index 5c2c700663..86ff353424 100644 --- a/std/math/emulated/field_assert.go +++ b/std/math/emulated/field_assert.go @@ -111,7 +111,7 @@ func (f *Field[T]) IsZero(a *Element[T]) frontend.Variable { // as ca is already reduced, then every limb overflow is already 0. Only // every addition adds a bit to the overflow totalOverflow := len(ca.Limbs) - 1 - if totalOverflow < int(f.maxOverflow()) { + if totalOverflow > int(f.maxOverflow()) { // the sums of limbs would overflow the native field. Use the first // approach instead. res := f.api.IsZero(ca.Limbs[0]) diff --git a/std/multicommit/nativecommit.go b/std/multicommit/nativecommit.go index 4f8e56a0cc..c260a16f46 100644 --- a/std/multicommit/nativecommit.go +++ b/std/multicommit/nativecommit.go @@ -89,15 +89,16 @@ func (mct *multicommitter) commitAndCall(api frontend.API) error { if !ok { panic("compiler doesn't implement frontend.Committer") } - cmt, err := commiter.Commit(mct.vars...) + rootCmt, err := commiter.Commit(mct.vars...) if err != nil { return fmt.Errorf("commit: %w", err) } + cmt := rootCmt if err = mct.cbs[0](api, cmt); err != nil { return fmt.Errorf("callback 0: %w", err) } for i := 1; i < len(mct.cbs); i++ { - cmt = api.Mul(cmt, cmt) + cmt = api.Mul(rootCmt, cmt) if err := mct.cbs[i](api, cmt); err != nil { return fmt.Errorf("callback %d: %w", i, err) } diff --git a/std/rangecheck/rangecheck_commit.go b/std/rangecheck/rangecheck_commit.go index 457c00ef36..acccb6bcca 100644 --- a/std/rangecheck/rangecheck_commit.go +++ b/std/rangecheck/rangecheck_commit.go @@ -154,10 +154,10 @@ func (c *commitChecker) getOptimalBasewidth(api frontend.API) int { } func optimalWidth(countFn func(baseLength int, collected []checkedVariable) int, collected []checkedVariable) int { - min := math.MaxInt64 + min := int64(math.MaxInt64) minVal := 0 for j := 2; j < 18; j++ { - current := countFn(j, collected) + current := int64(countFn(j, collected)) if current < min { min = current minVal = j diff --git a/std/recursion/wrapped_hash.go b/std/recursion/wrapped_hash.go index ab7b65137c..62dd1d740e 100644 --- a/std/recursion/wrapped_hash.go +++ b/std/recursion/wrapped_hash.go @@ -30,6 +30,9 @@ type shortNativeHash struct { // field and outputs element in the target field (usually the scalar field of // the circuit being recursed). The hash function is based on MiMC and // partitions the excess bits to not overflow the target field. +// +// NB! See the considerations in the package documentation of [mimc] for length +// extension attack. func NewShort(current, target *big.Int) (hash.Hash, error) { var h cryptomimc.Hash var bitBlockSize int @@ -155,8 +158,12 @@ func newHashFromParameter(api frontend.API, hf stdhash.FieldHasher, bitLength in // NewHash returns a circuit hash function which reads elements in the current // native field and outputs element in the target field (usually the scalar -// field of the circuit being recursed). The hash function is based on MiMC and -// partitions the excess bits to not overflow the target field. +// field of the circuit being recursed). The hash function is based on MiMC +// (from [mimc] package) and partitions the excess bits to not overflow the +// target field. +// +// NB! See the considerations in the package documentation of [mimc] for length +// extension attack. func NewHash(api frontend.API, target *big.Int, bitmode bool) (stdhash.FieldHasher, error) { h, err := mimc.NewMiMC(api) if err != nil { diff --git a/test/assert_checkcircuit.go b/test/assert_checkcircuit.go index 16420065fb..d75700513b 100644 --- a/test/assert_checkcircuit.go +++ b/test/assert_checkcircuit.go @@ -7,6 +7,7 @@ import ( "github.com/consensys/gnark/backend" "github.com/consensys/gnark/backend/groth16" "github.com/consensys/gnark/backend/plonk" + "github.com/consensys/gnark/backend/solidity" "github.com/consensys/gnark/backend/witness" "github.com/consensys/gnark/constraint" "github.com/consensys/gnark/frontend" @@ -139,9 +140,9 @@ func (assert *Assert) CheckCircuit(circuit frontend.Circuit, opts ...TestingOpti if checkSolidity { // check that the proof can be verified by gnark-solidity-checker - if _vk, ok := vk.(verifyingKey); ok { + if _vk, ok := vk.(solidity.VerifyingKey); ok { assert.Run(func(assert *Assert) { - assert.solidityVerification(b, _vk, proof, w.public) + assert.solidityVerification(b, _vk, proof, w.public, opt.solidityOpts) }, "solidity") } } diff --git a/test/assert_options.go b/test/assert_options.go index 31d0226183..570b3bbaa9 100644 --- a/test/assert_options.go +++ b/test/assert_options.go @@ -5,6 +5,7 @@ import ( "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/solidity" "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/frontend" ) @@ -20,6 +21,7 @@ type testingConfig struct { proverOpts []backend.ProverOption verifierOpts []backend.VerifierOption compileOpts []frontend.CompileOption + solidityOpts []solidity.ExportOption validAssignments []frontend.Circuit invalidAssignments []frontend.Circuit @@ -176,3 +178,12 @@ func WithVerifierOpts(verifierOpts ...backend.VerifierOption) TestingOption { return nil } } + +// WithSolidityExportOptions is a testing option which uses the given solidityOpts when +// calling ExportSolidity method on the verification key. +func WithSolidityExportOptions(solidityOpts ...solidity.ExportOption) TestingOption { + return func(tc *testingConfig) error { + tc.solidityOpts = solidityOpts + return nil + } +} diff --git a/test/assert_solidity.go b/test/assert_solidity.go index 16d1437706..f72818aae9 100644 --- a/test/assert_solidity.go +++ b/test/assert_solidity.go @@ -2,7 +2,6 @@ package test import ( "encoding/hex" - "io" "os" "os/exec" "path/filepath" @@ -10,25 +9,28 @@ import ( fr_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/fr" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/solidity" "github.com/consensys/gnark/backend/witness" ) -type verifyingKey interface { - NbPublicWitness() int - ExportSolidity(io.Writer) error -} - // solidityVerification checks that the exported solidity contract can verify the proof // and that the proof is valid. // It uses gnark-solidity-checker see test.WithSolidity option. -func (assert *Assert) solidityVerification(b backend.ID, vk verifyingKey, +func (assert *Assert) solidityVerification(b backend.ID, vk solidity.VerifyingKey, proof any, - validPublicWitness witness.Witness) { + validPublicWitness witness.Witness, + opts []solidity.ExportOption, +) { if !SolcCheck || len(validPublicWitness.Vector().(fr_bn254.Vector)) == 0 { return // nothing to check, will make solc fail. } assert.t.Helper() + // set default options for CI when none are provided + if len(opts) == 0 { + opts = append(opts, solidity.WithPragmaVersion("^0.8.0")) // to avoid needing sync Solidity CI all the time + } + // make temp dir tmpDir, err := os.MkdirTemp("", "gnark-solidity-check*") assert.NoError(err) @@ -38,7 +40,7 @@ func (assert *Assert) solidityVerification(b backend.ID, vk verifyingKey, fSolidity, err := os.Create(filepath.Join(tmpDir, "gnark_verifier.sol")) assert.NoError(err) - err = vk.ExportSolidity(fSolidity) + err = vk.ExportSolidity(fSolidity, opts...) assert.NoError(err) err = fSolidity.Close()