@aztec/foundation 4.0.0-devnet.2-patch.3 → 4.0.0-devnet.3-patch.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dest/branded-types/slot.d.ts +4 -1
  2. package/dest/branded-types/slot.d.ts.map +1 -1
  3. package/dest/branded-types/slot.js +3 -0
  4. package/dest/buffer/index.d.ts +2 -1
  5. package/dest/buffer/index.d.ts.map +1 -1
  6. package/dest/buffer/index.js +1 -0
  7. package/dest/buffer/utils.d.ts +3 -0
  8. package/dest/buffer/utils.d.ts.map +1 -0
  9. package/dest/buffer/utils.js +7 -0
  10. package/dest/collection/array.d.ts +3 -1
  11. package/dest/collection/array.d.ts.map +1 -1
  12. package/dest/collection/array.js +15 -0
  13. package/dest/config/env_var.d.ts +2 -2
  14. package/dest/config/env_var.d.ts.map +1 -1
  15. package/dest/config/index.d.ts +1 -1
  16. package/dest/config/index.d.ts.map +1 -1
  17. package/dest/config/index.js +15 -0
  18. package/dest/config/network_config.d.ts +19 -1
  19. package/dest/config/network_config.d.ts.map +1 -1
  20. package/dest/config/network_config.js +4 -1
  21. package/dest/config/network_name.d.ts +2 -2
  22. package/dest/config/network_name.d.ts.map +1 -1
  23. package/dest/config/network_name.js +0 -2
  24. package/dest/crypto/poseidon/index.d.ts +1 -1
  25. package/dest/crypto/poseidon/index.d.ts.map +1 -1
  26. package/dest/crypto/poseidon/index.js +40 -33
  27. package/dest/crypto/secp256k1-signer/utils.d.ts +12 -1
  28. package/dest/crypto/secp256k1-signer/utils.d.ts.map +1 -1
  29. package/dest/crypto/secp256k1-signer/utils.js +26 -0
  30. package/dest/curves/bn254/field.d.ts +2 -1
  31. package/dest/curves/bn254/field.d.ts.map +1 -1
  32. package/dest/curves/bn254/field.js +5 -2
  33. package/dest/curves/grumpkin/point.d.ts +1 -1
  34. package/dest/curves/grumpkin/point.js +1 -1
  35. package/dest/eth-signature/eth_signature.d.ts +2 -1
  36. package/dest/eth-signature/eth_signature.d.ts.map +1 -1
  37. package/dest/eth-signature/eth_signature.js +7 -2
  38. package/dest/log/bigint-utils.d.ts +1 -1
  39. package/dest/log/bigint-utils.d.ts.map +1 -1
  40. package/dest/log/bigint-utils.js +3 -0
  41. package/dest/schemas/api.d.ts +2 -2
  42. package/dest/schemas/api.d.ts.map +1 -1
  43. package/dest/serialize/buffer_reader.d.ts +20 -7
  44. package/dest/serialize/buffer_reader.d.ts.map +1 -1
  45. package/dest/serialize/buffer_reader.js +18 -5
  46. package/dest/serialize/field_reader.d.ts +13 -5
  47. package/dest/serialize/field_reader.d.ts.map +1 -1
  48. package/dest/serialize/field_reader.js +13 -0
  49. package/dest/transport/transport_client.js +2 -2
  50. package/package.json +2 -2
  51. package/src/branded-types/slot.ts +5 -0
  52. package/src/buffer/index.ts +1 -0
  53. package/src/buffer/utils.ts +8 -0
  54. package/src/collection/array.ts +14 -0
  55. package/src/config/env_var.ts +14 -4
  56. package/src/config/index.ts +15 -0
  57. package/src/config/network_config.ts +3 -0
  58. package/src/config/network_name.ts +1 -4
  59. package/src/crypto/poseidon/index.ts +42 -34
  60. package/src/crypto/secp256k1-signer/utils.ts +32 -0
  61. package/src/curves/bn254/field.ts +6 -2
  62. package/src/curves/grumpkin/point.ts +1 -1
  63. package/src/eth-signature/eth_signature.ts +7 -1
  64. package/src/log/bigint-utils.ts +3 -0
  65. package/src/schemas/api.ts +4 -1
  66. package/src/serialize/buffer_reader.ts +29 -6
  67. package/src/serialize/field_reader.ts +22 -4
  68. package/src/transport/transport_client.ts +2 -2
  69. package/src/trees/membership_witness.ts +2 -2
