diff --git a/docs/docs/developers/contracts/references/storage/main.md b/docs/docs/developers/contracts/references/storage/main.md index 439784739f4..0511b564313 100644 --- a/docs/docs/developers/contracts/references/storage/main.md +++ b/docs/docs/developers/contracts/references/storage/main.md @@ -88,8 +88,9 @@ require(minters[msg.sender], "caller is not minter"); ## Further Reading -- Managing [Public State](./public_state.md) -- Jump to the page on [Private State](./private_state.md) +- [Public State](./public_state.md) +- [Private State](./private_state.md) +- [Shared State](./shared_state.md) ## Concepts mentioned diff --git a/docs/docs/developers/contracts/references/storage/public_state.md b/docs/docs/developers/contracts/references/storage/public_state.md index 54b59a6d858..d67227f1e34 100644 --- a/docs/docs/developers/contracts/references/storage/public_state.md +++ b/docs/docs/developers/contracts/references/storage/public_state.md @@ -6,7 +6,7 @@ On this page we will look at how to manage public state in Aztec contracts. We w For a higher level overview of the state model in Aztec, see the [state model](../../../../learn/concepts/hybrid_state/main.md) page, or jump back to the previous page on [Storage](./main.md). -## Overview +## `PublicMutable` The `PublicMutable` (formerly known as `PublicState`) struct is generic over the variable type `T`. The type _must_ implement Serialize and Deserialize traits, as specified here: @@ -17,6 +17,8 @@ The struct contains a `storage_slot` which, similar to Ethereum, is used to figu You can find the details of `PublicMutable` in the implementation [here](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr). +For a version of `PublicMutable` that can also be read in private, head to [`SharedMutable`](./shared_state.md#sharedmutable). + :::info An example using a larger struct can be found in the [lending example](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-contracts/contracts/lending_contract)'s use of an [`Asset`](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/noir-contracts/contracts/lending_contract/src/asset.nr). ::: @@ -67,9 +69,9 @@ We have a `write` method on the `PublicMutable` struct that takes the value to w --- -## Public Immutable +## `PublicImmutable` -`PublicImmutable` is a type that can be written once during a contract deployment and read later on from public only. +`PublicImmutable` is a type that can be written once during a contract deployment and read later on from public only. For a version of `PublicImmutable` that can also be read in private, head to [`SharedImmutable`](./shared_state.md#sharedimmutable). Just like the `PublicMutable` it is generic over the variable type `T`. The type `MUST` implement Serialize and Deserialize traits. @@ -91,48 +93,3 @@ Is done exactly like the `PublicMutable` struct, but with the `PublicImmutable` Reading the value is just like `PublicMutable`. #include_code read_public_immutable /noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -## Shared Immutable - -`SharedImmutable` (formerly known as `StablePublicState`) is a type which is very similar to `PublicImmutable` but with an addition of a private getter (can be read from private). - -Since private execution is based on historical data, the user can pick ANY of its prior values to read from. This is why it `MUST` not be updated after the contract is deployed. The variable should be initialized at the constructor and then never changed. - -This makes the immutable public variables useful for stuff that you would usually have in `immutable` values in solidity. For example this can be the name of a token or its number of decimals. - -Just like the `PublicMutable` it is generic over the variable type `T`. The type `MUST` implement Serialize and Deserialize traits. - -You can find the details of `SharedImmutable` in the implementation [here](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/shared_immutable.nr). - -:::info -The word `Shared` in Aztec protocol means read/write from public, read only from private. -::: - -### `new` - -Is done exactly like the `PublicMutable` struct, but with the `SharedImmutable` struct. - -#include_code storage-shared-immutable-declaration /noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -#include_code storage-shared-immutable /noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -### `initialize` - -#include_code initialize_decimals /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust - -:::warning Should only be called as part of the deployment. -If this is called outside the deployment transaction multiple values could be used down the line, potentially breaking the contract. - -Currently this is not constrained as we are in the middle of changing deployments. -::: - -### `read_public` - -Reading the value is like `PublicMutable`, simply with `read_public` instead of `read`. -#include_code read_decimals_public /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust - -### `read_private` - -Reading the value is like `PublicMutable`, simply with `read_private` instead of `read`. This part can only be executed in private. - -#include_code read_decimals_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust diff --git a/docs/docs/developers/contracts/references/storage/shared_state.md b/docs/docs/developers/contracts/references/storage/shared_state.md new file mode 100644 index 00000000000..2039eaa56b7 --- /dev/null +++ b/docs/docs/developers/contracts/references/storage/shared_state.md @@ -0,0 +1,137 @@ +--- +title: Shared State +--- + +This page covers an advanced type of state called shared state, which is public state that can also be read in private (and hence _shared_ by both domains). It is highly recommended that you're familiar with both [private](./private_state.md) and [public](./public_state.md) state before reading this page. + +## Overview and Motivation + +A typical example of shared state is some kind of system configuration, such as a protocol fee or access control permissions. These values are public (known by everyone) and mutable. Reading them in private however is tricky: private execution is always asynchronous and performed over _historical_ state, and hence one cannot easily prove that a given public value is current. + +A naive way to solve this is to enqueue a public call that will assert the current public value, but this leaks _which_ public value is being read, severely reducing privacy. Even if the value itself is already public, the fact that we're using it because we're interacting with some related contract is not. For example, we may leak that we're interacting with a certain DeFi protocol by reading its fee. + +An alternative approach is to create notes in public that are then nullified in private, but this introduces contention: only a single user may use the note and therefore read the state, since nullifying it will prevent all others from doing the same. In some schemes there's only one account that will read the state anyway (such as when shielding token balances), but this is not the general case. + +Shared state works around this by introducing **delays**: while public values are mutable, they cannot change _immediately_. Instead, a value change must be scheduled ahead of time, and some minimum amount of time must pass between the scheduling and the new value taking effect. This means that we can privately prove that a historical public value cannot possibly change before some point in the future (due to the minimum delay), and therefore that our transaction will be valid **as long as it gets included before this future time**. + +This results in the following key properties of shared state: + +- shared values can only be changed after a certain delay has passed, never immediately +- the scheduling of value changes is itself public, including both the new value and the time at which the change will take effect +- transactions that read shared state become invalid after some time if not included in a block + +## Privacy Considerations + +While shared state variables are much less leaky than the assertion in public approach, they do reveal some information to external observers by setting the `max_block_number` property of the transaction request. The impact of this can be mitigated with proper selection of the delay value and schedule times. + +### Choosing Delays + +The `max_block_number` transaction property will be set to a value close to the current block number plus the duration of the delay in blocks. The exact value depends on the historical block over which the private proof is constructed. For example, if the current block number is 100 and a shared state variable has a delay of 20 blocks, then transactions that read this value privately will set `max_block_number` to a value close to 120 (clients building proofs on older state will select a lower `max_block_number`). This implicitly leaks the duration of the delay. + +Applications using similar delays will therefore be part of the same privacy set. It is expected for social coordination to result in small set of predetermined delays that developers choose from depending on their needs, as an example a viable set might be: 12 hours (for time-sensitive operations, such as emergency mechanisms), 5 days (for middle-of-the-road operations) and 2 weeks (for operations that require lengthy public scrutiny). + +:::note +Shared state delays are currently hardcoded at compilation time and cannot be changed, but there are plans to make this a mutable value. +:::note + +### Choosing Epochs + +If a value change is scheduled in the near future, then transactions that access this shared state will be forced to set a lower `max_block_number` right before the value change. For example, if the current block number is 100 and a shared state variable with a delay of 20 blocks has a value change scheduled for block 105, then transactions that read this value privately will set `max_block_number` to 104. Since the blocks at which shared state values change are public, it might be deduced that transactions with a `max_block_number` value close to the current block number are reading some state variable with a changed scheduled at `max_block_number + 1`. + +Applications that schedule value changes at the same time will therefore be part of the same privacy set. It is expected for social coordination to result in ways to achieve this, e.g. by scheduling value changes so that they land on blocks that are multiples of some value - we call these epochs. + +There is a tradeoff between frequent and infrequent epochs: frequent epochs means more of them, and therefore fewer updates on each, shrinking the privacy set. But infrequent epochs result in the effective delay of value changes being potentially larger than desired - though an application can always choose to do an out-of-epoch update if needed. + +:::note +Shared state variables do not allow selection of the value change block number, but there are plans to make this configurable. +:::note + +Note that wallets can also warn users that a value change will soon take place and that sending a transaction at that time might result in reduced privacy, allowing them to choose to wait until after the epoch. + +### Network Cooperation + +Even though only transactions that interact with shared state _need_ to set the `max_block_number` property, there is no reason why transactions that do not wouldn't also set this value. If indeed most applications converge on a small set of delays, then wallets could opt to select any of those to populate the `max_block_number` field, as if they were interacting with a shared state variable with that delay. + +This prevents the network-wide privacy set from being split between transactions that read shared state and those that don't, which is beneficial to everyone. + +## `SharedMutable` + +`SharedMutable` is a shared state variable for mutable state. It provides capabilities to read the same state both in private and public, and to schedule value changes after a delay. You can view the implementation [here](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr). + +Unlike other state variables, `SharedMutable` receives not only a type parameter for the underlying datatype, but also a `DELAY` type parameter with the value change delay as a number of blocks. + +#include_code shared_mutable_storage /noir-projects/noir-contracts/contracts/auth_contract/src/main.nr rust + +:::note +`SharedMutable` requires that the underlying type `T` implements both the `ToField` and `FromField` traits, meaning it must fit in a single `Field` value. There are plans to extend support by requiring instead an implementation of the `Serialize` and `Deserialize` traits, therefore allowing for multi-field variables, such as complex structs. +::: + +Since `SharedMutable` lives in public storage, by default its contents are zeroed-out. Intialization is performed by calling `schedule_value_change`, resulting in initialization itself being delayed. + +### `schedule_value_change` + +This is the means by which a `SharedMutable` variable mutates its contents. It schedules a value change for the variable at a future block after the `DELAY` has elapsed from the current block, at which point the scheduled value becomes the current value automatically and without any further action, both in public and in private. If a pending value change was scheduled but not yet effective (because insufficient blocks had elapsed), then the previous schedule value change is replaced with the new one and eliminated. There can only be one pending value change at a time. + +This function can only be called in public, typically after some access control check: + +#include_code shared_mutable_schedule /noir-projects/noir-contracts/contracts/auth_contract/src/main.nr rust + +If one wishes to schedule a value change from private, simply enqueue a public call to a public `internal` contract function. Recall that **all scheduled value changes, including the new value and scheduled block are public**. + +:::warning +A `SharedMutable`'s storage **must** only be mutated via `schedule_value_change`. Attempting to override this by manually accessing the underlying storage slots breaks all properties of the data structure, rendering it useless. +::: + +### `get_current_value_in_public` + +Returns the current value in a public execution context. Once a value change is scheduled via `schedule_value_change` and a number of blocks equal to the delay passes, this automatically returns the new value. + +#include_code shared_mutable_get_current_public /noir-projects/noir-contracts/contracts/auth_contract/src/main.nr rust + +### `get_current_value_in_private` + +Returns the current value in a private execution context. Once a value change is scheduled via `schedule_value_change` and a number of blocks equal to the delay passes, this automatically returns the new value. + +Calling this function will set the `max_block_number` property of the transaction request, introducing a new validity condition to the entire transaction: it cannot be included in any block with a block number larger than `max_block_number`. This could [potentially leak some privacy](#privacy-considerations). + +#include_code shared_mutable_get_current_private /noir-projects/noir-contracts/contracts/auth_contract/src/main.nr rust + +### `get_scheduled_value_in_public` + +Returns the last scheduled value change, along with the block number at which the scheduled value becomes the current value. This may either be a pending change, if the block number is in the future, or the last executed scheduled change if the block number is in the past (in which case there are no pending changes). + +#include_code shared_mutable_get_scheduled_public /noir-projects/noir-contracts/contracts/auth_contract/src/main.nr rust + +It is not possible to call this function in private: doing so would not be very useful at it cannot be asserted that a scheduled value change will not be immediately replaced if `shcedule_value_change` where to be called. + +## `SharedImmutable` + +`SharedImmutable` (formerly known as `StablePublicState`) is a simplification of the `SharedMutable` case, where the value can only be set once during initialization. Because there's no further mutation, there's no need for delays. These state variables are useful for stuff that you would usually have in `immutable` values in Solidity, e.g. this can be the name of a token or its number of decimals. + +Like most state variables, `SharedImmutable` is generic over the variable type `T`. This type `MUST` implement the `Serialize` and `Deserialize` traits. + +#include_code storage-shared-immutable-declaration /noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +You can find the details of `SharedImmutable` in the implementation [here](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/shared_immutable.nr). + +### `initialize` + +This function sets the immutable value. It must only be called once during contract construction. + +#include_code initialize_decimals /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust + +:::warning +A `SharedImmutable`'s storage **must** only be set once via `initialize`. Attempting to override this by manually accessing the underlying storage slots breaks all properties of the data structure, rendering it useless. +::: + +### `read_public` + +Returns the stored immutable value in a public execution context. + +#include_code read_decimals_public /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust + +### `read_private` + +Returns the stored immutable value in a private execution context. + +#include_code read_decimals_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust diff --git a/docs/docs/developers/contracts/resources/common_patterns/main.md b/docs/docs/developers/contracts/resources/common_patterns/main.md index 66c3b9b9138..312eed8552e 100644 --- a/docs/docs/developers/contracts/resources/common_patterns/main.md +++ b/docs/docs/developers/contracts/resources/common_patterns/main.md @@ -38,12 +38,11 @@ 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. This is the naive way: +You can't read public storage in private domain. But nevertheless reading public storage is desirable. There are two ways to achieve the desired effect: - +1. For public values that change infrequently, you can use [shared state](../../references/storage/shared_state.md). -- You pass the data as a parameter to your private method and later assert in public that the data is correct. E.g.: +1. You pass the data as a parameter to your private method and later assert in public that the data is correct. E.g.: ```rust struct Storage { diff --git a/docs/docs/learn/concepts/accounts/keys.md b/docs/docs/learn/concepts/accounts/keys.md index 5864abdc028..1cf42aa3755 100644 --- a/docs/docs/learn/concepts/accounts/keys.md +++ b/docs/docs/learn/concepts/accounts/keys.md @@ -27,15 +27,9 @@ Storing the signing public key in a private note makes it accessible from the en Similar to using a private note, but using an immutable private note removes the need to nullify the note on every read. This generates less nullifiers and commitments per transaction, and does not enforce an order across transactions. However, it does not allow the user to rotate their key should they lose it. - +A compromise between the two solutions above is to use [shared state](../../../developers/contracts/references/storage/shared_state.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 mutable shared state, as well as imposing restrictions on how fast keys can be rotated due to minimum delays. ### Reusing the privacy master key diff --git a/docs/docs/learn/concepts/accounts/main.md b/docs/docs/learn/concepts/accounts/main.md index deac366c16c..55746cc1524 100644 --- a/docs/docs/learn/concepts/accounts/main.md +++ b/docs/docs/learn/concepts/accounts/main.md @@ -129,11 +129,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). - - +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 [`SharedMutable`](../../../developers/contracts/references/storage/shared_state.md#sharedmutable) 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/learn/concepts/communication/public_private_calls.md b/docs/docs/learn/concepts/communication/public_private_calls.md index 2bd3d02e037..67ff389bfc6 100644 --- a/docs/docs/learn/concepts/communication/public_private_calls.md +++ b/docs/docs/learn/concepts/communication/public_private_calls.md @@ -78,10 +78,12 @@ Theoretically the builder has all the state trees after the public function has From the above, we should have a decent idea about what private and public functions can do inside the L2, and how they might interact. - +Access control like this cannot easily be enforced in the private domain, as reading is also nullifying (to ensure data is up to date). However, as it is possible to read historical public state, one can combine private and public functions to get the desired effect. + +This concept is known as [shared state](../../../developers/contracts/references/storage/shared_state.md), and relies on using delays when changing public data so that it can also be read in private with currentness guarantees. Since values cannot be immediately modified but instead require delays to elapse, it is possible to privately prove that an application is using the current value _as long as the transaction gets included before some time in the future_, which would be the earliest the value could possibly change. + +If the public state is only changed infrequently, and it is acceptable to have delays when doing so, then shared state is a good solution to this problem. diff --git a/docs/docs/misc/roadmap/engineering_roadmap.md b/docs/docs/misc/roadmap/engineering_roadmap.md index 51e49405774..61c5073f022 100644 --- a/docs/docs/misc/roadmap/engineering_roadmap.md +++ b/docs/docs/misc/roadmap/engineering_roadmap.md @@ -21,13 +21,13 @@ The engineering roadmap is long. There are no timings assigned here. In a loose - Recommended Aztec smart contract coding patterns - Access Control (whitelists/blacklists) - probably needs the Shared Mutable. - Basic _example_ private tokens - - Including recursive calls to 'get_notes'. + - Including recursive calls to 'get_notes'. - Compliant private tokens - Private NFTs - Public tokens - Depositing and withdrawing tokens - - L1<\>L2 - - public<\>private + - L1<\>L2 + - public<\>private - The Aztec Connect bridging pattern - Using Keys (the fully-featured version of keys that we want to build) - Plume nullifiers @@ -38,21 +38,23 @@ The engineering roadmap is long. There are no timings assigned here. In a loose ## Polishing what we have - Refactoring sprints. - - Reduce tech debt. - - More tests. + - Reduce tech debt. + - More tests. ## Enforcing correct ordering Public & Private State Transitions ## Enforcing correct ordering of other 'side effects' + - Log ordering - Enqueued public function calls ## What data actually needs to be submitted on-chain? + - For Public Functions: - - Just emit the initially-enqueued public function request data? (The 'inputs' of the tx); - - I.e. contract address, function selector, args, call_context. - - OR, Just emit the final state transitions? (The 'outputs' of the tx) - - I.e. the leaf indices and new values of the public data tree; and the new note hashes/nullifiers of the note hash tree; and logs; and l2->L1 messages. + - Just emit the initially-enqueued public function request data? (The 'inputs' of the tx); + - I.e. contract address, function selector, args, call_context. + - OR, Just emit the final state transitions? (The 'outputs' of the tx) + - I.e. the leaf indices and new values of the public data tree; and the new note hashes/nullifiers of the note hash tree; and logs; and l2->L1 messages. ## Proper specs @@ -86,16 +88,16 @@ CI takes up a significant amount of time. It gets its own section here, so we re ## Fees - Design the Protocol - - Interdependence on the Sequencer & Upgradeability protocols. - - Pay fees in 1 currency, or fee abstraction? - - Escrowing fees - - Rebates - - L1->L2 message fees. - - L2->L1 fees - - Etc. + - Interdependence on the Sequencer & Upgradeability protocols. + - Pay fees in 1 currency, or fee abstraction? + - Escrowing fees + - Rebates + - L1->L2 message fees. + - L2->L1 fees + - Etc. - Build it. - - Gas metering - - Etc. + - Gas metering + - Etc. ## Note Discovery @@ -119,14 +121,14 @@ CI takes up a significant amount of time. It gets its own section here, so we re ## Shared Mutable State -We _need_ a way to read mutable public data from a private function. We are moving away from the old Slow Updates Tree in favor of Shared Mutable. - +We _need_ a way to read mutable public data from a private function. We are moving away from the old Slow Updates Tree in favor of [Shared Mutable](../../developers/contracts/references/storage/shared_state.md). ## Contract classes and instances? - There's a suggestion to introduce contract classes. ## Delegatecalls vs SetCode + - Which? (if at all) ## Remove the contracts tree? 🤯 @@ -134,18 +136,18 @@ We _need_ a way to read mutable public data from a private function. We are movi - There's a suggestion to remove the notion of a contracts tree. What do we actually need the tree for? To check that a vk hash exists for a particular contract address? - If the contract address contains the function tree, and it also contains data about the constructor args to that contract, then perhaps we don't need the contract tree to exist. - We might still need the notion of a 'deployment': - - to broadcast a contract's bytecode; - - to 'reserve' a contract address; - - and possibly to prevent a constructor from being executed twice (although an app could do this ("constructor abstraction")). + - to broadcast a contract's bytecode; + - to 'reserve' a contract address; + - and possibly to prevent a constructor from being executed twice (although an app could do this ("constructor abstraction")). ## Cryptography review checkpoint - Once we have specs (see above), we should review the rigour and secureness to our protocol. - - Choice of hashes - - Domain separation - - Choice of encryption scheme - - Keys - - A security review of the protocol as a whole + - Choice of hashes + - Domain separation + - Choice of encryption scheme + - Keys + - A security review of the protocol as a whole ## Testing UX team @@ -153,46 +155,49 @@ A team focussed on testing and debugging UX. This team should have free rein to design and add any features they see fit. Some example features: + - Writing contract tests in Noir. - - Mocking oracles. + - Mocking oracles. - Taking inspiration from other testing frameworks. - Much more detailed logging if things go wrong. - Errors which only spit out opaque 254-bit hex numbers are bad. - - Ensure all circuits are fed the human-readable information underlying all of these hex numbers. - - If tree root comparisons (expected vs actual) fail, human-readable details about the leaves in trees should be provided. + - Ensure all circuits are fed the human-readable information underlying all of these hex numbers. + - If tree root comparisons (expected vs actual) fail, human-readable details about the leaves in trees should be provided. ## Tooling ## Proper Circuits ### Redesign + - The Bus - - The bus will have an impact on the way we design the circuit logic. - - We can hopefully avoid hashing in circuit 1 and unpacking that hash in circuit 2. - - Understand the 'bus' and how we can use it to pass variable amounts of data between recursive circuit iterations. - - Redesign all circuit logic to allow for the variable-sized arrays that the 'bus' enables. + - The bus will have an impact on the way we design the circuit logic. + - We can hopefully avoid hashing in circuit 1 and unpacking that hash in circuit 2. + - Understand the 'bus' and how we can use it to pass variable amounts of data between recursive circuit iterations. + - Redesign all circuit logic to allow for the variable-sized arrays that the 'bus' enables. - Enable 'dynamic/variable-sized **loops**' - - allow each `for` loop (eg read requests, insertions, commitment squashing, call stack processing, bip32 key derivation, etc.) to vary in size, by deferring each loop to its own final circuit. This would require complex folding stuff. - - This would give much more flexibility over the sizes of various arrays that a circuit can output. Without it, if one array of an app circuit needs to be size 2000, but other arrays aren't used, we'd use a kernel where every array is size 2048, meaning a huge amount of unnecessary loops of computation for those empty arrays. + - allow each `for` loop (eg read requests, insertions, commitment squashing, call stack processing, bip32 key derivation, etc.) to vary in size, by deferring each loop to its own final circuit. This would require complex folding stuff. + - This would give much more flexibility over the sizes of various arrays that a circuit can output. Without it, if one array of an app circuit needs to be size 2000, but other arrays aren't used, we'd use a kernel where every array is size 2048, meaning a huge amount of unnecessary loops of computation for those empty arrays. - Improvements - - We can definitely change how call stacks are processed within a kernel, to reduce hashing. - - Squash pending note hashes/nullifiers in every kernel iteration, to enable a deeper nested call depth. + - We can definitely change how call stacks are processed within a kernel, to reduce hashing. + - Squash pending note hashes/nullifiers in every kernel iteration, to enable a deeper nested call depth. - Topology of a rollup - - Revisit the current topology: - - We can make the rollup trees 'wonky' (rather than balanced), meaning a sequencer doesn't need to prove a load of pointless 'padding' proofs? - - This would also enable new txs (entering the tx pool) to be added to a rollup block 'on-the-fly' (mid way through a rollup being proven) - but of course, the sequencer selection protocol might require an up-front commitment, so this might not be possible for that reason (sad face). - - We can definitely redesign the public kernel circuit to be a '2x2' topology (i.e. a tree of public kernel proofs), to get a logarithmic speed-up (parallelism). The question is, with folding schemes, do we need that optimization? + - Revisit the current topology: + - We can make the rollup trees 'wonky' (rather than balanced), meaning a sequencer doesn't need to prove a load of pointless 'padding' proofs? + - This would also enable new txs (entering the tx pool) to be added to a rollup block 'on-the-fly' (mid way through a rollup being proven) - but of course, the sequencer selection protocol might require an up-front commitment, so this might not be possible for that reason (sad face). + - We can definitely redesign the public kernel circuit to be a '2x2' topology (i.e. a tree of public kernel proofs), to get a logarithmic speed-up (parallelism). The question is, with folding schemes, do we need that optimization? #### Refactor of packing & unpacking data in circuits We often pack data in circuit A, and then unpack it again in circuit B. + - args_hash - return_values_hash - call stacks - read requests - etc. -Also, for logs in particular, we allow arbitrary-sized logs. But this requires sha256 packing inside an app circuit (which is slow) (and sha256 unpacking in Solidity (which is relatively cheap)). Perhaps we also use the bus ideas for logs, to give _some_ variability in log length, but up to an upper bound. +Also, for logs in particular, we allow arbitrary-sized logs. But this requires sha256 packing inside an app circuit (which is slow) (and sha256 unpacking in Solidity (which is relatively cheap)). Perhaps we also use the bus ideas for logs, to give _some_ variability in log length, but up to an upper bound. Also, we do a lot of sha256-compressing in our kernel and rollup circuits for data which must be checked on-chain, but grows exponentially with every round of iteration. E.g.: new contract deployment data, new nullifiers, new note hashes, public state transition data, etc. This might be unavoidable. Maybe all we can do is use polynomial commitments when the EIP-4844 work is done. But maybe we can use the bus for this stuff too. @@ -214,9 +219,11 @@ Also, we do a lot of sha256-compressing in our kernel and rollup circuits for da ### Ultra -> Standard Squisher Circuit ## Authentication: access to private data + - Private data must not be returned to an app, unless the user authorizes it. ## Validation: preventing execution of malicious bytecode + - A node should check that the bytecode provided by an application for a given app matches the leaf in the contract tree to ensure that user doesn't execute unplanned code which might access their notes. ## Fuzz Testing @@ -235,22 +242,24 @@ An investigation into how formal verification techniques might improve the secur - Poseidon hashing in barretenberg. ## Tree epochs + - Nullifier tree epochs - Maybe other tree epochs. ## Chaining txs + - We have the ability to spend pending notes (which haven't-yet been added to the tree) _within the context of a single tx_. - We need the ability to spend pending notes (which haven't yet been added to the tree) across different txs, within the context of a single rollup. - - This happens if Alice generates two txs X & Y, where tx Y spends notes from tx X. If we want Alice to be able to generate these txs in parallel (without interacting with the network at all), we need a way for tx Y to spend notes before they've been added to the tree. The 'chaining tx' concepts from Aztec Connect can enable this. + - This happens if Alice generates two txs X & Y, where tx Y spends notes from tx X. If we want Alice to be able to generate these txs in parallel (without interacting with the network at all), we need a way for tx Y to spend notes before they've been added to the tree. The 'chaining tx' concepts from Aztec Connect can enable this. - This added _a lot_ of complexity to Aztec Connect. Especially around fees, iirc. Caution needed. ## EIP-4844 - Understand it. Spec it. Build it. - Includes: - - Smart Contract changes - - Circuit changes - - A circuit to prove equivalence vs a BLS12-381 polynomial commitment. + - Smart Contract changes + - Circuit changes + - A circuit to prove equivalence vs a BLS12-381 polynomial commitment. ## Make it all work in a browser @@ -259,8 +268,3 @@ An investigation into how formal verification techniques might improve the secur ## Internal Audit ## External Audits - - - - - diff --git a/docs/sidebars.js b/docs/sidebars.js index c10514ddbe9..d9f2ea07167 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -436,6 +436,7 @@ const sidebars = { items: [ "developers/contracts/references/storage/private_state", "developers/contracts/references/storage/public_state", + "developers/contracts/references/storage/shared_state", ], }, { diff --git a/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr b/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr index 04385202409..836e01bb41d 100644 --- a/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr @@ -11,7 +11,9 @@ contract Auth { struct Storage { // Admin can change the value of the authorized address via set_authorized() admin: PublicImmutable, + // docs:start:shared_mutable_storage authorized: SharedMutable, + // docs:end:shared_mutable_storage } #[aztec(public)] @@ -23,20 +25,27 @@ contract Auth { // value of 0 and block of change 0, meaning it is effectively autoinitialized at the zero address. } + // docs:start:shared_mutable_schedule #[aztec(public)] fn set_authorized(authorized: AztecAddress) { assert_eq(storage.admin.read(), context.msg_sender(), "caller is not admin"); storage.authorized.schedule_value_change(authorized); + // docs:end:shared_mutable_schedule } #[aztec(public)] fn get_authorized() -> AztecAddress { + // docs:start:shared_mutable_get_current_public storage.authorized.get_current_value_in_public() + // docs:end:shared_mutable_get_current_public } #[aztec(public)] fn get_scheduled_authorized() -> AztecAddress { - storage.authorized.get_scheduled_value_in_public().0 + // docs:start:shared_mutable_get_scheduled_public + let (scheduled_value, _block_of_change): (AztecAddress, u32) = storage.authorized.get_scheduled_value_in_public(); + // docs:end:shared_mutable_get_scheduled_public + scheduled_value } #[aztec(private)] @@ -44,7 +53,9 @@ contract Auth { // Reading a value from authorized in private automatically adds an extra validity condition: the base rollup // circuit will reject this tx if included in a block past the block horizon, which is as far as the circuit can // guarantee the value will not change from some historical value (due to CHANGE_AUTHORIZED_DELAY_BLOCKS). + // docs:start:shared_mutable_get_current_private let authorized = storage.authorized.get_current_value_in_private(); + // docs:end:shared_mutable_get_current_private assert_eq(authorized, context.msg_sender(), "caller is not authorized"); } } diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr index 4c4eb170e98..da0b4587bb6 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr @@ -76,9 +76,7 @@ contract DocsExample { set: PrivateSet::new(context, 5), // docs:end:storage-set-init private_immutable: PrivateImmutable::new(context, 6), - // docs:start:storage-shared-immutable shared_immutable: SharedImmutable::new(context, 7), - // docs:end:storage-shared-immutable // docs:start:storage-minters-init minters: Map::new( context,