@aztec/foundation 0.0.1-commit.e558bd1c → 0.0.1-commit.e5a3663dd

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 (221) hide show
  1. package/dest/array/sorted_array.d.ts +6 -1
  2. package/dest/array/sorted_array.d.ts.map +1 -1
  3. package/dest/array/sorted_array.js +18 -15
  4. package/dest/branded-types/buffer32_hash.d.ts +24 -0
  5. package/dest/branded-types/buffer32_hash.d.ts.map +1 -0
  6. package/dest/branded-types/buffer32_hash.js +19 -0
  7. package/dest/branded-types/checkpoint_number.d.ts +4 -1
  8. package/dest/branded-types/checkpoint_number.d.ts.map +1 -1
  9. package/dest/branded-types/checkpoint_number.js +3 -0
  10. package/dest/branded-types/index.d.ts +2 -1
  11. package/dest/branded-types/index.d.ts.map +1 -1
  12. package/dest/branded-types/index.js +1 -0
  13. package/dest/branded-types/slot.d.ts +4 -1
  14. package/dest/branded-types/slot.d.ts.map +1 -1
  15. package/dest/branded-types/slot.js +3 -0
  16. package/dest/buffer/buffer16.d.ts +3 -1
  17. package/dest/buffer/buffer16.d.ts.map +1 -1
  18. package/dest/buffer/buffer32.d.ts +28 -61
  19. package/dest/buffer/buffer32.d.ts.map +1 -1
  20. package/dest/buffer/buffer32.js +26 -60
  21. package/dest/buffer/index.d.ts +2 -1
  22. package/dest/buffer/index.d.ts.map +1 -1
  23. package/dest/buffer/index.js +1 -0
  24. package/dest/buffer/utils.d.ts +3 -0
  25. package/dest/buffer/utils.d.ts.map +1 -0
  26. package/dest/buffer/utils.js +7 -0
  27. package/dest/collection/array.d.ts +3 -1
  28. package/dest/collection/array.d.ts.map +1 -1
  29. package/dest/collection/array.js +15 -0
  30. package/dest/collection/index.d.ts +2 -1
  31. package/dest/collection/index.d.ts.map +1 -1
  32. package/dest/collection/index.js +1 -0
  33. package/dest/collection/lru_set.d.ts +35 -0
  34. package/dest/collection/lru_set.d.ts.map +1 -0
  35. package/dest/collection/lru_set.js +95 -0
  36. package/dest/config/env_var.d.ts +2 -2
  37. package/dest/config/env_var.d.ts.map +1 -1
  38. package/dest/config/index.d.ts +48 -30
  39. package/dest/config/index.d.ts.map +1 -1
  40. package/dest/config/index.js +35 -29
  41. package/dest/config/network_config.d.ts +19 -1
  42. package/dest/config/network_config.d.ts.map +1 -1
  43. package/dest/config/network_config.js +4 -1
  44. package/dest/config/network_name.d.ts +2 -2
  45. package/dest/config/network_name.d.ts.map +1 -1
  46. package/dest/config/network_name.js +2 -0
  47. package/dest/crypto/aes128/index.d.ts +2 -1
  48. package/dest/crypto/aes128/index.d.ts.map +1 -1
  49. package/dest/crypto/aes128/index.js +11 -2
  50. package/dest/crypto/bls/bn254_keystore.d.ts +1 -10
  51. package/dest/crypto/bls/bn254_keystore.d.ts.map +1 -1
  52. package/dest/crypto/bls/bn254_keystore.js +0 -17
  53. package/dest/crypto/poseidon/index.d.ts +1 -1
  54. package/dest/crypto/poseidon/index.d.ts.map +1 -1
  55. package/dest/crypto/poseidon/index.js +40 -33
  56. package/dest/crypto/secp256k1-signer/utils.d.ts +12 -1
  57. package/dest/crypto/secp256k1-signer/utils.d.ts.map +1 -1
  58. package/dest/crypto/secp256k1-signer/utils.js +26 -0
  59. package/dest/curves/bls12/field.d.ts +1 -1
  60. package/dest/curves/bls12/field.d.ts.map +1 -1
  61. package/dest/curves/bls12/field.js +0 -5
  62. package/dest/curves/bn254/field.d.ts +18 -16
  63. package/dest/curves/bn254/field.d.ts.map +1 -1
  64. package/dest/curves/bn254/field.js +23 -15
  65. package/dest/curves/grumpkin/point.d.ts +4 -4
  66. package/dest/curves/grumpkin/point.d.ts.map +1 -1
  67. package/dest/curves/grumpkin/point.js +7 -4
  68. package/dest/eth-address/index.d.ts +1 -1
  69. package/dest/eth-address/index.d.ts.map +1 -1
  70. package/dest/eth-address/index.js +0 -3
  71. package/dest/eth-signature/eth_signature.d.ts +2 -1
  72. package/dest/eth-signature/eth_signature.d.ts.map +1 -1
  73. package/dest/eth-signature/eth_signature.js +7 -2
  74. package/dest/fifo/fifo_frame_reader.d.ts +41 -0
  75. package/dest/fifo/fifo_frame_reader.d.ts.map +1 -0
  76. package/dest/fifo/fifo_frame_reader.js +74 -0
  77. package/dest/fifo/index.d.ts +2 -0
  78. package/dest/fifo/index.d.ts.map +1 -0
  79. package/dest/fifo/index.js +1 -0
  80. package/dest/jest/setup.js +24 -0
  81. package/dest/json-rpc/client/fetch.d.ts +1 -1
  82. package/dest/json-rpc/client/fetch.d.ts.map +1 -1
  83. package/dest/json-rpc/client/fetch.js +4 -3
  84. package/dest/json-rpc/client/safe_json_rpc_client.d.ts +2 -1
  85. package/dest/json-rpc/client/safe_json_rpc_client.d.ts.map +1 -1
  86. package/dest/json-rpc/client/safe_json_rpc_client.js +1 -1
  87. package/dest/json-rpc/client/undici.d.ts +1 -1
  88. package/dest/json-rpc/client/undici.d.ts.map +1 -1
  89. package/dest/json-rpc/client/undici.js +5 -3
  90. package/dest/json-rpc/server/api_key_auth.d.ts +19 -0
  91. package/dest/json-rpc/server/api_key_auth.d.ts.map +1 -0
  92. package/dest/json-rpc/server/api_key_auth.js +57 -0
  93. package/dest/json-rpc/server/index.d.ts +2 -1
  94. package/dest/json-rpc/server/index.d.ts.map +1 -1
  95. package/dest/json-rpc/server/index.js +1 -0
  96. package/dest/log/bigint-utils.d.ts +5 -0
  97. package/dest/log/bigint-utils.d.ts.map +1 -0
  98. package/dest/log/bigint-utils.js +21 -0
  99. package/dest/log/gcloud-logger-config.d.ts +1 -1
  100. package/dest/log/gcloud-logger-config.d.ts.map +1 -1
  101. package/dest/log/gcloud-logger-config.js +3 -0
  102. package/dest/log/log-filters.d.ts +17 -4
  103. package/dest/log/log-filters.d.ts.map +1 -1
  104. package/dest/log/log-filters.js +26 -12
  105. package/dest/log/pino-logger.d.ts +1 -1
  106. package/dest/log/pino-logger.d.ts.map +1 -1
  107. package/dest/log/pino-logger.js +23 -4
  108. package/dest/queue/base_memory_queue.d.ts +2 -2
  109. package/dest/queue/base_memory_queue.d.ts.map +1 -1
  110. package/dest/queue/batch_queue.d.ts +1 -1
  111. package/dest/queue/batch_queue.d.ts.map +1 -1
  112. package/dest/queue/batch_queue.js +1 -0
  113. package/dest/retry/index.d.ts +12 -1
  114. package/dest/retry/index.d.ts.map +1 -1
  115. package/dest/retry/index.js +21 -0
  116. package/dest/schemas/api.d.ts +2 -2
  117. package/dest/schemas/api.d.ts.map +1 -1
  118. package/dest/serialize/buffer_reader.d.ts +8 -1
  119. package/dest/serialize/buffer_reader.d.ts.map +1 -1
  120. package/dest/serialize/buffer_reader.js +13 -0
  121. package/dest/serialize/serialize.d.ts +19 -1
  122. package/dest/serialize/serialize.d.ts.map +1 -1
  123. package/dest/serialize/serialize.js +31 -0
  124. package/dest/sleep/index.d.ts +2 -1
  125. package/dest/sleep/index.d.ts.map +1 -1
  126. package/dest/sleep/index.js +10 -1
  127. package/dest/string/index.js +1 -1
  128. package/dest/timer/date.d.ts +3 -1
  129. package/dest/timer/date.d.ts.map +1 -1
  130. package/dest/timer/date.js +4 -0
  131. package/dest/transport/dispatch/create_dispatch_proxy.d.ts +11 -2
  132. package/dest/transport/dispatch/create_dispatch_proxy.d.ts.map +1 -1
  133. package/dest/transport/index.d.ts +1 -2
  134. package/dest/transport/index.d.ts.map +1 -1
  135. package/dest/transport/index.js +0 -1
  136. package/dest/transport/transport_client.js +2 -2
  137. package/dest/trees/balanced_merkle_tree_root.d.ts +2 -3
  138. package/dest/trees/balanced_merkle_tree_root.d.ts.map +1 -1
  139. package/dest/trees/balanced_merkle_tree_root.js +2 -3
  140. package/dest/trees/hasher.d.ts +3 -2
  141. package/dest/trees/hasher.d.ts.map +1 -1
  142. package/dest/trees/hasher.js +5 -5
  143. package/dest/trees/indexed_merkle_tree_calculator.d.ts +1 -1
  144. package/dest/trees/indexed_merkle_tree_calculator.d.ts.map +1 -1
  145. package/dest/trees/indexed_merkle_tree_calculator.js +5 -1
  146. package/dest/trees/merkle_tree_calculator.d.ts +4 -2
  147. package/dest/trees/merkle_tree_calculator.d.ts.map +1 -1
  148. package/dest/trees/merkle_tree_calculator.js +1 -5
  149. package/dest/trees/sibling_path.d.ts +2 -2
  150. package/dest/trees/sibling_path.d.ts.map +1 -1
  151. package/dest/trees/sibling_path.js +1 -5
  152. package/dest/trees/unbalanced_merkle_tree_root.d.ts +2 -3
  153. package/dest/trees/unbalanced_merkle_tree_root.d.ts.map +1 -1
  154. package/dest/trees/unbalanced_merkle_tree_root.js +2 -3
  155. package/dest/types/index.d.ts +9 -1
  156. package/dest/types/index.d.ts.map +1 -1
  157. package/package.json +3 -2
  158. package/src/array/sorted_array.ts +22 -17
  159. package/src/branded-types/buffer32_hash.ts +42 -0
  160. package/src/branded-types/checkpoint_number.ts +5 -0
  161. package/src/branded-types/index.ts +1 -0
  162. package/src/branded-types/slot.ts +5 -0
  163. package/src/buffer/buffer16.ts +3 -0
  164. package/src/buffer/buffer32.ts +36 -65
  165. package/src/buffer/index.ts +1 -0
  166. package/src/buffer/utils.ts +8 -0
  167. package/src/collection/array.ts +14 -0
  168. package/src/collection/index.ts +1 -0
  169. package/src/collection/lru_set.ts +115 -0
  170. package/src/config/env_var.ts +50 -13
  171. package/src/config/index.ts +110 -83
  172. package/src/config/network_config.ts +3 -0
  173. package/src/config/network_name.ts +4 -1
  174. package/src/crypto/aes128/index.ts +11 -2
  175. package/src/crypto/bls/bn254_keystore.ts +0 -23
  176. package/src/crypto/poseidon/index.ts +42 -34
  177. package/src/crypto/secp256k1-signer/utils.ts +32 -0
  178. package/src/curves/bls12/field.ts +0 -7
  179. package/src/curves/bn254/field.ts +31 -33
  180. package/src/curves/grumpkin/point.ts +6 -3
  181. package/src/eth-address/index.ts +0 -4
  182. package/src/eth-signature/eth_signature.ts +7 -1
  183. package/src/fifo/fifo_frame_reader.ts +98 -0
  184. package/src/fifo/index.ts +1 -0
  185. package/src/jest/setup.mjs +27 -0
  186. package/src/json-rpc/client/fetch.ts +4 -3
  187. package/src/json-rpc/client/safe_json_rpc_client.ts +2 -0
  188. package/src/json-rpc/client/undici.ts +5 -3
  189. package/src/json-rpc/server/api_key_auth.ts +63 -0
  190. package/src/json-rpc/server/index.ts +1 -0
  191. package/src/log/bigint-utils.ts +25 -0
  192. package/src/log/gcloud-logger-config.ts +5 -0
  193. package/src/log/log-filters.ts +29 -11
  194. package/src/log/pino-logger.ts +25 -4
  195. package/src/queue/base_memory_queue.ts +1 -1
  196. package/src/queue/batch_queue.ts +1 -0
  197. package/src/retry/index.ts +30 -0
  198. package/src/schemas/api.ts +4 -1
  199. package/src/serialize/buffer_reader.ts +15 -0
  200. package/src/serialize/serialize.ts +32 -0
  201. package/src/sleep/index.ts +10 -1
  202. package/src/string/index.ts +1 -1
  203. package/src/timer/date.ts +6 -0
  204. package/src/transport/dispatch/create_dispatch_proxy.ts +11 -1
  205. package/src/transport/index.ts +0 -1
  206. package/src/transport/transport_client.ts +2 -2
  207. package/src/trees/balanced_merkle_tree_root.ts +2 -5
  208. package/src/trees/hasher.ts +6 -3
  209. package/src/trees/indexed_merkle_tree_calculator.ts +5 -1
  210. package/src/trees/merkle_tree_calculator.ts +4 -10
  211. package/src/trees/sibling_path.ts +1 -2
  212. package/src/trees/unbalanced_merkle_tree_root.ts +2 -5
  213. package/src/types/index.ts +14 -0
  214. package/dest/crypto/serialize.d.ts +0 -51
  215. package/dest/crypto/serialize.d.ts.map +0 -1
  216. package/dest/crypto/serialize.js +0 -68
  217. package/dest/transport/dispatch/create_dispatch_fn.d.ts +0 -25
  218. package/dest/transport/dispatch/create_dispatch_fn.d.ts.map +0 -1
  219. package/dest/transport/dispatch/create_dispatch_fn.js +0 -17
  220. package/src/crypto/serialize.ts +0 -85
  221. package/src/transport/dispatch/create_dispatch_fn.ts +0 -35
