Faucet spending

This commit is contained in:
Sosthene00 2024-03-08 20:40:25 +01:00
parent 5f4efa5aa3
commit 7774207e01
2 changed files with 172 additions and 17 deletions

View File

@ -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<Message>;
type PeerMap = Arc<Mutex<HashMap<SocketAddr, Tx>>>;
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<Mutex<Daemon>>) -> Result<Transaction> {
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(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::<bitcoin::Transaction>(&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<Mutex<SpClient>>, daemon: Arc<Mutex<Daemon>>) -> Result<Txid> {
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<Mutex<SpClient>>, daemon: Arc<Mutex<Daemon>>) {
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(())
}
});

View File

@ -612,7 +612,7 @@ impl SpClient {
Ok(psbt)
}
pub fn get_a_sum_secret_keys(input: &Vec<SecretKey>) -> SecretKey {
pub fn get_a_sum_secret_keys(input: &[SecretKey]) -> SecretKey {
let secp = Secp256k1::new();
let mut negated_keys: Vec<SecretKey> = vec![];
@ -636,7 +636,7 @@ impl SpClient {
result
}
fn taproot_sighash<
pub(crate) fn taproot_sighash<
T: std::ops::Deref<Target = Transaction> + std::borrow::Borrow<Transaction>,
>(
input: &Input,