@aztec/merkle-tree 0.44.0 → 0.45.1

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.
package/dest/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export * from './sparse_tree/sparse_tree.js';
8
8
  export { StandardIndexedTree } from './standard_indexed_tree/standard_indexed_tree.js';
9
9
  export { StandardIndexedTreeWithAppend } from './standard_indexed_tree/test/standard_indexed_tree_with_append.js';
10
10
  export * from './standard_tree/standard_tree.js';
11
+ export * from './unbalanced_tree.js';
11
12
  export { INITIAL_LEAF, getTreeMeta } from './tree_base.js';
12
13
  export { newTree } from './new_tree.js';
13
14
  export { loadTree } from './load_tree.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,8BAA8B,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kDAAkD,CAAC;AACvF,OAAO,EAAE,6BAA6B,EAAE,MAAM,mEAAmE,CAAC;AAClH,cAAc,kCAAkC,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qCAAqC,CAAC;AACpD,cAAc,sCAAsC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,8BAA8B,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kDAAkD,CAAC;AACvF,OAAO,EAAE,6BAA6B,EAAE,MAAM,mEAAmE,CAAC;AAClH,cAAc,kCAAkC,CAAC;AACjD,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qCAAqC,CAAC;AACpD,cAAc,sCAAsC,CAAC"}
package/dest/index.js CHANGED
@@ -8,6 +8,7 @@ export * from './sparse_tree/sparse_tree.js';
8
8
  export { StandardIndexedTree } from './standard_indexed_tree/standard_indexed_tree.js';
9
9
  export { StandardIndexedTreeWithAppend } from './standard_indexed_tree/test/standard_indexed_tree_with_append.js';
10
10
  export * from './standard_tree/standard_tree.js';
11
+ export * from './unbalanced_tree.js';
11
12
  export { INITIAL_LEAF, getTreeMeta } from './tree_base.js';
12
13
  export { newTree } from './new_tree.js';
13
14
  export { loadTree } from './load_tree.js';
@@ -15,4 +16,4 @@ export * from './snapshots/snapshot_builder.js';
15
16
  export * from './snapshots/full_snapshot.js';
16
17
  export * from './snapshots/append_only_snapshot.js';
17
18
  export * from './snapshots/indexed_tree_snapshot.js';
