mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 21:49:26 +00:00
Write a new impl of the merkle algorithm
This one tries to be understandable.
This commit is contained in:
@@ -5,12 +5,11 @@ use std_shims::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
hash,
|
hash,
|
||||||
|
merkle::merkle_root,
|
||||||
serialize::*,
|
serialize::*,
|
||||||
transaction::{Input, Transaction},
|
transaction::{Input, Transaction},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod merkle_root;
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct BlockHeader {
|
pub struct BlockHeader {
|
||||||
pub major_version: u64,
|
pub major_version: u64,
|
||||||
@@ -71,11 +70,11 @@ impl Block {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tx_merkle_root(&self) -> [u8; 32] {
|
fn tx_merkle_root(&self) -> [u8; 32] {
|
||||||
merkle_root::tree_hash(self.miner_tx.hash(), &self.txs)
|
merkle_root(self.miner_tx.hash(), &self.txs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize_hashable(&self) -> Vec<u8> {
|
fn serialize_hashable(&self) -> Vec<u8> {
|
||||||
let mut blob = self.header.serialize();
|
let mut blob = self.header.serialize();
|
||||||
blob.extend_from_slice(&self.tx_merkle_root());
|
blob.extend_from_slice(&self.tx_merkle_root());
|
||||||
write_varint(&(1 + u64::try_from(self.txs.len()).unwrap()), &mut blob).unwrap();
|
write_varint(&(1 + u64::try_from(self.txs.len()).unwrap()), &mut blob).unwrap();
|
||||||
@@ -87,7 +86,7 @@ impl Block {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> [u8; 32] {
|
pub fn hash(&self) -> [u8; 32] {
|
||||||
// TODO: Handle block 202612
|
// TODO: Handle block 202612
|
||||||
// https://monero.stackexchange.com/questions/421/what-happened-at-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
|
// If this block's header is fully-equivalent to 202612, return the malformed hash instead
|
||||||
|
|||||||
@@ -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::<Vec<_>>();
|
|
||||||
|
|
||||||
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])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,8 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwar
|
|||||||
|
|
||||||
pub use monero_generators::H;
|
pub use monero_generators::H;
|
||||||
|
|
||||||
|
mod merkle;
|
||||||
|
|
||||||
mod serialize;
|
mod serialize;
|
||||||
use serialize::{read_byte, read_u16};
|
use serialize::{read_byte, read_u16};
|
||||||
|
|
||||||
|
|||||||
45
coins/monero/src/merkle.rs
Normal file
45
coins/monero/src/merkle.rs
Normal file
@@ -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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -279,7 +279,12 @@ impl<R: RpcConnection> Rpc<R> {
|
|||||||
self.json_rpc_call("get_block", Some(json!({ "hash": hex::encode(hash) }))).await?;
|
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
|
// 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<Block, RpcError> {
|
pub async fn get_block_by_number(&self, number: usize) -> Result<Block, RpcError> {
|
||||||
|
|||||||
Reference in New Issue
Block a user