Now that you understand SHAMap's architecture and node types, let's explore how navigation actually works and how hashes propagate through the tree.
These operations are at the heart of what makes SHAMap efficient:
Navigation: Finding the correct path to any account
Hashing: Computing cryptographic commitments at each level
Verification: Ensuring data integrity through hash chains
Navigation Algorithm
Finding a leaf in a SHAMap is straightforward because the account's key determines the exact path:
Algorithm: findLeaf
std::shared_ptr<SHAMapLeafNode>findLeaf(uint256key){std::shared_ptr<SHAMapTreeNode> node = mRoot;for(int depth =0; depth <64;++depth){// Is this a leaf?if(auto leaf =std::dynamic_pointer_cast<SHAMapLeafNode>(node)){return leaf;}// Must be inner nodeauto inner =std::dynamic_pointer_cast<SHAMapInnerNode>(node);// Extract 4-bit chunk (nibble) at position 'depth'int branch =key.nthNibble(depth);// Get child node at that branch node =inner->getChild(branch);if(!node){// Child doesn't exist - key not in treereturnnullptr;}}returnnullptr;// Key not found}
uint256 SHAMapInnerNode::computeHash() {
Blob data;
// For each of 16 possible children
for (int i = 0; i < 16; ++i) {
if (hasChild(i)) {
// Get child's hash (whether child exists in memory or disk)
uint256 childHash = getChildHash(i);
data.append(childHash); // Append 32 bytes
}
}
// Hash all non-empty child hashes
return SHA512Half(data);
}
Scenario: Account0 balance changes
Step 1: Leaf changes
Old: Account0 leaf hash = 0xOLD1
New: Account0 leaf hash = 0xNEW1
Step 2: Parent recomputes
Parent stored hash references to all 16 children
One changed: [0xAAA][0xNEW1][0xBBB]...
Old parent hash: 0xOLD2
New parent hash: 0xNEW2
Step 3: Grandparent recomputes
Parent hash changed: 0xOLD2 → 0xNEW2
Grandparent's child references change: [...][0xNEW2][...]
Old grandparent hash: 0xOLD3
New grandparent hash: 0xNEW3
... propagates up to root
Change 1 account → 1 leaf hash changes
→ parent hash changes
→ grandparent hash changes
→ ... all ancestors up to root change
Root hash changes with certainty
bool sameLedgerState = (treeA.getRootHash() == treeB.getRootHash());
if (sameLedgerState) {
// Entire trees identical
// Verified cryptographically
} else {
// At least one difference
// Must investigate child hashes to find it
}
Peer A: "My ledger root is 0xABCD..."
Peer B: "My ledger root is 0xABCD..."
Comparison: 1 operation
Result: "Ledgers identical" (proven cryptographically)
No need to compare millions of accounts
Root hash differs: 0xABCD != 0xXYZW
Compare children:
Child 0: 0xAA == 0xAA (same)
Child 1: 0xBB == 0xBB (same)
Child 2: 0xCC != 0xXX (different!)
Child 3: 0xDD == 0xDD (same)
...
Recursively descend into Child 2 and its siblings
Eventually reach specific accounts that differ
Complete recount: O(N) comparisons (N = number of accounts)
Merkle tree method: O(log N) comparisons (log_16 of N)
Example: 1M accounts = 64 bits / 4 bits per level
Binary search depth = 20
Radix-16 Merkle depth = 5
Merkle method: 5 hash comparisons to find differences
vs. 1,000,000 account comparisons
// Prove Account A exists in tree with root hash R
MerkleProof proof; // Vector of serialized nodes
std::optional<std::vector<Blob>> getProofPath(uint256 key) {
std::vector<Blob> path;
std::shared_ptr<SHAMapTreeNode> node = mRoot;
for (int depth = 0; depth < 64; ++depth) {
// Serialize current node
Blob serialized = node->serialize();
path.push_back(serialized);
if (auto leaf = std::dynamic_pointer_cast<SHAMapLeafNode>(node)) {
// Reached target leaf
return path;
}
// Descend to next node following key
auto inner = std::dynamic_pointer_cast<SHAMapInnerNode>(node);
int branch = key.nthNibble(depth);
node = inner->getChild(branch);
if (!node) {
return std::nullopt; // Key not found
}
}
return std::nullopt;
}
bool verifyProofPath(
uint256 key,
uint256 expectedRootHash,
std::vector<Blob> proof)
{
// Start from leaf end of proof
auto leafNode = deserialize(proof.back());
// Verify leaf contains the key
if (leafNode->getKey() != key) {
return false;
}
uint256 computedHash = leafNode->computeHash();
// Move from leaf toward root
for (int i = proof.size() - 2; i >= 0; --i) {
auto innerNode = deserialize(proof[i]);
// Verify the branch we came from
int depth = i; // Depth in tree
int branch = key.nthNibble(depth);
// Child hash must match computed hash
if (innerNode->getChildHash(branch) != computedHash) {
return false;
}
// Compute this node's hash
computedHash = innerNode->computeHash();
}
// Verify final hash matches expected root
return (computedHash == expectedRootHash);
}
Tree with 1M accounts:
Depth: log_16(1M) ≈ 5
Proof includes:
1 leaf node: ~50-100 bytes
5 inner nodes: ~100 bytes each
Total: ~600 bytes
Compare to:
Sending all accounts: millions of bytes
Merkle proof: <1 KB
Verification requires hashing ~5 nodes
vs. hashing millions of accounts
void addLeaf(uint256 key, SHAMapItem item) {
auto leaf = std::make_shared<SHAMapLeafNode>(key, item);
leaf->setCowID(mCowID); // Mark as owned by this tree
std::shared_ptr<SHAMapTreeNode> node = mRoot;
// Navigate to position
for (int depth = 0; depth < 64; ++depth) {
if (auto inner = std::dynamic_pointer_cast<SHAMapInnerNode>(node)) {
int branch = key.nthNibble(depth);
auto child = inner->getChild(branch);
if (!child) {
// Empty slot - insert leaf here
inner = unshareNode(inner); // Copy-on-write
inner->setChild(branch, leaf);
updateHashes(inner); // Recompute hashes up to root
return;
} else if (auto childLeaf =
std::dynamic_pointer_cast<SHAMapLeafNode>(child)) {
// Slot occupied by another leaf
// Need to split into inner node
// ... (complex branch splitting logic)
}
node = child;
}
}
}
void updateHashes(std::shared_ptr<SHAMapInnerNode> node) {
uint256 oldHash = node->getHash();
uint256 newHash = node->computeHash();
if (oldHash == newHash) {
return; // Nothing changed
}
// Find parent
auto parent = node->getParent();
if (parent) {
// Update parent's reference to this node
int branch = node->getNodeID().getNibbleAtDepth(
node->getDepth() - 1);
parent->setChildHash(branch, newHash);
// Recursively update parent
updateHashes(parent);
} else {
// This is root - update root hash
mRootHash = newHash;
}
}