c-kzg-4844/bindings/go/main_test.go

526 lines
13 KiB
Go

package cgokzg4844
import (
"encoding/hex"
"errors"
"fmt"
"math/rand"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
func TestMain(m *testing.M) {
ret := LoadTrustedSetupFile("../../src/trusted_setup.txt")
if ret != 0 {
panic("failed to load trusted setup")
}
defer FreeTrustedSetup()
code := m.Run()
os.Exit(code)
}
///////////////////////////////////////////////////////////////////////////////
// Helper Functions
///////////////////////////////////////////////////////////////////////////////
func (f *Bytes32) UnmarshalText(input []byte) error {
bytes, err := hex.DecodeString(string(input[2:]))
if err != nil {
return err
}
if len(bytes) != len(f) {
return errors.New("invalid Bytes32")
}
copy(f[:], bytes)
return nil
}
func (f *Bytes48) UnmarshalText(input []byte) error {
bytes, err := hex.DecodeString(string(input[2:]))
if err != nil {
return err
}
if len(bytes) != len(f) {
return errors.New("invalid Bytes48")
}
copy(f[:], bytes)
return nil
}
func (b *Blob) UnmarshalText(input []byte) error {
blobBytes, err := hex.DecodeString(string(input[2:]))
if err != nil {
return err
}
if len(blobBytes) != len(b) {
return errors.New("invalid Blob")
}
copy(b[:], blobBytes)
return nil
}
func GetRandFieldElement(seed int64) Bytes32 {
rand.Seed(seed)
bytes := make([]byte, 31)
_, err := rand.Read(bytes)
if err != nil {
panic("failed to get random field element")
}
// This leaves the last byte in fieldElementBytes as
// zero, which guarantees it's a canonical field element.
var fieldElementBytes Bytes32
copy(fieldElementBytes[:], bytes)
return fieldElementBytes
}
func GetRandBlob(seed int64) Blob {
var blob Blob
for i := 0; i < BytesPerBlob; i += BytesPerFieldElement {
fieldElementBytes := GetRandFieldElement(seed + int64(i))
copy(blob[i:i+BytesPerFieldElement], fieldElementBytes[:])
}
return blob
}
/*
HumanBytes will convert an integer to a human-readable value. Adapted from:
https://programming.guide/go/formatting-byte-size-to-human-readable-format.html
*/
func HumanBytes(b int64) string {
const unit = 1024
if b < unit {
return fmt.Sprintf("%dB", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%v%cB", float64(b)/float64(div), "KMGTPE"[exp])
}
///////////////////////////////////////////////////////////////////////////////
// Tests
///////////////////////////////////////////////////////////////////////////////
var (
testDir = "../../tests"
blobToKZGCommitmentTests = filepath.Join(testDir, "blob_to_kzg_commitment/*/*/*")
computeKZGProofTests = filepath.Join(testDir, "compute_kzg_proof/*/*/*")
computeBlobKZGProofTests = filepath.Join(testDir, "compute_blob_kzg_proof/*/*/*")
verifyKZGProofTests = filepath.Join(testDir, "verify_kzg_proof/*/*/*")
verifyBlobKZGProofTests = filepath.Join(testDir, "verify_blob_kzg_proof/*/*/*")
verifyBlobKZGProofBatchTests = filepath.Join(testDir, "verify_blob_kzg_proof_batch/*/*/*")
)
func TestBlobToKZGCommitment(t *testing.T) {
type Test struct {
Input struct {
Blob string `yaml:"blob"`
}
Output *Bytes48 `yaml:"output"`
}
tests, err := filepath.Glob(blobToKZGCommitmentTests)
require.NoError(t, err)
require.True(t, len(tests) > 0)
for _, testPath := range tests {
t.Run(testPath, func(t *testing.T) {
testFile, err := os.Open(testPath)
require.NoError(t, err)
test := Test{}
err = yaml.NewDecoder(testFile).Decode(&test)
require.NoError(t, testFile.Close())
require.NoError(t, err)
var blob Blob
err = blob.UnmarshalText([]byte(test.Input.Blob))
if err != nil {
require.Nil(t, test.Output)
return
}
commitment, ret := BlobToKZGCommitment(blob)
if ret == C_KZG_OK {
require.NotNil(t, test.Output)
require.Equal(t, test.Output[:], commitment[:])
} else {
require.Nil(t, test.Output)
}
})
}
}
func TestComputeKZGProof(t *testing.T) {
type Test struct {
Input struct {
Blob string `yaml:"blob"`
Z string `yaml:"z"`
}
Output *[]string `yaml:"output"`
}
tests, err := filepath.Glob(computeKZGProofTests)
require.NoError(t, err)
require.True(t, len(tests) > 0)
for _, testPath := range tests {
t.Run(testPath, func(t *testing.T) {
testFile, err := os.Open(testPath)
require.NoError(t, err)
test := Test{}
err = yaml.NewDecoder(testFile).Decode(&test)
require.NoError(t, testFile.Close())
require.NoError(t, err)
var blob Blob
err = blob.UnmarshalText([]byte(test.Input.Blob))
if err != nil {
require.Nil(t, test.Output)
return
}
var z Bytes32
err = z.UnmarshalText([]byte(test.Input.Z))
if err != nil {
require.Nil(t, test.Output)
return
}
proof, y, ret := ComputeKZGProof(blob, z)
if ret == C_KZG_OK {
require.NotNil(t, test.Output)
var expectedProof Bytes48
err = expectedProof.UnmarshalText([]byte((*test.Output)[0]))
require.NoError(t, err)
require.Equal(t, expectedProof[:], proof[:])
var expectedY Bytes32
err = expectedY.UnmarshalText([]byte((*test.Output)[1]))
require.NoError(t, err)
require.Equal(t, expectedY[:], y[:])
} else {
require.Nil(t, test.Output)
}
})
}
}
func TestComputeBlobKZGProof(t *testing.T) {
type Test struct {
Input struct {
Blob string `yaml:"blob"`
Commitment string `yaml:"commitment"`
}
Output *Bytes48 `yaml:"output"`
}
tests, err := filepath.Glob(computeBlobKZGProofTests)
require.NoError(t, err)
require.True(t, len(tests) > 0)
for _, testPath := range tests {
t.Run(testPath, func(t *testing.T) {
testFile, err := os.Open(testPath)
require.NoError(t, err)
test := Test{}
err = yaml.NewDecoder(testFile).Decode(&test)
require.NoError(t, testFile.Close())
require.NoError(t, err)
var blob Blob
err = blob.UnmarshalText([]byte(test.Input.Blob))
if err != nil {
require.Nil(t, test.Output)
return
}
var commitment Bytes48
err = commitment.UnmarshalText([]byte(test.Input.Commitment))
if err != nil {
require.Nil(t, test.Output)
return
}
proof, ret := ComputeBlobKZGProof(blob, commitment)
if ret == C_KZG_OK {
require.NotNil(t, test.Output)
require.Equal(t, test.Output[:], proof[:])
} else {
require.Nil(t, test.Output)
}
})
}
}
func TestVerifyKZGProof(t *testing.T) {
type Test struct {
Input struct {
Commitment string `yaml:"commitment"`
Z string `yaml:"z"`
Y string `yaml:"y"`
Proof string `yaml:"proof"`
}
Output *bool `yaml:"output"`
}
tests, err := filepath.Glob(verifyKZGProofTests)
require.NoError(t, err)
require.True(t, len(tests) > 0)
for _, testPath := range tests {
t.Run(testPath, func(t *testing.T) {
testFile, err := os.Open(testPath)
require.NoError(t, err)
test := Test{}
err = yaml.NewDecoder(testFile).Decode(&test)
require.NoError(t, testFile.Close())
require.NoError(t, err)
var commitment Bytes48
err = commitment.UnmarshalText([]byte(test.Input.Commitment))
if err != nil {
require.Nil(t, test.Output)
return
}
var z Bytes32
err = z.UnmarshalText([]byte(test.Input.Z))
if err != nil {
require.Nil(t, test.Output)
return
}
var y Bytes32
err = y.UnmarshalText([]byte(test.Input.Y))
if err != nil {
require.Nil(t, test.Output)
return
}
var proof Bytes48
err = proof.UnmarshalText([]byte(test.Input.Proof))
if err != nil {
require.Nil(t, test.Output)
return
}
valid, ret := VerifyKZGProof(commitment, z, y, proof)
if ret == C_KZG_OK {
require.NotNil(t, test.Output)
require.Equal(t, *test.Output, valid)
} else {
require.Nil(t, test.Output)
}
})
}
}
func TestVerifyBlobKZGProof(t *testing.T) {
type Test struct {
Input struct {
Blob string `yaml:"blob"`
Commitment string `yaml:"commitment"`
Proof string `yaml:"proof"`
}
Output *bool `yaml:"output"`
}
tests, err := filepath.Glob(verifyBlobKZGProofTests)
require.NoError(t, err)
require.True(t, len(tests) > 0)
for _, testPath := range tests {
t.Run(testPath, func(t *testing.T) {
testFile, err := os.Open(testPath)
require.NoError(t, err)
test := Test{}
err = yaml.NewDecoder(testFile).Decode(&test)
require.NoError(t, testFile.Close())
require.NoError(t, err)
var blob Blob
err = blob.UnmarshalText([]byte(test.Input.Blob))
if err != nil {
require.Nil(t, test.Output)
return
}
var commitment Bytes48
err = commitment.UnmarshalText([]byte(test.Input.Commitment))
if err != nil {
require.Nil(t, test.Output)
return
}
var proof Bytes48
err = proof.UnmarshalText([]byte(test.Input.Proof))
if err != nil {
require.Nil(t, test.Output)
return
}
valid, ret := VerifyBlobKZGProof(blob, commitment, proof)
if ret == C_KZG_OK {
require.NotNil(t, test.Output)
require.Equal(t, *test.Output, valid)
} else {
require.Nil(t, test.Output)
}
})
}
}
func TestVerifyBlobKZGProofBatch(t *testing.T) {
type Test struct {
Input struct {
Blobs []string `yaml:"blobs"`
Commitments []string `yaml:"commitments"`
Proofs []string `yaml:"proofs"`
}
Output *bool `yaml:"output"`
}
tests, err := filepath.Glob(verifyBlobKZGProofBatchTests)
require.NoError(t, err)
require.True(t, len(tests) > 0)
for _, testPath := range tests {
t.Run(testPath, func(t *testing.T) {
testFile, err := os.Open(testPath)
require.NoError(t, err)
test := Test{}
err = yaml.NewDecoder(testFile).Decode(&test)
require.NoError(t, testFile.Close())
require.NoError(t, err)
var blobs []Blob
for _, b := range test.Input.Blobs {
var blob Blob
err = blob.UnmarshalText([]byte(b))
if err != nil {
require.Nil(t, test.Output)
return
}
blobs = append(blobs, blob)
}
var commitments []Bytes48
for _, c := range test.Input.Commitments {
var commitment Bytes48
err = commitment.UnmarshalText([]byte(c))
if err != nil {
require.Nil(t, test.Output)
return
}
commitments = append(commitments, commitment)
}
var proofs []Bytes48
for _, p := range test.Input.Proofs {
var proof Bytes48
err = proof.UnmarshalText([]byte(p))
if err != nil {
require.Nil(t, test.Output)
return
}
proofs = append(proofs, proof)
}
valid, ret := VerifyBlobKZGProofBatch(blobs, commitments, proofs)
if ret == C_KZG_OK {
require.NotNil(t, test.Output)
require.Equal(t, *test.Output, valid)
} else {
require.Nil(t, test.Output)
}
})
}
}
///////////////////////////////////////////////////////////////////////////////
// Benchmarks
///////////////////////////////////////////////////////////////////////////////
func Benchmark(b *testing.B) {
const length = 64
blobs := [length]Blob{}
commitments := [length]Bytes48{}
proofs := [length]Bytes48{}
fields := [length]Bytes32{}
for i := 0; i < length; i++ {
blob := GetRandBlob(int64(i))
commitment, ret := BlobToKZGCommitment(blob)
require.Equal(b, ret, C_KZG_OK)
proof, ret := ComputeBlobKZGProof(blob, Bytes48(commitment))
require.Equal(b, ret, C_KZG_OK)
blobs[i] = blob
commitments[i] = Bytes48(commitment)
proofs[i] = Bytes48(proof)
fields[i] = GetRandFieldElement(int64(i))
}
///////////////////////////////////////////////////////////////////////////
// Public functions
///////////////////////////////////////////////////////////////////////////
b.Run("BlobToKZGCommitment", func(b *testing.B) {
for n := 0; n < b.N; n++ {
BlobToKZGCommitment(blobs[0])
}
})
b.Run("ComputeKZGProof", func(b *testing.B) {
for n := 0; n < b.N; n++ {
ComputeKZGProof(blobs[0], fields[0])
}
})
b.Run("ComputeBlobKZGProof", func(b *testing.B) {
for n := 0; n < b.N; n++ {
ComputeBlobKZGProof(blobs[0], commitments[0])
}
})
b.Run("VerifyKZGProof", func(b *testing.B) {
for n := 0; n < b.N; n++ {
VerifyKZGProof(commitments[0], fields[0], fields[1], proofs[0])
}
})
b.Run("VerifyBlobKZGProof", func(b *testing.B) {
for n := 0; n < b.N; n++ {
VerifyBlobKZGProof(blobs[0], commitments[0], proofs[0])
}
})
for i := 1; i <= len(blobs); i *= 2 {
b.Run(fmt.Sprintf("VerifyBlobKZGProofBatch(count=%v)", i), func(b *testing.B) {
for n := 0; n < b.N; n++ {
VerifyBlobKZGProofBatch(blobs[:i], commitments[:i], proofs[:i])
}
})
}
///////////////////////////////////////////////////////////////////////////
// Private functions
///////////////////////////////////////////////////////////////////////////
for i := 2; i <= 20; i += 2 {
numBytes := int64(1 << i)
bytes := make([]byte, numBytes)
b.Run(fmt.Sprintf("sha256(size=%v)", HumanBytes(numBytes)), func(b *testing.B) {
b.SetBytes(numBytes)
for n := 0; n < b.N; n++ {
sha256(bytes)
}
})
}
}