diff --git a/Cargo.lock b/Cargo.lock index d87e53f..05b6590 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -828,6 +828,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "unicode-xid", +] + [[package]] name = "digest" version = "0.9.0" @@ -1640,6 +1661,7 @@ dependencies = [ "byteorder", "cfg-if", "criterion", + "derive_more", "document-features", "lazy_static", "num-bigint", @@ -1653,6 +1675,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..e37d7aa 100644 --- a/rln-cli/src/examples/relay.rs +++ b/rln-cli/src/examples/relay.rs @@ -14,6 +14,7 @@ use rln::{ public::RLN, utils::{bytes_le_to_fr, fr_to_bytes_le, generate_input_buffer}, }; +use rln::protocol::IdSecret; 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, } @@ -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), diff --git a/rln/Cargo.toml b/rln/Cargo.toml index 1411320..e077650 100644 --- a/rln/Cargo.toml +++ b/rln/Cargo.toml @@ -54,6 +54,8 @@ 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 = { package = "zerokit_utils", version = "0.6.0", path = "../utils", default-features = false } +zeroize = "1.8.1" +derive_more = { version = "2.0.1", features = ["from", "into", "display"] } # serialization prost = "0.13.5" diff --git a/rln/src/protocol.rs b/rln/src/protocol.rs index 8b1cf00..dba2f64 100644 --- a/rln/src/protocol.rs +++ b/rln/src/protocol.rs @@ -1,19 +1,22 @@ // This crate collects all the underlying primitives used to implement RLN +use std::io::{Read, Write}; use ark_bn254::Fr; use ark_ff::AdditiveGroup; use ark_groth16::{prepare_verifying_key, Groth16, Proof as ArkProof, ProvingKey, VerifyingKey}; use ark_relations::r1cs::ConstraintMatrices; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, SerializationError, Valid, Validate}; use ark_std::{rand::thread_rng, UniformRand}; use num_bigint::BigInt; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; use serde::{Deserialize, Serialize}; +use zeroize::{Zeroize, ZeroizeOnDrop}; +use derive_more::{From, Into, Display}; + #[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}; @@ -31,7 +34,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 +116,9 @@ 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)); + let mut identity_secret: Fr = rln_witness.identity_secret.clone().into(); + serialized.extend_from_slice(&fr_to_bytes_le(&identity_secret)); + identity_secret.zeroize(); 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 +137,9 @@ 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 (mut identity_secret_, read) = bytes_le_to_fr(&serialized[all_read..]); + let identity_secret = IdSecret::from(identity_secret_); + identity_secret_.zeroize(); all_read += read; let (user_message_limit, read) = bytes_le_to_fr(&serialized[all_read..]); @@ -183,7 +190,10 @@ 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 (mut identity_secret_, read) = bytes_le_to_fr(&serialized[all_read..]); + let identity_secret = IdSecret::from(identity_secret_); + identity_secret_.zeroize(); + all_read += read; let id_index = usize::try_from(u64::from_le_bytes( @@ -239,7 +249,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 +275,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::from(hash_to_field(&rng.gen::<[u8; 32]>())); 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,7 +308,7 @@ 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_0 = rln_witness.identity_secret.clone().into(); let a_1 = poseidon_hash(&[a_0, rln_witness.external_nullifier, rln_witness.message_id]); let y = a_0 + rln_witness.x * a_1; @@ -371,7 +381,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 +394,9 @@ 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)); + let mut identity_secret_: Fr = identity_secret.into(); + serialized.extend_from_slice(&fr_to_bytes_le(&identity_secret_)); + identity_secret_.zeroize(); 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 +427,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 identity_secret_: Fr = identity_secret.clone().into(); + let id_commitment = poseidon_hash(&[identity_secret_]); + identity_secret_.zeroize(); let mut root = poseidon_hash(&[id_commitment, *user_message_limit]); for i in 0..identity_path_index.len() { @@ -441,11 +456,11 @@ 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]); - (identity_secret_hash, id_commitment) + (IdSecret::from(identity_secret_hash), id_commitment) } // Generates a tuple (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) where @@ -512,7 +527,32 @@ pub fn extended_seeded_keygen(signal: &[u8]) -> (Fr, Fr, Fr, Fr) { ) } -pub fn compute_id_secret(share1: (Fr, Fr), share2: (Fr, Fr)) -> Result { +#[derive(Debug, Zeroize, ZeroizeOnDrop, From, Into, Clone, PartialEq, Display)] +pub struct IdSecret(Fr); + +impl CanonicalSerialize for IdSecret { + fn serialize_with_mode(&self, writer: W, compress: Compress) -> Result<(), SerializationError> { + todo!() + } + + fn serialized_size(&self, compress: Compress) -> usize { + todo!() + } +} + +impl Valid for IdSecret { + fn check(&self) -> Result<(), SerializationError> { + self.0.check() + } +} + +impl CanonicalDeserialize for IdSecret { + fn deserialize_with_mode(reader: R, compress: Compress, validate: Validate) -> Result { + todo!() + } +} + +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 @@ -528,7 +568,7 @@ pub fn compute_id_secret(share1: (Fr, Fr), share2: (Fr, Fr)) -> Result(&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))?; + let mut identity_secret_hash_: Fr = identity_secret_hash.into(); + output_data.write_all(&fr_to_bytes_le(&identity_secret_hash_))?; + identity_secret_hash_.zeroize(); output_data.write_all(&fr_to_bytes_le(&id_commitment))?; Ok(()) @@ -1324,7 +1327,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(&fr_to_bytes_le(&recovered_identity_secret_hash.into()))?; } Ok(()) diff --git a/rln/src/public_api_tests.rs b/rln/src/public_api_tests.rs index f19e794..42d41d9 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.clone().into()); // 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,7 @@ 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.into()); } } diff --git a/rln/tests/ffi.rs b/rln/tests/ffi.rs index e697ab4..65ba3d7 100644 --- a/rln/tests/ffi.rs +++ b/rln/tests/ffi.rs @@ -49,15 +49,16 @@ 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(); + // FIXME: zeroize? let (identity_secret_hash, read) = bytes_le_to_fr(&result_data); let (id_commitment, _) = bytes_le_to_fr(&result_data[read..].to_vec()); - (identity_secret_hash, id_commitment) + (IdSecret::from(identity_secret_hash), id_commitment) } fn rln_proof_gen(rln_pointer: &mut RLN, serialized: &[u8]) -> Vec { @@ -273,6 +274,7 @@ mod test { // generate identity let identity_secret_hash = hash_to_field(b"test-merkle-proof"); let id_commitment = utils_poseidon_hash(&[identity_secret_hash]); + let identity_secret_hash = IdSecret::from(identity_secret_hash); let user_message_limit = Fr::from(100); let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]); @@ -683,7 +685,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 +720,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.into(), 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 @@ -741,7 +743,7 @@ mod test { // input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] // Note that epoch is the same as before let prove_input3 = prepare_prove_input( - identity_secret_hash, + identity_secret_hash.clone(), identity_index_new, user_message_limit, message_id, diff --git a/rln/tests/public.rs b/rln/tests/public.rs index ae52d21..1d40d06 100644 --- a/rln/tests/public.rs +++ b/rln/tests/public.rs @@ -10,7 +10,7 @@ mod test { use rand::Rng; use rln::circuit::Fr; use rln::hashers::{hash_to_field, poseidon_hash as utils_poseidon_hash, ROUND_PARAMS}; - use rln::protocol::deserialize_identity_tuple; + use rln::protocol::{deserialize_identity_tuple, IdSecret}; 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, @@ -30,6 +30,7 @@ mod test { // generate identity let identity_secret_hash = hash_to_field(b"test-merkle-proof"); let id_commitment = utils_poseidon_hash(&vec![identity_secret_hash]); + let identity_secret_hash = IdSecret::from(identity_secret_hash); let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit.into()]); // check that leaves indices is empty