use std::{ any::Any, collections::HashMap, env, fmt::Debug, net::SocketAddr, str::FromStr, sync::{Arc, Mutex, MutexGuard} }; use bitcoincore_rpc::json::{self as bitcoin_json}; use futures_util::{future, pin_mut, stream::TryStreamExt, FutureExt, StreamExt}; use log::{debug, error}; use sdk_common::network::{AnkFlag, AnkNetworkMsg, FaucetMessage, NewTxMessage}; use sp_backend::bitcoin::{ absolute::LockTime, consensus::{deserialize, serialize}, hex::DisplayHex, key::TapTweak, sighash::{Prevouts, SighashCache}, taproot::Signature, transaction::Version, Amount, OutPoint, Psbt, ScriptBuf, TapSighashType, Transaction, TxIn, TxOut, Witness, XOnlyPublicKey, }; use sp_backend::{ bitcoin::secp256k1::{ rand::{thread_rng, Rng}, Error as Secp256k1Error, Keypair, Message as Secp256k1Message, PublicKey, Scalar, Secp256k1, SecretKey, ThirtyTwoByteHash, }, spclient::SpWallet, }; use sp_backend::db::{JsonFile, Storage}; use sp_backend::silentpayments::receiving::Label; use sp_backend::silentpayments::sending::{generate_recipient_pubkeys, SilentPaymentAddress}; use sp_backend::silentpayments::utils::receiving::{ calculate_shared_secret, calculate_tweak_data, get_pubkey_from_input, }; use sp_backend::silentpayments::utils::sending::calculate_partial_secret; use sp_backend::spclient::{ derive_keys_from_seed, OutputSpendStatus, OwnedOutput, Recipient, SpClient, SpendKey, }; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::mpsc::{unbounded_channel, UnboundedSender}; use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_tungstenite::tungstenite::Message; use anyhow::{Error, Result}; use zeromq::{Socket, SocketRecv}; mod daemon; mod electrumclient; mod scan; use crate::{daemon::Daemon, scan::scan_blocks}; type Tx = UnboundedSender; type PeerMap = Arc>>; type SharedDaemon = Arc>; const FAUCET_AMT: Amount = Amount::from_sat(1000); pub(crate) trait MutexExt { fn lock_anyhow(&self) -> Result, Error>; } impl MutexExt for Mutex { fn lock_anyhow(&self) -> Result, Error> { self.lock() .map_err(|e| Error::msg(format!("Failed to lock: {}", e))) } } #[derive(Debug)] struct SilentPaymentWallet { sp_wallet: Mutex, storage: Mutex, } impl SilentPaymentWallet { pub fn get_wallet(&self) -> Result> { self.sp_wallet.lock_anyhow() } pub fn save(&self) -> Result<()> { self.storage.lock_anyhow()?.save(&self.sp_wallet) } } enum BroadcastType { Sender(SocketAddr), ExcludeSender(SocketAddr), #[allow(dead_code)] ToAll, } fn broadcast_message( peers: PeerMap, flag: AnkFlag, payload: String, broadcast: BroadcastType, ) -> Result<()> { // log::debug!("Broadcasting message: {}", msg); let ank_msg = AnkNetworkMsg { flag, content: payload, }; let msg = Message::Text(serde_json::to_string(&ank_msg)?); match broadcast { BroadcastType::Sender(addr) => { peers .lock() .map_err(|e| Error::msg(format!("Failed to lock peers: {}", e.to_string())))? .iter() .find(|(peer_addr, _)| peer_addr == &&addr) .ok_or(Error::msg("Failed to find the sender in the peer_map"))? .1 .send(msg.clone())?; } BroadcastType::ExcludeSender(addr) => { peers .lock() .map_err(|e| Error::msg(format!("Failed to lock peers: {}", e.to_string())))? .iter() .filter(|(peer_addr, _)| peer_addr != &&addr) .for_each(|(_, peer_tx)| { let _ = peer_tx.send(msg.clone()); }); } BroadcastType::ToAll => { peers .lock() .map_err(|e| Error::msg(format!("Failed to lock peers: {}", e.to_string())))? .iter() .for_each(|(_, peer_tx)| { let _ = peer_tx.send(msg.clone()); }); } } Ok(()) } fn spend_from_core( dest: XOnlyPublicKey, daemon: Arc>, ) -> Result<(Transaction, Amount)> { 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(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 find_owned_outputs( tx: &Transaction, ours: HashMap, HashMap>, ) -> Result> { let mut res: HashMap = HashMap::new(); for (label, map) in ours { res.extend(tx.output.iter().enumerate().filter_map( |(i, o)| match XOnlyPublicKey::from_slice(&o.script_pubkey.as_bytes()[2..]) { Ok(key) => { if let Some(scalar) = map.get(&key) { match SecretKey::from_slice(&scalar.to_be_bytes()) { Ok(tweak) => { let outpoint = OutPoint { txid: tx.txid(), vout: i as u32, }; let label_str: Option; if let Some(l) = &label { label_str = Some(l.as_inner().to_be_bytes().to_lower_hex_string()); } else { label_str = None; } return Some(( outpoint, OwnedOutput { blockheight: 0, tweak: tweak.secret_bytes().to_lower_hex_string(), amount: o.value, script: o.script_pubkey.as_bytes().to_lower_hex_string(), label: label_str, spend_status: OutputSpendStatus::Unspent, }, )); } Err(_) => { return None; } } } None } Err(_) => None, }, )); } Ok(res) } fn faucet_send( sp_address: SilentPaymentAddress, sp_wallet: Arc, shared_daemon: SharedDaemon, ) -> Result { let mut first_tx: Option = None; let final_tx: Transaction; let mut new_outpoints: HashMap; // 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 mut new_psbt = wallet .get_client() .create_new_psbt(inputs.clone(), vec![recipient], None)?; log::debug!("Created psbt: {}", new_psbt); SpClient::set_fees(&mut new_psbt, fee_estimate, sp_address.into())?; wallet.get_client().fill_sp_outputs(&mut new_psbt)?; log::debug!("Definitive psbt: {}", new_psbt); let mut aux_rand = [0u8; 32]; thread_rng().fill(&mut aux_rand); let mut signed = wallet.get_client().sign_psbt(new_psbt, &aux_rand)?; log::debug!("signed psbt: {}", signed); SpClient::finalize_psbt(&mut signed)?; final_tx = signed.extract_tx()?; // take all we need to register the new sp output let outpoints: Vec<(String, u32)> = final_tx .input .iter() .map(|i| (i.previous_output.txid.to_string(), i.previous_output.vout)) .collect(); let our_sp_address: SilentPaymentAddress = wallet .get_client() .sp_receiver .get_receiving_address() .try_into()?; let our_spend_pubkey = our_sp_address.get_spend_key(); let secp = Secp256k1::verification_only(); let input_pubkeys: Result, Secp256k1Error> = inputs .iter() .map(|(_, o)| { let tweak = SecretKey::from_str(&o.tweak)?; our_spend_pubkey.mul_tweak(&secp, &tweak.into()) }) .collect(); let input_pubkeys = input_pubkeys?; let input_pubkeys: Vec<&PublicKey> = input_pubkeys.iter().collect(); let partial_tweak = calculate_tweak_data(&input_pubkeys, &outpoints)?; let ecdh_shared_secret = calculate_shared_secret(partial_tweak, wallet.get_client().get_scan_key())?; let outputs_to_check: Result, Secp256k1Error> = final_tx .output .iter() .map(|o| XOnlyPublicKey::from_slice(&o.script_pubkey.as_bytes()[2..])) .collect(); let ours = wallet .get_client() .sp_receiver .scan_transaction(&ecdh_shared_secret, outputs_to_check?)?; new_outpoints = find_owned_outputs(&final_tx, ours)?; } 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 assert!( core_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(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::>() .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::>() .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()); faucet_tx.output.push(TxOut { value: FAUCET_AMT, script_pubkey: ext_spk, }); faucet_tx.output.push(TxOut { value: core_tx.output[0].value - FAUCET_AMT, script_pubkey: change_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()); // take all we need to register the new sp output let outpoints: Vec<(String, u32)> = vec![(core_tx.txid().to_string(), 0)]; first_tx = Some(core_tx); let client = sp_wallet.get_wallet()?; let input_pubkey = &keypair.public_key(); let input_pub_keys: Vec<&PublicKey> = vec![input_pubkey]; let partial_tweak = calculate_tweak_data(&input_pub_keys, &outpoints)?; let ecdh_shared_secret = calculate_shared_secret(partial_tweak, client.get_client().get_scan_key())?; let p2tr_outs = vec![ext_output_key, change_output_key]; let ours = client .get_client() .sp_receiver .scan_transaction(&ecdh_shared_secret, p2tr_outs)?; final_tx = faucet_tx; new_outpoints = find_owned_outputs(&final_tx, ours)?; } if let Ok(daemon) = shared_daemon.lock() { // get current blockheight let blkheight: u32 = daemon.get_current_height()?.try_into()?; // update the new outpoints for o in new_outpoints.iter_mut() { o.1.blockheight = blkheight; } // broadcast one or two transactions if first_tx.is_some() { daemon.broadcast(&first_tx.unwrap())?; } daemon.broadcast(&final_tx)?; } else { return Err(Error::msg("Failed to lock daemon")); } // update our sp_client with the change output(s) sp_wallet .get_wallet()? .get_mut_outputs() .extend_from(new_outpoints); // save to disk sp_wallet.save()?; Ok(final_tx.txid()) } fn handle_faucet_request( msg: &str, sp_wallet: Arc, shared_daemon: SharedDaemon, ) -> Result { if let Ok(sp_address) = SilentPaymentAddress::try_from(&msg["faucet".len()..]) { // send bootstrap coins to this sp_address faucet_send(sp_address, sp_wallet, shared_daemon) } else { Err(Error::msg(format!( "faucet message with unparsable sp_address" ))) } } async fn handle_connection( peers: PeerMap, shared_daemon: SharedDaemon, sp_wallet: Arc, raw_stream: TcpStream, addr: SocketAddr, ) { debug!("Incoming TCP connection from: {}", addr); let ws_stream = tokio_tungstenite::accept_async(raw_stream) .await .expect("Error during the websocket handshake occurred"); debug!("WebSocket connection established"); // Insert the write part of this peer to the peer map. let (tx, rx) = unbounded_channel(); match peers.lock_anyhow() { Ok(mut peer_map) => peer_map.insert(addr, tx), Err(e) => { log::error!("{}", e); panic!(); } }; let (outgoing, incoming) = ws_stream.split(); let broadcast_incoming = incoming.try_for_each({ let peers = peers.clone(); move |msg| { if let Ok(raw_msg) = msg.to_text() { debug!("Received msg: {}", raw_msg); let parsed = serde_json::from_str::(raw_msg); match parsed { Ok(ank_msg) => match ank_msg.flag { AnkFlag::Faucet => { debug!("Received a faucet message"); if let Ok(content) = serde_json::from_str::(&ank_msg.content) { match handle_faucet_request( &content.sp_address, sp_wallet.clone(), shared_daemon.clone(), ) { Ok(new_tx_msg) => { if let Err(e) = broadcast_message( peers.clone(), AnkFlag::NewTx, serde_json::to_string(&new_tx_msg) .expect("This should not fail"), BroadcastType::Sender(addr), ) { log::error!( "Failed to broadcast message: {}", e.to_string() ); } } Err(e) => { if let Err(e) = broadcast_message( peers.clone(), AnkFlag::Error, e.to_string(), BroadcastType::Sender(addr), ) { log::error!("Failed to broadcast message: {}", e); } } } } else { log::error!("Invalid content for faucet message"); } } AnkFlag::NewTx => unimplemented!(), AnkFlag::Error => unimplemented!(), AnkFlag::Unknown => unimplemented!(), }, Err(_) => log::error!("Failed to parse network message"), } } else { // we don't care log::debug!("Received non-text message {} from peer {}", msg, addr); } future::ok(()) } }); let receive_from_others = UnboundedReceiverStream::new(rx) .map(Ok) .forward(outgoing) .map(|result| { if let Err(e) = result { debug!("Error sending message: {}", e); } }); pin_mut!(broadcast_incoming, receive_from_others); future::select(broadcast_incoming, receive_from_others).await; debug!("{} disconnected", &addr); peers.lock().unwrap().remove(&addr); } fn compute_partial_tweak_to_transaction( tx: Transaction, daemon: Arc>, ) -> Result { let mut outpoints: Vec<(String, u32)> = Vec::with_capacity(tx.input.len()); let mut pubkeys: Vec = Vec::with_capacity(tx.input.len()); for input in tx.input { outpoints.push(( input.previous_output.txid.to_string(), input.previous_output.vout, )); let prev_tx = daemon .lock() .map_err(|e| Error::msg(format!("Failed to lock the daemon: {}", e)))? .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, daemon: Arc>) -> Result { let tx: Transaction = deserialize(&transaction)?; if tx.is_coinbase() { return Err(Error::msg("Can't process coinbase transaction")); } let partial_tweak = compute_partial_tweak_to_transaction(tx, daemon)?; Ok(NewTxMessage::new( transaction.to_lower_hex_string(), Some(partial_tweak.to_string()), )) } async fn handle_zmq(peers: PeerMap, shared_daemon: SharedDaemon) { debug!("Starting listening on Core"); let mut socket = zeromq::SubSocket::new(); socket.connect("tcp://127.0.0.1:29100").await.unwrap(); socket.subscribe("rawtx").await.unwrap(); // socket.subscribe("hashblock"); debug!("{:?}", socket.type_id()); loop { let core_msg = match socket.recv().await { Ok(m) => m, Err(e) => { error!("Zmq error: {}", e); continue; } }; debug!("Received a message"); let payload: String = if let (Some(topic), Some(data)) = (core_msg.get(0), core_msg.get(1)) { match std::str::from_utf8(&topic) { Ok("rawtx") => { match create_new_tx_message(data.to_vec(), shared_daemon.clone()) { Ok(m) => serde_json::to_string(&m).expect("This shouldn't fail"), Err(e) => { error!("{}", e); continue; } } } Ok("hashblock") => todo!(), _ => { error!("Unexpected message in zmq"); continue; } } } else { error!("Empty message"); continue; }; if let Err(e) = broadcast_message( peers.clone(), AnkFlag::NewTx, payload, BroadcastType::ToAll, ) { log::error!("{}", e.to_string()); } } } #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<()> { env_logger::init(); let addr = env::args() .nth(1) .unwrap_or_else(|| "127.0.0.1:8090".to_string()); let wallet_name = env::args().nth(2).unwrap_or_else(|| "default".to_owned()); let is_testnet: bool = env::args() .nth(3) .unwrap_or_else(|| "true".to_owned()) .parse() .expect("Please provide either \"true\" or \"false\""); let core_wallet: Option = env::args().nth(4); let peers = PeerMap::new(Mutex::new(HashMap::new())); // Connect the rpc daemon let shared_daemon = Arc::new(Mutex::new(Daemon::connect(core_wallet)?)); let current_tip: u32 = shared_daemon .lock_anyhow()? .get_current_height()? .try_into()?; let mut config_dir = env::var("HOME")?; config_dir.push_str("/.4nk"); let sp_wallet_file = JsonFile::new(&config_dir, &wallet_name); // load an existing sp_wallet, or create a new one let sp_wallet = match >::load(&sp_wallet_file) { Err(_) => { let mut seed = [0u8; 64]; thread_rng().fill(&mut seed); let (scan_sk, spend_sk) = derive_keys_from_seed(&seed, is_testnet) .expect("Couldn't generate a new sp_wallet"); let new_client = SpClient::new( wallet_name, scan_sk, SpendKey::Secret(spend_sk), None, is_testnet, ) .expect("Failed to create a new SpClient"); let mut wallet = SpWallet::new(new_client, None)?; // set birthday to avoid unnecessary scanning let outputs = wallet.get_mut_outputs(); outputs.set_birthday(current_tip); outputs.update_last_scan(current_tip); wallet } Ok(wallet) => wallet, }; log::info!( "Using wallet {} with address {}", sp_wallet.get_client().label, sp_wallet.get_client().get_receiving_address() ); log::info!( "Found {} outputs for a total balance of {}", sp_wallet.get_outputs().to_spendable_list().len(), sp_wallet.get_outputs().get_balance() ); let last_scan = sp_wallet.get_outputs().get_last_scan(); let shared_sp_wallet = Mutex::new(sp_wallet); let shared_wallet_storage = Mutex::new(sp_wallet_file); let sp_wallet = Arc::new(SilentPaymentWallet { sp_wallet: shared_sp_wallet, storage: shared_wallet_storage, }); sp_wallet.save()?; if last_scan < current_tip { log::info!("Scanning for our outputs"); scan_blocks( shared_daemon.clone(), sp_wallet.clone(), current_tip - last_scan, )?; } // Subscribe to Bitcoin Core tokio::spawn(handle_zmq(peers.clone(), shared_daemon.clone())); // Create the event loop and TCP listener we'll accept connections on. let try_socket = TcpListener::bind(&addr).await; let listener = try_socket.expect("Failed to bind"); debug!("Listening on: {}", addr); // Let's spawn the handling of each connection in a separate task. while let Ok((stream, addr)) = listener.accept().await { tokio::spawn(handle_connection( peers.clone(), shared_daemon.clone(), sp_wallet.clone(), stream, addr, )); } Ok(()) }