diff --git a/Cargo.toml b/Cargo.toml index 4324f32..23ae408 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,11 +15,9 @@ wasm-bindgen = "0.2.91" getrandom = { version="0.2.12", features = ["js"] } 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 = { git = "https://git.4nkweb.com/4nk/sdk_common.git", branch = "dev" } -shamir = { git = "https://github.com/Sosthene00/shamir", branch = "master" } +sdk_common = { path = "../sdk_common" } +# sdk_common = { git = "https://git.4nkweb.com/4nk/sdk_common.git", branch = "dev" } [dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/src/api.rs b/src/api.rs index 257ff83..c99c91f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,28 +1,39 @@ use std::any::Any; use std::borrow::Borrow; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::io::Write; +use std::ops::Index; use std::str::FromStr; use std::string::FromUtf8Error; -use std::sync::{Mutex, OnceLock, PoisonError}; +use std::sync::{Mutex, MutexGuard, OnceLock, PoisonError}; use std::time::{Duration, Instant}; +use std::u32; -use log::{debug, warn}; use rand::{thread_rng, Fill, Rng, RngCore}; +use sdk_common::aes_gcm::aead::generic_array::GenericArray; +use sdk_common::aes_gcm::aes::cipher::ArrayLength; +use sdk_common::aes_gcm::Nonce; +use sdk_common::log::{debug, warn}; +use anyhow::Context; use anyhow::Error as AnyhowError; +use anyhow::Result as AnyhowResult; +use sdk_common::aes_gcm::aead::{Aead, Payload}; use sdk_common::crypto::{ - AeadCore, Aes256Decryption, Aes256Encryption, Aes256Gcm, AnkSharedSecret, KeyInit, Purpose, + encrypt_with_key, AeadCore, Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD }; +use sdk_common::process::{lock_processes, Process, ProcessState}; +use sdk_common::signature::{AnkHash, AnkValidationNoHash, AnkValidationYesHash, Proof}; use sdk_common::sp_client::bitcoin::blockdata::fee_rate; use sdk_common::sp_client::bitcoin::consensus::{deserialize, serialize}; -use sdk_common::sp_client::bitcoin::hashes::HashEngine; -use sdk_common::sp_client::bitcoin::hashes::{sha256, Hash}; +use sdk_common::sp_client::bitcoin::hashes::{sha256, sha256t, Hash}; +use sdk_common::sp_client::bitcoin::hashes::{FromSliceError, HashEngine}; use sdk_common::sp_client::bitcoin::hex::{ - parse, DisplayHex, FromHex, HexToArrayError, HexToBytesError, + self, parse, DisplayHex, FromHex, HexToArrayError, HexToBytesError, }; use sdk_common::sp_client::bitcoin::key::{Parity, Secp256k1}; use sdk_common::sp_client::bitcoin::network::ParseNetworkError; +use sdk_common::sp_client::bitcoin::p2p::message::NetworkMessage; use sdk_common::sp_client::bitcoin::psbt::raw; use sdk_common::sp_client::bitcoin::secp256k1::ecdh::shared_secret_point; use sdk_common::sp_client::bitcoin::secp256k1::{PublicKey, Scalar, SecretKey}; @@ -38,132 +49,137 @@ use sdk_common::sp_client::silentpayments::{ utils::{Network as SpNetwork, SilentPaymentAddress}, Error as SpError, }; -use serde_json::{Error as SerdeJsonError, Value}; +use sdk_common::{signature, MAX_PRD_PAYLOAD_SIZE}; +use serde_json::{Error as SerdeJsonError, Map, Value}; use serde::{Deserialize, Serialize}; use tsify::{JsValueSerdeExt, Tsify}; -use wasm_bindgen::convert::FromWasmAbi; +use wasm_bindgen::convert::{FromWasmAbi, VectorFromWasmAbi}; use wasm_bindgen::prelude::*; +use sdk_common::device::Device; use sdk_common::network::{ - self, AnkFlag, AnkNetworkMsg, CachedMessage, CachedMessageStatus, CipherMessage, FaucetMessage, + self, AnkFlag, CachedMessage, CachedMessageStatus, CommitMessage, Envelope, FaucetMessage, NewTxMessage, }; +use sdk_common::pcd::{ + compare_maps, AnkPcdHash, AnkPcdTag, Member, Pcd, RoleDefinition, ValidationRule, +}; +use sdk_common::prd::{AnkPrdHash, Prd, PrdType}; use sdk_common::silentpayments::{create_transaction, map_outputs_to_sp_address}; - -use crate::wallet::{generate_sp_wallet, lock_freezed_utxos}; 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::user::{lock_local_device, set_new_device, Device, LOCAL_DEVICE}; -use crate::{lock_messages, CACHEDMESSAGES}; +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, +}; -use crate::process::Process; +#[derive(Debug, PartialEq, Tsify, Serialize, Deserialize, Default)] +#[tsify(into_wasm_abi, from_wasm_abi)] +#[allow(non_camel_case_types)] +pub struct ApiReturn { + pub updated_cached_msg: Vec, + pub updated_process: Option<(String, Process)>, + pub new_tx_to_send: Option, + pub ciphers_to_send: Vec, + pub commit_to_send: Option, +} pub type ApiResult = Result; const IS_TESTNET: bool = true; +const DEFAULT_AMOUNT: Amount = Amount::from_sat(1000); #[derive(Debug, PartialEq, Eq)] pub struct ApiError { pub message: String, } +impl ApiError { + pub fn new(message: String) -> Self { + ApiError { message } + } +} + impl From for ApiError { fn from(value: AnyhowError) -> Self { - ApiError { - message: value.to_string(), - } + ApiError::new(value.to_string()) } } impl From for ApiError { fn from(value: SpError) -> Self { - ApiError { - message: value.to_string(), - } + ApiError::new(value.to_string()) + } +} + +impl From for ApiError { + fn from(value: FromSliceError) -> Self { + ApiError::new(value.to_string()) } } impl From for ApiError { fn from(value: SerdeJsonError) -> Self { - ApiError { - message: value.to_string(), - } + ApiError::new(value.to_string()) } } impl From for ApiError { fn from(value: HexToBytesError) -> Self { - ApiError { - message: value.to_string(), - } + ApiError::new(value.to_string()) } } impl From for ApiError { fn from(value: HexToArrayError) -> Self { - ApiError { - message: value.to_string(), - } + ApiError::new(value.to_string()) } } impl From for ApiError { fn from(value: sdk_common::sp_client::bitcoin::psbt::PsbtParseError) -> Self { - ApiError { - message: value.to_string(), - } + ApiError::new(value.to_string()) } } impl From for ApiError { fn from(value: sdk_common::sp_client::bitcoin::psbt::ExtractTxError) -> Self { - ApiError { - message: value.to_string(), - } + ApiError::new(value.to_string()) } } impl From for ApiError { fn from(value: sdk_common::sp_client::bitcoin::secp256k1::Error) -> Self { - ApiError { - message: value.to_string(), - } + ApiError::new(value.to_string()) } } impl From for ApiError { fn from(value: sdk_common::sp_client::bitcoin::consensus::encode::Error) -> Self { - ApiError { - message: value.to_string(), - } + ApiError::new(value.to_string()) } } impl From for ApiError { fn from(value: FromUtf8Error) -> Self { - ApiError { - message: value.to_string(), - } + ApiError::new(value.to_string()) } } impl From for ApiError { fn from(value: ParseNetworkError) -> Self { - ApiError { - message: value.to_string(), - } + ApiError::new(value.to_string()) } } impl From for ApiError { fn from(value: ParseOutPointError) -> Self { - ApiError { - message: value.to_string(), - } + ApiError::new(value.to_string()) } } @@ -188,15 +204,6 @@ pub fn get_address() -> ApiResult { .get_receiving_address()) } -#[wasm_bindgen] -pub fn restore_device_from_sp_wallet(sp_wallet: String) -> ApiResult { - let sp_wallet: SpWallet = serde_json::from_str(&sp_wallet)?; - - let our_address = set_new_device(sp_wallet)?; - - Ok(our_address) -} - #[wasm_bindgen] pub fn restore_device(device_str: String) -> ApiResult<()> { let device: Device = serde_json::from_str(&device_str)?; @@ -208,6 +215,15 @@ pub fn restore_device(device_str: String) -> ApiResult<()> { Ok(()) } +#[wasm_bindgen] +pub fn create_device_from_sp_wallet(sp_wallet: String) -> ApiResult { + let sp_wallet: SpWallet = serde_json::from_str(&sp_wallet)?; + + let our_address = set_new_device(sp_wallet)?; + + Ok(our_address) +} + #[wasm_bindgen] pub fn create_new_device(birthday: u32, network_str: String) -> ApiResult { let sp_wallet = generate_sp_wallet(None, Network::from_core_arg(&network_str)?)?; @@ -218,127 +234,40 @@ pub fn create_new_device(birthday: u32, network_str: String) -> ApiResult ApiResult<()> { +pub fn pair_device(commitment_tx: String, mut sp_addresses: Vec) -> ApiResult<()> { let mut local_device = lock_local_device()?; - // check that we're still in pairing phase - if !local_device.is_pairing() { - return Err(ApiError { - message: "Already paired".to_owned(), - }); + if local_device.is_linked() { + return Err(ApiError::new("Already paired".to_owned())); } - let mut messages = lock_messages()?; + sp_addresses.push( + local_device + .get_wallet() + .get_client() + .get_receiving_address(), + ); - let my_address = local_device - .get_wallet() - .get_client() - .get_receiving_address(); - - if let Some(message) = messages.iter_mut().find(|m| m.id == message_id) { - if message.status != CachedMessageStatus::Pairing { - return Err(ApiError { - message: "Message is not pairing message".to_owned(), - }); - } - let link_with = if Some(my_address.as_str()) == message.sender.as_ref().map(|s| s.as_str()) - { - message.recipient.as_ref().ok_or(ApiError { - message: "Missing recipient".to_owned(), - })? - } else if Some(my_address.as_str()) == message.recipient.as_ref().map(|s| s.as_str()) { - message.sender.as_ref().ok_or(ApiError { - message: "Missing sender".to_owned(), - })? - } else { - // This should never happen - return Err(ApiError { - message: "We're not part of that message".to_owned(), - }); - }; - let revokation_index = message.tied_by.ok_or(ApiError { - message: "Missing tied_by".to_owned(), - })?; - let pairing_tx = message.commited_in.ok_or(ApiError { - message: "Missing commited_in".to_owned(), - })?; - local_device.new_link( - SilentPaymentAddress::try_from(link_with.as_str()).unwrap(), - pairing_tx.txid, - revokation_index, - Txid::from_str(&incoming_pairing_txid)?, - )?; - - message.status = CachedMessageStatus::Closed; - } else { - return Err(ApiError { - message: format!("Can't find message with id {}", message_id), - }); - } + local_device.pair( + OutPoint::from_str(&commitment_tx)?.txid, + Member::new( + sp_addresses + .into_iter() + .map(|a| TryInto::::try_into(a).unwrap()) + .collect(), + )?, + ); Ok(()) } -#[derive(Tsify, Serialize, Deserialize)] -#[tsify(into_wasm_abi)] -#[allow(non_camel_case_types)] -pub struct get_process_return(Vec); - #[wasm_bindgen] -pub fn get_processes() -> ApiResult { - let MEMBERS: [String;5] = [ - "tsp1qqdvmxycf3c3tf2qhpev0npx25rj05270d6j2pcsrfk2qn5gdy0rpwq6hd9u9sztl3fwmrzzqafzl3ymkq86aqfz5jl5egdkz72tqmhcnrswdz3pk".to_owned(), - "tsp1qqwafwn7dcr9d6ta0w8fjtd9s53u72x9qmmtgd8adqr7454xl90a5jq3vw23l2x8ypt55nrg7trl9lwz5xr5j357ucu4sf9rfmvc0zujcpqcps6rm".to_owned(), - "tsp1qqw02t5hmg5rxpjdkmjdnnmhvuc76wt6vlqdmn2zafnh6axxjd6e2gqcz04gzvnkzf572mur8spyx2a2s8sqzll2ymdpyz59cpl96j4zuvcdvrzxz".to_owned(), - "tsp1qqgpay2r5jswm7vcv24xd94shdf90w30vxtql9svw7qnlnrzd6xt02q7s7z57uw0sssh6c0xddcrryq4mxup93jsh3gfau3autrawl8umkgsyupkm".to_owned(), - "tsp1qqtsqmtgnxp0lsmnxyxcq52zpgxwugwlq8urlprs5pr5lwyqc789gjqhx5qra6g4rszsq43pms6nguee2l9trx905rk5sgntek05hnf7say4ru69y".to_owned(), - ]; - //instances of process - let process1 = Process { - id: 6, - name: String::from("Messaging"), - version: String::from("1.0"), - members: MEMBERS.to_vec(), - html: crate::process::HTML_MESSAGING.to_owned(), - style: crate::process::CSS.to_owned(), - script: "".to_owned(), - }; - let process2 = Process { - id: 7, - name: String::from("Kotpart"), - version: String::from("1.0"), - members: MEMBERS.to_vec(), - html: crate::process::HTML_MESSAGING.to_owned(), - style: crate::process::CSS.to_owned(), - script: "".to_owned(), - }; - let process3 = Process { - id: 8, - name: String::from("Storage"), - version: String::from("1.0"), - members: MEMBERS.to_vec(), - html: crate::process::HTML_MESSAGING.to_owned(), - style: crate::process::CSS.to_owned(), - script: "".to_owned(), - }; +pub fn unpair_device() -> ApiResult<()> { + let mut local_device = lock_local_device()?; - // vec with the instances of processes - let mut data_process: Vec = Vec::new(); - data_process.push(process1); - data_process.push(process2); - data_process.push(process3); - Ok(get_process_return(data_process)) -} + local_device.unpair(); -#[derive(Debug, Tsify, Serialize, Deserialize)] -#[tsify(from_wasm_abi)] -#[allow(non_camel_case_types)] -pub struct recover_data(Vec); - -impl recover_data { - fn as_inner(&self) -> &[u8] { - &self.0 - } + Ok(()) } #[derive(Debug, Tsify, Serialize, Deserialize)] @@ -353,54 +282,8 @@ impl outputs_list { } #[wasm_bindgen] -pub fn login(message_id: u32, outgoing_pairing_transaction: String) -> ApiResult<()> { - let mut local_device = lock_local_device()?; - let pairing_transaction: Transaction = - deserialize(&Vec::from_hex(&outgoing_pairing_transaction)?)?; - let pairing_transaction_txid = pairing_transaction.txid(); - - let messages = lock_messages()?; - - let login_output = pairing_transaction - .output - .first() - .expect("Transaction has at least one output"); - let new_remote_key = XOnlyPublicKey::from_slice(&login_output.script_pubkey.as_bytes()[2..])?; - - let new_session_key: SecretKey; - let notification_outpoint: OutPoint; - if let Some(message) = messages.iter().find(|m| m.id == message_id) { - notification_outpoint = message - .commited_in - .expect("message without commitment outpoint"); - let sp_wallet = local_device.get_wallet(); - let spendable_outputs = sp_wallet.get_outputs().to_spendable_list(); - - let output = spendable_outputs - .get(¬ification_outpoint) - .ok_or(ApiError { - message: "Unknown outpoint".to_owned(), - })?; - - let spend_key = sp_wallet.get_client().try_get_secret_spend_key()?; - let tweak = SecretKey::from_slice(&Vec::from_hex(&output.tweak)?)?; - new_session_key = spend_key.add_tweak(&tweak.into())?; - } else { - return Err(ApiError { - message: "Unknown message".to_owned(), - }); - } - - local_device.update_session( - new_session_key, - notification_outpoint, - OutPoint::new(pairing_transaction_txid, 1), - new_remote_key, - OutPoint::new(pairing_transaction_txid, 0), - OutPoint::new(notification_outpoint.txid, 1), - ); - - Ok(()) +pub fn login() -> ApiResult<()> { + unimplemented!(); } #[wasm_bindgen] @@ -415,6 +298,48 @@ pub fn dump_wallet() -> ApiResult { Ok(serde_json::to_string(device.get_wallet()).unwrap()) } +#[wasm_bindgen] +pub fn reset_process_cache() -> ApiResult<()> { + let mut cached_processes = lock_processes()?; + + *cached_processes = HashMap::new(); + + debug_assert!(cached_processes.is_empty()); + + Ok(()) +} + +#[wasm_bindgen] +pub fn dump_process_cache() -> ApiResult { + let cached_processes = lock_processes()?; + + let serializable_cache = cached_processes + .iter() + .map(|(outpoint, process)| { + ( + outpoint.to_string(), + serde_json::to_value(&process).unwrap(), + ) + }) + .collect::>(); + + let json_string = serde_json::to_string(&serializable_cache) + .map_err(|e| ApiError::new(format!("Failed to serialize process cache: {}", e)))?; + + Ok(json_string) +} + +#[wasm_bindgen] +pub fn set_process_cache(processes: String) -> ApiResult<()> { + let processes: HashMap = serde_json::from_str(&processes)?; + + let mut cached_processes = lock_processes()?; + + *cached_processes = processes; + + Ok(()) +} + #[wasm_bindgen] pub fn reset_message_cache() -> ApiResult<()> { let mut cached_msg = lock_messages()?; @@ -431,9 +356,7 @@ 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(), - }); + return Err(ApiError::new("Message cache not empty".to_owned())); } let new_cache: Result, serde_json::Error> = @@ -470,90 +393,54 @@ pub fn reset_device() -> ApiResult<()> { *device = Device::default(); reset_message_cache()?; + reset_process_cache()?; Ok(()) } +#[wasm_bindgen] +pub fn get_txid(transaction: String) -> ApiResult { + let tx: Transaction = deserialize(&Vec::from_hex(&transaction)?)?; + + Ok(tx.txid().to_string()) +} + fn handle_transaction( updated: HashMap, tx: &Transaction, - sp_wallet: &mut SpWallet, tweak_data: PublicKey, -) -> anyhow::Result { - let op_return = tx.output.iter().find(|o| o.script_pubkey.is_op_return()); - let commitment = if op_return.is_none() { - vec![] - } else { - op_return.unwrap().script_pubkey.as_bytes()[2..].to_vec() - }; - let commitment_str = commitment.to_lower_hex_string(); +) -> AnyhowResult { + let device = lock_local_device()?; + let sp_wallet = device.get_wallet(); + + let op_return: Vec<&sdk_common::sp_client::bitcoin::TxOut> = tx + .output + .iter() + .filter(|o| o.script_pubkey.is_op_return()) + .collect(); + if op_return.len() != 1 { + return Err(AnyhowError::msg( + "Transaction must have exactly one op_return output", + )); + } + let commitment = + AnkPrdHash::from_slice(&op_return.first().unwrap().script_pubkey.as_bytes()[2..])?; - // If we got updates from a transaction, it means that it creates an output to us, spend an output we owned, or both // Basically a transaction that destroyed utxo is a transaction we sent. let utxo_destroyed: HashMap<&OutPoint, &OwnedOutput> = updated .iter() .filter(|(outpoint, output)| output.spend_status != OutputSpendStatus::Unspent) .collect(); - let utxo_created: HashMap<&OutPoint, &OwnedOutput> = updated - .iter() - .filter(|(outpoint, output)| output.spend_status == OutputSpendStatus::Unspent) - .collect(); let mut messages = lock_messages()?; // empty utxo_destroyed means we received this transaction if utxo_destroyed.is_empty() { - // We first check for faucet transactions - if let Some(message) = messages.iter_mut().find(|m| { - if m.status == CachedMessageStatus::FaucetWaiting { - m.commitment.as_ref() == Some(&commitment_str) - } else { - false - } - }) { - 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(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(message) = messages.iter_mut().find(|m| { - m.commited_in == Some(input.previous_output) - && m.status == CachedMessageStatus::SentWaitingConfirmation - }) { - // sender needs to spent it back again to receiver - let (outpoint, output) = utxo_created.into_iter().next().unwrap(); - - message.confirmed_by = Some(outpoint.clone()); - message.status = CachedMessageStatus::MustSpendConfirmation; - - // Caller must interpret this message as "do spend confirmed_by outpoint to receiver" - return Ok(message.clone()); - } - } - - // if we've found nothing we are being notified let shared_point = sp_utils::receiving::calculate_ecdh_shared_secret( &tweak_data, &sp_wallet.get_client().get_scan_key(), ); - let shared_secret = AnkSharedSecret::new(shared_point); - - // debug!( - // "Shared secret: {}", - // shared_secret.to_byte_array().to_lower_hex_string() - // ); + let shared_secret = AnkSharedSecretHash::from_shared_point(shared_point); let mut plaintext: Vec = vec![]; if let Some(message) = messages.iter_mut().find(|m| { @@ -568,95 +455,78 @@ fn handle_transaction( return false; } }) { - let (outpoint, output) = utxo_created.into_iter().next().unwrap(); + let prd = Prd::extract_from_message_with_commitment(&plaintext, &commitment)?; - let cipher_msg: CipherMessage = serde_json::from_slice(&plaintext)?; - message.commited_in = Some(outpoint.clone()); - // freeze the commitment utxo - let mut freezed_utxos = lock_freezed_utxos()?; - freezed_utxos.insert(*outpoint); + let proof_key = prd.proof.unwrap().get_key(); + let mut actual_sender = String::default(); + for sp_address in serde_json::from_str::(&prd.sender)?.get_addresses() { + if proof_key + == SilentPaymentAddress::try_from(sp_address.as_str()) + .unwrap() + .get_spend_key() + .x_only_public_key() + .0 + { + actual_sender = sp_address; + } else { + continue; + } + break; + } - message.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string()); - message.commitment = Some(commitment_str); - 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; + let outpoint = OutPoint::from_str(&prd.root_commitment)?; - return Ok(message.clone()); + let updated_process: Process; + if let Some(process) = lock_processes()?.get_mut(&outpoint) { + process.insert_shared_secret( + SilentPaymentAddress::try_from(actual_sender.as_str()).unwrap(), + shared_secret, + ); + updated_process = process.clone(); + } else { + let mut shared_secrets = HashMap::new(); + shared_secrets.insert( + SilentPaymentAddress::try_from(actual_sender.as_str()).unwrap(), + shared_secret, + ); + let new_process = Process::new(vec![], shared_secrets, vec![]); + lock_processes()?.insert(outpoint, new_process.clone()); + updated_process = new_process; + } + + // We don't need the cached message anymore + let id_to_rm = message.id; + *message = CachedMessage { + id: id_to_rm, + ..Default::default() + }; + + return Ok(ApiReturn { + updated_cached_msg: vec![message.clone()], + updated_process: Some((outpoint.to_string(), updated_process)), + ..Default::default() + }); } else { // store it and wait for the message let mut new_msg = CachedMessage::new(); - let (outpoint, output) = utxo_created - .into_iter() - .next() - .expect("utxo_created shouldn't be empty"); - new_msg.commited_in = Some(outpoint.clone()); - // freeze the commitment utxo - let mut freezed_utxos = lock_freezed_utxos()?; - freezed_utxos.insert(*outpoint); + 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; - new_msg.commitment = Some(commitment.to_lower_hex_string()); - new_msg.recipient = Some(sp_wallet.get_client().get_receiving_address()); - new_msg.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string()); - new_msg.status = CachedMessageStatus::TxWaitingCipher; messages.push(new_msg.clone()); - return Ok(new_msg.clone()); + + return Ok(ApiReturn { + updated_cached_msg: vec![new_msg.clone()], + ..Default::default() + }); } } else { - // We are sender of a 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 let Some(message) = messages.iter().find(|m| { - if commitment.is_empty() || m.commitment.is_none() { - return false; - } - match m.status { - CachedMessageStatus::SentWaitingConfirmation - | CachedMessageStatus::Pairing - | CachedMessageStatus::Login => { - // commitment we're looking for is simply what's in the message - m.commitment - .as_ref() - .map(|c| Vec::from_hex(&c).unwrap()) - .unwrap() - == 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, - } - }) { - return Ok(message.clone()); - } else { - return Err(anyhow::Error::msg( - "Failed to find the message for one of our transactions", - )); - } + // We're sender of the transaction, do nothing + return Ok(ApiReturn { + ..Default::default() + }); } } @@ -666,96 +536,551 @@ fn process_transaction( tx_hex: String, blockheight: u32, tweak_data_hex: String, -) -> anyhow::Result> { +) -> anyhow::Result { let tx = deserialize::(&Vec::from_hex(&tx_hex)?)?; let tweak_data = PublicKey::from_str(&tweak_data_hex)?; - let mut device = lock_local_device()?; - let wallet = device.get_mut_wallet(); - let updated = wallet.update_wallet_with_transaction(&tx, blockheight, tweak_data)?; + let updated: HashMap; + { + let mut device = lock_local_device()?; + let wallet = device.get_mut_wallet(); + updated = wallet.update_wallet_with_transaction(&tx, blockheight, tweak_data)?; + } if updated.len() > 0 { - let updated_msg = handle_transaction(updated, &tx, wallet, tweak_data)?; - return Ok(Some(updated_msg)); + return handle_transaction(updated, &tx, tweak_data); + } + + Err(anyhow::Error::msg("Transaction is not our")) +} + +#[wasm_bindgen] +pub fn parse_new_tx(new_tx_msg: String, block_height: u32, fee_rate: u32) -> ApiResult { + let new_tx: NewTxMessage = serde_json::from_str(&new_tx_msg)?; + + if let Some(error) = new_tx.error { + return Err(ApiError::new(format!( + "NewTx returned with an error: {}", + error + ))); + } + + if new_tx.tweak_data.is_none() { + return Err(ApiError::new("Missing tweak_data".to_owned())); + } + + Ok(process_transaction( + new_tx.transaction, + block_height, + new_tx.tweak_data.unwrap(), + )?) +} + +fn try_decrypt_with_processes( + cipher: &[u8], + processes: MutexGuard>, +) -> Option<(Vec, SilentPaymentAddress, OutPoint)> { + let nonce = Nonce::from_slice(&cipher[..12]); + + for (outpoint, process) in processes.iter() { + for (address, secret) in process.get_all_secrets() { + let engine = Aes256Gcm::new(&secret.to_byte_array().into()); + if let Ok(plain) = engine.decrypt( + &nonce, + Payload { + msg: &cipher[12..], + aad: AAD, + }, + ) { + return Some(( + plain, + address, + *outpoint, + )); + } + } + } + None +} + +#[wasm_bindgen] +pub fn response_prd( + root_commitment: String, + prd: String, // The Prd we respond to + approval: bool, +) -> ApiResult { + let local_device = lock_local_device()?; + let member = local_device + .to_member() + .ok_or(ApiError::new("Unpaired device".to_owned()))?; + + let prd_to_respond = serde_json::from_str::(&prd)?; + + // Probably we should answer differently depending on the type of prd + let message_hash = match prd_to_respond.prd_type { + PrdType::Update => { + let pcd_hash: AnkPcdHash = + AnkPcdHash::from_value(&Value::from_str(&prd_to_respond.payload)?); + + let message_hash = if approval { + AnkHash::ValidationYes(AnkValidationYesHash::from_commitment(pcd_hash)) + } else { + AnkHash::ValidationNo(AnkValidationNoHash::from_commitment(pcd_hash)) + }; + + let proof = Proof::new( + message_hash, + local_device + .get_wallet() + .get_client() + .get_spend_key() + .try_into()?, + ); + + let prd = Prd::new_response( + OutPoint::from_str(&root_commitment)?, + serde_json::to_string(&member)?, + proof, + pcd_hash, + ); + + return Ok(ApiReturn { + ciphers_to_send: vec![prd.to_network_msg(local_device.get_wallet())?.to_string()], + ..Default::default() + }); + } + _ => unimplemented!(), + }; +} + +fn confirm_prd(prd: Prd, shared_secret: &AnkSharedSecretHash) -> AnyhowResult { + match prd.prd_type { + PrdType::Confirm | PrdType::Response | PrdType::List => { + return Err(AnyhowError::msg("Invalid prd type")); + } + _ => (), + } + + let outpoint = OutPoint::from_str(&prd.root_commitment)?; + + let local_device = lock_local_device()?; + let member = match local_device.to_member() { + Some(member) => member, + None => { + // This might be because we're pairing, let's see if our address is part of sender of the initial prd + let remote_member: Member = serde_json::from_str(&prd.sender)?; + let addresses = remote_member.get_addresses(); + let this_device_address = local_device + .get_wallet() + .get_client() + .get_receiving_address(); + if let Some(_) = addresses.into_iter().find(|a| *a == this_device_address) { + remote_member + } else { + return Err(AnyhowError::msg("Must pair device first")); + } + } + }; + + let pcd_commitment = AnkPcdHash::from_str(&prd.payload)?; + + let prd_confirm = Prd::new_confirm(outpoint, member, pcd_commitment); + + let prd_msg = prd_confirm.to_network_msg(local_device.get_wallet())?; + + 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 { + let pcd = &prd.payload; + + let cipher = encrypt_with_key(shared_secret.as_byte_array(), pcd.as_bytes())?; + + Ok(ApiReturn { + ciphers_to_send: vec![cipher.to_lower_hex_string()], + ..Default::default() + }) +} + +fn decrypt_with_cached_messages( + cipher: &[u8], +) -> anyhow::Result, AnkSharedSecretHash)>> { + let mut messages = lock_messages()?; + + let nonce = Nonce::from_slice(&cipher[..12]); + + 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, &commitment)?; + // Update the message status + message.status = CachedMessageStatus::NoStatus; + + return Ok(Some(( + plain, + AnkSharedSecretHash::from_str(message.shared_secrets.first().unwrap())?, + ))); + } } Ok(None) } -#[wasm_bindgen] -pub fn parse_new_tx(new_tx_msg: String, block_height: u32, fee_rate: u32) -> ApiResult> { - let new_tx: NewTxMessage = serde_json::from_str(&new_tx_msg)?; +fn decrypt_with_known_processes(cipher: &[u8]) -> anyhow::Result, OutPoint)>> { + let processes = lock_processes()?; - if let Some(error) = new_tx.error { - return Err(ApiError { - message: format!("NewTx returned with an error: {}", error), - }); + debug!("Known processes: {:#?}", processes); + + 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))); + } + } } - - if new_tx.tweak_data.is_none() { - return Err(ApiError { - message: "Missing tweak_data".to_owned(), - }); - } - - let msg = - process_transaction(new_tx.transaction, block_height, new_tx.tweak_data.unwrap())?; - - Ok(msg) + Ok(None) } -#[wasm_bindgen] -pub fn parse_cipher(cipher_msg: 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(&cipher_msg.trim_matches('\"'))?; - if let Some(message) = messages.iter_mut().find(|m| match m.status { - CachedMessageStatus::TxWaitingCipher | CachedMessageStatus::Trusted => { - m.try_decrypt_cipher(cipher.clone()).is_ok() - } - _ => return false, - }) { - let plain = message.try_decrypt_cipher(cipher).unwrap(); - // debug!("Found message {}", String::from_utf8(plain.clone())?); - if message.status == CachedMessageStatus::TxWaitingCipher { - let cipher_msg: CipherMessage = serde_json::from_slice(&plain)?; - // 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.sender = Some(cipher_msg.sender); - message.ciphertext = None; - if cipher_msg.message.starts_with("PAIRING") { - // we don't follow the classic confirmation pattern - // if we agree, we must send another notification to remote - // the notification output here will be spent as our first session - message.status = CachedMessageStatus::Pairing; - } else if cipher_msg.message.starts_with("LOGIN") { - message.status = CachedMessageStatus::Login; +/// 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, +) -> AnyhowResult { + // We already checked the commitment if any + let prd = Prd::extract_from_message(plain)?; + + debug!("found prd: {:#?}", prd); + + let proof_key = prd.proof.unwrap().get_key(); + + let sp_address = serde_json::from_str::(&prd.sender)? + .get_addresses() + .into_iter() + .find_map(|address| { + let parsed_address = SilentPaymentAddress::try_from(address.as_str()).ok()?; + let spend_key = parsed_address.get_spend_key().x_only_public_key().0; + if spend_key == proof_key { + Some(parsed_address) } else { - message.status = CachedMessageStatus::ReceivedMustConfirm; + None } - message.plaintext.push(cipher_msg.message); - } else { - // We're receiving a message for some action already engaged - // Let's update the message by pushing what we just found out - message.plaintext.push(String::from_utf8(plain)?); + }) + .ok_or_else(|| anyhow::Error::msg("No matching address found for the proof key"))?; + + let outpoint = OutPoint::from_str(&prd.root_commitment)?; + + let mut processes = lock_processes()?; + let relevant_process = match processes.entry(outpoint) { + 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![])) } - return Ok(message.clone()); - } else { - // let's keep it in case we receive the transaction later - let mut new_msg = CachedMessage::new(); - new_msg.status = CachedMessageStatus::CipherWaitingTx; - new_msg.ciphertext = Some(cipher_msg); - messages.push(new_msg.clone()); - return Ok(new_msg); + }; + + match prd.prd_type { + PrdType::Confirm => { + // It must match a prd we sent previously + // We send the whole data in a pcd + debug!("Received confirm prd {:#?}", prd); + let original_request = relevant_process + .get_impending_requests() + .into_iter() + .find(|r| { + if r.prd_type != PrdType::Update { + return false; + } + let hash = Value::from_str(&r.payload).unwrap().tagged_hash(); + 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", + ))?; + + return send_data(original_request, &shared_secret); + } + 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 cipher = confirm_prd(prd, &shared_secret)?; + + return Ok(ApiReturn { + ciphers_to_send: vec![cipher], + updated_process: Some((outpoint.to_string(), relevant_process.clone())), + ..Default::default() + }); + } + PrdType::Response => { + // We must know of a prd update that the response answers to + let original_request = relevant_process + .get_impending_requests() + .into_iter() + .find(|r| { + if r.prd_type != PrdType::Update { + return false; + } + let hash = Value::from_str(&r.payload).unwrap().tagged_hash(); + hash.to_string() == prd.payload + }) + .ok_or(anyhow::Error::msg("Original request not found"))?; + + return Ok(ApiReturn { + ..Default::default() + }); + } + _ => unimplemented!(), } } +fn handle_pcd(plain: Vec, root_commitment: OutPoint) -> AnyhowResult { + let pcd = Value::from_str(&String::from_utf8(plain)?)?; + + let pcd_commitment = pcd.tagged_hash(); + + let mut processes = lock_processes()?; + let relevant_process = processes.get_mut(&root_commitment).unwrap(); + + // We match the pcd with a prd and act accordingly + let prd = relevant_process + .get_impending_requests_mut() + .into_iter() + .find(|r| *r.payload == pcd_commitment.to_string()) + .ok_or(AnyhowError::msg("Failed to retrieve the matching prd"))?; + + // We update the process and return it + prd.payload = pcd.to_string(); + + debug!("New state of the process: {:#?}", prd); + + return Ok(ApiReturn { + updated_process: Some((root_commitment.to_string(), relevant_process.clone())), + ..Default::default() + }); +} + +fn handle_decrypted_message( + plain: Vec, + shared_secret: Option, + root_commitment: Option, +) -> anyhow::Result { + // Try to handle as PRD first + handle_prd(&plain, shared_secret) + .or_else(|_| { + // If PRD handling fails, try to handle as PCD + handle_pcd( + plain, + root_commitment.ok_or(anyhow::Error::msg( + "root_commitment must be known for a pcd", + ))?, + ) + }) + .map_err(|e| anyhow::Error::msg(format!("Failed to handle decrypted message: {}", e))) +} + +// fn match_with_active_processes(cipher: &[u8]) -> anyhow::Result { +// // Try decrypting the cipher with available processes and shared secrets +// let (plain, sp_address, process_root_commitment) = +// try_decrypt_with_processes(cipher, lock_processes()?) +// .ok_or_else(|| anyhow::Error::msg("No match in active processes"))?; + +// debug!("matched with an active process"); + +// // Try to handle the decrypted payload as PRD first +// if let Ok(res) = handle_prd(&plain, sp_address, None) { +// return Ok(res); +// } + +// // If not PRD, try to handle as PCD +// if let Ok(res) = handle_pcd(plain, sp_address, process_root_commitment) { +// return Ok(ApiReturn { +// updated_process: Some((process_root_commitment.to_string(), res)), +// ..Default::default() +// }); +// } + +// // If neither PRD nor PCD, return an error +// Err(anyhow::Error::msg("payload can't be parsed")) +// } + +// /// Go through cached messages for tx waiting for a prd +// /// We update the process so that the +// fn match_cipher_to_cached_messages(cipher: &[u8]) -> anyhow::Result { +// // let's try to decrypt with keys we found in transactions but haven't used yet +// let mut messages = lock_messages()?; +// let mut plain = vec![]; +// // debug!("messages: {:?}", messages); +// if let Some(message) = messages.iter_mut().find(|m| match m.status { +// CachedMessageStatus::TxWaitingPrd => { +// if let Ok(m) = m.try_decrypt_message(&cipher) { +// plain = m; +// return true; +// } else { +// return false; +// } +// } +// _ => return false, +// }) { +// // debug!("Found message {}", String::from_utf8(plain.clone())?); +// let commitment = AnkPrdHash::from_str(message.commitment.as_ref().unwrap())?; +// let prd = Prd::extract_from_message_with_commitment(&plain, &commitment)?; + +// let proof_key = prd.proof.unwrap().get_key(); +// let mut actual_sender = String::default(); +// for sp_address in serde_json::from_str::(&prd.sender)?.get_addresses() { +// if proof_key +// == SilentPaymentAddress::try_from(sp_address.as_str()) +// .unwrap() +// .get_spend_key() +// .x_only_public_key() +// .0 +// { +// actual_sender = sp_address; +// break; +// } else { +// continue; +// } +// } + +// let shared_secret = message.shared_secrets.first().unwrap(); +// let root_outpoint = OutPoint::from_str(&prd.root_commitment)?; + +// let mut processes = lock_processes()?; + +// let updated_process: Process; +// if let Some(process) = processes.get_mut(&root_outpoint) { +// // we're actually replacing the shared_secret for that process and that sender if it exists +// process.shared_secrets.insert( +// actual_sender, +// Vec::from_hex(shared_secret)?.to_lower_hex_string(), +// ); +// updated_process = process.clone(); +// } else { +// let mut shared_secrets = HashMap::new(); +// shared_secrets.insert( +// actual_sender, +// Vec::from_hex(shared_secret)?.to_lower_hex_string(), +// ); +// let new_process = Process { +// shared_secrets, +// ..Default::default() +// }; +// processes.insert(root_outpoint, new_process.clone()); +// updated_process = new_process; +// } + +// return Ok(ApiReturn { +// updated_cached_msg: vec![CachedMessage { +// id: message.id, +// ..Default::default() +// }], +// updated_process: Some((root_outpoint.to_string(), updated_process)), +// ..Default::default() +// }); +// } else { +// // let's keep it in case we receive the transaction later +// let mut new_msg = CachedMessage::new(); +// new_msg.status = CachedMessageStatus::CipherWaitingTx; +// new_msg.cipher.push(cipher.to_lower_hex_string()); +// messages.push(new_msg.clone()); +// return Ok(ApiReturn { +// updated_cached_msg: vec![new_msg], +// ..Default::default() +// }); +// } +// } + +#[wasm_bindgen] +pub fn parse_cipher(cipher_msg: String) -> ApiResult { + // 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( + "Invalid cipher: empty or too long".to_owned(), + )); + } + + 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) { + return handle_decrypted_message(plain, Some(shared_secret), None) + .map_err(|e| ApiError::new(format!("Failed to handle decrypted message: {}", e))); + } + + // If that fails, try decrypting with known processes + if let Ok(Some((plain, root_commitment))) = decrypt_with_known_processes(&cipher) { + 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, return an error + Err(ApiError::new( + "Failed to decrypt cipher with any known method".to_owned(), + )) +} + #[wasm_bindgen] pub fn get_outputs() -> ApiResult { let device = lock_local_device()?; @@ -770,303 +1095,180 @@ pub fn get_available_amount() -> ApiResult { Ok(device.get_wallet().get_outputs().get_balance().to_sat()) } -#[derive(Debug, Tsify, Serialize, Deserialize, Default)] -#[tsify(into_wasm_abi, from_wasm_abi)] -#[allow(non_camel_case_types)] -pub struct createTransactionReturn { - pub txid: String, - pub transaction: String, - pub new_network_msg: CachedMessage, +#[wasm_bindgen] +/// This takes some commitment and creates a commit msg so that relay commits it on chain +pub fn create_commit_message( + to_commit: String, + relay_address: String, + init_commitment_outpoint: Option, // if None, we're initializing a new commitment chain + fee_rate: u32, +) -> ApiResult { + let value = Value::from_str(&to_commit)?; + let commitment = value.tagged_hash(); + + // if the transaction exists, we don't need to create a transaction and just put the outpoint in the msg + if let Some(outpoint) = init_commitment_outpoint { + // Todo : if we have an init tx, look into cached processes and confirm that it exists + // We just send the message with the outpoint + return Ok(ApiReturn { + commit_to_send: Some(CommitMessage::new_update_commitment( + OutPoint::from_str(&outpoint)?, + 0, + commitment.to_byte_array(), + )), + ..Default::default() + }); + } else { + let freezed_utxos = lock_freezed_utxos()?; + + let local_device = lock_local_device()?; + + let sp_wallet = local_device.get_wallet(); + + let signed_psbt = create_transaction( + &vec![], + &freezed_utxos, + sp_wallet, + vec![Recipient { + address: relay_address, + amount: Amount::from_sat(1000), + nb_outputs: 1, + }], + None, + Amount::from_sat(fee_rate.into()), + None, + )?; + + let tx = signed_psbt.extract_tx()?; + + Ok(ApiReturn { + commit_to_send: Some(CommitMessage::new_first_commitment( + tx, + 0, + commitment.to_byte_array(), + )), + ..Default::default() + }) + } } -/// This is what we call to answer a confirmation as a sender #[wasm_bindgen] -pub fn answer_confirmation_transaction( - message_id: u32, +/// We assume that the provided tx outpoint exist +pub fn create_update_transaction( + init_commitment: Option, + new_state: String, fee_rate: u32, -) -> ApiResult { - 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() { - return Err(ApiError { - message: "Invalid network message".to_owned(), +) -> ApiResult { + let pcd = Value::from_str(&new_state)?; + let pcd_map = pcd + .as_object() + .ok_or(ApiError::new("new_state must be an object".to_owned()))?; + + let mut processes = lock_processes()?; + + let commitment_outpoint: OutPoint; + let relevant_process: &mut Process; + if let Some(s) = init_commitment { + // We're updating an existing contract + let outpoint = OutPoint::from_str(&s)?; + + if let Some(p) = processes.get_mut(&outpoint) { + // compare the provided new_state with the process defined template + let previous_state = &p.get_status_at(0).unwrap().encrypted_pcd; + if !compare_maps(previous_state.as_object().unwrap(), pcd_map) { + return Err(ApiError::new( + "Provided updated state is not consistent with the process template".to_owned(), + )); + } + relevant_process = p; + commitment_outpoint = outpoint; + } else { + // This is a process we don't know about, so we insert a new entry + processes.insert(outpoint, Process::default()); + relevant_process = processes.get_mut(&outpoint).unwrap(); + commitment_outpoint = outpoint; + } + } else { + // This is a creation with an init state, the commitment will come later + // We need a placeholder to keep track of the process before it's commited on chain + // We can take the hash of the init_state as a txid, and set the vout to the max as it is very unlikely to ever have a real commitment that will look like this + let dummy = pcd.tagged_hash(); + + let dummy_outpoint = OutPoint::new(Txid::from_slice(dummy.as_byte_array())?, u32::MAX); + + processes.insert(dummy_outpoint, Process::default()); + + relevant_process = processes.get_mut(&dummy_outpoint).unwrap(); + commitment_outpoint = dummy_outpoint; + } + + // We assume that all processes must have a roles key + let roles = pcd + .get("roles") + .ok_or(ApiError::new("No roles in new_state".to_owned()))?; + let roles_map = roles + .as_object() + .ok_or(ApiError::new("roles is not an object".to_owned()))? + .clone(); + let mut all_members: HashMap> = HashMap::new(); + for (name, role_def) in roles_map { + let role: RoleDefinition = serde_json::from_str(&role_def.to_string())?; + let fields: Vec = role + .validation_rules + .iter() + .flat_map(|rule| rule.fields.clone()) + .collect(); + for member in role.members { + if !all_members.contains_key(&member) { + all_members.insert(member.clone(), HashSet::new()); + } + all_members.get_mut(&member).unwrap().extend(fields.clone()); + } + } + + let nb_recipients = all_members.len(); + if nb_recipients == 0 { + return Err(ApiError::new( + "Can't create a process with 0 member".to_owned(), + )); + } + + let mut recipients: Vec = Vec::with_capacity(nb_recipients * 2); // We suppose that will work most of the time + // we actually have multiple "recipients" in a technical sense for each social recipient + // that's necessary because we don't want to miss a notification because we don't have a device atm + for member in all_members.keys() { + let addresses = member.get_addresses(); + for sp_address in addresses.into_iter() { + recipients.push(Recipient { + address: sp_address.into(), + amount: DEFAULT_AMOUNT, + nb_outputs: 1, }); } - - message = m; - } else { - return Err(ApiError { - message: format!("Can't find message for id {}", message_id), - }); } - let sp_address: SilentPaymentAddress = - message.recipient.as_ref().unwrap().as_str().try_into()?; + let mut fields2keys = Map::new(); + let mut fields2cipher = Map::new(); + let encrypted_pcd = pcd.clone(); + encrypted_pcd.encrypt_fields(&mut fields2keys, &mut fields2cipher); let local_device = lock_local_device()?; let sp_wallet = local_device.get_wallet(); - let recipient = Recipient { - address: sp_address.into(), - amount: DUST_THRESHOLD, - nb_outputs: 1, - }; + let sender: Member = local_device + .to_member() + .ok_or(ApiError::new("unpaired device".to_owned()))?; - let confirmed_by = message.confirmed_by.as_ref().unwrap(); - - let mut freezed_utxos = lock_freezed_utxos()?; - - // we remove the outpoint we want to spend from our freezed utxos list - freezed_utxos.remove(&confirmed_by); - - let signed_psbt = create_transaction( - &vec![confirmed_by], - &freezed_utxos, - sp_wallet, - vec![recipient], - None, - Amount::from_sat(fee_rate.into()), - message.recipient.clone(), - )?; - - let final_tx = signed_psbt.extract_tx()?; - - message.status = CachedMessageStatus::Trusted; - - Ok(createTransactionReturn { - txid: final_tx.txid().to_string(), - transaction: serialize(&final_tx).to_lower_hex_string(), - new_network_msg: message.clone(), - }) -} - -/// This is what we call to confirm as a receiver -#[wasm_bindgen] -pub fn create_confirmation_transaction( - message_id: u32, - fee_rate: u32, -) -> ApiResult { - 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() || m.plaintext.is_empty() { - return Err(ApiError { - message: "Invalid network message".to_owned(), - }); - } - - message = m; - } else { - return Err(ApiError { - message: format!("Can't find message for id {}", message_id), - }); - } - - let sp_address: SilentPaymentAddress = message.sender.as_ref().unwrap().as_str().try_into()?; - - let local_device = lock_local_device()?; - - let sp_wallet = local_device.get_wallet(); - - let recipient = Recipient { - address: sp_address.into(), - amount: DUST_THRESHOLD, - nb_outputs: 1, - }; - - let commited_in = message.commited_in.as_ref().unwrap(); - - let mut freezed_utxos = lock_freezed_utxos()?; - - // we remove the outpoint we want to spend from our freezed utxos list - freezed_utxos.remove(&commited_in); - - let signed_psbt = create_transaction( - &vec![commited_in], - &freezed_utxos, - &sp_wallet, - vec![recipient], - None, - Amount::from_sat(fee_rate.into()), - message.sender.clone(), - )?; - - // what's the vout of the output sent to sender? - let sp_address2vouts = map_outputs_to_sp_address(&signed_psbt.to_string())?; - let recipients_vouts = sp_address2vouts - .get::(&sp_address.into()) - .expect("recipients didn't change") - .as_slice(); - - let final_tx = signed_psbt.extract_tx()?; - - message.confirmed_by = Some(OutPoint { - txid: final_tx.txid(), - vout: recipients_vouts[0] as u32, - }); - - Ok(createTransactionReturn { - txid: final_tx.txid().to_string(), - transaction: serialize(&final_tx).to_lower_hex_string(), - new_network_msg: message.clone(), - }) -} - -#[wasm_bindgen] -pub fn create_login_transaction(fee_rate: u32) -> ApiResult { - // First thing is to check that all the revokation outpoints are still unspent - // We assume it has been checked before calling this function - let local_device = lock_local_device()?; - if !local_device.is_linked() { - return Err(ApiError { - message: "No linked device".to_owned(), - }); - } - - let sp_wallet = local_device.get_wallet(); - let paired_device = local_device.get_paired_device_info().unwrap(); - - let mut outpoint_to_spend = local_device.get_next_output_to_spend(); - - if outpoint_to_spend == OutPoint::default() { - // First login - outpoint_to_spend = OutPoint::new( - Txid::from_byte_array(paired_device.incoming_pairing_transaction), - 0, - ); - } - - debug!("outpoint_to_spend: {}", outpoint_to_spend); - - debug!("outputs: {:?}", sp_wallet.get_outputs().to_outpoints_list()); - - let cipher_msg = CipherMessage::new( - local_device - .get_wallet() - .get_client() - .get_receiving_address(), - "LOGIN".to_owned(), + // We first generate the prd with all the keys that we will keep to ourselves + let full_prd = Prd::new_update( + commitment_outpoint, + serde_json::to_string(&sender)?, + fields2cipher.clone(), + fields2keys.clone(), ); - let commitment = create_commitment(serde_json::to_string(&cipher_msg)?); - - let recipient_address = SilentPaymentAddress::try_from(paired_device.address.as_str()).unwrap(); - - let recipient = Recipient { - address: recipient_address.into(), - amount: Amount::from_sat(1200), - nb_outputs: 1, - }; - - let mut freezed_utxos = lock_freezed_utxos()?; - - // we remove the outpoint we want to spend from our freezed utxos list - freezed_utxos.remove(&outpoint_to_spend); - - let signed_psbt = create_transaction( - &vec![&outpoint_to_spend], - &freezed_utxos, - sp_wallet, - vec![recipient], - Some(Vec::from_hex(&commitment)?), - Amount::from_sat(fee_rate.into()), - None, - )?; - - let partial_secret = sp_wallet - .get_client() - .get_partial_secret_from_psbt(&signed_psbt)?; - - let shared_point = sp_utils::sending::calculate_ecdh_shared_secret( - &recipient_address.get_scan_key(), - &partial_secret, - ); - - let shared_secret = AnkSharedSecret::new(shared_point); - - let cipher = encrypt_with_key( - serde_json::to_string(&cipher_msg)?, - shared_secret.to_byte_array().to_lower_hex_string(), - )?; - - // update our cache - let sp_address2vouts = map_outputs_to_sp_address(&signed_psbt.to_string())?; - let recipients_vouts = sp_address2vouts - .get::(&recipient_address.into()) - .expect("recipients didn't change") - .as_slice(); - - let final_tx = signed_psbt.extract_tx()?; - - let mut new_msg = CachedMessage::new(); - new_msg.plaintext.push(cipher_msg.message); - new_msg.ciphertext = Some(cipher); - new_msg.commitment = Some(commitment); - new_msg.commited_in = Some(OutPoint { - txid: final_tx.txid(), - vout: recipients_vouts[0] as u32, - }); - new_msg.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string()); - new_msg.recipient = Some(recipient_address.into()); - new_msg.sender = Some(cipher_msg.sender); - new_msg.tied_by = Some(1); // for now we just assume that's the second utxo, first being the notification itself - new_msg.status = CachedMessageStatus::Login; - lock_messages()?.push(new_msg.clone()); - - return Ok(createTransactionReturn { - txid: final_tx.txid().to_string(), - transaction: serialize(&final_tx).to_lower_hex_string(), - new_network_msg: new_msg, - }); -} - -#[wasm_bindgen] -pub fn create_pairing_transaction( - address: String, - fee_rate: u32, -) -> ApiResult { - let my_address = lock_local_device()? - .get_wallet() - .get_client() - .get_receiving_address(); - - let cipher_message = CipherMessage::new(my_address, "PAIRING".to_owned()); - - let mut res = create_notification_transaction(address, cipher_message, fee_rate)?; - - let mut messages = lock_messages()?; - - if let Some(m) = messages.iter_mut().find(|m| m.id == res.new_network_msg.id) { - m.status = CachedMessageStatus::Pairing; - res.new_network_msg = m.clone(); - } else { - unreachable!("We don't have in cache the message we just created"); - } - - Ok(res) -} - -#[wasm_bindgen] -pub fn create_notification_transaction( - address: String, - cipher_message: CipherMessage, - fee_rate: u32, -) -> ApiResult { - let sp_address: SilentPaymentAddress = address.as_str().try_into()?; - - let local_device = lock_local_device()?; - - let sp_wallet = local_device.get_wallet(); - - let recipient = Recipient { - address: sp_address.into(), - amount: Amount::from_sat(1200), - nb_outputs: 1, - }; - - let commitment = create_commitment(serde_json::to_string(&cipher_message)?); + let prd_commitment = full_prd.create_commitment(); let freezed_utxos = lock_freezed_utxos()?; @@ -1074,55 +1276,61 @@ pub fn create_notification_transaction( &vec![], &freezed_utxos, sp_wallet, - vec![recipient], - Some(Vec::from_hex(&commitment)?), + recipients, + Some(prd_commitment.as_byte_array().to_vec()), Amount::from_sat(fee_rate.into()), None, )?; + let sp_address2vouts = map_outputs_to_sp_address(&signed_psbt.to_string())?; + 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(), - &partial_secret, - ); - - let shared_secret = AnkSharedSecret::new(shared_point); - - let cipher = encrypt_with_key( - 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.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 = signed_psbt.extract_tx()?; - let mut new_msg = CachedMessage::new(); - new_msg.plaintext.push(cipher_message.message); - new_msg.ciphertext = Some(cipher); - new_msg.commitment = Some(commitment); - new_msg.commited_in = Some(OutPoint { - txid: final_tx.txid(), - vout: recipients_vouts[0] as u32, - }); - new_msg.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string()); - new_msg.recipient = Some(address); - new_msg.sender = Some(cipher_message.sender); - new_msg.tied_by = Some(1); // for now we just assume that's the second utxo, first being the notification itself - new_msg.status = CachedMessageStatus::SentWaitingConfirmation; - lock_messages()?.push(new_msg.clone()); - Ok(createTransactionReturn { - txid: final_tx.txid().to_string(), - transaction: serialize(&final_tx).to_lower_hex_string(), - new_network_msg: new_msg, + let mut ciphers = vec![]; + for (member, visible_fields) in all_members { + let mut prd = full_prd.clone(); + prd.filter_keys(visible_fields); + // we hash the payload + prd.payload = Value::from_str(&prd.payload) + .unwrap() + .tagged_hash() + .to_string(); + let prd_msg = prd.to_network_msg(sp_wallet)?; + + let addresses = member.get_addresses(); + for sp_address in addresses.into_iter() { + let shared_point = sp_utils::sending::calculate_ecdh_shared_secret( + &::try_from(sp_address.as_str())?.get_scan_key(), + &partial_secret, + ); + + let shared_secret = AnkSharedSecretHash::from_shared_point(shared_point); + + let cipher = encrypt_with_key(shared_secret.as_byte_array(), prd_msg.as_bytes())?; + ciphers.push(cipher.to_lower_hex_string()); + relevant_process.insert_shared_secret(SilentPaymentAddress::try_from(sp_address)?, shared_secret); + } + } + relevant_process.insert_impending_request(full_prd); + relevant_process.insert_state(ProcessState { + commited_in: OutPoint::null(), + encrypted_pcd: Value::Object(fields2cipher), + keys: fields2keys, + validation_token: vec![], + }); + + // Create the new_tx message + let new_tx_msg = NewTxMessage::new(serialize(&final_tx).to_lower_hex_string(), None); + + Ok(ApiReturn { + new_tx_to_send: Some(new_tx_msg), + updated_process: Some((commitment_outpoint.to_string(), relevant_process.clone())), + ciphers_to_send: ciphers, + ..Default::default() }) } @@ -1134,66 +1342,6 @@ pub struct encryptWithNewKeyResult { pub key: String, } -#[wasm_bindgen] -pub fn encrypt_with_key(plaintext: String, key: String) -> ApiResult { - let nonce = Aes256Gcm::generate_nonce(&mut rand::thread_rng()); - - let mut aes_key = [0u8; 32]; - aes_key.copy_from_slice(&Vec::from_hex(&key)?); - - // encrypt - let aes_enc = Aes256Encryption::import_key( - Purpose::Arbitrary, - plaintext.into_bytes(), - aes_key, - nonce.into(), - )?; - - let cipher = aes_enc.encrypt_with_aes_key()?; - - Ok(cipher.to_lower_hex_string()) -} - -#[wasm_bindgen] -pub fn encrypt_with_new_key(plaintext: String) -> ApiResult { - let mut rng = rand::thread_rng(); - - // generate new key - let aes_key = Aes256Gcm::generate_key(&mut rng); - let nonce = Aes256Gcm::generate_nonce(&mut rng); - - // encrypt - let aes_enc = Aes256Encryption::import_key( - Purpose::Arbitrary, - plaintext.into_bytes(), - aes_key.into(), - nonce.into(), - )?; - - let cipher = aes_enc.encrypt_with_aes_key()?; - - Ok(encryptWithNewKeyResult { - cipher: cipher.to_lower_hex_string(), - key: aes_key.to_lower_hex_string(), - }) -} - -#[wasm_bindgen] -pub fn try_decrypt_with_key(cipher: String, key: String) -> ApiResult { - let key_bin = Vec::from_hex(&key)?; - if key_bin.len() != 32 { - return Err(ApiError { - message: "key of invalid lenght".to_owned(), - }); - } - let mut aes_key = [0u8; 32]; - aes_key.copy_from_slice(&Vec::from_hex(&key)?); - let aes_dec = Aes256Decryption::new(Purpose::Arbitrary, Vec::from_hex(&cipher)?, aes_key)?; - - let plain = String::from_utf8(aes_dec.decrypt_with_key()?)?; - Ok(plain) -} - #[wasm_bindgen] pub fn create_faucet_msg() -> ApiResult { let sp_address = lock_local_device()? @@ -1203,21 +1351,49 @@ pub fn create_faucet_msg() -> ApiResult { let faucet_msg = FaucetMessage::new(sp_address.clone()); - let mut cached_msg = CachedMessage::new(); - cached_msg.recipient = Some(sp_address); - cached_msg.commitment = Some(faucet_msg.commitment.clone()); - cached_msg.status = CachedMessageStatus::FaucetWaiting; - lock_messages()?.push(cached_msg.clone()); - - let network_msg = AnkNetworkMsg::new(AnkFlag::Faucet, &faucet_msg.to_string()); + let network_msg = Envelope::new(AnkFlag::Faucet, &faucet_msg.to_string()); Ok(network_msg.to_string()) } +/// Get active update proposals for a given process outpoint +/// Returns a vector with the latest commited state first and all active proposals #[wasm_bindgen] -pub fn create_commitment(payload_to_hash: String) -> String { - let mut engine = sha256::HashEngine::default(); - engine.write_all(&payload_to_hash.as_bytes()); - let hash = sha256::Hash::from_engine(engine); - hash.to_byte_array().to_lower_hex_string() +pub fn get_update_proposals(process_outpoint: String) -> ApiResult> { + let outpoint = OutPoint::from_str(&process_outpoint)?; + + let mut processes = lock_processes()?; + + let relevant_process = processes + .get(&outpoint) + .ok_or(ApiError::new("process not found".to_owned()))?; + + let update_proposals: Vec<&Prd> = relevant_process + .get_impending_requests() + .into_iter() + .filter(|r| r.prd_type == PrdType::Update) + .collect(); + + if update_proposals.is_empty() { + return Err(ApiError::new(format!( + "No active update proposals for process {}", + process_outpoint + ))); + } + + let mut res = vec![]; + + // If we don't have a latest state, it simply means this update is a creation + if let Some(latest_state) = relevant_process.get_latest_state() { + res.push(serde_json::to_string(&latest_state)?); + } + + for proposal in update_proposals { + let pcd = Value::from_str(&proposal.payload)?; + let mut decrypted_pcd = Map::new(); + pcd.decrypt_fields(&proposal.keys, &mut decrypted_pcd)?; + res.push(serde_json::to_string(&decrypted_pcd)?); + } + + Ok(res) } diff --git a/src/lib.rs b/src/lib.rs index 983e281..188a0bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,11 @@ #![allow(warnings)] use anyhow::Error; -use sdk_common::crypto::AnkSharedSecret; use sdk_common::network::CachedMessage; -use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; -use std::fmt::Debug; +use sdk_common::MutexExt; use std::sync::{Mutex, MutexGuard, OnceLock}; -use tsify::Tsify; pub mod api; mod peers; -mod process; mod user; mod wallet; @@ -21,14 +16,3 @@ pub fn lock_messages() -> Result>, Error> .get_or_init(|| Mutex::new(vec![])) .lock_anyhow() } - -pub(crate) trait MutexExt { - fn lock_anyhow(&self) -> Result, Error>; -} - -impl MutexExt for Mutex { - fn lock_anyhow(&self) -> Result, Error> { - self.lock() - .map_err(|e| Error::msg(format!("Failed to lock: {}", e))) - } -} diff --git a/src/process.rs b/src/process.rs deleted file mode 100644 index f9d7692..0000000 --- a/src/process.rs +++ /dev/null @@ -1,405 +0,0 @@ -use std::fmt::DebugStruct; - -use sdk_common::sp_client::silentpayments::utils::SilentPaymentAddress; -use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; -use tsify::Tsify; -use wasm_bindgen::prelude::*; - -pub const HTML_KOTPART: &str = " -
-
-

