From f25c6f27de152c84b0d14025474b0bb9a26cda15 Mon Sep 17 00:00:00 2001 From: NicolasCantu Date: Wed, 12 Mar 2025 13:02:26 +0100 Subject: [PATCH] Update to latest common --- src/api.rs | 236 +++++++++++++++++++++++++---------------------------- 1 file changed, 110 insertions(+), 126 deletions(-) diff --git a/src/api.rs b/src/api.rs index d405551..b9aa2d3 100644 --- a/src/api.rs +++ b/src/api.rs @@ -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::{ - decrypt_with_key, encrypt_with_key, AeadCore, Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD + decrypt_with_key, encrypt_with_key, generate_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}; @@ -63,7 +63,7 @@ use sdk_common::network::{ NewTxMessage, }; use sdk_common::pcd::{ - AnkPcdHash, AnkPcdTag, Member, Pcd, RoleDefinition, ValidationRule, + AnkPcdHash, AnkPcdTag, Member, Pcd, PcdCommitments, RoleDefinition, Roles, ValidationRule }; use sdk_common::prd::{AnkPrdHash, Prd, PrdType}; use sdk_common::silentpayments::{create_transaction, map_outputs_to_sp_address}; @@ -94,7 +94,7 @@ pub struct UserDiff { pub state_id: String, // TODO add a merkle proof that the new_value belongs to that state pub value_commitment: String, pub field: String, - pub roles: BTreeMap, + pub roles: Roles, pub description: Option, pub notify_user: bool, pub need_validation: bool, @@ -108,8 +108,8 @@ pub struct UpdatedProcess { pub process_id: OutPoint, pub current_process: Process, 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 + pub encrypted_data: BTreeMap, // hashes in pcd commitment to ciphers + pub validated_state: Option<[u8; 32]>, // when we add/receive validation proofs for a state } #[derive(Debug, PartialEq, Tsify, Serialize, Deserialize, Default)] @@ -126,15 +126,6 @@ pub struct ApiReturn { pub type ApiResult = Result; -#[derive(Debug, PartialEq, Tsify, Serialize, Deserialize, Default)] -#[tsify(into_wasm_abi)] -#[allow(non_camel_case_types)] -pub struct NewKey { - pub private_key: String, - pub x_only_public_key: String, - pub key_parity: bool -} - const IS_TESTNET: bool = true; const DEFAULT_AMOUNT: Amount = Amount::from_sat(1000); @@ -623,41 +614,41 @@ fn confirm_prd(prd: &Prd, shared_secret: &AnkSharedSecretHash) -> AnyhowResult (), } - let outpoint = OutPoint::from_str(&prd.process_id)?; + let outpoint = prd.process_id; let local_device = lock_local_device()?; let sender = local_device.to_member(); let prd_confirm = Prd::new_confirm(outpoint, sender, prd.pcd_commitments.clone()); - // debug!("Sending confirm prd: {:?}", prd_confirm); - let prd_msg = prd_confirm.to_network_msg(local_device.get_wallet())?; Ok(encrypt_with_key(shared_secret.as_byte_array(), prd_msg.as_bytes())?.to_lower_hex_string()) } fn create_diffs(process: &Process, new_state: &ProcessState) -> AnyhowResult> { - let new_state_commitments = new_state.pcd_commitment.as_object().ok_or(AnyhowError::msg("new_state commitments is not an object"))?; + let new_state_commitments = &new_state.pcd_commitment; let device = lock_local_device()?; let our_id = device.to_member(); let fields_to_validate = new_state.get_fields_to_validate_for_member(&our_id)?; - let new_state_root = &new_state.state_id; + let new_state_id = &new_state.state_id; let new_public_data = &new_state.public_data; let process_id = process.get_process_id()?.to_string(); let mut diffs = vec![]; - for (field, hash) in new_state_commitments { - let description = new_public_data.get(field).map(|d| d.to_string()); + for (field, hash) in new_state_commitments.iter() { + let description_field = new_public_data.get(field); + let has_description = description_field.as_ref().is_some_and(|v| v.is_string()); + let description = if has_description { description_field.unwrap().as_str().map(|s| s.to_owned()) } else { None }; 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(), + state_id: new_state_id.to_lower_hex_string(), + value_commitment: hash.to_lower_hex_string(), field: field.to_owned(), description, notify_user: false, @@ -691,9 +682,8 @@ fn handle_prd_connect(prd: Prd, secret: AnkSharedSecretHash) -> AnyhowResult(&prd.sender)?; let proof = prd.proof.unwrap(); - let actual_sender = sender.get_address_for_key(&proof.get_key()) + let actual_sender = prd.sender.get_address_for_key(&proof.get_key()) .ok_or(anyhow::Error::msg("Signer of the proof is not part of sender"))?; shared_secrets.confirm_secret_for_address(secret, actual_sender.clone().try_into()?); let mut secrets_return = SecretsStore::new(); @@ -704,8 +694,7 @@ fn handle_prd_connect(prd: Prd, secret: AnkSharedSecretHash) -> AnyhowResult(&prd.sender)?; - let actual_sender = sender.get_address_for_key(&proof.get_key()) + let actual_sender = prd.sender.get_address_for_key(&proof.get_key()) .ok_or(anyhow::Error::msg("Signer of the proof is not part of sender"))?; shared_secrets.confirm_secret_for_address(secret, actual_sender.clone().try_into()?); @@ -736,7 +725,7 @@ fn handle_prd( return handle_prd_connect(prd, secret); } - let outpoint = OutPoint::from_str(&prd.process_id)?; + let outpoint = prd.process_id; let mut processes = lock_processes()?; let relevant_process = match processes.entry(outpoint) { @@ -750,7 +739,7 @@ fn handle_prd( match prd.prd_type { 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(); + let update_merkle_root = prd.pcd_commitments.create_merkle_tree()?.root().ok_or(AnyhowError::msg("Invalid merkle tree"))?; if relevant_process.get_state_for_id(&update_merkle_root).is_ok() { // We already know about that state return Err(AnyhowError::msg("Received update for a state we already know")); @@ -786,7 +775,7 @@ fn handle_prd( }); } PrdType::Response => { - let update_state_id = prd.pcd_commitments.create_merkle_tree()?.root().ok_or(AnyhowError::msg("Invalid merkle tree"))?.to_lower_hex_string(); + let update_state_id = prd.pcd_commitments.create_merkle_tree()?.root().ok_or(AnyhowError::msg("Invalid merkle tree"))?; let mut to_update = relevant_process.get_state_for_id_mut(&update_state_id)?; let new_validations = prd.validation_tokens; @@ -819,19 +808,19 @@ fn handle_prd( let updated_state = to_update.clone(); - let validated_state = Some(to_update.state_id.clone()); + let validated_state = Some(to_update.state_id); let mut commit_msg = CommitMessage::new_update_commitment( - OutPoint::from_str(&prd.process_id)?, + prd.process_id, updated_state.pcd_commitment, - updated_state.roles.clone().into_iter().collect(), - updated_state.public_data.clone() + updated_state.roles, + updated_state.public_data ); commit_msg.set_validation_tokens(updated_state.validation_tokens); // We must return an update of the process let updated_process = UpdatedProcess { - process_id: OutPoint::from_str(&prd.process_id)?, + process_id: prd.process_id, current_process: relevant_process.clone(), validated_state, ..Default::default() @@ -846,7 +835,7 @@ fn handle_prd( PrdType::Request => { // We are being requested encrypted data for one or more states, to be uploaded on storage let states: Vec<[u8; 32]> = serde_json::from_str(&prd.payload)?; - let requester: Member = serde_json::from_str(&prd.sender)?; + let requester = prd.sender; // diffs will trigger upload of the encrypted data on storage let mut diffs = vec![]; @@ -855,7 +844,7 @@ fn handle_prd( 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()) { + let state = match relevant_process.get_state_for_id(&state_id) { Ok(state) => state, Err(_) => { debug!("Ignoring request for unknown state {}", state_id.to_lower_hex_string()); @@ -869,7 +858,7 @@ fn handle_prd( let mut relevant_fields: HashSet = HashSet::new(); let shared_secrets = lock_shared_secrets()?; - for (name, role) in &state.roles { + for (name, role) in state.roles.iter() { if !role.members.contains(&requester) { // This role doesn't concern requester continue; @@ -912,21 +901,21 @@ fn handle_prd( ciphers.push(cipher.to_lower_hex_string()); } - let pcd_commitment: HashMap = serde_json::from_value(state.pcd_commitment.clone())?; - for (field, hash) in pcd_commitment { + let pcd_commitment = &state.pcd_commitment; + for (field, hash) in pcd_commitment.iter() { // We only need field that are visible by requester - if !relevant_fields.contains(&field) { + if !relevant_fields.contains(field.as_str()) { continue; } let diff = UserDiff { process_id: outpoint.to_string(), state_id: state_id.to_lower_hex_string(), - value_commitment: hash.clone(), - field, + value_commitment: hash.to_lower_hex_string(), + field: field.to_owned(), ..Default::default() }; diffs.push(diff); - push_to_storage.push(hash); + push_to_storage.push(hash.to_lower_hex_string()); } } @@ -1092,15 +1081,15 @@ pub fn create_connect_transaction(addresses: Vec, fee_rate: u32) -> ApiR #[wasm_bindgen] pub fn create_new_process( - init_state_str: String, + init_state: JsValue, roles: JsValue, public_data: JsValue, relay_address: String, fee_rate: u32, ) -> ApiResult { - let init_state: Value = ::new_from_string(&init_state_str)?; - let roles: BTreeMap = serde_wasm_bindgen::from_value(roles)?; - let public_data: BTreeMap = serde_wasm_bindgen::from_value(public_data)?; + let init_state: Pcd = serde_wasm_bindgen::from_value(init_state)?; + let roles: Roles = serde_wasm_bindgen::from_value(roles)?; + let public_data: Pcd = serde_wasm_bindgen::from_value(public_data)?; // We create a transaction that spends to the relay address let psbt = create_transaction_for_addresses(vec![relay_address.clone()], fee_rate)?; @@ -1122,7 +1111,7 @@ pub fn create_new_process( let new_tx_msg = NewTxMessage::new(serialize(&transaction).to_lower_hex_string(), None); - let mut new_state = ProcessState::new(process_id, init_state.as_object().unwrap().clone(), &public_data, roles.clone())?; + let mut new_state = ProcessState::new(process_id, init_state.clone(), public_data.clone(), roles.clone())?; let pcd_commitment = new_state.pcd_commitment.clone(); @@ -1130,21 +1119,22 @@ pub fn create_new_process( 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); + let all_fields: Vec = init_state.iter().map(|(field, _)| field.clone()).collect(); + let mut fields2keys = BTreeMap::new(); + let mut encrypted_data = BTreeMap::new(); + + let mut rng = thread_rng(); + for (field, plain_value) in init_state.iter() { + let hash = pcd_commitment.get(field).ok_or(anyhow::Error::msg("Missing commitment"))?; + let key = generate_key(&mut rng); + let serialized = serde_json::to_string(plain_value)?; + let cipher = encrypt_with_key(&key, serialized.as_bytes())?; + fields2keys.insert(field.to_owned(), key); + encrypted_data.insert(hash.to_lower_hex_string(), cipher.to_lower_hex_string()); + } 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())?; { @@ -1159,7 +1149,7 @@ pub fn create_new_process( let commit_msg = CommitMessage::new_update_commitment( process_id, pcd_commitment, - roles.into_iter().collect(), + roles, public_data, ); @@ -1183,33 +1173,32 @@ 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: JsValue, + mut process: Process, new_attributes: JsValue, roles: JsValue, new_public_data: JsValue, ) -> ApiResult { - 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)?; - let new_public_data: BTreeMap = serde_wasm_bindgen::from_value(new_public_data)?; - // debug!("{:#?}", process); - // debug!("{:#?}", new_attributes); - // debug!("{:#?}", roles); + let new_attributes: Pcd = serde_wasm_bindgen::from_value(new_attributes)?; + let roles: Roles = serde_wasm_bindgen::from_value(roles)?; + let new_public_data: Pcd = serde_wasm_bindgen::from_value(new_public_data)?; 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 public_data = if new_public_data.len() > 0 { new_public_data } else { prev_state.public_data.clone() }; + let mut prev_public_data = prev_state.public_data.clone(); + for (field, value) in new_public_data.into_iter() { + prev_public_data.insert(field, value); + } - // We expect the whole set of attributes for now, even if value does'nt change since previous state + // We expect the whole set of attributes for now, even if value doesn't 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 mut new_state = ProcessState::new( process.get_process_tip()?, - new_attributes.to_value_object()?, - &public_data, + new_attributes.clone(), + prev_public_data, roles.clone() )?; @@ -1228,20 +1217,18 @@ pub fn update_process( let diffs = create_diffs(&process, &new_state)?; - let all_fields: Vec = new_attributes.as_object().unwrap().into_iter().map(|(field, _)| field.clone()).collect(); - let mut fields2keys = Map::new(); - let mut fields2cipher = Map::new(); - new_attributes.encrypt_fields(&all_fields, &mut fields2keys, &mut fields2cipher); + let all_fields: Vec = new_attributes.iter().map(|(field, _)| field.clone()).collect(); + let mut encrypted_data = BTreeMap::new(); - 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(); + let mut rng = thread_rng(); + for (field, plain_value) in new_attributes.iter() { + let hash = new_state.pcd_commitment.get(field).ok_or(anyhow::Error::msg("Missing commitment"))?; + let key = generate_key(&mut rng); + new_state.keys.insert(field.to_owned(), key); + let serialized = serde_json::to_string(plain_value)?; + let cipher = encrypt_with_key(&key, serialized.as_bytes())?; + encrypted_data.insert(hash.to_lower_hex_string(), cipher.to_lower_hex_string()); + } // Add the new state to the process process.insert_concurrent_state(new_state.clone())?; @@ -1264,7 +1251,7 @@ pub fn update_process( let commit_msg = CommitMessage::new_update_commitment( process_id, new_state.pcd_commitment, - roles.into_iter().collect(), + roles, new_state.public_data, ); @@ -1276,13 +1263,22 @@ pub fn update_process( } #[wasm_bindgen] -pub fn request_data(process_id: String, state_ids: Vec, roles: JsValue) -> ApiResult { +pub fn request_data(process_id: String, state_ids_str: Vec, roles: JsValue) -> 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 roles: Vec> = serde_wasm_bindgen::from_value(roles)?; + let roles: Vec = serde_wasm_bindgen::from_value(roles)?; + + let mut state_ids: Vec<[u8; 32]> = vec![]; + for s in state_ids_str { + if (s.len() == 0 || s == String::from_utf8(Vec::from([0u8; 32])).unwrap()) { continue; } + let state_id: Result<[u8; 32], _> = Vec::from_hex(&s)?.try_into().map_err(|_| ApiError::new("Invalid state id".to_owned())); + if let Ok(state_id) = state_id { + state_ids.push(state_id); + } + } let mut send_to: HashSet = HashSet::new(); for role in roles { @@ -1294,7 +1290,7 @@ pub fn request_data(process_id: String, state_ids: Vec, roles: JsValue) for member in members { for address in member.get_addresses() { if address == local_address { continue }; - send_to.insert(SilentPaymentAddress::try_from(address).unwrap()); + send_to.insert(SilentPaymentAddress::try_from(address)?); } } } @@ -1303,7 +1299,8 @@ pub fn request_data(process_id: String, state_ids: Vec, roles: JsValue) let prd_request = Prd::new_request( process_id, sender, - state_ids.iter().map(|s| Vec::from_hex(s).unwrap().try_into().unwrap()).collect()); + state_ids + ); let prd_msg = prd_request.to_network_msg(sp_wallet)?; @@ -1333,6 +1330,7 @@ pub fn create_update_message( let mut processes = lock_processes()?; let process_id = OutPoint::from_str(&process_id)?; + let state_id: [u8; 32] = Vec::from_hex(&state_id)?.try_into().map_err(|_| ApiError::new("Invalid state_id".to_owned()))?; let process = processes.get_mut(&process_id) .ok_or(ApiError::new("Unknown process".to_owned()))?; @@ -1346,7 +1344,7 @@ pub fn create_update_message( let mut all_members: HashMap> = HashMap::new(); let shared_secrets = lock_shared_secrets()?; - for (name, role) in &update_state.roles { + for (name, role) in update_state.roles.iter() { let fields: Vec = role .validation_rules .iter() @@ -1410,30 +1408,25 @@ pub fn create_update_message( } #[wasm_bindgen] -pub fn validate_state(process_id: String, state_id: String) -> ApiResult { - add_validation_token(process_id, state_id, true) +pub fn validate_state(process: Process, state_id: String) -> ApiResult { + add_validation_token(process, state_id, true) } #[wasm_bindgen] -pub fn refuse_state(process_id: String, state_id: String) -> ApiResult { - add_validation_token(process_id, state_id, false) +pub fn refuse_state(process: Process, state_id: String) -> ApiResult { + add_validation_token(process, state_id, false) } #[wasm_bindgen] -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()?; +pub fn evaluate_state(process_id: String, previous_state: Option, process_state: ProcessState) -> ApiResult { + process_state.is_valid(previous_state.as_ref())?; // We create a commit msg with the valid state 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(), + process_state.roles, process_state.public_data, ); @@ -1443,27 +1436,18 @@ pub fn evaluate_state(process_id: String, previous_state: Option, state: }) } -fn add_validation_token(process_id: String, state_id: String, approval: bool) -> ApiResult { - let mut processes = lock_processes()?; - - let outpoint = OutPoint::from_str(&process_id)?; - - let process = processes.get_mut(&outpoint) - .ok_or(ApiError::new("Unknown process".to_owned()))?; +fn add_validation_token(mut process: Process, state_id: String, approval: bool) -> ApiResult { + let process_id = process.get_process_id()?; + let state_id: [u8; 32] = Vec::from_hex(&state_id)?.try_into().map_err(|_| ApiError::new("Invalid state_id".to_owned()))?; + if state_id == [0u8; 32] { return Err(ApiError::new("Can't validate empty state".to_owned())); } { let update_state: &mut ProcessState = process.get_state_for_id_mut(&state_id)?; - let merkle_root: [u8; 32] = Vec::from_hex(&state_id)? - .try_into() - .map_err( - |_| ApiError::new(format!("Failed to deserialize state_id: {}", state_id)) - )?; - let message_hash = if approval { - AnkHash::ValidationYes(AnkValidationYesHash::from_merkle_root(merkle_root)) + AnkHash::ValidationYes(AnkValidationYesHash::from_merkle_root(state_id)) } else { - AnkHash::ValidationNo(AnkValidationNoHash::from_merkle_root(merkle_root)) + AnkHash::ValidationNo(AnkValidationNoHash::from_merkle_root(state_id)) }; let local_device = lock_local_device()?; @@ -1480,11 +1464,10 @@ fn add_validation_token(process_id: String, state_id: String, approval: bool) -> let update_is_valid = update_state.is_valid(process.get_parent_state(&update_state.commited_in)); if update_is_valid.is_ok() { - let pcd_commitment = update_state.pcd_commitment.clone(); let mut commit_msg = CommitMessage::new_update_commitment( process.get_process_id()?, - pcd_commitment, - update_state.roles.clone().into_iter().collect(), + update_state.pcd_commitment.clone(), + update_state.roles.clone(), update_state.public_data.clone(), ); commit_msg.set_validation_tokens(update_state.validation_tokens.clone()); @@ -1496,13 +1479,13 @@ fn add_validation_token(process_id: String, state_id: String, approval: bool) -> }; let updated_process = UpdatedProcess { - process_id: OutPoint::from_str(&process_id)?, + process_id, current_process: process.clone(), 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)?)?; + let ciphers_to_send = new_response_prd(process_id, process.get_state_for_id_mut(&state_id)?)?; Ok(ApiReturn { updated_process: Some(updated_process), @@ -1515,6 +1498,7 @@ fn add_validation_token(process_id: String, state_id: String, approval: bool) -> #[wasm_bindgen] pub fn create_response_prd(process_id: String, state_id: String) -> ApiResult { let mut processes = lock_processes()?; + let state_id: [u8; 32] = Vec::from_hex(&state_id)?.try_into().map_err(|_| ApiError::new("Invalid state_id".to_owned()))?; let outpoint = OutPoint::from_str(&process_id)?; @@ -1538,7 +1522,7 @@ fn new_response_prd(process_id: OutPoint, update_state: &mut ProcessState) -> An let mut all_members: HashMap> = HashMap::new(); let shared_secrets = lock_shared_secrets()?; - for (name, role) in &update_state.roles { + for (name, role) in update_state.roles.iter() { let fields: Vec = role .validation_rules .iter()