Skip to content

Commit

Permalink
chore(yellow_paper): fixes to my work on public private messages (#3507)
Browse files Browse the repository at this point in the history
PR #3491 got accidentally merged without reviews. So here we are!
  • Loading branch information
rahul-kothari committed Dec 6, 2023
1 parent 0d8dd8d commit 33a4f63
Showing 1 changed file with 32 additions and 10 deletions.
42 changes: 32 additions & 10 deletions yellow-paper/docs/calls/public_private_messaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,55 @@ sidebar_position: 5
This is a draft. These requirements need to be considered by the wider team, and might change significantly before a mainnet release.
:::

Private functions work by providing evidence of correct execution generated locally through kernel proofs. Public functions, on the other hand, are able to utilize the latest state to manage updates and perform alterations. As such, public state and private state are in different trees. In a private function you cannot reference or modify public state and vice versa.

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

For private execution, the user executed methods locally and presents evidence of correct execution as part of their transaction in the form of a kernel proof (generated locally on user device ahead of time). This way, the builder doesn't need to have knowledge of everything happening in the transaction, only the results. However, public functions are executed at the "tip" of the chain (i.e. make use of the latest updates), they can only be done by a builder who is aware of all the changes. Therefore a public function can't be executed locally by the user in the same way a private function is, as it would lead to race conditions, if the user doesn't keep track of the latest updates of the chain. If we were to build this public proof on the latest state, we would encounter problems. How can two different users build proofs at the same time, given that they will be executed one after the other by the sequencer? The simple answer is that they cannot, as race conditions would arise where one of the proofs would be invalidated by the other due to a change in the state root (which would nullify Merkle paths).
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.

As a result, 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.

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.

![Public - Private Ordering](./images/calls/pvt_pub_ordering.png)
Tx call order be:
```mermaid
graph TD
A[Private Function 1] -->|Calls| B(Public Function 1)
A -----> |Followed by| C[Private Function 2]
```

## Private to Public Messaging
If a private function in an Aztec smart contract wants to call a public function, it gets pushed into a separate public call stack that is enqueued. The private kernel circuit which must prove the execution of the private function(s), then hashes each of the item in the call stack and returns that. The private kernel proof, the public inputs of the private kernel (which contain the hash of the each of the public call stack item) and other transaction data (like enqueued public function calls, new commitments, nullifiers etc) get passed along to the sequencer. Sequencer then picks up the public call stack item and executes each of the functions. The Public VM which executes the methods then verifies that the hash provided by the private kernel matches the current call stack item.
But Tx execution order will be

```mermaid
graph TD
A[Private Function 1] -->|Calls| B(Private Function 2)
A -----> |Followed by| C[Public Function 1]
```

This way, you can destroy your private state and create them in public within the same transaction or indirectly assert constraints on the execution of the private functions with latest data.
## 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. 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. 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).

But what if the enqueued public function makes use of `msg_sender` which is meant to use
:::warning
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.

Specifically, when the call stack is passed to the kernel circuit, the kernel should assert the `msg_sender` is 0 and hash appropriately. `msg_sender` could be the contract address too instead of `0`, but it 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.

### Reverts

Expand Down

0 comments on commit 33a4f63

Please sign in to comment.