From a2125f7611ad9ab3f479b806cbcc7ff1f97db57e Mon Sep 17 00:00:00 2001 From: Leila Wang Date: Mon, 11 Sep 2023 21:32:18 +0200 Subject: [PATCH] fix: return partial witnesses based on the content of read requests. (#2164) Close #1660 - An app can apply a filter to the returned notes from the `getNotes` oracle call, to only read and make use of some of them. So the returned notes are not always included in the read_requests. We now construct the `readRequestPartialWitnesses` based on the content of the read_requests array in public inputs at the end of an execution. Refactoring: - Create `ExecutionNoteCache` to share new notes (pending notes) and emitted nullifiers among all executions. # Checklist: Remove the checklist to signal you've completed it. Enable auto-merge if the PR is ready to merge. - [ ] If the pull request requires a cryptography review (e.g. cryptographic algorithm implementations) I have added the 'crypto' tag. - [ ] I have reviewed my diff in github, line by line and removed unexpected formatting changes, testing logs, or commented-out code. - [ ] Every change is related to the PR description. - [ ] I have [linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) this pull request to relevant issues (if any exist). --- .../src/client/client_execution_context.ts | 219 ++++++++++-------- .../acir-simulator/src/client/db_oracle.ts | 12 +- .../src/client/execution_note_cache.ts | 102 ++++++++ .../src/client/execution_result.test.ts | 5 +- .../src/client/execution_result.ts | 28 +-- .../src/client/private_execution.test.ts | 53 +++-- .../src/client/private_execution.ts | 44 +--- .../acir-simulator/src/client/simulator.ts | 10 +- .../client/unconstrained_execution.test.ts | 1 + .../src/database/note_spending_info_dao.ts | 7 + .../src/kernel_prover/kernel_prover.test.ts | 2 +- .../src/kernel_prover/kernel_prover.ts | 4 +- .../src/note_processor/note_processor.ts | 3 +- .../aztec-rpc/src/simulator_oracle/index.ts | 21 +- yarn-project/circuits.js/src/abis/abis.ts | 6 +- .../aztec-noir/src/note/lifecycle.nr | 5 +- .../aztec-noir/src/note/note_getter.nr | 25 +- .../noir-libs/aztec-noir/src/oracle/notes.nr | 4 +- 18 files changed, 309 insertions(+), 242 deletions(-) create mode 100644 yarn-project/acir-simulator/src/client/execution_note_cache.ts diff --git a/yarn-project/acir-simulator/src/client/client_execution_context.ts b/yarn-project/acir-simulator/src/client/client_execution_context.ts index 1f2810a2231..61d1d09c779 100644 --- a/yarn-project/acir-simulator/src/client/client_execution_context.ts +++ b/yarn-project/acir-simulator/src/client/client_execution_context.ts @@ -1,5 +1,5 @@ import { CircuitsWasm, HistoricBlockData, ReadRequestMembershipWitness, TxContext } from '@aztec/circuits.js'; -import { siloNullifier } from '@aztec/circuits.js/abis'; +import { computeUniqueCommitment, siloCommitment } from '@aztec/circuits.js/abis'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, Point } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -12,17 +12,33 @@ import { toAcvmL1ToL2MessageLoadOracleInputs, } from '../acvm/index.js'; import { PackedArgsCache } from '../common/packed_args_cache.js'; -import { DBOracle, PendingNoteData } from './db_oracle.js'; +import { DBOracle } from './db_oracle.js'; +import { ExecutionNoteCache } from './execution_note_cache.js'; +import { NewNoteData } from './execution_result.js'; import { pickNotes } from './pick_notes.js'; /** * The execution context for a client tx simulation. */ export class ClientTxExecutionContext { - // Note: not forwarded to nested contexts via `extend()` because these witnesses - // are meant to be maintained on a per-call basis as they should mirror read requests - // output by an app circuit via public inputs. - private readRequestPartialWitnesses: ReadRequestMembershipWitness[] = []; + /** + * New notes created during this execution. + * It's possible that a note in this list has been nullified (in the same or other executions) and doen't exist in the ExecutionNoteCache and the final proof data. + * But we still include those notes in the execution result because their commitments are still in the public inputs of this execution. + * This information is only for references (currently used for tests), and is not used for any sort of constrains. + * Users can also use this to get a clearer idea of what's happened during a simulation. + */ + private newNotes: NewNoteData[] = []; + /** + * Notes from previous transactions that are returned to the oracle call `getNotes` during this execution. + * The mapping maps from the unique siloed note hash to the index for notes created in private executions. + * It maps from siloed note hash to the index for notes created by public functions. + * + * They are not part of the ExecutionNoteCache and being forwarded to nested contexts via `extend()` + * because these notes are meant to be maintained on a per-call basis + * They should act as references for the read requests output by an app circuit via public inputs. + */ + private gotNotes: Map = new Map(); /** Logger instance */ private logger = createDebugLogger('aztec:simulator:execution_context'); @@ -30,22 +46,13 @@ export class ClientTxExecutionContext { constructor( /** The database oracle. */ public db: DBOracle, - /** The tx nullifier, which is also the first nullifier. This will be used to compute the nonces for pending notes. */ - private txNullifier: Fr, /** The tx context. */ public txContext: TxContext, /** Data required to reconstruct the block hash, it contains historic roots. */ public historicBlockData: HistoricBlockData, /** The cache of packed arguments */ public packedArgsCache: PackedArgsCache, - /** Pending notes created (and not nullified) up to current point in execution. - * If a nullifier for a note in this list is emitted, the note will be REMOVED. */ - private pendingNotes: PendingNoteData[] = [], - /** The list of nullifiers created in this transaction. The commitment/note which is nullified - * might be pending or not (i.e., was generated in a previous transaction) - * Note that their value (bigint representation) is used because Frs cannot be looked up in Sets. */ - private pendingNullifiers: Set = new Set(), - + private noteCache: ExecutionNoteCache, private log = createDebugLogger('aztec:simulator:client_execution_context'), ) {} @@ -56,21 +63,39 @@ export class ClientTxExecutionContext { public extend() { return new ClientTxExecutionContext( this.db, - this.txNullifier, this.txContext, this.historicBlockData, this.packedArgsCache, - this.pendingNotes, - this.pendingNullifiers, + this.noteCache, ); } /** - * For getting accumulated data. + * This function will populate readRequestPartialWitnesses which + * here is just used to flag reads as "transient" for new notes created during this execution + * or to flag non-transient reads with their leafIndex. + * The KernelProver will use this to fully populate witnesses and provide hints to + * the kernel regarding which commitments each transient read request corresponds to. + * @param readRequests - Note hashed of the notes being read. * @returns An array of partially filled in read request membership witnesses. */ - public getReadRequestPartialWitnesses() { - return this.readRequestPartialWitnesses; + public getReadRequestPartialWitnesses(readRequests: Fr[]) { + return readRequests + .filter(r => !r.isZero()) + .map(r => { + const index = this.gotNotes.get(r.value); + return index !== undefined + ? ReadRequestMembershipWitness.empty(index) + : ReadRequestMembershipWitness.emptyTransient(); + }); + } + + /** + * Get the data for the newly created notes. + * @param innerNoteHashes - Inner note hashes for the notes. + */ + public getNewNotes(): NewNoteData[] { + return this.newNotes; } /** @@ -98,12 +123,6 @@ export class ClientTxExecutionContext { * Real notes coming from DB will have a leafIndex which * represents their index in the private data tree. * - * This function will populate this.readRequestPartialWitnesses which - * here is just used to flag reads as "transient" (one in getPendingNotes) - * or to flag non-transient reads with their leafIndex. - * The KernelProver will use this to fully populate witnesses and provide hints to - * the kernel regarding which commitments each transient read request corresponds to. - * * @param contractAddress - The contract address. * @param storageSlot - The storage slot. * @param numSelects - The number of valid selects in selectBy and selectValues. @@ -134,15 +153,13 @@ export class ClientTxExecutionContext { ): Promise { const storageSlotField = fromACVMField(storageSlot); - const pendingNotes = this.pendingNotes.filter( - n => n.contractAddress.equals(contractAddress) && n.storageSlot.equals(storageSlotField), - ); + // Nullified pending notes are already removed from the list. + const pendingNotes = this.noteCache.getNotes(contractAddress, storageSlotField); + const pendingNullifiers = this.noteCache.getNullifiers(contractAddress, storageSlotField); const dbNotes = await this.db.getNotes(contractAddress, storageSlotField); + const dbNotesFiltered = dbNotes.filter(n => !pendingNullifiers.has((n.siloedNullifier as Fr).value)); - const dbNotesFiltered = dbNotes.filter(n => !this.pendingNullifiers.has((n.siloedNullifier as Fr).value)); - - // Nullified pending notes are already removed from the list. const notes = pickNotes([...dbNotesFiltered, ...pendingNotes], { selects: selectBy .slice(0, numSelects) @@ -158,6 +175,15 @@ export class ClientTxExecutionContext { .join(', ')}`, ); + const wasm = await CircuitsWasm.get(); + notes.forEach(n => { + if (n.index !== undefined) { + const siloedNoteHash = siloCommitment(wasm, n.contractAddress, n.innerNoteHash); + const uniqueSiloedNoteHash = computeUniqueCommitment(wasm, n.nonce, siloedNoteHash); + this.gotNotes.set(uniqueSiloedNoteHash.value, n.index); + } + }); + // TODO: notice, that if we don't have a note in our DB, we don't know how big the preimage needs to be, and so we don't actually know how many dummy notes to return, or big to make those dummy notes, or where to position `is_some` booleans to inform the noir program that _all_ the notes should be dummies. // By a happy coincidence, a `0` field is interpreted as `is_none`, and since in this case (of an empty db) we'll return all zeros (paddedZeros), the noir program will treat the returned data as all dummies, but this is luck. Perhaps a preimage size should be conveyed by the get_notes noir oracle? const preimageLength = notes?.[0]?.preimage.length ?? 0; @@ -173,14 +199,6 @@ export class ClientTxExecutionContext { const realNotePreimages = notes.flatMap(({ nonce, preimage }) => [nonce, isSome, ...preimage]); - // Add a partial witness for each note. - // It contains the note index for db notes. And flagged as transient for pending notes. - notes.forEach(({ index }) => { - this.readRequestPartialWitnesses.push( - index !== undefined ? ReadRequestMembershipWitness.empty(index) : ReadRequestMembershipWitness.emptyTransient(), - ); - }); - const returnHeaderLength = 2; // is for the header values: `notes.length` and `contractAddress`. const extraPreimageLength = 2; // is for the nonce and isSome fields. const extendedPreimageLength = preimageLength + extraPreimageLength; @@ -202,6 +220,58 @@ export class ClientTxExecutionContext { ); } + /** + * Keep track of the new note created during execution. + * It can be used in subsequent calls (or transactions when chaining txs is possible). + * @param contractAddress - The contract address. + * @param storageSlot - The storage slot. + * @param preimage - The preimage of the new note. + * @param innerNoteHash - The inner note hash of the new note. + * @returns + */ + public handleNewNote( + contractAddress: AztecAddress, + storageSlot: ACVMField, + preimage: ACVMField[], + innerNoteHash: ACVMField, + ) { + this.noteCache.addNewNote({ + contractAddress, + storageSlot: fromACVMField(storageSlot), + nonce: Fr.ZERO, // Nonce cannot be known during private execution. + preimage: preimage.map(f => fromACVMField(f)), + siloedNullifier: undefined, // Siloed nullifier cannot be known for newly created note. + innerNoteHash: fromACVMField(innerNoteHash), + }); + this.newNotes.push({ + storageSlot: fromACVMField(storageSlot), + preimage: preimage.map(f => fromACVMField(f)), + }); + } + + /** + * Adding a siloed nullifier into the current set of all pending nullifiers created + * within the current transaction/execution. + * @param contractAddress - The contract address. + * @param storageSlot - The storage slot. + * @param innerNullifier - The pending nullifier to add in the list (not yet siloed by contract address). + * @param innerNoteHash - The inner note hash of the new note. + * @param contractAddress - The contract address + */ + public async handleNullifiedNote( + contractAddress: AztecAddress, + storageSlot: ACVMField, + innerNullifier: ACVMField, + innerNoteHash: ACVMField, + ) { + await this.noteCache.nullifyNote( + contractAddress, + fromACVMField(storageSlot), + fromACVMField(innerNullifier), + fromACVMField(innerNoteHash), + ); + } + /** * Fetches the a message from the db, given its key. * @param msgKey - A buffer representing the message key. @@ -219,71 +289,16 @@ export class ClientTxExecutionContext { * @returns The commitment data. */ public async getCommitment(contractAddress: AztecAddress, commitment: ACVMField) { + const innerNoteHash = fromACVMField(commitment); // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386): only works // for noteHashes/commitments created by public functions! Once public kernel or // base rollup circuit injects nonces, this can be used with commitments created by // private functions as well. - const commitmentInputs = await this.db.getCommitmentOracle(contractAddress, fromACVMField(commitment)); + const commitmentInputs = await this.db.getCommitmentOracle(contractAddress, innerNoteHash); // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1029): support pending commitments here - this.readRequestPartialWitnesses.push(ReadRequestMembershipWitness.empty(commitmentInputs.index)); - return toAcvmCommitmentLoadOracleInputs(commitmentInputs, this.historicBlockData.privateDataTreeRoot); - } - - /** - * Process new note created during execution. - * @param contractAddress - The contract address. - * @param storageSlot - The storage slot. - * @param preimage - new note preimage. - * @param innerNoteHash - inner note hash - */ - public pushNewNote(contractAddress: AztecAddress, storageSlot: Fr, preimage: Fr[], innerNoteHash: Fr) { - this.pendingNotes.push({ - contractAddress, - storageSlot: storageSlot, - nonce: Fr.ZERO, // nonce is cannot be known during private execution - preimage, - innerNoteHash, - }); - } - - /** - * Adding a siloed nullifier into the current set of all pending nullifiers created - * within the current transaction/execution. - * @param innerNullifier - The pending nullifier to add in the list (not yet siloed by contract address). - * @param contractAddress - The contract address - */ - public async pushNewNullifier(innerNullifier: Fr, contractAddress: AztecAddress) { const wasm = await CircuitsWasm.get(); - const siloedNullifier = siloNullifier(wasm, contractAddress, innerNullifier); - this.pendingNullifiers.add(siloedNullifier.value); - } - - /** - * Update the list of pending notes by chopping a note which is nullified. - * The identifier used to determine matching is the inner note hash value. - * However, we adopt a defensive approach and ensure that the contract address - * and storage slot do match. - * Note that this method might be called with an innerNoteHash referring to - * a note created in a previous transaction which will result in this array - * of pending notes left unchanged. - * @param innerNoteHash - The inner note hash value to which note will be chopped. - * @param contractAddress - The contract address - * @param storageSlot - The storage slot as a Field Fr element - */ - public nullifyPendingNotes(innerNoteHash: Fr, contractAddress: AztecAddress, storageSlot: Fr) { - // IMPORTANT: We do need an in-place array mutation of this.pendingNotes as this array is shared - // by reference among the nested calls. That is the main reason for 'splice' usage below. - this.pendingNotes.splice( - 0, - this.pendingNotes.length, - ...this.pendingNotes.filter( - n => - !( - n.innerNoteHash.equals(innerNoteHash) && - n.contractAddress.equals(contractAddress) && - n.storageSlot.equals(storageSlot) - ), - ), - ); + const siloedNoteHash = siloCommitment(wasm, contractAddress, innerNoteHash); + this.gotNotes.set(siloedNoteHash.value, commitmentInputs.index); + return toAcvmCommitmentLoadOracleInputs(commitmentInputs, this.historicBlockData.privateDataTreeRoot); } } diff --git a/yarn-project/acir-simulator/src/client/db_oracle.ts b/yarn-project/acir-simulator/src/client/db_oracle.ts index 5f8e68846a1..df56338f9fa 100644 --- a/yarn-project/acir-simulator/src/client/db_oracle.ts +++ b/yarn-project/acir-simulator/src/client/db_oracle.ts @@ -18,20 +18,14 @@ export interface NoteData { nonce: Fr; /** The preimage of the note */ preimage: Fr[]; - /** The corresponding nullifier of the note */ + /** The inner note hash of the note. */ + innerNoteHash: Fr; + /** The corresponding nullifier of the note. Undefined for pending notes. */ siloedNullifier?: Fr; /** The note's leaf index in the private data tree. Undefined for pending notes. */ index?: bigint; } -/** - * Information about a note needed during execution. - */ -export interface PendingNoteData extends NoteData { - /** The inner note hash (used as a nullified commitment). */ - innerNoteHash: Fr; -} - /** * The format that noir uses to get L1 to L2 Messages. */ diff --git a/yarn-project/acir-simulator/src/client/execution_note_cache.ts b/yarn-project/acir-simulator/src/client/execution_note_cache.ts new file mode 100644 index 00000000000..18de0658095 --- /dev/null +++ b/yarn-project/acir-simulator/src/client/execution_note_cache.ts @@ -0,0 +1,102 @@ +import { CircuitsWasm, EMPTY_NULLIFIED_COMMITMENT } from '@aztec/circuits.js'; +import { siloNullifier } from '@aztec/circuits.js/abis'; +import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { Fr } from '@aztec/foundation/fields'; + +import { NoteData } from './db_oracle.js'; + +/** + * Generate a key with the given contract address and storage slot. + * @param contractAddress - Contract address. + * @param storageSlot - Storage slot. + */ +const generateKeyForContractStorageSlot = (contractAddress: AztecAddress, storageSlot: Fr) => + `${contractAddress.toShortString}${storageSlot}`; + +/** + * Data that's accessible by all the function calls in an execution. + */ +export class ExecutionNoteCache { + /** + * Notes created during execution. + * The key of the mapping is a value representing a contract address and storage slot combination. + */ + private newNotes: Map = new Map(); + + /** + * The list of nullifiers created in this transaction. + * The key of the mapping is a value representing a contract address and storage slot combination. + * The note which is nullified might be new or not (i.e., was generated in a previous transaction). + * Note that their value (bigint representation) is used because Frs cannot be looked up in Sets. + */ + private nullifiers: Map> = new Map(); + + /** + * Add a new note to cache. + * @param note - New note created during execution. + */ + public addNewNote(note: NoteData) { + const key = generateKeyForContractStorageSlot(note.contractAddress, note.storageSlot); + const notes = this.newNotes.get(key) ?? []; + notes.push(note); + this.newNotes.set(key, notes); + } + + /** + * Add a nullifier to cache. It could be for a db note or a new note created during execution. + * @param contractAddress - Contract address of the note. + * @param storageSlot - Storage slot of the note. + * @param innerNullifier - Inner nullifier of the note. + * @param innerNoteHash - Inner note hash of the note. If this value equals EMPTY_NULLIFIED_COMMITMENT, it means the + * note being nullified is from a previous transaction (and thus not a new note). + */ + public async nullifyNote(contractAddress: AztecAddress, storageSlot: Fr, innerNullifier: Fr, innerNoteHash: Fr) { + const wasm = await CircuitsWasm.get(); + const siloedNullifier = siloNullifier(wasm, contractAddress, innerNullifier); + const nullifiers = this.getNullifiers(contractAddress, storageSlot); + if (nullifiers.has(siloedNullifier.value)) { + throw new Error('Attemp to nullify the same note twice.'); + } + + nullifiers.add(siloedNullifier.value); + const key = generateKeyForContractStorageSlot(contractAddress, storageSlot); + this.nullifiers.set(key, nullifiers); + + // Find and remove the matching new note if the emitted innerNoteHash is not empty. + if (!innerNoteHash.equals(new Fr(EMPTY_NULLIFIED_COMMITMENT))) { + const notes = this.newNotes.get(key) ?? []; + /** + * The identifier used to determine matching is the inner note hash value. + * However, we adopt a defensive approach and ensure that the contract address + * and storage slot do match. + */ + const noteIndexToRemove = notes.findIndex(n => n.innerNoteHash.equals(innerNoteHash)); + if (noteIndexToRemove === -1) { + throw new Error('Attemp to remove a pending note that does not exist.'); + } + notes.splice(noteIndexToRemove, 1); + this.newNotes.set(key, notes); + } + } + + /** + * Return notes created up to current point in execution. + * If a nullifier for a note in this list is emitted, the note will be deleted. + * @param contractAddress - Contract address of the notes. + * @param storageSlot - Storage slot of the notes. + **/ + public getNotes(contractAddress: AztecAddress, storageSlot: Fr) { + const key = generateKeyForContractStorageSlot(contractAddress, storageSlot); + return this.newNotes.get(key) ?? []; + } + + /** + * Return all nullifiers of a storage slot. + * @param contractAddress - Contract address of the notes. + * @param storageSlot - Storage slot of the notes. + */ + public getNullifiers(contractAddress: AztecAddress, storageSlot: Fr): Set { + const key = generateKeyForContractStorageSlot(contractAddress, storageSlot); + return this.nullifiers.get(key) || new Set(); + } +} diff --git a/yarn-project/acir-simulator/src/client/execution_result.test.ts b/yarn-project/acir-simulator/src/client/execution_result.test.ts index 5f99c60e094..bd635b2c098 100644 --- a/yarn-project/acir-simulator/src/client/execution_result.test.ts +++ b/yarn-project/acir-simulator/src/client/execution_result.test.ts @@ -10,10 +10,7 @@ function emptyExecutionResult(): ExecutionResult { partialWitness: new Map(), callStackItem: PrivateCallStackItem.empty(), readRequestPartialWitnesses: [], - preimages: { - newNotes: [], - nullifiedNotes: [], - }, + newNotes: [], returnValues: [], nestedExecutions: [], enqueuedPublicFunctionCalls: [], diff --git a/yarn-project/acir-simulator/src/client/execution_result.ts b/yarn-project/acir-simulator/src/client/execution_result.ts index 8effcb4649b..bf2b738953e 100644 --- a/yarn-project/acir-simulator/src/client/execution_result.ts +++ b/yarn-project/acir-simulator/src/client/execution_result.ts @@ -15,28 +15,6 @@ export interface NewNoteData { storageSlot: Fr; } -/** - * The contents of a nullified commitment. - */ -export interface NewNullifierData { - /** The preimage of the nullified commitment. */ - preimage: Fr[]; - /** The storage slot of the nullified commitment. */ - storageSlot: Fr; - /** The nullifier. */ - nullifier: Fr; -} - -/** - * The preimages of the executed function. - */ -export interface ExecutionPreimages { - /** The preimages of the new notes. */ - newNotes: NewNoteData[]; - /** The preimages of the nullified commitments. */ - nullifiedNotes: NewNullifierData[]; -} - /** * The result of executing a private function. */ @@ -53,9 +31,9 @@ export interface ExecutionResult { callStackItem: PrivateCallStackItem; /** The partially filled-in read request membership witnesses for commitments being read. */ readRequestPartialWitnesses: ReadRequestMembershipWitness[]; - // Needed for the user - /** The preimages of the executed function. */ - preimages: ExecutionPreimages; + // Needed when we enable chained txs. The new notes can be cached and used in a later transaction. + /** The notes created in the executed function. */ + newNotes: NewNoteData[]; /** The decoded return values of the executed function. */ returnValues: DecodedReturn; /** The nested executions. */ diff --git a/yarn-project/acir-simulator/src/client/private_execution.test.ts b/yarn-project/acir-simulator/src/client/private_execution.test.ts index 7f0dd6d8cba..e7d769cd235 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.test.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.test.ts @@ -185,11 +185,13 @@ describe('Private Execution test suite', () => { const noteHashIndex = Math.floor(Math.random()); // mock index in TX's final newNoteHashes array const nonce = computeCommitmentNonce(circuitsWasm, mockFirstNullifier, noteHashIndex); const preimage = [new Fr(amount), owner.toField(), Fr.random()]; + const innerNoteHash = Fr.fromBuffer(hash(preimage.map(p => p.toBuffer()))); return { contractAddress, storageSlot, nonce, preimage, + innerNoteHash, siloedNullifier: new Fr(0), index: currentNoteIndex++, }; @@ -259,8 +261,8 @@ describe('Private Execution test suite', () => { const result = await runSimulator({ args: [140, owner], abi }); - expect(result.preimages.newNotes).toHaveLength(1); - const newNote = result.preimages.newNotes[0]; + expect(result.newNotes).toHaveLength(1); + const newNote = result.newNotes[0]; expect(newNote.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm)); const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO)); @@ -277,8 +279,8 @@ describe('Private Execution test suite', () => { const result = await runSimulator({ args: [140, owner], abi }); - expect(result.preimages.newNotes).toHaveLength(1); - const newNote = result.preimages.newNotes[0]; + expect(result.newNotes).toHaveLength(1); + const newNote = result.newNotes[0]; expect(newNote.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm)); const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO)); @@ -313,8 +315,8 @@ describe('Private Execution test suite', () => { expect(newNullifiers).toHaveLength(consumedNotes.length); expect(newNullifiers).toEqual(expect.arrayContaining(consumedNotes.map(n => n.innerNullifier))); - expect(result.preimages.newNotes).toHaveLength(2); - const [changeNote, recipientNote] = result.preimages.newNotes; + expect(result.newNotes).toHaveLength(2); + const [changeNote, recipientNote] = result.newNotes; expect(recipientNote.storageSlot).toEqual(recipientStorageSlot); const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO)); @@ -357,8 +359,8 @@ describe('Private Execution test suite', () => { const newNullifiers = result.callStackItem.publicInputs.newNullifiers.filter(field => !field.equals(Fr.ZERO)); expect(newNullifiers).toEqual(consumedNotes.map(n => n.innerNullifier)); - expect(result.preimages.newNotes).toHaveLength(2); - const [changeNote, recipientNote] = result.preimages.newNotes; + expect(result.newNotes).toHaveLength(2); + const [changeNote, recipientNote] = result.newNotes; expect(recipientNote.preimage[0]).toEqual(new Fr(amountToTransfer)); expect(changeNote.preimage[0]).toEqual(new Fr(balance - amountToTransfer)); }); @@ -370,6 +372,8 @@ describe('Private Execution test suite', () => { const storageSlot = new Fr(2n); // choose nonzero nonce otherwise reads will be interpreted as transient (inner note hash instead of unique+siloed) const nonce = new Fr(1n); + const customNoteHash = hash([toBufferBE(amount, 32), secret.toBuffer()]); + const innerNoteHash = Fr.fromBuffer(hash([storageSlot.toBuffer(), customNoteHash])); oracle.getNotes.mockResolvedValue([ { @@ -377,6 +381,7 @@ describe('Private Execution test suite', () => { storageSlot, nonce, preimage: [new Fr(amount), secret], + innerNoteHash, siloedNullifier: new Fr(0), index: 1n, }, @@ -393,8 +398,6 @@ describe('Private Execution test suite', () => { // Check the read request was inserted successfully. const readRequests = result.callStackItem.publicInputs.readRequests.filter(field => !field.equals(Fr.ZERO)); - const customNoteHash = hash([toBufferBE(amount, 32), secret.toBuffer()]); - const innerNoteHash = Fr.fromBuffer(hash([storageSlot.toBuffer(), customNoteHash])); const siloedNoteHash = siloCommitment(circuitsWasm, contractAddress, innerNoteHash); const uniqueSiloedNoteHash = computeUniqueCommitment(circuitsWasm, nonce, siloedNoteHash); expect(readRequests).toEqual([uniqueSiloedNoteHash]); @@ -421,11 +424,13 @@ describe('Private Execution test suite', () => { const noteHashIndex = Math.floor(Math.random()); // mock index in TX's final newNoteHashes array const nonce = computeCommitmentNonce(circuitsWasm, mockFirstNullifier, noteHashIndex); const preimage = [new Fr(amount), owner.toField(), Fr.random()]; + const innerNoteHash = Fr.fromBuffer(hash(preimage.map(p => p.toBuffer()))); return { contractAddress, storageSlot, nonce, preimage, + innerNoteHash, siloedNullifier: new Fr(0), index: currentNoteIndex++, }; @@ -495,8 +500,8 @@ describe('Private Execution test suite', () => { const result = await runSimulator({ args: [140, owner], abi }); - expect(result.preimages.newNotes).toHaveLength(1); - const newNote = result.preimages.newNotes[0]; + expect(result.newNotes).toHaveLength(1); + const newNote = result.newNotes[0]; expect(newNote.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm)); const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO)); @@ -513,8 +518,8 @@ describe('Private Execution test suite', () => { const result = await runSimulator({ args: [140, owner], abi }); - expect(result.preimages.newNotes).toHaveLength(1); - const newNote = result.preimages.newNotes[0]; + expect(result.newNotes).toHaveLength(1); + const newNote = result.newNotes[0]; expect(newNote.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm)); const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO)); @@ -549,8 +554,8 @@ describe('Private Execution test suite', () => { expect(newNullifiers).toHaveLength(consumedNotes.length); expect(newNullifiers).toEqual(expect.arrayContaining(consumedNotes.map(n => n.innerNullifier))); - expect(result.preimages.newNotes).toHaveLength(2); - const [changeNote, recipientNote] = result.preimages.newNotes; + expect(result.newNotes).toHaveLength(2); + const [changeNote, recipientNote] = result.newNotes; expect(recipientNote.storageSlot).toEqual(recipientStorageSlot); const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO)); @@ -593,8 +598,8 @@ describe('Private Execution test suite', () => { const newNullifiers = result.callStackItem.publicInputs.newNullifiers.filter(field => !field.equals(Fr.ZERO)); expect(newNullifiers).toEqual(consumedNotes.map(n => n.innerNullifier)); - expect(result.preimages.newNotes).toHaveLength(2); - const [changeNote, recipientNote] = result.preimages.newNotes; + expect(result.newNotes).toHaveLength(2); + const [changeNote, recipientNote] = result.newNotes; expect(recipientNote.preimage[0]).toEqual(new Fr(amountToTransfer)); expect(changeNote.preimage[0]).toEqual(new Fr(balance - amountToTransfer)); }); @@ -868,8 +873,8 @@ describe('Private Execution test suite', () => { contractAddress, }); - expect(result.preimages.newNotes).toHaveLength(1); - const note = result.preimages.newNotes[0]; + expect(result.newNotes).toHaveLength(1); + const note = result.newNotes[0]; expect(note.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm)); expect(note.preimage[0]).toEqual(new Fr(amountToTransfer)); @@ -934,8 +939,8 @@ describe('Private Execution test suite', () => { const execGetThenNullify = result.nestedExecutions[1]; const getNotesAfterNullify = result.nestedExecutions[2]; - expect(execInsert.preimages.newNotes).toHaveLength(1); - const note = execInsert.preimages.newNotes[0]; + expect(execInsert.newNotes).toHaveLength(1); + const note = execInsert.newNotes[0]; expect(note.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm)); expect(note.preimage[0]).toEqual(new Fr(amountToTransfer)); @@ -983,8 +988,8 @@ describe('Private Execution test suite', () => { contractAddress, }); - expect(result.preimages.newNotes).toHaveLength(1); - const note = result.preimages.newNotes[0]; + expect(result.newNotes).toHaveLength(1); + const note = result.newNotes[0]; expect(note.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm)); expect(note.preimage[0]).toEqual(new Fr(amountToTransfer)); diff --git a/yarn-project/acir-simulator/src/client/private_execution.ts b/yarn-project/acir-simulator/src/client/private_execution.ts index bc470ab2813..79b5afc1f19 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.ts @@ -26,13 +26,7 @@ import { toAcvmEnqueuePublicFunctionResult, } from '../acvm/index.js'; import { ExecutionError } from '../common/errors.js'; -import { - AcirSimulator, - ExecutionResult, - FunctionAbiWithDebugMetadata, - NewNoteData, - NewNullifierData, -} from '../index.js'; +import { AcirSimulator, ExecutionResult, FunctionAbiWithDebugMetadata } from '../index.js'; import { ClientTxExecutionContext } from './client_execution_context.js'; import { acvmFieldMessageToString, oracleDebugCallToFormattedStr } from './debug.js'; @@ -63,11 +57,9 @@ export class PrivateFunctionExecution { const acir = Buffer.from(this.abi.bytecode, 'base64'); const initialWitness = this.getInitialWitness(); - // TODO: Move to ClientTxExecutionContext. - const newNotePreimages: NewNoteData[] = []; - const newNullifiers: NewNullifierData[] = []; const nestedExecutionContexts: ExecutionResult[] = []; const enqueuedPublicFunctionCalls: PublicCallRequest[] = []; + // TODO: Move to ClientTxExecutionContext. const encryptedLogs = new FunctionL2Logs([]); const unencryptedLogs = new FunctionL2Logs([]); @@ -104,29 +96,11 @@ export class PrivateFunctionExecution { ), getRandomField: () => Promise.resolve(toACVMField(Fr.random())), notifyCreatedNote: ([storageSlot], preimage, [innerNoteHash]) => { - this.context.pushNewNote( - this.contractAddress, - fromACVMField(storageSlot), - preimage.map(f => fromACVMField(f)), - fromACVMField(innerNoteHash), - ); - - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1040): remove newNotePreimages - // as it is redundant with pendingNoteData. Consider renaming pendingNoteData->pendingNotePreimages. - newNotePreimages.push({ - storageSlot: fromACVMField(storageSlot), - preimage: preimage.map(f => fromACVMField(f)), - }); + this.context.handleNewNote(this.contractAddress, storageSlot, preimage, innerNoteHash); return Promise.resolve(ZERO_ACVM_FIELD); }, - notifyNullifiedNote: async ([slot], [nullifier], acvmPreimage, [innerNoteHash]) => { - newNullifiers.push({ - preimage: acvmPreimage.map(f => fromACVMField(f)), - storageSlot: fromACVMField(slot), - nullifier: fromACVMField(nullifier), - }); - await this.context.pushNewNullifier(fromACVMField(nullifier), this.contractAddress); - this.context.nullifyPendingNotes(fromACVMField(innerNoteHash), this.contractAddress, fromACVMField(slot)); + notifyNullifiedNote: async ([slot], [innerNullifier], [innerNoteHash]) => { + await this.context.handleNullifiedNote(this.contractAddress, slot, innerNullifier, innerNoteHash); return Promise.resolve(ZERO_ACVM_FIELD); }, callPrivateFunction: async ([acvmContractAddress], [acvmFunctionSelector], [acvmArgsHash]) => { @@ -226,7 +200,8 @@ export class PrivateFunctionExecution { this.log(`Returning from call to ${this.contractAddress.toString()}:${selector}`); - const readRequestPartialWitnesses = this.context.getReadRequestPartialWitnesses(); + const readRequestPartialWitnesses = this.context.getReadRequestPartialWitnesses(publicInputs.readRequests); + const newNotes = this.context.getNewNotes(); return { acir, @@ -234,10 +209,7 @@ export class PrivateFunctionExecution { callStackItem, returnValues, readRequestPartialWitnesses, - preimages: { - newNotes: newNotePreimages, - nullifiedNotes: newNullifiers, - }, + newNotes, vk: Buffer.from(this.abi.verificationKey!, 'hex'), nestedExecutions: nestedExecutionContexts, enqueuedPublicFunctionCalls, diff --git a/yarn-project/acir-simulator/src/client/simulator.ts b/yarn-project/acir-simulator/src/client/simulator.ts index 5c4948b56e3..5a7003c02a6 100644 --- a/yarn-project/acir-simulator/src/client/simulator.ts +++ b/yarn-project/acir-simulator/src/client/simulator.ts @@ -1,5 +1,4 @@ -import { CallContext, CircuitsWasm, FunctionData, MAX_NOTE_FIELDS_LENGTH, TxContext } from '@aztec/circuits.js'; -import { computeTxHash } from '@aztec/circuits.js/abis'; +import { CallContext, FunctionData, MAX_NOTE_FIELDS_LENGTH, TxContext } from '@aztec/circuits.js'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { ArrayType, FunctionSelector, FunctionType, encodeArguments } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; @@ -14,6 +13,7 @@ import { createSimulationError } from '../common/errors.js'; import { PackedArgsCache } from '../common/packed_args_cache.js'; import { ClientTxExecutionContext } from './client_execution_context.js'; import { DBOracle, FunctionAbiWithDebugMetadata } from './db_oracle.js'; +import { ExecutionNoteCache } from './execution_note_cache.js'; import { ExecutionResult } from './execution_result.js'; import { PrivateFunctionExecution } from './private_execution.js'; import { UnconstrainedFunctionExecution } from './unconstrained_execution.js'; @@ -83,15 +83,13 @@ export class AcirSimulator { request.functionData.isConstructor, ); - const wasm = await CircuitsWasm.get(); - const txNullifier = computeTxHash(wasm, request.toTxRequest()); const execution = new PrivateFunctionExecution( new ClientTxExecutionContext( this.db, - txNullifier, request.txContext, historicBlockData, await PackedArgsCache.create(request.packedArguments), + new ExecutionNoteCache(), ), entryPointABI, contractAddress, @@ -143,10 +141,10 @@ export class AcirSimulator { const execution = new UnconstrainedFunctionExecution( new ClientTxExecutionContext( this.db, - Fr.ZERO, TxContext.empty(), historicBlockData, await PackedArgsCache.create([]), + new ExecutionNoteCache(), ), entryPointABI, contractAddress, diff --git a/yarn-project/acir-simulator/src/client/unconstrained_execution.test.ts b/yarn-project/acir-simulator/src/client/unconstrained_execution.test.ts index fb77873200d..ec318a9f6a5 100644 --- a/yarn-project/acir-simulator/src/client/unconstrained_execution.test.ts +++ b/yarn-project/acir-simulator/src/client/unconstrained_execution.test.ts @@ -53,6 +53,7 @@ describe('Unconstrained Execution test suite', () => { nonce: Fr.random(), isSome: new Fr(1), preimage, + innerNoteHash: Fr.random(), siloedNullifier: Fr.random(), index: BigInt(index), })), diff --git a/yarn-project/aztec-rpc/src/database/note_spending_info_dao.ts b/yarn-project/aztec-rpc/src/database/note_spending_info_dao.ts index 1b29ef9e055..91936a4d6f1 100644 --- a/yarn-project/aztec-rpc/src/database/note_spending_info_dao.ts +++ b/yarn-project/aztec-rpc/src/database/note_spending_info_dao.ts @@ -24,6 +24,11 @@ export interface NoteSpendingInfoDao { * The preimage of the note, containing essential information about the note. */ notePreimage: NotePreimage; + /** + * Inner note hash of the note. This is customisable by the app circuit. + * We can use this value to compute siloedNoteHash and uniqueSiloedNoteHash. + */ + innerNoteHash: Fr; /** * The nullifier of the note (siloed by contract address). */ @@ -43,6 +48,7 @@ export const createRandomNoteSpendingInfoDao = ({ nonce = Fr.random(), storageSlot = Fr.random(), notePreimage = NotePreimage.random(), + innerNoteHash = Fr.random(), siloedNullifier = Fr.random(), index = Fr.random().value, publicKey = Point.random(), @@ -51,6 +57,7 @@ export const createRandomNoteSpendingInfoDao = ({ nonce, storageSlot, notePreimage, + innerNoteHash, siloedNullifier, index, publicKey, diff --git a/yarn-project/aztec-rpc/src/kernel_prover/kernel_prover.test.ts b/yarn-project/aztec-rpc/src/kernel_prover/kernel_prover.test.ts index 32dfd66d47d..7ca44bded92 100644 --- a/yarn-project/aztec-rpc/src/kernel_prover/kernel_prover.test.ts +++ b/yarn-project/aztec-rpc/src/kernel_prover/kernel_prover.test.ts @@ -58,7 +58,7 @@ describe('Kernel Prover', () => { callStackItem: new PrivateCallStackItem(AztecAddress.ZERO, fnName as any, publicInputs, false), nestedExecutions: (dependencies[fnName] || []).map(name => createExecutionResult(name)), vk: VerificationKey.makeFake().toBuffer(), - preimages: { newNotes: newNoteIndices.map(idx => notes[idx]), nullifiedNotes: [] }, + newNotes: newNoteIndices.map(idx => notes[idx]), // TODO(dbanks12): should test kernel prover with non-transient reads. // This will be necessary once kernel actually checks (attempts to match) transient reads. readRequestPartialWitnesses: Array.from({ length: MAX_READ_REQUESTS_PER_CALL }, () => diff --git a/yarn-project/aztec-rpc/src/kernel_prover/kernel_prover.ts b/yarn-project/aztec-rpc/src/kernel_prover/kernel_prover.ts index 48608944a68..a5573b3b3bf 100644 --- a/yarn-project/aztec-rpc/src/kernel_prover/kernel_prover.ts +++ b/yarn-project/aztec-rpc/src/kernel_prover/kernel_prover.ts @@ -246,12 +246,12 @@ export class KernelProver { private async getNewNotes(executionResult: ExecutionResult): Promise { const { callStackItem: { publicInputs }, - preimages, + newNotes, } = executionResult; const contractAddress = publicInputs.callContext.storageContractAddress; // Assuming that for each new commitment there's an output note added to the execution result. const newCommitments = await this.proofCreator.getSiloedCommitments(publicInputs); - return preimages.newNotes.map((data, i) => ({ + return newNotes.map((data, i) => ({ contractAddress, data, commitment: newCommitments[i], diff --git a/yarn-project/aztec-rpc/src/note_processor/note_processor.ts b/yarn-project/aztec-rpc/src/note_processor/note_processor.ts index 3e1e9556d4d..5395b32e12d 100644 --- a/yarn-project/aztec-rpc/src/note_processor/note_processor.ts +++ b/yarn-project/aztec-rpc/src/note_processor/note_processor.ts @@ -117,7 +117,7 @@ export class NoteProcessor { if (noteSpendingInfo) { // We have successfully decrypted the data. try { - const { index, nonce, siloedNullifier } = await this.findNoteIndexAndNullifier( + const { index, nonce, innerNoteHash, siloedNullifier } = await this.findNoteIndexAndNullifier( dataStartIndexForTx, newCommitments, newNullifiers[0], @@ -126,6 +126,7 @@ export class NoteProcessor { noteSpendingInfoDaos.push({ ...noteSpendingInfo, nonce, + innerNoteHash, siloedNullifier, index, publicKey: this.publicKey, diff --git a/yarn-project/aztec-rpc/src/simulator_oracle/index.ts b/yarn-project/aztec-rpc/src/simulator_oracle/index.ts index 6ff952648a3..cfdeccff424 100644 --- a/yarn-project/aztec-rpc/src/simulator_oracle/index.ts +++ b/yarn-project/aztec-rpc/src/simulator_oracle/index.ts @@ -54,15 +54,18 @@ export class SimulatorOracle implements DBOracle { async getNotes(contractAddress: AztecAddress, storageSlot: Fr) { const noteDaos = await this.db.getNoteSpendingInfo(contractAddress, storageSlot); - return noteDaos.map(({ contractAddress, storageSlot, nonce, notePreimage, siloedNullifier, index }) => ({ - contractAddress, - storageSlot, - nonce, - preimage: notePreimage.items, - siloedNullifier, - // RPC Client can use this index to get full MembershipWitness - index, - })); + return noteDaos.map( + ({ contractAddress, storageSlot, nonce, notePreimage, innerNoteHash, siloedNullifier, index }) => ({ + contractAddress, + storageSlot, + nonce, + preimage: notePreimage.items, + innerNoteHash, + siloedNullifier, + // RPC Client can use this index to get full MembershipWitness + index, + }), + ); } async getFunctionABI( diff --git a/yarn-project/circuits.js/src/abis/abis.ts b/yarn-project/circuits.js/src/abis/abis.ts index b9acda0c2de..887efe1289a 100644 --- a/yarn-project/circuits.js/src/abis/abis.ts +++ b/yarn-project/circuits.js/src/abis/abis.ts @@ -271,12 +271,12 @@ export function computeCommitmentNonce(wasm: IWasmModule, nullifierZero: Fr, com * A siloed commitment effectively namespaces a commitment to a specific contract. * @param wasm - A module providing low-level wasm access. * @param contract - The contract address - * @param uniqueCommitment - The commitment to silo. + * @param innerCommitment - The commitment to silo. * @returns A siloed commitment. */ -export function siloCommitment(wasm: IWasmModule, contract: AztecAddress, uniqueCommitment: Fr): Fr { +export function siloCommitment(wasm: IWasmModule, contract: AztecAddress, innerCommitment: Fr): Fr { wasm.call('pedersen__init'); - return abisSiloCommitment(wasm, contract, uniqueCommitment); + return abisSiloCommitment(wasm, contract, innerCommitment); } /** diff --git a/yarn-project/noir-libs/aztec-noir/src/note/lifecycle.nr b/yarn-project/noir-libs/aztec-noir/src/note/lifecycle.nr index 1e5f75f79b8..b6e530ed94f 100644 --- a/yarn-project/noir-libs/aztec-noir/src/note/lifecycle.nr +++ b/yarn-project/noir-libs/aztec-noir/src/note/lifecycle.nr @@ -59,9 +59,6 @@ fn destroy_note( let compute_nullifier = note_interface.compute_nullifier; nullifier = compute_nullifier(note); - let serialise = note_interface.serialise; - let preimage = serialise(note); - // We also need the note commitment corresponding to the "nullifier" let get_header = note_interface.get_header; let header = get_header(note); @@ -75,7 +72,7 @@ fn destroy_note( // TODO(1718): Can we reuse the note commitment computed in `compute_nullifier`? nullified_commitment = compute_inner_note_hash(note_interface, note); } - assert(notify_nullified_note(storage_slot, nullifier, preimage, nullified_commitment) == 0); + assert(notify_nullified_note(storage_slot, nullifier, nullified_commitment) == 0); context.push_new_nullifier(nullifier, nullified_commitment) } \ No newline at end of file diff --git a/yarn-project/noir-libs/aztec-noir/src/note/note_getter.nr b/yarn-project/noir-libs/aztec-noir/src/note/note_getter.nr index 6a107c873bd..ee407657547 100644 --- a/yarn-project/noir-libs/aztec-noir/src/note/note_getter.nr +++ b/yarn-project/noir-libs/aztec-noir/src/note/note_getter.nr @@ -123,22 +123,17 @@ fn get_notes( let opt_notes = get_notes_internal(storage_slot, note_interface, options); for i in 0..opt_notes.len() { let opt_note = opt_notes[i]; - let mut note_hash_for_read_request = 0; if opt_note.is_some() { let note = opt_note.unwrap_unchecked(); check_note_header(*context, storage_slot, note_interface, note); - note_hash_for_read_request = compute_note_hash_for_read_or_nullify(note_interface, note); + + let note_hash_for_read_request = compute_note_hash_for_read_or_nullify(note_interface, note); + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1410): test to ensure + // failure if malicious oracle injects 0 nonce here for a "pre-existing" note. + context.push_read_request(note_hash_for_read_request); }; - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1410): test to ensure - // failure if malicious oracle injects 0 nonce here for a "pre-existing" note. - context.push_read_request(note_hash_for_read_request); }; - - // TODO(#1660) - // Move it back to get_notes_internal and only make read request for selected notes. - let filter = options.filter; - let filter_args = options.filter_args; - filter(opt_notes, filter_args) + opt_notes } unconstrained fn get_note_internal( @@ -170,7 +165,7 @@ unconstrained fn get_notes_internal( let (num_selects, select_by, select_values, sort_by, sort_order) = flatten_options(options.selects, options.sorts); let placeholder_opt_notes = [Option::none(); MAX_READ_REQUESTS_PER_CALL]; let placeholder_fields = [0; GET_NOTES_ORACLE_RETURN_LENGTH]; - oracle::notes::get_notes( + let opt_notes = oracle::notes::get_notes( storage_slot, note_interface, num_selects, @@ -182,7 +177,11 @@ unconstrained fn get_notes_internal( options.offset, placeholder_opt_notes, placeholder_fields, - ) + ); + + let filter = options.filter; + let filter_args = options.filter_args; + filter(opt_notes, filter_args) } unconstrained fn view_notes( diff --git a/yarn-project/noir-libs/aztec-noir/src/oracle/notes.nr b/yarn-project/noir-libs/aztec-noir/src/oracle/notes.nr index e37ae3894cb..52d4a445389 100644 --- a/yarn-project/noir-libs/aztec-noir/src/oracle/notes.nr +++ b/yarn-project/noir-libs/aztec-noir/src/oracle/notes.nr @@ -24,17 +24,15 @@ unconstrained fn notify_created_note( fn notify_nullified_note_oracle( _storage_slot: Field, _nullifier: Field, - _preimage: [Field; N], _inner_note_hash: Field, ) -> Field {} unconstrained fn notify_nullified_note( storage_slot: Field, nullifier: Field, - preimage: [Field; N], inner_note_hash: Field, ) -> Field { - notify_nullified_note_oracle(storage_slot, nullifier, preimage, inner_note_hash) + notify_nullified_note_oracle(storage_slot, nullifier, inner_note_hash) } #[oracle(getNotes)]