New User creation
* Device is an unlogged user that is just able to scan incoming transactions * There's always a Device defined either we load one from indexedDB or create one * Logged users have a defined SPENDING_CLIENT that can sign transactions * The pairing phase is when a new Device is created, we need another Device to send it it's own spending key via normal 4nk messaging * The new Device then encrypt it and send it back * In another transaction, the first Device does the exact same thing (probably possible to optimize) * The logging phase is sending a 4nk message with the encrypted spending key and getting the clear key back, basically logging with one device of a pair is the same operation than pairing but in reverse * Revokation output is an unique output for both devices, the validity of the pair depends of it being unspent * Revokation output is locked with some random key that both devices will need to keep * Todo: 1. implement the revokation scheme 2. optimize the pairing/logging flow
This commit is contained in:
parent
6f74996f39
commit
d737242bb5
337
src/api.rs
337
src/api.rs
@ -22,8 +22,10 @@ use sdk_common::sp_client::bitcoin::hex::{
|
|||||||
parse, DisplayHex, FromHex, HexToArrayError, HexToBytesError,
|
parse, DisplayHex, FromHex, HexToArrayError, HexToBytesError,
|
||||||
};
|
};
|
||||||
use sdk_common::sp_client::bitcoin::key::Secp256k1;
|
use sdk_common::sp_client::bitcoin::key::Secp256k1;
|
||||||
|
use sdk_common::sp_client::bitcoin::network::ParseNetworkError;
|
||||||
use sdk_common::sp_client::bitcoin::secp256k1::ecdh::shared_secret_point;
|
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, 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};
|
||||||
use sdk_common::sp_client::silentpayments::utils as sp_utils;
|
use sdk_common::sp_client::silentpayments::utils as sp_utils;
|
||||||
use sdk_common::sp_client::silentpayments::{
|
use sdk_common::sp_client::silentpayments::{
|
||||||
@ -52,7 +54,9 @@ use sdk_common::sp_client::spclient::{
|
|||||||
use sdk_common::sp_client::spclient::{SpWallet, SpendKey};
|
use sdk_common::sp_client::spclient::{SpWallet, SpendKey};
|
||||||
use crate::wallet::generate_sp_wallet;
|
use crate::wallet::generate_sp_wallet;
|
||||||
|
|
||||||
use crate::user::{lock_connected_user, User, UserWallets, CONNECTED_USER};
|
use crate::user::{
|
||||||
|
lock_local_device, lock_spending_client, Device, RevokeOutput, LOCAL_DEVICE, SPENDING_CLIENT,
|
||||||
|
};
|
||||||
use crate::{images, lock_messages, CACHEDMESSAGES};
|
use crate::{images, lock_messages, CACHEDMESSAGES};
|
||||||
|
|
||||||
use crate::process::Process;
|
use crate::process::Process;
|
||||||
@ -146,88 +150,90 @@ impl From<FromUtf8Error> for ApiError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ParseNetworkError> for ApiError {
|
||||||
|
fn from(value: ParseNetworkError) -> Self {
|
||||||
|
ApiError {
|
||||||
|
message: value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseOutPointError> for ApiError {
|
||||||
|
fn from(value: ParseOutPointError) -> Self {
|
||||||
|
ApiError {
|
||||||
|
message: value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Into<JsValue> for ApiError {
|
impl Into<JsValue> for ApiError {
|
||||||
fn into(self) -> JsValue {
|
fn into(self) -> JsValue {
|
||||||
JsValue::from_str(&self.message)
|
JsValue::from_str(&self.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Tsify, Serialize, Deserialize)]
|
|
||||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
pub struct createUserReturn {
|
|
||||||
pub user: User,
|
|
||||||
pub output_list_vec: Vec<OutputList>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn setup() {
|
pub fn setup() {
|
||||||
wasm_logger::init(wasm_logger::Config::default());
|
wasm_logger::init(wasm_logger::Config::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn get_recover_address() -> ApiResult<String> {
|
pub fn get_address() -> ApiResult<String> {
|
||||||
if let Ok(my_wallets) = lock_connected_user() {
|
let local_device = lock_local_device()?;
|
||||||
Ok(my_wallets
|
|
||||||
.try_get_recover()?
|
Ok(local_device
|
||||||
.get_client()
|
.get_watch_only()
|
||||||
.get_receiving_address())
|
.get_client()
|
||||||
} else {
|
.get_receiving_address())
|
||||||
Err(ApiError {
|
|
||||||
message: "Unknown user pre_id".to_owned(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn get_main_address() -> ApiResult<String> {
|
pub fn create_new_wallet(birthday: u32, network_str: String) -> ApiResult<String> {
|
||||||
if let Ok(my_wallets) = lock_connected_user() {
|
let network = Network::from_core_arg(&network_str)?;
|
||||||
Ok(my_wallets
|
let wallet = generate_sp_wallet(None, Network::Regtest)?;
|
||||||
.try_get_main()?
|
|
||||||
.get_client()
|
|
||||||
.get_receiving_address())
|
|
||||||
} else {
|
|
||||||
Err(ApiError {
|
|
||||||
message: "Unknown user pre_id".to_owned(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
// Let's create the new device
|
||||||
pub fn create_user(
|
let mut device = Device::new(
|
||||||
password: String, // Attention à la conversion depuis le js
|
wallet.get_client().get_scan_key(),
|
||||||
label: Option<String>,
|
wallet.get_client().get_spend_key().into(),
|
||||||
birthday_main: u32,
|
network,
|
||||||
birthday_signet: u32,
|
|
||||||
process: String,
|
|
||||||
) -> ApiResult<createUserReturn> {
|
|
||||||
//recover
|
|
||||||
let sp_wallet_recover = generate_sp_wallet(label.clone(), birthday_signet, Network::Signet)?;
|
|
||||||
//revoke
|
|
||||||
let sp_wallet_revoke = generate_sp_wallet(label.clone(), birthday_signet, Network::Signet)?;
|
|
||||||
//mainet
|
|
||||||
let sp_wallet_main = generate_sp_wallet(label, birthday_main, Network::Bitcoin)?;
|
|
||||||
|
|
||||||
let user_wallets = UserWallets::new(
|
|
||||||
Some(sp_wallet_main),
|
|
||||||
Some(sp_wallet_recover),
|
|
||||||
Some(sp_wallet_revoke),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let user = User::new(user_wallets.clone(), password, process)?;
|
device
|
||||||
|
.get_watch_only_mut()
|
||||||
|
.get_mut_outputs()
|
||||||
|
.set_birthday(birthday);
|
||||||
|
|
||||||
let outputs = user_wallets.get_all_outputs();
|
let our_address = device.get_watch_only().get_client().get_receiving_address();
|
||||||
|
|
||||||
// Setting CONNECTED_USER to user
|
// Set the LOCAL_DEVICE const with the new value
|
||||||
let mut connected_user = lock_connected_user()?;
|
LOCAL_DEVICE
|
||||||
*connected_user = user_wallets;
|
.set(Mutex::new(device))
|
||||||
|
.expect("We shouldn't already have initialized LOCAL_DEVICE now");
|
||||||
|
|
||||||
let generate_user = createUserReturn {
|
// Set the LOGGED_WALLET with the new wallet to keep it in memory while we wait for the linking
|
||||||
user,
|
SPENDING_CLIENT
|
||||||
output_list_vec: outputs,
|
.set(Mutex::new(wallet.get_client().clone()))
|
||||||
};
|
.expect("We shouldn't already have initialized SPENDING_CLIENT now");
|
||||||
|
|
||||||
Ok(generate_user)
|
Ok(our_address)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn update_linked_address(
|
||||||
|
spend_sk_cipher: Vec<u8>,
|
||||||
|
linked_with: String,
|
||||||
|
revokation_output: String,
|
||||||
|
) -> ApiResult<()> {
|
||||||
|
let mut device = lock_local_device()?;
|
||||||
|
|
||||||
|
device.new_link(
|
||||||
|
spend_sk_cipher,
|
||||||
|
linked_with.try_into()?,
|
||||||
|
OutPoint::from_str(&revokation_output)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@ -306,29 +312,19 @@ impl recover_data {
|
|||||||
#[derive(Debug, Tsify, Serialize, Deserialize)]
|
#[derive(Debug, Tsify, Serialize, Deserialize)]
|
||||||
#[tsify(from_wasm_abi, into_wasm_abi)]
|
#[tsify(from_wasm_abi, into_wasm_abi)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub struct outputs_list(Vec<OutputList>);
|
pub struct outputs_list(OutputList);
|
||||||
|
|
||||||
impl outputs_list {
|
impl outputs_list {
|
||||||
fn as_inner(&self) -> &[OutputList] {
|
fn as_inner(&self) -> &OutputList {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn login_user(
|
pub fn login_user(fee_rate: u32) -> ApiResult<()> {
|
||||||
user_password: String,
|
create_login_transaction(fee_rate)?;
|
||||||
pre_id: String,
|
|
||||||
recover: recover_data,
|
|
||||||
outputs: outputs_list,
|
|
||||||
) -> ApiResult<()> {
|
|
||||||
let res = User::login(
|
|
||||||
pre_id,
|
|
||||||
user_password,
|
|
||||||
recover.as_inner(),
|
|
||||||
outputs.as_inner(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_recover_transaction(
|
fn handle_recover_transaction(
|
||||||
@ -336,7 +332,6 @@ fn handle_recover_transaction(
|
|||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
sp_wallet: &mut SpWallet,
|
sp_wallet: &mut SpWallet,
|
||||||
tweak_data: PublicKey,
|
tweak_data: PublicKey,
|
||||||
fee_rate: u32,
|
|
||||||
) -> anyhow::Result<CachedMessage> {
|
) -> anyhow::Result<CachedMessage> {
|
||||||
let op_return = tx.output.iter().find(|o| o.script_pubkey.is_op_return());
|
let op_return = tx.output.iter().find(|o| o.script_pubkey.is_op_return());
|
||||||
let commitment = if op_return.is_none() {
|
let commitment = if op_return.is_none() {
|
||||||
@ -521,35 +516,18 @@ fn process_transaction(
|
|||||||
tx_hex: String,
|
tx_hex: String,
|
||||||
blockheight: u32,
|
blockheight: u32,
|
||||||
tweak_data_hex: String,
|
tweak_data_hex: String,
|
||||||
fee_rate: u32,
|
|
||||||
) -> anyhow::Result<CachedMessage> {
|
) -> anyhow::Result<CachedMessage> {
|
||||||
let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_hex)?)?;
|
let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_hex)?)?;
|
||||||
|
|
||||||
let tweak_data = PublicKey::from_str(&tweak_data_hex)?;
|
let tweak_data = PublicKey::from_str(&tweak_data_hex)?;
|
||||||
|
|
||||||
let mut connected_user = lock_connected_user()?;
|
let mut device = lock_local_device()?;
|
||||||
if let Ok(recover) = connected_user.try_get_mut_recover() {
|
let wallet = device.get_watch_only_mut();
|
||||||
let updated = recover.update_wallet_with_transaction(&tx, blockheight, tweak_data)?;
|
let updated = wallet.update_wallet_with_transaction(&tx, blockheight, tweak_data)?;
|
||||||
|
|
||||||
if updated.len() > 0 {
|
if updated.len() > 0 {
|
||||||
let updated_msg =
|
let updated_msg = handle_recover_transaction(updated, &tx, wallet, tweak_data)?;
|
||||||
handle_recover_transaction(updated, &tx, recover, tweak_data, fee_rate)?;
|
return Ok(updated_msg);
|
||||||
return Ok(updated_msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(main) = connected_user.try_get_mut_main() {
|
|
||||||
let updated = main.update_wallet_with_transaction(&tx, blockheight, tweak_data)?;
|
|
||||||
if updated.len() > 0 {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(revoke) = connected_user.try_get_mut_revoke() {
|
|
||||||
let updated = revoke.update_wallet_with_transaction(&tx, blockheight, tweak_data)?;
|
|
||||||
if updated.len() > 0 {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(anyhow::Error::msg("No output found"))
|
Err(anyhow::Error::msg("No output found"))
|
||||||
@ -580,12 +558,8 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<CachedMessage>
|
|||||||
message: "Missing tweak_data".to_owned(),
|
message: "Missing tweak_data".to_owned(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let network_msg = process_transaction(
|
let network_msg =
|
||||||
tx_message.transaction,
|
process_transaction(tx_message.transaction, 0, tx_message.tweak_data.unwrap())?;
|
||||||
0,
|
|
||||||
tx_message.tweak_data.unwrap(),
|
|
||||||
fee_rate,
|
|
||||||
)?;
|
|
||||||
return Ok(network_msg);
|
return Ok(network_msg);
|
||||||
}
|
}
|
||||||
AnkFlag::Faucet => {
|
AnkFlag::Faucet => {
|
||||||
@ -635,54 +609,19 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<CachedMessage>
|
|||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn get_outpoints_for_user() -> ApiResult<outputs_list> {
|
pub fn get_outpoints_for_user() -> ApiResult<outputs_list> {
|
||||||
let connected_user = lock_connected_user()?;
|
let device = lock_local_device()?;
|
||||||
if connected_user.is_not_empty() {
|
let outputs = device.get_watch_only().get_outputs().clone();
|
||||||
Ok(outputs_list(connected_user.get_all_outputs()))
|
Ok(outputs_list(outputs))
|
||||||
} else {
|
|
||||||
Err(ApiError {
|
|
||||||
message: "No user logged in".to_owned(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn get_available_amount_for_user(recover: bool) -> ApiResult<u64> {
|
pub fn get_available_amount_for_user() -> ApiResult<u64> {
|
||||||
let connected_user = lock_connected_user()?;
|
let device = lock_local_device()?;
|
||||||
if recover {
|
|
||||||
if let Ok(recover_wallet) = connected_user.try_get_recover() {
|
Ok(device.get_watch_only().get_outputs().get_balance().to_sat())
|
||||||
Ok(recover_wallet.get_outputs().get_balance().to_sat())
|
|
||||||
} else {
|
|
||||||
Err(ApiError {
|
|
||||||
message: "User doesn't have recover wallet available".to_owned(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ApiError {
|
|
||||||
message: "No user logged in".to_owned(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[derive(Tsify, Serialize, Deserialize, Default)]
|
||||||
pub fn is_tx_owned_by_user(pre_id: String, tx: String) -> ApiResult<bool> {
|
|
||||||
let transaction = deserialize::<Transaction>(&Vec::from_hex(&tx)?)?;
|
|
||||||
let txid = transaction.txid();
|
|
||||||
let connected_user = lock_connected_user()?;
|
|
||||||
|
|
||||||
if let Some(_) = connected_user
|
|
||||||
.try_get_recover()?
|
|
||||||
.get_outputs()
|
|
||||||
.to_outpoints_list()
|
|
||||||
.iter()
|
|
||||||
.find(|(outpoint, output)| outpoint.txid == txid)
|
|
||||||
{
|
|
||||||
Ok(true)
|
|
||||||
} else {
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Tsify, Serialize, Deserialize)]
|
|
||||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub struct createTransactionReturn {
|
pub struct createTransactionReturn {
|
||||||
@ -716,14 +655,13 @@ pub fn answer_confirmation_transaction(
|
|||||||
let sp_address: SilentPaymentAddress =
|
let sp_address: SilentPaymentAddress =
|
||||||
message.recipient.as_ref().unwrap().as_str().try_into()?;
|
message.recipient.as_ref().unwrap().as_str().try_into()?;
|
||||||
|
|
||||||
let connected_user = lock_connected_user()?;
|
let local_device = lock_local_device()?;
|
||||||
|
|
||||||
let sp_wallet: &SpWallet;
|
let current_outputs = local_device.get_watch_only().get_outputs().clone();
|
||||||
if sp_address.get_network() != SpNetwork::Mainnet {
|
|
||||||
sp_wallet = connected_user.try_get_recover()?;
|
let spending_client = lock_spending_client()?.clone();
|
||||||
} else {
|
|
||||||
sp_wallet = connected_user.try_get_main()?;
|
let sp_wallet = SpWallet::new(spending_client, Some(current_outputs))?;
|
||||||
}
|
|
||||||
|
|
||||||
let recipient = Recipient {
|
let recipient = Recipient {
|
||||||
address: sp_address.into(),
|
address: sp_address.into(),
|
||||||
@ -736,9 +674,10 @@ pub fn answer_confirmation_transaction(
|
|||||||
|
|
||||||
let signed_psbt = create_transaction_spend_outpoint(
|
let signed_psbt = create_transaction_spend_outpoint(
|
||||||
&confirmed_by,
|
&confirmed_by,
|
||||||
sp_wallet,
|
&sp_wallet,
|
||||||
recipient,
|
recipient,
|
||||||
&commited_in.txid,
|
&commited_in.txid,
|
||||||
|
None,
|
||||||
Amount::from_sat(fee_rate.into()),
|
Amount::from_sat(fee_rate.into()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -776,15 +715,24 @@ pub fn create_confirmation_transaction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let sp_address: SilentPaymentAddress = message.sender.as_ref().unwrap().as_str().try_into()?;
|
let sp_address: SilentPaymentAddress = message.sender.as_ref().unwrap().as_str().try_into()?;
|
||||||
let connected_user = lock_connected_user()?;
|
|
||||||
|
|
||||||
let sp_wallet: &SpWallet;
|
let mut local_device = lock_local_device()?;
|
||||||
if sp_address.get_network() != SpNetwork::Mainnet {
|
|
||||||
sp_wallet = connected_user.try_get_recover()?;
|
// Are we waiting for pairing?
|
||||||
|
let remote_key_cipher: Option<Vec<u8>>;
|
||||||
|
if !local_device.is_linked() {
|
||||||
|
let remote_spend_sk = SecretKey::from_str(message.plaintext.as_deref().unwrap())?;
|
||||||
|
remote_key_cipher = Some(local_device.encrypt_for_remote_device(remote_spend_sk)?);
|
||||||
} else {
|
} else {
|
||||||
sp_wallet = connected_user.try_get_main()?;
|
remote_key_cipher = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))?;
|
||||||
|
|
||||||
let recipient = Recipient {
|
let recipient = Recipient {
|
||||||
address: sp_address.into(),
|
address: sp_address.into(),
|
||||||
amount: Amount::from_sat(0),
|
amount: Amount::from_sat(0),
|
||||||
@ -795,9 +743,10 @@ pub fn create_confirmation_transaction(
|
|||||||
|
|
||||||
let signed_psbt = create_transaction_spend_outpoint(
|
let signed_psbt = create_transaction_spend_outpoint(
|
||||||
&commited_in,
|
&commited_in,
|
||||||
sp_wallet,
|
&sp_wallet,
|
||||||
recipient,
|
recipient,
|
||||||
&commited_in.txid,
|
&commited_in.txid,
|
||||||
|
remote_key_cipher,
|
||||||
Amount::from_sat(fee_rate.into()),
|
Amount::from_sat(fee_rate.into()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -822,6 +771,47 @@ pub fn create_confirmation_transaction(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn create_pairing_transaction(
|
||||||
|
address: String,
|
||||||
|
fee_rate: u32,
|
||||||
|
) -> ApiResult<createTransactionReturn> {
|
||||||
|
let message: CipherMessage;
|
||||||
|
{
|
||||||
|
let mut spending_wallet = lock_spending_client()?;
|
||||||
|
|
||||||
|
let our_address = spending_wallet.get_receiving_address();
|
||||||
|
let spend_sk: SecretKey = spending_wallet.get_spend_key().try_into()?;
|
||||||
|
|
||||||
|
message = CipherMessage::new(our_address, format!("{}", spend_sk.display_secret()));
|
||||||
|
|
||||||
|
// we forget our own wallet
|
||||||
|
*spending_wallet = SpClient::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
create_notification_transaction(address, message, fee_rate)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
create_notification_transaction(address, message, fee_rate)
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn create_notification_transaction(
|
pub fn create_notification_transaction(
|
||||||
address: String,
|
address: String,
|
||||||
@ -830,14 +820,13 @@ pub fn create_notification_transaction(
|
|||||||
) -> ApiResult<createTransactionReturn> {
|
) -> ApiResult<createTransactionReturn> {
|
||||||
let sp_address: SilentPaymentAddress = address.as_str().try_into()?;
|
let sp_address: SilentPaymentAddress = address.as_str().try_into()?;
|
||||||
|
|
||||||
let connected_user = lock_connected_user()?;
|
let local_device = lock_local_device()?;
|
||||||
|
|
||||||
let sp_wallet: &SpWallet;
|
let current_outputs = local_device.get_watch_only().get_outputs().clone();
|
||||||
if sp_address.get_network() != SpNetwork::Mainnet {
|
|
||||||
sp_wallet = connected_user.try_get_recover()?;
|
let spending_client = lock_spending_client()?.clone();
|
||||||
} else {
|
|
||||||
sp_wallet = connected_user.try_get_main()?;
|
let sp_wallet = SpWallet::new(spending_client, Some(current_outputs))?;
|
||||||
}
|
|
||||||
|
|
||||||
let recipient = Recipient {
|
let recipient = Recipient {
|
||||||
address: sp_address.into(),
|
address: sp_address.into(),
|
||||||
@ -849,7 +838,7 @@ pub fn create_notification_transaction(
|
|||||||
|
|
||||||
let signed_psbt = create_transaction_for_address_with_shared_secret(
|
let signed_psbt = create_transaction_for_address_with_shared_secret(
|
||||||
recipient,
|
recipient,
|
||||||
sp_wallet,
|
&sp_wallet,
|
||||||
Some(&commitment),
|
Some(&commitment),
|
||||||
Amount::from_sat(fee_rate.into()),
|
Amount::from_sat(fee_rate.into()),
|
||||||
)?;
|
)?;
|
||||||
@ -974,8 +963,10 @@ pub fn try_decrypt_with_key(cipher: String, key: String) -> ApiResult<String> {
|
|||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn create_faucet_msg() -> ApiResult<CachedMessage> {
|
pub fn create_faucet_msg() -> ApiResult<CachedMessage> {
|
||||||
let user = lock_connected_user()?;
|
let sp_address = lock_local_device()?
|
||||||
let sp_address = user.try_get_recover()?.get_client().get_receiving_address();
|
.get_watch_only()
|
||||||
|
.get_client()
|
||||||
|
.get_receiving_address();
|
||||||
|
|
||||||
let mut commitment = [0u8; 64];
|
let mut commitment = [0u8; 64];
|
||||||
thread_rng().fill_bytes(&mut commitment);
|
thread_rng().fill_bytes(&mut commitment);
|
||||||
|
501
src/user.rs
501
src/user.rs
@ -1,11 +1,9 @@
|
|||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use rand::{self, thread_rng, Rng, RngCore};
|
use rand::{self, thread_rng, Rng, RngCore};
|
||||||
use sdk_common::sp_client::bitcoin::hashes::Hash;
|
use sdk_common::sp_client::bitcoin::hashes::{Hash, HashEngine};
|
||||||
use sdk_common::sp_client::bitcoin::hashes::HashEngine;
|
|
||||||
use sdk_common::sp_client::bitcoin::hex::{DisplayHex, FromHex};
|
use sdk_common::sp_client::bitcoin::hex::{DisplayHex, FromHex};
|
||||||
use sdk_common::sp_client::bitcoin::secp256k1::SecretKey;
|
use sdk_common::sp_client::bitcoin::secp256k1::{PublicKey, SecretKey, ThirtyTwoByteHash};
|
||||||
use sdk_common::sp_client::bitcoin::secp256k1::ThirtyTwoByteHash;
|
use sdk_common::sp_client::bitcoin::{Network, OutPoint, ScriptBuf};
|
||||||
use sdk_common::sp_client::bitcoin::Network;
|
|
||||||
use sdk_common::sp_client::spclient::SpClient;
|
use sdk_common::sp_client::spclient::SpClient;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
@ -20,411 +18,164 @@ use std::sync::{Mutex, MutexGuard, OnceLock};
|
|||||||
|
|
||||||
use sdk_common::sp_client::bitcoin::secp256k1::constants::SECRET_KEY_SIZE;
|
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::bitcoin_hashes::sha256;
|
||||||
use sdk_common::sp_client::silentpayments::utils::SilentPaymentAddress;
|
use sdk_common::sp_client::silentpayments::utils::{Network as SpNetwork, SilentPaymentAddress};
|
||||||
use sdk_common::sp_client::spclient::SpendKey;
|
use sdk_common::sp_client::spclient::{OutputList, SpWallet, SpendKey};
|
||||||
use sdk_common::sp_client::spclient::{OutputList, SpWallet};
|
|
||||||
|
|
||||||
use crate::peers::Peer;
|
use crate::peers::Peer;
|
||||||
use crate::user;
|
use crate::wallet::generate_sp_wallet;
|
||||||
use crate::MutexExt;
|
use crate::MutexExt;
|
||||||
use sdk_common::crypto::{
|
use sdk_common::crypto::{
|
||||||
AeadCore, Aes256Decryption, Aes256Encryption, Aes256Gcm, HalfKey, KeyInit, Purpose,
|
AeadCore, Aes256Decryption, Aes256Encryption, Aes256Gcm, HalfKey, KeyInit, Purpose,
|
||||||
};
|
};
|
||||||
|
|
||||||
type PreId = String;
|
|
||||||
|
|
||||||
pub static CONNECTED_USER: OnceLock<Mutex<UserWallets>> = OnceLock::new();
|
|
||||||
|
|
||||||
pub fn lock_connected_user() -> Result<MutexGuard<'static, UserWallets>> {
|
pub static LOCAL_DEVICE: OnceLock<Mutex<Device>> = OnceLock::new();
|
||||||
CONNECTED_USER
|
|
||||||
.get_or_init(|| Mutex::new(UserWallets::default()))
|
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()
|
.lock_anyhow()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
pub struct UserWallets {
|
pub struct RevokeOutput {
|
||||||
main: Option<SpWallet>,
|
key: [u8; 32],
|
||||||
recover: Option<SpWallet>,
|
spk: ScriptBuf,
|
||||||
revoke: Option<SpWallet>,
|
outpoint: OutPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserWallets {
|
impl RevokeOutput {
|
||||||
pub fn new(
|
pub fn new(key: [u8; 32], spk: ScriptBuf, outpoint: OutPoint) -> Self {
|
||||||
main: Option<SpWallet>,
|
Self { key, spk, outpoint }
|
||||||
recover: Option<SpWallet>,
|
|
||||||
revoke: Option<SpWallet>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
main,
|
|
||||||
recover,
|
|
||||||
revoke,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_revoke(&self) -> Result<&SpWallet> {
|
|
||||||
if let Some(revoke) = &self.revoke {
|
|
||||||
Ok(revoke)
|
|
||||||
} else {
|
|
||||||
Err(Error::msg("No revoke wallet available"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_recover(&self) -> Result<&SpWallet> {
|
|
||||||
if let Some(recover) = &self.recover {
|
|
||||||
Ok(recover)
|
|
||||||
} else {
|
|
||||||
Err(Error::msg("No recover wallet available"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_main(&self) -> Result<&SpWallet> {
|
|
||||||
if let Some(main) = &self.main {
|
|
||||||
Ok(main)
|
|
||||||
} else {
|
|
||||||
Err(Error::msg("No main wallet available"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_mut_revoke(&mut self) -> Result<&mut SpWallet> {
|
|
||||||
if let Some(revoke) = &mut self.revoke {
|
|
||||||
Ok(revoke)
|
|
||||||
} else {
|
|
||||||
Err(Error::msg("No revoke wallet available"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_mut_recover(&mut self) -> Result<&mut SpWallet> {
|
|
||||||
if let Some(recover) = &mut self.recover {
|
|
||||||
Ok(recover)
|
|
||||||
} else {
|
|
||||||
Err(Error::msg("No recover wallet available"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_mut_main(&mut self) -> Result<&mut SpWallet> {
|
|
||||||
if let Some(main) = &mut self.main {
|
|
||||||
Ok(main)
|
|
||||||
} else {
|
|
||||||
Err(Error::msg("No main wallet available"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_not_empty(&self) -> bool {
|
|
||||||
self.get_all_outputs().len() > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_all_outputs(&self) -> Vec<OutputList> {
|
|
||||||
let mut res = Vec::<OutputList>::new();
|
|
||||||
if let Some(main) = &self.main {
|
|
||||||
res.push(main.get_outputs().clone());
|
|
||||||
}
|
|
||||||
if let Some(revoke) = &self.revoke {
|
|
||||||
res.push(revoke.get_outputs().clone());
|
|
||||||
}
|
|
||||||
if let Some(recover) = &self.recover {
|
|
||||||
res.push(recover.get_outputs().clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Tsify)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, Tsify)]
|
||||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||||
pub struct User {
|
pub struct Device {
|
||||||
pub pre_id: PreId,
|
watch_only_wallet: SpWallet,
|
||||||
pub processes: Vec<String>,
|
spend_sk_cipher: Vec<u8>,
|
||||||
pub peers: Vec<Peer>,
|
// Key used to encrypt the remote device spend_sk in the 2FA scheme
|
||||||
recover_data: Vec<u8>,
|
remote_device_key: [u8; 32],
|
||||||
revoke_data: Option<Vec<u8>>,
|
remote_address: Option<String>,
|
||||||
outputs: Vec<OutputList>,
|
revokation_output: Option<OutPoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl Device {
|
||||||
pub fn new(user_wallets: UserWallets, user_password: String, process: String) -> Result<Self> {
|
pub fn new(scan_sk: SecretKey, spend_pk: PublicKey, network: Network) -> Self {
|
||||||
// if we are already logged in, abort
|
let watch_only_client = SpClient::new(
|
||||||
if lock_connected_user()?.is_not_empty() {
|
"default".to_owned(),
|
||||||
return Err(Error::msg("User already logged in"));
|
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).expect("watch_only_wallet creation failed");
|
||||||
|
|
||||||
|
let spend_sk_cipher = vec![];
|
||||||
|
let remote_device_key = [0; 32];
|
||||||
|
|
||||||
|
Self {
|
||||||
|
watch_only_wallet,
|
||||||
|
spend_sk_cipher,
|
||||||
|
remote_device_key,
|
||||||
|
remote_address: None,
|
||||||
|
revokation_output: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_watch_only(&self) -> &SpWallet {
|
||||||
|
&self.watch_only_wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_watch_only_mut(&mut self) -> &mut SpWallet {
|
||||||
|
&mut self.watch_only_wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_linked(&self) -> bool {
|
||||||
|
self.remote_address.is_some() && self.spend_sk_cipher.len() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_remote_address(&self) -> Option<String> {
|
||||||
|
self.remote_address.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_encrypted_key(&self) -> Vec<u8> {
|
||||||
|
self.spend_sk_cipher.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt_for_remote_device(&mut self, remote_spend_sk: SecretKey) -> Result<Vec<u8>> {
|
||||||
|
// encrypt with the remote_device_key
|
||||||
|
let encryption = Aes256Encryption::new(
|
||||||
|
Purpose::ThirtyTwoBytes,
|
||||||
|
remote_spend_sk.secret_bytes().to_vec(),
|
||||||
|
)?;
|
||||||
|
let remote_spend_sk_cipher = encryption.encrypt_with_aes_key();
|
||||||
|
self.remote_device_key = encryption.export_key();
|
||||||
|
remote_spend_sk_cipher
|
||||||
|
}
|
||||||
|
|
||||||
|
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"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut rng = thread_rng();
|
let device = lock_local_device()?;
|
||||||
|
|
||||||
// image revoke
|
let watch_only = device.get_watch_only().get_client();
|
||||||
// We just take the 2 revoke keys
|
|
||||||
let mut revoke_data = Vec::with_capacity(64);
|
|
||||||
let revoke = user_wallets.try_get_revoke()?;
|
|
||||||
revoke_data.extend_from_slice(revoke.get_client().get_scan_key().as_ref());
|
|
||||||
revoke_data.extend_from_slice(revoke.get_client().try_get_secret_spend_key()?.as_ref());
|
|
||||||
|
|
||||||
// Take the 2 recover keys
|
let label = watch_only.label.clone();
|
||||||
let recover_spend_key = user_wallets
|
let scan_sk = watch_only.get_scan_key();
|
||||||
.try_get_recover()?
|
let network = match watch_only.sp_receiver.network {
|
||||||
.get_client()
|
SpNetwork::Mainnet => Network::Bitcoin,
|
||||||
.try_get_secret_spend_key()?
|
SpNetwork::Regtest => Network::Regtest,
|
||||||
.clone();
|
SpNetwork::Testnet => Network::Testnet,
|
||||||
let mut recover_data = Vec::<u8>::with_capacity(180); // 32 * 3 + (12+16)*3
|
};
|
||||||
|
|
||||||
// generate 3 tokens of 32B entropy
|
let spending_client =
|
||||||
let mut entropy_1: [u8; 32] = Aes256Gcm::generate_key(&mut rng).into();
|
SpClient::new(label, scan_sk, SpendKey::Secret(spend_sk), None, network)?;
|
||||||
let mut entropy_2: [u8; 32] = Aes256Gcm::generate_key(&mut rng).into();
|
|
||||||
|
|
||||||
recover_data.extend_from_slice(&entropy_1);
|
// check that we loaded the right key
|
||||||
recover_data.extend_from_slice(&entropy_2);
|
if spending_client.get_receiving_address() != watch_only.get_receiving_address() {
|
||||||
|
return Err(Error::msg("Provided the wrong spending key"));
|
||||||
|
}
|
||||||
|
|
||||||
// hash the concatenation
|
*locked_client = spending_client;
|
||||||
let mut engine = sha256::HashEngine::default();
|
|
||||||
engine.write_all(&user_password.as_bytes());
|
|
||||||
engine.write_all(&entropy_1);
|
|
||||||
let hash1 = sha256::Hash::from_engine(engine);
|
|
||||||
|
|
||||||
// take it as a AES key
|
Ok(())
|
||||||
let recover_key_encryption = Aes256Encryption::import_key(
|
|
||||||
Purpose::ThirtyTwoBytes,
|
|
||||||
recover_spend_key.secret_bytes().to_vec(),
|
|
||||||
hash1.to_byte_array(),
|
|
||||||
Aes256Gcm::generate_nonce(&mut rng).into(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// encrypt the part1 of the key
|
|
||||||
let cipher_recover = recover_key_encryption.encrypt_with_aes_key()?;
|
|
||||||
|
|
||||||
recover_data.extend_from_slice(&cipher_recover);
|
|
||||||
|
|
||||||
//Pre ID
|
|
||||||
let pre_id: PreId = Self::compute_pre_id(&user_password, &cipher_recover);
|
|
||||||
|
|
||||||
//scan key:
|
|
||||||
let mut engine = sha256::HashEngine::default();
|
|
||||||
engine.write_all(&user_password.as_bytes());
|
|
||||||
engine.write_all(&entropy_2);
|
|
||||||
let hash2 = sha256::Hash::from_engine(engine);
|
|
||||||
|
|
||||||
let scan_key_encryption = Aes256Encryption::import_key(
|
|
||||||
Purpose::ThirtyTwoBytes,
|
|
||||||
user_wallets
|
|
||||||
.try_get_recover()?
|
|
||||||
.get_client()
|
|
||||||
.get_scan_key()
|
|
||||||
.secret_bytes()
|
|
||||||
.to_vec(),
|
|
||||||
hash2.to_byte_array(),
|
|
||||||
Aes256Gcm::generate_nonce(&mut rng).into(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// encrypt the scan key
|
|
||||||
let cipher_scan_key = scan_key_encryption.encrypt_with_aes_key()?;
|
|
||||||
|
|
||||||
recover_data.extend_from_slice(&cipher_scan_key);
|
|
||||||
|
|
||||||
let all_outputs = user_wallets.get_all_outputs();
|
|
||||||
|
|
||||||
Ok(User {
|
|
||||||
pre_id: pre_id.to_string(),
|
|
||||||
processes: vec![process],
|
|
||||||
peers: vec![],
|
|
||||||
recover_data,
|
|
||||||
revoke_data: Some(revoke_data),
|
|
||||||
outputs: all_outputs,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn logout() -> Result<()> {
|
pub fn logout() -> Result<()> {
|
||||||
if let Ok(mut user) = lock_connected_user() {
|
if let Ok(mut client) = lock_spending_client() {
|
||||||
*user = UserWallets::default();
|
*client = SpClient::default();
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::msg("Failed to lock CONNECTED_USER"))
|
Err(Error::msg("Failed to lock CONNECTED_USER"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn login(
|
|
||||||
pre_id: PreId,
|
|
||||||
user_password: String,
|
|
||||||
recover_data: &[u8],
|
|
||||||
outputs: &[OutputList],
|
|
||||||
) -> Result<()> {
|
|
||||||
// if we are already logged in, abort
|
|
||||||
if lock_connected_user()?.is_not_empty() {
|
|
||||||
return Err(Error::msg("User already logged in"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut retrieved_spend_key = [0u8; 32];
|
|
||||||
let mut retrieved_scan_key = [0u8; 32];
|
|
||||||
let mut entropy1 = [0u8; 32];
|
|
||||||
let mut entropy2 = [0u8; 32];
|
|
||||||
let mut cipher_scan_key = [0u8; 60]; // cipher length == plain.len() + 16 + nonce.len()
|
|
||||||
let mut cipher_spend_key = [0u8; 60];
|
|
||||||
|
|
||||||
let mut reader = Cursor::new(recover_data);
|
|
||||||
reader.read_exact(&mut entropy1)?;
|
|
||||||
reader.read_exact(&mut entropy2)?;
|
|
||||||
reader.read_exact(&mut cipher_spend_key)?;
|
|
||||||
reader.read_exact(&mut cipher_scan_key)?;
|
|
||||||
|
|
||||||
// We can retrieve the pre_id and check that it matches
|
|
||||||
let retrieved_pre_id = Self::compute_pre_id(&user_password, &cipher_spend_key);
|
|
||||||
|
|
||||||
// If pre_id is not the same, password is probably false, or the client is feeding us garbage
|
|
||||||
if retrieved_pre_id != pre_id {
|
|
||||||
return Err(Error::msg("pre_id and recover_data don't match"));
|
|
||||||
}
|
|
||||||
|
|
||||||
retrieved_spend_key.copy_from_slice(&Self::recover_key_slice(
|
|
||||||
&user_password,
|
|
||||||
&entropy1,
|
|
||||||
cipher_spend_key.to_vec(),
|
|
||||||
)?);
|
|
||||||
|
|
||||||
retrieved_scan_key.copy_from_slice(&Self::recover_key_slice(
|
|
||||||
&user_password,
|
|
||||||
&entropy2,
|
|
||||||
cipher_scan_key.to_vec(),
|
|
||||||
)?);
|
|
||||||
|
|
||||||
// we can create the recover sp_client
|
|
||||||
let recover_client = SpClient::new(
|
|
||||||
"".to_owned(),
|
|
||||||
SecretKey::from_slice(&retrieved_scan_key)?,
|
|
||||||
SpendKey::Secret(SecretKey::from_slice(&retrieved_spend_key)?),
|
|
||||||
None,
|
|
||||||
Network::Signet,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let recover_outputs = outputs
|
|
||||||
.iter()
|
|
||||||
.find(|o| o.check_fingerprint(&recover_client))
|
|
||||||
.cloned();
|
|
||||||
|
|
||||||
let recover_wallet = SpWallet::new(recover_client, recover_outputs)?;
|
|
||||||
|
|
||||||
let user_wallets = UserWallets::new(None, Some(recover_wallet), None);
|
|
||||||
|
|
||||||
if let Ok(mut user) = lock_connected_user() {
|
|
||||||
*user = user_wallets;
|
|
||||||
} else {
|
|
||||||
return Err(Error::msg("Failed to lock CONNECTED_USER"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn recover_key_slice(password: &str, entropy: &[u8], ciphertext: Vec<u8>) -> Result<Vec<u8>> {
|
|
||||||
let mut engine = sha256::HashEngine::default();
|
|
||||||
engine.write_all(&password.as_bytes());
|
|
||||||
engine.write_all(&entropy);
|
|
||||||
let hash = sha256::Hash::from_engine(engine);
|
|
||||||
|
|
||||||
let aes_dec =
|
|
||||||
Aes256Decryption::new(Purpose::ThirtyTwoBytes, ciphertext, hash.to_byte_array())?;
|
|
||||||
|
|
||||||
aes_dec.decrypt_with_key()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_pre_id(user_password: &str, cipher_recover: &[u8]) -> PreId {
|
|
||||||
let mut engine = sha256::HashEngine::default();
|
|
||||||
engine.write_all(&user_password.as_bytes());
|
|
||||||
engine.write_all(&cipher_recover);
|
|
||||||
let pre_id = sha256::Hash::from_engine(engine);
|
|
||||||
|
|
||||||
pre_id.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*; // Import everything from the outer module
|
|
||||||
|
|
||||||
const RECOVER_SPEND: &str = "394ef7757f5bc8cd692337c62abf6fa0ce9932fd4ec6676daddfbe3c1b3b9d11";
|
|
||||||
const RECOVER_SCAN: &str = "3aa8cc570d17ec3a4dc4136e50151cc6de26052d968abfe02a5fea724ce38205";
|
|
||||||
const REVOKE_SPEND: &str = "821c1a84fa9ee718c02005505fb8315bd479c7b9a878b1eff45929c48dfcaf28";
|
|
||||||
const REVOKE_SCAN: &str = "a0f36cbc380624fa7eef022f39cab2716333451649dd8eb78e86d2e76bdb3f47";
|
|
||||||
const MAIN_SPEND: &str = "b9098a6598ac55d8dd0e6b7aab0d1f63eb8792d06143f3c0fb6f5b80476a1c0d";
|
|
||||||
const MAIN_SCAN: &str = "79dda4031663ac2cb250c46d896dc92b3c027a48a761b2342fabf1e441ea2857";
|
|
||||||
const USER_PASSWORD: &str = "correct horse battery staple";
|
|
||||||
const PROCESS: &str = "example";
|
|
||||||
|
|
||||||
fn helper_create_user_wallets() -> UserWallets {
|
|
||||||
let label = "default".to_owned();
|
|
||||||
let sp_main = SpClient::new(
|
|
||||||
label.clone(),
|
|
||||||
SecretKey::from_str(MAIN_SCAN).unwrap(),
|
|
||||||
SpendKey::Secret(SecretKey::from_str(MAIN_SPEND).unwrap()),
|
|
||||||
None,
|
|
||||||
Network::Bitcoin,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let sp_recover = SpClient::new(
|
|
||||||
label.clone(),
|
|
||||||
SecretKey::from_str(RECOVER_SCAN).unwrap(),
|
|
||||||
SpendKey::Secret(SecretKey::from_str(RECOVER_SPEND).unwrap()),
|
|
||||||
None,
|
|
||||||
Network::Signet,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let sp_revoke = SpClient::new(
|
|
||||||
label.clone(),
|
|
||||||
SecretKey::from_str(REVOKE_SCAN).unwrap(),
|
|
||||||
SpendKey::Secret(SecretKey::from_str(REVOKE_SPEND).unwrap()),
|
|
||||||
None,
|
|
||||||
Network::Signet,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let user_wallets = UserWallets::new(
|
|
||||||
Some(SpWallet::new(sp_main, None).unwrap()),
|
|
||||||
Some(SpWallet::new(sp_recover, None).unwrap()),
|
|
||||||
Some(SpWallet::new(sp_revoke, None).unwrap()),
|
|
||||||
);
|
|
||||||
|
|
||||||
user_wallets
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_successful_creation() {
|
|
||||||
let user_wallets = helper_create_user_wallets();
|
|
||||||
let result = User::new(user_wallets, USER_PASSWORD.to_owned(), PROCESS.to_owned());
|
|
||||||
|
|
||||||
assert!(result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_logout() {
|
|
||||||
let res = User::logout();
|
|
||||||
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_login() {
|
|
||||||
let user_wallets = helper_create_user_wallets();
|
|
||||||
let user = User::new(
|
|
||||||
user_wallets.clone(),
|
|
||||||
USER_PASSWORD.to_owned(),
|
|
||||||
PROCESS.to_owned(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let res = User::login(
|
|
||||||
user.pre_id.clone(),
|
|
||||||
USER_PASSWORD.to_owned(),
|
|
||||||
&user.recover_data,
|
|
||||||
&user_wallets.get_all_outputs(),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(res.is_ok());
|
|
||||||
|
|
||||||
let connected = lock_connected_user().unwrap();
|
|
||||||
|
|
||||||
let recover = connected.try_get_recover().unwrap();
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
format!(
|
|
||||||
"{}",
|
|
||||||
recover
|
|
||||||
.get_client()
|
|
||||||
.try_get_secret_spend_key()
|
|
||||||
.unwrap()
|
|
||||||
.display_secret()
|
|
||||||
) == RECOVER_SPEND
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user