From 94124b6992e81797d050e31b34f75c25a3eb1b72 Mon Sep 17 00:00:00 2001 From: Gautam Botrel Date: Fri, 8 Mar 2024 09:43:49 -0600 Subject: [PATCH] Revert "refactor: kill backend.PLONK_FRI" This reverts commit e7885c38533bd388fc613d28ea3034cfbb5c46e5. --- backend/backend.go | 5 +- backend/plonkfri/bls12-377/prove.go | 701 +++++++++++++++++++++++++++ backend/plonkfri/bls12-377/setup.go | 423 ++++++++++++++++ backend/plonkfri/bls12-377/verify.go | 398 +++++++++++++++ backend/plonkfri/bls12-381/prove.go | 701 +++++++++++++++++++++++++++ backend/plonkfri/bls12-381/setup.go | 423 ++++++++++++++++ backend/plonkfri/bls12-381/verify.go | 398 +++++++++++++++ backend/plonkfri/bls24-315/prove.go | 701 +++++++++++++++++++++++++++ backend/plonkfri/bls24-315/setup.go | 423 ++++++++++++++++ backend/plonkfri/bls24-315/verify.go | 398 +++++++++++++++ backend/plonkfri/bls24-317/prove.go | 701 +++++++++++++++++++++++++++ backend/plonkfri/bls24-317/setup.go | 423 ++++++++++++++++ backend/plonkfri/bls24-317/verify.go | 398 +++++++++++++++ backend/plonkfri/bn254/prove.go | 701 +++++++++++++++++++++++++++ backend/plonkfri/bn254/setup.go | 423 ++++++++++++++++ backend/plonkfri/bn254/verify.go | 398 +++++++++++++++ backend/plonkfri/bw6-633/prove.go | 701 +++++++++++++++++++++++++++ backend/plonkfri/bw6-633/setup.go | 423 ++++++++++++++++ backend/plonkfri/bw6-633/verify.go | 398 +++++++++++++++ backend/plonkfri/bw6-761/prove.go | 701 +++++++++++++++++++++++++++ backend/plonkfri/bw6-761/setup.go | 423 ++++++++++++++++ backend/plonkfri/bw6-761/verify.go | 398 +++++++++++++++ backend/plonkfri/plonkfri.go | 191 ++++++++ integration_test.go | 5 + internal/generator/backend/main.go | 14 + internal/stats/stats.go | 6 +- test/assert.go | 2 + test/assert_checkcircuit.go | 19 + 28 files changed, 10892 insertions(+), 4 deletions(-) create mode 100644 backend/plonkfri/bls12-377/prove.go create mode 100644 backend/plonkfri/bls12-377/setup.go create mode 100644 backend/plonkfri/bls12-377/verify.go create mode 100644 backend/plonkfri/bls12-381/prove.go create mode 100644 backend/plonkfri/bls12-381/setup.go create mode 100644 backend/plonkfri/bls12-381/verify.go create mode 100644 backend/plonkfri/bls24-315/prove.go create mode 100644 backend/plonkfri/bls24-315/setup.go create mode 100644 backend/plonkfri/bls24-315/verify.go create mode 100644 backend/plonkfri/bls24-317/prove.go create mode 100644 backend/plonkfri/bls24-317/setup.go create mode 100644 backend/plonkfri/bls24-317/verify.go create mode 100644 backend/plonkfri/bn254/prove.go create mode 100644 backend/plonkfri/bn254/setup.go create mode 100644 backend/plonkfri/bn254/verify.go create mode 100644 backend/plonkfri/bw6-633/prove.go create mode 100644 backend/plonkfri/bw6-633/setup.go create mode 100644 backend/plonkfri/bw6-633/verify.go create mode 100644 backend/plonkfri/bw6-761/prove.go create mode 100644 backend/plonkfri/bw6-761/setup.go create mode 100644 backend/plonkfri/bw6-761/verify.go create mode 100644 backend/plonkfri/plonkfri.go diff --git a/backend/backend.go b/backend/backend.go index 7ee17d6793..50f0ab15e5 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -29,11 +29,12 @@ const ( UNKNOWN ID = iota GROTH16 PLONK + PLONKFRI ) // Implemented return the list of proof systems implemented in gnark func Implemented() []ID { - return []ID{GROTH16, PLONK} + return []ID{GROTH16, PLONK, PLONKFRI} } // String returns the string representation of a proof system @@ -43,6 +44,8 @@ func (id ID) String() string { return "groth16" case PLONK: return "plonk" + case PLONKFRI: + return "plonkFRI" default: return "unknown" } diff --git a/backend/plonkfri/bls12-377/prove.go b/backend/plonkfri/bls12-377/prove.go new file mode 100644 index 0000000000..82144641b2 --- /dev/null +++ b/backend/plonkfri/bls12-377/prove.go @@ -0,0 +1,701 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by gnark DO NOT EDIT + +package plonkfri + +import ( + "math/big" + "math/bits" + "runtime" + + "github.com/consensys/gnark/backend/witness" + + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/fft" + + cs "github.com/consensys/gnark/constraint/bls12-377" + + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/fri" + + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/internal/utils" +) + +type Proof struct { + // commitments to the solution vectors + LROpp [3]fri.ProofOfProximity + + // commitment to Z (permutation polynomial) + // Z Commitment + Zpp fri.ProofOfProximity + + // commitment to h1,h2,h3 such that h = h1 + X**n*h2 + X**2nh3 the quotient polynomial + Hpp [3]fri.ProofOfProximity + + // opening proofs for L, R, O + OpeningsLROmp [3]fri.OpeningProof + + // opening proofs for Z, Zu + OpeningsZmp [2]fri.OpeningProof + + // opening proof for H + OpeningsHmp [3]fri.OpeningProof + + // opening proofs for ql, qr, qm, qo, qk + OpeningsQlQrQmQoQkincompletemp [5]fri.OpeningProof + + // openings of S1, S2, S3 + // OpeningsS1S2S3 [3]OpeningProof + OpeningsS1S2S3mp [3]fri.OpeningProof + + // openings of Id1, Id2, Id3 + OpeningsId1Id2Id3mp [3]fri.OpeningProof +} + +func Prove(spr *cs.SparseR1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*Proof, error) { + opt, err := backend.NewProverConfig(opts...) + if err != nil { + return nil, err + } + + var proof Proof + + // 0 - Fiat Shamir + fs := fiatshamir.NewTranscript(opt.ChallengeHash, "gamma", "beta", "alpha", "zeta") + + // 1 - solve the system + _solution, err := spr.Solve(fullWitness, opt.SolverOpts...) + if err != nil { + return nil, err + } + + solution := _solution.(*cs.SparseR1CSSolution) + evaluationLDomainSmall := []fr.Element(solution.L) + evaluationRDomainSmall := []fr.Element(solution.R) + evaluationODomainSmall := []fr.Element(solution.O) + + // 2 - commit to lro + blindedLCanonical, blindedRCanonical, blindedOCanonical, err := computeBlindedLROCanonical( + evaluationLDomainSmall, + evaluationRDomainSmall, + evaluationODomainSmall, + &pk.Domain[0]) + if err != nil { + return nil, err + } + proof.LROpp[0], err = pk.Vk.Iopp.BuildProofOfProximity(blindedLCanonical) + if err != nil { + return nil, err + } + proof.LROpp[1], err = pk.Vk.Iopp.BuildProofOfProximity(blindedRCanonical) + if err != nil { + return nil, err + } + proof.LROpp[2], err = pk.Vk.Iopp.BuildProofOfProximity(blindedOCanonical) + if err != nil { + return nil, err + } + + // 3 - compute Z, challenges are derived using L, R, O + public inputs + fw, ok := fullWitness.Vector().(fr.Vector) + if !ok { + return nil, witness.ErrInvalidWitness + } + dataFiatShamir := make([][fr.Bytes]byte, len(spr.Public)+3) + for i := 0; i < len(spr.Public); i++ { + copy(dataFiatShamir[i][:], fw[i].Marshal()) + } + copy(dataFiatShamir[len(spr.Public)][:], proof.LROpp[0].ID) + copy(dataFiatShamir[len(spr.Public)+1][:], proof.LROpp[1].ID) + copy(dataFiatShamir[len(spr.Public)+2][:], proof.LROpp[2].ID) + + beta, err := deriveRandomnessFixedSize(fs, "gamma", dataFiatShamir...) + if err != nil { + return nil, err + } + + gamma, err := deriveRandomness(fs, "beta", nil) + if err != nil { + return nil, err + } + + //var beta, gamma fr.Element + //beta.SetUint64(9) + // gamma.SetString("10") + blindedZCanonical, err := computeBlindedZCanonical( + evaluationLDomainSmall, + evaluationRDomainSmall, + evaluationODomainSmall, + pk, beta, gamma) + if err != nil { + return nil, err + } + + // 4 - commit Z + proof.Zpp, err = pk.Vk.Iopp.BuildProofOfProximity(blindedZCanonical) + if err != nil { + return nil, err + } + + // 5 - compute H + // var alpha fr.Element + alpha, err := deriveRandomness(fs, "alpha", proof.Zpp.ID) + if err != nil { + return nil, err + } + // alpha.SetUint64(11) + + evaluationQkCompleteDomainBigBitReversed := make([]fr.Element, pk.Domain[1].Cardinality) + copy(evaluationQkCompleteDomainBigBitReversed, fw[:len(spr.Public)]) + copy(evaluationQkCompleteDomainBigBitReversed[len(spr.Public):], pk.LQkIncompleteDomainSmall[len(spr.Public):]) + pk.Domain[0].FFTInverse(evaluationQkCompleteDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(evaluationQkCompleteDomainBigBitReversed[:pk.Domain[0].Cardinality]) + + evaluationQkCompleteDomainBigBitReversed = fftBigCosetWOBitReverse(evaluationQkCompleteDomainBigBitReversed, &pk.Domain[1]) + + evaluationBlindedLDomainBigBitReversed := fftBigCosetWOBitReverse(blindedLCanonical, &pk.Domain[1]) + evaluationBlindedRDomainBigBitReversed := fftBigCosetWOBitReverse(blindedRCanonical, &pk.Domain[1]) + evaluationBlindedODomainBigBitReversed := fftBigCosetWOBitReverse(blindedOCanonical, &pk.Domain[1]) + + evaluationConstraintsDomainBigBitReversed := evalConstraintsInd( + pk, + evaluationBlindedLDomainBigBitReversed, + evaluationBlindedRDomainBigBitReversed, + evaluationBlindedODomainBigBitReversed, + evaluationQkCompleteDomainBigBitReversed) + + evaluationBlindedZDomainBigBitReversed := fftBigCosetWOBitReverse(blindedZCanonical, &pk.Domain[1]) + + evaluationOrderingDomainBigBitReversed := evaluateOrderingDomainBigBitReversed( + pk, + evaluationBlindedZDomainBigBitReversed, + evaluationBlindedLDomainBigBitReversed, + evaluationBlindedRDomainBigBitReversed, + evaluationBlindedODomainBigBitReversed, + beta, gamma) + + h1Canonical, h2Canonical, h3Canonical := computeQuotientCanonical( + pk, + evaluationConstraintsDomainBigBitReversed, + evaluationOrderingDomainBigBitReversed, + evaluationBlindedZDomainBigBitReversed, + alpha) + + // 6 - commit to H + proof.Hpp[0], err = pk.Vk.Iopp.BuildProofOfProximity(h1Canonical) + if err != nil { + return nil, err + } + proof.Hpp[1], err = pk.Vk.Iopp.BuildProofOfProximity(h2Canonical) + if err != nil { + return nil, err + } + proof.Hpp[2], err = pk.Vk.Iopp.BuildProofOfProximity(h3Canonical) + if err != nil { + return nil, err + } + + // 7 - build the opening proofs + // compute the size of the domain of evaluation of the committed polynomial, + // the opening position. The challenge zeta will be g^{i} where i is the opening + // position, and g is the generator of the fri domain. + rho := uint64(fri.GetRho()) + friSize := 2 * rho * pk.Vk.Size + var bFriSize big.Int + bFriSize.SetInt64(int64(friSize)) + frOpeningPosition, err := deriveRandomness(fs, "zeta", proof.Hpp[0].ID, proof.Hpp[1].ID, proof.Hpp[2].ID) + if err != nil { + return nil, err + } + var bOpeningPosition big.Int + bOpeningPosition.SetBytes(frOpeningPosition.Marshal()).Mod(&bOpeningPosition, &bFriSize) + openingPosition := bOpeningPosition.Uint64() + + // ql, qr, qm, qo, qkIncomplete + proof.OpeningsQlQrQmQoQkincompletemp[0], err = pk.Vk.Iopp.Open(pk.CQl, openingPosition) + if err != nil { + return &proof, err + } + proof.OpeningsQlQrQmQoQkincompletemp[1], err = pk.Vk.Iopp.Open(pk.CQr, openingPosition) + if err != nil { + return &proof, err + } + proof.OpeningsQlQrQmQoQkincompletemp[2], err = pk.Vk.Iopp.Open(pk.CQm, openingPosition) + if err != nil { + return &proof, err + } + proof.OpeningsQlQrQmQoQkincompletemp[3], err = pk.Vk.Iopp.Open(pk.CQo, openingPosition) + if err != nil { + return &proof, err + } + proof.OpeningsQlQrQmQoQkincompletemp[4], err = pk.Vk.Iopp.Open(pk.CQkIncomplete, openingPosition) + if err != nil { + return &proof, err + } + + // l, r, o + proof.OpeningsLROmp[0], err = pk.Vk.Iopp.Open(blindedLCanonical, openingPosition) + if err != nil { + return &proof, err + } + proof.OpeningsLROmp[1], err = pk.Vk.Iopp.Open(blindedRCanonical, openingPosition) + if err != nil { + return &proof, err + } + proof.OpeningsLROmp[2], err = pk.Vk.Iopp.Open(blindedOCanonical, openingPosition) + if err != nil { + return &proof, err + } + + // h0, h1, h2 + proof.OpeningsHmp[0], err = pk.Vk.Iopp.Open(h1Canonical, openingPosition) + if err != nil { + return &proof, err + } + proof.OpeningsHmp[1], err = pk.Vk.Iopp.Open(h2Canonical, openingPosition) + if err != nil { + return &proof, err + } + proof.OpeningsHmp[2], err = pk.Vk.Iopp.Open(h3Canonical, openingPosition) + if err != nil { + return &proof, err + } + + // s0, s1, s2 + proof.OpeningsS1S2S3mp[0], err = pk.Vk.Iopp.Open(pk.Vk.SCanonical[0], openingPosition) + if err != nil { + return &proof, err + } + proof.OpeningsS1S2S3mp[1], err = pk.Vk.Iopp.Open(pk.Vk.SCanonical[1], openingPosition) + if err != nil { + return &proof, err + } + proof.OpeningsS1S2S3mp[2], err = pk.Vk.Iopp.Open(pk.Vk.SCanonical[2], openingPosition) + if err != nil { + return &proof, err + } + + // id0, id1, id2 + proof.OpeningsId1Id2Id3mp[0], err = pk.Vk.Iopp.Open(pk.Vk.IdCanonical[0], openingPosition) + if err != nil { + return &proof, err + } + proof.OpeningsId1Id2Id3mp[1], err = pk.Vk.Iopp.Open(pk.Vk.IdCanonical[1], openingPosition) + if err != nil { + return &proof, err + } + proof.OpeningsId1Id2Id3mp[2], err = pk.Vk.Iopp.Open(pk.Vk.IdCanonical[2], openingPosition) + if err != nil { + return &proof, err + } + + // zeta is shifted by g, the generator of Z/nZ where n is the number of constraints. We need + // to query the "rho" factor from FRI to know by what should be shifted the opening position. + // We multiply by 2 because FRI is instantiated with pk.Domain[0].Cardinality+2, which makes + // the iop's domain of size rho*(2*pk.Domain[0].Cardinality). + shiftedOpeningPosition := (openingPosition + uint64(2*rho)) % friSize + proof.OpeningsZmp[0], err = pk.Vk.Iopp.Open(blindedZCanonical, openingPosition) + if err != nil { + return &proof, err + } + proof.OpeningsZmp[1], err = pk.Vk.Iopp.Open(blindedZCanonical, shiftedOpeningPosition) + if err != nil { + return &proof, err + } + + return &proof, nil +} + +// evaluateOrderingDomainBigBitReversed computes the evaluation of Z(uX)g1g2g3-Z(X)f1f2f3 on the odd +// cosets of the big domain. +// +// * z evaluation of the blinded permutation accumulator polynomial on odd cosets +// * l, r, o evaluation of the blinded solution vectors on odd cosets +// * gamma randomization +func evaluateOrderingDomainBigBitReversed(pk *ProvingKey, z, l, r, o []fr.Element, beta, gamma fr.Element) []fr.Element { + + nbElmts := int(pk.Domain[1].Cardinality) + + // computes z_(uX)*(l(X)+s₁(X)*β+γ)*(r(X))+s₂(gⁱ)*β+γ)*(o(X))+s₃(X)*β+γ) - z(X)*(l(X)+X*β+γ)*(r(X)+u*X*β+γ)*(o(X)+u²*X*β+γ) + // on the big domain (coset). + res := make([]fr.Element, pk.Domain[1].Cardinality) // re use allocated memory for EvaluationS1BigDomain + + // utils variables useful for using bit reversed indices + nn := uint64(64 - bits.TrailingZeros64(uint64(nbElmts))) + + // needed to shift LsZ + toShift := int(pk.Domain[1].Cardinality / pk.Domain[0].Cardinality) + + var cosetShift, cosetShiftSquare fr.Element + cosetShift.Set(&pk.Vk.CosetShift) + cosetShiftSquare.Square(&pk.Vk.CosetShift) + + utils.Parallelize(int(pk.Domain[1].Cardinality), func(start, end int) { + + var evaluationIDBigDomain fr.Element + evaluationIDBigDomain.Exp(pk.Domain[1].Generator, big.NewInt(int64(start))). + Mul(&evaluationIDBigDomain, &pk.Domain[1].FrMultiplicativeGen) + + var f [3]fr.Element + var g [3]fr.Element + + for i := start; i < end; i++ { + + _i := bits.Reverse64(uint64(i)) >> nn + _is := bits.Reverse64(uint64((i+toShift)%nbElmts)) >> nn + + // in what follows gⁱ is understood as the generator of the chosen coset of domainBig + f[0].Mul(&evaluationIDBigDomain, &beta).Add(&f[0], &l[_i]).Add(&f[0], &gamma) //l(gⁱ)+gⁱ*β+γ + f[1].Mul(&evaluationIDBigDomain, &cosetShift).Mul(&f[1], &beta).Add(&f[1], &r[_i]).Add(&f[1], &gamma) //r(gⁱ)+u*gⁱ*β+γ + f[2].Mul(&evaluationIDBigDomain, &cosetShiftSquare).Mul(&f[2], &beta).Add(&f[2], &o[_i]).Add(&f[2], &gamma) //o(gⁱ)+u²*gⁱ*β+γ + + g[0].Mul(&pk.EvaluationS1BigDomain[_i], &beta).Add(&g[0], &l[_i]).Add(&g[0], &gamma) //l(gⁱ))+s1(gⁱ)*β+γ + g[1].Mul(&pk.EvaluationS2BigDomain[_i], &beta).Add(&g[1], &r[_i]).Add(&g[1], &gamma) //r(gⁱ))+s2(gⁱ)*β+γ + g[2].Mul(&pk.EvaluationS3BigDomain[_i], &beta).Add(&g[2], &o[_i]).Add(&g[2], &gamma) //o(gⁱ))+s3(gⁱ)*β+γ + + f[0].Mul(&f[0], &f[1]).Mul(&f[0], &f[2]).Mul(&f[0], &z[_i]) // z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + g[0].Mul(&g[0], &g[1]).Mul(&g[0], &g[2]).Mul(&g[0], &z[_is]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) + + res[_i].Sub(&g[0], &f[0]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) - z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + + evaluationIDBigDomain.Mul(&evaluationIDBigDomain, &pk.Domain[1].Generator) // gⁱ*g + } + }) + + return res +} + +// evalConstraintsInd computes the evaluation of lL+qrR+qqmL.R+qoO+k on +// the odd coset of (Z/8mZ)/(Z/4mZ), where m=nbConstraints+nbAssertions. +// +// * lsL, lsR, lsO are the evaluation of the blinded solution vectors on odd cosets +// * lsQk is the completed version of qk, in canonical version +// +// lsL, lsR, lsO are in bit reversed order, lsQk is in the correct order. +func evalConstraintsInd(pk *ProvingKey, lsL, lsR, lsO, lsQk []fr.Element) []fr.Element { + + res := make([]fr.Element, pk.Domain[1].Cardinality) + // nn := uint64(64 - bits.TrailingZeros64(pk.Domain[1].Cardinality)) + + utils.Parallelize(len(res), func(start, end int) { + + var t0, t1 fr.Element + + for i := start; i < end; i++ { + + // irev := bits.Reverse64(uint64(i)) >> nn + + t1.Mul(&pk.EvaluationQmDomainBigBitReversed[i], &lsR[i]) // qm.r + t1.Add(&t1, &pk.EvaluationQlDomainBigBitReversed[i]) // qm.r + ql + t1.Mul(&t1, &lsL[i]) // qm.l.r + ql.l + + t0.Mul(&pk.EvaluationQrDomainBigBitReversed[i], &lsR[i]) + t0.Add(&t0, &t1) // qm.l.r + ql.l + qr.r + + t1.Mul(&pk.EvaluationQoDomainBigBitReversed[i], &lsO[i]) + t0.Add(&t0, &t1) // ql.l + qr.r + qm.l.r + qo.o + res[i].Add(&t0, &lsQk[i]) // ql.l + qr.r + qm.l.r + qo.o + k + + } + }) + + return res +} + +// fftBigCosetWOBitReverse evaluates poly (canonical form) of degree m> nn + + t.Sub(&evaluationBlindedZDomainBigBitReversed[_i], &one) // evaluates L₁(X)*(Z(X)-1) on a coset of the big domain + h[_i].Mul(&startsAtOne[_i], &t).Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintOrderingBitReversed[_i]). + Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintsIndBitReversed[_i]). + Mul(&h[_i], &evaluationXnMinusOneInverse[i%ratio]) + } + }) + + // put h in canonical form. h is of degree 3*(n+1)+2. + // using fft.DIT put h revert bit reverse + pk.Domain[1].FFTInverse(h, fft.DIT, fft.OnCoset()) + + // degree of hi is n+2 because of the blinding + h1 := h[:pk.Domain[0].Cardinality+2] + h2 := h[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] + h3 := h[2*(pk.Domain[0].Cardinality+2) : 3*(pk.Domain[0].Cardinality+2)] + + return h1, h2, h3 + +} + +// computeZ computes Z, in canonical basis, where: +// +// - Z of degree n (domainNum.Cardinality) +// +// - Z(1)=1 +// (l_i+z**i+gamma)*(r_i+u*z**i+gamma)*(o_i+u**2z**i+gamma) +// +// - for i>0: Z(u**i) = Pi_{k "small" domain, used for individual polynomials + // 1 -> "big" domain, used for the computation of the quotient + Domain [2]fft.Domain + + // s1, s2, s3 (L=Lagrange basis small domain, C=canonical basis, Ls=Lagrange Shifted big domain) + LId []fr.Element + EvaluationId1BigDomain, EvaluationId2BigDomain, EvaluationId3BigDomain []fr.Element + EvaluationS1BigDomain, EvaluationS2BigDomain, EvaluationS3BigDomain []fr.Element + + // position -> permuted position (position in [0,3*sizeSystem-1]) + Permutation []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, that is the closest power of 2 bounding above + // number of constraints+number of public inputs + Size uint64 + SizeInv fr.Element + Generator fr.Element + NbPublicVariables uint64 + + // cosetShift generator of the coset on the small domain + CosetShift fr.Element + + // S commitments to S1, S2, S3 + SCanonical [3][]fr.Element + Spp [3]fri.ProofOfProximity + + // Id commitments to Id1, Id2, Id3 + // Id [3]Commitment + IdCanonical [3][]fr.Element + Idpp [3]fri.ProofOfProximity + + // Commitments to ql, qr, qm, qo prepended with as many zeroes (ones for l) as there are public inputs. + // In particular Qk is not complete. + Qpp [5]fri.ProofOfProximity // Ql, Qr, Qm, Qo, Qk + + // Iopp scheme (currently one for each size of polynomial) + Iopp fri.Iopp + + // generator of the group on which the Iopp works. If i is the opening position, + // the polynomials will be opened at genOpening^{i}. + GenOpening fr.Element +} + +// Setup sets proving and verifying keys +func Setup(spr *cs.SparseR1CS) (*ProvingKey, *VerifyingKey, error) { + + var pk ProvingKey + var vk VerifyingKey + + // The verifying key shares data with the proving key + pk.Vk = &vk + + nbConstraints := spr.GetNbConstraints() + + // fft domains + sizeSystem := uint64(nbConstraints + len(spr.Public)) // len(spr.Public) is for the placeholder constraints + pk.Domain[0] = *fft.NewDomain(sizeSystem) + + // h, the quotient polynomial is of degree 3(n+1)+2, so it's in a 3(n+2) dim vector space, + // the domain is the next power of 2 superior to 3(n+2). 4*domainNum is enough in all cases + // except when n<6. + if sizeSystem < 6 { + pk.Domain[1] = *fft.NewDomain(8 * sizeSystem) + } else { + pk.Domain[1] = *fft.NewDomain(4 * sizeSystem) + } + pk.Vk.CosetShift.Set(&pk.Domain[0].FrMultiplicativeGen) + + vk.Size = pk.Domain[0].Cardinality + vk.SizeInv.SetUint64(vk.Size).Inverse(&vk.SizeInv) + vk.Generator.Set(&pk.Domain[0].Generator) + vk.NbPublicVariables = uint64(len(spr.Public)) + + // IOP schemess + // The +2 is to handle the blinding. + sizeIopp := pk.Domain[0].Cardinality + 2 + vk.Iopp = fri.RADIX_2_FRI.New(sizeIopp, sha256.New()) + // only there to access the group used in FRI... + rho := uint64(fri.GetRho()) + // we multiply by 2 because the IOP is created with size pk.Domain[0].Cardinality + 2 (because + // of the blinding), so the domain will be rho*size_domain where size_domain is the next power + // of 2 after pk.Domain[0].Cardinality + 2, which is 2*rho*pk.Domain[0].Cardinality + tmpDomain := fft.NewDomain(2 * rho * pk.Domain[0].Cardinality) + vk.GenOpening.Set(&tmpDomain.Generator) + + // public polynomials corresponding to constraints: [ placholders | constraints | assertions ] + pk.EvaluationQlDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQrDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQmDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQoDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.LQkIncompleteDomainSmall = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQkIncomplete = make([]fr.Element, pk.Domain[0].Cardinality) + + for i := 0; i < len(spr.Public); i++ { // placeholders (-PUB_INPUT_i + qk_i = 0) TODO should return error if size is inconsistent + pk.EvaluationQlDomainBigBitReversed[i].SetOne().Neg(&pk.EvaluationQlDomainBigBitReversed[i]) + pk.EvaluationQrDomainBigBitReversed[i].SetZero() + pk.EvaluationQmDomainBigBitReversed[i].SetZero() + pk.EvaluationQoDomainBigBitReversed[i].SetZero() + pk.LQkIncompleteDomainSmall[i].SetZero() // --> to be completed by the prover + pk.CQkIncomplete[i].Set(&pk.LQkIncompleteDomainSmall[i]) // --> to be completed by the prover + } + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + pk.EvaluationQlDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QL]) + pk.EvaluationQrDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QR]) + pk.EvaluationQmDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QM]) + pk.EvaluationQoDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QO]) + pk.LQkIncompleteDomainSmall[offset+j].Set(&spr.Coefficients[c.QC]) + pk.CQkIncomplete[offset+j].Set(&pk.LQkIncompleteDomainSmall[offset+j]) + + j++ + } + + pk.Domain[0].FFTInverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.CQkIncomplete, fft.DIF) + fft.BitReverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.CQkIncomplete) + + // Commit to the polynomials to set up the verifying key + pk.CQl = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQr = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQm = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQo = make([]fr.Element, pk.Domain[0].Cardinality) + copy(pk.CQl, pk.EvaluationQlDomainBigBitReversed) + copy(pk.CQr, pk.EvaluationQrDomainBigBitReversed) + copy(pk.CQm, pk.EvaluationQmDomainBigBitReversed) + copy(pk.CQo, pk.EvaluationQoDomainBigBitReversed) + var err error + vk.Qpp[0], err = vk.Iopp.BuildProofOfProximity(pk.CQl) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[1], err = vk.Iopp.BuildProofOfProximity(pk.CQr) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[2], err = vk.Iopp.BuildProofOfProximity(pk.CQm) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[3], err = vk.Iopp.BuildProofOfProximity(pk.CQo) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[4], err = vk.Iopp.BuildProofOfProximity(pk.CQkIncomplete) + if err != nil { + return &pk, &vk, err + } + + pk.Domain[1].FFT(pk.EvaluationQlDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQrDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQmDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQoDomainBigBitReversed, fft.DIF, fft.OnCoset()) + + // build permutation. Note: at this stage, the permutation takes in account the placeholders + buildPermutation(spr, &pk) + + // set s1, s2, s3 + err = computePermutationPolynomials(&pk, &vk) + if err != nil { + return &pk, &vk, err + } + + return &pk, &vk, nil + +} + +// buildPermutation builds the Permutation associated with a circuit. +// +// The permutation s is composed of cycles of maximum length such that +// +// s. (l||r||o) = (l||r||o) +// +// , where l||r||o is the concatenation of the indices of l, r, o in +// ql.l+qr.r+qm.l.r+qo.O+k = 0. +// +// The permutation is encoded as a slice s of size 3*size(l), where the +// i-th entry of l||r||o is sent to the s[i]-th entry, so it acts on a tab +// like this: for i in tab: tab[i] = tab[permutation[i]] +func buildPermutation(spr *cs.SparseR1CS, pk *ProvingKey) { + + nbVariables := spr.NbInternalVariables + len(spr.Public) + len(spr.Secret) + sizeSolution := int(pk.Domain[0].Cardinality) + + // init permutation + pk.Permutation = make([]int64, 3*sizeSolution) + for i := 0; i < len(pk.Permutation); i++ { + pk.Permutation[i] = -1 + } + + // init LRO position -> variable_ID + lro := make([]int, 3*sizeSolution) // position -> variable_ID + for i := 0; i < len(spr.Public); i++ { + lro[i] = i // IDs of LRO associated to placeholders (only L needs to be taken care of) + } + + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + lro[offset+j] = int(c.XA) + lro[sizeSolution+offset+j] = int(c.XB) + lro[2*sizeSolution+offset+j] = int(c.XC) + j++ + } + + // init cycle: + // map ID -> last position the ID was seen + cycle := make([]int64, nbVariables) + for i := 0; i < len(cycle); i++ { + cycle[i] = -1 + } + + for i := 0; i < len(lro); i++ { + if cycle[lro[i]] != -1 { + // if != -1, it means we already encountered this value + // so we need to set the corresponding permutation index. + pk.Permutation[i] = cycle[lro[i]] + } + cycle[lro[i]] = int64(i) + } + + // complete the Permutation by filling the first IDs encountered + for i := 0; i < len(pk.Permutation); i++ { + if pk.Permutation[i] == -1 { + pk.Permutation[i] = cycle[lro[i]] + } + } +} + +// computePermutationPolynomials computes the LDE (Lagrange basis) of the permutations +// s1, s2, s3. +// +// 0 1 .. n-1 | n n+1 .. 2*n-1 | 2n 2n+1 .. 3n-1 | +// +// | +// | Permutation +// +// s00 s01 .. s0n-1 s10 s11 .. s1n-1 s20 s21 .. s2n-1 v +// \---------------/ \--------------------/ \------------------------/ +// +// s1 (LDE) s2 (LDE) s3 (LDE) +func computePermutationPolynomials(pk *ProvingKey, vk *VerifyingKey) error { + + nbElmt := int(pk.Domain[0].Cardinality) + + // sID = [1,..,g^{n-1},s,..,s*g^{n-1},s^2,..,s^2*g^{n-1}] + pk.LId = getIDSmallDomain(&pk.Domain[0]) + + // canonical form of S1, S2, S3 + pk.EvaluationS1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + for i := 0; i < nbElmt; i++ { + pk.EvaluationS1BigDomain[i].Set(&pk.LId[pk.Permutation[i]]) + pk.EvaluationS2BigDomain[i].Set(&pk.LId[pk.Permutation[nbElmt+i]]) + pk.EvaluationS3BigDomain[i].Set(&pk.LId[pk.Permutation[2*nbElmt+i]]) + } + + // Evaluations of Sid1, Sid2, Sid3 on cosets of Domain[1] + pk.EvaluationId1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + copy(pk.EvaluationId1BigDomain, pk.LId[:nbElmt]) + copy(pk.EvaluationId2BigDomain, pk.LId[nbElmt:2*nbElmt]) + copy(pk.EvaluationId3BigDomain, pk.LId[2*nbElmt:]) + pk.Domain[0].FFTInverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality]) + vk.IdCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.IdCanonical[0], pk.EvaluationId1BigDomain) + copy(vk.IdCanonical[1], pk.EvaluationId2BigDomain) + copy(vk.IdCanonical[2], pk.EvaluationId3BigDomain) + + var err error + vk.Idpp[0], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId1BigDomain) + if err != nil { + return err + } + vk.Idpp[1], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId2BigDomain) + if err != nil { + return err + } + vk.Idpp[2], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId3BigDomain) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationId1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId3BigDomain, fft.DIF, fft.OnCoset()) + + pk.Domain[0].FFTInverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + + // commit S1, S2, S3 + vk.SCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.SCanonical[0], pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[1], pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[2], pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + vk.Spp[0], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[0]) + if err != nil { + return err + } + vk.Spp[1], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[1]) + if err != nil { + return err + } + vk.Spp[2], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[2]) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationS1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS3BigDomain, fft.DIF, fft.OnCoset()) + + return nil + +} + +// getIDSmallDomain returns the Lagrange form of ID on the small domain +func getIDSmallDomain(domain *fft.Domain) []fr.Element { + + res := make([]fr.Element, 3*domain.Cardinality) + + res[0].SetOne() + res[domain.Cardinality].Set(&domain.FrMultiplicativeGen) + res[2*domain.Cardinality].Square(&domain.FrMultiplicativeGen) + + for i := uint64(1); i < domain.Cardinality; i++ { + res[i].Mul(&res[i-1], &domain.Generator) + res[domain.Cardinality+i].Mul(&res[domain.Cardinality+i-1], &domain.Generator) + res[2*domain.Cardinality+i].Mul(&res[2*domain.Cardinality+i-1], &domain.Generator) + } + + return res +} + +// NbPublicWitness returns the expected public witness size (number of field elements) +func (vk *VerifyingKey) NbPublicWitness() int { + return int(vk.NbPublicVariables) +} + +// VerifyingKey returns pk.Vk +func (pk *ProvingKey) VerifyingKey() interface{} { + return pk.Vk +} diff --git a/backend/plonkfri/bls12-377/verify.go b/backend/plonkfri/bls12-377/verify.go new file mode 100644 index 0000000000..88034fe0bb --- /dev/null +++ b/backend/plonkfri/bls12-377/verify.go @@ -0,0 +1,398 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by gnark DO NOT EDIT + +package plonkfri + +import ( + "errors" + "fmt" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/fri" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + "github.com/consensys/gnark/backend" + "math/big" +) + +var ErrInvalidAlgebraicRelation = errors.New("algebraic relation does not hold") + +func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { + cfg, err := backend.NewVerifierConfig(opts...) + if err != nil { + return fmt.Errorf("create backend config: %w", err) + } + + // 0 - derive the challenges with Fiat Shamir + fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") + + dataFiatShamir := make([][fr.Bytes]byte, len(publicWitness)+3) + for i := 0; i < len(publicWitness); i++ { + copy(dataFiatShamir[i][:], publicWitness[i].Marshal()) + } + copy(dataFiatShamir[len(publicWitness)][:], proof.LROpp[0].ID) + copy(dataFiatShamir[len(publicWitness)+1][:], proof.LROpp[1].ID) + copy(dataFiatShamir[len(publicWitness)+2][:], proof.LROpp[2].ID) + + beta, err := deriveRandomnessFixedSize(fs, "gamma", dataFiatShamir...) + if err != nil { + return err + } + + gamma, err := deriveRandomness(fs, "beta", nil) + if err != nil { + return err + } + + alpha, err := deriveRandomness(fs, "alpha", proof.Zpp.ID) + if err != nil { + return err + } + + // compute the size of the domain of evaluation of the committed polynomial, + // the opening position. The challenge zeta will be g^{i} where i is the opening + // position, and g is the generator of the fri domain. + rho := uint64(fri.GetRho()) + friSize := 2 * rho * vk.Size + var bFriSize big.Int + bFriSize.SetInt64(int64(friSize)) + frOpeningPosition, err := deriveRandomness(fs, "zeta", proof.Hpp[0].ID, proof.Hpp[1].ID, proof.Hpp[2].ID) + if err != nil { + return err + } + var bOpeningPosition big.Int + bOpeningPosition.SetBytes(frOpeningPosition.Marshal()).Mod(&bOpeningPosition, &bFriSize) + openingPosition := bOpeningPosition.Uint64() + + shiftedOpeningPosition := (openingPosition + uint64(2*rho)) % friSize + err = vk.Iopp.VerifyOpening(shiftedOpeningPosition, proof.OpeningsZmp[1], proof.Zpp) + if err != nil { + return err + } + + // 1 - verify that the commitments are low degree polynomials + + // ql, qr, qm, qo, qkIncomplete + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[2]) + if err != nil { + return err + } + + // s1, s2, s3 + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[2]) + if err != nil { + return err + } + + // id1, id2, id3 + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[2]) + if err != nil { + return err + } + + // Z + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // 2 - verify the openings + + // ql, qr, qm, qo, qkIncomplete + // openingPosition := uint64(2) + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[0], vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[1], vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[2], vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[3], vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[4], vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[0], proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[1], proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[2], proof.LROpp[2]) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[0], proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[1], proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[2], proof.Hpp[2]) + if err != nil { + return err + } + + // s0, s1, s2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[0], vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[1], vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[2], vk.Spp[2]) + if err != nil { + return err + } + + // id0, id1, id2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[0], vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[1], vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[2], vk.Idpp[2]) + if err != nil { + return err + } + + // Z, Zshift + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsZmp[0], proof.Zpp) + if err != nil { + return err + } + + // verification of the algebraic relation + var ql, qr, qm, qo, qk fr.Element + ql.Set(&proof.OpeningsQlQrQmQoQkincompletemp[0].ClaimedValue) + qr.Set(&proof.OpeningsQlQrQmQoQkincompletemp[1].ClaimedValue) + qm.Set(&proof.OpeningsQlQrQmQoQkincompletemp[2].ClaimedValue) + qo.Set(&proof.OpeningsQlQrQmQoQkincompletemp[3].ClaimedValue) + qk.Set(&proof.OpeningsQlQrQmQoQkincompletemp[4].ClaimedValue) // -> to be completed + + var l, r, o fr.Element + l.Set(&proof.OpeningsLROmp[0].ClaimedValue) + r.Set(&proof.OpeningsLROmp[1].ClaimedValue) + o.Set(&proof.OpeningsLROmp[2].ClaimedValue) + + var h1, h2, h3 fr.Element + h1.Set(&proof.OpeningsHmp[0].ClaimedValue) + h2.Set(&proof.OpeningsHmp[1].ClaimedValue) + h3.Set(&proof.OpeningsHmp[2].ClaimedValue) + + var s1, s2, s3 fr.Element + s1.Set(&proof.OpeningsS1S2S3mp[0].ClaimedValue) + s2.Set(&proof.OpeningsS1S2S3mp[1].ClaimedValue) + s3.Set(&proof.OpeningsS1S2S3mp[2].ClaimedValue) + + var id1, id2, id3 fr.Element + id1.Set(&proof.OpeningsId1Id2Id3mp[0].ClaimedValue) + id2.Set(&proof.OpeningsId1Id2Id3mp[1].ClaimedValue) + id3.Set(&proof.OpeningsId1Id2Id3mp[2].ClaimedValue) + + var z, zshift fr.Element + z.Set(&proof.OpeningsZmp[0].ClaimedValue) + zshift.Set(&proof.OpeningsZmp[1].ClaimedValue) + + // 2 - compute the LHS: (ql*l+..+qk)+ α*(z(μx)*(l+β*s₁+γ)*..-z*(l+β*id1+γ))+α²*z*(l1-1) + var zeta fr.Element + zeta.Exp(vk.GenOpening, &bOpeningPosition) + + var lhs, t1, t2, t3, tmp, tmp2 fr.Element + // 2.1 (ql*l+..+qk) + t1.Mul(&l, &ql) + tmp.Mul(&r, &qr) + t1.Add(&t1, &tmp) + tmp.Mul(&qm, &l).Mul(&tmp, &r) + t1.Add(&t1, &tmp) + tmp.Mul(&o, &qo) + t1.Add(&tmp, &t1) + tmp = completeQk(publicWitness, vk, zeta) + tmp.Add(&qk, &tmp) + t1.Add(&tmp, &t1) + + // 2.2 (z(ux)*(l+β*s1+γ)*..-z*(l+β*id1+γ)) + t2.Mul(&beta, &s1).Add(&t2, &l).Add(&t2, &gamma) + tmp.Mul(&beta, &s2).Add(&tmp, &r).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2) + tmp.Mul(&beta, &s3).Add(&tmp, &o).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2).Mul(&t2, &zshift) + + tmp.Mul(&beta, &id1).Add(&tmp, &l).Add(&tmp, &gamma) + tmp2.Mul(&beta, &id2).Add(&tmp2, &r).Add(&tmp2, &gamma) + tmp.Mul(&tmp, &tmp2) + tmp2.Mul(&beta, &id3).Add(&tmp2, &o).Add(&tmp2, &gamma) + tmp.Mul(&tmp2, &tmp).Mul(&tmp, &z) + + t2.Sub(&t2, &tmp) + + // 2.3 (z-1)*l1 + var one fr.Element + one.SetOne() + t3.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&t3, &one) + tmp.Sub(&zeta, &one).Inverse(&tmp).Mul(&tmp, &vk.SizeInv) + t3.Mul(&tmp, &t3) + tmp.Sub(&z, &one) + t3.Mul(&tmp, &t3) + + // 2.4 (ql*l+s+qk) + α*(z(ux)*(l+β*s1+γ)*...-z*(l+β*id1+γ)..)+ α²*z*(l1-1) + lhs.Set(&t3).Mul(&lhs, &alpha).Add(&lhs, &t2).Mul(&lhs, &alpha).Add(&lhs, &t1) + + // 3 - compute the RHS + var rhs fr.Element + tmp.Exp(zeta, big.NewInt(int64(vk.Size+2))) + rhs.Mul(&h3, &tmp). + Add(&rhs, &h2). + Mul(&rhs, &tmp). + Add(&rhs, &h1) + + tmp.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&tmp, &one) + rhs.Mul(&rhs, &tmp) + + // 4 - verify the relation LHS==RHS + if !rhs.Equal(&lhs) { + return ErrInvalidAlgebraicRelation + } + + return nil + +} + +// completeQk returns ∑_{i> nn + _is := bits.Reverse64(uint64((i+toShift)%nbElmts)) >> nn + + // in what follows gⁱ is understood as the generator of the chosen coset of domainBig + f[0].Mul(&evaluationIDBigDomain, &beta).Add(&f[0], &l[_i]).Add(&f[0], &gamma) //l(gⁱ)+gⁱ*β+γ + f[1].Mul(&evaluationIDBigDomain, &cosetShift).Mul(&f[1], &beta).Add(&f[1], &r[_i]).Add(&f[1], &gamma) //r(gⁱ)+u*gⁱ*β+γ + f[2].Mul(&evaluationIDBigDomain, &cosetShiftSquare).Mul(&f[2], &beta).Add(&f[2], &o[_i]).Add(&f[2], &gamma) //o(gⁱ)+u²*gⁱ*β+γ + + g[0].Mul(&pk.EvaluationS1BigDomain[_i], &beta).Add(&g[0], &l[_i]).Add(&g[0], &gamma) //l(gⁱ))+s1(gⁱ)*β+γ + g[1].Mul(&pk.EvaluationS2BigDomain[_i], &beta).Add(&g[1], &r[_i]).Add(&g[1], &gamma) //r(gⁱ))+s2(gⁱ)*β+γ + g[2].Mul(&pk.EvaluationS3BigDomain[_i], &beta).Add(&g[2], &o[_i]).Add(&g[2], &gamma) //o(gⁱ))+s3(gⁱ)*β+γ + + f[0].Mul(&f[0], &f[1]).Mul(&f[0], &f[2]).Mul(&f[0], &z[_i]) // z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + g[0].Mul(&g[0], &g[1]).Mul(&g[0], &g[2]).Mul(&g[0], &z[_is]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) + + res[_i].Sub(&g[0], &f[0]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) - z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + + evaluationIDBigDomain.Mul(&evaluationIDBigDomain, &pk.Domain[1].Generator) // gⁱ*g + } + }) + + return res +} + +// evalConstraintsInd computes the evaluation of lL+qrR+qqmL.R+qoO+k on +// the odd coset of (Z/8mZ)/(Z/4mZ), where m=nbConstraints+nbAssertions. +// +// * lsL, lsR, lsO are the evaluation of the blinded solution vectors on odd cosets +// * lsQk is the completed version of qk, in canonical version +// +// lsL, lsR, lsO are in bit reversed order, lsQk is in the correct order. +func evalConstraintsInd(pk *ProvingKey, lsL, lsR, lsO, lsQk []fr.Element) []fr.Element { + + res := make([]fr.Element, pk.Domain[1].Cardinality) + // nn := uint64(64 - bits.TrailingZeros64(pk.Domain[1].Cardinality)) + + utils.Parallelize(len(res), func(start, end int) { + + var t0, t1 fr.Element + + for i := start; i < end; i++ { + + // irev := bits.Reverse64(uint64(i)) >> nn + + t1.Mul(&pk.EvaluationQmDomainBigBitReversed[i], &lsR[i]) // qm.r + t1.Add(&t1, &pk.EvaluationQlDomainBigBitReversed[i]) // qm.r + ql + t1.Mul(&t1, &lsL[i]) // qm.l.r + ql.l + + t0.Mul(&pk.EvaluationQrDomainBigBitReversed[i], &lsR[i]) + t0.Add(&t0, &t1) // qm.l.r + ql.l + qr.r + + t1.Mul(&pk.EvaluationQoDomainBigBitReversed[i], &lsO[i]) + t0.Add(&t0, &t1) // ql.l + qr.r + qm.l.r + qo.o + res[i].Add(&t0, &lsQk[i]) // ql.l + qr.r + qm.l.r + qo.o + k + + } + }) + + return res +} + +// fftBigCosetWOBitReverse evaluates poly (canonical form) of degree m> nn + + t.Sub(&evaluationBlindedZDomainBigBitReversed[_i], &one) // evaluates L₁(X)*(Z(X)-1) on a coset of the big domain + h[_i].Mul(&startsAtOne[_i], &t).Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintOrderingBitReversed[_i]). + Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintsIndBitReversed[_i]). + Mul(&h[_i], &evaluationXnMinusOneInverse[i%ratio]) + } + }) + + // put h in canonical form. h is of degree 3*(n+1)+2. + // using fft.DIT put h revert bit reverse + pk.Domain[1].FFTInverse(h, fft.DIT, fft.OnCoset()) + + // degree of hi is n+2 because of the blinding + h1 := h[:pk.Domain[0].Cardinality+2] + h2 := h[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] + h3 := h[2*(pk.Domain[0].Cardinality+2) : 3*(pk.Domain[0].Cardinality+2)] + + return h1, h2, h3 + +} + +// computeZ computes Z, in canonical basis, where: +// +// - Z of degree n (domainNum.Cardinality) +// +// - Z(1)=1 +// (l_i+z**i+gamma)*(r_i+u*z**i+gamma)*(o_i+u**2z**i+gamma) +// +// - for i>0: Z(u**i) = Pi_{k "small" domain, used for individual polynomials + // 1 -> "big" domain, used for the computation of the quotient + Domain [2]fft.Domain + + // s1, s2, s3 (L=Lagrange basis small domain, C=canonical basis, Ls=Lagrange Shifted big domain) + LId []fr.Element + EvaluationId1BigDomain, EvaluationId2BigDomain, EvaluationId3BigDomain []fr.Element + EvaluationS1BigDomain, EvaluationS2BigDomain, EvaluationS3BigDomain []fr.Element + + // position -> permuted position (position in [0,3*sizeSystem-1]) + Permutation []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, that is the closest power of 2 bounding above + // number of constraints+number of public inputs + Size uint64 + SizeInv fr.Element + Generator fr.Element + NbPublicVariables uint64 + + // cosetShift generator of the coset on the small domain + CosetShift fr.Element + + // S commitments to S1, S2, S3 + SCanonical [3][]fr.Element + Spp [3]fri.ProofOfProximity + + // Id commitments to Id1, Id2, Id3 + // Id [3]Commitment + IdCanonical [3][]fr.Element + Idpp [3]fri.ProofOfProximity + + // Commitments to ql, qr, qm, qo prepended with as many zeroes (ones for l) as there are public inputs. + // In particular Qk is not complete. + Qpp [5]fri.ProofOfProximity // Ql, Qr, Qm, Qo, Qk + + // Iopp scheme (currently one for each size of polynomial) + Iopp fri.Iopp + + // generator of the group on which the Iopp works. If i is the opening position, + // the polynomials will be opened at genOpening^{i}. + GenOpening fr.Element +} + +// Setup sets proving and verifying keys +func Setup(spr *cs.SparseR1CS) (*ProvingKey, *VerifyingKey, error) { + + var pk ProvingKey + var vk VerifyingKey + + // The verifying key shares data with the proving key + pk.Vk = &vk + + nbConstraints := spr.GetNbConstraints() + + // fft domains + sizeSystem := uint64(nbConstraints + len(spr.Public)) // len(spr.Public) is for the placeholder constraints + pk.Domain[0] = *fft.NewDomain(sizeSystem) + + // h, the quotient polynomial is of degree 3(n+1)+2, so it's in a 3(n+2) dim vector space, + // the domain is the next power of 2 superior to 3(n+2). 4*domainNum is enough in all cases + // except when n<6. + if sizeSystem < 6 { + pk.Domain[1] = *fft.NewDomain(8 * sizeSystem) + } else { + pk.Domain[1] = *fft.NewDomain(4 * sizeSystem) + } + pk.Vk.CosetShift.Set(&pk.Domain[0].FrMultiplicativeGen) + + vk.Size = pk.Domain[0].Cardinality + vk.SizeInv.SetUint64(vk.Size).Inverse(&vk.SizeInv) + vk.Generator.Set(&pk.Domain[0].Generator) + vk.NbPublicVariables = uint64(len(spr.Public)) + + // IOP schemess + // The +2 is to handle the blinding. + sizeIopp := pk.Domain[0].Cardinality + 2 + vk.Iopp = fri.RADIX_2_FRI.New(sizeIopp, sha256.New()) + // only there to access the group used in FRI... + rho := uint64(fri.GetRho()) + // we multiply by 2 because the IOP is created with size pk.Domain[0].Cardinality + 2 (because + // of the blinding), so the domain will be rho*size_domain where size_domain is the next power + // of 2 after pk.Domain[0].Cardinality + 2, which is 2*rho*pk.Domain[0].Cardinality + tmpDomain := fft.NewDomain(2 * rho * pk.Domain[0].Cardinality) + vk.GenOpening.Set(&tmpDomain.Generator) + + // public polynomials corresponding to constraints: [ placholders | constraints | assertions ] + pk.EvaluationQlDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQrDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQmDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQoDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.LQkIncompleteDomainSmall = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQkIncomplete = make([]fr.Element, pk.Domain[0].Cardinality) + + for i := 0; i < len(spr.Public); i++ { // placeholders (-PUB_INPUT_i + qk_i = 0) TODO should return error if size is inconsistent + pk.EvaluationQlDomainBigBitReversed[i].SetOne().Neg(&pk.EvaluationQlDomainBigBitReversed[i]) + pk.EvaluationQrDomainBigBitReversed[i].SetZero() + pk.EvaluationQmDomainBigBitReversed[i].SetZero() + pk.EvaluationQoDomainBigBitReversed[i].SetZero() + pk.LQkIncompleteDomainSmall[i].SetZero() // --> to be completed by the prover + pk.CQkIncomplete[i].Set(&pk.LQkIncompleteDomainSmall[i]) // --> to be completed by the prover + } + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + pk.EvaluationQlDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QL]) + pk.EvaluationQrDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QR]) + pk.EvaluationQmDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QM]) + pk.EvaluationQoDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QO]) + pk.LQkIncompleteDomainSmall[offset+j].Set(&spr.Coefficients[c.QC]) + pk.CQkIncomplete[offset+j].Set(&pk.LQkIncompleteDomainSmall[offset+j]) + + j++ + } + + pk.Domain[0].FFTInverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.CQkIncomplete, fft.DIF) + fft.BitReverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.CQkIncomplete) + + // Commit to the polynomials to set up the verifying key + pk.CQl = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQr = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQm = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQo = make([]fr.Element, pk.Domain[0].Cardinality) + copy(pk.CQl, pk.EvaluationQlDomainBigBitReversed) + copy(pk.CQr, pk.EvaluationQrDomainBigBitReversed) + copy(pk.CQm, pk.EvaluationQmDomainBigBitReversed) + copy(pk.CQo, pk.EvaluationQoDomainBigBitReversed) + var err error + vk.Qpp[0], err = vk.Iopp.BuildProofOfProximity(pk.CQl) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[1], err = vk.Iopp.BuildProofOfProximity(pk.CQr) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[2], err = vk.Iopp.BuildProofOfProximity(pk.CQm) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[3], err = vk.Iopp.BuildProofOfProximity(pk.CQo) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[4], err = vk.Iopp.BuildProofOfProximity(pk.CQkIncomplete) + if err != nil { + return &pk, &vk, err + } + + pk.Domain[1].FFT(pk.EvaluationQlDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQrDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQmDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQoDomainBigBitReversed, fft.DIF, fft.OnCoset()) + + // build permutation. Note: at this stage, the permutation takes in account the placeholders + buildPermutation(spr, &pk) + + // set s1, s2, s3 + err = computePermutationPolynomials(&pk, &vk) + if err != nil { + return &pk, &vk, err + } + + return &pk, &vk, nil + +} + +// buildPermutation builds the Permutation associated with a circuit. +// +// The permutation s is composed of cycles of maximum length such that +// +// s. (l||r||o) = (l||r||o) +// +// , where l||r||o is the concatenation of the indices of l, r, o in +// ql.l+qr.r+qm.l.r+qo.O+k = 0. +// +// The permutation is encoded as a slice s of size 3*size(l), where the +// i-th entry of l||r||o is sent to the s[i]-th entry, so it acts on a tab +// like this: for i in tab: tab[i] = tab[permutation[i]] +func buildPermutation(spr *cs.SparseR1CS, pk *ProvingKey) { + + nbVariables := spr.NbInternalVariables + len(spr.Public) + len(spr.Secret) + sizeSolution := int(pk.Domain[0].Cardinality) + + // init permutation + pk.Permutation = make([]int64, 3*sizeSolution) + for i := 0; i < len(pk.Permutation); i++ { + pk.Permutation[i] = -1 + } + + // init LRO position -> variable_ID + lro := make([]int, 3*sizeSolution) // position -> variable_ID + for i := 0; i < len(spr.Public); i++ { + lro[i] = i // IDs of LRO associated to placeholders (only L needs to be taken care of) + } + + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + lro[offset+j] = int(c.XA) + lro[sizeSolution+offset+j] = int(c.XB) + lro[2*sizeSolution+offset+j] = int(c.XC) + j++ + } + + // init cycle: + // map ID -> last position the ID was seen + cycle := make([]int64, nbVariables) + for i := 0; i < len(cycle); i++ { + cycle[i] = -1 + } + + for i := 0; i < len(lro); i++ { + if cycle[lro[i]] != -1 { + // if != -1, it means we already encountered this value + // so we need to set the corresponding permutation index. + pk.Permutation[i] = cycle[lro[i]] + } + cycle[lro[i]] = int64(i) + } + + // complete the Permutation by filling the first IDs encountered + for i := 0; i < len(pk.Permutation); i++ { + if pk.Permutation[i] == -1 { + pk.Permutation[i] = cycle[lro[i]] + } + } +} + +// computePermutationPolynomials computes the LDE (Lagrange basis) of the permutations +// s1, s2, s3. +// +// 0 1 .. n-1 | n n+1 .. 2*n-1 | 2n 2n+1 .. 3n-1 | +// +// | +// | Permutation +// +// s00 s01 .. s0n-1 s10 s11 .. s1n-1 s20 s21 .. s2n-1 v +// \---------------/ \--------------------/ \------------------------/ +// +// s1 (LDE) s2 (LDE) s3 (LDE) +func computePermutationPolynomials(pk *ProvingKey, vk *VerifyingKey) error { + + nbElmt := int(pk.Domain[0].Cardinality) + + // sID = [1,..,g^{n-1},s,..,s*g^{n-1},s^2,..,s^2*g^{n-1}] + pk.LId = getIDSmallDomain(&pk.Domain[0]) + + // canonical form of S1, S2, S3 + pk.EvaluationS1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + for i := 0; i < nbElmt; i++ { + pk.EvaluationS1BigDomain[i].Set(&pk.LId[pk.Permutation[i]]) + pk.EvaluationS2BigDomain[i].Set(&pk.LId[pk.Permutation[nbElmt+i]]) + pk.EvaluationS3BigDomain[i].Set(&pk.LId[pk.Permutation[2*nbElmt+i]]) + } + + // Evaluations of Sid1, Sid2, Sid3 on cosets of Domain[1] + pk.EvaluationId1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + copy(pk.EvaluationId1BigDomain, pk.LId[:nbElmt]) + copy(pk.EvaluationId2BigDomain, pk.LId[nbElmt:2*nbElmt]) + copy(pk.EvaluationId3BigDomain, pk.LId[2*nbElmt:]) + pk.Domain[0].FFTInverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality]) + vk.IdCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.IdCanonical[0], pk.EvaluationId1BigDomain) + copy(vk.IdCanonical[1], pk.EvaluationId2BigDomain) + copy(vk.IdCanonical[2], pk.EvaluationId3BigDomain) + + var err error + vk.Idpp[0], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId1BigDomain) + if err != nil { + return err + } + vk.Idpp[1], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId2BigDomain) + if err != nil { + return err + } + vk.Idpp[2], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId3BigDomain) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationId1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId3BigDomain, fft.DIF, fft.OnCoset()) + + pk.Domain[0].FFTInverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + + // commit S1, S2, S3 + vk.SCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.SCanonical[0], pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[1], pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[2], pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + vk.Spp[0], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[0]) + if err != nil { + return err + } + vk.Spp[1], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[1]) + if err != nil { + return err + } + vk.Spp[2], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[2]) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationS1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS3BigDomain, fft.DIF, fft.OnCoset()) + + return nil + +} + +// getIDSmallDomain returns the Lagrange form of ID on the small domain +func getIDSmallDomain(domain *fft.Domain) []fr.Element { + + res := make([]fr.Element, 3*domain.Cardinality) + + res[0].SetOne() + res[domain.Cardinality].Set(&domain.FrMultiplicativeGen) + res[2*domain.Cardinality].Square(&domain.FrMultiplicativeGen) + + for i := uint64(1); i < domain.Cardinality; i++ { + res[i].Mul(&res[i-1], &domain.Generator) + res[domain.Cardinality+i].Mul(&res[domain.Cardinality+i-1], &domain.Generator) + res[2*domain.Cardinality+i].Mul(&res[2*domain.Cardinality+i-1], &domain.Generator) + } + + return res +} + +// NbPublicWitness returns the expected public witness size (number of field elements) +func (vk *VerifyingKey) NbPublicWitness() int { + return int(vk.NbPublicVariables) +} + +// VerifyingKey returns pk.Vk +func (pk *ProvingKey) VerifyingKey() interface{} { + return pk.Vk +} diff --git a/backend/plonkfri/bls12-381/verify.go b/backend/plonkfri/bls12-381/verify.go new file mode 100644 index 0000000000..1ee33e1eee --- /dev/null +++ b/backend/plonkfri/bls12-381/verify.go @@ -0,0 +1,398 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by gnark DO NOT EDIT + +package plonkfri + +import ( + "errors" + "fmt" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/fri" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + "github.com/consensys/gnark/backend" + "math/big" +) + +var ErrInvalidAlgebraicRelation = errors.New("algebraic relation does not hold") + +func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { + cfg, err := backend.NewVerifierConfig(opts...) + if err != nil { + return fmt.Errorf("create backend config: %w", err) + } + + // 0 - derive the challenges with Fiat Shamir + fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") + + dataFiatShamir := make([][fr.Bytes]byte, len(publicWitness)+3) + for i := 0; i < len(publicWitness); i++ { + copy(dataFiatShamir[i][:], publicWitness[i].Marshal()) + } + copy(dataFiatShamir[len(publicWitness)][:], proof.LROpp[0].ID) + copy(dataFiatShamir[len(publicWitness)+1][:], proof.LROpp[1].ID) + copy(dataFiatShamir[len(publicWitness)+2][:], proof.LROpp[2].ID) + + beta, err := deriveRandomnessFixedSize(fs, "gamma", dataFiatShamir...) + if err != nil { + return err + } + + gamma, err := deriveRandomness(fs, "beta", nil) + if err != nil { + return err + } + + alpha, err := deriveRandomness(fs, "alpha", proof.Zpp.ID) + if err != nil { + return err + } + + // compute the size of the domain of evaluation of the committed polynomial, + // the opening position. The challenge zeta will be g^{i} where i is the opening + // position, and g is the generator of the fri domain. + rho := uint64(fri.GetRho()) + friSize := 2 * rho * vk.Size + var bFriSize big.Int + bFriSize.SetInt64(int64(friSize)) + frOpeningPosition, err := deriveRandomness(fs, "zeta", proof.Hpp[0].ID, proof.Hpp[1].ID, proof.Hpp[2].ID) + if err != nil { + return err + } + var bOpeningPosition big.Int + bOpeningPosition.SetBytes(frOpeningPosition.Marshal()).Mod(&bOpeningPosition, &bFriSize) + openingPosition := bOpeningPosition.Uint64() + + shiftedOpeningPosition := (openingPosition + uint64(2*rho)) % friSize + err = vk.Iopp.VerifyOpening(shiftedOpeningPosition, proof.OpeningsZmp[1], proof.Zpp) + if err != nil { + return err + } + + // 1 - verify that the commitments are low degree polynomials + + // ql, qr, qm, qo, qkIncomplete + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[2]) + if err != nil { + return err + } + + // s1, s2, s3 + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[2]) + if err != nil { + return err + } + + // id1, id2, id3 + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[2]) + if err != nil { + return err + } + + // Z + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // 2 - verify the openings + + // ql, qr, qm, qo, qkIncomplete + // openingPosition := uint64(2) + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[0], vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[1], vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[2], vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[3], vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[4], vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[0], proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[1], proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[2], proof.LROpp[2]) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[0], proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[1], proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[2], proof.Hpp[2]) + if err != nil { + return err + } + + // s0, s1, s2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[0], vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[1], vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[2], vk.Spp[2]) + if err != nil { + return err + } + + // id0, id1, id2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[0], vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[1], vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[2], vk.Idpp[2]) + if err != nil { + return err + } + + // Z, Zshift + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsZmp[0], proof.Zpp) + if err != nil { + return err + } + + // verification of the algebraic relation + var ql, qr, qm, qo, qk fr.Element + ql.Set(&proof.OpeningsQlQrQmQoQkincompletemp[0].ClaimedValue) + qr.Set(&proof.OpeningsQlQrQmQoQkincompletemp[1].ClaimedValue) + qm.Set(&proof.OpeningsQlQrQmQoQkincompletemp[2].ClaimedValue) + qo.Set(&proof.OpeningsQlQrQmQoQkincompletemp[3].ClaimedValue) + qk.Set(&proof.OpeningsQlQrQmQoQkincompletemp[4].ClaimedValue) // -> to be completed + + var l, r, o fr.Element + l.Set(&proof.OpeningsLROmp[0].ClaimedValue) + r.Set(&proof.OpeningsLROmp[1].ClaimedValue) + o.Set(&proof.OpeningsLROmp[2].ClaimedValue) + + var h1, h2, h3 fr.Element + h1.Set(&proof.OpeningsHmp[0].ClaimedValue) + h2.Set(&proof.OpeningsHmp[1].ClaimedValue) + h3.Set(&proof.OpeningsHmp[2].ClaimedValue) + + var s1, s2, s3 fr.Element + s1.Set(&proof.OpeningsS1S2S3mp[0].ClaimedValue) + s2.Set(&proof.OpeningsS1S2S3mp[1].ClaimedValue) + s3.Set(&proof.OpeningsS1S2S3mp[2].ClaimedValue) + + var id1, id2, id3 fr.Element + id1.Set(&proof.OpeningsId1Id2Id3mp[0].ClaimedValue) + id2.Set(&proof.OpeningsId1Id2Id3mp[1].ClaimedValue) + id3.Set(&proof.OpeningsId1Id2Id3mp[2].ClaimedValue) + + var z, zshift fr.Element + z.Set(&proof.OpeningsZmp[0].ClaimedValue) + zshift.Set(&proof.OpeningsZmp[1].ClaimedValue) + + // 2 - compute the LHS: (ql*l+..+qk)+ α*(z(μx)*(l+β*s₁+γ)*..-z*(l+β*id1+γ))+α²*z*(l1-1) + var zeta fr.Element + zeta.Exp(vk.GenOpening, &bOpeningPosition) + + var lhs, t1, t2, t3, tmp, tmp2 fr.Element + // 2.1 (ql*l+..+qk) + t1.Mul(&l, &ql) + tmp.Mul(&r, &qr) + t1.Add(&t1, &tmp) + tmp.Mul(&qm, &l).Mul(&tmp, &r) + t1.Add(&t1, &tmp) + tmp.Mul(&o, &qo) + t1.Add(&tmp, &t1) + tmp = completeQk(publicWitness, vk, zeta) + tmp.Add(&qk, &tmp) + t1.Add(&tmp, &t1) + + // 2.2 (z(ux)*(l+β*s1+γ)*..-z*(l+β*id1+γ)) + t2.Mul(&beta, &s1).Add(&t2, &l).Add(&t2, &gamma) + tmp.Mul(&beta, &s2).Add(&tmp, &r).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2) + tmp.Mul(&beta, &s3).Add(&tmp, &o).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2).Mul(&t2, &zshift) + + tmp.Mul(&beta, &id1).Add(&tmp, &l).Add(&tmp, &gamma) + tmp2.Mul(&beta, &id2).Add(&tmp2, &r).Add(&tmp2, &gamma) + tmp.Mul(&tmp, &tmp2) + tmp2.Mul(&beta, &id3).Add(&tmp2, &o).Add(&tmp2, &gamma) + tmp.Mul(&tmp2, &tmp).Mul(&tmp, &z) + + t2.Sub(&t2, &tmp) + + // 2.3 (z-1)*l1 + var one fr.Element + one.SetOne() + t3.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&t3, &one) + tmp.Sub(&zeta, &one).Inverse(&tmp).Mul(&tmp, &vk.SizeInv) + t3.Mul(&tmp, &t3) + tmp.Sub(&z, &one) + t3.Mul(&tmp, &t3) + + // 2.4 (ql*l+s+qk) + α*(z(ux)*(l+β*s1+γ)*...-z*(l+β*id1+γ)..)+ α²*z*(l1-1) + lhs.Set(&t3).Mul(&lhs, &alpha).Add(&lhs, &t2).Mul(&lhs, &alpha).Add(&lhs, &t1) + + // 3 - compute the RHS + var rhs fr.Element + tmp.Exp(zeta, big.NewInt(int64(vk.Size+2))) + rhs.Mul(&h3, &tmp). + Add(&rhs, &h2). + Mul(&rhs, &tmp). + Add(&rhs, &h1) + + tmp.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&tmp, &one) + rhs.Mul(&rhs, &tmp) + + // 4 - verify the relation LHS==RHS + if !rhs.Equal(&lhs) { + return ErrInvalidAlgebraicRelation + } + + return nil + +} + +// completeQk returns ∑_{i> nn + _is := bits.Reverse64(uint64((i+toShift)%nbElmts)) >> nn + + // in what follows gⁱ is understood as the generator of the chosen coset of domainBig + f[0].Mul(&evaluationIDBigDomain, &beta).Add(&f[0], &l[_i]).Add(&f[0], &gamma) //l(gⁱ)+gⁱ*β+γ + f[1].Mul(&evaluationIDBigDomain, &cosetShift).Mul(&f[1], &beta).Add(&f[1], &r[_i]).Add(&f[1], &gamma) //r(gⁱ)+u*gⁱ*β+γ + f[2].Mul(&evaluationIDBigDomain, &cosetShiftSquare).Mul(&f[2], &beta).Add(&f[2], &o[_i]).Add(&f[2], &gamma) //o(gⁱ)+u²*gⁱ*β+γ + + g[0].Mul(&pk.EvaluationS1BigDomain[_i], &beta).Add(&g[0], &l[_i]).Add(&g[0], &gamma) //l(gⁱ))+s1(gⁱ)*β+γ + g[1].Mul(&pk.EvaluationS2BigDomain[_i], &beta).Add(&g[1], &r[_i]).Add(&g[1], &gamma) //r(gⁱ))+s2(gⁱ)*β+γ + g[2].Mul(&pk.EvaluationS3BigDomain[_i], &beta).Add(&g[2], &o[_i]).Add(&g[2], &gamma) //o(gⁱ))+s3(gⁱ)*β+γ + + f[0].Mul(&f[0], &f[1]).Mul(&f[0], &f[2]).Mul(&f[0], &z[_i]) // z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + g[0].Mul(&g[0], &g[1]).Mul(&g[0], &g[2]).Mul(&g[0], &z[_is]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) + + res[_i].Sub(&g[0], &f[0]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) - z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + + evaluationIDBigDomain.Mul(&evaluationIDBigDomain, &pk.Domain[1].Generator) // gⁱ*g + } + }) + + return res +} + +// evalConstraintsInd computes the evaluation of lL+qrR+qqmL.R+qoO+k on +// the odd coset of (Z/8mZ)/(Z/4mZ), where m=nbConstraints+nbAssertions. +// +// * lsL, lsR, lsO are the evaluation of the blinded solution vectors on odd cosets +// * lsQk is the completed version of qk, in canonical version +// +// lsL, lsR, lsO are in bit reversed order, lsQk is in the correct order. +func evalConstraintsInd(pk *ProvingKey, lsL, lsR, lsO, lsQk []fr.Element) []fr.Element { + + res := make([]fr.Element, pk.Domain[1].Cardinality) + // nn := uint64(64 - bits.TrailingZeros64(pk.Domain[1].Cardinality)) + + utils.Parallelize(len(res), func(start, end int) { + + var t0, t1 fr.Element + + for i := start; i < end; i++ { + + // irev := bits.Reverse64(uint64(i)) >> nn + + t1.Mul(&pk.EvaluationQmDomainBigBitReversed[i], &lsR[i]) // qm.r + t1.Add(&t1, &pk.EvaluationQlDomainBigBitReversed[i]) // qm.r + ql + t1.Mul(&t1, &lsL[i]) // qm.l.r + ql.l + + t0.Mul(&pk.EvaluationQrDomainBigBitReversed[i], &lsR[i]) + t0.Add(&t0, &t1) // qm.l.r + ql.l + qr.r + + t1.Mul(&pk.EvaluationQoDomainBigBitReversed[i], &lsO[i]) + t0.Add(&t0, &t1) // ql.l + qr.r + qm.l.r + qo.o + res[i].Add(&t0, &lsQk[i]) // ql.l + qr.r + qm.l.r + qo.o + k + + } + }) + + return res +} + +// fftBigCosetWOBitReverse evaluates poly (canonical form) of degree m> nn + + t.Sub(&evaluationBlindedZDomainBigBitReversed[_i], &one) // evaluates L₁(X)*(Z(X)-1) on a coset of the big domain + h[_i].Mul(&startsAtOne[_i], &t).Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintOrderingBitReversed[_i]). + Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintsIndBitReversed[_i]). + Mul(&h[_i], &evaluationXnMinusOneInverse[i%ratio]) + } + }) + + // put h in canonical form. h is of degree 3*(n+1)+2. + // using fft.DIT put h revert bit reverse + pk.Domain[1].FFTInverse(h, fft.DIT, fft.OnCoset()) + + // degree of hi is n+2 because of the blinding + h1 := h[:pk.Domain[0].Cardinality+2] + h2 := h[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] + h3 := h[2*(pk.Domain[0].Cardinality+2) : 3*(pk.Domain[0].Cardinality+2)] + + return h1, h2, h3 + +} + +// computeZ computes Z, in canonical basis, where: +// +// - Z of degree n (domainNum.Cardinality) +// +// - Z(1)=1 +// (l_i+z**i+gamma)*(r_i+u*z**i+gamma)*(o_i+u**2z**i+gamma) +// +// - for i>0: Z(u**i) = Pi_{k "small" domain, used for individual polynomials + // 1 -> "big" domain, used for the computation of the quotient + Domain [2]fft.Domain + + // s1, s2, s3 (L=Lagrange basis small domain, C=canonical basis, Ls=Lagrange Shifted big domain) + LId []fr.Element + EvaluationId1BigDomain, EvaluationId2BigDomain, EvaluationId3BigDomain []fr.Element + EvaluationS1BigDomain, EvaluationS2BigDomain, EvaluationS3BigDomain []fr.Element + + // position -> permuted position (position in [0,3*sizeSystem-1]) + Permutation []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, that is the closest power of 2 bounding above + // number of constraints+number of public inputs + Size uint64 + SizeInv fr.Element + Generator fr.Element + NbPublicVariables uint64 + + // cosetShift generator of the coset on the small domain + CosetShift fr.Element + + // S commitments to S1, S2, S3 + SCanonical [3][]fr.Element + Spp [3]fri.ProofOfProximity + + // Id commitments to Id1, Id2, Id3 + // Id [3]Commitment + IdCanonical [3][]fr.Element + Idpp [3]fri.ProofOfProximity + + // Commitments to ql, qr, qm, qo prepended with as many zeroes (ones for l) as there are public inputs. + // In particular Qk is not complete. + Qpp [5]fri.ProofOfProximity // Ql, Qr, Qm, Qo, Qk + + // Iopp scheme (currently one for each size of polynomial) + Iopp fri.Iopp + + // generator of the group on which the Iopp works. If i is the opening position, + // the polynomials will be opened at genOpening^{i}. + GenOpening fr.Element +} + +// Setup sets proving and verifying keys +func Setup(spr *cs.SparseR1CS) (*ProvingKey, *VerifyingKey, error) { + + var pk ProvingKey + var vk VerifyingKey + + // The verifying key shares data with the proving key + pk.Vk = &vk + + nbConstraints := spr.GetNbConstraints() + + // fft domains + sizeSystem := uint64(nbConstraints + len(spr.Public)) // len(spr.Public) is for the placeholder constraints + pk.Domain[0] = *fft.NewDomain(sizeSystem) + + // h, the quotient polynomial is of degree 3(n+1)+2, so it's in a 3(n+2) dim vector space, + // the domain is the next power of 2 superior to 3(n+2). 4*domainNum is enough in all cases + // except when n<6. + if sizeSystem < 6 { + pk.Domain[1] = *fft.NewDomain(8 * sizeSystem) + } else { + pk.Domain[1] = *fft.NewDomain(4 * sizeSystem) + } + pk.Vk.CosetShift.Set(&pk.Domain[0].FrMultiplicativeGen) + + vk.Size = pk.Domain[0].Cardinality + vk.SizeInv.SetUint64(vk.Size).Inverse(&vk.SizeInv) + vk.Generator.Set(&pk.Domain[0].Generator) + vk.NbPublicVariables = uint64(len(spr.Public)) + + // IOP schemess + // The +2 is to handle the blinding. + sizeIopp := pk.Domain[0].Cardinality + 2 + vk.Iopp = fri.RADIX_2_FRI.New(sizeIopp, sha256.New()) + // only there to access the group used in FRI... + rho := uint64(fri.GetRho()) + // we multiply by 2 because the IOP is created with size pk.Domain[0].Cardinality + 2 (because + // of the blinding), so the domain will be rho*size_domain where size_domain is the next power + // of 2 after pk.Domain[0].Cardinality + 2, which is 2*rho*pk.Domain[0].Cardinality + tmpDomain := fft.NewDomain(2 * rho * pk.Domain[0].Cardinality) + vk.GenOpening.Set(&tmpDomain.Generator) + + // public polynomials corresponding to constraints: [ placholders | constraints | assertions ] + pk.EvaluationQlDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQrDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQmDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQoDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.LQkIncompleteDomainSmall = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQkIncomplete = make([]fr.Element, pk.Domain[0].Cardinality) + + for i := 0; i < len(spr.Public); i++ { // placeholders (-PUB_INPUT_i + qk_i = 0) TODO should return error if size is inconsistent + pk.EvaluationQlDomainBigBitReversed[i].SetOne().Neg(&pk.EvaluationQlDomainBigBitReversed[i]) + pk.EvaluationQrDomainBigBitReversed[i].SetZero() + pk.EvaluationQmDomainBigBitReversed[i].SetZero() + pk.EvaluationQoDomainBigBitReversed[i].SetZero() + pk.LQkIncompleteDomainSmall[i].SetZero() // --> to be completed by the prover + pk.CQkIncomplete[i].Set(&pk.LQkIncompleteDomainSmall[i]) // --> to be completed by the prover + } + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + pk.EvaluationQlDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QL]) + pk.EvaluationQrDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QR]) + pk.EvaluationQmDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QM]) + pk.EvaluationQoDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QO]) + pk.LQkIncompleteDomainSmall[offset+j].Set(&spr.Coefficients[c.QC]) + pk.CQkIncomplete[offset+j].Set(&pk.LQkIncompleteDomainSmall[offset+j]) + + j++ + } + + pk.Domain[0].FFTInverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.CQkIncomplete, fft.DIF) + fft.BitReverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.CQkIncomplete) + + // Commit to the polynomials to set up the verifying key + pk.CQl = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQr = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQm = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQo = make([]fr.Element, pk.Domain[0].Cardinality) + copy(pk.CQl, pk.EvaluationQlDomainBigBitReversed) + copy(pk.CQr, pk.EvaluationQrDomainBigBitReversed) + copy(pk.CQm, pk.EvaluationQmDomainBigBitReversed) + copy(pk.CQo, pk.EvaluationQoDomainBigBitReversed) + var err error + vk.Qpp[0], err = vk.Iopp.BuildProofOfProximity(pk.CQl) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[1], err = vk.Iopp.BuildProofOfProximity(pk.CQr) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[2], err = vk.Iopp.BuildProofOfProximity(pk.CQm) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[3], err = vk.Iopp.BuildProofOfProximity(pk.CQo) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[4], err = vk.Iopp.BuildProofOfProximity(pk.CQkIncomplete) + if err != nil { + return &pk, &vk, err + } + + pk.Domain[1].FFT(pk.EvaluationQlDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQrDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQmDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQoDomainBigBitReversed, fft.DIF, fft.OnCoset()) + + // build permutation. Note: at this stage, the permutation takes in account the placeholders + buildPermutation(spr, &pk) + + // set s1, s2, s3 + err = computePermutationPolynomials(&pk, &vk) + if err != nil { + return &pk, &vk, err + } + + return &pk, &vk, nil + +} + +// buildPermutation builds the Permutation associated with a circuit. +// +// The permutation s is composed of cycles of maximum length such that +// +// s. (l||r||o) = (l||r||o) +// +// , where l||r||o is the concatenation of the indices of l, r, o in +// ql.l+qr.r+qm.l.r+qo.O+k = 0. +// +// The permutation is encoded as a slice s of size 3*size(l), where the +// i-th entry of l||r||o is sent to the s[i]-th entry, so it acts on a tab +// like this: for i in tab: tab[i] = tab[permutation[i]] +func buildPermutation(spr *cs.SparseR1CS, pk *ProvingKey) { + + nbVariables := spr.NbInternalVariables + len(spr.Public) + len(spr.Secret) + sizeSolution := int(pk.Domain[0].Cardinality) + + // init permutation + pk.Permutation = make([]int64, 3*sizeSolution) + for i := 0; i < len(pk.Permutation); i++ { + pk.Permutation[i] = -1 + } + + // init LRO position -> variable_ID + lro := make([]int, 3*sizeSolution) // position -> variable_ID + for i := 0; i < len(spr.Public); i++ { + lro[i] = i // IDs of LRO associated to placeholders (only L needs to be taken care of) + } + + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + lro[offset+j] = int(c.XA) + lro[sizeSolution+offset+j] = int(c.XB) + lro[2*sizeSolution+offset+j] = int(c.XC) + j++ + } + + // init cycle: + // map ID -> last position the ID was seen + cycle := make([]int64, nbVariables) + for i := 0; i < len(cycle); i++ { + cycle[i] = -1 + } + + for i := 0; i < len(lro); i++ { + if cycle[lro[i]] != -1 { + // if != -1, it means we already encountered this value + // so we need to set the corresponding permutation index. + pk.Permutation[i] = cycle[lro[i]] + } + cycle[lro[i]] = int64(i) + } + + // complete the Permutation by filling the first IDs encountered + for i := 0; i < len(pk.Permutation); i++ { + if pk.Permutation[i] == -1 { + pk.Permutation[i] = cycle[lro[i]] + } + } +} + +// computePermutationPolynomials computes the LDE (Lagrange basis) of the permutations +// s1, s2, s3. +// +// 0 1 .. n-1 | n n+1 .. 2*n-1 | 2n 2n+1 .. 3n-1 | +// +// | +// | Permutation +// +// s00 s01 .. s0n-1 s10 s11 .. s1n-1 s20 s21 .. s2n-1 v +// \---------------/ \--------------------/ \------------------------/ +// +// s1 (LDE) s2 (LDE) s3 (LDE) +func computePermutationPolynomials(pk *ProvingKey, vk *VerifyingKey) error { + + nbElmt := int(pk.Domain[0].Cardinality) + + // sID = [1,..,g^{n-1},s,..,s*g^{n-1},s^2,..,s^2*g^{n-1}] + pk.LId = getIDSmallDomain(&pk.Domain[0]) + + // canonical form of S1, S2, S3 + pk.EvaluationS1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + for i := 0; i < nbElmt; i++ { + pk.EvaluationS1BigDomain[i].Set(&pk.LId[pk.Permutation[i]]) + pk.EvaluationS2BigDomain[i].Set(&pk.LId[pk.Permutation[nbElmt+i]]) + pk.EvaluationS3BigDomain[i].Set(&pk.LId[pk.Permutation[2*nbElmt+i]]) + } + + // Evaluations of Sid1, Sid2, Sid3 on cosets of Domain[1] + pk.EvaluationId1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + copy(pk.EvaluationId1BigDomain, pk.LId[:nbElmt]) + copy(pk.EvaluationId2BigDomain, pk.LId[nbElmt:2*nbElmt]) + copy(pk.EvaluationId3BigDomain, pk.LId[2*nbElmt:]) + pk.Domain[0].FFTInverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality]) + vk.IdCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.IdCanonical[0], pk.EvaluationId1BigDomain) + copy(vk.IdCanonical[1], pk.EvaluationId2BigDomain) + copy(vk.IdCanonical[2], pk.EvaluationId3BigDomain) + + var err error + vk.Idpp[0], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId1BigDomain) + if err != nil { + return err + } + vk.Idpp[1], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId2BigDomain) + if err != nil { + return err + } + vk.Idpp[2], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId3BigDomain) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationId1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId3BigDomain, fft.DIF, fft.OnCoset()) + + pk.Domain[0].FFTInverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + + // commit S1, S2, S3 + vk.SCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.SCanonical[0], pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[1], pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[2], pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + vk.Spp[0], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[0]) + if err != nil { + return err + } + vk.Spp[1], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[1]) + if err != nil { + return err + } + vk.Spp[2], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[2]) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationS1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS3BigDomain, fft.DIF, fft.OnCoset()) + + return nil + +} + +// getIDSmallDomain returns the Lagrange form of ID on the small domain +func getIDSmallDomain(domain *fft.Domain) []fr.Element { + + res := make([]fr.Element, 3*domain.Cardinality) + + res[0].SetOne() + res[domain.Cardinality].Set(&domain.FrMultiplicativeGen) + res[2*domain.Cardinality].Square(&domain.FrMultiplicativeGen) + + for i := uint64(1); i < domain.Cardinality; i++ { + res[i].Mul(&res[i-1], &domain.Generator) + res[domain.Cardinality+i].Mul(&res[domain.Cardinality+i-1], &domain.Generator) + res[2*domain.Cardinality+i].Mul(&res[2*domain.Cardinality+i-1], &domain.Generator) + } + + return res +} + +// NbPublicWitness returns the expected public witness size (number of field elements) +func (vk *VerifyingKey) NbPublicWitness() int { + return int(vk.NbPublicVariables) +} + +// VerifyingKey returns pk.Vk +func (pk *ProvingKey) VerifyingKey() interface{} { + return pk.Vk +} diff --git a/backend/plonkfri/bls24-315/verify.go b/backend/plonkfri/bls24-315/verify.go new file mode 100644 index 0000000000..c5b58259cf --- /dev/null +++ b/backend/plonkfri/bls24-315/verify.go @@ -0,0 +1,398 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by gnark DO NOT EDIT + +package plonkfri + +import ( + "errors" + "fmt" + "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" + "github.com/consensys/gnark-crypto/ecc/bls24-315/fr/fri" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + "github.com/consensys/gnark/backend" + "math/big" +) + +var ErrInvalidAlgebraicRelation = errors.New("algebraic relation does not hold") + +func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { + cfg, err := backend.NewVerifierConfig(opts...) + if err != nil { + return fmt.Errorf("create backend config: %w", err) + } + + // 0 - derive the challenges with Fiat Shamir + fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") + + dataFiatShamir := make([][fr.Bytes]byte, len(publicWitness)+3) + for i := 0; i < len(publicWitness); i++ { + copy(dataFiatShamir[i][:], publicWitness[i].Marshal()) + } + copy(dataFiatShamir[len(publicWitness)][:], proof.LROpp[0].ID) + copy(dataFiatShamir[len(publicWitness)+1][:], proof.LROpp[1].ID) + copy(dataFiatShamir[len(publicWitness)+2][:], proof.LROpp[2].ID) + + beta, err := deriveRandomnessFixedSize(fs, "gamma", dataFiatShamir...) + if err != nil { + return err + } + + gamma, err := deriveRandomness(fs, "beta", nil) + if err != nil { + return err + } + + alpha, err := deriveRandomness(fs, "alpha", proof.Zpp.ID) + if err != nil { + return err + } + + // compute the size of the domain of evaluation of the committed polynomial, + // the opening position. The challenge zeta will be g^{i} where i is the opening + // position, and g is the generator of the fri domain. + rho := uint64(fri.GetRho()) + friSize := 2 * rho * vk.Size + var bFriSize big.Int + bFriSize.SetInt64(int64(friSize)) + frOpeningPosition, err := deriveRandomness(fs, "zeta", proof.Hpp[0].ID, proof.Hpp[1].ID, proof.Hpp[2].ID) + if err != nil { + return err + } + var bOpeningPosition big.Int + bOpeningPosition.SetBytes(frOpeningPosition.Marshal()).Mod(&bOpeningPosition, &bFriSize) + openingPosition := bOpeningPosition.Uint64() + + shiftedOpeningPosition := (openingPosition + uint64(2*rho)) % friSize + err = vk.Iopp.VerifyOpening(shiftedOpeningPosition, proof.OpeningsZmp[1], proof.Zpp) + if err != nil { + return err + } + + // 1 - verify that the commitments are low degree polynomials + + // ql, qr, qm, qo, qkIncomplete + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[2]) + if err != nil { + return err + } + + // s1, s2, s3 + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[2]) + if err != nil { + return err + } + + // id1, id2, id3 + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[2]) + if err != nil { + return err + } + + // Z + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // 2 - verify the openings + + // ql, qr, qm, qo, qkIncomplete + // openingPosition := uint64(2) + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[0], vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[1], vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[2], vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[3], vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[4], vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[0], proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[1], proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[2], proof.LROpp[2]) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[0], proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[1], proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[2], proof.Hpp[2]) + if err != nil { + return err + } + + // s0, s1, s2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[0], vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[1], vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[2], vk.Spp[2]) + if err != nil { + return err + } + + // id0, id1, id2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[0], vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[1], vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[2], vk.Idpp[2]) + if err != nil { + return err + } + + // Z, Zshift + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsZmp[0], proof.Zpp) + if err != nil { + return err + } + + // verification of the algebraic relation + var ql, qr, qm, qo, qk fr.Element + ql.Set(&proof.OpeningsQlQrQmQoQkincompletemp[0].ClaimedValue) + qr.Set(&proof.OpeningsQlQrQmQoQkincompletemp[1].ClaimedValue) + qm.Set(&proof.OpeningsQlQrQmQoQkincompletemp[2].ClaimedValue) + qo.Set(&proof.OpeningsQlQrQmQoQkincompletemp[3].ClaimedValue) + qk.Set(&proof.OpeningsQlQrQmQoQkincompletemp[4].ClaimedValue) // -> to be completed + + var l, r, o fr.Element + l.Set(&proof.OpeningsLROmp[0].ClaimedValue) + r.Set(&proof.OpeningsLROmp[1].ClaimedValue) + o.Set(&proof.OpeningsLROmp[2].ClaimedValue) + + var h1, h2, h3 fr.Element + h1.Set(&proof.OpeningsHmp[0].ClaimedValue) + h2.Set(&proof.OpeningsHmp[1].ClaimedValue) + h3.Set(&proof.OpeningsHmp[2].ClaimedValue) + + var s1, s2, s3 fr.Element + s1.Set(&proof.OpeningsS1S2S3mp[0].ClaimedValue) + s2.Set(&proof.OpeningsS1S2S3mp[1].ClaimedValue) + s3.Set(&proof.OpeningsS1S2S3mp[2].ClaimedValue) + + var id1, id2, id3 fr.Element + id1.Set(&proof.OpeningsId1Id2Id3mp[0].ClaimedValue) + id2.Set(&proof.OpeningsId1Id2Id3mp[1].ClaimedValue) + id3.Set(&proof.OpeningsId1Id2Id3mp[2].ClaimedValue) + + var z, zshift fr.Element + z.Set(&proof.OpeningsZmp[0].ClaimedValue) + zshift.Set(&proof.OpeningsZmp[1].ClaimedValue) + + // 2 - compute the LHS: (ql*l+..+qk)+ α*(z(μx)*(l+β*s₁+γ)*..-z*(l+β*id1+γ))+α²*z*(l1-1) + var zeta fr.Element + zeta.Exp(vk.GenOpening, &bOpeningPosition) + + var lhs, t1, t2, t3, tmp, tmp2 fr.Element + // 2.1 (ql*l+..+qk) + t1.Mul(&l, &ql) + tmp.Mul(&r, &qr) + t1.Add(&t1, &tmp) + tmp.Mul(&qm, &l).Mul(&tmp, &r) + t1.Add(&t1, &tmp) + tmp.Mul(&o, &qo) + t1.Add(&tmp, &t1) + tmp = completeQk(publicWitness, vk, zeta) + tmp.Add(&qk, &tmp) + t1.Add(&tmp, &t1) + + // 2.2 (z(ux)*(l+β*s1+γ)*..-z*(l+β*id1+γ)) + t2.Mul(&beta, &s1).Add(&t2, &l).Add(&t2, &gamma) + tmp.Mul(&beta, &s2).Add(&tmp, &r).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2) + tmp.Mul(&beta, &s3).Add(&tmp, &o).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2).Mul(&t2, &zshift) + + tmp.Mul(&beta, &id1).Add(&tmp, &l).Add(&tmp, &gamma) + tmp2.Mul(&beta, &id2).Add(&tmp2, &r).Add(&tmp2, &gamma) + tmp.Mul(&tmp, &tmp2) + tmp2.Mul(&beta, &id3).Add(&tmp2, &o).Add(&tmp2, &gamma) + tmp.Mul(&tmp2, &tmp).Mul(&tmp, &z) + + t2.Sub(&t2, &tmp) + + // 2.3 (z-1)*l1 + var one fr.Element + one.SetOne() + t3.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&t3, &one) + tmp.Sub(&zeta, &one).Inverse(&tmp).Mul(&tmp, &vk.SizeInv) + t3.Mul(&tmp, &t3) + tmp.Sub(&z, &one) + t3.Mul(&tmp, &t3) + + // 2.4 (ql*l+s+qk) + α*(z(ux)*(l+β*s1+γ)*...-z*(l+β*id1+γ)..)+ α²*z*(l1-1) + lhs.Set(&t3).Mul(&lhs, &alpha).Add(&lhs, &t2).Mul(&lhs, &alpha).Add(&lhs, &t1) + + // 3 - compute the RHS + var rhs fr.Element + tmp.Exp(zeta, big.NewInt(int64(vk.Size+2))) + rhs.Mul(&h3, &tmp). + Add(&rhs, &h2). + Mul(&rhs, &tmp). + Add(&rhs, &h1) + + tmp.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&tmp, &one) + rhs.Mul(&rhs, &tmp) + + // 4 - verify the relation LHS==RHS + if !rhs.Equal(&lhs) { + return ErrInvalidAlgebraicRelation + } + + return nil + +} + +// completeQk returns ∑_{i> nn + _is := bits.Reverse64(uint64((i+toShift)%nbElmts)) >> nn + + // in what follows gⁱ is understood as the generator of the chosen coset of domainBig + f[0].Mul(&evaluationIDBigDomain, &beta).Add(&f[0], &l[_i]).Add(&f[0], &gamma) //l(gⁱ)+gⁱ*β+γ + f[1].Mul(&evaluationIDBigDomain, &cosetShift).Mul(&f[1], &beta).Add(&f[1], &r[_i]).Add(&f[1], &gamma) //r(gⁱ)+u*gⁱ*β+γ + f[2].Mul(&evaluationIDBigDomain, &cosetShiftSquare).Mul(&f[2], &beta).Add(&f[2], &o[_i]).Add(&f[2], &gamma) //o(gⁱ)+u²*gⁱ*β+γ + + g[0].Mul(&pk.EvaluationS1BigDomain[_i], &beta).Add(&g[0], &l[_i]).Add(&g[0], &gamma) //l(gⁱ))+s1(gⁱ)*β+γ + g[1].Mul(&pk.EvaluationS2BigDomain[_i], &beta).Add(&g[1], &r[_i]).Add(&g[1], &gamma) //r(gⁱ))+s2(gⁱ)*β+γ + g[2].Mul(&pk.EvaluationS3BigDomain[_i], &beta).Add(&g[2], &o[_i]).Add(&g[2], &gamma) //o(gⁱ))+s3(gⁱ)*β+γ + + f[0].Mul(&f[0], &f[1]).Mul(&f[0], &f[2]).Mul(&f[0], &z[_i]) // z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + g[0].Mul(&g[0], &g[1]).Mul(&g[0], &g[2]).Mul(&g[0], &z[_is]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) + + res[_i].Sub(&g[0], &f[0]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) - z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + + evaluationIDBigDomain.Mul(&evaluationIDBigDomain, &pk.Domain[1].Generator) // gⁱ*g + } + }) + + return res +} + +// evalConstraintsInd computes the evaluation of lL+qrR+qqmL.R+qoO+k on +// the odd coset of (Z/8mZ)/(Z/4mZ), where m=nbConstraints+nbAssertions. +// +// * lsL, lsR, lsO are the evaluation of the blinded solution vectors on odd cosets +// * lsQk is the completed version of qk, in canonical version +// +// lsL, lsR, lsO are in bit reversed order, lsQk is in the correct order. +func evalConstraintsInd(pk *ProvingKey, lsL, lsR, lsO, lsQk []fr.Element) []fr.Element { + + res := make([]fr.Element, pk.Domain[1].Cardinality) + // nn := uint64(64 - bits.TrailingZeros64(pk.Domain[1].Cardinality)) + + utils.Parallelize(len(res), func(start, end int) { + + var t0, t1 fr.Element + + for i := start; i < end; i++ { + + // irev := bits.Reverse64(uint64(i)) >> nn + + t1.Mul(&pk.EvaluationQmDomainBigBitReversed[i], &lsR[i]) // qm.r + t1.Add(&t1, &pk.EvaluationQlDomainBigBitReversed[i]) // qm.r + ql + t1.Mul(&t1, &lsL[i]) // qm.l.r + ql.l + + t0.Mul(&pk.EvaluationQrDomainBigBitReversed[i], &lsR[i]) + t0.Add(&t0, &t1) // qm.l.r + ql.l + qr.r + + t1.Mul(&pk.EvaluationQoDomainBigBitReversed[i], &lsO[i]) + t0.Add(&t0, &t1) // ql.l + qr.r + qm.l.r + qo.o + res[i].Add(&t0, &lsQk[i]) // ql.l + qr.r + qm.l.r + qo.o + k + + } + }) + + return res +} + +// fftBigCosetWOBitReverse evaluates poly (canonical form) of degree m> nn + + t.Sub(&evaluationBlindedZDomainBigBitReversed[_i], &one) // evaluates L₁(X)*(Z(X)-1) on a coset of the big domain + h[_i].Mul(&startsAtOne[_i], &t).Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintOrderingBitReversed[_i]). + Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintsIndBitReversed[_i]). + Mul(&h[_i], &evaluationXnMinusOneInverse[i%ratio]) + } + }) + + // put h in canonical form. h is of degree 3*(n+1)+2. + // using fft.DIT put h revert bit reverse + pk.Domain[1].FFTInverse(h, fft.DIT, fft.OnCoset()) + + // degree of hi is n+2 because of the blinding + h1 := h[:pk.Domain[0].Cardinality+2] + h2 := h[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] + h3 := h[2*(pk.Domain[0].Cardinality+2) : 3*(pk.Domain[0].Cardinality+2)] + + return h1, h2, h3 + +} + +// computeZ computes Z, in canonical basis, where: +// +// - Z of degree n (domainNum.Cardinality) +// +// - Z(1)=1 +// (l_i+z**i+gamma)*(r_i+u*z**i+gamma)*(o_i+u**2z**i+gamma) +// +// - for i>0: Z(u**i) = Pi_{k "small" domain, used for individual polynomials + // 1 -> "big" domain, used for the computation of the quotient + Domain [2]fft.Domain + + // s1, s2, s3 (L=Lagrange basis small domain, C=canonical basis, Ls=Lagrange Shifted big domain) + LId []fr.Element + EvaluationId1BigDomain, EvaluationId2BigDomain, EvaluationId3BigDomain []fr.Element + EvaluationS1BigDomain, EvaluationS2BigDomain, EvaluationS3BigDomain []fr.Element + + // position -> permuted position (position in [0,3*sizeSystem-1]) + Permutation []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, that is the closest power of 2 bounding above + // number of constraints+number of public inputs + Size uint64 + SizeInv fr.Element + Generator fr.Element + NbPublicVariables uint64 + + // cosetShift generator of the coset on the small domain + CosetShift fr.Element + + // S commitments to S1, S2, S3 + SCanonical [3][]fr.Element + Spp [3]fri.ProofOfProximity + + // Id commitments to Id1, Id2, Id3 + // Id [3]Commitment + IdCanonical [3][]fr.Element + Idpp [3]fri.ProofOfProximity + + // Commitments to ql, qr, qm, qo prepended with as many zeroes (ones for l) as there are public inputs. + // In particular Qk is not complete. + Qpp [5]fri.ProofOfProximity // Ql, Qr, Qm, Qo, Qk + + // Iopp scheme (currently one for each size of polynomial) + Iopp fri.Iopp + + // generator of the group on which the Iopp works. If i is the opening position, + // the polynomials will be opened at genOpening^{i}. + GenOpening fr.Element +} + +// Setup sets proving and verifying keys +func Setup(spr *cs.SparseR1CS) (*ProvingKey, *VerifyingKey, error) { + + var pk ProvingKey + var vk VerifyingKey + + // The verifying key shares data with the proving key + pk.Vk = &vk + + nbConstraints := spr.GetNbConstraints() + + // fft domains + sizeSystem := uint64(nbConstraints + len(spr.Public)) // len(spr.Public) is for the placeholder constraints + pk.Domain[0] = *fft.NewDomain(sizeSystem) + + // h, the quotient polynomial is of degree 3(n+1)+2, so it's in a 3(n+2) dim vector space, + // the domain is the next power of 2 superior to 3(n+2). 4*domainNum is enough in all cases + // except when n<6. + if sizeSystem < 6 { + pk.Domain[1] = *fft.NewDomain(8 * sizeSystem) + } else { + pk.Domain[1] = *fft.NewDomain(4 * sizeSystem) + } + pk.Vk.CosetShift.Set(&pk.Domain[0].FrMultiplicativeGen) + + vk.Size = pk.Domain[0].Cardinality + vk.SizeInv.SetUint64(vk.Size).Inverse(&vk.SizeInv) + vk.Generator.Set(&pk.Domain[0].Generator) + vk.NbPublicVariables = uint64(len(spr.Public)) + + // IOP schemess + // The +2 is to handle the blinding. + sizeIopp := pk.Domain[0].Cardinality + 2 + vk.Iopp = fri.RADIX_2_FRI.New(sizeIopp, sha256.New()) + // only there to access the group used in FRI... + rho := uint64(fri.GetRho()) + // we multiply by 2 because the IOP is created with size pk.Domain[0].Cardinality + 2 (because + // of the blinding), so the domain will be rho*size_domain where size_domain is the next power + // of 2 after pk.Domain[0].Cardinality + 2, which is 2*rho*pk.Domain[0].Cardinality + tmpDomain := fft.NewDomain(2 * rho * pk.Domain[0].Cardinality) + vk.GenOpening.Set(&tmpDomain.Generator) + + // public polynomials corresponding to constraints: [ placholders | constraints | assertions ] + pk.EvaluationQlDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQrDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQmDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQoDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.LQkIncompleteDomainSmall = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQkIncomplete = make([]fr.Element, pk.Domain[0].Cardinality) + + for i := 0; i < len(spr.Public); i++ { // placeholders (-PUB_INPUT_i + qk_i = 0) TODO should return error if size is inconsistent + pk.EvaluationQlDomainBigBitReversed[i].SetOne().Neg(&pk.EvaluationQlDomainBigBitReversed[i]) + pk.EvaluationQrDomainBigBitReversed[i].SetZero() + pk.EvaluationQmDomainBigBitReversed[i].SetZero() + pk.EvaluationQoDomainBigBitReversed[i].SetZero() + pk.LQkIncompleteDomainSmall[i].SetZero() // --> to be completed by the prover + pk.CQkIncomplete[i].Set(&pk.LQkIncompleteDomainSmall[i]) // --> to be completed by the prover + } + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + pk.EvaluationQlDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QL]) + pk.EvaluationQrDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QR]) + pk.EvaluationQmDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QM]) + pk.EvaluationQoDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QO]) + pk.LQkIncompleteDomainSmall[offset+j].Set(&spr.Coefficients[c.QC]) + pk.CQkIncomplete[offset+j].Set(&pk.LQkIncompleteDomainSmall[offset+j]) + + j++ + } + + pk.Domain[0].FFTInverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.CQkIncomplete, fft.DIF) + fft.BitReverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.CQkIncomplete) + + // Commit to the polynomials to set up the verifying key + pk.CQl = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQr = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQm = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQo = make([]fr.Element, pk.Domain[0].Cardinality) + copy(pk.CQl, pk.EvaluationQlDomainBigBitReversed) + copy(pk.CQr, pk.EvaluationQrDomainBigBitReversed) + copy(pk.CQm, pk.EvaluationQmDomainBigBitReversed) + copy(pk.CQo, pk.EvaluationQoDomainBigBitReversed) + var err error + vk.Qpp[0], err = vk.Iopp.BuildProofOfProximity(pk.CQl) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[1], err = vk.Iopp.BuildProofOfProximity(pk.CQr) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[2], err = vk.Iopp.BuildProofOfProximity(pk.CQm) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[3], err = vk.Iopp.BuildProofOfProximity(pk.CQo) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[4], err = vk.Iopp.BuildProofOfProximity(pk.CQkIncomplete) + if err != nil { + return &pk, &vk, err + } + + pk.Domain[1].FFT(pk.EvaluationQlDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQrDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQmDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQoDomainBigBitReversed, fft.DIF, fft.OnCoset()) + + // build permutation. Note: at this stage, the permutation takes in account the placeholders + buildPermutation(spr, &pk) + + // set s1, s2, s3 + err = computePermutationPolynomials(&pk, &vk) + if err != nil { + return &pk, &vk, err + } + + return &pk, &vk, nil + +} + +// buildPermutation builds the Permutation associated with a circuit. +// +// The permutation s is composed of cycles of maximum length such that +// +// s. (l||r||o) = (l||r||o) +// +// , where l||r||o is the concatenation of the indices of l, r, o in +// ql.l+qr.r+qm.l.r+qo.O+k = 0. +// +// The permutation is encoded as a slice s of size 3*size(l), where the +// i-th entry of l||r||o is sent to the s[i]-th entry, so it acts on a tab +// like this: for i in tab: tab[i] = tab[permutation[i]] +func buildPermutation(spr *cs.SparseR1CS, pk *ProvingKey) { + + nbVariables := spr.NbInternalVariables + len(spr.Public) + len(spr.Secret) + sizeSolution := int(pk.Domain[0].Cardinality) + + // init permutation + pk.Permutation = make([]int64, 3*sizeSolution) + for i := 0; i < len(pk.Permutation); i++ { + pk.Permutation[i] = -1 + } + + // init LRO position -> variable_ID + lro := make([]int, 3*sizeSolution) // position -> variable_ID + for i := 0; i < len(spr.Public); i++ { + lro[i] = i // IDs of LRO associated to placeholders (only L needs to be taken care of) + } + + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + lro[offset+j] = int(c.XA) + lro[sizeSolution+offset+j] = int(c.XB) + lro[2*sizeSolution+offset+j] = int(c.XC) + j++ + } + + // init cycle: + // map ID -> last position the ID was seen + cycle := make([]int64, nbVariables) + for i := 0; i < len(cycle); i++ { + cycle[i] = -1 + } + + for i := 0; i < len(lro); i++ { + if cycle[lro[i]] != -1 { + // if != -1, it means we already encountered this value + // so we need to set the corresponding permutation index. + pk.Permutation[i] = cycle[lro[i]] + } + cycle[lro[i]] = int64(i) + } + + // complete the Permutation by filling the first IDs encountered + for i := 0; i < len(pk.Permutation); i++ { + if pk.Permutation[i] == -1 { + pk.Permutation[i] = cycle[lro[i]] + } + } +} + +// computePermutationPolynomials computes the LDE (Lagrange basis) of the permutations +// s1, s2, s3. +// +// 0 1 .. n-1 | n n+1 .. 2*n-1 | 2n 2n+1 .. 3n-1 | +// +// | +// | Permutation +// +// s00 s01 .. s0n-1 s10 s11 .. s1n-1 s20 s21 .. s2n-1 v +// \---------------/ \--------------------/ \------------------------/ +// +// s1 (LDE) s2 (LDE) s3 (LDE) +func computePermutationPolynomials(pk *ProvingKey, vk *VerifyingKey) error { + + nbElmt := int(pk.Domain[0].Cardinality) + + // sID = [1,..,g^{n-1},s,..,s*g^{n-1},s^2,..,s^2*g^{n-1}] + pk.LId = getIDSmallDomain(&pk.Domain[0]) + + // canonical form of S1, S2, S3 + pk.EvaluationS1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + for i := 0; i < nbElmt; i++ { + pk.EvaluationS1BigDomain[i].Set(&pk.LId[pk.Permutation[i]]) + pk.EvaluationS2BigDomain[i].Set(&pk.LId[pk.Permutation[nbElmt+i]]) + pk.EvaluationS3BigDomain[i].Set(&pk.LId[pk.Permutation[2*nbElmt+i]]) + } + + // Evaluations of Sid1, Sid2, Sid3 on cosets of Domain[1] + pk.EvaluationId1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + copy(pk.EvaluationId1BigDomain, pk.LId[:nbElmt]) + copy(pk.EvaluationId2BigDomain, pk.LId[nbElmt:2*nbElmt]) + copy(pk.EvaluationId3BigDomain, pk.LId[2*nbElmt:]) + pk.Domain[0].FFTInverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality]) + vk.IdCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.IdCanonical[0], pk.EvaluationId1BigDomain) + copy(vk.IdCanonical[1], pk.EvaluationId2BigDomain) + copy(vk.IdCanonical[2], pk.EvaluationId3BigDomain) + + var err error + vk.Idpp[0], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId1BigDomain) + if err != nil { + return err + } + vk.Idpp[1], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId2BigDomain) + if err != nil { + return err + } + vk.Idpp[2], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId3BigDomain) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationId1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId3BigDomain, fft.DIF, fft.OnCoset()) + + pk.Domain[0].FFTInverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + + // commit S1, S2, S3 + vk.SCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.SCanonical[0], pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[1], pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[2], pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + vk.Spp[0], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[0]) + if err != nil { + return err + } + vk.Spp[1], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[1]) + if err != nil { + return err + } + vk.Spp[2], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[2]) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationS1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS3BigDomain, fft.DIF, fft.OnCoset()) + + return nil + +} + +// getIDSmallDomain returns the Lagrange form of ID on the small domain +func getIDSmallDomain(domain *fft.Domain) []fr.Element { + + res := make([]fr.Element, 3*domain.Cardinality) + + res[0].SetOne() + res[domain.Cardinality].Set(&domain.FrMultiplicativeGen) + res[2*domain.Cardinality].Square(&domain.FrMultiplicativeGen) + + for i := uint64(1); i < domain.Cardinality; i++ { + res[i].Mul(&res[i-1], &domain.Generator) + res[domain.Cardinality+i].Mul(&res[domain.Cardinality+i-1], &domain.Generator) + res[2*domain.Cardinality+i].Mul(&res[2*domain.Cardinality+i-1], &domain.Generator) + } + + return res +} + +// NbPublicWitness returns the expected public witness size (number of field elements) +func (vk *VerifyingKey) NbPublicWitness() int { + return int(vk.NbPublicVariables) +} + +// VerifyingKey returns pk.Vk +func (pk *ProvingKey) VerifyingKey() interface{} { + return pk.Vk +} diff --git a/backend/plonkfri/bls24-317/verify.go b/backend/plonkfri/bls24-317/verify.go new file mode 100644 index 0000000000..758398865f --- /dev/null +++ b/backend/plonkfri/bls24-317/verify.go @@ -0,0 +1,398 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by gnark DO NOT EDIT + +package plonkfri + +import ( + "errors" + "fmt" + "github.com/consensys/gnark-crypto/ecc/bls24-317/fr" + "github.com/consensys/gnark-crypto/ecc/bls24-317/fr/fri" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + "github.com/consensys/gnark/backend" + "math/big" +) + +var ErrInvalidAlgebraicRelation = errors.New("algebraic relation does not hold") + +func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { + cfg, err := backend.NewVerifierConfig(opts...) + if err != nil { + return fmt.Errorf("create backend config: %w", err) + } + + // 0 - derive the challenges with Fiat Shamir + fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") + + dataFiatShamir := make([][fr.Bytes]byte, len(publicWitness)+3) + for i := 0; i < len(publicWitness); i++ { + copy(dataFiatShamir[i][:], publicWitness[i].Marshal()) + } + copy(dataFiatShamir[len(publicWitness)][:], proof.LROpp[0].ID) + copy(dataFiatShamir[len(publicWitness)+1][:], proof.LROpp[1].ID) + copy(dataFiatShamir[len(publicWitness)+2][:], proof.LROpp[2].ID) + + beta, err := deriveRandomnessFixedSize(fs, "gamma", dataFiatShamir...) + if err != nil { + return err + } + + gamma, err := deriveRandomness(fs, "beta", nil) + if err != nil { + return err + } + + alpha, err := deriveRandomness(fs, "alpha", proof.Zpp.ID) + if err != nil { + return err + } + + // compute the size of the domain of evaluation of the committed polynomial, + // the opening position. The challenge zeta will be g^{i} where i is the opening + // position, and g is the generator of the fri domain. + rho := uint64(fri.GetRho()) + friSize := 2 * rho * vk.Size + var bFriSize big.Int + bFriSize.SetInt64(int64(friSize)) + frOpeningPosition, err := deriveRandomness(fs, "zeta", proof.Hpp[0].ID, proof.Hpp[1].ID, proof.Hpp[2].ID) + if err != nil { + return err + } + var bOpeningPosition big.Int + bOpeningPosition.SetBytes(frOpeningPosition.Marshal()).Mod(&bOpeningPosition, &bFriSize) + openingPosition := bOpeningPosition.Uint64() + + shiftedOpeningPosition := (openingPosition + uint64(2*rho)) % friSize + err = vk.Iopp.VerifyOpening(shiftedOpeningPosition, proof.OpeningsZmp[1], proof.Zpp) + if err != nil { + return err + } + + // 1 - verify that the commitments are low degree polynomials + + // ql, qr, qm, qo, qkIncomplete + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[2]) + if err != nil { + return err + } + + // s1, s2, s3 + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[2]) + if err != nil { + return err + } + + // id1, id2, id3 + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[2]) + if err != nil { + return err + } + + // Z + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // 2 - verify the openings + + // ql, qr, qm, qo, qkIncomplete + // openingPosition := uint64(2) + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[0], vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[1], vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[2], vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[3], vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[4], vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[0], proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[1], proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[2], proof.LROpp[2]) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[0], proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[1], proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[2], proof.Hpp[2]) + if err != nil { + return err + } + + // s0, s1, s2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[0], vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[1], vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[2], vk.Spp[2]) + if err != nil { + return err + } + + // id0, id1, id2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[0], vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[1], vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[2], vk.Idpp[2]) + if err != nil { + return err + } + + // Z, Zshift + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsZmp[0], proof.Zpp) + if err != nil { + return err + } + + // verification of the algebraic relation + var ql, qr, qm, qo, qk fr.Element + ql.Set(&proof.OpeningsQlQrQmQoQkincompletemp[0].ClaimedValue) + qr.Set(&proof.OpeningsQlQrQmQoQkincompletemp[1].ClaimedValue) + qm.Set(&proof.OpeningsQlQrQmQoQkincompletemp[2].ClaimedValue) + qo.Set(&proof.OpeningsQlQrQmQoQkincompletemp[3].ClaimedValue) + qk.Set(&proof.OpeningsQlQrQmQoQkincompletemp[4].ClaimedValue) // -> to be completed + + var l, r, o fr.Element + l.Set(&proof.OpeningsLROmp[0].ClaimedValue) + r.Set(&proof.OpeningsLROmp[1].ClaimedValue) + o.Set(&proof.OpeningsLROmp[2].ClaimedValue) + + var h1, h2, h3 fr.Element + h1.Set(&proof.OpeningsHmp[0].ClaimedValue) + h2.Set(&proof.OpeningsHmp[1].ClaimedValue) + h3.Set(&proof.OpeningsHmp[2].ClaimedValue) + + var s1, s2, s3 fr.Element + s1.Set(&proof.OpeningsS1S2S3mp[0].ClaimedValue) + s2.Set(&proof.OpeningsS1S2S3mp[1].ClaimedValue) + s3.Set(&proof.OpeningsS1S2S3mp[2].ClaimedValue) + + var id1, id2, id3 fr.Element + id1.Set(&proof.OpeningsId1Id2Id3mp[0].ClaimedValue) + id2.Set(&proof.OpeningsId1Id2Id3mp[1].ClaimedValue) + id3.Set(&proof.OpeningsId1Id2Id3mp[2].ClaimedValue) + + var z, zshift fr.Element + z.Set(&proof.OpeningsZmp[0].ClaimedValue) + zshift.Set(&proof.OpeningsZmp[1].ClaimedValue) + + // 2 - compute the LHS: (ql*l+..+qk)+ α*(z(μx)*(l+β*s₁+γ)*..-z*(l+β*id1+γ))+α²*z*(l1-1) + var zeta fr.Element + zeta.Exp(vk.GenOpening, &bOpeningPosition) + + var lhs, t1, t2, t3, tmp, tmp2 fr.Element + // 2.1 (ql*l+..+qk) + t1.Mul(&l, &ql) + tmp.Mul(&r, &qr) + t1.Add(&t1, &tmp) + tmp.Mul(&qm, &l).Mul(&tmp, &r) + t1.Add(&t1, &tmp) + tmp.Mul(&o, &qo) + t1.Add(&tmp, &t1) + tmp = completeQk(publicWitness, vk, zeta) + tmp.Add(&qk, &tmp) + t1.Add(&tmp, &t1) + + // 2.2 (z(ux)*(l+β*s1+γ)*..-z*(l+β*id1+γ)) + t2.Mul(&beta, &s1).Add(&t2, &l).Add(&t2, &gamma) + tmp.Mul(&beta, &s2).Add(&tmp, &r).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2) + tmp.Mul(&beta, &s3).Add(&tmp, &o).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2).Mul(&t2, &zshift) + + tmp.Mul(&beta, &id1).Add(&tmp, &l).Add(&tmp, &gamma) + tmp2.Mul(&beta, &id2).Add(&tmp2, &r).Add(&tmp2, &gamma) + tmp.Mul(&tmp, &tmp2) + tmp2.Mul(&beta, &id3).Add(&tmp2, &o).Add(&tmp2, &gamma) + tmp.Mul(&tmp2, &tmp).Mul(&tmp, &z) + + t2.Sub(&t2, &tmp) + + // 2.3 (z-1)*l1 + var one fr.Element + one.SetOne() + t3.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&t3, &one) + tmp.Sub(&zeta, &one).Inverse(&tmp).Mul(&tmp, &vk.SizeInv) + t3.Mul(&tmp, &t3) + tmp.Sub(&z, &one) + t3.Mul(&tmp, &t3) + + // 2.4 (ql*l+s+qk) + α*(z(ux)*(l+β*s1+γ)*...-z*(l+β*id1+γ)..)+ α²*z*(l1-1) + lhs.Set(&t3).Mul(&lhs, &alpha).Add(&lhs, &t2).Mul(&lhs, &alpha).Add(&lhs, &t1) + + // 3 - compute the RHS + var rhs fr.Element + tmp.Exp(zeta, big.NewInt(int64(vk.Size+2))) + rhs.Mul(&h3, &tmp). + Add(&rhs, &h2). + Mul(&rhs, &tmp). + Add(&rhs, &h1) + + tmp.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&tmp, &one) + rhs.Mul(&rhs, &tmp) + + // 4 - verify the relation LHS==RHS + if !rhs.Equal(&lhs) { + return ErrInvalidAlgebraicRelation + } + + return nil + +} + +// completeQk returns ∑_{i> nn + _is := bits.Reverse64(uint64((i+toShift)%nbElmts)) >> nn + + // in what follows gⁱ is understood as the generator of the chosen coset of domainBig + f[0].Mul(&evaluationIDBigDomain, &beta).Add(&f[0], &l[_i]).Add(&f[0], &gamma) //l(gⁱ)+gⁱ*β+γ + f[1].Mul(&evaluationIDBigDomain, &cosetShift).Mul(&f[1], &beta).Add(&f[1], &r[_i]).Add(&f[1], &gamma) //r(gⁱ)+u*gⁱ*β+γ + f[2].Mul(&evaluationIDBigDomain, &cosetShiftSquare).Mul(&f[2], &beta).Add(&f[2], &o[_i]).Add(&f[2], &gamma) //o(gⁱ)+u²*gⁱ*β+γ + + g[0].Mul(&pk.EvaluationS1BigDomain[_i], &beta).Add(&g[0], &l[_i]).Add(&g[0], &gamma) //l(gⁱ))+s1(gⁱ)*β+γ + g[1].Mul(&pk.EvaluationS2BigDomain[_i], &beta).Add(&g[1], &r[_i]).Add(&g[1], &gamma) //r(gⁱ))+s2(gⁱ)*β+γ + g[2].Mul(&pk.EvaluationS3BigDomain[_i], &beta).Add(&g[2], &o[_i]).Add(&g[2], &gamma) //o(gⁱ))+s3(gⁱ)*β+γ + + f[0].Mul(&f[0], &f[1]).Mul(&f[0], &f[2]).Mul(&f[0], &z[_i]) // z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + g[0].Mul(&g[0], &g[1]).Mul(&g[0], &g[2]).Mul(&g[0], &z[_is]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) + + res[_i].Sub(&g[0], &f[0]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) - z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + + evaluationIDBigDomain.Mul(&evaluationIDBigDomain, &pk.Domain[1].Generator) // gⁱ*g + } + }) + + return res +} + +// evalConstraintsInd computes the evaluation of lL+qrR+qqmL.R+qoO+k on +// the odd coset of (Z/8mZ)/(Z/4mZ), where m=nbConstraints+nbAssertions. +// +// * lsL, lsR, lsO are the evaluation of the blinded solution vectors on odd cosets +// * lsQk is the completed version of qk, in canonical version +// +// lsL, lsR, lsO are in bit reversed order, lsQk is in the correct order. +func evalConstraintsInd(pk *ProvingKey, lsL, lsR, lsO, lsQk []fr.Element) []fr.Element { + + res := make([]fr.Element, pk.Domain[1].Cardinality) + // nn := uint64(64 - bits.TrailingZeros64(pk.Domain[1].Cardinality)) + + utils.Parallelize(len(res), func(start, end int) { + + var t0, t1 fr.Element + + for i := start; i < end; i++ { + + // irev := bits.Reverse64(uint64(i)) >> nn + + t1.Mul(&pk.EvaluationQmDomainBigBitReversed[i], &lsR[i]) // qm.r + t1.Add(&t1, &pk.EvaluationQlDomainBigBitReversed[i]) // qm.r + ql + t1.Mul(&t1, &lsL[i]) // qm.l.r + ql.l + + t0.Mul(&pk.EvaluationQrDomainBigBitReversed[i], &lsR[i]) + t0.Add(&t0, &t1) // qm.l.r + ql.l + qr.r + + t1.Mul(&pk.EvaluationQoDomainBigBitReversed[i], &lsO[i]) + t0.Add(&t0, &t1) // ql.l + qr.r + qm.l.r + qo.o + res[i].Add(&t0, &lsQk[i]) // ql.l + qr.r + qm.l.r + qo.o + k + + } + }) + + return res +} + +// fftBigCosetWOBitReverse evaluates poly (canonical form) of degree m> nn + + t.Sub(&evaluationBlindedZDomainBigBitReversed[_i], &one) // evaluates L₁(X)*(Z(X)-1) on a coset of the big domain + h[_i].Mul(&startsAtOne[_i], &t).Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintOrderingBitReversed[_i]). + Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintsIndBitReversed[_i]). + Mul(&h[_i], &evaluationXnMinusOneInverse[i%ratio]) + } + }) + + // put h in canonical form. h is of degree 3*(n+1)+2. + // using fft.DIT put h revert bit reverse + pk.Domain[1].FFTInverse(h, fft.DIT, fft.OnCoset()) + + // degree of hi is n+2 because of the blinding + h1 := h[:pk.Domain[0].Cardinality+2] + h2 := h[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] + h3 := h[2*(pk.Domain[0].Cardinality+2) : 3*(pk.Domain[0].Cardinality+2)] + + return h1, h2, h3 + +} + +// computeZ computes Z, in canonical basis, where: +// +// - Z of degree n (domainNum.Cardinality) +// +// - Z(1)=1 +// (l_i+z**i+gamma)*(r_i+u*z**i+gamma)*(o_i+u**2z**i+gamma) +// +// - for i>0: Z(u**i) = Pi_{k "small" domain, used for individual polynomials + // 1 -> "big" domain, used for the computation of the quotient + Domain [2]fft.Domain + + // s1, s2, s3 (L=Lagrange basis small domain, C=canonical basis, Ls=Lagrange Shifted big domain) + LId []fr.Element + EvaluationId1BigDomain, EvaluationId2BigDomain, EvaluationId3BigDomain []fr.Element + EvaluationS1BigDomain, EvaluationS2BigDomain, EvaluationS3BigDomain []fr.Element + + // position -> permuted position (position in [0,3*sizeSystem-1]) + Permutation []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, that is the closest power of 2 bounding above + // number of constraints+number of public inputs + Size uint64 + SizeInv fr.Element + Generator fr.Element + NbPublicVariables uint64 + + // cosetShift generator of the coset on the small domain + CosetShift fr.Element + + // S commitments to S1, S2, S3 + SCanonical [3][]fr.Element + Spp [3]fri.ProofOfProximity + + // Id commitments to Id1, Id2, Id3 + // Id [3]Commitment + IdCanonical [3][]fr.Element + Idpp [3]fri.ProofOfProximity + + // Commitments to ql, qr, qm, qo prepended with as many zeroes (ones for l) as there are public inputs. + // In particular Qk is not complete. + Qpp [5]fri.ProofOfProximity // Ql, Qr, Qm, Qo, Qk + + // Iopp scheme (currently one for each size of polynomial) + Iopp fri.Iopp + + // generator of the group on which the Iopp works. If i is the opening position, + // the polynomials will be opened at genOpening^{i}. + GenOpening fr.Element +} + +// Setup sets proving and verifying keys +func Setup(spr *cs.SparseR1CS) (*ProvingKey, *VerifyingKey, error) { + + var pk ProvingKey + var vk VerifyingKey + + // The verifying key shares data with the proving key + pk.Vk = &vk + + nbConstraints := spr.GetNbConstraints() + + // fft domains + sizeSystem := uint64(nbConstraints + len(spr.Public)) // len(spr.Public) is for the placeholder constraints + pk.Domain[0] = *fft.NewDomain(sizeSystem) + + // h, the quotient polynomial is of degree 3(n+1)+2, so it's in a 3(n+2) dim vector space, + // the domain is the next power of 2 superior to 3(n+2). 4*domainNum is enough in all cases + // except when n<6. + if sizeSystem < 6 { + pk.Domain[1] = *fft.NewDomain(8 * sizeSystem) + } else { + pk.Domain[1] = *fft.NewDomain(4 * sizeSystem) + } + pk.Vk.CosetShift.Set(&pk.Domain[0].FrMultiplicativeGen) + + vk.Size = pk.Domain[0].Cardinality + vk.SizeInv.SetUint64(vk.Size).Inverse(&vk.SizeInv) + vk.Generator.Set(&pk.Domain[0].Generator) + vk.NbPublicVariables = uint64(len(spr.Public)) + + // IOP schemess + // The +2 is to handle the blinding. + sizeIopp := pk.Domain[0].Cardinality + 2 + vk.Iopp = fri.RADIX_2_FRI.New(sizeIopp, sha256.New()) + // only there to access the group used in FRI... + rho := uint64(fri.GetRho()) + // we multiply by 2 because the IOP is created with size pk.Domain[0].Cardinality + 2 (because + // of the blinding), so the domain will be rho*size_domain where size_domain is the next power + // of 2 after pk.Domain[0].Cardinality + 2, which is 2*rho*pk.Domain[0].Cardinality + tmpDomain := fft.NewDomain(2 * rho * pk.Domain[0].Cardinality) + vk.GenOpening.Set(&tmpDomain.Generator) + + // public polynomials corresponding to constraints: [ placholders | constraints | assertions ] + pk.EvaluationQlDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQrDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQmDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQoDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.LQkIncompleteDomainSmall = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQkIncomplete = make([]fr.Element, pk.Domain[0].Cardinality) + + for i := 0; i < len(spr.Public); i++ { // placeholders (-PUB_INPUT_i + qk_i = 0) TODO should return error if size is inconsistent + pk.EvaluationQlDomainBigBitReversed[i].SetOne().Neg(&pk.EvaluationQlDomainBigBitReversed[i]) + pk.EvaluationQrDomainBigBitReversed[i].SetZero() + pk.EvaluationQmDomainBigBitReversed[i].SetZero() + pk.EvaluationQoDomainBigBitReversed[i].SetZero() + pk.LQkIncompleteDomainSmall[i].SetZero() // --> to be completed by the prover + pk.CQkIncomplete[i].Set(&pk.LQkIncompleteDomainSmall[i]) // --> to be completed by the prover + } + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + pk.EvaluationQlDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QL]) + pk.EvaluationQrDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QR]) + pk.EvaluationQmDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QM]) + pk.EvaluationQoDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QO]) + pk.LQkIncompleteDomainSmall[offset+j].Set(&spr.Coefficients[c.QC]) + pk.CQkIncomplete[offset+j].Set(&pk.LQkIncompleteDomainSmall[offset+j]) + + j++ + } + + pk.Domain[0].FFTInverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.CQkIncomplete, fft.DIF) + fft.BitReverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.CQkIncomplete) + + // Commit to the polynomials to set up the verifying key + pk.CQl = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQr = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQm = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQo = make([]fr.Element, pk.Domain[0].Cardinality) + copy(pk.CQl, pk.EvaluationQlDomainBigBitReversed) + copy(pk.CQr, pk.EvaluationQrDomainBigBitReversed) + copy(pk.CQm, pk.EvaluationQmDomainBigBitReversed) + copy(pk.CQo, pk.EvaluationQoDomainBigBitReversed) + var err error + vk.Qpp[0], err = vk.Iopp.BuildProofOfProximity(pk.CQl) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[1], err = vk.Iopp.BuildProofOfProximity(pk.CQr) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[2], err = vk.Iopp.BuildProofOfProximity(pk.CQm) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[3], err = vk.Iopp.BuildProofOfProximity(pk.CQo) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[4], err = vk.Iopp.BuildProofOfProximity(pk.CQkIncomplete) + if err != nil { + return &pk, &vk, err + } + + pk.Domain[1].FFT(pk.EvaluationQlDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQrDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQmDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQoDomainBigBitReversed, fft.DIF, fft.OnCoset()) + + // build permutation. Note: at this stage, the permutation takes in account the placeholders + buildPermutation(spr, &pk) + + // set s1, s2, s3 + err = computePermutationPolynomials(&pk, &vk) + if err != nil { + return &pk, &vk, err + } + + return &pk, &vk, nil + +} + +// buildPermutation builds the Permutation associated with a circuit. +// +// The permutation s is composed of cycles of maximum length such that +// +// s. (l||r||o) = (l||r||o) +// +// , where l||r||o is the concatenation of the indices of l, r, o in +// ql.l+qr.r+qm.l.r+qo.O+k = 0. +// +// The permutation is encoded as a slice s of size 3*size(l), where the +// i-th entry of l||r||o is sent to the s[i]-th entry, so it acts on a tab +// like this: for i in tab: tab[i] = tab[permutation[i]] +func buildPermutation(spr *cs.SparseR1CS, pk *ProvingKey) { + + nbVariables := spr.NbInternalVariables + len(spr.Public) + len(spr.Secret) + sizeSolution := int(pk.Domain[0].Cardinality) + + // init permutation + pk.Permutation = make([]int64, 3*sizeSolution) + for i := 0; i < len(pk.Permutation); i++ { + pk.Permutation[i] = -1 + } + + // init LRO position -> variable_ID + lro := make([]int, 3*sizeSolution) // position -> variable_ID + for i := 0; i < len(spr.Public); i++ { + lro[i] = i // IDs of LRO associated to placeholders (only L needs to be taken care of) + } + + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + lro[offset+j] = int(c.XA) + lro[sizeSolution+offset+j] = int(c.XB) + lro[2*sizeSolution+offset+j] = int(c.XC) + j++ + } + + // init cycle: + // map ID -> last position the ID was seen + cycle := make([]int64, nbVariables) + for i := 0; i < len(cycle); i++ { + cycle[i] = -1 + } + + for i := 0; i < len(lro); i++ { + if cycle[lro[i]] != -1 { + // if != -1, it means we already encountered this value + // so we need to set the corresponding permutation index. + pk.Permutation[i] = cycle[lro[i]] + } + cycle[lro[i]] = int64(i) + } + + // complete the Permutation by filling the first IDs encountered + for i := 0; i < len(pk.Permutation); i++ { + if pk.Permutation[i] == -1 { + pk.Permutation[i] = cycle[lro[i]] + } + } +} + +// computePermutationPolynomials computes the LDE (Lagrange basis) of the permutations +// s1, s2, s3. +// +// 0 1 .. n-1 | n n+1 .. 2*n-1 | 2n 2n+1 .. 3n-1 | +// +// | +// | Permutation +// +// s00 s01 .. s0n-1 s10 s11 .. s1n-1 s20 s21 .. s2n-1 v +// \---------------/ \--------------------/ \------------------------/ +// +// s1 (LDE) s2 (LDE) s3 (LDE) +func computePermutationPolynomials(pk *ProvingKey, vk *VerifyingKey) error { + + nbElmt := int(pk.Domain[0].Cardinality) + + // sID = [1,..,g^{n-1},s,..,s*g^{n-1},s^2,..,s^2*g^{n-1}] + pk.LId = getIDSmallDomain(&pk.Domain[0]) + + // canonical form of S1, S2, S3 + pk.EvaluationS1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + for i := 0; i < nbElmt; i++ { + pk.EvaluationS1BigDomain[i].Set(&pk.LId[pk.Permutation[i]]) + pk.EvaluationS2BigDomain[i].Set(&pk.LId[pk.Permutation[nbElmt+i]]) + pk.EvaluationS3BigDomain[i].Set(&pk.LId[pk.Permutation[2*nbElmt+i]]) + } + + // Evaluations of Sid1, Sid2, Sid3 on cosets of Domain[1] + pk.EvaluationId1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + copy(pk.EvaluationId1BigDomain, pk.LId[:nbElmt]) + copy(pk.EvaluationId2BigDomain, pk.LId[nbElmt:2*nbElmt]) + copy(pk.EvaluationId3BigDomain, pk.LId[2*nbElmt:]) + pk.Domain[0].FFTInverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality]) + vk.IdCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.IdCanonical[0], pk.EvaluationId1BigDomain) + copy(vk.IdCanonical[1], pk.EvaluationId2BigDomain) + copy(vk.IdCanonical[2], pk.EvaluationId3BigDomain) + + var err error + vk.Idpp[0], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId1BigDomain) + if err != nil { + return err + } + vk.Idpp[1], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId2BigDomain) + if err != nil { + return err + } + vk.Idpp[2], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId3BigDomain) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationId1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId3BigDomain, fft.DIF, fft.OnCoset()) + + pk.Domain[0].FFTInverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + + // commit S1, S2, S3 + vk.SCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.SCanonical[0], pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[1], pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[2], pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + vk.Spp[0], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[0]) + if err != nil { + return err + } + vk.Spp[1], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[1]) + if err != nil { + return err + } + vk.Spp[2], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[2]) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationS1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS3BigDomain, fft.DIF, fft.OnCoset()) + + return nil + +} + +// getIDSmallDomain returns the Lagrange form of ID on the small domain +func getIDSmallDomain(domain *fft.Domain) []fr.Element { + + res := make([]fr.Element, 3*domain.Cardinality) + + res[0].SetOne() + res[domain.Cardinality].Set(&domain.FrMultiplicativeGen) + res[2*domain.Cardinality].Square(&domain.FrMultiplicativeGen) + + for i := uint64(1); i < domain.Cardinality; i++ { + res[i].Mul(&res[i-1], &domain.Generator) + res[domain.Cardinality+i].Mul(&res[domain.Cardinality+i-1], &domain.Generator) + res[2*domain.Cardinality+i].Mul(&res[2*domain.Cardinality+i-1], &domain.Generator) + } + + return res +} + +// NbPublicWitness returns the expected public witness size (number of field elements) +func (vk *VerifyingKey) NbPublicWitness() int { + return int(vk.NbPublicVariables) +} + +// VerifyingKey returns pk.Vk +func (pk *ProvingKey) VerifyingKey() interface{} { + return pk.Vk +} diff --git a/backend/plonkfri/bn254/verify.go b/backend/plonkfri/bn254/verify.go new file mode 100644 index 0000000000..517d79b536 --- /dev/null +++ b/backend/plonkfri/bn254/verify.go @@ -0,0 +1,398 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by gnark DO NOT EDIT + +package plonkfri + +import ( + "errors" + "fmt" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/consensys/gnark-crypto/ecc/bn254/fr/fri" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + "github.com/consensys/gnark/backend" + "math/big" +) + +var ErrInvalidAlgebraicRelation = errors.New("algebraic relation does not hold") + +func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { + cfg, err := backend.NewVerifierConfig(opts...) + if err != nil { + return fmt.Errorf("create backend config: %w", err) + } + + // 0 - derive the challenges with Fiat Shamir + fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") + + dataFiatShamir := make([][fr.Bytes]byte, len(publicWitness)+3) + for i := 0; i < len(publicWitness); i++ { + copy(dataFiatShamir[i][:], publicWitness[i].Marshal()) + } + copy(dataFiatShamir[len(publicWitness)][:], proof.LROpp[0].ID) + copy(dataFiatShamir[len(publicWitness)+1][:], proof.LROpp[1].ID) + copy(dataFiatShamir[len(publicWitness)+2][:], proof.LROpp[2].ID) + + beta, err := deriveRandomnessFixedSize(fs, "gamma", dataFiatShamir...) + if err != nil { + return err + } + + gamma, err := deriveRandomness(fs, "beta", nil) + if err != nil { + return err + } + + alpha, err := deriveRandomness(fs, "alpha", proof.Zpp.ID) + if err != nil { + return err + } + + // compute the size of the domain of evaluation of the committed polynomial, + // the opening position. The challenge zeta will be g^{i} where i is the opening + // position, and g is the generator of the fri domain. + rho := uint64(fri.GetRho()) + friSize := 2 * rho * vk.Size + var bFriSize big.Int + bFriSize.SetInt64(int64(friSize)) + frOpeningPosition, err := deriveRandomness(fs, "zeta", proof.Hpp[0].ID, proof.Hpp[1].ID, proof.Hpp[2].ID) + if err != nil { + return err + } + var bOpeningPosition big.Int + bOpeningPosition.SetBytes(frOpeningPosition.Marshal()).Mod(&bOpeningPosition, &bFriSize) + openingPosition := bOpeningPosition.Uint64() + + shiftedOpeningPosition := (openingPosition + uint64(2*rho)) % friSize + err = vk.Iopp.VerifyOpening(shiftedOpeningPosition, proof.OpeningsZmp[1], proof.Zpp) + if err != nil { + return err + } + + // 1 - verify that the commitments are low degree polynomials + + // ql, qr, qm, qo, qkIncomplete + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[2]) + if err != nil { + return err + } + + // s1, s2, s3 + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[2]) + if err != nil { + return err + } + + // id1, id2, id3 + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[2]) + if err != nil { + return err + } + + // Z + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // 2 - verify the openings + + // ql, qr, qm, qo, qkIncomplete + // openingPosition := uint64(2) + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[0], vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[1], vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[2], vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[3], vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[4], vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[0], proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[1], proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[2], proof.LROpp[2]) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[0], proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[1], proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[2], proof.Hpp[2]) + if err != nil { + return err + } + + // s0, s1, s2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[0], vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[1], vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[2], vk.Spp[2]) + if err != nil { + return err + } + + // id0, id1, id2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[0], vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[1], vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[2], vk.Idpp[2]) + if err != nil { + return err + } + + // Z, Zshift + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsZmp[0], proof.Zpp) + if err != nil { + return err + } + + // verification of the algebraic relation + var ql, qr, qm, qo, qk fr.Element + ql.Set(&proof.OpeningsQlQrQmQoQkincompletemp[0].ClaimedValue) + qr.Set(&proof.OpeningsQlQrQmQoQkincompletemp[1].ClaimedValue) + qm.Set(&proof.OpeningsQlQrQmQoQkincompletemp[2].ClaimedValue) + qo.Set(&proof.OpeningsQlQrQmQoQkincompletemp[3].ClaimedValue) + qk.Set(&proof.OpeningsQlQrQmQoQkincompletemp[4].ClaimedValue) // -> to be completed + + var l, r, o fr.Element + l.Set(&proof.OpeningsLROmp[0].ClaimedValue) + r.Set(&proof.OpeningsLROmp[1].ClaimedValue) + o.Set(&proof.OpeningsLROmp[2].ClaimedValue) + + var h1, h2, h3 fr.Element + h1.Set(&proof.OpeningsHmp[0].ClaimedValue) + h2.Set(&proof.OpeningsHmp[1].ClaimedValue) + h3.Set(&proof.OpeningsHmp[2].ClaimedValue) + + var s1, s2, s3 fr.Element + s1.Set(&proof.OpeningsS1S2S3mp[0].ClaimedValue) + s2.Set(&proof.OpeningsS1S2S3mp[1].ClaimedValue) + s3.Set(&proof.OpeningsS1S2S3mp[2].ClaimedValue) + + var id1, id2, id3 fr.Element + id1.Set(&proof.OpeningsId1Id2Id3mp[0].ClaimedValue) + id2.Set(&proof.OpeningsId1Id2Id3mp[1].ClaimedValue) + id3.Set(&proof.OpeningsId1Id2Id3mp[2].ClaimedValue) + + var z, zshift fr.Element + z.Set(&proof.OpeningsZmp[0].ClaimedValue) + zshift.Set(&proof.OpeningsZmp[1].ClaimedValue) + + // 2 - compute the LHS: (ql*l+..+qk)+ α*(z(μx)*(l+β*s₁+γ)*..-z*(l+β*id1+γ))+α²*z*(l1-1) + var zeta fr.Element + zeta.Exp(vk.GenOpening, &bOpeningPosition) + + var lhs, t1, t2, t3, tmp, tmp2 fr.Element + // 2.1 (ql*l+..+qk) + t1.Mul(&l, &ql) + tmp.Mul(&r, &qr) + t1.Add(&t1, &tmp) + tmp.Mul(&qm, &l).Mul(&tmp, &r) + t1.Add(&t1, &tmp) + tmp.Mul(&o, &qo) + t1.Add(&tmp, &t1) + tmp = completeQk(publicWitness, vk, zeta) + tmp.Add(&qk, &tmp) + t1.Add(&tmp, &t1) + + // 2.2 (z(ux)*(l+β*s1+γ)*..-z*(l+β*id1+γ)) + t2.Mul(&beta, &s1).Add(&t2, &l).Add(&t2, &gamma) + tmp.Mul(&beta, &s2).Add(&tmp, &r).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2) + tmp.Mul(&beta, &s3).Add(&tmp, &o).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2).Mul(&t2, &zshift) + + tmp.Mul(&beta, &id1).Add(&tmp, &l).Add(&tmp, &gamma) + tmp2.Mul(&beta, &id2).Add(&tmp2, &r).Add(&tmp2, &gamma) + tmp.Mul(&tmp, &tmp2) + tmp2.Mul(&beta, &id3).Add(&tmp2, &o).Add(&tmp2, &gamma) + tmp.Mul(&tmp2, &tmp).Mul(&tmp, &z) + + t2.Sub(&t2, &tmp) + + // 2.3 (z-1)*l1 + var one fr.Element + one.SetOne() + t3.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&t3, &one) + tmp.Sub(&zeta, &one).Inverse(&tmp).Mul(&tmp, &vk.SizeInv) + t3.Mul(&tmp, &t3) + tmp.Sub(&z, &one) + t3.Mul(&tmp, &t3) + + // 2.4 (ql*l+s+qk) + α*(z(ux)*(l+β*s1+γ)*...-z*(l+β*id1+γ)..)+ α²*z*(l1-1) + lhs.Set(&t3).Mul(&lhs, &alpha).Add(&lhs, &t2).Mul(&lhs, &alpha).Add(&lhs, &t1) + + // 3 - compute the RHS + var rhs fr.Element + tmp.Exp(zeta, big.NewInt(int64(vk.Size+2))) + rhs.Mul(&h3, &tmp). + Add(&rhs, &h2). + Mul(&rhs, &tmp). + Add(&rhs, &h1) + + tmp.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&tmp, &one) + rhs.Mul(&rhs, &tmp) + + // 4 - verify the relation LHS==RHS + if !rhs.Equal(&lhs) { + return ErrInvalidAlgebraicRelation + } + + return nil + +} + +// completeQk returns ∑_{i> nn + _is := bits.Reverse64(uint64((i+toShift)%nbElmts)) >> nn + + // in what follows gⁱ is understood as the generator of the chosen coset of domainBig + f[0].Mul(&evaluationIDBigDomain, &beta).Add(&f[0], &l[_i]).Add(&f[0], &gamma) //l(gⁱ)+gⁱ*β+γ + f[1].Mul(&evaluationIDBigDomain, &cosetShift).Mul(&f[1], &beta).Add(&f[1], &r[_i]).Add(&f[1], &gamma) //r(gⁱ)+u*gⁱ*β+γ + f[2].Mul(&evaluationIDBigDomain, &cosetShiftSquare).Mul(&f[2], &beta).Add(&f[2], &o[_i]).Add(&f[2], &gamma) //o(gⁱ)+u²*gⁱ*β+γ + + g[0].Mul(&pk.EvaluationS1BigDomain[_i], &beta).Add(&g[0], &l[_i]).Add(&g[0], &gamma) //l(gⁱ))+s1(gⁱ)*β+γ + g[1].Mul(&pk.EvaluationS2BigDomain[_i], &beta).Add(&g[1], &r[_i]).Add(&g[1], &gamma) //r(gⁱ))+s2(gⁱ)*β+γ + g[2].Mul(&pk.EvaluationS3BigDomain[_i], &beta).Add(&g[2], &o[_i]).Add(&g[2], &gamma) //o(gⁱ))+s3(gⁱ)*β+γ + + f[0].Mul(&f[0], &f[1]).Mul(&f[0], &f[2]).Mul(&f[0], &z[_i]) // z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + g[0].Mul(&g[0], &g[1]).Mul(&g[0], &g[2]).Mul(&g[0], &z[_is]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) + + res[_i].Sub(&g[0], &f[0]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) - z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + + evaluationIDBigDomain.Mul(&evaluationIDBigDomain, &pk.Domain[1].Generator) // gⁱ*g + } + }) + + return res +} + +// evalConstraintsInd computes the evaluation of lL+qrR+qqmL.R+qoO+k on +// the odd coset of (Z/8mZ)/(Z/4mZ), where m=nbConstraints+nbAssertions. +// +// * lsL, lsR, lsO are the evaluation of the blinded solution vectors on odd cosets +// * lsQk is the completed version of qk, in canonical version +// +// lsL, lsR, lsO are in bit reversed order, lsQk is in the correct order. +func evalConstraintsInd(pk *ProvingKey, lsL, lsR, lsO, lsQk []fr.Element) []fr.Element { + + res := make([]fr.Element, pk.Domain[1].Cardinality) + // nn := uint64(64 - bits.TrailingZeros64(pk.Domain[1].Cardinality)) + + utils.Parallelize(len(res), func(start, end int) { + + var t0, t1 fr.Element + + for i := start; i < end; i++ { + + // irev := bits.Reverse64(uint64(i)) >> nn + + t1.Mul(&pk.EvaluationQmDomainBigBitReversed[i], &lsR[i]) // qm.r + t1.Add(&t1, &pk.EvaluationQlDomainBigBitReversed[i]) // qm.r + ql + t1.Mul(&t1, &lsL[i]) // qm.l.r + ql.l + + t0.Mul(&pk.EvaluationQrDomainBigBitReversed[i], &lsR[i]) + t0.Add(&t0, &t1) // qm.l.r + ql.l + qr.r + + t1.Mul(&pk.EvaluationQoDomainBigBitReversed[i], &lsO[i]) + t0.Add(&t0, &t1) // ql.l + qr.r + qm.l.r + qo.o + res[i].Add(&t0, &lsQk[i]) // ql.l + qr.r + qm.l.r + qo.o + k + + } + }) + + return res +} + +// fftBigCosetWOBitReverse evaluates poly (canonical form) of degree m> nn + + t.Sub(&evaluationBlindedZDomainBigBitReversed[_i], &one) // evaluates L₁(X)*(Z(X)-1) on a coset of the big domain + h[_i].Mul(&startsAtOne[_i], &t).Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintOrderingBitReversed[_i]). + Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintsIndBitReversed[_i]). + Mul(&h[_i], &evaluationXnMinusOneInverse[i%ratio]) + } + }) + + // put h in canonical form. h is of degree 3*(n+1)+2. + // using fft.DIT put h revert bit reverse + pk.Domain[1].FFTInverse(h, fft.DIT, fft.OnCoset()) + + // degree of hi is n+2 because of the blinding + h1 := h[:pk.Domain[0].Cardinality+2] + h2 := h[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] + h3 := h[2*(pk.Domain[0].Cardinality+2) : 3*(pk.Domain[0].Cardinality+2)] + + return h1, h2, h3 + +} + +// computeZ computes Z, in canonical basis, where: +// +// - Z of degree n (domainNum.Cardinality) +// +// - Z(1)=1 +// (l_i+z**i+gamma)*(r_i+u*z**i+gamma)*(o_i+u**2z**i+gamma) +// +// - for i>0: Z(u**i) = Pi_{k "small" domain, used for individual polynomials + // 1 -> "big" domain, used for the computation of the quotient + Domain [2]fft.Domain + + // s1, s2, s3 (L=Lagrange basis small domain, C=canonical basis, Ls=Lagrange Shifted big domain) + LId []fr.Element + EvaluationId1BigDomain, EvaluationId2BigDomain, EvaluationId3BigDomain []fr.Element + EvaluationS1BigDomain, EvaluationS2BigDomain, EvaluationS3BigDomain []fr.Element + + // position -> permuted position (position in [0,3*sizeSystem-1]) + Permutation []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, that is the closest power of 2 bounding above + // number of constraints+number of public inputs + Size uint64 + SizeInv fr.Element + Generator fr.Element + NbPublicVariables uint64 + + // cosetShift generator of the coset on the small domain + CosetShift fr.Element + + // S commitments to S1, S2, S3 + SCanonical [3][]fr.Element + Spp [3]fri.ProofOfProximity + + // Id commitments to Id1, Id2, Id3 + // Id [3]Commitment + IdCanonical [3][]fr.Element + Idpp [3]fri.ProofOfProximity + + // Commitments to ql, qr, qm, qo prepended with as many zeroes (ones for l) as there are public inputs. + // In particular Qk is not complete. + Qpp [5]fri.ProofOfProximity // Ql, Qr, Qm, Qo, Qk + + // Iopp scheme (currently one for each size of polynomial) + Iopp fri.Iopp + + // generator of the group on which the Iopp works. If i is the opening position, + // the polynomials will be opened at genOpening^{i}. + GenOpening fr.Element +} + +// Setup sets proving and verifying keys +func Setup(spr *cs.SparseR1CS) (*ProvingKey, *VerifyingKey, error) { + + var pk ProvingKey + var vk VerifyingKey + + // The verifying key shares data with the proving key + pk.Vk = &vk + + nbConstraints := spr.GetNbConstraints() + + // fft domains + sizeSystem := uint64(nbConstraints + len(spr.Public)) // len(spr.Public) is for the placeholder constraints + pk.Domain[0] = *fft.NewDomain(sizeSystem) + + // h, the quotient polynomial is of degree 3(n+1)+2, so it's in a 3(n+2) dim vector space, + // the domain is the next power of 2 superior to 3(n+2). 4*domainNum is enough in all cases + // except when n<6. + if sizeSystem < 6 { + pk.Domain[1] = *fft.NewDomain(8 * sizeSystem) + } else { + pk.Domain[1] = *fft.NewDomain(4 * sizeSystem) + } + pk.Vk.CosetShift.Set(&pk.Domain[0].FrMultiplicativeGen) + + vk.Size = pk.Domain[0].Cardinality + vk.SizeInv.SetUint64(vk.Size).Inverse(&vk.SizeInv) + vk.Generator.Set(&pk.Domain[0].Generator) + vk.NbPublicVariables = uint64(len(spr.Public)) + + // IOP schemess + // The +2 is to handle the blinding. + sizeIopp := pk.Domain[0].Cardinality + 2 + vk.Iopp = fri.RADIX_2_FRI.New(sizeIopp, sha256.New()) + // only there to access the group used in FRI... + rho := uint64(fri.GetRho()) + // we multiply by 2 because the IOP is created with size pk.Domain[0].Cardinality + 2 (because + // of the blinding), so the domain will be rho*size_domain where size_domain is the next power + // of 2 after pk.Domain[0].Cardinality + 2, which is 2*rho*pk.Domain[0].Cardinality + tmpDomain := fft.NewDomain(2 * rho * pk.Domain[0].Cardinality) + vk.GenOpening.Set(&tmpDomain.Generator) + + // public polynomials corresponding to constraints: [ placholders | constraints | assertions ] + pk.EvaluationQlDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQrDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQmDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQoDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.LQkIncompleteDomainSmall = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQkIncomplete = make([]fr.Element, pk.Domain[0].Cardinality) + + for i := 0; i < len(spr.Public); i++ { // placeholders (-PUB_INPUT_i + qk_i = 0) TODO should return error if size is inconsistent + pk.EvaluationQlDomainBigBitReversed[i].SetOne().Neg(&pk.EvaluationQlDomainBigBitReversed[i]) + pk.EvaluationQrDomainBigBitReversed[i].SetZero() + pk.EvaluationQmDomainBigBitReversed[i].SetZero() + pk.EvaluationQoDomainBigBitReversed[i].SetZero() + pk.LQkIncompleteDomainSmall[i].SetZero() // --> to be completed by the prover + pk.CQkIncomplete[i].Set(&pk.LQkIncompleteDomainSmall[i]) // --> to be completed by the prover + } + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + pk.EvaluationQlDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QL]) + pk.EvaluationQrDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QR]) + pk.EvaluationQmDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QM]) + pk.EvaluationQoDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QO]) + pk.LQkIncompleteDomainSmall[offset+j].Set(&spr.Coefficients[c.QC]) + pk.CQkIncomplete[offset+j].Set(&pk.LQkIncompleteDomainSmall[offset+j]) + + j++ + } + + pk.Domain[0].FFTInverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.CQkIncomplete, fft.DIF) + fft.BitReverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.CQkIncomplete) + + // Commit to the polynomials to set up the verifying key + pk.CQl = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQr = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQm = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQo = make([]fr.Element, pk.Domain[0].Cardinality) + copy(pk.CQl, pk.EvaluationQlDomainBigBitReversed) + copy(pk.CQr, pk.EvaluationQrDomainBigBitReversed) + copy(pk.CQm, pk.EvaluationQmDomainBigBitReversed) + copy(pk.CQo, pk.EvaluationQoDomainBigBitReversed) + var err error + vk.Qpp[0], err = vk.Iopp.BuildProofOfProximity(pk.CQl) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[1], err = vk.Iopp.BuildProofOfProximity(pk.CQr) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[2], err = vk.Iopp.BuildProofOfProximity(pk.CQm) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[3], err = vk.Iopp.BuildProofOfProximity(pk.CQo) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[4], err = vk.Iopp.BuildProofOfProximity(pk.CQkIncomplete) + if err != nil { + return &pk, &vk, err + } + + pk.Domain[1].FFT(pk.EvaluationQlDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQrDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQmDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQoDomainBigBitReversed, fft.DIF, fft.OnCoset()) + + // build permutation. Note: at this stage, the permutation takes in account the placeholders + buildPermutation(spr, &pk) + + // set s1, s2, s3 + err = computePermutationPolynomials(&pk, &vk) + if err != nil { + return &pk, &vk, err + } + + return &pk, &vk, nil + +} + +// buildPermutation builds the Permutation associated with a circuit. +// +// The permutation s is composed of cycles of maximum length such that +// +// s. (l||r||o) = (l||r||o) +// +// , where l||r||o is the concatenation of the indices of l, r, o in +// ql.l+qr.r+qm.l.r+qo.O+k = 0. +// +// The permutation is encoded as a slice s of size 3*size(l), where the +// i-th entry of l||r||o is sent to the s[i]-th entry, so it acts on a tab +// like this: for i in tab: tab[i] = tab[permutation[i]] +func buildPermutation(spr *cs.SparseR1CS, pk *ProvingKey) { + + nbVariables := spr.NbInternalVariables + len(spr.Public) + len(spr.Secret) + sizeSolution := int(pk.Domain[0].Cardinality) + + // init permutation + pk.Permutation = make([]int64, 3*sizeSolution) + for i := 0; i < len(pk.Permutation); i++ { + pk.Permutation[i] = -1 + } + + // init LRO position -> variable_ID + lro := make([]int, 3*sizeSolution) // position -> variable_ID + for i := 0; i < len(spr.Public); i++ { + lro[i] = i // IDs of LRO associated to placeholders (only L needs to be taken care of) + } + + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + lro[offset+j] = int(c.XA) + lro[sizeSolution+offset+j] = int(c.XB) + lro[2*sizeSolution+offset+j] = int(c.XC) + j++ + } + + // init cycle: + // map ID -> last position the ID was seen + cycle := make([]int64, nbVariables) + for i := 0; i < len(cycle); i++ { + cycle[i] = -1 + } + + for i := 0; i < len(lro); i++ { + if cycle[lro[i]] != -1 { + // if != -1, it means we already encountered this value + // so we need to set the corresponding permutation index. + pk.Permutation[i] = cycle[lro[i]] + } + cycle[lro[i]] = int64(i) + } + + // complete the Permutation by filling the first IDs encountered + for i := 0; i < len(pk.Permutation); i++ { + if pk.Permutation[i] == -1 { + pk.Permutation[i] = cycle[lro[i]] + } + } +} + +// computePermutationPolynomials computes the LDE (Lagrange basis) of the permutations +// s1, s2, s3. +// +// 0 1 .. n-1 | n n+1 .. 2*n-1 | 2n 2n+1 .. 3n-1 | +// +// | +// | Permutation +// +// s00 s01 .. s0n-1 s10 s11 .. s1n-1 s20 s21 .. s2n-1 v +// \---------------/ \--------------------/ \------------------------/ +// +// s1 (LDE) s2 (LDE) s3 (LDE) +func computePermutationPolynomials(pk *ProvingKey, vk *VerifyingKey) error { + + nbElmt := int(pk.Domain[0].Cardinality) + + // sID = [1,..,g^{n-1},s,..,s*g^{n-1},s^2,..,s^2*g^{n-1}] + pk.LId = getIDSmallDomain(&pk.Domain[0]) + + // canonical form of S1, S2, S3 + pk.EvaluationS1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + for i := 0; i < nbElmt; i++ { + pk.EvaluationS1BigDomain[i].Set(&pk.LId[pk.Permutation[i]]) + pk.EvaluationS2BigDomain[i].Set(&pk.LId[pk.Permutation[nbElmt+i]]) + pk.EvaluationS3BigDomain[i].Set(&pk.LId[pk.Permutation[2*nbElmt+i]]) + } + + // Evaluations of Sid1, Sid2, Sid3 on cosets of Domain[1] + pk.EvaluationId1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + copy(pk.EvaluationId1BigDomain, pk.LId[:nbElmt]) + copy(pk.EvaluationId2BigDomain, pk.LId[nbElmt:2*nbElmt]) + copy(pk.EvaluationId3BigDomain, pk.LId[2*nbElmt:]) + pk.Domain[0].FFTInverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality]) + vk.IdCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.IdCanonical[0], pk.EvaluationId1BigDomain) + copy(vk.IdCanonical[1], pk.EvaluationId2BigDomain) + copy(vk.IdCanonical[2], pk.EvaluationId3BigDomain) + + var err error + vk.Idpp[0], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId1BigDomain) + if err != nil { + return err + } + vk.Idpp[1], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId2BigDomain) + if err != nil { + return err + } + vk.Idpp[2], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId3BigDomain) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationId1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId3BigDomain, fft.DIF, fft.OnCoset()) + + pk.Domain[0].FFTInverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + + // commit S1, S2, S3 + vk.SCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.SCanonical[0], pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[1], pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[2], pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + vk.Spp[0], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[0]) + if err != nil { + return err + } + vk.Spp[1], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[1]) + if err != nil { + return err + } + vk.Spp[2], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[2]) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationS1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS3BigDomain, fft.DIF, fft.OnCoset()) + + return nil + +} + +// getIDSmallDomain returns the Lagrange form of ID on the small domain +func getIDSmallDomain(domain *fft.Domain) []fr.Element { + + res := make([]fr.Element, 3*domain.Cardinality) + + res[0].SetOne() + res[domain.Cardinality].Set(&domain.FrMultiplicativeGen) + res[2*domain.Cardinality].Square(&domain.FrMultiplicativeGen) + + for i := uint64(1); i < domain.Cardinality; i++ { + res[i].Mul(&res[i-1], &domain.Generator) + res[domain.Cardinality+i].Mul(&res[domain.Cardinality+i-1], &domain.Generator) + res[2*domain.Cardinality+i].Mul(&res[2*domain.Cardinality+i-1], &domain.Generator) + } + + return res +} + +// NbPublicWitness returns the expected public witness size (number of field elements) +func (vk *VerifyingKey) NbPublicWitness() int { + return int(vk.NbPublicVariables) +} + +// VerifyingKey returns pk.Vk +func (pk *ProvingKey) VerifyingKey() interface{} { + return pk.Vk +} diff --git a/backend/plonkfri/bw6-633/verify.go b/backend/plonkfri/bw6-633/verify.go new file mode 100644 index 0000000000..1cf213e2cc --- /dev/null +++ b/backend/plonkfri/bw6-633/verify.go @@ -0,0 +1,398 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by gnark DO NOT EDIT + +package plonkfri + +import ( + "errors" + "fmt" + "github.com/consensys/gnark-crypto/ecc/bw6-633/fr" + "github.com/consensys/gnark-crypto/ecc/bw6-633/fr/fri" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + "github.com/consensys/gnark/backend" + "math/big" +) + +var ErrInvalidAlgebraicRelation = errors.New("algebraic relation does not hold") + +func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { + cfg, err := backend.NewVerifierConfig(opts...) + if err != nil { + return fmt.Errorf("create backend config: %w", err) + } + + // 0 - derive the challenges with Fiat Shamir + fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") + + dataFiatShamir := make([][fr.Bytes]byte, len(publicWitness)+3) + for i := 0; i < len(publicWitness); i++ { + copy(dataFiatShamir[i][:], publicWitness[i].Marshal()) + } + copy(dataFiatShamir[len(publicWitness)][:], proof.LROpp[0].ID) + copy(dataFiatShamir[len(publicWitness)+1][:], proof.LROpp[1].ID) + copy(dataFiatShamir[len(publicWitness)+2][:], proof.LROpp[2].ID) + + beta, err := deriveRandomnessFixedSize(fs, "gamma", dataFiatShamir...) + if err != nil { + return err + } + + gamma, err := deriveRandomness(fs, "beta", nil) + if err != nil { + return err + } + + alpha, err := deriveRandomness(fs, "alpha", proof.Zpp.ID) + if err != nil { + return err + } + + // compute the size of the domain of evaluation of the committed polynomial, + // the opening position. The challenge zeta will be g^{i} where i is the opening + // position, and g is the generator of the fri domain. + rho := uint64(fri.GetRho()) + friSize := 2 * rho * vk.Size + var bFriSize big.Int + bFriSize.SetInt64(int64(friSize)) + frOpeningPosition, err := deriveRandomness(fs, "zeta", proof.Hpp[0].ID, proof.Hpp[1].ID, proof.Hpp[2].ID) + if err != nil { + return err + } + var bOpeningPosition big.Int + bOpeningPosition.SetBytes(frOpeningPosition.Marshal()).Mod(&bOpeningPosition, &bFriSize) + openingPosition := bOpeningPosition.Uint64() + + shiftedOpeningPosition := (openingPosition + uint64(2*rho)) % friSize + err = vk.Iopp.VerifyOpening(shiftedOpeningPosition, proof.OpeningsZmp[1], proof.Zpp) + if err != nil { + return err + } + + // 1 - verify that the commitments are low degree polynomials + + // ql, qr, qm, qo, qkIncomplete + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[2]) + if err != nil { + return err + } + + // s1, s2, s3 + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[2]) + if err != nil { + return err + } + + // id1, id2, id3 + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[2]) + if err != nil { + return err + } + + // Z + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // 2 - verify the openings + + // ql, qr, qm, qo, qkIncomplete + // openingPosition := uint64(2) + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[0], vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[1], vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[2], vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[3], vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[4], vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[0], proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[1], proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[2], proof.LROpp[2]) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[0], proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[1], proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[2], proof.Hpp[2]) + if err != nil { + return err + } + + // s0, s1, s2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[0], vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[1], vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[2], vk.Spp[2]) + if err != nil { + return err + } + + // id0, id1, id2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[0], vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[1], vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[2], vk.Idpp[2]) + if err != nil { + return err + } + + // Z, Zshift + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsZmp[0], proof.Zpp) + if err != nil { + return err + } + + // verification of the algebraic relation + var ql, qr, qm, qo, qk fr.Element + ql.Set(&proof.OpeningsQlQrQmQoQkincompletemp[0].ClaimedValue) + qr.Set(&proof.OpeningsQlQrQmQoQkincompletemp[1].ClaimedValue) + qm.Set(&proof.OpeningsQlQrQmQoQkincompletemp[2].ClaimedValue) + qo.Set(&proof.OpeningsQlQrQmQoQkincompletemp[3].ClaimedValue) + qk.Set(&proof.OpeningsQlQrQmQoQkincompletemp[4].ClaimedValue) // -> to be completed + + var l, r, o fr.Element + l.Set(&proof.OpeningsLROmp[0].ClaimedValue) + r.Set(&proof.OpeningsLROmp[1].ClaimedValue) + o.Set(&proof.OpeningsLROmp[2].ClaimedValue) + + var h1, h2, h3 fr.Element + h1.Set(&proof.OpeningsHmp[0].ClaimedValue) + h2.Set(&proof.OpeningsHmp[1].ClaimedValue) + h3.Set(&proof.OpeningsHmp[2].ClaimedValue) + + var s1, s2, s3 fr.Element + s1.Set(&proof.OpeningsS1S2S3mp[0].ClaimedValue) + s2.Set(&proof.OpeningsS1S2S3mp[1].ClaimedValue) + s3.Set(&proof.OpeningsS1S2S3mp[2].ClaimedValue) + + var id1, id2, id3 fr.Element + id1.Set(&proof.OpeningsId1Id2Id3mp[0].ClaimedValue) + id2.Set(&proof.OpeningsId1Id2Id3mp[1].ClaimedValue) + id3.Set(&proof.OpeningsId1Id2Id3mp[2].ClaimedValue) + + var z, zshift fr.Element + z.Set(&proof.OpeningsZmp[0].ClaimedValue) + zshift.Set(&proof.OpeningsZmp[1].ClaimedValue) + + // 2 - compute the LHS: (ql*l+..+qk)+ α*(z(μx)*(l+β*s₁+γ)*..-z*(l+β*id1+γ))+α²*z*(l1-1) + var zeta fr.Element + zeta.Exp(vk.GenOpening, &bOpeningPosition) + + var lhs, t1, t2, t3, tmp, tmp2 fr.Element + // 2.1 (ql*l+..+qk) + t1.Mul(&l, &ql) + tmp.Mul(&r, &qr) + t1.Add(&t1, &tmp) + tmp.Mul(&qm, &l).Mul(&tmp, &r) + t1.Add(&t1, &tmp) + tmp.Mul(&o, &qo) + t1.Add(&tmp, &t1) + tmp = completeQk(publicWitness, vk, zeta) + tmp.Add(&qk, &tmp) + t1.Add(&tmp, &t1) + + // 2.2 (z(ux)*(l+β*s1+γ)*..-z*(l+β*id1+γ)) + t2.Mul(&beta, &s1).Add(&t2, &l).Add(&t2, &gamma) + tmp.Mul(&beta, &s2).Add(&tmp, &r).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2) + tmp.Mul(&beta, &s3).Add(&tmp, &o).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2).Mul(&t2, &zshift) + + tmp.Mul(&beta, &id1).Add(&tmp, &l).Add(&tmp, &gamma) + tmp2.Mul(&beta, &id2).Add(&tmp2, &r).Add(&tmp2, &gamma) + tmp.Mul(&tmp, &tmp2) + tmp2.Mul(&beta, &id3).Add(&tmp2, &o).Add(&tmp2, &gamma) + tmp.Mul(&tmp2, &tmp).Mul(&tmp, &z) + + t2.Sub(&t2, &tmp) + + // 2.3 (z-1)*l1 + var one fr.Element + one.SetOne() + t3.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&t3, &one) + tmp.Sub(&zeta, &one).Inverse(&tmp).Mul(&tmp, &vk.SizeInv) + t3.Mul(&tmp, &t3) + tmp.Sub(&z, &one) + t3.Mul(&tmp, &t3) + + // 2.4 (ql*l+s+qk) + α*(z(ux)*(l+β*s1+γ)*...-z*(l+β*id1+γ)..)+ α²*z*(l1-1) + lhs.Set(&t3).Mul(&lhs, &alpha).Add(&lhs, &t2).Mul(&lhs, &alpha).Add(&lhs, &t1) + + // 3 - compute the RHS + var rhs fr.Element + tmp.Exp(zeta, big.NewInt(int64(vk.Size+2))) + rhs.Mul(&h3, &tmp). + Add(&rhs, &h2). + Mul(&rhs, &tmp). + Add(&rhs, &h1) + + tmp.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&tmp, &one) + rhs.Mul(&rhs, &tmp) + + // 4 - verify the relation LHS==RHS + if !rhs.Equal(&lhs) { + return ErrInvalidAlgebraicRelation + } + + return nil + +} + +// completeQk returns ∑_{i> nn + _is := bits.Reverse64(uint64((i+toShift)%nbElmts)) >> nn + + // in what follows gⁱ is understood as the generator of the chosen coset of domainBig + f[0].Mul(&evaluationIDBigDomain, &beta).Add(&f[0], &l[_i]).Add(&f[0], &gamma) //l(gⁱ)+gⁱ*β+γ + f[1].Mul(&evaluationIDBigDomain, &cosetShift).Mul(&f[1], &beta).Add(&f[1], &r[_i]).Add(&f[1], &gamma) //r(gⁱ)+u*gⁱ*β+γ + f[2].Mul(&evaluationIDBigDomain, &cosetShiftSquare).Mul(&f[2], &beta).Add(&f[2], &o[_i]).Add(&f[2], &gamma) //o(gⁱ)+u²*gⁱ*β+γ + + g[0].Mul(&pk.EvaluationS1BigDomain[_i], &beta).Add(&g[0], &l[_i]).Add(&g[0], &gamma) //l(gⁱ))+s1(gⁱ)*β+γ + g[1].Mul(&pk.EvaluationS2BigDomain[_i], &beta).Add(&g[1], &r[_i]).Add(&g[1], &gamma) //r(gⁱ))+s2(gⁱ)*β+γ + g[2].Mul(&pk.EvaluationS3BigDomain[_i], &beta).Add(&g[2], &o[_i]).Add(&g[2], &gamma) //o(gⁱ))+s3(gⁱ)*β+γ + + f[0].Mul(&f[0], &f[1]).Mul(&f[0], &f[2]).Mul(&f[0], &z[_i]) // z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + g[0].Mul(&g[0], &g[1]).Mul(&g[0], &g[2]).Mul(&g[0], &z[_is]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) + + res[_i].Sub(&g[0], &f[0]) // z_(ugⁱ)*(l(gⁱ))+s₁(gⁱ)*β+γ)*(r(gⁱ))+s₂(gⁱ)*β+γ)*(o(gⁱ))+s₃(gⁱ)*β+γ) - z(gⁱ)*(l(gⁱ)+g^i*β+γ)*(r(g^i)+u*g^i*β+γ)*(o(g^i)+u²*g^i*β+γ) + + evaluationIDBigDomain.Mul(&evaluationIDBigDomain, &pk.Domain[1].Generator) // gⁱ*g + } + }) + + return res +} + +// evalConstraintsInd computes the evaluation of lL+qrR+qqmL.R+qoO+k on +// the odd coset of (Z/8mZ)/(Z/4mZ), where m=nbConstraints+nbAssertions. +// +// * lsL, lsR, lsO are the evaluation of the blinded solution vectors on odd cosets +// * lsQk is the completed version of qk, in canonical version +// +// lsL, lsR, lsO are in bit reversed order, lsQk is in the correct order. +func evalConstraintsInd(pk *ProvingKey, lsL, lsR, lsO, lsQk []fr.Element) []fr.Element { + + res := make([]fr.Element, pk.Domain[1].Cardinality) + // nn := uint64(64 - bits.TrailingZeros64(pk.Domain[1].Cardinality)) + + utils.Parallelize(len(res), func(start, end int) { + + var t0, t1 fr.Element + + for i := start; i < end; i++ { + + // irev := bits.Reverse64(uint64(i)) >> nn + + t1.Mul(&pk.EvaluationQmDomainBigBitReversed[i], &lsR[i]) // qm.r + t1.Add(&t1, &pk.EvaluationQlDomainBigBitReversed[i]) // qm.r + ql + t1.Mul(&t1, &lsL[i]) // qm.l.r + ql.l + + t0.Mul(&pk.EvaluationQrDomainBigBitReversed[i], &lsR[i]) + t0.Add(&t0, &t1) // qm.l.r + ql.l + qr.r + + t1.Mul(&pk.EvaluationQoDomainBigBitReversed[i], &lsO[i]) + t0.Add(&t0, &t1) // ql.l + qr.r + qm.l.r + qo.o + res[i].Add(&t0, &lsQk[i]) // ql.l + qr.r + qm.l.r + qo.o + k + + } + }) + + return res +} + +// fftBigCosetWOBitReverse evaluates poly (canonical form) of degree m> nn + + t.Sub(&evaluationBlindedZDomainBigBitReversed[_i], &one) // evaluates L₁(X)*(Z(X)-1) on a coset of the big domain + h[_i].Mul(&startsAtOne[_i], &t).Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintOrderingBitReversed[_i]). + Mul(&h[_i], &alpha). + Add(&h[_i], &evaluationConstraintsIndBitReversed[_i]). + Mul(&h[_i], &evaluationXnMinusOneInverse[i%ratio]) + } + }) + + // put h in canonical form. h is of degree 3*(n+1)+2. + // using fft.DIT put h revert bit reverse + pk.Domain[1].FFTInverse(h, fft.DIT, fft.OnCoset()) + + // degree of hi is n+2 because of the blinding + h1 := h[:pk.Domain[0].Cardinality+2] + h2 := h[pk.Domain[0].Cardinality+2 : 2*(pk.Domain[0].Cardinality+2)] + h3 := h[2*(pk.Domain[0].Cardinality+2) : 3*(pk.Domain[0].Cardinality+2)] + + return h1, h2, h3 + +} + +// computeZ computes Z, in canonical basis, where: +// +// - Z of degree n (domainNum.Cardinality) +// +// - Z(1)=1 +// (l_i+z**i+gamma)*(r_i+u*z**i+gamma)*(o_i+u**2z**i+gamma) +// +// - for i>0: Z(u**i) = Pi_{k "small" domain, used for individual polynomials + // 1 -> "big" domain, used for the computation of the quotient + Domain [2]fft.Domain + + // s1, s2, s3 (L=Lagrange basis small domain, C=canonical basis, Ls=Lagrange Shifted big domain) + LId []fr.Element + EvaluationId1BigDomain, EvaluationId2BigDomain, EvaluationId3BigDomain []fr.Element + EvaluationS1BigDomain, EvaluationS2BigDomain, EvaluationS3BigDomain []fr.Element + + // position -> permuted position (position in [0,3*sizeSystem-1]) + Permutation []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, that is the closest power of 2 bounding above + // number of constraints+number of public inputs + Size uint64 + SizeInv fr.Element + Generator fr.Element + NbPublicVariables uint64 + + // cosetShift generator of the coset on the small domain + CosetShift fr.Element + + // S commitments to S1, S2, S3 + SCanonical [3][]fr.Element + Spp [3]fri.ProofOfProximity + + // Id commitments to Id1, Id2, Id3 + // Id [3]Commitment + IdCanonical [3][]fr.Element + Idpp [3]fri.ProofOfProximity + + // Commitments to ql, qr, qm, qo prepended with as many zeroes (ones for l) as there are public inputs. + // In particular Qk is not complete. + Qpp [5]fri.ProofOfProximity // Ql, Qr, Qm, Qo, Qk + + // Iopp scheme (currently one for each size of polynomial) + Iopp fri.Iopp + + // generator of the group on which the Iopp works. If i is the opening position, + // the polynomials will be opened at genOpening^{i}. + GenOpening fr.Element +} + +// Setup sets proving and verifying keys +func Setup(spr *cs.SparseR1CS) (*ProvingKey, *VerifyingKey, error) { + + var pk ProvingKey + var vk VerifyingKey + + // The verifying key shares data with the proving key + pk.Vk = &vk + + nbConstraints := spr.GetNbConstraints() + + // fft domains + sizeSystem := uint64(nbConstraints + len(spr.Public)) // len(spr.Public) is for the placeholder constraints + pk.Domain[0] = *fft.NewDomain(sizeSystem) + + // h, the quotient polynomial is of degree 3(n+1)+2, so it's in a 3(n+2) dim vector space, + // the domain is the next power of 2 superior to 3(n+2). 4*domainNum is enough in all cases + // except when n<6. + if sizeSystem < 6 { + pk.Domain[1] = *fft.NewDomain(8 * sizeSystem) + } else { + pk.Domain[1] = *fft.NewDomain(4 * sizeSystem) + } + pk.Vk.CosetShift.Set(&pk.Domain[0].FrMultiplicativeGen) + + vk.Size = pk.Domain[0].Cardinality + vk.SizeInv.SetUint64(vk.Size).Inverse(&vk.SizeInv) + vk.Generator.Set(&pk.Domain[0].Generator) + vk.NbPublicVariables = uint64(len(spr.Public)) + + // IOP schemess + // The +2 is to handle the blinding. + sizeIopp := pk.Domain[0].Cardinality + 2 + vk.Iopp = fri.RADIX_2_FRI.New(sizeIopp, sha256.New()) + // only there to access the group used in FRI... + rho := uint64(fri.GetRho()) + // we multiply by 2 because the IOP is created with size pk.Domain[0].Cardinality + 2 (because + // of the blinding), so the domain will be rho*size_domain where size_domain is the next power + // of 2 after pk.Domain[0].Cardinality + 2, which is 2*rho*pk.Domain[0].Cardinality + tmpDomain := fft.NewDomain(2 * rho * pk.Domain[0].Cardinality) + vk.GenOpening.Set(&tmpDomain.Generator) + + // public polynomials corresponding to constraints: [ placholders | constraints | assertions ] + pk.EvaluationQlDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQrDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQmDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationQoDomainBigBitReversed = make([]fr.Element, pk.Domain[1].Cardinality) + pk.LQkIncompleteDomainSmall = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQkIncomplete = make([]fr.Element, pk.Domain[0].Cardinality) + + for i := 0; i < len(spr.Public); i++ { // placeholders (-PUB_INPUT_i + qk_i = 0) TODO should return error if size is inconsistent + pk.EvaluationQlDomainBigBitReversed[i].SetOne().Neg(&pk.EvaluationQlDomainBigBitReversed[i]) + pk.EvaluationQrDomainBigBitReversed[i].SetZero() + pk.EvaluationQmDomainBigBitReversed[i].SetZero() + pk.EvaluationQoDomainBigBitReversed[i].SetZero() + pk.LQkIncompleteDomainSmall[i].SetZero() // --> to be completed by the prover + pk.CQkIncomplete[i].Set(&pk.LQkIncompleteDomainSmall[i]) // --> to be completed by the prover + } + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + pk.EvaluationQlDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QL]) + pk.EvaluationQrDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QR]) + pk.EvaluationQmDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QM]) + pk.EvaluationQoDomainBigBitReversed[offset+j].Set(&spr.Coefficients[c.QO]) + pk.LQkIncompleteDomainSmall[offset+j].Set(&spr.Coefficients[c.QC]) + pk.CQkIncomplete[offset+j].Set(&pk.LQkIncompleteDomainSmall[offset+j]) + + j++ + } + + pk.Domain[0].FFTInverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.CQkIncomplete, fft.DIF) + fft.BitReverse(pk.EvaluationQlDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQrDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQmDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationQoDomainBigBitReversed[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.CQkIncomplete) + + // Commit to the polynomials to set up the verifying key + pk.CQl = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQr = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQm = make([]fr.Element, pk.Domain[0].Cardinality) + pk.CQo = make([]fr.Element, pk.Domain[0].Cardinality) + copy(pk.CQl, pk.EvaluationQlDomainBigBitReversed) + copy(pk.CQr, pk.EvaluationQrDomainBigBitReversed) + copy(pk.CQm, pk.EvaluationQmDomainBigBitReversed) + copy(pk.CQo, pk.EvaluationQoDomainBigBitReversed) + var err error + vk.Qpp[0], err = vk.Iopp.BuildProofOfProximity(pk.CQl) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[1], err = vk.Iopp.BuildProofOfProximity(pk.CQr) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[2], err = vk.Iopp.BuildProofOfProximity(pk.CQm) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[3], err = vk.Iopp.BuildProofOfProximity(pk.CQo) + if err != nil { + return &pk, &vk, err + } + vk.Qpp[4], err = vk.Iopp.BuildProofOfProximity(pk.CQkIncomplete) + if err != nil { + return &pk, &vk, err + } + + pk.Domain[1].FFT(pk.EvaluationQlDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQrDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQmDomainBigBitReversed, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationQoDomainBigBitReversed, fft.DIF, fft.OnCoset()) + + // build permutation. Note: at this stage, the permutation takes in account the placeholders + buildPermutation(spr, &pk) + + // set s1, s2, s3 + err = computePermutationPolynomials(&pk, &vk) + if err != nil { + return &pk, &vk, err + } + + return &pk, &vk, nil + +} + +// buildPermutation builds the Permutation associated with a circuit. +// +// The permutation s is composed of cycles of maximum length such that +// +// s. (l||r||o) = (l||r||o) +// +// , where l||r||o is the concatenation of the indices of l, r, o in +// ql.l+qr.r+qm.l.r+qo.O+k = 0. +// +// The permutation is encoded as a slice s of size 3*size(l), where the +// i-th entry of l||r||o is sent to the s[i]-th entry, so it acts on a tab +// like this: for i in tab: tab[i] = tab[permutation[i]] +func buildPermutation(spr *cs.SparseR1CS, pk *ProvingKey) { + + nbVariables := spr.NbInternalVariables + len(spr.Public) + len(spr.Secret) + sizeSolution := int(pk.Domain[0].Cardinality) + + // init permutation + pk.Permutation = make([]int64, 3*sizeSolution) + for i := 0; i < len(pk.Permutation); i++ { + pk.Permutation[i] = -1 + } + + // init LRO position -> variable_ID + lro := make([]int, 3*sizeSolution) // position -> variable_ID + for i := 0; i < len(spr.Public); i++ { + lro[i] = i // IDs of LRO associated to placeholders (only L needs to be taken care of) + } + + offset := len(spr.Public) + + j := 0 + it := spr.GetSparseR1CIterator() + for c := it.Next(); c != nil; c = it.Next() { + lro[offset+j] = int(c.XA) + lro[sizeSolution+offset+j] = int(c.XB) + lro[2*sizeSolution+offset+j] = int(c.XC) + j++ + } + + // init cycle: + // map ID -> last position the ID was seen + cycle := make([]int64, nbVariables) + for i := 0; i < len(cycle); i++ { + cycle[i] = -1 + } + + for i := 0; i < len(lro); i++ { + if cycle[lro[i]] != -1 { + // if != -1, it means we already encountered this value + // so we need to set the corresponding permutation index. + pk.Permutation[i] = cycle[lro[i]] + } + cycle[lro[i]] = int64(i) + } + + // complete the Permutation by filling the first IDs encountered + for i := 0; i < len(pk.Permutation); i++ { + if pk.Permutation[i] == -1 { + pk.Permutation[i] = cycle[lro[i]] + } + } +} + +// computePermutationPolynomials computes the LDE (Lagrange basis) of the permutations +// s1, s2, s3. +// +// 0 1 .. n-1 | n n+1 .. 2*n-1 | 2n 2n+1 .. 3n-1 | +// +// | +// | Permutation +// +// s00 s01 .. s0n-1 s10 s11 .. s1n-1 s20 s21 .. s2n-1 v +// \---------------/ \--------------------/ \------------------------/ +// +// s1 (LDE) s2 (LDE) s3 (LDE) +func computePermutationPolynomials(pk *ProvingKey, vk *VerifyingKey) error { + + nbElmt := int(pk.Domain[0].Cardinality) + + // sID = [1,..,g^{n-1},s,..,s*g^{n-1},s^2,..,s^2*g^{n-1}] + pk.LId = getIDSmallDomain(&pk.Domain[0]) + + // canonical form of S1, S2, S3 + pk.EvaluationS1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationS3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + for i := 0; i < nbElmt; i++ { + pk.EvaluationS1BigDomain[i].Set(&pk.LId[pk.Permutation[i]]) + pk.EvaluationS2BigDomain[i].Set(&pk.LId[pk.Permutation[nbElmt+i]]) + pk.EvaluationS3BigDomain[i].Set(&pk.LId[pk.Permutation[2*nbElmt+i]]) + } + + // Evaluations of Sid1, Sid2, Sid3 on cosets of Domain[1] + pk.EvaluationId1BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId2BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + pk.EvaluationId3BigDomain = make([]fr.Element, pk.Domain[1].Cardinality) + copy(pk.EvaluationId1BigDomain, pk.LId[:nbElmt]) + copy(pk.EvaluationId2BigDomain, pk.LId[nbElmt:2*nbElmt]) + copy(pk.EvaluationId3BigDomain, pk.LId[2*nbElmt:]) + pk.Domain[0].FFTInverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationId1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationId3BigDomain[:pk.Domain[0].Cardinality]) + vk.IdCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.IdCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.IdCanonical[0], pk.EvaluationId1BigDomain) + copy(vk.IdCanonical[1], pk.EvaluationId2BigDomain) + copy(vk.IdCanonical[2], pk.EvaluationId3BigDomain) + + var err error + vk.Idpp[0], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId1BigDomain) + if err != nil { + return err + } + vk.Idpp[1], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId2BigDomain) + if err != nil { + return err + } + vk.Idpp[2], err = vk.Iopp.BuildProofOfProximity(pk.EvaluationId3BigDomain) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationId1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationId3BigDomain, fft.DIF, fft.OnCoset()) + + pk.Domain[0].FFTInverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + pk.Domain[0].FFTInverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality], fft.DIF) + fft.BitReverse(pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + fft.BitReverse(pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + + // commit S1, S2, S3 + vk.SCanonical[0] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[1] = make([]fr.Element, pk.Domain[0].Cardinality) + vk.SCanonical[2] = make([]fr.Element, pk.Domain[0].Cardinality) + copy(vk.SCanonical[0], pk.EvaluationS1BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[1], pk.EvaluationS2BigDomain[:pk.Domain[0].Cardinality]) + copy(vk.SCanonical[2], pk.EvaluationS3BigDomain[:pk.Domain[0].Cardinality]) + vk.Spp[0], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[0]) + if err != nil { + return err + } + vk.Spp[1], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[1]) + if err != nil { + return err + } + vk.Spp[2], err = vk.Iopp.BuildProofOfProximity(vk.SCanonical[2]) + if err != nil { + return err + } + pk.Domain[1].FFT(pk.EvaluationS1BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS2BigDomain, fft.DIF, fft.OnCoset()) + pk.Domain[1].FFT(pk.EvaluationS3BigDomain, fft.DIF, fft.OnCoset()) + + return nil + +} + +// getIDSmallDomain returns the Lagrange form of ID on the small domain +func getIDSmallDomain(domain *fft.Domain) []fr.Element { + + res := make([]fr.Element, 3*domain.Cardinality) + + res[0].SetOne() + res[domain.Cardinality].Set(&domain.FrMultiplicativeGen) + res[2*domain.Cardinality].Square(&domain.FrMultiplicativeGen) + + for i := uint64(1); i < domain.Cardinality; i++ { + res[i].Mul(&res[i-1], &domain.Generator) + res[domain.Cardinality+i].Mul(&res[domain.Cardinality+i-1], &domain.Generator) + res[2*domain.Cardinality+i].Mul(&res[2*domain.Cardinality+i-1], &domain.Generator) + } + + return res +} + +// NbPublicWitness returns the expected public witness size (number of field elements) +func (vk *VerifyingKey) NbPublicWitness() int { + return int(vk.NbPublicVariables) +} + +// VerifyingKey returns pk.Vk +func (pk *ProvingKey) VerifyingKey() interface{} { + return pk.Vk +} diff --git a/backend/plonkfri/bw6-761/verify.go b/backend/plonkfri/bw6-761/verify.go new file mode 100644 index 0000000000..fe74c9d864 --- /dev/null +++ b/backend/plonkfri/bw6-761/verify.go @@ -0,0 +1,398 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by gnark DO NOT EDIT + +package plonkfri + +import ( + "errors" + "fmt" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/fri" + fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" + "github.com/consensys/gnark/backend" + "math/big" +) + +var ErrInvalidAlgebraicRelation = errors.New("algebraic relation does not hold") + +func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { + cfg, err := backend.NewVerifierConfig(opts...) + if err != nil { + return fmt.Errorf("create backend config: %w", err) + } + + // 0 - derive the challenges with Fiat Shamir + fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") + + dataFiatShamir := make([][fr.Bytes]byte, len(publicWitness)+3) + for i := 0; i < len(publicWitness); i++ { + copy(dataFiatShamir[i][:], publicWitness[i].Marshal()) + } + copy(dataFiatShamir[len(publicWitness)][:], proof.LROpp[0].ID) + copy(dataFiatShamir[len(publicWitness)+1][:], proof.LROpp[1].ID) + copy(dataFiatShamir[len(publicWitness)+2][:], proof.LROpp[2].ID) + + beta, err := deriveRandomnessFixedSize(fs, "gamma", dataFiatShamir...) + if err != nil { + return err + } + + gamma, err := deriveRandomness(fs, "beta", nil) + if err != nil { + return err + } + + alpha, err := deriveRandomness(fs, "alpha", proof.Zpp.ID) + if err != nil { + return err + } + + // compute the size of the domain of evaluation of the committed polynomial, + // the opening position. The challenge zeta will be g^{i} where i is the opening + // position, and g is the generator of the fri domain. + rho := uint64(fri.GetRho()) + friSize := 2 * rho * vk.Size + var bFriSize big.Int + bFriSize.SetInt64(int64(friSize)) + frOpeningPosition, err := deriveRandomness(fs, "zeta", proof.Hpp[0].ID, proof.Hpp[1].ID, proof.Hpp[2].ID) + if err != nil { + return err + } + var bOpeningPosition big.Int + bOpeningPosition.SetBytes(frOpeningPosition.Marshal()).Mod(&bOpeningPosition, &bFriSize) + openingPosition := bOpeningPosition.Uint64() + + shiftedOpeningPosition := (openingPosition + uint64(2*rho)) % friSize + err = vk.Iopp.VerifyOpening(shiftedOpeningPosition, proof.OpeningsZmp[1], proof.Zpp) + if err != nil { + return err + } + + // 1 - verify that the commitments are low degree polynomials + + // ql, qr, qm, qo, qkIncomplete + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.LROpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(proof.Hpp[2]) + if err != nil { + return err + } + + // s1, s2, s3 + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Spp[2]) + if err != nil { + return err + } + + // id1, id2, id3 + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyProofOfProximity(vk.Idpp[2]) + if err != nil { + return err + } + + // Z + err = vk.Iopp.VerifyProofOfProximity(proof.Zpp) + if err != nil { + return err + } + + // 2 - verify the openings + + // ql, qr, qm, qo, qkIncomplete + // openingPosition := uint64(2) + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[0], vk.Qpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[1], vk.Qpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[2], vk.Qpp[2]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[3], vk.Qpp[3]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsQlQrQmQoQkincompletemp[4], vk.Qpp[4]) + if err != nil { + return err + } + + // l, r, o + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[0], proof.LROpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[1], proof.LROpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsLROmp[2], proof.LROpp[2]) + if err != nil { + return err + } + + // h0, h1, h2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[0], proof.Hpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[1], proof.Hpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsHmp[2], proof.Hpp[2]) + if err != nil { + return err + } + + // s0, s1, s2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[0], vk.Spp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[1], vk.Spp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsS1S2S3mp[2], vk.Spp[2]) + if err != nil { + return err + } + + // id0, id1, id2 + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[0], vk.Idpp[0]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[1], vk.Idpp[1]) + if err != nil { + return err + } + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsId1Id2Id3mp[2], vk.Idpp[2]) + if err != nil { + return err + } + + // Z, Zshift + err = vk.Iopp.VerifyOpening(openingPosition, proof.OpeningsZmp[0], proof.Zpp) + if err != nil { + return err + } + + // verification of the algebraic relation + var ql, qr, qm, qo, qk fr.Element + ql.Set(&proof.OpeningsQlQrQmQoQkincompletemp[0].ClaimedValue) + qr.Set(&proof.OpeningsQlQrQmQoQkincompletemp[1].ClaimedValue) + qm.Set(&proof.OpeningsQlQrQmQoQkincompletemp[2].ClaimedValue) + qo.Set(&proof.OpeningsQlQrQmQoQkincompletemp[3].ClaimedValue) + qk.Set(&proof.OpeningsQlQrQmQoQkincompletemp[4].ClaimedValue) // -> to be completed + + var l, r, o fr.Element + l.Set(&proof.OpeningsLROmp[0].ClaimedValue) + r.Set(&proof.OpeningsLROmp[1].ClaimedValue) + o.Set(&proof.OpeningsLROmp[2].ClaimedValue) + + var h1, h2, h3 fr.Element + h1.Set(&proof.OpeningsHmp[0].ClaimedValue) + h2.Set(&proof.OpeningsHmp[1].ClaimedValue) + h3.Set(&proof.OpeningsHmp[2].ClaimedValue) + + var s1, s2, s3 fr.Element + s1.Set(&proof.OpeningsS1S2S3mp[0].ClaimedValue) + s2.Set(&proof.OpeningsS1S2S3mp[1].ClaimedValue) + s3.Set(&proof.OpeningsS1S2S3mp[2].ClaimedValue) + + var id1, id2, id3 fr.Element + id1.Set(&proof.OpeningsId1Id2Id3mp[0].ClaimedValue) + id2.Set(&proof.OpeningsId1Id2Id3mp[1].ClaimedValue) + id3.Set(&proof.OpeningsId1Id2Id3mp[2].ClaimedValue) + + var z, zshift fr.Element + z.Set(&proof.OpeningsZmp[0].ClaimedValue) + zshift.Set(&proof.OpeningsZmp[1].ClaimedValue) + + // 2 - compute the LHS: (ql*l+..+qk)+ α*(z(μx)*(l+β*s₁+γ)*..-z*(l+β*id1+γ))+α²*z*(l1-1) + var zeta fr.Element + zeta.Exp(vk.GenOpening, &bOpeningPosition) + + var lhs, t1, t2, t3, tmp, tmp2 fr.Element + // 2.1 (ql*l+..+qk) + t1.Mul(&l, &ql) + tmp.Mul(&r, &qr) + t1.Add(&t1, &tmp) + tmp.Mul(&qm, &l).Mul(&tmp, &r) + t1.Add(&t1, &tmp) + tmp.Mul(&o, &qo) + t1.Add(&tmp, &t1) + tmp = completeQk(publicWitness, vk, zeta) + tmp.Add(&qk, &tmp) + t1.Add(&tmp, &t1) + + // 2.2 (z(ux)*(l+β*s1+γ)*..-z*(l+β*id1+γ)) + t2.Mul(&beta, &s1).Add(&t2, &l).Add(&t2, &gamma) + tmp.Mul(&beta, &s2).Add(&tmp, &r).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2) + tmp.Mul(&beta, &s3).Add(&tmp, &o).Add(&tmp, &gamma) + t2.Mul(&tmp, &t2).Mul(&t2, &zshift) + + tmp.Mul(&beta, &id1).Add(&tmp, &l).Add(&tmp, &gamma) + tmp2.Mul(&beta, &id2).Add(&tmp2, &r).Add(&tmp2, &gamma) + tmp.Mul(&tmp, &tmp2) + tmp2.Mul(&beta, &id3).Add(&tmp2, &o).Add(&tmp2, &gamma) + tmp.Mul(&tmp2, &tmp).Mul(&tmp, &z) + + t2.Sub(&t2, &tmp) + + // 2.3 (z-1)*l1 + var one fr.Element + one.SetOne() + t3.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&t3, &one) + tmp.Sub(&zeta, &one).Inverse(&tmp).Mul(&tmp, &vk.SizeInv) + t3.Mul(&tmp, &t3) + tmp.Sub(&z, &one) + t3.Mul(&tmp, &t3) + + // 2.4 (ql*l+s+qk) + α*(z(ux)*(l+β*s1+γ)*...-z*(l+β*id1+γ)..)+ α²*z*(l1-1) + lhs.Set(&t3).Mul(&lhs, &alpha).Add(&lhs, &t2).Mul(&lhs, &alpha).Add(&lhs, &t1) + + // 3 - compute the RHS + var rhs fr.Element + tmp.Exp(zeta, big.NewInt(int64(vk.Size+2))) + rhs.Mul(&h3, &tmp). + Add(&rhs, &h2). + Mul(&rhs, &tmp). + Add(&rhs, &h1) + + tmp.Exp(zeta, big.NewInt(int64(vk.Size))).Sub(&tmp, &one) + rhs.Mul(&rhs, &tmp) + + // 4 - verify the relation LHS==RHS + if !rhs.Equal(&lhs) { + return ErrInvalidAlgebraicRelation + } + + return nil + +} + +// completeQk returns ∑_{i