State Tree Structure
Shielded State Tree Structure - Technical Specification
This document provides a detailed technical specification of the shielded state tree structure used in the Roru Protocol.
Tree Architecture
Binary Merkle Tree
Tree Type: Binary Merkle tree
Properties:
Depth: 32 levels
Capacity: 2^32 leaves (4,294,967,296 notes)
Hash function: SHA-256
Leaf nodes: Note commitments
Internal nodes: Hash of children
Tree Structure:
Level 0 (Root): [State Root]
Level 1: [Node 0] [Node 1]
Level 2: [Node 0] [Node 1] [Node 2] [Node 3]
...
Level 32 (Leaves): [Note 0] [Note 1] ... [Note 2^32-1]Node Structure
Leaf Node:
pub struct LeafNode {
pub commitment: Commitment, // Pedersen commitment to note
pub index: u32, // Position in tree
}Internal Node:
pub struct InternalNode {
pub left_hash: Hash, // Hash of left child
pub right_hash: Hash, // Hash of right child
pub hash: Hash, // Hash of this node
}Commitment Structure
Note Commitment
Commitment Format:
pub struct Commitment {
pub point: Point, // Point on BLS12-381 curve
}Commitment Generation:
fn commit_note(note: &Note) -> Commitment {
let value_point = note.value * G;
let randomness_point = note.randomness * H;
let recipient_point = hash_to_point(¬e.recipient) * J;
value_point + randomness_point + recipient_point
}Commitment Properties:
Hiding: Commitment doesn't reveal value
Binding: Cannot change value without changing commitment
Additive: Can add commitments homomorphically
Tree Operations
Insertion
Insert Process:
Find next available leaf position
Create leaf node with commitment
Update path to root
Recalculate hashes
Update state root
Insertion Algorithm:
fn insert_commitment(tree: &mut StateTree, commitment: Commitment) -> Result<u32> {
let index = tree.next_index();
let mut current = index;
let mut path = Vec::new();
// Create leaf
let leaf = LeafNode { commitment, index };
tree.set_leaf(index, leaf);
// Update path to root
let mut level = 32;
while level > 0 {
let sibling = current ^ 1;
let sibling_hash = tree.get_node_hash(sibling, level - 1);
path.push(sibling_hash);
let left = if current % 2 == 0 { current } else { sibling };
let right = if current % 2 == 0 { sibling } else { current };
let node_hash = hash(left_hash || right_hash);
tree.set_node(current / 2, level - 1, node_hash);
current = current / 2;
level -= 1;
}
Ok(index)
}Deletion (Nullification)
Nullification Process:
Mark note as spent
Generate nullifier
Add nullifier to nullifier set
Note remains in tree (for history)
Cannot be used again
Nullification Algorithm:
fn nullify_note(tree: &mut StateTree, note_index: u32, nullifier_key: &Scalar) -> Nullifier {
let note = tree.get_note(note_index);
let nullifier = generate_nullifier(¬e, nullifier_key);
tree.nullifier_set.insert(nullifier);
nullifier
}Merkle Proofs
Proof Structure
Merkle Proof Format:
pub struct MerkleProof {
pub leaf: Commitment,
pub leaf_index: u32,
pub path: Vec<ProofNode>,
pub root: Hash,
}
pub struct ProofNode {
pub hash: Hash,
pub is_right: bool, // true if sibling is right child
}Proof Generation
Generation Algorithm:
fn generate_proof(tree: &StateTree, index: u32) -> MerkleProof {
let leaf = tree.get_leaf(index);
let mut path = Vec::new();
let mut current = index;
let mut level = 32;
while level > 0 {
let sibling = current ^ 1;
let sibling_hash = tree.get_node_hash(sibling, level - 1);
let is_right = current % 2 == 0;
path.push(ProofNode {
hash: sibling_hash,
is_right,
});
current = current / 2;
level -= 1;
}
MerkleProof {
leaf: leaf.commitment,
leaf_index: index,
path,
root: tree.get_root(),
}
}Proof Verification
Verification Algorithm:
fn verify_proof(proof: &MerkleProof) -> bool {
let mut current_hash = hash_commitment(&proof.leaf);
let mut current_index = proof.leaf_index;
for node in &proof.path {
let (left, right) = if node.is_right {
(current_hash, node.hash)
} else {
(node.hash, current_hash)
};
current_hash = hash(left || right);
current_index = current_index / 2;
}
current_hash == proof.root
}State Root
Root Calculation
Root Properties:
Commits to entire tree
Single hash value
Updated with each transaction
Verifiable by anyone
Root Format:
pub struct StateRoot {
pub hash: Hash,
pub height: u32,
pub timestamp: u64,
pub epoch: u64,
}Root Updates
Update Process:
Transaction processed
Tree updated
New root calculated
Root signed
Root published
Update Algorithm:
fn update_root(tree: &mut StateTree) -> StateRoot {
let root_hash = tree.calculate_root();
let root = StateRoot {
hash: root_hash,
height: tree.height(),
timestamp: current_timestamp(),
epoch: current_epoch(),
};
tree.set_root(root.clone());
root
}Incremental Updates
Efficient Updates
Update Strategy:
Only update changed paths
Cache intermediate hashes
Batch updates when possible
Incremental root calculation
Update Algorithm:
fn incremental_update(tree: &mut StateTree, updates: Vec<Update>) -> StateRoot {
let mut changed_paths = HashSet::new();
for update in updates {
let path = get_path_to_root(update.index);
changed_paths.extend(path);
}
for path_index in changed_paths {
recalculate_node_hash(tree, path_index);
}
update_root(tree)
}Sparse Tree Representation
Efficient Storage
Sparse Tree:
Only store non-empty nodes
Use hash map for nodes
Efficient memory usage
Fast lookups
Storage Format:
pub struct SparseTree {
pub nodes: HashMap<u64, Hash>, // (index, hash)
pub leaves: HashMap<u32, Commitment>, // (index, commitment)
pub root: Hash,
}Sparse Tree Operations
Lookup:
fn get_node_hash(tree: &SparseTree, index: u64) -> Hash {
tree.nodes.get(&index)
.copied()
.unwrap_or_else(|| default_hash(index))
}Update:
fn update_node(tree: &mut SparseTree, index: u64, hash: Hash) {
tree.nodes.insert(index, hash);
}Batch Operations
Batch Updates
Batch Processing:
Process multiple updates together
Optimize tree traversal
Reduce hash computations
Faster overall processing
Batch Algorithm:
fn batch_update(tree: &mut StateTree, updates: Vec<Update>) -> StateRoot {
// Group updates by path
let mut path_groups = group_by_path(updates);
// Process each path group
for (path, group_updates) in path_groups {
process_path_group(tree, path, group_updates);
}
update_root(tree)
}Performance Characteristics
Complexity
Operations:
Insert: O(log n)
Proof generation: O(log n)
Proof verification: O(log n)
Root update: O(log n)
Lookup: O(log n)
Space:
Full tree: O(n)
Sparse tree: O(k log n) where k is number of notes
Proof size: O(log n)
Optimization
Optimization Techniques:
Caching intermediate hashes
Batch processing
Parallel computation
Efficient data structures
Security Properties
Security Guarantees
Tree Security:
Collision resistance (SHA-256)
Binding commitments
Tamper resistance
Integrity verification
Privacy:
Commitments hide values
Proofs don't reveal paths
No information leakage
Complete privacy
Conclusion
The state tree provides:
Efficiency: Logarithmic operations
Scalability: Supports billions of notes
Security: Cryptographic guarantees
Privacy: Hidden state
Performance: Optimized operations
Understanding the tree structure is essential for protocol development and optimization.
Last updated
