From 29fdd6654ce7e3bdf4baee8960db2eaaa39748a1 Mon Sep 17 00:00:00 2001 From: Mitchell Tracy Date: Thu, 11 Jan 2024 09:16:53 -0500 Subject: [PATCH 1/4] 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 | 61 ++++++++++++++++--- yarn-project/types/src/l2_block_context.ts | 2 +- 6 files changed, 66 insertions(+), 34 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 868ee68823e6..3f9455dfe09d 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'; @@ -121,10 +121,6 @@ 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, - ); // 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; @@ -133,17 +129,15 @@ export class NoteProcessor { for (const logs of functionLogs.logs) { this.stats.seen++; const payload = L1NotePayload.fromEncryptedBuffer(logs, privateKey, curve); + const txHash = blockContext.getTxHash(indexOfTxInABlock); 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, @@ -160,7 +154,6 @@ export class NoteProcessor { payload.contractAddress, payload.storageSlot, txHash, - txNullifier, newCommitments, dataStartIndexForTx, ); @@ -254,8 +247,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 { @@ -264,7 +256,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 add855083a90..4230bd2431e2 100644 --- a/yarn-project/types/src/l2_block.ts +++ b/yarn-project/types/src/l2_block.ts @@ -25,6 +25,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. @@ -620,13 +621,7 @@ export class L2Block { * @returns The tx. */ getTx(txIndex: number) { - if (txIndex >= this.numberOfTxs) { - throw new Error( - `Failed to get tx ${txIndex}. Block ${this.header.globalVariables.blockNumber.toBigInt()} 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)) @@ -659,6 +654,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. @@ -690,6 +701,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. @@ -765,3 +786,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.`); + } +} diff --git a/yarn-project/types/src/l2_block_context.ts b/yarn-project/types/src/l2_block_context.ts index 338d81022755..a8a7b77915fa 100644 --- a/yarn-project/types/src/l2_block_context.ts +++ b/yarn-project/types/src/l2_block_context.ts @@ -20,7 +20,7 @@ export class L2BlockContext { * @returns The tx's hash. */ public getTxHash(txIndex: number): TxHash { - return this.txHashes ? this.txHashes[txIndex] : this.block.getTx(txIndex).txHash; + return this.txHashes ? this.txHashes[txIndex] : this.block.getTxHash(txIndex); } /** From 03e44d274868c11c05e3c878011e93732462f1e0 Mon Sep 17 00:00:00 2001 From: Mitchell Tracy Date: Thu, 11 Jan 2024 09:30:30 -0500 Subject: [PATCH 2/4] nit: only grab the txHash if we decrypt the payload --- yarn-project/pxe/src/note_processor/note_processor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/pxe/src/note_processor/note_processor.ts b/yarn-project/pxe/src/note_processor/note_processor.ts index 3f9455dfe09d..7c37f8644fc9 100644 --- a/yarn-project/pxe/src/note_processor/note_processor.ts +++ b/yarn-project/pxe/src/note_processor/note_processor.ts @@ -129,9 +129,9 @@ export class NoteProcessor { for (const logs of functionLogs.logs) { this.stats.seen++; const payload = L1NotePayload.fromEncryptedBuffer(logs, privateKey, curve); - const txHash = blockContext.getTxHash(indexOfTxInABlock); if (payload) { // We have successfully decrypted the data. + const txHash = blockContext.getTxHash(indexOfTxInABlock); try { const noteDao = await produceNoteDao( this.simulator, From ea5f606d217cfdf89b4d0fc2655f2d9ef72d4481 Mon Sep 17 00:00:00 2001 From: Mitchell Tracy Date: Thu, 11 Jan 2024 09:42:00 -0500 Subject: [PATCH 3/4] pr feedback --- yarn-project/types/src/l2_block.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/yarn-project/types/src/l2_block.ts b/yarn-project/types/src/l2_block.ts index 4230bd2431e2..834f9172969c 100644 --- a/yarn-project/types/src/l2_block.ts +++ b/yarn-project/types/src/l2_block.ts @@ -662,10 +662,7 @@ export class L2Block { 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]; + const firstNullifier = this.newNullifiers[txIndex * MAX_NEW_NULLIFIERS_PER_TX]; return new TxHash(firstNullifier.toBuffer()); } @@ -702,7 +699,7 @@ export class L2Block { } assertIndexInRange(txIndex: number) { - if (txIndex >= this.numberOfTxs) { + if (txIndex < 0 || txIndex >= this.numberOfTxs) { throw new IndexOutOfRangeError({ txIndex, numberOfTxs: this.numberOfTxs, @@ -809,6 +806,6 @@ export class IndexOutOfRangeError extends Error { */ blockNumber: number; }) { - super(`IndexOutOfRangeError: Failed to get tx ${txIndex}. Block ${blockNumber} only has ${numberOfTxs} txs.`); + super(`IndexOutOfRangeError: Failed to get tx at index ${txIndex}. Block ${blockNumber} has ${numberOfTxs} txs.`); } } From 34e516e8500f1c850a883d862311a3da0c350c5f Mon Sep 17 00:00:00 2001 From: Mitchell Tracy Date: Thu, 11 Jan 2024 10:41:38 -0500 Subject: [PATCH 4/4] rename variable --- yarn-project/pxe/src/note_processor/produce_note_dao.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 bc11f4715ad9..906948ef686e 100644 --- a/yarn-project/pxe/src/note_processor/produce_note_dao.ts +++ b/yarn-project/pxe/src/note_processor/produce_note_dao.ts @@ -79,7 +79,7 @@ async function findNoteIndexAndNullifier( let siloedNoteHash: Fr | undefined; let uniqueSiloedNoteHash: Fr | undefined; let innerNullifier: Fr | undefined; - const txNullifier = Fr.fromBuffer(txHash.toBuffer()); + const firstNullifier = Fr.fromBuffer(txHash.toBuffer()); for (; commitmentIndex < commitments.length; ++commitmentIndex) { if (excludedIndices.has(commitmentIndex)) { @@ -91,7 +91,7 @@ async function findNoteIndexAndNullifier( break; } - const expectedNonce = computeCommitmentNonce(txNullifier, commitmentIndex); + const expectedNonce = computeCommitmentNonce(firstNullifier, commitmentIndex); ({ innerNoteHash, siloedNoteHash, uniqueSiloedNoteHash, innerNullifier } = await simulator.computeNoteHashAndNullifier(contractAddress, expectedNonce, storageSlot, note)); if (commitment.equals(uniqueSiloedNoteHash)) {