Skip to content

Commit

Permalink
L1NotePayload refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan committed Oct 25, 2024
1 parent 0a496aa commit 1e49301
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 211 deletions.
131 changes: 111 additions & 20 deletions yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
*/
Expand All @@ -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);
Expand All @@ -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,
);
}

Expand All @@ -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);
}

Expand All @@ -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;
}
5 changes: 3 additions & 2 deletions yarn-project/end-to-end/src/e2e_block_building.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
22 changes: 4 additions & 18 deletions yarn-project/pxe/src/database/deferred_note_dao.test.ts
Original file line number Diff line number Diff line change
@@ -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<DeferredNoteDao> = {}) => {
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', () => {
Expand Down
25 changes: 6 additions & 19 deletions yarn-project/pxe/src/database/deferred_note_dao.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -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 */
Expand All @@ -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,
Expand All @@ -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(),
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/pxe/src/database/incoming_note_dao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ export class IncomingNoteDao implements NoteData {
) {}

static fromPayloadAndNoteInfo(
note: Note,
payload: L1NotePayload,
noteInfo: NoteInfo,
dataStartIndexForTx: number,
ivpkM: PublicKey,
) {
const noteHashIndexInTheWholeTree = BigInt(dataStartIndexForTx + noteInfo.noteHashIndex);
return new IncomingNoteDao(
payload.note,
note,
payload.contractAddress,
payload.storageSlot,
payload.noteTypeId,
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/pxe/src/database/kv_pxe_database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
3 changes: 2 additions & 1 deletion yarn-project/pxe/src/database/outgoing_note_dao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ export class OutgoingNoteDao {
) {}

static fromPayloadAndNoteInfo(
note: Note,
payload: L1NotePayload,
noteInfo: NoteInfo,
dataStartIndexForTx: number,
ivpkM: PublicKey,
) {
const noteHashIndexInTheWholeTree = BigInt(dataStartIndexForTx + noteInfo.noteHashIndex);
return new OutgoingNoteDao(
payload.note,
note,
payload.contractAddress,
payload.storageSlot,
payload.noteTypeId,
Expand Down
Loading

0 comments on commit 1e49301

Please sign in to comment.