@@ -177,6 +177,21 @@ export function bigintConfigHelper(defaultVal?: bigint): Pick<ConfigMapping, 'pa
177
177
  if (val === '') {
178
178
  return defaultVal;
179
179
  }
180
+ // Handle scientific notation (e.g. "1e+23", "2E23") which BigInt() doesn't accept directly.
181
+ // We parse it losslessly using bigint arithmetic instead of going through float64.
182
+ if (/[eE]/.test(val)) {
183
+ const match = val.match(/^(-?\d+(?:\.(\d+))?)[eE]([+-]?\d+)$/);
184
+ if (!match) {
185
+ throw new Error(`Cannot convert '${val}' to a BigInt`);
186
+ }
187
+ const digits = match[1].replace('.', '');
188
+ const decimalPlaces = match[2]?.length ?? 0;
189
+ const exponent = parseInt(match[3], 10) - decimalPlaces;
190
+ if (exponent < 0) {
191
+ throw new Error(`Cannot convert '${val}' to a BigInt: result is not an integer`);
192
+ }
193
+ return BigInt(digits) * 10n ** BigInt(exponent);
194
+ }
180
195
  return BigInt(val);
181
196
  },
182
197
  defaultValue: defaultVal,
@@ -5,10 +5,13 @@ export const NetworkConfigSchema = z
5
5
  bootnodes: z.array(z.string()),
6
6
  snapshots: z.array(z.string()),
7
7
  blobFileStoreUrls: z.array(z.string()).optional(),
8
+ txCollectionFileStoreUrls: z.array(z.string()).optional(),
8
9
  registryAddress: z.string(),
9
10
  feeAssetHandlerAddress: z.string().optional(),
10
11
  l1ChainId: z.number(),
11
12
  blockDurationMs: z.number().positive().optional(),
13
+ nodeVersion: z.string().optional(),
14
+ txPublicSetupAllowListExtend: z.string().optional(),
12
15
  })
13
16
  .passthrough(); // Allow additional unknown fields to pass through
14
17
 
@@ -5,8 +5,7 @@ export type NetworkNames =
5
5
  | 'testnet'
6
6
  | 'mainnet'
7
7
  | 'next-net'
8
- | 'devnet'
9
- | `v${number}-devnet-${number}`;
8
+ | 'devnet';
10
9
 
11
10
  export function getActiveNetworkName(name?: string): NetworkNames {
12
11
  const network = name || process.env.NETWORK;
@@ -24,8 +23,6 @@ export function getActiveNetworkName(name?: string): NetworkNames {
24
23
  return 'next-net';
25
24
  } else if (network === 'devnet') {
26
25
  return 'devnet';
27
- } else if (/^v\d+-devnet-\d+$/.test(network)) {
28
- return network as `v${number}-devnet-${number}`;
29
26
  }
30
27
  throw new Error(`Unknown network: ${network}`);
31
28
  }
@@ -1,21 +1,35 @@
1
- import { BarretenbergSync } from '@aztec/bb.js';
1
+ import { Barretenberg, BarretenbergSync } from '@aztec/bb.js';
2
2
 
3
3
  import { Fr } from '../../curves/bn254/field.js';
4
4
  import { type Fieldable, serializeToFields } from '../../serialize/serialize.js';
5
5
 
6
+ const IS_BROWSER = typeof self !== 'undefined';
7
+
8
+ async function poseidon2HashFields(inputFields: Fr[]): Promise<Fr> {
9
+ if (IS_BROWSER) {
10
+ await BarretenbergSync.initSingleton();
11
+ const api = BarretenbergSync.getSingleton();
12
+ const response = api.poseidon2Hash({
13
+ inputs: inputFields.map(i => i.toBuffer()),
14
+ });
15
+ return Fr.fromBuffer(Buffer.from(response.hash));
16
+ } else {
17
+ await Barretenberg.initSingleton();
18
+ const api = Barretenberg.getSingleton();
19
+ const response = await api.poseidon2Hash({
20
+ inputs: inputFields.map(i => i.toBuffer()),
21
+ });
22
+ return Fr.fromBuffer(Buffer.from(response.hash));
23
+ }
24
+ }
25
+
6
26
  /**
7
27
  * Create a poseidon hash (field) from an array of input fields.
8
28
  * @param input - The input fields to hash.
9
29
  * @returns The poseidon hash.
10
30
  */
