-
Notifications
You must be signed in to change notification settings - Fork 115
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chain: implement Bitcoin Merkle root computation
- Loading branch information
1 parent
440e183
commit 738b5b0
Showing
2 changed files
with
118 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,132 @@ | ||
//! The Bitcoin-inherited Merkle tree of transactions. | ||
#![allow(clippy::unit_arg)] | ||
|
||
use std::{fmt, io}; | ||
use std::{fmt, io::Write}; | ||
|
||
#[cfg(any(any(test, feature = "proptest-impl"), feature = "proptest-impl"))] | ||
use proptest_derive::Arbitrary; | ||
|
||
use crate::serialization::{sha256d, SerializationError, ZcashDeserialize, ZcashSerialize}; | ||
use crate::transaction::Transaction; | ||
use crate::serialization::sha256d; | ||
use crate::transaction::{self, Transaction}; | ||
|
||
/// A binary hash tree of SHA256d (two rounds of SHA256) hashes for | ||
/// node values. | ||
#[derive(Default)] | ||
pub struct Tree<T> { | ||
_leaves: Vec<T>, | ||
} | ||
/// The root of the Bitcoin-inherited transaction Merkle tree, binding the | ||
/// block header to the transactions in the block. | ||
/// | ||
/// Note that because of a flaw in Bitcoin's design, the `merkle_root` does | ||
/// not always precisely bind the contents of the block (CVE-2012-2459). It | ||
/// is sometimes possible for an attacker to create multiple distinct sets of | ||
/// transactions with the same Merkle root, although only one set will be | ||
/// valid. | ||
/// | ||
/// # Malleability | ||
/// | ||
/// The Bitcoin source code contains the following note: | ||
/// | ||
/// > WARNING! If you're reading this because you're learning about crypto | ||
/// > and/or designing a new system that will use merkle trees, keep in mind | ||
/// > that the following merkle tree algorithm has a serious flaw related to | ||
/// > duplicate txids, resulting in a vulnerability (CVE-2012-2459). | ||
/// > The reason is that if the number of hashes in the list at a given time | ||
/// > is odd, the last one is duplicated before computing the next level (which | ||
/// > is unusual in Merkle trees). This results in certain sequences of | ||
/// > transactions leading to the same merkle root. For example, these two | ||
/// > trees: | ||
/// > | ||
/// > ```ascii | ||
/// > A A | ||
/// > / \ / \ | ||
/// > B C B C | ||
/// > / \ | / \ / \ | ||
/// > D E F D E F F | ||
/// > / \ / \ / \ / \ / \ / \ / \ | ||
/// > 1 2 3 4 5 6 1 2 3 4 5 6 5 6 | ||
/// > ``` | ||
/// > | ||
/// > for transaction lists [1,2,3,4,5,6] and [1,2,3,4,5,6,5,6] (where 5 and | ||
/// > 6 are repeated) result in the same root hash A (because the hash of both | ||
/// > of (F) and (F,F) is C). | ||
/// > | ||
/// > The vulnerability results from being able to send a block with such a | ||
/// > transaction list, with the same merkle root, and the same block hash as | ||
/// > the original without duplication, resulting in failed validation. If the | ||
/// > receiving node proceeds to mark that block as permanently invalid | ||
/// > however, it will fail to accept further unmodified (and thus potentially | ||
/// > valid) versions of the same block. We defend against this by detecting | ||
/// > the case where we would hash two identical hashes at the end of the list | ||
/// > together, and treating that identically to the block having an invalid | ||
/// > merkle root. Assuming no double-SHA256 collisions, this will detect all | ||
/// > known ways of changing the transactions without affecting the merkle | ||
/// > root. | ||
/// | ||
/// This vulnerability does not apply to Zebra, because it does not store invalid | ||
/// data on disk, and because it does not permanently fail blocks or use an | ||
/// aggressive anti-DoS mechanism. | ||
#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] | ||
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] | ||
pub struct Root(pub [u8; 32]); | ||
|
||
impl<Transaction> ZcashSerialize for Tree<Transaction> { | ||
fn zcash_serialize<W: io::Write>(&self, _writer: W) -> Result<(), io::Error> { | ||
unimplemented!(); | ||
impl fmt::Debug for Root { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
f.debug_tuple("Root").field(&hex::encode(&self.0)).finish() | ||
} | ||
} | ||
|
||
impl<Transaction> ZcashDeserialize for Tree<Transaction> { | ||
fn zcash_deserialize<R: io::Read>(_reader: R) -> Result<Self, SerializationError> { | ||
unimplemented!(); | ||
fn hash(h1: &[u8; 32], h2: &[u8; 32]) -> [u8; 32] { | ||
let mut w = sha256d::Writer::default(); | ||
w.write_all(h1).unwrap(); | ||
w.write_all(h2).unwrap(); | ||
w.finish() | ||
} | ||
|
||
impl<T> std::iter::FromIterator<T> for Root | ||
where | ||
T: std::convert::AsRef<Transaction>, | ||
{ | ||
fn from_iter<I>(transactions: I) -> Self | ||
where | ||
I: IntoIterator<Item = T>, | ||
{ | ||
transactions | ||
.into_iter() | ||
.map(|tx| tx.as_ref().hash()) | ||
.collect() | ||
} | ||
} | ||
|
||
/// A SHA-256d hash of the root node of a merkle tree of SHA256-d | ||
/// hashed transactions in a block. | ||
#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] | ||
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] | ||
pub struct Root(pub [u8; 32]); | ||
impl std::iter::FromIterator<transaction::Hash> for Root { | ||
fn from_iter<I>(hashes: I) -> Self | ||
where | ||
I: IntoIterator<Item = transaction::Hash>, | ||
{ | ||
let mut hashes = hashes.into_iter().map(|hash| hash.0).collect::<Vec<_>>(); | ||
|
||
impl From<Tree<Transaction>> for Root { | ||
fn from(merkle_tree: Tree<Transaction>) -> Self { | ||
let mut hash_writer = sha256d::Writer::default(); | ||
merkle_tree | ||
.zcash_serialize(&mut hash_writer) | ||
.expect("Sha256dWriter is infallible"); | ||
Self(hash_writer.finish()) | ||
while hashes.len() > 1 { | ||
hashes = hashes | ||
.chunks(2) | ||
.map(|chunk| match chunk { | ||
[h1, h2] => hash(h1, h2), | ||
[h1] => hash(h1, h1), | ||
_ => unreachable!("chunks(2)"), | ||
}) | ||
.collect(); | ||
} | ||
|
||
Self(hashes[0]) | ||
} | ||
} | ||
|
||
impl fmt::Debug for Root { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
f.debug_tuple("Root").field(&hex::encode(&self.0)).finish() | ||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
use crate::{block::Block, serialization::ZcashDeserialize}; | ||
|
||
#[test] | ||
fn block_test_vectors() { | ||
for block_bytes in zebra_test::vectors::BLOCKS.iter() { | ||
let block = Block::zcash_deserialize(&**block_bytes).unwrap(); | ||
let merkle_root = block.transactions.iter().collect::<Root>(); | ||
assert_eq!(merkle_root, block.header.merkle_root); | ||
} | ||
} | ||
} |