Skip to content

Commit 271b060

Browse files
LeilaWangdbanks12
andauthored
fix: ensure_note_hash_exists (#2256)
Closes #1142 #1029 - Rename `oracle.getCommitment` to `oracle.checkNoteHashExists`. - Change `Set.assert_contains_and_remove(note, nonce)` to take a nonce in addition to a note. We calculate the inner note hash from the provided note. And although the nonce can be set to the header of the note, by making it a required parameter of this method makes it clearer that nonce is needed to check the existence of a note hash. (An example will be created in a later PR to show how a recipient can learn about the nonce if they don't have the encrypted data.) - Change the api on CommitmentDb to only return an index of a note hash: the index is all we need to know if a note hash exists, and to use it to get `readRequestMembershipWitnesses` later in the kernel prover. - (A tiny change that is not really related to this PR): We don't have to emit storage slot when notifying the simulator about a new nullifier. # 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). --------- Co-authored-by: David Banks <47112877+dbanks12@users.noreply.github.com>
1 parent f85db49 commit 271b060

File tree

27 files changed

+201
-326
lines changed

27 files changed

+201
-326
lines changed

yarn-project/acir-simulator/src/acvm/acvm.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type ORACLE_NAMES =
3838
| 'getSecretKey'
3939
| 'getNote'
4040
| 'getNotes'
41+
| 'checkNoteHashExists'
4142
| 'getRandomField'
4243
| 'notifyCreatedNote'
4344
| 'notifyNullifiedNote'
@@ -46,7 +47,6 @@ type ORACLE_NAMES =
4647
| 'enqueuePublicFunctionCall'
4748
| 'storageRead'
4849
| 'storageWrite'
49-
| 'getCommitment'
5050
| 'getL1ToL2Message'
5151
| 'getPortalContractAddress'
5252
| 'emitEncryptedLog'

yarn-project/acir-simulator/src/acvm/serialize.ts

+1-18
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from '@aztec/circuits.js';
1010
import { Fr } from '@aztec/foundation/fields';
1111

12-
import { CommitmentDataOracleInputs, MessageLoadOracleInputs } from '../client/db_oracle.js';
12+
import { MessageLoadOracleInputs } from '../client/db_oracle.js';
1313
import { ACVMField, toACVMField } from './acvm.js';
1414

1515
// Utilities to write TS classes to ACVM Field arrays
@@ -160,23 +160,6 @@ export function toAcvmL1ToL2MessageLoadOracleInputs(
160160
];
161161
}
162162

