diff --git a/src/api.rs b/src/api.rs index 842c92f..53efb6d 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,6 +1,6 @@ use std::any::Any; use std::borrow::Borrow; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::io::Write; use std::ops::Index; use std::str::FromStr; @@ -20,7 +20,7 @@ use anyhow::Error as AnyhowError; use anyhow::Result as AnyhowResult; use sdk_common::aes_gcm::aead::{Aead, Payload}; use sdk_common::crypto::{ - encrypt_with_key, AeadCore, Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD, + decrypt_with_key, encrypt_with_key, AeadCore, Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD }; use sdk_common::process::{check_tx_for_process_updates, lock_processes, Process, ProcessState}; use sdk_common::signature::{AnkHash, AnkMessageHash, AnkValidationNoHash, AnkValidationYesHash, Proof}; @@ -95,8 +95,6 @@ pub struct UserDiff { pub value_commitment: String, pub field: String, pub description: Option, - pub previous_value: Value, - pub new_value: Value, pub notify_user: bool, pub need_validation: bool, pub validation_status: DiffStatus, @@ -108,8 +106,8 @@ pub struct UserDiff { pub struct UpdatedProcess { pub process_id: OutPoint, pub current_process: Process, - pub up_to_date_roles: HashMap, pub diffs: Vec, // All diffs should have the same state_id + pub encrypted_data: HashMap, // hashes in pcd commitment to ciphers pub validated_state: Option, // when we add/receive validation proofs for a state } @@ -122,6 +120,7 @@ pub struct ApiReturn { pub new_tx_to_send: Option, pub ciphers_to_send: Vec, pub commit_to_send: Option, + pub push_to_storage: Vec, // hash of the requested data, must be in db } pub type ApiResult = Result; @@ -163,6 +162,12 @@ impl From for ApiError { } } +impl From for ApiError { + fn from(value: serde_wasm_bindgen::Error) -> Self { + ApiError::new(value.to_string()) + } +} + impl From for ApiError { fn from(value: SpError) -> Self { ApiError::new(value.to_string()) @@ -509,11 +514,28 @@ pub fn set_process_cache(processes: String) -> ApiResult<()> { let mut cached_processes = lock_processes()?; + if !cached_processes.is_empty() { + // Don't overwrite processes in memory + return Err(ApiError::new("Processes cache is not empty".to_owned())); + } + *cached_processes = parsed_processes?; Ok(()) } +#[wasm_bindgen] +pub fn add_to_process_cache(process_id: String, process: String) -> ApiResult<()> { + let process_id = OutPoint::from_str(&process_id)?; + let process: Process = serde_json::from_str(&process)?; + + let mut cached_processes = lock_processes()?; + + cached_processes.insert(process_id, process); + + Ok(()) +} + #[wasm_bindgen] pub fn reset_shared_secrets() -> ApiResult<()> { let mut shared_secrets = lock_shared_secrets()?; @@ -657,13 +679,11 @@ fn process_transaction( let processes = lock_processes()?; let process = processes.get(&outpoint).unwrap(); let new_state = process.get_latest_commited_state().unwrap(); - let roles = if let Ok(roles) = new_state.encrypted_pcd.extract_roles() { roles } else { HashMap::new() }; let diffs = if let Ok(diffs) = create_diffs(process, new_state) { diffs } else { vec![] }; let updated_process = UpdatedProcess { process_id: outpoint, current_process: process.clone(), - up_to_date_roles: roles, - diffs: diffs, + diffs, ..Default::default() }; let api_return = ApiReturn { @@ -741,75 +761,28 @@ fn create_diffs(process: &Process, new_state: &ProcessState) -> AnyhowResult val, - Err(_) => Map::new() - }; let new_state_descriptions = &new_state.descriptions; let process_id = process.get_process_id()?.to_string(); let mut diffs = vec![]; - if let Some(prev_state) = process.get_latest_commited_state() { - // We first decrypt as much as we can of the prev_state - let clear_prev_state = prev_state.decrypt_pcd()?; - // We just make a diff for values that are different from previous state - for (field, prev_hash) in prev_state.pcd_commitment.as_object().unwrap() { - let description = new_state_descriptions.get(field).map(|d| d.to_string()); - let new_value = if let Some(val) = new_state_decrypted.get(field.as_str()) { val.clone() } else { Value::Null }; - let need_validation = if (is_pairing && field.as_str() == "roles" && new_value != Value::Null) || fields_to_validate.contains(field) { true } else { false }; - if let Some(new_hash) = new_state_commitments.get(field.as_str()) { - if new_hash.as_str() == prev_hash.as_str() { - continue; - } else { - // There's a diff - let previous_value = clear_prev_state.get(field.as_str()).unwrap().clone(); - diffs.push(UserDiff { - process_id: process_id.clone(), - state_id: new_state_root.to_owned(), - value_commitment: new_hash.as_str().unwrap().to_string(), - field: field.to_owned(), - description, - previous_value, - new_value, - notify_user: false, - need_validation, - validation_status: DiffStatus::None, - }); - } - } else { - // We're missing a hash - return Err(AnyhowError::msg(format!("No commitment for field {} in new state", field))); - } - } - } else { - // All fields need a diff - for (field, hash) in new_state_commitments { - let description = new_state_descriptions.get(field).map(|d| d.to_string()); - let new_value = if let Some(val) = new_state_decrypted.get(field.as_str()) { val.clone() } else { Value::Null }; - let need_validation = if (is_pairing && field.as_str() == "roles" && new_value != Value::Null) || fields_to_validate.contains(field) { true } else { false }; - diffs.push(UserDiff { - process_id: process_id.clone(), - state_id: new_state_root.to_owned(), - value_commitment: hash.as_str().unwrap().to_string(), - field: field.to_owned(), - description, - previous_value: Value::Null, - new_value, - notify_user: false, - need_validation, - validation_status: DiffStatus::None, - }); - } + for (field, hash) in new_state_commitments { + let description = new_state_descriptions.get(field).map(|d| d.to_string()); + let need_validation = fields_to_validate.contains(field); + diffs.push(UserDiff { + process_id: process_id.clone(), + state_id: new_state_root.to_owned(), + value_commitment: hash.as_str().unwrap().to_string(), + field: field.to_owned(), + description, + notify_user: false, + need_validation, + validation_status: DiffStatus::None, + }); } Ok(diffs) @@ -904,39 +877,37 @@ fn handle_prd( let commited_in = relevant_process.get_process_tip()?; // Extract the roles from the payload - let proposal_roles: HashMap = serde_json::from_str(&prd.payload)?; + let proposal_roles: BTreeMap = serde_json::from_str(&prd.payload)?; // TODO: check that the role in the prd has the right commitment + // TODO: now that roles is not part of the pcd anymore, we need to think of a way to secure it + // Take the roles from the last validated state + let mut roles = if let Some(last_state) = relevant_process.get_latest_commited_state() { + last_state.roles.clone() + } else { + // We don't have commited state yet, let's take the current roles + proposal_roles + }; + let new_state = ProcessState { commited_in, pcd_commitment: prd.pcd_commitments, state_id: update_merkle_root.clone(), keys: prd.keys, + roles, ..Default::default() }; // Compute the diffs - // At this point we don't have the encrypted values let diffs = create_diffs(&relevant_process, &new_state)?; - // Take the roles from the last validated state - let mut roles = HashMap::new(); - if let Some(last_state) = relevant_process.get_latest_commited_state() { - let decrypted_last_state = last_state.decrypt_pcd()?; - roles = Value::Object(decrypted_last_state).extract_roles()?; - } else { - // We don't have commited state yet, let's take the current roles - roles = proposal_roles; - } - relevant_process.insert_concurrent_state(new_state)?; let updated_process = UpdatedProcess { process_id: outpoint, current_process: relevant_process.clone(), diffs, - up_to_date_roles: roles, ..Default::default() }; @@ -979,14 +950,11 @@ fn handle_prd( let updated_state = to_update.clone(); - let clear_state = to_update.decrypt_pcd()?; - - let roles = Value::Object(clear_state).extract_roles()?; let validated_state = Some(to_update.state_id.clone()); let mut commit_msg = CommitMessage::new_update_commitment( OutPoint::from_str(&prd.process_id)?, updated_state.pcd_commitment, - roles + updated_state.roles.clone().into_iter().collect() ); commit_msg.set_validation_tokens(updated_state.validation_tokens); @@ -1014,6 +982,7 @@ fn handle_prd( let mut diffs = vec![]; // This will notify the requester and provide relevant information if needed let mut ciphers = vec![]; + let mut push_to_storage = vec![]; for state_id in states { let state = match relevant_process.get_state_for_id(&state_id.to_lower_hex_string()) { @@ -1023,10 +992,6 @@ fn handle_prd( continue; } }; - let decrypted_map = state.decrypt_pcd()?; - - let roles = Value::Object(decrypted_map.clone()).extract_roles()?; - let local_device = lock_local_device()?; let sp_wallet = local_device.get_wallet(); @@ -1034,7 +999,7 @@ fn handle_prd( let mut relevant_fields: HashSet = HashSet::new(); let shared_secrets = lock_shared_secrets()?; - for (name, role) in &roles { + for (name, role) in &state.roles { if !role.members.contains(&requester) { // This role doesn't concern requester continue; @@ -1044,15 +1009,6 @@ fn handle_prd( .iter() .flat_map(|rule| rule.fields.clone()) .collect(); - if let Some(no_secret_address) = requester.get_addresses().iter() - .find(|a| shared_secrets.get_secret_for_address(a.as_str().try_into().unwrap()).is_none()) - { - // We ignore it if we don't have a secret with ourselves - if *no_secret_address != local_address { - // for now we return an error to keep it simple - return Err(AnyhowError::msg(format!("No shared secret for all addresses of {:?}\nPlease first connect", requester))); - } - } relevant_fields.extend(fields); } @@ -1062,7 +1018,7 @@ fn handle_prd( let mut full_prd = Prd::new_update( outpoint, sender, - roles, + state.roles.clone().into_iter().collect(), state.keys.clone(), state.pcd_commitment.clone(), ); @@ -1077,8 +1033,9 @@ fn handle_prd( continue; } - // We shouldn't ever have error here since we already checked above - let shared_secret = shared_secrets.get_secret_for_address(sp_address.as_str().try_into()?).unwrap(); + // We shouldn't ever have error here since we're answering a cipher + let shared_secret = shared_secrets.get_secret_for_address(sp_address.as_str().try_into()?) + .ok_or(AnyhowError::msg(format!("No secret for address {}", sp_address.as_str())))?; let cipher = encrypt_with_key(shared_secret.as_byte_array(), prd_msg.as_bytes())?; ciphers.push(cipher.to_lower_hex_string()); @@ -1093,12 +1050,12 @@ fn handle_prd( let diff = UserDiff { process_id: outpoint.to_string(), state_id: state_id.to_lower_hex_string(), - value_commitment: hash, - new_value: decrypted_map[&field].clone(), + value_commitment: hash.clone(), field, ..Default::default() }; diffs.push(diff); + push_to_storage.push(hash); } } @@ -1112,6 +1069,7 @@ fn handle_prd( return Ok(ApiReturn { updated_process, ciphers_to_send: ciphers, + push_to_storage, ..Default::default() }); } @@ -1131,95 +1089,6 @@ fn 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(process_id: 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(&process_id)?; - - 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_state_for_id_mut(&state_id)?; - - // 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 = state_commitments.iter() - .map(|(field, _)| { - (field.clone(), Value::Null) - }) - .collect(); - - 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_state_for_id(&state_id)?; - let diffs = create_diffs(&process, &state)?; - - let udpated_process = UpdatedProcess { - process_id: outpoint, - current_process: process.clone(), - 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 @@ -1353,16 +1222,15 @@ pub fn create_connect_transaction(addresses: Vec, fee_rate: u32) -> ApiR #[wasm_bindgen] pub fn create_new_process( init_state_str: String, + roles: String, descriptions_str: Option, relay_address: String, fee_rate: u32, ) -> ApiResult { let init_state = ::new_from_string(&init_state_str)?; + let roles: BTreeMap = serde_json::from_str(&roles)?; let descriptions = if let Some(d) = descriptions_str { ::new_from_string(&d)? } else { Value::Object(Map::new()) }; - // check that we have a proper roles map - let roles = init_state.extract_roles()?; - // We create a transaction that spends to the relay address let psbt = create_transaction_for_addresses(vec![relay_address.clone()], fee_rate)?; @@ -1379,32 +1247,55 @@ pub fn create_new_process( let transaction = psbt.extract_tx()?; // We now have the outpoint that will serve as id for the whole process - let outpoint = OutPoint::new(transaction.txid(), 0); + let process_id = OutPoint::new(transaction.txid(), 0); - let new_state = ProcessState::new(outpoint, init_state.to_value_object()?, descriptions.to_value_object()?)?; + let new_tx_msg = NewTxMessage::new(serialize(&transaction).to_lower_hex_string(), None); - let mut process = Process::new(outpoint); + let mut new_state = ProcessState::new(process_id, init_state.to_value_object()?, descriptions.to_value_object()?, roles.clone())?; + + let pcd_commitment = new_state.pcd_commitment.clone(); + + let mut process = Process::new(process_id); let diffs = create_diffs(&process, &new_state)?; + let all_fields: Vec = init_state.as_object().unwrap().into_iter().map(|(field, _)| field.clone()).collect(); + let mut fields2keys = Map::new(); + let mut fields2cipher = Map::new(); + init_state.encrypt_fields(&all_fields, &mut fields2keys, &mut fields2cipher); + + new_state.keys = fields2keys; + + let encrypted_data: HashMap = fields2cipher.into_iter() + .map(|(k, v)| { + let hash = new_state.pcd_commitment.get(k).unwrap().to_owned(); + let cipher = v.as_str().unwrap().to_owned(); + (hash.as_str().unwrap().to_owned(), cipher) + }) + .collect(); + process.insert_concurrent_state(new_state.clone())?; { let mut processes = lock_processes()?; // If we already have an entry with this outpoint, something's wrong - if processes.contains_key(&outpoint) { + if processes.contains_key(&process_id) { return Err(ApiError::new("There's already a process for this outpoint".to_owned())); } - processes.insert(outpoint.clone(), process.clone()); + processes.insert(process_id, process.clone()); } - let commit_msg = CommitMessage::new_first_commitment(transaction, new_state.pcd_commitment, roles.clone()); + let commit_msg = CommitMessage::new_update_commitment( + process_id, + pcd_commitment, + roles.into_iter().collect() + ); let updated_process = UpdatedProcess { - process_id: outpoint, + process_id, current_process: process, - up_to_date_roles: roles, - diffs: diffs, + diffs, + encrypted_data, ..Default::default() }; @@ -1412,6 +1303,7 @@ pub fn create_new_process( secrets: Some(secrets_return), commit_to_send: Some(commit_msg), updated_process: Some(updated_process), + new_tx_to_send: Some(new_tx_msg), ..Default::default() }) } @@ -1419,29 +1311,35 @@ pub fn create_new_process( #[wasm_bindgen] /// TODO allow modifications from user that doesn't have read access to all attributes pub fn update_process( - process_id: String, - new_state_str: String, + process: JsValue, + new_attributes: JsValue, + roles: JsValue ) -> ApiResult { - let outpoint = OutPoint::from_str(&process_id)?; + let mut process: Process = serde_wasm_bindgen::from_value(process)?; + let new_attributes: Value = serde_wasm_bindgen::from_value(new_attributes)?; + let roles: BTreeMap = serde_wasm_bindgen::from_value(roles)?; + debug!("{:#?}", process); + // debug!("{:#?}", new_attributes); + // debug!("{:#?}", roles); - let mut processes = lock_processes()?; - let process = processes.get_mut(&outpoint) - .ok_or(ApiError::new("Unknown process".to_owned()))?; + let process_id = process.get_process_id()?; let prev_state = process.get_latest_commited_state() .ok_or(ApiError::new("Process must have at least one state already commited".to_owned()))?; - let last_state_commitments = &prev_state.pcd_commitment; let last_state_descriptions = &prev_state.descriptions; - let clear_new_state = Value::from_str(&new_state_str)?; - - let roles = clear_new_state.extract_roles()?; - // We expect the whole set of attributes for now, even if value does'nt change since previous state // We rehash everything with the new txid, so we need the clear value // eventually we would like to be able to create partial states even if we don't have read access to some attributes - let new_state = ProcessState::new(process.get_process_tip()?, clear_new_state.to_value_object()?, last_state_descriptions.clone())?; + let mut new_state = ProcessState::new( + process.get_process_tip()?, + new_attributes.to_value_object()?, + last_state_descriptions.clone(), + roles.clone() + )?; + + debug!("1"); // We compare the new state with the previous one let last_state_merkle_root = &prev_state.state_id; @@ -1457,19 +1355,48 @@ pub fn update_process( } let diffs = create_diffs(&process, &new_state)?; + debug!("1"); + let all_fields: Vec = new_attributes.as_object().unwrap().into_iter().map(|(field, _)| field.clone()).collect(); + debug!("1"); + let mut fields2keys = Map::new(); + let mut fields2cipher = Map::new(); + new_attributes.encrypt_fields(&all_fields, &mut fields2keys, &mut fields2cipher); + debug!("1"); + + new_state.keys = fields2keys; + + let encrypted_data: HashMap = fields2cipher.into_iter() + .map(|(k, v)| { + let hash = new_state.pcd_commitment.get(k).unwrap().to_owned(); + let cipher = v.as_str().unwrap().to_owned(); + (hash.as_str().unwrap().to_owned(), cipher) + }) + .collect(); + + debug!("1"); // Add the new state to the process process.insert_concurrent_state(new_state.clone())?; + { + // save the process to wasm memory + let mut processes = lock_processes()?; + // To keep it simple, assume that db is true and replace what we have in memory + processes.insert(process_id, process.clone()); + } + let updated_process = UpdatedProcess { - process_id: outpoint, - current_process: process.clone(), - up_to_date_roles: roles.clone(), + process_id, + current_process: process, diffs, + encrypted_data, ..Default::default() }; - let commit_msg = CommitMessage::new_update_commitment(outpoint, new_state.pcd_commitment, roles); + let commit_msg = CommitMessage::new_update_commitment( + process_id, + new_state.pcd_commitment, + roles.into_iter().collect()); Ok(ApiReturn { updated_process: Some(updated_process), @@ -1479,29 +1406,50 @@ pub fn update_process( } #[wasm_bindgen] -pub fn request_data(process_id: String, state_ids: Vec, managers: Vec) -> ApiResult { - let outpoint = OutPoint::from_str(&process_id)?; - let send_to: Vec = managers.iter().flat_map(|m| { - let member: Member = serde_json::from_str(m).unwrap(); - member.get_addresses() - }) - .collect(); - let sender = lock_local_device()?.to_member(); +pub fn request_data(process_id: String, state_ids: Vec) -> ApiResult { + let process_id = OutPoint::from_str(&process_id)?; + let local_device = lock_local_device()?; + let sender = local_device.to_member(); + let sp_wallet = local_device.get_wallet(); + let local_address = sp_wallet.get_client().get_receiving_address(); + + let mut send_to: HashSet = HashSet::new(); + let processes = lock_processes()?; + let process = processes.get(&process_id).ok_or(ApiError::new("Unknown process".to_owned()))?; + for state_id in &state_ids { + if let Ok(state) = process.get_state_for_id(&state_id) { + let roles = &state.roles; + for (_, role_def) in roles { + let members = &role_def.members; + if !members.contains(&sender) { + continue; + } + for member in members { + for address in member.get_addresses() { + if address == local_address { continue }; + send_to.insert(SilentPaymentAddress::try_from(address).unwrap()); + } + } + } + } else { + // Ignoring unknown state + log::error!("Unknown state"); + continue; + } + } let prd_request = Prd::new_request( - outpoint, + process_id, sender, state_ids.iter().map(|s| Vec::from_hex(s).unwrap().try_into().unwrap()).collect()); - let local_device = lock_local_device()?; - let sp_wallet = local_device.get_wallet(); let prd_msg = prd_request.to_network_msg(sp_wallet)?; - // For now, we just send the request to all admins, but this could be refined + // For now, we just send the request to all members we share the data with, but this could be refined let shared_secrets = lock_shared_secrets()?; let mut ciphers = vec![]; for address in send_to { - if let Some(secret) = shared_secrets.get_secret_for_address(address.try_into()?) { + if let Some(secret) = shared_secrets.get_secret_for_address(address) { let cipher = encrypt_with_key(secret.as_byte_array(), prd_msg.as_bytes())?; ciphers.push(cipher.to_lower_hex_string()); } else { @@ -1529,11 +1477,6 @@ pub fn create_update_message( let update_state = process.get_state_for_id(&state_id)?; - // We must have at least the key for the roles field, otherwise we don't know who to send the message to - let clear_state = update_state.decrypt_pcd()?; - - let roles = Value::Object(clear_state).extract_roles()?; - let local_device = lock_local_device()?; let sp_wallet = local_device.get_wallet(); @@ -1541,7 +1484,7 @@ pub fn create_update_message( let mut all_members: HashMap> = HashMap::new(); let shared_secrets = lock_shared_secrets()?; - for (name, role) in &roles { + for (name, role) in &update_state.roles { let fields: Vec = role .validation_rules .iter() @@ -1571,7 +1514,7 @@ pub fn create_update_message( let full_prd = Prd::new_update( process_id, sender, - roles, + update_state.roles.clone().into_iter().collect(), update_state.keys.clone(), update_state.pcd_commitment.clone(), ); @@ -1604,28 +1547,31 @@ pub fn create_update_message( } #[wasm_bindgen] -pub fn validate_state(init_commitment: String, merkle_root_hex: String) -> ApiResult { - add_validation_token(init_commitment, merkle_root_hex, true) +pub fn validate_state(process_id: String, state_id: String) -> ApiResult { + add_validation_token(process_id, state_id, true) } #[wasm_bindgen] -pub fn refuse_state(init_commitment: String, merkle_root_hex: String) -> ApiResult { - add_validation_token(init_commitment, merkle_root_hex, false) +pub fn refuse_state(process_id: String, state_id: String) -> ApiResult { + add_validation_token(process_id, state_id, false) } #[wasm_bindgen] -pub fn evaluate_state(init_commitment: String, previous_state: Option, state: String) -> ApiResult { +pub fn evaluate_state(process_id: String, previous_state: Option, state: String) -> ApiResult { let prev_state: Option = if let Some(s) = previous_state { Some(serde_json::from_str(&s)?) } else { None }; let process_state: ProcessState = serde_json::from_str(&state)?; process_state.is_valid(prev_state.as_ref())?; let clear_pcd = process_state.decrypt_pcd()?; - let roles = Value::Object(clear_pcd).extract_roles()?; // We create a commit msg with the valid state - let outpoint: OutPoint = OutPoint::from_str(&init_commitment)?; - let commit_msg = CommitMessage::new_update_commitment(outpoint, process_state.pcd_commitment, roles); + let outpoint: OutPoint = OutPoint::from_str(&process_id)?; + let commit_msg = CommitMessage::new_update_commitment( + outpoint, + process_state.pcd_commitment, + process_state.roles.clone().into_iter().collect() + ); Ok(ApiReturn { commit_to_send: Some(commit_msg), @@ -1633,21 +1579,21 @@ pub fn evaluate_state(init_commitment: String, previous_state: Option, s }) } -fn add_validation_token(init_commitment: String, merkle_root_hex: String, approval: bool) -> ApiResult { +fn add_validation_token(process_id: String, state_id: String, approval: bool) -> ApiResult { let mut processes = lock_processes()?; - let outpoint = OutPoint::from_str(&init_commitment)?; + let outpoint = OutPoint::from_str(&process_id)?; let process = processes.get_mut(&outpoint) .ok_or(ApiError::new("Unknown process".to_owned()))?; { - let update_state: &mut ProcessState = process.get_state_for_id_mut(&merkle_root_hex)?; + let update_state: &mut ProcessState = process.get_state_for_id_mut(&state_id)?; - let merkle_root: [u8; 32] = Vec::from_hex(&merkle_root_hex)? + let merkle_root: [u8; 32] = Vec::from_hex(&state_id)? .try_into() .map_err( - |_| ApiError::new(format!("Failed to deserialize merkle_root_hex: {}", merkle_root_hex)) + |_| ApiError::new(format!("Failed to deserialize state_id: {}", state_id)) )?; let message_hash = if approval { @@ -1665,27 +1611,16 @@ fn add_validation_token(init_commitment: String, merkle_root_hex: String, approv let commit_msg: Option = { - let update_state = process.get_state_for_id(&merkle_root_hex)?; + let update_state = process.get_state_for_id(&state_id)?; // if the state is valid we also add a commit msg let update_is_valid = update_state.is_valid(process.get_parent_state(&update_state.commited_in)); if update_is_valid.is_ok() { - let roles = match update_state.encrypted_pcd.extract_roles() { - Ok(roles) => roles, - Err(_) => { - let mut fields2plains = Map::new(); - let fields2commit = update_state.pcd_commitment.as_object().ok_or(anyhow::Error::msg("pcd_commitment is not an object"))?; - update_state.encrypted_pcd - .decrypt_all(update_state.commited_in, &fields2commit, &update_state.keys, &mut fields2plains)?; - - Value::Object(fields2plains).extract_roles()? - } - }; let pcd_commitment = update_state.pcd_commitment.clone(); let mut commit_msg = CommitMessage::new_update_commitment( process.get_process_id()?, pcd_commitment, - roles, + update_state.roles.clone().into_iter().collect(), ); commit_msg.set_validation_tokens(update_state.validation_tokens.clone()); Some(commit_msg) @@ -1696,48 +1631,55 @@ fn add_validation_token(init_commitment: String, merkle_root_hex: String, approv }; let updated_process = UpdatedProcess { - process_id: OutPoint::from_str(&init_commitment)?, + process_id: OutPoint::from_str(&process_id)?, current_process: process.clone(), - validated_state: Some(merkle_root_hex), + validated_state: Some(state_id.clone()), ..Default::default() }; + let ciphers_to_send = new_response_prd(OutPoint::from_str(&process_id)?, process.get_state_for_id_mut(&state_id)?)?; + Ok(ApiReturn { updated_process: Some(updated_process), commit_to_send: commit_msg, + ciphers_to_send, ..Default::default() }) } #[wasm_bindgen] -pub fn create_response_prd(init_commitment: String, merkle_root_hex: String) -> ApiResult { +pub fn create_response_prd(process_id: String, state_id: String) -> ApiResult { let mut processes = lock_processes()?; - let outpoint = OutPoint::from_str(&init_commitment)?; + let outpoint = OutPoint::from_str(&process_id)?; let process = processes.get_mut(&outpoint) .ok_or(ApiError::new("Unknown process".to_owned()))?; - let update_state: &mut ProcessState = process.get_state_for_id_mut(&merkle_root_hex)?; + let update_state: &mut ProcessState = process.get_state_for_id_mut(&state_id)?; - // We must have at least the key for the roles field, otherwise we don't know who to send the message to - let clear_state = update_state.decrypt_pcd()?; + let ciphers = new_response_prd(outpoint, update_state)?; - let roles = Value::Object(clear_state).extract_roles()?; + Ok(ApiReturn { + ciphers_to_send: ciphers, + ..Default::default() + }) +} +fn new_response_prd(process_id: OutPoint, update_state: &mut ProcessState) -> AnyhowResult> { let local_device = lock_local_device()?; let sp_wallet = local_device.get_wallet(); let local_address = sp_wallet.get_client().get_receiving_address(); let mut all_members: HashMap> = HashMap::new(); let shared_secrets = lock_shared_secrets()?; - for (name, role) in roles { + for (name, role) in &update_state.roles { let fields: Vec = role .validation_rules .iter() .flat_map(|rule| rule.fields.clone()) .collect(); - for member in role.members { + for member in &role.members { // Check that we have a shared_secret with all members if let Some(no_secret_address) = member.get_addresses().iter() .find(|a| shared_secrets.get_secret_for_address(a.as_str().try_into().unwrap()).is_none()) @@ -1745,7 +1687,7 @@ pub fn create_response_prd(init_commitment: String, merkle_root_hex: String) -> // We ignore it if we don't have a secret with ourselves if *no_secret_address != local_address { // for now we return an error to keep it simple - return Err(ApiError::new(format!("No shared secret for all addresses of {:?}\nPlease first connect", member))); + return Err(AnyhowError::msg(format!("No shared secret for all addresses of {:?}\nPlease first connect", member))); } } if !all_members.contains_key(&member) { @@ -1757,13 +1699,13 @@ pub fn create_response_prd(init_commitment: String, merkle_root_hex: String) -> let our_key = SilentPaymentAddress::try_from(local_address.as_str())?.get_spend_key(); let proof = update_state.validation_tokens.iter().find(|t| t.get_key() == our_key) - .ok_or(ApiError::new("We haven't added our validation token yet".to_owned()))?; + .ok_or(AnyhowError::msg("We haven't added our validation token yet".to_owned()))?; let sender: Member = local_device .to_member(); let response_prd = Prd::new_response( - outpoint, + process_id, sender, vec![*proof], update_state.pcd_commitment.clone(), @@ -1780,17 +1722,15 @@ pub fn create_response_prd(init_commitment: String, merkle_root_hex: String) -> } // We shouldn't ever have error here since we already checked above - let shared_secret = shared_secrets.get_secret_for_address(sp_address.as_str().try_into()?).unwrap(); + let shared_secret = shared_secrets.get_secret_for_address(sp_address.as_str().try_into()?) + .ok_or(AnyhowError::msg("Failed to retrieve secret".to_owned()))?; let cipher = encrypt_with_key(shared_secret.as_byte_array(), prd_msg.as_bytes())?; ciphers.push(cipher.to_lower_hex_string()); } } - Ok(ApiReturn { - ciphers_to_send: ciphers, - ..Default::default() - }) + Ok(ciphers) } #[derive(Tsify, Serialize, Deserialize)] @@ -1821,18 +1761,9 @@ pub fn get_storages(process_outpoint: String) -> ApiResult> { } #[wasm_bindgen] -pub fn is_child_role(parent_role: String, child_role: String) -> ApiResult<()> { - let parent_role_def: Value = serde_json::from_str(&parent_role)?; - let child_role_def: Value = serde_json::from_str(&child_role)?; - let json = json!({ - "roles": parent_role_def - }); - - let parent_roles = json.extract_roles()?; - let json = json!({ - "roles": child_role_def - }); - let child_roles = json.extract_roles()?; +pub fn is_child_role(parent_roles: String, child_roles: String) -> ApiResult<()> { + let parent_roles: BTreeMap = serde_json::from_str(&parent_roles)?; + let child_roles: BTreeMap = serde_json::from_str(&child_roles)?; for (_, child_role) in &child_roles { for child_member in &child_role.members { @@ -1854,13 +1785,7 @@ pub fn is_child_role(parent_role: String, child_role: String) -> ApiResult<()> { #[wasm_bindgen] pub fn roles_contains_us(role: String) -> ApiResult<()> { - let roles: Value = serde_json::from_str(&role)?; - - let json = json!({ - "roles": roles - }); - - let roles = json.extract_roles()?; + let roles: HashMap = serde_json::from_str(&role)?; let device = lock_local_device()?; @@ -1883,13 +1808,7 @@ pub fn roles_contains_us(role: String) -> ApiResult<()> { #[wasm_bindgen] pub fn roles_contains_member(roles: String, member_str: Vec) -> ApiResult<()> { - let roles: Value = serde_json::from_str(&roles)?; - - let json = json!({ - "roles": roles - }); - - let roles = json.extract_roles()?; + let roles: BTreeMap = serde_json::from_str(&roles)?; let addresses: anyhow::Result> = member_str.iter() .map(|s| SilentPaymentAddress::try_from(s.as_str()).map_err(|_| anyhow::Error::msg("Invalid string"))) @@ -1907,13 +1826,7 @@ pub fn roles_contains_member(roles: String, member_str: Vec) -> ApiResul #[wasm_bindgen] pub fn members_in_same_roles_me(roles: String) -> ApiResult> { - let roles: Value = serde_json::from_str(&roles)?; - - let json = json!({ - "roles": roles - }); - - let roles = json.extract_roles()?; + let roles: BTreeMap = serde_json::from_str(&roles)?; let device = lock_local_device()?; @@ -1940,60 +1853,19 @@ pub fn members_in_same_roles_me(roles: String) -> ApiResult> { } #[wasm_bindgen] -pub fn sync_process_from_relay(process_id: String, process_str: String) -> ApiResult { - let outpoint = OutPoint::from_str(&process_id)?; +pub fn decrypt_data(key: &[u8], data: &[u8]) -> ApiResult { + let mut key_buf = [0u8; 32]; - let process: Process = serde_json::from_str(&process_str)?; - - // Are we part of this process? - let last_state = process.get_latest_commited_state().ok_or(ApiError::new("Process doesn't have a commited state".to_owned()))?; - - let roles = last_state.encrypted_pcd.extract_roles()?; - - let us = lock_local_device()?.to_member(); - - let mut contains_us = false; - for (_, role) in &roles { - if role.members.contains(&us) { - contains_us = true; - break; - } + if key.len() != 32 { + return Err(ApiError::new("key must be 32B long".to_owned())); } - let mut processes = lock_processes()?; - if !contains_us { - // If we're not part of this process, we just keep the minimum of public informations - processes.insert(outpoint, process.clone()); + key_buf.copy_from_slice(key); - let updated_process = Some(UpdatedProcess { - process_id: outpoint, - current_process: process, - up_to_date_roles: roles, - ..Default::default() - }); + debug!("{}", key_buf.as_hex()); - let api_return = ApiReturn { - updated_process, - ..Default::default() - }; - Ok(api_return) - } else { - // If we're part of this process, we want to complete what we are missing - let api_return: ApiReturn = if let Some(in_memory_process) = processes.get(&outpoint) { - debug!("We already know about this process"); - // We already know about this process - // For each state, do we have the keys? the encrypted values? - for state in in_memory_process.get_all_states_iter() { - debug!("parsing state {:?}", state); - } - ApiReturn::default() - } else { - debug!("We don't know about this process"); - // We didn't know about this process - // We simply request all the attributes from each state - ApiReturn::default() - }; + // decrypt the data + let clear = decrypt_with_key(&key_buf, data)?; - Ok(api_return) - } + Ok(String::from_utf8(clear)?) }