From 72641c878f24589dc047f968b588c0985ee938c2 Mon Sep 17 00:00:00 2001 From: Mitchell Tracy Date: Thu, 11 Jan 2024 07:58:00 -0500 Subject: [PATCH] do not pass redundant txNullifier when computing notes --- .../src/database/deferred_note_dao.test.ts | 2 - .../pxe/src/database/deferred_note_dao.ts | 6 +- .../pxe/src/note_processor/note_processor.ts | 15 +---- .../src/note_processor/produce_note_dao.ts | 14 ++--- yarn-project/types/src/l2_block.ts | 59 +++++++++++++++++-- 5 files changed, 65 insertions(+), 31 deletions(-) diff --git a/yarn-project/pxe/src/database/deferred_note_dao.test.ts b/yarn-project/pxe/src/database/deferred_note_dao.test.ts index 2eb6201c0b12..51f4ff9112f9 100644 --- a/yarn-project/pxe/src/database/deferred_note_dao.test.ts +++ b/yarn-project/pxe/src/database/deferred_note_dao.test.ts @@ -9,7 +9,6 @@ export const randomDeferredNoteDao = ({ contractAddress = AztecAddress.random(), txHash = randomTxHash(), storageSlot = Fr.random(), - txNullifier = Fr.random(), newCommitments = [Fr.random(), Fr.random()], dataStartIndexForTx = Math.floor(Math.random() * 100), }: Partial = {}) => { @@ -19,7 +18,6 @@ export const randomDeferredNoteDao = ({ contractAddress, storageSlot, txHash, - txNullifier, newCommitments, dataStartIndexForTx, ); diff --git a/yarn-project/pxe/src/database/deferred_note_dao.ts b/yarn-project/pxe/src/database/deferred_note_dao.ts index 034a192daad3..58e51aa6016f 100644 --- a/yarn-project/pxe/src/database/deferred_note_dao.ts +++ b/yarn-project/pxe/src/database/deferred_note_dao.ts @@ -17,10 +17,8 @@ export class DeferredNoteDao { public contractAddress: AztecAddress, /** The specific storage location of the note on the contract. */ public storageSlot: Fr, - /** The hash of the tx the note was created in. */ + /** The hash of the tx the note was created in. Equal to the first nullifier */ public txHash: TxHash, - /** The first nullifier emitted by the transaction */ - public txNullifier: Fr, /** New commitments in this transaction, one of which belongs to this note */ public newCommitments: Fr[], /** The next available leaf index for the note hash tree for this transaction */ @@ -34,7 +32,6 @@ export class DeferredNoteDao { this.contractAddress.toBuffer(), this.storageSlot.toBuffer(), this.txHash.toBuffer(), - this.txNullifier.toBuffer(), new Vector(this.newCommitments), this.dataStartIndexForTx, ); @@ -47,7 +44,6 @@ export class DeferredNoteDao { reader.readObject(AztecAddress), reader.readObject(Fr), reader.readObject(TxHash), - reader.readObject(Fr), reader.readVector(Fr), reader.readNumber(), ); diff --git a/yarn-project/pxe/src/note_processor/note_processor.ts b/yarn-project/pxe/src/note_processor/note_processor.ts index a4ccdebb848f..57de06c4aefd 100644 --- a/yarn-project/pxe/src/note_processor/note_processor.ts +++ b/yarn-project/pxe/src/note_processor/note_processor.ts @@ -1,5 +1,5 @@ import { ContractNotFoundError } from '@aztec/acir-simulator'; -import { MAX_NEW_COMMITMENTS_PER_TX, MAX_NEW_NULLIFIERS_PER_TX, PublicKey } from '@aztec/circuits.js'; +import { MAX_NEW_COMMITMENTS_PER_TX, PublicKey } from '@aztec/circuits.js'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -120,10 +120,7 @@ export class NoteProcessor { indexOfTxInABlock * MAX_NEW_COMMITMENTS_PER_TX, (indexOfTxInABlock + 1) * MAX_NEW_COMMITMENTS_PER_TX, ); - const newNullifiers = block.newNullifiers.slice( - indexOfTxInABlock * MAX_NEW_NULLIFIERS_PER_TX, - (indexOfTxInABlock + 1) * MAX_NEW_NULLIFIERS_PER_TX, - ); + const txHash = block.getTxHash(indexOfTxInABlock); // Note: Each tx generates a `TxL2Logs` object and for this reason we can rely on its index corresponding // to the index of a tx in a block. const txFunctionLogs = txLogs[indexOfTxInABlock].functionLogs; @@ -134,15 +131,12 @@ export class NoteProcessor { const payload = L1NotePayload.fromEncryptedBuffer(logs, privateKey, curve); if (payload) { // We have successfully decrypted the data. - const txHash = blockContext.getTxHash(indexOfTxInABlock); - const txNullifier = newNullifiers[0]; try { const noteDao = await produceNoteDao( this.simulator, this.publicKey, payload, txHash, - txNullifier, newCommitments, dataStartIndexForTx, excludedIndices, @@ -159,7 +153,6 @@ export class NoteProcessor { payload.contractAddress, payload.storageSlot, txHash, - txNullifier, newCommitments, dataStartIndexForTx, ); @@ -253,8 +246,7 @@ export class NoteProcessor { const excludedIndices: Set = new Set(); const noteDaos: NoteDao[] = []; for (const deferredNote of deferredNoteDaos) { - const { note, contractAddress, storageSlot, txHash, txNullifier, newCommitments, dataStartIndexForTx } = - deferredNote; + const { note, contractAddress, storageSlot, txHash, newCommitments, dataStartIndexForTx } = deferredNote; const payload = new L1NotePayload(note, contractAddress, storageSlot); try { @@ -263,7 +255,6 @@ export class NoteProcessor { this.publicKey, payload, txHash, - txNullifier, newCommitments, dataStartIndexForTx, excludedIndices, diff --git a/yarn-project/pxe/src/note_processor/produce_note_dao.ts b/yarn-project/pxe/src/note_processor/produce_note_dao.ts index 4a2dbec277ac..bc11f4715ad9 100644 --- a/yarn-project/pxe/src/note_processor/produce_note_dao.ts +++ b/yarn-project/pxe/src/note_processor/produce_note_dao.ts @@ -13,8 +13,7 @@ import { NoteDao } from '../database/note_dao.js'; * * @param publicKey - The public counterpart to the private key to be used in note decryption. * @param payload - An instance of l1NotePayload. - * @param txHash - The hash of the transaction that created the note. - * @param txNullifier - The first nullifier emitted by the transaction. + * @param txHash - The hash of the transaction that created the note. Equivalent to the first nullifier of the transaction. * @param newCommitments - New commitments in this transaction, one of which belongs to this note. * @param dataStartIndexForTx - The next available leaf index for the note hash tree for this transaction. * @param excludedIndices - Indices that have been assigned a note in the same tx. Notes in a tx can have the same l1NotePayload, we need to find a different index for each replicate. @@ -26,7 +25,6 @@ export async function produceNoteDao( publicKey: PublicKey, payload: L1NotePayload, txHash: TxHash, - txNullifier: Fr, newCommitments: Fr[], dataStartIndexForTx: number, excludedIndices: Set, @@ -34,7 +32,7 @@ export async function produceNoteDao( const { commitmentIndex, nonce, innerNoteHash, siloedNullifier } = await findNoteIndexAndNullifier( simulator, newCommitments, - txNullifier, + txHash, payload, excludedIndices, ); @@ -61,7 +59,7 @@ export async function produceNoteDao( * contract address, and the note associated with the l1NotePayload. * This method assists in identifying spent commitments in the private state. * @param commitments - Commitments in the tx. One of them should be the note's commitment. - * @param firstNullifier - First nullifier in the tx. + * @param txHash - First nullifier in the tx. * @param l1NotePayload - An instance of l1NotePayload. * @param excludedIndices - Indices that have been assigned a note in the same tx. Notes in a tx can have the same * l1NotePayload. We need to find a different index for each replicate. @@ -71,7 +69,7 @@ export async function produceNoteDao( async function findNoteIndexAndNullifier( simulator: AcirSimulator, commitments: Fr[], - firstNullifier: Fr, + txHash: TxHash, { contractAddress, storageSlot, note }: L1NotePayload, excludedIndices: Set, ) { @@ -81,6 +79,8 @@ async function findNoteIndexAndNullifier( let siloedNoteHash: Fr | undefined; let uniqueSiloedNoteHash: Fr | undefined; let innerNullifier: Fr | undefined; + const txNullifier = Fr.fromBuffer(txHash.toBuffer()); + for (; commitmentIndex < commitments.length; ++commitmentIndex) { if (excludedIndices.has(commitmentIndex)) { continue; @@ -91,7 +91,7 @@ async function findNoteIndexAndNullifier( break; } - const expectedNonce = computeCommitmentNonce(firstNullifier, commitmentIndex); + const expectedNonce = computeCommitmentNonce(txNullifier, commitmentIndex); ({ innerNoteHash, siloedNoteHash, uniqueSiloedNoteHash, innerNullifier } = await simulator.computeNoteHashAndNullifier(contractAddress, expectedNonce, storageSlot, note)); if (commitment.equals(uniqueSiloedNoteHash)) { diff --git a/yarn-project/types/src/l2_block.ts b/yarn-project/types/src/l2_block.ts index a8caf012445e..8fe63d6ec3d2 100644 --- a/yarn-project/types/src/l2_block.ts +++ b/yarn-project/types/src/l2_block.ts @@ -22,6 +22,7 @@ import { L2Tx } from './l2_tx.js'; import { LogType, TxL2Logs } from './logs/index.js'; import { L2BlockL2Logs } from './logs/l2_block_l2_logs.js'; import { PublicDataWrite } from './public_data_write.js'; +import { TxHash } from './tx/tx_hash.js'; /** * The data that makes up the rollup proof, with encoder decoder functions. @@ -734,11 +735,7 @@ export class L2Block { * @returns The tx. */ getTx(txIndex: number) { - if (txIndex >= this.numberOfTxs) { - throw new Error( - `Failed to get tx ${txIndex}. Block ${this.globalVariables.blockNumber} only has ${this.numberOfTxs} txs.`, - ); - } + this.assertIndexInRange(txIndex); const newCommitments = this.newCommitments .slice(MAX_NEW_COMMITMENTS_PER_TX * txIndex, MAX_NEW_COMMITMENTS_PER_TX * (txIndex + 1)) @@ -771,6 +768,22 @@ export class L2Block { ); } + /** + * A lightweight method to get the tx hash of a tx in the block. + * @param txIndex - the index of the tx in the block + * @returns a hash of the tx, which is the first nullifier in the tx + */ + getTxHash(txIndex: number): TxHash { + this.assertIndexInRange(txIndex); + + const firstNullifier = this.newNullifiers.slice( + txIndex * MAX_NEW_NULLIFIERS_PER_TX, + (txIndex + 1) * MAX_NEW_NULLIFIERS_PER_TX, + )[0]; + + return new TxHash(firstNullifier.toBuffer()); + } + /** * Get all the transaction in an L2 block. * @returns The tx. @@ -802,6 +815,16 @@ export class L2Block { }; } + assertIndexInRange(txIndex: number) { + if (txIndex >= this.numberOfTxs) { + throw new IndexOutOfRangeError({ + txIndex, + numberOfTxs: this.numberOfTxs, + blockNumber: this.number, + }); + } + } + /** * Inspect for debugging purposes.. * @param maxBufferSize - The number of bytes to be extracted from buffer. @@ -877,3 +900,29 @@ export class L2Block { return kernelPublicInputsLogsHash; } } + +/** + * Custom error class for when a requested tx index is out of range. + */ +export class IndexOutOfRangeError extends Error { + constructor({ + txIndex, + numberOfTxs, + blockNumber, + }: { + /** + * The requested index of the tx in the block. + */ + txIndex: number; + /** + * The number of txs in the block. + */ + numberOfTxs: number; + /** + * The number of the block. + */ + blockNumber: number; + }) { + super(`IndexOutOfRangeError: Failed to get tx ${txIndex}. Block ${blockNumber} only has ${numberOfTxs} txs.`); + } +}