use std::collections::HashMap; use anyhow::{Error, Result}; use rand::Rng; use serde::{Deserialize, Serialize}; use sp_client::bitcoin::secp256k1::ecdh::shared_secret_point; use sp_client::bitcoin::{secp256k1::PublicKey, Transaction}; use sp_client::bitcoin::{Amount, OutPoint, Txid}; use sp_client::silentpayments::sending::SilentPaymentAddress; use sp_client::spclient::{OwnedOutput, Recipient, SpClient, SpWallet}; use tsify::Tsify; use crate::crypto::AnkSharedSecret; // #[derive(Debug, Serialize, Deserialize, Tsify)] // #[tsify(into_wasm_abi, from_wasm_abi)] // pub struct ScannedTransaction(HashMap>); // impl ScannedTransaction { // pub fn new() -> Self { // Self(HashMap::new()) // } // pub fn get(&self) -> &HashMap> { // &self.0 // } // pub fn get_mut(&mut self) -> &mut HashMap> { // &mut self.0 // } // pub fn to_inner(&self) -> HashMap> { // self.0.clone() // } // } pub fn create_transaction_for_address_with_shared_secret( sp_address: SilentPaymentAddress, sp_wallet: &SpWallet, message: Option, fee_rate: Amount, ) -> Result<(Transaction, AnkSharedSecret)> { let available_outpoints = sp_wallet.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 mut total_available = Amount::from_sat(0); for (outpoint, output) in available_outpoints { total_available += output.amount; inputs.insert(outpoint, output); if total_available > Amount::from_sat(1000) { break; } } if total_available < Amount::from_sat(1000) { return Err(Error::msg("Not enough available funds")); } 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], message.as_ref().map(|m| m.as_bytes()), )?; let change_addr = sp_wallet.get_client().sp_receiver.get_change_address(); SpClient::set_fees(&mut new_psbt, fee_rate, change_addr)?; let partial_secret = sp_wallet .get_client() .get_partial_secret_from_psbt(&new_psbt)?; // This wouldn't work with many recipients in the same transaction // each address (or more precisely each scan public key) would have its own point let shared_point = shared_secret_point(&sp_address.get_scan_key(), &partial_secret); sp_wallet .get_client() .fill_sp_outputs(&mut new_psbt, partial_secret)?; 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)?; SpClient::finalize_psbt(&mut signed)?; let final_tx = signed.extract_tx()?; Ok((final_tx, AnkSharedSecret::new(shared_point, true))) } pub fn get_shared_point_from_transaction(tx: &Transaction, sp_wallet: &SpWallet, tweak_data: PublicKey) -> Result<[u8;64]> { let b_scan = sp_wallet.get_client().get_scan_key(); let test = sp_client::bitcoin::secp256k1::ecdh::shared_secret_point(&tweak_data, &b_scan); let ecdh_shared= sp_client::silentpayments::utils::receiving::calculate_shared_secret(tweak_data, b_scan)?; let uncompressed = ecdh_shared.serialize_uncompressed(); assert!(test == uncompressed[1..]); Ok(test) } pub fn check_transaction( tx: &Transaction, sp_wallet: &mut SpWallet, blockheight: u32, tweak_data: PublicKey, ) -> Result { let txid = tx.txid().to_string(); if sp_wallet.update_wallet_with_transaction(tx, blockheight, tweak_data)? > 0 { return Ok(txid); } return Err(Error::msg("No new outputs found")); }