mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-05-20 15:40:48 -04:00
Merge xmr_btc crate
Created network, storage and protocol modules. Organised files into the modules where the belong. xmr_btc crate moved into isolated modulein swap crate. Remove the xmr_btc module and integrate into swap crate. Consolidate message related code Reorganise imports Remove unused parent Message enum Remove unused parent State enum Remove unused dependencies from Cargo.toml
This commit is contained in:
parent
a0d859147a
commit
c900d12593
52 changed files with 1372 additions and 1933 deletions
346
swap/src/protocol/alice/steps.rs
Normal file
346
swap/src/protocol/alice/steps.rs
Normal file
|
@ -0,0 +1,346 @@
|
|||
use anyhow::{bail, Context, Result};
|
||||
use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic};
|
||||
use futures::{
|
||||
future::{select, Either},
|
||||
pin_mut,
|
||||
};
|
||||
use libp2p::request_response::ResponseChannel;
|
||||
use rand::rngs::OsRng;
|
||||
use sha2::Sha256;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tokio::time::timeout;
|
||||
use tracing::{info, trace};
|
||||
|
||||
use crate::{
|
||||
bitcoin,
|
||||
bitcoin::{
|
||||
poll_until_block_height_is_gte,
|
||||
timelocks::{BlockHeight, Timelock},
|
||||
BroadcastSignedTransaction, EncryptedSignature, GetBlockHeight, GetRawTransaction,
|
||||
TransactionBlockHeight, TxCancel, TxLock, TxRefund, WaitForTransactionFinality,
|
||||
WatchForRawTransaction,
|
||||
},
|
||||
config::Config,
|
||||
monero,
|
||||
monero::Transfer,
|
||||
network::request_response::AliceToBob,
|
||||
protocol::{alice, alice::event_loop::EventLoopHandle},
|
||||
SwapAmounts,
|
||||
};
|
||||
|
||||
pub async fn negotiate(
|
||||
state0: alice::State0,
|
||||
amounts: SwapAmounts,
|
||||
event_loop_handle: &mut EventLoopHandle,
|
||||
config: Config,
|
||||
) -> Result<(ResponseChannel<AliceToBob>, alice::State3)> {
|
||||
trace!("Starting negotiate");
|
||||
|
||||
// todo: we can move this out, we dont need to timeout here
|
||||
let _peer_id = timeout(
|
||||
config.bob_time_to_act,
|
||||
event_loop_handle.recv_conn_established(),
|
||||
)
|
||||
.await
|
||||
.context("Failed to receive dial connection from Bob")??;
|
||||
|
||||
let event = timeout(config.bob_time_to_act, event_loop_handle.recv_request())
|
||||
.await
|
||||
.context("Failed to receive amounts from Bob")??;
|
||||
|
||||
if event.btc != amounts.btc {
|
||||
bail!(
|
||||
"Bob proposed a different amount; got {}, expected: {}",
|
||||
event.btc,
|
||||
amounts.btc
|
||||
);
|
||||
}
|
||||
|
||||
event_loop_handle
|
||||
.send_amounts(event.channel, amounts)
|
||||
.await?;
|
||||
|
||||
let (bob_message0, channel) =
|
||||
timeout(config.bob_time_to_act, event_loop_handle.recv_message0()).await??;
|
||||
|
||||
let alice_message0 = state0.next_message(&mut OsRng);
|
||||
event_loop_handle
|
||||
.send_message0(channel, alice_message0)
|
||||
.await?;
|
||||
|
||||
let state1 = state0.receive(bob_message0)?;
|
||||
|
||||
let (bob_message1, channel) =
|
||||
timeout(config.bob_time_to_act, event_loop_handle.recv_message1()).await??;
|
||||
|
||||
let state2 = state1.receive(bob_message1);
|
||||
|
||||
event_loop_handle
|
||||
.send_message1(channel, state2.next_message())
|
||||
.await?;
|
||||
|
||||
let (bob_message2, channel) =
|
||||
timeout(config.bob_time_to_act, event_loop_handle.recv_message2()).await??;
|
||||
|
||||
let state3 = state2.receive(bob_message2)?;
|
||||
|
||||
Ok((channel, state3))
|
||||
}
|
||||
|
||||
// TODO(Franck): Use helper functions from xmr-btc instead of re-writing them
|
||||
// here
|
||||
pub async fn wait_for_locked_bitcoin<W>(
|
||||
lock_bitcoin_txid: bitcoin::Txid,
|
||||
bitcoin_wallet: Arc<W>,
|
||||
config: Config,
|
||||
) -> Result<()>
|
||||
where
|
||||
W: WatchForRawTransaction + WaitForTransactionFinality,
|
||||
{
|
||||
// We assume we will see Bob's transaction in the mempool first.
|
||||
timeout(
|
||||
config.bob_time_to_act,
|
||||
bitcoin_wallet.watch_for_raw_transaction(lock_bitcoin_txid),
|
||||
)
|
||||
.await
|
||||
.context("Failed to find lock Bitcoin tx")?;
|
||||
|
||||
// // We saw the transaction in the mempool, waiting for it to be confirmed.
|
||||
bitcoin_wallet
|
||||
.wait_for_transaction_finality(lock_bitcoin_txid, config)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn lock_xmr<W>(
|
||||
channel: ResponseChannel<AliceToBob>,
|
||||
amounts: SwapAmounts,
|
||||
state3: alice::State3,
|
||||
event_loop_handle: &mut EventLoopHandle,
|
||||
monero_wallet: Arc<W>,
|
||||
) -> Result<()>
|
||||
where
|
||||
W: Transfer,
|
||||
{
|
||||
let S_a = monero::PublicKey::from_private_key(&monero::PrivateKey {
|
||||
scalar: state3.s_a.into_ed25519(),
|
||||
});
|
||||
|
||||
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, amounts.xmr)
|
||||
.await?;
|
||||
|
||||
// TODO(Franck): Wait for Monero to be confirmed once
|
||||
|
||||
event_loop_handle
|
||||
.send_message2(channel, alice::Message2 {
|
||||
tx_lock_proof: transfer_proof,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn wait_for_bitcoin_encrypted_signature(
|
||||
event_loop_handle: &mut EventLoopHandle,
|
||||
timeout_duration: Duration,
|
||||
) -> Result<EncryptedSignature> {
|
||||
let msg3 = timeout(timeout_duration, event_loop_handle.recv_message3())
|
||||
.await
|
||||
.context("Failed to receive Bitcoin encrypted signature from Bob")??;
|
||||
Ok(msg3.tx_redeem_encsig)
|
||||
}
|
||||
|
||||
pub fn build_bitcoin_redeem_transaction(
|
||||
encrypted_signature: EncryptedSignature,
|
||||
tx_lock: &TxLock,
|
||||
a: bitcoin::SecretKey,
|
||||
s_a: cross_curve_dleq::Scalar,
|
||||
B: bitcoin::PublicKey,
|
||||
redeem_address: &bitcoin::Address,
|
||||
) -> Result<bitcoin::Transaction> {
|
||||
let adaptor = Adaptor::<Sha256, Deterministic<Sha256>>::default();
|
||||
|
||||
let tx_redeem = bitcoin::TxRedeem::new(tx_lock, redeem_address);
|
||||
|
||||
bitcoin::verify_encsig(
|
||||
B,
|
||||
s_a.into_secp256k1().into(),
|
||||
&tx_redeem.digest(),
|
||||
&encrypted_signature,
|
||||
)
|
||||
.context("Invalid encrypted signature received")?;
|
||||
|
||||
let sig_a = a.sign(tx_redeem.digest());
|
||||
let sig_b = adaptor.decrypt_signature(&s_a.into_secp256k1(), encrypted_signature);
|
||||
|
||||
let tx = tx_redeem
|
||||
.add_signatures(&tx_lock, (a.public(), sig_a), (B, sig_b))
|
||||
.context("sig_{a,b} are invalid for tx_redeem")?;
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
pub async fn publish_bitcoin_redeem_transaction<W>(
|
||||
redeem_tx: bitcoin::Transaction,
|
||||
bitcoin_wallet: Arc<W>,
|
||||
config: Config,
|
||||
) -> Result<()>
|
||||
where
|
||||
W: BroadcastSignedTransaction + WaitForTransactionFinality,
|
||||
{
|
||||
info!("Attempting to publish bitcoin redeem txn");
|
||||
let tx_id = bitcoin_wallet
|
||||
.broadcast_signed_transaction(redeem_tx)
|
||||
.await?;
|
||||
|
||||
bitcoin_wallet
|
||||
.wait_for_transaction_finality(tx_id, config)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn publish_cancel_transaction<W>(
|
||||
tx_lock: TxLock,
|
||||
a: bitcoin::SecretKey,
|
||||
B: bitcoin::PublicKey,
|
||||
cancel_timelock: Timelock,
|
||||
tx_cancel_sig_bob: bitcoin::Signature,
|
||||
bitcoin_wallet: Arc<W>,
|
||||
) -> Result<bitcoin::TxCancel>
|
||||
where
|
||||
W: GetRawTransaction + TransactionBlockHeight + GetBlockHeight + BroadcastSignedTransaction,
|
||||
{
|
||||
// First wait for cancel timelock to expire
|
||||
let tx_lock_height = bitcoin_wallet
|
||||
.transaction_block_height(tx_lock.txid())
|
||||
.await;
|
||||
poll_until_block_height_is_gte(bitcoin_wallet.as_ref(), tx_lock_height + 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
|
||||
.clone()
|
||||
.add_signatures(&tx_lock, (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?
|
||||
bitcoin_wallet
|
||||
.broadcast_signed_transaction(tx_cancel)
|
||||
.await?;
|
||||
|
||||
// TODO(Franck): Wait until transaction is mined and returned mined
|
||||
// block height
|
||||
}
|
||||
|
||||
Ok(tx_cancel)
|
||||
}
|
||||
|
||||
pub async fn wait_for_bitcoin_refund<W>(
|
||||
tx_cancel: &TxCancel,
|
||||
cancel_tx_height: BlockHeight,
|
||||
punish_timelock: Timelock,
|
||||
refund_address: &bitcoin::Address,
|
||||
bitcoin_wallet: Arc<W>,
|
||||
) -> Result<(bitcoin::TxRefund, Option<bitcoin::Transaction>)>
|
||||
where
|
||||
W: GetBlockHeight + WatchForRawTransaction,
|
||||
{
|
||||
let punish_timelock_expired =
|
||||
poll_until_block_height_is_gte(bitcoin_wallet.as_ref(), cancel_tx_height + punish_timelock);
|
||||
|
||||
let tx_refund = bitcoin::TxRefund::new(tx_cancel, refund_address);
|
||||
|
||||
// TODO(Franck): This only checks the mempool, need to cater for the case where
|
||||
// the transaction goes directly in a block
|
||||
let seen_refund_tx = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());
|
||||
|
||||
pin_mut!(punish_timelock_expired);
|
||||
pin_mut!(seen_refund_tx);
|
||||
|
||||
match select(punish_timelock_expired, seen_refund_tx).await {
|
||||
Either::Left(_) => Ok((tx_refund, None)),
|
||||
Either::Right((published_refund_tx, _)) => Ok((tx_refund, Some(published_refund_tx))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_monero_private_key(
|
||||
published_refund_tx: bitcoin::Transaction,
|
||||
tx_refund: TxRefund,
|
||||
s_a: cross_curve_dleq::Scalar,
|
||||
a: bitcoin::SecretKey,
|
||||
S_b_bitcoin: bitcoin::PublicKey,
|
||||
) -> Result<monero::PrivateKey> {
|
||||
let s_a = monero::PrivateKey {
|
||||
scalar: s_a.into_ed25519(),
|
||||
};
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn build_bitcoin_punish_transaction(
|
||||
tx_lock: &TxLock,
|
||||
cancel_timelock: Timelock,
|
||||
punish_address: &bitcoin::Address,
|
||||
punish_timelock: Timelock,
|
||||
tx_punish_sig_bob: bitcoin::Signature,
|
||||
a: bitcoin::SecretKey,
|
||||
B: bitcoin::PublicKey,
|
||||
) -> Result<bitcoin::Transaction> {
|
||||
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, a.public(), B);
|
||||
let tx_punish = bitcoin::TxPunish::new(&tx_cancel, &punish_address, punish_timelock);
|
||||
|
||||
let sig_a = a.sign(tx_punish.digest());
|
||||
let sig_b = tx_punish_sig_bob;
|
||||
|
||||
let signed_tx_punish = tx_punish
|
||||
.add_signatures(&tx_cancel, (a.public(), sig_a), (B, sig_b))
|
||||
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
||||
|
||||
Ok(signed_tx_punish)
|
||||
}
|
||||
|
||||
pub async fn publish_bitcoin_punish_transaction<W>(
|
||||
punish_tx: bitcoin::Transaction,
|
||||
bitcoin_wallet: Arc<W>,
|
||||
config: Config,
|
||||
) -> Result<bitcoin::Txid>
|
||||
where
|
||||
W: BroadcastSignedTransaction + WaitForTransactionFinality,
|
||||
{
|
||||
let txid = bitcoin_wallet
|
||||
.broadcast_signed_transaction(punish_tx)
|
||||
.await?;
|
||||
|
||||
bitcoin_wallet
|
||||
.wait_for_transaction_finality(txid, config)
|
||||
.await?;
|
||||
|
||||
Ok(txid)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue