From 41949a66ca879b61c367cf9652462e3cc400598b Mon Sep 17 00:00:00 2001 From: ThomasPiellard Date: Sat, 21 Dec 2024 16:17:52 +0100 Subject: [PATCH] feat: cleaned iop (#587) --- ecc/bls12-377/fr/iop/expressions.go | 1 - ecc/bls12-377/fr/iop/polynomial.go | 165 ++++++++++-------- ecc/bls12-377/fr/iop/polynomial_test.go | 123 +++++++------ ecc/bls12-377/fr/iop/quotient.go | 1 - ecc/bls12-381/fr/iop/expressions.go | 1 - ecc/bls12-381/fr/iop/polynomial.go | 165 ++++++++++-------- ecc/bls12-381/fr/iop/polynomial_test.go | 123 +++++++------ ecc/bls12-381/fr/iop/quotient.go | 1 - ecc/bls24-315/fr/iop/expressions.go | 1 - ecc/bls24-315/fr/iop/polynomial.go | 165 ++++++++++-------- ecc/bls24-315/fr/iop/polynomial_test.go | 123 +++++++------ ecc/bls24-315/fr/iop/quotient.go | 1 - ecc/bls24-317/fr/iop/expressions.go | 1 - ecc/bls24-317/fr/iop/polynomial.go | 165 ++++++++++-------- ecc/bls24-317/fr/iop/polynomial_test.go | 123 +++++++------ ecc/bls24-317/fr/iop/quotient.go | 1 - ecc/bn254/fr/iop/expressions.go | 1 - ecc/bn254/fr/iop/polynomial.go | 165 ++++++++++-------- ecc/bn254/fr/iop/polynomial_test.go | 123 +++++++------ ecc/bn254/fr/iop/quotient.go | 1 - ecc/bw6-633/fr/iop/expressions.go | 1 - ecc/bw6-633/fr/iop/polynomial.go | 165 ++++++++++-------- ecc/bw6-633/fr/iop/polynomial_test.go | 123 +++++++------ ecc/bw6-633/fr/iop/quotient.go | 1 - ecc/bw6-761/fr/iop/expressions.go | 1 - ecc/bw6-761/fr/iop/polynomial.go | 165 ++++++++++-------- ecc/bw6-761/fr/iop/polynomial_test.go | 123 +++++++------ ecc/bw6-761/fr/iop/quotient.go | 1 - .../iop/template/expressions.go.tmpl | 3 +- .../generator/iop/template/polynomial.go.tmpl | 165 ++++++++++-------- .../iop/template/polynomial.test.go.tmpl | 132 +++++++------- .../generator/iop/template/quotient.go.tmpl | 6 +- 32 files changed, 1279 insertions(+), 1057 deletions(-) diff --git a/ecc/bls12-377/fr/iop/expressions.go b/ecc/bls12-377/fr/iop/expressions.go index db75f5d8fc..b9195244b0 100644 --- a/ecc/bls12-377/fr/iop/expressions.go +++ b/ecc/bls12-377/fr/iop/expressions.go @@ -67,7 +67,6 @@ func Evaluate(f Expression, r []fr.Element, form Form, x ...*Polynomial) (*Polyn res := NewPolynomial(&r, form) res.size = x[0].size - res.blindedSize = x[0].size res.shift = 0 return res, nil diff --git a/ecc/bls12-377/fr/iop/polynomial.go b/ecc/bls12-377/fr/iop/polynomial.go index 83ba068533..c793a71407 100644 --- a/ecc/bls12-377/fr/iop/polynomial.go +++ b/ecc/bls12-377/fr/iop/polynomial.go @@ -61,9 +61,9 @@ var ( // default blindedSize=Size, until the polynomial is blinded. type Polynomial struct { *polynomial - shift int - size int - blindedSize int + shift int + size int + coset fr.Element // needed for evaluating the polynomial when it is expressed in Lagrange shifted basis } // NewPolynomial returned a Polynomial from the provided coefficients in the given form. @@ -72,9 +72,8 @@ type Polynomial struct { // shouldn't be mutated. func NewPolynomial(coeffs *[]fr.Element, form Form) *Polynomial { return &Polynomial{ - polynomial: newPolynomial(coeffs, form), - size: len(*coeffs), - blindedSize: len(*coeffs), + polynomial: newPolynomial(coeffs, form), + size: len(*coeffs), } } @@ -85,12 +84,6 @@ func (p *Polynomial) Shift(shift int) *Polynomial { return p } -// BlindedSize returns the the size of the polynomial when it is blinded. By -// default blindedSize=Size, until the polynomial is blinded. -func (p *Polynomial) BlindedSize() int { - return p.blindedSize -} - // Size returns the real size of the polynomial (seen as a vector). // For instance if len(P)=32 but P.Size=8, it means that P has been // extended (e.g. it is evaluated on a larger set) but P is a polynomial @@ -108,69 +101,31 @@ func (p *Polynomial) SetSize(size int) { p.size = size } -// SetBlindedSize sets the blinded size of the polynomial. -func (p *Polynomial) SetBlindedSize(size int) { - p.blindedSize = size -} - -// Blind blinds a polynomial q by adding Q(X)*(X^{n}-1), -// where deg Q = blindingOrder and Q is random, and n is the -// size of q. Sets the result to p and returns it. -// -// blindingOrder is the degree of Q, where the blinding is Q(X)*(X^{n}-1) -// where n is the size of p. The size of p is modified since the underlying -// polynomial is of bigger degree now. The new size is p.Size+1+blindingOrder. -// -// /!\ The code panics if wq is not in canonical, regular layout -func (p *Polynomial) Blind(blindingOrder int) *Polynomial { - // check that p is in canonical basis - if p.Form != canonicalRegular { - panic("the input must be in canonical basis, regular layout") - } - - // we add Q*(x^{n}-1) so the new size is deg(Q)+n+1 - // where n is the size of wq. - newSize := p.size + blindingOrder + 1 - - // Resize p. The size of wq might has already been increased - // (e.g. when the polynomial is evaluated on a larger domain), - // if that's the case we don't resize the polynomial. - p.grow(newSize) - - // blinding: we add Q(X)(X^{n}-1) to P, where deg(Q)=blindingOrder - var r fr.Element - - for i := 0; i <= blindingOrder; i++ { - r.SetRandom() - (*p.coefficients)[i].Sub(&(*p.coefficients)[i], &r) - (*p.coefficients)[i+p.size].Add(&(*p.coefficients)[i+p.size], &r) - } - p.blindedSize = newSize - - return p -} - // Evaluate evaluates p at x. // The code panics if the function is not in canonical form. func (p *Polynomial) Evaluate(x fr.Element) fr.Element { + if p.Basis == LagrangeCoset { + x.Div(&x, &p.coset) + } + if p.shift == 0 { return p.polynomial.evaluate(x) } var g fr.Element - gen, err := fft.Generator(uint64(p.size)) - if err != nil { - panic(err) - } if p.shift <= 5 { + gen, err := fft.Generator(uint64(p.size)) + if err != nil { + panic(err) + } g = smallExp(gen, p.shift) x.Mul(&x, &g) return p.polynomial.evaluate(x) } bs := big.NewInt(int64(p.shift)) - g = *g.Exp(gen, bs) + g = *g.Exp(g, bs) x.Mul(&x, &g) return p.polynomial.evaluate(x) @@ -245,20 +200,60 @@ func (p *polynomial) clone(capacity ...int) *polynomial { func (p *polynomial) evaluate(x fr.Element) fr.Element { var r fr.Element - if p.Basis != Canonical { - panic("p must be in canonical basis") + + evalLagrange := func() { + sizeP := p.coefficients.Len() + w, err := fft.Generator(uint64(sizeP)) + if err != nil { + panic(err) + } + var accw fr.Element + accw.SetOne() + dens := make([]fr.Element, sizeP) // [x-1, x-ω, x-ω², ...] + for i := 0; i < sizeP; i++ { + dens[i].Sub(&x, &accw) + accw.Mul(&accw, &w) + } + invdens := fr.BatchInvert(dens) // [1/(x-1), 1/(x-ω), 1/(x-ω²), ...] + var tmp fr.Element + var one fr.Element + one.SetOne() + tmp.Exp(x, big.NewInt(int64(sizeP))).Sub(&tmp, &one) // xⁿ-1 + var li fr.Element + li.SetUint64(uint64(sizeP)).Inverse(&li).Mul(&li, &tmp) // 1/n * (xⁿ-1) + if p.Layout == Regular { + for i := 0; i < sizeP; i++ { + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[i]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := 0; i < sizeP; i++ { + iRev := bits.Reverse64(uint64(i)) >> nn + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[iRev]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } } - if p.Layout == Regular { - for i := p.coefficients.Len() - 1; i >= 0; i-- { - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + if p.Basis == Canonical { + if p.Layout == Regular { + for i := p.coefficients.Len() - 1; i >= 0; i-- { + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := p.coefficients.Len() - 1; i >= 0; i-- { + iRev := bits.Reverse64(uint64(i)) >> nn + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) + } } } else { - nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) - for i := p.coefficients.Len() - 1; i >= 0; i-- { - iRev := bits.Reverse64(uint64(i)) >> nn - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) - } + evalLagrange() } return r @@ -362,6 +357,11 @@ func (p *polynomial) grow(newSize int) { // ToLagrangeCoset Sets p to q, in LagrangeCoset form and returns it. func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { + cosetTable, err := d.CosetTable() + if err != nil { + panic(err) + } + p.coset.Set(&cosetTable[1]) id := p.Form p.grow(int(d.Cardinality)) switch id { @@ -391,6 +391,7 @@ func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { // WriteTo implements io.WriterTo func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { + // encode coefficients n, err := p.polynomial.coefficients.WriteTo(w) if err != nil { @@ -403,7 +404,6 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { uint32(p.Layout), uint32(p.shift), uint32(p.size), - uint32(p.blindedSize), } for _, v := range data { err = binary.Write(w, binary.BigEndian, v) @@ -412,6 +412,15 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { } n += 4 } + + var buf [fr.Bytes]byte + fr.BigEndian.PutElement(&buf, p.coset) + m, err := w.Write(buf[:]) + n += int64(m) + if err != nil { + return n, err + } + return n, nil } @@ -430,8 +439,8 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { return n, err } - // decode Form.Basis, Form.Layout, shift, size & blindedSize as uint32 - var data [5]uint32 + // decode Form.Basis, Form.Layout, shift as uint32 + var data [4]uint32 var buf [4]byte for i := range data { read, err := io.ReadFull(r, buf[:4]) @@ -446,7 +455,17 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { p.Layout = Layout(data[1]) p.shift = int(data[2]) p.size = int(data[3]) - p.blindedSize = int(data[4]) + + var bufFr [fr.Bytes]byte + read, err := io.ReadFull(r, bufFr[:]) + n += int64(read) + if err != nil { + return n, err + } + p.coset, err = fr.BigEndian.Element(&bufFr) + if err != nil { + return n, err + } return n, nil } diff --git a/ecc/bls12-377/fr/iop/polynomial_test.go b/ecc/bls12-377/fr/iop/polynomial_test.go index b0decababf..a7b0ec595a 100644 --- a/ecc/bls12-377/fr/iop/polynomial_test.go +++ b/ecc/bls12-377/fr/iop/polynomial_test.go @@ -23,14 +23,14 @@ func TestEvaluation(t *testing.T) { shift := 2 d := fft.NewDomain(uint64(size)) c := randomVector(size) - wp := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) - wps := wp.ShallowClone().Shift(shift) - ref := wp.Clone() + p := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) + ps := p.ShallowClone().Shift(shift) + ref := p.Clone() ref.ToLagrange(d).ToRegular() - // regular layout - a := wp.Evaluate(d.Generator) - b := wps.Evaluate(d.Generator) + // canonical regular + a := p.Evaluate(d.Generator) + b := ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -38,11 +38,11 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } - // bit reversed layout - wp.ToBitReverse() - wps.ToBitReverse() - a = wp.Evaluate(d.Generator) - b = wps.Evaluate(d.Generator) + // canonical bit reversed + p.ToBitReverse() + ps.ToBitReverse() + a = p.Evaluate(d.Generator) + b = ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -50,6 +50,60 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } + // get reference values + var x fr.Element + x.SetRandom() + expectedEval := p.ToRegular().Evaluate(x) + expectedEvalShifted := ps.ToRegular().Evaluate(x) + + // lagrange regular + p.ToLagrange(d).ToRegular() + ps.ToLagrange(d).ToRegular() + plx := p.Evaluate(x) + pslx := ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange bit reverse + p.ToBitReverse() + ps.ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange coset regular + p.ToLagrangeCoset(d).ToRegular() + ps.ToLagrangeCoset(d).ToRegular() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + + // lagrange coset bit reverse + p.ToRegular().ToBitReverse() + ps.ToRegular().ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + } func randomVector(size int) *[]fr.Element { @@ -113,10 +167,9 @@ func TestRoundTrip(t *testing.T) { size := 8 d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - p := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}).ToCanonical(d).ToRegular() - p.Blind(blindingOrder) + p := NewPolynomial(randomVector(size), Form{Basis: Canonical, Layout: Regular}) + p.ToLagrangeCoset(d) // serialize written, err := p.WriteTo(&buf) @@ -134,52 +187,10 @@ func TestRoundTrip(t *testing.T) { assert.Equal(p.Layout, reconstructed.Layout) assert.Equal(p.shift, reconstructed.shift) assert.Equal(p.size, reconstructed.size) - assert.Equal(p.blindedSize, reconstructed.blindedSize) c1, c2 := p.Coefficients(), reconstructed.Coefficients() assert.True(reflect.DeepEqual(c1, c2)) } -func TestBlinding(t *testing.T) { - - size := 8 - d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - - // generate a random polynomial in Lagrange form for the moment - // to check that an error is raised when the polynomial is not - // in canonical form. - wp := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}) - - // checks the blinding is correct: the evaluation of the blinded polynomial - // should be the same as the original on d's domain - wp.Basis = Canonical - wt := wp.Clone() - wt.Blind(blindingOrder) - if wt.coefficients.Len() != blindingOrder+size+1 { - t.Fatal("size of blinded polynomial is incorrect") - } - if wt.blindedSize != size+blindingOrder+1 { - t.Fatal("Size field of blinded polynomial is incorrect") - } - if wt.size != size { - t.Fatal("the size should not have been modified") - } - x := make([]fr.Element, size) - x[0].SetOne() - for i := 1; i < size; i++ { - x[i].Mul(&x[i-1], &d.Generator) - } - var a, b fr.Element - for i := 0; i < size; i++ { - a = wt.Evaluate(x[i]) - b = wp.Evaluate(x[i]) - if a != b { - t.Fatal("polynomial and its blinded version should be equal on V(X^{n}-1)") - } - } - -} - // list of functions to turn a polynomial in Lagrange-regular form // to all different forms in ordered using this encoding: // int(p.Basis)*4 + int(p.Layout)*2 + int(p.Status) diff --git a/ecc/bls12-377/fr/iop/quotient.go b/ecc/bls12-377/fr/iop/quotient.go index 698ff0bb37..44ad792b5d 100644 --- a/ecc/bls12-377/fr/iop/quotient.go +++ b/ecc/bls12-377/fr/iop/quotient.go @@ -35,7 +35,6 @@ func DivideByXMinusOne(a *Polynomial, domains [2]*fft.Domain) (*Polynomial, erro coeffs := make([]fr.Element, a.coefficients.Len()) res := NewPolynomial(&coeffs, Form{Layout: BitReverse, Basis: LagrangeCoset}) res.size = a.size - res.blindedSize = a.blindedSize nn := uint64(64 - bits.TrailingZeros(uint(nbElmts))) parallel.Execute(a.coefficients.Len(), func(start, end int) { diff --git a/ecc/bls12-381/fr/iop/expressions.go b/ecc/bls12-381/fr/iop/expressions.go index 2926911d05..57822fd3cc 100644 --- a/ecc/bls12-381/fr/iop/expressions.go +++ b/ecc/bls12-381/fr/iop/expressions.go @@ -67,7 +67,6 @@ func Evaluate(f Expression, r []fr.Element, form Form, x ...*Polynomial) (*Polyn res := NewPolynomial(&r, form) res.size = x[0].size - res.blindedSize = x[0].size res.shift = 0 return res, nil diff --git a/ecc/bls12-381/fr/iop/polynomial.go b/ecc/bls12-381/fr/iop/polynomial.go index ed805584c0..9d20272e31 100644 --- a/ecc/bls12-381/fr/iop/polynomial.go +++ b/ecc/bls12-381/fr/iop/polynomial.go @@ -61,9 +61,9 @@ var ( // default blindedSize=Size, until the polynomial is blinded. type Polynomial struct { *polynomial - shift int - size int - blindedSize int + shift int + size int + coset fr.Element // needed for evaluating the polynomial when it is expressed in Lagrange shifted basis } // NewPolynomial returned a Polynomial from the provided coefficients in the given form. @@ -72,9 +72,8 @@ type Polynomial struct { // shouldn't be mutated. func NewPolynomial(coeffs *[]fr.Element, form Form) *Polynomial { return &Polynomial{ - polynomial: newPolynomial(coeffs, form), - size: len(*coeffs), - blindedSize: len(*coeffs), + polynomial: newPolynomial(coeffs, form), + size: len(*coeffs), } } @@ -85,12 +84,6 @@ func (p *Polynomial) Shift(shift int) *Polynomial { return p } -// BlindedSize returns the the size of the polynomial when it is blinded. By -// default blindedSize=Size, until the polynomial is blinded. -func (p *Polynomial) BlindedSize() int { - return p.blindedSize -} - // Size returns the real size of the polynomial (seen as a vector). // For instance if len(P)=32 but P.Size=8, it means that P has been // extended (e.g. it is evaluated on a larger set) but P is a polynomial @@ -108,69 +101,31 @@ func (p *Polynomial) SetSize(size int) { p.size = size } -// SetBlindedSize sets the blinded size of the polynomial. -func (p *Polynomial) SetBlindedSize(size int) { - p.blindedSize = size -} - -// Blind blinds a polynomial q by adding Q(X)*(X^{n}-1), -// where deg Q = blindingOrder and Q is random, and n is the -// size of q. Sets the result to p and returns it. -// -// blindingOrder is the degree of Q, where the blinding is Q(X)*(X^{n}-1) -// where n is the size of p. The size of p is modified since the underlying -// polynomial is of bigger degree now. The new size is p.Size+1+blindingOrder. -// -// /!\ The code panics if wq is not in canonical, regular layout -func (p *Polynomial) Blind(blindingOrder int) *Polynomial { - // check that p is in canonical basis - if p.Form != canonicalRegular { - panic("the input must be in canonical basis, regular layout") - } - - // we add Q*(x^{n}-1) so the new size is deg(Q)+n+1 - // where n is the size of wq. - newSize := p.size + blindingOrder + 1 - - // Resize p. The size of wq might has already been increased - // (e.g. when the polynomial is evaluated on a larger domain), - // if that's the case we don't resize the polynomial. - p.grow(newSize) - - // blinding: we add Q(X)(X^{n}-1) to P, where deg(Q)=blindingOrder - var r fr.Element - - for i := 0; i <= blindingOrder; i++ { - r.SetRandom() - (*p.coefficients)[i].Sub(&(*p.coefficients)[i], &r) - (*p.coefficients)[i+p.size].Add(&(*p.coefficients)[i+p.size], &r) - } - p.blindedSize = newSize - - return p -} - // Evaluate evaluates p at x. // The code panics if the function is not in canonical form. func (p *Polynomial) Evaluate(x fr.Element) fr.Element { + if p.Basis == LagrangeCoset { + x.Div(&x, &p.coset) + } + if p.shift == 0 { return p.polynomial.evaluate(x) } var g fr.Element - gen, err := fft.Generator(uint64(p.size)) - if err != nil { - panic(err) - } if p.shift <= 5 { + gen, err := fft.Generator(uint64(p.size)) + if err != nil { + panic(err) + } g = smallExp(gen, p.shift) x.Mul(&x, &g) return p.polynomial.evaluate(x) } bs := big.NewInt(int64(p.shift)) - g = *g.Exp(gen, bs) + g = *g.Exp(g, bs) x.Mul(&x, &g) return p.polynomial.evaluate(x) @@ -245,20 +200,60 @@ func (p *polynomial) clone(capacity ...int) *polynomial { func (p *polynomial) evaluate(x fr.Element) fr.Element { var r fr.Element - if p.Basis != Canonical { - panic("p must be in canonical basis") + + evalLagrange := func() { + sizeP := p.coefficients.Len() + w, err := fft.Generator(uint64(sizeP)) + if err != nil { + panic(err) + } + var accw fr.Element + accw.SetOne() + dens := make([]fr.Element, sizeP) // [x-1, x-ω, x-ω², ...] + for i := 0; i < sizeP; i++ { + dens[i].Sub(&x, &accw) + accw.Mul(&accw, &w) + } + invdens := fr.BatchInvert(dens) // [1/(x-1), 1/(x-ω), 1/(x-ω²), ...] + var tmp fr.Element + var one fr.Element + one.SetOne() + tmp.Exp(x, big.NewInt(int64(sizeP))).Sub(&tmp, &one) // xⁿ-1 + var li fr.Element + li.SetUint64(uint64(sizeP)).Inverse(&li).Mul(&li, &tmp) // 1/n * (xⁿ-1) + if p.Layout == Regular { + for i := 0; i < sizeP; i++ { + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[i]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := 0; i < sizeP; i++ { + iRev := bits.Reverse64(uint64(i)) >> nn + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[iRev]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } } - if p.Layout == Regular { - for i := p.coefficients.Len() - 1; i >= 0; i-- { - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + if p.Basis == Canonical { + if p.Layout == Regular { + for i := p.coefficients.Len() - 1; i >= 0; i-- { + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := p.coefficients.Len() - 1; i >= 0; i-- { + iRev := bits.Reverse64(uint64(i)) >> nn + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) + } } } else { - nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) - for i := p.coefficients.Len() - 1; i >= 0; i-- { - iRev := bits.Reverse64(uint64(i)) >> nn - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) - } + evalLagrange() } return r @@ -362,6 +357,11 @@ func (p *polynomial) grow(newSize int) { // ToLagrangeCoset Sets p to q, in LagrangeCoset form and returns it. func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { + cosetTable, err := d.CosetTable() + if err != nil { + panic(err) + } + p.coset.Set(&cosetTable[1]) id := p.Form p.grow(int(d.Cardinality)) switch id { @@ -391,6 +391,7 @@ func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { // WriteTo implements io.WriterTo func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { + // encode coefficients n, err := p.polynomial.coefficients.WriteTo(w) if err != nil { @@ -403,7 +404,6 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { uint32(p.Layout), uint32(p.shift), uint32(p.size), - uint32(p.blindedSize), } for _, v := range data { err = binary.Write(w, binary.BigEndian, v) @@ -412,6 +412,15 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { } n += 4 } + + var buf [fr.Bytes]byte + fr.BigEndian.PutElement(&buf, p.coset) + m, err := w.Write(buf[:]) + n += int64(m) + if err != nil { + return n, err + } + return n, nil } @@ -430,8 +439,8 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { return n, err } - // decode Form.Basis, Form.Layout, shift, size & blindedSize as uint32 - var data [5]uint32 + // decode Form.Basis, Form.Layout, shift as uint32 + var data [4]uint32 var buf [4]byte for i := range data { read, err := io.ReadFull(r, buf[:4]) @@ -446,7 +455,17 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { p.Layout = Layout(data[1]) p.shift = int(data[2]) p.size = int(data[3]) - p.blindedSize = int(data[4]) + + var bufFr [fr.Bytes]byte + read, err := io.ReadFull(r, bufFr[:]) + n += int64(read) + if err != nil { + return n, err + } + p.coset, err = fr.BigEndian.Element(&bufFr) + if err != nil { + return n, err + } return n, nil } diff --git a/ecc/bls12-381/fr/iop/polynomial_test.go b/ecc/bls12-381/fr/iop/polynomial_test.go index cba62562d3..96f67f8f82 100644 --- a/ecc/bls12-381/fr/iop/polynomial_test.go +++ b/ecc/bls12-381/fr/iop/polynomial_test.go @@ -23,14 +23,14 @@ func TestEvaluation(t *testing.T) { shift := 2 d := fft.NewDomain(uint64(size)) c := randomVector(size) - wp := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) - wps := wp.ShallowClone().Shift(shift) - ref := wp.Clone() + p := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) + ps := p.ShallowClone().Shift(shift) + ref := p.Clone() ref.ToLagrange(d).ToRegular() - // regular layout - a := wp.Evaluate(d.Generator) - b := wps.Evaluate(d.Generator) + // canonical regular + a := p.Evaluate(d.Generator) + b := ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -38,11 +38,11 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } - // bit reversed layout - wp.ToBitReverse() - wps.ToBitReverse() - a = wp.Evaluate(d.Generator) - b = wps.Evaluate(d.Generator) + // canonical bit reversed + p.ToBitReverse() + ps.ToBitReverse() + a = p.Evaluate(d.Generator) + b = ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -50,6 +50,60 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } + // get reference values + var x fr.Element + x.SetRandom() + expectedEval := p.ToRegular().Evaluate(x) + expectedEvalShifted := ps.ToRegular().Evaluate(x) + + // lagrange regular + p.ToLagrange(d).ToRegular() + ps.ToLagrange(d).ToRegular() + plx := p.Evaluate(x) + pslx := ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange bit reverse + p.ToBitReverse() + ps.ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange coset regular + p.ToLagrangeCoset(d).ToRegular() + ps.ToLagrangeCoset(d).ToRegular() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + + // lagrange coset bit reverse + p.ToRegular().ToBitReverse() + ps.ToRegular().ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + } func randomVector(size int) *[]fr.Element { @@ -113,10 +167,9 @@ func TestRoundTrip(t *testing.T) { size := 8 d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - p := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}).ToCanonical(d).ToRegular() - p.Blind(blindingOrder) + p := NewPolynomial(randomVector(size), Form{Basis: Canonical, Layout: Regular}) + p.ToLagrangeCoset(d) // serialize written, err := p.WriteTo(&buf) @@ -134,52 +187,10 @@ func TestRoundTrip(t *testing.T) { assert.Equal(p.Layout, reconstructed.Layout) assert.Equal(p.shift, reconstructed.shift) assert.Equal(p.size, reconstructed.size) - assert.Equal(p.blindedSize, reconstructed.blindedSize) c1, c2 := p.Coefficients(), reconstructed.Coefficients() assert.True(reflect.DeepEqual(c1, c2)) } -func TestBlinding(t *testing.T) { - - size := 8 - d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - - // generate a random polynomial in Lagrange form for the moment - // to check that an error is raised when the polynomial is not - // in canonical form. - wp := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}) - - // checks the blinding is correct: the evaluation of the blinded polynomial - // should be the same as the original on d's domain - wp.Basis = Canonical - wt := wp.Clone() - wt.Blind(blindingOrder) - if wt.coefficients.Len() != blindingOrder+size+1 { - t.Fatal("size of blinded polynomial is incorrect") - } - if wt.blindedSize != size+blindingOrder+1 { - t.Fatal("Size field of blinded polynomial is incorrect") - } - if wt.size != size { - t.Fatal("the size should not have been modified") - } - x := make([]fr.Element, size) - x[0].SetOne() - for i := 1; i < size; i++ { - x[i].Mul(&x[i-1], &d.Generator) - } - var a, b fr.Element - for i := 0; i < size; i++ { - a = wt.Evaluate(x[i]) - b = wp.Evaluate(x[i]) - if a != b { - t.Fatal("polynomial and its blinded version should be equal on V(X^{n}-1)") - } - } - -} - // list of functions to turn a polynomial in Lagrange-regular form // to all different forms in ordered using this encoding: // int(p.Basis)*4 + int(p.Layout)*2 + int(p.Status) diff --git a/ecc/bls12-381/fr/iop/quotient.go b/ecc/bls12-381/fr/iop/quotient.go index 5c4b2a9eb8..9d75028f1b 100644 --- a/ecc/bls12-381/fr/iop/quotient.go +++ b/ecc/bls12-381/fr/iop/quotient.go @@ -35,7 +35,6 @@ func DivideByXMinusOne(a *Polynomial, domains [2]*fft.Domain) (*Polynomial, erro coeffs := make([]fr.Element, a.coefficients.Len()) res := NewPolynomial(&coeffs, Form{Layout: BitReverse, Basis: LagrangeCoset}) res.size = a.size - res.blindedSize = a.blindedSize nn := uint64(64 - bits.TrailingZeros(uint(nbElmts))) parallel.Execute(a.coefficients.Len(), func(start, end int) { diff --git a/ecc/bls24-315/fr/iop/expressions.go b/ecc/bls24-315/fr/iop/expressions.go index b4706d77d6..be044147f9 100644 --- a/ecc/bls24-315/fr/iop/expressions.go +++ b/ecc/bls24-315/fr/iop/expressions.go @@ -67,7 +67,6 @@ func Evaluate(f Expression, r []fr.Element, form Form, x ...*Polynomial) (*Polyn res := NewPolynomial(&r, form) res.size = x[0].size - res.blindedSize = x[0].size res.shift = 0 return res, nil diff --git a/ecc/bls24-315/fr/iop/polynomial.go b/ecc/bls24-315/fr/iop/polynomial.go index 33a5265ee4..91ee36796a 100644 --- a/ecc/bls24-315/fr/iop/polynomial.go +++ b/ecc/bls24-315/fr/iop/polynomial.go @@ -61,9 +61,9 @@ var ( // default blindedSize=Size, until the polynomial is blinded. type Polynomial struct { *polynomial - shift int - size int - blindedSize int + shift int + size int + coset fr.Element // needed for evaluating the polynomial when it is expressed in Lagrange shifted basis } // NewPolynomial returned a Polynomial from the provided coefficients in the given form. @@ -72,9 +72,8 @@ type Polynomial struct { // shouldn't be mutated. func NewPolynomial(coeffs *[]fr.Element, form Form) *Polynomial { return &Polynomial{ - polynomial: newPolynomial(coeffs, form), - size: len(*coeffs), - blindedSize: len(*coeffs), + polynomial: newPolynomial(coeffs, form), + size: len(*coeffs), } } @@ -85,12 +84,6 @@ func (p *Polynomial) Shift(shift int) *Polynomial { return p } -// BlindedSize returns the the size of the polynomial when it is blinded. By -// default blindedSize=Size, until the polynomial is blinded. -func (p *Polynomial) BlindedSize() int { - return p.blindedSize -} - // Size returns the real size of the polynomial (seen as a vector). // For instance if len(P)=32 but P.Size=8, it means that P has been // extended (e.g. it is evaluated on a larger set) but P is a polynomial @@ -108,69 +101,31 @@ func (p *Polynomial) SetSize(size int) { p.size = size } -// SetBlindedSize sets the blinded size of the polynomial. -func (p *Polynomial) SetBlindedSize(size int) { - p.blindedSize = size -} - -// Blind blinds a polynomial q by adding Q(X)*(X^{n}-1), -// where deg Q = blindingOrder and Q is random, and n is the -// size of q. Sets the result to p and returns it. -// -// blindingOrder is the degree of Q, where the blinding is Q(X)*(X^{n}-1) -// where n is the size of p. The size of p is modified since the underlying -// polynomial is of bigger degree now. The new size is p.Size+1+blindingOrder. -// -// /!\ The code panics if wq is not in canonical, regular layout -func (p *Polynomial) Blind(blindingOrder int) *Polynomial { - // check that p is in canonical basis - if p.Form != canonicalRegular { - panic("the input must be in canonical basis, regular layout") - } - - // we add Q*(x^{n}-1) so the new size is deg(Q)+n+1 - // where n is the size of wq. - newSize := p.size + blindingOrder + 1 - - // Resize p. The size of wq might has already been increased - // (e.g. when the polynomial is evaluated on a larger domain), - // if that's the case we don't resize the polynomial. - p.grow(newSize) - - // blinding: we add Q(X)(X^{n}-1) to P, where deg(Q)=blindingOrder - var r fr.Element - - for i := 0; i <= blindingOrder; i++ { - r.SetRandom() - (*p.coefficients)[i].Sub(&(*p.coefficients)[i], &r) - (*p.coefficients)[i+p.size].Add(&(*p.coefficients)[i+p.size], &r) - } - p.blindedSize = newSize - - return p -} - // Evaluate evaluates p at x. // The code panics if the function is not in canonical form. func (p *Polynomial) Evaluate(x fr.Element) fr.Element { + if p.Basis == LagrangeCoset { + x.Div(&x, &p.coset) + } + if p.shift == 0 { return p.polynomial.evaluate(x) } var g fr.Element - gen, err := fft.Generator(uint64(p.size)) - if err != nil { - panic(err) - } if p.shift <= 5 { + gen, err := fft.Generator(uint64(p.size)) + if err != nil { + panic(err) + } g = smallExp(gen, p.shift) x.Mul(&x, &g) return p.polynomial.evaluate(x) } bs := big.NewInt(int64(p.shift)) - g = *g.Exp(gen, bs) + g = *g.Exp(g, bs) x.Mul(&x, &g) return p.polynomial.evaluate(x) @@ -245,20 +200,60 @@ func (p *polynomial) clone(capacity ...int) *polynomial { func (p *polynomial) evaluate(x fr.Element) fr.Element { var r fr.Element - if p.Basis != Canonical { - panic("p must be in canonical basis") + + evalLagrange := func() { + sizeP := p.coefficients.Len() + w, err := fft.Generator(uint64(sizeP)) + if err != nil { + panic(err) + } + var accw fr.Element + accw.SetOne() + dens := make([]fr.Element, sizeP) // [x-1, x-ω, x-ω², ...] + for i := 0; i < sizeP; i++ { + dens[i].Sub(&x, &accw) + accw.Mul(&accw, &w) + } + invdens := fr.BatchInvert(dens) // [1/(x-1), 1/(x-ω), 1/(x-ω²), ...] + var tmp fr.Element + var one fr.Element + one.SetOne() + tmp.Exp(x, big.NewInt(int64(sizeP))).Sub(&tmp, &one) // xⁿ-1 + var li fr.Element + li.SetUint64(uint64(sizeP)).Inverse(&li).Mul(&li, &tmp) // 1/n * (xⁿ-1) + if p.Layout == Regular { + for i := 0; i < sizeP; i++ { + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[i]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := 0; i < sizeP; i++ { + iRev := bits.Reverse64(uint64(i)) >> nn + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[iRev]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } } - if p.Layout == Regular { - for i := p.coefficients.Len() - 1; i >= 0; i-- { - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + if p.Basis == Canonical { + if p.Layout == Regular { + for i := p.coefficients.Len() - 1; i >= 0; i-- { + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := p.coefficients.Len() - 1; i >= 0; i-- { + iRev := bits.Reverse64(uint64(i)) >> nn + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) + } } } else { - nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) - for i := p.coefficients.Len() - 1; i >= 0; i-- { - iRev := bits.Reverse64(uint64(i)) >> nn - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) - } + evalLagrange() } return r @@ -362,6 +357,11 @@ func (p *polynomial) grow(newSize int) { // ToLagrangeCoset Sets p to q, in LagrangeCoset form and returns it. func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { + cosetTable, err := d.CosetTable() + if err != nil { + panic(err) + } + p.coset.Set(&cosetTable[1]) id := p.Form p.grow(int(d.Cardinality)) switch id { @@ -391,6 +391,7 @@ func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { // WriteTo implements io.WriterTo func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { + // encode coefficients n, err := p.polynomial.coefficients.WriteTo(w) if err != nil { @@ -403,7 +404,6 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { uint32(p.Layout), uint32(p.shift), uint32(p.size), - uint32(p.blindedSize), } for _, v := range data { err = binary.Write(w, binary.BigEndian, v) @@ -412,6 +412,15 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { } n += 4 } + + var buf [fr.Bytes]byte + fr.BigEndian.PutElement(&buf, p.coset) + m, err := w.Write(buf[:]) + n += int64(m) + if err != nil { + return n, err + } + return n, nil } @@ -430,8 +439,8 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { return n, err } - // decode Form.Basis, Form.Layout, shift, size & blindedSize as uint32 - var data [5]uint32 + // decode Form.Basis, Form.Layout, shift as uint32 + var data [4]uint32 var buf [4]byte for i := range data { read, err := io.ReadFull(r, buf[:4]) @@ -446,7 +455,17 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { p.Layout = Layout(data[1]) p.shift = int(data[2]) p.size = int(data[3]) - p.blindedSize = int(data[4]) + + var bufFr [fr.Bytes]byte + read, err := io.ReadFull(r, bufFr[:]) + n += int64(read) + if err != nil { + return n, err + } + p.coset, err = fr.BigEndian.Element(&bufFr) + if err != nil { + return n, err + } return n, nil } diff --git a/ecc/bls24-315/fr/iop/polynomial_test.go b/ecc/bls24-315/fr/iop/polynomial_test.go index 89d65b2fd6..a8e86b350f 100644 --- a/ecc/bls24-315/fr/iop/polynomial_test.go +++ b/ecc/bls24-315/fr/iop/polynomial_test.go @@ -23,14 +23,14 @@ func TestEvaluation(t *testing.T) { shift := 2 d := fft.NewDomain(uint64(size)) c := randomVector(size) - wp := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) - wps := wp.ShallowClone().Shift(shift) - ref := wp.Clone() + p := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) + ps := p.ShallowClone().Shift(shift) + ref := p.Clone() ref.ToLagrange(d).ToRegular() - // regular layout - a := wp.Evaluate(d.Generator) - b := wps.Evaluate(d.Generator) + // canonical regular + a := p.Evaluate(d.Generator) + b := ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -38,11 +38,11 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } - // bit reversed layout - wp.ToBitReverse() - wps.ToBitReverse() - a = wp.Evaluate(d.Generator) - b = wps.Evaluate(d.Generator) + // canonical bit reversed + p.ToBitReverse() + ps.ToBitReverse() + a = p.Evaluate(d.Generator) + b = ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -50,6 +50,60 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } + // get reference values + var x fr.Element + x.SetRandom() + expectedEval := p.ToRegular().Evaluate(x) + expectedEvalShifted := ps.ToRegular().Evaluate(x) + + // lagrange regular + p.ToLagrange(d).ToRegular() + ps.ToLagrange(d).ToRegular() + plx := p.Evaluate(x) + pslx := ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange bit reverse + p.ToBitReverse() + ps.ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange coset regular + p.ToLagrangeCoset(d).ToRegular() + ps.ToLagrangeCoset(d).ToRegular() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + + // lagrange coset bit reverse + p.ToRegular().ToBitReverse() + ps.ToRegular().ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + } func randomVector(size int) *[]fr.Element { @@ -113,10 +167,9 @@ func TestRoundTrip(t *testing.T) { size := 8 d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - p := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}).ToCanonical(d).ToRegular() - p.Blind(blindingOrder) + p := NewPolynomial(randomVector(size), Form{Basis: Canonical, Layout: Regular}) + p.ToLagrangeCoset(d) // serialize written, err := p.WriteTo(&buf) @@ -134,52 +187,10 @@ func TestRoundTrip(t *testing.T) { assert.Equal(p.Layout, reconstructed.Layout) assert.Equal(p.shift, reconstructed.shift) assert.Equal(p.size, reconstructed.size) - assert.Equal(p.blindedSize, reconstructed.blindedSize) c1, c2 := p.Coefficients(), reconstructed.Coefficients() assert.True(reflect.DeepEqual(c1, c2)) } -func TestBlinding(t *testing.T) { - - size := 8 - d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - - // generate a random polynomial in Lagrange form for the moment - // to check that an error is raised when the polynomial is not - // in canonical form. - wp := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}) - - // checks the blinding is correct: the evaluation of the blinded polynomial - // should be the same as the original on d's domain - wp.Basis = Canonical - wt := wp.Clone() - wt.Blind(blindingOrder) - if wt.coefficients.Len() != blindingOrder+size+1 { - t.Fatal("size of blinded polynomial is incorrect") - } - if wt.blindedSize != size+blindingOrder+1 { - t.Fatal("Size field of blinded polynomial is incorrect") - } - if wt.size != size { - t.Fatal("the size should not have been modified") - } - x := make([]fr.Element, size) - x[0].SetOne() - for i := 1; i < size; i++ { - x[i].Mul(&x[i-1], &d.Generator) - } - var a, b fr.Element - for i := 0; i < size; i++ { - a = wt.Evaluate(x[i]) - b = wp.Evaluate(x[i]) - if a != b { - t.Fatal("polynomial and its blinded version should be equal on V(X^{n}-1)") - } - } - -} - // list of functions to turn a polynomial in Lagrange-regular form // to all different forms in ordered using this encoding: // int(p.Basis)*4 + int(p.Layout)*2 + int(p.Status) diff --git a/ecc/bls24-315/fr/iop/quotient.go b/ecc/bls24-315/fr/iop/quotient.go index b023028856..be2c4dc48f 100644 --- a/ecc/bls24-315/fr/iop/quotient.go +++ b/ecc/bls24-315/fr/iop/quotient.go @@ -35,7 +35,6 @@ func DivideByXMinusOne(a *Polynomial, domains [2]*fft.Domain) (*Polynomial, erro coeffs := make([]fr.Element, a.coefficients.Len()) res := NewPolynomial(&coeffs, Form{Layout: BitReverse, Basis: LagrangeCoset}) res.size = a.size - res.blindedSize = a.blindedSize nn := uint64(64 - bits.TrailingZeros(uint(nbElmts))) parallel.Execute(a.coefficients.Len(), func(start, end int) { diff --git a/ecc/bls24-317/fr/iop/expressions.go b/ecc/bls24-317/fr/iop/expressions.go index 5c4d901223..8aa5681e32 100644 --- a/ecc/bls24-317/fr/iop/expressions.go +++ b/ecc/bls24-317/fr/iop/expressions.go @@ -67,7 +67,6 @@ func Evaluate(f Expression, r []fr.Element, form Form, x ...*Polynomial) (*Polyn res := NewPolynomial(&r, form) res.size = x[0].size - res.blindedSize = x[0].size res.shift = 0 return res, nil diff --git a/ecc/bls24-317/fr/iop/polynomial.go b/ecc/bls24-317/fr/iop/polynomial.go index 701a833862..ef62148eac 100644 --- a/ecc/bls24-317/fr/iop/polynomial.go +++ b/ecc/bls24-317/fr/iop/polynomial.go @@ -61,9 +61,9 @@ var ( // default blindedSize=Size, until the polynomial is blinded. type Polynomial struct { *polynomial - shift int - size int - blindedSize int + shift int + size int + coset fr.Element // needed for evaluating the polynomial when it is expressed in Lagrange shifted basis } // NewPolynomial returned a Polynomial from the provided coefficients in the given form. @@ -72,9 +72,8 @@ type Polynomial struct { // shouldn't be mutated. func NewPolynomial(coeffs *[]fr.Element, form Form) *Polynomial { return &Polynomial{ - polynomial: newPolynomial(coeffs, form), - size: len(*coeffs), - blindedSize: len(*coeffs), + polynomial: newPolynomial(coeffs, form), + size: len(*coeffs), } } @@ -85,12 +84,6 @@ func (p *Polynomial) Shift(shift int) *Polynomial { return p } -// BlindedSize returns the the size of the polynomial when it is blinded. By -// default blindedSize=Size, until the polynomial is blinded. -func (p *Polynomial) BlindedSize() int { - return p.blindedSize -} - // Size returns the real size of the polynomial (seen as a vector). // For instance if len(P)=32 but P.Size=8, it means that P has been // extended (e.g. it is evaluated on a larger set) but P is a polynomial @@ -108,69 +101,31 @@ func (p *Polynomial) SetSize(size int) { p.size = size } -// SetBlindedSize sets the blinded size of the polynomial. -func (p *Polynomial) SetBlindedSize(size int) { - p.blindedSize = size -} - -// Blind blinds a polynomial q by adding Q(X)*(X^{n}-1), -// where deg Q = blindingOrder and Q is random, and n is the -// size of q. Sets the result to p and returns it. -// -// blindingOrder is the degree of Q, where the blinding is Q(X)*(X^{n}-1) -// where n is the size of p. The size of p is modified since the underlying -// polynomial is of bigger degree now. The new size is p.Size+1+blindingOrder. -// -// /!\ The code panics if wq is not in canonical, regular layout -func (p *Polynomial) Blind(blindingOrder int) *Polynomial { - // check that p is in canonical basis - if p.Form != canonicalRegular { - panic("the input must be in canonical basis, regular layout") - } - - // we add Q*(x^{n}-1) so the new size is deg(Q)+n+1 - // where n is the size of wq. - newSize := p.size + blindingOrder + 1 - - // Resize p. The size of wq might has already been increased - // (e.g. when the polynomial is evaluated on a larger domain), - // if that's the case we don't resize the polynomial. - p.grow(newSize) - - // blinding: we add Q(X)(X^{n}-1) to P, where deg(Q)=blindingOrder - var r fr.Element - - for i := 0; i <= blindingOrder; i++ { - r.SetRandom() - (*p.coefficients)[i].Sub(&(*p.coefficients)[i], &r) - (*p.coefficients)[i+p.size].Add(&(*p.coefficients)[i+p.size], &r) - } - p.blindedSize = newSize - - return p -} - // Evaluate evaluates p at x. // The code panics if the function is not in canonical form. func (p *Polynomial) Evaluate(x fr.Element) fr.Element { + if p.Basis == LagrangeCoset { + x.Div(&x, &p.coset) + } + if p.shift == 0 { return p.polynomial.evaluate(x) } var g fr.Element - gen, err := fft.Generator(uint64(p.size)) - if err != nil { - panic(err) - } if p.shift <= 5 { + gen, err := fft.Generator(uint64(p.size)) + if err != nil { + panic(err) + } g = smallExp(gen, p.shift) x.Mul(&x, &g) return p.polynomial.evaluate(x) } bs := big.NewInt(int64(p.shift)) - g = *g.Exp(gen, bs) + g = *g.Exp(g, bs) x.Mul(&x, &g) return p.polynomial.evaluate(x) @@ -245,20 +200,60 @@ func (p *polynomial) clone(capacity ...int) *polynomial { func (p *polynomial) evaluate(x fr.Element) fr.Element { var r fr.Element - if p.Basis != Canonical { - panic("p must be in canonical basis") + + evalLagrange := func() { + sizeP := p.coefficients.Len() + w, err := fft.Generator(uint64(sizeP)) + if err != nil { + panic(err) + } + var accw fr.Element + accw.SetOne() + dens := make([]fr.Element, sizeP) // [x-1, x-ω, x-ω², ...] + for i := 0; i < sizeP; i++ { + dens[i].Sub(&x, &accw) + accw.Mul(&accw, &w) + } + invdens := fr.BatchInvert(dens) // [1/(x-1), 1/(x-ω), 1/(x-ω²), ...] + var tmp fr.Element + var one fr.Element + one.SetOne() + tmp.Exp(x, big.NewInt(int64(sizeP))).Sub(&tmp, &one) // xⁿ-1 + var li fr.Element + li.SetUint64(uint64(sizeP)).Inverse(&li).Mul(&li, &tmp) // 1/n * (xⁿ-1) + if p.Layout == Regular { + for i := 0; i < sizeP; i++ { + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[i]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := 0; i < sizeP; i++ { + iRev := bits.Reverse64(uint64(i)) >> nn + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[iRev]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } } - if p.Layout == Regular { - for i := p.coefficients.Len() - 1; i >= 0; i-- { - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + if p.Basis == Canonical { + if p.Layout == Regular { + for i := p.coefficients.Len() - 1; i >= 0; i-- { + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := p.coefficients.Len() - 1; i >= 0; i-- { + iRev := bits.Reverse64(uint64(i)) >> nn + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) + } } } else { - nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) - for i := p.coefficients.Len() - 1; i >= 0; i-- { - iRev := bits.Reverse64(uint64(i)) >> nn - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) - } + evalLagrange() } return r @@ -362,6 +357,11 @@ func (p *polynomial) grow(newSize int) { // ToLagrangeCoset Sets p to q, in LagrangeCoset form and returns it. func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { + cosetTable, err := d.CosetTable() + if err != nil { + panic(err) + } + p.coset.Set(&cosetTable[1]) id := p.Form p.grow(int(d.Cardinality)) switch id { @@ -391,6 +391,7 @@ func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { // WriteTo implements io.WriterTo func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { + // encode coefficients n, err := p.polynomial.coefficients.WriteTo(w) if err != nil { @@ -403,7 +404,6 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { uint32(p.Layout), uint32(p.shift), uint32(p.size), - uint32(p.blindedSize), } for _, v := range data { err = binary.Write(w, binary.BigEndian, v) @@ -412,6 +412,15 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { } n += 4 } + + var buf [fr.Bytes]byte + fr.BigEndian.PutElement(&buf, p.coset) + m, err := w.Write(buf[:]) + n += int64(m) + if err != nil { + return n, err + } + return n, nil } @@ -430,8 +439,8 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { return n, err } - // decode Form.Basis, Form.Layout, shift, size & blindedSize as uint32 - var data [5]uint32 + // decode Form.Basis, Form.Layout, shift as uint32 + var data [4]uint32 var buf [4]byte for i := range data { read, err := io.ReadFull(r, buf[:4]) @@ -446,7 +455,17 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { p.Layout = Layout(data[1]) p.shift = int(data[2]) p.size = int(data[3]) - p.blindedSize = int(data[4]) + + var bufFr [fr.Bytes]byte + read, err := io.ReadFull(r, bufFr[:]) + n += int64(read) + if err != nil { + return n, err + } + p.coset, err = fr.BigEndian.Element(&bufFr) + if err != nil { + return n, err + } return n, nil } diff --git a/ecc/bls24-317/fr/iop/polynomial_test.go b/ecc/bls24-317/fr/iop/polynomial_test.go index 3066d383ca..3153fd5963 100644 --- a/ecc/bls24-317/fr/iop/polynomial_test.go +++ b/ecc/bls24-317/fr/iop/polynomial_test.go @@ -23,14 +23,14 @@ func TestEvaluation(t *testing.T) { shift := 2 d := fft.NewDomain(uint64(size)) c := randomVector(size) - wp := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) - wps := wp.ShallowClone().Shift(shift) - ref := wp.Clone() + p := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) + ps := p.ShallowClone().Shift(shift) + ref := p.Clone() ref.ToLagrange(d).ToRegular() - // regular layout - a := wp.Evaluate(d.Generator) - b := wps.Evaluate(d.Generator) + // canonical regular + a := p.Evaluate(d.Generator) + b := ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -38,11 +38,11 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } - // bit reversed layout - wp.ToBitReverse() - wps.ToBitReverse() - a = wp.Evaluate(d.Generator) - b = wps.Evaluate(d.Generator) + // canonical bit reversed + p.ToBitReverse() + ps.ToBitReverse() + a = p.Evaluate(d.Generator) + b = ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -50,6 +50,60 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } + // get reference values + var x fr.Element + x.SetRandom() + expectedEval := p.ToRegular().Evaluate(x) + expectedEvalShifted := ps.ToRegular().Evaluate(x) + + // lagrange regular + p.ToLagrange(d).ToRegular() + ps.ToLagrange(d).ToRegular() + plx := p.Evaluate(x) + pslx := ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange bit reverse + p.ToBitReverse() + ps.ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange coset regular + p.ToLagrangeCoset(d).ToRegular() + ps.ToLagrangeCoset(d).ToRegular() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + + // lagrange coset bit reverse + p.ToRegular().ToBitReverse() + ps.ToRegular().ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + } func randomVector(size int) *[]fr.Element { @@ -113,10 +167,9 @@ func TestRoundTrip(t *testing.T) { size := 8 d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - p := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}).ToCanonical(d).ToRegular() - p.Blind(blindingOrder) + p := NewPolynomial(randomVector(size), Form{Basis: Canonical, Layout: Regular}) + p.ToLagrangeCoset(d) // serialize written, err := p.WriteTo(&buf) @@ -134,52 +187,10 @@ func TestRoundTrip(t *testing.T) { assert.Equal(p.Layout, reconstructed.Layout) assert.Equal(p.shift, reconstructed.shift) assert.Equal(p.size, reconstructed.size) - assert.Equal(p.blindedSize, reconstructed.blindedSize) c1, c2 := p.Coefficients(), reconstructed.Coefficients() assert.True(reflect.DeepEqual(c1, c2)) } -func TestBlinding(t *testing.T) { - - size := 8 - d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - - // generate a random polynomial in Lagrange form for the moment - // to check that an error is raised when the polynomial is not - // in canonical form. - wp := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}) - - // checks the blinding is correct: the evaluation of the blinded polynomial - // should be the same as the original on d's domain - wp.Basis = Canonical - wt := wp.Clone() - wt.Blind(blindingOrder) - if wt.coefficients.Len() != blindingOrder+size+1 { - t.Fatal("size of blinded polynomial is incorrect") - } - if wt.blindedSize != size+blindingOrder+1 { - t.Fatal("Size field of blinded polynomial is incorrect") - } - if wt.size != size { - t.Fatal("the size should not have been modified") - } - x := make([]fr.Element, size) - x[0].SetOne() - for i := 1; i < size; i++ { - x[i].Mul(&x[i-1], &d.Generator) - } - var a, b fr.Element - for i := 0; i < size; i++ { - a = wt.Evaluate(x[i]) - b = wp.Evaluate(x[i]) - if a != b { - t.Fatal("polynomial and its blinded version should be equal on V(X^{n}-1)") - } - } - -} - // list of functions to turn a polynomial in Lagrange-regular form // to all different forms in ordered using this encoding: // int(p.Basis)*4 + int(p.Layout)*2 + int(p.Status) diff --git a/ecc/bls24-317/fr/iop/quotient.go b/ecc/bls24-317/fr/iop/quotient.go index 22561fbdc1..9697b89361 100644 --- a/ecc/bls24-317/fr/iop/quotient.go +++ b/ecc/bls24-317/fr/iop/quotient.go @@ -35,7 +35,6 @@ func DivideByXMinusOne(a *Polynomial, domains [2]*fft.Domain) (*Polynomial, erro coeffs := make([]fr.Element, a.coefficients.Len()) res := NewPolynomial(&coeffs, Form{Layout: BitReverse, Basis: LagrangeCoset}) res.size = a.size - res.blindedSize = a.blindedSize nn := uint64(64 - bits.TrailingZeros(uint(nbElmts))) parallel.Execute(a.coefficients.Len(), func(start, end int) { diff --git a/ecc/bn254/fr/iop/expressions.go b/ecc/bn254/fr/iop/expressions.go index 396a25bc0f..57b95b2d6d 100644 --- a/ecc/bn254/fr/iop/expressions.go +++ b/ecc/bn254/fr/iop/expressions.go @@ -67,7 +67,6 @@ func Evaluate(f Expression, r []fr.Element, form Form, x ...*Polynomial) (*Polyn res := NewPolynomial(&r, form) res.size = x[0].size - res.blindedSize = x[0].size res.shift = 0 return res, nil diff --git a/ecc/bn254/fr/iop/polynomial.go b/ecc/bn254/fr/iop/polynomial.go index 4eb7e6bb8c..d5e1e0ad4f 100644 --- a/ecc/bn254/fr/iop/polynomial.go +++ b/ecc/bn254/fr/iop/polynomial.go @@ -61,9 +61,9 @@ var ( // default blindedSize=Size, until the polynomial is blinded. type Polynomial struct { *polynomial - shift int - size int - blindedSize int + shift int + size int + coset fr.Element // needed for evaluating the polynomial when it is expressed in Lagrange shifted basis } // NewPolynomial returned a Polynomial from the provided coefficients in the given form. @@ -72,9 +72,8 @@ type Polynomial struct { // shouldn't be mutated. func NewPolynomial(coeffs *[]fr.Element, form Form) *Polynomial { return &Polynomial{ - polynomial: newPolynomial(coeffs, form), - size: len(*coeffs), - blindedSize: len(*coeffs), + polynomial: newPolynomial(coeffs, form), + size: len(*coeffs), } } @@ -85,12 +84,6 @@ func (p *Polynomial) Shift(shift int) *Polynomial { return p } -// BlindedSize returns the the size of the polynomial when it is blinded. By -// default blindedSize=Size, until the polynomial is blinded. -func (p *Polynomial) BlindedSize() int { - return p.blindedSize -} - // Size returns the real size of the polynomial (seen as a vector). // For instance if len(P)=32 but P.Size=8, it means that P has been // extended (e.g. it is evaluated on a larger set) but P is a polynomial @@ -108,69 +101,31 @@ func (p *Polynomial) SetSize(size int) { p.size = size } -// SetBlindedSize sets the blinded size of the polynomial. -func (p *Polynomial) SetBlindedSize(size int) { - p.blindedSize = size -} - -// Blind blinds a polynomial q by adding Q(X)*(X^{n}-1), -// where deg Q = blindingOrder and Q is random, and n is the -// size of q. Sets the result to p and returns it. -// -// blindingOrder is the degree of Q, where the blinding is Q(X)*(X^{n}-1) -// where n is the size of p. The size of p is modified since the underlying -// polynomial is of bigger degree now. The new size is p.Size+1+blindingOrder. -// -// /!\ The code panics if wq is not in canonical, regular layout -func (p *Polynomial) Blind(blindingOrder int) *Polynomial { - // check that p is in canonical basis - if p.Form != canonicalRegular { - panic("the input must be in canonical basis, regular layout") - } - - // we add Q*(x^{n}-1) so the new size is deg(Q)+n+1 - // where n is the size of wq. - newSize := p.size + blindingOrder + 1 - - // Resize p. The size of wq might has already been increased - // (e.g. when the polynomial is evaluated on a larger domain), - // if that's the case we don't resize the polynomial. - p.grow(newSize) - - // blinding: we add Q(X)(X^{n}-1) to P, where deg(Q)=blindingOrder - var r fr.Element - - for i := 0; i <= blindingOrder; i++ { - r.SetRandom() - (*p.coefficients)[i].Sub(&(*p.coefficients)[i], &r) - (*p.coefficients)[i+p.size].Add(&(*p.coefficients)[i+p.size], &r) - } - p.blindedSize = newSize - - return p -} - // Evaluate evaluates p at x. // The code panics if the function is not in canonical form. func (p *Polynomial) Evaluate(x fr.Element) fr.Element { + if p.Basis == LagrangeCoset { + x.Div(&x, &p.coset) + } + if p.shift == 0 { return p.polynomial.evaluate(x) } var g fr.Element - gen, err := fft.Generator(uint64(p.size)) - if err != nil { - panic(err) - } if p.shift <= 5 { + gen, err := fft.Generator(uint64(p.size)) + if err != nil { + panic(err) + } g = smallExp(gen, p.shift) x.Mul(&x, &g) return p.polynomial.evaluate(x) } bs := big.NewInt(int64(p.shift)) - g = *g.Exp(gen, bs) + g = *g.Exp(g, bs) x.Mul(&x, &g) return p.polynomial.evaluate(x) @@ -245,20 +200,60 @@ func (p *polynomial) clone(capacity ...int) *polynomial { func (p *polynomial) evaluate(x fr.Element) fr.Element { var r fr.Element - if p.Basis != Canonical { - panic("p must be in canonical basis") + + evalLagrange := func() { + sizeP := p.coefficients.Len() + w, err := fft.Generator(uint64(sizeP)) + if err != nil { + panic(err) + } + var accw fr.Element + accw.SetOne() + dens := make([]fr.Element, sizeP) // [x-1, x-ω, x-ω², ...] + for i := 0; i < sizeP; i++ { + dens[i].Sub(&x, &accw) + accw.Mul(&accw, &w) + } + invdens := fr.BatchInvert(dens) // [1/(x-1), 1/(x-ω), 1/(x-ω²), ...] + var tmp fr.Element + var one fr.Element + one.SetOne() + tmp.Exp(x, big.NewInt(int64(sizeP))).Sub(&tmp, &one) // xⁿ-1 + var li fr.Element + li.SetUint64(uint64(sizeP)).Inverse(&li).Mul(&li, &tmp) // 1/n * (xⁿ-1) + if p.Layout == Regular { + for i := 0; i < sizeP; i++ { + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[i]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := 0; i < sizeP; i++ { + iRev := bits.Reverse64(uint64(i)) >> nn + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[iRev]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } } - if p.Layout == Regular { - for i := p.coefficients.Len() - 1; i >= 0; i-- { - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + if p.Basis == Canonical { + if p.Layout == Regular { + for i := p.coefficients.Len() - 1; i >= 0; i-- { + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := p.coefficients.Len() - 1; i >= 0; i-- { + iRev := bits.Reverse64(uint64(i)) >> nn + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) + } } } else { - nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) - for i := p.coefficients.Len() - 1; i >= 0; i-- { - iRev := bits.Reverse64(uint64(i)) >> nn - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) - } + evalLagrange() } return r @@ -362,6 +357,11 @@ func (p *polynomial) grow(newSize int) { // ToLagrangeCoset Sets p to q, in LagrangeCoset form and returns it. func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { + cosetTable, err := d.CosetTable() + if err != nil { + panic(err) + } + p.coset.Set(&cosetTable[1]) id := p.Form p.grow(int(d.Cardinality)) switch id { @@ -391,6 +391,7 @@ func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { // WriteTo implements io.WriterTo func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { + // encode coefficients n, err := p.polynomial.coefficients.WriteTo(w) if err != nil { @@ -403,7 +404,6 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { uint32(p.Layout), uint32(p.shift), uint32(p.size), - uint32(p.blindedSize), } for _, v := range data { err = binary.Write(w, binary.BigEndian, v) @@ -412,6 +412,15 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { } n += 4 } + + var buf [fr.Bytes]byte + fr.BigEndian.PutElement(&buf, p.coset) + m, err := w.Write(buf[:]) + n += int64(m) + if err != nil { + return n, err + } + return n, nil } @@ -430,8 +439,8 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { return n, err } - // decode Form.Basis, Form.Layout, shift, size & blindedSize as uint32 - var data [5]uint32 + // decode Form.Basis, Form.Layout, shift as uint32 + var data [4]uint32 var buf [4]byte for i := range data { read, err := io.ReadFull(r, buf[:4]) @@ -446,7 +455,17 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { p.Layout = Layout(data[1]) p.shift = int(data[2]) p.size = int(data[3]) - p.blindedSize = int(data[4]) + + var bufFr [fr.Bytes]byte + read, err := io.ReadFull(r, bufFr[:]) + n += int64(read) + if err != nil { + return n, err + } + p.coset, err = fr.BigEndian.Element(&bufFr) + if err != nil { + return n, err + } return n, nil } diff --git a/ecc/bn254/fr/iop/polynomial_test.go b/ecc/bn254/fr/iop/polynomial_test.go index 8bce78d41b..b716b83912 100644 --- a/ecc/bn254/fr/iop/polynomial_test.go +++ b/ecc/bn254/fr/iop/polynomial_test.go @@ -23,14 +23,14 @@ func TestEvaluation(t *testing.T) { shift := 2 d := fft.NewDomain(uint64(size)) c := randomVector(size) - wp := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) - wps := wp.ShallowClone().Shift(shift) - ref := wp.Clone() + p := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) + ps := p.ShallowClone().Shift(shift) + ref := p.Clone() ref.ToLagrange(d).ToRegular() - // regular layout - a := wp.Evaluate(d.Generator) - b := wps.Evaluate(d.Generator) + // canonical regular + a := p.Evaluate(d.Generator) + b := ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -38,11 +38,11 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } - // bit reversed layout - wp.ToBitReverse() - wps.ToBitReverse() - a = wp.Evaluate(d.Generator) - b = wps.Evaluate(d.Generator) + // canonical bit reversed + p.ToBitReverse() + ps.ToBitReverse() + a = p.Evaluate(d.Generator) + b = ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -50,6 +50,60 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } + // get reference values + var x fr.Element + x.SetRandom() + expectedEval := p.ToRegular().Evaluate(x) + expectedEvalShifted := ps.ToRegular().Evaluate(x) + + // lagrange regular + p.ToLagrange(d).ToRegular() + ps.ToLagrange(d).ToRegular() + plx := p.Evaluate(x) + pslx := ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange bit reverse + p.ToBitReverse() + ps.ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange coset regular + p.ToLagrangeCoset(d).ToRegular() + ps.ToLagrangeCoset(d).ToRegular() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + + // lagrange coset bit reverse + p.ToRegular().ToBitReverse() + ps.ToRegular().ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + } func randomVector(size int) *[]fr.Element { @@ -113,10 +167,9 @@ func TestRoundTrip(t *testing.T) { size := 8 d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - p := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}).ToCanonical(d).ToRegular() - p.Blind(blindingOrder) + p := NewPolynomial(randomVector(size), Form{Basis: Canonical, Layout: Regular}) + p.ToLagrangeCoset(d) // serialize written, err := p.WriteTo(&buf) @@ -134,52 +187,10 @@ func TestRoundTrip(t *testing.T) { assert.Equal(p.Layout, reconstructed.Layout) assert.Equal(p.shift, reconstructed.shift) assert.Equal(p.size, reconstructed.size) - assert.Equal(p.blindedSize, reconstructed.blindedSize) c1, c2 := p.Coefficients(), reconstructed.Coefficients() assert.True(reflect.DeepEqual(c1, c2)) } -func TestBlinding(t *testing.T) { - - size := 8 - d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - - // generate a random polynomial in Lagrange form for the moment - // to check that an error is raised when the polynomial is not - // in canonical form. - wp := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}) - - // checks the blinding is correct: the evaluation of the blinded polynomial - // should be the same as the original on d's domain - wp.Basis = Canonical - wt := wp.Clone() - wt.Blind(blindingOrder) - if wt.coefficients.Len() != blindingOrder+size+1 { - t.Fatal("size of blinded polynomial is incorrect") - } - if wt.blindedSize != size+blindingOrder+1 { - t.Fatal("Size field of blinded polynomial is incorrect") - } - if wt.size != size { - t.Fatal("the size should not have been modified") - } - x := make([]fr.Element, size) - x[0].SetOne() - for i := 1; i < size; i++ { - x[i].Mul(&x[i-1], &d.Generator) - } - var a, b fr.Element - for i := 0; i < size; i++ { - a = wt.Evaluate(x[i]) - b = wp.Evaluate(x[i]) - if a != b { - t.Fatal("polynomial and its blinded version should be equal on V(X^{n}-1)") - } - } - -} - // list of functions to turn a polynomial in Lagrange-regular form // to all different forms in ordered using this encoding: // int(p.Basis)*4 + int(p.Layout)*2 + int(p.Status) diff --git a/ecc/bn254/fr/iop/quotient.go b/ecc/bn254/fr/iop/quotient.go index 09c3d6681f..89056e1d12 100644 --- a/ecc/bn254/fr/iop/quotient.go +++ b/ecc/bn254/fr/iop/quotient.go @@ -35,7 +35,6 @@ func DivideByXMinusOne(a *Polynomial, domains [2]*fft.Domain) (*Polynomial, erro coeffs := make([]fr.Element, a.coefficients.Len()) res := NewPolynomial(&coeffs, Form{Layout: BitReverse, Basis: LagrangeCoset}) res.size = a.size - res.blindedSize = a.blindedSize nn := uint64(64 - bits.TrailingZeros(uint(nbElmts))) parallel.Execute(a.coefficients.Len(), func(start, end int) { diff --git a/ecc/bw6-633/fr/iop/expressions.go b/ecc/bw6-633/fr/iop/expressions.go index ea9678075b..08c735962f 100644 --- a/ecc/bw6-633/fr/iop/expressions.go +++ b/ecc/bw6-633/fr/iop/expressions.go @@ -67,7 +67,6 @@ func Evaluate(f Expression, r []fr.Element, form Form, x ...*Polynomial) (*Polyn res := NewPolynomial(&r, form) res.size = x[0].size - res.blindedSize = x[0].size res.shift = 0 return res, nil diff --git a/ecc/bw6-633/fr/iop/polynomial.go b/ecc/bw6-633/fr/iop/polynomial.go index 0d5c2cf8a2..354007c795 100644 --- a/ecc/bw6-633/fr/iop/polynomial.go +++ b/ecc/bw6-633/fr/iop/polynomial.go @@ -61,9 +61,9 @@ var ( // default blindedSize=Size, until the polynomial is blinded. type Polynomial struct { *polynomial - shift int - size int - blindedSize int + shift int + size int + coset fr.Element // needed for evaluating the polynomial when it is expressed in Lagrange shifted basis } // NewPolynomial returned a Polynomial from the provided coefficients in the given form. @@ -72,9 +72,8 @@ type Polynomial struct { // shouldn't be mutated. func NewPolynomial(coeffs *[]fr.Element, form Form) *Polynomial { return &Polynomial{ - polynomial: newPolynomial(coeffs, form), - size: len(*coeffs), - blindedSize: len(*coeffs), + polynomial: newPolynomial(coeffs, form), + size: len(*coeffs), } } @@ -85,12 +84,6 @@ func (p *Polynomial) Shift(shift int) *Polynomial { return p } -// BlindedSize returns the the size of the polynomial when it is blinded. By -// default blindedSize=Size, until the polynomial is blinded. -func (p *Polynomial) BlindedSize() int { - return p.blindedSize -} - // Size returns the real size of the polynomial (seen as a vector). // For instance if len(P)=32 but P.Size=8, it means that P has been // extended (e.g. it is evaluated on a larger set) but P is a polynomial @@ -108,69 +101,31 @@ func (p *Polynomial) SetSize(size int) { p.size = size } -// SetBlindedSize sets the blinded size of the polynomial. -func (p *Polynomial) SetBlindedSize(size int) { - p.blindedSize = size -} - -// Blind blinds a polynomial q by adding Q(X)*(X^{n}-1), -// where deg Q = blindingOrder and Q is random, and n is the -// size of q. Sets the result to p and returns it. -// -// blindingOrder is the degree of Q, where the blinding is Q(X)*(X^{n}-1) -// where n is the size of p. The size of p is modified since the underlying -// polynomial is of bigger degree now. The new size is p.Size+1+blindingOrder. -// -// /!\ The code panics if wq is not in canonical, regular layout -func (p *Polynomial) Blind(blindingOrder int) *Polynomial { - // check that p is in canonical basis - if p.Form != canonicalRegular { - panic("the input must be in canonical basis, regular layout") - } - - // we add Q*(x^{n}-1) so the new size is deg(Q)+n+1 - // where n is the size of wq. - newSize := p.size + blindingOrder + 1 - - // Resize p. The size of wq might has already been increased - // (e.g. when the polynomial is evaluated on a larger domain), - // if that's the case we don't resize the polynomial. - p.grow(newSize) - - // blinding: we add Q(X)(X^{n}-1) to P, where deg(Q)=blindingOrder - var r fr.Element - - for i := 0; i <= blindingOrder; i++ { - r.SetRandom() - (*p.coefficients)[i].Sub(&(*p.coefficients)[i], &r) - (*p.coefficients)[i+p.size].Add(&(*p.coefficients)[i+p.size], &r) - } - p.blindedSize = newSize - - return p -} - // Evaluate evaluates p at x. // The code panics if the function is not in canonical form. func (p *Polynomial) Evaluate(x fr.Element) fr.Element { + if p.Basis == LagrangeCoset { + x.Div(&x, &p.coset) + } + if p.shift == 0 { return p.polynomial.evaluate(x) } var g fr.Element - gen, err := fft.Generator(uint64(p.size)) - if err != nil { - panic(err) - } if p.shift <= 5 { + gen, err := fft.Generator(uint64(p.size)) + if err != nil { + panic(err) + } g = smallExp(gen, p.shift) x.Mul(&x, &g) return p.polynomial.evaluate(x) } bs := big.NewInt(int64(p.shift)) - g = *g.Exp(gen, bs) + g = *g.Exp(g, bs) x.Mul(&x, &g) return p.polynomial.evaluate(x) @@ -245,20 +200,60 @@ func (p *polynomial) clone(capacity ...int) *polynomial { func (p *polynomial) evaluate(x fr.Element) fr.Element { var r fr.Element - if p.Basis != Canonical { - panic("p must be in canonical basis") + + evalLagrange := func() { + sizeP := p.coefficients.Len() + w, err := fft.Generator(uint64(sizeP)) + if err != nil { + panic(err) + } + var accw fr.Element + accw.SetOne() + dens := make([]fr.Element, sizeP) // [x-1, x-ω, x-ω², ...] + for i := 0; i < sizeP; i++ { + dens[i].Sub(&x, &accw) + accw.Mul(&accw, &w) + } + invdens := fr.BatchInvert(dens) // [1/(x-1), 1/(x-ω), 1/(x-ω²), ...] + var tmp fr.Element + var one fr.Element + one.SetOne() + tmp.Exp(x, big.NewInt(int64(sizeP))).Sub(&tmp, &one) // xⁿ-1 + var li fr.Element + li.SetUint64(uint64(sizeP)).Inverse(&li).Mul(&li, &tmp) // 1/n * (xⁿ-1) + if p.Layout == Regular { + for i := 0; i < sizeP; i++ { + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[i]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := 0; i < sizeP; i++ { + iRev := bits.Reverse64(uint64(i)) >> nn + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[iRev]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } } - if p.Layout == Regular { - for i := p.coefficients.Len() - 1; i >= 0; i-- { - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + if p.Basis == Canonical { + if p.Layout == Regular { + for i := p.coefficients.Len() - 1; i >= 0; i-- { + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := p.coefficients.Len() - 1; i >= 0; i-- { + iRev := bits.Reverse64(uint64(i)) >> nn + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) + } } } else { - nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) - for i := p.coefficients.Len() - 1; i >= 0; i-- { - iRev := bits.Reverse64(uint64(i)) >> nn - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) - } + evalLagrange() } return r @@ -362,6 +357,11 @@ func (p *polynomial) grow(newSize int) { // ToLagrangeCoset Sets p to q, in LagrangeCoset form and returns it. func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { + cosetTable, err := d.CosetTable() + if err != nil { + panic(err) + } + p.coset.Set(&cosetTable[1]) id := p.Form p.grow(int(d.Cardinality)) switch id { @@ -391,6 +391,7 @@ func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { // WriteTo implements io.WriterTo func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { + // encode coefficients n, err := p.polynomial.coefficients.WriteTo(w) if err != nil { @@ -403,7 +404,6 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { uint32(p.Layout), uint32(p.shift), uint32(p.size), - uint32(p.blindedSize), } for _, v := range data { err = binary.Write(w, binary.BigEndian, v) @@ -412,6 +412,15 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { } n += 4 } + + var buf [fr.Bytes]byte + fr.BigEndian.PutElement(&buf, p.coset) + m, err := w.Write(buf[:]) + n += int64(m) + if err != nil { + return n, err + } + return n, nil } @@ -430,8 +439,8 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { return n, err } - // decode Form.Basis, Form.Layout, shift, size & blindedSize as uint32 - var data [5]uint32 + // decode Form.Basis, Form.Layout, shift as uint32 + var data [4]uint32 var buf [4]byte for i := range data { read, err := io.ReadFull(r, buf[:4]) @@ -446,7 +455,17 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { p.Layout = Layout(data[1]) p.shift = int(data[2]) p.size = int(data[3]) - p.blindedSize = int(data[4]) + + var bufFr [fr.Bytes]byte + read, err := io.ReadFull(r, bufFr[:]) + n += int64(read) + if err != nil { + return n, err + } + p.coset, err = fr.BigEndian.Element(&bufFr) + if err != nil { + return n, err + } return n, nil } diff --git a/ecc/bw6-633/fr/iop/polynomial_test.go b/ecc/bw6-633/fr/iop/polynomial_test.go index 9ac04bf6f9..584ec9c043 100644 --- a/ecc/bw6-633/fr/iop/polynomial_test.go +++ b/ecc/bw6-633/fr/iop/polynomial_test.go @@ -23,14 +23,14 @@ func TestEvaluation(t *testing.T) { shift := 2 d := fft.NewDomain(uint64(size)) c := randomVector(size) - wp := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) - wps := wp.ShallowClone().Shift(shift) - ref := wp.Clone() + p := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) + ps := p.ShallowClone().Shift(shift) + ref := p.Clone() ref.ToLagrange(d).ToRegular() - // regular layout - a := wp.Evaluate(d.Generator) - b := wps.Evaluate(d.Generator) + // canonical regular + a := p.Evaluate(d.Generator) + b := ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -38,11 +38,11 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } - // bit reversed layout - wp.ToBitReverse() - wps.ToBitReverse() - a = wp.Evaluate(d.Generator) - b = wps.Evaluate(d.Generator) + // canonical bit reversed + p.ToBitReverse() + ps.ToBitReverse() + a = p.Evaluate(d.Generator) + b = ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -50,6 +50,60 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } + // get reference values + var x fr.Element + x.SetRandom() + expectedEval := p.ToRegular().Evaluate(x) + expectedEvalShifted := ps.ToRegular().Evaluate(x) + + // lagrange regular + p.ToLagrange(d).ToRegular() + ps.ToLagrange(d).ToRegular() + plx := p.Evaluate(x) + pslx := ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange bit reverse + p.ToBitReverse() + ps.ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange coset regular + p.ToLagrangeCoset(d).ToRegular() + ps.ToLagrangeCoset(d).ToRegular() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + + // lagrange coset bit reverse + p.ToRegular().ToBitReverse() + ps.ToRegular().ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + } func randomVector(size int) *[]fr.Element { @@ -113,10 +167,9 @@ func TestRoundTrip(t *testing.T) { size := 8 d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - p := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}).ToCanonical(d).ToRegular() - p.Blind(blindingOrder) + p := NewPolynomial(randomVector(size), Form{Basis: Canonical, Layout: Regular}) + p.ToLagrangeCoset(d) // serialize written, err := p.WriteTo(&buf) @@ -134,52 +187,10 @@ func TestRoundTrip(t *testing.T) { assert.Equal(p.Layout, reconstructed.Layout) assert.Equal(p.shift, reconstructed.shift) assert.Equal(p.size, reconstructed.size) - assert.Equal(p.blindedSize, reconstructed.blindedSize) c1, c2 := p.Coefficients(), reconstructed.Coefficients() assert.True(reflect.DeepEqual(c1, c2)) } -func TestBlinding(t *testing.T) { - - size := 8 - d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - - // generate a random polynomial in Lagrange form for the moment - // to check that an error is raised when the polynomial is not - // in canonical form. - wp := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}) - - // checks the blinding is correct: the evaluation of the blinded polynomial - // should be the same as the original on d's domain - wp.Basis = Canonical - wt := wp.Clone() - wt.Blind(blindingOrder) - if wt.coefficients.Len() != blindingOrder+size+1 { - t.Fatal("size of blinded polynomial is incorrect") - } - if wt.blindedSize != size+blindingOrder+1 { - t.Fatal("Size field of blinded polynomial is incorrect") - } - if wt.size != size { - t.Fatal("the size should not have been modified") - } - x := make([]fr.Element, size) - x[0].SetOne() - for i := 1; i < size; i++ { - x[i].Mul(&x[i-1], &d.Generator) - } - var a, b fr.Element - for i := 0; i < size; i++ { - a = wt.Evaluate(x[i]) - b = wp.Evaluate(x[i]) - if a != b { - t.Fatal("polynomial and its blinded version should be equal on V(X^{n}-1)") - } - } - -} - // list of functions to turn a polynomial in Lagrange-regular form // to all different forms in ordered using this encoding: // int(p.Basis)*4 + int(p.Layout)*2 + int(p.Status) diff --git a/ecc/bw6-633/fr/iop/quotient.go b/ecc/bw6-633/fr/iop/quotient.go index 4420c026b2..7344abd32b 100644 --- a/ecc/bw6-633/fr/iop/quotient.go +++ b/ecc/bw6-633/fr/iop/quotient.go @@ -35,7 +35,6 @@ func DivideByXMinusOne(a *Polynomial, domains [2]*fft.Domain) (*Polynomial, erro coeffs := make([]fr.Element, a.coefficients.Len()) res := NewPolynomial(&coeffs, Form{Layout: BitReverse, Basis: LagrangeCoset}) res.size = a.size - res.blindedSize = a.blindedSize nn := uint64(64 - bits.TrailingZeros(uint(nbElmts))) parallel.Execute(a.coefficients.Len(), func(start, end int) { diff --git a/ecc/bw6-761/fr/iop/expressions.go b/ecc/bw6-761/fr/iop/expressions.go index ac0bd11bc8..3941e95266 100644 --- a/ecc/bw6-761/fr/iop/expressions.go +++ b/ecc/bw6-761/fr/iop/expressions.go @@ -67,7 +67,6 @@ func Evaluate(f Expression, r []fr.Element, form Form, x ...*Polynomial) (*Polyn res := NewPolynomial(&r, form) res.size = x[0].size - res.blindedSize = x[0].size res.shift = 0 return res, nil diff --git a/ecc/bw6-761/fr/iop/polynomial.go b/ecc/bw6-761/fr/iop/polynomial.go index 210e7a6b10..94fbb2d751 100644 --- a/ecc/bw6-761/fr/iop/polynomial.go +++ b/ecc/bw6-761/fr/iop/polynomial.go @@ -61,9 +61,9 @@ var ( // default blindedSize=Size, until the polynomial is blinded. type Polynomial struct { *polynomial - shift int - size int - blindedSize int + shift int + size int + coset fr.Element // needed for evaluating the polynomial when it is expressed in Lagrange shifted basis } // NewPolynomial returned a Polynomial from the provided coefficients in the given form. @@ -72,9 +72,8 @@ type Polynomial struct { // shouldn't be mutated. func NewPolynomial(coeffs *[]fr.Element, form Form) *Polynomial { return &Polynomial{ - polynomial: newPolynomial(coeffs, form), - size: len(*coeffs), - blindedSize: len(*coeffs), + polynomial: newPolynomial(coeffs, form), + size: len(*coeffs), } } @@ -85,12 +84,6 @@ func (p *Polynomial) Shift(shift int) *Polynomial { return p } -// BlindedSize returns the the size of the polynomial when it is blinded. By -// default blindedSize=Size, until the polynomial is blinded. -func (p *Polynomial) BlindedSize() int { - return p.blindedSize -} - // Size returns the real size of the polynomial (seen as a vector). // For instance if len(P)=32 but P.Size=8, it means that P has been // extended (e.g. it is evaluated on a larger set) but P is a polynomial @@ -108,69 +101,31 @@ func (p *Polynomial) SetSize(size int) { p.size = size } -// SetBlindedSize sets the blinded size of the polynomial. -func (p *Polynomial) SetBlindedSize(size int) { - p.blindedSize = size -} - -// Blind blinds a polynomial q by adding Q(X)*(X^{n}-1), -// where deg Q = blindingOrder and Q is random, and n is the -// size of q. Sets the result to p and returns it. -// -// blindingOrder is the degree of Q, where the blinding is Q(X)*(X^{n}-1) -// where n is the size of p. The size of p is modified since the underlying -// polynomial is of bigger degree now. The new size is p.Size+1+blindingOrder. -// -// /!\ The code panics if wq is not in canonical, regular layout -func (p *Polynomial) Blind(blindingOrder int) *Polynomial { - // check that p is in canonical basis - if p.Form != canonicalRegular { - panic("the input must be in canonical basis, regular layout") - } - - // we add Q*(x^{n}-1) so the new size is deg(Q)+n+1 - // where n is the size of wq. - newSize := p.size + blindingOrder + 1 - - // Resize p. The size of wq might has already been increased - // (e.g. when the polynomial is evaluated on a larger domain), - // if that's the case we don't resize the polynomial. - p.grow(newSize) - - // blinding: we add Q(X)(X^{n}-1) to P, where deg(Q)=blindingOrder - var r fr.Element - - for i := 0; i <= blindingOrder; i++ { - r.SetRandom() - (*p.coefficients)[i].Sub(&(*p.coefficients)[i], &r) - (*p.coefficients)[i+p.size].Add(&(*p.coefficients)[i+p.size], &r) - } - p.blindedSize = newSize - - return p -} - // Evaluate evaluates p at x. // The code panics if the function is not in canonical form. func (p *Polynomial) Evaluate(x fr.Element) fr.Element { + if p.Basis == LagrangeCoset { + x.Div(&x, &p.coset) + } + if p.shift == 0 { return p.polynomial.evaluate(x) } var g fr.Element - gen, err := fft.Generator(uint64(p.size)) - if err != nil { - panic(err) - } if p.shift <= 5 { + gen, err := fft.Generator(uint64(p.size)) + if err != nil { + panic(err) + } g = smallExp(gen, p.shift) x.Mul(&x, &g) return p.polynomial.evaluate(x) } bs := big.NewInt(int64(p.shift)) - g = *g.Exp(gen, bs) + g = *g.Exp(g, bs) x.Mul(&x, &g) return p.polynomial.evaluate(x) @@ -245,20 +200,60 @@ func (p *polynomial) clone(capacity ...int) *polynomial { func (p *polynomial) evaluate(x fr.Element) fr.Element { var r fr.Element - if p.Basis != Canonical { - panic("p must be in canonical basis") + + evalLagrange := func() { + sizeP := p.coefficients.Len() + w, err := fft.Generator(uint64(sizeP)) + if err != nil { + panic(err) + } + var accw fr.Element + accw.SetOne() + dens := make([]fr.Element, sizeP) // [x-1, x-ω, x-ω², ...] + for i := 0; i < sizeP; i++ { + dens[i].Sub(&x, &accw) + accw.Mul(&accw, &w) + } + invdens := fr.BatchInvert(dens) // [1/(x-1), 1/(x-ω), 1/(x-ω²), ...] + var tmp fr.Element + var one fr.Element + one.SetOne() + tmp.Exp(x, big.NewInt(int64(sizeP))).Sub(&tmp, &one) // xⁿ-1 + var li fr.Element + li.SetUint64(uint64(sizeP)).Inverse(&li).Mul(&li, &tmp) // 1/n * (xⁿ-1) + if p.Layout == Regular { + for i := 0; i < sizeP; i++ { + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[i]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := 0; i < sizeP; i++ { + iRev := bits.Reverse64(uint64(i)) >> nn + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[iRev]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } } - if p.Layout == Regular { - for i := p.coefficients.Len() - 1; i >= 0; i-- { - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + if p.Basis == Canonical { + if p.Layout == Regular { + for i := p.coefficients.Len() - 1; i >= 0; i-- { + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := p.coefficients.Len() - 1; i >= 0; i-- { + iRev := bits.Reverse64(uint64(i)) >> nn + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) + } } } else { - nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) - for i := p.coefficients.Len() - 1; i >= 0; i-- { - iRev := bits.Reverse64(uint64(i)) >> nn - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) - } + evalLagrange() } return r @@ -362,6 +357,11 @@ func (p *polynomial) grow(newSize int) { // ToLagrangeCoset Sets p to q, in LagrangeCoset form and returns it. func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { + cosetTable, err := d.CosetTable() + if err != nil { + panic(err) + } + p.coset.Set(&cosetTable[1]) id := p.Form p.grow(int(d.Cardinality)) switch id { @@ -391,6 +391,7 @@ func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { // WriteTo implements io.WriterTo func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { + // encode coefficients n, err := p.polynomial.coefficients.WriteTo(w) if err != nil { @@ -403,7 +404,6 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { uint32(p.Layout), uint32(p.shift), uint32(p.size), - uint32(p.blindedSize), } for _, v := range data { err = binary.Write(w, binary.BigEndian, v) @@ -412,6 +412,15 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { } n += 4 } + + var buf [fr.Bytes]byte + fr.BigEndian.PutElement(&buf, p.coset) + m, err := w.Write(buf[:]) + n += int64(m) + if err != nil { + return n, err + } + return n, nil } @@ -430,8 +439,8 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { return n, err } - // decode Form.Basis, Form.Layout, shift, size & blindedSize as uint32 - var data [5]uint32 + // decode Form.Basis, Form.Layout, shift as uint32 + var data [4]uint32 var buf [4]byte for i := range data { read, err := io.ReadFull(r, buf[:4]) @@ -446,7 +455,17 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { p.Layout = Layout(data[1]) p.shift = int(data[2]) p.size = int(data[3]) - p.blindedSize = int(data[4]) + + var bufFr [fr.Bytes]byte + read, err := io.ReadFull(r, bufFr[:]) + n += int64(read) + if err != nil { + return n, err + } + p.coset, err = fr.BigEndian.Element(&bufFr) + if err != nil { + return n, err + } return n, nil } diff --git a/ecc/bw6-761/fr/iop/polynomial_test.go b/ecc/bw6-761/fr/iop/polynomial_test.go index cee823decb..b04551d9a6 100644 --- a/ecc/bw6-761/fr/iop/polynomial_test.go +++ b/ecc/bw6-761/fr/iop/polynomial_test.go @@ -23,14 +23,14 @@ func TestEvaluation(t *testing.T) { shift := 2 d := fft.NewDomain(uint64(size)) c := randomVector(size) - wp := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) - wps := wp.ShallowClone().Shift(shift) - ref := wp.Clone() + p := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) + ps := p.ShallowClone().Shift(shift) + ref := p.Clone() ref.ToLagrange(d).ToRegular() - // regular layout - a := wp.Evaluate(d.Generator) - b := wps.Evaluate(d.Generator) + // canonical regular + a := p.Evaluate(d.Generator) + b := ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -38,11 +38,11 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } - // bit reversed layout - wp.ToBitReverse() - wps.ToBitReverse() - a = wp.Evaluate(d.Generator) - b = wps.Evaluate(d.Generator) + // canonical bit reversed + p.ToBitReverse() + ps.ToBitReverse() + a = p.Evaluate(d.Generator) + b = ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -50,6 +50,60 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } + // get reference values + var x fr.Element + x.SetRandom() + expectedEval := p.ToRegular().Evaluate(x) + expectedEvalShifted := ps.ToRegular().Evaluate(x) + + // lagrange regular + p.ToLagrange(d).ToRegular() + ps.ToLagrange(d).ToRegular() + plx := p.Evaluate(x) + pslx := ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange bit reverse + p.ToBitReverse() + ps.ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange coset regular + p.ToLagrangeCoset(d).ToRegular() + ps.ToLagrangeCoset(d).ToRegular() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + + // lagrange coset bit reverse + p.ToRegular().ToBitReverse() + ps.ToRegular().ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + } func randomVector(size int) *[]fr.Element { @@ -113,10 +167,9 @@ func TestRoundTrip(t *testing.T) { size := 8 d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - p := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}).ToCanonical(d).ToRegular() - p.Blind(blindingOrder) + p := NewPolynomial(randomVector(size), Form{Basis: Canonical, Layout: Regular}) + p.ToLagrangeCoset(d) // serialize written, err := p.WriteTo(&buf) @@ -134,52 +187,10 @@ func TestRoundTrip(t *testing.T) { assert.Equal(p.Layout, reconstructed.Layout) assert.Equal(p.shift, reconstructed.shift) assert.Equal(p.size, reconstructed.size) - assert.Equal(p.blindedSize, reconstructed.blindedSize) c1, c2 := p.Coefficients(), reconstructed.Coefficients() assert.True(reflect.DeepEqual(c1, c2)) } -func TestBlinding(t *testing.T) { - - size := 8 - d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - - // generate a random polynomial in Lagrange form for the moment - // to check that an error is raised when the polynomial is not - // in canonical form. - wp := NewPolynomial(randomVector(size), Form{Basis: Lagrange, Layout: Regular}) - - // checks the blinding is correct: the evaluation of the blinded polynomial - // should be the same as the original on d's domain - wp.Basis = Canonical - wt := wp.Clone() - wt.Blind(blindingOrder) - if wt.coefficients.Len() != blindingOrder+size+1 { - t.Fatal("size of blinded polynomial is incorrect") - } - if wt.blindedSize != size+blindingOrder+1 { - t.Fatal("Size field of blinded polynomial is incorrect") - } - if wt.size != size { - t.Fatal("the size should not have been modified") - } - x := make([]fr.Element, size) - x[0].SetOne() - for i := 1; i < size; i++ { - x[i].Mul(&x[i-1], &d.Generator) - } - var a, b fr.Element - for i := 0; i < size; i++ { - a = wt.Evaluate(x[i]) - b = wp.Evaluate(x[i]) - if a != b { - t.Fatal("polynomial and its blinded version should be equal on V(X^{n}-1)") - } - } - -} - // list of functions to turn a polynomial in Lagrange-regular form // to all different forms in ordered using this encoding: // int(p.Basis)*4 + int(p.Layout)*2 + int(p.Status) diff --git a/ecc/bw6-761/fr/iop/quotient.go b/ecc/bw6-761/fr/iop/quotient.go index 2c3f93e5e1..6329b85400 100644 --- a/ecc/bw6-761/fr/iop/quotient.go +++ b/ecc/bw6-761/fr/iop/quotient.go @@ -35,7 +35,6 @@ func DivideByXMinusOne(a *Polynomial, domains [2]*fft.Domain) (*Polynomial, erro coeffs := make([]fr.Element, a.coefficients.Len()) res := NewPolynomial(&coeffs, Form{Layout: BitReverse, Basis: LagrangeCoset}) res.size = a.size - res.blindedSize = a.blindedSize nn := uint64(64 - bits.TrailingZeros(uint(nbElmts))) parallel.Execute(a.coefficients.Len(), func(start, end int) { diff --git a/internal/generator/iop/template/expressions.go.tmpl b/internal/generator/iop/template/expressions.go.tmpl index ddf2330242..2692768753 100644 --- a/internal/generator/iop/template/expressions.go.tmpl +++ b/internal/generator/iop/template/expressions.go.tmpl @@ -60,8 +60,7 @@ func Evaluate(f Expression, r []fr.Element, form Form, x ...*Polynomial) (*Polyn res := NewPolynomial(&r, form) res.size = x[0].size - res.blindedSize = x[0].size res.shift = 0 return res, nil -} +} \ No newline at end of file diff --git a/internal/generator/iop/template/polynomial.go.tmpl b/internal/generator/iop/template/polynomial.go.tmpl index 9f5cb333e6..50a446e092 100644 --- a/internal/generator/iop/template/polynomial.go.tmpl +++ b/internal/generator/iop/template/polynomial.go.tmpl @@ -54,9 +54,9 @@ var ( // default blindedSize=Size, until the polynomial is blinded. type Polynomial struct { *polynomial - shift int - size int - blindedSize int + shift int + size int + coset fr.Element // needed for evaluating the polynomial when it is expressed in Lagrange shifted basis } // NewPolynomial returned a Polynomial from the provided coefficients in the given form. @@ -65,9 +65,8 @@ type Polynomial struct { // shouldn't be mutated. func NewPolynomial(coeffs *[]fr.Element, form Form) *Polynomial { return &Polynomial{ - polynomial: newPolynomial(coeffs, form), - size: len(*coeffs), - blindedSize: len(*coeffs), + polynomial: newPolynomial(coeffs, form), + size: len(*coeffs), } } @@ -78,12 +77,6 @@ func (p *Polynomial) Shift(shift int) *Polynomial { return p } -// BlindedSize returns the the size of the polynomial when it is blinded. By -// default blindedSize=Size, until the polynomial is blinded. -func (p *Polynomial) BlindedSize() int { - return p.blindedSize -} - // Size returns the real size of the polynomial (seen as a vector). // For instance if len(P)=32 but P.Size=8, it means that P has been // extended (e.g. it is evaluated on a larger set) but P is a polynomial @@ -101,69 +94,31 @@ func (p *Polynomial) SetSize(size int) { p.size = size } -// SetBlindedSize sets the blinded size of the polynomial. -func (p *Polynomial) SetBlindedSize(size int) { - p.blindedSize = size -} - -// Blind blinds a polynomial q by adding Q(X)*(X^{n}-1), -// where deg Q = blindingOrder and Q is random, and n is the -// size of q. Sets the result to p and returns it. -// -// blindingOrder is the degree of Q, where the blinding is Q(X)*(X^{n}-1) -// where n is the size of p. The size of p is modified since the underlying -// polynomial is of bigger degree now. The new size is p.Size+1+blindingOrder. -// -// /!\ The code panics if wq is not in canonical, regular layout -func (p *Polynomial) Blind(blindingOrder int) *Polynomial { - // check that p is in canonical basis - if p.Form != canonicalRegular { - panic("the input must be in canonical basis, regular layout") - } - - // we add Q*(x^{n}-1) so the new size is deg(Q)+n+1 - // where n is the size of wq. - newSize := p.size + blindingOrder + 1 - - // Resize p. The size of wq might has already been increased - // (e.g. when the polynomial is evaluated on a larger domain), - // if that's the case we don't resize the polynomial. - p.grow(newSize) - - // blinding: we add Q(X)(X^{n}-1) to P, where deg(Q)=blindingOrder - var r fr.Element - - for i := 0; i <= blindingOrder; i++ { - r.SetRandom() - (*p.coefficients)[i].Sub(&(*p.coefficients)[i], &r) - (*p.coefficients)[i+p.size].Add(&(*p.coefficients)[i+p.size], &r) - } - p.blindedSize = newSize - - return p -} - // Evaluate evaluates p at x. // The code panics if the function is not in canonical form. func (p *Polynomial) Evaluate(x fr.Element) fr.Element { + if p.Basis == LagrangeCoset { + x.Div(&x, &p.coset) + } + if p.shift == 0 { return p.polynomial.evaluate(x) } var g fr.Element - gen, err := fft.Generator(uint64(p.size)) - if err != nil { - panic(err) - } if p.shift <= 5 { + gen, err := fft.Generator(uint64(p.size)) + if err != nil { + panic(err) + } g = smallExp(gen, p.shift) x.Mul(&x, &g) return p.polynomial.evaluate(x) } bs := big.NewInt(int64(p.shift)) - g = *g.Exp(gen, bs) + g = *g.Exp(g, bs) x.Mul(&x, &g) return p.polynomial.evaluate(x) @@ -238,20 +193,60 @@ func (p *polynomial) clone(capacity ...int) *polynomial { func (p *polynomial) evaluate(x fr.Element) fr.Element { var r fr.Element - if p.Basis != Canonical { - panic("p must be in canonical basis") + + evalLagrange := func() { + sizeP := p.coefficients.Len() + w, err := fft.Generator(uint64(sizeP)) + if err != nil { + panic(err) + } + var accw fr.Element + accw.SetOne() + dens := make([]fr.Element, sizeP) // [x-1, x-ω, x-ω², ...] + for i := 0; i < sizeP; i++ { + dens[i].Sub(&x, &accw) + accw.Mul(&accw, &w) + } + invdens := fr.BatchInvert(dens) // [1/(x-1), 1/(x-ω), 1/(x-ω²), ...] + var tmp fr.Element + var one fr.Element + one.SetOne() + tmp.Exp(x, big.NewInt(int64(sizeP))).Sub(&tmp, &one) // xⁿ-1 + var li fr.Element + li.SetUint64(uint64(sizeP)).Inverse(&li).Mul(&li, &tmp) // 1/n * (xⁿ-1) + if p.Layout == Regular { + for i := 0; i < sizeP; i++ { + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[i]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := 0; i < sizeP; i++ { + iRev := bits.Reverse64(uint64(i)) >> nn + li.Mul(&li, &invdens[i]) // li <- li*ω/(x-ωⁱ) + tmp.Mul(&li, &(*p.coefficients)[iRev]) + r.Add(&r, &tmp) + li.Mul(&li, &dens[i]).Mul(&li, &w) // li <- li*ω*(x-ωⁱ) + } + } } - if p.Layout == Regular { - for i := p.coefficients.Len() - 1; i >= 0; i-- { - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + if p.Basis == Canonical { + if p.Layout == Regular { + for i := p.coefficients.Len() - 1; i >= 0; i-- { + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[i]) + } + } else { + nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) + for i := p.coefficients.Len() - 1; i >= 0; i-- { + iRev := bits.Reverse64(uint64(i)) >> nn + r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) + } } } else { - nn := uint64(64 - bits.TrailingZeros(uint(p.coefficients.Len()))) - for i := p.coefficients.Len() - 1; i >= 0; i-- { - iRev := bits.Reverse64(uint64(i)) >> nn - r.Mul(&r, &x).Add(&r, &(*p.coefficients)[iRev]) - } + evalLagrange() } return r @@ -355,6 +350,11 @@ func (p *polynomial) grow(newSize int) { // ToLagrangeCoset Sets p to q, in LagrangeCoset form and returns it. func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { + cosetTable, err := d.CosetTable() + if err != nil { + panic(err) + } + p.coset.Set(&cosetTable[1]) id := p.Form p.grow(int(d.Cardinality)) switch id { @@ -384,6 +384,7 @@ func (p *Polynomial) ToLagrangeCoset(d *fft.Domain) *Polynomial { // WriteTo implements io.WriterTo func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { + // encode coefficients n, err := p.polynomial.coefficients.WriteTo(w) if err != nil { @@ -396,7 +397,6 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { uint32(p.Layout), uint32(p.shift), uint32(p.size), - uint32(p.blindedSize), } for _, v := range data { err = binary.Write(w, binary.BigEndian, v) @@ -405,6 +405,15 @@ func (p *Polynomial) WriteTo(w io.Writer) (int64, error) { } n += 4 } + + var buf [fr.Bytes]byte + fr.BigEndian.PutElement(&buf, p.coset) + m, err := w.Write(buf[:]) + n += int64(m) + if err != nil { + return n, err + } + return n, nil } @@ -423,8 +432,8 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { return n, err } - // decode Form.Basis, Form.Layout, shift, size & blindedSize as uint32 - var data [5]uint32 + // decode Form.Basis, Form.Layout, shift as uint32 + var data [4]uint32 var buf [4]byte for i := range data { read, err := io.ReadFull(r, buf[:4]) @@ -439,7 +448,17 @@ func (p *Polynomial) ReadFrom(r io.Reader) (int64, error) { p.Layout = Layout(data[1]) p.shift = int(data[2]) p.size = int(data[3]) - p.blindedSize = int(data[4]) + + var bufFr [fr.Bytes]byte + read, err := io.ReadFull(r, bufFr[:]) + n += int64(read) + if err != nil { + return n, err + } + p.coset, err = fr.BigEndian.Element(&bufFr) + if err != nil { + return n, err + } return n, nil } \ No newline at end of file diff --git a/internal/generator/iop/template/polynomial.test.go.tmpl b/internal/generator/iop/template/polynomial.test.go.tmpl index 0bf76239be..11cf1cf1e1 100644 --- a/internal/generator/iop/template/polynomial.test.go.tmpl +++ b/internal/generator/iop/template/polynomial.test.go.tmpl @@ -16,14 +16,14 @@ func TestEvaluation(t *testing.T) { shift := 2 d := fft.NewDomain(uint64(size)) c := randomVector(size) - wp := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) - wps := wp.ShallowClone().Shift(shift) - ref := wp.Clone() + p := NewPolynomial(c, Form{Basis: Canonical, Layout: Regular}) + ps := p.ShallowClone().Shift(shift) + ref := p.Clone() ref.ToLagrange(d).ToRegular() - // regular layout - a := wp.Evaluate(d.Generator) - b := wps.Evaluate(d.Generator) + // canonical regular + a := p.Evaluate(d.Generator) + b := ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -31,11 +31,11 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } - // bit reversed layout - wp.ToBitReverse() - wps.ToBitReverse() - a = wp.Evaluate(d.Generator) - b = wps.Evaluate(d.Generator) + // canonical bit reversed + p.ToBitReverse() + ps.ToBitReverse() + a = p.Evaluate(d.Generator) + b = ps.Evaluate(d.Generator) if !a.Equal(&ref.Coefficients()[1]) { t.Fatal("error evaluation") } @@ -43,6 +43,60 @@ func TestEvaluation(t *testing.T) { t.Fatal("error evaluation shifted") } + // get reference values + var x fr.Element + x.SetRandom() + expectedEval := p.ToRegular().Evaluate(x) + expectedEvalShifted := ps.ToRegular().Evaluate(x) + + // lagrange regular + p.ToLagrange(d).ToRegular() + ps.ToLagrange(d).ToRegular() + plx := p.Evaluate(x) + pslx := ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange bit reverse + p.ToBitReverse() + ps.ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange shifted") + } + + // lagrange coset regular + p.ToLagrangeCoset(d).ToRegular() + ps.ToLagrangeCoset(d).ToRegular() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + + // lagrange coset bit reverse + p.ToRegular().ToBitReverse() + ps.ToRegular().ToBitReverse() + plx = p.Evaluate(x) + pslx = ps.Evaluate(x) + if !plx.Equal(&expectedEval) { + t.Fatal("error evaluation lagrange coset") + } + if !pslx.Equal(&expectedEvalShifted) { + t.Fatal("error evaluation lagrange coset shifted") + } + } func randomVector(size int) *[]fr.Element { @@ -100,17 +154,15 @@ func TestGetCoeff(t *testing.T) { } - func TestRoundTrip(t *testing.T) { assert := require.New(t) var buf bytes.Buffer size := 8 d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - p := NewPolynomial(randomVector(size), Form{Basis:Lagrange, Layout: Regular}).ToCanonical(d).ToRegular() - p.Blind(blindingOrder) + p := NewPolynomial(randomVector(size), Form{Basis: Canonical, Layout: Regular}) + p.ToLagrangeCoset(d) // serialize written, err := p.WriteTo(&buf) @@ -123,57 +175,15 @@ func TestRoundTrip(t *testing.T) { assert.Equal(read, written, "number of bytes written != number of bytes read") - // compare + // compare assert.Equal(p.Basis, reconstructed.Basis) assert.Equal(p.Layout, reconstructed.Layout) assert.Equal(p.shift, reconstructed.shift) assert.Equal(p.size, reconstructed.size) - assert.Equal(p.blindedSize, reconstructed.blindedSize) c1, c2 := p.Coefficients(), reconstructed.Coefficients() assert.True(reflect.DeepEqual(c1, c2)) } -func TestBlinding(t *testing.T) { - - size := 8 - d := fft.NewDomain(uint64(8)) - blindingOrder := 2 - - // generate a random polynomial in Lagrange form for the moment - // to check that an error is raised when the polynomial is not - // in canonical form. - wp := NewPolynomial(randomVector(size), Form{Basis:Lagrange, Layout: Regular}) - - // checks the blinding is correct: the evaluation of the blinded polynomial - // should be the same as the original on d's domain - wp.Basis = Canonical - wt := wp.Clone() - wt.Blind(blindingOrder) - if wt.coefficients.Len() != blindingOrder+size+1 { - t.Fatal("size of blinded polynomial is incorrect") - } - if wt.blindedSize != size+blindingOrder+1 { - t.Fatal("Size field of blinded polynomial is incorrect") - } - if wt.size != size { - t.Fatal("the size should not have been modified") - } - x := make([]fr.Element, size) - x[0].SetOne() - for i := 1; i < size; i++ { - x[i].Mul(&x[i-1], &d.Generator) - } - var a, b fr.Element - for i := 0; i < size; i++ { - a = wt.Evaluate(x[i]) - b = wp.Evaluate(x[i]) - if a != b { - t.Fatal("polynomial and its blinded version should be equal on V(X^{n}-1)") - } - } - -} - // list of functions to turn a polynomial in Lagrange-regular form // to all different forms in ordered using this encoding: // int(p.Basis)*4 + int(p.Layout)*2 + int(p.Status) @@ -263,8 +273,8 @@ func cmpCoefficents(p, q *fr.Vector) bool { return false } for i := 0; i < p.Len(); i++ { - if !((*p)[i].Equal(&(*q)[i])){ - return false + if !((*p)[i].Equal(&(*q)[i])) { + return false } } return true @@ -709,4 +719,4 @@ func TestPutInLagrangeCosetForm(t *testing.T) { } } -} +} \ No newline at end of file diff --git a/internal/generator/iop/template/quotient.go.tmpl b/internal/generator/iop/template/quotient.go.tmpl index aacb3283e8..5943283f22 100644 --- a/internal/generator/iop/template/quotient.go.tmpl +++ b/internal/generator/iop/template/quotient.go.tmpl @@ -28,10 +28,9 @@ func DivideByXMinusOne(a *Polynomial, domains [2]*fft.Domain) (*Polynomial, erro coeffs := make([]fr.Element, a.coefficients.Len()) res := NewPolynomial(&coeffs, Form{Layout: BitReverse, Basis: LagrangeCoset}) res.size = a.size - res.blindedSize = a.blindedSize nn := uint64(64 - bits.TrailingZeros(uint(nbElmts))) - parallel.Execute(a.coefficients.Len(), func(start, end int){ + parallel.Execute(a.coefficients.Len(), func(start, end int) { for i := start; i < end; i++ { iRev := bits.Reverse64(uint64(i)) >> nn c := a.GetCoeff(i) @@ -39,7 +38,6 @@ func DivideByXMinusOne(a *Polynomial, domains [2]*fft.Domain) (*Polynomial, erro Mul(&c, &xnMinusOneInverseLagrangeCoset[i%rho]) } }) - res.ToCanonical(domains[1]) @@ -71,4 +69,4 @@ func evaluateXnMinusOneDomainBigCoset(domains [2]*fft.Domain) []fr.Element { res = fr.BatchInvert(res) return res -} +} \ No newline at end of file