From 92f87f86ba9247b1a53adc8526eca74d8345b30b Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 16 Mar 2026 23:06:28 -0300 Subject: [PATCH 1/2] fix: avoid `Array.from` with untrusted sizes Calling `Array.from({length})` allocates length immediately. We were calling this method in the context of deserialization with untrusted input. This PR changes it so we use `new Array(size)` for untrusted input. A bit less efficient, but more secure. --- yarn-project/blob-lib/src/sponge_blob.ts | 4 +-- .../foundation/src/serialize/buffer_reader.ts | 35 +++++++++++++++---- .../src/trees/membership_witness.ts | 4 +-- .../stdlib/src/avm/avm_accumulated_data.ts | 8 ++--- .../src/avm/avm_circuit_public_inputs.ts | 4 +-- .../stdlib/src/kernel/claimed_length_array.ts | 2 +- .../hints/private_kernel_reset_hints.ts | 7 ++-- .../src/kernel/hints/read_request_hints.ts | 6 ++-- .../stdlib/src/kernel/padded_side_effects.ts | 6 ++-- .../kernel/private_to_avm_accumulated_data.ts | 6 ++-- .../private_to_public_accumulated_data.ts | 12 +++---- .../private_to_rollup_accumulated_data.ts | 10 +++--- yarn-project/stdlib/src/logs/private_log.ts | 2 +- yarn-project/stdlib/src/logs/public_log.ts | 12 ++++++- .../src/parity/parity_base_private_inputs.ts | 2 +- yarn-project/stdlib/src/proofs/chonk_proof.ts | 10 +++++- .../stdlib/src/rollup/base_rollup_hints.ts | 4 +-- .../block_root_rollup_private_inputs.ts | 16 ++++----- .../rollup/checkpoint_rollup_public_inputs.ts | 4 +-- .../checkpoint_root_rollup_private_inputs.ts | 8 ++--- .../src/rollup/root_rollup_public_inputs.ts | 4 +-- .../src/rollup/tree_snapshot_diff_hints.ts | 10 +++--- .../stdlib/src/tx/protocol_contracts.ts | 2 +- yarn-project/stdlib/src/vks/vk_data.ts | 2 +- 24 files changed, 112 insertions(+), 68 deletions(-) diff --git a/yarn-project/blob-lib/src/sponge_blob.ts b/yarn-project/blob-lib/src/sponge_blob.ts index 50a049bd829f..4578d30dabe7 100644 --- a/yarn-project/blob-lib/src/sponge_blob.ts +++ b/yarn-project/blob-lib/src/sponge_blob.ts @@ -91,8 +91,8 @@ export class Poseidon2Sponge { static fromBuffer(buffer: Buffer | BufferReader): Poseidon2Sponge { const reader = BufferReader.asReader(buffer); return new Poseidon2Sponge( - reader.readArray(3, Fr), - reader.readArray(4, Fr), + reader.readTuple(3, Fr), + reader.readTuple(4, Fr), reader.readNumber(), reader.readBoolean(), ); diff --git a/yarn-project/foundation/src/serialize/buffer_reader.ts b/yarn-project/foundation/src/serialize/buffer_reader.ts index 8cc8071db595..45a88457f668 100644 --- a/yarn-project/foundation/src/serialize/buffer_reader.ts +++ b/yarn-project/foundation/src/serialize/buffer_reader.ts @@ -286,16 +286,39 @@ export class BufferReader { } /** - * Read an array of a fixed size with elements of type T from the buffer. - * The 'itemDeserializer' object should have a 'fromBuffer' method that takes a BufferReader instance as input, - * and returns an instance of the desired deserialized data type T. - * This method will call the 'fromBuffer' method for each element in the array and return the resulting array. + * Read an array from the buffer using lazy allocation (new Array + loop). + * Safe for use with untrusted sizes — does not pre-allocate memory proportional to size. * - * @param size - The fixed number of elements in the array. + * @param size - The number of elements to read. * @param itemDeserializer - An object with a 'fromBuffer' method to deserialize individual elements of type T. * @returns An array of instances of type T. */ - public readArray( + public readArray( + size: number, + itemDeserializer: { + /** + * A function for deserializing data from a BufferReader instance. + */ + fromBuffer: (reader: BufferReader) => T; + }, + ): T[] { + const result = new Array(size); + for (let i = 0; i < size; i++) { + result[i] = itemDeserializer.fromBuffer(this); + } + return result; + } + + /** + * Read a fixed-size tuple from the buffer using dense allocation (Array.from). + * Only use with compile-time constant sizes — the size parameter MUST NOT come from untrusted input + * as Array.from pre-allocates memory proportional to size. + * + * @param size - The fixed number of elements (must be a compile-time constant). + * @param itemDeserializer - An object with a 'fromBuffer' method to deserialize individual elements of type T. + * @returns A densely-allocated tuple of instances of type T. + */ + public readTuple( size: N, itemDeserializer: { /** diff --git a/yarn-project/foundation/src/trees/membership_witness.ts b/yarn-project/foundation/src/trees/membership_witness.ts index bbc46f886b1e..c4500ad710d4 100644 --- a/yarn-project/foundation/src/trees/membership_witness.ts +++ b/yarn-project/foundation/src/trees/membership_witness.ts @@ -94,7 +94,7 @@ export class MembershipWitness { static fromBuffer(buffer: Buffer | BufferReader, size: N): MembershipWitness { const reader = BufferReader.asReader(buffer); const leafIndex = toBigIntBE(reader.readBytes(32)); - const siblingPath = reader.readArray(size, Fr); + const siblingPath = reader.readArray(size, Fr) as Tuple; return new MembershipWitness(size, leafIndex, siblingPath); } @@ -108,7 +108,7 @@ export class MembershipWitness { fromBuffer: (buffer: Buffer | BufferReader) => { const reader = BufferReader.asReader(buffer); const leafIndex = toBigIntBE(reader.readBytes(32)); - const siblingPath = reader.readArray(size, Fr); + const siblingPath = reader.readArray(size, Fr) as Tuple; return new MembershipWitness(size, leafIndex, siblingPath); }, }; diff --git a/yarn-project/stdlib/src/avm/avm_accumulated_data.ts b/yarn-project/stdlib/src/avm/avm_accumulated_data.ts index f072f5a00393..3e39ccb53948 100644 --- a/yarn-project/stdlib/src/avm/avm_accumulated_data.ts +++ b/yarn-project/stdlib/src/avm/avm_accumulated_data.ts @@ -88,11 +88,11 @@ export class AvmAccumulatedData { static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); return new this( - reader.readArray(MAX_NOTE_HASHES_PER_TX, Fr), - reader.readArray(MAX_NULLIFIERS_PER_TX, Fr), - reader.readArray(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), + reader.readTuple(MAX_NOTE_HASHES_PER_TX, Fr), + reader.readTuple(MAX_NULLIFIERS_PER_TX, Fr), + reader.readTuple(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), reader.readObject(FlatPublicLogs), - reader.readArray(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite), + reader.readTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite), ); } diff --git a/yarn-project/stdlib/src/avm/avm_circuit_public_inputs.ts b/yarn-project/stdlib/src/avm/avm_circuit_public_inputs.ts index 4dbeca81752c..eb6563aba31e 100644 --- a/yarn-project/stdlib/src/avm/avm_circuit_public_inputs.ts +++ b/yarn-project/stdlib/src/avm/avm_circuit_public_inputs.ts @@ -143,8 +143,8 @@ export class AvmCircuitPublicInputs { reader.readObject(AztecAddress), reader.readObject(Fr), reader.readObject(PublicCallRequestArrayLengths), - reader.readArray(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), - reader.readArray(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), + reader.readTuple(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), + reader.readTuple(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), reader.readObject(PublicCallRequest), reader.readObject(PrivateToAvmAccumulatedDataArrayLengths), reader.readObject(PrivateToAvmAccumulatedDataArrayLengths), diff --git a/yarn-project/stdlib/src/kernel/claimed_length_array.ts b/yarn-project/stdlib/src/kernel/claimed_length_array.ts index 109f7463137e..ef0c3ee67716 100644 --- a/yarn-project/stdlib/src/kernel/claimed_length_array.ts +++ b/yarn-project/stdlib/src/kernel/claimed_length_array.ts @@ -25,7 +25,7 @@ export class ClaimedLengthArray { arrayLength: N, ): ClaimedLengthArray { const reader = BufferReader.asReader(buffer); - const array = reader.readArray(arrayLength, deserializer); + const array = reader.readArray(arrayLength, deserializer) as Tuple; const claimedLength = reader.readNumber(); return new ClaimedLengthArray(array, claimedLength); } diff --git a/yarn-project/stdlib/src/kernel/hints/private_kernel_reset_hints.ts b/yarn-project/stdlib/src/kernel/hints/private_kernel_reset_hints.ts index 72c53ecb5a77..8fa1175f14b1 100644 --- a/yarn-project/stdlib/src/kernel/hints/private_kernel_reset_hints.ts +++ b/yarn-project/stdlib/src/kernel/hints/private_kernel_reset_hints.ts @@ -105,8 +105,11 @@ export class PrivateKernelResetHints< fromBuffer: buf => nullifierReadRequestHintsFromBuffer(buf, numNullifierReadRequestPending, numNullifierReadRequestSettled), }), - reader.readArray(numKeyValidationHints, KeyValidationHint), - reader.readArray(numTransientDataSquashingHints, TransientDataSquashingHint), + reader.readArray(numKeyValidationHints, KeyValidationHint) as Tuple, + reader.readArray(numTransientDataSquashingHints, TransientDataSquashingHint) as Tuple< + TransientDataSquashingHint, + TRANSIENT_DATA_HINTS_LEN + >, ); } } diff --git a/yarn-project/stdlib/src/kernel/hints/read_request_hints.ts b/yarn-project/stdlib/src/kernel/hints/read_request_hints.ts index 39b6663aa953..4f57bf2e327b 100644 --- a/yarn-project/stdlib/src/kernel/hints/read_request_hints.ts +++ b/yarn-project/stdlib/src/kernel/hints/read_request_hints.ts @@ -158,11 +158,11 @@ export class ReadRequestResetHints< > { const reader = BufferReader.asReader(buffer); return new ReadRequestResetHints( - reader.readArray(readRequestLen, ReadRequestAction), - reader.readArray(numPendingReads, PendingReadHint), + reader.readArray(readRequestLen, ReadRequestAction) as Tuple, + reader.readArray(numPendingReads, PendingReadHint) as Tuple, reader.readArray(numSettledReads, { fromBuffer: r => SettledReadHint.fromBuffer(r, treeHeight, leafPreimageFromBuffer), - }), + }) as Tuple, SETTLED_READ_HINTS_LEN>, ); } diff --git a/yarn-project/stdlib/src/kernel/padded_side_effects.ts b/yarn-project/stdlib/src/kernel/padded_side_effects.ts index f42207255465..48d2c2a2442d 100644 --- a/yarn-project/stdlib/src/kernel/padded_side_effects.ts +++ b/yarn-project/stdlib/src/kernel/padded_side_effects.ts @@ -19,9 +19,9 @@ export class PaddedSideEffects { static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); return new PaddedSideEffects( - reader.readArray(MAX_NOTE_HASHES_PER_TX, Fr), - reader.readArray(MAX_NULLIFIERS_PER_TX, Fr), - reader.readArray(MAX_PRIVATE_LOGS_PER_TX, PrivateLog), + reader.readTuple(MAX_NOTE_HASHES_PER_TX, Fr), + reader.readTuple(MAX_NULLIFIERS_PER_TX, Fr), + reader.readTuple(MAX_PRIVATE_LOGS_PER_TX, PrivateLog), ); } diff --git a/yarn-project/stdlib/src/kernel/private_to_avm_accumulated_data.ts b/yarn-project/stdlib/src/kernel/private_to_avm_accumulated_data.ts index 9442439a039a..d64dbdad345c 100644 --- a/yarn-project/stdlib/src/kernel/private_to_avm_accumulated_data.ts +++ b/yarn-project/stdlib/src/kernel/private_to_avm_accumulated_data.ts @@ -94,9 +94,9 @@ export class PrivateToAvmAccumulatedData { static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); return new PrivateToAvmAccumulatedData( - reader.readArray(MAX_NOTE_HASHES_PER_TX, Fr), - reader.readArray(MAX_NULLIFIERS_PER_TX, Fr), - reader.readArray(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), + reader.readTuple(MAX_NOTE_HASHES_PER_TX, Fr), + reader.readTuple(MAX_NULLIFIERS_PER_TX, Fr), + reader.readTuple(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), ); } diff --git a/yarn-project/stdlib/src/kernel/private_to_public_accumulated_data.ts b/yarn-project/stdlib/src/kernel/private_to_public_accumulated_data.ts index ab1b79190672..c35b96c5656d 100644 --- a/yarn-project/stdlib/src/kernel/private_to_public_accumulated_data.ts +++ b/yarn-project/stdlib/src/kernel/private_to_public_accumulated_data.ts @@ -76,12 +76,12 @@ export class PrivateToPublicAccumulatedData { static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); return new PrivateToPublicAccumulatedData( - reader.readArray(MAX_NOTE_HASHES_PER_TX, Fr), - reader.readArray(MAX_NULLIFIERS_PER_TX, Fr), - reader.readArray(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), - reader.readArray(MAX_PRIVATE_LOGS_PER_TX, PrivateLog), - reader.readArray(MAX_CONTRACT_CLASS_LOGS_PER_TX, ScopedLogHash), - reader.readArray(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), + reader.readTuple(MAX_NOTE_HASHES_PER_TX, Fr), + reader.readTuple(MAX_NULLIFIERS_PER_TX, Fr), + reader.readTuple(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), + reader.readTuple(MAX_PRIVATE_LOGS_PER_TX, PrivateLog), + reader.readTuple(MAX_CONTRACT_CLASS_LOGS_PER_TX, ScopedLogHash), + reader.readTuple(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), ); } diff --git a/yarn-project/stdlib/src/kernel/private_to_rollup_accumulated_data.ts b/yarn-project/stdlib/src/kernel/private_to_rollup_accumulated_data.ts index 7807f6ad9b97..74d1aeed52cd 100644 --- a/yarn-project/stdlib/src/kernel/private_to_rollup_accumulated_data.ts +++ b/yarn-project/stdlib/src/kernel/private_to_rollup_accumulated_data.ts @@ -95,11 +95,11 @@ export class PrivateToRollupAccumulatedData { static fromBuffer(buffer: Buffer | BufferReader): PrivateToRollupAccumulatedData { const reader = BufferReader.asReader(buffer); return new PrivateToRollupAccumulatedData( - reader.readArray(MAX_NOTE_HASHES_PER_TX, Fr), - reader.readArray(MAX_NULLIFIERS_PER_TX, Fr), - reader.readArray(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), - reader.readArray(MAX_PRIVATE_LOGS_PER_TX, PrivateLog), - reader.readArray(MAX_CONTRACT_CLASS_LOGS_PER_TX, ScopedLogHash), + reader.readTuple(MAX_NOTE_HASHES_PER_TX, Fr), + reader.readTuple(MAX_NULLIFIERS_PER_TX, Fr), + reader.readTuple(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), + reader.readTuple(MAX_PRIVATE_LOGS_PER_TX, PrivateLog), + reader.readTuple(MAX_CONTRACT_CLASS_LOGS_PER_TX, ScopedLogHash), ); } diff --git a/yarn-project/stdlib/src/logs/private_log.ts b/yarn-project/stdlib/src/logs/private_log.ts index 10f915a1e6bd..be11dda2b0bf 100644 --- a/yarn-project/stdlib/src/logs/private_log.ts +++ b/yarn-project/stdlib/src/logs/private_log.ts @@ -73,7 +73,7 @@ export class PrivateLog { static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); - return new PrivateLog(reader.readArray(PRIVATE_LOG_SIZE_IN_FIELDS, Fr), reader.readNumber()); + return new PrivateLog(reader.readTuple(PRIVATE_LOG_SIZE_IN_FIELDS, Fr), reader.readNumber()); } static random(tag = Fr.random()) { diff --git a/yarn-project/stdlib/src/logs/public_log.ts b/yarn-project/stdlib/src/logs/public_log.ts index 490e39a0bcae..19c9daaf5b32 100644 --- a/yarn-project/stdlib/src/logs/public_log.ts +++ b/yarn-project/stdlib/src/logs/public_log.ts @@ -1,4 +1,8 @@ -import { FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH, PUBLIC_LOG_HEADER_LENGTH } from '@aztec/constants'; +import { + FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH, + MAX_PUBLIC_LOG_SIZE_IN_FIELDS, + PUBLIC_LOG_HEADER_LENGTH, +} from '@aztec/constants'; import type { FieldsOf } from '@aztec/foundation/array'; import { Fr } from '@aztec/foundation/curves/bn254'; import { type ZodFor, schemas } from '@aztec/foundation/schemas'; @@ -76,6 +80,9 @@ export class FlatPublicLogs { static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); const length = reader.readNumber(); + if (length > FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH) { + throw new Error(`FlatPublicLogs length ${length} exceeds maximum ${FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH}`); + } return this.fromUnpaddedPayload(reader.readArray(length, Fr)); } @@ -171,6 +178,9 @@ export class PublicLog { static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); const fieldsLength = reader.readNumber(); + if (fieldsLength > MAX_PUBLIC_LOG_SIZE_IN_FIELDS) { + throw new Error(`PublicLog fields length ${fieldsLength} exceeds maximum ${MAX_PUBLIC_LOG_SIZE_IN_FIELDS}`); + } return new PublicLog(reader.readObject(AztecAddress), reader.readArray(fieldsLength, Fr)); } diff --git a/yarn-project/stdlib/src/parity/parity_base_private_inputs.ts b/yarn-project/stdlib/src/parity/parity_base_private_inputs.ts index a8a4dd68fd77..eece01c59c37 100644 --- a/yarn-project/stdlib/src/parity/parity_base_private_inputs.ts +++ b/yarn-project/stdlib/src/parity/parity_base_private_inputs.ts @@ -41,7 +41,7 @@ export class ParityBasePrivateInputs { */ static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); - return new ParityBasePrivateInputs(reader.readArray(NUM_MSGS_PER_BASE_PARITY, Fr), Fr.fromBuffer(reader)); + return new ParityBasePrivateInputs(reader.readTuple(NUM_MSGS_PER_BASE_PARITY, Fr), Fr.fromBuffer(reader)); } /** diff --git a/yarn-project/stdlib/src/proofs/chonk_proof.ts b/yarn-project/stdlib/src/proofs/chonk_proof.ts index bb22a6187fa0..6afe0a67c026 100644 --- a/yarn-project/stdlib/src/proofs/chonk_proof.ts +++ b/yarn-project/stdlib/src/proofs/chonk_proof.ts @@ -56,7 +56,10 @@ export class ChonkProof { static fromBuffer(buffer: Buffer | BufferReader): ChonkProof { const reader = BufferReader.asReader(buffer); const proofLength = reader.readNumber(); - const proof = reader.readArray(proofLength, Fr); + if (proofLength !== CHONK_PROOF_LENGTH) { + throw new Error(`Invalid ChonkProof length from buffer: ${proofLength}, expected ${CHONK_PROOF_LENGTH}`); + } + const proof = reader.readArray(CHONK_PROOF_LENGTH, Fr); return new ChonkProof(proof); } @@ -106,6 +109,11 @@ export class ChonkProofWithPublicInputs { static fromBuffer(buffer: Buffer | BufferReader): ChonkProofWithPublicInputs { const reader = BufferReader.asReader(buffer); const proofLength = reader.readNumber(); + if (proofLength < CHONK_PROOF_LENGTH) { + throw new Error( + `Invalid ChonkProofWithPublicInputs length from buffer: ${proofLength}, expected at least ${CHONK_PROOF_LENGTH}`, + ); + } const proof = reader.readArray(proofLength, Fr); return new ChonkProofWithPublicInputs(proof); } diff --git a/yarn-project/stdlib/src/rollup/base_rollup_hints.ts b/yarn-project/stdlib/src/rollup/base_rollup_hints.ts index c92989ea7846..d846f9627e80 100644 --- a/yarn-project/stdlib/src/rollup/base_rollup_hints.ts +++ b/yarn-project/stdlib/src/rollup/base_rollup_hints.ts @@ -86,7 +86,7 @@ export class PrivateBaseRollupHints { reader.readObject(SpongeBlob), reader.readObject(TreeSnapshotDiffHints), reader.readObject(PublicDataTreeLeafPreimage), - reader.readArray(ARCHIVE_HEIGHT, Fr), + reader.readTuple(ARCHIVE_HEIGHT, Fr), makeTuple(MAX_CONTRACT_CLASS_LOGS_PER_TX, () => reader.readObject(ContractClassLogFields)), reader.readObject(BlockConstantData), ); @@ -163,7 +163,7 @@ export class PublicBaseRollupHints { return new PublicBaseRollupHints( reader.readObject(SpongeBlob), reader.readObject(AppendOnlyTreeSnapshot), - reader.readArray(ARCHIVE_HEIGHT, Fr), + reader.readTuple(ARCHIVE_HEIGHT, Fr), makeTuple(MAX_CONTRACT_CLASS_LOGS_PER_TX, () => reader.readObject(ContractClassLogFields)), ); } diff --git a/yarn-project/stdlib/src/rollup/block_root_rollup_private_inputs.ts b/yarn-project/stdlib/src/rollup/block_root_rollup_private_inputs.ts index 19a99769dac9..8a9e94eb48cf 100644 --- a/yarn-project/stdlib/src/rollup/block_root_rollup_private_inputs.ts +++ b/yarn-project/stdlib/src/rollup/block_root_rollup_private_inputs.ts @@ -60,8 +60,8 @@ export class BlockRootFirstRollupPrivateInputs { ProofData.fromBuffer(reader, ParityPublicInputs), [ProofData.fromBuffer(reader, TxRollupPublicInputs), ProofData.fromBuffer(reader, TxRollupPublicInputs)], AppendOnlyTreeSnapshot.fromBuffer(reader), - reader.readArray(L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH, Fr), - reader.readArray(ARCHIVE_HEIGHT, Fr), + reader.readTuple(L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH, Fr), + reader.readTuple(ARCHIVE_HEIGHT, Fr), ); } @@ -124,8 +124,8 @@ export class BlockRootSingleTxFirstRollupPrivateInputs { ProofData.fromBuffer(reader, ParityPublicInputs), ProofData.fromBuffer(reader, TxRollupPublicInputs), AppendOnlyTreeSnapshot.fromBuffer(reader), - reader.readArray(L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH, Fr), - reader.readArray(ARCHIVE_HEIGHT, Fr), + reader.readTuple(L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH, Fr), + reader.readTuple(ARCHIVE_HEIGHT, Fr), ); } @@ -206,8 +206,8 @@ export class BlockRootEmptyTxFirstRollupPrivateInputs { StateReference.fromBuffer(reader), CheckpointConstantData.fromBuffer(reader), reader.readUInt64(), - reader.readArray(L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH, Fr), - reader.readArray(ARCHIVE_HEIGHT, Fr), + reader.readTuple(L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH, Fr), + reader.readTuple(ARCHIVE_HEIGHT, Fr), ); } @@ -248,7 +248,7 @@ export class BlockRootRollupPrivateInputs { const reader = BufferReader.asReader(buffer); return new BlockRootRollupPrivateInputs( [ProofData.fromBuffer(reader, TxRollupPublicInputs), ProofData.fromBuffer(reader, TxRollupPublicInputs)], - reader.readArray(ARCHIVE_HEIGHT, Fr), + reader.readTuple(ARCHIVE_HEIGHT, Fr), ); } @@ -289,7 +289,7 @@ export class BlockRootSingleTxRollupPrivateInputs { const reader = BufferReader.asReader(buffer); return new BlockRootSingleTxRollupPrivateInputs( ProofData.fromBuffer(reader, TxRollupPublicInputs), - reader.readArray(ARCHIVE_HEIGHT, Fr), + reader.readTuple(ARCHIVE_HEIGHT, Fr), ); } diff --git a/yarn-project/stdlib/src/rollup/checkpoint_rollup_public_inputs.ts b/yarn-project/stdlib/src/rollup/checkpoint_rollup_public_inputs.ts index fe80522432ab..6626279ec6ce 100644 --- a/yarn-project/stdlib/src/rollup/checkpoint_rollup_public_inputs.ts +++ b/yarn-project/stdlib/src/rollup/checkpoint_rollup_public_inputs.ts @@ -65,8 +65,8 @@ export class CheckpointRollupPublicInputs { reader.readObject(AppendOnlyTreeSnapshot), reader.readObject(AppendOnlyTreeSnapshot), reader.readObject(AppendOnlyTreeSnapshot), - reader.readArray(MAX_CHECKPOINTS_PER_EPOCH, Fr), - reader.readArray(MAX_CHECKPOINTS_PER_EPOCH, FeeRecipient), + reader.readTuple(MAX_CHECKPOINTS_PER_EPOCH, Fr), + reader.readTuple(MAX_CHECKPOINTS_PER_EPOCH, FeeRecipient), reader.readObject(BlobAccumulator), reader.readObject(BlobAccumulator), reader.readObject(FinalBlobBatchingChallenges), diff --git a/yarn-project/stdlib/src/rollup/checkpoint_root_rollup_private_inputs.ts b/yarn-project/stdlib/src/rollup/checkpoint_root_rollup_private_inputs.ts index bb15e9110fb0..58cd518b44e0 100644 --- a/yarn-project/stdlib/src/rollup/checkpoint_root_rollup_private_inputs.ts +++ b/yarn-project/stdlib/src/rollup/checkpoint_root_rollup_private_inputs.ts @@ -81,15 +81,15 @@ export class CheckpointRootRollupHints { const reader = BufferReader.asReader(buffer); return new CheckpointRootRollupHints( BlockHeader.fromBuffer(reader), - reader.readArray(ARCHIVE_HEIGHT, Fr), + reader.readTuple(ARCHIVE_HEIGHT, Fr), reader.readObject(AppendOnlyTreeSnapshot), - reader.readArray(OUT_HASH_TREE_HEIGHT, Fr), + reader.readTuple(OUT_HASH_TREE_HEIGHT, Fr), reader.readObject(BlobAccumulator), reader.readObject(FinalBlobBatchingChallenges), // Below line gives error 'Type instantiation is excessively deep and possibly infinite. ts(2589)' - // reader.readArray(FIELDS_PER_BLOB, Fr), + // reader.readTuple(FIELDS_PER_BLOB, Fr), Array.from({ length: FIELDS_PER_BLOB * BLOBS_PER_CHECKPOINT }, () => Fr.fromBuffer(reader)), - reader.readArray(BLOBS_PER_CHECKPOINT, BLS12Point), + reader.readTuple(BLOBS_PER_CHECKPOINT, BLS12Point), Fr.fromBuffer(reader), ); } diff --git a/yarn-project/stdlib/src/rollup/root_rollup_public_inputs.ts b/yarn-project/stdlib/src/rollup/root_rollup_public_inputs.ts index 9c362ccd9e59..73063f3180e9 100644 --- a/yarn-project/stdlib/src/rollup/root_rollup_public_inputs.ts +++ b/yarn-project/stdlib/src/rollup/root_rollup_public_inputs.ts @@ -68,8 +68,8 @@ export class RootRollupPublicInputs { Fr.fromBuffer(reader), Fr.fromBuffer(reader), Fr.fromBuffer(reader), - reader.readArray(MAX_CHECKPOINTS_PER_EPOCH, Fr), - reader.readArray(MAX_CHECKPOINTS_PER_EPOCH, FeeRecipient), + reader.readTuple(MAX_CHECKPOINTS_PER_EPOCH, Fr), + reader.readTuple(MAX_CHECKPOINTS_PER_EPOCH, FeeRecipient), EpochConstantData.fromBuffer(reader), reader.readObject(FinalBlobAccumulator), ); diff --git a/yarn-project/stdlib/src/rollup/tree_snapshot_diff_hints.ts b/yarn-project/stdlib/src/rollup/tree_snapshot_diff_hints.ts index a07bcdaec5cb..02cbb148fbfb 100644 --- a/yarn-project/stdlib/src/rollup/tree_snapshot_diff_hints.ts +++ b/yarn-project/stdlib/src/rollup/tree_snapshot_diff_hints.ts @@ -85,14 +85,14 @@ export class TreeSnapshotDiffHints { static fromBuffer(buffer: Buffer | BufferReader): TreeSnapshotDiffHints { const reader = BufferReader.asReader(buffer); return new TreeSnapshotDiffHints( - reader.readArray(NOTE_HASH_SUBTREE_ROOT_SIBLING_PATH_LENGTH, Fr), - reader.readArray(MAX_NULLIFIERS_PER_TX, NullifierLeafPreimage), - reader.readArray(MAX_NULLIFIERS_PER_TX, { + reader.readTuple(NOTE_HASH_SUBTREE_ROOT_SIBLING_PATH_LENGTH, Fr), + reader.readTuple(MAX_NULLIFIERS_PER_TX, NullifierLeafPreimage), + reader.readTuple(MAX_NULLIFIERS_PER_TX, { fromBuffer: buffer => MembershipWitness.fromBuffer(buffer, NULLIFIER_TREE_HEIGHT), }), - reader.readArray(MAX_NULLIFIERS_PER_TX, Fr), + reader.readTuple(MAX_NULLIFIERS_PER_TX, Fr), reader.readNumbers(MAX_NULLIFIERS_PER_TX), - reader.readArray(NULLIFIER_SUBTREE_ROOT_SIBLING_PATH_LENGTH, Fr), + reader.readTuple(NULLIFIER_SUBTREE_ROOT_SIBLING_PATH_LENGTH, Fr), MembershipWitness.fromBuffer(reader, PUBLIC_DATA_TREE_HEIGHT), ); } diff --git a/yarn-project/stdlib/src/tx/protocol_contracts.ts b/yarn-project/stdlib/src/tx/protocol_contracts.ts index 2abdbffe87a2..85ba8cb78142 100644 --- a/yarn-project/stdlib/src/tx/protocol_contracts.ts +++ b/yarn-project/stdlib/src/tx/protocol_contracts.ts @@ -39,7 +39,7 @@ export class ProtocolContracts { static fromBuffer(buffer: Buffer | BufferReader): ProtocolContracts { const reader = BufferReader.asReader(buffer); - return new ProtocolContracts(reader.readArray(MAX_PROTOCOL_CONTRACTS, AztecAddress)); + return new ProtocolContracts(reader.readTuple(MAX_PROTOCOL_CONTRACTS, AztecAddress)); } toBuffer() { diff --git a/yarn-project/stdlib/src/vks/vk_data.ts b/yarn-project/stdlib/src/vks/vk_data.ts index 8f5adce80099..d2a7b60825c1 100644 --- a/yarn-project/stdlib/src/vks/vk_data.ts +++ b/yarn-project/stdlib/src/vks/vk_data.ts @@ -29,7 +29,7 @@ export class VkData { return new VkData( reader.readObject(VerificationKeyData), reader.readNumber(), - reader.readArray(VK_TREE_HEIGHT, Fr), + reader.readTuple(VK_TREE_HEIGHT, Fr), ); } From 2154ecf4cf87ad3059a3728a0ef4d3f6d59a5fe2 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 16 Mar 2026 23:14:21 -0300 Subject: [PATCH 2/2] fix: same change but for field reader --- .../foundation/src/serialize/field_reader.ts | 26 ++++++++++++++++--- .../stdlib/src/avm/avm_accumulated_data.ts | 4 +-- .../src/avm/avm_circuit_public_inputs.ts | 4 +-- .../stdlib/src/kernel/claimed_length_array.ts | 2 +- .../kernel/private_to_avm_accumulated_data.ts | 2 +- .../private_to_public_accumulated_data.ts | 8 +++--- .../stdlib/src/tx/protocol_contracts.ts | 2 +- 7 files changed, 33 insertions(+), 15 deletions(-) diff --git a/yarn-project/foundation/src/serialize/field_reader.ts b/yarn-project/foundation/src/serialize/field_reader.ts index 3ffb1395a04b..d150a913a48a 100644 --- a/yarn-project/foundation/src/serialize/field_reader.ts +++ b/yarn-project/foundation/src/serialize/field_reader.ts @@ -169,12 +169,30 @@ export class FieldReader { * @param itemDeserializer - An object with a 'fromFields' method to deserialize individual elements of type T. * @returns An array of instances of type T. */ - public readArray( + /** + * Read an array from the field array using lazy allocation (new Array + loop). + * Safe for use with untrusted sizes. + */ + public readArray( + size: number, + itemDeserializer: { + fromFields: (reader: FieldReader) => T; + }, + ): T[] { + const result = new Array(size); + for (let i = 0; i < size; i++) { + result[i] = itemDeserializer.fromFields(this); + } + return result; + } + + /** + * Read a fixed-size tuple from the field array using dense allocation (Array.from). + * Only use with compile-time constant sizes — the size parameter MUST NOT come from untrusted input. + */ + public readTuple( size: N, itemDeserializer: { - /** - * A function for deserializing data from a FieldReader instance. - */ fromFields: (reader: FieldReader) => T; }, ): Tuple { diff --git a/yarn-project/stdlib/src/avm/avm_accumulated_data.ts b/yarn-project/stdlib/src/avm/avm_accumulated_data.ts index 3e39ccb53948..f7c9b3ce730f 100644 --- a/yarn-project/stdlib/src/avm/avm_accumulated_data.ts +++ b/yarn-project/stdlib/src/avm/avm_accumulated_data.ts @@ -115,9 +115,9 @@ export class AvmAccumulatedData { return new this( reader.readFieldArray(MAX_NOTE_HASHES_PER_TX), reader.readFieldArray(MAX_NULLIFIERS_PER_TX), - reader.readArray(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), + reader.readTuple(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), reader.readObject(FlatPublicLogs), - reader.readArray(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite), + reader.readTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite), ); } diff --git a/yarn-project/stdlib/src/avm/avm_circuit_public_inputs.ts b/yarn-project/stdlib/src/avm/avm_circuit_public_inputs.ts index eb6563aba31e..82b7a195fc2f 100644 --- a/yarn-project/stdlib/src/avm/avm_circuit_public_inputs.ts +++ b/yarn-project/stdlib/src/avm/avm_circuit_public_inputs.ts @@ -206,8 +206,8 @@ export class AvmCircuitPublicInputs { AztecAddress.fromFields(reader), reader.readField(), PublicCallRequestArrayLengths.fromFields(reader), - reader.readArray(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), - reader.readArray(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), + reader.readTuple(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), + reader.readTuple(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), PublicCallRequest.fromFields(reader), PrivateToAvmAccumulatedDataArrayLengths.fromFields(reader), PrivateToAvmAccumulatedDataArrayLengths.fromFields(reader), diff --git a/yarn-project/stdlib/src/kernel/claimed_length_array.ts b/yarn-project/stdlib/src/kernel/claimed_length_array.ts index ef0c3ee67716..66fab34851f9 100644 --- a/yarn-project/stdlib/src/kernel/claimed_length_array.ts +++ b/yarn-project/stdlib/src/kernel/claimed_length_array.ts @@ -42,7 +42,7 @@ export class ClaimedLengthArray { arrayLength: N, ): ClaimedLengthArray { const reader = FieldReader.asReader(fields); - const array = reader.readArray(arrayLength, deserializer); + const array = reader.readTuple(arrayLength, deserializer); const claimedLength = reader.readU32(); return new ClaimedLengthArray(array, claimedLength); } diff --git a/yarn-project/stdlib/src/kernel/private_to_avm_accumulated_data.ts b/yarn-project/stdlib/src/kernel/private_to_avm_accumulated_data.ts index d64dbdad345c..c7dfa601d38d 100644 --- a/yarn-project/stdlib/src/kernel/private_to_avm_accumulated_data.ts +++ b/yarn-project/stdlib/src/kernel/private_to_avm_accumulated_data.ts @@ -73,7 +73,7 @@ export class PrivateToAvmAccumulatedData { return new this( reader.readFieldArray(MAX_NOTE_HASHES_PER_TX), reader.readFieldArray(MAX_NULLIFIERS_PER_TX), - reader.readArray(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), + reader.readTuple(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), ); } diff --git a/yarn-project/stdlib/src/kernel/private_to_public_accumulated_data.ts b/yarn-project/stdlib/src/kernel/private_to_public_accumulated_data.ts index c35b96c5656d..1f8cab457db6 100644 --- a/yarn-project/stdlib/src/kernel/private_to_public_accumulated_data.ts +++ b/yarn-project/stdlib/src/kernel/private_to_public_accumulated_data.ts @@ -62,10 +62,10 @@ export class PrivateToPublicAccumulatedData { return new this( reader.readFieldArray(MAX_NOTE_HASHES_PER_TX), reader.readFieldArray(MAX_NULLIFIERS_PER_TX), - reader.readArray(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), - reader.readArray(MAX_PRIVATE_LOGS_PER_TX, PrivateLog), - reader.readArray(MAX_CONTRACT_CLASS_LOGS_PER_TX, ScopedLogHash), - reader.readArray(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), + reader.readTuple(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), + reader.readTuple(MAX_PRIVATE_LOGS_PER_TX, PrivateLog), + reader.readTuple(MAX_CONTRACT_CLASS_LOGS_PER_TX, ScopedLogHash), + reader.readTuple(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), ); } diff --git a/yarn-project/stdlib/src/tx/protocol_contracts.ts b/yarn-project/stdlib/src/tx/protocol_contracts.ts index 85ba8cb78142..5b64ff1d7893 100644 --- a/yarn-project/stdlib/src/tx/protocol_contracts.ts +++ b/yarn-project/stdlib/src/tx/protocol_contracts.ts @@ -30,7 +30,7 @@ export class ProtocolContracts { static fromFields(fields: Fr[] | FieldReader): ProtocolContracts { const reader = FieldReader.asReader(fields); - return new ProtocolContracts(reader.readArray(MAX_PROTOCOL_CONTRACTS, AztecAddress)); + return new ProtocolContracts(reader.readTuple(MAX_PROTOCOL_CONTRACTS, AztecAddress)); } toFields(): Fr[] {