diff --git a/rln/Cargo.toml b/rln/Cargo.toml index 133875c..7e7f514 100644 --- a/rln/Cargo.toml +++ b/rln/Cargo.toml @@ -33,6 +33,7 @@ num-bigint = { version = "0.4.3", default-features = false, features = ["rand"] num-traits = "0.2.11" once_cell = "1.14.0" rand = "0.8" +rand_chacha = "0.3.1" tiny-keccak = { version = "2.0.2", features = ["keccak"] } utils = { path = "../utils/", default-features = false } diff --git a/rln/src/ffi.rs b/rln/src/ffi.rs index ea18f4f..2206b6a 100644 --- a/rln/src/ffi.rs +++ b/rln/src/ffi.rs @@ -243,6 +243,26 @@ pub extern "C" fn key_gen(ctx: *const RLN, output_buffer: *mut Buffer) -> bool { } } +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn seeded_key_gen( + ctx: *const RLN, + input_buffer: *const Buffer, + output_buffer: *mut Buffer, +) -> bool { + let rln = unsafe { &*ctx }; + let input_data = <&[u8]>::from(unsafe { &*input_buffer }); + let mut output_data: Vec = Vec::new(); + if rln.seeded_key_gen(input_data, &mut output_data).is_ok() { + unsafe { *output_buffer = Buffer::from(&output_data[..]) }; + std::mem::forget(output_data); + true + } else { + std::mem::forget(output_data); + false + } +} + #[allow(clippy::not_unsafe_ptr_arg_deref)] #[no_mangle] pub extern "C" fn hash( @@ -758,6 +778,43 @@ mod test { assert_eq!(proof_is_valid, true); } + #[test] + // Tests hash to field using FFI APIs + fn test_seeded_keygen_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() }; + + // We generate a new identity pair from an input seed + let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let input_buffer = &Buffer::from(seed_bytes); + let mut output_buffer = MaybeUninit::::uninit(); + let success = seeded_key_gen(rln_pointer, input_buffer, output_buffer.as_mut_ptr()); + assert!(success, "seeded key gen call failed"); + let output_buffer = unsafe { output_buffer.assume_init() }; + let result_data = <&[u8]>::from(&output_buffer).to_vec(); + let (identity_secret, read) = bytes_le_to_fr(&result_data); + let (id_commitment, _) = bytes_le_to_fr(&result_data[read..].to_vec()); + + // We check against expected values + let expected_identity_secret_seed_bytes = str_to_fr( + "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", + 16, + ); + let expected_id_commitment_seed_bytes = str_to_fr( + "0xbf16d2b5c0d6f9d9d561e05bfca16a81b4b873bb063508fae360d8c74cef51f", + 16, + ); + + assert_eq!(identity_secret, expected_identity_secret_seed_bytes); + assert_eq!(id_commitment, expected_id_commitment_seed_bytes); + } + #[test] // Tests hash to field using FFI APIs fn test_hash_to_field_ffi() { diff --git a/rln/src/lib.rs b/rln/src/lib.rs index d942e41..bab26f2 100644 --- a/rln/src/lib.rs +++ b/rln/src/lib.rs @@ -429,4 +429,49 @@ mod test { let (deser, _) = deserialize_proof_values(&ser); assert_eq!(proof_values, deser); } + + #[test] + // Tests seeded keygen + // Note that hardcoded values are only valid for Bn254 + fn test_seeded_keygen() { + // Generate identity pair using a seed phrase + let seed_phrase: &str = "A seed phrase example"; + let (identity_secret, id_commitment) = seeded_keygen(seed_phrase.as_bytes()); + + // We check against expected values + let expected_identity_secret_seed_phrase = str_to_fr( + "0x20df38f3f00496f19fe7c6535492543b21798ed7cb91aebe4af8012db884eda3", + 16, + ); + let expected_id_commitment_seed_phrase = str_to_fr( + "0x1223a78a5d66043a7f9863e14507dc80720a5602b2a894923e5b5147d5a9c325", + 16, + ); + + assert_eq!(identity_secret, expected_identity_secret_seed_phrase); + assert_eq!(id_commitment, expected_id_commitment_seed_phrase); + + // Generate identity pair using an byte array + let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let (identity_secret, id_commitment) = seeded_keygen(seed_bytes); + + // We check against expected values + let expected_identity_secret_seed_bytes = str_to_fr( + "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", + 16, + ); + let expected_id_commitment_seed_bytes = str_to_fr( + "0xbf16d2b5c0d6f9d9d561e05bfca16a81b4b873bb063508fae360d8c74cef51f", + 16, + ); + + assert_eq!(identity_secret, expected_identity_secret_seed_bytes); + assert_eq!(id_commitment, expected_id_commitment_seed_bytes); + + // We check again if the identity pair generated with the same seed phrase corresponds to the previously generated one + let (identity_secret, id_commitment) = seeded_keygen(seed_phrase.as_bytes()); + + assert_eq!(identity_secret, expected_identity_secret_seed_phrase); + assert_eq!(id_commitment, expected_id_commitment_seed_phrase); + } } diff --git a/rln/src/protocol.rs b/rln/src/protocol.rs index 0a8ff49..ce2f3bb 100644 --- a/rln/src/protocol.rs +++ b/rln/src/protocol.rs @@ -10,7 +10,8 @@ use ark_relations::r1cs::SynthesisError; use ark_std::{rand::thread_rng, UniformRand}; use color_eyre::Result; use num_bigint::BigInt; -use rand::Rng; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha20Rng; #[cfg(not(target_arch = "wasm32"))] use std::sync::Mutex; #[cfg(debug_assertions)] @@ -338,6 +339,7 @@ pub fn compute_tree_root( // Generates a tupe (identity_secret, id_commitment) where // identity_secret is random and id_commitment = PoseidonHash(identity_secret) +// RNG is instantiated using thread_rng() pub fn keygen() -> (Fr, Fr) { let mut rng = thread_rng(); let identity_secret = Fr::rand(&mut rng); @@ -345,6 +347,23 @@ pub fn keygen() -> (Fr, Fr) { (identity_secret, id_commitment) } +// Generates a tupe (identity_secret, id_commitment) where +// identity_secret is random and id_commitment = PoseidonHash(identity_secret) +// RNG is instantiated using 20 rounds of ChaCha seeded with the hash of the input +pub fn seeded_keygen(signal: &[u8]) -> (Fr, Fr) { + // ChaCha20 requires a seed of exactly 32 bytes. + // We first hash the input seed signal to a 32 bytes array and pass this as seed to ChaCha20 + let mut seed = [0; 32]; + let mut hasher = Keccak::v256(); + hasher.update(signal); + hasher.finalize(&mut seed); + + let mut rng = ChaCha20Rng::from_seed(seed); + let identity_secret = Fr::rand(&mut rng); + let id_commitment = poseidon_hash(&[identity_secret]); + (identity_secret, id_commitment) +} + // Hashes arbitrary signal to the underlying prime field pub fn hash_to_field(signal: &[u8]) -> Fr { // We hash the input signal using Keccak256 diff --git a/rln/src/public.rs b/rln/src/public.rs index fcf5bac..6f684c5 100644 --- a/rln/src/public.rs +++ b/rln/src/public.rs @@ -343,6 +343,21 @@ impl RLN<'_> { Ok(()) } + pub fn seeded_key_gen( + &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 (id_key, id_commitment_key) = seeded_keygen(&serialized); + output_data.write_all(&fr_to_bytes_le(&id_key))?; + output_data.write_all(&fr_to_bytes_le(&id_commitment_key))?; + + Ok(()) + } + 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)?; @@ -824,6 +839,36 @@ mod test { assert!(verified); } + #[test] + fn test_seeded_keygen() { + let rln = RLN::default(); + + let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + + let mut input_buffer = Cursor::new(&seed_bytes); + let mut output_buffer = Cursor::new(Vec::::new()); + + rln.seeded_key_gen(&mut input_buffer, &mut output_buffer) + .unwrap(); + let serialized_output = output_buffer.into_inner(); + + let (identity_secret, read) = bytes_le_to_fr(&serialized_output); + let (id_commitment, _) = bytes_le_to_fr(&serialized_output[read..].to_vec()); + + // We check against expected values + let expected_identity_secret_seed_bytes = str_to_fr( + "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", + 16, + ); + let expected_id_commitment_seed_bytes = str_to_fr( + "0xbf16d2b5c0d6f9d9d561e05bfca16a81b4b873bb063508fae360d8c74cef51f", + 16, + ); + + assert_eq!(identity_secret, expected_identity_secret_seed_bytes); + assert_eq!(id_commitment, expected_id_commitment_seed_bytes); + } + #[test] fn test_hash_to_field() { let rln = RLN::default(); diff --git a/utils/Cargo.toml b/utils/Cargo.toml index dadd7bd..9d64bfe 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -15,4 +15,4 @@ tiny-keccak = { version = "2.0.2", features = ["keccak"] } [features] default = ["parallel"] -parallel = ["ark-ff/parallel"] \ No newline at end of file +parallel = ["ark-ff/parallel"]