@osmura/merkletreejs 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,454 @@
1
+ /// <reference types="node" />
2
+ /**
3
+ * Type aliases for fixed-length byte sequences used throughout the codebase.
4
+ * These help maintain type safety and clarify the expected byte lengths.
5
+ */
6
+ export declare type Address = Buffer;
7
+ export declare type Address32 = Buffer;
8
+ export declare type Bytes32 = Buffer;
9
+ /**
10
+ * Constants used for key derivation and tree organization.
11
+ * These define the structure and layout of the binary tree.
12
+ */
13
+ export declare const BASIC_DATA_LEAF_KEY = 0;
14
+ export declare const CODE_HASH_LEAF_KEY = 1;
15
+ export declare const HEADER_STORAGE_OFFSET = 64;
16
+ export declare const CODE_OFFSET = 128;
17
+ export declare const STEM_SUBTREE_WIDTH = 256;
18
+ export declare const MAIN_STORAGE_OFFSET = 256;
19
+ export declare const pushOffset = 95;
20
+ export declare const push1: number;
21
+ export declare const push32: number;
22
+ /** Function type for hash operations */
23
+ export declare type HashFunction = (data: any) => any;
24
+ /**
25
+ * Converts a 20-byte Ethereum address to a 32-byte address by left-padding with zeros.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const addr20 = Buffer.from('1234567890123456789012345678901234567890', 'hex')
30
+ * const addr32 = oldStyleAddressToAddress32(addr20)
31
+ * // addr32 = 0x000000000000123456789012345678901234567890 (32 bytes)
32
+ * ```
33
+ */
34
+ export declare function oldStyleAddressToAddress32(address: Address): Address32;
35
+ /**
36
+ * Applies a hash function to input data with proper buffering.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * const input = Buffer.from('Hello World')
41
+ * const hashFn = (data) => blake3.hash(data)
42
+ * const hash = treeHash(input, hashFn)
43
+ * // hash = 32-byte BLAKE3 hash of 'Hello World'
44
+ * ```
45
+ */
46
+ export declare function treeHash(input: Buffer, hashFn: HashFunction): Bytes32;
47
+ /**
48
+ * Derives a tree key from an address and indices using a hash function.
49
+ * Used to generate unique keys for different parts of the tree structure.
50
+ * The resulting key is composed of a 31-byte stem (derived from address and treeIndex)
51
+ * and a 1-byte subIndex.
52
+ *
53
+ * @param address - A 32-byte address to derive the key from
54
+ * @param treeIndex - Primary index used to derive different trees for the same address
55
+ * @param subIndex - Secondary index used to derive different keys within the same tree
56
+ * @param hashFn - Hash function to use for key derivation
57
+ * @returns A 32-byte key that uniquely identifies this storage slot
58
+ * @throws Error if address is not 32 bytes
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * const addr32 = oldStyleAddressToAddress32(address)
63
+ * const treeKey = getTreeKey(addr32, 0, 1, blake3.hash)
64
+ * // Returns a unique key for this address's tree at index 0, subIndex 1
65
+ * ```
66
+ */
67
+ export declare function getTreeKey(address: Address32, treeIndex: number, subIndex: number, hashFn: HashFunction): Address32;
68
+ /**
69
+ * Derives a key for storing an account's basic data (nonce, balance, etc.).
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const addr32 = oldStyleAddressToAddress32(address)
74
+ * const basicDataKey = getTreeKeyForBasicData(addr32, hashFn)
75
+ * tree.insert(basicDataKey, accountData)
76
+ * ```
77
+ */
78
+ export declare function getTreeKeyForBasicData(address: Address32, hashFn: HashFunction): Address32;
79
+ /**
80
+ * Derives a key for storing a contract's code hash.
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const addr32 = oldStyleAddressToAddress32(contractAddress)
85
+ * const codeHashKey = getTreeKeyForCodeHash(addr32, hashFn)
86
+ * tree.insert(codeHashKey, codeHash)
87
+ * ```
88
+ */
89
+ export declare function getTreeKeyForCodeHash(address: Address32, hashFn: HashFunction): Address32;
90
+ /**
91
+ * Derives a tree key for a storage slot in a contract's storage.
92
+ * Handles two types of storage:
93
+ * 1. Header storage (slots 0-63): Used for contract metadata and special storage
94
+ * 2. Main storage (slots 256+): Used for regular contract storage
95
+ *
96
+ * The storage layout is:
97
+ * - Header storage: slots [0, 63] mapped to positions [64, 127]
98
+ * - Main storage: slots [256+] mapped to positions [384+]
99
+ * This creates gaps in the tree to allow for future extensions.
100
+ *
101
+ * @param address - The 32-byte contract address
102
+ * @param storageKey - The storage slot number to access
103
+ * @param hashFn - Hash function to use for key derivation
104
+ * @returns A 32-byte key that uniquely identifies this storage slot
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * const addr32 = oldStyleAddressToAddress32(contractAddress)
109
+ * // Get key for a header storage slot (0-63)
110
+ * const headerKey = getTreeKeyForStorageSlot(addr32, 5, blake3.hash)
111
+ * // Get key for a main storage slot (256+)
112
+ * const mainKey = getTreeKeyForStorageSlot(addr32, 300, blake3.hash)
113
+ * ```
114
+ */
115
+ export declare function getTreeKeyForStorageSlot(address: Address32, storageKey: number, hashFn: HashFunction): Address32;
116
+ /**
117
+ * Derives a key for storing a chunk of contract code.
118
+ * Used when contract code is split into 32-byte chunks.
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const addr32 = oldStyleAddressToAddress32(contractAddress)
123
+ * const chunks = chunkifyCode(contractCode)
124
+ * chunks.forEach((chunk, i) => {
125
+ * const key = getTreeKeyForCodeChunk(addr32, i, hashFn)
126
+ * tree.insert(key, chunk)
127
+ * })
128
+ * ```
129
+ */
130
+ export declare function getTreeKeyForCodeChunk(address: Address32, chunkId: number, hashFn: HashFunction): Address32;
131
+ /**
132
+ * Splits EVM bytecode into 31-byte chunks with metadata.
133
+ * Each chunk is prefixed with a byte indicating the number of bytes
134
+ * that are part of PUSH data in the next chunk.
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * const code = Buffer.from('6001600201', 'hex') // PUSH1 01 PUSH1 02 ADD
139
+ * const chunks = chunkifyCode(code)
140
+ * // chunks[0] = [0x01, 0x60, 0x01, 0x60, 0x02, 0x01, 0x00...] (32 bytes)
141
+ * ```
142
+ */
143
+ export declare function chunkifyCode(code: Buffer): Bytes32[];
144
+ /**
145
+ * Node types in the binary tree.
146
+ * - StemNode: Leaf node containing up to 256 values
147
+ * - InternalNode: Internal node with left and right children
148
+ */
149
+ export declare type BinaryTreeNode = StemNode | InternalNode;
150
+ /**
151
+ * Leaf node in the binary tree that stores actual values.
152
+ * Contains a 31-byte stem and an array of 256 possible values.
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * const stem = Buffer.alloc(31, 0)
157
+ * const node = new StemNode(stem)
158
+ * node.setValue(0, Buffer.alloc(32).fill(1)) // Set value at index 0
159
+ * ```
160
+ */
161
+ export declare class StemNode {
162
+ stem: Buffer;
163
+ values: Array<Buffer | null>;
164
+ nodeType: 'stem';
165
+ /**
166
+ * Creates a new StemNode with the given stem.
167
+ *
168
+ * @param stem - The 31-byte stem for this node.
169
+ */
170
+ constructor(stem: Buffer);
171
+ /**
172
+ * Sets the value at the given index.
173
+ *
174
+ * @param index - The index to set the value at.
175
+ * @param value - The 32-byte value to set.
176
+ */
177
+ setValue(index: number, value: Buffer): void;
178
+ }
179
+ /**
180
+ * Internal node in the binary tree with left and right children.
181
+ * Used to create the tree structure based on key bit patterns.
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * const node = new InternalNode()
186
+ * node.left = new StemNode(Buffer.alloc(31, 0))
187
+ * node.right = new StemNode(Buffer.alloc(31, 1))
188
+ * ```
189
+ */
190
+ export declare class InternalNode {
191
+ left: BinaryTreeNode | null;
192
+ right: BinaryTreeNode | null;
193
+ nodeType: 'internal';
194
+ }
195
+ /**
196
+ * Main binary tree implementation that stores key-value pairs.
197
+ * Uses a configurable hash function and supports various operations.
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * const tree = new BinaryTree(blake3.hash)
202
+ * tree.insert(key, value)
203
+ * const root = tree.merkelize()
204
+ * const serialized = tree.serialize()
205
+ * ```
206
+ */
207
+ export declare class UnifiedBinaryTree {
208
+ root: BinaryTreeNode | null;
209
+ hashFn: HashFunction;
210
+ /**
211
+ * Creates a new BinaryTree instance with the given hash function.
212
+ *
213
+ * @param hashFn - The hash function to use for key derivation.
214
+ */
215
+ constructor(hashFn: HashFunction);
216
+ /**
217
+ * Inserts a key-value pair into the binary tree.
218
+ * The key is split into two parts:
219
+ * - stem (first 31 bytes): Determines the path in the tree
220
+ * - subIndex (last byte): Determines the position within a leaf node
221
+ *
222
+ * If this is the first insertion, creates a new leaf node.
223
+ * Otherwise, recursively traverses or builds the tree structure.
224
+ *
225
+ * @param key - A 32-byte key that determines where to store the value
226
+ * @param value - A 32-byte value to store
227
+ * @throws Error if key or value is not exactly 32 bytes
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * const tree = new BinaryTree(hashFn)
232
+ * const key = getTreeKey(address, 0, 1, hashFn)
233
+ * const value = Buffer.alloc(32).fill(1)
234
+ * tree.insert(key, value)
235
+ * ```
236
+ */
237
+ insert(key: Buffer, value: Buffer): void;
238
+ /**
239
+ * Recursively inserts a key-value pair into the tree.
240
+ * This method handles three cases:
241
+ * 1. Empty node: Creates a new leaf node
242
+ * 2. Stem node: Either updates value or splits into internal node
243
+ * 3. Internal node: Recursively traverses left or right based on stem bits
244
+ *
245
+ * @param node - Current node in traversal (null if empty)
246
+ * @param stem - The 31-byte path component of the key
247
+ * @param subIndex - The leaf position component of the key
248
+ * @param value - The 32-byte value to store
249
+ * @param depth - Current depth in the tree (max 247 to prevent hash collisions)
250
+ * @returns The new or updated node
251
+ * @throws Error if tree depth exceeds 247 levels
252
+ */
253
+ private insertRecursive;
254
+ /**
255
+ * Converts a byte array to an array of individual bits.
256
+ * Each byte is converted to 8 bits, maintaining the most-significant-bit first order.
257
+ * Used for making path decisions in the binary tree based on stem bytes.
258
+ *
259
+ * @param data - Buffer containing bytes to convert
260
+ * @returns Array of bits (0s and 1s) in MSB-first order
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * const bytes = Buffer.from([0xA5]) // Binary: 10100101
265
+ * const bits = bytesToBits(bytes)
266
+ * // bits = [1,0,1,0,0,1,0,1]
267
+ * // ^ MSB LSB ^
268
+ * ```
269
+ *
270
+ * Process for each byte:
271
+ * 1. Right shift by (7-i) positions to get desired bit to LSB
272
+ * 2. AND with 1 to isolate that bit
273
+ * 3. Push result (0 or 1) to output array
274
+ */
275
+ private bytesToBits;
276
+ /**
277
+ * Converts an array of bits back into a Buffer of bytes.
278
+ * This is the inverse operation of bytesToBits.
279
+ * Processes bits in groups of 8, maintaining MSB-first order.
280
+ *
281
+ * @param bits - Array of 0s and 1s to convert to bytes
282
+ * @returns Buffer containing the reconstructed bytes
283
+ * @throws Error if the number of bits is not divisible by 8
284
+ *
285
+ * @example
286
+ * ```typescript
287
+ * const bits = [1,0,1,0,0,1,0,1] // Represents binary 10100101
288
+ * const bytes = bitsToBytes(bits)
289
+ * // bytes = Buffer.from([0xA5])
290
+ * ```
291
+ *
292
+ * Process for each byte:
293
+ * 1. Take 8 bits at a time
294
+ * 2. For each bit:
295
+ * - Shift it left to its correct position (7-j positions)
296
+ * - OR it with the accumulating byte value
297
+ * 3. Add completed byte to array
298
+ */
299
+ private bitsToBytes;
300
+ /**
301
+ * Applies the hash function to the given data with special handling for null values.
302
+ * Used primarily for Merkle tree calculations and node hashing.
303
+ *
304
+ * Special cases:
305
+ * - null input -> returns 32-byte zero buffer
306
+ * - 64-byte zero buffer -> returns 32-byte zero buffer
307
+ * This handling ensures consistent treatment of empty/uninitialized nodes.
308
+ *
309
+ * @param data - Buffer to hash, must be either 32 or 64 bytes, or null
310
+ * @returns A 32-byte hash of the data, or zero32 for empty cases
311
+ * @throws Error if data length is not 32 or 64 bytes
312
+ *
313
+ * @example
314
+ * ```typescript
315
+ * // Regular hashing
316
+ * const hash1 = hashData(nodeBuffer) // Returns hash of data
317
+ *
318
+ * // Empty cases - all return 32 zeros
319
+ * const hash2 = hashData(null)
320
+ * const hash3 = hashData(Buffer.alloc(64, 0))
321
+ * ```
322
+ */
323
+ private hashData;
324
+ /**
325
+ * Computes the Merkle root of the entire tree.
326
+ * The Merkle root is a single 32-byte hash that uniquely represents the entire tree state.
327
+ *
328
+ * The computation follows these rules:
329
+ * 1. For Internal nodes: hash(leftChild || rightChild)
330
+ * 2. For Stem nodes: hash(stem || 0x00 || merkleOfValues)
331
+ * 3. For empty nodes: return 32 bytes of zeros
332
+ *
333
+ * @returns A 32-byte Buffer containing the Merkle root
334
+ *
335
+ * @example
336
+ * ```typescript
337
+ * const tree = new BinaryTree(hashFn)
338
+ * tree.insert(key1, value1)
339
+ * tree.insert(key2, value2)
340
+ * const root = tree.merkelize()
341
+ * // root now contains a 32-byte hash representing the entire tree
342
+ * ```
343
+ */
344
+ merkelize(): Buffer;
345
+ /**
346
+ * Incrementally updates the value for an existing key.
347
+ * For our implementation, update is the same as insert.
348
+ *
349
+ * @param key - A 32-byte key.
350
+ * @param value - A 32-byte value.
351
+ */
352
+ update(key: Buffer, value: Buffer): void;
353
+ /**
354
+ * Performs a batch insertion of key-value pairs.
355
+ *
356
+ * @param entries - An array of objects with 'key' and 'value' properties.
357
+ */
358
+ insertBatch(entries: {
359
+ key: Buffer;
360
+ value: Buffer;
361
+ }[]): void;
362
+ /**
363
+ * Serializes the entire tree structure into a JSON Buffer.
364
+ * Converts the tree into a format that can be stored or transmitted,
365
+ * preserving the complete structure and all values.
366
+ *
367
+ * The serialized format for each node type is:
368
+ * 1. Stem Node:
369
+ * ```json
370
+ * {
371
+ * "nodeType": "stem",
372
+ * "stem": "hex string of 31 bytes",
373
+ * "values": ["hex string or null", ...] // 256 entries
374
+ * }
375
+ * ```
376
+ * 2. Internal Node:
377
+ * ```json
378
+ * {
379
+ * "nodeType": "internal",
380
+ * "left": <node or null>,
381
+ * "right": <node or null>
382
+ * }
383
+ * ```
384
+ *
385
+ * @returns Buffer containing the JSON string representation of the tree
386
+ *
387
+ * @example
388
+ * ```typescript
389
+ * const tree = new BinaryTree(hashFn)
390
+ * tree.insert(key, value)
391
+ * const serialized = tree.serialize()
392
+ * // Save to file or transmit
393
+ * const newTree = UnifiedBinaryTree.deserialize(serialized, hashFn)
394
+ * ```
395
+ */
396
+ serialize(): Buffer;
397
+ /**
398
+ * Reconstructs a BinaryTree from its serialized form.
399
+ * This is the inverse operation of serialize().
400
+ *
401
+ * Expected input format:
402
+ * ```json
403
+ * {
404
+ * "root": {
405
+ * "nodeType": "internal"|"stem",
406
+ * // For stem nodes:
407
+ * "stem": "hex string",
408
+ * "values": ["hex string"|null, ...],
409
+ * // For internal nodes:
410
+ * "left": <node|null>,
411
+ * "right": <node|null>
412
+ * }
413
+ * }
414
+ * ```
415
+ *
416
+ * @param data - Buffer containing the JSON serialized tree
417
+ * @param hashFn - Hash function to use for the reconstructed tree
418
+ * @returns A new BinaryTree instance with the deserialized structure
419
+ * @throws Error if JSON parsing fails or format is invalid
420
+ *
421
+ * @example
422
+ * ```typescript
423
+ * const serialized = existingTree.serialize()
424
+ * const newTree = UnifiedBinaryTree.deserialize(serialized, hashFn)
425
+ * // newTree is now identical to existingTree
426
+ * ```
427
+ */
428
+ static deserialize(data: Buffer, hashFn: HashFunction): UnifiedBinaryTree;
429
+ /**
430
+ * Splits a leaf node when inserting a new key with a different stem.
431
+ * This method handles two cases:
432
+ * 1. Matching bits at current depth: Continue splitting recursively
433
+ * 2. Different bits at current depth: Create new internal node and arrange leaves
434
+ *
435
+ * The process ensures that keys with different stems are properly distributed
436
+ * in the tree based on their binary representation.
437
+ *
438
+ * @param leaf - The existing leaf node to split
439
+ * @param stemBits - Binary representation of the new stem
440
+ * @param existingStemBits - Binary representation of the existing stem
441
+ * @param subIndex - Position within leaf node for new value
442
+ * @param value - Value to store at the new position
443
+ * @param depth - Current depth in the tree
444
+ * @returns A new internal node containing both the existing and new data
445
+ *
446
+ * Example:
447
+ * If stems differ at bit 3:
448
+ * - New stem: [1,0,1,0,...]
449
+ * - Existing stem: [1,0,1,1,...]
450
+ * ^ split here
451
+ * Creates an internal node with the leaf nodes arranged based on bit 3
452
+ */
453
+ private splitLeaf;
454
+ }