@aztec/foundation 0.0.1-commit.993d240 → 0.0.1-commit.9a89641

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.
Files changed (76) hide show
  1. package/dest/config/env_var.d.ts +2 -2
  2. package/dest/config/env_var.d.ts.map +1 -1
  3. package/dest/config/index.d.ts +21 -8
  4. package/dest/config/index.d.ts.map +1 -1
  5. package/dest/config/index.js +46 -30
  6. package/dest/config/network_name.d.ts +2 -2
  7. package/dest/config/network_name.d.ts.map +1 -1
  8. package/dest/config/network_name.js +1 -1
  9. package/dest/crypto/bls/bn254_keystore.d.ts +2 -2
  10. package/dest/crypto/bls/bn254_keystore.d.ts.map +1 -1
  11. package/dest/crypto/bls/bn254_keystore.js +22 -17
  12. package/dest/crypto/keys/index.d.ts +3 -2
  13. package/dest/crypto/keys/index.d.ts.map +1 -1
  14. package/dest/crypto/keys/index.js +30 -6
  15. package/dest/fifo/fifo_frame_reader.d.ts +2 -2
  16. package/dest/fifo/fifo_frame_reader.d.ts.map +1 -1
  17. package/dest/fifo/fifo_frame_reader.js +21 -4
  18. package/dest/json-rpc/client/safe_json_rpc_client.d.ts +1 -1
  19. package/dest/json-rpc/client/safe_json_rpc_client.d.ts.map +1 -1
  20. package/dest/json-rpc/client/safe_json_rpc_client.js +3 -2
  21. package/dest/json-rpc/server/safe_json_rpc_server.d.ts +1 -1
  22. package/dest/json-rpc/server/safe_json_rpc_server.d.ts.map +1 -1
  23. package/dest/json-rpc/server/safe_json_rpc_server.js +5 -1
  24. package/dest/log/aws-logger-config.d.ts +8 -0
  25. package/dest/log/aws-logger-config.d.ts.map +1 -0
  26. package/dest/log/aws-logger-config.js +55 -0
  27. package/dest/log/pino-logger.d.ts +1 -1
  28. package/dest/log/pino-logger.d.ts.map +1 -1
  29. package/dest/log/pino-logger.js +12 -6
  30. package/dest/promise/running-promise.d.ts +5 -4
  31. package/dest/promise/running-promise.d.ts.map +1 -1
  32. package/dest/promise/running-promise.js +7 -3
  33. package/dest/retry/index.d.ts +20 -3
  34. package/dest/retry/index.d.ts.map +1 -1
  35. package/dest/retry/index.js +23 -2
  36. package/dest/serialize/buffer_reader.d.ts +1 -1
  37. package/dest/serialize/buffer_reader.d.ts.map +1 -1
  38. package/dest/serialize/buffer_reader.js +5 -0
  39. package/dest/serialize/serialize.d.ts +1 -32
  40. package/dest/serialize/serialize.d.ts.map +1 -1
  41. package/dest/serialize/serialize.js +0 -21
  42. package/dest/string/index.d.ts +3 -1
  43. package/dest/string/index.d.ts.map +1 -1
  44. package/dest/string/index.js +12 -0
  45. package/dest/testing/files/index.d.ts +3 -4
  46. package/dest/testing/files/index.d.ts.map +1 -1
  47. package/dest/testing/files/index.js +3 -4
  48. package/dest/trees/membership_witness.d.ts +1 -5
  49. package/dest/trees/membership_witness.d.ts.map +1 -1
  50. package/dest/trees/membership_witness.js +0 -9
  51. package/dest/trees/sibling_path.d.ts +9 -11
  52. package/dest/trees/sibling_path.d.ts.map +1 -1
  53. package/dest/trees/sibling_path.js +13 -20
  54. package/dest/types/index.d.ts +16 -2
  55. package/dest/types/index.d.ts.map +1 -1
  56. package/dest/types/index.js +15 -0
  57. package/package.json +2 -2
  58. package/src/config/env_var.ts +26 -5
  59. package/src/config/index.ts +46 -30
  60. package/src/config/network_name.ts +2 -2
  61. package/src/crypto/bls/bn254_keystore.ts +33 -20
  62. package/src/crypto/keys/index.ts +20 -4
  63. package/src/fifo/fifo_frame_reader.ts +17 -2
  64. package/src/json-rpc/client/safe_json_rpc_client.ts +3 -2
  65. package/src/json-rpc/server/safe_json_rpc_server.ts +5 -1
  66. package/src/log/aws-logger-config.ts +32 -0
  67. package/src/log/pino-logger.ts +14 -4
  68. package/src/promise/running-promise.ts +8 -5
  69. package/src/retry/index.ts +36 -4
  70. package/src/serialize/buffer_reader.ts +5 -0
  71. package/src/serialize/serialize.ts +0 -50
  72. package/src/string/index.ts +14 -0
  73. package/src/testing/files/index.ts +3 -4
  74. package/src/trees/membership_witness.ts +0 -8
  75. package/src/trees/sibling_path.ts +13 -24
  76. package/src/types/index.ts +34 -1
