From 12ddea2077cac8c122d200f84f7d05e9489c8027 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 18 Jun 2024 11:49:14 +0000 Subject: [PATCH] WIP --- .../logs/l1_payload/l1_note_payload.test.ts | 4 +- .../src/logs/l1_payload/l1_note_payload.ts | 38 +++++++++++- .../src/logs/l1_payload/tagged_log.ts | 60 +++++++++++-------- .../pxe/src/note_processor/note_processor.ts | 23 ++++--- 4 files changed, 82 insertions(+), 43 deletions(-) diff --git a/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.test.ts b/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.test.ts index 5a0bddc2081a..cc85f4fb7696 100644 --- a/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.test.ts +++ b/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.test.ts @@ -36,13 +36,13 @@ describe('L1 Note Payload', () => { it('decrypt a log as incoming', () => { const recreated = L1NotePayload.decryptAsIncoming(encrypted, ivskM); - expect(recreated.toBuffer()).toEqual(payload.toBuffer()); + expect(recreated!.toBuffer()).toEqual(payload.toBuffer()); }); it('decrypt a log as outgoing', () => { const recreated = L1NotePayload.decryptAsOutgoing(encrypted, ovskM); - expect(recreated.toBuffer()).toEqual(payload.toBuffer()); + expect(recreated!.toBuffer()).toEqual(payload.toBuffer()); }); }); 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 ee28010c1bfb..7213d4be8e5c 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,4 +1,5 @@ import { AztecAddress, type GrumpkinPrivateKey, type KeyValidationRequest, type PublicKey } from '@aztec/circuits.js'; +import { randomBytes } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; @@ -6,6 +7,16 @@ import { EncryptedNoteLogIncomingBody } from './encrypted_log_incoming_body/inde import { L1Payload } from './l1_payload.js'; import { Note } from './payload.js'; +// Note type id can occupy only 4 bytes. The rest is 0 and is used to determine whether a note was successfully +// decrypted in tagged_log.ts +const NUM_BYTES_PER_NOTE_TYPE_ID = 4; + +function isNoteTypeIdValid(noteTypeId: Fr): boolean { + const buf = noteTypeId.toBuffer(); + // check that the first 28 bytes are zero + return !buf.subarray(0, Fr.SIZE_IN_BYTES - NUM_BYTES_PER_NOTE_TYPE_ID).some(x => x !== 0); +} + /** * A class which wraps note data which is pushed on L1. * @remarks This data is required to compute a nullifier/to spend a note. Along with that this class contains @@ -31,6 +42,9 @@ export class L1NotePayload extends L1Payload { public noteTypeId: Fr, ) { super(); + if (!isNoteTypeIdValid(noteTypeId)) { + throw new Error('NoteTypeId should occupy only 4 bytes'); + } } /** @@ -62,7 +76,13 @@ export class L1NotePayload extends L1Payload { * @returns A random L1NotePayload object. */ static random(contract = AztecAddress.random()) { - return new L1NotePayload(Note.random(), contract, Fr.random(), Fr.random()); + const noteTypeId = Fr.fromBuffer( + Buffer.concat([ + Buffer.alloc(Fr.SIZE_IN_BYTES - NUM_BYTES_PER_NOTE_TYPE_ID), + randomBytes(NUM_BYTES_PER_NOTE_TYPE_ID), + ]), + ); + return new L1NotePayload(Note.random(), contract, Fr.random(), noteTypeId); } public encrypt(ephSk: GrumpkinPrivateKey, recipient: AztecAddress, ivpk: PublicKey, ovKeys: KeyValidationRequest) { @@ -98,7 +118,13 @@ export class L1NotePayload extends L1Payload { EncryptedNoteLogIncomingBody.fromCiphertext, ); - return new L1NotePayload(incomingBody.note, address, incomingBody.storageSlot, incomingBody.noteTypeId); + if (isNoteTypeIdValid(incomingBody.noteTypeId)) { + // We received valid note type id, hence the encryption was performed correctly + return new L1NotePayload(incomingBody.note, address, incomingBody.storageSlot, incomingBody.noteTypeId); + } + + // We failed to decrypt the note, return undefined + return undefined; } /** @@ -124,7 +150,13 @@ export class L1NotePayload extends L1Payload { EncryptedNoteLogIncomingBody.fromCiphertext, ); - return new L1NotePayload(incomingBody.note, address, incomingBody.storageSlot, incomingBody.noteTypeId); + if (isNoteTypeIdValid(incomingBody.noteTypeId)) { + // We received valid note type id, hence the encryption was performed correctly + return new L1NotePayload(incomingBody.note, address, incomingBody.storageSlot, incomingBody.noteTypeId); + } + + // We failed to decrypt the note, return undefined + return undefined; } public equals(other: L1NotePayload) { diff --git a/yarn-project/circuit-types/src/logs/l1_payload/tagged_log.ts b/yarn-project/circuit-types/src/logs/l1_payload/tagged_log.ts index 4904479acdb2..55221a73a745 100644 --- a/yarn-project/circuit-types/src/logs/l1_payload/tagged_log.ts +++ b/yarn-project/circuit-types/src/logs/l1_payload/tagged_log.ts @@ -78,26 +78,30 @@ export class TaggedLog { ivsk: GrumpkinPrivateKey, payloadType: typeof L1NotePayload | typeof L1EventPayload = L1NotePayload, ): TaggedLog | undefined { - // Right now heavily abusing that we will likely fail if bad decryption - // as some field will likely end up not being in the field etc. - try { - if (payloadType === L1EventPayload) { + if (payloadType === L1EventPayload) { + // Right now heavily abusing that we will likely fail if bad decryption + // as some field will likely end up not being in the field etc. + try { const reader = BufferReader.asReader((data as EncryptedL2Log).data); const incomingTag = Fr.fromBuffer(reader); const outgoingTag = Fr.fromBuffer(reader); // We must pass the entire encrypted log in. The tags are not stripped here from the original data const payload = L1EventPayload.decryptAsIncoming(data as EncryptedL2Log, ivsk); return new TaggedLog(payload, incomingTag, outgoingTag); - } else { - const input = Buffer.isBuffer(data) ? data : Buffer.from((data as bigint[]).map((x: bigint) => Number(x))); - const reader = BufferReader.asReader(input); - const incomingTag = Fr.fromBuffer(reader); - const outgoingTag = Fr.fromBuffer(reader); - const payload = L1NotePayload.decryptAsIncoming(reader.readToEnd(), ivsk); - return new TaggedLog(payload, incomingTag, outgoingTag); + } catch (e) { + return; + } + } else { + const input = Buffer.isBuffer(data) ? data : Buffer.from((data as bigint[]).map((x: bigint) => Number(x))); + const reader = BufferReader.asReader(input); + const incomingTag = Fr.fromBuffer(reader); + const outgoingTag = Fr.fromBuffer(reader); + const payload = L1NotePayload.decryptAsIncoming(reader.readToEnd(), ivsk); + if (!payload) { + // We failed to decrypt the note, return undefined + return; } - } catch (e) { - return; + return new TaggedLog(payload, incomingTag, outgoingTag); } } @@ -116,25 +120,29 @@ export class TaggedLog { ovsk: GrumpkinPrivateKey, payloadType: typeof L1NotePayload | typeof L1EventPayload = L1NotePayload, ) { - // Right now heavily abusing that we will likely fail if bad decryption - // as some field will likely end up not being in the field etc. - try { - if (payloadType === L1EventPayload) { + if (payloadType === L1EventPayload) { + // Right now heavily abusing that we will likely fail if bad decryption + // as some field will likely end up not being in the field etc. + try { const reader = BufferReader.asReader((data as EncryptedL2Log).data); const incomingTag = Fr.fromBuffer(reader); const outgoingTag = Fr.fromBuffer(reader); const payload = L1EventPayload.decryptAsOutgoing(data as EncryptedL2Log, ovsk); return new TaggedLog(payload, incomingTag, outgoingTag); - } else { - const input = Buffer.isBuffer(data) ? data : Buffer.from((data as bigint[]).map((x: bigint) => Number(x))); - const reader = BufferReader.asReader(input); - const incomingTag = Fr.fromBuffer(reader); - const outgoingTag = Fr.fromBuffer(reader); - const payload = L1NotePayload.decryptAsOutgoing(reader.readToEnd(), ovsk); - return new TaggedLog(payload, incomingTag, outgoingTag); + } catch (e) { + return; + } + } else { + const input = Buffer.isBuffer(data) ? data : Buffer.from((data as bigint[]).map((x: bigint) => Number(x))); + const reader = BufferReader.asReader(input); + const incomingTag = Fr.fromBuffer(reader); + const outgoingTag = Fr.fromBuffer(reader); + const payload = L1NotePayload.decryptAsOutgoing(reader.readToEnd(), ovsk); + if (!payload) { + // We failed to decrypt the note, return undefined + return; } - } catch (e) { - return; + return new TaggedLog(payload, incomingTag, outgoingTag); } } } diff --git a/yarn-project/pxe/src/note_processor/note_processor.ts b/yarn-project/pxe/src/note_processor/note_processor.ts index 61b1a820ba0a..859309439d06 100644 --- a/yarn-project/pxe/src/note_processor/note_processor.ts +++ b/yarn-project/pxe/src/note_processor/note_processor.ts @@ -151,18 +151,17 @@ export class NoteProcessor { const outgoingTaggedNote = TaggedLog.decryptAsOutgoing(log.data, ovskM)!; if (incomingTaggedNote || outgoingTaggedNote) { - // TODO(#7053): Re-enable this check - // if ( - // incomingTaggedNote && - // outgoingTaggedNote && - // !incomingTaggedNote.payload.equals(outgoingTaggedNote.payload) - // ) { - // throw new Error( - // `Incoming and outgoing note payloads do not match. Incoming: ${JSON.stringify( - // incomingTaggedNote.payload, - // )}, Outgoing: ${JSON.stringify(outgoingTaggedNote.payload)}`, - // ); - // } + if ( + incomingTaggedNote && + outgoingTaggedNote && + !incomingTaggedNote.payload.equals(outgoingTaggedNote.payload) + ) { + throw new Error( + `Incoming and outgoing note payloads do not match. Incoming: ${JSON.stringify( + incomingTaggedNote.payload, + )}, Outgoing: ${JSON.stringify(outgoingTaggedNote.payload)}`, + ); + } const payload = incomingTaggedNote?.payload || outgoingTaggedNote?.payload;