diff --git a/src/api.rs b/src/api.rs index b831153..bebdf80 100644 --- a/src/api.rs +++ b/src/api.rs @@ -49,7 +49,7 @@ use sdk_common::sp_client::silentpayments::{ utils::{Network as SpNetwork, SilentPaymentAddress}, Error as SpError, }; -use sdk_common::{signature, MAX_PRD_PAYLOAD_SIZE}; +use sdk_common::{signature, MutexExt, MAX_PRD_PAYLOAD_SIZE}; use serde_json::{Error as SerdeJsonError, Map, Value}; use serde::{Deserialize, Serialize}; @@ -71,16 +71,16 @@ use sdk_common::sp_client::spclient::{ derive_keys_from_seed, OutputList, OutputSpendStatus, OwnedOutput, Recipient, SpClient, }; use sdk_common::sp_client::spclient::{SpWallet, SpendKey}; +use sdk_common::secrets::SecretsStore; use crate::user::{lock_local_device, set_new_device, LOCAL_DEVICE}; use crate::wallet::{generate_sp_wallet, lock_freezed_utxos}; -use crate::{lock_messages, CACHEDMESSAGES}; #[derive(Debug, PartialEq, Tsify, Serialize, Deserialize, Default)] #[tsify(into_wasm_abi, from_wasm_abi)] #[allow(non_camel_case_types)] pub struct ApiReturn { - pub updated_cached_msg: Vec, + pub secrets: SecretsStore, pub updated_process: Option<(String, Process)>, pub new_tx_to_send: Option, pub ciphers_to_send: Vec, @@ -92,6 +92,14 @@ pub type ApiResult = Result; const IS_TESTNET: bool = true; const DEFAULT_AMOUNT: Amount = Amount::from_sat(1000); +pub static SHAREDSECRETS: OnceLock> = OnceLock::new(); + +pub fn lock_shared_secrets() -> Result, anyhow::Error> { + SHAREDSECRETS + .get_or_init(|| Mutex::new(SecretsStore::new())) + .lock_anyhow() +} + #[derive(Debug, PartialEq, Eq)] pub struct ApiError { pub message: String, @@ -439,44 +447,23 @@ pub fn set_process_cache(processes: String) -> ApiResult<()> { } #[wasm_bindgen] -pub fn reset_message_cache() -> ApiResult<()> { - let mut cached_msg = lock_messages()?; +pub fn reset_shared_secrets() -> ApiResult<()> { + let mut shared_secrets = lock_shared_secrets()?; - *cached_msg = vec![]; - - debug_assert!(cached_msg.is_empty()); + *shared_secrets = SecretsStore::new(); Ok(()) } #[wasm_bindgen] -pub fn set_message_cache(msg_cache: Vec) -> ApiResult<()> { - let mut cached_msg = lock_messages()?; +pub fn set_shared_secrets(secrets: String) -> ApiResult<()>{ + let mut shared_secrets = lock_shared_secrets()?; - if !cached_msg.is_empty() { - return Err(ApiError::new("Message cache not empty".to_owned())); - } - - let new_cache: Result, serde_json::Error> = - msg_cache.iter().map(|m| serde_json::from_str(m)).collect(); - - *cached_msg = new_cache?; + *shared_secrets = serde_json::from_str(&secrets)?; Ok(()) } -#[wasm_bindgen] -pub fn dump_message_cache() -> ApiResult> { - let cached_msg = lock_messages()?; - - let res: Vec = cached_msg - .iter() - .map(|m| serde_json::to_string(m).unwrap()) - .collect(); - - Ok(res) -} - #[wasm_bindgen] pub fn dump_device() -> ApiResult { let local_device = lock_local_device()?; @@ -490,7 +477,7 @@ pub fn reset_device() -> ApiResult<()> { *device = Device::default(); - reset_message_cache()?; + reset_shared_secrets()?; reset_process_cache()?; Ok(()) @@ -506,14 +493,16 @@ pub fn get_txid(transaction: String) -> ApiResult { fn handle_transaction( updated: HashMap, tx: &Transaction, - tweak_data: PublicKey, + public_data: PublicKey, ) -> AnyhowResult { let b_scan: SecretKey; - let local_address: SilentPaymentAddress; + let local_member: Member; + let sp_wallet: SpWallet; { let local_device = lock_local_device()?; + sp_wallet = local_device.get_wallet().clone(); b_scan = local_device.get_wallet().get_client().get_scan_key(); - local_address = local_device.get_wallet().get_client().get_receiving_address().try_into()?; + local_member = local_device.to_member(); } let op_return: Vec<&sdk_common::sp_client::bitcoin::TxOut> = tx @@ -535,55 +524,35 @@ fn handle_transaction( .filter(|(outpoint, output)| output.spend_status != OutputSpendStatus::Unspent) .collect(); - let mut messages = lock_messages()?; + let mut shared_secrets = lock_shared_secrets()?; // empty utxo_destroyed means we received this transaction if utxo_destroyed.is_empty() { let shared_point = sp_utils::receiving::calculate_ecdh_shared_secret( - &tweak_data, + &public_data, &b_scan, ); let shared_secret = AnkSharedSecretHash::from_shared_point(shared_point); - let mut plaintext: Vec = vec![]; - if let Some(message) = messages.iter_mut().find(|m| { - if m.status != CachedMessageStatus::CipherWaitingTx { - return false; - } - let res = m.try_decrypt_with_shared_secret(shared_secret.to_byte_array()); - if res.is_ok() { - plaintext = res.unwrap(); - return true; - } else { - return false; - } - }) { - // Calling this check that the prd we found check with the hashed commitment in transaction - // We also check the signed proof that is included in the prd - let prd = Prd::extract_from_message_with_commitment(&plaintext, local_address, &commitment)?; + // We keep the shared_secret as unconfirmed + shared_secrets.add_unconfirmed_secret(shared_secret); - // for now the previous method doesn't error if proof is missing, - // We must define if there are cases where a valid prd doesn't have proof + // We hash the shared secret to commit into the prd connect + let secret_hash = AnkMessageHash::from_message(shared_secret.as_byte_array()); - let outpoint = OutPoint::from_str(&prd.root_commitment)?; + // We still don't know who sent it, so we reply with a `Connect` prd + let prd_connect = Prd::new_connect(local_member, secret_hash, None); - handle_decrypted_message(plaintext, Some(shared_secret), Some(outpoint)) - } else { - // store it and wait for the message - let mut new_msg = CachedMessage::new(); - new_msg.commitment = Some(commitment.as_byte_array().to_lower_hex_string()); - new_msg - .shared_secrets - .push(shared_secret.to_byte_array().to_lower_hex_string()); - new_msg.status = CachedMessageStatus::TxWaitingPrd; + let msg = prd_connect.to_network_msg(&sp_wallet)?; - messages.push(new_msg.clone()); + // We encrypt the prd connect with the same secret + let cipher = encrypt_with_key(shared_secret.as_byte_array(), msg.as_bytes())?; - return Ok(ApiReturn { - updated_cached_msg: vec![new_msg.clone()], - ..Default::default() - }); - } + return Ok(ApiReturn { + secrets: shared_secrets.to_owned(), + ciphers_to_send: vec![cipher.to_lower_hex_string()], + ..Default::default() + }) } else { // We're sender of the transaction, do nothing return Ok(ApiReturn { @@ -639,29 +608,6 @@ pub fn parse_new_tx(new_tx_msg: String, block_height: u32, fee_rate: u32) -> Api )?) } -fn try_decrypt_with_processes( - cipher: &[u8], - processes: MutexGuard>, -) -> Option<(Vec, SilentPaymentAddress, OutPoint)> { - let nonce = Nonce::from_slice(&cipher[..12]); - - for (outpoint, process) in processes.iter() { - for (address, secret) in process.get_all_secrets() { - let engine = Aes256Gcm::new(&secret.to_byte_array().into()); - if let Ok(plain) = engine.decrypt( - &nonce, - Payload { - msg: &cipher[12..], - aad: AAD, - }, - ) { - return Some((plain, address, *outpoint)); - } - } - } - None -} - #[wasm_bindgen] /// Produce a proof and append it to a prd pub fn add_validation_token_to_prd( @@ -820,123 +766,62 @@ fn confirm_prd(prd: Prd, shared_secret: &AnkSharedSecretHash) -> AnyhowResult AnyhowResult { - let pcd = &prd.payload; - - let cipher = encrypt_with_key(shared_secret.as_byte_array(), pcd.as_bytes())?; - - Ok(ApiReturn { - ciphers_to_send: vec![cipher.to_lower_hex_string()], - ..Default::default() - }) -} - -fn decrypt_with_cached_messages( - cipher: &[u8], - messages: &mut MutexGuard> -) -> anyhow::Result, AnkSharedSecretHash)>> { - let nonce = Nonce::from_slice(&cipher[..12]); - let local_address: SilentPaymentAddress = lock_local_device()?.get_wallet().get_client().get_receiving_address().try_into()?; - - for message in messages.iter_mut() { - for shared_secret in message.shared_secrets.iter() { - let aes_key = match AnkSharedSecretHash::from_str(shared_secret) { - Ok(key) => key, - Err(_) => { - debug!( - "Invalid shared secret for message{}: {}", - message.id, shared_secret - ); - continue; - } - }; - - let engine = Aes256Gcm::new(aes_key.as_byte_array().into()); - - let plain = match engine.decrypt( - &nonce, - Payload { - msg: &cipher[12..], - aad: AAD, - }, - ) { - Ok(plain) => plain, - Err(_) => continue, - }; - - let commitment = AnkPrdHash::from_str( - message - .commitment - .as_ref() - .ok_or(anyhow::Error::msg("Missing commitment".to_owned()))?, - )?; - // A message matched against a new transaction must be a prd - // We just check the commitment while we're at it - let _ = Prd::extract_from_message_with_commitment(&plain, local_address, &commitment)?; - // Update the message status - message.status = CachedMessageStatus::NoStatus; - message.shared_secrets = vec![]; // this way we won't check it again - - return Ok(Some(( - plain, - aes_key, - ))); - } - } - - Ok(None) -} - -fn decrypt_with_known_processes(cipher: &[u8], processes: MutexGuard>) -> anyhow::Result, OutPoint)>> { - let nonce = Nonce::from_slice(&cipher[..12]); - - for (outpoint, process) in processes.iter() { - for (address, secret) in process.get_all_secrets() { - debug!("Attempting decryption with key {} for {}", secret, address); - let engine = Aes256Gcm::new(secret.as_byte_array().into()); - - if let Ok(plain) = engine.decrypt( - &nonce, - Payload { - msg: &cipher[12..], - aad: AAD, - }, - ) { - return Ok(Some((plain, *outpoint))); - } - } - } - Ok(None) -} - -/// Prd can come with an attached transaction or without -/// It's useful to commit it in a transaction in the case there are multiple recipients (guarantee that everyone gets the same payload) -/// Or if for some reasons we don't want to use the same shared secret again fn handle_prd( - plain: &[u8], - new_shared_secret: Option, + prd: Prd, + secret: AnkSharedSecretHash ) -> AnyhowResult { - let local_address: SilentPaymentAddress = lock_local_device()?.get_wallet().get_client().get_receiving_address().try_into()?; - // We already checked the commitment if any - let prd = Prd::extract_from_message(plain, local_address)?; - - debug!("found prd: {:#?}", prd); - - let proof_key = prd.proof.unwrap().get_key(); - - let sp_address = serde_json::from_str::(&prd.sender)? - .get_addresses() - .into_iter() - .find_map(|address| { - let parsed_address = SilentPaymentAddress::try_from(address.as_str()).ok()?; - let spend_key = parsed_address.get_spend_key().x_only_public_key().0; - if spend_key == proof_key { - Some(parsed_address) - } else { - None + // Connect is a bit different here because there's no associated process + // Let's handle that case separately + if prd.prd_type == PrdType::Connect { + let local_device = lock_local_device()?; + let local_member = local_device.to_member(); + let sp_wallet = local_device.get_wallet(); + let secret_hash = AnkMessageHash::from_message(secret.as_byte_array()); + let mut shared_secrets = lock_shared_secrets()?; + if let Some(prev_proof) = prd.validation_tokens.get(0) { + // check that the proof is valid + prev_proof.verify()?; + // Check it's signed with our key + let local_address = SilentPaymentAddress::try_from(sp_wallet.get_client().get_receiving_address())?; + if prev_proof.get_key() != local_address.get_spend_key() { + return Err(anyhow::Error::msg("Previous proof of a prd connect isn't signed by us")); } - }) - .ok_or_else(|| anyhow::Error::msg("No matching address found for the proof key"))?; + // Check it signs a prd connect that contains the commitment to the shared secret + let empty_prd = Prd::new_connect(local_member, secret_hash, None); + let msg = AnkMessageHash::from_message(empty_prd.to_string().as_bytes()); + if *msg.as_byte_array() != prev_proof.get_message() { + return Err(anyhow::Error::msg("Previous proof signs another message")); + } + // Now we can confirm the secret and link it to an address + let sender = serde_json::from_str::(&prd.sender)?; + let proof = prd.proof.unwrap(); + let actual_sender = 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.try_into()?); + debug!("updated secrets"); + return Ok(ApiReturn { + secrets: shared_secrets.to_owned(), + ..Default::default() + }) + } else { + let proof = prd.proof.unwrap(); + let sender = serde_json::from_str::(&prd.sender)?; + let actual_sender = 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.try_into()?); + + let prd_connect = Prd::new_connect(local_member, secret_hash, prd.proof); + let msg = prd_connect.to_network_msg(sp_wallet)?; + let cipher = encrypt_with_key(secret.as_byte_array(), msg.as_bytes())?; + + return Ok(ApiReturn { + ciphers_to_send: vec![cipher.to_lower_hex_string()], + secrets: shared_secrets.to_owned(), + ..Default::default() + }) + } + } let outpoint = OutPoint::from_str(&prd.root_commitment)?; @@ -945,11 +830,7 @@ 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); - let shared_secret = new_shared_secret - .ok_or_else(|| anyhow::Error::msg("Missing shared secret for new process"))?; - let mut shared_secrets = HashMap::new(); - shared_secrets.insert(sp_address, shared_secret); - entry.insert(Process::new(vec![], shared_secrets, vec![])) + entry.insert(Process::new(vec![], vec![])) } }; @@ -969,29 +850,52 @@ fn handle_prd( hash.to_string() == prd.payload }) .ok_or(anyhow::Error::msg("Original request not found"))?; - let shared_secret = relevant_process - .get_shared_secret_for_address(&sp_address) - .ok_or(anyhow::Error::msg( - "Missing shared secret for address in original request", - ))?; + let member: Member = serde_json::from_str(&prd.sender)?; - return send_data(original_request, &shared_secret); + // 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()); + } 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); + } + } + + // This should never happen since we sent a message to get a confirmation back + if ciphers.is_empty() { + return Err(anyhow::Error::msg(format!("No available secrets for member {:?}", member))); + } + + return Ok(ApiReturn { + ciphers_to_send: ciphers, + ..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 shared_secret = relevant_process - .get_shared_secret_for_address(&sp_address) - .ok_or(anyhow::Error::msg( - "Missing shared secret for address in original request", - ))?; + 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)?; + } 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); + } + } - let cipher = confirm_prd(prd, &shared_secret)?; + // This should never happen since we sent a message to get a confirmation back + if ciphers.is_empty() { + return Err(anyhow::Error::msg(format!("No available secrets for member {:?}", member))); + } return Ok(ApiReturn { - ciphers_to_send: vec![cipher], + ciphers_to_send: ciphers, updated_process: Some((outpoint.to_string(), relevant_process.clone())), ..Default::default() }); @@ -1052,28 +956,21 @@ fn handle_pcd(plain: Vec, root_commitment: OutPoint) -> AnyhowResult, - shared_secret: Option, - root_commitment: Option, ) -> anyhow::Result { - // Try to handle as PRD first - handle_prd(&plain, shared_secret) - .or_else(|_| { - // If PRD handling fails, try to handle as PCD - handle_pcd( - plain, - root_commitment.ok_or(anyhow::Error::msg( - "root_commitment must be known for a pcd", - ))?, - ) - }) - .map_err(|e| anyhow::Error::msg(format!("Failed to handle decrypted message: {}", e))) + 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] pub fn parse_cipher(cipher_msg: String) -> ApiResult { - let mut messages = lock_messages()?; - // Check that the cipher is not empty or too long if cipher_msg.is_empty() || cipher_msg.len() > MAX_PRD_PAYLOAD_SIZE { return Err(ApiError::new( @@ -1083,30 +980,13 @@ pub fn parse_cipher(cipher_msg: String) -> ApiResult { let cipher = Vec::from_hex(cipher_msg.trim_matches('"'))?; - // Try decrypting with cached messages first - if let Ok(Some((plain, shared_secret))) = decrypt_with_cached_messages(&cipher, &mut messages) { - return handle_decrypted_message(plain, Some(shared_secret), None) + let decrypt_res = lock_shared_secrets()?.try_decrypt(&cipher); + if let Ok((secret, plain)) = decrypt_res { + return handle_decrypted_message(secret, plain) .map_err(|e| ApiError::new(format!("Failed to handle decrypted message: {}", e))); } - // If that fails, try decrypting with known processes - let processes = lock_processes()?; - if let Ok(Some((plain, root_commitment))) = decrypt_with_known_processes(&cipher, processes) { - return handle_decrypted_message(plain, None, Some(root_commitment)) - .map_err(|e| ApiError::new(format!("Failed to handle decrypted message: {}", e))); - } - - // If both decryption attempts fail, we keep it just in case we receive the transaction later - let mut return_msg = CachedMessage::new(); - return_msg.cipher = vec![cipher_msg]; - return_msg.status = CachedMessageStatus::CipherWaitingTx; - - messages.push(return_msg.clone()); - - Ok(ApiReturn { - updated_cached_msg: vec![return_msg], - ..Default::default() - }) + Err(ApiError::new("Failed to decrypt cipher".to_owned())) } #[wasm_bindgen] @@ -1205,6 +1085,75 @@ pub fn create_commit_message( } } +#[wasm_bindgen] +/// We send a transaction that pays at least one output to each address of each member +/// The goal is to establish a shared_secret to be used as an encryption key for further communication +pub fn create_connect_transaction(members_str: Vec, fee_rate: u32) -> ApiResult { + let mut members: Vec = vec![]; + + for member in members_str { + members.push(serde_json::from_str(&member)?) + } + + let local_device = lock_local_device()?; + + let sp_wallet = local_device.get_wallet(); + let freezed_utxos = lock_freezed_utxos()?; + + let recipients = members.iter() + .flat_map(|member| { + member.get_addresses() + }) + .map(|address| { + Recipient { + address: address.clone(), + amount: DEFAULT_AMOUNT, + nb_outputs: 1 + } + }) + .collect(); + + let signed_psbt = create_transaction( + vec![], + &freezed_utxos, + sp_wallet, + recipients, + None, + Amount::from_sat(fee_rate.into()), + None, + )?; + + let partial_secret = sp_wallet + .get_client() + .get_partial_secret_from_psbt(&signed_psbt)?; + + // We now generate the shared secret for each address + let mut shared_secrets = lock_shared_secrets()?; + for member in members { + let addresses = member.get_addresses(); + + for address in addresses { + let sp_address = SilentPaymentAddress::try_from(address.as_str())?; + let shared_point = sp_utils::sending::calculate_ecdh_shared_secret( + &sp_address.get_scan_key(), + &partial_secret, + ); + + let shared_secret = AnkSharedSecretHash::from_shared_point(shared_point); + + shared_secrets.confirm_secret_for_address(shared_secret, sp_address); + } + } + + let transaction = signed_psbt.extract_tx()?; + + Ok(ApiReturn { + new_tx_to_send: Some(NewTxMessage::new(serialize(&transaction).to_lower_hex_string(), None)), + secrets: shared_secrets.to_owned(), + ..Default::default() + }) +} + #[wasm_bindgen] /// We assume that the provided tx outpoint exist pub fn create_update_transaction( diff --git a/tests/connect.rs b/tests/connect.rs new file mode 100644 index 0000000..1e4b87a --- /dev/null +++ b/tests/connect.rs @@ -0,0 +1,113 @@ +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, reset_shared_secrets, response_prd, restore_device, set_process_cache, set_shared_secrets, setup, ApiReturn +}; +use sdk_common::log::{debug, info}; +use sdk_common::pcd::{Member, RoleDefinition}; +use sdk_common::secrets::SecretsStore; +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 serde_json::{json, Value}; + +use tsify::JsValueSerdeExt; +use wasm_bindgen_test::*; + +mod utils; + +use utils::*; + +wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +fn test_connect() { + setup(); + // let mut alice_process_cache = HashMap::new(); + // let mut bob_process_cache = HashMap::new(); + let mut alice_secrets_store = SecretsStore::new(); + let mut bob_secrets_store = SecretsStore::new(); + + debug!("==============================================\nStarting test_connect\n=============================================="); + + // ========================= Alice + reset_device().unwrap(); + create_device_from_sp_wallet(ALICE_LOGIN_WALLET.to_owned()).unwrap(); + + // we get our own address + let alice_address = get_address().unwrap(); + + // we scan the qr code or get the address by any other means + let bob_address = helper_get_bob_address(); + + debug!("Alice establishes a shared secret with Bob"); + // We just send a transaction to Bob device to allow them to share encrypted message + // Since we're not paired we can just put bob's address in a disposable member + // create_connect_transaction needs to take members though because most of the time we'd rather create secrets with all the devices of a member + let bob_member = Member::new(vec![SilentPaymentAddress::try_from(bob_address.as_str()).unwrap()]).unwrap(); + let alice_connect_return = create_connect_transaction(vec![serde_json::to_string(&bob_member).unwrap()], 1).unwrap(); + + debug!("alice_connect_return: {:#?}", alice_connect_return); + + let connect_tx_msg = alice_connect_return.new_tx_to_send.unwrap(); + + // This is only for testing, the relay takes care of that in prod + let get_outputs_result = get_outputs().unwrap(); + + let alice_outputs: HashMap = get_outputs_result.into_serde().unwrap(); + + let alice_pairing_tweak_data = + helper_get_tweak_data(&connect_tx_msg.transaction, alice_outputs); + + // End of the test only part + + // Alice parses her own transaction + helper_parse_transaction(&connect_tx_msg.transaction, &alice_pairing_tweak_data); + + let alice_connect_transaction = connect_tx_msg.transaction; + + let alice_device = dump_device().unwrap(); + alice_secrets_store = alice_connect_return.secrets; + + // ======================= Bob + reset_device().unwrap(); + reset_shared_secrets().unwrap(); + create_device_from_sp_wallet(BOB_LOGIN_WALLET.to_owned()).unwrap(); + + debug!("Bob parses Alice connect transaction"); + let bob_parsed_transaction_return = helper_parse_transaction(&alice_connect_transaction, &alice_pairing_tweak_data); + + let bob_to_alice_cipher = &bob_parsed_transaction_return.ciphers_to_send[0]; + + let bob_device = dump_device().unwrap(); + bob_secrets_store = bob_parsed_transaction_return.secrets; + + // ======================= Alice + reset_device().unwrap(); + restore_device(alice_device).unwrap(); + set_shared_secrets(serde_json::to_string(&alice_secrets_store).unwrap()).unwrap(); + + debug!("Alice receives the connect Prd"); + let alice_parsed_connect = parse_cipher(bob_to_alice_cipher.clone()).unwrap(); + + // debug!("alice_parsed_confirm: {:#?}", alice_parsed_confirm); + + let alice_to_bob_cipher = alice_parsed_connect.ciphers_to_send.get(0).unwrap(); + alice_secrets_store = alice_parsed_connect.secrets; + + // ======================= Bob + reset_device().unwrap(); + restore_device(bob_device).unwrap(); + set_shared_secrets(serde_json::to_string(&bob_secrets_store).unwrap()).unwrap(); + + debug!("Bob parses alice prd connect"); + let bob_parsed_connect = parse_cipher(alice_to_bob_cipher.clone()).unwrap(); + + bob_secrets_store = bob_parsed_connect.secrets; + + // Assert that Alice and Bob now has the same secret + assert!(alice_secrets_store.get_secret_for_address(bob_address.try_into().unwrap()) == bob_secrets_store.get_secret_for_address(alice_address.try_into().unwrap())); +}