diff --git a/rln/src/ffi.rs b/rln/src/ffi.rs index d92c2bd..5ac3768 100644 --- a/rln/src/ffi.rs +++ b/rln/src/ffi.rs @@ -2,7 +2,7 @@ use std::slice; -use crate::public::RLN; +use crate::public::{hash as public_hash, poseidon_hash as public_poseidon_hash, RLN}; // Macro to call methods with arbitrary amount of arguments, // First argument to the macro is context, @@ -53,6 +53,28 @@ macro_rules! call_with_output_arg { false } } + }; + +} + +// Macro to call methods with arbitrary amount of arguments, +// which are not implemented in a ctx RLN object +// First argument is the method to call +// Second argument is the output buffer argument +// The remaining arguments are all other inputs to the method +macro_rules! no_ctx_call_with_output_arg { + ($method:ident, $output_arg:expr, $( $arg:expr ),* ) => { + { + let mut output_data: Vec = Vec::new(); + if $method($($arg.process()),*, &mut output_data).is_ok() { + unsafe { *$output_arg = Buffer::from(&output_data[..]) }; + std::mem::forget(output_data); + true + } else { + std::mem::forget(output_data); + false + } + } } } @@ -342,10 +364,12 @@ pub extern "C" fn recover_id_secret( #[allow(clippy::not_unsafe_ptr_arg_deref)] #[no_mangle] -pub extern "C" fn hash( - ctx: *mut RLN, - input_buffer: *const Buffer, - output_buffer: *mut Buffer, -) -> bool { - call_with_output_arg!(ctx, hash, output_buffer, input_buffer) +pub extern "C" fn hash(input_buffer: *const Buffer, output_buffer: *mut Buffer) -> bool { + no_ctx_call_with_output_arg!(public_hash, output_buffer, input_buffer) +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn poseidon_hash(input_buffer: *const Buffer, output_buffer: *mut Buffer) -> bool { + no_ctx_call_with_output_arg!(public_poseidon_hash, output_buffer, input_buffer) } diff --git a/rln/src/public.rs b/rln/src/public.rs index e34ddba..92ba43e 100644 --- a/rln/src/public.rs +++ b/rln/src/public.rs @@ -1,5 +1,5 @@ use crate::circuit::{vk_from_raw, zkey_from_raw, Curve, Fr}; -use crate::poseidon_hash::poseidon_hash; +use crate::poseidon_hash::poseidon_hash as utils_poseidon_hash; use crate::poseidon_tree::PoseidonTree; use crate::protocol::*; use crate::utils::*; @@ -953,14 +953,14 @@ impl RLN<'_> { // We skip deserialization of the zk-proof at the beginning let (proof_values_1, _) = deserialize_proof_values(&serialized[128..]); let external_nullifier_1 = - poseidon_hash(&[proof_values_1.epoch, proof_values_1.rln_identifier]); + utils_poseidon_hash(&[proof_values_1.epoch, proof_values_1.rln_identifier]); let mut serialized: Vec = Vec::new(); input_proof_data_2.read_to_end(&mut serialized)?; // We skip deserialization of the zk-proof at the beginning let (proof_values_2, _) = deserialize_proof_values(&serialized[128..]); let external_nullifier_2 = - poseidon_hash(&[proof_values_2.epoch, proof_values_2.rln_identifier]); + utils_poseidon_hash(&[proof_values_2.epoch, proof_values_2.rln_identifier]); // We continue only if the proof values are for the same epoch // The idea is that proof values that go as input to this function are verified first (with zk-proof verify), hence ensuring validity of epoch and other fields. @@ -984,38 +984,6 @@ impl RLN<'_> { Ok(()) } - /// Hashes an input signal to an element in the working prime field. - /// - /// The result is computed as the Keccak256 of the input signal modulo the prime field characteristic. - /// - /// Input values are: - /// - `input_data`: a reader for the byte vector containing the input signal. - /// - /// Output values are: - /// - `output_data`: a writer receiving the serialization of the resulting field element (serialization done with [`rln::utils::fr_to_bytes_le`](crate::utils::fr_to_bytes_le)) - /// - /// Example - /// ``` - /// let signal: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - /// - /// let mut input_buffer = Cursor::new(&signal); - /// let mut output_buffer = Cursor::new(Vec::::new()); - /// rln.hash(&mut input_buffer, &mut output_buffer) - /// .unwrap(); - /// - /// // We deserialize the keygen output - /// let field_element = deserialize_field_element(output_buffer.into_inner()); - /// ``` - pub fn hash(&self, mut input_data: R, mut output_data: W) -> io::Result<()> { - let mut serialized: Vec = Vec::new(); - input_data.read_to_end(&mut serialized)?; - - let hash = hash_to_field(&serialized); - output_data.write_all(&fr_to_bytes_le(&hash))?; - - Ok(()) - } - /// Returns the serialization of a [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) populated from the identity secret, the Merkle tree index, the epoch and signal. /// /// Input values are: @@ -1055,6 +1023,72 @@ impl Default for RLN<'_> { } } +/// Hashes an input signal to an element in the working prime field. +/// +/// The result is computed as the Keccak256 of the input signal modulo the prime field characteristic. +/// +/// Input values are: +/// - `input_data`: a reader for the byte vector containing the input signal. +/// +/// Output values are: +/// - `output_data`: a writer receiving the serialization of the resulting field element (serialization done with [`rln::utils::fr_to_bytes_le`](crate::utils::fr_to_bytes_le)) +/// +/// Example +/// ``` +/// let signal: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; +/// +/// let mut input_buffer = Cursor::new(&signal); +/// let mut output_buffer = Cursor::new(Vec::::new()); +/// hash(&mut input_buffer, &mut output_buffer) +/// .unwrap(); +/// +/// // We deserialize the keygen output +/// let field_element = deserialize_field_element(output_buffer.into_inner()); +/// ``` +pub fn hash(mut input_data: R, mut output_data: W) -> io::Result<()> { + let mut serialized: Vec = Vec::new(); + input_data.read_to_end(&mut serialized)?; + + let hash = hash_to_field(&serialized); + output_data.write_all(&fr_to_bytes_le(&hash))?; + + Ok(()) +} + +/// Hashes a set of elements to a single element in the working prime field, using Poseidon. +/// +/// The result is computed as the Poseidon Hash of the input signal. +/// +/// Input values are: +/// - `input_data`: a reader for the byte vector containing the input signal. +/// +/// Output values are: +/// - `output_data`: a writer receiving the serialization of the resulting field element (serialization done with [`rln::utils::fr_to_bytes_le`](crate::utils::fr_to_bytes_le)) +/// +/// Example +/// ``` +/// let data = vec![hash_to_field(b"foo")]; +/// let signal = vec_fr_to_bytes_le(&data); +/// +/// let mut input_buffer = Cursor::new(&signal); +/// let mut output_buffer = Cursor::new(Vec::::new()); +/// poseidon_hash(&mut input_buffer, &mut output_buffer) +/// .unwrap(); +/// +/// // We deserialize the hash output +/// let hash_result = deserialize_field_element(output_buffer.into_inner()); +/// ``` +pub fn poseidon_hash(mut input_data: R, mut output_data: W) -> io::Result<()> { + let mut serialized: Vec = Vec::new(); + input_data.read_to_end(&mut serialized)?; + + let (inputs, _) = bytes_le_to_vec_fr(&serialized); + let hash = utils_poseidon_hash(inputs.as_ref()); + output_data.write_all(&fr_to_bytes_le(&hash))?; + + Ok(()) +} + #[cfg(test)] mod test { use super::*; diff --git a/rln/tests/ffi.rs b/rln/tests/ffi.rs index a60137c..cffe0f1 100644 --- a/rln/tests/ffi.rs +++ b/rln/tests/ffi.rs @@ -3,8 +3,8 @@ mod test { use ark_std::{rand::thread_rng, UniformRand}; use rand::Rng; use rln::circuit::*; - use rln::ffi::*; - use rln::poseidon_hash::poseidon_hash; + use rln::ffi::{hash as ffi_hash, poseidon_hash as ffi_poseidon_hash, *}; + use rln::poseidon_hash::{poseidon_hash as utils_poseidon_hash, ROUND_PARAMS}; use rln::protocol::*; use rln::public::RLN; use rln::utils::*; @@ -280,7 +280,7 @@ mod test { // generate identity let identity_secret_hash = hash_to_field(b"test-merkle-proof"); - let id_commitment = poseidon_hash(&vec![identity_secret_hash]); + let id_commitment = utils_poseidon_hash(&vec![identity_secret_hash]); // We prepare id_commitment and we set the leaf at provided index let leaf_ser = fr_to_bytes_le(&id_commitment); @@ -1016,22 +1016,13 @@ mod test { #[test] // Tests hash to field using FFI APIs fn test_hash_to_field_ffi() { - let tree_height = TEST_TREE_HEIGHT; - - // We create a RLN instance - let mut rln_pointer = MaybeUninit::<*mut RLN>::uninit(); - let input_buffer = &Buffer::from(TEST_RESOURCES_FOLDER.as_bytes()); - let success = new(tree_height, input_buffer, rln_pointer.as_mut_ptr()); - assert!(success, "RLN object creation failed"); - let rln_pointer = unsafe { &mut *rln_pointer.assume_init() }; - let mut rng = rand::thread_rng(); let signal: [u8; 32] = rng.gen(); // We prepare id_commitment and we set the leaf at provided index let input_buffer = &Buffer::from(signal.as_ref()); let mut output_buffer = MaybeUninit::::uninit(); - let success = hash(rln_pointer, input_buffer, output_buffer.as_mut_ptr()); + let success = ffi_hash(input_buffer, output_buffer.as_mut_ptr()); assert!(success, "hash call failed"); let output_buffer = unsafe { output_buffer.assume_init() }; @@ -1043,4 +1034,30 @@ mod test { assert_eq!(hash1, hash2); } + + #[test] + // Test Poseidon hash FFI + fn test_poseidon_hash_ffi() { + // generate random number between 1..ROUND_PARAMS.len() + let mut rng = thread_rng(); + let number_of_inputs = rng.gen_range(1..ROUND_PARAMS.len()); + let mut inputs = Vec::with_capacity(number_of_inputs); + for _ in 0..number_of_inputs { + inputs.push(Fr::rand(&mut rng)); + } + let inputs_ser = vec_fr_to_bytes_le(&inputs); + let input_buffer = &Buffer::from(inputs_ser.as_ref()); + + let expected_hash = utils_poseidon_hash(inputs.as_ref()); + + let mut output_buffer = MaybeUninit::::uninit(); + let success = ffi_poseidon_hash(input_buffer, output_buffer.as_mut_ptr()); + assert!(success, "poseidon hash call failed"); + + let output_buffer = unsafe { output_buffer.assume_init() }; + let result_data = <&[u8]>::from(&output_buffer).to_vec(); + let (received_hash, _) = bytes_le_to_fr(&result_data); + + assert_eq!(received_hash, expected_hash); + } } diff --git a/rln/tests/public.rs b/rln/tests/public.rs index 2cb0d90..4826bf5 100644 --- a/rln/tests/public.rs +++ b/rln/tests/public.rs @@ -1,10 +1,11 @@ #[cfg(test)] mod test { + use ark_std::{rand::thread_rng, UniformRand}; use rand::Rng; - use rln::circuit::{TEST_RESOURCES_FOLDER, TEST_TREE_HEIGHT}; - use rln::poseidon_hash::poseidon_hash; + use rln::circuit::{Fr, TEST_RESOURCES_FOLDER, TEST_TREE_HEIGHT}; + use rln::poseidon_hash::{poseidon_hash as utils_poseidon_hash, ROUND_PARAMS}; use rln::protocol::{compute_tree_root, deserialize_identity_tuple, hash_to_field}; - use rln::public::RLN; + use rln::public::{hash as public_hash, poseidon_hash as public_poseidon_hash, RLN}; use rln::utils::*; use std::io::Cursor; @@ -19,7 +20,7 @@ mod test { // generate identity let identity_secret_hash = hash_to_field(b"test-merkle-proof"); - let id_commitment = poseidon_hash(&vec![identity_secret_hash]); + let id_commitment = utils_poseidon_hash(&vec![identity_secret_hash]); // We pass id_commitment as Read buffer to RLN's set_leaf let mut buffer = Cursor::new(fr_to_bytes_le(&id_commitment)); @@ -250,15 +251,13 @@ mod test { #[test] fn test_hash_to_field() { - let rln = RLN::default(); - - let mut rng = rand::thread_rng(); + let mut rng = thread_rng(); let signal: [u8; 32] = rng.gen(); let mut input_buffer = Cursor::new(&signal); let mut output_buffer = Cursor::new(Vec::::new()); - rln.hash(&mut input_buffer, &mut output_buffer).unwrap(); + public_hash(&mut input_buffer, &mut output_buffer).unwrap(); let serialized_hash = output_buffer.into_inner(); let (hash1, _) = bytes_le_to_fr(&serialized_hash); @@ -266,4 +265,24 @@ mod test { assert_eq!(hash1, hash2); } + + #[test] + fn test_poseidon_hash() { + let mut rng = thread_rng(); + let number_of_inputs = rng.gen_range(1..ROUND_PARAMS.len()); + let mut inputs = Vec::with_capacity(number_of_inputs); + for _ in 0..number_of_inputs { + inputs.push(Fr::rand(&mut rng)); + } + let expected_hash = utils_poseidon_hash(&inputs); + + let mut input_buffer = Cursor::new(vec_fr_to_bytes_le(&inputs)); + let mut output_buffer = Cursor::new(Vec::::new()); + + public_poseidon_hash(&mut input_buffer, &mut output_buffer).unwrap(); + let serialized_hash = output_buffer.into_inner(); + let (hash, _) = bytes_le_to_fr(&serialized_hash); + + assert_eq!(hash, expected_hash); + } }