mirror of
https://github.com/merkletreejs/merkletreejs.git
synced 2026-01-09 13:07:59 -05:00
docs: add generated documentation for all Merkle tree implementations
Add detailed README files for MerkleTree, UnifiedBinaryTree, MerkleSumTree, MerkleRadixTree, MerkleMountainRange, and IncrementalMerkleTree classes with API docs, examples, and usage patterns.
This commit is contained in:
572
README-IncrementalMerkleTree.md
Normal file
572
README-IncrementalMerkleTree.md
Normal file
@@ -0,0 +1,572 @@
|
||||
# IncrementalMerkleTree Class Documentation
|
||||
|
||||
A fixed-depth Merkle tree implementation optimized for scenarios where you know the maximum number of leaves in advance. This tree supports efficient insertions, updates, deletions, and proof generation while maintaining a consistent structure, making it ideal for applications like voting systems, membership proofs, and zero-knowledge applications.
|
||||
|
||||
## Features
|
||||
|
||||
- **Fixed Depth**: Pre-defined tree depth for consistent structure
|
||||
- **Configurable Arity**: Support for binary and n-ary trees (2, 4, 8, etc. children per node)
|
||||
- **Zero Value Padding**: Automatic padding with zero values for incomplete levels
|
||||
- **Efficient Updates**: Update individual leaves without rebuilding the tree
|
||||
- **Proof Generation**: Generate and verify inclusion proofs for any leaf
|
||||
- **Deletions**: Remove leaves by setting them to zero values
|
||||
- **Visual Representation**: Built-in tree visualization for debugging
|
||||
- **Type Safety**: Full TypeScript support with comprehensive type definitions
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install merkletreejs
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { IncrementalMerkleTree } from 'merkletreejs'
|
||||
import { poseidon } from 'circomlib' // Common in zero-knowledge applications
|
||||
|
||||
// Create a binary tree with depth 4 (max 16 leaves)
|
||||
const tree = new IncrementalMerkleTree(poseidon, {
|
||||
depth: 4,
|
||||
arity: 2,
|
||||
zeroValue: 0
|
||||
})
|
||||
|
||||
// Insert leaves
|
||||
tree.insert(1)
|
||||
tree.insert(2)
|
||||
tree.insert(3)
|
||||
|
||||
// Get root
|
||||
const root = tree.getHexRoot()
|
||||
console.log('Tree root:', root)
|
||||
|
||||
// Generate proof for leaf at index 1
|
||||
const proof = tree.getProof(1)
|
||||
|
||||
// Verify proof
|
||||
const isValid = tree.verify(proof)
|
||||
console.log('Proof valid:', isValid)
|
||||
|
||||
// Update a leaf
|
||||
tree.update(1, 10)
|
||||
console.log('Updated root:', tree.getHexRoot())
|
||||
```
|
||||
|
||||
## Constructor
|
||||
|
||||
### `new IncrementalMerkleTree(hashFunction, options)`
|
||||
|
||||
Creates a new incremental Merkle tree instance.
|
||||
|
||||
**Parameters:**
|
||||
- `hashFunction` (Function): Hash function to use for node calculations
|
||||
- `options` (Options): Configuration options
|
||||
|
||||
**Options:**
|
||||
```typescript
|
||||
interface Options {
|
||||
depth?: number // Tree depth (required)
|
||||
arity?: number // Number of children per node (default: 2)
|
||||
zeroValue?: any // Value used for empty positions (default: 0)
|
||||
}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
import { poseidon } from 'circomlib'
|
||||
|
||||
// Binary tree (2 children per node)
|
||||
const binaryTree = new IncrementalMerkleTree(poseidon, {
|
||||
depth: 3,
|
||||
arity: 2,
|
||||
zeroValue: 0
|
||||
})
|
||||
|
||||
// Quaternary tree (4 children per node)
|
||||
const quaternaryTree = new IncrementalMerkleTree(poseidon, {
|
||||
depth: 3,
|
||||
arity: 4,
|
||||
zeroValue: BigInt(0)
|
||||
})
|
||||
```
|
||||
|
||||
## Core Methods
|
||||
|
||||
### Tree Operations
|
||||
|
||||
#### `insert(leaf)`
|
||||
Inserts a new leaf at the next available position.
|
||||
|
||||
**Parameters:**
|
||||
- `leaf` (any): Value to insert
|
||||
|
||||
```typescript
|
||||
tree.insert(42)
|
||||
tree.insert('hello')
|
||||
tree.insert(BigInt(123))
|
||||
```
|
||||
|
||||
#### `update(index, newLeaf)`
|
||||
Updates the leaf at a specific index.
|
||||
|
||||
**Parameters:**
|
||||
- `index` (number): Index of leaf to update
|
||||
- `newLeaf` (any): New value for the leaf
|
||||
|
||||
```typescript
|
||||
tree.update(0, 'updated value')
|
||||
tree.update(2, BigInt(999))
|
||||
```
|
||||
|
||||
#### `delete(index)`
|
||||
Deletes a leaf by setting it to the zero value.
|
||||
|
||||
**Parameters:**
|
||||
- `index` (number): Index of leaf to delete
|
||||
|
||||
```typescript
|
||||
tree.delete(1) // Sets leaf at index 1 to zeroValue
|
||||
```
|
||||
|
||||
### Tree Information
|
||||
|
||||
#### `getRoot()`
|
||||
Returns the current root hash.
|
||||
|
||||
```typescript
|
||||
const root = tree.getRoot()
|
||||
```
|
||||
|
||||
#### `getHexRoot()`
|
||||
Returns the current root hash as a hex string.
|
||||
|
||||
```typescript
|
||||
const hexRoot = tree.getHexRoot()
|
||||
```
|
||||
|
||||
#### `getDepth()`
|
||||
Returns the tree depth.
|
||||
|
||||
```typescript
|
||||
const depth = tree.getDepth()
|
||||
```
|
||||
|
||||
#### `getArity()`
|
||||
Returns the tree arity (children per node).
|
||||
|
||||
```typescript
|
||||
const arity = tree.getArity()
|
||||
```
|
||||
|
||||
#### `getMaxLeaves()`
|
||||
Returns the maximum number of leaves the tree can hold.
|
||||
|
||||
```typescript
|
||||
const maxLeaves = tree.getMaxLeaves() // depth^arity
|
||||
```
|
||||
|
||||
#### `getLeaves()`
|
||||
Returns all leaves, including zero-padded positions.
|
||||
|
||||
```typescript
|
||||
const allLeaves = tree.getLeaves()
|
||||
```
|
||||
|
||||
#### `indexOf(leaf)`
|
||||
Returns the index of a specific leaf value.
|
||||
|
||||
**Parameters:**
|
||||
- `leaf` (any): Leaf value to find
|
||||
|
||||
**Returns:** Index of the leaf, or -1 if not found
|
||||
|
||||
```typescript
|
||||
const index = tree.indexOf(42)
|
||||
```
|
||||
|
||||
### Layer Operations
|
||||
|
||||
#### `getLayers()`
|
||||
Returns all tree layers as a 2D array.
|
||||
|
||||
```typescript
|
||||
const layers = tree.getLayers()
|
||||
console.log('Number of layers:', layers.length)
|
||||
```
|
||||
|
||||
#### `getHexLayers()`
|
||||
Returns all tree layers as hex strings.
|
||||
|
||||
```typescript
|
||||
const hexLayers = tree.getHexLayers()
|
||||
```
|
||||
|
||||
#### `getLayersAsObject()`
|
||||
Returns tree layers as a nested object for visualization.
|
||||
|
||||
```typescript
|
||||
const layersObj = tree.getLayersAsObject()
|
||||
console.log(JSON.stringify(layersObj, null, 2))
|
||||
```
|
||||
|
||||
### Proof Operations
|
||||
|
||||
#### `getProof(index)`
|
||||
Generates an inclusion proof for a leaf at the given index.
|
||||
|
||||
**Parameters:**
|
||||
- `index` (number): Index of leaf to prove
|
||||
|
||||
**Returns:** Proof object with root, leaf, path indices, and siblings
|
||||
|
||||
```typescript
|
||||
const proof = tree.getProof(2)
|
||||
console.log('Proof:', {
|
||||
root: proof.root,
|
||||
leaf: proof.leaf,
|
||||
pathIndices: proof.pathIndices,
|
||||
siblings: proof.siblings
|
||||
})
|
||||
```
|
||||
|
||||
#### `verify(proof)`
|
||||
Verifies an inclusion proof.
|
||||
|
||||
**Parameters:**
|
||||
- `proof` (object): Proof object from getProof()
|
||||
|
||||
**Returns:** boolean - true if proof is valid
|
||||
|
||||
```typescript
|
||||
const isValid = tree.verify(proof)
|
||||
```
|
||||
|
||||
### Utility Methods
|
||||
|
||||
#### `toString()`
|
||||
Returns a visual representation of the tree.
|
||||
|
||||
```typescript
|
||||
console.log(tree.toString())
|
||||
```
|
||||
|
||||
#### `computeRoot()`
|
||||
Manually recomputes the root hash.
|
||||
|
||||
```typescript
|
||||
const computedRoot = tree.computeRoot()
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Voting System
|
||||
|
||||
```typescript
|
||||
import { IncrementalMerkleTree } from 'merkletreejs'
|
||||
import { poseidon } from 'circomlib'
|
||||
|
||||
// Create tree for up to 1024 voters (depth 10, binary)
|
||||
const voterTree = new IncrementalMerkleTree(poseidon, {
|
||||
depth: 10,
|
||||
arity: 2,
|
||||
zeroValue: 0
|
||||
})
|
||||
|
||||
// Register voters with their IDs
|
||||
const voterIds = [12345, 67890, 11111, 22222, 33333]
|
||||
voterIds.forEach(id => voterTree.insert(id))
|
||||
|
||||
console.log('Voter tree root:', voterTree.getHexRoot())
|
||||
console.log('Registered voters:', voterIds.length)
|
||||
|
||||
// Generate proof for voter 67890 (index 1)
|
||||
const voterProof = voterTree.getProof(1)
|
||||
const proofValid = voterTree.verify(voterProof)
|
||||
console.log('Voter 67890 proof valid:', proofValid)
|
||||
|
||||
// Remove a voter (set to 0)
|
||||
voterTree.delete(2) // Remove voter 11111
|
||||
console.log('After removing voter:', voterTree.getHexRoot())
|
||||
```
|
||||
|
||||
### Membership System
|
||||
|
||||
```typescript
|
||||
const memberTree = new IncrementalMerkleTree(poseidon, {
|
||||
depth: 5, // Max 32 members
|
||||
arity: 2,
|
||||
zeroValue: BigInt(0)
|
||||
})
|
||||
|
||||
// Add members
|
||||
const members = [
|
||||
BigInt(1001), // Alice
|
||||
BigInt(1002), // Bob
|
||||
BigInt(1003), // Charlie
|
||||
BigInt(1004), // Dave
|
||||
]
|
||||
|
||||
members.forEach(member => memberTree.insert(member))
|
||||
|
||||
// Prove Alice's membership
|
||||
const aliceProof = memberTree.getProof(0)
|
||||
console.log('Alice membership proof:')
|
||||
console.log(' Root:', aliceProof.root.toString())
|
||||
console.log(' Leaf:', aliceProof.leaf.toString())
|
||||
console.log(' Path indices:', aliceProof.pathIndices)
|
||||
console.log(' Siblings count:', aliceProof.siblings.length)
|
||||
|
||||
// Verify proof
|
||||
const aliceValid = memberTree.verify(aliceProof)
|
||||
console.log('Alice membership valid:', aliceValid)
|
||||
```
|
||||
|
||||
### Whitelist Management
|
||||
|
||||
```typescript
|
||||
// Whitelist for NFT minting
|
||||
const whitelist = new IncrementalMerkleTree(poseidon, {
|
||||
depth: 8, // Max 256 addresses
|
||||
arity: 2,
|
||||
zeroValue: 0
|
||||
})
|
||||
|
||||
// Add whitelisted addresses (as numbers for simplicity)
|
||||
const addresses = [
|
||||
0x1234567890abcdef,
|
||||
0xfedcba0987654321,
|
||||
0x1111222233334444,
|
||||
0x5555666677778888
|
||||
]
|
||||
|
||||
addresses.forEach(addr => whitelist.insert(addr))
|
||||
|
||||
// Generate proof for an address
|
||||
const addressProof = whitelist.getProof(1)
|
||||
const addressValid = whitelist.verify(addressProof)
|
||||
console.log('Address whitelisted:', addressValid)
|
||||
|
||||
// Update an address
|
||||
whitelist.update(1, 0x9999aaaa0000bbbb)
|
||||
console.log('Updated whitelist root:', whitelist.getHexRoot())
|
||||
```
|
||||
|
||||
### Quaternary Tree Example
|
||||
|
||||
```typescript
|
||||
// 4-ary tree (4 children per node)
|
||||
const quaternaryTree = new IncrementalMerkleTree(poseidon, {
|
||||
depth: 3, // Max 4^3 = 64 leaves
|
||||
arity: 4,
|
||||
zeroValue: 0
|
||||
})
|
||||
|
||||
// Insert data
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
quaternaryTree.insert(i * 10)
|
||||
}
|
||||
|
||||
console.log('Quaternary tree structure:')
|
||||
console.log('Depth:', quaternaryTree.getDepth())
|
||||
console.log('Arity:', quaternaryTree.getArity())
|
||||
console.log('Max leaves:', quaternaryTree.getMaxLeaves())
|
||||
console.log('Current leaves:', quaternaryTree.getLeaves().filter(leaf => leaf !== 0).length)
|
||||
|
||||
// Generate proof
|
||||
const proof = quaternaryTree.getProof(5)
|
||||
console.log('Proof for index 5 valid:', quaternaryTree.verify(proof))
|
||||
```
|
||||
|
||||
### Zero-Knowledge Application
|
||||
|
||||
```typescript
|
||||
import { poseidon } from 'circomlib'
|
||||
|
||||
// Tree for zero-knowledge proofs
|
||||
const zkTree = new IncrementalMerkleTree(poseidon, {
|
||||
depth: 20, // Large tree for many users
|
||||
arity: 2,
|
||||
zeroValue: BigInt(0)
|
||||
})
|
||||
|
||||
// Simulate user commitments
|
||||
const userCommitments = [
|
||||
BigInt('12345678901234567890'),
|
||||
BigInt('98765432109876543210'),
|
||||
BigInt('11111111111111111111'),
|
||||
BigInt('22222222222222222222')
|
||||
]
|
||||
|
||||
userCommitments.forEach(commitment => zkTree.insert(commitment))
|
||||
|
||||
// Generate proof for privacy-preserving verification
|
||||
const userIndex = 1
|
||||
const userProof = zkTree.getProof(userIndex)
|
||||
|
||||
console.log('ZK Proof components:')
|
||||
console.log('Root:', userProof.root.toString())
|
||||
console.log('Leaf:', userProof.leaf.toString())
|
||||
console.log('Path indices:', userProof.pathIndices)
|
||||
console.log('Siblings:', userProof.siblings.map(s => s.toString()))
|
||||
|
||||
// Verify proof
|
||||
const zkValid = zkTree.verify(userProof)
|
||||
console.log('ZK proof valid:', zkValid)
|
||||
```
|
||||
|
||||
### Batch Operations
|
||||
|
||||
```typescript
|
||||
const batchTree = new IncrementalMerkleTree(poseidon, {
|
||||
depth: 4,
|
||||
arity: 2,
|
||||
zeroValue: 0
|
||||
})
|
||||
|
||||
// Batch insert
|
||||
const batchData = [100, 200, 300, 400, 500]
|
||||
batchData.forEach(value => batchTree.insert(value))
|
||||
|
||||
// Batch update
|
||||
const updates = [
|
||||
{ index: 0, value: 150 },
|
||||
{ index: 2, value: 350 },
|
||||
{ index: 4, value: 550 }
|
||||
]
|
||||
|
||||
updates.forEach(({ index, value }) => {
|
||||
batchTree.update(index, value)
|
||||
})
|
||||
|
||||
// Generate proofs for all updated positions
|
||||
updates.forEach(({ index }) => {
|
||||
const proof = batchTree.getProof(index)
|
||||
const valid = batchTree.verify(proof)
|
||||
console.log(`Proof for index ${index}: ${valid}`)
|
||||
})
|
||||
```
|
||||
|
||||
### Tree Visualization
|
||||
|
||||
```typescript
|
||||
const visualTree = new IncrementalMerkleTree(poseidon, {
|
||||
depth: 3,
|
||||
arity: 2,
|
||||
zeroValue: 0
|
||||
})
|
||||
|
||||
// Add some data
|
||||
visualTree.insert(10)
|
||||
visualTree.insert(20)
|
||||
visualTree.insert(30)
|
||||
|
||||
// Print tree structure
|
||||
console.log('Tree visualization:')
|
||||
console.log(visualTree.toString())
|
||||
|
||||
// Print layers
|
||||
const layers = visualTree.getLayers()
|
||||
layers.forEach((layer, i) => {
|
||||
console.log(`Layer ${i}:`, layer.map(node => node.toString()))
|
||||
})
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Custom Hash Functions
|
||||
|
||||
```typescript
|
||||
import SHA256 from 'crypto-js/sha256'
|
||||
|
||||
// Custom hash function for SHA256
|
||||
const sha256Hash = (inputs) => {
|
||||
const combined = inputs.join('')
|
||||
return SHA256(combined).toString()
|
||||
}
|
||||
|
||||
const sha256Tree = new IncrementalMerkleTree(sha256Hash, {
|
||||
depth: 4,
|
||||
arity: 2,
|
||||
zeroValue: '0'
|
||||
})
|
||||
|
||||
sha256Tree.insert('hello')
|
||||
sha256Tree.insert('world')
|
||||
```
|
||||
|
||||
### Large Trees
|
||||
|
||||
```typescript
|
||||
// Very large tree for scalable applications
|
||||
const largeTree = new IncrementalMerkleTree(poseidon, {
|
||||
depth: 32, // Max 2^32 leaves (over 4 billion)
|
||||
arity: 2,
|
||||
zeroValue: BigInt(0)
|
||||
})
|
||||
|
||||
// Insert data efficiently
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
largeTree.insert(BigInt(i))
|
||||
}
|
||||
|
||||
console.log('Large tree root:', largeTree.getHexRoot())
|
||||
```
|
||||
|
||||
### Tree Comparison
|
||||
|
||||
```typescript
|
||||
// Compare trees before and after operations
|
||||
const tree1 = new IncrementalMerkleTree(poseidon, {
|
||||
depth: 3, arity: 2, zeroValue: 0
|
||||
})
|
||||
|
||||
const tree2 = new IncrementalMerkleTree(poseidon, {
|
||||
depth: 3, arity: 2, zeroValue: 0
|
||||
})
|
||||
|
||||
// Add same data to both trees
|
||||
[1, 2, 3].forEach(value => {
|
||||
tree1.insert(value)
|
||||
tree2.insert(value)
|
||||
})
|
||||
|
||||
console.log('Trees equal:', tree1.getHexRoot() === tree2.getHexRoot())
|
||||
|
||||
// Modify one tree
|
||||
tree1.update(1, 99)
|
||||
console.log('After update equal:', tree1.getHexRoot() === tree2.getHexRoot())
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- **Fixed Depth**: All operations are O(depth), providing predictable performance
|
||||
- **Arity Choice**: Higher arity reduces depth but increases hash computation per node
|
||||
- **Memory Usage**: Tree stores all nodes, memory usage is O(arity^depth)
|
||||
- **Zero Padding**: Unused positions are filled with zero values
|
||||
- **Batch Operations**: Multiple updates can be done before recomputing root
|
||||
|
||||
## Security Notes
|
||||
|
||||
- **Hash Function**: Use cryptographically secure hash functions for production
|
||||
- **Zero Values**: Choose zero values that cannot be valid leaf values
|
||||
- **Index Validation**: Ensure indexes are within valid ranges
|
||||
- **Proof Verification**: Always verify proofs in critical applications
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Voting Systems**: Voter registration and proof of eligibility
|
||||
- **Membership Proofs**: Whitelist management and access control
|
||||
- **Zero-Knowledge Proofs**: Privacy-preserving membership proofs
|
||||
- **Airdrops**: Prove eligibility for token distributions
|
||||
- **Identity Systems**: Decentralized identity and reputation systems
|
||||
- **Gaming**: Leaderboards and achievement systems
|
||||
- **Compliance**: Regulatory reporting with privacy
|
||||
|
||||
## Browser Support
|
||||
|
||||
Works in both Node.js and browser environments. Ensure you have appropriate hash function implementations for your target environment.
|
||||
|
||||
## TypeScript Support
|
||||
|
||||
Full TypeScript support with comprehensive type definitions for all methods and data structures. The library provides proper typing for all tree operations and proof structures.
|
||||
558
README-MerkleMountainRange.md
Normal file
558
README-MerkleMountainRange.md
Normal file
@@ -0,0 +1,558 @@
|
||||
# MerkleMountainRange Class Documentation
|
||||
|
||||
A specialized Merkle tree implementation designed for append-only data structures. Merkle Mountain Range (MMR) is optimized for scenarios where data is continuously added but never modified, making it perfect for blockchain applications, audit logs, and immutable data structures.
|
||||
|
||||
## Features
|
||||
|
||||
- **Append-Only**: Optimized for adding new data without modifying existing entries
|
||||
- **Efficient Proofs**: Generate inclusion proofs for any historical data
|
||||
- **Peak Management**: Maintains multiple tree peaks for efficient operations
|
||||
- **Blockchain Ready**: Perfect for blockchain and cryptocurrency applications
|
||||
- **Roll-up Support**: Supports efficient batch updates and roll-ups
|
||||
- **Immutable History**: Once data is added, it cannot be changed
|
||||
- **Custom Hash Functions**: Support for various hash functions and peak bagging strategies
|
||||
- **Type Safety**: Full TypeScript support with comprehensive type definitions
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install merkletreejs
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { MerkleMountainRange } from 'merkletreejs'
|
||||
import SHA256 from 'crypto-js/sha256'
|
||||
|
||||
// Create MMR
|
||||
const mmr = new MerkleMountainRange(SHA256)
|
||||
|
||||
// Append data
|
||||
mmr.append('transaction 1')
|
||||
mmr.append('transaction 2')
|
||||
mmr.append('transaction 3')
|
||||
mmr.append('transaction 4')
|
||||
|
||||
// Get root
|
||||
const root = mmr.getHexRoot()
|
||||
console.log('MMR Root:', root)
|
||||
|
||||
// Generate proof for transaction 2 (index 2)
|
||||
const proof = mmr.getMerkleProof(2)
|
||||
console.log('Proof generated for transaction 2')
|
||||
|
||||
// Verify proof
|
||||
const isValid = mmr.verify(
|
||||
proof.root,
|
||||
proof.width,
|
||||
2, // index
|
||||
'transaction 2',
|
||||
proof.peakBagging,
|
||||
proof.siblings
|
||||
)
|
||||
console.log('Proof valid:', isValid)
|
||||
```
|
||||
|
||||
## Constructor
|
||||
|
||||
### `new MerkleMountainRange(hashFn?, leaves?, hashLeafFn?, peakBaggingFn?, hashBranchFn?)`
|
||||
|
||||
Creates a new Merkle Mountain Range instance.
|
||||
|
||||
**Parameters:**
|
||||
- `hashFn` (Function): Hash function to use (defaults to SHA256)
|
||||
- `leaves` (any[]): Initial data to append (optional)
|
||||
- `hashLeafFn` (Function): Custom leaf hashing function (optional)
|
||||
- `peakBaggingFn` (Function): Custom peak bagging function (optional)
|
||||
- `hashBranchFn` (Function): Custom branch hashing function (optional)
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
import SHA256 from 'crypto-js/sha256'
|
||||
|
||||
// Basic MMR
|
||||
const mmr = new MerkleMountainRange(SHA256)
|
||||
|
||||
// MMR with initial data
|
||||
const mmrWithData = new MerkleMountainRange(SHA256, ['data1', 'data2', 'data3'])
|
||||
|
||||
// MMR with custom functions
|
||||
const customMMR = new MerkleMountainRange(
|
||||
SHA256,
|
||||
[],
|
||||
(index, dataHash) => customLeafHash(index, dataHash),
|
||||
(size, peaks) => customPeakBagging(size, peaks),
|
||||
(index, left, right) => customBranchHash(index, left, right)
|
||||
)
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
- `root` (Buffer): Current root hash of the MMR
|
||||
- `size` (number): Total number of nodes in the MMR
|
||||
- `width` (number): Number of leaves in the MMR
|
||||
- `hashes` (object): Storage for node hashes
|
||||
- `data` (object): Storage for original data
|
||||
|
||||
## Core Methods
|
||||
|
||||
### Data Operations
|
||||
|
||||
#### `append(data)`
|
||||
Appends new data to the MMR.
|
||||
|
||||
**Parameters:**
|
||||
- `data` (Buffer | string): Data to append
|
||||
|
||||
```typescript
|
||||
mmr.append('new transaction')
|
||||
mmr.append(Buffer.from('binary data'))
|
||||
mmr.append('another entry')
|
||||
```
|
||||
|
||||
#### `getRoot()`
|
||||
Returns the current root hash as a Buffer.
|
||||
|
||||
```typescript
|
||||
const root = mmr.getRoot()
|
||||
```
|
||||
|
||||
#### `getHexRoot()`
|
||||
Returns the current root hash as a hex string.
|
||||
|
||||
```typescript
|
||||
const hexRoot = mmr.getHexRoot()
|
||||
console.log('Root:', hexRoot)
|
||||
```
|
||||
|
||||
#### `getNode(index)`
|
||||
Returns the hash value of a node at the given index.
|
||||
|
||||
**Parameters:**
|
||||
- `index` (number): Node index (1-based)
|
||||
|
||||
```typescript
|
||||
const nodeHash = mmr.getNode(5)
|
||||
```
|
||||
|
||||
### Peak Operations
|
||||
|
||||
#### `getPeaks()`
|
||||
Returns all current peak hashes.
|
||||
|
||||
```typescript
|
||||
const peaks = mmr.getPeaks()
|
||||
console.log('Number of peaks:', peaks.length)
|
||||
```
|
||||
|
||||
#### `getPeakIndexes(width)`
|
||||
Returns the indexes of all peaks for a given width.
|
||||
|
||||
**Parameters:**
|
||||
- `width` (number): Width to calculate peaks for
|
||||
|
||||
```typescript
|
||||
const peakIndexes = mmr.getPeakIndexes(mmr.width)
|
||||
```
|
||||
|
||||
#### `numOfPeaks(width)`
|
||||
Returns the number of peaks for a given width.
|
||||
|
||||
**Parameters:**
|
||||
- `width` (number): Width to calculate for
|
||||
|
||||
```typescript
|
||||
const numPeaks = mmr.numOfPeaks(mmr.width)
|
||||
```
|
||||
|
||||
### Proof Operations
|
||||
|
||||
#### `getMerkleProof(index)`
|
||||
Generates a Merkle proof for a leaf at the given index.
|
||||
|
||||
**Parameters:**
|
||||
- `index` (number): Leaf index (1-based)
|
||||
|
||||
**Returns:** Object with proof components
|
||||
|
||||
```typescript
|
||||
const proof = mmr.getMerkleProof(3)
|
||||
console.log('Proof:', {
|
||||
root: proof.root,
|
||||
width: proof.width,
|
||||
peakBagging: proof.peakBagging,
|
||||
siblings: proof.siblings
|
||||
})
|
||||
```
|
||||
|
||||
#### `verify(root, width, index, value, peaks, siblings)`
|
||||
Verifies a proof for a specific value.
|
||||
|
||||
**Parameters:**
|
||||
- `root` (Buffer): Root hash to verify against
|
||||
- `width` (number): Width when proof was generated
|
||||
- `index` (number): Leaf index
|
||||
- `value` (Buffer | string): Original value
|
||||
- `peaks` (Buffer[]): Peak hashes
|
||||
- `siblings` (Buffer[]): Sibling hashes
|
||||
|
||||
**Returns:** boolean - true if proof is valid
|
||||
|
||||
```typescript
|
||||
const isValid = mmr.verify(root, width, index, value, peaks, siblings)
|
||||
```
|
||||
|
||||
### Utility Methods
|
||||
|
||||
#### `mountainHeight(size)`
|
||||
Returns the height of the highest peak.
|
||||
|
||||
**Parameters:**
|
||||
- `size` (number): Size to calculate height for
|
||||
|
||||
```typescript
|
||||
const height = mmr.mountainHeight(mmr.size)
|
||||
```
|
||||
|
||||
#### `heightAt(index)`
|
||||
Returns the height of a node at the given index.
|
||||
|
||||
**Parameters:**
|
||||
- `index` (number): Node index
|
||||
|
||||
```typescript
|
||||
const height = mmr.heightAt(5)
|
||||
```
|
||||
|
||||
#### `isLeaf(index)`
|
||||
Checks if a node at the given index is a leaf.
|
||||
|
||||
**Parameters:**
|
||||
- `index` (number): Node index
|
||||
|
||||
```typescript
|
||||
const isLeaf = mmr.isLeaf(3)
|
||||
```
|
||||
|
||||
#### `getChildren(index)`
|
||||
Returns the children of a parent node.
|
||||
|
||||
**Parameters:**
|
||||
- `index` (number): Parent node index
|
||||
|
||||
**Returns:** Array with [left, right] child indexes
|
||||
|
||||
```typescript
|
||||
const [left, right] = mmr.getChildren(parentIndex)
|
||||
```
|
||||
|
||||
### Roll-up Operations
|
||||
|
||||
#### `rollUp(root, width, peaks, itemHashes)`
|
||||
Performs a roll-up operation with new item hashes.
|
||||
|
||||
**Parameters:**
|
||||
- `root` (Buffer): Current root
|
||||
- `width` (number): Current width
|
||||
- `peaks` (Buffer[]): Current peaks
|
||||
- `itemHashes` (Buffer[]): New item hashes to roll up
|
||||
|
||||
**Returns:** New root hash after roll-up
|
||||
|
||||
```typescript
|
||||
const newRoot = mmr.rollUp(currentRoot, currentWidth, currentPeaks, newItemHashes)
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Blockchain Transaction Log
|
||||
|
||||
```typescript
|
||||
import { MerkleMountainRange } from 'merkletreejs'
|
||||
import SHA256 from 'crypto-js/sha256'
|
||||
|
||||
const txLog = new MerkleMountainRange(SHA256)
|
||||
|
||||
// Add transactions
|
||||
txLog.append('tx1: Alice -> Bob: 10 ETH')
|
||||
txLog.append('tx2: Bob -> Charlie: 5 ETH')
|
||||
txLog.append('tx3: Charlie -> Alice: 2 ETH')
|
||||
txLog.append('tx4: Alice -> Dave: 3 ETH')
|
||||
|
||||
console.log('Transaction log root:', txLog.getHexRoot())
|
||||
console.log('Total transactions:', txLog.width)
|
||||
|
||||
// Prove transaction 2 exists
|
||||
const proof = txLog.getMerkleProof(2)
|
||||
const isValid = txLog.verify(
|
||||
proof.root,
|
||||
proof.width,
|
||||
2,
|
||||
'tx2: Bob -> Charlie: 5 ETH',
|
||||
proof.peakBagging,
|
||||
proof.siblings
|
||||
)
|
||||
console.log('Transaction 2 proof valid:', isValid)
|
||||
```
|
||||
|
||||
### Audit Log System
|
||||
|
||||
```typescript
|
||||
const auditLog = new MerkleMountainRange(SHA256)
|
||||
|
||||
// Add audit events
|
||||
const events = [
|
||||
'User login: admin@example.com at 2023-01-01T10:00:00Z',
|
||||
'File accessed: /secure/document.pdf by admin@example.com',
|
||||
'Permission changed: user123 granted read access to /data/',
|
||||
'User logout: admin@example.com at 2023-01-01T11:30:00Z',
|
||||
'Failed login attempt: hacker@evil.com at 2023-01-01T12:00:00Z'
|
||||
]
|
||||
|
||||
events.forEach(event => auditLog.append(event))
|
||||
|
||||
console.log('Audit log root:', auditLog.getHexRoot())
|
||||
|
||||
// Generate proof for security event
|
||||
const securityEventProof = auditLog.getMerkleProof(5)
|
||||
console.log('Security event proof generated')
|
||||
|
||||
// Verify the failed login attempt
|
||||
const proofValid = auditLog.verify(
|
||||
securityEventProof.root,
|
||||
securityEventProof.width,
|
||||
5,
|
||||
'Failed login attempt: hacker@evil.com at 2023-01-01T12:00:00Z',
|
||||
securityEventProof.peakBagging,
|
||||
securityEventProof.siblings
|
||||
)
|
||||
console.log('Security event proof valid:', proofValid)
|
||||
```
|
||||
|
||||
### Document Version Control
|
||||
|
||||
```typescript
|
||||
const versionControl = new MerkleMountainRange(SHA256)
|
||||
|
||||
// Add document versions
|
||||
const versions = [
|
||||
'doc-v1.0: Initial document creation',
|
||||
'doc-v1.1: Added introduction section',
|
||||
'doc-v1.2: Fixed typos in chapter 2',
|
||||
'doc-v2.0: Major restructure and new content',
|
||||
'doc-v2.1: Added appendix and references'
|
||||
]
|
||||
|
||||
versions.forEach(version => versionControl.append(version))
|
||||
|
||||
// Prove a specific version exists
|
||||
const v2Proof = versionControl.getMerkleProof(4)
|
||||
const v2Valid = versionControl.verify(
|
||||
v2Proof.root,
|
||||
v2Proof.width,
|
||||
4,
|
||||
'doc-v2.0: Major restructure and new content',
|
||||
v2Proof.peakBagging,
|
||||
v2Proof.siblings
|
||||
)
|
||||
console.log('Document v2.0 proof valid:', v2Valid)
|
||||
```
|
||||
|
||||
### Supply Chain Tracking
|
||||
|
||||
```typescript
|
||||
const supplyChain = new MerkleMountainRange(SHA256)
|
||||
|
||||
// Track supply chain events
|
||||
const events = [
|
||||
'Raw materials sourced from Supplier A',
|
||||
'Materials processed at Factory B',
|
||||
'Quality inspection passed at 2023-01-15',
|
||||
'Product packaged and labeled',
|
||||
'Shipped to Distribution Center C',
|
||||
'Delivered to Retailer D',
|
||||
'Sold to end customer'
|
||||
]
|
||||
|
||||
events.forEach(event => supplyChain.append(event))
|
||||
|
||||
// Prove quality inspection occurred
|
||||
const qualityProof = supplyChain.getMerkleProof(3)
|
||||
const qualityValid = supplyChain.verify(
|
||||
qualityProof.root,
|
||||
qualityProof.width,
|
||||
3,
|
||||
'Quality inspection passed at 2023-01-15',
|
||||
qualityProof.peakBagging,
|
||||
qualityProof.siblings
|
||||
)
|
||||
console.log('Quality inspection proof valid:', qualityValid)
|
||||
```
|
||||
|
||||
### Timestamped Data Archive
|
||||
|
||||
```typescript
|
||||
const archive = new MerkleMountainRange(SHA256)
|
||||
|
||||
// Add timestamped data
|
||||
const data = [
|
||||
{ timestamp: '2023-01-01T00:00:00Z', data: 'sensor reading: 23.5°C' },
|
||||
{ timestamp: '2023-01-01T01:00:00Z', data: 'sensor reading: 24.1°C' },
|
||||
{ timestamp: '2023-01-01T02:00:00Z', data: 'sensor reading: 23.8°C' },
|
||||
{ timestamp: '2023-01-01T03:00:00Z', data: 'sensor reading: 22.9°C' }
|
||||
]
|
||||
|
||||
data.forEach(entry => archive.append(JSON.stringify(entry)))
|
||||
|
||||
// Prove a specific reading
|
||||
const reading2Proof = archive.getMerkleProof(2)
|
||||
const reading2Valid = archive.verify(
|
||||
reading2Proof.root,
|
||||
reading2Proof.width,
|
||||
2,
|
||||
JSON.stringify(data[1]),
|
||||
reading2Proof.peakBagging,
|
||||
reading2Proof.siblings
|
||||
)
|
||||
console.log('Sensor reading 2 proof valid:', reading2Valid)
|
||||
```
|
||||
|
||||
### Custom Hash Functions
|
||||
|
||||
```typescript
|
||||
import { keccak256 } from 'js-sha3'
|
||||
|
||||
// Custom hash function
|
||||
const customHash = (data) => {
|
||||
return Buffer.from(keccak256.arrayBuffer(data))
|
||||
}
|
||||
|
||||
// Custom leaf hash function
|
||||
const customLeafHash = (index, dataHash) => {
|
||||
const indexBuffer = Buffer.alloc(4)
|
||||
indexBuffer.writeUInt32BE(index, 0)
|
||||
return customHash(Buffer.concat([Buffer.from('LEAF'), indexBuffer, dataHash]))
|
||||
}
|
||||
|
||||
// Custom branch hash function
|
||||
const customBranchHash = (index, left, right) => {
|
||||
const indexBuffer = Buffer.alloc(4)
|
||||
indexBuffer.writeUInt32BE(index, 0)
|
||||
return customHash(Buffer.concat([Buffer.from('BRANCH'), indexBuffer, left, right]))
|
||||
}
|
||||
|
||||
const customMMR = new MerkleMountainRange(
|
||||
customHash,
|
||||
[],
|
||||
customLeafHash,
|
||||
undefined, // use default peak bagging
|
||||
customBranchHash
|
||||
)
|
||||
|
||||
customMMR.append('data with custom hashing')
|
||||
```
|
||||
|
||||
### Peak Analysis
|
||||
|
||||
```typescript
|
||||
const mmr = new MerkleMountainRange(SHA256)
|
||||
|
||||
// Add various amounts of data to see peak changes
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
mmr.append(`data-${i}`)
|
||||
|
||||
const peaks = mmr.getPeaks()
|
||||
const peakIndexes = mmr.getPeakIndexes(mmr.width)
|
||||
|
||||
console.log(`After ${i} items:`)
|
||||
console.log(` Width: ${mmr.width}, Size: ${mmr.size}`)
|
||||
console.log(` Peaks: ${peaks.length}, Indexes: [${peakIndexes.join(', ')}]`)
|
||||
console.log(` Root: ${mmr.getHexRoot()}`)
|
||||
}
|
||||
```
|
||||
|
||||
### Batch Roll-up Operations
|
||||
|
||||
```typescript
|
||||
const mmr = new MerkleMountainRange(SHA256)
|
||||
|
||||
// Initial data
|
||||
mmr.append('initial-1')
|
||||
mmr.append('initial-2')
|
||||
|
||||
const currentRoot = mmr.getRoot()
|
||||
const currentWidth = mmr.width
|
||||
const currentPeaks = mmr.getPeaks()
|
||||
|
||||
// Prepare new data for roll-up
|
||||
const newData = ['batch-1', 'batch-2', 'batch-3']
|
||||
const newItemHashes = newData.map(item => SHA256(item))
|
||||
|
||||
// Perform roll-up
|
||||
const newRoot = mmr.rollUp(currentRoot, currentWidth, currentPeaks, newItemHashes)
|
||||
console.log('New root after roll-up:', newRoot.toString('hex'))
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Tree Structure Analysis
|
||||
|
||||
```typescript
|
||||
// Analyze tree structure
|
||||
console.log('MMR Analysis:')
|
||||
console.log('Width (leaves):', mmr.width)
|
||||
console.log('Size (total nodes):', mmr.size)
|
||||
console.log('Number of peaks:', mmr.numOfPeaks(mmr.width))
|
||||
console.log('Mountain height:', mmr.mountainHeight(mmr.size))
|
||||
|
||||
// Check each node
|
||||
for (let i = 1; i <= mmr.size; i++) {
|
||||
const height = mmr.heightAt(i)
|
||||
const isLeaf = mmr.isLeaf(i)
|
||||
console.log(`Node ${i}: Height ${height}, ${isLeaf ? 'Leaf' : 'Branch'}`)
|
||||
}
|
||||
```
|
||||
|
||||
### Proof Size Analysis
|
||||
|
||||
```typescript
|
||||
// Analyze proof sizes for different positions
|
||||
for (let i = 1; i <= mmr.width; i++) {
|
||||
const proof = mmr.getMerkleProof(i)
|
||||
console.log(`Proof for leaf ${i}: ${proof.siblings.length} siblings`)
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- **Append-Only**: Optimized for adding new data, not modifying existing
|
||||
- **Proof Size**: Logarithmic proof size relative to total data size
|
||||
- **Peak Management**: Efficient peak tracking for large datasets
|
||||
- **Memory Usage**: Only stores hashes, not full data (data can be stored separately)
|
||||
- **Batch Operations**: Roll-up operations allow efficient batch updates
|
||||
|
||||
## Security Notes
|
||||
|
||||
- **Immutable History**: Once data is appended, it cannot be changed
|
||||
- **Hash Function Security**: Use cryptographically secure hash functions
|
||||
- **Index Validation**: Ensure indexes are within valid ranges
|
||||
- **Proof Verification**: Always verify proofs against trusted roots
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Blockchain**: Transaction logs and block history
|
||||
- **Audit Logs**: Immutable audit trails for security
|
||||
- **Version Control**: Document and code version tracking
|
||||
- **Supply Chain**: Product tracking and provenance
|
||||
- **Timestamping**: Proof of existence at specific times
|
||||
- **Certificate Transparency**: Log of issued certificates
|
||||
- **IoT Data**: Sensor data with integrity proofs
|
||||
|
||||
## Browser Support
|
||||
|
||||
Works in both Node.js and browser environments. Ensure you have appropriate Buffer support for your target environment.
|
||||
|
||||
## TypeScript Support
|
||||
|
||||
Full TypeScript support with comprehensive type definitions for all methods and data structures. The library provides proper typing for all MMR operations and proof structures.
|
||||
442
README-MerkleRadixTree.md
Normal file
442
README-MerkleRadixTree.md
Normal file
@@ -0,0 +1,442 @@
|
||||
# MerkleRadixTree Class Documentation
|
||||
|
||||
A space-efficient tree data structure that combines the benefits of a radix tree (compressed trie) with Merkle tree cryptographic properties. This implementation is optimized for storing key-value pairs with shared prefixes and provides cryptographic proofs for data integrity.
|
||||
|
||||
## Features
|
||||
|
||||
- **Space Efficient**: Compresses common prefixes to reduce tree size
|
||||
- **Cryptographic Proofs**: Generate and verify inclusion proofs with hash chains
|
||||
- **Key-Value Storage**: Efficient storage and retrieval of string keys with any value type
|
||||
- **Prefix Compression**: Automatic compression of nodes with common prefixes
|
||||
- **Hash Updates**: Automatic hash recalculation when tree structure changes
|
||||
- **Proof Generation**: Create proofs for key existence and value verification
|
||||
- **Type Safety**: Full TypeScript support with comprehensive type definitions
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install merkletreejs
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { MerkleRadixTree } from 'merkletreejs'
|
||||
import SHA256 from 'crypto-js/sha256'
|
||||
|
||||
// Create tree
|
||||
const tree = new MerkleRadixTree(SHA256)
|
||||
|
||||
// Insert key-value pairs
|
||||
tree.insert('apple', 'red fruit')
|
||||
tree.insert('application', 'software program')
|
||||
tree.insert('apply', 'to put on')
|
||||
tree.insert('banana', 'yellow fruit')
|
||||
|
||||
// Lookup values
|
||||
const value = tree.lookup('apple')
|
||||
console.log('Apple is:', value) // 'red fruit'
|
||||
|
||||
// Generate proof
|
||||
const proof = tree.generateProof('apple')
|
||||
if (proof) {
|
||||
console.log('Proof generated for apple')
|
||||
|
||||
// Verify proof
|
||||
const rootHash = tree.root.hash
|
||||
const isValid = tree.verifyProof(proof, rootHash)
|
||||
console.log('Proof valid:', isValid)
|
||||
}
|
||||
```
|
||||
|
||||
## Constructor
|
||||
|
||||
### `new MerkleRadixTree(hashFunction)`
|
||||
|
||||
Creates a new Merkle radix tree instance.
|
||||
|
||||
**Parameters:**
|
||||
- `hashFunction` (Function): Hash function to use for node hashing
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
import SHA256 from 'crypto-js/sha256'
|
||||
const tree = new MerkleRadixTree(SHA256)
|
||||
```
|
||||
|
||||
## Core Methods
|
||||
|
||||
### Tree Operations
|
||||
|
||||
#### `insert(key, value)`
|
||||
Inserts a key-value pair into the tree.
|
||||
|
||||
**Parameters:**
|
||||
- `key` (string): The key to insert
|
||||
- `value` (any): The value to associate with the key
|
||||
|
||||
```typescript
|
||||
tree.insert('hello', 'world')
|
||||
tree.insert('help', 'assistance')
|
||||
tree.insert('helicopter', 'aircraft')
|
||||
```
|
||||
|
||||
#### `lookup(key)`
|
||||
Retrieves the value associated with a key.
|
||||
|
||||
**Parameters:**
|
||||
- `key` (string): The key to look up
|
||||
|
||||
**Returns:** The value associated with the key, or `null` if not found
|
||||
|
||||
```typescript
|
||||
const value = tree.lookup('hello')
|
||||
if (value !== null) {
|
||||
console.log('Found:', value)
|
||||
} else {
|
||||
console.log('Key not found')
|
||||
}
|
||||
```
|
||||
|
||||
### Proof Operations
|
||||
|
||||
#### `generateProof(key)`
|
||||
Generates a cryptographic proof for a key's existence and value.
|
||||
|
||||
**Parameters:**
|
||||
- `key` (string): The key to generate a proof for
|
||||
|
||||
**Returns:** Array of proof items, or `null` if key not found
|
||||
|
||||
```typescript
|
||||
const proof = tree.generateProof('hello')
|
||||
if (proof) {
|
||||
console.log('Proof generated with', proof.length, 'steps')
|
||||
} else {
|
||||
console.log('Key not found, cannot generate proof')
|
||||
}
|
||||
```
|
||||
|
||||
#### `verifyProof(proof, rootHash)`
|
||||
Verifies a proof against a root hash.
|
||||
|
||||
**Parameters:**
|
||||
- `proof` (ProofItem[]): The proof to verify
|
||||
- `rootHash` (Buffer): The root hash to verify against
|
||||
|
||||
**Returns:** boolean - true if proof is valid
|
||||
|
||||
```typescript
|
||||
const rootHash = tree.root.hash
|
||||
const isValid = tree.verifyProof(proof, rootHash)
|
||||
console.log('Proof verification:', isValid)
|
||||
```
|
||||
|
||||
## Proof Structure
|
||||
|
||||
Each proof item contains:
|
||||
|
||||
```typescript
|
||||
interface ProofItem {
|
||||
key: string // Node key
|
||||
hash: Buffer // Node hash
|
||||
siblings: { // Sibling nodes
|
||||
key: string
|
||||
hash: Buffer
|
||||
}[]
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Key-Value Operations
|
||||
|
||||
```typescript
|
||||
import { MerkleRadixTree } from 'merkletreejs'
|
||||
import SHA256 from 'crypto-js/sha256'
|
||||
|
||||
const tree = new MerkleRadixTree(SHA256)
|
||||
|
||||
// Insert data
|
||||
tree.insert('cat', 'feline animal')
|
||||
tree.insert('car', 'vehicle')
|
||||
tree.insert('card', 'playing card')
|
||||
tree.insert('care', 'attention')
|
||||
tree.insert('dog', 'canine animal')
|
||||
|
||||
// Lookup data
|
||||
console.log('cat:', tree.lookup('cat')) // 'feline animal'
|
||||
console.log('car:', tree.lookup('car')) // 'vehicle'
|
||||
console.log('care:', tree.lookup('care')) // 'attention'
|
||||
console.log('cat:', tree.lookup('cat')) // 'feline animal'
|
||||
console.log('bird:', tree.lookup('bird')) // null (not found)
|
||||
```
|
||||
|
||||
### Dictionary/Glossary System
|
||||
|
||||
```typescript
|
||||
const dictionary = new MerkleRadixTree(SHA256)
|
||||
|
||||
// Add dictionary entries
|
||||
dictionary.insert('algorithm', 'A step-by-step procedure for solving a problem')
|
||||
dictionary.insert('blockchain', 'A distributed ledger technology')
|
||||
dictionary.insert('cryptography', 'The practice of secure communication')
|
||||
dictionary.insert('database', 'An organized collection of data')
|
||||
dictionary.insert('encryption', 'The process of encoding information')
|
||||
|
||||
// Look up definitions
|
||||
const definition = dictionary.lookup('blockchain')
|
||||
console.log('Blockchain:', definition)
|
||||
|
||||
// Generate proof for a definition
|
||||
const proof = dictionary.generateProof('cryptography')
|
||||
if (proof) {
|
||||
const rootHash = dictionary.root.hash
|
||||
const isValid = dictionary.verifyProof(proof, rootHash)
|
||||
console.log('Cryptography definition proof valid:', isValid)
|
||||
}
|
||||
```
|
||||
|
||||
### File System Simulation
|
||||
|
||||
```typescript
|
||||
const fileSystem = new MerkleRadixTree(SHA256)
|
||||
|
||||
// Add files and directories
|
||||
fileSystem.insert('/home/user/documents/readme.txt', 'File content: README')
|
||||
fileSystem.insert('/home/user/documents/notes.md', 'File content: Notes')
|
||||
fileSystem.insert('/home/user/pictures/photo1.jpg', 'Image data')
|
||||
fileSystem.insert('/home/user/pictures/photo2.png', 'Image data')
|
||||
fileSystem.insert('/var/log/system.log', 'System log entries')
|
||||
fileSystem.insert('/var/log/error.log', 'Error log entries')
|
||||
|
||||
// Look up files
|
||||
const readmeContent = fileSystem.lookup('/home/user/documents/readme.txt')
|
||||
console.log('README content:', readmeContent)
|
||||
|
||||
// Generate proof for file existence
|
||||
const fileProof = fileSystem.generateProof('/home/user/pictures/photo1.jpg')
|
||||
if (fileProof) {
|
||||
console.log('File existence proof generated')
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Management
|
||||
|
||||
```typescript
|
||||
const config = new MerkleRadixTree(SHA256)
|
||||
|
||||
// Store configuration values
|
||||
config.insert('database.host', 'localhost')
|
||||
config.insert('database.port', '5432')
|
||||
config.insert('database.name', 'myapp')
|
||||
config.insert('database.ssl', 'true')
|
||||
config.insert('server.port', '3000')
|
||||
config.insert('server.timeout', '30000')
|
||||
config.insert('logging.level', 'info')
|
||||
config.insert('logging.file', '/var/log/app.log')
|
||||
|
||||
// Retrieve configuration
|
||||
const dbHost = config.lookup('database.host')
|
||||
const serverPort = config.lookup('server.port')
|
||||
console.log(`Database: ${dbHost}, Server port: ${serverPort}`)
|
||||
|
||||
// Generate proof for configuration integrity
|
||||
const proof = config.generateProof('database.host')
|
||||
const rootHash = config.root.hash
|
||||
console.log('Config integrity proof valid:', config.verifyProof(proof, rootHash))
|
||||
```
|
||||
|
||||
### User Session Management
|
||||
|
||||
```typescript
|
||||
const sessions = new MerkleRadixTree(SHA256)
|
||||
|
||||
// Store session data
|
||||
sessions.insert('session_abc123', JSON.stringify({
|
||||
userId: 'user1',
|
||||
loginTime: '2023-01-01T10:00:00Z',
|
||||
permissions: ['read', 'write']
|
||||
}))
|
||||
|
||||
sessions.insert('session_def456', JSON.stringify({
|
||||
userId: 'user2',
|
||||
loginTime: '2023-01-01T11:00:00Z',
|
||||
permissions: ['read']
|
||||
}))
|
||||
|
||||
// Retrieve session
|
||||
const sessionData = sessions.lookup('session_abc123')
|
||||
if (sessionData) {
|
||||
const session = JSON.parse(sessionData)
|
||||
console.log('User ID:', session.userId)
|
||||
console.log('Permissions:', session.permissions)
|
||||
}
|
||||
|
||||
// Verify session integrity
|
||||
const sessionProof = sessions.generateProof('session_abc123')
|
||||
if (sessionProof) {
|
||||
const isValid = sessions.verifyProof(sessionProof, sessions.root.hash)
|
||||
console.log('Session integrity verified:', isValid)
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-language Text Storage
|
||||
|
||||
```typescript
|
||||
const translations = new MerkleRadixTree(SHA256)
|
||||
|
||||
// Store translations
|
||||
translations.insert('en.welcome', 'Welcome')
|
||||
translations.insert('en.goodbye', 'Goodbye')
|
||||
translations.insert('en.hello', 'Hello')
|
||||
translations.insert('es.welcome', 'Bienvenido')
|
||||
translations.insert('es.goodbye', 'Adiós')
|
||||
translations.insert('es.hello', 'Hola')
|
||||
translations.insert('fr.welcome', 'Bienvenue')
|
||||
translations.insert('fr.goodbye', 'Au revoir')
|
||||
translations.insert('fr.hello', 'Bonjour')
|
||||
|
||||
// Get translations
|
||||
console.log('English welcome:', translations.lookup('en.welcome'))
|
||||
console.log('Spanish hello:', translations.lookup('es.hello'))
|
||||
console.log('French goodbye:', translations.lookup('fr.goodbye'))
|
||||
|
||||
// Prove translation exists
|
||||
const proof = translations.generateProof('es.welcome')
|
||||
const rootHash = translations.root.hash
|
||||
console.log('Translation proof valid:', translations.verifyProof(proof, rootHash))
|
||||
```
|
||||
|
||||
### Working with Different Data Types
|
||||
|
||||
```typescript
|
||||
const mixedData = new MerkleRadixTree(SHA256)
|
||||
|
||||
// Store different types of data
|
||||
mixedData.insert('user:1:name', 'Alice')
|
||||
mixedData.insert('user:1:age', 25)
|
||||
mixedData.insert('user:1:active', true)
|
||||
mixedData.insert('user:2:name', 'Bob')
|
||||
mixedData.insert('user:2:age', 30)
|
||||
mixedData.insert('user:2:active', false)
|
||||
|
||||
// Store complex objects as JSON
|
||||
mixedData.insert('config:app', JSON.stringify({
|
||||
version: '1.0.0',
|
||||
features: ['auth', 'api', 'ui'],
|
||||
settings: { theme: 'dark', lang: 'en' }
|
||||
}))
|
||||
|
||||
// Retrieve and parse complex data
|
||||
const appConfig = mixedData.lookup('config:app')
|
||||
if (appConfig) {
|
||||
const config = JSON.parse(appConfig)
|
||||
console.log('App version:', config.version)
|
||||
console.log('Features:', config.features)
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Custom Hash Functions
|
||||
|
||||
```typescript
|
||||
import { keccak256 } from 'js-sha3'
|
||||
|
||||
// Custom hash function
|
||||
const customHashFn = (data) => {
|
||||
return Buffer.from(keccak256.arrayBuffer(data))
|
||||
}
|
||||
|
||||
const tree = new MerkleRadixTree(customHashFn)
|
||||
tree.insert('test', 'value')
|
||||
```
|
||||
|
||||
### Proof Analysis
|
||||
|
||||
```typescript
|
||||
const proof = tree.generateProof('somekey')
|
||||
if (proof) {
|
||||
console.log('Proof analysis:')
|
||||
proof.forEach((item, index) => {
|
||||
console.log(`Step ${index}:`)
|
||||
console.log(` Key: ${item.key}`)
|
||||
console.log(` Hash: ${item.hash.toString('hex')}`)
|
||||
console.log(` Siblings: ${item.siblings.length}`)
|
||||
|
||||
item.siblings.forEach((sibling, sibIndex) => {
|
||||
console.log(` Sibling ${sibIndex}: ${sibling.key}`)
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Tree Inspection
|
||||
|
||||
```typescript
|
||||
// Access tree structure
|
||||
console.log('Root hash:', tree.root.hash.toString('hex'))
|
||||
console.log('Root key:', tree.root.key)
|
||||
console.log('Root value:', tree.root.value)
|
||||
console.log('Root children count:', tree.root.children.size)
|
||||
|
||||
// Iterate through immediate children
|
||||
tree.root.children.forEach((child, key) => {
|
||||
console.log(`Child key: ${key}, Child hash: ${child.hash.toString('hex')}`)
|
||||
})
|
||||
```
|
||||
|
||||
### Batch Operations
|
||||
|
||||
```typescript
|
||||
// Batch insert for better performance
|
||||
const batchData = [
|
||||
['key1', 'value1'],
|
||||
['key2', 'value2'],
|
||||
['key3', 'value3'],
|
||||
['key4', 'value4']
|
||||
]
|
||||
|
||||
batchData.forEach(([key, value]) => {
|
||||
tree.insert(key, value)
|
||||
})
|
||||
|
||||
// Batch lookup
|
||||
const keys = ['key1', 'key2', 'key3', 'key4']
|
||||
const values = keys.map(key => tree.lookup(key))
|
||||
console.log('Batch lookup results:', values)
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- **Prefix Compression**: Keys with common prefixes are stored efficiently
|
||||
- **Hash Updates**: Hash recalculation happens automatically on changes
|
||||
- **Memory Usage**: Space-efficient storage for keys with shared prefixes
|
||||
- **Lookup Time**: O(k) where k is the key length
|
||||
- **Proof Size**: Logarithmic with respect to the number of unique prefixes
|
||||
|
||||
## Security Notes
|
||||
|
||||
- **Hash Function**: Use cryptographically secure hash functions
|
||||
- **Proof Verification**: Always verify proofs against trusted root hashes
|
||||
- **Key Uniqueness**: Ensure keys are unique to avoid overwrites
|
||||
- **Value Integrity**: Hash includes both key and value for tamper detection
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Configuration Management**: Store and verify application settings
|
||||
- **Dictionary/Glossary**: Efficient storage of definitions with proofs
|
||||
- **File Systems**: Simulate file system with cryptographic integrity
|
||||
- **Session Management**: Store session data with tamper detection
|
||||
- **Internationalization**: Multi-language text storage and retrieval
|
||||
- **Database Indexing**: Create verifiable indexes for database records
|
||||
- **DNS Records**: Store domain name mappings with integrity proofs
|
||||
|
||||
## Browser Support
|
||||
|
||||
Works in both Node.js and browser environments. Ensure you have appropriate Buffer support for your target environment.
|
||||
|
||||
## TypeScript Support
|
||||
|
||||
Full TypeScript support with comprehensive type definitions for all methods and data structures. The library provides proper typing for keys, values, and proof structures.
|
||||
433
README-MerkleSumTree.md
Normal file
433
README-MerkleSumTree.md
Normal file
@@ -0,0 +1,433 @@
|
||||
# MerkleSumTree Class Documentation
|
||||
|
||||
A specialized Merkle tree implementation that maintains sums of ranges, enabling efficient range queries and proofs. This implementation is based on the Merkle Sum Tree concept and is particularly useful for applications requiring verifiable range proofs and sum calculations.
|
||||
|
||||
## Features
|
||||
|
||||
- **Range-Based Proofs**: Generate proofs for data within specific ranges
|
||||
- **Sum Verification**: Verify that sums are correctly calculated across ranges
|
||||
- **Consecutive Ranges**: Ensures leaf ranges are consecutive and non-overlapping
|
||||
- **Bucket Organization**: Efficient bucket-based storage with size tracking
|
||||
- **Cryptographic Security**: Uses configurable hash functions for security
|
||||
- **Proof Steps**: Detailed proof steps with positional information
|
||||
- **Type Safety**: Full TypeScript support with comprehensive type definitions
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install merkletreejs
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { MerkleSumTree, Leaf } from 'merkletreejs'
|
||||
import SHA256 from 'crypto-js/sha256'
|
||||
|
||||
// Create leaves with ranges and data
|
||||
const leaves = [
|
||||
new Leaf(SHA256, [0, 10], Buffer.from('data1')),
|
||||
new Leaf(SHA256, [10, 25], Buffer.from('data2')),
|
||||
new Leaf(SHA256, [25, 30], Buffer.from('data3'))
|
||||
]
|
||||
|
||||
// Create tree
|
||||
const tree = new MerkleSumTree(leaves, SHA256)
|
||||
|
||||
// Get root bucket
|
||||
const root = tree.root
|
||||
console.log('Root size:', root.size.toString())
|
||||
console.log('Root hash:', root.hashed.toString('hex'))
|
||||
|
||||
// Generate proof for leaf at index 1
|
||||
const proof = tree.getProof(1)
|
||||
|
||||
// Verify proof
|
||||
const isValid = tree.verifyProof(root, leaves[1], proof)
|
||||
console.log('Proof verified:', isValid)
|
||||
```
|
||||
|
||||
## Core Classes
|
||||
|
||||
### Leaf Class
|
||||
|
||||
Represents a leaf node with a range and associated data.
|
||||
|
||||
#### Constructor
|
||||
```typescript
|
||||
new Leaf(hashFn, range, data)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `hashFn` (Function): Hash function to use
|
||||
- `range` (number[] | BigInt[]): [start, end] range values
|
||||
- `data` (Buffer | null): Associated data
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
const leaf = new Leaf(SHA256, [0, 100], Buffer.from('my data'))
|
||||
```
|
||||
|
||||
#### Methods
|
||||
|
||||
##### `getBucket()`
|
||||
Returns a bucket representation of the leaf.
|
||||
|
||||
```typescript
|
||||
const bucket = leaf.getBucket()
|
||||
console.log('Bucket size:', bucket.size.toString())
|
||||
console.log('Bucket hash:', bucket.hashed.toString('hex'))
|
||||
```
|
||||
|
||||
### Bucket Class
|
||||
|
||||
Represents a tree node with size and hash information.
|
||||
|
||||
#### Constructor
|
||||
```typescript
|
||||
new Bucket(size, hashed)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `size` (BigInt | number): Size of the bucket
|
||||
- `hashed` (Buffer): Hash of the bucket
|
||||
|
||||
#### Properties
|
||||
- `size` (BigInt): Size of the bucket
|
||||
- `hashed` (Buffer): Hash value
|
||||
- `parent` (Bucket | null): Parent bucket
|
||||
- `left` (Bucket | null): Left sibling
|
||||
- `right` (Bucket | null): Right sibling
|
||||
|
||||
### ProofStep Class
|
||||
|
||||
Represents a step in a proof path.
|
||||
|
||||
#### Constructor
|
||||
```typescript
|
||||
new ProofStep(bucket, right)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `bucket` (Bucket): The bucket for this proof step
|
||||
- `right` (boolean): Whether the bucket should be on the right side
|
||||
|
||||
## MerkleSumTree Class
|
||||
|
||||
### Constructor
|
||||
|
||||
```typescript
|
||||
new MerkleSumTree(leaves, hashFn)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `leaves` (Leaf[]): Array of leaf nodes with consecutive ranges
|
||||
- `hashFn` (Function): Hash function to use
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
const tree = new MerkleSumTree(leaves, SHA256)
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
- `leaves` (Leaf[]): Original leaf nodes
|
||||
- `buckets` (Bucket[]): Bucket representation of leaves
|
||||
- `root` (Bucket): Root bucket of the tree
|
||||
- `hashFn` (Function): Hash function used
|
||||
|
||||
### Methods
|
||||
|
||||
#### `getProof(index)`
|
||||
Generates an inclusion/exclusion proof for a bucket at the specified index.
|
||||
|
||||
**Parameters:**
|
||||
- `index` (number | BigInt): Index of the leaf to prove
|
||||
|
||||
**Returns:** Array of ProofStep objects
|
||||
|
||||
```typescript
|
||||
const proof = tree.getProof(1)
|
||||
console.log('Proof steps:', proof.length)
|
||||
```
|
||||
|
||||
#### `verifyProof(root, leaf, proof)`
|
||||
Verifies a proof for a specific leaf against the root bucket.
|
||||
|
||||
**Parameters:**
|
||||
- `root` (Bucket): Root bucket to verify against
|
||||
- `leaf` (Leaf): Leaf being proven
|
||||
- `proof` (ProofStep[]): Proof steps
|
||||
|
||||
**Returns:** boolean - true if proof is valid
|
||||
|
||||
```typescript
|
||||
const isValid = tree.verifyProof(tree.root, leaves[1], proof)
|
||||
```
|
||||
|
||||
#### `sum(arr)`
|
||||
Calculates the sum of an array of BigInt values.
|
||||
|
||||
**Parameters:**
|
||||
- `arr` (BigInt[]): Array of values to sum
|
||||
|
||||
**Returns:** BigInt - Sum of all values
|
||||
|
||||
```typescript
|
||||
const total = tree.sum([BigInt(10), BigInt(20), BigInt(30)])
|
||||
// Returns: BigInt(60)
|
||||
```
|
||||
|
||||
#### `static checkConsecutive(leaves)`
|
||||
Validates that leaf ranges are consecutive and non-overlapping.
|
||||
|
||||
**Parameters:**
|
||||
- `leaves` (Leaf[]): Array of leaves to validate
|
||||
|
||||
**Throws:** Error if ranges are invalid
|
||||
|
||||
```typescript
|
||||
MerkleSumTree.checkConsecutive(leaves) // Validates ranges
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import { MerkleSumTree, Leaf } from 'merkletreejs'
|
||||
import SHA256 from 'crypto-js/sha256'
|
||||
|
||||
// Create consecutive range leaves
|
||||
const leaves = [
|
||||
new Leaf(SHA256, [0, 50], Buffer.from('Alice: 50 tokens')),
|
||||
new Leaf(SHA256, [50, 120], Buffer.from('Bob: 70 tokens')),
|
||||
new Leaf(SHA256, [120, 200], Buffer.from('Charlie: 80 tokens'))
|
||||
]
|
||||
|
||||
// Create tree
|
||||
const tree = new MerkleSumTree(leaves, SHA256)
|
||||
|
||||
console.log('Total sum:', tree.root.size.toString()) // 200
|
||||
console.log('Root hash:', tree.root.hashed.toString('hex'))
|
||||
```
|
||||
|
||||
### Range Proof Generation and Verification
|
||||
|
||||
```typescript
|
||||
// Generate proof for Bob's tokens (index 1)
|
||||
const bobProof = tree.getProof(1)
|
||||
|
||||
console.log('Proof steps for Bob:')
|
||||
bobProof.forEach((step, i) => {
|
||||
console.log(`Step ${i}:`, {
|
||||
size: step.bucket.size.toString(),
|
||||
hash: step.bucket.hashed.toString('hex'),
|
||||
right: step.right
|
||||
})
|
||||
})
|
||||
|
||||
// Verify the proof
|
||||
const isValidProof = tree.verifyProof(tree.root, leaves[1], bobProof)
|
||||
console.log('Bob\'s proof is valid:', isValidProof)
|
||||
```
|
||||
|
||||
### Financial Balance Verification
|
||||
|
||||
```typescript
|
||||
// Financial balance example
|
||||
const balanceLeaves = [
|
||||
new Leaf(SHA256, [0, 1000], Buffer.from('Account A: $1000')),
|
||||
new Leaf(SHA256, [1000, 2500], Buffer.from('Account B: $1500')),
|
||||
new Leaf(SHA256, [2500, 3000], Buffer.from('Account C: $500')),
|
||||
new Leaf(SHA256, [3000, 5000], Buffer.from('Account D: $2000'))
|
||||
]
|
||||
|
||||
const balanceTree = new MerkleSumTree(balanceLeaves, SHA256)
|
||||
|
||||
console.log('Total balance:', balanceTree.root.size.toString()) // $5000
|
||||
|
||||
// Prove Account B's balance
|
||||
const accountBProof = balanceTree.getProof(1)
|
||||
const accountBValid = balanceTree.verifyProof(
|
||||
balanceTree.root,
|
||||
balanceLeaves[1],
|
||||
accountBProof
|
||||
)
|
||||
console.log('Account B proof valid:', accountBValid)
|
||||
```
|
||||
|
||||
### Supply Chain Tracking
|
||||
|
||||
```typescript
|
||||
// Supply chain with cumulative quantities
|
||||
const supplyLeaves = [
|
||||
new Leaf(SHA256, [0, 100], Buffer.from('Supplier A: 100 units')),
|
||||
new Leaf(SHA256, [100, 350], Buffer.from('Supplier B: 250 units')),
|
||||
new Leaf(SHA256, [350, 500], Buffer.from('Supplier C: 150 units'))
|
||||
]
|
||||
|
||||
const supplyTree = new MerkleSumTree(supplyLeaves, SHA256)
|
||||
|
||||
// Verify total supply
|
||||
console.log('Total supply:', supplyTree.root.size.toString()) // 500 units
|
||||
|
||||
// Generate proof for Supplier B
|
||||
const supplierBProof = supplyTree.getProof(1)
|
||||
const supplierBValid = supplyTree.verifyProof(
|
||||
supplyTree.root,
|
||||
supplyLeaves[1],
|
||||
supplierBProof
|
||||
)
|
||||
```
|
||||
|
||||
### Voting System with Weighted Votes
|
||||
|
||||
```typescript
|
||||
// Voting system where ranges represent vote weights
|
||||
const voteLeaves = [
|
||||
new Leaf(SHA256, [0, 25], Buffer.from('Proposal A: 25 votes')),
|
||||
new Leaf(SHA256, [25, 70], Buffer.from('Proposal B: 45 votes')),
|
||||
new Leaf(SHA256, [70, 100], Buffer.from('Proposal C: 30 votes'))
|
||||
]
|
||||
|
||||
const voteTree = new MerkleSumTree(voteLeaves, SHA256)
|
||||
|
||||
console.log('Total votes:', voteTree.root.size.toString()) // 100
|
||||
|
||||
// Prove Proposal B received 45 votes
|
||||
const proposalBProof = voteTree.getProof(1)
|
||||
const proposalBValid = voteTree.verifyProof(
|
||||
voteTree.root,
|
||||
voteLeaves[1],
|
||||
proposalBProof
|
||||
)
|
||||
|
||||
console.log('Proposal B vote proof valid:', proposalBValid)
|
||||
```
|
||||
|
||||
### Working with Large Numbers
|
||||
|
||||
```typescript
|
||||
// Using BigInt for large values
|
||||
const largeLeaves = [
|
||||
new Leaf(SHA256, [0n, 1000000000000n], Buffer.from('Large value 1')),
|
||||
new Leaf(SHA256, [1000000000000n, 2500000000000n], Buffer.from('Large value 2')),
|
||||
new Leaf(SHA256, [2500000000000n, 3000000000000n], Buffer.from('Large value 3'))
|
||||
]
|
||||
|
||||
const largeTree = new MerkleSumTree(largeLeaves, SHA256)
|
||||
console.log('Large total:', largeTree.root.size.toString())
|
||||
|
||||
// Proof generation works the same
|
||||
const largeProof = largeTree.getProof(0)
|
||||
const largeValid = largeTree.verifyProof(largeTree.root, largeLeaves[0], largeProof)
|
||||
```
|
||||
|
||||
### Custom Hash Functions
|
||||
|
||||
```typescript
|
||||
import { keccak256 } from 'js-sha3'
|
||||
|
||||
// Using keccak256 instead of SHA256
|
||||
const customHashFn = (data) => {
|
||||
return Buffer.from(keccak256.arrayBuffer(data))
|
||||
}
|
||||
|
||||
const customLeaves = [
|
||||
new Leaf(customHashFn, [0, 10], Buffer.from('data1')),
|
||||
new Leaf(customHashFn, [10, 20], Buffer.from('data2'))
|
||||
]
|
||||
|
||||
const customTree = new MerkleSumTree(customLeaves, customHashFn)
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Range Validation
|
||||
|
||||
The tree automatically validates that ranges are consecutive:
|
||||
|
||||
```typescript
|
||||
// This will throw an error - ranges are not consecutive
|
||||
try {
|
||||
const invalidLeaves = [
|
||||
new Leaf(SHA256, [0, 10], Buffer.from('data1')),
|
||||
new Leaf(SHA256, [15, 25], Buffer.from('data2')), // Gap: 10-15 missing
|
||||
new Leaf(SHA256, [25, 30], Buffer.from('data3'))
|
||||
]
|
||||
const invalidTree = new MerkleSumTree(invalidLeaves, SHA256)
|
||||
} catch (error) {
|
||||
console.log('Error:', error.message) // "leaf ranges are invalid"
|
||||
}
|
||||
```
|
||||
|
||||
### Proof Analysis
|
||||
|
||||
```typescript
|
||||
// Analyze proof structure
|
||||
const proof = tree.getProof(1)
|
||||
console.log('Proof analysis:')
|
||||
|
||||
let totalLeftSize = BigInt(0)
|
||||
let totalRightSize = BigInt(0)
|
||||
|
||||
proof.forEach((step, i) => {
|
||||
if (step.right) {
|
||||
totalRightSize += step.bucket.size
|
||||
} else {
|
||||
totalLeftSize += step.bucket.size
|
||||
}
|
||||
|
||||
console.log(`Step ${i}: Size ${step.bucket.size}, Position: ${step.right ? 'right' : 'left'}`)
|
||||
})
|
||||
|
||||
console.log('Total left size:', totalLeftSize.toString())
|
||||
console.log('Total right size:', totalRightSize.toString())
|
||||
```
|
||||
|
||||
### Tree Structure Inspection
|
||||
|
||||
```typescript
|
||||
// Inspect tree structure
|
||||
console.log('Tree structure:')
|
||||
console.log('Number of leaves:', tree.leaves.length)
|
||||
console.log('Number of buckets:', tree.buckets.length)
|
||||
console.log('Root size:', tree.root.size.toString())
|
||||
|
||||
// Inspect individual buckets
|
||||
tree.buckets.forEach((bucket, i) => {
|
||||
console.log(`Bucket ${i}: Size ${bucket.size}, Hash ${bucket.hashed.toString('hex').slice(0, 8)}...`)
|
||||
})
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- **Range Continuity**: Ensure ranges are consecutive to avoid validation errors
|
||||
- **Data Size**: Large data in leaves affects hash computation time
|
||||
- **Proof Size**: Proof size is logarithmic with respect to number of leaves
|
||||
- **BigInt Operations**: Use BigInt for large range values to avoid overflow
|
||||
|
||||
## Security Notes
|
||||
|
||||
- **Range Validation**: Tree validates range continuity automatically
|
||||
- **Hash Function**: Use cryptographically secure hash functions
|
||||
- **Proof Verification**: Always verify proofs on both client and server
|
||||
- **Range Boundaries**: Ensure range boundaries are correctly specified
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Financial Systems**: Prove account balances within ranges
|
||||
- **Supply Chain**: Track cumulative quantities with proofs
|
||||
- **Voting Systems**: Verify weighted vote totals
|
||||
- **Resource Allocation**: Prove resource distribution across ranges
|
||||
- **Audit Trails**: Maintain verifiable sum calculations
|
||||
- **Token Distribution**: Prove token allocations within ranges
|
||||
|
||||
## Browser Support
|
||||
|
||||
Works in both Node.js and browser environments. Ensure you have appropriate Buffer and BigInt support for your target environment.
|
||||
|
||||
## TypeScript Support
|
||||
|
||||
Full TypeScript support with comprehensive type definitions for all classes and methods. The library properly handles BigInt types for large range values.
|
||||
524
README-MerkleTree.md
Normal file
524
README-MerkleTree.md
Normal file
@@ -0,0 +1,524 @@
|
||||
# MerkleTree Class Documentation
|
||||
|
||||
A comprehensive implementation of a Merkle Tree data structure for creating tamper-proof data structures and generating cryptographic proofs. This is the core class of the merkletreejs library.
|
||||
|
||||
## Features
|
||||
|
||||
- **Standard Merkle Tree**: Classic binary tree implementation
|
||||
- **Multiple Hash Functions**: Support for SHA256, keccak256, and custom hash functions
|
||||
- **Proof Generation**: Generate and verify inclusion proofs
|
||||
- **Multi-Proofs**: Generate and verify proofs for multiple leaves simultaneously
|
||||
- **Bitcoin Compatibility**: Support for Bitcoin-style Merkle trees
|
||||
- **Serialization**: Marshal/unmarshal trees and proofs to/from JSON
|
||||
- **Flexible Options**: Configurable sorting, duplicate handling, and tree completion
|
||||
- **Type Safety**: Full TypeScript support with proper type definitions
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install merkletreejs
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { MerkleTree } from 'merkletreejs'
|
||||
import SHA256 from 'crypto-js/sha256'
|
||||
|
||||
// Create leaves (typically hashed data)
|
||||
const leaves = ['a', 'b', 'c', 'd'].map(x => SHA256(x))
|
||||
|
||||
// Create tree
|
||||
const tree = new MerkleTree(leaves, SHA256)
|
||||
|
||||
// Get root
|
||||
const root = tree.getRoot()
|
||||
const hexRoot = tree.getHexRoot()
|
||||
|
||||
// Generate proof
|
||||
const leaf = SHA256('b')
|
||||
const proof = tree.getProof(leaf)
|
||||
|
||||
// Verify proof
|
||||
const verified = tree.verify(proof, leaf, root)
|
||||
console.log('Proof verified:', verified) // true
|
||||
```
|
||||
|
||||
## Constructor
|
||||
|
||||
### `new MerkleTree(leaves, hashFunction?, options?)`
|
||||
|
||||
Creates a new Merkle tree instance.
|
||||
|
||||
**Parameters:**
|
||||
- `leaves` (Buffer[]): Array of leaf nodes (should be pre-hashed)
|
||||
- `hashFunction` (Function): Hash function to use (defaults to SHA256)
|
||||
- `options` (Options): Configuration options
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
const tree = new MerkleTree(leaves, SHA256, {
|
||||
sortPairs: true,
|
||||
duplicateOdd: false
|
||||
})
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```typescript
|
||||
interface Options {
|
||||
duplicateOdd?: boolean // Duplicate odd nodes (default: false)
|
||||
hashLeaves?: boolean // Hash leaves before adding (default: false)
|
||||
isBitcoinTree?: boolean // Use Bitcoin-style tree (default: false)
|
||||
sortLeaves?: boolean // Sort leaves (default: false)
|
||||
sortPairs?: boolean // Sort pairs (default: false)
|
||||
sort?: boolean // Sort leaves and pairs (default: false)
|
||||
fillDefaultHash?: Function // Fill function for odd layers
|
||||
complete?: boolean // Create complete tree (default: false)
|
||||
concatenator?: Function // Custom concatenation function
|
||||
}
|
||||
```
|
||||
|
||||
## Core Methods
|
||||
|
||||
### Tree Information
|
||||
|
||||
#### `getRoot()`
|
||||
Returns the Merkle root as a Buffer.
|
||||
|
||||
```typescript
|
||||
const root = tree.getRoot()
|
||||
```
|
||||
|
||||
#### `getHexRoot()`
|
||||
Returns the Merkle root as a hex string with '0x' prefix.
|
||||
|
||||
```typescript
|
||||
const hexRoot = tree.getHexRoot()
|
||||
```
|
||||
|
||||
#### `getLeaves(values?)`
|
||||
Returns array of leaves. Optionally filter by specific values.
|
||||
|
||||
```typescript
|
||||
const allLeaves = tree.getLeaves()
|
||||
const filteredLeaves = tree.getLeaves([specificHash1, specificHash2])
|
||||
```
|
||||
|
||||
#### `getHexLeaves()`
|
||||
Returns array of leaves as hex strings.
|
||||
|
||||
```typescript
|
||||
const hexLeaves = tree.getHexLeaves()
|
||||
```
|
||||
|
||||
#### `getLeafCount()`
|
||||
Returns the total number of leaves.
|
||||
|
||||
```typescript
|
||||
const count = tree.getLeafCount()
|
||||
```
|
||||
|
||||
#### `getDepth()`
|
||||
Returns the tree depth (number of layers).
|
||||
|
||||
```typescript
|
||||
const depth = tree.getDepth()
|
||||
```
|
||||
|
||||
### Leaf Operations
|
||||
|
||||
#### `getLeaf(index)`
|
||||
Gets a leaf by index.
|
||||
|
||||
```typescript
|
||||
const leaf = tree.getLeaf(0)
|
||||
```
|
||||
|
||||
#### `getHexLeaf(index)`
|
||||
Gets a leaf by index as hex string.
|
||||
|
||||
```typescript
|
||||
const hexLeaf = tree.getHexLeaf(0)
|
||||
```
|
||||
|
||||
#### `getLeafIndex(leaf)`
|
||||
Gets the index of a leaf (-1 if not found).
|
||||
|
||||
```typescript
|
||||
const index = tree.getLeafIndex(leafHash)
|
||||
```
|
||||
|
||||
#### `addLeaf(leaf, shouldHash?)`
|
||||
Adds a single leaf to the tree.
|
||||
|
||||
```typescript
|
||||
tree.addLeaf(newLeafHash)
|
||||
tree.addLeaf('raw data', true) // Hash before adding
|
||||
```
|
||||
|
||||
#### `addLeaves(leaves, shouldHash?)`
|
||||
Adds multiple leaves to the tree.
|
||||
|
||||
```typescript
|
||||
tree.addLeaves([hash1, hash2, hash3])
|
||||
tree.addLeaves(['data1', 'data2'], true) // Hash before adding
|
||||
```
|
||||
|
||||
#### `removeLeaf(index)`
|
||||
Removes a leaf by index.
|
||||
|
||||
```typescript
|
||||
const removedLeaf = tree.removeLeaf(2)
|
||||
```
|
||||
|
||||
#### `updateLeaf(index, value, shouldHash?)`
|
||||
Updates a leaf at specific index.
|
||||
|
||||
```typescript
|
||||
tree.updateLeaf(1, newHash)
|
||||
tree.updateLeaf(1, 'new data', true) // Hash before updating
|
||||
```
|
||||
|
||||
### Proof Operations
|
||||
|
||||
#### `getProof(leaf, index?)`
|
||||
Generates a proof for a leaf.
|
||||
|
||||
```typescript
|
||||
const proof = tree.getProof(leafHash)
|
||||
// For duplicate leaves, specify index
|
||||
const proof = tree.getProof(leafHash, 2)
|
||||
```
|
||||
|
||||
#### `getHexProof(leaf, index?)`
|
||||
Generates a proof as hex strings.
|
||||
|
||||
```typescript
|
||||
const hexProof = tree.getHexProof(leafHash)
|
||||
```
|
||||
|
||||
#### `getPositionalHexProof(leaf, index?)`
|
||||
Generates a proof with positional information.
|
||||
|
||||
```typescript
|
||||
const positionalProof = tree.getPositionalHexProof(leafHash)
|
||||
// Returns: [[position, hash], [position, hash], ...]
|
||||
```
|
||||
|
||||
#### `verify(proof, targetNode, root)`
|
||||
Verifies a proof against a root.
|
||||
|
||||
```typescript
|
||||
const isValid = tree.verify(proof, leafHash, root)
|
||||
```
|
||||
|
||||
#### `getProofs()`
|
||||
Gets proofs for all leaves.
|
||||
|
||||
```typescript
|
||||
const allProofs = tree.getProofs()
|
||||
```
|
||||
|
||||
#### `getHexProofs()`
|
||||
Gets proofs for all leaves as hex strings.
|
||||
|
||||
```typescript
|
||||
const allHexProofs = tree.getHexProofs()
|
||||
```
|
||||
|
||||
### Multi-Proof Operations
|
||||
|
||||
#### `getMultiProof(indices)`
|
||||
Generates a multi-proof for multiple leaves.
|
||||
|
||||
```typescript
|
||||
const multiProof = tree.getMultiProof([0, 2, 4])
|
||||
```
|
||||
|
||||
#### `getHexMultiProof(tree, indices)`
|
||||
Generates a multi-proof as hex strings.
|
||||
|
||||
```typescript
|
||||
const hexMultiProof = tree.getHexMultiProof(flatTree, [0, 2, 4])
|
||||
```
|
||||
|
||||
#### `verifyMultiProof(root, proofIndices, proofLeaves, leavesCount, proof)`
|
||||
Verifies a multi-proof.
|
||||
|
||||
```typescript
|
||||
const isValid = tree.verifyMultiProof(
|
||||
root,
|
||||
[0, 2, 4],
|
||||
[leaf0, leaf2, leaf4],
|
||||
totalLeaves,
|
||||
multiProof
|
||||
)
|
||||
```
|
||||
|
||||
#### `getProofFlags(leaves, proofs)`
|
||||
Gets boolean flags for multi-proof verification.
|
||||
|
||||
```typescript
|
||||
const flags = tree.getProofFlags([leaf0, leaf2], multiProof)
|
||||
```
|
||||
|
||||
### Layer Operations
|
||||
|
||||
#### `getLayers()`
|
||||
Gets all tree layers as 2D array of Buffers.
|
||||
|
||||
```typescript
|
||||
const layers = tree.getLayers()
|
||||
```
|
||||
|
||||
#### `getHexLayers()`
|
||||
Gets all tree layers as 2D array of hex strings.
|
||||
|
||||
```typescript
|
||||
const hexLayers = tree.getHexLayers()
|
||||
```
|
||||
|
||||
#### `getLayersFlat()`
|
||||
Gets all tree layers as flat array.
|
||||
|
||||
```typescript
|
||||
const flatLayers = tree.getLayersFlat()
|
||||
```
|
||||
|
||||
#### `getHexLayersFlat()`
|
||||
Gets all tree layers as flat array of hex strings.
|
||||
|
||||
```typescript
|
||||
const hexFlatLayers = tree.getHexLayersFlat()
|
||||
```
|
||||
|
||||
### Serialization
|
||||
|
||||
#### `static marshalLeaves(leaves)`
|
||||
Converts leaves to JSON string.
|
||||
|
||||
```typescript
|
||||
const jsonLeaves = MerkleTree.marshalLeaves(leaves)
|
||||
```
|
||||
|
||||
#### `static unmarshalLeaves(jsonStr)`
|
||||
Converts JSON string back to leaves.
|
||||
|
||||
```typescript
|
||||
const leaves = MerkleTree.unmarshalLeaves(jsonLeaves)
|
||||
```
|
||||
|
||||
#### `static marshalProof(proof)`
|
||||
Converts proof to JSON string.
|
||||
|
||||
```typescript
|
||||
const jsonProof = MerkleTree.marshalProof(proof)
|
||||
```
|
||||
|
||||
#### `static unmarshalProof(jsonStr)`
|
||||
Converts JSON string back to proof.
|
||||
|
||||
```typescript
|
||||
const proof = MerkleTree.unmarshalProof(jsonProof)
|
||||
```
|
||||
|
||||
#### `static marshalTree(tree)`
|
||||
Converts entire tree to JSON string.
|
||||
|
||||
```typescript
|
||||
const jsonTree = MerkleTree.marshalTree(tree)
|
||||
```
|
||||
|
||||
#### `static unmarshalTree(jsonStr, hashFn?, options?)`
|
||||
Recreates tree from JSON string.
|
||||
|
||||
```typescript
|
||||
const tree = MerkleTree.unmarshalTree(jsonTree, SHA256)
|
||||
```
|
||||
|
||||
### Utility Methods
|
||||
|
||||
#### `resetTree()`
|
||||
Clears all leaves and layers.
|
||||
|
||||
```typescript
|
||||
tree.resetTree()
|
||||
```
|
||||
|
||||
#### `toString()`
|
||||
Returns visual representation of the tree.
|
||||
|
||||
```typescript
|
||||
console.log(tree.toString())
|
||||
```
|
||||
|
||||
#### `getOptions()`
|
||||
Returns current tree options.
|
||||
|
||||
```typescript
|
||||
const options = tree.getOptions()
|
||||
```
|
||||
|
||||
## Static Methods
|
||||
|
||||
### `MerkleTree.verify(proof, targetNode, root, hashFn?, options?)`
|
||||
Static method to verify a proof without tree instance.
|
||||
|
||||
```typescript
|
||||
const isValid = MerkleTree.verify(proof, leaf, root, SHA256)
|
||||
```
|
||||
|
||||
### `MerkleTree.getMultiProof(tree, indices)`
|
||||
Static method to generate multi-proof from flat tree.
|
||||
|
||||
```typescript
|
||||
const multiProof = MerkleTree.getMultiProof(flatTree, [0, 2, 4])
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import { MerkleTree } from 'merkletreejs'
|
||||
import SHA256 from 'crypto-js/sha256'
|
||||
|
||||
// Prepare data
|
||||
const leaves = ['alice', 'bob', 'charlie', 'dave'].map(x => SHA256(x))
|
||||
|
||||
// Create tree
|
||||
const tree = new MerkleTree(leaves, SHA256)
|
||||
|
||||
// Get root
|
||||
const root = tree.getHexRoot()
|
||||
console.log('Root:', root)
|
||||
|
||||
// Generate and verify proof
|
||||
const leaf = SHA256('bob')
|
||||
const proof = tree.getProof(leaf)
|
||||
const verified = tree.verify(proof, leaf, tree.getRoot())
|
||||
console.log('Proof verified:', verified)
|
||||
```
|
||||
|
||||
### Bitcoin-Style Tree
|
||||
|
||||
```typescript
|
||||
const tree = new MerkleTree(leaves, SHA256, {
|
||||
isBitcoinTree: true
|
||||
})
|
||||
```
|
||||
|
||||
### Sorted Tree (Recommended for Multi-Proofs)
|
||||
|
||||
```typescript
|
||||
const tree = new MerkleTree(leaves, SHA256, {
|
||||
sortPairs: true,
|
||||
sortLeaves: true
|
||||
})
|
||||
```
|
||||
|
||||
### Complete Tree
|
||||
|
||||
```typescript
|
||||
const tree = new MerkleTree(leaves, SHA256, {
|
||||
complete: true // Creates a complete binary tree
|
||||
})
|
||||
```
|
||||
|
||||
### Working with Raw Data
|
||||
|
||||
```typescript
|
||||
// Hash leaves automatically
|
||||
const tree = new MerkleTree(['a', 'b', 'c'], SHA256, {
|
||||
hashLeaves: true
|
||||
})
|
||||
|
||||
// Or hash manually
|
||||
const leaves = ['a', 'b', 'c'].map(x => SHA256(x))
|
||||
const tree = new MerkleTree(leaves, SHA256)
|
||||
```
|
||||
|
||||
### Multi-Proof Example
|
||||
|
||||
```typescript
|
||||
const tree = new MerkleTree(leaves, SHA256, { complete: true })
|
||||
const indices = [0, 2, 4]
|
||||
const multiProof = tree.getMultiProof(indices)
|
||||
const proofLeaves = indices.map(i => leaves[i])
|
||||
|
||||
const verified = tree.verifyMultiProof(
|
||||
tree.getRoot(),
|
||||
indices,
|
||||
proofLeaves,
|
||||
leaves.length,
|
||||
multiProof
|
||||
)
|
||||
```
|
||||
|
||||
### Serialization Example
|
||||
|
||||
```typescript
|
||||
// Serialize tree
|
||||
const jsonTree = MerkleTree.marshalTree(tree)
|
||||
localStorage.setItem('merkleTree', jsonTree)
|
||||
|
||||
// Deserialize tree
|
||||
const savedTree = localStorage.getItem('merkleTree')
|
||||
const restoredTree = MerkleTree.unmarshalTree(savedTree, SHA256)
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Custom Hash Functions
|
||||
|
||||
```typescript
|
||||
const customHash = (data) => {
|
||||
return crypto.createHash('sha1').update(data).digest()
|
||||
}
|
||||
|
||||
const tree = new MerkleTree(leaves, customHash)
|
||||
```
|
||||
|
||||
### Custom Concatenator
|
||||
|
||||
```typescript
|
||||
const tree = new MerkleTree(leaves, SHA256, {
|
||||
concatenator: (buffers) => {
|
||||
// Custom way to combine buffers
|
||||
return Buffer.concat(buffers.reverse())
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Fill Default Hash
|
||||
|
||||
```typescript
|
||||
const tree = new MerkleTree(leaves, SHA256, {
|
||||
fillDefaultHash: (index, hashFn) => {
|
||||
return hashFn(`default-${index}`)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Use `complete: true` for better multi-proof performance
|
||||
- Sort pairs and leaves for consistent tree structure
|
||||
- Consider tree depth for large datasets
|
||||
- Use static methods when you don't need tree instance
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Always hash your input data before creating leaves
|
||||
- Use cryptographically secure hash functions
|
||||
- Validate proofs on both client and server side
|
||||
- Be aware of second preimage attacks with certain configurations
|
||||
|
||||
## Browser Support
|
||||
|
||||
The MerkleTree class works in both Node.js and browser environments. For browser usage, ensure you have appropriate polyfills for Buffer operations.
|
||||
|
||||
## TypeScript Support
|
||||
|
||||
Full TypeScript support with comprehensive type definitions for all methods and options.
|
||||
411
README-UnifiedBinaryTree.md
Normal file
411
README-UnifiedBinaryTree.md
Normal file
@@ -0,0 +1,411 @@
|
||||
# UnifiedBinaryTree Class Documentation
|
||||
|
||||
A specialized binary tree implementation designed for Ethereum-style key-value storage with cryptographic proofs. This implementation is optimized for storing account data, contract code, and storage slots with efficient key derivation and tree organization.
|
||||
|
||||
## Features
|
||||
|
||||
- **Ethereum-Compatible**: Designed for Ethereum account and storage data
|
||||
- **Key Derivation**: Built-in functions for generating tree keys from addresses
|
||||
- **Code Chunkification**: Automatic splitting of contract bytecode into chunks
|
||||
- **Stem-Based Organization**: Efficient 256-value leaf nodes with 31-byte stems
|
||||
- **Cryptographic Proofs**: Generate and verify inclusion proofs
|
||||
- **Serialization**: Full tree serialization and deserialization support
|
||||
- **Batch Operations**: Efficient batch insertion of multiple key-value pairs
|
||||
- **Type Safety**: Full TypeScript support with comprehensive type definitions
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install merkletreejs
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { UnifiedBinaryTree, getTreeKey, oldStyleAddressToAddress32 } from 'merkletreejs'
|
||||
import { blake3 } from '@noble/hashes/blake3'
|
||||
|
||||
// Create tree with BLAKE3 hash function
|
||||
const tree = new UnifiedBinaryTree(blake3)
|
||||
|
||||
// Convert Ethereum address to 32-byte format
|
||||
const address = Buffer.from('1234567890123456789012345678901234567890', 'hex')
|
||||
const address32 = oldStyleAddressToAddress32(address)
|
||||
|
||||
// Generate key and insert data
|
||||
const key = getTreeKey(address32, 0, 1, blake3)
|
||||
const value = Buffer.alloc(32).fill(1)
|
||||
tree.insert(key, value)
|
||||
|
||||
// Get Merkle root
|
||||
const root = tree.merkelize()
|
||||
console.log('Tree root:', root.toString('hex'))
|
||||
```
|
||||
|
||||
## Constructor
|
||||
|
||||
### `new UnifiedBinaryTree(hashFunction)`
|
||||
|
||||
Creates a new unified binary tree instance.
|
||||
|
||||
**Parameters:**
|
||||
- `hashFunction` (HashFunction): Hash function to use for key derivation and tree operations
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
import { blake3 } from '@noble/hashes/blake3'
|
||||
const tree = new UnifiedBinaryTree(blake3)
|
||||
```
|
||||
|
||||
## Key Derivation Functions
|
||||
|
||||
### `oldStyleAddressToAddress32(address)`
|
||||
Converts a 20-byte Ethereum address to a 32-byte address by left-padding with zeros.
|
||||
|
||||
```typescript
|
||||
const addr20 = Buffer.from('1234567890123456789012345678901234567890', 'hex')
|
||||
const addr32 = oldStyleAddressToAddress32(addr20)
|
||||
// Returns: 32-byte padded address
|
||||
```
|
||||
|
||||
### `getTreeKey(address, treeIndex, subIndex, hashFn)`
|
||||
Derives a tree key from an address and indices.
|
||||
|
||||
**Parameters:**
|
||||
- `address` (Address32): 32-byte address
|
||||
- `treeIndex` (number): Primary index for different trees
|
||||
- `subIndex` (number): Secondary index within the tree
|
||||
- `hashFn` (HashFunction): Hash function to use
|
||||
|
||||
```typescript
|
||||
const key = getTreeKey(address32, 0, 1, blake3)
|
||||
```
|
||||
|
||||
### `getTreeKeyForBasicData(address, hashFn)`
|
||||
Derives a key for storing account basic data (nonce, balance, etc.).
|
||||
|
||||
```typescript
|
||||
const basicDataKey = getTreeKeyForBasicData(address32, blake3)
|
||||
tree.insert(basicDataKey, accountData)
|
||||
```
|
||||
|
||||
### `getTreeKeyForCodeHash(address, hashFn)`
|
||||
Derives a key for storing a contract's code hash.
|
||||
|
||||
```typescript
|
||||
const codeHashKey = getTreeKeyForCodeHash(address32, blake3)
|
||||
tree.insert(codeHashKey, codeHash)
|
||||
```
|
||||
|
||||
### `getTreeKeyForStorageSlot(address, storageKey, hashFn)`
|
||||
Derives a key for a storage slot in a contract's storage.
|
||||
|
||||
```typescript
|
||||
// Header storage (slots 0-63)
|
||||
const headerKey = getTreeKeyForStorageSlot(address32, 5, blake3)
|
||||
|
||||
// Main storage (slots 256+)
|
||||
const mainKey = getTreeKeyForStorageSlot(address32, 300, blake3)
|
||||
```
|
||||
|
||||
### `getTreeKeyForCodeChunk(address, chunkId, hashFn)`
|
||||
Derives a key for storing a chunk of contract code.
|
||||
|
||||
```typescript
|
||||
const chunks = chunkifyCode(contractCode)
|
||||
chunks.forEach((chunk, i) => {
|
||||
const key = getTreeKeyForCodeChunk(address32, i, blake3)
|
||||
tree.insert(key, chunk)
|
||||
})
|
||||
```
|
||||
|
||||
## Code Chunkification
|
||||
|
||||
### `chunkifyCode(code)`
|
||||
Splits EVM bytecode into 31-byte chunks with metadata.
|
||||
|
||||
```typescript
|
||||
const code = Buffer.from('6001600201', 'hex') // PUSH1 01 PUSH1 02 ADD
|
||||
const chunks = chunkifyCode(code)
|
||||
// Returns array of 32-byte chunks with PUSH data metadata
|
||||
```
|
||||
|
||||
Each chunk contains:
|
||||
- 1 byte: Number of PUSH data bytes at start of next chunk
|
||||
- 31 bytes: Actual bytecode
|
||||
|
||||
## Core Methods
|
||||
|
||||
### Tree Operations
|
||||
|
||||
#### `insert(key, value)`
|
||||
Inserts a key-value pair into the tree.
|
||||
|
||||
**Parameters:**
|
||||
- `key` (Buffer): 32-byte key
|
||||
- `value` (Buffer): 32-byte value
|
||||
|
||||
```typescript
|
||||
tree.insert(key, value)
|
||||
```
|
||||
|
||||
#### `update(key, value)`
|
||||
Updates the value for an existing key (same as insert).
|
||||
|
||||
```typescript
|
||||
tree.update(key, newValue)
|
||||
```
|
||||
|
||||
#### `insertBatch(entries)`
|
||||
Performs batch insertion of multiple key-value pairs.
|
||||
|
||||
```typescript
|
||||
const entries = [
|
||||
{ key: key1, value: value1 },
|
||||
{ key: key2, value: value2 },
|
||||
{ key: key3, value: value3 }
|
||||
]
|
||||
tree.insertBatch(entries)
|
||||
```
|
||||
|
||||
#### `merkelize()`
|
||||
Computes the Merkle root of the entire tree.
|
||||
|
||||
```typescript
|
||||
const root = tree.merkelize()
|
||||
```
|
||||
|
||||
### Serialization
|
||||
|
||||
#### `serialize()`
|
||||
Serializes the entire tree structure to a Buffer.
|
||||
|
||||
```typescript
|
||||
const serialized = tree.serialize()
|
||||
// Save to file or transmit over network
|
||||
```
|
||||
|
||||
#### `static deserialize(data, hashFn)`
|
||||
Reconstructs a tree from its serialized form.
|
||||
|
||||
```typescript
|
||||
const newTree = UnifiedBinaryTree.deserialize(serialized, blake3)
|
||||
```
|
||||
|
||||
## Node Types
|
||||
|
||||
### StemNode
|
||||
Leaf node containing up to 256 values with a 31-byte stem.
|
||||
|
||||
```typescript
|
||||
const stem = Buffer.alloc(31, 0)
|
||||
const node = new StemNode(stem)
|
||||
node.setValue(0, Buffer.alloc(32).fill(1))
|
||||
```
|
||||
|
||||
### InternalNode
|
||||
Internal node with left and right children.
|
||||
|
||||
```typescript
|
||||
const node = new InternalNode()
|
||||
node.left = leftChild
|
||||
node.right = rightChild
|
||||
```
|
||||
|
||||
## Storage Layout
|
||||
|
||||
The tree uses a specific storage layout for Ethereum data:
|
||||
|
||||
### Address Space Organization
|
||||
- **Header Storage**: Slots 0-63 → Tree positions 64-127
|
||||
- **Code Storage**: Starting at position 128
|
||||
- **Main Storage**: Slots 256+ → Tree positions 384+
|
||||
|
||||
### Tree Key Structure
|
||||
- **31 bytes**: Stem (derived from address and tree index)
|
||||
- **1 byte**: Sub-index (0-255 for values within a stem node)
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Account Data Storage
|
||||
|
||||
```typescript
|
||||
import { UnifiedBinaryTree, getTreeKeyForBasicData, oldStyleAddressToAddress32 } from 'merkletreejs'
|
||||
import { blake3 } from '@noble/hashes/blake3'
|
||||
|
||||
const tree = new UnifiedBinaryTree(blake3)
|
||||
|
||||
// Store account basic data
|
||||
const address = Buffer.from('1234567890123456789012345678901234567890', 'hex')
|
||||
const address32 = oldStyleAddressToAddress32(address)
|
||||
const basicDataKey = getTreeKeyForBasicData(address32, blake3)
|
||||
|
||||
// Account data: nonce, balance, etc.
|
||||
const accountData = Buffer.alloc(32)
|
||||
accountData.writeUInt32BE(42, 28) // nonce = 42
|
||||
|
||||
tree.insert(basicDataKey, accountData)
|
||||
const root = tree.merkelize()
|
||||
```
|
||||
|
||||
### Contract Code Storage
|
||||
|
||||
```typescript
|
||||
// Store contract code hash
|
||||
const codeHash = blake3(contractBytecode)
|
||||
const codeHashKey = getTreeKeyForCodeHash(address32, blake3)
|
||||
tree.insert(codeHashKey, Buffer.from(codeHash))
|
||||
|
||||
// Store contract code chunks
|
||||
const chunks = chunkifyCode(contractBytecode)
|
||||
chunks.forEach((chunk, i) => {
|
||||
const key = getTreeKeyForCodeChunk(address32, i, blake3)
|
||||
tree.insert(key, chunk)
|
||||
})
|
||||
```
|
||||
|
||||
### Storage Slot Management
|
||||
|
||||
```typescript
|
||||
// Store header storage (special contract storage)
|
||||
for (let slot = 0; slot < 64; slot++) {
|
||||
const key = getTreeKeyForStorageSlot(address32, slot, blake3)
|
||||
const value = Buffer.alloc(32).fill(slot)
|
||||
tree.insert(key, value)
|
||||
}
|
||||
|
||||
// Store main storage
|
||||
const storageSlot = 300
|
||||
const storageKey = getTreeKeyForStorageSlot(address32, storageSlot, blake3)
|
||||
const storageValue = Buffer.alloc(32).fill(0xFF)
|
||||
tree.insert(storageKey, storageValue)
|
||||
```
|
||||
|
||||
### Batch Operations
|
||||
|
||||
```typescript
|
||||
const entries = []
|
||||
|
||||
// Prepare multiple entries
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const key = getTreeKey(address32, 0, i, blake3)
|
||||
const value = Buffer.alloc(32).fill(i)
|
||||
entries.push({ key, value })
|
||||
}
|
||||
|
||||
// Insert all at once
|
||||
tree.insertBatch(entries)
|
||||
const root = tree.merkelize()
|
||||
```
|
||||
|
||||
### Tree Serialization
|
||||
|
||||
```typescript
|
||||
// Create and populate tree
|
||||
const tree = new UnifiedBinaryTree(blake3)
|
||||
tree.insert(key1, value1)
|
||||
tree.insert(key2, value2)
|
||||
|
||||
// Serialize tree
|
||||
const serialized = tree.serialize()
|
||||
console.log('Serialized size:', serialized.length, 'bytes')
|
||||
|
||||
// Save to file (Node.js)
|
||||
require('fs').writeFileSync('tree.json', serialized)
|
||||
|
||||
// Deserialize tree
|
||||
const savedData = require('fs').readFileSync('tree.json')
|
||||
const restoredTree = UnifiedBinaryTree.deserialize(savedData, blake3)
|
||||
|
||||
// Verify trees are identical
|
||||
const originalRoot = tree.merkelize()
|
||||
const restoredRoot = restoredTree.merkelize()
|
||||
console.log('Trees match:', originalRoot.equals(restoredRoot))
|
||||
```
|
||||
|
||||
### Working with Different Hash Functions
|
||||
|
||||
```typescript
|
||||
import { sha256 } from '@noble/hashes/sha256'
|
||||
import { keccak256 } from '@noble/hashes/keccak'
|
||||
|
||||
// Different trees with different hash functions
|
||||
const blake3Tree = new UnifiedBinaryTree(blake3)
|
||||
const sha256Tree = new UnifiedBinaryTree(sha256)
|
||||
const keccakTree = new UnifiedBinaryTree(keccak256)
|
||||
|
||||
// Same data, different roots
|
||||
const key = getTreeKey(address32, 0, 1, blake3)
|
||||
const value = Buffer.alloc(32).fill(42)
|
||||
|
||||
blake3Tree.insert(key, value)
|
||||
sha256Tree.insert(key, value)
|
||||
keccakTree.insert(key, value)
|
||||
|
||||
// Different Merkle roots
|
||||
console.log('BLAKE3 root:', blake3Tree.merkelize().toString('hex'))
|
||||
console.log('SHA256 root:', sha256Tree.merkelize().toString('hex'))
|
||||
console.log('Keccak root:', keccakTree.merkelize().toString('hex'))
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Custom Key Derivation
|
||||
|
||||
```typescript
|
||||
// Custom key derivation for special use cases
|
||||
function getCustomTreeKey(
|
||||
address: Buffer,
|
||||
customIndex: number,
|
||||
hashFn: HashFunction
|
||||
): Buffer {
|
||||
const address32 = oldStyleAddressToAddress32(address)
|
||||
return getTreeKey(address32, customIndex, 0, hashFn)
|
||||
}
|
||||
|
||||
const customKey = getCustomTreeKey(address, 999, blake3)
|
||||
tree.insert(customKey, customValue)
|
||||
```
|
||||
|
||||
### Tree Inspection
|
||||
|
||||
```typescript
|
||||
// Check if tree is empty
|
||||
const isEmpty = tree.root === null
|
||||
|
||||
// Get tree structure info
|
||||
const serialized = tree.serialize()
|
||||
const treeData = JSON.parse(serialized.toString('utf8'))
|
||||
console.log('Tree structure:', JSON.stringify(treeData, null, 2))
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- **Batch Operations**: Use `insertBatch()` for multiple insertions
|
||||
- **Key Locality**: Keys with similar stems are stored in the same leaf nodes
|
||||
- **Tree Depth**: Maximum depth is 247 levels to prevent hash collisions
|
||||
- **Memory Usage**: Each stem node can hold up to 256 values efficiently
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Uses cryptographically secure hash functions for key derivation
|
||||
- Tree depth is limited to prevent collision attacks
|
||||
- All keys must be exactly 32 bytes
|
||||
- All values must be exactly 32 bytes
|
||||
- Stems are exactly 31 bytes for consistent tree structure
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Ethereum State Trees**: Store account data, code, and storage
|
||||
- **Layer 2 Solutions**: Efficient state management for rollups
|
||||
- **Blockchain Indexing**: Organize blockchain data with cryptographic proofs
|
||||
- **Verifiable Databases**: Create tamper-proof key-value stores
|
||||
- **Smart Contract Storage**: Efficient storage layout for contract data
|
||||
|
||||
## Browser Support
|
||||
|
||||
Works in both Node.js and browser environments. Ensure you have appropriate hash function implementations for your target environment.
|
||||
|
||||
## TypeScript Support
|
||||
|
||||
Full TypeScript support with comprehensive type definitions for all functions, classes, and interfaces.
|
||||
12
README.md
12
README.md
@@ -150,12 +150,12 @@ npm test
|
||||
|
||||
- Q: What other types of merkle trees are supported?
|
||||
|
||||
- Besides standard `MerkleTree`, there's these implementation classes available:
|
||||
- `MerkleMountainRange`
|
||||
- `MerkleSumTree`
|
||||
- `IncrementalMerkleTree`
|
||||
- `MerkleRadixTree`
|
||||
- `UnifiedBinaryTree` (EIP-7864)
|
||||
- Besides standard [`MerkleTree`](./README-MerkleTree.md), there's these implementation classes available:
|
||||
- [`MerkleMountainRange`](./README-MerkleMountainRange.md)
|
||||
- [`MerkleSumTree`](./README-MerkleSumTree.md)
|
||||
- [`IncrementalMerkleTree`](./README-IncrementalMerkleTree.md)
|
||||
- [`MerkleRadixTree`](./README-MerkleRadixTree.md)
|
||||
- [`UnifiedBinaryTree`](./README-UnifiedBinaryTree.md) (EIP-7864)
|
||||
|
||||
Example import of other classes:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user