Skip to content

Commit

Permalink
Ignore claim tx if preimage is not known or invalid (breez#653)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielgranhao authored Jan 14, 2025
1 parent 22e79fc commit 0fa2862
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 45 deletions.
50 changes: 44 additions & 6 deletions lib/core/src/recover/recoverer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::wallet::OnchainWallet;
use crate::{
chain::{bitcoin::BitcoinChainService, liquid::LiquidChainService},
recover::model::{BtcScript, HistoryTxId, LBtcScript},
utils,
};

pub(crate) struct Recoverer {
Expand All @@ -44,10 +45,10 @@ impl Recoverer {
})
}

async fn recover_preimages<'a>(
async fn recover_preimages(
&self,
claim_tx_ids_by_swap_id: HashMap<&'a String, Txid>,
) -> Result<HashMap<&'a String, String>> {
claim_tx_ids_by_swap_id: HashMap<&String, Txid>,
) -> Result<HashMap<String, String>> {
let claim_tx_ids: Vec<Txid> = claim_tx_ids_by_swap_id.values().copied().collect();

let claim_txs = self
Expand All @@ -70,8 +71,17 @@ impl Recoverer {

let mut preimages = HashMap::new();
for (swap_id, claim_tx) in claim_txs_by_swap_id {
if let Ok(preimage) = Self::get_send_swap_preimage_from_claim_tx(swap_id, &claim_tx) {
preimages.insert(swap_id, preimage);
match Self::get_send_swap_preimage_from_claim_tx(swap_id, &claim_tx) {
Ok(preimage) => {
preimages.insert(swap_id.to_string(), preimage);
}
Err(e) => {
debug!(
"Couldn't get swap preimage from claim tx {} for swap {swap_id}: {e} - \
could be a cooperative claim tx",
claim_tx.txid()
);
}
}
}
Ok(preimages)
Expand Down Expand Up @@ -121,7 +131,7 @@ impl Recoverer {
let swaps_list = swaps.to_vec().try_into()?;
let histories = self.fetch_swaps_histories(&swaps_list).await?;

let recovered_send_data = self.recover_send_swap_tx_ids(&tx_map, histories.send)?;
let mut recovered_send_data = self.recover_send_swap_tx_ids(&tx_map, histories.send)?;
let recovered_send_with_claim_tx = recovered_send_data
.iter()
.filter_map(|(swap_id, send_data)| {
Expand All @@ -132,6 +142,34 @@ impl Recoverer {
})
.collect::<HashMap<&String, Txid>>();
let mut recovered_preimages = self.recover_preimages(recovered_send_with_claim_tx).await?;
// Keep only verified preimages
recovered_preimages.retain(|swap_id, preimage| {
if let Some(Swap::Send(send_swap)) = swaps.iter().find(|s| s.id() == *swap_id) {
match utils::verify_payment_hash(preimage, &send_swap.invoice) {
Ok(_) => true,
Err(e) => {
error!("Failed to verify recovered preimage for swap {swap_id}: {e}");
false
}
}
} else {
false
}
});
// Keep only claim tx for which there is a recovered or synced preimage
for (swap_id, send_data) in recovered_send_data.iter_mut() {
if let Some(Swap::Send(send_swap)) = swaps.iter().find(|s| s.id() == *swap_id) {
if send_data.claim_tx_id.is_some()
&& !recovered_preimages.contains_key(swap_id)
&& send_swap.preimage.is_none()
{
error!(
"Seemingly found a claim tx but no preimage for swap {swap_id}. Ignoring claim tx."
);
send_data.claim_tx_id = None;
}
}
}

let recovered_receive_data = self.recover_receive_swap_tx_ids(
&tx_map,
Expand Down
16 changes: 1 addition & 15 deletions lib/core/src/send_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ use anyhow::{anyhow, Result};
use async_trait::async_trait;
use boltz_client::swaps::boltz;
use boltz_client::swaps::{boltz::CreateSubmarineResponse, boltz::SubSwapStates};
use boltz_client::util::secrets::Preimage;
use boltz_client::Bolt11Invoice;
use futures_util::TryFutureExt;
use log::{debug, error, info, warn};
use lwk_wollet::elements::{LockTime, Transaction};
Expand Down Expand Up @@ -431,7 +429,7 @@ impl SendSwapHandler {
invoice: &str,
preimage: &str,
) -> Result<(), PaymentError> {
Self::verify_payment_hash(preimage, invoice)?;
utils::verify_payment_hash(preimage, invoice)?;
info!("Preimage is valid for Send Swap {swap_id}");
Ok(())
}
Expand Down Expand Up @@ -598,18 +596,6 @@ impl SendSwapHandler {
}),
}
}

fn verify_payment_hash(preimage: &str, invoice: &str) -> Result<(), PaymentError> {
let preimage = Preimage::from_str(preimage)?;
let preimage_hash = preimage.sha256.to_string();
let invoice = Bolt11Invoice::from_str(invoice)
.map_err(|err| PaymentError::invalid_invoice(&err.to_string()))?;
let invoice_payment_hash = invoice.payment_hash();

(invoice_payment_hash.to_string() == preimage_hash)
.then_some(())
.ok_or(PaymentError::InvalidPreimage)
}
}

#[cfg(test)]
Expand Down
27 changes: 3 additions & 24 deletions lib/core/src/swapper/boltz/liquid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use boltz_client::{
boltz::SwapTxKind,
elements::Transaction,
util::{liquid_genesis_hash, secrets::Preimage},
Amount, Bolt11Invoice, ElementsAddress as Address, LBtcSwapTx,
Amount, ElementsAddress as Address, LBtcSwapTx,
};
use log::info;

Expand All @@ -15,6 +15,7 @@ use crate::{
ChainSwap, Direction, LiquidNetwork, ReceiveSwap, Swap, Utxo,
LOWBALL_FEE_RATE_SAT_PER_VBYTE, STANDARD_FEE_RATE_SAT_PER_VBYTE,
},
utils,
};

use super::BoltzSwapper;
Expand All @@ -26,33 +27,11 @@ impl BoltzSwapper {
invoice: &str,
preimage: &str,
) -> Result<(), PaymentError> {
Self::verify_payment_hash(preimage, invoice)?;
utils::verify_payment_hash(preimage, invoice)?;
info!("Preimage is valid for Send Swap {swap_id}");
Ok(())
}

pub(crate) fn verify_payment_hash(preimage: &str, invoice: &str) -> Result<(), PaymentError> {
let preimage = Preimage::from_str(preimage)?;
let preimage_hash = preimage.sha256.to_string();

let invoice_payment_hash = match Bolt11Invoice::from_str(invoice) {
Ok(invoice) => Ok(invoice.payment_hash().to_string()),
Err(_) => match crate::utils::parse_bolt12_invoice(invoice) {
Ok(invoice) => Ok(invoice.payment_hash().to_string()),
Err(e) => Err(PaymentError::Generic {
err: format!("Could not parse invoice: {e:?}"),
}),
},
}?;

ensure_sdk!(
invoice_payment_hash == preimage_hash,
PaymentError::InvalidPreimage
);

Ok(())
}

pub(crate) fn new_receive_claim_tx(
&self,
swap: &ReceiveSwap,
Expand Down
64 changes: 64 additions & 0 deletions lib/core/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};

use crate::ensure_sdk;
use crate::error::{PaymentError, SdkResult};
use anyhow::{anyhow, ensure, Result};
use boltz_client::util::secrets::Preimage;
use boltz_client::ToHex;
use lwk_wollet::elements::encode::deserialize;
use lwk_wollet::elements::hex::FromHex;
Expand Down Expand Up @@ -81,3 +83,65 @@ pub(crate) fn get_invoice_destination_pubkey(invoice: &str, is_bolt12: bool) ->
.map_err(Into::into)
}
}

