Skip to content

Commit e79d5cd

Browse files
committed
f Validate merkle inclusion proofs
1 parent 5d6e258 commit e79d5cd

File tree

1 file changed

+36
-2
lines changed

1 file changed

+36
-2
lines changed

lightning-transaction-sync/src/electrum.rs

+36-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ use crate::error::{TxSyncError, InternalError};
33

44
use electrum_client::Client as ElectrumClient;
55
use electrum_client::ElectrumApi;
6+
use electrum_client::GetMerkleRes;
67

78
use lightning::util::logger::Logger;
89
use lightning::{log_error, log_debug, log_trace};
910
use lightning::chain::WatchedOutput;
1011
use lightning::chain::{Confirm, Filter};
1112

1213
use bitcoin::{BlockHash, BlockHeader, Script, Txid};
14+
use bitcoin::hashes::Hash;
15+
use bitcoin::hashes::sha256d::Hash as Sha256d;
1316

1417
use std::ops::Deref;
1518
use std::sync::Mutex;
@@ -263,7 +266,10 @@ where
263266
debug_assert_eq!(prob_conf_height, merkle_res.block_height as u32);
264267
match self.client.block_header(prob_conf_height as usize) {
265268
Ok(block_header) => {
266-
// TODO can we check the merkle proof here to be sure?
269+
if !self.validate_merkle_proof(**txid, block_header.merkle_root.as_hash(), &merkle_res)? {
270+
log_trace!(self.logger, "Inconsistency: Block {} was unconfirmed during syncing.", block_header.block_hash());
271+
return Err(InternalError::Inconsistency);
272+
}
267273
confirmed_txs.push(ConfirmedTx { tx: tx.clone(), block_header, block_height: prob_conf_height, pos: merkle_res.pos });
268274
}
269275
Err(e) => {
@@ -301,7 +307,10 @@ where
301307
debug_assert_eq!(prob_conf_height, merkle_res.block_height as u32);
302308
match self.client.block_header(prob_conf_height as usize) {
303309
Ok(block_header) => {
304-
// TODO can we check the merkle proof here to be sure?
310+
if !self.validate_merkle_proof(txid, block_header.merkle_root.as_hash(), &merkle_res)? {
311+
log_trace!(self.logger, "Inconsistency: Block {} was unconfirmed during syncing.", block_header.block_hash());
312+
return Err(InternalError::Inconsistency);
313+
}
305314
confirmed_txs.push(ConfirmedTx { tx: tx.clone(), block_header, block_height: prob_conf_height, pos: merkle_res.pos });
306315
}
307316
Err(e) => {
@@ -384,6 +393,31 @@ where
384393
&self.client
385394
}
386395

396+
fn validate_merkle_proof(&self, txid: Txid, merkle_root: Sha256d, merkle_res: &GetMerkleRes) -> Result<bool, InternalError> {
397+
let mut index = merkle_res.pos;
398+
let mut cur = txid.as_hash();
399+
for bytes in &merkle_res.merkle {
400+
let mut reversed = Vec::with_capacity(32);
401+
reversed.truncate(0);
402+
reversed.extend(bytes.iter().rev());
403+
let next_hash = Sha256d::from_slice(&reversed).map_err(|_| {
404+
log_error!(self.logger, "Failed due to the server sending us bogus transaction data. This should not happen. Please verify server integrity.");
405+
InternalError::Failed
406+
})?;
407+
408+
let (left, right) = if index % 2 == 0 {
409+
(cur, next_hash)
410+
} else {
411+
(next_hash, cur)
412+
};
413+
414+
let data = [&left[..], &right[..]].concat();
415+
cur = Sha256d::hash(&data);
416+
index /= 2;
417+
}
418+
419+
Ok(cur == merkle_root)
420+
}
387421
}
388422

389423
impl<L: Deref> Filter for ElectrumSyncClient<L>

0 commit comments

Comments
 (0)