diff --git a/Cargo.lock b/Cargo.lock index d163205..56f1e18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1612,6 +1612,7 @@ dependencies = [ "serde_json", "thiserror", "tiny-keccak", + "zeroize", "zerokit_utils", ] diff --git a/rln-cli/src/examples/relay.rs b/rln-cli/src/examples/relay.rs index 2977909..46a0923 100644 --- a/rln-cli/src/examples/relay.rs +++ b/rln-cli/src/examples/relay.rs @@ -7,12 +7,13 @@ use std::{ use clap::{Parser, Subcommand}; use color_eyre::{eyre::eyre, Report, Result}; +use rln::utils::IdSecret; use rln::{ circuit::Fr, hashers::{hash_to_field, poseidon_hash}, protocol::{keygen, prepare_prove_input, prepare_verify_input}, public::RLN, - utils::{bytes_le_to_fr, fr_to_bytes_le, generate_input_buffer}, + utils::{fr_to_bytes_le, generate_input_buffer}, }; const MESSAGE_LIMIT: u32 = 1; @@ -44,7 +45,7 @@ enum Commands { #[derive(Debug, Clone)] struct Identity { - identity_secret_hash: Fr, + identity_secret_hash: IdSecret, id_commitment: Fr, } @@ -103,7 +104,7 @@ impl RLNSystem { println!("Registered users:"); for (index, identity) in &self.local_identities { println!("User Index: {index}"); - println!("+ Identity Secret Hash: {}", identity.identity_secret_hash); + println!("+ Identity Secret Hash: {}", *identity.identity_secret_hash); println!("+ Identity Commitment: {}", identity.id_commitment); println!(); } @@ -118,7 +119,7 @@ impl RLNSystem { match self.rln.set_next_leaf(&mut buffer) { Ok(_) => { println!("Registered User Index: {index}"); - println!("+ Identity secret hash: {}", identity.identity_secret_hash); + println!("+ Identity secret hash: {}", *identity.identity_secret_hash); println!("+ Identity commitment: {},", identity.id_commitment); self.local_identities.insert(index, identity); } @@ -143,7 +144,7 @@ impl RLNSystem { }; let serialized = prepare_prove_input( - identity.identity_secret_hash, + identity.identity_secret_hash.clone(), user_index, Fr::from(MESSAGE_LIMIT), Fr::from(message_id), @@ -211,7 +212,7 @@ impl RLNSystem { { Ok(_) => { let output_data = output.into_inner(); - let (leaked_identity_secret_hash, _) = bytes_le_to_fr(&output_data); + let (leaked_identity_secret_hash, _) = IdSecret::from_bytes_le(&output_data); if let Some((user_index, identity)) = self .local_identities @@ -221,20 +222,21 @@ impl RLNSystem { }) .map(|(index, identity)| (*index, identity)) { - let real_identity_secret_hash = identity.identity_secret_hash; + let real_identity_secret_hash = identity.identity_secret_hash.clone(); if leaked_identity_secret_hash != real_identity_secret_hash { - Err(eyre!("identity secret hash mismatch {leaked_identity_secret_hash} != {real_identity_secret_hash}")) + Err(eyre!("identity secret hash mismatch: leaked_identity_secret_hash != real_identity_secret_hash")) } else { - println!("DUPLICATE message ID detected! Reveal identity secret hash: {leaked_identity_secret_hash}"); + println!( + "DUPLICATE message ID detected! Reveal identity secret hash: {}", + *leaked_identity_secret_hash + ); self.local_identities.remove(&user_index); self.rln.delete_leaf(user_index)?; println!("User index {user_index} has been SLASHED"); Ok(()) } } else { - Err(eyre!( - "user identity secret hash {leaked_identity_secret_hash} not found" - )) + Err(eyre!("user identity secret hash ******** not found")) } } Err(err) => Err(eyre!("Failed to recover identity secret: {err}")), diff --git a/rln-cli/src/examples/stateless.rs b/rln-cli/src/examples/stateless.rs index 0633810..30794b6 100644 --- a/rln-cli/src/examples/stateless.rs +++ b/rln-cli/src/examples/stateless.rs @@ -6,6 +6,7 @@ use std::{ use clap::{Parser, Subcommand}; use color_eyre::{eyre::eyre, Result}; +use rln::utils::IdSecret; use rln::{ circuit::{Fr, TEST_TREE_HEIGHT}, hashers::{hash_to_field, poseidon_hash}, @@ -45,7 +46,7 @@ enum Commands { #[derive(Debug, Clone)] struct Identity { - identity_secret_hash: Fr, + identity_secret_hash: IdSecret, id_commitment: Fr, } @@ -93,7 +94,7 @@ impl RLNSystem { println!("Registered users:"); for (index, identity) in &self.local_identities { println!("User Index: {index}"); - println!("+ Identity Secret Hash: {}", identity.identity_secret_hash); + println!("+ Identity Secret Hash: {}", *identity.identity_secret_hash); println!("+ Identity Commitment: {}", identity.id_commitment); println!(); } @@ -107,7 +108,7 @@ impl RLNSystem { self.tree.update_next(rate_commitment)?; println!("Registered User Index: {index}"); - println!("+ Identity secret hash: {}", identity.identity_secret_hash); + println!("+ Identity secret hash: {}", *identity.identity_secret_hash); println!("+ Identity commitment: {}", identity.id_commitment); self.local_identities.insert(index, identity); @@ -130,7 +131,7 @@ impl RLNSystem { let x = hash_to_field(signal.as_bytes()); let rln_witness = rln_witness_from_values( - identity.identity_secret_hash, + identity.identity_secret_hash.clone(), &merkle_proof, x, external_nullifier, @@ -208,7 +209,7 @@ impl RLNSystem { { Ok(_) => { let output_data = output.into_inner(); - let (leaked_identity_secret_hash, _) = bytes_le_to_fr(&output_data); + let (leaked_identity_secret_hash, _) = IdSecret::from_bytes_le(&output_data); if let Some((user_index, identity)) = self .local_identities @@ -218,19 +219,19 @@ impl RLNSystem { }) .map(|(index, identity)| (*index, identity)) { - let real_identity_secret_hash = identity.identity_secret_hash; + let real_identity_secret_hash = identity.identity_secret_hash.clone(); if leaked_identity_secret_hash != real_identity_secret_hash { - Err(eyre!("identity secret hash mismatch {leaked_identity_secret_hash} != {real_identity_secret_hash}")) + Err(eyre!("identity secret hash mismatch: leaked_identity_secret_hash != real_identity_secret_hash")) } else { - println!("DUPLICATE message ID detected! Reveal identity secret hash: {leaked_identity_secret_hash}"); + println!( + "DUPLICATE message ID detected! Reveal identity secret hash: ********" + ); self.local_identities.remove(&user_index); println!("User index {user_index} has been SLASHED"); Ok(()) } } else { - Err(eyre!( - "user identity secret hash {leaked_identity_secret_hash} not found" - )) + Err(eyre!("user identity secret hash ******** not found")) } } Err(err) => Err(eyre!("Failed to recover identity secret: {err}")), diff --git a/rln-wasm/tests/browser.rs b/rln-wasm/tests/browser.rs index 83a6e2e..f7ea447 100644 --- a/rln-wasm/tests/browser.rs +++ b/rln-wasm/tests/browser.rs @@ -7,7 +7,7 @@ mod tests { use rln::hashers::{hash_to_field, poseidon_hash}; use rln::poseidon_tree::PoseidonTree; use rln::protocol::{prepare_verify_input, rln_witness_from_values, serialize_witness}; - use rln::utils::{bytes_le_to_fr, fr_to_bytes_le}; + use rln::utils::{bytes_le_to_fr, fr_to_bytes_le, IdSecret}; use rln_wasm::{ wasm_generate_rln_proof_with_witness, wasm_key_gen, wasm_new, wasm_rln_witness_to_json, wasm_verify_with_roots, @@ -121,7 +121,7 @@ mod tests { // Generate identity pair for other benchmarks let mem_keys = wasm_key_gen(rln_instance).expect("Failed to generate keys"); let id_key = mem_keys.subarray(0, 32); - let (identity_secret_hash, _) = bytes_le_to_fr(&id_key.to_vec()); + let (identity_secret_hash, _) = IdSecret::from_bytes_le(&id_key.to_vec()); let (id_commitment, _) = bytes_le_to_fr(&mem_keys.subarray(32, 64).to_vec()); let epoch = hash_to_field(b"test-epoch"); diff --git a/rln-wasm/tests/node.rs b/rln-wasm/tests/node.rs index 2835424..4e4b688 100644 --- a/rln-wasm/tests/node.rs +++ b/rln-wasm/tests/node.rs @@ -8,7 +8,7 @@ mod tests { use rln::hashers::{hash_to_field, poseidon_hash}; use rln::poseidon_tree::PoseidonTree; use rln::protocol::{prepare_verify_input, rln_witness_from_values, serialize_witness}; - use rln::utils::{bytes_le_to_fr, fr_to_bytes_le}; + use rln::utils::{bytes_le_to_fr, fr_to_bytes_le, IdSecret}; use rln_wasm::{ wasm_generate_rln_proof_with_witness, wasm_key_gen, wasm_new, wasm_rln_witness_to_json, wasm_verify_with_roots, @@ -86,7 +86,7 @@ mod tests { // Generate identity pair for other benchmarks let mem_keys = wasm_key_gen(rln_instance).expect("Failed to generate keys"); let id_key = mem_keys.subarray(0, 32); - let (identity_secret_hash, _) = bytes_le_to_fr(&id_key.to_vec()); + let (identity_secret_hash, _) = IdSecret::from_bytes_le(&id_key.to_vec()); let (id_commitment, _) = bytes_le_to_fr(&mem_keys.subarray(32, 64).to_vec()); let epoch = hash_to_field(b"test-epoch"); diff --git a/rln/Cargo.toml b/rln/Cargo.toml index 5b01963..a4e070d 100644 --- a/rln/Cargo.toml +++ b/rln/Cargo.toml @@ -53,7 +53,8 @@ rand = "0.8.5" rand_chacha = "0.3.1" ruint = { version = "1.15.0", features = ["rand", "serde", "ark-ff-04"] } tiny-keccak = { version = "2.0.2", features = ["keccak"] } -utils = { path = "../utils", package = "zerokit_utils", version = "0.6.0", default-features = false } +zeroize = "1.8" +utils = { package = "zerokit_utils", version = "0.6.0", path = "../utils", default-features = false } # serialization prost = "0.13.5" diff --git a/rln/src/circuit/iden3calc.rs b/rln/src/circuit/iden3calc.rs index 481f262..584b2c9 100644 --- a/rln/src/circuit/iden3calc.rs +++ b/rln/src/circuit/iden3calc.rs @@ -8,19 +8,33 @@ pub mod storage; use ruint::aliases::U256; use std::collections::HashMap; use storage::deserialize_witnesscalc_graph; +use zeroize::zeroize_flat_type; +use crate::circuit::iden3calc::graph::fr_to_u256; use crate::circuit::Fr; -use graph::{fr_to_u256, Node}; +use crate::utils::FrOrSecret; +use graph::Node; pub type InputSignalsInfo = HashMap; -pub fn calc_witness)>>( +pub fn calc_witness)>>( inputs: I, graph_data: &[u8], ) -> Vec { - let inputs: HashMap> = inputs + let mut inputs: HashMap> = inputs .into_iter() - .map(|(key, value)| (key, value.iter().map(fr_to_u256).collect())) + .map(|(key, value)| { + ( + key, + value + .iter() + .map(|f_| match f_ { + FrOrSecret::IdSecret(s) => s.to_u256(), + FrOrSecret::Fr(f) => fr_to_u256(f), + }) + .collect(), + ) + }) .collect(); let (nodes, signals, input_mapping): (Vec, Vec, InputSignalsInfo) = @@ -28,8 +42,15 @@ pub fn calc_witness)>>( let mut inputs_buffer = get_inputs_buffer(get_inputs_size(&nodes)); populate_inputs(&inputs, &input_mapping, &mut inputs_buffer); - - graph::evaluate(&nodes, inputs_buffer.as_slice(), &signals) + if let Some(v) = inputs.get_mut("identitySecret") { + // ~== v[0] = U256::ZERO; + unsafe { zeroize_flat_type(v) }; + } + let res = graph::evaluate(&nodes, inputs_buffer.as_slice(), &signals); + inputs_buffer.iter_mut().for_each(|i| { + unsafe { zeroize_flat_type(i) }; + }); + res } fn get_inputs_size(nodes: &[Node]) -> usize { diff --git a/rln/src/circuit/mod.rs b/rln/src/circuit/mod.rs index 24e95e3..5d41dc2 100644 --- a/rln/src/circuit/mod.rs +++ b/rln/src/circuit/mod.rs @@ -20,6 +20,7 @@ use crate::circuit::iden3calc::calc_witness; #[cfg(feature = "arkzkey")] use {ark_ff::Field, ark_serialize::CanonicalDeserialize, ark_serialize::CanonicalSerialize}; +use crate::utils::FrOrSecret; #[cfg(not(feature = "arkzkey"))] use {crate::circuit::zkey::read_zkey, std::io::Cursor}; @@ -84,7 +85,7 @@ pub fn zkey_from_folder() -> &'static (ProvingKey, ConstraintMatrices &ZKEY } -pub fn calculate_rln_witness)>>( +pub fn calculate_rln_witness)>>( inputs: I, graph_data: &[u8], ) -> Vec { diff --git a/rln/src/protocol.rs b/rln/src/protocol.rs index 8b1cf00..03a1279 100644 --- a/rln/src/protocol.rs +++ b/rln/src/protocol.rs @@ -1,5 +1,14 @@ // This crate collects all the underlying primitives used to implement RLN +use crate::circuit::{calculate_rln_witness, qap::CircomReduction, Curve}; +use crate::error::{ComputeIdSecretError, ConversionError, ProofError, ProtocolError}; +use crate::hashers::{hash_to_field, poseidon_hash}; +use crate::poseidon_tree::{MerkleProof, PoseidonTree}; +use crate::public::RLN_IDENTIFIER; +use crate::utils::{ + bytes_le_to_fr, bytes_le_to_vec_fr, bytes_le_to_vec_u8, fr_byte_size, fr_to_bytes_le, + normalize_usize, to_bigint, vec_fr_to_bytes_le, vec_u8_to_bytes_le, FrOrSecret, IdSecret, +}; use ark_bn254::Fr; use ark_ff::AdditiveGroup; use ark_groth16::{prepare_verifying_key, Groth16, Proof as ArkProof, ProvingKey, VerifyingKey}; @@ -13,17 +22,8 @@ use serde::{Deserialize, Serialize}; #[cfg(test)] use std::time::Instant; use tiny_keccak::{Hasher as _, Keccak}; - -use crate::circuit::{calculate_rln_witness, qap::CircomReduction, Curve}; -use crate::error::{ComputeIdSecretError, ConversionError, ProofError, ProtocolError}; -use crate::hashers::{hash_to_field, poseidon_hash}; -use crate::poseidon_tree::{MerkleProof, PoseidonTree}; -use crate::public::RLN_IDENTIFIER; -use crate::utils::{ - bytes_le_to_fr, bytes_le_to_vec_fr, bytes_le_to_vec_u8, fr_byte_size, fr_to_bytes_le, - normalize_usize, to_bigint, vec_fr_to_bytes_le, vec_u8_to_bytes_le, -}; use utils::{ZerokitMerkleProof, ZerokitMerkleTree}; +use zeroize::Zeroize; /////////////////////////////////////////////////////// // RLN Witness data structure and utility functions /////////////////////////////////////////////////////// @@ -31,7 +31,7 @@ use utils::{ZerokitMerkleProof, ZerokitMerkleTree}; #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct RLNWitnessInput { #[serde(serialize_with = "ark_se", deserialize_with = "ark_de")] - identity_secret: Fr, + identity_secret: IdSecret, #[serde(serialize_with = "ark_se", deserialize_with = "ark_de")] user_message_limit: Fr, #[serde(serialize_with = "ark_se", deserialize_with = "ark_de")] @@ -113,7 +113,7 @@ pub fn serialize_witness(rln_witness: &RLNWitnessInput) -> Result, Proto fr_byte_size() * (5 + rln_witness.path_elements.len()) + rln_witness.identity_path_index.len(), ); - serialized.extend_from_slice(&fr_to_bytes_le(&rln_witness.identity_secret)); + serialized.extend_from_slice(&rln_witness.identity_secret.to_bytes_le()); serialized.extend_from_slice(&fr_to_bytes_le(&rln_witness.user_message_limit)); serialized.extend_from_slice(&fr_to_bytes_le(&rln_witness.message_id)); serialized.extend_from_slice(&vec_fr_to_bytes_le(&rln_witness.path_elements)); @@ -132,7 +132,7 @@ pub fn serialize_witness(rln_witness: &RLNWitnessInput) -> Result, Proto pub fn deserialize_witness(serialized: &[u8]) -> Result<(RLNWitnessInput, usize), ProtocolError> { let mut all_read: usize = 0; - let (identity_secret, read) = bytes_le_to_fr(&serialized[all_read..]); + let (identity_secret, read) = IdSecret::from_bytes_le(&serialized[all_read..]); all_read += read; let (user_message_limit, read) = bytes_le_to_fr(&serialized[all_read..]); @@ -183,7 +183,7 @@ pub fn proof_inputs_to_rln_witness( ) -> Result<(RLNWitnessInput, usize), ProtocolError> { let mut all_read: usize = 0; - let (identity_secret, read) = bytes_le_to_fr(&serialized[all_read..]); + let (identity_secret, read) = IdSecret::from_bytes_le(&serialized[all_read..]); all_read += read; let id_index = usize::try_from(u64::from_le_bytes( @@ -239,7 +239,7 @@ pub fn proof_inputs_to_rln_witness( /// /// Returns an error if `message_id` is not within `user_message_limit`. pub fn rln_witness_from_values( - identity_secret: Fr, + identity_secret: IdSecret, merkle_proof: &MerkleProof, x: Fr, external_nullifier: Fr, @@ -265,7 +265,7 @@ pub fn rln_witness_from_values( pub fn random_rln_witness(tree_height: usize) -> RLNWitnessInput { let mut rng = thread_rng(); - let identity_secret = hash_to_field(&rng.gen::<[u8; 32]>()); + let identity_secret = IdSecret::rand(&mut rng); let x = hash_to_field(&rng.gen::<[u8; 32]>()); let epoch = hash_to_field(&rng.gen::<[u8; 32]>()); let rln_identifier = hash_to_field(RLN_IDENTIFIER); //hash_to_field(&rng.gen::<[u8; 32]>()); @@ -298,12 +298,18 @@ pub fn proof_values_from_witness( message_id_range_check(&rln_witness.message_id, &rln_witness.user_message_limit)?; // y share - let a_0 = rln_witness.identity_secret; - let a_1 = poseidon_hash(&[a_0, rln_witness.external_nullifier, rln_witness.message_id]); - let y = a_0 + rln_witness.x * a_1; + let a_0 = &rln_witness.identity_secret; + let mut to_hash = [ + *(a_0.clone()), + rln_witness.external_nullifier, + rln_witness.message_id, + ]; + let a_1 = poseidon_hash(&to_hash); + let y = *(a_0.clone()) + rln_witness.x * a_1; // Nullifier let nullifier = poseidon_hash(&[a_1]); + to_hash[0].zeroize(); // Merkle tree root computations let root = compute_tree_root( @@ -371,7 +377,7 @@ pub fn deserialize_proof_values(serialized: &[u8]) -> (RLNProofValues, usize) { // input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal ] pub fn prepare_prove_input( - identity_secret: Fr, + identity_secret: IdSecret, id_index: usize, user_message_limit: Fr, message_id: Fr, @@ -384,7 +390,7 @@ pub fn prepare_prove_input( // - variable length signal data let mut serialized = Vec::with_capacity(fr_byte_size() * 4 + 16 + signal.len()); // length of 4 fr elements + 16 bytes (id_index + len) + signal length - serialized.extend_from_slice(&fr_to_bytes_le(&identity_secret)); + serialized.extend_from_slice(&identity_secret.to_bytes_le()); serialized.extend_from_slice(&normalize_usize(id_index)); serialized.extend_from_slice(&fr_to_bytes_le(&user_message_limit)); serialized.extend_from_slice(&fr_to_bytes_le(&message_id)); @@ -415,12 +421,15 @@ pub fn prepare_verify_input(proof_data: Vec, signal: &[u8]) -> Vec { /////////////////////////////////////////////////////// pub fn compute_tree_root( - identity_secret: &Fr, + identity_secret: &IdSecret, user_message_limit: &Fr, path_elements: &[Fr], identity_path_index: &[u8], ) -> Fr { - let id_commitment = poseidon_hash(&[*identity_secret]); + let mut to_hash = [*identity_secret.clone()]; + let id_commitment = poseidon_hash(&to_hash); + to_hash[0].zeroize(); + let mut root = poseidon_hash(&[id_commitment, *user_message_limit]); for i in 0..identity_path_index.len() { @@ -441,10 +450,12 @@ pub fn compute_tree_root( // 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) { +pub fn keygen() -> (IdSecret, Fr) { let mut rng = thread_rng(); - let identity_secret_hash = Fr::rand(&mut rng); - let id_commitment = poseidon_hash(&[identity_secret_hash]); + let identity_secret_hash = IdSecret::rand(&mut rng); + let mut to_hash = [*identity_secret_hash.clone()]; + let id_commitment = poseidon_hash(&to_hash); + to_hash[0].zeroize(); (identity_secret_hash, id_commitment) } @@ -512,7 +523,10 @@ pub fn extended_seeded_keygen(signal: &[u8]) -> (Fr, Fr, Fr, Fr) { ) } -pub fn compute_id_secret(share1: (Fr, Fr), share2: (Fr, Fr)) -> Result { +pub fn compute_id_secret( + share1: (Fr, Fr), + share2: (Fr, Fr), +) -> Result { // Assuming a0 is the identity secret and a1 = poseidonHash([a0, external_nullifier]), // a (x,y) share satisfies the following relation // y = a_0 + x * a_1 @@ -525,10 +539,11 @@ pub fn compute_id_secret(share1: (Fr, Fr), share2: (Fr, Fr)) -> Result Result<[(&str, Vec); 7], ProtocolError> { +) -> Result<[(&str, Vec); 7], ProtocolError> { message_id_range_check(&rln_witness.message_id, &rln_witness.user_message_limit)?; let mut identity_path_index = Vec::with_capacity(rln_witness.identity_path_index.len()); @@ -618,13 +633,33 @@ pub fn inputs_for_witness_calculation( .for_each(|v| identity_path_index.push(Fr::from(*v))); Ok([ - ("identitySecret", vec![rln_witness.identity_secret]), - ("userMessageLimit", vec![rln_witness.user_message_limit]), - ("messageId", vec![rln_witness.message_id]), - ("pathElements", rln_witness.path_elements.clone()), - ("identityPathIndex", identity_path_index), - ("x", vec![rln_witness.x]), - ("externalNullifier", vec![rln_witness.external_nullifier]), + ( + "identitySecret", + vec![rln_witness.identity_secret.clone().into()], + ), + ( + "userMessageLimit", + vec![rln_witness.user_message_limit.into()], + ), + ("messageId", vec![rln_witness.message_id.into()]), + ( + "pathElements", + rln_witness + .path_elements + .iter() + .cloned() + .map(Into::into) + .collect(), + ), + ( + "identityPathIndex", + identity_path_index.into_iter().map(Into::into).collect(), + ), + ("x", vec![rln_witness.x.into()]), + ( + "externalNullifier", + vec![rln_witness.external_nullifier.into()], + ), ]) } diff --git a/rln/src/public.rs b/rln/src/public.rs index 775d9d9..c80f8d3 100644 --- a/rln/src/public.rs +++ b/rln/src/public.rs @@ -2,14 +2,11 @@ use crate::circuit::{zkey_from_raw, Curve, Fr}; use crate::hashers::{hash_to_field, poseidon_hash as utils_poseidon_hash}; use crate::protocol::{ compute_id_secret, deserialize_proof_values, deserialize_witness, extended_keygen, - extended_seeded_keygen, generate_proof, keygen, proof_inputs_to_rln_witness, - proof_values_from_witness, rln_witness_to_bigint_json, rln_witness_to_json, seeded_keygen, - serialize_proof_values, serialize_witness, verify_proof, -}; -use crate::utils::{ - bytes_le_to_fr, bytes_le_to_vec_fr, bytes_le_to_vec_u8, fr_byte_size, fr_to_bytes_le, - vec_fr_to_bytes_le, vec_u8_to_bytes_le, + extended_seeded_keygen, generate_proof, keygen, proof_values_from_witness, + rln_witness_to_bigint_json, rln_witness_to_json, seeded_keygen, serialize_proof_values, + verify_proof, }; +use crate::utils::{bytes_le_to_fr, bytes_le_to_vec_fr, fr_byte_size, fr_to_bytes_le}; #[cfg(not(target_arch = "wasm32"))] use { crate::circuit::{graph_from_folder, zkey_from_folder}, @@ -23,9 +20,12 @@ use crate::protocol::generate_proof_with_witness; /// used by tests etc. as well #[cfg(not(feature = "stateless"))] use { + crate::protocol::{proof_inputs_to_rln_witness, serialize_witness}, + crate::utils::{bytes_le_to_vec_u8, vec_fr_to_bytes_le, vec_u8_to_bytes_le}, crate::{circuit::TEST_TREE_HEIGHT, poseidon_tree::PoseidonTree}, serde_json::{json, Value}, std::str::FromStr, + utils::error::ZerokitMerkleTreeError, utils::{Hasher, ZerokitMerkleProof, ZerokitMerkleTree}, }; @@ -36,7 +36,6 @@ use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, Write}; #[cfg(target_arch = "wasm32")] use num_bigint::BigInt; use std::io::Cursor; -use utils::error::ZerokitMerkleTreeError; /// The application-specific RLN identifier. /// @@ -1133,7 +1132,7 @@ impl RLN { /// ``` pub fn key_gen(&self, mut output_data: W) -> Result<(), RLNError> { let (identity_secret_hash, id_commitment) = keygen(); - output_data.write_all(&fr_to_bytes_le(&identity_secret_hash))?; + output_data.write_all(&identity_secret_hash.to_bytes_le())?; output_data.write_all(&fr_to_bytes_le(&id_commitment))?; Ok(()) @@ -1324,7 +1323,7 @@ impl RLN { compute_id_secret(share1, share2).map_err(RLNError::RecoverSecret)?; // If an identity secret hash is recovered, we write it to output_data, otherwise nothing will be written. - output_data.write_all(&fr_to_bytes_le(&recovered_identity_secret_hash))?; + output_data.write_all(&recovered_identity_secret_hash.to_bytes_le())?; } Ok(()) diff --git a/rln/src/public_api_tests.rs b/rln/src/public_api_tests.rs index f19e794..1fbcb00 100644 --- a/rln/src/public_api_tests.rs +++ b/rln/src/public_api_tests.rs @@ -880,7 +880,7 @@ mod tree_test { // We prepare input for generate_rln_proof API // input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal ] let prove_input1 = prepare_prove_input( - identity_secret_hash, + identity_secret_hash.clone(), identity_index, user_message_limit, message_id, @@ -889,7 +889,7 @@ mod tree_test { ); let prove_input2 = prepare_prove_input( - identity_secret_hash, + identity_secret_hash.clone(), identity_index, user_message_limit, message_id, @@ -932,7 +932,7 @@ mod tree_test { // 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); + assert_eq!(recovered_identity_secret_hash, *identity_secret_hash); // 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 @@ -982,7 +982,10 @@ mod tree_test { // ensure that the recovered secret does not match with either of the // used secrets in proof generation - assert_ne!(recovered_identity_secret_hash_new, identity_secret_hash_new); + assert_ne!( + recovered_identity_secret_hash_new, + *identity_secret_hash_new + ); } } @@ -1136,7 +1139,7 @@ mod stateless_test { let merkle_proof = tree.proof(identity_index).expect("proof should exist"); let rln_witness1 = rln_witness_from_values( - identity_secret_hash, + identity_secret_hash.clone(), &merkle_proof, x1, external_nullifier, @@ -1146,7 +1149,7 @@ mod stateless_test { .unwrap(); let rln_witness2 = rln_witness_from_values( - identity_secret_hash, + identity_secret_hash.clone(), &merkle_proof, x2, external_nullifier, @@ -1186,7 +1189,7 @@ mod stateless_test { // 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); + assert_eq!(recovered_identity_secret_hash, *identity_secret_hash); // 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 @@ -1202,7 +1205,7 @@ mod stateless_test { let merkle_proof_new = tree.proof(identity_index_new).expect("proof should exist"); let rln_witness3 = rln_witness_from_values( - identity_secret_hash_new, + identity_secret_hash_new.clone(), &merkle_proof_new, x3, external_nullifier, @@ -1230,7 +1233,7 @@ mod stateless_test { let serialized_identity_secret_hash = output_buffer.into_inner(); let (recovered_identity_secret_hash_new, _) = - bytes_le_to_fr(&serialized_identity_secret_hash); + IdSecret::from_bytes_le(&serialized_identity_secret_hash); // ensure that the recovered secret does not match with either of the // used secrets in proof generation diff --git a/rln/src/utils.rs b/rln/src/utils.rs index 4b2cbbd..1df454a 100644 --- a/rln/src/utils.rs +++ b/rln/src/utils.rs @@ -3,10 +3,16 @@ use crate::circuit::Fr; use crate::error::ConversionError; use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::UniformRand; use num_bigint::{BigInt, BigUint}; use num_traits::Num; +use rand::Rng; +use ruint::aliases::U256; use serde_json::json; use std::io::Cursor; +use std::ops::Deref; +use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; #[inline(always)] pub fn to_bigint(el: &Fr) -> BigInt { @@ -149,3 +155,81 @@ pub fn normalize_usize(input: usize) -> [u8; 8] { pub fn generate_input_buffer() -> Cursor { Cursor::new(json!({}).to_string()) } + +#[derive( + Debug, Zeroize, ZeroizeOnDrop, Clone, PartialEq, CanonicalSerialize, CanonicalDeserialize, +)] +pub struct IdSecret(ark_bn254::Fr); + +impl IdSecret { + pub fn rand(rng: &mut R) -> Self { + let mut fr = Fr::rand(rng); + let res = Self::from(&mut fr); + // No need to zeroize fr (already zeroiz'ed in from implementation) + #[allow(clippy::let_and_return)] + res + } + + pub fn from_bytes_le(input: &[u8]) -> (Self, usize) { + let el_size = fr_byte_size(); + let b_uint = BigUint::from_bytes_le(&input[0..el_size]); + let mut fr = Fr::from(b_uint); + let res = IdSecret::from(&mut fr); + // Note: no zeroize on b_uint as it has been moved + (res, el_size) + } + + pub(crate) fn to_bytes_le(&self) -> Zeroizing> { + let input_biguint: BigUint = self.0.into(); + let mut res = input_biguint.to_bytes_le(); + res.resize(fr_byte_size(), 0); + Zeroizing::new(res) + } + + /// Warning: this can leak the secret value + /// Warning: Leaked value is of type 'U256' which implement Copy (every copy will not be zeroized) + pub(crate) fn to_u256(&self) -> U256 { + let mut big_int = self.0.into_bigint(); + let res = U256::from_limbs(big_int.0); + big_int.zeroize(); + res + } +} + +impl From<&mut Fr> for IdSecret { + fn from(value: &mut Fr) -> Self { + let id_secret = Self(*value); + value.zeroize(); + id_secret + } +} + +impl Deref for IdSecret { + type Target = Fr; + + /// Deref to &Fr + /// + /// Warning: this can leak the secret value + /// Warning: Leaked value is of type 'Fr' which implement Copy (every copy will not be zeroized) + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Zeroize, ZeroizeOnDrop)] +pub enum FrOrSecret { + IdSecret(IdSecret), + Fr(Fr), +} + +impl From for FrOrSecret { + fn from(value: Fr) -> Self { + FrOrSecret::Fr(value) + } +} + +impl From for FrOrSecret { + fn from(value: IdSecret) -> Self { + FrOrSecret::IdSecret(value) + } +} diff --git a/rln/tests/ffi.rs b/rln/tests/ffi.rs index e697ab4..7d387cf 100644 --- a/rln/tests/ffi.rs +++ b/rln/tests/ffi.rs @@ -14,6 +14,7 @@ mod test { use std::io::Read; use std::mem::MaybeUninit; use std::time::{Duration, Instant}; + use zeroize::Zeroize; const NO_OF_LEAVES: usize = 256; @@ -49,13 +50,13 @@ mod test { root } - fn identity_pair_gen(rln_pointer: &mut RLN) -> (Fr, Fr) { + fn identity_pair_gen(rln_pointer: &mut RLN) -> (IdSecret, Fr) { let mut output_buffer = MaybeUninit::::uninit(); let success = key_gen(rln_pointer, output_buffer.as_mut_ptr()); 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_hash, read) = bytes_le_to_fr(&result_data); + let (identity_secret_hash, read) = IdSecret::from_bytes_le(&result_data); let (id_commitment, _) = bytes_le_to_fr(&result_data[read..].to_vec()); (identity_secret_hash, id_commitment) } @@ -271,8 +272,11 @@ mod test { let rln_pointer = create_rln_instance(); // generate identity - let identity_secret_hash = hash_to_field(b"test-merkle-proof"); - let id_commitment = utils_poseidon_hash(&[identity_secret_hash]); + let mut identity_secret_hash_ = hash_to_field(b"test-merkle-proof"); + let identity_secret_hash = IdSecret::from(&mut identity_secret_hash_); + let mut to_hash = [*identity_secret_hash.clone()]; + let id_commitment = utils_poseidon_hash(&to_hash); + to_hash[0].zeroize(); let user_message_limit = Fr::from(100); let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]); @@ -674,7 +678,7 @@ mod test { // We prepare input for generate_rln_proof API // input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal ] let prove_input1 = prepare_prove_input( - identity_secret_hash, + identity_secret_hash.clone(), identity_index, user_message_limit, message_id, @@ -683,7 +687,7 @@ mod test { ); let prove_input2 = prepare_prove_input( - identity_secret_hash, + identity_secret_hash.clone(), identity_index, user_message_limit, message_id, @@ -718,7 +722,7 @@ mod test { // 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); + assert_eq!(recovered_identity_secret_hash, *identity_secret_hash); // 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 @@ -772,7 +776,10 @@ mod test { // ensure that the recovered secret does not match with either of the // used secrets in proof generation - assert_ne!(recovered_identity_secret_hash_new, identity_secret_hash_new); + assert_ne!( + recovered_identity_secret_hash_new, + *identity_secret_hash_new + ); } #[test] @@ -1009,13 +1016,13 @@ mod stateless_test { unsafe { &mut *rln_pointer.assume_init() } } - fn identity_pair_gen(rln_pointer: &mut RLN) -> (Fr, Fr) { + fn identity_pair_gen(rln_pointer: &mut RLN) -> (IdSecret, Fr) { let mut output_buffer = MaybeUninit::::uninit(); let success = key_gen(rln_pointer, output_buffer.as_mut_ptr()); 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_hash, read) = bytes_le_to_fr(&result_data); + let (identity_secret_hash, read) = IdSecret::from_bytes_le(&result_data); let (id_commitment, _) = bytes_le_to_fr(&result_data[read..].to_vec()); (identity_secret_hash, id_commitment) } @@ -1068,7 +1075,7 @@ mod stateless_test { // We prepare input for generate_rln_proof API let rln_witness1 = rln_witness_from_values( - identity_secret_hash, + identity_secret_hash.clone(), &merkle_proof, x1, external_nullifier, @@ -1079,7 +1086,7 @@ mod stateless_test { let serialized1 = serialize_witness(&rln_witness1).unwrap(); let rln_witness2 = rln_witness_from_values( - identity_secret_hash, + identity_secret_hash.clone(), &merkle_proof, x2, external_nullifier, @@ -1115,7 +1122,8 @@ mod stateless_test { assert!(!serialized_identity_secret_hash.is_empty()); // 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); + let (recovered_identity_secret_hash, _) = + IdSecret::from_bytes_le(&serialized_identity_secret_hash); assert_eq!(recovered_identity_secret_hash, identity_secret_hash); // 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 @@ -1133,7 +1141,7 @@ mod stateless_test { let merkle_proof_new = tree.proof(identity_index_new).expect("proof should exist"); let rln_witness3 = rln_witness_from_values( - identity_secret_hash_new, + identity_secret_hash_new.clone(), &merkle_proof_new, x3, external_nullifier, @@ -1166,7 +1174,10 @@ mod stateless_test { // ensure that the recovered secret does not match with either of the // used secrets in proof generation - assert_ne!(recovered_identity_secret_hash_new, identity_secret_hash_new); + assert_ne!( + recovered_identity_secret_hash_new, + *identity_secret_hash_new + ); } #[test] diff --git a/rln/tests/public.rs b/rln/tests/public.rs index ae52d21..b6ee409 100644 --- a/rln/tests/public.rs +++ b/rln/tests/public.rs @@ -14,9 +14,10 @@ mod test { use rln::public::{hash as public_hash, poseidon_hash as public_poseidon_hash, RLN}; use rln::utils::{ bytes_le_to_fr, bytes_le_to_vec_fr, bytes_le_to_vec_u8, bytes_le_to_vec_usize, - fr_to_bytes_le, generate_input_buffer, str_to_fr, vec_fr_to_bytes_le, + fr_to_bytes_le, generate_input_buffer, str_to_fr, vec_fr_to_bytes_le, IdSecret, }; use std::io::Cursor; + use zeroize::Zeroize; #[test] // This test is similar to the one in lib, but uses only public API @@ -28,8 +29,13 @@ mod test { let mut rln = RLN::new(TEST_TREE_HEIGHT, generate_input_buffer()).unwrap(); // generate identity - let identity_secret_hash = hash_to_field(b"test-merkle-proof"); - let id_commitment = utils_poseidon_hash(&vec![identity_secret_hash]); + let mut identity_secret_hash_ = hash_to_field(b"test-merkle-proof"); + let identity_secret_hash = IdSecret::from(&mut identity_secret_hash_); + + let mut to_hash = [*identity_secret_hash.clone()]; + let id_commitment = utils_poseidon_hash(&to_hash); + to_hash[0].zeroize(); + let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit.into()]); // check that leaves indices is empty