Skip to content

Commit

Permalink
feat: add tagged note structure (#4843)
Browse files Browse the repository at this point in the history
Fixes #4572
  • Loading branch information
spypsy authored Mar 1, 2024
1 parent ea16333 commit 553c2c6
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 18 deletions.
4 changes: 2 additions & 2 deletions yarn-project/circuit-types/src/logs/function_l2_logs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { sha256 } from '@aztec/foundation/crypto';
import { Point } from '@aztec/foundation/fields';
import { Fr, Point } from '@aztec/foundation/fields';
import { BufferReader, prefixBufferWithLength } from '@aztec/foundation/serialize';

import { randomBytes } from 'crypto';
Expand Down Expand Up @@ -77,7 +77,7 @@ export class FunctionL2Logs {
if (logType === LogType.ENCRYPTED) {
const randomEphPubKey = Point.random();
const randomLogContent = randomBytes(144 - Point.SIZE_IN_BYTES);
logs.push(Buffer.concat([randomLogContent, randomEphPubKey.toBuffer()]));
logs.push(Buffer.concat([Fr.random().toBuffer(), randomLogContent, randomEphPubKey.toBuffer()]));
} else {
logs.push(UnencryptedL2Log.random().toBuffer());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './encrypt_buffer.js';
export * from './note.js';
export * from './l1_note_payload.js';
export * from './tagged_note.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Grumpkin } from '@aztec/circuits.js/barretenberg';
import { GrumpkinScalar, Point } from '@aztec/foundation/fields';

import { L1NotePayload } from './l1_note_payload.js';
import { TaggedNote } from './tagged_note.js';

describe('L1 Note Payload', () => {
let grumpkin: Grumpkin;

beforeAll(() => {
grumpkin = new Grumpkin();
});

it('convert to and from buffer', () => {
const payload = L1NotePayload.random();
const taggedNote = new TaggedNote(payload);
const buf = taggedNote.toBuffer();
expect(TaggedNote.fromBuffer(buf).notePayload).toEqual(taggedNote.notePayload);
});

it('convert to and from encrypted buffer', () => {
const payload = L1NotePayload.random();
const taggedNote = new TaggedNote(payload);
const ownerPrivKey = GrumpkinScalar.random();
const ownerPubKey = grumpkin.mul(Grumpkin.generator, ownerPrivKey);
const encrypted = taggedNote.toEncryptedBuffer(ownerPubKey, grumpkin);
const decrypted = TaggedNote.fromEncryptedBuffer(encrypted, ownerPrivKey, grumpkin);
expect(decrypted).not.toBeUndefined();
expect(decrypted?.notePayload).toEqual(payload);
});

it('return undefined if unable to decrypt the encrypted buffer', () => {
const payload = L1NotePayload.random();
const taggedNote = new TaggedNote(payload);
const ownerPubKey = Point.random();
const encrypted = taggedNote.toEncryptedBuffer(ownerPubKey, grumpkin);
const randomPrivKey = GrumpkinScalar.random();
const decrypted = TaggedNote.fromEncryptedBuffer(encrypted, randomPrivKey, grumpkin);
expect(decrypted).toBeUndefined();
});
});
71 changes: 71 additions & 0 deletions yarn-project/circuit-types/src/logs/l1_note_payload/tagged_note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { GrumpkinPrivateKey, PublicKey } from '@aztec/circuits.js';
import { Grumpkin } from '@aztec/circuits.js/barretenberg';
import { Fr } from '@aztec/foundation/fields';
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';

import { L1NotePayload } from './l1_note_payload.js';

// placeholder value until tagging is implemented
const PLACEHOLDER_TAG = new Fr(33);

/**
* Encrypted note payload with a tag used for retrieval by clients.
*/
export class TaggedNote {
constructor(public notePayload: L1NotePayload, public tag = PLACEHOLDER_TAG) {}

/**
* Deserializes the TaggedNote object from a Buffer.
* @param buffer - Buffer or BufferReader object to deserialize.
* @returns An instance of TaggedNote.
*/
static fromBuffer(buffer: Buffer | BufferReader): TaggedNote {
const reader = BufferReader.asReader(buffer);
const tag = Fr.fromBuffer(reader);
const payload = L1NotePayload.fromBuffer(reader);
return new TaggedNote(payload, tag);
}

/**
* Serializes the TaggedNote object into a Buffer.
* @returns Buffer representation of the TaggedNote object (unencrypted).
*/
public toBuffer(): Buffer {
return serializeToBuffer(this.tag, this.notePayload);
}

/**
* Encrypt the L1NotePayload object using the owner's public key and the ephemeral private key, then attach the tag.
* @param ownerPubKey - Public key of the owner of the TaggedNote object.
* @param curve - The curve instance to use.
* @returns The encrypted TaggedNote object.
*/
public toEncryptedBuffer(ownerPubKey: PublicKey, curve: Grumpkin): Buffer {
const encryptedL1NotePayload = this.notePayload.toEncryptedBuffer(ownerPubKey, curve);
return serializeToBuffer(this.tag, encryptedL1NotePayload);
}

/**
* Decrypts the L1NotePayload object using the owner's private key.
* @param data - Encrypted TaggedNote object.
* @param ownerPrivKey - Private key of the owner of the TaggedNote object.
* @param curve - The curve instance to use.
* @returns Instance of TaggedNote if the decryption was successful, undefined otherwise.
*/
static fromEncryptedBuffer(data: Buffer, ownerPrivKey: GrumpkinPrivateKey, curve: Grumpkin): TaggedNote | undefined {
const reader = BufferReader.asReader(data);
const tag = Fr.fromBuffer(reader);

const encryptedL1NotePayload = reader.readToEnd();

const payload = L1NotePayload.fromEncryptedBuffer(encryptedL1NotePayload, ownerPrivKey, curve);
if (!payload) {
return;
}
return new TaggedNote(payload, tag);
}

static random(): TaggedNote {
return new TaggedNote(L1NotePayload.random());
}
}
20 changes: 11 additions & 9 deletions yarn-project/pxe/src/note_processor/note_processor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
L2BlockContext,
L2BlockL2Logs,
Note,
TaggedNote,
TxL2Logs,
} from '@aztec/circuit-types';
import { Fr, MAX_NEW_NOTE_HASHES_PER_TX } from '@aztec/circuits.js';
Expand Down Expand Up @@ -47,8 +48,8 @@ describe('Note Processor', () => {
const computeMockNoteHash = (note: Note) => pedersenHash(note.items.map(i => i.toBuffer()));

// ownedData: [tx1, tx2, ...], the numbers in each tx represents the indices of the note hashes the account owns.
const createEncryptedLogsAndOwnedL1NotePayloads = (ownedData: number[][], ownedNotes: L1NotePayload[]) => {
const newNotes: L1NotePayload[] = [];
const createEncryptedLogsAndOwnedL1NotePayloads = (ownedData: number[][], ownedNotes: TaggedNote[]) => {
const newNotes: TaggedNote[] = [];
const ownedL1NotePayloads: L1NotePayload[] = [];
const txLogs: TxL2Logs[] = [];
let usedOwnedNote = 0;
Expand All @@ -62,12 +63,13 @@ describe('Note Processor', () => {
for (let noteIndex = 0; noteIndex < MAX_NEW_NOTE_HASHES_PER_TX; ++noteIndex) {
const isOwner = ownedDataIndices.includes(noteIndex);
const publicKey = isOwner ? owner.getPublicKey() : Point.random();
const note = (isOwner && ownedNotes[usedOwnedNote]) || L1NotePayload.random();
const note = (isOwner && ownedNotes[usedOwnedNote]) || TaggedNote.random();
usedOwnedNote += note === ownedNotes[usedOwnedNote] ? 1 : 0;
newNotes.push(note);
if (isOwner) {
ownedL1NotePayloads.push(note);
ownedL1NotePayloads.push(note.notePayload);
}
// const encryptedNote =
const log = note.toEncryptedBuffer(publicKey, grumpkin);
// 1 tx containing 1 function invocation containing 1 log
logs.push(new FunctionL2Logs([log]));
Expand All @@ -80,10 +82,10 @@ describe('Note Processor', () => {
};

const mockData = (
ownedData: number[][],
ownedData: number[][], // = [[2]]
prependedBlocks = 0,
appendedBlocks = 0,
ownedNotes: L1NotePayload[] = [],
ownedNotes: TaggedNote[] = [], // L1NotePayload[] = [],
) => {
if (ownedData.length > TXS_PER_BLOCK) {
throw new Error(`Tx size should be less than ${TXS_PER_BLOCK}.`);
Expand All @@ -108,7 +110,7 @@ describe('Note Processor', () => {
ownedL1NotePayloads.push(...payloads);
for (let i = 0; i < TXS_PER_BLOCK; i++) {
block.body.txEffects[i].newNoteHashes = newNotes
.map(n => computeMockNoteHash(n.note))
.map(n => computeMockNoteHash(n.notePayload.note))
.slice(i * MAX_NEW_NOTE_HASHES_PER_TX, (i + 1) * MAX_NEW_NOTE_HASHES_PER_TX) as Tuple<
Fr,
typeof MAX_NEW_NOTE_HASHES_PER_TX
Expand Down Expand Up @@ -208,8 +210,8 @@ describe('Note Processor', () => {
});

it('should be able to recover two note payloads with containing the same note', async () => {
const note = L1NotePayload.random();
const note2 = L1NotePayload.random();
const note = TaggedNote.random(); // L1NotePayload.random();
const note2 = TaggedNote.random(); // L1NotePayload.random();
// All note payloads except one have the same contract address, storage slot, and the actual note.
const notes = [note, note, note, note2, note];
const { blockContexts, encryptedLogsArr, ownedL1NotePayloads } = mockData([[0, 2], [], [0, 1, 3]], 0, 0, notes);
Expand Down
8 changes: 5 additions & 3 deletions yarn-project/pxe/src/note_processor/note_processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
L1NotePayload,
L2BlockContext,
L2BlockL2Logs,
TaggedNote,
} from '@aztec/circuit-types';
import { NoteProcessorStats } from '@aztec/circuit-types/stats';
import { MAX_NEW_NOTE_HASHES_PER_TX, PublicKey } from '@aztec/circuits.js';
Expand Down Expand Up @@ -130,10 +131,11 @@ export class NoteProcessor {
const txFunctionLogs = txLogs[indexOfTxInABlock].functionLogs;
const excludedIndices: Set<number> = new Set();
for (const functionLogs of txFunctionLogs) {
for (const logs of functionLogs.logs) {
for (const log of functionLogs.logs) {
this.stats.seen++;
const payload = L1NotePayload.fromEncryptedBuffer(logs, privateKey, curve);
if (payload) {
const taggedNote = TaggedNote.fromEncryptedBuffer(log, privateKey, curve);
if (taggedNote?.notePayload) {
const { notePayload: payload } = taggedNote;
// We have successfully decrypted the data.
const txHash = blockContext.getTxHash(indexOfTxInABlock);
try {
Expand Down
10 changes: 7 additions & 3 deletions yarn-project/pxe/src/synchronizer/synchronizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,19 @@ export class Synchronizer {

// filter out note processors that are already caught up
// and sort them by the block number they are lagging behind in ascending order
this.noteProcessorsToCatchUp = this.noteProcessorsToCatchUp.filter(noteProcessor => {
const noteProcessorsToCatchUp: NoteProcessor[] = [];

this.noteProcessorsToCatchUp.forEach(noteProcessor => {
if (noteProcessor.status.syncedToBlock >= toBlockNumber) {
// Note processor is ahead of main sync, nothing to do
this.noteProcessors.push(noteProcessor);
return false;
} else {
noteProcessorsToCatchUp.push(noteProcessor);
}
return true;
});

this.noteProcessorsToCatchUp = noteProcessorsToCatchUp;

if (!this.noteProcessorsToCatchUp.length) {
// No note processors to catch up, nothing to do here,
// but we return true to continue with the normal flow.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
L1NotePayload,
Note,
NoteStatus,
TaggedNote,
UnencryptedL2Log,
} from '@aztec/circuit-types';
import {
Expand Down Expand Up @@ -293,7 +294,8 @@ export class ClientExecutionContext extends ViewDataOracle {
public emitEncryptedLog(contractAddress: AztecAddress, storageSlot: Fr, noteTypeId: Fr, publicKey: Point, log: Fr[]) {
const note = new Note(log);
const l1NotePayload = new L1NotePayload(note, contractAddress, storageSlot, noteTypeId);
const encryptedNote = l1NotePayload.toEncryptedBuffer(publicKey, this.curve);
const taggedNote = new TaggedNote(l1NotePayload);
const encryptedNote = taggedNote.toEncryptedBuffer(publicKey, this.curve);
this.encryptedLogs.push(encryptedNote);
}

Expand Down

0 comments on commit 553c2c6

Please sign in to comment.