diff --git a/lightning-transaction-sync/src/electrum.rs b/lightning-transaction-sync/src/electrum.rs index aca2384146..9df0f178a5 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 !self.validate_merkle_proof(**txid, block_header.merkle_root.as_hash(), &merkle_res)? { + log_trace!(self.logger, "Inconsistency: Block {} was unconfirmed during syncing.", block_header.block_hash()); + return Err(InternalError::Inconsistency); + } 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 !self.validate_merkle_proof(txid, block_header.merkle_root.as_hash(), &merkle_res)? { + log_trace!(self.logger, "Inconsistency: Block {} was unconfirmed during syncing.", block_header.block_hash()); + return Err(InternalError::Inconsistency); + } confirmed_txs.push(ConfirmedTx { tx: tx.clone(), block_header, block_height: prob_conf_height, pos: merkle_res.pos }); } Err(e) => { @@ -384,6 +393,31 @@ where &self.client } + fn validate_merkle_proof(&self, txid: Txid, merkle_root: Sha256d, merkle_res: &GetMerkleRes) -> Result { + let mut index = merkle_res.pos; + let mut cur = txid.as_hash(); + for bytes in &merkle_res.merkle { + let mut reversed = Vec::with_capacity(32); + reversed.truncate(0); + reversed.extend(bytes.iter().rev()); + let next_hash = Sha256d::from_slice(&reversed).map_err(|_| { + log_error!(self.logger, "Failed due to the server sending us bogus transaction data. This should not happen. Please verify server integrity."); + InternalError::Failed + })?; + + 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; + } + + Ok(cur == merkle_root) + } } impl Filter for ElectrumSyncClient