github.com/consensys/gnark@v0.11.0/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl (about) 1 import ( 2 "errors" 3 "fmt" 4 "io" 5 "math/big" 6 {{ if eq .Curve "BN254" -}} 7 "text/template" 8 {{- end }} 9 "time" 10 "github.com/consensys/gnark/backend/solidity" 11 12 "github.com/consensys/gnark-crypto/ecc" 13 {{ template "import_curve" . }} 14 {{ template "import_fr" . }} 15 {{ if eq .Curve "BN254" -}} 16 "github.com/consensys/gnark-crypto/ecc/bn254/fp" 17 {{- end }} 18 {{- template "import_hash_to_field" . }} 19 {{ template "import_kzg" . }} 20 fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" 21 "github.com/consensys/gnark/backend" 22 "github.com/consensys/gnark/logger" 23 ) 24 25 var ( 26 errAlgebraicRelation = errors.New("algebraic relation does not hold") 27 errInvalidWitness = errors.New("witness length is invalid") 28 errInvalidPoint = errors.New("point is not on the curve") 29 ) 30 31 func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { 32 33 log := logger.Logger().With().Str("curve", "{{ toLower .Curve }}").Str("backend", "plonk").Logger() 34 start := time.Now() 35 cfg, err := backend.NewVerifierConfig(opts...) 36 if err != nil { 37 return fmt.Errorf("create backend config: %w", err) 38 } 39 40 if len(proof.Bsb22Commitments) != len(vk.Qcp) { 41 return errors.New("BSB22 Commitment number mismatch") 42 } 43 44 if len(publicWitness) != int(vk.NbPublicVariables) { 45 return errInvalidWitness 46 } 47 48 // check that the points in the proof are on the curve 49 for i := 0; i < len(proof.LRO); i++ { 50 if !proof.LRO[i].IsInSubGroup() { 51 return errInvalidPoint 52 } 53 } 54 if !proof.Z.IsInSubGroup() { 55 return errInvalidPoint 56 } 57 for i := 0; i < len(proof.H); i++ { 58 if !proof.H[i].IsInSubGroup() { 59 return errInvalidPoint 60 } 61 } 62 for i := 0; i < len(proof.Bsb22Commitments); i++ { 63 if !proof.Bsb22Commitments[i].IsInSubGroup() { 64 return errInvalidPoint 65 } 66 } 67 if !proof.BatchedProof.H.IsInSubGroup() { 68 return errInvalidPoint 69 } 70 if !proof.ZShiftedOpening.H.IsInSubGroup() { 71 return errInvalidPoint 72 } 73 74 // transcript to derive the challenge 75 fs := fiatshamir.NewTranscript(cfg.ChallengeHash, "gamma", "beta", "alpha", "zeta") 76 77 // The first challenge is derived using the public data: the commitments to the permutation, 78 // the coefficients of the circuit, and the public inputs. 79 // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) 80 if err := bindPublicData(fs, "gamma", vk, publicWitness); err != nil { 81 return err 82 } 83 gamma, err := deriveRandomness(fs, "gamma", &proof.LRO[0], &proof.LRO[1], &proof.LRO[2]) 84 if err != nil { 85 return err 86 } 87 88 // derive beta from Comm(l), Comm(r), Comm(o) 89 beta, err := deriveRandomness(fs, "beta") 90 if err != nil { 91 return err 92 } 93 94 // derive alpha from Com(Z), Bsb22Commitments 95 alphaDeps := make([]*curve.G1Affine, len(proof.Bsb22Commitments)+1) 96 for i := range proof.Bsb22Commitments { 97 alphaDeps[i] = &proof.Bsb22Commitments[i] 98 } 99 alphaDeps[len(alphaDeps)-1] = &proof.Z 100 alpha, err := deriveRandomness(fs, "alpha", alphaDeps...) 101 if err != nil { 102 return err 103 } 104 105 // derive zeta, the point of evaluation 106 zeta, err := deriveRandomness(fs, "zeta", &proof.H[0], &proof.H[1], &proof.H[2]) 107 if err != nil { 108 return err 109 } 110 111 // evaluation of zhZeta=ζⁿ-1 112 var zetaPowerM, zhZeta, lagrangeZero fr.Element 113 var bExpo big.Int 114 one := fr.One() 115 bExpo.SetUint64(vk.Size) 116 zetaPowerM.Exp(zeta, &bExpo) 117 zhZeta.Sub(&zetaPowerM, &one) // ζⁿ-1 118 lagrangeZero.Sub(&zeta, &one). // ζ-1 119 Inverse(&lagrangeZero). // 1/(ζ-1) 120 Mul(&lagrangeZero, &zhZeta). // (ζ^n-1)/(ζ-1) 121 Mul(&lagrangeZero, &vk.SizeInv) // 1/n * (ζ^n-1)/(ζ-1) 122 123 // compute PI = ∑_{i<n} Lᵢ*wᵢ 124 var pi fr.Element 125 var accw fr.Element 126 { 127 // [ζ-1,ζ-ω,ζ-ω²,..] 128 dens := make([]fr.Element, len(publicWitness)) 129 accw.SetOne() 130 for i := 0; i < len(publicWitness); i++ { 131 dens[i].Sub(&zeta, &accw) 132 accw.Mul(&accw, &vk.Generator) 133 } 134 135 // [1/(ζ-1),1/(ζ-ω),1/(ζ-ω²),..] 136 invDens := fr.BatchInvert(dens) 137 138 accw.SetOne() 139 var xiLi fr.Element 140 for i := 0; i < len(publicWitness); i++ { 141 xiLi.Mul(&zhZeta, &invDens[i]). 142 Mul(&xiLi, &vk.SizeInv). 143 Mul(&xiLi, &accw). 144 Mul(&xiLi, &publicWitness[i]) // Pi[i]*(ωⁱ/n)(ζ^n-1)/(ζ-ω^i) 145 accw.Mul(&accw, &vk.Generator) 146 pi.Add(&pi, &xiLi) 147 } 148 149 if cfg.HashToFieldFn == nil { 150 cfg.HashToFieldFn = hash_to_field.New([]byte("BSB22-Plonk")) 151 } 152 var hashedCmt fr.Element 153 nbBuf := fr.Bytes 154 if cfg.HashToFieldFn.Size() < fr.Bytes { 155 nbBuf = cfg.HashToFieldFn.Size() 156 } 157 var wPowI, den, lagrange fr.Element 158 for i, cci := range vk.CommitmentConstraintIndexes { 159 cfg.HashToFieldFn.Write(proof.Bsb22Commitments[i].Marshal()) 160 hashBts := cfg.HashToFieldFn.Sum(nil) 161 cfg.HashToFieldFn.Reset() 162 hashedCmt.SetBytes(hashBts[:nbBuf]) 163 164 // Computing Lᵢ(ζ) where i=CommitmentIndex 165 wPowI.Exp(vk.Generator, big.NewInt(int64(vk.NbPublicVariables)+int64(cci))) 166 den.Sub(&zeta, &wPowI) // ζ-wⁱ 167 lagrange.SetOne(). 168 Sub(&zetaPowerM, &lagrange). // ζⁿ-1 169 Mul(&lagrange, &wPowI). // wⁱ(ζⁿ-1) 170 Div(&lagrange, &den). // wⁱ(ζⁿ-1)/(ζ-wⁱ) 171 Mul(&lagrange, &vk.SizeInv) // wⁱ/n (ζⁿ-1)/(ζ-wⁱ) 172 173 xiLi.Mul(&lagrange, &hashedCmt) 174 pi.Add(&pi, &xiLi) 175 } 176 } 177 178 var _s1, _s2, tmp fr.Element 179 l := proof.BatchedProof.ClaimedValues[1] 180 r := proof.BatchedProof.ClaimedValues[2] 181 o := proof.BatchedProof.ClaimedValues[3] 182 s1 := proof.BatchedProof.ClaimedValues[4] 183 s2 := proof.BatchedProof.ClaimedValues[5] 184 185 // Z(ωζ) 186 zu := proof.ZShiftedOpening.ClaimedValue 187 188 // α²*L₁(ζ) 189 var alphaSquarelagrangeZero fr.Element 190 alphaSquarelagrangeZero.Mul(&lagrangeZero, &alpha). 191 Mul(&alphaSquarelagrangeZero, &alpha) // α²*L₁(ζ) 192 193 // computing the constant coefficient of the full algebraic relation 194 // , corresponding to the value of the linearisation polynomiat at ζ 195 // PI(ζ) - α²*L₁(ζ) + α(l(ζ)+β*s1(ζ)+γ)(r(ζ)+β*s2(ζ)+γ)(o(ζ)+γ)*z(ωζ) 196 var constLin fr.Element 197 constLin.Mul(&beta, &s1).Add(&constLin, &gamma).Add(&constLin, &l) // (l(ζ)+β*s1(ζ)+γ) 198 tmp.Mul(&s2, &beta).Add(&tmp, &gamma).Add(&tmp, &r) // (r(ζ)+β*s2(ζ)+γ) 199 constLin.Mul(&constLin, &tmp) // (l(ζ)+β*s1(ζ)+γ)(r(ζ)+β*s2(ζ)+γ) 200 tmp.Add(&o, &gamma) // (o(ζ)+γ) 201 constLin.Mul(&tmp, &constLin).Mul(&constLin, &alpha).Mul(&constLin, &zu) // α(l(ζ)+β*s1(ζ)+γ)(r(ζ)+β*s2(ζ)+γ)(o(ζ)+γ)*z(ωζ) 202 203 constLin.Sub(&constLin, &alphaSquarelagrangeZero).Add(&constLin, &pi) // PI(ζ) - α²*L₁(ζ) + α(l(ζ)+β*s1(ζ)+γ)(r(ζ)+β*s2(ζ)+γ)(o(ζ)+γ)*z(ωζ) 204 constLin.Neg(&constLin) // -[PI(ζ) - α²*L₁(ζ) + α(l(ζ)+β*s1(ζ)+γ)(r(ζ)+β*s2(ζ)+γ)(o(ζ)+γ)*z(ωζ)] 205 206 // check that the opening of the linearised polynomial is equal to -constLin 207 openingLinPol := proof.BatchedProof.ClaimedValues[0] 208 if !constLin.Equal(&openingLinPol) { 209 return errAlgebraicRelation 210 } 211 212 // computing the linearised polynomial digest 213 // α²*L₁(ζ)*[Z] + 214 // _s1*[s3]+_s2*[Z] + l(ζ)*[Ql] + 215 // l(ζ)r(ζ)*[Qm] + r(ζ)*[Qr] + o(ζ)*[Qo] + [Qk] + ∑ᵢQcp_(ζ)[Pi_i] - 216 // Z_{H}(ζ)*(([H₀] + ζᵐ⁺²*[H₁] + ζ²⁽ᵐ⁺²⁾*[H₂]) 217 // where 218 // _s1 = α*(l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*β*Z(μζ) 219 // _s2 = -α*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ) 220 221 // _s1 = α*(l(ζ)+β*s1(β)+γ)*(r(ζ)+β*s2(β)+γ)*β*Z(μζ) 222 _s1.Mul(&beta, &s1).Add(&_s1, &l).Add(&_s1, &gamma) // (l(ζ)+β*s1(β)+γ) 223 tmp.Mul(&beta, &s2).Add(&tmp, &r).Add(&tmp, &gamma) // (r(ζ)+β*s2(β)+γ) 224 _s1.Mul(&_s1, &tmp).Mul(&_s1, &beta).Mul(&_s1, &alpha).Mul(&_s1, &zu) // α*(l(ζ)+β*s1(β)+γ)*(r(ζ)+β*s2(β)+γ)*β*Z(μζ) 225 226 // _s2 = -α*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ) 227 _s2.Mul(&beta, &zeta).Add(&_s2, &gamma).Add(&_s2, &l) // (l(ζ)+β*ζ+γ) 228 tmp.Mul(&beta, &vk.CosetShift).Mul(&tmp, &zeta).Add(&tmp, &gamma).Add(&tmp, &r) // (r(ζ)+β*u*ζ+γ) 229 _s2.Mul(&_s2, &tmp) // (l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ) 230 tmp.Mul(&beta, &vk.CosetShift).Mul(&tmp, &vk.CosetShift).Mul(&tmp, &zeta).Add(&tmp, &o).Add(&tmp, &gamma) // (o(ζ)+β*u²*ζ+γ) 231 _s2.Mul(&_s2, &tmp).Mul(&_s2, &alpha).Neg(&_s2) // -α*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ) 232 233 // α²*L₁(ζ) - α*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ) 234 var coeffZ fr.Element 235 coeffZ.Add(&alphaSquarelagrangeZero, &_s2) 236 237 // l(ζ)*r(ζ) 238 var rl fr.Element 239 rl.Mul(&l, &r) 240 241 // -ζⁿ⁺²*(ζⁿ-1), -ζ²⁽ⁿ⁺²⁾*(ζⁿ-1), -(ζⁿ-1) 242 nPlusTwo := big.NewInt(int64(vk.Size) + 2) 243 var zetaNPlusTwoZh, zetaNPlusTwoSquareZh, zh fr.Element 244 zetaNPlusTwoZh.Exp(zeta, nPlusTwo) 245 zetaNPlusTwoSquareZh.Mul(&zetaNPlusTwoZh, &zetaNPlusTwoZh) // ζ²⁽ⁿ⁺²⁾ 246 zetaNPlusTwoZh.Mul(&zetaNPlusTwoZh, &zhZeta).Neg(&zetaNPlusTwoZh) // -ζⁿ⁺²*(ζⁿ-1) 247 zetaNPlusTwoSquareZh.Mul(&zetaNPlusTwoSquareZh, &zhZeta).Neg(&zetaNPlusTwoSquareZh) // -ζ²⁽ⁿ⁺²⁾*(ζⁿ-1) 248 zh.Neg(&zhZeta) 249 250 var linearizedPolynomialDigest curve.G1Affine 251 points := append(proof.Bsb22Commitments, 252 vk.Ql, vk.Qr, vk.Qm, vk.Qo, vk.Qk, 253 vk.S[2], proof.Z, 254 proof.H[0], proof.H[1], proof.H[2], 255 ) 256 257 qC := make([]fr.Element, len(proof.Bsb22Commitments)) 258 copy(qC, proof.BatchedProof.ClaimedValues[6:]) 259 260 scalars := append(qC, 261 l, r, rl, o, one, 262 _s1, coeffZ, 263 zh, zetaNPlusTwoZh, zetaNPlusTwoSquareZh, 264 ) 265 if _, err := linearizedPolynomialDigest.MultiExp(points, scalars, ecc.MultiExpConfig{}); err != nil { 266 return err 267 } 268 269 // Fold the first proof 270 digestsToFold := make([]curve.G1Affine, len(vk.Qcp)+6) 271 copy(digestsToFold[6:], vk.Qcp) 272 digestsToFold[0] = linearizedPolynomialDigest 273 digestsToFold[1] = proof.LRO[0] 274 digestsToFold[2] = proof.LRO[1] 275 digestsToFold[3] = proof.LRO[2] 276 digestsToFold[4] = vk.S[0] 277 digestsToFold[5] = vk.S[1] 278 foldedProof, foldedDigest, err := kzg.FoldProof( 279 digestsToFold, 280 &proof.BatchedProof, 281 zeta, 282 cfg.KZGFoldingHash, 283 zu.Marshal(), 284 ) 285 if err != nil { 286 return err 287 } 288 289 // Batch verify 290 var shiftedZeta fr.Element 291 shiftedZeta.Mul(&zeta, &vk.Generator) 292 err = kzg.BatchVerifyMultiPoints([]kzg.Digest{ 293 foldedDigest, 294 proof.Z, 295 }, 296 []kzg.OpeningProof{ 297 foldedProof, 298 proof.ZShiftedOpening, 299 }, 300 []fr.Element{ 301 zeta, 302 shiftedZeta, 303 }, 304 vk.Kzg, 305 ) 306 307 log.Debug().Dur("took", time.Since(start)).Msg("verifier done") 308 309 return err 310 } 311 312 func bindPublicData(fs *fiatshamir.Transcript, challenge string, vk *VerifyingKey, publicInputs []fr.Element) error { 313 314 // permutation 315 if err := fs.Bind(challenge, vk.S[0].Marshal()); err != nil { 316 return err 317 } 318 if err := fs.Bind(challenge, vk.S[1].Marshal()); err != nil { 319 return err 320 } 321 if err := fs.Bind(challenge, vk.S[2].Marshal()); err != nil { 322 return err 323 } 324 325 // coefficients 326 if err := fs.Bind(challenge, vk.Ql.Marshal()); err != nil { 327 return err 328 } 329 if err := fs.Bind(challenge, vk.Qr.Marshal()); err != nil { 330 return err 331 } 332 if err := fs.Bind(challenge, vk.Qm.Marshal()); err != nil { 333 return err 334 } 335 if err := fs.Bind(challenge, vk.Qo.Marshal()); err != nil { 336 return err 337 } 338 if err := fs.Bind(challenge, vk.Qk.Marshal()); err != nil { 339 return err 340 } 341 for i := range vk.Qcp { 342 if err := fs.Bind(challenge, vk.Qcp[i].Marshal()); err != nil { 343 return err 344 } 345 } 346 347 // public inputs 348 for i := 0; i < len(publicInputs); i++ { 349 if err := fs.Bind(challenge, publicInputs[i].Marshal()); err != nil { 350 return err 351 } 352 } 353 354 return nil 355 356 } 357 358 func deriveRandomness(fs *fiatshamir.Transcript, challenge string, points ...*curve.G1Affine) (fr.Element, error) { 359 360 var buf [curve.SizeOfG1AffineUncompressed]byte 361 var r fr.Element 362 363 for _, p := range points { 364 buf = p.RawBytes() 365 if err := fs.Bind(challenge, buf[:]); err != nil { 366 return r, err 367 } 368 } 369 370 b, err := fs.ComputeChallenge(challenge) 371 if err != nil { 372 return r, err 373 } 374 r.SetBytes(b) 375 return r, nil 376 } 377 378 379 {{if eq .Curve "BN254"}} 380 // ExportSolidity exports the verifying key to a solidity smart contract. 381 // 382 // See https://github.com/ConsenSys/gnark-tests for example usage. 383 // 384 // Code has not been audited and is provided as-is, we make no guarantees or warranties to its safety and reliability. 385 func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error { 386 funcMap := template.FuncMap{ 387 "hex": func(i int) string { 388 return fmt.Sprintf("0x%x", i) 389 }, 390 "mul": func(a, b int) int { 391 return a * b 392 }, 393 "inc": func(i int) int { 394 return i + 1 395 }, 396 "frstr": func(x fr.Element) string { 397 // we use big.Int to always get a positive string. 398 // not the most efficient hack, but it works better for .sol generation. 399 bv := new(big.Int) 400 x.BigInt(bv) 401 return bv.String() 402 }, 403 "fpstr": func(x fp.Element) string { 404 bv := new(big.Int) 405 x.BigInt(bv) 406 return bv.String() 407 }, 408 "add": func(i, j int) int { 409 return i + j 410 }, 411 } 412 413 t, err := template.New("t").Funcs(funcMap).Parse(tmplSolidityVerifier) 414 if err != nil { 415 return err 416 } 417 418 cfg, err := solidity.NewExportConfig(exportOpts...) 419 if err != nil { 420 return err 421 } 422 if cfg.HashToFieldFn != nil { 423 return fmt.Errorf("setting hash to field function is not supported for PLONK Solidity export. Hash function is hardcoded to RFC9380") 424 } 425 426 return t.Execute(w, struct { 427 Cfg solidity.ExportConfig 428 Vk VerifyingKey 429 }{ 430 Cfg: cfg, 431 Vk: *vk, 432 }) 433 } 434 435 {{else}} 436 // ExportSolidity not implemented for {{.Curve}} 437 func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error { 438 return errors.New("not implemented") 439 } 440 {{end}}