feat: Include proof of reserves in quote

This commit is contained in:
binarybaron 2024-08-07 14:56:32 +02:00
parent b18ba95e8c
commit a810c0108b
No known key found for this signature in database
GPG Key ID: 99B75D3E1476A26E
7 changed files with 97 additions and 9 deletions

View File

@ -1,9 +1,8 @@
use std::fmt;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use rust_decimal::Decimal; use rust_decimal::Decimal;
use serde::de::Error; use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use std::fmt;
#[jsonrpc_client::api(version = "2.0")] #[jsonrpc_client::api(version = "2.0")]
pub trait MoneroWalletRpc { pub trait MoneroWalletRpc {
@ -36,6 +35,18 @@ pub trait MoneroWalletRpc {
async fn refresh(&self) -> Refreshed; async fn refresh(&self) -> Refreshed;
async fn sweep_all(&self, address: String) -> SweepAll; async fn sweep_all(&self, address: String) -> SweepAll;
async fn get_version(&self) -> Version; 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)] #[jsonrpc_client::implement(MoneroWalletRpc)]
@ -216,6 +227,16 @@ pub struct SweepAll {
pub tx_hash_list: Vec<String>, 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)] #[derive(Debug, Copy, Clone, Deserialize)]
pub struct Version { pub struct Version {
pub version: u32, pub version: u32,

View File

@ -2,6 +2,7 @@ use crate::api::Context;
use crate::bitcoin::{Amount, ExpiredTimelocks, TxLock}; use crate::bitcoin::{Amount, ExpiredTimelocks, TxLock};
use crate::cli::{list_sellers, EventLoop, SellerStatus}; use crate::cli::{list_sellers, EventLoop, SellerStatus};
use crate::libp2p_ext::MultiAddrExt; use crate::libp2p_ext::MultiAddrExt;
use crate::monero::ReserveProof;
use crate::network::quote::{BidQuote, ZeroQuoteReceived}; use crate::network::quote::{BidQuote, ZeroQuoteReceived};
use crate::network::swarm; use crate::network::swarm;
use crate::protocol::bob::{BobState, Swap}; 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 { context.tasks.clone().spawn(async move {
tokio::select! { tokio::select! {
biased; biased;
@ -440,6 +443,9 @@ impl Request {
swap_result = async { swap_result = async {
let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size()); let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size());
let estimate_fee = |amount| bitcoin_wallet.estimate_fee(TxLock::weight(), amount); 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( let determine_amount = determine_btc_to_swap(
context.config.json, context.config.json,
@ -449,6 +455,7 @@ impl Request {
max_givable, max_givable,
|| bitcoin_wallet.sync(), || bitcoin_wallet.sync(),
estimate_fee, estimate_fee,
check_reserve_proof,
); );
let (amount, fees) = match determine_amount.await { let (amount, fees) = match determine_amount.await {
@ -501,7 +508,7 @@ impl Request {
Ok(json!({ Ok(json!({
"swapId": swap_id.to_string(), "swapId": swap_id.to_string(),
"quote": bid_quote, "quote": bid_quote_clone,
})) }))
} }
Method::Resume { swap_id } => { Method::Resume { swap_id } => {
@ -763,7 +770,7 @@ impl Request {
.await?; .await?;
for seller in &sellers { for seller in &sellers {
match seller.status { match seller.status.clone() {
SellerStatus::Online(quote) => { SellerStatus::Online(quote) => {
tracing::info!( tracing::info!(
price = %quote.price.to_string(), price = %quote.price.to_string(),
@ -858,7 +865,7 @@ fn qr_code(value: &impl ToString) -> Result<String> {
Ok(qr_code) 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, json: bool,
bid_quote: BidQuote, bid_quote: BidQuote,
get_new_address: impl Future<Output = Result<bitcoin::Address>>, get_new_address: impl Future<Output = Result<bitcoin::Address>>,
@ -866,6 +873,7 @@ pub async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS, FFE, TFE>(
max_giveable_fn: FMG, max_giveable_fn: FMG,
sync: FS, sync: FS,
estimate_fee: FFE, estimate_fee: FFE,
check_reserve_proof: FCRP,
) -> Result<(Amount, Amount)> ) -> Result<(Amount, Amount)>
where where
TB: Future<Output = Result<Amount>>, TB: Future<Output = Result<Amount>>,
@ -874,8 +882,10 @@ where
FMG: Fn() -> TMG, FMG: Fn() -> TMG,
TS: Future<Output = Result<()>>, TS: Future<Output = Result<()>>,
FS: Fn() -> TS, FS: Fn() -> TS,
FFE: Fn(Amount) -> TFE,
TFE: Future<Output = Result<Amount>>, 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 { if bid_quote.max_quantity == Amount::ZERO {
bail!(ZeroQuoteReceived) bail!(ZeroQuoteReceived)
@ -888,6 +898,15 @@ where
"Received quote", "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?; sync().await?;
let mut max_giveable = max_giveable_fn().await?; let mut max_giveable = max_giveable_fn().await?;

View File

@ -406,9 +406,12 @@ where
price: ask_price, price: ask_price,
min_quantity: bitcoin::Amount::ZERO, min_quantity: bitcoin::Amount::ZERO,
max_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 { if max_buy > max_bitcoin_for_monero {
tracing::warn!( 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 {}", "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, price: ask_price,
min_quantity: min_buy, min_quantity: min_buy,
max_quantity: max_bitcoin_for_monero, max_quantity: max_bitcoin_for_monero,
reserve_proof: Some(xmr_reserve_proof),
}); });
} }
@ -425,6 +429,7 @@ where
price: ask_price, price: ask_price,
min_quantity: min_buy, min_quantity: min_buy,
max_quantity: max_buy, max_quantity: max_buy,
reserve_proof: Some(xmr_reserve_proof),
}) })
} }

View File

@ -67,7 +67,7 @@ pub struct Seller {
pub multiaddr: Multiaddr, 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 { pub enum Status {
Online(BidQuote), Online(BidQuote),
Unreachable, Unreachable,
@ -284,7 +284,7 @@ impl EventLoop {
Ok(Seller { Ok(Seller {
multiaddr: address.clone(), multiaddr: address.clone(),
status: Status::Online(*quote), status: Status::Online(quote.clone()),
}) })
} }
QuoteStatus::Received(Status::Unreachable) => { QuoteStatus::Received(Status::Unreachable) => {

View File

@ -199,6 +199,13 @@ pub struct TransferProof {
tx_key: PrivateKey, 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 { impl TransferProof {
pub fn new(tx_hash: TxHash, tx_key: PrivateKey) -> Self { pub fn new(tx_hash: TxHash, tx_key: PrivateKey) -> Self {
Self { tx_hash, tx_key } Self { tx_hash, tx_key }

View File

@ -13,6 +13,8 @@ use tokio::sync::Mutex;
use tokio::time::Interval; use tokio::time::Interval;
use url::Url; use url::Url;
use super::ReserveProof;
#[derive(Debug)] #[derive(Debug)]
pub struct Wallet { pub struct Wallet {
inner: Mutex<wallet::Client>, inner: Mutex<wallet::Client>,
@ -314,6 +316,38 @@ impl Wallet {
} }
unreachable!("Loop should have returned by now"); 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)] #[derive(Debug)]

View File

@ -1,3 +1,4 @@
use crate::monero::ReserveProof;
use crate::network::json_pull_codec::JsonPullCodec; use crate::network::json_pull_codec::JsonPullCodec;
use crate::{asb, bitcoin, cli}; use crate::{asb, bitcoin, cli};
use libp2p::core::ProtocolName; use libp2p::core::ProtocolName;
@ -24,7 +25,7 @@ impl ProtocolName for BidQuoteProtocol {
} }
/// Represents a quote for buying XMR. /// 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 { pub struct BidQuote {
/// The price at which the maker is willing to buy at. /// The price at which the maker is willing to buy at.
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
@ -35,6 +36,7 @@ pub struct BidQuote {
/// The maximum quantity the maker is willing to buy. /// The maximum quantity the maker is willing to buy.
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
pub max_quantity: bitcoin::Amount, pub max_quantity: bitcoin::Amount,
pub reserve_proof: Option<ReserveProof>,
} }
#[derive(Clone, Copy, Debug, thiserror::Error)] #[derive(Clone, Copy, Debug, thiserror::Error)]