Faucet spending
This commit is contained in:
parent
5f4efa5aa3
commit
7774207e01
185
src/main.rs
185
src/main.rs
@ -1,12 +1,9 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap, env, io::Error as IoError, net::SocketAddr, sync::{Arc, Mutex}
|
||||||
env,
|
|
||||||
io::Error as IoError,
|
|
||||||
net::SocketAddr,
|
|
||||||
sync::{Arc, Mutex, MutexGuard},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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 bitcoincore_rpc::json as bitcoin_json;
|
||||||
use futures_util::{future, pin_mut, stream::TryStreamExt, FutureExt, StreamExt};
|
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>>>;
|
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);
|
debug!("Incoming TCP connection from: {}", addr);
|
||||||
|
|
||||||
let ws_stream = tokio_tungstenite::accept_async(raw_stream)
|
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 broadcast_incoming = incoming.try_for_each({
|
||||||
let peer_map = peer_map.clone();
|
let peer_map = peer_map.clone();
|
||||||
move |msg| {
|
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
|
let (_, peer_tx) = peers
|
||||||
peers
|
.iter()
|
||||||
.iter()
|
.find(|(peer_addr, _)| peer_addr == &&addr)
|
||||||
.filter(|(peer_addr, _)| peer_addr != &&addr)
|
.unwrap();
|
||||||
.for_each(|(_, peer_tx)| {
|
|
||||||
let _ = peer_tx.send(msg.clone());
|
|
||||||
});
|
|
||||||
|
|
||||||
|
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(())
|
future::ok(())
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -612,7 +612,7 @@ impl SpClient {
|
|||||||
Ok(psbt)
|
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 secp = Secp256k1::new();
|
||||||
|
|
||||||
let mut negated_keys: Vec<SecretKey> = vec![];
|
let mut negated_keys: Vec<SecretKey> = vec![];
|
||||||
@ -636,7 +636,7 @@ impl SpClient {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn taproot_sighash<
|
pub(crate) fn taproot_sighash<
|
||||||
T: std::ops::Deref<Target = Transaction> + std::borrow::Borrow<Transaction>,
|
T: std::ops::Deref<Target = Transaction> + std::borrow::Borrow<Transaction>,
|
||||||
>(
|
>(
|
||||||
input: &Input,
|
input: &Input,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user