261 lines
7.2 KiB
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
|
|
}
|