commit d0af4143e2598026e4dcf70dbb0acc97e9ef6175 Author: jangko Date: Thu Feb 9 01:41:29 2023 +0700 initial commit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..451e383 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,162 @@ +name: CI +on: + push: + branches: + - master + pull_request: + workflow_dispatch: + +jobs: + build: + strategy: + fail-fast: false + matrix: + target: + - os: linux + cpu: amd64 + - os: linux + cpu: i386 + - os: macos + cpu: amd64 + - os: windows + cpu: amd64 + - os: windows + cpu: i386 + branch: [version-1-2, version-1-4, version-1-6, devel] + include: + - target: + os: linux + builder: ubuntu-20.04 + shell: bash + - target: + os: macos + builder: macos-12 + shell: bash + - target: + os: windows + builder: windows-2019 + shell: msys2 {0} + + defaults: + run: + shell: ${{ matrix.shell }} + + name: '${{ matrix.target.os }}-${{ matrix.target.cpu }} (Nim ${{ matrix.branch }})' + runs-on: ${{ matrix.builder }} + steps: + - name: Checkout nim-kzg4844 + uses: actions/checkout@v3 + with: + submodules: true + + - name: Install build dependencies (Linux i386) + if: runner.os == 'Linux' && matrix.target.cpu == 'i386' + run: | + sudo dpkg --add-architecture i386 + sudo apt-fast update -qq + sudo DEBIAN_FRONTEND='noninteractive' apt-fast install \ + --no-install-recommends -yq gcc-multilib g++-multilib \ + libssl-dev:i386 + mkdir -p external/bin + cat << EOF > external/bin/gcc + #!/bin/bash + exec $(which gcc) -m32 "\$@" + EOF + cat << EOF > external/bin/g++ + #!/bin/bash + exec $(which g++) -m32 "\$@" + EOF + chmod 755 external/bin/gcc external/bin/g++ + echo '${{ github.workspace }}/external/bin' >> $GITHUB_PATH + + - name: MSYS2 (Windows i386) + if: runner.os == 'Windows' && matrix.target.cpu == 'i386' + uses: msys2/setup-msys2@v2 + with: + path-type: inherit + msystem: MINGW32 + install: >- + base-devel + git + mingw-w64-i686-toolchain + + - name: MSYS2 (Windows amd64) + if: runner.os == 'Windows' && matrix.target.cpu == 'amd64' + uses: msys2/setup-msys2@v2 + with: + path-type: inherit + install: >- + base-devel + git + mingw-w64-x86_64-toolchain + + - name: Restore Nim DLLs dependencies (Windows) from cache + if: runner.os == 'Windows' + id: windows-dlls-cache + uses: actions/cache@v3 + with: + path: external/dlls + key: 'dlls' + + - name: Install DLL dependencies (Windows) + if: > + steps.windows-dlls-cache.outputs.cache-hit != 'true' && + runner.os == 'Windows' + run: | + mkdir external + curl -L "https://nim-lang.org/download/windeps.zip" -o external/windeps.zip + 7z x external/windeps.zip -oexternal/dlls + + - name: Path to cached dependencies (Windows) + if: > + runner.os == 'Windows' + run: | + echo '${{ github.workspace }}'"/external/dlls" >> $GITHUB_PATH + + - name: Derive environment variables + run: | + if [[ '${{ matrix.target.cpu }}' == 'amd64' ]]; then + PLATFORM=x64 + else + PLATFORM=x86 + fi + echo "PLATFORM=$PLATFORM" >> $GITHUB_ENV + + ncpu= + MAKE_CMD="make" + case '${{ runner.os }}' in + 'Linux') + ncpu=$(nproc) + ;; + 'macOS') + ncpu=$(sysctl -n hw.ncpu) + ;; + 'Windows') + ncpu=$NUMBER_OF_PROCESSORS + MAKE_CMD="mingw32-make" + ;; + esac + [[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1 + echo "ncpu=$ncpu" >> $GITHUB_ENV + echo "MAKE_CMD=${MAKE_CMD}" >> $GITHUB_ENV + + - name: Build Nim and Nimble + run: | + curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh + env MAKE="${MAKE_CMD} -j${ncpu}" ARCH_OVERRIDE=${PLATFORM} NIM_COMMIT=${{ matrix.branch }} \ + QUICK_AND_DIRTY_COMPILER=1 QUICK_AND_DIRTY_NIMBLE=1 CC=gcc \ + bash build_nim.sh nim csources dist/nimble NimBinaries + echo '${{ github.workspace }}/nim/bin' >> $GITHUB_PATH + + - name: Run nim-kzg4844 tests + run: | + if [[ "${{ matrix.target.os }}" == "windows" ]]; then + # https://github.com/status-im/nimbus-eth2/issues/3121 + export NIMFLAGS="-d:nimRawSetjmp" + fi + nim --version + nimble --version + nimble install -y --depsOnly + rm -f nimble.lock + env TEST_LANG="c" nimble test + env TEST_LANG="cpp" nimble test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e5a29e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/build +nimble.develop +nimble.paths +*.exe diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..139f7d9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "kzg4844/csources"] + path = kzg4844/csources + url = https://github.com/ethereum/c-kzg-4844 diff --git a/README.md b/README.md new file mode 100644 index 0000000..ffb51d9 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# kzg4844 wrapper in nim + +![Github action](https://github.com/status-im/nim-kzg4844/workflows/CI/badge.svg) + +This is a Nim wrapper of original C implementation of [c-kzg-4844](https://github.com/ethereum/c-kzg-4844) + +## License + +Licensed and distributed under either of + +* MIT license: [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT + +or + +* Apache License, Version 2.0, ([LICENSE-APACHEv2](LICENSE-APACHEv2) or http://www.apache.org/licenses/LICENSE-2.0) + +at your option. These files may not be copied, modified, or distributed except according to those terms. diff --git a/kzg4844.nim b/kzg4844.nim new file mode 100644 index 0000000..5049a19 --- /dev/null +++ b/kzg4844.nim @@ -0,0 +1,15 @@ +# nim-kzg4844 +# Copyright (c) 2023 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +import + kzg4844/kzg + +export + kzg + diff --git a/kzg4844.nimble b/kzg4844.nimble new file mode 100644 index 0000000..84291ee --- /dev/null +++ b/kzg4844.nimble @@ -0,0 +1,33 @@ +# nim-kzg4844 +# Copyright (c) 2023 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +mode = ScriptMode.Verbose + +packageName = "kzg4844" +version = "0.1.0" +author = "Status Research & Development GmbH" +description = "c-kzg-4844 wrapper in Nim" +license = "Apache License 2.0" +skipDirs = @["tests"] + +requires "nim >= 1.2.0" +requires "stew >= 0.1.0" +requires "unittest2" + +# Helper functions +proc test(args, path: string) = + if not dirExists "build": + mkDir "build" + exec "nim " & getEnv("TEST_LANG", "c") & " " & getEnv("NIMFLAGS") & " " & args & + " --outdir:build -r -f --hints:off --warnings:off --skipParentCfg " & path + +task test, "Run all tests": + test "-d:debug", "tests/test_all" + test "-d:release", "tests/test_all" + test "--threads:on -d:release", "tests/test_all" diff --git a/kzg4844/csources b/kzg4844/csources new file mode 160000 index 0000000..5cfbc34 --- /dev/null +++ b/kzg4844/csources @@ -0,0 +1 @@ +Subproject commit 5cfbc341352f12330aaf8e5f5c4df14dca829f1a diff --git a/kzg4844/kzg.nim b/kzg4844/kzg.nim new file mode 100644 index 0000000..175db27 --- /dev/null +++ b/kzg4844/kzg.nim @@ -0,0 +1,190 @@ +# nim-kzg4844 +# Copyright (c) 2023 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +import + std/[streams, strutils], + kzg_abi, + stew/[results, byteutils] + +export + results, + kzg_abi + +type + KzgCtx* = ref object + val: KzgSettings + + G1Data* = array[48, byte] + G2Data* = array[96, byte] + + Bytes32 = array[32, byte] + +when (NimMajor, NimMinor) < (1, 4): + {.push raises: [Defect].} +else: + {.push raises: [].} + +############################################################## +# Private helpers +############################################################## + +proc destroy(x: KzgCtx) = + free_trusted_setup(x.val) + +proc newKzgCtx(): KzgCtx = + new(result, destroy) + +template getPtr(x: untyped): auto = + when (NimMajor, NimMinor) <= (1,6): + unsafeAddr(x) + else: + addr(x) + +template verify(res: KZG_RET) = + if res != KZG_OK: + return err($res) + +############################################################## +# Public functions +############################################################## + +proc loadTrustedSetup*(input: File): Result[KzgCtx, string] = + let + ctx = newKzgCtx() + res = load_trusted_setup_file(ctx.val, input) + + verify(res) + ok(ctx) + +proc loadTrustedSetup*(fileName: string): Result[KzgCtx, string] = + try: + let file = open(fileName) + result = file.loadTrustedSetup() + file.close() + except IOError as ex: + return err(ex.msg) + +proc loadTrustedSetup*(g1: openArray[G1Data], + g2: openArray[G2Data]): + Result[KzgCtx, string] = + if g1.len == 0 or g2.len == 0: + return err($KZG_BADARGS) + + let + ctx = newKzgCtx() + res = load_trusted_setup(ctx.val, + g1[0][0].getPtr, + g1.len.csize_t, + g2[0][0].getPtr, + g2.len.csize_t) + + verify(res) + ok(ctx) + +proc loadTrustedSetupFromString*(input: string): Result[KzgCtx, string] = + var + s = newStringStream(input) + g1: array[FIELD_ELEMENTS_PER_BLOB, G1Data] + g2: array[65, G2Data] + + try: + let fieldElems = s.readLine().parseInt() + doAssert fieldElems == FIELD_ELEMENTS_PER_BLOB + let numG2 = s.readLine().parseInt() + doAssert numG2 == 65 + + for i in 0 ..< FIELD_ELEMENTS_PER_BLOB: + g1[i] = hexToByteArray[48](s.readLine()) + + for i in 0 ..< 65: + g2[i] = hexToByteArray[96](s.readLine()) + except ValueError as ex: + return err(ex.msg) + except OSError as ex: + return err(ex.msg) + except IOError as ex: + return err(ex.msg) + + loadTrustedSetup(g1, g2) + +proc toCommitment*(ctx: KzgCtx, + blob: KzgBlob): + Result[KzgCommitment, string] = + var kate: KzgCommitment + let res = blob_to_kzg_commitment(kate, blob, ctx.val) + verify(res) + ok(kate) + +proc computeProof*(ctx: KzgCtx, + blobs: openArray[KzgBlob]): + Result[KzgProof, string] = + var proof: KzgProof + let res = compute_aggregate_kzg_proof( + proof, + blobs[0].getPtr, + blobs.len.csize_t, + ctx.val) + verify(res) + ok(proof) + +proc verifyProof*(ctx: KzgCtx, + blobs: openArray[KzgBlob], + commitments: openArray[KzgCommitment], + proof: KzgProof): Result[void, string] = + + if blobs.len == 0 or commitments.len == 0: + return err($KZG_BADARGS) + + if blobs.len != commitments.len: + return err($KZG_BADARGS) + + var ok: bool + let res = verify_aggregate_kzg_proof( + ok, + blobs[0].getPtr, + commitments[0].getPtr, + blobs.len.csize_t, + proof, + ctx.val) + verify(res) + if not ok: + return err($KZG_ERROR) + ok() + +proc computeProof*(ctx: KzgCtx, + blob: KzgBlob, + z: Bytes32): Result[KzgProof, string] = + var proof: KzgProof + let res = compute_kzg_proof( + proof, + blob, + z, + ctx.val) + verify(res) + ok(proof) + +proc verifyProof*(ctx: KzgCtx, + commitment: KzgCommitment, + z: Bytes32, # Input Point + y: Bytes32, # Claimed Value + proof: KzgProof): Result[void, string] = + var ok: bool + let res = verify_kzg_proof( + ok, + commitment, + z, + y, + proof, + ctx.val) + verify(res) + if not ok: + return err($KZG_ERROR) + ok() + +{. pop .} diff --git a/kzg4844/kzg_abi.nim b/kzg4844/kzg_abi.nim new file mode 100644 index 0000000..13fff03 --- /dev/null +++ b/kzg4844/kzg_abi.nim @@ -0,0 +1,118 @@ +# nim-kzg4844 +# Copyright (c) 2023 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +import + std/strformat, + strutils + +from os import DirSep + +const FIELD_ELEMENTS_PER_BLOB*{.strdefine.} = 4096 + +const + ckzgPath = currentSourcePath.rsplit(DirSep, 1)[0] & "/csources/" + blstPath = ckzgPath & "blst/" + srcPath = ckzgPath & "src/" + bindingsPath = blstPath & "bindings" + +when not defined(externalBlst): + {.compile: blstPath & "build/assembly.S".} + {.compile: blstPath & "src/server.c"} + +{.compile: srcPath & "c_kzg_4844.c"} + +{.passc: "-I" & bindingsPath & + " -DFIELD_ELEMENTS_PER_BLOB=" & + fmt"{FIELD_ELEMENTS_PER_BLOB}".} +{.passc: "-I" & srcPath .} + +const + BYTES_PER_FIELD_ELEMENT* = 32 + KzgBlobSize* = FIELD_ELEMENTS_PER_BLOB*BYTES_PER_FIELD_ELEMENT + +type + KZG_RET* = distinct cint + +const + KZG_OK* = (0).KZG_RET + KZG_BADARGS* = (1).KZG_RET + KZG_ERROR* = (2).KZG_RET + KZG_MALLOC* = (3).KZG_RET + +proc `$`*(x: KZG_RET): string = + case x + of KZG_OK: "ok" + of KZG_BADARGS: "kzg badargs" + of KZG_ERROR: "kzg error" + of KZG_MALLOC: "kzg malloc" + else: "kzg unknown error" + +proc `==`*(a, b: KZG_RET): bool = + a.cint == b.cint + +type + KzgBlob* = array[KzgBlobSize, byte] + + KzgSettings* {.importc: "KZGSettings", + header: "c_kzg_4844.h", byref.} = object + + Bytes48 = array[48, byte] + Bytes32 = array[32, byte] + + KzgCommitment* = Bytes48 + KzgProof* = Bytes48 + +{.pragma: kzg_abi, cdecl, header: "c_kzg_4844.h".} + +proc load_trusted_setup*(res: KzgSettings, + g1Bytes: ptr byte, # n1 * 48 bytes + n1: csize_t, + g2Bytes: ptr byte, # n2 * 96 bytes + n2: csize_t): KZG_RET {.kzg_abi, + importc: "load_trusted_setup".} + +proc load_trusted_setup_file*(res: KzgSettings, + input: File): KZG_RET {.kzg_abi, + importc: "load_trusted_setup_file" .} + +proc free_trusted_setup*(s: KzgSettings) {.kzg_abi, + importc: "free_trusted_setup" .} + +proc blob_to_kzg_commitment*(res: var KzgCommitment, + blob: KzgBlob, + s: KzgSettings): KZG_RET {.kzg_abi, + importc: "blob_to_kzg_commitment" .} + +proc compute_aggregate_kzg_proof*(res: var KzgProof, + blobs: ptr KzgBlob, + n: csize_t, + s: KzgSettings): KZG_RET {.kzg_abi, + importc: "compute_aggregate_kzg_proof" .} + +proc verify_aggregate_kzg_proof*(re: var bool, + blobs: ptr KzgBlob, + commitmentsBytes: ptr KzgCommitment, + n: csize_t, + aggregatedProofBytes: KzgProof, + s: KzgSettings): KZG_RET {.kzg_abi, + importc: "verify_aggregate_kzg_proof" .} + +proc compute_kzg_proof*(res: var KzgProof, + blob: KzgBlob, + zBytes: Bytes32, + s: KzgSettings): KZG_RET {.kzg_abi, + importc: "compute_kzg_proof" .} + +proc verify_kzg_proof*(res: var bool, + commitmentBytes: KzgCommitment, + zBytes: Bytes32, + yBytes: Bytes32, + proofBytes: KzgProof, + s: KzgSettings): KZG_RET {.kzg_abi, + importc: "verify_kzg_proof" .} diff --git a/tests/config.nims b/tests/config.nims new file mode 100644 index 0000000..bda1e13 --- /dev/null +++ b/tests/config.nims @@ -0,0 +1,2 @@ +--styleCheck:usages +--styleCheck:error diff --git a/tests/test_abi.nim b/tests/test_abi.nim new file mode 100644 index 0000000..0445770 --- /dev/null +++ b/tests/test_abi.nim @@ -0,0 +1,118 @@ +# nim-kzg4844 +# Copyright (c) 2023 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +{.used.} + +import + std/[sysrand, streams, strutils], + unittest2, + stew/byteutils, + ../kzg4844/kzg_abi, + ./types + +proc readSetup(): KzgSettings = + var + s = newFileStream(trustedSetupFile) + g1Bytes: array[FIELD_ELEMENTS_PER_BLOB * 48, byte] + g2Bytes: array[65 * 96, byte] + + doAssert(s.isNil.not, + "FAILED TO OPEN: " & trustedSetupFile) + + let fieldElems = s.readLine().parseInt() + doAssert fieldElems == FIELD_ELEMENTS_PER_BLOB + let numG2 = s.readLine().parseInt() + doAssert numG2 == 65 + + for i in 0 ..< FIELD_ELEMENTS_PER_BLOB: + let z = hexToByteArray[48](s.readLine()) + g1Bytes[i*48 ..< i*48+48] = z[0..<48] + + for i in 0 ..< 65: + let z = hexToByteArray[96](s.readLine()) + g2Bytes[i*96 ..< i*96+96] = z[0..<96] + + let res = load_trusted_setup(result, + g1Bytes[0].addr, FIELD_ELEMENTS_PER_BLOB, + g2Bytes[0].addr, 65) + + doAssert(res == KZG_OK, + "ERROR: " & $res) + +proc readSetup(filename: string): KzgSettings = + var file = open(filename) + let ret = load_trusted_setup_file(result, file) + doAssert ret == KZG_OK + file.close() + +proc createKateBlobs(s: KzgSettings, n: int): KateBlobs = + for i in 0.. MAX_TOP_BYTE and i %% BYTES_PER_FIELD_ELEMENT == 31: + blob[i] = MAX_TOP_BYTE + result.blobs.add(blob) + + for i in 0.. MAX_TOP_BYTE and i %% BYTES_PER_FIELD_ELEMENT == 31: + blob[i] = MAX_TOP_BYTE + result.blobs.add(blob) + + for i in 0..