From 3ed94c7538e75e3419217455bb2e214f8c76346a Mon Sep 17 00:00:00 2001 From: Sosthene Date: Wed, 11 Dec 2024 23:34:00 +0100 Subject: [PATCH] Update parse_cipher for prd update --- src/api.rs | 184 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 110 insertions(+), 74 deletions(-) diff --git a/src/api.rs b/src/api.rs index b844315..151683d 100644 --- a/src/api.rs +++ b/src/api.rs @@ -893,36 +893,37 @@ fn handle_prd( ..Default::default() }) } - PrdType::Update | PrdType::TxProposal | PrdType::Message => { - // Those all have some new data we don't know about yet - // We send a Confirm to get the pcd - // Add the prd to our list of actions for this process - relevant_process.insert_impending_request(prd.clone()); - let member: Member = serde_json::from_str(&prd.sender)?; - let mut ciphers = vec![]; - for address in member.get_addresses() { - if let Some(shared_secret) = lock_shared_secrets()?.get_secret_for_address(address.as_str().try_into()?) { - let cipher = confirm_prd(&prd, &shared_secret)?; - ciphers.push(cipher); - } else { - // For now we don't fail if we're missing an address for a member but maybe we should - warn!("Failed to find secret for address {}", address); - } + PrdType::Update => { + // Compute the merkle tree root for the proposed new state to see if we already know about it + let update_merkle_root = prd.pcd_commitments.create_merkle_tree()?.root().ok_or(AnyhowError::msg("Invalid merkle tree"))?.to_lower_hex_string(); + if relevant_process.get_state_for_commitments_root(&update_merkle_root).is_ok() { + // We already know about that state + return Err(AnyhowError::msg("Received update for a state we already know")); } - // This should never happen - if ciphers.is_empty() { - return Err(anyhow::Error::msg(format!("No available secrets for member {:?}", member))); - } + let new_state = ProcessState { + commited_in: OutPoint::from_str(&prd.root_commitment)?, + pcd_commitment: prd.pcd_commitments, + merkle_root: update_merkle_root.clone(), + keys: prd.keys, + ..Default::default() + }; + + // Compute the diffs + // At this point we don't have the encrypted values + // But it can still be useful to track diffs + let diffs = create_diffs(&relevant_process, &new_state)?; + + relevant_process.insert_concurrent_state(new_state); let updated_process = UpdatedProcess { commitment_tx: outpoint, current_process: relevant_process.clone(), + new_diffs: diffs, ..Default::default() }; return Ok(ApiReturn { - ciphers_to_send: ciphers, updated_process: Some(updated_process), ..Default::default() }); @@ -936,9 +937,6 @@ fn handle_prd( }) .ok_or(anyhow::Error::msg("Original request not found"))?; - // Once we found the prd update, we can add the received proofs as validation tokens - let previous_state = to_update.clone(); - to_update .validation_tokens .extend(prd.validation_tokens); @@ -949,7 +947,7 @@ fn handle_prd( let updated_process = UpdatedProcess { commitment_tx: OutPoint::from_str(&prd.root_commitment)?, current_process: relevant_process.clone(), - modified_state: Some((previous_state, updated_state.clone())), + modified_state: Some(updated_state.merkle_root), ..Default::default() }; @@ -962,54 +960,6 @@ fn handle_prd( } } -fn handle_pcd(pcd: Value) -> AnyhowResult { - // We received an encrypted pcd, so we can compute the merkle root of a tree where all the encrypted values are the leaves - // We pass an empty Outpoint as salt, as there's no point salting hash of encrypted values - let encrypted_pcd_commitments = pcd.hash_all_fields(OutPoint::null())?; - let encrypted_pcd_root = ::create_merkle_tree(&Value::Object(encrypted_pcd_commitments))?.root().unwrap().to_lower_hex_string(); - let mut processes = lock_processes()?; - let updated_prd: Prd; - for (outpoint, process) in processes.iter_mut() { - // We check all pending requests and match the payload with the hash of this pcd - if let Some(prd) = process - .get_impending_requests_mut() - .into_iter() - .find(|r| *r.payload == encrypted_pcd_root) - { - // We update the process and return it - prd.payload = pcd.to_string(); - updated_prd = prd.clone(); - // We can now safely mark the prd to be remove from the process - prd.prd_type = PrdType::None; - } else { - continue; - } - let new_state = ProcessState { - commited_in: *outpoint, - pcd_commitment: updated_prd.pcd_commitments, - encrypted_pcd: pcd, - keys: updated_prd.keys, - validation_tokens: vec![] - }; - process.insert_concurrent_state(new_state.clone())?; - process.prune_impending_requests(); - - let udpated_process = UpdatedProcess { - commitment_tx: *outpoint, - current_process: process.clone(), - new_state: Some(new_state), - ..Default::default() - }; - - return Ok(ApiReturn { - updated_process: Some(udpated_process), - ..Default::default() - }); - } - - Err(anyhow::Error::msg("Failed to find matching prd")) -} - fn handle_decrypted_message( secret: AnkSharedSecretHash, plain: Vec, @@ -1017,13 +967,99 @@ fn handle_decrypted_message( let local_address: SilentPaymentAddress = lock_local_device()?.get_wallet().get_client().get_receiving_address().try_into()?; if let Ok(prd) = Prd::extract_from_message(&plain, local_address) { handle_prd(prd, secret) - } else if let Ok(pcd) = Value::from_str(&String::from_utf8(plain)?) { - handle_pcd(pcd) } else { Err(anyhow::Error::msg("Failed to handle decrypted message")) } } +#[wasm_bindgen] +/// Use the provided Map to update a state +/// The map uses hash commitment as keys, as in storage +pub fn update_process_state(init_commitment: String, state_id: String, hash2values: String) -> ApiResult { + let hash2values_map = serde_json::from_str::(&hash2values)?.to_value_object()?; + + // Get the process + let outpoint = OutPoint::from_str(&init_commitment)?; + + let mut processes = lock_processes()?; + { + // First a mutable borrow of the process + let process = processes.get_mut(&outpoint) + .ok_or(ApiError::new("Unknown process".to_owned()))?; + + // Get the state + let state = process.get_latest_concurrent_states_mut()? + .into_iter() + .find(|state| state.merkle_root == state_id) + .ok_or(ApiError::new("Unknown state".to_owned()))?; + + // Update each value + // Check if there's already something + // If we have the key, decrypt and compare to the commitment + if state.encrypted_pcd.as_object().is_some() && !state.encrypted_pcd.as_object().unwrap().is_empty() { + return Err(ApiError::new("State already existing".to_owned())); + } + let state_commitments = state.pcd_commitment.to_value_object()?; + let mut new_encrypted_pcd: Map = Map::with_capacity(hash2values_map.len()); + + for (hash, value) in hash2values_map { + // Check the hash in pcd_commitment, get the corresponding field name + let (field, _) = state_commitments.iter().find(|(field, commitment)| *hash == **commitment) + .ok_or(ApiError::new(format!("Failed to find the commitment {}", hash)))?; + + new_encrypted_pcd.insert(field.clone(), value); + } + + // decrypt all we can and check it matches the commitment + state.encrypted_pcd = Value::Object(new_encrypted_pcd); + let commited_in = serialize(&state.commited_in); + + let clear_pcd = state.decrypt_pcd()?; + + for (i, (key, value)) in clear_pcd.iter().enumerate() { + // hash each value, and check the result against commitments + if let Some(expected) = state_commitments.get(key.as_str()) { + // value can already be the commitment, if we don't have the encryption key + if value.is_hex_string(Some(32)).is_ok() { + // check if the clear value is the commitment + if expected.as_str().unwrap() == value.as_str().unwrap() { continue; } + } + // Otherwise we hash the value whatever it is, it must match the commitment + let mut value_bin = value.to_string().into_bytes(); + value_bin.push(i.try_into().unwrap()); + let tagged_hash = AnkPcdHash::from_value_with_outpoint(&value_bin, &commited_in); + if tagged_hash.as_byte_array().to_lower_hex_string() != expected.as_str().unwrap() { + // We set the encrypted pcd back to empty + state.encrypted_pcd = Value::Object(Map::new()); + return Err(ApiError::new(format!("Retrieved value for {} doesn't match the commitment", key))); + } + } else { + // This shouldn't be possible + state.encrypted_pcd = Value::Object(Map::new()); + return Err(ApiError::new(format!("Missing commitment for key {}", key))); + } + } + } + + // If every value we can decrypt is valid, then we return the new state and diffs + // We borrow it again immutably + let process = processes.get(&outpoint).unwrap(); + let state = process.get_latest_concurrent_states()?.into_iter().find(|s| s.merkle_root == state_id).unwrap(); + let diffs = create_diffs(&process, &state)?; + + let udpated_process = UpdatedProcess { + commitment_tx: outpoint, + current_process: process.clone(), + new_diffs: diffs, + ..Default::default() + }; + + Ok(ApiReturn { + updated_process: Some(udpated_process), + ..Default::default() + }) +} + #[wasm_bindgen] pub fn parse_cipher(cipher_msg: String) -> ApiResult { // Check that the cipher is not empty or too long