Files
mev-beta/vendor/github.com/crate-crypto/go-ipa/multiproof.go

353 lines
10 KiB
Go

package multiproof
import (
"encoding/binary"
"errors"
"fmt"
"io"
"runtime"
"github.com/crate-crypto/go-ipa/bandersnatch/fr"
"github.com/crate-crypto/go-ipa/banderwagon"
"github.com/crate-crypto/go-ipa/common"
"github.com/crate-crypto/go-ipa/ipa"
)
// The following are unexported labels to be used in Fiat-Shamir during the
// multiproof protocol.
//
// The following is a short description on how they're used in the protocol:
// 1. Append the domain separator. (labelDomainSep)
// 2. For each opening, we append to the transcript:
// a. The polynomial commitment (labelC).
// b. The evaluation point (labelZ).
// c. The evaluation result (labelY).
// 3. Pull a scalar-field element from the transcript to be used for
// the random linear combination of openings. (labelR)
// 4. Append point D which is sum(r^i * (f_i(x)-y_i)/(x-z_i)). (labelD)
// 5. Pull a random scalar-field to be used as a random evaluation point. (labelT)
// 5. Append point E which is sum(r^i * f_i(x)/(t-z_i)). (labelE)
// 7. Create the IPA proof for (E-D) at point `t`. See the `ipa` package for the FS description.
//
// Note: this package must not mutate these label values, nor pass them to
// parts of the code that would mutate them.
var (
labelC = []byte("C")
labelZ = []byte("z")
labelY = []byte("y")
labelD = []byte("D")
labelE = []byte("E")
labelT = []byte("t")
labelR = []byte("r")
labelDomainSep = []byte("multiproof")
)
// MultiProof is a multi-proof for several polynomials in evaluation form.
type MultiProof struct {
IPA ipa.IPAProof
D banderwagon.Element
}
// CreateMultiProof creates a multi-proof for several polynomials in evaluation form.
// The list of triplets (C, Fs, Z) represents each polynomial commitment, evaluations in the domain, and evaluation
// point respectively.
func CreateMultiProof(transcript *common.Transcript, ipaConf *ipa.IPAConfig, Cs []*banderwagon.Element, fs [][]fr.Element, zs []uint8) (*MultiProof, error) {
transcript.DomainSep(labelDomainSep)
for _, f := range fs {
if len(f) != common.VectorLength {
return nil, fmt.Errorf("polynomial length = %d, while expected length = %d", len(f), common.VectorLength)
}
}
if len(Cs) != len(fs) {
return nil, fmt.Errorf("number of commitments = %d, while number of functions = %d", len(Cs), len(fs))
}
if len(Cs) != len(zs) {
return nil, fmt.Errorf("number of commitments = %d, while number of points = %d", len(Cs), len(zs))
}
num_queries := len(Cs)
if num_queries == 0 {
return nil, errors.New("cannot create a multiproof with 0 queries")
}
if err := banderwagon.BatchNormalize(Cs); err != nil {
return nil, fmt.Errorf("could not batch normalize commitments: %w", err)
}
for i := 0; i < num_queries; i++ {
transcript.AppendPoint(Cs[i], labelC)
var z = domainToFr(zs[i])
transcript.AppendScalar(&z, labelZ)
// get the `y` value
f := fs[i]
y := f[zs[i]]
transcript.AppendScalar(&y, labelY)
}
r := transcript.ChallengeScalar(labelR)
powersOfR := common.PowersOf(r, num_queries)
// Compute g(x)
// We first compute the polynomials in lagrange form grouped by evaluation point, and
// then we compute g(X). This limit the numbers of DivideOnDomain() calls up to
// the domain size.
groupedFs := groupPolynomialsByEvaluationPoint(fs, powersOfR, zs)
g_x := make([]fr.Element, common.VectorLength)
for index, f := range groupedFs {
// If there is no polynomial for this evaluation point, we skip it.
if len(f) == 0 {
continue
}
quotient := ipaConf.PrecomputedWeights.DivideOnDomain(uint8(index), f)
for j := 0; j < common.VectorLength; j++ {
g_x[j].Add(&g_x[j], &quotient[j])
}
}
D := ipaConf.Commit(g_x)
transcript.AppendPoint(&D, labelD)
t := transcript.ChallengeScalar(labelT)
// Calculate the denominator inverses only for referenced evaluation points.
den_inv := make([]fr.Element, 0, common.VectorLength)
for z, f := range groupedFs {
if len(f) == 0 {
continue
}
var z = domainToFr(uint8(z))
var den fr.Element
den.Sub(&t, &z)
den_inv = append(den_inv, den)
}
den_inv = fr.BatchInvert(den_inv)
// Compute h(X) = g_1(X)
h_x := make([]fr.Element, common.VectorLength)
denInvIdx := 0
for _, f := range groupedFs {
if len(f) == 0 {
continue
}
for k := 0; k < common.VectorLength; k++ {
var tmp fr.Element
tmp.Mul(&f[k], &den_inv[denInvIdx])
h_x[k].Add(&h_x[k], &tmp)
}
denInvIdx++
}
h_minus_g := make([]fr.Element, common.VectorLength)
for i := 0; i < common.VectorLength; i++ {
h_minus_g[i].Sub(&h_x[i], &g_x[i])
}
E := ipaConf.Commit(h_x)
transcript.AppendPoint(&E, labelE)
var EminusD banderwagon.Element
EminusD.Sub(&E, &D)
ipaProof, err := ipa.CreateIPAProof(transcript, ipaConf, EminusD, h_minus_g, t)
if err != nil {
return nil, fmt.Errorf("could not create IPA proof: %w", err)
}
return &MultiProof{
IPA: ipaProof,
D: D,
}, nil
}
// CheckMultiProof verifies a multi-proof for several polynomials in evaluation form.
// The list of triplets (C, Y, Z) represents each polynomial commitment, evaluation
// result, and evaluation point in the domain.
func CheckMultiProof(transcript *common.Transcript, ipaConf *ipa.IPAConfig, proof *MultiProof, Cs []*banderwagon.Element, ys []*fr.Element, zs []uint8) (bool, error) {
transcript.DomainSep(labelDomainSep)
if len(Cs) != len(ys) {
return false, fmt.Errorf("number of commitments = %d, while number of output points = %d", len(Cs), len(ys))
}
if len(Cs) != len(zs) {
return false, fmt.Errorf("number of commitments = %d, while number of input points = %d", len(Cs), len(zs))
}
num_queries := len(Cs)
if num_queries == 0 {
return false, errors.New("number of queries is zero")
}
for i := 0; i < num_queries; i++ {
transcript.AppendPoint(Cs[i], labelC)
var z = domainToFr(zs[i])
transcript.AppendScalar(&z, labelZ)
transcript.AppendScalar(ys[i], labelY)
}
r := transcript.ChallengeScalar(labelR)
powers_of_r := common.PowersOf(r, num_queries)
transcript.AppendPoint(&proof.D, labelD)
t := transcript.ChallengeScalar(labelT)
// Compute the polynomials in lagrange form grouped by evaluation point, and
// the needed helper scalars.
groupedEvals := make([]fr.Element, common.VectorLength)
for i := 0; i < num_queries; i++ {
z := zs[i]
// r * y_i
r := powers_of_r[i]
var scaledEvaluation fr.Element
scaledEvaluation.Mul(&r, ys[i])
groupedEvals[z].Add(&groupedEvals[z], &scaledEvaluation)
}
// Compute helper_scalar_den. This is 1 / t - z_i
helper_scalar_den := make([]fr.Element, common.VectorLength)
for i := 0; i < common.VectorLength; i++ {
// (t - z_i)
var z = domainToFr(uint8(i))
helper_scalar_den[i].Sub(&t, &z)
}
helper_scalar_den = fr.BatchInvert(helper_scalar_den)
// Compute g_2(t) = SUM (y_i * r^i) / (t - z_i) = SUM (y_i * r) * helper_scalars_den
g_2_t := fr.Zero()
for i := 0; i < common.VectorLength; i++ {
if groupedEvals[i].IsZero() {
continue
}
var tmp fr.Element
tmp.Mul(&groupedEvals[i], &helper_scalar_den[i])
g_2_t.Add(&g_2_t, &tmp)
}
// Compute E = SUM C_i * (r^i / t - z_i) = SUM C_i * msm_scalars
msm_scalars := make([]fr.Element, len(Cs))
Csnp := make([]banderwagon.Element, len(Cs))
for i := 0; i < len(Cs); i++ {
Csnp[i] = *Cs[i]
msm_scalars[i].Mul(&powers_of_r[i], &helper_scalar_den[zs[i]])
}
E, err := ipa.MultiScalar(Csnp, msm_scalars)
if err != nil {
return false, fmt.Errorf("could not compute E: %w", err)
}
transcript.AppendPoint(&E, labelE)
var E_minus_D banderwagon.Element
E_minus_D.Sub(&E, &proof.D)
ok, err := ipa.CheckIPAProof(transcript, ipaConf, E_minus_D, proof.IPA, t, g_2_t)
if err != nil {
return false, fmt.Errorf("could not check IPA proof: %w", err)
}
return ok, nil
}
func domainToFr(in uint8) fr.Element {
var x fr.Element
x.SetUint64(uint64(in))
return x
}
// Write serializes a multi-proof to a writer.
func (mp *MultiProof) Write(w io.Writer) error {
if err := binary.Write(w, binary.BigEndian, mp.D.Bytes()); err != nil {
return fmt.Errorf("failed to write D: %w", err)
}
if err := mp.IPA.Write(w); err != nil {
return fmt.Errorf("failed to write IPA proof: %w", err)
}
return nil
}
// Read deserializes a multi-proof from a reader.
func (mp *MultiProof) Read(r io.Reader) error {
D, err := common.ReadPoint(r)
if err != nil {
return fmt.Errorf("failed to read D: %w", err)
}
mp.D = *D
if err := mp.IPA.Read(r); err != nil {
return fmt.Errorf("failed to read IPA proof: %w", err)
}
// Check that the next read is EOF.
var buf [1]byte
if _, err := r.Read(buf[:]); err != io.EOF {
return errors.New("expected EOF")
}
return nil
}
// Equal checks if two multi-proofs are equal.
func (mp MultiProof) Equal(other MultiProof) bool {
if !mp.IPA.Equal(other.IPA) {
return false
}
return mp.D.Equal(&other.D)
}
func groupPolynomialsByEvaluationPoint(fs [][]fr.Element, powersOfR []fr.Element, zs []uint8) [common.VectorLength][]fr.Element {
workersAggregations := make(chan [common.VectorLength][]fr.Element)
numWorkers := runtime.NumCPU()
batchSize := (len(fs) + numWorkers - 1) / numWorkers
for i := 0; i < numWorkers; i++ {
go func(start, end int) {
if end > len(fs) {
end = len(fs)
}
var groupedFs [common.VectorLength][]fr.Element
for i := start; i < end; i++ {
z := zs[i]
if len(groupedFs[z]) == 0 {
groupedFs[z] = make([]fr.Element, common.VectorLength)
}
for j := 0; j < common.VectorLength; j++ {
var scaledEvaluation fr.Element
scaledEvaluation.Mul(&powersOfR[i], &fs[i][j])
groupedFs[z][j].Add(&groupedFs[z][j], &scaledEvaluation)
}
}
workersAggregations <- groupedFs
}(i*batchSize, (i+1)*batchSize)
}
// Each worker has computed its own aggregation. Now we aggregate the results.
// This is bounded to reducing a `numWorkers` sized array of `common.VectorLength` sized arrays.
var groupedFs [common.VectorLength][]fr.Element
for i := 0; i < numWorkers; i++ {
workerAggregation := <-workersAggregations
for z := range workerAggregation {
if len(workerAggregation[z]) == 0 {
continue
}
// If this is the first time we see this evaluation point, we initialize it
// reusing the worker result.
if groupedFs[z] == nil {
groupedFs[z] = workerAggregation[z]
continue
}
// If not, we aggregate the worker result with the previous result for this evaluation.
for j := 0; j < common.VectorLength; j++ {
groupedFs[z][j].Add(&groupedFs[z][j], &workerAggregation[z][j])
}
}
}
return groupedFs
}