working login
This commit is contained in:
parent
30759027b0
commit
d82e145f50
575
src/api.rs
575
src/api.rs
@ -21,12 +21,18 @@ use sdk_common::sp_client::bitcoin::hashes::{sha256, Hash};
|
||||
use sdk_common::sp_client::bitcoin::hex::{
|
||||
parse, DisplayHex, FromHex, HexToArrayError, HexToBytesError,
|
||||
};
|
||||
use sdk_common::sp_client::bitcoin::key::Secp256k1;
|
||||
use sdk_common::sp_client::bitcoin::key::{Parity, Secp256k1};
|
||||
use sdk_common::sp_client::bitcoin::network::ParseNetworkError;
|
||||
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, SecretKey};
|
||||
use sdk_common::sp_client::bitcoin::secp256k1::{PublicKey, Scalar, SecretKey};
|
||||
use sdk_common::sp_client::bitcoin::transaction::ParseOutPointError;
|
||||
use sdk_common::sp_client::bitcoin::{Amount, Network, OutPoint, Psbt, Transaction, Txid};
|
||||
use sdk_common::sp_client::bitcoin::{
|
||||
Amount, Network, OutPoint, Psbt, Transaction, Txid, XOnlyPublicKey,
|
||||
};
|
||||
use sdk_common::sp_client::constants::{
|
||||
DUST_THRESHOLD, PSBT_SP_ADDRESS_KEY, PSBT_SP_PREFIX, PSBT_SP_SUBTYPE,
|
||||
};
|
||||
use sdk_common::sp_client::silentpayments::utils as sp_utils;
|
||||
use sdk_common::sp_client::silentpayments::{
|
||||
utils::{Network as SpNetwork, SilentPaymentAddress},
|
||||
@ -45,15 +51,13 @@ use sdk_common::network::{
|
||||
};
|
||||
use sdk_common::silentpayments::{create_transaction, map_outputs_to_sp_address};
|
||||
|
||||
use crate::wallet::generate_sp_wallet;
|
||||
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, lock_spending_client, Device, RevokeOutput, LOCAL_DEVICE, SPENDING_CLIENT,
|
||||
};
|
||||
use crate::user::{lock_local_device, set_new_device, Device, LOCAL_DEVICE};
|
||||
use crate::{images, lock_messages, CACHEDMESSAGES};
|
||||
|
||||
use crate::process::Process;
|
||||
@ -179,116 +183,98 @@ pub fn get_address() -> ApiResult<String> {
|
||||
let local_device = lock_local_device()?;
|
||||
|
||||
Ok(local_device
|
||||
.get_watch_only()
|
||||
.get_wallet()
|
||||
.get_client()
|
||||
.get_receiving_address())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn restore_device_from_sp_wallet(sp_wallet: String) -> ApiResult<String> {
|
||||
let wallet: SpWallet = serde_json::from_str(&sp_wallet)?;
|
||||
let sp_wallet: SpWallet = serde_json::from_str(&sp_wallet)?;
|
||||
|
||||
let mut device = Device::new(
|
||||
wallet.get_client().get_scan_key(),
|
||||
wallet.get_client().get_spend_key().into(),
|
||||
wallet.get_client().get_network(),
|
||||
);
|
||||
|
||||
let birthday = wallet.get_outputs().get_birthday();
|
||||
let outputs = wallet.get_outputs().to_outpoints_list();
|
||||
device
|
||||
.get_watch_only_mut()
|
||||
.get_mut_outputs()
|
||||
.set_birthday(birthday);
|
||||
device
|
||||
.get_watch_only_mut()
|
||||
.get_mut_outputs()
|
||||
.extend_from(outputs);
|
||||
|
||||
let json_return = serde_json::to_string(&device)?;
|
||||
|
||||
// Set the LOCAL_DEVICE const with the new value
|
||||
let mut local_device = lock_local_device()?;
|
||||
|
||||
if *local_device.get_watch_only().get_client() == SpClient::default() {
|
||||
*local_device = device;
|
||||
} else {
|
||||
return Err(ApiError {
|
||||
message: "device is already initialized".to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
// Set the LOGGED_WALLET with the new wallet to keep it in memory while we wait for the linking
|
||||
let mut spending_client = lock_spending_client()?;
|
||||
|
||||
if *spending_client == SpClient::default() {
|
||||
*spending_client = wallet.get_client().clone();
|
||||
} else {
|
||||
return Err(ApiError {
|
||||
message: "device is already initialized".to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(json_return)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn create_new_device(birthday: u32, network_str: String) -> ApiResult<String> {
|
||||
let network = Network::from_core_arg(&network_str)?;
|
||||
let wallet = generate_sp_wallet(None, Network::Regtest)?;
|
||||
|
||||
// Let's create the new device
|
||||
let mut device = Device::new(
|
||||
wallet.get_client().get_scan_key(),
|
||||
wallet.get_client().get_spend_key().into(),
|
||||
network,
|
||||
);
|
||||
|
||||
device
|
||||
.get_watch_only_mut()
|
||||
.get_mut_outputs()
|
||||
.set_birthday(birthday);
|
||||
|
||||
let our_address = device.get_watch_only().get_client().get_receiving_address();
|
||||
|
||||
// Set the LOCAL_DEVICE const with the new value
|
||||
let mut local_device = lock_local_device()?;
|
||||
|
||||
if *local_device.get_watch_only().get_client() == SpClient::default() {
|
||||
*local_device = device;
|
||||
} else {
|
||||
return Err(ApiError {
|
||||
message: "device is already initialized".to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
// Set the LOGGED_WALLET with the new wallet to keep it in memory while we wait for the linking
|
||||
let mut spending_client = lock_spending_client()?;
|
||||
|
||||
if *spending_client == SpClient::default() {
|
||||
*spending_client = wallet.get_client().clone();
|
||||
} else {
|
||||
return Err(ApiError {
|
||||
message: "device is already initialized".to_owned(),
|
||||
});
|
||||
}
|
||||
let our_address = set_new_device(sp_wallet)?;
|
||||
|
||||
Ok(our_address)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn pair_device(
|
||||
spend_sk_cipher: Vec<u8>,
|
||||
linked_with: String,
|
||||
revokation_output: String,
|
||||
) -> ApiResult<()> {
|
||||
let mut device = lock_local_device()?;
|
||||
pub fn restore_device(device_str: String) -> ApiResult<()> {
|
||||
let device: Device = serde_json::from_str(&device_str)?;
|
||||
|
||||
device.new_link(
|
||||
spend_sk_cipher,
|
||||
linked_with.try_into()?,
|
||||
OutPoint::from_str(&revokation_output)?,
|
||||
);
|
||||
let mut local_device = lock_local_device()?;
|
||||
|
||||
*local_device = device;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn create_new_device(birthday: u32, network_str: String) -> ApiResult<String> {
|
||||
let sp_wallet = generate_sp_wallet(None, Network::from_core_arg(&network_str)?)?;
|
||||
|
||||
let our_address = set_new_device(sp_wallet)?;
|
||||
|
||||
Ok(our_address)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn pair_device(message_id: u32, incoming_pairing_txid: String) -> 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(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut messages = lock_messages()?;
|
||||
|
||||
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),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -378,26 +364,66 @@ impl outputs_list {
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn login_user(fee_rate: u32) -> ApiResult<()> {
|
||||
create_login_transaction(fee_rate)?;
|
||||
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(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn logout() -> ApiResult<()> {
|
||||
let mut spending_client = lock_spending_client()?;
|
||||
|
||||
*spending_client = SpClient::default();
|
||||
|
||||
Ok(())
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn dump_watch_only_wallet() -> ApiResult<String> {
|
||||
pub fn dump_wallet() -> ApiResult<String> {
|
||||
let device = lock_local_device()?;
|
||||
|
||||
Ok(serde_json::to_string(device.get_watch_only()).unwrap())
|
||||
Ok(serde_json::to_string(device.get_wallet()).unwrap())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@ -441,12 +467,15 @@ pub fn dump_message_cache() -> ApiResult<Vec<String>> {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn dump_device() -> ApiResult<String> {
|
||||
let local_device = lock_local_device()?;
|
||||
|
||||
Ok(serde_json::to_string(&local_device.clone())?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn reset_device() -> ApiResult<()> {
|
||||
let mut spending_client = lock_spending_client()?;
|
||||
|
||||
*spending_client = SpClient::default();
|
||||
|
||||
let mut device = lock_local_device()?;
|
||||
|
||||
*device = Device::default();
|
||||
@ -456,32 +485,6 @@ pub fn reset_device() -> ApiResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn encrypt_remote_key(
|
||||
remote_key: String,
|
||||
aes_key: Option<String>,
|
||||
nonce: Option<String>,
|
||||
) -> ApiResult<String> {
|
||||
let mut device = lock_local_device()?;
|
||||
let encrypted_key: Vec<u8>;
|
||||
if let (Some(key_hex), Some(nonce_hex)) = (aes_key, nonce) {
|
||||
// check that the hex we got for key and nonce is valid
|
||||
let mut key = [0u8; 32];
|
||||
let mut nonce = [0u8; 12];
|
||||
key.copy_from_slice(&Vec::from_hex(&key_hex)?);
|
||||
nonce.copy_from_slice(&Vec::from_hex(&nonce_hex)?);
|
||||
encrypted_key = device.encrypt_for_remote_device(
|
||||
SecretKey::from_str(&remote_key)?,
|
||||
Some(key),
|
||||
Some(nonce),
|
||||
)?;
|
||||
} else {
|
||||
encrypted_key =
|
||||
device.encrypt_for_remote_device(SecretKey::from_str(&remote_key)?, None, None)?;
|
||||
}
|
||||
Ok(encrypted_key.to_lower_hex_string())
|
||||
}
|
||||
|
||||
fn handle_recover_transaction(
|
||||
updated: HashMap<OutPoint, OwnedOutput>,
|
||||
tx: &Transaction,
|
||||
@ -536,10 +539,10 @@ fn handle_recover_transaction(
|
||||
// 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))
|
||||
{
|
||||
} 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();
|
||||
|
||||
@ -558,10 +561,10 @@ fn handle_recover_transaction(
|
||||
);
|
||||
let shared_secret = AnkSharedSecret::new(shared_point);
|
||||
|
||||
debug!(
|
||||
"Shared secret: {}",
|
||||
shared_secret.to_byte_array().to_lower_hex_string()
|
||||
);
|
||||
// debug!(
|
||||
// "Shared secret: {}",
|
||||
// shared_secret.to_byte_array().to_lower_hex_string()
|
||||
// );
|
||||
|
||||
let mut plaintext: Vec<u8> = vec![];
|
||||
if let Some(message) = messages.iter_mut().find(|m| {
|
||||
@ -580,6 +583,10 @@ fn handle_recover_transaction(
|
||||
|
||||
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);
|
||||
|
||||
message.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string());
|
||||
message.commitment = Some(commitment_str);
|
||||
message.plaintext.push(cipher_msg.message);
|
||||
@ -597,6 +604,10 @@ fn handle_recover_transaction(
|
||||
.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.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());
|
||||
@ -614,7 +625,9 @@ fn handle_recover_transaction(
|
||||
return false;
|
||||
}
|
||||
match m.status {
|
||||
CachedMessageStatus::SentWaitingConfirmation => {
|
||||
CachedMessageStatus::SentWaitingConfirmation
|
||||
| CachedMessageStatus::Pairing
|
||||
| CachedMessageStatus::Login => {
|
||||
// commitment we're looking for is simply what's in the message
|
||||
m.commitment
|
||||
.as_ref()
|
||||
@ -670,7 +683,7 @@ fn process_transaction(
|
||||
let tweak_data = PublicKey::from_str(&tweak_data_hex)?;
|
||||
|
||||
let mut device = lock_local_device()?;
|
||||
let wallet = device.get_watch_only_mut();
|
||||
let wallet = device.get_mut_wallet();
|
||||
let updated = wallet.update_wallet_with_transaction(&tx, blockheight, tweak_data)?;
|
||||
|
||||
if updated.len() > 0 {
|
||||
@ -721,18 +734,16 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<CachedMessage>
|
||||
// let's try to decrypt with keys we found in transactions but haven't used yet
|
||||
let mut messages = lock_messages()?;
|
||||
let cipher = Vec::from_hex(&ank_msg.content.trim_matches('\"'))?;
|
||||
if let Some(message) = messages.iter_mut().find(|m| {
|
||||
// debug!("Trying message: {:?}", m);
|
||||
match m.status {
|
||||
CachedMessageStatus::TxWaitingCipher | CachedMessageStatus::Trusted => {
|
||||
m.try_decrypt_cipher(cipher.clone()).is_ok()
|
||||
}
|
||||
_ => return false,
|
||||
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();
|
||||
let cipher_msg: CipherMessage = serde_json::from_slice(&plain)?;
|
||||
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 {
|
||||
@ -740,15 +751,24 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<CachedMessage>
|
||||
message: "Message doesn't match commitment".to_owned(),
|
||||
});
|
||||
}
|
||||
message.plaintext.push(cipher_msg.message);
|
||||
message.sender = Some(cipher_msg.sender);
|
||||
message.ciphertext = None;
|
||||
message.status = CachedMessageStatus::ReceivedMustConfirm;
|
||||
if cipher_msg.message.starts_with("PAIRING") {
|
||||
// we don't follow the classic confirmation pattern
|
||||
// set the status to sth else
|
||||
// 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;
|
||||
} else {
|
||||
message.status = CachedMessageStatus::ReceivedMustConfirm;
|
||||
}
|
||||
message.plaintext.push(cipher_msg.message);
|
||||
} else {
|
||||
// We're receiving a message for some action already engaged
|
||||
// We could check the sender, but do we really care since we alredy trust it?
|
||||
// Let's update the message by pushing what we just found out
|
||||
message.plaintext.push(cipher_msg.message);
|
||||
message.plaintext.push(String::from_utf8(plain)?);
|
||||
}
|
||||
return Ok(message.clone());
|
||||
} else {
|
||||
@ -772,7 +792,7 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<CachedMessage>
|
||||
#[wasm_bindgen]
|
||||
pub fn get_outputs() -> ApiResult<JsValue> {
|
||||
let device = lock_local_device()?;
|
||||
let outputs = device.get_watch_only().get_outputs().clone();
|
||||
let outputs = device.get_wallet().get_outputs().clone();
|
||||
Ok(JsValue::from_serde(&outputs.to_outpoints_list())?)
|
||||
}
|
||||
|
||||
@ -780,7 +800,7 @@ pub fn get_outputs() -> ApiResult<JsValue> {
|
||||
pub fn get_available_amount() -> ApiResult<u64> {
|
||||
let device = lock_local_device()?;
|
||||
|
||||
Ok(device.get_watch_only().get_outputs().get_balance().to_sat())
|
||||
Ok(device.get_wallet().get_outputs().get_balance().to_sat())
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Serialize, Deserialize, Default)]
|
||||
@ -819,24 +839,26 @@ pub fn answer_confirmation_transaction(
|
||||
|
||||
let local_device = lock_local_device()?;
|
||||
|
||||
let current_outputs = local_device.get_watch_only().get_outputs().clone();
|
||||
|
||||
let spending_client = lock_spending_client()?.clone();
|
||||
|
||||
let sp_wallet = SpWallet::new(spending_client, Some(current_outputs), vec![])?;
|
||||
let sp_wallet = local_device.get_wallet();
|
||||
|
||||
let recipient = Recipient {
|
||||
address: sp_address.into(),
|
||||
amount: Amount::from_sat(0), // we'll set amount to what's available in the confirmed_by output we don't want change
|
||||
amount: DUST_THRESHOLD,
|
||||
nb_outputs: 1,
|
||||
};
|
||||
|
||||
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],
|
||||
&sp_wallet,
|
||||
recipient,
|
||||
&freezed_utxos,
|
||||
sp_wallet,
|
||||
vec![recipient],
|
||||
None,
|
||||
Amount::from_sat(fee_rate.into()),
|
||||
message.recipient.clone(),
|
||||
@ -877,24 +899,28 @@ pub fn create_confirmation_transaction(
|
||||
|
||||
let sp_address: SilentPaymentAddress = message.sender.as_ref().unwrap().as_str().try_into()?;
|
||||
|
||||
let current_outputs = lock_local_device()?.get_watch_only().get_outputs().clone();
|
||||
let local_device = lock_local_device()?;
|
||||
|
||||
let spending_client = lock_spending_client()?.clone();
|
||||
|
||||
let sp_wallet = SpWallet::new(spending_client, Some(current_outputs), vec![])?;
|
||||
let sp_wallet = local_device.get_wallet();
|
||||
|
||||
let recipient = Recipient {
|
||||
address: sp_address.into(),
|
||||
amount: Amount::from_sat(0),
|
||||
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,
|
||||
recipient,
|
||||
vec![recipient],
|
||||
None,
|
||||
Amount::from_sat(fee_rate.into()),
|
||||
message.sender.clone(),
|
||||
@ -921,64 +947,138 @@ pub fn create_confirmation_transaction(
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn create_login_transaction(fee_rate: u32) -> ApiResult<createTransactionReturn> {
|
||||
// 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(),
|
||||
);
|
||||
|
||||
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::<String>(&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<createTransactionReturn> {
|
||||
let message: CipherMessage;
|
||||
{
|
||||
let mut spending_client = lock_spending_client()?;
|
||||
let my_address = lock_local_device()?
|
||||
.get_wallet()
|
||||
.get_client()
|
||||
.get_receiving_address();
|
||||
|
||||
let our_address = spending_client.get_receiving_address();
|
||||
let spend_sk: SecretKey = spending_client.get_spend_key().try_into()?;
|
||||
let cipher_message = CipherMessage::new(my_address, "PAIRING".to_owned());
|
||||
|
||||
message = CipherMessage::new(our_address, format!("{}", spend_sk.display_secret()));
|
||||
}
|
||||
let mut res = create_notification_transaction(address, cipher_message, fee_rate)?;
|
||||
|
||||
let mut res = create_notification_transaction(address, message, fee_rate);
|
||||
|
||||
// we slightly alter the message
|
||||
if let Ok(ref mut new_msg) = res {
|
||||
new_msg.new_network_msg.plaintext = vec![]; // we don't want to let that information available
|
||||
new_msg.new_network_msg.status = CachedMessageStatus::Trusted;
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Update the msg in cache
|
||||
let mut messages = lock_messages()?;
|
||||
let mut updated = res.as_ref().unwrap().new_network_msg.clone();
|
||||
let pos = messages.iter().position(|m| m.id == updated.id).unwrap();
|
||||
// clone the cached message and erase the ciphertext
|
||||
updated.ciphertext = None;
|
||||
messages[pos] = updated;
|
||||
|
||||
// we forget our own wallet
|
||||
let mut spending_client = lock_spending_client()?;
|
||||
*spending_client = SpClient::default();
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn create_login_transaction(fee_rate: u32) -> ApiResult<createTransactionReturn> {
|
||||
let address = lock_local_device()?.get_remote_address().ok_or(ApiError {
|
||||
message: "Wallet is not linked".to_owned(),
|
||||
})?;
|
||||
|
||||
let message: CipherMessage;
|
||||
{
|
||||
let device = lock_local_device()?;
|
||||
|
||||
let our_address = device.get_watch_only().get_client().get_receiving_address();
|
||||
|
||||
let encrypted_key = device.get_encrypted_key().to_lower_hex_string();
|
||||
|
||||
message = CipherMessage::new(our_address, encrypted_key);
|
||||
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");
|
||||
}
|
||||
|
||||
create_notification_transaction(address, message, fee_rate)
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@ -991,11 +1091,7 @@ pub fn create_notification_transaction(
|
||||
|
||||
let local_device = lock_local_device()?;
|
||||
|
||||
let current_outputs = local_device.get_watch_only().get_outputs().clone();
|
||||
|
||||
let spending_client = lock_spending_client()?.clone();
|
||||
|
||||
let sp_wallet = SpWallet::new(spending_client, Some(current_outputs), vec![])?;
|
||||
let sp_wallet = local_device.get_wallet();
|
||||
|
||||
let recipient = Recipient {
|
||||
address: sp_address.into(),
|
||||
@ -1005,10 +1101,13 @@ pub fn create_notification_transaction(
|
||||
|
||||
let commitment = create_commitment(serde_json::to_string(&cipher_message)?);
|
||||
|
||||
let freezed_utxos = lock_freezed_utxos()?;
|
||||
|
||||
let signed_psbt = create_transaction(
|
||||
&vec![],
|
||||
&sp_wallet,
|
||||
recipient,
|
||||
&freezed_utxos,
|
||||
sp_wallet,
|
||||
vec![recipient],
|
||||
Some(Vec::from_hex(&commitment)?),
|
||||
Amount::from_sat(fee_rate.into()),
|
||||
None,
|
||||
@ -1025,11 +1124,6 @@ pub fn create_notification_transaction(
|
||||
|
||||
let shared_secret = AnkSharedSecret::new(shared_point);
|
||||
|
||||
// debug!(
|
||||
// "Created transaction with secret {}",
|
||||
// shared_secret.to_byte_array().to_lower_hex_string()
|
||||
// );
|
||||
|
||||
let cipher = encrypt_with_key(
|
||||
serde_json::to_string(&cipher_message)?,
|
||||
shared_secret.to_byte_array().to_lower_hex_string(),
|
||||
@ -1054,6 +1148,7 @@ pub fn create_notification_transaction(
|
||||
new_msg.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string());
|
||||
new_msg.recipient = Some(address);
|
||||
new_msg.sender = Some(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());
|
||||
|
||||
@ -1135,7 +1230,7 @@ pub fn try_decrypt_with_key(cipher: String, key: String) -> ApiResult<String> {
|
||||
#[wasm_bindgen]
|
||||
pub fn create_faucet_msg() -> ApiResult<CachedMessage> {
|
||||
let sp_address = lock_local_device()?
|
||||
.get_watch_only()
|
||||
.get_wallet()
|
||||
.get_client()
|
||||
.get_receiving_address();
|
||||
|
||||
|
248
src/user.rs
248
src/user.rs
@ -1,13 +1,18 @@
|
||||
use anyhow::{Error, Result};
|
||||
use rand::{self, thread_rng, Rng, RngCore};
|
||||
use sdk_common::sp_client::bitcoin::consensus::{deserialize, serialize};
|
||||
use sdk_common::sp_client::bitcoin::hashes::{Hash, HashEngine};
|
||||
use sdk_common::sp_client::bitcoin::hex::{DisplayHex, FromHex};
|
||||
use sdk_common::sp_client::bitcoin::key::{Parity, Secp256k1};
|
||||
use sdk_common::sp_client::bitcoin::secp256k1::{PublicKey, SecretKey, ThirtyTwoByteHash};
|
||||
use sdk_common::sp_client::bitcoin::{Network, OutPoint, ScriptBuf};
|
||||
use sdk_common::sp_client::bitcoin::{
|
||||
Network, OutPoint, ScriptBuf, Transaction, Txid, XOnlyPublicKey,
|
||||
};
|
||||
use sdk_common::sp_client::spclient::SpClient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::convert::VectorFromWasmAbi;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
@ -30,172 +35,165 @@ use sdk_common::crypto::{
|
||||
|
||||
pub static LOCAL_DEVICE: OnceLock<Mutex<Device>> = OnceLock::new();
|
||||
|
||||
pub fn set_new_device(sp_wallet: SpWallet) -> Result<String> {
|
||||
let mut device = Device::new(sp_wallet);
|
||||
|
||||
let mut local_device = lock_local_device()?;
|
||||
if *local_device.get_wallet().get_client() != SpClient::default() {
|
||||
return Err(Error::msg("Device already initialized".to_owned()));
|
||||
} else {
|
||||
*local_device = device;
|
||||
}
|
||||
|
||||
let our_address = local_device
|
||||
.get_wallet()
|
||||
.get_client()
|
||||
.get_receiving_address();
|
||||
|
||||
Ok(our_address)
|
||||
}
|
||||
|
||||
pub fn lock_local_device() -> Result<MutexGuard<'static, Device>> {
|
||||
LOCAL_DEVICE
|
||||
.get_or_init(|| Mutex::new(Device::default()))
|
||||
.lock_anyhow()
|
||||
}
|
||||
|
||||
pub static SPENDING_CLIENT: OnceLock<Mutex<SpClient>> = OnceLock::new();
|
||||
|
||||
pub fn lock_spending_client() -> Result<MutexGuard<'static, SpClient>> {
|
||||
SPENDING_CLIENT
|
||||
.get_or_init(|| Mutex::new(SpClient::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
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct RevokeOutput {
|
||||
key: [u8; 32],
|
||||
spk: ScriptBuf,
|
||||
outpoint: OutPoint,
|
||||
}
|
||||
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));
|
||||
|
||||
impl RevokeOutput {
|
||||
pub fn new(key: [u8; 32], spk: ScriptBuf, outpoint: OutPoint) -> Self {
|
||||
Self { key, spk, outpoint }
|
||||
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 {
|
||||
watch_only_wallet: SpWallet,
|
||||
spend_sk_cipher: Vec<u8>,
|
||||
// Key used to encrypt the remote device spend_sk in the 2FA scheme
|
||||
remote_device_key: [u8; 32],
|
||||
remote_address: Option<String>,
|
||||
revokation_output: Option<OutPoint>,
|
||||
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<PairedDevice>,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
pub fn new(scan_sk: SecretKey, spend_pk: PublicKey, network: Network) -> Self {
|
||||
let watch_only_client = SpClient::new(
|
||||
"default".to_owned(),
|
||||
scan_sk,
|
||||
SpendKey::Public(spend_pk),
|
||||
None,
|
||||
network,
|
||||
)
|
||||
.expect("watch_only_client creation failed");
|
||||
|
||||
let mut watch_only_wallet =
|
||||
SpWallet::new(watch_only_client, None, vec![]).expect("watch_only_wallet creation failed");
|
||||
|
||||
let spend_sk_cipher = vec![];
|
||||
let remote_device_key = [0; 32];
|
||||
|
||||
pub fn new(sp_wallet: SpWallet) -> Self {
|
||||
Self {
|
||||
watch_only_wallet,
|
||||
spend_sk_cipher,
|
||||
remote_device_key,
|
||||
remote_address: None,
|
||||
revokation_output: None,
|
||||
sp_wallet,
|
||||
current_session_outpoint: OutPoint::default(),
|
||||
current_session_key: [0u8; 32],
|
||||
current_session_revokation_outpoint: OutPoint::default(),
|
||||
paired_device: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_watch_only(&self) -> &SpWallet {
|
||||
&self.watch_only_wallet
|
||||
pub fn get_wallet(&self) -> &SpWallet {
|
||||
&self.sp_wallet
|
||||
}
|
||||
|
||||
pub fn get_watch_only_mut(&mut self) -> &mut SpWallet {
|
||||
&mut self.watch_only_wallet
|
||||
pub fn get_mut_wallet(&mut self) -> &mut SpWallet {
|
||||
&mut self.sp_wallet
|
||||
}
|
||||
|
||||
pub fn is_linked(&self) -> bool {
|
||||
self.remote_address.is_some() && self.spend_sk_cipher.len() != 0
|
||||
self.paired_device.is_some()
|
||||
}
|
||||
|
||||
pub fn get_remote_address(&self) -> Option<String> {
|
||||
self.remote_address.clone()
|
||||
pub fn is_pairing(&self) -> bool {
|
||||
self.current_session_key == [0u8; 32]
|
||||
}
|
||||
|
||||
pub fn get_encrypted_key(&self) -> Vec<u8> {
|
||||
self.spend_sk_cipher.clone()
|
||||
pub fn get_paired_device_info(&self) -> Option<PairedDevice> {
|
||||
self.paired_device.clone()
|
||||
}
|
||||
|
||||
pub fn encrypt_for_remote_device(
|
||||
&mut self,
|
||||
remote_spend_sk: SecretKey,
|
||||
aes_key: Option<[u8; 32]>,
|
||||
nonce: Option<[u8; 12]>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let encryption: Aes256Encryption;
|
||||
match (aes_key, nonce) {
|
||||
(Some(key), Some(nonce)) => {
|
||||
encryption = Aes256Encryption::import_key(
|
||||
Purpose::ThirtyTwoBytes,
|
||||
remote_spend_sk.secret_bytes().to_vec(),
|
||||
key,
|
||||
nonce,
|
||||
)?;
|
||||
}
|
||||
(None, None) => {
|
||||
encryption = Aes256Encryption::new(
|
||||
Purpose::ThirtyTwoBytes,
|
||||
remote_spend_sk.secret_bytes().to_vec(),
|
||||
)?;
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::msg(
|
||||
"aes_key and nonce must either both be set or empty",
|
||||
))
|
||||
}
|
||||
}
|
||||
let remote_spend_sk_cipher = encryption.encrypt_with_aes_key();
|
||||
self.remote_device_key = encryption.export_key();
|
||||
remote_spend_sk_cipher
|
||||
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,
|
||||
spend_sk_cipher: Vec<u8>,
|
||||
linked_with: SilentPaymentAddress,
|
||||
revokation_output: OutPoint,
|
||||
) {
|
||||
self.spend_sk_cipher = spend_sk_cipher;
|
||||
self.remote_address = Some(linked_with.into());
|
||||
self.revokation_output = Some(revokation_output);
|
||||
}
|
||||
|
||||
pub fn login(spend_sk: SecretKey) -> Result<()> {
|
||||
let mut locked_client = lock_spending_client()?;
|
||||
if *locked_client != SpClient::default() {
|
||||
// We already have a key charged
|
||||
return Err(Error::msg("We're already logged in"));
|
||||
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);
|
||||
}
|
||||
|
||||
let device = lock_local_device()?;
|
||||
|
||||
let watch_only = device.get_watch_only().get_client();
|
||||
|
||||
let label = watch_only.label.clone();
|
||||
let scan_sk = watch_only.get_scan_key();
|
||||
let network = match watch_only.sp_receiver.network {
|
||||
SpNetwork::Mainnet => Network::Bitcoin,
|
||||
SpNetwork::Regtest => Network::Regtest,
|
||||
SpNetwork::Testnet => Network::Testnet,
|
||||
};
|
||||
|
||||
let spending_client =
|
||||
SpClient::new(label, scan_sk, SpendKey::Secret(spend_sk), None, network)?;
|
||||
|
||||
// check that we loaded the right key
|
||||
if spending_client.get_receiving_address() != watch_only.get_receiving_address() {
|
||||
return Err(Error::msg("Provided the wrong spending key"));
|
||||
}
|
||||
|
||||
*locked_client = spending_client;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn logout() -> Result<()> {
|
||||
if let Ok(mut client) = lock_spending_client() {
|
||||
*client = SpClient::default();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::msg("Failed to lock CONNECTED_USER"))
|
||||
// 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(())
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,26 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
sync::{Mutex, MutexGuard, OnceLock},
|
||||
};
|
||||
|
||||
use anyhow::Error;
|
||||
use rand::Rng;
|
||||
use sdk_common::sp_client::{
|
||||
bitcoin::Network,
|
||||
bitcoin::{Network, OutPoint},
|
||||
silentpayments::utils::SilentPaymentAddress,
|
||||
spclient::{derive_keys_from_seed, SpClient, SpWallet, SpendKey},
|
||||
};
|
||||
|
||||
use crate::MutexExt;
|
||||
|
||||
pub static FREEZED_UTXOS: OnceLock<Mutex<HashSet<OutPoint>>> = OnceLock::new();
|
||||
|
||||
pub fn lock_freezed_utxos() -> Result<MutexGuard<'static, HashSet<OutPoint>>, Error> {
|
||||
FREEZED_UTXOS
|
||||
.get_or_init(|| Mutex::new(HashSet::new()))
|
||||
.lock_anyhow()
|
||||
}
|
||||
|
||||
pub fn generate_sp_wallet(label: Option<String>, network: Network) -> anyhow::Result<SpWallet> {
|
||||
let mut seed = [0u8; 64];
|
||||
rand::thread_rng().fill(&mut seed);
|
||||
|
776
tests/browser.rs
776
tests/browser.rs
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user