Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error handling and init no-copy in JNI #158

Merged
merged 3 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
305 changes: 228 additions & 77 deletions ipa-multipoint/ipa_multipoint_jni/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/*
* Copyright Besu Contributors
*
/* Copyright Besu Contributors
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
Expand All @@ -12,111 +10,264 @@
*
* SPDX-License-Identifier: Apache-2.0
*/

use std::convert::TryInto;
mod parsers;
use parsers::{parse_scalars, parse_indices, parse_commitment, parse_commitments};

use jni::objects::JClass;
use jni::sys::jbyteArray;
use jni::JNIEnv;
use once_cell::sync::Lazy;

use std::convert::TryInto;


// TODO: Use a pointer here instead. This is only being used so that the interface does not get changed.
// TODO: and bindings do not need to be modified.
pub static CONFIG: Lazy<ffi_interface::Context> = Lazy::new(ffi_interface::Context::default);


/// Commit receives a list of 32 byte scalars and returns a 32 byte scalar
/// Scalar is actually the map_to_field(commitment) because we want to reuse the commitment in parent node.
/// This is ported from rust-verkle.
#[no_mangle]
pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_commit(
env: JNIEnv,
_class: JClass<'_>,
input: jbyteArray,
env: JNIEnv, _class: JClass<'_>, values: jbyteArray,
) -> jbyteArray {
let input = env
.convert_byte_array(input)
.expect("Cannot convert jbyteArray to rust array");

let commitment = ffi_interface::commit_to_scalars(&CONFIG, &input).unwrap();

env.byte_array_from_slice(&commitment)
.expect("Couldn't convert to byte array")
let input = match parse_scalars(&env, values) {
Ok(v) => v,
Err(e) => {
env.throw_new("java/lang/IllegalArgumentException", e)
.expect("Failed to throw exception for commit inputs.");
return std::ptr::null_mut();
}
};
let commitment = match ffi_interface::commit_to_scalars(&CONFIG, &input) {
Ok(v) => v,
Err(_) => {
env.throw_new("java/lang/IllegalArgumentException",
"Could not commit to scalars.")
.expect("Failed to throw exception for commit inputs.");
return std::ptr::null_mut();
}
};
let result = match env.byte_array_from_slice(&commitment) {
Ok(v) => v,
Err(_) => {
env.throw_new("java/lang/IllegalArgumentException",
"Couldn't return commitment.")
.expect("Couldn't convert to byte array");
return std::ptr::null_mut();
}
};
result
}