@@ -1,6 +1,6 @@
1
1
  export type NetworkNames =
2
2
  | 'local'
3
- | 'staging-public'
3
+ | 'staging'
4
4
  | 'testnet'
5
5
  | 'mainnet'
6
6
  | 'next-net'
@@ -11,7 +11,7 @@ export function getActiveNetworkName(name?: string): NetworkNames {
11
11
  const network = name || process.env.NETWORK;
12
12
  if (!network || network === '' || network === 'local') {
13
13
  return 'local';
14
- } else if (network === 'staging-public') {
14
+ } else if (network === 'staging') {
15
15
  return network;
16
16
  } else if (network === 'testnet' || network === 'alpha-testnet') {
17
17
  return 'testnet';
@@ -1,7 +1,8 @@
1
1
  import { randomBytes } from '@aztec/foundation/crypto/random';
2
2
 
3
- import { createCipheriv, createDecipheriv, createHash, pbkdf2Sync, randomUUID } from 'crypto';
3
+ import { createCipheriv, createDecipheriv, createHash, pbkdf2, pbkdf2Sync, randomUUID } from 'crypto';
4
4
  import { readFileSync } from 'fs';
5
+ import { promisify } from 'util';
5
6
  import { z } from 'zod';
6
7
 
7
8
  /**
@@ -100,24 +101,15 @@ export interface Bn254KeystoreInterface {
100
101
  version: number;
101
102
  }
102
103
 
103
- /**
104
- * Creates a BN254 keystore object for a BN254 BLS private key.
105
- *
106
- * Uses PBKDF2 with SHA-256 for key derivation and AES-128-CTR for encryption,
107
- * following the EIP-2335 specification format.
108
- *
109
- * @param password - Password for encrypting the private key
110
- * @param privateKeyHex - Private key as 0x-prefixed hex string (32 bytes)
111
- * @param pubkeyHex - Public key as hex string (compressed or uncompressed)
112
- * @param derivationPath - BIP-44 style derivation path (e.g., "m/12381/3600/0/0/0")
113
- * @returns BN254 keystore object ready to be serialized to JSON
114
- * @throws Error if private key is not 32-byte hex
115
- */
116
- export function createBn254Keystore(
117
- password: string,
104
+ const pbkdf2Async = promisify(pbkdf2);
105
+
106
+ function createBn254KeystoreFromDerivedKey(
118
107
  privateKeyHex: string,
119
108
  pubkeyHex: string,
120
109
  derivationPath: string,
110
+ salt: Buffer,
111
+ iv: Buffer,
112
+ dk: Buffer,
121
113
  ): Bn254Keystore {
122
114
  const ensureHex = (hex: string) => hex.replace(/^0x/i, '');
123
115
  const privHex = ensureHex(privateKeyHex);
@@ -125,11 +117,7 @@ export function createBn254Keystore(
125
117
  throw new Error('BLS private key must be 32-byte hex');
126
118
  }
127
119
 
128
- const salt = randomBytes(32);
129
- const iv = randomBytes(16);
130
- const dk = pbkdf2Sync(Buffer.from(password.normalize('NFKD'), 'utf8'), salt, 262144, 32, 'sha256');
131
120
  const cipherKey = dk.subarray(0, 16);
132
-
133
121
  const cipher = createCipheriv('aes-128-ctr', cipherKey, iv);
134
122
  const plaintext = Buffer.from(privHex, 'hex');
135
123
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
@@ -166,6 +154,31 @@ export function createBn254Keystore(
166
154
  };
167
155
  }
168
156
 
157
+ /**
158
+ * Creates a BN254 keystore object for a BN254 BLS private key.
159
+ *
160
+ * Uses PBKDF2 with SHA-256 for key derivation and AES-128-CTR for encryption,
161
+ * following the EIP-2335 specification format.
162
+ *
163
+ * @param password - Password for encrypting the private key
164
+ * @param privateKeyHex - Private key as 0x-prefixed hex string (32 bytes)
165
+ * @param pubkeyHex - Public key as hex string (compressed or uncompressed)
166
+ * @param derivationPath - BIP-44 style derivation path (e.g., "m/12381/3600/0/0/0")
167
+ * @returns BN254 keystore object ready to be serialized to JSON
168
+ * @throws Error if private key is not 32-byte hex
169
+ */
170
+ export async function createBn254Keystore(
171
+ password: string,
172
+ privateKeyHex: string,
173
+ pubkeyHex: string,
174
+ derivationPath: string,
175
+ ): Promise<Bn254Keystore> {
176
+ const salt = randomBytes(32);
177
+ const iv = randomBytes(16);
178
+ const dk = await pbkdf2Async(Buffer.from(password.normalize('NFKD'), 'utf8'), salt, 262144, 32, 'sha256');
179
+ return createBn254KeystoreFromDerivedKey(privateKeyHex, pubkeyHex, derivationPath, salt, iv, dk);
180
+ }
181
+
169
182
  /**
170
183
  * Loads and validates a BN254 keystore file.
171
184
  *
@@ -1,10 +1,26 @@
1
- import { BarretenbergSync } from '@aztec/bb.js';
1
+ import { BarretenbergSync, CircuitKind } from '@aztec/bb.js';
2
2
 
3
3
  import { Fr } from '../../curves/bn254/field.js';
4
4
 
5
- export async function vkAsFieldsMegaHonk(input: Buffer): Promise<Fr[]> {
5
+ export async function vkAsFields(input: Buffer, kind: CircuitKind): Promise<Fr[]> {
6
6
  await BarretenbergSync.initSingleton();
7
7
  const api = BarretenbergSync.getSingleton();
8
- const response = api.megaVkAsFields({ verificationKey: input });
9
- return response.fields.map(field => Fr.fromBuffer(Buffer.from(field)));
8
+ switch (kind) {
9
+ case CircuitKind.App: {
10
+ const response = api.megaAppVkAsFields({ verificationKey: input });
11
+ return response.fields.map(field => Fr.fromBuffer(Buffer.from(field)));
12
+ }
13
+ case CircuitKind.Kernel: {
14
+ const response = api.megaKernelVkAsFields({ verificationKey: input });
15
+ return response.fields.map(field => Fr.fromBuffer(Buffer.from(field)));
16
+ }
17
+ case CircuitKind.HidingKernel: {
18
+ const response = api.megaZKVkAsFields({ verificationKey: input });
19
+ return response.fields.map(field => Fr.fromBuffer(Buffer.from(field)));
20
+ }
21
+ default: {
22
+ const _exhaustive: never = kind;
23
+ throw new Error(`vkAsFields: unhandled CircuitKind ${_exhaustive}`);
24
+ }
25
+ }
10
26
  }
@@ -1,5 +1,6 @@
1
1
  import EventEmitter from 'node:events';
2
2
  import * as fs from 'node:fs';
3
+ import * as net from 'node:net';
3
4
  import type { Readable } from 'node:stream';
4
5
 
5
6
  /**
@@ -36,8 +37,22 @@ export class FifoFrameReader extends EventEmitter<FifoFrameReaderEvents> {
36
37
  }
37
38
 
38
39
  /** Open a FIFO at the given path and start reading frames. */
39
- start(fifoPath: string, highWaterMark = 64 * 1024): void {
40
- this.startFromStream(fs.createReadStream(fifoPath, { highWaterMark }));
40
+ start(fifoPath: string): void {
41
+ // Read the FIFO through a libuv pipe handle (net.Socket) rather than fs.createReadStream.
42
+ // An fs read stream services the pipe with a blocking threadpool read that destroy() cannot
43
+ // cancel: if a writer never closes, that read stays parked and keeps the host process alive
44
+ // (manifesting as Jest "did not exit"). Opening O_NONBLOCK and wrapping the fd in a pipe
45
+ // handle makes reads epoll-based, so stop()'s destroy() releases the handle immediately,
46
+ // regardless of whether the writer is still attached.
47
+ const fd = fs.openSync(fifoPath, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK);
48
+ let socket: net.Socket;
49
+ try {
50
+ socket = new net.Socket({ fd, readable: true, writable: false });
51
+ } catch (err) {
52
+ fs.closeSync(fd);
53
+ throw err;
54
+ }
55
+ this.startFromStream(socket);
41
56
  }
42
57
 
43
58
  /** Start reading frames from an existing readable stream. */
@@ -14,8 +14,9 @@ const DEFAULT_BATCH_WINDOW_MS = 0;
14
14
  // the maximum size of a batched request
15
15
  const DEFAULT_MAX_BATCH_SIZE = 100;
16
16
 
17
- // 10 mb
18
- const DEFAULT_MAX_REQUESTY_BODY_SIZE = 10 * 1024 * 1024;
17
+ // 1 mb, matching the JSON-RPC server's default maxBodySizeBytes so the client never assembles a batch the
18
+ // server rejects for exceeding the body limit
19
+ const DEFAULT_MAX_REQUESTY_BODY_SIZE = 1 * 1024 * 1024;
19
20
 
20
21
  export type SafeJsonRpcClientOptions = {
21
22
  namespaceMethods?: string | false;
@@ -224,7 +224,11 @@ export class SafeJsonRpcServer {
224
224
  result = await this.proxy.call(method, params);
225
225
  }
226
226
 
227
- return { jsonrpc, id, result };
227
+ // Coerce an undefined return value to null so the response always carries a `result` key.
228
+ // JSON.stringify drops undefined-valued keys, which would otherwise produce a JSON-RPC
229
+ // response with neither `result` nor `error` — a spec violation that leaves callers unable
230
+ // to distinguish "not found" from a malformed response.
231
+ return { jsonrpc, id, result: result ?? null };
228
232
  } catch (err: any) {
229
233
  if (err && err instanceof ZodError) {
230
234
  const message = err.issues.map(e => `${e.message} (${e.path.join('.')})`).join('. ') || 'Validation error';
@@ -0,0 +1,32 @@
1
+ import type { pino } from 'pino';
2
+
3
+ function getSeverity(label: string): { severityText: string; severityNumber: number } {
4
+ switch (label) {
5
+ case 'trace':
6
+ return { severityText: 'TRACE', severityNumber: 1 };
7
+ case 'debug':
8
+ return { severityText: 'DEBUG', severityNumber: 5 };
9
+ case 'verbose':
10
+ return { severityText: 'VERBOSE', severityNumber: 7 };
11
+ case 'info':
12
+ return { severityText: 'INFO', severityNumber: 9 };
13
+ case 'warn':
14
+ return { severityText: 'WARN', severityNumber: 13 };
15
+ case 'error':
16
+ return { severityText: 'ERROR', severityNumber: 17 };
17
+ case 'fatal':
18
+ return { severityText: 'FATAL', severityNumber: 21 };
19
+ default:
20
+ return { severityText: 'UNSPECIFIED', severityNumber: 0 };
21
+ }
22
+ }
23
+
24
+ /** Pino configuration that adds OpenTelemetry severity fields understood by CloudWatch Logs. */
25
+ export const AWSCloudLoggerConfig = {
26
+ messageKey: 'msg',
27
+ formatters: {
28
+ level(label: string, level: number): object {
29
+ return { level, ...getSeverity(label) };
30
+ },
31
+ },
32
+ } satisfies pino.LoggerOptions;
@@ -7,6 +7,7 @@ import { inspect } from 'util';
7
7
  import { compactArray } from '../collection/array.js';
8
8
  import type { EnvVar } from '../config/index.js';
9
9
  import { parseBooleanEnv } from '../config/parse-env.js';
10
+ import { AWSCloudLoggerConfig } from './aws-logger-config.js';
10
11
  import { convertBigintsToStrings } from './bigint-utils.js';
11
12
  import { GoogleCloudLoggerConfig } from './gcloud-logger-config.js';
12
13
  import { getLogLevelFromFilters, parseLogLevelEnvVar } from './log-filters.js';
@@ -134,8 +135,17 @@ export const [logLevel, logFilters] = parseLogLevelEnvVar(process.env.LOG_LEVEL,
134
135
  // Define custom logging levels for pino.
135
136
  const customLevels = { verbose: 25 };
136
137
 
137
- // Global pino options, tweaked for google cloud if running there.
138
+ // Global pino options, tweaked for the active cloud logging backend.
138
139
  const useGcloudLogging = parseBooleanEnv(process.env['USE_GCLOUD_LOGGING' satisfies EnvVar]);
140
+ const useAwsLogging = parseBooleanEnv(process.env['USE_AWS_LOGGING' satisfies EnvVar]);
141
+ const loggingConfig = useGcloudLogging
142
+ ? GoogleCloudLoggerConfig
143
+ : useAwsLogging
144
+ ? AWSCloudLoggerConfig
145
+ : {
146
+ formatters: {},
147
+ messageKey: 'msg',
148
+ };
139
149
 
140
150
  const redactedPaths = [
141
151
  'validatorPrivateKeys',
@@ -156,7 +166,7 @@ const redactedPaths = [
156
166
 
157
167
  const pinoOpts: pino.LoggerOptions<keyof typeof customLevels> = {
158
168
  customLevels,
159
- messageKey: 'msg',
169
+ messageKey: loggingConfig.messageKey,
160
170
  useOnlyCustomLevels: false,
161
171
  level: logLevel,
162
172
  redact: {
@@ -170,8 +180,8 @@ const pinoOpts: pino.LoggerOptions<keyof typeof customLevels> = {
170
180
  },
171
181
  formatters: {
172
182
  log: obj => convertBigintsToStrings(obj) as Record<string, unknown>,
183
+ ...loggingConfig.formatters,
173
184
  },
174
- ...(useGcloudLogging ? GoogleCloudLoggerConfig : {}),
175
185
  };
176
186
 
177
187
  export const levels = {
@@ -241,7 +251,7 @@ const pinoPrettyBaseOpts = {
241
251
  destination: 2,
242
252
  sync: true,
243
253
  colorize: useColor,
244
- ignore: 'module,actor,instanceId,pid,hostname,trace_id,span_id,trace_flags,severity',
254
+ ignore: 'module,actor,instanceId,pid,hostname,trace_id,span_id,trace_flags,severity,severityText,severityNumber',
245
255
  customLevels: 'fatal:60,error:50,warn:40,info:30,verbose:25,debug:20,trace:10',
246
256
  customColors: 'fatal:bgRed,error:red,warn:yellow,info:green,verbose:magenta,debug:blue,trace:gray',
247
257
  minimumLevel: 'trace' as const,
@@ -22,16 +22,17 @@ export function makeLoggingErrorHandler(
22
22
  * at a specified polling interval. It allows starting, stopping, and checking the status of the
23
23
  * internally managed promise. The class also supports interrupting the polling process when stopped.
24
24
  */
25
- export class RunningPromise {
25
+ export class RunningPromise<T = void> {
26
26
  private running = false;
27
27
  private runningPromise = Promise.resolve();
28
28
  private interruptibleSleep = new InterruptibleSleep();
29
29
  private requested: PromiseWithResolvers<void> | undefined = undefined;
30
+ private requestedArg: T | undefined = undefined;
30
31
 
31
32
  public static readonly EXIT: typeof EXIT = EXIT;
32
33
 
33
34
  constructor(
34
- private fn: () => void | Promise<void>,
35
+ private fn: (arg?: T) => void | Promise<void>,
35
36
  private logger = createLogger('running-promise'),
36
37
  private pollingIntervalMS = 10000,
37
38
  private handleError: ErrorHandler = makeLoggingErrorHandler(logger),
@@ -51,7 +52,7 @@ export class RunningPromise {
51
52
  while (this.running) {
52
53
  const hasRequested = this.requested !== undefined;
53
54
  try {
54
- await this.fn();
55
+ await this.fn(this.requestedArg);
55
56
  } catch (err) {
56
57
  const code = await this.handleError(err);
57
58
  if (code === RunningPromise.EXIT) {
@@ -64,6 +65,7 @@ export class RunningPromise {
64
65
  if (hasRequested) {
65
66
  this.requested!.resolve();
66
67
  this.requested = undefined;
68
+ this.requestedArg = undefined;
67
69
  }
68
70
 
69
71
  // If no immediate run was requested, sleep for the polling interval.
@@ -101,15 +103,16 @@ export class RunningPromise {
101
103
  * Triggers an immediate run of the function, bypassing the polling interval.
102
104
  * If the function is currently running, it will be allowed to continue and then called again immediately.
103
105
  */
104
- public async trigger() {
106
+ public async trigger(arg?: T): Promise<void> {
105
107
  if (!this.running) {
106
- return this.fn();
108
+ return this.fn(arg);
107
109
  }
108
110
 
109
111
  let requested = this.requested;
110
112
  if (!requested) {
111
113
  requested = promiseWithResolvers<void>();
112
114
  this.requested = requested;
115
+ this.requestedArg = arg;
113
116
  this.interruptibleSleep.interrupt();
114
117
  }
115
118
  await requested!.promise;
@@ -1,7 +1,7 @@
1
1
  import { TimeoutError } from '../error/index.js';
2
2
  import { type Logger, createLogger } from '../log/index.js';
3
3
  import { sleep } from '../sleep/index.js';
4
- import { Timer } from '../timer/index.js';
4
+ import { type DateProvider, Timer } from '../timer/index.js';
5
5
 
6
6
  /** An error that indicates that the operation should not be retried. */
7
7
  export class NoRetryError extends Error {}
@@ -72,6 +72,34 @@ export async function retry<Result>(
72
72
  }
73
73
  }
74
74
 
75
+ /**
76
+ * Timeout specification accepted by {@link retryUntil}. Either a plain number of seconds, an explicit
77
+ * `{ timeout }` in seconds, or an absolute `{ deadline }` with an optional {@link DateProvider} used to
78
+ * read the current time. A deadline is converted to the remaining seconds at call time; a deadline that
79
+ * has already passed yields a zero remaining budget, matching the immediate-timeout semantics of a
80
+ * non-positive numeric timeout.
81
+ */
82
+ export type RetryUntilTimeout = number | { timeout: number } | { deadline: Date; dateProvider?: DateProvider };
83
+
84
+ /**
85
+ * Resolves a {@link RetryUntilTimeout} to a numeric timeout in seconds for the legacy timer-based loop.
86
+ * A numeric/`{ timeout }` value passes through unchanged (0 means never time out, negative times out on the
87
+ * first interval). A `{ deadline }` is the remaining seconds until the deadline; when the deadline is already
88
+ * at or past `now` it resolves to a negative value so the loop times out immediately instead of being read as
89
+ * the never-timeout sentinel 0.
90
+ */
91
+ function resolveRetryUntilTimeoutSeconds(timeout: RetryUntilTimeout): number {
92
+ if (typeof timeout === 'number') {
93
+ return timeout;
94
+ }
95
+ if ('deadline' in timeout) {
96
+ const now = timeout.dateProvider?.now() ?? Date.now();
97
+ const remainingSeconds = (timeout.deadline.getTime() - now) / 1000;
98
+ return remainingSeconds > 0 ? remainingSeconds : -1;
99
+ }
100
+ return timeout.timeout;
101
+ }
102
+
75
103
  /**
76
104
  * Retry an asynchronous function until it returns a truthy value or the specified timeout is exceeded.
77
105
  * The function is retried periodically with a fixed interval between attempts. The operation can be named for better error messages.
@@ -79,16 +107,20 @@ export async function retry<Result>(
79
107
  *
80
108
  * @param fn - The asynchronous function to be retried, which should return a truthy value upon success or undefined otherwise.
81
109
  * @param name - The optional name of the operation, used for generating timeout error message.
82
- * @param timeout - The optional maximum time, in seconds, to keep retrying before throwing a timeout error. Defaults to 0 (never timeout).
110
+ * @param timeout - The maximum time to keep retrying before throwing a timeout error. Accepts a number of
111
+ * seconds (0 = never time out), an explicit `{ timeout }` in seconds, or an absolute `{ deadline }` with an
112
+ * optional `dateProvider`. A deadline already in the past times out on the first interval, the same as a
113
+ * zero/negative numeric timeout. Defaults to 0 (never timeout).
83
114
  * @param interval - The optional interval, in seconds, between retry attempts. Defaults to 1 second.
84
115
  * @returns A Promise that resolves with the successful (truthy) result of the provided function, or rejects if timeout is exceeded.
85
116
  */
86
117
  export async function retryUntil<T>(
87
118
  fn: () => (T | undefined) | Promise<T | undefined>,
88
119
  name = '',
89
- timeout = 0,
120
+ timeout: RetryUntilTimeout = 0,
90
121
  interval = 1,
91
122
  ) {
123
+ const timeoutSeconds = resolveRetryUntilTimeoutSeconds(timeout);
92
124
  const timer = new Timer();
93
125
  while (true) {
94
126
  const result = await fn();
@@ -98,7 +130,7 @@ export async function retryUntil<T>(
98
130
 
99
131
  await sleep(interval * 1000);
100
132
 
101
- if (timeout && timer.s() > timeout) {
133
+ if (timeoutSeconds && timer.s() > timeoutSeconds) {
102
134
  throw new TimeoutError(name ? `Timeout awaiting ${name}` : 'Timeout');
103
135
  }
104
136
  }
@@ -255,6 +255,11 @@ export class BufferReader {
255
255
  if (maxSize !== undefined && size > maxSize) {
256
256
  throw new Error(`Vector size ${size} exceeds maximum allowed ${maxSize}`);
257
257
  }
258
+ // Every element consumes at least one byte, so a size beyond the bytes left is unsatisfiable. Reject it
259
+ // up front rather than relying on each item deserializer to bounds-check as the loop runs out of input.
260
+ if (size > this.remainingBytes()) {
261
+ throw new Error(`Vector size ${size} exceeds remaining buffer length ${this.remainingBytes()}`);
262
+ }
258
263
  const result = new Array<T>(size);
259
264
  for (let i = 0; i < size; i++) {
260
265
  result[i] = itemDeserializer.fromBuffer(this);
@@ -23,56 +23,6 @@ export function serializeArrayOfBufferableToVector(objs: Bufferable[], prefixLen
23
23
  return Buffer.concat([lengthBuf, ...arr]);
24
24
  }
25
25
 
26
- /**
27
- * Helper function for deserializeArrayFromVector.
28
- */
29
- type DeserializeFn<T> = (
30
- buf: Buffer,
31
- offset: number,
32
- ) => {
33
- /**
34
- * The deserialized type.
35
- */
36
- elem: T;
37
- /**
38
- * How many bytes to advance by.
39
- */
40
- adv: number;
41
- };
42
-
43
- /**
44
- * Deserializes an array from a vector on an element-by-element basis.
45
- * @param deserialize - A function used to deserialize each element of the vector.
46
- * @param vector - The vector to deserialize.
47
- * @param offset - The position in the vector to start deserializing from.
48
- * @returns Deserialized array and how many bytes we advanced by.
49
- */
50
- export function deserializeArrayFromVector<T>(
51
- deserialize: DeserializeFn<T>,
52
- vector: Buffer,
53
- offset = 0,
54
- ): {
55
- /**
56
- * The deserialized array.
57
- */
58
- elem: T[];
59
- /**
60
- * How many bytes we advanced by.
61
- */
62
- adv: number;
63
- } {
64
- let pos = offset;
65
- const size = vector.readUInt32BE(pos);
66
- pos += 4;
67
- const arr = new Array<T>(size);
68
- for (let i = 0; i < size; ++i) {
69
- const { elem, adv } = deserialize(vector, pos);
70
- pos += adv;
71
- arr[i] = elem;
72
- }
73
- return { elem: arr, adv: pos - offset };
74
- }
75
-
76
26
  /**
77
27
  * Cast a uint8 array to a number.
78
28
  * @param array - The uint8 array.
@@ -37,6 +37,20 @@ export function truncate(str: string, length: number = 64): string {
37
37
  return str.length > length ? str.slice(0, length) + '...' : str;
38
38
  }
39
39
 
40
+ /** Formats a duration in seconds into a compact human-readable string (e.g. `45s`, `12m`, `2h 5m`). */
41
+ export function formatSeconds(seconds: number): string {
42
+ if (seconds < 60) {
43
+ return `${Math.round(seconds)}s`;
44
+ }
45
+ const minutes = Math.round(seconds / 60);
46
+ if (minutes < 60) {
47
+ return `${minutes}m`;
48
+ }
49
+ const hours = Math.floor(minutes / 60);
50
+ const remMinutes = minutes % 60;
51
+ return remMinutes > 0 ? `${hours}h ${remMinutes}m` : `${hours}h`;
52
+ }
53
+
40
54
  export function isoDate(date?: Date) {
41
55
  return (date ?? new Date()).toISOString().replace(/[-:T]/g, '').replace(/\..+$/, '');
42
56
  }
@@ -50,14 +50,13 @@ export function updateInlineTestData(targetFileFromRepoRoot: string, itemName: s
50
50
  }
51
51
 
52
52
  /**
53
- * Updates the sample Prover.toml files in noir-projects/noir-protocol-circuits/crates/.
53
+ * Updates the sample Prover.toml files in noir-projects/fnd/noir-protocol-circuits/crates/.
54
54
  * @remarks Requires AZTEC_GENERATE_TEST_DATA=1 to be set
55
- * To re-gen, run 'AZTEC_GENERATE_TEST_DATA=1 FAKE_PROOFS=1 yarn test:e2e full.test'
56
- * To re-gen public base only, run 'AZTEC_GENERATE_TEST_DATA=1 yarn workspace @aztec/prover-client test orchestrator_public_functions'
55
+ * To re-gen, run 'AZTEC_GENERATE_TEST_DATA=1 FAKE_PROOFS=1 yarn test:e2e e2e_prover/full.test'
57
56
  */
58
57
  export function updateProtocolCircuitSampleInputs(circuitName: string, value: string) {
59
58
  const logger = createConsoleLogger('aztec:testing:test_data');
60
- const targetFileFromRepoRoot = `noir-projects/noir-protocol-circuits/crates/${circuitName}/Prover.toml`;
59
+ const targetFileFromRepoRoot = `noir-projects/fnd/noir-protocol-circuits/crates/${circuitName}/Prover.toml`;
61
60
  const targetFile = getPathToFile(targetFileFromRepoRoot);
62
61
  writeFileSync(targetFile, value);
63
62
  logger(`Updated test data in ${targetFile} for ${circuitName}`);
@@ -38,14 +38,6 @@ export class MembershipWitness<N extends number> {
38
38
  return [new Fr(this.leafIndex), ...this.siblingPath];
39
39
  }
40
40
 
41
- /**
42
- * Returns a representation of the membership witness as expected by intrinsic Noir deserialization.
43
- */
44
- public toNoirRepresentation(): (string | string[])[] {
45
- // TODO(#12874): remove the stupid as string conversion by modifying ForeignCallOutput type in acvm.js
46
- return [new Fr(this.leafIndex).toString() as string, this.siblingPath.map(fr => fr.toString()) as string[]];
47
- }
48
-
49
41
  static schemaFor<N extends number>(size: N) {
50
42
  return schemas.Buffer.transform(b => MembershipWitness.fromBuffer(b, size));
51
43
  }
@@ -3,15 +3,18 @@ import { z } from 'zod';
3
3
  import { makeTuple } from '../array/array.js';
4
4
  import { Fr } from '../curves/bn254/index.js';
5
5
  import { schemas } from '../schemas/index.js';
6
- import {
7
- type Tuple,
8
- assertLength,
9
- deserializeArrayFromVector,
10
- serializeArrayOfBufferableToVector,
11
- } from '../serialize/index.js';
6
+ import { BufferReader, type Tuple, assertLength, serializeArrayOfBufferableToVector } from '../serialize/index.js';
12
7
  import { bufferToHex, hexToBuffer } from '../string/index.js';
13
8
  import type { Hasher } from './hasher.js';
14
9
 
10
+ /**
11
+ * Upper bound on the elements a serialized sibling path may declare. The deepest protocol tree is 42 levels,
12
+ * and the stacked path proving L2-to-L1 message inclusion spans four unbalanced trees, so this leaves room to
13
+ * spare while keeping a malformed length prefix from driving a large allocation. `stdlib` asserts that it stays
14
+ * above every protocol tree height, which this package cannot check itself without depending on `@aztec/constants`.
15
+ */
16
+ export const MAX_SIBLING_PATH_LENGTH = 128;
17
+
15
18
  /**
16
19
  * Contains functionality to compute and serialize/deserialize a sibling path.
17
20
  * E.g. Sibling path for a leaf at index 3 in a tree of depth 3 consists of:
@@ -118,26 +121,12 @@ export class SiblingPath<N extends number> {
118
121
  * @param buf - A buffer containing the buffer representation of SiblingPath.
119
122
  * @param offset - An offset to start deserializing from.
120
123
  * @returns A SiblingPath object.
124
+ * @throws If the length prefix exceeds MAX_SIBLING_PATH_LENGTH or the buffer holds fewer elements than it declares.
121
125
  */
122
126
  static fromBuffer<N extends number>(buf: Buffer, offset = 0): SiblingPath<N> {
123
- const { elem } = SiblingPath.deserialize<N>(buf, offset);
124
- return elem;
125
- }
126
-
127
- /**
128
- * Deserializes a SiblingPath object from a slice of a part of a buffer and returns the amount of bytes advanced.
129
- * @param buf - A buffer representation of the sibling path.
130
- * @param offset - An offset to start deserializing from.
131
- * @returns The deserialized sibling path and the number of bytes advanced.
132
- */
133
- static deserialize<N extends number>(buf: Buffer, offset = 0) {
134
- const deserializePath = (buf: Buffer, offset: number) => ({
135
- elem: buf.slice(offset, offset + 32),
136
- adv: 32,
137
- });
138
- const { elem, adv } = deserializeArrayFromVector(deserializePath, buf, offset);
139
- const size = elem.length;
140
- return { elem: new SiblingPath<N>(size as N, elem), adv };
127
+ const reader = new BufferReader(buf, offset);
128
+ const path = reader.readVector({ fromBuffer: r => r.readBytes(Fr.SIZE_IN_BYTES) }, MAX_SIBLING_PATH_LENGTH);
129
+ return new SiblingPath<N>(path.length as N, path);
141
130
  }
142
131
 
143
132
  /**
@@ -29,13 +29,43 @@ export function isErrorClass<T extends Error>(value: unknown, errorClass: new (.
29
29
  return value instanceof errorClass || (value instanceof Error && value.name === errorClass.name);
30
30
  }
31
31
 
32
+ const MAX_ERR_DEPTH = 10;
33
+
34
+ /** Returns the first error in the cause chain matching the given error class. */
35
+ export function getErrorCause<T extends Error>(err: unknown, errorClass: new (...args: any[]) => T): T | undefined {
36
+ let current = err;
37
+ for (let i = 0; current !== undefined && current !== null && i < MAX_ERR_DEPTH; i++) {
38
+ if (isErrorClass(current, errorClass)) {
39
+ return current;
40
+ }
41
+
42
+ if (typeof current === 'object' && Object.hasOwn(current, 'cause')) {
43
+ current = (current as { cause: unknown }).cause;
44
+ } else {
45
+ return undefined;
46
+ }
47
+ }
48
+ return undefined;
49
+ }
50
+
32
51
  /** Resolves a record-like type. Lifted from viem. */
33
52
  export type Prettify<T> = {
34
53
  [K in keyof T]: T[K];
35
54
  } & {};
36
55
 
56
+ /** Returns a type T based on a flag: T if true, undefined if false, optional otherwise. */
57
+ export type DefineIfFlag<Opts, Key extends keyof Opts, T> = Opts extends {
58
+ [K in Key]: true;
59
+ }
60
+ ? T
61
+ : Opts extends { [K in Key]: false }
62
+ ? never
63
+ : Opts extends { [K in Key]?: boolean }
64
+ ? T | undefined
65
+ : never;
66
+
37
67
  /** Returns a type with fields conditionally required based on a flag */
38
- export type IfFlag<
68
+ export type PickIfFlag<
39
69
  OptsSchema,
40
70
  Opts extends OptsSchema,
41
71
  Key extends keyof OptsSchema,
@@ -48,6 +78,9 @@ export type IfFlag<
48
78
  ? Partial<Field>
49
79
  : {};
50
80
 
81
+ /** Picks only the defined (non-undefined) properties of a type. */
82
+ export type PickDefined<T> = Prettify<Pick<T, { [K in keyof T]: T[K] extends undefined ? never : K }[keyof T]>>;
83
+
51
84
  /**
52
85
  * Type-safe Event Emitter type
53
86
  * @example