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`",
},