diff --git a/crates/common/src/hashchain.rs b/crates/common/src/hashchain.rs index 0baa8692..8f2f7386 100644 --- a/crates/common/src/hashchain.rs +++ b/crates/common/src/hashchain.rs @@ -6,14 +6,7 @@ use std::{ ops::{Deref, DerefMut}, }; -use crate::{ - digest::Digest, - hasher::Hasher, - keys::VerifyingKey, - operation::{ - CreateAccountArgs, Operation, RegisterServiceArgs, ServiceChallenge, ServiceChallengeInput, - }, -}; +use crate::{digest::Digest, hasher::Hasher, keys::VerifyingKey, operation::Operation}; #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Hashchain { @@ -69,38 +62,6 @@ impl Hashchain { Ok(hc) } - pub fn create_account( - id: String, - value: VerifyingKey, - service_id: String, - challenge: ServiceChallengeInput, - prev_hash: Digest, - signature: Vec, - ) -> Result { - let mut hc = Hashchain::empty(id.clone()); - let operation = Operation::CreateAccount(CreateAccountArgs { - id, - value, - service_id, - challenge, - prev_hash, - signature, - }); - hc.perform_operation(operation)?; - Ok(hc) - } - - pub fn register_service(id: String, challenge: ServiceChallenge) -> Result { - let mut hc = Hashchain::empty(id.clone()); - let operation = Operation::RegisterService(RegisterServiceArgs { - id, - creation_gate: challenge, - prev_hash: Digest::zero(), - }); - hc.perform_operation(operation)?; - Ok(hc) - } - pub fn empty(id: String) -> Self { Self { id, @@ -108,99 +69,6 @@ impl Hashchain { } } - pub fn verify_last_entry(&self) -> Result<()> { - if self.entries.is_empty() { - return Ok(()); - } - - let mut valid_keys: HashSet = HashSet::new(); - - for (index, entry) in self.entries.iter().enumerate().take(self.entries.len() - 1) { - match &entry.operation { - Operation::RegisterService(_) => { - if index != 0 { - bail!("RegisterService operation must be the first entry"); - } - } - Operation::CreateAccount(args) => { - if index != 0 { - bail!("CreateAccount operation must be the first entry"); - } - valid_keys.insert(args.value.clone()); - } - Operation::AddKey(args) => { - valid_keys.insert(args.value.clone()); - } - Operation::RevokeKey(args) => { - valid_keys.remove(&args.value); - } - Operation::AddData(_) => {} - } - } - - let last_entry = self.entries.last().unwrap(); - let last_index = self.entries.len() - 1; - if last_index > 0 { - let prev_entry = &self.entries[last_index - 1]; - if last_entry.previous_hash != prev_entry.hash { - bail!("Previous hash mismatch for the last entry"); - } - } - - match &last_entry.operation { - // TODO: RegisterService should not be permissionless at first, until we have state bloat metrics - Operation::RegisterService(_) => { - if last_index != 0 { - bail!("RegisterService operation must be the first entry"); - } - } - Operation::CreateAccount(args) => { - if last_index != 0 { - bail!("CreateAccount operation must be the first entry"); - } - args.value.verify_signature( - &bincode::serialize( - &last_entry.operation.without_signature().without_challenge(), - )?, - &args.signature, - )?; - } - Operation::AddKey(args) | Operation::RevokeKey(args) => { - self.verify_signature_at_key_idx( - &last_entry.operation, - &args.signature.signature, - args.signature.key_idx, - &valid_keys, - )?; - } - Operation::AddData(args) => { - self.verify_signature_at_key_idx( - &last_entry.operation, - &args.op_signature.signature, - args.op_signature.key_idx, - &valid_keys, - )?; - - let Some(value_signature) = &args.value_signature else { - return Ok(()); - }; - - // If data to be added is signed, also validate its signature - value_signature - .verifying_key - .verify_signature(&args.value, &value_signature.signature)?; - } - } - - Ok(()) - } - - pub(crate) fn insert_unsafe(&self, new_entry: HashchainEntry) -> Hashchain { - let mut new = self.clone(); - new.entries.push(new_entry); - new - } - pub fn get_key_at_index(&self, idx: usize) -> Result<&VerifyingKey> { self.entries .get(idx) @@ -228,7 +96,7 @@ impl Hashchain { valid_keys } - pub fn is_key_revoked(&self, key: VerifyingKey) -> bool { + pub fn is_key_invalid(&self, key: VerifyingKey) -> bool { self.iter() .rev() .find_map(|entry| match entry.operation.clone() { @@ -237,35 +105,7 @@ impl Hashchain { Operation::CreateAccount(args) if args.value == key => Some(false), _ => None, }) - .unwrap_or(false) - } - - fn verify_signature_at_key_idx( - &self, - operation: &Operation, - signature: &[u8], - idx: usize, - valid_keys: &HashSet, - ) -> Result<()> { - let verifying_key = self.get_key_at_index(idx)?; - if !valid_keys.contains(verifying_key) { - bail!( - "Key intended to verify signature {:?} is not in valid keys {:?}", - verifying_key, - valid_keys - ); - } - - let message = bincode::serialize(&operation.without_signature())?; - verifying_key.verify_signature(&message, signature) - } - - pub fn iter(&self) -> std::slice::Iter<'_, HashchainEntry> { - self.entries.iter() - } - - pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, HashchainEntry> { - self.entries.iter_mut() + .unwrap_or(true) } pub fn get(&self, idx: usize) -> &HashchainEntry { @@ -303,40 +143,61 @@ impl Hashchain { } if args.prev_hash != Digest::zero() { - bail!("Previous hash for initial operation must be zero") + bail!( + "Previous hash for initial operation must be zero, but was {}", + args.prev_hash + ) } Ok(()) } Operation::AddKey(args) | Operation::RevokeKey(args) => { - if args.prev_hash != self.last_hash() { - bail!("Previous hash for key operation must be the last hash") + let last_hash = self.last_hash(); + if args.prev_hash != last_hash { + bail!( + "Previous hash for key operation must be the last hash - prev: {}, last: {}", + args.prev_hash, + last_hash + ) } - let signing_key = self.get_key_at_index(args.signature.key_idx)?; + let key_idx = args.signature.key_idx; + let verifying_key = self.get_key_at_index(key_idx)?; - if self.is_key_revoked(signing_key.clone()) { - bail!("The signing key is revoked"); + if self.is_key_invalid(verifying_key.clone()) { + bail!( + "The key at index {}, intended to verify this operation, is invalid", + key_idx + ); } - operation.verify_user_signature(signing_key) + operation.verify_user_signature(verifying_key) } Operation::AddData(args) => { - if args.prev_hash != self.last_hash() { - bail!("Previous hash for add-data operation must be the last hash") + let last_hash = self.last_hash(); + if args.prev_hash != last_hash { + bail!( + "Previous hash for add-data operation is not equal to the last hash - prev: {}, last: {}", + args.prev_hash, + last_hash + ) } - let signing_key = self.get_key_at_index(args.op_signature.key_idx)?; + let key_idx = args.op_signature.key_idx; + let verifying_key = self.get_key_at_index(key_idx)?; - if self.is_key_revoked(signing_key.clone()) { - bail!("The signing key is revoked"); + if self.is_key_invalid(verifying_key.clone()) { + bail!( + "The key at index {}, intended to verify this operation, is invalid", + key_idx + ); } - operation.verify_user_signature(signing_key) + operation.verify_user_signature(verifying_key) } Operation::CreateAccount(args) => { if !self.entries.is_empty() { - bail!("RegisterService operation must be the first entry"); + bail!("CreateAccount operation must be the first entry"); } if args.prev_hash != Digest::zero() { diff --git a/crates/common/src/test_utils.rs b/crates/common/src/test_utils.rs index 98fe46d2..aa279b82 100644 --- a/crates/common/src/test_utils.rs +++ b/crates/common/src/test_utils.rs @@ -1,5 +1,7 @@ use crate::{ + digest::Digest, hashchain::Hashchain, + hasher::Hasher, keys::{SigningKey, VerifyingKey}, operation::{Operation, ServiceChallenge, SignatureBundle}, tree::{ @@ -50,10 +52,10 @@ impl TestTreeState { pub fn register_service(&mut self, service_id: String) -> Service { let service_key = create_mock_signing_key(); - let hashchain = Hashchain::register_service( + let hashchain = Hashchain::from_operation(Operation::new_register_service( service_id.clone(), ServiceChallenge::from(service_key.clone()), - ) + )) .unwrap(); let key_hash = hashchain.get_keyhash(); @@ -189,12 +191,20 @@ pub fn create_random_insert(state: &mut TestTreeState, rng: &mut StdRng) -> Inse let (_, service) = state.services.iter().nth(rng.gen_range(0..state.services.len())).unwrap(); - let hc = create_new_hashchain(random_string.as_str(), &sk, service.clone()); //Hashchain::new(random_string); - let key = hc.get_keyhash(); + let operation = Operation::new_create_account( + random_string.clone(), + &sk, + service.id.clone(), + &service.sk, + ) + .expect("Creating account operation should succeed"); + + let hashed_id = Digest::hash(&random_string); + let key_hash = KeyHash::with::(hashed_id); - if !state.inserted_keys.contains(&key) { - let proof = state.tree.insert(key, hc).expect("Insert should succeed"); - state.inserted_keys.insert(key); + if !state.inserted_keys.contains(&key_hash) { + let proof = state.tree.insert(key_hash, operation).expect("Insert should succeed"); + state.inserted_keys.insert(key_hash); state.signing_keys.insert(random_string, sk); return proof; } @@ -208,7 +218,7 @@ pub fn create_random_update(state: &mut TestTreeState, rng: &mut StdRng) -> Upda let key = *state.inserted_keys.iter().nth(rng.gen_range(0..state.inserted_keys.len())).unwrap(); - let Found(mut hc, _) = state.tree.get(key).unwrap() else { + let Found(hc, _) = state.tree.get(key).unwrap() else { panic!("No response found for key. Cannot perform update."); }; @@ -229,9 +239,14 @@ pub fn create_random_update(state: &mut TestTreeState, rng: &mut StdRng) -> Upda 0, ) .unwrap(); - hc.perform_operation(operation).expect("Adding to hashchain should succeed"); - state.tree.update(key, hc.last().unwrap().clone()).expect("Update should succeed") + let Proof::Update(update_proof) = + state.tree.process_operation(&operation).expect("Processing operation should succeed") + else { + panic!("No update proof returned."); + }; + + *update_proof } #[cfg(not(feature = "secp256k1"))] diff --git a/crates/common/src/tree.rs b/crates/common/src/tree.rs index d1f1902b..939b864d 100644 --- a/crates/common/src/tree.rs +++ b/crates/common/src/tree.rs @@ -11,7 +11,7 @@ use std::{convert::Into, sync::Arc}; use crate::{ digest::Digest, - hashchain::{Hashchain, HashchainEntry}, + hashchain::Hashchain, hasher::Hasher, operation::{ AddDataArgs, CreateAccountArgs, KeyOperationArgs, Operation, RegisterServiceArgs, @@ -72,23 +72,22 @@ pub struct InsertProof { pub new_root: Digest, pub membership_proof: SparseMerkleProof, - pub value: Hashchain, + pub insertion_op: Operation, } impl InsertProof { pub fn verify(&self) -> Result<()> { self.non_membership_proof.verify().context("Invalid NonMembershipProof")?; - let value = bincode::serialize(&self.value)?; + let hashchain = Hashchain::from_operation(self.insertion_op.clone())?; + let serialized_hashchain = bincode::serialize(&hashchain)?; self.membership_proof.clone().verify_existence( self.new_root.into(), self.non_membership_proof.key, - value, + serialized_hashchain, )?; - self.value.verify_last_entry()?; - Ok(()) } } @@ -99,8 +98,8 @@ pub struct UpdateProof { pub new_root: RootHash, pub key: KeyHash, - pub old_value: Hashchain, - pub new_entry: HashchainEntry, + pub old_hashchain: Hashchain, + pub update_op: Operation, /// Inclusion proof of [`old_value`] pub inclusion_proof: SparseMerkleProof, @@ -112,19 +111,19 @@ impl UpdateProof { pub fn verify(&self) -> Result<()> { // Verify existence of old value. // Otherwise, any arbitrary hashchain could be set - let old_value = bincode::serialize(&self.old_value)?; - self.inclusion_proof.verify_existence(self.old_root, self.key, old_value)?; + let old_serialized_hashchain = bincode::serialize(&self.old_hashchain)?; + self.inclusion_proof.verify_existence(self.old_root, self.key, old_serialized_hashchain)?; + let mut hashchain_after_update = self.old_hashchain.clone(); // Append the new entry and verify it's validity - let new_hashchain = self.old_value.insert_unsafe(self.new_entry.clone()); - new_hashchain.verify_last_entry()?; + hashchain_after_update.perform_operation(self.update_op.clone())?; // Ensure the update proof corresponds to the new hashchain value - let new_value = bincode::serialize(&new_hashchain)?; + let new_serialized_hashchain = bincode::serialize(&hashchain_after_update)?; self.update_proof.clone().verify_update( self.old_root, self.new_root, - vec![(self.key, Some(new_value))], + vec![(self.key, Some(new_serialized_hashchain))], )?; Ok(()) @@ -143,8 +142,8 @@ pub enum HashchainResponse { pub trait SnarkableTree { fn process_operation(&mut self, operation: &Operation) -> Result; - fn insert(&mut self, key: KeyHash, value: Hashchain) -> Result; - fn update(&mut self, key: KeyHash, value: HashchainEntry) -> Result; + fn insert(&mut self, key: KeyHash, initial_op: Operation) -> Result; + fn update(&mut self, key: KeyHash, update_op: Operation) -> Result; fn get(&self, key: KeyHash) -> Result; } @@ -235,27 +234,16 @@ where let hashed_id = Digest::hash(id); let key_hash = KeyHash::with::(hashed_id); - match self.get(key_hash)? { - Found(mut current_chain, _) => { - let new_entry = current_chain.perform_operation(operation.clone())?; - - debug!("updating hashchain for user id {}", id.clone()); - let proof = self.update(key_hash, new_entry.clone())?; + debug!("updating hashchain for user id {}", id.clone()); + let proof = self.update(key_hash, operation.clone())?; - Ok(Proof::Update(Box::new(proof))) - } - NotFound(_) => { - bail!("Failed to get hashchain for ID {}", id) - } - } + Ok(Proof::Update(Box::new(proof))) } Operation::CreateAccount(CreateAccountArgs { id, - value, service_id, challenge, - prev_hash, - signature, + .. }) => { let hashed_id = Digest::hash(id); let account_key_hash = KeyHash::with::(hashed_id); @@ -274,9 +262,9 @@ where bail!("Failed to get hashchain for service ID {}", service_id); }; - let service_last_entry = service_hashchain.last().ok_or(anyhow!( - "Service hashchain is empty, could not retrieve challenge key" - ))?; + let Some(service_last_entry) = service_hashchain.last() else { + bail!("Service hashchain is empty, could not retrieve challenge key"); + }; let creation_gate = match &service_last_entry.operation { Operation::RegisterService(args) => &args.creation_gate, @@ -287,64 +275,34 @@ where let ServiceChallenge::Signed(service_pubkey) = creation_gate; - let new_account_chain = Hashchain::create_account( - id.clone(), - value.clone(), - service_id.clone(), - challenge.clone(), - *prev_hash, - signature.clone(), - )?; - let new_account_entry = new_account_chain.last().unwrap(); - let ServiceChallengeInput::Signed(challenge_signature) = &challenge; service_pubkey.verify_signature( - &bincode::serialize(&new_account_entry.operation.without_challenge())?, + &bincode::serialize(&operation.without_challenge())?, challenge_signature, )?; - value.verify_signature( - &bincode::serialize( - &new_account_entry.operation.without_challenge().without_signature(), - )?, - signature, - )?; - debug!("creating new hashchain for user ID {}", id); - Ok(Proof::Insert(Box::new( - self.insert(account_key_hash, new_account_chain)?, - ))) + let insert_proof = self.insert(account_key_hash, operation.clone())?; + Ok(Proof::Insert(Box::new(insert_proof))) } - Operation::RegisterService(RegisterServiceArgs { - id, creation_gate, .. - }) => { + Operation::RegisterService(RegisterServiceArgs { id, .. }) => { let hashed_id = Digest::hash(id); let key_hash = KeyHash::with::(hashed_id); - // hashchain should not already exist - let NotFound(_) = self.get(key_hash)? else { - bail!(DatabaseError::NotFoundError(format!( - "empty slot for ID {}", - id - ))); - }; - debug!("creating new hashchain for service id {}", id); - let chain = Hashchain::register_service(id.clone(), creation_gate.clone())?; - Ok(Proof::Insert(Box::new( - self.insert(KeyHash::with::(hashed_id), chain)?, - ))) + let insert_proof = self.insert(key_hash, operation.clone())?; + Ok(Proof::Insert(Box::new(insert_proof))) } } } - fn insert(&mut self, key: KeyHash, value: Hashchain) -> Result { - let serialized_value = Self::serialize_value(&value)?; - + fn insert(&mut self, key: KeyHash, insertion_op: Operation) -> Result { let old_root = self.get_current_root()?; - let (old_value, non_membership_merkle_proof) = self.jmt.get_with_proof(key, self.epoch)?; + let (None, non_membership_merkle_proof) = self.jmt.get_with_proof(key, self.epoch)? else { + bail!("Key already exists"); + }; let non_membership_proof = NonMembershipProof { root: old_root.into(), @@ -352,14 +310,13 @@ where key, }; - if old_value.is_some() { - bail!("Key already exists"); - } + let hashchain = Hashchain::from_operation(insertion_op.clone())?; + let serialized_hashchain = Self::serialize_value(&hashchain)?; // the update proof just contains another nm proof let (new_root, _, tree_update_batch) = self .jmt - .put_value_set_with_proof(vec![(key, Some(serialized_value))], self.epoch + 1)?; + .put_value_set_with_proof(vec![(key, Some(serialized_hashchain))], self.epoch + 1)?; self.queue_batch(tree_update_batch); self.write_batch()?; @@ -367,23 +324,24 @@ where Ok(InsertProof { new_root: new_root.into(), - value, + insertion_op, non_membership_proof, membership_proof, }) } - fn update(&mut self, key: KeyHash, new_entry: HashchainEntry) -> Result { + fn update(&mut self, key: KeyHash, update_op: Operation) -> Result { let old_root = self.get_current_root()?; - let (old_value, inclusion_proof) = self.jmt.get_with_proof(key, self.epoch)?; - - if old_value.is_none() { + let (Some(old_serialized_hashchain), inclusion_proof) = + self.jmt.get_with_proof(key, self.epoch)? + else { bail!("Key does not exist"); - } + }; + + let old_hashchain: Hashchain = bincode::deserialize(old_serialized_hashchain.as_slice())?; - let old_value: Hashchain = bincode::deserialize(old_value.unwrap().as_slice())?; - let new_hashchain = old_value.insert_unsafe(new_entry.clone()); - new_hashchain.verify_last_entry()?; + let mut new_hashchain = old_hashchain.clone(); + new_hashchain.perform_operation(update_op.clone())?; let serialized_value = Self::serialize_value(&new_hashchain)?; @@ -398,10 +356,10 @@ where old_root, new_root, inclusion_proof, - old_value, + old_hashchain, key, - new_entry, update_proof, + update_op, }) } @@ -613,7 +571,7 @@ mod tests { tree_state.insert_account(service.registration).unwrap(); let root_before = tree_state.tree.get_current_root().unwrap(); - tree_state.tree.insert(account.key_hash, account.hashchain).unwrap(); + tree_state.insert_account(account).unwrap(); let root_after = tree_state.tree.get_current_root().unwrap(); assert_ne!(root_before, root_after); diff --git a/elf/riscv32im-succinct-zkvm-elf b/elf/riscv32im-succinct-zkvm-elf index bf8c98b7..fcef340f 100755 Binary files a/elf/riscv32im-succinct-zkvm-elf and b/elf/riscv32im-succinct-zkvm-elf differ