Add connect logic

This commit is contained in:
Sosthene 2024-10-29 12:55:02 +01:00
parent 56cadb71f6
commit 2910125bd1
2 changed files with 324 additions and 262 deletions

View File

@ -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<CachedMessage>,
pub secrets: SecretsStore,
pub updated_process: Option<(String, Process)>,
pub new_tx_to_send: Option<NewTxMessage>,
pub ciphers_to_send: Vec<String>,
@ -92,6 +92,14 @@ pub type ApiResult<T: FromWasmAbi> = Result<T, ApiError>;
const IS_TESTNET: bool = true;
const DEFAULT_AMOUNT: Amount = Amount::from_sat(1000);
pub static SHAREDSECRETS: OnceLock<Mutex<SecretsStore>> = OnceLock::new();
pub fn lock_shared_secrets() -> Result<MutexGuard<'static, SecretsStore>, 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<String>) -> 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<Vec<CachedMessage>, 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<Vec<String>> {
let cached_msg = lock_messages()?;
let res: Vec<String> = cached_msg
.iter()
.map(|m| serde_json::to_string(m).unwrap())
.collect();
Ok(res)
}
#[wasm_bindgen]
pub fn dump_device() -> ApiResult<String> {
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<String> {
fn handle_transaction(
updated: HashMap<OutPoint, OwnedOutput>,
tx: &Transaction,
tweak_data: PublicKey,
public_data: PublicKey,
) -> AnyhowResult<ApiReturn> {
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<u8> = 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()],
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<HashMap<OutPoint, Process>>,
) -> Option<(Vec<u8>, 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<St
Ok(encrypt_with_key(shared_secret.as_byte_array(), prd_msg.as_bytes())?.to_lower_hex_string())
}
fn send_data(prd: &Prd, shared_secret: &AnkSharedSecretHash) -> AnyhowResult<ApiReturn> {
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()],
fn handle_prd(
prd: Prd,
secret: AnkSharedSecretHash
) -> AnyhowResult<ApiReturn> {
// 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"));
}
// 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::<Member>(&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()
})
}
fn decrypt_with_cached_messages(
cipher: &[u8],
messages: &mut MutexGuard<Vec<CachedMessage>>
) -> anyhow::Result<Option<(Vec<u8>, 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<HashMap<OutPoint, Process>>) -> anyhow::Result<Option<(Vec<u8>, 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<AnkSharedSecretHash>,
) -> AnyhowResult<ApiReturn> {
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::<Member>(&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
}
let proof = prd.proof.unwrap();
let sender = serde_json::from_str::<Member>(&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()
})
.ok_or_else(|| anyhow::Error::msg("No matching address found for the proof key"))?;
}
}
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<u8>, root_commitment: OutPoint) -> AnyhowResult<ApiRetu
}
fn handle_decrypted_message(
secret: AnkSharedSecretHash,
plain: Vec<u8>,
shared_secret: Option<AnkSharedSecretHash>,
root_commitment: Option<OutPoint>,
) -> anyhow::Result<ApiReturn> {
// 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<ApiReturn> {
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<ApiReturn> {
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<String>, fee_rate: u32) -> ApiResult<ApiReturn> {
let mut members: Vec<Member> = 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(

113
tests/connect.rs Normal file
View File

@ -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<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()));
}