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

Optimized validate_blob_transaction_wrapper() function #4

Closed
wants to merge 5 commits into from
Closed
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
63 changes: 50 additions & 13 deletions core/types/data_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import (
"encoding/hex"
"errors"
"fmt"
"io"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg"
"github.com/ethereum/go-ethereum/params"
kbls "github.com/kilic/bls12-381"
"github.com/protolambda/go-kzg/bls"
"github.com/protolambda/ztyp/codec"
"github.com/protolambda/ztyp/tree"
"io"
)

const BLOB_COMMITMENT_VERSION_KZG byte = 0x01
Expand Down Expand Up @@ -71,8 +71,8 @@ func (p *KZGCommitment) UnmarshalText(text []byte) error {
return err
}

func (p *KZGCommitment) Point() (*kbls.PointG1, error) {
return kbls.NewG1().FromCompressed(p[:])
func (p *KZGCommitment) Point() (*bls.G1Point, error) {
return bls.FromCompressedG1(p[:])
}

func (kzg KZGCommitment) ComputeVersionedHash() common.Hash {
Expand Down Expand Up @@ -158,6 +158,19 @@ func (blob Blob) ComputeCommitment() (commitment KZGCommitment, ok bool) {

type BlobKzgs []KZGCommitment

// Extract the crypto material underlying these commitments
func (li BlobKzgs) Commitments() ([]*bls.G1Point, error) {
var points []*bls.G1Point
for _, c := range li {
p, err := c.Point()
if err != nil {
return nil, errors.New("internal error commitments")
}
points = append(points, p)
}
return points, nil
}

func (li *BlobKzgs) Deserialize(dr *codec.DecodingReader) error {
return dr.List(func() codec.Deserializable {
i := len(*li)
Expand Down Expand Up @@ -194,6 +207,28 @@ func (li BlobKzgs) copy() BlobKzgs {

type Blobs []Blob

// Extract the crypto material underlying these blobs
func (blobs Blobs) Blobs() ([][]bls.Fr, error) {
var result [][]bls.Fr

// Iterate over every blob
for _, b := range blobs {
var blob []bls.Fr
// Iterate over each chunk of the blob and parse it into an Fr
for _, chunk := range b {
var chunkFr bls.Fr
ok := bls.FrFrom32(&chunkFr, chunk)
if ok != true {
return nil, errors.New("internal error commitments")
}
blob = append(blob, chunkFr)
}
// Add each individiual blob to the result
result = append(result, blob)
}
return result, nil
}

func (a *Blobs) Deserialize(dr *codec.DecodingReader) error {
return dr.List(func() codec.Deserializable {
i := len(*a)
Expand Down Expand Up @@ -288,16 +323,18 @@ func (b *BlobTxWrapData) checkWrapping(inner TxData) error {
return fmt.Errorf("versioned hash %d supposedly %s but does not match computed %s", i, h, computed)
}
}
// TODO: george/dankrad: faster check if kzg commitment matches blob data, Dankrad: "Instead of executing
// this per blob, it should ideally be taking a random linear combination on each side."
for i, c := range b.BlobKzgs {
if computed, ok := b.Blobs[i].ComputeCommitment(); !ok {
return fmt.Errorf("failed to parse blob %d to compute commitment for verification", i)
} else if computed != c {
return fmt.Errorf("kzg commitment %d supposedly %s but does not match computed %s", i, c, computed)
}

// Time to verify that the KZG commitments match the included blobs:
// first extract crypto material out of our types and pass them to the crypto layer
commitments, err := b.BlobKzgs.Commitments()
if err != nil {
return fmt.Errorf("internal commitments error")
}
return nil
blobs, err := b.Blobs.Blobs()
if err != nil {
return fmt.Errorf("internal blobs error")
}
return kzg.VerifyBlobs(commitments, blobs)
}

func (b *BlobTxWrapData) copy() TxWrapData {
Expand Down
96 changes: 82 additions & 14 deletions crypto/kzg/kzg.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@ package kzg

import (
"encoding/json"
"errors"

"github.com/ethereum/go-ethereum/crypto"

"github.com/ethereum/go-ethereum/params"
"github.com/protolambda/go-kzg/bls"
)

var crsG2 []bls.G2Point
var crsLagrange []bls.G1Point
var CrsG1 []bls.G1Point // only used in tests (for proof creation)
// KZG CRS for G2
var kzgSetupG2 []bls.G2Point

// KZG CRS for commitment computation
var kzgSetupLagrange []bls.G1Point

// KZG CRS for G1 (only used in tests (for proof creation))
var KzgSetupG1 []bls.G1Point

// Convert polynomial in evaluation form to KZG commitment
func BlobToKzg(eval []bls.Fr) *bls.G1Point {
return bls.LinCombG1(crsLagrange, eval)
return bls.LinCombG1(kzgSetupLagrange, eval)
}

// Verify a KZG proof
Expand All @@ -24,27 +30,89 @@ func VerifyKzgProof(commitment *bls.G1Point, x *bls.Fr, y *bls.Fr, proof *bls.G1
var xG2 bls.G2Point
bls.MulG2(&xG2, &bls.GenG2, x)
var sMinuxX bls.G2Point
bls.SubG2(&sMinuxX, &crsG2[1], &xG2)
bls.SubG2(&sMinuxX, &kzgSetupG2[1], &xG2)
var yG1 bls.G1Point
bls.MulG1(&yG1, &bls.GenG1, y)
var commitmentMinusY bls.G1Point
bls.SubG1(&commitmentMinusY, commitment, &yG1)

// This trick may be applied in the BLS-lib specific code:
//
// e([commitment - y], [1]) = e([proof], [s - x])
// equivalent to
// e([commitment - y]^(-1), [1]) * e([proof], [s - x]) = 1_T
//
return bls.PairingsVerify(&commitmentMinusY, &bls.GenG2, proof, &sMinuxX)
}

// Return versioned hash that corresponds to KZG commitment
func KzgToVersionedHash(commitment *bls.G1Point) [32]byte {
h := crypto.Keccak256Hash(bls.ToCompressedG1(commitment))
h[0] = byte(params.BlobCommitmentVersionKZG)
return h
}

// Verify that the list of `commitments` maps to the list of `blobs`
//
// This is an optimization over the naive approach (found in the EIP) of iteratively checking each blob against each
// commitment. The naive approach requires n*l scalar multiplications where `n` is the number of blobs and `l` is
// FIELD_ELEMENTS_PER_BLOB to compute the commitments for all blobs.
//
// A more efficient approach is to build a linear combination of all blobs and commitments and check all of them in a
// single multi-scalar multiplication.
//
// The MSM would look like this (for three blobs with two field elements each):
// r_0(b0_0*L_0 + b0_1*L_1) + r_1(b1_0*L_0 + b1_1*L_1) + r_2(b2_0*L_0 + b2_1*L_1)
// which we would need to check against the linear combination of commitments: r_0*C_0 + r_1*C_1 + r_2*C_2
// In the above, `r` are the random scalars of the linear combination, `b0` is the zero blob, `L` are the elements
// of the KZG_SETUP_LAGRANGE and `C` are the commitments provided.
//
// By regrouping the above equation around the `L` points we can reduce the length of the MSM further
// (down to just `n` scalar multiplications) by making it look like this:
// (r_0*b0_0 + r_1*b1_0 + r_2*b2_0) * L_0 + (r_0*b0_1 + r_1*b1_1 + r_2*b2_1) * L_1
func VerifyBlobs(commitments []*bls.G1Point, blobs [][]bls.Fr) error {
// Prepare objects to hold our two MSMs
lPoints := make([]bls.G1Point, params.FieldElementsPerBlob)
lScalars := make([]bls.Fr, params.FieldElementsPerBlob)
rPoints := make([]bls.G1Point, len(commitments))
rScalars := make([]bls.Fr, len(commitments))

// Generate list of random scalars for lincomb
rList := make([]bls.Fr, len(blobs))
for i := 0; i < len(blobs); i++ {
bls.CopyFr(&rList[i], bls.RandomFr())
}

// Build left-side MSM:
// (r_0*b0_0 + r_1*b1_0 + r_2*b2_0) * L_0 + (r_0*b0_1 + r_1*b1_1 + r_2*b2_1) * L_1
for c := 0; c < params.FieldElementsPerBlob; c++ {
var sum bls.Fr
for i := 0; i < len(blobs); i++ {
var tmp bls.Fr

r := rList[i]
blob := blobs[i]

bls.MulModFr(&tmp, &r, &blob[c])
bls.AddModFr(&sum, &sum, &tmp)
}
lScalars[c] = sum
lPoints[c] = kzgSetupLagrange[c]
}

// Build right-side MSM: r_0 * C_0 + r_1 * C_1 + r_2 * C_2 + ...
for i, commitment := range commitments {
rScalars[i] = rList[i]
rPoints[i] = *commitment
}

// Compute both MSMs and check equality
lResult := bls.LinCombG1(lPoints, lScalars)
rResult := bls.LinCombG1(rPoints, rScalars)
if !bls.EqualG1(lResult, rResult) {
return errors.New("VerifyBlobs failed")
}

// TODO: Potential improvement is to unify both MSMs into a single MSM, but you would need to batch-invert the `r`s
// of the right-side MSM to effectively pull them to the left side.

return nil
}

type JSONTrustedSetup struct {
SetupG1 []bls.G1Point
SetupG2 []bls.G2Point
Expand All @@ -61,7 +129,7 @@ func init() {
panic(err)
}

crsG2 = parsedSetup.SetupG2
crsLagrange = parsedSetup.SetupLagrange
CrsG1 = parsedSetup.SetupG1
kzgSetupG2 = parsedSetup.SetupG2
kzgSetupLagrange = parsedSetup.SetupLagrange
KzgSetupG1 = parsedSetup.SetupG1
}
1 change: 1 addition & 0 deletions tests/kzg_testdata/kzg_blobs.json

Large diffs are not rendered by default.

92 changes: 84 additions & 8 deletions tests/sharding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package tests

import (
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"math"
"strings"
"testing"

"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"

"github.com/ethereum/go-ethereum/crypto/kzg"
Expand Down Expand Up @@ -73,10 +76,9 @@ func ComputeProof(poly []bls.Fr, x uint64, crsG1 []bls.G1Point) *bls.G1Point {
return bls.LinCombG1(crsG1[:len(quotientPolynomial)], quotientPolynomial)
}

// Test the go-kzg library for correctness
// Do the trusted setup, generate a polynomial, commit to it, make proof, verify proof.
func TestGoKzg(t *testing.T) {
/// Test the go-kzg library for correctness
/// Do the trusted setup, generate a polynomial, commit to it, make proof, verify proof.

// Generate roots of unity
fs := gokzg.NewFFTSettings(uint8(math.Log2(params.FieldElementsPerBlob)))

Expand Down Expand Up @@ -114,7 +116,7 @@ func TestGoKzg(t *testing.T) {

// Create proof for testing
x := uint64(17)
proof := ComputeProof(polynomial, x, kzg.CrsG1)
proof := ComputeProof(polynomial, x, kzg.KzgSetupG1)

// Get actual evaluation at x
var xFr bls.Fr
Expand All @@ -128,9 +130,8 @@ func TestGoKzg(t *testing.T) {
}
}

// Test the geth KZG module (use our trusted setup instead of creating a new one)
func TestKzg(t *testing.T) {
/// Test the geth KZG module (use our trusted setup instead of creating a new one)

// First let's do some go-kzg preparations to be able to convert polynomial between coefficient and evaluation form
fs := gokzg.NewFFTSettings(uint8(math.Log2(params.FieldElementsPerBlob)))

Expand All @@ -152,7 +153,7 @@ func TestKzg(t *testing.T) {

// Create proof for testing
x := uint64(17)
proof := ComputeProof(polynomial, x, kzg.CrsG1)
proof := ComputeProof(polynomial, x, kzg.KzgSetupG1)

// Get actual evaluation at x
var xFr bls.Fr
Expand All @@ -167,6 +168,80 @@ func TestKzg(t *testing.T) {
}
}

type JSONTestdataBlobs struct {
KzgBlob1 string
KzgBlob2 string
}

// Test the optimized VerifyBlobs function
func TestVerifyBlobs(t *testing.T) {
data, err := ioutil.ReadFile("kzg_testdata/kzg_blobs.json")
if err != nil {
t.Fatal(err)
}

var jsonBlobs JSONTestdataBlobs
err = json.Unmarshal(data, &jsonBlobs)
if err != nil {
t.Fatal(err)
}

// Pack all those bytes into two blobs
var blob1 types.Blob
var blob2 types.Blob
for i := 0; i < params.FieldElementsPerBlob; i++ {
var tmp [32]byte
// Be conservative and only pack 31 bytes per Fr element
copy(tmp[:32], []byte(jsonBlobs.KzgBlob1[i*31:(i+1)*31]))
blob1 = append(blob1, tmp)
copy(tmp[:32], []byte(jsonBlobs.KzgBlob2[i*31:(i+1)*31]))
blob2 = append(blob2, tmp)
}

// Compute KZG commitments for both of the blobs above
kzg1, ok1 := blob1.ComputeCommitment()
kzg2, ok2 := blob2.ComputeCommitment()
if ok1 == false || ok2 == false {
panic("failed to compute commitments")
}

// Create the dummy object with all that data we prepared
blob_data := types.BlobTxWrapData{
BlobKzgs: []types.KZGCommitment{kzg1, kzg2},
Blobs: []types.Blob{blob1, blob2},
}

// Extract cryptographic material out of the blobs/commitments
commitments, err := blob_data.BlobKzgs.Commitments()
if err != nil {
panic("internal commitments")
}
blobs, err := blob_data.Blobs.Blobs()
if err != nil {
panic("internal blobs")
}

// Verify the blobs against the commitments!!
err = kzg.VerifyBlobs(commitments, blobs)
if err != nil {
panic("bad verifyBlobs")
}

// Now let's do a bad case:
// mutate a single chunk of a single blob and VerifyBlobs() must fail
blob1[42][1] = 0x42
blobs, err = blob_data.Blobs.Blobs()
if err != nil {
panic("internal blobs")
}

err = kzg.VerifyBlobs(commitments, blobs)
if err == nil {
panic("bad VerifyBlobs actually succeeded")
}
}

// Helper: Create test vector for the BlobVerification precompile
func TestBlobVerificationTestVector(t *testing.T) {
data := []byte(strings.Repeat("HELPMELOVEME ", 10083))[:params.FieldElementsPerBlob*32]

Expand All @@ -186,6 +261,7 @@ func TestBlobVerificationTestVector(t *testing.T) {
fmt.Printf("%d\n", len(testVector))
}

// Helper: Create test vector for the PointEvaluation precompile
func TestPointEvaluationTestVector(t *testing.T) {
fs := gokzg.NewFFTSettings(uint8(math.Log2(params.FieldElementsPerBlob)))

Expand All @@ -206,7 +282,7 @@ func TestPointEvaluationTestVector(t *testing.T) {

// Create proof for testing
x := uint64(0x42)
proof := ComputeProof(polynomial, x, kzg.CrsG1)
proof := ComputeProof(polynomial, x, kzg.KzgSetupG1)

// Get actual evaluation at x
var xFr bls.Fr
Expand Down