Skip to content

Commit d526c2d

Browse files
committed
fix(pxe): always inject protocol nullifier in kernelless simulation
Block #24580's mechanical next-boundary re-graft (99cbb5d) pulled the PXE kernelless-simulation files from the v5 tip, which predates the always-inject protocol-nullifier scheme introduced by private#487 (4b4245f circuits, 0d643f4 PXE note nonces, d98ceba kernelless sim). That reverted ExecutionNoteCache and generateSimulatedProvingResult to the old conditional scheme, while the Noir kernels (with-kernels path) still always inject the protocol nullifier. The kernelless gas estimate therefore under-counted the protocol nullifier's DA gas (e.g. 128 vs 192) and note-hash nonces used the wrong generator, producing the e2e_kernelless_simulation mismatch and the related "hinted siloed note hash does not match nullified note hash" failures. Re-apply next's always-inject scheme in the kernelless path: - ExecutionNoteCache: the nonce generator and the tx first nullifier are always the protocol nullifier, and getAllNullifiers always prepends it (restores next's file verbatim; drops the vestigial usedProtocolNullifierForNonces flag and finish()). - generateSimulatedProvingResult: unconditionally unshift the protocol nullifier as the first non-revertible nullifier, mirroring the init kernel. - Drop the now-removed noteCache.finish() calls in the PXE simulator and the two TXE callers. Reproduces private#487 / d98ceba, lost in the v5 forward-port.
1 parent 090156f commit d526c2d

4 files changed

Lines changed: 12 additions & 37 deletions

File tree

yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,6 @@ export class ContractFunctionSimulator {
314314
);
315315
const simulatorTeardownTimer = new Timer();
316316

317-
noteCache.finish();
318317
const firstNullifierHint = noteCache.getNonceGenerator();
319318

320319
const publicCallRequests = collectNested([executionResult], r =>
@@ -657,12 +656,12 @@ export async function generateSimulatedProvingResult(
657656
siloedNullifiers,
658657
minRevertibleSideEffectCounter,
659658
);
659+
// The protocol nullifier is the nonce generator and the tx's first nullifier. It is injected by the
660+
// init kernel (at counter 1, index 0) rather than emitted by any private call, so it is absent from
661+
// the executed nullifiers above. Mirror the kernel here by prepending it as the first non-revertible
662+
// nullifier.
660663
const nonceGenerator = privateExecutionResult.firstNullifier;
661-
if (nonRevertibleNullifiers.length === 0) {
662-
nonRevertibleNullifiers.push(nonceGenerator);
663-
} else if (!nonRevertibleNullifiers[0].equals(nonceGenerator)) {
664-
throw new Error('The first non revertible nullifier should be equal to the nonce generator. This is a bug!');
665-
}
664+
nonRevertibleNullifiers.unshift(nonceGenerator);
666665

667666
if (isPrivateOnlyTx) {
668667
// We must make the note hashes unique by using the

yarn-project/pxe/src/contract_function_simulator/execution_note_cache.ts

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,6 @@ export class ExecutionNoteCache {
4343

4444
private inRevertiblePhase = false;
4545

46-
/**
47-
* Whether the protocol nullifier was used for nonce generation.
48-
* We don't need to use the protocol nullifier if a non-revertible nullifier is emitted.
49-
*/
50-
private usedProtocolNullifierForNonces: boolean | undefined;
51-
5246
constructor(private readonly protocolNullifier: Fr) {}
5347

5448
/**
@@ -64,12 +58,9 @@ export class ExecutionNoteCache {
6458
this.inRevertiblePhase = true;
6559
this.minRevertibleSideEffectCounter = minRevertibleSideEffectCounter;
6660

67-
const nullifiers = this.getEmittedNullifiers();
68-
// If there are no nullifiers emitted by private calls so far, we use the protocol nullifier as the nonce generator.
69-
// Note: There could still be nullifiers emitted after the counter is set, but those nullifiers are revertible, so
70-
// we don't want to use them as the nonce generator.
71-
this.usedProtocolNullifierForNonces = nullifiers.length === 0;
72-
const nonceGenerator = this.usedProtocolNullifierForNonces ? this.protocolNullifier : new Fr(nullifiers[0]);
61+
// The protocol nullifier is always the tx's first nullifier (injected by the kernel init), so it is the
62+
// nonce generator for every note hash.
63+
const nonceGenerator = this.protocolNullifier;
7364

7465
// The existing pending notes are all non-revertible.
7566
// They cannot be squashed by nullifiers emitted after minRevertibleSideEffectCounter is set.
@@ -102,14 +93,6 @@ export class ExecutionNoteCache {
10293
return sideEffectCounter >= this.minRevertibleSideEffectCounter;
10394
}
10495

105-
public finish() {
106-
// If we never entered the revertible phase, and there are no nullifiers emitted, we need to use the protocol
107-
// nullifier as the nonce generator.
108-
if (!this.inRevertiblePhase) {
109-
this.usedProtocolNullifierForNonces = this.getEmittedNullifiers().length === 0;
110-
}
111-
}
112-
11396
/**
11497
* Add a new note to cache.
11598
* @param note - New note created during execution.
@@ -223,19 +206,15 @@ export class ExecutionNoteCache {
223206
}
224207

225208
/**
226-
* @returns All nullifiers emitted by private calls in this transaction. If the protocol nullifier was used as the
227-
* nonce generator, it is injected as the first nullifier.
209+
* @returns All nullifiers of the transaction: the protocol nullifier (the kernel-injected first nullifier),
210+
* followed by those emitted by private calls.
228211
*/
229212
getAllNullifiers(): Fr[] {
230-
if (this.usedProtocolNullifierForNonces === undefined) {
231-
throw new Error('usedProtocolNullifierForNonces is not set yet. Call finish() to complete the transaction.');
232-
}
233-
const allNullifiers = this.getEmittedNullifiers();
234-
return [...(this.usedProtocolNullifierForNonces ? [this.protocolNullifier] : []), ...allNullifiers];
213+
return [this.protocolNullifier, ...this.getEmittedNullifiers()];
235214
}
236215

237216
getNonceGenerator(): Fr {
238-
return this.getAllNullifiers()[0];
217+
return this.protocolNullifier;
239218
}
240219

241220
#recordNullifier(contractAddress: AztecAddress, siloedNullifier: bigint) {

yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,6 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
543543
}),
544544
);
545545

546-
noteCache.finish();
547546
const nonceGenerator = noteCache.getNonceGenerator();
548547
result = new PrivateExecutionResult(executionResult, nonceGenerator, publicFunctionsCalldata);
549548
} catch (err) {

yarn-project/txe/src/utils/tx_effect_creation.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import { TxEffect, TxHash } from '@aztec/stdlib/tx';
77
export async function makeTxEffect(noteCache: ExecutionNoteCache, txBlockNumber: BlockNumber): Promise<TxEffect> {
88
const txEffect = TxEffect.empty();
99

10-
noteCache.finish();
11-
1210
const nonceGenerator = noteCache.getNonceGenerator();
1311
txEffect.noteHashes = await Promise.all(
1412
noteCache

0 commit comments

Comments
 (0)