Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
contract TestLog {
use dep::aztec::prelude::PrivateSet;
use dep::aztec::protocol_types::{traits::Serialize, grumpkin_point::GrumpkinPoint, grumpkin_private_key::GrumpkinPrivateKey};
use dep::aztec::protocol_types::{
traits::Serialize, grumpkin_point::GrumpkinPoint, grumpkin_private_key::GrumpkinPrivateKey,
address::AztecAddress
};
use dep::value_note::value_note::ValueNote;
use dep::aztec::encrypted_logs::incoming_body::EncryptedLogIncomingBody;
use dep::aztec::event::event_interface::EventInterface;
Expand Down Expand Up @@ -42,25 +45,38 @@ contract TestLog {
}

#[aztec(private)]
fn emit_encrypted_events(randomness: [Field; 2], preimages: [Field; 4]) {
fn emit_encrypted_events(other: AztecAddress, randomness: [Field; 2], preimages: [Field; 4]) {
let event0 = ExampleEvent0 { value0: preimages[0], value1: preimages[1] };

event0.emit(
encode_and_encrypt_event(
&mut context,
randomness[0],
context.msg_sender(),
// outgoing is set to other, incoming is set to msg sender
other,
context.msg_sender()
)
);

// We duplicate the emission, but specifying different incoming and outgoing parties
event0.emit(
encode_and_encrypt_event(
&mut context,
randomness[0],
// outgoing is set to msg sender, incoming is set to other
context.msg_sender(),
other
)
);

let event1 = ExampleEvent1 { value2: preimages[2], value3: preimages[3] };

event1.emit(
encode_and_encrypt_event(
&mut context,
randomness[1],
context.msg_sender(),
// outgoing is set to other, incoming is set to msg sender
other,
context.msg_sender()
)
);
Expand Down
7 changes: 5 additions & 2 deletions yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,11 @@ export abstract class BaseWallet implements Wallet {
eventMetadata: EventMetadata<T>,
from: number,
limit: number,
ivpk: Point = this.getCompleteAddress().publicKeys.masterIncomingViewingPublicKey,
vpks: Point[] = [
this.getCompleteAddress().publicKeys.masterIncomingViewingPublicKey,
this.getCompleteAddress().publicKeys.masterOutgoingViewingPublicKey,
],
) {
return this.pxe.getEvents(type, eventMetadata, from, limit, ivpk);
return this.pxe.getEvents(type, eventMetadata, from, limit, vpks);
}
}
4 changes: 2 additions & 2 deletions yarn-project/circuit-types/src/interfaces/pxe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,15 +386,15 @@ export interface PXE {
* @param eventMetadata - Identifier of the event. This should be the class generated from the contract. e.g. Contract.events.Event
* @param from - The block number to search from.
* @param limit - The amount of blocks to search.
* @param ivpk - (Used for encrypted logs only) The incoming viewing public key that corresponds to the incoming viewing secret key that can decrypt the log.
* @param vpks - (Used for encrypted logs only) The viewing (incoming and outgoing) public keys that correspond to the viewing secret keys that can decrypt the log.
* @returns - The deserialized events.
*/
getEvents<T>(
type: EventType,
eventMetadata: EventMetadata<T>,
from: number,
limit: number,
ivpk: Point,
vpks: Point[],
): Promise<T[]>;
}
// docs:end:pxe-interface
Expand Down
78 changes: 66 additions & 12 deletions yarn-project/end-to-end/src/e2e_event_logs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
EventType,
Fr,
L1EventPayload,
type PXE,
TaggedLog,
} from '@aztec/aztec.js';
import { deriveMasterIncomingViewingSecretKey } from '@aztec/circuits.js';
Expand All @@ -24,30 +25,36 @@ describe('Logs', () => {

let wallets: AccountWalletWithSecretKey[];
let node: AztecNode;
let pxe: PXE;

let teardown: () => Promise<void>;

beforeAll(async () => {
({ teardown, wallets, aztecNode: node } = await setup(2));
({ teardown, wallets, aztecNode: node, pxe } = await setup(2));

await publicDeployAccounts(wallets[0], wallets.slice(0, 2));

testLogContract = await TestLogContract.deploy(wallets[0]).send().deployed();

await pxe.registerRecipient(wallets[1].getCompleteAddress());
});

afterAll(() => teardown());

describe('functionality around emitting an encrypted log', () => {
it('emits multiple events as encrypted logs and decodes a single one manually', async () => {
it('emits multiple events as encrypted logs and decodes them one manually', async () => {
const randomness = makeTuple(2, Fr.random);
const preimage = makeTuple(4, Fr.random);

const tx = await testLogContract.methods.emit_encrypted_events(randomness, preimage).send().wait();
const tx = await testLogContract.methods
.emit_encrypted_events(wallets[1].getAddress(), randomness, preimage)
.send()
.wait();

const txEffect = await node.getTxEffect(tx.txHash);

const encryptedLogs = txEffect!.encryptedLogs.unrollLogs();
expect(encryptedLogs.length).toBe(2);
expect(encryptedLogs.length).toBe(3);

const decryptedLog0 = TaggedLog.decryptAsIncoming(
encryptedLogs[0],
Expand All @@ -73,7 +80,8 @@ describe('Logs', () => {
expect(badEvent0).toBe(undefined);

const decryptedLog1 = TaggedLog.decryptAsIncoming(
encryptedLogs[1],
// We want to skip the second emitted log as it is irrelevant in this test.
encryptedLogs[2],
deriveMasterIncomingViewingSecretKey(wallets[0].getSecretKey()),
L1EventPayload,
);
Expand Down Expand Up @@ -101,38 +109,84 @@ describe('Logs', () => {
const preimage = makeTuple(5, makeTuple.bind(undefined, 4, Fr.random)) as Tuple<Tuple<Fr, 4>, 5>;

let i = 0;
const firstTx = await testLogContract.methods.emit_encrypted_events(randomness[i], preimage[i]).send().wait();
const firstTx = await testLogContract.methods
.emit_encrypted_events(wallets[1].getAddress(), randomness[i], preimage[i])
.send()
.wait();
await Promise.all(
[...new Array(3)].map(() =>
testLogContract.methods.emit_encrypted_events(randomness[++i], preimage[i]).send().wait(),
testLogContract.methods
.emit_encrypted_events(wallets[1].getAddress(), randomness[++i], preimage[i])
.send()
.wait(),
),
);
const lastTx = await testLogContract.methods.emit_encrypted_events(randomness[++i], preimage[i]).send().wait();
const lastTx = await testLogContract.methods
.emit_encrypted_events(wallets[1].getAddress(), randomness[++i], preimage[i])
.send()
.wait();

// We get all the events we can decrypt with either our incoming or outgoing viewing keys
const collectedEvent0s = await wallets[0].getEvents(
EventType.Encrypted,
TestLogContract.events.ExampleEvent0,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
);

const collectedEvent0sWithIncoming = await wallets[0].getEvents(
EventType.Encrypted,
TestLogContract.events.ExampleEvent0,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
// This function can be called specifying the viewing public keys associated with the encrypted event.
[wallets[0].getCompleteAddress().publicKeys.masterIncomingViewingPublicKey],
);

const collectedEvent0sWithOutgoing = await wallets[0].getEvents(
EventType.Encrypted,
TestLogContract.events.ExampleEvent0,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
[wallets[0].getCompleteAddress().publicKeys.masterOutgoingViewingPublicKey],
);

const collectedEvent1s = await wallets[0].getEvents(
EventType.Encrypted,
TestLogContract.events.ExampleEvent1,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
// This function can also be called specifying the incoming viewing public key associated with the encrypted event.
wallets[0].getCompleteAddress().publicKeys.masterIncomingViewingPublicKey,
[wallets[0].getCompleteAddress().publicKeys.masterIncomingViewingPublicKey],
);

expect(collectedEvent0s.length).toBe(5);
expect(collectedEvent0sWithIncoming.length).toBe(5);
expect(collectedEvent0sWithOutgoing.length).toBe(5);
expect(collectedEvent0s.length).toBe(10);
expect(collectedEvent1s.length).toBe(5);

const emptyEvent1s = await wallets[0].getEvents(
EventType.Encrypted,
TestLogContract.events.ExampleEvent1,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
[wallets[0].getCompleteAddress().publicKeys.masterOutgoingViewingPublicKey],
);

expect(emptyEvent1s.length).toBe(0);

const exampleEvent0Sort = (a: ExampleEvent0, b: ExampleEvent0) => (a.value0 > b.value0 ? 1 : -1);
expect(collectedEvent0s.sort(exampleEvent0Sort)).toStrictEqual(
expect(collectedEvent0sWithIncoming.sort(exampleEvent0Sort)).toStrictEqual(
preimage.map(preimage => ({ value0: preimage[0], value1: preimage[1] })).sort(exampleEvent0Sort),
);

expect(collectedEvent0sWithOutgoing.sort(exampleEvent0Sort)).toStrictEqual(
preimage.map(preimage => ({ value0: preimage[0], value1: preimage[1] })).sort(exampleEvent0Sort),
);

expect([...collectedEvent0sWithIncoming, ...collectedEvent0sWithOutgoing].sort(exampleEvent0Sort)).toStrictEqual(
collectedEvent0s.sort(exampleEvent0Sort),
);

const exampleEvent1Sort = (a: ExampleEvent1, b: ExampleEvent1) => (a.value2 > b.value2 ? 1 : -1);
expect(collectedEvent1s.sort(exampleEvent1Sort)).toStrictEqual(
preimage.map(preimage => ({ value2: preimage[2], value3: preimage[3] })).sort(exampleEvent1Sort),
Expand Down
36 changes: 27 additions & 9 deletions yarn-project/pxe/src/pxe_service/pxe_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {
FunctionSelector,
encodeArguments,
} from '@aztec/foundation/abi';
import { type Fq, Fr, Point } from '@aztec/foundation/fields';
import { type Fq, Fr, type Point } from '@aztec/foundation/fields';
import { SerialQueue } from '@aztec/foundation/fifo';
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
import { type KeyStore } from '@aztec/key-store';
Expand Down Expand Up @@ -846,7 +846,7 @@ export class PXEService implements PXE {
eventMetadata: EventMetadata<T>,
from: number,
limit: number,
ivpk: Point,
vpks: Point[],
): Promise<T[]>;
public getEvents<T>(
type: EventType.Unencrypted,
Expand All @@ -859,28 +859,46 @@ export class PXEService implements PXE {
eventMetadata: EventMetadata<T>,
from: number,
limit: number,
ivpk: Point = Point.ZERO,
vpks: Point[] = [],
): Promise<T[]> {
if (type.includes(EventType.Encrypted)) {
return this.getEncryptedEvents(from, limit, eventMetadata, ivpk);
return this.getEncryptedEvents(from, limit, eventMetadata, vpks);
}

return this.getUnencryptedEvents(from, limit, eventMetadata);
}

async getEncryptedEvents<T>(from: number, limit: number, eventMetadata: EventMetadata<T>, ivpk: Point): Promise<T[]> {
async getEncryptedEvents<T>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think it would be useful to throw here if vpks is empty since we would not be able to decrypt anything, so it is essentially a no-op, where we might as well give a neat error instead of having him wait to fetch the blocks from node and then don't find anything 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, have addressed 🙏

from: number,
limit: number,
eventMetadata: EventMetadata<T>,
vpks: Point[],
): Promise<T[]> {
if (vpks.length === 0) {
throw new Error('Tried to get encrypted events without supplying any viewing public keys');
}

const blocks = await this.node.getBlocks(from, limit);

const txEffects = blocks.flatMap(block => block.body.txEffects);
const encryptedTxLogs = txEffects.flatMap(txEffect => txEffect.encryptedLogs);

const encryptedLogs = encryptedTxLogs.flatMap(encryptedTxLog => encryptedTxLog.unrollLogs());

const ivsk = await this.keyStore.getMasterSecretKey(ivpk);
const vsks = await Promise.all(vpks.map(vpk => this.keyStore.getMasterSecretKey(vpk)));

const visibleEvents = encryptedLogs
.map(encryptedLog => TaggedLog.decryptAsIncoming(encryptedLog, ivsk, L1EventPayload))
.filter(item => item !== undefined) as TaggedLog<L1EventPayload>[];
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];
}
}

return [];
});

const decodedEvents = visibleEvents
.map(visibleEvent => {
Expand Down