diff --git a/.circleci/config.yml b/.circleci/config.yml index fc0c098126e..06d72664280 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -676,6 +676,17 @@ jobs: name: "Test" command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_escrow_contract.test.ts + e2e-inclusion-proofs-contract: + docker: + - image: aztecprotocol/alpine-build-image + resource_class: small + steps: + - *checkout + - *setup_env + - run: + name: "Test" + command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_inclusion_proofs_contract.test.ts + e2e-pending-commitments-contract: docker: - image: aztecprotocol/alpine-build-image @@ -1113,6 +1124,7 @@ workflows: - e2e-public-to-private-messaging: *e2e_test - e2e-account-contracts: *e2e_test - e2e-escrow-contract: *e2e_test + - e2e-inclusion-proofs-contract: *e2e_test - e2e-pending-commitments-contract: *e2e_test - e2e-ordering: *e2e_test - uniswap-trade-on-l1-from-l2: *e2e_test @@ -1148,6 +1160,7 @@ workflows: - e2e-public-to-private-messaging - e2e-account-contracts - e2e-escrow-contract + - e2e-inclusion-proofs-contract - e2e-pending-commitments-contract - e2e-ordering - uniswap-trade-on-l1-from-l2 diff --git a/circuits/cpp/src/aztec3/circuits/abis/historic_block_data.hpp b/circuits/cpp/src/aztec3/circuits/abis/historic_block_data.hpp index d66ddf1a21a..45161128c06 100644 --- a/circuits/cpp/src/aztec3/circuits/abis/historic_block_data.hpp +++ b/circuits/cpp/src/aztec3/circuits/abis/historic_block_data.hpp @@ -118,7 +118,7 @@ template struct HistoricBlockData { nullifier_tree_root, contract_tree_root, l1_to_l2_messages_tree_root, - blocks_tree_root, // Note private_kernel_vk_tree_root, is not included yet as + blocks_tree_root, // TODO(#3441) Note private_kernel_vk_tree_root, is not included yet as // it is not present in noir, public_data_tree_root, global_variables_hash }; diff --git a/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts b/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts index 443b1cda7bb..ebc8ca180f1 100644 --- a/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts @@ -4,10 +4,10 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { padArrayEnd } from '@aztec/foundation/collection'; import { Fr, Point } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; -import { UnencryptedL2Log } from '@aztec/types'; +import { MerkleTreeId, UnencryptedL2Log } from '@aztec/types'; import { ACVMField } from '../acvm_types.js'; -import { fromACVMField } from '../deserialize.js'; +import { frToNumber, fromACVMField } from '../deserialize.js'; import { toACVMField, toAcvmCallPrivateStackItem, @@ -46,6 +46,79 @@ export class Oracle { return [publicKey.x, publicKey.y, partialAddress].map(toACVMField); } + async getMembershipWitness( + [blockNumber]: ACVMField[], + [treeId]: ACVMField[], + [leafValue]: ACVMField[], + ): Promise { + const parsedBlockNumber = frToNumber(fromACVMField(blockNumber)); + const parsedTreeId = frToNumber(fromACVMField(treeId)); + const parsedLeafValue = fromACVMField(leafValue); + + const witness = await this.typedOracle.getMembershipWitness(parsedBlockNumber, parsedTreeId, parsedLeafValue); + if (!witness) { + throw new Error( + `Leaf ${leafValue} not found in the tree ${MerkleTreeId[parsedTreeId]} at block ${parsedBlockNumber}.`, + ); + } + return witness.map(toACVMField); + } + + async getSiblingPath( + [blockNumber]: ACVMField[], + [treeId]: ACVMField[], + [leafIndex]: ACVMField[], + ): Promise { + const parsedBlockNumber = frToNumber(fromACVMField(blockNumber)); + const parsedTreeId = frToNumber(fromACVMField(treeId)); + const parsedLeafIndex = fromACVMField(leafIndex); + + const path = await this.typedOracle.getSiblingPath(parsedBlockNumber, parsedTreeId, parsedLeafIndex); + return path.map(toACVMField); + } + + async getNullifierMembershipWitness( + [blockNumber]: ACVMField[], + [nullifier]: ACVMField[], // nullifier, we try to find the witness for (to prove inclusion) + ): Promise { + const parsedBlockNumber = frToNumber(fromACVMField(blockNumber)); + const parsedNullifier = fromACVMField(nullifier); + + const witness = await this.typedOracle.getNullifierMembershipWitness(parsedBlockNumber, parsedNullifier); + if (!witness) { + throw new Error( + `Low nullifier witness not found for nullifier ${parsedNullifier} at block ${parsedBlockNumber}.`, + ); + } + return witness.toFieldArray().map(toACVMField); + } + + async getLowNullifierMembershipWitness( + [blockNumber]: ACVMField[], + [nullifier]: ACVMField[], // nullifier, we try to find the low nullifier witness for (to prove non-inclusion) + ): Promise { + const parsedBlockNumber = frToNumber(fromACVMField(blockNumber)); + const parsedNullifier = fromACVMField(nullifier); + + const witness = await this.typedOracle.getLowNullifierMembershipWitness(parsedBlockNumber, parsedNullifier); + if (!witness) { + throw new Error( + `Low nullifier witness not found for nullifier ${parsedNullifier} at block ${parsedBlockNumber}.`, + ); + } + return witness.toFieldArray().map(toACVMField); + } + + async getBlockData([blockNumber]: ACVMField[]): Promise { + const parsedBlockNumber = frToNumber(fromACVMField(blockNumber)); + + const blockData = await this.typedOracle.getBlockData(parsedBlockNumber); + if (!blockData) { + throw new Error(`Block data not found for block ${parsedBlockNumber}.`); + } + return blockData.toArray().map(toACVMField); + } + async getAuthWitness([messageHash]: ACVMField[]): Promise { const messageHashField = fromACVMField(messageHash); const witness = await this.typedOracle.getAuthWitness(messageHashField); diff --git a/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts index 0dfbd9affcd..9138009309c 100644 --- a/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts @@ -1,9 +1,16 @@ -import { PrivateCallStackItem, PublicCallRequest } from '@aztec/circuits.js'; +import { HistoricBlockData, PrivateCallStackItem, PublicCallRequest } from '@aztec/circuits.js'; import { FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr, GrumpkinScalar } from '@aztec/foundation/fields'; -import { CompleteAddress, Note, PublicKey, UnencryptedL2Log } from '@aztec/types'; +import { + CompleteAddress, + MerkleTreeId, + Note, + NullifierMembershipWitness, + PublicKey, + UnencryptedL2Log, +} from '@aztec/types'; /** * Information about a note needed during execution. @@ -72,6 +79,33 @@ export abstract class TypedOracle { throw new Error('Not available.'); } + getPublicKeyAndPartialAddress(_address: AztecAddress): Promise { + throw new Error('Not available.'); + } + + getMembershipWitness(_blockNumber: number, _treeId: MerkleTreeId, _leafValue: Fr): Promise { + throw new Error('Not available.'); + } + + getSiblingPath(_blockNumber: number, _treeId: MerkleTreeId, _leafIndex: Fr): Promise { + throw new Error('Not available.'); + } + + getNullifierMembershipWitness(_blockNumber: number, _nullifier: Fr): Promise { + throw new Error('Not available.'); + } + + getLowNullifierMembershipWitness( + _blockNumber: number, + _nullifier: Fr, + ): Promise { + throw new Error('Not available.'); + } + + getBlockData(_blockNumber: number): Promise { + throw new Error('Not available.'); + } + getCompleteAddress(_address: AztecAddress): Promise { throw new Error('Not available.'); } diff --git a/yarn-project/acir-simulator/src/client/db_oracle.ts b/yarn-project/acir-simulator/src/client/db_oracle.ts index 7b61a60e6bd..ad2cb1a19c4 100644 --- a/yarn-project/acir-simulator/src/client/db_oracle.ts +++ b/yarn-project/acir-simulator/src/client/db_oracle.ts @@ -3,6 +3,7 @@ import { FunctionArtifact, FunctionDebugMetadata, FunctionSelector } from '@azte import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; +import { L2Block, MerkleTreeId, NullifierMembershipWitness } from '@aztec/types'; import { NoteData } from '../acvm/index.js'; import { CommitmentsDB } from '../public/index.js'; @@ -114,4 +115,48 @@ export interface DBOracle extends CommitmentsDB { * @returns A Promise that resolves to a HistoricBlockData object. */ getHistoricBlockData(): Promise; + + /** + * Fetch the index of the leaf in the respective tree + * @param blockNumber - The block number at which to get the leaf index. + * @param treeId - The id of the tree to search. + * @param leafValue - The leaf value buffer. + * @returns - The index of the leaf. Undefined if it does not exist in the tree. + */ + findLeafIndex(blockNumber: number, treeId: MerkleTreeId, leafValue: Fr): Promise; + + /** + * Fetch the sibling path of the leaf in the respective tree + * @param blockNumber - The block number at which to get the sibling path. + * @param treeId - The id of the tree to search. + * @param leafIndex - The index of the leaf. + * @returns - The sibling path of the leaf. + */ + getSiblingPath(blockNumber: number, treeId: MerkleTreeId, leafIndex: bigint): Promise; + + /** + * Returns a nullifier membership witness for a given nullifier at a given block. + * @param blockNumber - The block number at which to get the index. + * @param nullifier - Nullifier we try to find witness for. + * @returns The nullifier membership witness (if found). + */ + getNullifierMembershipWitness(blockNumber: number, nullifier: Fr): Promise; + + /** + * Returns a low nullifier membership witness for a given nullifier at a given block. + * @param blockNumber - The block number at which to get the index. + * @param nullifier - Nullifier we try to find the low nullifier witness for. + * @returns The low nullifier membership witness (if found). + * @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked + * list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier + * we are trying to prove non-inclusion for. + */ + getLowNullifierMembershipWitness(blockNumber: number, nullifier: Fr): Promise; + + /** + * Fetch a block corresponding to the given block number. + * @param blockNumber - The block number of a block to fetch. + * @returns - The block corresponding to the given block number. Undefined if it does not exist. + */ + getBlock(blockNumber: number): Promise; } diff --git a/yarn-project/acir-simulator/src/client/view_data_oracle.ts b/yarn-project/acir-simulator/src/client/view_data_oracle.ts index cc168317756..fe124f853d2 100644 --- a/yarn-project/acir-simulator/src/client/view_data_oracle.ts +++ b/yarn-project/acir-simulator/src/client/view_data_oracle.ts @@ -1,9 +1,9 @@ import { HistoricBlockData, PublicKey } from '@aztec/circuits.js'; -import { siloNullifier } from '@aztec/circuits.js/abis'; +import { computeGlobalsHash, siloNullifier } from '@aztec/circuits.js/abis'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; -import { AuthWitness, AztecNode, CompleteAddress } from '@aztec/types'; +import { AuthWitness, AztecNode, CompleteAddress, MerkleTreeId, NullifierMembershipWitness } from '@aztec/types'; import { NoteData, TypedOracle } from '../acvm/index.js'; import { DBOracle } from './db_oracle.js'; @@ -35,6 +35,84 @@ export class ViewDataOracle extends TypedOracle { return this.db.getSecretKey(this.contractAddress, owner); } + /** + * Fetches the index and sibling path of a leaf at a given block from a given tree. + * @param blockNumber - The block number at which to get the membership witness. + * @param treeId - Id of the tree to get the sibling path from. + * @param leafValue - The leaf value + * @returns The index and sibling path concatenated [index, sibling_path] + */ + public async getMembershipWitness(blockNumber: number, treeId: MerkleTreeId, leafValue: Fr): Promise { + const index = await this.db.findLeafIndex(blockNumber, treeId, leafValue); + if (!index) { + throw new Error(`Leaf value: ${leafValue} not found in ${MerkleTreeId[treeId]}`); + } + const siblingPath = await this.db.getSiblingPath(blockNumber, treeId, index); + return [new Fr(index), ...siblingPath]; + } + + /** + * Fetches a sibling path at a given block and index from a tree specified by `treeId`. + * @param blockNumber - The block number at which to get the membership witness. + * @param treeId - Id of the tree to get the sibling path from. + * @param leafIndex - Index of the leaf to get sibling path for + * @returns The sibling path. + */ + public getSiblingPath(blockNumber: number, treeId: MerkleTreeId, leafIndex: Fr): Promise { + return this.db.getSiblingPath(blockNumber, treeId, leafIndex.toBigInt()); + } + + /** + * Returns a nullifier membership witness for a given nullifier at a given block. + * @param blockNumber - The block number at which to get the index. + * @param nullifier - Nullifier we try to find witness for. + * @returns The nullifier membership witness (if found). + */ + public async getNullifierMembershipWitness( + blockNumber: number, + nullifier: Fr, + ): Promise { + return await this.db.getNullifierMembershipWitness(blockNumber, nullifier); + } + + /** + * Returns a low nullifier membership witness for a given nullifier at a given block. + * @param blockNumber - The block number at which to get the index. + * @param nullifier - Nullifier we try to find the low nullifier witness for. + * @returns The low nullifier membership witness (if found). + * @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked + * list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier + * we are trying to prove non-inclusion for. + */ + public async getLowNullifierMembershipWitness( + blockNumber: number, + nullifier: Fr, + ): Promise { + return await this.db.getLowNullifierMembershipWitness(blockNumber, nullifier); + } + + /** + * Fetches historic block data for a given block. + * @param blockNumber - The block number at which to get the historic block data. + * @returns Historic block data extracted from a block with block number `blockNumber`. + */ + public async getBlockData(blockNumber: number): Promise { + const block = await this.db.getBlock(blockNumber); + if (!block) { + return undefined; + } + return new HistoricBlockData( + block.endNoteHashTreeSnapshot.root, + block.endNullifierTreeSnapshot.root, + block.endContractTreeSnapshot.root, + block.endL1ToL2MessagesTreeSnapshot.root, + block.endHistoricBlocksTreeSnapshot.root, + new Fr(0), // TODO(#3441) privateKernelVkTreeRoot is not present in L2Block and it's not yet populated in noir + block.endPublicDataTreeRoot, + computeGlobalsHash(block.globalVariables), + ); + } + /** * Retrieve the complete address associated to a given address. * @param address - Address to fetch the complete address for. diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index b2f83b86232..817b1a6b206 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -3,9 +3,12 @@ import { CONTRACT_TREE_HEIGHT, Fr, GlobalVariables, + HISTORIC_BLOCKS_TREE_HEIGHT, HistoricBlockData, L1_TO_L2_MSG_TREE_HEIGHT, NOTE_HASH_TREE_HEIGHT, + NULLIFIER_TREE_HEIGHT, + PUBLIC_DATA_TREE_HEIGHT, } from '@aztec/circuits.js'; import { computeGlobalsHash, computePublicDataTreeIndex } from '@aztec/circuits.js/abis'; import { L1ContractAddresses, createEthereumChain } from '@aztec/ethereum'; @@ -34,6 +37,7 @@ import { LogFilter, LogType, MerkleTreeId, + NullifierMembershipWitness, SequencerConfig, SiblingPath, Tx, @@ -171,9 +175,9 @@ export class AztecNodeService implements AztecNode { } /** - * Get the a given block. + * Get a block specified by its number. * @param number - The block number being requested. - * @returns The blocks requested. + * @returns The requested block. */ public async getBlock(number: number): Promise { return await this.blockSource.getBlock(number); @@ -307,7 +311,7 @@ export class AztecNodeService implements AztecNode { } /** - * Returns the sibling path for the given index in the contract tree. + * Returns a sibling path for the given index in the contract tree. * @param leafIndex - The index of the leaf for which the sibling path is required. * @returns The sibling path for the leaf index. */ @@ -317,7 +321,17 @@ export class AztecNodeService implements AztecNode { } /** - * Returns the sibling path for the given index in the data tree. + * Returns a sibling path for the given index in the nullifier tree. + * @param leafIndex - The index of the leaf for which the sibling path is required. + * @returns The sibling path for the leaf index. + */ + public async getNullifierTreeSiblingPath(leafIndex: bigint): Promise> { + const committedDb = await this.#getWorldState(); + return committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, leafIndex); + } + + /** + * Returns a sibling path for the given index in the data tree. * @param leafIndex - The index of the leaf for which the sibling path is required. * @returns The sibling path for the leaf index. */ @@ -340,7 +354,7 @@ export class AztecNodeService implements AztecNode { } /** - * Returns the sibling path for a leaf in the committed l1 to l2 data tree. + * Returns a sibling path for a leaf in the committed l1 to l2 data tree. * @param leafIndex - Index of the leaf in the tree. * @returns The sibling path. */ @@ -349,6 +363,96 @@ export class AztecNodeService implements AztecNode { return committedDb.getSiblingPath(MerkleTreeId.L1_TO_L2_MESSAGES_TREE, leafIndex); } + /** + * Returns a sibling path for a leaf in the committed historic blocks tree. + * @param leafIndex - Index of the leaf in the tree. + * @returns The sibling path. + */ + public async getHistoricBlocksTreeSiblingPath( + leafIndex: bigint, + ): Promise> { + const committedDb = await this.#getWorldState(); + return committedDb.getSiblingPath(MerkleTreeId.BLOCKS_TREE, leafIndex); + } + + /** + * Returns a sibling path for a leaf in the committed public data tree. + * @param leafIndex - Index of the leaf in the tree. + * @returns The sibling path. + */ + public async getPublicDataTreeSiblingPath(leafIndex: bigint): Promise> { + const committedDb = await this.#getWorldState(); + return committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex); + } + + /** + * Returns a nullifier membership witness for a given nullifier at a given block. + * @param blockNumber - The block number at which to get the index. + * @param nullifier - Nullifier we try to find witness for. + * @returns The nullifier membership witness (if found). + */ + public async getNullifierMembershipWitness( + blockNumber: number, + nullifier: Fr, + ): Promise { + const committedDb = await this.#getWorldState(); + const index = await committedDb.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()); + if (!index) { + return undefined; + } + + const leafDataPromise = committedDb.getLeafData(MerkleTreeId.NULLIFIER_TREE, Number(index)); + const siblingPathPromise = committedDb.getSiblingPath( + MerkleTreeId.NULLIFIER_TREE, + BigInt(index), + ); + + const [leafData, siblingPath] = await Promise.all([leafDataPromise, siblingPathPromise]); + + if (!leafData) { + return undefined; + } + + return new NullifierMembershipWitness(BigInt(index), leafData, siblingPath); + } + + /** + * Returns a low nullifier membership witness for a given nullifier at a given block. + * @param blockNumber - The block number at which to get the index. + * @param nullifier - Nullifier we try to find the low nullifier witness for. + * @returns The low nullifier membership witness (if found). + * @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked + * list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier + * we are trying to prove non-inclusion for. + * + * Note: This function returns the membership witness of the nullifier itself and not the low nullifier when + * the nullifier already exists in the tree. This is because the `getPreviousValueIndex` function returns the + * index of the nullifier itself when it already exists in the tree. + * TODO: This is a confusing behavior and we should eventually address that. + */ + public async getLowNullifierMembershipWitness( + blockNumber: number, + nullifier: Fr, + ): Promise { + const committedDb = await this.#getWorldState(); + const { index, alreadyPresent } = await committedDb.getPreviousValueIndex( + MerkleTreeId.NULLIFIER_TREE, + nullifier.toBigInt(), + ); + if (alreadyPresent) { + this.log.warn(`Nullifier ${nullifier.toBigInt()} already exists in the tree`); + } + const leafData = await committedDb.getLeafData(MerkleTreeId.NULLIFIER_TREE, index); + if (!leafData) { + return undefined; + } + const siblingPath = await committedDb.getSiblingPath( + MerkleTreeId.NULLIFIER_TREE, + BigInt(index), + ); + return new NullifierMembershipWitness(BigInt(index), leafData, siblingPath); + } + /** * Gets the storage value at the given contract storage slot. * diff --git a/yarn-project/aztec-nr/aztec/src/abi.nr b/yarn-project/aztec-nr/aztec/src/abi.nr index 541dc73cf3a..c84ad8b6d8b 100644 --- a/yarn-project/aztec-nr/aztec/src/abi.nr +++ b/yarn-project/aztec-nr/aztec/src/abi.nr @@ -20,6 +20,7 @@ use crate::constants_gen::{ CONTRACT_STORAGE_READ_LENGTH, PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH, PUBLIC_CIRCUIT_PUBLIC_INPUTS_HASH_INPUT_LENGTH, + GENERATOR_INDEX__BLOCK_HASH, GENERATOR_INDEX__PUBLIC_CIRCUIT_PUBLIC_INPUTS, GENERATOR_INDEX__FUNCTION_DATA, GENERATOR_INDEX__PUBLIC_DATA_READ, @@ -166,9 +167,35 @@ impl HistoricBlockData { ] } + pub fn deserialize(deserialized: [Field; HISTORIC_BLOCK_DATA_LENGTH]) -> Self { + HistoricBlockData { + note_hash_tree_root: deserialized[0], + nullifier_tree_root: deserialized[1], + contract_tree_root: deserialized[2], + l1_to_l2_messages_tree_root: deserialized[3], + blocks_tree_root: deserialized[4], + public_data_tree_root: deserialized[5], + global_variables_hash: deserialized[6], + } + } + pub fn empty() -> Self { Self { note_hash_tree_root: 0, nullifier_tree_root: 0, contract_tree_root: 0, l1_to_l2_messages_tree_root: 0, blocks_tree_root: 0, public_data_tree_root: 0, global_variables_hash: 0 } } + + pub fn block_hash(self) -> Field { + // TODO(#3442): Unify the ordering in `HistoricBlockData::serialize` function and the ordering + // in the block hash preimage --> This requires changes in the circuits. + let inputs = [ + self.global_variables_hash, + self.note_hash_tree_root, + self.nullifier_tree_root, + self.contract_tree_root, + self.l1_to_l2_messages_tree_root, + self.public_data_tree_root + ]; + pedersen_hash(inputs, GENERATOR_INDEX__BLOCK_HASH) + } } struct FunctionData { diff --git a/yarn-project/aztec-nr/aztec/src/context.nr b/yarn-project/aztec-nr/aztec/src/context.nr index e10b6233ec6..3efe8bda4ac 100644 --- a/yarn-project/aztec-nr/aztec/src/context.nr +++ b/yarn-project/aztec-nr/aztec/src/context.nr @@ -46,6 +46,7 @@ use crate::oracle::{ public_call::call_public_function_internal, enqueue_public_function_call::enqueue_public_function_call_internal, context::get_portal_address, + get_block_data::get_block_data, }; use dep::std::option::Option; @@ -128,6 +129,10 @@ impl PrivateContext { self.inputs.call_context.function_selector } + pub fn get_block_data(self, block_number: Field) -> HistoricBlockData { + get_block_data(block_number, self) + } + pub fn finish(self) -> abi::PrivateCircuitPublicInputs { // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1165) let encrypted_logs_hash = [0; NUM_FIELDS_PER_SHA256]; diff --git a/yarn-project/aztec-nr/aztec/src/note/utils.nr b/yarn-project/aztec-nr/aztec/src/note/utils.nr index bce81ff225e..cf40eaeba9b 100644 --- a/yarn-project/aztec-nr/aztec/src/note/utils.nr +++ b/yarn-project/aztec-nr/aztec/src/note/utils.nr @@ -1,9 +1,13 @@ -use crate::note::{ - note_hash::{compute_inner_hash, compute_siloed_hash, compute_unique_hash}, - note_header::NoteHeader, - note_interface::NoteInterface, +use crate::{ + constants_gen::GENERATOR_INDEX__OUTER_NULLIFIER, + note::{ + note_hash::{compute_inner_hash, compute_siloed_hash, compute_unique_hash}, + note_header::NoteHeader, + note_interface::NoteInterface, + }, + utils::arr_copy_slice, + hash::pedersen_hash, }; -use crate::utils::arr_copy_slice; pub fn compute_inner_note_hash(note_interface: NoteInterface, note: Note) -> Field { let get_header = note_interface.get_header; @@ -33,6 +37,17 @@ pub fn compute_unique_siloed_note_hash(note_interface: NoteInterface(note_interface: NoteInterface, note_with_header: Note) -> Field { + let get_header = note_interface.get_header; + let header = get_header(note_with_header); + + let compute_nullifier = note_interface.compute_nullifier; + let inner_nullifier = compute_nullifier(note_with_header); + + let input = [header.contract_address, inner_nullifier]; + pedersen_hash(input, GENERATOR_INDEX__OUTER_NULLIFIER) +} + pub fn compute_note_hash_for_read_or_nullify(note_interface: NoteInterface, note_with_header: Note) -> Field { let get_header = note_interface.get_header; let header = get_header(note_with_header); diff --git a/yarn-project/aztec-nr/aztec/src/oracle.nr b/yarn-project/aztec-nr/aztec/src/oracle.nr index f5c27c53298..4bed8383bfa 100644 --- a/yarn-project/aztec-nr/aztec/src/oracle.nr +++ b/yarn-project/aztec-nr/aztec/src/oracle.nr @@ -7,10 +7,14 @@ mod call_private_function; mod context; mod debug_log; mod get_l1_to_l2_message; +mod get_nullifier_membership_witness; +mod get_membership_witness; mod get_public_key; mod get_secret_key; +mod get_sibling_path; mod rand; mod enqueue_public_function_call; +mod get_block_data; mod public_call; mod notes; mod storage; diff --git a/yarn-project/aztec-nr/aztec/src/oracle/get_block_data.nr b/yarn-project/aztec-nr/aztec/src/oracle/get_block_data.nr new file mode 100644 index 00000000000..2b645a62dd9 --- /dev/null +++ b/yarn-project/aztec-nr/aztec/src/oracle/get_block_data.nr @@ -0,0 +1,39 @@ +use dep::std::merkle::compute_merkle_root; +use crate::{ + abi::HistoricBlockData, + constants_gen::{ + HISTORIC_BLOCK_DATA_LENGTH, + HISTORIC_BLOCKS_TREE_HEIGHT, + }, + context::PrivateContext, + oracle::get_membership_witness::{ + get_membership_witness, + MembershipWitness, + }, +}; + +#[oracle(getBlockData)] +fn get_block_data_oracle(_block_number: Field) -> [Field; HISTORIC_BLOCK_DATA_LENGTH] {} + +unconstrained pub fn get_block_data_internal(block_number: Field) -> HistoricBlockData { + let block_data = get_block_data_oracle(block_number); + HistoricBlockData::deserialize(block_data) +} + +pub fn get_block_data(block_number: Field, context: PrivateContext) -> HistoricBlockData { + // 1) Get historic block data from oracle at the given block + let block_data = get_block_data_internal(block_number); + + // 2) Compute the block hash from the block data + let block_hash = block_data.block_hash(); + + // 3) Get the membership wintess of the block in the blocks tree + let blocks_tree_id = 5; // TODO(#3443) + let witness: MembershipWitness = get_membership_witness(block_number, blocks_tree_id, block_hash); + + // 4) Check that the block is in the blocks tree (i.e. the witness is valid) + assert(context.block_data.blocks_tree_root == compute_merkle_root(block_hash, witness.index, witness.path), "Proving membership of a block in blocks tree failed"); + + // 5) Return the block data + block_data +} diff --git a/yarn-project/aztec-nr/aztec/src/oracle/get_membership_witness.nr b/yarn-project/aztec-nr/aztec/src/oracle/get_membership_witness.nr new file mode 100644 index 00000000000..5122e685d3e --- /dev/null +++ b/yarn-project/aztec-nr/aztec/src/oracle/get_membership_witness.nr @@ -0,0 +1,22 @@ +use crate::constants_gen::NOTE_HASH_TREE_HEIGHT; +use crate::utils::arr_copy_slice; + +// Note: We have M here because we need to somehow set it when calling get_membership_witness function and one way to +// do it is to set M here and then set type of the return param, e.g.: +// +// `let witness: MembershipWitness = get_membership_witness(...);` +// +// Another way to do it would be to add "type_hint: [Field; T]" as argument to `get_membership_witness` but that's +// a bit too boilerplatey for my taste. +struct MembershipWitness { + index: Field, + path: [Field; N], +} + +#[oracle(getMembershipWitness)] +fn get_membership_witness_oracle(_block_number: Field, _tree_id: Field, _leaf_value: Field) -> [Field; M] {} + +unconstrained pub fn get_membership_witness(block_number: Field, tree_id: Field, leaf_value: Field) -> MembershipWitness { + let fields: [Field; M] = get_membership_witness_oracle(block_number, tree_id, leaf_value); + MembershipWitness { index: fields[0], path: arr_copy_slice(fields, [0; N], 1) } +} diff --git a/yarn-project/aztec-nr/aztec/src/oracle/get_nullifier_membership_witness.nr b/yarn-project/aztec-nr/aztec/src/oracle/get_nullifier_membership_witness.nr new file mode 100644 index 00000000000..64d073d42cb --- /dev/null +++ b/yarn-project/aztec-nr/aztec/src/oracle/get_nullifier_membership_witness.nr @@ -0,0 +1,60 @@ +use crate::constants_gen::NULLIFIER_TREE_HEIGHT; +use crate::utils::arr_copy_slice; +use crate::hash::pedersen_hash; + +global LEAF_DATA_LENGTH: Field = 3; +// TODO: move this to constants.hpp so that it gets computed as INDEX_LENGTH + LEAF_DATA_LENGTH + NULLIFIER_TREE_HEIGHT +global NULLIFIER_MEMBERSHIP_WITNESS: Field = 24; + +// Noir version of LeafData interface from indexed merkle tree. +// TODO(#3470) replace with /mnt/user-data/jan/aztec-packages/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/abis/nullifier_leaf_preimage.nr +struct LeafData { + value: Field, + next_index: Field, + next_value: Field, +} + +impl LeafData { + fn serialize(self) -> [Field; LEAF_DATA_LENGTH] { + [self.value, self.next_index, self.next_value] + } + + fn hash(self) -> Field { + // Performs the same hashing as StandardIndexedTree::encodeLeaf(...) + pedersen_hash(self.serialize(), 0) + } +} + +struct NullifierMembershipWitness { + index: Field, + leaf_data: LeafData, + path: [Field; NULLIFIER_TREE_HEIGHT], +} + +#[oracle(getLowNullifierMembershipWitness)] +fn get_low_nullifier_membership_witness_oracle(_block_number: Field, _nullifier: Field) -> [Field; NULLIFIER_MEMBERSHIP_WITNESS] {} + +// Nullifier here refers to the nullifier we are looking to get non-inclusion proof for (by proving that a lower +// nullifier's next_value is bigger than the nullifier) +unconstrained pub fn get_low_nullifier_membership_witness(block_number: Field, nullifier: Field) -> NullifierMembershipWitness { + let fields = get_low_nullifier_membership_witness_oracle(block_number, nullifier); + NullifierMembershipWitness { + index: fields[0], + leaf_data: LeafData { value: fields[1], next_index: fields[2], next_value: fields[3] }, + path: arr_copy_slice(fields, [0; NULLIFIER_TREE_HEIGHT], 1 + LEAF_DATA_LENGTH) + } +} + +#[oracle(getNullifierMembershipWitness)] +fn get_nullifier_membership_witness_oracle(_block_number: Field, _nullifier: Field) -> [Field; NULLIFIER_MEMBERSHIP_WITNESS] {} + +// Nullifier here refers to the nullifier we are looking to get non-inclusion proof for (by proving that a lower +// nullifier's next_value is bigger than the nullifier) +unconstrained pub fn get_nullifier_membership_witness(block_number: Field, nullifier: Field) -> NullifierMembershipWitness { + let fields = get_nullifier_membership_witness_oracle(block_number, nullifier); + NullifierMembershipWitness { + index: fields[0], + leaf_data: LeafData { value: fields[1], next_index: fields[2], next_value: fields[3] }, + path: arr_copy_slice(fields, [0; NULLIFIER_TREE_HEIGHT], 1 + LEAF_DATA_LENGTH) + } +} \ No newline at end of file diff --git a/yarn-project/aztec-nr/aztec/src/oracle/get_sibling_path.nr b/yarn-project/aztec-nr/aztec/src/oracle/get_sibling_path.nr new file mode 100644 index 00000000000..076a4748d69 --- /dev/null +++ b/yarn-project/aztec-nr/aztec/src/oracle/get_sibling_path.nr @@ -0,0 +1,10 @@ +use crate::constants_gen::NOTE_HASH_TREE_HEIGHT; +use crate::utils::arr_copy_slice; + +#[oracle(getSiblingPath)] +fn get_sibling_path_oracle(_block_number: Field, _tree_id: Field, _leaf_index: Field) -> [Field; N] {} + +unconstrained pub fn get_sibling_path(block_number: Field, tree_id: Field, leaf_index: Field) -> [Field; N] { + let value: [Field; N] = get_sibling_path_oracle(block_number, tree_id, leaf_index); + value +} diff --git a/yarn-project/circuits.js/src/abis/abis.ts b/yarn-project/circuits.js/src/abis/abis.ts index fda355297e4..dd249bbb5d6 100644 --- a/yarn-project/circuits.js/src/abis/abis.ts +++ b/yarn-project/circuits.js/src/abis/abis.ts @@ -323,6 +323,7 @@ export function computeBlockHash( * Computes the globals hash given the globals. * @param globals - The global variables to put into the block hash. * @returns The globals hash. + * TODO: move this to GlobalVariables? */ export function computeGlobalsHash(globals: GlobalVariables): Fr { return Fr.fromBuffer( diff --git a/yarn-project/circuits.js/src/structs/kernel/historic_block_data.ts b/yarn-project/circuits.js/src/structs/kernel/historic_block_data.ts index f3d09abc864..eda21d334f1 100644 --- a/yarn-project/circuits.js/src/structs/kernel/historic_block_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/historic_block_data.ts @@ -37,7 +37,7 @@ export class HistoricBlockData { /** * Root of the private kernel vk tree at the time of when this information was assembled. */ - public privateKernelVkTreeRoot: Fr, // future enhancement + public privateKernelVkTreeRoot: Fr, // TODO(#3441) future enhancement /** * Current public state tree hash. */ @@ -97,7 +97,7 @@ export class HistoricBlockData { this.nullifierTreeRoot, this.contractTreeRoot, this.l1ToL2MessagesTreeRoot, - this.blocksTreeRoot, // Note private_kernel_vk_tree_root, is not included yet as + this.blocksTreeRoot, // TODO(#3441) Note private_kernel_vk_tree_root, is not included yet as // it is not present in noir, this.publicDataTreeRoot, this.globalVariablesHash, diff --git a/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts b/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts index 3f89fa44cd8..946f972e67b 100644 --- a/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts +++ b/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts @@ -107,6 +107,7 @@ EasyPrivateVotingContractArtifact EcdsaAccountContractArtifact EscrowContractArtifact ImportTestContractArtifact +InclusionProofsContractArtifact LendingContractArtifact ParentContractArtifact PendingCommitmentsContractArtifact diff --git a/yarn-project/end-to-end/src/e2e_inclusion_proofs_contract.test.ts b/yarn-project/end-to-end/src/e2e_inclusion_proofs_contract.test.ts new file mode 100644 index 00000000000..d2b5d7aac34 --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_inclusion_proofs_contract.test.ts @@ -0,0 +1,122 @@ +import { AccountWallet, AztecAddress, CompleteAddress, Fr, PXE } from '@aztec/aztec.js'; +import { InclusionProofsContract } from '@aztec/noir-contracts/types'; + +import { jest } from '@jest/globals'; +import { type MemDown, default as memdown } from 'memdown'; + +import { setup } from './fixtures/utils.js'; + +export const createMemDown = () => (memdown as any)() as MemDown; + +const TIMEOUT = 90_000; + +describe('e2e_inclusion_proofs_contract', () => { + jest.setTimeout(TIMEOUT); + + let pxe: PXE; + let teardown: () => Promise; + let wallets: AccountWallet[]; + let accounts: CompleteAddress[]; + + let contract: InclusionProofsContract; + const publicValue = 236n; + + beforeAll(async () => { + ({ pxe, teardown, wallets, accounts } = await setup(1)); + + contract = await InclusionProofsContract.deploy(wallets[0], publicValue).send().deployed(); + }, 100_000); + + afterAll(() => teardown()); + + it('proves note existence and its nullifier non-existence and nullifier non-existence failure case', async () => { + // Owner of a note + const owner = accounts[0].address; + { + // Create a note + const value = 100n; + const receipt = await contract.methods.create_note(owner, value).send().wait({ debug: true }); + const { newCommitments, visibleNotes } = receipt.debugInfo!; + expect(newCommitments.length).toBe(1); + expect(visibleNotes.length).toBe(1); + const [receivedValue, receivedOwner, _randomness] = visibleNotes[0].note.items; + expect(receivedValue.toBigInt()).toBe(value); + expect(receivedOwner).toEqual(owner.toField()); + } + + { + // Prove note inclusion in a given block. + // We prove the note existence at current block number because we don't currently have historical data + const blockNumber = await pxe.getBlockNumber(); + const ignoredCommitment = 0; // Not ignored only when the note doesn't exist + await contract.methods.proveNoteInclusion(owner, blockNumber, ignoredCommitment).send().wait(); + } + + { + // Prove that the note has not been nullified + // We prove the note existence at current block number because we don't currently have historical data + const blockNumber = await pxe.getBlockNumber(); + const ignoredNullifier = 0; // Not ignored only when the note doesn't exist + await contract.methods.proveNullifierNonInclusion(owner, blockNumber, ignoredNullifier).send().wait(); + } + + { + // We test the failure case now --> The proof should fail when the nullifier already exists + const receipt = await contract.methods.nullifyNote(owner).send().wait({ debug: true }); + const { newNullifiers } = receipt.debugInfo!; + expect(newNullifiers.length).toBe(2); + + const blockNumber = await pxe.getBlockNumber(); + const nullifier = newNullifiers[1]; + // Note: getLowNullifierMembershipWitness returns the membership witness of the nullifier itself and not + // the low nullifier when the nullifier already exists in the tree and for this reason the execution fails + // on low_nullifier.value < nullifier.value check. + await expect( + contract.methods.proveNullifierNonInclusion(owner, blockNumber, nullifier).send().wait(), + ).rejects.toThrowError( + /Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed/, + ); + } + }); + + it('note existence failure case', async () => { + // Owner of a note + const owner = AztecAddress.random(); + + const blockNumber = await pxe.getBlockNumber(); + const randomNoteCommitment = Fr.random(); + await expect( + contract.methods.proveNoteInclusion(owner, blockNumber, randomNoteCommitment).send().wait(), + ).rejects.toThrow(/Leaf value: 0x[0-9a-fA-F]+ not found in NOTE_HASH_TREE/); + }); + + it('proves an existence of a public value in private context', async () => { + const blockNumber = await pxe.getBlockNumber(); + await contract.methods.provePublicValueInclusion(publicValue, blockNumber).send().wait(); + }); + + it('public value existence failure case', async () => { + const blockNumber = await pxe.getBlockNumber(); + const randomPublicValue = Fr.random(); + await expect( + contract.methods.provePublicValueInclusion(randomPublicValue, blockNumber).send().wait(), + ).rejects.toThrow(/Proving public value inclusion failed/); + }); + + it('proves existence of a nullifier in private context', async () => { + const blockNumber = await pxe.getBlockNumber(); + const block = await pxe.getBlock(blockNumber); + const nullifier = block?.newNullifiers[0]; + + await contract.methods.proveNullifierInclusion(nullifier!, blockNumber).send().wait(); + }); + + it('nullifier existence failure case', async () => { + const blockNumber = await pxe.getBlockNumber(); + const randomNullifier = Fr.random(); + + await expect(contract.methods.proveNullifierInclusion(randomNullifier, blockNumber).send().wait()).rejects.toThrow( + /Low nullifier witness not found for nullifier 0x[0-9a-fA-F]+ at block/, + ); + }); +}); diff --git a/yarn-project/merkle-tree/src/interfaces/indexed_tree.ts b/yarn-project/merkle-tree/src/interfaces/indexed_tree.ts index a684541fa48..1b8bade092e 100644 --- a/yarn-project/merkle-tree/src/interfaces/indexed_tree.ts +++ b/yarn-project/merkle-tree/src/interfaces/indexed_tree.ts @@ -1,26 +1,8 @@ -import { SiblingPath } from '@aztec/types'; +import { LeafData, SiblingPath } from '@aztec/types'; import { LowLeafWitnessData } from '../index.js'; import { AppendOnlyTree } from './append_only_tree.js'; -/** - * A leaf of a tree. - */ -export interface LeafData { - /** - * A value of the leaf. - */ - value: bigint; - /** - * An index of the next leaf. - */ - nextIndex: bigint; - /** - * A value of the next leaf. - */ - nextValue: bigint; -} - /** * Indexed merkle tree. */ diff --git a/yarn-project/merkle-tree/src/interfaces/update_only_tree.ts b/yarn-project/merkle-tree/src/interfaces/update_only_tree.ts index df77c5fd6cf..59a82d0b118 100644 --- a/yarn-project/merkle-tree/src/interfaces/update_only_tree.ts +++ b/yarn-project/merkle-tree/src/interfaces/update_only_tree.ts @@ -1,4 +1,5 @@ -import { LeafData } from '../index.js'; +import { LeafData } from '@aztec/types'; + import { MerkleTree } from './merkle_tree.js'; /** diff --git a/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts b/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts index 29c2bac627c..c32ce3221e1 100644 --- a/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts +++ b/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts @@ -1,8 +1,8 @@ import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer'; import { createDebugLogger } from '@aztec/foundation/log'; -import { SiblingPath } from '@aztec/types'; +import { LeafData, SiblingPath } from '@aztec/types'; -import { IndexedTree, LeafData } from '../interfaces/indexed_tree.js'; +import { IndexedTree } from '../interfaces/indexed_tree.js'; import { TreeBase } from '../tree_base.js'; const log = createDebugLogger('aztec:standard-indexed-tree'); diff --git a/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts b/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts index e3ebc9b167b..49a90e611f1 100644 --- a/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts +++ b/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts @@ -1,6 +1,7 @@ import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; +import { LeafData } from '@aztec/types'; -import { LeafData, StandardIndexedTree } from '../../index.js'; +import { StandardIndexedTree } from '../../index.js'; /** * A testing utility which is here to store the original implementation of StandardIndexedTree.appendLeaves method diff --git a/yarn-project/noir-contracts/src/contracts/inclusion_proofs_contract/Nargo.toml b/yarn-project/noir-contracts/src/contracts/inclusion_proofs_contract/Nargo.toml new file mode 100644 index 00000000000..b11305196cd --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/inclusion_proofs_contract/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "inclusion_proofs_contract" +authors = [""] +compiler_version = ">=0.18.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../../aztec-nr/aztec" } +value_note = { path = "../../../../aztec-nr/value-note" } \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/inclusion_proofs_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/inclusion_proofs_contract/src/main.nr new file mode 100644 index 00000000000..7a8871b7218 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/inclusion_proofs_contract/src/main.nr @@ -0,0 +1,291 @@ +mod utils; + +// A demonstration of inclusion and non-inclusion proofs. +contract InclusionProofs { + use dep::std::merkle::compute_merkle_root; + use dep::aztec::{ + state_vars::{ + map::Map, + set::Set, + public_state::PublicState, + }, + selector::compute_selector, + types::{ + address::AztecAddress, + type_serialization::field_serialization::FieldSerializationMethods, + }, + context::Context, + note::{ + note_getter_options::NoteGetterOptions, + note_header::NoteHeader, + utils as note_utils, + }, + constants_gen::{ + NOTE_HASH_TREE_HEIGHT, + NULLIFIER_TREE_HEIGHT, + HISTORIC_BLOCKS_TREE_HEIGHT, + PUBLIC_DATA_TREE_HEIGHT, + GENERATOR_INDEX__PUBLIC_LEAF_INDEX, + }, + oracle::{ + get_block_data::get_block_data, + get_membership_witness::{ + get_membership_witness, + MembershipWitness, + }, + get_sibling_path::get_sibling_path, + get_nullifier_membership_witness::{ + get_low_nullifier_membership_witness, + get_nullifier_membership_witness, + NullifierMembershipWitness, + }, + }, + hash::pedersen_hash, + }; + use dep::value_note::value_note::{ValueNote, ValueNoteMethods, VALUE_NOTE_LEN}; + + use crate::utils::{ + full_field_less_than, + full_field_greater_than, + }; + + struct Storage { + private_values: Map>, + public_value: PublicState, + } + + impl Storage { + fn init(context: Context) -> Self { + Storage { + private_values: Map::new( + context, + 1, // Storage slot + |context, slot| { + Set::new(context, slot, ValueNoteMethods) + }, + ), + public_value: PublicState::new( + context, + 2, // Storage slot + FieldSerializationMethods, + ), + } + } + } + + #[aztec(private)] + fn constructor(public_value: Field) { + let selector = compute_selector("_initialize(Field)"); + context.call_public_function(context.this_address(), selector, [public_value]); + } + + #[aztec(public)] + internal fn _initialize(value: Field) { + storage.public_value.write(value); + } + + // Creates a value note owned by `owner`. + #[aztec(private)] + fn create_note( + owner: AztecAddress, + value: Field, + ) { + let owner_private_values = storage.private_values.at(owner.address); + let mut note = ValueNote::new(value, owner.address); + owner_private_values.insert(&mut note, true); + } + + + // Proves that the owner owned a ValueNote at block `block_number`. + #[aztec(private)] + fn proveNoteInclusion( + owner: AztecAddress, + block_number: Field, // The block at which we'll prove that the note exists + spare_commitment: Field, // This is only used when the note is not found --> used to test the failure case + ) { + // TODO: assert that block number is less than the block number of context.block_data + // --> This will either require a new oracle method that returns block_data.global_variables_hash preimage + // or modifying the private context so that we somehow expose it. + + // 1) Get historic block data from oracle and ensure that the block hash is included in the current blocks tree + // root. + let block_data = context.get_block_data(block_number); + + // 2) Get the note from PXE. + let private_values = storage.private_values.at(owner.address); + let options = NoteGetterOptions::new().select(1, owner.address).set_limit(1); + let notes = private_values.get_notes(options); + let maybe_note = notes[0]; + + // 3) Compute the commitment from the note + let note_commitment = if maybe_note.is_some() { + note_utils::compute_unique_siloed_note_hash(ValueNoteMethods, maybe_note.unwrap_unchecked()) + } else { + // Note was not found so we will use the spare commitment + spare_commitment + }; + + // 4) Get the membership witness of the note in the note hash tree + let note_hash_tree_id = 2; // TODO(#3443) + let witness: MembershipWitness = + get_membership_witness(block_number, note_hash_tree_id, note_commitment); + + // 5) Prove that the commitment is in the note hash tree + assert( + block_data.note_hash_tree_root == compute_merkle_root(note_commitment, witness.index, witness.path), + "Proving note inclusion failed" + ); + + // --> Now we have traversed the trees all the way up to blocks tree root. + } + + // Proves that the note was not yet nullified at block `block_number`. + #[aztec(private)] + fn proveNullifierNonInclusion( + owner: AztecAddress, + block_number: Field, // The block at which we'll prove that the nullifier does not exists + spare_nullifier: Field, // This is only used when the note is not found --> used to test the failure case + ) { + // TODO: assert that block number is less than the block number of context.block_data + // --> This will either require a new oracle method that returns block_data.global_variables_hash preimage + // or modifying the private context so that we somehow expose it. + + // 1) Get historic block data from oracle and ensure that the block hash is included in the current blocks tree + // root. + let block_data = context.get_block_data(block_number); + + // 2) Get the note from PXE + let private_values = storage.private_values.at(owner.address); + let options = NoteGetterOptions::new().select(1, owner.address).set_limit(1); + let notes = private_values.get_notes(options); + let maybe_note = notes[0]; + + // 3) Compute the nullifier from the note + let nullifier = if maybe_note.is_some() { + note_utils::compute_siloed_nullifier(ValueNoteMethods, maybe_note.unwrap_unchecked()) + } else { + // Note was not found so we will use the spare nullifier + spare_nullifier + }; + + // 4) Get the membership witness of a low nullifier of the nullifier + let witness = get_low_nullifier_membership_witness(block_number, nullifier); + + // 5) Prove that the nullifier is not included in the nullifier tree + + // 5.a) Compute the low nullifier leaf and prove that it is in the nullifier tree + let low_nullifier_leaf = witness.leaf_data.hash(); + assert( + block_data.nullifier_tree_root == compute_merkle_root(low_nullifier_leaf, witness.index, witness.path), + "Proving nullifier non-inclusion failed: Could not prove low nullifier inclusion" + ); + + // 5.b) Prove that the low nullifier is smaller than the nullifier + assert( + full_field_less_than(witness.leaf_data.value, nullifier), + "Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed" + ); + + // 5.c) Prove that the low nullifier is pointing "over" the nullifier to prove that the nullifier is not + // included in the nullifier tree + assert( + full_field_greater_than(witness.leaf_data.next_value, nullifier), + "Proving nullifier non-inclusion failed: low_nullifier.next_value > nullifier.value check failed" + ); + + // --> Now we have traversed the trees all the way up to blocks tree root and verified that the nullifier + // was not yet included in the nullifier tree. + } + + #[aztec(private)] + fn nullifyNote( + owner: AztecAddress, + ) { + let private_values = storage.private_values.at(owner.address); + let options = NoteGetterOptions::new().select(1, owner.address).set_limit(1); + let notes = private_values.get_notes(options); + let note = notes[0].unwrap(); + + private_values.remove(note); + } + + // Proves nullifier existed at block `block_number`. + // Note: I am not getting a nullifier of the note that was created in this contract in this function because it is + // currently not possible to obtain a nullified note from PXE. + #[aztec(private)] + fn proveNullifierInclusion( + nullifier: Field, + block_number: Field, // The block at which we'll prove that the nullifier not exists in the tree + ) { + // TODO: assert that block number is less than the block number of context.block_data + // --> This will either require a new oracle method that returns block_data.global_variables_hash preimage + // or modifying the private context so that we somehow expose it. + + // 1) Get historic block data from oracle and ensure that the block hash is included in the current blocks tree + // root. + let block_data = context.get_block_data(block_number); + + // 2) Get the membership witness of the nullifier + let witness = get_nullifier_membership_witness(block_number, nullifier); + + // 3) Check that the witness we obtained matches the nullifier + assert(witness.leaf_data.value == nullifier, "Nullifier does not match value in witness"); + + // 4) Compute the nullifier tree leaf + let nullifier_leaf = witness.leaf_data.hash(); + + // 5) Prove that the nullifier is in the nullifier tree + assert( + block_data.nullifier_tree_root == compute_merkle_root(nullifier_leaf, witness.index, witness.path), + "Proving nullifier inclusion failed" + ); + + // --> Now we have traversed the trees all the way up to blocks tree root and verified that the nullifier + // was not yet included in the nullifier tree. + } + + #[aztec(private)] + fn provePublicValueInclusion( + public_value: Field, + block_number: Field, // The block at which we'll prove that the public value exists + ) { + // TODO: assert that block number is less than the block number of context.block_data + // --> This will either require a new oracle method that returns block_data.global_variables_hash preimage + // or modifying the private context so that we somehow expose it. + + // 1) Get historic block data from oracle and ensure that the block hash is included in the current blocks tree + // root. + let block_data = context.get_block_data(block_number); + + // 2) Compute the public value leaf index. + // We have to compute the leaf index here because unlike in the case of note commitments, public values are + // not siloed with contract address so an oracle could cheat and give us a membership witness for arbitrary + // value in the public data tree. + let public_value_leaf_index = pedersen_hash( + [context.this_address(), storage.public_value.storage_slot], + GENERATOR_INDEX__PUBLIC_LEAF_INDEX + ); + + // 3) Get the sibling path of the public value leaf index in the public data tree at block `block_number`. + let public_data_tree_id = 3; // TODO(#3443) + let path: [Field; PUBLIC_DATA_TREE_HEIGHT] = + get_sibling_path(block_number, public_data_tree_id, public_value_leaf_index); + + // 4) Prove that the public value provided on input is in the public data tree + assert( + block_data.public_data_tree_root == compute_merkle_root(public_value, public_value_leaf_index, path), + "Proving public value inclusion failed" + ); + + // --> Now we have traversed the trees all the way up to blocks tree root and that way verified that + // a specific `public_value` was really set in a given contract storage slot at block `block_number`. + } + + // Computes note hash and nullifier. + // Note 1: Needs to be defined by every contract producing logs. + // Note 2: Having it in all the contracts gives us the ability to compute the note hash and nullifier differently for different kind of notes. + unconstrained fn compute_note_hash_and_nullifier(contract_address: Field, nonce: Field, storage_slot: Field, serialized_note: [Field; VALUE_NOTE_LEN]) -> [Field; 4] { + let note_header = NoteHeader::new(contract_address, nonce, storage_slot); + note_utils::compute_note_hash_and_nullifier(ValueNoteMethods, note_header, serialized_note) + } +} diff --git a/yarn-project/noir-contracts/src/contracts/inclusion_proofs_contract/src/utils.nr b/yarn-project/noir-contracts/src/contracts/inclusion_proofs_contract/src/utils.nr new file mode 100644 index 00000000000..e67cf2fd776 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/inclusion_proofs_contract/src/utils.nr @@ -0,0 +1,11 @@ +// TODO(#3470): Copied over from https://github.com/AztecProtocol/aztec-packages/blob/a07c4bd47313be6aa604a63f37857eb0136b41ba/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/base/base_rollup_inputs.nr#L599 +// move to a shared place? + +// TODO to radix returns u8, so we cannot use bigger radixes. It'd be ideal to use a radix of the maximum range-constrained integer noir supports +pub fn full_field_less_than(lhs: Field, rhs: Field) -> bool { + dep::std::eddsa::lt_bytes32(lhs, rhs) +} + +pub fn full_field_greater_than(lhs: Field, rhs: Field) -> bool { + dep::std::eddsa::lt_bytes32(rhs, lhs) +} \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr index 42332685bc6..c06c1468966 100644 --- a/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr @@ -11,9 +11,7 @@ contract TokenBridge { context::{Context}, hash::{compute_secret_hash}, state_vars::{public_state::PublicState}, - types::type_serialization::field_serialization::{ - FieldSerializationMethods, FIELD_SERIALIZED_LEN, - }, + types::type_serialization::field_serialization::FieldSerializationMethods, types::address::{AztecAddress, EthereumAddress}, selector::compute_selector, }; diff --git a/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/historical_block_data.nr b/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/historical_block_data.nr index d9a8d180ade..488d4cf6ba0 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/historical_block_data.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/historical_block_data.nr @@ -17,7 +17,7 @@ impl HistoricalBlockData { fn to_array(self) -> [Field;7] { // This comment was copied from the cpp codebase. // - // TODO: Note private_kernel_vk_tree_root, is not included yet as + // TODO(#3441): Note private_kernel_vk_tree_root, is not included yet as // it is not present in noir, [ self.block.note_hash_tree_root, diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index f62b7683fa2..42c3edd74c4 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -9,7 +9,8 @@ import { HistoricBlockData, PublicKey, } from '@aztec/circuits.js'; -import { KeyStore, MerkleTreeId, StateInfoProvider } from '@aztec/types'; +import { createDebugLogger } from '@aztec/foundation/log'; +import { KeyStore, L2Block, MerkleTreeId, NullifierMembershipWitness, StateInfoProvider } from '@aztec/types'; import { ContractDataOracle } from '../contract_data_oracle/index.js'; import { Database } from '../database/index.js'; @@ -23,6 +24,7 @@ export class SimulatorOracle implements DBOracle { private db: Database, private keyStore: KeyStore, private stateInfoProvider: StateInfoProvider, + private log = createDebugLogger('aztec:pxe:simulator_oracle'), ) {} getSecretKey(_contractAddress: AztecAddress, pubKey: PublicKey): Promise { @@ -134,6 +136,48 @@ export class SimulatorOracle implements DBOracle { return await this.stateInfoProvider.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier); } + public async findLeafIndex(blockNumber: number, treeId: MerkleTreeId, leafValue: Fr): Promise { + this.log.warn('Block number ignored in SimulatorOracle.findLeafIndex because archival node is not yet implemented'); + return await this.stateInfoProvider.findLeafIndex(treeId, leafValue); + } + + public async getSiblingPath(blockNumber: number, treeId: MerkleTreeId, leafIndex: bigint): Promise { + this.log.warn( + 'Block number ignored in SimulatorOracle.getSiblingPath because archival node is not yet implemented', + ); + // @todo Doing a nasty workaround here because of https://github.com/AztecProtocol/aztec-packages/issues/3414 + switch (treeId) { + case MerkleTreeId.NULLIFIER_TREE: + return (await this.stateInfoProvider.getNullifierTreeSiblingPath(leafIndex)).toFieldArray(); + case MerkleTreeId.NOTE_HASH_TREE: + return (await this.stateInfoProvider.getNoteHashSiblingPath(leafIndex)).toFieldArray(); + case MerkleTreeId.BLOCKS_TREE: + return (await this.stateInfoProvider.getHistoricBlocksTreeSiblingPath(leafIndex)).toFieldArray(); + case MerkleTreeId.PUBLIC_DATA_TREE: + return (await this.stateInfoProvider.getPublicDataTreeSiblingPath(leafIndex)).toFieldArray(); + default: + throw new Error('Not implemented'); + } + } + + public getNullifierMembershipWitness( + blockNumber: number, + nullifier: Fr, + ): Promise { + return this.stateInfoProvider.getNullifierMembershipWitness(blockNumber, nullifier); + } + + public getLowNullifierMembershipWitness( + blockNumber: number, + nullifier: Fr, + ): Promise { + return this.stateInfoProvider.getLowNullifierMembershipWitness(blockNumber, nullifier); + } + + public async getBlock(blockNumber: number): Promise { + return await this.stateInfoProvider.getBlock(blockNumber); + } + /** * Retrieve the databases view of the Historic Block Data object. * This structure is fed into the circuits simulator and is used to prove against certain historic roots. diff --git a/yarn-project/types/src/interfaces/aztec-node.ts b/yarn-project/types/src/interfaces/aztec-node.ts index a366e7332d5..e03966ec8ae 100644 --- a/yarn-project/types/src/interfaces/aztec-node.ts +++ b/yarn-project/types/src/interfaces/aztec-node.ts @@ -30,13 +30,6 @@ export interface AztecNode extends StateInfoProvider { */ isReady(): Promise; - /** - * Get the a given block. - * @param number - The block number being requested. - * @returns The blocks requested. - */ - getBlock(number: number): Promise; - /** * Method to request blocks. Will attempt to return all requested blocks but will return only those available. * @param from - The start of the range of blocks to return. diff --git a/yarn-project/types/src/interfaces/index.ts b/yarn-project/types/src/interfaces/index.ts index ddef4f6b1f8..f351287cab2 100644 --- a/yarn-project/types/src/interfaces/index.ts +++ b/yarn-project/types/src/interfaces/index.ts @@ -6,3 +6,5 @@ export * from './deployed-contract.js'; export * from './node-info.js'; export * from './sync-status.js'; export * from './configs.js'; +export * from './leaf_data.js'; +export * from './nullifier_witness.js'; diff --git a/yarn-project/types/src/interfaces/leaf_data.ts b/yarn-project/types/src/interfaces/leaf_data.ts new file mode 100644 index 00000000000..2edc8e09818 --- /dev/null +++ b/yarn-project/types/src/interfaces/leaf_data.ts @@ -0,0 +1,17 @@ +/** + * A leaf of a tree. + */ +export interface LeafData { + /** + * A value of the leaf. + */ + value: bigint; + /** + * An index of the next leaf. + */ + nextIndex: bigint; + /** + * A value of the next leaf. + */ + nextValue: bigint; +} diff --git a/yarn-project/types/src/interfaces/nullifier_witness.ts b/yarn-project/types/src/interfaces/nullifier_witness.ts new file mode 100644 index 00000000000..90dc6d9a1c7 --- /dev/null +++ b/yarn-project/types/src/interfaces/nullifier_witness.ts @@ -0,0 +1,41 @@ +import { Fr, NULLIFIER_TREE_HEIGHT } from '@aztec/circuits.js'; + +import { SiblingPath } from '../sibling_path.js'; +import { LeafData } from './leaf_data.js'; + +/** + * Nullifier membership witness. + * @remarks When this represents membership witness of a low nullifier it can be used to perform a nullifier + * non-inclusion proof by leveraging the "linked list structure" of leaves and proving that a lower nullifier + * is pointing to a bigger next value than the nullifier we are trying to prove non-inclusion for. + */ +export class NullifierMembershipWitness { + constructor( + /** + * The index of the nullifier in a tree. + */ + public readonly index: bigint, + /** + * Preimage of the nullifier. + */ + public readonly leafData: LeafData, + /** + * Sibling path to prove membership of the nullifier. + */ + public readonly siblingPath: SiblingPath, + ) {} + + /** + * Returns a field array representation of a nullifier witness. + * @returns A field array representation of a nullifier witness. + */ + public toFieldArray(): Fr[] { + return [ + new Fr(this.index), + new Fr(this.leafData.value), + new Fr(this.leafData.nextIndex), + new Fr(this.leafData.nextValue), + ...this.siblingPath.toFieldArray(), + ]; + } +} diff --git a/yarn-project/types/src/interfaces/state_provider.ts b/yarn-project/types/src/interfaces/state_provider.ts index a08347cee39..68d01812a7e 100644 --- a/yarn-project/types/src/interfaces/state_provider.ts +++ b/yarn-project/types/src/interfaces/state_provider.ts @@ -1,8 +1,18 @@ -import { CONTRACT_TREE_HEIGHT, Fr, L1_TO_L2_MSG_TREE_HEIGHT, NOTE_HASH_TREE_HEIGHT } from '@aztec/circuits.js'; +import { + CONTRACT_TREE_HEIGHT, + Fr, + HISTORIC_BLOCKS_TREE_HEIGHT, + L1_TO_L2_MSG_TREE_HEIGHT, + NOTE_HASH_TREE_HEIGHT, + NULLIFIER_TREE_HEIGHT, + PUBLIC_DATA_TREE_HEIGHT, +} from '@aztec/circuits.js'; import { L1ToL2MessageAndIndex } from '../l1_to_l2_message.js'; +import { L2Block } from '../l2_block.js'; import { MerkleTreeId } from '../merkle_tree_id.js'; import { SiblingPath } from '../sibling_path.js'; +import { NullifierMembershipWitness } from './nullifier_witness.js'; /** * Interface providing methods for retrieving information about content of the state trees. @@ -17,16 +27,26 @@ export interface StateInfoProvider { findLeafIndex(treeId: MerkleTreeId, leafValue: Fr): Promise; /** - * Returns the sibling path for the given index in the contract tree. + * Returns a sibling path for the given index in the contract tree. * @param leafIndex - The index of the leaf for which the sibling path is required. * @returns The sibling path for the leaf index. + * TODO: https://github.com/AztecProtocol/aztec-packages/issues/3414 */ getContractSiblingPath(leafIndex: bigint): Promise>; /** - * Returns the sibling path for the given index in the note hash tree. + * Returns a sibling path for the given index in the nullifier tree. * @param leafIndex - The index of the leaf for which the sibling path is required. * @returns The sibling path for the leaf index. + * TODO: https://github.com/AztecProtocol/aztec-packages/issues/3414 + */ + getNullifierTreeSiblingPath(leafIndex: bigint): Promise>; + + /** + * Returns a sibling path for the given index in the note hash tree. + * @param leafIndex - The index of the leaf for which the sibling path is required. + * @returns The sibling path for the leaf index. + * TODO: https://github.com/AztecProtocol/aztec-packages/issues/3414 */ getNoteHashSiblingPath(leafIndex: bigint): Promise>; @@ -39,9 +59,52 @@ export interface StateInfoProvider { getL1ToL2MessageAndIndex(messageKey: Fr): Promise; /** - * Returns the sibling path for a leaf in the committed l1 to l2 data tree. + * Returns a sibling path for a leaf in the committed l1 to l2 data tree. * @param leafIndex - Index of the leaf in the tree. * @returns The sibling path. + * TODO: https://github.com/AztecProtocol/aztec-packages/issues/3414 */ getL1ToL2MessageSiblingPath(leafIndex: bigint): Promise>; + + /** + * Returns a sibling path for a leaf in the committed historic blocks tree. + * @param leafIndex - Index of the leaf in the tree. + * @returns The sibling path. + * TODO: https://github.com/AztecProtocol/aztec-packages/issues/3414 + */ + getHistoricBlocksTreeSiblingPath(leafIndex: bigint): Promise>; + + /** + * Returns a sibling path for a leaf in the committed public data tree. + * @param leafIndex - Index of the leaf in the tree. + * @returns The sibling path. + * TODO: https://github.com/AztecProtocol/aztec-packages/issues/3414 + */ + getPublicDataTreeSiblingPath(leafIndex: bigint): Promise>; + + /** + * Returns a nullifier membership witness for a given nullifier at a given block. + * @param blockNumber - The block number at which to get the index. + * @param nullifier - Nullifier we try to find witness for. + * @returns The nullifier membership witness (if found). + */ + getNullifierMembershipWitness(blockNumber: number, nullifier: Fr): Promise; + + /** + * Returns a low nullifier membership witness for a given nullifier at a given block. + * @param blockNumber - The block number at which to get the index. + * @param nullifier - Nullifier we try to find the low nullifier witness for. + * @returns The low nullifier membership witness (if found). + * @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked + * list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier + * we are trying to prove non-inclusion for. + */ + getLowNullifierMembershipWitness(blockNumber: number, nullifier: Fr): Promise; + + /** + * Get a block specified by its number. + * @param number - The block number being requested. + * @returns The requested block. + */ + getBlock(number: number): Promise; } diff --git a/yarn-project/world-state/src/merkle-tree/merkle_tree_operations_facade.ts b/yarn-project/world-state/src/merkle-tree/merkle_tree_operations_facade.ts index 7dfb03d8db2..5917a863694 100644 --- a/yarn-project/world-state/src/merkle-tree/merkle_tree_operations_facade.ts +++ b/yarn-project/world-state/src/merkle-tree/merkle_tree_operations_facade.ts @@ -1,15 +1,8 @@ import { Fr } from '@aztec/foundation/fields'; import { LowLeafWitnessData } from '@aztec/merkle-tree'; -import { L2Block, MerkleTreeId, SiblingPath } from '@aztec/types'; - -import { - CurrentTreeRoots, - HandleL2BlockResult, - LeafData, - MerkleTreeDb, - MerkleTreeOperations, - TreeInfo, -} from '../index.js'; +import { L2Block, LeafData, MerkleTreeId, SiblingPath } from '@aztec/types'; + +import { CurrentTreeRoots, HandleL2BlockResult, MerkleTreeDb, MerkleTreeOperations, TreeInfo } from '../index.js'; /** * Wraps a MerkleTreeDbOperations to call all functions with a preset includeUncommitted flag. diff --git a/yarn-project/world-state/src/world-state-db/index.ts b/yarn-project/world-state/src/world-state-db/index.ts index 8c0a92a77dd..9ebc6f456d7 100644 --- a/yarn-project/world-state/src/world-state-db/index.ts +++ b/yarn-project/world-state/src/world-state-db/index.ts @@ -1,3 +1,2 @@ export * from './merkle_trees.js'; export * from './merkle_tree_db.js'; -export { LeafData } from '@aztec/merkle-tree'; diff --git a/yarn-project/world-state/src/world-state-db/merkle_tree_db.ts b/yarn-project/world-state/src/world-state-db/merkle_tree_db.ts index 0f23abaa257..04f4d749fee 100644 --- a/yarn-project/world-state/src/world-state-db/merkle_tree_db.ts +++ b/yarn-project/world-state/src/world-state-db/merkle_tree_db.ts @@ -1,8 +1,8 @@ import { MAX_NEW_NULLIFIERS_PER_TX } from '@aztec/circuits.js'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; -import { LeafData, LowLeafWitnessData } from '@aztec/merkle-tree'; -import { L2Block, MerkleTreeId, SiblingPath } from '@aztec/types'; +import { LowLeafWitnessData } from '@aztec/merkle-tree'; +import { L2Block, LeafData, MerkleTreeId, SiblingPath } from '@aztec/types'; /** * Type alias for the nullifier tree ID. diff --git a/yarn-project/world-state/src/world-state-db/merkle_trees.ts b/yarn-project/world-state/src/world-state-db/merkle_trees.ts index 1e8fdc7b5c1..cdf1aa5c9bd 100644 --- a/yarn-project/world-state/src/world-state-db/merkle_trees.ts +++ b/yarn-project/world-state/src/world-state-db/merkle_trees.ts @@ -16,7 +16,6 @@ import { createDebugLogger } from '@aztec/foundation/log'; import { AppendOnlyTree, IndexedTree, - LeafData, LowLeafWitnessData, Pedersen, SparseTree, @@ -26,7 +25,7 @@ import { loadTree, newTree, } from '@aztec/merkle-tree'; -import { L2Block, MerkleTreeId, SiblingPath } from '@aztec/types'; +import { L2Block, LeafData, MerkleTreeId, SiblingPath } from '@aztec/types'; import { default as levelup } from 'levelup';