diff --git a/docs/docs-developers/docs/aztec-nr/framework-description/advanced/how_to_prove_history.md b/docs/docs-developers/docs/aztec-nr/framework-description/advanced/how_to_prove_history.md index 60ca53ff58b2..2e54b20ab5f4 100644 --- a/docs/docs-developers/docs/aztec-nr/framework-description/advanced/how_to_prove_history.md +++ b/docs/docs-developers/docs/aztec-nr/framework-description/advanced/how_to_prove_history.md @@ -20,7 +20,7 @@ You can create proofs for these elements at any past block height: - **Note inclusion** - prove a note existed in the note hash tree - **Note validity** - prove a note existed and wasn't nullified at a specific block - **Nullifier inclusion/non-inclusion** - prove a nullifier was or wasn't in the nullifier tree -- **Contract deployment** - prove a contract was deployed or initialized +- **Contract deployment** - prove a contract's bytecode was published or initialized Common use cases: - Verify ownership of an asset from another contract without revealing which specific note @@ -29,7 +29,7 @@ Common use cases: ## Prove note inclusion -Import the trait: +Import the function: #include_code history_import noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr rust @@ -42,10 +42,10 @@ Prove a note exists in the note hash tree: To prove a note was valid (existed AND wasn't nullified) at a historical block: ```rust -use dep::aztec::history::note_validity::ProveNoteValidity; +use dep::aztec::history::note::assert_note_was_valid_by; let header = self.context.get_anchor_block_header(); -header.prove_note_validity(hinted_note, &mut self.context); +assert_note_was_valid_by(header, hinted_note, &mut self.context); ``` This verifies both: @@ -57,8 +57,10 @@ This verifies both: To prove against state at a specific past block (not just the anchor block): ```rust +use dep::aztec::history::note::assert_note_existed_by; + let historical_header = self.context.get_block_header_at(block_number); -historical_header.prove_note_inclusion(hinted_note); +assert_note_existed_by(historical_header, hinted_note); ``` :::warning @@ -70,45 +72,45 @@ Using `get_block_header_at` adds ~3k constraints to prove Archive tree membershi To prove a note has been spent/nullified: ```rust -use dep::aztec::history::nullifier_inclusion::ProveNoteIsNullified; +use dep::aztec::history::note::assert_note_was_nullified_by; let header = self.context.get_anchor_block_header(); -header.prove_note_is_nullified(hinted_note, &mut self.context); +assert_note_was_nullified_by(header, confirmed_note, &mut self.context); ``` -## Prove contract deployment +## Prove contract bytecode was published -To prove a contract was deployed at a historical block: +To prove a contract's bytecode was published at a historical block: ```rust -use dep::aztec::history::contract_inclusion::ProveContractDeployment; +use dep::aztec::history::deployment::assert_contract_bytecode_was_published_by; let header = self.context.get_anchor_block_header(); -header.prove_contract_deployment(contract_address); +assert_contract_bytecode_was_published_by(header, contract_address); ``` You can also prove a contract was initialized (constructor was called): ```rust -use dep::aztec::history::contract_inclusion::ProveContractInitialization; +use dep::aztec::history::deployment::assert_contract_was_initialized_by; let header = self.context.get_anchor_block_header(); -header.prove_contract_initialization(contract_address); +assert_contract_was_initialized_by(header, contract_address); ``` -## Available proof traits - -The `aztec::history` module provides these traits: - -| Trait | Purpose | -|-------|---------| -| `ProveNoteInclusion` | Prove note exists in note hash tree | -| `ProveNoteValidity` | Prove note exists and is not nullified | -| `ProveNoteIsNullified` | Prove note's nullifier is in nullifier tree | -| `ProveNoteNotNullified` | Prove note's nullifier is not in nullifier tree | -| `ProveNullifierInclusion` | Prove a raw nullifier exists | -| `ProveNullifierNonInclusion` | Prove a raw nullifier does not exist | -| `ProveContractDeployment` | Prove a contract was deployed | -| `ProveContractNonDeployment` | Prove a contract was not deployed | -| `ProveContractInitialization` | Prove a contract was initialized | -| `ProveContractNonInitialization` | Prove a contract was not initialized | +## Available proof functions + +The `aztec::history` module provides these functions: + +| Function | Module | Purpose | +|----------|--------|---------| +| `assert_note_existed_by` | `history::note` | Prove note exists in note hash tree | +| `assert_note_was_valid_by` | `history::note` | Prove note exists and is not nullified | +| `assert_note_was_nullified_by` | `history::note` | Prove note's nullifier is in nullifier tree | +| `assert_note_was_not_nullified_by` | `history::note` | Prove note's nullifier is not in nullifier tree | +| `assert_nullifier_existed_by` | `history::nullifier` | Prove a raw nullifier exists | +| `assert_nullifier_did_not_exist_by` | `history::nullifier` | Prove a raw nullifier does not exist | +| `assert_contract_bytecode_was_published_by` | `history::deployment` | Prove a contract's bytecode was published | +| `assert_contract_bytecode_was_not_published_by` | `history::deployment` | Prove a contract's bytecode was not published | +| `assert_contract_was_initialized_by` | `history::deployment` | Prove a contract was initialized | +| `assert_contract_was_not_initialized_by` | `history::deployment` | Prove a contract was not initialized | diff --git a/docs/docs-developers/docs/resources/migration_notes.md b/docs/docs-developers/docs/resources/migration_notes.md index a89e869c1731..d2d11cce0588 100644 --- a/docs/docs-developers/docs/resources/migration_notes.md +++ b/docs/docs-developers/docs/resources/migration_notes.md @@ -9,6 +9,35 @@ Aztec is in active development. Each version may introduce breaking changes that ## TBD +### [aztec-nr] History module refactored to use standalone functions + +The `aztec::history` module has been refactored to use standalone functions instead of traits. This changes the calling convention from method syntax to function syntax. + +```diff +- use dep::aztec::history::note_inclusion::ProveNoteInclusion; ++ use dep::aztec::history::note::assert_note_existed_by; + +let block_header = context.get_anchor_block_header(); +- let confirmed_note = block_header.prove_note_inclusion(hinted_note); ++ let confirmed_note = assert_note_existed_by(block_header, hinted_note); +``` + +**Function name and module mapping:** + +| Old (trait method) | New (standalone function) | +|--------------------|---------------------------| +| `history::note_inclusion::prove_note_inclusion` | `history::note::assert_note_existed_by` | +| `history::note_validity::prove_note_validity` | `history::note::assert_note_was_valid_by` | +| `history::nullifier_inclusion::prove_nullifier_inclusion` | `history::nullifier::assert_nullifier_existed_by` | +| `history::nullifier_inclusion::prove_note_is_nullified` | `history::note::assert_note_was_nullified_by` | +| `history::nullifier_non_inclusion::prove_nullifier_non_inclusion` | `history::nullifier::assert_nullifier_did_not_exist_by` | +| `history::nullifier_non_inclusion::prove_note_not_nullified` | `history::note::assert_note_was_not_nullified_by` | +| `history::contract_inclusion::prove_contract_deployment` | `history::deployment::assert_contract_bytecode_was_published_by` | +| `history::contract_inclusion::prove_contract_non_deployment` | `history::deployment::assert_contract_bytecode_was_not_published_by` | +| `history::contract_inclusion::prove_contract_initialization` | `history::deployment::assert_contract_was_initialized_by` | +| `history::contract_inclusion::prove_contract_non_initialization` | `history::deployment::assert_contract_was_not_initialized_by` | +| `history::public_storage::public_storage_historical_read` | `history::storage::public_storage_historical_read` | + ### [Aztec.js] Transaction sending API redesign The old chained `.send().wait()` pattern has been replaced with a single `.send(options)` call that handles both sending and waiting. @@ -4308,7 +4337,7 @@ await expect( ### [Aztec.nr] Public storage historical read API improvement -`history::public_value_inclusion::prove_public_value_inclusion` has been renamed to `history::public_storage::public_storage_historical_read`, and its API changed slightly. Instead of receiving a `value` parameter it now returns the historical value stored at that slot. +`history::public_value_inclusion::prove_public_value_inclusion` has been renamed to `history::storage::public_storage_historical_read`, and its API changed slightly. Instead of receiving a `value` parameter it now returns the historical value stored at that slot. If you were using an oracle to get the value to pass to `prove_public_value_inclusion`, drop the oracle and use the return value from `public_storage_historical_read` instead: 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 e98aaadf5065..9b3effee810e 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -746,9 +746,9 @@ impl PrivateContext { /// /// ## Historical Notes /// - /// If you need to assert that a note existed _at some specific time in the past_, instead of simply proving that it - /// exists in the current anchor block, use - /// [crate::history::note_inclusion::ProveNoteInclusion::prove_note_inclusion] instead. + /// If you need to assert that a note existed _by some specific block in the past_, instead of simply proving that it + /// exists by the current anchor block, use + /// [crate::history::note::assert_note_existed_by] instead. /// /// ## Cost /// @@ -784,9 +784,9 @@ impl PrivateContext { /// /// ## Historical Nullifiers /// - /// If you need to assert that a nullifier existed _at some specific time in the past_, instead of simply proving - /// that it exists in the current anchor block, use - /// [crate::history::nullifier_inclusion::ProveNullifierInclusion::prove_nullifier_inclusion] instead. + /// If you need to assert that a nullifier existed _by some specific block in the past_, instead of simply proving + /// that it exists by the current anchor block, use + /// [crate::history::nullifier::assert_nullifier_existed_by] instead. /// /// ## Public vs Private /// diff --git a/noir-projects/aztec-nr/aztec/src/history/contract_inclusion.nr b/noir-projects/aztec-nr/aztec/src/history/contract_inclusion.nr deleted file mode 100644 index d8e4b0484853..000000000000 --- a/noir-projects/aztec-nr/aztec/src/history/contract_inclusion.nr +++ /dev/null @@ -1,71 +0,0 @@ -use dep::protocol_types::{ - abis::block_header::BlockHeader, address::AztecAddress, - constants::CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, hash::compute_siloed_nullifier, - traits::ToField, -}; - -use crate::history::{ - nullifier_inclusion::ProveNullifierInclusion, - nullifier_non_inclusion::ProveNullifierNonInclusion, -}; - -// This is tested in `noir-projects/noir-contracts/test_contract/src/test.nr because we cannot define a contract -// from within aztec.nr (due to the contract macro). - -pub trait ProveContractDeployment { - fn prove_contract_deployment(header: BlockHeader, contract_address: AztecAddress); -} - -impl ProveContractDeployment for BlockHeader { - fn prove_contract_deployment(self, contract_address: AztecAddress) { - // Compute deployment nullifier - let nullifier = compute_siloed_nullifier( - CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, - contract_address.to_field(), - ); - - self.prove_nullifier_inclusion(nullifier); - } -} - -pub trait ProveContractNonDeployment { - fn prove_contract_non_deployment(header: BlockHeader, contract_address: AztecAddress); -} - -impl ProveContractNonDeployment for BlockHeader { - fn prove_contract_non_deployment(self, contract_address: AztecAddress) { - // Compute deployment nullifier - let nullifier = compute_siloed_nullifier( - CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, - contract_address.to_field(), - ); - - self.prove_nullifier_non_inclusion(nullifier); - } -} - -pub trait ProveContractInitialization { - fn prove_contract_initialization(header: BlockHeader, contract_address: AztecAddress); -} - -impl ProveContractInitialization for BlockHeader { - fn prove_contract_initialization(self, contract_address: AztecAddress) { - // Compute initialization nullifier - let nullifier = compute_siloed_nullifier(contract_address, contract_address.to_field()); - - self.prove_nullifier_inclusion(nullifier); - } -} - -pub trait ProveContractNonInitialization { - fn prove_contract_non_initialization(header: BlockHeader, contract_address: AztecAddress); -} - -impl ProveContractNonInitialization for BlockHeader { - fn prove_contract_non_initialization(self, contract_address: AztecAddress) { - // Compute initialization nullifier - let nullifier = compute_siloed_nullifier(contract_address, contract_address.to_field()); - - self.prove_nullifier_non_inclusion(nullifier); - } -} diff --git a/noir-projects/aztec-nr/aztec/src/history/deployment.nr b/noir-projects/aztec-nr/aztec/src/history/deployment.nr new file mode 100644 index 000000000000..4b2cf004e739 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/history/deployment.nr @@ -0,0 +1,56 @@ +//! Contract deployments. + +use dep::protocol_types::{ + abis::block_header::BlockHeader, address::AztecAddress, + constants::CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, hash::compute_siloed_nullifier, + traits::ToField, +}; + +use crate::history::nullifier::{assert_nullifier_did_not_exist_by, assert_nullifier_existed_by}; + +// This is tested in `noir-projects/noir-contracts/test_contract/src/test.nr because we cannot define a contract +// from within aztec.nr (due to the contract macro). + +pub fn assert_contract_bytecode_was_published_by( + block_header: BlockHeader, + contract_address: AztecAddress, +) { + let bytecode_publishing_nullifier = compute_siloed_nullifier( + CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, + contract_address.to_field(), + ); + + assert_nullifier_existed_by(block_header, bytecode_publishing_nullifier); +} + +pub fn assert_contract_bytecode_was_not_published_by( + block_header: BlockHeader, + contract_address: AztecAddress, +) { + let bytecode_publishing_nullifier = compute_siloed_nullifier( + CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, + contract_address.to_field(), + ); + + assert_nullifier_did_not_exist_by(block_header, bytecode_publishing_nullifier); +} + +pub fn assert_contract_was_initialized_by( + block_header: BlockHeader, + contract_address: AztecAddress, +) { + let initialization_nullifier = + compute_siloed_nullifier(contract_address, contract_address.to_field()); + + assert_nullifier_existed_by(block_header, initialization_nullifier); +} + +pub fn assert_contract_was_not_initialized_by( + block_header: BlockHeader, + contract_address: AztecAddress, +) { + let initialization_nullifier = + compute_siloed_nullifier(contract_address, contract_address.to_field()); + + assert_nullifier_did_not_exist_by(block_header, initialization_nullifier); +} diff --git a/noir-projects/aztec-nr/aztec/src/history/mod.nr b/noir-projects/aztec-nr/aztec/src/history/mod.nr index 027feb11c159..8070efbcf885 100644 --- a/noir-projects/aztec-nr/aztec/src/history/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/history/mod.nr @@ -1,9 +1,18 @@ //! Proofs of Aztec history. +//! +//! ## Data Availability +//! +//! Functions in these modules assert statements about the past state of the Aztec network. While this is possible in +//! theory, actual network nodes might not have the information required to produce proofs at relatively old blocks. +//! +//! In particular, many nodes prune old state and do not keep records of the different state trees (note hashes, +//! nullifiers, public storage, etc.) at blocks older than a couple hours, which are required in order to produce the +//! sibling paths these functions need. +//! +//! An archive node is therefore required when using any of these functions on non-recent blocks. -pub mod contract_inclusion; -pub mod note_inclusion; -pub mod note_validity; -pub mod nullifier_inclusion; -pub mod nullifier_non_inclusion; -pub mod public_storage; +pub mod deployment; +pub mod note; +pub mod nullifier; +pub mod storage; mod test; diff --git a/noir-projects/aztec-nr/aztec/src/history/note.nr b/noir-projects/aztec-nr/aztec/src/history/note.nr new file mode 100644 index 000000000000..11aae76e9f6a --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/history/note.nr @@ -0,0 +1,109 @@ +//! Note existence and non-nullification. + +use protocol_types::{ + abis::block_header::BlockHeader, + hash::{compute_siloed_note_hash, compute_siloed_nullifier, compute_unique_note_hash}, + merkle_tree::root::root_from_sibling_path, +}; + +use crate::{ + context::PrivateContext, + history::nullifier::{assert_nullifier_did_not_exist_by, assert_nullifier_existed_by}, + note::{ + ConfirmedNote, HintedNote, note_interface::NoteHash, + utils::compute_confirmed_note_hash_for_nullification, + }, + oracle::get_membership_witness::get_note_hash_membership_witness, +}; + +mod test; + +pub fn assert_note_existed_by( + block_header: BlockHeader, + hinted_note: HintedNote, +) -> ConfirmedNote +where + Note: NoteHash, +{ + let note_hash = hinted_note.note.compute_note_hash( + hinted_note.owner, + hinted_note.storage_slot, + hinted_note.randomness, + ); + + let siloed_note_hash = compute_siloed_note_hash(hinted_note.contract_address, note_hash); + + let unique_note_hash = compute_unique_note_hash( + hinted_note.metadata.to_settled().note_nonce(), + siloed_note_hash, + ); + + // Safety: The witness is only used as a "magical value" that makes the merkle proof below pass. Hence it's safe. + let witness = unsafe { get_note_hash_membership_witness(block_header, unique_note_hash) }; + + // Note inclusion is fairly straightforward, since all we need to prove is that a note exists in the note tree - + // we don't even care _where_ in the tree it is stored. This is because entries in the note hash tree are + // unique. + assert_eq( + block_header.state.partial.note_hash_tree.root, + root_from_sibling_path(unique_note_hash, witness.index, witness.path), + "Proving note inclusion failed", + ); + + ConfirmedNote::new(hinted_note, unique_note_hash) +} + +pub fn assert_note_was_valid_by( + block_header: BlockHeader, + hinted_note: HintedNote, + context: &mut PrivateContext, +) +where + Note: NoteHash, +{ + let confirmed_note = assert_note_existed_by(block_header, hinted_note); + assert_note_was_not_nullified_by(block_header, confirmed_note, context); +} + +pub fn assert_note_was_nullified_by( + block_header: BlockHeader, + confirmed_note: ConfirmedNote, + context: &mut PrivateContext, +) +where + Note: NoteHash, +{ + let note_hash_for_nullification = compute_confirmed_note_hash_for_nullification(confirmed_note); + let inner_nullifier = confirmed_note.note.compute_nullifier( + context, + confirmed_note.owner, + note_hash_for_nullification, + ); + + let siloed_nullifier = + compute_siloed_nullifier(confirmed_note.contract_address, inner_nullifier); + + assert_nullifier_existed_by(block_header, siloed_nullifier); +} + +pub fn assert_note_was_not_nullified_by( + block_header: BlockHeader, + confirmed_note: ConfirmedNote, + context: &mut PrivateContext, +) +where + Note: NoteHash, +{ + let note_hash_for_nullification = compute_confirmed_note_hash_for_nullification(confirmed_note); + + let inner_nullifier = confirmed_note.note.compute_nullifier( + context, + confirmed_note.owner, + note_hash_for_nullification, + ); + + let siloed_nullifier = + compute_siloed_nullifier(confirmed_note.contract_address, inner_nullifier); + + assert_nullifier_did_not_exist_by(block_header, siloed_nullifier); +} diff --git a/noir-projects/aztec-nr/aztec/src/history/note_validity/test.nr b/noir-projects/aztec-nr/aztec/src/history/note/test.nr similarity index 52% rename from noir-projects/aztec-nr/aztec/src/history/note_validity/test.nr rename to noir-projects/aztec-nr/aztec/src/history/note/test.nr index 2e7fa5c3f6ea..ccdd076be49a 100644 --- a/noir-projects/aztec-nr/aztec/src/history/note_validity/test.nr +++ b/noir-projects/aztec-nr/aztec/src/history/note/test.nr @@ -1,17 +1,42 @@ -use crate::history::note_validity::ProveNoteValidity; -use crate::history::test; +use crate::history::{note::{assert_note_existed_by, assert_note_was_valid_by}, test}; use crate::test::helpers::test_environment::PrivateContextOptions; +#[test] +unconstrained fn succeeds_on_blocks_after_note_creation() { + let (env, hinted_note) = test::create_note(); + + env.private_context_opts( + PrivateContextOptions::new().at_anchor_block_number(test::NOTE_CREATED_AT), + |context| { + let header = context.anchor_block_header; + let _ = assert_note_existed_by(header, hinted_note); + }, + ); +} + #[test(should_fail_with = "not found in the note hash tree at block")] unconstrained fn fails_on_blocks_before_note_creation() { + let (env, hinted_note) = test::create_note(); + + env.private_context_opts( + PrivateContextOptions::new().at_anchor_block_number(test::NOTE_CREATED_AT - 1), + |context| { + let header = context.anchor_block_header; + let _ = assert_note_existed_by(header, hinted_note); + }, + ); +} + +#[test(should_fail_with = "not found in the note hash tree at block")] +unconstrained fn validity_fails_on_blocks_before_note_creation() { let (env, hinted_note) = test::create_note_and_nullify_it(); env.private_context_opts( PrivateContextOptions::new().at_anchor_block_number(test::NOTE_CREATED_AT - 1), |context| { let header = context.anchor_block_header; - header.prove_note_validity(hinted_note, context); + assert_note_was_valid_by(header, hinted_note, context); }, ); } @@ -24,7 +49,7 @@ unconstrained fn succeeds_on_blocks_after_creation_and_before_nullification() { PrivateContextOptions::new().at_anchor_block_number(test::NOTE_CREATED_AT), |context| { let header = context.anchor_block_header; - header.prove_note_validity(hinted_note, context); + assert_note_was_valid_by(header, hinted_note, context); }, ); } @@ -37,7 +62,7 @@ unconstrained fn fails_on_blocks_after_note_nullification() { PrivateContextOptions::new().at_anchor_block_number(test::NOTE_NULLIFIED_AT), |context| { let header = context.anchor_block_header; - header.prove_note_validity(hinted_note, context); + assert_note_was_valid_by(header, hinted_note, context); }, ); } diff --git a/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr b/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr deleted file mode 100644 index 26890ac24c42..000000000000 --- a/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr +++ /dev/null @@ -1,55 +0,0 @@ -use protocol_types::{ - abis::block_header::BlockHeader, - hash::{compute_siloed_note_hash, compute_unique_note_hash}, - merkle_tree::root::root_from_sibling_path, -}; - -use crate::{ - note::{ConfirmedNote, HintedNote, note_interface::NoteHash}, - oracle::get_membership_witness::get_note_hash_membership_witness, -}; - -mod test; - -pub trait ProveNoteInclusion { - fn prove_note_inclusion( - header: BlockHeader, - hinted_note: HintedNote, - ) -> ConfirmedNote - where - Note: NoteHash; -} - -impl ProveNoteInclusion for BlockHeader { - fn prove_note_inclusion(self, hinted_note: HintedNote) -> ConfirmedNote - where - Note: NoteHash, - { - let note_hash = hinted_note.note.compute_note_hash( - hinted_note.owner, - hinted_note.storage_slot, - hinted_note.randomness, - ); - - let siloed_note_hash = compute_siloed_note_hash(hinted_note.contract_address, note_hash); - - let unique_note_hash = compute_unique_note_hash( - hinted_note.metadata.to_settled().note_nonce(), - siloed_note_hash, - ); - - // Safety: The witness is only used as a "magical value" that makes the merkle proof below pass. Hence it's safe. - let witness = unsafe { get_note_hash_membership_witness(self, unique_note_hash) }; - - // Note inclusion is fairly straightforward, since all we need to prove is that a note exists in the note tree - - // we don't even care _where_ in the tree it is stored. This is because entries in the note hash tree are - // unique. - assert_eq( - self.state.partial.note_hash_tree.root, - root_from_sibling_path(unique_note_hash, witness.index, witness.path), - "Proving note inclusion failed", - ); - - ConfirmedNote::new(hinted_note, unique_note_hash) - } -} diff --git a/noir-projects/aztec-nr/aztec/src/history/note_inclusion/test.nr b/noir-projects/aztec-nr/aztec/src/history/note_inclusion/test.nr deleted file mode 100644 index d8a1283899a6..000000000000 --- a/noir-projects/aztec-nr/aztec/src/history/note_inclusion/test.nr +++ /dev/null @@ -1,29 +0,0 @@ -use crate::history::{note_inclusion::ProveNoteInclusion, test}; - -use crate::test::helpers::test_environment::PrivateContextOptions; - -#[test] -unconstrained fn succeeds_on_blocks_after_note_creation() { - let (env, hinted_note) = test::create_note(); - - env.private_context_opts( - PrivateContextOptions::new().at_anchor_block_number(test::NOTE_CREATED_AT), - |context| { - let header = context.anchor_block_header; - let _ = header.prove_note_inclusion(hinted_note); - }, - ); -} - -#[test(should_fail_with = "not found in the note hash tree at block")] -unconstrained fn fails_on_blocks_before_note_creation() { - let (env, hinted_note) = test::create_note(); - - env.private_context_opts( - PrivateContextOptions::new().at_anchor_block_number(test::NOTE_CREATED_AT - 1), - |context| { - let header = context.anchor_block_header; - let _ = header.prove_note_inclusion(hinted_note); - }, - ); -} diff --git a/noir-projects/aztec-nr/aztec/src/history/note_validity.nr b/noir-projects/aztec-nr/aztec/src/history/note_validity.nr deleted file mode 100644 index 8202989420cb..000000000000 --- a/noir-projects/aztec-nr/aztec/src/history/note_validity.nr +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{ - context::PrivateContext, - history::{note_inclusion::ProveNoteInclusion, nullifier_non_inclusion::ProveNoteNotNullified}, - note::{HintedNote, note_interface::NoteHash}, -}; - -use protocol_types::abis::block_header::BlockHeader; - -mod test; - -trait ProveNoteValidity { - fn prove_note_validity( - header: BlockHeader, - hinted_note: HintedNote, - context: &mut PrivateContext, - ) - where - Note: NoteHash; -} - -impl ProveNoteValidity for BlockHeader { - fn prove_note_validity(self, hinted_note: HintedNote, context: &mut PrivateContext) - where - Note: NoteHash, - { - let confirmed_note = self.prove_note_inclusion(hinted_note); - self.prove_note_not_nullified(confirmed_note, context); - } -} diff --git a/noir-projects/aztec-nr/aztec/src/history/nullifier.nr b/noir-projects/aztec-nr/aztec/src/history/nullifier.nr new file mode 100644 index 000000000000..21ca867a6052 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/history/nullifier.nr @@ -0,0 +1,127 @@ +//! Nullifier existence and non-existence. + +use crate::oracle::get_nullifier_membership_witness::{ + get_low_nullifier_membership_witness, get_nullifier_membership_witness, +}; + +use protocol_types::{ + abis::block_header::BlockHeader, + merkle_tree::root::root_from_sibling_path, + traits::Hash, + utils::field::{full_field_greater_than, full_field_less_than}, +}; + +mod test; + +/// Asserts that a nullifier existed by the time a block was mined. +/// +/// This function takes a _siloed_ nullifier, i.e. the value that is actually stored in the tree. Use +/// [`crate::protocol_types::hash::compute_siloed_nullifier`] to convert an inner nullifier (what +/// [`PrivateContext::push_nullifier`](crate::context::PrivateContext::push_nullifier) takes) into a siloed one. +/// +/// Note that this does not mean that the nullifier was created **at** `block_header`, only that it was present in the +/// tree once all transactions from `block_header` were executed. +/// +/// In order to prove that a nullifier did **not** exist by `block_header`, use [`assert_nullifier_did_not_exist_by`]. +/// +/// ## Cost +/// +/// This function performs a full merkle tree inclusion proof, which is in the order of 4k gates. +/// +/// If you don't need to assert existence at a _specific_ past block, consider using +/// [`PrivateContext::assert_nullifier_exists`](crate::context::PrivateContext::assert_nullifier_exists) instead, which +/// is typically cheaper. Note that there are semantic differences though, as that function also considers _pending_ +/// nullifiers. +pub fn assert_nullifier_existed_by(block_header: BlockHeader, siloed_nullifier: Field) { + // 1) Get the membership witness of the nullifier + // Safety: The witness is only used as a "magical value" that makes the proof below pass. Hence it's safe. + let witness = unsafe { get_nullifier_membership_witness(block_header, siloed_nullifier) }; + + // 2) First we prove that the tree leaf in the witness is present in the nullifier tree. This is expected to be + // the leaf that contains the nullifier we're proving inclusion for. + assert_eq( + block_header.state.partial.nullifier_tree.root, + root_from_sibling_path(witness.leaf_preimage.hash(), witness.index, witness.path), + "Proving nullifier inclusion failed", + ); + + // 3) Then we simply check that the value in the leaf is the expected one. Note that we don't need to perform + // any checks on the rest of the values in the leaf preimage (the next index or next nullifier), since all we + // care about is showing that the tree contains an entry with the expected nullifier. + assert_eq( + witness.leaf_preimage.nullifier, + siloed_nullifier, + "Nullifier does not match value in witness", + ); +} + +/// Asserts that a nullifier did not exist by the time a block was mined. +/// +/// This function takes a _siloed_ nullifier, i.e. the value that is actually stored in the tree. Use +/// [`crate::protocol_types::hash::compute_siloed_nullifier`] to convert an inner nullifier (what +/// [`PrivateContext::push_nullifier`](crate::context::PrivateContext::push_nullifier) takes) into a siloed one. +/// +/// In order to prove that a nullifier **did** exist by `block_header`, use [`assert_nullifier_existed_by`]. +/// +/// ## Nullifier Non-Existence +/// +/// Proving nullifier non-existence is always nuanced, as it is not possible to privately prove that a nullifier does +/// not exist by the time a transaction is executed. What this function does instead is assert that once all +/// transactions from `block_header` were executed, the nullifier was not in the tree. +/// +/// If you **must** prove that a nullifier does not exist by the time a transaction is executed, there only two ways to +/// do this: by actually emitting the nullifier via +/// [`PrivateContext::push_nullifier`](crate::context::PrivateContext::push_nullifier) (which can of course can be done +/// once), or by calling a public contract function that calls +/// [`PublicContext::nullifier_exists_unsafe`](crate::context::PublicContext::nullifier_exists_unsafe) (which leaks that +/// this nullifier is being checked): +/// +/// ```noir +/// #[external("public")] +/// #[only_self] +/// fn _assert_nullifier_does_not_exist(unsileod_nullifier: Field, contract_address: AztecAddress) { +/// assert(!self.context.nullifier_exists_unsafe(unsiloed_nullifier, contract_address)); +/// } +/// ``` +/// +/// ## Cost +/// +/// This function performs a full merkle tree inclusion proof, which is in the order of 4k gates. +pub fn assert_nullifier_did_not_exist_by(block_header: BlockHeader, siloed_nullifier: Field) { + // 1) Get the membership witness of a low nullifier of the nullifier + // Safety: The witness is only used as a "magical value" that makes the proof below pass. Hence it's safe. + let witness = unsafe { get_low_nullifier_membership_witness(block_header, siloed_nullifier) }; + + // 2) Check that the leaf preimage is not empty. + // An empty leaf preimage would pass validation as a low leaf. However, it's not a valid low leaf. It's used to + // pad the nullifiers emitted from a tx so they can be inserted into the tree in a fixed-size batch. + assert( + !witness.leaf_preimage.is_empty(), + "The provided nullifier tree leaf preimage cannot be empty", + ); + + // 3) Prove that the tree leaf in the witness is present in the nullifier tree. This is expected to be + // the 'low leaf', i.e. the leaf that would come immediately before the nullifier's leaf, if the nullifier were + // to be in the tree. + let low_nullifier_leaf = witness.leaf_preimage; + assert_eq( + block_header.state.partial.nullifier_tree.root, + root_from_sibling_path(low_nullifier_leaf.hash(), witness.index, witness.path), + "Proving nullifier non-inclusion failed: Could not prove low nullifier inclusion", + ); + + // 4) Prove that the low leaf is indeed smaller than the nullifier + assert( + full_field_less_than(low_nullifier_leaf.nullifier, siloed_nullifier), + "Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed", + ); + + // 5) Prove that the low leaf is pointing "over" the nullifier, which means that the nullifier is not included + // in the nullifier tree, since if it were it'd need to be between the low leaf and the next leaf. Note the + // special case in which the low leaf is the largest of all entries, in which case there's no 'next' entry. + assert( + full_field_greater_than(low_nullifier_leaf.next_nullifier, siloed_nullifier) + | (low_nullifier_leaf.next_index == 0), + "Proving nullifier non-inclusion failed: low_nullifier.next_value > nullifier.value check failed", + ); +} diff --git a/noir-projects/aztec-nr/aztec/src/history/nullifier/test.nr b/noir-projects/aztec-nr/aztec/src/history/nullifier/test.nr new file mode 100644 index 000000000000..785b19fa9df1 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/history/nullifier/test.nr @@ -0,0 +1,138 @@ +use crate::history::{ + note::{assert_note_existed_by, assert_note_was_not_nullified_by, assert_note_was_nullified_by}, + nullifier::{assert_nullifier_did_not_exist_by, assert_nullifier_existed_by}, + test, +}; +use crate::oracle::{nullifiers::notify_created_nullifier, random::random}; +use crate::test::helpers::test_environment::{PrivateContextOptions, TestEnvironment}; +use dep::protocol_types::{ + constants::DOM_SEP__OUTER_NULLIFIER, hash::poseidon2_hash_with_separator, traits::ToField, +}; + +#[test] +unconstrained fn note_is_nullified_succeeds_on_blocks_after_note_nullification() { + let (env, hinted_note) = test::create_note_and_nullify_it(); + + env.private_context_opts( + PrivateContextOptions::new().at_anchor_block_number(test::NOTE_NULLIFIED_AT), + |context| { + let header = context.anchor_block_header; + let confirmed_note = assert_note_existed_by(header, hinted_note); + assert_note_was_nullified_by(header, confirmed_note, context); + }, + ); +} + +#[test(should_fail_with = "Nullifier membership witness not found at block")] +unconstrained fn note_is_nullified_fails_on_blocks_before_note_nullification() { + let (env, hinted_note) = test::create_note_and_nullify_it(); + + env.private_context_opts( + PrivateContextOptions::new().at_anchor_block_number(test::NOTE_NULLIFIED_AT - 1), + |context| { + let header = context.anchor_block_header; + let confirmed_note = assert_note_existed_by(header, hinted_note); + assert_note_was_nullified_by(header, confirmed_note, context); + }, + ); +} + +#[test] +unconstrained fn assert_nullifier_existed_by_succeeds_on_blocks_after_nullifier_creation() { + let env = TestEnvironment::new(); + + let siloed_nullifier = env.private_context(|context| { + let unsiloed_nullifier = 42069; + notify_created_nullifier(unsiloed_nullifier); + + // We need to compute the siloed nullifier so we can check for its inclusion in state. + poseidon2_hash_with_separator( + [context.this_address().to_field(), unsiloed_nullifier], + DOM_SEP__OUTER_NULLIFIER, + ) + }); + + let nullifier_created_at = env.last_block_number(); + + env.private_context_opts( + PrivateContextOptions::new().at_anchor_block_number(nullifier_created_at), + |context| { + let header = context.anchor_block_header; + assert_nullifier_existed_by(header, siloed_nullifier); + }, + ); +} + +#[test(should_fail_with = "Nullifier membership witness not found")] +unconstrained fn assert_nullifier_existed_by_fails_on_blocks_before_nullifier_creation() { + let env = TestEnvironment::new(); + + env.private_context(|context| { + let header = context.anchor_block_header; + assert_nullifier_existed_by(header, random()); + }); +} + +#[test] +unconstrained fn note_not_nullified_succeeds_in_blocks_before_note_nullification() { + let (env, hinted_note) = test::create_note_and_nullify_it(); + + env.private_context_opts( + PrivateContextOptions::new().at_anchor_block_number(test::NOTE_NULLIFIED_AT - 1), + |context| { + let header = context.anchor_block_header; + let confirmed_note = assert_note_existed_by(header, hinted_note); + assert_note_was_not_nullified_by(header, confirmed_note, context); + }, + ); +} + +#[test(should_fail_with = "Proving nullifier non-inclusion failed")] +unconstrained fn note_not_nullified_fails_in_blocks_after_note_nullification_fails() { + let (env, hinted_note) = test::create_note_and_nullify_it(); + + env.private_context_opts( + PrivateContextOptions::new().at_anchor_block_number(test::NOTE_NULLIFIED_AT), + |context| { + let header = context.anchor_block_header; + let confirmed_note = assert_note_existed_by(header, hinted_note); + assert_note_was_not_nullified_by(header, confirmed_note, context); + }, + ); +} + +#[test] +unconstrained fn nullifier_non_inclusion_succeeds_in_blocks_before_nullifier_creation() { + let env = TestEnvironment::new(); + + env.private_context(|context| { + let header = context.anchor_block_header; + assert_nullifier_did_not_exist_by(header, random()); + }); +} + +#[test(should_fail_with = "Proving nullifier non-inclusion failed")] +unconstrained fn nullifier_non_inclusion_fails_in_blocks_after_nullifier_creation() { + let env = TestEnvironment::new(); + + let siloed_nullifier = env.private_context(|context| { + let unsiloed_nullifier = 42069; + notify_created_nullifier(unsiloed_nullifier); + + // We need to compute the siloed nullifier so we can check for its inclusion in state. + poseidon2_hash_with_separator( + [context.this_address().to_field(), unsiloed_nullifier], + DOM_SEP__OUTER_NULLIFIER, + ) + }); + + let nullifier_created_at = env.last_block_number(); + + env.private_context_opts( + PrivateContextOptions::new().at_anchor_block_number(nullifier_created_at), + |context| { + let header = context.anchor_block_header; + assert_nullifier_did_not_exist_by(header, siloed_nullifier); + }, + ); +} diff --git a/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr b/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr deleted file mode 100644 index 3a3b9dc4677c..000000000000 --- a/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr +++ /dev/null @@ -1,78 +0,0 @@ -use crate::{ - context::PrivateContext, - note::{ - ConfirmedNote, note_interface::NoteHash, - utils::compute_confirmed_note_hash_for_nullification, - }, - oracle::get_nullifier_membership_witness::get_nullifier_membership_witness, -}; - -use protocol_types::{ - abis::block_header::BlockHeader, hash::compute_siloed_nullifier, - merkle_tree::root::root_from_sibling_path, traits::Hash, -}; - -mod test; - -pub trait ProveNullifierInclusion { - fn prove_nullifier_inclusion(header: BlockHeader, nullifier: Field); -} - -impl ProveNullifierInclusion for BlockHeader { - fn prove_nullifier_inclusion(self, nullifier: Field) { - // 1) Get the membership witness of the nullifier - // Safety: The witness is only used as a "magical value" that makes the proof below pass. Hence it's safe. - let witness = unsafe { get_nullifier_membership_witness(self, nullifier) }; - - // 2) First we prove that the tree leaf in the witness is present in the nullifier tree. This is expected to be - // the leaf that contains the nullifier we're proving inclusion for. - assert_eq( - self.state.partial.nullifier_tree.root, - root_from_sibling_path(witness.leaf_preimage.hash(), witness.index, witness.path), - "Proving nullifier inclusion failed", - ); - - // 3) Then we simply check that the value in the leaf is the expected one. Note that we don't need to perform - // any checks on the rest of the values in the leaf preimage (the next index or next nullifier), since all we - // care about is showing that the tree contains an entry with the expected nullifier. - assert_eq( - witness.leaf_preimage.nullifier, - nullifier, - "Nullifier does not match value in witness", - ); - } -} - -pub trait ProveNoteIsNullified { - fn prove_note_is_nullified( - header: BlockHeader, - confirmed_note: ConfirmedNote, - context: &mut PrivateContext, - ) - where - Note: NoteHash; -} - -impl ProveNoteIsNullified for BlockHeader { - fn prove_note_is_nullified( - self, - confirmed_note: ConfirmedNote, - context: &mut PrivateContext, - ) - where - Note: NoteHash, - { - let note_hash_for_nullification = - compute_confirmed_note_hash_for_nullification(confirmed_note); - let inner_nullifier = confirmed_note.note.compute_nullifier( - context, - confirmed_note.owner, - note_hash_for_nullification, - ); - - let siloed_nullifier = - compute_siloed_nullifier(confirmed_note.contract_address, inner_nullifier); - - self.prove_nullifier_inclusion(siloed_nullifier); - } -} diff --git a/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion/test.nr b/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion/test.nr deleted file mode 100644 index 6f3e65c0e713..000000000000 --- a/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion/test.nr +++ /dev/null @@ -1,74 +0,0 @@ -use crate::history::{ - note_inclusion::ProveNoteInclusion, - nullifier_inclusion::{ProveNoteIsNullified, ProveNullifierInclusion}, - test, -}; -use crate::oracle::{nullifiers::notify_created_nullifier, random::random}; -use crate::test::helpers::test_environment::{PrivateContextOptions, TestEnvironment}; -use dep::protocol_types::{ - constants::DOM_SEP__OUTER_NULLIFIER, hash::poseidon2_hash_with_separator, traits::ToField, -}; - -#[test] -unconstrained fn note_is_nullified_succeeds_on_blocks_after_note_nullification() { - let (env, hinted_note) = test::create_note_and_nullify_it(); - - env.private_context_opts( - PrivateContextOptions::new().at_anchor_block_number(test::NOTE_NULLIFIED_AT), - |context| { - let header = context.anchor_block_header; - let confirmed_note = header.prove_note_inclusion(hinted_note); - header.prove_note_is_nullified(confirmed_note, context); - }, - ); -} - -#[test(should_fail_with = "Nullifier membership witness not found at block")] -unconstrained fn note_is_nullified_fails_on_blocks_before_note_nullification() { - let (env, hinted_note) = test::create_note_and_nullify_it(); - - env.private_context_opts( - PrivateContextOptions::new().at_anchor_block_number(test::NOTE_NULLIFIED_AT - 1), - |context| { - let header = context.anchor_block_header; - let confirmed_note = header.prove_note_inclusion(hinted_note); - header.prove_note_is_nullified(confirmed_note, context); - }, - ); -} - -#[test] -unconstrained fn prove_nullifier_inclusion_succeeds_on_blocks_after_nullifier_creation() { - let env = TestEnvironment::new(); - - let siloed_nullifier = env.private_context(|context| { - let unsiloed_nullifier = 42069; - notify_created_nullifier(unsiloed_nullifier); - - // We need to compute the siloed nullifier so we can check for its inclusion in state. - poseidon2_hash_with_separator( - [context.this_address().to_field(), unsiloed_nullifier], - DOM_SEP__OUTER_NULLIFIER, - ) - }); - - let nullifier_created_at = env.last_block_number(); - - env.private_context_opts( - PrivateContextOptions::new().at_anchor_block_number(nullifier_created_at), - |context| { - let header = context.anchor_block_header; - header.prove_nullifier_inclusion(siloed_nullifier); - }, - ); -} - -#[test(should_fail_with = "Nullifier membership witness not found")] -unconstrained fn prove_nullifier_inclusion_fails_on_blocks_before_nullifier_creation() { - let env = TestEnvironment::new(); - - env.private_context(|context| { - let header = context.anchor_block_header; - header.prove_nullifier_inclusion(random()); - }); -} diff --git a/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion.nr b/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion.nr deleted file mode 100644 index 6462b3c8fdb5..000000000000 --- a/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion.nr +++ /dev/null @@ -1,97 +0,0 @@ -use crate::{ - context::PrivateContext, - note::{ - ConfirmedNote, note_interface::NoteHash, - utils::compute_confirmed_note_hash_for_nullification, - }, - oracle::get_nullifier_membership_witness::get_low_nullifier_membership_witness, -}; -use protocol_types::{ - abis::block_header::BlockHeader, - hash::compute_siloed_nullifier, - merkle_tree::root::root_from_sibling_path, - traits::Hash, - utils::field::{full_field_greater_than, full_field_less_than}, -}; - -mod test; - -pub trait ProveNullifierNonInclusion { - fn prove_nullifier_non_inclusion(header: BlockHeader, nullifier: Field); -} - -impl ProveNullifierNonInclusion for BlockHeader { - fn prove_nullifier_non_inclusion(self, nullifier: Field) { - // 1) Get the membership witness of a low nullifier of the nullifier - // Safety: The witness is only used as a "magical value" that makes the proof below pass. Hence it's safe. - let witness = unsafe { get_low_nullifier_membership_witness(self, nullifier) }; - - // 2) Check that the leaf preimage is not empty. - // An empty leaf preimage would pass validation as a low leaf. However, it's not a valid low leaf. It's used to - // pad the nullifiers emitted from a tx so they can be inserted into the tree in a fixed-size batch. - assert( - !witness.leaf_preimage.is_empty(), - "The provided public data tree leaf preimage cannot be empty", - ); - - // 3) Prove that the tree leaf in the witness is present in the nullifier tree. This is expected to be - // the 'low leaf', i.e. the leaf that would come immediately before the nullifier's leaf, if the nullifier were - // to be in the tree. - let low_nullifier_leaf = witness.leaf_preimage; - assert_eq( - self.state.partial.nullifier_tree.root, - root_from_sibling_path(low_nullifier_leaf.hash(), witness.index, witness.path), - "Proving nullifier non-inclusion failed: Could not prove low nullifier inclusion", - ); - - // 4) Prove that the low leaf is indeed smaller than the nullifier - assert( - full_field_less_than(low_nullifier_leaf.nullifier, nullifier), - "Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed", - ); - - // 5) Prove that the low leaf is pointing "over" the nullifier, which means that the nullifier is not included - // in the nullifier tree, since if it were it'd need to be between the low leaf and the next leaf. Note the - // special case in which the low leaf is the largest of all entries, in which case there's no 'next' entry. - assert( - full_field_greater_than(low_nullifier_leaf.next_nullifier, nullifier) - | (low_nullifier_leaf.next_index == 0), - "Proving nullifier non-inclusion failed: low_nullifier.next_value > nullifier.value check failed", - ); - } -} - -pub trait ProveNoteNotNullified { - fn prove_note_not_nullified( - header: BlockHeader, - confirmed_note: ConfirmedNote, - context: &mut PrivateContext, - ) - where - Note: NoteHash; -} - -impl ProveNoteNotNullified for BlockHeader { - fn prove_note_not_nullified( - self, - confirmed_note: ConfirmedNote, - context: &mut PrivateContext, - ) - where - Note: NoteHash, - { - let note_hash_for_nullification = - compute_confirmed_note_hash_for_nullification(confirmed_note); - - let inner_nullifier = confirmed_note.note.compute_nullifier( - context, - confirmed_note.owner, - note_hash_for_nullification, - ); - - let siloed_nullifier = - compute_siloed_nullifier(confirmed_note.contract_address, inner_nullifier); - - self.prove_nullifier_non_inclusion(siloed_nullifier); - } -} diff --git a/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion/test.nr b/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion/test.nr deleted file mode 100644 index 2cc531973a70..000000000000 --- a/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion/test.nr +++ /dev/null @@ -1,74 +0,0 @@ -use crate::history::{ - note_inclusion::ProveNoteInclusion, - nullifier_non_inclusion::{ProveNoteNotNullified, ProveNullifierNonInclusion}, - test, -}; -use crate::oracle::{nullifiers::notify_created_nullifier, random::random}; -use crate::test::helpers::test_environment::{PrivateContextOptions, TestEnvironment}; -use dep::protocol_types::{ - constants::DOM_SEP__OUTER_NULLIFIER, hash::poseidon2_hash_with_separator, traits::ToField, -}; - -#[test] -unconstrained fn note_not_nullified_succeeds_in_blocks_before_note_nullification() { - let (env, hinted_note) = test::create_note_and_nullify_it(); - - env.private_context_opts( - PrivateContextOptions::new().at_anchor_block_number(test::NOTE_NULLIFIED_AT - 1), - |context| { - let header = context.anchor_block_header; - let confirmed_note = header.prove_note_inclusion(hinted_note); - header.prove_note_not_nullified(confirmed_note, context); - }, - ); -} - -#[test(should_fail_with = "Proving nullifier non-inclusion failed")] -unconstrained fn note_not_nullified_fails_in_blocks_after_note_nullification_fails() { - let (env, hinted_note) = test::create_note_and_nullify_it(); - - env.private_context_opts( - PrivateContextOptions::new().at_anchor_block_number(test::NOTE_NULLIFIED_AT), - |context| { - let header = context.anchor_block_header; - let confirmed_note = header.prove_note_inclusion(hinted_note); - header.prove_note_not_nullified(confirmed_note, context); - }, - ); -} - -#[test] -unconstrained fn nullifier_non_inclusion_succeeds_in_blocks_before_nullifier_creation() { - let env = TestEnvironment::new(); - - env.private_context(|context| { - let header = context.anchor_block_header; - header.prove_nullifier_non_inclusion(random()); - }); -} - -#[test(should_fail_with = "Proving nullifier non-inclusion failed")] -unconstrained fn nullifier_non_inclusion_fails_in_blocks_after_nullifier_creation() { - let env = TestEnvironment::new(); - - let siloed_nullifier = env.private_context(|context| { - let unsiloed_nullifier = 42069; - notify_created_nullifier(unsiloed_nullifier); - - // We need to compute the siloed nullifier so we can check for its inclusion in state. - poseidon2_hash_with_separator( - [context.this_address().to_field(), unsiloed_nullifier], - DOM_SEP__OUTER_NULLIFIER, - ) - }); - - let nullifier_created_at = env.last_block_number(); - - env.private_context_opts( - PrivateContextOptions::new().at_anchor_block_number(nullifier_created_at), - |context| { - let header = context.anchor_block_header; - header.prove_nullifier_non_inclusion(siloed_nullifier); - }, - ); -} diff --git a/noir-projects/aztec-nr/aztec/src/history/public_storage.nr b/noir-projects/aztec-nr/aztec/src/history/public_storage.nr deleted file mode 100644 index 57ef0f2ad351..000000000000 --- a/noir-projects/aztec-nr/aztec/src/history/public_storage.nr +++ /dev/null @@ -1,42 +0,0 @@ -use dep::protocol_types::{ - abis::block_header::BlockHeader, address::AztecAddress, constants::DOM_SEP__PUBLIC_LEAF_INDEX, - data::public_data_storage_read, hash::poseidon2_hash_with_separator, - merkle_tree::MembershipWitness, traits::ToField, -}; - -use crate::oracle::get_public_data_witness::get_public_data_witness; - -mod test; - -pub trait PublicStorageHistoricalRead { - fn public_storage_historical_read( - header: BlockHeader, - storage_slot: Field, - contract_address: AztecAddress, - ) -> Field; -} - -impl PublicStorageHistoricalRead for BlockHeader { - fn public_storage_historical_read( - self, - storage_slot: Field, - contract_address: AztecAddress, - ) -> Field { - // 1) Compute the leaf index by siloing the storage slot with the contract address - let public_data_tree_index = poseidon2_hash_with_separator( - [contract_address.to_field(), storage_slot], - DOM_SEP__PUBLIC_LEAF_INDEX, - ); - - // 2) Get the membership witness for the tree index. - // Safety: The witness is only used as a "magical value" that makes the proof below pass. Hence it's safe. - let witness = unsafe { get_public_data_witness(self, public_data_tree_index) }; - - public_data_storage_read( - self.state.partial.public_data_tree.root, - public_data_tree_index, - MembershipWitness { leaf_index: witness.index, sibling_path: witness.path }, - witness.leaf_preimage, - ) - } -} diff --git a/noir-projects/aztec-nr/aztec/src/history/storage.nr b/noir-projects/aztec-nr/aztec/src/history/storage.nr new file mode 100644 index 000000000000..71f1ece50542 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/history/storage.nr @@ -0,0 +1,34 @@ +//! Historical storage accesses. + +use dep::protocol_types::{ + abis::block_header::BlockHeader, address::AztecAddress, constants::DOM_SEP__PUBLIC_LEAF_INDEX, + data::public_data_storage_read, hash::poseidon2_hash_with_separator, + merkle_tree::MembershipWitness, traits::ToField, +}; + +use crate::oracle::get_public_data_witness::get_public_data_witness; + +mod test; + +pub fn public_storage_historical_read( + block_header: BlockHeader, + storage_slot: Field, + contract_address: AztecAddress, +) -> Field { + // 1) Compute the leaf index by siloing the storage slot with the contract address + let public_data_tree_index = poseidon2_hash_with_separator( + [contract_address.to_field(), storage_slot], + DOM_SEP__PUBLIC_LEAF_INDEX, + ); + + // 2) Get the membership witness for the tree index. + // Safety: The witness is only used as a "magical value" that makes the proof below pass. Hence it's safe. + let witness = unsafe { get_public_data_witness(block_header, public_data_tree_index) }; + + public_data_storage_read( + block_header.state.partial.public_data_tree.root, + public_data_tree_index, + MembershipWitness { leaf_index: witness.index, sibling_path: witness.path }, + witness.leaf_preimage, + ) +} diff --git a/noir-projects/aztec-nr/aztec/src/history/public_storage/test.nr b/noir-projects/aztec-nr/aztec/src/history/storage/test.nr similarity index 81% rename from noir-projects/aztec-nr/aztec/src/history/public_storage/test.nr rename to noir-projects/aztec-nr/aztec/src/history/storage/test.nr index 8f495f9e0201..5066cbda7d86 100644 --- a/noir-projects/aztec-nr/aztec/src/history/public_storage/test.nr +++ b/noir-projects/aztec-nr/aztec/src/history/storage/test.nr @@ -1,5 +1,5 @@ use crate::{ - history::public_storage::PublicStorageHistoricalRead, + history::storage::public_storage_historical_read, test::helpers::test_environment::{PrivateContextOptions, TestEnvironment}, }; @@ -12,7 +12,8 @@ unconstrained fn historical_no_write_public_storage_read() { env.private_context(|context| { assert_eq( - context.anchor_block_header.public_storage_historical_read( + public_storage_historical_read( + context.anchor_block_header, STORAGE_SLOT, context.this_address(), ), @@ -33,7 +34,8 @@ unconstrained fn historical_pre_write_public_storage_read() { PrivateContextOptions::new().at_anchor_block_number(write_block_number - 1), |context| { assert_eq( - context.anchor_block_header.public_storage_historical_read( + public_storage_historical_read( + context.anchor_block_header, STORAGE_SLOT, context.this_address(), ), @@ -51,7 +53,8 @@ unconstrained fn historical_post_write_public_storage_read() { env.private_context(|context| { assert_eq( - context.anchor_block_header.public_storage_historical_read( + public_storage_historical_read( + context.anchor_block_header, STORAGE_SLOT, context.this_address(), ), diff --git a/noir-projects/aztec-nr/aztec/src/note/confirmed_note.nr b/noir-projects/aztec-nr/aztec/src/note/confirmed_note.nr index 1cc86fc76d88..522336d54fb7 100644 --- a/noir-projects/aztec-nr/aztec/src/note/confirmed_note.nr +++ b/noir-projects/aztec-nr/aztec/src/note/confirmed_note.nr @@ -11,7 +11,7 @@ use protocol_types::address::AztecAddress; /// /// `ConfirmedNote`s can be obtained by reading notes from PXE via [crate::note::note_getter::get_note] and /// [crate::note::note_getter::get_notes], or by confirming a [crate::note::hinted_note::HintedNote] via -/// [crate::history::note_inclusion::ProveNoteInclusion::prove_note_inclusion]. +/// [crate::history::note::assert_note_existed_by]. /// /// ## Pending Notes /// diff --git a/noir-projects/aztec-nr/aztec/src/note/hinted_note.nr b/noir-projects/aztec-nr/aztec/src/note/hinted_note.nr index 7084282e77b2..c9a685b35026 100644 --- a/noir-projects/aztec-nr/aztec/src/note/hinted_note.nr +++ b/noir-projects/aztec-nr/aztec/src/note/hinted_note.nr @@ -8,7 +8,7 @@ use protocol_types::{address::AztecAddress, traits::{Deserialize, Packable, Seri /// /// This value typically unconstrained (originating from oracles or as a contract function parameter), and can be /// converted into a [crate::note::confirmed_note::ConfirmedNote] via -/// [crate::history::note_inclusion::ProveNoteInclusion::prove_note_inclusion]. +/// [crate::history::note::assert_note_existed_by]. #[derive(Deserialize, Eq, Serialize, Packable)] pub struct HintedNote { pub note: Note, diff --git a/noir-projects/aztec-nr/aztec/src/note/mod.nr b/noir-projects/aztec-nr/aztec/src/note/mod.nr index d8fd8d1d8ee2..360af4800d11 100644 --- a/noir-projects/aztec-nr/aztec/src/note/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/note/mod.nr @@ -1,4 +1,7 @@ //! Note traits and utilities. +//! +//! ## Siloing +//! pub mod constants; pub mod lifecycle; diff --git a/noir-projects/aztec-nr/aztec/src/utils/with_hash.nr b/noir-projects/aztec-nr/aztec/src/utils/with_hash.nr index 429801328556..0a7585b145f8 100644 --- a/noir-projects/aztec-nr/aztec/src/utils/with_hash.nr +++ b/noir-projects/aztec-nr/aztec/src/utils/with_hash.nr @@ -1,6 +1,6 @@ use crate::{ context::{PublicContext, UtilityContext}, - history::public_storage::PublicStorageHistoricalRead, + history::storage::public_storage_historical_read, oracle, }; use dep::protocol_types::{ @@ -74,7 +74,7 @@ where // The actual `value` (of type T, of packed length M fields) is stored in contiguous fields from the `storage_slot`. // The _hash_ of the `value` is stored at the end, at slot: `storage_slot + M`. let hash = - header_to_read_from.public_storage_historical_read(storage_slot + M as Field, address); + public_storage_historical_read(header_to_read_from, storage_slot + M as Field, address); if hash != 0 { assert_eq(hash, hint.get_hash(), "Hint values do not match hash"); diff --git a/noir-projects/aztec-nr/uint-note/src/uint_note.nr b/noir-projects/aztec-nr/uint-note/src/uint_note.nr index 9859a66ed947..db0b58dc6f7f 100644 --- a/noir-projects/aztec-nr/uint-note/src/uint_note.nr +++ b/noir-projects/aztec-nr/uint-note/src/uint_note.nr @@ -1,6 +1,6 @@ use dep::aztec::{ context::{PrivateContext, PublicContext}, - history::nullifier_inclusion::ProveNullifierInclusion, + history::nullifier::assert_nullifier_existed_by, keys::getters::{get_nsk_app, get_public_keys}, macros::notes::custom_note, messages::logs::partial_note::compute_partial_note_private_content_log, @@ -236,12 +236,15 @@ impl PartialUintNote { // We verify that the partial note we're completing is valid (i.e. completer is correct, it uses the correct // state variable's storage slot, and it is internally consistent). let validity_commitment = self.compute_validity_commitment(completer); - // `prove_nullifier_inclusion` function expects the nullifier to be siloed (hashed with the address of + // `assert_nullifier_existed_by` function expects the nullifier to be siloed (hashed with the address of // the contract that emitted the nullifier) as it checks the value directly against the nullifier tree and all // the nullifiers in the tree are siloed by the protocol. let siloed_validity_commitment = compute_siloed_nullifier(context.this_address(), validity_commitment); - context.get_anchor_block_header().prove_nullifier_inclusion(siloed_validity_commitment); + assert_nullifier_existed_by( + context.get_anchor_block_header(), + siloed_validity_commitment, + ); // We need to do two things: // - emit an unencrypted log containing the public fields (the value) via the private log channel. The diff --git a/noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr index a7e1d963e554..523706c476af 100644 --- a/noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr @@ -3,7 +3,7 @@ use dep::aztec::macros::aztec; #[aztec] pub contract Claim { // docs:start:history_import - use dep::aztec::history::note_inclusion::ProveNoteInclusion; + use dep::aztec::history::note::assert_note_existed_by; // docs:end:history_import use dep::aztec::{ macros::{functions::{external, initializer}, storage::storage}, @@ -48,7 +48,7 @@ pub contract Claim { // 3) Prove that the note hash exists in the note hash tree // docs:start:prove_note_inclusion let header = self.context.get_anchor_block_header(); - let _ = header.prove_note_inclusion(hinted_note); + let _ = assert_note_existed_by(header, hinted_note); // docs:end:prove_note_inclusion // 4) Compute and emit a nullifier which is unique to the note and this contract to ensure the reward can be @@ -57,7 +57,7 @@ pub contract Claim { // The nullifier is unique to the note and THIS contract because the protocol siloes all nullifiers with // the address of a contract it was emitted from. // TODO(#7775): manually computing the hash and passing it to compute_nullifier func is not great as note could - // handle it on its own or we could make prove_note_inclusion return note_hash_for_nullification. + // handle it on its own or we could make assert_note_existed_by return note_hash_for_nullification. let note_hash_for_nullification = compute_note_hash_for_nullification(hinted_note); let nullifier = hinted_note.note.compute_nullifier( self.context, diff --git a/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr index 762dbf4527be..9c47442d5021 100644 --- a/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr @@ -37,7 +37,7 @@ pub contract Test { // Event related messages::message_delivery::MessageDelivery, // History and inclusion proofs - history::note_inclusion::ProveNoteInclusion, + history::note::assert_note_existed_by, // Key management keys::getters::get_public_keys, // Macros @@ -261,7 +261,7 @@ pub contract Test { }; let header = self.context.get_anchor_block_header(); - let _ = header.prove_note_inclusion(hinted_note); + let _ = assert_note_existed_by(header, hinted_note); } #[external("private")] diff --git a/noir-projects/noir-contracts/contracts/test/test_contract/src/test/deployment_proofs.nr b/noir-projects/noir-contracts/contracts/test/test_contract/src/test/deployment_proofs.nr index 701d441bb99a..f03154a44e4e 100644 --- a/noir-projects/noir-contracts/contracts/test/test_contract/src/test/deployment_proofs.nr +++ b/noir-projects/noir-contracts/contracts/test/test_contract/src/test/deployment_proofs.nr @@ -1,14 +1,14 @@ use crate::Test; use dep::aztec::{ - history::contract_inclusion::{ - ProveContractDeployment, ProveContractInitialization, ProveContractNonDeployment, - ProveContractNonInitialization, + history::deployment::{ + assert_contract_bytecode_was_not_published_by, assert_contract_bytecode_was_published_by, + assert_contract_was_initialized_by, assert_contract_was_not_initialized_by, }, protocol_types::address::AztecAddress, test::helpers::test_environment::{PrivateContextOptions, TestEnvironment}, }; -// These tests need to be here instead of a saner place like aztec-nr/aztec/src/history/contract_inclusion/test.nr +// These tests need to be here instead of a saner place like aztec-nr/aztec/src/history/deployment/test.nr // because we are unable to define nor deploy a contract from that location and currently do not have plans to change // that. @@ -41,69 +41,93 @@ unconstrained fn contract_historical_proofs_happy_path() { env.private_context_opts( PrivateContextOptions::new().at_anchor_block_number(CONTRACT_DEPLOYED_AT - 1), - |context| { context.anchor_block_header.prove_contract_non_deployment(contract_address); }, + |context| { + assert_contract_bytecode_was_not_published_by( + context.anchor_block_header, + contract_address, + ); + }, ); env.private_context_opts( PrivateContextOptions::new().at_anchor_block_number(CONTRACT_DEPLOYED_AT), - |context| { context.anchor_block_header.prove_contract_deployment(contract_address); }, + |context| { + assert_contract_bytecode_was_published_by( + context.anchor_block_header, + contract_address, + ); + }, ); env.private_context_opts( PrivateContextOptions::new().at_anchor_block_number(CONTRACT_INITIALIZED_AT - 1), |context| { - context.anchor_block_header.prove_contract_non_initialization(contract_address); + assert_contract_was_not_initialized_by(context.anchor_block_header, contract_address); }, ); env.private_context_opts( PrivateContextOptions::new().at_anchor_block_number(CONTRACT_INITIALIZED_AT), - |context| { context.anchor_block_header.prove_contract_initialization(contract_address); }, + |context| { + assert_contract_was_initialized_by(context.anchor_block_header, contract_address); + }, ); } #[test(should_fail_with = "Nullifier membership witness not found at block")] -unconstrained fn prove_contract_deployment_before_deployment_fails() { +unconstrained fn assert_contract_bytecode_was_published_by_before_deployment_fails() { let (env, contract_address, _owner) = setup(); // Note that we're only testing that the function fails, but not that it would correct reject bad hints from an oracle env.private_context_opts( PrivateContextOptions::new().at_anchor_block_number(CONTRACT_DEPLOYED_AT - 1), - |context| { context.anchor_block_header.prove_contract_deployment(contract_address); }, + |context| { + assert_contract_bytecode_was_published_by( + context.anchor_block_header, + contract_address, + ); + }, ); } #[test(should_fail_with = "Nullifier membership witness not found at block")] -unconstrained fn prove_contract_initialization_before_initialization_fails() { +unconstrained fn assert_contract_was_initialized_by_before_initialization_fails() { let (env, contract_address, _owner) = setup(); // Note that we're only testing that the function fails, but not that it would correct reject bad hints from an oracle env.private_context_opts( PrivateContextOptions::new().at_anchor_block_number(CONTRACT_INITIALIZED_AT - 1), - |context| { context.anchor_block_header.prove_contract_initialization(contract_address); }, + |context| { + assert_contract_was_initialized_by(context.anchor_block_header, contract_address); + }, ); } #[test(should_fail_with = "Proving nullifier non-inclusion failed")] -unconstrained fn prove_contract_non_deployment_of_deployed_fails() { +unconstrained fn assert_contract_bytecode_was_not_published_by_of_deployed_fails() { let (env, contract_address, _owner) = setup(); // Note that we're only testing that the function fails, but not that it would correct reject bad hints from an oracle env.private_context_opts( PrivateContextOptions::new().at_anchor_block_number(CONTRACT_DEPLOYED_AT), - |context| { context.anchor_block_header.prove_contract_non_deployment(contract_address); }, + |context| { + assert_contract_bytecode_was_not_published_by( + context.anchor_block_header, + contract_address, + ); + }, ); } #[test(should_fail_with = "Proving nullifier non-inclusion failed")] -unconstrained fn prove_contract_non_initialization_of_initialized_fails() { +unconstrained fn assert_contract_was_not_initialized_by_of_initialized_fails() { let (env, contract_address, _owner) = setup(); // Note that we're only testing that the function fails, but not that it would correct reject bad hints from an oracle env.private_context_opts( PrivateContextOptions::new().at_anchor_block_number(CONTRACT_INITIALIZED_AT), |context| { - context.anchor_block_header.prove_contract_non_initialization(contract_address); + assert_contract_was_not_initialized_by(context.anchor_block_header, contract_address); }, ); }