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

261 lines
7.2 KiB
Go

package ipa
import (
"encoding/binary"
"fmt"
"io"
"math/big"
"github.com/crate-crypto/go-ipa/bandersnatch/fr"
"github.com/crate-crypto/go-ipa/banderwagon"
"github.com/crate-crypto/go-ipa/common"
)
var maxEvalPointInsideDomain fr.Element
func init() {
maxEvalPointInsideDomain.SetUint64(common.VectorLength - 1)
}
// The following are unexported labels to be used in Fiat-Shamir during the
// inner-product argument protocol.
//
// The following is a short description on how they're used in the protocol:
// 1. Append the domain separator. (labelDomainSep)
// 2. Append the commitment to the polynomial. (labelC)
// 3. Append the input point. (labelInputPoint)
// 4. Append the output point. (labelOutputPoint)
// 5. Pull the re-scaling factor `w` to scale Q. (labelW).
// 6. For each round of the IPA protocol:
// a. Append the resulting point C_L. (labelL)
// b. Append the resulting point C_R. (labelR)
// c. Pull the random scalar-field element `x`. (labelX)
//
// Note: this package must not mutate these label values, nor pass them to
// parts of the code that would mutate them.
var (
labelDomainSep = []byte("ipa")
labelC = []byte("C")
labelInputPoint = []byte("input point")
labelOutputPoint = []byte("output point")
labelW = []byte("w")
labelL = []byte("L")
labelR = []byte("R")
labelX = []byte("x")
)
// IPAProof is an inner product argument proof.
type IPAProof struct {
L []banderwagon.Element
R []banderwagon.Element
A_scalar fr.Element
}
// CreateIPAProof creates an IPA proof for a committed polynomial in evaluation form.
// `a` are the evaluation of the polynomial in the domain, and `evalPoint` represents the
// evaluation point. The evaluation of the polynomial at such point is computed automatically.
func CreateIPAProof(transcript *common.Transcript, ic *IPAConfig, commitment banderwagon.Element, a []fr.Element, evalPoint fr.Element) (IPAProof, error) {
transcript.DomainSep(labelDomainSep)
b := computeBVector(ic, evalPoint)
inner_prod, err := InnerProd(a, b)
if err != nil {
return IPAProof{}, fmt.Errorf("could not compute inner product: %w", err)
}
transcript.AppendPoint(&commitment, labelC)
transcript.AppendScalar(&evalPoint, labelInputPoint)
transcript.AppendScalar(&inner_prod, labelOutputPoint)
w := transcript.ChallengeScalar(labelW)
var q banderwagon.Element
q.ScalarMul(&ic.Q, &w)
num_rounds := ic.numRounds
current_basis := ic.SRS
L := make([]banderwagon.Element, num_rounds)
R := make([]banderwagon.Element, num_rounds)
for i := 0; i < int(num_rounds); i++ {
a_L, a_R, err := splitScalars(a)
if err != nil {
return IPAProof{}, fmt.Errorf("could not split a scalars: %w", err)
}
b_L, b_R, err := splitScalars(b)
if err != nil {
return IPAProof{}, fmt.Errorf("could not split b scalars: %w", err)
}
G_L, G_R, err := splitPoints(current_basis)
if err != nil {
return IPAProof{}, fmt.Errorf("could not split G points: %w", err)
}
z_L, err := InnerProd(a_R, b_L)
if err != nil {
return IPAProof{}, fmt.Errorf("could not compute a_r*b_L inner product: %w", err)
}
z_R, err := InnerProd(a_L, b_R)
if err != nil {
return IPAProof{}, fmt.Errorf("could not compute a_L*b_R inner product: %w", err)
}
C_L_1, err := commit(G_L, a_R)
if err != nil {
return IPAProof{}, fmt.Errorf("could not do G_L*a_R MSM: %w", err)
}
C_L, err := commit([]banderwagon.Element{C_L_1, q}, []fr.Element{fr.One(), z_L})
if err != nil {
return IPAProof{}, fmt.Errorf("could not do C_L_1+z_L*q MSM: %w", err)
}
C_R_1, err := commit(G_R, a_L)
if err != nil {
return IPAProof{}, fmt.Errorf("could not do G_R*a_L MSM: %w", err)
}
C_R, err := commit([]banderwagon.Element{C_R_1, q}, []fr.Element{fr.One(), z_R})
if err != nil {
return IPAProof{}, fmt.Errorf("could not do C_R_1+z_R*q MSM: %w", err)
}
L[i] = C_L
R[i] = C_R
transcript.AppendPoint(&C_L, labelL)
transcript.AppendPoint(&C_R, labelR)
x := transcript.ChallengeScalar(labelX)
var xInv fr.Element
xInv.Inverse(&x)
// TODO: We could use a for loop here like in the Rust code
a, err = foldScalars(a_L, a_R, x)
if err != nil {
return IPAProof{}, fmt.Errorf("could not fold a scalars a_L and a_R with x: %w", err)
}
b, err = foldScalars(b_L, b_R, xInv)
if err != nil {
return IPAProof{}, fmt.Errorf("could not fold b scalars b_L and b_R with xInv: %w", err)
}
current_basis, err = foldPoints(G_L, G_R, xInv)
if err != nil {
return IPAProof{}, fmt.Errorf("could not fold points G_L and G_R with xInv: %w", err)
}
}
if len(a) != 1 {
return IPAProof{}, fmt.Errorf("length of `a` should be 1 at the end of the reduction")
}
return IPAProof{
L: L,
R: R,
A_scalar: a[0],
}, nil
}
// Write serializes the IPA proof to the given writer.
func (ip *IPAProof) Write(w io.Writer) error {
for _, el := range ip.L {
if err := binary.Write(w, binary.BigEndian, el.Bytes()); err != nil {
return fmt.Errorf("failed to write L: %w", err)
}
}
for _, ar := range ip.R {
if err := binary.Write(w, binary.BigEndian, ar.Bytes()); err != nil {
return fmt.Errorf("failed to write R: %w", err)
}
}
if err := binary.Write(w, binary.BigEndian, ip.A_scalar.BytesLE()); err != nil {
return fmt.Errorf("failed to write A_scalar: %w", err)
}
return nil
}
// Read deserializes the IPA proof from the given reader.
func (ip *IPAProof) Read(r io.Reader) error {
var L []banderwagon.Element
for i := 0; i < 8; i++ {
L_i, err := common.ReadPoint(r)
if err != nil {
return fmt.Errorf("failed to read L[%d]: %w", i, err)
}
L = append(L, *L_i)
}
ip.L = L
var R []banderwagon.Element
for i := 0; i < 8; i++ {
R_i, err := common.ReadPoint(r)
if err != nil {
return fmt.Errorf("failed to read R[%d]: %w", i, err)
}
R = append(R, *R_i)
}
ip.R = R
A_Scalar, err := common.ReadScalar(r)
if err != nil {
return fmt.Errorf("failed to read A_scalar: %w", err)
}
ip.A_scalar = *A_Scalar
return nil
}
// Equal checks if two IPA proofs are equal.
func (ip IPAProof) Equal(other IPAProof) bool {
num_rounds := 8
if len(ip.L) != len(other.L) {
return false
}
if len(ip.R) != len(other.R) {
return false
}
if len(ip.L) != len(ip.R) {
return false
}
if len(ip.L) != num_rounds {
return false
}
for i := 0; i < num_rounds; i++ {
expect_L_i := ip.L[i]
expect_R_i := ip.R[i]
got_L_i := other.L[i]
got_R_i := other.R[i]
if !expect_L_i.Equal(&got_L_i) {
return false
}
if !expect_R_i.Equal(&got_R_i) {
return false
}
}
return ip.A_scalar.Equal(&other.A_scalar)
}
func computeBVector(ic *IPAConfig, evalPoint fr.Element) []fr.Element {
if evalPoint.Cmp(&maxEvalPointInsideDomain) > 0 {
return ic.PrecomputedWeights.ComputeBarycentricCoefficients(evalPoint)
}
// We build b = [0, 0, 0, ... , 1, .., 0] where the 1 element is at the index of the evaluation point.
// This is correct since innerProductArgument(a, b) will return the evaluation of the polynomial at the
// evaluation point in the domain.
b := make([]fr.Element, common.VectorLength)
var evalPointBI big.Int
evalPoint.ToBigIntRegular(&evalPointBI)
// Uint64() is safe because we checked that evalPoint is inside the domain (i.e <256).
b[evalPointBI.Uint64()] = fr.One()
return b
}