diff --git a/crates/sp_client/src/api.rs b/crates/sp_client/src/api.rs index 2788999..c9b80b6 100644 --- a/crates/sp_client/src/api.rs +++ b/crates/sp_client/src/api.rs @@ -3,15 +3,18 @@ use std::collections::HashMap; use std::str::FromStr; use std::sync::{Mutex, OnceLock, PoisonError}; +use log::debug; use rand::Rng; use anyhow::Error as AnyhowError; +use sdk_common::crypto::AnkSharedSecret; use serde_json::Error as SerdeJsonError; use shamir::SecretData; -use sp_backend::bitcoin::consensus::deserialize; -use sp_backend::bitcoin::hex::{FromHex, HexToBytesError}; +use sp_backend::bitcoin::consensus::{deserialize, serialize}; +use sp_backend::bitcoin::hex::{parse, DisplayHex, FromHex, HexToBytesError}; +use sp_backend::bitcoin::secp256k1::ecdh::SharedSecret; use sp_backend::bitcoin::secp256k1::{PublicKey, SecretKey}; -use sp_backend::bitcoin::{Transaction, Txid}; +use sp_backend::bitcoin::{OutPoint, Transaction, Txid}; use sp_backend::silentpayments::Error as SpError; use serde::{Deserialize, Serialize}; @@ -20,12 +23,13 @@ use tsify::Tsify; use wasm_bindgen::convert::FromWasmAbi; use wasm_bindgen::prelude::*; -use sp_backend::spclient::{derive_keys_from_seed, OutputList, SpClient}; +use sdk_common::network::{AnkFlag, AnkNetworkMsg, NewTxMessage}; + +use sp_backend::spclient::{derive_keys_from_seed, OutputList, OwnedOutput, SpClient}; use sp_backend::spclient::{SpWallet, SpendKey}; use crate::images; -use crate::network::{BitcoinNetworkMsg, BitcoinTopic, AnkNetworkMsg, AnkTopic}; -use crate::silentpayments::check_transaction; +use crate::silentpayments::{check_transaction, create_transaction_for_address}; use crate::user::{lock_connected_users, User, UserWallets, CONNECTED_USERS}; use crate::process::Process; @@ -264,7 +268,7 @@ impl shamir_shares { } #[derive(Debug, Tsify, Serialize, Deserialize)] -#[tsify(from_wasm_abi)] +#[tsify(from_wasm_abi, into_wasm_abi)] #[allow(non_camel_case_types)] pub struct outputs_list(Vec); @@ -296,30 +300,58 @@ pub fn login_user( #[wasm_bindgen] pub fn check_transaction_for_silent_payments( tx_hex: String, + blockheight: u32, tweak_data_hex: String, -) -> ApiResult<()> { +) -> ApiResult { let tx = deserialize::(&Vec::from_hex(&tx_hex)?)?; let tweak_data = PublicKey::from_str(&tweak_data_hex)?; - check_transaction(tx, tweak_data); + let updated_user = check_transaction(&tx, blockheight, tweak_data)?; - Ok(()) + Ok(updated_user) +} + +#[derive(Tsify, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +#[allow(non_camel_case_types)] +pub struct parseNetworkMsgReturn { + topic: String, + message: String, } #[wasm_bindgen] -pub fn parse_bitcoin_network_msg(msg: Vec) -> ApiResult<()> { - let parsed_msg = BitcoinNetworkMsg::new(&msg)?; - - match parsed_msg.topic { - BitcoinTopic::RawTx => { - let tx = deserialize::(parsed_msg.data)?; - let tweak_data = PublicKey::from_slice(parsed_msg.addon)?; - check_transaction(tx, tweak_data); +pub fn parse_network_msg(raw: String) -> ApiResult { + if let Ok(ank_msg) = serde_json::from_str::(&raw) { + match ank_msg.flag { + AnkFlag::NewTx => { + let tx_message = serde_json::from_str::(&ank_msg.content)?; + let tx = deserialize::(&Vec::from_hex(&tx_message.transaction)?)?; + if tx_message.tweak_data.is_none() { + return Err(ApiError { + message: "Missing tweak_data".to_owned(), + }); + } + let partial_tweak = PublicKey::from_str(&tx_message.tweak_data.unwrap())?; + let txid = check_transaction(&tx, 0, partial_tweak)?; + return Ok(parseNetworkMsgReturn { + topic: AnkFlag::NewTx.as_str().to_owned(), + message: txid, + }); + } + AnkFlag::Faucet => unimplemented!(), + AnkFlag::Error => { + return Ok(parseNetworkMsgReturn { + topic: AnkFlag::Error.as_str().to_owned(), + message: ank_msg.content.to_owned(), + }) + } + _ => unimplemented!(), } - BitcoinTopic::RawBlock => (), + } else { + Err(ApiError { + message: format!("Can't parse message as a valid 4nk message: {}", raw), + }) } - - Ok(()) } #[wasm_bindgen] diff --git a/crates/sp_client/src/silentpayments.rs b/crates/sp_client/src/silentpayments.rs index 5ab45d4..14964f6 100644 --- a/crates/sp_client/src/silentpayments.rs +++ b/crates/sp_client/src/silentpayments.rs @@ -18,59 +18,33 @@ type FoundOutputs = HashMap, HashMap>; pub fn check_transaction(tx: Transaction, tweak_data: PublicKey) -> Result { let connected_users = lock_connected_users()?; - let pubkeys_to_check: HashMap = (0u32..) - .zip(tx.output) - .filter_map(|(i, o)| { - if o.script_pubkey.is_p2tr() { - let xonly = XOnlyPublicKey::from_slice(&o.script_pubkey.as_bytes()[2..]) - .expect("Transaction is invalid"); - Some((xonly, i)) - } else { - None - } - }) - .collect(); +pub fn check_transaction( + tx: &Transaction, + blockheight: u32, + tweak_data: PublicKey, +) -> Result { + let connected_users = lock_connected_users()?; + let txid = tx.txid().to_string(); // Check the transaction for all connected users for (pre_id, keys) in connected_users.clone() { - let recover = keys.recover; - let shared_secret = - calculate_shared_secret(tweak_data, recover.get_client().get_scan_key())?; - let res = recover - .get_client() - .sp_receiver - .scan_transaction(&shared_secret, pubkeys_to_check.keys().cloned().collect())?; - - if res.len() > 0 { - return Ok(res); + let mut recover = keys.recover; + if recover.update_wallet_with_transaction(tx, blockheight, tweak_data)? > 0 { + return Ok(txid); } - if let Some(main) = keys.main { - let shared_secret = - calculate_shared_secret(tweak_data, main.get_client().get_scan_key())?; - let res = main - .get_client() - .sp_receiver - .scan_transaction(&shared_secret, pubkeys_to_check.keys().cloned().collect())?; - - if res.len() > 0 { - return Ok(res); + if let Some(mut main) = keys.main { + if main.update_wallet_with_transaction(tx, blockheight, tweak_data)? > 0 { + return Ok(txid); } } - if let Some(revoke) = keys.revoke { - let shared_secret = - calculate_shared_secret(tweak_data, revoke.get_client().get_scan_key())?; - let res = revoke - .get_client() - .sp_receiver - .scan_transaction(&shared_secret, pubkeys_to_check.keys().cloned().collect())?; - - if res.len() > 0 { - return Ok(res); + if let Some(mut revoke) = keys.revoke { + if revoke.update_wallet_with_transaction(tx, blockheight, tweak_data)? > 0 { + return Ok(txid); } } } - Ok(HashMap::new()) + return Err(Error::msg("No new outputs found")); } diff --git a/src/services.ts b/src/services.ts index e7de153..b7952d4 100644 --- a/src/services.ts +++ b/src/services.ts @@ -177,18 +177,6 @@ class Services { return imageBytes; } - public async parseBitcoinMessage(raw: Blob): Promise { - try { - const buffer = await raw.arrayBuffer(); - const uint8Array = new Uint8Array(buffer); - const msg: string = this.sdkClient.parse_bitcoin_network_msg(uint8Array); - return msg; - } catch (error) { - console.error("Error processing the blob:", error); - return null; - } - } - public async displayRevoke(): Promise { const services = await Services.getInstance(); await services.revokeInjectHtml(); @@ -211,30 +199,15 @@ class Services { services.attachSubmitListener("form4nk", services.updateAnId); } - public async parse4nkMessage(raw: string): Promise { - const msg: string = this.sdkClient.parse_4nk_msg(raw); + public async parseNetworkMessage(raw: string): Promise { + const services = await Services.getInstance(); + try { + const msg: parseNetworkMsgReturn = services.sdkClient.parse_network_msg(raw); return msg; - } - - public injectUpdateAnIdHtml(bodyToInject: string, styleToInject: string, scriptToInject: string) { - console.log("JS html : "+bodyToInject); - const body = document.getElementsByTagName('body')[0]; - - if (!body) { - console.error("No body tag"); - return; + } catch (error) { + console.error(error); + return null; } - body.innerHTML = styleToInject + bodyToInject; - - const script = document.createElement("script"); - script.innerHTML = scriptToInject; - document.body.appendChild(script); - script.onload = () => { - console.log('Script loaded successfuly'); - }; - script.onerror = () => { - console.log('Error loading script'); - }; } public async updateAnId(event: Event): Promise { @@ -288,11 +261,13 @@ class Services { return []; } } - - public async checkTransaction(tx: string): Promise { + public async checkTransaction(tx: string, tweak_data: string, blkheight: number): Promise { const services = await Services.getInstance(); + try { - return services.sdkClient.check_network_transaction(tx); + const updated_user: string = services.sdkClient.check_transaction_for_silent_payments(tx, blkheight, tweak_data); + await services.updateOwnedOutputsForUser(updated_user); + return updated_user; } catch (error) { console.error(error); return null; @@ -647,7 +622,11 @@ class Services { return null; } try { - connection.sendMessage('faucet'+spaddress); + const flag: AnkFlag = "Faucet"; + const faucetMsg: FaucetMessage = { + 'sp_address': spaddress + } + connection.sendMessage(flag, JSON.stringify(faucetMsg)); } catch (error) { console.error("Failed to obtain tokens with relay ", connection.getUrl()); return null; diff --git a/src/websockets.ts b/src/websockets.ts index 72d8c80..c73e7dd 100644 --- a/src/websockets.ts +++ b/src/websockets.ts @@ -1,5 +1,5 @@ import Services from "./services"; -// import * as mempool from "./mempool"; +import { AnkFlag, AnkNetworkMsg, parseNetworkMsgReturn } from "../dist/pkg/sdk_client"; class WebSocketClient { private ws: WebSocket; @@ -25,28 +25,18 @@ class WebSocketClient { console.log(msgData); (async () => { - if (msgData instanceof Blob) { - // bitcoin network msg is just bytes - let res = await services.parseBitcoinMessage(msgData); - if (res) { - let ours = await services.checkTransaction(res); - if (ours) { - console.log("Found our utxo in "+res); - } else { - console.log("No utxo found in tx "+res); - } - } else { - console.error("Faile to parse a bitcoin network msg"); - } - } else if (typeof(msgData) === 'string') { - // json strings are 4nk message + if (typeof(msgData) === 'string') { console.log("Received text message: "+msgData); - let res = await services.parse4nkMessage(msgData); + let res = await services.parseNetworkMessage(msgData); if (res) { - console.debug(res); + if (res.topic === 'new_tx') { + // we received a tx + window.alert(`New tx\n${res.message}`); + await services.updateOwnedOutputsForUser(res.message); + } } } else { - console.error("Received an unknown message"); + console.error("Received an invalid message"); } })(); }); @@ -63,9 +53,14 @@ class WebSocketClient { } // Method to send messages - public sendMessage(message: string): void { + public sendMessage(flag: AnkFlag, message: string): void { if (this.ws.readyState === WebSocket.OPEN) { - this.ws.send(message); + const networkMessage: AnkNetworkMsg = { + 'flag': flag, + 'content': message + } + // console.debug("Sending message:", JSON.stringify(networkMessage)); + this.ws.send(JSON.stringify(networkMessage)); } else { console.error('WebSocket is not open. ReadyState:', this.ws.readyState); this.messageQueue.push(message);