18
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxrQ0FBa0MsQ0FBQztBQUNqRCxjQUFjLDhCQUE4QixDQUFDO0FBQzdDLGNBQWMsNkJBQTZCLENBQUM7QUFDNUMsY0FBYyxrQ0FBa0MsQ0FBQztBQUNqRCxjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLDhCQUE4QixDQUFDO0FBQzdDLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGtEQUFrRCxDQUFDO0FBQ3ZGLE9BQU8sRUFBRSw2QkFBNkIsRUFBRSxNQUFNLG1FQUFtRSxDQUFDO0FBQ2xILGNBQWMsa0NBQWtDLENBQUM7QUFDakQsT0FBTyxFQUFFLFlBQVksRUFBRSxXQUFXLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzRCxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUMxQyxjQUFjLGlDQUFpQyxDQUFDO0FBQ2hELGNBQWMsOEJBQThCLENBQUM7QUFDN0MsY0FBYyxxQ0FBcUMsQ0FBQztBQUNwRCxjQUFjLHNDQUFzQyxDQUFDIn0=
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxrQ0FBa0MsQ0FBQztBQUNqRCxjQUFjLDhCQUE4QixDQUFDO0FBQzdDLGNBQWMsNkJBQTZCLENBQUM7QUFDNUMsY0FBYyxrQ0FBa0MsQ0FBQztBQUNqRCxjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLDhCQUE4QixDQUFDO0FBQzdDLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGtEQUFrRCxDQUFDO0FBQ3ZGLE9BQU8sRUFBRSw2QkFBNkIsRUFBRSxNQUFNLG1FQUFtRSxDQUFDO0FBQ2xILGNBQWMsa0NBQWtDLENBQUM7QUFDakQsY0FBYyxzQkFBc0IsQ0FBQztBQUNyQyxPQUFPLEVBQUUsWUFBWSxFQUFFLFdBQVcsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzNELE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDeEMsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzFDLGNBQWMsaUNBQWlDLENBQUM7QUFDaEQsY0FBYyw4QkFBOEIsQ0FBQztBQUM3QyxjQUFjLHFDQUFxQyxDQUFDO0FBQ3BELGNBQWMsc0NBQXNDLENBQUMifQ==
@@ -0,0 +1,108 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import { SiblingPath } from '@aztec/circuit-types';
3
+ import { type Bufferable, type FromBuffer } from '@aztec/foundation/serialize';
4
+ import { type Hasher } from '@aztec/types/interfaces';
5
+ import { HasherWithStats } from './hasher_with_stats.js';
6
+ import { type MerkleTree } from './interfaces/merkle_tree.js';
7
+ /**
8
+ * An ephemeral unbalanced Merkle tree implementation.
9
+ * Follows the rollup implementation which greedily hashes pairs of nodes up the tree.
10
+ * Remaining rightmost nodes are shifted up until they can be paired. See proving-state.ts -> findMergeLevel.
11
+ */
12
+ export declare class UnbalancedTree<T extends Bufferable = Buffer> implements MerkleTree<T> {
13
+ private name;
14
+ private maxDepth;
15
+ protected deserializer: FromBuffer<T>;
16
+ private cache;
17
+ private valueCache;
18
+ protected size: bigint;
19
+ protected readonly maxIndex: bigint;
20
+ protected hasher: HasherWithStats;
21
+ root: Buffer;
22
+ constructor(hasher: Hasher, name: string, maxDepth: number, deserializer: FromBuffer<T>);
23
+ /**
24
+ * Returns the root of the tree.
25
+ * @returns The root of the tree.
26
+ */
27
+ getRoot(): Buffer;
28
+ /**
29
+ * Returns the number of leaves in the tree.
30
+ * @returns The number of leaves in the tree.
31
+ */
32
+ getNumLeaves(): bigint;
33
+ /**
34
+ * Returns the max depth of the tree.
35
+ * @returns The max depth of the tree.
36
+ */
37
+ getDepth(): number;
38
+ /**
39
+ * @remark A wonky tree is (currently) only ever ephemeral, so we don't use any db to commit to.
40
+ * The fn must exist to implement MerkleTree however.
41
+ */
42
+ commit(): Promise<void>;
43
+ /**
44
+ * Rolls back the not-yet-committed changes.
45
+ * @returns Empty promise.
46
+ */
47
+ rollback(): Promise<void>;
48
+ /**
49
+ * Clears the cache.
50
+ */
51
+ private clearCache;
52
+ /**
53
+ * @remark A wonky tree can validly have duplicate indices:
54
+ * e.g. 001 (index 1 at level 3) and 01 (index 1 at level 2)
55
+ * So this function cannot reliably give the expected leaf value.
56
+ * We cannot add level as an input as its based on the MerkleTree class's function.
57
+ */
58
+ getLeafValue(_index: bigint): undefined;
59
+ /**
60
+ * Returns the index of a leaf given its value, or undefined if no leaf with that value is found.
61
+ * @param leaf - The leaf value to look for.
62
+ * @returns The index of the first leaf found with a given value (undefined if not found).
63
+ * @remark This is NOT the index as inserted, but the index which will be used to calculate path structure.
64
+ */
65
+ findLeafIndex(value: T): bigint | undefined;
66
+ /**
67
+ * Returns the first index containing a leaf value after `startIndex`.
68
+ * @param value - The leaf value to look for.
69
+ * @param startIndex - The index to start searching from.
70
+ * @returns The index of the first leaf found with a given value (undefined if not found).
71
+ * @remark This is not really used for a wonky tree, but required to implement MerkleTree.
72
+ */
73
+ findLeafIndexAfter(value: T, startIndex: bigint): bigint | undefined;
74
+ /**
75
+ * Returns the node at the given level and index
76
+ * @param level - The level of the element (root is at level 0).
77
+ * @param index - The index of the element.
78
+ * @returns Leaf or node value, or undefined.
79
+ */
80
+ getNode(level: number, index: bigint): Buffer | undefined;
81
+ /**
82
+ * Returns a sibling path for the element at the given index.
83
+ * @param value - The value of the element.
84
+ * @returns A sibling path for the element.
85
+ * Note: The sibling path is an array of sibling hashes, with the lowest hash (leaf hash) first, and the highest hash last.
86
+ */
87
+ getSiblingPath<N extends number>(value: bigint): Promise<SiblingPath<N>>;
88
+ /**
89
+ * Appends the given leaves to the tree.
90
+ * @param leaves - The leaves to append.
91
+ * @returns Empty promise.
92
+ */
93
+ appendLeaves(leaves: T[]): Promise<void>;
94
+ /**
95
+ * Calculates root while adding leaves and nodes to the cache.
96
+ * @param leaves - The leaves to append.
97
+ * @returns Resulting root of the tree.
98
+ */
99
+ private batchInsert;
100
+ /**
101
+ * Stores the given node in the cache.
102
+ * @param value - The value to store.
103
+ * @param depth - The depth of the node in the full tree.
104
+ * @param index - The index of the node at the given depth.
105
+ */
106
+ private storeNode;
107
+ }
108
+ //# sourceMappingURL=unbalanced_tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unbalanced_tree.d.ts","sourceRoot":"","sources":["../src/unbalanced_tree.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAqB,MAAM,6BAA6B,CAAC;AAClG,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAI9D;;;;GAIG;AACH,qBAAa,cAAc,CAAC,CAAC,SAAS,UAAU,GAAG,MAAM,CAAE,YAAW,UAAU,CAAC,CAAC,CAAC;IAa/E,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,QAAQ;IAChB,SAAS,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;IAbvC,OAAO,CAAC,KAAK,CAAiC;IAE9C,OAAO,CAAC,UAAU,CAAiC;IACnD,SAAS,CAAC,IAAI,EAAE,MAAM,CAAM;IAC5B,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAEpC,SAAS,CAAC,MAAM,EAAE,eAAe,CAAC;IAClC,IAAI,EAAE,MAAM,CAAoB;gBAG9B,MAAM,EAAE,MAAM,EACN,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAU,EAClB,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;IAMvC;;;OAGG;IACI,OAAO,IAAI,MAAM;IAIxB;;;OAGG;IACI,YAAY;IAInB;;;OAGG;IACI,QAAQ,IAAI,MAAM;IAIzB;;;OAGG;IACI,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAK9B;;;OAGG;IACI,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAKhC;;OAEG;IACH,OAAO,CAAC,UAAU;IAKlB;;;;;OAKG;IACI,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS;IAI9C;;;;;OAKG;IACI,aAAa,CAAC,KAAK,EAAE,CAAC,GAAG,MAAM,GAAG,SAAS;IAMlD;;;;;;OAMG;IACI,kBAAkB,CAAC,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQ3E;;;;;OAKG;IACI,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAYhE;;;;;OAKG;IACI,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAgB/E;;;;OAIG;IACI,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAc/C;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAyCnB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;CAKlB"}
@@ -0,0 +1,212 @@
1
+ import { SiblingPath } from '@aztec/circuit-types';
2
+ import { serializeToBuffer } from '@aztec/foundation/serialize';
3
+ import { HasherWithStats } from './hasher_with_stats.js';
4
+ const indexToKeyHash = (name, level, index) => `${name}:${level}:${index}`;
5
+ /**
6
+ * An ephemeral unbalanced Merkle tree implementation.
7
+ * Follows the rollup implementation which greedily hashes pairs of nodes up the tree.
8
+ * Remaining rightmost nodes are shifted up until they can be paired. See proving-state.ts -> findMergeLevel.
9
+ */
10
+ export class UnbalancedTree {
11
+ constructor(hasher, name, maxDepth = 0, deserializer) {
12
+ this.name = name;
13
+ this.maxDepth = maxDepth;
14
+ this.deserializer = deserializer;
15
+ // This map stores index and depth -> value
16
+ this.cache = {};
17
+ // This map stores value -> index and depth, since we have variable depth
18
+ this.valueCache = {};
19
+ this.size = 0n;
20
+ this.root = Buffer.alloc(32);
21
+ this.hasher = new HasherWithStats(hasher);
22
+ this.maxIndex = 2n ** BigInt(this.maxDepth) - 1n;
23
+ }
24
+ /**
25
+ * Returns the root of the tree.
26
+ * @returns The root of the tree.
27
+ */
28
+ getRoot() {
29
+ return this.root;
30
+ }
31
+ /**
32
+ * Returns the number of leaves in the tree.
33
+ * @returns The number of leaves in the tree.
34
+ */
35
+ getNumLeaves() {
36
+ return this.size;
37
+ }
38
+ /**
39
+ * Returns the max depth of the tree.
40
+ * @returns The max depth of the tree.
41
+ */
42
+ getDepth() {
43
+ return this.maxDepth;
44
+ }
45
+ /**
46
+ * @remark A wonky tree is (currently) only ever ephemeral, so we don't use any db to commit to.
47
+ * The fn must exist to implement MerkleTree however.
48
+ */
49
+ commit() {
50
+ throw new Error("Unsupported function - cannot commit on an unbalanced tree as it's always ephemeral.");
51
+ return Promise.resolve();
52
+ }
53
+ /**
54
+ * Rolls back the not-yet-committed changes.
55
+ * @returns Empty promise.
56
+ */
57
+ rollback() {
58
+ this.clearCache();
59
+ return Promise.resolve();
60
+ }
61
+ /**
62
+ * Clears the cache.
63
+ */
64
+ clearCache() {
65
+ this.cache = {};
66
+ this.size = 0n;
67
+ }
68
+ /**
69
+ * @remark A wonky tree can validly have duplicate indices:
70
+ * e.g. 001 (index 1 at level 3) and 01 (index 1 at level 2)
71
+ * So this function cannot reliably give the expected leaf value.
72
+ * We cannot add level as an input as its based on the MerkleTree class's function.
73
+ */
74
+ getLeafValue(_index) {
75
+ throw new Error('Unsupported function - cannot get leaf value from an index in an unbalanced tree.');
76
+ }
77
+ /**
78
+ * Returns the index of a leaf given its value, or undefined if no leaf with that value is found.
79
+ * @param leaf - The leaf value to look for.
80
+ * @returns The index of the first leaf found with a given value (undefined if not found).
81
+ * @remark This is NOT the index as inserted, but the index which will be used to calculate path structure.
82
+ */
83
+ findLeafIndex(value) {
84
+ const key = this.valueCache[serializeToBuffer(value).toString('hex')];
85
+ const [, , index] = key.split(':');
86
+ return BigInt(index);
87
+ }
88
+ /**
89
+ * Returns the first index containing a leaf value after `startIndex`.
90
+ * @param value - The leaf value to look for.
91
+ * @param startIndex - The index to start searching from.
92
+ * @returns The index of the first leaf found with a given value (undefined if not found).
93
+ * @remark This is not really used for a wonky tree, but required to implement MerkleTree.
94
+ */
95
+ findLeafIndexAfter(value, startIndex) {
96
+ const index = this.findLeafIndex(value);
97
+ if (!index || index < startIndex) {
98
+ return undefined;
99
+ }
100
+ return index;
101
+ }
102
+ /**
103
+ * Returns the node at the given level and index
104
+ * @param level - The level of the element (root is at level 0).
105
+ * @param index - The index of the element.
106
+ * @returns Leaf or node value, or undefined.
107
+ */
108
+ getNode(level, index) {
109
+ if (level < 0 || level > this.maxDepth) {
110
+ throw Error('Invalid level: ' + level);
111
+ }
112
+ if (index < 0 || index >= this.maxIndex) {
113
+ throw Error('Invalid index: ' + index);
114
+ }
115
+ return this.cache[indexToKeyHash(this.name, level, index)];
116
+ }
117
+ /**
118
+ * Returns a sibling path for the element at the given index.
119
+ * @param value - The value of the element.
120
+ * @returns A sibling path for the element.
121
+ * Note: The sibling path is an array of sibling hashes, with the lowest hash (leaf hash) first, and the highest hash last.
122
+ */
123
+ getSiblingPath(value) {
124
+ const path = [];
125
+ const [, depth, _index] = this.valueCache[serializeToBuffer(value).toString('hex')].split(':');
126
+ let level = parseInt(depth, 10);
127
+ let index = BigInt(_index);
128
+ while (level > 0) {
129
+ const isRight = index & 0x01n;
130
+ const key = indexToKeyHash(this.name, level, isRight ? index - 1n : index + 1n);
131
+ const sibling = this.cache[key];
132
+ path.push(sibling);
133
+ level -= 1;
134
+ index >>= 1n;
135
+ }
136
+ return Promise.resolve(new SiblingPath(parseInt(depth, 10), path));
137
+ }
138
+ /**
139
+ * Appends the given leaves to the tree.
140
+ * @param leaves - The leaves to append.
141
+ * @returns Empty promise.
142
+ */
143
+ appendLeaves(leaves) {
144
+ this.hasher.reset();
145
+ if (this.size != BigInt(0)) {
146
+ throw Error(`Can't re-append to an unbalanced tree. Current has ${this.size} leaves.`);
147
+ }
148
+ if (this.size + BigInt(leaves.length) - 1n > this.maxIndex) {
149
+ throw Error(`Can't append beyond max index. Max index: ${this.maxIndex}`);
150
+ }
151
+ const root = this.batchInsert(leaves);
152
+ this.root = root;
153
+ return Promise.resolve();
154
+ }
155
+ /**
156
+ * Calculates root while adding leaves and nodes to the cache.
157
+ * @param leaves - The leaves to append.
158
+ * @returns Resulting root of the tree.
159
+ */
160
+ batchInsert(_leaves) {
161
+ // If we have an even number of leaves, hash them all in pairs
162
+ // Otherwise, store the final leaf to be shifted up to the next odd sized level
163
+ let [layerWidth, nodeToShift] = _leaves.length & 1
164
+ ? [_leaves.length - 1, serializeToBuffer(_leaves[_leaves.length - 1])]
165
+ : [_leaves.length, Buffer.alloc(0)];
166
+ // Allocate this layer's leaves and init the next layer up
167
+ let thisLayer = _leaves.slice(0, layerWidth).map(l => serializeToBuffer(l));
168
+ let nextLayer = [];
169
+ // Store the bottom level leaves
170
+ thisLayer.forEach((leaf, i) => this.storeNode(leaf, this.maxDepth, BigInt(i)));
171
+ for (let i = 0; i < this.maxDepth; i++) {
172
+ for (let j = 0; j < layerWidth; j += 2) {
173
+ // Store the hash of each pair one layer up
174
+ nextLayer[j / 2] = this.hasher.hash(serializeToBuffer(thisLayer[j]), serializeToBuffer(thisLayer[j + 1]));
175
+ this.storeNode(nextLayer[j / 2], this.maxDepth - i - 1, BigInt(j >> 1));
176
+ }
177
+ layerWidth /= 2;
178
+ if (layerWidth & 1) {
179
+ if (nodeToShift.length) {
180
+ // If the next layer has odd length, and we have a node that needs to be shifted up, add it here
181
+ nextLayer.push(serializeToBuffer(nodeToShift));
182
+ this.storeNode(nodeToShift, this.maxDepth - i - 1, BigInt((layerWidth * 2) >> 1));
183
+ layerWidth += 1;
184
+ nodeToShift = Buffer.alloc(0);
185
+ }
186
+ else {
187
+ // If we don't have a node waiting to be shifted, store the next layer's final node to be shifted
188
+ layerWidth -= 1;
189
+ nodeToShift = nextLayer[layerWidth];
190
+ }
191
+ }
192
+ // reset the layers
193
+ thisLayer = nextLayer;
194
+ nextLayer = [];
195
+ }
196
+ this.size += BigInt(_leaves.length);
197
+ // return the root
198
+ return thisLayer[0];
199
+ }
200
+ /**
201
+ * Stores the given node in the cache.
202
+ * @param value - The value to store.
203
+ * @param depth - The depth of the node in the full tree.
204
+ * @param index - The index of the node at the given depth.
205
+ */
206
+ storeNode(value, depth, index) {
207
+ const key = indexToKeyHash(this.name, depth, index);
208
+ this.cache[key] = serializeToBuffer(value);
209
+ this.valueCache[serializeToBuffer(value).toString('hex')] = key;
210
+ }
211
+ }
212
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidW5iYWxhbmNlZF90cmVlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3VuYmFsYW5jZWRfdHJlZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDbkQsT0FBTyxFQUFvQyxpQkFBaUIsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBR2xHLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUd6RCxNQUFNLGNBQWMsR0FBRyxDQUFDLElBQVksRUFBRSxLQUFhLEVBQUUsS0FBYSxFQUFFLEVBQUUsQ0FBQyxHQUFHLElBQUksSUFBSSxLQUFLLElBQUksS0FBSyxFQUFFLENBQUM7QUFFbkc7Ozs7R0FJRztBQUNILE1BQU0sT0FBTyxjQUFjO0lBV3pCLFlBQ0UsTUFBYyxFQUNOLElBQVksRUFDWixXQUFtQixDQUFDLEVBQ2xCLFlBQTJCO1FBRjdCLFNBQUksR0FBSixJQUFJLENBQVE7UUFDWixhQUFRLEdBQVIsUUFBUSxDQUFZO1FBQ2xCLGlCQUFZLEdBQVosWUFBWSxDQUFlO1FBZHZDLDJDQUEyQztRQUNuQyxVQUFLLEdBQThCLEVBQUUsQ0FBQztRQUM5Qyx5RUFBeUU7UUFDakUsZUFBVSxHQUE4QixFQUFFLENBQUM7UUFDekMsU0FBSSxHQUFXLEVBQUUsQ0FBQztRQUk1QixTQUFJLEdBQVcsTUFBTSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsQ0FBQztRQVE5QixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksZUFBZSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzFDLElBQUksQ0FBQyxRQUFRLEdBQUcsRUFBRSxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsRUFBRSxDQUFDO0lBQ25ELENBQUM7SUFFRDs7O09BR0c7SUFDSSxPQUFPO1FBQ1osT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFDO0lBQ25CLENBQUM7SUFFRDs7O09BR0c7SUFDSSxZQUFZO1FBQ2pCLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQztJQUNuQixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksUUFBUTtRQUNiLE9BQU8sSUFBSSxDQUFDLFFBQVEsQ0FBQztJQUN2QixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksTUFBTTtRQUNYLE1BQU0sSUFBSSxLQUFLLENBQUMsc0ZBQXNGLENBQUMsQ0FBQztRQUN4RyxPQUFPLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUMzQixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksUUFBUTtRQUNiLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUNsQixPQUFPLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUMzQixDQUFDO0lBRUQ7O09BRUc7SUFDSyxVQUFVO1FBQ2hCLElBQUksQ0FBQyxLQUFLLEdBQUcsRUFBRSxDQUFDO1FBQ2hCLElBQUksQ0FBQyxJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQ2pCLENBQUM7SUFFRDs7Ozs7T0FLRztJQUNJLFlBQVksQ0FBQyxNQUFjO1FBQ2hDLE1BQU0sSUFBSSxLQUFLLENBQUMsbUZBQW1GLENBQUMsQ0FBQztJQUN2RyxDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSSxhQUFhLENBQUMsS0FBUTtRQUMzQixNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLGlCQUFpQixDQUFDLEtBQUssQ0FBQyxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO1FBQ3RFLE1BQU0sQ0FBQyxFQUFFLEFBQUQsRUFBRyxLQUFLLENBQUMsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ25DLE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3ZCLENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSSxrQkFBa0IsQ0FBQyxLQUFRLEVBQUUsVUFBa0I7UUFDcEQsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN4QyxJQUFJLENBQUMsS0FBSyxJQUFJLEtBQUssR0FBRyxVQUFVLEVBQUUsQ0FBQztZQUNqQyxPQUFPLFNBQVMsQ0FBQztRQUNuQixDQUFDO1FBQ0QsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSSxPQUFPLENBQUMsS0FBYSxFQUFFLEtBQWE7UUFDekMsSUFBSSxLQUFLLEdBQUcsQ0FBQyxJQUFJLEtBQUssR0FBRyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDdkMsTUFBTSxLQUFLLENBQUMsaUJBQWlCLEdBQUcsS0FBSyxDQUFDLENBQUM7UUFDekMsQ0FBQztRQUVELElBQUksS0FBSyxHQUFHLENBQUMsSUFBSSxLQUFLLElBQUksSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ3hDLE1BQU0sS0FBSyxDQUFDLGlCQUFpQixHQUFHLEtBQUssQ0FBQyxDQUFDO1FBQ3pDLENBQUM7UUFFRCxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDN0QsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0ksY0FBYyxDQUFtQixLQUFhO1FBQ25ELE1BQU0sSUFBSSxHQUFhLEVBQUUsQ0FBQztRQUMxQixNQUFNLENBQUMsRUFBRSxLQUFLLEVBQUUsTUFBTSxDQUFDLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxpQkFBaUIsQ0FBQyxLQUFLLENBQUMsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDL0YsSUFBSSxLQUFLLEdBQUcsUUFBUSxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNoQyxJQUFJLEtBQUssR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDM0IsT0FBTyxLQUFLLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDakIsTUFBTSxPQUFPLEdBQUcsS0FBSyxHQUFHLEtBQUssQ0FBQztZQUM5QixNQUFNLEdBQUcsR0FBRyxjQUFjLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQyxLQUFLLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLEdBQUcsRUFBRSxDQUFDLENBQUM7WUFDaEYsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUNoQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQ25CLEtBQUssSUFBSSxDQUFDLENBQUM7WUFDWCxLQUFLLEtBQUssRUFBRSxDQUFDO1FBQ2YsQ0FBQztRQUNELE9BQU8sT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLFdBQVcsQ0FBSSxRQUFRLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBTSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7SUFDN0UsQ0FBQztJQUVEOzs7O09BSUc7SUFDSSxZQUFZLENBQUMsTUFBVztRQUM3QixJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ3BCLElBQUksSUFBSSxDQUFDLElBQUksSUFBSSxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUMzQixNQUFNLEtBQUssQ0FBQyxzREFBc0QsSUFBSSxDQUFDLElBQUksVUFBVSxDQUFDLENBQUM7UUFDekYsQ0FBQztRQUNELElBQUksSUFBSSxDQUFDLElBQUksR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDM0QsTUFBTSxLQUFLLENBQUMsNkNBQTZDLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBQzVFLENBQUM7UUFDRCxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3RDLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO1FBRWpCLE9BQU8sT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQzNCLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ssV0FBVyxDQUFDLE9BQVk7UUFDOUIsOERBQThEO1FBQzlELCtFQUErRTtRQUMvRSxJQUFJLENBQUMsVUFBVSxFQUFFLFdBQVcsQ0FBQyxHQUMzQixPQUFPLENBQUMsTUFBTSxHQUFHLENBQUM7WUFDaEIsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsaUJBQWlCLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUN0RSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUN4QywwREFBMEQ7UUFDMUQsSUFBSSxTQUFTLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsVUFBVSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUM1RSxJQUFJLFNBQVMsR0FBRyxFQUFFLENBQUM7UUFDbkIsZ0NBQWdDO1FBQ2hDLFNBQVMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDL0UsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztZQUN2QyxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsVUFBVSxFQUFFLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztnQkFDdkMsMkNBQTJDO2dCQUMzQyxTQUFTLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLGlCQUFpQixDQUFDLFNBQVMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUMxRyxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsSUFBSSxDQUFDLFFBQVEsR0FBRyxDQUFDLEdBQUcsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUMxRSxDQUFDO1lBQ0QsVUFBVSxJQUFJLENBQUMsQ0FBQztZQUNoQixJQUFJLFVBQVUsR0FBRyxDQUFDLEVBQUUsQ0FBQztnQkFDbkIsSUFBSSxXQUFXLENBQUMsTUFBTSxFQUFFLENBQUM7b0JBQ3ZCLGdHQUFnRztvQkFDaEcsU0FBUyxDQUFDLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDO29CQUMvQyxJQUFJLENBQUMsU0FBUyxDQUFDLFdBQVcsRUFBRSxJQUFJLENBQUMsUUFBUSxHQUFHLENBQUMsR0FBRyxDQUFDLEVBQUUsTUFBTSxDQUFDLENBQUMsVUFBVSxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7b0JBQ2xGLFVBQVUsSUFBSSxDQUFDLENBQUM7b0JBQ2hCLFdBQVcsR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUNoQyxDQUFDO3FCQUFNLENBQUM7b0JBQ04saUdBQWlHO29CQUNqRyxVQUFVLElBQUksQ0FBQyxDQUFDO29CQUNoQixXQUFXLEdBQUcsU0FBUyxDQUFDLFVBQVUsQ0FBQyxDQUFDO2dCQUN0QyxDQUFDO1lBQ0gsQ0FBQztZQUNELG1CQUFtQjtZQUNuQixTQUFTLEdBQUcsU0FBUyxDQUFDO1lBQ3RCLFNBQVMsR0FBRyxFQUFFLENBQUM7UUFDakIsQ0FBQztRQUNELElBQUksQ0FBQyxJQUFJLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNwQyxrQkFBa0I7UUFDbEIsT0FBTyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDdEIsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0ssU0FBUyxDQUFDLEtBQWlCLEVBQUUsS0FBYSxFQUFFLEtBQWE7UUFDL0QsTUFBTSxHQUFHLEdBQUcsY0FBYyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQ3BELElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsaUJBQWlCLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDM0MsSUFBSSxDQUFDLFVBQVUsQ0FBQyxpQkFBaUIsQ0FBQyxLQUFLLENBQUMsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxHQUFHLENBQUM7SUFDbEUsQ0FBQztDQUNGIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/merkle-tree",
3
- "version": "0.44.0",
3
+ "version": "0.45.1",
4
4
  "type": "module",
