Skip to content

Commit

Permalink
Add a rust tool to create mixhash and calc proof by block hash
Browse files Browse the repository at this point in the history
  • Loading branch information
weiqiushi committed Aug 19, 2024
1 parent f063f75 commit 51d956e
Show file tree
Hide file tree
Showing 4 changed files with 365 additions and 10 deletions.
16 changes: 16 additions & 0 deletions proof-tool/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "rust"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "4.5", features = ["derive"] }
sha2 = "0.10"
sha3 = "0.10"
generic-array = "0.14"
serde_json = "1"
hex = "0.4"
rand = { version = "0.8", features = ["min_const_gen"] }
serde = { version = "1.0.208", features = ["derive"] }
172 changes: 172 additions & 0 deletions proof-tool/src/ercmerkle_tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
use clap::ValueEnum;
use generic_array::GenericArray;
use generic_array::typenum::{U16, U32};
use sha2::{Sha256, Digest};
use sha3::Keccak256;
use serde::{Deserialize, Serialize};

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)]
pub enum HashType {
Sha256,
Keccak256,
}

pub type Hash = GenericArray<u8, U32>;
type HalfHash = GenericArray<u8, U16>;

pub fn hex(hash: &[u8]) -> String {
format!("0x{}", hex::encode(hash))
}

fn decode_hash(hex: &str) -> Hash {
Hash::from_slice(hex::decode(&hex.as_bytes()[2..]).unwrap().as_slice()).clone()
}

fn decode_half_hash(hex: &str) -> HalfHash {
HalfHash::from_slice(hex::decode(&hex.as_bytes()[2..]).unwrap().as_slice()).clone()
}

pub fn calc_hash(hash_type: &HashType, data: &[u8]) -> Hash {
match hash_type {
HashType::Sha256 => {
let mut sha256 = Sha256::new();
sha256.update(data);
sha256.finalize()
}
HashType::Keccak256 => {
let mut sha3 = Keccak256::new();
sha3.update(data);
sha3.finalize()
}
}
}

#[derive(Serialize, Deserialize)]
pub struct MerkleTreeData {
hash_type: HashType,
tree: Vec<Vec<String>>,
root: String
}

pub struct MerkleTree {
leaf_hash: Vec<HalfHash>,
tree: Vec<Vec<HalfHash>>,
root: Hash,
hash_type: HashType,
}

impl MerkleTree {
pub fn new(hash_type: HashType) -> Self {
MerkleTree {
leaf_hash: vec![],
tree: vec![],
root: Default::default(),
hash_type,
}
}

pub fn load(data: MerkleTreeData) -> Self {
Self {
leaf_hash: vec![],
tree: data.tree.iter().map(|v|v.iter().map(|s|decode_half_hash(s)).collect()).collect(),
root: decode_hash(&data.root),
hash_type: data.hash_type,
}
}

pub fn save(&self) -> MerkleTreeData {
MerkleTreeData {
hash_type: self.hash_type.clone(),
tree: self.tree.iter().map(|v|v.iter().map(|h|hex(h.as_slice())).collect()).collect(),
root: hex(self.root.as_slice()),
}
}

pub fn add_leaf(&mut self, leaf: &[u8]) {
let hash32 = calc_hash(&self.hash_type, leaf);
self.leaf_hash.push(HalfHash::from_slice(&hash32.as_slice()[16..]).clone());
}

pub fn calc_tree(&mut self) {
let mut cur_layer = self.leaf_hash.clone();
let mut next_layer= Vec::new();
self.tree.push(cur_layer.clone());
let mut hash = Hash::default();
while cur_layer.len() > 1 {
for chunk in cur_layer.chunks(2) {
if chunk.len() == 1 {
next_layer.push(chunk[0].clone());
} else {
hash = calc_hash(&self.hash_type, &chunk.concat());
next_layer.push(HalfHash::from_slice(&hash.as_slice()[16..]).clone())
}
}
self.tree.push(next_layer.clone());
cur_layer = next_layer.clone();
next_layer.clear();
}

self.root = hash;
}

pub fn get_root(&self) -> &Hash {
&self.root
}

pub fn update_root(&mut self, new_root: Hash) {
self.root = new_root;
}

pub fn get_path(&self, index: u64) -> Vec<HalfHash> {
let mut cur_index = index as usize;
let mut ret: Vec<HalfHash> = Vec::new();
for layer in &self.tree {
if layer.len() < 2 {
break;
}

if cur_index % 2 == 1 {
ret.push(layer.get(cur_index-1).unwrap().clone());
} else {
ret.push(layer.get(cur_index+1).unwrap_or(&HalfHash::default()).clone())
}

cur_index = cur_index / 2;
}

return ret;
}

pub fn proof_by_path(&self, proofs: Vec<HalfHash>, leaf_index: u64, leafdata: &[u8]) -> Hash {
//println!("check leaf index {}", leaf_index);
let leaf_hash = calc_hash(&self.hash_type, leafdata);
let mut current32hash = leaf_hash;
let mut cur_index = leaf_index;

for proof in proofs {
let current16hash = HalfHash::from_slice(&current32hash.as_slice()[16..]).clone();
if proof.ne(&HalfHash::default()) {
if cur_index % 2 == 0 {
//println!("leaf index {}, proof i at right", cur_index);
//println!("calc hash {} + {}", hex(current16hash.as_slice()), hex(proof.as_slice()));
current32hash = calc_hash(&self.hash_type, &[current16hash, proof].concat()).into();
//println!("\t = {}", hex(current32hash.as_slice()));
} else {
//println!("leaf index {}, proof i at left", cur_index);
//println!("calc hash {} + {}", hex(proof.as_slice()), hex(current16hash.as_slice()));
current32hash = calc_hash(&self.hash_type, &[proof, current16hash].concat()).into();
//println!("\t = {}", hex(current32hash.as_slice()));
}
}

cur_index = cur_index / 2;
}

return current32hash;
}

pub fn verify(&self, proof: Vec<HalfHash>, leaf_index: u64, leafdata: &[u8]) -> bool {
let calc_root = self.proof_by_path(proof, leaf_index, leafdata);
calc_root.eq(&self.root)
}
}
146 changes: 146 additions & 0 deletions proof-tool/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
mod ercmerkle_tree;

