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::{
|
||||
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(())
|
||||
}
|
||||
});
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user