181 lines
7.0 KiB
Go
181 lines
7.0 KiB
Go
package kzg
|
|
|
|
import (
|
|
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
|
|
"github.com/crate-crypto/go-eth-kzg/internal/domain"
|
|
)
|
|
|
|
// Open verifies that a polynomial f(x) when evaluated at a point `z` is equal to `f(z)`
|
|
//
|
|
// numGoRoutines is used to configure the amount of concurrency needed. Setting this
|
|
// value to a negative number or 0 will make it default to the number of CPUs.
|
|
//
|
|
// [compute_kzg_proof_impl]: https://github.com/ethereum/consensus-specs/blob/017a8495f7671f5fff2075a9bfc9238c1a0982f8/specs/deneb/polynomial-commitments.md#compute_kzg_proof_impl
|
|
func Open(domain *domain.Domain, p Polynomial, evaluationPoint fr.Element, ck *CommitKey, numGoRoutines int) (OpeningProof, error) {
|
|
if len(p) == 0 || len(p) > len(ck.G1) {
|
|
return OpeningProof{}, ErrInvalidPolynomialSize
|
|
}
|
|
|
|
outputPoint, indexInDomain, err := domain.EvaluateLagrangePolynomialWithIndex(p, evaluationPoint)
|
|
if err != nil {
|
|
return OpeningProof{}, err
|
|
}
|
|
|
|
// Compute the quotient polynomial
|
|
quotientPoly, err := computeQuotientPoly(domain, p, indexInDomain, *outputPoint, evaluationPoint)
|
|
if err != nil {
|
|
return OpeningProof{}, err
|
|
}
|
|
|
|
// Commit to Quotient polynomial
|
|
quotientCommit, err := ck.Commit(quotientPoly, numGoRoutines)
|
|
if err != nil {
|
|
return OpeningProof{}, err
|
|
}
|
|
|
|
res := OpeningProof{
|
|
InputPoint: evaluationPoint,
|
|
ClaimedValue: *outputPoint,
|
|
}
|
|
|
|
res.QuotientCommitment.Set(quotientCommit)
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// computeQuotientPoly computes q(X) = (f(X) - f(z)) / (X - z) in Lagrange form.
|
|
//
|
|
// We refer to the result q(X) as the quotient polynomial.
|
|
//
|
|
// The division needs to be handled differently if `z` is an element in the domain
|
|
// because the naive formula would compute 0/0. Hence, you will observe that this function
|
|
// will follow a different code-path depending on this condition.
|
|
//
|
|
// In our situation, both f(z) and whether z is inside the domain are always known to the caller,
|
|
// so we just take is as input rather than (re-)computing it ourself. The method does not check that those
|
|
// values provided are correct.
|
|
//
|
|
// indexInDomain needs to be set to -1 to indicate that z is not in the domain and to the index in the domain if it is.
|
|
//
|
|
// The matching code for this method is in `compute_kzg_proof_impl` where the quotient polynomial
|
|
// is computed.
|
|
func computeQuotientPoly(domain *domain.Domain, f Polynomial, indexInDomain int64, fz, z fr.Element) (Polynomial, error) {
|
|
if domain.Cardinality != uint64(len(f)) {
|
|
return nil, ErrPolynomialMismatchedSizeDomain
|
|
}
|
|
|
|
if indexInDomain != -1 {
|
|
// Note: the uint64 conversion is both semantically correct and safer
|
|
// than accepting an `int``, since we know it shouldn't be negative
|
|
// and it should cause a panic, if not checked; uint64(-1) = 2^64 -1
|
|
return computeQuotientPolyOnDomain(domain, f, uint64(indexInDomain))
|
|
}
|
|
|
|
return computeQuotientPolyOutsideDomain(domain, f, fz, z)
|
|
}
|
|
|
|
// computeQuotientPolyOutsideDomain computes q(X) = (f(X) - f(z)) / (X - z) in lagrange form where `z` is not in the domain.
|
|
//
|
|
// This is the implementation of computeQuotientPoly for the case where z is not in the domain.
|
|
// Since both input and output polynomials are given in evaluation form, this method just performs the desired operation pointwise.
|
|
func computeQuotientPolyOutsideDomain(domain *domain.Domain, f Polynomial, fz, z fr.Element) (Polynomial, error) {
|
|
// Compute the lagrange form of the denominator X - z.
|
|
// This means that we need to compute w - z for all points w in the domain.
|
|
tmpDenom := make(Polynomial, len(f))
|
|
for i := 0; i < len(f); i++ {
|
|
tmpDenom[i].Sub(&domain.Roots[i], &z)
|
|
}
|
|
|
|
// To invert the denominator polynomial at each point of the domain, we perform a batch-inversion.
|
|
// Since `z` is not in the domain, we are sure that there are no zeroes in this inversion.
|
|
//
|
|
// Note: if there was a zero, the gnark-crypto library would skip
|
|
// it and not panic.
|
|
// Note: the returned slice is a new slice, thus we are free to use tmpDenom.
|
|
denominator := fr.BatchInvert(tmpDenom)
|
|
|
|
// Compute the lagrange form of the numerator f(X) - f(z)
|
|
// Since f(X) is already in lagrange form, we can compute f(X) - f(z)
|
|
// by shifting all elements in f(X) by f(z)
|
|
numerator := tmpDenom
|
|
for i := 0; i < len(f); i++ {
|
|
numerator[i].Sub(&f[i], &fz)
|
|
}
|
|
|
|
// Compute the quotient q(X)
|
|
for i := 0; i < len(f); i++ {
|
|
denominator[i].Mul(&denominator[i], &numerator[i])
|
|
}
|
|
|
|
return denominator, nil
|
|
}
|
|
|
|
// computeQuotientPolyOnDomain computes (f(X) - f(z)) / (X - z) in Lagrange form where `z` is in the domain.
|
|
//
|
|
// This is the implementation of computeQuotientPoly for the case where the evaluation point is in the domain.
|
|
//
|
|
// [compute_quotient_eval_within_domain]: https://github.com/ethereum/consensus-specs/blob/017a8495f7671f5fff2075a9bfc9238c1a0982f8/specs/deneb/polynomial-commitments.md#compute_quotient_eval_within_domain
|
|
func computeQuotientPolyOnDomain(domain *domain.Domain, f Polynomial, index uint64) (Polynomial, error) {
|
|
fz := f[index]
|
|
z := domain.Roots[index]
|
|
invZ := domain.PreComputedInverses[index]
|
|
|
|
// Compute the evaluation of X - z at every point in the domain.
|
|
rootsMinusZ := make([]fr.Element, domain.Cardinality)
|
|
for i := 0; i < int(domain.Cardinality); i++ {
|
|
rootsMinusZ[i].Sub(&domain.Roots[i], &z)
|
|
}
|
|
|
|
// Since we know that `z` is in the domain, rootsMinusZ[index] will be zero.
|
|
// We set this value to `1` instead to compute the batch inversion without having to special-case here.
|
|
// This way, the value of rootsMinusZ[index] will stay untouched.
|
|
// Note: The underlying gnark-crypto library will not panic if
|
|
// one of the elements is zero, but this is not common across libraries so we just set it to one.
|
|
rootsMinusZ[index].SetOne()
|
|
|
|
// Evaluation of 1/(X-z) at every point of the domain, except for index.
|
|
invRootsMinusZ := fr.BatchInvert(rootsMinusZ)
|
|
|
|
// The rootsMinusZ is now free to reuse, since BatchInvert returned
|
|
// a fresh slice. But we need to ensure to set the value for 'index' to zero
|
|
quotientPoly := rootsMinusZ
|
|
quotientPoly[index] = fr.Element{}
|
|
|
|
for j := 0; j < int(domain.Cardinality); j++ {
|
|
// Check if we are on the current root of unity
|
|
// Note: For notations below, we use `m` to denote `index`
|
|
if uint64(j) == index {
|
|
continue
|
|
}
|
|
|
|
// Compute q_j = f_j / w^j - w^m for j != m.
|
|
// This is exactly the same as in the computeQuotientPolyOutsideDomain - case.
|
|
//
|
|
// Note: f_j is the numerator of the quotient polynomial ie f_j = f[j] - f(z)
|
|
//
|
|
//
|
|
var q_j fr.Element
|
|
q_j.Sub(&f[j], &fz)
|
|
q_j.Mul(&q_j, &invRootsMinusZ[j])
|
|
quotientPoly[j] = q_j
|
|
|
|
// Compute the contribution to q_m coming from the j'th term of the input.
|
|
// This term is given by
|
|
// q_m_j = (f_j / w^m - w^j) * (w^j/w^m) , where w^m = z
|
|
// = - q_j * w^{j-m}
|
|
//
|
|
// We _could_ find 1 / w^{j-m} via a lookup table
|
|
// but we want to avoid lookup tables because
|
|
// the roots are bit-reversed which can make the
|
|
// code less readable.
|
|
var q_m_j fr.Element
|
|
q_m_j.Neg(&q_j)
|
|
q_m_j.Mul(&q_m_j, &domain.Roots[j])
|
|
q_m_j.Mul(&q_m_j, &invZ)
|
|
|
|
quotientPoly[index].Add("ientPoly[index], &q_m_j)
|
|
}
|
|
|
|
return quotientPoly, nil
|
|
}
|