11
- export async function poseidon2Hash(input: Fieldable[]): Promise<Fr> {
12
- const inputFields = serializeToFields(input);
13
- await BarretenbergSync.initSingleton();
14
- const api = BarretenbergSync.getSingleton();
15
- const response = api.poseidon2Hash({
16
- inputs: inputFields.map(i => i.toBuffer()),
17
- });
18
- return Fr.fromBuffer(Buffer.from(response.hash));
31
+ export function poseidon2Hash(input: Fieldable[]): Promise<Fr> {
32
+ return poseidon2HashFields(serializeToFields(input));
19
33
  }
20
34
 
21
35
  /**
@@ -24,15 +38,10 @@ export async function poseidon2Hash(input: Fieldable[]): Promise<Fr> {
24
38
  * @param separator - The domain separator.
25
39
  * @returns The poseidon hash.
26
40
  */
27
- export async function poseidon2HashWithSeparator(input: Fieldable[], separator: number): Promise<Fr> {
41
+ export function poseidon2HashWithSeparator(input: Fieldable[], separator: number): Promise<Fr> {
28
42
  const inputFields = serializeToFields(input);
29
43
  inputFields.unshift(new Fr(separator));
30
- await BarretenbergSync.initSingleton();
31
- const api = BarretenbergSync.getSingleton();
32
- const response = api.poseidon2Hash({
33
- inputs: inputFields.map(i => i.toBuffer()),
34
- });
35
- return Fr.fromBuffer(Buffer.from(response.hash));
44
+ return poseidon2HashFields(inputFields);
36
45
  }
37
46
 
38
47
  /**
@@ -42,19 +51,24 @@ export async function poseidon2HashWithSeparator(input: Fieldable[], separator:
42
51
  */
43
52
  export async function poseidon2Permutation(input: Fieldable[]): Promise<Fr[]> {
44
53
  const inputFields = serializeToFields(input);
45
- // We'd like this assertion but it's not possible to use it in the browser.
46
- // assert(input.length === 4, 'Input state must be of size 4');
47
- await BarretenbergSync.initSingleton();
48
- const api = BarretenbergSync.getSingleton();
49
- const response = api.poseidon2Permutation({
50
- inputs: inputFields.map(i => i.toBuffer()),
51
- });
52
- // We'd like this assertion but it's not possible to use it in the browser.
53
- // assert(response.outputs.length === 4, 'Output state must be of size 4');
54
- return response.outputs.map(o => Fr.fromBuffer(Buffer.from(o)));
54
+ if (IS_BROWSER) {
55
+ await BarretenbergSync.initSingleton();
56
+ const api = BarretenbergSync.getSingleton();
57
+ const response = api.poseidon2Permutation({
58
+ inputs: inputFields.map(i => i.toBuffer()),
59
+ });
60
+ return response.outputs.map(o => Fr.fromBuffer(Buffer.from(o)));
61
+ } else {
62
+ await Barretenberg.initSingleton();
63
+ const api = Barretenberg.getSingleton();
64
+ const response = await api.poseidon2Permutation({
65
+ inputs: inputFields.map(i => i.toBuffer()),
66
+ });
67
+ return response.outputs.map(o => Fr.fromBuffer(Buffer.from(o)));
68
+ }
55
69
  }
56
70
 
57
- export async function poseidon2HashBytes(input: Buffer): Promise<Fr> {
71
+ export function poseidon2HashBytes(input: Buffer): Promise<Fr> {
58
72
  const inputFields = [];
59
73
  for (let i = 0; i < input.length; i += 31) {
60
74
  const fieldBytes = Buffer.alloc(32, 0);
@@ -65,11 +79,5 @@ export async function poseidon2HashBytes(input: Buffer): Promise<Fr> {
65
79
  inputFields.push(Fr.fromBuffer(fieldBytes));
66
80
  }
67
81
 
68
- await BarretenbergSync.initSingleton();
69
- const api = BarretenbergSync.getSingleton();
70
- const response = api.poseidon2Hash({
71
- inputs: inputFields.map(i => i.toBuffer()),
72
- });
73
-
74
- return Fr.fromBuffer(Buffer.from(response.hash));
82
+ return poseidon2HashFields(inputFields);
75
83
  }
@@ -210,3 +210,35 @@ export function recoverPublicKey(hash: Buffer32, signature: Signature, opts: Rec
210
210
  const publicKey = sig.recoverPublicKey(hash.buffer).toHex(false);
211
211
  return Buffer.from(publicKey, 'hex');
212
212
  }
213
+
214
+ /** Arbitrary hash used for testing signature recoverability. */
215
+ const PROBE_HASH = Buffer32.fromBuffer(keccak256(Buffer.from('signature-recoverability-probe')));
216
+
217
+ /**
218
+ * Generates a random valid ECDSA signature that is recoverable to some address.
219
+ * Since Signature.random() produces real signatures via secp256k1 signing, the result is always
220
+ * recoverable, but we verify defensively by checking tryRecoverAddress.
221
+ */
222
+ export function generateRecoverableSignature(): Signature {
223
+ for (let i = 0; i < 100; i++) {
224
+ const sig = Signature.random();
225
+ if (tryRecoverAddress(PROBE_HASH, sig) !== undefined) {
226
+ return sig;
227
+ }
228
+ }
229
+ throw new Secp256k1Error('Failed to generate a recoverable signature after 100 attempts');
230
+ }
231
+
232
+ /**
233
+ * Generates a random signature where ECDSA address recovery fails.
234
+ * Uses random r/s values (not from real signing) so that r is unlikely to be a valid secp256k1 x-coordinate.
235
+ */
236
+ export function generateUnrecoverableSignature(): Signature {
237
+ for (let i = 0; i < 100; i++) {
238
+ const sig = new Signature(Buffer32.random(), Buffer32.random(), 27);
239
+ if (tryRecoverAddress(PROBE_HASH, sig) === undefined) {
240
+ return sig;
241
+ }
242
+ }
243
+ throw new Secp256k1Error('Failed to generate an unrecoverable signature after 100 attempts');
244
+ }
@@ -118,14 +118,18 @@ abstract class BaseField {
118
118
  }
119
119
 
120
120
  cmp(rhs: BaseField): -1 | 0 | 1 {
121
- const rhsBigInt = rhs.asBigInt;
122
- return this.asBigInt === rhsBigInt ? 0 : this.asBigInt < rhsBigInt ? -1 : 1;
121
+ return BaseField.cmpAsBigInt(this.asBigInt, rhs.asBigInt);
123
122
  }
124
123
 
125
124
  static cmp(lhs: BaseField, rhs: BaseField): -1 | 0 | 1 {
126
125
  return lhs.cmp(rhs);
127
126
  }
128
127
 
128
+ // Actual bigint comparison. Arguments must have been validated previously.
129
+ static cmpAsBigInt(lhs: bigint, rhs: bigint): -1 | 0 | 1 {
130
+ return lhs === rhs ? 0 : lhs < rhs ? -1 : 1;
131
+ }
132
+
129
133
  isZero(): boolean {
130
134
  return this.asBigInt === 0n;
131
135
  }
@@ -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
  */
@@ -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 {
@@ -11,6 +11,9 @@ export function convertBigintsToStrings(obj: unknown): unknown {
11
11
  }
12
12
 
13
13
  if (obj !== null && typeof obj === 'object') {
14
+ if (typeof (obj as any).toJSON === 'function') {
15
+ return convertBigintsToStrings((obj as any).toJSON());
16
+ }
14
17
  const result: Record<string, unknown> = {};
15
18
  for (const key in obj) {
16
19
  result[key] = convertBigintsToStrings((obj as Record<string, unknown>)[key]);
@@ -37,7 +37,10 @@ export type ApiSchema = {
37
37
  };
38
38
 
39
39
  /** Return whether an API schema defines a valid function schema for a given method name. */
40
- export function schemaHasMethod(schema: ApiSchema, methodName: string) {
40
+ export function schemaHasMethod<T extends ApiSchema>(
41
+ schema: T,
42
+ methodName: string,
43
+ ): methodName is Extract<keyof T, string> {
41
44
  return (
42
45
  typeof methodName === 'string' &&
43
46
  Object.hasOwn(schema, methodName) &&
@@ -286,16 +286,39 @@ export class BufferReader {
286
286
  }
287
287
 
288
288
  /**
289
- * Read an array of a fixed size with elements of type T from the buffer.
290
- * The 'itemDeserializer' object should have a 'fromBuffer' method that takes a BufferReader instance as input,
291
- * and returns an instance of the desired deserialized data type T.
292
- * This method will call the 'fromBuffer' method for each element in the array and return the resulting array.
289
+ * Read an array from the buffer using lazy allocation (new Array + loop).
290
+ * Safe for use with untrusted sizes does not pre-allocate memory proportional to size.
293
291
  *
294
- * @param size - The fixed number of elements in the array.
292
+ * @param size - The number of elements to read.
295
293
  * @param itemDeserializer - An object with a 'fromBuffer' method to deserialize individual elements of type T.
296
294
  * @returns An array of instances of type T.
297
295
  */
298
- public readArray<T, N extends number>(
296
+ public readArray<T>(
297
+ size: number,
298
+ itemDeserializer: {
299
+ /**
300
+ * A function for deserializing data from a BufferReader instance.
301
+ */
302
+ fromBuffer: (reader: BufferReader) => T;
303
+ },
304
+ ): T[] {
305
+ const result = new Array<T>(size);
306
+ for (let i = 0; i < size; i++) {
307
+ result[i] = itemDeserializer.fromBuffer(this);
308
+ }
309
+ return result;
310
+ }
311
+
312
+ /**
313
+ * Read a fixed-size tuple from the buffer using dense allocation (Array.from).
314
+ * Only use with compile-time constant sizes — the size parameter MUST NOT come from untrusted input
315
+ * as Array.from pre-allocates memory proportional to size.
316
+ *
317
+ * @param size - The fixed number of elements (must be a compile-time constant).
318
+ * @param itemDeserializer - An object with a 'fromBuffer' method to deserialize individual elements of type T.
319
+ * @returns A densely-allocated tuple of instances of type T.
320
+ */
321
+ public readTuple<T, N extends number>(
299
322
  size: N,
300
323
  itemDeserializer: {
301
324
  /**
@@ -169,12 +169,30 @@ export class FieldReader {
169
169
  * @param itemDeserializer - An object with a 'fromFields' method to deserialize individual elements of type T.
170
170
  * @returns An array of instances of type T.
171
171
  */
172
- public readArray<T, N extends number>(
172
+ /**
173
+ * Read an array from the field array using lazy allocation (new Array + loop).
174
+ * Safe for use with untrusted sizes.
175
+ */
176
+ public readArray<T>(
177
+ size: number,
178
+ itemDeserializer: {
179
+ fromFields: (reader: FieldReader) => T;
180
+ },
181
+ ): T[] {
182
+ const result = new Array<T>(size);
183
+ for (let i = 0; i < size; i++) {
184
+ result[i] = itemDeserializer.fromFields(this);
185
+ }
186
+ return result;
187
+ }
188
+
189
+ /**
190
+ * Read a fixed-size tuple from the field array using dense allocation (Array.from).
191
+ * Only use with compile-time constant sizes — the size parameter MUST NOT come from untrusted input.
192
+ */
193
+ public readTuple<T, N extends number>(
173
194
  size: N,
174
195
  itemDeserializer: {
175
- /**
176
- * A function for deserializing data from a FieldReader instance.
177
- */
178
196
  fromFields: (reader: FieldReader) => T;
179
197
  },
180
198
  ): Tuple<T, N> {
@@ -91,7 +91,7 @@ export class TransportClient<Payload> extends EventEmitter {
91
91
  }
92
92
  const msgId = this.msgId++;
93
93
  const msg = { msgId, payload };
94
- log.debug(format(`->`, msg));
94
+ log.trace(format(`->`, msg));
95
95
  return new Promise<any>((resolve, reject) => {
96
96
  this.pendingRequests.push({ resolve, reject, msgId });
97
97
  this.socket!.send(msg, transfer).catch(reject);
@@ -111,7 +111,7 @@ export class TransportClient<Payload> extends EventEmitter {
111
111
  this.close();
112
112
  return;
113
113
  }
114
- log.debug(format(`<-`, msg));
114
+ log.trace(format(`<-`, msg));
115
115
  if (isEventMessage(msg)) {
116
116
  this.emit('event_msg', msg.payload);
117
117
  return;
@@ -94,7 +94,7 @@ export class MembershipWitness<N extends number> {
94
94
  static fromBuffer<N extends number>(buffer: Buffer | BufferReader, size: N): MembershipWitness<N> {
95
95
  const reader = BufferReader.asReader(buffer);
96
96
  const leafIndex = toBigIntBE(reader.readBytes(32));
97
- const siblingPath = reader.readArray(size, Fr);
97
+ const siblingPath = reader.readArray(size, Fr) as Tuple<Fr, N>;
98
98
  return new MembershipWitness(size, leafIndex, siblingPath);
99
99
  }
100
100
 
@@ -108,7 +108,7 @@ export class MembershipWitness<N extends number> {
108
108
  fromBuffer: (buffer: Buffer | BufferReader) => {
109
109
  const reader = BufferReader.asReader(buffer);
110
110
  const leafIndex = toBigIntBE(reader.readBytes(32));
111
- const siblingPath = reader.readArray(size, Fr);
111
+ const siblingPath = reader.readArray(size, Fr) as Tuple<Fr, N>;
112
112
  return new MembershipWitness(size, leafIndex, siblingPath);
113
113
  },
114
114
  };