diff --git a/src/main.rs b/src/main.rs index 5cdb014..74a0ebe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,9 @@ use std::{ - collections::HashMap, - env, - io::Error as IoError, - net::SocketAddr, - sync::{Arc, Mutex, MutexGuard}, + collections::HashMap, env, io::Error as IoError, net::SocketAddr, sync::{Arc, Mutex} }; -use bitcoin::{consensus::deserialize, key::TapTweak, secp256k1::PublicKey, OutPoint, ScriptBuf, XOnlyPublicKey}; +use bitcoin::{absolute::LockTime, consensus::deserialize, key::TapTweak, secp256k1::PublicKey, transaction::Version, Amount, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid, XOnlyPublicKey}; +use bitcoin::secp256k1::{Message as Secp256k1Message, ThirtyTwoByteHash}; use bitcoincore_rpc::json as bitcoin_json; use futures_util::{future, pin_mut, stream::TryStreamExt, FutureExt, StreamExt}; @@ -35,7 +32,127 @@ type Tx = UnboundedSender; type PeerMap = Arc>>; -async fn handle_connection(peer_map: PeerMap, raw_stream: TcpStream, addr: SocketAddr) { +const FAUCET_AMT: Amount = Amount::from_sat(1000); + +fn spend_from_core(dest: XOnlyPublicKey, daemon: Arc>) -> Result { + let core = daemon.lock().map_err(|e| Error::msg(format!("Failed to lock daemon: {}", e.to_string())))?; + let unspent_list: Vec = core.list_unspent_from_to(Some(101), None)?; // we're (probably) spending coinbase, so let's be extra cautious and not spend before 101 confirmations + + // just take the first of the list + if let Some(unspent) = unspent_list.get(0) { + let network = core.get_network()?; + + let spk = ScriptBuf::new_p2tr_tweaked(dest.dangerous_assume_tweaked()); + + let new_psbt = core.create_psbt(unspent.clone(), spk, network)?; + let processed_psbt = core.process_psbt(new_psbt)?; + let tx = core.finalize_psbt(processed_psbt)?; + let final_tx = deserialize::(&tx)?; + + let _ = core.broadcast(&final_tx)?; + + Ok(final_tx) + } else { + // we have 0 spendable outputs + Err(Error::msg("No spendable outputs")) + } +} + +fn faucet_send(sp_address: SilentPaymentAddress, sp_client: Arc>, daemon: Arc>) -> Result { + let wallet = sp_client.lock().map_err(|e| Error::msg(format!("{}", e.to_string())))?; + let final_tx: Transaction; + if let Some(utxo) = wallet.list_outpoints() + .into_iter() + // do we have a sp output available ? + .find(|o| o.spend_status == OutputSpendStatus::Unspent) { + // create a new transaction with an available output + let recipient = Recipient { + address: sp_address.into(), + amount: utxo.amount, + nb_outputs: 1 + }; + let mut new_psbt = wallet.create_new_psbt(vec![utxo], vec![recipient], None)?; + SpClient::set_fees(&mut new_psbt, 1, sp_address.into())?; + wallet.fill_sp_outputs(&mut new_psbt)?; + let mut signed = wallet.sign_psbt(new_psbt)?; + SpClient::finalize_psbt(&mut signed)?; + + final_tx = signed.extract_tx()?; + } else { + drop(wallet); // we don't want to keep locking it + // let's try to spend directly from the mining address + let secp = bitcoin::secp256k1::Secp256k1::signing_only(); + let keypair = bitcoin::secp256k1::Keypair::new(&secp, &mut thread_rng()); + + // we first spend from core to the pubkey we just created + let first_tx = spend_from_core(keypair.x_only_public_key().0, daemon.clone())?; + + // check that the first output of the transaction pays to the key we just created + assert!(first_tx.output[0].script_pubkey == ScriptBuf::new_p2tr_tweaked(keypair.x_only_public_key().0.dangerous_assume_tweaked())); + + // 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(first_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 a_sum = SpClient::get_a_sum_secret_keys(&vec![keypair.secret_key()]); + let prev_outpoint = faucet_tx.input[0].previous_output; + let outpoints_hash = silentpayments::utils::hash_outpoints(&vec![(prev_outpoint.txid.to_string(), prev_outpoint.vout)], keypair.public_key())?; + let partial_secret = silentpayments::utils::sending::sender_calculate_partial_secret(a_sum, outpoints_hash)?; + + let ext_output_key = silentpayments::sending::generate_recipient_pubkey(sp_address.into(), partial_secret)?; + let change_sp_address = sp_client.lock() + .map_err(|e| Error::msg(format!("Failed to lock sp_client: {}", e.to_string())))? + .get_receiving_address(); + let change_output_key = silentpayments::sending::generate_recipient_pubkey(change_sp_address, partial_secret)?; + + 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()); + + faucet_tx.output.push(TxOut { + value: FAUCET_AMT, + script_pubkey: ext_spk + }); + faucet_tx.output.push(TxOut { + value: first_tx.output[0].value - FAUCET_AMT, + script_pubkey: change_spk + }); + + let first_tx_outputs = vec![first_tx.output[0].clone()]; + let prevouts = bitcoin::sighash::Prevouts::All(&first_tx_outputs); + + let hash_ty = bitcoin::TapSighashType::Default; + + let mut cache = bitcoin::sighash::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 = bitcoin::taproot::Signature{ sig, hash_ty }; + + faucet_tx.input[0].witness.push(final_sig.to_vec()); + + final_tx = faucet_tx; + } + daemon.lock() + .map_err(|e| Error::msg(format!("{}", e.to_string())))? + .broadcast(&final_tx)?; + + Ok(final_tx.txid()) +} + +async fn handle_connection(peer_map: PeerMap, raw_stream: TcpStream, addr: SocketAddr, sp_client: Arc>, daemon: Arc>) { debug!("Incoming TCP connection from: {}", addr); let ws_stream = tokio_tungstenite::accept_async(raw_stream) @@ -52,16 +169,54 @@ async fn handle_connection(peer_map: PeerMap, raw_stream: TcpStream, addr: Socke let broadcast_incoming = incoming.try_for_each({ let peer_map = peer_map.clone(); move |msg| { - let peers = peer_map.lock().unwrap(); + match msg.is_text() { + true => { + let msg_str = msg.to_string(); + match msg_str.starts_with("faucet") { + true => { + match SilentPaymentAddress::try_from(&msg_str["faucet".len()..]) { + Ok(sp_address) => { + // send bootstrap coins to this sp_address + match faucet_send(sp_address, sp_client.clone(), daemon.clone()) { + Ok(txid) => { + log::info!("New faucet payment: {}", txid); + }, + Err(e) => { + log::error!("faucet failed with error {}", e); + let peers = peer_map.lock().unwrap(); - // Broadcast message to other peers - peers - .iter() - .filter(|(peer_addr, _)| peer_addr != &&addr) - .for_each(|(_, peer_tx)| { - let _ = peer_tx.send(msg.clone()); - }); + let (_, peer_tx) = peers + .iter() + .find(|(peer_addr, _)| peer_addr == &&addr) + .unwrap(); + let _ = peer_tx.send(Message::Text(format!("RELAY_ERROR: {}", e))); + } + } + }, + Err(_) => { + log::error!("faucet message with unparsable sp_address received from {}", addr); + } + } + }, + false => { + let peers = peer_map.lock().unwrap(); + + // Broadcast message to other peers + peers + .iter() + .filter(|(peer_addr, _)| peer_addr != &&addr) + .for_each(|(_, peer_tx)| { + let _ = peer_tx.send(msg.clone()); + }); + } + } + }, + false => { + // we don't care + log::debug!("Received non-text message from peer {}", addr); + } + } future::ok(()) } }); diff --git a/src/spclient.rs b/src/spclient.rs index 7a3893d..0c2eb8b 100644 --- a/src/spclient.rs +++ b/src/spclient.rs @@ -612,7 +612,7 @@ impl SpClient { Ok(psbt) } - pub fn get_a_sum_secret_keys(input: &Vec) -> SecretKey { + pub fn get_a_sum_secret_keys(input: &[SecretKey]) -> SecretKey { let secp = Secp256k1::new(); let mut negated_keys: Vec = vec![]; @@ -636,7 +636,7 @@ impl SpClient { result } - fn taproot_sighash< + pub(crate) fn taproot_sighash< T: std::ops::Deref + std::borrow::Borrow, >( input: &Input,