[Java binding] Load trusted setup from params + custom exception

This commit is contained in:
Stefan Bratanov 2022-12-16 13:42:53 +02:00
parent 17626f0fd7
commit 46d64e231a
7 changed files with 251 additions and 66 deletions

View File

@ -3,9 +3,6 @@
#include "c_kzg_4844_jni.h"
#include "c_kzg_4844.h"
static const char *C_KZG_RETURN_TYPES[] = {
"C_KZG_OK", "C_KZG_BADARGS", "C_KZG_ERROR", "C_KZG_MALLOC"};
static const char *TRUSTED_SETUP_NOT_LOADED = "Trusted Setup is not loaded.";
KZGSettings *settings;
@ -18,8 +15,17 @@ void reset_trusted_setup()
void throw_exception(JNIEnv *env, const char *message)
{
jclass Exception = (*env)->FindClass(env, "java/lang/RuntimeException");
(*env)->ThrowNew(env, Exception, message);
jclass exception_class = (*env)->FindClass(env, "java/lang/RuntimeException");
(*env)->ThrowNew(env, exception_class, message);
}
void throw_c_kzg_exception(JNIEnv *env, C_KZG_RET error_code, const char *message)
{
jclass exception_class = (*env)->FindClass(env, "ethereum/ckzg4844/CKZGException");
jstring error_message = (*env)->NewStringUTF(env, message);
jmethodID exception_init = (*env)->GetMethodID(env, exception_class, "<init>", "(ILjava/lang/String;)V");
jobject exception = (*env)->NewObject(env, exception_class, exception_init, error_code, error_message);
(*env)->Throw(env, exception);
}
JNIEXPORT jint JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_getFieldElementsPerBlob(JNIEnv *env, jclass thisCls)
@ -27,7 +33,7 @@ JNIEXPORT jint JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_getFieldElementsPerBlo
return (jint)FIELD_ELEMENTS_PER_BLOB;
}
JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_loadTrustedSetup(JNIEnv *env, jclass thisCls, jstring file)
JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_loadTrustedSetup__Ljava_lang_String_2(JNIEnv *env, jclass thisCls, jstring file)
{
if (settings != NULL)
{
@ -55,9 +61,7 @@ JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_loadTrustedSetup(JNIEn
reset_trusted_setup();
(*env)->ReleaseStringUTFChars(env, file, file_native);
fclose(f);
char arr[100];
sprintf(arr, "There was an error while loading the Trusted Setup: %s", C_KZG_RETURN_TYPES[ret]);
throw_exception(env, arr);
throw_c_kzg_exception(env, ret, "There was an error while loading the Trusted Setup.");
return;
}
@ -66,6 +70,28 @@ JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_loadTrustedSetup(JNIEn
(*env)->ReleaseStringUTFChars(env, file, file_native);
}
JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_loadTrustedSetup___3BJ_3BJ(JNIEnv *env, jclass thisCls, jbyteArray g1, jlong g1Count, jbyteArray g2, jlong g2Count)
{
if (settings != NULL)
{
throw_exception(env, "Trusted Setup is already loaded. Free it before loading a new one.");
return;
}
settings = malloc(sizeof(KZGSettings));
uint8_t *g1_native = (uint8_t *)(*env)->GetByteArrayElements(env, g1, NULL);
uint8_t *g2_native = (uint8_t *)(*env)->GetByteArrayElements(env, g2, NULL);
C_KZG_RET ret = load_trusted_setup(settings, g1_native, (size_t)g1Count, g2_native, (size_t)g2Count);
if (ret != C_KZG_OK)
{
reset_trusted_setup();
throw_c_kzg_exception(env, ret, "There was an error while loading the Trusted Setup.");
return;
}
}
JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_freeTrustedSetup(JNIEnv *env, jclass thisCls)
{
if (settings == NULL)
@ -85,6 +111,13 @@ JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_computeAggregate
return NULL;
}
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);
KZGProof p;
@ -93,9 +126,7 @@ JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_computeAggregate
if (ret != C_KZG_OK)
{
char arr[100];
sprintf(arr, "There was an error while computing aggregate kzg proof: %s", C_KZG_RETURN_TYPES[ret]);
throw_exception(env, arr);
throw_c_kzg_exception(env, ret, "There was an error while computing aggregate kzg proof.");
return NULL;
}
@ -127,7 +158,7 @@ JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_verifyAggregateKzg
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);
size_t native_count = (size_t)count;
size_t count_native = (size_t)count;
KZGProof f;
@ -137,36 +168,32 @@ JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_verifyAggregateKzg
if (ret != C_KZG_OK)
{
char arr[100];
sprintf(arr, "There was an error while converting proof bytes to g1: %s", C_KZG_RETURN_TYPES[ret]);
throw_exception(env, arr);
throw_c_kzg_exception(env, ret, "There was an error while converting proof to g1.");
return 0;
}
KZGCommitment *c = calloc(native_count, sizeof(KZGCommitment));
KZGCommitment *c = calloc(count_native, sizeof(KZGCommitment));
for (size_t i = 0; i < native_count; i++)
for (size_t i = 0; i < count_native; i++)
{
ret = bytes_to_g1(&c[i], &commitments_native[i * BYTES_PER_COMMITMENT]);
if (ret != C_KZG_OK)
{
free(c);
char arr[100];
sprintf(arr, "There was an error while converting commitment (%zu/%zu) bytes to g1: %s", i + 1, native_count, C_KZG_RETURN_TYPES[ret]);
throw_exception(env, arr);
sprintf(arr, "There was an error while converting commitment (%zu/%zu) to g1.", i + 1, count_native);
throw_c_kzg_exception(env, ret, arr);
return 0;
}
}
bool out;
ret = verify_aggregate_kzg_proof(&out, (uint8_t const(*)[BYTES_PER_BLOB])blobs_native, c, native_count, &f, settings);
ret = verify_aggregate_kzg_proof(&out, (uint8_t const(*)[BYTES_PER_BLOB])blobs_native, c, count_native, &f, settings);
if (ret != C_KZG_OK)
{
free(c);
char arr[100];
sprintf(arr, "There was an error while verifying aggregate kzg proof: %s", C_KZG_RETURN_TYPES[ret]);
throw_exception(env, arr);
throw_c_kzg_exception(env, ret, "There was an error while verifying aggregate kzg proof.");
return 0;
}
@ -199,10 +226,8 @@ JNIEXPORT jbyteArray JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_blobToKzgCommitm
if (ret != C_KZG_OK)
{
char arr[100];
sprintf(arr, "There was an error while converting blob bytes to a commitment: %s", C_KZG_RETURN_TYPES[ret]);
throw_exception(env, arr);
return 0;
throw_c_kzg_exception(env, ret, "There was an error while converting blob to commitment.");
return NULL;
}
jbyteArray commitment = (*env)->NewByteArray(env, BYTES_PER_COMMITMENT);
@ -238,9 +263,7 @@ JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_verifyKzgProof(JNI
if (ret != C_KZG_OK)
{
char arr[100];
sprintf(arr, "There was an error while converting commitment bytes to g1: %s", C_KZG_RETURN_TYPES[ret]);
throw_exception(env, arr);
throw_c_kzg_exception(env, ret, "There was an error while converting commitment to g1.");
return 0;
}
@ -248,9 +271,7 @@ JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_verifyKzgProof(JNI
if (ret != C_KZG_OK)
{
char arr[100];
sprintf(arr, "There was an error while converting proof bytes to g1: %s", C_KZG_RETURN_TYPES[ret]);
throw_exception(env, arr);
throw_c_kzg_exception(env, ret, "There was an error while converting proof to g1.");
return 0;
}
@ -258,9 +279,7 @@ JNIEXPORT jboolean JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_verifyKzgProof(JNI
if (ret != C_KZG_OK)
{
char arr[100];
sprintf(arr, "There was an error while verifying kzg proof: %s", C_KZG_RETURN_TYPES[ret]);
throw_exception(env, arr);
throw_c_kzg_exception(env, ret, "There was an error while verifying kzg proof.");
return 0;
}

View File

@ -26,7 +26,14 @@ extern "C"
* 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__Ljava_lang_String_2(JNIEnv *, jclass, jstring);
/*
* Class: ethereum_ckzg4844_CKZG4844JNI
* Method: loadTrustedSetup
* Signature: ([BJ[BJ)V
*/
JNIEXPORT void JNICALL Java_ethereum_ckzg4844_CKZG4844JNI_loadTrustedSetup___3BJ_3BJ(JNIEnv *, jclass, jbyteArray, jlong, jbyteArray, jlong);
/*
* Class: ethereum_ckzg4844_CKZG4844JNI

View File

@ -87,12 +87,26 @@ public class CKZG4844JNI {
* 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.
* the crypto native calls will throw a {@link RuntimeException}.
*
* @param file a path to a trusted setup file
* @throws CKZGException if there is a crypto error
*/
public static native void loadTrustedSetup(String file);
/**
* An alternative to {@link #loadTrustedSetup(String)}. Loads the trusted setup from method
* parameters instead of a file.
*
* @param g1 g1 values as bytes
* @param g1Count the count of the g1 values
* @param g2 g2 values as bytes
* @param g2Count the count of the g2 values
* @throws CKZGException if there is a crypto error
*/
public static native void loadTrustedSetup(byte[] g1, long g1Count, byte[] g2,
long g2Count);
/**
* Free the current trusted setup. This method will throw an exception if no trusted setup has
* been loaded.
@ -105,6 +119,7 @@ public class CKZG4844JNI {
* @param blobs blobs as flattened bytes
* @param count the count of the blobs
* @return the aggregated proof
* @throws CKZGException if there is a crypto error
*/
public static native byte[] computeAggregateKzgProof(byte[] blobs, long count);
@ -116,6 +131,7 @@ public class CKZG4844JNI {
* @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
* @throws CKZGException if there is a crypto error
*/
public static native boolean verifyAggregateKzgProof(byte[] blobs, byte[] commitments, long count,
byte[] proof);
@ -125,6 +141,7 @@ public class CKZG4844JNI {
*
* @param blob blob bytes
* @return the commitment
* @throws CKZGException if there is a crypto error
*/
public static native byte[] blobToKzgCommitment(byte[] blob);
@ -136,6 +153,7 @@ public class CKZG4844JNI {
* @param y Y
* @param proof the proof that needs verifying
* @return true if the proof is valid and false otherwise
* @throws CKZGException if there is a crypto error
*/
public static native boolean verifyKzgProof(byte[] commitment, byte[] z, byte[] y, byte[] proof);

View File

@ -0,0 +1,45 @@
package ethereum.ckzg4844;
import static ethereum.ckzg4844.CKZGException.CKZGError.fromErrorCode;
import java.util.Arrays;
/**
* Thrown when there is an error in the underlying c-kzg library.
*/
public class CKZGException extends RuntimeException {
private final CKZGError error;
private final String errorMessage;
public CKZGException(int errorCode, String errorMessage) {
super(String.format("%s (%s)", errorMessage, fromErrorCode(errorCode)));
this.error = fromErrorCode(errorCode);
this.errorMessage = errorMessage;
}
public CKZGError getError() {
return error;
}
public String getErrorMessage() {
return errorMessage;
}
public enum CKZGError {
UNKNOWN(0), C_KZG_BADARGS(1), C_KZG_ERROR(2), C_KZG_MALLOC(3);
public final int errorCode;
CKZGError(int errorCode) {
this.errorCode = errorCode;
}
public static CKZGError fromErrorCode(int errorCode) {
return Arrays.stream(CKZGError.values()).filter(error -> error.errorCode == errorCode)
.findFirst().orElse(UNKNOWN);
}
}
}

View File

@ -6,17 +6,23 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import ethereum.ckzg4844.CKZG4844JNI.Preset;
import ethereum.ckzg4844.CKZGException.CKZGError;
import java.util.Map;
import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
public class CKZG4844JNITest {
private static final Preset PRESET;
private static final Map<Preset, String> TRUSTED_SETUP_FILE_BY_PRESET = Map.of(Preset.MAINNET,
"../../src/trusted_setup.txt", Preset.MINIMAL, "../../src/trusted_setup_4.txt");
static {
PRESET = Optional.ofNullable(System.getenv("PRESET")).map(String::toUpperCase)
.map(Preset::valueOf).orElse(Preset.MAINNET);
@ -30,10 +36,11 @@ public class CKZG4844JNITest {
CKZG4844JNI.getBytesPerBlob());
}
@Test
public void computesAndVerifiesProofs() {
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void computesAndVerifiesProofs(final boolean useTrustedSetupFile) {
loadTrustedSetup();
loadTrustedSetup(useTrustedSetupFile);
final int count = 3;
@ -75,6 +82,23 @@ public class CKZG4844JNITest {
parameters.getProof()));
}
@Test
public void checkCustomExceptionIsThrownAsExpected() {
loadTrustedSetup();
final byte[] blob = TestUtils.createNonCanonicalBlob();
final CKZGException exception = assertThrows(CKZGException.class,
() -> CKZG4844JNI.blobToKzgCommitment(blob));
assertEquals(CKZGError.C_KZG_BADARGS, exception.getError());
assertEquals("There was an error while converting blob to commitment.",
exception.getErrorMessage());
CKZG4844JNI.freeTrustedSetup();
}
@Test
public void passingZeroElementArraysForBlobsDoesNotCauseSegmentationFaultErrors() {
@ -86,10 +110,15 @@ public class CKZG4844JNITest {
assertEquals("Passing byte array with 0 elements for a blob is not supported.",
exception.getMessage());
exception = assertThrows(RuntimeException.class,
() -> CKZG4844JNI.computeAggregateKzgProof(new byte[0], 1));
assertEquals("Passing byte array with 0 elements for blobs is not supported.",
exception.getMessage());
exception = assertThrows(RuntimeException.class,
() -> CKZG4844JNI.verifyAggregateKzgProof(new byte[0], TestUtils.createRandomCommitment(),
1,
TestUtils.createRandomProof(1)));
1, TestUtils.createRandomProof(1)));
assertEquals("Passing byte array with 0 elements for blobs is not supported.",
exception.getMessage());
@ -136,17 +165,23 @@ public class CKZG4844JNITest {
assertEquals("Trusted Setup is not loaded.", exception.getMessage());
}
private static void loadTrustedSetup() {
if (PRESET.equals(Preset.MINIMAL)) {
CKZG4844JNI.loadTrustedSetup("../../src/trusted_setup_4.txt");
private static void loadTrustedSetup(final boolean useFile) {
if (useFile) {
loadTrustedSetup();
} else {
CKZG4844JNI.loadTrustedSetup("../../src/trusted_setup.txt");
final LoadTrustedSetupParameters parameters = TestUtils.createLoadTrustedSetupParameters(
TRUSTED_SETUP_FILE_BY_PRESET.get(PRESET));
CKZG4844JNI.loadTrustedSetup(parameters.getG1(), parameters.getG1Count(), parameters.getG2(),
parameters.getG2Count());
}
}
private static void loadTrustedSetup() {
CKZG4844JNI.loadTrustedSetup(TRUSTED_SETUP_FILE_BY_PRESET.get(PRESET));
}
private static Stream<VerifyKzgProofParameters> getVerifyKzgProofTestVectors() {
loadTrustedSetup();
return TestUtils.getVerifyKzgProofTestVectors().stream()
.onClose(CKZG4844JNI::freeTrustedSetup);
return TestUtils.getVerifyKzgProofTestVectors().stream().onClose(CKZG4844JNI::freeTrustedSetup);
}
}

View File

@ -0,0 +1,33 @@
package ethereum.ckzg4844;
public class LoadTrustedSetupParameters {
private final byte[] g1;
private final long g1Count;
private final byte[] g2;
private final long g2Count;
public LoadTrustedSetupParameters(final byte[] g1, final long g1Count, final byte[] g2,
final long g2Count) {
this.g1 = g1;
this.g1Count = g1Count;
this.g2 = g2;
this.g2Count = g2Count;
}
public byte[] getG1() {
return g1;
}
public long getG1Count() {
return g1Count;
}
public byte[] getG2() {
return g2;
}
public long getG2Count() {
return g2Count;
}
}

View File

@ -3,6 +3,8 @@ package ethereum.ckzg4844;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
@ -35,8 +37,7 @@ public class TestUtils {
public static byte[] createRandomBlob() {
final byte[][] blob = IntStream.range(0, CKZG4844JNI.getFieldElementsPerBlob())
.mapToObj(__ -> randomBLSFieldElement())
.map(fieldElement -> fieldElement.toArray(ByteOrder.LITTLE_ENDIAN))
.toArray(byte[][]::new);
.map(fieldElement -> fieldElement.toArray(ByteOrder.LITTLE_ENDIAN)).toArray(byte[][]::new);
return flatten(blob);
}
@ -60,6 +61,14 @@ public class TestUtils {
return flatten(commitments);
}
public static byte[] createNonCanonicalBlob() {
final byte[][] blob = IntStream.range(0, CKZG4844JNI.getFieldElementsPerBlob())
.mapToObj(__ -> UInt256.valueOf(CKZG4844JNI.BLS_MODULUS.add(BigInteger.valueOf(42))))
.map(greaterThanModulus -> greaterThanModulus.toArray(ByteOrder.LITTLE_ENDIAN))
.toArray(byte[][]::new);
return flatten(blob);
}
/**
* Generated using <a
* href="https://github.com/crate-crypto/proto-danksharding-fuzzy-test/">proto-danksharding-fuzzy-test</a>
@ -74,9 +83,7 @@ public class TestUtils {
final ArrayNode testCases = (ArrayNode) jsonNode.get("TestCases");
final Stream.Builder<VerifyKzgProofParameters> testVectors = Stream.builder();
testVectors.add(VerifyKzgProofParameters.ZERO);
IntStream.range(0,
jsonNode.get("NumTestCases").asInt())
.mapToObj(i -> {
IntStream.range(0, jsonNode.get("NumTestCases").asInt()).mapToObj(i -> {
final JsonNode testCase = testCases.get(i);
final Bytes32 z = Bytes32.fromHexString(testCase.get("InputPoint").asText());
final Bytes32 y = Bytes32.fromHexString(testCase.get("ClaimedValue").asText());
@ -86,11 +93,32 @@ public class TestUtils {
CKZG4844JNI.BYTES_PER_PROOF);
return new VerifyKzgProofParameters(commitment.toArray(), z.toArray(), y.toArray(),
proof.toArray());
})
.forEach(testVectors::add);
}).forEach(testVectors::add);
return testVectors.build().collect(Collectors.toList());
}
public static LoadTrustedSetupParameters createLoadTrustedSetupParameters(
final String trustedSetup) {
try (final BufferedReader reader = new BufferedReader(new FileReader(trustedSetup))) {
final int g1Count = Integer.parseInt(reader.readLine());
final int g2Count = Integer.parseInt(reader.readLine());
final ByteBuffer g1 = ByteBuffer.allocate(g1Count * 48);
final ByteBuffer g2 = ByteBuffer.allocate(g2Count * 96);
for (int i = 0; i < g1Count; i++) {
g1.put(Bytes.fromHexString(reader.readLine()).toArray());
}
for (int i = 0; i < g2Count; i++) {
g2.put(Bytes.fromHexString(reader.readLine()).toArray());
}
return new LoadTrustedSetupParameters(g1.array(), g1Count, g2.array(), g2Count);
} catch (final IOException ex) {
throw new UncheckedIOException(ex);
}
}
private static UInt256 randomBLSFieldElement() {
final BigInteger attempt = new BigInteger(CKZG4844JNI.BLS_MODULUS.bitLength(), RANDOM);
if (attempt.compareTo(CKZG4844JNI.BLS_MODULUS) < 0) {