From 6db40dce2a99a500f1d54c511f0e5e9607a25afc Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Thu, 5 Nov 2020 15:48:31 +1100 Subject: [PATCH] Implement swap recover function for Bob --- swap/Cargo.toml | 1 + swap/src/recover.rs | 117 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 105 insertions(+), 13 deletions(-) diff --git a/swap/Cargo.toml b/swap/Cargo.toml index e8ab1323..e1371f91 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -29,6 +29,7 @@ serde = { version = "1", features = ["derive"] } serde_cbor = "0.11" serde_derive = "1.0" serde_json = "1" +sha2 = "0.9" sled = "0.34" structopt = "0.3" tempfile = "3" diff --git a/swap/src/recover.rs b/swap/src/recover.rs index 87cb11ee..f3addbf0 100644 --- a/swap/src/recover.rs +++ b/swap/src/recover.rs @@ -3,13 +3,15 @@ use crate::{ state::{Alice, Bob, Swap}, }; use anyhow::Result; +use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic}; use futures::{ future::{select, Either}, pin_mut, }; +use sha2::Sha256; use xmr_btc::bitcoin::{ poll_until_block_height_is_gte, BroadcastSignedTransaction, TransactionBlockHeight, TxCancel, - TxPunish, TxRefund, WatchForRawTransaction, + TxPunish, TxRedeem, TxRefund, WatchForRawTransaction, }; pub async fn recover( @@ -29,7 +31,7 @@ pub async fn alice_recover( state: Alice, ) -> Result<()> { match state { - Alice::Handshaken(_) | Alice::BtcLocked(_) | Alice::SwapComplete => Ok(()), + Alice::Handshaken(_) | Alice::BtcLocked(_) | Alice::SwapComplete => {} Alice::XmrLocked(state) => { let tx_cancel = TxCancel::new( &state.tx_lock, @@ -131,14 +133,11 @@ pub async fn alice_recover( .await?; } }; - - Ok(()) } Alice::BtcRedeemable { redeem_tx, .. } => { bitcoin_wallet .broadcast_signed_transaction(redeem_tx) .await?; - Ok(()) } Alice::BtcPunishable(state) => { let tx_cancel = TxCancel::new( @@ -162,8 +161,6 @@ pub async fn alice_recover( bitcoin_wallet .broadcast_signed_transaction(sig_tx_punish) .await?; - - Ok(()) } Alice::BtcRefunded { view_key, @@ -173,15 +170,109 @@ pub async fn alice_recover( monero_wallet .create_and_load_wallet_for_output(spend_key, view_key) .await?; - Ok(()) } - } + }; + + Ok(()) } pub async fn bob_recover( - _bitcoin_wallet: crate::bitcoin::Wallet, - _monero_wallet: crate::monero::Wallet, - _state: Bob, + bitcoin_wallet: crate::bitcoin::Wallet, + monero_wallet: crate::monero::Wallet, + state: Bob, ) -> Result<()> { - todo!() + match state { + Bob::Handshaken(_) | Bob::SwapComplete => {} + Bob::BtcLocked(state) | Bob::XmrLocked(state) | Bob::BtcRefundable(state) => { + let tx_cancel = TxCancel::new( + &state.tx_lock, + state.refund_timelock, + state.A.clone(), + state.b.public(), + ); + + // Ensure that TxCancel is on the blockchain + if bitcoin_wallet + .0 + .get_raw_transaction(tx_cancel.txid()) + .await + .is_err() + { + let tx_lock_height = bitcoin_wallet + .transaction_block_height(state.tx_lock.txid()) + .await; + poll_until_block_height_is_gte( + &bitcoin_wallet, + tx_lock_height + state.refund_timelock, + ) + .await; + + let sig_a = state.tx_cancel_sig_a.clone(); + let sig_b = state.b.sign(tx_cancel.digest()); + + let tx_cancel = tx_cancel + .clone() + .add_signatures( + &state.tx_lock, + (state.A.clone(), sig_a), + (state.b.public(), sig_b), + ) + .expect("sig_{a,b} to be valid signatures for tx_cancel"); + + // TODO: We should not fail if the transaction is already on the blockchain + bitcoin_wallet + .broadcast_signed_transaction(tx_cancel) + .await?; + } + + let tx_refund = TxRefund::new(&tx_cancel, &state.refund_address); + let signed_tx_refund = { + let adaptor = Adaptor::>::default(); + let sig_a = adaptor + .decrypt_signature(&state.s_b.into_secp256k1(), state.tx_refund_encsig.clone()); + let sig_b = state.b.sign(tx_refund.digest()); + + tx_refund + .add_signatures( + &tx_cancel, + (state.A.clone(), sig_a), + (state.b.public(), sig_b), + ) + .expect("sig_{a,b} to be valid signatures for tx_refund") + }; + + bitcoin_wallet + .broadcast_signed_transaction(signed_tx_refund) + .await?; + } + Bob::BtcRedeemed(state) => { + let tx_redeem = TxRedeem::new(&state.tx_lock, &state.redeem_address); + let tx_redeem_published = bitcoin_wallet + .0 + .get_raw_transaction(tx_redeem.txid()) + .await?; + + let tx_redeem_encsig = state + .b + .encsign(state.S_a_bitcoin.clone(), tx_redeem.digest()); + let tx_redeem_sig = + tx_redeem.extract_signature_by_key(tx_redeem_published, state.b.public())?; + + let s_a = + xmr_btc::bitcoin::recover(state.S_a_bitcoin, tx_redeem_sig, tx_redeem_encsig)?; + let s_a = monero::PrivateKey::from_scalar( + xmr_btc::monero::Scalar::from_bytes_mod_order(s_a.to_bytes()), + ); + + let s_b = monero::PrivateKey { + scalar: state.s_b.into_ed25519(), + }; + + monero_wallet + .create_and_load_wallet_for_output(s_a + s_b, state.v) + .await?; + } + }; + + Ok(()) }