Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: untangling tangled encryption code #8827

Merged
merged 24 commits into from
Sep 30, 2024
Merged
Prev Previous commit
Next Next commit
WIP
benesjan committed Sep 30, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit ba0f6a9ad344d54524d71f49a245955ced2e3581
2 changes: 1 addition & 1 deletion yarn-project/aztec.js/src/index.ts
Original file line number Diff line number Diff line change
@@ -134,7 +134,7 @@ export {
merkleTreeIds,
mockTx,
mockEpochProofQuote,
TaggedLog,
EncryptedLogPayload,
L1NotePayload,
L1EventPayload,
EpochProofQuote,
3 changes: 2 additions & 1 deletion yarn-project/circuit-types/src/logs/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
export * from './encrypted_log_payload.js';
export * from './encrypted_l2_note_log.js';
export * from './encrypted_l2_log.js';
export * from './l1_event_payload.js';
export * from './get_unencrypted_logs_response.js';
export * from './function_l2_logs.js';
export * from './l2_block_l2_logs.js';
export * from './l2_logs_source.js';
export * from './note_payload.js';
export * from './l1_note_payload.js';
export * from './log_id.js';
export * from './log_type.js';
export * from './log_filter.js';
14 changes: 14 additions & 0 deletions yarn-project/circuit-types/src/logs/l1_event_payload.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AztecAddress } from '@aztec/circuits.js';

import { L1EventPayload } from './l1_event_payload.js';

describe('L1EventPayload', () => {
it('can encode L1EventPayload to plaintext and back', () => {
const app = AztecAddress.random();
const original = L1EventPayload.random(app);
const payloadPlaintext = original.toIncomingBodyPlaintext();
const recovered = L1EventPayload.fromIncomingBodyPlaintextAndContractAddress(payloadPlaintext, app);

expect(recovered).toEqual(original);
});
});
81 changes: 81 additions & 0 deletions yarn-project/circuit-types/src/logs/l1_event_payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { AztecAddress } from '@aztec/circuits.js';
import { EventSelector } from '@aztec/foundation/abi';
import { Fr } from '@aztec/foundation/fields';
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';

import { Event } from './l1_payload/payload.js';

/**
* A class which wraps event data which is pushed on L1.
*/
export class L1EventPayload {
constructor(
/**
* A event as emitted from Noir contract. Can be used along with private key to compute nullifier.
*/
public event: Event,
/**
* Address of the contract this tx is interacting with.
*/
public contractAddress: AztecAddress,
/**
* Randomness used to mask the contract address.
*/
public randomness: Fr,
/**
* Type identifier for the underlying event, required to determine how to compute its hash and nullifier.
*/
public eventTypeId: EventSelector,
) {}

/**
* Deserializes the L1EventPayload object from a Buffer.
* @param plaintext - Incoming body plaintext.
* @returns An instance of L1EventPayload.
*/
static fromIncomingBodyPlaintextAndContractAddress(
plaintext: Buffer,
contractAddress: AztecAddress,
): L1EventPayload | undefined {
try {
const reader = BufferReader.asReader(plaintext);
const fields = reader.readArray(plaintext.length / Fr.SIZE_IN_BYTES, Fr);

const storageSlot = fields[0];
const eventTypeId = EventSelector.fromField(fields[1]);

const event = new Event(fields.slice(2));

return new L1EventPayload(event, contractAddress, storageSlot, eventTypeId);
} catch (e) {
return undefined;
}
}

/**
* Serializes the L1EventPayload object into a Buffer.
* @returns Buffer representation of the L1EventPayload object.
*/
toIncomingBodyPlaintext() {
const fields = [this.randomness, this.eventTypeId.toField(), ...this.event.items];
return serializeToBuffer(fields);
}

/**
* Create a random L1EventPayload object (useful for testing purposes).
* @param contract - The address of a contract the event was emitted from.
* @returns A random L1EventPayload object.
*/
static random(contract = AztecAddress.random()) {
return new L1EventPayload(Event.random(), contract, Fr.random(), EventSelector.random());
}

public equals(other: L1EventPayload) {
return (
this.event.equals(other.event) &&
this.contractAddress.equals(other.contractAddress) &&
this.randomness.equals(other.randomness) &&
this.eventTypeId.equals(other.eventTypeId)
);
}
}
14 changes: 14 additions & 0 deletions yarn-project/circuit-types/src/logs/l1_note_payload.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AztecAddress } from '@aztec/circuits.js';

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