5
5
  "exports": "./dest/index.js",
6
6
  "typedocOptions": {
@@ -55,10 +55,10 @@
55
55
  ]
56
56
  },
57
57
  "dependencies": {
58
- "@aztec/circuit-types": "0.44.0",
59
- "@aztec/foundation": "0.44.0",
60
- "@aztec/kv-store": "0.44.0",
61
- "@aztec/types": "0.44.0",
58
+ "@aztec/circuit-types": "0.45.1",
59
+ "@aztec/foundation": "0.45.1",
60
+ "@aztec/kv-store": "0.45.1",
61
+ "@aztec/types": "0.45.1",
62
62
  "sha256": "^0.2.0",
63
63
  "tslib": "^2.4.0"
64
64
  },
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ export * from './sparse_tree/sparse_tree.js';
8
8
  export { StandardIndexedTree } from './standard_indexed_tree/standard_indexed_tree.js';
9
9
  export { StandardIndexedTreeWithAppend } from './standard_indexed_tree/test/standard_indexed_tree_with_append.js';
10
10
  export * from './standard_tree/standard_tree.js';
11
+ export * from './unbalanced_tree.js';
11
12
  export { INITIAL_LEAF, getTreeMeta } from './tree_base.js';
12
13
  export { newTree } from './new_tree.js';