163-
/**
164-
* Converts the result of loading commitments to ACVM fields.
165-
* @param commitmentLoadOracleInputs - The result of loading messages to convert.
166-
* @param l1ToL2MessagesTreeRoot - The L1 to L2 messages tree root
167-
* @returns The Message Oracle Fields.
168-
*/
169-
export function toAcvmCommitmentLoadOracleInputs(
170-
messageLoadOracleInputs: CommitmentDataOracleInputs,
171-
l1ToL2MessagesTreeRoot: Fr,
172-
): ACVMField[] {
173-
return [
174-
toACVMField(messageLoadOracleInputs.commitment),
175-
toACVMField(messageLoadOracleInputs.index),
176-
toACVMField(l1ToL2MessagesTreeRoot),
177-
];
178-
}
179-
180163
/**
181164
* Inserts a list of ACVM fields to a witness.
182165
* @param witnessStartIndex - The index where to start inserting the fields.

yarn-project/acir-simulator/src/client/client_execution_context.ts

+37-28
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import { createDebugLogger } from '@aztec/foundation/log';
66

77
import {
88
ACVMField,
9+
ONE_ACVM_FIELD,
10+
ZERO_ACVM_FIELD,
911
fromACVMField,
1012
toACVMField,
11-
toAcvmCommitmentLoadOracleInputs,
1213
toAcvmL1ToL2MessageLoadOracleInputs,
1314
} from '../acvm/index.js';
1415
import { PackedArgsCache } from '../common/packed_args_cache.js';
@@ -156,7 +157,7 @@ export class ClientTxExecutionContext {
156157
// Nullified pending notes are already removed from the list.
157158
const pendingNotes = this.noteCache.getNotes(contractAddress, storageSlotField);
158159

159-
const pendingNullifiers = this.noteCache.getNullifiers(contractAddress, storageSlotField);
160+
const pendingNullifiers = this.noteCache.getNullifiers(contractAddress);
160161
const dbNotes = await this.db.getNotes(contractAddress, storageSlotField);
161162
const dbNotesFiltered = dbNotes.filter(n => !pendingNullifiers.has((n.siloedNullifier as Fr).value));
162163

@@ -253,23 +254,12 @@ export class ClientTxExecutionContext {
253254
* Adding a siloed nullifier into the current set of all pending nullifiers created
254255
* within the current transaction/execution.
255256
* @param contractAddress - The contract address.
256-
* @param storageSlot - The storage slot.
257257
* @param innerNullifier - The pending nullifier to add in the list (not yet siloed by contract address).
258258
* @param innerNoteHash - The inner note hash of the new note.
259259
* @param contractAddress - The contract address
260260
*/
261-
public async handleNullifiedNote(
262-
contractAddress: AztecAddress,
263-
storageSlot: ACVMField,
264-
innerNullifier: ACVMField,
265-
innerNoteHash: ACVMField,
266-
) {
267-
await this.noteCache.nullifyNote(
268-
contractAddress,
269-
fromACVMField(storageSlot),
270-
fromACVMField(innerNullifier),
271-
fromACVMField(innerNoteHash),
272-
);
261+
public async handleNullifiedNote(contractAddress: AztecAddress, innerNullifier: ACVMField, innerNoteHash: ACVMField) {
262+
await this.noteCache.nullifyNote(contractAddress, fromACVMField(innerNullifier), fromACVMField(innerNoteHash));
273263
}
274264

275265
/**
@@ -285,20 +275,39 @@ export class ClientTxExecutionContext {
285275
/**
286276
* Fetches a path to prove existence of a commitment in the db, given its contract side commitment (before silo).
287277
* @param contractAddress - The contract address.
288-
* @param commitment - The commitment.
289-
* @returns The commitment data.
278+
* @param nonce - The nonce of the note.
279+
* @param innerNoteHash - The inner note hash of the note.
280+
* @returns 1 if (persistent or transient) note hash exists, 0 otherwise. Value is in ACVMField form.
290281
*/
291-
public async getCommitment(contractAddress: AztecAddress, commitment: ACVMField) {
292-
const innerNoteHash = fromACVMField(commitment);
293-
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386): only works
294-
// for noteHashes/commitments created by public functions! Once public kernel or
295-
// base rollup circuit injects nonces, this can be used with commitments created by
296-
// private functions as well.
297-
const commitmentInputs = await this.db.getCommitmentOracle(contractAddress, innerNoteHash);
298-
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1029): support pending commitments here
282+
public async checkNoteHashExists(
283+
contractAddress: AztecAddress,
284+
nonce: ACVMField,
285+
innerNoteHash: ACVMField,
286+
): Promise<ACVMField> {
287+
const nonceField = fromACVMField(nonce);
288+
const innerNoteHashField = fromACVMField(innerNoteHash);
289+
if (nonceField.isZero()) {
290+
// If nonce is 0, we are looking for a new note created in this transaction.
291+
const exists = this.noteCache.checkNoteExists(contractAddress, innerNoteHashField);
292+
if (exists) {
293+
return ONE_ACVM_FIELD;
294+
}
295+
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386)
296+
// Currently it can also be a note created from public if nonce is 0.
297+
// If we can't find a matching new note, keep looking for the match from the notes created in previous transactions.
298+
}
299+
300+
// If nonce is zero, SHOULD only be able to reach this point if note was publicly created
299301
const wasm = await CircuitsWasm.get();
300-
const siloedNoteHash = siloCommitment(wasm, contractAddress, innerNoteHash);
301-
this.gotNotes.set(siloedNoteHash.value, commitmentInputs.index);
302-
return toAcvmCommitmentLoadOracleInputs(commitmentInputs, this.historicBlockData.privateDataTreeRoot);
302+
let noteHashToLookUp = siloCommitment(wasm, contractAddress, innerNoteHashField);
303+
if (!nonceField.isZero()) {
304+
noteHashToLookUp = computeUniqueCommitment(wasm, nonceField, noteHashToLookUp);
305+
}
306+
307+
const index = await this.db.getCommitmentIndex(noteHashToLookUp);
308+
if (index !== undefined) {
309+
this.gotNotes.set(noteHashToLookUp.value, index);
310+
}
311+
return index !== undefined ? ONE_ACVM_FIELD : ZERO_ACVM_FIELD;
303312
}
304313
}

