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

     1  import (
     2  	"crypto/sha256"
     3  	"errors"
     4  	"math/big"
     5  	"math/bits"
     6  	"sort"
     7  
     8  	"github.com/consensys/gnark-crypto/ecc/{{ .Name }}/fr"
     9  	"github.com/consensys/gnark-crypto/ecc/{{ .Name }}/fr/fft"
    10  	"github.com/consensys/gnark-crypto/ecc/{{ .Name }}/kzg"
    11  	fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir"
    12  )
    13  
    14  var (
    15  	ErrNotInTable          = errors.New("some value in the vector is not in the lookup table")
    16  	ErrPlookupVerification = errors.New("plookup verification failed")
    17  	ErrGenerator           = errors.New("wrong generator")
    18  )
    19  
    20  
    21  // Proof Plookup proof, containing opening proofs
    22  type ProofLookupVector struct {
    23  
    24  	// size of the system
    25  	size uint64
    26  
    27  	// generator of the fft domain, used for shifting the evaluation point
    28  	g fr.Element
    29  
    30  	// Commitments to h1, h2, t, z, f, h
    31  	h1, h2, t, z, f, h kzg.Digest
    32  
    33  	// Batch opening proof of h1, h2, z, t
    34  	BatchedProof kzg.BatchOpeningProof
    35  
    36  	// Batch opening proof of h1, h2, z shifted by g
    37  	BatchedProofShifted kzg.BatchOpeningProof
    38  }
    39  
    40  // evaluateAccumulationPolynomial computes Z, in Lagrange basis. Z is the accumulation of the partial
    41  // ratios of 2 fully split polynomials (cf https://eprint.iacr.org/2020/315.pdf)
    42  // * lf is the list of values that should be in lt
    43  // * lt is the lookup table
    44  // * lh1, lh2 is lf sorted by lt split in 2 overlapping slices
    45  // * beta, gamma are challenges (Schwartz-zippel: they are the random evaluations point)
    46  func evaluateAccumulationPolynomial(lf, lt, lh1, lh2 []fr.Element, beta, gamma fr.Element) []fr.Element {
    47  
    48  	z := make([]fr.Element, len(lt))
    49  
    50  	n := len(lt)
    51  	d := make([]fr.Element, n-1)
    52  	var u, c fr.Element
    53  	c.SetOne().
    54  		Add(&c, &beta).
    55  		Mul(&c, &gamma)
    56  	for i := 0; i < n-1; i++ {
    57  
    58  		d[i].Mul(&beta, &lh1[i+1]).
    59  			Add(&d[i], &lh1[i]).
    60  			Add(&d[i], &c)
    61  
    62  		u.Mul(&beta, &lh2[i+1]).
    63  			Add(&u, &lh2[i]).
    64  			Add(&u, &c)
    65  
    66  		d[i].Mul(&d[i], &u)
    67  	}
    68  	d = fr.BatchInvert(d)
    69  
    70  	z[0].SetOne()
    71  	var a, b, e fr.Element
    72  	e.SetOne().Add(&e, &beta)
    73  	for i := 0; i < n-1; i++ {
    74  
    75  		a.Add(&gamma, &lf[i])
    76  
    77  		b.Mul(&beta, &lt[i+1]).
    78  			Add(&b, &lt[i]).
    79  			Add(&b, &c)
    80  
    81  		a.Mul(&a, &b).
    82  			Mul(&a, &e)
    83  
    84  		z[i+1].Mul(&z[i], &a).
    85  			Mul(&z[i+1], &d[i])
    86  	}
    87  
    88  	return z
    89  }
    90  
    91  // evaluateNumBitReversed computes the evaluation (shifted, bit reversed) of h where
    92  // h = (x-1)*z*(1+\beta)*(\gamma+f)*(\gamma(1+\beta) + t+ \beta*t(gX)) -
    93  //		(x-1)*z(gX)*(\gamma(1+\beta) + h_{1} + \beta*h_{1}(gX))*(\gamma(1+\beta) + h_{2} + \beta*h_{2}(gX) )
    94  //
    95  // * cz, ch1, ch2, ct, cf are the polynomials z, h1, h2, t, f in canonical basis
    96  // * _lz, _lh1, _lh2, _lt, _lf are the polynomials z, h1, h2, t, f in shifted Lagrange basis (domainBig)
    97  // * beta, gamma are the challenges
    98  // * it returns h in canonical basis
    99  func evaluateNumBitReversed(_lz, _lh1, _lh2, _lt, _lf []fr.Element, beta, gamma fr.Element, domainBig *fft.Domain) []fr.Element {
   100  
   101  	// result
   102  	s := int(domainBig.Cardinality)
   103  	num := make([]fr.Element, domainBig.Cardinality)
   104  
   105  	var u, onePlusBeta, GammaTimesOnePlusBeta, m, n, one fr.Element
   106  
   107  	one.SetOne()
   108  	onePlusBeta.Add(&one, &beta)
   109  	GammaTimesOnePlusBeta.Mul(&onePlusBeta, &gamma)
   110  
   111  	g := make([]fr.Element, s)
   112  	g[0].Set(&domainBig.FrMultiplicativeGen)
   113  	for i := 1; i < s; i++ {
   114  		g[i].Mul(&g[i-1], &domainBig.Generator)
   115  	}
   116  
   117  	var gg fr.Element
   118  	expo := big.NewInt(int64(domainBig.Cardinality>>1 - 1))
   119  	gg.Square(&domainBig.Generator).Exp(gg, expo)
   120  
   121  	nn := uint64(64 - bits.TrailingZeros64(domainBig.Cardinality))
   122  
   123  	for i := 0; i < s; i++ {
   124  
   125  		_i := int(bits.Reverse64(uint64(i)) >> nn)
   126  		_is := int(bits.Reverse64(uint64((i+2)%s)) >> nn)
   127  
   128  		// m = z*(1+\beta)*(\gamma+f)*(\gamma(1+\beta) + t+ \beta*t(gX))
   129  		m.Mul(&onePlusBeta, &_lz[_i])
   130  		u.Add(&gamma, &_lf[_i])
   131  		m.Mul(&m, &u)
   132  		u.Mul(&beta, &_lt[_is]).
   133  			Add(&u, &_lt[_i]).
   134  			Add(&u, &GammaTimesOnePlusBeta)
   135  		m.Mul(&m, &u)
   136  
   137  		// n = z(gX)*(\gamma(1+\beta) + h_{1} + \beta*h_{1}(gX))*(\gamma(1+\beta) + h_{2} + \beta*h_{2}(gX)
   138  		n.Mul(&beta, &_lh1[_is]).
   139  			Add(&n, &_lh1[_i]).
   140  			Add(&n, &GammaTimesOnePlusBeta)
   141  		u.Mul(&beta, &_lh2[_is]).
   142  			Add(&u, &_lh2[_i]).
   143  			Add(&u, &GammaTimesOnePlusBeta)
   144  		n.Mul(&n, &u).
   145  			Mul(&n, &_lz[_is])
   146  
   147  		// (x-gg**(n-1))*(m-n)
   148  		num[_i].Sub(&m, &n)
   149  		u.Sub(&g[i], &gg)
   150  		num[_i].Mul(&num[_i], &u)
   151  
   152  	}
   153  
   154  	return num
   155  }
   156  
   157  // evaluateXnMinusOneDomainBig returns the evaluation of (x^{n}-1) on FrMultiplicativeGen*< g  >
   158  func evaluateXnMinusOneDomainBig(domainBig *fft.Domain) [2]fr.Element {
   159  
   160  	sizeDomainSmall := domainBig.Cardinality / 2
   161  
   162  	var one fr.Element
   163  	one.SetOne()
   164  
   165  	// x^{n}-1 on FrMultiplicativeGen*< g  >
   166  	var res [2]fr.Element
   167  	var shift fr.Element
   168  	shift.Exp(domainBig.FrMultiplicativeGen, big.NewInt(int64(sizeDomainSmall)))
   169  	res[0].Sub(&shift, &one)
   170  	res[1].Add(&shift, &one).Neg(&res[1])
   171  
   172  	return res
   173  
   174  }
   175  
   176  // evaluateL0DomainBig returns the evaluation of (x^{n}-1)/(x-1) on
   177  // x^{n}-1 on FrMultiplicativeGen*< g  >
   178  func evaluateL0DomainBig(domainBig *fft.Domain) ([2]fr.Element, []fr.Element) {
   179  
   180  	var one fr.Element
   181  	one.SetOne()
   182  
   183  	// x^{n}-1 on FrMultiplicativeGen*< g  >
   184  	xnMinusOne := evaluateXnMinusOneDomainBig(domainBig)
   185  
   186  	// 1/(x-1) on FrMultiplicativeGen*< g  >
   187  	var acc fr.Element
   188  	denL0 := make([]fr.Element, domainBig.Cardinality)
   189  	acc.Set(&domainBig.FrMultiplicativeGen)
   190  	for i := 0; i < int(domainBig.Cardinality); i++ {
   191  		denL0[i].Sub(&acc, &one)
   192  		acc.Mul(&acc, &domainBig.Generator)
   193  	}
   194  	denL0 = fr.BatchInvert(denL0)
   195  
   196  	return xnMinusOne, denL0
   197  }
   198  
   199  // evaluationLnDomainBig returns the evaluation of (x^{n}-1)/(x-g^{n-1}) on
   200  // x^{n}-1 on FrMultiplicativeGen*< g  >
   201  func evaluationLnDomainBig(domainBig *fft.Domain) ([2]fr.Element, []fr.Element) {
   202  
   203  	sizeDomainSmall := domainBig.Cardinality / 2
   204  
   205  	var one fr.Element
   206  	one.SetOne()
   207  
   208  	// x^{n}-1 on FrMultiplicativeGen*< g  >
   209  	numLn := evaluateXnMinusOneDomainBig(domainBig)
   210  
   211  	// 1/(x-g^{n-1}) on FrMultiplicativeGen*< g  >
   212  	var gg, acc fr.Element
   213  	gg.Square(&domainBig.Generator).Exp(gg, big.NewInt(int64(sizeDomainSmall-1)))
   214  	denLn := make([]fr.Element, domainBig.Cardinality)
   215  	acc.Set(&domainBig.FrMultiplicativeGen)
   216  	for i := 0; i < int(domainBig.Cardinality); i++ {
   217  		denLn[i].Sub(&acc, &gg)
   218  		acc.Mul(&acc, &domainBig.Generator)
   219  	}
   220  	denLn = fr.BatchInvert(denLn)
   221  
   222  	return numLn, denLn
   223  
   224  }
   225  
   226  // evaluateZStartsByOneBitReversed returns l0 * (z-1), in Lagrange basis and bit reversed order
   227  func evaluateZStartsByOneBitReversed(lsZBitReversed []fr.Element, domainBig *fft.Domain) []fr.Element {
   228  
   229  	var one fr.Element
   230  	one.SetOne()
   231  
   232  	res := make([]fr.Element, domainBig.Cardinality)
   233  
   234  	nn := uint64(64 - bits.TrailingZeros64(domainBig.Cardinality))
   235  
   236  	xnMinusOne, denL0 := evaluateL0DomainBig(domainBig)
   237  
   238  	for i := 0; i < len(lsZBitReversed); i++ {
   239  		_i := int(bits.Reverse64(uint64(i)) >> nn)
   240  		res[_i].Sub(&lsZBitReversed[_i], &one).
   241  			Mul(&res[_i], &xnMinusOne[i%2]).
   242  			Mul(&res[_i], &denL0[i])
   243  	}
   244  
   245  	return res
   246  }
   247  
   248  // evaluateZEndsByOneBitReversed returns ln * (z-1), in Lagrange basis and bit reversed order
   249  func evaluateZEndsByOneBitReversed(lsZBitReversed []fr.Element, domainBig *fft.Domain) []fr.Element {
   250  
   251  	var one fr.Element
   252  	one.SetOne()
   253  
   254  	numLn, denLn := evaluationLnDomainBig(domainBig)
   255  
   256  	res := make([]fr.Element, len(lsZBitReversed))
   257  	nn := uint64(64 - bits.TrailingZeros64(domainBig.Cardinality))
   258  
   259  	for i := 0; i < len(lsZBitReversed); i++ {
   260  		_i := int(bits.Reverse64(uint64(i)) >> nn)
   261  		res[_i].Sub(&lsZBitReversed[_i], &one).
   262  			Mul(&res[_i], &numLn[i%2]).
   263  			Mul(&res[_i], &denLn[i])
   264  	}
   265  
   266  	return res
   267  }
   268  
   269  // evaluateOverlapH1h2BitReversed returns ln * (h1 - h2(g.x)), in Lagrange basis and bit reversed order
   270  func evaluateOverlapH1h2BitReversed(_lh1, _lh2 []fr.Element, domainBig *fft.Domain) []fr.Element {
   271  
   272  	var one fr.Element
   273  	one.SetOne()
   274  
   275  	numLn, denLn := evaluationLnDomainBig(domainBig)
   276  
   277  	res := make([]fr.Element, len(_lh1))
   278  	nn := uint64(64 - bits.TrailingZeros64(domainBig.Cardinality))
   279  
   280  	s := len(_lh1)
   281  	for i := 0; i < s; i++ {
   282  
   283  		_i := int(bits.Reverse64(uint64(i)) >> nn)
   284  		_is := int(bits.Reverse64(uint64((i+2)%s)) >> nn)
   285  
   286  		res[_i].Sub(&_lh1[_i], &_lh2[_is]).
   287  			Mul(&res[_i], &numLn[i%2]).
   288  			Mul(&res[_i], &denLn[i])
   289  	}
   290  
   291  	return res
   292  }
   293  
   294  // computeQuotientCanonical computes the full quotient of the plookup protocol.
   295  // * alpha is the challenge to fold the numerator
   296  // * lh, lh0, lhn, lh1h2 are the various pieces of the numerator (Lagrange shifted form, bit reversed order)
   297  // * domainBig fft domain
   298  // It returns the quotient, in canonical basis
   299  func computeQuotientCanonical(alpha fr.Element, lh, lh0, lhn, lh1h2 []fr.Element, domainBig *fft.Domain) []fr.Element {
   300  
   301  	sizeDomainBig := int(domainBig.Cardinality)
   302  	res := make([]fr.Element, sizeDomainBig)
   303  
   304  	var one fr.Element
   305  	one.SetOne()
   306  
   307  	numLn := evaluateXnMinusOneDomainBig(domainBig)
   308  	numLn[0].Inverse(&numLn[0])
   309  	numLn[1].Inverse(&numLn[1])
   310  	nn := uint64(64 - bits.TrailingZeros64(domainBig.Cardinality))
   311  
   312  	for i := 0; i < sizeDomainBig; i++ {
   313  
   314  		_i := int(bits.Reverse64(uint64(i)) >> nn)
   315  
   316  		res[_i].Mul(&lh1h2[_i], &alpha).
   317  			Add(&res[_i], &lhn[_i]).
   318  			Mul(&res[_i], &alpha).
   319  			Add(&res[_i], &lh0[_i]).
   320  			Mul(&res[_i], &alpha).
   321  			Add(&res[_i], &lh[_i]).
   322  			Mul(&res[_i], &numLn[i%2])
   323  	}
   324  
   325  	domainBig.FFTInverse(res, fft.DIT, fft.OnCoset())
   326  
   327  	return res
   328  }
   329  
   330  // ProveLookupVector returns proof that the values in f are in t.
   331  //
   332  // /!\IMPORTANT/!\
   333  //
   334  // If the table t is already committed somewhere (which is the normal workflow
   335  // before generating a lookup proof), the commitment needs to be done on the
   336  // table sorted. Otherwise the commitment in proof.t will not be the same as
   337  // the public commitment: it will contain the same values, but permuted.
   338  //
   339  func ProveLookupVector(pk kzg.ProvingKey, f, t fr.Vector) (ProofLookupVector, error) {
   340  
   341  	// res
   342  	var proof ProofLookupVector
   343  	var err error
   344  
   345  	// hash function used for Fiat Shamir
   346  	hFunc := sha256.New()
   347  
   348  	// transcript to derive the challenge
   349  	fs := fiatshamir.NewTranscript(hFunc, "beta", "gamma", "alpha", "nu")
   350  
   351  	// create domains
   352  	var domainSmall *fft.Domain
   353  	if len(t) <= len(f) {
   354  		domainSmall = fft.NewDomain(uint64(len(f) + 1))
   355  	} else {
   356  		domainSmall = fft.NewDomain(uint64(len(t)))
   357  	}
   358  	sizeDomainSmall := int(domainSmall.Cardinality)
   359  
   360  	// set the size
   361  	proof.size = domainSmall.Cardinality
   362  
   363  	// set the generator
   364  	proof.g.Set(&domainSmall.Generator)
   365  
   366  	// resize f and t
   367  	// note: the last element of lf does not matter
   368  	lf := make([]fr.Element, sizeDomainSmall)
   369  	lt := make([]fr.Element, sizeDomainSmall)
   370  	cf := make([]fr.Element, sizeDomainSmall)
   371  	ct := make([]fr.Element, sizeDomainSmall)
   372  	copy(lt, t)
   373  	copy(lf, f)
   374  	for i := len(f); i < sizeDomainSmall; i++ {
   375  		lf[i] = f[len(f)-1]
   376  	}
   377  	for i := len(t); i < sizeDomainSmall; i++ {
   378  		lt[i] = t[len(t)-1]
   379  	}
   380  	sort.Sort(fr.Vector(lt))
   381  	copy(ct, lt)
   382  	copy(cf, lf)
   383  	domainSmall.FFTInverse(ct, fft.DIF)
   384  	domainSmall.FFTInverse(cf, fft.DIF)
   385  	fft.BitReverse(ct)
   386  	fft.BitReverse(cf)
   387  	proof.t, err = kzg.Commit(ct, pk)
   388  	if err != nil {
   389  		return proof, err
   390  	}
   391  	proof.f, err = kzg.Commit(cf, pk)
   392  	if err != nil {
   393  		return proof, err
   394  	}
   395  
   396  	// write f sorted by t
   397  	lfSortedByt := make(fr.Vector, 2*domainSmall.Cardinality-1)
   398  	copy(lfSortedByt, lt)
   399  	copy(lfSortedByt[domainSmall.Cardinality:], lf)
   400  	sort.Sort(lfSortedByt)
   401  
   402  	// compute h1, h2, commit to them
   403  	lh1 := make([]fr.Element, sizeDomainSmall)
   404  	lh2 := make([]fr.Element, sizeDomainSmall)
   405  	ch1 := make([]fr.Element, sizeDomainSmall)
   406  	ch2 := make([]fr.Element, sizeDomainSmall)
   407  	copy(lh1, lfSortedByt[:sizeDomainSmall])
   408  	copy(lh2, lfSortedByt[sizeDomainSmall-1:])
   409  
   410  	copy(ch1, lfSortedByt[:sizeDomainSmall])
   411  	copy(ch2, lfSortedByt[sizeDomainSmall-1:])
   412  	domainSmall.FFTInverse(ch1, fft.DIF)
   413  	domainSmall.FFTInverse(ch2, fft.DIF)
   414  	fft.BitReverse(ch1)
   415  	fft.BitReverse(ch2)
   416  
   417  	proof.h1, err = kzg.Commit(ch1, pk)
   418  	if err != nil {
   419  		return proof, err
   420  	}
   421  	proof.h2, err = kzg.Commit(ch2, pk)
   422  	if err != nil {
   423  		return proof, err
   424  	}
   425  
   426  	// derive beta, gamma
   427  	beta, err := deriveRandomness(fs, "beta", &proof.t, &proof.f, &proof.h1, &proof.h2)
   428  	if err != nil {
   429  		return proof, err
   430  	}
   431  	gamma, err := deriveRandomness(fs, "gamma")
   432  	if err != nil {
   433  		return proof, err
   434  	}
   435  
   436  	// Compute to Z
   437  	lz := evaluateAccumulationPolynomial(lf, lt, lh1, lh2, beta, gamma)
   438  	cz := make([]fr.Element, len(lz))
   439  	copy(cz, lz)
   440  	domainSmall.FFTInverse(cz, fft.DIF)
   441  	fft.BitReverse(cz)
   442  	proof.z, err = kzg.Commit(cz, pk)
   443  	if err != nil {
   444  		return proof, err
   445  	}
   446  
   447  	// prepare data for computing the quotient
   448  	// compute the numerator
   449  	s := domainSmall.Cardinality
   450  	domainBig := fft.NewDomain(uint64(2 * s))
   451  
   452  	_lz := make([]fr.Element, 2*s)
   453  	_lh1 := make([]fr.Element, 2*s)
   454  	_lh2 := make([]fr.Element, 2*s)
   455  	_lt := make([]fr.Element, 2*s)
   456  	_lf := make([]fr.Element, 2*s)
   457  	copy(_lz, cz)
   458  	copy(_lh1, ch1)
   459  	copy(_lh2, ch2)
   460  	copy(_lt, ct)
   461  	copy(_lf, cf)
   462  	domainBig.FFT(_lz, fft.DIF, fft.OnCoset())
   463  	domainBig.FFT(_lh1, fft.DIF, fft.OnCoset())
   464  	domainBig.FFT(_lh2, fft.DIF, fft.OnCoset())
   465  	domainBig.FFT(_lt, fft.DIF, fft.OnCoset())
   466  	domainBig.FFT(_lf, fft.DIF, fft.OnCoset())
   467  
   468  	// compute h
   469  	lh := evaluateNumBitReversed(_lz, _lh1, _lh2, _lt, _lf, beta, gamma, domainBig)
   470  
   471  	// compute l0*(z-1)
   472  	lh0 := evaluateZStartsByOneBitReversed(_lz, domainBig)
   473  
   474  	// compute ln(z-1)
   475  	lhn := evaluateZEndsByOneBitReversed(_lz, domainBig)
   476  
   477  	// compute ln*(h1-h2(g*X))
   478  	lh1h2 := evaluateOverlapH1h2BitReversed(_lh1, _lh2, domainBig)
   479  
   480  	// compute the quotient
   481  	alpha, err := deriveRandomness(fs, "alpha", &proof.z)
   482  	if err != nil {
   483  		return proof, err
   484  	}
   485  	ch := computeQuotientCanonical(alpha, lh, lh0, lhn, lh1h2, domainBig)
   486  	proof.h, err = kzg.Commit(ch, pk)
   487  	if err != nil {
   488  		return proof, err
   489  	}
   490  
   491  	// build the opening proofs
   492  	nu, err := deriveRandomness(fs, "nu", &proof.h)
   493  	if err != nil {
   494  		return proof, err
   495  	}
   496  	proof.BatchedProof, err = kzg.BatchOpenSinglePoint(
   497  		[][]fr.Element{
   498  			ch1,
   499  			ch2,
   500  			ct,
   501  			cz,
   502  			cf,
   503  			ch,
   504  		},
   505  		[]kzg.Digest{
   506  			proof.h1,
   507  			proof.h2,
   508  			proof.t,
   509  			proof.z,
   510  			proof.f,
   511  			proof.h,
   512  		},
   513  		nu,
   514  		hFunc,
   515  		pk,
   516  	)
   517  	if err != nil {
   518  		return proof, err
   519  	}
   520  
   521  	nu.Mul(&nu, &domainSmall.Generator)
   522  	proof.BatchedProofShifted, err = kzg.BatchOpenSinglePoint(
   523  		[][]fr.Element{
   524  			ch1,
   525  			ch2,
   526  			ct,
   527  			cz,
   528  		},
   529  		[]kzg.Digest{
   530  			proof.h1,
   531  			proof.h2,
   532  			proof.t,
   533  			proof.z,
   534  		},
   535  		nu,
   536  		hFunc,
   537  		pk,
   538  	)
   539  	if err != nil {
   540  		return proof, err
   541  	}
   542  
   543  	return proof, nil
   544  }
   545  
   546  // VerifyLookupVector verifies that a ProofLookupVector proof is correct
   547  func VerifyLookupVector(vk kzg.VerifyingKey, proof ProofLookupVector) error {
   548  
   549  	// hash function that is used for Fiat Shamir
   550  	hFunc := sha256.New()
   551  
   552  	// transcript to derive the challenge
   553  	fs := fiatshamir.NewTranscript(hFunc, "beta", "gamma", "alpha", "nu")
   554  
   555  	// derive the various challenges
   556  	beta, err := deriveRandomness(fs, "beta", &proof.t, &proof.f, &proof.h1, &proof.h2)
   557  	if err != nil {
   558  		return err
   559  	}
   560  
   561  	gamma, err := deriveRandomness(fs, "gamma")
   562  	if err != nil {
   563  		return err
   564  	}
   565  
   566  	alpha, err := deriveRandomness(fs, "alpha", &proof.z)
   567  	if err != nil {
   568  		return err
   569  	}
   570  
   571  	nu, err := deriveRandomness(fs, "nu", &proof.h)
   572  	if err != nil {
   573  		return err
   574  	}
   575  
   576  	// check opening proofs
   577  	err = kzg.BatchVerifySinglePoint(
   578  		[]kzg.Digest{
   579  			proof.h1,
   580  			proof.h2,
   581  			proof.t,
   582  			proof.z,
   583  			proof.f,
   584  			proof.h,
   585  		},
   586  		&proof.BatchedProof,
   587  		nu,
   588  		hFunc,
   589  		vk,
   590  	)
   591  	if err != nil {
   592  		return err
   593  	}
   594  
   595  	// shift the point and verify shifted proof
   596  	var shiftedNu fr.Element
   597  	shiftedNu.Mul(&nu, &proof.g)
   598  	err = kzg.BatchVerifySinglePoint(
   599  		[]kzg.Digest{
   600  			proof.h1,
   601  			proof.h2,
   602  			proof.t,
   603  			proof.z,
   604  		},
   605  		&proof.BatchedProofShifted,
   606  		shiftedNu,
   607  		hFunc,
   608  		vk,
   609  	)
   610  	if err != nil {
   611  		return err
   612  	}
   613  
   614  	// check the generator is correct
   615  	var checkOrder, one fr.Element
   616  	one.SetOne()
   617  	checkOrder.Exp(proof.g, big.NewInt(int64(proof.size/2)))
   618  	if checkOrder.Equal(&one) {
   619  		return ErrGenerator
   620  	}
   621  	checkOrder.Square(&checkOrder)
   622  	if !checkOrder.Equal(&one) {
   623  		return ErrGenerator
   624  	}
   625  
   626  	// check polynomial relation using Schwartz Zippel
   627  	var lhs, rhs, nun, g, _g, a, v, w fr.Element
   628  	g.Exp(proof.g, big.NewInt(int64(proof.size-1)))
   629  
   630  	v.Add(&one, &beta)
   631  	w.Mul(&v, &gamma)
   632  
   633  	// h(ν) where
   634  	// h = (xⁿ⁻¹-1)*z*(1+β)*(γ+f)*(γ(1+β) + t+ β*t(gX)) -
   635  	//		(xⁿ⁻¹-1)*z(gX)*(γ(1+β) + h₁ + β*h₁(gX))*(γ(1+β) + h₂ + β*h₂(gX) )
   636  	lhs.Sub(&nu, &g). // (ν-gⁿ⁻¹)
   637  				Mul(&lhs, &proof.BatchedProof.ClaimedValues[3]).
   638  				Mul(&lhs, &v)
   639  	a.Add(&gamma, &proof.BatchedProof.ClaimedValues[4])
   640  	lhs.Mul(&lhs, &a)
   641  	a.Mul(&beta, &proof.BatchedProofShifted.ClaimedValues[2]).
   642  		Add(&a, &proof.BatchedProof.ClaimedValues[2]).
   643  		Add(&a, &w)
   644  	lhs.Mul(&lhs, &a)
   645  
   646  	rhs.Sub(&nu, &g).
   647  		Mul(&rhs, &proof.BatchedProofShifted.ClaimedValues[3])
   648  	a.Mul(&beta, &proof.BatchedProofShifted.ClaimedValues[0]).
   649  		Add(&a, &proof.BatchedProof.ClaimedValues[0]).
   650  		Add(&a, &w)
   651  	rhs.Mul(&rhs, &a)
   652  	a.Mul(&beta, &proof.BatchedProofShifted.ClaimedValues[1]).
   653  		Add(&a, &proof.BatchedProof.ClaimedValues[1]).
   654  		Add(&a, &w)
   655  	rhs.Mul(&rhs, &a)
   656  
   657  	lhs.Sub(&lhs, &rhs)
   658  
   659  	// check consistency of bounds
   660  	var l0, ln, d1, d2 fr.Element
   661  	l0.Exp(nu, big.NewInt(int64(proof.size))).Sub(&l0, &one)
   662  	ln.Set(&l0)
   663  	d1.Sub(&nu, &one)
   664  	d2.Sub(&nu, &g)
   665  	l0.Div(&l0, &d1) // (νⁿ-1)/(ν-1)
   666  	ln.Div(&ln, &d2) // (νⁿ-1)/(ν-gⁿ⁻¹)
   667  
   668  	// l₀*(z-1) = (νⁿ-1)/(ν-1)*(z-1)
   669  	var l0z fr.Element
   670  	l0z.Sub(&proof.BatchedProof.ClaimedValues[3], &one).
   671  		Mul(&l0z, &l0)
   672  
   673  	// lₙ*(z-1) = (νⁿ-1)/(ν-gⁿ⁻¹)*(z-1)
   674  	var lnz fr.Element
   675  	lnz.Sub(&proof.BatchedProof.ClaimedValues[3], &one).
   676  		Mul(&ln, &lnz)
   677  
   678  	// lₙ*(h1 - h₂(g.x))
   679  	var lnh1h2 fr.Element
   680  	lnh1h2.Sub(&proof.BatchedProof.ClaimedValues[0], &proof.BatchedProofShifted.ClaimedValues[1]).
   681  		Mul(&lnh1h2, &ln)
   682  
   683  	// fold the numerator
   684  	lnh1h2.Mul(&lnh1h2, &alpha).
   685  		Add(&lnh1h2, &lnz).
   686  		Mul(&lnh1h2, &alpha).
   687  		Add(&lnh1h2, &l0z).
   688  		Mul(&lnh1h2, &alpha).
   689  		Add(&lnh1h2, &lhs)
   690  
   691  	// (xⁿ-1) * h(x) evaluated at ν
   692  	nun.Exp(nu, big.NewInt(int64(proof.size)))
   693  	_g.Sub(&nun, &one)
   694  	_g.Mul(&proof.BatchedProof.ClaimedValues[5], &_g)
   695  	if !lnh1h2.Equal(&_g) {
   696  		return ErrPlookupVerification
   697  	}
   698  
   699  	return nil
   700  }