Skip to content

Commit 57524e3

Browse files
committed
Verify Electrum transaction responses before use
Electrum confirmations must reject transaction_get responses whose body does not compute the requested txid. Otherwise a malicious server can substitute an unrelated transaction and provide matching Merkle data for the substituted body. Co-Authored-By: HAL 9000 This finding was discovered by Project Loupe
1 parent e1c411e commit 57524e3

1 file changed

Lines changed: 30 additions & 0 deletions

File tree

lightning-transaction-sync/src/electrum.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,11 @@ impl<L: Logger> ElectrumSyncClient<L> {
277277
for txid in &sync_state.watched_transactions {
278278
match self.client.transaction_get(&txid) {
279279
Ok(tx) => {
280+
if tx.compute_txid() != *txid {
281+
log_error!(self.logger, "Retrieved transaction for txid {} doesn't match expectations. This should not happen. Please verify server integrity.", txid);
282+
return Err(InternalError::Failed);
283+
}
284+
280285
// Bitcoin Core's Merkle tree implementation has no way to discern between
281286
// internal and leaf node entries. As a consequence it is susceptible to an
282287
// attacker injecting additional transactions by crafting 64-byte
@@ -369,6 +374,11 @@ impl<L: Logger> ElectrumSyncClient<L> {
369374

370375
match self.client.transaction_get(&txid) {
371376
Ok(tx) => {
377+
if tx.compute_txid() != txid {
378+
log_error!(self.logger, "Retrieved transaction for txid {} doesn't match expectations. This should not happen. Please verify server integrity.", txid);
379+
return Err(InternalError::Failed);
380+
}
381+
372382
let mut is_spend = false;
373383
for txin in &tx.input {
374384
let watched_outpoint =
@@ -517,3 +527,23 @@ impl<L: Logger> Filter for ElectrumSyncClient<L> {
517527
locked_queue.outputs.insert(output.outpoint.into_bitcoin_outpoint(), output);
518528
}
519529
}
530+
531+
#[cfg(test)]
532+
mod tests {
533+
#[test]
534+
fn transaction_get_responses_are_verified_at_call_sites() {
535+
let src = include_str!("electrum.rs");
536+
let watched_transaction_check = concat!("if tx.compute_", "txid() != *txid");
537+
let watched_output_spend_check = concat!("if tx.compute_", "txid() != txid");
538+
539+
assert!(
540+
src.contains(watched_transaction_check),
541+
"watched transaction_get responses must be verified against the requested txid"
542+
);
543+
assert!(
544+
src.contains(watched_output_spend_check),
545+
"watched-output spend transaction_get responses must be verified against the \
546+
requested txid"
547+
);
548+
}
549+
}

0 commit comments

Comments
 (0)