From 32f3202e9dacde84c0f1552582732dbac5da9964 Mon Sep 17 00:00:00 2001 From: G <28568419+s1fr0@users.noreply.github.com> Date: Mon, 12 Dec 2022 00:48:22 +0100 Subject: [PATCH] feat(rln): add extended keygen APIs for Semaphore-compatible credentials (#85) * refactor(rln): update APIs based on updated rln circuit design * chore(rln): update rln vendor submodule * fix(ci): update ci to not ignore rln resources changes * feat(rln): add extended keygen APIs * refactor(rln): rename id_secret/id_key to identity_secret_hash as per RFC * fix(rln): cargo fmt --- rln/README.md | 4 +- rln/src/ffi.rs | 148 +++++++++++++++++++++------ rln/src/lib.rs | 33 +++--- rln/src/protocol.rs | 97 +++++++++++++++--- rln/src/public.rs | 238 +++++++++++++++++++++++++++++++++----------- 5 files changed, 408 insertions(+), 112 deletions(-) diff --git a/rln/README.md b/rln/README.md index dac9f16..8cbe59e 100644 --- a/rln/README.md +++ b/rln/README.md @@ -97,7 +97,7 @@ rln.key_gen(&mut buffer).unwrap(); // We deserialize the keygen output to obtain // the identiy_secret and id_commitment -let (identity_secret, id_commitment) = deserialize_identity_pair(buffer.into_inner()); +let (identity_secret_hash, id_commitment) = deserialize_identity_pair(buffer.into_inner()); ``` ### Add ID commitment to the RLN Merkle tree @@ -139,7 +139,7 @@ Input buffer is serialized as `[ identity_key | id_index | epoch | signal_len | ```rust // We prepare input to the proof generation routine -let proof_input = prepare_prove_input(identity_secret, id_index, epoch, signal); +let proof_input = prepare_prove_input(identity_secret_hash, id_index, epoch, signal); ``` We are now ready to generate a RLN ZK proof along with the _public outputs_ of the ZK circuit evaluation. diff --git a/rln/src/ffi.rs b/rln/src/ffi.rs index fc1e90e..cd55251 100644 --- a/rln/src/ffi.rs +++ b/rln/src/ffi.rs @@ -297,6 +297,44 @@ pub extern "C" fn seeded_key_gen( } } +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn extended_key_gen(ctx: *const RLN, output_buffer: *mut Buffer) -> bool { + let rln = unsafe { &*ctx }; + let mut output_data: Vec = Vec::new(); + if rln.extended_key_gen(&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 seeded_extended_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_extended_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 recover_id_secret( @@ -623,8 +661,8 @@ mod test { let rln_pointer = unsafe { &mut *rln_pointer.assume_init() }; // generate identity - let identity_secret = hash_to_field(b"test-merkle-proof"); - let id_commitment = poseidon_hash(&vec![identity_secret]); + let identity_secret_hash = hash_to_field(b"test-merkle-proof"); + let id_commitment = 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); @@ -924,7 +962,7 @@ mod test { assert!(success, "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 (identity_secret_hash, read) = bytes_le_to_fr(&result_data); let (id_commitment, _) = bytes_le_to_fr(&result_data[read..].to_vec()); // We set as leaf id_commitment, its index would be equal to no_of_leaves @@ -944,9 +982,9 @@ mod test { let epoch = hash_to_field(b"test-epoch"); // We prepare input for generate_rln_proof API - // input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] + // input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] let mut serialized: Vec = Vec::new(); - serialized.append(&mut fr_to_bytes_le(&identity_secret)); + serialized.append(&mut fr_to_bytes_le(&identity_secret_hash)); serialized.append(&mut identity_index.to_le_bytes().to_vec()); serialized.append(&mut fr_to_bytes_le(&epoch)); serialized.append(&mut signal_len.to_le_bytes().to_vec()); @@ -1009,7 +1047,7 @@ mod test { assert!(success, "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 (identity_secret_hash, read) = bytes_le_to_fr(&result_data); let (id_commitment, _) = bytes_le_to_fr(&result_data[read..].to_vec()); // We set as leaf id_commitment, its index would be equal to no_of_leaves @@ -1029,9 +1067,9 @@ mod test { let epoch = hash_to_field(b"test-epoch"); // We prepare input for generate_rln_proof API - // input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] + // input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] let mut serialized: Vec = Vec::new(); - serialized.append(&mut fr_to_bytes_le(&identity_secret)); + serialized.append(&mut fr_to_bytes_le(&identity_secret_hash)); serialized.append(&mut identity_index.to_le_bytes().to_vec()); serialized.append(&mut fr_to_bytes_le(&epoch)); serialized.append(&mut signal_len.to_le_bytes().to_vec()); @@ -1122,7 +1160,7 @@ mod test { assert!(success, "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 (identity_secret_hash, read) = bytes_le_to_fr(&result_data); let (id_commitment, _) = bytes_le_to_fr(&result_data[read..].to_vec()); // We set as leaf id_commitment, its index would be equal to 0 since tree is empty @@ -1148,9 +1186,9 @@ mod test { let epoch = hash_to_field(b"test-epoch"); // We prepare input for generate_rln_proof API - // input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] + // input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] let mut serialized1: Vec = Vec::new(); - serialized1.append(&mut fr_to_bytes_le(&identity_secret)); + serialized1.append(&mut fr_to_bytes_le(&identity_secret_hash)); serialized1.append(&mut identity_index.to_le_bytes().to_vec()); serialized1.append(&mut fr_to_bytes_le(&epoch)); @@ -1194,17 +1232,17 @@ mod test { ); assert!(success, "recover id secret call failed"); let output_buffer = unsafe { output_buffer.assume_init() }; - let serialized_id_secret = <&[u8]>::from(&output_buffer).to_vec(); + let serialized_identity_secret_hash = <&[u8]>::from(&output_buffer).to_vec(); // We passed two shares for the same secret, so recovery should be successful - // To check it, we ensure that recovered_id_secret is non-empty - assert!(!serialized_id_secret.is_empty()); + // To check it, we ensure that recovered identity secret hash is empty + assert!(!serialized_identity_secret_hash.is_empty()); - // We check if the recovered id secret corresponds to the original one - let (recovered_id_secret, _) = bytes_le_to_fr(&serialized_id_secret); - assert_eq!(recovered_id_secret, identity_secret); + // We check if the recovered identity secret hash corresponds to the original one + let (recovered_identity_secret_hash, _) = bytes_le_to_fr(&serialized_identity_secret_hash); + assert_eq!(recovered_identity_secret_hash, identity_secret_hash); - // We now test that computing_id_secret is unsuccessful if shares computed from two different id secrets but within same epoch are passed + // We now test that computing identity_secret_hash is unsuccessful if shares computed from two different identity secret hashes but within same epoch are passed // We generate a new identity pair let mut output_buffer = MaybeUninit::::uninit(); @@ -1212,7 +1250,7 @@ mod test { assert!(success, "key gen call failed"); let output_buffer = unsafe { output_buffer.assume_init() }; let result_data = <&[u8]>::from(&output_buffer).to_vec(); - let (identity_secret_new, read) = bytes_le_to_fr(&result_data); + let (identity_secret_hash_new, read) = bytes_le_to_fr(&result_data); let (id_commitment_new, _) = bytes_le_to_fr(&result_data[read..].to_vec()); // We set as leaf id_commitment, its index would be equal to 1 since at 0 there is id_commitment @@ -1228,10 +1266,10 @@ mod test { let signal3_len = u64::try_from(signal3.len()).unwrap(); // We prepare input for generate_rln_proof API - // input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] + // input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] // Note that epoch is the same as before let mut serialized: Vec = Vec::new(); - serialized.append(&mut fr_to_bytes_le(&identity_secret_new)); + serialized.append(&mut fr_to_bytes_le(&identity_secret_hash_new)); serialized.append(&mut identity_index_new.to_le_bytes().to_vec()); serialized.append(&mut fr_to_bytes_le(&epoch)); serialized.append(&mut signal3_len.to_le_bytes().to_vec()); @@ -1246,7 +1284,7 @@ mod test { // result_data is [ proof<128> | share_y<32> | nullifier<32> | root<32> | epoch<32> | share_x<32> | rln_identifier<32> ] let proof_data_3 = <&[u8]>::from(&output_buffer).to_vec(); - // We attempt to recover the secret using share1 (coming from identity_secret) and share3 (coming from identity_secret_new) + // We attempt to recover the secret using share1 (coming from identity_secret_hash) and share3 (coming from identity_secret_hash_new) let input_proof_buffer_1 = &Buffer::from(proof_data_1.as_ref()); let input_proof_buffer_3 = &Buffer::from(proof_data_3.as_ref()); @@ -1259,11 +1297,11 @@ mod test { ); assert!(success, "recover id secret call failed"); let output_buffer = unsafe { output_buffer.assume_init() }; - let serialized_id_secret = <&[u8]>::from(&output_buffer).to_vec(); + let serialized_identity_secret_hash = <&[u8]>::from(&output_buffer).to_vec(); // We passed two shares for different secrets, so recovery should be not successful - // To check it, we ensure that recovered_id_secret is empty - assert!(serialized_id_secret.is_empty()); + // To check it, we ensure that recovered identity secret hash is empty + assert!(serialized_identity_secret_hash.is_empty()); } #[test] @@ -1286,11 +1324,11 @@ mod test { 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 (identity_secret_hash, 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( + let expected_identity_secret_hash_seed_bytes = str_to_fr( "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", 16, ); @@ -1299,7 +1337,61 @@ mod test { 16, ); - assert_eq!(identity_secret, expected_identity_secret_seed_bytes); + assert_eq!( + identity_secret_hash, + expected_identity_secret_hash_seed_bytes + ); + assert_eq!(id_commitment, expected_id_commitment_seed_bytes); + } + + #[test] + // Tests hash to field using FFI APIs + fn test_seeded_extended_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 tuple 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_extended_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_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) = + deserialize_identity_tuple(result_data); + + // We check against expected values + let expected_identity_trapdoor_seed_bytes = str_to_fr( + "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", + 16, + ); + let expected_identity_nullifier_seed_bytes = str_to_fr( + "0x1f18714c7bc83b5bca9e89d404cf6f2f585bc4c0f7ed8b53742b7e2b298f50b4", + 16, + ); + let expected_identity_secret_hash_seed_bytes = str_to_fr( + "0x2aca62aaa7abaf3686fff2caf00f55ab9462dc12db5b5d4bcf3994e671f8e521", + 16, + ); + let expected_id_commitment_seed_bytes = str_to_fr( + "0x68b66aa0a8320d2e56842581553285393188714c48f9b17acd198b4f1734c5c", + 16, + ); + + assert_eq!(identity_trapdoor, expected_identity_trapdoor_seed_bytes); + assert_eq!(identity_nullifier, expected_identity_nullifier_seed_bytes); + assert_eq!( + identity_secret_hash, + expected_identity_secret_hash_seed_bytes + ); assert_eq!(id_commitment, expected_id_commitment_seed_bytes); } diff --git a/rln/src/lib.rs b/rln/src/lib.rs index bab26f2..22cdb90 100644 --- a/rln/src/lib.rs +++ b/rln/src/lib.rs @@ -179,8 +179,8 @@ mod test { let leaf_index = 3; // generate identity - let identity_secret = hash_to_field(b"test-merkle-proof"); - let id_commitment = poseidon_hash(&vec![identity_secret]); + let identity_secret_hash = hash_to_field(b"test-merkle-proof"); + let id_commitment = poseidon_hash(&vec![identity_secret_hash]); // generate merkle tree let default_leaf = Fr::from(0); @@ -365,7 +365,7 @@ mod test { let leaf_index = 3; // Generate identity pair - let (identity_secret, id_commitment) = keygen(); + let (identity_secret_hash, id_commitment) = keygen(); //// generate merkle tree let default_leaf = Fr::from(0); @@ -382,7 +382,7 @@ mod test { //let rln_identifier = hash_to_field(b"test-rln-identifier"); let rln_witness: RLNWitnessInput = rln_witness_from_values( - identity_secret, + identity_secret_hash, &merkle_proof, x, epoch, /*, rln_identifier*/ @@ -436,10 +436,10 @@ mod test { 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()); + let (identity_secret_hash, id_commitment) = seeded_keygen(seed_phrase.as_bytes()); // We check against expected values - let expected_identity_secret_seed_phrase = str_to_fr( + let expected_identity_secret_hash_seed_phrase = str_to_fr( "0x20df38f3f00496f19fe7c6535492543b21798ed7cb91aebe4af8012db884eda3", 16, ); @@ -448,15 +448,18 @@ mod test { 16, ); - assert_eq!(identity_secret, expected_identity_secret_seed_phrase); + assert_eq!( + identity_secret_hash, + expected_identity_secret_hash_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); + let (identity_secret_hash, id_commitment) = seeded_keygen(seed_bytes); // We check against expected values - let expected_identity_secret_seed_bytes = str_to_fr( + let expected_identity_secret_hash_seed_bytes = str_to_fr( "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", 16, ); @@ -465,13 +468,19 @@ mod test { 16, ); - assert_eq!(identity_secret, expected_identity_secret_seed_bytes); + assert_eq!( + identity_secret_hash, + expected_identity_secret_hash_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()); + let (identity_secret_hash, id_commitment) = seeded_keygen(seed_phrase.as_bytes()); - assert_eq!(identity_secret, expected_identity_secret_seed_phrase); + assert_eq!( + identity_secret_hash, + expected_identity_secret_hash_seed_phrase + ); assert_eq!(id_commitment, expected_id_commitment_seed_phrase); } } diff --git a/rln/src/protocol.rs b/rln/src/protocol.rs index 9c6c1e1..ed461c8 100644 --- a/rln/src/protocol.rs +++ b/rln/src/protocol.rs @@ -63,10 +63,32 @@ pub fn deserialize_field_element(serialized: Vec) -> Fr { } pub fn deserialize_identity_pair(serialized: Vec) -> (Fr, Fr) { - let (identity_secret, read) = bytes_le_to_fr(&serialized); + let (identity_secret_hash, read) = bytes_le_to_fr(&serialized); let (id_commitment, _) = bytes_le_to_fr(&serialized[read..].to_vec()); - return (identity_secret, id_commitment); + return (identity_secret_hash, id_commitment); +} + +pub fn deserialize_identity_tuple(serialized: Vec) -> (Fr, Fr, Fr, Fr) { + let mut all_read = 0; + + let (identity_trapdoor, read) = bytes_le_to_fr(&serialized[all_read..].to_vec()); + all_read += read; + + let (identity_nullifier, read) = bytes_le_to_fr(&serialized[all_read..].to_vec()); + all_read += read; + + let (identity_secret_hash, read) = bytes_le_to_fr(&serialized[all_read..].to_vec()); + all_read += read; + + let (identity_commitment, _) = bytes_le_to_fr(&serialized[all_read..].to_vec()); + + return ( + identity_trapdoor, + identity_nullifier, + identity_secret_hash, + identity_commitment, + ); } pub fn serialize_witness(rln_witness: &RLNWitnessInput) -> Vec { @@ -121,7 +143,7 @@ pub fn deserialize_witness(serialized: &[u8]) -> (RLNWitnessInput, usize) { // This function deserializes input for kilic's rln generate_proof public API // https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L148 -// input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] +// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] // return value is a rln witness populated according to this information pub fn proof_inputs_to_rln_witness( tree: &mut PoseidonTree, @@ -387,18 +409,38 @@ pub fn compute_tree_root( // Protocol utility functions /////////////////////////////////////////////////////// -// Generates a tupe (identity_secret, id_commitment) where -// identity_secret is random and id_commitment = PoseidonHash(identity_secret) +// Generates a tuple (identity_secret_hash, id_commitment) where +// identity_secret_hash is random and id_commitment = PoseidonHash(identity_secret_hash) // RNG is instantiated using thread_rng() pub fn keygen() -> (Fr, Fr) { let mut rng = thread_rng(); - let identity_secret = Fr::rand(&mut rng); - let id_commitment = poseidon_hash(&[identity_secret]); - (identity_secret, id_commitment) + let identity_secret_hash = Fr::rand(&mut rng); + let id_commitment = poseidon_hash(&[identity_secret_hash]); + (identity_secret_hash, id_commitment) } -// Generates a tupe (identity_secret, id_commitment) where -// identity_secret is random and id_commitment = PoseidonHash(identity_secret) +// Generates a tuple (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) where +// identity_trapdoor and identity_nullifier are random, +// identity_secret_hash = PoseidonHash(identity_trapdoor, identity_nullifier), +// id_commitment = PoseidonHash(identity_secret_hash), +// RNG is instantiated using thread_rng() +// Generated credentials are compatible with Semaphore credentials +pub fn extended_keygen() -> (Fr, Fr, Fr, Fr) { + let mut rng = thread_rng(); + let identity_trapdoor = Fr::rand(&mut rng); + let identity_nullifier = Fr::rand(&mut rng); + let identity_secret_hash = poseidon_hash(&[identity_trapdoor, identity_nullifier]); + let id_commitment = poseidon_hash(&[identity_secret_hash]); + ( + identity_trapdoor, + identity_nullifier, + identity_secret_hash, + id_commitment, + ) +} + +// Generates a tuple (identity_secret_hash, id_commitment) where +// identity_secret_hash is random and id_commitment = PoseidonHash(identity_secret_hash) // 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. @@ -409,9 +451,36 @@ pub fn seeded_keygen(signal: &[u8]) -> (Fr, Fr) { 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) + let identity_secret_hash = Fr::rand(&mut rng); + let id_commitment = poseidon_hash(&[identity_secret_hash]); + (identity_secret_hash, id_commitment) +} + +// Generates a tuple (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) where +// identity_trapdoor and identity_nullifier are random, +// identity_secret_hash = PoseidonHash(identity_trapdoor, identity_nullifier), +// id_commitment = PoseidonHash(identity_secret_hash), +// RNG is instantiated using 20 rounds of ChaCha seeded with the hash of the input +// Generated credentials are compatible with Semaphore credentials +pub fn extended_seeded_keygen(signal: &[u8]) -> (Fr, Fr, 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_trapdoor = Fr::rand(&mut rng); + let identity_nullifier = Fr::rand(&mut rng); + let identity_secret_hash = poseidon_hash(&[identity_trapdoor, identity_nullifier]); + let id_commitment = poseidon_hash(&[identity_secret_hash]); + ( + identity_trapdoor, + identity_nullifier, + identity_secret_hash, + id_commitment, + ) } // Hashes arbitrary signal to the underlying prime field @@ -452,7 +521,7 @@ pub fn compute_id_secret( // We successfully recovered the identity secret return Ok(a_0); } else { - return Err("Cannot recover id_secret from provided shares".into()); + return Err("Cannot recover identity_secret_hash from provided shares".into()); } } diff --git a/rln/src/public.rs b/rln/src/public.rs index 99ac8a3..fbfbc19 100644 --- a/rln/src/public.rs +++ b/rln/src/public.rs @@ -177,8 +177,8 @@ impl RLN<'_> { /// ``` /// use crate::protocol::*; /// - /// // We generate a random id secret and commitment pair - /// let (identity_secret, id_commitment) = keygen(); + /// // We generate a random identity secret hash and commitment pair + /// let (identity_secret_hash, id_commitment) = keygen(); /// /// // We define the tree index where id_commitment will be added /// let id_index = 10; @@ -489,7 +489,7 @@ impl RLN<'_> { /// Computes a zkSNARK RLN proof from the identity secret, the Merkle tree index, the epoch and signal. /// /// Input values are: - /// - `input_data`: a reader for the serialization of `[ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ]` + /// - `input_data`: a reader for the serialization of `[ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ]` /// /// Output values are: /// - `output_data`: a writer receiving the serialization of the zkSNARK proof and the circuit evaluations outputs, i.e. `[ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ]` @@ -500,7 +500,7 @@ impl RLN<'_> { /// use rln::utils::*; /// /// // Generate identity pair - /// let (identity_secret, id_commitment) = keygen(); + /// let (identity_secret_hash, id_commitment) = keygen(); /// /// // We set as leaf id_commitment after storing its index /// let identity_index = 10; @@ -516,9 +516,9 @@ impl RLN<'_> { /// let epoch = hash_to_field(b"test-epoch"); /// /// // We prepare input for generate_rln_proof API - /// // input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] + /// // input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] /// let mut serialized: Vec = Vec::new(); - /// serialized.append(&mut fr_to_bytes_le(&identity_secret)); + /// serialized.append(&mut fr_to_bytes_le(&identity_secret_hash)); /// serialized.append(&mut identity_index.to_le_bytes().to_vec()); /// serialized.append(&mut fr_to_bytes_le(&epoch)); /// serialized.append(&mut signal_len.to_le_bytes().to_vec()); @@ -782,12 +782,45 @@ impl RLN<'_> { /// rln.key_gen(&mut buffer).unwrap(); /// /// // We deserialize the keygen output - /// let (identity_secret, id_commitment) = deserialize_identity_pair(buffer.into_inner()); + /// let (identity_secret_hash, id_commitment) = deserialize_identity_pair(buffer.into_inner()); /// ``` pub fn key_gen(&self, mut output_data: W) -> io::Result<()> { - let (id_key, id_commitment_key) = keygen(); - output_data.write_all(&fr_to_bytes_le(&id_key))?; - output_data.write_all(&fr_to_bytes_le(&id_commitment_key))?; + let (identity_secret_hash, id_commitment) = keygen(); + output_data.write_all(&fr_to_bytes_le(&identity_secret_hash))?; + output_data.write_all(&fr_to_bytes_le(&id_commitment))?; + + Ok(()) + } + + /// Returns an identity trapdoor, nullifier, secret and commitment tuple. + /// + /// The identity secret is the Poseidon hash of the identity trapdoor and identity nullifier. + /// + /// The identity commitment is the Poseidon hash of the identity secret. + /// + /// Generated credentials are compatible with [Semaphore](https://semaphore.appliedzkp.org/docs/guides/identities)'s credentials. + /// + /// Output values are: + /// - `output_data`: a writer receiving the serialization of the identity tapdoor, identity nullifier, identity secret and identity commitment (serialization done with `rln::utils::fr_to_bytes_le`) + /// + /// Example + /// ``` + /// use rln::protocol::*; + /// + /// // We generate an identity tuple + /// let mut buffer = Cursor::new(Vec::::new()); + /// rln.extended_key_gen(&mut buffer).unwrap(); + /// + /// // We deserialize the keygen output + /// let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) = deserialize_identity_tuple(buffer.into_inner()); + /// ``` + pub fn extended_key_gen(&self, mut output_data: W) -> io::Result<()> { + let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) = + extended_keygen(); + output_data.write_all(&fr_to_bytes_le(&identity_trapdoor))?; + output_data.write_all(&fr_to_bytes_le(&identity_nullifier))?; + output_data.write_all(&fr_to_bytes_le(&identity_secret_hash))?; + output_data.write_all(&fr_to_bytes_le(&id_commitment))?; Ok(()) } @@ -814,7 +847,7 @@ impl RLN<'_> { /// .unwrap(); /// /// // We deserialize the keygen output - /// let (identity_secret, id_commitment) = deserialize_identity_pair(output_buffer.into_inner()); + /// let (identity_secret_hash, id_commitment) = deserialize_identity_pair(output_buffer.into_inner()); /// ``` pub fn seeded_key_gen( &self, @@ -824,9 +857,55 @@ impl RLN<'_> { 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))?; + let (identity_secret_hash, id_commitment) = seeded_keygen(&serialized); + output_data.write_all(&fr_to_bytes_le(&identity_secret_hash))?; + output_data.write_all(&fr_to_bytes_le(&id_commitment))?; + + Ok(()) + } + + /// Returns an identity trapdoor, nullifier, secret and commitment tuple generated using a seed. + /// + /// The identity secret is the Poseidon hash of the identity trapdoor and identity nullifier. + /// + /// The identity commitment is the Poseidon hash of the identity secret. + /// + /// Generated credentials are compatible with [Semaphore](https://semaphore.appliedzkp.org/docs/guides/identities)'s credentials. + /// + /// Input values are: + /// - `input_data`: a reader for the byte vector containing the seed + /// + /// Output values are: + /// - `output_data`: a writer receiving the serialization of the identity tapdoor, identity nullifier, identity secret and identity commitment (serialization done with `rln::utils::fr_to_bytes_le`) + /// + /// Example + /// ``` + /// use rln::protocol::*; + /// + /// 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(); + /// + /// // We deserialize the keygen output + /// let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) = deserialize_identity_tuple(buffer.into_inner()); + /// ``` + pub fn seeded_extended_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 (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) = + extended_seeded_keygen(&serialized); + output_data.write_all(&fr_to_bytes_le(&identity_trapdoor))?; + output_data.write_all(&fr_to_bytes_le(&identity_nullifier))?; + output_data.write_all(&fr_to_bytes_le(&identity_secret_hash))?; + output_data.write_all(&fr_to_bytes_le(&id_commitment))?; Ok(()) } @@ -838,11 +917,11 @@ impl RLN<'_> { /// - `input_proof_data_2`: same as `input_proof_data_1` /// /// Output values are: - /// - `output_data`: a writer receiving the serialization of the recovered identity secret field element if correctly recovered (serialization done with [`rln::utils::fr_to_bytes_le`](crate::utils::fr_to_bytes_le)), a writer receiving an empty byte vector if not. + /// - `output_data`: a writer receiving the serialization of the recovered identity secret hash field element if correctly recovered (serialization done with [`rln::utils::fr_to_bytes_le`](crate::utils::fr_to_bytes_le)), a writer receiving an empty byte vector if not. /// /// Example /// ``` - /// // identity_secret, proof_data_1 and proof_data_2 are computed as in the example code snippet provided for rln::public::RLN::generate_rln_proof using same identity secret and epoch (but not necessarily same signal) + /// // identity_secret_hash, proof_data_1 and proof_data_2 are computed as in the example code snippet provided for rln::public::RLN::generate_rln_proof using same identity secret and epoch (but not necessarily same signal) /// /// let mut input_proof_data_1 = Cursor::new(proof_data_1); /// let mut input_proof_data_2 = Cursor::new(proof_data_2); @@ -854,14 +933,14 @@ impl RLN<'_> { /// ) /// .unwrap(); /// - /// let serialized_id_secret = output_buffer.into_inner(); + /// let serialized_identity_secret_hash = output_buffer.into_inner(); /// /// // We ensure that a non-empty value is written to output_buffer - /// assert!(!serialized_id_secret.is_empty()); + /// assert!(!serialized_identity_secret_hash.is_empty()); /// - /// // We check if the recovered id secret corresponds to the original one - /// let (recovered_id_secret, _) = bytes_le_to_fr(&serialized_id_secret); - /// assert_eq!(recovered_id_secret, identity_secret); + /// // We check if the recovered identity secret hash corresponds to the original one + /// let (recovered_identity_secret_hash, _) = bytes_le_to_fr(&serialized_identity_secret_hash); + /// assert_eq!(recovered_identity_secret_hash, identity_secret_hash); /// ``` pub fn recover_id_secret( &self, @@ -894,12 +973,13 @@ impl RLN<'_> { let share2 = (proof_values_2.x, proof_values_2.y); // We recover the secret - let recovered_id_secret = compute_id_secret(share1, share2, external_nullifier_1); + let recovered_identity_secret_hash = + compute_id_secret(share1, share2, external_nullifier_1); - // If an id secret is recovered, we write it to output_data, otherwise nothing will be written. - if recovered_id_secret.is_ok() { - let id_secret = recovered_id_secret.unwrap(); - output_data.write_all(&fr_to_bytes_le(&id_secret))?; + // If an identity secret hash is recovered, we write it to output_data, otherwise nothing will be written. + if recovered_identity_secret_hash.is_ok() { + let identity_secret_hash = recovered_identity_secret_hash.unwrap(); + output_data.write_all(&fr_to_bytes_le(&identity_secret_hash))?; } } @@ -941,7 +1021,7 @@ impl RLN<'_> { /// 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_data`: a reader for the serialization of `[ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ]` + /// - `input_data`: a reader for the serialization of `[ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ]` /// /// The function returns the corresponding [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object serialized using [`rln::protocol::serialize_witness`](crate::protocol::serialize_witness)). pub fn get_serialized_rln_witness(&mut self, mut input_data: R) -> Vec { @@ -1197,8 +1277,8 @@ mod test { let mut rln = RLN::new(tree_height, input_buffer); // generate identity - let identity_secret = hash_to_field(b"test-merkle-proof"); - let id_commitment = poseidon_hash(&vec![identity_secret]); + let identity_secret_hash = hash_to_field(b"test-merkle-proof"); + let id_commitment = 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)); @@ -1412,7 +1492,7 @@ mod test { rln.init_tree_with_leaves(&mut buffer).unwrap(); // Generate identity pair - let (identity_secret, id_commitment) = keygen(); + let (identity_secret_hash, id_commitment) = keygen(); // We set as leaf id_commitment after storing its index let identity_index = u64::try_from(rln.tree.leaves_set()).unwrap(); @@ -1428,9 +1508,9 @@ mod test { let epoch = hash_to_field(b"test-epoch"); // We prepare input for generate_rln_proof API - // input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] + // input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] let mut serialized: Vec = Vec::new(); - serialized.append(&mut fr_to_bytes_le(&identity_secret)); + serialized.append(&mut fr_to_bytes_le(&identity_secret_hash)); serialized.append(&mut identity_index.to_le_bytes().to_vec()); serialized.append(&mut fr_to_bytes_le(&epoch)); serialized.append(&mut signal_len.to_le_bytes().to_vec()); @@ -1477,7 +1557,7 @@ mod test { rln.init_tree_with_leaves(&mut buffer).unwrap(); // Generate identity pair - let (identity_secret, id_commitment) = keygen(); + let (identity_secret_hash, id_commitment) = keygen(); // We set as leaf id_commitment after storing its index let identity_index = u64::try_from(rln.tree.leaves_set()).unwrap(); @@ -1493,9 +1573,9 @@ mod test { let epoch = hash_to_field(b"test-epoch"); // We prepare input for generate_rln_proof API - // input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] + // input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] let mut serialized: Vec = Vec::new(); - serialized.append(&mut fr_to_bytes_le(&identity_secret)); + serialized.append(&mut fr_to_bytes_le(&identity_secret_hash)); serialized.append(&mut identity_index.to_le_bytes().to_vec()); serialized.append(&mut fr_to_bytes_le(&epoch)); serialized.append(&mut signal_len.to_le_bytes().to_vec()); @@ -1564,11 +1644,11 @@ mod test { .unwrap(); let serialized_output = output_buffer.into_inner(); - let (identity_secret, read) = bytes_le_to_fr(&serialized_output); + let (identity_secret_hash, 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( + let expected_identity_secret_hash_seed_bytes = str_to_fr( "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", 16, ); @@ -1577,7 +1657,53 @@ mod test { 16, ); - assert_eq!(identity_secret, expected_identity_secret_seed_bytes); + assert_eq!( + identity_secret_hash, + expected_identity_secret_hash_seed_bytes + ); + assert_eq!(id_commitment, expected_id_commitment_seed_bytes); + } + + #[test] + fn test_seeded_extended_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_extended_key_gen(&mut input_buffer, &mut output_buffer) + .unwrap(); + let serialized_output = output_buffer.into_inner(); + + let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) = + deserialize_identity_tuple(serialized_output); + + // We check against expected values + let expected_identity_trapdoor_seed_bytes = str_to_fr( + "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", + 16, + ); + let expected_identity_nullifier_seed_bytes = str_to_fr( + "0x1f18714c7bc83b5bca9e89d404cf6f2f585bc4c0f7ed8b53742b7e2b298f50b4", + 16, + ); + let expected_identity_secret_hash_seed_bytes = str_to_fr( + "0x2aca62aaa7abaf3686fff2caf00f55ab9462dc12db5b5d4bcf3994e671f8e521", + 16, + ); + let expected_id_commitment_seed_bytes = str_to_fr( + "0x68b66aa0a8320d2e56842581553285393188714c48f9b17acd198b4f1734c5c", + 16, + ); + + assert_eq!(identity_trapdoor, expected_identity_trapdoor_seed_bytes); + assert_eq!(identity_nullifier, expected_identity_nullifier_seed_bytes); + assert_eq!( + identity_secret_hash, + expected_identity_secret_hash_seed_bytes + ); assert_eq!(id_commitment, expected_id_commitment_seed_bytes); } @@ -1622,7 +1748,7 @@ mod test { rln.init_tree_with_leaves(&mut buffer).unwrap(); // Generate identity pair - let (identity_secret, id_commitment) = keygen(); + let (identity_secret_hash, id_commitment) = keygen(); // We set as leaf id_commitment after storing its index let identity_index = u64::try_from(rln.tree.leaves_set()).unwrap(); @@ -1638,9 +1764,9 @@ mod test { let epoch = hash_to_field(b"test-epoch"); // We prepare input for generate_rln_proof API - // input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] + // input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] let mut serialized: Vec = Vec::new(); - serialized.append(&mut fr_to_bytes_le(&identity_secret)); + serialized.append(&mut fr_to_bytes_le(&identity_secret_hash)); serialized.append(&mut identity_index.to_le_bytes().to_vec()); serialized.append(&mut fr_to_bytes_le(&epoch)); serialized.append(&mut signal_len.to_le_bytes().to_vec()); @@ -1705,7 +1831,7 @@ mod test { let mut rln = RLN::new(tree_height, input_buffer); // Generate identity pair - let (identity_secret, id_commitment) = keygen(); + let (identity_secret_hash, id_commitment) = keygen(); // We set as leaf id_commitment after storing its index let identity_index = u64::try_from(rln.tree.leaves_set()).unwrap(); @@ -1726,9 +1852,9 @@ mod test { // We generate two proofs using same epoch but different signals. // We prepare input for generate_rln_proof API - // input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] + // input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] let mut serialized1: Vec = Vec::new(); - serialized1.append(&mut fr_to_bytes_le(&identity_secret)); + serialized1.append(&mut fr_to_bytes_le(&identity_secret_hash)); serialized1.append(&mut identity_index.to_le_bytes().to_vec()); serialized1.append(&mut fr_to_bytes_le(&epoch)); @@ -1767,19 +1893,19 @@ mod test { ) .unwrap(); - let serialized_id_secret = output_buffer.into_inner(); + let serialized_identity_secret_hash = output_buffer.into_inner(); // We ensure that a non-empty value is written to output_buffer - assert!(!serialized_id_secret.is_empty()); + assert!(!serialized_identity_secret_hash.is_empty()); - // We check if the recovered id secret corresponds to the original one - let (recovered_id_secret, _) = bytes_le_to_fr(&serialized_id_secret); - assert_eq!(recovered_id_secret, identity_secret); + // We check if the recovered identity secret hash corresponds to the original one + let (recovered_identity_secret_hash, _) = bytes_le_to_fr(&serialized_identity_secret_hash); + assert_eq!(recovered_identity_secret_hash, identity_secret_hash); - // We now test that computing_id_secret is unsuccessful if shares computed from two different id secrets but within same epoch are passed + // We now test that computing identity_secret_hash is unsuccessful if shares computed from two different identity secret hashes but within same epoch are passed // We generate a new identity pair - let (identity_secret_new, id_commitment_new) = keygen(); + let (identity_secret_hash_new, id_commitment_new) = keygen(); // We add it to the tree let identity_index_new = u64::try_from(rln.tree.leaves_set()).unwrap(); @@ -1791,9 +1917,9 @@ mod test { let signal3_len = u64::try_from(signal3.len()).unwrap(); // We prepare proof input. Note that epoch is the same as before - // input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] + // input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] let mut serialized3: Vec = Vec::new(); - serialized3.append(&mut fr_to_bytes_le(&identity_secret_new)); + serialized3.append(&mut fr_to_bytes_le(&identity_secret_hash_new)); serialized3.append(&mut identity_index_new.to_le_bytes().to_vec()); serialized3.append(&mut fr_to_bytes_le(&epoch)); serialized3.append(&mut signal3_len.to_le_bytes().to_vec()); @@ -1806,7 +1932,7 @@ mod test { .unwrap(); let proof_data_3 = output_buffer.into_inner(); - // We attempt to recover the secret using share1 (coming from identity_secret) and share3 (coming from identity_secret_new) + // We attempt to recover the secret using share1 (coming from identity_secret_hash) and share3 (coming from identity_secret_hash_new) let mut input_proof_data_1 = Cursor::new(proof_data_1.clone()); let mut input_proof_data_3 = Cursor::new(proof_data_3); @@ -1818,9 +1944,9 @@ mod test { ) .unwrap(); - let serialized_id_secret = output_buffer.into_inner(); + let serialized_identity_secret_hash = output_buffer.into_inner(); // We ensure that an empty value was written to output_buffer, i.e. no secret is recovered - assert!(serialized_id_secret.is_empty()); + assert!(serialized_identity_secret_hash.is_empty()); } }