sdk_common/src/silentpayments.rs

122 lines
3.9 KiB
Rust

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<Txid, Vec<AnkSharedSecret>>);
// impl ScannedTransaction {
// pub fn new() -> Self {
// Self(HashMap::new())
// }
// pub fn get(&self) -> &HashMap<Txid, Vec<AnkSharedSecret>> {
// &self.0
// }
// pub fn get_mut(&mut self) -> &mut HashMap<Txid, Vec<AnkSharedSecret>> {
// &mut self.0
// }
// pub fn to_inner(&self) -> HashMap<Txid, Vec<AnkSharedSecret>> {
// self.0.clone()
// }
// }
pub fn create_transaction_for_address_with_shared_secret(
sp_address: SilentPaymentAddress,
sp_wallet: &SpWallet,
message: Option<String>,
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<OutPoint, OwnedOutput> = 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<String> {
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"));
}