diff --git a/coins/monero/src/block.rs b/coins/monero/src/block.rs index ddf030ab..9f5192c1 100644 --- a/coins/monero/src/block.rs +++ b/coins/monero/src/block.rs @@ -5,12 +5,11 @@ use std_shims::{ use crate::{ hash, + merkle::merkle_root, serialize::*, transaction::{Input, Transaction}, }; -mod merkle_root; - #[derive(Clone, PartialEq, Eq, Debug)] pub struct BlockHeader { pub major_version: u64, @@ -71,11 +70,11 @@ impl Block { Ok(()) } - pub fn tx_merkle_root(&self) -> [u8; 32] { - merkle_root::tree_hash(self.miner_tx.hash(), &self.txs) + fn tx_merkle_root(&self) -> [u8; 32] { + merkle_root(self.miner_tx.hash(), &self.txs) } - pub fn serialize_hashable(&self) -> Vec { + fn serialize_hashable(&self) -> Vec { let mut blob = self.header.serialize(); blob.extend_from_slice(&self.tx_merkle_root()); write_varint(&(1 + u64::try_from(self.txs.len()).unwrap()), &mut blob).unwrap(); @@ -87,7 +86,7 @@ impl Block { out } - pub fn id(&self) -> [u8; 32] { + pub fn hash(&self) -> [u8; 32] { // TODO: Handle block 202612 // https://monero.stackexchange.com/questions/421/what-happened-at-block-202612 // If this block's header is fully-equivalent to 202612, return the malformed hash instead diff --git a/coins/monero/src/block/merkle_root.rs b/coins/monero/src/block/merkle_root.rs deleted file mode 100644 index 00eb0354..00000000 --- a/coins/monero/src/block/merkle_root.rs +++ /dev/null @@ -1,68 +0,0 @@ -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 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]) - } - } -} diff --git a/coins/monero/src/lib.rs b/coins/monero/src/lib.rs index 83a0af0d..cd5b4d96 100644 --- a/coins/monero/src/lib.rs +++ b/coins/monero/src/lib.rs @@ -18,6 +18,8 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwar pub use monero_generators::H; +mod merkle; + mod serialize; use serialize::{read_byte, read_u16}; diff --git a/coins/monero/src/merkle.rs b/coins/monero/src/merkle.rs new file mode 100644 index 00000000..be56c1a8 --- /dev/null +++ b/coins/monero/src/merkle.rs @@ -0,0 +1,45 @@ +use crate::hash; + +pub fn merkle_root(root: [u8; 32], leafs: &[[u8; 32]]) -> [u8; 32] { + match leafs.len() { + 0 => root, + 1 => hash(&[root, leafs[0]].concat()), + _ => { + let mut hashes = Vec::with_capacity(1 + leafs.len()); + hashes.push(root); + hashes.extend(leafs); + + // Monero preprocess this so the length is a power of 2 + let mut high_pow_2 = 4; // 4 is the lowest value this can be + while high_pow_2 < hashes.len() { + high_pow_2 *= 2; + } + let low_pow_2 = high_pow_2 / 2; + + // Merge hashes until we're at the low_pow_2 + let mut i = high_pow_2 - hashes.len(); + while hashes.len() != low_pow_2 { + let l = hashes.remove(i); + let r = hashes.remove(i); + hashes.insert(i, hash(&[l.as_ref(), &r].concat())); + i += 1; + } + assert_eq!(hashes.len(), i); + assert_eq!(hashes.len(), low_pow_2); + + // Do a traditional pairing off + let mut new_hashes = Vec::with_capacity(hashes.len() / 2); + while hashes.len() > 2 { + let mut i = 0; + while i < hashes.len() { + new_hashes.push(hash(&[hashes[i], hashes[i + 1]].concat())); + i += 2; + } + + hashes = new_hashes; + new_hashes = Vec::with_capacity(hashes.len() / 2); + } + hashes[0] + } + } +} diff --git a/coins/monero/src/rpc/mod.rs b/coins/monero/src/rpc/mod.rs index 5874b0e8..19826db9 100644 --- a/coins/monero/src/rpc/mod.rs +++ b/coins/monero/src/rpc/mod.rs @@ -279,7 +279,12 @@ impl Rpc { self.json_rpc_call("get_block", Some(json!({ "hash": hex::encode(hash) }))).await?; // TODO: Verify the TXs included are actually committed to by the header - Block::read::<&[u8]>(&mut rpc_hex(&res.blob)?.as_ref()).map_err(|_| RpcError::InvalidNode) + let block = + Block::read::<&[u8]>(&mut rpc_hex(&res.blob)?.as_ref()).map_err(|_| RpcError::InvalidNode)?; + if block.hash() != hash { + Err(RpcError::InvalidNode)?; + } + Ok(block) } pub async fn get_block_by_number(&self, number: usize) -> Result {