From fed74bd5efffe3cc0b904e5447e468ddf5ffe6b6 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 15 Jul 2025 12:30:23 -0300 Subject: [PATCH] add sparse merkle tree impl --- risc0-selective-privacy-poc/Cargo.toml | 1 + .../sparse_merkle_tree/Cargo.toml | 7 + .../sparse_merkle_tree/src/default_hashes.rs | 142 ++++++++++++ .../sparse_merkle_tree/src/lib.rs | 203 ++++++++++++++++++ 4 files changed, 353 insertions(+) create mode 100644 risc0-selective-privacy-poc/sparse_merkle_tree/Cargo.toml create mode 100644 risc0-selective-privacy-poc/sparse_merkle_tree/src/default_hashes.rs create mode 100644 risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs diff --git a/risc0-selective-privacy-poc/Cargo.toml b/risc0-selective-privacy-poc/Cargo.toml index a32e53f..a236a11 100644 --- a/risc0-selective-privacy-poc/Cargo.toml +++ b/risc0-selective-privacy-poc/Cargo.toml @@ -11,6 +11,7 @@ outer-methods = { path = "outer_methods" } serde = "1.0" tracing-subscriber = { version = "0.3", features = ["env-filter"] } rand = "0.8" +sparse-merkle-tree = {path="./sparse_merkle_tree/"} [features] cuda = ["risc0-zkvm/cuda"] diff --git a/risc0-selective-privacy-poc/sparse_merkle_tree/Cargo.toml b/risc0-selective-privacy-poc/sparse_merkle_tree/Cargo.toml new file mode 100644 index 0000000..5e893be --- /dev/null +++ b/risc0-selective-privacy-poc/sparse_merkle_tree/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "sparse-merkle-tree" +version = "0.1.0" +edition = "2021" + +[dependencies] +sha2 = "0.10.9" diff --git a/risc0-selective-privacy-poc/sparse_merkle_tree/src/default_hashes.rs b/risc0-selective-privacy-poc/sparse_merkle_tree/src/default_hashes.rs new file mode 100644 index 0000000..a465f59 --- /dev/null +++ b/risc0-selective-privacy-poc/sparse_merkle_tree/src/default_hashes.rs @@ -0,0 +1,142 @@ +// Values computed as follows +// +// fn default_hashes() -> Vec<[u8; 32]> { +// let mut defaults = vec![ZERO_HASH]; +// for i in 1..TREE_DEPTH { +// let h = hash_node(&defaults[i - 1], &defaults[i - 1]); +// defaults.push(h); +// } +// defaults.into_iter().rev().collect() +// } +// +// +pub(crate) const DEFAULT_HASHES: [[u8; 32]; 32] = [ + [ + 157, 148, 193, 146, 141, 23, 128, 25, 196, 90, 21, 193, 179, 235, 209, 157, 146, 64, 171, + 100, 192, 44, 121, 46, 78, 53, 190, 198, 191, 82, 85, 16, + ], + [ + 244, 159, 70, 55, 214, 43, 252, 179, 167, 206, 218, 26, 170, 158, 22, 127, 12, 155, 130, + 224, 1, 98, 105, 28, 59, 57, 233, 189, 41, 50, 63, 115, + ], + [ + 204, 67, 6, 158, 244, 197, 238, 10, 26, 69, 178, 150, 78, 185, 192, 92, 5, 224, 36, 51, + 115, 21, 130, 142, 229, 223, 102, 195, 57, 198, 17, 27, + ], + [ + 49, 193, 198, 248, 157, 86, 23, 193, 111, 194, 178, 234, 70, 158, 128, 133, 165, 168, 43, + 60, 43, 15, 129, 209, 237, 51, 21, 134, 74, 209, 147, 88, + ], + [ + 219, 156, 83, 145, 70, 138, 195, 224, 161, 165, 121, 148, 215, 154, 104, 234, 219, 0, 91, + 255, 134, 28, 229, 108, 126, 225, 184, 6, 104, 156, 105, 140, + ], + [ + 196, 49, 181, 107, 231, 60, 253, 69, 245, 236, 7, 145, 32, 86, 58, 140, 47, 241, 0, 236, + 104, 61, 176, 23, 170, 95, 128, 66, 179, 134, 192, 209, + ], + [ + 155, 28, 109, 240, 82, 104, 71, 197, 186, 104, 178, 17, 138, 195, 57, 194, 194, 216, 96, + 246, 131, 233, 26, 84, 7, 124, 175, 159, 223, 60, 187, 161, + ], + [ + 183, 22, 254, 169, 215, 123, 104, 4, 156, 9, 23, 45, 110, 238, 115, 162, 108, 188, 142, + 141, 151, 185, 20, 199, 63, 150, 94, 146, 124, 30, 53, 145, + ], + [ + 122, 148, 43, 150, 236, 64, 36, 158, 18, 108, 140, 219, 34, 52, 143, 194, 69, 12, 185, 195, + 88, 206, 30, 249, 126, 255, 18, 221, 99, 72, 18, 91, + ], + [ + 51, 0, 157, 127, 41, 170, 190, 201, 194, 188, 222, 202, 115, 37, 229, 84, 111, 185, 104, + 69, 151, 66, 69, 34, 201, 161, 159, 139, 200, 11, 135, 67, + ], + [ + 186, 234, 98, 18, 205, 31, 46, 119, 118, 209, 66, 20, 180, 72, 129, 169, 242, 250, 48, 128, + 81, 175, 108, 228, 250, 226, 170, 123, 227, 21, 242, 221, + ], + [ + 27, 207, 232, 194, 77, 200, 137, 234, 233, 209, 180, 73, 180, 248, 193, 243, 50, 118, 191, + 199, 245, 30, 142, 242, 28, 234, 249, 134, 195, 154, 138, 162, + ], + [ + 199, 222, 136, 204, 114, 129, 19, 245, 177, 223, 179, 178, 201, 1, 202, 99, 26, 55, 146, + 90, 166, 193, 206, 36, 34, 171, 170, 245, 236, 35, 142, 161, + ], + [ + 121, 214, 101, 193, 197, 86, 227, 248, 59, 227, 3, 15, 20, 191, 124, 129, 209, 226, 93, + 128, 155, 137, 229, 66, 156, 221, 29, 179, 227, 120, 78, 59, + ], + [ + 118, 250, 222, 147, 174, 99, 105, 0, 241, 223, 160, 108, 11, 209, 143, 124, 59, 56, 11, + 164, 127, 2, 3, 18, 236, 149, 4, 176, 167, 196, 138, 245, + ], + [ + 204, 148, 248, 102, 164, 48, 65, 245, 219, 189, 191, 120, 157, 122, 63, 66, 228, 30, 143, + 166, 50, 157, 68, 187, 191, 110, 195, 83, 158, 2, 133, 52, + ], + [ + 179, 199, 88, 222, 194, 63, 148, 195, 88, 33, 190, 181, 102, 109, 100, 199, 212, 19, 198, + 123, 91, 167, 50, 157, 151, 242, 194, 103, 171, 143, 88, 198, + ], + [ + 209, 77, 39, 86, 1, 182, 123, 170, 109, 89, 182, 199, 89, 116, 244, 69, 49, 192, 149, 31, + 156, 226, 106, 73, 2, 112, 161, 78, 75, 153, 68, 189, + ], + [ + 158, 75, 216, 188, 35, 5, 86, 141, 82, 160, 215, 125, 16, 116, 45, 129, 224, 201, 105, 239, + 127, 37, 135, 136, 159, 255, 91, 222, 78, 64, 60, 246, + ], + [ + 121, 98, 24, 197, 183, 169, 52, 200, 156, 241, 142, 73, 241, 171, 113, 215, 133, 250, 13, + 105, 112, 253, 80, 197, 118, 105, 228, 77, 237, 254, 195, 66, + ], + [ + 188, 30, 229, 197, 205, 48, 162, 67, 206, 188, 130, 44, 72, 150, 168, 221, 170, 202, 59, + 110, 83, 205, 9, 10, 130, 11, 129, 79, 5, 218, 164, 97, + ], + [ + 182, 25, 214, 145, 150, 6, 219, 238, 38, 49, 166, 24, 255, 75, 56, 6, 31, 46, 163, 172, + 120, 213, 141, 74, 137, 21, 191, 169, 116, 50, 172, 71, + ], + [ + 52, 69, 229, 255, 230, 237, 127, 41, 223, 116, 249, 52, 228, 220, 231, 233, 38, 66, 188, + 188, 141, 176, 216, 204, 129, 209, 214, 199, 116, 203, 218, 0, + ], + [ + 34, 210, 93, 96, 203, 255, 15, 139, 0, 56, 109, 64, 224, 255, 168, 143, 235, 238, 144, 247, + 57, 237, 244, 210, 215, 160, 98, 250, 108, 101, 127, 130, + ], + [ + 57, 218, 36, 154, 181, 246, 243, 88, 152, 87, 31, 19, 81, 50, 15, 16, 66, 65, 78, 191, 194, + 47, 162, 102, 108, 254, 215, 38, 131, 209, 233, 88, + ], + [ + 155, 30, 22, 245, 84, 30, 111, 118, 197, 124, 53, 108, 138, 34, 183, 149, 93, 161, 54, 20, + 81, 52, 135, 241, 96, 199, 21, 156, 123, 208, 105, 244, + ], + [ + 173, 212, 84, 212, 76, 106, 120, 123, 235, 152, 249, 21, 121, 57, 137, 70, 8, 109, 9, 102, + 153, 66, 109, 61, 116, 176, 20, 123, 52, 240, 173, 143, + ], + [ + 148, 174, 121, 229, 202, 140, 51, 7, 46, 210, 185, 87, 169, 223, 189, 164, 252, 59, 133, + 226, 4, 99, 142, 243, 43, 14, 151, 8, 159, 60, 235, 60, + ], + [ + 175, 132, 242, 248, 185, 9, 188, 62, 34, 213, 240, 199, 176, 177, 75, 99, 187, 215, 70, + 226, 72, 67, 45, 66, 103, 218, 50, 31, 1, 52, 216, 168, + ], + [ + 248, 211, 204, 204, 180, 196, 230, 213, 226, 254, 251, 255, 140, 104, 170, 245, 141, 86, + 82, 142, 59, 109, 142, 191, 7, 180, 33, 12, 239, 230, 161, 241, + ], + [ + 178, 137, 222, 169, 44, 165, 171, 165, 242, 225, 137, 26, 26, 241, 27, 226, 121, 20, 196, + 136, 84, 219, 15, 229, 180, 187, 149, 193, 55, 224, 242, 214, + ], + [ + 110, 52, 11, 156, 255, 179, 122, 152, 156, 165, 68, 230, 187, 120, 10, 44, 120, 144, 29, + 63, 179, 55, 56, 118, 133, 17, 163, 6, 23, 175, 160, 29, + ], +]; diff --git a/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs b/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs new file mode 100644 index 0000000..483981f --- /dev/null +++ b/risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs @@ -0,0 +1,203 @@ +use sha2::{Digest, Sha256}; +use std::collections::{HashMap, HashSet}; + +mod default_hashes; +use default_hashes::DEFAULT_HASHES; + +const TREE_DEPTH: usize = 32; +const ZERO_HASH: [u8; 32] = [ + 110, 52, 11, 156, 255, 179, 122, 152, 156, 165, 68, 230, 187, 120, 10, 44, 120, 144, 29, 63, + 179, 55, 56, 118, 133, 17, 163, 6, 23, 175, 160, 29, +]; +const ONE_HASH: [u8; 32] = [ + 75, 245, 18, 47, 52, 69, 84, 197, 59, 222, 46, 187, 140, 210, 183, 227, 209, 96, 10, 214, 49, + 195, 133, 165, 215, 204, 226, 60, 119, 133, 69, 154, +]; + +/// Hash a leaf from arbitrary bytes +fn hash_leaf(data: &[u8]) -> [u8; 32] { + Sha256::digest(data).into() +} + +/// Hash two child nodes +fn hash_node(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(left); + hasher.update(right); + hasher.finalize().into() +} + +/// Sparse Merkle Tree with 2^32 leaves +pub struct SparseMerkleTree { + values: HashSet, // store leaf hashes +} + +impl SparseMerkleTree { + pub fn new(values: HashSet) -> Self { + Self { values } + } + + pub fn new_empty() -> Self { + Self { + values: HashSet::new(), + } + } + + fn node_map(&self) -> HashMap<(usize, u32), [u8; 32]> { + let mut nodes: HashMap<(usize, u32), [u8; 32]> = HashMap::new(); + + // Start from occupied leaves + for &leaf_index in &self.values { + nodes.insert((TREE_DEPTH, leaf_index), ONE_HASH); + } + + // Build tree bottom-up + for depth in (0..TREE_DEPTH).rev() { + let mut next_level = HashMap::new(); + let indices: Vec = nodes + .keys() + .filter(|(d, _)| *d == depth + 1) + .map(|(_, i)| i >> 1) // parent index + .collect(); + + for &parent_index in indices.iter() { + let left_index = parent_index << 1; + let right_index = left_index | 1; + + let left = nodes + .get(&(depth + 1, left_index)) + .unwrap_or(&DEFAULT_HASHES[depth]); + let right = nodes + .get(&(depth + 1, right_index)) + .unwrap_or(&DEFAULT_HASHES[depth]); + + if left != &DEFAULT_HASHES[depth] || right != &DEFAULT_HASHES[depth] { + let h = hash_node(left, right); + next_level.insert((depth, parent_index), h); + } + } + + nodes.extend(next_level); + } + nodes + } + + pub fn root(&self) -> [u8; 32] { + let nodes = self.node_map(); + nodes.get(&(0, 0)).cloned().unwrap_or(DEFAULT_HASHES[0]) + } + + pub fn get_authentication_path_for_index(&self, mut index: u32) -> [[u8; 32]; 32] { + let mut path = [[0u8; 32]; 32]; + + let nodes = self.node_map(); + let mut current_index = index; + + for depth in (0..32).rev() { + let sibling_index = current_index ^ 1; + + let sibling_hash = nodes + .get(&(depth + 1, sibling_index)) + .cloned() + .unwrap_or(DEFAULT_HASHES[depth]); + + path[31 - depth] = sibling_hash; + current_index >>= 1; + } + + path + } + + pub fn verify_index_set(mut index: u32, path: [[u8; 32]; 32], root: [u8; 32]) -> bool { + let mut hash = ONE_HASH; + for path_value in path.iter() { + if index & 1 == 0 { + hash = hash_node(&hash, path_value); + } else { + hash = hash_node(path_value, &hash); + } + index >>= 1; + } + root == hash + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_default_hashes() { + assert_eq!(DEFAULT_HASHES[TREE_DEPTH - 1], ZERO_HASH); + assert_eq!( + DEFAULT_HASHES[0], + [ + 157, 148, 193, 146, 141, 23, 128, 25, 196, 90, 21, 193, 179, 235, 209, 157, 146, + 64, 171, 100, 192, 44, 121, 46, 78, 53, 190, 198, 191, 82, 85, 16 + ] + ); + } + + #[test] + fn test_empty_tree() { + let empty_tree = SparseMerkleTree::new_empty(); + assert_eq!(empty_tree.root(), DEFAULT_HASHES[0]); + } + + #[test] + fn test_tree_1() { + let values: HashSet = vec![0, 1, 2, 3].into_iter().collect(); + let tree = SparseMerkleTree::new(values); + assert_eq!( + tree.root(), + [ + 109, 94, 224, 93, 195, 77, 137, 36, 108, 105, 177, 22, 212, 17, 160, 255, 224, 61, + 191, 17, 129, 10, 26, 76, 197, 42, 230, 160, 80, 44, 101, 184 + ] + ); + } + + #[test] + fn test_tree_2() { + let values: HashSet = vec![2147483648, 2147483649, 2147483650, 2147483651] + .into_iter() + .collect(); + let tree = SparseMerkleTree::new(values); + + assert_eq!( + tree.root(), + [ + 36, 178, 159, 245, 165, 76, 242, 85, 25, 218, 149, 135, 194, 127, 130, 201, 219, + 187, 167, 216, 1, 222, 234, 197, 152, 156, 243, 174, 68, 27, 114, 8 + ] + ); + } + + #[test] + fn test_tree_3() { + let values: HashSet = vec![2147483648, 0, 1, 2147483649].into_iter().collect(); + let tree = SparseMerkleTree::new(values); + + assert_eq!( + tree.root(), + [ + 148, 76, 190, 191, 248, 243, 89, 40, 197, 157, 206, 23, 58, 197, 86, 169, 225, 217, + 110, 166, 54, 10, 245, 175, 168, 4, 145, 220, 30, 210, 67, 113 + ] + ); + } + + #[test] + fn test_auth_path_1() { + let values: HashSet = vec![0, 1, 2, 3, 1337].into_iter().collect(); + let tree = SparseMerkleTree::new(values); + let root = tree.root(); + let path = tree.get_authentication_path_for_index(0); + assert!(SparseMerkleTree::verify_index_set(0, path, root)); + let path = tree.get_authentication_path_for_index(1); + assert!(SparseMerkleTree::verify_index_set(1, path, root)); + let path = tree.get_authentication_path_for_index(1337); + assert!(SparseMerkleTree::verify_index_set(1337, path, root)); + let path = tree.get_authentication_path_for_index(1338); + assert!(!SparseMerkleTree::verify_index_set(1338, path, root)); + } +}