/// Verifies a BOLT11/12 invoice against a preimage
pub(crate) fn verify_payment_hash(
preimage: &str,
invoice: &str,
) -> std::result::Result<(), PaymentError> {
let preimage = Preimage::from_str(preimage)?;
let preimage_hash = preimage.sha256.to_string();

let invoice_payment_hash = match Bolt11Invoice::from_str(invoice) {
Ok(invoice) => Ok(invoice.payment_hash().to_string()),
Err(_) => match parse_bolt12_invoice(invoice) {
Ok(invoice) => Ok(invoice.payment_hash().to_string()),
Err(e) => Err(PaymentError::InvalidInvoice {
err: format!("Could not parse invoice: {e:?}"),
}),
},
}?;

ensure_sdk!(
invoice_payment_hash == preimage_hash,
PaymentError::InvalidPreimage
);

Ok(())
}

#[cfg(test)]
mod tests {
use crate::error::PaymentError;
use crate::utils::verify_payment_hash;

#[test]
fn test_verify_payment_hash() -> anyhow::Result<()> {
let bolt11_invoice = "lnbc10u1pnczjaupp55392fur38rc2y9vzmhdy0tclvfels0lvlmzgvmhpg6q2mndxzmrsdqqcqzzsxqyz5vqsp5ya6pvchlsvl3mzqh3zw4hg3tz5pww77q6rcwfr52qchyrp7s6krs9p4gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqysgqgnp0sskk0ljjew8vkc3udhzgquzs79evf5wezfaex9q4gjk5qcn8m3luauyte93lgassd8skh5m90glhtt52ry2wtftzrjn4h076z7sqdjry3d";
let bolt11_preimage = "c17a0a28d0523596ec909c2d439c0c2315b5bd996bf4ff48be50b2df08fb8ac1";
let bolt12_invoice = "lni1qqg274t4yefgrj0pn3cwjz4vaacayyxvqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glc7quxqu8s2dggmw92army03kdxt825vqkawcz33kvrennr6g2fu6f7vpqx40040703nghrcj3pp9gax3y2my9v4kd5gtzv5k68k7vnxa3y7dlqqee9gty2hdrprsd8t04jz5nea79phgs9vyp9ruexzczwfhzg57c3yl345x3jy3kqpgr3e3u54fu0vewzjv2jq4lvj8gghjf5h8kgpw23c8tugua4qh432mylsdj3ac260fwvptzgcqpq486c0qlz6aqrj7q7k804w5mv92jqv85yszcyypft03wvpgapj7t0h58t6da6tdwx69admya2p0dl435la7wq4ljk79ql5qe5quxfmcztl0gldv8mxy3sm8x5jscdz27u39fy6luxu8zcdn9j73l3upa3vjg727ft7cwfkg4yqxyctm98hq6utue2k5k5at05azu2wgw57szq2qztaq2rnqjt6ugna4em3uj2el3cr7gj2glzuwkm346qpx93y9ruqz9fkumys35w9jdqxs45qzec44fhpy7lldwzt80y3q33sk09nkgf7h9r6etd45zp80snmz5x4uquqk7a0cusp0sluhku8md0eaxejqvkdd6mcp0gxqr6hsfwsxu4vx6lx08axqpj3fe87jeqfvdmetqxcaadn993vv3fe3qpny568lpz00dj3w6rag6gv3jyj9nnqmh6455l4h7ewe4zstwprmumemut8fexnnmgqmfzj0xwgr3mmwygw59jjqqv0h9vgc8vhkcx4g3s3av4kd48w4p4qs29zggh4vz924t23m7va0am4d7d4uur96uypayuchcgs2wxm0ktsaadewffys0jdlz245saetsd4f2m7ljp3tdxmt45qw64slkmlwaeak0h7508hftjdh6vyzr7skx2eucwwmgce0pydvgx5egmv4fnu0e7383ygyhwa0vwd4gy6zsez6kktvdezn79ejh2n8zmdtk998jvzuq7syv4gsuqqqq86qqqqqxgqfqqqqqqqqqqqp7sqqqqqqqq85ysqqqpfqyv7pxql4xqvq4rq9gyztzsk4ktppyn45peyvhfpl6lv8ewjr666gkzttspjkp8zn0g2n9f2srpapyptsrqgqqpvppq2lkfr5ytey6tnmyqh9gur47yww6st6c4dj0cxeg7u9d85hxq43yduzqrxguu82stp5egwzefmhvm9k63r0nxemf0pg54j3hdfzgt068te3dv5s089p54gcplnk778kcnfhkn6l8tggjqmgyc88vrgr6gc3gx7q";
let bolt12_preimage = "443c900d61ed8e90a7bfbb7958f1485a7f57e74adacd3e216deba03f8326a392";

// Test valid inputs
verify_payment_hash(bolt11_preimage, bolt11_invoice)?;
verify_payment_hash(bolt12_preimage, bolt12_invoice)?;

// Test invalid preimages
assert!(matches!(
verify_payment_hash(bolt12_preimage, bolt11_invoice),
Err(PaymentError::InvalidPreimage { .. })
));
assert!(matches!(
verify_payment_hash(bolt11_preimage, bolt12_invoice),
Err(PaymentError::InvalidPreimage { .. })
));

// Test invalid invoice
assert!(matches!(
verify_payment_hash(bolt11_preimage, "not an invoice"),
Err(PaymentError::InvalidInvoice { .. })
));

Ok(())
}
}

0 comments on commit 0fa2862

Please sign in to comment.