spend refactoring

This commit is contained in:
Sosthene00 2024-04-18 00:34:10 +02:00
parent ee5fcb4932
commit 94b96320d7
2 changed files with 88 additions and 179 deletions

36
Cargo.lock generated
View File

@ -939,9 +939,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.80" version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e" checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -1097,7 +1097,7 @@ dependencies = [
"rand", "rand",
"serde", "serde",
"serde_json", "serde_json",
"sp_backend", "sp_client 0.1.0 (git+https://github.com/Sosthene00/sp-backend?branch=sp_client)",
"tsify", "tsify",
"uuid", "uuid",
"wasm-bindgen", "wasm-bindgen",
@ -1118,7 +1118,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_with", "serde_with",
"sp_backend", "sp_client 0.1.0 (git+https://github.com/Sosthene00/sp-client?branch=sp_client)",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tokio-tungstenite", "tokio-tungstenite",
@ -1168,18 +1168,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.197" version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.197" version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1299,9 +1299,21 @@ dependencies = [
] ]
[[package]] [[package]]
name = "sp_backend" name = "sp_client"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/Sosthene00/sp-backend?branch=sp_client#d09c92ac7718c34c9ce3ff55298140ebda0b2e1a" source = "git+https://github.com/Sosthene00/sp-backend?branch=sp_client#4eaa51ed306fc939f487c0e14208bf36da8c8a26"
dependencies = [
"anyhow",
"bitcoin 0.31.2",
"serde",
"serde_json",
"silentpayments",
]
[[package]]
name = "sp_client"
version = "0.1.0"
source = "git+https://github.com/Sosthene00/sp-client?branch=sp_client#4eaa51ed306fc939f487c0e14208bf36da8c8a26"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitcoin 0.31.2", "bitcoin 0.31.2",
@ -1330,9 +1342,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.59" version = "2.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a" checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -1,5 +1,11 @@
use std::{ use std::{
any::Any, collections::HashMap, env, fmt::Debug, net::SocketAddr, str::FromStr, sync::{Arc, Mutex, MutexGuard} any::Any,
collections::HashMap,
env,
fmt::Debug,
net::SocketAddr,
str::FromStr,
sync::{Arc, Mutex, MutexGuard},
}; };
use bitcoincore_rpc::json::{self as bitcoin_json}; use bitcoincore_rpc::json::{self as bitcoin_json};
@ -20,8 +26,8 @@ use sp_client::bitcoin::{
use sp_client::{ use sp_client::{
bitcoin::secp256k1::{ bitcoin::secp256k1::{
rand::{thread_rng, Rng}, rand::{thread_rng, Rng},
Error as Secp256k1Error, Keypair, Message as Secp256k1Message, PublicKey, Scalar, Keypair, Message as Secp256k1Message, PublicKey,
Secp256k1, SecretKey, ThirtyTwoByteHash, Secp256k1, ThirtyTwoByteHash,
}, },
spclient::SpWallet, spclient::SpWallet,
}; };
@ -86,6 +92,7 @@ impl SilentPaymentWallet {
enum BroadcastType { enum BroadcastType {
Sender(SocketAddr), Sender(SocketAddr),
#[allow(dead_code)]
ExcludeSender(SocketAddr), ExcludeSender(SocketAddr),
#[allow(dead_code)] #[allow(dead_code)]
ToAll, ToAll,
@ -169,55 +176,6 @@ fn spend_from_core(
} }
} }
fn find_owned_outputs(
tx: &Transaction,
ours: HashMap<Option<Label>, HashMap<XOnlyPublicKey, Scalar>>,
) -> Result<HashMap<OutPoint, OwnedOutput>> {
let mut res: HashMap<OutPoint, OwnedOutput> = 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<String>;
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( fn faucet_send(
sp_address: SilentPaymentAddress, sp_address: SilentPaymentAddress,
sp_wallet: Arc<SilentPaymentWallet>, sp_wallet: Arc<SilentPaymentWallet>,
@ -225,7 +183,6 @@ fn faucet_send(
) -> Result<Transaction> { ) -> Result<Transaction> {
let mut first_tx: Option<Transaction> = None; let mut first_tx: Option<Transaction> = None;
let final_tx: Transaction; let final_tx: Transaction;
let mut new_outpoints: HashMap<OutPoint, OwnedOutput>;
// do we have a sp output available ? // do we have a sp output available ?
let available_outpoints = sp_wallet.get_wallet()?.get_outputs().to_spendable_list(); let available_outpoints = sp_wallet.get_wallet()?.get_outputs().to_spendable_list();
@ -270,7 +227,8 @@ fn faucet_send(
.create_new_psbt(inputs.clone(), vec![recipient], None)?; .create_new_psbt(inputs.clone(), vec![recipient], None)?;
log::debug!("Created psbt: {}", new_psbt); log::debug!("Created psbt: {}", new_psbt);
SpClient::set_fees(&mut new_psbt, fee_estimate, sp_address.into())?; SpClient::set_fees(&mut new_psbt, fee_estimate, sp_address.into())?;
wallet.get_client().fill_sp_outputs(&mut new_psbt)?; let partial_secret = wallet.get_client().get_partial_secret_from_psbt(&new_psbt)?;
wallet.get_client().fill_sp_outputs(&mut new_psbt, partial_secret)?;
log::debug!("Definitive psbt: {}", new_psbt); log::debug!("Definitive psbt: {}", new_psbt);
let mut aux_rand = [0u8; 32]; let mut aux_rand = [0u8; 32];
thread_rng().fill(&mut aux_rand); thread_rng().fill(&mut aux_rand);
@ -279,46 +237,6 @@ fn faucet_send(
SpClient::finalize_psbt(&mut signed)?; SpClient::finalize_psbt(&mut signed)?;
final_tx = signed.extract_tx()?; 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<Vec<PublicKey>, 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<Vec<XOnlyPublicKey>, 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 { } else {
// let's try to spend directly from the mining address // let's try to spend directly from the mining address
let secp = Secp256k1::signing_only(); let secp = Secp256k1::signing_only();
@ -329,7 +247,7 @@ fn faucet_send(
spend_from_core(keypair.x_only_public_key().0, shared_daemon.clone())?; 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 // check that the first output of the transaction pays to the key we just created
assert!( debug_assert!(
core_tx.output[0].script_pubkey core_tx.output[0].script_pubkey
== ScriptBuf::new_p2tr_tweaked( == ScriptBuf::new_p2tr_tweaked(
keypair.x_only_public_key().0.dangerous_assume_tweaked() keypair.x_only_public_key().0.dangerous_assume_tweaked()
@ -411,40 +329,12 @@ fn faucet_send(
faucet_tx.input[0].witness.push(final_sig.to_vec()); 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); 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; final_tx = faucet_tx;
new_outpoints = find_owned_outputs(&final_tx, ours)?;
} }
if let Ok(daemon) = shared_daemon.lock() { 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 // broadcast one or two transactions
if first_tx.is_some() { if first_tx.is_some() {
daemon.broadcast(&first_tx.unwrap())?; daemon.broadcast(&first_tx.unwrap())?;
@ -454,17 +344,6 @@ fn faucet_send(
return Err(Error::msg("Failed to lock daemon")); 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);
debug!("{:?}", sp_wallet);
// save to disk
sp_wallet.save()?;
Ok(final_tx) Ok(final_tx)
} }
@ -476,9 +355,33 @@ fn handle_faucet_request(
if let Ok(sp_address) = SilentPaymentAddress::try_from(msg) { if let Ok(sp_address) = SilentPaymentAddress::try_from(msg) {
debug!("Sending bootstrap coins to {}", sp_address); debug!("Sending bootstrap coins to {}", sp_address);
// send bootstrap coins to this sp_address // send bootstrap coins to this sp_address
let tx = faucet_send(sp_address, sp_wallet, shared_daemon.clone())?; let tx = faucet_send(sp_address, sp_wallet.clone(), shared_daemon.clone())?;
let partial_tweak =
compute_partial_tweak_to_transaction(tx.clone(), shared_daemon.clone())?; // get the tweak
let partial_tweak = compute_partial_tweak_to_transaction(&tx, shared_daemon.clone())?;
debug!("Got the tweak");
// get current blockheight
let blkheight: u32 = shared_daemon
.lock_anyhow()?
.get_current_height()?
.try_into()?;
// update our sp_client with the change output(s)
sp_wallet.get_wallet()?.update_wallet_with_transaction(
&tx,
blkheight,
partial_tweak,
)?;
debug!("{:?}", sp_wallet.get_wallet()?.get_outputs());
debug!("updated the wallet");
// save to disk
sp_wallet.save()?;
debug!("saved the wallet");
Ok(NewTxMessage::new( Ok(NewTxMessage::new(
serialize(&tx).to_lower_hex_string(), serialize(&tx).to_lower_hex_string(),
Some(partial_tweak.to_string()), Some(partial_tweak.to_string()),
@ -594,19 +497,18 @@ async fn handle_connection(
} }
fn compute_partial_tweak_to_transaction( fn compute_partial_tweak_to_transaction(
tx: Transaction, tx: &Transaction,
daemon: Arc<Mutex<Daemon>>, daemon: Arc<Mutex<Daemon>>,
) -> Result<PublicKey> { ) -> Result<PublicKey> {
let mut outpoints: Vec<(String, u32)> = Vec::with_capacity(tx.input.len()); let mut outpoints: Vec<(String, u32)> = Vec::with_capacity(tx.input.len());
let mut pubkeys: Vec<PublicKey> = Vec::with_capacity(tx.input.len()); let mut pubkeys: Vec<PublicKey> = Vec::with_capacity(tx.input.len());
for input in tx.input { for input in tx.input.iter() {
outpoints.push(( outpoints.push((
input.previous_output.txid.to_string(), input.previous_output.txid.to_string(),
input.previous_output.vout, input.previous_output.vout,
)); ));
let prev_tx = daemon let prev_tx = daemon
.lock() .lock_anyhow()?
.map_err(|e| Error::msg(format!("Failed to lock the daemon: {}", e)))?
.get_transaction(&input.previous_output.txid, None) .get_transaction(&input.previous_output.txid, None)
.map_err(|e| Error::msg(format!("Failed to find previous transaction: {}", e)))?; .map_err(|e| Error::msg(format!("Failed to find previous transaction: {}", e)))?;
@ -642,7 +544,7 @@ fn create_new_tx_message(transaction: Vec<u8>, daemon: Arc<Mutex<Daemon>>) -> Re
return Err(Error::msg("Can't process coinbase transaction")); return Err(Error::msg("Can't process coinbase transaction"));
} }
let partial_tweak = compute_partial_tweak_to_transaction(tx, daemon)?; let partial_tweak = compute_partial_tweak_to_transaction(&tx, daemon)?;
Ok(NewTxMessage::new( Ok(NewTxMessage::new(
transaction.to_lower_hex_string(), transaction.to_lower_hex_string(),
Some(partial_tweak.to_string()), Some(partial_tweak.to_string()),
@ -666,35 +568,30 @@ async fn handle_zmq(peers: PeerMap, shared_daemon: SharedDaemon) {
}; };
debug!("Received a message"); debug!("Received a message");
let payload: String = let payload: String = if let (Some(topic), Some(data)) = (core_msg.get(0), core_msg.get(1))
if let (Some(topic), Some(data)) = (core_msg.get(0), core_msg.get(1)) { {
match std::str::from_utf8(&topic) { match std::str::from_utf8(&topic) {
Ok("rawtx") => { Ok("rawtx") => match create_new_tx_message(data.to_vec(), shared_daemon.clone()) {
match create_new_tx_message(data.to_vec(), shared_daemon.clone()) { Ok(m) => serde_json::to_string(&m).expect("This shouldn't fail"),
Ok(m) => serde_json::to_string(&m).expect("This shouldn't fail"), Err(e) => {
Err(e) => { error!("{}", e);
error!("{}", e);
continue;
}
}
}
Ok("hashblock") => todo!(),
_ => {
error!("Unexpected message in zmq");
continue; continue;
} }
},
Ok("hashblock") => todo!(),
_ => {
error!("Unexpected message in zmq");
continue;
} }
} else { }
error!("Empty message"); } else {
continue; error!("Empty message");
}; continue;
};
if let Err(e) = broadcast_message( if let Err(e) =
peers.clone(), broadcast_message(peers.clone(), AnkFlag::NewTx, payload, BroadcastType::ToAll)
AnkFlag::NewTx, {
payload,
BroadcastType::ToAll,
) {
log::error!("{}", e.to_string()); log::error!("{}", e.to_string());
} }
} }