diff --git a/lightning-transaction-sync/src/electrum.rs b/lightning-transaction-sync/src/electrum.rs index aca23841462..b23a9a26b3f 100644 --- a/lightning-transaction-sync/src/electrum.rs +++ b/lightning-transaction-sync/src/electrum.rs @@ -3,6 +3,7 @@ use crate::error::{TxSyncError, InternalError}; use electrum_client::Client as ElectrumClient; use electrum_client::ElectrumApi; +use electrum_client::GetMerkleRes; use lightning::util::logger::Logger; use lightning::{log_error, log_debug, log_trace}; @@ -10,6 +11,8 @@ use lightning::chain::WatchedOutput; use lightning::chain::{Confirm, Filter}; use bitcoin::{BlockHash, BlockHeader, Script, Txid}; +use bitcoin::hashes::Hash; +use bitcoin::hashes::sha256d::Hash as Sha256d; use std::ops::Deref; use std::sync::Mutex; @@ -263,7 +266,10 @@ where debug_assert_eq!(prob_conf_height, merkle_res.block_height as u32); match self.client.block_header(prob_conf_height as usize) { Ok(block_header) => { - // TODO can we check the merkle proof here to be sure? + if !validate_merkle_proof(**txid, block_header.merkle_root.as_hash(), &merkle_res) { + log_error!(self.logger, "Retrieved Merkle block for txid {} doesn't match expectations. This should not happen. Please verify server integrity.", txid); + return Err(InternalError::Failed); + } confirmed_txs.push(ConfirmedTx { tx: tx.clone(), block_header, block_height: prob_conf_height, pos: merkle_res.pos }); } Err(e) => { @@ -301,7 +307,10 @@ where debug_assert_eq!(prob_conf_height, merkle_res.block_height as u32); match self.client.block_header(prob_conf_height as usize) { Ok(block_header) => { - // TODO can we check the merkle proof here to be sure? + if !validate_merkle_proof(txid, block_header.merkle_root.as_hash(), &merkle_res) { + log_error!(self.logger, "Retrieved Merkle block for txid {} doesn't match expectations. This should not happen. Please verify server integrity.", txid); + return Err(InternalError::Failed); + } confirmed_txs.push(ConfirmedTx { tx: tx.clone(), block_header, block_height: prob_conf_height, pos: merkle_res.pos }); } Err(e) => { @@ -386,6 +395,34 @@ where } +fn validate_merkle_proof(txid: Txid, merkle_root: Sha256d, merkle_res: &GetMerkleRes) -> bool { + let mut hashes = Vec::new(); + for m in &merkle_res.merkle { + let mut reversed = Vec::with_capacity(32); + reversed.truncate(0); + reversed.extend(m.iter().rev()); + let h = Sha256d::from_slice(&reversed).unwrap(); + hashes.push(h); + } + + let mut index = merkle_res.pos; + let mut cur = txid.as_hash(); + hashes.reverse(); + + while let Some(next_hash) = hashes.pop() { + let (left, right) = if index % 2 == 0 { + (cur, next_hash) + } else { + (next_hash, cur) + }; + + let data = [&left[..], &right[..]].concat(); + cur = Sha256d::hash(&data); + index /= 2; + } + cur == merkle_root +} + impl Filter for ElectrumSyncClient where L::Target: Logger,