From bad2246852f919ac278a41bfcaf45895a7e11d97 Mon Sep 17 00:00:00 2001 From: benesjan <13470840+benesjan@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:42:17 +0000 Subject: [PATCH] feat: `ExecutionTaggingIndexCache` Fixes https://github.com/AztecProtocol/aztec-packages/issues/15753 In this PR I implement an ExecutionTaggingIndexCache which stores the used indexes during an execution and at the end we store them in the database. By storing that info once execution is done we'll be able to have indexes associated with a transaction hash. This is a necessary step towards improving of unconstrained tagging as described in [this post](https://forum.aztec.network/t/on-note-discovery-and-index-coordination/7165). Given that once this PR is merged we will store the indexes in the database only for executions and not for simulations this PR should fix https://github.com/AztecProtocol/aztec-packages/issues/15753. --- .../test/test_log_contract/src/main.nr | 33 +++++++++++ .../end-to-end/src/e2e_event_logs.test.ts | 58 +++++++++++++++++++ .../contract_function_simulator.ts | 3 + .../execution_data_provider.ts | 10 ++-- .../execution_tagging_index_cache.ts | 29 ++++++++++ .../src/contract_function_simulator/index.ts | 1 + .../oracle/private_execution.test.ts | 13 +++-- .../oracle/private_execution.ts | 2 + .../oracle/private_execution_oracle.ts | 51 ++++++++++++---- .../pxe_oracle_interface.ts | 28 +-------- .../private_kernel_execution_prover.test.ts | 1 + yarn-project/pxe/src/pxe.ts | 15 ++++- .../logs/directional_app_tagging_secret.ts | 6 ++ .../stdlib/src/logs/indexed_tagging_secret.ts | 14 ++++- yarn-project/stdlib/src/tests/mocks.ts | 1 + .../src/tx/private_execution_result.test.ts | 1 + .../stdlib/src/tx/private_execution_result.ts | 6 ++ .../oracle/txe_oracle_top_level_context.ts | 3 + yarn-project/txe/src/txe_session.ts | 15 +++-- 19 files changed, 235 insertions(+), 55 deletions(-) create mode 100644 yarn-project/pxe/src/contract_function_simulator/execution_tagging_index_cache.ts 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();