Update to latest sdk_common dev + minor refactoring
This commit is contained in:
parent
8e50727880
commit
e5e7496611
570
Cargo.lock
generated
570
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
292
src/faucet.rs
Normal file
292
src/faucet.rs
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
str::FromStr,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bitcoincore_rpc::json::{self as bitcoin_json};
|
||||||
|
use sdk_common::sp_client::bitcoin::secp256k1::{
|
||||||
|
rand::thread_rng, Keypair, Message as Secp256k1Message, Secp256k1, ThirtyTwoByteHash,
|
||||||
|
};
|
||||||
|
use sdk_common::sp_client::bitcoin::{
|
||||||
|
absolute::LockTime,
|
||||||
|
consensus::serialize,
|
||||||
|
hex::{DisplayHex, FromHex},
|
||||||
|
key::TapTweak,
|
||||||
|
script::PushBytesBuf,
|
||||||
|
sighash::{Prevouts, SighashCache},
|
||||||
|
taproot::Signature,
|
||||||
|
transaction::Version,
|
||||||
|
Amount, OutPoint, Psbt, ScriptBuf, TapSighashType, Transaction, TxIn, TxOut, Witness,
|
||||||
|
XOnlyPublicKey,
|
||||||
|
};
|
||||||
|
use sdk_common::{
|
||||||
|
network::{FaucetMessage, NewTxMessage},
|
||||||
|
silentpayments::create_transaction_for_address_with_shared_secret,
|
||||||
|
};
|
||||||
|
|
||||||
|
use sdk_common::sp_client::silentpayments::sending::generate_recipient_pubkeys;
|
||||||
|
use sdk_common::sp_client::silentpayments::utils::sending::calculate_partial_secret;
|
||||||
|
use sdk_common::sp_client::silentpayments::utils::SilentPaymentAddress;
|
||||||
|
use sdk_common::sp_client::spclient::Recipient;
|
||||||
|
|
||||||
|
use anyhow::{Error, Result};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
daemon::Daemon, scan::compute_partial_tweak_to_transaction, MutexExt, SharedDaemon,
|
||||||
|
SilentPaymentWallet, FAUCET_AMT,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn spend_from_core(
|
||||||
|
dest: XOnlyPublicKey,
|
||||||
|
daemon: Arc<Mutex<Daemon>>,
|
||||||
|
) -> Result<(Transaction, Amount)> {
|
||||||
|
let core = daemon
|
||||||
|
.lock()
|
||||||
|
.map_err(|e| Error::msg(format!("Failed to lock daemon: {}", e.to_string())))?;
|
||||||
|
let unspent_list: Vec<bitcoin_json::ListUnspentResultEntry> =
|
||||||
|
core.list_unspent_from_to(None)?;
|
||||||
|
|
||||||
|
if !unspent_list.is_empty() {
|
||||||
|
let network = core.get_network()?;
|
||||||
|
|
||||||
|
let spk = ScriptBuf::new_p2tr_tweaked(dest.dangerous_assume_tweaked());
|
||||||
|
|
||||||
|
let new_psbt = core.create_psbt(&unspent_list, spk, network)?;
|
||||||
|
let processed_psbt = core.process_psbt(new_psbt)?;
|
||||||
|
let finalize_psbt_result = core.finalize_psbt(processed_psbt)?;
|
||||||
|
let final_psbt = Psbt::from_str(&finalize_psbt_result)?;
|
||||||
|
let total_fee = final_psbt.fee()?;
|
||||||
|
let final_tx = final_psbt.extract_tx()?;
|
||||||
|
let fee_rate = total_fee
|
||||||
|
.checked_div(final_tx.weight().to_vbytes_ceil())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok((final_tx, fee_rate))
|
||||||
|
} else {
|
||||||
|
// we don't have enough available coins to pay for this faucet request
|
||||||
|
Err(Error::msg("No spendable outputs"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn faucet_send(
|
||||||
|
sp_address: SilentPaymentAddress,
|
||||||
|
commitment: &str,
|
||||||
|
sp_wallet: Arc<SilentPaymentWallet>,
|
||||||
|
shared_daemon: SharedDaemon,
|
||||||
|
) -> Result<Transaction> {
|
||||||
|
let mut first_tx: Option<Transaction> = None;
|
||||||
|
let final_tx: Transaction;
|
||||||
|
|
||||||
|
// do we have a sp output available ?
|
||||||
|
let available_outpoints = sp_wallet.get_wallet()?.get_outputs().to_spendable_list();
|
||||||
|
|
||||||
|
let available_amt = available_outpoints
|
||||||
|
.iter()
|
||||||
|
.fold(Amount::from_sat(0), |acc, (_, x)| acc + x.amount);
|
||||||
|
|
||||||
|
// If we don't have at least 4 times the amount we need to send, we take some reserves out
|
||||||
|
if available_amt > FAUCET_AMT.checked_mul(4).unwrap() {
|
||||||
|
let mut total_amt = Amount::from_sat(0);
|
||||||
|
let mut inputs = HashMap::new();
|
||||||
|
for (outpoint, output) in available_outpoints {
|
||||||
|
total_amt += output.amount;
|
||||||
|
inputs.insert(outpoint, output);
|
||||||
|
if total_amt >= FAUCET_AMT {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let recipient = Recipient {
|
||||||
|
address: sp_address.into(),
|
||||||
|
amount: FAUCET_AMT,
|
||||||
|
nb_outputs: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let fee_estimate = shared_daemon
|
||||||
|
.lock_anyhow()?
|
||||||
|
.estimate_fee(6)
|
||||||
|
.unwrap_or(Amount::from_sat(1000))
|
||||||
|
.checked_div(1000)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
log::debug!("fee estimate for 6 blocks: {}", fee_estimate);
|
||||||
|
|
||||||
|
let wallet = sp_wallet.get_wallet()?;
|
||||||
|
|
||||||
|
let signed_psbt = create_transaction_for_address_with_shared_secret(
|
||||||
|
recipient,
|
||||||
|
&wallet,
|
||||||
|
Some(commitment),
|
||||||
|
fee_estimate,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let psbt = Psbt::from_str(&signed_psbt)?;
|
||||||
|
|
||||||
|
final_tx = psbt.extract_tx()?;
|
||||||
|
} else {
|
||||||
|
// let's try to spend directly from the mining address
|
||||||
|
let secp = Secp256k1::signing_only();
|
||||||
|
let keypair = Keypair::new(&secp, &mut thread_rng());
|
||||||
|
|
||||||
|
// we first spend from core to the pubkey we just created
|
||||||
|
let (core_tx, fee_rate) =
|
||||||
|
spend_from_core(keypair.x_only_public_key().0, shared_daemon.clone())?;
|
||||||
|
|
||||||
|
// check that the first output of the transaction pays to the key we just created
|
||||||
|
debug_assert!(
|
||||||
|
core_tx.output[0].script_pubkey
|
||||||
|
== ScriptBuf::new_p2tr_tweaked(
|
||||||
|
keypair.x_only_public_key().0.dangerous_assume_tweaked()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is ugly and can be streamlined
|
||||||
|
// create a new transaction that spends the newly created UTXO to the sp_address
|
||||||
|
let mut faucet_tx = Transaction {
|
||||||
|
input: vec![TxIn {
|
||||||
|
previous_output: OutPoint::new(core_tx.txid(), 0),
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
output: vec![],
|
||||||
|
version: Version::TWO,
|
||||||
|
lock_time: LockTime::ZERO,
|
||||||
|
};
|
||||||
|
|
||||||
|
// now do the silent payment operations with the final recipient address
|
||||||
|
let partial_secret = calculate_partial_secret(
|
||||||
|
&[(keypair.secret_key(), true)],
|
||||||
|
&[(core_tx.txid().to_string(), 0)],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let ext_output_key: XOnlyPublicKey =
|
||||||
|
generate_recipient_pubkeys(vec![sp_address.into()], partial_secret)?
|
||||||
|
.into_values()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<XOnlyPublicKey>>()
|
||||||
|
.get(0)
|
||||||
|
.expect("Failed to generate keys")
|
||||||
|
.to_owned();
|
||||||
|
let change_sp_address = sp_wallet.get_wallet()?.get_client().get_receiving_address();
|
||||||
|
let change_output_key: XOnlyPublicKey =
|
||||||
|
generate_recipient_pubkeys(vec![change_sp_address], partial_secret)?
|
||||||
|
.into_values()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<XOnlyPublicKey>>()
|
||||||
|
.get(0)
|
||||||
|
.expect("Failed to generate keys")
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let ext_spk = ScriptBuf::new_p2tr_tweaked(ext_output_key.dangerous_assume_tweaked());
|
||||||
|
let change_spk = ScriptBuf::new_p2tr_tweaked(change_output_key.dangerous_assume_tweaked());
|
||||||
|
|
||||||
|
let mut op_return = PushBytesBuf::new();
|
||||||
|
op_return.extend_from_slice(&Vec::from_hex(commitment)?)?;
|
||||||
|
let data_spk = ScriptBuf::new_op_return(op_return);
|
||||||
|
|
||||||
|
// Take some margin to pay for the fees
|
||||||
|
if core_tx.output[0].value < FAUCET_AMT * 4 {
|
||||||
|
return Err(Error::msg("Not enough funds"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let change_amt = core_tx.output[0].value.checked_sub(FAUCET_AMT).unwrap();
|
||||||
|
|
||||||
|
faucet_tx.output.push(TxOut {
|
||||||
|
value: FAUCET_AMT,
|
||||||
|
script_pubkey: ext_spk,
|
||||||
|
});
|
||||||
|
faucet_tx.output.push(TxOut {
|
||||||
|
value: change_amt,
|
||||||
|
script_pubkey: change_spk,
|
||||||
|
});
|
||||||
|
faucet_tx.output.push(TxOut {
|
||||||
|
value: Amount::from_sat(0),
|
||||||
|
script_pubkey: data_spk,
|
||||||
|
});
|
||||||
|
|
||||||
|
// dummy signature only used for fee estimation
|
||||||
|
faucet_tx.input[0].witness.push([1; 64].to_vec());
|
||||||
|
|
||||||
|
let abs_fee = fee_rate
|
||||||
|
.checked_mul(faucet_tx.weight().to_vbytes_ceil())
|
||||||
|
.ok_or_else(|| Error::msg("Fee rate multiplication overflowed"))?;
|
||||||
|
|
||||||
|
// reset the witness to empty
|
||||||
|
faucet_tx.input[0].witness = Witness::new();
|
||||||
|
|
||||||
|
faucet_tx.output[1].value -= abs_fee;
|
||||||
|
|
||||||
|
let first_tx_outputs = vec![core_tx.output[0].clone()];
|
||||||
|
let prevouts = Prevouts::All(&first_tx_outputs);
|
||||||
|
|
||||||
|
let hash_ty = TapSighashType::Default;
|
||||||
|
|
||||||
|
let mut cache = SighashCache::new(&faucet_tx);
|
||||||
|
|
||||||
|
let sighash = cache.taproot_key_spend_signature_hash(0, &prevouts, hash_ty)?;
|
||||||
|
|
||||||
|
let msg = Secp256k1Message::from_digest(sighash.into_32());
|
||||||
|
|
||||||
|
let sig = secp.sign_schnorr_with_rng(&msg, &keypair, &mut thread_rng());
|
||||||
|
let final_sig = Signature { sig, hash_ty };
|
||||||
|
|
||||||
|
faucet_tx.input[0].witness.push(final_sig.to_vec());
|
||||||
|
|
||||||
|
first_tx = Some(core_tx);
|
||||||
|
|
||||||
|
final_tx = faucet_tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(daemon) = shared_daemon.lock() {
|
||||||
|
// broadcast one or two transactions
|
||||||
|
if first_tx.is_some() {
|
||||||
|
daemon.broadcast(&first_tx.unwrap())?;
|
||||||
|
}
|
||||||
|
let txid = daemon.broadcast(&final_tx)?;
|
||||||
|
log::debug!("Sent tx {}", txid);
|
||||||
|
} else {
|
||||||
|
return Err(Error::msg("Failed to lock daemon"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(final_tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_faucet_request(
|
||||||
|
msg: &FaucetMessage,
|
||||||
|
sp_wallet: Arc<SilentPaymentWallet>,
|
||||||
|
shared_daemon: SharedDaemon,
|
||||||
|
) -> Result<NewTxMessage> {
|
||||||
|
let sp_address = SilentPaymentAddress::try_from(msg.sp_address.as_str())?;
|
||||||
|
log::debug!("Sending bootstrap coins to {}", sp_address);
|
||||||
|
// send bootstrap coins to this sp_address
|
||||||
|
let tx = faucet_send(
|
||||||
|
sp_address,
|
||||||
|
&msg.commitment,
|
||||||
|
sp_wallet.clone(),
|
||||||
|
shared_daemon.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// get the tweak
|
||||||
|
let partial_tweak = compute_partial_tweak_to_transaction(&tx, shared_daemon.clone())?;
|
||||||
|
|
||||||
|
// get current blockheight
|
||||||
|
let blkheight: u32 = shared_daemon
|
||||||
|
.lock_anyhow()?
|
||||||
|
.get_current_height()?
|
||||||
|
.try_into()?;
|
||||||
|
|
||||||
|
// update our sp_client with the change output(s)
|
||||||
|
sp_wallet
|
||||||
|
.get_wallet()?
|
||||||
|
.update_wallet_with_transaction(&tx, blkheight, partial_tweak)?;
|
||||||
|
|
||||||
|
log::debug!("updated the wallet");
|
||||||
|
// save to disk
|
||||||
|
sp_wallet.save()?;
|
||||||
|
|
||||||
|
log::debug!("saved the wallet");
|
||||||
|
Ok(NewTxMessage::new(
|
||||||
|
serialize(&tx).to_lower_hex_string(),
|
||||||
|
Some(partial_tweak.to_string()),
|
||||||
|
))
|
||||||
|
}
|
431
src/main.rs
431
src/main.rs
@ -3,6 +3,7 @@ use std::{
|
|||||||
env,
|
env,
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
fs,
|
fs,
|
||||||
|
io::{Read, Write},
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
@ -13,39 +14,23 @@ use std::{
|
|||||||
use bitcoincore_rpc::json::{self as bitcoin_json};
|
use bitcoincore_rpc::json::{self as bitcoin_json};
|
||||||
use futures_util::{future, pin_mut, stream::TryStreamExt, FutureExt, StreamExt};
|
use futures_util::{future, pin_mut, stream::TryStreamExt, FutureExt, StreamExt};
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
|
use scan::compute_partial_tweak_to_transaction;
|
||||||
use sdk_common::sp_client::bitcoin::{
|
use sdk_common::sp_client::bitcoin::{
|
||||||
absolute::LockTime,
|
|
||||||
consensus::{deserialize, serialize},
|
consensus::{deserialize, serialize},
|
||||||
hex::{DisplayHex, FromHex},
|
hex::{DisplayHex, FromHex},
|
||||||
key::TapTweak,
|
Amount, Network, Transaction,
|
||||||
script::PushBytesBuf,
|
|
||||||
sighash::{Prevouts, SighashCache},
|
|
||||||
taproot::Signature,
|
|
||||||
transaction::Version,
|
|
||||||
Amount, Network, OutPoint, Psbt, ScriptBuf, TapSighashType, Transaction, TxIn, TxOut, Witness,
|
|
||||||
XOnlyPublicKey,
|
|
||||||
};
|
};
|
||||||
|
use sdk_common::sp_client::silentpayments::utils::Network as SpNetwork;
|
||||||
use sdk_common::sp_client::{
|
use sdk_common::sp_client::{
|
||||||
bitcoin::secp256k1::{
|
bitcoin::secp256k1::rand::{thread_rng, Rng},
|
||||||
rand::{thread_rng, Rng},
|
|
||||||
Keypair, Message as Secp256k1Message, PublicKey, Secp256k1, ThirtyTwoByteHash,
|
|
||||||
},
|
|
||||||
spclient::SpWallet,
|
spclient::SpWallet,
|
||||||
};
|
};
|
||||||
use sdk_common::{
|
use sdk_common::{
|
||||||
error::AnkError,
|
error::AnkError,
|
||||||
network::{AnkFlag, AnkNetworkMsg, FaucetMessage, NewTxMessage},
|
network::{AnkFlag, AnkNetworkMsg, FaucetMessage, NewTxMessage},
|
||||||
silentpayments::create_transaction_for_address_with_shared_secret,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use sdk_common::sp_client::db::{JsonFile, Storage};
|
use sdk_common::sp_client::spclient::{derive_keys_from_seed, SpClient, SpendKey};
|
||||||
use sdk_common::sp_client::silentpayments::sending::generate_recipient_pubkeys;
|
|
||||||
use sdk_common::sp_client::silentpayments::utils::SilentPaymentAddress;
|
|
||||||
use sdk_common::sp_client::silentpayments::utils::receiving::{
|
|
||||||
calculate_tweak_data, get_pubkey_from_input,
|
|
||||||
};
|
|
||||||
use sdk_common::sp_client::silentpayments::utils::sending::calculate_partial_secret;
|
|
||||||
use sdk_common::sp_client::spclient::{derive_keys_from_seed, Recipient, SpClient, SpendKey};
|
|
||||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
|
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
net::{TcpListener, TcpStream},
|
net::{TcpListener, TcpStream},
|
||||||
@ -59,8 +44,10 @@ use zeromq::{Socket, SocketRecv};
|
|||||||
|
|
||||||
mod daemon;
|
mod daemon;
|
||||||
mod electrumclient;
|
mod electrumclient;
|
||||||
|
mod faucet;
|
||||||
mod scan;
|
mod scan;
|
||||||
|
|
||||||
|
use crate::faucet::handle_faucet_request;
|
||||||
use crate::{daemon::Daemon, scan::scan_blocks};
|
use crate::{daemon::Daemon, scan::scan_blocks};
|
||||||
|
|
||||||
type Tx = UnboundedSender<Message>;
|
type Tx = UnboundedSender<Message>;
|
||||||
@ -69,6 +56,61 @@ type PeerMap = Arc<Mutex<HashMap<SocketAddr, Tx>>>;
|
|||||||
|
|
||||||
type SharedDaemon = Arc<Mutex<Daemon>>;
|
type SharedDaemon = Arc<Mutex<Daemon>>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct WalletFile {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WalletFile {
|
||||||
|
fn new(path: PathBuf) -> Self {
|
||||||
|
Self { path }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(&self) -> Result<()> {
|
||||||
|
let parent: PathBuf;
|
||||||
|
if let Some(dir) = self.path.parent() {
|
||||||
|
if !dir.ends_with(".4nk") {
|
||||||
|
return Err(Error::msg("parent dir must be \".4nk\""));
|
||||||
|
}
|
||||||
|
parent = dir.to_path_buf();
|
||||||
|
} else {
|
||||||
|
return Err(Error::msg("wallet file has no parent dir"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that parent exists
|
||||||
|
if !parent.exists() {
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::File::create_new(&self.path)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(&self, new_value: &SpWallet) -> Result<()> {
|
||||||
|
let mut f = fs::File::options()
|
||||||
|
.write(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(&self.path)?;
|
||||||
|
|
||||||
|
let json = serde_json::to_string(new_value)?;
|
||||||
|
f.write_all(json.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(&self) -> Result<SpWallet> {
|
||||||
|
let mut f = fs::File::open(&self.path)?;
|
||||||
|
|
||||||
|
let mut content = vec![];
|
||||||
|
f.read_to_end(&mut content)?;
|
||||||
|
|
||||||
|
let res: SpWallet = serde_json::from_slice(&content)?;
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static MESSAGECACHE: OnceLock<MessageCache> = OnceLock::new();
|
static MESSAGECACHE: OnceLock<MessageCache> = OnceLock::new();
|
||||||
|
|
||||||
const MESSAGECACHEDURATION: Duration = Duration::from_secs(10);
|
const MESSAGECACHEDURATION: Duration = Duration::from_secs(10);
|
||||||
@ -95,9 +137,8 @@ impl MessageCache {
|
|||||||
let store = self.store.lock().unwrap();
|
let store = self.store.lock().unwrap();
|
||||||
store.contains_key(key)
|
store.contains_key(key)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async fn clean_up() {
|
async fn clean_up() {
|
||||||
let cache = MESSAGECACHE.get().unwrap();
|
let cache = MESSAGECACHE.get().unwrap();
|
||||||
|
|
||||||
let mut interval = time::interval(MESSAGECACHEINTERVAL);
|
let mut interval = time::interval(MESSAGECACHEINTERVAL);
|
||||||
@ -127,6 +168,7 @@ async fn clean_up() {
|
|||||||
store.remove(&key);
|
store.remove(&key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FAUCET_AMT: Amount = Amount::from_sat(100_000);
|
const FAUCET_AMT: Amount = Amount::from_sat(100_000);
|
||||||
@ -145,7 +187,7 @@ impl<T: Debug> MutexExt<T> for Mutex<T> {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SilentPaymentWallet {
|
struct SilentPaymentWallet {
|
||||||
sp_wallet: Mutex<SpWallet>,
|
sp_wallet: Mutex<SpWallet>,
|
||||||
storage: Mutex<JsonFile>,
|
storage: Mutex<WalletFile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SilentPaymentWallet {
|
impl SilentPaymentWallet {
|
||||||
@ -154,7 +196,8 @@ impl SilentPaymentWallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&self) -> Result<()> {
|
pub fn save(&self) -> Result<()> {
|
||||||
self.storage.lock_anyhow()?.save(&self.sp_wallet)
|
let wallet = self.sp_wallet.lock_anyhow()?;
|
||||||
|
self.storage.lock_anyhow()?.save(&wallet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,262 +255,6 @@ fn broadcast_message(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spend_from_core(
|
|
||||||
dest: XOnlyPublicKey,
|
|
||||||
daemon: Arc<Mutex<Daemon>>,
|
|
||||||
) -> Result<(Transaction, Amount)> {
|
|
||||||
let core = daemon
|
|
||||||
.lock()
|
|
||||||
.map_err(|e| Error::msg(format!("Failed to lock daemon: {}", e.to_string())))?;
|
|
||||||
let unspent_list: Vec<bitcoin_json::ListUnspentResultEntry> =
|
|
||||||
core.list_unspent_from_to(None)?;
|
|
||||||
|
|
||||||
if !unspent_list.is_empty() {
|
|
||||||
let network = core.get_network()?;
|
|
||||||
|
|
||||||
let spk = ScriptBuf::new_p2tr_tweaked(dest.dangerous_assume_tweaked());
|
|
||||||
|
|
||||||
let new_psbt = core.create_psbt(&unspent_list, spk, network)?;
|
|
||||||
let processed_psbt = core.process_psbt(new_psbt)?;
|
|
||||||
let finalize_psbt_result = core.finalize_psbt(processed_psbt)?;
|
|
||||||
let final_psbt = Psbt::from_str(&finalize_psbt_result)?;
|
|
||||||
let total_fee = final_psbt.fee()?;
|
|
||||||
let final_tx = final_psbt.extract_tx()?;
|
|
||||||
let fee_rate = total_fee
|
|
||||||
.checked_div(final_tx.weight().to_vbytes_ceil())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Ok((final_tx, fee_rate))
|
|
||||||
} else {
|
|
||||||
// we don't have enough available coins to pay for this faucet request
|
|
||||||
Err(Error::msg("No spendable outputs"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn faucet_send(
|
|
||||||
sp_address: SilentPaymentAddress,
|
|
||||||
commitment: &str,
|
|
||||||
sp_wallet: Arc<SilentPaymentWallet>,
|
|
||||||
shared_daemon: SharedDaemon,
|
|
||||||
) -> Result<Transaction> {
|
|
||||||
let mut first_tx: Option<Transaction> = None;
|
|
||||||
let final_tx: Transaction;
|
|
||||||
|
|
||||||
// do we have a sp output available ?
|
|
||||||
let available_outpoints = sp_wallet.get_wallet()?.get_outputs().to_spendable_list();
|
|
||||||
|
|
||||||
let available_amt = available_outpoints
|
|
||||||
.iter()
|
|
||||||
.fold(Amount::from_sat(0), |acc, (_, x)| acc + x.amount);
|
|
||||||
|
|
||||||
// If we don't have at least 4 times the amount we need to send, we take some reserves out
|
|
||||||
if available_amt > FAUCET_AMT.checked_mul(4).unwrap() {
|
|
||||||
let mut total_amt = Amount::from_sat(0);
|
|
||||||
let mut inputs = HashMap::new();
|
|
||||||
for (outpoint, output) in available_outpoints {
|
|
||||||
total_amt += output.amount;
|
|
||||||
inputs.insert(outpoint, output);
|
|
||||||
if total_amt >= FAUCET_AMT {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let recipient = Recipient {
|
|
||||||
address: sp_address.into(),
|
|
||||||
amount: FAUCET_AMT,
|
|
||||||
nb_outputs: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let fee_estimate = shared_daemon
|
|
||||||
.lock_anyhow()?
|
|
||||||
.estimate_fee(6)
|
|
||||||
.unwrap_or(Amount::from_sat(1000))
|
|
||||||
.checked_div(1000)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
log::debug!("fee estimate for 6 blocks: {}", fee_estimate);
|
|
||||||
|
|
||||||
let wallet = sp_wallet.get_wallet()?;
|
|
||||||
|
|
||||||
let signed_psbt = create_transaction_for_address_with_shared_secret(
|
|
||||||
recipient,
|
|
||||||
&wallet,
|
|
||||||
Some(commitment),
|
|
||||||
fee_estimate,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let psbt = Psbt::from_str(&signed_psbt)?;
|
|
||||||
|
|
||||||
final_tx = psbt.extract_tx()?;
|
|
||||||
} else {
|
|
||||||
// let's try to spend directly from the mining address
|
|
||||||
let secp = Secp256k1::signing_only();
|
|
||||||
let keypair = Keypair::new(&secp, &mut thread_rng());
|
|
||||||
|
|
||||||
// we first spend from core to the pubkey we just created
|
|
||||||
let (core_tx, fee_rate) =
|
|
||||||
spend_from_core(keypair.x_only_public_key().0, shared_daemon.clone())?;
|
|
||||||
|
|
||||||
// check that the first output of the transaction pays to the key we just created
|
|
||||||
debug_assert!(
|
|
||||||
core_tx.output[0].script_pubkey
|
|
||||||
== ScriptBuf::new_p2tr_tweaked(
|
|
||||||
keypair.x_only_public_key().0.dangerous_assume_tweaked()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// This is ugly and can be streamlined
|
|
||||||
// create a new transaction that spends the newly created UTXO to the sp_address
|
|
||||||
let mut faucet_tx = Transaction {
|
|
||||||
input: vec![TxIn {
|
|
||||||
previous_output: OutPoint::new(core_tx.txid(), 0),
|
|
||||||
..Default::default()
|
|
||||||
}],
|
|
||||||
output: vec![],
|
|
||||||
version: Version::TWO,
|
|
||||||
lock_time: LockTime::ZERO,
|
|
||||||
};
|
|
||||||
|
|
||||||
// now do the silent payment operations with the final recipient address
|
|
||||||
let partial_secret = calculate_partial_secret(
|
|
||||||
&[(keypair.secret_key(), true)],
|
|
||||||
&[(core_tx.txid().to_string(), 0)],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let ext_output_key: XOnlyPublicKey =
|
|
||||||
generate_recipient_pubkeys(vec![sp_address.into()], partial_secret)?
|
|
||||||
.into_values()
|
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<XOnlyPublicKey>>()
|
|
||||||
.get(0)
|
|
||||||
.expect("Failed to generate keys")
|
|
||||||
.to_owned();
|
|
||||||
let change_sp_address = sp_wallet.get_wallet()?.get_client().get_receiving_address();
|
|
||||||
let change_output_key: XOnlyPublicKey =
|
|
||||||
generate_recipient_pubkeys(vec![change_sp_address], partial_secret)?
|
|
||||||
.into_values()
|
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<XOnlyPublicKey>>()
|
|
||||||
.get(0)
|
|
||||||
.expect("Failed to generate keys")
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
let ext_spk = ScriptBuf::new_p2tr_tweaked(ext_output_key.dangerous_assume_tweaked());
|
|
||||||
let change_spk = ScriptBuf::new_p2tr_tweaked(change_output_key.dangerous_assume_tweaked());
|
|
||||||
|
|
||||||
let mut op_return = PushBytesBuf::new();
|
|
||||||
op_return.extend_from_slice(&Vec::from_hex(commitment)?)?;
|
|
||||||
let data_spk = ScriptBuf::new_op_return(op_return);
|
|
||||||
|
|
||||||
// Take some margin to pay for the fees
|
|
||||||
if core_tx.output[0].value < FAUCET_AMT * 4 {
|
|
||||||
return Err(Error::msg("Not enough funds"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let change_amt = core_tx.output[0].value.checked_sub(FAUCET_AMT).unwrap();
|
|
||||||
|
|
||||||
faucet_tx.output.push(TxOut {
|
|
||||||
value: FAUCET_AMT,
|
|
||||||
script_pubkey: ext_spk,
|
|
||||||
});
|
|
||||||
faucet_tx.output.push(TxOut {
|
|
||||||
value: change_amt,
|
|
||||||
script_pubkey: change_spk,
|
|
||||||
});
|
|
||||||
faucet_tx.output.push(TxOut {
|
|
||||||
value: Amount::from_sat(0),
|
|
||||||
script_pubkey: data_spk,
|
|
||||||
});
|
|
||||||
|
|
||||||
// dummy signature only used for fee estimation
|
|
||||||
faucet_tx.input[0].witness.push([1; 64].to_vec());
|
|
||||||
|
|
||||||
let abs_fee = fee_rate
|
|
||||||
.checked_mul(faucet_tx.weight().to_vbytes_ceil())
|
|
||||||
.ok_or_else(|| Error::msg("Fee rate multiplication overflowed"))?;
|
|
||||||
|
|
||||||
// reset the witness to empty
|
|
||||||
faucet_tx.input[0].witness = Witness::new();
|
|
||||||
|
|
||||||
faucet_tx.output[1].value -= abs_fee;
|
|
||||||
|
|
||||||
let first_tx_outputs = vec![core_tx.output[0].clone()];
|
|
||||||
let prevouts = Prevouts::All(&first_tx_outputs);
|
|
||||||
|
|
||||||
let hash_ty = TapSighashType::Default;
|
|
||||||
|
|
||||||
let mut cache = SighashCache::new(&faucet_tx);
|
|
||||||
|
|
||||||
let sighash = cache.taproot_key_spend_signature_hash(0, &prevouts, hash_ty)?;
|
|
||||||
|
|
||||||
let msg = Secp256k1Message::from_digest(sighash.into_32());
|
|
||||||
|
|
||||||
let sig = secp.sign_schnorr_with_rng(&msg, &keypair, &mut thread_rng());
|
|
||||||
let final_sig = Signature { sig, hash_ty };
|
|
||||||
|
|
||||||
faucet_tx.input[0].witness.push(final_sig.to_vec());
|
|
||||||
|
|
||||||
first_tx = Some(core_tx);
|
|
||||||
|
|
||||||
final_tx = faucet_tx;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(daemon) = shared_daemon.lock() {
|
|
||||||
// broadcast one or two transactions
|
|
||||||
if first_tx.is_some() {
|
|
||||||
daemon.broadcast(&first_tx.unwrap())?;
|
|
||||||
}
|
|
||||||
let txid = daemon.broadcast(&final_tx)?;
|
|
||||||
debug!("Sent tx {}", txid);
|
|
||||||
} else {
|
|
||||||
return Err(Error::msg("Failed to lock daemon"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(final_tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_faucet_request(
|
|
||||||
msg: &FaucetMessage,
|
|
||||||
sp_wallet: Arc<SilentPaymentWallet>,
|
|
||||||
shared_daemon: SharedDaemon,
|
|
||||||
) -> Result<NewTxMessage> {
|
|
||||||
let sp_address = SilentPaymentAddress::try_from(msg.sp_address.as_str())?;
|
|
||||||
debug!("Sending bootstrap coins to {}", sp_address);
|
|
||||||
// send bootstrap coins to this sp_address
|
|
||||||
let tx = faucet_send(
|
|
||||||
sp_address,
|
|
||||||
&msg.commitment,
|
|
||||||
sp_wallet.clone(),
|
|
||||||
shared_daemon.clone(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// get the tweak
|
|
||||||
let partial_tweak = compute_partial_tweak_to_transaction(&tx, shared_daemon.clone())?;
|
|
||||||
|
|
||||||
// get current blockheight
|
|
||||||
let blkheight: u32 = shared_daemon
|
|
||||||
.lock_anyhow()?
|
|
||||||
.get_current_height()?
|
|
||||||
.try_into()?;
|
|
||||||
|
|
||||||
// update our sp_client with the change output(s)
|
|
||||||
sp_wallet
|
|
||||||
.get_wallet()?
|
|
||||||
.update_wallet_with_transaction(&tx, blkheight, partial_tweak)?;
|
|
||||||
|
|
||||||
debug!("{:?}", sp_wallet);
|
|
||||||
|
|
||||||
debug!("updated the wallet");
|
|
||||||
// save to disk
|
|
||||||
sp_wallet.save()?;
|
|
||||||
|
|
||||||
debug!("saved the wallet");
|
|
||||||
Ok(NewTxMessage::new(
|
|
||||||
serialize(&tx).to_lower_hex_string(),
|
|
||||||
Some(partial_tweak.to_string()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_new_tx_request(new_tx_msg: &mut NewTxMessage, shared_daemon: SharedDaemon) -> Result<()> {
|
fn handle_new_tx_request(new_tx_msg: &mut NewTxMessage, shared_daemon: SharedDaemon) -> Result<()> {
|
||||||
let tx = deserialize::<Transaction>(&Vec::from_hex(&new_tx_msg.transaction)?)?;
|
let tx = deserialize::<Transaction>(&Vec::from_hex(&new_tx_msg.transaction)?)?;
|
||||||
let mempool_accept = shared_daemon.lock_anyhow()?.test_mempool_accept(&tx)?;
|
let mempool_accept = shared_daemon.lock_anyhow()?.test_mempool_accept(&tx)?;
|
||||||
@ -646,47 +433,6 @@ async fn handle_connection(
|
|||||||
peers.lock().unwrap().remove(&addr);
|
peers.lock().unwrap().remove(&addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_partial_tweak_to_transaction(
|
|
||||||
tx: &Transaction,
|
|
||||||
daemon: Arc<Mutex<Daemon>>,
|
|
||||||
) -> Result<PublicKey> {
|
|
||||||
let mut outpoints: Vec<(String, u32)> = Vec::with_capacity(tx.input.len());
|
|
||||||
let mut pubkeys: Vec<PublicKey> = Vec::with_capacity(tx.input.len());
|
|
||||||
for input in tx.input.iter() {
|
|
||||||
outpoints.push((
|
|
||||||
input.previous_output.txid.to_string(),
|
|
||||||
input.previous_output.vout,
|
|
||||||
));
|
|
||||||
let prev_tx = daemon
|
|
||||||
.lock_anyhow()?
|
|
||||||
.get_transaction(&input.previous_output.txid, None)
|
|
||||||
.map_err(|e| Error::msg(format!("Failed to find previous transaction: {}", e)))?;
|
|
||||||
|
|
||||||
if let Some(output) = prev_tx.output.get(input.previous_output.vout as usize) {
|
|
||||||
match get_pubkey_from_input(
|
|
||||||
&input.script_sig.to_bytes(),
|
|
||||||
&input.witness.to_vec(),
|
|
||||||
&output.script_pubkey.to_bytes(),
|
|
||||||
) {
|
|
||||||
Ok(Some(pubkey)) => pubkeys.push(pubkey),
|
|
||||||
Ok(None) => continue,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(Error::msg(format!(
|
|
||||||
"Can't extract pubkey from input: {}",
|
|
||||||
e
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::msg("Transaction with a non-existing input"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let input_pub_keys: Vec<&PublicKey> = pubkeys.iter().collect();
|
|
||||||
let partial_tweak = calculate_tweak_data(&input_pub_keys, &outpoints)?;
|
|
||||||
Ok(partial_tweak)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_new_tx_message(transaction: Vec<u8>, daemon: Arc<Mutex<Daemon>>) -> Result<NewTxMessage> {
|
fn create_new_tx_message(transaction: Vec<u8>, daemon: Arc<Mutex<Daemon>>) -> Result<NewTxMessage> {
|
||||||
// debug!("Creating tx message");
|
// debug!("Creating tx message");
|
||||||
let tx: Transaction = deserialize(&transaction)?;
|
let tx: Transaction = deserialize(&transaction)?;
|
||||||
@ -767,7 +513,15 @@ async fn main() -> Result<()> {
|
|||||||
let network_arg: String = env::args().nth(4).unwrap_or_else(|| "signet".to_owned());
|
let network_arg: String = env::args().nth(4).unwrap_or_else(|| "signet".to_owned());
|
||||||
let core_wallet: Option<String> = env::args().nth(5);
|
let core_wallet: Option<String> = env::args().nth(5);
|
||||||
|
|
||||||
let network = Network::from_core_arg(&network_arg)?;
|
let network: Network = match network_arg.as_str() {
|
||||||
|
"main" => Network::Bitcoin,
|
||||||
|
"test" => Network::Testnet,
|
||||||
|
"signet" => Network::Testnet,
|
||||||
|
"regtest" => Network::Regtest,
|
||||||
|
_ => return Err(Error::msg("Invalid network")),
|
||||||
|
};
|
||||||
|
|
||||||
|
// let network = Network::from_core_arg(&network_arg)?;
|
||||||
|
|
||||||
if network == Network::Bitcoin {
|
if network == Network::Bitcoin {
|
||||||
warn!("Running on mainnet, you're on your own");
|
warn!("Running on mainnet, you're on your own");
|
||||||
@ -777,12 +531,14 @@ async fn main() -> Result<()> {
|
|||||||
.set(MessageCache::new())
|
.set(MessageCache::new())
|
||||||
.expect("Message Cache initialization failed");
|
.expect("Message Cache initialization failed");
|
||||||
|
|
||||||
tokio::spawn(clean_up());
|
|
||||||
|
|
||||||
let peers = PeerMap::new(Mutex::new(HashMap::new()));
|
let peers = PeerMap::new(Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
// Connect the rpc daemon
|
// Connect the rpc daemon
|
||||||
let shared_daemon = Arc::new(Mutex::new(Daemon::connect(core_wallet, rpc_url, network)?));
|
let shared_daemon = Arc::new(Mutex::new(Daemon::connect(
|
||||||
|
core_wallet,
|
||||||
|
rpc_url,
|
||||||
|
Network::from_core_arg(&network_arg)?,
|
||||||
|
)?));
|
||||||
|
|
||||||
let current_tip: u32 = shared_daemon
|
let current_tip: u32 = shared_daemon
|
||||||
.lock_anyhow()?
|
.lock_anyhow()?
|
||||||
@ -791,27 +547,24 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
let mut config_dir = PathBuf::from_str(&env::var("HOME")?)?;
|
let mut config_dir = PathBuf::from_str(&env::var("HOME")?)?;
|
||||||
config_dir.push(".4nk");
|
config_dir.push(".4nk");
|
||||||
let sp_wallet_file = JsonFile::new(&config_dir, &PathBuf::from_str(&wallet_name)?);
|
config_dir.push(&wallet_name);
|
||||||
fs::create_dir_all(config_dir)?;
|
|
||||||
|
let wallet_file = WalletFile::new(config_dir);
|
||||||
|
|
||||||
// load an existing sp_wallet, or create a new one
|
// load an existing sp_wallet, or create a new one
|
||||||
let is_testnet = if network == Network::Bitcoin {
|
let sp_wallet = match wallet_file.load() {
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
};
|
|
||||||
let sp_wallet = match <JsonFile as Storage<SpWallet>>::load(&sp_wallet_file) {
|
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
wallet_file.create()?;
|
||||||
let mut seed = [0u8; 64];
|
let mut seed = [0u8; 64];
|
||||||
thread_rng().fill(&mut seed);
|
thread_rng().fill(&mut seed);
|
||||||
let (scan_sk, spend_sk) = derive_keys_from_seed(&seed, is_testnet)
|
let (scan_sk, spend_sk) =
|
||||||
.expect("Couldn't generate a new sp_wallet");
|
derive_keys_from_seed(&seed, network).expect("Couldn't generate a new sp_wallet");
|
||||||
let new_client = SpClient::new(
|
let new_client = SpClient::new(
|
||||||
wallet_name,
|
wallet_name,
|
||||||
scan_sk,
|
scan_sk,
|
||||||
SpendKey::Secret(spend_sk),
|
SpendKey::Secret(spend_sk),
|
||||||
None,
|
None,
|
||||||
is_testnet,
|
network,
|
||||||
)
|
)
|
||||||
.expect("Failed to create a new SpClient");
|
.expect("Failed to create a new SpClient");
|
||||||
|
|
||||||
@ -842,7 +595,7 @@ async fn main() -> Result<()> {
|
|||||||
let last_scan = sp_wallet.get_outputs().get_last_scan();
|
let last_scan = sp_wallet.get_outputs().get_last_scan();
|
||||||
|
|
||||||
let shared_sp_wallet = Mutex::new(sp_wallet);
|
let shared_sp_wallet = Mutex::new(sp_wallet);
|
||||||
let shared_wallet_storage = Mutex::new(sp_wallet_file);
|
let shared_wallet_storage = Mutex::new(wallet_file);
|
||||||
|
|
||||||
let sp_wallet = Arc::new(SilentPaymentWallet {
|
let sp_wallet = Arc::new(SilentPaymentWallet {
|
||||||
sp_wallet: shared_sp_wallet,
|
sp_wallet: shared_sp_wallet,
|
||||||
@ -868,6 +621,8 @@ async fn main() -> Result<()> {
|
|||||||
let listener = try_socket.expect("Failed to bind");
|
let listener = try_socket.expect("Failed to bind");
|
||||||
debug!("Listening on: {}", listening_addr);
|
debug!("Listening on: {}", listening_addr);
|
||||||
|
|
||||||
|
tokio::spawn(MessageCache::clean_up());
|
||||||
|
|
||||||
// Let's spawn the handling of each connection in a separate task.
|
// Let's spawn the handling of each connection in a separate task.
|
||||||
while let Ok((stream, addr)) = listener.accept().await {
|
while let Ok((stream, addr)) = listener.accept().await {
|
||||||
tokio::spawn(handle_connection(
|
tokio::spawn(handle_connection(
|
||||||
|
47
src/scan.rs
47
src/scan.rs
@ -1,6 +1,6 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use electrum_client::ElectrumApi;
|
use electrum_client::ElectrumApi;
|
||||||
@ -10,11 +10,56 @@ use sdk_common::sp_client::bitcoin::hex::DisplayHex;
|
|||||||
use sdk_common::sp_client::bitcoin::secp256k1::{All, PublicKey, Scalar, Secp256k1, SecretKey};
|
use sdk_common::sp_client::bitcoin::secp256k1::{All, PublicKey, Scalar, Secp256k1, SecretKey};
|
||||||
use sdk_common::sp_client::bitcoin::{BlockHash, OutPoint, Transaction, TxOut, XOnlyPublicKey};
|
use sdk_common::sp_client::bitcoin::{BlockHash, OutPoint, Transaction, TxOut, XOnlyPublicKey};
|
||||||
use sdk_common::sp_client::silentpayments::receiving::Receiver;
|
use sdk_common::sp_client::silentpayments::receiving::Receiver;
|
||||||
|
use sdk_common::sp_client::silentpayments::utils::receiving::{
|
||||||
|
calculate_tweak_data, get_pubkey_from_input,
|
||||||
|
};
|
||||||
use sdk_common::sp_client::spclient::{OutputSpendStatus, OwnedOutput};
|
use sdk_common::sp_client::spclient::{OutputSpendStatus, OwnedOutput};
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
|
|
||||||
|
use crate::daemon::Daemon;
|
||||||
use crate::{electrumclient, MutexExt, SharedDaemon, SilentPaymentWallet};
|
use crate::{electrumclient, MutexExt, SharedDaemon, SilentPaymentWallet};
|
||||||
|
|
||||||
|
pub fn compute_partial_tweak_to_transaction(
|
||||||
|
tx: &Transaction,
|
||||||
|
daemon: Arc<Mutex<Daemon>>,
|
||||||
|
) -> Result<PublicKey> {
|
||||||
|
let mut outpoints: Vec<(String, u32)> = Vec::with_capacity(tx.input.len());
|
||||||
|
let mut pubkeys: Vec<PublicKey> = Vec::with_capacity(tx.input.len());
|
||||||
|
for input in tx.input.iter() {
|
||||||
|
outpoints.push((
|
||||||
|
input.previous_output.txid.to_string(),
|
||||||
|
input.previous_output.vout,
|
||||||
|
));
|
||||||
|
let prev_tx = daemon
|
||||||
|
.lock_anyhow()?
|
||||||
|
.get_transaction(&input.previous_output.txid, None)
|
||||||
|
.map_err(|e| Error::msg(format!("Failed to find previous transaction: {}", e)))?;
|
||||||
|
|
||||||
|
if let Some(output) = prev_tx.output.get(input.previous_output.vout as usize) {
|
||||||
|
match get_pubkey_from_input(
|
||||||
|
&input.script_sig.to_bytes(),
|
||||||
|
&input.witness.to_vec(),
|
||||||
|
&output.script_pubkey.to_bytes(),
|
||||||
|
) {
|
||||||
|
Ok(Some(pubkey)) => pubkeys.push(pubkey),
|
||||||
|
Ok(None) => continue,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(Error::msg(format!(
|
||||||
|
"Can't extract pubkey from input: {}",
|
||||||
|
e
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::msg("Transaction with a non-existing input"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let input_pub_keys: Vec<&PublicKey> = pubkeys.iter().collect();
|
||||||
|
let partial_tweak = calculate_tweak_data(&input_pub_keys, &outpoints)?;
|
||||||
|
Ok(partial_tweak)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_script_to_secret_map(
|
fn get_script_to_secret_map(
|
||||||
sp_receiver: &Receiver,
|
sp_receiver: &Receiver,
|
||||||
tweak_data_vec: Vec<String>,
|
tweak_data_vec: Vec<String>,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user