import { PRIVATE_LOG_LENGTH, PRIVATE_LOG_SIZE_IN_FIELDS } from '@aztec/constants'; import { type FieldsOf, makeTuple } from '@aztec/foundation/array'; import { padArrayEnd } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/curves/bn254'; import { schemas } from '@aztec/foundation/schemas'; import { BufferReader, FieldReader, type Tuple, serializeToBuffer, serializeToFields, } from '@aztec/foundation/serialize'; import { inspect } from 'util'; import { z } from 'zod'; export class PrivateLog { static SIZE_IN_BYTES = Fr.SIZE_IN_BYTES * PRIVATE_LOG_LENGTH; constructor( public fields: Tuple, // Named `emittedLength` instead of `length` to avoid being confused with fields.length. public emittedLength: number, ) {} static from(fields: FieldsOf) { return new PrivateLog(...PrivateLog.getFields(fields)); } static getFields(fields: FieldsOf) { return [fields.fields, fields.emittedLength] as const; } toFields(): Fr[] { return serializeToFields(...PrivateLog.getFields(this)); } static fromFields(fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); return new PrivateLog(reader.readFieldArray(PRIVATE_LOG_SIZE_IN_FIELDS), reader.readU32()); } getEmittedFields() { return this.fields.slice(0, this.emittedLength); } getEmittedFieldsWithoutTag() { return this.fields.slice(1, this.emittedLength); } toBlobFields(): Fr[] { return this.getEmittedFields(); } static fromBlobFields(emittedLength: number, fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); const emittedFields = reader.readFieldArray(emittedLength); return new PrivateLog(padArrayEnd(emittedFields, Fr.ZERO, PRIVATE_LOG_SIZE_IN_FIELDS), emittedLength); } isEmpty() { // Faster to check emittedLength than compare all fields against zero return this.emittedLength === 0; } static empty() { return new PrivateLog(makeTuple(PRIVATE_LOG_SIZE_IN_FIELDS, Fr.zero), 0); } toBuffer(): Buffer { return serializeToBuffer(this.fields, this.emittedLength); } static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); return new PrivateLog(reader.readTuple(PRIVATE_LOG_SIZE_IN_FIELDS, Fr), reader.readNumber()); } static random(tag = Fr.random()) { const fields = makeTuple(PRIVATE_LOG_SIZE_IN_FIELDS, Fr.random); fields[0] = tag; return new PrivateLog(fields, PRIVATE_LOG_SIZE_IN_FIELDS); } static get schema() { return z .object({ fields: z.array(schemas.Fr), emittedLength: z.number(), }) .strict() .transform(({ fields, emittedLength }) => PrivateLog.fromFields(fields.concat(new Fr(emittedLength)))); } /** * Creates a PrivateLog from a plain object without Zod validation. * This method is optimized for performance and skips validation, making it suitable * for deserializing trusted data (e.g., from C++ via MessagePack). * @param obj - Plain object containing PrivateLog fields * @returns A PrivateLog instance */ static fromPlainObject(obj: any): PrivateLog { if (obj instanceof PrivateLog) { return obj; } return new PrivateLog( obj.fields.map((f: any) => Fr.fromPlainObject(f)), obj.emittedLength, ); } equals(other: PrivateLog) { return this.fields.every((field, i) => field.equals(other.fields[i])) && this.emittedLength === other.emittedLength; } [inspect.custom](): string { return `PrivateLog { fields: [${this.fields.map(x => inspect(x)).join(', ')}], emittedLength: ${this.emittedLength}, }`; } }