diff --git a/docs/docs/about_aztec/roadmap/engineering_roadmap.md b/docs/docs/about_aztec/roadmap/engineering_roadmap.md index a9f71aa40fa..890d0f1e14a 100644 --- a/docs/docs/about_aztec/roadmap/engineering_roadmap.md +++ b/docs/docs/about_aztec/roadmap/engineering_roadmap.md @@ -119,7 +119,9 @@ CI takes up a significant amount of time. It gets its own section here, so we re ## Slow Updates tree? -We _need_ a way to read mutable public data from a private function. +We _need_ a way to read mutable public data from a private function. + +Note: we just published the [Slow Updates Tree](../../concepts/foundation/communication/public_private_calls/slow_updates_tree.md). ## Contract classes and instances? diff --git a/docs/docs/concepts/foundation/accounts/keys.md b/docs/docs/concepts/foundation/accounts/keys.md index b3a49ffbe93..589667cbab3 100644 --- a/docs/docs/concepts/foundation/accounts/keys.md +++ b/docs/docs/concepts/foundation/accounts/keys.md @@ -28,7 +28,7 @@ Similar to using a private note, but using an immutable private note removes the ### Using the slow updates tree -A compromise between the two solutions above is to use the slow updates tree. This would not generate additional nullifiers and commitments for each transaction while allowing the user to rotate their key. However, this causes every transaction to now have a time-to-live determined by the frequency of the slow updates tree. +A compromise between the two solutions above is to use the [slow updates tree](../communication/public_private_calls/slow_updates_tree.md). This would not generate additional nullifiers and commitments for each transaction while allowing the user to rotate their key. However, this causes every transaction to now have a time-to-live determined by the frequency of the slow updates tree. ### Reusing the privacy master key diff --git a/docs/docs/concepts/foundation/accounts/main.md b/docs/docs/concepts/foundation/accounts/main.md index 23dcfa6c549..c96238632be 100644 --- a/docs/docs/concepts/foundation/accounts/main.md +++ b/docs/docs/concepts/foundation/accounts/main.md @@ -127,7 +127,7 @@ These two patterns combined allow an account contract to answer whether an actio Aztec requires users to define [encryption and nullifying keys](./keys.md) that are needed for receiving and spending private notes. Unlike transaction signing, encryption and nullifying is enshrined at the protocol. This means that there is a single scheme used for encryption and nullifying. These keys are derived from a master public key. This master public key, in turn, is used when deterministically deriving the account's address. -A side effect of committing to a master public key as part of the address is that _this key cannot be rotated_. While an account contract implementation could include methods for rotating the signing key, this is unfortunately not possible for encryption and nullifying keys (note that rotating nullifying keys also creates other challenges such as preventing double spends). We are exploring usage of the slow updates tree to enable rotating these keys. +A side effect of committing to a master public key as part of the address is that _this key cannot be rotated_. While an account contract implementation could include methods for rotating the signing key, this is unfortunately not possible for encryption and nullifying keys (note that rotating nullifying keys also creates other challenges such as preventing double spends). We are exploring usage of the [slow updates tree](../communication/public_private_calls/slow_updates_tree.md) to enable rotating these keys. NOTE: While we entertained the idea of abstracting note encryption, where account contracts would define an `encrypt` method that would use a user-defined scheme, there are two main reasons we decided against this. First is that this entailed that, in order to receive funds, a user had to first deploy their account contract, which is a major UX issue. Second, users could define malicious `encrypt` methods that failed in certain circumstances, breaking application flows that required them to receive a private note. While this issue already exists in Ethereum when transferring ETH (see the [king of the hill](https://coinsbench.com/27-king-ethernaut-da5021cd4aa6)), its impact is made worse in Aztec since any execution failure in a private function makes the entire transaction unprovable (ie it is not possible to catch errors in calls to other private functions), and furthermore because encryption is required for any private state (not just for transferring ETH). Nevertheless, both of these problems are solvable. Initialization can be worked around by embedding a commitment to the bytecode in the address and removing the need for actually deploying contracts before interacting with them, and the king of the hill issue can be mitigated by introducing a full private VM that allows catching reverts. As such, we may be able to abstract encryption in the future as well. diff --git a/docs/docs/concepts/foundation/communication/main.md b/docs/docs/concepts/foundation/communication/main.md index 3dcd7fb7f5b..1f85585dacc 100644 --- a/docs/docs/concepts/foundation/communication/main.md +++ b/docs/docs/concepts/foundation/communication/main.md @@ -4,6 +4,6 @@ title: Contract Communication This section will walk over communication types that behaves differently than normal function calls from. -Namely, if functions are in different domains, private vs. public, their execution behaves a little differently to what you might expect! See [Private <--> Public execution](./public_private_calls.md). +Namely, if functions are in different domains, private vs. public, their execution behaves a little differently to what you might expect! See [Private <--> Public execution](./public_private_calls/main.md). Likewise, executing a function on a different domain than its origin needs a bit extra thought. See [L1 <--> L2 communication](./cross_chain_calls.md). \ No newline at end of file diff --git a/docs/docs/concepts/foundation/communication/public_private_calls.md b/docs/docs/concepts/foundation/communication/public_private_calls/main.md similarity index 94% rename from docs/docs/concepts/foundation/communication/public_private_calls.md rename to docs/docs/concepts/foundation/communication/public_private_calls/main.md index 224574beeb7..34a98047f4f 100644 --- a/docs/docs/concepts/foundation/communication/public_private_calls.md +++ b/docs/docs/concepts/foundation/communication/public_private_calls/main.md @@ -4,7 +4,7 @@ title: Private <--> Public execution import Image from "@theme/IdealImage"; -import Disclaimer from "../../../misc/common/\_disclaimer.mdx"; +import Disclaimer from "../../../../misc/common/\_disclaimer.mdx"; @@ -105,3 +105,5 @@ function onlyFresh(pub uint256 blockNumber) public { :::info This is not a perfect solution, as any functions using access control might end up doing a lot of public calls it could put a significant burden on sequencers and greatly increase the cost of the transaction for the user. We are investigating ways to improve. ::: + +Using a dual-tree structure with a pending and a current tree, it is possible to update public data from a private function. The update is fulfilled when the pending tree becomes the current after the end of a specified epoch. It is also possible to read historical public data directly from a private function. This works perfectly for public data that is not updated often, such as a blacklist. This structure is called a slow updates tree, and you can read about how it works [in the next section](./slow_updates_tree.md). diff --git a/docs/docs/concepts/foundation/communication/public_private_calls/slow_updates_tree.md b/docs/docs/concepts/foundation/communication/public_private_calls/slow_updates_tree.md new file mode 100644 index 00000000000..c6ceb070e26 --- /dev/null +++ b/docs/docs/concepts/foundation/communication/public_private_calls/slow_updates_tree.md @@ -0,0 +1,73 @@ +--- +title: Privately access Historical Public Data +--- + +In Aztec, private and public execution environments are completely separate and operate with distinct state management. It is not possible for private functions to reliably access the most recent public data public state - only sequencers can do that. You'll want to [read the previous section](./main.md) to understand this before reading this page. + +But, what about historical public data (or public data that changes infrequently)? Through a **slow updates tree**, private functions can access historical public state. Please note that we are still experimenting with this feature. + +On this page you will learn: + +1. Why a slow updates tree exists & use cases +2. How it works +3. How it can be used to access historical public data +4. Limitations + +## The need for a slow updates tree + +This structure was created specifically to privately & publicly access historical public data. It should be used to store public data that doesn't change often. + +- Access historical public data from a private function +- Access historical public data from a public function +- Update public data (that does not need updated often) from public and private functions + +This data structure is ideal for these use cases: + +- Address Registry: Enabling contracts to interact with other contracts more easily requires address storage accessible in both public and private executions. This can be particularly helpful in things such as proxy contracts. +- Access Control: Managing privileges in contracts, such as a token contract owner’s ability to mint new tokens, is streamlined when control information is shared between public and private executions. This might include things like blacklists and whitelists. + +## How it works + +We developed the Slow Updates Tree to help balance public and private execution in a blockchain context. Earlier systems typically used either fully public or entirely private state trees. + +The Slow Updates Tree is a dual-tree structure - a current tree and a pending tree. Any updates are added to the pending tree, which then becomes the current tree at the end of an epoch. The pending tree is replicated from the current tree, and the cycle begins again. + +```mermaid +graph TD; + Change{Epoch Over} -->|True| Current{Current} + Change -->|False| Pending{Pending} + Current --> Current1[Current Commitment 1] + Current --> CurrentM[Current Commitment 2] + CurrentM --> Value1[Current Value 1] + CurrentM --> Value2[Current Value 2] + CurrentM --> ValueN[Current Value n] + Pending --> PendingM[Pending Commitment 1] + PendingM --> PValue1[Pending Value 1] + PendingM --> PValue2[Pending Value 2] + PendingM --> PValueN[Pending Value n] +``` + +This way, we can ensure that the values are stable throughout the epoch, and that the membership proofs are not invalidated by changes in other contracts more than once every epoch. + +## Reads and Writes + +### Accessing Data + +*From public state:* Accessed directly from the state +*From private state:* Performs a membership proof for the values in the tree, ensuring that they are part of the commitment. + +### Updating Data + +Updates are made to the pending tree. Then at the end of each epoch, the updates in the pending tree are committed and it becomes the current tree. + +## Limitations + +### Delayed State Finality + +Updates in the Slow Updates Tree are only finalized at the end of an epoch. + +Developers are used to instant state updates, so the Slow Updates Tree might take some getting used to. But we believe this won't take long! + +## Dive into the code + +For a code walkthrough of how a token blacklist contract can use a slow updates tree, read [this](../../../../dev_docs/contracts/syntax/slow_updates_tree.md). \ No newline at end of file diff --git a/docs/docs/concepts/foundation/main.md b/docs/docs/concepts/foundation/main.md index 0791cd279d3..5d7e9579882 100644 --- a/docs/docs/concepts/foundation/main.md +++ b/docs/docs/concepts/foundation/main.md @@ -24,7 +24,7 @@ A user of the Aztec network will interact with the network through Aztec.js. Azt ### Private Execution Environment -The PXE provides a secure environment for the execution of sensitive operations, ensuring private information and decrypted data are not accessible to unauthorized applications. It hides the details of the [state model](./state_model/main.md) from end users, but the state model is important for Aztec developers to understand as it has implications for [private/public execution](./communication/public_private_calls.md) and [L1/L2 communication](./communication/cross_chain_calls.md). The PXE also includes the [ACIR Simulator](../advanced/acir_simulator.md) for private executions and the KeyStore for secure key management. +The PXE provides a secure environment for the execution of sensitive operations, ensuring private information and decrypted data are not accessible to unauthorized applications. It hides the details of the [state model](./state_model/main.md) from end users, but the state model is important for Aztec developers to understand as it has implications for [private/public execution](./communication/public_private_calls/main.md) and [L1/L2 communication](./communication/cross_chain_calls.md). The PXE also includes the [ACIR Simulator](../advanced/acir_simulator.md) for private executions and the KeyStore for secure key management. Procedurally, the PXE sends results of private function execution and requests for public function executions to the [sequencer](./nodes_clients/sequencer.md), which will update the state of the rollup. diff --git a/docs/docs/dev_docs/contracts/resources/common_patterns/main.md b/docs/docs/dev_docs/contracts/resources/common_patterns/main.md index d0c535df871..a75e128fa6e 100644 --- a/docs/docs/dev_docs/contracts/resources/common_patterns/main.md +++ b/docs/docs/dev_docs/contracts/resources/common_patterns/main.md @@ -41,7 +41,7 @@ Note - you could also create a note and send it to the user. The problem is ther ### Reading public storage in private You can't read public storage in private domain. But nevertheless reading public storage is desirable. There are two ways: -1. For public storage that changes infrequently, use the slow updates tree! More details TBD +1. For public storage that changes infrequently, use the slow updates tree! Learn more about it [here](../../../../concepts/foundation/communication/public_private_calls/slow_updates_tree.md). 2. You pass the data as a parameter to your private method and later assert in public that the data is correct. E.g.: ```rust diff --git a/docs/docs/dev_docs/contracts/syntax/functions.md b/docs/docs/dev_docs/contracts/syntax/functions.md index 996e369dff1..334aaa1401b 100644 --- a/docs/docs/dev_docs/contracts/syntax/functions.md +++ b/docs/docs/dev_docs/contracts/syntax/functions.md @@ -20,7 +20,7 @@ In Aztec there are multiple different types of visibility that can be applied to ### Data Visibility -Data visibility is used to describe whether the data (or state) used in a function is generally accessible (public) or on a need to know basis (private). Functions with public data visibility are executed by the sequencer, and functions with private data visibility are executed by the user. For more information on why this is the case, see [communication](../../../concepts/foundation/communication/public_private_calls.md). +Data visibility is used to describe whether the data (or state) used in a function is generally accessible (public) or on a need to know basis (private). Functions with public data visibility are executed by the sequencer, and functions with private data visibility are executed by the user. For more information on why this is the case, see [communication](../../../concepts/foundation/communication/public_private_calls/main.md). In the following sections, we are going to see how these two "types" co-exists and interact. @@ -213,7 +213,7 @@ Calling a public function from another public function is quite similar to what ### Private -> Public -As discussed above, private function execution and calls take place on the user's device, while public function execution and calls take place on a sequencer, in two different places at two different times, it is natural to question how we can achieve composability between the two. The solution is asynchronicity. Further reading can be found in the foundational concepts [here](../../../concepts/foundation/communication/public_private_calls.md). +As discussed above, private function execution and calls take place on the user's device, while public function execution and calls take place on a sequencer, in two different places at two different times, it is natural to question how we can achieve composability between the two. The solution is asynchronicity. Further reading can be found in the foundational concepts [here](../../../concepts/foundation/communication/public_private_calls/main.md). Private function execution takes place on the users device, where it keeps track of any public function calls that have been made. Whenever private execution completes, and a kernel proof is produced, the transaction sent to the network will include all of the public calls that were dispatched. When the sequencer receives the messages, it will take over and execute the public parts of the transaction. diff --git a/docs/docs/dev_docs/contracts/syntax/slow_updates_tree.md b/docs/docs/dev_docs/contracts/syntax/slow_updates_tree.md new file mode 100644 index 00000000000..e108e0cff23 --- /dev/null +++ b/docs/docs/dev_docs/contracts/syntax/slow_updates_tree.md @@ -0,0 +1,268 @@ +--- +title: Slow Updates Tree +--- + +Slow Updates Tree is a data structure that allows for historical public data to be accessed in both private and public domains. Read the high level overview in the [concepts section](../../../concepts/foundation/communication/public_private_calls/slow_updates_tree.md). + +The slow updates tree works by having a current tree and a pending tree, and replacing the current tree with the pending tree after an epoch has passed. Public functions can read directly from the current tree, and private functions can perform a membership proof that values are part of a commitment to the current state of the tree. + +On this page you will learn: + +1. [The components involved in using the slow updates tree](slow_updates_tree.md#components-involved-in-implementing-a-slow-updates-tree) +2. [How you can integrate it into your own smart contract](slow_updates_tree.md#how-to-integrate-a-slow-updates-tree) +3. [An example of a token blacklisting contract that uses the slow updates tree](slow_updates_tree.md#exploring-an-example-integration-through-a-tokenblacklist-smart-contract) +4. [Interface Reference](slow_updates_tree.md#reference) + +# Components involved in implementing a slow updates tree + +There are generally 4 main components involved to make it easier to use a slow updates tree, with 3 already implemented by Aztec. This makes it easier to interact with a slow updates tree through a simple interface. These four components are: + +## Main smart contract + +This is the primary smart contract that will use the slow updates tree. In the example we use a [token with blacklisting features](slow_updates_tree.md#exploring-an-example-integration-through-a-tokenblacklist-smart-contract). + +## Interface + +This interface of the slow updates tree contract allows your contract to interact with the Slow Updates Tree contract. It provides methods for reading and updating values in the tree in both public and private contexts. You can find it [here](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/interfaces.nr). + +## SlowTree.nr contract + +This is a smart contract developed by Aztec that establishes and manages a slow updates tree structure. It allows developers to access and interact with the tree, such as reading and updating data. + +You can find it [here](https://github.com/AztecProtocol/aztec-packages/tree/master/yarn-project/noir-contracts/src/contracts/slow_tree_contract). + +## SlowMap type + +This is a type in the Aztec.nr library that is utilized by the SlowTree contract. It defines the underlying data structure for the slow updates tree, and handles storing both the current and pending values for each data entry. + +You can find it [here](https://github.com/AztecProtocol/aztec-nr/blob/master/slow-updates-tree/src/slow_map.nr). + +The diagram below describes how these components work together. It does not contain all the functionality. + +```mermaid +graph TD + MSC[Main Smart Contract] --> INT[Interface] + STC --> SMT + + INT_RAP[read_at_pub] <--> STC_RAP[read_at_public] + INT_RA[read_at] <--> STC_RA[read_at] + INT_UAP[update_at_public] <--> STC_UAP[update_at_public] + INT_UA[update_at_private] <--> STC_UA[update_at_private] + + STC_RA <--> VMP[verify_membership_proof] + STC_UA <--> CR[compute_roots] + + subgraph INT[Interface] + INT_RAP + INT_UAP + INT_RA + INT_UA + end + + subgraph STC[SlowTree.nr] + STC_RAP + STC_UAP + STC_RA + STC_UA + end + + subgraph SMT[SlowMap Type] + Change{Epoch Over} -->|True| Current{Current} + Change -->|False| Pending{Pending} + Current --> Current1[Current Commitment 1] + Current --> CurrentM[Current Commitment M] + CurrentM --> Value1[Current Value 1] + CurrentM --> Value2[Current Value 2] + CurrentM --> ValueN[Current Value N] + Pending --> PendingM[Pending Commitment 1] + PendingM --> PValue1[Pending Value 1] + PendingM --> PValue2[Pending Value 2] + PendingM --> PValueN[Pending Value N] + end + + style INT fill:#fff,stroke:#333,stroke-width:1px + style STC fill:#fff,stroke:#333,stroke-width:1px + style SMT fill:#fff,stroke:#333,stroke-width:1px +``` + +# How to integrate a slow updates tree + +1. Copy the *SlowTree.nr* example and its dependencies, found [here](https://github.com/AztecProtocol/aztec-packages/tree/master/yarn-project/noir-contracts/src/contracts/slow_tree_contract). Replace the constants with whatever you like and deploy it to your sandbox +2. Copy the *SlowMap interface* for easy interaction with your deployed SlowTree. Find it [here](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/interfaces.nr) +3. Import this interface into your contract + +#include_code interface yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +5. Create a storage init function for the same value in both public and private storage + +#include_code slow_updates_storage yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +6. Store the SlowTree address in private storage as a FieldNote + +#include_code constructor yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +7. Store the SlowTree address in public storage and initialize an instance of SlowMap using this address + +#include_code write_slow_update_public yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +8. Now you can read and update from private functions: + +#include_code get_and_update_private yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +9. Or from public functions: + +#include_code get_public yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +View the [reference](slow_updates_tree.md#reference) for more information. + +## Exploring an example integration through a **`TokenBlacklist`** Smart Contract + +The `TokenBlacklist` contract is a token contract that does not allow blacklisted accounts to perform mints or transfers. In this section we will go through how this is achieved through the slow updates tree. + +You can find the full code for the TokenBlacklist smart contract [here](https://github.com/AztecProtocol/aztec-packages/tree/master/yarn-project/noir-contracts/src/contracts/token_blacklist_contract). + +### Importing SlowMap + +The contract first imports the **`SlowMap`** interface: + +#include_code interface yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +This interface allows the contract to interact with its attached SlowTree. It abstracts these functions so they do not have to be implemented in the TokenBlacklist contract. + +### Constructor and initialization + +The contract's constructor takes the address of the slow updates contract: + +#include_code constructor yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +This initialization sets up the connection between the **`TokenBlacklist`** contract and a previously deployed SlowTree, allowing it to use the interface to directly interact with the SlowTree. + +### Private transfer function utilizing the slow updates tree + +In the private transfer function, the contract uses the interface to check if a user is blacklisted: + +#include_code transfer_private yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +Here, the contract reads the roles of the sender and recipient from the SlowTree using the **`read_at`** function in the interface. It checks if either party is blacklisted, and if so, the transaction does not go ahead. + +# Reference + +## Struct `SlowMap` + +### Overview +The `SlowMap` struct is used to interact with a slow updates tree deployed via the SlowTree smart contract. + +### Fields + +| Name | Type | Description | +|---------|-----------|---------------------------------| +| address | `Field` | The address of the SlowTree contract | + +## Functions + +### at + +Returns an instance of `SlowMap` at the specified address. + +**Parameters** + +| Name | Type | Description | +|----------|----------------|----------------------------| +| `address`| `AztecAddress` | The address of the SlowTree | + +**Return** + +| Name | Type | Description | +|-------|-----------|------------------------------| +| - | `SlowMap` | The `SlowMap` instance | + +**Example** + +#include_code slowmap_at yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +### initialize + +Initializes the `SlowMap`. + +**Parameters** + +| Name | Type | Description | +|-----------|-----------------|----------------------| +| `context` | `PublicContext` | The execution context | + +**Return** + +| Name | Type | Description | +|------|------|-------------| +| - | - | - | + +**Example** + +#include_code slowmap_initialize yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +### read_at_pub + +Reads a value at a specified index from a public function. + +**Parameters** + +| Name | Type | Description | +|-----------|-----------------|-----------------------| +| `context` | `PublicContext` | The execution context | +| `index` | `Field` | The index to read at | + +**Return** + +| Name | Type | Description | +|----------|--------|-----------------------| +| `result` | `Field`| The value at `index` | + +**Example** + +#include_code read_at_pub yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +### read_at + +Reads a value at a specified index from a private function. + +**Parameters** + +| Name | Type | Description | +|-----------|--------------------|------------------------| +| `context` | `PrivateContext` | The execution context | +| `index` | `Field` | The index to read at | + +**Return** + +| Name | Type | Description | +|----------|--------|-----------------------| +| `result` | `Field`| The value at `index` | + +**Example** + +#include_code slowmap_read_at yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +### update_at_private + +Updates a value at a specified index from a private function. Does not return anything. + +**Parameters** + +| Name | Type | Description | +|-------------|--------------------|------------------------| +| `context` | `PrivateContext` | The execution context | +| `index` | `Field` | The index to update | +| `new_value` | `Field` | The new value | + +**Example** + +#include_code get_and_update_private yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + +## Updating from public + +This is not a method in the interface as it can be done using regular Aztec.nr public storage update syntax. + +**Example** + +#include_code write_slow_update_public yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr rust + diff --git a/docs/docs/dev_docs/contracts/syntax/storage/main.md b/docs/docs/dev_docs/contracts/syntax/storage/main.md index 0a33ce92a6a..b647cc6ff50 100644 --- a/docs/docs/dev_docs/contracts/syntax/storage/main.md +++ b/docs/docs/dev_docs/contracts/syntax/storage/main.md @@ -23,7 +23,7 @@ On this page, you’ll learn: Public state variables can be read by anyone, while private state variables can only be read by their owner (or people whom the owner has shared the decrypted data or note viewing key with). -Public state follows the Ethereum style account model, where each contract has its own key-value datastore. Private state follows a UTXO model, where note contents (pre-images) are only known by the sender and those able to decrypt them - see ([state model](../../../../concepts/foundation/state_model/main.md) and [private/public execution](../../../../concepts/foundation/communication/public_private_calls.md)) for more background. +Public state follows the Ethereum style account model, where each contract has its own key-value datastore. Private state follows a UTXO model, where note contents (pre-images) are only known by the sender and those able to decrypt them - see ([state model](../../../../concepts/foundation/state_model/main.md) and [private/public execution](../../../../concepts/foundation/communication/public_private_calls/main.md)) for more background. ## Storage struct @@ -172,7 +172,7 @@ We know its verbose, and are working on making it less so. #### Mapping example -Say we want to have a group of `minters` that are able to mint assets in our contract, and we want them in public storage, because [access control in private is quite cumbersome](../../../../concepts/foundation/communication/public_private_calls.md#a-note-on-l2-access-control). In the `Storage` struct we can add it as follows: +Say we want to have a group of `minters` that are able to mint assets in our contract, and we want them in public storage, because [access control in private is quite cumbersome](../../../../concepts/foundation/communication/public_private_calls/main.md#a-note-on-l2-access-control). In the `Storage` struct we can add it as follows: #include_code storage_minters /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust diff --git a/docs/docs/dev_docs/debugging/sandbox-errors.md b/docs/docs/dev_docs/debugging/sandbox-errors.md index fd69c43d831..c7c2879eef3 100644 --- a/docs/docs/dev_docs/debugging/sandbox-errors.md +++ b/docs/docs/dev_docs/debugging/sandbox-errors.md @@ -86,7 +86,7 @@ Calling a private Aztec.nr function in a public kernel is not allowed. #### 3005 - PUBLIC_KERNEL\_\_NON_EMPTY_PRIVATE_CALL_STACK -Public functions are executed after all the private functions are (see [private-public execution](../../concepts/foundation/communication/public_private_calls.md)). As such, private call stack must be empty when executing in the public kernel. +Public functions are executed after all the private functions are (see [private-public execution](../../concepts/foundation/communication/public_private_calls/main.md)). As such, private call stack must be empty when executing in the public kernel. #### 3011 - PUBLIC_KERNEL\_\_CALCULATED_PRIVATE_CALL_HASH_AND_PROVIDED_PRIVATE_CALL_HASH_MISMATCH @@ -191,7 +191,7 @@ Users may create a proof against a historical state in Aztec. The rollup circuit - "${treeName} tree next available leaf index mismatch" - validating a tree's root is not enough. It also checks that the `next_available_leaf_index` is as expected. This is the next index we can insert new values into. Note that for the public data tree, this test is skipped since as it is a sparse tree unlike the others. -- "Public call stack size exceeded" - In Aztec, the sequencer executes all enqueued public functions in a transaction (to prevent race conditions - see [private-public execution](../../concepts/foundation/communication/public_private_calls.md)). This error says there are too many public functions requested. +- "Public call stack size exceeded" - In Aztec, the sequencer executes all enqueued public functions in a transaction (to prevent race conditions - see [private-public execution](../../concepts/foundation/communication/public_private_calls/main.md)). This error says there are too many public functions requested. - "Array size exceeds target length" - happens if you add more items than allowed by the constants set due to our circuit limitations (eg sending too many L2 to L1 messages or creating a function that exceeds the call stack length or returns more values than what Aztec.nr functions allow) diff --git a/docs/docs/dev_docs/tutorials/writing_private_voting_contract.md b/docs/docs/dev_docs/tutorials/writing_private_voting_contract.md index ecfa4fb2b2d..a541e3abf5d 100644 --- a/docs/docs/dev_docs/tutorials/writing_private_voting_contract.md +++ b/docs/docs/dev_docs/tutorials/writing_private_voting_contract.md @@ -105,7 +105,7 @@ This `init` function will be called every time we access `storage` in our functi The next step is to initialize the contract with a constructor. The constructor will take an address as a parameter and set the admin. -All constructors must be private, and because the admin is in public storage, we cannot directly update it from the constructor. You can find more information about this [here](../../concepts/foundation/communication/public_private_calls.md). +All constructors must be private, and because the admin is in public storage, we cannot directly update it from the constructor. You can find more information about this [here](../../concepts/foundation/communication/public_private_calls/main.md). Therefore our constructor must call a public function by using `context.call_public_function()`. Paste this under the `impl` storage block: diff --git a/docs/sidebars.js b/docs/sidebars.js index 3a20fac23d1..4eb6dbdcac7 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -105,7 +105,17 @@ const sidebars = { id: "concepts/foundation/communication/main", }, items: [ - "concepts/foundation/communication/public_private_calls", + { + label: "Public <> Private Communication", + type: "category", + link: { + type: "doc", + id: "concepts/foundation/communication/public_private_calls/main", + }, + items: [ + "concepts/foundation/communication/public_private_calls/slow_updates_tree" + ] + }, "concepts/foundation/communication/cross_chain_calls", ], }, @@ -320,6 +330,7 @@ const sidebars = { }, "dev_docs/contracts/syntax/events", "dev_docs/contracts/syntax/functions", + "dev_docs/contracts/syntax/slow_updates_tree", "dev_docs/contracts/syntax/context", "dev_docs/contracts/syntax/globals", ], diff --git a/yarn-project/aztec-nr/slow-updates-tree/src/slow_map.nr b/yarn-project/aztec-nr/slow-updates-tree/src/slow_map.nr index f36d9f79593..d2dc5c67830 100644 --- a/yarn-project/aztec-nr/slow-updates-tree/src/slow_map.nr +++ b/yarn-project/aztec-nr/slow-updates-tree/src/slow_map.nr @@ -43,6 +43,7 @@ struct SlowUpdateInner { sibling_path: [Field; N], } +// docs:start:slow_update_proof // The slow update proof. Containing two merkle paths // One for the before and one for the after trees. // M = 2 * N + 4 @@ -52,6 +53,7 @@ struct SlowUpdateProof { before: SlowUpdateInner, after: SlowUpdateInner, } +// docs:end:slow_update_proof pub fn deserialize_slow_update_proof(serialized: [Field; M]) -> SlowUpdateProof { SlowUpdateProof::deserialize(serialized) @@ -142,11 +144,14 @@ impl SlowMap { } } + // docs:start:read_leaf_at pub fn read_leaf_at(self: Self, key: Field) -> Leaf { let derived_storage_slot = pedersen_hash([self.storage_slot, key]); storage_read(derived_storage_slot, deserialize_leaf) } + // docs:end:read_leaf_at + // docs:start:read_at // Reads the "CURRENT" value of the leaf pub fn read_at(self: Self, key: Field) -> Field { let time = self.context.public.unwrap().timestamp() as u120; @@ -157,6 +162,7 @@ impl SlowMap { leaf.after } } + // docs:end:read_at // Will update values in the "AFTER" tree // - updates the leaf and root to follow current values, moving from after to before if diff --git a/yarn-project/noir-contracts/src/contracts/slow_tree_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/slow_tree_contract/src/main.nr index 0af6f5565a2..f745e9acac0 100644 --- a/yarn-project/noir-contracts/src/contracts/slow_tree_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/slow_tree_contract/src/main.nr @@ -31,6 +31,7 @@ contract SlowTree { use crate::capsule::pop_capsule; use crate::types::{MembershipProof, deserialize_membership_proof}; + // docs:start:constants_and_storage global TREE_HEIGHT: Field = 254; global MEMBERSHIP_SIZE: Field = 256; // TREE_HEIGHT + 2 global UPDATE_SIZE: Field = 512; // TREE_HEIGHT * 2 + 4 @@ -40,6 +41,7 @@ contract SlowTree { struct Storage { trees: Map>, } + // docs:end:constants_and_storage impl Storage { fn init(context: Context) -> pub Self { @@ -60,22 +62,23 @@ contract SlowTree { #[aztec(private)] fn constructor() {} - + // docs:start:initialize #[aztec(public)] fn initialize() { storage.trees.at(context.msg_sender()).initialize(EMPTY_ROOT); } - + // docs:end:initialize + // docs:start:read_at_pub #[aztec(public)] fn read_at_pub(key: Field) -> Field { storage.trees.at(context.msg_sender()).read_at(key) } - + // docs:end:read_at_pub #[aztec(public)] fn read_leaf_at_pub(key: Field) -> Leaf { storage.trees.at(context.msg_sender()).read_leaf_at(key) } - + // docs:start:read_at_private #[aztec(private)] fn read_at(index: Field) -> Field { let fields = pop_capsule(); @@ -90,18 +93,22 @@ contract SlowTree { p.value } - + // docs:end:read_at_private + // docs:start:assert_current_root #[aztec(public)] internal fn _assert_current_root(caller: Field, expected: Field) { let root = storage.trees.at(caller).current_root(); assert(root == expected, "Root does not match expected"); } + // docs:end:assert_current_root + // docs:start:update_at_pub #[aztec(public)] fn update_at_public(p: SlowUpdateProof) { storage.trees.at(context.msg_sender()).update_at(p); } - + // docs:end:update_at_pub + // docs:start:update_at_private #[aztec(private)] fn update_at_private(index: Field, new_value: Field) { let fields = pop_capsule(); @@ -126,7 +133,8 @@ contract SlowTree { new_after_root ]); } - + // docs:end:update_at_private + // docs:start:_update #[aztec(public)] internal fn _update(caller: Field, index: Field, new_value: Field, before: Field, after: Field, new_root: Field) { let current_root = storage.trees.at(caller).current_root(); @@ -137,7 +145,7 @@ contract SlowTree { storage.trees.at(caller).update_unsafe_at(index, new_value, new_root); } - + // docs:end:_update unconstrained fn un_read_leaf_at(address: Field, key: Field) -> Leaf { storage.trees.at(address).read_leaf_at(key) } diff --git a/yarn-project/noir-contracts/src/contracts/slow_tree_contract/src/types.nr b/yarn-project/noir-contracts/src/contracts/slow_tree_contract/src/types.nr index 0f4f5eb4119..9245cb9aa13 100644 --- a/yarn-project/noir-contracts/src/contracts/slow_tree_contract/src/types.nr +++ b/yarn-project/noir-contracts/src/contracts/slow_tree_contract/src/types.nr @@ -1,3 +1,4 @@ +// docs:start:membership_proof // A single inclusion proof. // M = N + 2 struct MembershipProof { @@ -5,6 +6,7 @@ struct MembershipProof { value: Field, sibling_path: [Field; N], } +// docs:end:membership_proof fn deserialize_membership_proof(serialized: [Field; M]) -> MembershipProof { let mut sibling_path = [0; N]; diff --git a/yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr index 95e9a33d65f..73b14c9cf43 100644 --- a/yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/token_blacklist_contract/src/main.nr @@ -52,7 +52,9 @@ contract TokenBlacklist { safe_u120_serialization::{SafeU120SerializationMethods, SAFE_U120_SERIALIZED_LEN}, roles::UserFlags, }; + // docs:start:interface use crate::interfaces::SlowMap; + // docs:end:interface struct Storage { admin: PublicState, @@ -91,21 +93,24 @@ contract TokenBlacklist { }, ), // Below is an abomination to have same value in private and public (immutable in solidity). + // docs:start:slow_updates_storage slow_update: ImmutableSingleton::new(context, 7, FieldNoteMethods), public_slow_update: PublicState::new( context, 8, AztecAddressSerializationMethods, ), + // docs:end:slow_updates_storage + } } } - + // docs:start:constructor #[aztec(private)] fn constructor(admin: AztecAddress, slow_updates_contract: AztecAddress) { let mut slow_note = FieldNote::new(slow_updates_contract.address); storage.slow_update.initialize(&mut slow_note, Option::none(), false); - + // docs:end:constructor let selector = compute_selector("_initialize((Field),(Field))"); context.call_public_function(context.this_address(), selector, @@ -121,8 +126,10 @@ contract TokenBlacklist { #[aztec(private)] fn init_slow_tree(user: AztecAddress) { let roles = UserFlags { is_admin: true, is_minter: false, is_blacklisted: false }.get_value() as Field; + // docs:start:get_and_update_private let slow = SlowMap::at(AztecAddress::new(storage.slow_update.get_note().value)); slow.update_at_private(&mut context, user.address, roles); + // docs:end:get_and_update_private context.call_public_function(context.this_address(), compute_selector("_init_slow_tree((Field))"), [context.msg_sender()]); @@ -138,13 +145,20 @@ contract TokenBlacklist { internal fn _initialize(new_admin: AztecAddress, slow_updates_contract: AztecAddress) { assert(new_admin.address != 0, "invalid admin"); storage.admin.write(new_admin); + // docs:start:write_slow_update_public storage.public_slow_update.write(slow_updates_contract); + // docs:end:write_slow_update_public + // docs:start:slowmap_initialize SlowMap::at(slow_updates_contract).initialize(context); + // docs:end:slowmap_initialize + } #[aztec(private)] fn update_roles(user: AztecAddress, roles: Field) { + // docs:start:slowmap_at let slow = SlowMap::at(AztecAddress::new(storage.slow_update.get_note().value)); + // docs:end:slowmap_at let caller_roles = UserFlags::new(slow.read_at(&mut context, context.msg_sender()) as u120); assert(caller_roles.is_admin, "caller is not admin"); @@ -153,8 +167,12 @@ contract TokenBlacklist { #[aztec(public)] fn mint_public(to: AztecAddress, amount: Field) { + // docs:start:get_public let slow = SlowMap::at(storage.public_slow_update.read()); + // docs:end:get_public + // docs:start:read_at_pub let to_roles = UserFlags::new(slow.read_at_pub(context, to.address) as u120); + // docs:end:read_at_pub assert(!to_roles.is_blacklisted, "Blacklisted: Recipient"); let caller_roles = UserFlags::new(slow.read_at_pub(context, context.msg_sender()) as u120); @@ -250,7 +268,9 @@ contract TokenBlacklist { #[aztec(private)] fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) { let slow = SlowMap::at(AztecAddress::new(storage.slow_update.get_note().value)); + // docs:start:slowmap_read_at let to_roles = UserFlags::new(slow.read_at(&mut context, to.address) as u120); + // docs:end:slowmap_read_at assert(!to_roles.is_blacklisted, "Blacklisted: Recipient"); let pending_shields = storage.pending_shields; @@ -287,6 +307,7 @@ contract TokenBlacklist { context.call_public_function(context.this_address(), selector, [to.address, amount]); } + // docs:start:transfer_private #[aztec(private)] fn transfer(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { let slow = SlowMap::at(AztecAddress::new(storage.slow_update.get_note().value)); @@ -294,6 +315,7 @@ contract TokenBlacklist { assert(!from_roles.is_blacklisted, "Blacklisted: Sender"); let to_roles = UserFlags::new(slow.read_at(&mut context, to.address) as u120); assert(!to_roles.is_blacklisted, "Blacklisted: Recipient"); + // docs:end:transfer_private if (from.address != context.msg_sender()) { assert_current_call_valid_authwit(&mut context, from); @@ -322,6 +344,7 @@ contract TokenBlacklist { let selector = compute_selector("_reduce_total_supply(Field)"); context.call_public_function(context.this_address(), selector, [amount]); + } /// Internal ///