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) { err := LoadTrustedSetupFile("../../src/trusted_setup.txt") if err != nil { 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, err := BlobToKZGCommitment(blob) if err == nil { 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, err := ComputeKZGProof(blob, z) if err == nil { 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, err := ComputeBlobKZGProof(blob, commitment) if err == nil { 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, err := VerifyKZGProof(commitment, z, y, proof) if err == nil { 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, err := VerifyBlobKZGProof(blob, commitment, proof) if err == nil { 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, err := VerifyBlobKZGProofBatch(blobs, commitments, proofs) if err == nil { 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, err := BlobToKZGCommitment(blob) require.NoError(b, err) proof, err := ComputeBlobKZGProof(blob, Bytes48(commitment)) require.NoError(b, err) 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) } }) } }