Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: use FK20 to generate proofs #81

Merged
merged 8 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/crate-crypto/go-eth-kzg/internal/kzg"
kzgmulti "github.com/crate-crypto/go-eth-kzg/internal/kzg_multi"
"github.com/crate-crypto/go-eth-kzg/internal/kzg_multi/fk20"
)

// Context holds the necessary configuration needed to create and verify proofs.
Expand All @@ -18,6 +19,8 @@ type Context struct {
commitKeyMonomial *kzg.CommitKey
openKey *kzg.OpeningKey

fk20 *fk20.FK20

dataRecovery *kzgmulti.DataRecovery
}

Expand Down Expand Up @@ -128,12 +131,15 @@ func NewContext4096(trustedSetup *JSONTrustedSetup) (*Context, error) {
domainExtended := kzg.NewDomain(scalarsPerExtBlob)
domainExtended.ReverseRoots()

fk20 := fk20.NewFK20(commitKeyMonomial.G1, scalarsPerExtBlob, scalarsPerCell)

return &Context{
domain: domain,
domainExtended: domainExtended,
commitKeyLagrange: &commitKeyLagrange,
commitKeyMonomial: &commitKeyMonomial,
openKey: &openingKey,
fk20: &fk20,
// TODO: We compute the extendedDomain again in here.
// TODO: We could pass it in, but it breaks the API.
// TODO: And although its not an issue now because fft uses just the primitiveGenerator, the extended domain
Expand Down
7 changes: 3 additions & 4 deletions api_eip7594.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,8 @@ func (ctx *Context) ComputeCellsAndKZGProofs(blob *Blob, numGoRoutines int) ([Ce

//lint:ignore U1000 still fleshing out the API
func (ctx *Context) computeCellsAndKZGProofsFromPolyCoeff(polyCoeff []fr.Element, _ int) ([CellsPerExtBlob]*Cell, [CellsPerExtBlob]KZGProof, error) {
// Partition the extended roots to form cosets
cosets := partition(ctx.domainExtended.Roots, scalarsPerCell)

// Compute all proofs and cells
proofs, cosetEvaluations, err := kzgmulti.ComputeMultiPointKZGProofs(polyCoeff, cosets, ctx.commitKeyMonomial)
proofs, cosetEvaluations, err := kzgmulti.ComputeMultiPointKZGProofs(ctx.fk20, polyCoeff)
if err != nil {
return [CellsPerExtBlob]*Cell{}, [CellsPerExtBlob]KZGProof{}, err
}
Expand Down Expand Up @@ -201,6 +198,8 @@ func (ctx *Context) VerifyCellKZGProofBatch(rowCommitments []KZGCommitment, rowI
// Output: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
//
// Panics if the slice cannot be divided into chunks of size k
// TODO: Remove, once we make verification not require the cosets
// TODO: These are not needed in a optimized version
func partition(slice []fr.Element, k int) [][]fr.Element {
var result [][]fr.Element

Expand Down
181 changes: 181 additions & 0 deletions internal/kzg_multi/fk20/fk20.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package fk20

import (
"errors"
"slices"

bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
"github.com/crate-crypto/go-eth-kzg/internal/kzg"
"github.com/crate-crypto/go-eth-kzg/internal/utils"
)

type FK20 struct {
batchMulAgg BatchToeplitzMatrixVecMul

proofDomain kzg.Domain
extDomain kzg.Domain

numPointsToOpen int
evalSetSize int
}

func NewFK20(srs []bls12381.G1Affine, numPointsToOpen, evalSetSize int) FK20 {
if !utils.IsPowerOfTwo(uint64(evalSetSize)) {
panic("the evaluation set size should be a power of two. It is the size of each coset")
}

srs = slices.Clone(srs)

slices.Reverse(srs)
srsTruncated := srs[evalSetSize:]
srsVectors := takeEveryNth(srsTruncated, evalSetSize)
padToPowerOfTwo(srsVectors)

batchMul := newBatchToeplitzMatrixVecMul(srsVectors)

// Compute the number of proofs
numProofs := numPointsToOpen / evalSetSize

proofDomain := kzg.NewDomain(uint64(numProofs))

// The size of the extension domain corresponds to the number of points that we want to open
extDomain := kzg.NewDomain(uint64(numPointsToOpen))

return FK20{
batchMulAgg: batchMul,
proofDomain: *proofDomain,
extDomain: *extDomain,

numPointsToOpen: numPointsToOpen,
evalSetSize: evalSetSize,
}
}

// TODO: move to reed-solomon, though it is somewhat hard to figure out
// TODO: what points we are opening for
func (fk *FK20) ComputeEvaluationSet(polyCoeff []fr.Element) [][]fr.Element {
// Pad to the correct length
for i := len(polyCoeff); i < len(fk.extDomain.Roots); i++ {
polyCoeff = append(polyCoeff, fr.Element{})
}

evaluations := fk.extDomain.FftFr(polyCoeff)
// TODO: move this to top level, same comment in ComputeMultiOpenProof
kzg.BitReverse(evaluations)

return partition(evaluations, fk.evalSetSize)
}

func (fk *FK20) ComputeMultiOpenProof(poly []fr.Element) ([]bls12381.G1Affine, error) {
hComms, err := fk.computeHPolysComm(poly)
if err != nil {
return nil, err
}

// Padd hComms since fft does not do this
numProofs := len(fk.proofDomain.Roots)
for i := len(hComms); i < numProofs; i++ {
hComms = append(hComms, bls12381.G1Affine{})
}

proofs := fk.proofDomain.FftG1(hComms)
// TODO: move this to top level
kzg.BitReverse(proofs)

return proofs, nil
}

func (fk *FK20) computeHPolysComm(polyCoeff []fr.Element) ([]bls12381.G1Affine, error) {
if !utils.IsPowerOfTwo(uint64(len(polyCoeff))) {
return nil, errors.New("expected the polynomial to have power of two number of coefficients")
}

// Reverse polynomial so that we have the highest coefficient
// be first.
polyCoeff = slices.Clone(polyCoeff) // TODO: Clone since we reverse and use PolyCoeff to evaluate after this call
slices.Reverse(polyCoeff)

toeplitzRows := takeEveryNth(polyCoeff, fk.evalSetSize)

toeplitzMatrices := make([]toeplitzMatrix, len(toeplitzRows))
for i := 0; i < len(toeplitzRows); i++ {
row := toeplitzRows[i]

column := make([]fr.Element, len(row))
column[0] = row[0]

toeplitzMatrices[i] = newToeplitz(row, column)
}

return fk.batchMulAgg.BatchMulAggregation(toeplitzMatrices)
}

func takeEveryNth[T any](list []T, n int) [][]T {
result := make([][]T, n)

for i := 0; i < n; i++ {
subList := make([]T, 0, (len(list)+n-1)/n) // Pre-allocate capacity
for j := i; j < len(list); j += n {
subList = append(subList, list[j])
}
result[i] = subList
}

return result
}

// nextPowerOfTwo returns the next power of two greater than or equal to n
func nextPowerOfTwo(n int) int {
if n == 0 {
return 1
}
k := 1
for k <= n {
k <<= 1
}
return k

// p := 1
// for p < n {
// p *= 2
// }
// return p
}

// padToPowerOfTwo pads each inner slice to the next power of two in-place
func padToPowerOfTwo(matrix [][]bls12381.G1Affine) {
for i, slice := range matrix {
currentLen := len(slice)
nextPow2 := nextPowerOfTwo(currentLen)

if nextPow2 > currentLen {
identityPoint := bls12381.G1Affine{}

// Extend the slice to the next power of two
for j := currentLen; j < nextPow2; j++ {
matrix[i] = append(matrix[i], identityPoint)
}
}
}
}

// partition groups a slice into chunks of size k
// Example:
// Input: [1, 2, 3, 4, 5, 6, 7, 8, 9], k: 3
// Output: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
//
// Panics if the slice cannot be divided into chunks of size k
func partition(slice []fr.Element, k int) [][]fr.Element {
var result [][]fr.Element

for i := 0; i < len(slice); i += k {
end := i + k
if end > len(slice) {
panic("all partitions should have the same size")
}
result = append(result, slice[i:end])
}

return result
}
41 changes: 41 additions & 0 deletions internal/kzg_multi/fk20/fk20_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package fk20

import (
"slices"
"testing"
)

func TestTakeEvery(t *testing.T) {
list := []int{0, 1, 2, 3, 4, 5, 6}
chunks := takeEveryNth(list, 2)
if len(chunks) != 2 {
t.Fatalf("slices should have a size of 2")
}

expected := [][]int{{0, 3, 5}, {1, 4, 6}}
for i := 0; i < 2; i++ {
if slices.Equal(chunks[i], expected[i]) {
t.Fatalf("slices are not equal")
}
}
}

func TestTransposeVectors(t *testing.T) {
intVectors := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
transposedInts := transposeVectors(intVectors)
transposedVectors := [][]int{
{1, 4, 7},
{2, 5, 8},
{3, 6, 9},
}

for i := 0; i < len(transposedVectors); i++ {
if !slices.Equal(transposedInts[i], transposedVectors[i]) {
t.Fatalf("vectors are not equal")
}
}
}
Loading
Loading