From b3a6e31eac5d5b3ca3dcc63c5deae681f7b4c241 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 1 Mar 2024 15:55:45 -0300 Subject: [PATCH] feat: Allow nullifier historic proofs in public --- .../aztec-nr/aztec/src/context/interface.nr | 2 ++ .../aztec/src/context/private_context.nr | 12 ++++----- .../aztec/src/context/public_context.nr | 4 +++ .../aztec/src/history/nullifier_inclusion.nr | 6 ++--- .../inclusion_proofs_contract/src/main.nr | 6 +++++ .../src/e2e_inclusion_proofs_contract.test.ts | 7 +++++ .../pxe/src/simulator_oracle/index.ts | 4 +++ .../src/simulator/public_executor.ts | 26 +++++++++++++++++++ yarn-project/simulator/src/public/db.ts | 10 ++++++- .../src/public/public_execution_context.ts | 12 ++++++++- 10 files changed, 78 insertions(+), 11 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/context/interface.nr b/noir-projects/aztec-nr/aztec/src/context/interface.nr index 6b7c17c0e7f..b86969c7b5b 100644 --- a/noir-projects/aztec-nr/aztec/src/context/interface.nr +++ b/noir-projects/aztec-nr/aztec/src/context/interface.nr @@ -1,6 +1,7 @@ use dep::protocol_types::{ abis::function_selector::FunctionSelector, address::{AztecAddress, EthAddress}, + header::Header, }; trait ContextInterface { @@ -12,4 +13,5 @@ trait ContextInterface { fn chain_id(self) -> Field; fn version(self) -> Field; fn selector(self) -> FunctionSelector; + fn get_header(self) -> Header; } \ No newline at end of file diff --git a/noir-projects/aztec-nr/aztec/src/context/private_context.nr b/noir-projects/aztec-nr/aztec/src/context/private_context.nr index 7f6757f7455..7a60c69bdbf 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -93,6 +93,12 @@ impl ContextInterface for PrivateContext { self.inputs.call_context.function_selector } + // Returns the header of a block whose state is used during private execution (not the block the transaction is + // included in). + pub fn get_header(self) -> Header { + self.historical_header + } + fn push_new_note_hash(&mut self, note_hash: Field) { let side_effect = SideEffect { value: note_hash, counter: self.side_effect_counter }; self.new_note_hashes.push(side_effect); @@ -140,12 +146,6 @@ impl PrivateContext { false } - // Returns the header of a block whose state is used during private execution (not the block the transaction is - // included in). - pub fn get_header(self) -> Header { - self.historical_header - } - // Returns the header of an arbitrary block whose block number is less than or equal to the block number // of historical header. pub fn get_header_at(self, block_number: u32) -> Header { diff --git a/noir-projects/aztec-nr/aztec/src/context/public_context.nr b/noir-projects/aztec-nr/aztec/src/context/public_context.nr index 810934586f9..50a25dd96b9 100644 --- a/noir-projects/aztec-nr/aztec/src/context/public_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/public_context.nr @@ -71,6 +71,10 @@ impl ContextInterface for PublicContext { self.inputs.call_context.function_selector } + fn get_header(self) -> Header { + self.historical_header + } + fn push_new_note_hash(&mut self, note_hash: Field) { let side_effect = SideEffect { value: note_hash, counter: self.side_effect_counter }; self.new_note_hashes.push(side_effect); diff --git a/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr b/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr index e827d43c6b2..a199c0606d1 100644 --- a/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr +++ b/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr @@ -2,7 +2,7 @@ use dep::std::merkle::compute_merkle_root; use dep::protocol_types::header::Header; use crate::{ - context::PrivateContext, oracle::get_nullifier_membership_witness::get_nullifier_membership_witness, + context::{PrivateContext, ContextInterface}, oracle::get_nullifier_membership_witness::get_nullifier_membership_witness, note::{utils::compute_siloed_nullifier, note_interface::NoteInterface} }; @@ -25,8 +25,8 @@ fn _nullifier_inclusion(nullifier: Field, header: Header) { // was included in the nullifier tree. } -pub fn prove_nullifier_inclusion(nullifier: Field, context: PrivateContext) { - _nullifier_inclusion(nullifier, context.historical_header); +pub fn prove_nullifier_inclusion(nullifier: Field, context: TContext) where TContext: ContextInterface { + _nullifier_inclusion(nullifier, context.get_header()); } pub fn prove_nullifier_inclusion_at( diff --git a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr index 95385adc294..422a4100b1c 100644 --- a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr @@ -183,6 +183,12 @@ contract InclusionProofs { } } + // Proves nullifier existed at latest block + #[aztec(public)] + fn test_nullifier_inclusion_from_public(nullifier: Field) { + prove_nullifier_inclusion(nullifier, context); + } + #[aztec(private)] fn test_public_unused_value_inclusion(block_number: u32 // The block at which we'll prove that the public value exists ) { 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 index 37c3ee7318b..4d70a918e9a 100644 --- 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 @@ -228,6 +228,13 @@ describe('e2e_inclusion_proofs_contract', () => { await contract.methods.test_nullifier_inclusion(nullifier!, false, 0n).send().wait(); }); + it('proves existence of a nullifier in public context', async () => { + const block = await pxe.getBlock(deploymentBlockNumber); + const nullifier = block?.body.txEffects[0].nullifiers[0]; + + await contract.methods.test_nullifier_inclusion_from_public(nullifier!).send().wait(); + }); + it('nullifier existence failure case', async () => { // Choose random block number between first block and current block number to test archival node const blockNumber = await getRandomBlockNumber(); diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 32525ffd578..eae626c07c5 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -176,6 +176,10 @@ export class SimulatorOracle implements DBOracle { } } + public async getNullifierMembershipWitnessAtLatestBlock(nullifier: Fr) { + return this.getNullifierMembershipWitness(await this.getBlockNumber(), nullifier); + } + public getNullifierMembershipWitness( blockNumber: number, 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 a937c9dca81..3c7c7bdcbf1 100644 --- a/yarn-project/sequencer-client/src/simulator/public_executor.ts +++ b/yarn-project/sequencer-client/src/simulator/public_executor.ts @@ -3,6 +3,7 @@ import { ExtendedContractData, L1ToL2MessageSource, MerkleTreeId, + NullifierMembershipWitness, Tx, UnencryptedL2Log, } from '@aztec/circuit-types'; @@ -14,6 +15,8 @@ import { Fr, FunctionSelector, L1_TO_L2_MSG_TREE_HEIGHT, + NULLIFIER_TREE_HEIGHT, + NullifierLeafPreimage, PublicDataTreeLeafPreimage, } from '@aztec/circuits.js'; import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; @@ -217,6 +220,29 @@ export class WorldStatePublicDB implements PublicStateDB { export class WorldStateDB implements CommitmentsDB { constructor(private db: MerkleTreeOperations, private l1ToL2MessageSource: L1ToL2MessageSource) {} + public async getNullifierMembershipWitnessAtLatestBlock( + nullifier: Fr, + ): Promise { + const index = await this.db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()); + if (!index) { + return undefined; + } + + const leafPreimagePromise = this.db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index); + const siblingPathPromise = this.db.getSiblingPath( + MerkleTreeId.NULLIFIER_TREE, + BigInt(index), + ); + + const [leafPreimage, siblingPath] = await Promise.all([leafPreimagePromise, siblingPathPromise]); + + if (!leafPreimage) { + return undefined; + } + + return new NullifierMembershipWitness(BigInt(index), leafPreimage as NullifierLeafPreimage, siblingPath); + } + public async getL1ToL2MembershipWitness( entryKey: Fr, ): Promise> { diff --git a/yarn-project/simulator/src/public/db.ts b/yarn-project/simulator/src/public/db.ts index ba901a80854..316b14d6752 100644 --- a/yarn-project/simulator/src/public/db.ts +++ b/yarn-project/simulator/src/public/db.ts @@ -1,3 +1,4 @@ +import { NullifierMembershipWitness } from '@aztec/circuit-types'; import { EthAddress, FunctionSelector, L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; @@ -66,7 +67,7 @@ export interface PublicContractsDB { getPortalContractAddress(address: AztecAddress): Promise; } -/** Database interface for providing access to commitment tree and l1 to l2 message tree (append only data trees). */ +/** Database interface for providing access to commitment tree, l1 to l2 message tree, and nullifier tree. */ export interface CommitmentsDB { /** * Gets a confirmed L1 to L2 message for the given entry key. @@ -89,4 +90,11 @@ export interface CommitmentsDB { * @returns - The index of the nullifier. Undefined if it does not exist in the tree. */ getNullifierIndex(nullifier: Fr): Promise; + + /** + * Returns a nullifier membership witness for the given nullifier or undefined if not found. + * REFACTOR: Same as getL1ToL2MembershipWitness, can be combined with aztec-node method that does almost the same thing. + * @param nullifier - Nullifier we're looking for. + */ + getNullifierMembershipWitnessAtLatestBlock(nullifier: Fr): Promise; } diff --git a/yarn-project/simulator/src/public/public_execution_context.ts b/yarn-project/simulator/src/public/public_execution_context.ts index c60bfedacbd..70a8f6ceafd 100644 --- a/yarn-project/simulator/src/public/public_execution_context.ts +++ b/yarn-project/simulator/src/public/public_execution_context.ts @@ -1,4 +1,4 @@ -import { FunctionL2Logs, UnencryptedL2Log } from '@aztec/circuit-types'; +import { FunctionL2Logs, NullifierMembershipWitness, UnencryptedL2Log } from '@aztec/circuit-types'; import { CallContext, FunctionData, FunctionSelector, GlobalVariables, Header } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -226,4 +226,14 @@ export class PublicExecutionContext extends TypedOracle { return childExecutionResult.returnValues; } + + public async getNullifierMembershipWitness( + blockNumber: number, + nullifier: Fr, + ): Promise { + if (!this.header.globalVariables.blockNumber.equals(new Fr(blockNumber))) { + throw new Error(`Public execution oracle can only access nullifier membership witnesses for the current block`); + } + return await this.commitmentsDb.getNullifierMembershipWitnessAtLatestBlock(nullifier); + } }