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

ENH: adds ipa-multipoint library with Pedersen primitives #123

Merged
merged 3 commits into from
Nov 3, 2023
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
24 changes: 19 additions & 5 deletions ipa-multipoint/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,25 @@ plugins {
}

dependencies {
implementation 'net.java.dev.jna:jna:5.12.1'
testImplementation 'org.apache.tuweni:tuweni-bytes:2.2.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.mockito:mockito-core:4.4.0'
implementation 'net.java.dev.jna:jna:5.12.1'
testImplementation 'org.apache.tuweni:tuweni-bytes:2.3.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.2'
testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.12.5'
testImplementation 'org.assertj:assertj-core:3.22.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
}

test {
useJUnitPlatform()
}

sourceSets {
test {
resources {
srcDirs = ['src/test/resources']
}
}
}

task macArmLibCopy(type: Copy) {
Expand Down
12 changes: 10 additions & 2 deletions ipa-multipoint/ipa_multipoint_jni/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,19 @@ repository = "https://github.com/hyperledger/besu-native"
edition = "2018"

[dependencies]
ipa-multipoint = { git = "https://github.com/crate-crypto/ipa_multipoint", rev = "d5590ef5" }
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.
ark-ff = "0.3.0"
hex = "0.4.3"
num-bigint = "0.4.4"


[lib]
name = "ipa_multipoint_jni"
Expand Down
220 changes: 142 additions & 78 deletions ipa-multipoint/ipa_multipoint_jni/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,99 +12,163 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
use std::convert::TryFrom;
use ark_ff::bytes::{FromBytes, ToBytes};
use bandersnatch::Fr;
use ipa_multipoint::lagrange_basis::LagrangeBasis;
use ipa_multipoint::multiproof::CRS;
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 jni::JNIEnv;
use jni::objects::JClass;
use jni::sys::{jbyteArray, jobjectArray, jsize};
use jni::sys::jbyteArray;


// Seed used to compute the 256 pedersen generators
// using try-and-increment
// 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";

/// 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)
/// where constant = 2 + 256*64
/// address_low = lower 16 bytes of the address interpreted as a little endian integer
/// address_high = higher 16 bytes of the address interpreted as a little endian integer
/// trie_index_low = lower 16 bytes of the trie index
/// trie_index_high = higher 16 bytes of the trie index
/// The result is a 256 bit hash
/// This is ported from rust-verkle/verkle-specs
#[no_mangle]
pub extern "system" fn Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_pedersenHash(
env: JNIEnv,
_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);

hash64(hash_input)
}

