From 1f8da7ea61b86e2d6c167caab8890591af450f5c Mon Sep 17 00:00:00 2001 From: Sosthene Date: Wed, 22 May 2024 10:22:53 +0200 Subject: [PATCH] Refactor silentpayments --- src/silentpayments.rs | 155 ++++++++++++++++++++++++------------------ 1 file changed, 88 insertions(+), 67 deletions(-) diff --git a/src/silentpayments.rs b/src/silentpayments.rs index d7c835e..b7e2023 100644 --- a/src/silentpayments.rs +++ b/src/silentpayments.rs @@ -1,39 +1,16 @@ use std::collections::HashMap; +use std::str::FromStr; 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 rand::{thread_rng, Rng, RngCore}; +use sp_client::bitcoin::psbt::raw; +use sp_client::bitcoin::{Psbt, Transaction}; +use sp_client::bitcoin::{Amount, OutPoint}; +use sp_client::bitcoin::consensus::{deserialize, serialize}; 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() -// } -// } +use sp_client::constants; pub fn create_transaction(sp_address: SilentPaymentAddress, sp_wallet: &SpWallet, fee_rate: Amount) -> Result { let available_outpoints = sp_wallet.get_outputs().to_spendable_list(); @@ -77,7 +54,7 @@ pub fn create_transaction(sp_address: SilentPaymentAddress, sp_wallet: &SpWallet // 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); + // let shared_point = shared_secret_point(&sp_address.get_scan_key(), &partial_secret); sp_wallet .get_client() @@ -92,12 +69,66 @@ pub fn create_transaction(sp_address: SilentPaymentAddress, sp_wallet: &SpWallet Ok(final_tx) } -pub fn create_transaction_for_address_with_shared_secret( - sp_address: SilentPaymentAddress, +pub fn create_transaction_spend_outpoint( + outpoint: &OutPoint, sp_wallet: &SpWallet, - message: Option, + recipient: Recipient, + fee_rate: Amount +) -> Result { + let available_outpoints = sp_wallet.get_outputs().to_spendable_list(); + + let mut inputs: HashMap = HashMap::new(); + let mut total_available = Amount::from_sat(0); + let (must_outpoint, must_output) = available_outpoints.get_key_value(outpoint).ok_or_else(|| Error::msg("Mandatory outpoint unknown"))?; + total_available += must_output.amount; + inputs.insert(*must_outpoint, must_output.clone()); + + for (outpoint, output) in available_outpoints { + if total_available > Amount::from_sat(1000) { + break; + } + total_available += output.amount; + inputs.insert(outpoint, output); + } + + if total_available < Amount::from_sat(1000) { + return Err(Error::msg("Not enough available funds")); + } + + // create a dummy commitment + let mut buf = [0u8;64]; + thread_rng().fill_bytes(&mut buf); + + let mut new_psbt = sp_wallet.get_client().create_new_psbt( + inputs, + vec![recipient], + Some(&buf), + )?; + + 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)?; + + sp_wallet + .get_client() + .fill_sp_outputs(&mut new_psbt, partial_secret)?; + let mut aux_rand = [0u8; 32]; + thread_rng().fill(&mut aux_rand); + let mut signed = sp_wallet.get_client().sign_psbt(new_psbt, &aux_rand)?; + SpClient::finalize_psbt(&mut signed)?; + + Ok(signed) +} + +pub fn create_transaction_for_address_with_shared_secret( + recipient: Recipient, + sp_wallet: &SpWallet, + message: Option<&str>, fee_rate: Amount, -) -> Result<(Transaction, AnkSharedSecret)> { +) -> Result { let available_outpoints = sp_wallet.get_outputs().to_spendable_list(); // Here we need to add more heuristics about which outpoint we spend @@ -118,16 +149,10 @@ pub fn create_transaction_for_address_with_shared_secret( 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()), + message.map(|m| m.as_bytes()), )?; let change_addr = sp_wallet.get_client().sp_receiver.get_change_address(); @@ -137,10 +162,6 @@ pub fn create_transaction_for_address_with_shared_secret( .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)?; @@ -149,30 +170,30 @@ pub fn create_transaction_for_address_with_shared_secret( 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))) + Ok(signed.to_string()) } -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 map_outputs_to_sp_address(psbt_str: &str) -> Result>> { + let psbt = Psbt::from_str(&psbt_str)?; -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); + let mut res: HashMap> = HashMap::new(); + for (i, output) in psbt.outputs.iter().enumerate() { + if let Some(value) = output.proprietary.get(&raw::ProprietaryKey { + prefix: constants::PSBT_SP_PREFIX.as_bytes().to_vec(), + subtype: constants::PSBT_SP_SUBTYPE, + key: constants::PSBT_SP_ADDRESS_KEY.as_bytes().to_vec(), + }) { + let sp_address = SilentPaymentAddress::try_from(deserialize::(value)?)?; + if let Some(vouts) = res.get_mut::(&sp_address.into()) { + vouts.push(i); + } else { + res.insert(sp_address.into(), vec![i]); + } + } else { + // Not a sp output + continue; + } } - return Err(Error::msg("No new outputs found")); + Ok(res) }