diff --git a/src/api.rs b/src/api.rs index 24088a9..492071e 100644 --- a/src/api.rs +++ b/src/api.rs @@ -85,7 +85,7 @@ pub struct ApiReturn { pub new_tx_to_send: Option, pub ciphers_to_send: Vec, pub commit_to_send: Option, - pub decrypted_pcds: Vec, + pub decrypted_pcds: HashMap, } pub type ApiResult = Result; @@ -779,7 +779,11 @@ fn handle_prd( std::collections::hash_map::Entry::Occupied(entry) => entry.into_mut(), std::collections::hash_map::Entry::Vacant(entry) => { debug!("Creating new process for outpoint: {}", outpoint); - entry.insert(Process::new(vec![], vec![])) + let empty_state = ProcessState { + commited_in: outpoint, + ..Default::default() + }; + entry.insert(Process::new(vec![empty_state], vec![])) } }; @@ -788,23 +792,22 @@ fn handle_prd( // It must match a prd we sent previously // We send the whole data in a pcd debug!("Received confirm prd {:#?}", prd); - let original_request = relevant_process - .get_impending_requests() + let relevant_state = relevant_process + .get_latest_concurrent_states()? .into_iter() - .find(|r| { - if r.prd_type != PrdType::Update { - return false; - } - r.pcd_commitments == prd.pcd_commitments + .find(|state| { + state.pcd_commitment == prd.pcd_commitments }) .ok_or(anyhow::Error::msg("Original request not found"))?; + let member: Member = serde_json::from_str(&prd.sender)?; // We send the data to all addresses of the member we know a secret for 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 = encrypt_with_key(shared_secret.as_byte_array(), prd.payload.as_bytes()); + let cipher = encrypt_with_key(shared_secret.as_byte_array(), serde_json::to_string(&relevant_state.encrypted_pcd)?.as_bytes())?; + ciphers.push(cipher.to_lower_hex_string()); } 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); @@ -831,6 +834,7 @@ fn handle_prd( 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); @@ -880,26 +884,33 @@ 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 - // Like the sender of the update did // We pass an empty Outpoint as salt, as there's no point salting hash of encrypted values let encrypted_pcd_commitments = pcd.hash_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 == pcd.to_string()) + .find(|r| *r.payload == encrypted_pcd_root) { // We update the process and return it prd.payload = pcd.to_string(); - return Ok(ApiReturn { - updated_process: Some((outpoint.to_string(), process.clone())), - ..Default::default() - }); + updated_prd = prd.clone(); + // We can now safely mark the prd to be remove from the process + prd.prd_type = PrdType::None; } else { continue; } + debug!("Updating process states with {:#?}", updated_prd); + process.insert_state(&updated_prd)?; + process.prune_impending_requests(); + return Ok(ApiReturn { + updated_process: Some((outpoint.to_string(), process.clone())), + ..Default::default() + }); } Err(anyhow::Error::msg("Failed to find matching prd")) @@ -1135,10 +1146,22 @@ pub fn update_process( let last_state = process.get_latest_commited_state() .ok_or(ApiError::new("Process must have at least one state already commited".to_owned()))?; - let last_state_encrypted_val = &last_state.encrypted_pcd; + let last_state_commitments = &last_state.pcd_commitment; let new_state_val = Value::from_str(&new_state)?; + // We hash all the new values + let pcd_commitment = new_state_val.hash_fields(outpoint)?; + let new_state_merkle_root = ::create_merkle_tree(&Value::Object(pcd_commitment))?.root().unwrap(); + + // We compare the new state with the previous one + let last_state_merkle_root = ::create_merkle_tree(last_state_commitments)?.root().unwrap(); + + if last_state_merkle_root == new_state_merkle_root { + return Err(ApiError::new("new proposed state is identical to the previous commited state".to_owned())); + } + + // We create the encrypted pcd let mut fields2keys = Map::new(); let mut fields2cipher = Map::new(); let fields_to_encrypt: Vec = new_state_val @@ -1149,18 +1172,25 @@ pub fn update_process( .collect(); new_state_val.encrypt_fields(&fields_to_encrypt, &mut fields2keys, &mut fields2cipher); - // TODO what are the actual differences? - if *last_state_encrypted_val == new_state_val { - return Err(ApiError::new("New state is identical to last state".to_owned())); - } + // We create an encrypted values merkle root + let new_state_encrypted_commitments = Value::Object(fields2cipher).hash_fields(OutPoint::null())?; + let new_state_encrypted_root = ::create_merkle_tree(&Value::Object(new_state_encrypted_commitments.clone()))?.root().unwrap(); - let mut to_update = process.get_latest_state().unwrap().clone(); // This is an empty state with `commited_in` set as the last unspent output + let to_update = process.get_latest_state().unwrap(); // This is an empty state with `commited_in` set as the last unspent output - to_update.encrypted_pcd = Value::Object(fields2cipher); - to_update.keys = fields2keys; + let device = lock_local_device()?; + let sender = device.to_member(); + + let update_prd = Prd::new_update( + outpoint, + serde_json::to_string(&sender)?, + new_state_encrypted_root.to_lower_hex_string(), + fields2keys, + Value::Object(new_state_encrypted_commitments), + ); // Add the new state to the process - process.insert_state(to_update); + process.insert_state(&update_prd); Ok(ApiReturn { updated_process: Some((init_commitment, process.clone())), @@ -1185,7 +1215,7 @@ pub fn create_update_message( let new_state_commitments = ::from_string(&pcd_commitment)?; let update_state: &ProcessState; - if let Some(state) = latest_states.into_iter().find(|state| state.encrypted_pcd == new_state_commitments) + if let Some(state) = latest_states.into_iter().find(|state| state.pcd_commitment == new_state_commitments) { update_state = state; } else { @@ -1195,9 +1225,13 @@ pub fn create_update_message( // 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().as_object().unwrap().clone(); - // debug!("clear_state: {:#?}", clear_state); let roles = Value::Object(clear_state).extract_roles()?; + 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 { @@ -1207,12 +1241,16 @@ pub fn create_update_message( .flat_map(|rule| rule.fields.clone()) .collect(); for member in role.members { + debug!("member: {:?}", member); // Check that we have a shared_secret with all members - if member.get_addresses().iter() - .any(|a| shared_secrets.get_secret_for_address(a.as_str().try_into().unwrap()).is_none()) + 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()) { - // 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))); + // 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))); + } } if !all_members.contains_key(&member) { all_members.insert(member.clone(), HashSet::new()); @@ -1221,18 +1259,13 @@ pub fn create_update_message( } } - let local_device = lock_local_device()?; - - let sp_wallet = local_device.get_wallet(); - let local_address = sp_wallet.get_client().get_receiving_address(); - let sender: Member = local_device .to_member(); // To allow the recipient to identify the pcd that contains only encrypted values, we compute the merkle tree of the encrypted pcd // we then put the root in the payload of the prd update let encrypted_pcd_hash = update_state.encrypted_pcd.hash_fields(OutPoint::null())?; - let encrypted_pcd_merkle_root = ::create_merkle_tree(Value::Object(encrypted_pcd_hash))?.root().unwrap(); + let encrypted_pcd_merkle_root = ::create_merkle_tree(&Value::Object(encrypted_pcd_hash))?.root().unwrap(); let full_prd = Prd::new_update( outpoint, @@ -1262,6 +1295,11 @@ pub fn create_update_message( ciphers.push(cipher.to_lower_hex_string()); } } + + if ciphers.is_empty() { + return Err(ApiError::new("Empty ciphers list".to_owned())); + } + process.insert_impending_request(full_prd); Ok(ApiReturn { @@ -1271,6 +1309,116 @@ pub fn create_update_message( }) } +#[wasm_bindgen] +pub fn create_response_message(init_commitment: String, pcd_commitment: String, approval: bool) -> ApiResult { + let mut processes = lock_processes()?; + + let outpoint = OutPoint::from_str(&init_commitment)?; + + let process = processes.get_mut(&outpoint) + .ok_or(ApiError::new("Unknown process".to_owned()))?; + + let latest_states = process.get_latest_concurrent_states_mut()?; + // This is a map of keys to hash of the clear values + let new_state_commitments = ::from_string(&pcd_commitment)?; + + let update_state: &mut ProcessState; + if let Some(state) = latest_states.into_iter().find(|state| state.pcd_commitment == new_state_commitments) + { + update_state = state; + } else { + return Err(ApiError::new("Can't find the state to update".to_owned())); + } + + // 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().as_object().unwrap().clone(); + + let roles = Value::Object(clear_state).extract_roles()?; + + 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 { + let fields: Vec = role + .validation_rules + .iter() + .flat_map(|rule| rule.fields.clone()) + .collect(); + for member in role.members { + debug!("member: {:?}", member); + // 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()) + { + // 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))); + } + } + if !all_members.contains_key(&member) { + all_members.insert(member.clone(), HashSet::new()); + } + all_members.get_mut(&member).unwrap().extend(fields.clone()); + } + } + + let sender: Member = local_device + .to_member(); + + let encrypted_pcd_hash = update_state.encrypted_pcd.hash_fields(OutPoint::null())?; + let encrypted_pcd_merkle_root = ::create_merkle_tree(&Value::Object(encrypted_pcd_hash))?.root().unwrap(); + + let root = ::create_merkle_tree(&new_state_commitments)?.root().unwrap(); + let message_hash = if approval { + AnkHash::ValidationYes(AnkValidationYesHash::from_byte_array(root)) + } else { + AnkHash::ValidationNo(AnkValidationNoHash::from_byte_array(root)) + }; + let proof = Proof::new(message_hash, sp_wallet.get_client().get_spend_key().try_into()?); + + let response_prd = Prd::new_response( + outpoint, + serde_json::to_string(&sender)?, + vec![], + new_state_commitments + ); + let prd_msg = response_prd.to_network_msg(sp_wallet)?; + + let mut ciphers = vec![]; + for (member, visible_fields) in all_members { + let addresses = member.get_addresses(); + for sp_address in addresses.into_iter() { + // We skip our own device address, no point sending ourself a cipher + if sp_address == local_address { + 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(); + + let cipher = encrypt_with_key(shared_secret.as_byte_array(), prd_msg.as_bytes())?; + ciphers.push(cipher.to_lower_hex_string()); + } + } + + if ciphers.is_empty() { + return Err(ApiError::new("Empty ciphers list".to_owned())); + } + + update_state.validation_tokens.push(proof); + + Ok(ApiReturn { + updated_process: Some((outpoint.to_string(), process.clone())), + ciphers_to_send: ciphers, + ..Default::default() + }) +} + #[derive(Tsify, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] #[allow(non_camel_case_types)] @@ -1304,77 +1452,33 @@ pub fn get_update_proposals(process_outpoint: String) -> ApiResult { .get(&outpoint) .ok_or(ApiError::new("process not found".to_owned()))?; - let mut updated_process = relevant_process.clone(); - - let update_proposals: Vec<&Prd> = relevant_process - .get_impending_requests() - .into_iter() - .filter(|r| r.prd_type == PrdType::Update) - .collect(); - - if update_proposals.is_empty() { - return Err(ApiError::new(format!( - "No active update proposals for process {}", - process_outpoint - ))); - } - - let mut decrypted_pcds = vec![]; + let mut decrypted_pcds = HashMap::new(); // We first push the last commited state, if any match relevant_process.get_latest_commited_state() { Some(state) => { let mut decrypted_pcd = Map::new(); state.encrypted_pcd.decrypt_fields(&state.keys, &mut decrypted_pcd); - decrypted_pcds.push(Value::Object(decrypted_pcd)); + let root = ::create_merkle_tree(&state.pcd_commitment)?.root().unwrap(); + decrypted_pcds.insert(root.to_lower_hex_string(), Value::Object(decrypted_pcd)); } None => () } - // Maybe that's the right place for adding a new state with what we've got from the prd update - // We should probably iterate on every update proposals and see which one don't have a state yet - let mut update_states = false; - for proposal in update_proposals { - // Is there a state that matches this proposal? If not, let's add it - debug!("Trying proposal {:#?}", proposal); - let pcd = match Value::from_str(&proposal.payload) { - Ok(value) => value, - Err(e) => continue - }; - debug!("found pcd {:#?}", pcd); - let pcd_hash = AnkPcdHash::from_value(&pcd); - // We look for a pending state for the exact same state as the one in the proposal - if let None = relevant_process.get_latest_concurrent_states()? - .into_iter() - .find(|state| { - state.pcd_commitment == proposal.pcd_commitments - }) - { - // If not, we first add a new state - updated_process.insert_state(ProcessState { - commited_in: OutPoint::new(Txid::from_str(&pcd_hash.to_string())?, u32::MAX), - encrypted_pcd: pcd.clone(), - keys: proposal.keys.clone(), - validation_tokens: proposal.validation_tokens.clone(), - pcd_commitment: proposal.pcd_commitments.clone() - }); - update_states = true; + for state in relevant_process.get_latest_concurrent_states()? { + if state.encrypted_pcd == Value::Null { + // This is the last empty state, ignore it + continue; } - // We add the decrypted state to our return variable + let mut decrypted_pcd = Map::new(); - pcd.decrypt_fields(&proposal.keys, &mut decrypted_pcd)?; - decrypted_pcds.push(Value::Object(decrypted_pcd)); + state.encrypted_pcd.decrypt_fields(&state.keys, &mut decrypted_pcd)?; + let root = ::create_merkle_tree(&state.pcd_commitment)?.root().unwrap(); + decrypted_pcds.insert(root.to_lower_hex_string(), Value::Object(decrypted_pcd)); } - let mut res = ApiReturn::default(); - if update_states { - // We replace the process - processes.insert(outpoint, updated_process.clone()); - res.updated_process = Some((outpoint.to_string(), updated_process)); - } - // else we do nothing - - res.decrypted_pcds = decrypted_pcds; - - Ok(res) + Ok(ApiReturn { + decrypted_pcds, + ..Default::default() + }) } diff --git a/tests/pairing.rs b/tests/pairing.rs index 3eab488..2d04431 100644 --- a/tests/pairing.rs +++ b/tests/pairing.rs @@ -2,19 +2,15 @@ use std::collections::HashMap; use std::str::FromStr; use sdk_client::api::{ - add_validation_token_to_prd, create_commit_message, create_connect_transaction, create_device_from_sp_wallet, create_update_message, dump_device, dump_process_cache, get_address, get_outputs, get_update_proposals, pair_device, parse_cipher, reset_device, response_prd, restore_device, set_process_cache, setup, ApiReturn + create_device_from_sp_wallet, create_new_process, create_response_message, create_update_message, dump_device, get_address, get_update_proposals, pair_device, parse_cipher, reset_device, restore_device, set_process_cache, set_shared_secrets, setup }; -use sdk_common::log::{debug, info}; -use sdk_common::pcd::{Member, RoleDefinition}; -use sdk_common::sp_client::bitcoin::consensus::deserialize; -use sdk_common::sp_client::bitcoin::hex::FromHex; -use sdk_common::sp_client::bitcoin::{OutPoint, Transaction}; -use sdk_common::sp_client::spclient::OwnedOutput; -use sdk_common::sp_client::silentpayments::utils::SilentPaymentAddress; +use sdk_common::crypto::AnkSharedSecretHash; +use sdk_common::log::debug; +use sdk_common::pcd::{Member, Pcd}; +use sdk_common::sp_client::bitcoin::hex::DisplayHex; use sdk_common::secrets::SecretsStore; use serde_json::{json, Value}; -use tsify::JsValueSerdeExt; use wasm_bindgen_test::*; mod utils; @@ -93,6 +89,8 @@ wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] fn test_pairing() { + const RELAY_ADDRESS: &str = "tsp1qqvfm6wvd55r68ltysdhmagg7qavxrzlmm9a7tujsp8qqy6x2vr0muqajt5p2jdxfw450wyeygevypxte29sxlxzgprmh2gwnutnt09slrcqqy5h4"; + setup(); let mut alice_process_cache = HashMap::new(); let mut bob_process_cache = HashMap::new(); @@ -107,14 +105,18 @@ fn test_pairing() { // we get our own address let alice_address = get_address().unwrap(); + debug!("alice address: {}", alice_address); // we scan the qr code or get the address by any other means let bob_address = helper_get_bob_address(); + debug!("bob_address: {}", bob_address); // we add some shared_secret in both secrets_store - let shared_secret = "c3f1a64e15d2e8d50f852c20b7f0b47cbe002d9ef80bc79582d09d6f38612d45"; - alice_secrets_store.confirm_secret_for_address(shared_secret, bob_address.try_into().unwrap()); - bob_secrets_store.confirm_secret_for_address(shared_secret, alice_address.try_into().unwrap()); + let shared_secret = AnkSharedSecretHash::from_str("c3f1a64e15d2e8d50f852c20b7f0b47cbe002d9ef80bc79582d09d6f38612d45").unwrap(); + alice_secrets_store.confirm_secret_for_address(shared_secret, bob_address.as_str().try_into().unwrap()); + bob_secrets_store.confirm_secret_for_address(shared_secret, alice_address.as_str().try_into().unwrap()); + + set_shared_secrets(serde_json::to_string(&alice_secrets_store).unwrap()).unwrap(); // Alice creates the new member with Bob address let new_member = Member::new(vec![ @@ -127,6 +129,10 @@ fn test_pairing() { let initial_session_pubkey = [0u8; 32]; let pairing_init_state = json!({ + "html": "", + "js": "", + "style": "", + "zones": [], "description": "AliceBob", "roles": { "owner": { @@ -155,48 +161,63 @@ fn test_pairing() { "key_parity": true, // This allows us to use a 32 bytes array in serialization }); + let create_process_return = create_new_process(pairing_init_state.to_string(), RELAY_ADDRESS.to_owned(), 1).unwrap(); + + let commit_msg = create_process_return.commit_to_send.unwrap(); + alice_secrets_store = create_process_return.secrets; + let (outpoint, new_process) = create_process_return.updated_process.unwrap(); + alice_process_cache.insert(outpoint.clone(), new_process); + + // We send the commit_msg to the relay we got the address from + + // now we create prd update for this new process + let create_update_return = create_update_message(outpoint, commit_msg.pcd_commitment.to_string()).unwrap(); + + let (root_outpoint, alice_init_process) = create_update_return.updated_process.unwrap(); + alice_process_cache.insert(root_outpoint.clone(), alice_init_process); + debug!("Alice pairs her device"); // we can update our local device now, first with an empty txid - pair_device(OutPoint::null().to_string(), vec![helper_get_bob_address()]).unwrap(); + pair_device(root_outpoint, vec![helper_get_bob_address()]).unwrap(); debug!("Alice sends an update prd to Bob"); let alice_pairing_return = - create_update_message(None, pairing_init_state.to_string()).unwrap(); + create_update_message(alice_process_cache.keys().next().unwrap().to_owned(), commit_msg.pcd_commitment.to_string()).unwrap(); + + // debug!("{:#?}", alice_pairing_return); let (root_outpoint, alice_init_process) = alice_pairing_return.updated_process.unwrap(); alice_process_cache.insert(root_outpoint.clone(), alice_init_process.clone()); - let alice_to_bob_cipher = alice_pairing_return.ciphers_to_send[0]; + let alice_to_bob_cipher = &alice_pairing_return.ciphers_to_send[0]; // this is only for testing, as we're playing both parts let alice_device = dump_device().unwrap(); - let alice_processes = dump_process_cache().unwrap(); // ======================= Bob reset_device().unwrap(); create_device_from_sp_wallet(BOB_LOGIN_WALLET.to_owned()).unwrap(); + set_shared_secrets(serde_json::to_string(&bob_secrets_store).unwrap()).unwrap(); debug!("Bob receives the update prd"); - let bob_parsed_return = parse_cipher(alice_to_bob_cipher).unwrap(); + let bob_parsed_return = parse_cipher(alice_to_bob_cipher.to_owned()).unwrap(); - debug!("Bob retrieved prd: {:#?}", bob_retrieved_prd); - - let (root_commitment, relevant_process) = bob_retrieved_prd.updated_process.unwrap(); + let (root_commitment, relevant_process) = bob_parsed_return.updated_process.unwrap(); bob_process_cache.insert(root_commitment.clone(), relevant_process); - let prd_confirm_cipher = bob_retrieved_prd.ciphers_to_send.iter().next().unwrap(); + let prd_confirm_cipher = bob_parsed_return.ciphers_to_send.iter().next().unwrap(); debug!("Bob sends a Confirm Prd to Alice"); // this is only for testing, as we're playing both parts let bob_device = dump_device().unwrap(); - let bob_processes = dump_process_cache().unwrap(); // ======================= Alice reset_device().unwrap(); restore_device(alice_device).unwrap(); - set_process_cache(alice_processes).unwrap(); + set_process_cache(serde_json::to_string(&alice_process_cache).unwrap()).unwrap(); + set_shared_secrets(serde_json::to_string(&alice_secrets_store).unwrap()).unwrap(); debug!("Alice receives the Confirm Prd"); let alice_parsed_confirm = parse_cipher(prd_confirm_cipher.clone()).unwrap(); @@ -209,81 +230,35 @@ fn test_pairing() { // Alice simply shoots back the return value in the ws let bob_received_pcd = alice_parsed_confirm.ciphers_to_send[0].clone(); - // Now that we're sure that bob got the prd udpate we also produce the prd response and shoot it - let alice_prd_update_commitment = alice_init_process - .get_impending_requests() - .get(0) - .unwrap() - .create_commitment(); - let (_, alice_validated_prd) = add_validation_token_to_prd( - root_outpoint.clone(), - alice_prd_update_commitment.to_string(), - true, - ) - .unwrap() - .updated_process - .unwrap(); - - alice_process_cache.insert(root_outpoint.clone(), alice_validated_prd); - - let alice_prd_response = - response_prd(root_outpoint, alice_prd_update_commitment.to_string(), true).unwrap(); - - let bob_received_response = alice_prd_response.ciphers_to_send.get(0).unwrap().clone(); - // ======================= Bob reset_device().unwrap(); restore_device(bob_device).unwrap(); - set_process_cache(bob_processes).unwrap(); + set_process_cache(serde_json::to_string(&bob_process_cache).unwrap()).unwrap(); + set_shared_secrets(serde_json::to_string(&bob_secrets_store).unwrap()).unwrap(); debug!("Bob parses Alice's pcd"); let bob_parsed_pcd_return = parse_cipher(bob_received_pcd).unwrap(); - debug!("bob_parsed_pcd: {:#?}", bob_parsed_pcd_return); - + let (root_outpoint, updated_process) = bob_parsed_pcd_return.updated_process.unwrap(); + // Here we would update our database bob_process_cache.insert( - root_commitment.clone(), - bob_parsed_pcd_return.updated_process.unwrap().1, + root_outpoint.clone(), + updated_process ); - // We now need Alice prd response, and update our process with it - debug!("Bob also parses alice prd response"); - let bob_parsed_response = parse_cipher(bob_received_response).unwrap(); - - debug!("bob_parsed_response: {:#?}", bob_parsed_response); - - bob_process_cache.insert( - root_commitment.clone(), - bob_parsed_response.updated_process.unwrap().1, - ); - - debug!("{:#?}", bob_process_cache.get(&root_commitment).unwrap()); - // At this point, user must validate the pairing proposal received from Alice // We decrypt the content of the pcd so that we can display to user what matters - let alice_proposal = get_update_proposals(root_commitment.clone()).unwrap(); + let alice_proposal = get_update_proposals(root_outpoint.clone()).unwrap().decrypted_pcds; debug!("Alice proposal: {:#?}", alice_proposal); - let proposal = Value::from_str(&alice_proposal.get(0).unwrap()).unwrap(); - debug!("proposal: {:#?}", proposal); + let (pcd_commitment_root, proposal) = alice_proposal.iter().next().unwrap(); + + // debug!("proposal: {:#?}", proposal); // get the roles from the proposal - let roles = proposal - .get("roles") - .and_then(|v| Value::from_str(v.as_str().unwrap()).ok()) - .unwrap() - .as_object() - .unwrap() - .iter() - .map(|(role_name, role_value)| { - let role_def: RoleDefinition = serde_json::from_value(role_value.clone())?; - Ok((role_name.clone(), role_def)) - }) - .collect::, anyhow::Error>>(); - - let roles = roles.unwrap(); + let roles = proposal.extract_roles().unwrap(); // we check that the proposal contains only one member assert!(roles.len() == 1); @@ -309,37 +284,33 @@ fn test_pairing() { debug!("proposal_members: {:?}", proposal_members); // we can now show all the addresses to the user on device to prompt confirmation - info!("Pop-up: User confirmation"); + debug!("Pop-up: User confirmation"); // If user is ok, we can add our own validation token - let prd_to_respond = bob_process_cache - .get(&root_commitment) - .unwrap() - .get_impending_requests() - .get(0) - .unwrap() - .to_owned(); - let bob_added_validation = - add_validation_token_to_prd(root_commitment.clone(), prd_to_respond.create_commitment().to_string(), true).unwrap(); + // Get the whole commitment from the process + let process = alice_process_cache.get(&root_outpoint).unwrap(); + let mut pcd_commitment = String::default(); + for p in process.get_latest_concurrent_states().unwrap() { + let root = ::create_merkle_tree(&p.pcd_commitment).unwrap().root().unwrap(); + if *pcd_commitment_root == root.to_lower_hex_string() { + pcd_commitment = p.pcd_commitment.to_string(); + break; + } + } + let bob_response = create_response_message(root_outpoint, pcd_commitment, true).unwrap(); + + let (root_outpoint, updated_process) = bob_response.updated_process.unwrap(); + let ciphers = bob_response.ciphers_to_send; // We would send it to Alice to let her know we agree bob_process_cache.insert( - root_commitment.clone(), - bob_added_validation.updated_process.unwrap().1, + root_outpoint.clone(), + updated_process, ); - // We create the commit msg for the relay that includes Alice and Bob's proofs - let commit_msg = create_commit_message(root_commitment.clone(), "tsp1qqvfm6wvd55r68ltysdhmagg7qavxrzlmm9a7tujsp8qqy6x2vr0muqajt5p2jdxfw450wyeygevypxte29sxlxzgprmh2gwnutnt09slrcqqy5h4".to_owned(), 1).unwrap().commit_to_send.unwrap(); - - let tx: Transaction = deserialize(&Vec::from_hex(&commit_msg.init_tx).unwrap()).unwrap(); - - // We send the commit_msg to the relay we got the address from - // We also send - - // We can just take the txid of the transaction we created for the commitment - let commitment_outpoint = OutPoint::new(tx.txid(), 0); + // We also send an update to the other members debug!("Bob pairs device with Alice"); - pair_device(commitment_outpoint.to_string(), proposal_members).unwrap(); + pair_device(root_outpoint, proposal_members).unwrap(); // To make the pairing effective, alice and bob must now creates a new transaction where they both control one output