Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Include proof of reserves in quote #1749

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions monero-rpc/src/wallet.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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<u64>,
message: Option<String>,
) -> GetReserveProof;
async fn check_reserve_proof(
&self,
address: String,
message: Option<String>,
signature: String,
) -> CheckReserveProof;
}

#[jsonrpc_client::implement(MoneroWalletRpc)]
Expand Down Expand Up @@ -216,6 +227,16 @@ pub struct SweepAll {
pub tx_hash_list: Vec<String>,
}

#[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,
Expand Down
27 changes: 23 additions & 4 deletions swap/src/api/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -419,6 +420,8 @@ impl Request {
}
};

let bid_quote_clone = bid_quote.clone();

context.tasks.clone().spawn(async move {
tokio::select! {
biased;
Expand All @@ -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,
Expand All @@ -449,6 +455,7 @@ impl Request {
max_givable,
|| bitcoin_wallet.sync(),
estimate_fee,
check_reserve_proof,
);

let (amount, fees) = match determine_amount.await {
Expand Down Expand Up @@ -501,7 +508,7 @@ impl Request {

Ok(json!({
"swapId": swap_id.to_string(),
"quote": bid_quote,
"quote": bid_quote_clone,
}))
}
Method::Resume { swap_id } => {
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -858,14 +865,15 @@ fn qr_code(value: &impl ToString) -> Result<String> {
Ok(qr_code)
}

pub async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS, FFE, TFE>(
pub async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS, FFE, TFE, FCRP, CRP>(
json: bool,
bid_quote: BidQuote,
get_new_address: impl Future<Output = Result<bitcoin::Address>>,
balance: FB,
max_giveable_fn: FMG,
sync: FS,
estimate_fee: FFE,
check_reserve_proof: FCRP,
) -> Result<(Amount, Amount)>
where
TB: Future<Output = Result<Amount>>,
Expand All @@ -874,8 +882,10 @@ where
FMG: Fn() -> TMG,
TS: Future<Output = Result<()>>,
FS: Fn() -> TS,
FFE: Fn(Amount) -> TFE,
TFE: Future<Output = Result<Amount>>,
FFE: Fn(Amount) -> TFE,
CRP: Future<Output = Result<bool>>,
FCRP: Fn(ReserveProof) -> CRP,
{
if bid_quote.max_quantity == Amount::ZERO {
bail!(ZeroQuoteReceived)
Expand All @@ -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?;

Expand Down
5 changes: 5 additions & 0 deletions swap/src/asb/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}",
Expand All @@ -418,13 +421,15 @@ where
price: ask_price,
min_quantity: min_buy,
max_quantity: max_bitcoin_for_monero,
reserve_proof: Some(xmr_reserve_proof),
});
}

Ok(BidQuote {
price: ask_price,
min_quantity: min_buy,
max_quantity: max_buy,
reserve_proof: Some(xmr_reserve_proof),
})
}

Expand Down
4 changes: 2 additions & 2 deletions swap/src/cli/list_sellers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -284,7 +284,7 @@ impl EventLoop {

Ok(Seller {
multiaddr: address.clone(),
status: Status::Online(*quote),
status: Status::Online(quote.clone()),
})
}
QuoteStatus::Received(Status::Unreachable) => {
Expand Down
7 changes: 7 additions & 0 deletions swap/src/monero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
}

impl TransferProof {
pub fn new(tx_hash: TxHash, tx_key: PrivateKey) -> Self {
Self { tx_hash, tx_key }
Expand Down
34 changes: 34 additions & 0 deletions swap/src/monero/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<wallet::Client>,
Expand Down Expand Up @@ -314,6 +316,38 @@ impl Wallet {
}
unreachable!("Loop should have returned by now");
}

pub async fn get_reserve_proof(
&self,
amount: Option<u64>,
message: Option<String>,
) -> Result<ReserveProof> {
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<bool> {
Ok(self
.inner
.lock()
.await
.check_reserve_proof(proof.address, proof.message, proof.signature)
.await?
.good)
}
}

#[derive(Debug)]
Expand Down
4 changes: 3 additions & 1 deletion swap/src/network/quote.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::monero::ReserveProof;
use crate::network::json_pull_codec::JsonPullCodec;
use crate::{asb, bitcoin, cli};
use libp2p::core::ProtocolName;
Expand All @@ -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")]
Expand All @@ -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<ReserveProof>,
}

#[derive(Clone, Copy, Debug, thiserror::Error)]
Expand Down
Loading