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

chore: Switch to ffi_interface #139

Merged
merged 8 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
export PATH=$GOROOT/bin:$PATH
# rust dependencies
export CARGO_HOME="$HOME/.cargo"
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.68.2
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.75.0
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was added because CI uses stable and matt was getting an unstable error from one of the newer libraries for a feature which should be stable as of 1.75. (1.68 is quite old)

- name: Checkout Repo
uses: actions/checkout@v4
with:
Expand Down Expand Up @@ -123,7 +123,7 @@ jobs:
brew install go@1.20 || true
# rust dependencies
export CARGO_HOME="$HOME/.cargo"
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.68.2
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.75.0
# install both x86 and arm64 toolchains
export PATH="$HOME/.cargo/bin:$PATH"
rustup target add x86_64-apple-darwin
Expand Down Expand Up @@ -189,7 +189,7 @@ jobs:
brew install go@1.20 || true
# rust dependencies
export CARGO_HOME="$HOME/.cargo"
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.68.2
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.75.0
# install both x86 and arm64 toolchains
export PATH="$HOME/.cargo/bin:$PATH"
rustup target add x86_64-apple-darwin
Expand Down
19 changes: 7 additions & 12 deletions ipa-multipoint/ipa_multipoint_jni/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,15 @@ repository = "https://github.com/hyperledger/besu-native"
edition = "2018"

[dependencies]
verkle-spec = { git = "https://github.com/crate-crypto/rust-verkle", branch = "master" }
verkle-trie = { git = "https://github.com/crate-crypto/rust-verkle", branch = "master" }
ipa-multipoint = { git = "https://github.com/crate-crypto/ipa_multipoint", branch = "banderwagon_migration" }
banderwagon = { git = "https://github.com/crate-crypto/banderwagon" }
bandersnatch = "0.1.1"
ark-ff = { version = "^0.3.0", default-features = false }
ark-ec = { version = "^0.3.0", default-features = false }
ark-serialize = { version = "^0.3.0", default-features = false }
ark-std = { version = "^0.3.0", default-features = false }
jni = { version = "0.19.0", features = ["invocation"] } # We use invocation in tests.
ffi_interface = { git = "https://github.com/crate-crypto/rust-verkle", rev = "13dd7e9b4cb4491230fb0bda1759ef9c62938f28" }
ipa-multipoint = { git = "https://github.com/crate-crypto/rust-verkle", rev = "13dd7e9b4cb4491230fb0bda1759ef9c62938f28" }
jni = { version = "0.19.0", features = [
"invocation",
] } # We use invocation in tests.
hex = "0.4.3"
num-bigint = "0.4.4"

once_cell = "1.19.0"

[lib]
name = "ipa_multipoint_jni"
crate-type = ["cdylib"]
crate-type = ["cdylib"]
191 changes: 70 additions & 121 deletions ipa-multipoint/ipa_multipoint_jni/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,27 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
use ark_ff::PrimeField;
use banderwagon::{Fr, multi_scalar_mul};
use ipa_multipoint::crs::CRS;
use verkle_spec::*;
// use crate::{vergroup_to_field};
use ark_serialize::CanonicalSerialize;
use verkle_trie::*;

// use group_to_field;
use std::convert::TryInto;

use jni::JNIEnv;
use ipa_multipoint::committer::DefaultCommitter;
use ipa_multipoint::crs::CRS;
use jni::objects::JClass;
use jni::sys::jbyteArray;
use jni::JNIEnv;
use once_cell::sync::Lazy;

pub struct Config {
committer: DefaultCommitter,
}

// Copied from rust-verkle: https://github.com/crate-crypto/rust-verkle/blob/581200474327f5d12629ac2e1691eff91f944cec/verkle-trie/src/constants.rs#L12
const PEDERSEN_SEED: &'static [u8] = b"eth_verkle_oct_2021";
// 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<Config> = Lazy::new(|| {
let crs = CRS::default();
let committer = DefaultCommitter::new(&crs.G);
Config { committer }
});

/// Pedersen hash receives an address and a trie index and returns a hash calculated this way:
/// H(constant || address_low || address_high || trie_index_low || trie_index_high)
Expand All @@ -45,130 +49,75 @@ pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaM
_class: JClass,
input: jbyteArray,
) -> jbyteArray {

let input = env.convert_byte_array(input).unwrap();

let mut address32 = [0u8; 32];

address32.copy_from_slice(&input[0..32]);

let mut trie_index= [0u8; 32];

trie_index.copy_from_slice(&input[32..64]);
trie_index.reverse(); // reverse for little endian per specs

let base_hash = hash_addr_int(&address32, &trie_index);

let result = base_hash.as_fixed_bytes();
let output = env.byte_array_from_slice(result).unwrap();
output
}