/// 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: jobjectArray)
input: jbyteArray)
-> jbyteArray {
let length = env.get_array_length(input).unwrap();
let len = <usize as TryFrom<jsize>>::try_from(length)
.expect("invalid jsize, in jsize => usize conversation");
let mut vec = Vec::with_capacity(len);
for i in 0..length {
let jbarray: jbyteArray = env.get_object_array_element(input, i).unwrap().cast();
let barray = env.convert_byte_array(jbarray).expect("Couldn't read byte array input");
vec.push(Fr::read(barray.as_ref()).unwrap())
// 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);

let poly = LagrangeBasis::new(vec);
let crs = CRS::new(256, PEDERSEN_SEED);
let result = crs.commit_lagrange_poly(&poly);
let mut result_bytes = [0u8; 128];
result.write(result_bytes.as_mut()).unwrap();
let javaarray = env.byte_array_from_slice(&result_bytes).expect("Couldn't convert to byte array");
return javaarray;
// 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");
}

#[cfg(test)]
mod tests {
use std::ops::Deref;

use ark_ff::biginteger::BigInteger256;
use ark_ff::ToBytes;
use bandersnatch::Fr;
use hex;
use jni::{InitArgsBuilder, JavaVM};

use crate::Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_commit;

#[test]
fn commit_multiproof_lagrange() {
let f1_from_repr = Fr::from(BigInteger256([
0xc81265fb4130fe0c,
0xb308836c14e22279,
0x699e887f96bff372,
0x84ecc7e76c11ad,
]));

let mut f1_bytes = [0u8; 32];
f1_from_repr.write(f1_bytes.as_mut()).unwrap();

let jvm_args = InitArgsBuilder::default().build().unwrap();
let jvm = JavaVM::new(jvm_args).unwrap();
let guard = jvm.attach_current_thread().unwrap();
let env = guard.deref();
let class = env.find_class("java/lang/String").unwrap();
let jarray = env.byte_array_from_slice(&f1_bytes).unwrap();
let objarray = env.new_object_array(4, "java/lang/byte[]", jarray).unwrap();
env.set_object_array_element(objarray, 1, jarray).expect("cannot set input");
env.set_object_array_element(objarray, 2, jarray).expect("cannot set input");
env.set_object_array_element(objarray, 3, jarray).expect("cannot set input");
let result = Java_org_hyperledger_besu_nativelib_ipamultipoint_LibIpaMultipoint_commit(*env, class, objarray);
let result_u8 = env.convert_byte_array(result).unwrap();
assert_eq!("0fc066481fb30a138938dc749fa3608fc840386671d3ee355d778ed4e1843117a73b5363f846b850a958dab228d6c181f6e2c1035dad9b3b47c4d4bbe4b8671adc36f4edb34ac17a093f1c183f00f6e4863a2b38a7470edd1739cc1fdbc6541bc3b7896389a3fe5f59cdefe3ac2f8ae89101c227395d6fc7bca05f138683e204", hex::encode(result_u8));
}

#[test]
fn commit_multiproof_lagrange_known_input() {
let mut vec = Vec::with_capacity(len);
vec.insert(2, Fr::read(hex::decode("")).unwrap());
for i in 0..length {
let jbarray: jbyteArray = env.get_object_array_element(input, i).unwrap().cast();
let barray = env.convert_byte_array(jbarray).expect("Couldn't read byte array input");
vec.push(Fr::read(barray.as_ref()).unwrap())
}

let poly = LagrangeBasis::new(vec);
let crs = CRS::new(256, PEDERSEN_SEED);
let result = crs.commit_lagrange_poly(&poly);
let mut result_bytes = [0u8; 128];
result.write(result_bytes.as_mut()).unwrap();
assert_eq!("0fc066481fb30a138938dc749fa3608fc840386671d3ee355d778ed4e1843117a73b5363f846b850a958dab228d6c181f6e2c1035dad9b3b47c4d4bbe4b8671adc36f4edb34ac17a093f1c183f00f6e4863a2b38a7470edd1739cc1fdbc6541bc3b7896389a3fe5f59cdefe3ac2f8ae89101c227395d6fc7bca05f138683e204", hex::encode(result_u8));
/// 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_commit_root(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();

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


// 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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,24 @@ public class LibIpaMultipoint {
}

/**
* Evaluates a polynomial of degree 3 (uniquely defined by 4 values) at a specific point on the curve.
* Evaluates a polynomial of degree 255 (uniquely defined by 256 values) at a specific point on the curve.

* @param input polynomial elements
* @return the coordinates of the projection of the polynomial on the curve
* @param input [Fr,Fr,Fr...]
* @return group_to_field(commitment)
*/
public static native byte[] commit(byte[][] input);
public static native byte[] commit(byte[] input);

/**
* Evaluates a polynomial of degree 255 (uniquely defined by 256 values) at a specific point on the curve.
* @param input [Fr,Fr,Fr...]
* @return commitment.to_bytes()
*/
public static native byte[] commit_root(byte[] input);

/**
* Pedersen hash as specified in https://notes.ethereum.org/@vbuterin/verkle_tree_eip
* @param input Expects 64byte value as input encoded as byte[] e.g. "0x000..." <-> [48,48,48...] (48 is 0 in ASCII)
* @return 32bytes as byte[] "0x000..." <-> [48,48,48...] (48 is 0 in ASCII)
*/
public static native byte[] pedersenHash(byte[] input);
}
Loading
Loading