diff --git a/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.ts b/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.ts index c3f20cc0b109..122f81a07ece 100644 --- a/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.ts +++ b/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.ts @@ -1,11 +1,11 @@ -import { AztecAddress } from '@aztec/circuits.js'; +import { AztecAddress, Vector } from '@aztec/circuits.js'; import { NoteSelector } from '@aztec/foundation/abi'; +import { randomInt } from '@aztec/foundation/crypto'; import { type Fq, Fr } from '@aztec/foundation/fields'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; -import { type EncryptedL2NoteLog } from '../encrypted_l2_note_log.js'; +import { EncryptedL2NoteLog } from '../encrypted_l2_note_log.js'; import { EncryptedLogPayload } from './encrypted_log_payload.js'; -import { Note } from './payload.js'; /** * A class which wraps note data which is pushed on L1. @@ -14,10 +14,6 @@ import { Note } from './payload.js'; */ export class L1NotePayload { constructor( - /** - * A note as emitted from Noir contract. Can be used along with private key to compute nullifier. - */ - public note: Note, /** * Address of the contract this tx is interacting with. */ @@ -30,11 +26,24 @@ export class L1NotePayload { * Type identifier for the underlying note, required to determine how to compute its hash and nullifier. */ public noteTypeId: NoteSelector, + /** + * Note values delivered encrypted. + * @dev Note that to recreate the correct note we need to merge privateNoteValues and publicNoteValues. To do that + * we need access to the contract ABI (that is done in the NoteProcessor). + */ + public privateNoteValues: Fr[], + /** + * Note values delivered in plaintext. + * @dev Note that to recreate the correct note we need to merge privateNoteValues and publicNoteValues. To do that + * we need access to the contract ABI (that is done in the NoteProcessor). + */ + public publicNoteValues: Fr[], ) {} - static fromIncomingBodyPlaintextAndContractAddress( + static fromIncomingBodyPlaintextContractAndPublicValues( plaintext: Buffer, contractAddress: AztecAddress, + publicNoteValues: Fr[], ): L1NotePayload | undefined { try { const reader = BufferReader.asReader(plaintext); @@ -43,35 +52,39 @@ export class L1NotePayload { const storageSlot = fields[0]; const noteTypeId = NoteSelector.fromField(fields[1]); - const note = new Note(fields.slice(2)); + const privateNoteValues = fields.slice(2); - return new L1NotePayload(note, contractAddress, storageSlot, noteTypeId); + return new L1NotePayload(contractAddress, storageSlot, noteTypeId, privateNoteValues, publicNoteValues); } catch (e) { return undefined; } } - static decryptAsIncoming(log: EncryptedL2NoteLog, sk: Fq): L1NotePayload | undefined { - const decryptedLog = EncryptedLogPayload.decryptAsIncoming(log.data, sk); + static decryptAsIncoming(log: Buffer, sk: Fq): L1NotePayload | undefined { + const { publicValues, encryptedLog } = parseLog(log); + const decryptedLog = EncryptedLogPayload.decryptAsIncoming(encryptedLog.data, sk); if (!decryptedLog) { return undefined; } - return this.fromIncomingBodyPlaintextAndContractAddress( + return this.fromIncomingBodyPlaintextContractAndPublicValues( decryptedLog.incomingBodyPlaintext, decryptedLog.contractAddress, + publicValues, ); } - static decryptAsOutgoing(log: EncryptedL2NoteLog, sk: Fq): L1NotePayload | undefined { - const decryptedLog = EncryptedLogPayload.decryptAsOutgoing(log.data, sk); + static decryptAsOutgoing(log: Buffer, sk: Fq): L1NotePayload | undefined { + const { publicValues, encryptedLog } = parseLog(log); + const decryptedLog = EncryptedLogPayload.decryptAsOutgoing(encryptedLog.data, sk); if (!decryptedLog) { return undefined; } - return this.fromIncomingBodyPlaintextAndContractAddress( + return this.fromIncomingBodyPlaintextContractAndPublicValues( decryptedLog.incomingBodyPlaintext, decryptedLog.contractAddress, + publicValues, ); } @@ -80,7 +93,7 @@ export class L1NotePayload { * @returns Buffer representation of the L1NotePayload object. */ toIncomingBodyPlaintext() { - const fields = [this.storageSlot, this.noteTypeId.toField(), ...this.note.items]; + const fields = [this.storageSlot, this.noteTypeId.toField(), ...this.privateNoteValues]; return serializeToBuffer(fields); } @@ -90,15 +103,93 @@ export class L1NotePayload { * @returns A random L1NotePayload object. */ static random(contract = AztecAddress.random()) { - return new L1NotePayload(Note.random(), contract, Fr.random(), NoteSelector.random()); + const numPrivateNoteValues = randomInt(10) + 1; + const privateNoteValues = Array.from({ length: numPrivateNoteValues }, () => Fr.random()); + + const numPublicNoteValues = randomInt(10) + 1; + const publicNoteValues = Array.from({ length: numPublicNoteValues }, () => Fr.random()); + + return new L1NotePayload(contract, Fr.random(), NoteSelector.random(), privateNoteValues, publicNoteValues); } public equals(other: L1NotePayload) { return ( - this.note.equals(other.note) && this.contractAddress.equals(other.contractAddress) && this.storageSlot.equals(other.storageSlot) && - this.noteTypeId.equals(other.noteTypeId) + this.noteTypeId.equals(other.noteTypeId) && + this.privateNoteValues.every((value, index) => value.equals(other.privateNoteValues[index])) && + this.publicNoteValues.every((value, index) => value.equals(other.publicNoteValues[index])) ); } + + toBuffer() { + return serializeToBuffer( + this.contractAddress, + this.storageSlot, + this.noteTypeId, + new Vector(this.privateNoteValues), + new Vector(this.publicNoteValues), + ); + } + + static fromBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + return new L1NotePayload( + reader.readObject(AztecAddress), + reader.readObject(Fr), + reader.readObject(NoteSelector), + reader.readVector(Fr), + reader.readVector(Fr), + ); + } +} + +/** + * Parse the given log into an array of public values and an encrypted log. + * + * @param log - Log to be parsed. + * @returns An object containing the public values and the encrypted log. + */ +function parseLog(log: Buffer) { + // First we remove padding bytes + const processedLog = removePaddingBytes(log); + + const reader = new BufferReader(processedLog); + + // Then we extract public values from the log + const numPublicValues = reader.readUInt8(); + + const publicValuesLength = numPublicValues * Fr.SIZE_IN_BYTES; + const encryptedLogLength = reader.remainingBytes() - publicValuesLength; + + // Now we get the buffer corresponding to the encrypted log + const encryptedLog = new EncryptedL2NoteLog(reader.readBytes(encryptedLogLength)); + + // At last we load the public values + const publicValues = reader.readArray(numPublicValues, Fr); + + return { publicValues, encryptedLog }; +} + +/** + * When a log is emitted via the unencrypted log channel each field contains only 1 byte. OTOH when a log is emitted + * via the encrypted log channel there are no empty bytes. This function removes the padding bytes. + * @param unprocessedLog - Log to be processed. + * @returns Log with padding bytes removed. + */ +function removePaddingBytes(unprocessedLog: Buffer) { + // Determine whether first 31 bytes of each 32 bytes block of bytes are 0 + const is1FieldPerByte = unprocessedLog.every((byte, index) => index % 32 === 31 || byte === 0); + + if (is1FieldPerByte) { + // We take every 32nd byte from the log and return the result + const processedLog = Buffer.alloc(unprocessedLog.length / 32); + for (let i = 0; i < processedLog.length; i++) { + processedLog[i] = unprocessedLog[31 + i * 32]; + } + + return processedLog; + } + + return unprocessedLog; } diff --git a/yarn-project/end-to-end/src/e2e_block_building.test.ts b/yarn-project/end-to-end/src/e2e_block_building.test.ts index d93f42e3ea08..8fd6fc859530 100644 --- a/yarn-project/end-to-end/src/e2e_block_building.test.ts +++ b/yarn-project/end-to-end/src/e2e_block_building.test.ts @@ -299,8 +299,9 @@ describe('e2e_block_building', () => { // compare logs expect(rct.status).toEqual('success'); const noteValues = tx.noteEncryptedLogs.unrollLogs().map(l => { - const notePayload = L1NotePayload.decryptAsIncoming(l, thisWallet.getEncryptionSecret()); - return notePayload?.note.items[0]; + const notePayload = L1NotePayload.decryptAsIncoming(l.data, thisWallet.getEncryptionSecret()); + // In this test we care only about the privately delivered values + return notePayload?.privateNoteValues[0]; }); expect(noteValues[0]).toEqual(new Fr(10)); expect(noteValues[1]).toEqual(new Fr(11)); diff --git a/yarn-project/pxe/src/database/deferred_note_dao.test.ts b/yarn-project/pxe/src/database/deferred_note_dao.test.ts index 90da662aa5c6..79250c687b81 100644 --- a/yarn-project/pxe/src/database/deferred_note_dao.test.ts +++ b/yarn-project/pxe/src/database/deferred_note_dao.test.ts @@ -1,32 +1,18 @@ -import { Note, UnencryptedTxL2Logs, randomTxHash } from '@aztec/circuit-types'; -import { AztecAddress, Fr, Point } from '@aztec/circuits.js'; -import { NoteSelector } from '@aztec/foundation/abi'; +import { L1NotePayload, UnencryptedTxL2Logs, randomTxHash } from '@aztec/circuit-types'; +import { Fr, Point } from '@aztec/circuits.js'; import { randomInt } from '@aztec/foundation/crypto'; import { DeferredNoteDao } from './deferred_note_dao.js'; export const randomDeferredNoteDao = ({ publicKey = Point.random(), - note = Note.random(), - contractAddress = AztecAddress.random(), + payload = L1NotePayload.random(), txHash = randomTxHash(), - storageSlot = Fr.random(), - noteTypeId = NoteSelector.random(), noteHashes = [Fr.random(), Fr.random()], dataStartIndexForTx = randomInt(100), unencryptedLogs = UnencryptedTxL2Logs.random(1, 1), }: Partial = {}) => { - return new DeferredNoteDao( - publicKey, - note, - contractAddress, - storageSlot, - noteTypeId, - txHash, - noteHashes, - dataStartIndexForTx, - unencryptedLogs, - ); + return new DeferredNoteDao(publicKey, payload, txHash, noteHashes, dataStartIndexForTx, unencryptedLogs); }; describe('Deferred Note DAO', () => { diff --git a/yarn-project/pxe/src/database/deferred_note_dao.ts b/yarn-project/pxe/src/database/deferred_note_dao.ts index 1351b5b3b2d4..d5cafe85f895 100644 --- a/yarn-project/pxe/src/database/deferred_note_dao.ts +++ b/yarn-project/pxe/src/database/deferred_note_dao.ts @@ -1,6 +1,5 @@ -import { Note, TxHash, UnencryptedTxL2Logs } from '@aztec/circuit-types'; -import { AztecAddress, Fr, Point, type PublicKey, Vector } from '@aztec/circuits.js'; -import { NoteSelector } from '@aztec/foundation/abi'; +import { L1NotePayload, TxHash, UnencryptedTxL2Logs } from '@aztec/circuit-types'; +import { Fr, Point, type PublicKey, Vector } from '@aztec/circuits.js'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; /** @@ -12,14 +11,8 @@ export class DeferredNoteDao { constructor( /** IvpkM or OvpkM (depending on if incoming or outgoing) the note was encrypted with. */ public publicKey: PublicKey, - /** The note as emitted from the Noir contract. */ - public note: Note, - /** The contract address this note is created in. */ - public contractAddress: AztecAddress, - /** The specific storage location of the note on the contract. */ - public storageSlot: Fr, - /** The type ID of the note on the contract. */ - public noteTypeId: NoteSelector, + /** The note payload delivered via L1. */ + public payload: L1NotePayload, /** The hash of the tx the note was created in. Equal to the first nullifier */ public txHash: TxHash, /** New note hashes in this transaction, one of which belongs to this note */ @@ -33,10 +26,7 @@ export class DeferredNoteDao { toBuffer(): Buffer { return serializeToBuffer( this.publicKey, - this.note, - this.contractAddress, - this.storageSlot, - this.noteTypeId, + this.payload, this.txHash, new Vector(this.noteHashes), this.dataStartIndexForTx, @@ -47,10 +37,7 @@ export class DeferredNoteDao { const reader = BufferReader.asReader(buffer); return new DeferredNoteDao( reader.readObject(Point), - reader.readObject(Note), - reader.readObject(AztecAddress), - reader.readObject(Fr), - reader.readObject(NoteSelector), + reader.readObject(L1NotePayload), reader.readObject(TxHash), reader.readVector(Fr), reader.readNumber(), diff --git a/yarn-project/pxe/src/database/incoming_note_dao.ts b/yarn-project/pxe/src/database/incoming_note_dao.ts index 889612406382..0bd0c6a992c1 100644 --- a/yarn-project/pxe/src/database/incoming_note_dao.ts +++ b/yarn-project/pxe/src/database/incoming_note_dao.ts @@ -41,6 +41,7 @@ export class IncomingNoteDao implements NoteData { ) {} static fromPayloadAndNoteInfo( + note: Note, payload: L1NotePayload, noteInfo: NoteInfo, dataStartIndexForTx: number, @@ -48,7 +49,7 @@ export class IncomingNoteDao implements NoteData { ) { const noteHashIndexInTheWholeTree = BigInt(dataStartIndexForTx + noteInfo.noteHashIndex); return new IncomingNoteDao( - payload.note, + note, payload.contractAddress, payload.storageSlot, payload.noteTypeId, diff --git a/yarn-project/pxe/src/database/kv_pxe_database.ts b/yarn-project/pxe/src/database/kv_pxe_database.ts index 280bc679fd76..412aacf4fa51 100644 --- a/yarn-project/pxe/src/database/kv_pxe_database.ts +++ b/yarn-project/pxe/src/database/kv_pxe_database.ts @@ -215,7 +215,7 @@ export class KVPxeDatabase implements PxeDatabase { const newLength = await this.#deferredNotes.push(...deferredNotes.map(note => note.toBuffer())); for (const [index, note] of deferredNotes.entries()) { const noteId = newLength - deferredNotes.length + index; - await this.#deferredNotesByContract.set(note.contractAddress.toString(), noteId); + await this.#deferredNotesByContract.set(note.payload.contractAddress.toString(), noteId); } } diff --git a/yarn-project/pxe/src/database/outgoing_note_dao.ts b/yarn-project/pxe/src/database/outgoing_note_dao.ts index a2efe0375d49..048bef1ad3b8 100644 --- a/yarn-project/pxe/src/database/outgoing_note_dao.ts +++ b/yarn-project/pxe/src/database/outgoing_note_dao.ts @@ -35,6 +35,7 @@ export class OutgoingNoteDao { ) {} static fromPayloadAndNoteInfo( + note: Note, payload: L1NotePayload, noteInfo: NoteInfo, dataStartIndexForTx: number, @@ -42,7 +43,7 @@ export class OutgoingNoteDao { ) { const noteHashIndexInTheWholeTree = BigInt(dataStartIndexForTx + noteInfo.noteHashIndex); return new OutgoingNoteDao( - payload.note, + note, payload.contractAddress, payload.storageSlot, payload.noteTypeId, diff --git a/yarn-project/pxe/src/note_processor/note_processor.test.ts b/yarn-project/pxe/src/note_processor/note_processor.test.ts index fc9211b7f919..0d145b91b99b 100644 --- a/yarn-project/pxe/src/note_processor/note_processor.test.ts +++ b/yarn-project/pxe/src/note_processor/note_processor.test.ts @@ -1,4 +1,11 @@ -import { type AztecNode, EncryptedL2NoteLog, EncryptedLogPayload, L1NotePayload, L2Block } from '@aztec/circuit-types'; +import { + type AztecNode, + EncryptedL2NoteLog, + EncryptedLogPayload, + L1NotePayload, + L2Block, + Note, +} from '@aztec/circuit-types'; import { AztecAddress, CompleteAddress, @@ -71,11 +78,18 @@ class MockNoteRequest { ); } - get notePayload(): L1NotePayload | undefined { - return L1NotePayload.fromIncomingBodyPlaintextAndContractAddress( + get snippetOfNoteDao() { + const payload = L1NotePayload.fromIncomingBodyPlaintextContractAndPublicValues( this.logPayload.incomingBodyPlaintext, this.logPayload.contractAddress, - ); + [], + )!; + return { + note: new Note(payload.privateNoteValues), + contractAddress: payload.contractAddress, + storageSlot: payload.storageSlot, + noteTypeId: payload.noteTypeId, + }; } } @@ -113,8 +127,8 @@ describe('Note Processor', () => { // Then we update the relevant note hashes to match the note requests for (const request of noteRequestsForBlock) { - const notePayload = request.notePayload; - const noteHash = pedersenHash(notePayload!.note.items); + const note = request.snippetOfNoteDao.note; + const noteHash = pedersenHash(note.items); block.body.txEffects[request.txIndex].noteHashes[request.noteHashIndex] = noteHash; // Now we populate the log - to simplify we say that there is only 1 function invocation in each tx @@ -198,7 +212,7 @@ describe('Note Processor', () => { expect(addNotesSpy).toHaveBeenCalledWith( [ expect.objectContaining({ - ...request.notePayload, + ...request.snippetOfNoteDao, index: request.indexWithinNoteHashTree, }), ], @@ -215,7 +229,7 @@ describe('Note Processor', () => { expect(addNotesSpy).toHaveBeenCalledTimes(1); // For outgoing notes, the resulting DAO does not contain index. - expect(addNotesSpy).toHaveBeenCalledWith([], [expect.objectContaining(request.notePayload)], account.address); + expect(addNotesSpy).toHaveBeenCalledWith([], [expect.objectContaining(request.snippetOfNoteDao)], account.address); }, 25_000); it('should store multiple notes that belong to us', async () => { @@ -242,23 +256,23 @@ describe('Note Processor', () => { // Incoming should contain notes from requests 0, 2, 4 because in those requests we set owner ivpk. [ expect.objectContaining({ - ...requests[0].notePayload, + ...requests[0].snippetOfNoteDao, index: requests[0].indexWithinNoteHashTree, }), expect.objectContaining({ - ...requests[2].notePayload, + ...requests[2].snippetOfNoteDao, index: requests[2].indexWithinNoteHashTree, }), expect.objectContaining({ - ...requests[4].notePayload, + ...requests[4].snippetOfNoteDao, index: requests[4].indexWithinNoteHashTree, }), ], // Outgoing should contain notes from requests 0, 1, 4 because in those requests we set owner ovKeys. [ - expect.objectContaining(requests[0].notePayload), - expect.objectContaining(requests[1].notePayload), - expect.objectContaining(requests[4].notePayload), + expect.objectContaining(requests[0].snippetOfNoteDao), + expect.objectContaining(requests[1].snippetOfNoteDao), + expect.objectContaining(requests[4].snippetOfNoteDao), ], account.address, ); @@ -294,11 +308,11 @@ describe('Note Processor', () => { { const addedIncoming: IncomingNoteDao[] = addNotesSpy.mock.calls[0][0]; expect(addedIncoming.map(dao => dao)).toEqual([ - expect.objectContaining({ ...requests[0].notePayload, index: requests[0].indexWithinNoteHashTree }), - expect.objectContaining({ ...requests[1].notePayload, index: requests[1].indexWithinNoteHashTree }), - expect.objectContaining({ ...requests[2].notePayload, index: requests[2].indexWithinNoteHashTree }), - expect.objectContaining({ ...requests[3].notePayload, index: requests[3].indexWithinNoteHashTree }), - expect.objectContaining({ ...requests[4].notePayload, index: requests[4].indexWithinNoteHashTree }), + expect.objectContaining({ ...requests[0].snippetOfNoteDao, index: requests[0].indexWithinNoteHashTree }), + expect.objectContaining({ ...requests[1].snippetOfNoteDao, index: requests[1].indexWithinNoteHashTree }), + expect.objectContaining({ ...requests[2].snippetOfNoteDao, index: requests[2].indexWithinNoteHashTree }), + expect.objectContaining({ ...requests[3].snippetOfNoteDao, index: requests[3].indexWithinNoteHashTree }), + expect.objectContaining({ ...requests[4].snippetOfNoteDao, index: requests[4].indexWithinNoteHashTree }), ]); // Check that every note has a different nonce. @@ -311,11 +325,11 @@ describe('Note Processor', () => { { const addedOutgoing: OutgoingNoteDao[] = addNotesSpy.mock.calls[0][1]; expect(addedOutgoing.map(dao => dao)).toEqual([ - expect.objectContaining(requests[0].notePayload), - expect.objectContaining(requests[1].notePayload), - expect.objectContaining(requests[2].notePayload), - expect.objectContaining(requests[3].notePayload), - expect.objectContaining(requests[4].notePayload), + expect.objectContaining(requests[0].snippetOfNoteDao), + expect.objectContaining(requests[1].snippetOfNoteDao), + expect.objectContaining(requests[2].snippetOfNoteDao), + expect.objectContaining(requests[3].snippetOfNoteDao), + expect.objectContaining(requests[4].snippetOfNoteDao), ]); // Outgoing note daos do not have a nonce so we don't check it. diff --git a/yarn-project/pxe/src/note_processor/note_processor.ts b/yarn-project/pxe/src/note_processor/note_processor.ts index 3600989eb6da..9e4fcfae5cb2 100644 --- a/yarn-project/pxe/src/note_processor/note_processor.ts +++ b/yarn-project/pxe/src/note_processor/note_processor.ts @@ -1,4 +1,4 @@ -import { type AztecNode, EncryptedL2NoteLog, L1NotePayload, type L2Block } from '@aztec/circuit-types'; +import { type AztecNode, L1NotePayload, type L2Block } from '@aztec/circuit-types'; import { type NoteProcessorStats } from '@aztec/circuit-types/stats'; import { type CompleteAddress, @@ -7,9 +7,8 @@ import { type PublicKey, computeAddressSecret, } from '@aztec/circuits.js'; -import { Fr } from '@aztec/foundation/fields'; +import { type Fr } from '@aztec/foundation/fields'; import { type Logger, createDebugLogger } from '@aztec/foundation/log'; -import { BufferReader } from '@aztec/foundation/serialize'; import { Timer } from '@aztec/foundation/timer'; import { type KeyStore } from '@aztec/key-store'; import { type AcirSimulator } from '@aztec/simulator'; @@ -19,7 +18,6 @@ import { type IncomingNoteDao } from '../database/incoming_note_dao.js'; import { type PxeDatabase } from '../database/index.js'; import { type OutgoingNoteDao } from '../database/outgoing_note_dao.js'; import { getAcirSimulator } from '../simulator/index.js'; -import { addPublicValuesToPayload } from './utils/add_public_values_to_payload.js'; import { produceNoteDaos } from './utils/produce_note_daos.js'; /** @@ -158,10 +156,9 @@ export class NoteProcessor { for (const txFunctionLogs of [encryptedTxFunctionLogs, unencryptedTxFunctionLogs]) { for (const functionLogs of txFunctionLogs) { for (const unprocessedLog of functionLogs.logs) { - const { publicValues, encryptedLog } = parseLog(unprocessedLog.data); this.stats.seen++; - const incomingNotePayload = L1NotePayload.decryptAsIncoming(encryptedLog, addressSecret); - const outgoingNotePayload = L1NotePayload.decryptAsOutgoing(encryptedLog, ovskM); + const incomingNotePayload = L1NotePayload.decryptAsIncoming(unprocessedLog.data, addressSecret); + const outgoingNotePayload = L1NotePayload.decryptAsOutgoing(unprocessedLog.data, ovskM); if (incomingNotePayload || outgoingNotePayload) { if (incomingNotePayload && outgoingNotePayload && !incomingNotePayload.equals(outgoingNotePayload)) { @@ -172,11 +169,7 @@ export class NoteProcessor { ); } - let payload = incomingNotePayload || outgoingNotePayload; - - if (publicValues.length > 0) { - payload = await addPublicValuesToPayload(this.db, payload!, publicValues); - } + const payload = incomingNotePayload || outgoingNotePayload; const txEffect = block.body.txEffects[indexOfTxInABlock]; const { incomingNote, outgoingNote, incomingDeferredNote, outgoingDeferredNote } = @@ -289,15 +282,15 @@ export class NoteProcessor { await this.db.addDeferredNotes([...deferredIncomingNotes, ...deferredOutgoingNotes]); deferredIncomingNotes.forEach(noteDao => { this.log.verbose( - `Deferred incoming note for contract ${noteDao.contractAddress} at slot ${ - noteDao.storageSlot + `Deferred incoming note for contract ${noteDao.payload.contractAddress} at slot ${ + noteDao.payload.storageSlot } in tx ${noteDao.txHash.toString()}`, ); }); deferredOutgoingNotes.forEach(noteDao => { this.log.verbose( - `Deferred outgoing note for contract ${noteDao.contractAddress} at slot ${ - noteDao.storageSlot + `Deferred outgoing note for contract ${noteDao.payload.contractAddress} at slot ${ + noteDao.payload.storageSlot } in tx ${noteDao.txHash.toString()}`, ); }); @@ -322,18 +315,7 @@ export class NoteProcessor { const outgoingNotes: OutgoingNoteDao[] = []; for (const deferredNote of deferredNoteDaos) { - const { - publicKey, - note, - contractAddress, - storageSlot, - noteTypeId, - txHash, - noteHashes, - dataStartIndexForTx, - unencryptedLogs, - } = deferredNote; - const payload = new L1NotePayload(note, contractAddress, storageSlot, noteTypeId); + const { publicKey, payload, txHash, noteHashes, dataStartIndexForTx, unencryptedLogs } = deferredNote; const isIncoming = publicKey.equals(this.ivpkM); const isOutgoing = publicKey.equals(this.ovpkM); @@ -376,53 +358,3 @@ export class NoteProcessor { return { incomingNotes, outgoingNotes }; } } - -/** - * Parse the given log into an array of public values and an encrypted log. - * - * @param log - Log to be parsed. - * @returns An object containing the public values and the encrypted log. - */ -function parseLog(log: Buffer) { - // First we remove padding bytes - const processedLog = removePaddingBytes(log); - - const reader = new BufferReader(processedLog); - - // Then we extract public values from the log - const numPublicValues = reader.readUInt8(); - - const publicValuesLength = numPublicValues * Fr.SIZE_IN_BYTES; - const encryptedLogLength = reader.remainingBytes() - publicValuesLength; - - // Now we get the buffer corresponding to the encrypted log - const encryptedLog = new EncryptedL2NoteLog(reader.readBytes(encryptedLogLength)); - - // At last we load the public values - const publicValues = reader.readArray(numPublicValues, Fr); - - return { publicValues, encryptedLog }; -} - -/** - * When a log is emitted via the unencrypted log channel each field contains only 1 byte. OTOH when a log is emitted - * via the encrypted log channel there are no empty bytes. This function removes the padding bytes. - * @param unprocessedLog - Log to be processed. - * @returns Log with padding bytes removed. - */ -function removePaddingBytes(unprocessedLog: Buffer) { - // Determine whether first 31 bytes of each 32 bytes block of bytes are 0 - const is1FieldPerByte = unprocessedLog.every((byte, index) => index % 32 === 31 || byte === 0); - - if (is1FieldPerByte) { - // We take every 32nd byte from the log and return the result - const processedLog = Buffer.alloc(unprocessedLog.length / 32); - for (let i = 0; i < processedLog.length; i++) { - processedLog[i] = unprocessedLog[31 + i * 32]; - } - - return processedLog; - } - - return unprocessedLog; -} diff --git a/yarn-project/pxe/src/note_processor/utils/add_public_values_to_payload.ts b/yarn-project/pxe/src/note_processor/utils/add_public_values_to_payload.ts index 431febd06b83..8a249ceab6d2 100644 --- a/yarn-project/pxe/src/note_processor/utils/add_public_values_to_payload.ts +++ b/yarn-project/pxe/src/note_processor/utils/add_public_values_to_payload.ts @@ -1,25 +1,26 @@ -import { L1NotePayload, Note } from '@aztec/circuit-types'; -import { type Fr } from '@aztec/foundation/fields'; +import { type L1NotePayload, Note } from '@aztec/circuit-types'; import { ContractNotFoundError } from '@aztec/simulator'; import { type PxeDatabase } from '../../database/pxe_database.js'; /** - * Inserts publicly delivered values into the note payload. + * Merges privately and publicly delivered note values. * @param db - PXE database used to fetch contract instance and artifact. - * @param payload - Note payload to which public fields should be added. - * @param publicValues - List of public values to be added to the note payload. + * @param payload - Payload corresponding to the note. * @returns Note payload with public fields added. */ -export async function addPublicValuesToPayload( +export async function getOrderedNoteItems( db: PxeDatabase, - payload: L1NotePayload, - publicValues: Fr[], -): Promise { - const instance = await db.getContractInstance(payload.contractAddress); + { contractAddress, noteTypeId, privateNoteValues, publicNoteValues }: L1NotePayload, +): Promise { + if (publicNoteValues.length === 0) { + return new Note(privateNoteValues); + } + + const instance = await db.getContractInstance(contractAddress); if (!instance) { throw new ContractNotFoundError( - `Could not find instance for ${payload.contractAddress.toString()}. This should never happen here as the partial notes flow should be triggered only for non-deferred notes.`, + `Could not find instance for ${contractAddress.toString()}. This should never happen here as the partial notes flow should be triggered only for non-deferred notes.`, ); } @@ -30,27 +31,27 @@ export async function addPublicValuesToPayload( ); } - const noteFields = Object.values(artifact.notes).find(note => note.id.equals(payload.noteTypeId))?.fields; + const noteFields = Object.values(artifact.notes).find(note => note.id.equals(noteTypeId))?.fields; if (!noteFields) { - throw new Error(`Could not find note fields for note type ${payload.noteTypeId.toString()}.`); + throw new Error(`Could not find note fields for note type ${noteTypeId.toString()}.`); } // We sort note fields by index so that we can iterate over them in order. noteFields.sort((a, b) => a.index - b.index); // Now we insert the public fields into the note based on its indices defined in the ABI. - const modifiedNoteItems = [...payload.note.items]; + const modifiedNoteItems = privateNoteValues; let indexInPublicValues = 0; for (let i = 0; i < noteFields.length; i++) { const noteField = noteFields[i]; if (noteField.nullable) { if (i == noteFields.length - 1) { // We are processing the last field so we simply insert the rest of the public fields at the end - modifiedNoteItems.push(...publicValues.slice(indexInPublicValues)); + modifiedNoteItems.push(...publicNoteValues.slice(indexInPublicValues)); } else { const noteFieldLength = noteFields[i + 1].index - noteField.index; - const publicValuesToInsert = publicValues.slice(indexInPublicValues, indexInPublicValues + noteFieldLength); + const publicValuesToInsert = publicNoteValues.slice(indexInPublicValues, indexInPublicValues + noteFieldLength); indexInPublicValues += noteFieldLength; // Now we insert the public fields at the note field index modifiedNoteItems.splice(noteField.index, 0, ...publicValuesToInsert); @@ -58,10 +59,5 @@ export async function addPublicValuesToPayload( } } - return new L1NotePayload( - new Note(modifiedNoteItems), - payload.contractAddress, - payload.storageSlot, - payload.noteTypeId, - ); + return new Note(modifiedNoteItems); } diff --git a/yarn-project/pxe/src/note_processor/utils/brute_force_note_info.ts b/yarn-project/pxe/src/note_processor/utils/brute_force_note_info.ts index f7515a61b255..cde87260e719 100644 --- a/yarn-project/pxe/src/note_processor/utils/brute_force_note_info.ts +++ b/yarn-project/pxe/src/note_processor/utils/brute_force_note_info.ts @@ -1,5 +1,7 @@ -import { type L1NotePayload, type TxHash } from '@aztec/circuit-types'; +import { type Note, type TxHash } from '@aztec/circuit-types'; +import { type AztecAddress } from '@aztec/circuits.js'; import { computeNoteHashNonce, siloNullifier } from '@aztec/circuits.js/hash'; +import { type NoteSelector } from '@aztec/foundation/abi'; import { Fr } from '@aztec/foundation/fields'; import { type AcirSimulator } from '@aztec/simulator'; @@ -18,7 +20,10 @@ export interface NoteInfo { * @remarks This method assists in identifying spent notes in the note hash tree. * @param siloedNoteHashes - Note hashes in the tx. One of them should correspond to the note we are looking for * @param txHash - Hash of a tx the note was emitted in. - * @param l1NotePayload - The note payload. + * @param contractAddress - Address of the contract the note was emitted in. + * @param storageSlot - Storage slot of the note. + * @param noteTypeId - Type of the note. + * @param note - Note items. * @param excludedIndices - Indices that have been assigned a note in the same tx. Notes in a tx can have the same * l1NotePayload. We need to find a different index for each replicate. * @param computeNullifier - A flag indicating whether to compute the nullifier or just return 0. @@ -29,7 +34,10 @@ export async function bruteForceNoteInfo( simulator: AcirSimulator, siloedNoteHashes: Fr[], txHash: TxHash, - { contractAddress, storageSlot, noteTypeId, note }: L1NotePayload, + contractAddress: AztecAddress, + storageSlot: Fr, + noteTypeId: NoteSelector, + note: Note, excludedIndices: Set, computeNullifier: boolean, ): Promise { diff --git a/yarn-project/pxe/src/note_processor/utils/produce_note_daos.ts b/yarn-project/pxe/src/note_processor/utils/produce_note_daos.ts index 5198664efe23..15127d36cd9d 100644 --- a/yarn-project/pxe/src/note_processor/utils/produce_note_daos.ts +++ b/yarn-project/pxe/src/note_processor/utils/produce_note_daos.ts @@ -46,8 +46,6 @@ export async function produceNoteDaos( incomingDeferredNote: DeferredNoteDao | undefined; outgoingDeferredNote: DeferredNoteDao | undefined; }> { - // WARNING: This code is full of tech debt and will be refactored once we have final design of partial notes - // delivery. if (!ivpkM && !ovpkM) { throw new Error('Both ivpkM and ovpkM are undefined. Cannot create note.'); } @@ -78,11 +76,11 @@ export async function produceNoteDaos( // Incoming note is defined meaning that this PXE has both the incoming and outgoing keys. We can skip computing // note hash and note index since we already have them in the incoming note. outgoingNote = new OutgoingNoteDao( - payload.note, - payload.contractAddress, - payload.storageSlot, - payload.noteTypeId, - txHash, + incomingNote.note, + incomingNote.contractAddress, + incomingNote.storageSlot, + incomingNote.noteTypeId, + incomingNote.txHash, incomingNote.nonce, incomingNote.noteHash, incomingNote.index, diff --git a/yarn-project/pxe/src/note_processor/utils/produce_note_daos_for_key.ts b/yarn-project/pxe/src/note_processor/utils/produce_note_daos_for_key.ts index 313170976b96..42b04fc3c137 100644 --- a/yarn-project/pxe/src/note_processor/utils/produce_note_daos_for_key.ts +++ b/yarn-project/pxe/src/note_processor/utils/produce_note_daos_for_key.ts @@ -1,10 +1,11 @@ -import { type L1NotePayload, type TxHash, type UnencryptedTxL2Logs } from '@aztec/circuit-types'; +import { type L1NotePayload, type Note, type TxHash, type UnencryptedTxL2Logs } from '@aztec/circuit-types'; import { type Fr, type PublicKey } from '@aztec/circuits.js'; import { type Logger } from '@aztec/foundation/log'; import { type AcirSimulator, ContractNotFoundError } from '@aztec/simulator'; import { DeferredNoteDao } from '../../database/deferred_note_dao.js'; import { type PxeDatabase } from '../../database/pxe_database.js'; +import { getOrderedNoteItems } from './add_public_values_to_payload.js'; import { type NoteInfo, bruteForceNoteInfo } from './brute_force_note_info.js'; export async function produceNoteDaosForKey( @@ -18,38 +19,40 @@ export async function produceNoteDaosForKey( excludedIndices: Set, logger: Logger, unencryptedLogs: UnencryptedTxL2Logs, - daoConstructor: (payload: L1NotePayload, noteInfo: NoteInfo, dataStartIndexForTx: number, pkM: PublicKey) => T, + daoConstructor: ( + note: Note, + payload: L1NotePayload, + noteInfo: NoteInfo, + dataStartIndexForTx: number, + pkM: PublicKey, + ) => T, ): Promise<[T | undefined, DeferredNoteDao | undefined]> { let noteDao: T | undefined; let deferredNoteDao: DeferredNoteDao | undefined; try { + // We get the note by merging publicly and privately delivered note values. + const note = await getOrderedNoteItems(db, payload); + const noteInfo = await bruteForceNoteInfo( simulator, noteHashes, txHash, - payload, + payload.contractAddress, + payload.storageSlot, + payload.noteTypeId, + note, excludedIndices, true, // For incoming we compute a nullifier (recipient of incoming is the party that nullifies). ); excludedIndices?.add(noteInfo.noteHashIndex); - noteDao = daoConstructor(payload, noteInfo, dataStartIndexForTx, pkM); + noteDao = daoConstructor(note, payload, noteInfo, dataStartIndexForTx, pkM); } catch (e) { if (e instanceof ContractNotFoundError) { logger.warn(e.message); - deferredNoteDao = new DeferredNoteDao( - pkM, - payload.note, - payload.contractAddress, - payload.storageSlot, - payload.noteTypeId, - txHash, - noteHashes, - dataStartIndexForTx, - unencryptedLogs, - ); + deferredNoteDao = new DeferredNoteDao(pkM, payload, txHash, noteHashes, dataStartIndexForTx, unencryptedLogs); } else { logger.error(`Could not process note because of "${e}". Discarding note...`); }