github.com/consensys/gnark-crypto@v0.14.0/internal/generator/iop/template/polynomial.go.tmpl (about) 1 import ( 2 "math/big" 3 "math/bits" 4 "io" 5 "encoding/binary" 6 "runtime" 7 8 "github.com/consensys/gnark-crypto/ecc/{{ .Name }}/fr" 9 "github.com/consensys/gnark-crypto/ecc/{{ .Name }}/fr/fft" 10 ) 11 12 // Basis indicates the basis in which a polynomial is represented. 13 type Basis uint32 14 15 const ( 16 Canonical Basis = 1 << iota 17 Lagrange 18 LagrangeCoset 19 ) 20 21 // Layout indicates if a polynomial has a BitReverse or a Regular layout 22 type Layout uint32 23 24 const ( 25 Regular Layout = 8 << iota 26 BitReverse 27 ) 28 29 // Form describes the form of a polynomial. 30 // TODO should be a regular enum? 31 type Form struct { 32 Basis Basis 33 Layout Layout 34 } 35 36 // enum of the possible Form values for type-safe switches 37 // in this package 38 var ( 39 canonicalRegular = Form{Canonical, Regular} 40 canonicalBitReverse = Form{Canonical, BitReverse} 41 lagrangeRegular = Form{Lagrange, Regular} 42 lagrangeBitReverse = Form{Lagrange, BitReverse} 43 lagrangeCosetRegular = Form{LagrangeCoset, Regular} 44 lagrangeCosetBitReverse = Form{LagrangeCoset, BitReverse} 45 ) 46 47 // Polynomial wraps a polynomial so that it is 48 // interpreted as P'(X)=P(\omega^{s}X). 49 // Size is the real size of the polynomial (seen as a vector). 50 // For instance if len(P)=32 but P.Size=8, it means that P has been 51 // extended (e.g. it is evaluated on a larger set) but P is a polynomial 52 // of degree 7. 53 // blindedSize is the size of the polynomial when it is blinded. By 54 // default blindedSize=Size, until the polynomial is blinded. 55 type Polynomial struct { 56 *polynomial 57 shift int 58 size int 59 blindedSize int 60 } 61 62 // NewPolynomial returned a Polynomial from the provided coefficients in the given form. 63 // A Polynomial can be seen as a "shared pointer" on a list of coefficients. 64 // It is the responsibility of the user to call the Clone method if the coefficients 65 // shouldn't be mutated. 66 func NewPolynomial(coeffs *[]fr.Element, form Form) *Polynomial { 67 return &Polynomial{ 68 polynomial: newPolynomial(coeffs, form), 69 size: len(*coeffs), 70 blindedSize: len(*coeffs), 71 } 72 } 73 74 // Shift the wrapped polynomial; it doesn't modify the underlying data structure, 75 // but flag the Polynomial such that it will be interpreted as p(\omega^shift X) 76 func (p *Polynomial) Shift(shift int) *Polynomial { 77 p.shift = shift 78 return p 79 } 80 81 // BlindedSize returns the the size of the polynomial when it is blinded. By 82 // default blindedSize=Size, until the polynomial is blinded. 83 func (p *Polynomial) BlindedSize() int { 84 return p.blindedSize 85 } 86 87 // Size returns the real size of the polynomial (seen as a vector). 88 // For instance if len(P)=32 but P.Size=8, it means that P has been 89 // extended (e.g. it is evaluated on a larger set) but P is a polynomial 90 // of degree 7. 91 func (p *Polynomial) Size() int { 92 return p.size 93 } 94 95 // SetSize sets the size of the polynomial. 96 // size is the real size of the polynomial (seen as a vector). 97 // For instance if len(P)=32 but P.size=8, it means that P has been 98 // extended (e.g. it is evaluated on a larger set) but P is a polynomial 99 // of degree 7. 100 func (p *Polynomial) SetSize(size int) { 101 p.size = size 102 } 103 104 // SetBlindedSize sets the blinded size of the polynomial. 105 func (p *Polynomial) SetBlindedSize(size int) { 106 p.blindedSize = size 107 } 108 109 // Blind blinds a polynomial q by adding Q(X)*(X^{n}-1), 110 // where deg Q = blindingOrder and Q is random, and n is the 111 // size of q. Sets the result to p and returns it. 112 // 113 // blindingOrder is the degree of Q, where the blinding is Q(X)*(X^{n}-1) 114 // where n is the size of p. The size of p is modified since the underlying 115 // polynomial is of bigger degree now. The new size is p.Size+1+blindingOrder. 116 // 117 // /!\ The code panics if wq is not in canonical, regular layout 118 func (p *Polynomial) Blind(blindingOrder int) *Polynomial { 119 // check that p is in canonical basis 120 if p.Form != canonicalRegular { 121 panic("the input must be in canonical basis, regular layout") 122 } 123 124 // we add Q*(x^{n}-1) so the new size is deg(Q)+n+1 125 // where n is the size of wq. 126 newSize := p.size + blindingOrder + 1 127 128 // Resize p. The size of wq might has already been increased 129 // (e.g. when the polynomial is evaluated on a larger domain), 130 // if that's the case we don't resize the polynomial. 131 p.grow(newSize) 132 133 // blinding: we add Q(X)(X^{n}-1) to P, where deg(Q)=blindingOrder 134 var r fr.Element 135 136 for i := 0; i <= blindingOrder; i++ { 137 r.SetRandom() 138 (*p.coefficients)[i].Sub(&(*p.coefficients)[i], &r) 139 (*p.coefficients)[i+p.size].Add(&(*p.coefficients)[i+p.size], &r) 140 } 141 p.blindedSize = newSize 142 143 return p 144 } 145 146 // Evaluate evaluates p at x. 147 // The code panics if the function is not in canonical form. 148 func (p *Polynomial) Evaluate(x fr.Element) fr.Element { 149 150 if p.shift == 0 { 151 return p.polynomial.evaluate(x) 152 } 153 154 var g fr.Element 155 if p.shift <= 5 { 156 gen, err := fft.Generator(uint64(p.size)) 157 if err != nil { 158 panic(err) 159 } 160 g = smallExp(gen, p.shift) 161 x.Mul(&x, &g) 162 return p.polynomial.evaluate(x) 163 } 164 165 bs := big.NewInt(int64(p.shift)) 166 g = *g.Exp(g, bs) 167 x.Mul(&x, &g) 168 return p.polynomial.evaluate(x) 169 } 170 171 // Clone returns a deep copy of p. The underlying polynomial is cloned; 172 // see also ShallowClone to perform a ShallowClone on the underlying polynomial. 173 // If capacity is provided, the new coefficient slice capacity will be set accordingly. 174 func (p *Polynomial) Clone(capacity ...int) *Polynomial { 175 res := p.ShallowClone() 176 res.polynomial = p.polynomial.clone(capacity...) 177 return res 178 } 179 180 // ShallowClone returns a shallow copy of p. The underlying polynomial coefficient 181 // is NOT cloned and both objects will point to the same coefficient vector. 182 func (p *Polynomial) ShallowClone() *Polynomial { 183 res := *p 184 return &res 185 } 186 187 // GetCoeff returns the i-th entry of p, taking the layout in account. 188 func (p *Polynomial) GetCoeff(i int) fr.Element { 189 190 n := p.coefficients.Len() 191 rho := n / p.size 192 if p.polynomial.Form.Layout == Regular { 193 return (*p.coefficients)[(i+rho*p.shift)%n] 194 } else { 195 nn := uint64(64 - bits.TrailingZeros(uint(n))) 196 iRev := bits.Reverse64(uint64((i+rho*p.shift)%n)) >> nn 197 return (*p.coefficients)[iRev] 198 } 199 200 } 201 202 // polynomial represents a polynomial, the vector of coefficients 203 // along with the basis and the layout. 204 type polynomial struct { 205 coefficients *fr.Vector 206 Form 207 } 208 209 // Coefficients returns a slice on the underlying data structure. 210 func (p *polynomial) Coefficients() []fr.Element { 211 return (*p.coefficients) 212 } 213 214 // newPolynomial creates a new polynomial. The slice coeff NOT copied 215 // but directly assigned to the new polynomial. 216 func newPolynomial(coeffs *[]fr.Element, form Form) *polynomial { 217 return &polynomial{coefficients: (*fr.Vector)(coeffs), Form: form} 218 } 219 220 // clone returns a deep copy of the underlying data structure. 221 func (p *polynomial) clone(capacity ...int) *polynomial { 222 c := p.coefficients.Len() 223 if len(capacity) == 1 && capacity[0] > c { 224 c = capacity[0] 225 } 226 newCoeffs := make(fr.Vector, p.coefficients.Len(), c) 227 r := &polynomial{ 228 coefficients: &newCoeffs, 229 Form: p.Form, 230 } 231 copy((*r.coefficients), (*p.coefficients)) 232 return r 233 } 234 235 // evaluate evaluates p at x. 236 // The code panics if the function is not in canonical form. 237 func (p *polynomial) evaluate(x fr.Element) fr.Element { 238 239 var r fr.Element 240 if p.Basis != Canonical { 241 panic("p must be in canonical basis") 242 } 243 244 if p.Layout == Regular { 245 for i := p.coefficients.Len() - 1; i >= 0; i-- { 246 r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) 247 } 248 } else { 249 nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) 250 for i := p.coefficients.Len() - 1; i >= 0; i-- { 251 iRev := bits.Reverse64(uint64(i)) >> nn 252 r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) 253 } 254 } 255 256 return r 257 258 } 259 260 // ToRegular changes the layout of p to Regular. 261 // Leaves p unchanged if p's layout was already Regular. 262 func (p *Polynomial) ToRegular() *Polynomial { 263 if p.Layout == Regular { 264 return p 265 } 266 fft.BitReverse((*p.coefficients)) 267 p.Layout = Regular 268 return p 269 } 270 271 // ToBitReverse changes the layout of p to BitReverse. 272 // Leaves p unchanged if p's layout was already BitReverse. 273 func (p *Polynomial) ToBitReverse() *Polynomial { 274 if p.Layout == BitReverse { 275 return p 276 } 277 fft.BitReverse((*p.coefficients)) 278 p.Layout = BitReverse 279 return p 280 } 281 282 // ToLagrange converts p to Lagrange form. 283 // Leaves p unchanged if p was already in Lagrange form. 284 func (p *Polynomial) ToLagrange(d *fft.Domain, nbTasks ...int) *Polynomial { 285 id := p.Form 286 p.grow(int(d.Cardinality)) 287 288 n := runtime.NumCPU() 289 if len(nbTasks) > 0 { 290 n = nbTasks[0] 291 } 292 293 switch id { 294 case canonicalRegular: 295 p.Layout = BitReverse 296 d.FFT((*p.coefficients), fft.DIF, fft.WithNbTasks(n)) 297 case canonicalBitReverse: 298 p.Layout = Regular 299 d.FFT((*p.coefficients), fft.DIT, fft.WithNbTasks(n)) 300 case lagrangeRegular, lagrangeBitReverse: 301 return p 302 case lagrangeCosetRegular: 303 p.Layout = Regular 304 d.FFTInverse((*p.coefficients), fft.DIF, fft.OnCoset(), fft.WithNbTasks(n)) 305 d.FFT((*p.coefficients), fft.DIT) 306 case lagrangeCosetBitReverse: 307 p.Layout = BitReverse 308 d.FFTInverse((*p.coefficients), fft.DIT, fft.OnCoset(), fft.WithNbTasks(n)) 309 d.FFT((*p.coefficients), fft.DIF) 310 default: 311 panic("unknown ID") 312 } 313 p.Basis = Lagrange 314 return p 315 } 316 317 // ToCanonical converts p to canonical form. 318 // Leaves p unchanged if p was already in Canonical form. 319 func (p *Polynomial) ToCanonical(d *fft.Domain, nbTasks ...int) *Polynomial { 320 id := p.Form 321 p.grow(int(d.Cardinality)) 322 n := runtime.NumCPU() 323 if len(nbTasks) > 0 { 324 n = nbTasks[0] 325 } 326 switch id { 327 case canonicalRegular, canonicalBitReverse: 328 return p 329 case lagrangeRegular: 330 p.Layout = BitReverse 331 d.FFTInverse((*p.coefficients), fft.DIF, fft.WithNbTasks(n)) 332 case lagrangeBitReverse: 333 p.Layout = Regular 334 d.FFTInverse((*p.coefficients), fft.DIT, fft.WithNbTasks(n)) 335 case lagrangeCosetRegular: 336 p.Layout = BitReverse 337 d.FFTInverse((*p.coefficients), fft.DIF, fft.OnCoset(), fft.WithNbTasks(n)) 338 case lagrangeCosetBitReverse: 339 p.Layout = Regular 340 d.FFTInverse((*p.coefficients), fft.DIT, fft.OnCoset(), fft.WithNbTasks(n)) 341 default: 342 panic("unknown ID") 343 } 344 p.Basis = Canonical 345 return p 346 } 347 348 func (p *polynomial) grow(newSize int) { 349 offset := newSize - p.coefficients.Len() 350 if offset > 0 { 351 (*p.coefficients) = append((*p.coefficients), make(fr.Vector, offset)...) 352 } 353 } 354 355 // ToLagrangeCoset Sets p to q, in LagrangeCoset form and returns it. 356 func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { 357 id := p.Form 358 p.grow(int(d.Cardinality)) 359 switch id { 360 case canonicalRegular: 361 p.Layout = BitReverse 362 d.FFT((*p.coefficients), fft.DIF, fft.OnCoset()) 363 case canonicalBitReverse: 364 p.Layout = Regular 365 d.FFT((*p.coefficients), fft.DIT, fft.OnCoset()) 366 case lagrangeRegular: 367 p.Layout = Regular 368 d.FFTInverse((*p.coefficients), fft.DIF) 369 d.FFT((*p.coefficients), fft.DIT, fft.OnCoset()) 370 case lagrangeBitReverse: 371 p.Layout = BitReverse 372 d.FFTInverse((*p.coefficients), fft.DIT) 373 d.FFT((*p.coefficients), fft.DIF, fft.OnCoset()) 374 case lagrangeCosetRegular, lagrangeCosetBitReverse: 375 return p 376 default: 377 panic("unknown ID") 378 } 379 380 p.Basis = LagrangeCoset 381 return p 382 } 383 384 // WriteTo implements io.WriterTo 385 func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { 386 // encode coefficients 387 n, err := p.polynomial.coefficients.WriteTo(w) 388 if err != nil { 389 return n, err 390 } 391 392 // encode Form.Basis, Form.Layout, shift, size & blindedSize as uint32 393 var data = []uint32{ 394 uint32(p.Basis), 395 uint32(p.Layout), 396 uint32(p.shift), 397 uint32(p.size), 398 uint32(p.blindedSize), 399 } 400 for _, v := range data { 401 err = binary.Write(w, binary.BigEndian, v) 402 if err != nil { 403 return n, err 404 } 405 n += 4 406 } 407 return n, nil 408 } 409 410 // ReadFrom implements io.ReaderFrom 411 func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { 412 // decode coefficients 413 if p.polynomial == nil { 414 p.polynomial = new(polynomial) 415 } 416 if p.polynomial.coefficients == nil { 417 v := make(fr.Vector, 0) 418 p.polynomial.coefficients = &v 419 } 420 n, err := p.polynomial.coefficients.ReadFrom(r) 421 if err != nil { 422 return n, err 423 } 424 425 // decode Form.Basis, Form.Layout, shift, size & blindedSize as uint32 426 var data [5]uint32 427 var buf [4]byte 428 for i := range data { 429 read, err := io.ReadFull(r, buf[:4]) 430 n += int64(read) 431 if err != nil { 432 return n, err 433 } 434 data[i] = binary.BigEndian.Uint32(buf[:4]) 435 } 436 437 p.Basis = Basis(data[0]) 438 p.Layout = Layout(data[1]) 439 p.shift = int(data[2]) 440 p.size = int(data[3]) 441 p.blindedSize = int(data[4]) 442 443 return n, nil 444 }