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] /// Tests the connection process between two devices, Alice and Bob, by executing a secure /// transaction to establish a shared secret for encrypted communication. /// /// The basics are that one device will initiate the process by sending a transaction that pays another device. /// The recipient of the transaction as soon as it finds it, can extract a shared secret and send an encrypted /// message back. Upon receiving this message, the initiator answers with a similar message similarly encrypted. /// Upon receiving this message, the recipient can be assured that the communication is safe, and start using /// the secret to communicate. /// /// The security of the shared secret rest on the soundness of the silent payment protocol for Bitcoin. /// In its encrypted response, the initiator adds a signature that is proof that it indeed controls the /// private key for the silent payment address it announced, so recipient knows there's no mitm or impostor. /// /// # Detailed Process /// /// ## Alice sends a transaction that pays Bob: /// - Alice initializes her device from an `sp_wallet` object and sets it as the local device. /// - She retrieves her own address and obtains Bob’s address. /// - Alice creates a new member using Bob’s device address (this is mainly for testing purpose, /// because `create_connection_transaction` would take members as argument). /// - She generates a connection transaction (`connect_tx`) targeting Bob's device. /// - Alice processes her own transaction and stores the derived shared secrets in `alice_secrets_store`, /// associating the shared secret with Bob's addresses. /// /// ## Bob parses the transaction: /// - Bob initializes his device from his own `sp_wallet`. /// - He parses Alice’s connection transaction to retrieve the shared secret Alice created for him. /// - Bob saves these derived shared secrets in `bob_secrets_store` but can't index it with Alice's address yet. /// /// ## Prd Connect exchange /// - Bob then responds by sending a prd connect back to Alice encrypted with the shared secret. /// This prd is very simple and basically contains the following: /// * All Bob's devices addresses /// * a commitment to the shared secret /// * a proof signed with Bob's device spend key /// - Alice receives and decrypts the message from Bob. /// - She replies to Bob by encrypting another prd connect which is basically the same, but keeping Bob's proof and adding her own. /// - **Bob’s Confirmation**: Bob receives Alice’s confirmation message, decrypts it, and updates his secret in `bob_secrets_store`. /// /// ## Verification: /// - Finally, the function asserts that Alice and Bob now share the same secrets, confirming successful /// connection and mutual authentication between the devices. 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())); }