@@ -6,9 +6,6 @@ import { toBigIntBE, toBufferBE } from '../../bigint-buffer/index.js';
6
6
  import { randomBytes } from '../../crypto/random/index.js';
7
7
  import { hexSchemaFor } from '../../schemas/utils.js';
8
8
  import { BufferReader } from '../../serialize/buffer_reader.js';
9
- import { TypeRegistry } from '../../serialize/type_registry.js';
10
-
11
- /* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
12
9
 
13
10
  /**
14
11
  * Represents a field derived from BaseField.
@@ -118,14 +115,18 @@ abstract class BaseField {
118
115
  }
119
116
 
120
117
  cmp(rhs: BaseField): -1 | 0 | 1 {
121
- const rhsBigInt = rhs.asBigInt;
122
- return this.asBigInt === rhsBigInt ? 0 : this.asBigInt < rhsBigInt ? -1 : 1;
118
+ return BaseField.cmpAsBigInt(this.asBigInt, rhs.asBigInt);
123
119
  }
124
120
 
125
121
  static cmp(lhs: BaseField, rhs: BaseField): -1 | 0 | 1 {
126
122
  return lhs.cmp(rhs);
127
123
  }
128
124
 
125
+ // Actual bigint comparison. Arguments must have been validated previously.
126
+ static cmpAsBigInt(lhs: bigint, rhs: bigint): -1 | 0 | 1 {
127
+ return lhs === rhs ? 0 : lhs < rhs ? -1 : 1;
128
+ }
129
+
129
130
  isZero(): boolean {
130
131
  return this.asBigInt === 0n;
131
132
  }
@@ -183,10 +184,25 @@ function fromHexString<T extends BaseField>(buf: string, f: DerivedField<T>) {
183
184
  return new f(toBigIntBE(buffer));
184
185
  }
185
186
 
186
- /** Branding to ensure fields are not interchangeable types. */
187
- export interface Fr {
188
- /** Brand. */
189
- _branding: 'Fr';
187
+ /**
188
+ * Abstract unbranded base class for BN254 scalar field elements.
189
+ * Extend this instead of Fr when defining a branded subtype (e.g. BlockHash)
190
+ * to avoid inheriting Fr's `_branding`.
191
+ */
192
+ export abstract class BaseFr extends BaseField {
193
+ static MODULUS = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n;
194
+
195
+ constructor(value: number | bigint | boolean | BaseField | Buffer) {
196
+ super(value);
197
+ }
198
+
199
+ protected modulus() {
200
+ return BaseFr.MODULUS;
201
+ }
202
+
203
+ toJSON() {
204
+ return this.toString();
205
+ }
190
206
  }
191
207
 
192
208
  /**
@@ -194,10 +210,12 @@ export interface Fr {
194
210
  * @dev This class is used to represent elements of BN254 scalar field or elements in the base field of Grumpkin.
195
211
  * (Grumpkin's scalar field corresponds to BN254's base field and vice versa.)
196
212
  */
197
- export class Fr extends BaseField {
213
+ export class Fr extends BaseFr {
214
+ /** Branding for nominal typing. */
215
+ declare private readonly _branding: 'Fr';
216
+ static override MODULUS = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n;
198
217
  static ZERO = new Fr(0n);
199
218
  static ONE = new Fr(1n);
200
- static MODULUS = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n;
201
219
  static MAX_FIELD_VALUE = new Fr(this.MODULUS - 1n);
202
220
 
203
221
  constructor(value: number | bigint | boolean | Fr | Buffer) {
@@ -208,10 +226,6 @@ export class Fr extends BaseField {
208
226
  return `Fr<${this.toString()}>`;
209
227
  }
210
228
 
211
- protected modulus() {
212
- return Fr.MODULUS;
213
- }
214
-
215
229
  static random() {
216
230
  return random(Fr);
217
231
  }
@@ -316,10 +330,6 @@ export class Fr extends BaseField {
316
330
  return Fr.fromBuffer(Buffer.from(response.value));
317
331
  }
318
332
 
319
- toJSON() {
320
- return this.toString();
321
- }
322
-
323
333
  /**
324
334
  * Creates an Fr instance from a plain object without Zod validation.
325
335
  * This method is optimized for performance and skips validation, making it suitable
@@ -341,23 +351,14 @@ export class Fr extends BaseField {
341
351
  }
342
352
  }
343
353
 
344
- // For deserializing JSON.
345
- TypeRegistry.register('Fr', Fr);
346
-
347
- /**
348
- * Branding to ensure fields are not interchangeable types.
349
- */
350
- export interface Fq {
351
- /** Brand. */
352
- _branding: 'Fq';
353
- }
354
-
355
354
  /**
356
355
  * Fq field class.
357
356
  * @dev This class is used to represent elements of BN254 base field or elements in the scalar field of Grumpkin.
358
357
  * (Grumpkin's scalar field corresponds to BN254's base field and vice versa.)
359
358
  */
360
359
  export class Fq extends BaseField {
360
+ /** Branding for nominal typing. */
361
+ declare private readonly _branding: 'Fq';
361
362
  static ZERO = new Fq(0n);
362
363
  static MODULUS = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47n;
363
364
  private static HIGH_SHIFT = BigInt((BaseField.SIZE_IN_BYTES / 2) * 8);
@@ -466,9 +467,6 @@ export class Fq extends BaseField {
466
467
  }
467
468
  }
468
469
 
469
- // For deserializing JSON.
470
- TypeRegistry.register('Fq', Fq);
471
-
472
470
  // Beware: Performance bottleneck below
473
471
 
474
472
  /**
@@ -65,7 +65,7 @@ export class Point {
65
65
  }
66
66
 
67
67
  /**
68
- * Generate a random Point instance.
68
+ * Generate a random Point instance that is on the curve.
69
69
  *
70
70
  * @returns A randomly generated Point instance.
71
71
  */
@@ -125,9 +125,12 @@ export class Point {
125
125
  }
126
126
 
127
127
  /**
128
- * Returns the contents of the point as an array of 2 fields.
129
- * @returns The point as an array of 2 fields
128
+ * Returns the contents of the point as an array of 3 fields.
129
+ * @returns The point as an array of 3 fields
130
130
  */
131
+ // TODO(F-553): Once we take the breaking change and drop the custom Noir `Point` wrapper in
132
+ // `noir-projects/noir-protocol-circuits/crates/types/src/point.nr`, revert this to serialize as `[x, y]` (2 fields)
133
+ // and drop the `is_infinite` flag from `fromFields` / `toNoirStruct` below.
131
134
  toFields() {
132
135
  return [this.x, this.y, new Fr(this.isInfinite)];
133
136
  }
@@ -5,7 +5,6 @@ import { randomBytes } from '../crypto/random/index.js';
5
5
  import { Fr } from '../curves/bn254/index.js';
6
6
  import { hexSchemaFor } from '../schemas/utils.js';
7
7
  import { BufferReader, FieldReader } from '../serialize/index.js';
8
- import { TypeRegistry } from '../serialize/type_registry.js';
9
8
  import { bufferToHex } from '../string/index.js';
10
9
 
11
10
  /**
@@ -268,6 +267,3 @@ export class EthAddress {
268
267
  return addrA.equals(addrB);
269
268
  }
270
269
  }
271
-
272
- // For deserializing JSON.
273
- TypeRegistry.register('EthAddress', EthAddress);
@@ -1,8 +1,10 @@
1
1
  import { Buffer32 } from '@aztec/foundation/buffer';
2
2
  import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
3
3
 
4
+ import { secp256k1 } from '@noble/curves/secp256k1';
4
5
  import { z } from 'zod';
5
6
 
7
+ import { randomBytes } from '../crypto/random/index.js';
6
8
  import { hasHexPrefix, hexToBuffer } from '../string/index.js';
7
9
 
8
10
  /**
@@ -77,8 +79,12 @@ export class Signature {
77
79
  return new Signature(Buffer32.fromBuffer(hexToBuffer(sig.r)), Buffer32.fromBuffer(hexToBuffer(sig.s)), sig.yParity);
78
80
  }
79
81
 
82
+ /** Generates a random valid ECDSA signature with a low s-value by signing a random message with a random key. */
80
83
  static random(): Signature {
81
- return new Signature(Buffer32.random(), Buffer32.random(), 1);
84
+ const privateKey = randomBytes(32);
85
+ const message = randomBytes(32);
86
+ const { r, s, recovery } = secp256k1.sign(message, privateKey);
87
+ return new Signature(Buffer32.fromBigInt(r), Buffer32.fromBigInt(s), recovery ? 28 : 27);
82
88
  }
83
89
 
84
90
  static empty(): Signature {
@@ -0,0 +1,98 @@
1
+ import EventEmitter from 'node:events';
2
+ import * as fs from 'node:fs';
3
+ import type { Readable } from 'node:stream';
4
+
5
+ /**
6
+ * Events emitted by FifoFrameReader.
7
+ *
8
+ * - `frame`: A complete frame payload (without the 4-byte length header).
9
+ * - `error`: An unrecoverable error (invalid frame length, stream error).
10
+ * - `end`: The underlying stream has ended.
11
+ */
12
+ export interface FifoFrameReaderEvents {
13
+ frame: [payload: Buffer];
14
+ error: [error: Error];
15
+ end: [];
16
+ }
17
+
18
+ /**
19
+ * Reads length-delimited frames from a readable stream (typically a named FIFO pipe).
20
+ *
21
+ * Wire format: `[4-byte big-endian payload length][payload bytes]`
22
+ *
23
+ * Emits a `frame` event for each complete frame with the raw payload buffer.
24
+ * Callers are responsible for deserializing the payload (e.g., via msgpack).
25
+ *
26
+ * On encountering an invalid payload length (0 or >maxPayloadSize), emits `error`
27
+ * and destroys the stream.
28
+ */
29
+ export class FifoFrameReader extends EventEmitter<FifoFrameReaderEvents> {
30
+ private stream: Readable | null = null;
31
+ private pendingBuf: Buffer = Buffer.alloc(0);
32
+ private running = false;
33
+
34
+ constructor(private readonly maxPayloadSize = 10 * 1024 * 1024) {
35
+ super();
36
+ }
37
+
38
+ /** 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 }));
41
+ }
42
+
43
+ /** Start reading frames from an existing readable stream. */
44
+ startFromStream(stream: Readable): void {
45
+ if (this.running) {
46
+ throw new Error('FifoFrameReader is already running');
47
+ }
48
+ this.running = true;
49
+ this.pendingBuf = Buffer.alloc(0);
50
+ this.stream = stream;
51
+
52
+ stream.on('data', (chunk: Buffer | string) => {
53
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
54
+ this.pendingBuf = this.pendingBuf.length > 0 ? Buffer.concat([this.pendingBuf, buf]) : buf;
55
+ this.drainFrames();
56
+ });
57
+
58
+ stream.on('error', (err: Error) => {
59
+ if (this.running) {
60
+ this.emit('error', err);
61
+ }
62
+ });
63
+
64
+ stream.on('end', () => {
65
+ this.emit('end');
66
+ });
67
+ }
68
+
69
+ /** Stop reading and destroy the underlying stream. */
70
+ stop(): void {
71
+ this.running = false;
72
+ if (this.stream) {
73
+ this.stream.destroy();
74
+ this.stream = null;
75
+ }
76
+ }
77
+
78
+ /** Parse complete frames out of the pending buffer. */
79
+ private drainFrames(): void {
80
+ while (this.pendingBuf.length >= 4) {
81
+ const payloadLen = this.pendingBuf.readUInt32BE(0);
82
+ if (payloadLen === 0 || payloadLen > this.maxPayloadSize) {
83
+ this.emit('error', new Error(`Invalid payload length: ${payloadLen}`));
84
+ this.stop();
85
+ return;
86
+ }
87
+
88
+ const frameLen = 4 + payloadLen;
89
+ if (this.pendingBuf.length < frameLen) {
90
+ break; // Wait for more data
91
+ }
92
+
93
+ const payload = this.pendingBuf.subarray(4, frameLen);
94
+ this.pendingBuf = this.pendingBuf.subarray(frameLen);
95
+ this.emit('frame', Buffer.from(payload));
96
+ }
97
+ }
98
+ }
@@ -0,0 +1 @@
1
+ export { FifoFrameReader } from './fifo_frame_reader.js';
@@ -10,3 +10,30 @@ import pretty from 'pino-pretty';
10
10
  if (!parseBooleanEnv(process.env.LOG_JSON)) {
11
11
  overwriteLoggingStream(pretty(pinoPrettyOpts));
12
12
  }
13
+
14
+ // Prevent timers from keeping the process alive after tests complete.
15
+ // Libraries like viem create internal polling loops (via setTimeout) that
16
+ // reschedule themselves indefinitely. In test environments we never want a
17
+ // timer to be the reason the process can't exit. We also unref stdout/stderr
18
+ // which, when they are pipes (as in Jest workers), remain ref'd by default.
19
+ {
20
+ const origSetTimeout = globalThis.setTimeout;
21
+ const origSetInterval = globalThis.setInterval;
22
+ globalThis.setTimeout = function unrefSetTimeout(...args) {
23
+ const id = origSetTimeout.apply(this, args);
24
+ id?.unref?.();
25
+ return id;
26
+ };
27
+ // Preserve .unref, .__promisify__ etc. that may exist on the original
28
+ Object.setPrototypeOf(globalThis.setTimeout, origSetTimeout);
29
+
30
+ globalThis.setInterval = function unrefSetInterval(...args) {
31
+ const id = origSetInterval.apply(this, args);
32
+ id?.unref?.();
33
+ return id;
34
+ };
35
+ Object.setPrototypeOf(globalThis.setInterval, origSetInterval);
36
+
37
+ if (process.stdout?._handle?.unref) process.stdout._handle.unref();
38
+ if (process.stderr?._handle?.unref) process.stderr._handle.unref();
39
+ }
@@ -43,17 +43,18 @@ export async function defaultFetch(
43
43
  }
44
44
 
45
45
  let responseJson;
46
+ const responseText = await resp.text();
46
47
  try {
47
- responseJson = await resp.json();
48
+ responseJson = JSON.parse(responseText);
48
49
  } catch {
49
50
  if (!resp.ok) {
50
51
  throw new Error(resp.statusText);
51
52
  }
52
- throw new Error(`Failed to parse body as JSON: ${await resp.text()}`);
53
+ throw new Error(`Failed to parse body as JSON: ${responseText}`);
53
54
  }
54
55
 
55
56
  if (!resp.ok) {
56
- const errorMessage = `Error ${resp.status} from server ${host}: ${responseJson.error.message}`;
57
+ const errorMessage = `Error ${resp.status} from server ${host}: ${responseJson?.error?.message ?? JSON.stringify(responseJson)}`;
57
58
  if (noRetry || (resp.status >= 400 && resp.status < 500)) {
58
59
  throw new NoRetryError(errorMessage);
59
60
  } else {
@@ -24,6 +24,7 @@ export type SafeJsonRpcClientOptions = {
24
24
  batchWindowMS?: number;
25
25
  maxBatchSize?: number;
26
26
  maxRequestBodySize?: number;
27
+ extraHeaders?: Record<string, string>;
27
28
  onResponse?: (res: {
28
29
  response: any;
29
30
  headers: { get: (header: string) => string | null | undefined };
@@ -129,6 +130,7 @@ export function createSafeJsonRpcClient<T extends object>(
129
130
  const { response, headers } = await fetch(
130
131
  host,
131
132
  rpcCalls.map(({ request }) => request),
133
+ config.extraHeaders,
132
134
  );
133
135
 
134
136
  if (config.onResponse) {
@@ -44,18 +44,20 @@ export function makeUndiciFetch(client = new Agent()): JsonRpcFetch {
44
44
  let responseJson: any;
45
45
  const responseOk = resp.statusCode >= 200 && resp.statusCode <= 299;
46
46
  const contentEncoding = resp.headers['content-encoding'];
47
+ let responseText: string;
47
48
  try {
48
49
  if (contentEncoding === 'gzip') {
49
50
  const jsonBuffer = await gunzip(await resp.body.arrayBuffer());
50
- responseJson = JSON.parse(jsonBuffer.toString('utf-8'));
51
+ responseText = jsonBuffer.toString('utf-8');
51
52
  } else {
52
- responseJson = await resp.body.json();
53
+ responseText = await resp.body.text();
53
54
  }
55
+ responseJson = JSON.parse(responseText);
54
56
  } catch {
55
57
  if (!responseOk) {
56
58
  throw new Error('HTTP ' + resp.statusCode);
57
59
  }
58
- throw new Error(`Failed to parse body as JSON. encoding: ${contentEncoding}, body: ${await resp.body.text()}`);
60
+ throw new Error(`Failed to parse body as JSON. encoding: ${contentEncoding}, body: ${responseText!}`);
59
61
  }
60
62
 
61
63
  if (!responseOk) {
@@ -0,0 +1,63 @@
1
+ import { timingSafeEqual } from 'crypto';
2
+ import type Koa from 'koa';
3
+
4
+ import { sha256 } from '../../crypto/sha256/index.js';
5
+ import { createLogger } from '../../log/index.js';
6
+
7
+ const log = createLogger('json-rpc:api-key-auth');
8
+
9
+ /**
10
+ * Computes the SHA-256 hash of a string and returns it as a Buffer.
11
+ * @param input - The input string to hash.
12
+ * @returns The SHA-256 hash as a Buffer.
13
+ */
14
+ export function sha256Hash(input: string): Buffer {
15
+ return sha256(Buffer.from(input));
16
+ }
17
+
18
+ /**
19
+ * Creates a Koa middleware that enforces API key authentication on all requests
20
+ * except the health check endpoint (GET /status).
21
+ *
22
+ * The API key can be provided via the `x-api-key` header or the `Authorization: Bearer <key>` header.
23
+ * Comparison is done by hashing the provided key with SHA-256 and comparing against the stored hash.
24
+ *
25
+ * @param apiKeyHash - The SHA-256 hash of the expected API key as a Buffer.
26
+ * @returns A Koa middleware that rejects requests without a valid API key.
27
+ */
28
+ export function getApiKeyAuthMiddleware(
29
+ apiKeyHash: Buffer,
30
+ ): (ctx: Koa.Context, next: () => Promise<void>) => Promise<void> {
31
+ return async (ctx: Koa.Context, next: () => Promise<void>) => {
32
+ // Allow health check through without auth
33
+ if (ctx.path === '/status' && ctx.method === 'GET') {
34
+ return next();
35
+ }
36
+
37
+ const providedKey = ctx.get('x-api-key') || ctx.get('authorization')?.replace(/^Bearer\s+/i, '');
38
+ if (!providedKey) {
39
+ log.warn(`Rejected admin RPC request from ${ctx.ip}: missing API key`);
40
+ ctx.status = 401;
41
+ ctx.body = {
42
+ jsonrpc: '2.0',
43
+ id: null,
44
+ error: { code: -32000, message: 'Unauthorized: invalid or missing API key' },
45
+ };
46
+ return;
47
+ }
48
+
49
+ const providedHashBuf = sha256Hash(providedKey);
50
+ if (!timingSafeEqual(apiKeyHash, providedHashBuf)) {
51
+ log.warn(`Rejected admin RPC request from ${ctx.ip}: invalid API key`);
52
+ ctx.status = 401;
53
+ ctx.body = {
54
+ jsonrpc: '2.0',
55
+ id: null,
56
+ error: { code: -32000, message: 'Unauthorized: invalid or missing API key' },
57
+ };
58
+ return;
59
+ }
60
+
61
+ await next();
62
+ };
63
+ }
@@ -1 +1,2 @@
1
+ export * from './api_key_auth.js';
1
2
  export * from './safe_json_rpc_server.js';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Converts bigint values to strings recursively in a log object to avoid serialization issues.
3
+ */
4
+ export function convertBigintsToStrings(obj: unknown): unknown {
5
+ if (typeof obj === 'bigint') {
6
+ return String(obj);
7
+ }
8
+
9
+ if (Array.isArray(obj)) {
10
+ return obj.map(item => convertBigintsToStrings(item));
11
+ }
12
+
13
+ if (obj !== null && typeof obj === 'object') {
14
+ if (typeof (obj as any).toJSON === 'function') {
15
+ return convertBigintsToStrings((obj as any).toJSON());
16
+ }
17
+ const result: Record<string, unknown> = {};
18
+ for (const key in obj) {
19
+ result[key] = convertBigintsToStrings((obj as Record<string, unknown>)[key]);
20
+ }
21
+ return result;
22
+ }
23
+
24
+ return obj;
25
+ }
@@ -1,5 +1,7 @@
1
1
  import type { pino } from 'pino';
2
2
 
3
+ import { convertBigintsToStrings } from './bigint-utils.js';
4
+
3
5
  /* eslint-disable camelcase */
4
6
 
5
7
  const GOOGLE_CLOUD_TRACE_ID = 'logging.googleapis.com/trace';
@@ -15,6 +17,9 @@ export const GoogleCloudLoggerConfig = {
15
17
  messageKey: 'message',
16
18
  formatters: {
17
19
  log(object: Record<string, unknown>): Record<string, unknown> {
20
+ // Convert bigints to strings recursively to avoid serialization issues
21
+ object = convertBigintsToStrings(object) as Record<string, unknown>;
22
+
18
23
  // Add trace context attributes following Cloud Logging structured log format described
19
24
  // in https://cloud.google.com/logging/docs/structured-logging#special-payload-fields
20
25
  const { trace_id, span_id, trace_flags, ...rest } = object;
@@ -19,22 +19,40 @@ export function getLogLevelFromFilters(filters: LogFilters, module: string): Log
19
19
  return undefined;
20
20
  }
21
21
 
22
- export function assertLogLevel(level: string): asserts level is LogLevel {
23
- if (!LogLevels.includes(level as LogLevel)) {
24
- throw new Error(`Invalid log level: ${level}`);
22
+ /**
23
+ * Parses the LOG_LEVEL env string into a default level and per-module filter overrides.
24
+ *
25
+ * Format: `<default_level>;<level>:<module1>,<module2>;<level>:<module3>;...`
26
+ * - First segment (before the first `;`) is the default log level for all modules.
27
+ * - Remaining segments are `level:module` pairs: apply the given level to the listed modules (comma-separated).
28
+ * - Later filters override earlier ones for overlapping module matches.
29
+ * - The `aztec:` prefix is stripped from module names; spaces are trimmed.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * parseLogLevel('debug;warn:module1,module2;error:module3', 'info')
34
+ * // => ['debug', [['module3', 'error'], ['module2', 'warn'], ['module1', 'warn']]]
35
+ * ```
36
+ */
37
+ export function parseLogLevelEnvVar(
38
+ logLevelEnvVar: string | undefined,
39
+ defaultLevel: LogLevel,
40
+ ): [LogLevel, LogFilters] {
41
+ if (!logLevelEnvVar) {
42
+ return [defaultLevel, []];
25
43
  }
44
+ const [level] = logLevelEnvVar.split(';', 1);
45
+ assertValidLogLevel(level);
46
+ return [level, parseFilters(logLevelEnvVar.slice(level.length + 1))];
26
47
  }
27
48
 
28
- export function parseEnv(env: string | undefined, defaultLevel: LogLevel): [LogLevel, LogFilters] {
29
- if (!env) {
30
- return [defaultLevel, []];
49
+ function assertValidLogLevel(level: string): asserts level is LogLevel {
50
+ if (!LogLevels.includes(level as LogLevel)) {
51
+ throw new Error(`Invalid log level: ${level}`);
31
52
  }
32
- const [level] = env.split(';', 1);
33
- assertLogLevel(level);
34
- return [level, parseFilters(env.slice(level.length + 1))];
35
53
  }
36
54
 
37
- export function parseFilters(definition: string | undefined): LogFilters {
55
+ function parseFilters(definition: string | undefined): LogFilters {
38
56
  if (!definition) {
39
57
  return [];
40
58
  }
@@ -48,7 +66,7 @@ export function parseFilters(definition: string | undefined): LogFilters {
48
66
  throw new Error(`Invalid log filter statement: ${statement}`);
49
67
  }
50
68
  const sanitizedLevel = level.trim().toLowerCase();
51
- assertLogLevel(sanitizedLevel);
69
+ assertValidLogLevel(sanitizedLevel);
52
70
  for (const module of modules.split(',')) {
53
71
  filters.push([
54
72
  module
@@ -7,8 +7,9 @@ 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 { convertBigintsToStrings } from './bigint-utils.js';
10
11
  import { GoogleCloudLoggerConfig } from './gcloud-logger-config.js';
11
- import { getLogLevelFromFilters, parseEnv } from './log-filters.js';
12
+ import { getLogLevelFromFilters, parseLogLevelEnvVar } from './log-filters.js';
12
13
  import type { LogLevel } from './log-levels.js';
13
14
  import type { LogData, LogFn } from './log_fn.js';
14
15
 
@@ -20,6 +21,8 @@ export type LoggerBindings = {
20
21
  instanceId?: string;
21
22
  };
22
23
 
24
+ const MAX_MODULE_NAME_LENGTH = 256;
25
+
23
26
  // Allow global hooks for providing default bindings.
24
27
  // Used by withLoggerBindings in pino-logger-server to propagate bindings via AsyncLocalStorage.
25
28
  type LogBindingsHandler = () => LoggerBindings | undefined;
@@ -47,7 +50,7 @@ function getBindingsFromHandlers(): LoggerBindings | undefined {
47
50
  }
48
51
 
49
52
  export function createLogger(module: string, bindings?: LoggerBindings): Logger {
50
- module = module.replace(/^aztec:/, '');
53
+ module = module.slice(0, MAX_MODULE_NAME_LENGTH).replace(/^aztec:/, '');
51
54
 
52
55
  const resolvedBindings = { ...getBindingsFromHandlers(), ...bindings };
53
56
  const actor = resolvedBindings?.actor;
@@ -126,7 +129,7 @@ function isLevelEnabled(logger: pino.Logger<'verbose', boolean>, level: LogLevel
126
129
 
127
130
  // Load log levels from environment variables.
128
131
  const defaultLogLevel = process.env.NODE_ENV === 'test' ? 'silent' : 'info';
129
- export const [logLevel, logFilters] = parseEnv(process.env.LOG_LEVEL, defaultLogLevel);
132
+ export const [logLevel, logFilters] = parseLogLevelEnvVar(process.env.LOG_LEVEL, defaultLogLevel);
130
133
 
131
134
  // Define custom logging levels for pino.
132
135
  const customLevels = { verbose: 25 };
@@ -165,6 +168,9 @@ const pinoOpts: pino.LoggerOptions<keyof typeof customLevels> = {
165
168
  ...redactedPaths.map(p => `opts.${p}`),
166
169
  ],
167
170
  },
171
+ formatters: {
172
+ log: obj => convertBigintsToStrings(obj) as Record<string, unknown>,
173
+ },
168
174
  ...(useGcloudLogging ? GoogleCloudLoggerConfig : {}),
169
175
  };
170
176
 
@@ -371,5 +377,20 @@ export type Logger = { [K in LogLevel]: LogFn } & { /** Error log function */ er
371
377
  * @returns A string with both the log message and the error message.
372
378
  */
373
379
  function formatErr(msg: string, err?: unknown): string {
374
- return err ? `${msg}: ${inspect(err)}` : msg;
380
+ if (!err) {
381
+ return msg;
382
+ }
383
+
384
+ try {
385
+ return `${msg}: ${inspect(err)}`;
386
+ } catch {
387
+ // inspect can crash on error objects with broken property descriptors
388
+ try {
389
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
390
+ return `${msg}: ${err instanceof Error ? `${err.name}: ${err.message}` : String(err)}`;
391
+ } catch {
392
+ // if even String(err) fails, return the original message with a note about the error
393
+ return `${msg}: [unserializable error]`;
394
+ }
395
+ }
375
396
  }
@@ -122,7 +122,7 @@ export abstract class BaseMemoryQueue<T> {
122
122
  * @param handler - A function that takes an item of type T and returns a Promise<void> after processing the item.
123
123
  * @returns A Promise<void> that resolves when the queue is finished processing.
124
124
  */
125
- public async process(handler: (item: T) => Promise<void>) {
125
+ public async process(handler: (item: T) => Promise<void> | void) {
126
126
  try {
127
127
  while (true) {
128
128
  const item = await this.get();
@@ -105,6 +105,7 @@ export class BatchQueue<T, K extends string | number> {
105
105
  return Promise.resolve();
106
106
  }
107
107
 
108
+ this.flushCurrentBatch();
108
109
  this.container.end();
109
110
  return runningPromise;
110
111
  }