diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr index 442d6c2fdfbc..96930588e654 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -316,6 +316,11 @@ contract AvmTest { context.nullifier_exists(nullifier) } + #[aztec(public-vm)] + fn assert_nullifier_exists(nullifier: Field) { + assert(context.nullifier_exists(nullifier)); + } + // Use the standard context interface to emit a new nullifier #[aztec(public-vm)] fn emit_nullifier_and_check(nullifier: Field) { diff --git a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts index 2e9dfacae341..93255bc73fef 100644 --- a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts +++ b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts @@ -1,4 +1,5 @@ -import { AztecAddress, TxStatus, type Wallet } from '@aztec/aztec.js'; +import { AztecAddress, Fr, TxStatus, type Wallet } from '@aztec/aztec.js'; +import { siloNullifier } from '@aztec/circuits.js/hash'; import { AvmTestContract } from '@aztec/noir-contracts.js'; import { jest } from '@jest/globals'; @@ -51,9 +52,21 @@ describe('e2e_avm_simulator', () => { }); describe('Nullifiers', () => { - it('Emit and check', async () => { + // Nullifier will not be siloed. + it('Emit and check in the same tx', async () => { const tx = await avmContact.methods.emit_nullifier_and_check(123456).send().wait(); expect(tx.status).toEqual(TxStatus.MINED); }); + + // Nullifier will be siloed. + it('Emit and check in separate tx', async () => { + const nullifier = new Fr(123456); + let tx = await avmContact.methods.new_nullifier(nullifier).send().wait(); + expect(tx.status).toEqual(TxStatus.MINED); + + const siloedNullifier = siloNullifier(avmContact.address, nullifier); + tx = await avmContact.methods.assert_nullifier_exists(siloedNullifier).send().wait(); + expect(tx.status).toEqual(TxStatus.MINED); + }); }); }); diff --git a/yarn-project/simulator/src/avm/journal/nullifiers.ts b/yarn-project/simulator/src/avm/journal/nullifiers.ts index f8374f6f8a76..561c90e4dc65 100644 --- a/yarn-project/simulator/src/avm/journal/nullifiers.ts +++ b/yarn-project/simulator/src/avm/journal/nullifiers.ts @@ -1,4 +1,3 @@ -import { siloNullifier } from '@aztec/circuits.js/hash'; import { Fr } from '@aztec/foundation/fields'; import type { CommitmentsDB } from '../../index.js'; @@ -49,8 +48,8 @@ export class Nullifiers { // If the value is found in the database, it will be associated with a leaf index! let leafIndex: bigint | undefined = undefined; if (!existsAsPending) { - // silo the nullifier before checking for its existence in the host - leafIndex = await this.hostNullifiers.getNullifierIndex(siloNullifier(storageAddress, nullifier)); + // We don't silo the nullifier here, because if it's pending, it hasn't yet been siloed by the kernel. + leafIndex = await this.hostNullifiers.getNullifierIndex(nullifier); } const exists = existsAsPending || leafIndex !== undefined; leafIndex = leafIndex === undefined ? BigInt(0) : leafIndex; diff --git a/yarn-project/simulator/src/avm/temporary_executor_migration.ts b/yarn-project/simulator/src/avm/temporary_executor_migration.ts index 2d226c7de489..6869b0bc84dd 100644 --- a/yarn-project/simulator/src/avm/temporary_executor_migration.ts +++ b/yarn-project/simulator/src/avm/temporary_executor_migration.ts @@ -7,7 +7,7 @@ import { L2ToL1Message, type ReadRequest, SideEffect, - type SideEffectLinkedToNoteHash, + SideEffectLinkedToNoteHash, } from '@aztec/circuits.js'; import { Fr } from '@aztec/foundation/fields'; @@ -95,7 +95,9 @@ export function temporaryConvertAvmResults( const nestedExecutions: PublicExecutionResult[] = []; const nullifierReadRequests: ReadRequest[] = []; const nullifierNonExistentReadRequests: ReadRequest[] = []; - const newNullifiers: SideEffectLinkedToNoteHash[] = []; + const newNullifiers: SideEffectLinkedToNoteHash[] = newWorldState.newNullifiers.map( + (nullifier, i) => new SideEffectLinkedToNoteHash(nullifier.toField(), Fr.zero(), new Fr(i + 1)), + ); const unencryptedLogs = UnencryptedFunctionL2Logs.empty(); const newL2ToL1Messages = newWorldState.newL1Messages.map(() => L2ToL1Message.empty()); // TODO keep track of side effect counters diff --git a/yellow-paper/docs/public-vm/gen/_instruction-set.mdx b/yellow-paper/docs/public-vm/gen/_instruction-set.mdx index 3d6e36698256..ecd9bb40bbcf 100644 --- a/yellow-paper/docs/public-vm/gen/_instruction-set.mdx +++ b/yellow-paper/docs/public-vm/gen/_instruction-set.mdx @@ -1463,7 +1463,7 @@ Check whether a nullifier exists in the nullifier tree (including nullifiers fro - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - **Args**: - - **nullifierOffset**: memory offset of the unsiloed nullifier + - **nullifierOffset**: memory offset of the nullifier - **existsOffset**: memory offset specifying where to store operation's result (whether the nullifier exists) - **Expression**: @@ -1472,6 +1472,7 @@ Check whether a nullifier exists in the nullifier tree (including nullifiers fro ) M[existsOffset] = exists`} +- **Details**: The nullifier is expected to be _unsiloed_ if checking for nullifiers from the current transaction and _siloed_ if checking for nullifiers from previous transactions - **World State access tracing**: {`context.worldStateAccessTrace.nullifierChecks.append( @@ -1483,7 +1484,7 @@ M[existsOffset] = exists`} } )`} -- **Triggers downstream circuit operations**: Nullifier siloing (hash with storage contract address), nullifier tree membership check +- **Triggers downstream circuit operations**: Nullifier tree membership check - **Tag updates**: `T[existsOffset] = u8` - **Bit-size**: 88 diff --git a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js index 5f1202d70799..58e72853452c 100644 --- a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js +++ b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js @@ -911,7 +911,7 @@ context.worldStateAccessTrace.newNoteHashes.append( {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, ], "Args": [ - {"name": "nullifierOffset", "description": "memory offset of the unsiloed nullifier"}, + {"name": "nullifierOffset", "description": "memory offset of the nullifier"}, {"name": "existsOffset", "description": "memory offset specifying where to store operation's result (whether the nullifier exists)"}, ], "Expression": ` @@ -921,6 +921,7 @@ exists = context.worldState.nullifiers.has( M[existsOffset] = exists `, "Summary": "Check whether a nullifier exists in the nullifier tree (including nullifiers from earlier in the current transaction or from earlier in the current block)", + "Details": "The nullifier is expected to be _unsiloed_ if checking for nullifiers from the current transaction and _siloed_ if checking for nullifiers from previous transactions", "World State access tracing": ` context.worldStateAccessTrace.nullifierChecks.append( TracedNullifierCheck { @@ -931,7 +932,7 @@ context.worldStateAccessTrace.nullifierChecks.append( } ) `, - "Triggers downstream circuit operations": "Nullifier siloing (hash with storage contract address), nullifier tree membership check", + "Triggers downstream circuit operations": "Nullifier tree membership check", "Tag checks": "", "Tag updates": "`T[existsOffset] = u8`", },