// Helper function to hash an address and an integer taken from rust-verkle/verkle-specs.
pub(crate) fn hash_addr_int(addr: &[u8; 32], integer: &[u8; 32]) -> H256 {

let address_bytes = addr;

let integer_bytes = integer;
let mut hash_input = [0u8; 64];
let (first_half, second_half) = hash_input.split_at_mut(32);

// Copy address and index into slice, then hash it
first_half.copy_from_slice(address_bytes);
second_half.copy_from_slice(integer_bytes);
let committer = &CONFIG.committer;

let mut input: [u8; 64] = match input.try_into() {
Ok(input) => input,
Err(_) => {
env.throw_new(
"java/lang/IllegalArgumentException",
"Invalid input length. Should be 64-bytes.",
)
.expect("Failed to throw exception");
return std::ptr::null_mut(); // Return null pointer to indicate an error
}
};

// The tree_index is interpreted as a little endian integer
// But its given in big endian format.
// The tree_index is the last 32 bytes of the input,
// so we use this method to reverse its endian
fn reverse_last_32_bytes(arr: &mut [u8; 64]) {
let last_32 = &mut arr[32..];
last_32.reverse();
}
reverse_last_32_bytes(&mut input);

hash64(hash_input)
let hash = ffi_interface::get_tree_key_hash(committer, input);
env.byte_array_from_slice(&hash).unwrap()
}

/// 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)
-> jbyteArray {
// Input should be a multiple of 32-be-bytes.
let inp = env.convert_byte_array(input).expect("Cannot convert jbyteArray to rust array");
let len = inp.len();
if len % 32 != 0 {
env.throw_new("java/lang/IllegalArgumentException", "Invalid input length. Should be a multiple of 32-bytes.")
.expect("Failed to throw exception");
return std::ptr::null_mut(); // Return null pointer to indicate an error
}
let n_scalars = len / 32;
if n_scalars > 256 {
env.throw_new("java/lang/IllegalArgumentException", "Invalid input length. Should be at most 256 elements of 32-bytes.")
.expect("Failed to throw exception");
return std::ptr::null_mut(); // Return null pointer to indicate an error
}

// Each 32-be-bytes are interpreted as field elements.
let mut scalars: Vec<Fr> = Vec::with_capacity(n_scalars);
for b in inp.chunks(32) {
scalars.push(Fr::from_be_bytes_mod_order(b));
}

// Committing all values at once.
let bases = CRS::new(n_scalars, PEDERSEN_SEED);
let commit = multi_scalar_mul(&bases.G, &scalars);

// Serializing via x/y in projective coordinates, to int and to scalars.
let scalar = group_to_field(&commit);
let mut scalar_bytes = [0u8; 32];
scalar.serialize(&mut scalar_bytes[..]).expect("could not serialise Fr into a 32 byte array");
scalar_bytes.reverse();

return env.byte_array_from_slice(&scalar_bytes).expect("Couldn't convert to byte array");
}
pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_commit(
env: JNIEnv,
_class: JClass<'_>,
input: jbyteArray,
) -> jbyteArray {
let input = env
.convert_byte_array(input)
.expect("Cannot convert jbyteArray to rust array");

let committer = &CONFIG.committer;

let commitment = ffi_interface::commit_to_scalars(committer, &input).unwrap();
let hash = ffi_interface::hash_commitment(commitment);

env.byte_array_from_slice(&hash)
.expect("Couldn't convert to byte array")
}

