diff --git a/noir-projects/noir-contracts/contracts/test/test_log_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/test_log_contract/src/main.nr index c0d60b896751..537495c533cc 100644 --- a/noir-projects/noir-contracts/contracts/test/test_log_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/test_log_contract/src/main.nr @@ -6,6 +6,7 @@ pub contract TestLog { event::event_emission::{emit_event_in_private, emit_event_in_public}, macros::{events::event, functions::{private, public}, storage::storage}, messages::message_delivery::MessageDelivery, + oracle::random::random, protocol_types::{address::AztecAddress, traits::FromField}, state_vars::PrivateSet, }; @@ -75,4 +76,36 @@ pub contract TestLog { &mut context, ); } + + #[private] + fn emit_encrypted_events_nested(other: AztecAddress, num_nested_calls: u32) { + // Safety: We use the following just as an arbitrary test value + let random_value_0 = unsafe { random() }; + // Safety: We use the following just as an arbitrary test value + let random_value_1 = unsafe { random() }; + // Safety: We use the following just as an arbitrary test value + let random_value_2 = unsafe { random() }; + // Safety: We use the following just as an arbitrary test value + let random_value_3 = unsafe { random() }; + + emit_event_in_private( + ExampleEvent0 { value0: random_value_0, value1: random_value_1 }, + &mut context, + other, + MessageDelivery.UNCONSTRAINED_ONCHAIN, + ); + + emit_event_in_private( + ExampleEvent0 { value0: random_value_2, value1: random_value_3 }, + &mut context, + other, + MessageDelivery.UNCONSTRAINED_ONCHAIN, + ); + + if num_nested_calls > 0 { + TestLog::at(context.this_address()) + .emit_encrypted_events_nested(other, num_nested_calls - 1) + .call(&mut context); + } + } } 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 d4355092db41..6fca8a0bb360 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 @@ -160,5 +160,63 @@ describe('Logs', () => { .sort(exampleEvent1Sort), ); }); + + // This test verifies that tags remain unique: + // 1. Across nested calls within the same contract, confirming proper propagation of the ExecutionTaggingIndexCache + // between calls, + // 2. across separate transactions that interact with the same contract function, confirming proper persistence + // of the cache contents in the database (TaggingDataProvider) after transaction proving completes. + it('produces unique tags for encrypted logs across nested calls and different transactions', async () => { + let tx1Tags: string[]; + // With 4 nestings we have 5 total calls, each emitting 2 logs => 10 logs + const tx1NumLogs = 10; + { + // Call the private function that emits two encrypted logs per call and recursively nests 4 times + const tx = await testLogContract.methods + .emit_encrypted_events_nested(account2Address, 4) + .send({ from: account1Address }) + .wait(); + + const blockNumber = tx.blockNumber!; + + // Fetch raw private logs for that block and check tag uniqueness + const privateLogs = await aztecNode.getPrivateLogs(blockNumber, 1); + const logs = privateLogs.filter(l => !l.isEmpty()); + + expect(logs.length).toBe(tx1NumLogs); + + const tags = logs.map(l => l.fields[0].toString()); + expect(new Set(tags).size).toBe(tx1NumLogs); + tx1Tags = tags; + } + + let tx2Tags: string[]; + // With 2 nestings we have 3 total calls, each emitting 2 logs => 6 logs + const tx2NumLogs = 6; + { + // Call the private function that emits two encrypted logs per call and recursively nests 2 times + const tx = await testLogContract.methods + .emit_encrypted_events_nested(account2Address, 2) + .send({ from: account1Address }) + .wait(); + + const blockNumber = tx.blockNumber!; + + // Fetch raw private logs for that block and check tag uniqueness + const privateLogs = await aztecNode.getPrivateLogs(blockNumber, 1); + const logs = privateLogs.filter(l => !l.isEmpty()); + + expect(logs.length).toBe(tx2NumLogs); + + const tags = logs.map(l => l.fields[0].toString()); + expect(new Set(tags).size).toBe(tx2NumLogs); + tx2Tags = tags; + } + + // Now we create a set from both tx1Tags and tx2Tags and expect it to be the same size as the sum of the number + // of logs in both transactions + const allTags = new Set([...tx1Tags, ...tx2Tags]); + expect(allTags.size).toBe(tx1NumLogs + tx2NumLogs); + }); }); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index 95c4d56c9215..6b7a0e66fd60 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -65,6 +65,7 @@ import { import type { ContractDataProvider } from '../storage/index.js'; import type { ExecutionDataProvider } from './execution_data_provider.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; +import { ExecutionTaggingIndexCache } from './execution_tagging_index_cache.js'; import { HashedValuesCache } from './hashed_values_cache.js'; import { Oracle } from './oracle/oracle.js'; import { executePrivateFunction, verifyCurrentClassId } from './oracle/private_execution.js'; @@ -133,6 +134,7 @@ export class ContractFunctionSimulator { const txRequestHash = await request.toTxRequest().hash(); const noteCache = new ExecutionNoteCache(txRequestHash); + const taggingIndexCache = new ExecutionTaggingIndexCache(); const privateExecutionOracle = new PrivateExecutionOracle( request.firstCallArgsHash, @@ -143,6 +145,7 @@ export class ContractFunctionSimulator { request.capsules, HashedValuesCache.create(request.argsOfCalls), noteCache, + taggingIndexCache, this.executionDataProvider, 0, // totalPublicArgsCount startSideEffectCounter, diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 6fba05199e10..25ebefbef080 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -10,7 +10,6 @@ import type { NoteStatus } from '@aztec/stdlib/note'; import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, NodeStats } from '@aztec/stdlib/tx'; -import type { Tag } from '../tagging/tag.js'; import type { NoteData } from './oracle/interfaces.js'; import type { MessageLoadOracleInputs } from './oracle/message_load_oracle_inputs.js'; @@ -240,12 +239,11 @@ export interface ExecutionDataProvider { syncTaggedLogsAsSender(secret: DirectionalAppTaggingSecret, contractAddress: AztecAddress): Promise; /** - * Returns the next app tag for a given directional app tagging secret. - * @param secret - The secret that's unique for (sender, recipient, contract) tuple while - * direction of sender -> recipient matters. - * @returns The computed tag. + * Returns the next index to be used to compute a tag when sending a log. + * @param secret - The directional app tagging secret. + * @returns The next index to be used to compute a tag for the given directional app tagging secret. */ - getNextAppTagAsSender(secret: DirectionalAppTaggingSecret): Promise; + getNextIndexAsSender(secret: DirectionalAppTaggingSecret): Promise; /** * Synchronizes the private logs tagged with scoped addresses and all the senders in the address book. Stores the found diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_tagging_index_cache.ts b/yarn-project/pxe/src/contract_function_simulator/execution_tagging_index_cache.ts new file mode 100644 index 000000000000..8523b125ee35 --- /dev/null +++ b/yarn-project/pxe/src/contract_function_simulator/execution_tagging_index_cache.ts @@ -0,0 +1,29 @@ +import { DirectionalAppTaggingSecret, type IndexedTaggingSecret } from '@aztec/stdlib/logs'; + +/** + * A map that stores the tagging index for a given directional app tagging secret. + * Note: The directional app tagging secret is unique for a (sender, recipient, contract) tuple while the direction + * of sender -> recipient matters. + */ +export class ExecutionTaggingIndexCache { + private taggingIndexMap: Map = new Map(); + + public getTaggingIndex(secret: DirectionalAppTaggingSecret): number | undefined { + return this.taggingIndexMap.get(secret.toString()); + } + + public setTaggingIndex(secret: DirectionalAppTaggingSecret, index: number) { + const currentValue = this.taggingIndexMap.get(secret.toString()); + if (currentValue !== undefined && currentValue !== index - 1) { + throw new Error(`Invalid tagging index update. Current value: ${currentValue}, new value: ${index}`); + } + this.taggingIndexMap.set(secret.toString(), index); + } + + public getIndexedTaggingSecrets(): IndexedTaggingSecret[] { + return Array.from(this.taggingIndexMap.entries()).map(([secret, index]) => ({ + secret: DirectionalAppTaggingSecret.fromString(secret), + index, + })); + } +} diff --git a/yarn-project/pxe/src/contract_function_simulator/index.ts b/yarn-project/pxe/src/contract_function_simulator/index.ts index 4ff25fb4c272..25bd54b59cf7 100644 --- a/yarn-project/pxe/src/contract_function_simulator/index.ts +++ b/yarn-project/pxe/src/contract_function_simulator/index.ts @@ -1,4 +1,5 @@ export { ExecutionNoteCache } from './execution_note_cache.js'; +export { ExecutionTaggingIndexCache } from './execution_tagging_index_cache.js'; export { HashedValuesCache } from './hashed_values_cache.js'; export { pickNotes } from './pick_notes.js'; export type { NoteData, IMiscOracle, IUtilityExecutionOracle, IPrivateExecutionOracle } from './oracle/interfaces.js'; diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index 287c132e07b7..820adacb3b47 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -65,7 +65,6 @@ import { jest } from '@jest/globals'; import { Matcher, type MatcherCreator, type MockProxy, mock } from 'jest-mock-extended'; import { toFunctionSelector } from 'viem'; -import { Tag } from '../../tagging/tag.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; @@ -303,8 +302,8 @@ describe('Private Execution test suite', () => { throw new Error(`Unknown address: ${address}. Recipient: ${recipient}, Owner: ${owner}`); }); - executionDataProvider.getNextAppTagAsSender.mockImplementation((_secret: DirectionalAppTaggingSecret) => { - return Promise.resolve(Tag.compute({ secret: _secret, index: 0 })); + executionDataProvider.getNextIndexAsSender.mockImplementation((_secret: DirectionalAppTaggingSecret) => { + return Promise.resolve(0); }); executionDataProvider.getFunctionArtifact.mockImplementation(async (address, selector) => { const contract = contracts[address.toString()]; @@ -331,8 +330,12 @@ describe('Private Execution test suite', () => { }); executionDataProvider.syncTaggedLogs.mockImplementation((_, __) => Promise.resolve()); - executionDataProvider.calculateDirectionalAppTaggingSecret.mockResolvedValue( - DirectionalAppTaggingSecret.fromString('0x1'), + // Provide tagging-related mocks expected by private log emission + executionDataProvider.calculateDirectionalAppTaggingSecret.mockImplementation((_contract, _sender, _recipient) => { + return Promise.resolve(DirectionalAppTaggingSecret.fromString('0x1')); + }); + executionDataProvider.syncTaggedLogsAsSender.mockImplementation((_directionalAppTaggingSecret, _contractAddress) => + Promise.resolve(), ); executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts index fb33bf37637b..f065ed5237f1 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts @@ -88,6 +88,7 @@ export async function executePrivateFunction( const newNotes = privateExecutionOracle.getNewNotes(); const noteHashNullifierCounterMap = privateExecutionOracle.getNoteHashNullifierCounterMap(); const offchainEffects = privateExecutionOracle.getOffchainEffects(); + const indexedTaggingSecrets = privateExecutionOracle.getIndexedTaggingSecrets(); const nestedExecutionResults = privateExecutionOracle.getNestedExecutionResults(); let timerSubtractionList = nestedExecutionResults; @@ -111,6 +112,7 @@ export async function executePrivateFunction( noteHashNullifierCounterMap, rawReturnValues, offchainEffects, + indexedTaggingSecrets, nestedExecutionResults, contractClassLogs, { diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 0a90a283aaba..fc659be9276a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -14,7 +14,7 @@ import type { AuthWitness } from '@aztec/stdlib/auth-witness'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import { PrivateContextInputs } from '@aztec/stdlib/kernel'; -import type { ContractClassLog } from '@aztec/stdlib/logs'; +import type { ContractClassLog, IndexedTaggingSecret } from '@aztec/stdlib/logs'; import { Note, type NoteStatus } from '@aztec/stdlib/note'; import { type BlockHeader, @@ -26,9 +26,10 @@ import { type TxContext, } from '@aztec/stdlib/tx'; -import type { Tag } from '../../tagging/tag.js'; +import { Tag } from '../../tagging/tag.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; import type { ExecutionNoteCache } from '../execution_note_cache.js'; +import { ExecutionTaggingIndexCache } from '../execution_tagging_index_cache.js'; import type { HashedValuesCache } from '../hashed_values_cache.js'; import { pickNotes } from '../pick_notes.js'; import type { IPrivateExecutionOracle, NoteData } from './interfaces.js'; @@ -76,6 +77,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP capsules: Capsule[], private readonly executionCache: HashedValuesCache, private readonly noteCache: ExecutionNoteCache, + private readonly taggingIndexCache: ExecutionTaggingIndexCache, executionDataProvider: ExecutionDataProvider, private totalPublicCalldataCount: number = 0, protected sideEffectCounter: number = 0, @@ -149,6 +151,13 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP return this.offchainEffects; } + /** + * Return the tagging indexes incremented by this execution along with the directional app tagging secrets. + */ + public getIndexedTaggingSecrets(): IndexedTaggingSecret[] { + return this.taggingIndexCache.getIndexedTaggingSecrets(); + } + /** * Return the nested execution results during this execution. */ @@ -193,21 +202,40 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP * @returns An app tag to be used in a log. */ public async privateGetNextAppTagAsSender(sender: AztecAddress, recipient: AztecAddress): Promise { - const directionalAppTaggingSecret = await this.executionDataProvider.calculateDirectionalAppTaggingSecret( + const secret = await this.executionDataProvider.calculateDirectionalAppTaggingSecret( this.contractAddress, sender, recipient, ); - // TODO(benesjan): In a follow-up PR we will load here the index from the ExecutionTaggingIndexCache if present - // and if not we will obtain it from the execution data provider. + // If we have the tagging index in the cache, we use it. If not we obtain it from the execution data provider. + // TODO(benesjan): Make this be `getLastUsedIndex` and refactor this function to look as proposed in this comment: + // https://github.com/AztecProtocol/aztec-packages/pull/17445#discussion_r2400365845 + const maybeTaggingIndex = this.taggingIndexCache.getTaggingIndex(secret); + let taggingIndex: number; + + if (maybeTaggingIndex !== undefined) { + taggingIndex = maybeTaggingIndex; + } else { + // This is a tagging secret we've not yet used in this tx, so first sync our store to make sure its indices + // are up to date. We do this here because this store is not synced as part of the global sync because + // that'd be wasteful as most tagging secrets are not used in each tx. + this.log.debug(`Syncing tagged logs as sender ${sender} for contract ${this.contractAddress}`, { + directionalAppTaggingSecret: secret, + recipient, + }); + await this.executionDataProvider.syncTaggedLogsAsSender(secret, this.contractAddress); + taggingIndex = await this.executionDataProvider.getNextIndexAsSender(secret); + } - this.log.debug(`Syncing tagged logs as sender ${sender} for contract ${this.contractAddress}`, { - directionalAppTaggingSecret, - recipient, - }); - await this.executionDataProvider.syncTaggedLogsAsSender(directionalAppTaggingSecret, this.contractAddress); - return this.executionDataProvider.getNextAppTagAsSender(directionalAppTaggingSecret); + // Now we increment the index by 1 and store it in the cache. + const nextTaggingIndex = taggingIndex + 1; + this.log.debug( + `Incrementing tagging index for sender: ${sender}, recipient: ${recipient}, contract: ${this.contractAddress} to ${nextTaggingIndex}`, + ); + this.taggingIndexCache.setTaggingIndex(secret, nextTaggingIndex); + + return Tag.compute({ secret, index: taggingIndex }); } /** @@ -480,6 +508,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.capsules, this.executionCache, this.noteCache, + this.taggingIndexCache, this.executionDataProvider, this.totalPublicCalldataCount, sideEffectCounter, diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index 214951aaeeb8..789a3de28b76 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -269,32 +269,8 @@ export class PXEOracleInterface implements ExecutionDataProvider { return this.taggingDataProvider.getSenderAddresses(); } - /** - * Returns the next app tag for a given sender and recipient pair. - * @param contractAddress - The contract address emitting the log. - * @param sender - The address sending the note - * @param recipient - The address receiving the note - * @returns The computed tag. - * TODO(benesjan): In a follow-up PR this will only return the index and that's it. - */ - public async getNextAppTagAsSender(secret: DirectionalAppTaggingSecret): Promise { - const index = await this.taggingDataProvider.getNextIndexAsSender(secret); - - // TODO(benesjan): This will be reworked in a follow-up PR where we will store the new indexes in the db once - // the execution finishes (then we dump the contents of the ExecutionTaggingIndexCache into the db) - // Increment the index for next time - // const contractName = await this.contractDataProvider.getDebugContractName(contractAddress); - // this.log.debug(`Incrementing app tagging secret at ${contractName}(${contractAddress})`, { - // directionalAppTaggingSecret, - // sender, - // recipient, - // contractName, - // contractAddress, - // }); - await this.taggingDataProvider.setNextIndexesAsSender([{ secret, index: index + 1 }]); - - // Compute and return the tag using the current index - return Tag.compute({ secret, index }); + public getNextIndexAsSender(secret: DirectionalAppTaggingSecret): Promise { + return this.taggingDataProvider.getNextIndexAsSender(secret); } public async calculateDirectionalAppTaggingSecret( diff --git a/yarn-project/pxe/src/private_kernel/private_kernel_execution_prover.test.ts b/yarn-project/pxe/src/private_kernel/private_kernel_execution_prover.test.ts index 8781871ce182..1455717802ea 100644 --- a/yarn-project/pxe/src/private_kernel/private_kernel_execution_prover.test.ts +++ b/yarn-project/pxe/src/private_kernel/private_kernel_execution_prover.test.ts @@ -74,6 +74,7 @@ describe('Private Kernel Sequencer', () => { new Map(), [], [], + [], (dependencies[fnName] || []).map(name => createCallExecutionResult(name)), [], ); diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index d55c312aec09..39769e41dc6e 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -736,10 +736,23 @@ export class PXE { }; this.log.debug(`Proving completed in ${totalTime}ms`, { timings }); - return new TxProvingResult(privateExecutionResult, publicInputs, clientIvcProof!, { + + const txProvingResult = new TxProvingResult(privateExecutionResult, publicInputs, clientIvcProof!, { timings, nodeRPCCalls: contractFunctionSimulator?.getStats().nodeRPCCalls, }); + + const indexedTaggingSecretsIncrementedInTheTx = privateExecutionResult.entrypoint.indexedTaggingSecrets; + if (indexedTaggingSecretsIncrementedInTheTx.length > 0) { + await this.taggingDataProvider.setNextIndexesAsSender(indexedTaggingSecretsIncrementedInTheTx); + this.log.debug(`Incremented next tagging secret indexes as sender for the tx`, { + indexedTaggingSecretsIncrementedInTheTx, + }); + } else { + this.log.debug(`No next tagging secret indexes incremented in the tx`); + } + + return txProvingResult; } catch (err: any) { throw this.#contextualizeError(err, inspect(txRequest), inspect(privateExecutionResult)); } diff --git a/yarn-project/stdlib/src/logs/directional_app_tagging_secret.ts b/yarn-project/stdlib/src/logs/directional_app_tagging_secret.ts index c2619c6b2eca..3c10393a2f18 100644 --- a/yarn-project/stdlib/src/logs/directional_app_tagging_secret.ts +++ b/yarn-project/stdlib/src/logs/directional_app_tagging_secret.ts @@ -1,6 +1,8 @@ import { Grumpkin, poseidon2Hash } from '@aztec/foundation/crypto'; import { type Fq, Fr, type Point } from '@aztec/foundation/fields'; +import { z } from 'zod'; + import type { AztecAddress } from '../aztec-address/index.js'; import type { CompleteAddress } from '../contract/complete_address.js'; import { computeAddressSecret, computePreaddress } from '../keys/derivation.js'; @@ -70,3 +72,7 @@ async function computeSharedTaggingSecret( // leads to a positive y-coordinate, which is the only valid address point return curve.mul(externalAddressPoint, await computeAddressSecret(knownPreaddress, localIvsk)); } + +export const DirectionalAppTaggingSecretSchema = z.object({ + value: Fr.schema, +}); diff --git a/yarn-project/stdlib/src/logs/indexed_tagging_secret.ts b/yarn-project/stdlib/src/logs/indexed_tagging_secret.ts index 8992e08f0103..112fa3b83ec4 100644 --- a/yarn-project/stdlib/src/logs/indexed_tagging_secret.ts +++ b/yarn-project/stdlib/src/logs/indexed_tagging_secret.ts @@ -1,4 +1,11 @@ -import type { DirectionalAppTaggingSecret } from './directional_app_tagging_secret.js'; +import { schemas } from '@aztec/foundation/schemas'; + +import { z } from 'zod'; + +import { + type DirectionalAppTaggingSecret, + DirectionalAppTaggingSecretSchema, +} from './directional_app_tagging_secret.js'; /** * Represents a preimage of a private log tag (see `Tag` in `pxe/src/tagging`). @@ -11,3 +18,8 @@ export type IndexedTaggingSecret = { secret: DirectionalAppTaggingSecret; index: number; }; + +export const IndexedTaggingSecretSchema = z.object({ + secret: DirectionalAppTaggingSecretSchema, + index: schemas.Integer, +}); diff --git a/yarn-project/stdlib/src/tests/mocks.ts b/yarn-project/stdlib/src/tests/mocks.ts index f135835c520b..85dc9040b61f 100644 --- a/yarn-project/stdlib/src/tests/mocks.ts +++ b/yarn-project/stdlib/src/tests/mocks.ts @@ -184,6 +184,7 @@ const emptyPrivateCallExecutionResult = () => [], [], [], + [], ); const emptyPrivateExecutionResult = () => new PrivateExecutionResult(emptyPrivateCallExecutionResult(), Fr.zero(), []); diff --git a/yarn-project/stdlib/src/tx/private_execution_result.test.ts b/yarn-project/stdlib/src/tx/private_execution_result.test.ts index 8396cb7a2168..6b71f51665a5 100644 --- a/yarn-project/stdlib/src/tx/private_execution_result.test.ts +++ b/yarn-project/stdlib/src/tx/private_execution_result.test.ts @@ -23,6 +23,7 @@ function emptyCallExecutionResult(): PrivateCallExecutionResult { [], [], [], + [], ); } diff --git a/yarn-project/stdlib/src/tx/private_execution_result.ts b/yarn-project/stdlib/src/tx/private_execution_result.ts index a4ba52526e1f..7f606a0ca6b5 100644 --- a/yarn-project/stdlib/src/tx/private_execution_result.ts +++ b/yarn-project/stdlib/src/tx/private_execution_result.ts @@ -10,6 +10,7 @@ import { PrivateCircuitPublicInputs } from '../kernel/private_circuit_public_inp import type { IsEmpty } from '../kernel/utils/interfaces.js'; import { sortByCounter } from '../kernel/utils/order_and_comparison.js'; import { ContractClassLog, ContractClassLogFields } from '../logs/contract_class_log.js'; +import { type IndexedTaggingSecret, IndexedTaggingSecretSchema } from '../logs/indexed_tagging_secret.js'; import { Note } from '../note/note.js'; import { type ZodFor, mapSchema, schemas } from '../schemas/index.js'; import type { UInt32 } from '../types/index.js'; @@ -135,6 +136,8 @@ export class PrivateCallExecutionResult { public returnValues: Fr[], /** The offchain effects emitted during execution of this function call via the `emit_offchain_effect` oracle. */ public offchainEffects: { data: Fr[] }[], + /** The tagging indexes incremented by this execution along with the directional app tagging secrets. */ + public indexedTaggingSecrets: IndexedTaggingSecret[], /** The nested executions. */ public nestedExecutionResults: PrivateCallExecutionResult[], /** @@ -158,6 +161,7 @@ export class PrivateCallExecutionResult { noteHashNullifierCounterMap: mapSchema(z.coerce.number(), z.number()), returnValues: z.array(schemas.Fr), offchainEffects: z.array(z.object({ data: z.array(schemas.Fr) })), + indexedTaggingSecrets: z.array(IndexedTaggingSecretSchema), nestedExecutionResults: z.array(z.lazy(() => PrivateCallExecutionResult.schema)), contractClassLogs: z.array(CountedContractClassLog.schema), }) @@ -175,6 +179,7 @@ export class PrivateCallExecutionResult { fields.noteHashNullifierCounterMap, fields.returnValues, fields.offchainEffects, + fields.indexedTaggingSecrets, fields.nestedExecutionResults, fields.contractClassLogs, ); @@ -195,6 +200,7 @@ export class PrivateCallExecutionResult { data: [Fr.random()], }, ], + [], await timesParallel(nested, () => PrivateCallExecutionResult.random(0)), [new CountedContractClassLog(await ContractClassLog.random(), randomInt(10))], ); diff --git a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts index 34965c801fa3..6a8671436e66 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts @@ -19,6 +19,7 @@ import { } from '@aztec/pxe/server'; import { ExecutionNoteCache, + ExecutionTaggingIndexCache, HashedValuesCache, type IMiscOracle, Oracle, @@ -292,6 +293,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl const txRequestHash = getSingleTxBlockRequestHash(blockNumber); const noteCache = new ExecutionNoteCache(txRequestHash); + const taggingIndexCache = new ExecutionTaggingIndexCache(); const simulator = new WASMSimulator(); @@ -307,6 +309,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl [], HashedValuesCache.create([new HashedValues(args, argsHash)]), noteCache, + taggingIndexCache, this.pxeOracleInterface, 0, 1, diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index 8ae404bf0b4b..f7e3c91f50fd 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -13,6 +13,7 @@ import { } from '@aztec/pxe/server'; import { ExecutionNoteCache, + ExecutionTaggingIndexCache, HashedValuesCache, type IPrivateExecutionOracle, type IUtilityExecutionOracle, @@ -67,6 +68,7 @@ type SessionState = nextBlockGlobalVariables: GlobalVariables; txRequestHash: Fr; noteCache: ExecutionNoteCache; + taggingIndexCache: ExecutionTaggingIndexCache; } /** * The public state is entered via the `public_context` function. In this state the AVM opcodes that `#[public]` @@ -294,6 +296,7 @@ export class TXESession implements TXESessionStateHandler { const txRequestHash = getSingleTxBlockRequestHash(nextBlockGlobalVariables.blockNumber); const noteCache = new ExecutionNoteCache(txRequestHash); + const taggingIndexCache = new ExecutionTaggingIndexCache(); this.oracleHandler = new PrivateExecutionOracle( Fr.ZERO, @@ -304,14 +307,16 @@ export class TXESession implements TXESessionStateHandler { [], new HashedValuesCache(), noteCache, + taggingIndexCache, this.pxeOracleInterface, ); - // We store the note cache fed into the PrivateExecutionOracle (along with some other auxiliary data) in order to - // refer to it later, mimicking the way this object is used by the ContractFunctionSimulator. The difference resides - // in that the simulator has all information needed in order to run the simulation, while ours will be ongoing as - // the different oracles will be invoked from the Noir test, until eventually the private execution finishes. - this.state = { name: 'PRIVATE', nextBlockGlobalVariables, txRequestHash, noteCache }; + // We store the note and tagging index caches fed into the PrivateExecutionOracle (along with some other auxiliary + // data) in order to refer to it later, mimicking the way this object is used by the ContractFunctionSimulator. The + // difference resides in that the simulator has all information needed in order to run the simulation, while ours + // will be ongoing as the different oracles will be invoked from the Noir test, until eventually the private + // execution finishes. + this.state = { name: 'PRIVATE', nextBlockGlobalVariables, txRequestHash, noteCache, taggingIndexCache }; this.logger.debug(`Entered state ${this.state.name}`); return (this.oracleHandler as PrivateExecutionOracle).getPrivateContextInputs();