feat(rln): expose poseidon to ffi (#112)

This commit is contained in:
Aaryamann Challani 2023-02-16 13:26:13 +05:30 committed by GitHub
parent e21e9954ac
commit a6145ab201
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 157 additions and 63 deletions

View File

@ -2,7 +2,7 @@
use std::slice; 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, // Macro to call methods with arbitrary amount of arguments,
// First argument to the macro is context, // First argument to the macro is context,
@ -53,6 +53,28 @@ macro_rules! call_with_output_arg {
false 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<u8> = 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)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle] #[no_mangle]
pub extern "C" fn hash( pub extern "C" fn hash(input_buffer: *const Buffer, output_buffer: *mut Buffer) -> bool {
ctx: *mut RLN, no_ctx_call_with_output_arg!(public_hash, output_buffer, input_buffer)
input_buffer: *const Buffer, }
output_buffer: *mut Buffer,
) -> bool { #[allow(clippy::not_unsafe_ptr_arg_deref)]
call_with_output_arg!(ctx, hash, output_buffer, input_buffer) #[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)
} }

View File

@ -1,5 +1,5 @@
use crate::circuit::{vk_from_raw, zkey_from_raw, Curve, Fr}; 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::poseidon_tree::PoseidonTree;
use crate::protocol::*; use crate::protocol::*;
use crate::utils::*; use crate::utils::*;
@ -953,14 +953,14 @@ impl RLN<'_> {
// We skip deserialization of the zk-proof at the beginning // We skip deserialization of the zk-proof at the beginning
let (proof_values_1, _) = deserialize_proof_values(&serialized[128..]); let (proof_values_1, _) = deserialize_proof_values(&serialized[128..]);
let external_nullifier_1 = 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<u8> = Vec::new(); let mut serialized: Vec<u8> = Vec::new();
input_proof_data_2.read_to_end(&mut serialized)?; input_proof_data_2.read_to_end(&mut serialized)?;
// We skip deserialization of the zk-proof at the beginning // We skip deserialization of the zk-proof at the beginning
let (proof_values_2, _) = deserialize_proof_values(&serialized[128..]); let (proof_values_2, _) = deserialize_proof_values(&serialized[128..]);
let external_nullifier_2 = 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 // 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. // 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(()) 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::<u8>::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<R: Read, W: Write>(&self, mut input_data: R, mut output_data: W) -> io::Result<()> {
let mut serialized: Vec<u8> = 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. /// 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: /// 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::<u8>::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<R: Read, W: Write>(mut input_data: R, mut output_data: W) -> io::Result<()> {
let mut serialized: Vec<u8> = 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::<u8>::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<R: Read, W: Write>(mut input_data: R, mut output_data: W) -> io::Result<()> {
let mut serialized: Vec<u8> = 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)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View File

@ -3,8 +3,8 @@ mod test {
use ark_std::{rand::thread_rng, UniformRand}; use ark_std::{rand::thread_rng, UniformRand};
use rand::Rng; use rand::Rng;
use rln::circuit::*; use rln::circuit::*;
use rln::ffi::*; use rln::ffi::{hash as ffi_hash, poseidon_hash as ffi_poseidon_hash, *};
use rln::poseidon_hash::poseidon_hash; use rln::poseidon_hash::{poseidon_hash as utils_poseidon_hash, ROUND_PARAMS};
use rln::protocol::*; use rln::protocol::*;
use rln::public::RLN; use rln::public::RLN;
use rln::utils::*; use rln::utils::*;
@ -280,7 +280,7 @@ mod test {
// generate identity // generate identity
let identity_secret_hash = hash_to_field(b"test-merkle-proof"); 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 // We prepare id_commitment and we set the leaf at provided index
let leaf_ser = fr_to_bytes_le(&id_commitment); let leaf_ser = fr_to_bytes_le(&id_commitment);
@ -1016,22 +1016,13 @@ mod test {
#[test] #[test]
// Tests hash to field using FFI APIs // Tests hash to field using FFI APIs
fn test_hash_to_field_ffi() { 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 mut rng = rand::thread_rng();
let signal: [u8; 32] = rng.gen(); let signal: [u8; 32] = rng.gen();
// We prepare id_commitment and we set the leaf at provided index // We prepare id_commitment and we set the leaf at provided index
let input_buffer = &Buffer::from(signal.as_ref()); let input_buffer = &Buffer::from(signal.as_ref());
let mut output_buffer = MaybeUninit::<Buffer>::uninit(); let mut output_buffer = MaybeUninit::<Buffer>::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"); assert!(success, "hash call failed");
let output_buffer = unsafe { output_buffer.assume_init() }; let output_buffer = unsafe { output_buffer.assume_init() };
@ -1043,4 +1034,30 @@ mod test {
assert_eq!(hash1, hash2); 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::<Buffer>::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);
}
} }

View File

@ -1,10 +1,11 @@
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use ark_std::{rand::thread_rng, UniformRand};
use rand::Rng; use rand::Rng;
use rln::circuit::{TEST_RESOURCES_FOLDER, TEST_TREE_HEIGHT}; use rln::circuit::{Fr, TEST_RESOURCES_FOLDER, TEST_TREE_HEIGHT};
use rln::poseidon_hash::poseidon_hash; 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::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 rln::utils::*;
use std::io::Cursor; use std::io::Cursor;
@ -19,7 +20,7 @@ mod test {
// generate identity // generate identity
let identity_secret_hash = hash_to_field(b"test-merkle-proof"); 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 // We pass id_commitment as Read buffer to RLN's set_leaf
let mut buffer = Cursor::new(fr_to_bytes_le(&id_commitment)); let mut buffer = Cursor::new(fr_to_bytes_le(&id_commitment));
@ -250,15 +251,13 @@ mod test {
#[test] #[test]
fn test_hash_to_field() { fn test_hash_to_field() {
let rln = RLN::default(); let mut rng = thread_rng();
let mut rng = rand::thread_rng();
let signal: [u8; 32] = rng.gen(); let signal: [u8; 32] = rng.gen();
let mut input_buffer = Cursor::new(&signal); let mut input_buffer = Cursor::new(&signal);
let mut output_buffer = Cursor::new(Vec::<u8>::new()); let mut output_buffer = Cursor::new(Vec::<u8>::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 serialized_hash = output_buffer.into_inner();
let (hash1, _) = bytes_le_to_fr(&serialized_hash); let (hash1, _) = bytes_le_to_fr(&serialized_hash);
@ -266,4 +265,24 @@ mod test {
assert_eq!(hash1, hash2); 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::<u8>::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);
}
} }