diff --git a/.circleci/config.yml b/.circleci/config.yml index 49097fff0e3..cc8dc727b50 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -630,6 +630,17 @@ jobs: name: "Build" command: build end-to-end + e2e-stateful-test-contract: + machine: + 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_stateful_test_contract.test.ts + e2e-2-pxes: docker: - image: aztecprotocol/alpine-build-image @@ -1354,6 +1365,7 @@ workflows: - e2e-2-pxes: *e2e_test - e2e-deploy-contract: *e2e_test + - e2e-stateful-test-contract: *e2e_test - e2e-lending-contract: *e2e_test - e2e-token-contract: *e2e_test - e2e-sandbox-example: *e2e_test @@ -1388,6 +1400,7 @@ workflows: requires: - e2e-2-pxes - e2e-deploy-contract + - e2e-stateful-test-contract - e2e-lending-contract - e2e-token-contract - e2e-sandbox-example diff --git a/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts b/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts index 08944796c0c..0ae5dc4fc09 100644 --- a/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts @@ -4,7 +4,7 @@ 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.js'; import { convertACVMFieldToBuffer, fromACVMField } from '../deserialize.js'; @@ -127,6 +127,11 @@ export class Oracle { return toAcvmL1ToL2MessageLoadOracleInputs(message, root); } + async getMembershipWitness([treeId]: ACVMField[], [leafValue]: ACVMField[]): Promise { + const merkleTreeId: MerkleTreeId = Number(fromACVMField(treeId).toBigInt()); + return (await this.typedOracle.getMembershipWitness(merkleTreeId, fromACVMField(leafValue))).map(toACVMField); + } + async getPortalContractAddress([aztecAddress]: ACVMField[]): Promise { const contractAddress = AztecAddress.fromString(aztecAddress); const portalContactAddress = await this.typedOracle.getPortalContractAddress(contractAddress); 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 a864471a722..cbb9b029ea6 100644 --- a/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts @@ -3,7 +3,7 @@ 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, PublicKey, UnencryptedL2Log } from '@aztec/types'; /** * Information about a note needed during execution. @@ -109,6 +109,10 @@ export abstract class TypedOracle { throw new Error('Not available.'); } + getMembershipWitness(_treeId: MerkleTreeId, _leafValue: Fr): Promise { + throw new Error('Not available.'); + } + getPortalContractAddress(_contractAddress: AztecAddress): Promise { throw new Error('Not available.'); } diff --git a/yarn-project/acir-simulator/src/client/client_execution_context.ts b/yarn-project/acir-simulator/src/client/client_execution_context.ts index 5236304a716..34186ecbc25 100644 --- a/yarn-project/acir-simulator/src/client/client_execution_context.ts +++ b/yarn-project/acir-simulator/src/client/client_execution_context.ts @@ -238,6 +238,39 @@ export class ClientExecutionContext extends ViewDataOracle { return notes; } + /** + * Fetches a path to prove existence of a commitment in the db, given its contract side commitment (before silo). + * @param nonce - The nonce of the note. + * @param innerNoteHash - The inner note hash of the note. + * @returns 1 if (persistent or transient) note hash exists, 0 otherwise. Value is in ACVMField form. + */ + public async checkNoteHashExists(nonce: Fr, innerNoteHash: Fr): Promise { + if (nonce.isZero()) { + // If nonce is 0, we are looking for a new note created in this transaction. + const exists = this.noteCache.checkNoteExists(this.contractAddress, innerNoteHash); + if (exists) { + return true; + } + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386) + // Currently it can also be a note created from public if nonce is 0. + // If we can't find a matching new note, keep looking for the match from the notes created in previous transactions. + } + + // If nonce is zero, SHOULD only be able to reach this point if note was publicly created + const wasm = await CircuitsWasm.get(); + let noteHashToLookUp = siloCommitment(wasm, this.contractAddress, innerNoteHash); + if (!nonce.isZero()) { + noteHashToLookUp = computeUniqueCommitment(wasm, nonce, noteHashToLookUp); + } + + const index = await this.db.getCommitmentIndex(noteHashToLookUp.toBuffer()); + const exists = index !== undefined; + if (exists) { + this.gotNotes.set(noteHashToLookUp.value, index); + } + return exists; + } + /** * Keep track of the new note created during execution. * It can be used in subsequent calls (or transactions when chaining txs is possible). @@ -305,7 +338,7 @@ export class ClientExecutionContext extends ViewDataOracle { */ async callPrivateFunction(targetContractAddress: AztecAddress, functionSelector: FunctionSelector, argsHash: Fr) { this.log( - `Calling private function ${this.contractAddress}:${functionSelector} from ${this.callContext.storageContractAddress}`, + `Calling private function ${targetContractAddress}:${functionSelector} from ${this.callContext.storageContractAddress}`, ); const targetArtifact = await this.db.getFunctionArtifact(targetContractAddress, functionSelector); diff --git a/yarn-project/acir-simulator/src/client/db_oracle.ts b/yarn-project/acir-simulator/src/client/db_oracle.ts index 6dd86e95d45..035a2db9947 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 { MerkleTreeId } from '@aztec/types'; import { NoteData } from '../acvm/index.js'; import { CommitmentsDB } from '../public/index.js'; @@ -107,4 +108,19 @@ 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 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(treeId: MerkleTreeId, leafValue: Buffer): Promise; + + /** + * Fetch the sibling path of the leaf in the respective tree + * @param treeId - The id of the tree to search. + * @param leafIndex - The index of the leaf. + */ + getSiblingPath(treeId: MerkleTreeId, leafIndex: bigint): Promise; } diff --git a/yarn-project/acir-simulator/src/client/private_execution.test.ts b/yarn-project/acir-simulator/src/client/private_execution.test.ts index 2b20f2fd241..ba7033394e0 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.test.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.test.ts @@ -3,7 +3,6 @@ import { CircuitsWasm, CompleteAddress, ContractDeploymentData, - EMPTY_NULLIFIED_COMMITMENT, FieldsOf, FunctionData, HistoricBlockData, @@ -19,7 +18,7 @@ import { computeCommitmentNonce, computeSecretMessageHash, computeVarArgsHash, - siloCommitment, + siloCommitment } from '@aztec/circuits.js/abis'; import { pedersenHashInputs } from '@aztec/circuits.js/barretenberg'; import { makeContractDeploymentData } from '@aztec/circuits.js/factories'; @@ -44,7 +43,7 @@ import { Note, PackedArguments, TxExecutionRequest } from '@aztec/types'; import { jest } from '@jest/globals'; import { MockProxy, mock } from 'jest-mock-extended'; import { default as levelup } from 'levelup'; -import { type MemDown, default as memdown } from 'memdown'; +import { default as memdown, type MemDown } from 'memdown'; import { getFunctionSelector } from 'viem'; import { buildL1ToL2Message, getFunctionArtifact, getFunctionArtifactWithSelector } from '../test/utils.js'; @@ -484,20 +483,10 @@ describe('Private Execution test suite', () => { const secretHash = computeSecretMessageHash(wasm, secret); const note = new Note([new Fr(amount), secretHash]); const noteHash = hashFields(note.items); - const storageSlot = new Fr(5); + const storageSlot = new Fr(2); const innerNoteHash = hashFields([storageSlot, noteHash]); const siloedNoteHash = siloCommitment(wasm, contractAddress, innerNoteHash); - oracle.getNotes.mockResolvedValue([ - { - contractAddress, - storageSlot, - nonce: Fr.ZERO, - note, - innerNoteHash: new Fr(EMPTY_NULLIFIED_COMMITMENT), - siloedNullifier: Fr.random(), - index: 1n, - }, - ]); + oracle.getCommitmentIndex.mockResolvedValue(0n); const result = await runSimulator({ artifact, 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 0194b91ad2b..8446147d9d9 100644 --- a/yarn-project/acir-simulator/src/client/view_data_oracle.ts +++ b/yarn-project/acir-simulator/src/client/view_data_oracle.ts @@ -3,7 +3,7 @@ import { 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 } from '@aztec/types'; import { NoteData, TypedOracle } from '../acvm/index.js'; import { DBOracle } from './db_oracle.js'; @@ -115,6 +115,21 @@ export class ViewDataOracle extends TypedOracle { return { ...message, root: this.historicBlockData.l1ToL2MessagesTreeRoot }; } + /** + * Fetches the index and sibling path for a leaf value + * @param treeId - The tree id + * @param leafValue - The leaf value + * @returns The index and sibling path concatenated [index, sibling_path] + */ + public async getMembershipWitness(treeId: MerkleTreeId, leafValue: Fr): Promise { + const index = await this.db.findLeafIndex(treeId, leafValue.toBuffer()); + if (!index) { + throw new Error(`Leaf value: ${leafValue} not found in tree ${treeId}`); + } + const siblingPath = await this.db.getSiblingPath(treeId, index); + return [new Fr(index), ...siblingPath]; + } + /** * Retrieves the portal contract address associated with the given contract address. * Throws an error if the input contract address is not found or invalid. diff --git a/yarn-project/acir-simulator/src/public/db.ts b/yarn-project/acir-simulator/src/public/db.ts index fecfe87fc1e..1fbc46267d8 100644 --- a/yarn-project/acir-simulator/src/public/db.ts +++ b/yarn-project/acir-simulator/src/public/db.ts @@ -65,9 +65,9 @@ export interface CommitmentsDB { getL1ToL2Message(msgKey: Fr): Promise; /** - * Gets the index of a commitment in the note hash tree. + * Find the index of the given commitment in the note hash tree. * @param commitment - The commitment. - * @returns - The index of the commitment. Undefined if it does not exist in the tree. + * @returns The index of the commitment. Undefined if it does not exist in the tree. */ getCommitmentIndex(commitment: Fr): Promise; } diff --git a/yarn-project/acir-simulator/src/public/public_execution_context.ts b/yarn-project/acir-simulator/src/public/public_execution_context.ts index 4097087e19a..403c11be313 100644 --- a/yarn-project/acir-simulator/src/public/public_execution_context.ts +++ b/yarn-project/acir-simulator/src/public/public_execution_context.ts @@ -1,4 +1,4 @@ -import { CallContext, FunctionData, FunctionSelector, GlobalVariables, HistoricBlockData } from '@aztec/circuits.js'; +import { CallContext, CircuitsWasm, FunctionData, FunctionSelector, GlobalVariables, HistoricBlockData } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; @@ -17,6 +17,7 @@ import { CommitmentsDB, PublicContractsDB, PublicStateDB } from './db.js'; import { PublicExecution, PublicExecutionResult } from './execution.js'; import { executePublicFunction } from './executor.js'; import { ContractStorageActionsCollector } from './state_actions.js'; +import { siloCommitment } from '@aztec/circuits.js/abis'; /** * The execution context for a public tx simulation. @@ -107,6 +108,21 @@ export class PublicExecutionContext extends TypedOracle { return { ...message, root: this.historicBlockData.l1ToL2MessagesTreeRoot }; } + /** + * Fetches a path to prove existence of a commitment in the db, given its contract side commitment (before silo). + * @param nonce - The nonce of the note. + * @param innerNoteHash - The inner note hash of the note. + * @returns 1 if (persistent or transient) note hash exists, 0 otherwise. Value is in ACVMField form. + */ + public async checkNoteHashExists(nonce: Fr, innerNoteHash: Fr): Promise { + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386) + // Once public kernel or base rollup circuit injects nonces, this can be updated to use uniqueSiloedCommitment. + const wasm = await CircuitsWasm.get(); + const siloedNoteHash = siloCommitment(wasm, this.execution.contractAddress, innerNoteHash); + const index = await this.commitmentsDb.getCommitmentIndex(siloedNoteHash); + return index !== undefined; + } + /** * Emit an unencrypted log. * @param log - The unencrypted log to be emitted. diff --git a/yarn-project/aztec-nr/aztec/src/messaging.nr b/yarn-project/aztec-nr/aztec/src/messaging.nr index 7e7aeae4500..55ed31682ac 100644 --- a/yarn-project/aztec-nr/aztec/src/messaging.nr +++ b/yarn-project/aztec-nr/aztec/src/messaging.nr @@ -5,7 +5,7 @@ use l1_to_l2_message_getter_data::make_l1_to_l2_message_getter_data; use crate::abi::PublicContextInputs; use crate::oracle::get_l1_to_l2_message::get_l1_to_l2_message_call; - +use dep::std::merkle::compute_merkle_root; // Returns the nullifier for the message pub fn process_l1_to_l2_message(l1_to_l2_root: Field, storage_contract_address: Field, msg_key: Field, content: Field, secret: Field) -> Field{ @@ -13,8 +13,13 @@ pub fn process_l1_to_l2_message(l1_to_l2_root: Field, storage_contract_address: let returned_message = get_l1_to_l2_message_call(msg_key); let l1_to_l2_message_data = make_l1_to_l2_message_getter_data(returned_message, 0, secret); - // Check tree roots against the inputs - assert(l1_to_l2_message_data.root == l1_to_l2_root); + // leaf should equal the msg_key if the message exsits and oracle is honest. + let leaf = l1_to_l2_message_data.message.message_hash(); + assert(leaf == msg_key, "Data don't match the message key"); + + // Validate that the commitment is indeed in the l1 to l2 message tree. + let root = compute_merkle_root(leaf, l1_to_l2_message_data.leaf_index, l1_to_l2_message_data.sibling_path); + assert(root == l1_to_l2_root, "Invalid root"); // Validate this is the target contract assert(l1_to_l2_message_data.message.recipient == storage_contract_address); diff --git a/yarn-project/end-to-end/src/e2e_stateful_test_contract.test.ts b/yarn-project/end-to-end/src/e2e_stateful_test_contract.test.ts new file mode 100644 index 00000000000..a5fe381ccf1 --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_stateful_test_contract.test.ts @@ -0,0 +1,101 @@ +/* eslint-disable camelcase */ +import { AztecNodeService } from '@aztec/aztec-node'; +import { CheatCodes, Fr, TxStatus, Wallet } from '@aztec/aztec.js'; +import { CircuitsWasm, CompleteAddress, FunctionSelector } from '@aztec/circuits.js'; +import { pedersenHashInputs, pedersenPlookupCommitInputs } from '@aztec/circuits.js/barretenberg'; +import { StatefulTestContract } from '@aztec/noir-contracts/types'; +import { MerkleTreeId, PXE } from '@aztec/types'; + +import { setup } from './fixtures/utils.js'; + +describe('e2e_deploy_contract', () => { + let aztecNode: AztecNodeService | undefined; + let pxe: PXE; + let accounts: CompleteAddress[]; + let wallet: Wallet; + let teardown: () => Promise; + let cheatCodes: CheatCodes; + + let statefulContract: StatefulTestContract; + + beforeEach(async () => { + ({ teardown, aztecNode, pxe, accounts, wallet, cheatCodes } = await setup()); + statefulContract = await StatefulTestContract.deploy(wallet, accounts[0].address, 1).send().deployed(); + }, 100_000); + + afterEach(() => teardown()); + + it('Check commitment inclusion in historic data', async () => { + if (!aztecNode) { + throw new Error('No aztec node'); + } + const owner = accounts[0].address; + + const tx = statefulContract.methods.create_note(owner, 99n).send(); + const receipt = await tx.wait(); + + const storageSlot = cheatCodes.aztec.computeSlotInMap(1n, owner); + const notes = await pxe.getPrivateStorageAt(owner, statefulContract.address, storageSlot); + const note = notes[1]; + + const nonces = await pxe.getNoteNonces(statefulContract.address, storageSlot, note, receipt.txHash); + + const valueNote = { + value: note.items[0], + owner: note.items[1], + randomness: note.items[2], + header: { + contract_address: statefulContract.address, + nonce: nonces[0], + storage_slot: storageSlot, + }, + }; + + const circuitsWasm = await CircuitsWasm.get(); + + // These functions should be the same after modifications to underlying. + const h1 = (a: Fr, b: Fr) => Fr.fromBuffer(pedersenPlookupCommitInputs(circuitsWasm, [a.toBuffer(), b.toBuffer()])); // Matches noir + const h2 = (a: Fr, b: Fr) => Fr.fromBuffer(pedersenHashInputs(circuitsWasm, [a.toBuffer(), b.toBuffer()])); // Matches kernel and circuits + + const commitment = new Fr(await statefulContract.methods.get_commitment(valueNote).view()); + const index = await aztecNode.getCommitmentIndex(commitment.toBuffer()); + const path = await statefulContract.methods.get_path(valueNote).view(); + const root = await statefulContract.methods.get_root(valueNote).view(); // computes root in noir using path + + const rootFromPath = (leaf: Fr, index: bigint, path: any[], hash: (a: Fr, b: Fr) => Fr) => { + const temps: bigint[] = []; + let node = leaf; + let i = index; + for (const sibling of path) { + if (i % 2n === 0n) { + node = hash(node, new Fr(sibling)); + } else { + node = hash(new Fr(sibling), node); + } + + temps.push(node.toBigInt()); + i /= 2n; + } + return node; + }; + + const myRootPedersenCommit = rootFromPath(commitment, index ?? 0n, path.slice(), h1); + const myRootPedersenHash = rootFromPath(commitment, index ?? 0n, path.slice(), h2); + + const historic = await aztecNode.getHistoricBlockData(); + + expect(historic.privateDataTreeRoot).toEqual(myRootPedersenCommit); + expect(historic.privateDataTreeRoot).toEqual(myRootPedersenHash); + expect(historic.privateDataTreeRoot).toEqual(new Fr(root)); + + const isIncludedTx = statefulContract.methods.is_included_in_history(valueNote).send(); + const isIncludedReceipt = await isIncludedTx.wait(); + expect(isIncludedReceipt.status).toEqual(TxStatus.MINED); + + const badValueNote = { ...valueNote, owner: 100n }; + const badLeafValue = new Fr(await statefulContract.methods.get_commitment(badValueNote).view()); + await expect(statefulContract.methods.is_included_in_history(badValueNote).simulate()).rejects.toThrowError( + `Leaf value: ${badLeafValue} not found in tree ${MerkleTreeId.PRIVATE_DATA_TREE}`, + ); + }, 30_000); +}); diff --git a/yarn-project/noir-contracts/src/contracts/stateful_test_contract/src/almost_value_note.nr b/yarn-project/noir-contracts/src/contracts/stateful_test_contract/src/almost_value_note.nr new file mode 100644 index 00000000000..1ccf8caa400 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/stateful_test_contract/src/almost_value_note.nr @@ -0,0 +1,39 @@ + +use dep::value_note::value_note::ValueNote; +use dep::aztec::note::note_header::NoteHeader; + +struct AlmostValueNote { + value: Field, + owner: Field, + randomness: Field, + header: NoteHeader, +} + +impl AlmostValueNote { + fn serialize(self) -> [Field; 6] { + [self.value, self.owner, self.randomness, self.header.contract_address, self.header.nonce, self.header.storage_slot] + } + + fn deserialize(serialized: [Field; 6]) -> Self { + AlmostValueNote { + value: serialized[0], + owner: serialized[1], + randomness: serialized[2], + header: NoteHeader { + contract_address: serialized[3], + nonce: serialized[4], + storage_slot: serialized[5], + is_transient: false, // TODO: should this be serialized and deserialized? + }, + } + } + + fn to_value_note(self) -> ValueNote { + ValueNote { + value: self.value, + owner: self.owner, + randomness: self.randomness, + header: self.header, + } + } +} \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/stateful_test_contract/src/historic_membership_oracle.nr b/yarn-project/noir-contracts/src/contracts/stateful_test_contract/src/historic_membership_oracle.nr new file mode 100644 index 00000000000..60c9efe07f8 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/stateful_test_contract/src/historic_membership_oracle.nr @@ -0,0 +1,22 @@ +use dep::aztec::constants_gen::NOTE_HASH_TREE_HEIGHT; +use dep::aztec::utils::arr_copy_slice; + +global NOTE_HASH_MEMBERSHIP_WITNESS_LEN: Field = 33; + +struct NoteHashMembershipWitness { + index: Field, + path: [Field; NOTE_HASH_TREE_HEIGHT], +} + +#[oracle(getMembershipWitness)] +fn get_note_hash_membership_witness_oracle(_treeId: Field, _commitment: Field) -> [Field; NOTE_HASH_MEMBERSHIP_WITNESS_LEN] {} + +unconstrained fn get_note_hash_membership_witness(commitment: Field) -> NoteHashMembershipWitness { + assert(NOTE_HASH_MEMBERSHIP_WITNESS_LEN == NOTE_HASH_TREE_HEIGHT + 1); + let fields = get_note_hash_membership_witness_oracle(2, commitment); + NoteHashMembershipWitness { + index: fields[0], + path: arr_copy_slice(fields, [0; NOTE_HASH_TREE_HEIGHT], 1) + } + +} \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/stateful_test_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/stateful_test_contract/src/main.nr index d43ed800a9e..9ad4f0d5162 100644 --- a/yarn-project/noir-contracts/src/contracts/stateful_test_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/stateful_test_contract/src/main.nr @@ -1,6 +1,10 @@ +mod historic_membership_oracle; +mod almost_value_note; + // A contract used for testing a random hodgepodge of small features from simulator and end-to-end tests. contract StatefulTest { use dep::std::option::Option; + use dep::std::merkle::compute_merkle_root; use dep::value_note::{ balance_utils, utils::{increment, decrement}, @@ -17,6 +21,10 @@ contract StatefulTest { FieldSerializationMethods, FIELD_SERIALIZED_LEN, }, }; + use dep::aztec::constants_gen::NOTE_HASH_TREE_HEIGHT; + use crate::historic_membership_oracle::{get_note_hash_membership_witness, NoteHashMembershipWitness}; + use dep::aztec::note::utils::compute_note_hash_for_read_or_nullify; + use crate::almost_value_note::AlmostValueNote; struct Storage { notes: Map>, @@ -82,6 +90,40 @@ contract StatefulTest { increment(recipient_notes, amount, recipient); } + // We cannot use valueNote directly because of its serialize here. Unconstrained are different. + #[aztec(private)] + fn is_included_in_history( + note: AlmostValueNote + ) { + let note = note.to_value_note(); + let commitment = compute_note_hash_for_read_or_nullify(ValueNoteMethods, note); + let witness = get_note_hash_membership_witness(commitment); + let root = compute_merkle_root(commitment, witness.index, witness.path); + assert(context.block_data.note_hash_tree_root == root, "Note is not included in history"); + } + + unconstrained fn get_path( + note: ValueNote + ) -> [Field; NOTE_HASH_TREE_HEIGHT] { + // compute the commitment + let commitment = compute_note_hash_for_read_or_nullify(ValueNoteMethods, note); + let witness = get_note_hash_membership_witness(commitment); + witness.path + } + + unconstrained fn get_commitment( + note: ValueNote + ) -> Field { + compute_note_hash_for_read_or_nullify(ValueNoteMethods, note) + } + + unconstrained fn get_root(note: ValueNote) -> Field { + // compute the commitment + let commitment = compute_note_hash_for_read_or_nullify(ValueNoteMethods, note); + let witness = get_note_hash_membership_witness(commitment); + compute_merkle_root(commitment, witness.index, witness.path) + } + unconstrained fn summed_values( owner: Field, ) -> Field { diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 53642ae9cd2..793a61e39c3 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -116,7 +116,26 @@ export class SimulatorOracle implements DBOracle { * @returns - The index of the commitment. Undefined if it does not exist in the tree. */ async getCommitmentIndex(commitment: Fr) { - return await this.stateInfoProvider.findLeafIndex(MerkleTreeId.NOTE_HASH_TREE, commitment); + return await this.findLeafIndex(MerkleTreeId.NOTE_HASH_TREE, commitment); + } + + public async findLeafIndex(treeId: MerkleTreeId, leafValue: Fr): Promise { + switch (treeId) { + case MerkleTreeId.NOTE_HASH_TREE: + return await this.stateInfoProvider.findLeafIndex(treeId, leafValue); + default: + throw new Error('Not implemented'); + } + } + + public async getSiblingPath(treeId: MerkleTreeId, leafIndex: bigint): Promise { + // @todo This is doing a nasty workaround as http_rpc_client was not happy about a generic `getSiblingPath` function being exposed. + switch (treeId) { + case MerkleTreeId.NOTE_HASH_TREE: + return (await this.stateInfoProvider.getNoteHashSiblingPath(leafIndex)).toFieldArray(); + default: + throw new Error('Not implemented'); + } } async getNullifierIndex(nullifier: Fr) { diff --git a/yarn-project/sequencer-client/src/simulator/public_executor.ts b/yarn-project/sequencer-client/src/simulator/public_executor.ts index affb9e1ccb6..66cac26866c 100644 --- a/yarn-project/sequencer-client/src/simulator/public_executor.ts +++ b/yarn-project/sequencer-client/src/simulator/public_executor.ts @@ -7,7 +7,7 @@ import { } from '@aztec/acir-simulator'; import { AztecAddress, CircuitsWasm, EthAddress, Fr, FunctionSelector, HistoricBlockData } from '@aztec/circuits.js'; import { computePublicDataTreeIndex } from '@aztec/circuits.js/abis'; -import { ContractDataSource, ExtendedContractData, L1ToL2MessageSource, MerkleTreeId, Tx } from '@aztec/types'; +import { ContractDataSource, ExtendedContractData, L1ToL2MessageSource, MerkleTreeId, SiblingPath, Tx } from '@aztec/types'; import { MerkleTreeOperations } from '@aztec/world-state'; /** @@ -148,4 +148,8 @@ export class WorldStateDB implements CommitmentsDB { public async getCommitmentIndex(commitment: Fr): Promise { return await this.db.findLeafIndex(MerkleTreeId.NOTE_HASH_TREE, commitment.toBuffer()); } + + public async getNoteHashSiblingPath(leafIndex: bigint): Promise> { + return await this.db.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex); + } }