From 8f6918748dcad0897facb361d0d9c6b434f2109c Mon Sep 17 00:00:00 2001 From: Sosthene00 <674694@protonmail.ch> Date: Wed, 17 Apr 2024 09:15:44 +0200 Subject: [PATCH] add notify address for message --- crates/sp_client/src/api.rs | 81 ++++++++++++++++++++------ crates/sp_client/src/silentpayments.rs | 80 ++++++++++++++++++++++++- src/services.ts | 24 ++++++++ 3 files changed, 165 insertions(+), 20 deletions(-) diff --git a/crates/sp_client/src/api.rs b/crates/sp_client/src/api.rs index c9b80b6..3182318 100644 --- a/crates/sp_client/src/api.rs +++ b/crates/sp_client/src/api.rs @@ -355,24 +355,69 @@ pub fn parse_network_msg(raw: String) -> ApiResult { } #[wasm_bindgen] -pub fn parse_4nk_msg(raw: String) -> Option{ - if let Ok(msg) = AnkNetworkMsg::new(&raw) { - match msg.topic { - AnkTopic::Faucet => { - match Txid::from_str(msg.content) { - Ok(txid) => { - // return the txid for verification - Some(txid.to_string()) - }, - Err(e) => { - log::error!("Invalid txid with a \"faucet\" message: {}", e.to_string()); - None - } - } - } - } +pub fn get_outpoints_for_user(pre_id: String) -> ApiResult { + let connected_users = lock_connected_users()?; + let user = connected_users.get(&pre_id).ok_or(ApiError { + message: "Can't find user".to_owned(), + })?; + Ok(outputs_list(user.get_all_outputs())) +} + +#[wasm_bindgen] +pub fn is_tx_owned_by_user(pre_id: String, tx: String) -> ApiResult { + let transaction = deserialize::(&Vec::from_hex(&tx)?)?; + let txid = transaction.txid(); + let connected_users = lock_connected_users()?; + let user = connected_users.get(&pre_id).ok_or(ApiError { + message: "Can't find user".to_owned(), + })?; + + if let Some(_) = user + .recover + .get_outputs() + .to_outpoints_list() + .iter() + .find(|(outpoint, output)| outpoint.txid == txid) + { + Ok(true) } else { - log::debug!("Can't parse message as a valid 4nk message: {}", raw); - None + Ok(false) } } + +#[derive(Tsify, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +#[allow(non_camel_case_types)] +pub struct createNotificationTransactionReturn { + pub transaction: String, + pub spaddress2secret: HashMap, +} + +#[wasm_bindgen] +pub fn create_notification_transaction( + user_pre_id: String, + recipient: String, + message: String, +) -> ApiResult { + let sp_address: SilentPaymentAddress = recipient.try_into()?; + + let (transaction, notification_information) = + create_transaction_for_address(user_pre_id, sp_address, message)?; + + // The secret is an ecc point and *must* be hashed to produce a proper ecdh secret + // For now we propose to implement a tagged hash for it + // It could be interesting to add some piece of data to allow for the derivation of multiple secrets + + let spaddress2secret = notification_information + .into_iter() + .map(|(address, shared_pubkey)| { + let shared_secret = AnkSharedSecret::new_from_public_key(shared_pubkey); + (address.into(), shared_secret.to_string()) + }) + .collect(); + + Ok(createNotificationTransactionReturn { + transaction: serialize(&transaction).to_lower_hex_string(), + spaddress2secret, + }) +} diff --git a/crates/sp_client/src/silentpayments.rs b/crates/sp_client/src/silentpayments.rs index 14964f6..cee9658 100644 --- a/crates/sp_client/src/silentpayments.rs +++ b/crates/sp_client/src/silentpayments.rs @@ -1,8 +1,14 @@ use std::collections::HashMap; -use anyhow::Result; +use anyhow::{Error, Result}; +use rand::Rng; +use sp_backend::bitcoin::policy::DUST_RELAY_TX_FEE; +use sp_backend::bitcoin::secp256k1::ecdh::SharedSecret; +use sp_backend::bitcoin::{block, Amount, OutPoint}; +use sp_backend::silentpayments::sending::SilentPaymentAddress; use sp_backend::silentpayments::utils::receiving::calculate_shared_secret; +use sp_backend::spclient::{OutputList, OwnedOutput, Recipient, SpClient}; use sp_backend::{ bitcoin::{ secp256k1::{PublicKey, Scalar, XOnlyPublicKey}, @@ -15,9 +21,79 @@ use crate::user::{lock_connected_users, CONNECTED_USERS}; type FoundOutputs = HashMap, HashMap>; -pub fn check_transaction(tx: Transaction, tweak_data: PublicKey) -> Result { +type NotificationInformation = (Transaction, Vec<(SilentPaymentAddress, PublicKey)>); + +pub fn create_transaction_for_address( + send_as: String, + sp_address: SilentPaymentAddress, + message: String, +) -> Result { let connected_users = lock_connected_users()?; + let sender = connected_users + .get(&send_as) + .ok_or(Error::msg("Unknown sender"))?; + + let sp_wallet = if sp_address.is_testnet() { + &sender.recover + } else { + if let Some(main) = &sender.main { + main + } else { + return Err(Error::msg("Can't spend on mainnet")); + } + }; + + let available_outpoints = sender.recover.get_outputs().to_spendable_list(); + + // Here we need to add more heuristics about which outpoint we spend + // For now let's keep it simple + + let mut inputs: HashMap = HashMap::new(); + + let total_available = + available_outpoints + .into_iter() + .try_fold(Amount::from_sat(0), |acc, (outpoint, output)| { + let new_total = acc + output.amount; + inputs.insert(outpoint, output); + if new_total > Amount::from_sat(1000) { + Err(new_total) + } else { + Ok(new_total) + } + }); + + match total_available { + Err(total) => log::debug!("Spending {} outputs totaling {} sats", inputs.len(), total), + Ok(_) => return Err(Error::msg("Not enought fund available")), + } + + let recipient = Recipient { + address: sp_address.into(), + amount: Amount::from_sat(1000), + nb_outputs: 1, + }; + + let mut new_psbt = sp_wallet + .get_client() + .create_new_psbt(inputs, vec![recipient], None)?; + log::debug!("Created psbt: {}", new_psbt); + SpClient::set_fees(&mut new_psbt, Amount::from_sat(1000), sp_address.into())?; + let shared_secrets: Vec<(SilentPaymentAddress, PublicKey)> = + sp_wallet.get_client().fill_sp_outputs(&mut new_psbt)?; + log::debug!("Definitive psbt: {}", new_psbt); + let mut aux_rand = [0u8; 32]; + rand::thread_rng().fill(&mut aux_rand); + let mut signed = sp_wallet.get_client().sign_psbt(new_psbt, &aux_rand)?; + log::debug!("signed psbt: {}", signed); + SpClient::finalize_psbt(&mut signed)?; + + let final_tx = signed.extract_tx()?; + + Ok((final_tx, shared_secrets)) +} + pub fn check_transaction( tx: &Transaction, blockheight: u32, diff --git a/src/services.ts b/src/services.ts index 593557b..1643b09 100644 --- a/src/services.ts +++ b/src/services.ts @@ -740,6 +740,30 @@ class Services { return null; } } + + public async notify_address_for_message(sp_address: string, message: string): Promise { + const services = await Services.getInstance(); + let user: User; + try { + let possibleUser = await services.getUserInfo(); + if (!possibleUser) { + console.error("No user loaded, please first create a new user or login"); + return null; + } else { + user = possibleUser; + } + } catch (error) { + throw error; + } + + try { + let notificationInfo: createNotificationTransactionReturn = services.sdkClient.create_notification_transaction(user, sp_address, message); + return notificationInfo; + } catch { + console.error("Failed to create notification transaction for user", user); + return null + } + } } export default Services;