@aztec/foundation 1.2.0 → 2.0.0-nightly.20250813
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/array/array.d.ts +5 -0
- package/dest/array/array.d.ts.map +1 -1
- package/dest/array/array.js +11 -0
- package/dest/async-pool/index.d.ts.map +1 -1
- package/dest/async-pool/index.js +1 -0
- package/dest/collection/array.d.ts +2 -0
- package/dest/collection/array.d.ts.map +1 -1
- package/dest/collection/array.js +10 -0
- package/dest/collection/object.d.ts +2 -0
- package/dest/collection/object.d.ts.map +1 -1
- package/dest/collection/object.js +3 -0
- package/dest/config/env_var.d.ts +1 -1
- package/dest/config/env_var.d.ts.map +1 -1
- package/dest/crypto/secp256k1-signer/utils.d.ts.map +1 -1
- package/dest/fields/fields.d.ts.map +1 -1
- package/dest/fields/fields.js +0 -2
- package/dest/log/index.d.ts +1 -0
- package/dest/log/index.d.ts.map +1 -1
- package/dest/log/index.js +1 -0
- package/dest/log/pino-logger.d.ts +1 -0
- package/dest/log/pino-logger.d.ts.map +1 -1
- package/dest/log/pino-logger.js +4 -2
- package/dest/number/index.d.ts +3 -0
- package/dest/number/index.d.ts.map +1 -0
- package/dest/number/index.js +12 -0
- package/dest/promise/running-promise.d.ts.map +1 -1
- package/dest/promise/running-promise.js +0 -1
- package/dest/schemas/schemas.d.ts +1 -1
- package/dest/schemas/schemas.d.ts.map +1 -1
- package/dest/serialize/serialize.d.ts +1 -0
- package/dest/serialize/serialize.d.ts.map +1 -1
- package/dest/timer/timeout.d.ts +16 -6
- package/dest/timer/timeout.d.ts.map +1 -1
- package/dest/timer/timeout.js +34 -33
- package/dest/trees/merkle_tree_calculator.d.ts +10 -0
- package/dest/trees/merkle_tree_calculator.d.ts.map +1 -1
- package/dest/trees/merkle_tree_calculator.js +31 -1
- package/dest/trees/unbalanced_merkle_tree.d.ts +20 -6
- package/dest/trees/unbalanced_merkle_tree.d.ts.map +1 -1
- package/dest/trees/unbalanced_merkle_tree.js +50 -51
- package/dest/trees/unbalanced_merkle_tree_calculator.d.ts +3 -1
- package/dest/trees/unbalanced_merkle_tree_calculator.d.ts.map +1 -1
- package/dest/trees/unbalanced_merkle_tree_calculator.js +16 -6
- package/dest/types/index.d.ts +2 -0
- package/dest/types/index.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/array/array.ts +13 -0
- package/src/async-pool/index.ts +1 -0
- package/src/collection/array.ts +12 -0
- package/src/collection/object.ts +5 -0
- package/src/config/env_var.ts +33 -4
- package/src/crypto/secp256k1-signer/utils.ts +0 -1
- package/src/fields/fields.ts +0 -2
- package/src/log/index.ts +1 -0
- package/src/log/pino-logger.ts +5 -0
- package/src/number/index.ts +14 -0
- package/src/promise/running-promise.ts +0 -1
- package/src/schemas/schemas.ts +1 -1
- package/src/serialize/serialize.ts +2 -0
- package/src/timer/timeout.ts +33 -30
- package/src/trees/merkle_tree_calculator.ts +34 -1
- package/src/trees/unbalanced_merkle_tree.ts +51 -43
- package/src/trees/unbalanced_merkle_tree_calculator.ts +22 -8
- package/src/types/index.ts +2 -0
package/src/timer/timeout.ts
CHANGED
|
@@ -8,23 +8,15 @@ import { TimeoutError } from '../error/index.js';
|
|
|
8
8
|
* @typeparam T - The return type of the asynchronous function to be executed.
|
|
9
9
|
*/
|
|
10
10
|
export class TimeoutTask<T> {
|
|
11
|
-
private interruptPromise!: Promise<any>;
|
|
12
|
-
private interrupt = () => {};
|
|
13
|
-
private interrupted = false;
|
|
14
11
|
private totalTime = 0;
|
|
12
|
+
private timeoutPromise: Promise<never> | undefined;
|
|
15
13
|
|
|
16
14
|
constructor(
|
|
17
15
|
private fn: (signal: AbortSignal) => Promise<T>,
|
|
18
16
|
private timeout: number,
|
|
19
|
-
errorFn: () =>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
this.interrupt = () => {
|
|
23
|
-
this.interrupted = true;
|
|
24
|
-
reject(errorFn());
|
|
25
|
-
};
|
|
26
|
-
});
|
|
27
|
-
}
|
|
17
|
+
private errorFn: () => Error,
|
|
18
|
+
private onAbort?: () => void,
|
|
19
|
+
) {}
|
|
28
20
|
|
|
29
21
|
/**
|
|
30
22
|
* Executes the given function with a specified timeout.
|
|
@@ -35,22 +27,22 @@ export class TimeoutTask<T> {
|
|
|
35
27
|
* @throws An error with a message indicating the function was interrupted due to exceeding the specified timeout.
|
|
36
28
|
*/
|
|
37
29
|
public async exec() {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
30
|
+
const signal = AbortSignal.timeout(this.timeout);
|
|
31
|
+
this.timeoutPromise = new Promise<never>((_, reject) => {
|
|
32
|
+
signal!.addEventListener(
|
|
33
|
+
'abort',
|
|
34
|
+
() => {
|
|
35
|
+
this.onAbort?.();
|
|
36
|
+
reject(this.errorFn());
|
|
37
|
+
},
|
|
38
|
+
{ once: true },
|
|
39
|
+
);
|
|
40
|
+
});
|
|
49
41
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
const start = Date.now();
|
|
43
|
+
const result = await Promise.race<T>([this.fn(signal), this.timeoutPromise]);
|
|
44
|
+
this.totalTime = Date.now() - start;
|
|
45
|
+
return result;
|
|
54
46
|
}
|
|
55
47
|
|
|
56
48
|
/**
|
|
@@ -61,7 +53,7 @@ export class TimeoutTask<T> {
|
|
|
61
53
|
* @returns The interrupt promise associated with the task.
|
|
62
54
|
*/
|
|
63
55
|
public getInterruptPromise() {
|
|
64
|
-
return this.
|
|
56
|
+
return this.timeoutPromise;
|
|
65
57
|
}
|
|
66
58
|
|
|
67
59
|
/**
|
|
@@ -75,15 +67,26 @@ export class TimeoutTask<T> {
|
|
|
75
67
|
}
|
|
76
68
|
}
|
|
77
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Executes a function with a timeout.
|
|
72
|
+
* @param fn - The function to execute, accepts AbortSignal and returns a Promise.
|
|
73
|
+
* @param timeout - The maximum time in milliseconds to allow the function to run.
|
|
74
|
+
* @param errorOrFnName - Optional function name or a function that returns an Error to throw if the timeout is reached.
|
|
75
|
+
* @param onAbort - Optional callback to execute when the task is aborted.
|
|
76
|
+
*
|
|
77
|
+
* @returns A Promise that resolves with the result of the function fn if it completes within the timeout
|
|
78
|
+
*
|
|
79
|
+
* */
|
|
78
80
|
export async function executeTimeout<T>(
|
|
79
81
|
fn: (signal: AbortSignal) => Promise<T>,
|
|
80
82
|
timeout: number,
|
|
81
|
-
errorOrFnName?: string | (() =>
|
|
83
|
+
errorOrFnName?: string | (() => Error),
|
|
84
|
+
onAbort?: () => void,
|
|
82
85
|
) {
|
|
83
86
|
const errorFn =
|
|
84
87
|
typeof errorOrFnName === 'function'
|
|
85
88
|
? errorOrFnName
|
|
86
89
|
: () => new TimeoutError(`Timeout running ${errorOrFnName ?? 'function'} after ${timeout}ms.`);
|
|
87
|
-
const task = new TimeoutTask(fn, timeout, errorFn);
|
|
90
|
+
const task = new TimeoutTask(fn, timeout, errorFn, onAbort);
|
|
88
91
|
return await task.exec();
|
|
89
92
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pedersenHash } from '@aztec/foundation/crypto';
|
|
1
|
+
import { pedersenHash, sha256Trunc } from '@aztec/foundation/crypto';
|
|
2
2
|
|
|
3
3
|
import type { AsyncHasher } from './hasher.js';
|
|
4
4
|
import { MerkleTree } from './merkle_tree.js';
|
|
@@ -69,4 +69,37 @@ export class MerkleTreeCalculator {
|
|
|
69
69
|
|
|
70
70
|
return leaves[0];
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Computes the Merkle root with the provided leaves **synchronously**.
|
|
75
|
+
*
|
|
76
|
+
* This method uses a synchronous hash function (defaults to `sha256Trunc`) and **does not** allow for padding.
|
|
77
|
+
* If the number of leaves is not a power of two, it throws an error.
|
|
78
|
+
* This contrasts with the above non-static async method `computeTreeRoot`, which can handle any number of leaves by
|
|
79
|
+
* padding with zero hashes.
|
|
80
|
+
*/
|
|
81
|
+
static computeTreeRootSync(leaves: Buffer[], hasher = sha256Trunc): Buffer {
|
|
82
|
+
if (leaves.length === 0) {
|
|
83
|
+
throw new Error('Cannot compute a Merkle root with no leaves');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const height = Math.log2(leaves.length);
|
|
87
|
+
if (!Number.isInteger(height)) {
|
|
88
|
+
throw new Error('Cannot compute a Merkle root with a non-power-of-two number of leaves');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let nodes = leaves.slice();
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < height; ++i) {
|
|
94
|
+
let j = 0;
|
|
95
|
+
for (; j < nodes.length / 2; ++j) {
|
|
96
|
+
const l = nodes[j * 2];
|
|
97
|
+
const r = nodes[j * 2 + 1];
|
|
98
|
+
nodes[j] = hasher(Buffer.concat([l, r]));
|
|
99
|
+
}
|
|
100
|
+
nodes = nodes.slice(0, j);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return nodes[0];
|
|
104
|
+
}
|
|
72
105
|
}
|
|
@@ -1,61 +1,69 @@
|
|
|
1
|
-
import { padArrayEnd } from '../collection/array.js';
|
|
2
1
|
import { sha256Trunc } from '../crypto/sha256/index.js';
|
|
2
|
+
import { MerkleTreeCalculator } from './merkle_tree_calculator.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Computes the
|
|
5
|
+
* Computes the Merkle root of an unbalanced tree.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Unlike a balanced Merkle tree, which requires the number of leaves to be a power of two, an unbalanced tree can have
|
|
8
|
+
* any number of leaves.
|
|
9
|
+
*
|
|
10
|
+
* The tree is constructed by iteratively extracting the smallest power-of-two-sized subtrees from **right to left**.
|
|
11
|
+
* For each such subtree, it computes the subtree root and then combines all subtree roots (again from right to left)
|
|
12
|
+
* into a single root using the provided hash function.
|
|
13
|
+
*
|
|
14
|
+
* Note: We need the final tree to be as shallow as possible, to minimize the size of the sibling path required to prove
|
|
15
|
+
* membership of a leaf. Therefor, the computation proceeds from right to left - smaller subtrees must always be
|
|
16
|
+
* combined before being merged with a larger sibling on their left.
|
|
17
|
+
*
|
|
18
|
+
* For example, consider an unbalanced tree made of three subtrees of sizes 2, 4, and 8. If we combine the size-2 and
|
|
19
|
+
* size-4 subtrees first (producing a subtree of depth 3), and then merge it with the size-8 subtree (also depth 3), the
|
|
20
|
+
* resulting tree has a maximum depth of 4.
|
|
21
|
+
*
|
|
22
|
+
* But if we instead combine the size-4 and size-8 subtrees first (depth 4), and then merge with the size-2 subtree
|
|
23
|
+
* (depth 1), the final tree has a depth of 5.
|
|
11
24
|
*/
|
|
12
|
-
export function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (emptyLeaf === undefined) {
|
|
16
|
-
throw new Error('Cannot compute a Merkle root with less than 2 leaves');
|
|
17
|
-
} else {
|
|
18
|
-
leaves = padArrayEnd(leaves, emptyLeaf, 2);
|
|
19
|
-
}
|
|
25
|
+
export function computeUnbalancedMerkleTreeRoot(leaves: Buffer[], hasher = sha256Trunc): Buffer {
|
|
26
|
+
if (!leaves.length) {
|
|
27
|
+
throw new Error('Cannot compute a Merkle root with no leaves');
|
|
20
28
|
}
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
let
|
|
27
|
-
let
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (nodeToShift.length) {
|
|
36
|
-
// If the next layer has odd length, and we have a node that needs to be shifted up, add it here
|
|
37
|
-
nextLayer.push(nodeToShift);
|
|
38
|
-
layerWidth += 1;
|
|
39
|
-
nodeToShift = Buffer.alloc(0);
|
|
30
|
+
if (leaves.length === 1) {
|
|
31
|
+
return leaves[0];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let numRemainingLeaves = leaves.length;
|
|
35
|
+
let subtreeSize = 1;
|
|
36
|
+
let root: Buffer | undefined;
|
|
37
|
+
while (numRemainingLeaves > 1) {
|
|
38
|
+
if ((numRemainingLeaves & subtreeSize) !== 0) {
|
|
39
|
+
const subtreeLeaves = leaves.slice(numRemainingLeaves - subtreeSize, numRemainingLeaves);
|
|
40
|
+
const subtreeRoot = MerkleTreeCalculator.computeTreeRootSync(subtreeLeaves, hasher);
|
|
41
|
+
if (!root) {
|
|
42
|
+
root = subtreeRoot;
|
|
40
43
|
} else {
|
|
41
|
-
|
|
42
|
-
layerWidth -= 1;
|
|
43
|
-
nodeToShift = nextLayer[layerWidth];
|
|
44
|
+
root = hasher(Buffer.concat([subtreeRoot, root]));
|
|
44
45
|
}
|
|
46
|
+
|
|
47
|
+
numRemainingLeaves -= subtreeSize;
|
|
45
48
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
nextLayer = [];
|
|
49
|
+
|
|
50
|
+
subtreeSize *= 2;
|
|
49
51
|
}
|
|
50
|
-
|
|
51
|
-
return
|
|
52
|
+
|
|
53
|
+
return root!;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
/// Get the depth of the maximum balanced tree that can be created with the given number of leaves. The subtree will be
|
|
57
|
+
/// the left most subtree of the wonky tree with a total of `numLeaves` leaves.
|
|
58
|
+
///
|
|
59
|
+
/// Note: All the leaves may not be used to form the tree. For example, if there are 5 leaves, the maximum depth is 2,
|
|
60
|
+
/// only 4 leaves are used to form a balanced tree.
|
|
61
|
+
function getMaxBalancedSubtreeDepth(numLeaves: number) {
|
|
55
62
|
return Math.floor(Math.log2(numLeaves));
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
|
|
65
|
+
/// Get the maximum depth of an unbalanced tree that can be created with the given number of leaves.
|
|
66
|
+
export function getMaxUnbalancedTreeDepth(numLeaves: number) {
|
|
59
67
|
return Math.ceil(Math.log2(numLeaves));
|
|
60
68
|
}
|
|
61
69
|
|
|
@@ -72,7 +80,7 @@ function findPosition(
|
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
// The largest balanced tree that can be created with the given number of leaves.
|
|
75
|
-
const maxBalancedTreeDepth =
|
|
83
|
+
const maxBalancedTreeDepth = getMaxBalancedSubtreeDepth(numLeaves);
|
|
76
84
|
const numBalancedLeaves = 2 ** maxBalancedTreeDepth;
|
|
77
85
|
const numRemainingLeaves = numLeaves - numBalancedLeaves;
|
|
78
86
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { serializeToBuffer } from '@aztec/foundation/serialize';
|
|
1
|
+
import { type Bufferable, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
2
2
|
import type { AsyncHasher } from '@aztec/foundation/trees';
|
|
3
3
|
import { SiblingPath } from '@aztec/foundation/trees';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { sha256Trunc } from '../crypto/index.js';
|
|
6
6
|
|
|
7
7
|
const indexToKeyHash = (level: number, index: bigint) => `${level}:${index}`;
|
|
8
8
|
|
|
@@ -10,6 +10,7 @@ const indexToKeyHash = (level: number, index: bigint) => `${level}:${index}`;
|
|
|
10
10
|
* An ephemeral unbalanced Merkle tree implementation.
|
|
11
11
|
* Follows the rollup implementation which greedily hashes pairs of nodes up the tree.
|
|
12
12
|
* Remaining rightmost nodes are shifted up until they can be paired.
|
|
13
|
+
* If there is only one leaf, the root is the leaf.
|
|
13
14
|
*/
|
|
14
15
|
export class UnbalancedMerkleTreeCalculator {
|
|
15
16
|
// This map stores index and depth -> value
|
|
@@ -27,8 +28,8 @@ export class UnbalancedMerkleTreeCalculator {
|
|
|
27
28
|
|
|
28
29
|
static create(
|
|
29
30
|
height: number,
|
|
30
|
-
hasher =
|
|
31
|
-
(
|
|
31
|
+
hasher = (left: Buffer, right: Buffer) =>
|
|
32
|
+
Promise.resolve(sha256Trunc(Buffer.concat([left, right])) as Buffer<ArrayBuffer>),
|
|
32
33
|
) {
|
|
33
34
|
return new UnbalancedMerkleTreeCalculator(height, hasher);
|
|
34
35
|
}
|
|
@@ -47,7 +48,11 @@ export class UnbalancedMerkleTreeCalculator {
|
|
|
47
48
|
* @returns A sibling path for the element.
|
|
48
49
|
* Note: The sibling path is an array of sibling hashes, with the lowest hash (leaf hash) first, and the highest hash last.
|
|
49
50
|
*/
|
|
50
|
-
public getSiblingPath<N extends number>(value:
|
|
51
|
+
public getSiblingPath<N extends number>(value: Bufferable): Promise<SiblingPath<N>> {
|
|
52
|
+
if (this.size === 1n) {
|
|
53
|
+
return Promise.resolve(new SiblingPath<N>(0 as N, []));
|
|
54
|
+
}
|
|
55
|
+
|
|
51
56
|
const path: Buffer[] = [];
|
|
52
57
|
const [depth, _index] = this.valueCache[serializeToBuffer(value).toString('hex')].split(':');
|
|
53
58
|
let level = parseInt(depth, 10);
|
|
@@ -72,8 +77,17 @@ export class UnbalancedMerkleTreeCalculator {
|
|
|
72
77
|
if (this.size != BigInt(0)) {
|
|
73
78
|
throw Error(`Can't re-append to an unbalanced tree. Current has ${this.size} leaves.`);
|
|
74
79
|
}
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
if (leaves.length === 0) {
|
|
81
|
+
throw Error(`Can't append 0 leaves to an unbalanced tree.`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (leaves.length === 1) {
|
|
85
|
+
this.root = leaves[0];
|
|
86
|
+
} else {
|
|
87
|
+
this.root = await this.batchInsert(leaves);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.size = BigInt(leaves.length);
|
|
77
91
|
|
|
78
92
|
return Promise.resolve();
|
|
79
93
|
}
|
|
@@ -119,7 +133,7 @@ export class UnbalancedMerkleTreeCalculator {
|
|
|
119
133
|
thisLayer = nextLayer;
|
|
120
134
|
nextLayer = [];
|
|
121
135
|
}
|
|
122
|
-
|
|
136
|
+
|
|
123
137
|
// return the root
|
|
124
138
|
return thisLayer[0];
|
|
125
139
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -31,9 +31,11 @@ export function unfreeze<T>(obj: T): Writeable<T> {
|
|
|
31
31
|
* }
|
|
32
32
|
*/
|
|
33
33
|
export interface TypedEventEmitter<TEventMap extends { [key in keyof TEventMap]: (...args: any[]) => void }> {
|
|
34
|
+
once<K extends keyof TEventMap>(event: K, listener: TEventMap[K]): this;
|
|
34
35
|
on<K extends keyof TEventMap>(event: K, listener: TEventMap[K]): this;
|
|
35
36
|
off<K extends keyof TEventMap>(event: K, listener: TEventMap[K]): this;
|
|
36
37
|
emit<K extends keyof TEventMap>(event: K, ...args: Parameters<TEventMap[K]>): boolean;
|
|
37
38
|
removeListener<K extends keyof TEventMap>(event: K, listener: TEventMap[K]): this;
|
|
39
|
+
removeAllListeners<K extends keyof TEventMap>(event: K): this;
|
|
38
40
|
// Can add other EventEmitter methods if needed, like once(), listenerCount(), etc.
|
|
39
41
|
}
|