/// Commit_root receives a list of 32 byte scalars and returns a 32 byte commitment.to_bytes()
/// This is ported from rust-verkle.
#[no_mangle]
pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_commitRoot(
env: JNIEnv,
_class: JClass<'_>,
input: jbyteArray,
pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_commitAsCompressed(
env: JNIEnv, _class: JClass<'_>, values: jbyteArray
) -> jbyteArray {
let input = env
.convert_byte_array(input)
.expect("Cannot convert jbyteArray to rust array");

let commitment = ffi_interface::commit_to_scalars(&CONFIG, &input).unwrap();
let hash = ffi_interface::serialize_commitment(commitment);
let input = match parse_scalars(&env, values) {
Ok(v) => v,
Err(e) => {
env.throw_new("java/lang/IllegalArgumentException", e)
.expect("Failed to throw exception for commit inputs.");
return std::ptr::null_mut();
}
};
let commitment = match ffi_interface::commit_to_scalars(&CONFIG, &input) {
Ok(v) => v,
Err(e) => {
env.throw_new("java/lang/IllegalArgumentException", format!("{e:?}"))
.expect("Failed to throw exception for commit inputs.");
return std::ptr::null_mut();
}
};
let compressed = ffi_interface::serialize_commitment(commitment);
let result = match env.byte_array_from_slice(&compressed) {
Ok(v) => v,
Err(_) => {
env.throw_new("java/lang/IllegalArgumentException",
"Couldn't return commitment.")
.expect("Couldn't convert to byte array");
return std::ptr::null_mut();
}
};
result
}

env.byte_array_from_slice(&hash)
.expect("Couldn't convert to byte array")
#[no_mangle]
pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_updateSparse(
env: JNIEnv, _class: JClass<'_>, commitment: jbyteArray, indices: jbyteArray, old_values: jbyteArray, new_values: jbyteArray
) -> jbyteArray {
let commitment = match parse_commitment(&env, commitment) {
Ok(v) => v,
Err(e) => {
env.throw_new("java/lang/IllegalArgumentException", e)
.expect("Failed to throw exception for updateSparse commitment input.");
return std::ptr::null_mut();
}
};
let pos = match parse_indices(&env, indices) {
Ok(v) => v,
Err(e) => {
env.throw_new("java/lang/IllegalArgumentException", e)
.expect("Failed to throw exception for commit inputs.");
return std::ptr::null_mut();
}
};
let old = match parse_scalars(&env, old_values) {
Ok(v) => v,
Err(e) => {
env.throw_new("java/lang/IllegalArgumentException", e)
.expect("Failed to throw exception for commit inputs.");
return std::ptr::null_mut();
}
};
let old: Vec<ffi_interface::ScalarBytes> = old.chunks_exact(32).map(|x| {
let mut array = [0u8; 32];
array.copy_from_slice(x);
array
}).collect();
let new = match parse_scalars(&env, new_values) {
Ok(v) => v,
Err(e) => {
env.throw_new("java/lang/IllegalArgumentException", e)
.expect("Failed to throw exception for commit inputs.");
return std::ptr::null_mut();
}
};
let new: Vec<ffi_interface::ScalarBytes> = new.chunks_exact(32).map(|x| {
let mut array = [0u8; 32];
array.copy_from_slice(x);
array
}).collect();
let commitment = match ffi_interface::update_commitment_sparse(&CONFIG, commitment, pos, old, new) {
Ok(v) => v,
Err(e) => {
env.throw_new("java/lang/IllegalArgumentException", format!("{e:?}"))
.expect("Failed to throw exception for commit inputs.");
return std::ptr::null_mut();
}
};
let result = match env.byte_array_from_slice(&commitment) {
Ok(v) => v,
Err(_) => {
env.throw_new("java/lang/IllegalArgumentException", "Couldn't return commitment.")
.expect("Couldn't convert to byte array");
return std::ptr::null_mut();
}
};
result
}

#[no_mangle]
pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_groupToField(
env: JNIEnv,
_class: JClass<'_>,
input: jbyteArray,
pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_compress(
env: JNIEnv, _class: JClass<'_>, commitment: jbyteArray
) -> jbyteArray {
let commitment = env
.convert_byte_array(input)
.expect("Cannot convert jbyteArray to rust array");

let commitment_bytes = commitment.try_into().unwrap();
let commitment = match parse_commitment(&env, commitment) {
Ok(v) => v,
Err(e) => {
env.throw_new("java/lang/IllegalArgumentException", e)
.expect("Failed to throw exception for commit inputs.");
return std::ptr::null_mut();
}
};
let compressed = ffi_interface::serialize_commitment(commitment);
let result = match env.byte_array_from_slice(&compressed) {
Ok(s) => s,
Err(_) => {
env.throw_new(
"java/lang/IllegalArgumentException",
"Invalid commitment output. Couldn't convert to byte array.")
.expect("Couldn't convert to byte array");
return std::ptr::null_mut();
}
};
result
}

let hash = ffi_interface::hash_commitment(commitment_bytes);
#[no_mangle]
pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_compressMany(
env: JNIEnv, _class: JClass<'_>, commitments: jbyteArray
) -> jbyteArray {

env.byte_array_from_slice(&hash)
.expect("Couldn't convert to byte array")
let commitments = match parse_commitments(&env, commitments) {
Ok(v) => v,
Err(e) => {
env.throw_new("java/lang/IllegalArgumentException", e)
.expect("Failed to throw exception for commit inputs.");
return std::ptr::null_mut();
}
};
let compressed: Vec<u8> = commitments.chunks_exact(64).flat_map(|x| ffi_interface::serialize_commitment(x.try_into().unwrap())).collect();
let result = match env.byte_array_from_slice(&compressed) {
Ok(s) => s,
Err(_) => {
env.throw_new(
"java/lang/IllegalArgumentException",
"Invalid commitment output. Couldn't convert to byte array.")
.expect("Couldn't convert to byte array");
return std::ptr::null_mut();
}
};
result
}

/// Update commitment sparse
/// Expects byteArray of fixed 64bytes for the commitment
/// and dynamic tuple (old_scalar(32 bytes), new_scalar(32 bytes), index(1 byte)) in this sequence
/// Bytearray is processed with ffi_interface::deserialize_update_commitment_sparse and sent to ffi_interface::update_commitment_sparse.
/// We get updated commitemnt and return it as 64 bytes.
/// If Commitment is empty we should pass https://github.com/crate-crypto/rust-verkle/blob/bb5af2f2fe9788d49d2896b9614a3125f8227818/ffi_interface/src/lib.rs#L57
#[no_mangle]
pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_updateCommitmentSparse(
env: JNIEnv,
_class: JClass<'_>,
input: jbyteArray,
pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_hash(
env: JNIEnv, _class: JClass<'_>, commitment: jbyteArray
) -> jbyteArray {
let input = env
.convert_byte_array(input)
.expect("Cannot convert jbyteArray to rust array");

let (old_commitment_bytes, indexes, old_scalars, new_scalars) =
match ffi_interface::deserialize_update_commitment_sparse(input) {
Ok(decomposed_input) => decomposed_input,
Err(err) => {
env.throw_new(
"java/text/ParseException",
format!("Could not deserialize the input, error : {:?}", err),
)
.expect("Failed to throw exception");
return std::ptr::null_mut(); // Return null pointer to indicate an error
}
};
let updated_commitment = ffi_interface::update_commitment_sparse(
&CONFIG,
old_commitment_bytes,
indexes,
old_scalars,
new_scalars,
)
.unwrap();
let commitment = match parse_commitment(&env, commitment) {
Ok(v) => v,
Err(e) => {
env.throw_new("java/lang/IllegalArgumentException", e)
.expect("Failed to throw exception for commit inputs.");
return std::ptr::null_mut();
}
};
let hash = ffi_interface::hash_commitment(commitment);
let result = match env.byte_array_from_slice(&hash) {
Ok(s) => s,
Err(_) => {
env.throw_new(
"java/lang/IllegalArgumentException",
"Invalid commitment output. Couldn't convert to byte array.")
.expect("Couldn't convert to byte array");
return std::ptr::null_mut();
}
};
result
}

env.byte_array_from_slice(&updated_commitment)
.expect("Couldn't convert to byte array")
#[no_mangle]
pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_hashMany(
env: JNIEnv, _class: JClass<'_>, commitments: jbyteArray
) -> jbyteArray {
let input = match parse_commitments(&env, commitments) {
Ok(v) => v,
Err(e) => {
env.throw_new("java/lang/IllegalArgumentException", e)
.expect("Failed to throw exception for commit inputs.");
return std::ptr::null_mut();
}
};
let input: Vec<ffi_interface::CommitmentBytes> = input.chunks_exact(64).map(|x| {
let mut array = [0u8; 64];
array.copy_from_slice(x);
array
}).collect();
let hashes = ffi_interface::hash_commitments(&input);
let hashes: Vec<u8> = hashes.iter().flat_map(|x| x.iter().copied()).collect();
let result = match env.byte_array_from_slice(&hashes) {
Ok(s) => s,
Err(_) => {
env.throw_new(
"java/lang/IllegalArgumentException",
"Invalid scalars output. Couldn't convert to byte array.")
.expect("Couldn't convert to byte array");
return std::ptr::null_mut();
}
};
result
}
44 changes: 44 additions & 0 deletions ipa-multipoint/ipa_multipoint_jni/src/parsers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use jni::JNIEnv;
use jni::objects::ReleaseMode;
use jni::sys::jbyteArray;

use std::convert::TryFrom;

use ffi_interface::CommitmentBytes;


pub fn parse_scalars<'a>(env: &'a JNIEnv<'a>, values: jbyteArray) -> Result<&'a [u8], String> {
let input_len = env.get_array_length(values).map_err(|_| "Cannot get array lenght".to_string())? as usize;
if input_len % 32 != 0 {
return Err("Wrong input size: should be a mulitple of 32 bytes".to_string())
};
let input_elements = env.get_primitive_array_critical(values, ReleaseMode::NoCopyBack).map_err(|_| "Cannot get array elements".to_string())?;
let input_slice = unsafe { std::slice::from_raw_parts(input_elements.as_ptr() as *const u8, input_len) };
Ok(input_slice)
}

pub fn parse_indices(env: &JNIEnv, values: jbyteArray) -> Result<Vec<usize>, String> {
let input_len = env.get_array_length(values).map_err(|_| "Cannot get array lenght".to_string())? as usize;
let input_elements = env.get_primitive_array_critical(values, ReleaseMode::NoCopyBack).map_err(|_| "Cannot get array elements".to_string())?;
let input_slice = unsafe { std::slice::from_raw_parts(input_elements.as_ptr() as *const u8, input_len) };
let result: Vec<usize> = input_slice.iter().map(|&x| x as usize).collect();
Ok(result)
}

pub fn parse_commitment(env: &JNIEnv, commitment: jbyteArray) -> Result<CommitmentBytes, String> {
let input_len = env.get_array_length(commitment).map_err(|_| "Cannot get commitment lenght".to_string())? as usize;
let input_elements = env.get_primitive_array_critical(commitment, ReleaseMode::NoCopyBack).map_err(|_| "Cannot get array elements".to_string())?;
let input_slice = unsafe { std::slice::from_raw_parts(input_elements.as_ptr() as *const u8, input_len) };
let result: CommitmentBytes = CommitmentBytes::try_from(input_slice).map_err(|_| "Wrong commitment size: should be 64 bytes".to_string())?;
Ok(result)
}

pub fn parse_commitments<'a>(env: &'a JNIEnv<'a>, commitment: jbyteArray) -> Result<&'a [u8], String> {
let input_len = env.get_array_length(commitment).map_err(|_| "Cannot get commitment lenght".to_string())? as usize;
if input_len % 64 != 0 {
return Err("Wrong input size: should be a mulitple of 64 bytes".to_string())
};
let input_elements = env.get_primitive_array_critical(commitment, ReleaseMode::NoCopyBack).map_err(|_| "Cannot get array elements".to_string())?;
let input_slice = unsafe { std::slice::from_raw_parts(input_elements.as_ptr() as *const u8, input_len) };
Ok(input_slice)
}
Loading
Loading