github.com/consensys/gnark-crypto@v0.14.0/internal/generator/iop/template/ratios.go.tmpl (about)

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