mirror of
https://github.com/aljazceru/breez-sdk-liquid.git
synced 2026-01-19 14:04:22 +01:00
Ignore claim tx if preimage is not known or invalid (#653)
This commit is contained in:
@@ -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 {
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)| {
|
||||
@@ -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,
|
||||
|
||||
@@ -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};
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user