github.com/consensys/gnark-crypto@v0.14.0/internal/generator/permutation/template/permutation.go.tmpl (about) 1 import ( 2 "crypto/sha256" 3 "errors" 4 "math/big" 5 "math/bits" 6 7 "github.com/consensys/gnark-crypto/ecc/{{ .Name }}" 8 "github.com/consensys/gnark-crypto/ecc/{{ .Name }}/fr" 9 "github.com/consensys/gnark-crypto/ecc/{{ .Name }}/fr/fft" 10 "github.com/consensys/gnark-crypto/ecc/{{ .Name }}/kzg" 11 fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir" 12 ) 13 14 var ( 15 ErrIncompatibleSize = errors.New("t1 and t2 should be of the same size") 16 ErrSize = errors.New("t1 and t2 should be of size a power of 2") 17 ErrPermutationProof = errors.New("permutation proof verification failed") 18 ErrGenerator = errors.New("wrong generator") 19 ) 20 21 // Proof proof that the commitments of t1 and t2 come from 22 // the same vector but permuted. 23 type Proof struct { 24 25 // size of the polynomials 26 size int 27 28 // generator of the fft domain, used for shifting the evaluation point 29 g fr.Element 30 31 // commitments of t1 & t2, the permuted vectors, and z, the accumulation 32 // polynomial 33 t1, t2, z kzg.Digest 34 35 // commitment to the quotient polynomial 36 q kzg.Digest 37 38 // opening proofs of t1, t2, z, q (in that order) 39 batchedProof kzg.BatchOpeningProof 40 41 // shifted opening proof of z 42 shiftedProof kzg.OpeningProof 43 } 44 45 // evaluateAccumulationPolynomialBitReversed returns the accumulation polynomial in Lagrange basis. 46 func evaluateAccumulationPolynomialBitReversed(lt1, lt2 []fr.Element, epsilon fr.Element) []fr.Element { 47 48 s := len(lt1) 49 z := make([]fr.Element, s) 50 d := make([]fr.Element, s) 51 z[0].SetOne() 52 d[0].SetOne() 53 nn := uint64(64 - bits.TrailingZeros64(uint64(s))) 54 var t fr.Element 55 for i := 0; i < s-1; i++ { 56 _i := int(bits.Reverse64(uint64(i)) >> nn) 57 _ii := int(bits.Reverse64(uint64((i+1)%s)) >> nn) 58 z[_ii].Mul(&z[_i], t.Sub(&epsilon, <1[i])) 59 d[i+1].Mul(&d[i], t.Sub(&epsilon, <2[i])) 60 } 61 d = fr.BatchInvert(d) 62 for i := 0; i < s-1; i++ { 63 _ii := int(bits.Reverse64(uint64((i+1)%s)) >> nn) 64 z[_ii].Mul(&z[_ii], &d[i+1]) 65 } 66 67 return z 68 } 69 70 // evaluateFirstPartNumReverse computes lt2*z(gx) - lt1*z 71 func evaluateFirstPartNumReverse(lt1, lt2, lz []fr.Element, epsilon fr.Element) []fr.Element { 72 73 s := len(lt1) 74 res := make([]fr.Element, s) 75 var a, b fr.Element 76 nn := uint64(64 - bits.TrailingZeros64(uint64(s))) 77 for i := 0; i < s; i++ { 78 _i := int(bits.Reverse64(uint64(i)) >> nn) 79 _ii := int(bits.Reverse64(uint64((i+1)%s)) >> nn) 80 a.Sub(&epsilon, <2[_i]) 81 a.Mul(&lz[_ii], &a) 82 b.Sub(&epsilon, <1[_i]) 83 b.Mul(&lz[_i], &b) 84 res[_i].Sub(&a, &b) 85 } 86 return res 87 } 88 89 // evaluateSecondPartNumReverse computes L0 * (z-1) 90 func evaluateSecondPartNumReverse(lz []fr.Element, d *fft.Domain) []fr.Element { 91 92 var tn, o, g fr.Element 93 o.SetOne() 94 tn.Exp(d.FrMultiplicativeGen, big.NewInt(int64(d.Cardinality))). 95 Sub(&tn, &o) 96 s := len(lz) 97 u := make([]fr.Element, s) 98 g.Set(&d.FrMultiplicativeGen) 99 for i := 0; i < s; i++ { 100 u[i].Sub(&g, &o) 101 g.Mul(&g, &d.Generator) 102 } 103 u = fr.BatchInvert(u) 104 res := make([]fr.Element, s) 105 nn := uint64(64 - bits.TrailingZeros64(uint64(s))) 106 for i := 0; i < s; i++ { 107 _i := int(bits.Reverse64(uint64(i)) >> nn) 108 res[_i].Sub(&lz[_i], &o). 109 Mul(&res[_i], &u[i]). 110 Mul(&res[_i], &tn) 111 } 112 return res 113 } 114 115 // Prove generates a proof that t1 and t2 are the same but permuted. 116 // The size of t1 and t2 should be the same and a power of 2. 117 func Prove(pk kzg.ProvingKey, t1, t2 []fr.Element) (Proof, error) { 118 119 // res 120 var proof Proof 121 var err error 122 123 // size checking 124 if len(t1) != len(t2) { 125 return proof, ErrIncompatibleSize 126 } 127 128 // create the domains 129 d := fft.NewDomain(uint64(len(t1))) 130 if d.Cardinality != uint64(len(t1)) { 131 return proof, ErrSize 132 } 133 s := int(d.Cardinality) 134 proof.size = s 135 proof.g.Set(&d.Generator) 136 137 // hash function for Fiat Shamir 138 hFunc := sha256.New() 139 140 // transcript to derive the challenge 141 fs := fiatshamir.NewTranscript(hFunc, "epsilon", "omega", "eta") 142 143 // commit t1, t2 144 ct1 := make([]fr.Element, s) 145 ct2 := make([]fr.Element, s) 146 copy(ct1, t1) 147 copy(ct2, t2) 148 d.FFTInverse(ct1, fft.DIF) 149 d.FFTInverse(ct2, fft.DIF) 150 fft.BitReverse(ct1) 151 fft.BitReverse(ct2) 152 proof.t1, err = kzg.Commit(ct1, pk) 153 if err != nil { 154 return proof, err 155 } 156 proof.t2, err = kzg.Commit(ct2, pk) 157 if err != nil { 158 return proof, err 159 } 160 161 // derive challenge for z 162 epsilon, err := deriveRandomness(fs, "epsilon", &proof.t1, &proof.t2) 163 if err != nil { 164 return proof, err 165 } 166 167 // compute Z and commit it 168 cz := evaluateAccumulationPolynomialBitReversed(t1, t2, epsilon) 169 d.FFTInverse(cz, fft.DIT) 170 proof.z, err = kzg.Commit(cz, pk) 171 if err != nil { 172 return proof, err 173 } 174 lz := make([]fr.Element, s) 175 copy(lz, cz) 176 d.FFT(lz, fft.DIF, fft.OnCoset()) 177 178 // compute the first part of the numerator 179 lt1 := make([]fr.Element, s) 180 lt2 := make([]fr.Element, s) 181 copy(lt1, ct1) 182 copy(lt2, ct2) 183 d.FFT(lt1, fft.DIF, fft.OnCoset()) 184 d.FFT(lt2, fft.DIF, fft.OnCoset()) 185 lsNumFirstPart := evaluateFirstPartNumReverse(lt1, lt2, lz, epsilon) 186 187 // compute second part of the numerator 188 lsNum := evaluateSecondPartNumReverse(lz, d) 189 190 // derive challenge used for the folding 191 omega, err := deriveRandomness(fs, "omega", &proof.z) 192 if err != nil { 193 return proof, err 194 } 195 196 // fold the numerator and divide it by x^n-1 197 var t, one fr.Element 198 one.SetOne() 199 t.Exp(d.FrMultiplicativeGen, big.NewInt(int64(d.Cardinality))).Sub(&t, &one).Inverse(&t) 200 for i := 0; i < s; i++ { 201 lsNum[i].Mul(&omega, &lsNum[i]). 202 Add(&lsNum[i], &lsNumFirstPart[i]). 203 Mul(&lsNum[i], &t) 204 } 205 206 // get the quotient and commit it 207 d.FFTInverse(lsNum, fft.DIT, fft.OnCoset()) 208 proof.q, err = kzg.Commit(lsNum, pk) 209 if err != nil { 210 return proof, err 211 } 212 213 // derive the evaluation challenge 214 eta, err := deriveRandomness(fs, "eta", &proof.q) 215 if err != nil { 216 return proof, err 217 } 218 219 // compute the opening proofs 220 proof.batchedProof, err = kzg.BatchOpenSinglePoint( 221 [][]fr.Element{ 222 ct1, 223 ct2, 224 cz, 225 lsNum, 226 }, 227 []kzg.Digest{ 228 proof.t1, 229 proof.t2, 230 proof.z, 231 proof.q, 232 }, 233 eta, 234 hFunc, 235 pk, 236 ) 237 if err != nil { 238 return proof, err 239 } 240 241 var shiftedEta fr.Element 242 shiftedEta.Mul(&eta, &d.Generator) 243 proof.shiftedProof, err = kzg.Open( 244 cz, 245 shiftedEta, 246 pk, 247 ) 248 if err != nil { 249 return proof, err 250 } 251 252 // done 253 return proof, nil 254 255 } 256 257 // Verify verifies a permutation proof. 258 func Verify(vk kzg.VerifyingKey, proof Proof) error { 259 260 // hash function that is used for Fiat Shamir 261 hFunc := sha256.New() 262 263 // transcript to derive the challenge 264 fs := fiatshamir.NewTranscript(hFunc, "epsilon", "omega", "eta") 265 266 // derive the challenges 267 epsilon, err := deriveRandomness(fs, "epsilon", &proof.t1, &proof.t2) 268 if err != nil { 269 return err 270 } 271 272 omega, err := deriveRandomness(fs, "omega", &proof.z) 273 if err != nil { 274 return err 275 } 276 277 eta, err := deriveRandomness(fs, "eta", &proof.q) 278 if err != nil { 279 return err 280 } 281 282 // check the relation 283 bs := big.NewInt(int64(proof.size)) 284 var l0, a, b, one, rhs, lhs fr.Element 285 one.SetOne() 286 rhs.Exp(eta, bs). 287 Sub(&rhs, &one) 288 a.Sub(&eta, &one) 289 l0.Div(&rhs, &a) 290 rhs.Mul(&rhs, &proof.batchedProof.ClaimedValues[3]) 291 a.Sub(&epsilon, &proof.batchedProof.ClaimedValues[1]). 292 Mul(&a, &proof.shiftedProof.ClaimedValue) 293 b.Sub(&epsilon, &proof.batchedProof.ClaimedValues[0]). 294 Mul(&b, &proof.batchedProof.ClaimedValues[2]) 295 lhs.Sub(&a, &b) 296 a.Sub(&proof.batchedProof.ClaimedValues[2], &one). 297 Mul(&a, &l0). 298 Mul(&a, &omega) 299 lhs.Add(&a, &lhs) 300 if !lhs.Equal(&rhs) { 301 return ErrPermutationProof 302 } 303 304 // check the opening proofs 305 err = kzg.BatchVerifySinglePoint( 306 []kzg.Digest{ 307 proof.t1, 308 proof.t2, 309 proof.z, 310 proof.q, 311 }, 312 &proof.batchedProof, 313 eta, 314 hFunc, 315 vk, 316 ) 317 if err != nil { 318 return err 319 } 320 321 var shiftedEta fr.Element 322 shiftedEta.Mul(&eta, &proof.g) 323 err = kzg.Verify(&proof.z, &proof.shiftedProof, shiftedEta, vk) 324 if err != nil { 325 return err 326 } 327 328 // check the generator is correct 329 var checkOrder fr.Element 330 checkOrder.Exp(proof.g, big.NewInt(int64(proof.size/2))) 331 if checkOrder.Equal(&one) { 332 return ErrGenerator 333 } 334 checkOrder.Square(&checkOrder) 335 if !checkOrder.Equal(&one) { 336 return ErrGenerator 337 } 338 339 return nil 340 } 341 342 // TODO put that in fiat-shamir package 343 func deriveRandomness(fs *fiatshamir.Transcript, challenge string, points ...*{{ .CurvePackage }}.G1Affine) (fr.Element, error) { 344 345 var buf [{{ .CurvePackage }}.SizeOfG1AffineUncompressed]byte 346 var r fr.Element 347 348 for _, p := range points { 349 buf = p.RawBytes() 350 if err := fs.Bind(challenge, buf[:]); err != nil { 351 return r, err 352 } 353 } 354 355 b, err := fs.ComputeChallenge(challenge) 356 if err != nil { 357 return r, err 358 } 359 r.SetBytes(b) 360 return r, nil 361 }