From 99824e6a741c0117cc6d28e8457483818db24e00 Mon Sep 17 00:00:00 2001 From: Cat McGee Date: Wed, 24 Jan 2024 15:46:10 +0600 Subject: [PATCH] feat(docs): Historical trees docs (#3895) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Please provide a paragraph or two giving a summary of the change, including relevant motivation and context. # Checklist: Remove the checklist to signal you've completed it. Enable auto-merge if the PR is ready to merge. - [x] If the pull request requires a cryptography review (e.g. cryptographic algorithm implementations) I have added the 'crypto' tag. - [x] I have reviewed my diff in github, line by line and removed unexpected formatting changes, testing logs, or commented-out code. - [x] Every change is related to the PR description. - [x] I have [linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) this pull request to relevant issues (if any exist). --------- Co-authored-by: josh crites Co-authored-by: Jan Beneš Co-authored-by: Rahul Kothari --- .../advanced/data_structures/trees.md | 34 ++++- .../history_lib_reference.md | 119 ++++++++++++++++++ .../historical_access/how_to_prove_history.md | 102 +++++++++++++++ docs/sidebars.js | 9 ++ .../inclusion_proofs_contract/src/main.nr | 24 +++- 5 files changed, 282 insertions(+), 6 deletions(-) create mode 100644 docs/docs/dev_docs/contracts/syntax/historical_access/history_lib_reference.md create mode 100644 docs/docs/dev_docs/contracts/syntax/historical_access/how_to_prove_history.md diff --git a/docs/docs/concepts/advanced/data_structures/trees.md b/docs/docs/concepts/advanced/data_structures/trees.md index 658782ad010..d29f9c04669 100644 --- a/docs/docs/concepts/advanced/data_structures/trees.md +++ b/docs/docs/concepts/advanced/data_structures/trees.md @@ -14,7 +14,7 @@ Data includes: - Nullifiers to invalidate old private state ([Nullifier tree](#nullifier-tree)) - Public state ([Public State tree](#public-state-tree)) - Contracts ([Contract tree](#contract-tree)) -- Previous block headers ([Archive tree](#archive-tree)) +- Archive tree for Historical access ([Archive tree](#archive-tree)) - Global Variables ```mermaid @@ -181,9 +181,37 @@ Aztec supports the ability to keep the logic of private functions of a smart con ## Archive Tree -A block includes the root to the Archive Tree (sometimes called the blocks tree). +The archive tree is an append-only Merkle tree that stores hashes of headers of all previous blocks in the chain as its leaves. -The leaves of the tree are hashes of previous block headers. This tree can be used to verify data of any of the trees above at some previous point in time by doing a membership check of the corresponding tree root in the block header and the block header hash in the blocks tree. +As private execution relies on proofs generated by the user, the current block header is not known. We can instead base the proofs on historical state. By including all prior block headers (which include commitments to the state), the historical access tree allows us to easily prove that the historical state that a transaction is using for a proof is valid. + +```mermaid +graph TD; + +PartialStateReference[Partial State
snapshot of note hash tree, nullifier tree, contract tree, public data tree] +StateReference[State Reference
snapshot of L1<>L2 message tree] +GlobalVariables[Global Variables
block number, timestamp, version, chain_id] +Header[Block Header
last snapshot of tree, hash of body] +Logs[Transaction Logs
encrypted & non-encrypted logs] +PublicDataWrite[Public Data Writes
] +ContractData[Contract Data
leaf, address] +TxEffect[Transaction Effects
note hashes, nullifiers, contracts, public writes] +Body[ Body
L1<>L2 messages, transaction effects] +ArchiveTree[Archive Tree
used to validate user-generated proofs and access historical data] + +StateReference --- PartialStateReference +Header --- Body +Header --- StateReference +Header --- GlobalVariables +TxEffect --- ContractData +TxEffect --- PublicDataWrite +TxEffect --- Logs +Body --- TxEffect +HistoricalAccessTree --- Header + +``` + +It can also be used to find information about notes, public state, and contracts that were included in a certain block using [inclusion and non-inclusion proofs](../../../dev_docs/contracts/syntax/historical_access/how_to_prove_history.md). ## Trees of valid Kernel/Rollup circuit Verification Keys diff --git a/docs/docs/dev_docs/contracts/syntax/historical_access/history_lib_reference.md b/docs/docs/dev_docs/contracts/syntax/historical_access/history_lib_reference.md new file mode 100644 index 00000000000..b798a129243 --- /dev/null +++ b/docs/docs/dev_docs/contracts/syntax/historical_access/history_lib_reference.md @@ -0,0 +1,119 @@ +--- +title: History Reference +--- + + + +## Note inclusion + +Note inclusion proves that a note existed (its hash was included in a note hash tree) at a specific block number. + +## prove_note_inclusion + +`prove_note_inclusion` takes 4 parameters: + +| Name | Type | Description | +|-----------------|------------------------|-----------------------------------------------------| +| note_interface | NoteInterface | Interface for the note with necessary functionality| +| note_with_header| Note | The note you are proving inclusion for | +| block_number | u32 | Block number for proving note's existence | +| context | PrivateContext | Private context | + +## prove_note_commitment_inclusion + +A **commitment**, also referred to as a **note hash** is a public acknowledgment of the existence of a note without revealing the content of the note. You can learn more about how to compress a note to a note hash [here](../../../../concepts/advanced/data_structures/trees.md#example-note). + +`prove_note_commitment_inclusion` takes 3 parameters: + +| Name | Type | Description | +|-----------------|------------------------|-----------------------------------------------------| +| commitment | Field | Note commitment we are checking inclusion of | +| block_number | u32 | Block number for proving note's existence | +| context| PrivateContext | Private Context | + +## Note validity + +This proves that a note exists and has not been nullified at a specified block. + +### prove_note_validity + +`prove_note_validity` takes 4 parameters: + +| Name | Type | Description | +|-----------------|------------------------|-----------------------------------------------------| +| note_interface | NoteInterface | Interface for the note with necessary functionality| +| note_with_header| Note | The note you are proving inclusion for | +| block_number | u32 | Block number for proving note's existence | +| context | PrivateContext | Private context | + +## Nullifier inclusion + +This proves that a nullifier was included in a certain block (can be used to prove that a note had been nullified). + +### prove_nullifier_inclusion + +`prove_nullifier_inclusion` takes 3 parameters: + +| Name | Type | Description | +|-----------------|------------------------|-----------------------------------------------------| +| nullifier | Field | The nullifier you are proving inclusion for | +| block_number | u32 | Block number for proving note's existence | +| context | PrivateContext | Private context | + +## Nullifier non inclusion + +This proves that a nullifier was not included in a certain block (can be used to prove that a note had not yet been nullified in a given block). + +### prove_nullifier_non_inclusion + +`prove_nullifier_non_inclusion` takes 3 parameters: + +| Name | Type | Description | +|-----------------|------------------------|-----------------------------------------------------| +| nullifier | Field | The nullifier you are proving inclusion for | +| block_number | u32 | Block number for proving note's existence | +| context | PrivateContext | Private context | + + +### note_not_nullified + +Instead of passing the nullifier, you can check that a note has not been nullified by passing the note. + +## Public value inclusion + +This proves that a public value exists at a certain block. + +### prove_public_value_inclusion + +`prove_public_value_inclusion` takes 4 parameters: + +| Name | Type | Description | +|-----------------|------------------------|-----------------------------------------------------| +| value | Field | The public value you are proving inclusion for | +| storage_slot | Field | Storage slot the value exists in | +| block_number | u32 | Block number for proving value's existence | +| context | PrivateContext | Private context | + +## Contract inclusion + +This proves that a contract exists in, ie had been deployed before or in, a certain block. + +### prove_contract_inclusion + +`prove_contract_inclusion` takes 7 parameters: + +| Name | Type | Description | +|---------------------------|-----------------|-------------------------------------------------------| +| deployer_public_key | GrumpkinPoint | Public key of the contract deployer | +| contract_address_salt | Field | Unique identifier for the contract's address | +| function_tree_root | Field | Root of the contract's function tree | +| constructor_hash | Field | Hash of the contract's constructor | +| portal_contract_address | EthAddress | Ethereum address of the associated portal contract | +| block_number | u32 | Block number for proof verification | +| context | PrivateContext | Private context | + +If there is no associated portal contract, you can use a zero Ethereum address: + +```ts +new EthAddress(Buffer.alloc(EthAddress.SIZE_IN_BYTES)); +``` diff --git a/docs/docs/dev_docs/contracts/syntax/historical_access/how_to_prove_history.md b/docs/docs/dev_docs/contracts/syntax/historical_access/how_to_prove_history.md new file mode 100644 index 00000000000..28e3ea04ccf --- /dev/null +++ b/docs/docs/dev_docs/contracts/syntax/historical_access/how_to_prove_history.md @@ -0,0 +1,102 @@ +--- +title: How to prove existence of historical notes and nullifiers +--- + +The Aztec Protocol uses an append-only Merkle tree to store hashes of the headers of all previous blocks in the chain as its leaves. This is known as an archive tree. You can learn more about how it works in the [concepts section](../../../../concepts/advanced/data_structures/trees.md#archive-tree). + +# History library + +The history library allows you to prove any of the following at a given block height before the current height: +* [Note inclusion](./history_lib_reference.md#note-inclusion) +* [Nullifier inclusion](./history_lib_reference.md#nullifier-inclusion) +* [Note validity](./history_lib_reference.md#note-validity) +* [Existence of public value](./history_lib_reference.md#public-value-inclusion) +* [Contract inclusion](./history_lib_reference.md#contract-inclusion) + +Using this library, you can check that specific notes or nullifiers were part of Aztec network state at specific blocks. This can be useful for things such as: + +* Verifying a minimum timestamp from a private context +* Checking eligibility based on historical events (e.g. for an airdrop by proving that you owned a note) +* Verifying historic ownership / relinquishing of assets +* Proving existence of a value in public data tree at a given contract slot +* Proving that a contract was deployed in a given block with some parameters + +**In this guide you will learn how to** +* Prove a note was included in a specified block +* Create a nullifier and prove it was not included in a specified block + +For a more extensive reference, go to [the reference page](./history_lib_reference.md). + +## 1. Import the `history` library from `aztec` + +```rust +aztec::{ + #include_code imports yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr raw +} +``` + +This imports all functions from the `history` library. You should only import the functions you will use in your contract. + +## 2. Create a note to prove inclusion of + +In general you will likely have the note you want to prove inclusion of. But if you are just experimenting you can create a note with a function like below: + +#include_code create_note yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr rust + +## 3. Get the note from the PXE + +Retrieve the note from the user's PXE. + +#include_code get_note_from_pxe yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr rust + +In this example, the user's notes are stored in a map called `private_values`. We retrieve this map, then select 1 note from it with the value of `1`. + +## 4. Prove that a note was included in a specified block + +To prove that a note existed in a specified block, call `prove_note_inclusion` as shown in this example: + +#include_code prove_note_inclusion yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr rust + +This function takes in 4 arguments: + +1. The note interface (`ValueNoteMethods`) +2. The note (`maybe_note.unwrap_unchecked()`). Here, `unwrap_unchecked()` returns the inner value without asserting `self.is_some()` +3. The block number +4. Private context + +Note: for this to work, you will need to import `ValueNoteMethods` at the beginning of the contract: + +#include_code value_note_imports yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr rust + +This will only prove the note existed, not whether or not the note has been nullified. You can prove that a note existed and had not been nullified in a specified block by using `prove_note_validity` which takes the same arguments: + +#include_code prove_note_validity yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr rust + +## 5. Create a nullifier to prove inclusion of + +You can easily nullify a note like so: + +#include_code nullify_note yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr rust + +This function gets a note from the PXE like we did in [step 3](#3-get-the-note-from-the-pxe) and nullifies it with `remove()`. + +You can then compute this nullifier with `note.compute_nullifier(&mut context)`. + +## 6. Prove that a nullifier was included in a specified block + +Call `prove_nullifier_inclusion` like so: + +#include_code prove_nullifier_inclusion yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr rust + +This takes three arguments: +1. The nullifier +2. Block number +3. Private context + +You can also prove that a nullifier was not included in a specified block by using `prove_nullifier_non_inclusion` which takes the same arguments: + +#include_code prove_nullifier_non_inclusion yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr rust + +## Prove contract inclusion, public value inclusion, and note commitment inclusion + +To see what else you can do with the `history` library, check out the [reference](./history_lib_reference.md). diff --git a/docs/sidebars.js b/docs/sidebars.js index ae468539d33..30cd202bb3c 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -326,7 +326,16 @@ const sidebars = { }, "dev_docs/contracts/syntax/events", "dev_docs/contracts/syntax/functions", + { + label: "Proving Historical Blockchain Data", + type: "category", + items: [ + "dev_docs/contracts/syntax/historical_access/how_to_prove_history", + "dev_docs/contracts/syntax/historical_access/history_lib_reference", + ], + }, "dev_docs/contracts/syntax/slow_updates_tree", + "dev_docs/contracts/syntax/context", "dev_docs/contracts/syntax/globals", ], diff --git a/yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr b/yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr index 411fcaf5c35..5b99fdd60bb 100644 --- a/yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr @@ -23,11 +23,11 @@ contract InclusionProofs { note_header::NoteHeader, utils as note_utils, }, - + // docs:start:imports history::{ contract_inclusion::{ prove_contract_inclusion, - }, + }, note_inclusion::{ prove_note_commitment_inclusion, prove_note_inclusion, @@ -46,9 +46,11 @@ contract InclusionProofs { prove_public_value_inclusion, }, }, + // docs:end:imports }; + // docs:start:value_note_imports use dep::value_note::value_note::{ValueNote, ValueNoteMethods, VALUE_NOTE_LEN}; - + // docs:end:value_note_imports struct Storage { private_values: Map>, public_value: PublicState, @@ -84,6 +86,7 @@ contract InclusionProofs { storage.public_value.write(value); } + // docs:start:create_note // Creates a value note owned by `owner`. #[aztec(private)] fn create_note(owner: AztecAddress, value: Field) { @@ -91,6 +94,7 @@ contract InclusionProofs { let mut note = ValueNote::new(value, owner); owner_private_values.insert(&mut note, true); } + // docs:end:create_note // Proves that the owner owned a ValueNote at block `block_number`. #[aztec(private)] @@ -102,20 +106,24 @@ contract InclusionProofs { // PXE performs note commitment inclusion check when you add a new note). spare_commitment: Field ) { + // docs:start:get_note_from_pxe // 1) Get the note from PXE. let private_values = storage.private_values.at(owner); let options = NoteGetterOptions::new().select(1, owner.to_field()).set_limit(1); let notes = private_values.get_notes(options); let maybe_note = notes[0]; + // docs:end:get_note_from_pxe // 2) Prove the note inclusion if maybe_note.is_some() { + // docs:start:prove_note_inclusion prove_note_inclusion( ValueNoteMethods, maybe_note.unwrap_unchecked(), block_number, context ); + // docs:end:prove_note_inclusion } else { // Note was not found so we will prove inclusion of the spare commitment prove_note_commitment_inclusion(spare_commitment, block_number, context); @@ -140,15 +148,19 @@ contract InclusionProofs { // 3) Compute the nullifier from the note if maybe_note.is_some() { + // docs:start:prove_note_not_nullified prove_note_not_nullified( ValueNoteMethods, maybe_note.unwrap_unchecked(), block_number, &mut context ); + // docs:end:prove_note_not_nullified } else { // Note was not found so we will use the spare nullifier + // docs:start:prove_nullifier_non_inclusion prove_nullifier_non_inclusion(spare_nullifier, block_number, context); + // docs:end:prove_nullifier_non_inclusion }; } @@ -164,9 +176,12 @@ contract InclusionProofs { let note = notes[0].unwrap(); // 2) Prove the note validity + // docs:start:prove_note_validity prove_note_validity(ValueNoteMethods, note, block_number, &mut context); + // docs:end:prove_note_validity } + // docs:start:nullify_note #[aztec(private)] fn nullify_note(owner: AztecAddress) { let private_values = storage.private_values.at(owner); @@ -176,6 +191,7 @@ contract InclusionProofs { private_values.remove(note); } + // docs:end:nullify_note // Proves nullifier existed at block `block_number`. // Note: I am not getting a nullifier of the note that was created in this contract in this function because it is @@ -185,7 +201,9 @@ contract InclusionProofs { nullifier: Field, block_number: u32 // The block at which we'll prove that the nullifier not exists in the tree ) { + // docs:start:prove_nullifier_inclusion prove_nullifier_inclusion(nullifier, block_number, context); + // docs:end:prove_nullifier_inclusion } #[aztec(private)]