mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Resolve rebase issues, restructure code and fix warnings
This commit is contained in:
parent
437c1cbb80
commit
3b005bd15c
@ -2,9 +2,8 @@
|
|||||||
//! Alice holds XMR and wishes receive BTC.
|
//! Alice holds XMR and wishes receive BTC.
|
||||||
use self::{amounts::*, message0::*, message1::*, message2::*, message3::*};
|
use self::{amounts::*, message0::*, message1::*, message2::*, message3::*};
|
||||||
use crate::{
|
use crate::{
|
||||||
alice::execution::{lock_xmr, negotiate},
|
|
||||||
bitcoin,
|
bitcoin,
|
||||||
bitcoin::{EncryptedSignature, TX_LOCK_MINE_TIMEOUT},
|
bitcoin::TX_LOCK_MINE_TIMEOUT,
|
||||||
monero,
|
monero,
|
||||||
network::{
|
network::{
|
||||||
peer_tracker::{self, PeerTracker},
|
peer_tracker::{self, PeerTracker},
|
||||||
@ -16,34 +15,23 @@ use crate::{
|
|||||||
storage::Database,
|
storage::Database,
|
||||||
SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
|
SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::Result;
|
||||||
use async_recursion::async_recursion;
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
|
use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
|
||||||
use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic};
|
|
||||||
use futures::{
|
|
||||||
future::{select, Either},
|
|
||||||
pin_mut,
|
|
||||||
};
|
|
||||||
use genawaiter::GeneratorState;
|
use genawaiter::GeneratorState;
|
||||||
use libp2p::{
|
use libp2p::{
|
||||||
core::{identity::Keypair, Multiaddr},
|
core::{identity::Keypair, Multiaddr},
|
||||||
request_response::ResponseChannel,
|
request_response::ResponseChannel,
|
||||||
NetworkBehaviour, PeerId,
|
NetworkBehaviour, PeerId,
|
||||||
};
|
};
|
||||||
use rand::{rngs::OsRng, CryptoRng, RngCore};
|
use rand::rngs::OsRng;
|
||||||
use sha2::Sha256;
|
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
use tokio::{sync::Mutex, time::timeout};
|
use tokio::sync::Mutex;
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use xmr_btc::{
|
use xmr_btc::{
|
||||||
alice::{self, action_generator, Action, ReceiveBitcoinRedeemEncsig, State0, State3},
|
alice::{self, action_generator, Action, ReceiveBitcoinRedeemEncsig, State0},
|
||||||
bitcoin::{
|
bitcoin::BroadcastSignedTransaction,
|
||||||
poll_until_block_height_is_gte, BroadcastSignedTransaction, GetRawTransaction,
|
|
||||||
TransactionBlockHeight, TxCancel, TxRefund, WaitForTransactionFinality,
|
|
||||||
WatchForRawTransaction,
|
|
||||||
},
|
|
||||||
bob, cross_curve_dleq,
|
bob, cross_curve_dleq,
|
||||||
monero::{CreateWalletForOutput, Transfer},
|
monero::{CreateWalletForOutput, Transfer},
|
||||||
};
|
};
|
||||||
@ -54,440 +42,7 @@ mod message0;
|
|||||||
mod message1;
|
mod message1;
|
||||||
mod message2;
|
mod message2;
|
||||||
mod message3;
|
mod message3;
|
||||||
|
pub mod swap;
|
||||||
trait Rng: RngCore + CryptoRng + Send {}
|
|
||||||
|
|
||||||
impl<T> Rng for T where T: RngCore + CryptoRng + Send {}
|
|
||||||
|
|
||||||
// The same data structure is used for swap execution and recovery.
|
|
||||||
// This allows for a seamless transition from a failed swap to recovery.
|
|
||||||
pub enum AliceState {
|
|
||||||
Started {
|
|
||||||
amounts: SwapAmounts,
|
|
||||||
a: bitcoin::SecretKey,
|
|
||||||
s_a: cross_curve_dleq::Scalar,
|
|
||||||
v_a: monero::PrivateViewKey,
|
|
||||||
},
|
|
||||||
Negotiated {
|
|
||||||
channel: ResponseChannel<AliceToBob>,
|
|
||||||
amounts: SwapAmounts,
|
|
||||||
state3: State3,
|
|
||||||
},
|
|
||||||
BtcLocked {
|
|
||||||
channel: ResponseChannel<AliceToBob>,
|
|
||||||
amounts: SwapAmounts,
|
|
||||||
state3: State3,
|
|
||||||
},
|
|
||||||
XmrLocked {
|
|
||||||
state3: State3,
|
|
||||||
},
|
|
||||||
EncSignLearned {
|
|
||||||
state3: State3,
|
|
||||||
encrypted_signature: EncryptedSignature,
|
|
||||||
},
|
|
||||||
BtcRedeemed,
|
|
||||||
BtcCancelled {
|
|
||||||
state3: State3,
|
|
||||||
tx_cancel: TxCancel,
|
|
||||||
},
|
|
||||||
BtcRefunded {
|
|
||||||
tx_refund: TxRefund,
|
|
||||||
published_refund_tx: ::bitcoin::Transaction,
|
|
||||||
state3: State3,
|
|
||||||
},
|
|
||||||
BtcPunishable {
|
|
||||||
tx_refund: TxRefund,
|
|
||||||
state3: State3,
|
|
||||||
},
|
|
||||||
BtcPunished {
|
|
||||||
tx_refund: TxRefund,
|
|
||||||
punished_tx_id: bitcoin::Txid,
|
|
||||||
state3: State3,
|
|
||||||
},
|
|
||||||
XmrRefunded,
|
|
||||||
WaitingToCancel {
|
|
||||||
state3: State3,
|
|
||||||
},
|
|
||||||
Punished,
|
|
||||||
SafelyAborted,
|
|
||||||
}
|
|
||||||
|
|
||||||
// State machine driver for swap execution
|
|
||||||
#[async_recursion]
|
|
||||||
pub async fn simple_swap(
|
|
||||||
state: AliceState,
|
|
||||||
mut swarm: Swarm,
|
|
||||||
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
|
||||||
monero_wallet: Arc<crate::monero::Wallet>,
|
|
||||||
) -> Result<AliceState> {
|
|
||||||
match state {
|
|
||||||
AliceState::Started {
|
|
||||||
amounts,
|
|
||||||
a,
|
|
||||||
s_a,
|
|
||||||
v_a,
|
|
||||||
} => {
|
|
||||||
let (channel, amounts, state3) =
|
|
||||||
negotiate(amounts, a, s_a, v_a, &mut swarm, bitcoin_wallet.clone()).await?;
|
|
||||||
|
|
||||||
simple_swap(
|
|
||||||
AliceState::Negotiated {
|
|
||||||
channel,
|
|
||||||
amounts,
|
|
||||||
state3,
|
|
||||||
},
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
AliceState::Negotiated {
|
|
||||||
state3,
|
|
||||||
channel,
|
|
||||||
amounts,
|
|
||||||
} => {
|
|
||||||
timeout(
|
|
||||||
Duration::from_secs(TX_LOCK_MINE_TIMEOUT),
|
|
||||||
bitcoin_wallet.wait_for_transaction_finality(state3.tx_lock.txid()),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("Timed out, Bob did not lock Bitcoin in time")?;
|
|
||||||
|
|
||||||
simple_swap(
|
|
||||||
AliceState::BtcLocked {
|
|
||||||
channel,
|
|
||||||
amounts,
|
|
||||||
state3,
|
|
||||||
},
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
AliceState::BtcLocked {
|
|
||||||
channel,
|
|
||||||
amounts,
|
|
||||||
state3,
|
|
||||||
} => {
|
|
||||||
lock_xmr(
|
|
||||||
channel,
|
|
||||||
amounts,
|
|
||||||
state3.clone(),
|
|
||||||
&mut swarm,
|
|
||||||
monero_wallet.clone(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
simple_swap(
|
|
||||||
AliceState::XmrLocked { state3 },
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
AliceState::XmrLocked { state3 } => {
|
|
||||||
let encsig = timeout(
|
|
||||||
// Give a set arbitrary time to Bob to send us `tx_redeem_encsign`
|
|
||||||
Duration::from_secs(TX_LOCK_MINE_TIMEOUT),
|
|
||||||
async {
|
|
||||||
match swarm.next().await {
|
|
||||||
OutEvent::Message3(msg) => Ok(msg.tx_redeem_encsig),
|
|
||||||
other => Err(anyhow!(
|
|
||||||
"Expected Bob's Bitcoin redeem encsig, got: {:?}",
|
|
||||||
other
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("Timed out, Bob did not send redeem encsign in time");
|
|
||||||
|
|
||||||
match encsig {
|
|
||||||
Err(_timeout_error) => {
|
|
||||||
// TODO(Franck): Insert in DB
|
|
||||||
|
|
||||||
simple_swap(
|
|
||||||
AliceState::WaitingToCancel { state3 },
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Ok(Err(_unexpected_msg_error)) => {
|
|
||||||
// TODO(Franck): Insert in DB
|
|
||||||
|
|
||||||
simple_swap(
|
|
||||||
AliceState::WaitingToCancel { state3 },
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Ok(Ok(encrypted_signature)) => {
|
|
||||||
// TODO(Franck): Insert in DB
|
|
||||||
|
|
||||||
simple_swap(
|
|
||||||
AliceState::EncSignLearned {
|
|
||||||
state3,
|
|
||||||
encrypted_signature,
|
|
||||||
},
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AliceState::EncSignLearned {
|
|
||||||
state3,
|
|
||||||
encrypted_signature,
|
|
||||||
} => {
|
|
||||||
let (signed_tx_redeem, _tx_redeem_txid) = {
|
|
||||||
let adaptor = Adaptor::<Sha256, Deterministic<Sha256>>::default();
|
|
||||||
|
|
||||||
let tx_redeem = bitcoin::TxRedeem::new(&state3.tx_lock, &state3.redeem_address);
|
|
||||||
|
|
||||||
bitcoin::verify_encsig(
|
|
||||||
state3.B.clone(),
|
|
||||||
state3.s_a.into_secp256k1().into(),
|
|
||||||
&tx_redeem.digest(),
|
|
||||||
&encrypted_signature,
|
|
||||||
)
|
|
||||||
.context("Invalid encrypted signature received")?;
|
|
||||||
|
|
||||||
let sig_a = state3.a.sign(tx_redeem.digest());
|
|
||||||
let sig_b = adaptor
|
|
||||||
.decrypt_signature(&state3.s_a.into_secp256k1(), encrypted_signature.clone());
|
|
||||||
|
|
||||||
let tx = tx_redeem
|
|
||||||
.add_signatures(
|
|
||||||
&state3.tx_lock,
|
|
||||||
(state3.a.public(), sig_a),
|
|
||||||
(state3.B.clone(), sig_b),
|
|
||||||
)
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_redeem");
|
|
||||||
let txid = tx.txid();
|
|
||||||
|
|
||||||
(tx, txid)
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO(Franck): Insert in db
|
|
||||||
|
|
||||||
let _ = bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(signed_tx_redeem)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// TODO(Franck) Wait for confirmations
|
|
||||||
|
|
||||||
simple_swap(
|
|
||||||
AliceState::BtcRedeemed,
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
AliceState::WaitingToCancel { state3 } => {
|
|
||||||
let tx_lock_height = bitcoin_wallet
|
|
||||||
.transaction_block_height(state3.tx_lock.txid())
|
|
||||||
.await;
|
|
||||||
poll_until_block_height_is_gte(
|
|
||||||
bitcoin_wallet.as_ref(),
|
|
||||||
tx_lock_height + state3.refund_timelock,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
|
||||||
&state3.tx_lock,
|
|
||||||
state3.refund_timelock,
|
|
||||||
state3.a.public(),
|
|
||||||
state3.B.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let None = bitcoin_wallet.get_raw_transaction(tx_cancel.txid()).await {
|
|
||||||
let sig_a = state3.a.sign(tx_cancel.digest());
|
|
||||||
let sig_b = state3.tx_cancel_sig_bob.clone();
|
|
||||||
|
|
||||||
let tx_cancel = tx_cancel
|
|
||||||
.clone()
|
|
||||||
.add_signatures(
|
|
||||||
&state3.tx_lock,
|
|
||||||
(state3.a.public(), sig_a),
|
|
||||||
(state3.B.clone(), sig_b),
|
|
||||||
)
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
|
||||||
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(tx_cancel)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
simple_swap(
|
|
||||||
AliceState::BtcCancelled { state3, tx_cancel },
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
AliceState::BtcCancelled { state3, tx_cancel } => {
|
|
||||||
let tx_cancel_height = bitcoin_wallet
|
|
||||||
.transaction_block_height(tx_cancel.txid())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let reached_t2 = poll_until_block_height_is_gte(
|
|
||||||
bitcoin_wallet.as_ref(),
|
|
||||||
tx_cancel_height + state3.punish_timelock,
|
|
||||||
);
|
|
||||||
|
|
||||||
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state3.refund_address);
|
|
||||||
let seen_refund_tx = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());
|
|
||||||
|
|
||||||
pin_mut!(reached_t2);
|
|
||||||
pin_mut!(seen_refund_tx);
|
|
||||||
|
|
||||||
match select(reached_t2, seen_refund_tx).await {
|
|
||||||
Either::Left(_) => {
|
|
||||||
simple_swap(
|
|
||||||
AliceState::BtcPunishable { tx_refund, state3 },
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet.clone(),
|
|
||||||
monero_wallet,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Either::Right((published_refund_tx, _)) => {
|
|
||||||
simple_swap(
|
|
||||||
AliceState::BtcRefunded {
|
|
||||||
tx_refund,
|
|
||||||
published_refund_tx,
|
|
||||||
state3,
|
|
||||||
},
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet.clone(),
|
|
||||||
monero_wallet,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AliceState::BtcRefunded {
|
|
||||||
tx_refund,
|
|
||||||
published_refund_tx,
|
|
||||||
state3,
|
|
||||||
} => {
|
|
||||||
let s_a = monero::PrivateKey {
|
|
||||||
scalar: state3.s_a.into_ed25519(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tx_refund_sig = tx_refund
|
|
||||||
.extract_signature_by_key(published_refund_tx, state3.a.public())
|
|
||||||
.context("Failed to extract signature from Bitcoin refund tx")?;
|
|
||||||
let tx_refund_encsig = state3
|
|
||||||
.a
|
|
||||||
.encsign(state3.S_b_bitcoin.clone(), tx_refund.digest());
|
|
||||||
|
|
||||||
let s_b = bitcoin::recover(state3.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;
|
|
||||||
let view_key = state3.v;
|
|
||||||
|
|
||||||
monero_wallet
|
|
||||||
.create_and_load_wallet_for_output(spend_key, view_key)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(AliceState::XmrRefunded)
|
|
||||||
}
|
|
||||||
AliceState::BtcPunishable { tx_refund, state3 } => {
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
|
||||||
&state3.tx_lock,
|
|
||||||
state3.refund_timelock,
|
|
||||||
state3.a.public(),
|
|
||||||
state3.B.clone(),
|
|
||||||
);
|
|
||||||
let tx_punish =
|
|
||||||
bitcoin::TxPunish::new(&tx_cancel, &state3.punish_address, state3.punish_timelock);
|
|
||||||
let punished_tx_id = tx_punish.txid();
|
|
||||||
|
|
||||||
let sig_a = state3.a.sign(tx_punish.digest());
|
|
||||||
let sig_b = state3.tx_punish_sig_bob.clone();
|
|
||||||
|
|
||||||
let signed_tx_punish = tx_punish
|
|
||||||
.add_signatures(
|
|
||||||
&tx_cancel,
|
|
||||||
(state3.a.public(), sig_a),
|
|
||||||
(state3.B.clone(), sig_b),
|
|
||||||
)
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
|
||||||
|
|
||||||
let _ = bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(signed_tx_punish)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
simple_swap(
|
|
||||||
AliceState::BtcPunished {
|
|
||||||
tx_refund,
|
|
||||||
punished_tx_id,
|
|
||||||
state3,
|
|
||||||
},
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet.clone(),
|
|
||||||
monero_wallet,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
AliceState::BtcPunished {
|
|
||||||
punished_tx_id,
|
|
||||||
tx_refund,
|
|
||||||
state3,
|
|
||||||
} => {
|
|
||||||
let punish_tx_finalised = bitcoin_wallet.wait_for_transaction_finality(punished_tx_id);
|
|
||||||
|
|
||||||
let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());
|
|
||||||
|
|
||||||
pin_mut!(punish_tx_finalised);
|
|
||||||
pin_mut!(refund_tx_seen);
|
|
||||||
|
|
||||||
match select(punish_tx_finalised, refund_tx_seen).await {
|
|
||||||
Either::Left(_) => {
|
|
||||||
simple_swap(
|
|
||||||
AliceState::Punished,
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet.clone(),
|
|
||||||
monero_wallet,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Either::Right((published_refund_tx, _)) => {
|
|
||||||
simple_swap(
|
|
||||||
AliceState::BtcRefunded {
|
|
||||||
tx_refund,
|
|
||||||
published_refund_tx,
|
|
||||||
state3,
|
|
||||||
},
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet.clone(),
|
|
||||||
monero_wallet,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AliceState::XmrRefunded => Ok(AliceState::XmrRefunded),
|
|
||||||
AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed),
|
|
||||||
AliceState::Punished => Ok(AliceState::Punished),
|
|
||||||
AliceState::SafelyAborted => Ok(AliceState::SafelyAborted),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn swap(
|
pub async fn swap(
|
||||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||||
@ -497,7 +52,25 @@ pub async fn swap(
|
|||||||
transport: SwapTransport,
|
transport: SwapTransport,
|
||||||
behaviour: Behaviour,
|
behaviour: Behaviour,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
struct Network(Arc<Mutex<Swarm>>);
|
struct Network {
|
||||||
|
swarm: Arc<Mutex<Swarm>>,
|
||||||
|
channel: Option<ResponseChannel<AliceToBob>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Network {
|
||||||
|
pub async fn send_message2(&mut self, proof: monero::TransferProof) {
|
||||||
|
match self.channel.take() {
|
||||||
|
None => warn!("Channel not found, did you call this twice?"),
|
||||||
|
Some(channel) => {
|
||||||
|
let mut guard = self.swarm.lock().await;
|
||||||
|
guard.send_message2(channel, alice::Message2 {
|
||||||
|
tx_lock_proof: proof,
|
||||||
|
});
|
||||||
|
info!("Sent transfer proof");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: For retry, use `backoff::ExponentialBackoff` in production as opposed
|
// TODO: For retry, use `backoff::ExponentialBackoff` in production as opposed
|
||||||
// to `ConstantBackoff`.
|
// to `ConstantBackoff`.
|
||||||
@ -508,7 +81,7 @@ pub async fn swap(
|
|||||||
struct UnexpectedMessage;
|
struct UnexpectedMessage;
|
||||||
|
|
||||||
let encsig = (|| async {
|
let encsig = (|| async {
|
||||||
let mut guard = self.0.lock().await;
|
let mut guard = self.swarm.lock().await;
|
||||||
let encsig = match guard.next().await {
|
let encsig = match guard.next().await {
|
||||||
OutEvent::Message3(msg) => msg.tx_redeem_encsig,
|
OutEvent::Message3(msg) => msg.tx_redeem_encsig,
|
||||||
other => {
|
other => {
|
||||||
@ -602,8 +175,11 @@ pub async fn swap(
|
|||||||
let msg = state2.next_message();
|
let msg = state2.next_message();
|
||||||
swarm.send_message1(channel, msg);
|
swarm.send_message1(channel, msg);
|
||||||
|
|
||||||
let state3 = match swarm.next().await {
|
let (state3, channel) = match swarm.next().await {
|
||||||
OutEvent::Message2(msg) => state2.receive(msg)?,
|
OutEvent::Message2 { msg, channel } => {
|
||||||
|
let state3 = state2.receive(msg)?;
|
||||||
|
(state3, channel)
|
||||||
|
}
|
||||||
other => panic!("Unexpected event: {:?}", other),
|
other => panic!("Unexpected event: {:?}", other),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -613,10 +189,13 @@ pub async fn swap(
|
|||||||
|
|
||||||
info!("Handshake complete, we now have State3 for Alice.");
|
info!("Handshake complete, we now have State3 for Alice.");
|
||||||
|
|
||||||
let network = Arc::new(Mutex::new(Network(unimplemented!())));
|
let network = Arc::new(Mutex::new(Network {
|
||||||
|
swarm: Arc::new(Mutex::new(swarm)),
|
||||||
|
channel: Some(channel),
|
||||||
|
}));
|
||||||
|
|
||||||
let mut action_generator = action_generator(
|
let mut action_generator = action_generator(
|
||||||
network,
|
network.clone(),
|
||||||
bitcoin_wallet.clone(),
|
bitcoin_wallet.clone(),
|
||||||
state3.clone(),
|
state3.clone(),
|
||||||
TX_LOCK_MINE_TIMEOUT,
|
TX_LOCK_MINE_TIMEOUT,
|
||||||
@ -636,12 +215,16 @@ pub async fn swap(
|
|||||||
db.insert_latest_state(swap_id, state::Alice::BtcLocked(state3.clone()).into())
|
db.insert_latest_state(swap_id, state::Alice::BtcLocked(state3.clone()).into())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let _ = monero_wallet
|
let (transfer_proof, _) = monero_wallet
|
||||||
.transfer(public_spend_key, public_view_key, amount)
|
.transfer(public_spend_key, public_view_key, amount)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
db.insert_latest_state(swap_id, state::Alice::XmrLocked(state3.clone()).into())
|
db.insert_latest_state(swap_id, state::Alice::XmrLocked(state3.clone()).into())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let mut guard = network.as_ref().lock().await;
|
||||||
|
guard.send_message2(transfer_proof).await;
|
||||||
|
info!("Sent transfer proof");
|
||||||
}
|
}
|
||||||
|
|
||||||
GeneratorState::Yielded(Action::RedeemBtc(tx)) => {
|
GeneratorState::Yielded(Action::RedeemBtc(tx)) => {
|
||||||
@ -728,7 +311,10 @@ pub enum OutEvent {
|
|||||||
msg: bob::Message1,
|
msg: bob::Message1,
|
||||||
channel: ResponseChannel<AliceToBob>,
|
channel: ResponseChannel<AliceToBob>,
|
||||||
},
|
},
|
||||||
Message2(bob::Message2),
|
Message2 {
|
||||||
|
msg: bob::Message2,
|
||||||
|
channel: ResponseChannel<AliceToBob>,
|
||||||
|
},
|
||||||
Message3(bob::Message3),
|
Message3(bob::Message3),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -767,7 +353,7 @@ impl From<message1::OutEvent> for OutEvent {
|
|||||||
impl From<message2::OutEvent> for OutEvent {
|
impl From<message2::OutEvent> for OutEvent {
|
||||||
fn from(event: message2::OutEvent) -> Self {
|
fn from(event: message2::OutEvent) -> Self {
|
||||||
match event {
|
match event {
|
||||||
message2::OutEvent::Msg { msg, .. } => OutEvent::Message2(msg),
|
message2::OutEvent::Msg { msg, channel } => OutEvent::Message2 { msg, channel },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -827,6 +413,16 @@ impl Behaviour {
|
|||||||
self.message1.send(channel, msg);
|
self.message1.send(channel, msg);
|
||||||
debug!("Sent Message1");
|
debug!("Sent Message1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send Message2 to Bob in response to receiving his Message2.
|
||||||
|
pub fn send_message2(
|
||||||
|
&mut self,
|
||||||
|
channel: ResponseChannel<AliceToBob>,
|
||||||
|
msg: xmr_btc::alice::Message2,
|
||||||
|
) {
|
||||||
|
self.message2.send(channel, msg);
|
||||||
|
debug!("Sent Message2");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Behaviour {
|
impl Default for Behaviour {
|
||||||
|
472
swap/src/alice/swap.rs
Normal file
472
swap/src/alice/swap.rs
Normal file
@ -0,0 +1,472 @@
|
|||||||
|
//! Run an XMR/BTC swap in the role of Alice.
|
||||||
|
//! Alice holds XMR and wishes receive BTC.
|
||||||
|
use crate::{
|
||||||
|
alice::{
|
||||||
|
execution::{lock_xmr, negotiate},
|
||||||
|
OutEvent, Swarm,
|
||||||
|
},
|
||||||
|
bitcoin,
|
||||||
|
bitcoin::{EncryptedSignature, TX_LOCK_MINE_TIMEOUT},
|
||||||
|
monero,
|
||||||
|
network::request_response::AliceToBob,
|
||||||
|
SwapAmounts,
|
||||||
|
};
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use async_recursion::async_recursion;
|
||||||
|
|
||||||
|
use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic};
|
||||||
|
use futures::{
|
||||||
|
future::{select, Either},
|
||||||
|
pin_mut,
|
||||||
|
};
|
||||||
|
|
||||||
|
use libp2p::request_response::ResponseChannel;
|
||||||
|
use rand::{CryptoRng, RngCore};
|
||||||
|
use sha2::Sha256;
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
use tokio::time::timeout;
|
||||||
|
|
||||||
|
use xmr_btc::{
|
||||||
|
alice::State3,
|
||||||
|
bitcoin::{
|
||||||
|
poll_until_block_height_is_gte, BroadcastSignedTransaction, GetRawTransaction,
|
||||||
|
TransactionBlockHeight, TxCancel, TxRefund, WaitForTransactionFinality,
|
||||||
|
WatchForRawTransaction,
|
||||||
|
},
|
||||||
|
cross_curve_dleq,
|
||||||
|
monero::CreateWalletForOutput,
|
||||||
|
};
|
||||||
|
|
||||||
|
trait Rng: RngCore + CryptoRng + Send {}
|
||||||
|
|
||||||
|
impl<T> Rng for T where T: RngCore + CryptoRng + Send {}
|
||||||
|
|
||||||
|
// The same data structure is used for swap execution and recovery.
|
||||||
|
// This allows for a seamless transition from a failed swap to recovery.
|
||||||
|
pub enum AliceState {
|
||||||
|
Started {
|
||||||
|
amounts: SwapAmounts,
|
||||||
|
a: bitcoin::SecretKey,
|
||||||
|
s_a: cross_curve_dleq::Scalar,
|
||||||
|
v_a: monero::PrivateViewKey,
|
||||||
|
},
|
||||||
|
Negotiated {
|
||||||
|
channel: ResponseChannel<AliceToBob>,
|
||||||
|
amounts: SwapAmounts,
|
||||||
|
state3: State3,
|
||||||
|
},
|
||||||
|
BtcLocked {
|
||||||
|
channel: ResponseChannel<AliceToBob>,
|
||||||
|
amounts: SwapAmounts,
|
||||||
|
state3: State3,
|
||||||
|
},
|
||||||
|
XmrLocked {
|
||||||
|
state3: State3,
|
||||||
|
},
|
||||||
|
EncSignLearned {
|
||||||
|
state3: State3,
|
||||||
|
encrypted_signature: EncryptedSignature,
|
||||||
|
},
|
||||||
|
BtcRedeemed,
|
||||||
|
BtcCancelled {
|
||||||
|
state3: State3,
|
||||||
|
tx_cancel: TxCancel,
|
||||||
|
},
|
||||||
|
BtcRefunded {
|
||||||
|
tx_refund: TxRefund,
|
||||||
|
published_refund_tx: ::bitcoin::Transaction,
|
||||||
|
state3: State3,
|
||||||
|
},
|
||||||
|
BtcPunishable {
|
||||||
|
tx_refund: TxRefund,
|
||||||
|
state3: State3,
|
||||||
|
},
|
||||||
|
BtcPunished {
|
||||||
|
tx_refund: TxRefund,
|
||||||
|
punished_tx_id: bitcoin::Txid,
|
||||||
|
state3: State3,
|
||||||
|
},
|
||||||
|
XmrRefunded,
|
||||||
|
WaitingToCancel {
|
||||||
|
state3: State3,
|
||||||
|
},
|
||||||
|
Punished,
|
||||||
|
SafelyAborted,
|
||||||
|
}
|
||||||
|
|
||||||
|
// State machine driver for swap execution
|
||||||
|
#[async_recursion]
|
||||||
|
pub async fn swap(
|
||||||
|
state: AliceState,
|
||||||
|
mut swarm: Swarm,
|
||||||
|
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||||
|
monero_wallet: Arc<crate::monero::Wallet>,
|
||||||
|
) -> Result<AliceState> {
|
||||||
|
match state {
|
||||||
|
AliceState::Started {
|
||||||
|
amounts,
|
||||||
|
a,
|
||||||
|
s_a,
|
||||||
|
v_a,
|
||||||
|
} => {
|
||||||
|
let (channel, amounts, state3) =
|
||||||
|
negotiate(amounts, a, s_a, v_a, &mut swarm, bitcoin_wallet.clone()).await?;
|
||||||
|
|
||||||
|
swap(
|
||||||
|
AliceState::Negotiated {
|
||||||
|
channel,
|
||||||
|
amounts,
|
||||||
|
state3,
|
||||||
|
},
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
AliceState::Negotiated {
|
||||||
|
state3,
|
||||||
|
channel,
|
||||||
|
amounts,
|
||||||
|
} => {
|
||||||
|
timeout(
|
||||||
|
Duration::from_secs(TX_LOCK_MINE_TIMEOUT),
|
||||||
|
bitcoin_wallet.wait_for_transaction_finality(state3.tx_lock.txid()),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("Timed out, Bob did not lock Bitcoin in time")?;
|
||||||
|
|
||||||
|
swap(
|
||||||
|
AliceState::BtcLocked {
|
||||||
|
channel,
|
||||||
|
amounts,
|
||||||
|
state3,
|
||||||
|
},
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
AliceState::BtcLocked {
|
||||||
|
channel,
|
||||||
|
amounts,
|
||||||
|
state3,
|
||||||
|
} => {
|
||||||
|
lock_xmr(
|
||||||
|
channel,
|
||||||
|
amounts,
|
||||||
|
state3.clone(),
|
||||||
|
&mut swarm,
|
||||||
|
monero_wallet.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
swap(
|
||||||
|
AliceState::XmrLocked { state3 },
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
AliceState::XmrLocked { state3 } => {
|
||||||
|
let encsig = timeout(
|
||||||
|
// Give a set arbitrary time to Bob to send us `tx_redeem_encsign`
|
||||||
|
Duration::from_secs(TX_LOCK_MINE_TIMEOUT),
|
||||||
|
async {
|
||||||
|
match swarm.next().await {
|
||||||
|
OutEvent::Message3(msg) => Ok(msg.tx_redeem_encsig),
|
||||||
|
other => Err(anyhow!(
|
||||||
|
"Expected Bob's Bitcoin redeem encsig, got: {:?}",
|
||||||
|
other
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("Timed out, Bob did not send redeem encsign in time");
|
||||||
|
|
||||||
|
match encsig {
|
||||||
|
Err(_timeout_error) => {
|
||||||
|
// TODO(Franck): Insert in DB
|
||||||
|
|
||||||
|
swap(
|
||||||
|
AliceState::WaitingToCancel { state3 },
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Ok(Err(_unexpected_msg_error)) => {
|
||||||
|
// TODO(Franck): Insert in DB
|
||||||
|
|
||||||
|
swap(
|
||||||
|
AliceState::WaitingToCancel { state3 },
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Ok(Ok(encrypted_signature)) => {
|
||||||
|
// TODO(Franck): Insert in DB
|
||||||
|
|
||||||
|
swap(
|
||||||
|
AliceState::EncSignLearned {
|
||||||
|
state3,
|
||||||
|
encrypted_signature,
|
||||||
|
},
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AliceState::EncSignLearned {
|
||||||
|
state3,
|
||||||
|
encrypted_signature,
|
||||||
|
} => {
|
||||||
|
let (signed_tx_redeem, _tx_redeem_txid) = {
|
||||||
|
let adaptor = Adaptor::<Sha256, Deterministic<Sha256>>::default();
|
||||||
|
|
||||||
|
let tx_redeem = bitcoin::TxRedeem::new(&state3.tx_lock, &state3.redeem_address);
|
||||||
|
|
||||||
|
bitcoin::verify_encsig(
|
||||||
|
state3.B.clone(),
|
||||||
|
state3.s_a.into_secp256k1().into(),
|
||||||
|
&tx_redeem.digest(),
|
||||||
|
&encrypted_signature,
|
||||||
|
)
|
||||||
|
.context("Invalid encrypted signature received")?;
|
||||||
|
|
||||||
|
let sig_a = state3.a.sign(tx_redeem.digest());
|
||||||
|
let sig_b = adaptor
|
||||||
|
.decrypt_signature(&state3.s_a.into_secp256k1(), encrypted_signature.clone());
|
||||||
|
|
||||||
|
let tx = tx_redeem
|
||||||
|
.add_signatures(
|
||||||
|
&state3.tx_lock,
|
||||||
|
(state3.a.public(), sig_a),
|
||||||
|
(state3.B.clone(), sig_b),
|
||||||
|
)
|
||||||
|
.expect("sig_{a,b} to be valid signatures for tx_redeem");
|
||||||
|
let txid = tx.txid();
|
||||||
|
|
||||||
|
(tx, txid)
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(Franck): Insert in db
|
||||||
|
|
||||||
|
let _ = bitcoin_wallet
|
||||||
|
.broadcast_signed_transaction(signed_tx_redeem)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// TODO(Franck) Wait for confirmations
|
||||||
|
|
||||||
|
swap(
|
||||||
|
AliceState::BtcRedeemed,
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
AliceState::WaitingToCancel { state3 } => {
|
||||||
|
let tx_lock_height = bitcoin_wallet
|
||||||
|
.transaction_block_height(state3.tx_lock.txid())
|
||||||
|
.await;
|
||||||
|
poll_until_block_height_is_gte(
|
||||||
|
bitcoin_wallet.as_ref(),
|
||||||
|
tx_lock_height + state3.refund_timelock,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let tx_cancel = bitcoin::TxCancel::new(
|
||||||
|
&state3.tx_lock,
|
||||||
|
state3.refund_timelock,
|
||||||
|
state3.a.public(),
|
||||||
|
state3.B.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(_e) = bitcoin_wallet.get_raw_transaction(tx_cancel.txid()).await {
|
||||||
|
let sig_a = state3.a.sign(tx_cancel.digest());
|
||||||
|
let sig_b = state3.tx_cancel_sig_bob.clone();
|
||||||
|
|
||||||
|
let tx_cancel = tx_cancel
|
||||||
|
.clone()
|
||||||
|
.add_signatures(
|
||||||
|
&state3.tx_lock,
|
||||||
|
(state3.a.public(), sig_a),
|
||||||
|
(state3.B.clone(), sig_b),
|
||||||
|
)
|
||||||
|
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
||||||
|
|
||||||
|
bitcoin_wallet
|
||||||
|
.broadcast_signed_transaction(tx_cancel)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
swap(
|
||||||
|
AliceState::BtcCancelled { state3, tx_cancel },
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
AliceState::BtcCancelled { state3, tx_cancel } => {
|
||||||
|
let tx_cancel_height = bitcoin_wallet
|
||||||
|
.transaction_block_height(tx_cancel.txid())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let reached_t2 = poll_until_block_height_is_gte(
|
||||||
|
bitcoin_wallet.as_ref(),
|
||||||
|
tx_cancel_height + state3.punish_timelock,
|
||||||
|
);
|
||||||
|
|
||||||
|
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state3.refund_address);
|
||||||
|
let seen_refund_tx = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());
|
||||||
|
|
||||||
|
pin_mut!(reached_t2);
|
||||||
|
pin_mut!(seen_refund_tx);
|
||||||
|
|
||||||
|
match select(reached_t2, seen_refund_tx).await {
|
||||||
|
Either::Left(_) => {
|
||||||
|
swap(
|
||||||
|
AliceState::BtcPunishable { tx_refund, state3 },
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet.clone(),
|
||||||
|
monero_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Either::Right((published_refund_tx, _)) => {
|
||||||
|
swap(
|
||||||
|
AliceState::BtcRefunded {
|
||||||
|
tx_refund,
|
||||||
|
published_refund_tx,
|
||||||
|
state3,
|
||||||
|
},
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet.clone(),
|
||||||
|
monero_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AliceState::BtcRefunded {
|
||||||
|
tx_refund,
|
||||||
|
published_refund_tx,
|
||||||
|
state3,
|
||||||
|
} => {
|
||||||
|
let s_a = monero::PrivateKey {
|
||||||
|
scalar: state3.s_a.into_ed25519(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_refund_sig = tx_refund
|
||||||
|
.extract_signature_by_key(published_refund_tx, state3.a.public())
|
||||||
|
.context("Failed to extract signature from Bitcoin refund tx")?;
|
||||||
|
let tx_refund_encsig = state3
|
||||||
|
.a
|
||||||
|
.encsign(state3.S_b_bitcoin.clone(), tx_refund.digest());
|
||||||
|
|
||||||
|
let s_b = bitcoin::recover(state3.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;
|
||||||
|
let view_key = state3.v;
|
||||||
|
|
||||||
|
monero_wallet
|
||||||
|
.create_and_load_wallet_for_output(spend_key, view_key)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(AliceState::XmrRefunded)
|
||||||
|
}
|
||||||
|
AliceState::BtcPunishable { tx_refund, state3 } => {
|
||||||
|
let tx_cancel = bitcoin::TxCancel::new(
|
||||||
|
&state3.tx_lock,
|
||||||
|
state3.refund_timelock,
|
||||||
|
state3.a.public(),
|
||||||
|
state3.B.clone(),
|
||||||
|
);
|
||||||
|
let tx_punish =
|
||||||
|
bitcoin::TxPunish::new(&tx_cancel, &state3.punish_address, state3.punish_timelock);
|
||||||
|
let punished_tx_id = tx_punish.txid();
|
||||||
|
|
||||||
|
let sig_a = state3.a.sign(tx_punish.digest());
|
||||||
|
let sig_b = state3.tx_punish_sig_bob.clone();
|
||||||
|
|
||||||
|
let signed_tx_punish = tx_punish
|
||||||
|
.add_signatures(
|
||||||
|
&tx_cancel,
|
||||||
|
(state3.a.public(), sig_a),
|
||||||
|
(state3.B.clone(), sig_b),
|
||||||
|
)
|
||||||
|
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
||||||
|
|
||||||
|
let _ = bitcoin_wallet
|
||||||
|
.broadcast_signed_transaction(signed_tx_punish)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
swap(
|
||||||
|
AliceState::BtcPunished {
|
||||||
|
tx_refund,
|
||||||
|
punished_tx_id,
|
||||||
|
state3,
|
||||||
|
},
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet.clone(),
|
||||||
|
monero_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
AliceState::BtcPunished {
|
||||||
|
punished_tx_id,
|
||||||
|
tx_refund,
|
||||||
|
state3,
|
||||||
|
} => {
|
||||||
|
let punish_tx_finalised = bitcoin_wallet.wait_for_transaction_finality(punished_tx_id);
|
||||||
|
|
||||||
|
let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());
|
||||||
|
|
||||||
|
pin_mut!(punish_tx_finalised);
|
||||||
|
pin_mut!(refund_tx_seen);
|
||||||
|
|
||||||
|
match select(punish_tx_finalised, refund_tx_seen).await {
|
||||||
|
Either::Left(_) => {
|
||||||
|
swap(
|
||||||
|
AliceState::Punished,
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet.clone(),
|
||||||
|
monero_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Either::Right((published_refund_tx, _)) => {
|
||||||
|
swap(
|
||||||
|
AliceState::BtcRefunded {
|
||||||
|
tx_refund,
|
||||||
|
published_refund_tx,
|
||||||
|
state3,
|
||||||
|
},
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet.clone(),
|
||||||
|
monero_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AliceState::XmrRefunded => Ok(AliceState::XmrRefunded),
|
||||||
|
AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed),
|
||||||
|
AliceState::Punished => Ok(AliceState::Punished),
|
||||||
|
AliceState::SafelyAborted => Ok(AliceState::SafelyAborted),
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use swap::{
|
use swap::{alice::swap::swap, bob::swap::BobState, cli::Options, storage::Database};
|
||||||
bob_simple::{simple_swap, BobState},
|
|
||||||
cli::Options,
|
|
||||||
storage::Database,
|
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
@ -20,16 +15,7 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
match opt {
|
match opt {
|
||||||
Options::Alice { .. } => {
|
Options::Alice { .. } => {
|
||||||
simple_swap(
|
swap(bob_state, swarm, bitcoin_wallet, monero_wallet).await?;
|
||||||
bob_state,
|
|
||||||
swarm,
|
|
||||||
db,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
rng,
|
|
||||||
Uuid::new_v4(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
Options::Recover { .. } => {
|
Options::Recover { .. } => {
|
||||||
let _stored_state: BobState = unimplemented!("io.get_state(uuid)?");
|
let _stored_state: BobState = unimplemented!("io.get_state(uuid)?");
|
||||||
|
@ -99,7 +99,6 @@ impl BroadcastSignedTransaction for Wallet {
|
|||||||
|
|
||||||
// TODO: For retry, use `backoff::ExponentialBackoff` in production as opposed
|
// TODO: For retry, use `backoff::ExponentialBackoff` in production as opposed
|
||||||
// to `ConstantBackoff`.
|
// to `ConstantBackoff`.
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WatchForRawTransaction for Wallet {
|
impl WatchForRawTransaction for Wallet {
|
||||||
async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction {
|
async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction {
|
||||||
@ -112,6 +111,7 @@ impl WatchForRawTransaction for Wallet {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl GetRawTransaction for Wallet {
|
impl GetRawTransaction for Wallet {
|
||||||
|
// todo: potentially replace with option
|
||||||
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction> {
|
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction> {
|
||||||
Ok(self.0.get_raw_transaction(txid).await?)
|
Ok(self.0.get_raw_transaction(txid).await?)
|
||||||
}
|
}
|
||||||
@ -154,13 +154,6 @@ impl TransactionBlockHeight for Wallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl GetRawTransaction for Wallet {
|
|
||||||
async fn get_raw_transaction(&self, _txid: Txid) -> Option<Transaction> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WaitForTransactionFinality for Wallet {
|
impl WaitForTransactionFinality for Wallet {
|
||||||
async fn wait_for_transaction_finality(&self, _txid: Txid) {
|
async fn wait_for_transaction_finality(&self, _txid: Txid) {
|
||||||
|
127
swap/src/bob.rs
127
swap/src/bob.rs
@ -1,7 +1,7 @@
|
|||||||
//! Run an XMR/BTC swap in the role of Bob.
|
//! Run an XMR/BTC swap in the role of Bob.
|
||||||
//! Bob holds BTC and wishes receive XMR.
|
//! Bob holds BTC and wishes receive XMR.
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_recursion::async_recursion;
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
|
use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
|
||||||
use futures::{
|
use futures::{
|
||||||
@ -21,11 +21,11 @@ mod message0;
|
|||||||
mod message1;
|
mod message1;
|
||||||
mod message2;
|
mod message2;
|
||||||
mod message3;
|
mod message3;
|
||||||
|
pub mod swap;
|
||||||
|
|
||||||
use self::{amounts::*, message0::*, message1::*, message2::*, message3::*};
|
use self::{amounts::*, message0::*, message1::*, message2::*, message3::*};
|
||||||
use crate::{
|
use crate::{
|
||||||
bitcoin::{self, TX_LOCK_MINE_TIMEOUT},
|
bitcoin::{self, TX_LOCK_MINE_TIMEOUT},
|
||||||
io::Io,
|
|
||||||
monero,
|
monero,
|
||||||
network::{
|
network::{
|
||||||
peer_tracker::{self, PeerTracker},
|
peer_tracker::{self, PeerTracker},
|
||||||
@ -43,129 +43,6 @@ use xmr_btc::{
|
|||||||
monero::CreateWalletForOutput,
|
monero::CreateWalletForOutput,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The same data structure is used for swap execution and recovery.
|
|
||||||
// This allows for a seamless transition from a failed swap to recovery.
|
|
||||||
pub enum BobState {
|
|
||||||
Started,
|
|
||||||
Negotiated,
|
|
||||||
BtcLocked,
|
|
||||||
XmrLocked,
|
|
||||||
BtcRedeemed,
|
|
||||||
BtcRefunded,
|
|
||||||
XmrRedeemed,
|
|
||||||
Cancelled,
|
|
||||||
Punished,
|
|
||||||
SafelyAborted,
|
|
||||||
}
|
|
||||||
|
|
||||||
// State machine driver for swap execution
|
|
||||||
#[async_recursion]
|
|
||||||
pub async fn simple_swap(state: BobState, io: Io) -> Result<BobState> {
|
|
||||||
match state {
|
|
||||||
BobState::Started => {
|
|
||||||
// Alice and Bob exchange swap info
|
|
||||||
// Todo: Poll the swarm here until Alice and Bob have exchanged info
|
|
||||||
simple_swap(BobState::Negotiated, io).await
|
|
||||||
}
|
|
||||||
BobState::Negotiated => {
|
|
||||||
// Alice and Bob have exchanged info
|
|
||||||
// Bob Locks Btc
|
|
||||||
simple_swap(BobState::BtcLocked, io).await
|
|
||||||
}
|
|
||||||
BobState::BtcLocked => {
|
|
||||||
// Bob has locked Btc
|
|
||||||
// Watch for Alice to Lock Xmr
|
|
||||||
simple_swap(BobState::XmrLocked, io).await
|
|
||||||
}
|
|
||||||
BobState::XmrLocked => {
|
|
||||||
// Alice has locked Xmr
|
|
||||||
// Bob sends Alice his key
|
|
||||||
// Todo: This should be a oneshot
|
|
||||||
if unimplemented!("Redeemed before t1") {
|
|
||||||
simple_swap(BobState::BtcRedeemed, io).await
|
|
||||||
} else {
|
|
||||||
// submit TxCancel
|
|
||||||
simple_swap(BobState::Cancelled, io).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BobState::Cancelled => {
|
|
||||||
if unimplemented!("<t2") {
|
|
||||||
// submit TxRefund
|
|
||||||
simple_swap(BobState::BtcRefunded, io).await
|
|
||||||
} else {
|
|
||||||
simple_swap(BobState::Punished, io).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BobState::BtcRefunded => Ok(BobState::BtcRefunded),
|
|
||||||
BobState::BtcRedeemed => {
|
|
||||||
// Bob redeems XMR using revealed s_a
|
|
||||||
simple_swap(BobState::XmrRedeemed, io).await
|
|
||||||
}
|
|
||||||
BobState::Punished => Ok(BobState::Punished),
|
|
||||||
BobState::SafelyAborted => Ok(BobState::SafelyAborted),
|
|
||||||
BobState::XmrRedeemed => Ok(BobState::XmrRedeemed),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// State machine driver for recovery execution
|
|
||||||
#[async_recursion]
|
|
||||||
pub async fn abort(state: BobState, io: Io) -> Result<BobState> {
|
|
||||||
match state {
|
|
||||||
BobState::Started => {
|
|
||||||
// Nothing has been commited by either party, abort swap.
|
|
||||||
abort(BobState::SafelyAborted, io).await
|
|
||||||
}
|
|
||||||
BobState::Negotiated => {
|
|
||||||
// Nothing has been commited by either party, abort swap.
|
|
||||||
abort(BobState::SafelyAborted, io).await
|
|
||||||
}
|
|
||||||
BobState::BtcLocked => {
|
|
||||||
// Bob has locked BTC and must refund it
|
|
||||||
// Bob waits for alice to publish TxRedeem or t1
|
|
||||||
if unimplemented!("TxRedeemSeen") {
|
|
||||||
// Alice has redeemed revealing s_a
|
|
||||||
abort(BobState::BtcRedeemed, io).await
|
|
||||||
} else if unimplemented!("T1Elapsed") {
|
|
||||||
// publish TxCancel or see if it has been published
|
|
||||||
abort(BobState::Cancelled, io).await
|
|
||||||
} else {
|
|
||||||
Err(unimplemented!())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BobState::XmrLocked => {
|
|
||||||
// Alice has locked Xmr
|
|
||||||
// Wait until t1
|
|
||||||
if unimplemented!(">t1 and <t2") {
|
|
||||||
// Bob publishes TxCancel
|
|
||||||
abort(BobState::Cancelled, io).await
|
|
||||||
} else {
|
|
||||||
// >t2
|
|
||||||
// submit TxCancel
|
|
||||||
abort(BobState::Punished, io).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BobState::Cancelled => {
|
|
||||||
// Bob has cancelled the swap
|
|
||||||
// If <t2 Bob refunds
|
|
||||||
if unimplemented!("<t2") {
|
|
||||||
// Submit TxRefund
|
|
||||||
abort(BobState::BtcRefunded, io).await
|
|
||||||
} else {
|
|
||||||
// Bob failed to refund in time and has been punished
|
|
||||||
abort(BobState::Punished, io).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BobState::BtcRedeemed => {
|
|
||||||
// Bob uses revealed s_a to redeem XMR
|
|
||||||
abort(BobState::XmrRedeemed, io).await
|
|
||||||
}
|
|
||||||
BobState::BtcRefunded => Ok(BobState::BtcRefunded),
|
|
||||||
BobState::Punished => Ok(BobState::Punished),
|
|
||||||
BobState::SafelyAborted => Ok(BobState::SafelyAborted),
|
|
||||||
BobState::XmrRedeemed => Ok(BobState::XmrRedeemed),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn swap(
|
pub async fn swap(
|
||||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||||
|
@ -1,36 +1,24 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
bitcoin::{self},
|
|
||||||
bob::{OutEvent, Swarm},
|
bob::{OutEvent, Swarm},
|
||||||
network::{transport::SwapTransport, TokioExecutor},
|
|
||||||
state,
|
state,
|
||||||
storage::Database,
|
storage::Database,
|
||||||
Cmd, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
|
Cmd, Rsp, PUNISH_TIMELOCK, REFUND_TIMELOCK,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
use async_trait::async_trait;
|
|
||||||
use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
|
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::mpsc::{Receiver, Sender},
|
channel::mpsc::{Receiver, Sender},
|
||||||
future::Either,
|
StreamExt,
|
||||||
FutureExt, StreamExt,
|
|
||||||
};
|
};
|
||||||
use genawaiter::GeneratorState;
|
|
||||||
use libp2p::{core::identity::Keypair, Multiaddr, NetworkBehaviour, PeerId};
|
use libp2p::PeerId;
|
||||||
use rand::{rngs::OsRng, CryptoRng, RngCore};
|
use rand::rngs::OsRng;
|
||||||
use std::{process, sync::Arc, time::Duration};
|
use std::{process, sync::Arc};
|
||||||
use tokio::sync::Mutex;
|
|
||||||
use tracing::{debug, info, warn};
|
use tracing::info;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use xmr_btc::{
|
use xmr_btc::bob::{self, State0};
|
||||||
alice,
|
|
||||||
bitcoin::{
|
|
||||||
poll_until_block_height_is_gte, BroadcastSignedTransaction, EncryptedSignature, SignTxLock,
|
|
||||||
TransactionBlockHeight,
|
|
||||||
},
|
|
||||||
bob::{self, action_generator, ReceiveTransferProof, State0},
|
|
||||||
monero::CreateWalletForOutput,
|
|
||||||
};
|
|
||||||
|
|
||||||
// The same data structure is used for swap execution and recovery.
|
// The same data structure is used for swap execution and recovery.
|
||||||
// This allows for a seamless transition from a failed swap to recovery.
|
// This allows for a seamless transition from a failed swap to recovery.
|
||||||
@ -50,7 +38,7 @@ pub enum BobState {
|
|||||||
|
|
||||||
// State machine driver for swap execution
|
// State machine driver for swap execution
|
||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
pub async fn simple_swap(
|
pub async fn swap(
|
||||||
state: BobState,
|
state: BobState,
|
||||||
mut swarm: Swarm,
|
mut swarm: Swarm,
|
||||||
db: Database,
|
db: Database,
|
||||||
@ -122,7 +110,7 @@ pub async fn simple_swap(
|
|||||||
|
|
||||||
info!("Handshake complete");
|
info!("Handshake complete");
|
||||||
|
|
||||||
simple_swap(
|
swap(
|
||||||
BobState::Negotiated(state2, alice_peer_id),
|
BobState::Negotiated(state2, alice_peer_id),
|
||||||
swarm,
|
swarm,
|
||||||
db,
|
db,
|
||||||
@ -137,7 +125,7 @@ pub async fn simple_swap(
|
|||||||
// Alice and Bob have exchanged info
|
// Alice and Bob have exchanged info
|
||||||
let state3 = state2.lock_btc(bitcoin_wallet.as_ref()).await?;
|
let state3 = state2.lock_btc(bitcoin_wallet.as_ref()).await?;
|
||||||
// db.insert_latest_state(state);
|
// db.insert_latest_state(state);
|
||||||
simple_swap(
|
swap(
|
||||||
BobState::BtcLocked(state3, alice_peer_id),
|
BobState::BtcLocked(state3, alice_peer_id),
|
||||||
swarm,
|
swarm,
|
||||||
db,
|
db,
|
||||||
@ -160,7 +148,7 @@ pub async fn simple_swap(
|
|||||||
}
|
}
|
||||||
other => panic!("unexpected event: {:?}", other),
|
other => panic!("unexpected event: {:?}", other),
|
||||||
};
|
};
|
||||||
simple_swap(
|
swap(
|
||||||
BobState::XmrLocked(state4, alice_peer_id),
|
BobState::XmrLocked(state4, alice_peer_id),
|
||||||
swarm,
|
swarm,
|
||||||
db,
|
db,
|
||||||
@ -182,7 +170,7 @@ pub async fn simple_swap(
|
|||||||
// should happen in this arm?
|
// should happen in this arm?
|
||||||
swarm.send_message3(alice_peer_id.clone(), tx_redeem_encsig);
|
swarm.send_message3(alice_peer_id.clone(), tx_redeem_encsig);
|
||||||
|
|
||||||
simple_swap(
|
swap(
|
||||||
BobState::EncSigSent(state, alice_peer_id),
|
BobState::EncSigSent(state, alice_peer_id),
|
||||||
swarm,
|
swarm,
|
||||||
db,
|
db,
|
||||||
@ -200,7 +188,7 @@ pub async fn simple_swap(
|
|||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
val = redeem_watcher => {
|
val = redeem_watcher => {
|
||||||
simple_swap(
|
swap(
|
||||||
BobState::BtcRedeemed(val?),
|
BobState::BtcRedeemed(val?),
|
||||||
swarm,
|
swarm,
|
||||||
db,
|
db,
|
||||||
@ -211,14 +199,14 @@ pub async fn simple_swap(
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
val = t1_timeout => {
|
_ = t1_timeout => {
|
||||||
// Check whether TxCancel has been published.
|
// Check whether TxCancel has been published.
|
||||||
// We should not fail if the transaction is already on the blockchain
|
// We should not fail if the transaction is already on the blockchain
|
||||||
if let Err(_e) = state.check_for_tx_cancel(bitcoin_wallet.as_ref()).await {
|
if let Err(_) = state.check_for_tx_cancel(bitcoin_wallet.as_ref()).await {
|
||||||
state.submit_tx_cancel(bitcoin_wallet.as_ref()).await;
|
state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
simple_swap(
|
swap(
|
||||||
BobState::Cancelled(state),
|
BobState::Cancelled(state),
|
||||||
swarm,
|
swarm,
|
||||||
db,
|
db,
|
||||||
@ -235,7 +223,7 @@ pub async fn simple_swap(
|
|||||||
BobState::BtcRedeemed(state) => {
|
BobState::BtcRedeemed(state) => {
|
||||||
// Bob redeems XMR using revealed s_a
|
// Bob redeems XMR using revealed s_a
|
||||||
state.claim_xmr(monero_wallet.as_ref()).await?;
|
state.claim_xmr(monero_wallet.as_ref()).await?;
|
||||||
simple_swap(
|
swap(
|
||||||
BobState::XmrRedeemed,
|
BobState::XmrRedeemed,
|
||||||
swarm,
|
swarm,
|
||||||
db,
|
db,
|
||||||
@ -253,6 +241,7 @@ pub async fn simple_swap(
|
|||||||
BobState::XmrRedeemed => Ok(BobState::XmrRedeemed),
|
BobState::XmrRedeemed => Ok(BobState::XmrRedeemed),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// // State machine driver for recovery execution
|
// // State machine driver for recovery execution
|
||||||
// #[async_recursion]
|
// #[async_recursion]
|
||||||
// pub async fn abort(state: BobState, io: Io) -> Result<BobState> {
|
// pub async fn abort(state: BobState, io: Io) -> Result<BobState> {
|
@ -1,8 +0,0 @@
|
|||||||
// This struct contains all the I/O required to execute a swap
|
|
||||||
pub struct Io {
|
|
||||||
// swarm: libp2p::Swarm<>,
|
|
||||||
// bitcoind_rpc: _,
|
|
||||||
// monerod_rpc: _,
|
|
||||||
// monero_wallet_rpc: _,
|
|
||||||
// db: _,
|
|
||||||
}
|
|
@ -6,9 +6,7 @@ use std::fmt::{self, Display};
|
|||||||
pub mod alice;
|
pub mod alice;
|
||||||
pub mod bitcoin;
|
pub mod bitcoin;
|
||||||
pub mod bob;
|
pub mod bob;
|
||||||
pub mod bob_simple;
|
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod io;
|
|
||||||
pub mod monero;
|
pub mod monero;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod recover;
|
pub mod recover;
|
||||||
|
@ -186,6 +186,7 @@ pub trait WatchForRawTransaction {
|
|||||||
async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction;
|
async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
pub trait WaitForTransactionFinality {
|
pub trait WaitForTransactionFinality {
|
||||||
async fn wait_for_transaction_finality(&self, txid: Txid);
|
async fn wait_for_transaction_finality(&self, txid: Txid);
|
||||||
}
|
}
|
||||||
|
@ -879,63 +879,3 @@ impl State5 {
|
|||||||
self.tx_lock.txid()
|
self.tx_lock.txid()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Watch for the refund transaction on the blockchain. Watch until t2 has
|
|
||||||
/// elapsed.
|
|
||||||
pub async fn watch_for_refund_btc<W>(state: State5, bitcoin_wallet: &W) -> Result<()>
|
|
||||||
where
|
|
||||||
W: WatchForRawTransaction,
|
|
||||||
{
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
|
||||||
&state.tx_lock,
|
|
||||||
state.refund_timelock,
|
|
||||||
state.A.clone(),
|
|
||||||
state.b.public(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address);
|
|
||||||
|
|
||||||
let tx_refund_watcher = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch for refund transaction on the blockchain
|
|
||||||
pub async fn watch_for_redeem_btc<W>(state: State4, bitcoin_wallet: &W) -> Result<State5>
|
|
||||||
where
|
|
||||||
W: WatchForRawTransaction,
|
|
||||||
{
|
|
||||||
let tx_redeem = bitcoin::TxRedeem::new(&state.tx_lock, &state.redeem_address);
|
|
||||||
let tx_redeem_encsig = state
|
|
||||||
.b
|
|
||||||
.encsign(state.S_a_bitcoin.clone(), tx_redeem.digest());
|
|
||||||
|
|
||||||
let tx_redeem_candidate = bitcoin_wallet
|
|
||||||
.watch_for_raw_transaction(tx_redeem.txid())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let tx_redeem_sig =
|
|
||||||
tx_redeem.extract_signature_by_key(tx_redeem_candidate, state.b.public())?;
|
|
||||||
let s_a = bitcoin::recover(state.S_a_bitcoin.clone(), tx_redeem_sig, tx_redeem_encsig)?;
|
|
||||||
let s_a = monero::private_key_from_secp256k1_scalar(s_a.into());
|
|
||||||
|
|
||||||
Ok(State5 {
|
|
||||||
A: state.A.clone(),
|
|
||||||
b: state.b.clone(),
|
|
||||||
s_a,
|
|
||||||
s_b: state.s_b,
|
|
||||||
S_a_monero: state.S_a_monero,
|
|
||||||
S_a_bitcoin: state.S_a_bitcoin.clone(),
|
|
||||||
v: state.v,
|
|
||||||
btc: state.btc,
|
|
||||||
xmr: state.xmr,
|
|
||||||
refund_timelock: state.refund_timelock,
|
|
||||||
punish_timelock: state.punish_timelock,
|
|
||||||
refund_address: state.refund_address.clone(),
|
|
||||||
redeem_address: state.redeem_address.clone(),
|
|
||||||
punish_address: state.punish_address.clone(),
|
|
||||||
tx_lock: state.tx_lock.clone(),
|
|
||||||
tx_refund_encsig: state.tx_refund_encsig.clone(),
|
|
||||||
tx_cancel_sig: state.tx_cancel_sig_a.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user