diff --git a/monero-rpc/src/wallet.rs b/monero-rpc/src/wallet.rs index 3e21ad1b8..3af1caf6e 100644 --- a/monero-rpc/src/wallet.rs +++ b/monero-rpc/src/wallet.rs @@ -1,9 +1,8 @@ -use std::fmt; - use anyhow::{Context, Result}; use rust_decimal::Decimal; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize}; +use std::fmt; #[jsonrpc_client::api(version = "2.0")] pub trait MoneroWalletRpc { @@ -36,6 +35,18 @@ pub trait MoneroWalletRpc { async fn refresh(&self) -> Refreshed; async fn sweep_all(&self, address: String) -> SweepAll; async fn get_version(&self) -> Version; + async fn get_reserve_proof( + &self, + all: bool, + amount: Option, + message: Option, + ) -> GetReserveProof; + async fn check_reserve_proof( + &self, + address: String, + message: Option, + signature: String, + ) -> CheckReserveProof; } #[jsonrpc_client::implement(MoneroWalletRpc)] @@ -216,6 +227,16 @@ pub struct SweepAll { pub tx_hash_list: Vec, } +#[derive(Debug, Clone, Deserialize)] +pub struct GetReserveProof { + pub signature: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct CheckReserveProof { + pub good: bool, +} + #[derive(Debug, Copy, Clone, Deserialize)] pub struct Version { pub version: u32, diff --git a/swap/src/api/request.rs b/swap/src/api/request.rs index b1e9c68cc..c7849c42f 100644 --- a/swap/src/api/request.rs +++ b/swap/src/api/request.rs @@ -2,6 +2,7 @@ use crate::api::Context; use crate::bitcoin::{Amount, ExpiredTimelocks, TxLock}; use crate::cli::{list_sellers, EventLoop, SellerStatus}; use crate::libp2p_ext::MultiAddrExt; +use crate::monero::ReserveProof; use crate::network::quote::{BidQuote, ZeroQuoteReceived}; use crate::network::swarm; use crate::protocol::bob::{BobState, Swap}; @@ -419,6 +420,8 @@ impl Request { } }; + let bid_quote_clone = bid_quote.clone(); + context.tasks.clone().spawn(async move { tokio::select! { biased; @@ -440,6 +443,9 @@ impl Request { swap_result = async { let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size()); let estimate_fee = |amount| bitcoin_wallet.estimate_fee(TxLock::weight(), amount); + let check_reserve_proof = |reserve_proof: ReserveProof| { + monero_wallet.check_reserve_proof(reserve_proof) + }; let determine_amount = determine_btc_to_swap( context.config.json, @@ -449,6 +455,7 @@ impl Request { max_givable, || bitcoin_wallet.sync(), estimate_fee, + check_reserve_proof, ); let (amount, fees) = match determine_amount.await { @@ -501,7 +508,7 @@ impl Request { Ok(json!({ "swapId": swap_id.to_string(), - "quote": bid_quote, + "quote": bid_quote_clone, })) } Method::Resume { swap_id } => { @@ -763,7 +770,7 @@ impl Request { .await?; for seller in &sellers { - match seller.status { + match seller.status.clone() { SellerStatus::Online(quote) => { tracing::info!( price = %quote.price.to_string(), @@ -858,7 +865,7 @@ fn qr_code(value: &impl ToString) -> Result { Ok(qr_code) } -pub async fn determine_btc_to_swap( +pub async fn determine_btc_to_swap( json: bool, bid_quote: BidQuote, get_new_address: impl Future>, @@ -866,6 +873,7 @@ pub async fn determine_btc_to_swap( max_giveable_fn: FMG, sync: FS, estimate_fee: FFE, + check_reserve_proof: FCRP, ) -> Result<(Amount, Amount)> where TB: Future>, @@ -874,8 +882,10 @@ where FMG: Fn() -> TMG, TS: Future>, FS: Fn() -> TS, - FFE: Fn(Amount) -> TFE, TFE: Future>, + FFE: Fn(Amount) -> TFE, + CRP: Future>, + FCRP: Fn(ReserveProof) -> CRP, { if bid_quote.max_quantity == Amount::ZERO { bail!(ZeroQuoteReceived) @@ -888,6 +898,15 @@ where "Received quote", ); + if let Some(reserve_proof) = &bid_quote.reserve_proof { + tracing::info!("Received reserve proof"); + if check_reserve_proof(reserve_proof.clone()).await? { + tracing::info!("Reserve proof is valid"); + } else { + bail!("Reserve proof is invalid"); + } + } + sync().await?; let mut max_giveable = max_giveable_fn().await?; diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 8e3ac4c34..454062606 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -406,9 +406,12 @@ where price: ask_price, min_quantity: bitcoin::Amount::ZERO, max_quantity: bitcoin::Amount::ZERO, + reserve_proof: None, }); } + let xmr_reserve_proof = self.monero_wallet.get_reserve_proof(None, None).await?; + if max_buy > max_bitcoin_for_monero { tracing::warn!( "Your Monero balance is too low to initiate a swap with the maximum swap amount {} that you have specified in your config. You can at most swap {}", @@ -418,6 +421,7 @@ where price: ask_price, min_quantity: min_buy, max_quantity: max_bitcoin_for_monero, + reserve_proof: Some(xmr_reserve_proof), }); } @@ -425,6 +429,7 @@ where price: ask_price, min_quantity: min_buy, max_quantity: max_buy, + reserve_proof: Some(xmr_reserve_proof), }) } diff --git a/swap/src/cli/list_sellers.rs b/swap/src/cli/list_sellers.rs index 381c561f9..8b7f5997b 100644 --- a/swap/src/cli/list_sellers.rs +++ b/swap/src/cli/list_sellers.rs @@ -67,7 +67,7 @@ pub struct Seller { pub multiaddr: Multiaddr, } -#[derive(Debug, Serialize, PartialEq, Eq, Hash, Copy, Clone, Ord, PartialOrd)] +#[derive(Debug, Serialize, PartialEq, Eq, Hash, Clone, Ord, PartialOrd)] pub enum Status { Online(BidQuote), Unreachable, @@ -284,7 +284,7 @@ impl EventLoop { Ok(Seller { multiaddr: address.clone(), - status: Status::Online(*quote), + status: Status::Online(quote.clone()), }) } QuoteStatus::Received(Status::Unreachable) => { diff --git a/swap/src/monero.rs b/swap/src/monero.rs index 8205e75f9..756d2ffc5 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -199,6 +199,13 @@ pub struct TransferProof { tx_key: PrivateKey, } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct ReserveProof { + address: String, + signature: String, + message: Option, +} + impl TransferProof { pub fn new(tx_hash: TxHash, tx_key: PrivateKey) -> Self { Self { tx_hash, tx_key } diff --git a/swap/src/monero/wallet.rs b/swap/src/monero/wallet.rs index e3db3416a..a96fde6d6 100644 --- a/swap/src/monero/wallet.rs +++ b/swap/src/monero/wallet.rs @@ -13,6 +13,8 @@ use tokio::sync::Mutex; use tokio::time::Interval; use url::Url; +use super::ReserveProof; + #[derive(Debug)] pub struct Wallet { inner: Mutex, @@ -314,6 +316,38 @@ impl Wallet { } unreachable!("Loop should have returned by now"); } + + pub async fn get_reserve_proof( + &self, + amount: Option, + message: Option, + ) -> Result { + let signature = self + .inner + .lock() + .await + .get_reserve_proof(amount.is_none(), amount, message.clone()) + .await? + .signature; + + let address = self.inner.lock().await.get_address(0).await?.address; + + Ok(ReserveProof { + address, + signature, + message, + }) + } + + pub async fn check_reserve_proof(&self, proof: ReserveProof) -> Result { + Ok(self + .inner + .lock() + .await + .check_reserve_proof(proof.address, proof.message, proof.signature) + .await? + .good) + } } #[derive(Debug)] diff --git a/swap/src/network/quote.rs b/swap/src/network/quote.rs index caf99e09c..245d62a1c 100644 --- a/swap/src/network/quote.rs +++ b/swap/src/network/quote.rs @@ -1,3 +1,4 @@ +use crate::monero::ReserveProof; use crate::network::json_pull_codec::JsonPullCodec; use crate::{asb, bitcoin, cli}; use libp2p::core::ProtocolName; @@ -24,7 +25,7 @@ impl ProtocolName for BidQuoteProtocol { } /// Represents a quote for buying XMR. -#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct BidQuote { /// The price at which the maker is willing to buy at. #[serde(with = "::bitcoin::util::amount::serde::as_sat")] @@ -35,6 +36,7 @@ pub struct BidQuote { /// The maximum quantity the maker is willing to buy. #[serde(with = "::bitcoin::util::amount::serde::as_sat")] pub max_quantity: bitcoin::Amount, + pub reserve_proof: Option, } #[derive(Clone, Copy, Debug, thiserror::Error)]