use std::env::join_paths;
use std::io::{Read, Write};
use std::os::windows::prelude::FileExt;
use std::path::PathBuf;
use clap::{Parser, Subcommand};
use crate::ercmerkle_tree::{calc_hash, HashType, hex, MerkleTree};

fn compare_bytes(a: &[u8], b: &[u8]) -> i32 {
let n = a.len().min(b.len());

for i in 0..n {
if a[i] != b[i] {
return a[i] as i32 - b[i] as i32;
}
}

return (a.len() - b.len()) as i32;
}


#[derive(Parser)]
struct App {
#[command(subcommand)]
command: Subcommands
}

#[derive(Subcommand)]
enum Subcommands {
Create {
#[arg(value_name="FILE")]
file_path: PathBuf,
#[arg(value_enum)]
hash_type: HashType
},
Proof {
#[arg(value_name="FILE")]
file_path: PathBuf,
nonce_hash: String,

leaf_index: Option<u64>,
}
}

fn main() {
let cli = App::parse();
match cli.command {
Subcommands::Create { file_path, hash_type } => {
let mut file = std::fs::File::open(&file_path).unwrap();
let size = file.metadata().unwrap().len();
println!("calcuting merkle tree...");
let mut merkle_tree = MerkleTree::new(hash_type);
let mut read_buf = Vec::with_capacity(1024);
read_buf.resize(1024, 0);
loop {
read_buf.fill(0);
let readed = file.read(&mut read_buf).unwrap();
if readed == 0 { break; }
merkle_tree.add_leaf(&read_buf);
}
merkle_tree.calc_tree();

let mut root_hash = merkle_tree.get_root().clone();
root_hash.as_mut_slice().write(&size.to_be_bytes()).unwrap();
root_hash.as_mut_slice()[0] &= (1 << 6) - 1;

match hash_type {
HashType::Sha256 => {
// do nothing
}
HashType::Keccak256 => {
root_hash.as_mut_slice()[0] |= 1 << 7;
}
}

merkle_tree.update_root(root_hash);

let merkle_data = merkle_tree.save();
std::fs::write(file_path.with_extension("merkle"), &serde_json::to_vec(&merkle_data).unwrap()).unwrap();

println!("create file root hash 0x{}", hex::encode(&root_hash));
}
Subcommands::Proof { file_path, nonce_hash , leaf_index } => {
let merkle_data = serde_json::from_slice(&std::fs::read(file_path.with_extension("merkle")).unwrap()).unwrap();
let merkle_tree = MerkleTree::load(merkle_data);
let hash = hex::decode(&nonce_hash.as_str()[2..]).unwrap();

let file = std::fs::File::open(file_path).unwrap();
let length = file.metadata().unwrap().len();
let total_leaf_size = (length as f64 / 1024f64).ceil() as u64;

let mut min_root: Option<ercmerkle_tree::Hash> = None;
let mut min_index = None;

let mut read_buf = Vec::with_capacity(1024);
read_buf.resize(1024, 0);
if let Some(i) = leaf_index {
read_buf.fill(0);
file.seek_read(&mut read_buf, i * 1024).unwrap();
let path = merkle_tree.get_path(i);
let new_leaf: Vec<u8> = read_buf.iter().chain(hash.iter()).map(|v|*v).collect();
let new_root = merkle_tree.proof_by_path(path, i, &new_leaf);
//println!("index {} new root {}", i, hex(new_root.as_slice()));
if let Some(root) = min_root {
if compare_bytes(new_root.as_slice(), root.as_slice()) < 0 {
min_root.insert(new_root);
min_index.insert(i);
}
} else {
min_root.insert(new_root);
min_index.insert(i);
}
} else {
for i in 0..total_leaf_size {
read_buf.fill(0);
file.seek_read(&mut read_buf, i*1024).unwrap();
let path = merkle_tree.get_path(i);
let new_leaf: Vec<u8> = read_buf.iter().chain(hash.iter()).map(|v|*v).collect();
let new_root = merkle_tree.proof_by_path(path, i, &new_leaf);
//println!("index {} new root {}", i, hex(new_root.as_slice()));
if let Some(root) = min_root {
if compare_bytes(new_root.as_slice(), root.as_slice()) < 0 {
min_root.insert(new_root);
min_index.insert(i);
}
} else {
min_root.insert(new_root);
min_index.insert(i);
}
}
}


println!("found min index {}, min root {}", min_index.unwrap(), hex(min_root.unwrap().as_slice()));
let paths: Vec<String> = merkle_tree.get_path(min_index.unwrap()).iter().map(|v|hex(v.as_slice())).collect();
println!("min path [");
for path in paths {
println!("\t{},", path)
}
println!("]")
}
}


}
Loading

0 comments on commit 51d956e

Please sign in to comment.