diff --git a/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr index ef63b330603..41f9772ec52 100644 --- a/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr @@ -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; @@ -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() ) ); diff --git a/yarn-project/aztec.js/src/wallet/base_wallet.ts b/yarn-project/aztec.js/src/wallet/base_wallet.ts index 737247c2270..43b6753efce 100644 --- a/yarn-project/aztec.js/src/wallet/base_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/base_wallet.ts @@ -187,8 +187,11 @@ export abstract class BaseWallet implements Wallet { eventMetadata: EventMetadata, 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); } } diff --git a/yarn-project/circuit-types/src/interfaces/pxe.ts b/yarn-project/circuit-types/src/interfaces/pxe.ts index 1cdc9d9f6fe..b4e6e6eda2e 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.ts @@ -386,7 +386,7 @@ 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( @@ -394,7 +394,7 @@ export interface PXE { eventMetadata: EventMetadata, from: number, limit: number, - ivpk: Point, + vpks: Point[], ): Promise; } // docs:end:pxe-interface diff --git a/yarn-project/end-to-end/src/e2e_event_logs.test.ts b/yarn-project/end-to-end/src/e2e_event_logs.test.ts index 8421e87b8dc..d49bef63dd1 100644 --- a/yarn-project/end-to-end/src/e2e_event_logs.test.ts +++ b/yarn-project/end-to-end/src/e2e_event_logs.test.ts @@ -4,6 +4,7 @@ import { EventType, Fr, L1EventPayload, + type PXE, TaggedLog, } from '@aztec/aztec.js'; import { deriveMasterIncomingViewingSecretKey } from '@aztec/circuits.js'; @@ -24,30 +25,36 @@ describe('Logs', () => { let wallets: AccountWalletWithSecretKey[]; let node: AztecNode; + let pxe: PXE; let teardown: () => Promise; 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], @@ -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, ); @@ -101,14 +109,24 @@ describe('Logs', () => { const preimage = makeTuple(5, makeTuple.bind(undefined, 4, Fr.random)) as Tuple, 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, @@ -116,23 +134,59 @@ describe('Logs', () => { 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), diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index e9b76b6d07c..87c74997393 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -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'; @@ -846,7 +846,7 @@ export class PXEService implements PXE { eventMetadata: EventMetadata, from: number, limit: number, - ivpk: Point, + vpks: Point[], ): Promise; public getEvents( type: EventType.Unencrypted, @@ -859,16 +859,25 @@ export class PXEService implements PXE { eventMetadata: EventMetadata, from: number, limit: number, - ivpk: Point = Point.ZERO, + vpks: Point[] = [], ): Promise { 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(from: number, limit: number, eventMetadata: EventMetadata, ivpk: Point): Promise { + async getEncryptedEvents( + from: number, + limit: number, + eventMetadata: EventMetadata, + vpks: Point[], + ): Promise { + 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); @@ -876,11 +885,20 @@ export class PXEService implements PXE { 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[]; + 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 => {