From 0afccb412e2b0d2a182d13d1f42f6528307802ae Mon Sep 17 00:00:00 2001 From: Mamy Ratsimbazafy Date: Wed, 13 Dec 2023 06:54:57 +0100 Subject: [PATCH] Ethereum EIP-4844: Go wrapper for KZG commitments (#318) * Go API: initial compilation * cleanup KZG header * go: add KZG prototypes * KZG: Pass tests with Go API and -modfile=../go_test.mod * Go KZG: Add Go to CI * kzg go: fixes * don't use direct linking on Windows, .lib files are rejected * MacOS fscanf needs an extra char in the buffer * go fmt * typo --- .github/workflows/ci.yml | 59 ++- .gitignore | 9 + bindings/lib_constantine.nim | 1 + constantine-go/constantine.go | 180 +++++++ constantine-go/constantine_test.go | 440 ++++++++++++++++++ constantine.nimble | 3 +- constantine/ethereum_eip4844_kzg.nim | 14 +- .../trusted_setups/ethereum_kzg_srs.nim | 18 +- go.mod | 3 + go.sum | 0 go_test.mod | 13 + go_test.sum | 9 + .../protocols/ethereum_eip4844_kzg.h | 125 +++-- .../constantine/protocols/ethereum_kzg4844.h | 174 ------- 14 files changed, 770 insertions(+), 278 deletions(-) create mode 100644 constantine-go/constantine.go create mode 100644 constantine-go/constantine_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 go_test.mod create mode 100644 go_test.sum delete mode 100644 include/constantine/protocols/ethereum_kzg4844.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dde695d51..00b624da7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,7 @@ jobs: matrix: nim_version: [version-1-6] # [version-1-4, devel] rust_toolchain: [stable] # [beta, nightly] + go_toolchain: [stable] target: - os: linux cpu: i386 @@ -78,21 +79,21 @@ jobs: access_token: ${{ github.token }} - name: Checkout constantine - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: path: constantine - name: Restore MinGW-W64 (Windows) from cache if: runner.os == 'Windows' id: windows-mingw-cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: external/mingw-${{ matrix.target.cpu }} key: 'mingw-${{ matrix.target.cpu }}' - name: Restore Nim DLLs dependencies (Windows) from cache if: runner.os == 'Windows' id: windows-dlls-cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: external/dlls-${{ matrix.target.cpu }} key: 'dlls-${{ matrix.target.cpu }}' @@ -224,32 +225,23 @@ jobs: update: false install: base-devel git mingw-w64-x86_64-toolchain - - name: Install test dependencies (Windows) - if: runner.os == 'Windows' - shell: msys2 {0} - run: | - pacman -S --needed --noconfirm mingw-w64-x86_64-gmp mingw-w64-x86_64-llvm - nimble refresh --verbose -y - nimble install --verbose -y gmp jsony asynctools yaml@1.1.0 - - - name: Install test dependencies - if: runner.os != 'Windows' - shell: bash - run: | - nimble refresh --verbose -y - nimble install --verbose -y gmp jsony asynctools yaml@1.1.0 + - name: Install Go [${{ matrix.go_toolchain }}] + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go_toolchain }} - - name: Install Rust + - name: Install Rust [${{ matrix.rust_toolchain }}] shell: bash run: rustup update ${{ matrix.rust_toolchain }} && rustup default ${{ matrix.rust_toolchain }} - - name: Print Nim, Rust, LLVM versions and CPU specs. + - name: Print Nim, Go Rust, LLVM versions and CPU specs. shell: bash # gcc is an alias to Apple Clang on MacOS run: | nim -v gcc -v clang -v + go version rustup --version if [[ '${{ matrix.target.cpu }}' != 'i386' && '${{ runner.os }}' != 'Windows' ]]; then llvm-config --version @@ -263,6 +255,27 @@ jobs: sysctl -a | grep hw.optional fi + - name: Install test dependencies (Windows) + if: runner.os == 'Windows' + shell: msys2 {0} + run: | + pacman -S --needed --noconfirm mingw-w64-x86_64-gmp mingw-w64-x86_64-llvm + nimble refresh --verbose -y + nimble install --verbose -y gmp jsony asynctools yaml@1.1.0 + + cd constantine + go mod download -modfile=go_test.mod + + - name: Install test dependencies + if: runner.os != 'Windows' + shell: bash + run: | + nimble refresh --verbose -y + nimble install --verbose -y gmp jsony asynctools yaml@1.1.0 + + cd constantine + go mod download -modfile=go_test.mod + - name: Run Constantine as C library tests (UNIX with Assembly) if: runner.os != 'Windows' && matrix.target.BACKEND == 'ASM' shell: bash @@ -300,6 +313,14 @@ jobs: nimble make_headers --verbose nimble test_lib --verbose + - name: Run Constantine as Go library tests + # This reuses the static library built with `nimble make_lib` + if: matrix.target.cpu != 'i386' + shell: bash + run: | + cd constantine/constantine-go + go test -modfile=../go_test.mod + - name: Run Constantine as Rust library tests (with Assembly) if: matrix.target.BACKEND == 'ASM' && matrix.target.cpu != 'i386' shell: bash diff --git a/.gitignore b/.gitignore index 62bcb2b78..b17171963 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Executables shall be put in an ignored build/ directory # Ignore dynamic, static libs and libtool archive files +# ----------------------------------------------------------------------------------------- build/ *.so *.so.* @@ -11,6 +12,10 @@ build/ *.exe *.out +# Go +# ----------------------------------------------------------------------------------------- +*.test + # Nim # ----------------------------------------------------------------------------------------- nimcache/ @@ -37,6 +42,10 @@ Cargo.lock # ----------------------------------------------------------------------------------------- *.sage.py +# MacOS +# ----------------------------------------------------------------------------------------- +*.dylib.dSYM/ + # Swap or debug # ----------------------------------------------------------------------------------------- *.swp diff --git a/bindings/lib_constantine.nim b/bindings/lib_constantine.nim index 77ae9e091..0c619ce17 100644 --- a/bindings/lib_constantine.nim +++ b/bindings/lib_constantine.nim @@ -21,6 +21,7 @@ import ../constantine/csprngs, # Protocols ../constantine/ethereum_bls_signatures, + ../constantine/trusted_setups/ethereum_kzg_srs, ../constantine/ethereum_eip4844_kzg, # Ensure globals like proc from kernel32.dll are populated at library load time diff --git a/constantine-go/constantine.go b/constantine-go/constantine.go new file mode 100644 index 000000000..75ad4b6ee --- /dev/null +++ b/constantine-go/constantine.go @@ -0,0 +1,180 @@ +/** Constantine + * Copyright (c) 2018-2019 Status Research & Development GmbH + * Copyright (c) 2020-Present Mamy André-Ratsimbazafy + * Licensed and distributed under either of + * * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). + * * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). + * at your option. This file may not be copied, modified, or distributed except according to those terms. + */ + +package constantine + +/* +#cgo CFLAGS: -I"${SRCDIR}/../include" +#cgo !windows LDFLAGS: "${SRCDIR}/../lib/libconstantine.a" +// The ending in .lib is rejected, so we can't use the direct linking syntax: +// https://github.com/golang/go/blob/46ea4ab/src/cmd/go/internal/work/security.go#L216 +// #cgo windows LDFLAGS: "${SRCDIR}/../lib/constantine.lib" +#cgo windows LDFLAGS: -L"${SRCDIR}/../lib" -Wl,-Bstatic -lconstantine -Wl,-Bdynamic + +#include +#include +*/ +import "C" +import ( + "errors" + "unsafe" +) + +// Threadpool API +// ----------------------------------------------------- + +type Threadpool struct { + ctx *C.ctt_threadpool +} + +func ThreadpoolNew(numThreads int) Threadpool { + return Threadpool{ + ctx: C.ctt_threadpool_new(C.size_t(numThreads)), + } +} + +func (tp Threadpool) Shutdown() { + C.ctt_threadpool_shutdown(tp.ctx) +} + +// Ethereum EIP-4844 KZG API +// ----------------------------------------------------- + +type ( + EthKzgCommitment [48]byte + EthKzgProof [48]byte + EthBlob [4096 * 32]byte + EthKzgChallenge [32]byte + EthKzgEvalAtChallenge [32]byte +) + +type EthKzgContext struct { + cCtx *C.ctt_eth_kzg_context +} + +func EthKzgContextNew(trustedSetupFile string) (ctx EthKzgContext, err error) { + cFile := C.CString(trustedSetupFile) + defer C.free(unsafe.Pointer(cFile)) + status := C.ctt_eth_trusted_setup_load( + &ctx.cCtx, + cFile, + C.cttEthTSFormat_ckzg4844, + ) + if status != C.cttEthTS_Success { + err = errors.New( + C.GoString(C.ctt_eth_trusted_setup_status_to_string(status)), + ) + } + return ctx, err +} + +func (ctx EthKzgContext) Delete() { + C.ctt_eth_trusted_setup_delete(ctx.cCtx) +} + +func (ctx EthKzgContext) BlobToKZGCommitment(blob EthBlob) (commitment EthKzgCommitment, err error) { + status := C.ctt_eth_kzg_blob_to_kzg_commitment( + ctx.cCtx, + (*C.ctt_eth_kzg_commitment)(unsafe.Pointer(&commitment)), + (*C.ctt_eth_kzg_blob)(unsafe.Pointer(&blob)), + ) + if status != C.cttEthKzg_Success { + err = errors.New( + C.GoString(C.ctt_eth_kzg_status_to_string(status)), + ) + } + return commitment, err +} + +func (ctx EthKzgContext) ComputeKzgProof(blob EthBlob, z EthKzgChallenge) (proof EthKzgProof, y EthKzgEvalAtChallenge, err error) { + status := C.ctt_eth_kzg_compute_kzg_proof( + ctx.cCtx, + (*C.ctt_eth_kzg_proof)(unsafe.Pointer(&proof)), + (*C.ctt_eth_kzg_eval_at_challenge)(unsafe.Pointer(&y)), + (*C.ctt_eth_kzg_blob)(unsafe.Pointer(&blob)), + (*C.ctt_eth_kzg_challenge)(unsafe.Pointer(&z)), + ) + if status != C.cttEthKzg_Success { + err = errors.New( + C.GoString(C.ctt_eth_kzg_status_to_string(status)), + ) + } + return proof, y, err +} + +func (ctx EthKzgContext) VerifyKzgProof(commitment EthKzgCommitment, z EthKzgChallenge, y EthKzgEvalAtChallenge, proof EthKzgProof) (bool, error) { + status := C.ctt_eth_kzg_verify_kzg_proof( + ctx.cCtx, + (*C.ctt_eth_kzg_commitment)(unsafe.Pointer(&commitment)), + (*C.ctt_eth_kzg_challenge)(unsafe.Pointer(&z)), + (*C.ctt_eth_kzg_eval_at_challenge)(unsafe.Pointer(&y)), + (*C.ctt_eth_kzg_proof)(unsafe.Pointer(&proof)), + ) + if status != C.cttEthKzg_Success { + err := errors.New( + C.GoString(C.ctt_eth_kzg_status_to_string(status)), + ) + return false, err + } + return true, nil +} + +func (ctx EthKzgContext) ComputeBlobKzgProof(blob EthBlob, commitment EthKzgCommitment) (proof EthKzgProof, err error) { + status := C.ctt_eth_kzg_compute_blob_kzg_proof( + ctx.cCtx, + (*C.ctt_eth_kzg_proof)(unsafe.Pointer(&proof)), + (*C.ctt_eth_kzg_blob)(unsafe.Pointer(&blob)), + (*C.ctt_eth_kzg_commitment)(unsafe.Pointer(&commitment)), + ) + if status != C.cttEthKzg_Success { + err = errors.New( + C.GoString(C.ctt_eth_kzg_status_to_string(status)), + ) + } + return proof, err +} + +func (ctx EthKzgContext) VerifyBlobKzgProof(blob EthBlob, commitment EthKzgCommitment, proof EthKzgProof) (bool, error) { + status := C.ctt_eth_kzg_verify_blob_kzg_proof( + ctx.cCtx, + (*C.ctt_eth_kzg_blob)(unsafe.Pointer(&blob)), + (*C.ctt_eth_kzg_commitment)(unsafe.Pointer(&commitment)), + (*C.ctt_eth_kzg_proof)(unsafe.Pointer(&proof)), + ) + if status != C.cttEthKzg_Success { + err := errors.New( + C.GoString(C.ctt_eth_kzg_status_to_string(status)), + ) + return false, err + } + return true, nil +} + +func (ctx EthKzgContext) VerifyBlobKzgProofBatch(blobs []EthBlob, commitments []EthKzgCommitment, proofs []EthKzgProof, secureRandomBytes [32]byte) (bool, error) { + + if len(blobs) != len(commitments) || len(blobs) != len(proofs) { + return false, errors.New("VerifyBlobKzgProofBatch: Lengths of inputs do not match.") + } + + status := C.ctt_eth_kzg_verify_blob_kzg_proof_batch( + ctx.cCtx, + *(**C.ctt_eth_kzg_blob)(unsafe.Pointer(&blobs)), + *(**C.ctt_eth_kzg_commitment)(unsafe.Pointer(&commitments)), + *(**C.ctt_eth_kzg_proof)(unsafe.Pointer(&proofs)), + (C.size_t)(len(blobs)), + (*C.uint8_t)(unsafe.Pointer(&secureRandomBytes)), + ) + if status != C.cttEthKzg_Success { + err := errors.New( + C.GoString(C.ctt_eth_kzg_status_to_string(status)), + ) + return false, err + } + return true, nil +} diff --git a/constantine-go/constantine_test.go b/constantine-go/constantine_test.go new file mode 100644 index 000000000..219b263d8 --- /dev/null +++ b/constantine-go/constantine_test.go @@ -0,0 +1,440 @@ +/** Constantine + * Copyright (c) 2018-2019 Status Research & Development GmbH + * Copyright (c) 2020-Present Mamy André-Ratsimbazafy + * Licensed and distributed under either of + * * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). + * * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). + * at your option. This file may not be copied, modified, or distributed except according to those terms. + */ + +package constantine + +import ( + "crypto/rand" + "encoding/hex" + "errors" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +// Threadpool smoke test +// ---------------------------------------------------------- + +func TestThreadpool(t *testing.T) { + tp := ThreadpoolNew(runtime.NumCPU()) + tp.Shutdown() +} + +// Ethereum EIP-4844 KZG tests +// ---------------------------------------------------------- +// +// Source: https://github.com/ethereum/c-kzg-4844 + +var ( + trustedSetupFile = "../constantine/trusted_setups/trusted_setup_ethereum_kzg4844_reference.dat" + testDir = "../tests/protocol_ethereum_eip4844_deneb_kzg" + 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 fromHexImpl(dst []byte, input []byte) error { + s := string(input) + if strings.HasPrefix(s, "0x") { + s = s[2:] + } + bytes, err := hex.DecodeString(s) + if err != nil { + return err + } + if len(bytes) != len(dst) { + return errors.New( + "Length of input doesn't match expected length.", + ) + } + copy(dst, bytes) + return nil +} + +func (dst *EthKzgCommitment) UnmarshalText(input []byte) error { + return fromHexImpl(dst[:], input) +} +func (dst *EthKzgProof) UnmarshalText(input []byte) error { + return fromHexImpl(dst[:], input) +} +func (dst *EthBlob) UnmarshalText(input []byte) error { + return fromHexImpl(dst[:], input) +} +func (dst *EthKzgChallenge) UnmarshalText(input []byte) error { + return fromHexImpl(dst[:], input) +} +func (dst *EthKzgEvalAtChallenge) UnmarshalText(input []byte) error { + return fromHexImpl(dst[:], input) +} + +func TestBlobToKZGCommitment(t *testing.T) { + type Test struct { + Input struct { + Blob string `yaml:"blob"` + } + Output *EthKzgCommitment `yaml:"output"` + } + + ctx, tsErr := EthKzgContextNew(trustedSetupFile) + require.NoError(t, tsErr) + defer ctx.Delete() + + 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 EthBlob + err = blob.UnmarshalText([]byte(test.Input.Blob)) + if err != nil { + require.Nil(t, test.Output) + return + } + + commitment, err := ctx.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"` + } + + ctx, tsErr := EthKzgContextNew(trustedSetupFile) + require.NoError(t, tsErr) + defer ctx.Delete() + + 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 EthBlob + err = blob.UnmarshalText([]byte(test.Input.Blob)) + if err != nil { + require.Nil(t, test.Output) + return + } + + var z EthKzgChallenge + err = z.UnmarshalText([]byte(test.Input.Z)) + if err != nil { + require.Nil(t, test.Output) + return + } + + proof, y, err := ctx.ComputeKzgProof(blob, z) + if err == nil { + require.NotNil(t, test.Output) + var expectedProof EthKzgProof + err = expectedProof.UnmarshalText([]byte((*test.Output)[0])) + require.NoError(t, err) + require.Equal(t, expectedProof[:], proof[:]) + var expectedY EthKzgEvalAtChallenge + err = expectedY.UnmarshalText([]byte((*test.Output)[1])) + require.NoError(t, err) + require.Equal(t, expectedY[:], y[:]) + } 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"` + } + + ctx, tsErr := EthKzgContextNew(trustedSetupFile) + require.NoError(t, tsErr) + defer ctx.Delete() + + 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 EthKzgCommitment + err = commitment.UnmarshalText([]byte(test.Input.Commitment)) + if err != nil { + require.Nil(t, test.Output) + return + } + + var z EthKzgChallenge + err = z.UnmarshalText([]byte(test.Input.Z)) + if err != nil { + require.Nil(t, test.Output) + return + } + + var y EthKzgEvalAtChallenge + err = y.UnmarshalText([]byte(test.Input.Y)) + if err != nil { + require.Nil(t, test.Output) + return + } + + var proof EthKzgProof + err = proof.UnmarshalText([]byte(test.Input.Proof)) + if err != nil { + require.Nil(t, test.Output) + return + } + + valid, err := ctx.VerifyKzgProof(commitment, z, y, proof) + if err == nil { + require.NotNil(t, test.Output) + require.Equal(t, *test.Output, valid) + } else { + if test.Output != nil { + require.Equal(t, *test.Output, valid) + } + } + }) + } +} + +func TestComputeBlobKzgProof(t *testing.T) { + type Test struct { + Input struct { + Blob string `yaml:"blob"` + Commitment string `yaml:"commitment"` + } + Output *EthKzgProof `yaml:"output"` + } + + ctx, tsErr := EthKzgContextNew(trustedSetupFile) + require.NoError(t, tsErr) + defer ctx.Delete() + + 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 EthBlob + err = blob.UnmarshalText([]byte(test.Input.Blob)) + if err != nil { + require.Nil(t, test.Output) + return + } + + var commitment EthKzgCommitment + err = commitment.UnmarshalText([]byte(test.Input.Commitment)) + if err != nil { + require.Nil(t, test.Output) + return + } + + proof, err := ctx.ComputeBlobKzgProof(blob, commitment) + if err == nil { + require.NotNil(t, test.Output) + require.Equal(t, test.Output[:], proof[:]) + } 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"` + } + + ctx, tsErr := EthKzgContextNew(trustedSetupFile) + require.NoError(t, tsErr) + defer ctx.Delete() + + 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 EthBlob + err = blob.UnmarshalText([]byte(test.Input.Blob)) + if err != nil { + require.Nil(t, test.Output) + return + } + + var commitment EthKzgCommitment + err = commitment.UnmarshalText([]byte(test.Input.Commitment)) + if err != nil { + require.Nil(t, test.Output) + return + } + + var proof EthKzgProof + err = proof.UnmarshalText([]byte(test.Input.Proof)) + if err != nil { + require.Nil(t, test.Output) + return + } + + valid, err := ctx.VerifyBlobKzgProof(blob, commitment, proof) + if err == nil { + require.NotNil(t, test.Output) + require.Equal(t, *test.Output, valid) + } else { + if test.Output != nil { + require.Equal(t, *test.Output, valid) + } + } + }) + } +} + +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"` + } + + ctx, tsErr := EthKzgContextNew(trustedSetupFile) + require.NoError(t, tsErr) + defer ctx.Delete() + + var secureRandomBytes [32]byte + _, _ = rand.Read(secureRandomBytes[:]) + + 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 []EthBlob + for _, b := range test.Input.Blobs { + var blob EthBlob + err = blob.UnmarshalText([]byte(b)) + if err != nil { + require.Nil(t, test.Output) + return + } + blobs = append(blobs, blob) + } + + var commitments []EthKzgCommitment + for _, c := range test.Input.Commitments { + var commitment EthKzgCommitment + err = commitment.UnmarshalText([]byte(c)) + if err != nil { + require.Nil(t, test.Output) + return + } + commitments = append(commitments, commitment) + } + + var proofs []EthKzgProof + for _, p := range test.Input.Proofs { + var proof EthKzgProof + err = proof.UnmarshalText([]byte(p)) + if err != nil { + require.Nil(t, test.Output) + return + } + proofs = append(proofs, proof) + } + + valid, err := ctx.VerifyBlobKzgProofBatch(blobs, commitments, proofs, secureRandomBytes) + if err == nil { + require.NotNil(t, test.Output) + require.Equal(t, *test.Output, valid) + } else { + if test.Output != nil { + require.Equal(t, *test.Output, valid) + } + } + }) + } +} diff --git a/constantine.nimble b/constantine.nimble index 6621d8f0c..820daa5f6 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -174,7 +174,8 @@ proc releaseBuildOptions(buildMode = bmBinary): string = # to consolidate all objects into one. let ltoFlags = " -d:lto " & # " --UseAsmSyntaxIntel --passC:-flto=auto --passL:-flto=auto " # With LTO, the GCC linker produces lots of spurious warnings when copying into openArrays/strings - " --passC:-Wno-stringop-overflow --passL:-Wno-stringop-overflow " + " --passC:-Wno-stringop-overflow --passL:-Wno-stringop-overflow " & + " --passC:-Wno-alloc-size-larger-than --passL:-Wno-alloc-size-larger-than " let apple = defined(macos) or defined(macox) or defined(ios) let ltoOptions = if useLtoDefault: diff --git a/constantine/ethereum_eip4844_kzg.nim b/constantine/ethereum_eip4844_kzg.nim index 6989d0366..b15d71ba3 100644 --- a/constantine/ethereum_eip4844_kzg.nim +++ b/constantine/ethereum_eip4844_kzg.nim @@ -43,7 +43,7 @@ export trusted_setup_load, trusted_setup_delete, TrustedSetupFormat, TrustedSetu ## - Audited reference implementation ## https://github.com/ethereum/c-kzg-4844 -const prefix_eth_kzg_4844 = "ctt_eth_kzg4844_" +const prefix_eth_kzg4844 = "ctt_eth_kzg_" import ./zoo_exports # Constants @@ -269,7 +269,7 @@ template check(Section: untyped, evalExpr: CttCodecEccStatus): untyped {.dirty.} func blob_to_kzg_commitment*( ctx: ptr EthereumKZGContext, dst: var array[48, byte], - blob: ptr Blob): cttEthKzgStatus = + blob: ptr Blob): cttEthKzgStatus {.libPrefix: prefix_eth_kzg4844, tags:[Alloca, HeapAlloc, Vartime].} = ## Compute a commitment to the `blob`. ## The commitment can be verified without needing the full `blob` ## @@ -306,7 +306,7 @@ func compute_kzg_proof*( proof_bytes: var array[48, byte], y_bytes: var array[32, byte], blob: ptr Blob, - z_bytes: array[32, byte]): cttEthKzgStatus {.tags:[Alloca, HeapAlloc, Vartime].} = + z_bytes: array[32, byte]): cttEthKzgStatus {.libPrefix: prefix_eth_kzg4844, tags:[Alloca, HeapAlloc, Vartime].} = ## Generate: ## - A proof of correct evaluation. ## - y = p(z), the evaluation of p at the challenge z, with p being the Blob interpreted as a polynomial. @@ -353,7 +353,7 @@ func verify_kzg_proof*( commitment_bytes: array[48, byte], z_bytes: array[32, byte], y_bytes: array[32, byte], - proof_bytes: array[48, byte]): cttEthKzgStatus {.tags:[Alloca, Vartime].} = + proof_bytes: array[48, byte]): cttEthKzgStatus {.libPrefix: prefix_eth_kzg4844, tags:[Alloca, Vartime].} = ## Verify KZG proof that p(z) == y where p(z) is the polynomial represented by "polynomial_kzg" var commitment {.noInit.}: KZGCommitment @@ -381,7 +381,7 @@ func compute_blob_kzg_proof*( ctx: ptr EthereumKZGContext, proof_bytes: var array[48, byte], blob: ptr Blob, - commitment_bytes: array[48, byte]): cttEthKzgStatus {.tags:[Alloca, HeapAlloc, Vartime].} = + commitment_bytes: array[48, byte]): cttEthKzgStatus {.libPrefix: prefix_eth_kzg4844, tags:[Alloca, HeapAlloc, Vartime].} = ## Given a blob, return the KZG proof that is used to verify it against the commitment. ## This method does not verify that the commitment is correct with respect to `blob`. @@ -420,7 +420,7 @@ func verify_blob_kzg_proof*( ctx: ptr EthereumKZGContext, blob: ptr Blob, commitment_bytes: array[48, byte], - proof_bytes: array[48, byte]): cttEthKzgStatus {.tags:[Alloca, HeapAlloc, Vartime].} = + proof_bytes: array[48, byte]): cttEthKzgStatus {.libPrefix: prefix_eth_kzg4844, tags:[Alloca, HeapAlloc, Vartime].} = ## Given a blob and a KZG proof, verify that the blob data corresponds to the provided commitment. var commitment {.noInit.}: KZGCommitment @@ -482,7 +482,7 @@ func verify_blob_kzg_proof_batch*( commitments_bytes: ptr UncheckedArray[array[48, byte]], proof_bytes: ptr UncheckedArray[array[48, byte]], n: int, - secureRandomBytes: array[32, byte]): cttEthKzgStatus {.tags:[Alloca, HeapAlloc, Vartime].} = + secureRandomBytes: array[32, byte]): cttEthKzgStatus {.libPrefix: prefix_eth_kzg4844, tags:[Alloca, HeapAlloc, Vartime].} = ## Verify `n` (blob, commitment, proof) sets efficiently ## ## `n` is the number of verifications set diff --git a/constantine/trusted_setups/ethereum_kzg_srs.nim b/constantine/trusted_setups/ethereum_kzg_srs.nim index e5810892b..dc8dcd945 100644 --- a/constantine/trusted_setups/ethereum_kzg_srs.nim +++ b/constantine/trusted_setups/ethereum_kzg_srs.nim @@ -115,7 +115,7 @@ const KZG_SETUP_G2_LENGTH = 65 # are different from multiproofs type - EthereumKZGContext* {.exportc: "ctt_eth_kzg4844_context".} = object + EthereumKZGContext* = object ## KZG commitment context # Trusted setup, see https://vitalik.ca/general/2022/03/14/trustedsetup.html @@ -221,14 +221,14 @@ proc load_ckzg4844(ctx: ptr EthereumKZGContext, f: File): TrustedSetupStatus = block: # G1 points - 96 characters + newline - var bufG1Hex {.noInit.}: array[2*g1Bytes, char] + var bufG1Hex {.noInit.}: array[2*g1Bytes+1, char] # On MacOS, an extra byte seems to be needed for fscanf or AddressSanitizer complains var bufG1bytes {.noInit.}: array[g1Bytes, byte] var charsRead: cint for i in 0 ..< FIELD_ELEMENTS_PER_BLOB: - let num_matches = f.c_fscanf(readHexG1, bufG1Hex, charsRead.addr) + let num_matches = f.c_fscanf(readHexG1, bufG1Hex.addr, charsRead.addr) if num_matches != 1 and charsRead != 2*g1Bytes: return tsInvalidFile - bufG1bytes.fromHex(bufG1Hex) + bufG1bytes.fromHex(bufG1Hex.toOpenArray(0, 2*g1Bytes-1)) let status = ctx.srs_lagrange_g1.evals[i].deserialize_g1_compressed(bufG1bytes) if status != cttCodecEcc_Success: c_printf("[Constantine Trusted Setup] Invalid G1 point on line %d: CttCodecEccStatus code %d\n", cint(2+i), status) @@ -236,14 +236,14 @@ proc load_ckzg4844(ctx: ptr EthereumKZGContext, f: File): TrustedSetupStatus = block: # G2 points - 192 characters + newline - var bufG2Hex {.noInit.}: array[2*g2Bytes, char] + var bufG2Hex {.noInit.}: array[2*g2Bytes+1, char] # On MacOS, an extra byte seems to be needed for fscanf or AddressSanitizer complains var bufG2bytes {.noInit.}: array[g2Bytes, byte] var charsRead: cint for i in 0 ..< KZG_SETUP_G2_LENGTH: - let num_matches = f.c_fscanf(readHexG2, bufG2Hex, charsRead.addr) + let num_matches = f.c_fscanf(readHexG2, bufG2Hex.addr, charsRead.addr) if num_matches != 1 and charsRead != 2*g2Bytes: return tsInvalidFile - bufG2bytes.fromHex(bufG2Hex) + bufG2bytes.fromHex(bufG2Hex.toOpenArray(0, 2*g2Bytes-1)) let status = ctx.srs_monomial_g2.coefs[i].deserialize_g2_compressed(bufG2bytes) if status != cttCodecEcc_Success: c_printf("[Constantine Trusted Setup] Invalid G2 point on line %d: CttCodecEccStatus code %d\n", cint(2+FIELD_ELEMENTS_PER_BLOB+i), status) @@ -271,7 +271,7 @@ proc load_ckzg4844(ctx: ptr EthereumKZGContext, f: File): TrustedSetupStatus = return tsSuccess -proc trusted_setup_load*(ctx: var ptr EthereumKZGContext, filepath: cstring, format: TrustedSetupFormat): TrustedSetupStatus {.libPrefix: prefix_eth_kzg4844.} = +proc trusted_setup_load*(ctx: var ptr EthereumKZGContext, filepath: cstring, format: TrustedSetupFormat): TrustedSetupStatus {.libPrefix: "ctt_eth_".} = ## Load trusted setup from path ## Currently the only format supported ## is from the reference implementation c-kzg-4844 text file @@ -289,6 +289,6 @@ proc trusted_setup_load*(ctx: var ptr EthereumKZGContext, filepath: cstring, for fileio.close(f) return status -proc trusted_setup_delete*(ctx: ptr EthereumKZGContext) {.libPrefix: prefix_eth_kzg4844.} = +proc trusted_setup_delete*(ctx: ptr EthereumKZGContext) {.libPrefix: "ctt_eth_".} = if not ctx.isNil: freeHeapAligned(ctx) diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..58704a913 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/mratsim/constantine + +go 1.21.5 diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..e69de29bb diff --git a/go_test.mod b/go_test.mod new file mode 100644 index 000000000..8f94d6af8 --- /dev/null +++ b/go_test.mod @@ -0,0 +1,13 @@ +module github.com/mratsim/constantine + +go 1.21.5 + +require ( + github.com/stretchr/testify v1.8.4 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect +) \ No newline at end of file diff --git a/go_test.sum b/go_test.sum new file mode 100644 index 000000000..8cf66553b --- /dev/null +++ b/go_test.sum @@ -0,0 +1,9 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/include/constantine/protocols/ethereum_eip4844_kzg.h b/include/constantine/protocols/ethereum_eip4844_kzg.h index 068e9ac18..bdafc3d45 100644 --- a/include/constantine/protocols/ethereum_eip4844_kzg.h +++ b/include/constantine/protocols/ethereum_eip4844_kzg.h @@ -10,39 +10,21 @@ #define __CTT_H_ETHEREUM_EIP4844_KZG__ #include "constantine/core/datatypes.h" -#include "constantine/core/serialization.h" #ifdef __cplusplus extern "C" { #endif -// EIP4844 KZG sizes +// Ethereum EIP-4844 KZG types // ------------------------------------------------------------------------------------------------ -/** The number of bytes in a KZG commitment. */ -#define BYTES_PER_COMMITMENT 48 +typedef struct ctt_eth_kzg_context_struct ctt_eth_kzg_context; -/** The number of bytes in a KZG proof. */ -#define BYTES_PER_PROOF 48 - -/** The number of bytes in a BLS scalar field element. */ -#define BYTES_PER_FIELD_ELEMENT 32 - -/** The number of field elements in a blob. */ -#define FIELD_ELEMENTS_PER_BLOB 4096 - -/** The number of bytes in a blob. */ -#define BYTES_PER_BLOB (FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT) - -// EIP4844 KZG commitment types -// ------------------------------------------------------------------------------------------------ - -typedef struct ctt_eth_kzg_context ctt_eth_kzg_context; - -struct ctt_eth_kzg_fp { byte raw[48]; }; - -typedef struct { struct ctt_eth_kzg_fp x, y; } ctt_eth_kzg_commitment; -typedef struct { struct ctt_eth_kzg_fp x, y; } ctt_eth_kzg_proof; +typedef struct { byte raw[48]; } ctt_eth_kzg_commitment; +typedef struct { byte raw[48]; } ctt_eth_kzg_proof; +typedef struct { byte raw[4096 * 32]; } ctt_eth_kzg_blob; +typedef struct { byte raw[32]; } ctt_eth_kzg_challenge; +typedef struct { byte raw[32]; } ctt_eth_kzg_eval_at_challenge; typedef enum __attribute__((__packed__)) { cttEthKzg_Success, @@ -52,7 +34,7 @@ typedef enum __attribute__((__packed__)) { cttEthKzg_EccInvalidEncoding, cttEthKzg_EccCoordinateGreaterThanOrEqualModulus, cttEthKzg_EccPointNotOnCurve, - cttEthKzg_EccPointNotInSubGroup, + cttEthKzg_EccPointNotInSubgroup, } ctt_eth_kzg_status; static const char* ctt_eth_kzg_status_to_string(ctt_eth_kzg_status status) { @@ -64,7 +46,7 @@ static const char* ctt_eth_kzg_status_to_string(ctt_eth_kzg_status status) { "cttEthKzg_EccInvalidEncoding", "cttEthKzg_EccCoordinateGreaterThanOrEqualModulus", "cttEthKzg_EccPointNotOnCurve", - "cttEthKzg_EccPointNotInSubGroup", + "cttEthKzg_EccPointNotInSubgroup", }; size_t length = sizeof statuses / sizeof *statuses; if (0 <= status && status < length) { @@ -96,9 +78,9 @@ typedef enum __attribute__((__packed__)) { cttEthTSFormat_ckzg4844, } ctt_eth_trusted_setup_format; -// EIP4844 KZG API -// ------------------------------------------------------------------------------------------------ +// Ethereum EIP-4844 KZG Interface +// ------------------------------------------------------------------------------------------------ /** Compute a commitment to the `blob`. * The commitment can be verified without needing the full `blob` @@ -118,9 +100,9 @@ typedef enum __attribute__((__packed__)) { * with proof = [(p(τ) - p(z)) / (τ-z)]₁ */ ctt_eth_kzg_status ctt_eth_kzg_blob_to_kzg_commitment( - const ctt_eth_kzg_context* ctx, - byte dst[BYTES_PER_COMMITMENT], - byte blob[BYTES_PER_BLOB] + const ctt_eth_kzg_context* ctx, + ctt_eth_kzg_commitment* dst, + const ctt_eth_kzg_blob* blob ) __attribute__((warn_unused_result)); /** Generate: @@ -138,65 +120,71 @@ ctt_eth_kzg_status ctt_eth_kzg_blob_to_kzg_commitment( * - and at the verification challenge z. */ ctt_eth_kzg_status ctt_eth_kzg_compute_kzg_proof( - const ctt_eth_kzg_context* ctx, - byte proof[BYTES_PER_PROOF], - byte y[BYTES_PER_FIELD_ELEMENT], - byte blob[BYTES_PER_BLOB], - byte z[BYTES_PER_FIELD_ELEMENT] + const ctt_eth_kzg_context* ctx, + ctt_eth_kzg_proof* proof, + ctt_eth_kzg_eval_at_challenge* y, + const ctt_eth_kzg_blob* blob, + const ctt_eth_kzg_challenge* z ) __attribute__((warn_unused_result)); -/** Verify a short KZG proof that p(z) == y - * where p is the blob polynomial used for the commitment. +/** Verify KZG proof + * that p(z) == y where + * - z is a random challenge + * - y is the evaluation of the "KZG polynomial" p at z + * - commitment is p(τ), the evaluation of p at the trusted setup τ, + * - [proof]₁ = [(p(τ) - p(z)) / (τ-z)]₁, ensure that p(z) evaluation was correct + * without needing access to the polynomial p itself. */ ctt_eth_kzg_status ctt_eth_kzg_verify_kzg_proof( - const ctt_eth_kzg_context* ctx, - byte commitment[BYTES_PER_COMMITMENT], - byte z[BYTES_PER_FIELD_ELEMENT], - byte y[BYTES_PER_FIELD_ELEMENT], - byte proof[BYTES_PER_PROOF] + const ctt_eth_kzg_context* ctx, + const ctt_eth_kzg_commitment* commitment, + const ctt_eth_kzg_challenge* z, + const ctt_eth_kzg_eval_at_challenge* y, + const ctt_eth_kzg_proof* proof ) __attribute__((__warn_unused_result__)); /** Given a blob, return the KZG proof that is used to verify it against the commitment. * This method does not verify that the commitment is correct with respect to `blob`. */ ctt_eth_kzg_status ctt_eth_kzg_compute_blob_kzg_proof( - const ctt_eth_kzg_context* ctx, - byte proof[BYTES_PER_PROOF], - byte blob[BYTES_PER_BLOB], - byte commitment[BYTES_PER_COMMITMENT] + const ctt_eth_kzg_context* ctx, + ctt_eth_kzg_proof* proof, + const ctt_eth_kzg_blob* blob, + const ctt_eth_kzg_commitment* commitment ) __attribute__((__warn_unused_result__)); /** Given a blob and a KZG proof, verify that the blob data corresponds to the provided commitment. */ ctt_eth_kzg_status ctt_eth_kzg_verify_blob_kzg_proof( - const ctt_eth_kzg_context* ctx, - byte blob[BYTES_PER_BLOB], - byte commitment[BYTES_PER_COMMITMENT], - byte proof[BYTES_PER_PROOF] + const ctt_eth_kzg_context* ctx, + const ctt_eth_kzg_blob* blob, + const ctt_eth_kzg_commitment* commitment, + const ctt_eth_kzg_proof* proof ) __attribute__((__warn_unused_result__)); /** Verify `n` (blob, commitment, proof) sets efficiently * - * `n` is the number of verifications set - * - if n is negative, this procedure returns verification failure - * - if n is zero, this procedure returns verification success + * `n` is the number of verifications set + * - if n is negative, this procedure returns verification failure + * - if n is zero, this procedure returns verification success * - * `secure_random_bytes` random byte must come from a cryptographically secure RNG - * or computed through the Fiat-Shamir heuristic. - * It serves as a random number - * that is not in the control of a potential attacker to prevent potential - * rogue commitments attacks due to homomorphic properties of pairings, - * i.e. commitments that are linear combination of others and sum would be zero. + * `secure_random_bytes` random bytes must come from a cryptographically secure RNG + * or computed through the Fiat-Shamir heuristic. + * It serves as a random number + * that is not in the control of a potential attacker to prevent potential + * rogue commitments attacks due to homomorphic properties of pairings, + * i.e. commitments that are linear combination of others and sum would be zero. */ ctt_eth_kzg_status ctt_eth_kzg_verify_blob_kzg_proof_batch( - const ctt_eth_kzg_context* ctx, - byte blob[][BYTES_PER_BLOB], - byte commitments[][BYTES_PER_COMMITMENT], - byte proofs[][BYTES_PER_PROOF], - size_t n, - byte secure_random_bytes[32] + const ctt_eth_kzg_context* ctx, + const ctt_eth_kzg_blob blobs[], + const ctt_eth_kzg_commitment commitments[], + const ctt_eth_kzg_proof proofs[], + size_t n, + const byte secure_random_bytes[32] ) __attribute__((__warn_unused_result__)); + // EIP4844 KZG Trusted setup // ------------------------------------------------------------------------------------------------ @@ -214,8 +202,9 @@ ctt_eth_trusted_setup_status ctt_eth_trusted_setup_load( */ void ctt_eth_trusted_setup_delete(ctt_eth_kzg_context* ctx); + #ifdef __cplusplus } #endif -#endif // __CTT_H_ETHEREUM_EIP4844_KZG__ \ No newline at end of file +#endif // __CTT_H_ETHEREUM_KZG4844_KZG__ diff --git a/include/constantine/protocols/ethereum_kzg4844.h b/include/constantine/protocols/ethereum_kzg4844.h deleted file mode 100644 index 9c07c7a83..000000000 --- a/include/constantine/protocols/ethereum_kzg4844.h +++ /dev/null @@ -1,174 +0,0 @@ - -/** Constantine - * Copyright (c) 2018-2019 Status Research & Development GmbH - * Copyright (c) 2020-Present Mamy André-Ratsimbazafy - * Licensed and distributed under either of - * * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). - * * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). - * at your option. This file may not be copied, modified, or distributed except according to those terms. - */ -#ifndef __CTT_H_ETHEREUM_KZG_4844__ -#define __CTT_H_ETHEREUM_KZG_4844__ - -#include "constantine/core/datatypes.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// Ethereum EIP-4844 KZG types -// ------------------------------------------------------------------------------------------------ - -#define BYTES_PER_COMMITMENT 48 -#define BYTES_PER_PROOF 48 -#define BYTES_PER_FIELD_ELEMENT 32 -#define FIELD_ELEMENTS_PER_BLOB 4096 -#define BYTES_PER_BLOB (FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT) - -typedef struct ctt_eth_kzg4844_commitment { byte raw[BYTES_PER_COMMITMENT]; } ctt_eth_kzg4844_commitment; -typedef struct ctt_eth_kzg4844_proof { byte raw[BYTES_PER_PROOF]; } ctt_eth_kzg4844_proof; -typedef struct ctt_eth_kzg4844_blob { byte raw[BYTES_PER_BLOB]; } ctt_eth_kzg4844_blob; -typedef struct ctt_eth_kzg4844_challenge { byte raw[BYTES_PER_FIELD_ELEMENT]; } ctt_eth_kzg4844_challenge; -typedef struct ctt_eth_kzg4844_eval_at_challenge { byte raw[BYTES_PER_FIELD_ELEMENT]; } ctt_eth_kzg4844_eval_at_challenge; - -typedef enum __attribute__((__packed__)) { - cttEthKzg_Success, - cttEthKzg_VerificationFailure, - cttEthKzg_ScalarZero, - cttEthKzg_ScalarLargerThanCurveOrder, - cttEthKzg_EccInvalidEncoding, - cttEthKzg_EccCoordinateGreaterThanOrEqualModulus, - cttEthKzg_EccPointNotOnCurve, - cttEthKzg_EccPointNotInSubgroup, -} ctt_eth_kzg4844_status; - -static const char* ctt_eth_kzg4844_status_to_string(ctt_eth_kzg4844_status status) { - static const char* const statuses[] = { - "cttEthKzg_Success", - "cttEthKzg_VerificationFailure", - "cttEthKzg_ScalarZero", - "cttEthKzg_ScalarLargerThanCurveOrder", - "cttEthKzg_EccInvalidEncoding", - "cttEthKzg_EccCoordinateGreaterThanOrEqualModulus", - "cttEthKzg_EccPointNotOnCurve", - "cttEthKzg_EccPointNotInSubgroup", - }; - size_t length = sizeof statuses / sizeof *statuses; - if (0 <= status && status < length) { - return statuses[status]; - } - return "cttEthKzg_InvalidStatusCode"; -} - -typedef struct ctt_eth_kzg4844_context_struct ctt_eth_kzg4844_context; - - -// Ethereum EIP-4844 KZG Interface -// ------------------------------------------------------------------------------------------------ - -/** Compute a commitment to the `blob`. - * The commitment can be verified without needing the full `blob` - * - * Mathematical description - * commitment = [p(τ)]₁ - * - * The blob data is used as a polynomial, - * the polynomial is evaluated at powers of tau τ, a trusted setup. - * - * Verification can be done by verifying the relation: - * proof.(τ - z) = p(τ)-p(z) - * which doesn't require the full blob but only evaluations of it - * - at τ, p(τ) is the commitment - * - and at the verification challenge z. - */ -ctt_eth_kzg4844_status ctt_eth_kzg4844_blob_to_kzg_commitment( - const ctt_eth_kzg4844_context* ctx, - ctt_eth_kzg4844_commitment* dst, - const ctt_eth_kzg4844_blob* blob -); - -/** Generate: - * - A proof of correct evaluation. - * - y = p(z), the evaluation of p at the challenge z, with p being the Blob interpreted as a polynomial. - * - * Mathematical description - * [proof]₁ = [(p(τ) - p(z)) / (τ-z)]₁, with p(τ) being the commitment, i.e. the evaluation of p at the powers of τ - * The notation [a]₁ corresponds to the scalar multiplication of a by the generator of 𝔾1 - * - * Verification can be done by verifying the relation: - * proof.(τ - z) = p(τ)-p(z) - * which doesn't require the full blob but only evaluations of it - * - at τ, p(τ) is the commitment - */ -ctt_eth_kzg4844_status ctt_eth_kzg4844_compute_kzg_proof( - const ctt_eth_kzg4844_context* ctx, - ctt_eth_kzg4844_proof* proof, - ctt_eth_kzg4844_eval_at_challenge* y, - const ctt_eth_kzg4844_blob* blob, - const ctt_eth_kzg4844_challenge* z -); - -/** Verify KZG proof - * that p(z) == y where - * - z is a random challenge - * - y is the evaluation of the "KZG polynomial" p at z - * - commitment is p(τ), the evaluation of p at the trusted setup τ, - * - [proof]₁ = [(p(τ) - p(z)) / (τ-z)]₁, ensure that p(z) evaluation was correct - * without needing access to the polynomial p itself. - */ -ctt_eth_kzg4844_status ctt_eth_kzg4844_verify_kzg_proof( - const ctt_eth_kzg4844_context* ctx, - const ctt_eth_kzg4844_commitment* commitment, - const ctt_eth_kzg4844_challenge* z, - const ctt_eth_kzg4844_eval_at_challenge* y, - const ctt_eth_kzg4844_proof* proof -); - -/** Given a blob, return the KZG proof that is used to verify it against the commitment. - * This method does not verify that the commitment is correct with respect to `blob`. - */ -ctt_eth_kzg4844_status ctt_eth_kzg4844_compute_blob_kzg_proof( - const ctt_eth_kzg4844_context* ctx, - ctt_eth_kzg4844_proof* proof, - const ctt_eth_kzg4844_blob* blob, - const ctt_eth_kzg4844_commitment* commitment -); - -/** Given a blob and a KZG proof, verify that the blob data corresponds to the provided commitment. - */ -ctt_eth_kzg4844_status ctt_eth_kzg4844_verify_blob_kzg_proof( - const ctt_eth_kzg4844_context* ctx, - const ctt_eth_kzg4844_blob* blob, - const ctt_eth_kzg4844_commitment* commitment, - const ctt_eth_kzg4844_proof* proof -); - -/** Verify `n` (blob, commitment, proof) sets efficiently - * - * `n` is the number of verifications set - * - if n is negative, this procedure returns verification failure - * - if n is zero, this procedure returns verification success - * - * `secure_random_bytes` random bytes must come from a cryptographically secure RNG - * or computed through the Fiat-Shamir heuristic. - * It serves as a random number - * that is not in the control of a potential attacker to prevent potential - * rogue commitments attacks due to homomorphic properties of pairings, - * i.e. commitments that are linear combination of others and sum would be zero. - */ -ctt_eth_kzg4844_status ctt_eth_kzg4844_verify_blob_kzg_proof_batch( - const ctt_eth_kzg4844_context* ctx, - const ctt_eth_kzg4844_blob blobs[], - const ctt_eth_kzg4844_commitment commitments[], - const ctt_eth_kzg4844_proof proofs[], - size_t n, - byte secure_random_bytes[32] -); - - - -#ifdef __cplusplus -} -#endif - -#endif