github.com/consensys/gnark-crypto@v0.14.0/internal/generator/iop/template/ratios.go.tmpl (about) 1 import ( 2 "errors" 3 "math/bits" 4 "math/big" 5 "runtime" 6 "sync" 7 8 "github.com/consensys/gnark-crypto/internal/parallel" 9 10 "github.com/consensys/gnark-crypto/ecc/{{ .Name }}/fr" 11 "github.com/consensys/gnark-crypto/ecc/{{ .Name }}/fr/fft" 12 ) 13 14 // errors related to the computation of the quotient and the ratios. 15 var ( 16 ErrMustBeRegular = errors.New("the layout must be Regular") 17 ErrMustBeCanonical = errors.New("the basis must be Canonical") 18 ErrMustBeLagrangeCoset = errors.New("the basis must be LagrangeCoset") 19 ErrInconsistentFormat = errors.New("the format of the polynomials must be the same") 20 ErrInconsistentSize = errors.New("the sizes of the polynomial must be the same as the size of the domain") 21 ErrNumberPolynomials = errors.New("the number of polynomials in the denominator and the numerator must be the same") 22 ErrSizeNotPowerOfTwo = errors.New("the size of the polynomials must be a power of two") 23 ErrInconsistentSizeDomain = errors.New("the size of the domain must be consistent with the size of the polynomials") 24 ErrIncorrectNumberOfVariables = errors.New("the number of variables is incorrect") 25 ) 26 27 // Build an 'accumulating ratio' polynomial. 28 // * numerator list of polynomials that will form the numerator of the ratio 29 // * denominator list of polynomials that will form the denominator of the ratio 30 // The polynomials in the denominator and the numerator are expected to be of 31 // the same size and the size must be a power of 2. The polynomials are given as 32 // pointers in case the caller wants to FFTInv the polynomials during the process. 33 // * beta variable at which the numerator and denominators are evaluated 34 // * expectedForm expected form of the resulting polynomial 35 // * Return: say beta=β, numerator = [P₁,...,P_m], denominator = [Q₁,..,Q_m]. The function 36 // returns a polynomial whose evaluation on the j-th root of unity is 37 // (Π_{k<j}Π_{i<m}(β-Pᵢ(ωᵏ)))/(β-Qᵢ(ωᵏ)) 38 func BuildRatioShuffledVectors(numerator, denominator []*Polynomial, beta fr.Element, expectedForm Form, domain *fft.Domain) (*Polynomial, error) { 39 40 // check that len(numerator)=len(denominator) 41 if len(numerator) != len(denominator) { 42 return nil, ErrNumberPolynomials 43 } 44 nbPolynomials := len(numerator) 45 46 // check that the sizes are consistent 47 err := checkSize(numerator, denominator) 48 if err != nil { 49 return nil, err 50 } 51 52 // create the domain + some checks on the sizes of the polynomials 53 n := numerator[0].coefficients.Len() 54 domain, err = buildDomain(n, domain) 55 if err != nil { 56 return nil, err 57 } 58 59 // put every polynomials in Lagrange form. Also make sure 60 // that we don't modify the slices numerator and denominator, but 61 // only their entries. If the polynomials are unlocked, the 62 // entries of the slices numerator and denominator will be 63 // modified. 64 for i := 0; i < nbPolynomials; i++ { 65 numerator[i].ToLagrange(domain) 66 denominator[i].ToLagrange(domain) 67 } 68 69 // build the ratio (careful with the indices of 70 // the polynomials which are bit reversed) 71 coeffs := make([]fr.Element, n) 72 t := make([]fr.Element, n) 73 coeffs[0].SetOne() 74 t[0].SetOne() 75 var a, b, c, d fr.Element 76 77 nn := uint64(64 - bits.TrailingZeros(uint(n))) 78 for i := 0; i < n-1; i++ { 79 80 b.SetOne() 81 d.SetOne() 82 83 iRev := bits.Reverse64(uint64(i)) >> nn 84 85 for j := 0; j < nbPolynomials; j++ { 86 87 if numerator[j].Layout == BitReverse { 88 a.Sub(&beta, &numerator[j].Coefficients()[iRev]) 89 } else { 90 a.Sub(&beta, &numerator[j].Coefficients()[i]) 91 } 92 b.Mul(&b, &a) 93 94 if denominator[j].Layout == BitReverse { 95 c.Sub(&beta, &denominator[j].Coefficients()[iRev]) 96 } else { 97 c.Sub(&beta, &denominator[j].Coefficients()[i]) 98 } 99 d.Mul(&d, &c) 100 } 101 // b = Πₖ (β-Pₖ(ωⁱ⁻¹)) 102 // d = Πₖ (β-Qₖ(ωⁱ⁻¹)) 103 104 coeffs[i+1].Mul(&coeffs[i], &b) 105 t[i+1].Mul(&t[i], &d) 106 107 } 108 109 t = fr.BatchInvert(t) 110 for i := 1; i < n; i++ { 111 coeffs[i].Mul(&coeffs[i], &t[i]) 112 } 113 114 res := NewPolynomial(&coeffs, expectedForm) 115 116 // at this stage the result is in Lagrange form, Regular layout 117 putInExpectedFormFromLagrangeRegular(res, domain, expectedForm) 118 119 return res, nil 120 } 121 122 // BuildRatioCopyConstraint builds the accumulating ratio polynomial to prove that 123 // [P₁ ∥ .. ∥ P_{n—1}] is invariant by the permutation \sigma. 124 // Namely it returns the polynomial Z whose evaluation on the j-th root of unity is 125 // Z(ω^j) = Π_{i<j}(Π_{k<n}(P_k(ω^i)+β*u^k+γ))/(P_k(ω^i)+σ(kn+i)+γ))) 126 // * entries list of polynomials whose evaluation are invariant under \sigma 127 // * beta, gamma challenges 128 // * expectedForm expected form of the resulting polynomial 129 func BuildRatioCopyConstraint( 130 entries []*Polynomial, 131 permutation []int64, 132 beta, gamma fr.Element, 133 expectedForm Form, 134 domain *fft.Domain) (*Polynomial, error) { 135 136 nbPolynomials := len(entries) 137 138 // check that the sizes are consistent 139 err := checkSize(entries) 140 if err != nil { 141 return nil, err 142 } 143 144 // create the domain + some checks on the sizes of the polynomials 145 n := entries[0].coefficients.Len() 146 domain, err = buildDomain(n, domain) 147 if err != nil { 148 return nil, err 149 } 150 151 // put every polynomials in Lagrange form. Also make sure 152 // that we don't modify the slice entries 153 for i := 0; i < nbPolynomials; i++ { 154 entries[i].ToLagrange(domain) 155 } 156 157 // get the support for the permutation 158 evaluationIDSmallDomain := getSupportIdentityPermutation(nbPolynomials, domain) 159 160 // build the ratio (careful with the indices of 161 // the polynomials which are bit reversed) 162 coeffs := make([]fr.Element, n) 163 t := make([]fr.Element, n) 164 coeffs[0].SetOne() 165 t[0].SetOne() 166 167 parallel.Execute(n-1, func(start, end int) { 168 var a, b, c, d fr.Element 169 nn := uint64(64 - bits.TrailingZeros(uint(n))) 170 for i := start; i < end; i++ { 171 b.SetOne() 172 d.SetOne() 173 174 iRev := int(bits.Reverse64(uint64(i)) >> nn) 175 176 for j, p := range entries { 177 idx := i 178 if p.Layout == BitReverse { 179 idx = iRev 180 } 181 182 a.Mul(&beta, &evaluationIDSmallDomain[i+j*n]). 183 Add(&a, &gamma). 184 Add(&a, &p.Coefficients()[idx]) 185 186 b.Mul(&b, &a) 187 188 c.Mul(&beta, &evaluationIDSmallDomain[permutation[i+j*n]]). 189 Add(&c, &gamma). 190 Add(&c, &p.Coefficients()[idx]) 191 d.Mul(&d, &c) 192 } 193 194 // b = Πⱼ(Pⱼ(ωⁱ)+β*ωⁱνʲ+γ) 195 // d = Πⱼ(Qⱼ(ωⁱ)+β*σ(j*n+i)+γ) 196 coeffs[i+1].Set(&b) 197 t[i+1].Set(&d) 198 } 199 }) 200 201 chCoeffs := make(chan struct{}, 1) 202 go func() { 203 for i := 2; i < n; i++ { 204 // ignoring coeffs[0] 205 coeffs[i].Mul(&coeffs[i], &coeffs[i-1]) 206 } 207 close(chCoeffs) 208 }() 209 210 for i := 2; i < n; i++ { 211 // ignoring t[0] 212 t[i].Mul(&t[i], &t[i-1]) 213 } 214 <-chCoeffs 215 216 // rough ratio inverse to mul; see if it makes sense to parallelize the batch inverse. 217 const ratioInvMul = 1000 / 17 218 nbTasks := runtime.NumCPU() 219 if ratio := n / ratioInvMul; ratio < nbTasks { 220 nbTasks = ratio 221 } 222 223 parallel.Execute(n-1, func(start, end int) { 224 // ignoring t[0] and coeff[0] 225 start++ 226 end++ 227 tInv := fr.BatchInvert(t[start:end]) 228 for i := start; i < end; i++ { 229 coeffs[i].Mul(&coeffs[i], &tInv[i-start]) 230 } 231 }, nbTasks) 232 233 res := NewPolynomial(&coeffs, expectedForm) 234 // at this stage the result is in Lagrange form, Regular layout 235 putInExpectedFormFromLagrangeRegular(res, domain, expectedForm) 236 237 return res, nil 238 239 } 240 241 func putInExpectedFormFromLagrangeRegular(p *Polynomial, domain *fft.Domain, expectedForm Form) { 242 p.Basis = expectedForm.Basis 243 p.Layout = expectedForm.Layout 244 245 if expectedForm.Basis == Canonical { 246 domain.FFTInverse(p.Coefficients(), fft.DIF) 247 if expectedForm.Layout == Regular { 248 fft.BitReverse(p.Coefficients()) 249 } 250 return 251 } 252 253 if expectedForm.Basis == LagrangeCoset { 254 domain.FFTInverse(p.Coefficients(), fft.DIF) 255 domain.FFT(p.Coefficients(), fft.DIT, fft.OnCoset()) 256 if expectedForm.Layout == BitReverse { 257 fft.BitReverse(p.Coefficients()) 258 } 259 return 260 } 261 262 if expectedForm.Layout == BitReverse { 263 fft.BitReverse(p.Coefficients()) 264 } 265 266 } 267 268 // check that the polynomials are of the same size. 269 // It assumes that pols contains slices of the same size. 270 func checkSize(pols ...[]*Polynomial) error { 271 272 // check sizes between one another 273 m := len(pols) 274 n := pols[0][0].coefficients.Len() 275 for i := 0; i < m; i++ { 276 for j := 0; j < len(pols); j++ { 277 if pols[i][j].coefficients.Len() != n { 278 return ErrInconsistentSize 279 } 280 } 281 } 282 283 return nil 284 } 285 286 // buildDomain builds the fft domain necessary to do FFTs. 287 // n is the cardinality of the domain, it must be a power of 2. 288 func buildDomain(n int, domain *fft.Domain) (*fft.Domain, error) { 289 290 // check if the sizes are a power of 2 291 if n&(n-1) != 0 { 292 return nil, ErrSizeNotPowerOfTwo 293 } 294 295 // if the domain doesn't exist we create it. 296 if domain == nil { 297 domain = fft.NewDomain(uint64(n)) 298 } 299 300 // in case domain was not nil, it must match the size of the polynomials. 301 if domain.Cardinality != uint64(n) { 302 return nil, ErrInconsistentSizeDomain 303 } 304 305 return domain, nil 306 } 307 308 // getSupportIdentityPermutation returns the support on which the permutation acts. 309 // Concretely it's X evaluated on 310 // [1,ω,..,ωˢ⁻¹,g,g*ω,..,g*ωˢ⁻¹,..,gⁿ⁻¹,gⁿ⁻¹*ω,..,gⁿ⁻¹*ωˢ⁻¹] 311 // nbCopies is the number of cosets of the roots of unity that are needed, including the set of 312 // roots of unity itself. 313 func getSupportIdentityPermutation(nbCopies int, domain *fft.Domain) []fr.Element { 314 if nbCopies <= 0 { 315 panic("getSupportIdentityPermutation: nbCopies must be positive") 316 } 317 318 res := make([]fr.Element, uint64(nbCopies)*domain.Cardinality) 319 sizePoly := int(domain.Cardinality) 320 321 // TODO @gbotrel check if we can reuse the pre-computed twiddles from the domain. 322 res[0].SetOne() 323 if len(res) > 1 { 324 res[1].Set(&domain.Generator) 325 for i := 2; i < len(res); i++ { 326 res[i].Mul(&res[i-1], &domain.Generator) 327 } 328 } 329 330 if nbCopies <= 1 { 331 return res 332 } 333 var wg sync.WaitGroup 334 wg.Add(nbCopies - 1) 335 for i := 1; i < nbCopies; i++ { 336 i := i 337 338 var coset fr.Element 339 coset.Exp(domain.FrMultiplicativeGen, big.NewInt(int64(i))) 340 341 go func() { 342 parallel.Execute(sizePoly, func(start, end int) { 343 for j := start; j < end; j++ { 344 res[i*sizePoly+j].Mul(&res[j], &coset) 345 } 346 }, (runtime.NumCPU()/(nbCopies-1))+1) 347 wg.Done() 348 }() 349 } 350 351 wg.Wait() 352 353 return res 354 }