xmr-btc-swap/swap/src/protocol/alice/steps.rs
Thomas Eizinger a57f88d1b4
Properly handle concurrent messages to and from peers
Previously, we were forwarding incoming messages from peers to all
swaps that were currently running. That is obviously wrong. The new
design scopes an `EventLoopHandle` to a specific PeerId to avoid
this problem.
2021-03-17 16:32:17 +11:00

149 lines
4.7 KiB
Rust

use crate::bitcoin::{
CancelTimelock, EncryptedSignature, PunishTimelock, TxCancel, TxLock, TxRefund,
};
use crate::protocol::alice;
use crate::protocol::alice::event_loop::EventLoopHandle;
use crate::protocol::alice::TransferProof;
use crate::{bitcoin, monero};
use anyhow::{bail, Context, Result};
use futures::pin_mut;
pub async fn lock_xmr(
state3: alice::State3,
event_loop_handle: &mut EventLoopHandle,
monero_wallet: &monero::Wallet,
) -> Result<()> {
let S_a = monero::PublicKey::from_private_key(&monero::PrivateKey { scalar: state3.s_a });
let public_spend_key = S_a + state3.S_b_monero;
let public_view_key = state3.v.public();
let transfer_proof = monero_wallet
.transfer(public_spend_key, public_view_key, state3.xmr)
.await?;
// TODO(Franck): Wait for Monero to be confirmed once
// Waiting for XMR confirmations should not be done in here, but in a separate
// state! We have to record that Alice has already sent the transaction.
// Otherwise Alice might publish the lock tx twice!
event_loop_handle
.send_transfer_proof(TransferProof {
tx_lock_proof: transfer_proof,
})
.await?;
Ok(())
}
pub async fn wait_for_bitcoin_encrypted_signature(
event_loop_handle: &mut EventLoopHandle,
) -> Result<EncryptedSignature> {
let msg3 = event_loop_handle
.recv_encrypted_signature()
.await
.context("Failed to receive Bitcoin encrypted signature from Bob")?;
tracing::debug!("Message 3 received, returning it");
Ok(msg3.tx_redeem_encsig)
}
pub async fn publish_cancel_transaction(
tx_lock: TxLock,
a: bitcoin::SecretKey,
B: bitcoin::PublicKey,
cancel_timelock: CancelTimelock,
tx_cancel_sig_bob: bitcoin::Signature,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<()> {
bitcoin_wallet
.watch_until_status(&tx_lock, |status| status.is_confirmed_with(cancel_timelock))
.await?;
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, a.public(), B);
// If Bob hasn't yet broadcasted the tx cancel, we do it
if bitcoin_wallet
.get_raw_transaction(tx_cancel.txid())
.await
.is_err()
{
// TODO(Franck): Maybe the cancel transaction is already mined, in this case,
// the broadcast will error out.
let sig_a = a.sign(tx_cancel.digest());
let sig_b = tx_cancel_sig_bob.clone();
let tx_cancel = tx_cancel
.add_signatures((a.public(), sig_a), (B, sig_b))
.expect("sig_{a,b} to be valid signatures for tx_cancel");
// TODO(Franck): Error handling is delicate, why can't we broadcast?
let (..) = bitcoin_wallet.broadcast(tx_cancel, "cancel").await?;
// TODO(Franck): Wait until transaction is mined and returned mined
// block height
}
Ok(())
}
pub async fn wait_for_bitcoin_refund(
tx_cancel: &TxCancel,
tx_refund: &TxRefund,
punish_timelock: PunishTimelock,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<Option<bitcoin::Transaction>> {
let refund_tx_id = tx_refund.txid();
let seen_refund_tx =
bitcoin_wallet.watch_until_status(tx_refund, |status| status.has_been_seen());
let punish_timelock_expired = bitcoin_wallet.watch_until_status(tx_cancel, |status| {
status.is_confirmed_with(punish_timelock)
});
pin_mut!(punish_timelock_expired);
pin_mut!(seen_refund_tx);
tokio::select! {
seen_refund = seen_refund_tx => {
match seen_refund {
Ok(()) => {
let published_refund_tx = bitcoin_wallet.get_raw_transaction(refund_tx_id).await?;
Ok(Some(published_refund_tx))
}
Err(e) => {
bail!(e.context("Failed to monitor refund transaction"))
}
}
}
_ = punish_timelock_expired => {
Ok(None)
}
}
}
pub fn extract_monero_private_key(
published_refund_tx: bitcoin::Transaction,
tx_refund: &TxRefund,
s_a: monero::Scalar,
a: bitcoin::SecretKey,
S_b_bitcoin: bitcoin::PublicKey,
) -> Result<monero::PrivateKey> {
let s_a = monero::PrivateKey { scalar: s_a };
let tx_refund_sig = tx_refund
.extract_signature_by_key(published_refund_tx, a.public())
.context("Failed to extract signature from Bitcoin refund tx")?;
let tx_refund_encsig = a.encsign(S_b_bitcoin, tx_refund.digest());
let s_b = bitcoin::recover(S_b_bitcoin, tx_refund_sig, tx_refund_encsig)
.context("Failed to recover Monero secret key from Bitcoin signature")?;
let s_b = monero::private_key_from_secp256k1_scalar(s_b.into());
let spend_key = s_a + s_b;
Ok(spend_key)
}