sdk_client/tests/connect.rs
2024-10-30 16:06:52 +01:00

156 lines
7.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 Bobs address.
/// - Alice creates a new member using Bobs 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 Alices 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.
/// - **Bobs Confirmation**: Bob receives Alices 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<OutPoint, OwnedOutput> = 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()));
}