yarn-project/acir-simulator/src/client/db_oracle.ts

-16
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,6 @@ export interface MessageLoadOracleInputs {
4545
index: bigint;
4646
}
4747

48-
/**
49-
* The format noir uses to get commitments.
50-
*/
51-
export interface CommitmentDataOracleInputs {
52-
/** The siloed commitment. */
53-
commitment: Fr;
54-
/**
55-
* The path in the merkle tree to the commitment.
56-
*/
57-
siblingPath: Fr[];
58-
/**
59-
* The index of the message commitment in the merkle tree.
60-
*/
61-
index: bigint;
62-
}
63-
6448
/**
6549
* A function ABI with optional debug metadata
6650
*/

yarn-project/acir-simulator/src/client/execution_note_cache.ts

+29-39
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,32 @@ import { Fr } from '@aztec/foundation/fields';
55

66
import { NoteData } from './db_oracle.js';
77

8-
/**
9-
* Generate a key with the given contract address and storage slot.
10-
* @param contractAddress - Contract address.
11-
* @param storageSlot - Storage slot.
12-
*/
13-
const generateKeyForContractStorageSlot = (contractAddress: AztecAddress, storageSlot: Fr) =>
14-
`${contractAddress.toShortString}${storageSlot}`;
15-
168
/**
179
* Data that's accessible by all the function calls in an execution.
1810
*/
1911
export class ExecutionNoteCache {
2012
/**
21-
* Notes created during execution.
22-
* The key of the mapping is a value representing a contract address and storage slot combination.
13+
* New notes created in this transaction.
14+
* This mapping maps from a contract address to the notes in the contract.
2315
*/
24-
private newNotes: Map<string, NoteData[]> = new Map();
16+
private newNotes: Map<bigint, NoteData[]> = new Map();
2517

2618
/**
2719
* The list of nullifiers created in this transaction.
28-
* The key of the mapping is a value representing a contract address and storage slot combination.
20+
* This mapping maps from a contract address to the nullifiers emitted from the contract.
2921
* The note which is nullified might be new or not (i.e., was generated in a previous transaction).
3022
* Note that their value (bigint representation) is used because Frs cannot be looked up in Sets.
3123
*/
32-
private nullifiers: Map<string, Set<bigint>> = new Map();
24+
private nullifiers: Map<bigint, Set<bigint>> = new Map();
3325

3426
/**
3527
* Add a new note to cache.
3628
* @param note - New note created during execution.
3729
*/
3830
public addNewNote(note: NoteData) {
39-
const key = generateKeyForContractStorageSlot(note.contractAddress, note.storageSlot);
40-
const notes = this.newNotes.get(key) ?? [];
31+
const notes = this.newNotes.get(note.contractAddress.toBigInt()) ?? [];
4132
notes.push(note);
42-
this.newNotes.set(key, notes);
33+
this.newNotes.set(note.contractAddress.toBigInt(), notes);
4334
}
4435

4536
/**
@@ -50,32 +41,22 @@ export class ExecutionNoteCache {
5041
* @param innerNoteHash - Inner note hash of the note. If this value equals EMPTY_NULLIFIED_COMMITMENT, it means the
5142
* note being nullified is from a previous transaction (and thus not a new note).
5243
*/
53-
public async nullifyNote(contractAddress: AztecAddress, storageSlot: Fr, innerNullifier: Fr, innerNoteHash: Fr) {
44+
public async nullifyNote(contractAddress: AztecAddress, innerNullifier: Fr, innerNoteHash: Fr) {
5445
const wasm = await CircuitsWasm.get();
5546
const siloedNullifier = siloNullifier(wasm, contractAddress, innerNullifier);
56-
const nullifiers = this.getNullifiers(contractAddress, storageSlot);
57-
if (nullifiers.has(siloedNullifier.value)) {
58-
throw new Error('Attemp to nullify the same note twice.');
59-
}
60-
47+
const nullifiers = this.getNullifiers(contractAddress);
6148
nullifiers.add(siloedNullifier.value);
62-
const key = generateKeyForContractStorageSlot(contractAddress, storageSlot);
63-
this.nullifiers.set(key, nullifiers);
49+
this.nullifiers.set(contractAddress.toBigInt(), nullifiers);
6450

6551
// Find and remove the matching new note if the emitted innerNoteHash is not empty.
6652
if (!innerNoteHash.equals(new Fr(EMPTY_NULLIFIED_COMMITMENT))) {
67-
const notes = this.newNotes.get(key) ?? [];
68-
/**
69-
* The identifier used to determine matching is the inner note hash value.
70-
* However, we adopt a defensive approach and ensure that the contract address
71-
* and storage slot do match.
72-
*/
53+
const notes = this.newNotes.get(contractAddress.toBigInt()) ?? [];
7354
const noteIndexToRemove = notes.findIndex(n => n.innerNoteHash.equals(innerNoteHash));
7455
if (noteIndexToRemove === -1) {
7556
throw new Error('Attemp to remove a pending note that does not exist.');
7657
}
7758
notes.splice(noteIndexToRemove, 1);
78-
this.newNotes.set(key, notes);
59+
this.newNotes.set(contractAddress.toBigInt(), notes);
7960
}
8061
}
8162

@@ -86,17 +67,26 @@ export class ExecutionNoteCache {
8667
* @param storageSlot - Storage slot of the notes.
8768
**/
8869
public getNotes(contractAddress: AztecAddress, storageSlot: Fr) {
89-
const key = generateKeyForContractStorageSlot(contractAddress, storageSlot);
90-
return this.newNotes.get(key) ?? [];
70+
const notes = this.newNotes.get(contractAddress.toBigInt()) ?? [];
71+
return notes.filter(n => n.storageSlot.equals(storageSlot));
9172
}
9273

9374
/**
94-
* Return all nullifiers of a storage slot.
95-
* @param contractAddress - Contract address of the notes.
96-
* @param storageSlot - Storage slot of the notes.
75+
* Check if a note exists in the newNotes array.
76+
* @param contractAddress - Contract address of the note.
77+
* @param storageSlot - Storage slot of the note.
78+
* @param innerNoteHash - Inner note hash of the note.
79+
**/
80+
public checkNoteExists(contractAddress: AztecAddress, innerNoteHash: Fr) {
81+
const notes = this.newNotes.get(contractAddress.toBigInt()) ?? [];
82+
return notes.some(n => n.innerNoteHash.equals(innerNoteHash));
83+
}
84+
85+
/**
86+
* Return all nullifiers emitted from a contract.
87+
* @param contractAddress - Address of the contract.
9788
*/
98-
public getNullifiers(contractAddress: AztecAddress, storageSlot: Fr): Set<bigint> {
99-
const key = generateKeyForContractStorageSlot(contractAddress, storageSlot);
100-
return this.nullifiers.get(key) || new Set();
89+
public getNullifiers(contractAddress: AztecAddress): Set<bigint> {
90+
return this.nullifiers.get(contractAddress.toBigInt()) ?? new Set();
10191
}
10292
}

yarn-project/acir-simulator/src/client/private_execution.test.ts

+4-28
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ describe('Private Execution test suite', () => {
365365
expect(changeNote.preimage[0]).toEqual(new Fr(balance - amountToTransfer));
366366
});
367367

368-
it('Should be able to claim a note by providing the correct secret', async () => {
368+
it('Should be able to claim a note by providing the correct secret and nonce', async () => {
369369
const amount = 100n;
370370
const secret = Fr.random();
371371
const abi = getFunctionAbi(PrivateTokenAirdropContractAbi, 'claim');
@@ -374,22 +374,11 @@ describe('Private Execution test suite', () => {
374374
const nonce = new Fr(1n);
375375
const customNoteHash = hash([toBufferBE(amount, 32), secret.toBuffer()]);
376376
const innerNoteHash = Fr.fromBuffer(hash([storageSlot.toBuffer(), customNoteHash]));
377-
378-
oracle.getNotes.mockResolvedValue([
379-
{
380-
contractAddress,
381-
storageSlot,
382-
nonce,
383-
preimage: [new Fr(amount), secret],
384-
innerNoteHash,
385-
siloedNullifier: new Fr(0),
386-
index: 1n,
387-
},
388-
]);
377+
oracle.getCommitmentIndex.mockResolvedValue(2n);
389378

390379
const result = await runSimulator({
391380
abi,
392-
args: [amount, secret, recipient],
381+
args: [amount, secret, recipient, nonce],
393382
});
394383

395384
// Check a nullifier has been inserted.
@@ -751,20 +740,7 @@ describe('Private Execution test suite', () => {
751740
const storageSlot = new Fr(2);
752741
const innerNoteHash = hash([storageSlot.toBuffer(), noteHash.toBuffer()]);
753742
const siloedNoteHash = siloCommitment(wasm, contractAddress, Fr.fromBuffer(innerNoteHash));
754-
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386): should insert
755-
// uniqueSiloedNoteHash in tree and that should be what is expected in Noir
756-
//const uniqueSiloedNoteHash = computeUniqueCommitment(wasm, nonce, Fr.fromBuffer(innerNoteHash));
757-
758-
const tree = await insertLeaves([siloedNoteHash]);
759-
760-
oracle.getCommitmentOracle.mockImplementation(async () => {
761-
// Check the calculated commitment is correct
762-
return Promise.resolve({
763-
commitment: siloedNoteHash,
764-
siblingPath: (await tree.getSiblingPath(0n, false)).toFieldArray(),
765-
index: 0n,
766-
});
767-
});
743+
oracle.getCommitmentIndex.mockResolvedValue(0n);
768744

769745
const result = await runSimulator({
770746
abi,

yarn-project/acir-simulator/src/client/private_execution.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,12 @@ export class PrivateFunctionExecution {
9999
this.context.handleNewNote(this.contractAddress, storageSlot, preimage, innerNoteHash);
100100
return Promise.resolve(ZERO_ACVM_FIELD);
101101
},
102-
notifyNullifiedNote: async ([slot], [innerNullifier], [innerNoteHash]) => {
103-
await this.context.handleNullifiedNote(this.contractAddress, slot, innerNullifier, innerNoteHash);
102+
notifyNullifiedNote: async ([innerNullifier], [innerNoteHash]) => {
103+
await this.context.handleNullifiedNote(this.contractAddress, innerNullifier, innerNoteHash);
104104
return Promise.resolve(ZERO_ACVM_FIELD);
105105
},
106+
checkNoteHashExists: ([nonce], [innerNoteHash]) =>
107+
this.context.checkNoteHashExists(this.contractAddress, nonce, innerNoteHash),
106108
callPrivateFunction: async ([acvmContractAddress], [acvmFunctionSelector], [acvmArgsHash]) => {
107109
const contractAddress = fromACVMField(acvmContractAddress);
108110
const functionSelector = fromACVMField(acvmFunctionSelector);
@@ -125,7 +127,6 @@ export class PrivateFunctionExecution {
125127
getL1ToL2Message: ([msgKey]) => {
126128
return this.context.getL1ToL2Message(fromACVMField(msgKey));
127129
},
128-
getCommitment: ([commitment]) => this.context.getCommitment(this.contractAddress, commitment),
129130
debugLog: (...args) => {
130131
this.log(oracleDebugCallToFormattedStr(args));
131132
return Promise.resolve(ZERO_ACVM_FIELD);

yarn-project/acir-simulator/src/client/unconstrained_execution.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,14 @@ export class UnconstrainedFunctionExecution {
7272
+offset,
7373
+returnSize,
7474
),
75+
checkNoteHashExists: ([nonce], [innerNoteHash]) =>
76+
this.context.checkNoteHashExists(this.contractAddress, nonce, innerNoteHash),
7577
getRandomField: () => Promise.resolve(toACVMField(Fr.random())),
7678
debugLog: (...params) => {
7779
this.log(oracleDebugCallToFormattedStr(params));
7880
return Promise.resolve(ZERO_ACVM_FIELD);
7981
},
8082
getL1ToL2Message: ([msgKey]) => this.context.getL1ToL2Message(fromACVMField(msgKey)),
81-
getCommitment: ([commitment]) => this.context.getCommitment(this.contractAddress, commitment),
8283
storageRead: async ([slot], [numberOfElements]) => {
8384
if (!aztecNode) {
8485
const errMsg = `Aztec node is undefined, cannot read storage`;

0 commit comments

Comments
 (0)