Skip to content

Commit b64efcd

Browse files
committed
Validate Esplora merkle proof against the block header's merkle root
`EsploraSyncClient::get_confirmed_tx` parsed the SPV proof returned by the Esplora server but threw away the security check: the merkle root computed by `PartialMerkleTree::extract_matches` was discarded (`let _ = …`), and only the leaf-equality check (`matches[0] == txid`) remained. Anyone can construct a single-leaf partial tree advertising an arbitrary txid via `PartialMerkleTree::from_txids(&[txid], &[true])`, so this gate was vacuous. A malicious or compromised Esplora server could therefore convince `EsploraSyncClient` that any transaction was confirmed in any block by returning `MerkleBlock { header: real_header, txn: forged_partial_tree }`, causing LDK to feed a synthesized `ConfirmedTx` into `Confirm` implementations such as `ChannelManager` / `ChainMonitor`. From there, the channel-funding / closing / HTLC flows would treat the transaction as confirmed at an attacker-chosen height, with consequences ranging from premature state transitions to force-close races. Capture the merkle root returned by `extract_matches` and require it to equal `block_header.merkle_root`, matching the validation the Electrum sibling already performs via `validate_merkle_proof`. Co-Authored-By: HAL 9000
1 parent 1a26867 commit b64efcd

1 file changed

Lines changed: 7 additions & 2 deletions

File tree

lightning-transaction-sync/src/esplora.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,8 +361,13 @@ impl<L: Logger> EsploraSyncClient<L> {
361361

362362
let mut matches = Vec::new();
363363
let mut indexes = Vec::new();
364-
let _ = merkle_block.txn.extract_matches(&mut matches, &mut indexes);
365-
if indexes.len() != 1 || matches.len() != 1 || matches[0] != txid {
364+
let computed_merkle_root =
365+
merkle_block.txn.extract_matches(&mut matches, &mut indexes).ok();
366+
if computed_merkle_root != Some(block_header.merkle_root)
367+
|| indexes.len() != 1
368+
|| matches.len() != 1
369+
|| matches[0] != txid
370+
{
366371
log_error!(self.logger, "Retrieved Merkle block for txid {} doesn't match expectations. This should not happen. Please verify server integrity.", txid);
367372
return Err(InternalError::Failed);
368373
}

0 commit comments

Comments
 (0)