Send encrypted messages

- -
-
- - -
- - -
- -
-
- "; - -pub const HTML_STORAGE: &str = " -
-
-

Send encrypted messages

- -
-
- - -
- - -
- -
-
- "; - -pub const HTML_MESSAGING: &str = " -
-
-

Send encrypted messages

- -
-
-
- - -
- - -
- -
-
- "; - -pub const CSS: &str = " - body { - margin: 0; - padding: 0; - display: flex; - align-items: center; - justify-content: center; - min-height: 100vh; - background-color: #f4f4f4; - font-family: 'Arial', sans-serif; - } - .container { - text-align: center; - } - .card { - max-width: 400px; - width: 100%; - padding: 20px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - background-color: #ffffff; - border-radius: 8px; - text-align: left; - overflow: hidden; - } - form { - display: flex; - flex-direction: column; - /* flex-wrap: wrap; */ - } - label { - font-weight: bold; - margin-bottom: 8px; - } - hr { - border: 0; - height: 1px; - background-color: #ddd; - margin: 10px 0; - } - input, select { - width: 100%; - padding: 10px; - margin: 8px 0; - box-sizing: border-box; - } - select { - padding: 10px; - background-color: #f9f9f9; - border: 1px solid #ddd; - border-radius: 4px; - cursor: pointer; - } - button { - display: inline-block; - background-color: #4caf50; - color: #fff; - border: none; - padding: 12px 17px; - border-radius: 4px; - cursor: pointer; - } - button:hover { - background-color: #45a049; - } - .side-by-side { - display: flex; - align-items: center; - justify-content: space-between; - } - .side-by-side>* { - display: inline-block; - } - button.recover { - display: inline-block; - text-align: center; - text-decoration: none; - display: inline-block; - background-color: #4caf50; - color: #fff; - border: none; - padding: 12px 17px; - border-radius: 4px; - cursor: pointer; - } - button.recover:hover { - background-color: #45a049; - } - a.btn { - display: inline-block; - text-align: center; - text-decoration: none; - display: inline-block; - background-color: #4caf50; - color: #fff; - border: none; - padding: 12px 17px; - border-radius: 4px; - cursor: pointer; - } - - a.btn:hover { - background-color: #45a049; - } - - a { - text-decoration: none; - color: #78a6de; - } - .bg-secondary { - background-color: #2b81ed; - } - .bg-primary { - background-color: #1A61ED; - } - .bg-primary:hover { - background-color: #457be8; - } - .card-revoke { - display: flex; - flex-direction: column; - max-width: 400px; - width: 100%; - padding: 20px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - background-color: #ffffff; - border-radius: 8px; - text-align: center; - align-items: center; - overflow: hidden; - } - .card-revoke a { - max-width: 50px; - width: 100%; - background: none; - border: none; - cursor: pointer; - } - .card-revoke button { - max-width: 200px; - width: 100%; - background: none; - border: none; - cursor: pointer; - color: #78a6de; - } - .card-revoke svg { - width: 100%; - height: auto; - fill: #333; - } - .image-label { - display: block; - color: #fff; - padding: 5px; - margin-top: 10px; - } - .image-container { - width: 400px; - height: 300px; - overflow: hidden; - } - .image-container img { - text-align: center; - width: 100%; - height: 100%; - object-fit: cover; - object-position: center center; - } - .passwordalert { - color: #FF0000; - } - "; - -pub const CSSUPDATE: &str = " - - "; - -pub const JSUPDATE: &str = " - var addSpAddressBtn = document.getElementById('add-sp-address-btn'); - var removeSpAddressBtn = document.querySelectorAll('.minus-sp-address-btn'); - - addSpAddressBtn.addEventListener('click', function (event) { - addDynamicField(this); - }); - - function addDynamicField(element) { - var addSpAddressBlock = document.getElementById('sp-address-block'); - var spAddress = addSpAddressBlock.querySelector('#sp-address').value; - addSpAddressBlock.querySelector('#sp-address').value = ''; - spAddress = spAddress.trim(); - if (spAddress != '') { - var sideBySideDiv = document.createElement('div'); - sideBySideDiv.className = 'side-by-side'; - - var inputElement = document.createElement('input'); - inputElement.type = 'text'; - inputElement.name = 'spAddresses[]'; - inputElement.setAttribute('form', 'no-form'); - inputElement.value = spAddress; - inputElement.disabled = true; - - var buttonElement = document.createElement('button'); - buttonElement.type = 'button'; - buttonElement.className = - 'circle-btn bg-secondary minus-sp-address-btn'; - buttonElement.innerHTML = '-'; - - buttonElement.addEventListener('click', function (event) { - removeDynamicField(this.parentElement); - }); - - sideBySideDiv.appendChild(inputElement); - sideBySideDiv.appendChild(buttonElement); - - addSpAddressBlock.appendChild(sideBySideDiv); - } - function removeDynamicField(element) { - element.remove(); - } - } - "; - -#[derive(Debug, Serialize, Deserialize, Default, Tsify)] -#[tsify(into_wasm_abi, from_wasm_abi)] -pub struct Process { - pub id: u32, - pub name: String, - pub version: String, - pub members: Vec, - pub html: String, - pub style: String, - pub script: String, - // Add conditions : process, member, peer, artefact -} diff --git a/src/user.rs b/src/user.rs index f244aa2..6097f17 100644 --- a/src/user.rs +++ b/src/user.rs @@ -21,6 +21,7 @@ use std::io::{Cursor, Read, Write}; use std::str::FromStr; use std::sync::{Mutex, MutexGuard, OnceLock}; +use sdk_common::device::Device; use sdk_common::sp_client::bitcoin::secp256k1::constants::SECRET_KEY_SIZE; use sdk_common::sp_client::silentpayments::bitcoin_hashes::sha256; use sdk_common::sp_client::silentpayments::utils::{Network as SpNetwork, SilentPaymentAddress}; @@ -29,9 +30,7 @@ use sdk_common::sp_client::spclient::{OutputList, SpWallet, SpendKey}; use crate::peers::Peer; use crate::wallet::generate_sp_wallet; use crate::MutexExt; -use sdk_common::crypto::{ - AeadCore, Aes256Decryption, Aes256Encryption, Aes256Gcm, HalfKey, KeyInit, Purpose, -}; +use sdk_common::crypto::{AeadCore, Aes256Gcm, KeyInit}; pub static LOCAL_DEVICE: OnceLock> = OnceLock::new(); @@ -58,142 +57,3 @@ pub fn lock_local_device() -> Result> { .get_or_init(|| Mutex::new(Device::default())) .lock_anyhow() } - -#[derive(Debug, Serialize, Deserialize, Clone, Default, Tsify)] -#[tsify(into_wasm_abi, from_wasm_abi)] -pub struct PairedDevice { - pub address: String, - pub outgoing_pairing_transaction: [u8; 32], - pub revokation_index: u32, - pub incoming_pairing_transaction: [u8; 32], - pub current_remote_key: [u8; 32], - pub current_session_outpoint: OutPoint, // This will be spend by remote device to notify us of next login - pub current_session_revokation_outpoint: OutPoint, // remote device can revoke current session by spending this -} - -impl PairedDevice { - pub fn new(address: SilentPaymentAddress, pairing_txid: Txid, revokation_index: u32) -> Self { - let mut pairing_transaction_buf = [0u8; 32]; - pairing_transaction_buf.copy_from_slice(&serialize(&pairing_txid)); - - Self { - address: address.into(), - outgoing_pairing_transaction: pairing_transaction_buf, - revokation_index, - incoming_pairing_transaction: [0u8; 32], - current_session_revokation_outpoint: OutPoint::default(), - current_session_outpoint: OutPoint::default(), - current_remote_key: [0u8; 32], - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, Default, Tsify)] -#[tsify(into_wasm_abi, from_wasm_abi)] -pub struct Device { - sp_wallet: SpWallet, - current_session_outpoint: OutPoint, // This is the notification output of incoming login tx - current_session_key: [u8; 32], - current_session_revokation_outpoint: OutPoint, // This is the revokation outpoint of outgoing login tx - paired_device: Option, -} - -impl Device { - pub fn new(sp_wallet: SpWallet) -> Self { - Self { - sp_wallet, - current_session_outpoint: OutPoint::default(), - current_session_key: [0u8; 32], - current_session_revokation_outpoint: OutPoint::default(), - paired_device: None, - } - } - - pub fn get_wallet(&self) -> &SpWallet { - &self.sp_wallet - } - - pub fn get_mut_wallet(&mut self) -> &mut SpWallet { - &mut self.sp_wallet - } - - pub fn is_linked(&self) -> bool { - self.paired_device.is_some() - } - - pub fn is_pairing(&self) -> bool { - self.current_session_key == [0u8; 32] - } - - pub fn get_paired_device_info(&self) -> Option { - self.paired_device.clone() - } - - pub fn get_next_output_to_spend(&self) -> OutPoint { - self.current_session_outpoint - } - - pub fn get_session_revokation_outpoint(&self) -> OutPoint { - self.current_session_revokation_outpoint - } - - pub fn sign_with_current_session_key(&self) -> Result<()> { - unimplemented!(); - } - - pub fn encrypt_with_current_session_key(&self) -> Result<()> { - unimplemented!(); - } - - pub fn new_link( - &mut self, - link_with: SilentPaymentAddress, - outgoing_pairing_tx: Txid, - revokation_output: u32, - incoming_pairing_tx: Txid, - ) -> Result<()> { - let address_looked_for: String = link_with.into(); - if let Some(paired_device) = self.paired_device.as_ref() { - return Err(Error::msg(format!( - "Found an already paired device with address {} and revokable by {}:{}", - paired_device.address, - Txid::from_byte_array(paired_device.outgoing_pairing_transaction), - paired_device.revokation_index - ))); - } else { - let mut new_device = - PairedDevice::new(link_with, outgoing_pairing_tx, revokation_output); - new_device.incoming_pairing_transaction = incoming_pairing_tx.to_byte_array(); - self.paired_device = Some(new_device); - } - - Ok(()) - } - - // We call that when we spent to the remote device and it similarly spent to us - pub fn update_session( - &mut self, - new_session_key: SecretKey, - new_session_outpoint: OutPoint, - new_revokation_outpoint: OutPoint, - new_remote_key: XOnlyPublicKey, - new_remote_session_outpoint: OutPoint, - new_remote_revokation_outpoint: OutPoint, - ) -> Result<()> { - if !self.is_linked() { - return Err(Error::msg("Can't update an unpaired device")); - } - self.paired_device - .as_mut() - .map(|d| { - d.current_remote_key = new_remote_key.serialize(); - d.current_session_outpoint = new_remote_session_outpoint; - d.current_session_revokation_outpoint = new_remote_revokation_outpoint; - }); - self.current_session_key = new_session_key.secret_bytes(); - self.current_session_outpoint = new_session_outpoint; - self.current_session_revokation_outpoint = new_revokation_outpoint; - - Ok(()) - } -} diff --git a/src/wallet.rs b/src/wallet.rs index 6114274..c82f079 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -33,7 +33,7 @@ pub fn generate_sp_wallet(label: Option, network: Network) -> anyhow::Re network, )?; let our_address: SilentPaymentAddress = sp_client.get_receiving_address().try_into()?; - log::info!( + sdk_common::log::info!( "Created client for sp with address: {}", our_address.to_string() ); diff --git a/tests/browser.rs b/tests/browser.rs deleted file mode 100644 index 412b0e8..0000000 --- a/tests/browser.rs +++ /dev/null @@ -1,550 +0,0 @@ -use std::collections::HashMap; - -use log::debug; -use sdk_client::api::{ - answer_confirmation_transaction, create_confirmation_transaction, create_login_transaction, create_notification_transaction, create_pairing_transaction, dump_device, dump_message_cache, dump_wallet, encrypt_with_key, encrypt_with_new_key, get_outputs, login, pair_device, parse_network_msg, reset_device, reset_message_cache, restore_device, restore_device_from_sp_wallet, set_message_cache, setup -}; -use sdk_common::network::{ - AnkNetworkMsg, CachedMessage, CachedMessageStatus, 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::secp256k1::PublicKey; -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::{OutputSpendStatus, 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 for pairing and login 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\"}}},\"tx_history\":[]}"; -const ALICE_LOGIN_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\":2146,\"outputs\":{\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:1\":{\"blockheight\":0,\"tweak\":\"7c84a3074c18c23a65eceaca49a327989ef6e7240e0e080421afb80f06906d73\",\"amount\":306,\"script\":\"5120eb78084d7a2ccbdb7eb7e9bba7cf875c4e54a5aba0beac57c5d3e41133bd8fdd\",\"label\":null,\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:2\":{\"blockheight\":0,\"tweak\":\"e3e21d551e933199f8c977bc0362616bdfb128da7ca9728bd7254977541c39cc\",\"amount\":3936897,\"script\":\"5120df3af55a63bd056c5d6d09f47a3a19c382d938c08db8bc41a59c5235970209ad\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:0\":{\"blockheight\":0,\"tweak\":\"6e5c1d4690b9ff4fa26e5185ada25af5c2987650e629a518db8a43fdaad7a26c\",\"amount\":349,\"script\":\"512088ac90e180c87b7fa6aa33db4c72a8620cd08fda3ba1c71430b904eb068c587f\",\"label\":null,\"spend_status\":\"Unspent\"},\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292\"}}}},\"tx_history\":[]}"; -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\"}}},\"tx_history\":[]}"; -const BOB_LOGIN_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\":{\"93722ea2fb9b74954210b4cdd1360e280b7ff1bc156d6b75f847e62411c588fb:0\":{\"blockheight\":0,\"tweak\":\"da5e3aa2378e3a257f99eb1e0ae4c672916f6a2f32a8ed9a8e146f2074da981b\",\"amount\":443,\"script\":\"51209eb9e950127b6a7d81668f25b4d5b164b42dafe59ce40b80e6c489ec983540d7\",\"label\":null,\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":{\"Spent\":\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f\"}},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:1\":{\"blockheight\":0,\"tweak\":\"f0ad83734cdc7d73575e5a32651390cf30b92cc7e44cf94ec37da46900ecaf71\",\"amount\":654,\"script\":\"5120230cc1e85829be238e666f469653cbc2f1c0e3675a9bf33e1d1f91115f5dd306\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":\"Unspent\"}}},\"tx_history\":[]}"; -const BOB_PAIRED_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\":{\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:1\":{\"blockheight\":0,\"tweak\":\"f0ad83734cdc7d73575e5a32651390cf30b92cc7e44cf94ec37da46900ecaf71\",\"amount\":654,\"script\":\"5120230cc1e85829be238e666f469653cbc2f1c0e3675a9bf33e1d1f91115f5dd306\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":{\"Spent\":\"a70e0a70c4df32e9e2bde88b9623eead5e24f25260ecb14252f0f0dd3094164a\"}},\"93722ea2fb9b74954210b4cdd1360e280b7ff1bc156d6b75f847e62411c588fb:0\":{\"blockheight\":0,\"tweak\":\"da5e3aa2378e3a257f99eb1e0ae4c672916f6a2f32a8ed9a8e146f2074da981b\",\"amount\":443,\"script\":\"51209eb9e950127b6a7d81668f25b4d5b164b42dafe59ce40b80e6c489ec983540d7\",\"label\":null,\"spend_status\":{\"Spent\":\"a70e0a70c4df32e9e2bde88b9623eead5e24f25260ecb14252f0f0dd3094164a\"}},\"e34824b0de3f00416caf295dd919fd695404c34334a36b9e7b81917c64fc9701:0\":{\"blockheight\":0,\"tweak\":\"49ab242529118b90ee3db481f7f979588bea59abaf71f6fdb1326156ec95d1f9\",\"amount\":1200,\"script\":\"51204d4dafb5ba432c6ecffc2db7965c901b20312e1beb08132ef9893073e0cbb1bd\",\"label\":null,\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":{\"Spent\":\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f\"}},\"a70e0a70c4df32e9e2bde88b9623eead5e24f25260ecb14252f0f0dd3094164a:1\":{\"blockheight\":0,\"tweak\":\"70265572d5f239fb96022c48c49ba73dc0c544cfe4a2a77900aae12476258e7c\",\"amount\":191,\"script\":\"5120e3c9e3a1a606214c0b2138efe90bf394a92f3210062899ad11e58b35dbf12d9d\",\"label\":null,\"spend_status\":\"Unspent\"},\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":{\"Spent\":\"a70e0a70c4df32e9e2bde88b9623eead5e24f25260ecb14252f0f0dd3094164a\"}},\"a70e0a70c4df32e9e2bde88b9623eead5e24f25260ecb14252f0f0dd3094164a:2\":{\"blockheight\":0,\"tweak\":\"dc37abf37053e241d71f6c401b056d8e6f944f06811daa18494387001f20faed\",\"amount\":99895946,\"script\":\"512050de7190c8b7dd4d6268e716f89a9be001674b8779574bed42be53fa02531c68\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"}}},\"tx_history\":[]}"; -const ALICE_SPK: &str = "51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1"; -const REVOKATION_OUTPUT: &str = - "3dd51588af6cc2e4ff8e405fd2620f19c8f4e09e05692ad57a9208a061687295:3"; -const ALICE_ANSWER_CACHE: &str = "[{\"id\":1436790358,\"status\":\"MustSpendConfirmation\",\"ciphertext\":\"05ec465a397eee0e6a01884010dace55ccb539413e46f12ac760ba811e206e22df9fc621ccb21c2485b548281e668eec8e6a814d509ce6b99febc40036d5f763f86f26f0db52badf0e662cb7533f5298afb09e2fe8624af126c90102084888bcd1d17c9419a045a73d983f3fb2825681d9c25d5875bc77c95eb016bf5df70123753e181078a6101f777e19b0d3d27f1032ee050c7b3635b039b5c94cb93b83c499ffacf8eb56b4d1ba693f8e1fb2e466761169af63222155f250ee1c80\",\"plaintext\":[\"TEST\"],\"commited_in\":\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:0\",\"tied_by\":1,\"commitment\":\"d12f3c5b37240bc3abf2976f41fdf9a594f0680aafd2781ac448f80440fbeb99\",\"sender\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"recipient\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"shared_secret\":\"ef89aabce6639a82436c10083dfaccf152aeb7c16731e7e639866da1c86d2fd9\",\"key\":null,\"confirmed_by\":\"650088ebc28e4d5d7b4ee2e0337b4907a02537f5be6c28c63da88a6b67b9af1d:0\",\"timestamp\":1723198590537,\"error\":null}]"; -const ALICE_FINAL_CACHE: &str = "[{\"id\":1436790358,\"status\":\"Trusted\",\"ciphertext\":\"05ec465a397eee0e6a01884010dace55ccb539413e46f12ac760ba811e206e22df9fc621ccb21c2485b548281e668eec8e6a814d509ce6b99febc40036d5f763f86f26f0db52badf0e662cb7533f5298afb09e2fe8624af126c90102084888bcd1d17c9419a045a73d983f3fb2825681d9c25d5875bc77c95eb016bf5df70123753e181078a6101f777e19b0d3d27f1032ee050c7b3635b039b5c94cb93b83c499ffacf8eb56b4d1ba693f8e1fb2e466761169af63222155f250ee1c80\",\"plaintext\":[\"TEST\"],\"commited_in\":\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:0\",\"tied_by\":1,\"commitment\":\"d12f3c5b37240bc3abf2976f41fdf9a594f0680aafd2781ac448f80440fbeb99\",\"sender\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"recipient\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"shared_secret\":\"ef89aabce6639a82436c10083dfaccf152aeb7c16731e7e639866da1c86d2fd9\",\"key\":null,\"confirmed_by\":\"650088ebc28e4d5d7b4ee2e0337b4907a02537f5be6c28c63da88a6b67b9af1d:0\",\"timestamp\":1723198590537,\"error\":null}]"; -const BOB_ANSWER_CACHE: &str = "[{\"id\":2887008190,\"status\":\"ReceivedMustConfirm\",\"ciphertext\":null,\"plaintext\":[\"TEST\"],\"commited_in\":\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:0\",\"tied_by\":null,\"commitment\":\"d12f3c5b37240bc3abf2976f41fdf9a594f0680aafd2781ac448f80440fbeb99\",\"sender\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"recipient\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"shared_secret\":\"ef89aabce6639a82436c10083dfaccf152aeb7c16731e7e639866da1c86d2fd9\",\"key\":null,\"confirmed_by\":\"650088ebc28e4d5d7b4ee2e0337b4907a02537f5be6c28c63da88a6b67b9af1d:0\",\"timestamp\":1723198590605,\"error\":null}]"; -const BOB_FINAL_CACHE: &str = "[{\"id\":2887008190,\"status\":\"Trusted\",\"ciphertext\":null,\"plaintext\":[\"TEST\"],\"commited_in\":\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:0\",\"tied_by\":null,\"commitment\":\"d12f3c5b37240bc3abf2976f41fdf9a594f0680aafd2781ac448f80440fbeb99\",\"sender\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"recipient\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"shared_secret\":\"ef89aabce6639a82436c10083dfaccf152aeb7c16731e7e639866da1c86d2fd9\",\"key\":null,\"confirmed_by\":\"650088ebc28e4d5d7b4ee2e0337b4907a02537f5be6c28c63da88a6b67b9af1d:0\",\"timestamp\":1723198590605,\"error\":null}]"; - -const BOB_PAIRING_CACHE: &str = "[{\"id\":2853916441,\"status\":\"Pairing\",\"ciphertext\":null,\"plaintext\":[\"PAIRING\"],\"commited_in\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:0\",\"tied_by\":null,\"commitment\":\"7f340cd5f070a4bd8d16077214a0488e43bd41d64d8b2b39ab0fc016c1093eba\",\"sender\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"recipient\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"shared_secret\":\"a86c30250a8e6a8dc9581b3ab2dacafda6446f6c65f4debe124f5870b6796ea1\",\"key\":null,\"confirmed_by\":null,\"timestamp\":1722436062445,\"error\":null}, {\"id\":1045548533,\"status\":\"Closed\",\"ciphertext\":\"bd2acbc73fc402a8cb57121153f2667b944fe31f67ccdd6031e7d9481642f42ebdae00b572b1be6735ff413ceedccf29d2f0700021c6cf61b954891912968a449e6cb735e9344d90db21268ff596f844db358bc4852358ac89a71e81179b249b499a6b70bef341ff71737ea0a9a7e3f2a90bb41d376272410a18ec367d5b6f417420d6d6ee06a132c263269cba281c1fa6e644b5beed716783e973594f5e9da76c28bdc10968ec7e49031924fb8051ec80d27f87e943f884263cb1a273b18f79\",\"plaintext\":[\"PAIRING\"],\"commited_in\":\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:0\",\"tied_by\":1,\"commitment\":\"213c1295d6721f3194745063190487e73853c21373218255d2af20c4b1ba6a7b\",\"sender\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"recipient\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"shared_secret\":\"094849bcf340d0791fa0bae31c9b58b8572ad3c9bd028de3a26e501b578cb24b\",\"key\":null,\"confirmed_by\":null,\"timestamp\":1722436062534,\"error\":null}]"; -const ALICE_PAIRING_CACHE: &str = "[{\"id\":2059106889,\"status\":\"Closed\",\"ciphertext\":\"6634050737f9dc18b187c69872943f9a11764252bac9e163fcb7480fd0aa3cf543f7b0b7eef9579d1ddba0b79fdf8c59747050313d0518ea4c8d23d2665948549efd72d0fde7bc8e7a02936de78b7406a31072429d9a81751d5665e9333f8c429c59e47a11b8993583fc5806c8e8695db970247761d11782188c550bd19f7a2c60172104c2892a463f79c19e4173852279012003edf3f777e7c1a95e7d87863d155bcaee1fc4c1b0cb69cab42a45ad5c6271a9c9ba4f79e49a3de2e0fa0aa9bc\",\"plaintext\":[\"PAIRING\"],\"commited_in\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:0\",\"tied_by\":1,\"commitment\":\"7f340cd5f070a4bd8d16077214a0488e43bd41d64d8b2b39ab0fc016c1093eba\",\"sender\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"recipient\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"shared_secret\":\"a86c30250a8e6a8dc9581b3ab2dacafda6446f6c65f4debe124f5870b6796ea1\",\"key\":null,\"confirmed_by\":null,\"timestamp\":1722436062375,\"error\":null}, {\"id\":3841225687,\"status\":\"Pairing\",\"ciphertext\":null,\"plaintext\":[\"PAIRING\"],\"commited_in\":\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:0\",\"tied_by\":null,\"commitment\":\"213c1295d6721f3194745063190487e73853c21373218255d2af20c4b1ba6a7b\",\"sender\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"recipient\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"shared_secret\":\"094849bcf340d0791fa0bae31c9b58b8572ad3c9bd028de3a26e501b578cb24b\",\"key\":null,\"confirmed_by\":null,\"timestamp\":1722436062605,\"error\":null}]"; - -const ALICE_CHALLENGE_CACHE: &str = "[{\"id\":1436790358,\"status\":\"SentWaitingConfirmation\",\"ciphertext\":\"05ec465a397eee0e6a01884010dace55ccb539413e46f12ac760ba811e206e22df9fc621ccb21c2485b548281e668eec8e6a814d509ce6b99febc40036d5f763f86f26f0db52badf0e662cb7533f5298afb09e2fe8624af126c90102084888bcd1d17c9419a045a73d983f3fb2825681d9c25d5875bc77c95eb016bf5df70123753e181078a6101f777e19b0d3d27f1032ee050c7b3635b039b5c94cb93b83c499ffacf8eb56b4d1ba693f8e1fb2e466761169af63222155f250ee1c80\",\"plaintext\":[\"TEST\"],\"commited_in\":\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:0\",\"tied_by\":1,\"commitment\":\"d12f3c5b37240bc3abf2976f41fdf9a594f0680aafd2781ac448f80440fbeb99\",\"sender\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"recipient\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"shared_secret\":\"ef89aabce6639a82436c10083dfaccf152aeb7c16731e7e639866da1c86d2fd9\",\"key\":null,\"confirmed_by\":null,\"timestamp\":1723198590537,\"error\":null}]"; -const BOB_CHALLENGE_CACHE: &str = "[{\"id\":2887008190,\"status\":\"ReceivedMustConfirm\",\"ciphertext\":null,\"plaintext\":[\"TEST\"],\"commited_in\":\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:0\",\"tied_by\":null,\"commitment\":\"d12f3c5b37240bc3abf2976f41fdf9a594f0680aafd2781ac448f80440fbeb99\",\"sender\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"recipient\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"shared_secret\":\"ef89aabce6639a82436c10083dfaccf152aeb7c16731e7e639866da1c86d2fd9\",\"key\":null,\"confirmed_by\":null,\"timestamp\":1723198590605,\"error\":null}]"; - -const ALICE_PAIRED_DEVICE: &str = "{\"sp_wallet\":{\"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\":2146,\"outputs\":{\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:2\":{\"blockheight\":0,\"tweak\":\"e3e21d551e933199f8c977bc0362616bdfb128da7ca9728bd7254977541c39cc\",\"amount\":3936897,\"script\":\"5120df3af55a63bd056c5d6d09f47a3a19c382d938c08db8bc41a59c5235970209ad\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292\"}},\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:0\":{\"blockheight\":0,\"tweak\":\"dac814c1ceff86cac3476e028ffa6eda0d9858bd74288a0e6e9e5cef271f723c\",\"amount\":1200,\"script\":\"51205e349be9fff1dc0c8e6bae3d35679029c57d9e554f0bb9705b71491d9b7569f1\",\"label\":null,\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:1\":{\"blockheight\":0,\"tweak\":\"7c84a3074c18c23a65eceaca49a327989ef6e7240e0e080421afb80f06906d73\",\"amount\":306,\"script\":\"5120eb78084d7a2ccbdb7eb7e9bba7cf875c4e54a5aba0beac57c5d3e41133bd8fdd\",\"label\":null,\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:0\":{\"blockheight\":0,\"tweak\":\"6e5c1d4690b9ff4fa26e5185ada25af5c2987650e629a518db8a43fdaad7a26c\",\"amount\":349,\"script\":\"512088ac90e180c87b7fa6aa33db4c72a8620cd08fda3ba1c71430b904eb068c587f\",\"label\":null,\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:2\":{\"blockheight\":0,\"tweak\":\"6ebffdcaf1da7b160b167caed039e795f8b6d5168903972f9436aaedef574f28\",\"amount\":3935806,\"script\":\"5120fe0ec587ba0a8f8b1ef7c708a8b67723e55d45fb8e5c12ffc8874d61dedc3c16\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"},\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:1\":{\"blockheight\":0,\"tweak\":\"cfea493360d6ffe2bfd13e4c5c2f677351e0c038f1ce0cd5383fadad6adc79f1\",\"amount\":191,\"script\":\"51200f008ff13f876bf328c69f10ad8621c9770ff8d5eb1861cb67199189a6308bb9\",\"label\":null,\"spend_status\":\"Unspent\"}}},\"tx_history\":[]},\"current_session_outpoint\":\"0000000000000000000000000000000000000000000000000000000000000000:4294967295\",\"current_session_key\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"current_session_revokation_outpoint\":\"0000000000000000000000000000000000000000000000000000000000000000:4294967295\",\"paired_device\":{\"address\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"outgoing_pairing_transaction\":[184,15,3,43,210,105,33,82,25,230,171,60,50,65,148,233,155,169,157,72,81,194,100,136,113,83,51,13,148,124,233,120],\"revokation_index\":1,\"incoming_pairing_transaction\":[45,121,119,37,134,133,105,239,129,247,25,31,24,117,14,236,248,39,135,158,142,195,2,15,53,45,106,175,177,156,160,35],\"current_remote_key\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"current_session_outpoint\":\"0000000000000000000000000000000000000000000000000000000000000000:4294967295\",\"current_session_revokation_outpoint\":\"0000000000000000000000000000000000000000000000000000000000000000:4294967295\"}}"; -const BOB_PAIRED_DEVICE: &str = "{\"sp_wallet\":{\"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\":{\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:0\":{\"blockheight\":0,\"tweak\":\"898284108952bd8b0e74030e00b81c1b387ee151a810ca3b40143da227707df2\",\"amount\":1200,\"script\":\"5120c0ff94a1318f0061960a713c927ff175a0d71a791497e94e70601c3baae739b8\",\"label\":null,\"spend_status\":\"Unspent\"},\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:1\":{\"blockheight\":0,\"tweak\":\"a68aed757eb3aca6bcbd0deb749eb26767963401e07d568b19f05f9945fce9b3\",\"amount\":248,\"script\":\"5120d3a39aed25fccd3338aa8c9d40c015febfb4361cffb6cfdcede3032317dc26ff\",\"label\":null,\"spend_status\":\"Unspent\"},\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:2\":{\"blockheight\":0,\"tweak\":\"e02bb8059b6c787d22715be95de87ea75c120e0f4e0507233bb3e1547b50d98b\",\"amount\":99895292,\"script\":\"51203207d027bb48df9a7c5798091b8bd4912c436c19ea41f04b48ea5dad31118183\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"93722ea2fb9b74954210b4cdd1360e280b7ff1bc156d6b75f847e62411c588fb:0\":{\"blockheight\":0,\"tweak\":\"da5e3aa2378e3a257f99eb1e0ae4c672916f6a2f32a8ed9a8e146f2074da981b\",\"amount\":443,\"script\":\"51209eb9e950127b6a7d81668f25b4d5b164b42dafe59ce40b80e6c489ec983540d7\",\"label\":null,\"spend_status\":{\"Spent\":\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d\"}},\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":{\"Spent\":\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d\"}},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:1\":{\"blockheight\":0,\"tweak\":\"f0ad83734cdc7d73575e5a32651390cf30b92cc7e44cf94ec37da46900ecaf71\",\"amount\":654,\"script\":\"5120230cc1e85829be238e666f469653cbc2f1c0e3675a9bf33e1d1f91115f5dd306\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":{\"Spent\":\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f\"}}}},\"tx_history\":[]},\"current_session_outpoint\":\"0000000000000000000000000000000000000000000000000000000000000000:4294967295\",\"current_session_key\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"current_session_revokation_outpoint\":\"0000000000000000000000000000000000000000000000000000000000000000:4294967295\",\"paired_device\":{\"address\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"outgoing_pairing_transaction\":[45,121,119,37,134,133,105,239,129,247,25,31,24,117,14,236,248,39,135,158,142,195,2,15,53,45,106,175,177,156,160,35],\"revokation_index\":1,\"incoming_pairing_transaction\":[184,15,3,43,210,105,33,82,25,230,171,60,50,65,148,233,155,169,157,72,81,194,100,136,113,83,51,13,148,124,233,120],\"current_remote_key\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"current_session_outpoint\":\"0000000000000000000000000000000000000000000000000000000000000000:4294967295\",\"current_session_revokation_outpoint\":\"0000000000000000000000000000000000000000000000000000000000000000:4294967295\"}}"; - -const ALICE_LOGGED_DEVICE: &str = "{\"sp_wallet\":{\"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\":2146,\"outputs\":{\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:0\":{\"blockheight\":0,\"tweak\":\"2e3636950174a42842da254d7936f589e8c9ded6e17c90656bb759467fff9ea0\",\"amount\":1200,\"script\":\"51209c3a71193f7fc52482fd516a1a36f4df55f646a15f15ecc4f55497262b06e7c8\",\"label\":null,\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:1\":{\"blockheight\":0,\"tweak\":\"7c84a3074c18c23a65eceaca49a327989ef6e7240e0e080421afb80f06906d73\",\"amount\":306,\"script\":\"5120eb78084d7a2ccbdb7eb7e9bba7cf875c4e54a5aba0beac57c5d3e41133bd8fdd\",\"label\":null,\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:0\":{\"blockheight\":0,\"tweak\":\"6e5c1d4690b9ff4fa26e5185ada25af5c2987650e629a518db8a43fdaad7a26c\",\"amount\":349,\"script\":\"512088ac90e180c87b7fa6aa33db4c72a8620cd08fda3ba1c71430b904eb068c587f\",\"label\":null,\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:2\":{\"blockheight\":0,\"tweak\":\"86a78efb4f522790d46096df9261435e3f7cab5f5129efe4ff77562fb4834a0c\",\"amount\":3935451,\"script\":\"5120c314279d078acaa41b5d897f98b81f09546001b6422e90e0b3f902ac7442a6b7\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"},\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:1\":{\"blockheight\":0,\"tweak\":\"49471984d232f62afb7421c904a26ec073b968e76e3e1b8f4371f2f730ba627f\",\"amount\":191,\"script\":\"51203b12839f99ca62bd09eb277bab720b9bb506003ed5b3fa6654f37a42f3cf1825\",\"label\":null,\"spend_status\":\"Unspent\"},\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292\"}},\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:1\":{\"blockheight\":0,\"tweak\":\"cfea493360d6ffe2bfd13e4c5c2f677351e0c038f1ce0cd5383fadad6adc79f1\",\"amount\":191,\"script\":\"51200f008ff13f876bf328c69f10ad8621c9770ff8d5eb1861cb67199189a6308bb9\",\"label\":null,\"spend_status\":{\"Spent\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c\"}},\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:2\":{\"blockheight\":0,\"tweak\":\"6ebffdcaf1da7b160b167caed039e795f8b6d5168903972f9436aaedef574f28\",\"amount\":3935806,\"script\":\"5120fe0ec587ba0a8f8b1ef7c708a8b67723e55d45fb8e5c12ffc8874d61dedc3c16\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c\"}},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:2\":{\"blockheight\":0,\"tweak\":\"e3e21d551e933199f8c977bc0362616bdfb128da7ca9728bd7254977541c39cc\",\"amount\":3936897,\"script\":\"5120df3af55a63bd056c5d6d09f47a3a19c382d938c08db8bc41a59c5235970209ad\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:0\":{\"blockheight\":0,\"tweak\":\"dac814c1ceff86cac3476e028ffa6eda0d9858bd74288a0e6e9e5cef271f723c\",\"amount\":1200,\"script\":\"51205e349be9fff1dc0c8e6bae3d35679029c57d9e554f0bb9705b71491d9b7569f1\",\"label\":null,\"spend_status\":{\"Spent\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c\"}}}},\"tx_history\":[]},\"current_session_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:0\",\"current_session_key\":[193,95,100,240,34,120,208,149,63,78,80,240,135,212,31,167,234,42,121,233,178,209,49,231,189,60,70,89,71,185,1,187],\"current_session_revokation_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:1\",\"paired_device\":{\"address\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"outgoing_pairing_transaction\":[184,15,3,43,210,105,33,82,25,230,171,60,50,65,148,233,155,169,157,72,81,194,100,136,113,83,51,13,148,124,233,120],\"revokation_index\":1,\"incoming_pairing_transaction\":[45,121,119,37,134,133,105,239,129,247,25,31,24,117,14,236,248,39,135,158,142,195,2,15,53,45,106,175,177,156,160,35],\"current_remote_key\":[16,187,49,126,30,231,11,43,228,86,96,159,92,122,234,168,16,107,97,83,38,34,15,14,115,173,94,120,208,50,47,34],\"current_session_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:0\",\"current_session_revokation_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:1\"}}"; -const BOB_LOGGED_DEVICE: &str = "{\"sp_wallet\":{\"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\":{\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:1\":{\"blockheight\":0,\"tweak\":\"73cd0a0ebe8e61c3f5ec6b414107934238c4642924272823fb630ef828c14a97\",\"amount\":248,\"script\":\"51202c1f1be3cff73416dc34de0d5f7760095a51f6b4a7bfa118441221718394beea\",\"label\":null,\"spend_status\":\"Unspent\"},\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:0\":{\"blockheight\":0,\"tweak\":\"898284108952bd8b0e74030e00b81c1b387ee151a810ca3b40143da227707df2\",\"amount\":1200,\"script\":\"5120c0ff94a1318f0061960a713c927ff175a0d71a791497e94e70601c3baae739b8\",\"label\":null,\"spend_status\":{\"Spent\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f\"}},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:1\":{\"blockheight\":0,\"tweak\":\"f0ad83734cdc7d73575e5a32651390cf30b92cc7e44cf94ec37da46900ecaf71\",\"amount\":654,\"script\":\"5120230cc1e85829be238e666f469653cbc2f1c0e3675a9bf33e1d1f91115f5dd306\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:2\":{\"blockheight\":0,\"tweak\":\"b13107995241e7daaf13e89ff62a007dd592173c02284a46c9d40778e9fd8165\",\"amount\":99894746,\"script\":\"5120fc7226dc54ed54144d7f6081726fa1d6b5d0fc6aca09a0cc689cc9552e81db14\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":{\"Spent\":\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f\"}},\"93722ea2fb9b74954210b4cdd1360e280b7ff1bc156d6b75f847e62411c588fb:0\":{\"blockheight\":0,\"tweak\":\"da5e3aa2378e3a257f99eb1e0ae4c672916f6a2f32a8ed9a8e146f2074da981b\",\"amount\":443,\"script\":\"51209eb9e950127b6a7d81668f25b4d5b164b42dafe59ce40b80e6c489ec983540d7\",\"label\":null,\"spend_status\":{\"Spent\":\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d\"}},\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:1\":{\"blockheight\":0,\"tweak\":\"a68aed757eb3aca6bcbd0deb749eb26767963401e07d568b19f05f9945fce9b3\",\"amount\":248,\"script\":\"5120d3a39aed25fccd3338aa8c9d40c015febfb4361cffb6cfdcede3032317dc26ff\",\"label\":null,\"spend_status\":\"Unspent\"},\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:2\":{\"blockheight\":0,\"tweak\":\"e02bb8059b6c787d22715be95de87ea75c120e0f4e0507233bb3e1547b50d98b\",\"amount\":99895292,\"script\":\"51203207d027bb48df9a7c5798091b8bd4912c436c19ea41f04b48ea5dad31118183\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":{\"Spent\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f\"}},\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:0\":{\"blockheight\":0,\"tweak\":\"ac6c259ddf88509b3663a412f4d40403cafb3a89c8a6922770384ba1806ecda0\",\"amount\":1200,\"script\":\"512010bb317e1ee70b2be456609f5c7aeaa8106b615326220f0e73ad5e78d0322f22\",\"label\":null,\"spend_status\":\"Unspent\"},\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":{\"Spent\":\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d\"}}}},\"tx_history\":[]},\"current_session_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:0\",\"current_session_key\":[92,106,141,254,85,117,14,178,239,50,33,247,83,151,23,25,144,142,183,115,190,26,14,113,238,72,13,18,29,254,37,87],\"current_session_revokation_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:1\",\"paired_device\":{\"address\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"outgoing_pairing_transaction\":[45,121,119,37,134,133,105,239,129,247,25,31,24,117,14,236,248,39,135,158,142,195,2,15,53,45,106,175,177,156,160,35],\"revokation_index\":1,\"incoming_pairing_transaction\":[184,15,3,43,210,105,33,82,25,230,171,60,50,65,148,233,155,169,157,72,81,194,100,136,113,83,51,13,148,124,233,120],\"current_remote_key\":[156,58,113,25,63,127,197,36,130,253,81,106,26,54,244,223,85,246,70,161,95,21,236,196,245,84,151,38,43,6,231,200],\"current_session_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:0\",\"current_session_revokation_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:1\"}}"; - -const ALICE_CHALLENGE_DEVICE: &str = "{\"sp_wallet\":{\"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\":2146,\"outputs\":{\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:1\":{\"blockheight\":0,\"tweak\":\"cfea493360d6ffe2bfd13e4c5c2f677351e0c038f1ce0cd5383fadad6adc79f1\",\"amount\":191,\"script\":\"51200f008ff13f876bf328c69f10ad8621c9770ff8d5eb1861cb67199189a6308bb9\",\"label\":null,\"spend_status\":{\"Spent\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c\"}},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:2\":{\"blockheight\":0,\"tweak\":\"e3e21d551e933199f8c977bc0362616bdfb128da7ca9728bd7254977541c39cc\",\"amount\":3936897,\"script\":\"5120df3af55a63bd056c5d6d09f47a3a19c382d938c08db8bc41a59c5235970209ad\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:2\":{\"blockheight\":0,\"tweak\":\"6493d306b4650a36321d05270e2067b58839637da52f0fc29cd2f05817fff0d6\",\"amount\":3933896,\"script\":\"51206666fc11233881b1c831e032e9b06cf05a90ed93fef4f35c78583b9eec845ab5\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"},\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:1\":{\"blockheight\":0,\"tweak\":\"49471984d232f62afb7421c904a26ec073b968e76e3e1b8f4371f2f730ba627f\",\"amount\":191,\"script\":\"51203b12839f99ca62bd09eb277bab720b9bb506003ed5b3fa6654f37a42f3cf1825\",\"label\":null,\"spend_status\":{\"Spent\":\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2\"}},\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:0\":{\"blockheight\":0,\"tweak\":\"2e3636950174a42842da254d7936f589e8c9ded6e17c90656bb759467fff9ea0\",\"amount\":1200,\"script\":\"51209c3a71193f7fc52482fd516a1a36f4df55f646a15f15ecc4f55497262b06e7c8\",\"label\":null,\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:1\":{\"blockheight\":0,\"tweak\":\"7c84a3074c18c23a65eceaca49a327989ef6e7240e0e080421afb80f06906d73\",\"amount\":306,\"script\":\"5120eb78084d7a2ccbdb7eb7e9bba7cf875c4e54a5aba0beac57c5d3e41133bd8fdd\",\"label\":null,\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:0\":{\"blockheight\":0,\"tweak\":\"6e5c1d4690b9ff4fa26e5185ada25af5c2987650e629a518db8a43fdaad7a26c\",\"amount\":349,\"script\":\"512088ac90e180c87b7fa6aa33db4c72a8620cd08fda3ba1c71430b904eb068c587f\",\"label\":null,\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:2\":{\"blockheight\":0,\"tweak\":\"86a78efb4f522790d46096df9261435e3f7cab5f5129efe4ff77562fb4834a0c\",\"amount\":3935451,\"script\":\"5120c314279d078acaa41b5d897f98b81f09546001b6422e90e0b3f902ac7442a6b7\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2\"}},\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:2\":{\"blockheight\":0,\"tweak\":\"6ebffdcaf1da7b160b167caed039e795f8b6d5168903972f9436aaedef574f28\",\"amount\":3935806,\"script\":\"5120fe0ec587ba0a8f8b1ef7c708a8b67723e55d45fb8e5c12ffc8874d61dedc3c16\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c\"}},\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292\"}},\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:0\":{\"blockheight\":0,\"tweak\":\"dac814c1ceff86cac3476e028ffa6eda0d9858bd74288a0e6e9e5cef271f723c\",\"amount\":1200,\"script\":\"51205e349be9fff1dc0c8e6bae3d35679029c57d9e554f0bb9705b71491d9b7569f1\",\"label\":null,\"spend_status\":{\"Spent\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c\"}},\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:1\":{\"blockheight\":0,\"tweak\":\"97b06ded6644c6ae2a861342db3375d10a002da72f367a0acd5b8890c277df1e\",\"amount\":248,\"script\":\"5120902e75b33f6a80f20edd2574f02f5eaf3fc196aaad9c494263465519606a47a7\",\"label\":null,\"spend_status\":\"Unspent\"}}},\"tx_history\":[]},\"current_session_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:0\",\"current_session_key\":[193,95,100,240,34,120,208,149,63,78,80,240,135,212,31,167,234,42,121,233,178,209,49,231,189,60,70,89,71,185,1,187],\"current_session_revokation_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:1\",\"paired_device\":{\"address\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"outgoing_pairing_transaction\":[184,15,3,43,210,105,33,82,25,230,171,60,50,65,148,233,155,169,157,72,81,194,100,136,113,83,51,13,148,124,233,120],\"revokation_index\":1,\"incoming_pairing_transaction\":[45,121,119,37,134,133,105,239,129,247,25,31,24,117,14,236,248,39,135,158,142,195,2,15,53,45,106,175,177,156,160,35],\"current_remote_key\":[16,187,49,126,30,231,11,43,228,86,96,159,92,122,234,168,16,107,97,83,38,34,15,14,115,173,94,120,208,50,47,34],\"current_session_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:0\",\"current_session_revokation_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:1\"}}"; -const BOB_CHALLENGE_DEVICE: &str = "{\"sp_wallet\":{\"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\":{\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:2\":{\"blockheight\":0,\"tweak\":\"e02bb8059b6c787d22715be95de87ea75c120e0f4e0507233bb3e1547b50d98b\",\"amount\":99895292,\"script\":\"51203207d027bb48df9a7c5798091b8bd4912c436c19ea41f04b48ea5dad31118183\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":{\"Spent\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f\"}},\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:1\":{\"blockheight\":0,\"tweak\":\"a68aed757eb3aca6bcbd0deb749eb26767963401e07d568b19f05f9945fce9b3\",\"amount\":248,\"script\":\"5120d3a39aed25fccd3338aa8c9d40c015febfb4361cffb6cfdcede3032317dc26ff\",\"label\":null,\"spend_status\":\"Unspent\"},\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:0\":{\"blockheight\":0,\"tweak\":\"898284108952bd8b0e74030e00b81c1b387ee151a810ca3b40143da227707df2\",\"amount\":1200,\"script\":\"5120c0ff94a1318f0061960a713c927ff175a0d71a791497e94e70601c3baae739b8\",\"label\":null,\"spend_status\":{\"Spent\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f\"}},\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":{\"Spent\":\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d\"}},\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:0\":{\"blockheight\":0,\"tweak\":\"21fc32bded971dd15387671436232f17d932e18758f69b476219e2d6e688767f\",\"amount\":1200,\"script\":\"5120230d06322d10a5838634df3c55eef9959a1c0c144108e0416b9687eaf22e6baa\",\"label\":null,\"spend_status\":\"Unspent\"},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:1\":{\"blockheight\":0,\"tweak\":\"f0ad83734cdc7d73575e5a32651390cf30b92cc7e44cf94ec37da46900ecaf71\",\"amount\":654,\"script\":\"5120230cc1e85829be238e666f469653cbc2f1c0e3675a9bf33e1d1f91115f5dd306\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"93722ea2fb9b74954210b4cdd1360e280b7ff1bc156d6b75f847e62411c588fb:0\":{\"blockheight\":0,\"tweak\":\"da5e3aa2378e3a257f99eb1e0ae4c672916f6a2f32a8ed9a8e146f2074da981b\",\"amount\":443,\"script\":\"51209eb9e950127b6a7d81668f25b4d5b164b42dafe59ce40b80e6c489ec983540d7\",\"label\":null,\"spend_status\":{\"Spent\":\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d\"}},\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:1\":{\"blockheight\":0,\"tweak\":\"73cd0a0ebe8e61c3f5ec6b414107934238c4642924272823fb630ef828c14a97\",\"amount\":248,\"script\":\"51202c1f1be3cff73416dc34de0d5f7760095a51f6b4a7bfa118441221718394beea\",\"label\":null,\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":{\"Spent\":\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f\"}},\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:0\":{\"blockheight\":0,\"tweak\":\"ac6c259ddf88509b3663a412f4d40403cafb3a89c8a6922770384ba1806ecda0\",\"amount\":1200,\"script\":\"512010bb317e1ee70b2be456609f5c7aeaa8106b615326220f0e73ad5e78d0322f22\",\"label\":null,\"spend_status\":\"Unspent\"},\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:2\":{\"blockheight\":0,\"tweak\":\"b13107995241e7daaf13e89ff62a007dd592173c02284a46c9d40778e9fd8165\",\"amount\":99894746,\"script\":\"5120fc7226dc54ed54144d7f6081726fa1d6b5d0fc6aca09a0cc689cc9552e81db14\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"}}},\"tx_history\":[]},\"current_session_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:0\",\"current_session_key\":[92,106,141,254,85,117,14,178,239,50,33,247,83,151,23,25,144,142,183,115,190,26,14,113,238,72,13,18,29,254,37,87],\"current_session_revokation_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:1\",\"paired_device\":{\"address\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"outgoing_pairing_transaction\":[45,121,119,37,134,133,105,239,129,247,25,31,24,117,14,236,248,39,135,158,142,195,2,15,53,45,106,175,177,156,160,35],\"revokation_index\":1,\"incoming_pairing_transaction\":[184,15,3,43,210,105,33,82,25,230,171,60,50,65,148,233,155,169,157,72,81,194,100,136,113,83,51,13,148,124,233,120],\"current_remote_key\":[156,58,113,25,63,127,197,36,130,253,81,106,26,54,244,223,85,246,70,161,95,21,236,196,245,84,151,38,43,6,231,200],\"current_session_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:0\",\"current_session_revokation_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:1\"}}"; - -const ALICE_ANSWER_DEVICE: &str = "{\"sp_wallet\":{\"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\":2146,\"outputs\":{\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:2\":{\"blockheight\":0,\"tweak\":\"6493d306b4650a36321d05270e2067b58839637da52f0fc29cd2f05817fff0d6\",\"amount\":3933896,\"script\":\"51206666fc11233881b1c831e032e9b06cf05a90ed93fef4f35c78583b9eec845ab5\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"},\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:2\":{\"blockheight\":0,\"tweak\":\"86a78efb4f522790d46096df9261435e3f7cab5f5129efe4ff77562fb4834a0c\",\"amount\":3935451,\"script\":\"5120c314279d078acaa41b5d897f98b81f09546001b6422e90e0b3f902ac7442a6b7\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2\"}},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:2\":{\"blockheight\":0,\"tweak\":\"e3e21d551e933199f8c977bc0362616bdfb128da7ca9728bd7254977541c39cc\",\"amount\":3936897,\"script\":\"5120df3af55a63bd056c5d6d09f47a3a19c382d938c08db8bc41a59c5235970209ad\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:0\":{\"blockheight\":0,\"tweak\":\"6e5c1d4690b9ff4fa26e5185ada25af5c2987650e629a518db8a43fdaad7a26c\",\"amount\":349,\"script\":\"512088ac90e180c87b7fa6aa33db4c72a8620cd08fda3ba1c71430b904eb068c587f\",\"label\":null,\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:2\":{\"blockheight\":0,\"tweak\":\"6ebffdcaf1da7b160b167caed039e795f8b6d5168903972f9436aaedef574f28\",\"amount\":3935806,\"script\":\"5120fe0ec587ba0a8f8b1ef7c708a8b67723e55d45fb8e5c12ffc8874d61dedc3c16\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c\"}},\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:0\":{\"blockheight\":0,\"tweak\":\"dac814c1ceff86cac3476e028ffa6eda0d9858bd74288a0e6e9e5cef271f723c\",\"amount\":1200,\"script\":\"51205e349be9fff1dc0c8e6bae3d35679029c57d9e554f0bb9705b71491d9b7569f1\",\"label\":null,\"spend_status\":{\"Spent\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c\"}},\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:1\":{\"blockheight\":0,\"tweak\":\"cfea493360d6ffe2bfd13e4c5c2f677351e0c038f1ce0cd5383fadad6adc79f1\",\"amount\":191,\"script\":\"51200f008ff13f876bf328c69f10ad8621c9770ff8d5eb1861cb67199189a6308bb9\",\"label\":null,\"spend_status\":{\"Spent\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c\"}},\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292\"}},\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:1\":{\"blockheight\":0,\"tweak\":\"97b06ded6644c6ae2a861342db3375d10a002da72f367a0acd5b8890c277df1e\",\"amount\":248,\"script\":\"5120902e75b33f6a80f20edd2574f02f5eaf3fc196aaad9c494263465519606a47a7\",\"label\":null,\"spend_status\":\"Unspent\"},\"650088ebc28e4d5d7b4ee2e0337b4907a02537f5be6c28c63da88a6b67b9af1d:0\":{\"blockheight\":0,\"tweak\":\"de507c854cee16d2e157fbf6c6dbfd2e721b861efc7f657bb724790fc7829665\",\"amount\":349,\"script\":\"5120028143243ed5d4554e60d96ee61fdf6f6e359a9083311c210645c0a7fef51248\",\"label\":null,\"spend_status\":\"Unspent\"},\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:1\":{\"blockheight\":0,\"tweak\":\"49471984d232f62afb7421c904a26ec073b968e76e3e1b8f4371f2f730ba627f\",\"amount\":191,\"script\":\"51203b12839f99ca62bd09eb277bab720b9bb506003ed5b3fa6654f37a42f3cf1825\",\"label\":null,\"spend_status\":{\"Spent\":\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2\"}},\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:0\":{\"blockheight\":0,\"tweak\":\"2e3636950174a42842da254d7936f589e8c9ded6e17c90656bb759467fff9ea0\",\"amount\":1200,\"script\":\"51209c3a71193f7fc52482fd516a1a36f4df55f646a15f15ecc4f55497262b06e7c8\",\"label\":null,\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:1\":{\"blockheight\":0,\"tweak\":\"7c84a3074c18c23a65eceaca49a327989ef6e7240e0e080421afb80f06906d73\",\"amount\":306,\"script\":\"5120eb78084d7a2ccbdb7eb7e9bba7cf875c4e54a5aba0beac57c5d3e41133bd8fdd\",\"label\":null,\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}}}},\"tx_history\":[]},\"current_session_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:0\",\"current_session_key\":[193,95,100,240,34,120,208,149,63,78,80,240,135,212,31,167,234,42,121,233,178,209,49,231,189,60,70,89,71,185,1,187],\"current_session_revokation_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:1\",\"paired_device\":{\"address\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"outgoing_pairing_transaction\":[184,15,3,43,210,105,33,82,25,230,171,60,50,65,148,233,155,169,157,72,81,194,100,136,113,83,51,13,148,124,233,120],\"revokation_index\":1,\"incoming_pairing_transaction\":[45,121,119,37,134,133,105,239,129,247,25,31,24,117,14,236,248,39,135,158,142,195,2,15,53,45,106,175,177,156,160,35],\"current_remote_key\":[16,187,49,126,30,231,11,43,228,86,96,159,92,122,234,168,16,107,97,83,38,34,15,14,115,173,94,120,208,50,47,34],\"current_session_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:0\",\"current_session_revokation_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:1\"}}"; -const BOB_ANSWER_DEVICE: &str = "{\"sp_wallet\":{\"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\":{\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:2\":{\"blockheight\":0,\"tweak\":\"e02bb8059b6c787d22715be95de87ea75c120e0f4e0507233bb3e1547b50d98b\",\"amount\":99895292,\"script\":\"51203207d027bb48df9a7c5798091b8bd4912c436c19ea41f04b48ea5dad31118183\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":{\"Spent\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f\"}},\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:0\":{\"blockheight\":0,\"tweak\":\"898284108952bd8b0e74030e00b81c1b387ee151a810ca3b40143da227707df2\",\"amount\":1200,\"script\":\"5120c0ff94a1318f0061960a713c927ff175a0d71a791497e94e70601c3baae739b8\",\"label\":null,\"spend_status\":{\"Spent\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f\"}},\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:2\":{\"blockheight\":0,\"tweak\":\"b13107995241e7daaf13e89ff62a007dd592173c02284a46c9d40778e9fd8165\",\"amount\":99894746,\"script\":\"5120fc7226dc54ed54144d7f6081726fa1d6b5d0fc6aca09a0cc689cc9552e81db14\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:0\":{\"blockheight\":0,\"tweak\":\"ac6c259ddf88509b3663a412f4d40403cafb3a89c8a6922770384ba1806ecda0\",\"amount\":1200,\"script\":\"512010bb317e1ee70b2be456609f5c7aeaa8106b615326220f0e73ad5e78d0322f22\",\"label\":null,\"spend_status\":\"Unspent\"},\"650088ebc28e4d5d7b4ee2e0337b4907a02537f5be6c28c63da88a6b67b9af1d:1\":{\"blockheight\":0,\"tweak\":\"281ea54f74e5f6582a7462bdd9397b9a786f97455e858e7901a6a4fa95f39192\",\"amount\":654,\"script\":\"5120aa4395e2be9cbce580e94f4308d64422da251b7df12fb8c571675d40cc50cf00\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:1\":{\"blockheight\":0,\"tweak\":\"f0ad83734cdc7d73575e5a32651390cf30b92cc7e44cf94ec37da46900ecaf71\",\"amount\":654,\"script\":\"5120230cc1e85829be238e666f469653cbc2f1c0e3675a9bf33e1d1f91115f5dd306\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":{\"Spent\":\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f\"}},\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:1\":{\"blockheight\":0,\"tweak\":\"a68aed757eb3aca6bcbd0deb749eb26767963401e07d568b19f05f9945fce9b3\",\"amount\":248,\"script\":\"5120d3a39aed25fccd3338aa8c9d40c015febfb4361cffb6cfdcede3032317dc26ff\",\"label\":null,\"spend_status\":\"Unspent\"},\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:1\":{\"blockheight\":0,\"tweak\":\"73cd0a0ebe8e61c3f5ec6b414107934238c4642924272823fb630ef828c14a97\",\"amount\":248,\"script\":\"51202c1f1be3cff73416dc34de0d5f7760095a51f6b4a7bfa118441221718394beea\",\"label\":null,\"spend_status\":\"Unspent\"},\"93722ea2fb9b74954210b4cdd1360e280b7ff1bc156d6b75f847e62411c588fb:0\":{\"blockheight\":0,\"tweak\":\"da5e3aa2378e3a257f99eb1e0ae4c672916f6a2f32a8ed9a8e146f2074da981b\",\"amount\":443,\"script\":\"51209eb9e950127b6a7d81668f25b4d5b164b42dafe59ce40b80e6c489ec983540d7\",\"label\":null,\"spend_status\":{\"Spent\":\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d\"}},\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":{\"Spent\":\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d\"}},\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:0\":{\"blockheight\":0,\"tweak\":\"21fc32bded971dd15387671436232f17d932e18758f69b476219e2d6e688767f\",\"amount\":1200,\"script\":\"5120230d06322d10a5838634df3c55eef9959a1c0c144108e0416b9687eaf22e6baa\",\"label\":null,\"spend_status\":{\"Spent\":\"650088ebc28e4d5d7b4ee2e0337b4907a02537f5be6c28c63da88a6b67b9af1d\"}}}},\"tx_history\":[]},\"current_session_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:0\",\"current_session_key\":[92,106,141,254,85,117,14,178,239,50,33,247,83,151,23,25,144,142,183,115,190,26,14,113,238,72,13,18,29,254,37,87],\"current_session_revokation_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:1\",\"paired_device\":{\"address\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"outgoing_pairing_transaction\":[45,121,119,37,134,133,105,239,129,247,25,31,24,117,14,236,248,39,135,158,142,195,2,15,53,45,106,175,177,156,160,35],\"revokation_index\":1,\"incoming_pairing_transaction\":[184,15,3,43,210,105,33,82,25,230,171,60,50,65,148,233,155,169,157,72,81,194,100,136,113,83,51,13,148,124,233,120],\"current_remote_key\":[156,58,113,25,63,127,197,36,130,253,81,106,26,54,244,223,85,246,70,161,95,21,236,196,245,84,151,38,43,6,231,200],\"current_session_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:0\",\"current_session_revokation_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:1\"}}"; - -const ALICE_FINAL_DEVICE: &str = "{\"sp_wallet\":{\"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\":2146,\"outputs\":{\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:1\":{\"blockheight\":0,\"tweak\":\"cfea493360d6ffe2bfd13e4c5c2f677351e0c038f1ce0cd5383fadad6adc79f1\",\"amount\":191,\"script\":\"51200f008ff13f876bf328c69f10ad8621c9770ff8d5eb1861cb67199189a6308bb9\",\"label\":null,\"spend_status\":{\"Spent\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c\"}},\"650088ebc28e4d5d7b4ee2e0337b4907a02537f5be6c28c63da88a6b67b9af1d:0\":{\"blockheight\":0,\"tweak\":\"de507c854cee16d2e157fbf6c6dbfd2e721b861efc7f657bb724790fc7829665\",\"amount\":349,\"script\":\"5120028143243ed5d4554e60d96ee61fdf6f6e359a9083311c210645c0a7fef51248\",\"label\":null,\"spend_status\":{\"Spent\":\"f07c116303c88e7b22169f0872c67e3196ad8312170313643a8f47482c6bd308\"}},\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:1\":{\"blockheight\":0,\"tweak\":\"97b06ded6644c6ae2a861342db3375d10a002da72f367a0acd5b8890c277df1e\",\"amount\":248,\"script\":\"5120902e75b33f6a80f20edd2574f02f5eaf3fc196aaad9c494263465519606a47a7\",\"label\":null,\"spend_status\":{\"Spent\":\"f07c116303c88e7b22169f0872c67e3196ad8312170313643a8f47482c6bd308\"}},\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:1\":{\"blockheight\":0,\"tweak\":\"49471984d232f62afb7421c904a26ec073b968e76e3e1b8f4371f2f730ba627f\",\"amount\":191,\"script\":\"51203b12839f99ca62bd09eb277bab720b9bb506003ed5b3fa6654f37a42f3cf1825\",\"label\":null,\"spend_status\":{\"Spent\":\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2\"}},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:2\":{\"blockheight\":0,\"tweak\":\"e3e21d551e933199f8c977bc0362616bdfb128da7ca9728bd7254977541c39cc\",\"amount\":3936897,\"script\":\"5120df3af55a63bd056c5d6d09f47a3a19c382d938c08db8bc41a59c5235970209ad\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:2\":{\"blockheight\":0,\"tweak\":\"6ebffdcaf1da7b160b167caed039e795f8b6d5168903972f9436aaedef574f28\",\"amount\":3935806,\"script\":\"5120fe0ec587ba0a8f8b1ef7c708a8b67723e55d45fb8e5c12ffc8874d61dedc3c16\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c\"}},\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292\"}},\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:0\":{\"blockheight\":0,\"tweak\":\"dac814c1ceff86cac3476e028ffa6eda0d9858bd74288a0e6e9e5cef271f723c\",\"amount\":1200,\"script\":\"51205e349be9fff1dc0c8e6bae3d35679029c57d9e554f0bb9705b71491d9b7569f1\",\"label\":null,\"spend_status\":{\"Spent\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c\"}},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:0\":{\"blockheight\":0,\"tweak\":\"6e5c1d4690b9ff4fa26e5185ada25af5c2987650e629a518db8a43fdaad7a26c\",\"amount\":349,\"script\":\"512088ac90e180c87b7fa6aa33db4c72a8620cd08fda3ba1c71430b904eb068c587f\",\"label\":null,\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:0\":{\"blockheight\":0,\"tweak\":\"2e3636950174a42842da254d7936f589e8c9ded6e17c90656bb759467fff9ea0\",\"amount\":1200,\"script\":\"51209c3a71193f7fc52482fd516a1a36f4df55f646a15f15ecc4f55497262b06e7c8\",\"label\":null,\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:1\":{\"blockheight\":0,\"tweak\":\"7c84a3074c18c23a65eceaca49a327989ef6e7240e0e080421afb80f06906d73\",\"amount\":306,\"script\":\"5120eb78084d7a2ccbdb7eb7e9bba7cf875c4e54a5aba0beac57c5d3e41133bd8fdd\",\"label\":null,\"spend_status\":{\"Spent\":\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8\"}},\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:2\":{\"blockheight\":0,\"tweak\":\"86a78efb4f522790d46096df9261435e3f7cab5f5129efe4ff77562fb4834a0c\",\"amount\":3935451,\"script\":\"5120c314279d078acaa41b5d897f98b81f09546001b6422e90e0b3f902ac7442a6b7\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2\"}},\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:2\":{\"blockheight\":0,\"tweak\":\"6493d306b4650a36321d05270e2067b58839637da52f0fc29cd2f05817fff0d6\",\"amount\":3933896,\"script\":\"51206666fc11233881b1c831e032e9b06cf05a90ed93fef4f35c78583b9eec845ab5\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"}}},\"tx_history\":[]},\"current_session_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:0\",\"current_session_key\":[193,95,100,240,34,120,208,149,63,78,80,240,135,212,31,167,234,42,121,233,178,209,49,231,189,60,70,89,71,185,1,187],\"current_session_revokation_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:1\",\"paired_device\":{\"address\":\"sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u\",\"outgoing_pairing_transaction\":[184,15,3,43,210,105,33,82,25,230,171,60,50,65,148,233,155,169,157,72,81,194,100,136,113,83,51,13,148,124,233,120],\"revokation_index\":1,\"incoming_pairing_transaction\":[45,121,119,37,134,133,105,239,129,247,25,31,24,117,14,236,248,39,135,158,142,195,2,15,53,45,106,175,177,156,160,35],\"current_remote_key\":[16,187,49,126,30,231,11,43,228,86,96,159,92,122,234,168,16,107,97,83,38,34,15,14,115,173,94,120,208,50,47,34],\"current_session_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:0\",\"current_session_revokation_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:1\"}}"; -const BOB_FINAL_DEVICE: &str = "{\"sp_wallet\":{\"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\":{\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:1\":{\"blockheight\":0,\"tweak\":\"f0ad83734cdc7d73575e5a32651390cf30b92cc7e44cf94ec37da46900ecaf71\",\"amount\":654,\"script\":\"5120230cc1e85829be238e666f469653cbc2f1c0e3675a9bf33e1d1f91115f5dd306\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"78e97c940d3353718864c251489da99be99441323cabe619522169d22b030fb8:0\":{\"blockheight\":0,\"tweak\":\"898284108952bd8b0e74030e00b81c1b387ee151a810ca3b40143da227707df2\",\"amount\":1200,\"script\":\"5120c0ff94a1318f0061960a713c927ff175a0d71a791497e94e70601c3baae739b8\",\"label\":null,\"spend_status\":{\"Spent\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f\"}},\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:2\":{\"blockheight\":0,\"tweak\":\"b13107995241e7daaf13e89ff62a007dd592173c02284a46c9d40778e9fd8165\",\"amount\":99894746,\"script\":\"5120fc7226dc54ed54144d7f6081726fa1d6b5d0fc6aca09a0cc689cc9552e81db14\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":{\"Spent\":\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f\"}},\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:1\":{\"blockheight\":0,\"tweak\":\"a68aed757eb3aca6bcbd0deb749eb26767963401e07d568b19f05f9945fce9b3\",\"amount\":248,\"script\":\"5120d3a39aed25fccd3338aa8c9d40c015febfb4361cffb6cfdcede3032317dc26ff\",\"label\":null,\"spend_status\":\"Unspent\"},\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d:2\":{\"blockheight\":0,\"tweak\":\"e02bb8059b6c787d22715be95de87ea75c120e0f4e0507233bb3e1547b50d98b\",\"amount\":99895292,\"script\":\"51203207d027bb48df9a7c5798091b8bd4912c436c19ea41f04b48ea5dad31118183\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":{\"Spent\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f\"}},\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:1\":{\"blockheight\":0,\"tweak\":\"73cd0a0ebe8e61c3f5ec6b414107934238c4642924272823fb630ef828c14a97\",\"amount\":248,\"script\":\"51202c1f1be3cff73416dc34de0d5f7760095a51f6b4a7bfa118441221718394beea\",\"label\":null,\"spend_status\":\"Unspent\"},\"93722ea2fb9b74954210b4cdd1360e280b7ff1bc156d6b75f847e62411c588fb:0\":{\"blockheight\":0,\"tweak\":\"da5e3aa2378e3a257f99eb1e0ae4c672916f6a2f32a8ed9a8e146f2074da981b\",\"amount\":443,\"script\":\"51209eb9e950127b6a7d81668f25b4d5b164b42dafe59ce40b80e6c489ec983540d7\",\"label\":null,\"spend_status\":{\"Spent\":\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d\"}},\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":{\"Spent\":\"23a09cb1af6a2d350f02c38e9e8727f8ec0e75181f19f781ef6985862577792d\"}},\"494aa6088c4ff368c04ee9dcacad8ab20d91587bacd48e030ab510b012131fe2:0\":{\"blockheight\":0,\"tweak\":\"21fc32bded971dd15387671436232f17d932e18758f69b476219e2d6e688767f\",\"amount\":1200,\"script\":\"5120230d06322d10a5838634df3c55eef9959a1c0c144108e0416b9687eaf22e6baa\",\"label\":null,\"spend_status\":{\"Spent\":\"650088ebc28e4d5d7b4ee2e0337b4907a02537f5be6c28c63da88a6b67b9af1d\"}},\"650088ebc28e4d5d7b4ee2e0337b4907a02537f5be6c28c63da88a6b67b9af1d:1\":{\"blockheight\":0,\"tweak\":\"281ea54f74e5f6582a7462bdd9397b9a786f97455e858e7901a6a4fa95f39192\",\"amount\":654,\"script\":\"5120aa4395e2be9cbce580e94f4308d64422da251b7df12fb8c571675d40cc50cf00\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:0\":{\"blockheight\":0,\"tweak\":\"ac6c259ddf88509b3663a412f4d40403cafb3a89c8a6922770384ba1806ecda0\",\"amount\":1200,\"script\":\"512010bb317e1ee70b2be456609f5c7aeaa8106b615326220f0e73ad5e78d0322f22\",\"label\":null,\"spend_status\":\"Unspent\"},\"f07c116303c88e7b22169f0872c67e3196ad8312170313643a8f47482c6bd308:0\":{\"blockheight\":0,\"tweak\":\"ef47f5ff1081b30725e1954a1f0df9b3785378be5ed4aa9542c77cad55a4265f\",\"amount\":385,\"script\":\"51205500d79439795bf164bebd259f1d4a00d172f9e68a4dd42a64830e8c99d2988f\",\"label\":null,\"spend_status\":\"Unspent\"}}},\"tx_history\":[]},\"current_session_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:0\",\"current_session_key\":[92,106,141,254,85,117,14,178,239,50,33,247,83,151,23,25,144,142,183,115,190,26,14,113,238,72,13,18,29,254,37,87],\"current_session_revokation_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:1\",\"paired_device\":{\"address\":\"sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q\",\"outgoing_pairing_transaction\":[45,121,119,37,134,133,105,239,129,247,25,31,24,117,14,236,248,39,135,158,142,195,2,15,53,45,106,175,177,156,160,35],\"revokation_index\":1,\"incoming_pairing_transaction\":[184,15,3,43,210,105,33,82,25,230,171,60,50,65,148,233,155,169,157,72,81,194,100,136,113,83,51,13,148,124,233,120],\"current_remote_key\":[156,58,113,25,63,127,197,36,130,253,81,106,26,54,244,223,85,246,70,161,95,21,236,196,245,84,151,38,43,6,231,200],\"current_session_outpoint\":\"4b7a78bb8a715fa4875543025182b3f0bb1bc5ca44b852cab57c2929231ddf3f:0\",\"current_session_revokation_outpoint\":\"5e18963dcdd5e94b4b5dfaa147a6e62889a367d9fc1a016973f84c07a192982c:1\"}}"; - -fn helper_switch_device(wallet: String) { - reset_device().unwrap(); - restore_device_from_sp_wallet(wallet.clone()).unwrap(); -} - -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, outpoints: HashMap) -> String { - let tx = deserialize::(&Vec::from_hex(transaction).unwrap()).unwrap(); - let mut outpoint_data = vec![]; - let mut witnesses = vec![]; - let mut spks = vec![]; - for prevout in tx.input { - outpoint_data.push(( - prevout.previous_output.txid.to_string(), - prevout.previous_output.vout, - )); - witnesses.push(prevout.witness); - if let Some(output) = outpoints.get(&prevout.previous_output) { - spks.push(ScriptBuf::from_hex(&output.script).unwrap()); - } - } - let mut input_pubkeys = vec![]; - for (spk, witness) in spks.iter().zip(witnesses) { - let input_pubkey = - get_pubkey_from_input(&vec![], &witness.to_vec(), spk.as_bytes()).unwrap(); - input_pubkeys.push(input_pubkey.unwrap()); - } - let ref_pubkeys: Vec<&PublicKey> = input_pubkeys.iter().collect(); - let tweak_data = calculate_tweak_data(&ref_pubkeys, &outpoint_data).unwrap(); - tweak_data.to_string() -} - -fn helper_parse_transaction(transaction: &str, tweak_data: &str) -> CachedMessage { - let new_tx_msg = serde_json::to_string(&NewTxMessage::new( - transaction.to_owned(), - Some(tweak_data.to_owned()), - )) - .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), - }; -} - -#[wasm_bindgen_test] -fn test_pairing() { - setup(); - debug!("==============================================\nStarting test_pairing\n=============================================="); - - // ========================= Alice - helper_switch_device(ALICE_LOGIN_WALLET.to_owned()); - - debug!("Alice sends a pairing transaction to Bob"); - let alice_pairing_tx = create_pairing_transaction(helper_get_bob_address(), 1).unwrap(); - - debug!("Alice pairing tx: {}", alice_pairing_tx.txid); - - // This is sent on 4nk network along with the transaction - let alice_pairing_cipher = AnkNetworkMsg::new( - sdk_common::network::AnkFlag::Cipher, - &alice_pairing_tx.new_network_msg.ciphertext.unwrap(), - ); - - 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(&alice_pairing_tx.transaction, alice_outputs); - - // Alice parse her own transaction - let alice_msg_id = - helper_parse_transaction(&alice_pairing_tx.transaction, &alice_pairing_tweak_data).id; - - let alice_wallet = dump_wallet().unwrap(); - let alice_cache = dump_message_cache().unwrap(); - - // ======================= Bob - reset_device().unwrap(); - helper_switch_device(BOB_LOGIN_WALLET.to_owned()); - - // Bob receives Alice pairing transaction - // if he agrees, he must send another pairing transaction to Alice - // he can also spend the output that notified him that will become Alice first session key - debug!("Bob parses Alice pairing transaction"); - helper_parse_transaction(&alice_pairing_tx.transaction, &alice_pairing_tweak_data); - let alice_pairing_res = helper_parse_ank_msg(alice_pairing_cipher.to_string()); - - assert!(alice_pairing_res.status == CachedMessageStatus::Pairing); - - // Bob takes the txid of the incoming transaction from Alice, he will need it for pairing - let incoming_txid = alice_pairing_res.commited_in.unwrap().txid; - - // At this point, user must validate the pairing proposal received from Alice - - debug!("Bob sends a pairing transaction back"); - let bob_pairing_tx = create_pairing_transaction(alice_pairing_res.sender.unwrap(), 1).unwrap(); - - let bob_pairing_cipher = AnkNetworkMsg::new( - sdk_common::network::AnkFlag::Cipher, - &bob_pairing_tx.new_network_msg.ciphertext.unwrap(), - ); - - let get_outputs_result = get_outputs().unwrap(); - - let bob_outputs: HashMap = get_outputs_result.into_serde().unwrap(); - - let bob_pairing_tweak_data = helper_get_tweak_data(&bob_pairing_tx.transaction, bob_outputs); - - helper_parse_transaction(&bob_pairing_tx.transaction, &bob_pairing_tweak_data); - - debug!("Bob pairs device with Alice"); - pair_device(bob_pairing_tx.new_network_msg.id, incoming_txid.to_string()).unwrap(); - - // ======================== Alice - reset_device().unwrap(); - helper_switch_device(alice_wallet); - set_message_cache(alice_cache).unwrap(); - - // parse Bob's pairing transaction - helper_parse_transaction(&bob_pairing_tx.transaction, &bob_pairing_tweak_data); - let bob_pairing_msg = helper_parse_ank_msg(bob_pairing_cipher.to_string()); - - assert!(bob_pairing_msg.status == CachedMessageStatus::Pairing); - - debug!("Alice pairs device"); - pair_device(alice_msg_id, bob_pairing_tx.txid).unwrap(); -} - -#[wasm_bindgen_test] -fn test_first_login() { - reset_device().unwrap(); - setup(); - debug!("==============================================\nStarting test_first_login\n=============================================="); - - restore_device(BOB_PAIRED_DEVICE.to_owned()).unwrap(); - set_message_cache( - serde_json::from_str::>(BOB_PAIRING_CACHE) - .unwrap() - .into_iter() - .map(|v| v.to_string()) - .collect(), - ) - .unwrap(); - - // Bob can now spend the notification output, that will become Alice's first session key - debug!("Bob first login"); - let bob_first_login_tx = create_login_transaction(1).unwrap(); - let bob_login_cipher = AnkNetworkMsg::new( - sdk_common::network::AnkFlag::Cipher, - &bob_first_login_tx.new_network_msg.ciphertext.unwrap(), - ); - - let get_outputs_result = get_outputs().unwrap(); - - let bob_outputs: HashMap = get_outputs_result.into_serde().unwrap(); - - let bob_login_tweak_data = helper_get_tweak_data(&bob_first_login_tx.transaction, bob_outputs); - - debug!("Bob parses his own login transaction"); - helper_parse_transaction(&bob_first_login_tx.transaction, &bob_login_tweak_data); - - let bob_device = dump_device().unwrap(); - let bob_cache = dump_message_cache().unwrap(); - - // ======================== Alice - reset_device().unwrap(); - restore_device(ALICE_PAIRED_DEVICE.to_owned()).unwrap(); - set_message_cache( - serde_json::from_str::>(ALICE_PAIRING_CACHE) - .unwrap() - .into_iter() - .map(|v| v.to_string()) - .collect(), - ) - .unwrap(); - - debug!("Alice finds out the login demand from Bob"); - helper_parse_transaction(&bob_first_login_tx.transaction, &bob_login_tweak_data); - let bob_login_msg = helper_parse_ank_msg(bob_login_cipher.to_string()); - - // At this point Alice can fire up the revokation output if the login demand is illegitimate - // OR she must answer with a login transaction to Bob - debug!("Alice first login"); - let alice_first_login_tx = create_login_transaction(1).unwrap(); - let alice_login_cipher = AnkNetworkMsg::new( - sdk_common::network::AnkFlag::Cipher, - &alice_first_login_tx.new_network_msg.ciphertext.unwrap(), - ); - - let get_outputs_result = get_outputs().unwrap(); - - let alice_outputs: HashMap = get_outputs_result.into_serde().unwrap(); - - let alice_login_tweak_data = - helper_get_tweak_data(&alice_first_login_tx.transaction, alice_outputs); - - helper_parse_transaction(&alice_first_login_tx.transaction, &alice_login_tweak_data); - - login(bob_login_msg.id, alice_first_login_tx.transaction.clone()).unwrap(); - - // ======================= Bob - reset_device().unwrap(); - restore_device(bob_device).unwrap(); - set_message_cache(bob_cache).unwrap(); - - helper_parse_transaction(&alice_first_login_tx.transaction, &alice_login_tweak_data); - let alice_login_msg = helper_parse_ank_msg(alice_login_cipher.to_string()); - - assert!(alice_login_msg.status == CachedMessageStatus::Login); - - login(alice_login_msg.id, bob_first_login_tx.transaction).unwrap(); -} - -#[wasm_bindgen_test] -fn test_login() { - reset_device().unwrap(); - setup(); - debug!("==============================================\nStarting test_login\n=============================================="); - - // ======================= Alice - restore_device(ALICE_LOGGED_DEVICE.to_owned()).unwrap(); - - debug!("Alice sends a login transaction to Bob, which creates a new key for him"); - let alice_login_tx = create_login_transaction(1).unwrap(); - let alice_login_cipher = AnkNetworkMsg::new( - sdk_common::network::AnkFlag::Cipher, - &alice_login_tx.new_network_msg.ciphertext.unwrap(), - ); - - let get_outputs_result = get_outputs().unwrap(); - - let alice_outputs: HashMap = get_outputs_result.into_serde().unwrap(); - - let alice_login_tweak_data = helper_get_tweak_data( - &alice_login_tx.transaction, - alice_outputs - ); - - debug!("Parsing Alice login transaction"); - helper_parse_transaction(&alice_login_tx.transaction, &alice_login_tweak_data); - - let alice_device = dump_device().unwrap(); - let alice_cache = dump_message_cache().unwrap(); - - // ============================== Bob - reset_device().unwrap(); - restore_device(BOB_LOGGED_DEVICE.to_owned()).unwrap(); - - debug!("Bob finds out the login demand from Alice"); - helper_parse_transaction(&alice_login_tx.transaction, &alice_login_tweak_data); - let alice_login_msg = helper_parse_ank_msg(alice_login_cipher.to_string()); - - // Bob must confirm that he agrees to log in - - // Boutons "login" ou "Refuser" - // At this point Bob can fire up the revokation output if the login demand is illegitimate - // revoke_paired_device(1).unwrap(); - // OR it must answer with a login transaction to Alice - let bob_login_tx = create_login_transaction(1).unwrap(); - let bob_login_cipher = AnkNetworkMsg::new( - sdk_common::network::AnkFlag::Cipher, - &bob_login_tx.new_network_msg.ciphertext.unwrap(), - ); - - let get_outputs_result = get_outputs().unwrap(); - - let bob_outputs: HashMap = get_outputs_result.into_serde().unwrap(); - - let bob_login_tweak_data = helper_get_tweak_data(&bob_login_tx.transaction, bob_outputs); - - debug!("Bob parses his own login transaction"); - helper_parse_transaction(&bob_login_tx.transaction, &bob_login_tweak_data); - - login(alice_login_msg.id, alice_login_tx.transaction.clone()).unwrap(); - - // =================== Alice - - reset_device().unwrap(); - restore_device(alice_device).unwrap(); - set_message_cache(alice_cache).unwrap(); - - helper_parse_transaction(&bob_login_tx.transaction, &bob_login_tweak_data); - let bob_login_msg = helper_parse_ank_msg(bob_login_cipher.to_string()); - - assert!(bob_login_msg.status == CachedMessageStatus::Login); - - login(bob_login_msg.id, alice_login_tx.transaction).unwrap(); -} - -#[wasm_bindgen_test] -fn test_alice_notifies_bob() { - reset_device().unwrap(); - setup(); - debug!("==============================================\nStarting test_alice_notifies_bob\n=============================================="); - - // ============================ Alice - restore_device(ALICE_LOGGED_DEVICE.to_owned()).unwrap(); - - debug!("Alice notified Bob about a message she sent to it"); - - let cipher_msg = CipherMessage::new(helper_get_alice_address(), "TEST".to_owned()); - let notification_tx = - create_notification_transaction(helper_get_bob_address(), cipher_msg, 1).unwrap(); - - let get_outputs_result = get_outputs().unwrap(); - - let alice_outputs: HashMap = get_outputs_result.into_serde().unwrap(); - let notification_tweak_data = helper_get_tweak_data( - ¬ification_tx.transaction, - alice_outputs - ); - - 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, - ¬ification_tweak_data, - ); - - reset_device().unwrap(); - restore_device(BOB_LOGGED_DEVICE.to_owned()).unwrap(); - - debug!( - "Bob parses notification transaction {}", - notification_tx.txid - ); - // bob parses the transaction and the message - helper_parse_transaction(¬ification_tx.transaction, ¬ification_tweak_data); - helper_parse_ank_msg(ank_msg.to_string()); -} - -#[wasm_bindgen_test] -fn test_bob_challenges_alice() { - reset_device().unwrap(); - debug!("==============================================\nStarting test_bob_challenges_alice\n=============================================="); - - // =============================== Bob - setup(); - restore_device(BOB_CHALLENGE_DEVICE.to_owned()).unwrap(); - set_message_cache( - serde_json::from_str::>(BOB_CHALLENGE_CACHE) - .unwrap() - .into_iter() - .map(|v| v.to_string()) - .collect(), - ) - .unwrap(); - - let notification_msg: CachedMessage = serde_json::from_str(dump_message_cache().unwrap().get(0).unwrap()).unwrap(); - - let get_outputs_result = get_outputs().unwrap(); - - let bob_outputs: HashMap = get_outputs_result.into_serde().unwrap(); - - 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, - bob_outputs - ); - - debug!("Bob parsing its own confirmation tx"); - helper_parse_transaction( - &confirmation_tx.transaction, - &confirmation_tweak_data, - ); - - // ========================== Alice - reset_device().unwrap(); - restore_device(ALICE_CHALLENGE_DEVICE.to_owned()).unwrap(); - set_message_cache( - serde_json::from_str::>(ALICE_CHALLENGE_CACHE) - .unwrap() - .into_iter() - .map(|v| v.to_string()) - .collect(), - ) - .unwrap(); - - debug!("Alice parsing confirmation tx"); - helper_parse_transaction(&confirmation_tx.transaction, &confirmation_tweak_data); -} - -#[wasm_bindgen_test] -fn test_alice_answers_bob() { - reset_device().unwrap(); - setup(); - debug!("==============================================\nStarting test_alice_answers_bob\n=============================================="); - restore_device(ALICE_ANSWER_DEVICE.to_owned()).unwrap(); - set_message_cache( - serde_json::from_str::>(ALICE_ANSWER_CACHE) - .unwrap() - .into_iter() - .map(|v| v.to_string()) - .collect(), - ) - .unwrap(); - - let challenge_msg: CachedMessage = serde_json::from_str(dump_message_cache().unwrap().get(0).unwrap()).unwrap(); - - debug!("Alice answers bob's challenge"); - let answer_tx = answer_confirmation_transaction(challenge_msg.id, 1).unwrap(); - - let get_outputs_result = get_outputs().unwrap(); - - let alice_outputs: HashMap = get_outputs_result.into_serde().unwrap(); - - let answer_tweak_data = helper_get_tweak_data( - &answer_tx.transaction, - alice_outputs, - ); - - debug!("Alice parses her own transaction"); - helper_parse_transaction(&answer_tx.transaction, &answer_tweak_data); - - reset_device().unwrap(); - restore_device(BOB_ANSWER_DEVICE.to_owned()).unwrap(); - - set_message_cache( - serde_json::from_str::>(BOB_ANSWER_CACHE) - .unwrap() - .into_iter() - .map(|v| v.to_string()) - .collect(), - ) - .unwrap(); - - debug!("Bob parses answer transaction {}", answer_tx.txid); - let answer_msg = helper_parse_transaction(&answer_tx.transaction, &answer_tweak_data); -} - -#[wasm_bindgen_test] -fn test_alice_sends_message_through_trusted_channel() { - reset_device().unwrap(); - setup(); - debug!("==============================================\nStarting test_alice_sends_message_through_trusted_channel\n=============================================="); - - restore_device(ALICE_FINAL_DEVICE.to_owned()).unwrap(); - - set_message_cache( - serde_json::from_str::>(ALICE_FINAL_CACHE) - .unwrap() - .into_iter() - .map(|v| v.to_string()) - .collect(), - ) - .unwrap(); - - let answered_msg: CachedMessage = serde_json::from_str(dump_message_cache().unwrap().get(0).unwrap()).unwrap(); - - // Bob sends a message to Alice - debug!("Bob Sends a message to Alice"); - let secret_msg = "Hello Alice"; - let cipher = - encrypt_with_key(secret_msg.to_owned(), answered_msg.shared_secret.unwrap()).unwrap(); - let bob_msg = AnkNetworkMsg::new(sdk_common::network::AnkFlag::Cipher, &cipher); - - // Alice can find the message - let mut result = helper_parse_ank_msg(bob_msg.to_string()); - assert!(result.plaintext.pop() == Some(secret_msg.to_owned())); -} diff --git a/tests/pairing.rs b/tests/pairing.rs new file mode 100644 index 0000000..a4917de --- /dev/null +++ b/tests/pairing.rs @@ -0,0 +1,283 @@ +use std::collections::HashMap; +use std::str::FromStr; + +use sdk_client::api::{ + create_device_from_sp_wallet, create_update_transaction, dump_device, dump_process_cache, + get_address, get_outputs, get_update_proposals, pair_device, parse_cipher, reset_device, + response_prd, restore_device, set_process_cache, setup, ApiReturn, +}; +use sdk_common::log::debug; +use sdk_common::pcd::{Member, Pcd, RoleDefinition}; +use sdk_common::sp_client::bitcoin::OutPoint; +use sdk_common::sp_client::spclient::OwnedOutput; +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_pairing() { + setup(); + debug!("==============================================\nStarting test_pairing\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(); + + // Alice creates the new member with Bob address + let new_member = Member::new(vec![ + alice_address.as_str().try_into().unwrap(), + bob_address.as_str().try_into().unwrap(), + ]) + .unwrap(); + + let pairing_init_state = json!({ + "html": "", + "style": "", + "script": "", + "description": "AliceBob", + "roles": { + "owner": { + "members": + [ + new_member + ], + "validation_rules": + [ + { + "quorum": 1.0, + "fields": [ + "roles", + "pairing_tx" + ], + "min_sig_member": 1.0 + } + ] + } + }, + "pairing_tx": OutPoint::null(), + }); + + debug!("Alice pairs her device"); + // we can update our local device now, first with an empty txid + pair_device(OutPoint::null().to_string(), vec![helper_get_bob_address()]).unwrap(); + + debug!("Alice sends a transaction commiting to an update prd to Bob"); + let alice_pairing_return = + create_update_transaction(None, pairing_init_state.to_string(), 1).unwrap(); + + let (root_outpoint, alice_init_process) = alice_pairing_return.updated_process.unwrap(); + let alice_pcd_commitment = Value::from_str( + &alice_init_process + .get_impending_requests() + .get(0) + .unwrap() + .payload, + ) + .unwrap() + .tagged_hash(); + + let pairing_tx = alice_pairing_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(&pairing_tx, alice_outputs); + + // End of the test only part + + // Alice parses her own transaction + helper_parse_transaction(&pairing_tx, &alice_pairing_tweak_data); + + // Notify user that we're waiting for confirmation from the other device + // We can update the local device with the actual pairing outpoint + pair_device( + OutPoint::new(pairing_tx.txid(), 0).to_string(), + vec![helper_get_bob_address()], + ) + .unwrap(); + + // TODO unpair device + + // We can produce the prd response now even if we can't use it yet + let alice_prd_response = response_prd( + root_outpoint, + alice_init_process + .get_impending_requests() + .get(0) + .unwrap() + .to_string(), + true, + ) + .unwrap() + .ciphers_to_send + .get(0) + .unwrap() + .clone(); + + // We can also create the first login transaction and sign it + + // this is only for testing, as we're playing both parts + let alice_device = dump_device().unwrap(); + let alice_processes = dump_process_cache().unwrap(); + + // ======================= Bob + reset_device().unwrap(); + create_device_from_sp_wallet(BOB_LOGIN_WALLET.to_owned()).unwrap(); + + // Bob receives Alice pairing transaction + debug!("Bob parses Alice pairing transaction"); + helper_parse_transaction(&pairing_tx, &alice_pairing_tweak_data); + + debug!("Bob receives the prd"); + let mut bob_retrieved_prd: ApiReturn = ApiReturn::default(); + for cipher in alice_pairing_return.ciphers_to_send.iter() { + // debug!("Parsing cipher: {:#?}", cipher); + match parse_cipher(cipher.clone()) { + Ok(res) => bob_retrieved_prd = res, + Err(e) => { + debug!("Error parsing cipher: {:#?}", e); + continue; + } + } + } + + assert!(bob_retrieved_prd.ciphers_to_send.len() == 1); + assert!(bob_retrieved_prd.updated_process.is_some()); + + debug!("Bob retrieved prd: {:#?}", bob_retrieved_prd); + + let (root_commitment, relevant_process) = bob_retrieved_prd.updated_process.unwrap(); + let pcd_commitment = relevant_process + .get_impending_requests() + .get(0) + .unwrap() + .payload + .clone(); + let prd_confirm_cipher = bob_retrieved_prd.ciphers_to_send.iter().next().unwrap(); + + debug!("Bob sends a Confirm Prd to Alice"); + + // this is only for testing, as we're playing both parts + let bob_device = dump_device().unwrap(); + let bob_processes = dump_process_cache().unwrap(); + + // ======================= Alice + reset_device().unwrap(); + restore_device(alice_device).unwrap(); + set_process_cache(alice_processes).unwrap(); + + debug!("Alice receives the Confirm Prd"); + let alice_parsed_prd = parse_cipher(prd_confirm_cipher.clone()).unwrap(); + + debug!("Alice parsed Bob's Confirm Prd: {:#?}", alice_parsed_prd); + + // ======================= Bob + reset_device().unwrap(); + restore_device(bob_device).unwrap(); + set_process_cache(bob_processes).unwrap(); + + debug!("Bob parses Alice's pcd"); + let bob_parsed_pcd_return = parse_cipher(alice_parsed_prd.ciphers_to_send[0].clone()).unwrap(); + + debug!("bob_parsed_pcd: {:#?}", bob_parsed_pcd_return); + + let (root_commitment, prd_update) = bob_parsed_pcd_return.updated_process.unwrap(); + + // At this point, user must validate the pairing proposal received from Alice + // We decrypt the content of the pcd so that we can display to user what matters + let alice_proposal = + get_update_proposals(root_commitment.clone()).unwrap(); + + debug!("Alice proposal: {:#?}", alice_proposal); + + // get the pairing tx from the proposal + let proposal = Value::from_str(&alice_proposal.get(0).unwrap()).unwrap(); + debug!("proposal: {:#?}", proposal); + let pairing_tx = proposal + .get("pairing_tx") + .unwrap() + .as_str() + .unwrap() + .trim_matches('"'); + + let roles = proposal + .get("roles") + .and_then(|v| Value::from_str(v.as_str().unwrap()).ok()) + .unwrap() + .as_object() + .unwrap() + .iter() + .map(|(role_name, role_value)| { + let role_def: RoleDefinition = serde_json::from_value(role_value.clone())?; + Ok((role_name.clone(), role_def)) + }) + .collect::, anyhow::Error>>(); + + let roles = roles.unwrap(); + + // we check that the proposal contains only one member + assert!(roles.len() == 1); + assert!(roles["owner"].members.len() == 1); + + // we get all the addresses of the members of the proposal + let proposal_members = roles + .iter() + .flat_map(|(_, members)| members.members.iter().flat_map(|m| m.get_addresses())) + .collect::>(); + + // we can automatically check that a pairing member contains local device address + the one that sent the proposal + assert!(proposal_members.contains(&alice_address)); + assert!(proposal_members.contains(&bob_address)); + assert!(proposal_members.len() == 2); // no free riders + + // We remove the local address, but maybe that's the responsibility of the Member type + let proposal_members = proposal_members + .into_iter() + .filter(|m| m != &bob_address) + .collect::>(); + + debug!("proposal_members: {:?}", proposal_members); + + // we can now show all the addresses + pairing tx to the user on device to prompt confirmation + + debug!("Bob pairs device with Alice"); + pair_device(pairing_tx.to_owned(), proposal_members.clone()).unwrap(); + + let prd_to_respond = prd_update.get_impending_requests().get(0).unwrap().to_string(); + + // Bob signs the proposal and sends a prd response too + let bob_prd_response = response_prd(root_commitment, prd_to_respond, true) + .unwrap() + .ciphers_to_send + .get(0) + .unwrap(); + + // To make the pairing effective, alice and bob must now creates a new transaction where they both control one output + + // login logic: user must have access to both devices to login + // user must still have access to both devices in case an action needs both devices (as defined in the roles) + // process can define that acting on some fields of the state only needs a fraction of the devices to sign + + // Problem: we need both devices to sign the next login transaction. + // Simplest solution: device A creates the transaction with both inputs and outputs, signs its input and sends to device B + // device B notify user that a login is underway, user either accepts (sign the transaction and broadcast), or refuses (actually we should think of some revokation step from here) + + // login(); + + // Once we know this tx id, we can commit to the relay +} diff --git a/tests/utils.rs b/tests/utils.rs new file mode 100644 index 0000000..177d7bb --- /dev/null +++ b/tests/utils.rs @@ -0,0 +1,74 @@ +use std::collections::HashMap; + +use sdk_client::api::{parse_new_tx, ApiReturn}; +use sdk_common::network::NewTxMessage; +use sdk_common::sp_client::bitcoin::consensus::serialize; +use sdk_common::sp_client::bitcoin::hex::DisplayHex; +use sdk_common::sp_client::bitcoin::secp256k1::PublicKey; +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; + +// We're using alice and bob for clarity, but it's important to remember that for pairing and login Alice and Bob are the same person +pub 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\"}}},\"tx_history\":[]}"; +pub const ALICE_LOGIN_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\":2146,\"outputs\":{\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:1\":{\"blockheight\":0,\"tweak\":\"7c84a3074c18c23a65eceaca49a327989ef6e7240e0e080421afb80f06906d73\",\"amount\":306,\"script\":\"5120eb78084d7a2ccbdb7eb7e9bba7cf875c4e54a5aba0beac57c5d3e41133bd8fdd\",\"label\":null,\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:2\":{\"blockheight\":0,\"tweak\":\"e3e21d551e933199f8c977bc0362616bdfb128da7ca9728bd7254977541c39cc\",\"amount\":3936897,\"script\":\"5120df3af55a63bd056c5d6d09f47a3a19c382d938c08db8bc41a59c5235970209ad\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:0\":{\"blockheight\":0,\"tweak\":\"6e5c1d4690b9ff4fa26e5185ada25af5c2987650e629a518db8a43fdaad7a26c\",\"amount\":349,\"script\":\"512088ac90e180c87b7fa6aa33db4c72a8620cd08fda3ba1c71430b904eb068c587f\",\"label\":null,\"spend_status\":\"Unspent\"},\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292\"}}}},\"tx_history\":[]}"; +pub 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\"}}},\"tx_history\":[]}"; +pub const BOB_LOGIN_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\":{\"93722ea2fb9b74954210b4cdd1360e280b7ff1bc156d6b75f847e62411c588fb:0\":{\"blockheight\":0,\"tweak\":\"da5e3aa2378e3a257f99eb1e0ae4c672916f6a2f32a8ed9a8e146f2074da981b\",\"amount\":443,\"script\":\"51209eb9e950127b6a7d81668f25b4d5b164b42dafe59ce40b80e6c489ec983540d7\",\"label\":null,\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":{\"Spent\":\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f\"}},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:1\":{\"blockheight\":0,\"tweak\":\"f0ad83734cdc7d73575e5a32651390cf30b92cc7e44cf94ec37da46900ecaf71\",\"amount\":654,\"script\":\"5120230cc1e85829be238e666f469653cbc2f1c0e3675a9bf33e1d1f91115f5dd306\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":\"Unspent\"}}},\"tx_history\":[]}"; + +pub const RELAY_ADDRESS: &str = "sprt1qqfmqt0ngq99y8t4ke6uhtm2a2vc2zxvhj7hjrqu599kn30d4cs9rwqn6n079mdr4dfqg72yrtvuxf43yswscw86nvvl09mc5ljx65vfh75fkza35"; +pub const DEFAULT_NYM: &str = "AliceBob"; + +pub fn helper_get_alice_address() -> String { + let wallet: SpWallet = serde_json::from_str(ALICE_START_WALLET).unwrap(); + wallet.get_client().get_receiving_address() +} + +pub fn helper_get_bob_address() -> String { + let wallet: SpWallet = serde_json::from_str(BOB_START_WALLET).unwrap(); + wallet.get_client().get_receiving_address() +} + +pub fn helper_get_tweak_data( + tx: &Transaction, + outpoints: HashMap, +) -> String { + let mut outpoint_data = vec![]; + let mut witnesses = vec![]; + let mut spks = vec![]; + for prevout in tx.input.iter() { + outpoint_data.push(( + prevout.previous_output.txid.to_string(), + prevout.previous_output.vout, + )); + witnesses.push(prevout.witness.clone()); + if let Some(output) = outpoints.get(&prevout.previous_output) { + spks.push(ScriptBuf::from_hex(&output.script).unwrap()); + } + } + let mut input_pubkeys = vec![]; + for (spk, witness) in spks.iter().zip(witnesses) { + let input_pubkey = + get_pubkey_from_input(&vec![], &witness.to_vec(), spk.as_bytes()).unwrap(); + input_pubkeys.push(input_pubkey.unwrap()); + } + let ref_pubkeys: Vec<&PublicKey> = input_pubkeys.iter().collect(); + let tweak_data = calculate_tweak_data(&ref_pubkeys, &outpoint_data).unwrap(); + tweak_data.to_string() +} + +pub fn helper_parse_transaction(transaction: &Transaction, tweak_data: &str) -> ApiReturn { + let new_tx_msg = serde_json::to_string(&NewTxMessage::new( + serialize(transaction).to_lower_hex_string(), + Some(tweak_data.to_owned()), + )) + .unwrap(); + // debug!("new_tx_msg: {:?}", new_tx_msg); + let result = parse_new_tx(new_tx_msg, 0, 1); + match result { + Ok(m) => m, + Err(e) => panic!("Unexpected error: {}", e.message), + } +}