diff --git a/Cargo.toml b/Cargo.toml index 463dd17..077b0f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [lib] name = "sdk_client" -crate-type = ["cdylib"] +crate-type = ["lib", "cdylib"] [dependencies] anyhow = "1.0" @@ -17,7 +17,7 @@ wasm-logger = "0.2.0" rand = "0.8.5" log = "0.4.6" tsify = { git = "https://github.com/Sosthene00/tsify", branch = "next" } -#sdk_common = { path = "../../../sdk_common" } +# sdk_common = { path = "../sdk_common" } sdk_common = { git = "https://git.4nkweb.com/4nk/sdk_common.git", branch = "dev" } shamir = { git = "https://github.com/Sosthene00/shamir", branch = "master" } img-parts = "0.3.0" diff --git a/src/api.rs b/src/api.rs index 28824ae..5293d68 100644 --- a/src/api.rs +++ b/src/api.rs @@ -35,7 +35,7 @@ use sdk_common::sp_client::silentpayments::{ use serde_json::{Error as SerdeJsonError, Value}; use serde::{Deserialize, Serialize}; -use tsify::Tsify; +use tsify::{JsValueSerdeExt, Tsify}; use wasm_bindgen::convert::FromWasmAbi; use wasm_bindgen::prelude::*; @@ -43,16 +43,13 @@ use sdk_common::network::{ self, AnkFlag, AnkNetworkMsg, CachedMessage, CachedMessageStatus, CipherMessage, FaucetMessage, NewTxMessage, }; -use sdk_common::silentpayments::{ - create_transaction, create_transaction_for_address_with_shared_secret, - create_transaction_spend_outpoint, map_outputs_to_sp_address, -}; +use sdk_common::silentpayments::{create_transaction, map_outputs_to_sp_address}; +use crate::wallet::generate_sp_wallet; use sdk_common::sp_client::spclient::{ derive_keys_from_seed, OutputList, OutputSpendStatus, OwnedOutput, Recipient, SpClient, }; use sdk_common::sp_client::spclient::{SpWallet, SpendKey}; -use crate::wallet::generate_sp_wallet; use crate::user::{ lock_local_device, lock_spending_client, Device, RevokeOutput, LOCAL_DEVICE, SPENDING_CLIENT, @@ -61,13 +58,13 @@ use crate::{images, lock_messages, CACHEDMESSAGES}; use crate::process::Process; -type ApiResult = Result; +pub type ApiResult = Result; const IS_TESTNET: bool = true; -#[derive(Debug)] -struct ApiError { - message: String, +#[derive(Debug, PartialEq, Eq)] +pub struct ApiError { + pub message: String, } impl From for ApiError { @@ -188,7 +185,55 @@ pub fn get_address() -> ApiResult { } #[wasm_bindgen] -pub fn create_new_wallet(birthday: u32, network_str: String) -> ApiResult { +pub fn restore_device_from_sp_wallet(sp_wallet: String) -> ApiResult { + let wallet: SpWallet = serde_json::from_str(&sp_wallet)?; + + let mut device = Device::new( + wallet.get_client().get_scan_key(), + wallet.get_client().get_spend_key().into(), + wallet.get_client().get_network(), + ); + + let birthday = wallet.get_outputs().get_birthday(); + let outputs = wallet.get_outputs().to_outpoints_list(); + device + .get_watch_only_mut() + .get_mut_outputs() + .set_birthday(birthday); + device + .get_watch_only_mut() + .get_mut_outputs() + .extend_from(outputs); + + let json_return = serde_json::to_string(&device)?; + + // Set the LOCAL_DEVICE const with the new value + let mut local_device = lock_local_device()?; + + if *local_device.get_watch_only().get_client() == SpClient::default() { + *local_device = device; + } else { + return Err(ApiError { + message: "device is already initialized".to_owned(), + }); + } + + // Set the LOGGED_WALLET with the new wallet to keep it in memory while we wait for the linking + let mut spending_client = lock_spending_client()?; + + if *spending_client == SpClient::default() { + *spending_client = wallet.get_client().clone(); + } else { + return Err(ApiError { + message: "device is already initialized".to_owned(), + }); + } + + Ok(json_return) +} + +#[wasm_bindgen] +pub fn create_new_device(birthday: u32, network_str: String) -> ApiResult { let network = Network::from_core_arg(&network_str)?; let wallet = generate_sp_wallet(None, Network::Regtest)?; @@ -207,20 +252,32 @@ pub fn create_new_wallet(birthday: u32, network_str: String) -> ApiResult, linked_with: String, revokation_output: String, @@ -327,6 +384,104 @@ pub fn login_user(fee_rate: u32) -> ApiResult<()> { Ok(()) } +#[wasm_bindgen] +pub fn logout() -> ApiResult<()> { + let mut spending_client = lock_spending_client()?; + + *spending_client = SpClient::default(); + + Ok(()) +} + +#[wasm_bindgen] +pub fn dump_watch_only_wallet() -> ApiResult { + let device = lock_local_device()?; + + Ok(serde_json::to_string(device.get_watch_only()).unwrap()) +} + +#[wasm_bindgen] +pub fn reset_message_cache() -> ApiResult<()> { + let mut cached_msg = lock_messages()?; + + *cached_msg = vec![]; + + debug_assert!(cached_msg.is_empty()); + + Ok(()) +} + +#[wasm_bindgen] +pub fn set_message_cache(msg_cache: Vec) -> ApiResult<()> { + let mut cached_msg = lock_messages()?; + + if !cached_msg.is_empty() { + return Err(ApiError { + message: "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?; + + 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 reset_device() -> ApiResult<()> { + let mut spending_client = lock_spending_client()?; + + *spending_client = SpClient::default(); + + let mut device = lock_local_device()?; + + *device = Device::default(); + + reset_message_cache()?; + + Ok(()) +} + +#[wasm_bindgen] +pub fn encrypt_remote_key( + remote_key: String, + aes_key: Option, + nonce: Option, +) -> ApiResult { + let mut device = lock_local_device()?; + let encrypted_key: Vec; + if let (Some(key_hex), Some(nonce_hex)) = (aes_key, nonce) { + // check that the hex we got for key and nonce is valid + let mut key = [0u8; 32]; + let mut nonce = [0u8; 12]; + key.copy_from_slice(&Vec::from_hex(&key_hex)?); + nonce.copy_from_slice(&Vec::from_hex(&nonce_hex)?); + encrypted_key = device.encrypt_for_remote_device( + SecretKey::from_str(&remote_key)?, + Some(key), + Some(nonce), + )?; + } else { + encrypted_key = + device.encrypt_for_remote_device(SecretKey::from_str(&remote_key)?, None, None)?; + } + Ok(encrypted_key.to_lower_hex_string()) +} + fn handle_recover_transaction( updated: HashMap, tx: &Transaction, @@ -357,48 +512,37 @@ fn handle_recover_transaction( // empty utxo_destroyed means we received this transaction if utxo_destroyed.is_empty() { // We first check for faucet transactions - if let Some(pos) = messages.iter().position(|m| { + if let Some(message) = messages.iter_mut().find(|m| { if m.status == CachedMessageStatus::FaucetWaiting { m.commitment.as_ref() == Some(&commitment_str) } else { false } }) { - let message = messages.get_mut(pos).unwrap(); - match message.status { - CachedMessageStatus::FaucetWaiting => { - message.status = CachedMessageStatus::FaucetComplete; - message.commited_in = utxo_created - .into_iter() - .next() - .map(|(outpoint, _)| *outpoint); - return Ok(message.clone()); - } - // Actually this is unreachable - CachedMessageStatus::FaucetComplete => return Ok(message.clone()), - _ => (), - } + message.status = CachedMessageStatus::Closed; + message.commited_in = utxo_created + .into_iter() + .next() + .map(|(outpoint, _)| *outpoint); + return Ok(message.clone()); } // we inspect inputs looking for links with previous tx for input in tx.input.iter() { - if let Some(pos) = messages.iter().position(|m| { - debug!("{:?}", Some(input.previous_output)); - m.confirmed_by == Some(input.previous_output) - }) { - let message = messages.get_mut(pos).unwrap(); - // If we are receiver, that's pretty much it, just set status to complete - message.status = CachedMessageStatus::Complete; + if let Some(message) = messages + .iter_mut() + .find(|m| m.confirmed_by == Some(input.previous_output)) + { + // If we are receiver, that's pretty much it + message.status = CachedMessageStatus::Trusted; return Ok(message.clone()); - } else if let Some(pos) = messages - .iter() - .position(|m| m.commited_in == Some(input.previous_output)) + } else if let Some(message) = messages + .iter_mut() + .find(|m| m.commited_in == Some(input.previous_output)) { // sender needs to spent it back again to receiver let (outpoint, output) = utxo_created.into_iter().next().unwrap(); - let message = messages.get_mut(pos).unwrap(); - message.confirmed_by = Some(outpoint.clone()); message.status = CachedMessageStatus::MustSpendConfirmation; @@ -420,7 +564,7 @@ fn handle_recover_transaction( ); let mut plaintext: Vec = vec![]; - if let Some(cipher_pos) = messages.iter().position(|m| { + if let Some(message) = messages.iter_mut().find(|m| { if m.status != CachedMessageStatus::CipherWaitingTx { return false; } @@ -432,15 +576,14 @@ fn handle_recover_transaction( return false; } }) { - let message = messages.get_mut(cipher_pos).unwrap(); - let (outpoint, output) = utxo_created.into_iter().next().unwrap(); let cipher_msg: CipherMessage = serde_json::from_slice(&plaintext)?; message.commited_in = Some(outpoint.clone()); message.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string()); message.commitment = Some(commitment_str); - message.plaintext = Some(cipher_msg.message); + message.plaintext.push(cipher_msg.message); + message.ciphertext = None; message.sender = Some(cipher_msg.sender); message.recipient = Some(sp_wallet.get_client().get_receiving_address()); message.status = CachedMessageStatus::ReceivedMustConfirm; @@ -466,7 +609,6 @@ fn handle_recover_transaction( // We only need to return the message // eiter this is notification, a challenge, or response to a challenge // if notification, commitment is the same than in the message - // if challenge or response, commitment is H(commitment | b_scan), b_scan being different depending on who we are if let Some(message) = messages.iter().find(|m| { if commitment.is_empty() || m.commitment.is_none() { return false; @@ -480,23 +622,29 @@ fn handle_recover_transaction( .unwrap() == commitment } - CachedMessageStatus::MustSpendConfirmation - | CachedMessageStatus::ReceivedMustConfirm => { - // we compute the potential commitment - let m_commitment = m - .commitment - .as_ref() - .map(|c| Vec::from_hex(&c).unwrap()) - .unwrap(); - let mut buf = [0u8; 64]; - buf[..32].copy_from_slice(&m_commitment); - buf[32..] - .copy_from_slice(&sp_wallet.get_client().get_scan_key().secret_bytes()); - - let mut engine = sha256::HashEngine::default(); - engine.write_all(&buf).unwrap(); - let hash = sha256::Hash::from_engine(engine); - hash.to_byte_array().to_vec() == commitment + CachedMessageStatus::ReceivedMustConfirm => { + // we look for a message that has commited_in as input of the transaction + if let Some(_) = tx + .input + .iter() + .find(|i| Some(i.previous_output) == m.commited_in) + { + return true; + } else { + return false; + } + } + CachedMessageStatus::MustSpendConfirmation | CachedMessageStatus::Trusted => { + // we look for a message that has confirm_by as input + if let Some(_) = tx + .input + .iter() + .find(|i| Some(i.previous_output) == m.confirmed_by) + { + return true; + } else { + return false; + } } _ => return false, } @@ -504,7 +652,7 @@ fn handle_recover_transaction( return Ok(message.clone()); } else { return Err(anyhow::Error::msg( - "We spent a transaction for a commitment we don't know", + "Failed to find the message for one of our transactions", )); } } @@ -573,21 +721,35 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult // let's try to decrypt with keys we found in transactions but haven't used yet let mut messages = lock_messages()?; let cipher = Vec::from_hex(&ank_msg.content.trim_matches('\"'))?; - let cipher_pos = messages.iter().position(|m| { - debug!("Trying message: {:?}", m); - if m.status != CachedMessageStatus::TxWaitingCipher { - return false; + if let Some(message) = messages.iter_mut().find(|m| { + // debug!("Trying message: {:?}", m); + match m.status { + CachedMessageStatus::TxWaitingCipher | CachedMessageStatus::Trusted => { + m.try_decrypt_cipher(cipher.clone()).is_ok() + } + _ => return false, } - m.try_decrypt_cipher(cipher.clone()).is_ok() - }); - if cipher_pos.is_some() { - let mut message = messages.get_mut(cipher_pos.unwrap()).unwrap(); + }) { let plain = message.try_decrypt_cipher(cipher).unwrap(); let cipher_msg: CipherMessage = serde_json::from_slice(&plain)?; - message.plaintext = Some(cipher_msg.message); - message.sender = Some(cipher_msg.sender); - message.ciphertext = Some(ank_msg.content); - message.status = CachedMessageStatus::ReceivedMustConfirm; + if message.status == CachedMessageStatus::TxWaitingCipher { + // does the retrieved message match with the commited hash? + let hash = create_commitment(serde_json::to_string(&cipher_msg)?); + if Some(hash) != message.commitment { + return Err(ApiError { + message: "Message doesn't match commitment".to_owned(), + }); + } + message.plaintext.push(cipher_msg.message); + message.sender = Some(cipher_msg.sender); + message.ciphertext = None; + message.status = CachedMessageStatus::ReceivedMustConfirm; + } else { + // We're receiving a message for some action already engaged + // We could check the sender, but do we really care since we alredy trust it? + // Let's update the message by pushing what we just found out + message.plaintext.push(cipher_msg.message); + } return Ok(message.clone()); } else { // let's keep it in case we receive the transaction later @@ -608,20 +770,20 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult } #[wasm_bindgen] -pub fn get_outpoints_for_user() -> ApiResult { +pub fn get_outputs() -> ApiResult { let device = lock_local_device()?; let outputs = device.get_watch_only().get_outputs().clone(); - Ok(outputs_list(outputs)) + Ok(JsValue::from_serde(&outputs.to_outpoints_list())?) } #[wasm_bindgen] -pub fn get_available_amount_for_user() -> ApiResult { +pub fn get_available_amount() -> ApiResult { let device = lock_local_device()?; Ok(device.get_watch_only().get_outputs().get_balance().to_sat()) } -#[derive(Tsify, Serialize, Deserialize, Default)] +#[derive(Debug, Tsify, Serialize, Deserialize, Default)] #[tsify(into_wasm_abi, from_wasm_abi)] #[allow(non_camel_case_types)] pub struct createTransactionReturn { @@ -661,7 +823,7 @@ pub fn answer_confirmation_transaction( let spending_client = lock_spending_client()?.clone(); - let sp_wallet = SpWallet::new(spending_client, Some(current_outputs))?; + let sp_wallet = SpWallet::new(spending_client, Some(current_outputs), vec![])?; let recipient = Recipient { address: sp_address.into(), @@ -669,21 +831,20 @@ pub fn answer_confirmation_transaction( nb_outputs: 1, }; - let confirmed_by = message.confirmed_by.clone().unwrap(); - let commited_in = message.commited_in.clone().unwrap(); + let confirmed_by = message.confirmed_by.as_ref().unwrap(); - let signed_psbt = create_transaction_spend_outpoint( - &confirmed_by, + let signed_psbt = create_transaction( + &vec![confirmed_by], &sp_wallet, recipient, - &commited_in.txid, None, Amount::from_sat(fee_rate.into()), + message.recipient.clone(), )?; let final_tx = signed_psbt.extract_tx()?; - message.status = CachedMessageStatus::Complete; + message.status = CachedMessageStatus::Trusted; Ok(createTransactionReturn { txid: final_tx.txid().to_string(), @@ -701,7 +862,7 @@ pub fn create_confirmation_transaction( let mut messages = lock_messages()?; let message: &mut CachedMessage; if let Some(m) = messages.iter_mut().find(|m| m.id == message_id) { - if m.sender.is_none() || m.commited_in.is_none() { + if m.sender.is_none() || m.commited_in.is_none() || m.plaintext.is_empty() { return Err(ApiError { message: "Invalid network message".to_owned(), }); @@ -716,22 +877,11 @@ pub fn create_confirmation_transaction( let sp_address: SilentPaymentAddress = message.sender.as_ref().unwrap().as_str().try_into()?; - let mut local_device = lock_local_device()?; - - // Are we waiting for pairing? - let remote_key_cipher: Option>; - if !local_device.is_linked() { - let remote_spend_sk = SecretKey::from_str(message.plaintext.as_deref().unwrap())?; - remote_key_cipher = Some(local_device.encrypt_for_remote_device(remote_spend_sk)?); - } else { - remote_key_cipher = None; - } - - let current_outputs = local_device.get_watch_only().get_outputs().clone(); + let current_outputs = lock_local_device()?.get_watch_only().get_outputs().clone(); let spending_client = lock_spending_client()?.clone(); - let sp_wallet = SpWallet::new(spending_client, Some(current_outputs))?; + let sp_wallet = SpWallet::new(spending_client, Some(current_outputs), vec![])?; let recipient = Recipient { address: sp_address.into(), @@ -739,15 +889,15 @@ pub fn create_confirmation_transaction( nb_outputs: 1, }; - let commited_in = message.commited_in.clone().unwrap(); + let commited_in = message.commited_in.as_ref().unwrap(); - let signed_psbt = create_transaction_spend_outpoint( - &commited_in, + let signed_psbt = create_transaction( + &vec![commited_in], &sp_wallet, recipient, - &commited_in.txid, - remote_key_cipher, + None, Amount::from_sat(fee_rate.into()), + message.sender.clone(), )?; // what's the vout of the output sent to sender? @@ -778,18 +928,37 @@ pub fn create_pairing_transaction( ) -> ApiResult { let message: CipherMessage; { - let mut spending_wallet = lock_spending_client()?; + let mut spending_client = lock_spending_client()?; - let our_address = spending_wallet.get_receiving_address(); - let spend_sk: SecretKey = spending_wallet.get_spend_key().try_into()?; + let our_address = spending_client.get_receiving_address(); + let spend_sk: SecretKey = spending_client.get_spend_key().try_into()?; message = CipherMessage::new(our_address, format!("{}", spend_sk.display_secret())); - - // we forget our own wallet - *spending_wallet = SpClient::default(); } - create_notification_transaction(address, message, fee_rate) + let mut res = create_notification_transaction(address, message, fee_rate); + + // we slightly alter the message + if let Ok(ref mut new_msg) = res { + new_msg.new_network_msg.plaintext = vec![]; // we don't want to let that information available + new_msg.new_network_msg.status = CachedMessageStatus::Trusted; + } else { + return res; + } + + // Update the msg in cache + let mut messages = lock_messages()?; + let mut updated = res.as_ref().unwrap().new_network_msg.clone(); + let pos = messages.iter().position(|m| m.id == updated.id).unwrap(); + // clone the cached message and erase the ciphertext + updated.ciphertext = None; + messages[pos] = updated; + + // we forget our own wallet + let mut spending_client = lock_spending_client()?; + *spending_client = SpClient::default(); + + res } #[wasm_bindgen] @@ -815,7 +984,7 @@ pub fn create_login_transaction(fee_rate: u32) -> ApiResult ApiResult { let sp_address: SilentPaymentAddress = address.as_str().try_into()?; @@ -826,7 +995,7 @@ pub fn create_notification_transaction( let spending_client = lock_spending_client()?.clone(); - let sp_wallet = SpWallet::new(spending_client, Some(current_outputs))?; + let sp_wallet = SpWallet::new(spending_client, Some(current_outputs), vec![])?; let recipient = Recipient { address: sp_address.into(), @@ -834,18 +1003,20 @@ pub fn create_notification_transaction( nb_outputs: 1, }; - let commitment = create_commitment(serde_json::to_string(&message)?); + let commitment = create_commitment(serde_json::to_string(&cipher_message)?); - let signed_psbt = create_transaction_for_address_with_shared_secret( - recipient, + let signed_psbt = create_transaction( + &vec![], &sp_wallet, - Some(&commitment), + recipient, + Some(Vec::from_hex(&commitment)?), Amount::from_sat(fee_rate.into()), + None, )?; - let psbt = Psbt::from_str(&signed_psbt)?; - - let partial_secret = sp_wallet.get_client().get_partial_secret_from_psbt(&psbt)?; + let partial_secret = sp_wallet + .get_client() + .get_partial_secret_from_psbt(&signed_psbt)?; let shared_point = sp_utils::sending::calculate_ecdh_shared_secret( &sp_address.get_scan_key(), @@ -854,26 +1025,26 @@ pub fn create_notification_transaction( let shared_secret = AnkSharedSecret::new(shared_point); - debug!( - "Created transaction with secret {}", - shared_secret.to_byte_array().to_lower_hex_string() - ); + // debug!( + // "Created transaction with secret {}", + // shared_secret.to_byte_array().to_lower_hex_string() + // ); let cipher = encrypt_with_key( - serde_json::to_string(&message)?, + serde_json::to_string(&cipher_message)?, shared_secret.to_byte_array().to_lower_hex_string(), )?; // update our cache - let sp_address2vouts = map_outputs_to_sp_address(&signed_psbt)?; + let sp_address2vouts = map_outputs_to_sp_address(&signed_psbt.to_string())?; let recipients_vouts = sp_address2vouts .get::(&address) .expect("recipients didn't change") .as_slice(); // for now let's just take the smallest vout that belongs to the recipient - let final_tx = psbt.extract_tx()?; + let final_tx = signed_psbt.extract_tx()?; let mut new_msg = CachedMessage::new(); - new_msg.plaintext = Some(message.message); + new_msg.plaintext.push(cipher_message.message); new_msg.ciphertext = Some(cipher); new_msg.commitment = Some(commitment); new_msg.commited_in = Some(OutPoint { @@ -882,7 +1053,7 @@ pub fn create_notification_transaction( }); new_msg.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string()); new_msg.recipient = Some(address); - new_msg.sender = Some(sp_wallet.get_client().get_receiving_address()); + new_msg.sender = Some(cipher_message.sender); new_msg.status = CachedMessageStatus::SentWaitingConfirmation; lock_messages()?.push(new_msg.clone()); diff --git a/src/user.rs b/src/user.rs index faa1616..10c7594 100644 --- a/src/user.rs +++ b/src/user.rs @@ -28,8 +28,6 @@ use sdk_common::crypto::{ AeadCore, Aes256Decryption, Aes256Encryption, Aes256Gcm, HalfKey, KeyInit, Purpose, }; - - pub static LOCAL_DEVICE: OnceLock> = OnceLock::new(); pub fn lock_local_device() -> Result> { @@ -82,7 +80,7 @@ impl Device { .expect("watch_only_client creation failed"); let mut watch_only_wallet = - SpWallet::new(watch_only_client, None).expect("watch_only_wallet creation failed"); + SpWallet::new(watch_only_client, None, vec![]).expect("watch_only_wallet creation failed"); let spend_sk_cipher = vec![]; let remote_device_key = [0; 32]; @@ -116,12 +114,34 @@ impl Device { self.spend_sk_cipher.clone() } - pub fn encrypt_for_remote_device(&mut self, remote_spend_sk: SecretKey) -> Result> { - // encrypt with the remote_device_key - let encryption = Aes256Encryption::new( - Purpose::ThirtyTwoBytes, - remote_spend_sk.secret_bytes().to_vec(), - )?; + pub fn encrypt_for_remote_device( + &mut self, + remote_spend_sk: SecretKey, + aes_key: Option<[u8; 32]>, + nonce: Option<[u8; 12]>, + ) -> Result> { + let encryption: Aes256Encryption; + match (aes_key, nonce) { + (Some(key), Some(nonce)) => { + encryption = Aes256Encryption::import_key( + Purpose::ThirtyTwoBytes, + remote_spend_sk.secret_bytes().to_vec(), + key, + nonce, + )?; + } + (None, None) => { + encryption = Aes256Encryption::new( + Purpose::ThirtyTwoBytes, + remote_spend_sk.secret_bytes().to_vec(), + )?; + } + _ => { + return Err(Error::msg( + "aes_key and nonce must either both be set or empty", + )) + } + } let remote_spend_sk_cipher = encryption.encrypt_with_aes_key(); self.remote_device_key = encryption.export_key(); remote_spend_sk_cipher diff --git a/src/wallet.rs b/src/wallet.rs index 578861d..188c610 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -22,7 +22,7 @@ pub fn generate_sp_wallet(label: Option, network: Network) -> anyhow::Re our_address.to_string() ); - let res = SpWallet::new(sp_client, None)?; + let res = SpWallet::new(sp_client, None, vec![])?; Ok(res) } diff --git a/tests/browser.rs b/tests/browser.rs new file mode 100644 index 0000000..b46542b --- /dev/null +++ b/tests/browser.rs @@ -0,0 +1,479 @@ +use std::collections::HashMap; + +use log::debug; +use sdk_client::api::{ + answer_confirmation_transaction, create_confirmation_transaction, + create_notification_transaction, encrypt_remote_key, get_outputs, pair_device, + parse_network_msg, reset_device, restore_device_from_sp_wallet, set_message_cache, setup, +}; +use sdk_common::network::{ + AnkNetworkMsg, CachedMessage, CipherMessage, NewTxMessage, TrustedChannel, +}; +use sdk_common::sp_client::bitcoin::consensus::deserialize; +use sdk_common::sp_client::bitcoin::hex::FromHex; +use sdk_common::sp_client::bitcoin::{OutPoint, ScriptBuf, Transaction}; +use sdk_common::sp_client::silentpayments::utils::receiving::{ + calculate_tweak_data, get_pubkey_from_input, +}; +use sdk_common::sp_client::spclient::{OwnedOutput, SpWallet}; +use serde_json; + +use tsify::JsValueSerdeExt; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); + +// We're using alice and bob for clarity, but it's important to remember that here Alice and Bob are the same person +const ALICE_START_WALLET: &str = "{\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"e3d8922a41a7cb1a84a90f4334e987bb5ea2df6a1fdf44f789b5302de119f9e2\",\"spend_key\":{\"Secret\":\"93292e5b21042c6cfc742ba30e9d2a1e01609b12d154a1825184ed12c7b9631b\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,104,242,105,185,6,124,208,34,44,149,52,163,38,63,221,150,12,198,24,95,143,126,235,37,149,233,88,118,32,86,233,152],\"spend_pubkey\":[3,198,82,196,243,12,59,126,109,143,144,157,128,176,168,94,54,134,232,139,115,102,11,178,128,244,239,251,40,228,67,153,72],\"change_label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"labels\":[[\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",[2,244,223,255,57,50,216,27,133,112,138,69,120,126,85,110,6,242,141,33,136,191,82,164,241,54,179,115,84,161,145,174,154]]]}},\"outputs\":{\"wallet_fingerprint\":[187,119,108,230,171,125,106,11],\"birthday\":1620,\"last_scan\":2146,\"outputs\":{\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"}}}}"; +const ALICE_CHALLENGE_WALLET: &str = "{\"client\":{\"label\":\"default\",\"scan_sk\":\"e3d8922a41a7cb1a84a90f4334e987bb5ea2df6a1fdf44f789b5302de119f9e2\",\"spend_key\":{\"Secret\":\"93292e5b21042c6cfc742ba30e9d2a1e01609b12d154a1825184ed12c7b9631b\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,104,242,105,185,6,124,208,34,44,149,52,163,38,63,221,150,12,198,24,95,143,126,235,37,149,233,88,118,32,86,233,152],\"spend_pubkey\":[3,198,82,196,243,12,59,126,109,143,144,157,128,176,168,94,54,134,232,139,115,102,11,178,128,244,239,251,40,228,67,153,72],\"change_label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"labels\":[[\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",[2,244,223,255,57,50,216,27,133,112,138,69,120,126,85,110,6,242,141,33,136,191,82,164,241,54,179,115,84,161,145,174,154]]]},\"network\":\"regtest\"},\"outputs\":{\"wallet_fingerprint\":[187,119,108,230,171,125,106,11],\"birthday\":1620,\"last_scan\":0,\"outputs\":{\"d5edbf1f60ae4fd7d698626b44ed1e85edddf65b45f62e5f82928a9e12759db9:1\":{\"blockheight\":0,\"tweak\":\"28994b2f2ee8e5f35d6d2dcdee1580d0455fe3dc37f81e0a36864473ee86f5c4\",\"amount\":3937246,\"script\":\"51207d06144e982b6fd38a85d6152f1f95746b059553258a31e04df97fe6b5f19ea1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"},\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"d5edbf1f60ae4fd7d698626b44ed1e85edddf65b45f62e5f82928a9e12759db9\"}}}}}"; +const ALICE_ANSWER_WALLET: &str = "{\"client\":{\"label\":\"default\",\"scan_sk\":\"e3d8922a41a7cb1a84a90f4334e987bb5ea2df6a1fdf44f789b5302de119f9e2\",\"spend_key\":{\"Secret\":\"93292e5b21042c6cfc742ba30e9d2a1e01609b12d154a1825184ed12c7b9631b\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,104,242,105,185,6,124,208,34,44,149,52,163,38,63,221,150,12,198,24,95,143,126,235,37,149,233,88,118,32,86,233,152],\"spend_pubkey\":[3,198,82,196,243,12,59,126,109,143,144,157,128,176,168,94,54,134,232,139,115,102,11,178,128,244,239,251,40,228,67,153,72],\"change_label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"labels\":[[\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",[2,244,223,255,57,50,216,27,133,112,138,69,120,126,85,110,6,242,141,33,136,191,82,164,241,54,179,115,84,161,145,174,154]]]},\"network\":\"regtest\"},\"outputs\":{\"wallet_fingerprint\":[187,119,108,230,171,125,106,11],\"birthday\":1620,\"last_scan\":0,\"outputs\":{\"57ca073676fa6130397ce1e4738278952483cee943daa1664b9a1807d4700066:0\":{\"blockheight\":0,\"tweak\":\"b99660cd873026aebeb378ae2ff32aa1c79a5946c462a36a01247f8afcfc5dba\",\"amount\":1046,\"script\":\"51209b80b6e8b3e93437c82a26c977860fb4d2bef9f27f6332f811a41187592502ed\",\"label\":null,\"spend_status\":\"Unspent\"},\"d5edbf1f60ae4fd7d698626b44ed1e85edddf65b45f62e5f82928a9e12759db9:1\":{\"blockheight\":0,\"tweak\":\"28994b2f2ee8e5f35d6d2dcdee1580d0455fe3dc37f81e0a36864473ee86f5c4\",\"amount\":3937246,\"script\":\"51207d06144e982b6fd38a85d6152f1f95746b059553258a31e04df97fe6b5f19ea1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"},\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"d5edbf1f60ae4fd7d698626b44ed1e85edddf65b45f62e5f82928a9e12759db9\"}}}}}"; +const BOB_START_WALLET: &str = "{\"client\":{\"label\":\"default\",\"scan_sk\":\"0de90b7195c1380d5fde13de3f1d66d53423a9896314839e36ba672653af60b4\",\"spend_key\":{\"Secret\":\"affe686075ecbe17b8ce7de45ec31314804259d0a4bc1c863de21ffd6dc598f8\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,85,96,92,243,247,237,192,205,9,178,146,101,237,132,232,15,2,69,138,31,118,76,140,142,207,90,13,192,94,254,150,133],\"spend_pubkey\":[3,5,157,91,250,169,41,61,190,37,30,98,152,253,180,138,250,86,162,102,82,148,130,220,44,153,127,83,43,246,93,17,232],\"change_label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"labels\":[[\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",[2,237,237,247,213,154,87,34,239,168,235,87,122,152,94,41,35,101,184,201,58,201,6,185,58,157,52,208,129,167,2,224,198]]]},\"network\":\"regtest\"},\"outputs\":{\"wallet_fingerprint\":[203,200,4,248,139,36,241,232],\"birthday\":2146,\"last_scan\":2146,\"outputs\":{\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":\"Unspent\"}}}}"; +const BOB_CHALLENGE_WALLET: &str = "{\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"0de90b7195c1380d5fde13de3f1d66d53423a9896314839e36ba672653af60b4\",\"spend_key\":{\"Secret\":\"affe686075ecbe17b8ce7de45ec31314804259d0a4bc1c863de21ffd6dc598f8\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,85,96,92,243,247,237,192,205,9,178,146,101,237,132,232,15,2,69,138,31,118,76,140,142,207,90,13,192,94,254,150,133],\"spend_pubkey\":[3,5,157,91,250,169,41,61,190,37,30,98,152,253,180,138,250,86,162,102,82,148,130,220,44,153,127,83,43,246,93,17,232],\"change_label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"labels\":[[\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",[2,237,237,247,213,154,87,34,239,168,235,87,122,152,94,41,35,101,184,201,58,201,6,185,58,157,52,208,129,167,2,224,198]]]}},\"outputs\":{\"wallet_fingerprint\":[203,200,4,248,139,36,241,232],\"birthday\":2146,\"last_scan\":0,\"outputs\":{\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":\"Unspent\"},\"d5edbf1f60ae4fd7d698626b44ed1e85edddf65b45f62e5f82928a9e12759db9:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":\"Unspent\"}}}}"; +const BOB_ANSWER_WALLET: &str = "{\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"0de90b7195c1380d5fde13de3f1d66d53423a9896314839e36ba672653af60b4\",\"spend_key\":{\"Secret\":\"affe686075ecbe17b8ce7de45ec31314804259d0a4bc1c863de21ffd6dc598f8\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,85,96,92,243,247,237,192,205,9,178,146,101,237,132,232,15,2,69,138,31,118,76,140,142,207,90,13,192,94,254,150,133],\"spend_pubkey\":[3,5,157,91,250,169,41,61,190,37,30,98,152,253,180,138,250,86,162,102,82,148,130,220,44,153,127,83,43,246,93,17,232],\"change_label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"labels\":[[\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",[2,237,237,247,213,154,87,34,239,168,235,87,122,152,94,41,35,101,184,201,58,201,6,185,58,157,52,208,129,167,2,224,198]]]}},\"outputs\":{\"wallet_fingerprint\":[203,200,4,248,139,36,241,232],\"birthday\":2146,\"last_scan\":0,\"outputs\":{\"wallet_fingerprint\":[203,200,4,248,139,36,241,232],\"birthday\":2146,\"last_scan\":0,\"outputs\":{\"d5edbf1f60ae4fd7d698626b44ed1e85edddf65b45f62e5f82928a9e12759db9:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":{\"Spent\":\"57ca073676fa6130397ce1e4738278952483cee943daa1664b9a1807d4700066\"}},\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":\"Unspent\"}}}}"; +const ALICE_SPK: &str = "51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1"; +const REVOKATION_OUTPUT: &str = + "3dd51588af6cc2e4ff8e405fd2620f19c8f4e09e05692ad57a9208a061687295:3"; +const ALICE_CHALLENGE_CACHE: &str = "{\"id\":1283337801,\"status\":\"SentWaitingConfirmation\",\"ciphertext\":\"d53ec574b09068cc661a76e6934cb9131df1b8d84b0ec791cb2c2789c42aae53bf2e7d514a00b2c27a78a786fee97f55dd022a99551770c4f05edfc4296993b2f82104933f61cd0d7c2e02a21188c58b492ac0a190e711ddc697505e106df087f8f9d3591aa30798608c2a0eb1e7c124d54e88dd9271884a3fe7f24f628a150d0dc90ded4753f9aa4e2730c7a339bfd7ef0faa02ee6d4cd3b1de36f8796485bd6bd8eee0e78c1e6d6c822dd9532df4c963835ea412bb36827cd89d3466\",\"plaintext\":[\"TEST\"],\"commited_in\":\"d5edbf1f60ae4fd7d698626b44ed1e85edddf65b45f62e5f82928a9e12759db9:0\",\"tie_by\":null,\"commitment\":\"d12f3c5b37240bc3abf2976f41fdf9a594f0680aafd2781ac448f80440fbeb99\",\"sender\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"recipient\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"shared_secret\":\"5a28562161f939bf77983df807dd914e73f02ea67a21ed976d214452887ae43e\",\"key\":null,\"confirmed_by\":null,\"timestamp\":1720966687785,\"error\":null}"; +const ALICE_ANSWER_CACHE: &str = "{\"id\":1283337801,\"status\":\"MustSpendConfirmation\",\"ciphertext\":\"d53ec574b09068cc661a76e6934cb9131df1b8d84b0ec791cb2c2789c42aae53bf2e7d514a00b2c27a78a786fee97f55dd022a99551770c4f05edfc4296993b2f82104933f61cd0d7c2e02a21188c58b492ac0a190e711ddc697505e106df087f8f9d3591aa30798608c2a0eb1e7c124d54e88dd9271884a3fe7f24f628a150d0dc90ded4753f9aa4e2730c7a339bfd7ef0faa02ee6d4cd3b1de36f8796485bd6bd8eee0e78c1e6d6c822dd9532df4c963835ea412bb36827cd89d3466\",\"plaintext\":[\"TEST\"],\"commited_in\":\"d5edbf1f60ae4fd7d698626b44ed1e85edddf65b45f62e5f82928a9e12759db9:0\",\"tie_by\":null,\"commitment\":\"d12f3c5b37240bc3abf2976f41fdf9a594f0680aafd2781ac448f80440fbeb99\",\"sender\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"recipient\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"shared_secret\":\"5a28562161f939bf77983df807dd914e73f02ea67a21ed976d214452887ae43e\",\"key\":null,\"confirmed_by\":\"57ca073676fa6130397ce1e4738278952483cee943daa1664b9a1807d4700066:0\",\"timestamp\":1720966687785,\"error\":null}"; +const BOB_CHALLENGE_CACHE: &str = "{\"id\":2639151119,\"status\":\"ReceivedMustConfirm\",\"ciphertext\":null,\"plaintext\":[\"TEST\"],\"commited_in\":\"d5edbf1f60ae4fd7d698626b44ed1e85edddf65b45f62e5f82928a9e12759db9:0\",\"tie_by\":null,\"commitment\":\"d12f3c5b37240bc3abf2976f41fdf9a594f0680aafd2781ac448f80440fbeb99\",\"sender\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"recipient\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"shared_secret\":\"5a28562161f939bf77983df807dd914e73f02ea67a21ed976d214452887ae43e\",\"key\":null,\"confirmed_by\":null,\"timestamp\":1721315780531,\"error\":null}"; +const BOB_ANSWER_CACHE: &str = "{\"id\":2639151119,\"status\":\"ReceivedMustConfirm\",\"ciphertext\":null,\"plaintext\":[\"TEST\"],\"commited_in\":\"d5edbf1f60ae4fd7d698626b44ed1e85edddf65b45f62e5f82928a9e12759db9:0\",\"tie_by\":null,\"commitment\":\"d12f3c5b37240bc3abf2976f41fdf9a594f0680aafd2781ac448f80440fbeb99\",\"sender\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"recipient\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"shared_secret\":\"5a28562161f939bf77983df807dd914e73f02ea67a21ed976d214452887ae43e\",\"key\":null,\"confirmed_by\":\"57ca073676fa6130397ce1e4738278952483cee943daa1664b9a1807d4700066:0\",\"timestamp\":1721315780531,\"error\":null}"; + +fn helper_switch_device(wallet: String) { + reset_device().unwrap(); + // debug!("{}", wallet); + restore_device_from_sp_wallet(wallet.clone()).unwrap(); + // debug!("Restored device with wallet {}", wallet); +} + +fn helper_get_alice_address() -> String { + let wallet: SpWallet = serde_json::from_str(ALICE_START_WALLET).unwrap(); + wallet.get_client().get_receiving_address() +} + +fn helper_get_bob_address() -> String { + let wallet: SpWallet = serde_json::from_str(BOB_START_WALLET).unwrap(); + wallet.get_client().get_receiving_address() +} + +fn helper_get_tweak_data(transaction: &str, spk: ScriptBuf) -> String { + let tx = deserialize::(&Vec::from_hex(transaction).unwrap()).unwrap(); + let prevout = tx.input.get(0).unwrap().to_owned(); + let outpoint_data = ( + prevout.previous_output.txid.to_string(), + prevout.previous_output.vout, + ); + let input_pubkey = + get_pubkey_from_input(&vec![], &prevout.witness.to_vec(), spk.as_bytes()).unwrap(); + let tweak_data = + calculate_tweak_data(&vec![&input_pubkey.unwrap()], &vec![outpoint_data]).unwrap(); + tweak_data.to_string() +} + +fn helper_parse_transaction(transaction: &str, tweak_data: String) -> CachedMessage { + let new_tx_msg = + serde_json::to_string(&NewTxMessage::new(transaction.to_owned(), Some(tweak_data))) + .unwrap(); + let network_msg = serde_json::to_string(&AnkNetworkMsg::new( + sdk_common::network::AnkFlag::NewTx, + &new_tx_msg, + )) + .unwrap(); + let result = parse_network_msg(network_msg, 1); + match result { + Ok(m) => m, + Err(e) => panic!("Unexpected error: {}", e.message), + } +} + +fn helper_parse_ank_msg(ank_network_msg: String) -> CachedMessage { + let result = parse_network_msg(ank_network_msg, 1); + match result { + Ok(r) => return r, + Err(e) => panic!("Unexpected error: {}", e.message), + }; +} + +fn helper_link_alice() { + const ALICE_SPEND_KEY: &str = + "93292e5b21042c6cfc742ba30e9d2a1e01609b12d154a1825184ed12c7b9631b"; + let encrypted_alice_key = encrypt_remote_key( + ALICE_SPEND_KEY.to_owned(), + Some("073ec0cd90cd77f5486cbfb586ed639c4d435d39bb038dc905d7a65762d29ff9".to_owned()), + Some("46b4e8e9cfb1b6fd139d480f".to_owned()), + ) + .unwrap(); + pair_device( + Vec::from_hex(&encrypted_alice_key).unwrap(), + helper_get_alice_address(), + REVOKATION_OUTPUT.to_owned(), + ) + .unwrap(); +} + +fn helper_link_bob() { + const BOB_ENCRYPTED_KEY: &str = "62f10cffb1ed127ef88826c0e65d21dc337c763db53a3d814e1bf21460b53d84d612995ab3b272ac395221bf49cb54df6250502fc8941ba06d85bd58"; + pair_device( + Vec::from_hex(BOB_ENCRYPTED_KEY).unwrap(), + helper_get_alice_address(), + REVOKATION_OUTPUT.to_owned(), + ) + .unwrap(); +} + +// #[wasm_bindgen_test] +// fn test_pairing_transaction() { +// setup(); + +// helper_switch_bob(); + +// // bob sends pairing transaction to alice +// let result = create_pairing_transaction(helper_get_alice_address(), 1); +// let bob_pairing_transaction = match result { +// Ok(t) => t, +// Err(e) => { +// panic!("create_pairing_transaction failed with {}", e.message); +// } +// }; + +// // get the tweak data to add to the transaction +// let bob_pairing_tweak_data = helper_get_tweak_data( +// &bob_pairing_transaction.transaction, +// ScriptBuf::from_hex(BOB_SPK).unwrap(), +// ); + +// // dump bob message cache +// let bob_msg_cache = dump_message_cache().unwrap(); +// reset_message_cache().unwrap(); + +// // switch to alice +// helper_switch_alice(); + +// // alice receives and parses that transaction +// helper_parse_transaction(&bob_pairing_transaction.transaction, bob_pairing_tweak_data); + +// // alice receives and parses the message +// let bob_sent_ank_msg = AnkNetworkMsg::new( +// sdk_common::network::AnkFlag::Cipher, +// &bob_pairing_transaction.new_network_msg.ciphertext.unwrap(), +// ); +// let mut pairing_msg = helper_parse_ank_msg(bob_sent_ank_msg.to_string()); + +// // Alice takes the key out of the plaintext cached message and returns it encrypted to Bob +// let bob_key = pairing_msg.plaintext.pop().unwrap(); +// let encrypted_bob_key = encrypt_remote_key( +// bob_key, +// Some("d8ac223089c8f5e575a06bc0e7071a0dc1124688722ecb34ccf9b3c95e5baaa8".to_owned()), +// Some("62f10cffb1ed127ef88826c0".to_owned()), +// ) +// .unwrap(); +// let cipher_msg = CipherMessage::new(helper_get_alice_address(), encrypted_bob_key.clone()); +// let encrypted_payload = +// encrypt_with_key(cipher_msg.to_string(), pairing_msg.shared_secret.unwrap()).unwrap(); +// let alice_response_msg = +// AnkNetworkMsg::new(sdk_common::network::AnkFlag::Cipher, &encrypted_payload).to_string(); + +// // same switch back to Bob +// // let alice_msg_cache = dump_message_cache().unwrap(); +// reset_message_cache().unwrap(); + +// helper_switch_bob(); + +// set_message_cache(bob_msg_cache).unwrap(); + +// let bob_received_msg = helper_parse_ank_msg(alice_response_msg); +// assert!(*bob_received_msg.plaintext.get(0).unwrap() == encrypted_bob_key); + +// // take the revokation output +// let keypair = Keypair::from_seckey_str( +// &Secp256k1::signing_only(), +// &bob_received_msg.shared_secret.clone().unwrap(), +// ) +// .unwrap(); +// let spk = ScriptBuf::new_p2tr_tweaked(keypair.x_only_public_key().0.dangerous_assume_tweaked()); +// let pairing_transaction = +// deserialize::(&Vec::from_hex(&bob_pairing_transaction.transaction).unwrap()) +// .unwrap(); + +// let vout = pairing_transaction +// .output +// .iter() +// .position(|o| o.script_pubkey == spk) +// .unwrap(); + +// let revokation_outpoint = +// OutPoint::new(pairing_transaction.txid(), vout.try_into().unwrap()).to_string(); + +// pair_device( +// Vec::from_hex(bob_received_msg.plaintext.get(0).unwrap()).unwrap(), +// bob_received_msg.recipient.clone().unwrap(), +// revokation_outpoint.clone(), +// ) +// .unwrap(); + +// // Now we can set a trusted channel, which is basically the pairing +// let mut trusted_channel = TrustedChannel::new(bob_received_msg.recipient.unwrap()).unwrap(); +// trusted_channel +// .set_revokation(revokation_outpoint, bob_received_msg.shared_secret.unwrap()) +// .unwrap(); +// } + +#[wasm_bindgen_test] +fn test_alice_notifies_bob() { + setup(); + debug!("==============================================\nStarting test_alice_notifies_bob\n=============================================="); + + // Alice notifies Bob + helper_switch_device(ALICE_START_WALLET.to_owned()); + helper_link_alice(); + + let cipher_msg = CipherMessage::new(helper_get_alice_address(), "TEST".to_owned()); + + debug!("Alice notifies Bob"); + let notification_tx = + create_notification_transaction(helper_get_bob_address(), cipher_msg, 1).unwrap(); + + // debug!("new_network_msg: {:?}", notification_tx.new_network_msg); + // debug!("notification_tx: {}", serde_json::to_string(¬ification_tx).unwrap()); + + let notification_tweak_data = helper_get_tweak_data( + ¬ification_tx.transaction, + ScriptBuf::from_hex(ALICE_SPK).unwrap(), + ); + + let ank_msg = AnkNetworkMsg::new( + sdk_common::network::AnkFlag::Cipher, + ¬ification_tx.new_network_msg.ciphertext.unwrap(), + ); + + debug!("Alice parse her own notification transaction"); + // Alice parse her own transaction to update her utxos + helper_parse_transaction( + ¬ification_tx.transaction, + notification_tweak_data.clone(), + ); + + // debug!("Alice parsed transaction {}", notification_tx.txid); + // debug!("alice_wallet_dump: {:?}", dump_watch_only_wallet().unwrap()); + + reset_device().unwrap(); + helper_switch_device(BOB_START_WALLET.to_owned()); + helper_link_bob(); + + debug!( + "Bob parses notification transaction {}", + notification_tx.txid + ); + // bob parse the transaction and the message + helper_parse_transaction(¬ification_tx.transaction, notification_tweak_data); + helper_parse_ank_msg(ank_msg.to_string()); +} + +#[wasm_bindgen_test] +fn test_bob_challenges_alice() { + setup(); + debug!("==============================================\nStarting test_bob_challenges_alice\n=============================================="); + helper_switch_device(BOB_CHALLENGE_WALLET.to_owned()); + helper_link_bob(); + + set_message_cache(vec![BOB_CHALLENGE_CACHE.to_owned()]).unwrap(); + let notification_msg: CachedMessage = serde_json::from_str(BOB_CHALLENGE_CACHE).unwrap(); + + let commited_in = notification_msg.commited_in.unwrap(); + + let get_outputs_result = get_outputs().unwrap(); + + let bob_outputs: HashMap = get_outputs_result.into_serde().unwrap(); + + let bob_spk = &bob_outputs.get(&commited_in).unwrap().script; + + debug!("Bob sends a challenge transaction back to Alice"); + let confirmation_tx = create_confirmation_transaction(notification_msg.id, 1).unwrap(); + + let confirmation_tweak_data = helper_get_tweak_data( + &confirmation_tx.transaction, + ScriptBuf::from_hex(&bob_spk).unwrap(), + ); + + debug!("Bob parsing its own confirmation tx"); + helper_parse_transaction( + &confirmation_tx.transaction, + confirmation_tweak_data.clone(), + ); + + reset_device().unwrap(); + helper_switch_device(ALICE_CHALLENGE_WALLET.to_owned()); + helper_link_alice(); + + set_message_cache(vec![ALICE_CHALLENGE_CACHE.to_owned()]).unwrap(); + + debug!("Alice parsing confirmation tx"); + helper_parse_transaction(&confirmation_tx.transaction, confirmation_tweak_data); +} + +#[wasm_bindgen_test] +fn test_alice_answers_bob() { + setup(); + debug!("==============================================\nStarting test_alice_answers_bob\n=============================================="); + helper_switch_device(ALICE_ANSWER_WALLET.to_owned()); + helper_link_alice(); + + set_message_cache(vec![ALICE_ANSWER_CACHE.to_owned()]).unwrap(); + let challenge_msg: CachedMessage = serde_json::from_str(ALICE_ANSWER_CACHE).unwrap(); + + debug!("Alice answers bob's challenge"); + let answer_tx = answer_confirmation_transaction(challenge_msg.id, 1).unwrap(); + + // debug!("answer_tx: {:?}", answer_tx.new_network_msg); + let confirmed_by = challenge_msg.confirmed_by.unwrap(); + debug!("confirmed_by: {}", confirmed_by); + + let get_outputs_result = get_outputs().unwrap(); + + let alice_outputs: HashMap = get_outputs_result.into_serde().unwrap(); + + let alice_spk = &alice_outputs.get(&confirmed_by).unwrap().script; + + let answer_tweak_data = helper_get_tweak_data( + &answer_tx.transaction, + ScriptBuf::from_hex(&alice_spk).unwrap(), + ); + + debug!("Alice parsing its own answer tx"); + debug!( + "message: {}", + serde_json::to_string(&answer_tx.new_network_msg).unwrap() + ); + helper_parse_transaction(&answer_tx.transaction, answer_tweak_data.clone()); + + reset_device().unwrap(); + helper_switch_device(BOB_ANSWER_WALLET.to_owned()); + helper_link_bob(); + + set_message_cache(vec![BOB_ANSWER_CACHE.to_owned()]).unwrap(); + + debug!("Bob parses answer transaction {}", answer_tx.txid); + let answer_msg = helper_parse_transaction(&answer_tx.transaction, answer_tweak_data); + debug!( + "bob's message: {}", + serde_json::to_string(&answer_msg).unwrap() + ); +} + +// #[wasm_bindgen_test] +// fn test_linking_devices() { +// setup(); +// helper_switch_bob(); + +// // bob sends pairing transaction to alice +// let result = create_pairing_transaction(helper_get_alice_address(), 1); +// let bob_pairing_transaction = match result { +// Ok(t) => t, +// Err(e) => { +// panic!("create_pairing_transaction failed with {}", e.message); +// } +// }; + +// // get the tweak data to add to the transaction +// let bob_pairing_tweak_data = helper_get_tweak_data( +// &bob_pairing_transaction.transaction, +// ScriptBuf::from_hex(BOB_SPK).unwrap(), +// ); + +// // alice receives and parses that transaction +// helper_switch_alice(); + +// helper_parse_transaction(&bob_pairing_transaction.transaction, bob_pairing_tweak_data); + +// // alice receives and parses the message +// let pairing_msg = +// helper_parse_cipher_msg(bob_pairing_transaction.new_network_msg.ciphertext.unwrap()); + +// // and sends the encrypted key back in a confirm transaction +// let result = create_confirmation_transaction(pairing_msg.id, 1); +// let alice_confirm_transaction = match result { +// Ok(r) => r, +// Err(e) => { +// panic!("create_pairing_transaction failed with {}", e.message); +// } +// }; + +// // get the tweak data to add to the transaction +// let alice_confirm_tweak_data = helper_get_tweak_data( +// &alice_confirm_transaction.transaction, +// helper_get_spk( +// bob_pairing_transaction.transaction, +// alice_confirm_transaction +// .new_network_msg +// .commited_in +// .unwrap(), +// ), +// ); + +// // alice parses her own transaction to update her utxoset +// helper_parse_transaction( +// &alice_confirm_transaction.transaction, +// alice_confirm_tweak_data.clone(), +// ); + +// // alice sends her own pairing transaction +// let result = create_pairing_transaction(helper_get_bob_address(), 1); +// let alice_pairing_transaction = match result { +// Ok(t) => t, +// Err(e) => { +// panic!("create_pairing_transaction failed with {}", e.message); +// } +// }; + +// // get the tweak data to add to the transaction +// let alice_pairing_tweak_data = helper_get_tweak_data( +// &alice_pairing_transaction.transaction, +// ScriptBuf::from_hex(ALICE_SPK).unwrap(), +// ); + +// helper_switch_bob(); + +// // bob receives the spend_key from alice and does the same thing +// helper_parse_transaction( +// &alice_pairing_transaction.transaction, +// alice_pairing_tweak_data, +// ); + +// let pairing_msg = helper_parse_cipher_msg( +// alice_pairing_transaction +// .new_network_msg +// .ciphertext +// .unwrap(), +// ); + +// // let result = create_confirmation_transaction(pairing_msg.id, 1); +// // let bob_confirm_transaction = match result { +// // Ok(r) => r, +// // Err(e) => { +// // panic!("create_pairing_transaction failed with {}", e.message); +// // } +// // }; + +// // bob also received the confirmation transaction from alice, containing his own encrypted key +// log::debug!("Parsing alice confirmation transaction"); +// helper_parse_transaction( +// &alice_confirm_transaction.transaction, +// alice_confirm_tweak_data, +// ); + +// log::debug!("Parsing the message that goes with it"); +// let confirmation_msg = helper_parse_cipher_msg( +// alice_confirm_transaction +// .new_network_msg +// .ciphertext +// .unwrap(), +// ); +// log::debug!("{:?}", confirmation_msg); + +// // bob can now update its device with the linking information +// pair_device( +// confirmation_msg.plaintext.get(0).unwrap().to_owned().into(), +// confirmation_msg.recipient.unwrap(), +// OutPoint::null().to_string(), +// ) +// .unwrap(); +// }