diff --git a/coins/monero/src/block.rs b/coins/monero/src/block.rs index f24519eb..50679709 100644 --- a/coins/monero/src/block.rs +++ b/coins/monero/src/block.rs @@ -1,10 +1,13 @@ use std::io::{self, Read, Write}; use crate::{ + hash, serialize::*, transaction::{Input, Transaction}, }; +mod merkle_root; + #[derive(Clone, PartialEq, Eq, Debug)] pub struct BlockHeader { pub major_version: u64, @@ -65,6 +68,29 @@ impl Block { Ok(()) } + pub fn tx_merkle_root(&self) -> [u8; 32] { + merkle_root::tree_hash(self.miner_tx.hash(), &self.txs) + } + + pub fn serialize_hashable(&self) -> Vec { + let mut blob = self.header.serialize(); + + blob.extend_from_slice(&self.tx_merkle_root()); + + write_varint(&(1 + self.txs.len() as u64), &mut blob).unwrap(); + + let mut out = vec![]; + write_varint(&(blob.len() as u64), &mut out).unwrap(); + out.append(&mut blob); + + out + } + + pub fn id(&self) -> [u8; 32] { + // TODO: block 202612? + hash(&self.serialize_hashable()) + } + pub fn serialize(&self) -> Vec { let mut serialized = vec![]; self.write(&mut serialized).unwrap(); diff --git a/coins/monero/src/block/merkle_root.rs b/coins/monero/src/block/merkle_root.rs new file mode 100644 index 00000000..9ad40aef --- /dev/null +++ b/coins/monero/src/block/merkle_root.rs @@ -0,0 +1,68 @@ +use crate::hash; + +/// Round to power of two, for count>=3 and for count being not too large (<= 2^28) +/// (as reasonable for tree hash calculations) +/// +fn tree_hash_cnt(count: usize) -> usize { + // This algo has some bad history but all we are doing is 1 << floor(log2(count)) + // There are _many_ ways to do log2, for some reason the one selected was the most obscure one, + // and fixing it made it even more obscure. + // + // Iterative method implemented below aims for clarity over speed, if performance is needed + // then my advice is to use the BSR instruction on x86 + // + // All the paranoid asserts have been removed since it is trivial to mathematically prove that + // the return will always be a power of 2. + // Problem space has been defined as 3 <= count <= 2^28. Of course quarter of a billion + // transactions is not a sane upper limit for a block, so there will be tighter limits + // in other parts of the code + + assert!(count >= 3); // cases for 0,1,2 are handled elsewhere + assert!(count <= 0x10000000); // sanity limit to 2^28, MSB=1 will cause an inf loop + + let mut pow = 2_usize; + while pow < count { + pow <<= 1 + } + + pow >> 1 +} + +fn hash_concat(a: [u8; 32], b: [u8; 32]) -> [u8; 32] { + let mut v = [a, b].concat(); + hash(&v) +} + +/// Compute tree hash as defined by Cryptonote +pub fn tree_hash(root_hash: [u8; 32], extra_hashes: &[[u8; 32]]) -> [u8; 32] { + match extra_hashes.len() { + 0 => root_hash, + 1 => hash_concat(root_hash, extra_hashes[0]), + other => { + let count = other + 1; + + let mut cnt = tree_hash_cnt(count); + + let mut hashes = + std::iter::once(root_hash).chain(extra_hashes.iter().copied()).collect::>(); + + let mut i = 2 * cnt - count; + let mut j = 2 * cnt - count; + while j < cnt { + hashes[j] = hash_concat(hashes[i], hashes[i + 1]); + i += 2; + j += 1; + } + assert_eq!(i, count); + + while cnt > 2 { + cnt >>= 1; + for i in 0 .. cnt { + hashes[i] = hash_concat(hashes[2 * i], hashes[2 * i + 1]); + } + } + + hash_concat(hashes[0], hashes[1]) + } + } +}