github.com/consensys/gnark@v0.11.0/internal/generator/backend/template/zkpschemes/groth16/groth16.verify.go.tmpl (about) 1 import ( 2 "errors" 3 "fmt" 4 "io" 5 {{- if eq .Curve "BN254"}} 6 "math/big" 7 "text/template" 8 "bytes" 9 "crypto/sha256" 10 "golang.org/x/crypto/sha3" 11 {{- template "import_fp" . }} 12 {{- end}} 13 "time" 14 15 "github.com/consensys/gnark-crypto/ecc" 16 {{- template "import_curve" . }} 17 {{- template "import_fr" . }} 18 {{- template "import_pedersen" .}} 19 {{- template "import_hash_to_field" . }} 20 "github.com/consensys/gnark-crypto/utils" 21 "github.com/consensys/gnark/backend" 22 "github.com/consensys/gnark/backend/solidity" 23 "github.com/consensys/gnark/constraint" 24 "github.com/consensys/gnark/logger" 25 ) 26 27 var ( 28 errPairingCheckFailed = errors.New("pairing doesn't match") 29 errCorrectSubgroupCheckFailed = errors.New("points in the proof are not in the correct subgroup") 30 ) 31 32 // Verify verifies a proof with given VerifyingKey and publicWitness 33 func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...backend.VerifierOption) error { 34 opt, err := backend.NewVerifierConfig(opts...) 35 if err != nil { 36 return fmt.Errorf("new verifier config: %w", err) 37 } 38 if opt.HashToFieldFn == nil { 39 opt.HashToFieldFn = hash_to_field.New([]byte(constraint.CommitmentDst)) 40 } 41 42 nbPublicVars := len(vk.G1.K) - len(vk.PublicAndCommitmentCommitted) 43 44 if len(publicWitness) != nbPublicVars-1 { 45 return fmt.Errorf("invalid witness size, got %d, expected %d (public - ONE_WIRE)", len(publicWitness), len(vk.G1.K) - 1) 46 } 47 log := logger.Logger().With().Str("curve", vk.CurveID().String()).Str("backend", "groth16").Logger() 48 start := time.Now() 49 50 // check that the points in the proof are in the correct subgroup 51 if !proof.isValid() { 52 return errCorrectSubgroupCheckFailed 53 } 54 55 var doubleML curve.GT 56 chDone := make(chan error, 1) 57 58 // compute (eKrsδ, eArBs) 59 go func() { 60 var errML error 61 doubleML, errML = curve.MillerLoop([]curve.G1Affine{proof.Krs, proof.Ar}, []curve.G2Affine{vk.G2.deltaNeg, proof.Bs}) 62 chDone <- errML 63 close(chDone) 64 }() 65 66 maxNbPublicCommitted := 0 67 for _, s := range vk.PublicAndCommitmentCommitted { // iterate over commitments 68 maxNbPublicCommitted = utils.Max(maxNbPublicCommitted, len(s)) 69 } 70 commitmentsSerialized := make([]byte, len(vk.PublicAndCommitmentCommitted)*fr.Bytes) 71 commitmentPrehashSerialized := make([]byte, curve.SizeOfG1AffineUncompressed+maxNbPublicCommitted*fr.Bytes) 72 for i := range vk.PublicAndCommitmentCommitted { // solveCommitmentWire 73 copy(commitmentPrehashSerialized, proof.Commitments[i].Marshal()) 74 offset := curve.SizeOfG1AffineUncompressed 75 for j := range vk.PublicAndCommitmentCommitted[i] { 76 copy(commitmentPrehashSerialized[offset:], publicWitness[vk.PublicAndCommitmentCommitted[i][j]-1].Marshal()) 77 offset += fr.Bytes 78 } 79 opt.HashToFieldFn.Write(commitmentPrehashSerialized[:offset]) 80 hashBts := opt.HashToFieldFn.Sum(nil) 81 opt.HashToFieldFn.Reset() 82 nbBuf := fr.Bytes 83 if opt.HashToFieldFn.Size() < fr.Bytes { 84 nbBuf = opt.HashToFieldFn.Size() 85 } 86 var res fr.Element 87 res.SetBytes(hashBts[:nbBuf]) 88 publicWitness = append(publicWitness, res) 89 copy(commitmentsSerialized[i*fr.Bytes:], res.Marshal()) 90 } 91 if len(vk.CommitmentKeys) > 0 { 92 challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1) 93 if err != nil { 94 return err 95 } 96 if err = pedersen.BatchVerifyMultiVk(vk.CommitmentKeys, proof.Commitments, []curve.G1Affine{proof.CommitmentPok}, challenge[0]); err != nil { 97 return err 98 } 99 } 100 101 // compute e(Σx.[Kvk(t)]1, -[γ]2) 102 var kSum curve.G1Jac 103 if _, err := kSum.MultiExp(vk.G1.K[1:], publicWitness, ecc.MultiExpConfig{}); err != nil { 104 return err 105 } 106 kSum.AddMixed(&vk.G1.K[0]) 107 108 for i := range proof.Commitments { 109 kSum.AddMixed(&proof.Commitments[i]) 110 } 111 112 var kSumAff curve.G1Affine 113 kSumAff.FromJacobian(&kSum) 114 115 right, err := curve.MillerLoop([]curve.G1Affine{kSumAff}, []curve.G2Affine{vk.G2.gammaNeg}) 116 if err != nil { 117 return err 118 } 119 120 // wait for (eKrsδ, eArBs) 121 if err := <-chDone; err != nil { 122 return err 123 } 124 125 right = curve.FinalExponentiation(&right, &doubleML) 126 if !vk.e.Equal(&right) { 127 return errPairingCheckFailed 128 } 129 130 log.Debug().Dur("took", time.Since(start)).Msg("verifier done") 131 return nil 132 } 133 134 135 {{if eq .Curve "BN254"}} 136 // ExportSolidity writes a solidity Verifier contract on provided writer. 137 // This is an experimental feature and gnark solidity generator as not been thoroughly tested. 138 // 139 // See https://github.com/ConsenSys/gnark-tests for example usage. 140 func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error { 141 cfg, err := solidity.NewExportConfig(exportOpts...) 142 log := logger.Logger() 143 if err != nil { 144 return err 145 } 146 if cfg.HashToFieldFn == nil { 147 // set the target hash function to legacy keccak256 as it is the default for `solidity.WithTargetSolidityVerifier`` 148 cfg.HashToFieldFn = sha3.NewLegacyKeccak256() 149 log.Debug().Msg("hash to field function not set, using keccak256 as default") 150 } 151 // a bit hacky way to understand what hash function is provided. We already 152 // receive instance of hash function but it is difficult to compare it with 153 // sha256.New() or sha3.NewLegacyKeccak256() directly. 154 // 155 // So, we hash an empty input and compare the outputs. 156 cfg.HashToFieldFn.Reset() 157 hashBts := cfg.HashToFieldFn.Sum(nil) 158 var hashFnName string 159 if bytes.Equal(hashBts, sha256.New().Sum(nil)) { 160 hashFnName = "sha256" 161 } else if bytes.Equal(hashBts, sha3.NewLegacyKeccak256().Sum(nil)) { 162 hashFnName = "keccak256" 163 } else { 164 return fmt.Errorf("unsupported hash function used, only supported sha256 and legacy keccak256") 165 } 166 cfg.HashToFieldFn.Reset() 167 helpers := template.FuncMap{ 168 "sum": func(a, b int) int { 169 return a + b 170 }, 171 "sub": func(a, b int) int { 172 return a - b 173 }, 174 "mul": func(a, b int) int { 175 return a * b 176 }, 177 "intRange": func(max int) []int { 178 out := make([]int, max) 179 for i := 0; i < max; i++ { 180 out[i] = i 181 } 182 return out 183 }, 184 "fpstr": func(x fp.Element) string { 185 bv := new(big.Int) 186 x.BigInt(bv) 187 return bv.String() 188 }, 189 "hashFnName": func() string { 190 return hashFnName 191 }, 192 } 193 194 if len(vk.PublicAndCommitmentCommitted) > 1 { 195 log.Warn().Msg("exporting solidity verifier with more than one commitment is not supported") 196 } else if len(vk.PublicAndCommitmentCommitted) == 1 { 197 log.Warn().Msg("exporting solidity verifier only supports `sha256` as `HashToField`. The generated contract may not work for proofs generated with other hash functions.") 198 } 199 200 tmpl, err := template.New("").Funcs(helpers).Parse(solidityTemplate) 201 if err != nil { 202 return err 203 } 204 205 // negate Beta, Gamma and Delta, to avoid negating proof elements in the verifier 206 var betaNeg curve.G2Affine 207 betaNeg.Neg(&vk.G2.Beta) 208 beta := vk.G2.Beta 209 vk.G2.Beta = betaNeg 210 vk.G2.Gamma, vk.G2.gammaNeg = vk.G2.gammaNeg, vk.G2.Gamma 211 vk.G2.Delta, vk.G2.deltaNeg = vk.G2.deltaNeg, vk.G2.Delta 212 213 214 // execute template 215 err = tmpl.Execute(w, struct { 216 Cfg solidity.ExportConfig 217 Vk VerifyingKey 218 }{ 219 Cfg: cfg, 220 Vk: *vk, 221 }) 222 223 // restore Beta, Gamma and Delta 224 vk.G2.Beta = beta 225 vk.G2.Gamma, vk.G2.gammaNeg = vk.G2.gammaNeg, vk.G2.Gamma 226 vk.G2.Delta, vk.G2.deltaNeg = vk.G2.deltaNeg, vk.G2.Delta 227 228 return err 229 } 230 231 {{else}} 232 // ExportSolidity not implemented for {{.Curve}} 233 func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error { 234 return errors.New("not implemented") 235 } 236 {{end}}