13
14
  export { loadTree } from './load_tree.js';
@@ -0,0 +1,239 @@
1
+ import { SiblingPath } from '@aztec/circuit-types';
2
+ import { type Bufferable, type FromBuffer, serializeToBuffer } from '@aztec/foundation/serialize';
3
+ import { type Hasher } from '@aztec/types/interfaces';
4
+
5
+ import { HasherWithStats } from './hasher_with_stats.js';
6
+ import { type MerkleTree } from './interfaces/merkle_tree.js';
7
+
8
+ const indexToKeyHash = (name: string, level: number, index: bigint) => `${name}:${level}:${index}`;
9
+
10
+ /**
11
+ * An ephemeral unbalanced Merkle tree implementation.
12
+ * Follows the rollup implementation which greedily hashes pairs of nodes up the tree.
13
+ * Remaining rightmost nodes are shifted up until they can be paired. See proving-state.ts -> findMergeLevel.
14
+ */
15
+ export class UnbalancedTree<T extends Bufferable = Buffer> implements MerkleTree<T> {
16
+ // This map stores index and depth -> value
17
+ private cache: { [key: string]: Buffer } = {};
18
+ // This map stores value -> index and depth, since we have variable depth
19
+ private valueCache: { [key: string]: string } = {};
20
+ protected size: bigint = 0n;
21
+ protected readonly maxIndex: bigint;
22
+
23
+ protected hasher: HasherWithStats;
24
+ root: Buffer = Buffer.alloc(32);
25
+
26
+ public constructor(
27
+ hasher: Hasher,
28
+ private name: string,
29
+ private maxDepth: number = 0,
30
+ protected deserializer: FromBuffer<T>,
31
+ ) {
32
+ this.hasher = new HasherWithStats(hasher);
33
+ this.maxIndex = 2n ** BigInt(this.maxDepth) - 1n;
34
+ }
35
+
36
+ /**
37
+ * Returns the root of the tree.
38
+ * @returns The root of the tree.
39
+ */
40
+ public getRoot(): Buffer {
41
+ return this.root;
42
+ }
43
+
44
+ /**
45
+ * Returns the number of leaves in the tree.
46
+ * @returns The number of leaves in the tree.
47
+ */
48
+ public getNumLeaves() {
49
+ return this.size;
50
+ }
51
+
52
+ /**
53
+ * Returns the max depth of the tree.
54
+ * @returns The max depth of the tree.
55
+ */
56
+ public getDepth(): number {
57
+ return this.maxDepth;
58
+ }
59
+
60
+ /**
61
+ * @remark A wonky tree is (currently) only ever ephemeral, so we don't use any db to commit to.
62
+ * The fn must exist to implement MerkleTree however.
63
+ */
64
+ public commit(): Promise<void> {
65
+ throw new Error("Unsupported function - cannot commit on an unbalanced tree as it's always ephemeral.");
66
+ return Promise.resolve();
67
+ }
68
+
69
+ /**
70
+ * Rolls back the not-yet-committed changes.
71
+ * @returns Empty promise.
72
+ */
73
+ public rollback(): Promise<void> {
74
+ this.clearCache();
75
+ return Promise.resolve();
76
+ }
77
+
78
+ /**
79
+ * Clears the cache.
80
+ */
81
+ private clearCache() {
82
+ this.cache = {};
83
+ this.size = 0n;
84
+ }
85
+
86
+ /**
87
+ * @remark A wonky tree can validly have duplicate indices:
88
+ * e.g. 001 (index 1 at level 3) and 01 (index 1 at level 2)
89
+ * So this function cannot reliably give the expected leaf value.
90
+ * We cannot add level as an input as its based on the MerkleTree class's function.
91
+ */
92
+ public getLeafValue(_index: bigint): undefined {
93
+ throw new Error('Unsupported function - cannot get leaf value from an index in an unbalanced tree.');
94
+ }
95
+
96
+ /**
97
+ * Returns the index of a leaf given its value, or undefined if no leaf with that value is found.
98
+ * @param leaf - The leaf value to look for.
99
+ * @returns The index of the first leaf found with a given value (undefined if not found).
100
+ * @remark This is NOT the index as inserted, but the index which will be used to calculate path structure.
101
+ */
102
+ public findLeafIndex(value: T): bigint | undefined {
103
+ const key = this.valueCache[serializeToBuffer(value).toString('hex')];
104
+ const [, , index] = key.split(':');
105
+ return BigInt(index);
106
+ }
107
+
108
+ /**
109
+ * Returns the first index containing a leaf value after `startIndex`.
110
+ * @param value - The leaf value to look for.
111
+ * @param startIndex - The index to start searching from.
112
+ * @returns The index of the first leaf found with a given value (undefined if not found).
113
+ * @remark This is not really used for a wonky tree, but required to implement MerkleTree.
114
+ */
115
+ public findLeafIndexAfter(value: T, startIndex: bigint): bigint | undefined {
116
+ const index = this.findLeafIndex(value);
117
+ if (!index || index < startIndex) {
118
+ return undefined;
119
+ }
120
+ return index;
121
+ }
122
+
123
+ /**
124
+ * Returns the node at the given level and index
125
+ * @param level - The level of the element (root is at level 0).
126
+ * @param index - The index of the element.
127
+ * @returns Leaf or node value, or undefined.
128
+ */
129
+ public getNode(level: number, index: bigint): Buffer | undefined {
130
+ if (level < 0 || level > this.maxDepth) {
131
+ throw Error('Invalid level: ' + level);
132
+ }
133
+
134
+ if (index < 0 || index >= this.maxIndex) {
135
+ throw Error('Invalid index: ' + index);
136
+ }
137
+
138
+ return this.cache[indexToKeyHash(this.name, level, index)];
139
+ }
140
+
141
+ /**
142
+ * Returns a sibling path for the element at the given index.
143
+ * @param value - The value of the element.
144
+ * @returns A sibling path for the element.
145
+ * Note: The sibling path is an array of sibling hashes, with the lowest hash (leaf hash) first, and the highest hash last.
146
+ */
147
+ public getSiblingPath<N extends number>(value: bigint): Promise<SiblingPath<N>> {
148
+ const path: Buffer[] = [];
149
+ const [, depth, _index] = this.valueCache[serializeToBuffer(value).toString('hex')].split(':');
150
+ let level = parseInt(depth, 10);
151
+ let index = BigInt(_index);
152
+ while (level > 0) {
153
+ const isRight = index & 0x01n;
154
+ const key = indexToKeyHash(this.name, level, isRight ? index - 1n : index + 1n);
155
+ const sibling = this.cache[key];
156
+ path.push(sibling);
157
+ level -= 1;
158
+ index >>= 1n;
159
+ }
160
+ return Promise.resolve(new SiblingPath<N>(parseInt(depth, 10) as N, path));
161
+ }
162
+
163
+ /**
164
+ * Appends the given leaves to the tree.
165
+ * @param leaves - The leaves to append.
166
+ * @returns Empty promise.
167
+ */
168
+ public appendLeaves(leaves: T[]): Promise<void> {
169
+ this.hasher.reset();
170
+ if (this.size != BigInt(0)) {
171
+ throw Error(`Can't re-append to an unbalanced tree. Current has ${this.size} leaves.`);
172
+ }
173
+ if (this.size + BigInt(leaves.length) - 1n > this.maxIndex) {
174
+ throw Error(`Can't append beyond max index. Max index: ${this.maxIndex}`);
175
+ }
176
+ const root = this.batchInsert(leaves);
177
+ this.root = root;
178
+
179
+ return Promise.resolve();
180
+ }
181
+
182
+ /**
183
+ * Calculates root while adding leaves and nodes to the cache.
184
+ * @param leaves - The leaves to append.
185
+ * @returns Resulting root of the tree.
186
+ */
187
+ private batchInsert(_leaves: T[]): Buffer {
188
+ // If we have an even number of leaves, hash them all in pairs
189
+ // Otherwise, store the final leaf to be shifted up to the next odd sized level
190
+ let [layerWidth, nodeToShift] =
191
+ _leaves.length & 1
192
+ ? [_leaves.length - 1, serializeToBuffer(_leaves[_leaves.length - 1])]
193
+ : [_leaves.length, Buffer.alloc(0)];
194
+ // Allocate this layer's leaves and init the next layer up
195
+ let thisLayer = _leaves.slice(0, layerWidth).map(l => serializeToBuffer(l));
196
+ let nextLayer = [];
197
+ // Store the bottom level leaves
198
+ thisLayer.forEach((leaf, i) => this.storeNode(leaf, this.maxDepth, BigInt(i)));
199
+ for (let i = 0; i < this.maxDepth; i++) {
200
+ for (let j = 0; j < layerWidth; j += 2) {
201
+ // Store the hash of each pair one layer up
202
+ nextLayer[j / 2] = this.hasher.hash(serializeToBuffer(thisLayer[j]), serializeToBuffer(thisLayer[j + 1]));
203
+ this.storeNode(nextLayer[j / 2], this.maxDepth - i - 1, BigInt(j >> 1));
204
+ }
205
+ layerWidth /= 2;
206
+ if (layerWidth & 1) {
207
+ if (nodeToShift.length) {
208
+ // If the next layer has odd length, and we have a node that needs to be shifted up, add it here
209
+ nextLayer.push(serializeToBuffer(nodeToShift));
210
+ this.storeNode(nodeToShift, this.maxDepth - i - 1, BigInt((layerWidth * 2) >> 1));
211
+ layerWidth += 1;
212
+ nodeToShift = Buffer.alloc(0);
213
+ } else {
214
+ // If we don't have a node waiting to be shifted, store the next layer's final node to be shifted
215
+ layerWidth -= 1;
216
+ nodeToShift = nextLayer[layerWidth];
217
+ }
218
+ }
219
+ // reset the layers
220
+ thisLayer = nextLayer;
221
+ nextLayer = [];
222
+ }
223
+ this.size += BigInt(_leaves.length);
224
+ // return the root
225
+ return thisLayer[0];
226
+ }
227
+
228
+ /**
229
+ * Stores the given node in the cache.
230
+ * @param value - The value to store.
231
+ * @param depth - The depth of the node in the full tree.
232
+ * @param index - The index of the node at the given depth.
233
+ */
234
+ private storeNode(value: T | Buffer, depth: number, index: bigint) {
235
+ const key = indexToKeyHash(this.name, depth, index);
236
+ this.cache[key] = serializeToBuffer(value);
237
+ this.valueCache[serializeToBuffer(value).toString('hex')] = key;
238
+ }
239
+ }