diff --git a/backend/plonk/bls12-377/marshal.go b/backend/plonk/bls12-377/marshal.go index 3d5eebd1d5..cba95047c3 100644 --- a/backend/plonk/bls12-377/marshal.go +++ b/backend/plonk/bls12-377/marshal.go @@ -156,7 +156,6 @@ func (pk *ProvingKey) writeTo(w io.Writer, withCompression bool) (n int64, err e pk.trace.Qo.Coefficients(), pk.trace.Qk.Coefficients(), coefficients(pk.trace.Qcp), - pk.lQk.Coefficients(), pk.trace.S1.Coefficients(), pk.trace.S2.Coefficients(), pk.trace.S3.Coefficients(), @@ -215,7 +214,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err dec := curve.NewDecoder(r) - var ql, qr, qm, qo, qk, lqk, s1, s2, s3 []fr.Element + var ql, qr, qm, qo, qk, s1, s2, s3 []fr.Element var qcp [][]fr.Element // TODO @gbotrel: this is a bit ugly, we should probably refactor this. @@ -231,16 +230,15 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err chErr chan error } - vectors := make([]v, 9) + vectors := make([]v, 8) vectors[0] = v{data: (*fr.Vector)(&ql)} vectors[1] = v{data: (*fr.Vector)(&qr)} vectors[2] = v{data: (*fr.Vector)(&qm)} vectors[3] = v{data: (*fr.Vector)(&qo)} vectors[4] = v{data: (*fr.Vector)(&qk)} - vectors[5] = v{data: (*fr.Vector)(&lqk)} - vectors[6] = v{data: (*fr.Vector)(&s1)} - vectors[7] = v{data: (*fr.Vector)(&s2)} - vectors[8] = v{data: (*fr.Vector)(&s3)} + vectors[5] = v{data: (*fr.Vector)(&s1)} + vectors[6] = v{data: (*fr.Vector)(&s2)} + vectors[7] = v{data: (*fr.Vector)(&s3)} // read ql, qr, qm, qo, qk for i := 0; i < 5; i++ { @@ -258,7 +256,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err } // read lqk, s1, s2, s3 - for i := 5; i < 9; i++ { + for i := 5; i < 8; i++ { n2, err, ch := vectors[i].data.AsyncReadFrom(r) n += n2 if err != nil { @@ -293,8 +291,6 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err for i := range qcp { pk.trace.Qcp[i] = iop.NewPolynomial(&qcp[i], canReg) } - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) // wait for FFT to be precomputed <-chDomain0 diff --git a/backend/plonk/bls12-377/marshal_test.go b/backend/plonk/bls12-377/marshal_test.go index 9325743aaf..76f40b01b8 100644 --- a/backend/plonk/bls12-377/marshal_test.go +++ b/backend/plonk/bls12-377/marshal_test.go @@ -166,7 +166,6 @@ func (pk *ProvingKey) randomize() { qm := randomScalars(n) qo := randomScalars(n) qk := randomScalars(n) - lqk := randomScalars(n) s1 := randomScalars(n) s2 := randomScalars(n) s3 := randomScalars(n) @@ -191,9 +190,6 @@ func (pk *ProvingKey) randomize() { pk.trace.S[0] = -12 pk.trace.S[len(pk.trace.S)-1] = 8888 - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) - pk.computeLagrangeCosetPolys() } diff --git a/backend/plonk/bls12-377/prove.go b/backend/plonk/bls12-377/prove.go index 3f767215ba..4297937c13 100644 --- a/backend/plonk/bls12-377/prove.go +++ b/backend/plonk/bls12-377/prove.go @@ -18,6 +18,7 @@ package plonk import ( "crypto/sha256" + "errors" "math/big" "runtime" "sync" @@ -100,15 +101,19 @@ func bsb22ComputeCommitmentHint(spr *cs.SparseR1CS, pk *ProvingKey, proof *Proof } func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*Proof, error) { + log := logger.Logger().With(). + Str("curve", spr.CurveID().String()). + Int("nbConstraints", spr.GetNbConstraints()). + Str("backend", "plonk").Logger() - log := logger.Logger().With().Str("curve", spr.CurveID().String()).Int("nbConstraints", spr.GetNbConstraints()).Str("backend", "plonk").Logger() - + // parse the options opt, err := backend.NewProverConfig(opts...) if err != nil { return nil, err } start := time.Now() + // pick a hash function that will be used to derive the challenges hFunc := sha256.New() @@ -122,11 +127,14 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts commitmentVal := make([]fr.Element, len(commitmentInfo)) // TODO @Tabaie get rid of this cCommitments := make([]*iop.Polynomial, len(commitmentInfo)) proof.Bsb22Commitments = make([]kzg.Digest, len(commitmentInfo)) + + // override the hint for the commitment constraints for i := range commitmentInfo { opt.SolverOpts = append(opt.SolverOpts, solver.OverrideHint(commitmentInfo[i].HintID, bsb22ComputeCommitmentHint(spr, pk, proof, cCommitments, &commitmentVal[i], i))) } + // override the hint for GKR constraints if spr.GkrInfo.Is() { var gkrData cs.GkrSolvingData opt.SolverOpts = append(opt.SolverOpts, @@ -135,47 +143,43 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // query l, r, o in Lagrange basis, not blinded + lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} _solution, err := spr.Solve(fullWitness, opt.SolverOpts...) if err != nil { return nil, err } - // TODO @gbotrel deal with that conversion lazily - lcCommitments := make([]*iop.Polynomial, len(cCommitments)) - for i := range cCommitments { - lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]) // lagrange coset form - } solution := _solution.(*cs.SparseR1CSSolution) evaluationLDomainSmall := []fr.Element(solution.L) evaluationRDomainSmall := []fr.Element(solution.R) evaluationODomainSmall := []fr.Element(solution.O) + wliop := iop.NewPolynomial(&evaluationLDomainSmall, lagReg) + wriop := iop.NewPolynomial(&evaluationRDomainSmall, lagReg) + woiop := iop.NewPolynomial(&evaluationODomainSmall, lagReg) + + // convert the commitment polynomials to LagrangeCoset basis + lcCommitments := make([]*iop.Polynomial, len(cCommitments)) + chLcc := make(chan struct{}, 1) + go func() { + for i := range cCommitments { + lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]).ToRegular() // lagrange coset form + } + close(chLcc) + }() - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} // l, r, o and blinded versions - var ( - wliop, - wriop, - woiop, - bwliop, - bwriop, - bwoiop *iop.Polynomial - ) + var bwliop, bwriop, bwoiop *iop.Polynomial var wgLRO sync.WaitGroup wgLRO.Add(3) go func() { - // we keep in lagrange regular form since iop.BuildRatioCopyConstraint prefers it in this form. - wliop = iop.NewPolynomial(&evaluationLDomainSmall, lagReg) - // we set the underlying slice capacity to domain[1].Cardinality to minimize mem moves. - bwliop = wliop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwliop = wliop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - wriop = iop.NewPolynomial(&evaluationRDomainSmall, lagReg) - bwriop = wriop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwriop = wriop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - woiop = iop.NewPolynomial(&evaluationODomainSmall, lagReg) - bwoiop = woiop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwoiop = woiop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() @@ -189,27 +193,22 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chLcqk := make(chan struct{}, 1) go func() { // compute qk in canonical basis, completed with the public inputs - // We copy the coeffs of qk to pk is not mutated - lqkcoef := pk.lQk.Coefficients() - qkCompletedCanonical := make([]fr.Element, len(lqkcoef)) + // TODO @ThomasPiellard should we do ToLagrange or ToLagrangeCoset here? + lcqk = pk.trace.Qk.Clone(int(pk.Domain[1].Cardinality)).ToLagrange(&pk.Domain[0]).ToRegular() + qkCompletedCanonical := lcqk.Coefficients() copy(qkCompletedCanonical, fw[:len(spr.Public)]) - copy(qkCompletedCanonical[len(spr.Public):], lqkcoef[len(spr.Public):]) for i := range commitmentInfo { qkCompletedCanonical[spr.GetNbPublicVariables()+commitmentInfo[i].CommitmentIndex] = commitmentVal[i] } - pk.Domain[0].FFTInverse(qkCompletedCanonical, fft.DIF) - fft.BitReverse(qkCompletedCanonical) - - canReg := iop.Form{Basis: iop.Canonical, Layout: iop.Regular} - lcqk = iop.NewPolynomial(&qkCompletedCanonical, canReg) - lcqk.ToLagrangeCoset(&pk.Domain[1]) + lcqk.ToCanonical(&pk.Domain[0]).ToRegular(). + ToLagrangeCoset(&pk.Domain[1]).ToRegular() close(chLcqk) }() // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { return nil, err } @@ -234,22 +233,30 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts // l, r, o are already blinded wgLRO.Add(3) + + // compute l,r,o (blinded version) in LagrangeCoset basis. + // we keep the regular version in memory for the next step + var lcbwliop, lcbwriop, lcbwoiop *iop.Polynomial + go func() { - bwliop.ToLagrangeCoset(&pk.Domain[1]) + lcbwliop = bwliop.Clone(int(pk.Domain[1].Cardinality)) + lcbwliop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwriop.ToLagrangeCoset(&pk.Domain[1]) + lcbwriop = bwriop.Clone(int(pk.Domain[1].Cardinality)) + lcbwriop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwoiop.ToLagrangeCoset(&pk.Domain[1]) + lcbwoiop = bwoiop.Clone(int(pk.Domain[1].Cardinality)) + lcbwoiop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() // compute the copy constraint's ratio // note that wliop, wriop and woiop are fft'ed (mutated) in the process. - ziop, err := iop.BuildRatioCopyConstraint( + bwziop, err := iop.BuildRatioCopyConstraint( []*iop.Polynomial{ wliop, wriop, @@ -264,13 +271,18 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts if err != nil { return proof, err } + // unused. + wliop = nil + wriop = nil + woiop = nil // commit to the blinded version of z chZ := make(chan error, 1) - var bwziop, bwsziop *iop.Polynomial + var bwsziop *iop.Polynomial var alpha fr.Element go func() { - bwziop = ziop // iop.NewWrappedPolynomial(&ziop) + // blind Z + // TODO @gbotrel memory wise we should allocate a bigger result for BuildRatioCopyConstraint bwziop.Blind(2) proof.Z, err = kzg.Commit(bwziop.Coefficients(), pk.Kzg, runtime.NumCPU()*2) if err != nil { @@ -283,37 +295,50 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chZ <- err } - // Store z(g*x), without reallocating a slice - bwsziop = bwziop.ShallowClone().Shift(1) - bwsziop.ToLagrangeCoset(&pk.Domain[1]) + // Store z(g*x) + // perf note: converting ToRegular here perfoms better on Apple M1, but not on a hpc machine. + bwsziop = bwziop.Clone().ToLagrangeCoset(&pk.Domain[1]) //.ToRegular() + chZ <- nil close(chZ) }() + // l , r , o, z, zs, qk , Bsb22Commitments, qCPrime + const ( + idx_L int = iota // bwliop + idx_R // bwriop + idx_O // bwoiop + idx_Z // bwsziop + idx_ZS // bwsziop shifted + idx_QK // lcqk + idx_Bsb22Commitments // lcCommitments ... pk.lcQcp... + ) + // Full capture using latest gnark crypto... - fic := func(fql, fqr, fqm, fqo, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { // TODO @Tabaie make use of the fact that qCPrime is a selector: sparse and binary + fic := func(i int, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { var ic, tmp fr.Element - ic.Mul(&fql, &l) - tmp.Mul(&fqr, &r) + ic.Mul(&pk.expandedTrace.Polynomials[i][idx_QL], &l) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QR], &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqm, &l).Mul(&tmp, &r) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QM], &l).Mul(&tmp, &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqo, &o) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QO], &o) ic.Add(&ic, &tmp).Add(&ic, &fqk) nbComms := len(commitmentInfo) - for i := range commitmentInfo { - tmp.Mul(&pi2QcPrime[i], &pi2QcPrime[i+nbComms]) + for j := range commitmentInfo { + tmp.Mul(&pi2QcPrime[j], &pi2QcPrime[j+nbComms]) ic.Add(&ic, &tmp) } return ic } - fo := func(l, r, o, fid, fs1, fs2, fs3, fz, fzs fr.Element) fr.Element { + fo := func(i int, fid, l, r, o, fz, fzs fr.Element) fr.Element { u := &pk.Domain[0].FrMultiplicativeGen var a, b, tmp fr.Element + b.Mul(&beta, &fid) a.Add(&b, &l).Add(&a, &gamma) b.Mul(&b, u) @@ -322,10 +347,10 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts tmp.Mul(&b, u).Add(&tmp, &o).Add(&tmp, &gamma) a.Mul(&a, &tmp).Mul(&a, &fz) - b.Mul(&beta, &fs1).Add(&b, &l).Add(&b, &gamma) - tmp.Mul(&beta, &fs2).Add(&tmp, &r).Add(&tmp, &gamma) + b.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S1]).Add(&b, &l).Add(&b, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S2]).Add(&tmp, &r).Add(&tmp, &gamma) b.Mul(&b, &tmp) - tmp.Mul(&beta, &fs3).Add(&tmp, &o).Add(&tmp, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S3]).Add(&tmp, &o).Add(&tmp, &gamma) b.Mul(&b, &tmp).Mul(&b, &fzs) b.Sub(&b, &a) @@ -333,19 +358,16 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts return b } - fone := func(fz, flone fr.Element) fr.Element { + fone := func(i int, fz fr.Element) fr.Element { one := fr.One() - one.Sub(&fz, &one).Mul(&one, &flone) + one.Sub(&fz, &one).Mul(&one, &pk.expandedTrace.Polynomials[i][idx_LONE]) return one } - // 0 , 1 , 2, 3 , 4 , 5 , 6 , 7, 8 , 9 , 10, 11, 12, 13, 14, 15:15+nbComm , 15+nbComm:15+2×nbComm - // l , r , o, id, s1, s2, s3, z, zs, ql, qr, qm, qo, qk ,lone, Bsb22Commitments, qCPrime - fm := func(x ...fr.Element) fr.Element { - - a := fic(x[9], x[10], x[11], x[12], x[13], x[0], x[1], x[2], x[15:]) - b := fo(x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8]) - c := fone(x[7], x[14]) + fm := func(i int, fid fr.Element, x ...fr.Element) fr.Element { + a := fic(i, x[idx_QK], x[idx_L], x[idx_R], x[idx_O], x[idx_Bsb22Commitments:]) + b := fo(i, fid, x[idx_L], x[idx_R], x[idx_O], x[idx_Z], x[idx_ZS]) + c := fone(i, x[idx_Z]) c.Mul(&c, &alpha).Add(&c, &b).Mul(&c, &alpha).Add(&c, &a) @@ -361,39 +383,36 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // wait for l, r o lagrange coset conversion + <-chLcc wgLRO.Wait() - - toEval := []*iop.Polynomial{ - bwliop, - bwriop, - bwoiop, - pk.lcIdIOP, - pk.lcS1, - pk.lcS2, - pk.lcS3, - bwziop, - bwsziop, - pk.lcQl, - pk.lcQr, - pk.lcQm, - pk.lcQo, - lcqk, - pk.lLoneIOP, - } - toEval = append(toEval, lcCommitments...) // TODO: Add this at beginning - toEval = append(toEval, pk.lcQcp...) - systemEvaluation, err := iop.Evaluate(fm, iop.Form{Basis: iop.LagrangeCoset, Layout: iop.BitReverse}, toEval...) + toEval := make([]*iop.Polynomial, 6+2*len(commitmentInfo)) + toEval[idx_L] = lcbwliop + toEval[idx_R] = lcbwriop + toEval[idx_O] = lcbwoiop + toEval[idx_Z] = bwsziop.ShallowClone() + toEval[idx_ZS] = bwsziop.Shift(1) + toEval[idx_QK] = lcqk + copy(toEval[idx_Bsb22Commitments:], lcCommitments) + copy(toEval[idx_Bsb22Commitments+len(lcCommitments):], pk.lcQcp) + + // systemEvaluation reuses lcqk for memory. + systemEvaluation, err := evaluate(lcqk, pk, fm, toEval...) if err != nil { return nil, err } - // open blinded Z at zeta*z - chbwzIOP := make(chan struct{}, 1) - go func() { - bwziop.ToCanonical(&pk.Domain[1]).ToRegular() - close(chbwzIOP) - }() - h, err := iop.DivideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc + toEval = nil + bwsziop = nil + lcqk = nil + lcbwliop = nil + lcbwriop = nil + lcbwoiop = nil + + for i := range lcCommitments { + lcCommitments[i] = nil + } + + h, err := divideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc if err != nil { return nil, err } @@ -420,7 +439,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var wgEvals sync.WaitGroup wgEvals.Add(3) evalAtZeta := func(poly *iop.Polynomial, res *fr.Element) { - poly.ToCanonical(&pk.Domain[1]).ToRegular() *res = poly.Evaluate(zeta) wgEvals.Done() } @@ -436,7 +454,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var zetaShifted fr.Element zetaShifted.Mul(&zeta, &pk.Vk.Generator) - <-chbwzIOP proof.ZShiftedOpening, err = kzg.Open( bwziop.Coefficients()[:bwziop.BlindedSize()], zetaShifted, @@ -468,11 +485,12 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts h2 := h.Coefficients()[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] h1 := h.Coefficients()[:pk.Domain[0].Cardinality+2] utils.Parallelize(len(foldedH), func(start, end int) { + var t fr.Element for i := start; i < end; i++ { - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 - foldedH[i].Add(&foldedH[i], &h2[i]) // ζ^{m+2)*h3+h2 - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² - foldedH[i].Add(&foldedH[i], &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 + t.Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 + t.Add(&t, &h2[i]) // ζ^{m+2)*h3+h2 + t.Mul(&t, &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² + foldedH[i].Add(&t, &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 } }) close(computeFoldedH) @@ -679,8 +697,15 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, Mul(&lagrangeZeta, &pk.Domain[0].CardinalityInv) // (1/n)*α²*L₁(ζ) s3canonical := pk.trace.S3.Coefficients() + utils.Parallelize(len(blindedZCanonical), func(start, end int) { + cql := pk.trace.Ql.Coefficients() + cqr := pk.trace.Qr.Coefficients() + cqm := pk.trace.Qm.Coefficients() + cqo := pk.trace.Qo.Coefficients() + cqk := pk.trace.Qk.Coefficients() + var t, t0, t1 fr.Element for i := start; i < end; i++ { @@ -696,22 +721,21 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, t.Mul(&t, &alpha) // α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*Z(μζ)*s3(X) - Z(X)*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ)) - cql := pk.trace.Ql.Coefficients() - cqr := pk.trace.Qr.Coefficients() - cqm := pk.trace.Qm.Coefficients() - cqo := pk.trace.Qo.Coefficients() - cqk := pk.trace.Qk.Coefficients() if i < len(cqm) { t1.Mul(&cqm[i], &rl) // linPol = linPol + l(ζ)r(ζ)*Qm(X) + t0.Mul(&cql[i], &lZeta) t0.Add(&t0, &t1) + t.Add(&t, &t0) // linPol = linPol + l(ζ)*Ql(X) t0.Mul(&cqr[i], &rZeta) t.Add(&t, &t0) // linPol = linPol + r(ζ)*Qr(X) - t0.Mul(&cqo[i], &oZeta).Add(&t0, &cqk[i]) + t0.Mul(&cqo[i], &oZeta) + t0.Add(&t0, &cqk[i]) + t.Add(&t, &t0) // linPol = linPol + o(ζ)*Qo(X) + Qk(X) for j := range qcpZeta { @@ -726,3 +750,118 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, }) return blindedZCanonical } + +// TODO @gbotrel these changes below should be merge upstream in gnark-crypto + +// expression represents a multivariate polynomial. +type expression func(i int, fid fr.Element, x ...fr.Element) fr.Element + +// Evaluate evaluates f on each entry of x. The returned value is +// the vector of evaluations of e on x. +// The form of the result is LagrangeCoset Regular. +// The Size field of the result is the same as the one of x[0]. +// The blindedSize field of the result is the same as Size. +// The Shift field of the result is 0. +// The result re-uses result memory space. +func evaluate(result *iop.Polynomial, pk *ProvingKey, f expression, x ...*iop.Polynomial) (*iop.Polynomial, error) { + if len(x) == 0 { + return nil, errors.New("need at lest one input") + } + + // check that the sizes are consistent + n := len(x[0].Coefficients()) + m := len(x) + for i := 1; i < m; i++ { + if n != len(x[i].Coefficients()) { + return nil, errors.New("inconsistent sizes") + } + } + + // result coefficients + r := result.Coefficients() + if len(r) != n { + return nil, errors.New("inconsistent sizes") + } + + utils.Parallelize(n, func(start, end int) { + // inputs to the expression we will evaluate + vx := make([]fr.Element, m) + generator := pk.Domain[1].Generator + + // we inject id polynomial + var fid fr.Element + fid.Exp(generator, big.NewInt(int64(start))) + fid.Mul(&fid, &pk.Domain[1].FrMultiplicativeGen) + + for i := start; i < end; i++ { + for j := 0; j < m; j++ { + vx[j] = x[j].GetCoeff(i) + } + r[i] = f(i, fid, vx...) + fid.Mul(&fid, &generator) + } + }) + + res := iop.NewPolynomial(&r, iop.Form{Layout: iop.Regular, Basis: iop.LagrangeCoset}) + res.SetSize(x[0].Size()) + res.SetBlindedSize(x[0].Size()) + + return res, nil +} + +// divideByXMinusOne +// 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) { + + // check that the basis is LagrangeCoset + if a.Basis != iop.LagrangeCoset { + return nil, errors.New("invalid form") + } + + // prepare the evaluations of x^n-1 on the big domain's coset + xnMinusOneInverseLagrangeCoset := evaluateXnMinusOneDomainBigCoset(domains) + + nbElmts := len(a.Coefficients()) + rho := nbElmts / a.Size() + + r := a.Coefficients() + + utils.Parallelize(nbElmts, func(start, end int) { + for i := start; i < end; i++ { + r[i].Mul(&r[i], &xnMinusOneInverseLagrangeCoset[i%rho]) + } + }) + + // TODO @gbotrel this is the only place we do a FFT inverse (on coset) with domain[1] + a.ToCanonical(domains[1]).ToRegular() + + return a, nil + +} + +// evaluateXnMinusOneDomainBigCoset evaluates Xᵐ-1 on DomainBig coset +func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { + + ratio := domains[1].Cardinality / domains[0].Cardinality + + res := make([]fr.Element, ratio) + + expo := big.NewInt(int64(domains[0].Cardinality)) + res[0].Exp(domains[1].FrMultiplicativeGen, expo) + + var t fr.Element + t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + + one := fr.One() + + for i := 1; i < int(ratio); i++ { + res[i].Mul(&res[i-1], &t) + res[i-1].Sub(&res[i-1], &one) + } + res[len(res)-1].Sub(&res[len(res)-1], &one) + + res = fr.BatchInvert(res) + + return res +} diff --git a/backend/plonk/bls12-377/setup.go b/backend/plonk/bls12-377/setup.go index 9416161b6d..3325b08c7e 100644 --- a/backend/plonk/bls12-377/setup.go +++ b/backend/plonk/bls12-377/setup.go @@ -26,38 +26,18 @@ import ( "github.com/consensys/gnark/backend/plonk/internal" "github.com/consensys/gnark/constraint" cs "github.com/consensys/gnark/constraint/bls12-377" + "github.com/consensys/gnark/logger" + "runtime" "sync" + "time" ) -// Trace stores a plonk trace as columns -type Trace struct { - - // Constants describing a plonk circuit. The first entries - // of LQk (whose index correspond to the public inputs) are set to 0, and are to be - // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 - // so the first nb_public_variables constraints look like this: - // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. - Ql, Qr, Qm, Qo, Qk *iop.Polynomial - Qcp []*iop.Polynomial - - // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. - // The set of interpolation is of size N, so to represent the permutation S we let S acts on the - // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). - // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are - // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . - S1, S2, S3 *iop.Polynomial - - // S full permutation, i -> S[i] - S []int64 -} - // VerifyingKey stores the data needed to verify a proof: // * The commitment scheme // * Commitments of ql prepended with as many ones as there are public inputs // * Commitments of qr, qm, qo, qk prepended with as many zeroes as there are public inputs // * Commitments to S1, S2, S3 type VerifyingKey struct { - // Size circuit Size uint64 SizeInv fr.Element @@ -81,6 +61,46 @@ type VerifyingKey struct { CommitmentConstraintIndexes []uint64 } +// Trace stores a plonk trace as columns +type Trace struct { + // Constants describing a plonk circuit. The first entries + // of LQk (whose index correspond to the public inputs) are set to 0, and are to be + // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 + // so the first nb_public_variables constraints look like this: + // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. + Ql, Qr, Qm, Qo, Qk *iop.Polynomial + Qcp []*iop.Polynomial + + // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. + // The set of interpolation is of size N, so to represent the permutation S we let S acts on the + // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). + // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are + // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . + S1, S2, S3 *iop.Polynomial + + // S full permutation, i -> S[i] + S []int64 +} + +// ExpandedTrace stores Ql, Qr, Qm, Qo, Qk, Qcp, S1, S2, S3 in LagrangeCoset form; +// the expanded trace is stored in a memory layout that is optimized for the prover +type ExpandedTrace struct { + Polynomials [][sizeExpandedTrace]fr.Element +} + +// Enum for the expanded trace indexes. +const ( + idx_QL int = iota + idx_QR + idx_QM + idx_QO + idx_S1 + idx_S2 + idx_S3 + idx_LONE + sizeExpandedTrace +) + // ProvingKey stores the data needed to generate a proof: // * the commitment scheme // * ql, prepended with as many ones as they are public inputs @@ -90,7 +110,6 @@ type VerifyingKey struct { // * sigma_1, sigma_2, sigma_3 in both basis // * the copy constraint permutation type ProvingKey struct { - // stores ql, qr, qm, qo, qk (-> to be completed by the prover) // and s1, s2, s3. They are set in canonical basis before generating the proof, they will be used // for computing the opening proofs (hence the canonical form). The canonical version @@ -98,28 +117,24 @@ type ProvingKey struct { // The polynomials in trace are in canonical basis. trace Trace + // perf: this is quite fat; so we don't serialize it by default. + expandedTrace *ExpandedTrace + Kzg kzg.ProvingKey // Verifying Key is embedded into the proving key (needed by Prove) Vk *VerifyingKey - // qr,ql,qm,qo,qcp in LagrangeCoset --> these are not serialized, but computed from Ql, Qr, Qm, Qo, Qcp once. - lcQl, lcQr, lcQm, lcQo *iop.Polynomial - lcQcp []*iop.Polynomial + // qcp in LagrangeCoset form; not serialized + lcQcp []*iop.Polynomial // LQk qk in Lagrange form -> to be completed by the prover. After being completed, - lQk *iop.Polynomial + // lQk *iop.Polynomial // Domains used for the FFTs. // Domain[0] = small Domain // Domain[1] = big Domain Domain [2]fft.Domain - - // in lagrange coset basis --> these are not serialized, but computed from S1Canonical, S2Canonical, S3Canonical once. - lcS1, lcS2, lcS3 *iop.Polynomial - - // in lagrange coset basis --> not serialized id and L_{g^{0}} - lcIdIOP, lLoneIOP *iop.Polynomial } func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, error) { @@ -160,7 +175,6 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // All the above polynomials are expressed in canonical basis afterwards. This is why // we save lqk before, because the prover needs to complete it in Lagrange form, and // then express it on the Lagrange coset basis. - pk.lQk = pk.trace.Qk.Clone() // it will be completed by the prover, and the evaluated on the coset err := commitTrace(&pk.trace, &pk) if err != nil { return nil, nil, err @@ -177,65 +191,63 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // computeLagrangeCosetPolys computes each polynomial except qk in Lagrange coset // basis. Qk will be evaluated in Lagrange coset basis once it is completed by the prover. func (pk *ProvingKey) computeLagrangeCosetPolys() { - var wg sync.WaitGroup - wg.Add(7 + len(pk.trace.Qcp)) - n1 := int(pk.Domain[1].Cardinality) - pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) - for i, qcpI := range pk.trace.Qcp { - go func(i int, qcpI *iop.Polynomial) { - pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }(i, qcpI) + pk.expandedTrace = &ExpandedTrace{ + Polynomials: make([][sizeExpandedTrace]fr.Element, pk.Domain[1].Cardinality), } - go func() { - pk.lcQl = pk.trace.Ql.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQr = pk.trace.Qr.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQm = pk.trace.Qm.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQo = pk.trace.Qo.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS1 = pk.trace.S1.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS2 = pk.trace.S2.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + log := logger.Logger().With().Str("backend", "plonk").Logger() + start := time.Now() + + var wg sync.WaitGroup + wg.Add(8) + + // fillFunc converts p to LagrangeCoset and fills the pk.expandedTrace.Polynomials structure. + fillFunc := func(p *iop.Polynomial, idx int) { + scratch := make([]fr.Element, len(p.Coefficients()), pk.Domain[1].Cardinality) + copy(scratch, p.Coefficients()) + pp := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Canonical, Layout: iop.Regular}) + pp.SetSize(p.Size()) + pp.SetBlindedSize(p.BlindedSize()) + pp.ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx] = pp.GetCoeff(i) + } + wg.Done() - }() + } + + go fillFunc(pk.trace.Ql, idx_QL) + go fillFunc(pk.trace.Qr, idx_QR) + go fillFunc(pk.trace.Qm, idx_QM) + go fillFunc(pk.trace.Qo, idx_QO) + go fillFunc(pk.trace.S1, idx_S1) + go fillFunc(pk.trace.S2, idx_S2) + go fillFunc(pk.trace.S3, idx_S3) + go func() { - pk.lcS3 = pk.trace.S3.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + n1 := int(pk.Domain[1].Cardinality) + pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) + for i, qcpI := range pk.trace.Qcp { + pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]).ToRegular() + } wg.Done() }() - // storing Id - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - id := make([]fr.Element, pk.Domain[1].Cardinality) - id[0].Set(&pk.Domain[1].FrMultiplicativeGen) - for i := 1; i < int(pk.Domain[1].Cardinality); i++ { - id[i].Mul(&id[i-1], &pk.Domain[1].Generator) - } - pk.lcIdIOP = iop.NewPolynomial(&id, lagReg) // L_{g^{0}} - cap := pk.Domain[1].Cardinality - if cap < pk.Domain[0].Cardinality { - cap = pk.Domain[0].Cardinality // sanity check + scratch := make([]fr.Element, pk.Domain[0].Cardinality, pk.Domain[1].Cardinality) + scratch[0].SetOne() + + p := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Lagrange, Layout: iop.Regular}) + p.ToCanonical(&pk.Domain[0]).ToRegular().ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx_LONE] = p.GetCoeff(i) } - lone := make([]fr.Element, pk.Domain[0].Cardinality, cap) - lone[0].SetOne() - pk.lLoneIOP = iop.NewPolynomial(&lone, lagReg).ToCanonical(&pk.Domain[0]). - ToRegular(). - ToLagrangeCoset(&pk.Domain[1]) wg.Wait() + runtime.GC() + + log.Debug().Dur("computeLagrangeCosetPolys", time.Since(start)).Msg("setup done") } // NbPublicWitness returns the expected public witness size (number of field elements) diff --git a/backend/plonk/bls12-377/verify.go b/backend/plonk/bls12-377/verify.go index c28a894d02..ad1c3c374f 100644 --- a/backend/plonk/bls12-377/verify.go +++ b/backend/plonk/bls12-377/verify.go @@ -51,7 +51,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *vk, publicWitness, proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", vk, publicWitness, proof.Bsb22Commitments); err != nil { return err } gamma, err := deriveRandomness(&fs, "gamma", &proof.LRO[0], &proof.LRO[1], &proof.LRO[2]) @@ -270,7 +270,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { return err } -func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { +func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk *VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { // permutation if err := fs.Bind(challenge, vk.S[0].Marshal()); err != nil { diff --git a/backend/plonk/bls12-381/marshal.go b/backend/plonk/bls12-381/marshal.go index cd0b5204a5..432093de93 100644 --- a/backend/plonk/bls12-381/marshal.go +++ b/backend/plonk/bls12-381/marshal.go @@ -156,7 +156,6 @@ func (pk *ProvingKey) writeTo(w io.Writer, withCompression bool) (n int64, err e pk.trace.Qo.Coefficients(), pk.trace.Qk.Coefficients(), coefficients(pk.trace.Qcp), - pk.lQk.Coefficients(), pk.trace.S1.Coefficients(), pk.trace.S2.Coefficients(), pk.trace.S3.Coefficients(), @@ -215,7 +214,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err dec := curve.NewDecoder(r) - var ql, qr, qm, qo, qk, lqk, s1, s2, s3 []fr.Element + var ql, qr, qm, qo, qk, s1, s2, s3 []fr.Element var qcp [][]fr.Element // TODO @gbotrel: this is a bit ugly, we should probably refactor this. @@ -231,16 +230,15 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err chErr chan error } - vectors := make([]v, 9) + vectors := make([]v, 8) vectors[0] = v{data: (*fr.Vector)(&ql)} vectors[1] = v{data: (*fr.Vector)(&qr)} vectors[2] = v{data: (*fr.Vector)(&qm)} vectors[3] = v{data: (*fr.Vector)(&qo)} vectors[4] = v{data: (*fr.Vector)(&qk)} - vectors[5] = v{data: (*fr.Vector)(&lqk)} - vectors[6] = v{data: (*fr.Vector)(&s1)} - vectors[7] = v{data: (*fr.Vector)(&s2)} - vectors[8] = v{data: (*fr.Vector)(&s3)} + vectors[5] = v{data: (*fr.Vector)(&s1)} + vectors[6] = v{data: (*fr.Vector)(&s2)} + vectors[7] = v{data: (*fr.Vector)(&s3)} // read ql, qr, qm, qo, qk for i := 0; i < 5; i++ { @@ -258,7 +256,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err } // read lqk, s1, s2, s3 - for i := 5; i < 9; i++ { + for i := 5; i < 8; i++ { n2, err, ch := vectors[i].data.AsyncReadFrom(r) n += n2 if err != nil { @@ -293,8 +291,6 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err for i := range qcp { pk.trace.Qcp[i] = iop.NewPolynomial(&qcp[i], canReg) } - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) // wait for FFT to be precomputed <-chDomain0 diff --git a/backend/plonk/bls12-381/marshal_test.go b/backend/plonk/bls12-381/marshal_test.go index 372e8aacc1..5fbe7685bf 100644 --- a/backend/plonk/bls12-381/marshal_test.go +++ b/backend/plonk/bls12-381/marshal_test.go @@ -166,7 +166,6 @@ func (pk *ProvingKey) randomize() { qm := randomScalars(n) qo := randomScalars(n) qk := randomScalars(n) - lqk := randomScalars(n) s1 := randomScalars(n) s2 := randomScalars(n) s3 := randomScalars(n) @@ -191,9 +190,6 @@ func (pk *ProvingKey) randomize() { pk.trace.S[0] = -12 pk.trace.S[len(pk.trace.S)-1] = 8888 - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) - pk.computeLagrangeCosetPolys() } diff --git a/backend/plonk/bls12-381/prove.go b/backend/plonk/bls12-381/prove.go index 17627cd176..3c049c0ce3 100644 --- a/backend/plonk/bls12-381/prove.go +++ b/backend/plonk/bls12-381/prove.go @@ -18,6 +18,7 @@ package plonk import ( "crypto/sha256" + "errors" "math/big" "runtime" "sync" @@ -100,15 +101,19 @@ func bsb22ComputeCommitmentHint(spr *cs.SparseR1CS, pk *ProvingKey, proof *Proof } func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*Proof, error) { + log := logger.Logger().With(). + Str("curve", spr.CurveID().String()). + Int("nbConstraints", spr.GetNbConstraints()). + Str("backend", "plonk").Logger() - log := logger.Logger().With().Str("curve", spr.CurveID().String()).Int("nbConstraints", spr.GetNbConstraints()).Str("backend", "plonk").Logger() - + // parse the options opt, err := backend.NewProverConfig(opts...) if err != nil { return nil, err } start := time.Now() + // pick a hash function that will be used to derive the challenges hFunc := sha256.New() @@ -122,11 +127,14 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts commitmentVal := make([]fr.Element, len(commitmentInfo)) // TODO @Tabaie get rid of this cCommitments := make([]*iop.Polynomial, len(commitmentInfo)) proof.Bsb22Commitments = make([]kzg.Digest, len(commitmentInfo)) + + // override the hint for the commitment constraints for i := range commitmentInfo { opt.SolverOpts = append(opt.SolverOpts, solver.OverrideHint(commitmentInfo[i].HintID, bsb22ComputeCommitmentHint(spr, pk, proof, cCommitments, &commitmentVal[i], i))) } + // override the hint for GKR constraints if spr.GkrInfo.Is() { var gkrData cs.GkrSolvingData opt.SolverOpts = append(opt.SolverOpts, @@ -135,47 +143,43 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // query l, r, o in Lagrange basis, not blinded + lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} _solution, err := spr.Solve(fullWitness, opt.SolverOpts...) if err != nil { return nil, err } - // TODO @gbotrel deal with that conversion lazily - lcCommitments := make([]*iop.Polynomial, len(cCommitments)) - for i := range cCommitments { - lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]) // lagrange coset form - } solution := _solution.(*cs.SparseR1CSSolution) evaluationLDomainSmall := []fr.Element(solution.L) evaluationRDomainSmall := []fr.Element(solution.R) evaluationODomainSmall := []fr.Element(solution.O) + wliop := iop.NewPolynomial(&evaluationLDomainSmall, lagReg) + wriop := iop.NewPolynomial(&evaluationRDomainSmall, lagReg) + woiop := iop.NewPolynomial(&evaluationODomainSmall, lagReg) + + // convert the commitment polynomials to LagrangeCoset basis + lcCommitments := make([]*iop.Polynomial, len(cCommitments)) + chLcc := make(chan struct{}, 1) + go func() { + for i := range cCommitments { + lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]).ToRegular() // lagrange coset form + } + close(chLcc) + }() - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} // l, r, o and blinded versions - var ( - wliop, - wriop, - woiop, - bwliop, - bwriop, - bwoiop *iop.Polynomial - ) + var bwliop, bwriop, bwoiop *iop.Polynomial var wgLRO sync.WaitGroup wgLRO.Add(3) go func() { - // we keep in lagrange regular form since iop.BuildRatioCopyConstraint prefers it in this form. - wliop = iop.NewPolynomial(&evaluationLDomainSmall, lagReg) - // we set the underlying slice capacity to domain[1].Cardinality to minimize mem moves. - bwliop = wliop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwliop = wliop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - wriop = iop.NewPolynomial(&evaluationRDomainSmall, lagReg) - bwriop = wriop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwriop = wriop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - woiop = iop.NewPolynomial(&evaluationODomainSmall, lagReg) - bwoiop = woiop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwoiop = woiop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() @@ -189,27 +193,22 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chLcqk := make(chan struct{}, 1) go func() { // compute qk in canonical basis, completed with the public inputs - // We copy the coeffs of qk to pk is not mutated - lqkcoef := pk.lQk.Coefficients() - qkCompletedCanonical := make([]fr.Element, len(lqkcoef)) + // TODO @ThomasPiellard should we do ToLagrange or ToLagrangeCoset here? + lcqk = pk.trace.Qk.Clone(int(pk.Domain[1].Cardinality)).ToLagrange(&pk.Domain[0]).ToRegular() + qkCompletedCanonical := lcqk.Coefficients() copy(qkCompletedCanonical, fw[:len(spr.Public)]) - copy(qkCompletedCanonical[len(spr.Public):], lqkcoef[len(spr.Public):]) for i := range commitmentInfo { qkCompletedCanonical[spr.GetNbPublicVariables()+commitmentInfo[i].CommitmentIndex] = commitmentVal[i] } - pk.Domain[0].FFTInverse(qkCompletedCanonical, fft.DIF) - fft.BitReverse(qkCompletedCanonical) - - canReg := iop.Form{Basis: iop.Canonical, Layout: iop.Regular} - lcqk = iop.NewPolynomial(&qkCompletedCanonical, canReg) - lcqk.ToLagrangeCoset(&pk.Domain[1]) + lcqk.ToCanonical(&pk.Domain[0]).ToRegular(). + ToLagrangeCoset(&pk.Domain[1]).ToRegular() close(chLcqk) }() // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { return nil, err } @@ -234,22 +233,30 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts // l, r, o are already blinded wgLRO.Add(3) + + // compute l,r,o (blinded version) in LagrangeCoset basis. + // we keep the regular version in memory for the next step + var lcbwliop, lcbwriop, lcbwoiop *iop.Polynomial + go func() { - bwliop.ToLagrangeCoset(&pk.Domain[1]) + lcbwliop = bwliop.Clone(int(pk.Domain[1].Cardinality)) + lcbwliop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwriop.ToLagrangeCoset(&pk.Domain[1]) + lcbwriop = bwriop.Clone(int(pk.Domain[1].Cardinality)) + lcbwriop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwoiop.ToLagrangeCoset(&pk.Domain[1]) + lcbwoiop = bwoiop.Clone(int(pk.Domain[1].Cardinality)) + lcbwoiop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() // compute the copy constraint's ratio // note that wliop, wriop and woiop are fft'ed (mutated) in the process. - ziop, err := iop.BuildRatioCopyConstraint( + bwziop, err := iop.BuildRatioCopyConstraint( []*iop.Polynomial{ wliop, wriop, @@ -264,13 +271,18 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts if err != nil { return proof, err } + // unused. + wliop = nil + wriop = nil + woiop = nil // commit to the blinded version of z chZ := make(chan error, 1) - var bwziop, bwsziop *iop.Polynomial + var bwsziop *iop.Polynomial var alpha fr.Element go func() { - bwziop = ziop // iop.NewWrappedPolynomial(&ziop) + // blind Z + // TODO @gbotrel memory wise we should allocate a bigger result for BuildRatioCopyConstraint bwziop.Blind(2) proof.Z, err = kzg.Commit(bwziop.Coefficients(), pk.Kzg, runtime.NumCPU()*2) if err != nil { @@ -283,37 +295,50 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chZ <- err } - // Store z(g*x), without reallocating a slice - bwsziop = bwziop.ShallowClone().Shift(1) - bwsziop.ToLagrangeCoset(&pk.Domain[1]) + // Store z(g*x) + // perf note: converting ToRegular here perfoms better on Apple M1, but not on a hpc machine. + bwsziop = bwziop.Clone().ToLagrangeCoset(&pk.Domain[1]) //.ToRegular() + chZ <- nil close(chZ) }() + // l , r , o, z, zs, qk , Bsb22Commitments, qCPrime + const ( + idx_L int = iota // bwliop + idx_R // bwriop + idx_O // bwoiop + idx_Z // bwsziop + idx_ZS // bwsziop shifted + idx_QK // lcqk + idx_Bsb22Commitments // lcCommitments ... pk.lcQcp... + ) + // Full capture using latest gnark crypto... - fic := func(fql, fqr, fqm, fqo, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { // TODO @Tabaie make use of the fact that qCPrime is a selector: sparse and binary + fic := func(i int, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { var ic, tmp fr.Element - ic.Mul(&fql, &l) - tmp.Mul(&fqr, &r) + ic.Mul(&pk.expandedTrace.Polynomials[i][idx_QL], &l) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QR], &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqm, &l).Mul(&tmp, &r) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QM], &l).Mul(&tmp, &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqo, &o) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QO], &o) ic.Add(&ic, &tmp).Add(&ic, &fqk) nbComms := len(commitmentInfo) - for i := range commitmentInfo { - tmp.Mul(&pi2QcPrime[i], &pi2QcPrime[i+nbComms]) + for j := range commitmentInfo { + tmp.Mul(&pi2QcPrime[j], &pi2QcPrime[j+nbComms]) ic.Add(&ic, &tmp) } return ic } - fo := func(l, r, o, fid, fs1, fs2, fs3, fz, fzs fr.Element) fr.Element { + fo := func(i int, fid, l, r, o, fz, fzs fr.Element) fr.Element { u := &pk.Domain[0].FrMultiplicativeGen var a, b, tmp fr.Element + b.Mul(&beta, &fid) a.Add(&b, &l).Add(&a, &gamma) b.Mul(&b, u) @@ -322,10 +347,10 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts tmp.Mul(&b, u).Add(&tmp, &o).Add(&tmp, &gamma) a.Mul(&a, &tmp).Mul(&a, &fz) - b.Mul(&beta, &fs1).Add(&b, &l).Add(&b, &gamma) - tmp.Mul(&beta, &fs2).Add(&tmp, &r).Add(&tmp, &gamma) + b.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S1]).Add(&b, &l).Add(&b, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S2]).Add(&tmp, &r).Add(&tmp, &gamma) b.Mul(&b, &tmp) - tmp.Mul(&beta, &fs3).Add(&tmp, &o).Add(&tmp, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S3]).Add(&tmp, &o).Add(&tmp, &gamma) b.Mul(&b, &tmp).Mul(&b, &fzs) b.Sub(&b, &a) @@ -333,19 +358,16 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts return b } - fone := func(fz, flone fr.Element) fr.Element { + fone := func(i int, fz fr.Element) fr.Element { one := fr.One() - one.Sub(&fz, &one).Mul(&one, &flone) + one.Sub(&fz, &one).Mul(&one, &pk.expandedTrace.Polynomials[i][idx_LONE]) return one } - // 0 , 1 , 2, 3 , 4 , 5 , 6 , 7, 8 , 9 , 10, 11, 12, 13, 14, 15:15+nbComm , 15+nbComm:15+2×nbComm - // l , r , o, id, s1, s2, s3, z, zs, ql, qr, qm, qo, qk ,lone, Bsb22Commitments, qCPrime - fm := func(x ...fr.Element) fr.Element { - - a := fic(x[9], x[10], x[11], x[12], x[13], x[0], x[1], x[2], x[15:]) - b := fo(x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8]) - c := fone(x[7], x[14]) + fm := func(i int, fid fr.Element, x ...fr.Element) fr.Element { + a := fic(i, x[idx_QK], x[idx_L], x[idx_R], x[idx_O], x[idx_Bsb22Commitments:]) + b := fo(i, fid, x[idx_L], x[idx_R], x[idx_O], x[idx_Z], x[idx_ZS]) + c := fone(i, x[idx_Z]) c.Mul(&c, &alpha).Add(&c, &b).Mul(&c, &alpha).Add(&c, &a) @@ -361,39 +383,36 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // wait for l, r o lagrange coset conversion + <-chLcc wgLRO.Wait() - - toEval := []*iop.Polynomial{ - bwliop, - bwriop, - bwoiop, - pk.lcIdIOP, - pk.lcS1, - pk.lcS2, - pk.lcS3, - bwziop, - bwsziop, - pk.lcQl, - pk.lcQr, - pk.lcQm, - pk.lcQo, - lcqk, - pk.lLoneIOP, - } - toEval = append(toEval, lcCommitments...) // TODO: Add this at beginning - toEval = append(toEval, pk.lcQcp...) - systemEvaluation, err := iop.Evaluate(fm, iop.Form{Basis: iop.LagrangeCoset, Layout: iop.BitReverse}, toEval...) + toEval := make([]*iop.Polynomial, 6+2*len(commitmentInfo)) + toEval[idx_L] = lcbwliop + toEval[idx_R] = lcbwriop + toEval[idx_O] = lcbwoiop + toEval[idx_Z] = bwsziop.ShallowClone() + toEval[idx_ZS] = bwsziop.Shift(1) + toEval[idx_QK] = lcqk + copy(toEval[idx_Bsb22Commitments:], lcCommitments) + copy(toEval[idx_Bsb22Commitments+len(lcCommitments):], pk.lcQcp) + + // systemEvaluation reuses lcqk for memory. + systemEvaluation, err := evaluate(lcqk, pk, fm, toEval...) if err != nil { return nil, err } - // open blinded Z at zeta*z - chbwzIOP := make(chan struct{}, 1) - go func() { - bwziop.ToCanonical(&pk.Domain[1]).ToRegular() - close(chbwzIOP) - }() - h, err := iop.DivideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc + toEval = nil + bwsziop = nil + lcqk = nil + lcbwliop = nil + lcbwriop = nil + lcbwoiop = nil + + for i := range lcCommitments { + lcCommitments[i] = nil + } + + h, err := divideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc if err != nil { return nil, err } @@ -420,7 +439,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var wgEvals sync.WaitGroup wgEvals.Add(3) evalAtZeta := func(poly *iop.Polynomial, res *fr.Element) { - poly.ToCanonical(&pk.Domain[1]).ToRegular() *res = poly.Evaluate(zeta) wgEvals.Done() } @@ -436,7 +454,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var zetaShifted fr.Element zetaShifted.Mul(&zeta, &pk.Vk.Generator) - <-chbwzIOP proof.ZShiftedOpening, err = kzg.Open( bwziop.Coefficients()[:bwziop.BlindedSize()], zetaShifted, @@ -468,11 +485,12 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts h2 := h.Coefficients()[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] h1 := h.Coefficients()[:pk.Domain[0].Cardinality+2] utils.Parallelize(len(foldedH), func(start, end int) { + var t fr.Element for i := start; i < end; i++ { - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 - foldedH[i].Add(&foldedH[i], &h2[i]) // ζ^{m+2)*h3+h2 - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² - foldedH[i].Add(&foldedH[i], &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 + t.Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 + t.Add(&t, &h2[i]) // ζ^{m+2)*h3+h2 + t.Mul(&t, &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² + foldedH[i].Add(&t, &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 } }) close(computeFoldedH) @@ -679,8 +697,15 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, Mul(&lagrangeZeta, &pk.Domain[0].CardinalityInv) // (1/n)*α²*L₁(ζ) s3canonical := pk.trace.S3.Coefficients() + utils.Parallelize(len(blindedZCanonical), func(start, end int) { + cql := pk.trace.Ql.Coefficients() + cqr := pk.trace.Qr.Coefficients() + cqm := pk.trace.Qm.Coefficients() + cqo := pk.trace.Qo.Coefficients() + cqk := pk.trace.Qk.Coefficients() + var t, t0, t1 fr.Element for i := start; i < end; i++ { @@ -696,22 +721,21 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, t.Mul(&t, &alpha) // α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*Z(μζ)*s3(X) - Z(X)*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ)) - cql := pk.trace.Ql.Coefficients() - cqr := pk.trace.Qr.Coefficients() - cqm := pk.trace.Qm.Coefficients() - cqo := pk.trace.Qo.Coefficients() - cqk := pk.trace.Qk.Coefficients() if i < len(cqm) { t1.Mul(&cqm[i], &rl) // linPol = linPol + l(ζ)r(ζ)*Qm(X) + t0.Mul(&cql[i], &lZeta) t0.Add(&t0, &t1) + t.Add(&t, &t0) // linPol = linPol + l(ζ)*Ql(X) t0.Mul(&cqr[i], &rZeta) t.Add(&t, &t0) // linPol = linPol + r(ζ)*Qr(X) - t0.Mul(&cqo[i], &oZeta).Add(&t0, &cqk[i]) + t0.Mul(&cqo[i], &oZeta) + t0.Add(&t0, &cqk[i]) + t.Add(&t, &t0) // linPol = linPol + o(ζ)*Qo(X) + Qk(X) for j := range qcpZeta { @@ -726,3 +750,118 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, }) return blindedZCanonical } + +// TODO @gbotrel these changes below should be merge upstream in gnark-crypto + +// expression represents a multivariate polynomial. +type expression func(i int, fid fr.Element, x ...fr.Element) fr.Element + +// Evaluate evaluates f on each entry of x. The returned value is +// the vector of evaluations of e on x. +// The form of the result is LagrangeCoset Regular. +// The Size field of the result is the same as the one of x[0]. +// The blindedSize field of the result is the same as Size. +// The Shift field of the result is 0. +// The result re-uses result memory space. +func evaluate(result *iop.Polynomial, pk *ProvingKey, f expression, x ...*iop.Polynomial) (*iop.Polynomial, error) { + if len(x) == 0 { + return nil, errors.New("need at lest one input") + } + + // check that the sizes are consistent + n := len(x[0].Coefficients()) + m := len(x) + for i := 1; i < m; i++ { + if n != len(x[i].Coefficients()) { + return nil, errors.New("inconsistent sizes") + } + } + + // result coefficients + r := result.Coefficients() + if len(r) != n { + return nil, errors.New("inconsistent sizes") + } + + utils.Parallelize(n, func(start, end int) { + // inputs to the expression we will evaluate + vx := make([]fr.Element, m) + generator := pk.Domain[1].Generator + + // we inject id polynomial + var fid fr.Element + fid.Exp(generator, big.NewInt(int64(start))) + fid.Mul(&fid, &pk.Domain[1].FrMultiplicativeGen) + + for i := start; i < end; i++ { + for j := 0; j < m; j++ { + vx[j] = x[j].GetCoeff(i) + } + r[i] = f(i, fid, vx...) + fid.Mul(&fid, &generator) + } + }) + + res := iop.NewPolynomial(&r, iop.Form{Layout: iop.Regular, Basis: iop.LagrangeCoset}) + res.SetSize(x[0].Size()) + res.SetBlindedSize(x[0].Size()) + + return res, nil +} + +// divideByXMinusOne +// 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) { + + // check that the basis is LagrangeCoset + if a.Basis != iop.LagrangeCoset { + return nil, errors.New("invalid form") + } + + // prepare the evaluations of x^n-1 on the big domain's coset + xnMinusOneInverseLagrangeCoset := evaluateXnMinusOneDomainBigCoset(domains) + + nbElmts := len(a.Coefficients()) + rho := nbElmts / a.Size() + + r := a.Coefficients() + + utils.Parallelize(nbElmts, func(start, end int) { + for i := start; i < end; i++ { + r[i].Mul(&r[i], &xnMinusOneInverseLagrangeCoset[i%rho]) + } + }) + + // TODO @gbotrel this is the only place we do a FFT inverse (on coset) with domain[1] + a.ToCanonical(domains[1]).ToRegular() + + return a, nil + +} + +// evaluateXnMinusOneDomainBigCoset evaluates Xᵐ-1 on DomainBig coset +func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { + + ratio := domains[1].Cardinality / domains[0].Cardinality + + res := make([]fr.Element, ratio) + + expo := big.NewInt(int64(domains[0].Cardinality)) + res[0].Exp(domains[1].FrMultiplicativeGen, expo) + + var t fr.Element + t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + + one := fr.One() + + for i := 1; i < int(ratio); i++ { + res[i].Mul(&res[i-1], &t) + res[i-1].Sub(&res[i-1], &one) + } + res[len(res)-1].Sub(&res[len(res)-1], &one) + + res = fr.BatchInvert(res) + + return res +} diff --git a/backend/plonk/bls12-381/setup.go b/backend/plonk/bls12-381/setup.go index 46b2820ce2..2c75063773 100644 --- a/backend/plonk/bls12-381/setup.go +++ b/backend/plonk/bls12-381/setup.go @@ -26,38 +26,18 @@ import ( "github.com/consensys/gnark/backend/plonk/internal" "github.com/consensys/gnark/constraint" cs "github.com/consensys/gnark/constraint/bls12-381" + "github.com/consensys/gnark/logger" + "runtime" "sync" + "time" ) -// Trace stores a plonk trace as columns -type Trace struct { - - // Constants describing a plonk circuit. The first entries - // of LQk (whose index correspond to the public inputs) are set to 0, and are to be - // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 - // so the first nb_public_variables constraints look like this: - // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. - Ql, Qr, Qm, Qo, Qk *iop.Polynomial - Qcp []*iop.Polynomial - - // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. - // The set of interpolation is of size N, so to represent the permutation S we let S acts on the - // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). - // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are - // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . - S1, S2, S3 *iop.Polynomial - - // S full permutation, i -> S[i] - S []int64 -} - // VerifyingKey stores the data needed to verify a proof: // * The commitment scheme // * Commitments of ql prepended with as many ones as there are public inputs // * Commitments of qr, qm, qo, qk prepended with as many zeroes as there are public inputs // * Commitments to S1, S2, S3 type VerifyingKey struct { - // Size circuit Size uint64 SizeInv fr.Element @@ -81,6 +61,46 @@ type VerifyingKey struct { CommitmentConstraintIndexes []uint64 } +// Trace stores a plonk trace as columns +type Trace struct { + // Constants describing a plonk circuit. The first entries + // of LQk (whose index correspond to the public inputs) are set to 0, and are to be + // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 + // so the first nb_public_variables constraints look like this: + // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. + Ql, Qr, Qm, Qo, Qk *iop.Polynomial + Qcp []*iop.Polynomial + + // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. + // The set of interpolation is of size N, so to represent the permutation S we let S acts on the + // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). + // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are + // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . + S1, S2, S3 *iop.Polynomial + + // S full permutation, i -> S[i] + S []int64 +} + +// ExpandedTrace stores Ql, Qr, Qm, Qo, Qk, Qcp, S1, S2, S3 in LagrangeCoset form; +// the expanded trace is stored in a memory layout that is optimized for the prover +type ExpandedTrace struct { + Polynomials [][sizeExpandedTrace]fr.Element +} + +// Enum for the expanded trace indexes. +const ( + idx_QL int = iota + idx_QR + idx_QM + idx_QO + idx_S1 + idx_S2 + idx_S3 + idx_LONE + sizeExpandedTrace +) + // ProvingKey stores the data needed to generate a proof: // * the commitment scheme // * ql, prepended with as many ones as they are public inputs @@ -90,7 +110,6 @@ type VerifyingKey struct { // * sigma_1, sigma_2, sigma_3 in both basis // * the copy constraint permutation type ProvingKey struct { - // stores ql, qr, qm, qo, qk (-> to be completed by the prover) // and s1, s2, s3. They are set in canonical basis before generating the proof, they will be used // for computing the opening proofs (hence the canonical form). The canonical version @@ -98,28 +117,24 @@ type ProvingKey struct { // The polynomials in trace are in canonical basis. trace Trace + // perf: this is quite fat; so we don't serialize it by default. + expandedTrace *ExpandedTrace + Kzg kzg.ProvingKey // Verifying Key is embedded into the proving key (needed by Prove) Vk *VerifyingKey - // qr,ql,qm,qo,qcp in LagrangeCoset --> these are not serialized, but computed from Ql, Qr, Qm, Qo, Qcp once. - lcQl, lcQr, lcQm, lcQo *iop.Polynomial - lcQcp []*iop.Polynomial + // qcp in LagrangeCoset form; not serialized + lcQcp []*iop.Polynomial // LQk qk in Lagrange form -> to be completed by the prover. After being completed, - lQk *iop.Polynomial + // lQk *iop.Polynomial // Domains used for the FFTs. // Domain[0] = small Domain // Domain[1] = big Domain Domain [2]fft.Domain - - // in lagrange coset basis --> these are not serialized, but computed from S1Canonical, S2Canonical, S3Canonical once. - lcS1, lcS2, lcS3 *iop.Polynomial - - // in lagrange coset basis --> not serialized id and L_{g^{0}} - lcIdIOP, lLoneIOP *iop.Polynomial } func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, error) { @@ -160,7 +175,6 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // All the above polynomials are expressed in canonical basis afterwards. This is why // we save lqk before, because the prover needs to complete it in Lagrange form, and // then express it on the Lagrange coset basis. - pk.lQk = pk.trace.Qk.Clone() // it will be completed by the prover, and the evaluated on the coset err := commitTrace(&pk.trace, &pk) if err != nil { return nil, nil, err @@ -177,65 +191,63 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // computeLagrangeCosetPolys computes each polynomial except qk in Lagrange coset // basis. Qk will be evaluated in Lagrange coset basis once it is completed by the prover. func (pk *ProvingKey) computeLagrangeCosetPolys() { - var wg sync.WaitGroup - wg.Add(7 + len(pk.trace.Qcp)) - n1 := int(pk.Domain[1].Cardinality) - pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) - for i, qcpI := range pk.trace.Qcp { - go func(i int, qcpI *iop.Polynomial) { - pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }(i, qcpI) + pk.expandedTrace = &ExpandedTrace{ + Polynomials: make([][sizeExpandedTrace]fr.Element, pk.Domain[1].Cardinality), } - go func() { - pk.lcQl = pk.trace.Ql.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQr = pk.trace.Qr.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQm = pk.trace.Qm.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQo = pk.trace.Qo.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS1 = pk.trace.S1.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS2 = pk.trace.S2.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + log := logger.Logger().With().Str("backend", "plonk").Logger() + start := time.Now() + + var wg sync.WaitGroup + wg.Add(8) + + // fillFunc converts p to LagrangeCoset and fills the pk.expandedTrace.Polynomials structure. + fillFunc := func(p *iop.Polynomial, idx int) { + scratch := make([]fr.Element, len(p.Coefficients()), pk.Domain[1].Cardinality) + copy(scratch, p.Coefficients()) + pp := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Canonical, Layout: iop.Regular}) + pp.SetSize(p.Size()) + pp.SetBlindedSize(p.BlindedSize()) + pp.ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx] = pp.GetCoeff(i) + } + wg.Done() - }() + } + + go fillFunc(pk.trace.Ql, idx_QL) + go fillFunc(pk.trace.Qr, idx_QR) + go fillFunc(pk.trace.Qm, idx_QM) + go fillFunc(pk.trace.Qo, idx_QO) + go fillFunc(pk.trace.S1, idx_S1) + go fillFunc(pk.trace.S2, idx_S2) + go fillFunc(pk.trace.S3, idx_S3) + go func() { - pk.lcS3 = pk.trace.S3.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + n1 := int(pk.Domain[1].Cardinality) + pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) + for i, qcpI := range pk.trace.Qcp { + pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]).ToRegular() + } wg.Done() }() - // storing Id - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - id := make([]fr.Element, pk.Domain[1].Cardinality) - id[0].Set(&pk.Domain[1].FrMultiplicativeGen) - for i := 1; i < int(pk.Domain[1].Cardinality); i++ { - id[i].Mul(&id[i-1], &pk.Domain[1].Generator) - } - pk.lcIdIOP = iop.NewPolynomial(&id, lagReg) // L_{g^{0}} - cap := pk.Domain[1].Cardinality - if cap < pk.Domain[0].Cardinality { - cap = pk.Domain[0].Cardinality // sanity check + scratch := make([]fr.Element, pk.Domain[0].Cardinality, pk.Domain[1].Cardinality) + scratch[0].SetOne() + + p := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Lagrange, Layout: iop.Regular}) + p.ToCanonical(&pk.Domain[0]).ToRegular().ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx_LONE] = p.GetCoeff(i) } - lone := make([]fr.Element, pk.Domain[0].Cardinality, cap) - lone[0].SetOne() - pk.lLoneIOP = iop.NewPolynomial(&lone, lagReg).ToCanonical(&pk.Domain[0]). - ToRegular(). - ToLagrangeCoset(&pk.Domain[1]) wg.Wait() + runtime.GC() + + log.Debug().Dur("computeLagrangeCosetPolys", time.Since(start)).Msg("setup done") } // NbPublicWitness returns the expected public witness size (number of field elements) diff --git a/backend/plonk/bls12-381/verify.go b/backend/plonk/bls12-381/verify.go index 3124e92c5e..681612913c 100644 --- a/backend/plonk/bls12-381/verify.go +++ b/backend/plonk/bls12-381/verify.go @@ -51,7 +51,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *vk, publicWitness, proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", vk, publicWitness, proof.Bsb22Commitments); err != nil { return err } gamma, err := deriveRandomness(&fs, "gamma", &proof.LRO[0], &proof.LRO[1], &proof.LRO[2]) @@ -270,7 +270,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { return err } -func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { +func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk *VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { // permutation if err := fs.Bind(challenge, vk.S[0].Marshal()); err != nil { diff --git a/backend/plonk/bls24-315/marshal.go b/backend/plonk/bls24-315/marshal.go index ad1f16dcfe..5842bbfea6 100644 --- a/backend/plonk/bls24-315/marshal.go +++ b/backend/plonk/bls24-315/marshal.go @@ -156,7 +156,6 @@ func (pk *ProvingKey) writeTo(w io.Writer, withCompression bool) (n int64, err e pk.trace.Qo.Coefficients(), pk.trace.Qk.Coefficients(), coefficients(pk.trace.Qcp), - pk.lQk.Coefficients(), pk.trace.S1.Coefficients(), pk.trace.S2.Coefficients(), pk.trace.S3.Coefficients(), @@ -215,7 +214,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err dec := curve.NewDecoder(r) - var ql, qr, qm, qo, qk, lqk, s1, s2, s3 []fr.Element + var ql, qr, qm, qo, qk, s1, s2, s3 []fr.Element var qcp [][]fr.Element // TODO @gbotrel: this is a bit ugly, we should probably refactor this. @@ -231,16 +230,15 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err chErr chan error } - vectors := make([]v, 9) + vectors := make([]v, 8) vectors[0] = v{data: (*fr.Vector)(&ql)} vectors[1] = v{data: (*fr.Vector)(&qr)} vectors[2] = v{data: (*fr.Vector)(&qm)} vectors[3] = v{data: (*fr.Vector)(&qo)} vectors[4] = v{data: (*fr.Vector)(&qk)} - vectors[5] = v{data: (*fr.Vector)(&lqk)} - vectors[6] = v{data: (*fr.Vector)(&s1)} - vectors[7] = v{data: (*fr.Vector)(&s2)} - vectors[8] = v{data: (*fr.Vector)(&s3)} + vectors[5] = v{data: (*fr.Vector)(&s1)} + vectors[6] = v{data: (*fr.Vector)(&s2)} + vectors[7] = v{data: (*fr.Vector)(&s3)} // read ql, qr, qm, qo, qk for i := 0; i < 5; i++ { @@ -258,7 +256,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err } // read lqk, s1, s2, s3 - for i := 5; i < 9; i++ { + for i := 5; i < 8; i++ { n2, err, ch := vectors[i].data.AsyncReadFrom(r) n += n2 if err != nil { @@ -293,8 +291,6 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err for i := range qcp { pk.trace.Qcp[i] = iop.NewPolynomial(&qcp[i], canReg) } - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) // wait for FFT to be precomputed <-chDomain0 diff --git a/backend/plonk/bls24-315/marshal_test.go b/backend/plonk/bls24-315/marshal_test.go index 6eda2dd12e..60be0fbfb9 100644 --- a/backend/plonk/bls24-315/marshal_test.go +++ b/backend/plonk/bls24-315/marshal_test.go @@ -166,7 +166,6 @@ func (pk *ProvingKey) randomize() { qm := randomScalars(n) qo := randomScalars(n) qk := randomScalars(n) - lqk := randomScalars(n) s1 := randomScalars(n) s2 := randomScalars(n) s3 := randomScalars(n) @@ -191,9 +190,6 @@ func (pk *ProvingKey) randomize() { pk.trace.S[0] = -12 pk.trace.S[len(pk.trace.S)-1] = 8888 - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) - pk.computeLagrangeCosetPolys() } diff --git a/backend/plonk/bls24-315/prove.go b/backend/plonk/bls24-315/prove.go index 7ed6145ebb..260e6787af 100644 --- a/backend/plonk/bls24-315/prove.go +++ b/backend/plonk/bls24-315/prove.go @@ -18,6 +18,7 @@ package plonk import ( "crypto/sha256" + "errors" "math/big" "runtime" "sync" @@ -100,15 +101,19 @@ func bsb22ComputeCommitmentHint(spr *cs.SparseR1CS, pk *ProvingKey, proof *Proof } func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*Proof, error) { + log := logger.Logger().With(). + Str("curve", spr.CurveID().String()). + Int("nbConstraints", spr.GetNbConstraints()). + Str("backend", "plonk").Logger() - log := logger.Logger().With().Str("curve", spr.CurveID().String()).Int("nbConstraints", spr.GetNbConstraints()).Str("backend", "plonk").Logger() - + // parse the options opt, err := backend.NewProverConfig(opts...) if err != nil { return nil, err } start := time.Now() + // pick a hash function that will be used to derive the challenges hFunc := sha256.New() @@ -122,11 +127,14 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts commitmentVal := make([]fr.Element, len(commitmentInfo)) // TODO @Tabaie get rid of this cCommitments := make([]*iop.Polynomial, len(commitmentInfo)) proof.Bsb22Commitments = make([]kzg.Digest, len(commitmentInfo)) + + // override the hint for the commitment constraints for i := range commitmentInfo { opt.SolverOpts = append(opt.SolverOpts, solver.OverrideHint(commitmentInfo[i].HintID, bsb22ComputeCommitmentHint(spr, pk, proof, cCommitments, &commitmentVal[i], i))) } + // override the hint for GKR constraints if spr.GkrInfo.Is() { var gkrData cs.GkrSolvingData opt.SolverOpts = append(opt.SolverOpts, @@ -135,47 +143,43 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // query l, r, o in Lagrange basis, not blinded + lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} _solution, err := spr.Solve(fullWitness, opt.SolverOpts...) if err != nil { return nil, err } - // TODO @gbotrel deal with that conversion lazily - lcCommitments := make([]*iop.Polynomial, len(cCommitments)) - for i := range cCommitments { - lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]) // lagrange coset form - } solution := _solution.(*cs.SparseR1CSSolution) evaluationLDomainSmall := []fr.Element(solution.L) evaluationRDomainSmall := []fr.Element(solution.R) evaluationODomainSmall := []fr.Element(solution.O) + wliop := iop.NewPolynomial(&evaluationLDomainSmall, lagReg) + wriop := iop.NewPolynomial(&evaluationRDomainSmall, lagReg) + woiop := iop.NewPolynomial(&evaluationODomainSmall, lagReg) + + // convert the commitment polynomials to LagrangeCoset basis + lcCommitments := make([]*iop.Polynomial, len(cCommitments)) + chLcc := make(chan struct{}, 1) + go func() { + for i := range cCommitments { + lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]).ToRegular() // lagrange coset form + } + close(chLcc) + }() - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} // l, r, o and blinded versions - var ( - wliop, - wriop, - woiop, - bwliop, - bwriop, - bwoiop *iop.Polynomial - ) + var bwliop, bwriop, bwoiop *iop.Polynomial var wgLRO sync.WaitGroup wgLRO.Add(3) go func() { - // we keep in lagrange regular form since iop.BuildRatioCopyConstraint prefers it in this form. - wliop = iop.NewPolynomial(&evaluationLDomainSmall, lagReg) - // we set the underlying slice capacity to domain[1].Cardinality to minimize mem moves. - bwliop = wliop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwliop = wliop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - wriop = iop.NewPolynomial(&evaluationRDomainSmall, lagReg) - bwriop = wriop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwriop = wriop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - woiop = iop.NewPolynomial(&evaluationODomainSmall, lagReg) - bwoiop = woiop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwoiop = woiop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() @@ -189,27 +193,22 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chLcqk := make(chan struct{}, 1) go func() { // compute qk in canonical basis, completed with the public inputs - // We copy the coeffs of qk to pk is not mutated - lqkcoef := pk.lQk.Coefficients() - qkCompletedCanonical := make([]fr.Element, len(lqkcoef)) + // TODO @ThomasPiellard should we do ToLagrange or ToLagrangeCoset here? + lcqk = pk.trace.Qk.Clone(int(pk.Domain[1].Cardinality)).ToLagrange(&pk.Domain[0]).ToRegular() + qkCompletedCanonical := lcqk.Coefficients() copy(qkCompletedCanonical, fw[:len(spr.Public)]) - copy(qkCompletedCanonical[len(spr.Public):], lqkcoef[len(spr.Public):]) for i := range commitmentInfo { qkCompletedCanonical[spr.GetNbPublicVariables()+commitmentInfo[i].CommitmentIndex] = commitmentVal[i] } - pk.Domain[0].FFTInverse(qkCompletedCanonical, fft.DIF) - fft.BitReverse(qkCompletedCanonical) - - canReg := iop.Form{Basis: iop.Canonical, Layout: iop.Regular} - lcqk = iop.NewPolynomial(&qkCompletedCanonical, canReg) - lcqk.ToLagrangeCoset(&pk.Domain[1]) + lcqk.ToCanonical(&pk.Domain[0]).ToRegular(). + ToLagrangeCoset(&pk.Domain[1]).ToRegular() close(chLcqk) }() // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { return nil, err } @@ -234,22 +233,30 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts // l, r, o are already blinded wgLRO.Add(3) + + // compute l,r,o (blinded version) in LagrangeCoset basis. + // we keep the regular version in memory for the next step + var lcbwliop, lcbwriop, lcbwoiop *iop.Polynomial + go func() { - bwliop.ToLagrangeCoset(&pk.Domain[1]) + lcbwliop = bwliop.Clone(int(pk.Domain[1].Cardinality)) + lcbwliop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwriop.ToLagrangeCoset(&pk.Domain[1]) + lcbwriop = bwriop.Clone(int(pk.Domain[1].Cardinality)) + lcbwriop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwoiop.ToLagrangeCoset(&pk.Domain[1]) + lcbwoiop = bwoiop.Clone(int(pk.Domain[1].Cardinality)) + lcbwoiop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() // compute the copy constraint's ratio // note that wliop, wriop and woiop are fft'ed (mutated) in the process. - ziop, err := iop.BuildRatioCopyConstraint( + bwziop, err := iop.BuildRatioCopyConstraint( []*iop.Polynomial{ wliop, wriop, @@ -264,13 +271,18 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts if err != nil { return proof, err } + // unused. + wliop = nil + wriop = nil + woiop = nil // commit to the blinded version of z chZ := make(chan error, 1) - var bwziop, bwsziop *iop.Polynomial + var bwsziop *iop.Polynomial var alpha fr.Element go func() { - bwziop = ziop // iop.NewWrappedPolynomial(&ziop) + // blind Z + // TODO @gbotrel memory wise we should allocate a bigger result for BuildRatioCopyConstraint bwziop.Blind(2) proof.Z, err = kzg.Commit(bwziop.Coefficients(), pk.Kzg, runtime.NumCPU()*2) if err != nil { @@ -283,37 +295,50 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chZ <- err } - // Store z(g*x), without reallocating a slice - bwsziop = bwziop.ShallowClone().Shift(1) - bwsziop.ToLagrangeCoset(&pk.Domain[1]) + // Store z(g*x) + // perf note: converting ToRegular here perfoms better on Apple M1, but not on a hpc machine. + bwsziop = bwziop.Clone().ToLagrangeCoset(&pk.Domain[1]) //.ToRegular() + chZ <- nil close(chZ) }() + // l , r , o, z, zs, qk , Bsb22Commitments, qCPrime + const ( + idx_L int = iota // bwliop + idx_R // bwriop + idx_O // bwoiop + idx_Z // bwsziop + idx_ZS // bwsziop shifted + idx_QK // lcqk + idx_Bsb22Commitments // lcCommitments ... pk.lcQcp... + ) + // Full capture using latest gnark crypto... - fic := func(fql, fqr, fqm, fqo, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { // TODO @Tabaie make use of the fact that qCPrime is a selector: sparse and binary + fic := func(i int, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { var ic, tmp fr.Element - ic.Mul(&fql, &l) - tmp.Mul(&fqr, &r) + ic.Mul(&pk.expandedTrace.Polynomials[i][idx_QL], &l) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QR], &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqm, &l).Mul(&tmp, &r) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QM], &l).Mul(&tmp, &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqo, &o) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QO], &o) ic.Add(&ic, &tmp).Add(&ic, &fqk) nbComms := len(commitmentInfo) - for i := range commitmentInfo { - tmp.Mul(&pi2QcPrime[i], &pi2QcPrime[i+nbComms]) + for j := range commitmentInfo { + tmp.Mul(&pi2QcPrime[j], &pi2QcPrime[j+nbComms]) ic.Add(&ic, &tmp) } return ic } - fo := func(l, r, o, fid, fs1, fs2, fs3, fz, fzs fr.Element) fr.Element { + fo := func(i int, fid, l, r, o, fz, fzs fr.Element) fr.Element { u := &pk.Domain[0].FrMultiplicativeGen var a, b, tmp fr.Element + b.Mul(&beta, &fid) a.Add(&b, &l).Add(&a, &gamma) b.Mul(&b, u) @@ -322,10 +347,10 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts tmp.Mul(&b, u).Add(&tmp, &o).Add(&tmp, &gamma) a.Mul(&a, &tmp).Mul(&a, &fz) - b.Mul(&beta, &fs1).Add(&b, &l).Add(&b, &gamma) - tmp.Mul(&beta, &fs2).Add(&tmp, &r).Add(&tmp, &gamma) + b.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S1]).Add(&b, &l).Add(&b, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S2]).Add(&tmp, &r).Add(&tmp, &gamma) b.Mul(&b, &tmp) - tmp.Mul(&beta, &fs3).Add(&tmp, &o).Add(&tmp, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S3]).Add(&tmp, &o).Add(&tmp, &gamma) b.Mul(&b, &tmp).Mul(&b, &fzs) b.Sub(&b, &a) @@ -333,19 +358,16 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts return b } - fone := func(fz, flone fr.Element) fr.Element { + fone := func(i int, fz fr.Element) fr.Element { one := fr.One() - one.Sub(&fz, &one).Mul(&one, &flone) + one.Sub(&fz, &one).Mul(&one, &pk.expandedTrace.Polynomials[i][idx_LONE]) return one } - // 0 , 1 , 2, 3 , 4 , 5 , 6 , 7, 8 , 9 , 10, 11, 12, 13, 14, 15:15+nbComm , 15+nbComm:15+2×nbComm - // l , r , o, id, s1, s2, s3, z, zs, ql, qr, qm, qo, qk ,lone, Bsb22Commitments, qCPrime - fm := func(x ...fr.Element) fr.Element { - - a := fic(x[9], x[10], x[11], x[12], x[13], x[0], x[1], x[2], x[15:]) - b := fo(x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8]) - c := fone(x[7], x[14]) + fm := func(i int, fid fr.Element, x ...fr.Element) fr.Element { + a := fic(i, x[idx_QK], x[idx_L], x[idx_R], x[idx_O], x[idx_Bsb22Commitments:]) + b := fo(i, fid, x[idx_L], x[idx_R], x[idx_O], x[idx_Z], x[idx_ZS]) + c := fone(i, x[idx_Z]) c.Mul(&c, &alpha).Add(&c, &b).Mul(&c, &alpha).Add(&c, &a) @@ -361,39 +383,36 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // wait for l, r o lagrange coset conversion + <-chLcc wgLRO.Wait() - - toEval := []*iop.Polynomial{ - bwliop, - bwriop, - bwoiop, - pk.lcIdIOP, - pk.lcS1, - pk.lcS2, - pk.lcS3, - bwziop, - bwsziop, - pk.lcQl, - pk.lcQr, - pk.lcQm, - pk.lcQo, - lcqk, - pk.lLoneIOP, - } - toEval = append(toEval, lcCommitments...) // TODO: Add this at beginning - toEval = append(toEval, pk.lcQcp...) - systemEvaluation, err := iop.Evaluate(fm, iop.Form{Basis: iop.LagrangeCoset, Layout: iop.BitReverse}, toEval...) + toEval := make([]*iop.Polynomial, 6+2*len(commitmentInfo)) + toEval[idx_L] = lcbwliop + toEval[idx_R] = lcbwriop + toEval[idx_O] = lcbwoiop + toEval[idx_Z] = bwsziop.ShallowClone() + toEval[idx_ZS] = bwsziop.Shift(1) + toEval[idx_QK] = lcqk + copy(toEval[idx_Bsb22Commitments:], lcCommitments) + copy(toEval[idx_Bsb22Commitments+len(lcCommitments):], pk.lcQcp) + + // systemEvaluation reuses lcqk for memory. + systemEvaluation, err := evaluate(lcqk, pk, fm, toEval...) if err != nil { return nil, err } - // open blinded Z at zeta*z - chbwzIOP := make(chan struct{}, 1) - go func() { - bwziop.ToCanonical(&pk.Domain[1]).ToRegular() - close(chbwzIOP) - }() - h, err := iop.DivideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc + toEval = nil + bwsziop = nil + lcqk = nil + lcbwliop = nil + lcbwriop = nil + lcbwoiop = nil + + for i := range lcCommitments { + lcCommitments[i] = nil + } + + h, err := divideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc if err != nil { return nil, err } @@ -420,7 +439,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var wgEvals sync.WaitGroup wgEvals.Add(3) evalAtZeta := func(poly *iop.Polynomial, res *fr.Element) { - poly.ToCanonical(&pk.Domain[1]).ToRegular() *res = poly.Evaluate(zeta) wgEvals.Done() } @@ -436,7 +454,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var zetaShifted fr.Element zetaShifted.Mul(&zeta, &pk.Vk.Generator) - <-chbwzIOP proof.ZShiftedOpening, err = kzg.Open( bwziop.Coefficients()[:bwziop.BlindedSize()], zetaShifted, @@ -468,11 +485,12 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts h2 := h.Coefficients()[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] h1 := h.Coefficients()[:pk.Domain[0].Cardinality+2] utils.Parallelize(len(foldedH), func(start, end int) { + var t fr.Element for i := start; i < end; i++ { - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 - foldedH[i].Add(&foldedH[i], &h2[i]) // ζ^{m+2)*h3+h2 - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² - foldedH[i].Add(&foldedH[i], &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 + t.Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 + t.Add(&t, &h2[i]) // ζ^{m+2)*h3+h2 + t.Mul(&t, &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² + foldedH[i].Add(&t, &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 } }) close(computeFoldedH) @@ -679,8 +697,15 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, Mul(&lagrangeZeta, &pk.Domain[0].CardinalityInv) // (1/n)*α²*L₁(ζ) s3canonical := pk.trace.S3.Coefficients() + utils.Parallelize(len(blindedZCanonical), func(start, end int) { + cql := pk.trace.Ql.Coefficients() + cqr := pk.trace.Qr.Coefficients() + cqm := pk.trace.Qm.Coefficients() + cqo := pk.trace.Qo.Coefficients() + cqk := pk.trace.Qk.Coefficients() + var t, t0, t1 fr.Element for i := start; i < end; i++ { @@ -696,22 +721,21 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, t.Mul(&t, &alpha) // α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*Z(μζ)*s3(X) - Z(X)*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ)) - cql := pk.trace.Ql.Coefficients() - cqr := pk.trace.Qr.Coefficients() - cqm := pk.trace.Qm.Coefficients() - cqo := pk.trace.Qo.Coefficients() - cqk := pk.trace.Qk.Coefficients() if i < len(cqm) { t1.Mul(&cqm[i], &rl) // linPol = linPol + l(ζ)r(ζ)*Qm(X) + t0.Mul(&cql[i], &lZeta) t0.Add(&t0, &t1) + t.Add(&t, &t0) // linPol = linPol + l(ζ)*Ql(X) t0.Mul(&cqr[i], &rZeta) t.Add(&t, &t0) // linPol = linPol + r(ζ)*Qr(X) - t0.Mul(&cqo[i], &oZeta).Add(&t0, &cqk[i]) + t0.Mul(&cqo[i], &oZeta) + t0.Add(&t0, &cqk[i]) + t.Add(&t, &t0) // linPol = linPol + o(ζ)*Qo(X) + Qk(X) for j := range qcpZeta { @@ -726,3 +750,118 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, }) return blindedZCanonical } + +// TODO @gbotrel these changes below should be merge upstream in gnark-crypto + +// expression represents a multivariate polynomial. +type expression func(i int, fid fr.Element, x ...fr.Element) fr.Element + +// Evaluate evaluates f on each entry of x. The returned value is +// the vector of evaluations of e on x. +// The form of the result is LagrangeCoset Regular. +// The Size field of the result is the same as the one of x[0]. +// The blindedSize field of the result is the same as Size. +// The Shift field of the result is 0. +// The result re-uses result memory space. +func evaluate(result *iop.Polynomial, pk *ProvingKey, f expression, x ...*iop.Polynomial) (*iop.Polynomial, error) { + if len(x) == 0 { + return nil, errors.New("need at lest one input") + } + + // check that the sizes are consistent + n := len(x[0].Coefficients()) + m := len(x) + for i := 1; i < m; i++ { + if n != len(x[i].Coefficients()) { + return nil, errors.New("inconsistent sizes") + } + } + + // result coefficients + r := result.Coefficients() + if len(r) != n { + return nil, errors.New("inconsistent sizes") + } + + utils.Parallelize(n, func(start, end int) { + // inputs to the expression we will evaluate + vx := make([]fr.Element, m) + generator := pk.Domain[1].Generator + + // we inject id polynomial + var fid fr.Element + fid.Exp(generator, big.NewInt(int64(start))) + fid.Mul(&fid, &pk.Domain[1].FrMultiplicativeGen) + + for i := start; i < end; i++ { + for j := 0; j < m; j++ { + vx[j] = x[j].GetCoeff(i) + } + r[i] = f(i, fid, vx...) + fid.Mul(&fid, &generator) + } + }) + + res := iop.NewPolynomial(&r, iop.Form{Layout: iop.Regular, Basis: iop.LagrangeCoset}) + res.SetSize(x[0].Size()) + res.SetBlindedSize(x[0].Size()) + + return res, nil +} + +// divideByXMinusOne +// 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) { + + // check that the basis is LagrangeCoset + if a.Basis != iop.LagrangeCoset { + return nil, errors.New("invalid form") + } + + // prepare the evaluations of x^n-1 on the big domain's coset + xnMinusOneInverseLagrangeCoset := evaluateXnMinusOneDomainBigCoset(domains) + + nbElmts := len(a.Coefficients()) + rho := nbElmts / a.Size() + + r := a.Coefficients() + + utils.Parallelize(nbElmts, func(start, end int) { + for i := start; i < end; i++ { + r[i].Mul(&r[i], &xnMinusOneInverseLagrangeCoset[i%rho]) + } + }) + + // TODO @gbotrel this is the only place we do a FFT inverse (on coset) with domain[1] + a.ToCanonical(domains[1]).ToRegular() + + return a, nil + +} + +// evaluateXnMinusOneDomainBigCoset evaluates Xᵐ-1 on DomainBig coset +func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { + + ratio := domains[1].Cardinality / domains[0].Cardinality + + res := make([]fr.Element, ratio) + + expo := big.NewInt(int64(domains[0].Cardinality)) + res[0].Exp(domains[1].FrMultiplicativeGen, expo) + + var t fr.Element + t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + + one := fr.One() + + for i := 1; i < int(ratio); i++ { + res[i].Mul(&res[i-1], &t) + res[i-1].Sub(&res[i-1], &one) + } + res[len(res)-1].Sub(&res[len(res)-1], &one) + + res = fr.BatchInvert(res) + + return res +} diff --git a/backend/plonk/bls24-315/setup.go b/backend/plonk/bls24-315/setup.go index 16702c5ee1..48c72ecd2a 100644 --- a/backend/plonk/bls24-315/setup.go +++ b/backend/plonk/bls24-315/setup.go @@ -26,38 +26,18 @@ import ( "github.com/consensys/gnark/backend/plonk/internal" "github.com/consensys/gnark/constraint" cs "github.com/consensys/gnark/constraint/bls24-315" + "github.com/consensys/gnark/logger" + "runtime" "sync" + "time" ) -// Trace stores a plonk trace as columns -type Trace struct { - - // Constants describing a plonk circuit. The first entries - // of LQk (whose index correspond to the public inputs) are set to 0, and are to be - // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 - // so the first nb_public_variables constraints look like this: - // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. - Ql, Qr, Qm, Qo, Qk *iop.Polynomial - Qcp []*iop.Polynomial - - // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. - // The set of interpolation is of size N, so to represent the permutation S we let S acts on the - // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). - // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are - // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . - S1, S2, S3 *iop.Polynomial - - // S full permutation, i -> S[i] - S []int64 -} - // VerifyingKey stores the data needed to verify a proof: // * The commitment scheme // * Commitments of ql prepended with as many ones as there are public inputs // * Commitments of qr, qm, qo, qk prepended with as many zeroes as there are public inputs // * Commitments to S1, S2, S3 type VerifyingKey struct { - // Size circuit Size uint64 SizeInv fr.Element @@ -81,6 +61,46 @@ type VerifyingKey struct { CommitmentConstraintIndexes []uint64 } +// Trace stores a plonk trace as columns +type Trace struct { + // Constants describing a plonk circuit. The first entries + // of LQk (whose index correspond to the public inputs) are set to 0, and are to be + // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 + // so the first nb_public_variables constraints look like this: + // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. + Ql, Qr, Qm, Qo, Qk *iop.Polynomial + Qcp []*iop.Polynomial + + // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. + // The set of interpolation is of size N, so to represent the permutation S we let S acts on the + // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). + // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are + // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . + S1, S2, S3 *iop.Polynomial + + // S full permutation, i -> S[i] + S []int64 +} + +// ExpandedTrace stores Ql, Qr, Qm, Qo, Qk, Qcp, S1, S2, S3 in LagrangeCoset form; +// the expanded trace is stored in a memory layout that is optimized for the prover +type ExpandedTrace struct { + Polynomials [][sizeExpandedTrace]fr.Element +} + +// Enum for the expanded trace indexes. +const ( + idx_QL int = iota + idx_QR + idx_QM + idx_QO + idx_S1 + idx_S2 + idx_S3 + idx_LONE + sizeExpandedTrace +) + // ProvingKey stores the data needed to generate a proof: // * the commitment scheme // * ql, prepended with as many ones as they are public inputs @@ -90,7 +110,6 @@ type VerifyingKey struct { // * sigma_1, sigma_2, sigma_3 in both basis // * the copy constraint permutation type ProvingKey struct { - // stores ql, qr, qm, qo, qk (-> to be completed by the prover) // and s1, s2, s3. They are set in canonical basis before generating the proof, they will be used // for computing the opening proofs (hence the canonical form). The canonical version @@ -98,28 +117,24 @@ type ProvingKey struct { // The polynomials in trace are in canonical basis. trace Trace + // perf: this is quite fat; so we don't serialize it by default. + expandedTrace *ExpandedTrace + Kzg kzg.ProvingKey // Verifying Key is embedded into the proving key (needed by Prove) Vk *VerifyingKey - // qr,ql,qm,qo,qcp in LagrangeCoset --> these are not serialized, but computed from Ql, Qr, Qm, Qo, Qcp once. - lcQl, lcQr, lcQm, lcQo *iop.Polynomial - lcQcp []*iop.Polynomial + // qcp in LagrangeCoset form; not serialized + lcQcp []*iop.Polynomial // LQk qk in Lagrange form -> to be completed by the prover. After being completed, - lQk *iop.Polynomial + // lQk *iop.Polynomial // Domains used for the FFTs. // Domain[0] = small Domain // Domain[1] = big Domain Domain [2]fft.Domain - - // in lagrange coset basis --> these are not serialized, but computed from S1Canonical, S2Canonical, S3Canonical once. - lcS1, lcS2, lcS3 *iop.Polynomial - - // in lagrange coset basis --> not serialized id and L_{g^{0}} - lcIdIOP, lLoneIOP *iop.Polynomial } func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, error) { @@ -160,7 +175,6 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // All the above polynomials are expressed in canonical basis afterwards. This is why // we save lqk before, because the prover needs to complete it in Lagrange form, and // then express it on the Lagrange coset basis. - pk.lQk = pk.trace.Qk.Clone() // it will be completed by the prover, and the evaluated on the coset err := commitTrace(&pk.trace, &pk) if err != nil { return nil, nil, err @@ -177,65 +191,63 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // computeLagrangeCosetPolys computes each polynomial except qk in Lagrange coset // basis. Qk will be evaluated in Lagrange coset basis once it is completed by the prover. func (pk *ProvingKey) computeLagrangeCosetPolys() { - var wg sync.WaitGroup - wg.Add(7 + len(pk.trace.Qcp)) - n1 := int(pk.Domain[1].Cardinality) - pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) - for i, qcpI := range pk.trace.Qcp { - go func(i int, qcpI *iop.Polynomial) { - pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }(i, qcpI) + pk.expandedTrace = &ExpandedTrace{ + Polynomials: make([][sizeExpandedTrace]fr.Element, pk.Domain[1].Cardinality), } - go func() { - pk.lcQl = pk.trace.Ql.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQr = pk.trace.Qr.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQm = pk.trace.Qm.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQo = pk.trace.Qo.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS1 = pk.trace.S1.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS2 = pk.trace.S2.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + log := logger.Logger().With().Str("backend", "plonk").Logger() + start := time.Now() + + var wg sync.WaitGroup + wg.Add(8) + + // fillFunc converts p to LagrangeCoset and fills the pk.expandedTrace.Polynomials structure. + fillFunc := func(p *iop.Polynomial, idx int) { + scratch := make([]fr.Element, len(p.Coefficients()), pk.Domain[1].Cardinality) + copy(scratch, p.Coefficients()) + pp := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Canonical, Layout: iop.Regular}) + pp.SetSize(p.Size()) + pp.SetBlindedSize(p.BlindedSize()) + pp.ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx] = pp.GetCoeff(i) + } + wg.Done() - }() + } + + go fillFunc(pk.trace.Ql, idx_QL) + go fillFunc(pk.trace.Qr, idx_QR) + go fillFunc(pk.trace.Qm, idx_QM) + go fillFunc(pk.trace.Qo, idx_QO) + go fillFunc(pk.trace.S1, idx_S1) + go fillFunc(pk.trace.S2, idx_S2) + go fillFunc(pk.trace.S3, idx_S3) + go func() { - pk.lcS3 = pk.trace.S3.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + n1 := int(pk.Domain[1].Cardinality) + pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) + for i, qcpI := range pk.trace.Qcp { + pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]).ToRegular() + } wg.Done() }() - // storing Id - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - id := make([]fr.Element, pk.Domain[1].Cardinality) - id[0].Set(&pk.Domain[1].FrMultiplicativeGen) - for i := 1; i < int(pk.Domain[1].Cardinality); i++ { - id[i].Mul(&id[i-1], &pk.Domain[1].Generator) - } - pk.lcIdIOP = iop.NewPolynomial(&id, lagReg) // L_{g^{0}} - cap := pk.Domain[1].Cardinality - if cap < pk.Domain[0].Cardinality { - cap = pk.Domain[0].Cardinality // sanity check + scratch := make([]fr.Element, pk.Domain[0].Cardinality, pk.Domain[1].Cardinality) + scratch[0].SetOne() + + p := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Lagrange, Layout: iop.Regular}) + p.ToCanonical(&pk.Domain[0]).ToRegular().ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx_LONE] = p.GetCoeff(i) } - lone := make([]fr.Element, pk.Domain[0].Cardinality, cap) - lone[0].SetOne() - pk.lLoneIOP = iop.NewPolynomial(&lone, lagReg).ToCanonical(&pk.Domain[0]). - ToRegular(). - ToLagrangeCoset(&pk.Domain[1]) wg.Wait() + runtime.GC() + + log.Debug().Dur("computeLagrangeCosetPolys", time.Since(start)).Msg("setup done") } // NbPublicWitness returns the expected public witness size (number of field elements) diff --git a/backend/plonk/bls24-315/verify.go b/backend/plonk/bls24-315/verify.go index 81dc747e0b..5e9f65fdb5 100644 --- a/backend/plonk/bls24-315/verify.go +++ b/backend/plonk/bls24-315/verify.go @@ -51,7 +51,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *vk, publicWitness, proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", vk, publicWitness, proof.Bsb22Commitments); err != nil { return err } gamma, err := deriveRandomness(&fs, "gamma", &proof.LRO[0], &proof.LRO[1], &proof.LRO[2]) @@ -270,7 +270,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { return err } -func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { +func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk *VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { // permutation if err := fs.Bind(challenge, vk.S[0].Marshal()); err != nil { diff --git a/backend/plonk/bls24-317/marshal.go b/backend/plonk/bls24-317/marshal.go index f3f8280898..deabc04088 100644 --- a/backend/plonk/bls24-317/marshal.go +++ b/backend/plonk/bls24-317/marshal.go @@ -156,7 +156,6 @@ func (pk *ProvingKey) writeTo(w io.Writer, withCompression bool) (n int64, err e pk.trace.Qo.Coefficients(), pk.trace.Qk.Coefficients(), coefficients(pk.trace.Qcp), - pk.lQk.Coefficients(), pk.trace.S1.Coefficients(), pk.trace.S2.Coefficients(), pk.trace.S3.Coefficients(), @@ -215,7 +214,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err dec := curve.NewDecoder(r) - var ql, qr, qm, qo, qk, lqk, s1, s2, s3 []fr.Element + var ql, qr, qm, qo, qk, s1, s2, s3 []fr.Element var qcp [][]fr.Element // TODO @gbotrel: this is a bit ugly, we should probably refactor this. @@ -231,16 +230,15 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err chErr chan error } - vectors := make([]v, 9) + vectors := make([]v, 8) vectors[0] = v{data: (*fr.Vector)(&ql)} vectors[1] = v{data: (*fr.Vector)(&qr)} vectors[2] = v{data: (*fr.Vector)(&qm)} vectors[3] = v{data: (*fr.Vector)(&qo)} vectors[4] = v{data: (*fr.Vector)(&qk)} - vectors[5] = v{data: (*fr.Vector)(&lqk)} - vectors[6] = v{data: (*fr.Vector)(&s1)} - vectors[7] = v{data: (*fr.Vector)(&s2)} - vectors[8] = v{data: (*fr.Vector)(&s3)} + vectors[5] = v{data: (*fr.Vector)(&s1)} + vectors[6] = v{data: (*fr.Vector)(&s2)} + vectors[7] = v{data: (*fr.Vector)(&s3)} // read ql, qr, qm, qo, qk for i := 0; i < 5; i++ { @@ -258,7 +256,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err } // read lqk, s1, s2, s3 - for i := 5; i < 9; i++ { + for i := 5; i < 8; i++ { n2, err, ch := vectors[i].data.AsyncReadFrom(r) n += n2 if err != nil { @@ -293,8 +291,6 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err for i := range qcp { pk.trace.Qcp[i] = iop.NewPolynomial(&qcp[i], canReg) } - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) // wait for FFT to be precomputed <-chDomain0 diff --git a/backend/plonk/bls24-317/marshal_test.go b/backend/plonk/bls24-317/marshal_test.go index 4c60a4450e..dfec816948 100644 --- a/backend/plonk/bls24-317/marshal_test.go +++ b/backend/plonk/bls24-317/marshal_test.go @@ -166,7 +166,6 @@ func (pk *ProvingKey) randomize() { qm := randomScalars(n) qo := randomScalars(n) qk := randomScalars(n) - lqk := randomScalars(n) s1 := randomScalars(n) s2 := randomScalars(n) s3 := randomScalars(n) @@ -191,9 +190,6 @@ func (pk *ProvingKey) randomize() { pk.trace.S[0] = -12 pk.trace.S[len(pk.trace.S)-1] = 8888 - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) - pk.computeLagrangeCosetPolys() } diff --git a/backend/plonk/bls24-317/prove.go b/backend/plonk/bls24-317/prove.go index 0cdc878d8d..31fcdda4cf 100644 --- a/backend/plonk/bls24-317/prove.go +++ b/backend/plonk/bls24-317/prove.go @@ -18,6 +18,7 @@ package plonk import ( "crypto/sha256" + "errors" "math/big" "runtime" "sync" @@ -100,15 +101,19 @@ func bsb22ComputeCommitmentHint(spr *cs.SparseR1CS, pk *ProvingKey, proof *Proof } func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*Proof, error) { + log := logger.Logger().With(). + Str("curve", spr.CurveID().String()). + Int("nbConstraints", spr.GetNbConstraints()). + Str("backend", "plonk").Logger() - log := logger.Logger().With().Str("curve", spr.CurveID().String()).Int("nbConstraints", spr.GetNbConstraints()).Str("backend", "plonk").Logger() - + // parse the options opt, err := backend.NewProverConfig(opts...) if err != nil { return nil, err } start := time.Now() + // pick a hash function that will be used to derive the challenges hFunc := sha256.New() @@ -122,11 +127,14 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts commitmentVal := make([]fr.Element, len(commitmentInfo)) // TODO @Tabaie get rid of this cCommitments := make([]*iop.Polynomial, len(commitmentInfo)) proof.Bsb22Commitments = make([]kzg.Digest, len(commitmentInfo)) + + // override the hint for the commitment constraints for i := range commitmentInfo { opt.SolverOpts = append(opt.SolverOpts, solver.OverrideHint(commitmentInfo[i].HintID, bsb22ComputeCommitmentHint(spr, pk, proof, cCommitments, &commitmentVal[i], i))) } + // override the hint for GKR constraints if spr.GkrInfo.Is() { var gkrData cs.GkrSolvingData opt.SolverOpts = append(opt.SolverOpts, @@ -135,47 +143,43 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // query l, r, o in Lagrange basis, not blinded + lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} _solution, err := spr.Solve(fullWitness, opt.SolverOpts...) if err != nil { return nil, err } - // TODO @gbotrel deal with that conversion lazily - lcCommitments := make([]*iop.Polynomial, len(cCommitments)) - for i := range cCommitments { - lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]) // lagrange coset form - } solution := _solution.(*cs.SparseR1CSSolution) evaluationLDomainSmall := []fr.Element(solution.L) evaluationRDomainSmall := []fr.Element(solution.R) evaluationODomainSmall := []fr.Element(solution.O) + wliop := iop.NewPolynomial(&evaluationLDomainSmall, lagReg) + wriop := iop.NewPolynomial(&evaluationRDomainSmall, lagReg) + woiop := iop.NewPolynomial(&evaluationODomainSmall, lagReg) + + // convert the commitment polynomials to LagrangeCoset basis + lcCommitments := make([]*iop.Polynomial, len(cCommitments)) + chLcc := make(chan struct{}, 1) + go func() { + for i := range cCommitments { + lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]).ToRegular() // lagrange coset form + } + close(chLcc) + }() - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} // l, r, o and blinded versions - var ( - wliop, - wriop, - woiop, - bwliop, - bwriop, - bwoiop *iop.Polynomial - ) + var bwliop, bwriop, bwoiop *iop.Polynomial var wgLRO sync.WaitGroup wgLRO.Add(3) go func() { - // we keep in lagrange regular form since iop.BuildRatioCopyConstraint prefers it in this form. - wliop = iop.NewPolynomial(&evaluationLDomainSmall, lagReg) - // we set the underlying slice capacity to domain[1].Cardinality to minimize mem moves. - bwliop = wliop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwliop = wliop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - wriop = iop.NewPolynomial(&evaluationRDomainSmall, lagReg) - bwriop = wriop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwriop = wriop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - woiop = iop.NewPolynomial(&evaluationODomainSmall, lagReg) - bwoiop = woiop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwoiop = woiop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() @@ -189,27 +193,22 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chLcqk := make(chan struct{}, 1) go func() { // compute qk in canonical basis, completed with the public inputs - // We copy the coeffs of qk to pk is not mutated - lqkcoef := pk.lQk.Coefficients() - qkCompletedCanonical := make([]fr.Element, len(lqkcoef)) + // TODO @ThomasPiellard should we do ToLagrange or ToLagrangeCoset here? + lcqk = pk.trace.Qk.Clone(int(pk.Domain[1].Cardinality)).ToLagrange(&pk.Domain[0]).ToRegular() + qkCompletedCanonical := lcqk.Coefficients() copy(qkCompletedCanonical, fw[:len(spr.Public)]) - copy(qkCompletedCanonical[len(spr.Public):], lqkcoef[len(spr.Public):]) for i := range commitmentInfo { qkCompletedCanonical[spr.GetNbPublicVariables()+commitmentInfo[i].CommitmentIndex] = commitmentVal[i] } - pk.Domain[0].FFTInverse(qkCompletedCanonical, fft.DIF) - fft.BitReverse(qkCompletedCanonical) - - canReg := iop.Form{Basis: iop.Canonical, Layout: iop.Regular} - lcqk = iop.NewPolynomial(&qkCompletedCanonical, canReg) - lcqk.ToLagrangeCoset(&pk.Domain[1]) + lcqk.ToCanonical(&pk.Domain[0]).ToRegular(). + ToLagrangeCoset(&pk.Domain[1]).ToRegular() close(chLcqk) }() // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { return nil, err } @@ -234,22 +233,30 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts // l, r, o are already blinded wgLRO.Add(3) + + // compute l,r,o (blinded version) in LagrangeCoset basis. + // we keep the regular version in memory for the next step + var lcbwliop, lcbwriop, lcbwoiop *iop.Polynomial + go func() { - bwliop.ToLagrangeCoset(&pk.Domain[1]) + lcbwliop = bwliop.Clone(int(pk.Domain[1].Cardinality)) + lcbwliop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwriop.ToLagrangeCoset(&pk.Domain[1]) + lcbwriop = bwriop.Clone(int(pk.Domain[1].Cardinality)) + lcbwriop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwoiop.ToLagrangeCoset(&pk.Domain[1]) + lcbwoiop = bwoiop.Clone(int(pk.Domain[1].Cardinality)) + lcbwoiop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() // compute the copy constraint's ratio // note that wliop, wriop and woiop are fft'ed (mutated) in the process. - ziop, err := iop.BuildRatioCopyConstraint( + bwziop, err := iop.BuildRatioCopyConstraint( []*iop.Polynomial{ wliop, wriop, @@ -264,13 +271,18 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts if err != nil { return proof, err } + // unused. + wliop = nil + wriop = nil + woiop = nil // commit to the blinded version of z chZ := make(chan error, 1) - var bwziop, bwsziop *iop.Polynomial + var bwsziop *iop.Polynomial var alpha fr.Element go func() { - bwziop = ziop // iop.NewWrappedPolynomial(&ziop) + // blind Z + // TODO @gbotrel memory wise we should allocate a bigger result for BuildRatioCopyConstraint bwziop.Blind(2) proof.Z, err = kzg.Commit(bwziop.Coefficients(), pk.Kzg, runtime.NumCPU()*2) if err != nil { @@ -283,37 +295,50 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chZ <- err } - // Store z(g*x), without reallocating a slice - bwsziop = bwziop.ShallowClone().Shift(1) - bwsziop.ToLagrangeCoset(&pk.Domain[1]) + // Store z(g*x) + // perf note: converting ToRegular here perfoms better on Apple M1, but not on a hpc machine. + bwsziop = bwziop.Clone().ToLagrangeCoset(&pk.Domain[1]) //.ToRegular() + chZ <- nil close(chZ) }() + // l , r , o, z, zs, qk , Bsb22Commitments, qCPrime + const ( + idx_L int = iota // bwliop + idx_R // bwriop + idx_O // bwoiop + idx_Z // bwsziop + idx_ZS // bwsziop shifted + idx_QK // lcqk + idx_Bsb22Commitments // lcCommitments ... pk.lcQcp... + ) + // Full capture using latest gnark crypto... - fic := func(fql, fqr, fqm, fqo, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { // TODO @Tabaie make use of the fact that qCPrime is a selector: sparse and binary + fic := func(i int, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { var ic, tmp fr.Element - ic.Mul(&fql, &l) - tmp.Mul(&fqr, &r) + ic.Mul(&pk.expandedTrace.Polynomials[i][idx_QL], &l) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QR], &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqm, &l).Mul(&tmp, &r) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QM], &l).Mul(&tmp, &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqo, &o) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QO], &o) ic.Add(&ic, &tmp).Add(&ic, &fqk) nbComms := len(commitmentInfo) - for i := range commitmentInfo { - tmp.Mul(&pi2QcPrime[i], &pi2QcPrime[i+nbComms]) + for j := range commitmentInfo { + tmp.Mul(&pi2QcPrime[j], &pi2QcPrime[j+nbComms]) ic.Add(&ic, &tmp) } return ic } - fo := func(l, r, o, fid, fs1, fs2, fs3, fz, fzs fr.Element) fr.Element { + fo := func(i int, fid, l, r, o, fz, fzs fr.Element) fr.Element { u := &pk.Domain[0].FrMultiplicativeGen var a, b, tmp fr.Element + b.Mul(&beta, &fid) a.Add(&b, &l).Add(&a, &gamma) b.Mul(&b, u) @@ -322,10 +347,10 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts tmp.Mul(&b, u).Add(&tmp, &o).Add(&tmp, &gamma) a.Mul(&a, &tmp).Mul(&a, &fz) - b.Mul(&beta, &fs1).Add(&b, &l).Add(&b, &gamma) - tmp.Mul(&beta, &fs2).Add(&tmp, &r).Add(&tmp, &gamma) + b.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S1]).Add(&b, &l).Add(&b, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S2]).Add(&tmp, &r).Add(&tmp, &gamma) b.Mul(&b, &tmp) - tmp.Mul(&beta, &fs3).Add(&tmp, &o).Add(&tmp, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S3]).Add(&tmp, &o).Add(&tmp, &gamma) b.Mul(&b, &tmp).Mul(&b, &fzs) b.Sub(&b, &a) @@ -333,19 +358,16 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts return b } - fone := func(fz, flone fr.Element) fr.Element { + fone := func(i int, fz fr.Element) fr.Element { one := fr.One() - one.Sub(&fz, &one).Mul(&one, &flone) + one.Sub(&fz, &one).Mul(&one, &pk.expandedTrace.Polynomials[i][idx_LONE]) return one } - // 0 , 1 , 2, 3 , 4 , 5 , 6 , 7, 8 , 9 , 10, 11, 12, 13, 14, 15:15+nbComm , 15+nbComm:15+2×nbComm - // l , r , o, id, s1, s2, s3, z, zs, ql, qr, qm, qo, qk ,lone, Bsb22Commitments, qCPrime - fm := func(x ...fr.Element) fr.Element { - - a := fic(x[9], x[10], x[11], x[12], x[13], x[0], x[1], x[2], x[15:]) - b := fo(x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8]) - c := fone(x[7], x[14]) + fm := func(i int, fid fr.Element, x ...fr.Element) fr.Element { + a := fic(i, x[idx_QK], x[idx_L], x[idx_R], x[idx_O], x[idx_Bsb22Commitments:]) + b := fo(i, fid, x[idx_L], x[idx_R], x[idx_O], x[idx_Z], x[idx_ZS]) + c := fone(i, x[idx_Z]) c.Mul(&c, &alpha).Add(&c, &b).Mul(&c, &alpha).Add(&c, &a) @@ -361,39 +383,36 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // wait for l, r o lagrange coset conversion + <-chLcc wgLRO.Wait() - - toEval := []*iop.Polynomial{ - bwliop, - bwriop, - bwoiop, - pk.lcIdIOP, - pk.lcS1, - pk.lcS2, - pk.lcS3, - bwziop, - bwsziop, - pk.lcQl, - pk.lcQr, - pk.lcQm, - pk.lcQo, - lcqk, - pk.lLoneIOP, - } - toEval = append(toEval, lcCommitments...) // TODO: Add this at beginning - toEval = append(toEval, pk.lcQcp...) - systemEvaluation, err := iop.Evaluate(fm, iop.Form{Basis: iop.LagrangeCoset, Layout: iop.BitReverse}, toEval...) + toEval := make([]*iop.Polynomial, 6+2*len(commitmentInfo)) + toEval[idx_L] = lcbwliop + toEval[idx_R] = lcbwriop + toEval[idx_O] = lcbwoiop + toEval[idx_Z] = bwsziop.ShallowClone() + toEval[idx_ZS] = bwsziop.Shift(1) + toEval[idx_QK] = lcqk + copy(toEval[idx_Bsb22Commitments:], lcCommitments) + copy(toEval[idx_Bsb22Commitments+len(lcCommitments):], pk.lcQcp) + + // systemEvaluation reuses lcqk for memory. + systemEvaluation, err := evaluate(lcqk, pk, fm, toEval...) if err != nil { return nil, err } - // open blinded Z at zeta*z - chbwzIOP := make(chan struct{}, 1) - go func() { - bwziop.ToCanonical(&pk.Domain[1]).ToRegular() - close(chbwzIOP) - }() - h, err := iop.DivideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc + toEval = nil + bwsziop = nil + lcqk = nil + lcbwliop = nil + lcbwriop = nil + lcbwoiop = nil + + for i := range lcCommitments { + lcCommitments[i] = nil + } + + h, err := divideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc if err != nil { return nil, err } @@ -420,7 +439,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var wgEvals sync.WaitGroup wgEvals.Add(3) evalAtZeta := func(poly *iop.Polynomial, res *fr.Element) { - poly.ToCanonical(&pk.Domain[1]).ToRegular() *res = poly.Evaluate(zeta) wgEvals.Done() } @@ -436,7 +454,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var zetaShifted fr.Element zetaShifted.Mul(&zeta, &pk.Vk.Generator) - <-chbwzIOP proof.ZShiftedOpening, err = kzg.Open( bwziop.Coefficients()[:bwziop.BlindedSize()], zetaShifted, @@ -468,11 +485,12 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts h2 := h.Coefficients()[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] h1 := h.Coefficients()[:pk.Domain[0].Cardinality+2] utils.Parallelize(len(foldedH), func(start, end int) { + var t fr.Element for i := start; i < end; i++ { - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 - foldedH[i].Add(&foldedH[i], &h2[i]) // ζ^{m+2)*h3+h2 - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² - foldedH[i].Add(&foldedH[i], &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 + t.Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 + t.Add(&t, &h2[i]) // ζ^{m+2)*h3+h2 + t.Mul(&t, &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² + foldedH[i].Add(&t, &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 } }) close(computeFoldedH) @@ -679,8 +697,15 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, Mul(&lagrangeZeta, &pk.Domain[0].CardinalityInv) // (1/n)*α²*L₁(ζ) s3canonical := pk.trace.S3.Coefficients() + utils.Parallelize(len(blindedZCanonical), func(start, end int) { + cql := pk.trace.Ql.Coefficients() + cqr := pk.trace.Qr.Coefficients() + cqm := pk.trace.Qm.Coefficients() + cqo := pk.trace.Qo.Coefficients() + cqk := pk.trace.Qk.Coefficients() + var t, t0, t1 fr.Element for i := start; i < end; i++ { @@ -696,22 +721,21 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, t.Mul(&t, &alpha) // α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*Z(μζ)*s3(X) - Z(X)*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ)) - cql := pk.trace.Ql.Coefficients() - cqr := pk.trace.Qr.Coefficients() - cqm := pk.trace.Qm.Coefficients() - cqo := pk.trace.Qo.Coefficients() - cqk := pk.trace.Qk.Coefficients() if i < len(cqm) { t1.Mul(&cqm[i], &rl) // linPol = linPol + l(ζ)r(ζ)*Qm(X) + t0.Mul(&cql[i], &lZeta) t0.Add(&t0, &t1) + t.Add(&t, &t0) // linPol = linPol + l(ζ)*Ql(X) t0.Mul(&cqr[i], &rZeta) t.Add(&t, &t0) // linPol = linPol + r(ζ)*Qr(X) - t0.Mul(&cqo[i], &oZeta).Add(&t0, &cqk[i]) + t0.Mul(&cqo[i], &oZeta) + t0.Add(&t0, &cqk[i]) + t.Add(&t, &t0) // linPol = linPol + o(ζ)*Qo(X) + Qk(X) for j := range qcpZeta { @@ -726,3 +750,118 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, }) return blindedZCanonical } + +// TODO @gbotrel these changes below should be merge upstream in gnark-crypto + +// expression represents a multivariate polynomial. +type expression func(i int, fid fr.Element, x ...fr.Element) fr.Element + +// Evaluate evaluates f on each entry of x. The returned value is +// the vector of evaluations of e on x. +// The form of the result is LagrangeCoset Regular. +// The Size field of the result is the same as the one of x[0]. +// The blindedSize field of the result is the same as Size. +// The Shift field of the result is 0. +// The result re-uses result memory space. +func evaluate(result *iop.Polynomial, pk *ProvingKey, f expression, x ...*iop.Polynomial) (*iop.Polynomial, error) { + if len(x) == 0 { + return nil, errors.New("need at lest one input") + } + + // check that the sizes are consistent + n := len(x[0].Coefficients()) + m := len(x) + for i := 1; i < m; i++ { + if n != len(x[i].Coefficients()) { + return nil, errors.New("inconsistent sizes") + } + } + + // result coefficients + r := result.Coefficients() + if len(r) != n { + return nil, errors.New("inconsistent sizes") + } + + utils.Parallelize(n, func(start, end int) { + // inputs to the expression we will evaluate + vx := make([]fr.Element, m) + generator := pk.Domain[1].Generator + + // we inject id polynomial + var fid fr.Element + fid.Exp(generator, big.NewInt(int64(start))) + fid.Mul(&fid, &pk.Domain[1].FrMultiplicativeGen) + + for i := start; i < end; i++ { + for j := 0; j < m; j++ { + vx[j] = x[j].GetCoeff(i) + } + r[i] = f(i, fid, vx...) + fid.Mul(&fid, &generator) + } + }) + + res := iop.NewPolynomial(&r, iop.Form{Layout: iop.Regular, Basis: iop.LagrangeCoset}) + res.SetSize(x[0].Size()) + res.SetBlindedSize(x[0].Size()) + + return res, nil +} + +// divideByXMinusOne +// 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) { + + // check that the basis is LagrangeCoset + if a.Basis != iop.LagrangeCoset { + return nil, errors.New("invalid form") + } + + // prepare the evaluations of x^n-1 on the big domain's coset + xnMinusOneInverseLagrangeCoset := evaluateXnMinusOneDomainBigCoset(domains) + + nbElmts := len(a.Coefficients()) + rho := nbElmts / a.Size() + + r := a.Coefficients() + + utils.Parallelize(nbElmts, func(start, end int) { + for i := start; i < end; i++ { + r[i].Mul(&r[i], &xnMinusOneInverseLagrangeCoset[i%rho]) + } + }) + + // TODO @gbotrel this is the only place we do a FFT inverse (on coset) with domain[1] + a.ToCanonical(domains[1]).ToRegular() + + return a, nil + +} + +// evaluateXnMinusOneDomainBigCoset evaluates Xᵐ-1 on DomainBig coset +func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { + + ratio := domains[1].Cardinality / domains[0].Cardinality + + res := make([]fr.Element, ratio) + + expo := big.NewInt(int64(domains[0].Cardinality)) + res[0].Exp(domains[1].FrMultiplicativeGen, expo) + + var t fr.Element + t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + + one := fr.One() + + for i := 1; i < int(ratio); i++ { + res[i].Mul(&res[i-1], &t) + res[i-1].Sub(&res[i-1], &one) + } + res[len(res)-1].Sub(&res[len(res)-1], &one) + + res = fr.BatchInvert(res) + + return res +} diff --git a/backend/plonk/bls24-317/setup.go b/backend/plonk/bls24-317/setup.go index 60cc0a3020..cfd7c5804c 100644 --- a/backend/plonk/bls24-317/setup.go +++ b/backend/plonk/bls24-317/setup.go @@ -26,38 +26,18 @@ import ( "github.com/consensys/gnark/backend/plonk/internal" "github.com/consensys/gnark/constraint" cs "github.com/consensys/gnark/constraint/bls24-317" + "github.com/consensys/gnark/logger" + "runtime" "sync" + "time" ) -// Trace stores a plonk trace as columns -type Trace struct { - - // Constants describing a plonk circuit. The first entries - // of LQk (whose index correspond to the public inputs) are set to 0, and are to be - // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 - // so the first nb_public_variables constraints look like this: - // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. - Ql, Qr, Qm, Qo, Qk *iop.Polynomial - Qcp []*iop.Polynomial - - // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. - // The set of interpolation is of size N, so to represent the permutation S we let S acts on the - // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). - // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are - // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . - S1, S2, S3 *iop.Polynomial - - // S full permutation, i -> S[i] - S []int64 -} - // VerifyingKey stores the data needed to verify a proof: // * The commitment scheme // * Commitments of ql prepended with as many ones as there are public inputs // * Commitments of qr, qm, qo, qk prepended with as many zeroes as there are public inputs // * Commitments to S1, S2, S3 type VerifyingKey struct { - // Size circuit Size uint64 SizeInv fr.Element @@ -81,6 +61,46 @@ type VerifyingKey struct { CommitmentConstraintIndexes []uint64 } +// Trace stores a plonk trace as columns +type Trace struct { + // Constants describing a plonk circuit. The first entries + // of LQk (whose index correspond to the public inputs) are set to 0, and are to be + // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 + // so the first nb_public_variables constraints look like this: + // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. + Ql, Qr, Qm, Qo, Qk *iop.Polynomial + Qcp []*iop.Polynomial + + // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. + // The set of interpolation is of size N, so to represent the permutation S we let S acts on the + // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). + // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are + // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . + S1, S2, S3 *iop.Polynomial + + // S full permutation, i -> S[i] + S []int64 +} + +// ExpandedTrace stores Ql, Qr, Qm, Qo, Qk, Qcp, S1, S2, S3 in LagrangeCoset form; +// the expanded trace is stored in a memory layout that is optimized for the prover +type ExpandedTrace struct { + Polynomials [][sizeExpandedTrace]fr.Element +} + +// Enum for the expanded trace indexes. +const ( + idx_QL int = iota + idx_QR + idx_QM + idx_QO + idx_S1 + idx_S2 + idx_S3 + idx_LONE + sizeExpandedTrace +) + // ProvingKey stores the data needed to generate a proof: // * the commitment scheme // * ql, prepended with as many ones as they are public inputs @@ -90,7 +110,6 @@ type VerifyingKey struct { // * sigma_1, sigma_2, sigma_3 in both basis // * the copy constraint permutation type ProvingKey struct { - // stores ql, qr, qm, qo, qk (-> to be completed by the prover) // and s1, s2, s3. They are set in canonical basis before generating the proof, they will be used // for computing the opening proofs (hence the canonical form). The canonical version @@ -98,28 +117,24 @@ type ProvingKey struct { // The polynomials in trace are in canonical basis. trace Trace + // perf: this is quite fat; so we don't serialize it by default. + expandedTrace *ExpandedTrace + Kzg kzg.ProvingKey // Verifying Key is embedded into the proving key (needed by Prove) Vk *VerifyingKey - // qr,ql,qm,qo,qcp in LagrangeCoset --> these are not serialized, but computed from Ql, Qr, Qm, Qo, Qcp once. - lcQl, lcQr, lcQm, lcQo *iop.Polynomial - lcQcp []*iop.Polynomial + // qcp in LagrangeCoset form; not serialized + lcQcp []*iop.Polynomial // LQk qk in Lagrange form -> to be completed by the prover. After being completed, - lQk *iop.Polynomial + // lQk *iop.Polynomial // Domains used for the FFTs. // Domain[0] = small Domain // Domain[1] = big Domain Domain [2]fft.Domain - - // in lagrange coset basis --> these are not serialized, but computed from S1Canonical, S2Canonical, S3Canonical once. - lcS1, lcS2, lcS3 *iop.Polynomial - - // in lagrange coset basis --> not serialized id and L_{g^{0}} - lcIdIOP, lLoneIOP *iop.Polynomial } func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, error) { @@ -160,7 +175,6 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // All the above polynomials are expressed in canonical basis afterwards. This is why // we save lqk before, because the prover needs to complete it in Lagrange form, and // then express it on the Lagrange coset basis. - pk.lQk = pk.trace.Qk.Clone() // it will be completed by the prover, and the evaluated on the coset err := commitTrace(&pk.trace, &pk) if err != nil { return nil, nil, err @@ -177,65 +191,63 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // computeLagrangeCosetPolys computes each polynomial except qk in Lagrange coset // basis. Qk will be evaluated in Lagrange coset basis once it is completed by the prover. func (pk *ProvingKey) computeLagrangeCosetPolys() { - var wg sync.WaitGroup - wg.Add(7 + len(pk.trace.Qcp)) - n1 := int(pk.Domain[1].Cardinality) - pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) - for i, qcpI := range pk.trace.Qcp { - go func(i int, qcpI *iop.Polynomial) { - pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }(i, qcpI) + pk.expandedTrace = &ExpandedTrace{ + Polynomials: make([][sizeExpandedTrace]fr.Element, pk.Domain[1].Cardinality), } - go func() { - pk.lcQl = pk.trace.Ql.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQr = pk.trace.Qr.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQm = pk.trace.Qm.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQo = pk.trace.Qo.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS1 = pk.trace.S1.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS2 = pk.trace.S2.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + log := logger.Logger().With().Str("backend", "plonk").Logger() + start := time.Now() + + var wg sync.WaitGroup + wg.Add(8) + + // fillFunc converts p to LagrangeCoset and fills the pk.expandedTrace.Polynomials structure. + fillFunc := func(p *iop.Polynomial, idx int) { + scratch := make([]fr.Element, len(p.Coefficients()), pk.Domain[1].Cardinality) + copy(scratch, p.Coefficients()) + pp := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Canonical, Layout: iop.Regular}) + pp.SetSize(p.Size()) + pp.SetBlindedSize(p.BlindedSize()) + pp.ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx] = pp.GetCoeff(i) + } + wg.Done() - }() + } + + go fillFunc(pk.trace.Ql, idx_QL) + go fillFunc(pk.trace.Qr, idx_QR) + go fillFunc(pk.trace.Qm, idx_QM) + go fillFunc(pk.trace.Qo, idx_QO) + go fillFunc(pk.trace.S1, idx_S1) + go fillFunc(pk.trace.S2, idx_S2) + go fillFunc(pk.trace.S3, idx_S3) + go func() { - pk.lcS3 = pk.trace.S3.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + n1 := int(pk.Domain[1].Cardinality) + pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) + for i, qcpI := range pk.trace.Qcp { + pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]).ToRegular() + } wg.Done() }() - // storing Id - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - id := make([]fr.Element, pk.Domain[1].Cardinality) - id[0].Set(&pk.Domain[1].FrMultiplicativeGen) - for i := 1; i < int(pk.Domain[1].Cardinality); i++ { - id[i].Mul(&id[i-1], &pk.Domain[1].Generator) - } - pk.lcIdIOP = iop.NewPolynomial(&id, lagReg) // L_{g^{0}} - cap := pk.Domain[1].Cardinality - if cap < pk.Domain[0].Cardinality { - cap = pk.Domain[0].Cardinality // sanity check + scratch := make([]fr.Element, pk.Domain[0].Cardinality, pk.Domain[1].Cardinality) + scratch[0].SetOne() + + p := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Lagrange, Layout: iop.Regular}) + p.ToCanonical(&pk.Domain[0]).ToRegular().ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx_LONE] = p.GetCoeff(i) } - lone := make([]fr.Element, pk.Domain[0].Cardinality, cap) - lone[0].SetOne() - pk.lLoneIOP = iop.NewPolynomial(&lone, lagReg).ToCanonical(&pk.Domain[0]). - ToRegular(). - ToLagrangeCoset(&pk.Domain[1]) wg.Wait() + runtime.GC() + + log.Debug().Dur("computeLagrangeCosetPolys", time.Since(start)).Msg("setup done") } // NbPublicWitness returns the expected public witness size (number of field elements) diff --git a/backend/plonk/bls24-317/verify.go b/backend/plonk/bls24-317/verify.go index a6a7479e08..f4b9c70ce2 100644 --- a/backend/plonk/bls24-317/verify.go +++ b/backend/plonk/bls24-317/verify.go @@ -51,7 +51,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *vk, publicWitness, proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", vk, publicWitness, proof.Bsb22Commitments); err != nil { return err } gamma, err := deriveRandomness(&fs, "gamma", &proof.LRO[0], &proof.LRO[1], &proof.LRO[2]) @@ -270,7 +270,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { return err } -func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { +func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk *VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { // permutation if err := fs.Bind(challenge, vk.S[0].Marshal()); err != nil { diff --git a/backend/plonk/bn254/marshal.go b/backend/plonk/bn254/marshal.go index 5d690ee24d..3a1b424ef1 100644 --- a/backend/plonk/bn254/marshal.go +++ b/backend/plonk/bn254/marshal.go @@ -156,7 +156,6 @@ func (pk *ProvingKey) writeTo(w io.Writer, withCompression bool) (n int64, err e pk.trace.Qo.Coefficients(), pk.trace.Qk.Coefficients(), coefficients(pk.trace.Qcp), - pk.lQk.Coefficients(), pk.trace.S1.Coefficients(), pk.trace.S2.Coefficients(), pk.trace.S3.Coefficients(), @@ -215,7 +214,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err dec := curve.NewDecoder(r) - var ql, qr, qm, qo, qk, lqk, s1, s2, s3 []fr.Element + var ql, qr, qm, qo, qk, s1, s2, s3 []fr.Element var qcp [][]fr.Element // TODO @gbotrel: this is a bit ugly, we should probably refactor this. @@ -231,16 +230,15 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err chErr chan error } - vectors := make([]v, 9) + vectors := make([]v, 8) vectors[0] = v{data: (*fr.Vector)(&ql)} vectors[1] = v{data: (*fr.Vector)(&qr)} vectors[2] = v{data: (*fr.Vector)(&qm)} vectors[3] = v{data: (*fr.Vector)(&qo)} vectors[4] = v{data: (*fr.Vector)(&qk)} - vectors[5] = v{data: (*fr.Vector)(&lqk)} - vectors[6] = v{data: (*fr.Vector)(&s1)} - vectors[7] = v{data: (*fr.Vector)(&s2)} - vectors[8] = v{data: (*fr.Vector)(&s3)} + vectors[5] = v{data: (*fr.Vector)(&s1)} + vectors[6] = v{data: (*fr.Vector)(&s2)} + vectors[7] = v{data: (*fr.Vector)(&s3)} // read ql, qr, qm, qo, qk for i := 0; i < 5; i++ { @@ -258,7 +256,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err } // read lqk, s1, s2, s3 - for i := 5; i < 9; i++ { + for i := 5; i < 8; i++ { n2, err, ch := vectors[i].data.AsyncReadFrom(r) n += n2 if err != nil { @@ -293,8 +291,6 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err for i := range qcp { pk.trace.Qcp[i] = iop.NewPolynomial(&qcp[i], canReg) } - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) // wait for FFT to be precomputed <-chDomain0 diff --git a/backend/plonk/bn254/marshal_test.go b/backend/plonk/bn254/marshal_test.go index 1ec158a9c8..9590b77da8 100644 --- a/backend/plonk/bn254/marshal_test.go +++ b/backend/plonk/bn254/marshal_test.go @@ -166,7 +166,6 @@ func (pk *ProvingKey) randomize() { qm := randomScalars(n) qo := randomScalars(n) qk := randomScalars(n) - lqk := randomScalars(n) s1 := randomScalars(n) s2 := randomScalars(n) s3 := randomScalars(n) @@ -191,9 +190,6 @@ func (pk *ProvingKey) randomize() { pk.trace.S[0] = -12 pk.trace.S[len(pk.trace.S)-1] = 8888 - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) - pk.computeLagrangeCosetPolys() } diff --git a/backend/plonk/bn254/prove.go b/backend/plonk/bn254/prove.go index 40b120a21f..4af0cbc197 100644 --- a/backend/plonk/bn254/prove.go +++ b/backend/plonk/bn254/prove.go @@ -18,6 +18,7 @@ package plonk import ( "crypto/sha256" + "errors" "math/big" "runtime" "sync" @@ -100,15 +101,19 @@ func bsb22ComputeCommitmentHint(spr *cs.SparseR1CS, pk *ProvingKey, proof *Proof } func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*Proof, error) { + log := logger.Logger().With(). + Str("curve", spr.CurveID().String()). + Int("nbConstraints", spr.GetNbConstraints()). + Str("backend", "plonk").Logger() - log := logger.Logger().With().Str("curve", spr.CurveID().String()).Int("nbConstraints", spr.GetNbConstraints()).Str("backend", "plonk").Logger() - + // parse the options opt, err := backend.NewProverConfig(opts...) if err != nil { return nil, err } start := time.Now() + // pick a hash function that will be used to derive the challenges hFunc := sha256.New() @@ -122,11 +127,14 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts commitmentVal := make([]fr.Element, len(commitmentInfo)) // TODO @Tabaie get rid of this cCommitments := make([]*iop.Polynomial, len(commitmentInfo)) proof.Bsb22Commitments = make([]kzg.Digest, len(commitmentInfo)) + + // override the hint for the commitment constraints for i := range commitmentInfo { opt.SolverOpts = append(opt.SolverOpts, solver.OverrideHint(commitmentInfo[i].HintID, bsb22ComputeCommitmentHint(spr, pk, proof, cCommitments, &commitmentVal[i], i))) } + // override the hint for GKR constraints if spr.GkrInfo.Is() { var gkrData cs.GkrSolvingData opt.SolverOpts = append(opt.SolverOpts, @@ -135,47 +143,43 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // query l, r, o in Lagrange basis, not blinded + lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} _solution, err := spr.Solve(fullWitness, opt.SolverOpts...) if err != nil { return nil, err } - // TODO @gbotrel deal with that conversion lazily - lcCommitments := make([]*iop.Polynomial, len(cCommitments)) - for i := range cCommitments { - lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]) // lagrange coset form - } solution := _solution.(*cs.SparseR1CSSolution) evaluationLDomainSmall := []fr.Element(solution.L) evaluationRDomainSmall := []fr.Element(solution.R) evaluationODomainSmall := []fr.Element(solution.O) + wliop := iop.NewPolynomial(&evaluationLDomainSmall, lagReg) + wriop := iop.NewPolynomial(&evaluationRDomainSmall, lagReg) + woiop := iop.NewPolynomial(&evaluationODomainSmall, lagReg) + + // convert the commitment polynomials to LagrangeCoset basis + lcCommitments := make([]*iop.Polynomial, len(cCommitments)) + chLcc := make(chan struct{}, 1) + go func() { + for i := range cCommitments { + lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]).ToRegular() // lagrange coset form + } + close(chLcc) + }() - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} // l, r, o and blinded versions - var ( - wliop, - wriop, - woiop, - bwliop, - bwriop, - bwoiop *iop.Polynomial - ) + var bwliop, bwriop, bwoiop *iop.Polynomial var wgLRO sync.WaitGroup wgLRO.Add(3) go func() { - // we keep in lagrange regular form since iop.BuildRatioCopyConstraint prefers it in this form. - wliop = iop.NewPolynomial(&evaluationLDomainSmall, lagReg) - // we set the underlying slice capacity to domain[1].Cardinality to minimize mem moves. - bwliop = wliop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwliop = wliop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - wriop = iop.NewPolynomial(&evaluationRDomainSmall, lagReg) - bwriop = wriop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwriop = wriop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - woiop = iop.NewPolynomial(&evaluationODomainSmall, lagReg) - bwoiop = woiop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwoiop = woiop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() @@ -189,27 +193,22 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chLcqk := make(chan struct{}, 1) go func() { // compute qk in canonical basis, completed with the public inputs - // We copy the coeffs of qk to pk is not mutated - lqkcoef := pk.lQk.Coefficients() - qkCompletedCanonical := make([]fr.Element, len(lqkcoef)) + // TODO @ThomasPiellard should we do ToLagrange or ToLagrangeCoset here? + lcqk = pk.trace.Qk.Clone(int(pk.Domain[1].Cardinality)).ToLagrange(&pk.Domain[0]).ToRegular() + qkCompletedCanonical := lcqk.Coefficients() copy(qkCompletedCanonical, fw[:len(spr.Public)]) - copy(qkCompletedCanonical[len(spr.Public):], lqkcoef[len(spr.Public):]) for i := range commitmentInfo { qkCompletedCanonical[spr.GetNbPublicVariables()+commitmentInfo[i].CommitmentIndex] = commitmentVal[i] } - pk.Domain[0].FFTInverse(qkCompletedCanonical, fft.DIF) - fft.BitReverse(qkCompletedCanonical) - - canReg := iop.Form{Basis: iop.Canonical, Layout: iop.Regular} - lcqk = iop.NewPolynomial(&qkCompletedCanonical, canReg) - lcqk.ToLagrangeCoset(&pk.Domain[1]) + lcqk.ToCanonical(&pk.Domain[0]).ToRegular(). + ToLagrangeCoset(&pk.Domain[1]).ToRegular() close(chLcqk) }() // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { return nil, err } @@ -234,22 +233,30 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts // l, r, o are already blinded wgLRO.Add(3) + + // compute l,r,o (blinded version) in LagrangeCoset basis. + // we keep the regular version in memory for the next step + var lcbwliop, lcbwriop, lcbwoiop *iop.Polynomial + go func() { - bwliop.ToLagrangeCoset(&pk.Domain[1]) + lcbwliop = bwliop.Clone(int(pk.Domain[1].Cardinality)) + lcbwliop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwriop.ToLagrangeCoset(&pk.Domain[1]) + lcbwriop = bwriop.Clone(int(pk.Domain[1].Cardinality)) + lcbwriop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwoiop.ToLagrangeCoset(&pk.Domain[1]) + lcbwoiop = bwoiop.Clone(int(pk.Domain[1].Cardinality)) + lcbwoiop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() // compute the copy constraint's ratio // note that wliop, wriop and woiop are fft'ed (mutated) in the process. - ziop, err := iop.BuildRatioCopyConstraint( + bwziop, err := iop.BuildRatioCopyConstraint( []*iop.Polynomial{ wliop, wriop, @@ -264,13 +271,18 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts if err != nil { return proof, err } + // unused. + wliop = nil + wriop = nil + woiop = nil // commit to the blinded version of z chZ := make(chan error, 1) - var bwziop, bwsziop *iop.Polynomial + var bwsziop *iop.Polynomial var alpha fr.Element go func() { - bwziop = ziop // iop.NewWrappedPolynomial(&ziop) + // blind Z + // TODO @gbotrel memory wise we should allocate a bigger result for BuildRatioCopyConstraint bwziop.Blind(2) proof.Z, err = kzg.Commit(bwziop.Coefficients(), pk.Kzg, runtime.NumCPU()*2) if err != nil { @@ -283,37 +295,50 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chZ <- err } - // Store z(g*x), without reallocating a slice - bwsziop = bwziop.ShallowClone().Shift(1) - bwsziop.ToLagrangeCoset(&pk.Domain[1]) + // Store z(g*x) + // perf note: converting ToRegular here perfoms better on Apple M1, but not on a hpc machine. + bwsziop = bwziop.Clone().ToLagrangeCoset(&pk.Domain[1]) //.ToRegular() + chZ <- nil close(chZ) }() + // l , r , o, z, zs, qk , Bsb22Commitments, qCPrime + const ( + idx_L int = iota // bwliop + idx_R // bwriop + idx_O // bwoiop + idx_Z // bwsziop + idx_ZS // bwsziop shifted + idx_QK // lcqk + idx_Bsb22Commitments // lcCommitments ... pk.lcQcp... + ) + // Full capture using latest gnark crypto... - fic := func(fql, fqr, fqm, fqo, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { // TODO @Tabaie make use of the fact that qCPrime is a selector: sparse and binary + fic := func(i int, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { var ic, tmp fr.Element - ic.Mul(&fql, &l) - tmp.Mul(&fqr, &r) + ic.Mul(&pk.expandedTrace.Polynomials[i][idx_QL], &l) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QR], &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqm, &l).Mul(&tmp, &r) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QM], &l).Mul(&tmp, &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqo, &o) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QO], &o) ic.Add(&ic, &tmp).Add(&ic, &fqk) nbComms := len(commitmentInfo) - for i := range commitmentInfo { - tmp.Mul(&pi2QcPrime[i], &pi2QcPrime[i+nbComms]) + for j := range commitmentInfo { + tmp.Mul(&pi2QcPrime[j], &pi2QcPrime[j+nbComms]) ic.Add(&ic, &tmp) } return ic } - fo := func(l, r, o, fid, fs1, fs2, fs3, fz, fzs fr.Element) fr.Element { + fo := func(i int, fid, l, r, o, fz, fzs fr.Element) fr.Element { u := &pk.Domain[0].FrMultiplicativeGen var a, b, tmp fr.Element + b.Mul(&beta, &fid) a.Add(&b, &l).Add(&a, &gamma) b.Mul(&b, u) @@ -322,10 +347,10 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts tmp.Mul(&b, u).Add(&tmp, &o).Add(&tmp, &gamma) a.Mul(&a, &tmp).Mul(&a, &fz) - b.Mul(&beta, &fs1).Add(&b, &l).Add(&b, &gamma) - tmp.Mul(&beta, &fs2).Add(&tmp, &r).Add(&tmp, &gamma) + b.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S1]).Add(&b, &l).Add(&b, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S2]).Add(&tmp, &r).Add(&tmp, &gamma) b.Mul(&b, &tmp) - tmp.Mul(&beta, &fs3).Add(&tmp, &o).Add(&tmp, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S3]).Add(&tmp, &o).Add(&tmp, &gamma) b.Mul(&b, &tmp).Mul(&b, &fzs) b.Sub(&b, &a) @@ -333,19 +358,16 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts return b } - fone := func(fz, flone fr.Element) fr.Element { + fone := func(i int, fz fr.Element) fr.Element { one := fr.One() - one.Sub(&fz, &one).Mul(&one, &flone) + one.Sub(&fz, &one).Mul(&one, &pk.expandedTrace.Polynomials[i][idx_LONE]) return one } - // 0 , 1 , 2, 3 , 4 , 5 , 6 , 7, 8 , 9 , 10, 11, 12, 13, 14, 15:15+nbComm , 15+nbComm:15+2×nbComm - // l , r , o, id, s1, s2, s3, z, zs, ql, qr, qm, qo, qk ,lone, Bsb22Commitments, qCPrime - fm := func(x ...fr.Element) fr.Element { - - a := fic(x[9], x[10], x[11], x[12], x[13], x[0], x[1], x[2], x[15:]) - b := fo(x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8]) - c := fone(x[7], x[14]) + fm := func(i int, fid fr.Element, x ...fr.Element) fr.Element { + a := fic(i, x[idx_QK], x[idx_L], x[idx_R], x[idx_O], x[idx_Bsb22Commitments:]) + b := fo(i, fid, x[idx_L], x[idx_R], x[idx_O], x[idx_Z], x[idx_ZS]) + c := fone(i, x[idx_Z]) c.Mul(&c, &alpha).Add(&c, &b).Mul(&c, &alpha).Add(&c, &a) @@ -361,39 +383,36 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // wait for l, r o lagrange coset conversion + <-chLcc wgLRO.Wait() - - toEval := []*iop.Polynomial{ - bwliop, - bwriop, - bwoiop, - pk.lcIdIOP, - pk.lcS1, - pk.lcS2, - pk.lcS3, - bwziop, - bwsziop, - pk.lcQl, - pk.lcQr, - pk.lcQm, - pk.lcQo, - lcqk, - pk.lLoneIOP, - } - toEval = append(toEval, lcCommitments...) // TODO: Add this at beginning - toEval = append(toEval, pk.lcQcp...) - systemEvaluation, err := iop.Evaluate(fm, iop.Form{Basis: iop.LagrangeCoset, Layout: iop.BitReverse}, toEval...) + toEval := make([]*iop.Polynomial, 6+2*len(commitmentInfo)) + toEval[idx_L] = lcbwliop + toEval[idx_R] = lcbwriop + toEval[idx_O] = lcbwoiop + toEval[idx_Z] = bwsziop.ShallowClone() + toEval[idx_ZS] = bwsziop.Shift(1) + toEval[idx_QK] = lcqk + copy(toEval[idx_Bsb22Commitments:], lcCommitments) + copy(toEval[idx_Bsb22Commitments+len(lcCommitments):], pk.lcQcp) + + // systemEvaluation reuses lcqk for memory. + systemEvaluation, err := evaluate(lcqk, pk, fm, toEval...) if err != nil { return nil, err } - // open blinded Z at zeta*z - chbwzIOP := make(chan struct{}, 1) - go func() { - bwziop.ToCanonical(&pk.Domain[1]).ToRegular() - close(chbwzIOP) - }() - h, err := iop.DivideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc + toEval = nil + bwsziop = nil + lcqk = nil + lcbwliop = nil + lcbwriop = nil + lcbwoiop = nil + + for i := range lcCommitments { + lcCommitments[i] = nil + } + + h, err := divideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc if err != nil { return nil, err } @@ -420,7 +439,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var wgEvals sync.WaitGroup wgEvals.Add(3) evalAtZeta := func(poly *iop.Polynomial, res *fr.Element) { - poly.ToCanonical(&pk.Domain[1]).ToRegular() *res = poly.Evaluate(zeta) wgEvals.Done() } @@ -436,7 +454,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var zetaShifted fr.Element zetaShifted.Mul(&zeta, &pk.Vk.Generator) - <-chbwzIOP proof.ZShiftedOpening, err = kzg.Open( bwziop.Coefficients()[:bwziop.BlindedSize()], zetaShifted, @@ -468,11 +485,12 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts h2 := h.Coefficients()[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] h1 := h.Coefficients()[:pk.Domain[0].Cardinality+2] utils.Parallelize(len(foldedH), func(start, end int) { + var t fr.Element for i := start; i < end; i++ { - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 - foldedH[i].Add(&foldedH[i], &h2[i]) // ζ^{m+2)*h3+h2 - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² - foldedH[i].Add(&foldedH[i], &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 + t.Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 + t.Add(&t, &h2[i]) // ζ^{m+2)*h3+h2 + t.Mul(&t, &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² + foldedH[i].Add(&t, &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 } }) close(computeFoldedH) @@ -679,8 +697,15 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, Mul(&lagrangeZeta, &pk.Domain[0].CardinalityInv) // (1/n)*α²*L₁(ζ) s3canonical := pk.trace.S3.Coefficients() + utils.Parallelize(len(blindedZCanonical), func(start, end int) { + cql := pk.trace.Ql.Coefficients() + cqr := pk.trace.Qr.Coefficients() + cqm := pk.trace.Qm.Coefficients() + cqo := pk.trace.Qo.Coefficients() + cqk := pk.trace.Qk.Coefficients() + var t, t0, t1 fr.Element for i := start; i < end; i++ { @@ -696,22 +721,21 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, t.Mul(&t, &alpha) // α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*Z(μζ)*s3(X) - Z(X)*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ)) - cql := pk.trace.Ql.Coefficients() - cqr := pk.trace.Qr.Coefficients() - cqm := pk.trace.Qm.Coefficients() - cqo := pk.trace.Qo.Coefficients() - cqk := pk.trace.Qk.Coefficients() if i < len(cqm) { t1.Mul(&cqm[i], &rl) // linPol = linPol + l(ζ)r(ζ)*Qm(X) + t0.Mul(&cql[i], &lZeta) t0.Add(&t0, &t1) + t.Add(&t, &t0) // linPol = linPol + l(ζ)*Ql(X) t0.Mul(&cqr[i], &rZeta) t.Add(&t, &t0) // linPol = linPol + r(ζ)*Qr(X) - t0.Mul(&cqo[i], &oZeta).Add(&t0, &cqk[i]) + t0.Mul(&cqo[i], &oZeta) + t0.Add(&t0, &cqk[i]) + t.Add(&t, &t0) // linPol = linPol + o(ζ)*Qo(X) + Qk(X) for j := range qcpZeta { @@ -726,3 +750,118 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, }) return blindedZCanonical } + +// TODO @gbotrel these changes below should be merge upstream in gnark-crypto + +// expression represents a multivariate polynomial. +type expression func(i int, fid fr.Element, x ...fr.Element) fr.Element + +// Evaluate evaluates f on each entry of x. The returned value is +// the vector of evaluations of e on x. +// The form of the result is LagrangeCoset Regular. +// The Size field of the result is the same as the one of x[0]. +// The blindedSize field of the result is the same as Size. +// The Shift field of the result is 0. +// The result re-uses result memory space. +func evaluate(result *iop.Polynomial, pk *ProvingKey, f expression, x ...*iop.Polynomial) (*iop.Polynomial, error) { + if len(x) == 0 { + return nil, errors.New("need at lest one input") + } + + // check that the sizes are consistent + n := len(x[0].Coefficients()) + m := len(x) + for i := 1; i < m; i++ { + if n != len(x[i].Coefficients()) { + return nil, errors.New("inconsistent sizes") + } + } + + // result coefficients + r := result.Coefficients() + if len(r) != n { + return nil, errors.New("inconsistent sizes") + } + + utils.Parallelize(n, func(start, end int) { + // inputs to the expression we will evaluate + vx := make([]fr.Element, m) + generator := pk.Domain[1].Generator + + // we inject id polynomial + var fid fr.Element + fid.Exp(generator, big.NewInt(int64(start))) + fid.Mul(&fid, &pk.Domain[1].FrMultiplicativeGen) + + for i := start; i < end; i++ { + for j := 0; j < m; j++ { + vx[j] = x[j].GetCoeff(i) + } + r[i] = f(i, fid, vx...) + fid.Mul(&fid, &generator) + } + }) + + res := iop.NewPolynomial(&r, iop.Form{Layout: iop.Regular, Basis: iop.LagrangeCoset}) + res.SetSize(x[0].Size()) + res.SetBlindedSize(x[0].Size()) + + return res, nil +} + +// divideByXMinusOne +// 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) { + + // check that the basis is LagrangeCoset + if a.Basis != iop.LagrangeCoset { + return nil, errors.New("invalid form") + } + + // prepare the evaluations of x^n-1 on the big domain's coset + xnMinusOneInverseLagrangeCoset := evaluateXnMinusOneDomainBigCoset(domains) + + nbElmts := len(a.Coefficients()) + rho := nbElmts / a.Size() + + r := a.Coefficients() + + utils.Parallelize(nbElmts, func(start, end int) { + for i := start; i < end; i++ { + r[i].Mul(&r[i], &xnMinusOneInverseLagrangeCoset[i%rho]) + } + }) + + // TODO @gbotrel this is the only place we do a FFT inverse (on coset) with domain[1] + a.ToCanonical(domains[1]).ToRegular() + + return a, nil + +} + +// evaluateXnMinusOneDomainBigCoset evaluates Xᵐ-1 on DomainBig coset +func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { + + ratio := domains[1].Cardinality / domains[0].Cardinality + + res := make([]fr.Element, ratio) + + expo := big.NewInt(int64(domains[0].Cardinality)) + res[0].Exp(domains[1].FrMultiplicativeGen, expo) + + var t fr.Element + t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + + one := fr.One() + + for i := 1; i < int(ratio); i++ { + res[i].Mul(&res[i-1], &t) + res[i-1].Sub(&res[i-1], &one) + } + res[len(res)-1].Sub(&res[len(res)-1], &one) + + res = fr.BatchInvert(res) + + return res +} diff --git a/backend/plonk/bn254/setup.go b/backend/plonk/bn254/setup.go index 20f7c9a64a..6b96b89891 100644 --- a/backend/plonk/bn254/setup.go +++ b/backend/plonk/bn254/setup.go @@ -26,38 +26,18 @@ import ( "github.com/consensys/gnark/backend/plonk/internal" "github.com/consensys/gnark/constraint" cs "github.com/consensys/gnark/constraint/bn254" + "github.com/consensys/gnark/logger" + "runtime" "sync" + "time" ) -// Trace stores a plonk trace as columns -type Trace struct { - - // Constants describing a plonk circuit. The first entries - // of LQk (whose index correspond to the public inputs) are set to 0, and are to be - // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 - // so the first nb_public_variables constraints look like this: - // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. - Ql, Qr, Qm, Qo, Qk *iop.Polynomial - Qcp []*iop.Polynomial - - // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. - // The set of interpolation is of size N, so to represent the permutation S we let S acts on the - // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). - // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are - // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . - S1, S2, S3 *iop.Polynomial - - // S full permutation, i -> S[i] - S []int64 -} - // VerifyingKey stores the data needed to verify a proof: // * The commitment scheme // * Commitments of ql prepended with as many ones as there are public inputs // * Commitments of qr, qm, qo, qk prepended with as many zeroes as there are public inputs // * Commitments to S1, S2, S3 type VerifyingKey struct { - // Size circuit Size uint64 SizeInv fr.Element @@ -81,6 +61,46 @@ type VerifyingKey struct { CommitmentConstraintIndexes []uint64 } +// Trace stores a plonk trace as columns +type Trace struct { + // Constants describing a plonk circuit. The first entries + // of LQk (whose index correspond to the public inputs) are set to 0, and are to be + // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 + // so the first nb_public_variables constraints look like this: + // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. + Ql, Qr, Qm, Qo, Qk *iop.Polynomial + Qcp []*iop.Polynomial + + // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. + // The set of interpolation is of size N, so to represent the permutation S we let S acts on the + // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). + // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are + // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . + S1, S2, S3 *iop.Polynomial + + // S full permutation, i -> S[i] + S []int64 +} + +// ExpandedTrace stores Ql, Qr, Qm, Qo, Qk, Qcp, S1, S2, S3 in LagrangeCoset form; +// the expanded trace is stored in a memory layout that is optimized for the prover +type ExpandedTrace struct { + Polynomials [][sizeExpandedTrace]fr.Element +} + +// Enum for the expanded trace indexes. +const ( + idx_QL int = iota + idx_QR + idx_QM + idx_QO + idx_S1 + idx_S2 + idx_S3 + idx_LONE + sizeExpandedTrace +) + // ProvingKey stores the data needed to generate a proof: // * the commitment scheme // * ql, prepended with as many ones as they are public inputs @@ -90,7 +110,6 @@ type VerifyingKey struct { // * sigma_1, sigma_2, sigma_3 in both basis // * the copy constraint permutation type ProvingKey struct { - // stores ql, qr, qm, qo, qk (-> to be completed by the prover) // and s1, s2, s3. They are set in canonical basis before generating the proof, they will be used // for computing the opening proofs (hence the canonical form). The canonical version @@ -98,28 +117,24 @@ type ProvingKey struct { // The polynomials in trace are in canonical basis. trace Trace + // perf: this is quite fat; so we don't serialize it by default. + expandedTrace *ExpandedTrace + Kzg kzg.ProvingKey // Verifying Key is embedded into the proving key (needed by Prove) Vk *VerifyingKey - // qr,ql,qm,qo,qcp in LagrangeCoset --> these are not serialized, but computed from Ql, Qr, Qm, Qo, Qcp once. - lcQl, lcQr, lcQm, lcQo *iop.Polynomial - lcQcp []*iop.Polynomial + // qcp in LagrangeCoset form; not serialized + lcQcp []*iop.Polynomial // LQk qk in Lagrange form -> to be completed by the prover. After being completed, - lQk *iop.Polynomial + // lQk *iop.Polynomial // Domains used for the FFTs. // Domain[0] = small Domain // Domain[1] = big Domain Domain [2]fft.Domain - - // in lagrange coset basis --> these are not serialized, but computed from S1Canonical, S2Canonical, S3Canonical once. - lcS1, lcS2, lcS3 *iop.Polynomial - - // in lagrange coset basis --> not serialized id and L_{g^{0}} - lcIdIOP, lLoneIOP *iop.Polynomial } func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, error) { @@ -160,7 +175,6 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // All the above polynomials are expressed in canonical basis afterwards. This is why // we save lqk before, because the prover needs to complete it in Lagrange form, and // then express it on the Lagrange coset basis. - pk.lQk = pk.trace.Qk.Clone() // it will be completed by the prover, and the evaluated on the coset err := commitTrace(&pk.trace, &pk) if err != nil { return nil, nil, err @@ -177,65 +191,63 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // computeLagrangeCosetPolys computes each polynomial except qk in Lagrange coset // basis. Qk will be evaluated in Lagrange coset basis once it is completed by the prover. func (pk *ProvingKey) computeLagrangeCosetPolys() { - var wg sync.WaitGroup - wg.Add(7 + len(pk.trace.Qcp)) - n1 := int(pk.Domain[1].Cardinality) - pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) - for i, qcpI := range pk.trace.Qcp { - go func(i int, qcpI *iop.Polynomial) { - pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }(i, qcpI) + pk.expandedTrace = &ExpandedTrace{ + Polynomials: make([][sizeExpandedTrace]fr.Element, pk.Domain[1].Cardinality), } - go func() { - pk.lcQl = pk.trace.Ql.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQr = pk.trace.Qr.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQm = pk.trace.Qm.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQo = pk.trace.Qo.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS1 = pk.trace.S1.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS2 = pk.trace.S2.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + log := logger.Logger().With().Str("backend", "plonk").Logger() + start := time.Now() + + var wg sync.WaitGroup + wg.Add(8) + + // fillFunc converts p to LagrangeCoset and fills the pk.expandedTrace.Polynomials structure. + fillFunc := func(p *iop.Polynomial, idx int) { + scratch := make([]fr.Element, len(p.Coefficients()), pk.Domain[1].Cardinality) + copy(scratch, p.Coefficients()) + pp := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Canonical, Layout: iop.Regular}) + pp.SetSize(p.Size()) + pp.SetBlindedSize(p.BlindedSize()) + pp.ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx] = pp.GetCoeff(i) + } + wg.Done() - }() + } + + go fillFunc(pk.trace.Ql, idx_QL) + go fillFunc(pk.trace.Qr, idx_QR) + go fillFunc(pk.trace.Qm, idx_QM) + go fillFunc(pk.trace.Qo, idx_QO) + go fillFunc(pk.trace.S1, idx_S1) + go fillFunc(pk.trace.S2, idx_S2) + go fillFunc(pk.trace.S3, idx_S3) + go func() { - pk.lcS3 = pk.trace.S3.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + n1 := int(pk.Domain[1].Cardinality) + pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) + for i, qcpI := range pk.trace.Qcp { + pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]).ToRegular() + } wg.Done() }() - // storing Id - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - id := make([]fr.Element, pk.Domain[1].Cardinality) - id[0].Set(&pk.Domain[1].FrMultiplicativeGen) - for i := 1; i < int(pk.Domain[1].Cardinality); i++ { - id[i].Mul(&id[i-1], &pk.Domain[1].Generator) - } - pk.lcIdIOP = iop.NewPolynomial(&id, lagReg) // L_{g^{0}} - cap := pk.Domain[1].Cardinality - if cap < pk.Domain[0].Cardinality { - cap = pk.Domain[0].Cardinality // sanity check + scratch := make([]fr.Element, pk.Domain[0].Cardinality, pk.Domain[1].Cardinality) + scratch[0].SetOne() + + p := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Lagrange, Layout: iop.Regular}) + p.ToCanonical(&pk.Domain[0]).ToRegular().ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx_LONE] = p.GetCoeff(i) } - lone := make([]fr.Element, pk.Domain[0].Cardinality, cap) - lone[0].SetOne() - pk.lLoneIOP = iop.NewPolynomial(&lone, lagReg).ToCanonical(&pk.Domain[0]). - ToRegular(). - ToLagrangeCoset(&pk.Domain[1]) wg.Wait() + runtime.GC() + + log.Debug().Dur("computeLagrangeCosetPolys", time.Since(start)).Msg("setup done") } // NbPublicWitness returns the expected public witness size (number of field elements) diff --git a/backend/plonk/bn254/verify.go b/backend/plonk/bn254/verify.go index 88bd903f5e..fc79994419 100644 --- a/backend/plonk/bn254/verify.go +++ b/backend/plonk/bn254/verify.go @@ -55,7 +55,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *vk, publicWitness, proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", vk, publicWitness, proof.Bsb22Commitments); err != nil { return err } gamma, err := deriveRandomness(&fs, "gamma", &proof.LRO[0], &proof.LRO[1], &proof.LRO[2]) @@ -274,7 +274,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { return err } -func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { +func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk *VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { // permutation if err := fs.Bind(challenge, vk.S[0].Marshal()); err != nil { diff --git a/backend/plonk/bw6-633/marshal.go b/backend/plonk/bw6-633/marshal.go index 7c6a31e4ea..7826cf2cfb 100644 --- a/backend/plonk/bw6-633/marshal.go +++ b/backend/plonk/bw6-633/marshal.go @@ -156,7 +156,6 @@ func (pk *ProvingKey) writeTo(w io.Writer, withCompression bool) (n int64, err e pk.trace.Qo.Coefficients(), pk.trace.Qk.Coefficients(), coefficients(pk.trace.Qcp), - pk.lQk.Coefficients(), pk.trace.S1.Coefficients(), pk.trace.S2.Coefficients(), pk.trace.S3.Coefficients(), @@ -215,7 +214,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err dec := curve.NewDecoder(r) - var ql, qr, qm, qo, qk, lqk, s1, s2, s3 []fr.Element + var ql, qr, qm, qo, qk, s1, s2, s3 []fr.Element var qcp [][]fr.Element // TODO @gbotrel: this is a bit ugly, we should probably refactor this. @@ -231,16 +230,15 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err chErr chan error } - vectors := make([]v, 9) + vectors := make([]v, 8) vectors[0] = v{data: (*fr.Vector)(&ql)} vectors[1] = v{data: (*fr.Vector)(&qr)} vectors[2] = v{data: (*fr.Vector)(&qm)} vectors[3] = v{data: (*fr.Vector)(&qo)} vectors[4] = v{data: (*fr.Vector)(&qk)} - vectors[5] = v{data: (*fr.Vector)(&lqk)} - vectors[6] = v{data: (*fr.Vector)(&s1)} - vectors[7] = v{data: (*fr.Vector)(&s2)} - vectors[8] = v{data: (*fr.Vector)(&s3)} + vectors[5] = v{data: (*fr.Vector)(&s1)} + vectors[6] = v{data: (*fr.Vector)(&s2)} + vectors[7] = v{data: (*fr.Vector)(&s3)} // read ql, qr, qm, qo, qk for i := 0; i < 5; i++ { @@ -258,7 +256,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err } // read lqk, s1, s2, s3 - for i := 5; i < 9; i++ { + for i := 5; i < 8; i++ { n2, err, ch := vectors[i].data.AsyncReadFrom(r) n += n2 if err != nil { @@ -293,8 +291,6 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err for i := range qcp { pk.trace.Qcp[i] = iop.NewPolynomial(&qcp[i], canReg) } - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) // wait for FFT to be precomputed <-chDomain0 diff --git a/backend/plonk/bw6-633/marshal_test.go b/backend/plonk/bw6-633/marshal_test.go index 0863b5967d..c75e369499 100644 --- a/backend/plonk/bw6-633/marshal_test.go +++ b/backend/plonk/bw6-633/marshal_test.go @@ -166,7 +166,6 @@ func (pk *ProvingKey) randomize() { qm := randomScalars(n) qo := randomScalars(n) qk := randomScalars(n) - lqk := randomScalars(n) s1 := randomScalars(n) s2 := randomScalars(n) s3 := randomScalars(n) @@ -191,9 +190,6 @@ func (pk *ProvingKey) randomize() { pk.trace.S[0] = -12 pk.trace.S[len(pk.trace.S)-1] = 8888 - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) - pk.computeLagrangeCosetPolys() } diff --git a/backend/plonk/bw6-633/prove.go b/backend/plonk/bw6-633/prove.go index 1ea6ccf02d..e08830f072 100644 --- a/backend/plonk/bw6-633/prove.go +++ b/backend/plonk/bw6-633/prove.go @@ -18,6 +18,7 @@ package plonk import ( "crypto/sha256" + "errors" "math/big" "runtime" "sync" @@ -100,15 +101,19 @@ func bsb22ComputeCommitmentHint(spr *cs.SparseR1CS, pk *ProvingKey, proof *Proof } func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*Proof, error) { + log := logger.Logger().With(). + Str("curve", spr.CurveID().String()). + Int("nbConstraints", spr.GetNbConstraints()). + Str("backend", "plonk").Logger() - log := logger.Logger().With().Str("curve", spr.CurveID().String()).Int("nbConstraints", spr.GetNbConstraints()).Str("backend", "plonk").Logger() - + // parse the options opt, err := backend.NewProverConfig(opts...) if err != nil { return nil, err } start := time.Now() + // pick a hash function that will be used to derive the challenges hFunc := sha256.New() @@ -122,11 +127,14 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts commitmentVal := make([]fr.Element, len(commitmentInfo)) // TODO @Tabaie get rid of this cCommitments := make([]*iop.Polynomial, len(commitmentInfo)) proof.Bsb22Commitments = make([]kzg.Digest, len(commitmentInfo)) + + // override the hint for the commitment constraints for i := range commitmentInfo { opt.SolverOpts = append(opt.SolverOpts, solver.OverrideHint(commitmentInfo[i].HintID, bsb22ComputeCommitmentHint(spr, pk, proof, cCommitments, &commitmentVal[i], i))) } + // override the hint for GKR constraints if spr.GkrInfo.Is() { var gkrData cs.GkrSolvingData opt.SolverOpts = append(opt.SolverOpts, @@ -135,47 +143,43 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // query l, r, o in Lagrange basis, not blinded + lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} _solution, err := spr.Solve(fullWitness, opt.SolverOpts...) if err != nil { return nil, err } - // TODO @gbotrel deal with that conversion lazily - lcCommitments := make([]*iop.Polynomial, len(cCommitments)) - for i := range cCommitments { - lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]) // lagrange coset form - } solution := _solution.(*cs.SparseR1CSSolution) evaluationLDomainSmall := []fr.Element(solution.L) evaluationRDomainSmall := []fr.Element(solution.R) evaluationODomainSmall := []fr.Element(solution.O) + wliop := iop.NewPolynomial(&evaluationLDomainSmall, lagReg) + wriop := iop.NewPolynomial(&evaluationRDomainSmall, lagReg) + woiop := iop.NewPolynomial(&evaluationODomainSmall, lagReg) + + // convert the commitment polynomials to LagrangeCoset basis + lcCommitments := make([]*iop.Polynomial, len(cCommitments)) + chLcc := make(chan struct{}, 1) + go func() { + for i := range cCommitments { + lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]).ToRegular() // lagrange coset form + } + close(chLcc) + }() - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} // l, r, o and blinded versions - var ( - wliop, - wriop, - woiop, - bwliop, - bwriop, - bwoiop *iop.Polynomial - ) + var bwliop, bwriop, bwoiop *iop.Polynomial var wgLRO sync.WaitGroup wgLRO.Add(3) go func() { - // we keep in lagrange regular form since iop.BuildRatioCopyConstraint prefers it in this form. - wliop = iop.NewPolynomial(&evaluationLDomainSmall, lagReg) - // we set the underlying slice capacity to domain[1].Cardinality to minimize mem moves. - bwliop = wliop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwliop = wliop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - wriop = iop.NewPolynomial(&evaluationRDomainSmall, lagReg) - bwriop = wriop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwriop = wriop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - woiop = iop.NewPolynomial(&evaluationODomainSmall, lagReg) - bwoiop = woiop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwoiop = woiop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() @@ -189,27 +193,22 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chLcqk := make(chan struct{}, 1) go func() { // compute qk in canonical basis, completed with the public inputs - // We copy the coeffs of qk to pk is not mutated - lqkcoef := pk.lQk.Coefficients() - qkCompletedCanonical := make([]fr.Element, len(lqkcoef)) + // TODO @ThomasPiellard should we do ToLagrange or ToLagrangeCoset here? + lcqk = pk.trace.Qk.Clone(int(pk.Domain[1].Cardinality)).ToLagrange(&pk.Domain[0]).ToRegular() + qkCompletedCanonical := lcqk.Coefficients() copy(qkCompletedCanonical, fw[:len(spr.Public)]) - copy(qkCompletedCanonical[len(spr.Public):], lqkcoef[len(spr.Public):]) for i := range commitmentInfo { qkCompletedCanonical[spr.GetNbPublicVariables()+commitmentInfo[i].CommitmentIndex] = commitmentVal[i] } - pk.Domain[0].FFTInverse(qkCompletedCanonical, fft.DIF) - fft.BitReverse(qkCompletedCanonical) - - canReg := iop.Form{Basis: iop.Canonical, Layout: iop.Regular} - lcqk = iop.NewPolynomial(&qkCompletedCanonical, canReg) - lcqk.ToLagrangeCoset(&pk.Domain[1]) + lcqk.ToCanonical(&pk.Domain[0]).ToRegular(). + ToLagrangeCoset(&pk.Domain[1]).ToRegular() close(chLcqk) }() // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { return nil, err } @@ -234,22 +233,30 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts // l, r, o are already blinded wgLRO.Add(3) + + // compute l,r,o (blinded version) in LagrangeCoset basis. + // we keep the regular version in memory for the next step + var lcbwliop, lcbwriop, lcbwoiop *iop.Polynomial + go func() { - bwliop.ToLagrangeCoset(&pk.Domain[1]) + lcbwliop = bwliop.Clone(int(pk.Domain[1].Cardinality)) + lcbwliop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwriop.ToLagrangeCoset(&pk.Domain[1]) + lcbwriop = bwriop.Clone(int(pk.Domain[1].Cardinality)) + lcbwriop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwoiop.ToLagrangeCoset(&pk.Domain[1]) + lcbwoiop = bwoiop.Clone(int(pk.Domain[1].Cardinality)) + lcbwoiop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() // compute the copy constraint's ratio // note that wliop, wriop and woiop are fft'ed (mutated) in the process. - ziop, err := iop.BuildRatioCopyConstraint( + bwziop, err := iop.BuildRatioCopyConstraint( []*iop.Polynomial{ wliop, wriop, @@ -264,13 +271,18 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts if err != nil { return proof, err } + // unused. + wliop = nil + wriop = nil + woiop = nil // commit to the blinded version of z chZ := make(chan error, 1) - var bwziop, bwsziop *iop.Polynomial + var bwsziop *iop.Polynomial var alpha fr.Element go func() { - bwziop = ziop // iop.NewWrappedPolynomial(&ziop) + // blind Z + // TODO @gbotrel memory wise we should allocate a bigger result for BuildRatioCopyConstraint bwziop.Blind(2) proof.Z, err = kzg.Commit(bwziop.Coefficients(), pk.Kzg, runtime.NumCPU()*2) if err != nil { @@ -283,37 +295,50 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chZ <- err } - // Store z(g*x), without reallocating a slice - bwsziop = bwziop.ShallowClone().Shift(1) - bwsziop.ToLagrangeCoset(&pk.Domain[1]) + // Store z(g*x) + // perf note: converting ToRegular here perfoms better on Apple M1, but not on a hpc machine. + bwsziop = bwziop.Clone().ToLagrangeCoset(&pk.Domain[1]) //.ToRegular() + chZ <- nil close(chZ) }() + // l , r , o, z, zs, qk , Bsb22Commitments, qCPrime + const ( + idx_L int = iota // bwliop + idx_R // bwriop + idx_O // bwoiop + idx_Z // bwsziop + idx_ZS // bwsziop shifted + idx_QK // lcqk + idx_Bsb22Commitments // lcCommitments ... pk.lcQcp... + ) + // Full capture using latest gnark crypto... - fic := func(fql, fqr, fqm, fqo, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { // TODO @Tabaie make use of the fact that qCPrime is a selector: sparse and binary + fic := func(i int, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { var ic, tmp fr.Element - ic.Mul(&fql, &l) - tmp.Mul(&fqr, &r) + ic.Mul(&pk.expandedTrace.Polynomials[i][idx_QL], &l) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QR], &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqm, &l).Mul(&tmp, &r) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QM], &l).Mul(&tmp, &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqo, &o) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QO], &o) ic.Add(&ic, &tmp).Add(&ic, &fqk) nbComms := len(commitmentInfo) - for i := range commitmentInfo { - tmp.Mul(&pi2QcPrime[i], &pi2QcPrime[i+nbComms]) + for j := range commitmentInfo { + tmp.Mul(&pi2QcPrime[j], &pi2QcPrime[j+nbComms]) ic.Add(&ic, &tmp) } return ic } - fo := func(l, r, o, fid, fs1, fs2, fs3, fz, fzs fr.Element) fr.Element { + fo := func(i int, fid, l, r, o, fz, fzs fr.Element) fr.Element { u := &pk.Domain[0].FrMultiplicativeGen var a, b, tmp fr.Element + b.Mul(&beta, &fid) a.Add(&b, &l).Add(&a, &gamma) b.Mul(&b, u) @@ -322,10 +347,10 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts tmp.Mul(&b, u).Add(&tmp, &o).Add(&tmp, &gamma) a.Mul(&a, &tmp).Mul(&a, &fz) - b.Mul(&beta, &fs1).Add(&b, &l).Add(&b, &gamma) - tmp.Mul(&beta, &fs2).Add(&tmp, &r).Add(&tmp, &gamma) + b.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S1]).Add(&b, &l).Add(&b, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S2]).Add(&tmp, &r).Add(&tmp, &gamma) b.Mul(&b, &tmp) - tmp.Mul(&beta, &fs3).Add(&tmp, &o).Add(&tmp, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S3]).Add(&tmp, &o).Add(&tmp, &gamma) b.Mul(&b, &tmp).Mul(&b, &fzs) b.Sub(&b, &a) @@ -333,19 +358,16 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts return b } - fone := func(fz, flone fr.Element) fr.Element { + fone := func(i int, fz fr.Element) fr.Element { one := fr.One() - one.Sub(&fz, &one).Mul(&one, &flone) + one.Sub(&fz, &one).Mul(&one, &pk.expandedTrace.Polynomials[i][idx_LONE]) return one } - // 0 , 1 , 2, 3 , 4 , 5 , 6 , 7, 8 , 9 , 10, 11, 12, 13, 14, 15:15+nbComm , 15+nbComm:15+2×nbComm - // l , r , o, id, s1, s2, s3, z, zs, ql, qr, qm, qo, qk ,lone, Bsb22Commitments, qCPrime - fm := func(x ...fr.Element) fr.Element { - - a := fic(x[9], x[10], x[11], x[12], x[13], x[0], x[1], x[2], x[15:]) - b := fo(x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8]) - c := fone(x[7], x[14]) + fm := func(i int, fid fr.Element, x ...fr.Element) fr.Element { + a := fic(i, x[idx_QK], x[idx_L], x[idx_R], x[idx_O], x[idx_Bsb22Commitments:]) + b := fo(i, fid, x[idx_L], x[idx_R], x[idx_O], x[idx_Z], x[idx_ZS]) + c := fone(i, x[idx_Z]) c.Mul(&c, &alpha).Add(&c, &b).Mul(&c, &alpha).Add(&c, &a) @@ -361,39 +383,36 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // wait for l, r o lagrange coset conversion + <-chLcc wgLRO.Wait() - - toEval := []*iop.Polynomial{ - bwliop, - bwriop, - bwoiop, - pk.lcIdIOP, - pk.lcS1, - pk.lcS2, - pk.lcS3, - bwziop, - bwsziop, - pk.lcQl, - pk.lcQr, - pk.lcQm, - pk.lcQo, - lcqk, - pk.lLoneIOP, - } - toEval = append(toEval, lcCommitments...) // TODO: Add this at beginning - toEval = append(toEval, pk.lcQcp...) - systemEvaluation, err := iop.Evaluate(fm, iop.Form{Basis: iop.LagrangeCoset, Layout: iop.BitReverse}, toEval...) + toEval := make([]*iop.Polynomial, 6+2*len(commitmentInfo)) + toEval[idx_L] = lcbwliop + toEval[idx_R] = lcbwriop + toEval[idx_O] = lcbwoiop + toEval[idx_Z] = bwsziop.ShallowClone() + toEval[idx_ZS] = bwsziop.Shift(1) + toEval[idx_QK] = lcqk + copy(toEval[idx_Bsb22Commitments:], lcCommitments) + copy(toEval[idx_Bsb22Commitments+len(lcCommitments):], pk.lcQcp) + + // systemEvaluation reuses lcqk for memory. + systemEvaluation, err := evaluate(lcqk, pk, fm, toEval...) if err != nil { return nil, err } - // open blinded Z at zeta*z - chbwzIOP := make(chan struct{}, 1) - go func() { - bwziop.ToCanonical(&pk.Domain[1]).ToRegular() - close(chbwzIOP) - }() - h, err := iop.DivideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc + toEval = nil + bwsziop = nil + lcqk = nil + lcbwliop = nil + lcbwriop = nil + lcbwoiop = nil + + for i := range lcCommitments { + lcCommitments[i] = nil + } + + h, err := divideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc if err != nil { return nil, err } @@ -420,7 +439,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var wgEvals sync.WaitGroup wgEvals.Add(3) evalAtZeta := func(poly *iop.Polynomial, res *fr.Element) { - poly.ToCanonical(&pk.Domain[1]).ToRegular() *res = poly.Evaluate(zeta) wgEvals.Done() } @@ -436,7 +454,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var zetaShifted fr.Element zetaShifted.Mul(&zeta, &pk.Vk.Generator) - <-chbwzIOP proof.ZShiftedOpening, err = kzg.Open( bwziop.Coefficients()[:bwziop.BlindedSize()], zetaShifted, @@ -468,11 +485,12 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts h2 := h.Coefficients()[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] h1 := h.Coefficients()[:pk.Domain[0].Cardinality+2] utils.Parallelize(len(foldedH), func(start, end int) { + var t fr.Element for i := start; i < end; i++ { - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 - foldedH[i].Add(&foldedH[i], &h2[i]) // ζ^{m+2)*h3+h2 - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² - foldedH[i].Add(&foldedH[i], &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 + t.Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 + t.Add(&t, &h2[i]) // ζ^{m+2)*h3+h2 + t.Mul(&t, &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² + foldedH[i].Add(&t, &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 } }) close(computeFoldedH) @@ -679,8 +697,15 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, Mul(&lagrangeZeta, &pk.Domain[0].CardinalityInv) // (1/n)*α²*L₁(ζ) s3canonical := pk.trace.S3.Coefficients() + utils.Parallelize(len(blindedZCanonical), func(start, end int) { + cql := pk.trace.Ql.Coefficients() + cqr := pk.trace.Qr.Coefficients() + cqm := pk.trace.Qm.Coefficients() + cqo := pk.trace.Qo.Coefficients() + cqk := pk.trace.Qk.Coefficients() + var t, t0, t1 fr.Element for i := start; i < end; i++ { @@ -696,22 +721,21 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, t.Mul(&t, &alpha) // α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*Z(μζ)*s3(X) - Z(X)*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ)) - cql := pk.trace.Ql.Coefficients() - cqr := pk.trace.Qr.Coefficients() - cqm := pk.trace.Qm.Coefficients() - cqo := pk.trace.Qo.Coefficients() - cqk := pk.trace.Qk.Coefficients() if i < len(cqm) { t1.Mul(&cqm[i], &rl) // linPol = linPol + l(ζ)r(ζ)*Qm(X) + t0.Mul(&cql[i], &lZeta) t0.Add(&t0, &t1) + t.Add(&t, &t0) // linPol = linPol + l(ζ)*Ql(X) t0.Mul(&cqr[i], &rZeta) t.Add(&t, &t0) // linPol = linPol + r(ζ)*Qr(X) - t0.Mul(&cqo[i], &oZeta).Add(&t0, &cqk[i]) + t0.Mul(&cqo[i], &oZeta) + t0.Add(&t0, &cqk[i]) + t.Add(&t, &t0) // linPol = linPol + o(ζ)*Qo(X) + Qk(X) for j := range qcpZeta { @@ -726,3 +750,118 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, }) return blindedZCanonical } + +// TODO @gbotrel these changes below should be merge upstream in gnark-crypto + +// expression represents a multivariate polynomial. +type expression func(i int, fid fr.Element, x ...fr.Element) fr.Element + +// Evaluate evaluates f on each entry of x. The returned value is +// the vector of evaluations of e on x. +// The form of the result is LagrangeCoset Regular. +// The Size field of the result is the same as the one of x[0]. +// The blindedSize field of the result is the same as Size. +// The Shift field of the result is 0. +// The result re-uses result memory space. +func evaluate(result *iop.Polynomial, pk *ProvingKey, f expression, x ...*iop.Polynomial) (*iop.Polynomial, error) { + if len(x) == 0 { + return nil, errors.New("need at lest one input") + } + + // check that the sizes are consistent + n := len(x[0].Coefficients()) + m := len(x) + for i := 1; i < m; i++ { + if n != len(x[i].Coefficients()) { + return nil, errors.New("inconsistent sizes") + } + } + + // result coefficients + r := result.Coefficients() + if len(r) != n { + return nil, errors.New("inconsistent sizes") + } + + utils.Parallelize(n, func(start, end int) { + // inputs to the expression we will evaluate + vx := make([]fr.Element, m) + generator := pk.Domain[1].Generator + + // we inject id polynomial + var fid fr.Element + fid.Exp(generator, big.NewInt(int64(start))) + fid.Mul(&fid, &pk.Domain[1].FrMultiplicativeGen) + + for i := start; i < end; i++ { + for j := 0; j < m; j++ { + vx[j] = x[j].GetCoeff(i) + } + r[i] = f(i, fid, vx...) + fid.Mul(&fid, &generator) + } + }) + + res := iop.NewPolynomial(&r, iop.Form{Layout: iop.Regular, Basis: iop.LagrangeCoset}) + res.SetSize(x[0].Size()) + res.SetBlindedSize(x[0].Size()) + + return res, nil +} + +// divideByXMinusOne +// 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) { + + // check that the basis is LagrangeCoset + if a.Basis != iop.LagrangeCoset { + return nil, errors.New("invalid form") + } + + // prepare the evaluations of x^n-1 on the big domain's coset + xnMinusOneInverseLagrangeCoset := evaluateXnMinusOneDomainBigCoset(domains) + + nbElmts := len(a.Coefficients()) + rho := nbElmts / a.Size() + + r := a.Coefficients() + + utils.Parallelize(nbElmts, func(start, end int) { + for i := start; i < end; i++ { + r[i].Mul(&r[i], &xnMinusOneInverseLagrangeCoset[i%rho]) + } + }) + + // TODO @gbotrel this is the only place we do a FFT inverse (on coset) with domain[1] + a.ToCanonical(domains[1]).ToRegular() + + return a, nil + +} + +// evaluateXnMinusOneDomainBigCoset evaluates Xᵐ-1 on DomainBig coset +func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { + + ratio := domains[1].Cardinality / domains[0].Cardinality + + res := make([]fr.Element, ratio) + + expo := big.NewInt(int64(domains[0].Cardinality)) + res[0].Exp(domains[1].FrMultiplicativeGen, expo) + + var t fr.Element + t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + + one := fr.One() + + for i := 1; i < int(ratio); i++ { + res[i].Mul(&res[i-1], &t) + res[i-1].Sub(&res[i-1], &one) + } + res[len(res)-1].Sub(&res[len(res)-1], &one) + + res = fr.BatchInvert(res) + + return res +} diff --git a/backend/plonk/bw6-633/setup.go b/backend/plonk/bw6-633/setup.go index 7390c3ee77..80091b7ad9 100644 --- a/backend/plonk/bw6-633/setup.go +++ b/backend/plonk/bw6-633/setup.go @@ -26,38 +26,18 @@ import ( "github.com/consensys/gnark/backend/plonk/internal" "github.com/consensys/gnark/constraint" cs "github.com/consensys/gnark/constraint/bw6-633" + "github.com/consensys/gnark/logger" + "runtime" "sync" + "time" ) -// Trace stores a plonk trace as columns -type Trace struct { - - // Constants describing a plonk circuit. The first entries - // of LQk (whose index correspond to the public inputs) are set to 0, and are to be - // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 - // so the first nb_public_variables constraints look like this: - // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. - Ql, Qr, Qm, Qo, Qk *iop.Polynomial - Qcp []*iop.Polynomial - - // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. - // The set of interpolation is of size N, so to represent the permutation S we let S acts on the - // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). - // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are - // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . - S1, S2, S3 *iop.Polynomial - - // S full permutation, i -> S[i] - S []int64 -} - // VerifyingKey stores the data needed to verify a proof: // * The commitment scheme // * Commitments of ql prepended with as many ones as there are public inputs // * Commitments of qr, qm, qo, qk prepended with as many zeroes as there are public inputs // * Commitments to S1, S2, S3 type VerifyingKey struct { - // Size circuit Size uint64 SizeInv fr.Element @@ -81,6 +61,46 @@ type VerifyingKey struct { CommitmentConstraintIndexes []uint64 } +// Trace stores a plonk trace as columns +type Trace struct { + // Constants describing a plonk circuit. The first entries + // of LQk (whose index correspond to the public inputs) are set to 0, and are to be + // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 + // so the first nb_public_variables constraints look like this: + // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. + Ql, Qr, Qm, Qo, Qk *iop.Polynomial + Qcp []*iop.Polynomial + + // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. + // The set of interpolation is of size N, so to represent the permutation S we let S acts on the + // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). + // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are + // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . + S1, S2, S3 *iop.Polynomial + + // S full permutation, i -> S[i] + S []int64 +} + +// ExpandedTrace stores Ql, Qr, Qm, Qo, Qk, Qcp, S1, S2, S3 in LagrangeCoset form; +// the expanded trace is stored in a memory layout that is optimized for the prover +type ExpandedTrace struct { + Polynomials [][sizeExpandedTrace]fr.Element +} + +// Enum for the expanded trace indexes. +const ( + idx_QL int = iota + idx_QR + idx_QM + idx_QO + idx_S1 + idx_S2 + idx_S3 + idx_LONE + sizeExpandedTrace +) + // ProvingKey stores the data needed to generate a proof: // * the commitment scheme // * ql, prepended with as many ones as they are public inputs @@ -90,7 +110,6 @@ type VerifyingKey struct { // * sigma_1, sigma_2, sigma_3 in both basis // * the copy constraint permutation type ProvingKey struct { - // stores ql, qr, qm, qo, qk (-> to be completed by the prover) // and s1, s2, s3. They are set in canonical basis before generating the proof, they will be used // for computing the opening proofs (hence the canonical form). The canonical version @@ -98,28 +117,24 @@ type ProvingKey struct { // The polynomials in trace are in canonical basis. trace Trace + // perf: this is quite fat; so we don't serialize it by default. + expandedTrace *ExpandedTrace + Kzg kzg.ProvingKey // Verifying Key is embedded into the proving key (needed by Prove) Vk *VerifyingKey - // qr,ql,qm,qo,qcp in LagrangeCoset --> these are not serialized, but computed from Ql, Qr, Qm, Qo, Qcp once. - lcQl, lcQr, lcQm, lcQo *iop.Polynomial - lcQcp []*iop.Polynomial + // qcp in LagrangeCoset form; not serialized + lcQcp []*iop.Polynomial // LQk qk in Lagrange form -> to be completed by the prover. After being completed, - lQk *iop.Polynomial + // lQk *iop.Polynomial // Domains used for the FFTs. // Domain[0] = small Domain // Domain[1] = big Domain Domain [2]fft.Domain - - // in lagrange coset basis --> these are not serialized, but computed from S1Canonical, S2Canonical, S3Canonical once. - lcS1, lcS2, lcS3 *iop.Polynomial - - // in lagrange coset basis --> not serialized id and L_{g^{0}} - lcIdIOP, lLoneIOP *iop.Polynomial } func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, error) { @@ -160,7 +175,6 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // All the above polynomials are expressed in canonical basis afterwards. This is why // we save lqk before, because the prover needs to complete it in Lagrange form, and // then express it on the Lagrange coset basis. - pk.lQk = pk.trace.Qk.Clone() // it will be completed by the prover, and the evaluated on the coset err := commitTrace(&pk.trace, &pk) if err != nil { return nil, nil, err @@ -177,65 +191,63 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // computeLagrangeCosetPolys computes each polynomial except qk in Lagrange coset // basis. Qk will be evaluated in Lagrange coset basis once it is completed by the prover. func (pk *ProvingKey) computeLagrangeCosetPolys() { - var wg sync.WaitGroup - wg.Add(7 + len(pk.trace.Qcp)) - n1 := int(pk.Domain[1].Cardinality) - pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) - for i, qcpI := range pk.trace.Qcp { - go func(i int, qcpI *iop.Polynomial) { - pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }(i, qcpI) + pk.expandedTrace = &ExpandedTrace{ + Polynomials: make([][sizeExpandedTrace]fr.Element, pk.Domain[1].Cardinality), } - go func() { - pk.lcQl = pk.trace.Ql.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQr = pk.trace.Qr.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQm = pk.trace.Qm.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQo = pk.trace.Qo.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS1 = pk.trace.S1.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS2 = pk.trace.S2.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + log := logger.Logger().With().Str("backend", "plonk").Logger() + start := time.Now() + + var wg sync.WaitGroup + wg.Add(8) + + // fillFunc converts p to LagrangeCoset and fills the pk.expandedTrace.Polynomials structure. + fillFunc := func(p *iop.Polynomial, idx int) { + scratch := make([]fr.Element, len(p.Coefficients()), pk.Domain[1].Cardinality) + copy(scratch, p.Coefficients()) + pp := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Canonical, Layout: iop.Regular}) + pp.SetSize(p.Size()) + pp.SetBlindedSize(p.BlindedSize()) + pp.ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx] = pp.GetCoeff(i) + } + wg.Done() - }() + } + + go fillFunc(pk.trace.Ql, idx_QL) + go fillFunc(pk.trace.Qr, idx_QR) + go fillFunc(pk.trace.Qm, idx_QM) + go fillFunc(pk.trace.Qo, idx_QO) + go fillFunc(pk.trace.S1, idx_S1) + go fillFunc(pk.trace.S2, idx_S2) + go fillFunc(pk.trace.S3, idx_S3) + go func() { - pk.lcS3 = pk.trace.S3.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + n1 := int(pk.Domain[1].Cardinality) + pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) + for i, qcpI := range pk.trace.Qcp { + pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]).ToRegular() + } wg.Done() }() - // storing Id - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - id := make([]fr.Element, pk.Domain[1].Cardinality) - id[0].Set(&pk.Domain[1].FrMultiplicativeGen) - for i := 1; i < int(pk.Domain[1].Cardinality); i++ { - id[i].Mul(&id[i-1], &pk.Domain[1].Generator) - } - pk.lcIdIOP = iop.NewPolynomial(&id, lagReg) // L_{g^{0}} - cap := pk.Domain[1].Cardinality - if cap < pk.Domain[0].Cardinality { - cap = pk.Domain[0].Cardinality // sanity check + scratch := make([]fr.Element, pk.Domain[0].Cardinality, pk.Domain[1].Cardinality) + scratch[0].SetOne() + + p := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Lagrange, Layout: iop.Regular}) + p.ToCanonical(&pk.Domain[0]).ToRegular().ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx_LONE] = p.GetCoeff(i) } - lone := make([]fr.Element, pk.Domain[0].Cardinality, cap) - lone[0].SetOne() - pk.lLoneIOP = iop.NewPolynomial(&lone, lagReg).ToCanonical(&pk.Domain[0]). - ToRegular(). - ToLagrangeCoset(&pk.Domain[1]) wg.Wait() + runtime.GC() + + log.Debug().Dur("computeLagrangeCosetPolys", time.Since(start)).Msg("setup done") } // NbPublicWitness returns the expected public witness size (number of field elements) diff --git a/backend/plonk/bw6-633/verify.go b/backend/plonk/bw6-633/verify.go index 63c4b1dbfb..dcfd7cf601 100644 --- a/backend/plonk/bw6-633/verify.go +++ b/backend/plonk/bw6-633/verify.go @@ -51,7 +51,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *vk, publicWitness, proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", vk, publicWitness, proof.Bsb22Commitments); err != nil { return err } gamma, err := deriveRandomness(&fs, "gamma", &proof.LRO[0], &proof.LRO[1], &proof.LRO[2]) @@ -270,7 +270,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { return err } -func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { +func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk *VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { // permutation if err := fs.Bind(challenge, vk.S[0].Marshal()); err != nil { diff --git a/backend/plonk/bw6-761/marshal.go b/backend/plonk/bw6-761/marshal.go index 6fff182303..68e05831db 100644 --- a/backend/plonk/bw6-761/marshal.go +++ b/backend/plonk/bw6-761/marshal.go @@ -156,7 +156,6 @@ func (pk *ProvingKey) writeTo(w io.Writer, withCompression bool) (n int64, err e pk.trace.Qo.Coefficients(), pk.trace.Qk.Coefficients(), coefficients(pk.trace.Qcp), - pk.lQk.Coefficients(), pk.trace.S1.Coefficients(), pk.trace.S2.Coefficients(), pk.trace.S3.Coefficients(), @@ -215,7 +214,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err dec := curve.NewDecoder(r) - var ql, qr, qm, qo, qk, lqk, s1, s2, s3 []fr.Element + var ql, qr, qm, qo, qk, s1, s2, s3 []fr.Element var qcp [][]fr.Element // TODO @gbotrel: this is a bit ugly, we should probably refactor this. @@ -231,16 +230,15 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err chErr chan error } - vectors := make([]v, 9) + vectors := make([]v, 8) vectors[0] = v{data: (*fr.Vector)(&ql)} vectors[1] = v{data: (*fr.Vector)(&qr)} vectors[2] = v{data: (*fr.Vector)(&qm)} vectors[3] = v{data: (*fr.Vector)(&qo)} vectors[4] = v{data: (*fr.Vector)(&qk)} - vectors[5] = v{data: (*fr.Vector)(&lqk)} - vectors[6] = v{data: (*fr.Vector)(&s1)} - vectors[7] = v{data: (*fr.Vector)(&s2)} - vectors[8] = v{data: (*fr.Vector)(&s3)} + vectors[5] = v{data: (*fr.Vector)(&s1)} + vectors[6] = v{data: (*fr.Vector)(&s2)} + vectors[7] = v{data: (*fr.Vector)(&s3)} // read ql, qr, qm, qo, qk for i := 0; i < 5; i++ { @@ -258,7 +256,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err } // read lqk, s1, s2, s3 - for i := 5; i < 9; i++ { + for i := 5; i < 8; i++ { n2, err, ch := vectors[i].data.AsyncReadFrom(r) n += n2 if err != nil { @@ -293,8 +291,6 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err for i := range qcp { pk.trace.Qcp[i] = iop.NewPolynomial(&qcp[i], canReg) } - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) // wait for FFT to be precomputed <-chDomain0 diff --git a/backend/plonk/bw6-761/marshal_test.go b/backend/plonk/bw6-761/marshal_test.go index 7e6d0e70c7..cd7dab66bf 100644 --- a/backend/plonk/bw6-761/marshal_test.go +++ b/backend/plonk/bw6-761/marshal_test.go @@ -166,7 +166,6 @@ func (pk *ProvingKey) randomize() { qm := randomScalars(n) qo := randomScalars(n) qk := randomScalars(n) - lqk := randomScalars(n) s1 := randomScalars(n) s2 := randomScalars(n) s3 := randomScalars(n) @@ -191,9 +190,6 @@ func (pk *ProvingKey) randomize() { pk.trace.S[0] = -12 pk.trace.S[len(pk.trace.S)-1] = 8888 - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) - pk.computeLagrangeCosetPolys() } diff --git a/backend/plonk/bw6-761/prove.go b/backend/plonk/bw6-761/prove.go index 603a95cf31..f83c22331c 100644 --- a/backend/plonk/bw6-761/prove.go +++ b/backend/plonk/bw6-761/prove.go @@ -18,6 +18,7 @@ package plonk import ( "crypto/sha256" + "errors" "math/big" "runtime" "sync" @@ -100,15 +101,19 @@ func bsb22ComputeCommitmentHint(spr *cs.SparseR1CS, pk *ProvingKey, proof *Proof } func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*Proof, error) { + log := logger.Logger().With(). + Str("curve", spr.CurveID().String()). + Int("nbConstraints", spr.GetNbConstraints()). + Str("backend", "plonk").Logger() - log := logger.Logger().With().Str("curve", spr.CurveID().String()).Int("nbConstraints", spr.GetNbConstraints()).Str("backend", "plonk").Logger() - + // parse the options opt, err := backend.NewProverConfig(opts...) if err != nil { return nil, err } start := time.Now() + // pick a hash function that will be used to derive the challenges hFunc := sha256.New() @@ -122,11 +127,14 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts commitmentVal := make([]fr.Element, len(commitmentInfo)) // TODO @Tabaie get rid of this cCommitments := make([]*iop.Polynomial, len(commitmentInfo)) proof.Bsb22Commitments = make([]kzg.Digest, len(commitmentInfo)) + + // override the hint for the commitment constraints for i := range commitmentInfo { opt.SolverOpts = append(opt.SolverOpts, solver.OverrideHint(commitmentInfo[i].HintID, bsb22ComputeCommitmentHint(spr, pk, proof, cCommitments, &commitmentVal[i], i))) } + // override the hint for GKR constraints if spr.GkrInfo.Is() { var gkrData cs.GkrSolvingData opt.SolverOpts = append(opt.SolverOpts, @@ -135,47 +143,43 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // query l, r, o in Lagrange basis, not blinded + lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} _solution, err := spr.Solve(fullWitness, opt.SolverOpts...) if err != nil { return nil, err } - // TODO @gbotrel deal with that conversion lazily - lcCommitments := make([]*iop.Polynomial, len(cCommitments)) - for i := range cCommitments { - lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]) // lagrange coset form - } solution := _solution.(*cs.SparseR1CSSolution) evaluationLDomainSmall := []fr.Element(solution.L) evaluationRDomainSmall := []fr.Element(solution.R) evaluationODomainSmall := []fr.Element(solution.O) + wliop := iop.NewPolynomial(&evaluationLDomainSmall, lagReg) + wriop := iop.NewPolynomial(&evaluationRDomainSmall, lagReg) + woiop := iop.NewPolynomial(&evaluationODomainSmall, lagReg) + + // convert the commitment polynomials to LagrangeCoset basis + lcCommitments := make([]*iop.Polynomial, len(cCommitments)) + chLcc := make(chan struct{}, 1) + go func() { + for i := range cCommitments { + lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]).ToRegular() // lagrange coset form + } + close(chLcc) + }() - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} // l, r, o and blinded versions - var ( - wliop, - wriop, - woiop, - bwliop, - bwriop, - bwoiop *iop.Polynomial - ) + var bwliop, bwriop, bwoiop *iop.Polynomial var wgLRO sync.WaitGroup wgLRO.Add(3) go func() { - // we keep in lagrange regular form since iop.BuildRatioCopyConstraint prefers it in this form. - wliop = iop.NewPolynomial(&evaluationLDomainSmall, lagReg) - // we set the underlying slice capacity to domain[1].Cardinality to minimize mem moves. - bwliop = wliop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwliop = wliop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - wriop = iop.NewPolynomial(&evaluationRDomainSmall, lagReg) - bwriop = wriop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwriop = wriop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - woiop = iop.NewPolynomial(&evaluationODomainSmall, lagReg) - bwoiop = woiop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwoiop = woiop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() @@ -189,27 +193,22 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chLcqk := make(chan struct{}, 1) go func() { // compute qk in canonical basis, completed with the public inputs - // We copy the coeffs of qk to pk is not mutated - lqkcoef := pk.lQk.Coefficients() - qkCompletedCanonical := make([]fr.Element, len(lqkcoef)) + // TODO @ThomasPiellard should we do ToLagrange or ToLagrangeCoset here? + lcqk = pk.trace.Qk.Clone(int(pk.Domain[1].Cardinality)).ToLagrange(&pk.Domain[0]).ToRegular() + qkCompletedCanonical := lcqk.Coefficients() copy(qkCompletedCanonical, fw[:len(spr.Public)]) - copy(qkCompletedCanonical[len(spr.Public):], lqkcoef[len(spr.Public):]) for i := range commitmentInfo { qkCompletedCanonical[spr.GetNbPublicVariables()+commitmentInfo[i].CommitmentIndex] = commitmentVal[i] } - pk.Domain[0].FFTInverse(qkCompletedCanonical, fft.DIF) - fft.BitReverse(qkCompletedCanonical) - - canReg := iop.Form{Basis: iop.Canonical, Layout: iop.Regular} - lcqk = iop.NewPolynomial(&qkCompletedCanonical, canReg) - lcqk.ToLagrangeCoset(&pk.Domain[1]) + lcqk.ToCanonical(&pk.Domain[0]).ToRegular(). + ToLagrangeCoset(&pk.Domain[1]).ToRegular() close(chLcqk) }() // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { return nil, err } @@ -234,22 +233,30 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts // l, r, o are already blinded wgLRO.Add(3) + + // compute l,r,o (blinded version) in LagrangeCoset basis. + // we keep the regular version in memory for the next step + var lcbwliop, lcbwriop, lcbwoiop *iop.Polynomial + go func() { - bwliop.ToLagrangeCoset(&pk.Domain[1]) + lcbwliop = bwliop.Clone(int(pk.Domain[1].Cardinality)) + lcbwliop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwriop.ToLagrangeCoset(&pk.Domain[1]) + lcbwriop = bwriop.Clone(int(pk.Domain[1].Cardinality)) + lcbwriop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwoiop.ToLagrangeCoset(&pk.Domain[1]) + lcbwoiop = bwoiop.Clone(int(pk.Domain[1].Cardinality)) + lcbwoiop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() // compute the copy constraint's ratio // note that wliop, wriop and woiop are fft'ed (mutated) in the process. - ziop, err := iop.BuildRatioCopyConstraint( + bwziop, err := iop.BuildRatioCopyConstraint( []*iop.Polynomial{ wliop, wriop, @@ -264,13 +271,18 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts if err != nil { return proof, err } + // unused. + wliop = nil + wriop = nil + woiop = nil // commit to the blinded version of z chZ := make(chan error, 1) - var bwziop, bwsziop *iop.Polynomial + var bwsziop *iop.Polynomial var alpha fr.Element go func() { - bwziop = ziop // iop.NewWrappedPolynomial(&ziop) + // blind Z + // TODO @gbotrel memory wise we should allocate a bigger result for BuildRatioCopyConstraint bwziop.Blind(2) proof.Z, err = kzg.Commit(bwziop.Coefficients(), pk.Kzg, runtime.NumCPU()*2) if err != nil { @@ -283,37 +295,50 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chZ <- err } - // Store z(g*x), without reallocating a slice - bwsziop = bwziop.ShallowClone().Shift(1) - bwsziop.ToLagrangeCoset(&pk.Domain[1]) + // Store z(g*x) + // perf note: converting ToRegular here perfoms better on Apple M1, but not on a hpc machine. + bwsziop = bwziop.Clone().ToLagrangeCoset(&pk.Domain[1]) //.ToRegular() + chZ <- nil close(chZ) }() + // l , r , o, z, zs, qk , Bsb22Commitments, qCPrime + const ( + idx_L int = iota // bwliop + idx_R // bwriop + idx_O // bwoiop + idx_Z // bwsziop + idx_ZS // bwsziop shifted + idx_QK // lcqk + idx_Bsb22Commitments // lcCommitments ... pk.lcQcp... + ) + // Full capture using latest gnark crypto... - fic := func(fql, fqr, fqm, fqo, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { // TODO @Tabaie make use of the fact that qCPrime is a selector: sparse and binary + fic := func(i int, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { var ic, tmp fr.Element - ic.Mul(&fql, &l) - tmp.Mul(&fqr, &r) + ic.Mul(&pk.expandedTrace.Polynomials[i][idx_QL], &l) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QR], &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqm, &l).Mul(&tmp, &r) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QM], &l).Mul(&tmp, &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqo, &o) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QO], &o) ic.Add(&ic, &tmp).Add(&ic, &fqk) nbComms := len(commitmentInfo) - for i := range commitmentInfo { - tmp.Mul(&pi2QcPrime[i], &pi2QcPrime[i+nbComms]) + for j := range commitmentInfo { + tmp.Mul(&pi2QcPrime[j], &pi2QcPrime[j+nbComms]) ic.Add(&ic, &tmp) } return ic } - fo := func(l, r, o, fid, fs1, fs2, fs3, fz, fzs fr.Element) fr.Element { + fo := func(i int, fid, l, r, o, fz, fzs fr.Element) fr.Element { u := &pk.Domain[0].FrMultiplicativeGen var a, b, tmp fr.Element + b.Mul(&beta, &fid) a.Add(&b, &l).Add(&a, &gamma) b.Mul(&b, u) @@ -322,10 +347,10 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts tmp.Mul(&b, u).Add(&tmp, &o).Add(&tmp, &gamma) a.Mul(&a, &tmp).Mul(&a, &fz) - b.Mul(&beta, &fs1).Add(&b, &l).Add(&b, &gamma) - tmp.Mul(&beta, &fs2).Add(&tmp, &r).Add(&tmp, &gamma) + b.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S1]).Add(&b, &l).Add(&b, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S2]).Add(&tmp, &r).Add(&tmp, &gamma) b.Mul(&b, &tmp) - tmp.Mul(&beta, &fs3).Add(&tmp, &o).Add(&tmp, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S3]).Add(&tmp, &o).Add(&tmp, &gamma) b.Mul(&b, &tmp).Mul(&b, &fzs) b.Sub(&b, &a) @@ -333,19 +358,16 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts return b } - fone := func(fz, flone fr.Element) fr.Element { + fone := func(i int, fz fr.Element) fr.Element { one := fr.One() - one.Sub(&fz, &one).Mul(&one, &flone) + one.Sub(&fz, &one).Mul(&one, &pk.expandedTrace.Polynomials[i][idx_LONE]) return one } - // 0 , 1 , 2, 3 , 4 , 5 , 6 , 7, 8 , 9 , 10, 11, 12, 13, 14, 15:15+nbComm , 15+nbComm:15+2×nbComm - // l , r , o, id, s1, s2, s3, z, zs, ql, qr, qm, qo, qk ,lone, Bsb22Commitments, qCPrime - fm := func(x ...fr.Element) fr.Element { - - a := fic(x[9], x[10], x[11], x[12], x[13], x[0], x[1], x[2], x[15:]) - b := fo(x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8]) - c := fone(x[7], x[14]) + fm := func(i int, fid fr.Element, x ...fr.Element) fr.Element { + a := fic(i, x[idx_QK], x[idx_L], x[idx_R], x[idx_O], x[idx_Bsb22Commitments:]) + b := fo(i, fid, x[idx_L], x[idx_R], x[idx_O], x[idx_Z], x[idx_ZS]) + c := fone(i, x[idx_Z]) c.Mul(&c, &alpha).Add(&c, &b).Mul(&c, &alpha).Add(&c, &a) @@ -361,39 +383,36 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // wait for l, r o lagrange coset conversion + <-chLcc wgLRO.Wait() - - toEval := []*iop.Polynomial{ - bwliop, - bwriop, - bwoiop, - pk.lcIdIOP, - pk.lcS1, - pk.lcS2, - pk.lcS3, - bwziop, - bwsziop, - pk.lcQl, - pk.lcQr, - pk.lcQm, - pk.lcQo, - lcqk, - pk.lLoneIOP, - } - toEval = append(toEval, lcCommitments...) // TODO: Add this at beginning - toEval = append(toEval, pk.lcQcp...) - systemEvaluation, err := iop.Evaluate(fm, iop.Form{Basis: iop.LagrangeCoset, Layout: iop.BitReverse}, toEval...) + toEval := make([]*iop.Polynomial, 6+2*len(commitmentInfo)) + toEval[idx_L] = lcbwliop + toEval[idx_R] = lcbwriop + toEval[idx_O] = lcbwoiop + toEval[idx_Z] = bwsziop.ShallowClone() + toEval[idx_ZS] = bwsziop.Shift(1) + toEval[idx_QK] = lcqk + copy(toEval[idx_Bsb22Commitments:], lcCommitments) + copy(toEval[idx_Bsb22Commitments+len(lcCommitments):], pk.lcQcp) + + // systemEvaluation reuses lcqk for memory. + systemEvaluation, err := evaluate(lcqk, pk, fm, toEval...) if err != nil { return nil, err } - // open blinded Z at zeta*z - chbwzIOP := make(chan struct{}, 1) - go func() { - bwziop.ToCanonical(&pk.Domain[1]).ToRegular() - close(chbwzIOP) - }() - h, err := iop.DivideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc + toEval = nil + bwsziop = nil + lcqk = nil + lcbwliop = nil + lcbwriop = nil + lcbwoiop = nil + + for i := range lcCommitments { + lcCommitments[i] = nil + } + + h, err := divideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc if err != nil { return nil, err } @@ -420,7 +439,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var wgEvals sync.WaitGroup wgEvals.Add(3) evalAtZeta := func(poly *iop.Polynomial, res *fr.Element) { - poly.ToCanonical(&pk.Domain[1]).ToRegular() *res = poly.Evaluate(zeta) wgEvals.Done() } @@ -436,7 +454,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var zetaShifted fr.Element zetaShifted.Mul(&zeta, &pk.Vk.Generator) - <-chbwzIOP proof.ZShiftedOpening, err = kzg.Open( bwziop.Coefficients()[:bwziop.BlindedSize()], zetaShifted, @@ -468,11 +485,12 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts h2 := h.Coefficients()[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] h1 := h.Coefficients()[:pk.Domain[0].Cardinality+2] utils.Parallelize(len(foldedH), func(start, end int) { + var t fr.Element for i := start; i < end; i++ { - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 - foldedH[i].Add(&foldedH[i], &h2[i]) // ζ^{m+2)*h3+h2 - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² - foldedH[i].Add(&foldedH[i], &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 + t.Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 + t.Add(&t, &h2[i]) // ζ^{m+2)*h3+h2 + t.Mul(&t, &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² + foldedH[i].Add(&t, &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 } }) close(computeFoldedH) @@ -679,8 +697,15 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, Mul(&lagrangeZeta, &pk.Domain[0].CardinalityInv) // (1/n)*α²*L₁(ζ) s3canonical := pk.trace.S3.Coefficients() + utils.Parallelize(len(blindedZCanonical), func(start, end int) { + cql := pk.trace.Ql.Coefficients() + cqr := pk.trace.Qr.Coefficients() + cqm := pk.trace.Qm.Coefficients() + cqo := pk.trace.Qo.Coefficients() + cqk := pk.trace.Qk.Coefficients() + var t, t0, t1 fr.Element for i := start; i < end; i++ { @@ -696,22 +721,21 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, t.Mul(&t, &alpha) // α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*Z(μζ)*s3(X) - Z(X)*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ)) - cql := pk.trace.Ql.Coefficients() - cqr := pk.trace.Qr.Coefficients() - cqm := pk.trace.Qm.Coefficients() - cqo := pk.trace.Qo.Coefficients() - cqk := pk.trace.Qk.Coefficients() if i < len(cqm) { t1.Mul(&cqm[i], &rl) // linPol = linPol + l(ζ)r(ζ)*Qm(X) + t0.Mul(&cql[i], &lZeta) t0.Add(&t0, &t1) + t.Add(&t, &t0) // linPol = linPol + l(ζ)*Ql(X) t0.Mul(&cqr[i], &rZeta) t.Add(&t, &t0) // linPol = linPol + r(ζ)*Qr(X) - t0.Mul(&cqo[i], &oZeta).Add(&t0, &cqk[i]) + t0.Mul(&cqo[i], &oZeta) + t0.Add(&t0, &cqk[i]) + t.Add(&t, &t0) // linPol = linPol + o(ζ)*Qo(X) + Qk(X) for j := range qcpZeta { @@ -726,3 +750,118 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, }) return blindedZCanonical } + +// TODO @gbotrel these changes below should be merge upstream in gnark-crypto + +// expression represents a multivariate polynomial. +type expression func(i int, fid fr.Element, x ...fr.Element) fr.Element + +// Evaluate evaluates f on each entry of x. The returned value is +// the vector of evaluations of e on x. +// The form of the result is LagrangeCoset Regular. +// The Size field of the result is the same as the one of x[0]. +// The blindedSize field of the result is the same as Size. +// The Shift field of the result is 0. +// The result re-uses result memory space. +func evaluate(result *iop.Polynomial, pk *ProvingKey, f expression, x ...*iop.Polynomial) (*iop.Polynomial, error) { + if len(x) == 0 { + return nil, errors.New("need at lest one input") + } + + // check that the sizes are consistent + n := len(x[0].Coefficients()) + m := len(x) + for i := 1; i < m; i++ { + if n != len(x[i].Coefficients()) { + return nil, errors.New("inconsistent sizes") + } + } + + // result coefficients + r := result.Coefficients() + if len(r) != n { + return nil, errors.New("inconsistent sizes") + } + + utils.Parallelize(n, func(start, end int) { + // inputs to the expression we will evaluate + vx := make([]fr.Element, m) + generator := pk.Domain[1].Generator + + // we inject id polynomial + var fid fr.Element + fid.Exp(generator, big.NewInt(int64(start))) + fid.Mul(&fid, &pk.Domain[1].FrMultiplicativeGen) + + for i := start; i < end; i++ { + for j := 0; j < m; j++ { + vx[j] = x[j].GetCoeff(i) + } + r[i] = f(i, fid, vx...) + fid.Mul(&fid, &generator) + } + }) + + res := iop.NewPolynomial(&r, iop.Form{Layout: iop.Regular, Basis: iop.LagrangeCoset}) + res.SetSize(x[0].Size()) + res.SetBlindedSize(x[0].Size()) + + return res, nil +} + +// divideByXMinusOne +// 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) { + + // check that the basis is LagrangeCoset + if a.Basis != iop.LagrangeCoset { + return nil, errors.New("invalid form") + } + + // prepare the evaluations of x^n-1 on the big domain's coset + xnMinusOneInverseLagrangeCoset := evaluateXnMinusOneDomainBigCoset(domains) + + nbElmts := len(a.Coefficients()) + rho := nbElmts / a.Size() + + r := a.Coefficients() + + utils.Parallelize(nbElmts, func(start, end int) { + for i := start; i < end; i++ { + r[i].Mul(&r[i], &xnMinusOneInverseLagrangeCoset[i%rho]) + } + }) + + // TODO @gbotrel this is the only place we do a FFT inverse (on coset) with domain[1] + a.ToCanonical(domains[1]).ToRegular() + + return a, nil + +} + +// evaluateXnMinusOneDomainBigCoset evaluates Xᵐ-1 on DomainBig coset +func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { + + ratio := domains[1].Cardinality / domains[0].Cardinality + + res := make([]fr.Element, ratio) + + expo := big.NewInt(int64(domains[0].Cardinality)) + res[0].Exp(domains[1].FrMultiplicativeGen, expo) + + var t fr.Element + t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + + one := fr.One() + + for i := 1; i < int(ratio); i++ { + res[i].Mul(&res[i-1], &t) + res[i-1].Sub(&res[i-1], &one) + } + res[len(res)-1].Sub(&res[len(res)-1], &one) + + res = fr.BatchInvert(res) + + return res +} diff --git a/backend/plonk/bw6-761/setup.go b/backend/plonk/bw6-761/setup.go index 16c2ca2da0..6fd74e6fc4 100644 --- a/backend/plonk/bw6-761/setup.go +++ b/backend/plonk/bw6-761/setup.go @@ -26,38 +26,18 @@ import ( "github.com/consensys/gnark/backend/plonk/internal" "github.com/consensys/gnark/constraint" cs "github.com/consensys/gnark/constraint/bw6-761" + "github.com/consensys/gnark/logger" + "runtime" "sync" + "time" ) -// Trace stores a plonk trace as columns -type Trace struct { - - // Constants describing a plonk circuit. The first entries - // of LQk (whose index correspond to the public inputs) are set to 0, and are to be - // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 - // so the first nb_public_variables constraints look like this: - // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. - Ql, Qr, Qm, Qo, Qk *iop.Polynomial - Qcp []*iop.Polynomial - - // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. - // The set of interpolation is of size N, so to represent the permutation S we let S acts on the - // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). - // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are - // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . - S1, S2, S3 *iop.Polynomial - - // S full permutation, i -> S[i] - S []int64 -} - // VerifyingKey stores the data needed to verify a proof: // * The commitment scheme // * Commitments of ql prepended with as many ones as there are public inputs // * Commitments of qr, qm, qo, qk prepended with as many zeroes as there are public inputs // * Commitments to S1, S2, S3 type VerifyingKey struct { - // Size circuit Size uint64 SizeInv fr.Element @@ -81,6 +61,46 @@ type VerifyingKey struct { CommitmentConstraintIndexes []uint64 } +// Trace stores a plonk trace as columns +type Trace struct { + // Constants describing a plonk circuit. The first entries + // of LQk (whose index correspond to the public inputs) are set to 0, and are to be + // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 + // so the first nb_public_variables constraints look like this: + // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. + Ql, Qr, Qm, Qo, Qk *iop.Polynomial + Qcp []*iop.Polynomial + + // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. + // The set of interpolation is of size N, so to represent the permutation S we let S acts on the + // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). + // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are + // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . + S1, S2, S3 *iop.Polynomial + + // S full permutation, i -> S[i] + S []int64 +} + +// ExpandedTrace stores Ql, Qr, Qm, Qo, Qk, Qcp, S1, S2, S3 in LagrangeCoset form; +// the expanded trace is stored in a memory layout that is optimized for the prover +type ExpandedTrace struct { + Polynomials [][sizeExpandedTrace]fr.Element +} + +// Enum for the expanded trace indexes. +const ( + idx_QL int = iota + idx_QR + idx_QM + idx_QO + idx_S1 + idx_S2 + idx_S3 + idx_LONE + sizeExpandedTrace +) + // ProvingKey stores the data needed to generate a proof: // * the commitment scheme // * ql, prepended with as many ones as they are public inputs @@ -90,7 +110,6 @@ type VerifyingKey struct { // * sigma_1, sigma_2, sigma_3 in both basis // * the copy constraint permutation type ProvingKey struct { - // stores ql, qr, qm, qo, qk (-> to be completed by the prover) // and s1, s2, s3. They are set in canonical basis before generating the proof, they will be used // for computing the opening proofs (hence the canonical form). The canonical version @@ -98,28 +117,24 @@ type ProvingKey struct { // The polynomials in trace are in canonical basis. trace Trace + // perf: this is quite fat; so we don't serialize it by default. + expandedTrace *ExpandedTrace + Kzg kzg.ProvingKey // Verifying Key is embedded into the proving key (needed by Prove) Vk *VerifyingKey - // qr,ql,qm,qo,qcp in LagrangeCoset --> these are not serialized, but computed from Ql, Qr, Qm, Qo, Qcp once. - lcQl, lcQr, lcQm, lcQo *iop.Polynomial - lcQcp []*iop.Polynomial + // qcp in LagrangeCoset form; not serialized + lcQcp []*iop.Polynomial // LQk qk in Lagrange form -> to be completed by the prover. After being completed, - lQk *iop.Polynomial + // lQk *iop.Polynomial // Domains used for the FFTs. // Domain[0] = small Domain // Domain[1] = big Domain Domain [2]fft.Domain - - // in lagrange coset basis --> these are not serialized, but computed from S1Canonical, S2Canonical, S3Canonical once. - lcS1, lcS2, lcS3 *iop.Polynomial - - // in lagrange coset basis --> not serialized id and L_{g^{0}} - lcIdIOP, lLoneIOP *iop.Polynomial } func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, error) { @@ -160,7 +175,6 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // All the above polynomials are expressed in canonical basis afterwards. This is why // we save lqk before, because the prover needs to complete it in Lagrange form, and // then express it on the Lagrange coset basis. - pk.lQk = pk.trace.Qk.Clone() // it will be completed by the prover, and the evaluated on the coset err := commitTrace(&pk.trace, &pk) if err != nil { return nil, nil, err @@ -177,65 +191,63 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // computeLagrangeCosetPolys computes each polynomial except qk in Lagrange coset // basis. Qk will be evaluated in Lagrange coset basis once it is completed by the prover. func (pk *ProvingKey) computeLagrangeCosetPolys() { - var wg sync.WaitGroup - wg.Add(7 + len(pk.trace.Qcp)) - n1 := int(pk.Domain[1].Cardinality) - pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) - for i, qcpI := range pk.trace.Qcp { - go func(i int, qcpI *iop.Polynomial) { - pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }(i, qcpI) + pk.expandedTrace = &ExpandedTrace{ + Polynomials: make([][sizeExpandedTrace]fr.Element, pk.Domain[1].Cardinality), } - go func() { - pk.lcQl = pk.trace.Ql.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQr = pk.trace.Qr.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQm = pk.trace.Qm.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQo = pk.trace.Qo.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS1 = pk.trace.S1.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS2 = pk.trace.S2.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + log := logger.Logger().With().Str("backend", "plonk").Logger() + start := time.Now() + + var wg sync.WaitGroup + wg.Add(8) + + // fillFunc converts p to LagrangeCoset and fills the pk.expandedTrace.Polynomials structure. + fillFunc := func(p *iop.Polynomial, idx int) { + scratch := make([]fr.Element, len(p.Coefficients()), pk.Domain[1].Cardinality) + copy(scratch, p.Coefficients()) + pp := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Canonical, Layout: iop.Regular}) + pp.SetSize(p.Size()) + pp.SetBlindedSize(p.BlindedSize()) + pp.ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx] = pp.GetCoeff(i) + } + wg.Done() - }() + } + + go fillFunc(pk.trace.Ql, idx_QL) + go fillFunc(pk.trace.Qr, idx_QR) + go fillFunc(pk.trace.Qm, idx_QM) + go fillFunc(pk.trace.Qo, idx_QO) + go fillFunc(pk.trace.S1, idx_S1) + go fillFunc(pk.trace.S2, idx_S2) + go fillFunc(pk.trace.S3, idx_S3) + go func() { - pk.lcS3 = pk.trace.S3.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + n1 := int(pk.Domain[1].Cardinality) + pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) + for i, qcpI := range pk.trace.Qcp { + pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]).ToRegular() + } wg.Done() }() - // storing Id - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - id := make([]fr.Element, pk.Domain[1].Cardinality) - id[0].Set(&pk.Domain[1].FrMultiplicativeGen) - for i := 1; i < int(pk.Domain[1].Cardinality); i++ { - id[i].Mul(&id[i-1], &pk.Domain[1].Generator) - } - pk.lcIdIOP = iop.NewPolynomial(&id, lagReg) // L_{g^{0}} - cap := pk.Domain[1].Cardinality - if cap < pk.Domain[0].Cardinality { - cap = pk.Domain[0].Cardinality // sanity check + scratch := make([]fr.Element, pk.Domain[0].Cardinality, pk.Domain[1].Cardinality) + scratch[0].SetOne() + + p := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Lagrange, Layout: iop.Regular}) + p.ToCanonical(&pk.Domain[0]).ToRegular().ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx_LONE] = p.GetCoeff(i) } - lone := make([]fr.Element, pk.Domain[0].Cardinality, cap) - lone[0].SetOne() - pk.lLoneIOP = iop.NewPolynomial(&lone, lagReg).ToCanonical(&pk.Domain[0]). - ToRegular(). - ToLagrangeCoset(&pk.Domain[1]) wg.Wait() + runtime.GC() + + log.Debug().Dur("computeLagrangeCosetPolys", time.Since(start)).Msg("setup done") } // NbPublicWitness returns the expected public witness size (number of field elements) diff --git a/backend/plonk/bw6-761/verify.go b/backend/plonk/bw6-761/verify.go index 51c0719b8d..2070c521ff 100644 --- a/backend/plonk/bw6-761/verify.go +++ b/backend/plonk/bw6-761/verify.go @@ -51,7 +51,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *vk, publicWitness, proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", vk, publicWitness, proof.Bsb22Commitments); err != nil { return err } gamma, err := deriveRandomness(&fs, "gamma", &proof.LRO[0], &proof.LRO[1], &proof.LRO[2]) @@ -270,7 +270,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { return err } -func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { +func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk *VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { // permutation if err := fs.Bind(challenge, vk.S[0].Marshal()); err != nil { diff --git a/go.mod b/go.mod index 82deb5fc39..11ede24ffe 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/bits-and-blooms/bitset v1.7.0 github.com/blang/semver/v4 v4.0.0 github.com/consensys/bavard v0.1.13 - github.com/consensys/gnark-crypto v0.11.1-0.20230724160225-800ddb59f51b + github.com/consensys/gnark-crypto v0.11.1-0.20230808213703-6d1518c44279 github.com/fxamacker/cbor/v2 v2.4.0 github.com/google/go-cmp v0.5.9 github.com/google/pprof v0.0.0-20230309165930-d61513b1440d @@ -17,7 +17,10 @@ require ( golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb ) -require github.com/felixge/fgprof v0.9.3 // indirect +require ( + github.com/felixge/fgprof v0.9.3 // indirect + golang.org/x/tools v0.6.0 // indirect +) require ( github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 6989d66760..bf844fe003 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.11.1-0.20230724160225-800ddb59f51b h1:tlxRLAcCOcdC3TcP5uqrKaK+OF7eBy1YB5AYUUhOayM= github.com/consensys/gnark-crypto v0.11.1-0.20230724160225-800ddb59f51b/go.mod h1:6C2ytC8zmP8uH2GKVfPOjf0Vw3KwMAaUxlCPK5WQqmw= +github.com/consensys/gnark-crypto v0.11.1-0.20230808213703-6d1518c44279 h1:fmqes05IozYFph10ZRpJ0XmMbECoL0/LoqBDz2PF7zE= +github.com/consensys/gnark-crypto v0.11.1-0.20230808213703-6d1518c44279/go.mod h1:6C2ytC8zmP8uH2GKVfPOjf0Vw3KwMAaUxlCPK5WQqmw= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -74,6 +76,8 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/internal/generator/backend/template/zkpschemes/plonk/plonk.marshal.go.tmpl b/internal/generator/backend/template/zkpschemes/plonk/plonk.marshal.go.tmpl index 6639c4434e..efec83e41e 100644 --- a/internal/generator/backend/template/zkpschemes/plonk/plonk.marshal.go.tmpl +++ b/internal/generator/backend/template/zkpschemes/plonk/plonk.marshal.go.tmpl @@ -136,7 +136,6 @@ func (pk *ProvingKey) writeTo(w io.Writer, withCompression bool) (n int64, err e pk.trace.Qo.Coefficients(), pk.trace.Qk.Coefficients(), coefficients(pk.trace.Qcp), - pk.lQk.Coefficients(), pk.trace.S1.Coefficients(), pk.trace.S2.Coefficients(), pk.trace.S3.Coefficients(), @@ -195,7 +194,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err dec := curve.NewDecoder(r) - var ql, qr, qm, qo, qk, lqk, s1, s2, s3 []fr.Element + var ql, qr, qm, qo, qk, s1, s2, s3 []fr.Element var qcp [][]fr.Element // TODO @gbotrel: this is a bit ugly, we should probably refactor this. @@ -211,16 +210,15 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err chErr chan error } - vectors := make([]v, 9) + vectors := make([]v, 8) vectors[0] = v{data: (*fr.Vector)(&ql)} vectors[1] = v{data: (*fr.Vector)(&qr)} vectors[2] = v{data: (*fr.Vector)(&qm)} vectors[3] = v{data: (*fr.Vector)(&qo)} vectors[4] = v{data: (*fr.Vector)(&qk)} - vectors[5] = v{data: (*fr.Vector)(&lqk)} - vectors[6] = v{data: (*fr.Vector)(&s1)} - vectors[7] = v{data: (*fr.Vector)(&s2)} - vectors[8] = v{data: (*fr.Vector)(&s3)} + vectors[5] = v{data: (*fr.Vector)(&s1)} + vectors[6] = v{data: (*fr.Vector)(&s2)} + vectors[7] = v{data: (*fr.Vector)(&s3)} // read ql, qr, qm, qo, qk for i := 0; i < 5; i++ { @@ -238,7 +236,7 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err } // read lqk, s1, s2, s3 - for i := 5; i < 9; i++ { + for i := 5; i < 8; i++ { n2, err, ch := vectors[i].data.AsyncReadFrom(r) n += n2 if err != nil { @@ -273,8 +271,6 @@ func (pk *ProvingKey) readFrom(r io.Reader, withSubgroupChecks bool) (int64, err for i := range qcp { pk.trace.Qcp[i] = iop.NewPolynomial(&qcp[i], canReg) } - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) // wait for FFT to be precomputed diff --git a/internal/generator/backend/template/zkpschemes/plonk/plonk.prove.go.tmpl b/internal/generator/backend/template/zkpschemes/plonk/plonk.prove.go.tmpl index def44c21ec..edf29cc6cd 100644 --- a/internal/generator/backend/template/zkpschemes/plonk/plonk.prove.go.tmpl +++ b/internal/generator/backend/template/zkpschemes/plonk/plonk.prove.go.tmpl @@ -4,6 +4,7 @@ import ( "runtime" "time" "sync" + "errors" "github.com/consensys/gnark/backend/witness" @@ -41,7 +42,6 @@ type Proof struct { // Opening proof of Z at zeta*mu ZShiftedOpening kzg.OpeningProof } - // Computing and verifying Bsb22 multi-commits explained in https://hackmd.io/x8KsadW3RRyX7YTCFJIkHg func bsb22ComputeCommitmentHint(spr *cs.SparseR1CS, pk *ProvingKey, proof *Proof, cCommitments []*iop.Polynomial, res *fr.Element, commDepth int) solver.Hint { return func(_ *big.Int, ins, outs []*big.Int) error { @@ -78,15 +78,19 @@ func bsb22ComputeCommitmentHint(spr *cs.SparseR1CS, pk *ProvingKey, proof *Proof } func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*Proof, error) { + log := logger.Logger().With(). + Str("curve", spr.CurveID().String()). + Int("nbConstraints", spr.GetNbConstraints()). + Str("backend", "plonk").Logger() - log := logger.Logger().With().Str("curve", spr.CurveID().String()).Int("nbConstraints", spr.GetNbConstraints()).Str("backend", "plonk").Logger() - + // parse the options opt, err := backend.NewProverConfig(opts...) if err != nil { return nil, err } start := time.Now() + // pick a hash function that will be used to derive the challenges hFunc := sha256.New() @@ -100,11 +104,14 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts commitmentVal := make([]fr.Element, len(commitmentInfo)) // TODO @Tabaie get rid of this cCommitments := make([]*iop.Polynomial, len(commitmentInfo)) proof.Bsb22Commitments = make([]kzg.Digest, len(commitmentInfo)) + + // override the hint for the commitment constraints for i := range commitmentInfo { opt.SolverOpts = append(opt.SolverOpts, solver.OverrideHint(commitmentInfo[i].HintID, bsb22ComputeCommitmentHint(spr, pk, proof, cCommitments, &commitmentVal[i], i))) } + // override the hint for GKR constraints if spr.GkrInfo.Is() { var gkrData cs.GkrSolvingData opt.SolverOpts = append(opt.SolverOpts, @@ -113,47 +120,45 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // query l, r, o in Lagrange basis, not blinded + lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} _solution, err := spr.Solve(fullWitness, opt.SolverOpts...) if err != nil { return nil, err } - // TODO @gbotrel deal with that conversion lazily - lcCommitments := make([]*iop.Polynomial, len(cCommitments)) - for i := range cCommitments { - lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]) // lagrange coset form - } solution := _solution.(*cs.SparseR1CSSolution) evaluationLDomainSmall := []fr.Element(solution.L) evaluationRDomainSmall := []fr.Element(solution.R) evaluationODomainSmall := []fr.Element(solution.O) + wliop := iop.NewPolynomial(&evaluationLDomainSmall, lagReg) + wriop := iop.NewPolynomial(&evaluationRDomainSmall, lagReg) + woiop := iop.NewPolynomial(&evaluationODomainSmall, lagReg) - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} + + // convert the commitment polynomials to LagrangeCoset basis + lcCommitments := make([]*iop.Polynomial, len(cCommitments)) + chLcc := make(chan struct{}, 1) + go func() { + for i := range cCommitments { + lcCommitments[i] = cCommitments[i].Clone(int(pk.Domain[1].Cardinality)).ToLagrangeCoset(&pk.Domain[1]).ToRegular() // lagrange coset form + } + close(chLcc) + }() + + // l, r, o and blinded versions - var ( - wliop, - wriop, - woiop, - bwliop, - bwriop, - bwoiop *iop.Polynomial - ) + var bwliop, bwriop, bwoiop *iop.Polynomial var wgLRO sync.WaitGroup wgLRO.Add(3) go func() { - // we keep in lagrange regular form since iop.BuildRatioCopyConstraint prefers it in this form. - wliop = iop.NewPolynomial(&evaluationLDomainSmall, lagReg) - // we set the underlying slice capacity to domain[1].Cardinality to minimize mem moves. - bwliop = wliop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwliop = wliop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - wriop = iop.NewPolynomial(&evaluationRDomainSmall, lagReg) - bwriop = wriop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwriop = wriop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() go func() { - woiop = iop.NewPolynomial(&evaluationODomainSmall, lagReg) - bwoiop = woiop.Clone(int(pk.Domain[1].Cardinality)).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) + bwoiop = woiop.Clone(int(pk.Domain[0].Cardinality) + 2).ToCanonical(&pk.Domain[0]).ToRegular().Blind(1) wgLRO.Done() }() @@ -167,27 +172,22 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chLcqk := make(chan struct{}, 1) go func() { // compute qk in canonical basis, completed with the public inputs - // We copy the coeffs of qk to pk is not mutated - lqkcoef := pk.lQk.Coefficients() - qkCompletedCanonical := make([]fr.Element, len(lqkcoef)) + // TODO @ThomasPiellard should we do ToLagrange or ToLagrangeCoset here? + lcqk = pk.trace.Qk.Clone(int(pk.Domain[1].Cardinality)).ToLagrange(&pk.Domain[0]).ToRegular() + qkCompletedCanonical := lcqk.Coefficients() copy(qkCompletedCanonical, fw[:len(spr.Public)]) - copy(qkCompletedCanonical[len(spr.Public):], lqkcoef[len(spr.Public):]) for i := range commitmentInfo { qkCompletedCanonical[spr.GetNbPublicVariables()+commitmentInfo[i].CommitmentIndex] = commitmentVal[i] } - pk.Domain[0].FFTInverse(qkCompletedCanonical, fft.DIF) - fft.BitReverse(qkCompletedCanonical) - - canReg := iop.Form{Basis: iop.Canonical, Layout: iop.Regular} - lcqk = iop.NewPolynomial(&qkCompletedCanonical, canReg) - lcqk.ToLagrangeCoset(&pk.Domain[1]) + lcqk.ToCanonical(&pk.Domain[0]).ToRegular(). + ToLagrangeCoset(&pk.Domain[1]).ToRegular() close(chLcqk) }() // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", pk.Vk, fw[:len(spr.Public)], proof.Bsb22Commitments); err != nil { return nil, err } @@ -212,22 +212,30 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts // l, r, o are already blinded wgLRO.Add(3) + + // compute l,r,o (blinded version) in LagrangeCoset basis. + // we keep the regular version in memory for the next step + var lcbwliop, lcbwriop, lcbwoiop *iop.Polynomial + go func() { - bwliop.ToLagrangeCoset(&pk.Domain[1]) + lcbwliop = bwliop.Clone(int(pk.Domain[1].Cardinality)) + lcbwliop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwriop.ToLagrangeCoset(&pk.Domain[1]) + lcbwriop = bwriop.Clone(int(pk.Domain[1].Cardinality)) + lcbwriop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() go func() { - bwoiop.ToLagrangeCoset(&pk.Domain[1]) + lcbwoiop = bwoiop.Clone(int(pk.Domain[1].Cardinality)) + lcbwoiop.ToLagrangeCoset(&pk.Domain[1]).ToRegular() wgLRO.Done() }() // compute the copy constraint's ratio // note that wliop, wriop and woiop are fft'ed (mutated) in the process. - ziop, err := iop.BuildRatioCopyConstraint( + bwziop, err := iop.BuildRatioCopyConstraint( []*iop.Polynomial{ wliop, wriop, @@ -242,13 +250,18 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts if err != nil { return proof, err } + // unused. + wliop = nil + wriop = nil + woiop = nil // commit to the blinded version of z chZ := make(chan error, 1) - var bwziop, bwsziop *iop.Polynomial + var bwsziop *iop.Polynomial var alpha fr.Element go func() { - bwziop = ziop // iop.NewWrappedPolynomial(&ziop) + // blind Z + // TODO @gbotrel memory wise we should allocate a bigger result for BuildRatioCopyConstraint bwziop.Blind(2) proof.Z, err = kzg.Commit(bwziop.Coefficients(), pk.Kzg, runtime.NumCPU()*2) if err != nil { @@ -261,37 +274,51 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts chZ <- err } - // Store z(g*x), without reallocating a slice - bwsziop = bwziop.ShallowClone().Shift(1) - bwsziop.ToLagrangeCoset(&pk.Domain[1]) + // Store z(g*x) + // perf note: converting ToRegular here perfoms better on Apple M1, but not on a hpc machine. + bwsziop = bwziop.Clone().ToLagrangeCoset(&pk.Domain[1])//.ToRegular() + chZ <- nil close(chZ) }() + // l , r , o, z, zs, qk , Bsb22Commitments, qCPrime + const ( + idx_L int = iota // bwliop + idx_R // bwriop + idx_O // bwoiop + idx_Z // bwsziop + idx_ZS // bwsziop shifted + idx_QK // lcqk + idx_Bsb22Commitments // lcCommitments ... pk.lcQcp... + ) + + // Full capture using latest gnark crypto... - fic := func(fql, fqr, fqm, fqo, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { // TODO @Tabaie make use of the fact that qCPrime is a selector: sparse and binary + fic := func(i int, fqk, l, r, o fr.Element, pi2QcPrime []fr.Element) fr.Element { var ic, tmp fr.Element - ic.Mul(&fql, &l) - tmp.Mul(&fqr, &r) + ic.Mul(&pk.expandedTrace.Polynomials[i][idx_QL], &l) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QR], &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqm, &l).Mul(&tmp, &r) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QM], &l).Mul(&tmp, &r) ic.Add(&ic, &tmp) - tmp.Mul(&fqo, &o) + tmp.Mul(&pk.expandedTrace.Polynomials[i][idx_QO], &o) ic.Add(&ic, &tmp).Add(&ic, &fqk) nbComms := len(commitmentInfo) - for i := range commitmentInfo { - tmp.Mul(&pi2QcPrime[i], &pi2QcPrime[i+nbComms]) + for j := range commitmentInfo { + tmp.Mul(&pi2QcPrime[j], &pi2QcPrime[j+nbComms]) ic.Add(&ic, &tmp) } return ic } - fo := func(l, r, o, fid, fs1, fs2, fs3, fz, fzs fr.Element) fr.Element { + fo := func(i int, fid, l, r, o, fz, fzs fr.Element) fr.Element { u := &pk.Domain[0].FrMultiplicativeGen var a, b, tmp fr.Element + b.Mul(&beta, &fid) a.Add(&b, &l).Add(&a, &gamma) b.Mul(&b, u) @@ -300,10 +327,10 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts tmp.Mul(&b, u).Add(&tmp, &o).Add(&tmp, &gamma) a.Mul(&a, &tmp).Mul(&a, &fz) - b.Mul(&beta, &fs1).Add(&b, &l).Add(&b, &gamma) - tmp.Mul(&beta, &fs2).Add(&tmp, &r).Add(&tmp, &gamma) + b.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S1]).Add(&b, &l).Add(&b, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S2]).Add(&tmp, &r).Add(&tmp, &gamma) b.Mul(&b, &tmp) - tmp.Mul(&beta, &fs3).Add(&tmp, &o).Add(&tmp, &gamma) + tmp.Mul(&beta, &pk.expandedTrace.Polynomials[i][idx_S3]).Add(&tmp, &o).Add(&tmp, &gamma) b.Mul(&b, &tmp).Mul(&b, &fzs) b.Sub(&b, &a) @@ -311,19 +338,16 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts return b } - fone := func(fz, flone fr.Element) fr.Element { + fone := func(i int, fz fr.Element) fr.Element { one := fr.One() - one.Sub(&fz, &one).Mul(&one, &flone) + one.Sub(&fz, &one).Mul(&one, &pk.expandedTrace.Polynomials[i][idx_LONE]) return one } - // 0 , 1 , 2, 3 , 4 , 5 , 6 , 7, 8 , 9 , 10, 11, 12, 13, 14, 15:15+nbComm , 15+nbComm:15+2×nbComm - // l , r , o, id, s1, s2, s3, z, zs, ql, qr, qm, qo, qk ,lone, Bsb22Commitments, qCPrime - fm := func(x ...fr.Element) fr.Element { - - a := fic(x[9], x[10], x[11], x[12], x[13], x[0], x[1], x[2], x[15:]) - b := fo(x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8]) - c := fone(x[7], x[14]) + fm := func(i int, fid fr.Element, x ...fr.Element) fr.Element { + a := fic(i, x[idx_QK], x[idx_L], x[idx_R], x[idx_O], x[idx_Bsb22Commitments:]) + b := fo(i, fid, x[idx_L], x[idx_R], x[idx_O], x[idx_Z], x[idx_ZS]) + c := fone(i, x[idx_Z]) c.Mul(&c, &alpha).Add(&c, &b).Mul(&c, &alpha).Add(&c, &a) @@ -339,39 +363,37 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts } // wait for l, r o lagrange coset conversion + <-chLcc wgLRO.Wait() - - toEval := []*iop.Polynomial{ - bwliop, - bwriop, - bwoiop, - pk.lcIdIOP, - pk.lcS1, - pk.lcS2, - pk.lcS3, - bwziop, - bwsziop, - pk.lcQl, - pk.lcQr, - pk.lcQm, - pk.lcQo, - lcqk, - pk.lLoneIOP, - } - toEval = append(toEval, lcCommitments...) // TODO: Add this at beginning - toEval = append(toEval, pk.lcQcp...) - systemEvaluation, err := iop.Evaluate(fm, iop.Form{Basis: iop.LagrangeCoset, Layout: iop.BitReverse}, toEval...) + toEval := make([]*iop.Polynomial, 6+2*len(commitmentInfo)) + toEval[idx_L] = lcbwliop + toEval[idx_R] = lcbwriop + toEval[idx_O] = lcbwoiop + toEval[idx_Z] = bwsziop.ShallowClone() + toEval[idx_ZS] = bwsziop.Shift(1) + toEval[idx_QK] = lcqk + copy(toEval[idx_Bsb22Commitments:], lcCommitments) + copy(toEval[idx_Bsb22Commitments+len(lcCommitments):], pk.lcQcp) + + // systemEvaluation reuses lcqk for memory. + systemEvaluation, err := evaluate(lcqk, pk, fm, toEval...) if err != nil { return nil, err } - // open blinded Z at zeta*z - chbwzIOP := make(chan struct{}, 1) - go func() { - bwziop.ToCanonical(&pk.Domain[1]).ToRegular() - close(chbwzIOP) - }() - h, err := iop.DivideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc + toEval = nil + bwsziop = nil + lcqk = nil + lcbwliop= nil + lcbwriop = nil + lcbwoiop = nil + + for i := range lcCommitments { + lcCommitments[i] = nil + } + + + h, err := divideByXMinusOne(systemEvaluation, [2]*fft.Domain{&pk.Domain[0], &pk.Domain[1]}) // TODO Rename to DivideByXNMinusOne or DivideByVanishingPoly etc if err != nil { return nil, err } @@ -398,7 +420,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var wgEvals sync.WaitGroup wgEvals.Add(3) evalAtZeta := func(poly *iop.Polynomial, res *fr.Element) { - poly.ToCanonical(&pk.Domain[1]).ToRegular() *res = poly.Evaluate(zeta) wgEvals.Done() } @@ -414,7 +435,6 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts var zetaShifted fr.Element zetaShifted.Mul(&zeta, &pk.Vk.Generator) - <-chbwzIOP proof.ZShiftedOpening, err = kzg.Open( bwziop.Coefficients()[:bwziop.BlindedSize()], zetaShifted, @@ -446,11 +466,12 @@ func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts h2 := h.Coefficients()[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] h1 := h.Coefficients()[:pk.Domain[0].Cardinality+2] utils.Parallelize(len(foldedH), func(start, end int) { + var t fr.Element for i := start; i < end; i++ { - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 - foldedH[i].Add(&foldedH[i], &h2[i]) // ζ^{m+2)*h3+h2 - foldedH[i].Mul(&foldedH[i], &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² - foldedH[i].Add(&foldedH[i], &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 + t.Mul(&foldedH[i], &zetaPowerm) // ζᵐ⁺²*h3 + t.Add(&t, &h2[i]) // ζ^{m+2)*h3+h2 + t.Mul(&t, &zetaPowerm) // ζ²⁽ᵐ⁺²⁾*h3+h2*ζᵐ⁺² + foldedH[i].Add(&t, &h1[i]) // ζ^{2(m+2)*h3+ζᵐ⁺²*h2 + h1 } }) close(computeFoldedH) @@ -657,8 +678,15 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, Mul(&lagrangeZeta, &pk.Domain[0].CardinalityInv) // (1/n)*α²*L₁(ζ) s3canonical := pk.trace.S3.Coefficients() + utils.Parallelize(len(blindedZCanonical), func(start, end int) { + cql := pk.trace.Ql.Coefficients() + cqr := pk.trace.Qr.Coefficients() + cqm := pk.trace.Qm.Coefficients() + cqo := pk.trace.Qo.Coefficients() + cqk := pk.trace.Qk.Coefficients() + var t, t0, t1 fr.Element for i := start; i < end; i++ { @@ -674,22 +702,22 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, t.Mul(&t, &alpha) // α*( (l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*Z(μζ)*s3(X) - Z(X)*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ)) - cql := pk.trace.Ql.Coefficients() - cqr := pk.trace.Qr.Coefficients() - cqm := pk.trace.Qm.Coefficients() - cqo := pk.trace.Qo.Coefficients() - cqk := pk.trace.Qk.Coefficients() if i < len(cqm) { t1.Mul(&cqm[i], &rl) // linPol = linPol + l(ζ)r(ζ)*Qm(X) + t0.Mul(&cql[i], &lZeta) t0.Add(&t0, &t1) + t.Add(&t, &t0) // linPol = linPol + l(ζ)*Ql(X) t0.Mul(&cqr[i], &rZeta) t.Add(&t, &t0) // linPol = linPol + r(ζ)*Qr(X) - t0.Mul(&cqo[i], &oZeta).Add(&t0, &cqk[i]) + + t0.Mul(&cqo[i], &oZeta) + t0.Add(&t0, &cqk[i]) + t.Add(&t, &t0) // linPol = linPol + o(ζ)*Qo(X) + Qk(X) for j := range qcpZeta { @@ -704,3 +732,124 @@ func computeLinearizedPolynomial(lZeta, rZeta, oZeta, alpha, beta, gamma, zeta, }) return blindedZCanonical } + + +// TODO @gbotrel these changes below should be merge upstream in gnark-crypto + + +// expression represents a multivariate polynomial. +type expression func(i int, fid fr.Element, x ...fr.Element) fr.Element + +// Evaluate evaluates f on each entry of x. The returned value is +// the vector of evaluations of e on x. +// The form of the result is LagrangeCoset Regular. +// The Size field of the result is the same as the one of x[0]. +// The blindedSize field of the result is the same as Size. +// The Shift field of the result is 0. +// The result re-uses result memory space. +func evaluate(result *iop.Polynomial, pk *ProvingKey, f expression, x ...*iop.Polynomial) (*iop.Polynomial, error) { + if len(x) == 0 { + return nil, errors.New("need at lest one input") + } + + // check that the sizes are consistent + n := len(x[0].Coefficients()) + m := len(x) + for i := 1; i < m; i++ { + if n != len(x[i].Coefficients()) { + return nil, errors.New("inconsistent sizes") + } + } + + // result coefficients + r := result.Coefficients() + if len(r) != n { + return nil, errors.New("inconsistent sizes") + } + + utils.Parallelize(n, func(start, end int) { + // inputs to the expression we will evaluate + vx := make([]fr.Element, m) + generator := pk.Domain[1].Generator + + // we inject id polynomial + var fid fr.Element + fid.Exp(generator, big.NewInt(int64(start))) + fid.Mul(&fid, &pk.Domain[1].FrMultiplicativeGen) + + for i := start; i < end; i++ { + for j := 0; j < m; j++ { + vx[j] = x[j].GetCoeff(i) + } + r[i] = f(i, fid, vx...) + fid.Mul(&fid, &generator) + } + }) + + res := iop.NewPolynomial(&r, iop.Form{Layout: iop.Regular, Basis: iop.LagrangeCoset}) + res.SetSize(x[0].Size()) + res.SetBlindedSize(x[0].Size()) + + + return res, nil +} + + + + +// divideByXMinusOne +// 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) { + + // check that the basis is LagrangeCoset + if a.Basis != iop.LagrangeCoset { + return nil, errors.New("invalid form") + } + + // prepare the evaluations of x^n-1 on the big domain's coset + xnMinusOneInverseLagrangeCoset := evaluateXnMinusOneDomainBigCoset(domains) + + nbElmts := len(a.Coefficients()) + rho := nbElmts / a.Size() + + r := a.Coefficients() + + utils.Parallelize(nbElmts, func(start, end int) { + for i := start; i < end; i++ { + r[i].Mul(&r[i], &xnMinusOneInverseLagrangeCoset[i%rho]) + } + }) + + // TODO @gbotrel this is the only place we do a FFT inverse (on coset) with domain[1] + a.ToCanonical(domains[1]).ToRegular() + + return a, nil + +} + +// evaluateXnMinusOneDomainBigCoset evaluates Xᵐ-1 on DomainBig coset +func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { + + ratio := domains[1].Cardinality / domains[0].Cardinality + + res := make([]fr.Element, ratio) + + expo := big.NewInt(int64(domains[0].Cardinality)) + res[0].Exp(domains[1].FrMultiplicativeGen, expo) + + var t fr.Element + t.Exp(domains[1].Generator, big.NewInt(int64(domains[0].Cardinality))) + + one := fr.One() + + for i := 1; i < int(ratio); i++ { + res[i].Mul(&res[i-1], &t) + res[i-1].Sub(&res[i-1], &one) + } + res[len(res)-1].Sub(&res[len(res)-1], &one) + + res = fr.BatchInvert(res) + + return res +} 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 d2f6a0ed60..a356cc6e61 100644 --- a/internal/generator/backend/template/zkpschemes/plonk/plonk.setup.go.tmpl +++ b/internal/generator/backend/template/zkpschemes/plonk/plonk.setup.go.tmpl @@ -8,38 +8,18 @@ import ( "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark/backend/plonk/internal" "github.com/consensys/gnark/constraint" + "github.com/consensys/gnark/logger" + "runtime" + "time" "sync" ) -// Trace stores a plonk trace as columns -type Trace struct { - - // Constants describing a plonk circuit. The first entries - // of LQk (whose index correspond to the public inputs) are set to 0, and are to be - // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 - // so the first nb_public_variables constraints look like this: - // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. - Ql, Qr, Qm, Qo, Qk *iop.Polynomial - Qcp []*iop.Polynomial - - // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. - // The set of interpolation is of size N, so to represent the permutation S we let S acts on the - // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). - // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are - // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . - S1, S2, S3 *iop.Polynomial - - // S full permutation, i -> S[i] - S []int64 -} - // VerifyingKey stores the data needed to verify a proof: // * The commitment scheme // * Commitments of ql prepended with as many ones as there are public inputs // * Commitments of qr, qm, qo, qk prepended with as many zeroes as there are public inputs // * Commitments to S1, S2, S3 type VerifyingKey struct { - // Size circuit Size uint64 SizeInv fr.Element @@ -63,6 +43,47 @@ type VerifyingKey struct { CommitmentConstraintIndexes []uint64 } + +// Trace stores a plonk trace as columns +type Trace struct { + // Constants describing a plonk circuit. The first entries + // of LQk (whose index correspond to the public inputs) are set to 0, and are to be + // completed by the prover. At those indices i (so from 0 to nb_public_variables), LQl[i]=-1 + // so the first nb_public_variables constraints look like this: + // -1*Wire[i] + 0* + 0 . It is zero when the constant coefficient is replaced by Wire[i]. + Ql, Qr, Qm, Qo, Qk *iop.Polynomial + Qcp []*iop.Polynomial + + // Polynomials representing the splitted permutation. The full permutation's support is 3*N where N=nb wires. + // The set of interpolation is of size N, so to represent the permutation S we let S acts on the + // set A=(, u*, u^{2}*) of size 3*N, where u is outside (its use is to shift the set ). + // We obtain a permutation of A, A'. We split A' in 3 (A'_{1}, A'_{2}, A'_{3}), and S1, S2, S3 are + // respectively the interpolation of A'_{1}, A'_{2}, A'_{3} on . + S1, S2, S3 *iop.Polynomial + + // S full permutation, i -> S[i] + S []int64 +} + +// ExpandedTrace stores Ql, Qr, Qm, Qo, Qk, Qcp, S1, S2, S3 in LagrangeCoset form; +// the expanded trace is stored in a memory layout that is optimized for the prover +type ExpandedTrace struct { + Polynomials [][sizeExpandedTrace]fr.Element +} + +// Enum for the expanded trace indexes. +const ( + idx_QL int = iota + idx_QR + idx_QM + idx_QO + idx_S1 + idx_S2 + idx_S3 + idx_LONE + sizeExpandedTrace +) + // ProvingKey stores the data needed to generate a proof: // * the commitment scheme // * ql, prepended with as many ones as they are public inputs @@ -72,7 +93,6 @@ type VerifyingKey struct { // * sigma_1, sigma_2, sigma_3 in both basis // * the copy constraint permutation type ProvingKey struct { - // stores ql, qr, qm, qo, qk (-> to be completed by the prover) // and s1, s2, s3. They are set in canonical basis before generating the proof, they will be used // for computing the opening proofs (hence the canonical form). The canonical version @@ -80,30 +100,28 @@ type ProvingKey struct { // The polynomials in trace are in canonical basis. trace Trace + // perf: this is quite fat; so we don't serialize it by default. + expandedTrace *ExpandedTrace + Kzg kzg.ProvingKey // Verifying Key is embedded into the proving key (needed by Prove) Vk *VerifyingKey - // qr,ql,qm,qo,qcp in LagrangeCoset --> these are not serialized, but computed from Ql, Qr, Qm, Qo, Qcp once. - lcQl, lcQr, lcQm, lcQo *iop.Polynomial + // qcp in LagrangeCoset form; not serialized lcQcp []*iop.Polynomial + // LQk qk in Lagrange form -> to be completed by the prover. After being completed, - lQk *iop.Polynomial + // lQk *iop.Polynomial // Domains used for the FFTs. // Domain[0] = small Domain // Domain[1] = big Domain Domain [2]fft.Domain - - // in lagrange coset basis --> these are not serialized, but computed from S1Canonical, S2Canonical, S3Canonical once. - lcS1, lcS2, lcS3 *iop.Polynomial - - // in lagrange coset basis --> not serialized id and L_{g^{0}} - lcIdIOP, lLoneIOP *iop.Polynomial } + func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, error) { var pk ProvingKey @@ -142,7 +160,6 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // All the above polynomials are expressed in canonical basis afterwards. This is why // we save lqk before, because the prover needs to complete it in Lagrange form, and // then express it on the Lagrange coset basis. - pk.lQk = pk.trace.Qk.Clone() // it will be completed by the prover, and the evaluated on the coset err := commitTrace(&pk.trace, &pk) if err != nil { return nil, nil, err @@ -159,65 +176,63 @@ func Setup(spr *cs.SparseR1CS, kzgSrs kzg.SRS) (*ProvingKey, *VerifyingKey, erro // computeLagrangeCosetPolys computes each polynomial except qk in Lagrange coset // basis. Qk will be evaluated in Lagrange coset basis once it is completed by the prover. func (pk *ProvingKey) computeLagrangeCosetPolys() { + pk.expandedTrace = &ExpandedTrace{ + Polynomials: make([][sizeExpandedTrace]fr.Element, pk.Domain[1].Cardinality), + } + log := logger.Logger().With().Str("backend", "plonk").Logger() + start := time.Now() + var wg sync.WaitGroup - wg.Add(7 + len(pk.trace.Qcp)) - n1 := int(pk.Domain[1].Cardinality) - pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) - for i, qcpI := range pk.trace.Qcp { - go func(i int, qcpI *iop.Polynomial) { - pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }(i, qcpI) + wg.Add(8) + + // fillFunc converts p to LagrangeCoset and fills the pk.expandedTrace.Polynomials structure. + fillFunc := func(p *iop.Polynomial, idx int) { + scratch := make([]fr.Element,len(p.Coefficients()), pk.Domain[1].Cardinality) + copy(scratch, p.Coefficients()) + pp := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Canonical, Layout: iop.Regular}) + pp.SetSize(p.Size()) + pp.SetBlindedSize(p.BlindedSize()) + pp.ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx] = pp.GetCoeff(i) + } + + wg.Done() } + + go fillFunc(pk.trace.Ql, idx_QL) + go fillFunc(pk.trace.Qr, idx_QR) + go fillFunc(pk.trace.Qm, idx_QM) + go fillFunc(pk.trace.Qo, idx_QO) + go fillFunc(pk.trace.S1, idx_S1) + go fillFunc(pk.trace.S2, idx_S2) + go fillFunc(pk.trace.S3, idx_S3) + go func() { - pk.lcQl = pk.trace.Ql.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) + n1 := int(pk.Domain[1].Cardinality) + pk.lcQcp = make([]*iop.Polynomial, len(pk.trace.Qcp)) + for i, qcpI := range pk.trace.Qcp { + pk.lcQcp[i] = qcpI.Clone(n1).ToLagrangeCoset(&pk.Domain[1]).ToRegular() + } wg.Done() }() - go func() { - pk.lcQr = pk.trace.Qr.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQm = pk.trace.Qm.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcQo = pk.trace.Qo.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS1 = pk.trace.S1.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS2 = pk.trace.S2.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - go func() { - pk.lcS3 = pk.trace.S3.Clone(n1).ToLagrangeCoset(&pk.Domain[1]) - wg.Done() - }() - // storing Id - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - id := make([]fr.Element, pk.Domain[1].Cardinality) - id[0].Set(&pk.Domain[1].FrMultiplicativeGen) - for i := 1; i < int(pk.Domain[1].Cardinality); i++ { - id[i].Mul(&id[i-1], &pk.Domain[1].Generator) - } - pk.lcIdIOP = iop.NewPolynomial(&id, lagReg) // L_{g^{0}} - cap := pk.Domain[1].Cardinality - if cap < pk.Domain[0].Cardinality { - cap = pk.Domain[0].Cardinality // sanity check + scratch := make([]fr.Element, pk.Domain[0].Cardinality, pk.Domain[1].Cardinality) + scratch[0].SetOne() + + p := iop.NewPolynomial(&scratch, iop.Form{Basis: iop.Lagrange, Layout: iop.Regular}) + p.ToCanonical(&pk.Domain[0]).ToRegular().ToLagrangeCoset(&pk.Domain[1]).ToRegular() + + for i := 0; i < int(pk.Domain[1].Cardinality); i++ { + pk.expandedTrace.Polynomials[i][idx_LONE] = p.GetCoeff(i) } - lone := make([]fr.Element, pk.Domain[0].Cardinality, cap) - lone[0].SetOne() - pk.lLoneIOP = iop.NewPolynomial(&lone, lagReg).ToCanonical(&pk.Domain[0]). - ToRegular(). - ToLagrangeCoset(&pk.Domain[1]) - wg.Wait() + wg.Wait() + runtime.GC() + + log.Debug().Dur("computeLagrangeCosetPolys", time.Since(start)).Msg("setup done") } @@ -269,6 +284,7 @@ func BuildTrace(spr *cs.SparseR1CS, pt *Trace) { j++ } + lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} pt.Ql = iop.NewPolynomial(&ql, lagReg) 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 43e5bd3c6f..75450c53d7 100644 --- a/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl +++ b/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl @@ -35,7 +35,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { // The first challenge is derived using the public data: the commitments to the permutation, // the coefficients of the circuit, and the public inputs. // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) - if err := bindPublicData(&fs, "gamma", *vk, publicWitness, proof.Bsb22Commitments); err != nil { + if err := bindPublicData(&fs, "gamma", vk, publicWitness, proof.Bsb22Commitments); err != nil { return err } gamma, err := deriveRandomness(&fs, "gamma", &proof.LRO[0], &proof.LRO[1], &proof.LRO[2]) @@ -254,7 +254,7 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector) error { return err } -func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { +func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk *VerifyingKey, publicInputs []fr.Element, pi2 []kzg.Digest) error { // permutation if err := fs.Bind(challenge, vk.S[0].Marshal()); err != nil { diff --git a/internal/generator/backend/template/zkpschemes/plonk/tests/marshal.go.tmpl b/internal/generator/backend/template/zkpschemes/plonk/tests/marshal.go.tmpl index 138cef204b..c95eb8c477 100644 --- a/internal/generator/backend/template/zkpschemes/plonk/tests/marshal.go.tmpl +++ b/internal/generator/backend/template/zkpschemes/plonk/tests/marshal.go.tmpl @@ -147,7 +147,6 @@ func (pk *ProvingKey) randomize() { qm := randomScalars(n) qo := randomScalars(n) qk := randomScalars(n) - lqk := randomScalars(n) s1 := randomScalars(n) s2 := randomScalars(n) s3 := randomScalars(n) @@ -172,8 +171,6 @@ func (pk *ProvingKey) randomize() { pk.trace.S[0] = -12 pk.trace.S[len(pk.trace.S)-1] = 8888 - lagReg := iop.Form{Basis: iop.Lagrange, Layout: iop.Regular} - pk.lQk = iop.NewPolynomial(&lqk, lagReg) pk.computeLagrangeCosetPolys() }