describe('L1NotePayload', () => {
it('can encode L1NotePayload to plaintext and back', () => {
const app = AztecAddress.random();
const original = L1NotePayload.random(app);
const payloadPlaintext = original.toIncomingBodyPlaintext();
const recovered = L1NotePayload.fromIncomingBodyPlaintextAndContractAddress(payloadPlaintext, app);

expect(recovered).toEqual(original);
});
});
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ import { Note } from './l1_payload/payload.js';
* @remarks This data is required to compute a nullifier/to spend a note. Along with that this class contains
* the necessary functionality to encrypt and decrypt the data.
*/
export class NotePayload {
export class L1NotePayload {
constructor(
/**
* A note as emitted from Noir contract. Can be used along with private key to compute nullifier.
@@ -31,14 +31,14 @@ export class NotePayload {
) {}

/**
* Deserializes the NotePayload object from a Buffer.
* Deserializes the L1NotePayload object from a Buffer.
* @param plaintext - Incoming body plaintext.
* @returns An instance of NotePayload.
* @returns An instance of L1NotePayload.
*/
static fromIncomingBodyPlaintextAndContractAddress(
plaintext: Buffer,
contractAddress: AztecAddress,
): NotePayload | undefined {
): L1NotePayload | undefined {
try {
const reader = BufferReader.asReader(plaintext);
const fields = reader.readArray(plaintext.length / Fr.SIZE_IN_BYTES, Fr);
@@ -48,31 +48,31 @@ export class NotePayload {

const note = new Note(fields.slice(2));

return new NotePayload(note, contractAddress, storageSlot, noteTypeId);
return new L1NotePayload(note, contractAddress, storageSlot, noteTypeId);
} catch (e) {
return undefined;
}
}

/**
* Serializes the NotePayload object into a Buffer.
* @returns Buffer representation of the NotePayload object.
* Serializes the L1NotePayload object into a Buffer.
* @returns Buffer representation of the L1NotePayload object.
*/
toIncomingBodyPlaintext() {
const fields = [this.storageSlot, this.noteTypeId.toField(), ...this.note.items];
return serializeToBuffer(fields);
}

/**
* Create a random NotePayload object (useful for testing purposes).
* Create a random L1NotePayload object (useful for testing purposes).
* @param contract - The address of a contract the note was emitted from.
* @returns A random NotePayload object.
* @returns A random L1NotePayload object.
*/
static random(contract = AztecAddress.random()) {
return new NotePayload(Note.random(), contract, Fr.random(), NoteSelector.random());
return new L1NotePayload(Note.random(), contract, Fr.random(), NoteSelector.random());
}

public equals(other: NotePayload) {
public equals(other: L1NotePayload) {
return (
this.note.equals(other.note) &&
this.contractAddress.equals(other.contractAddress) &&
5 changes: 1 addition & 4 deletions yarn-project/circuit-types/src/logs/l1_payload/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
export * from './encrypt_buffer.js';
export * from './payload.js';
export * from './l1_event_payload.js';
export * from './l1_note_payload.js';
export * from './tagged_log.js';
export * from './encrypted_log_incoming_body/index.js';
export * from './encrypted_log_header.js';
export * from './encrypted_log_outgoing_body.js';
export * from './payload.js';

This file was deleted.

147 changes: 0 additions & 147 deletions yarn-project/circuit-types/src/logs/l1_payload/l1_event_payload.ts

This file was deleted.

This file was deleted.

139 changes: 0 additions & 139 deletions yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.ts

This file was deleted.

164 changes: 0 additions & 164 deletions yarn-project/circuit-types/src/logs/l1_payload/l1_payload.ts

This file was deleted.

168 changes: 0 additions & 168 deletions yarn-project/circuit-types/src/logs/l1_payload/tagged_log.test.ts

This file was deleted.

158 changes: 0 additions & 158 deletions yarn-project/circuit-types/src/logs/l1_payload/tagged_log.ts

This file was deleted.

14 changes: 0 additions & 14 deletions yarn-project/circuit-types/src/logs/note_payload.test.ts

This file was deleted.

20 changes: 12 additions & 8 deletions yarn-project/end-to-end/src/e2e_block_building.test.ts
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import {
ContractDeployer,
ContractFunctionInteraction,
type DebugLogger,
EncryptedLogPayload,
Fq,
Fr,
L1NotePayload,
@@ -22,7 +23,6 @@ import { TokenContract } from '@aztec/noir-contracts.js/Token';

import 'jest-extended';

import { TaggedLog } from '../../circuit-types/src/logs/l1_payload/tagged_log.js';
import { DUPLICATE_NULLIFIER_ERROR } from './fixtures/fixtures.js';
import { setup } from './fixtures/utils.js';

@@ -293,13 +293,17 @@ describe('e2e_block_building', () => {

// compare logs
expect(rct.status).toEqual('success');
const decryptedLogs = tx.noteEncryptedLogs
.unrollLogs()
.map(l => TaggedLog.decryptAsIncoming(l.data, keys.masterIncomingViewingSecretKey, L1NotePayload));
const notevalues = decryptedLogs.map(l => l?.payload.note.items[0]);
expect(notevalues[0]).toEqual(new Fr(10));
expect(notevalues[1]).toEqual(new Fr(11));
expect(notevalues[2]).toEqual(new Fr(12));
const noteValues = tx.noteEncryptedLogs.unrollLogs().map(l => {
const payload = EncryptedLogPayload.decryptAsIncoming(l.data, keys.masterIncomingViewingSecretKey);
const notePayload = L1NotePayload.fromIncomingBodyPlaintextAndContractAddress(
payload!.incomingBodyPlaintext,
payload!.contract,
);
return notePayload?.note;
});
expect(noteValues[0]).toEqual(new Fr(10));
expect(noteValues[1]).toEqual(new Fr(11));
expect(noteValues[2]).toEqual(new Fr(12));
}, 30_000);

it('calls a method with nested encrypted logs', async () => {
45 changes: 23 additions & 22 deletions yarn-project/end-to-end/src/e2e_event_logs.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {
type AccountWalletWithSecretKey,
type AztecNode,
EncryptedLogPayload,
EventType,
Fr,
L1EventPayload,
type PXE,
TaggedLog,
} from '@aztec/aztec.js';
import { deriveMasterIncomingViewingSecretKey } from '@aztec/circuits.js';
import { EventSelector } from '@aztec/foundation/abi';
@@ -56,52 +56,53 @@ describe('Logs', () => {
const encryptedLogs = txEffect!.encryptedLogs.unrollLogs();
expect(encryptedLogs.length).toBe(3);

const decryptedLog0 = TaggedLog.decryptAsIncoming(
encryptedLogs[0],
const decryptedLog0 = EncryptedLogPayload.decryptAsIncoming(
encryptedLogs[0].toBuffer(),
deriveMasterIncomingViewingSecretKey(wallets[0].getSecretKey()),
L1EventPayload,
);
const decryptedEvent0 = L1EventPayload.fromIncomingBodyPlaintextAndContractAddress(
decryptedLog0!.incomingBodyPlaintext,
decryptedLog0!.contract,
)!;

expect(decryptedLog0?.payload.contractAddress).toStrictEqual(testLogContract.address);
expect(decryptedLog0?.payload.randomness).toStrictEqual(randomness[0]);
expect(decryptedLog0?.payload.eventTypeId).toStrictEqual(
EventSelector.fromSignature('ExampleEvent0(Field,Field)'),
);
expect(decryptedEvent0.contractAddress).toStrictEqual(testLogContract.address);
expect(decryptedEvent0.randomness).toStrictEqual(randomness[0]);
expect(decryptedEvent0.eventTypeId).toStrictEqual(EventSelector.fromSignature('ExampleEvent0(Field,Field)'));

// We decode our event into the event type
const event0 = TestLogContract.events.ExampleEvent0.decode(decryptedLog0!.payload);
const event0 = TestLogContract.events.ExampleEvent0.decode(decryptedEvent0);

// We check that the event was decoded correctly
expect(event0?.value0).toStrictEqual(preimage[0].toBigInt());
expect(event0?.value1).toStrictEqual(preimage[1].toBigInt());

// We check that an event that does not match, is not decoded correctly due to an event type id mismatch
const badEvent0 = TestLogContract.events.ExampleEvent1.decode(decryptedLog0!.payload);
const badEvent0 = TestLogContract.events.ExampleEvent1.decode(decryptedEvent0);
expect(badEvent0).toBe(undefined);

const decryptedLog1 = TaggedLog.decryptAsIncoming(
// We want to skip the second emitted log as it is irrelevant in this test.
encryptedLogs[2],
const decryptedLog1 = EncryptedLogPayload.decryptAsIncoming(
encryptedLogs[1].toBuffer(),
deriveMasterIncomingViewingSecretKey(wallets[0].getSecretKey()),
L1EventPayload,
);
const decryptedEvent1 = L1EventPayload.fromIncomingBodyPlaintextAndContractAddress(
decryptedLog1!.incomingBodyPlaintext,
decryptedLog1!.contract,
)!;

expect(decryptedLog1?.payload.contractAddress).toStrictEqual(testLogContract.address);
expect(decryptedLog1?.payload.randomness).toStrictEqual(randomness[1]);
expect(decryptedLog1?.payload.eventTypeId).toStrictEqual(
EventSelector.fromSignature('ExampleEvent1((Field),u8)'),
);
expect(decryptedEvent1.contractAddress).toStrictEqual(testLogContract.address);
expect(decryptedEvent1.randomness).toStrictEqual(randomness[1]);
expect(decryptedEvent1.eventTypeId).toStrictEqual(EventSelector.fromSignature('ExampleEvent1((Field),u8)'));

// We check our second event, which is a different type
const event1 = TestLogContract.events.ExampleEvent1.decode(decryptedLog1!.payload);
const event1 = TestLogContract.events.ExampleEvent1.decode(decryptedEvent1);

// We expect the fields to have been populated correctly
expect(event1?.value2).toStrictEqual(preimage[2]);
// We get the last byte here because value3 is of type u8
expect(event1?.value3).toStrictEqual(BigInt(preimage[3].toBuffer().subarray(31).readUint8()));

// Again, trying to decode another event with mismatching data does not yield anything
const badEvent1 = TestLogContract.events.ExampleEvent0.decode(decryptedLog1!.payload);
const badEvent1 = TestLogContract.events.ExampleEvent0.decode(decryptedEvent1);
expect(badEvent1).toBe(undefined);
});

8 changes: 4 additions & 4 deletions yarn-project/pxe/src/note_processor/note_processor.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type AztecNode, EncryptedL2NoteLog, EncryptedLogPayload, L2Block, NotePayload } from '@aztec/circuit-types';
import { type AztecNode, EncryptedL2NoteLog, EncryptedLogPayload, L1NotePayload, L2Block } from '@aztec/circuit-types';
import {
AztecAddress,
CompleteAddress,
@@ -68,8 +68,8 @@ class MockNoteRequest {
);
}

get notePayload(): NotePayload | undefined {
return NotePayload.fromIncomingBodyPlaintextAndContractAddress(
get notePayload(): L1NotePayload | undefined {
return L1NotePayload.fromIncomingBodyPlaintextAndContractAddress(
this.logPayload.incomingBodyPlaintext,
this.logPayload.contract,
);
@@ -354,6 +354,6 @@ describe('Note Processor', () => {
});

function getRandomNoteLogPayload(app = AztecAddress.random()): EncryptedLogPayload {
return new EncryptedLogPayload(Fr.random(), Fr.random(), app, NotePayload.random(app).toIncomingBodyPlaintext());
return new EncryptedLogPayload(Fr.random(), Fr.random(), app, L1NotePayload.random(app).toIncomingBodyPlaintext());
}
});
8 changes: 4 additions & 4 deletions yarn-project/pxe/src/note_processor/note_processor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type AztecNode, EncryptedLogPayload, type L2Block, NotePayload } from '@aztec/circuit-types';
import { type AztecNode, EncryptedLogPayload, L1NotePayload, type L2Block } from '@aztec/circuit-types';
import { type NoteProcessorStats } from '@aztec/circuit-types/stats';
import { type AztecAddress, INITIAL_L2_BLOCK_NUM, MAX_NOTE_HASHES_PER_TX, type PublicKey } from '@aztec/circuits.js';
import { type Fr } from '@aztec/foundation/fields';
@@ -146,13 +146,13 @@ export class NoteProcessor {
const outgoingDecryptedLog = EncryptedLogPayload.decryptAsOutgoing(log.data, ovskM);

const incomingNotePayload = incomingDecryptedLog
? NotePayload.fromIncomingBodyPlaintextAndContractAddress(
? L1NotePayload.fromIncomingBodyPlaintextAndContractAddress(
incomingDecryptedLog.incomingBodyPlaintext,
incomingDecryptedLog.contract,
)
: undefined;
const outgoingNotePayload = outgoingDecryptedLog
? NotePayload.fromIncomingBodyPlaintextAndContractAddress(
? L1NotePayload.fromIncomingBodyPlaintextAndContractAddress(
outgoingDecryptedLog.incomingBodyPlaintext,
outgoingDecryptedLog.contract,
)
@@ -311,7 +311,7 @@ export class NoteProcessor {
for (const deferredNote of deferredNoteDaos) {
const { publicKey, note, contractAddress, storageSlot, noteTypeId, txHash, noteHashes, dataStartIndexForTx } =
deferredNote;
const payload = new NotePayload(note, contractAddress, storageSlot, noteTypeId);
const payload = new L1NotePayload(note, contractAddress, storageSlot, noteTypeId);

const isIncoming = publicKey.equals(this.ivpkM);
const isOutgoing = publicKey.equals(this.ovpkM);
6 changes: 3 additions & 3 deletions yarn-project/pxe/src/note_processor/produce_note_dao.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type NotePayload, type TxHash } from '@aztec/circuit-types';
import { type L1NotePayload, type TxHash } from '@aztec/circuit-types';
import { Fr, type PublicKey } from '@aztec/circuits.js';
import { computeNoteHashNonce, siloNullifier } from '@aztec/circuits.js/hash';
import { type Logger } from '@aztec/foundation/log';
@@ -28,7 +28,7 @@ export async function produceNoteDaos(
simulator: AcirSimulator,
ivpkM: PublicKey | undefined,
ovpkM: PublicKey | undefined,
payload: NotePayload,
payload: L1NotePayload,
txHash: TxHash,
noteHashes: Fr[],
dataStartIndexForTx: number,
@@ -183,7 +183,7 @@ async function findNoteIndexAndNullifier(
simulator: AcirSimulator,
siloedNoteHashes: Fr[],
txHash: TxHash,
{ contractAddress, storageSlot, noteTypeId, note }: NotePayload,
{ contractAddress, storageSlot, noteTypeId, note }: L1NotePayload,
excludedIndices: Set<number>,
computeNullifier: boolean,
) {
22 changes: 13 additions & 9 deletions yarn-project/pxe/src/pxe_service/pxe_service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
type AuthWitness,
type AztecNode,
EncryptedLogPayload,
EncryptedNoteTxL2Logs,
EncryptedTxL2Logs,
type EventMetadata,
@@ -20,7 +21,6 @@ import {
type SiblingPath,
SimulatedTx,
SimulationError,
TaggedLog,
Tx,
type TxEffect,
type TxExecutionRequest,
@@ -945,10 +945,14 @@ export class PXEService implements PXE {
const visibleEvents = encryptedLogs.flatMap(encryptedLog => {
for (const sk of vsks) {
const decryptedLog =
TaggedLog.decryptAsIncoming(encryptedLog, sk, L1EventPayload) ??
TaggedLog.decryptAsOutgoing(encryptedLog, sk, L1EventPayload);
if (decryptedLog !== undefined) {
return [decryptedLog];
EncryptedLogPayload.decryptAsIncoming(encryptedLog.toBuffer(), sk) ??
EncryptedLogPayload.decryptAsOutgoing(encryptedLog.toBuffer(), sk);
const decryptedEvent = L1EventPayload.fromIncomingBodyPlaintextAndContractAddress(
decryptedLog!.incomingBodyPlaintext,
decryptedLog!.contract,
);
if (decryptedEvent !== undefined) {
return [decryptedEvent];
}
}

@@ -957,19 +961,19 @@ export class PXEService implements PXE {

const decodedEvents = visibleEvents
.map(visibleEvent => {
if (visibleEvent.payload === undefined) {
if (visibleEvent === undefined) {
return undefined;
}
if (!visibleEvent.payload.eventTypeId.equals(eventMetadata.eventSelector)) {
if (!visibleEvent.eventTypeId.equals(eventMetadata.eventSelector)) {
return undefined;
}
if (visibleEvent.payload.event.items.length !== eventMetadata.fieldNames.length) {
if (visibleEvent.event.items.length !== eventMetadata.fieldNames.length) {
throw new Error(
'Something is weird here, we have matching EventSelectors, but the actual payload has mismatched length',
);
}

return eventMetadata.decode(visibleEvent.payload);
return eventMetadata.decode(visibleEvent);
})
.filter(visibleEvent => visibleEvent !== undefined) as T[];