From 0072efe70a367126e0db28e058de04202036560a Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Tue, 6 Dec 2022 12:58:56 +0000 Subject: [PATCH] Java binding - load library based on a preset --- .github/workflows/java-bindings-test.yml | 21 ++- bindings/java/.gitignore | 3 +- bindings/java/Makefile | 30 ++- bindings/java/README.md | 22 ++- bindings/java/build.gradle | 4 + bindings/java/c_kzg_4844_jni.c | 35 +++- bindings/java/c_kzg_4844_jni.h | 43 +++-- .../ckzg4844/CKZG4844JNIBenchmark.java | 93 ++++++++++ .../java/ethereum/ckzg4844/CKZG4844JNI.java | 142 ++++++++++++++ .../java/ethereum/ckzg4844/CKzg4844JNI.java | 100 ---------- .../ethereum/ckzg4844/CKZG4844JNITest.java | 152 +++++++++++++++ .../ethereum/ckzg4844/CKzg4844JNITest.java | 173 ------------------ .../java/ethereum/ckzg4844/TestUtils.java | 58 ++++++ src/Makefile | 3 +- 14 files changed, 563 insertions(+), 316 deletions(-) create mode 100644 bindings/java/src/jmh/java/ethereum/ckzg4844/CKZG4844JNIBenchmark.java create mode 100644 bindings/java/src/main/java/ethereum/ckzg4844/CKZG4844JNI.java delete mode 100644 bindings/java/src/main/java/ethereum/ckzg4844/CKzg4844JNI.java create mode 100644 bindings/java/src/test/java/ethereum/ckzg4844/CKZG4844JNITest.java delete mode 100644 bindings/java/src/test/java/ethereum/ckzg4844/CKzg4844JNITest.java create mode 100644 bindings/java/src/testFixtures/java/ethereum/ckzg4844/TestUtils.java diff --git a/.github/workflows/java-bindings-test.yml b/.github/workflows/java-bindings-test.yml index a4fd997..92ef49b 100644 --- a/.github/workflows/java-bindings-test.yml +++ b/.github/workflows/java-bindings-test.yml @@ -10,7 +10,10 @@ on: jobs: test-java-bindings: - runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 with: @@ -19,7 +22,15 @@ jobs: with: distribution: "temurin" java-version: "11" - - name: Setup - run: cd src && make blst && make all - - name: Test - run: cd bindings/java && make build test + - name: Build blst + run: | + cd src + make blst + - name: Build and Test (mainnet preset) + run: | + cd bindings/java + make build test + - name: Build and Test (minimal preset) + run: | + cd bindings/java + make PRESET=minimal build test -B diff --git a/bindings/java/.gitignore b/bindings/java/.gitignore index ac34dbc..a62b543 100644 --- a/bindings/java/.gitignore +++ b/bindings/java/.gitignore @@ -3,4 +3,5 @@ build/ bin/ .idea/ .iml -*.o \ No newline at end of file +*.o +*.log \ No newline at end of file diff --git a/bindings/java/Makefile b/bindings/java/Makefile index 2a8833d..9e891bb 100644 --- a/bindings/java/Makefile +++ b/bindings/java/Makefile @@ -1,21 +1,19 @@ INCLUDE_DIRS = ../../src ../../blst/bindings -LIBRARY_FOLDER=src/main/resources/ethereum/ckzg4844/lib - -FIELD_ELEMENTS_PER_BLOB ?= 4096 +TARGETS=c_kzg_4844_jni.c ../../src/c_kzg_4844.c ../../lib/libblst.a CC_FLAGS= +OPTIMIZATION_LEVEL=-O2 ifeq ($(OS),Windows_NT) - CLANG_EXECUTABLE=clang - GRADLE_COMMAND=gradlew + CLANG_EXECUTABLE=gcc CLANG_FLAGS=-shared JNI_INCLUDE_FOLDER=win32 OS_ARCH=amd64 LIBRARY_RESOURCE=ckzg4844jni.dll + GRADLE_COMMAND=gradlew else CLANG_EXECUTABLE=clang - GRADLE_COMMAND=./gradlew UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) CLANG_FLAGS=-fPIC -shared @@ -32,18 +30,32 @@ else OS_ARCH=x86_64 LIBRARY_RESOURCE=libckzg4844jni.dylib endif + GRADLE_COMMAND=./gradlew endif +PRESET ?= mainnet + +ifeq ($(PRESET),minimal) + FIELD_ELEMENTS_PER_BLOB ?= 4 +else + FIELD_ELEMENTS_PER_BLOB ?= 4096 +endif + +LIBRARY_FOLDER=src/main/resources/ethereum/ckzg4844/lib/${OS_ARCH}/${PRESET} + ifeq ($(JAVA_HOME),) $(error JAVA_HOME is not set and autodetection failed) endif -all: build test +all: build test benchmark build: - mkdir -p ${LIBRARY_FOLDER}/${OS_ARCH} - ${CLANG_EXECUTABLE} ${CC_FLAGS} ${CLANG_FLAGS} -O -Wall ${addprefix -I,${INCLUDE_DIRS}} -I"${JAVA_HOME}/include" -I"${JAVA_HOME}/include/${JNI_INCLUDE_FOLDER}" -DFIELD_ELEMENTS_PER_BLOB=${FIELD_ELEMENTS_PER_BLOB} -o ${LIBRARY_FOLDER}/${OS_ARCH}/${LIBRARY_RESOURCE} c_kzg_4844_jni.c c_kzg_4844.o ../../lib/libblst.a + mkdir -p ${LIBRARY_FOLDER} + ${CLANG_EXECUTABLE} ${CC_FLAGS} ${CLANG_FLAGS} ${OPTIMIZATION_LEVEL} -Wall -Wno-missing-braces ${addprefix -I,${INCLUDE_DIRS}} -I"${JAVA_HOME}/include" -I"${JAVA_HOME}/include/${JNI_INCLUDE_FOLDER}" -DFIELD_ELEMENTS_PER_BLOB=${FIELD_ELEMENTS_PER_BLOB} -o ${LIBRARY_FOLDER}/${LIBRARY_RESOURCE} ${TARGETS} test: ${GRADLE_COMMAND} clean test +benchmark: + ${GRADLE_COMMAND} clean jmh + diff --git a/bindings/java/README.md b/bindings/java/README.md index 5a8558a..6242b79 100644 --- a/bindings/java/README.md +++ b/bindings/java/README.md @@ -2,17 +2,33 @@ ## Prerequisites -* Follow the instructions in the [README.md](../../README.md) to build blst and the C-KZG library. -* `JAVA_HOME` environment variable is set to a JDK with an `include` folder containing a `jni.h` file. +* Follow the instructions in the [README.md](../../README.md) to build blst. +* `JAVA_HOME` environment variable is set to a JDK with an `include` folder containing a `jni.h` + file. ## Build + ```bash make build ``` -This will install the library in the `src/main/resources/ethereum/ckzg4844/lib` folder with a name according to your OS +This will install the library in `src/main/resources/ethereum/ckzg4844/lib` with a folder structure +and name according to the preset selected (mainnet or minimal) and your OS. + +All variables which could be passed to the `make` command and the defaults can be found in +the [Makefile](./Makefile) ## Test + ```bash make test ``` + +## Benchmark + +JMH is used for benchmarking. +See [CKZG4844JNIBenchmark.java](src/jmh/java/ethereum/ckzg4844/CKZG4844JNIBenchmark.java) for more information. + +```bash +make benchmark +``` diff --git a/bindings/java/build.gradle b/bindings/java/build.gradle index 00165b8..b096609 100644 --- a/bindings/java/build.gradle +++ b/bindings/java/build.gradle @@ -1,5 +1,7 @@ plugins { id "application" + id "java-test-fixtures" + id "me.champeau.jmh" version "0.6.8" } repositories { @@ -13,6 +15,8 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter:${junitVersion}") testImplementation("org.junit.jupiter:junit-jupiter-params:${junitVersion}") + testFixturesImplementation("org.apache.tuweni:tuweni-units:2.3.1") + } test { diff --git a/bindings/java/c_kzg_4844_jni.c b/bindings/java/c_kzg_4844_jni.c index 82367f3..1a188a1 100644 --- a/bindings/java/c_kzg_4844_jni.c +++ b/bindings/java/c_kzg_4844_jni.c @@ -22,7 +22,12 @@ void throw_exception(JNIEnv *env, const char *message) (*env)->ThrowNew(env, Exception, message); } -JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_loadTrustedSetup(JNIEnv *env, jclass thisCls, jstring file) +JNIEXPORT jint JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_getFieldElementsPerBlob(JNIEnv *env, jclass thisCls) +{ + return (jint)FIELD_ELEMENTS_PER_BLOB; +} + +JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_loadTrustedSetup(JNIEnv *env, jclass thisCls, jstring file) { if (settings != NULL) { @@ -61,7 +66,7 @@ JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_loadTrustedSetup(JNIEn (*env)->ReleaseStringUTFChars(env, file, file_native); } -JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_freeTrustedSetup(JNIEnv *env, jclass thisCls) +JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_freeTrustedSetup(JNIEnv *env, jclass thisCls) { if (settings == NULL) { @@ -72,7 +77,7 @@ JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_freeTrustedSetup(JNIEn reset_trusted_setup(); } -JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_computeAggregateKzgProof(JNIEnv *env, jclass thisCls, jbyteArray blobs, jlong count) +JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_computeAggregateKzgProof(JNIEnv *env, jclass thisCls, jbyteArray blobs, jlong count) { if (settings == NULL) { @@ -104,7 +109,7 @@ JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_computeAggregate return proof; } -JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_verifyAggregateKzgProof(JNIEnv *env, jclass thisCls, jbyteArray blobs, jbyteArray commitments, jlong count, jbyteArray proof) +JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_verifyAggregateKzgProof(JNIEnv *env, jclass thisCls, jbyteArray blobs, jbyteArray commitments, jlong count, jbyteArray proof) { if (settings == NULL) { @@ -112,6 +117,13 @@ JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_verifyAggregateKzg return 0; } + size_t blobs_size = (size_t)(*env)->GetArrayLength(env, blobs); + if (blobs_size == 0) + { + throw_exception(env, "Passing byte array with 0 elements for blobs is not supported."); + return 0; + } + jbyte *blobs_native = (*env)->GetByteArrayElements(env, blobs, NULL); uint8_t *commitments_native = (uint8_t *)(*env)->GetByteArrayElements(env, commitments, NULL); uint8_t *proof_native = (uint8_t *)(*env)->GetByteArrayElements(env, proof, NULL); @@ -163,7 +175,7 @@ JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_verifyAggregateKzg return (jboolean)out; } -JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_blobToKzgCommitment(JNIEnv *env, jclass thisCls, jbyteArray blob) +JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_blobToKzgCommitment(JNIEnv *env, jclass thisCls, jbyteArray blob) { if (settings == NULL) { @@ -171,10 +183,17 @@ JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_blobToKzgCommitm return NULL; } - uint8_t *blob_native = (uint8_t *)(*env)->GetByteArrayElements(env, blob, NULL); + size_t blob_size = (size_t)(*env)->GetArrayLength(env, blob); + if (blob_size == 0) + { + throw_exception(env, "Passing byte array with 0 elements for a blob is not supported."); + return NULL; + } + + jbyte *blob_native = (*env)->GetByteArrayElements(env, blob, NULL); KZGCommitment c; - blob_to_kzg_commitment(&c, blob_native, settings); + blob_to_kzg_commitment(&c, (uint8_t *)blob_native, settings); jbyteArray commitment = (*env)->NewByteArray(env, BYTES_PER_COMMITMENT); uint8_t *out = (uint8_t *)(*env)->GetByteArrayElements(env, commitment, 0); @@ -186,7 +205,7 @@ JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_blobToKzgCommitm return commitment; } -JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_verifyKzgProof(JNIEnv *env, jclass thisCls, jbyteArray commitment, jbyteArray z, jbyteArray y, jbyteArray proof) +JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_verifyKzgProof(JNIEnv *env, jclass thisCls, jbyteArray commitment, jbyteArray z, jbyteArray y, jbyteArray proof) { if (settings == NULL) { diff --git a/bindings/java/c_kzg_4844_jni.h b/bindings/java/c_kzg_4844_jni.h index d2eef06..bc7cc56 100644 --- a/bindings/java/c_kzg_4844_jni.h +++ b/bindings/java/c_kzg_4844_jni.h @@ -1,54 +1,67 @@ /* DO NOT EDIT THIS FILE - it is machine generated */ #include -/* Header for class ethereum_ckzg4844_CKzg4844JNI */ +/* Header for class ethereum_ckzg4844_CKZG4844JNI */ -#ifndef _Included_ethereum_ckzg4844_CKzg4844JNI -#define _Included_ethereum_ckzg4844_CKzg4844JNI +#ifndef _Included_ethereum_ckzg4844_CKZG4844JNI +#define _Included_ethereum_ckzg4844_CKZG4844JNI #ifdef __cplusplus extern "C" { #endif +#undef ethereum_ckzg4844_CKZG4844JNI_BYTES_PER_COMMITMENT +#define ethereum_ckzg4844_CKZG4844JNI_BYTES_PER_COMMITMENT 48L +#undef ethereum_ckzg4844_CKZG4844JNI_BYTES_PER_PROOF +#define ethereum_ckzg4844_CKZG4844JNI_BYTES_PER_PROOF 48L +#undef ethereum_ckzg4844_CKZG4844JNI_BYTES_PER_FIELD_ELEMENT +#define ethereum_ckzg4844_CKZG4844JNI_BYTES_PER_FIELD_ELEMENT 32L /* - * Class: ethereum_ckzg4844_CKzg4844JNI + * Class: ethereum_ckzg4844_CKZG4844JNI + * Method: getFieldElementsPerBlob + * Signature: ()I + */ + JNIEXPORT jint JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_getFieldElementsPerBlob(JNIEnv *, jclass); + + /* + * Class: ethereum_ckzg4844_CKZG4844JNI * Method: loadTrustedSetup * Signature: (Ljava/lang/String;)V */ - JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_loadTrustedSetup(JNIEnv *, jclass, jstring); + JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_loadTrustedSetup(JNIEnv *, jclass, jstring); /* - * Class: ethereum_ckzg4844_CKzg4844JNI + * Class: ethereum_ckzg4844_CKZG4844JNI * Method: freeTrustedSetup * Signature: ()V */ - JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_freeTrustedSetup(JNIEnv *, jclass); + JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_freeTrustedSetup(JNIEnv *, jclass); /* - * Class: ethereum_ckzg4844_CKzg4844JNI + * Class: ethereum_ckzg4844_CKZG4844JNI * Method: computeAggregateKzgProof * Signature: ([BJ)[B */ - JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_computeAggregateKzgProof(JNIEnv *, jclass, jbyteArray, jlong); + JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_computeAggregateKzgProof(JNIEnv *, jclass, jbyteArray, jlong); /* - * Class: ethereum_ckzg4844_CKzg4844JNI + * Class: ethereum_ckzg4844_CKZG4844JNI * Method: verifyAggregateKzgProof * Signature: ([B[BJ[B)Z */ - JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_verifyAggregateKzgProof(JNIEnv *, jclass, jbyteArray, jbyteArray, jlong, jbyteArray); + JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_verifyAggregateKzgProof(JNIEnv *, jclass, jbyteArray, jbyteArray, jlong, jbyteArray); /* - * Class: ethereum_ckzg4844_CKzg4844JNI + * Class: ethereum_ckzg4844_CKZG4844JNI * Method: blobToKzgCommitment * Signature: ([B)[B */ - JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_blobToKzgCommitment(JNIEnv *, jclass, jbyteArray); + JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_blobToKzgCommitment(JNIEnv *, jclass, jbyteArray); /* - * Class: ethereum_ckzg4844_CKzg4844JNI + * Class: ethereum_ckzg4844_CKZG4844JNI * Method: verifyKzgProof * Signature: ([B[B[B[B)Z */ - JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKzg4844JNI_verifyKzgProof(JNIEnv *, jclass, jbyteArray, jbyteArray, jbyteArray, jbyteArray); + JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_verifyKzgProof(JNIEnv *, jclass, jbyteArray, jbyteArray, jbyteArray, jbyteArray); #ifdef __cplusplus } diff --git a/bindings/java/src/jmh/java/ethereum/ckzg4844/CKZG4844JNIBenchmark.java b/bindings/java/src/jmh/java/ethereum/ckzg4844/CKZG4844JNIBenchmark.java new file mode 100644 index 0000000..e4ad457 --- /dev/null +++ b/bindings/java/src/jmh/java/ethereum/ckzg4844/CKZG4844JNIBenchmark.java @@ -0,0 +1,93 @@ +package ethereum.ckzg4844; + +import ethereum.ckzg4844.CKZG4844JNI.Preset; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; + +@BenchmarkMode(Mode.AverageTime) +@Fork(value = 1) +@Warmup(iterations = 1, time = 1000, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +public class CKZG4844JNIBenchmark { + + static { + CKZG4844JNI.loadNativeLibrary(Preset.MAINNET); + } + + @State(Scope.Benchmark) + public static class BlobToKzgCommitmentState { + + private byte[] blob; + + @Setup(Level.Iteration) + public void setUp() { + blob = TestUtils.createRandomBlob(); + } + } + + @State(Scope.Benchmark) + public static class ComputeAndVerifyState { + + @Param({"1", "4", "8", "16"}) + private int count; + + private byte[] blobs; + private byte[] commitments; + private byte[] proof; + + @Setup(Level.Iteration) + public void setUp() { + final byte[][] blobs = new byte[count][]; + final byte[][] commitments = new byte[count][]; + IntStream.range(0, count).forEach(i -> { + blobs[i] = TestUtils.createRandomBlob(); + commitments[i] = CKZG4844JNI.blobToKzgCommitment(blobs[i]); + }); + this.blobs = TestUtils.flatten(blobs); + this.commitments = TestUtils.flatten(commitments); + proof = CKZG4844JNI.computeAggregateKzgProof(TestUtils.flatten(blobs), count); + } + } + + @Setup + public void setUp() { + CKZG4844JNI.loadTrustedSetup("../../src/trusted_setup.txt"); + } + + @TearDown + public void tearDown() { + CKZG4844JNI.freeTrustedSetup(); + } + + @Benchmark + public byte[] blobToKzgCommitment(final BlobToKzgCommitmentState state) { + return CKZG4844JNI.blobToKzgCommitment(state.blob); + } + + @Benchmark + public byte[] computeAggregateKzgProof(final ComputeAndVerifyState state) { + return CKZG4844JNI.computeAggregateKzgProof(state.blobs, state.count); + } + + @Benchmark + public boolean verifyAggregateKzgProof(final ComputeAndVerifyState state) { + return CKZG4844JNI.verifyAggregateKzgProof(state.blobs, state.commitments, state.count, + state.proof); + } + +} \ No newline at end of file diff --git a/bindings/java/src/main/java/ethereum/ckzg4844/CKZG4844JNI.java b/bindings/java/src/main/java/ethereum/ckzg4844/CKZG4844JNI.java new file mode 100644 index 0000000..45b2aef --- /dev/null +++ b/bindings/java/src/main/java/ethereum/ckzg4844/CKZG4844JNI.java @@ -0,0 +1,142 @@ +package ethereum.ckzg4844; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.math.BigInteger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +public class CKZG4844JNI { + + private static final String LIBRARY_NAME = "ckzg4844jni"; + private static final String PLATFORM_NATIVE_LIBRARY_NAME = System.mapLibraryName(LIBRARY_NAME); + + /** + * Loads the appropriate native library based on your platform and the selected {@link Preset} + * + * @param preset the preset + */ + public static void loadNativeLibrary(Preset preset) { + String libraryResourcePath = + "lib/" + System.getProperty("os.arch") + "/" + preset.name().toLowerCase() + "/" + + PLATFORM_NATIVE_LIBRARY_NAME; + InputStream libraryResource = CKZG4844JNI.class.getResourceAsStream(libraryResourcePath); + if (libraryResource == null) { + try { + System.loadLibrary(LIBRARY_NAME); + } catch (UnsatisfiedLinkError __) { + String exceptionMessage = String.format( + "Couldn't load native library (%s). It wasn't available at %s or the library path.", + LIBRARY_NAME, libraryResourcePath); + throw new RuntimeException(exceptionMessage); + } + } else { + try { + Path tempDir = Files.createTempDirectory(LIBRARY_NAME + "@"); + tempDir.toFile().deleteOnExit(); + Path tempDll = tempDir.resolve(PLATFORM_NATIVE_LIBRARY_NAME); + tempDll.toFile().deleteOnExit(); + Files.copy(libraryResource, tempDll, StandardCopyOption.REPLACE_EXISTING); + libraryResource.close(); + System.load(tempDll.toString()); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + } + + public enum Preset { + MAINNET(4096), MINIMAL(4); + + public final int fieldElementsPerBlob; + + Preset(int fieldElementsPerBlob) { + this.fieldElementsPerBlob = fieldElementsPerBlob; + } + } + + public static final BigInteger BLS_MODULUS = new BigInteger( + "52435875175126190479447740508185965837690552500527637822603658699938581184513"); + public static final int BYTES_PER_COMMITMENT = 48; + public static final int BYTES_PER_PROOF = 48; + public static final int BYTES_PER_FIELD_ELEMENT = 32; + + private CKZG4844JNI() { + } + + /** + * Calculates the bytes per blob based on the output from {@link #getFieldElementsPerBlob()} + * + * @return the bytes per blob + */ + public static int getBytesPerBlob() { + return getFieldElementsPerBlob() * BYTES_PER_FIELD_ELEMENT; + } + + /** + * Retrieves the compile-time configured FIELD_ELEMENTS_PER_BLOB. The value will be based on the + * selected {@link Preset} when loading the native library. + * + * @return the field elements per blob + */ + public static native int getFieldElementsPerBlob(); + + /** + * Loads the trusted setup from a file. Once loaded, the same setup will be used for all the + * crypto native calls. To load a new setup, free the current one by calling + * {@link #freeTrustedSetup()} and then load the new one. If no trusted setup has been loaded, all + * the crypto native calls will throw an exception. + * + * @param file a path to a trusted setup file + */ + public static native void loadTrustedSetup(String file); + + /** + * Free the current trusted setup. This method will throw an exception if no trusted setup has + * been loaded. + */ + public static native void freeTrustedSetup(); + + /** + * Calculates aggregated proof for the given blobs + * + * @param blobs blobs as flattened bytes + * @param count the count of the blobs + * @return the aggregated proof + */ + public static native byte[] computeAggregateKzgProof(byte[] blobs, long count); + + /** + * Verify aggregated proof and commitments for the given blobs + * + * @param blobs blobs as flattened bytes + * @param commitments commitments as flattened bytes + * @param count the count of the blobs (should be same as the count of the commitments) + * @param proof the proof that needs verifying + * @return true if the proof is valid and false otherwise + */ + public static native boolean verifyAggregateKzgProof(byte[] blobs, byte[] commitments, long count, + byte[] proof); + + /** + * Calculates commitment for a given blob + * + * @param blob blob bytes + * @return the commitment + */ + public static native byte[] blobToKzgCommitment(byte[] blob); + + /** + * Verify the proof by point evaluation for the given commitment + * + * @param commitment commitment bytes + * @param z Z + * @param y Y + * @param proof the proof that needs verifying + * @return true if the proof is valid and false otherwise + */ + public static native boolean verifyKzgProof(byte[] commitment, byte[] z, byte[] y, byte[] proof); + +} diff --git a/bindings/java/src/main/java/ethereum/ckzg4844/CKzg4844JNI.java b/bindings/java/src/main/java/ethereum/ckzg4844/CKzg4844JNI.java deleted file mode 100644 index c56ac17..0000000 --- a/bindings/java/src/main/java/ethereum/ckzg4844/CKzg4844JNI.java +++ /dev/null @@ -1,100 +0,0 @@ -package ethereum.ckzg4844; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; - -public class CKzg4844JNI { - - private static final String LIBRARY_NAME = "ckzg4844jni"; - private static final String PLATFORM_NATIVE_LIBRARY_NAME = System.mapLibraryName(LIBRARY_NAME); - - static { - InputStream libraryResource = CKzg4844JNI.class.getResourceAsStream( - "lib/" + System.getProperty("os.arch") + "/" + PLATFORM_NATIVE_LIBRARY_NAME); - if (libraryResource == null) { - try { - System.loadLibrary(LIBRARY_NAME); - } catch (UnsatisfiedLinkError ex) { - throw new RuntimeException(ex); - } - } else { - try { - Path tmpdir = Files.createTempDirectory(LIBRARY_NAME + "@"); - tmpdir.toFile().deleteOnExit(); - Path tmpdll = tmpdir.resolve(PLATFORM_NATIVE_LIBRARY_NAME); - tmpdll.toFile().deleteOnExit(); - Files.copy(libraryResource, tmpdll, StandardCopyOption.REPLACE_EXISTING); - libraryResource.close(); - System.load(tmpdll.toString()); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - } - - public static int BYTES_PER_COMMITMENT = 48; - public static int BYTES_PER_PROOF = 48; - public static int FIELD_ELEMENTS_PER_BLOB = 4096; - public static int BYTES_PER_FIELD_ELEMENT = 32; - public static int BYTES_PER_BLOB = FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT; - - /** - * Loads the trusted setup from a file. Once loaded, the same setup will be used for all the - * native calls. To load a new setup, free the current one by calling {@link #freeTrustedSetup()} - * and then load the new one. If no trusted setup has been loaded, all the native calls will throw - * an exception. - * - * @param file a path to a trusted setup file - */ - public static native void loadTrustedSetup(String file); - - /** - * Free the current trusted setup. This method will throw an exception if no trusted setup has - * been loaded. - */ - public static native void freeTrustedSetup(); - - /** - * Calculates aggregated proof for the given blobs - * - * @param blobs blobs as flattened bytes - * @param count the count of the blobs - * @return the aggregated proof - */ - public static native byte[] computeAggregateKzgProof(byte[] blobs, long count); - - /** - * Verify aggregated proof and commitments for the given blobs - * - * @param blobs blobs as flattened bytes - * @param commitments commitments as flattened bytes - * @param count the count of the blobs (should be same as the count of the commitments) - * @param proof the proof that needs verifying - * @return true if the proof is valid and false otherwise - */ - public static native boolean verifyAggregateKzgProof(byte[] blobs, byte[] commitments, long count, - byte[] proof); - - /** - * Calculates commitment for a given blob - * - * @param blob blob bytes - * @return the commitment - */ - public static native byte[] blobToKzgCommitment(byte[] blob); - - /** - * Verify the proof by point evaluation for the given commitment - * - * @param commitment commitment bytes - * @param z Z - * @param y Y - * @param proof the proof that needs verifying - * @return true if the proof is valid and false otherwise - */ - public static native boolean verifyKzgProof(byte[] commitment, byte[] z, byte[] y, byte[] proof); - -} diff --git a/bindings/java/src/test/java/ethereum/ckzg4844/CKZG4844JNITest.java b/bindings/java/src/test/java/ethereum/ckzg4844/CKZG4844JNITest.java new file mode 100644 index 0000000..19305b2 --- /dev/null +++ b/bindings/java/src/test/java/ethereum/ckzg4844/CKZG4844JNITest.java @@ -0,0 +1,152 @@ +package ethereum.ckzg4844; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import ethereum.ckzg4844.CKZG4844JNI.Preset; +import java.util.Optional; +import java.util.stream.IntStream; +import org.junit.jupiter.api.Test; + +public class CKZG4844JNITest { + + private static final Preset PRESET; + + static { + PRESET = Optional.ofNullable(System.getenv("PRESET")).map(String::toUpperCase) + .map(Preset::valueOf).orElse(Preset.MAINNET); + CKZG4844JNI.loadNativeLibrary(PRESET); + } + + @Test + public void getsTheConfiguredFieldElementsPerBlob() { + assertEquals(PRESET.fieldElementsPerBlob, CKZG4844JNI.getFieldElementsPerBlob()); + assertEquals(PRESET.fieldElementsPerBlob * CKZG4844JNI.BYTES_PER_FIELD_ELEMENT, + CKZG4844JNI.getBytesPerBlob()); + } + + @Test + public void computesAndVerifiesProofs() { + + loadTrustedSetup(); + + final int count = 3; + + final byte[][] blobsArray = new byte[count][]; + final byte[][] commitmentsArray = new byte[count][]; + IntStream.range(0, count).forEach(i -> { + blobsArray[i] = TestUtils.createRandomBlob(); + commitmentsArray[i] = CKZG4844JNI.blobToKzgCommitment(blobsArray[i]); + }); + final byte[] blobs = TestUtils.flatten(blobsArray); + final byte[] commitments = TestUtils.flatten(commitmentsArray); + + assertEquals(CKZG4844JNI.BYTES_PER_COMMITMENT * count, commitments.length); + + final byte[] proof = CKZG4844JNI.computeAggregateKzgProof(blobs, count); + + assertEquals(CKZG4844JNI.BYTES_PER_PROOF, proof.length); + + assertTrue(CKZG4844JNI.verifyAggregateKzgProof(blobs, commitments, count, proof)); + + final byte[] fakeProof = TestUtils.createRandomProof(count); + assertFalse(CKZG4844JNI.verifyAggregateKzgProof(blobs, commitments, count, fakeProof)); + + final byte[] fakeBlobs = TestUtils.createRandomBlobs(count); + assertFalse(CKZG4844JNI.verifyAggregateKzgProof(fakeBlobs, commitments, count, proof)); + + final byte[] fakeCommitments = TestUtils.createRandomCommitments(count); + assertFalse(CKZG4844JNI.verifyAggregateKzgProof(blobs, fakeCommitments, count, proof)); + + CKZG4844JNI.freeTrustedSetup(); + + } + + @Test + public void verifiesPointEvaluationPrecompile() { + + loadTrustedSetup(); + + final byte[] commitment = new byte[CKZG4844JNI.BYTES_PER_COMMITMENT]; + commitment[0] = (byte) 0xc0; + final byte[] z = new byte[32]; + final byte[] y = new byte[32]; + final byte[] proof = new byte[CKZG4844JNI.BYTES_PER_PROOF]; + proof[0] = (byte) 0xc0; + + assertTrue(CKZG4844JNI.verifyKzgProof(commitment, z, y, proof)); + + CKZG4844JNI.freeTrustedSetup(); + + } + + @Test + public void passingZeroElementArraysForBlobsDoesNotCauseSegmentationFaultErrors() { + + loadTrustedSetup(); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> CKZG4844JNI.blobToKzgCommitment(new byte[0])); + + assertEquals("Passing byte array with 0 elements for a blob is not supported.", + exception.getMessage()); + + exception = assertThrows(RuntimeException.class, + () -> CKZG4844JNI.verifyAggregateKzgProof(new byte[0], TestUtils.createRandomCommitment(), + 1, + TestUtils.createRandomProof(1))); + + assertEquals("Passing byte array with 0 elements for blobs is not supported.", + exception.getMessage()); + + CKZG4844JNI.freeTrustedSetup(); + } + + @Test + public void throwsIfMethodIsUsedWithoutLoadingTrustedSetup() { + + final RuntimeException exception = assertThrows(RuntimeException.class, + () -> CKZG4844JNI.blobToKzgCommitment(TestUtils.createRandomBlob())); + + assertExceptionIsTrustedSetupIsNotLoaded(exception); + + } + + @Test + public void throwsIfSetupIsLoadedTwice() { + + loadTrustedSetup(); + + final RuntimeException exception = assertThrows(RuntimeException.class, this::loadTrustedSetup); + + assertEquals("Trusted Setup is already loaded. Free it before loading a new one.", + exception.getMessage()); + + CKZG4844JNI.freeTrustedSetup(); + + } + + @Test + public void throwsIfTryToFreeTrustedSetupWithoutLoadingIt() { + + final RuntimeException exception = assertThrows(RuntimeException.class, + CKZG4844JNI::freeTrustedSetup); + + assertExceptionIsTrustedSetupIsNotLoaded(exception); + + } + + private void assertExceptionIsTrustedSetupIsNotLoaded(final RuntimeException exception) { + assertEquals("Trusted Setup is not loaded.", exception.getMessage()); + } + + private void loadTrustedSetup() { + if (PRESET.equals(Preset.MINIMAL)) { + CKZG4844JNI.loadTrustedSetup("../../src/trusted_setup_4.txt"); + } else { + CKZG4844JNI.loadTrustedSetup("../../src/trusted_setup.txt"); + } + } +} diff --git a/bindings/java/src/test/java/ethereum/ckzg4844/CKzg4844JNITest.java b/bindings/java/src/test/java/ethereum/ckzg4844/CKzg4844JNITest.java deleted file mode 100644 index c81decd..0000000 --- a/bindings/java/src/test/java/ethereum/ckzg4844/CKzg4844JNITest.java +++ /dev/null @@ -1,173 +0,0 @@ -package ethereum.ckzg4844; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Random; -import java.util.stream.IntStream; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -public class CKzg4844JNITest { - - private final Random random = new Random(); - - @Test - public void computesAndVerifiesProofs() { - - loadTrustedSetup(); - - final byte[] blob = createRandomBlob(); - final byte[] blob2 = createRandomBlob(); - - final byte[] commitment = CKzg4844JNI.blobToKzgCommitment(blob); - final byte[] commitment2 = CKzg4844JNI.blobToKzgCommitment(blob2); - - assertEquals(CKzg4844JNI.BYTES_PER_COMMITMENT, commitment.length); - assertEquals(CKzg4844JNI.BYTES_PER_COMMITMENT, commitment2.length); - - final byte[] blobs = flatten(blob, blob2); - final byte[] commitments = flatten(commitment, commitment2); - - final byte[] proof = CKzg4844JNI.computeAggregateKzgProof(blobs, 2); - - assertEquals(CKzg4844JNI.BYTES_PER_PROOF, proof.length); - - assertTrue(CKzg4844JNI.verifyAggregateKzgProof(blobs, commitments, 2, proof)); - - final byte[] fakeProof = createRandomProof(2); - assertFalse(CKzg4844JNI.verifyAggregateKzgProof(blobs, commitments, 2, fakeProof)); - - CKzg4844JNI.freeTrustedSetup(); - - } - - @Test - public void verifiesPointEvaluationPrecompile() { - - loadTrustedSetup(); - - final byte[] commitment = new byte[48]; - commitment[0] = (byte) 0xc0; - final byte[] z = new byte[32]; - final byte[] y = new byte[32]; - final byte[] proof = new byte[48]; - proof[0] = (byte) 0xc0; - - assertTrue(CKzg4844JNI.verifyKzgProof(commitment, z, y, proof)); - - CKzg4844JNI.freeTrustedSetup(); - - } - - @Disabled("Use for manually testing performance.") - @ParameterizedTest - @ValueSource(ints = {1, 10, 100, 1000}) - public void testPerformance(final int count) { - - loadTrustedSetup(); - - final byte[] blobs = createRandomBlobs(count); - final byte[] commitments = getCommitmentsForBlobs(blobs, count); - - long startTime = System.currentTimeMillis(); - final byte[] proof = CKzg4844JNI.computeAggregateKzgProof(blobs, count); - long endTime = System.currentTimeMillis(); - - System.out.printf("Computing aggregate proof for %d blobs took %d milliseconds%n", count, - endTime - startTime); - - startTime = System.currentTimeMillis(); - boolean proofValidity = CKzg4844JNI.verifyAggregateKzgProof(blobs, commitments, count, proof); - endTime = System.currentTimeMillis(); - - assertTrue(proofValidity); - - System.out.printf("Verifying aggregate proof for %d blobs took %d milliseconds%n", count, - endTime - startTime); - - CKzg4844JNI.freeTrustedSetup(); - - } - - @Test - public void throwsIfMethodIsUsedWithoutLoadingTrustedSetup() { - - final RuntimeException exception = assertThrows(RuntimeException.class, - () -> CKzg4844JNI.blobToKzgCommitment(createRandomBlob())); - - assertExceptionIsTrustedSetupIsNotLoaded(exception); - - } - - @Test - public void throwsIfSetupIsLoadedTwice() { - - loadTrustedSetup(); - - final RuntimeException exception = assertThrows(RuntimeException.class, this::loadTrustedSetup); - - assertEquals("Trusted Setup is already loaded. Free it before loading a new one.", - exception.getMessage()); - - CKzg4844JNI.freeTrustedSetup(); - - } - - @Test - public void throwsIfTryToFreeTrustedSetupWithoutLoadingIt() { - - final RuntimeException exception = assertThrows(RuntimeException.class, - CKzg4844JNI::freeTrustedSetup); - - assertExceptionIsTrustedSetupIsNotLoaded(exception); - - } - - private void assertExceptionIsTrustedSetupIsNotLoaded(final RuntimeException exception) { - assertEquals("Trusted Setup is not loaded.", exception.getMessage()); - } - - private void loadTrustedSetup() { - CKzg4844JNI.loadTrustedSetup("../../src/trusted_setup.txt"); - } - - private byte[] createRandomBlob() { - final byte[] blob = new byte[CKzg4844JNI.BYTES_PER_BLOB]; - random.nextBytes(blob); - return blob; - } - - private byte[] createRandomBlobs(final int count) { - final byte[][] blobs = IntStream.range(0, count).mapToObj(__ -> createRandomBlob()) - .toArray(byte[][]::new); - return flatten(blobs); - } - - private byte[] createRandomProof(final int count) { - return CKzg4844JNI.computeAggregateKzgProof(createRandomBlobs(count), count); - } - - private byte[] getCommitmentsForBlobs(final byte[] blobs, final int count) { - final byte[][] commitments = new byte[count][]; - IntStream.range(0, count).forEach(i -> { - final byte[] blob = Arrays.copyOfRange(blobs, i * CKzg4844JNI.BYTES_PER_BLOB, - (i + 1) * CKzg4844JNI.BYTES_PER_BLOB); - commitments[i] = CKzg4844JNI.blobToKzgCommitment(blob); - }); - return flatten(commitments); - } - - private byte[] flatten(final byte[]... bytes) { - final int capacity = Arrays.stream(bytes).mapToInt(b -> b.length).sum(); - final ByteBuffer buffer = ByteBuffer.allocate(capacity); - Arrays.stream(bytes).forEach(buffer::put); - return buffer.array(); - } -} diff --git a/bindings/java/src/testFixtures/java/ethereum/ckzg4844/TestUtils.java b/bindings/java/src/testFixtures/java/ethereum/ckzg4844/TestUtils.java new file mode 100644 index 0000000..4f92cf0 --- /dev/null +++ b/bindings/java/src/testFixtures/java/ethereum/ckzg4844/TestUtils.java @@ -0,0 +1,58 @@ +package ethereum.ckzg4844; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; +import java.util.stream.IntStream; +import org.apache.tuweni.units.bigints.UInt256; + +public class TestUtils { + + private static final Random RANDOM = new Random(); + + public static byte[] flatten(final byte[]... bytes) { + final int capacity = Arrays.stream(bytes).mapToInt(b -> b.length).sum(); + final ByteBuffer buffer = ByteBuffer.allocate(capacity); + Arrays.stream(bytes).forEach(buffer::put); + return buffer.array(); + } + + public static byte[] createRandomBlob() { + final byte[][] blob = IntStream.range(0, CKZG4844JNI.getFieldElementsPerBlob()) + .mapToObj(__ -> randomBigIntegerInModulus()) + .map(UInt256::valueOf) + .map(UInt256::toArray) + .toArray(byte[][]::new); + return flatten(blob); + } + + public static byte[] createRandomBlobs(final int count) { + final byte[][] blobs = IntStream.range(0, count).mapToObj(__ -> createRandomBlob()) + .toArray(byte[][]::new); + return flatten(blobs); + } + + public static byte[] createRandomProof(final int count) { + return CKZG4844JNI.computeAggregateKzgProof(createRandomBlobs(count), count); + } + + public static byte[] createRandomCommitment() { + return CKZG4844JNI.blobToKzgCommitment(createRandomBlob()); + } + + public static byte[] createRandomCommitments(final int count) { + final byte[][] commitments = IntStream.range(0, count).mapToObj(__ -> createRandomCommitment()) + .toArray(byte[][]::new); + return flatten(commitments); + } + + private static BigInteger randomBigIntegerInModulus() { + final BigInteger attempt = new BigInteger(CKZG4844JNI.BLS_MODULUS.bitLength(), RANDOM); + if (attempt.compareTo(CKZG4844JNI.BLS_MODULUS) < 0) { + return attempt; + } + return randomBigIntegerInModulus(); + } + +} diff --git a/src/Makefile b/src/Makefile index cbe99e7..527dd8f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -25,7 +25,6 @@ blst: cp libblst.a ../lib && \ cp bindings/*.h ../inc -# Make sure c_kzg_4844.o is built and copy it for the NodeJS and Java bindings +# Make sure c_kzg_4844.o is built and copy it for the NodeJS bindings lib: c_kzg_4844.o Makefile cp *.o ../bindings/node.js - cp *.o ../bindings/java