diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index 7b782259b..5fb25109c 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -33,7 +33,7 @@ use zebra_state as zs; use crate::{error::*, transaction as tx}; use crate::{script, BoxError}; -mod check; +pub mod check; mod subsidy; #[cfg(test)] mod tests; diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 46701b18d..6e7e1a1fd 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -183,10 +183,22 @@ pub fn merkle_root_validity( // Bitcoin's transaction Merkle trees are malleable, allowing blocks with // duplicate transactions to have the same Merkle root as blocks without - // duplicate transactions. Duplicate transactions should cause a block to be + // duplicate transactions. + // + // Collecting into a HashSet deduplicates, so this checks that there are no + // duplicate transaction hashes, preventing Merkle root malleability. + // + // ## Full Block Validation + // + // Duplicate transactions should cause a block to be // rejected, as duplicate transactions imply that the block contains a // double-spend. As a defense-in-depth, however, we also check that there - // are no duplicate transaction hashes, by collecting into a HashSet. + // are no duplicate transaction hashes. + // + // ## Checkpoint Validation + // + // To prevent malleability (CVE-2012-2459), we also need to check + // whether the transaction hashes are unique. use std::collections::HashSet; if transaction_hashes.len() != transaction_hashes.iter().collect::>().len() { return Err(BlockError::DuplicateTransaction); diff --git a/zebra-consensus/src/checkpoint.rs b/zebra-consensus/src/checkpoint.rs index 9bf07c05b..8c2c7190e 100644 --- a/zebra-consensus/src/checkpoint.rs +++ b/zebra-consensus/src/checkpoint.rs @@ -14,7 +14,7 @@ //! block for the configured network. use std::{ - collections::{BTreeMap, HashSet}, + collections::BTreeMap, ops::{Bound, Bound::*}, pin::Pin, sync::Arc, @@ -30,10 +30,11 @@ use tracing::instrument; use zebra_chain::{ block::{self, Block}, parameters::{Network, GENESIS_PREVIOUS_BLOCK_HASH}, + work::equihash, }; use zebra_state as zs; -use crate::BoxError; +use crate::{block::VerifyBlockError, error::BlockError, BoxError}; pub(crate) mod list; mod types; @@ -453,23 +454,8 @@ where .iter() .map(|tx| tx.hash()) .collect::>(); - let merkle_root = transaction_hashes.iter().cloned().collect(); - // Check that the Merkle root is valid. - if block.header.merkle_root != merkle_root { - return Err(VerifyCheckpointError::BadMerkleRoot { - expected: block.header.merkle_root, - actual: merkle_root, - }); - } - - // To prevent malleability (CVE-2012-2459), we also need to check - // whether the transaction hashes are unique. Collecting into a HashSet - // deduplicates, so this checks that there are no duplicate transaction - // hashes, preventing Merkle root malleability. - if transaction_hashes.len() != transaction_hashes.iter().collect::>().len() { - return Err(VerifyCheckpointError::DuplicateTransaction); - } + crate::block::check::merkle_root_validity(&block, &transaction_hashes)?; Ok(block_height) } @@ -831,6 +817,8 @@ pub enum VerifyCheckpointError { CommitFinalized(BoxError), #[error(transparent)] CheckpointList(BoxError), + #[error(transparent)] + VerifyBlock(BoxError), #[error("too many queued blocks at this height")] QueuedLimit, #[error("the block hash does not match the chained checkpoint hash, expected {expected:?} found {found:?}")] @@ -842,6 +830,18 @@ pub enum VerifyCheckpointError { ShuttingDown, } +impl From for VerifyCheckpointError { + fn from(err: VerifyBlockError) -> VerifyCheckpointError { + VerifyCheckpointError::VerifyBlock(err.into()) + } +} + +impl From for VerifyCheckpointError { + fn from(err: BlockError) -> VerifyCheckpointError { + VerifyCheckpointError::VerifyBlock(err.into()) + } +} + /// The CheckpointVerifier service implementation. /// /// After verification, the block futures resolve to their hashes.