/// 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)
-> jbyteArray {
// Input should be a multiple of 32-be-bytes.
let inp = env.convert_byte_array(input).expect("Cannot convert jbyteArray to rust array");
let len = inp.len();
if len % 32 != 0 {
env.throw_new("java/lang/IllegalArgumentException", "Invalid input length. Should be a multiple of 32-bytes.")
.expect("Failed to throw exception");
return std::ptr::null_mut(); // Return null pointer to indicate an error
}
let n_scalars = len / 32;
if n_scalars > 256 {
env.throw_new("java/lang/IllegalArgumentException", "Invalid input length. Should be at most 256 elements of 32-bytes.")
.expect("Failed to throw exception");
return std::ptr::null_mut(); // Return null pointer to indicate an error
}

// Each 32-be-bytes are interpreted as field elements.
let mut scalars: Vec<Fr> = Vec::with_capacity(n_scalars);
for b in inp.chunks(32) {
scalars.push(Fr::from_be_bytes_mod_order(b));
}

// Committing all values at once.
let bases = CRS::new(n_scalars, PEDERSEN_SEED);
let commit = multi_scalar_mul(&bases.G, &scalars);

// Serializing using first affine coordinate
let commit_bytes = commit.to_bytes();
pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_commitRoot(
env: JNIEnv,
_class: JClass<'_>,
input: jbyteArray,
) -> jbyteArray {
let input = env
.convert_byte_array(input)
.expect("Cannot convert jbyteArray to rust array");

return env.byte_array_from_slice(&commit_bytes).expect("Couldn't convert to byte array");
}
let committer = &CONFIG.committer;

let commitment = ffi_interface::commit_to_scalars(committer, &input).unwrap();
let hash = ffi_interface::deprecated_serialize_commitment(commitment);

// Note: This is a 2 to 1 map, but the two preimages are identified to be the same
// TODO: Create a document showing that this poses no problems
pub(crate)fn group_to_field(point: &Element) -> Fr {
let base_field = point.map_to_field();
let mut bytes = [0u8; 32];
base_field
.serialize(&mut bytes[..])
.expect("could not serialise point into a 32 byte array");
Fr::from_le_bytes_mod_order(&bytes)
}
env.byte_array_from_slice(&hash)
.expect("Couldn't convert to byte array")
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ public void testCallLibrary() {

@Test
public void testCallLibraryCommitRoot() {
Bytes32 input = Bytes32.fromHexString("0xf6e31f7a565a390b48fdd24569ac10d43562d19de37ea951c7f9f250a339d059");
Bytes32 input = Bytes32.fromHexString("0x00e31f7a565a390b48fdd24569ac10d43562d19de37ea951c7f9f250a339d059");
Bytes32 result = Bytes32.wrap(LibIpaMultipoint.commitRoot(input.toArray()));
Bytes32 expected = Bytes32.fromHexString("0x588f93e52b41d8d3abade94bf44a2ccd5d6ef9090a63164fba8d02c909168c53");
Bytes32 expected = Bytes32.fromHexString("0x3337896554fd3960bef9a4d0ff658ee8ee470cf9ca88a3c807cbe128536c5c05");
assertThat(result).isEqualTo(expected);
}

Expand All @@ -55,24 +55,24 @@ public void testCallLibraryWithManyElements() {

@Test
public void testCallLibraryWithMaxElements() {
Bytes32 element = Bytes32.fromHexString("0xd36f20567f74f607d9252186ff8efed04de4578d1ddb3de4fe6c5e4249e0045b");
Bytes32 element = Bytes32.fromHexString("0x006f20567f74f607d9252186ff8efed04de4578d1ddb3de4fe6c5e4249e0045b");
Bytes32[] arr = new Bytes32[256];
for (int i = 0; i < 256; i++) {
arr[i] = element;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are from #142

Bytes input = Bytes.concatenate(arr);
Bytes32 result = Bytes32.wrap(LibIpaMultipoint.commit(input.toArray()));
Bytes32 expected = Bytes32.fromHexString("0x069e4460d5dd6b48cfcb0a3338a84bc6e38fe686c9d9035017570c3fa10471d6");
Bytes32 expected = Bytes32.fromHexString("0x148badc53042581c95e71f21b2b85826bc2f4088e617cd18f488ab664af1d043");
assertThat(result).isEqualTo(expected);
}

@Test
public void testCallLibraryPedersenHash() {
// Example of passing address and trieIndex to pedersenHash.
Bytes32 address = Bytes32.fromHexString("0xed3f9549040250ec5cdef31947e5213edee80ad2d5bba35c9e48246c5d9213d6");
Bytes32 trieIndex = Bytes32.fromHexString("0x1C4C6CE0115457AC1AB82968749EB86ED2D984743D609647AE88299989F91271");
Bytes32 address = Bytes32.fromHexString("0x003f9549040250ec5cdef31947e5213edee80ad2d5bba35c9e48246c5d9213d6");
Bytes32 trieIndex = Bytes32.fromHexString("0x004C6CE0115457AC1AB82968749EB86ED2D984743D609647AE88299989F91271");
byte[] total = Bytes.wrap(address, trieIndex).toArray();
Bytes result = Bytes.of(LibIpaMultipoint.pedersenHash(total));
assertThat(result).isEqualTo(Bytes32.fromHexString("0x2e50716b7d8c6d13d6005ea248f63f5a11ed63318cad38010f4bcb9a9c2e8b43"));
assertThat(result).isEqualTo(Bytes32.fromHexString("0xff6e8f1877fd27f91772a4cec41d99d2f835d7320e929b8d509c5fa7ce095c51"));
}
}
Loading