Skip to content

Commit

Permalink
chore: yp docs sidebar (with some auto-formatting) (#3893)
Browse files Browse the repository at this point in the history
- Introducing a sidebar, because it turns out the docusaurus default (of
specifying `sidebar_position` doesn't scale).
- Also some markdown auto-formatting which didn't look too
controversial, so I kept it in.
- Also file & folder renaming to be `this-case` instead of `this_case`
(I copied the dominant case of this yellow paper dir), which will
probably make the diff look horrendous.
  • Loading branch information
iAmMichaelConnor authored Jan 9, 2024
1 parent 158c5be commit f7b007a
Show file tree
Hide file tree
Showing 74 changed files with 611 additions and 578 deletions.
2 changes: 2 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"cheatcodes",
"checksummed",
"cimg",
"ciphertext",
"clonedeep",
"clonedeepwith",
"cmd",
Expand Down Expand Up @@ -150,6 +151,7 @@
"persistable",
"pids",
"pkgs",
"plaintext",
"Plookup",
"pnat",
"Pokeable",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
---
title: Diversified and Stealth Accounts
sidebar_position: 4
---

The [keys specification](./specification.md) describes derivation mechanisms for diversified and stealth public keys. However, the protocol requires users to interact with addresses.
The [keys specification](./specification.md) describes derivation mechanisms for diversified and stealth public keys. However, the protocol requires users to interact with addresses.

## Computing Addresses

Expand All @@ -21,10 +20,10 @@ contract DiversifiedAccount
private fn entrypoint(payload: action[])
assert msg_sender == get_owner_address()
execute(payload)
private fn is_valid(message_hash: Field)
return get_owner_address().is_valid(message_hash)
internal private get_owner_address()
let address_preimage = pxe.get_address_preimage(this)
assert hash(address_preimage) == this
Expand All @@ -39,4 +38,4 @@ Given the contract does not require initialization since it has no constructor,

## Discarded Approaches

An alternative approach was to introduce a new type of call, a diversified call, that would allow the caller to impersonate any address they can derive from their own, for an enshrined derivation mechanism. Account contracts could use this opcode, as opposed to a regular call, to issue calls on behalf on their diversified and stealth addresses. However, this approach failed to account for calls made back to the account contracts, in particular authwit checks. It also required protocol changes, introducing a new type of call which could be difficult to reason about, and increased attack surface. The only benefit over the approach chosen is that it would require one less extra function call to hop from the user's main account contract to the diversified or stealth one.
An alternative approach was to introduce a new type of call, a diversified call, that would allow the caller to impersonate any address they can derive from their own, for an enshrined derivation mechanism. Account contracts could use this opcode, as opposed to a regular call, to issue calls on behalf on their diversified and stealth addresses. However, this approach failed to account for calls made back to the account contracts, in particular authwit checks. It also required protocol changes, introducing a new type of call which could be difficult to reason about, and increased attack surface. The only benefit over the approach chosen is that it would require one less extra function call to hop from the user's main account contract to the diversified or stealth one.
5 changes: 2 additions & 3 deletions yellow-paper/docs/addresses-and-keys/index.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
---
title: Addresses and Keys
sidebar_position: 2
---

Aztec has no concept of externally-owned accounts. Every address is meant to identify a smart contract in the network. Addresses are then a commitment to a contract class, a list of constructor arguments, and a set of keys.

Keys in Aztec are used both for authorization and privacy. Authorization keys are managed by account contracts, and not mandated by the protocol. Each account contract may use different authorization keys, if at all, with different signing mechanisms.
Keys in Aztec are used both for authorization and privacy. Authorization keys are managed by account contracts, and not mandated by the protocol. Each account contract may use different authorization keys, if at all, with different signing mechanisms.

Privacy keys are used for note encryption, tagging, and nullifying. These are also not enforced by the protocol. However, for facilitating composability, the protocol enshrines a set of well-known encryption and tagging mechanisms, that can be leveraged by applications as they interact with accounts.

The [specification](./specification.md) covers the main requirements for addresses and keys, along with their specification and derivation mechanisms, while the [precompiles](./precompiles.md) section describes well-known contract addresses, with implementations defined by the protocol, used for note encryption and tagging.
The [specification](./specification.md) covers the main requirements for addresses and keys, along with their specification and derivation mechanisms, while the [precompiles](./precompiles.md) section describes well-known contract addresses, with implementations defined by the protocol, used for note encryption and tagging.

Last, the [diversified and stealth accounts](./diversified-and-stealth.md) sections describe application-level recommendations for diversified and stealth accounts.

Expand Down
6 changes: 1 addition & 5 deletions yellow-paper/docs/addresses-and-keys/specification.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
title: Specification
sidebar_position: 1
description: Specification of address format in the protocol, default privacy keys format and derivation, and nullifier derivation.
---

Expand Down Expand Up @@ -94,10 +93,6 @@ $$

## Requirements for Keys

:::info Disclaimer
This is a draft. These requirements need to be considered by the wider team, and might change significantly before a mainnet release.
:::

### Scenario

A common illustration in this document is Bob sending funds to Alice, by:
Expand Down Expand Up @@ -594,6 +589,7 @@ Bob wants to send himself a private message (e.g. a record of the outgoing notes
> Note: rather than copying the 'shared secret' approach of Bob sending to Alice, we can cut a corner (because Bob is the sender and recipient, and so knows his own secrets).
> Note: if Bob has sent a private message to Alice, and he also wants to send himself a corresponding message:
>
> - he can likely re-use the ephemeral keypairs for himself.
> - he can include $\esk$ in the plaintext that he sends to himself, as a way of reducing the size of his $\ciphertext$ (since the $\esk$ will enable him to access all the information in the ciphertext that was sent to Alice).
Expand Down
4 changes: 0 additions & 4 deletions yellow-paper/docs/bytecode/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
title: Bytecode
---

:::info Disclaimer
This is a draft. The public VM and brillig are under heavy development, and specific details about how they are compiled and their bytecode might change in the future.
:::

This section describes how contracts are represented within the protocol for execution.

In the context of Aztec, a contract is a set of functions which can be of one of three types:
Expand Down
8 changes: 2 additions & 6 deletions yellow-paper/docs/calls/batched-calls.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
---
sidebar_position: 3
---

# Batched calls

Calls to private functions can be _batched_ instead of executed [synchronously](./sync-calls.md). When executing a batched call to a private function, the function is not executed on the spot, but enqueued for execution at the end of local execution. Once the private call stack has been emptied, all batched execution requests are grouped by target (contract and function selector), and executed via a single call to each target.
Expand All @@ -13,7 +9,7 @@ Batched calls are processed by the private kernel circuit. On each kernel circui
The arguments for the batched call are arranged in an array with one position for each individual call. Each position within the array is a nested array where the first element is the call context for that individual call, followed by the actual arguments of the call. A batched call is expected to return an array of `PrivateCircuitPublicInputs`, where each public input's call context matches the call context from the corresponding individual call. This allows batched delegate calls, where each individual call processed has a context of its own. This can be used to emit logs on behalf of multiple contracts within a single batched call.

<!-- TODO: The above seems to make the kernel circuit unnecessarily more complex, since we now need dedicated kernels that handle arrays of app circuit outputs instead of a single one. However, it is needed for precompiles that need to emit tagged notes on behalf of multiple calling contracts. The other option here is to grant precompiles special privileges to emit an event on behalf of any address, so they just use the call_context.msg_sender from each individual call. But the phrase "special privileges" makes me wary. -->

In pseudocode, the kernel circuit executes the following logic:

```
Expand All @@ -28,4 +24,4 @@ loop:
break
```

The rationale for batched calls is to minimize the number of function calls in private execution, in order to reduce total proving times. Batched calls are mostly intended for usage with note delivery precompiles, since these do not require synchronous execution, and allows for processing all notes to be encrypted and tagged with the same mechanism using the same call. Batched calls can also be used for other common functions that do not require to be executed synchronously and are likely to be invoked multiple times.
The rationale for batched calls is to minimize the number of function calls in private execution, in order to reduce total proving times. Batched calls are mostly intended for usage with note delivery precompiles, since these do not require synchronous execution, and allows for processing all notes to be encrypted and tagged with the same mechanism using the same call. Batched calls can also be used for other common functions that do not require to be executed synchronously and are likely to be invoked multiple times.
4 changes: 0 additions & 4 deletions yellow-paper/docs/calls/delegate-calls.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
---
sidebar_position: 6
---

# Delegate calls

Delegate calls are function calls against a contract class identifier instead of an instance. Any call, synchronous or asynchronous, can be made as a delegate call. The behavior of a delegate call is to execute the function code in the specified class identifier but on the context of the current instance. This opens the door to script-like executions and upgradeable contracts. Delegate calls are based on [EIP7](https://eips.ethereum.org/EIPS/eip-7).
Expand Down
5 changes: 1 addition & 4 deletions yellow-paper/docs/calls/enqueued-calls.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
---
sidebar_position: 2
---
# Enqueued calls

Calls from private functions to public functions are asynchronous. Since private and public functions are executed in different domains at different times and in different contexts, as the former are run by the user on a PXE and the latter by the sequencer, it is not possible for a private function to call a public one and await its result. Instead, private functions can _enqueue_ public function calls.

The process is analogous to [synchronous calls](./sync-calls.md), but rely on an `enqueuePublicFunctionCall` oracle call that accepts the same arguments. The returned object by the enqueue call is a `PublicCallStackItem` with a flag `is_execution_request` set and empty side effects, to reflect that the stack item has not been executed yet. As with synchronous calls, the caller is responsible for validating the function and arguments in the call stack item, and to push its hash to its public call stack, which represents the list of enqueued public function calls.

As the transaction is received by the sequencer, the public kernel circuit begins processing the enqueued public function calls from the transaction public call stack, pushing new recursive calls as needed, until the public call stack is empty, as described in the [synchronous calls](./sync-calls.md) section.
As the transaction is received by the sequencer, the public kernel circuit begins processing the enqueued public function calls from the transaction public call stack, pushing new recursive calls as needed, until the public call stack is empty, as described in the [synchronous calls](./sync-calls.md) section.
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
---
sidebar_position: 10
---

# Inter-Layer Calls

## Public-Private messaging

:::info Disclaimer
This is a draft. These requirements need to be considered by the wider team, and might change significantly before a mainnet release.
:::

Public state and private state exist in different trees. In a private function you cannot reference or modify public state.
Public state and private state exist in different trees. In a private function you cannot reference or modify public state.
Yet, it should be possible for:

1. private functions to call private or public functions
2. public functions to call private or public functions

Private functions are executed locally by the user and work by providing evidence of correct execution generated locally through kernel proofs. This way, the sequencer doesn't need to have knowledge of everything happening in the transaction, only the results. Public functions, on the other hand, are able to utilize the latest state to manage updates and perform alterations, as they are executed by the sequencer.
Private functions are executed locally by the user and work by providing evidence of correct execution generated locally through kernel proofs. This way, the sequencer doesn't need to have knowledge of everything happening in the transaction, only the results. Public functions, on the other hand, are able to utilize the latest state to manage updates and perform alterations, as they are executed by the sequencer.

Therefore, private functions are always executed first, as they are executed on a state $S_i$, where $i \le n$, with $S_n$ representing the current state where the public functions always operate on the current state $S_n$.
Therefore, private functions are always executed first, as they are executed on a state $S_i$, where $i \le n$, with $S_n$ representing the current state where the public functions always operate on the current state $S_n$.

This enables private functions to enqueue calls to public functions. But vice-versa is not true. Since private functions execute first, it cannot "wait" on the results of any of their calls to public functions. Stated differently, any calls made across domains are unilateral in nature.
This enables private functions to enqueue calls to public functions. But vice-versa is not true. Since private functions execute first, it cannot "wait" on the results of any of their calls to public functions. Stated differently, any calls made across domains are unilateral in nature.

The figure below shows the order of function calls on the left-hand side, while the right-hand side shows how the functions will be executed. Notably, the second private function call is independent of the output of the public function and merely occurs after its execution.

Tx call order be:

```mermaid
graph TD
A[Private Function 1] -->|Calls| B(Public Function 1)
Expand All @@ -39,17 +33,20 @@ graph TD
```

## Private to Public Messaging

When a private function calls a public function:

1. Public function args get hashed together
1. A public call stack item is created with the public function selector, it's contract address and args hash
1. The hash of the item gets enqueued into a separate public call stack and passed as inputs to the private kernel
1. The private kernel pushes these hashes into the public input, which the sequencer can see.
1. The private kernel pushes these hashes into the public input, which the sequencer can see.
1. PXE creates a transaction object as outlined [here](../transactions/tx-object.md) where it passes the hashes and the actual call stack item
1. PXE sends the transaction to the sequencer.
1. Sequencer then picks up the public call stack item and executes each of the functions.
1. Sequencer then picks up the public call stack item and executes each of the functions.
1. The Public VM which executes the methods then verifies that the hash provided by the private kernel matches the current call stack item.

### Handling Privacy Leakage and `msg.sender`

In the above design, the sequencer only sees the public part of the call stack along with any new commitments, nullifiers etc that were created in the private transaction i.e. should learns nothing more of the private transaction (such as its origin, execution logic etc).

:::warning
Expand All @@ -58,16 +55,17 @@ TODO: Haven't finalized what msg.sender will be

Within the context of these enqueued public functions, any usage of `msg_sender` should return **TODO**. If the `msg_sender` is the actual user, then it leaks privacy. If `msg_sender` is the contract address, this leaks which contract is calling the public method and therefore leaks which contract the user was interacting with in private land.

Therefore, when the call stack is passed to the kernel circuit, the kernel should assert the `msg_sender` is 0 and hash appropriately.
Therefore, when the call stack is passed to the kernel circuit, the kernel should assert the `msg_sender` is 0 and hash appropriately.

### Reverts

If the private part of the transaction reverts, then public calls are never enqueued. But if the public part of the transaction reverts, it should still revert the entire transaction i.e. the sequencer should drop the execution results of the private part of the transaction and not include those in the state transitioner smart contract. However, since the sequencer had to execute your transaction, appropriate fee will be charged. Reverting in public causing the whole transaction to be dropped enables existing paradigms of ethereum where your valid transaction can revert because of altered state e.g., trade incurring too much slippage.

## Public to Private Messaging

Since public functions execute after private functions, it isn't possible for public to call a private function in the same transaction. Nevertheless, it is quite useful for public functions to have a message passing system to private. A public function could add messages to an append only merkle tree to save messages from a public function call, that can later be executed by a private function. Note, only a transaction coming after the one including the message from a public function can consume it. In practice this means that unless you are the sequencer it will not be within the same rollup.

To elaborate, a public function may not have read access to encrypted private state in the note hash tree, but it can write to it. You could create a note in the public domain, compute it's note hash which gets passed to the inputs of the public VM which adds the hash to the note hash tree. The user who wants to redeem the note can add the note preimage to their PXE and then redeem/nullify the note in the private domain at a later time.
To elaborate, a public function may not have read access to encrypted private state in the note hash tree, but it can write to it. You could create a note in the public domain, compute it's note hash which gets passed to the inputs of the public VM which adds the hash to the note hash tree. The user who wants to redeem the note can add the note preimage to their PXE and then redeem/nullify the note in the private domain at a later time.

In the picture below, it is worth noting that all data reads performed by private functions are historical in nature, and that private functions are not capable of modifying public storage. Conversely, public functions have the capacity to manipulate private storage (e.g., inserting new commitments, potentially as part of transferring funds from the public domain to the private domain).

Expand Down
4 changes: 1 addition & 3 deletions yellow-paper/docs/calls/static-calls.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
---
sidebar_position: 5
---
# Static calls

[Synchronous calls](./sync-calls.md), both private and public, can be executed as _static_ calls. This means that the called function, and all nested calls within, cannot emit any modifying side effects, such as creating or consuming notes, writing to storage, or emitting events. The purpose of a static call is to query another contract while ensuring that the call will not modify state. Static calls are based on [EIP214](https://eips.ethereum.org/EIPS/eip-214).

In particular, the following fields of the returned `CallStackItem` must be zero or empty in a static call:

- `new_commitments`
- `new_nullifiers`
- `nullified_commitments`
Expand Down
Loading

0 comments on commit f7b007a

Please sign in to comment.