Skip to content

Commit dba4a18

Browse files
authored
fix(audit): merkle library audit fixes (#1606)
<!-- 🚨 ATTENTION! 🚨 This PR template is REQUIRED. PRs not following this format will be closed without review. Requirements: - PR title must follow commit conventions: https://www.conventionalcommits.org/en/v1.0.0/ - Label your PR with the correct type (e.g., 🐛 Bug, ✨ Enhancement, 🧪 Test, etc.) - Provide clear and specific details in each section --> **Motivation:** In response to a recent audit report, we are closing out Lows and Infos related to the Merkle library. **Modifications:** * [fix(audit): Merkle library infos (#1597)](a98493f) * [fix(L-01): prevent uninitialized roots from being used (#1586)](30ec964) **Result:** Cleaner, safer code
1 parent 5e133a1 commit dba4a18

File tree

4 files changed

+562
-86
lines changed

4 files changed

+562
-86
lines changed

docs/core/libraries/Merkle.md

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
## Merkle
2+
3+
| File | Notes |
4+
| -------- | -------- |
5+
| [`Merkle.sol`](../../../src/contracts/libraries/Merkle.sol) | Core Merkle tree library |
6+
7+
## Overview
8+
9+
The `Merkle` library provides cryptographically secure Merkle tree functionality for the EigenLayer protocol. It supports both Keccak256 and SHA-256 hash functions for different use cases across the system. The library enables efficient verification of data inclusion in large datasets without requiring the full dataset, which is essential for scalable proof systems in EigenLayer.
10+
11+
Key capabilities include:
12+
- **Proof verification**: Verify that a leaf exists in a Merkle tree given a root and proof
13+
- **Tree construction**: Build Merkle trees from arrays of leaves
14+
- **Proof generation**: Generate inclusion proofs for specific leaves
15+
- **Dual hash function support**: Both Keccak256 (for EVM compatibility) and SHA-256 (for beacon chain compatibility)
16+
17+
## Prior Reading
18+
19+
* Understanding of [Merkle trees](https://en.wikipedia.org/wiki/Merkle_tree) and cryptographic hash functions
20+
* [EIP-197](https://eips.ethereum.org/EIPS/eip-197) for understanding precompiled contracts
21+
22+
## Usage in EigenLayer
23+
24+
The Merkle library is used extensively throughout EigenLayer for various proof systems:
25+
26+
- **[`BN254CertificateVerifier`](../../../src/contracts/multichain/BN254CertificateVerifier.sol)**: Verifies operator information inclusion in certificate Merkle trees (see [CertificateVerifier.md](../../multichain/destination/CertificateVerifier.md))
27+
- **[`OperatorTableUpdater`](../../../src/contracts/multichain/OperatorTableUpdater.sol)**: Manages operator set proofs for multichain operations (see [OperatorTableUpdater.md](../../multichain/destination/OperatorTableUpdater.md))
28+
- **[`RewardsCoordinator`](../../../src/contracts/core/RewardsCoordinator.sol)**: Verifies reward distribution claims (see [RewardsCoordinator.md](../RewardsCoordinator.md))
29+
- **[`BeaconChainProofs`](../../../src/contracts/libraries/BeaconChainProofs.sol)**: Processes beacon chain state proofs (see [EigenPod.md](../EigenPod.md) for beacon chain proof usage)
30+
31+
## Security Considerations
32+
33+
### **Critical Security Warning**
34+
35+
**You should avoid using leaf values that are 64 bytes long prior to hashing, salt the leaves, or hash the leaves with a hash function other than what is used for the Merkle tree's internal nodes.** This prevents potential collision attacks where the concatenation of a sorted pair of internal nodes could be reinterpreted as a leaf value.
36+
37+
### **Zero Hash Padding**
38+
39+
When trees are not perfect powers of 2, the library pads with `bytes32(0)` values. For security-critical applications, consider using unique filler values to prevent potential collision attacks with legitimate zero-valued leaves.
40+
41+
---
42+
43+
## Proof Verification
44+
45+
### **Keccak256 Proof Verification**
46+
47+
#### `verifyInclusionKeccak`
48+
49+
```solidity
50+
function verifyInclusionKeccak(
51+
bytes memory proof,
52+
bytes32 root,
53+
bytes32 leaf,
54+
uint256 index
55+
) internal pure returns (bool)
56+
```
57+
58+
Verifies that a given leaf is included in a Merkle tree using Keccak256.
59+
60+
*Effects:*
61+
* Computes the root hash by traversing the Merkle proof path
62+
* Compares computed root with expected root for verification
63+
64+
*Used in:*
65+
* [`BN254CertificateVerifier.verifyOperatorInfoProof`](../../../src/contracts/multichain/BN254CertificateVerifier.sol)
66+
* [`OperatorTableUpdater.checkGlobalTableHash`](../../../src/contracts/multichain/OperatorTableUpdater.sol)
67+
* [`RewardsCoordinator`](../../../src/contracts/core/RewardsCoordinator.sol) for reward claim verification
68+
69+
#### `processInclusionProofKeccak`
70+
71+
```solidity
72+
function processInclusionProofKeccak(
73+
bytes memory proof,
74+
bytes32 leaf,
75+
uint256 index
76+
) internal pure returns (bytes32)
77+
```
78+
79+
Returns the computed root hash by traversing up the tree from the leaf.
80+
81+
*Effects:*
82+
* Traverses the Merkle tree from leaf to root using provided proof
83+
* Computes hash at each level by combining current hash with proof siblings
84+
* Returns the final computed root hash
85+
86+
*Requirements:*
87+
* Proof length MUST be a multiple of 32 bytes (reverts with `InvalidProofLength` otherwise)
88+
* Index MUST reach 0 after processing all proof elements (reverts with `InvalidIndex` if proof length mismatch)
89+
90+
### **SHA-256 Proof Verification**
91+
92+
#### `verifyInclusionSha256`
93+
94+
```solidity
95+
function verifyInclusionSha256(
96+
bytes memory proof,
97+
bytes32 root,
98+
bytes32 leaf,
99+
uint256 index
100+
) internal pure returns (bool)
101+
```
102+
103+
Verifies inclusion using SHA-256 hash function via precompiled contract.
104+
105+
*Effects:*
106+
* Computes the root hash using SHA-256 via precompiled contract
107+
* Compares computed root with expected root for verification
108+
109+
*Used in:*
110+
* [`BeaconChainProofs`](../../../src/contracts/libraries/BeaconChainProofs.sol) for beacon chain state verification
111+
112+
#### `processInclusionProofSha256`
113+
114+
```solidity
115+
function processInclusionProofSha256(
116+
bytes memory proof,
117+
bytes32 leaf,
118+
uint256 index
119+
) internal view returns (bytes32)
120+
```
121+
122+
Returns the computed root hash by traversing up the tree from the leaf using SHA-256.
123+
124+
*Effects:*
125+
* Traverses the Merkle tree from leaf to root using provided proof
126+
* Computes hash at each level combining current hash with proof siblings
127+
* Returns the final computed root hash
128+
129+
*Requirements:*
130+
* Proof length MUST be non-zero and a multiple of 32 bytes (reverts with `InvalidProofLength` otherwise)
131+
* Index MUST reach 0 after processing all proof elements (reverts with `InvalidIndex` if proof length mismatch)
132+
133+
---
134+
135+
## Tree Construction
136+
137+
### **Keccak256 Tree Construction**
138+
139+
#### `merkleizeKeccak`
140+
141+
```solidity
142+
function merkleizeKeccak(bytes32[] memory leaves) internal pure returns (bytes32)
143+
```
144+
145+
Constructs a Merkle tree root from an array of leaves using Keccak256. Accepts any non-empty array of leaves, including single-leaf trees, and automatically pads to the next power of 2 using `bytes32(0)` values.
146+
147+
*Effects:*
148+
* Pads input array to next power of 2 (if needed) using `bytes32(0)` values
149+
* Constructs binary Merkle tree bottom-up by hashing pairs using in-place array modification
150+
* For single-leaf arrays, returns the leaf itself as the root
151+
* Returns the single root hash representing the entire tree
152+
153+
*Algorithm:*
154+
1. Pad leaves array to next power of 2
155+
2. Iteratively hash pairs level by level until single root remains
156+
3. Uses in-place array modification for gas efficiency
157+
158+
*Used in:*
159+
* [`BN254CertificateVerifier`](../../../src/contracts/multichain/BN254CertificateVerifier.sol) for operator info trees
160+
* [`OperatorTableUpdater`](../../../src/contracts/multichain/OperatorTableUpdater.sol) for operator set hashing
161+
162+
### **SHA-256 Tree Construction**
163+
164+
#### `merkleizeSha256`
165+
166+
```solidity
167+
function merkleizeSha256(bytes32[] memory leaves) internal pure returns (bytes32)
168+
```
169+
170+
Constructs a Merkle tree root using SHA-256 hash function.
171+
172+
*Effects:*
173+
* Validates input meets strict requirements (power of 2, minimum 2 leaves)
174+
* Constructs binary Merkle tree bottom-up using SHA-256 hashing
175+
* Returns the single root hash representing the entire tree
176+
177+
*Requirements*:
178+
* Input array MUST contain at least 2 leaves (rejects single-leaf trees with `NotEnoughLeaves` error)
179+
* Input array length MUST be an exact power of 2 (validates with `LeavesNotPowerOfTwo` error)
180+
* No auto-padding available - stricter requirements for beacon chain compatibility
181+
182+
*Used in:*
183+
* [`BeaconChainProofs`](../../../src/contracts/libraries/BeaconChainProofs.sol) for beacon chain compatibility
184+
185+
---
186+
187+
## Proof Generation
188+
189+
### **Keccak256 Proof Generation**
190+
191+
#### `getProofKeccak`
192+
193+
```solidity
194+
function getProofKeccak(bytes32[] memory leaves, uint256 index) internal pure returns (bytes memory proof)
195+
```
196+
197+
Generates an inclusion proof for a specific leaf in a Keccak256 tree. Supports single-leaf trees (returns empty proof) and automatically handles non-power-of-2 leaf arrays through padding.
198+
199+
*Effects:*
200+
* Constructs a Merkle tree from the provided leaves with automatic padding
201+
* Traverses from specified leaf to root, collecting sibling hashes at each level
202+
* For single-leaf trees, returns empty proof since root equals leaf
203+
* Returns concatenated proof bytes for verification
204+
205+
*Algorithm:*
206+
1. Pad leaves to next power of 2
207+
2. For each tree level, find sibling of current index
208+
3. Append sibling to proof bytes
209+
4. Move up tree by dividing index by 2
210+
5. Continue until reaching root
211+
212+
*Used in:*
213+
* Test frameworks for generating proofs
214+
* Off-chain proof generation systems
215+
216+
### **SHA-256 Proof Generation**
217+
218+
#### `getProofSha256`
219+
220+
```solidity
221+
function getProofSha256(bytes32[] memory leaves, uint256 index) internal pure returns (bytes memory proof)
222+
```
223+
224+
Generates SHA-256 inclusion proof with same algorithm as Keccak version but using SHA-256 hashing.
225+
226+
*Effects:*
227+
* Validates input meets SHA-256 requirements (minimum 2 leaves)
228+
* Constructs a Merkle tree using SHA-256 hashing
229+
* Traverses from specified leaf to root, collecting sibling hashes
230+
* Returns concatenated proof bytes for verification
231+
232+
*Requirements*:
233+
* Input array MUST contain at least 2 leaves (rejects single-leaf trees with `NotEnoughLeaves` error)
234+
* Cannot generate proofs for single-element arrays
235+
* Follows stricter validation for beacon chain compatibility
236+
237+
---
238+
239+
## Utility Functions
240+
241+
### **Power of Two Check**
242+
243+
#### `isPowerOfTwo`
244+
245+
```solidity
246+
function isPowerOfTwo(uint256 value) internal pure returns (bool)
247+
```
248+
249+
Efficiently determines if a value is a power of 2 using bit manipulation.
250+
251+
*Effects:*
252+
* Performs bitwise operations to check power-of-2 property
253+
254+
*Used internally* for validation in `merkleizeSha256` and optimization paths.
255+
256+
---
257+
258+
## Error Reference
259+
260+
| Error | Code | Description |
261+
|-------|------|-------------|
262+
| `InvalidProofLength` | `0x4dc5f6a4` | Proof length not multiple of 32 bytes |
263+
| `InvalidIndex` | `0x63df8171` | Index outside valid range for tree |
264+
| `LeavesNotPowerOfTwo` | `0xf6558f51` | Leaves array not power of 2 (SHA-256 only) |
265+
| `NoLeaves` | `0xbaec3d9a` | Empty leaves array provided |
266+
| `NotEnoughLeaves` | `0xf8ef0367` | Less than 2 leaves for SHA-256 operations |
267+
268+
---
269+
270+
## Implementation Optimizations
271+
272+
### **Assembly Usage**
273+
- Proof verification uses inline assembly for gas efficiency
274+
- Manual memory management avoids Solidity's safety overhead
275+
- Direct opcode usage (Keccak256) vs precompile calls (SHA-256)
276+
277+
### **Memory Efficiency**
278+
- Tree construction reuses input array space
279+
- In-place modifications reduce memory allocation costs
280+
- Sibling calculation uses XOR instead of arithmetic operations
281+
282+
### **Precompile Handling**
283+
- SHA-256 functions reserve gas before precompile calls
284+
- Static call pattern optimized for success case
285+
- Explicit revert handling for precompile failures
286+
287+
---
288+
289+
## Implementation Notes
290+
291+
### **Keccak256 vs SHA-256 Differences**
292+
293+
The library provides two distinct implementations with different design philosophies:
294+
295+
| Feature | Keccak256 Functions | SHA-256 Functions |
296+
|---------|-------------------|-------------------|
297+
| **Single-leaf trees** | ✅ Supported | ❌ Rejected (`NotEnoughLeaves`) |
298+
| **Input validation** | Flexible (any non-empty array) | Strict (≥2 leaves, power of 2) |
299+
| **Auto-padding** | ✅ Pads to next power of 2 | ❌ Requires exact power of 2 |
300+
| **Use case** | General EVM Merkle trees | Beacon chain compatibility |
301+
| **Error handling** | Permissive | Strict validation |
302+
303+
### **Tree Padding Strategy**
304+
The library uses different padding strategies for different hash functions:
305+
306+
- **Keccak256**: Pads with `bytes32(0)` to next power of 2
307+
- **SHA-256**: Requires exact power of 2, no padding
308+
309+
### **Index Validation**
310+
Index validation occurs during proof processing rather than upfront, allowing the tree traversal algorithm to naturally detect out-of-bounds conditions.
311+
312+
### **Memory Layout**
313+
Proof bytes are laid out as concatenated 32-byte chunks: `[sibling₀, sibling₁, ..., siblingₙ]` where siblings are sequenced by tree depth, with `sibling₀` being the sibling at the leaf level, `sibling₁` at the next level up, and so on until reaching the root.

0 commit comments

Comments
 (0)