mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-12-18 04:14:32 -05:00
Merge pull request #2 from comit-network/async-squashed
Execute Alice and Bob state machines concurrently
This commit is contained in:
commit
36608657a5
147
.gitignore
vendored
147
.gitignore
vendored
@ -1,3 +1,148 @@
|
|||||||
|
|
||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/rust,clion+all,emacs
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=rust,clion+all,emacs
|
||||||
|
|
||||||
|
### CLion+all ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### CLion+all Patch ###
|
||||||
|
# Ignores the whole .idea folder and all .iml files
|
||||||
|
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||||
|
|
||||||
|
*.iml
|
||||||
|
modules.xml
|
||||||
|
.idea/misc.xml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
# Sonarlint plugin
|
||||||
|
.idea/sonarlint
|
||||||
|
|
||||||
|
### Emacs ###
|
||||||
|
# -*- mode: gitignore; -*-
|
||||||
|
*~
|
||||||
|
\#*\#
|
||||||
|
/.emacs.desktop
|
||||||
|
/.emacs.desktop.lock
|
||||||
|
*.elc
|
||||||
|
auto-save-list
|
||||||
|
tramp
|
||||||
|
.\#*
|
||||||
|
|
||||||
|
# Org-mode
|
||||||
|
.org-id-locations
|
||||||
|
*_archive
|
||||||
|
|
||||||
|
# flymake-mode
|
||||||
|
*_flymake.*
|
||||||
|
|
||||||
|
# eshell files
|
||||||
|
/eshell/history
|
||||||
|
/eshell/lastdir
|
||||||
|
|
||||||
|
# elpa packages
|
||||||
|
/elpa/
|
||||||
|
|
||||||
|
# reftex files
|
||||||
|
*.rel
|
||||||
|
|
||||||
|
# AUCTeX auto folder
|
||||||
|
/auto/
|
||||||
|
|
||||||
|
# cask packages
|
||||||
|
.cask/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Flycheck
|
||||||
|
flycheck_*.el
|
||||||
|
|
||||||
|
# server auth directory
|
||||||
|
/server/
|
||||||
|
|
||||||
|
# projectiles files
|
||||||
|
.projectile
|
||||||
|
|
||||||
|
# directory configuration
|
||||||
|
.dir-locals.el
|
||||||
|
|
||||||
|
# network security
|
||||||
|
/network-security.data
|
||||||
|
|
||||||
|
|
||||||
|
### Rust ###
|
||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
/target/
|
/target/
|
||||||
@ -8,3 +153,5 @@ Cargo.lock
|
|||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/rust,clion+all,emacs
|
||||||
|
@ -17,11 +17,15 @@ monero = "0.9"
|
|||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
sha2 = "0.9"
|
sha2 = "0.9"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
tracing = "0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
base64 = "0.12"
|
base64 = "0.12"
|
||||||
bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "d402b36d3d6406150e3bfb71492ff4a0a7cb290e" }
|
bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "d402b36d3d6406150e3bfb71492ff4a0a7cb290e" }
|
||||||
|
futures = "0.3"
|
||||||
monero-harness = { path = "../monero-harness" }
|
monero-harness = { path = "../monero-harness" }
|
||||||
reqwest = { version = "0.10", default-features = false }
|
reqwest = { version = "0.10", default-features = false }
|
||||||
testcontainers = "0.10"
|
testcontainers = "0.10"
|
||||||
tokio = { version = "0.2", default-features = false, features = ["blocking", "macros", "rt-core", "time", "rt-threaded"] }
|
tokio = { version = "0.2", default-features = false, features = ["blocking", "macros", "rt-core", "time", "rt-threaded"] }
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = "0.2"
|
||||||
|
@ -1,31 +1,129 @@
|
|||||||
|
use crate::{
|
||||||
|
bitcoin,
|
||||||
|
bitcoin::{BroadcastSignedTransaction, WatchForRawTransaction},
|
||||||
|
bob, monero,
|
||||||
|
monero::{CreateWalletForOutput, Transfer},
|
||||||
|
transport::{ReceiveMessage, SendMessage},
|
||||||
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use ecdsa_fun::adaptor::{Adaptor, EncryptedSignature};
|
use ecdsa_fun::{
|
||||||
|
adaptor::{Adaptor, EncryptedSignature},
|
||||||
|
nonce::Deterministic,
|
||||||
|
};
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
|
|
||||||
use crate::{bitcoin, bitcoin::GetRawTransaction, bob, monero, monero::ImportOutput};
|
|
||||||
use ecdsa_fun::{nonce::Deterministic, Signature};
|
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub mod message;
|
||||||
pub struct Message0 {
|
pub use message::{Message, Message0, Message1, Message2};
|
||||||
pub(crate) A: bitcoin::PublicKey,
|
|
||||||
pub(crate) S_a_monero: monero::PublicKey,
|
pub async fn next_state<
|
||||||
pub(crate) S_a_bitcoin: bitcoin::PublicKey,
|
R: RngCore + CryptoRng,
|
||||||
pub(crate) dleq_proof_s_a: cross_curve_dleq::Proof,
|
B: WatchForRawTransaction + BroadcastSignedTransaction,
|
||||||
pub(crate) v_a: monero::PrivateViewKey,
|
M: CreateWalletForOutput + Transfer,
|
||||||
pub(crate) redeem_address: bitcoin::Address,
|
T: SendMessage<Message> + ReceiveMessage<bob::Message>,
|
||||||
pub(crate) punish_address: bitcoin::Address,
|
>(
|
||||||
|
bitcoin_wallet: &B,
|
||||||
|
monero_wallet: &M,
|
||||||
|
transport: &mut T,
|
||||||
|
state: State,
|
||||||
|
rng: &mut R,
|
||||||
|
) -> Result<State> {
|
||||||
|
match state {
|
||||||
|
State::State0(state0) => {
|
||||||
|
transport
|
||||||
|
.send_message(state0.next_message(rng).into())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let bob_message0 = transport.receive_message().await?.try_into()?;
|
||||||
|
let state1 = state0.receive(bob_message0)?;
|
||||||
|
Ok(state1.into())
|
||||||
|
}
|
||||||
|
State::State1(state1) => {
|
||||||
|
let bob_message1 = transport.receive_message().await?.try_into()?;
|
||||||
|
let state2 = state1.receive(bob_message1);
|
||||||
|
let alice_message1 = state2.next_message();
|
||||||
|
transport.send_message(alice_message1.into()).await?;
|
||||||
|
Ok(state2.into())
|
||||||
|
}
|
||||||
|
State::State2(state2) => {
|
||||||
|
let bob_message2 = transport.receive_message().await?.try_into()?;
|
||||||
|
let state3 = state2.receive(bob_message2)?;
|
||||||
|
Ok(state3.into())
|
||||||
|
}
|
||||||
|
State::State3(state3) => {
|
||||||
|
tracing::info!("alice is watching for locked btc");
|
||||||
|
let state4 = state3.watch_for_lock_btc(bitcoin_wallet).await?;
|
||||||
|
Ok(state4.into())
|
||||||
|
}
|
||||||
|
State::State4(state4) => {
|
||||||
|
let state5 = state4.lock_xmr(monero_wallet).await?;
|
||||||
|
tracing::info!("alice has locked xmr");
|
||||||
|
Ok(state5.into())
|
||||||
|
}
|
||||||
|
State::State5(state5) => {
|
||||||
|
transport.send_message(state5.next_message().into()).await?;
|
||||||
|
// todo: pass in state4b as a parameter somewhere in this call to prevent the
|
||||||
|
// user from waiting for a message that wont be sent
|
||||||
|
let message3 = transport.receive_message().await?.try_into()?;
|
||||||
|
let state6 = state5.receive(message3);
|
||||||
|
tracing::info!("alice has received bob message 3");
|
||||||
|
tracing::info!("alice is redeeming btc");
|
||||||
|
state6.redeem_btc(bitcoin_wallet).await?;
|
||||||
|
Ok(state6.into())
|
||||||
|
}
|
||||||
|
State::State6(state6) => Ok(state6.into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Message1 {
|
pub enum State {
|
||||||
pub(crate) tx_cancel_sig: Signature,
|
State0(State0),
|
||||||
pub(crate) tx_refund_encsig: EncryptedSignature,
|
State1(State1),
|
||||||
|
State2(State2),
|
||||||
|
State3(State3),
|
||||||
|
State4(State4),
|
||||||
|
State5(State5),
|
||||||
|
State6(State6),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl_try_from_parent_enum!(State0, State);
|
||||||
pub struct Message2 {
|
impl_try_from_parent_enum!(State1, State);
|
||||||
pub(crate) tx_lock_proof: monero::TransferProof,
|
impl_try_from_parent_enum!(State2, State);
|
||||||
|
impl_try_from_parent_enum!(State3, State);
|
||||||
|
impl_try_from_parent_enum!(State4, State);
|
||||||
|
impl_try_from_parent_enum!(State5, State);
|
||||||
|
impl_try_from_parent_enum!(State6, State);
|
||||||
|
|
||||||
|
impl_from_child_enum!(State0, State);
|
||||||
|
impl_from_child_enum!(State1, State);
|
||||||
|
impl_from_child_enum!(State2, State);
|
||||||
|
impl_from_child_enum!(State3, State);
|
||||||
|
impl_from_child_enum!(State4, State);
|
||||||
|
impl_from_child_enum!(State5, State);
|
||||||
|
impl_from_child_enum!(State6, State);
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn new<R: RngCore + CryptoRng>(
|
||||||
|
rng: &mut R,
|
||||||
|
btc: bitcoin::Amount,
|
||||||
|
xmr: monero::Amount,
|
||||||
|
refund_timelock: u32,
|
||||||
|
punish_timelock: u32,
|
||||||
|
redeem_address: bitcoin::Address,
|
||||||
|
punish_address: bitcoin::Address,
|
||||||
|
) -> Self {
|
||||||
|
Self::State0(State0::new(
|
||||||
|
rng,
|
||||||
|
btc,
|
||||||
|
xmr,
|
||||||
|
refund_timelock,
|
||||||
|
punish_timelock,
|
||||||
|
redeem_address,
|
||||||
|
punish_address,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -250,12 +348,15 @@ pub struct State3 {
|
|||||||
impl State3 {
|
impl State3 {
|
||||||
pub async fn watch_for_lock_btc<W>(self, bitcoin_wallet: &W) -> Result<State4>
|
pub async fn watch_for_lock_btc<W>(self, bitcoin_wallet: &W) -> Result<State4>
|
||||||
where
|
where
|
||||||
W: bitcoin::GetRawTransaction,
|
W: bitcoin::WatchForRawTransaction,
|
||||||
{
|
{
|
||||||
let _ = bitcoin_wallet
|
tracing::info!("watching for lock btc with txid: {}", self.tx_lock.txid());
|
||||||
.get_raw_transaction(self.tx_lock.txid())
|
let tx = bitcoin_wallet
|
||||||
|
.watch_for_raw_transaction(self.tx_lock.txid())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
tracing::info!("tx lock seen with txid: {}", tx.txid());
|
||||||
|
|
||||||
Ok(State4 {
|
Ok(State4 {
|
||||||
a: self.a,
|
a: self.a,
|
||||||
B: self.B,
|
B: self.B,
|
||||||
@ -298,7 +399,7 @@ pub struct State4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl State4 {
|
impl State4 {
|
||||||
pub async fn lock_xmr<W>(self, monero_wallet: &W) -> Result<(State4b, monero::Amount)>
|
pub async fn lock_xmr<W>(self, monero_wallet: &W) -> Result<State5>
|
||||||
where
|
where
|
||||||
W: monero::Transfer,
|
W: monero::Transfer,
|
||||||
{
|
{
|
||||||
@ -311,28 +412,26 @@ impl State4 {
|
|||||||
.transfer(S_a + S_b, self.v.public(), self.xmr)
|
.transfer(S_a + S_b, self.v.public(), self.xmr)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok((
|
Ok(State5 {
|
||||||
State4b {
|
a: self.a,
|
||||||
a: self.a,
|
B: self.B,
|
||||||
B: self.B,
|
s_a: self.s_a,
|
||||||
s_a: self.s_a,
|
S_b_monero: self.S_b_monero,
|
||||||
S_b_monero: self.S_b_monero,
|
S_b_bitcoin: self.S_b_bitcoin,
|
||||||
S_b_bitcoin: self.S_b_bitcoin,
|
v: self.v,
|
||||||
v: self.v,
|
btc: self.btc,
|
||||||
btc: self.btc,
|
xmr: self.xmr,
|
||||||
xmr: self.xmr,
|
refund_timelock: self.refund_timelock,
|
||||||
refund_timelock: self.refund_timelock,
|
punish_timelock: self.punish_timelock,
|
||||||
punish_timelock: self.punish_timelock,
|
refund_address: self.refund_address,
|
||||||
refund_address: self.refund_address,
|
redeem_address: self.redeem_address,
|
||||||
redeem_address: self.redeem_address,
|
punish_address: self.punish_address,
|
||||||
punish_address: self.punish_address,
|
tx_lock: self.tx_lock,
|
||||||
tx_lock: self.tx_lock,
|
tx_lock_proof,
|
||||||
tx_lock_proof,
|
tx_punish_sig_bob: self.tx_punish_sig_bob,
|
||||||
tx_punish_sig_bob: self.tx_punish_sig_bob,
|
tx_cancel_sig_bob: self.tx_cancel_sig_bob,
|
||||||
tx_cancel_sig_bob: self.tx_cancel_sig_bob,
|
lock_xmr_fee: fee,
|
||||||
},
|
})
|
||||||
fee,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn punish<W: bitcoin::BroadcastSignedTransaction>(
|
pub async fn punish<W: bitcoin::BroadcastSignedTransaction>(
|
||||||
@ -383,7 +482,7 @@ impl State4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct State4b {
|
pub struct State5 {
|
||||||
a: bitcoin::SecretKey,
|
a: bitcoin::SecretKey,
|
||||||
B: bitcoin::PublicKey,
|
B: bitcoin::PublicKey,
|
||||||
s_a: cross_curve_dleq::Scalar,
|
s_a: cross_curve_dleq::Scalar,
|
||||||
@ -401,17 +500,18 @@ pub struct State4b {
|
|||||||
tx_lock_proof: monero::TransferProof,
|
tx_lock_proof: monero::TransferProof,
|
||||||
tx_punish_sig_bob: bitcoin::Signature,
|
tx_punish_sig_bob: bitcoin::Signature,
|
||||||
tx_cancel_sig_bob: bitcoin::Signature,
|
tx_cancel_sig_bob: bitcoin::Signature,
|
||||||
|
lock_xmr_fee: monero::Amount,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State4b {
|
impl State5 {
|
||||||
pub fn next_message(&self) -> Message2 {
|
pub fn next_message(&self) -> Message2 {
|
||||||
Message2 {
|
Message2 {
|
||||||
tx_lock_proof: self.tx_lock_proof.clone(),
|
tx_lock_proof: self.tx_lock_proof.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn receive(self, msg: bob::Message3) -> State5 {
|
pub fn receive(self, msg: bob::Message3) -> State6 {
|
||||||
State5 {
|
State6 {
|
||||||
a: self.a,
|
a: self.a,
|
||||||
B: self.B,
|
B: self.B,
|
||||||
s_a: self.s_a,
|
s_a: self.s_a,
|
||||||
@ -428,14 +528,15 @@ impl State4b {
|
|||||||
tx_lock: self.tx_lock,
|
tx_lock: self.tx_lock,
|
||||||
tx_punish_sig_bob: self.tx_punish_sig_bob,
|
tx_punish_sig_bob: self.tx_punish_sig_bob,
|
||||||
tx_redeem_encsig: msg.tx_redeem_encsig,
|
tx_redeem_encsig: msg.tx_redeem_encsig,
|
||||||
|
lock_xmr_fee: self.lock_xmr_fee,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// watch for refund on btc, recover s_b and refund xmr
|
// watch for refund on btc, recover s_b and refund xmr
|
||||||
pub async fn refund_xmr<B, M>(self, bitcoin_wallet: &B, monero_wallet: &M) -> Result<()>
|
pub async fn refund_xmr<B, M>(self, bitcoin_wallet: &B, monero_wallet: &M) -> Result<()>
|
||||||
where
|
where
|
||||||
B: GetRawTransaction,
|
B: WatchForRawTransaction,
|
||||||
M: ImportOutput,
|
M: CreateWalletForOutput,
|
||||||
{
|
{
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
let tx_cancel = bitcoin::TxCancel::new(
|
||||||
&self.tx_lock,
|
&self.tx_lock,
|
||||||
@ -448,7 +549,9 @@ impl State4b {
|
|||||||
|
|
||||||
let tx_refund_encsig = self.a.encsign(self.S_b_bitcoin.clone(), tx_refund.digest());
|
let tx_refund_encsig = self.a.encsign(self.S_b_bitcoin.clone(), tx_refund.digest());
|
||||||
|
|
||||||
let tx_refund_candidate = bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?;
|
let tx_refund_candidate = bitcoin_wallet
|
||||||
|
.watch_for_raw_transaction(tx_refund.txid())
|
||||||
|
.await?;
|
||||||
|
|
||||||
let tx_refund_sig =
|
let tx_refund_sig =
|
||||||
tx_refund.extract_signature_by_key(tx_refund_candidate, self.a.public())?;
|
tx_refund.extract_signature_by_key(tx_refund_candidate, self.a.public())?;
|
||||||
@ -462,7 +565,7 @@ impl State4b {
|
|||||||
// NOTE: This actually generates and opens a new wallet, closing the currently
|
// NOTE: This actually generates and opens a new wallet, closing the currently
|
||||||
// open one.
|
// open one.
|
||||||
monero_wallet
|
monero_wallet
|
||||||
.import_output(monero::PrivateKey::from_scalar(s), self.v)
|
.create_and_load_wallet_for_output(monero::PrivateKey::from_scalar(s), self.v)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -470,7 +573,7 @@ impl State4b {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct State5 {
|
pub struct State6 {
|
||||||
a: bitcoin::SecretKey,
|
a: bitcoin::SecretKey,
|
||||||
B: bitcoin::PublicKey,
|
B: bitcoin::PublicKey,
|
||||||
s_a: cross_curve_dleq::Scalar,
|
s_a: cross_curve_dleq::Scalar,
|
||||||
@ -487,9 +590,10 @@ pub struct State5 {
|
|||||||
tx_lock: bitcoin::TxLock,
|
tx_lock: bitcoin::TxLock,
|
||||||
tx_punish_sig_bob: bitcoin::Signature,
|
tx_punish_sig_bob: bitcoin::Signature,
|
||||||
tx_redeem_encsig: EncryptedSignature,
|
tx_redeem_encsig: EncryptedSignature,
|
||||||
|
lock_xmr_fee: monero::Amount,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State5 {
|
impl State6 {
|
||||||
pub async fn redeem_btc<W: bitcoin::BroadcastSignedTransaction>(
|
pub async fn redeem_btc<W: bitcoin::BroadcastSignedTransaction>(
|
||||||
&self,
|
&self,
|
||||||
bitcoin_wallet: &W,
|
bitcoin_wallet: &W,
|
||||||
@ -513,4 +617,8 @@ impl State5 {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lock_xmr_fee(&self) -> monero::Amount {
|
||||||
|
self.lock_xmr_fee
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
42
xmr-btc/src/alice/message.rs
Normal file
42
xmr-btc/src/alice/message.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use ecdsa_fun::{adaptor::EncryptedSignature, Signature};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use crate::{bitcoin, monero};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Message {
|
||||||
|
Message0(Message0),
|
||||||
|
Message1(Message1),
|
||||||
|
Message2(Message2),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Message0 {
|
||||||
|
pub(crate) A: bitcoin::PublicKey,
|
||||||
|
pub(crate) S_a_monero: monero::PublicKey,
|
||||||
|
pub(crate) S_a_bitcoin: bitcoin::PublicKey,
|
||||||
|
pub(crate) dleq_proof_s_a: cross_curve_dleq::Proof,
|
||||||
|
pub(crate) v_a: monero::PrivateViewKey,
|
||||||
|
pub(crate) redeem_address: bitcoin::Address,
|
||||||
|
pub(crate) punish_address: bitcoin::Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Message1 {
|
||||||
|
pub(crate) tx_cancel_sig: Signature,
|
||||||
|
pub(crate) tx_refund_encsig: EncryptedSignature,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Message2 {
|
||||||
|
pub(crate) tx_lock_proof: monero::TransferProof,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_try_from_parent_enum!(Message0, Message);
|
||||||
|
impl_try_from_parent_enum!(Message1, Message);
|
||||||
|
impl_try_from_parent_enum!(Message2, Message);
|
||||||
|
|
||||||
|
impl_from_child_enum!(Message0, Message);
|
||||||
|
impl_from_child_enum!(Message1, Message);
|
||||||
|
impl_from_child_enum!(Message2, Message);
|
@ -1,6 +1,4 @@
|
|||||||
pub mod transactions;
|
pub mod transactions;
|
||||||
#[cfg(test)]
|
|
||||||
pub mod wallet;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@ -10,6 +8,7 @@ use bitcoin::{
|
|||||||
util::psbt::PartiallySignedTransaction,
|
util::psbt::PartiallySignedTransaction,
|
||||||
SigHash, Transaction,
|
SigHash, Transaction,
|
||||||
};
|
};
|
||||||
|
pub use bitcoin::{Address, Amount, OutPoint, Txid};
|
||||||
use ecdsa_fun::{
|
use ecdsa_fun::{
|
||||||
adaptor::Adaptor,
|
adaptor::Adaptor,
|
||||||
fun::{
|
fun::{
|
||||||
@ -19,17 +18,13 @@ use ecdsa_fun::{
|
|||||||
nonce::Deterministic,
|
nonce::Deterministic,
|
||||||
ECDSA,
|
ECDSA,
|
||||||
};
|
};
|
||||||
|
pub use ecdsa_fun::{adaptor::EncryptedSignature, Signature};
|
||||||
use miniscript::{Descriptor, Segwitv0};
|
use miniscript::{Descriptor, Segwitv0};
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
pub use crate::bitcoin::transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund};
|
pub use crate::bitcoin::transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund};
|
||||||
pub use bitcoin::{Address, Amount, OutPoint, Txid};
|
|
||||||
pub use ecdsa_fun::{adaptor::EncryptedSignature, Signature};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub use wallet::{make_wallet, Wallet};
|
|
||||||
|
|
||||||
pub const TX_FEE: u64 = 10_000;
|
pub const TX_FEE: u64 = 10_000;
|
||||||
|
|
||||||
@ -193,8 +188,8 @@ pub trait BroadcastSignedTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait GetRawTransaction {
|
pub trait WatchForRawTransaction {
|
||||||
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction>;
|
async fn watch_for_raw_transaction(&self, txid: Txid) -> Result<Transaction>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Result<SecretKey> {
|
pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Result<SecretKey> {
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
alice,
|
alice,
|
||||||
bitcoin::{self, BuildTxLockPsbt, GetRawTransaction, TxCancel},
|
bitcoin::{
|
||||||
|
self, BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, TxCancel,
|
||||||
|
WatchForRawTransaction,
|
||||||
|
},
|
||||||
monero,
|
monero,
|
||||||
|
monero::{CheckTransfer, CreateWalletForOutput},
|
||||||
|
transport::{ReceiveMessage, SendMessage},
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use ecdsa_fun::{
|
use ecdsa_fun::{
|
||||||
@ -11,32 +16,88 @@ use ecdsa_fun::{
|
|||||||
};
|
};
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub mod message;
|
||||||
pub struct Message0 {
|
pub use message::{Message, Message0, Message1, Message2, Message3};
|
||||||
pub(crate) B: bitcoin::PublicKey,
|
|
||||||
pub(crate) S_b_monero: monero::PublicKey,
|
pub async fn next_state<
|
||||||
pub(crate) S_b_bitcoin: bitcoin::PublicKey,
|
R: RngCore + CryptoRng,
|
||||||
pub(crate) dleq_proof_s_b: cross_curve_dleq::Proof,
|
B: WatchForRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction,
|
||||||
pub(crate) v_b: monero::PrivateViewKey,
|
M: CreateWalletForOutput + CheckTransfer,
|
||||||
pub(crate) refund_address: bitcoin::Address,
|
T: SendMessage<Message> + ReceiveMessage<alice::Message>,
|
||||||
|
>(
|
||||||
|
bitcoin_wallet: &B,
|
||||||
|
monero_wallet: &M,
|
||||||
|
transport: &mut T,
|
||||||
|
state: State,
|
||||||
|
rng: &mut R,
|
||||||
|
) -> Result<State> {
|
||||||
|
match state {
|
||||||
|
State::State0(state0) => {
|
||||||
|
transport
|
||||||
|
.send_message(state0.next_message(rng).into())
|
||||||
|
.await?;
|
||||||
|
let message0 = transport.receive_message().await?.try_into()?;
|
||||||
|
let state1 = state0.receive(bitcoin_wallet, message0).await?;
|
||||||
|
Ok(state1.into())
|
||||||
|
}
|
||||||
|
State::State1(state1) => {
|
||||||
|
transport.send_message(state1.next_message().into()).await?;
|
||||||
|
|
||||||
|
let message1 = transport.receive_message().await?.try_into()?;
|
||||||
|
let state2 = state1.receive(message1)?;
|
||||||
|
Ok(state2.into())
|
||||||
|
}
|
||||||
|
State::State2(state2) => {
|
||||||
|
let message2 = state2.next_message();
|
||||||
|
let state3 = state2.lock_btc(bitcoin_wallet).await?;
|
||||||
|
tracing::info!("bob has locked btc");
|
||||||
|
transport.send_message(message2.into()).await?;
|
||||||
|
Ok(state3.into())
|
||||||
|
}
|
||||||
|
State::State3(state3) => {
|
||||||
|
let message2 = transport.receive_message().await?.try_into()?;
|
||||||
|
let state4 = state3.watch_for_lock_xmr(monero_wallet, message2).await?;
|
||||||
|
tracing::info!("bob has seen that alice has locked xmr");
|
||||||
|
Ok(state4.into())
|
||||||
|
}
|
||||||
|
State::State4(state4) => {
|
||||||
|
transport.send_message(state4.next_message().into()).await?;
|
||||||
|
tracing::info!("bob is watching for redeem_btc");
|
||||||
|
let state5 = state4.watch_for_redeem_btc(bitcoin_wallet).await?;
|
||||||
|
tracing::info!("bob has seen that alice has redeemed btc");
|
||||||
|
state5.claim_xmr(monero_wallet).await?;
|
||||||
|
tracing::info!("bob has claimed xmr");
|
||||||
|
Ok(state5.into())
|
||||||
|
}
|
||||||
|
State::State5(state5) => Ok(state5.into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Message1 {
|
pub enum State {
|
||||||
pub(crate) tx_lock: bitcoin::TxLock,
|
State0(State0),
|
||||||
|
State1(State1),
|
||||||
|
State2(State2),
|
||||||
|
State3(State3),
|
||||||
|
State4(State4),
|
||||||
|
State5(State5),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl_try_from_parent_enum!(State0, State);
|
||||||
pub struct Message2 {
|
impl_try_from_parent_enum!(State1, State);
|
||||||
pub(crate) tx_punish_sig: Signature,
|
impl_try_from_parent_enum!(State2, State);
|
||||||
pub(crate) tx_cancel_sig: Signature,
|
impl_try_from_parent_enum!(State3, State);
|
||||||
}
|
impl_try_from_parent_enum!(State4, State);
|
||||||
|
impl_try_from_parent_enum!(State5, State);
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl_from_child_enum!(State0, State);
|
||||||
pub struct Message3 {
|
impl_from_child_enum!(State1, State);
|
||||||
pub(crate) tx_redeem_encsig: EncryptedSignature,
|
impl_from_child_enum!(State2, State);
|
||||||
}
|
impl_from_child_enum!(State3, State);
|
||||||
|
impl_from_child_enum!(State4, State);
|
||||||
|
impl_from_child_enum!(State5, State);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct State0 {
|
pub struct State0 {
|
||||||
@ -228,17 +289,18 @@ impl State2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn lock_btc<W>(self, bitcoin_wallet: &W) -> Result<State2b>
|
pub async fn lock_btc<W>(self, bitcoin_wallet: &W) -> Result<State3>
|
||||||
where
|
where
|
||||||
W: bitcoin::SignTxLock + bitcoin::BroadcastSignedTransaction,
|
W: bitcoin::SignTxLock + bitcoin::BroadcastSignedTransaction,
|
||||||
{
|
{
|
||||||
let signed_tx_lock = bitcoin_wallet.sign_tx_lock(self.tx_lock.clone()).await?;
|
let signed_tx_lock = bitcoin_wallet.sign_tx_lock(self.tx_lock.clone()).await?;
|
||||||
|
|
||||||
|
tracing::info!("{}", self.tx_lock.txid());
|
||||||
let _ = bitcoin_wallet
|
let _ = bitcoin_wallet
|
||||||
.broadcast_signed_transaction(signed_tx_lock)
|
.broadcast_signed_transaction(signed_tx_lock)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(State2b {
|
Ok(State3 {
|
||||||
A: self.A,
|
A: self.A,
|
||||||
b: self.b,
|
b: self.b,
|
||||||
s_b: self.s_b,
|
s_b: self.s_b,
|
||||||
@ -259,8 +321,8 @@ impl State2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct State2b {
|
pub struct State3 {
|
||||||
A: bitcoin::PublicKey,
|
A: bitcoin::PublicKey,
|
||||||
b: bitcoin::SecretKey,
|
b: bitcoin::SecretKey,
|
||||||
s_b: cross_curve_dleq::Scalar,
|
s_b: cross_curve_dleq::Scalar,
|
||||||
@ -279,8 +341,8 @@ pub struct State2b {
|
|||||||
tx_refund_encsig: EncryptedSignature,
|
tx_refund_encsig: EncryptedSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State2b {
|
impl State3 {
|
||||||
pub async fn watch_for_lock_xmr<W>(self, xmr_wallet: &W, msg: alice::Message2) -> Result<State3>
|
pub async fn watch_for_lock_xmr<W>(self, xmr_wallet: &W, msg: alice::Message2) -> Result<State4>
|
||||||
where
|
where
|
||||||
W: monero::CheckTransfer,
|
W: monero::CheckTransfer,
|
||||||
{
|
{
|
||||||
@ -293,7 +355,7 @@ impl State2b {
|
|||||||
.check_transfer(S, self.v.public(), msg.tx_lock_proof, self.xmr)
|
.check_transfer(S, self.v.public(), msg.tx_lock_proof, self.xmr)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(State3 {
|
Ok(State4 {
|
||||||
A: self.A,
|
A: self.A,
|
||||||
b: self.b,
|
b: self.b,
|
||||||
s_b: self.s_b,
|
s_b: self.s_b,
|
||||||
@ -359,15 +421,13 @@ impl State2b {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn tx_lock_id(&self) -> bitcoin::Txid {
|
pub fn tx_lock_id(&self) -> bitcoin::Txid {
|
||||||
self.tx_lock.txid()
|
self.tx_lock.txid()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct State3 {
|
pub struct State4 {
|
||||||
A: bitcoin::PublicKey,
|
A: bitcoin::PublicKey,
|
||||||
b: bitcoin::SecretKey,
|
b: bitcoin::SecretKey,
|
||||||
s_b: cross_curve_dleq::Scalar,
|
s_b: cross_curve_dleq::Scalar,
|
||||||
@ -386,7 +446,7 @@ pub struct State3 {
|
|||||||
tx_refund_encsig: EncryptedSignature,
|
tx_refund_encsig: EncryptedSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State3 {
|
impl State4 {
|
||||||
pub fn next_message(&self) -> Message3 {
|
pub fn next_message(&self) -> Message3 {
|
||||||
let tx_redeem = bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address);
|
let tx_redeem = bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address);
|
||||||
let tx_redeem_encsig = self.b.encsign(self.S_a_bitcoin.clone(), tx_redeem.digest());
|
let tx_redeem_encsig = self.b.encsign(self.S_a_bitcoin.clone(), tx_redeem.digest());
|
||||||
@ -394,14 +454,16 @@ impl State3 {
|
|||||||
Message3 { tx_redeem_encsig }
|
Message3 { tx_redeem_encsig }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn watch_for_redeem_btc<W>(self, bitcoin_wallet: &W) -> Result<State4>
|
pub async fn watch_for_redeem_btc<W>(self, bitcoin_wallet: &W) -> Result<State5>
|
||||||
where
|
where
|
||||||
W: GetRawTransaction,
|
W: WatchForRawTransaction,
|
||||||
{
|
{
|
||||||
let tx_redeem = bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address);
|
let tx_redeem = bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address);
|
||||||
let tx_redeem_encsig = self.b.encsign(self.S_a_bitcoin.clone(), tx_redeem.digest());
|
let tx_redeem_encsig = self.b.encsign(self.S_a_bitcoin.clone(), tx_redeem.digest());
|
||||||
|
|
||||||
let tx_redeem_candidate = bitcoin_wallet.get_raw_transaction(tx_redeem.txid()).await?;
|
let tx_redeem_candidate = bitcoin_wallet
|
||||||
|
.watch_for_raw_transaction(tx_redeem.txid())
|
||||||
|
.await?;
|
||||||
|
|
||||||
let tx_redeem_sig =
|
let tx_redeem_sig =
|
||||||
tx_redeem.extract_signature_by_key(tx_redeem_candidate, self.b.public())?;
|
tx_redeem.extract_signature_by_key(tx_redeem_candidate, self.b.public())?;
|
||||||
@ -409,7 +471,7 @@ impl State3 {
|
|||||||
let s_a =
|
let s_a =
|
||||||
monero::PrivateKey::from_scalar(monero::Scalar::from_bytes_mod_order(s_a.to_bytes()));
|
monero::PrivateKey::from_scalar(monero::Scalar::from_bytes_mod_order(s_a.to_bytes()));
|
||||||
|
|
||||||
Ok(State4 {
|
Ok(State5 {
|
||||||
A: self.A,
|
A: self.A,
|
||||||
b: self.b,
|
b: self.b,
|
||||||
s_a,
|
s_a,
|
||||||
@ -432,7 +494,7 @@ impl State3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct State4 {
|
pub struct State5 {
|
||||||
A: bitcoin::PublicKey,
|
A: bitcoin::PublicKey,
|
||||||
b: bitcoin::SecretKey,
|
b: bitcoin::SecretKey,
|
||||||
s_a: monero::PrivateKey,
|
s_a: monero::PrivateKey,
|
||||||
@ -452,10 +514,10 @@ pub struct State4 {
|
|||||||
tx_cancel_sig: Signature,
|
tx_cancel_sig: Signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State4 {
|
impl State5 {
|
||||||
pub async fn claim_xmr<W>(&self, monero_wallet: &W) -> Result<()>
|
pub async fn claim_xmr<W>(&self, monero_wallet: &W) -> Result<()>
|
||||||
where
|
where
|
||||||
W: monero::ImportOutput,
|
W: monero::CreateWalletForOutput,
|
||||||
{
|
{
|
||||||
let s_b = monero::PrivateKey {
|
let s_b = monero::PrivateKey {
|
||||||
scalar: self.s_b.into_ed25519(),
|
scalar: self.s_b.into_ed25519(),
|
||||||
@ -465,8 +527,13 @@ impl State4 {
|
|||||||
|
|
||||||
// NOTE: This actually generates and opens a new wallet, closing the currently
|
// NOTE: This actually generates and opens a new wallet, closing the currently
|
||||||
// open one.
|
// open one.
|
||||||
monero_wallet.import_output(s, self.v).await?;
|
monero_wallet
|
||||||
|
.create_and_load_wallet_for_output(s, self.v)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
pub fn tx_lock_id(&self) -> bitcoin::Txid {
|
||||||
|
self.tx_lock.txid()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
48
xmr-btc/src/bob/message.rs
Normal file
48
xmr-btc/src/bob/message.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use crate::{bitcoin, monero};
|
||||||
|
use anyhow::Result;
|
||||||
|
use ecdsa_fun::{adaptor::EncryptedSignature, Signature};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Message {
|
||||||
|
Message0(Message0),
|
||||||
|
Message1(Message1),
|
||||||
|
Message2(Message2),
|
||||||
|
Message3(Message3),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Message0 {
|
||||||
|
pub(crate) B: bitcoin::PublicKey,
|
||||||
|
pub(crate) S_b_monero: monero::PublicKey,
|
||||||
|
pub(crate) S_b_bitcoin: bitcoin::PublicKey,
|
||||||
|
pub(crate) dleq_proof_s_b: cross_curve_dleq::Proof,
|
||||||
|
pub(crate) v_b: monero::PrivateViewKey,
|
||||||
|
pub(crate) refund_address: bitcoin::Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Message1 {
|
||||||
|
pub(crate) tx_lock: bitcoin::TxLock,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Message2 {
|
||||||
|
pub(crate) tx_punish_sig: Signature,
|
||||||
|
pub(crate) tx_cancel_sig: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Message3 {
|
||||||
|
pub(crate) tx_redeem_encsig: EncryptedSignature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_try_from_parent_enum!(Message0, Message);
|
||||||
|
impl_try_from_parent_enum!(Message1, Message);
|
||||||
|
impl_try_from_parent_enum!(Message2, Message);
|
||||||
|
impl_try_from_parent_enum!(Message3, Message);
|
||||||
|
|
||||||
|
impl_from_child_enum!(Message0, Message);
|
||||||
|
impl_from_child_enum!(Message1, Message);
|
||||||
|
impl_from_child_enum!(Message2, Message);
|
||||||
|
impl_from_child_enum!(Message3, Message);
|
@ -14,371 +14,39 @@
|
|||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod utils {
|
||||||
|
|
||||||
|
macro_rules! impl_try_from_parent_enum {
|
||||||
|
($type:ident, $parent:ident) => {
|
||||||
|
impl TryFrom<$parent> for $type {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
fn try_from(from: $parent) -> Result<Self> {
|
||||||
|
if let $parent::$type(inner) = from {
|
||||||
|
Ok(inner)
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"Failed to convert parent state to child state"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_from_child_enum {
|
||||||
|
($type:ident, $parent:ident) => {
|
||||||
|
impl From<$type> for $parent {
|
||||||
|
fn from(from: $type) -> Self {
|
||||||
|
$parent::$type(from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub mod alice;
|
pub mod alice;
|
||||||
pub mod bitcoin;
|
pub mod bitcoin;
|
||||||
pub mod bob;
|
pub mod bob;
|
||||||
pub mod monero;
|
pub mod monero;
|
||||||
|
pub mod transport;
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::{
|
|
||||||
alice, bitcoin,
|
|
||||||
bitcoin::{Amount, TX_FEE},
|
|
||||||
bob, monero,
|
|
||||||
};
|
|
||||||
use bitcoin_harness::Bitcoind;
|
|
||||||
use monero_harness::Monero;
|
|
||||||
use rand::rngs::OsRng;
|
|
||||||
use testcontainers::clients::Cli;
|
|
||||||
|
|
||||||
const TEN_XMR: u64 = 10_000_000_000_000;
|
|
||||||
|
|
||||||
pub async fn init_bitcoind(tc_client: &Cli) -> Bitcoind<'_> {
|
|
||||||
let bitcoind = Bitcoind::new(tc_client, "0.19.1").expect("failed to create bitcoind");
|
|
||||||
let _ = bitcoind.init(5).await;
|
|
||||||
|
|
||||||
bitcoind
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn happy_path() {
|
|
||||||
let cli = Cli::default();
|
|
||||||
let monero = Monero::new(&cli);
|
|
||||||
let bitcoind = init_bitcoind(&cli).await;
|
|
||||||
|
|
||||||
// must be bigger than our hardcoded fee of 10_000
|
|
||||||
let btc_amount = bitcoin::Amount::from_sat(10_000_000);
|
|
||||||
let xmr_amount = monero::Amount::from_piconero(1_000_000_000_000);
|
|
||||||
|
|
||||||
let fund_alice = TEN_XMR;
|
|
||||||
let fund_bob = 0;
|
|
||||||
monero.init(fund_alice, fund_bob).await.unwrap();
|
|
||||||
|
|
||||||
let alice_monero_wallet = monero::AliceWallet(&monero);
|
|
||||||
let bob_monero_wallet = monero::BobWallet(&monero);
|
|
||||||
|
|
||||||
let alice_btc_wallet = bitcoin::Wallet::new("alice", &bitcoind.node_url)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let bob_btc_wallet = bitcoin::make_wallet("bob", &bitcoind, btc_amount)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let alice_initial_btc_balance = alice_btc_wallet.balance().await.unwrap();
|
|
||||||
let bob_initial_btc_balance = bob_btc_wallet.balance().await.unwrap();
|
|
||||||
|
|
||||||
let alice_initial_xmr_balance = alice_monero_wallet.0.get_balance_alice().await.unwrap();
|
|
||||||
let bob_initial_xmr_balance = bob_monero_wallet.0.get_balance_bob().await.unwrap();
|
|
||||||
|
|
||||||
let redeem_address = alice_btc_wallet.new_address().await.unwrap();
|
|
||||||
let punish_address = redeem_address.clone();
|
|
||||||
let refund_address = bob_btc_wallet.new_address().await.unwrap();
|
|
||||||
|
|
||||||
let refund_timelock = 1;
|
|
||||||
let punish_timelock = 1;
|
|
||||||
|
|
||||||
let alice_state0 = alice::State0::new(
|
|
||||||
&mut OsRng,
|
|
||||||
btc_amount,
|
|
||||||
xmr_amount,
|
|
||||||
refund_timelock,
|
|
||||||
punish_timelock,
|
|
||||||
redeem_address,
|
|
||||||
punish_address,
|
|
||||||
);
|
|
||||||
let bob_state0 = bob::State0::new(
|
|
||||||
&mut OsRng,
|
|
||||||
btc_amount,
|
|
||||||
xmr_amount,
|
|
||||||
refund_timelock,
|
|
||||||
punish_timelock,
|
|
||||||
refund_address.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let alice_message0 = alice_state0.next_message(&mut OsRng);
|
|
||||||
let bob_message0 = bob_state0.next_message(&mut OsRng);
|
|
||||||
|
|
||||||
let alice_state1 = alice_state0.receive(bob_message0).unwrap();
|
|
||||||
let bob_state1 = bob_state0
|
|
||||||
.receive(&bob_btc_wallet, alice_message0)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let bob_message1 = bob_state1.next_message();
|
|
||||||
let alice_state2 = alice_state1.receive(bob_message1);
|
|
||||||
let alice_message1 = alice_state2.next_message();
|
|
||||||
let bob_state2 = bob_state1.receive(alice_message1).unwrap();
|
|
||||||
|
|
||||||
let bob_message2 = bob_state2.next_message();
|
|
||||||
let alice_state3 = alice_state2.receive(bob_message2).unwrap();
|
|
||||||
|
|
||||||
let bob_state2b = bob_state2.lock_btc(&bob_btc_wallet).await.unwrap();
|
|
||||||
let lock_txid = bob_state2b.tx_lock_id();
|
|
||||||
|
|
||||||
let alice_state4 = alice_state3
|
|
||||||
.watch_for_lock_btc(&alice_btc_wallet)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let (alice_state4b, lock_tx_monero_fee) =
|
|
||||||
alice_state4.lock_xmr(&alice_monero_wallet).await.unwrap();
|
|
||||||
|
|
||||||
let alice_message2 = alice_state4b.next_message();
|
|
||||||
|
|
||||||
let bob_state3 = bob_state2b
|
|
||||||
.watch_for_lock_xmr(&bob_monero_wallet, alice_message2)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let bob_message3 = bob_state3.next_message();
|
|
||||||
let alice_state5 = alice_state4b.receive(bob_message3);
|
|
||||||
|
|
||||||
alice_state5.redeem_btc(&alice_btc_wallet).await.unwrap();
|
|
||||||
let bob_state4 = bob_state3
|
|
||||||
.watch_for_redeem_btc(&bob_btc_wallet)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
bob_state4.claim_xmr(&bob_monero_wallet).await.unwrap();
|
|
||||||
|
|
||||||
let alice_final_btc_balance = alice_btc_wallet.balance().await.unwrap();
|
|
||||||
let bob_final_btc_balance = bob_btc_wallet.balance().await.unwrap();
|
|
||||||
|
|
||||||
let lock_tx_bitcoin_fee = bob_btc_wallet.transaction_fee(lock_txid).await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
alice_final_btc_balance,
|
|
||||||
alice_initial_btc_balance + btc_amount - bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
bob_final_btc_balance,
|
|
||||||
bob_initial_btc_balance - btc_amount - lock_tx_bitcoin_fee
|
|
||||||
);
|
|
||||||
|
|
||||||
let alice_final_xmr_balance = alice_monero_wallet.0.get_balance_alice().await.unwrap();
|
|
||||||
bob_monero_wallet
|
|
||||||
.0
|
|
||||||
.wait_for_bob_wallet_block_height()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let bob_final_xmr_balance = bob_monero_wallet.0.get_balance_bob().await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
alice_final_xmr_balance,
|
|
||||||
alice_initial_xmr_balance - u64::from(xmr_amount) - u64::from(lock_tx_monero_fee)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
bob_final_xmr_balance,
|
|
||||||
bob_initial_xmr_balance + u64::from(xmr_amount)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn both_refund() {
|
|
||||||
let cli = Cli::default();
|
|
||||||
let monero = Monero::new(&cli);
|
|
||||||
let bitcoind = init_bitcoind(&cli).await;
|
|
||||||
|
|
||||||
// must be bigger than our hardcoded fee of 10_000
|
|
||||||
let btc_amount = bitcoin::Amount::from_sat(10_000_000);
|
|
||||||
let xmr_amount = monero::Amount::from_piconero(1_000_000_000_000);
|
|
||||||
|
|
||||||
let alice_btc_wallet = bitcoin::Wallet::new("alice", &bitcoind.node_url)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let bob_btc_wallet = bitcoin::make_wallet("bob", &bitcoind, btc_amount)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let fund_alice = TEN_XMR;
|
|
||||||
let fund_bob = 0;
|
|
||||||
|
|
||||||
monero.init(fund_alice, fund_bob).await.unwrap();
|
|
||||||
let alice_monero_wallet = monero::AliceWallet(&monero);
|
|
||||||
let bob_monero_wallet = monero::BobWallet(&monero);
|
|
||||||
|
|
||||||
let alice_initial_btc_balance = alice_btc_wallet.balance().await.unwrap();
|
|
||||||
let bob_initial_btc_balance = bob_btc_wallet.balance().await.unwrap();
|
|
||||||
|
|
||||||
let bob_initial_xmr_balance = bob_monero_wallet.0.get_balance_bob().await.unwrap();
|
|
||||||
|
|
||||||
let redeem_address = alice_btc_wallet.new_address().await.unwrap();
|
|
||||||
let punish_address = redeem_address.clone();
|
|
||||||
let refund_address = bob_btc_wallet.new_address().await.unwrap();
|
|
||||||
|
|
||||||
let refund_timelock = 1;
|
|
||||||
let punish_timelock = 1;
|
|
||||||
|
|
||||||
let alice_state0 = alice::State0::new(
|
|
||||||
&mut OsRng,
|
|
||||||
btc_amount,
|
|
||||||
xmr_amount,
|
|
||||||
refund_timelock,
|
|
||||||
punish_timelock,
|
|
||||||
redeem_address,
|
|
||||||
punish_address,
|
|
||||||
);
|
|
||||||
let bob_state0 = bob::State0::new(
|
|
||||||
&mut OsRng,
|
|
||||||
btc_amount,
|
|
||||||
xmr_amount,
|
|
||||||
refund_timelock,
|
|
||||||
punish_timelock,
|
|
||||||
refund_address.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let alice_message0 = alice_state0.next_message(&mut OsRng);
|
|
||||||
let bob_message0 = bob_state0.next_message(&mut OsRng);
|
|
||||||
|
|
||||||
let alice_state1 = alice_state0.receive(bob_message0).unwrap();
|
|
||||||
let bob_state1 = bob_state0
|
|
||||||
.receive(&bob_btc_wallet, alice_message0)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let bob_message1 = bob_state1.next_message();
|
|
||||||
let alice_state2 = alice_state1.receive(bob_message1);
|
|
||||||
let alice_message1 = alice_state2.next_message();
|
|
||||||
let bob_state2 = bob_state1.receive(alice_message1).unwrap();
|
|
||||||
|
|
||||||
let bob_message2 = bob_state2.next_message();
|
|
||||||
let alice_state3 = alice_state2.receive(bob_message2).unwrap();
|
|
||||||
|
|
||||||
let bob_state2b = bob_state2.lock_btc(&bob_btc_wallet).await.unwrap();
|
|
||||||
|
|
||||||
let alice_state4 = alice_state3
|
|
||||||
.watch_for_lock_btc(&alice_btc_wallet)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let (alice_state4b, _lock_tx_monero_fee) =
|
|
||||||
alice_state4.lock_xmr(&alice_monero_wallet).await.unwrap();
|
|
||||||
|
|
||||||
bob_state2b.refund_btc(&bob_btc_wallet).await.unwrap();
|
|
||||||
|
|
||||||
alice_state4b
|
|
||||||
.refund_xmr(&alice_btc_wallet, &alice_monero_wallet)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let alice_final_btc_balance = alice_btc_wallet.balance().await.unwrap();
|
|
||||||
let bob_final_btc_balance = bob_btc_wallet.balance().await.unwrap();
|
|
||||||
|
|
||||||
// lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal
|
|
||||||
// to TX_FEE
|
|
||||||
let lock_tx_bitcoin_fee = bob_btc_wallet
|
|
||||||
.transaction_fee(bob_state2b.tx_lock_id())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(alice_final_btc_balance, alice_initial_btc_balance);
|
|
||||||
assert_eq!(
|
|
||||||
bob_final_btc_balance,
|
|
||||||
// The 2 * TX_FEE corresponds to tx_refund and tx_cancel.
|
|
||||||
bob_initial_btc_balance - Amount::from_sat(2 * TX_FEE) - lock_tx_bitcoin_fee
|
|
||||||
);
|
|
||||||
|
|
||||||
alice_monero_wallet
|
|
||||||
.0
|
|
||||||
.wait_for_alice_wallet_block_height()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let alice_final_xmr_balance = alice_monero_wallet.0.get_balance_alice().await.unwrap();
|
|
||||||
let bob_final_xmr_balance = bob_monero_wallet.0.get_balance_bob().await.unwrap();
|
|
||||||
|
|
||||||
// Because we create a new wallet when claiming Monero, we can only assert on
|
|
||||||
// this new wallet owning all of `xmr_amount` after refund
|
|
||||||
assert_eq!(alice_final_xmr_balance, u64::from(xmr_amount));
|
|
||||||
assert_eq!(bob_final_xmr_balance, bob_initial_xmr_balance);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn alice_punishes() {
|
|
||||||
let cli = Cli::default();
|
|
||||||
let bitcoind = init_bitcoind(&cli).await;
|
|
||||||
|
|
||||||
// must be bigger than our hardcoded fee of 10_000
|
|
||||||
let btc_amount = bitcoin::Amount::from_sat(10_000_000);
|
|
||||||
let xmr_amount = monero::Amount::from_piconero(1_000_000_000_000);
|
|
||||||
|
|
||||||
let alice_btc_wallet = bitcoin::Wallet::new("alice", &bitcoind.node_url)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let bob_btc_wallet = bitcoin::make_wallet("bob", &bitcoind, btc_amount)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let alice_initial_btc_balance = alice_btc_wallet.balance().await.unwrap();
|
|
||||||
let bob_initial_btc_balance = bob_btc_wallet.balance().await.unwrap();
|
|
||||||
|
|
||||||
let redeem_address = alice_btc_wallet.new_address().await.unwrap();
|
|
||||||
let punish_address = redeem_address.clone();
|
|
||||||
let refund_address = bob_btc_wallet.new_address().await.unwrap();
|
|
||||||
|
|
||||||
let refund_timelock = 1;
|
|
||||||
let punish_timelock = 1;
|
|
||||||
|
|
||||||
let alice_state0 = alice::State0::new(
|
|
||||||
&mut OsRng,
|
|
||||||
btc_amount,
|
|
||||||
xmr_amount,
|
|
||||||
refund_timelock,
|
|
||||||
punish_timelock,
|
|
||||||
redeem_address,
|
|
||||||
punish_address,
|
|
||||||
);
|
|
||||||
let bob_state0 = bob::State0::new(
|
|
||||||
&mut OsRng,
|
|
||||||
btc_amount,
|
|
||||||
xmr_amount,
|
|
||||||
refund_timelock,
|
|
||||||
punish_timelock,
|
|
||||||
refund_address.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let alice_message0 = alice_state0.next_message(&mut OsRng);
|
|
||||||
let bob_message0 = bob_state0.next_message(&mut OsRng);
|
|
||||||
|
|
||||||
let alice_state1 = alice_state0.receive(bob_message0).unwrap();
|
|
||||||
let bob_state1 = bob_state0
|
|
||||||
.receive(&bob_btc_wallet, alice_message0)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let bob_message1 = bob_state1.next_message();
|
|
||||||
let alice_state2 = alice_state1.receive(bob_message1);
|
|
||||||
let alice_message1 = alice_state2.next_message();
|
|
||||||
let bob_state2 = bob_state1.receive(alice_message1).unwrap();
|
|
||||||
|
|
||||||
let bob_message2 = bob_state2.next_message();
|
|
||||||
let alice_state3 = alice_state2.receive(bob_message2).unwrap();
|
|
||||||
|
|
||||||
let bob_state2b = bob_state2.lock_btc(&bob_btc_wallet).await.unwrap();
|
|
||||||
|
|
||||||
let alice_state4 = alice_state3
|
|
||||||
.watch_for_lock_btc(&alice_btc_wallet)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
alice_state4.punish(&alice_btc_wallet).await.unwrap();
|
|
||||||
|
|
||||||
let alice_final_btc_balance = alice_btc_wallet.balance().await.unwrap();
|
|
||||||
let bob_final_btc_balance = bob_btc_wallet.balance().await.unwrap();
|
|
||||||
|
|
||||||
// lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal
|
|
||||||
// to TX_FEE
|
|
||||||
let lock_tx_bitcoin_fee = bob_btc_wallet
|
|
||||||
.transaction_fee(bob_state2b.tx_lock_id())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
alice_final_btc_balance,
|
|
||||||
alice_initial_btc_balance + btc_amount - Amount::from_sat(2 * TX_FEE)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
bob_final_btc_balance,
|
|
||||||
bob_initial_btc_balance - btc_amount - lock_tx_bitcoin_fee
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
#[cfg(test)]
|
|
||||||
pub mod wallet;
|
|
||||||
|
|
||||||
use std::ops::Add;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use rand::{CryptoRng, RngCore};
|
|
||||||
|
|
||||||
pub use curve25519_dalek::scalar::Scalar;
|
pub use curve25519_dalek::scalar::Scalar;
|
||||||
pub use monero::{Address, PrivateKey, PublicKey};
|
pub use monero::{Address, PrivateKey, PublicKey};
|
||||||
|
use rand::{CryptoRng, RngCore};
|
||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
pub fn random_private_key<R: RngCore + CryptoRng>(rng: &mut R) -> PrivateKey {
|
pub fn random_private_key<R: RngCore + CryptoRng>(rng: &mut R) -> PrivateKey {
|
||||||
let scalar = Scalar::random(rng);
|
let scalar = Scalar::random(rng);
|
||||||
@ -16,9 +11,6 @@ pub fn random_private_key<R: RngCore + CryptoRng>(rng: &mut R) -> PrivateKey {
|
|||||||
PrivateKey::from_scalar(scalar)
|
PrivateKey::from_scalar(scalar)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub use wallet::{AliceWallet, BobWallet};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct PrivateViewKey(PrivateKey);
|
pub struct PrivateViewKey(PrivateKey);
|
||||||
|
|
||||||
@ -69,6 +61,9 @@ impl Amount {
|
|||||||
pub fn from_piconero(amount: u64) -> Self {
|
pub fn from_piconero(amount: u64) -> Self {
|
||||||
Amount(amount)
|
Amount(amount)
|
||||||
}
|
}
|
||||||
|
pub fn as_piconero(&self) -> u64 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Amount> for u64 {
|
impl From<Amount> for u64 {
|
||||||
@ -83,8 +78,21 @@ pub struct TransferProof {
|
|||||||
tx_key: PrivateKey,
|
tx_key: PrivateKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TransferProof {
|
||||||
|
pub fn new(tx_hash: TxHash, tx_key: PrivateKey) -> Self {
|
||||||
|
Self { tx_hash, tx_key }
|
||||||
|
}
|
||||||
|
pub fn tx_hash(&self) -> TxHash {
|
||||||
|
self.tx_hash.clone()
|
||||||
|
}
|
||||||
|
pub fn tx_key(&self) -> PrivateKey {
|
||||||
|
self.tx_key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add constructor/ change String to fixed length byte array
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TxHash(String);
|
pub struct TxHash(pub String);
|
||||||
|
|
||||||
impl From<TxHash> for String {
|
impl From<TxHash> for String {
|
||||||
fn from(from: TxHash) -> Self {
|
fn from(from: TxHash) -> Self {
|
||||||
@ -114,8 +122,8 @@ pub trait CheckTransfer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ImportOutput {
|
pub trait CreateWalletForOutput {
|
||||||
async fn import_output(
|
async fn create_and_load_wallet_for_output(
|
||||||
&self,
|
&self,
|
||||||
private_spend_key: PrivateKey,
|
private_spend_key: PrivateKey,
|
||||||
private_view_key: PrivateViewKey,
|
private_view_key: PrivateViewKey,
|
||||||
|
12
xmr-btc/src/transport.rs
Normal file
12
xmr-btc/src/transport.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait SendMessage<SendMsg> {
|
||||||
|
async fn send_message(&mut self, message: SendMsg) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait ReceiveMessage<RecvMsg> {
|
||||||
|
async fn receive_message(&mut self) -> Result<RecvMsg>;
|
||||||
|
}
|
398
xmr-btc/tests/e2e.rs
Normal file
398
xmr-btc/tests/e2e.rs
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
use crate::harness::wallet;
|
||||||
|
use bitcoin_harness::Bitcoind;
|
||||||
|
use harness::{
|
||||||
|
node::{AliceNode, BobNode},
|
||||||
|
transport::Transport,
|
||||||
|
};
|
||||||
|
use monero_harness::Monero;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use testcontainers::clients::Cli;
|
||||||
|
use tokio::sync::{
|
||||||
|
mpsc,
|
||||||
|
mpsc::{Receiver, Sender},
|
||||||
|
};
|
||||||
|
use xmr_btc::{alice, bitcoin, bob, monero};
|
||||||
|
|
||||||
|
mod harness;
|
||||||
|
|
||||||
|
const TEN_XMR: u64 = 10_000_000_000_000;
|
||||||
|
const RELATIVE_REFUND_TIMELOCK: u32 = 1;
|
||||||
|
const RELATIVE_PUNISH_TIMELOCK: u32 = 1;
|
||||||
|
|
||||||
|
pub async fn init_bitcoind(tc_client: &Cli) -> Bitcoind<'_> {
|
||||||
|
let bitcoind = Bitcoind::new(tc_client, "0.19.1").expect("failed to create bitcoind");
|
||||||
|
let _ = bitcoind.init(5).await;
|
||||||
|
|
||||||
|
bitcoind
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InitialBalances {
|
||||||
|
alice_xmr: u64,
|
||||||
|
alice_btc: bitcoin::Amount,
|
||||||
|
bob_xmr: u64,
|
||||||
|
bob_btc: bitcoin::Amount,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SwapAmounts {
|
||||||
|
xmr: monero::Amount,
|
||||||
|
btc: bitcoin::Amount,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_alice_and_bob_transports() -> (
|
||||||
|
Transport<alice::Message, bob::Message>,
|
||||||
|
Transport<bob::Message, alice::Message>,
|
||||||
|
) {
|
||||||
|
let (a_sender, b_receiver): (Sender<alice::Message>, Receiver<alice::Message>) =
|
||||||
|
mpsc::channel(5);
|
||||||
|
let (b_sender, a_receiver): (Sender<bob::Message>, Receiver<bob::Message>) = mpsc::channel(5);
|
||||||
|
|
||||||
|
let a_transport = Transport {
|
||||||
|
sender: a_sender,
|
||||||
|
receiver: a_receiver,
|
||||||
|
};
|
||||||
|
|
||||||
|
let b_transport = Transport {
|
||||||
|
sender: b_sender,
|
||||||
|
receiver: b_receiver,
|
||||||
|
};
|
||||||
|
|
||||||
|
(a_transport, b_transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn init_test<'a>(
|
||||||
|
monero: &'a Monero<'a>,
|
||||||
|
bitcoind: &Bitcoind<'_>,
|
||||||
|
) -> (
|
||||||
|
alice::State0,
|
||||||
|
bob::State0,
|
||||||
|
AliceNode<'a>,
|
||||||
|
BobNode<'a>,
|
||||||
|
InitialBalances,
|
||||||
|
SwapAmounts,
|
||||||
|
) {
|
||||||
|
// must be bigger than our hardcoded fee of 10_000
|
||||||
|
let btc_amount = bitcoin::Amount::from_sat(10_000_000);
|
||||||
|
let xmr_amount = monero::Amount::from_piconero(1_000_000_000_000);
|
||||||
|
|
||||||
|
let swap_amounts = SwapAmounts {
|
||||||
|
xmr: xmr_amount,
|
||||||
|
btc: btc_amount,
|
||||||
|
};
|
||||||
|
|
||||||
|
let fund_alice = TEN_XMR;
|
||||||
|
let fund_bob = 0;
|
||||||
|
monero.init(fund_alice, fund_bob).await.unwrap();
|
||||||
|
|
||||||
|
let alice_monero_wallet = wallet::monero::AliceWallet(&monero);
|
||||||
|
let bob_monero_wallet = wallet::monero::BobWallet(&monero);
|
||||||
|
|
||||||
|
let alice_btc_wallet = wallet::bitcoin::Wallet::new("alice", &bitcoind.node_url)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let bob_btc_wallet = wallet::bitcoin::make_wallet("bob", &bitcoind, btc_amount)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (alice_transport, bob_transport) = init_alice_and_bob_transports();
|
||||||
|
let alice = AliceNode::new(alice_transport, alice_btc_wallet, alice_monero_wallet);
|
||||||
|
|
||||||
|
let bob = BobNode::new(bob_transport, bob_btc_wallet, bob_monero_wallet);
|
||||||
|
|
||||||
|
let alice_initial_btc_balance = alice.bitcoin_wallet.balance().await.unwrap();
|
||||||
|
let bob_initial_btc_balance = bob.bitcoin_wallet.balance().await.unwrap();
|
||||||
|
|
||||||
|
let alice_initial_xmr_balance = alice.monero_wallet.0.get_balance_alice().await.unwrap();
|
||||||
|
let bob_initial_xmr_balance = bob.monero_wallet.0.get_balance_bob().await.unwrap();
|
||||||
|
|
||||||
|
let redeem_address = alice.bitcoin_wallet.new_address().await.unwrap();
|
||||||
|
let punish_address = redeem_address.clone();
|
||||||
|
let refund_address = bob.bitcoin_wallet.new_address().await.unwrap();
|
||||||
|
|
||||||
|
let alice_state0 = alice::State0::new(
|
||||||
|
&mut OsRng,
|
||||||
|
btc_amount,
|
||||||
|
xmr_amount,
|
||||||
|
RELATIVE_REFUND_TIMELOCK,
|
||||||
|
RELATIVE_PUNISH_TIMELOCK,
|
||||||
|
redeem_address.clone(),
|
||||||
|
punish_address.clone(),
|
||||||
|
);
|
||||||
|
let bob_state0 = bob::State0::new(
|
||||||
|
&mut OsRng,
|
||||||
|
btc_amount,
|
||||||
|
xmr_amount,
|
||||||
|
RELATIVE_REFUND_TIMELOCK,
|
||||||
|
RELATIVE_PUNISH_TIMELOCK,
|
||||||
|
refund_address,
|
||||||
|
);
|
||||||
|
let initial_balances = InitialBalances {
|
||||||
|
alice_xmr: alice_initial_xmr_balance,
|
||||||
|
alice_btc: alice_initial_btc_balance,
|
||||||
|
bob_xmr: bob_initial_xmr_balance,
|
||||||
|
bob_btc: bob_initial_btc_balance,
|
||||||
|
};
|
||||||
|
(
|
||||||
|
alice_state0,
|
||||||
|
bob_state0,
|
||||||
|
alice,
|
||||||
|
bob,
|
||||||
|
initial_balances,
|
||||||
|
swap_amounts,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
harness,
|
||||||
|
harness::node::{run_alice_until, run_bob_until},
|
||||||
|
init_bitcoind, init_test,
|
||||||
|
};
|
||||||
|
use futures::future;
|
||||||
|
use monero_harness::Monero;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use testcontainers::clients::Cli;
|
||||||
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
|
use xmr_btc::{
|
||||||
|
alice, bitcoin,
|
||||||
|
bitcoin::{Amount, TX_FEE},
|
||||||
|
bob,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn happy_path() {
|
||||||
|
let _guard = tracing_subscriber::fmt()
|
||||||
|
.with_env_filter("info")
|
||||||
|
.set_default();
|
||||||
|
|
||||||
|
let cli = Cli::default();
|
||||||
|
let monero = Monero::new(&cli);
|
||||||
|
let bitcoind = init_bitcoind(&cli).await;
|
||||||
|
|
||||||
|
let (
|
||||||
|
alice_state0,
|
||||||
|
bob_state0,
|
||||||
|
mut alice_node,
|
||||||
|
mut bob_node,
|
||||||
|
initial_balances,
|
||||||
|
swap_amounts,
|
||||||
|
) = init_test(&monero, &bitcoind).await;
|
||||||
|
|
||||||
|
let (alice_state, bob_state) = future::try_join(
|
||||||
|
run_alice_until(
|
||||||
|
&mut alice_node,
|
||||||
|
alice_state0.into(),
|
||||||
|
harness::alice::is_state6,
|
||||||
|
&mut OsRng,
|
||||||
|
),
|
||||||
|
run_bob_until(
|
||||||
|
&mut bob_node,
|
||||||
|
bob_state0.into(),
|
||||||
|
harness::bob::is_state5,
|
||||||
|
&mut OsRng,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let alice_state6: alice::State6 = alice_state.try_into().unwrap();
|
||||||
|
let bob_state5: bob::State5 = bob_state.try_into().unwrap();
|
||||||
|
|
||||||
|
let alice_final_btc_balance = alice_node.bitcoin_wallet.balance().await.unwrap();
|
||||||
|
let bob_final_btc_balance = bob_node.bitcoin_wallet.balance().await.unwrap();
|
||||||
|
|
||||||
|
let lock_tx_bitcoin_fee = bob_node
|
||||||
|
.bitcoin_wallet
|
||||||
|
.transaction_fee(bob_state5.tx_lock_id())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let alice_final_xmr_balance = alice_node
|
||||||
|
.monero_wallet
|
||||||
|
.0
|
||||||
|
.get_balance_alice()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
bob_node
|
||||||
|
.monero_wallet
|
||||||
|
.0
|
||||||
|
.wait_for_bob_wallet_block_height()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let bob_final_xmr_balance = bob_node.monero_wallet.0.get_balance_bob().await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
alice_final_btc_balance,
|
||||||
|
initial_balances.alice_btc + swap_amounts.btc
|
||||||
|
- bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
bob_final_btc_balance,
|
||||||
|
initial_balances.bob_btc - swap_amounts.btc - lock_tx_bitcoin_fee
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
alice_final_xmr_balance,
|
||||||
|
initial_balances.alice_xmr
|
||||||
|
- u64::from(swap_amounts.xmr)
|
||||||
|
- u64::from(alice_state6.lock_xmr_fee())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
bob_final_xmr_balance,
|
||||||
|
initial_balances.bob_xmr + u64::from(swap_amounts.xmr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn both_refund() {
|
||||||
|
let _guard = tracing_subscriber::fmt()
|
||||||
|
.with_env_filter("info")
|
||||||
|
.set_default();
|
||||||
|
|
||||||
|
let cli = Cli::default();
|
||||||
|
let monero = Monero::new(&cli);
|
||||||
|
let bitcoind = init_bitcoind(&cli).await;
|
||||||
|
|
||||||
|
let (
|
||||||
|
alice_state0,
|
||||||
|
bob_state0,
|
||||||
|
mut alice_node,
|
||||||
|
mut bob_node,
|
||||||
|
initial_balances,
|
||||||
|
swap_amounts,
|
||||||
|
) = init_test(&monero, &bitcoind).await;
|
||||||
|
|
||||||
|
let (alice_state, bob_state) = future::try_join(
|
||||||
|
run_alice_until(
|
||||||
|
&mut alice_node,
|
||||||
|
alice_state0.into(),
|
||||||
|
harness::alice::is_state5,
|
||||||
|
&mut OsRng,
|
||||||
|
),
|
||||||
|
run_bob_until(
|
||||||
|
&mut bob_node,
|
||||||
|
bob_state0.into(),
|
||||||
|
harness::bob::is_state3,
|
||||||
|
&mut OsRng,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let alice_state5: alice::State5 = alice_state.try_into().unwrap();
|
||||||
|
let bob_state3: bob::State3 = bob_state.try_into().unwrap();
|
||||||
|
|
||||||
|
bob_state3
|
||||||
|
.refund_btc(&bob_node.bitcoin_wallet)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
alice_state5
|
||||||
|
.refund_xmr(&alice_node.bitcoin_wallet, &alice_node.monero_wallet)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let alice_final_btc_balance = alice_node.bitcoin_wallet.balance().await.unwrap();
|
||||||
|
let bob_final_btc_balance = bob_node.bitcoin_wallet.balance().await.unwrap();
|
||||||
|
|
||||||
|
// lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal
|
||||||
|
// to TX_FEE
|
||||||
|
let lock_tx_bitcoin_fee = bob_node
|
||||||
|
.bitcoin_wallet
|
||||||
|
.transaction_fee(bob_state3.tx_lock_id())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
alice_node
|
||||||
|
.monero_wallet
|
||||||
|
.0
|
||||||
|
.wait_for_alice_wallet_block_height()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let alice_final_xmr_balance = alice_node
|
||||||
|
.monero_wallet
|
||||||
|
.0
|
||||||
|
.get_balance_alice()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let bob_final_xmr_balance = bob_node.monero_wallet.0.get_balance_bob().await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(alice_final_btc_balance, initial_balances.alice_btc);
|
||||||
|
assert_eq!(
|
||||||
|
bob_final_btc_balance,
|
||||||
|
// The 2 * TX_FEE corresponds to tx_refund and tx_cancel.
|
||||||
|
initial_balances.bob_btc - Amount::from_sat(2 * TX_FEE) - lock_tx_bitcoin_fee
|
||||||
|
);
|
||||||
|
|
||||||
|
// Because we create a new wallet when claiming Monero, we can only assert on
|
||||||
|
// this new wallet owning all of `xmr_amount` after refund
|
||||||
|
assert_eq!(alice_final_xmr_balance, u64::from(swap_amounts.xmr));
|
||||||
|
assert_eq!(bob_final_xmr_balance, initial_balances.bob_xmr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn alice_punishes() {
|
||||||
|
let _guard = tracing_subscriber::fmt()
|
||||||
|
.with_env_filter("info")
|
||||||
|
.set_default();
|
||||||
|
|
||||||
|
let cli = Cli::default();
|
||||||
|
let monero = Monero::new(&cli);
|
||||||
|
let bitcoind = init_bitcoind(&cli).await;
|
||||||
|
|
||||||
|
let (
|
||||||
|
alice_state0,
|
||||||
|
bob_state0,
|
||||||
|
mut alice_node,
|
||||||
|
mut bob_node,
|
||||||
|
initial_balances,
|
||||||
|
swap_amounts,
|
||||||
|
) = init_test(&monero, &bitcoind).await;
|
||||||
|
|
||||||
|
let (alice_state, bob_state) = future::try_join(
|
||||||
|
run_alice_until(
|
||||||
|
&mut alice_node,
|
||||||
|
alice_state0.into(),
|
||||||
|
harness::alice::is_state4,
|
||||||
|
&mut OsRng,
|
||||||
|
),
|
||||||
|
run_bob_until(
|
||||||
|
&mut bob_node,
|
||||||
|
bob_state0.into(),
|
||||||
|
harness::bob::is_state3,
|
||||||
|
&mut OsRng,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let alice_state4: alice::State4 = alice_state.try_into().unwrap();
|
||||||
|
let bob_state3: bob::State3 = bob_state.try_into().unwrap();
|
||||||
|
|
||||||
|
alice_state4
|
||||||
|
.punish(&alice_node.bitcoin_wallet)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let alice_final_btc_balance = alice_node.bitcoin_wallet.balance().await.unwrap();
|
||||||
|
let bob_final_btc_balance = bob_node.bitcoin_wallet.balance().await.unwrap();
|
||||||
|
|
||||||
|
// lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal
|
||||||
|
// to TX_FEE
|
||||||
|
let lock_tx_bitcoin_fee = bob_node
|
||||||
|
.bitcoin_wallet
|
||||||
|
.transaction_fee(bob_state3.tx_lock_id())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
alice_final_btc_balance,
|
||||||
|
initial_balances.alice_btc + swap_amounts.btc - Amount::from_sat(2 * TX_FEE)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
bob_final_btc_balance,
|
||||||
|
initial_balances.bob_btc - swap_amounts.btc - lock_tx_bitcoin_fee
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
36
xmr-btc/tests/harness/mod.rs
Normal file
36
xmr-btc/tests/harness/mod.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
pub mod node;
|
||||||
|
pub mod transport;
|
||||||
|
pub mod wallet;
|
||||||
|
|
||||||
|
pub mod bob {
|
||||||
|
use xmr_btc::bob::State;
|
||||||
|
|
||||||
|
// TODO: use macro or generics
|
||||||
|
pub fn is_state5(state: &State) -> bool {
|
||||||
|
matches!(state, State::State5 { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use macro or generics
|
||||||
|
pub fn is_state3(state: &State) -> bool {
|
||||||
|
matches!(state, State::State3 { .. })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod alice {
|
||||||
|
use xmr_btc::alice::State;
|
||||||
|
|
||||||
|
// TODO: use macro or generics
|
||||||
|
pub fn is_state4(state: &State) -> bool {
|
||||||
|
matches!(state, State::State4 { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use macro or generics
|
||||||
|
pub fn is_state5(state: &State) -> bool {
|
||||||
|
matches!(state, State::State5 { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use macro or generics
|
||||||
|
pub fn is_state6(state: &State) -> bool {
|
||||||
|
matches!(state, State::State6 { .. })
|
||||||
|
}
|
||||||
|
}
|
92
xmr-btc/tests/harness/node.rs
Normal file
92
xmr-btc/tests/harness/node.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
use crate::harness::{transport::Transport, wallet};
|
||||||
|
use anyhow::Result;
|
||||||
|
use rand::{CryptoRng, RngCore};
|
||||||
|
use xmr_btc::{alice, bob};
|
||||||
|
|
||||||
|
// TODO: merge this with bob node
|
||||||
|
// This struct is responsible for I/O
|
||||||
|
pub struct AliceNode<'a> {
|
||||||
|
transport: Transport<alice::Message, bob::Message>,
|
||||||
|
pub bitcoin_wallet: wallet::bitcoin::Wallet,
|
||||||
|
pub monero_wallet: wallet::monero::AliceWallet<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AliceNode<'a> {
|
||||||
|
pub fn new(
|
||||||
|
transport: Transport<alice::Message, bob::Message>,
|
||||||
|
bitcoin_wallet: wallet::bitcoin::Wallet,
|
||||||
|
monero_wallet: wallet::monero::AliceWallet<'a>,
|
||||||
|
) -> AliceNode<'a> {
|
||||||
|
Self {
|
||||||
|
transport,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_alice_until<'a, R: RngCore + CryptoRng>(
|
||||||
|
alice: &mut AliceNode<'a>,
|
||||||
|
initial_state: alice::State,
|
||||||
|
is_state: fn(&alice::State) -> bool,
|
||||||
|
rng: &mut R,
|
||||||
|
) -> Result<alice::State> {
|
||||||
|
let mut result = initial_state;
|
||||||
|
loop {
|
||||||
|
result = alice::next_state(
|
||||||
|
&alice.bitcoin_wallet,
|
||||||
|
&alice.monero_wallet,
|
||||||
|
&mut alice.transport,
|
||||||
|
result,
|
||||||
|
rng,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
if is_state(&result) {
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: merge this with alice node
|
||||||
|
// This struct is responsible for I/O
|
||||||
|
pub struct BobNode<'a> {
|
||||||
|
transport: Transport<bob::Message, alice::Message>,
|
||||||
|
pub bitcoin_wallet: wallet::bitcoin::Wallet,
|
||||||
|
pub monero_wallet: wallet::monero::BobWallet<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BobNode<'a> {
|
||||||
|
pub fn new(
|
||||||
|
transport: Transport<bob::Message, alice::Message>,
|
||||||
|
bitcoin_wallet: wallet::bitcoin::Wallet,
|
||||||
|
monero_wallet: wallet::monero::BobWallet<'a>,
|
||||||
|
) -> BobNode<'a> {
|
||||||
|
Self {
|
||||||
|
transport,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_bob_until<'a, R: RngCore + CryptoRng>(
|
||||||
|
bob: &mut BobNode<'a>,
|
||||||
|
initial_state: bob::State,
|
||||||
|
is_state: fn(&bob::State) -> bool,
|
||||||
|
rng: &mut R,
|
||||||
|
) -> Result<bob::State> {
|
||||||
|
let mut result = initial_state;
|
||||||
|
loop {
|
||||||
|
result = bob::next_state(
|
||||||
|
&bob.bitcoin_wallet,
|
||||||
|
&bob.monero_wallet,
|
||||||
|
&mut bob.transport,
|
||||||
|
result,
|
||||||
|
rng,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
if is_state(&result) {
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
xmr-btc/tests/harness/transport.rs
Normal file
45
xmr-btc/tests/harness/transport.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use tokio::{
|
||||||
|
stream::StreamExt,
|
||||||
|
sync::mpsc::{Receiver, Sender},
|
||||||
|
};
|
||||||
|
use xmr_btc::transport::{ReceiveMessage, SendMessage};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Transport<SendMsg, RecvMsg> {
|
||||||
|
pub sender: Sender<SendMsg>,
|
||||||
|
pub receiver: Receiver<RecvMsg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<SendMsg, RecvMsg> SendMessage<SendMsg> for Transport<SendMsg, RecvMsg>
|
||||||
|
where
|
||||||
|
SendMsg: Send + Sync,
|
||||||
|
RecvMsg: std::marker::Send,
|
||||||
|
{
|
||||||
|
async fn send_message(&mut self, message: SendMsg) -> Result<()> {
|
||||||
|
let _ = self
|
||||||
|
.sender
|
||||||
|
.send(message)
|
||||||
|
.await
|
||||||
|
.map_err(|_| anyhow!("failed to send message"))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<SendMsg, RecvMsg> ReceiveMessage<RecvMsg> for Transport<SendMsg, RecvMsg>
|
||||||
|
where
|
||||||
|
SendMsg: std::marker::Send,
|
||||||
|
RecvMsg: Send + Sync,
|
||||||
|
{
|
||||||
|
async fn receive_message(&mut self) -> Result<RecvMsg> {
|
||||||
|
let message = self
|
||||||
|
.receiver
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.ok_or_else(|| anyhow!("failed to receive message"))?;
|
||||||
|
Ok(message)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,3 @@
|
|||||||
use crate::bitcoin::{
|
|
||||||
BroadcastSignedTransaction, BuildTxLockPsbt, GetRawTransaction, SignTxLock, TxLock,
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bitcoin::{util::psbt::PartiallySignedTransaction, Address, Amount, Transaction, Txid};
|
use bitcoin::{util::psbt::PartiallySignedTransaction, Address, Amount, Transaction, Txid};
|
||||||
@ -8,6 +5,9 @@ use bitcoin_harness::{bitcoind_rpc::PsbtBase64, Bitcoind};
|
|||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
use xmr_btc::bitcoin::{
|
||||||
|
BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, TxLock, WatchForRawTransaction,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Wallet(pub bitcoin_harness::Wallet);
|
pub struct Wallet(pub bitcoin_harness::Wallet);
|
||||||
@ -107,10 +107,13 @@ impl BroadcastSignedTransaction for Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl GetRawTransaction for Wallet {
|
impl WatchForRawTransaction for Wallet {
|
||||||
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction> {
|
async fn watch_for_raw_transaction(&self, txid: Txid) -> Result<Transaction> {
|
||||||
let tx = self.0.get_raw_transaction(txid).await?;
|
loop {
|
||||||
|
if let Ok(tx) = self.0.get_raw_transaction(txid).await {
|
||||||
Ok(tx)
|
return Ok(tx);
|
||||||
|
}
|
||||||
|
time::delay_for(Duration::from_millis(200)).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
2
xmr-btc/tests/harness/wallet/mod.rs
Normal file
2
xmr-btc/tests/harness/wallet/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod bitcoin;
|
||||||
|
pub mod monero;
|
@ -1,12 +1,12 @@
|
|||||||
use crate::monero::{
|
|
||||||
Amount, CheckTransfer, ImportOutput, PrivateViewKey, PublicKey, PublicViewKey, Transfer,
|
|
||||||
TransferProof, TxHash,
|
|
||||||
};
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use monero::{Address, Network, PrivateKey};
|
use monero::{Address, Network, PrivateKey};
|
||||||
use monero_harness::Monero;
|
use monero_harness::Monero;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use xmr_btc::monero::{
|
||||||
|
Amount, CheckTransfer, CreateWalletForOutput, PrivateViewKey, PublicKey, PublicViewKey,
|
||||||
|
Transfer, TransferProof, TxHash,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AliceWallet<'c>(pub &'c Monero<'c>);
|
pub struct AliceWallet<'c>(pub &'c Monero<'c>);
|
||||||
@ -24,7 +24,7 @@ impl Transfer for AliceWallet<'_> {
|
|||||||
|
|
||||||
let res = self
|
let res = self
|
||||||
.0
|
.0
|
||||||
.transfer_from_alice(amount.0, &destination_address.to_string())
|
.transfer_from_alice(amount.as_piconero(), &destination_address.to_string())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let tx_hash = TxHash(res.tx_hash);
|
let tx_hash = TxHash(res.tx_hash);
|
||||||
@ -32,75 +32,13 @@ impl Transfer for AliceWallet<'_> {
|
|||||||
|
|
||||||
let fee = Amount::from_piconero(res.fee);
|
let fee = Amount::from_piconero(res.fee);
|
||||||
|
|
||||||
Ok((TransferProof { tx_hash, tx_key }, fee))
|
Ok((TransferProof::new(tx_hash, tx_key), fee))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BobWallet<'c>(pub &'c Monero<'c>);
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl CheckTransfer for BobWallet<'_> {
|
|
||||||
async fn check_transfer(
|
|
||||||
&self,
|
|
||||||
public_spend_key: PublicKey,
|
|
||||||
public_view_key: PublicViewKey,
|
|
||||||
transfer_proof: TransferProof,
|
|
||||||
amount: Amount,
|
|
||||||
) -> Result<()> {
|
|
||||||
let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key.into());
|
|
||||||
|
|
||||||
let cli = self.0.bob_wallet_rpc_client();
|
|
||||||
|
|
||||||
let res = cli
|
|
||||||
.check_tx_key(
|
|
||||||
&String::from(transfer_proof.tx_hash),
|
|
||||||
&transfer_proof.tx_key.to_string(),
|
|
||||||
&address.to_string(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if res.received != u64::from(amount) {
|
|
||||||
bail!(
|
|
||||||
"tx_lock doesn't pay enough: expected {:?}, got {:?}",
|
|
||||||
res.received,
|
|
||||||
amount
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ImportOutput for BobWallet<'_> {
|
impl CreateWalletForOutput for AliceWallet<'_> {
|
||||||
async fn import_output(
|
async fn create_and_load_wallet_for_output(
|
||||||
&self,
|
|
||||||
private_spend_key: PrivateKey,
|
|
||||||
private_view_key: PrivateViewKey,
|
|
||||||
) -> Result<()> {
|
|
||||||
let public_spend_key = PublicKey::from_private_key(&private_spend_key);
|
|
||||||
let public_view_key = PublicKey::from_private_key(&private_view_key.into());
|
|
||||||
|
|
||||||
let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key);
|
|
||||||
|
|
||||||
let _ = self
|
|
||||||
.0
|
|
||||||
.bob_wallet_rpc_client()
|
|
||||||
.generate_from_keys(
|
|
||||||
&address.to_string(),
|
|
||||||
&private_spend_key.to_string(),
|
|
||||||
&PrivateKey::from(private_view_key).to_string(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl ImportOutput for AliceWallet<'_> {
|
|
||||||
async fn import_output(
|
|
||||||
&self,
|
&self,
|
||||||
private_spend_key: PrivateKey,
|
private_spend_key: PrivateKey,
|
||||||
private_view_key: PrivateViewKey,
|
private_view_key: PrivateViewKey,
|
||||||
@ -123,3 +61,65 @@ impl ImportOutput for AliceWallet<'_> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BobWallet<'c>(pub &'c Monero<'c>);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl CheckTransfer for BobWallet<'_> {
|
||||||
|
async fn check_transfer(
|
||||||
|
&self,
|
||||||
|
public_spend_key: PublicKey,
|
||||||
|
public_view_key: PublicViewKey,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
|
amount: Amount,
|
||||||
|
) -> Result<()> {
|
||||||
|
let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key.into());
|
||||||
|
|
||||||
|
let cli = self.0.bob_wallet_rpc_client();
|
||||||
|
|
||||||
|
let res = cli
|
||||||
|
.check_tx_key(
|
||||||
|
&String::from(transfer_proof.tx_hash()),
|
||||||
|
&transfer_proof.tx_key().to_string(),
|
||||||
|
&address.to_string(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if res.received != u64::from(amount) {
|
||||||
|
bail!(
|
||||||
|
"tx_lock doesn't pay enough: expected {:?}, got {:?}",
|
||||||
|
res.received,
|
||||||
|
amount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl CreateWalletForOutput for BobWallet<'_> {
|
||||||
|
async fn create_and_load_wallet_for_output(
|
||||||
|
&self,
|
||||||
|
private_spend_key: PrivateKey,
|
||||||
|
private_view_key: PrivateViewKey,
|
||||||
|
) -> Result<()> {
|
||||||
|
let public_spend_key = PublicKey::from_private_key(&private_spend_key);
|
||||||
|
let public_view_key = PublicKey::from_private_key(&private_view_key.into());
|
||||||
|
|
||||||
|
let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key);
|
||||||
|
|
||||||
|
let _ = self
|
||||||
|
.0
|
||||||
|
.bob_wallet_rpc_client()
|
||||||
|
.generate_from_keys(
|
||||||
|
&address.to_string(),
|
||||||
|
&private_spend_key.to_string(),
|
||||||
|
&PrivateKey::from(private_view_key).to_string(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user