diff --git a/cspell.json b/cspell.json index c9298ca6640..299b4ccf203 100644 --- a/cspell.json +++ b/cspell.json @@ -168,6 +168,7 @@ "println", "productionify", "protobuf", + "protogalaxy", "proxied", "proxified", "proxify", @@ -229,6 +230,7 @@ "unshield", "unshielding", "unzipit", + "updateable", "upperfirst", "usecase", "usecases", diff --git a/yellow-paper/docs/addresses-and-keys/address.md b/yellow-paper/docs/addresses-and-keys/address.md new file mode 100644 index 00000000000..1304efcf15b --- /dev/null +++ b/yellow-paper/docs/addresses-and-keys/address.md @@ -0,0 +1,49 @@ +--- +title: Address +--- + +An address is computed as the hash of the following fields: + + +| Field | Type | Description | +|----------|----------|----------| +| `salt` | `Field` | User-generated pseudorandom value for uniqueness. | +| `deployer` | `AztecAddress` | Optional address of the deployer of the contract. | +| `contract_class_id` | `Field` | Identifier of the contract class for this instance. | +| `initialization_hash` | `Field` | Hash of the selector and arguments to the constructor. | +| `portal_contract_address` | `EthereumAddress` | Address of the L1 portal contract, zero if none. | +| `public_keys_hash` | `Field` | Hash of the struct of public keys used for encryption and nullifying by this contract, zero if no public keys. | + +Storing these fields in the address preimage allows any part of the protocol to check them by recomputing the hash and verifying that the address matches. Examples of these checks are: + +- Sending an encrypted note to an undeployed account, which requires the sender app to check the recipient's public key given their address. This scenario also requires the recipient to share with the sender their public key and rest of preimage. +- Having the kernel circuit verify that the code executed at a given address matches the one from the class. +- Asserting that the initialization hash matches the function call in the contract constructor. +- Checking the portal contract address when sending a cross-chain message. + +:::warning +We may remove the `portal_contract_address` as a first-class citizen. +::: + +The hashing scheme for the address should then ensure that checks that are more frequent can be done cheaply, and that data shared out of band is kept manageable. We define the hash to be computed as follows: + +``` +salted_initialization_hash = pedersen([salt, initialization_hash, deployer as Field, portal_contract_address as Field], GENERATOR__SALTED_INITIALIZATION_HASH) +partial_address = pedersen([contract_class_id, salted_initialization_hash], GENERATOR__CONTRACT_PARTIAL_ADDRESS_V1) +address = pedersen([public_keys_hash, partial_address], GENERATOR__CONTRACT_ADDRESS_V1) +``` + +The `public_keys` array can vary depending on the format of keys used by the address, but it is suggested it includes the master keys defined in the [keys section](./keys.md). + +``` +public_keys_hash = pedersen([ + nullifier_pubkey.x, nullifier_pubkey.y, + tagging_pubkey.x, tagging_pubkey.y, + incoming_view_pubkey.x, incoming_view_pubkey.y, + outgoing_view_pubkey.x, outgoing_view_pubkey.y +], GENERATOR__PUBLIC_KEYS) +``` + +This recommended hash format is compatible with the [encryption precompiles](./precompiles.md#encryption-and-tagging-precompiles) initially defined in the protocol and advertised in the canonical [registry](../private-message-delivery/registry.md) for private message delivery. An address that chooses to use a different format for its keys will not be compatible with apps that rely on the registry for note encryption. Nevertheless, new precompiles introduced in future versions of the protocol could use different public keys formats. + + diff --git a/yellow-paper/docs/addresses-and-keys/diversified-and-stealth.md b/yellow-paper/docs/addresses-and-keys/diversified-and-stealth.md index 3da4713d275..51a274e12b4 100644 --- a/yellow-paper/docs/addresses-and-keys/diversified-and-stealth.md +++ b/yellow-paper/docs/addresses-and-keys/diversified-and-stealth.md @@ -2,14 +2,19 @@ title: Diversified and Stealth Accounts --- -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](./keys.md) describes derivation mechanisms for diversified and stealth public keys. However, the protocol requires users to interact with addresses. ## Computing Addresses -To support diversified and stealth accounts, a user may compute the deterministic address for a given account contract that is deployed using the diversified or stealth public key, so a sender can interact with the resulting address even before the account contract is deployed. +To support diversified and stealth accounts, a user may compute the deterministic address for a given account contract that is deployed using a diversified or stealth public key, so a sender can interact with the resulting so-called "diversified" or "stealth" address even before the account contract is deployed. When the user wants to access the notes that were sent to the diversified or stealth address, they can deploy the contract at their address, and control it privately from their main account. + + ## Account Contract Pseudocode As an example implementation, account contracts for diversified and stealth accounts can be designed to require no private constructor or state, and delegate entrypoint access control to their master address. @@ -32,6 +37,11 @@ contract DiversifiedAccount Given the contract does not require initialization since it has no constructor, it can be used by its owner without being actually deployed, which reduces the setup cost. + + + + + ## 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. diff --git a/yellow-paper/docs/addresses-and-keys/index.md b/yellow-paper/docs/addresses-and-keys/index.md index 89888dacf46..d9c2730d8fd 100644 --- a/yellow-paper/docs/addresses-and-keys/index.md +++ b/yellow-paper/docs/addresses-and-keys/index.md @@ -2,13 +2,19 @@ title: Addresses and Keys --- -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. + + +Aztec has no concept of externally-owned accounts. Every address identifies a smart contract in the network. + +For end users to interact with the network, they'll most-likely need to [deploy](../contract-deployment/index.md) a so-called "account contract". + +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. -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. +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 enshrined 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 [requirements](./keys-requirements.md) section outlines the features that were sought when designing Aztec's addresses and keys. We then specify how [addresses](./address.md) are derived, as well as the default way in which [keys](./keys.md) will be derived. The [precompiles](./precompiles.md) section describes enshrined 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. diff --git a/yellow-paper/docs/addresses-and-keys/keys-requirements.md b/yellow-paper/docs/addresses-and-keys/keys-requirements.md new file mode 100644 index 00000000000..d7fa8ed9a52 --- /dev/null +++ b/yellow-paper/docs/addresses-and-keys/keys-requirements.md @@ -0,0 +1,186 @@ +--- +title: Requirements +description: Requirements which motivated Aztec's design for addresses and keys. +--- + +## Requirements for Keys + +### Scenario + +A common illustration in this document is Bob sending funds to Alice, by: + +- creating a "note" for her; +- committing to the contents of that note (a "note hash"); +- inserting that note hash into a utxo tree; +- encrypting the contents of that note for Alice; +- optionally encrypting the contents of that note for Bob's future reference; +- optionally deriving an additional "tag" (a.k.a. "clue") to accompany the ciphertexts for faster note discovery; +- broadcasting the resulting ciphertext(s) (and tag(s)); +- optionally identifying the tags; +- decrypting the ciphertexts; storing the note; and some time later spending (nullifying) the note. + +> Note: there is nothing to stop an app and wallet from implementing its own key derivation scheme. Nevertheless, we're designing a 'canonical' scheme that most developers and wallets can use. + +### Authorization keys + +Aztec has native account abstraction, so tx authentication is done via an account contract, meaning tx authentication can be implemented however the user sees fit. That is, authorization keys aren't specified at the protocol level. + +A tx authentication secret key is arguably the most important key to keep private, because knowledge of such a key could potentially enable an attacker to impersonate the user and execute a variety of functions on the network. + +**Requirements:** + +- A tx authentication secret key SHOULD NOT enter Aztec software, and SHOULD NOT enter a circuit. + - Reason: this is just best practice. + +### Master & Siloed Keys + +**Requirements:** + +- All keys must be re-derivable from a single `seed` secret. +- Users must have the option of keeping this `seed` offline, e.g. in a hardware wallet, or on a piece of paper. +- All master keys (for a particular user) must be linkable to a single address for that user. +- For each contract, a siloed set of all secret keys MUST be derivable. + - Reason: secret keys must be siloed, so that a malicious app circuit cannot access and emit (as an unencrypted event or as args to a public function) a user's master secret keys or the secret keys of other apps. +- Master _secret_ keys must not be passed into an app circuit, except for precompiles. + - Reason: a malicious app could broadcast these secret keys to the world. +- Siloed secret keys _of other apps_ must not be passed into an app circuit. + - Reason: a malicious app could broadcast these secret keys to the world. +- The PXE must prevent an app from accessing master secret keys. +- The PXE must prevent an app from accessing siloed secret keys that belong to another contract address. + - Note: To achieve this, the PXE simulator will need to check whether the bytecode being executed (that is requesting secret keys) actually exists at the contract address. +- There must be one and only one way to derive all (current\*) master keys, and all siloed keys, for a particular user address. + - For example, a user should not be able to derive multiple different outgoing viewing keys for a single incoming viewing key (note: this was a 'bug' that was fixed between ZCash Sapling and Orchard). + - \*"current", alludes to the possibility that the user might wish to support rotating keys, but only if one and only one set of keys is derivable as "current". +- All app-siloed keys can all be deterministically linked back to the user's address, without leaking important secrets to the app. + +#### Security assumptions + +- The Aztec private execution client (PXE), precompiled contracts (vetted application circuits), and the kernel circuit (a core protocol circuit) can be trusted with master secret keys (_except for_ the tx authorization secret key, whose security assumptions are abstracted-away to wallet designers). + +### Encryption and decryption + +Definitions (from the point of view of a user ("yourself")): + +- Incoming data: Data which has been created by someone else, and sent to yourself. +- Outgoing data: Data which has been sent to somebody else, from you. +- Internal Incoming data: Data which has been created by you, and has been sent to yourself. + - Note: this was an important observation by ZCash. Before this distinction, whenever a 'change' note was being created, it was being broadcast as incoming data, but that allowed a 3rd party who was only meant to have been granted access to view "incoming" data (and not "outgoing" data), was also able to learn that an "outgoing" transaction had taken place (including information about the notes which were spent). The addition of "internal incoming" keys enables a user to keep interactions with themselves private and separate from interactions with others. + +**Requirements:** + +- A user can derive app-siloed incoming internal and outgoing viewing keys. + - Reason: Allows users to share app-siloed keys to trusted 3rd parties such as auditors, scoped by app. + - Incoming viewing keys are not considered for siloed derivation due to the lack of a suitable public key derivation mechanism. +- A user can encrypt a record of any actions, state changes, or messages, to _themselves_, so that they may re-sync their entire history of actions from their `seed`. + +### Nullifier keys + +Derivation of a nullifier is app-specific; a nullifier is just a `field` (siloed by contract address), from the pov of the protocol. + +Many private application devs will choose to inject a secret "nullifier key" into a nullifier. Such a nullifier key would be tied to a user's public identifier (e.g. their address), and that identifier would be tied to the note being nullified (e.g. the note might contain that identifier). This is a common pattern in existing privacy protocols. Injecting a secret "nullifier key" in this way serves to hide what the nullifier is nullifying, and ensures the nullifier can only be derived by one person (assuming the nullifier key isn't leaked). + +> Note: not all nullifiers require injection of a secret _which is tied to a user's identity in some way_. Sometimes an app will need just need a guarantee that some value will be unique, and so will insert it into the nullifier tree. + +**Requirements:** + +- Support use cases where an app requires a secret "nullifier key" (linked to a user identity) to be derivable. + - Reason: it's a very common pattern. + +#### Is a nullifier key _pair_ needed? + +I.e. do we need both a nullifier secret key and a nullifier public key? Zcash sapling had both, but Zcash orchard (an upgrade) replaced the notion of a keypair with a single nullifier key. The [reason](https://zcash.github.io/orchard/design/keys.html) being: + +- _"[The nullifier secret key's (nsk's)] purpose in Sapling was as defense-in-depth, in case RedDSA [(the scheme used for signing txs, using the authentication secret key ask)] was found to have weaknesses; an adversary who could recover ask would not be able to spend funds. In practice it has not been feasible to manage nsk much more securely than a full viewing key [(dk, ak, nk, ovk)], as the computational power required to generate Sapling proofs has made it necessary to perform this step [(deriving nk from nsk)] on the same device that is creating the overall transaction (rather than on a more constrained device like a hardware wallet). We are also more confident in RedDSA now."_ + +A nullifier public key might have the benefit (in Aztec) that a user could (optionally) provide their nullifier key nk to some 3rd party, to enable that 3rd party to see when the user's notes have been nullified for a particular app, without having the ability to nullify those notes. + +- This presumes that within a circuit, the nk (not a public key; still secret!) would be derived from an nsk, and the nk would be injected into the nullifier. +- BUT, of course, it would be BAD if the nk were derivable as a bip32 normal child, because then everyone would be able to derive the nk from the master key, and be able to view whenever a note is nullified! +- The nk would need to ba a hardened key (derivable only from a secret). + +Given that it's acceptable to ZCash Orchard, we accept that a nullifier master secret key may be 'seen' by Aztec software. + +### Auditability + +Some app developers will wish to give users the option of sharing private transaction details with a trusted 3rd party. + +> Note: The block hashes tree will enable a user to prove many things about their historical transaction history, including historical encrypted event logs. This feature will open up exciting audit patterns, where a user will be able to provably respond to questions without necessarily revealing their private data. However, sometimes this might be an inefficient pattern; in particular when a user is asked to prove a negative statement (e.g. "prove that you've never owned a rock NFT"). Proving such negative statements might require the user to execute an enormous recursive function to iterate through the entire tx history of the network, for example: proving that, out of all the encrypted events that the user _can_ decrypt, none of them relate to ownership of a rock NFT. Given this (possibly huge) inefficiency, these key requirements include the more traditional ability to share certain keys with a trusted 3rd party. + +**Requirements:** + +- "Shareable" secret keys. + - A user can optionally share "shareable" secret keys, to enable a 3rd party to decrypt the following data: + - Outgoing data, across all apps + - Outgoing data, siloed for a single app + - Incoming internal data, across all apps + - Incoming internal data, siloed for a single app + - Incoming data, across all apps + - Incoming data, siloed for a single app, is **not** required due to lack of a suitable derivation scheme + - Shareable nullifier key. + - A user can optionally share a "shareable" nullifier key, which would enable a trusted 3rd party to see _when_ a particular note hash has been nullified, but would not divulge the contents of the note, or the circumstances under which the note was nullified (as such info would only be gleanable with the shareable viewing keys). + - Given one (or many) shareable keys, a 3rd part MUST NOT be able to derive any of a user's other secret keys; be they shareable or non-shareable. + - Further, they must not be able to derive any relationships _between_ other keys. +- No impersonation. + - The sharing of any (or all) "shareable" key(s) MUST NOT enable the trusted 3rd party to perform any actions on the network, on behalf of the user. + - The sharing of a "shareable" outgoing viewing secret (and a "shareable" _internal_ incoming viewing key) MUST NOT enable the trusted 3rd party to emit encrypted events that could be perceived as "outgoing data" (or internal incoming data) originating from the user. +- Control over incoming/outgoing data. + - A user can choose to only give incoming data viewing rights to a 3rd party. (Gives rise to incoming viewing keys). + - A user can choose to only give outgoing data viewing rights to a 3rd party. (Gives rise to outgoing viewing keys). + - A user can choose to keep interactions with themselves private and distinct from the viewability of interactions with other parties. (Gives rise to _internal_ incoming viewing keys). + +### Sending funds before deployment + +**Requirements:** + +- A user can generate an address to which funds (and other notes) can be sent, without that user having ever interacted with the network. + - To put it another way: A user can be sent money before they've interacted with the Aztec network (i.e. before they've deployed an account contract). e.g their incoming viewing key can be derived. +- An address (user identifier) can be derived deterministically, before deploying an account contract. + +### Note Discovery + +**Requirements:** + +- A user should be able to discover which notes belong to them, without having to trial-decrypt every single note broadcasted on chain. +- Users should be able to opt-in to using new note discovery mechanisms as they are made available in the protocol. + +#### Tag Hopping + +Given that this is our best-known approach, we include some requirements relating to it: + +**Requirements:** + +- A user Bob can non-interactively generate a sequence of tags for some other user Alice, and non-interactively communicate that sequencer of tags to Alice. +- If a shared secret (that is used for generating a sequence of tags) is leaked, Bob can non-interactively generate and communicate a new sequence of tags to Alice, without requiring Bob nor Alice to rotate their keys. + - Note: if the shared secret is leaked through Bob/Alice accidentally leaking one of their keys, then they might need to actually rotate their keys. + +### Constraining key derivations + +- An app has the ability to constrain the correct encryption and/or note discovery tagging scheme. +- An app can _choose_ whether or not to constrain the correct encryption and/or note discovery tagging scheme. + - Reason: constraining these computations (key derivations, encryption algorithms, tag derivations) will be costly (in terms of constraints), and some apps might not need to constrain it (e.g. zcash does not constrain correct encryption). + +### Rotating keys + +- A user should be able to rotate their set of keys, without having to deploy a new account contract. + - Reason: keys can be compromised, and setting up a new identity is costly, since the user needs to migrate all their assets. Rotating encryption keys allows the user to regain privacy for all subsequent interactions while keeping their identity. + - This requirement causes a security risk when applied to nullifier keys. If a user can rotate their nullifier key, then the nullifier for any of their notes changes, so they can re-spend any note. Rotating nullifier keys requires the nullifier public key, or at least an identifier of it, to be stored as part of the note. Alternatively, this requirement can be removed for nullifier keys, which are not allowed to be rotated. + + + +### Diversified Keys + +- Alice can derive a diversified address; a random-looking address which she can (interactively) provide to Bob, so that Bob may send her funds (and general notes). + - Reason: By having the recipient derive a distinct payment address _per counterparty_, and then interactively provide that address to the sender, means that if two counterparties collude, they won't be able to convince the other that they both interacted with the same recipient. +- Random-looking addresses can be derived from a 'main' address, so that private to public function calls don't reveal the true `msg_sender`. These random-looking addresses can be provably linked back to the 'main' address. + > Note: both diversified and stealth addresses would meet this requirement. +- Distributing many diversified addresses must not increase the amount of time needed to scan the blockchain (they must all share a single set of viewing keys). + +### Stealth Addresses + +Not to be confused with diversified addresses. A diversified address is generated by the recipient, and interactively given to a sender, for the sender to then use. But a stealth address is generated by the _sender_, and non-interactively shared with the recipient. + +Requirement: + +- Random-looking addresses can be derived from a 'main' address, so that private -> public function calls don't reveal the true `msg_sender`. These random-looking addresses can be provably linked back to the 'main' address. + > Note: both diversified and stealth addresses would meet this requirement. +- Unlimited random-looking addresses can be non-interactively derived by a sender for a particular recipient, in such a way that the recipient can use one set of keys to decrypt state changes or change states which are 'owned' by that stealth address. diff --git a/yellow-paper/docs/addresses-and-keys/specification.md b/yellow-paper/docs/addresses-and-keys/keys.md similarity index 57% rename from yellow-paper/docs/addresses-and-keys/specification.md rename to yellow-paper/docs/addresses-and-keys/keys.md index d04e3f2aa76..4e1885aaefd 100644 --- a/yellow-paper/docs/addresses-and-keys/specification.md +++ b/yellow-paper/docs/addresses-and-keys/keys.md @@ -1,8 +1,10 @@ --- -title: Specification -description: Specification of address format in the protocol, default privacy keys format and derivation, and nullifier derivation. +title: Default Keys Specification +description: Specification for default privacy keys format and derivation, and nullifier derivation. --- + + $$ \gdef\sk{\color{red}{sk}} @@ -91,207 +93,8 @@ $$ $$ -## Requirements for Keys - -### Scenario - -A common illustration in this document is Bob sending funds to Alice, by: - -- creating a "note" for her; -- committing to the contents of that note (a "note hash"); -- inserting that note hash into a utxo tree; -- encrypting the contents of that note for Alice; -- optionally encrypting the contents of that note for Bob's future reference; -- optionally deriving an additional "tag" (a.k.a. "clue") to accompany the ciphertexts for faster note discovery; -- broadcasting the resulting ciphertext(s) (and tag(s)); -- optionally identifying the tags; -- decrypting the ciphertexts; storing the note; and some time later spending (nullifying) the note. - -> Note: there is nothing to stop an app and wallet from implementing its own key derivation scheme. Nevertheless, we're designing a 'canonical' scheme that most developers and wallets can use. - -### Authorization keys - -Aztec has native account abstraction, so tx authentication is done via an account contract, meaning tx authentication can be implemented however the user sees fit. That is, authorization keys aren't specified at the protocol level. - -A tx authentication secret key is arguably the most important key to keep private, because knowledge of such a key could potentially enable an attacker to impersonate the user and execute a variety of functions on the network. - -**Requirements:** - -- A tx authentication secret key SHOULD NOT enter Aztec software, and SHOULD NOT enter a circuit. - - Reason: this is just best practice. - -### Master & Siloed Keys - -**Requirements:** - -- All keys must be re-derivable from a single `seed` secret. -- Users must have the option of keeping this `seed` offline, e.g. in a hardware wallet, or on a piece of paper. -- All master keys (for a particular user) must be linkable to a single address for that user. -- For each contract, a siloed set of all secret keys MUST be derivable. - - Reason: secret keys must be siloed, so that a malicious app circuit cannot access and emit (as an unencrypted event or as args to a public function) a user's master secret keys or the secret keys of other apps. -- Master _secret_ keys must not be passed into an app circuit, except for precompiles. - - Reason: a malicious app could broadcast these secret keys to the world. -- Siloed secret keys _of other apps_ must not be passed into an app circuit. - - Reason: a malicious app could broadcast these secret keys to the world. -- The PXE must prevent an app from accessing master secret keys. -- The PXE must prevent an app from accessing siloed secret keys that belong to another contract address. - - Note: To achieve this, the PXE simulator will need to check whether the bytecode being executed (that is requesting secret keys) actually exists at the contract address. -- There must be one and only one way to derive all (current\*) master keys, and all siloed keys, for a particular user address. - - For example, a user should not be able to derive multiple different outgoing viewing keys for a single incoming viewing key (note: this was a 'bug' that was fixed between ZCash Sapling and Orchard). - - \*"current", alludes to the possibility that the user might wish to support rotating keys, but only if one and only one set of keys is derivable as "current". -- All app-siloed keys can all be deterministically linked back to the user's address, without leaking important secrets to the app. - -#### Security assumptions - -- The Aztec private execution client (PXE), precompiled contracts (vetted application circuits), and the kernel circuit (a core protocol circuit) can be trusted with master secret keys (_except for_ the tx authorization secret key, whose security assumptions are abstracted-away to wallet designers). - -### Encryption and decryption - -Definitions (from the point of view of a user ("yourself")): - -- Incoming data: Data which has been created by someone else, and sent to yourself. -- Outgoing data: Data which has been sent to somebody else, from you. -- Internal Incoming data: Data which has been created by you, and has been sent to yourself. - - Note: this was an important observation by ZCash. Before this distinction, whenever a 'change' note was being created, it was being broadcast as incoming data, but that allowed a 3rd party who was only meant to have been granted access to view "incoming" data (and not "outgoing" data), was also able to learn that an "outgoing" transaction had taken place (including information about the notes which were spent). The addition of "internal incoming" keys enables a user to keep interactions with themselves private and separate from interactions with others. - -**Requirements:** - -- A user can derive app-siloed incoming internal and outgoing viewing keys. - - Reason: Allows users to share app-siloed keys to trusted 3rd parties such as auditors, scoped by app. - - Incoming viewing keys are not considered for siloed derivation due to the lack of a suitable public key derivation mechanism. -- A user can encrypt a record of any actions, state changes, or messages, to _themselves_, so that they may re-sync their entire history of actions from their `seed`. - -### Nullifier keys - -Derivation of a nullifier is app-specific; a nullifier is just a `field` (siloed by contract address), from the pov of the protocol. - -Many private application devs will choose to inject a secret "nullifier key" into a nullifier. Such a nullifier key would be tied to a user's public identifier (e.g. their address), and that identifier would be tied to the note being nullified (e.g. the note might contain that identifier). This is a common pattern in existing privacy protocols. Injecting a secret "nullifier key" in this way serves to hide what the nullifier is nullifying, and ensures the nullifier can only be derived by one person (assuming the nullifier key isn't leaked). - -> Note: not all nullifiers require injection of a secret _which is tied to a user's identity in some way_. Sometimes an app will need just need a guarantee that some value will be unique, and so will insert it into the nullifier tree. - -**Requirements:** - -- Support use cases where an app requires a secret "nullifier key" (linked to a user identity) to be derivable. - - Reason: it's a very common pattern. - -#### Is a nullifier key _pair_ needed? - -I.e. do we need both a nullifier secret key and a nullifier public key? Zcash sapling had both, but Zcash orchard (an upgrade) replaced the notion of a keypair with a single nullifier key. The [reason](https://zcash.github.io/orchard/design/keys.html) being: - -- _"[The nullifier secret key's (nsk's)] purpose in Sapling was as defense-in-depth, in case RedDSA [(the scheme used for signing txs, using the authentication secret key ask)] was found to have weaknesses; an adversary who could recover ask would not be able to spend funds. In practice it has not been feasible to manage nsk much more securely than a full viewing key [(dk, ak, nk, ovk)], as the computational power required to generate Sapling proofs has made it necessary to perform this step [(deriving nk from nsk)] on the same device that is creating the overall transaction (rather than on a more constrained device like a hardware wallet). We are also more confident in RedDSA now."_ - -A nullifier public key might have the benefit (in Aztec) that a user could (optionally) provide their nullifier key nk to some 3rd party, to enable that 3rd party to see when the user's notes have been nullified for a particular app, without having the ability to nullify those notes. - -- This presumes that within a circuit, the nk (not a public key; still secret!) would be derived from an nsk, and the nk would be injected into the nullifier. -- BUT, of course, it would be BAD if the nk were derivable as a bip32 normal child, because then everyone would be able to derive the nk from the master key, and be able to view whenever a note is nullified! -- The nk would need to ba a hardened key (derivable only from a secret). - -Given that it's acceptable to ZCash Orchard, we accept that a nullifier master secret key may be 'seen' by Aztec software. - -### Auditability - -Some app developers will wish to give users the option of sharing private transaction details with a trusted 3rd party. - -> Note: The block hashes tree will enable a user to prove many things about their historical transaction history, including historical encrypted event logs. This feature will open up exciting audit patterns, where a user will be able to provably respond to questions without necessarily revealing their private data. However, sometimes this might be an inefficient pattern; in particular when a user is asked to prove a negative statement (e.g. "prove that you've never owned a rock NFT"). Proving such negative statements might require the user to execute an enormous recursive function to iterate through the entire tx history of the network, for example: proving that, out of all the encrypted events that the user _can_ decrypt, none of them relate to ownership of a rock NFT. Given this (possibly huge) inefficiency, these key requirements include the more traditional ability to share certain keys with a trusted 3rd party. - -**Requirements:** - -- "Shareable" secret keys. - - A user can optionally share "shareable" secret keys, to enable a 3rd party to decrypt the following data: - - Outgoing data, across all apps - - Outgoing data, siloed for a single app - - Incoming internal data, across all apps - - Incoming internal data, siloed for a single app - - Incoming data, across all apps - - Incoming data, siloed for a single app, is **not** required due to lack of a suitable derivation scheme - - Shareable nullifier key. - - A user can optionally share a "shareable" nullifier key, which would enable a trusted 3rd party to see _when_ a particular note hash has been nullified, but would not divulge the contents of the note, or the circumstances under which the note was nullified (as such info would only be gleanable with the shareable viewing keys). - - Given one (or many) shareable keys, a 3rd part MUST NOT be able to derive any of a user's other secret keys; be they shareable or non-shareable. - - Further, they must not be able to derive any relationships _between_ other keys. -- No impersonation. - - The sharing of any (or all) "shareable" key(s) MUST NOT enable the trusted 3rd party to perform any actions on the network, on behalf of the user. - - The sharing of a "shareable" outgoing viewing secret (and a "shareable" _internal_ incoming viewing key) MUST NOT enable the trusted 3rd party to emit encrypted events that could be perceived as "outgoing data" (or internal incoming data) originating from the user. -- Control over incoming/outgoing data. - - A user can choose to only give incoming data viewing rights to a 3rd party. (Gives rise to incoming viewing keys). - - A user can choose to only give outgoing data viewing rights to a 3rd party. (Gives rise to outgoing viewing keys). - - A user can choose to keep interactions with themselves private and distinct from the viewability of interactions with other parties. (Gives rise to _internal_ incoming viewing keys). - -### Sending funds before deployment - -**Requirements:** - -- A user can generate an address to which funds (and other notes) can be sent, without that user having ever interacted with the network. - - To put it another way: A user can be sent money before they've interacted with the Aztec network (i.e. before they've deployed an account contract). e.g their incoming viewing key can be derived. -- An address (user identifier) can be derived deterministically, before deploying an account contract. - -### Note Discovery - -**Requirements:** - -- A user should be able to discover which notes belong to them, without having to trial-decrypt every single note broadcasted on chain. -- Users should be able to opt-in to using new note discovery mechanisms as they are made available in the protocol. - -#### Tag Hopping - -Given that this is our best-known approach, we include some requirements relating to it: - -**Requirements:** - -- A user Bob can non-interactively generate a sequence of tags for some other user Alice, and non-interactively communicate that sequencer of tags to Alice. -- If a shared secret (that is used for generating a sequence of tags) is leaked, Bob can non-interactively generate and communicate a new sequence of tags to Alice, without requiring Bob nor Alice to rotate their keys. - - Note: if the shared secret is leaked through Bob/Alice accidentally leaking one of their keys, then they might need to actually rotate their keys. - -### Constraining key derivations - -- An app has the ability to constrain the correct encryption and/or note discovery tagging scheme. -- An app can _choose_ whether or not to constrain the correct encryption and/or note discovery tagging scheme. - - Reason: constraining these computations (key derivations, encryption algorithms, tag derivations) will be costly (in terms of constraints), and some apps might not need to constrain it (e.g. zcash does not constrain correct encryption). - -### Rotating keys - -- A user should be able to rotate their set of keys, without having to deploy a new account contract. - - Reason: keys can be compromised, and setting up a new identity is costly, since the user needs to migrate all their assets. Rotating encryption keys allows the user to regain privacy for all subsequent interactions while keeping their identity. - - This requirement causes a security risk when applied to nullifier keys. If a user can rotate their nullifier key, then the nullifier for any of their notes changes, so they can re-spend any note. Rotating nullifier keys requires the nullifier public key, or at least an identifier of it, to be stored as part of the note. Alternatively, this requirement can be removed for nullifier keys, which are not allowed to be rotated. - - - -### Diversified Keys - -- Alice can derive a diversified address; a random-looking address which she can (interactively) provide to Bob, so that Bob may send her funds (and general notes). - - Reason: By having the recipient derive a distinct payment address _per counterparty_, and then interactively provide that address to the sender, means that if two counterparties collude, they won't be able to convince the other that they both interacted with the same recipient. -- Random-looking addresses can be derived from a 'main' address, so that private to public function calls don't reveal the true `msg_sender`. These random-looking addresses can be provably linked back to the 'main' address. - > Note: both diversified and stealth addresses would meet this requirement. -- Distributing many diversified addresses must not increase the amount of time needed to scan the blockchain (they must all share a single set of viewing keys). - -### Stealth Addresses - -Not to be confused with diversified addresses. A diversified address is generated by the recipient, and interactively given to a sender, for the sender to then use. But a stealth address is generated by the _sender_, and non-interactively shared with the recipient. - -Requirement: - -- Random-looking addresses can be derived from a 'main' address, so that private -> public function calls don't reveal the true `msg_sender`. These random-looking addresses can be provably linked back to the 'main' address. - > Note: both diversified and stealth addresses would meet this requirement. -- Unlimited random-looking addresses can be non-interactively derived by a sender for a particular recipient, in such a way that the recipient can use one set of keys to decrypt state changes or change states which are 'owned' by that stealth address. - -## Notation - -- An upper-case first letter is used for elliptic curve points (all on the Grumpkin curve) (e.g. $\Ivpkm$). -- A lower-case first letter is used for scalars. (TODO: improve this. Some hash outputs might be 256-bit instead of a field element, for example). -- $G$ is a generator point on the Grumpkin curve. -- A "cdot" ("$\cdot$") is used to denote scalar multiplication. -- "$+$" should be clear from context whether it's field or point addition. -- A function 'h()' is a placeholder for some as-yet-undecided hash function or pseudo-random function, the choice of which is tbd. Note: importantly, 'h()' is lazy notation, in that not all instances of h() imply the same hash function should be used. -- The string "?" is a lazy placeholder domain separator. -- A function $encrypt_{enc\_key}^{pub\_key}(plaintext)$ is a placeholder for some as-yet-undecided symmetric encryption function. The $enc\_key$ subscript indicates the encryption key, and the superscript $pub\_key$ is an occasional reminder of the public key of the recipient. -- A function $decrypt_{enc\_key}(ciphertext)$ is the counterpart to the $encrypt()$ placeholder function. -- A subscript $m$ is used on keys to mean "master key". -- A subscript $app$ is used on keys to mean "an app-siloed key, derived from the master key and the app's contract address". -- A subscript $d$ is used on keys to mean "diversified". Although note: a diversifier value of $d = 1$ implies no diversification, as will often be the case in practice. - ## Colour Key -> Haha. Key. Good one. - - $\color{green}{green}$ = Publicly shareable information. - $\color{red}{red}$ = Very secret information. A user MUST NOT share this information. - TODO: perhaps we distinguish between information that must not be shared to prevent theft, and information that must not be shared to preserve privacy? @@ -325,55 +128,7 @@ $\Ovpkm$ | $\ovskm \cdot G$ | outgoing viewing public key | | Only included so t > \*These keys could also be safely passed into the Kernel circuit, but there's no immediately obvious use, so "K" has been omitted, to make design intentions clearer. - - -## Address - -An address is computed as the hash of the following fields: - - -| Field | Type | Description | -|----------|----------|----------| -| `salt` | `Field` | User-generated pseudorandom value for uniqueness. | -| `deployer` | `AztecAddress` | Optional address of the deployer of the contract. | -| `contract_class_id` | `Field` | Identifier of the contract class for this instance. | -| `initialization_hash` | `Field` | Hash of the selector and arguments to the constructor. | -| `portal_contract_address` | `EthereumAddress` | Address of the L1 portal contract, zero if none. | -| `public_keys_hash` | `Field` | Hash of the struct of public keys used for encryption and nullifying by this contract, zero if no public keys. | - -Storing these fields in the address preimage allows any part of the protocol to check them by recomputing the hash and verifying that the address matches. Examples of these checks are: -- Sending an encrypted note to an undeployed account, which requires the sender app to check the recipient's public key given their address. This scenario also requires the recipient to share with the sender their public key and rest of preimage. -- Having the kernel circuit verify that the code executed at a given address matches the one from the class. -- Asserting that the initialization hash matches the function call in the contract constructor. -- Checking the portal contract address when sending a cross-chain message. - -:::warning -We may remove the `portal_contract_address` as a first-class citizen. -::: - -The hashing scheme for the address should then ensure that checks that are more frequent can be done cheaply, and that data shared out of band is kept manageable. We define the hash to be computed as follows: - -``` -salted_initialization_hash = pedersen([salt, initialization_hash, deployer as Field, portal_contract_address as Field], GENERATOR__SALTED_INITIALIZATION_HASH) -partial_address = pedersen([contract_class_id, salted_initialization_hash], GENERATOR__CONTRACT_PARTIAL_ADDRESS_V1) -address = pedersen([public_keys_hash, partial_address], GENERATOR__CONTRACT_ADDRESS_V1) -``` - -The `public_keys` array can vary depending on the format of keys used by the address, but it is suggested it includes the master keys defined above: $\Npkm$, $\Tpkm$, $\Ivpkm$, $\Ovpkm$. A suggested hashing is: -``` -public_keys_hash = pedersen([ - nullifier_pubkey.x, nullifier_pubkey.y, - tagging_pubkey.x, tagging_pubkey.y, - incoming_view_pubkey.x, incoming_view_pubkey.y, - outgoing_view_pubkey.x, outgoing_view_pubkey.y -], GENERATOR__PUBLIC_KEYS) -``` - -This recommended hash format is compatible with the [encryption precompiles](./precompiles.md#encryption-and-tagging-precompiles) initially defined in the protocol and advertised in the canonical [registry](../private-message-delivery/registry.md) for private message delivery. An address that chooses to use a different format for its keys will not be compatible with apps that rely on the registry for note encryption. Nevertheless, new precompiles introduced in future versions of the protocol could use different public keys formats. - - - -## Derive siloed keys +## Deriving siloed keys ### Nullifier keys @@ -464,7 +219,7 @@ This can be done by either: - Generating a very basic sequence of tags $\tagg_{app, i}^{Bob \rightarrow Bob} = h(\ovskapp, i)$ (at the app level) and $\tagg_{m, i}^{Bob \rightarrow Bob} = h(\ovskm, i)$. - Note: In the case of deriving app-specific sequences of tags, Bob might wish to also encrypt the app*address as a ciphertext header (and attach a master tag $\tagg*{m, i}^{Bob \rightarrow Bob}$), to remind himself of the apps that he should derive tags _for_. -## Derive diversified public keys +## Deriving diversified public keys A diversified public key can be derived from Alice's keys, to enhance Alice's transaction privacy. If Alice's counterparties' databases are compromised, it enables Alice to retain privacy from such leakages. Diversified public keys are used for generating diversified addresses. @@ -485,7 +240,7 @@ $\Ivpkmd$ | $\ivskm \cdot \Gd$ | Diversified incoming viewing public key | > Notice: when $\d = 1$, $\Ivpkmd = \Ivpkm$. Often, it will be unncessary to diversify the below data, but we keep $\d$ around for the most generality. -## Derive stealth public keys +## Deriving stealth public keys > All of the key information below is Alice's @@ -530,7 +285,7 @@ $\Pkappdstealth$ | $\Ivpkappdstealth$ | Alias: "Alice's Stealth Public Key" | -## Derive nullifier +## Deriving a nullifier within an app contract Let's assume a developer wants a nullifier of a note to be derived as: diff --git a/yellow-paper/docs/addresses-and-keys/precompiles.md b/yellow-paper/docs/addresses-and-keys/precompiles.md index 1412f9d0abb..093ff92fc4d 100644 --- a/yellow-paper/docs/addresses-and-keys/precompiles.md +++ b/yellow-paper/docs/addresses-and-keys/precompiles.md @@ -1,25 +1,32 @@ --- title: Precompiles -sidebar_position: 2 --- -Precompiled contracts, which borrow their name from Ethereum's, are contracts not deployed by users but defined at the protocol level. These contracts and their classes are assigned well-known low-number addresses and identifiers, and their implementation is subject to change via protocol upgrades. Precompiled contracts in Aztec are implemented as a set of circuits, one for each function they expose, like user-defined private contracts. Precompiles may make use of the local PXE oracle. Note that, unlike user-defined contracts, the address of a precompiled contract instance and the identifier of its class both have no known preimage. + -Rationale for precompiled contracts is to provide a set of vetted primitives for note encryption and tagging that applications can use safely. These primitives are guaranteed to be always satisfiable when called with valid arguments. This allows account contracts to choose their preferred method of encryption and tagging from any primitive in this set, and application contracts to call into them without the risk of calling into a untrusted code, which could potentially halt the execution flow via an unsatisfiable constrain. Furthermore, by exposing these primitives in a reserved set of well-known addresses, applications can be forward-compatible and incorporate new encryption and tagging methods as accounts opt into them. +Precompiled contracts, which borrow their name from Ethereum's, are contracts not deployed by users but defined at the protocol level. These contract [instances](../contract-deployment/instances.md) and their [classes](../contract-deployment/classes.md) are assigned well-known low-number addresses and identifiers, and their implementation is subject to change via protocol upgrades. Precompiled contracts in Aztec are implemented as a set of circuits, one for each function they expose, like user-defined private contracts. Precompiles may make use of the local PXE oracle. + +Note that, unlike user-defined contracts, the address of a precompiled [contract instance](../contract-deployment/instances.md) and the [identifier of its class](../contract-deployment/classes.md#class-identifier) both have no known preimage. + +The rationale for precompiled contracts is to provide a set of vetted primitives for [note encryption](../private-message-delivery/encryption-and-decryption.md) and [tagging](../private-message-delivery/note-discovery.md) that applications can use safely. These primitives are guaranteed to be always-satisfiable when called with valid arguments. This allows account contracts to choose their preferred method of encryption and tagging from any primitive in this set, and application contracts to call into them without the risk of calling into a untrusted code, which could potentially halt the execution flow via an unsatisfiable constraint. Furthermore, by exposing these primitives in a reserved set of well-known addresses, applications can be forward-compatible and incorporate new encryption and tagging methods as accounts opt into them. ## Constants -- `ENCRYPTION_BATCH_SIZES=[4, 16, 32]`: Defines what max batch sizes are supported in precompiled encryption methods. +- `ENCRYPTION_BATCH_SIZES=[4, 8, 16, 32]`: Defines what max [batch sizes](../calls/batched-calls.md) are supported in precompiled encryption methods. - `ENCRYPTION_PRECOMPILE_ADDRESS_RANGE=0x00..0xFFFF`: Defines the range of addresses reserved for precompiles used for encryption and tagging. - `MAX_PLAINTEXT_LENGTH`: Defines the maximum length of a plaintext to encrypt. -- `MAX_CYPHERTEXT_LENGTH`: Defines the maximum length of a returned encrypted cyphertext. -- `MAX_TAGGED_CYPHERTEXT_LENGTH`: Defines the maximum length of a returned encrypted cyphertext prefixed with a note tag. +- `MAX_CIPHERTEXT_LENGTH`: Defines the maximum length of a returned encrypted ciphertext. +- `MAX_TAGGED_CIPHERTEXT_LENGTH`: Defines the maximum length of a returned encrypted ciphertext prefixed with a note tag. + + ## Encryption and tagging precompiles -All precompiles in the address range `ENCRYPTION_PRECOMPILE_ADDRESS_RANGE` are reserved for encryption and tagging. Application contracts can expected to call into these contracts with note plaintext, recipients, and public keys. To facilitate forward compatibility, all unassigned addresses within the range expose the functions below as no-ops, meaning that no actions will be executed when calling into them. +All precompiles in the address range `ENCRYPTION_PRECOMPILE_ADDRESS_RANGE` are reserved for encryption and tagging. Application contracts can expected to call into these contracts with note plaintext(s), recipient address(es), and public key(s). To facilitate forward compatibility, all unassigned addresses within the range expose the functions below as no-ops, meaning that no actions will be executed when calling into them. + + -All functions in these precompiles accept a `PublicKeys` struct which contains the user advertised public keys. The structure of each of the public keys included can change from one encryption method to another, with the exception of the `nullifier_key` which is always restricted to a single field element. For forward compatibility, the precompiles interface accepts a hash of the public keys, which can be expanded within each method via an oracle call. +All functions in these precompiles accept a `PublicKeys` struct which contains the user-advertised public keys. The structure of each of the public keys included can change from one encryption method to another, with the exception of the `nullifier_key` which is always restricted to a single field element. For forward compatibility, the precompiles interface accepts a hash of the public keys, which can be expanded within each method via an oracle call. ``` struct PublicKeys: @@ -48,36 +55,36 @@ validate_keys(public_keys_hash: Field): bool Returns true if the set of public keys represented by `public_keys` is valid for this encryption and tagging mechanism. The precompile must guarantee that any of its methods must succeed if called with a set of public keys deemed as valid. This method returns `false` for undefined precompiles. ``` -encrypt(public_keys_hash: Field, encryption_type: EncryptionType, recipient: AztecAddress, plaintext: Field[MAX_PLAINTEXT_LENGTH]): Field[MAX_CYPHERTEXT_LENGTH] +encrypt(public_keys_hash: Field, encryption_type: EncryptionType, recipient: AztecAddress, plaintext: Field[MAX_PLAINTEXT_LENGTH]): Field[MAX_CIPHERTEXT_LENGTH] ``` -Encrypts the given plaintext using the provided public keys, and returns the encrypted cyphertext. +Encrypts the given plaintext using the provided public keys, and returns the encrypted ciphertext. ``` -encrypt_and_tag(public_keys_hash: Field, encryption_type: EncryptionType, recipient: AztecAddress, plaintext: Field[MAX_PLAINTEXT_LENGTH]): Field[MAX_TAGGED_CYPHERTEXT_LENGTH] +encrypt_and_tag(public_keys_hash: Field, encryption_type: EncryptionType, recipient: AztecAddress, plaintext: Field[MAX_PLAINTEXT_LENGTH]): Field[MAX_TAGGED_CIPHERTEXT_LENGTH] ``` Encrypts and tags the given plaintext using the provided public keys, and returns the encrypted note prefixed with its tag for note discovery. ``` -encrypt_and_broadcast(public_keys_hash: Field, encryption_type: EncryptionType, recipient: AztecAddress, plaintext: Field[MAX_PLAINTEXT_LENGTH]): Field[MAX_TAGGED_CYPHERTEXT_LENGTH] +encrypt_and_broadcast(public_keys_hash: Field, encryption_type: EncryptionType, recipient: AztecAddress, plaintext: Field[MAX_PLAINTEXT_LENGTH]): Field[MAX_TAGGED_CIPHERTEXT_LENGTH] ``` Encrypts and tags the given plaintext using the provided public keys, broadcasts them as an event, and returns the encrypted note prefixed with its tag for note discovery. This functions should be invoked via a [delegate call](../calls/delegate-calls.md), so that the broadcasted event is emitted as if it were from the caller contract. ``` -encrypt([call_context: CallContext, public_keys_hash: Field, encryption_type: EncryptionType, recipient: AztecAddress, plaintext: Field[MAX_PLAINTEXT_LENGTH] ][N]): Field[MAX_CYPHERTEXT_LENGTH][N] -encrypt_and_tag([call_context: CallContext, public_keys_hash: Field, encryption_type: EncryptionType, recipient: AztecAddress, plaintext: Field[MAX_PLAINTEXT_LENGTH] ][N]): Field[MAX_TAGGED_CYPHERTEXT_LENGTH][N] -encrypt_and_broadcast([call_context: CallContext, public_keys_hash: Field, encryption_type: EncryptionType, recipient: AztecAddress, plaintext: Field[MAX_PLAINTEXT_LENGTH] ][N]): Field[MAX_TAGGED_CYPHERTEXT_LENGTH][N] +encrypt([call_context: CallContext, public_keys_hash: Field, encryption_type: EncryptionType, recipient: AztecAddress, plaintext: Field[MAX_PLAINTEXT_LENGTH] ][N]): Field[MAX_CIPHERTEXT_LENGTH][N] +encrypt_and_tag([call_context: CallContext, public_keys_hash: Field, encryption_type: EncryptionType, recipient: AztecAddress, plaintext: Field[MAX_PLAINTEXT_LENGTH] ][N]): Field[MAX_TAGGED_CIPHERTEXT_LENGTH][N] +encrypt_and_broadcast([call_context: CallContext, public_keys_hash: Field, encryption_type: EncryptionType, recipient: AztecAddress, plaintext: Field[MAX_PLAINTEXT_LENGTH] ][N]): Field[MAX_TAGGED_CIPHERTEXT_LENGTH][N] ``` Batched versions of the methods above, which accept an array of `N` tuples of public keys, recipient, and plaintext to encrypt in batch. Precompiles expose instances of this method for multiple values of `N` as defined by `ENCRYPTION_BATCH_SIZES`. Values in the batch with zeroes are skipped. These functions are intended to be used in [batched calls](../calls/batched-calls.md). ``` -decrypt(public_keys_hash: Field, encryption_type: EncryptionType, owner: AztecAddress, cyphertext: Field[MAX_CYPHERTEXT_LENGTH]): Field[MAX_PLAINTEXT_LENGTH] +decrypt(public_keys_hash: Field, encryption_type: EncryptionType, owner: AztecAddress, ciphertext: Field[MAX_CIPHERTEXT_LENGTH]): Field[MAX_PLAINTEXT_LENGTH] ``` -Decrypts the given cyphertext, encrypted for the provided owner. Instead of receiving the decryption key, this method triggers an oracle call to fetch the private decryption key directly from the local PXE and validates it against the supplied public key, in order to avoid leaking a user secret to untrusted application code. This method is intended for provable decryption use cases. +Decrypts the given ciphertext, encrypted for the provided owner. Instead of receiving the decryption key, this method triggers an oracle call to fetch the private decryption key directly from the local PXE and validates it against the supplied public key, in order to avoid leaking a user secret to untrusted application code. This method is intended for provable decryption use cases. ## Encryption strategies @@ -85,7 +92,7 @@ List of encryption strategies implemented by precompiles: ### AES128 -Uses AES128 for encryption, by generating an AES128 symmetric key and an IV from a shared secret derived from the recipient's public key and an ephemeral keypair. Requires that the recipient's keys are points in the Grumpkin curve. The output of the encryption is the concatenation of the encrypted cyphertext and the ephemeral public key. +Uses AES128 for encryption, by generating an AES128 symmetric key and an IV from a shared secret derived from the recipient's public key and an ephemeral keypair. Requires that the recipient's keys are points in the Grumpkin curve. The output of the encryption is the concatenation of the encrypted ciphertext and the ephemeral public key. Pseudocode for the encryption process: @@ -100,11 +107,11 @@ encrypt(plaintext, recipient_public_key): Pseudocode for the decryption process: ``` -decrypt(cyphertext, recipient_private_key): - ephemeral_public_key = cyphertext[0:64] +decrypt(ciphertext, recipient_private_key): + ephemeral_public_key = ciphertext[0:64] shared_secret = ephemeral_public_key * recipient_private_key [aes_key, aes_iv] = sha256(shared_secret ++ [0x01]) - return aes_decrypt(aes_key, aes_iv, cyphertext[64:]) + return aes_decrypt(aes_key, aes_iv, ciphertext[64:]) ``` @@ -142,7 +149,7 @@ When Alice wants to send a message to Bob for the first time: 3. Alice's PXE looks up the shared secret which doesn't exist since this is their first interaction. 4. Alice's PXE generates a random shared secret, and stores it associated Bob along with `counter=1`. 5. The precompile makes a call to the `Handshake` contract that emits the shared secret, encrypted for Bob and optionally Alice. -6. The precompile computes `new_tag = hash(alice, bob, secret, counter)`, emits it as a nullifier, and prepends it to the note cyphertext before broadcasting it. +6. The precompile computes `new_tag = hash(alice, bob, secret, counter)`, emits it as a nullifier, and prepends it to the note ciphertext before broadcasting it. For all subsequent messages: @@ -150,15 +157,16 @@ For all subsequent messages: 2. The precompile makes an oracle call to `getSharedSecret(Alice, Bob)`. 3. Alice's PXE looks up the shared secret and returns it, along with the current value for `counter`, and locally increments `counter`. 4. The precompile computes `previous_tag = hash(alice, bob, secret, counter)`, and performs a merkle membership proof for it in the nullifier tree. This ensures that tags are incremental and cannot be skipped. -5. The precompile computes `new_tag = hash(alice, bob, secret, counter + 1)`, emits it as a nullifier, and prepends it to the note cyphertext before broadcasting it. +5. The precompile computes `new_tag = hash(alice, bob, secret, counter + 1)`, emits it as a nullifier, and prepends it to the note ciphertext before broadcasting it. ## Defined precompiles List of precompiles defined by the protocol: + | Address | Encryption | Note Tagging | Comments | -|---------|------------|--------------|----------| -| 0x01 | Noop | Noop | Used by accounts to explicitly signal that they cannot receive encrypted payloads. Validation method returns `true` only for an empty list of public keys. All other methods return empty. | -| 0x02 | AES128 | Trial decryption | | -| 0x03 | AES128 | Delegated trial decryption | | -| 0x04 | AES128 | Tag hopping | | +| ------- | ---------- | ------------ | -------- | +| 0x01 | Noop | Noop | Used by accounts to explicitly signal that they cannot receive encrypted payloads. Validation method returns `true` only for an empty list of public keys. All other methods return empty. | +| 0x02 | AES128 | Trial decryption | | +| 0x03 | AES128 | Delegated trial decryption | | +| 0x04 | AES128 | Tag hopping | | | diff --git a/yellow-paper/docs/bytecode/index.md b/yellow-paper/docs/bytecode/index.md index 6ebfd61e6e8..887acd29435 100644 --- a/yellow-paper/docs/bytecode/index.md +++ b/yellow-paper/docs/bytecode/index.md @@ -2,17 +2,32 @@ title: Bytecode --- + + 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: -- Private functions: The functions that run on user's machines. At the noir level, they are regular functions. +- Private functions: The functions that run on user's machines. At the noir level, they are regular programs. - Public functions: The functions that are run by sequencers. At the noir level, they are unconstrained functions, that are later proven by the public VM. -- Unconstrained functions: Helper functions that are run on user's machines but are not transacted to, meant to provide users with digested data about the contract's state. At the noir level, they are top level unconstrained functions. +- Unconstrained functions: Helper functions that are run on users' machines but are not constrained. At the noir level, they are top level unconstrained functions. + - Unconstrained functions are often used to fetch and serialize private data, for use as witnesses to a circuit. + - They can also be used to convey how dapps should handle a particular contract's data. When a contract is compiled, private and unconstrained functions are compiled individually. Public functions are compiled together to a single bytecode with an initial dispatch table based on function selectors. Since public functions are run in a VM, we do not incur a huge extra proving cost for the branching that is required to execute different functions. -There are three different (but related) bytecode standards that are used in Aztec, AVM bytecode, Brillig bytecode and ACIR bytecode. + + + +There are three different (but related) bytecode standards that are used in Aztec: AVM bytecode, Brillig bytecode and ACIR bytecode. # AVM Bytecode @@ -20,36 +35,38 @@ The AVM bytecode is the compilation target of the public functions of a contract # Brillig Bytecode -Brillig bytecode is the compilation target of all the unconstrained functions in noir. Any unconstrained function used by a private function is compiled to Brillig bytecode. Also, contract's top level unconstrained functions are entirely compiled to Brillig bytecode. +Brillig bytecode is the compilation target of all the unconstrained functions in noir. Any unconstrained function used by a private function is compiled to Brillig bytecode. Also, contracts' top level unconstrained functions are entirely compiled to Brillig bytecode. -Brillig bytecode will be a thin superset of AVM bytecode that allows for the use of oracles. Oracles allow nondeterminism during the execution of a given function, allowing the simulator entity to choose the value of a given oracle during the simulation process. Oracles are heavily used by aztec.nr to fetch data during simulation of private and unconstrained functions, such as fetching notes. They are also used to notify the simulator about events of the execution, such as a nullified note so it's not offered again in the simulation. Similarly to AVM bytecode, Brillig bytecode allows control flow. +Brillig bytecode will be a thin superset of AVM bytecode that allows for the use of oracles. Oracles allow nondeterminism during the execution of a given function, allowing the simulator entity to choose the value that an oracle will return during the simulation process. Oracles are heavily used by aztec.nr to fetch data during simulation of private and unconstrained functions, such as fetching notes. They are also used to notify the simulator about events arising during execution, such as a nullified note so that it's not offered again during the simulation. Similarly to AVM bytecode, Brillig bytecode allows control flow. The current implementation of Brillig can be found [in the noir repository](https://github.com/noir-lang/noir/blob/master/acvm-repo/brillig/src/opcodes.rs#L60). It'll change when the specification of AVM bytecode is finished to become a superset of it. # ACIR Bytecode -ACIR bytecode is the compilation target of all regular noir functions, including contract private functions. ACIR expresses arithmetic circuits and thus has no control flow. Control flow in regular functions is either unrolled (for loops) or flattened (by inlining and adding predicates), resulting in a single function with no control flow to be transformed to ACIR. +ACIR bytecode is the compilation target of all regular noir programs, including contract private functions. ACIR expresses arithmetic circuits and thus has no control flow. Control flow in regular functions is either unrolled (for loops) or flattened (by inlining and adding predicates), resulting in a single function with no control flow to be transformed to ACIR. The types of opcodes that can appear in ACIR are: - Arithmetic: They can express any degree-2 multivariate relation between witness indices. They are the most common opcodes in ACIR. - BlackBoxFuncCall: They assign the witnesses of the parameters and the witnesses of the return values of black box functions. Black box functions are commonly used operations that are treated as a black box, meaning that the underlying backend chooses how to prove them efficiently. -- Brillig: They assign the witnesses of the parameters and the witnesses of the return values of brillig functions. When an unconstrained function is called from a regular function, the bytecode for the called function gets embedded in a Brillig opcode. The simulator entity is the one responsible for executing the brillig bytecode. The results of the execution of the function are assigned to the witnesses of the return values and they should be constrained to be correct by the ACIR bytecode. +- Brillig: They assign the witnesses of the parameters and the witnesses of the return values of brillig functions. When an unconstrained function is called from a regular function, the bytecode for the called function gets embedded in a Brillig opcode . The simulator entity is the one responsible for executing the brillig bytecode. The results of the execution of the function are assigned to the witnesses of the return values and they should be constrained to be correct by the ACIR bytecode. - MemoryOp: They handle memory operations. When accessing arrays with indices unknown at compile time, the compiler cannot know which witness index is being read. The memory abstraction allows noir to read and write to dynamic positions in arrays in an efficient manner, offloading the responsibility of proving the correct access to the underlying backend. # Usage of the bytecode ### Compiling a contract + + When a contract is compiled, an artifact will be generated containing: - The private functions compiled to ACIR bytecode. The verification key of the private functions can be generated from the ACIR bytecode. - The unconstrained functions compiled to Brillig bytecode. - A public bytecode blob containing the bytecode of all the public functions compiled to AVM bytecode. -The public bytecode needs to be published to a data availability solution, since the sequencers need to have the data available to run the public functions. Also, it needs to use an encoding that is friendly to the public VM, such as the one specified in the [AVM section](../public-vm/bytecode-validation-circuit). +The public bytecode needs to be published to a data availability solution, since the sequencers need to have the data available to run the public functions. Also, it needs to use an encoding that is friendly to the public VM, such as the one specified in the [AVM section](../public-vm/bytecode-validation-circuit). -The bytecode of private and unconstrained functions doesn't need to be published, instead, users that desire to use a given contract can add the artifact to their PXE before interacting with it. Publishing it is convenient, but not required. However, the verification key of the private functions is tracked by the protocol, so the user can prove to the protocol that he executed the function correctly. +The bytecode of private and unconstrained functions doesn't need to be published, instead, users that desire to use a given contract can add the artifact to their PXE before interacting with it. Publishing it is convenient, but not required . However, the verification key of a private function is hashed into the function's leaf of the contract's function tree, so the user can prove to the protocol that he executed the function correctly. This implies that the encoding of private and unconstrained functions does not need to be friendly to circuits, since the only thing the protocol tracks is the verification key, allowing to use compression techniques. diff --git a/yellow-paper/docs/calls/batched-calls.md b/yellow-paper/docs/calls/batched-calls.md index 001f6819cd5..3da63823802 100644 --- a/yellow-paper/docs/calls/batched-calls.md +++ b/yellow-paper/docs/calls/batched-calls.md @@ -1,10 +1,18 @@ # Batched calls +:::warning +The low-level specifics of how batched calls will work is still being discussed. +::: + 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. -Batched calls are implemented by pushing a `PrivateCallStackItem` with the flag `is_execution_request` into a `private_batched_queue` in the execution context, and require an oracle call to `batchPrivateFunctionCall` with the same arguments as other oracle function calls. + + +Batched calls are implemented by pushing a [`PrivateCallStackItem`](../circuits/private-kernel-initial.mdx#privatecallstackitem) with the flag `is_execution_request` into a `private_batched_queue` in the execution context, and require an oracle call to a `batchPrivateFunctionCall` function with the same argument types as for other oracle function calls. + +Batched calls are processed by the private kernel circuit. On each kernel circuit iteration, if the private call stack is not empty, the kernel circuit pops and processes the topmost entry. Otherwise, if the batched queue is not empty, the kernel pops the first item, collects and deletes all other items with the same target, and calls into the target. Note that this allows batched calls to trigger further synchronous calls. -Batched calls are processed by the private kernel circuit. On each kernel circuit iteration, if the private call stack is not empty, the kernel circuit pops and processes the topmost entry. Otherwise, if the batched queue is not empty, the kernel pops the first item, collects and deletes all other items with the same target, and calls into the target. Note that this allows batched calls to trigger synchronous calls. + 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. @@ -24,4 +32,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 that are to be encrypted and tagged with the same mechanism using a single call. Batched calls can also be used for other common functions which do not require synchronous execution and which are likely to be invoked multiple times. diff --git a/yellow-paper/docs/calls/delegate-calls.md b/yellow-paper/docs/calls/delegate-calls.md index ccd0b7011a2..a9edd81b23e 100644 --- a/yellow-paper/docs/calls/delegate-calls.md +++ b/yellow-paper/docs/calls/delegate-calls.md @@ -1,7 +1,17 @@ # 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). + + +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 in 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). At the protocol level, a delegate call is identified by a `is_delegate_call` flag in the `CircuitPublicInputs` of the `CallStackItem`. The `contract_address` field is reinterpreted as a contract class instead. When executing a delegate call, the kernel preserves the values of the `CallContext` `msgSender` and `storageContractAddress`. + + At the contract level, a caller can initiate a delegate call via a `delegateCallPrivateFunction` or `delegateCallPublicFunction` oracle call. The caller is responsible for asserting that the returned `CallStackItem` has the `is_delegate_call` flag correctly set. diff --git a/yellow-paper/docs/calls/enqueued-calls.md b/yellow-paper/docs/calls/enqueued-calls.md index a654e021dfd..8b7cd09fe3d 100644 --- a/yellow-paper/docs/calls/enqueued-calls.md +++ b/yellow-paper/docs/calls/enqueued-calls.md @@ -1,7 +1,9 @@ # 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. +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 -- the former are run locally by the user in 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. -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. +The process is analogous to [synchronous calls](./sync-calls.md), but relies on an `enqueuePublicFunctionCall` oracle call that accepts the same arguments. The object returned by the oracle 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. + +Once the transaction is received by the sequencer, the public kernel circuit can begin processing the enqueued public function calls from the transaction's public call stack, pushing new recursive calls to the stack as needed, and popping-off one call stack item at a time, until the public call stack is empty, as described in the [synchronous calls](./sync-calls.md) section. diff --git a/yellow-paper/docs/calls/index.md b/yellow-paper/docs/calls/index.md index 5122c700f29..b19bfcbc5b3 100644 --- a/yellow-paper/docs/calls/index.md +++ b/yellow-paper/docs/calls/index.md @@ -2,9 +2,22 @@ title: Calls --- + + + + # Calls -Functions in the Aztec Network can call other functions. These calls are [synchronous](./sync-calls.md) when they they occur within private functions or within public functions, but are [enqueued](./enqueued-calls.md) when done from a private to a public function, and optionally [batched](./batched-calls.md). The protocol also supports alternate call methods, such as [static](./static-calls.md), [delegate](./delegate-calls.md), and [unconstrained](./unconstrained-calls.md) calls. +Functions in the Aztec Network can call other functions. There are several types of call: + +- [Synchronous calls](./sync-calls.md): when a private function calls another private function; or when a public function calls another public function. +- [Enqueued calls](./enqueued-calls.md): when a private function calls a public function. +- [Batched calls](./batched-calls.md): when multiple calls to the same function are enqueued and processed as a single call on a concatenation of the arguments. + +The protocol also supports alternative call methods, such as [static](./static-calls.md), [delegate](./delegate-calls.md), and [unconstrained](./unconstrained-calls.md) calls. In addition to function calls, the protocol allows for communication via message-passing back-and-forth between L1 and L2, as well as from public to private functions. diff --git a/yellow-paper/docs/calls/public-private-messaging.md b/yellow-paper/docs/calls/public-private-messaging.md index ddcff5a7b0e..b75cc6f622d 100644 --- a/yellow-paper/docs/calls/public-private-messaging.md +++ b/yellow-paper/docs/calls/public-private-messaging.md @@ -1,71 +1,83 @@ # Inter-Layer Calls + + ## Public-Private messaging -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](../state/index.md). 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, so that the user can ensure privacy of their data. Public functions are executed by the sequencer, who is the only party with an up-to-date view of the latest public state. It's natural, then, that private functions be executed first, and public functions be executed after the user has submitted a [transaction object](../transactions/tx-object.md) (which contains proof of private execution) to the network. Since a user doesn't have an up-to-date view of the latest state, private functions are always executed on some historical snapshot of the network's state. + +Given this natural flow from private-land to public-land, private functions can enqueue calls to public functions. But the opposite direction is not true. We'll see [below](#public-to-private-messaging) that public functions cannot "call" private functions, but rather they must pass messages. -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$. +Since private functions execute first, they cannot 'wait' on the results of any of their calls to public functions. -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. +By way of example, suppose a function makes a call to a public function, and then to a private function. The public function will not be executed immediately, but will instead be enqueued for the sequencer to execute later. -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. +```mermaid +graph LR + A[Private Function 1] --> |1st call| B(Public Function 1) + A --> |2nd call| C[Private Function 2] + C --> |return values| A + A --> |3rd call| D(Public Function 2) + A --> |4th call| E[Private Function 3] + E --> |return values| A +``` -Tx call order be: +The order of execution will actually be: ```mermaid -graph TD - A[Private Function 1] -->|Calls| B(Public Function 1) - A -----> |Followed by| C[Private Function 2] +graph LR + A[Private Function 1] --> C[Private Function 2] + C --> |return values| A + A[Private Function 1] --> E[Private Function 3] + E --> |return values| A + A -----> |Enqueued| B(Public Function 1) + A -----> |Enqueued| D(Public Function 2) ``` -But Tx execution order will be +And the order of proving will actually be: ```mermaid -graph TD - A[Private Function 1] -->|Calls| B(Private Function 2) - A -----> |Followed by| C[Public Function 1] +flowchart LR + A[Private Function 1] --> C[Private Function 2] --> E[Private Function 3] ----> B(Public Function 1) --> D(Public Function 2) ``` ## 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. +1. The arguments to the public function are hashed into an `args_hash`. +1. A `public_call_stack_item` is created, which includes the public function's `function_selector` , `contract_address`, and `args_hash`. +1. A hash of the `public_call_stack_item` gets enqueued into a separate [`public_call_stack`](../circuits/private-function.md#public-inputs) and passed as inputs to the private kernel. +1. The private kernel pushes these hashes onto its own the [`public_inputs`](../circuits/private-kernel-initial.mdx#public-inputs), which the sequencer can see. +1. The PXE creates a [`transaction_object`](../transactions/tx-object.md) which includes the kernel's `public_inputs`. +1. The PXE sends the `transaction_object` to the sequencer. +1. Sequencer then unpacks the `public_call_stack_item` and executes each of the functions. +1. The Public VM executes the enqueued public calls, and 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 -TODO: Haven't finalized what msg.sender will be -::: +The sequencer only sees the data in the [`transaction_object`](../transactions/tx-object.md), which shouldn't expose any private information. There are some [practical caveats](http://docs.aztec.network). -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. +When making a private-to-public call, the `msg_sender` will become public. If this is the actual user, then it leaks privacy. If `msg_sender` is some application's 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. +An out-of-protocol option to randomizing `msg.sender` (as a user) would be to deploy a [diversified account contract](../addresses-and-keys/diversified-and-stealth.md) and route transactions through this contract. Application developers might also be able to do something similar, to randomize the `msg.sender` of their app contract's address. ### 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. +If the private part of a 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. A fee can still be charged by the sequencer for their compute effort. ## 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. +Since public functions execute after private functions, it isn't possible for a public function 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 land. A public function can add messages to the [Note Hash Tree](../state/note-hash-tree.md) to save messages from a public function call, that can later be consumed by a private function. Note: such a message can only be consumed by a _later_ transaction. In practice this means that unless you are the sequencer (or have an out of protocol agreement with the sequencer) it cannot be consumed 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 its 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). diff --git a/yellow-paper/docs/calls/static-calls.md b/yellow-paper/docs/calls/static-calls.md index 2462d95639a..61756cfb413 100644 --- a/yellow-paper/docs/calls/static-calls.md +++ b/yellow-paper/docs/calls/static-calls.md @@ -4,6 +4,15 @@ In particular, the following fields of the returned `CallStackItem` must be zero or empty in a static call: + + + - `new_commitments` - `new_nullifiers` - `nullified_commitments` diff --git a/yellow-paper/docs/calls/sync-calls.md b/yellow-paper/docs/calls/sync-calls.md index 8b3b8941c6d..b760716eb92 100644 --- a/yellow-paper/docs/calls/sync-calls.md +++ b/yellow-paper/docs/calls/sync-calls.md @@ -1,16 +1,20 @@ # Synchronous calls + + Calls from a private function to another private function, as well as calls from a public function to another public function, are _synchronous_. When a synchronous function call is found during execution, execution jumps to the target of the call, and returns to the caller with a return value from the function called. This allows easy composability across contracts. -At the protocol level, each call is represented as a `CallStackItem`, which includes the contract address and function being called, as well as the public inputs `PrivateCircuitPublicInputs` or `PublicCircuitPublicInputs` that are outputted by the execution of the called function. These public inputs include information on the call context, the side effects of the execution, and the block header. +At the protocol level, each call is represented as a [`CallStackItem`](../circuits/private-kernel-initial.mdx#privatecallstackitem), which includes the contract address and function being called, as well as the public inputs [`PrivateCircuitPublicInputs`](../circuits/private-function.md#public-inputs) or `PublicCircuitPublicInputs` that are outputted by the execution of the called function. These public inputs include information on the call context, the side effects of the execution, and the block header. At the contract level, a call is executed via an oracle call `callPrivateFunction` or `callPublicFunction`, both of which accept the contract address to call, the function selector, and a hash of the arguments. The oracle call prompts the executor to pause the current frame, jump to the target of the call, and return its result. The result is a `CallStackItem` that represents the nested execution. -The caller is responsible for asserting that the function and arguments in the returned `CallStackItem` match the requested ones, otherwise a malicious oracle could return a `CallStackItem` for a different execution. The caller must also push the hash of the returned `CallStackItem` into the private or public call stack of the current execution context, which is returned as part of the `CircuitPublicInputs` output. The end result is a top-level entrypoint `CallStackItem`, with a stack of nested call stack items to process. +The calling function is responsible for asserting that the function and arguments in the returned `CallStackItem` match the requested ones, otherwise a malicious oracle could return a `CallStackItem` for a different execution. The calling function must also push the hash of the returned `CallStackItem` into the private or public call stack of the current execution context, which is returned as part of the circuit's [PublicInputs](../circuits/private-function.md#public-inputs) output. The end result is a top-level entrypoint `CallStackItem`, which itself contains (nested within) a stack of call stack items to process. + +The kernel circuit is then responsible for iteratively processing each `CallStackItem`, pushing new items into the stack as it encounters nested calls, and popping one item off the stack with each kernel iteration until the stack is empty. The private kernel circuit processes private function calls locally in the PXE, whereas the public kernel circuit processes public function calls on the sequencer's machine. -The kernel circuit is then responsible for iteratively processing each `CallStackItem`, pushing new items into the stack as it encounters nested calls, until the stack is empty. The private kernel circuit processes private function calls locally in the PXE, whereas the public kernel circuit processes public function calls on the sequencer. +The private kernel circuit iterations begin with the entrypoint execution, empty output and proof. The public kernel circuit starts with the public call stack in the transaction object , and builds on top of the output and proof of the private kernel circuit. -The private kernel circuit iterations begin with the entrypoint execution, empty output and proof. The public kernel circuit starts with the public call stack in the transaction object, and builds on top of the output and proof of the private kernel circuit. + ``` let call_stack, kernel_public_inputs, proof @@ -30,3 +34,5 @@ while call_stack is not empty: ``` The kernel circuit asserts that nested functions and their side effects are processed in order, and that the hash of each nested execution matches the corresponding hash outputted in the call stack by each `CircuitPublicInputs`. + +For more information about how the private kernel circuit works, see [here](../circuits/private-kernel-initial.mdx). diff --git a/yellow-paper/docs/calls/unconstrained-calls.md b/yellow-paper/docs/calls/unconstrained-calls.md index 9a90061cc4a..9fe11f46600 100644 --- a/yellow-paper/docs/calls/unconstrained-calls.md +++ b/yellow-paper/docs/calls/unconstrained-calls.md @@ -1,11 +1,15 @@ # Unconstrained calls - + + Private function calls can be executed as _unconstrained_. Unconstrained function calls execute the code at the target and return the result, but their execution is not constrained. It is responsibility of the caller to constrain the result, if needed. Unconstrained calls are a generalization of oracle function calls, where the call is not to a PXE function but to another contract. Side effects from unconstrained calls are ignored. Note that all calls executed from an unconstrained call frame will be unconstrained as well. Unconstrained calls are executed via a `unconstrainedCallPrivateFunction` oracle call, which accepts the same arguments as a regular `callPrivateFunction`, and return the result from the function call. Unconstrained calls are not pushed into the `private_call_stack` and do not incur in an additional kernel iteration. -Rationale for unconstrained calls is to allows apps to consume results from functions that do not need to be provable. An example use case for unconstrained calls is unconstrained encryption and note tagging, which can be used when the sender is incentivized to ensure the recipient receives the data sent. +THe rationale for unconstrained calls is to allows apps to consume results from functions that do not need to be provable. An example use case for unconstrained calls is unconstrained encryption and note tagging, which can be used in applications where constraining such encryption computations isn't necessary, e.g. if the sender is incentivized to ensure the recipient receives the correct data. Another motivation for unconstrained calls is for retrieving or computing data where the end result can be more efficiently constrained by the caller. diff --git a/yellow-paper/docs/circuits/high-level-topology.md b/yellow-paper/docs/circuits/high-level-topology.md index dd6263760f2..a25e9b5007d 100644 --- a/yellow-paper/docs/circuits/high-level-topology.md +++ b/yellow-paper/docs/circuits/high-level-topology.md @@ -4,10 +4,17 @@ A transaction begins with a call to a private function, which may invoke nested calls to other private and public functions. The entire set of private function calls is executed in a secure environment, and their proofs are validated and aggregated by private kernel circuits. Meanwhile, any public function calls triggered from private functions will be enqueued. The proofs for these calls, along with those from the nested public function calls, are generated and processed through public kernel circuits in any entity possessing the correct contexts. -Once all functions in a transaction are executed, the accumulated data is outputted from a tail circuit. These values are then inserted or updated to the trees within the base rollup circuit. The merge rollup circuit facilitates the merging of two rollup proofs. Repeating this merging process enables the inclusion of more transactions in a block. Finally, the root rollup circuit produces the final proof, which is subsequently submitted and validated onchain. +Once all functions in a transaction are executed, the accumulated data is outputted from a tail circuit. These values are then inserted or updated to the [state trees](../state/index.md) within the base rollup circuit. The merge rollup circuit facilitates the merging of two rollup proofs. Repeating this merging process enables the inclusion of more transactions in a block. Finally, the root rollup circuit produces the final proof, which is subsequently submitted and validated onchain. To illustrate, consider a transaction involving the following functions, where circles depict private functions, and squares denote public functions: +:::info +A note for Aztec protocol developers: In this yellow paper, the order in which the kernel circuit processes calls is different from previous literature, and is different from the current implementation (as at January 2024). +::: + + + ```mermaid flowchart LR f0([f0]) --> f1([f1]) @@ -123,8 +130,8 @@ flowchart TB A few things to note: -- A transaction always starts with an [initial private kernel circuit](./private-kernel-initial.md). -- An [inner private kernel circuit](./private-kernel-inner.md) won't be required if there is only one private function in a transaction. +- A transaction always starts with an [initial private kernel circuit](./private-kernel-initial.mdx). +- An [inner private kernel circuit](./private-kernel-inner.mdx) won't be required if there is only one private function in a transaction. - A [reset private kernel circuit](./private-kernel-reset.md) can be executed between two private kernel circuits to "reset" transient data. The reset process can be repeated as needed. - Public functions are "enqueued" when invoked from a private function. Public kernel circuits will be executed after the completion of all private kernel iterations. - A [base rollup circuit](../rollup-circuits/base-rollup.md) can accept either a [tail public kernel circuit](./public-kernel-tail.md), or a [tail private kernel circuit](./private-kernel-tail.md) in cases where no public functions are present in the transaction. diff --git a/yellow-paper/docs/circuits/private-function.md b/yellow-paper/docs/circuits/private-function.md index 8418056b83a..7f843a23d21 100644 --- a/yellow-paper/docs/circuits/private-function.md +++ b/yellow-paper/docs/circuits/private-function.md @@ -2,9 +2,9 @@ ## Requirements -Private function circuits represent smart contract functions that modify the Aztec private state trees. They serve as untrusted, third-party code that is executed as part of evaluating an Aztec transaction. +Private function circuits represent smart contract functions that can: privately read and modify leaves of the note hash tree and nullifier tree; perform computations on private data; and can be executed without revealing which function or contract has been executed. -The logic of each private function circuit is tailored to the needs of a particular application or scenario, yet its public inputs must adhere to a specific format. This circuit should be designed to handle private data processing while generating public inputs that safeguard the application and account's intentions without compromising sensitive information. +The logic of each private function circuit is tailored to the needs of a particular application or scenario, but the public inputs of every private function circuit _must_ adhere to a specific format. This specific format (often referred to as the "public inputs ABI for private functions") ensures that the [private kernel circuits](./private-kernel-initial.mdx) can correctly interpret the actions of every private function circuit. ## Private Inputs @@ -12,105 +12,137 @@ The private inputs of a private function circuit are customizable. ## Public Inputs -The public inputs of a private function circuit will be incorporated into the private inputs of a private kernel circuit. Private kernel circuits leverage these public inputs, coupled with proof data and verification key from a private function circuit, to prove the correct execution of a private function. - -The following format defines the ABI that is used by the private kernel circuit when processing private function public inputs: - -| Field | Type | Description | -| ----------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------- | -| _call_context_ | _[CallContext](#callcontext)_ | Context of the call corresponding to this function execution. | -| _args_hash_ | _field_ | Hash of the function arguments. | -| _return_values_ | [_field_; _C_] | Return values of this function call. | -| _read_requests_ | [_[ReadRequest](#readrequest)_; _C_] | Requests to read notes in the note hash tree. | -| _nullifier_key_validation_requests_ | [_[NullifierKeyValidationRequest](#nullifierkeyvalidationrequest)_; _C_] | Requests to validate nullifier keys used in this function call. | -| _note_hashes_ | [_[NoteHash](#notehash)_; _C_] | New note hashes created in this function call. | -| _nullifiers_ | [_[Nullifier](#nullifier)_; _C_] | New nullifiers created in this function call. | -| _l2_to_l1_messages_ | [_field_; _C_] | New L2 to L1 messages created in this function call. | -| _unencrypted_log_hashes_ | [_[UnencryptedLogHash](#unencryptedloghash)_; _C_] | Hashes of the unencrypted logs emitted in this function call. | -| _encrypted_log_hashes_ | [_[EncryptedLogHash](#encryptedloghash)_; _C_] | Hashes of the encrypted logs emitted in this function call. | -| _encrypted_note_preimage_hashes_ | [_[EncryptedNotePreimageHash](#encryptednotepreimagehash)_; _C_] | Hashes of the encrypted note preimages emitted in this function call. | -| _private_call_stack_item_hashes_ | [_field_; _C_] | Hashes of the private function calls initiated by this function. | -| _public_call_stack_item_hashes_ | [_field_; _C_] | Hashes of the public function calls initiated by this function. | -| _header_ | _[Header](#header)_ | Header of a block which was used when assembling the tx. | -| _chain_id_ | _field_ | Chain ID of the transaction. | -| _version_ | _field_ | Version of the transaction. | - -> The above **C**s represent constants defined by the protocol. Each **C** might have a different value from the others. + + + + +The public inputs of _every_ private function _must_ adhere to the following ABI: + +| Field | Type | Description | +| ----------------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- | +| `call_context` | [`CallContext`](#callcontext) | Context of the call corresponding to this function execution. | +| `args_hash` | `field` | Hash of the function arguments. | +| `return_values` | `[field; C]` | Return values of this function call. | +| `read_requests` | [`[ReadRequest; C]`](#readrequest) | Requests to read notes in the note hash tree. | +| `nullifier_key_validation_requests` | [`[NullifierKeyValidationRequest]; C]`](#nullifierkeyvalidationrequest) | Requests to validate nullifier keys used in this function call. | +| `note_hashes` | [`[NoteHash; C]`](#notehash) | New note hashes created in this function call. | +| `nullifiers` | [`[Nullifier; C]`](#nullifier) | New nullifiers created in this function call. | +| `l2_to_l1_messages` | `[field; C]` | New L2 to L1 messages created in this function call. | +| `unencrypted_log_hashes` | [`[UnencryptedLogHash; C]`](#unencryptedloghash) | Hashes of the unencrypted logs emitted in this function call. | +| `encrypted_log_hashes` | [`[EncryptedLogHash; C]`](#encryptedloghash) | Hashes of the encrypted logs emitted in this function call. | +| `encrypted_note_preimage_hashes` | [`[EncryptedNotePreimageHash]; C]`](#encryptednotepreimagehash) | Hashes of the encrypted note preimages emitted in this function call. | +| `private_call_stack_item_hashes` | `[field; C]` | Hashes of the private function calls initiated by this function. | +| `public_call_stack_item_hashes` | `[field; C]` | Hashes of the public function calls initiated by this function. | +| `block_header` | [`BlockHeader`](#blockheader) | Information about the trees used for the transaction. | +| `chain_id` | `field` | Chain ID of the transaction. | +| `version` | `field` | Version of the transaction. | + +After generating a proof for a private function circuit, that proof (and associated public inputs) will be passed-into a private kernel circuit as private inputs. Private kernel circuits use the private function's proof, public inputs, and verification key, to verify the correct execution of the private function. Private kernel circuits then perform a number of checks and computations on the private function's public inputs. + +> The above `C`s represent constants defined by the protocol. Each `C` might have a different value from the others. + + ## Types -#### _CallContext_ +### `CallContext` -| Field | Type | Description | -| -------------------------- | -------------- | ----------------------------------------------------------------------- | -| _msg_sender_ | _AztecAddress_ | Address of the caller contract. | -| _storage_contract_address_ | _AztecAddress_ | Address of the contract against which all state changes will be stored. | -| _portal_contract_address_ | _AztecAddress_ | Address of the portal contract to the storage contract. | -| _is_delegate_call_ | _bool_ | A flag indicating whether the call is a delegate call. | -| _is_static_call_ | _bool_ | A flag indicating whether the call is a static call. | +| Field | Type | Description | +| -------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `msg_sender` | `AztecAddress` | Address of the caller contract. | +| `storage_contract_address` | `AztecAddress` | Address of the contract against which all state changes will be stored. (It is not called `contract_address`, because in the context of delegate calls, that would be an ambiguous name.) | +| `portal_contract_address` | `AztecAddress` | Address of the portal contract to the storage contract. | +| `is_delegate_call` | `bool` | A flag indicating whether the call is a [delegate call](../calls/delegate-calls.md). | +| `is_static_call` | `bool` | A flag indicating whether the call is a [static call](../calls/static-calls.md). | -#### _ReadRequest_ +### `ReadRequest` | Field | Type | Description | | ----------- | ------- | -------------------------------------- | -| _note_hash_ | _field_ | Hash of the note to be read. | -| _counter_ | _field_ | Counter at which the request was made. | +| `note_hash` | `field` | Hash of the note to be read. | +| `counter` | `field` | Counter at which the request was made. | -#### _NullifierKeyValidationRequest_ +### `NullifierKeyValidationRequest` + + | Field | Type | Description | | ------------ | ------- | -------------------------------------------------------------------- | -| _public_key_ | _field_ | Nullifier public key of an account. | -| _secret_key_ | _field_ | Nullifier secret key of an account siloed with the contract address. | +| `public_key` | `field` | Nullifier public key of an account. | +| `secret_key` | `field` | Nullifier secret key of an account siloed with the contract address. | -#### _NoteHash_ +### `NoteHash` | Field | Type | Description | | --------- | ------- | ------------------------------------------- | -| _value_ | _field_ | Hash of the note. | -| _counter_ | _field_ | Counter at which the note hash was created. | +| `value` | `field` | Hash of the note. | +| `counter` | `field` | Counter at which the note hash was created. | -#### _Nullifier_ +### `Nullifier` | Field | Type | Description | | ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------ | -| _value_ | _field_ | Value of the nullifier. | -| _counter_ | _field_ | Counter at which the nullifier was created. | -| _note_hash_counter_ | _field_ | Counter of the transient note the nullifier is created for. 0 if the nullifier does not associate with a transient note. | +| `value` | `field` | Value of the nullifier. | +| `counter` | `field` | Counter at which the nullifier was created. | +| `note_hash_counter` | `field` | Counter of the transient note the nullifier is created for. 0 if the nullifier does not associate with a transient note. | + +### `UnencryptedLogHash` -#### _UnencryptedLogHash_ + | Field | Type | Description | | --------- | ------- | -------------------------------------- | -| _hash_ | _field_ | Hash of the unencrypted log. | -| _length_ | _field_ | Number of fields of the log preimage. | -| _counter_ | _field_ | Counter at which the hash was emitted. | +| `hash` | `field` | Hash of the unencrypted log. | +| `length` | `field` | Number of fields of the log preimage. | +| `counter` | `field` | Counter at which the hash was emitted. | -#### _EncryptedLogHash_ +### `EncryptedLogHash` | Field | Type | Description | | ------------ | ------- | -------------------------------------------- | -| _hash_ | _field_ | Hash of the encrypted log. | -| _length_ | _field_ | Number of fields of the log preimage. | -| _randomness_ | _field_ | A random value to hide the contract address. | -| _counter_ | _field_ | Counter at which the hash was emitted. | +| `hash` | `field` | Hash of the encrypted log. | +| `length` | `field` | Number of fields of the log preimage. | +| `counter` | `field` | Counter at which the hash was emitted. | +| `randomness` | `field` | A random value to hide the contract address. | -#### _EncryptedNotePreimageHash_ +### `EncryptedNotePreimageHash` | Field | Type | Description | | ------------------- | ------- | --------------------------------------- | -| _hash_ | _field_ | Hash of the encrypted note preimage. | -| _length_ | _field_ | Number of fields of the note preimage. | -| _counter_ | _field_ | Counter at which the hash was emitted. | -| _note_hash_counter_ | _field_ | Counter of the corresponding note hash. | +| `hash` | `field` | Hash of the encrypted note preimage. | +| `length` | `field` | Number of fields of the note preimage. | +| `counter` | `field` | Counter at which the hash was emitted. | +| `note_hash_counter` | `field` | Counter of the corresponding note hash. | -#### _Header_ +### `BlockHeader` | Field | Type | Description | | ----------------------------- | ------- | ----------------------------------------------------------------------------------------------- | -| _note_hash_tree_root_ | _field_ | Root of the note hash tree. | -| _nullifier_tree_root_ | _field_ | Root of the nullifier tree. | -| _l1_to_l2_messages_tree_root_ | _field_ | Root of the l1-to-l2 messages tree. | -| _public_data_tree_root_ | _field_ | Root of the public data tree. | -| _archive_tree_root_ | _field_ | Root of the state roots tree archived at the block prior to when the transaction was assembled. | -| _global_variables_hash_ | _field_ | Hash of the previous global variables. | +| `note_hash_tree_root` | `field` | Root of the note hash tree. | +| `nullifier_tree_root` | `field` | Root of the nullifier tree. | +| `l1_to_l2_messages_tree_root` | `field` | Root of the l1-to-l2 messages tree. | +| `public_data_tree_root` | `field` | Root of the public data tree. | +| `archive_tree_root` | `field` | Root of the state roots tree archived at the block prior to when the transaction was assembled. | +| `global_variables_hash` | `field` | Hash of the previous global variables. | diff --git a/yellow-paper/docs/circuits/private-kernel-initial.md b/yellow-paper/docs/circuits/private-kernel-initial.md deleted file mode 100644 index 05c952d1a98..00000000000 --- a/yellow-paper/docs/circuits/private-kernel-initial.md +++ /dev/null @@ -1,395 +0,0 @@ -# Private Kernel Circuit - Initial - -## Requirements - -In the **initial** kernel iteration, the process involves taking a transaction request and private call data, verifying their integrity, and preparing the necessary data for subsequent circuits to operate. This step is particularly beneficial due to its separation from the [inner private kernel circuit](./private-kernel-inner.md), as the first call lacks a "previous kernel" to process. Additionally, it executes tasks that are pertinent to a transaction and need only occur once. - -### Key Responsibilities Specific to this Circuit - -#### Validating the correspondence of function call with caller's intent. - -This entails ensuring that the following from the _[private_call](#privatecall)_ aligns with the specifications in the _[transaction_request](#transactionrequest)_: - -- _contract_address_ -- _function_data_ -- _args_hash_: Hash of the function arguments. - -> Although it's not enforced in the protocol, it is customary to provide a signature signed over the transaction request and verify it in the first function call. This practice guarantees that only the party possessing the key(s) can authorize a transaction with the exact transaction request on behalf of an account. - -#### Verifying the legitimacy of the function as the entrypoint. - -For the _[function_data](#functiondata)_ in _[private_call](#privatecall).[call_stack_item](#privatecallstackitem)_, the circuit verifies that: - -- It must be a private function: - - _`function_data.function_type == private`_ - -#### Ensuring the function call is the first call. - -For the _[call_context](./private-function.md#callcontext)_ in _[private_call](#privatecall).[call_stack_item](#privatecallstackitem).[public_inputs](./private-function.md#public-inputs)_, the circuit checks that: - -- It must not be a delegate call: - - _`call_context.is_delegate_call == false`_ -- It must not be a static call: - - _`call_context.is_static_call == false`_ - -#### Ensuring transaction uniqueness. - -It must emit the hash of the _[transaction_request](#transactionrequest)_ as the **first** nullifier. - -The hash is computed as: - -_`hash(origin, function_data.hash(), args_hash, tx_context.hash())`_ - -Where _function_data.hash()_ and _tx_context.hash()_ are the hashes of the serialized field elements. - -This nullifier serves multiple purposes: - -- Identifying a transaction. -- Non-malleability. Preventing the signature of a transaction request from being reused in another transaction. -- Generating values that should be maintained within the transaction's scope. For example, it is utilized to [compute the nonces](./private-kernel-tail.md#siloing-values) for all the note hashes in a transaction. - -> Note that the final transaction data is not deterministic for a given transaction request. The production of new notes, the destruction of notes, and various other values are likely to change based on the time and conditions when a transaction is being composed. However, the intricacies of implementation should not be a concern for the entity initiating the transaction. - -### Processing Private Function Call - -#### Ensuring the function being called exists in the contract. - -With the following data provided from _[private_inputs](#private-inputs).[private_call](#privatecall)_: - -- _contract_address_ in _private_call.[call_stack_item](#privatecallstackitem)_. -- _contract_data_ -- _contract_class_data_ -- _function_data_ in _private_call.[call_stack_item](#privatecallstackitem)_. - -This circuit validates the existence of the function in the contract through the following checks: - -1. Verify that the _contract_address_ can be derived from the _public_keys_hash_, _salted_initialization_hash_, and a _contract_class_id. - - Refer to the details [here](../contract-deployment/instances.md#address) for the process of computing the address for a contract instance. - -2. Verify that _contract_class_id_ in the _contract_address_ preimage can be derived from the _contract_class_artifact_hash_, _contract_class_public_bytecode_commitment_, and a _private_functions_root_. - - Refer to the details [here](../contract-deployment/classes.md#class-identifier) for the process of computing the _contract_class_id_. - -3. Verify that _contract_class.private_functions_ includes the function being called: - - 1. Compute the hash of the verification key: - - _`vk_hash = hash(private_call.vk)`_ - 2. Compute the function leaf: - - _`hash(function_data.selector, vk_hash)`_ - 3. Perform a membership check on the function leaf, where: - - The index and sibling path are provided through _function_leaf_membership_witness_ within _[private_call](#privatecall)_. - - The root is in the preimage of the _contract_class_id_ from the previous step. - -#### Verifying the private function proof. - -It verifies that the private function was executed successfully with the provided proof data, verification key, and the public inputs of the [private function circuit](./private-function.md). - -#### Verifying the public inputs of the private function circuit. - -It ensures the private function circuit's intention by checking the following in _[private_call](#privatecall).[call_stack_item](#privatecallstackitem).[public_inputs](./private-function.md#public-inputs)_: - -- The _header_ must match the one in the _[constant_data](#constantdata)_. - -#### Verifying the counters. - -It verifies that each value listed below is associated with a legitimate counter. - -1. For the _[call_stack_item](#privatecallstackitem)_: - - - The _counter_start_ must be 0. - - This check can be skipped for [inner private kernel circuit](./private-kernel-inner.md#verifying-the-counters). - - The _counter_end_ must be greater than the _counter_start_. - -2. For items in each ordered array in _[call_stack_item](#privatecallstackitem).[public_inputs](./private-function.md#public-inputs)_: - - - The _counter_ of the first item must be greater than the _counter_start_ of the _call_stack_item_. - - The _counter_ of each subsequent item must be greater than the _counter_ of the previous item. - - The _counter_ of the last item must be less than the _counter_end_ of the _call_stack_item_. - - The ordered arrays include: - - - _note_hashes_ - - _nullifiers_ - - _read_requests_ - - _unencrypted_log_hashes_ - - _encrypted_log_hashes_ - - _encrypted_note_preimage_hashes_ - -3. For the last _N_ non-empty items in the _private_call_requests_ in the _[transient_accumulated_data](#transientaccumulateddata)_: - - - The _counter_end_ of each request must be greater than its _counter_start_. - - The _counter_end_ of the first request must be less than the _counter_end_ of the _call_stack_item_. - - The _counter_end_ of the second and subsequent requests must be less than the _counter_start_ of the previous request. - - The _counter_start_ of the last request must be greater than the _counter_start_ of the _call_stack_item_. - - > _N_ is the number of non-zero hashes in the _private_call_stack_item_hashes_ in _[private_inputs](#private-inputs).[private_call](#privatecall).[public_inputs](./private-function.md#public-inputs)_. - -4. For the last _N_ non-empty items in the _public_call_requests_ in the _[transient_accumulated_data](#transientaccumulateddata)_: - - - The _counter_start_ of the first request must be greater than the _counter_start_ of the _call_stack_item_. - - The _counter_start_ of each subsequent request must be greater than the _counter_start_ of the previous item. - - The _counter_start_ of the last item must be less than the _counter_end_ of the _call_stack_item_. - - > _N_ is the number of non-zero hashes in the _public_call_stack_item_hashes_ in _[private_inputs](#private-inputs).[private_call](#privatecall).[public_inputs](./private-function.md#public-inputs)_. - - > Note that the _counter_end_ of public call request is unknown at this point. Both counters will be [recalibrated](./public-kernel-initial.md#recalibrating-counters) in the initial public kernel circuit following the simulation of all public function calls. - -> Note that, for the initial private kernel circuit, all values within the _[transient_accumulated_data](#transientaccumulateddata)_ originate from the _[private_call](#privatecall)_. However, this process of counter verification is also applicable to the [inner private kernel circuit](./private-kernel-inner.md#verifying-the-counters), where the _transient_accumulated_data_ comprises values from previous iterations and the current function call. Therefor, only the last _N_ items need to be checked in the above operations. - -### Validating Public Inputs - -#### Verifying the transient accumulated data. - -Within the _[public_inputs](#public-inputs)_, the _[transient_accumulated_data](#transientaccumulateddata)_ encapsulates values reflecting the operations conducted by the _private_call_. - -This circuit verifies that the values in _[private_inputs](#private-inputs).[private_call](#privatecall).[call_stack_item](#privatecallstackitem).[public_inputs](./private-function.md#public-inputs)_ (_private_function_public_inputs_) are aggregated into the _public_inputs_ correctly: - -1. Ensure that the specified values in the following arrays match those in the corresponding arrays in the _private_function_public_inputs_: - - - _note_hash_contexts_ - - _value_, _counter_ - - _nullifier_contexts_ - - _value_, _counter_ - - _l2_to_l1_message_contexts_ - - _value_ - - _read_request_contexts_ - - _note_hash_, _counter_ - - _public_call_requests_ - - _hash_, _counter_ - - _unencrypted_log_hash_contexts_ - - _hash_, _length_, _counter_ - - _encrypted_log_hash_contexts_ - - _hash_, _length_, _randomness_, _counter_ - - _encrypted_note_preimage_hash_contexts_ - - _hash_, _length_, _counter_, _note_hash_counter_ - -2. Check that the hashes in the _private_call_requests_ align with the values in the _private_call_stack_item_hashes_ in the _private_function_public_inputs_, but in **reverse** order. - - > It's important that the call requests are arranged in reverse order to ensure they are executed in chronological order. - -3. For each non-empty call request in both _private_call_requests_ and _public_call_requests_: - - - The _caller_contract_address_ equals the _contract_address_ in _[private_call](#privatecall).[call_stack_item](#privatecallstackitem)_. - - The following values in _caller_context_ are either empty or align with the values in the _call_context_ within _private_function_public_inputs_: - - _`(caller_context.msg_sender == 0) & (caller_context.storage_contract_address == 0)`_ - - Or _`(caller_context.msg_sender == call_context.msg_sender) & (caller_context.storage_contract_address == call_context.storage_contract_address)`_ - - The _is_static_call_ flag must be propagated: - - _`caller_context.is_static_call == call_context.is_static_call`_ - - > The caller context in a call request may be empty for standard calls. This precaution is crucial to prevent information leakage, particularly as revealing the _msg_sender_ of this private function when calling a public function could pose security risks. - -4. For each non-empty item in the following arrays, its _contract_address_ must equal the _storage_contract_address_ defined in _private_function_public_inputs.call_context_: - - - _note_hash_contexts_ - - _nullifier_contexts_ - - _l2_to_l1_message_contexts_ - - _read_request_contexts_ - - _nullifier_key_validation_request_contexts_ - - _unencrypted_log_hash_contexts_ - - _encrypted_log_hash_contexts_ - - _encrypted_note_preimage_hash_contexts_ - - > Ensuring the alignment of the contract addresses is crucial, as it is later used to [silo the values](./private-kernel-tail.md#siloing-values) and to establish associations with values within the same contract. - -5. For each non-empty item in _l2_to_l1_message_contexts_, its _portal_contract_address_ must equal the _portal_contract_address_ defined in _private_function_public_inputs.call_context_. - -6. For each _note_hash_ in the _note_hash_contexts_, verify that it is associated with a _nullifier_counter_. The value of the _nullifier_counter_ can be: - - - Zero: if the note is not nullified in the same transaction. - - Greater than _note_hash.counter_: if the note is nullified in the same transaction. - - > Nullifier counters are used in the [reset private kernel circuit](./private-kernel-reset.md#read-request-reset-private-kernel-circuit) to ensure a read happens **before** a transient note is nullified. - - > Zero can be used to indicate a non-existing transient nullifier, as this value can never serve as the counter of a nullifier. It corresponds to the _counter_start_ of the first function call. - -> Note that the verification process outlined above is also applicable to the inner private kernel circuit. However, given that the _transient_accumulated_data_ for the inner private kernel circuit comprises both values from previous iterations and the _private_call_, the above process specifically targets the values stemming from the _private_call_. The inner kernel circuit performs an [extra check](./private-kernel-inner.md#verifying-the-transient-accumulated-data) to ensure that the _transient_accumulated_data_ also contains values from the previous iterations. - -#### Verifying the constant data. - -It verifies that: - -- The _tx_context_ in the _[constant_data](#constantdata)_ matches the _tx_context_ in the _[transaction_request](#transactionrequest)_. -- The _header_ must align with the one used in the private function circuit, as verified [earlier](#verifying-the-public-inputs-of-the-private-function-circuit). - -## Private Inputs - -### _TransactionRequest_ - -Data that represents the caller's intent. - -| Field | Type | Description | -| --------------- | ------------------------------------------- | -------------------------------------------- | -| _origin_ | _AztecAddress_ | The Aztec address of the transaction sender. | -| _function_data_ | _[FunctionData](#functiondata)_ | Data of the function being called. | -| _args_hash_ | _field_ | Hash of the function arguments. | -| _tx_context_ | _[TransactionContext](#transactioncontext)_ | Information about the transaction. | - -### _PrivateCall_ - -Data that holds details about the current private function call. - -| Field | Type | Description | -| ---------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------- | -| _call_stack_item_ | _[PrivateCallStackItem](#privatecallstackitem)_ | Information about the current private function call. | -| _proof_ | _Proof_ | Proof of the private function circuit. | -| _vk_ | _VerificationKey_ | Verification key of the private function circuit. | -| _bytecode_hash_ | _field_ | Hash of the function bytecode. | -| _salted_initialization_hash_ | _field_ | Part of the address preimage. | -| _public_keys_hash_ | _field_ | Part of the address preimage. | -| _contract_class_artifact_hash_ | _field_ | Part of the contract class identifier preimage. | -| _contract_class_public_bytecode_commitment_ | _field_ | Part of the contract class identifier preimage. | -| _function_leaf_membership_witness_ | _[MembershipWitness](#membershipwitness)_ | Membership witness for the function being called. | - -## Public Inputs - -### _ConstantData_ - -Data that remains the same throughout the entire transaction. - -| Field | Type | Description | -| -------------- | -------------------------------------------------- | ------------------------------------------------------------- | -| _header_ | _[Header](./private-function.md#header)_ | Header of a block which was used when assembling the tx. | -| _tx_context_ | _[TransactionContext](#transactioncontext)_ | Context of the transaction. | - -### _TransientAccumulatedData_ - -| Field | Type | Description | -| ------------------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | -| _note_hash_contexts_ | [_[NoteHashContext](#notehashcontext)_; _C_] | Note hashes with extra data aiding verification. | -| _nullifier_contexts_ | [_[NullifierContext](#nullifiercontext)_; _C_] | Nullifiers with extra data aiding verification. | -| _l2_to_l1_message_contexts_ | [_[L2toL1MessageContext](#l2tol1messagecontext)_; _C_] | L2-to-l1 messages with extra data aiding verification. | -| _read_request_contexts_ | [_[ReadRequestContext](#readrequestcontext)_; _C_] | Requests to read notes in the note hash tree. | -| _nullifier_key_validation_request_contexts_ | [_[NullifierKeyValidationRequestContext](#nullifierkeyvalidationrequestcontext)_; _C_] | Requests to validate nullifier keys. | -| _unencrypted_log_hash_contexts_ | [_[EncryptedLogHashContext](#encryptedloghashcontext)_; _C_] | Hashes of the unencrypted logs with extra data aiding verification. | -| _encrypted_log_hash_contexts_ | [_[UnencryptedLogHashContext](#unencryptedloghashcontext)_; _C_] | Hashes of the encrypted logs with extra data aiding verification. | -| _encrypted_note_preimage_hash_contexts_ | [_[EncryptedNotePreimageHashContext](#encryptednotepreimagehash)_; _C_] | Hashes of the encrypted note preimages with extra data aiding verification. | -| _private_call_requests_ | [_[CallRequest](#callrequest)_; _C_] | Requests to call private functions. | -| _public_call_requests_ | [_[CallRequest](#callrequest)_; _C_] | Requests to call publics functions. | - -> The above **C**s represent constants defined by the protocol. Each **C** might have a different value from the others. - -## Types - -#### _FunctionData_ - -| Field | Type | Description | -| ------------------- | ---------------------------------- | --------------------------------------------------- | -| _function_selector_ | _u32_ | Selector of the function being called. | -| _function_type_ | private \| public \| unconstrained | Type of the function being called. | - -#### _TransactionContext_ - -| Field | Type | Description | -| ---------- | ------------------------------------ | ---------------------------- | -| _tx_type_ | standard \| fee_paying \| fee_rebate | Type of the transaction. | -| _chain_id_ | _field_ | Chain ID of the transaction. | -| _version_ | _field_ | Version of the transaction. | - -#### _PrivateCallStackItem_ - -| Field | Type | Description | -| ------------------ | -------------------------------------------------------------------- | --------------------------------------------------------- | -| _contract_address_ | _AztecAddress_ | Address of the contract on which the function is invoked. | -| _function_data_ | _[FunctionData](#functiondata)_ | Data of the function being called. | -| _public_inputs_ | _[PrivateFunctionPublicInputs](./private-function.md#public-inputs)_ | Public inputs of the private function circuit. | -| _counter_start_ | _field_ | Counter at which the function call was initiated. | -| _counter_end_ | _field_ | Counter at which the function call ended. | - -#### _CallRequest_ - -| Field | Type | Description | -| ----------------- | --------------------------------- | --------------------------------------------- | -| _hash_ | _field_ | Hash of the call stack item. | -| _caller_contract_ | _AztecAddress_ | Address of the contract calling the function. | -| _caller_context_ | _[CallerContext](#callercontext)_ | Context of the contract calling the function. | -| _counter_start_ | _field_ | Counter at which the call was initiated. | -| _counter_end_ | _field_ | Counter at which the call ended. | - -#### _CallerContext_ - -| Field | Type | Description | -| ------------------ | -------------- | ---------------------------------------------------- | -| _msg_sender_ | _AztecAddress_ | Address of the caller contract. | -| _storage_contract_ | _AztecAddress_ | Storage contract address of the caller contract. | -| _is_static_call_ | _bool_ | A flag indicating whether the call is a static call. | - -#### _NoteHashContext_ - -| Field | Type | Description | -| ------------------- | -------------- | -------------------------------------------------------- | -| _value_ | _field_ | Hash of the note. | -| _counter_ | _field_ | Counter at which the note hash was created. | -| _nullifier_counter_ | _field_ | Counter at which the nullifier for the note was created. | -| _contract_address_ | _AztecAddress_ | Address of the contract the note was created. | - -#### _NullifierContext_ - -| Field | Type | Description | -| ------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------ | -| _value_ | _field_ | Value of the nullifier. | -| _counter_ | _field_ | Counter at which the nullifier was created. | -| _note_hash_counter_ | _field_ | Counter of the transient note the nullifier is created for. 0 if the nullifier does not associate with a transient note. | -| _contract_address_ | _AztecAddress_ | Address of the contract the nullifier was created. | - -#### _L2toL1MessageContext_ - -| Field | Type | Description | -| ------------------------- | -------------- | ------------------------------------------------ | -| _value_ | _field_ | L2-to-l2 message. | -| _portal_contract_address_ | _AztecAddress_ | Address of the portal contract to the contract. | -| _contract_address_ | _AztecAddress_ | Address of the contract the message was created. | - -#### _ReadRequestContext_ - -| Field | Type | Description | -| ------------------ | -------------- | --------------------------------------------- | -| _note_hash_ | _field_ | Hash of the note to be read. | -| _counter_ | _field_ | Counter at which the request was made. | -| _contract_address_ | _AztecAddress_ | Address of the contract the request was made. | - -#### _NullifierKeyValidationRequestContext_ - -| Field | Type | Description | -| ------------------ | -------------- | ---------------------------------------------------------- | -| _public_key_ | _field_ | Nullifier public key of an account. | -| _secret_key_ | _field_ | Secret key of an account siloed with the contract address. | -| _contract_address_ | _AztecAddress_ | Address of the contract the request was made. | - -#### _UnencryptedLogHashContext_ - -| Field | Type | Description | -| ------------------ | -------------- | -------------------------------------------- | -| _hash_ | _field_ | Hash of the unencrypted log. | -| _length_ | _field_ | Number of fields of the log preimage. | -| _counter_ | _field_ | Counter at which the hash was emitted. | -| _contract_address_ | _AztecAddress_ | Address of the contract the log was emitted. | - -#### _EncryptedLogHashContext_ - -| Field | Type | Description | -| ------------------ | -------------- | -------------------------------------------- | -| _hash_ | _field_ | Hash of the encrypted log. | -| _length_ | _field_ | Number of fields of the log preimage. | -| _randomness_ | _field_ | A random value to hide the contract address. | -| _counter_ | _field_ | Counter at which the hash was emitted. | -| _contract_address_ | _AztecAddress_ | Address of the contract the log was emitted. | - -#### _EncryptedNotePreimageHashContext_ - -| Field | Type | Description | -| ------------------- | -------------- | -------------------------------------------- | -| _hash_ | _field_ | Hash of the encrypted note preimage. | -| _length_ | _field_ | Number of fields of the note preimage. | -| _note_hash_counter_ | _field_ | Counter of the corresponding note hash. | -| _counter_ | _field_ | Counter at which the hash was emitted. | -| _contract_address_ | _AztecAddress_ | Address of the contract the log was emitted. | - -#### _MembershipWitness_ - -| Field | Type | Description | -| -------------- | ------------ | ------------------------------------- | -| _leaf_index_ | _field_ | Index of the leaf in the tree. | -| _sibling_path_ | [_field_; H] | Sibling path to the leaf in the tree. | - -> **H** represents the height of the tree. diff --git a/yellow-paper/docs/circuits/private-kernel-initial.mdx b/yellow-paper/docs/circuits/private-kernel-initial.mdx new file mode 100644 index 00000000000..8115a11af77 --- /dev/null +++ b/yellow-paper/docs/circuits/private-kernel-initial.mdx @@ -0,0 +1,898 @@ +# Private Kernel Circuit - Initial + + + +## Requirements + +In the **initial** kernel iteration, the process involves taking a [`transaction_request`](#transactionrequest) and private call data , performing checks on this data (see below), and preparing the necessary data for subsequent circuits to operate. This "initial" circuit is an optimization over the [inner private kernel circuit](./private-kernel-inner.mdx), as there is no "previous kernel" to verify at the beginning of a transaction. Additionally, this circuit executes tasks that need only occur once per transaction. + +### Key Checks within this Circuit + +#### This first function call of the transaction must match the caller's intent + +The following data in the [`private_inputs`](#private-inputs)[`.private_call`](#privatecall) must match the corresponding fields of the user's [`private_inputs`](#private-inputs)[`.transaction_request`](#transactionrequest): + +- `contract_address` +- `function_data` +- `args_hash`: Hash of the function arguments. + +> Notice: a `transaction_request` doesn't explicitly contain a signature. Aztec implements [account abstraction](../addresses-and-keys/keys-requirements.md#authorization-keys), so the process for authorizing a transaction (if at all) is dictated by the logic of the functions of that transaction. In particular, an account contract can be called as an 'entrypoint' to a transaction, and there, custom authorization logic can be executed. + +#### The function call must be a [private function](./private-function.md) + +For the [`private_inputs`](#private-inputs)[`.private_call`](#privatecall)[`.call_stack_item`](#privatecallstackitem)[`.function_data`](#functiondata), the circuit verifies that: + +- It must be a private function: + - `function_data.function_type == private` +- It must not be an internal function: + - `function_data.is_internal == false` + +#### It must be a standard [synchronous function call](../calls/sync-calls.md) + + + +For the [`private_inputs`](#private-inputs)[`.private_call`](#privatecall)[`.call_stack_item`](#privatecallstackitem)[`.public_inputs`](./private-function.md#public-inputs)[`.call_context: CallContext`](./private-function.md#callcontext), the circuit checks that: + +- It must not be a delegate call: + - `call_context.is_delegate_call == false` +- It must not be a static call: + - `call_context.is_static_call == false` + + + +#### The `transaction_request` must be unique + + + +It must emit the hash of the [`private_inputs.transaction_request`](#transactionrequest) as the **first** nullifier. + + + +The hash is computed as: + +```js +let { origin, function_data, args_hash, tx_context } = + private_inputs.transaction_request; +let tx_hash = hash(origin, function_data.hash(), args_hash, tx_context.hash()); +``` + +Where `function_data.hash()` and `tx_context.hash()` are the hashes of the serialized field elements. + +This nullifier serves multiple purposes: + +- Identifying a transaction. +- Non-malleability. Preventing the signature of a transaction request from being reused in another transaction. +- Generating values that should be maintained within the transaction's scope. For example, it is utilized to [compute the nonces](./private-kernel-tail.md#siloing-values) for all the note hashes in a transaction. + + + +> Note that the final transaction data is not deterministic for a given transaction request. The production of new notes, the destruction of notes, and various other values are likely to change based on the time and conditions when a transaction is being composed. However, the intricacies of implementation should not be a concern for the entity initiating the transaction. + +### Processing a Private Function Call + +#### The function being called must exist within the contract class of the called `contract_address` + + + +With the following data provided from [`private_inputs`](#private-inputs)[`.private_call`](#privatecall): + +- `contract_address` in `private_call`[`.call_stack_item`](#privatecallstackitem). +- `contract_instance` +- `contract_class` +- `function_data` in `private_call`[`.call_stack_item`](#privatecallstackitem). + +This circuit validates the existence of the function in the contract through the following checks: + + + +1. Verify that the `contract_address` can be derived from the `contract_instance`: + + Refer to the details [here](../contract-deployment/instances.md#address) for the process of computing the address for a contract instance. + +2. Verify that the `contract_instance.contract_class_id` can be derived from the given `contract_class`: + + Refer to the details [here](../contract-deployment/classes.md#class-identifier) for the process of computing the _contract_class_id_. + +3. Verify that _contract_class_data.private_functions_ includes the function being called: + + 1. Compute the hash of the verification key: + - `vk_hash = hash(private_call.vk)` + 2. Compute the function leaf: + - `hash(function_data.selector, vk_hash, private_call.bytecode_hash)` + 3. Perform a membership check; that the function leaf exists within the function tree, where: + - The index and sibling path are provided through [`private_call`](#privatecall)`.function_leaf_membership_witness`. + - The root is `contract_class.private_functions`. + +#### The private function proof must verify + +It verifies that the private function was executed successfully with the provided proof data, verification key, and the public inputs of the [private function circuit](./private-function.md). +I.e. `private_inputs.private_call.vk`, `private_inputs.private_call.proof`, and `private_inputs.private_call.call_stack_item.public_inputs`. + +#### Validate the counters. + +It validates that all of the counters follow the rules of the protocol. + +1. For the [`private_inputs.private_call.call_stack_item`](#privatecallstackitem): + + - The `counter_start` must be `0`. + - This check can be skipped for [inner private kernel circuit](./private-kernel-inner.mdx#verifying-the-counters). + - The `counter_end` must be strictly greater than the `counter_start`. + +2. Validate the items in each ordered array of [private_inputs.private_call.call_stack_item](#privatecallstackitem).[public_inputs](./private-function.md#public-inputs). + +The "ordered arrays" include: + +- `note_hashes` +- `nullifiers` +- `read_requests` +- `unencrypted_log_hashes` +- `encrypted_log_hashes` +- `encrypted_note_preimage_hashes` + + + +Notice that each element of these arrays is a [type](./private-function.md#types) that contains a `counter`. They are so-called "ordered arrays" because the counters must be in a strictly-increasing order within an array. + +For each element of each "ordered array" of [`private_inputs.private_call.call_stack_item`](#privatecallstackitem)[`.public_inputs`](./private-function.md#public-inputs): + +- The `counter` of the first element must be strictly greater than the `counter_start` of the `private_inputs.private_call.call_stack_item`. +- The `counter` of each subsequent element must be strictly greater than the `counter` of the previous item. +- The `counter` of the last element must be strictly less than the `counter_end` of the `private_inputs.private_call.call_stack_item`. + + + + +3. For the `N` non-empty requests in [`public_inputs`](#public-inputs)[`.transient_accumulated_data`](#transientaccumulateddata)[`.private_call_requests: [CallRequest; _]`](#callrequest): + + + + + +> `N` is the number of non-zero entries in [`private_inputs`](#private-inputs)[`.private_call`](#privatecall)[`.call_stack_item`](#privatecallstackitem)[`.public_inputs`](./private-function.md#public-inputs)[`.private_call_stack_item_hashes: [Field; _]`]. + + + +- The `counter_end` of each request must be strictly greater than its `counter_start`. +- The `counter_end` of the first request must be strictly less than the `counter_end` of the `call_stack_item`. +- The `counter_end` of the second and each of the subsequent requests must be strictly less than the `counter_start` of the previous request. +- The `counter_start` of the last request must be strictly greater than the `counter_start` of the `call_stack_item`. + +4. For the `N` non-empty requests in [`public_inputs.transient_accumulated_data`](#transientaccumulateddata)`.public_call_requests`: + + > `N` is the number of non-zero entries in the `public_call_stack_item_hashes` in [`private_inputs`](#private-inputs)[`.private_call`](#privatecall)[`.public_inputs`](./private-function.md#public-inputs). + + + +- The `counter_start` of the first request must be strictly greater than the `counter_start` of the `call_stack_item`. +- The `counter_start` of each subsequent request must be strictly greater than the `counter_start` of the previous item. +- The `counter_start` of the last item must be strictly less than the `counter_end` of the `call_stack_item`. + +> Note that the `counter_end` of each public call request is unknown at this point . Both the `counter_start` and `counter_end` of a `PublicCallStackItem` will be [recalibrated](./public-kernel-initial.md#recalibrating-counters) in the initial _public_ kernel circuit following the simulation of all public function calls. + +> Note that, for this initial private kernel circuit, all values within the [`public_inputs.transient_accumulated_data`](#transientaccumulateddata) originate from the [`private_call`](#privatecall). However, this process of counter verification is also applicable to the [inner private kernel circuit](./private-kernel-inner.mdx#verifying-the-counters), where the `transient_accumulated_data` comprises values from previous iterations and the current function call. Therefore, only the last `N` requests (which correspond to the new requests which originated from the `private_inputs.private_call`) need to be checked in the above operations. + +### Validating Public Inputs + +#### Verifying the `TransientAccumulatedData`. + +The various side effects of the [`private_inputs`](#private-inputs)[`.private_call`](#privatecall)[`.call_stack_item`](#privatecallstackitem)[`.public_inputs: PrivateFunctionPublicInputs`](./private-function.md#public-inputs) are formatted and pushed to the various arrays of the [`public_inputs`](#public-inputs)[`.transient_accumulated_data: TransientAccumulatedData`](#transientaccumulateddata). + +This circuit verifies that the values in [`private_inputs`](#private-inputs)[`.private_call`](#privatecall)[`.call_stack_item`](#privatecallstackitem)[`.public_inputs: PrivateFunctionPublicInputs`](./private-function.md#public-inputs) are aggregated into the various arrays of the [`public_inputs`](#public-inputs)[`.transient_accumulated_data: TransientAccumulatedData`](#transientaccumulateddata) correctly. + +1. Ensure that the specified values in the following arrays match those in the corresponding arrays in the `private_function_public_inputs`: + +- `note_hash_contexts` + - `value`, `counter` +- `nullifier_contexts` + - `value`, `counter` +- `l2_to_l1_message_contexts` + - `value` +- `read_request_contexts` + - `note_hash`, `counter` +- `nullifier_key_validation_request` + - `public_key`, `secret_key` +- `unencrypted_log_hash_contexts` + - `hash`, `length`, `counter` +- `encrypted_log_hash_contexts` + - `hash`, `length`, `randomness`, `counter` +- `encrypted_note_preimage_hash_contexts` + - `hash`, `length`, `counter`, `note_hash_counter` +- `private_call_requests` + - `hash`, `counter` +- `public_call_requests` + - `hash`, `counter` + +2. Check that the hashes in the `private_call_requests` align with the values in the `private_call_stack_item_hashes` in the `private_function_public_inputs`, but in **reverse** order. + + > It's important that the `private_call_requests` are arranged in reverse order to ensure they are executed in chronological order. + +3. For each non-empty call request in both `public_inputs.transient_accumulated_data.private_call_requests` and `public_inputs.transient_accumulated_data.public_call_requests`: + + - The `caller_contract_address` equals the [`private_call`](#privatecall)[`.call_stack_item`](#privatecallstackitem)[`.contract_address`]. + - The following values in `caller_context` are either empty or align with the values in the `private_inputs.private_call.call_stack_item.public_inputs.call_context`: + - `(caller_context.msg_sender == 0) & (caller_context.storage_contract_address == 0)` + - Or `(caller_context.msg_sender == call_context.msg_sender) & (caller_context.storage_contract_address == call_context.storage_contract_address)` + - The `is_static_call` flag must be propagated: + - `caller_context.is_static_call == call_context.is_static_call` + + + +> The caller context in a call request may be empty for standard calls. This precaution is crucial to prevent information leakage, particularly as revealing the _msg_sender_ of this private function when calling a public function could pose security risks. + +4. For each non-empty item in the following arrays, its `contract_address` must equal the `storage_contract_address` in `private_inputs.private_call.call_stack_item.public_inputs.call_context`: + + - `note_hash_contexts` + - `nullifier_contexts` + - `l2_to_l1_message_contexts` + - `read_request_contexts` + - `nullifier_key_validation_request_contexts` + - `unencrypted_log_hash_contexts` + - `encrypted_log_hash_contexts` + - `encrypted_note_preimage_hash_contexts` + + > Ensuring the alignment of the contract addresses is crucial, as it is later used to [silo the values](./private-kernel-tail.md#siloing-values) and to establish associations with values within the same contract. + +5. For each non-empty item in `l2_to_l1_message_contexts`, its `portal_contract_address` must equal the `portal_contract_address` defined in `private_function_public_inputs.call_context`. + + +6. For each `note_hash_context: NoteHashContext` in the `note_hash_contexts`, validate its `nullifier_counter`. The value of the `nullifier_counter` can be: + + - Zero: if the note is not nullified in the same transaction. + - Strictly greater than `note_hash.counter`: if the note is nullified in the same transaction. + + > Nullifier counters are used in the [reset private kernel circuit](./private-kernel-reset.md#read-request-reset-private-kernel-circuit) to ensure a read happens **before** a transient note is nullified. + + > Zero can be used to indicate a non-existing transient nullifier, as this value can never serve as the counter of a nullifier. It corresponds to the `counter_start` of the first function call. + +> Note that the verification process outlined above is also applicable to the inner private kernel circuit. However, given that the `transient_accumulated_data` for the inner private kernel circuit comprises both values from previous iterations and the `private_call`, the above process specifically targets the values stemming from the `private_call`. The inner kernel circuit performs an [extra check](./private-kernel-inner.mdx#verifying-the-transient-accumulated-data) to ensure that the `transient_accumulated_data` also contains values from the previous iterations. + +#### Verifying the constant data. + +It verifies that: + +- The `tx_context` in the [`constant_data`](#constantdata) matches the `tx_context` in the [`transaction_request`](#transactionrequest). +- The `block_header` must align with the one used in the private function circuit, as verified [earlier](#verifying-the-public-inputs-of-the-private-function-circuit). + +## Diagram + +This diagram flows from the private inputs (which can be considered "inputs") to the public inputs (which can be considered "outputs"). + +--- + +Key: + +
+ +```mermaid +classDiagram +direction LR +class ParentClass { + child: ChildClass +} +class ChildClass { + x +} +class A { + a +} +class B { + a +} +class C { + c +} +class D { + c +} +ParentClass *-- ChildClass: Composition. +A .. B: Perform a consistency check on values in these classes. +C ..> D: Copy the data from the inputs A to the outputs B\n(possibly with some modification along the way). +``` + +
+ +--- + +The diagram: + + + + + +
+
+ +```mermaid +classDiagram +direction TB + +class PrivateInputs { + transaction_request: TransactionRequest + private_call: PrivateCall +} +PrivateInputs *-- TransactionRequest: transaction_request +PrivateInputs *-- PrivateCall: private_call + +class TransactionRequest { + origin: AztecAddress + function_data: FunctionData + args_hash: field + tx_context: TransactionContext +} +TransactionRequest *-- FunctionData: function_data +TransactionRequest *-- TransactionContext: tx_context + +TransactionRequest ..> ConstantData: tx_context + +class PrivateCall { + call_stack_item: PrivateCallStackItem + proof: Proof + vk: VerificationKey + bytecode_hash: field + contract_instance: ContractInstance + contract_class: ContractClass + function_leaf_membership_witness: MembershipWitness +} +PrivateCall *-- PrivateCallStackItem: call_stack_item +PrivateCall *-- Proof: proof +PrivateCall *-- VerificationKey: vk +PrivateCall *-- ContractInstance: contract_instance +PrivateCall *-- ContractClass: contract_class +PrivateCall *-- MembershipWitness: function_leaf_membership_witness + +VerificationKey ..> FUNCTION_EXISTENCE_CHECK: Check vk exists within function leaf +FunctionData ..> FUNCTION_EXISTENCE_CHECK: Check function_data exists within function leaf +MembershipWitness ..> FUNCTION_EXISTENCE_CHECK: Check function leaf exists within \nprivate function tree + +FUNCTION_EXISTENCE_CHECK .. ContractClass: computed_root == private_functions + +VerificationKey ..> PROOF_VERIFICATION +Proof ..> PROOF_VERIFICATION +PrivateFunctionPublicInputs ..> PROOF_VERIFICATION + +ContractClass .. ContractInstance: hash(contract_class) == contract_class_id + +class ContractClass { + version: u8 + registerer_address: AztecAddress + artifact_hash: field + private_functions: field + public_functions: field + unconstrained_functions: field +} + +class TransactionContext { + tx_type: standard|fee_paying|fee_rebate + chain_id: field + version: field +} + +class PrivateCallStackItem { + contract_address: AztecAddress + function_data: FunctionData + public_inputs: PrivateFunctionPublicInputs + counter_start: field + counter_end: field +} +PrivateCallStackItem *-- FunctionData: function_data +PrivateCallStackItem *-- PrivateFunctionPublicInputs: public_inputs + +PrivateCallStackItem .. TransactionRequest: function_data==function_data + + +PrivateCallStackItem .. CallContext: if is_delegatecall then\n contract_address == msg_sender \nelse \n contract_address == storage_contract_address + +PrivateCallStackItem .. PrivateFunctionPublicInputs: Validate counter_start & counter_end\nvs. the counters of the ordered arrays + +PrivateCallStackItem .. CallRequest: Validate all counter_start\n& counter_end values. + +TransactionRequest .. PrivateFunctionPublicInputs: args_hash == args_hash + +TransactionRequest .. CallContext: origin == msg_sender + +ContractInstance .. PrivateCallStackItem: hash(contract_instance) == contract_address + +class FunctionData { + function_selector: u32 + function_type: private|public|unconstrained + is_internal: bool +} + +class PrivateFunctionPublicInputs { + call_context: CallContext + args_hash: field + return_values: List~field~ + note_hashes: List~NoteHash~ + nullifiers: List~Nullifier~ + l2_to_l1_messages: List~field~ + read_requests: List~ReadRequest~ + nullifier_key_validation_requests: List~NullifierKeyValidationRequest~ + unencrypted_log_hashes: List~UnencryptedLogHash~ + encrypted_log_hashes: List~EncryptedLogHash~ + encrypted_note_preimage_hashes: List~EncryptedNotePreimageHash~ + private_call_stack_item_hashes: List~field~ + public_call_stack_item_hashes: List~field~ + block_header: BlockHeader + chain_id: field + version: field +} +PrivateFunctionPublicInputs *-- CallContext: call_context +PrivateFunctionPublicInputs *-- NoteHash: note_hashes +PrivateFunctionPublicInputs *-- Nullifier: nullifiers +PrivateFunctionPublicInputs *-- ReadRequest: read_requests +PrivateFunctionPublicInputs *-- NullifierKeyValidationRequest: nullifier_key_validation_requests +PrivateFunctionPublicInputs *-- UnencryptedLogHash: unencrypted_log_hashes +PrivateFunctionPublicInputs *-- EncryptedLogHash: encrypted_log_hashes +PrivateFunctionPublicInputs *-- EncryptedNotePreimageHash: encrypted_note_preimage_hashes +PrivateFunctionPublicInputs *-- BlockHeader: block_header + +TransactionContext .. PrivateFunctionPublicInputs: chain_id==chain_id\nversion==version + +class FUNCTION_EXISTENCE_CHECK { + Check the vk, function_data, + exist within the private function tree root +} +class PROOF_VERIFICATION { + Verify the proof +} + + +class CallContext { + msg_sender: AztecAddress + storage_contract_address: AztecAddress + portal_contract_address: AztecAddress + is_delegate_call: bool + is_static_call: bool +} +CallContext ..> CallerContext : call_context + +CallContext .. NoteHashContext: storage_contract_address\n== contract_address +CallContext .. NullifierContext: storage_contract_address\n== contract_address +CallContext .. ReadRequestContext: storage_contract_address\n== contract_address +CallContext .. NullifierKeyValidationRequestContext: storage_contract_address\n== contract_address +CallContext .. UnencryptedLogHashContext: storage_contract_address\n== contract_address +CallContext .. EncryptedLogHashContext: storage_contract_address\n== contract_address +CallContext .. EncryptedNotePreimageHashContext: storage_contract_address\n== contract_address + + +PrivateFunctionPublicInputs ..> L2ToL1MessageContext: l2_to_l1_messages\n->l2_to_l1_message_contexts + +class NoteHash { + value: field + counter: field +} +NoteHash ..> NoteHashContext: note_hashes\n->note_hash_contexts + +class Nullifier { + value: field + counter: field + note_hash_counter: field +} +Nullifier ..> NullifierContext: nullifiers\n->nullifier_contexts + +class ReadRequest { + note_hash: field + counter: field +} +ReadRequest ..> ReadRequestContext: read_requests\n->read_request_contexts + +class NullifierKeyValidationRequest { + public_key: GrumpkinPoint + secret_key: fq +} +NullifierKeyValidationRequest ..> NullifierKeyValidationRequestContext: nullifier_key_validation_requests\n->nullifier_key_validation_request_contexts + +class UnencryptedLogHash { + hash: field + length: field + counter: field +} +UnencryptedLogHash ..> UnencryptedLogHashContext: unencrypted_log_hashes\n->unencrypted_log_hash_contexts + +class EncryptedLogHash { + hash: field + length: field + counter: field + randomness: field +} +EncryptedLogHash ..> EncryptedLogHashContext: encrypted_log_hashes\n->encrypted_log_hash_contexts + +class EncryptedNotePreimageHash { + hash: field + length: field + counter: field + note_hash_counter: field +} +EncryptedNotePreimageHash ..> EncryptedNotePreimageHashContext: encrypted_note_preimage_hashes\n->encrypted_note_preimage_hash_contexts + + +class BlockHeader { + note_hash_tree_root: field + nullifier_tree_root: field + l1_to_l2_message_tree_root: field + public_data_tree_root: field + archive_tree_root: field + global_variables_hash: field +} + +class CallRequest { + call_stack_item_hash: field + caller_contract: AztecAddress + caller_context: CallerContext + counter_start: field + counter_end: field +} +CallerContext --* CallRequest : caller_context + +PrivateFunctionPublicInputs ..> CallRequest: private_call_stack_item_hash->call_stack_item_hash\npublic_call_stack_item_hash->call_stack_item_hash + +class CallerContext { + msg_sender: AztecAddress + storage_contract_address: AztecAddress + is_static_call: bool +} + + + +class NoteHashContext { + value: field + counter: field + nullifier_counter: field + contract_address: AztecAddress +} + +class NullifierContext { + value: field + counter: field + note_hash_counter: field + contract_address: AztecAddress +} + +class L2ToL1MessageContext { + value: field + portal_contract_address: AztecAddress + contract_address: AztecAddress +} + +class ReadRequestContext { + note_hash: field + counter: field + contract_address: AztecAddress +} + +class NullifierKeyValidationRequestContext { + public_key: field + secret_key: field + contract_address: AztecAddress +} + +class UnencryptedLogHashContext { + hash: field + length: field + counter: field + contract_address: AztecAddress +} + +class EncryptedLogHashContext { + hash: field + length: field + counter: field + contract_address: AztecAddress + randomness: field +} + +class EncryptedNotePreimageHashContext { + hash: field + length: field + counter: field + contract_address: AztecAddress + note_hash_counter: field +} + +class MembershipWitness { + leaf_index: field + sibling_path: List~field~ +} + +class ContractInstance { + version: u8 + deployer_address: AztecAddress + salt: field + contract_class_id: field + contract_args_hash: field + portal_contract_address: EthereumAddress + public_keys_hash: Field +} + +class TransientAccumulatedData { + note_hash_contexts: List~NoteHashContext~ + nullifier_contexts: List~NullifierContext~ + l2_to_l1_message_contexts: List~L2ToL1MessageContext~ + read_request_contexts: List~ReadRequestContext~ + nullifier_key_validation_request_contexts: List~NullifierKeyValidationRequestContext~ + unencrypted_log_hash_contexts: List~UnencryptedLogHashContext~ + encrypted_log_hash_contexts: List~EncryptedLogHashContext~ + encrypted_note_preimage_hash_contexts: List~EncryptedNotePreimageHashContext~ + private_call_requests: List~CallRequest~ + public_call_requests: List~CallRequest~ +} +NoteHashContext --* TransientAccumulatedData: note_hash_contexts +NullifierContext --* TransientAccumulatedData: nullifier_contexts +L2ToL1MessageContext --* TransientAccumulatedData: l2_to_l1_message_contexts +ReadRequestContext --* TransientAccumulatedData: read_request_contexts +NullifierKeyValidationRequestContext --* TransientAccumulatedData: nullifier_key_validation_request_contexts +UnencryptedLogHashContext --* TransientAccumulatedData: unencrypted_log_hash_contexts +EncryptedLogHashContext --* TransientAccumulatedData: encrypted_log_hash_contexts +EncryptedNotePreimageHashContext --* TransientAccumulatedData: encrypted_note_preimage_hash_contexts +CallRequest --* TransientAccumulatedData: private_call_requests +CallRequest --* TransientAccumulatedData: public_call_requests + +class ConstantData { + block_header: BlockHeader + tx_context: TransactionContext +} +BlockHeader ..> ConstantData: block_header + +class PublicInputs { + constant_data: ConstantData + transient_accumulated_data: TransientAccumulatedData +} +ConstantData --* PublicInputs : constant_data +TransientAccumulatedData --* PublicInputs: transient_accumulated_data + +``` + +
+
+ +## `PrivateInputs` + +| Field | Type | Description | +| --------------------- | ------------------------------------------- | ----------- | +| `transaction_request` | [`TransactionRequest`](#transactionrequest) | | +| `private_call` | [`PrivateCall`](#privatecall) | | + +### `TransactionRequest` + +Data that represents the caller's intent. + +| Field | Type | Description | +| --------------- | ------------------------------------------- | -------------------------------------------- | +| `origin` | `AztecAddress` | The Aztec address of the transaction sender. | +| `function_data` | [`FunctionData`](#functiondata) | Data of the function being called. | +| `args_hash` | `Field` | Hash of the function arguments. | +| `tx_context` | [`TransactionContext`](#transactioncontext) | Information about the transaction. | + +### `PrivateCall` + +Data that holds details about the current private function call. + +| Field | Type | Description | +| ---------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------- | +| `call_stack_item` | [`PrivateCallStackItem`](#privatecallstackitem) | Information about the current private function call. | +| `proof` | `Proof` | Proof of the private function circuit. | +| `vk` | `VerificationKey` | Verification key of the private function circuit. | +| `bytecode_hash` | `Field` | Hash of the function bytecode. | +| `contract_instance` | [`ContractInstance`](../contract-deployment/instances.md#structure) | Data of the contract instance being called. | +| `contract_class` | [`ContractClass`](#contractclassdata) | Data of the contract class. | +| `function_leaf_membership_witness` | [`MembershipWitness`](#membershipwitness) | Membership witness for the function being called. | + +## `PublicInputs` + +| Field | Type | Description | +| ---------------------------- | ------------------------------------------------------- | ----------- | +| `constant_data` | [`ConstantData`](#constantdata) | | +| `transient_accumulated_data` | [`TransientAccumulatedData`](#transientaccumulateddata) | | + +### `ConstantData` + +Data that remains the same throughout the entire transaction. + +| Field | Type | Description | +| ------------ | ------------------------------------------- | -------------------------------------------------------- | +| `header` | [`Header`](./private-function.md#header) | Header of a block which was used when assembling the tx. | +| `tx_context` | [`TransactionContext`](#transactioncontext) | Context of the transaction. | + +### `TransientAccumulatedData` + + + +| Field | Type | Description | +| ------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- | +| `note_hash_contexts` | [`[NoteHashContext; C]`](#notehashcontext) | Note hashes with extra data aiding verification. | +| `nullifier_contexts` | [`[NullifierContext; C]`](#nullifiercontext) | Nullifiers with extra data aiding verification. | +| `l2_to_l1_message_contexts` | [`[L2toL1MessageContext; C]`](#l2tol1messagecontext) | L2-to-l1 messages with extra data aiding verification. | +| `read_request_contexts` | [`[ReadRequestContext; C]`](#readrequestcontext) | Requests to read notes in the note hash tree. | +| `nullifier_key_validation_request_contexts` | [`[NullifierKeyValidationRequestContext; C]`](#nullifierkeyvalidationrequestcontext) | Requests to validate nullifier keys. | +| `unencrypted_log_hash_contexts` | [`[UnencryptedLogHashContext; C]`](#unencryptedloghashcontext) | Hashes of the unencrypted logs with extra data aiding verification. | +| `encrypted_log_hash_contexts` | [`[EncryptedLogHashContext; C]`](#encryptedloghashcontext) | Hashes of the encrypted logs with extra data aiding verification. | +| `encrypted_note_preimage_hash_contexts` | [`[EncryptedNotePreimageHashContext; C]`](#encryptednotepreimagehash) | Hashes of the encrypted note preimages with extra data aiding verification. | +| `private_call_requests` | [`[CallRequest; C]`](#callrequest) | Requests to call private functions. | +| `public_call_requests` | [`[CallRequest; C]`](#callrequest) | Requests to call publics functions. | + +> The above **C**s represent constants defined by the protocol. Each **C** might have a different value from the others. + +## Types + +### `FunctionData` + + + + +| Field | Type | Description | +| ------------------- | ---------------------------------- | --------------------------------------------------- | +| `function_selector` | `u32` | Selector of the function being called. | +| `function_type` | `private | public | unconstrained` | Type of the function being called. | +| `is_internal` | `bool` | A flag indicating whether the function is internal. | + + + +### `ContractClass` + + + + +| Field | Type | Description | +| ------------------------- | -------------- | ------------------------------------------------------------------ | +| `version` | `u8` | Version identifier. | +| `registerer_address` | `AztecAddress` | Address of the canonical contract used for registering this class. | +| `artifact_hash` | `Field` | Hash of the contract artifact. | +| `private_functions` | `Field` | Merkle root of the private function tree. | +| `public_functions` | `Field` | Merkle root of the public function tree. | +| `unconstrained_functions` | `Field` | Merkle root of the unconstrained function tree. | + +### `TransactionContext` + + +| Field | Type | Description | +| ---------- | ------------------------------------ | ---------------------------- | +| `tx_type` | `standard | fee_paying | fee_rebate` | Type of the transaction. | +| `chain_id` | `Field` | Chain ID of the transaction. | +| `version` | `Field` | Version of the transaction. | + +### `PrivateCallStackItem` + +| Field | Type | Description | +| ------------------ | -------------------------------------------------------------------- | --------------------------------------------------------- | +| `contract_address` | `AztecAddress` | Address of the contract on which the function is invoked. | +| `function_data` | [`FunctionData`](#functiondata) | Data of the function being called. | +| `public_inputs` | [`PrivateFunctionPublicInputs`](./private-function.md#public-inputs) | Public inputs of the private function circuit. | +| `counter_start` | `Field` | Counter at which the function call was initiated. | +| `counter_end` | `Field` | Counter at which the function call ended. | + + + +### `CallRequest` + + + + +| Field | Type | Description | +| ---------------------- | --------------------------------- | --------------------------------------------- | +| `call_stack_item_hash` | `Field` | Hash of the call stack item. | +| `caller_contract` | `AztecAddress` | Address of the contract calling the function. | +| `caller_context` | [`CallerContext`](#callercontext) | Context of the contract calling the function. | +| `counter_start` | `Field` | Counter at which the call was initiated. | +| `counter_end` | `Field` | Counter at which the call ended. | + +### `CallerContext` + + + +| Field | Type | Description | +| ------------------ | -------------- | ---------------------------------------------------- | +| `msg_sender` | `AztecAddress` | Address of the caller contract. | +| `storage_contract` | `AztecAddress` | Storage contract address of the caller contract. | +| `is_static_call` | `bool` | A flag indicating whether the call is a static call. | + +### `NoteHashContext` + + + +| Field | Type | Description | +| ------------------- | -------------- | -------------------------------------------------------- | +| `value` | `Field` | Hash of the note. | +| `counter` | `Field` | Counter at which the note hash was created. | +| `nullifier_counter` | `Field` | Counter at which the nullifier for the note was created. | +| `contract_address` | `AztecAddress` | Address of the contract the note was created. | + +### `NullifierContext` + +| Field | Type | Description | +| ------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `value` | `Field` | Value of the nullifier. | +| `counter` | `Field` | Counter at which the nullifier was created. | +| `note_hash_counter` | `Field` | Counter of the transient note the nullifier is created for. 0 if the nullifier does not associate with a transient note. | +| `contract_address` | `AztecAddress` | Address of the contract the nullifier was created. | + +### `L2toL1MessageContext` + +| Field | Type | Description | +| ------------------------- | -------------- | ------------------------------------------------ | +| `value` | `Field` | L2-to-l2 message. | +| `portal_contract_address` | `AztecAddress` | Address of the portal contract to the contract. | +| `contract_address` | `AztecAddress` | Address of the contract the message was created. | + +### `ReadRequestContext` + +| Field | Type | Description | +| ------------------ | -------------- | --------------------------------------------- | +| `note_hash` | `Field` | Hash of the note to be read. | +| `counter` | `Field` | Counter at which the request was made. | +| `contract_address` | `AztecAddress` | Address of the contract the request was made. | + +### `NullifierKeyValidationRequestContext` + +| Field | Type | Description | +| ------------------ | -------------- | ---------------------------------------------------------- | +| `public_key` | `Field` | Nullifier public key of an account. | +| `secret_key` | `Field` | Secret key of an account siloed with the contract address. | +| `contract_address` | `AztecAddress` | Address of the contract the request was made. | + +### `UnencryptedLogHashContext` + + + +| Field | Type | Description | +| ------------------ | -------------- | -------------------------------------------- | +| `hash` | `Field` | Hash of the unencrypted log. | +| `length` | `Field` | Number of fields of the log preimage. | +| `counter` | `Field` | Counter at which the hash was emitted. | +| `contract_address` | `AztecAddress` | Address of the contract the log was emitted. | + +### `EncryptedLogHashContext` + +| Field | Type | Description | +| ------------------ | -------------- | -------------------------------------------- | +| `hash` | `Field` | Hash of the encrypted log. | +| `length` | `Field` | Number of fields of the log preimage. | +| `counter` | `Field` | Counter at which the hash was emitted. | +| `contract_address` | `AztecAddress` | Address of the contract the log was emitted. | +| `randomness` | `Field` | A random value to hide the contract address. | + +### `EncryptedNotePreimageHashContext` + +| Field | Type | Description | +| ------------------- | -------------- | -------------------------------------------- | +| `hash` | `Field` | Hash of the encrypted note preimage. | +| `length` | `Field` | Number of fields of the note preimage. | +| `counter` | `Field` | Counter at which the hash was emitted. | +| `contract_address` | `AztecAddress` | Address of the contract the log was emitted. | +| `note_hash_counter` | `Field` | Counter of the corresponding note hash. | + +### `MembershipWitness` + +| Field | Type | Description | +| -------------- | ------------ | ------------------------------------- | +| `leaf_index` | `Field` | Index of the leaf in the tree. | +| `sibling_path` | `[Field; H]` | Sibling path to the leaf in the tree. | + +> `H` represents the height of the tree. diff --git a/yellow-paper/docs/circuits/private-kernel-inner.md b/yellow-paper/docs/circuits/private-kernel-inner.md deleted file mode 100644 index 49648035775..00000000000 --- a/yellow-paper/docs/circuits/private-kernel-inner.md +++ /dev/null @@ -1,143 +0,0 @@ -# Private Kernel Circuit - Inner - -## Requirements - -Each **inner** kernel iteration processes a private function call and the results of a previous kernel iteration. - -### Verification of the Previous Iteration - -#### Verifying the previous kernel proof. - -It verifies that the previous iteration was executed successfully with the provided proof data, verification key, and public inputs, sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel)_. - -The preceding proof can be: - -- [Initial private kernel proof](./private-kernel-initial.md). -- Inner private kernel proof. -- [Reset private kernel proof](./private-kernel-reset.md). - -The previous proof and the proof for the current function call are verified using recursion. - -### Processing Private Function Call - -#### Ensuring the function being called exists in the contract. - -This section follows the same [process](./private-kernel-initial.md#ensuring-the-function-being-called-exists-in-the-contract) as outlined in the initial private kernel circuit. - -#### Ensuring the function is legitimate: - -For the _[function_data](./private-kernel-initial.md#functiondata)_ in _[private_call](#privatecall).[call_stack_item](./private-kernel-initial.md#privatecallstackitem)_, this circuit verifies that: - -- It must be a private function: - - _`function_data.function_type == private`_ - -#### Ensuring the current call matches the call request. - -The top item in the _private_call_requests_ of the _[previous_kernel](#previouskernel)_ must pertain to the current function call. - -This circuit will: - -1. Pop the request from the stack: - - - _`call_request = previous_kernel.public_inputs.transient_accumulated_data.private_call_requests.pop()`_ - -2. Compare the hash with that of the current function call: - - - _`call_request.hash == private_call.call_stack_item.hash()`_ - - The hash of the _call_stack_item_ is computed as: - - _`hash(contract_address, function_data.hash(), public_inputs.hash(), counter_start, counter_end)`_ - - Where _function_data.hash()_ and _public_inputs.hash()_ are the hashes of the serialized field elements. - -#### Ensuring this function is called with the correct context. - -For the _call_context_ in the [public_inputs](./private-function.md#public-inputs) of the _[private_call](#privatecall).[call_stack_item](./private-kernel-initial.md#privatecallstackitem)_ and the _call_request_ popped in the [previous step](#ensuring-the-current-call-matches-the-call-request), this circuit checks that: - -1. If it is a standard call: _`call_context.is_delegate_call == false`_ - - - The _msg_sender_ of the current iteration must be the same as the caller's _contract_address_: - - _`call_context.msg_sender == call_request.caller_contract_address`_ - - The _storage_contract_address_ of the current iteration must be the same as its _contract_address_: - - _`call_context.storage_contract_address == call_stack_item.contract_address`_ - -2. If it is a delegate call: _`call_context.is_delegate_call == true`_ - - - The _caller_context_ in the _call_request_ must not be empty. Specifically, the following values of the caller must not be zeros: - - _msg_sender_ - - _storage_contract_address_ - - The _msg_sender_ of the current iteration must equal the caller's _msg_sender_: - - _`call_context.msg_sender == caller_context.msg_sender`_ - - The _storage_contract_address_ of the current iteration must equal the caller's _storage_contract_address_: - - _`call_context.storage_contract_address == caller_context.storage_contract_address`_ - - The _storage_contract_address_ of the current iteration must not equal the _contract_address_: - - _`call_context.storage_contract_address != call_stack_item.contract_address`_ - -3. If it is NOT a static call: _`call_context.is_static_call == false`_ - - - The previous iteration must not be a static call: - - _`caller_context.is_static_call == false`_ - -#### Verifying the private function proof. - -It verifies that the private function was executed successfully with the provided proof data, verification key, and the public inputs, sourced from _[private_inputs](#private-inputs).[private_call](#privatecall)_. - -This circuit verifies this proof and [the proof of the previous kernel iteration](#verifying-the-previous-kernel-proof) using recursion, and generates a single proof. This consolidation of multiple proofs into one is what allows the private kernel circuits to gradually merge private function proofs into a single proof of execution that represents the entire private section of a transaction. - -#### Verifying the public inputs of the private function circuit. - -It ensures the private function circuit's intention by checking the following in _[private_call](#privatecall).[call_stack_item](#privatecallstackitem).[public_inputs](./private-function.md#public-inputs)_: - -- The _header_ must match the one in the _[constant_data](./private-kernel-initial.md#constantdata)_. -- If it is a static call (_`public_inputs.call_context.is_static_call == true`_), it ensures that the function does not induce any state changes by verifying that the following arrays are empty: - - _note_hashes_ - - _nullifiers_ - - _l2_to_l1_messages_ - - _unencrypted_log_hashes_ - - _encrypted_log_hashes_ - - _encrypted_note_preimage_hashes_ - -#### Verifying the counters. - -This section follows the same [process](./private-kernel-initial.md#verifying-the-counters) as outlined in the initial private kernel circuit. - -Additionally, it verifies that for the _[call_stack_item](#privatecallstackitem)_, the _counter_start_ and _counter_end_ must match those in the _call_request_ [popped](#ensuring-the-current-call-matches-the-call-request) from the _private_call_requests_ in a previous step. - -### Validating Public Inputs - -#### Verifying the transient accumulated data. - -The _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ in this circuit's _[public_inputs](#public-inputs)_ includes values from both the previous iterations and the _[private_call](#privatecall)_. - -For each array in the _transient_accumulated_data_, this circuit verifies that: - -1. It is populated with the values from the previous iterations, specifically: - - - _`public_inputs.transient_accumulated_data.ARRAY[0..N] == private_inputs.previous_kernel.public_inputs.transient_accumulated_data.ARRAY[0..N]`_ - - > It's important to note that the top item in the _private_call_requests_ from the _previous_kernel_ won't be included, as it has been removed in a [previous step](#ensuring-the-current-call-matches-the-call-request). - -2. As for the subsequent items appended after the values from the previous iterations, they constitute the values from the _private_call_, and each must undergo the same [verification](./private-kernel-initial.md#verifying-the-transient-accumulated-data) as outlined in the initial private kernel circuit. - -#### Verifying the constant data. - -It verifies that the _[constant_data](./private-kernel-initial.md#constantdata)_ in the _[public_inputs](#public-inputs)_ matches the _constant_data_ in _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.md#public-inputs)_. - -## Private Inputs - -### _PreviousKernel_ - -Data of the previous kernel iteration. - -| Field | Type | Description | -| -------------------- | ------------------------------------------------------------------------------- | -------------------------------------------- | -| _public_inputs_ | _[InitialPrivateKernelPublicInputs](./private-kernel-initial.md#public-inputs)_ | Public inputs of the proof. | -| _proof_ | _Proof_ | Proof of the kernel circuit. | -| _vk_ | _VerificationKey_ | Verification key of the kernel circuit. | -| _membership_witness_ | _[MembershipWitness](./private-kernel-initial.md#membershipwitness)_ | Membership witness for the verification key. | - -### _PrivateCall_ - -The format aligns with the _[PrivateCall](./private-kernel-initial.md#privatecall)_ of the initial private kernel circuit. - -## Public Inputs - -The format aligns with the _[Public Inputs](./private-kernel-initial.md#public-inputs)_ of the initial private kernel circuit. diff --git a/yellow-paper/docs/circuits/private-kernel-inner.mdx b/yellow-paper/docs/circuits/private-kernel-inner.mdx new file mode 100644 index 00000000000..44f639181f7 --- /dev/null +++ b/yellow-paper/docs/circuits/private-kernel-inner.mdx @@ -0,0 +1,556 @@ +# Private Kernel Circuit - Inner + + + +## Requirements + +Each **inner** kernel iteration processes a private function call and the results of a previous kernel iteration. + +### Verification of the Previous Iteration + +#### Verifying the previous kernel proof. + +It verifies that the previous iteration was executed successfully with the provided proof data, verification key, and public inputs, sourced from [`private_inputs`](#private-inputs).[`previous_kernel`](#previouskernel). + +The preceding proof can be: + +- [Initial private kernel proof](./private-kernel-initial.mdx). +- Inner private kernel proof. +- [Reset private kernel proof](./private-kernel-reset.md). + +The previous proof and the proof for the current function call are verified using recursion. + + + +### Processing Private Function Call + +#### Ensuring the function being called exists in the contract. + +This section follows the same [process](./private-kernel-initial.mdx#ensuring-the-function-being-called-exists-in-the-contract) as outlined in the initial private kernel circuit. + +#### Ensuring the function is legitimate: + +For the [`function_data`](./private-kernel-initial.mdx#functiondata) in [`private_call`](#privatecall)[`.call_stack_item`](./private-kernel-initial.mdx#privatecallstackitem), this circuit verifies that: + +- It must be a private function: + - `function_data.function_type == private` + +#### Ensuring the current call matches the call request. + + + +The top item in the `private_call_requests` of the [`previous_kernel`](#previouskernel) must pertain to the current function call. + +This circuit will: + +1. Pop the request from the stack: + + - `call_request = previous_kernel.public_inputs.transient_accumulated_data.private_call_requests.pop()` + +2. Compare the hash with that of the current function call: + + - `call_request.call_stack_item_hash == private_call.call_stack_item.hash()` + - The hash of the `call_stack_item` is computed as: + - `hash(contract_address, function_data.hash(), public_inputs.hash(), counter_start, counter_end)` + - Where `function_data.hash()` and `public_inputs.hash()` are the hashes of the serialized field elements. + +#### Ensuring this function is called with the correct context. + +For the `call_context` in the [`public_inputs`](./private-function.md#public-inputs) of the [`private_call`](#privatecall)[`.call_stack_item`](./private-kernel-initial.mdx#privatecallstackitem) and the `call_request` popped in the [previous step](#ensuring-the-current-call-matches-the-call-request), this circuit checks that: + +1. If it is a standard call: `call_context.is_delegate_call == false` + + - The `msg_sender` of the current iteration must be the same as the caller's `contract_address`: + - `call_context.msg_sender == call_request.caller_contract_address` + - The `storage_contract_address` of the current iteration must be the same as its `contract_address`: + - `call_context.storage_contract_address == call_stack_item.contract_address` + +2. If it is a delegate call: `call_context.is_delegate_call == true` + + - The `caller_context` in the `call_request` must not be empty. Specifically, the following values of the caller must not be zeros: + - `msg_sender` + - `storage_contract_address` + - The `msg_sender` of the current iteration must equal the caller's `msg_sender`: + - `call_context.msg_sender == caller_context.msg_sender` + - The `storage_contract_address` of the current iteration must equal the caller's `storage_contract_address`: + - `call_context.storage_contract_address == caller_context.storage_contract_address` + - The `storage_contract_address` of the current iteration must not equal the `contract_address`: + - `call_context.storage_contract_address != call_stack_item.contract_address` + +3. If it is NOT a static call: `call_context.is_static_call == false` + + - The previous iteration must not be a static call: + - `caller_context.is_static_call == false` + +4. If it is an internal call: `call_stack_item.function_data.is_internal == true` + + - The `msg_sender` of the current iteration must equal the `storage_contract_address`: + - `call_context.msg_sender == call_context.storage_contract_address` + +#### Verifying the private function proof. + +It verifies that the private function was executed successfully with the provided proof data, verification key, and the public inputs, sourced from [`private_inputs`](#private-inputs)[`.private_call`](#privatecall). + +This circuit verifies this proof and [the proof of the previous kernel iteration](#verifying-the-previous-kernel-proof) using recursion, and generates a single proof. This consolidation of multiple proofs into one is what allows the private kernel circuits to gradually merge private function proofs into a single proof of execution that represents the entire private section of a transaction. + +#### Verifying the public inputs of the private function circuit. + +It ensures the private function circuit's intention by checking the following in [`private_call`](#privatecall)[`.call_stack_item`](#privatecallstackitem)[`.public_inputs`](./private-function.md#public-inputs): + +- The `block_header` must match the one in the [constant_data](./private-kernel-initial.mdx#constantdata). +- If it is a static call (`public_inputs.call_context.is_static_call == true`), it ensures that the function does not induce any state changes by verifying that the following arrays are empty: + - `note_hashes` + - `nullifiers` + - `l2_to_l1_messages` + - `unencrypted_log_hashes` + - `encrypted_log_hashes` + - `encrypted_note_preimage_hashes` + +#### Verifying the counters. + +This section follows the same [process](./private-kernel-initial.mdx#verifying-the-counters) as outlined in the initial private kernel circuit. + +Additionally, it verifies that for the [`call_stack_item`](#privatecallstackitem), the `counter_start` and `counter_end` must match those in the `call_request` [popped](#ensuring-the-current-call-matches-the-call-request) from the `private_call_requests` in a previous step. + +### Validating Public Inputs + +#### Verifying the transient accumulated data. + +The [`transient_accumulated_data`](./private-kernel-initial.mdx#transientaccumulateddata) in this circuit's [`public_inputs`](#public-inputs) includes values from both the previous iterations and the [`private_call`](#privatecall). + +For each array in the `transient_accumulated_data`, this circuit verifies that: + +1. It is populated with the values from the previous iterations, specifically: + + - `public_inputs.transient_accumulated_data.ARRAY[0..N] == private_inputs.previous_kernel.public_inputs.transient_accumulated_data.ARRAY[0..N]` + + > It's important to note that the top item in the `private_call_requests` from the `previous_kernel` won't be included, as it has been removed in a [previous step](#ensuring-the-current-call-matches-the-call-request). + +2. As for the subsequent items appended after the values from the previous iterations, they constitute the values from the `private_call`, and each must undergo the same [verification](./private-kernel-initial.mdx#verifying-the-transient-accumulated-data) as outlined in the initial private kernel circuit. + +#### Verifying the constant data. + +It verifies that the [`constant_data`](./private-kernel-initial.mdx#constantdata) in the [`public_inputs`](#public-inputs) matches the `constant_data` in [`private_inputs`](#private-inputs)[`.previous_kernel`](#previouskernel)[`.public_inputs`](./private-kernel-initial.mdx#public-inputs). + + + +
+
+ + + +```mermaid +classDiagram +direction TB + +class PrivateInputs { + transaction_request: TransactionRequest + private_call: PrivateCall + previous_kernel: PreviousKernel +} +PrivateInputs *-- TransactionRequest: transaction_request +PrivateInputs *-- PrivateCall: private_call + +class TransactionRequest { + origin: AztecAddress + function_data: FunctionData + args_hash: field + tx_context: TransactionContext +} +TransactionRequest *-- FunctionData: function_data +TransactionRequest *-- TransactionContext: tx_context + +TransactionRequest ..> ConstantData: tx_context + +class PrivateCall { + call_stack_item: PrivateCallStackItem + proof: Proof + vk: VerificationKey + bytecode_hash: field + contract_data: ContractInstance + contract_class: ContractClass + function_leaf_membership_witness: MembershipWitness +} +PrivateCall *-- PrivateCallStackItem: call_stack_item +PrivateCall *-- Proof: proof +PrivateCall *-- VerificationKey: vk +PrivateCall *-- ContractInstance: contract_data +PrivateCall *-- ContractClass: contract_class +PrivateCall *-- MembershipWitness: function_leaf_membership_witness + +VerificationKey ..> FUNCTION_EXISTENCE_CHECK: Check vk exists within function leaf +FunctionData ..> FUNCTION_EXISTENCE_CHECK: Check function_data exists within function leaf +MembershipWitness ..> FUNCTION_EXISTENCE_CHECK: Check function leaf exists within \nprivate function tree + +FUNCTION_EXISTENCE_CHECK .. ContractClass: computed_root == private_functions + +VerificationKey ..> PROOF_VERIFICATION +Proof ..> PROOF_VERIFICATION +PrivateFunctionPublicInputs ..> PROOF_VERIFICATION + +ContractClass .. ContractInstance: hash(contract_class) == contract_class_id + +class ContractClass { + version: u8 + registerer_address: AztecAddress + artifact_hash: field + private_functions: field + public_functions: field + unconstrained_functions: field +} + +class TransactionContext { + tx_type: standard|fee_paying|fee_rebate + chain_id: field + version: field +} + +class PrivateCallStackItem { + contract_address: AztecAddress + function_data: FunctionData + public_inputs: PrivateFunctionPublicInputs + counter_start: field + counter_end: field +} +PrivateCallStackItem *-- FunctionData: function_data +PrivateCallStackItem *-- PrivateFunctionPublicInputs: public_inputs + +PrivateCallStackItem .. TransactionRequest: function_data==function_data + + +PrivateCallStackItem .. CallContext: if is_delegatecall then\n contract_address == msg_sender \nelse \n contract_address == storage_contract_address + +PrivateCallStackItem .. PrivateFunctionPublicInputs: Validate counter_start & counter_end\nvs. the counters of the ordered arrays + +PrivateCallStackItem .. CallRequest: Validate all counter_start\n& counter_end values. + +TransactionRequest .. PrivateFunctionPublicInputs: args_hash == args_hash + +TransactionRequest .. CallContext: origin == msg_sender + +ContractInstance .. PrivateCallStackItem: hash(contract_data) == contract_address + +class FunctionData { + function_selector: u32 + function_type: private|public|unconstrained + is_internal: bool +} + +class PrivateFunctionPublicInputs { + call_context: CallContext + args_hash: field + return_values: List~field~ + note_hashes: List~NoteHash~ + nullifiers: List~Nullifier~ + l2_to_l1_messages: List~field~ + read_requests: List~ReadRequest~ + nullifier_key_validation_requests: List~NullifierKeyValidationRequest~ + unencrypted_log_hashes: List~UnencryptedLogHash~ + encrypted_log_hashes: List~EncryptedLogHash~ + encrypted_note_preimage_hashes: List~EncryptedNotePreimageHash~ + private_call_stack_item_hashes: List~field~ + public_call_stack_item_hashes: List~field~ + block_header: BlockHeader + chain_id: field + version: field +} +PrivateFunctionPublicInputs *-- CallContext: call_context +PrivateFunctionPublicInputs *-- NoteHash: note_hashes +PrivateFunctionPublicInputs *-- Nullifier: nullifiers +PrivateFunctionPublicInputs *-- ReadRequest: read_requests +PrivateFunctionPublicInputs *-- NullifierKeyValidationRequest: nullifier_key_validation_requests +PrivateFunctionPublicInputs *-- UnencryptedLogHash: unencrypted_log_hashes +PrivateFunctionPublicInputs *-- EncryptedLogHash: encrypted_log_hashes +PrivateFunctionPublicInputs *-- EncryptedNotePreimageHash: encrypted_note_preimage_hashes +PrivateFunctionPublicInputs *-- BlockHeader: block_header + +TransactionContext .. PrivateFunctionPublicInputs: chain_id==chain_id\nversion==version + +class FUNCTION_EXISTENCE_CHECK { + Check the vk, function_data, + exist within the private function tree root +} +class PROOF_VERIFICATION { + Verify the proof +} + + +class CallContext { + msg_sender: AztecAddress + storage_contract_address: AztecAddress + portal_contract_address: AztecAddress + is_delegate_call: bool + is_static_call: bool +} +CallContext ..> CallerContext : call_context + +CallContext .. NoteHashContext: storage_contract_address\n== contract_address +CallContext .. NullifierContext: storage_contract_address\n== contract_address +CallContext .. ReadRequestContext: storage_contract_address\n== contract_address +CallContext .. NullifierKeyValidationRequestContext: storage_contract_address\n== contract_address +CallContext .. UnencryptedLogHashContext: storage_contract_address\n== contract_address +CallContext .. EncryptedLogHashContext: storage_contract_address\n== contract_address +CallContext .. EncryptedNotePreimageHashContext: storage_contract_address\n== contract_address + + +PrivateFunctionPublicInputs ..> L2ToL1MessageContext: l2_to_l1_messages\n->l2_to_l1_message_contexts + +class NoteHash { + value: field + counter: field +} +NoteHash ..> NoteHashContext: note_hashes\n->note_hash_contexts + +class Nullifier { + value: field + counter: field + note_hash_counter: field +} +Nullifier ..> NullifierContext: nullifiers\n->nullifier_contexts + +class ReadRequest { + note_hash: field + counter: field +} +ReadRequest ..> ReadRequestContext: read_requests\n->read_request_contexts + +class NullifierKeyValidationRequest { + public_key: GrumpkinPoint + secret_key: fq +} +NullifierKeyValidationRequest ..> NullifierKeyValidationRequestContext: nullifier_key_validation_requests\n->nullifier_key_validation_request_contexts + +class UnencryptedLogHash { + hash: field + length: field + counter: field +} +UnencryptedLogHash ..> UnencryptedLogHashContext: unencrypted_log_hashes\n->unencrypted_log_hash_contexts + +class EncryptedLogHash { + hash: field + length: field + counter: field + randomness: field +} +EncryptedLogHash ..> EncryptedLogHashContext: encrypted_log_hashes\n->encrypted_log_hash_contexts + +class EncryptedNotePreimageHash { + hash: field + length: field + counter: field + note_hash_counter: field +} +EncryptedNotePreimageHash ..> EncryptedNotePreimageHashContext: encrypted_note_preimage_hashes\n->encrypted_note_preimage_hash_contexts + + +class BlockHeader { + note_hash_tree_root: field + nullifier_tree_root: field + l1_to_l2_message_tree_root: field + public_data_tree_root: field + archive_tree_root: field + global_variables_hash: field +} + +class CallRequest { + call_stack_item_hash: field + caller_contract: AztecAddress + caller_context: CallerContext + counter_start: field + counter_end: field +} +CallerContext --* CallRequest : caller_context + +PrivateFunctionPublicInputs ..> CallRequest: private_call_stack_item_hash->call_stack_item_hash\npublic_call_stack_item_hash->call_stack_item_hash + +class CallerContext { + msg_sender: AztecAddress + storage_contract_address: AztecAddress + is_static_call: bool +} + + + +class NoteHashContext { + value: field + counter: field + nullifier_counter: field + contract_address: AztecAddress +} + +class NullifierContext { + value: field + counter: field + note_hash_counter: field + contract_address: AztecAddress +} + +class L2ToL1MessageContext { + value: field + portal_contract_address: AztecAddress + contract_address: AztecAddress +} + +class ReadRequestContext { + note_hash: field + counter: field + contract_address: AztecAddress +} + +class NullifierKeyValidationRequestContext { + public_key: field + secret_key: field + contract_address: AztecAddress +} + +class UnencryptedLogHashContext { + hash: field + length: field + counter: field + contract_address: AztecAddress +} + +class EncryptedLogHashContext { + hash: field + length: field + counter: field + contract_address: AztecAddress + randomness: field +} + +class EncryptedNotePreimageHashContext { + hash: field + length: field + counter: field + contract_address: AztecAddress + note_hash_counter: field +} + +class MembershipWitness { + leaf_index: field + sibling_path: List~field~ +} + +class ContractInstance { + version: u8 + deployer_address: AztecAddress + salt: field + contract_class_id: field + contract_args_hash: field + portal_contract_address: EthereumAddress + public_keys_hash: Field +} + +class TransientAccumulatedData { + note_hash_contexts: List~NoteHashContext~ + nullifier_contexts: List~NullifierContext~ + l2_to_l1_message_contexts: List~L2ToL1MessageContext~ + read_request_contexts: List~ReadRequestContext~ + nullifier_key_validation_request_contexts: List~NullifierKeyValidationRequestContext~ + unencrypted_log_hash_contexts: List~UnencryptedLogHashContext~ + encrypted_log_hash_contexts: List~EncryptedLogHashContext~ + encrypted_note_preimage_hash_contexts: List~EncryptedNotePreimageHashContext~ + private_call_requests: List~CallRequest~ + public_call_requests: List~CallRequest~ +} +NoteHashContext --* TransientAccumulatedData: note_hash_contexts +NullifierContext --* TransientAccumulatedData: nullifier_contexts +L2ToL1MessageContext --* TransientAccumulatedData: l2_to_l1_message_contexts +ReadRequestContext --* TransientAccumulatedData: read_request_contexts +NullifierKeyValidationRequestContext --* TransientAccumulatedData: nullifier_key_validation_request_contexts +UnencryptedLogHashContext --* TransientAccumulatedData: unencrypted_log_hash_contexts +EncryptedLogHashContext --* TransientAccumulatedData: encrypted_log_hash_contexts +EncryptedNotePreimageHashContext --* TransientAccumulatedData: encrypted_note_preimage_hash_contexts +CallRequest --* TransientAccumulatedData: private_call_requests +CallRequest --* TransientAccumulatedData: public_call_requests + +class ConstantData { + block_header: BlockHeader + tx_context: TransactionContext +} +BlockHeader ..> ConstantData: block_header + +class PublicInputs { + constant_data: ConstantData + transient_accumulated_data: TransientAccumulatedData +} +ConstantData --* PublicInputs : constant_data +TransientAccumulatedData --* PublicInputs: transient_accumulated_data + + + + + +%%========================================================================================================================================================= +%% EVERYTHING ABOVE THIS LINE SHOULD BE COPY-PASTED FROM THE DIAGRAM IN ../private-kernel-initial.mdx +%% EVERYTHING BELOW THIS LINE NEEDS TO BE EDITED IN LINE WITH PROTOCOL CHANGES. +%%========================================================================================================================================================= + + + + + + +%% You'll also need to modify the PrivateInputs class (way above) to include an extra item: `previous_kernel: PreviousKernel` + +PrivateInputs *.. PreviousKernel: previous_kernel + +class PreviousKernel { + public_inputs: PrivateKernelPublicInputs + proof: KernelProof (aka Proof) + vk: KernelVerificationKey (aka VerificationKey) + membership_witness: KernelVKMembershipWitness (aka MembershipWitness) +} +PreviousKernel *-- PrivateKernelPublicInputs: public_inputs\n(the same PublicInputs type \n as the public_inputs \n being output by this circuit) +PreviousKernel *-- KernelProof: proof +PreviousKernel *-- KernelVerificationKey: vk +PreviousKernel *-- KernelVKMembershipWitness: membership_witness +PreviousKernel *-- PublicInputs: public_inputs\nBEWARE, this line is just showing class\ndependency and the recursive nature of this circuit.\nThe "output" public_inputs of the PREVIOUS kernel iteration\n are "fed" as inputs into this kernel iteration. + +class KERNEL_VK_EXISTENCE_CHECK { + Check the vk + exists within the vk tree root +} +class KERNEL_PROOF_VERIFICATION { + Verify the kernel proof +} +KernelProof ..> KERNEL_PROOF_VERIFICATION +KernelVerificationKey ..> KERNEL_PROOF_VERIFICATION +PrivateKernelPublicInputs ..> KERNEL_PROOF_VERIFICATION + +KernelVerificationKey ..> KERNEL_VK_EXISTENCE_CHECK +KernelVKMembershipWitness ..> KERNEL_VK_EXISTENCE_CHECK + + +``` + +
+
+ +## Private Inputs + +### `PreviousKernel` + +Data of the previous kernel iteration. + + + + +| Field | Type | Description | +| -------------------- | ------------------------------------------------------------------------------- | -------------------------------------------- | +| `public_inputs` | [`InitialPrivateKernelPublicInputs`](./private-kernel-initial.mdx#publicinputs) | Public inputs of the proof. | +| `proof` | `Proof` | Proof of the kernel circuit. | +| `vk` | `VerificationKey` | Verification key of the kernel circuit. | +| `membership_witness` | [`MembershipWitness`](./private-kernel-initial.mdx#membershipwitness) | Membership witness for the verification key. | + +### `PrivateCall` + +The format aligns with the [`PrivateCall`](./private-kernel-initial.mdx#privatecall) of the initial private kernel circuit. + +## `PublicInputs` + +The format aligns with the [`Public Inputs`](./private-kernel-initial.mdx#publicinputs) of the initial private kernel circuit. diff --git a/yellow-paper/docs/circuits/private-kernel-reset.md b/yellow-paper/docs/circuits/private-kernel-reset.md index a5c6bff1cae..a9c4feb7b5f 100644 --- a/yellow-paper/docs/circuits/private-kernel-reset.md +++ b/yellow-paper/docs/circuits/private-kernel-reset.md @@ -1,159 +1,168 @@ # Private Kernel Circuit - Reset + + + + ## Requirements A **reset** circuit is designed to abstain from processing individual private function calls. Instead, it injects the outcomes of an initial, inner, or another reset private kernel circuit, scrutinizes the public inputs, and clears the verifiable data within its scope. A reset circuit can be executed either preceding the tail private kernel circuit, or as a means to "reset" public inputs at any point between two private kernels, allowing data to accumulate seamlessly in subsequent iterations. There are 2 variations of reset circuits: + + + - [Read Request Reset Private Kernel Circuit](#read-request-reset-private-kernel-circuit). -- [Nullifier Key Validation Request Reset Private Kernel Circuit](#nullifier-key-validation-request-reset-private-kernel-circuit). +- [Nullifier Key Validation Request Reset Private Kernel Circuit](#nullifier-key-validation-request-reset-private-kernel-circuit). - [Transient Note Reset Private Kernel Circuit](#transient-note-reset-private-kernel-circuit). The incorporation of these circuits not only enhances the modularity and repeatability of the "reset" process but also diminishes the overall workload. Rather than conducting resource-intensive computations such as membership checks in each iteration, these tasks are only performed as necessary within the reset circuits. ### Read Request Reset Private Kernel Circuit. -This reset circuit conducts verification on some or all accumulated read requests and subsequently removes them from the _read_request_contexts_ in the _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ within the _public_inputs_ of the [_previous_kernel_](#previouskernel). + + +This reset circuit conducts verification on some or all accumulated read requests and subsequently removes them from the [`read_request_contexts`](./private-kernel-initial.mdx/#readrequestcontext) in the [`transient_accumulated_data`](./private-kernel-initial.mdx#transientaccumulateddata) within the [`public_inputs`](./private-kernel-initial.mdx#publicinputs) of the [`previous_kernel`](#previouskernel). A read request can pertain to one of two note types: - A settled note: generated in a prior successful transaction and included in the note hash tree. - A pending note: created in the current transaction, not yet part of the note hash tree. -1. To clear read requests for settled notes, the circuit performs membership checks for the targeted read requests using the [hints](#hints-for-read-request-reset-private-kernel-circuit) provided via _private_inputs_. +1. To clear read requests for settled notes, the circuit performs membership checks for the targeted read requests using the [hints](#hints-for-read-request-reset-private-kernel-circuit) provided via `private_inputs`. - For each _persistent_read_index_ at index _i_ in _persistent_read_indices_: + For each `persistent_read_index` at index `i` in `persistent_read_indices`: - 1. If the _persistent_read_index_ equals the length of the _read_request_contexts_ array, there is no read request to be verified. Skip the rest. - 2. Locate the _read_request_: _`read_request_contexts[persistent_read_index]`_ + 1. If the `persistent_read_index` equals the length of the _read_request_contexts_ array, there is no read request to be verified. Skip the rest. + 2. Locate the `read_request`: `read_request_contexts[persistent_read_index]` 3. Perform a membership check on the note being read. Where: - - The leaf corresponds to the hash of the note: _`read_request.note_hash`_ - - The index and sibling path are in: _`read_request_membership_witnesses[i]`_. - - The root is the _note_hash_tree_root_ in the _[header](./private-function.md#header)_ within _[public_inputs](#public-inputs).[constant_data](./private-kernel-initial.md#constantdata)_. + - The leaf corresponds to the hash of the note: `read_request.note_hash` + - The index and sibling path are in: `read_request_membership_witnesses[i]`. + - The root is the `note_hash_tree_root` in the [block_header](./private-function.md#header) within [`public_inputs`](#public-inputs)[`.constant_data`](./private-kernel-initial.mdx#constantdata). - > Following the above process, at most _N_ read requests will be cleared, where _N_ is the length of the _persistent_read_indices_ array. It's worth noting that there can be multiple versions of this reset circuit, each with a different value of _N_. + > Following the above process, at most `N` read requests will be cleared, where `N` is the length of the `persistent_read_indices` array. It's worth noting that there can be multiple versions of this reset circuit, each with a different value of `N`. -2. To clear read requests for pending notes, the circuit ensures that the notes were created before the corresponding read operation, utilizing the [hints](#hints-for-read-request-reset-private-kernel-circuit) provided via _private_inputs_ +2. To clear read requests for pending notes, the circuit ensures that the notes were created before the corresponding read operation, utilizing the [hints](#hints-for-read-request-reset-private-kernel-circuit) provided via `private_inputs` - For each _transient_read_index_ at index _i_ in _transient_read_indices_: + For each `transient_read_index` at index `i` in `transient_read_indices`: - 1. If the _transient_read_index_ equals the length of the _read_request_contexts_ array, there is no read request to be verified. Skip the rest. - 2. Locate the _read_request_: _`read_request_contexts[transient_read_index]`_ - 3. Locate the _note_hash_ being read: _`note_hash_contexts[pending_note_indices[i]]`_ + 1. If the `transient_read_index` equals the length of the _read_request_contexts_ array, there is no read request to be verified. Skip the rest. + 2. Locate the `read_request`: `read_request_contexts[transient_read_index]` + 3. Locate the `note_hash` being read: `note_hash_contexts[pending_note_indices[i]]` 4. Verify the following: - - _`read_request.note_hash == note_hash.value`_ - - _`read_request.contract_address == note_hash.contract_address`_ - - _`read_request.counter > note_hash.counter`_ - - _`(read_request.counter < note_hash.nullifier_counter) | (note_hash.nullifier_counter == 0)`_ + - `read_request.note_hash == note_hash.value` + - `read_request.contract_address == note_hash.contract_address` + - `read_request.counter > note_hash.counter` + - `(read_request.counter < note_hash.nullifier_counter) | (note_hash.nullifier_counter == 0)` - > Given that a reset circuit can execute between two private kernel circuits, there's a possibility that a note is created in a nested execution and hasn't been added to the _public_inputs_. In such cases, the read request cannot be verified in the current reset circuit and must be processed in another reset circuit after the note has been included in the _public_inputs_. + > Given that a reset circuit can execute between two private kernel circuits, there's a possibility that a note is created in a nested execution and hasn't been added to the `public_inputs`. In such cases, the read request cannot be verified in the current reset circuit and must be processed in another reset circuit after the note has been included in the `public_inputs`. -3. This circuit then ensures that the read requests that haven't been verified should remain in the _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ within its _public_inputs_. +3. This circuit then ensures that the read requests that haven't been verified should remain in the [transient_accumulated_data](./private-kernel-initial.mdx#transientaccumulateddata) within its `public_inputs`. - For each _read_request_ at index _i_ in the _read_request_contexts_ within the _private_inputs_, find its _status_ at _`read_request_statuses[i]`_: + For each `read_request` at index `i` in the `read_request_contexts` within the `private_inputs`, find its `status` at `read_request_statuses[i]`: - - If _status.state == persistent_, _`i == persistent_read_indices[status.index]`_. - - If _status.state == transient_, _`i == transient_read_indices[status.index]`_. - - If _status.state == nada_, _`read_request == public_inputs.transient_accumulated_data.read_request_contexts[status.index]`_. + - If `status.state == persistent`, `i == persistent_read_indices[status.index]`. + - If `status.state == transient`, `i == transient_read_indices[status.index]`. + - If `status.state == nada`, `read_request == public_inputs.transient_accumulated_data.read_request_contexts[status.index]`. ### Nullifier Key Validation Request Reset Private Kernel Circuit. -This reset circuit validates the correct derivation of nullifier secret keys used in private functions, and subsequently removes them from the _nullifier_key_validation_request_contexts_ in the _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ within the _public_inputs_ of the [_previous_kernel_](#previouskernel). +This reset circuit validates the correct derivation of nullifier secret keys used in private functions, and subsequently removes them from the `nullifier_key_validation_request_contexts` in the [`transient_accumulated_data`](./private-kernel-initial.mdx#transientaccumulateddata) within the `public_inputs` of the [`previous_kernel`](#previouskernel). -Initialize _requests_kept_ to _0_. +Initialize `requests_kept` to `0`. -For each _request_ at index _i_ in _nullifier_key_validation_request_contexts_, locate the _master_secret_key_ at _`master_nullifier_secret_keys[i]`_, provided as [hints](#hints-for-nullifier-key-validation-request-reset-private-kernel-circuit) through _private_inputs_. +For each `request` at index `i` in `nullifier_key_validation_request_contexts`, locate the `master_secret_key` at `master_nullifier_secret_keys[i]`, provided as [hints](#hints-for-nullifier-key-validation-request-reset-private-kernel-circuit) through `private_inputs`. -1. If _`master_secret_key == 0`_, ensure the request remain within the _public_inputs_.: +1. If `master_secret_key == 0`, ensure the request remain within the `public_inputs`.: - - _`public_inputs.transient_accumulated_data.nullifier_key_validation_request_contexts[requests_kept] == request`_ - - Increase _requests_kept_ by _1_: _`requests_kept += 1`_ + - `public_inputs.transient_accumulated_data.nullifier_key_validation_request_contexts[requests_kept] == request` + - Increase `requests_kept` by 1: `requests_kept += 1` 2. Else: - - Verify that the public key is associated with the _master_secret_key_: - _`request.public_key == master_secret_key * G`_ + - Verify that the public key is associated with the `master_secret_key`: + `request.public_key == master_secret_key * G` - Verify that the secret key was correctly derived for the contract: - _`request.secret_key == hash(master_secret_key, request.contract_address)`_ + `request.secret_key == hash(master_secret_key, request.contract_address)` ### Transient Note Reset Private Kernel Circuit. In the event that a pending note is nullified within the same transaction, its note hash, nullifier, and all encrypted note preimage hashes can be removed from the public inputs. This not only avoids redundant data being broadcasted, but also frees up space for additional note hashes and nullifiers in the subsequent iterations. -1. Ensure that each note hash is either propagated to the _public_inputs_ or nullified in the same transaction. +1. Ensure that each note hash is either propagated to the `public_inputs` or nullified in the same transaction. - Initialize both _notes_kept_ and _notes_removed_ to _0_. + Initialize both `notes_kept` and `notes_removed` to `0`. - For each _note_hash_ at index _i_ in _note_hash_contexts_ within the _private_inputs_, find the index of its nullifer at _`transient_nullifier_indices[i]`_, provided as [hints](#hints-for-transient-note-reset-private-kernel-circuit): + For each `note_hash` at index `i` in `note_hash_contexts` within the `private_inputs`, find the index of its nullifer at `transient_nullifier_indices[i]`, provided as [hints](#hints-for-transient-note-reset-private-kernel-circuit): - - If _`transient_nullifier_indices[i] == nullifier_contexts.len()`_: - - Verify that the _note_hash_ remains within the _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ in the _public_inputs_: - _`note_hash == public_inputs.transient_accumulated_data.note_hash_contexts[notes_kept]`_ - - Increment _notes_kept_ by 1: _`notes_kept += 1`_ - - Else, locate the _nullifier_ at _`nullifier_contexts[transient_nullifier_indices[i]]`_: + - If `transient_nullifier_indices[i] == nullifier_contexts.len()`: + - Verify that the `note_hash` remains within the [transient_accumulated_data](./private-kernel-initial.mdx#transientaccumulateddata) in the `public_inputs`: + `note_hash == public_inputs.transient_accumulated_data.note_hash_contexts[notes_kept]` + - Increment `notes_kept` by 1: `notes_kept += 1` + - Else, locate the `nullifier` at `nullifier_contexts[transient_nullifier_indices[i]]`: - Verify that the nullifier is associated with the note: - - _`nullifier.contract_address == note_hash.contract_address`_ - - _`nullifier.note_hash_counter == note_hash.counter`_ - - _`nullifier.counter == note_hash.nullifier_counter`_ - - Increment _notes_removed_ by 1: _`notes_removed += 1`_ - - Ensure that an empty _note_hash_ is appended to the end of _note_hash_contexts_ in the _public_inputs_: - - _`public_inputs.transient_accumulated_data.note_hash_contexts[N - notes_removed].is_empty() == true`_ - - Where _N_ is the length of _note_hash_contexts_. + - `nullifier.contract_address == note_hash.contract_address` + - `nullifier.note_hash_counter == note_hash.counter` + - `nullifier.counter == note_hash.nullifier_counter` + - Increment `notes_removed` by 1: `notes_removed += 1` + - Ensure that an empty `note_hash` is appended to the end of `note_hash_contexts` in the `public_inputs`: + - `public_inputs.transient_accumulated_data.note_hash_contexts[N - notes_removed].is_empty() == true` + - Where `N` is the length of `note_hash_contexts`. - > Note that the check `nullifier.counter > note_hash.counter` is not necessary as the _nullifier_counter_ is assured to be greater than the counter of the note hash when [propagated](./private-kernel-initial.md#verifying-the-transient-accumulated-data) from either the initial or inner private kernel circuits. + > Note that the check `nullifier.counter > note_hash.counter` is not necessary as the `nullifier_counter` is assured to be greater than the counter of the note hash when [propagated](./private-kernel-initial.mdx#verifying-the-transient-accumulated-data) from either the initial or inner private kernel circuits. -2. Ensure that nullifiers not associated with note hashes removed in the previous step are retained within the _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ in the _public_inputs_. +2. Ensure that nullifiers not associated with note hashes removed in the previous step are retained within the [transient_accumulated_data](./private-kernel-initial.mdx#transientaccumulateddata) in the `public_inputs`. - Initialize both _nullifiers_kept_ and _nullifiers_removed_ to _0_. + Initialize both `nullifiers_kept` and `nullifiers_removed` to `0`. - For each _nullifier_ at index _i_ in the _nullifier_contexts_ within the _private_inputs_, find the index of its corresponding transient nullifier at _`nullifier_index_hints[i]`_, provided as [hints](#hints-for-transient-note-reset-private-kernel-circuit): + For each `nullifier` at index `i` in the `nullifier_contexts` within the `private_inputs`, find the index of its corresponding transient nullifier at `nullifier_index_hints[i]`, provided as [hints](#hints-for-transient-note-reset-private-kernel-circuit): - - If _`nullifier_index_hints[i] == transient_nullifier_indices.len()`_: - - Verify that the _nullifier_ remains within the _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ in the _public_inputs_: - _`nullifier == public_inputs.transient_accumulated_data.nullifier_contexts[nullifiers_kept]`_ - - Increment _nullifiers_kept_ by 1: _`nullifiers_kept += 1`_ - - Else, compute _transient_nullifier_index_ as _`transient_nullifier_indices[nullifier_index_hints[i]]`_: - - Verify that: _`transient_nullifier_index == i`_ - - Increment _nullifiers_removed_ by 1: _`nullifiers_removed += 1`_ - - Ensure that an empty _nullifier_ is appended to the end of _nullifier_contexts_ in the _public_inputs_: - - _`public_inputs.transient_accumulated_data.nullifier_contexts[N - nullifiers_removed].is_empty() == true`_ - - Where _N_ is the length of _nullifer_contexts_. + - If `nullifier_index_hints[i] == transient_nullifier_indices.len()`: + - Verify that the `nullifier` remains within the [`transient_accumulated_data`](./private-kernel-initial.mdx#transientaccumulateddata) in the `public_inputs`: + `nullifier == public_inputs.transient_accumulated_data.nullifier_contexts[nullifiers_kept]` + - Increment `nullifiers_kept` by 1: `nullifiers_kept += 1` + - Else, compute `transient_nullifier_index` as `transient_nullifier_indices[nullifier_index_hints[i]]`: + - Verify that: `transient_nullifier_index == i` + - Increment `nullifiers_removed` by 1: `nullifiers_removed += 1` + - Ensure that an empty `nullifier` is appended to the end of `nullifier_contexts` in the `public_inputs`: + - `public_inputs.transient_accumulated_data.nullifier_contexts[N - nullifiers_removed].is_empty() == true` + - Where `N` is the length of `nullifer_contexts`. After these steps, ensure that all nullifiers associated with transient note hashes have been identified and removed: - _`nullifiers_removed == notes_removed`_ + `nullifiers_removed == notes_removed` -3. Ensure that _encrypted_note_preimage_hashes_ not associated with note hashes removed in the previous step are retained within the _[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ in the _public_inputs_. +3. Ensure that `encrypted_note_preimage_hashes` not associated with note hashes removed in the previous step are retained within the `[transient_accumulated_data](./private-kernel-initial.mdx#transientaccumulateddata)` in the `public_inputs`. - Initialize both _hashes_kept_ and _hashes_removed_ to _0_. + Initialize both `hashes_kept` and `hashes_removed` to `0`. - For each _preimage_hash_ at index _i_ in the _encrypted_note_preimage_hash_contexts_ within the _private_inputs_, find the _index_hint_ of its corresponding hash within _public_inputs_ at _`encrypted_note_preimage_hash_index_hints[i]`_, provided as [hints](#hints-for-transient-note-reset-private-kernel-circuit): + For each `preimage_hash` at index `i` in the `encrypted_note_preimage_hash_contexts` within the `private_inputs`, find the `index_hint` of its corresponding hash within `public_inputs` at `encrypted_note_preimage_hash_index_hints[i]`, provided as [hints](#hints-for-transient-note-reset-private-kernel-circuit): - - If _`index_hint == encrypted_note_preimage_hash_contexts.len()`_: + - If `index_hint == encrypted_note_preimage_hash_contexts.len()`: - Ensure that the associated note hash is removed: - - Locate the _note_hash_ at _`private_inputs.transient_accumulated_data.note_hash_contexts[log_note_hash_hints[i]]`_. - - Verify that the _preimage_hash_ is associated with the _note_hash_: - - _`preimage_hash.note_hash_counter == note_hash.counter`_ - - _`preimage_hash.contract_address == note_hash.contract_address`_ - - Confirm that the _note_hash_ has a corresponding nullifier and has been removed in the first step of this section: - - _`transient_nullifier_indices[log_note_hash_hints[i]] != nullifier_contexts.len()`_ - - Increment _hashes_removed_ by 1: _`hashes_removed += 1`_ - - Ensure that an empty item is appended to the end of _encrypted_note_preimage_hash_contexts_ in the _public_inputs_: - - _`encrypted_note_preimage_hash_contexts[N - hashes_removed].is_empty() == true`_ - - Where _N_ is the length of _encrypted_note_preimage_hash_contexts_. - - Else, find the _mapped_preimage_hash_ at _`encrypted_note_preimage_hash_contexts[index_hint]`_ within _public_inputs_: - - Verify that the context is aggregated to the _public_inputs_ correctly: - - _`index_hint == hashes_kept`_ - - _`mapped_preimage_hash == preimage_hash`_ - - Ensure that the associated note hash is retained in the _public_inputs_: - - Locate the _note_hash_ at _`public_inputs.transient_accumulated_data.note_hash_contexts[log_note_hash_hints[i]]`_. - - Verify that the _preimage_hash_ is associated with the _note_hash_: - - _`preimage_hash.note_hash_counter == note_hash.counter`_ - - _`preimage_hash.contract_address == note_hash.contract_address`_ - - Increment _hashes_kept_ by 1: _`hashes_kept += 1`_ - -> Note that this reset process may not necessarily be applied to all transient notes at a time. In cases where a note will be read in a yet-to-be-processed nested execution, the transient note hash and its nullifier must be retained in the _public_inputs_. The reset can only occur in a later reset circuit after all associated read requests have been verified and cleared. + - Locate the `note_hash` at `private_inputs.transient_accumulated_data.note_hash_contexts[log_note_hash_hints[i]]`. + - Verify that the `preimage_hash` is associated with the `note_hash`: + - `preimage_hash.note_hash_counter == note_hash.counter` + - `preimage_hash.contract_address == note_hash.contract_address` + - Confirm that the `note_hash` has a corresponding nullifier and has been removed in the first step of this section: + - `transient_nullifier_indices[log_note_hash_hints[i]] != nullifier_contexts.len()` + - Increment `hashes_removed` by 1: `hashes_removed += 1` + - Ensure that an empty item is appended to the end of `encrypted_note_preimage_hash_contexts` in the `public_inputs`: + - `encrypted_note_preimage_hash_contexts[N - hashes_removed].is_empty() == true` + - Where `N` is the length of `encrypted_note_preimage_hash_contexts`. + - Else, find the `mapped_preimage_hash` at `encrypted_note_preimage_hash_contexts[index_hint]` within `public_inputs`: + - Verify that the context is aggregated to the `public_inputs` correctly: + - `index_hint == hashes_kept` + - `mapped_preimage_hash == preimage_hash` + - Ensure that the associated note hash is retained in the `public_inputs`: + - Locate the `note_hash` at `public_inputs.transient_accumulated_data.note_hash_contexts[log_note_hash_hints[i]]`. + - Verify that the `preimage_hash` is associated with the `note_hash`: + - `preimage_hash.note_hash_counter == note_hash.counter` + - `preimage_hash.contract_address == note_hash.contract_address` + - Increment `hashes_kept` by 1: `hashes_kept += 1` + +> Note that this reset process may not necessarily be applied to all transient notes at a time. In cases where a note will be read in a yet-to-be-processed nested execution, the transient note hash and its nullifier must be retained in the `public_inputs`. The reset can only occur in a later reset circuit after all associated read requests have been verified and cleared. ### Common Verifications @@ -161,79 +170,79 @@ Below are the verifications applicable to all reset circuits: #### Verifying the previous kernel proof. -It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel)_. +It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from [private_inputs](#private-inputs).[previous_kernel](#previouskernel). The preceding proof can be: -- [Initial private kernel proof](./private-kernel-initial.md). -- [Inner private kernel proof](./private-kernel-inner.md). +- [Initial private kernel proof](./private-kernel-initial.mdx). +- [Inner private kernel proof](./private-kernel-inner.mdx). - Reset private kernel proof. #### Verifying the accumulated data. -It ensures that the _accumulated_data_ in the _[public_inputs](#public-inputs)_ matches the _accumulated_data_ in _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.md#public-inputs)_. +It ensures that the `accumulated_data` in the `[public_inputs](#public-inputs)` matches the `accumulated_data` in [private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.mdx#public-inputs). #### Verifying the transient accumulated data. -The following must equal the corresponding arrays in _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.md#public-inputs).[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_: +The following must equal the corresponding arrays in [`private_inputs`](#private-inputs)[`.previous_kernel`](#previouskernel)[`.public_inputs`](./private-kernel-initial.mdx#public-inputs)[`.transient_accumulated_data`](./private-kernel-initial.mdx#transientaccumulateddata): -- _l2_to_l1_message_contexts_ -- _private_call_requests_ -- _public_call_requests_ +- `l2_to_l1_message_contexts` +- `private_call_requests` +- `public_call_requests` The following must remain the same for [read request reset private kernel circuit](#read-request-reset-private-kernel-circuit): -- _note_hash_contexts_ -- _nullifier_contexts_ +- `note_hash_contexts` +- `nullifier_contexts` The following must remain the same for [transient note reset private kernel circuit](#transient-note-reset-private-kernel-circuit): -- _read_request_contexts_ +- `read_request_contexts` #### Verifying the constant data. -This section follows the same [process](./private-kernel-inner.md#verifying-the-constant-data) as outlined in the inner private kernel circuit. +This section follows the same [process](./private-kernel-inner.mdx#verifying-the-constant-data) as outlined in the inner private kernel circuit. -## Private Inputs +## `PrivateInputs` -### _PreviousKernel_ +### `PreviousKernel` -The format aligns with the _[PreviousKernel](./private-kernel-inner.md#previouskernel)_ of the inner private kernel circuit. +The format aligns with the [`PreviousKernel`](./private-kernel-inner.mdx#previouskernel) of the inner private kernel circuit. ### _Hints_ for [Read Request Reset Private Kernel Circuit](#read-request-reset-private-kernel-circuit) -| Field | Type | Description | -| ----------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | -| _transient_read_indices_ | [_field_; _N_] | Indices of the read requests for transient notes. | -| _pending_note_indices_ | [_field_; _N_] | Indices of the note hash contexts for transient reads. | -| _persistent_read_indices_ | [_field_; _M_] | Indices of the read requests for settled notes. | -| _read_request_membership_witnesses_ | [_[MembershipWitness](./private-kernel-initial.md#membershipwitness)_; _M_] | Membership witnesses for the persistent reads. | -| _read_request_statuses_ | [_[ReadRequestStatus](#readrequeststatus)_; _C_] | Statuses of the read request contexts. _C_ equals the length of _read_request_contexts_. | +| Field | Type | Description | +| ----------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| `transient_read_indices` | `[field; N]` | Indices of the read requests for transient notes. | +| `pending_note_indices` | `[field; N]` | Indices of the note hash contexts for transient reads. | +| `persistent_read_indices` | `[field; M]` | Indices of the read requests for settled notes. | +| `read_request_membership_witnesses` | [`[MembershipWitness; M]`](./private-kernel-initial.mdx#membershipwitness) | Membership witnesses for the persistent reads. | +| `read_request_statuses` | [`[ReadRequestStatus; C]`](#readrequeststatus) | Statuses of the read request contexts. `C` equals the length of `read_request_contexts`. | -> There can be multiple versions of the read request reset private kernel circuit, each with a different values of _N_ and _M_. +> There can be multiple versions of the read request reset private kernel circuit, each with a different values of `N` and `M`. -#### _ReadRequestStatus_ +#### `ReadRequestStatus` -| Field | Type | Description | -| ------- | ------------------------------- | --------------------------------------- | -| _state_ | persistent \| transient \| nada | State of the read request. | -| _index_ | _field_ | Index of the hint for the read request. | +| Field | Type | Description | +| ------- | --------------------------------- | --------------------------------------- | +| `state` | `persistent \| transient \| nada` | State of the read request. | +| `index` | `field` | Index of the hint for the read request. | ### _Hints_ for [Nullifier Key Validation Request Reset Private Kernel Circuit](#nullifier-key-validation-request-reset-private-kernel-circuit) -| Field | Type | Description | -| ------------------------------ | -------------- | -------------------------------------------------------------------------------------------------------------------------- | -| _master_nullifier_secret_keys_ | [_field_; _C_] | Master nullifier secret keys for the nullifier keys. _C_ equals the length of _nullifier_key_validation_request_contexts_. | +| Field | Type | Description | +| ------------------------------ | ------------ | -------------------------------------------------------------------------------------------------------------------------- | +| `master_nullifier_secret_keys` | `[field; C]` | Master nullifier secret keys for the nullifier keys. `C` equals the length of `nullifier_key_validation_request_contexts`. | ### _Hints_ for [Transient Note Reset Private Kernel Circuit](#transient-note-reset-private-kernel-circuit) -| Field | Type | Description | -| ------------------------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -| _transient_nullifier_indices_ | [_field_; _C_] | Indices of the nullifiers for transient notes. _C_ equals the length of _note_hash_contexts_. | -| _nullifier_index_hints_ | [_field_; _C_] | Indices of the _transient_nullifier_indices_ for transient nullifiers. _C_ equals the length of _nullifier_contexts_. | -| _encrypted_note_preimage_hash_index_hints_ | [_field_; _C_] | Indices of the _encrypted_note_preimage_hash_contexts_ for transient preimage hashes. _C_ equals the length of _encrypted_note_preimage_hash_contexts_. | -| _log_note_hash_hints_ | [_field_; _C_] | Indices of the _note_hash_contexts_ for transient preimage hashes. _C_ equals the length of _note_hash_contexts_. | +| Field | Type | Description | +| ------------------------------------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `transient_nullifier_indices` | `[field; C]` | Indices of the nullifiers for transient notes. `C` equals the length of `note_hash_contexts`. | +| `nullifier_index_hints` | `[field; C]` | Indices of the `transient_nullifier_indices` for transient nullifiers. `C` equals the length of `nullifier_contexts`. | +| `encrypted_note_preimage_hash_index_hints` | `[field; C]` | Indices of the `encrypted_note_preimage_hash_contexts` for transient preimage hashes. `C` equals the length of `encrypted_note_preimage_hash_contexts`. | +| `log_note_hash_hints` | `[field; C]` | Indices of the `note_hash_contexts` for transient preimage hashes. `C` equals the length of `note_hash_contexts`. | -## Public Inputs +## `PublicInputs` -The format aligns with the _[Public Inputs](./private-kernel-initial.md#public-inputs)_ of the initial private kernel circuit. +The format aligns with the [`PublicInputs`](./private-kernel-initial.mdx#publicinputs) of the initial private kernel circuit. diff --git a/yellow-paper/docs/circuits/private-kernel-tail.md b/yellow-paper/docs/circuits/private-kernel-tail.md index 4f7b27cd676..cbfe8088a88 100644 --- a/yellow-paper/docs/circuits/private-kernel-tail.md +++ b/yellow-paper/docs/circuits/private-kernel-tail.md @@ -8,30 +8,30 @@ The **tail** circuit abstains from processing individual private function calls. #### Verifying the previous kernel proof. -It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel)_. +It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from [`private_inputs`](#private-inputs)[`.previous_kernel`](#previouskernel). The preceding proof can be: -- [Initial private kernel proof](./private-kernel-initial.md). -- [Inner private kernel proof](./private-kernel-inner.md). +- [Initial private kernel proof](./private-kernel-initial.mdx). +- [Inner private kernel proof](./private-kernel-inner.mdx). - [Reset private kernel proof](./private-kernel-reset.md). An inner iteration may be omitted when there's only a single private function call for the transaction. And a reset iteration can be skipped if there are no read requests and transient notes in the public inputs from the last iteration. #### Ensuring the previous iteration is the last. -It checks the data within _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.md#public-inputs).[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_ to ensure that no further private kernel iteration is needed. +It checks the data within [`private_inputs`](#private-inputs)[`.previous_kernel`](#previouskernel)[`.public_inputs`](./private-kernel-initial.mdx#public-inputs)[`.transient_accumulated_data`](./private-kernel-initial.mdx#transientaccumulateddata) to ensure that no further private kernel iteration is needed. 1. The following must be empty to ensure all the private function calls are processed: - - _private_call_requests_ + - `private_call_requests` 2. The following must be empty to ensure a comprehensive final reset: - - _read_requests_ - - _nullifier_key_validation_request_contexts_ - - The _nullifier_counter_ associated with each note hash in _note_hash_contexts_. - - The _note_hash_counter_ associated with each nullifier in _nullifier_contexts_. + - `read_requests` + - `nullifier_key_validation_request_contexts` + - The `nullifier_counter` associated with each note hash in `note_hash_contexts`. + - The `note_hash_counter` associated with each nullifier in `nullifier_contexts`. > A [reset iteration](./private-kernel-reset.md) should ideally precede this step. Although it doesn't have to be executed immediately before the tail circuit, as long as it effectively clears the specified values. @@ -41,52 +41,56 @@ It checks the data within _[private_inputs](#private-inputs).[previous_kernel](# Siloing a value with the address of the contract generating the value ensures that data produced by a contract is accurately attributed to the correct contract and cannot be misconstrued as data created in a different contract. This circuit guarantees the following siloed values: -1. Silo _nullifiers_: +1. Silo `nullifiers`: - For each _nullifier_ at index _i_ **> 0** in the _nullifier_contexts_ within _private_inputs_, if _`nullifier.value != 0`_: + For each `nullifier` at index `i > 0` in the `nullifier_contexts` within `private_inputs`, if `nullifier.value != 0`: - _`nullifier_contexts[i].value = hash(nullifier.contract_address, nullifier.value)`_ + `nullifier_contexts[i].value = hash(nullifier.contract_address, nullifier.value)` - > This process does not apply to _nullifier_contexts[0]_, which is the [hash of the transaction request](./private-kernel-initial.md#ensuring-transaction-uniqueness) created by the initial private kernel circuit. + > This process does not apply to `nullifier_contexts[0]`, which is the [hash of the transaction request](./private-kernel-initial.mdx#ensuring-transaction-uniqueness) created by the initial private kernel circuit. -2. Silo _note_hashes_: + - For each _note_hash_ at index _i_ in the _note_hash_contexts_ within _private_inputs_, if _`note_hash.value != 0`_: +2. Silo `note_hashes`: - _`note_hash_contexts[i].value = hash(nonce, siloed_hash)`_ + For each `note_hash` at index `i` in the `note_hash_contexts` within `private_inputs`, if `note_hash.value != 0`: + + `note_hash_contexts[i].value = hash(nonce, siloed_hash)` Where: - - _`nonce = hash(first_nullifier, index)`_ - - _`first_nullifier = nullifier_contexts[0].value`_. - - _`index = note_hash_hints[i]`_, which is the index of the same note hash within _public_inputs.note_hashes_. Where _note_hash_hints_ is provided as [hints](#hints) via _private_inputs_. - - _`siloed_hash = hash(note_hash.contract_address, note_hash.value)`_ + - `nonce = hash(first_nullifier, index)` + - `first_nullifier = nullifier_contexts[0].value`. + - `index = note_hash_hints[i]`, which is the index of the same note hash within `public_inputs.note_hashes`. Where `note_hash_hints` is provided as [hints](#hints) via `private_inputs`. + - `siloed_hash = hash(note_hash.contract_address, note_hash.value)` > Siloing with a nonce guarantees that each final note hash is a unique value in the note hash tree. -3. Verify the _l2_to_l1_messages_ within _[public_inputs](#public-inputs).[accumulated_data](./public-kernel-tail.md#accumulateddata)_: +3. Verify the `l2_to_l1_messages` within [`public_inputs`](#public-inputs)[`.accumulated_data`](./public-kernel-tail.md#accumulateddata): - For each _l2_to_l1_message_ at index _i_ in _l2_to_l1_message_contexts_ within _[private_inputs](#private-inputs).[previous_kernel](./private-kernel-inner.md#previouskernel).[public_inputs](./private-kernel-initial.md#private-inputs).[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_: + For each `l2_to_l1_message` at index `i` in `l2_to_l1_message_contexts` within [`private_inputs`](#private-inputs)[`.previous_kernel`](./private-kernel-inner.mdx#previouskernel)[`.public_inputs`](./private-kernel-initial.mdx#private-inputs)[`.transient_accumulated_data`](./private-kernel-initial.mdx#transientaccumulateddata): - - If _l2_to_l1_message.value == 0_: - - Verify that _`l2_to_l1_messages[i] == 0`_ + - If `l2_to_l1_message.value == 0`: + - Verify that `l2_to_l1_messages[i] == 0` - Else: - - Verify that _`l2_to_l1_messages[i] == hash(l2_to_l1_message.contract_address, version_id, l2_to_l1_message.portal_contract_address, chain_id, l2_to_l1_message.value)`_ - - Where _version_id_ and _chain_id_ are defined in _[public_inputs](#public-inputs).[constant_data](./private-kernel-initial.md#constantdata).[tx_context](./private-kernel-initial.md#transactioncontext)_. + - Verify that `l2_to_l1_messages[i] == hash(l2_to_l1_message.contract_address, version_id, l2_to_l1_message.portal_contract_address, chain_id, l2_to_l1_message.value)` + - Where `version_id` and `chain_id` are defined in [`public_inputs`](#public-inputs)[`.constant_data`](./private-kernel-initial.mdx#constantdata)[`.tx_context`](./private-kernel-initial.mdx#transactioncontext). + +4. Silo `unencrypted_log_hashes`: -4. Silo _unencrypted_log_hashes_: + For each `log_hash` at index `i` in the `unencrypted_log_hash_contexts` within `private_inputs`, if `log_hash.hash != 0`: - For each _log_hash_ at index _i_ in the _unencrypted_log_hash_contexts_ within _private_inputs_, if _`log_hash.hash != 0`_: + `unencrypted_log_hash_contexts[i].value = hash(log_hash.hash, log_hash.contract_address)` - _`unencrypted_log_hash_contexts[i].value = hash(log_hash.hash, log_hash.contract_address)`_ +5. Silo `encrypted_log_hashes`: -5. Silo _encrypted_log_hashes_: + For each `log_hash` at index `i` in the `encrypted_log_hash_contexts` within `private_inputs`, if `log_hash.hash != 0`: - For each _log_hash_ at index _i_ in the _encrypted_log_hash_contexts_ within _private_inputs_, if _`log_hash.hash != 0`_: + `encrypted_log_hash_contexts[i].value = hash(log_hash.hash, contract_address_tag)` - _`encrypted_log_hash_contexts[i].value = hash(log_hash.hash, contract_address_tag)`_ + Where `contract_address_tag = hash(log_hash.contract_address, log_hash.randomness)` - Where _`contract_address_tag = hash(log_hash.contract_address, log_hash.randomness)`_ + #### Verifying ordered arrays. @@ -94,62 +98,62 @@ The initial and inner kernel iterations may produce values in an unordered state This circuit ensures the correct ordering of the following arrays: -- _note_hashes_ -- _nullifiers_ -- _public_call_requests_ -- _ordered_unencrypted_log_hashes_ -- _ordered_encrypted_log_hashes_ -- _ordered_encrypted_note_preimage_hashes_ +- `note_hashes` +- `nullifiers` +- `public_call_requests` +- `ordered_unencrypted_log_hashes` +- `ordered_encrypted_log_hashes` +- `ordered_encrypted_note_preimage_hashes` Where: -- _note_hashes_, _nullifiers_, and _public_call_requests_ are within _[public_inputs](#public-inputs).[accumulated_data](./public-kernel-tail.md#accumulateddata)_. -- _ordered_unencrypted_log_hashes_, _ordered_encrypted_log_hashes_, and _ordered_encrypted_note_preimage_hashes_ are provided as hints through _private_inputs_. -- Every corresponding unordered array for each of the ordered array is sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./private-kernel-initial.md#public-inputs).[transient_accumulated_data](./private-kernel-initial.md#transientaccumulateddata)_. +- `note_hashes`, `nullifiers`, and `public_call_requests` are within [`public_inputs`](#public-inputs)[`.accumulated_data`](./public-kernel-tail.md#accumulateddata). +- `ordered_unencrypted_log_hashes`, `ordered_encrypted_log_hashes`, and `ordered_encrypted_note_preimage_hashes` are provided as hints through `private_inputs`. +- Every corresponding unordered array for each of the ordered array is sourced from [`private_inputs`](#private-inputs)[`.previous_kernel`](#previouskernel)[`.public_inputs`](./private-kernel-initial.mdx#public-inputs)[`.transient_accumulated_data`](./private-kernel-initial.mdx#transientaccumulateddata). -1. Verify ordered _public_call_requests_: +1. Verify ordered `public_call_requests`: - For each _request_ at index _i_ in _`private_inputs.previous_kernel.public_inputs.transient_accumulated_data.public_call_requests[i]`_, the associated _mapped_request_ is at _`public_call_requests[public_call_request_hints[i]]`_ within _public_inputs_. + For each `request` at index `i` in `private_inputs.previous_kernel.public_inputs.transient_accumulated_data.public_call_requests[i]`, the associated `mapped_request` is at `public_call_requests[public_call_request_hints[i]]` within `public_inputs`. - - If _`request.hash != 0`_, verify that: - - _`request.hash == mapped_request.hash`_ - - _`request.caller_contract == mapped_request.caller_contract`_ - - _`request.caller_context == mapped_request.caller_context`_ - - If _i > 0_, verify that: - - _`mapped_request[i].counter < mapped_request[i - 1].counter`_ + - If `request.hash != 0`, verify that: + - `request.hash == mapped_request.hash` + - `request.caller_contract == mapped_request.caller_contract` + - `request.caller_context == mapped_request.caller_context` + - If `i > 0`, verify that: + - `mapped_request[i].counter < mapped_request[i - 1].counter` - Else: - - All the subsequent requests (_index >= i_) in both _public_call_requests_ and _unordered_requests_ must be empty. + - All the subsequent requests (_index >= i_) in both `public_call_requests` and `unordered_requests` must be empty. - > Note that _public_call_requests_ must be arranged in descending order to ensure the calls are executed in chronological order. + > Note that `public_call_requests` must be arranged in descending order to ensure the calls are executed in chronological order. 2. Verify the rest of the ordered arrays: - For each _note_hash_context_ at index _i_ in the **unordered** _note_hash_contexts_ within _private_inputs_, the associated _note_hash_ is at _`note_hashes[note_hash_hints[i]]`_. + For each `note_hash_context` at index `i` in the **unordered** `note_hash_contexts` within `private_inputs`, the associated `note_hash` is at `note_hashes[note_hash_hints[i]]`. - - If _`note_hash != 0`_, verify that: - - _`note_hash == note_hash_context.value`_ - - If _i > 0_, verify that: - - _`note_hashes[i].counter > note_hashes[i - 1].counter`_ + - If `note_hash != 0`, verify that: + - `note_hash == note_hash_context.value` + - If `i > 0`, verify that: + - `note_hashes[i].counter > note_hashes[i - 1].counter` - Else: - - All the subsequent items (_index >= i_) in both _note_hashes_ and _note_hash_contexts_ must be empty. + - All the subsequent items (index `>= i`) in both `note_hashes` and `note_hash_contexts` must be empty. - Repeat the same process for _nullifiers_, _ordered_unencrypted_log_hashes_, _ordered_encrypted_log_hashes_, and _ordered_encrypted_note_preimage_hashes_. + Repeat the same process for `nullifiers`, `ordered_unencrypted_log_hashes`, `ordered_encrypted_log_hashes`, and `ordered_encrypted_note_preimage_hashes`. > While ordering could occur gradually in each kernel iteration, the implementation is much simpler and **typically** more efficient to be done once in the tail circuit. #### Recalibrating counters. -While the _counter_start_ of a _public_call_request_ is initially assigned in the private function circuit to ensure proper ordering within the transaction, it should be modified in this step. As using _counter_start_ values obtained from private function circuits may leak information. +While the `counter_start` of a `public_call_request` is initially assigned in the private function circuit to ensure proper ordering within the transaction, it should be modified in this step. As using `counter_start` values obtained from private function circuits may leak information. -The _counter_start_ in the _public_call_requests_ within _public_inputs_ should have been recalibrated. This circuit validates the values through the following checks: +The `counter_start` in the `public_call_requests` within `public_inputs` should have been recalibrated. This circuit validates the values through the following checks: -- The _counter_start_ of the non-empty requests are continuous values in descending order: - - _`public_call_requests[i].counter_start == public_call_requests[i + 1].counter_start + 1`_ -- The _counter_start_ of the last non-empty request must be _1_. +- The `counter_start` of the non-empty requests are continuous values in descending order: + - `public_call_requests[i].counter_start == public_call_requests[i + 1].counter_start + 1` +- The `counter_start` of the last non-empty request must be `1`. -> It's crucial for the _counter_start_ of the last request to be _1_, as it's assumed in the [tail public kernel circuit](./public-kernel-tail.md#grouping-storage-writes) that no storage writes have a counter _1_. +> It's crucial for the `counter_start` of the last request to be `1`, as it's assumed in the [tail public kernel circuit](./public-kernel-tail.md#grouping-storage-writes) that no storage writes have a counter `1`. -> The _counter_end_ for a public call request is determined by the overall count of call requests, reads and writes, note hashes and nullifiers within its scope, including those nested within its child function executions. This calculation will be performed by the sequencer for the executions of public function calls. +> The `counter_end` for a public call request is determined by the overall count of call requests, reads and writes, note hashes and nullifiers within its scope, including those nested within its child function executions. This calculation will be performed by the sequencer for the executions of public function calls. ### Validating Public Inputs @@ -157,65 +161,65 @@ The _counter_start_ in the _public_call_requests_ within _public_inputs_ should 1. The following must align with the results after siloing, as verified in a [previous step](#siloing-values): - - _l2_to_l1_messages_ + - `l2_to_l1_messages` 2. The following must align with the results after ordering, as verified in a [previous step](#verifying-ordered-arrays): - - _note_hashes_ - - _nullifiers_ + - `note_hashes` + - `nullifiers` 3. The hashes and lengths for all logs are accumulated as follows: - For each non-empty _log_hash_ at index _i_ in _ordered_unencrypted_log_hashes_, which is provided as [hints](#hints), and the [ordering](#verifying-ordered-arrays) was verified against the [siloed hashes](#siloing-values) in previous steps: + For each non-empty `log_hash` at index `i` in `ordered_unencrypted_log_hashes`, which is provided as [hints](#hints), and the [ordering](#verifying-ordered-arrays) was verified against the [siloed hashes](#siloing-values) in previous steps: - - _`accumulated_logs_hash = hash(accumulated_logs_hash, log_hash.hash)`_ - - If _i == 0_: _`accumulated_logs_hash = log_hash.hash`_ - - _`accumulated_logs_length += log_hash.length`_ + - `accumulated_logs_hash = hash(accumulated_logs_hash, log_hash.hash)` + - If `i == 0`: `accumulated_logs_hash = log_hash.hash` + - `accumulated_logs_length += log_hash.length` - Check the values in the _public_inputs_ are correct: + Check the values in the `public_inputs` are correct: - - _`unencrypted_logs_hash == accumulated_logs_hash`_ - - _`unencrypted_log_preimages_length == accumulated_logs_length`_ + - `unencrypted_logs_hash == accumulated_logs_hash` + - `unencrypted_log_preimages_length == accumulated_logs_length` - Repeat the same process for _encrypted_logs_hash_, _encrypted_log_preimages_length_, _encrypted_note_preimages_hash_ and _encrypted_note_preimages_length_. + Repeat the same process for `encrypted_logs_hash`, `encrypted_log_preimages_length`, `encrypted_note_preimages_hash` and `encrypted_note_preimages_length`. 4. The following must be empty: - - _old_public_data_tree_snapshot_ - - _new_public_data_tree_snapshot_ + - `old_public_data_tree_snapshot` + - `new_public_data_tree_snapshot` #### Verifying the transient accumulated data. -It ensures that all data in the _[transient_accumulated_data](./public-kernel-tail.md#transientaccumulateddata)_ within _[public_inputs](#public-inputs)_ is empty, with the exception of the _public_call_requests_. +It ensures that all data in the [`transient_accumulated_data`](./public-kernel-tail.md#transientaccumulateddata) within [`public_inputs`](#public-inputs) is empty, with the exception of the `public_call_requests`. -The _public_call_requests_ must [adhere to a specific order](#verifying-ordered-arrays) with [recalibrated counters](#recalibrating-counters), as verified in the previous steps. +The `public_call_requests` must [adhere to a specific order](#verifying-ordered-arrays) with [recalibrated counters](#recalibrating-counters), as verified in the previous steps. #### Verifying the constant data. -This section follows the same [process](./private-kernel-inner.md#verifying-the-constant-data) as outlined in the inner private kernel circuit. +This section follows the same [process](./private-kernel-inner.mdx#verifying-the-constant-data) as outlined in the inner private kernel circuit. -## Private Inputs +## `PrivateInputs` -### _PreviousKernel_ +### `PreviousKernel` -The format aligns with the _[PreviousKernel](./private-kernel-inner.md#previouskernel)_ of the inner private kernel circuit. +The format aligns with the [PreviousKernel](./private-kernel-inner.mdx#previouskernel) of the inner private kernel circuit. ### _Hints_ Data that aids in the verifications carried out in this circuit: -| Field | Type | Description | -| ---------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| _note_hash_hints_ | [_field_; _C_] | Indices of ordered _note_hashes_ for _note_hash_contexts_. _C_ equals the length of _note_hash_contexts_. | -| _nullifier_hints_ | [_field_; _C_] | Indices of ordered _nullifiers_ for _nullifier_contexts_. _C_ equals the length of _nullifier_contexts_. | -| _public_call_request_hints_ | [_field_; _C_] | Indices of ordered _public_call_requests_ for _public_call_requests_. _C_ equals the length of _public_call_requests_. | -| _ordered_unencrypted_log_hashes_ | [_field_; _C_] | Ordered _unencrypted_log_hashes_. _C_ equals the length of _unencrypted_log_hash_contexts_. | -| _unencrypted_log_hash_hints_ | [_field_; _C_] | Indices of _ordered_unencrypted_log_hashes_ for _unencrypted_log_hash_contexts_. _C_ equals the length of _unencrypted_log_hash_contexts_. | -| _ordered_encrypted_log_hashes_ | [_field_; _C_] | Ordered _encrypted_log_hashes_. _C_ equals the length of _encrypted_log_hash_contexts_. | -| _encrypted_log_hash_hints_ | [_field_; _C_] | Indices of _ordered_encrypted_log_hashes_ for _encrypted_log_hash_contexts_. _C_ equals the length of _encrypted_log_hash_contexts_. | -| _ordered_encrypted_note_preimage_hashes_ | [_field_; _C_] | Ordered _encrypted_note_preimage_hashes_. _C_ equals the length of _encrypted_note_preimage_hash_contexts_. | -| _encrypted_note_preimage_hints_ | [_field_; _C_] | Indices of _ordered_encrypted_note_preimage_hashes_ for _encrypted_note_preimage_hash_contexts_. _C_ equals the length of _encrypted_note_preimage_hash_contexts_. | - -## Public Inputs - -The format aligns with the _[Public Inputs](./public-kernel-tail.md#public-inputs)_ of the tail public kernel circuit. +| Field | Type | Description | +| ---------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `note_hash_hints` | `[field; C]` | Indices of ordered `note_hashes` for `note_hash_contexts`. `C` equals the length of `note_hash_contexts`. | +| `nullifier_hints` | `[field; C]` | Indices of ordered `nullifiers` for `nullifier_contexts`. `C` equals the length of `nullifier_contexts`. | +| `public_call_request_hints` | `[field; C]` | Indices of ordered `public_call_requests` for `public_call_requests`. `C` equals the length of `public_call_requests`. | +| `ordered_unencrypted_log_hashes` | `[field; C]` | Ordered `unencrypted_log_hashes`. `C` equals the length of `unencrypted_log_hash_contexts`. | +| `unencrypted_log_hash_hints` | `[field; C]` | Indices of `ordered_unencrypted_log_hashes` for `unencrypted_log_hash_contexts`. `C` equals the length of `unencrypted_log_hash_contexts`. | +| `ordered_encrypted_log_hashes` | `[field; C]` | Ordered `encrypted_log_hashes`. `C` equals the length of `encrypted_log_hash_contexts`. | +| `encrypted_log_hash_hints` | `[field; C]` | Indices of `ordered_encrypted_log_hashes` for `encrypted_log_hash_contexts`. `C` equals the length of `encrypted_log_hash_contexts`. | +| `ordered_encrypted_note_preimage_hashes` | `[field; C]` | Ordered `encrypted_note_preimage_hashes`. `C` equals the length of `encrypted_note_preimage_hash_contexts`. | +| `encrypted_note_preimage_hints` | `[field; C]` | Indices of `ordered_encrypted_note_preimage_hashes` for `encrypted_note_preimage_hash_contexts`. `C` equals the length of `encrypted_note_preimage_hash_contexts`. | + +## `PublicInputs` + +The format aligns with the [Public Inputs](./public-kernel-tail.md#public-inputs) of the tail public kernel circuit. diff --git a/yellow-paper/docs/circuits/public-kernel-initial.md b/yellow-paper/docs/circuits/public-kernel-initial.md index be814bed9af..d813922e9b1 100644 --- a/yellow-paper/docs/circuits/public-kernel-initial.md +++ b/yellow-paper/docs/circuits/public-kernel-initial.md @@ -1,5 +1,9 @@ # Public Kernel Circuit - Initial +:::Danger +The public kernel circuits are being redesigned to accommodate the latest AVM designs. This page is therefore highly likely to change significantly. +::: + ## Requirements The **initial** public kernel iteration undergoes processes to prepare the necessary data for the executions of the public function calls. @@ -8,7 +12,7 @@ The **initial** public kernel iteration undergoes processes to prepare the neces #### Verifying the previous kernel proof. -It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel)_. +It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from [`private_inputs`](#private-inputs)[`.previous_kernel`](#previouskernel). The preceding proof can only be: @@ -20,38 +24,38 @@ The preceding proof can only be: While the counters outputted from the tail private kernel circuit preserve the correct ordering of the _public_call_requests_, they do not reflect the actual number of side effects each public call entails. This circuit allows the recalibration of counters for _public_call_requests_, ensuring subsequent public kernels can be executed with the correct counter range. -For each _request_ at index _i_ in the _public_call_requests_ within _[public_inputs](#public-inputs).[transient_accumulated_data](./public-kernel-tail.md#transientaccumulateddata)_: +For each _request_ at index _i_ in the _public_call_requests_ within [`public_inputs`](#public-inputs).[`.transient_accumulated_data`](./public-kernel-tail.md#transientaccumulateddata): 1. Its hash must match the corresponding item in the _public_call_requests_ within the previous kernel's public inputs: - - _`request.hash == private_inputs.previous_kernel_public_inputs.public_call_requests[i].hash`_ -2. Its _counter_end_ must be greater than its _counter_start_. -3. Its _counter_start_ must be greater than the _counter_end_ of the item at index _i + 1_. -4. If it's the last item, its _counter_start_ must be _1_. + - `request.hash == private_inputs.previous_kernel_public_inputs.public_call_requests[i].hash` +2. Its `counter_end` must be greater than its `counter_start`. +3. Its `counter_start` must be greater than the `counter_end` of the item at index `i + 1`. +4. If it's the last item, its `counter_start` must be `1`. -> It's crucial for the _counter_start_ of the last item to be _1_, as it's assumed in the [tail public kernel circuit](./public-kernel-tail.md#grouping-storage-writes) that no storage writes have a counter _1_. +> It's crucial for the `counter_start` of the last item to be `1`, as it's assumed in the [tail public kernel circuit](./public-kernel-tail.md#grouping-storage-writes) that no storage writes have a counter `1`. ### Validating Public Inputs #### Verifying the accumulated data. -It ensures that the _accumulated_data_ in the _[public_inputs](#public-inputs)_ matches the _accumulated_data_ in _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./public-kernel-tail.md#public-inputs)_. +It ensures that the `accumulated_data` in the [`public_inputs`](#public-inputs) matches the `accumulated_data` in [`private_inputs`](#private-inputs)[`.previous_kernel`](#previouskernel)[`.public_inputs`](./public-kernel-tail.md#public-inputs). #### Verifying the transient accumulated data. -It ensures that all data in the _[transient_accumulated_data](./public-kernel-tail.md#transientaccumulateddata)_ within _[public_inputs](#public-inputs)_ is empty, with the exception of the _public_call_requests_. +It ensures that all data in the [`transient_accumulated_data`](./public-kernel-tail.md#transientaccumulateddata) within [`public_inputs`](#public-inputs) is empty, with the exception of the `public_call_requests`. -The values in _public_call_requests_ are verified in a [previous step](#recalibrating-counters). +The values in `public_call_requests` are verified in a [previous step](#recalibrating-counters). #### Verifying the constant data. -This section follows the same [process](./private-kernel-inner.md#verifying-the-constant-data) as outlined in the inner private kernel circuit. +This section follows the same [process](./private-kernel-inner.mdx#verifying-the-constant-data) as outlined in the inner private kernel circuit. -## Private Inputs +## `PrivateInputs` -### _PreviousKernel_ +### `PreviousKernel` -The format aligns with the _[PreviousKernel](./private-kernel-tail.md#previouskernel)_ of the tail public kernel circuit. +The format aligns with the [PreviousKernel](./private-kernel-tail.md#previouskernel)` of the tail public kernel circuit. -## Public Inputs +## `PublicInputs` -The format aligns with the _[Public Inputs](./public-kernel-tail.md#public-inputs)_ of the tail public kernel circuit. +The format aligns with the [`PublicInputs`](./public-kernel-tail.md#public-inputs)` of the tail public kernel circuit. diff --git a/yellow-paper/docs/circuits/public-kernel-inner.md b/yellow-paper/docs/circuits/public-kernel-inner.md index 1bf0af700c8..887260c52ce 100644 --- a/yellow-paper/docs/circuits/public-kernel-inner.md +++ b/yellow-paper/docs/circuits/public-kernel-inner.md @@ -1,5 +1,9 @@ # Public Kernel Circuit - Inner +:::Danger +The public kernel circuits are being redesigned to accommodate the latest AVM designs. This page is therefore highly likely to change significantly. +::: + ## Requirements In the public kernel iteration, the process involves taking a previous iteration and public call data, verifying their integrity, and preparing the necessary data for subsequent circuits to operate. @@ -8,7 +12,7 @@ In the public kernel iteration, the process involves taking a previous iteration #### Verifying the previous kernel proof. -It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel)_. +It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from [private_inputs](#private-inputs).[previous_kernel](#previouskernel). The preceding proof can be: @@ -19,45 +23,45 @@ The preceding proof can be: #### Ensuring the function being called exists in the contract. -This section follows the same [process](./private-kernel-initial.md#ensuring-the-function-being-called-exists-in-the-contract) as outlined in the initial private kernel circuit. +This section follows the same [process](./private-kernel-initial.mdx#ensuring-the-function-being-called-exists-in-the-contract) as outlined in the initial private kernel circuit. #### Ensuring the contract instance being called is deployed. It verifies the public deployment of the contract instance by conducting a membership proof, where: -- The leaf is a nullifier emitting from the deployer contract, computed as _`hash(deployer_address, contract_address)`_, where: - - _deployer_address_ is defined in _[private_inputs](#private-inputs).[public_call](#publiccall).[contract_data](../contract-deployment/instances.md#structure)_. - - _contract_data_ is defined in _[private_inputs](#private-inputs).[public_call](#publiccall).[call_stack_item](#publiccallstackitem)_. -- The index and sibling path are provided in _contract_deployment_membership_witness_ through _[private_inputs](#private-inputs).[public_call](#publiccall)_. -- The root is the _nullifier_tree_root_ in the _[header](./private-function.md#header)_ within _[public_inputs](#public-inputs).[constant_data](./private-kernel-initial.md#constantdata)_. +- The leaf is a nullifier emitting from the deployer contract, computed as `hash(deployer_address, contract_address)`, where: + - `deployer_address` is defined in [`private_inputs`](#private-inputs)[`.public_call`](#publiccall)[`.contract_data`](../contract-deployment/instances.md#structure). + - `contract_data` is defined in [`private_inputs`](#private-inputs)[`.public_call`](#publiccall)[`.call_stack_item`](#publiccallstackitem). +- The index and sibling path are provided in `contract_deployment_membership_witness` through [`private_inputs`](#private-inputs)[`.public_call`](#publiccall)\_. +- The root is the `nullifier_tree_root` in the [`header`](./private-function.md#header) within [`public_inputs`](#public-inputs)[`.constant_data`](./private-kernel-initial.mdx#constantdata). #### Ensuring the function is legitimate: -For the _[function_data](./private-kernel-initial.md#functiondata)_ in _[public_call](#publiccall).[call_stack_item](#publiccallstackitem)_, this circuit verifies that: +For the [`function_data`](./private-kernel-initial.mdx#functiondata) in [`public_call`](#publiccall)[`.call_stack_item`](#publiccallstackitem), this circuit verifies that: - It must be a public function: - - _`function_data.function_type == public`_ + - `function_data.function_type == public` #### Ensuring the current call matches the call request. -The top item in the _public_call_requests_ of the _[previous_kernel](#previouskernel)_ must pertain to the current function call. +The top item in the `public_call_requests` of the [`previous_kernel`](#previouskernel) must pertain to the current function call. This circuit will: 1. Pop the request from the stack: - - _`call_request = previous_kernel.public_inputs.transient_accumulated_data.public_call_requests.pop()`_ + - `call_request = previous_kernel.public_inputs.transient_accumulated_data.public_call_requests.pop()` 2. Compare the hash with that of the current function call: - - _`call_request.hash == public_call.call_stack_item.hash()`_ - - The hash of the _call_stack_item_ is computed as: - - _`hash(contract_address, function_data.hash(), public_inputs.hash(), counter_start, counter_end)`_ - - Where _function_data.hash()_ and _public_inputs.hash()_ are the hashes of the serialized field elements. + - `call_request.hash == public_call.call_stack_item.hash()` + - The hash of the `call_stack_item` is computed as: + - `hash(contract_address, function_data.hash(), public_inputs.hash(), counter_start, counter_end)` + - Where `function_data.hash()` and `public_inputs.hash()` are the hashes of the serialized field elements. #### Ensuring this function is called with the correct context. -This section follows the same [process](./private-kernel-inner.md#ensuring-this-function-is-called-with-the-correct-context) as outlined in the inner private kernel circuit. +This section follows the same [process](./private-kernel-inner.mdx#ensuring-this-function-is-called-with-the-correct-context) as outlined in the inner private kernel circuit. #### Verifying the public function proof. @@ -65,112 +69,112 @@ It verifies that the public function was executed with the provided proof data, #### Verifying the public inputs of the public function circuit. -It ensures the public function's intention by checking the following in _[public_call](#publiccall).[call_stack_item](#publiccallstackitem).[public_inputs](#publicfunctionpublicinputs)_: +It ensures the public function's intention by checking the following in [`public_call`](#publiccall)[`.call_stack_item`](#publiccallstackitem)[`.public_inputs`](#publicfunctionpublicinputs): -- The _header_ must match the one in the _[constant_data](./private-kernel-initial.md#constantdata)_. -- If it is a static call (_`public_inputs.call_context.is_static_call == true`_), it ensures that the function does not induce any state changes by verifying that the following arrays are empty: - - _note_hashes_ - - _nullifiers_ - - _l2_to_l1_messages_ - - _storage_writes_ - - _unencrypted_log_hashes_ +- The `header` must match the one in the [`constant_data`](./private-kernel-initial.mdx#constantdata). +- If it is a static call (`public_inputs.call_context.is_static_call == true`), it ensures that the function does not induce any state changes by verifying that the following arrays are empty: + - `note_hashes` + - `nullifiers` + - `l2_to_l1_messages` + - `storage_writes` + - `unencrypted_log_hashes` #### Verifying the counters. It verifies that each value listed below is associated with a legitimate counter. -1. For the _[call_stack_item](#privatecallstackitem)_: +1. For the [`call_stack_item`](#privatecallstackitem): - - The _counter_start_ and _counter_end_ must match those in the _call_request_ [popped](#ensuring-the-current-call-matches-the-call-request) from the _public_call_requests_ in a previous step. + - The `counter_start` and `counter_end` must match those in the `call_request` [popped](#ensuring-the-current-call-matches-the-call-request) from the `public_call_requests` in a previous step. -2. For items in each ordered array in _[call_stack_item](#publiccallstackitem).[public_inputs](#publicfunctionpublicinputs)_: +2. For items in each ordered array in [`call_stack_item`](#publiccallstackitem)[`.public_inputs`](#publicfunctionpublicinputs): - - The counter of the first item must be greater than the _counter_start_ of the current call. + - The counter of the first item must be greater than the `counter_start` of the current call. - The counter of each subsequent item must be greater than the counter of the previous item. - - The counter of the last item must be less than the _counter_end_ of the current call. + - The counter of the last item must be less than the `counter_end` of the current call. The ordered arrays include: - - _storage_reads_ - - _storage_writes_ + - `storage_reads` + - `storage_writes` -3. For the last _N_ non-empty requests in _public_call_requests_ within _[public_inputs](#public-inputs).[transient_accumulated_data](#transientaccumulateddata)_: +3. For the last `N` non-empty requests in `public_call_requests` within [`public_inputs`](#public-inputs)[`.transient_accumulated_data`](#transientaccumulateddata): - - The _counter_end_ of each request must be greater than its _counter_start_. - - The _counter_start_ of the first request must be greater than the _counter_start_ of the _call_stack_item_. - - The _counter_start_ of the second and subsequent requests must be greater than the _counter_end_ of the previous request. - - The _counter_end_ of the last request must be less than the _counter_end_ of the _call_stack_item_. + - The `counter_end` of each request must be greater than its `counter_start`. + - The `counter_start` of the first request must be greater than the `counter_start` of the `call_stack_item`. + - The `counter_start` of the second and subsequent requests must be greater than the `counter_end` of the previous request. + - The `counter_end` of the last request must be less than the `counter_end` of the `call_stack_item`. - > _N_ is the number of non-zero hashes in the _public_call_stack_item_hashes_ in _[private_inputs](#private-inputs).[public_call](#publiccall).[public_inputs](#publicfunctionpublicinputs)_. + > `N` is the number of non-zero hashes in the `public_call_stack_item_hashes` in [`private_inputs`](#private-inputs)[`.public_call`](#publiccall)[`.public_inputs`](#publicfunctionpublicinputs). ### Validating Public Inputs #### Verifying the accumulated data. -1. It verifies that the following in the _[accumulated_data](#accumulateddata)_ align with their corresponding values in _[public_call](#publiccall).[call_stack_item](#publiccallstackitem).[public_inputs](#publicfunctionpublicinputs)_. +1. It verifies that the following in the [`accumulated_data`](#accumulateddata) align with their corresponding values in [`public_call`](#publiccall)[`.call_stack_item`](#publiccallstackitem)[`.public_inputs`](#publicfunctionpublicinputs). - - _note_hashes_ - - _nullifiers_ - - _l2_to_l1_messages_ - - _encrypted_logs_hash_ - - _encrypted_log_preimages_length_ - - _encrypted_note_preimages_hash_ - - _encrypted_note_preimages_length_ - - _old_public_data_tree_snapshot_ - - _new_public_data_tree_snapshot_ + - `note_hashes` + - `nullifiers` + - `l2_to_l1_messages` + - `encrypted_logs_hash` + - `encrypted_log_preimages_length` + - `encrypted_note_preimages_hash` + - `encrypted_note_preimages_length` + - `old_public_data_tree_snapshot` + - `new_public_data_tree_snapshot` #### Verifying the transient accumulated data. -The _[transient_accumulated_data](./public-kernel-tail.md#transientaccumulateddata)_ in this circuit's _[public_inputs](#public-inputs)_ includes values from both the previous iterations and the _[public_call](#publiccall)_. +The [`transient_accumulated_data`](./public-kernel-tail.md#transientaccumulateddata) in this circuit's [`public_inputs`](#public-inputs)\_ includes values from both the previous iterations and the [`public_call`](#publiccall). -For each array in the _transient_accumulated_data_, this circuit verifies that it is populated with the values from the previous iterations, specifically: +For each array in the `transient_accumulated_data`, this circuit verifies that it is populated with the values from the previous iterations, specifically: -- _`public_inputs.transient_accumulated_data.ARRAY[0..N] == private_inputs.previous_kernel.public_inputs.transient_accumulated_data.ARRAY[0..N]`_ +- `public_inputs.transient_accumulated_data.ARRAY[0..N] == private_inputs.previous_kernel.public_inputs.transient_accumulated_data.ARRAY[0..N]` -> It's important to note that the top item in the _public_call_requests_ from the _previous_kernel_ won't be included, as it has been removed in a [previous step](#ensuring-the-current-call-matches-the-call-request). +> It's important to note that the top item in the `public_call_requests` from the _previous_kernel_ won't be included, as it has been removed in a [previous step](#ensuring-the-current-call-matches-the-call-request). -For the subsequent items appended after the values from the previous iterations, they constitute the values from _[private_inputs](#private-inputs).[public_call](#publiccall).[call_stack_item](#publiccallstackitem).[public_inputs](#publicfunctionpublicinputs)_ (_public_function_public_inputs_), and must undergo the following verifications: +For the subsequent items appended after the values from the previous iterations, they constitute the values from [`private_inputs`](#private-inputs).[public_call](#publiccall).[call_stack_item](#publiccallstackitem).[public_inputs](#publicfunctionpublicinputs) (`public_function_public_inputs`), and must undergo the following verifications: -1. Ensure that the specified values in the following arrays match those in the corresponding arrays in the _public_function_public_inputs_: +1. Ensure that the specified values in the following arrays match those in the corresponding arrays in the `public_function_public_inputs`: - - _note_hash_contexts_ - - _value_, _counter_ - - _nullifier_contexts_ - - _value_, _counter_ - - _l2_to_l1_message_contexts_ - - _value_ - - _storage_reads_ - - _value_, _counter_ - - _storage_writes_ - - _value_, _counter_ - - _unencrypted_log_hash_contexts_ - - _hash_, _length_, _counter_ + - `note_hash_contexts` + - `value`, `counter` + - `nullifier_contexts` + - `value`, `counter` + - `l2_to_l1_message_contexts` + - `value` + - `storage_reads` + - `value`, `counter` + - `storage_writes` + - `value`, `counter` + - `unencrypted_log_hash_contexts` + - `hash`, `length`, `counter` -2. For _public_call_requests_: +2. For `public_call_requests`: - - The hashes align with the values in the _public_call_stack_item_hashes_ within _public_function_public_inputs_, but in **reverse** order. - - The _caller_contract_address_ equals the _contract_address_ in _[public_call](#publiccall).[call_stack_item](#publiccallstackitem)_. - - The _caller_context_ aligns with the values in the _call_context_ within _public_function_public_inputs_. + - The hashes align with the values in the `public_call_stack_item_hashes` within `public_function_public_inputs`, but in **reverse** order. + - The `caller_contract_address` equals the `contract_address` in [`public_call`](#publiccall)[`.call_stack_item`](#publiccallstackitem). + - The `caller_context` aligns with the values in the `call_context` within `public_function_public_inputs`. > It's important that the call requests are arranged in reverse order to ensure they are executed in chronological order. -3. The _contract_address_ for each non-empty item in the following arrays must equal the _storage_contract_address_ defined in _public_function_public_inputs.call_context_: +3. The `contract_address` for each non-empty item in the following arrays must equal the `storage_contract_address` defined in `public_function_public_inputs.call_context`: - - _note_hash_contexts_ - - _nullifier_contexts_ - - _l2_to_l1_message_contexts_ - - _storage_reads_ - - _storage_writes_ - - _unencrypted_log_hash_contexts_ + - `note_hash_contexts` + - `nullifier_contexts` + - `l2_to_l1_message_contexts` + - `storage_reads` + - `storage_writes` + - `unencrypted_log_hash_contexts` > Ensuring the alignment of the contract addresses is crucial, as it is later used to [silo the values](./public-kernel-tail.md#siloing-values) and to establish associations with values within the same contract. -4. The _portal_contract_address_ for each non-empty item in _l2_to_l1_message_contexts_ must equal the _portal_contract_address_ defined in _public_function_public_inputs.call_context_. +4. The _portal_contract_address_ for each non-empty item in `l2_to_l1_message_contexts` must equal the _portal_contract_address_ defined in _public_function_public_inputs.call_context_. -5. For each _storage_write_ in _storage_writes_, verify that it is associated with an _override_counter_. The value of the _override_counter_ can be: +5. For each `storage_write` in `storage_writes`, verify that it is associated with an _override_counter_. The value of the _override_counter_ can be: - - Zero: if the _storage_slot_ does not change later in the same transaction. - - Greater than _storage_write.counter_: if the _storage_slot_ is written again later in the same transaction. + - Zero: if the `storage_slot` does not change later in the same transaction. + - Greater than `storage_write.counter`: if the `storage_slot` is written again later in the same transaction. > Override counters are used in the [tail public kernel circuit](./public-kernel-tail.md) to ensure a read happens **before** the value is changed in a subsequent write. @@ -178,61 +182,61 @@ For the subsequent items appended after the values from the previous iterations, #### Verifying the constant data. -This section follows the same [process](./private-kernel-inner.md#verifying-the-constant-data) as outlined in the inner private kernel circuit. +This section follows the same [process](./private-kernel-inner.mdx#verifying-the-constant-data) as outlined in the inner private kernel circuit. -## Private Inputs +## `PrivateInputs` -### _PreviousKernel_ +### `PreviousKernel` -The format aligns with the _[PreviousKernel](./private-kernel-tail.md#previouskernel)_ of the tail public kernel circuit. +The format aligns with the [`PreviousKernel`](./private-kernel-tail.md#previouskernel) of the tail public kernel circuit. -### _PublicCall_ +### `PublicCall` Data that holds details about the current public function call. -| Field | Type | Description | -| ---------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------- | -| _call_stack_item_ | _[PublicCallStackItem](#publiccallstackitem)_ | Information about the current public function call. | -| _proof_ | _Proof_ | Proof of the public function circuit. | -| _vk_ | _VerificationKey_ | Verification key of the public function circuit. | -| _bytecode_hash_ | _field_ | Hash of the function bytecode. | -| _contract_data_ | _[ContractInstance](../contract-deployment/instances.md#structure)_ | Data of the contract instance being called. | -| _contract_class_data_ | _[ContractClassData](./private-kernel-initial.md#contractclassdata)_ | Data of the contract class. | -| _function_leaf_membership_witness_ | _[MembershipWitness](./private-kernel-inner.md#membershipwitness)_ | Membership witness for the function being called. | -| _contract_deployment_membership_witness_ | _[MembershipWitness](./private-kernel-inner.md#membershipwitness)_ | Membership witness for the deployment of the contract being called. | +| Field | Type | Description | +| ---------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `call_stack_item` | [`PublicCallStackItem`](#publiccallstackitem) | Information about the current public function call. | +| `proof` | `Proof` | Proof of the public function circuit. | +| `vk` | `VerificationKey` | Verification key of the public function circuit. | +| `bytecode_hash` | `field` | Hash of the function bytecode. | +| `contract_data` | [`ContractInstance`](../contract-deployment/instances.md#structure) | Data of the contract instance being called. | +| `contract_class_data` | [`ContractClass`](./private-kernel-initial.mdx#contractclassdata) | Data of the contract class. | +| `function_leaf_membership_witness` | [`MembershipWitness`](./private-kernel-inner.mdx#membershipwitness) | Membership witness for the function being called. | +| `contract_deployment_membership_witness` | [`MembershipWitness`](./private-kernel-inner.mdx#membershipwitness) | Membership witness for the deployment of the contract being called. | -## Public Inputs +## `PublicInputs` -The format aligns with the _[Public Inputs](./public-kernel-tail.md#public-inputs)_ of the tail public kernel circuit. +The format aligns with the [`PublicInputs`](./public-kernel-tail.md#public-inputs) of the tail public kernel circuit. ## Types -### _PublicCallStackItem_ +### `PublicCallStackItem` | Field | Type | Description | | ------------------ | ----------------------------------------------------------- | --------------------------------------------------------- | -| _contract_address_ | _AztecAddress_ | Address of the contract on which the function is invoked. | -| _function_data_ | _[FunctionData](#functiondata)_ | Data of the function being called. | -| _public_inputs_ | _[PublicFunctionPublicInputs](#publicfunctionpublicinputs)_ | Public inputs of the public vm circuit. | -| _counter_start_ | _field_ | Counter at which the function call was initiated. | -| _counter_end_ | _field_ | Counter at which the function call ended. | - -### _PublicFunctionPublicInputs_ - -| Field | Type | Description | -| ------------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------- | -| _call_context_ | _[CallContext](./private-function.md#callcontext)_ | Context of the call corresponding to this function execution. | -| _args_hash_ | _field_ | Hash of the function arguments. | -| _return_values_ | [_field_; _C_] | Return values of this function call. | -| _note_hashes_ | [_[NoteHash](./private-function.md#notehash)_; _C_] | New note hashes created in this function call. | -| _nullifiers_ | [_[Nullifier](./private-function.md#nullifier)_; _C_] | New nullifiers created in this function call. | -| _l2_to_l1_messages_ | [_field_; _C_] | New L2 to L1 messages created in this function call. | -| _storage_reads_ | [_[StorageRead](./public-kernel-tail.md#storageread)_; _C_] | Data read from the public data tree. | -| _storage_writes_ | [_[StorageWrite](./public-kernel-tail.md#storagewrite)_; _C_] | Data written to the public data tree. | -| _unencrypted_log_hashes_ | [_[UnencryptedLogHash](./private-function.md#unencryptedloghash)_; _C_] | Hashes of the unencrypted logs emitted in this function call. | -| _public_call_stack_item_hashes_ | [_field_; _C_] | Hashes of the public function calls initiated by this function. | -| _header_ | _[Header](./private-function.md#header)_ | Information about the trees used for the transaction. | -| _chain_id_ | _field_ | Chain ID of the transaction. | -| _version_ | _field_ | Version of the transaction. | +| `contract_address` | `AztecAddress` | Address of the contract on which the function is invoked. | +| `function_data` | [`FunctionData`](#functiondata) | Data of the function being called. | +| `public_inputs` | [`PublicFunctionPublicInputs`](#publicfunctionpublicinputs) | Public inputs of the public vm circuit. | +| `counter_start` | `field` | Counter at which the function call was initiated. | +| `counter_end` | `field` | Counter at which the function call ended. | + +### `PublicFunctionPublicInputs` + +| Field | Type | Description | +| ------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------- | +| `call_context` | [`CallContext`](./private-function.md#callcontext) | Context of the call corresponding to this function execution. | +| `args_hash` | `field` | Hash of the function arguments. | +| `return_values` | `[field; C]` | Return values of this function call. | +| `note_hashes` | `[`[`NoteHash`](./private-function.md#notehash)`; C]` | New note hashes created in this function call. | +| `nullifiers` | [`[Nullifier; C]`](./private-function.md#nullifier) | New nullifiers created in this function call. | +| `l2_to_l1_messages` | `[field; C]` | New L2 to L1 messages created in this function call. | +| `storage_reads` | [`[StorageRead_; C]`](./public-kernel-tail.md#storageread) | Data read from the public data tree. | +| `storage_writes` | [`[StorageWrite; C]`](./public-kernel-tail.md#storagewrite) | Data written to the public data tree. | +| `unencrypted_log_hashes` | [`[UnencryptedLogHash; C]`](./private-function.md#unencryptedloghash) | Hashes of the unencrypted logs emitted in this function call. | +| `public_call_stack_item_hashes` | `[field; C]` | Hashes of the public function calls initiated by this function. | +| `header` | [`Header`](./private-function.md#header) | Information about the trees used for the transaction. | +| `chain_id` | `field` | Chain ID of the transaction. | +| `version` | `field` | Version of the transaction. | > The above **C**s represent constants defined by the protocol. Each **C** might have a different value from the others. diff --git a/yellow-paper/docs/circuits/public-kernel-tail.md b/yellow-paper/docs/circuits/public-kernel-tail.md index 69631ea47c5..4c0f0bae098 100644 --- a/yellow-paper/docs/circuits/public-kernel-tail.md +++ b/yellow-paper/docs/circuits/public-kernel-tail.md @@ -1,5 +1,9 @@ # Public Kernel Circuit - Tail +:::Danger +The public kernel circuits are being redesigned to accommodate the latest AVM designs. This page is therefore highly likely to change significantly. +::: + ## Requirements The **tail** circuit refrains from processing individual public function calls. Instead, it integrates the results of inner public kernel circuit and performs additional verification and processing necessary for generating the final public inputs. @@ -8,7 +12,7 @@ The **tail** circuit refrains from processing individual public function calls. #### Verifying the previous kernel proof. -It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from _[private_inputs](#private-inputs).[previous_kernel](#previouskernel)_. +It verifies that the previous iteration was executed successfully with the given proof data, verification key, and public inputs, sourced from [`private_inputs`](#private-inputs)[`.previous_kernel`](#previouskernel). The preceding proof can only be: @@ -18,7 +22,7 @@ The preceding proof can only be: The following must be empty to ensure all the public function calls are processed: -- _public_call_requests_ within _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./public-kernel-tail.md#public-inputs).[transient_accumulated_data](./public-kernel-tail.md#transientaccumulateddata)_. +- `public_call_requests` within [`private_inputs`](#private-inputs)[`.previous_kernel`](#previouskernel)[`.public_inputs`](./public-kernel-tail.md#public-inputs)[`.transient_accumulated_data`](./public-kernel-tail.md#transientaccumulateddata). ### Processing Final Outputs @@ -26,10 +30,10 @@ The following must be empty to ensure all the public function calls are processe This section follows the same [process](./private-kernel-tail.md#siloing-values) as outlined in the tail private kernel circuit. -Additionally, it silos the _storage_slot_ of each non-empty item in the following arrays: +Additionally, it silos the `storage_slot` of each non-empty item in the following arrays: -- _storage_reads_ -- _storage_writes_ +- `storage_reads` +- `storage_writes` The siloed storage slot is computed as: `hash(contract_address, storage_slot)`. @@ -39,173 +43,173 @@ The iterations of the public kernel may yield values in an unordered state due t This circuit ensures the correct ordering of the following: -- _note_hashes_ -- _nullifiers_ -- _storage_reads_ -- _storage_writes_ -- _ordered_unencrypted_log_hashes_ +- `note_hashes` +- `nullifiers` +- `storage_reads` +- `storage_writes` +- `ordered_unencrypted_log_hashes` -1. For _note_hashes_, _nullifiers_, and _ordered_unencrypted_log_hashes_, they undergo the same [process](./private-kernel-tail.md#verifying-ordered-arrays) as outlined in the tail private kernel circuit. With the exception that the loop starts from index _offset + i_, where _offset_ is the number of non-zero values in the _note_hashes_ and _nullifiers_ arrays within _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs](./public-kernel-tail.md#public-inputs).[accumulated_data](./public-kernel-tail.md#accumulateddata)_. +1. For `note_hashes`, `nullifiers`, and `ordered_unencrypted_log_hashes`, they undergo the same [process](./private-kernel-tail.md#verifying-ordered-arrays) as outlined in the tail private kernel circuit. With the exception that the loop starts from index `offset + i`, where `offset` is the number of non-zero values in the `note_hashes` and `nullifiers` arrays within [`private_inputs`](#private-inputs)[`.previous_kernel`](#previouskernel)[`.public_inputs`](./public-kernel-tail.md#public-inputs)[`.accumulated_data`](./public-kernel-tail.md#accumulateddata). -2. For _storage_reads_, an _ordered_storage_reads_ and _storage_read_hints_ are provided as [hints](#hints) through _private_inputs_. This circuit checks that: +2. For `storage_reads`, an `ordered_storage_reads` and `storage_read_hints` are provided as [hints](#hints) through `private_inputs`. This circuit checks that: - For each _read_ at index _i_ in _`storage_reads[i]`_, the associated _mapped_read_ is at _`ordered_storage_reads[storage_read_hints[i]]`_. + For each `read` at index `i` in `storage_reads[i]`, the associated `mapped_read` is at `ordered_storage_reads[storage_read_hints[i]]`. - - If _`read.is_empty() == false`_, verify that: - - All values in _read_ align with those in _mapped_read_: - - _`read.contract_address == mapped_read.contract_address`_ - - _`read.storage_slot == mapped_read.storage_slot`_ - - _`read.value == mapped_read.value`_ - - _`read.counter == mapped_read.counter`_ - - If _i > 0_, verify that: - - _`mapped_read[i].counter > mapped_read[i - 1].counter`_ + - If `read.is_empty() == false`, verify that: + - All values in `read` align with those in `mapped_read`: + - `read.contract_address == mapped_read.contract_address` + - `read.storage_slot == mapped_read.storage_slot` + - `read.value == mapped_read.value` + - `read.counter == mapped_read.counter` + - If `i > 0`, verify that: + - `mapped_read[i].counter > mapped_read[i - 1].counter` - Else: - - All the subsequent reads (_index >= i_) in both _storage_reads_ and _ordered_storage_reads_ must be empty. + - All the subsequent reads (index `>= i`) in both `storage_reads` and `ordered_storage_reads` must be empty. -3. For _storage_writes_, an _ordered_storage_writes_ and _storage_write_hints_ are provided as [hints](#hints) through _private_inputs_. The verification is the same as the process for _storage_reads_. +3. For `storage_writes`, an `ordered_storage_writes` and `storage_write_hints` are provided as [hints](#hints) through `private_inputs`. The verification is the same as the process for `storage_reads`. #### Verifying public data snaps. -The _public_data_snaps_ is provided through _private_inputs_, serving as hints for _storage_reads_ to prove that the value in the tree aligns with the read operation. For _storage_writes_, it substantiates the presence or absence of the storage slot in the public data tree. +The `public_data_snaps` is provided through `private_inputs`, serving as hints for `storage_reads` to prove that the value in the tree aligns with the read operation. For `storage_writes`, it substantiates the presence or absence of the storage slot in the public data tree. -A _[public_data_snap](#publicdatasnap)_ contains: +A [public_data_snap](#publicdatasnap) contains: -- A _storage_slot_ and its _value_. -- An _override_counter_, indicating the counter of the first _storage_write_ that writes to the storage slot. Zero if the storage slot is not written in this transaction. -- A flag _exists_ indicating its presence or absence in the public data tree. +- A `storage_slot` and its `value`. +- An `override_counter`, indicating the counter of the first `storage_write` that writes to the storage slot. Zero if the storage slot is not written in this transaction. +- A flag `exists` indicating its presence or absence in the public data tree. -This circuit ensures the uniqueness of each snap in _public_data_snaps_. It verifies that: +This circuit ensures the uniqueness of each snap in `public_data_snaps`. It verifies that: -For each snap at index _i_, where _i_ > 0: +For each snap at index `i`, where `i` > 0: -- If _snap.is_empty() == false_ - - _`snap.storage_slot > public_data_snaps[i - 1].storage_slot`_ +- If `snap.is_empty() == false` + - `snap.storage_slot > public_data_snaps[i - 1].storage_slot` > It is crucial for each snap to be unique, as duplicated snaps would disrupt a group of writes for the same storage slot. This could enable the unauthorized act of reading the old value after it has been updated. #### Grouping storage writes. -To facilitate the verification of _storage_reads_ and streamline _storage_writes_, it is imperative to establish connections between writes targeting the same storage slot. Furthermore, the first write in a group must be linked to a _public_data_snap_, ensuring the dataset has progressed from the right initial state. +To facilitate the verification of `storage_reads` and streamline `storage_writes`, it is imperative to establish connections between writes targeting the same storage slot. Furthermore, the first write in a group must be linked to a `public_data_snap`, ensuring the dataset has progressed from the right initial state. -A new field, _prev_counter_, is incorporated to the _ordered_storage_writes_ to indicate whether each write has a preceding snap or write. Another field, _exists_, is also added to signify the presence or absence of the storage slot in the tree. +A new field, `prev_counter`, is incorporated to the `ordered_storage_writes` to indicate whether each write has a preceding snap or write. Another field, `exists`, is also added to signify the presence or absence of the storage slot in the tree. -1. For each _snap_ at index _i_ in _public_data_snaps_: +1. For each `snap` at index `i` in `public_data_snaps`: - - Skip the remaining steps if it is empty or if its _override_counter_ is _0_. - - Locate the _write_ at _`ordered_storage_writes[storage_write_indices[i]]`_. + - Skip the remaining steps if it is empty or if its `override_counter` is `0`. + - Locate the `write` at `ordered_storage_writes[storage_write_indices[i]]`. - Verify the following: - - _`write.storage_slot == snap.storage_slot`_ - - _`write.counter == snap.override_counter`_ - - _`write.prev_counter == 0`_ - - Update the hints in _write_: - - _`write.prev_counter = 1`_ - - _`write.exists = snap.exists`_ + - `write.storage_slot == snap.storage_slot` + - `write.counter == snap.override_counter` + - `write.prev_counter == 0` + - Update the hints in `write`: + - `write.prev_counter = 1` + - `write.exists = snap.exists` - > The value _1_ can be utilized to signify a preceding _snap_, as this value can never serve as the counter of a _storage_write_. Because the _counter_start_ for the first public function call must be 1, the counters for all subsequent side effects should exceed this initial value. + > The value _1_ can be utilized to signify a preceding `snap`, as this value can never serve as the counter of a `storage_write`. Because the _counter_start_ for the first public function call must be 1, the counters for all subsequent side effects should exceed this initial value. -2. For each _write_ at index _i_ in _ordered_storage_writes_: +2. For each `write` at index `i` in `ordered_storage_writes`: - - Skip the remaining steps if its _next_counter_ is _0_. - - Locate the _next_write_ at _`ordered_storage_writes[next_storage_write_indices[i]]`_. + - Skip the remaining steps if its `next_counter` is `0`. + - Locate the `next_write` at `ordered_storage_writes[next_storage_write_indices[i]]`. - Verify the following: - - _`write.storage_slot == next_write.storage_slot`_ - - _`write.next_counter == next_write.counter`_ - - _`write.prev_counter == 0`_ - - Update the hints in _next_write_: - - _`next_write.prev_counter = write.counter`_ - - _`next_write.exists = write.exists`_ + - `write.storage_slot == next_write.storage_slot` + - `write.next_counter == next_write.counter` + - `write.prev_counter == 0` + - Update the hints in `next_write`: + - `next_write.prev_counter = write.counter` + - `next_write.exists = write.exists` -3. Following the previous two steps, verify that all non-empty writes in _ordered_storage_writes_ have a non-zero _prev_counter_. +3. Following the previous two steps, verify that all non-empty writes in `ordered_storage_writes` have a non-zero `prev_counter`. #### Verifying storage reads. A storage read can be reading: -- An uninitialized storage slot: the value is zero. There isn't a leaf in the public data tree representing its storage slot, nor in the _storage_writes_. +- An uninitialized storage slot: the value is zero. There isn't a leaf in the public data tree representing its storage slot, nor in the `storage_writes`. - An existing storage slot: written in a prior successful transaction. The value being read is the value in the public data tree. -- An updated storage slot: initialized or updated in the current transaction. The value being read is in a _storage_write_. +- An updated storage slot: initialized or updated in the current transaction. The value being read is in a `storage_write`. -For each non-empty _read_ at index _i_ in _ordered_storage_reads_, it must satisfy one of the following conditions: +For each non-empty `read` at index `i` in `ordered_storage_reads`, it must satisfy one of the following conditions: -1. If reading an uninitialized or an existing storage slot, the value is in a _snap_: +1. If reading an uninitialized or an existing storage slot, the value is in a `snap`: - - Locate the _snap_ at _`public_data_snaps[persistent_read_hints[i]]`_. + - Locate the `snap` at `public_data_snaps[persistent_read_hints[i]]`. - Verify the following: - - _`read.storage_slot == snap.storage_slot`_ - - _`read.value == snap.value`_ - - _`(read.counter < snap.override_counter) | (snap.override_counter == 0)`_ - - If _`snap.exists == false`_: - - _`read.value == 0`_ + - `read.storage_slot == snap.storage_slot` + - `read.value == snap.value` + - `(read.counter < snap.override_counter) | (snap.override_counter == 0)` + - If `snap.exists == false`: + - `read.value == 0` - Depending on the value of the _exists_ flag in the snap, verify its presence or absence in the public data tree: + Depending on the value of the `exists` flag in the snap, verify its presence or absence in the public data tree: - - If _exists_ is true: + - If `exists` is true: - It must pass a membership check on the leaf. - - If _exists_ is false: - - It must pass a non-membership check on the low leaf. The preimage of the low leaf is at _`storage_read_low_leaf_preimages[i]`_. + - If `exists` is false: + - It must pass a non-membership check on the low leaf. The preimage of the low leaf is at `storage_read_low_leaf_preimages[i]`. - > The (non-)membership checks are executed against the root in _old_public_data_tree_snapshot_. The membership witnesses for the leaves are in _storage_read_membership_witnesses_, provided as [hints](#hints) through _private_inputs_. + > The (non-)membership checks are executed against the root in `old_public_data_tree_snapshot`. The membership witnesses for the leaves are in `storage_read_membership_witnesses`, provided as [hints](#hints) through `private_inputs`. -2. If reading an updated storage slot, the value is in a _storage_write_: +2. If reading an updated storage slot, the value is in a `storage_write`: - - Locates the _storage_write_ at _`ordered_storage_writes[transient_read_hints[i]]`_. + - Locates the `storage_write` at `ordered_storage_writes[transient_read_hints[i]]`. - Verify the following: - - _`read.storage_slot == storage_write.storage_slot`_ - - _`read.value == storage_write.value`_ - - _`read.counter > storage_write.counter`_ - - _`(read.counter < storage_write.next_counter) | (storage_write.next_counter == 0)`_ + - `read.storage_slot == storage_write.storage_slot` + - `read.value == storage_write.value` + - `read.counter > storage_write.counter` + - `(read.counter < storage_write.next_counter) | (storage_write.next_counter == 0)` - > A zero _next_counter_ indicates that the value is not written again in the transaction. + > A zero `next_counter` indicates that the value is not written again in the transaction. #### Updating the public data tree. -It updates the public data tree with the values in _storage_writes_. The _latest_root_ of the tree is _old_public_data_tree_snapshot.root_. +It updates the public data tree with the values in `storage_writes`. The `latest_root` of the tree is _old_public_data_tree_snapshot.root_. -For each non-empty _write_ at index _i_ in _ordered_storage_writes_, the circuit processes it base on its type: +For each non-empty `write` at index `i` in `ordered_storage_writes`, the circuit processes it base on its type: 1. Transient write. - If _`write.next_counter != 0`_, the same storage slot is written again by another storage write that occurs later in the same transaction. This transient _write_ can be ignored as the final state of the tree won't be affected by it. + If `write.next_counter != 0`, the same storage slot is written again by another storage write that occurs later in the same transaction. This transient `write` can be ignored as the final state of the tree won't be affected by it. 2. Updating an existing storage slot. - For a non-transient _write_ (_write.next_counter == 0_), if _`write.exists == true`_, it is updating an existing storage slot. The circuit does the following for such a write: + For a non-transient `write` (`write.next_counter == 0`), if `write.exists == true`, it is updating an existing storage slot. The circuit does the following for such a write: - Performs a membership check, where: - The leaf if for the existing storage slot. - - _`leaf.storage_slot = write.storage_slot`_ - - The old value is the value in a _snap_: - - _`leaf.value = public_data_snaps[public_data_snap_indices[i]].value`_ - - The index and the sibling path are in _storage_write_membership_witnesses_, provided as [hints](#hints) through _private_inputs_. - - The root is the _latest_root_ after processing the previous write. - - Derives the _latest_root_ for the _latest_public_data_tree_ with the updated leaf, where _`leaf.value = write.value`_. + - `leaf.storage_slot = write.storage_slot` + - The old value is the value in a `snap`: + - `leaf.value = public_data_snaps[public_data_snap_indices[i]].value` + - The index and the sibling path are in `storage_write_membership_witnesses`, provided as [hints](#hints) through `private_inputs`. + - The root is the `latest_root` after processing the previous write. + - Derives the `latest_root` for the `latest_public_data_tree` with the updated leaf, where `leaf.value = write.value`. 3. Creating a new storage slot. - For a non-transient _write_ (_write.next_counter == 0_), if _`write.exists == false`_, it is initializing a storage slot. The circuit adds it to a subtree: + For a non-transient `write` (`write.next_counter == 0`), if `write.exists == false`, it is initializing a storage slot. The circuit adds it to a subtree: - Perform a membership check on the low leaf in the _latest_public_data_tree_ and in the subtree. One check must succeed. - - The low leaf preimage is at _storage_write_low_leaf_preimages[i]_. - - The membership witness for the public data tree is at _storage_write_membership_witnesses[i]_. - - The membership witness for the subtree is at _subtree_membership_witnesses[i]_. - - The above are provided as [hints](#hints) through _private_inputs_. + - The low leaf preimage is at `storage_write_low_leaf_preimages[i]`. + - The membership witness for the public data tree is at `storage_write_membership_witnesses[i]`. + - The membership witness for the subtree is at `subtree_membership_witnesses[i]`. + - The above are provided as [hints](#hints) through `private_inputs`. - Update the low leaf to point to the new leaf: - - _`low_leaf.next_slot = write.storage_slot`_ - - _`low_leaf.next_index = old_public_data_tree_snapshot.next_available_leaf_index + number_of_new_leaves`_ - - If the low leaf is in the _latest_public_data_tree_, derive the _latest_root_ from the updated low leaf. - - If the low leaf is in the subtree, derive the _subtree_root_ from the updated low leaf. - - Append the new leaf to the subtree. Derive the _subtree_root_. - - Increment _number_of_new_leaves_ by 1. + - `low_leaf.next_slot = write.storage_slot` + - `low_leaf.next_index = old_public_data_tree_snapshot.next_available_leaf_index + number_of_new_leaves` + - If the low leaf is in the `latest_public_data_tree`, derive the `latest_root` from the updated low leaf. + - If the low leaf is in the subtree, derive the `subtree_root` from the updated low leaf. + - Append the new leaf to the subtree. Derive the `subtree_root`. + - Increment `number_of_new_leaves` by `1`. > The subtree and _number_of_new_leaves_ are initialized to empty and 0 at the beginning of the process. After all the storage writes are processed: - Batch insert the subtree to the public data tree. - - The insertion index is _`old_public_data_tree_snapshot.next_available_leaf_index`_. + - The insertion index is `old_public_data_tree_snapshot.next_available_leaf_index`. - Verify the following: - - _`latest_root == new_public_data_tree_snapshot.root`_ - - _`new_public_data_tree_snapshot.next_available_leaf_index == old_public_data_tree_snapshot.next_available_leaf_index + number_of_new_leaves`_ + - `latest_root == new_public_data_tree_snapshot.root` + - `new_public_data_tree_snapshot.next_available_leaf_index == old_public_data_tree_snapshot.next_available_leaf_index + number_of_new_leaves` ### Validating Public Inputs @@ -213,31 +217,31 @@ After all the storage writes are processed: 1. The following must align with the results after siloing, as verified in a [previous step](#siloing-values): - - _l2_to_l1_messages_ + - `l2_to_l1_messages` 2. The following must align with the results after ordering, as verified in a [previous step](#verifying-ordered-arrays): - - _note_hashes_ - - _nullifiers_ + - `note_hashes` + - `nullifiers` 3. The hashes and lengths for unencrypted logs are accumulated as follows: - Initialize _accumulated_logs_hash_ to be the _unencrypted_logs_hash_ within _[private_inputs](#private-inputs).[previous_kernel](#previouskernel).[public_inputs].[accumulated_data](#accumulateddata)_. + Initialize `accumulated_logs_hash` to be the `unencrypted_logs_hash` within [`private_inputs`](#private-inputs)[`.previous_kernel`](#previouskernel).[public_inputs].[accumulated_data](#accumulateddata). - For each non-empty _log_hash_ at index _i_ in _ordered_unencrypted_log_hashes_, which is provided as [hints](#hints), and the [ordering](#verifying-ordered-arrays) was verified against the [siloed hashes](#siloing-values) in previous steps: + For each non-empty _log_hash_ at index `i` in `ordered_unencrypted_log_hashes`, which is provided as [hints](#hints), and the [ordering](#verifying-ordered-arrays) was verified against the [siloed hashes](#siloing-values) in previous steps: - - _`accumulated_logs_hash = hash(accumulated_logs_hash, log_hash.hash)`_ - - _`accumulated_logs_length += log_hash.length`_ + - `accumulated_logs_hash = hash(accumulated_logs_hash, log_hash.hash)` + - `accumulated_logs_length += log_hash.length` - Check the values in the _public_inputs_ are correct: + Check the values in the `public_inputs` are correct: - - _`unencrypted_logs_hash == accumulated_logs_hash`_ - - _`unencrypted_log_preimages_length == accumulated_logs_length`_ + - `unencrypted_logs_hash == accumulated_logs_hash` + - `unencrypted_log_preimages_length == accumulated_logs_length` 4. The following is referenced and verified in a [previous step](#updating-the-public-data-tree): - - _old_public_data_tree_snapshot_ - - _new_public_data_tree_snapshot_ + - `old_public_data_tree_snapshot` + - `new_public_data_tree_snapshot` #### Verifying the transient accumulated data. @@ -245,146 +249,146 @@ It ensures that the transient accumulated data is empty. #### Verifying the constant data. -This section follows the same [process](./private-kernel-inner.md#verifying-the-constant-data) as outlined in the inner private kernel circuit. +This section follows the same [process](./private-kernel-inner.mdx#verifying-the-constant-data) as outlined in the inner private kernel circuit. -## Private Inputs +## `PrivateInputs` -### _PreviousKernel_ +### `PreviousKernel` -| Field | Type | Description | -| -------------------- | -------------------------------------------------------------------- | -------------------------------------------- | -| _public_inputs_ | _[PublicKernelPublicInputs](#public-inputs)_ | Public inputs of the proof. | -| _proof_ | _Proof_ | Proof of the kernel circuit. | -| _vk_ | _VerificationKey_ | Verification key of the kernel circuit. | -| _membership_witness_ | _[MembershipWitness](./private-kernel-initial.md#membershipwitness)_ | Membership witness for the verification key. | +| Field | Type | Description | +| -------------------- | --------------------------------------------------------------------- | -------------------------------------------- | +| `public_inputs` | [`PublicKernelPublicInputs`](#public-inputs) | Public inputs of the proof. | +| `proof` | `Proof` | Proof of the kernel circuit. | +| `vk` | `VerificationKey` | Verification key of the kernel circuit. | +| `membership_witness` | [`MembershipWitness`](./private-kernel-initial.mdx#membershipwitness) | Membership witness for the verification key. | ### _Hints_ Data that aids in the verifications carried out in this circuit: -| Field | Type | Description | -| ------------------------------------ | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -| _note_hash_indices_ | [_field_; _C_] | Indices of _note_hashes_ for _note_hash_contexts_. _C_ equals the length of _note_hashes_. | -| _note_hash_hints_ | [_field_; _C_] | Indices of _note_hash_contexts_ for ordered _note_hashes_. _C_ equals the length of _note_hash_contexts_. | -| _nullifier_hints_ | [_field_; _C_] | Indices of _nullifier_contexts_ for ordered _nullifiers_. _C_ equals the length of _nullifier_contexts_. | -| _ordered_unencrypted_log_hashes_ | [_field_; _C_] | Ordered _unencrypted_log_hashes_. _C_ equals the length of _unencrypted_log_hashes_. | -| _unencrypted_log_hash_hints_ | [_field_; _C_] | Indices of _ordered_unencrypted_log_hashes_ for _unencrypted_log_hash_contexts_. _C_ equals the length of _unencrypted_log_hash_contexts_. | -| _ordered_storage_reads_ | [_[StorageReadContext](#storagereadcontext)_; _C_] | Ordered _storage_reads_. _C_ equals the length of _storage_reads_. | -| _storage_read_hints_ | [_field_; _C_] | Indices of reads for _ordered_storage_reads_. _C_ equals the length of _storage_reads_. | -| _ordered_storage_writes_ | [_[StorageWriteContext](#storagewritecontext)_; _C_] | Ordered _storage_writes_. _C_ equals the length of _storage_writes_. | -| _storage_write_hints_ | [_field_; _C_] | Indices of writes for _ordered_storage_writes_. _C_ equals the length of _storage_writes_. | -| _public_data_snaps_ | [_[PublicDataSnap](#publicdatasnap)_; _C_] | Data that aids verification of storage reads and writes. _C_ equals the length of _ordered_storage_writes_ + _ordered_storage_reads_. | -| _storage_write_indices_ | [_field_; _C_] | Indices of _ordered_storage_writes_ for _public_data_snaps_. _C_ equals the length of _public_data_snaps_. | -| _transient_read_hints_ | [_field_; _C_] | Indices of _ordered_storage_writes_ for transient reads. _C_ equals the length of _ordered_storage_reads_. | -| _persistent_read_hints_ | [_field_; _C_] | Indices of _ordered_storage_writes_ for persistent reads. _C_ equals the length of _ordered_storage_reads_. | -| _public_data_snap_indices_ | [_field_; _C_] | Indices of _public_data_snaps_ for persistent write. _C_ equals the length of _ordered_storage_writes_. | -| _storage_read_low_leaf_preimages_ | [_[PublicDataLeafPreimage](#publicdataleafpreimage)_; _C_] | Preimages for public data leaf. _C_ equals the length of _ordered_storage_writes_. | -| _storage_read_membership_witnesses_ | [_[MembershipWitness](./private-kernel-initial.md#membershipwitness)_; _C_] | Membership witnesses for persistent reads. _C_ equals the length of _ordered_storage_writes_. | -| _storage_write_low_leaf_preimages_ | [_[PublicDataLeafPreimage](#publicdataleafpreimage)_; _C_] | Preimages for public data. _C_ equals the length of _ordered_storage_writes_. | -| _storage_write_membership_witnesses_ | [_[MembershipWitness](./private-kernel-initial.md#membershipwitness)_; _C_] | Membership witnesses for public data tree. _C_ equals the length of _ordered_storage_writes_. | -| _subtree_membership_witnesses_ | [_[MembershipWitness](./private-kernel-initial.md#membershipwitness)_; _C_] | Membership witnesses for the public data subtree. _C_ equals the length of _ordered_storage_writes_. | +| Field | Type | Description | +| ------------------------------------ | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `note_hash_indices` | `[field; C]` | Indices of `note_hashes` for `note_hash_contexts`. `C` equals the length of `note_hashes`. | +| `note_hash_hints` | `[field; C]` | Indices of `note_hash_contexts` for ordered `note_hashes`. `C` equals the length of `note_hash_contexts`. | +| `nullifier_hints` | `[field; C]` | Indices of _nullifier_contexts_ for ordered `nullifiers`. `C` equals the length of _nullifier_contexts_. | +| `ordered_unencrypted_log_hashes` | `[field; C]` | Ordered _unencrypted_log_hashes_. `C` equals the length of _unencrypted_log_hashes_. | +| `unencrypted_log_hash_hints` | `[field; C]` | Indices of `ordered_unencrypted_log_hashes` for _unencrypted_log_hash_contexts_. `C` equals the length of _unencrypted_log_hash_contexts_. | +| `ordered_storage_reads` | [`[StorageReadContext; C]`](#storagereadcontext) | Ordered `storage_reads`. `C` equals the length of `storage_reads`. | +| `storage_read_hints` | `[field; C]` | Indices of reads for `ordered_storage_reads`. `C` equals the length of `storage_reads`. | +| `ordered_storage_writes` | [`[StorageWriteContext; C]`](#storagewritecontext) | Ordered `storage_writes`. `C` equals the length of `storage_writes`. | +| `storage_write_hints` | `[field; C]` | Indices of writes for `ordered_storage_writes`. `C` equals the length of `storage_writes`. | +| `public_data_snaps` | [`[PublicDataSnap; C]`](#publicdatasnap) | Data that aids verification of storage reads and writes. `C` equals the length of `ordered_storage_writes` + `ordered_storage_reads`. | +| `storage_write_indices` | `[field; C]` | Indices of `ordered_storage_writes` for `public_data_snaps`. `C` equals the length of `public_data_snaps`. | +| `transient_read_hints` | `[field; C]` | Indices of `ordered_storage_writes` for transient reads. `C` equals the length of `ordered_storage_reads`. | +| `persistent_read_hints` | `[field; C]` | Indices of `ordered_storage_writes` for persistent reads. `C` equals the length of `ordered_storage_reads`. | +| `public_data_snap_indices` | `[field; C]` | Indices of `public_data_snaps` for persistent write. `C` equals the length of `ordered_storage_writes`. | +| `storage_read_low_leaf_preimages` | [`[PublicDataLeafPreimage; C]`](#publicdataleafpreimage) | Preimages for public data leaf. `C` equals the length of `ordered_storage_writes`. | +| `storage_read_membership_witnesses` | [`[MembershipWitness; C]`](./private-kernel-initial.mdx#membershipwitness) | Membership witnesses for persistent reads. `C` equals the length of `ordered_storage_writes`. | +| `storage_write_low_leaf_preimages` | [`[PublicDataLeafPreimage; C]`](#publicdataleafpreimage) | Preimages for public data. `C` equals the length of `ordered_storage_writes`. | +| `storage_write_membership_witnesses` | [`[MembershipWitness; C]`](./private-kernel-initial.mdx#membershipwitness) | Membership witnesses for public data tree. `C` equals the length of `ordered_storage_writes`. | +| `subtree_membership_witnesses` | [`[MembershipWitness; C]`](./private-kernel-initial.mdx#membershipwitness) | Membership witnesses for the public data subtree. `C` equals the length of `ordered_storage_writes`. | ## Public Inputs -### _ConstantData_ +### `ConstantData` -These are constants that remain the same throughout the entire transaction. Its format aligns with the _[ConstantData](./private-kernel-initial.md#constantdata)_ of the initial private kernel circuit. +These are constants that remain the same throughout the entire transaction. Its format aligns with the [ConstantData](./private-kernel-initial.mdx#constantdata) of the initial private kernel circuit. -### _AccumulatedData_ +### `AccumulatedData` Data accumulated during the execution of the transaction. -| Field | Type | Description | -| ---------------------------------- | ------------------------------- | ----------------------------------------------------------- | -| _note_hashes_ | [_field_; _C_] | Note hashes created in the transaction. | -| _nullifiers_ | [_field_; _C_] | Nullifiers created in the transaction. | -| _l2_to_l1_messages_ | [_field_; _C_] | L2-to-L1 messages created in the transaction. | -| _unencrypted_logs_hash_ | _field_ | Hash of the accumulated unencrypted logs. | -| _unencrypted_log_preimages_length_ | _field_ | Length of all unencrypted log preimages. | -| _encrypted_logs_hash_ | _field_ | Hash of the accumulated encrypted logs. | -| _encrypted_log_preimages_length_ | _field_ | Length of all encrypted log preimages. | -| _encrypted_note_preimages_hash_ | _field_ | Hash of the accumulated encrypted note preimages. | -| _encrypted_note_preimages_length_ | _field_ | Length of all encrypted note preimages. | -| _old_public_data_tree_snapshot_ | _[TreeSnapshot](#treesnapshot)_ | Snapshot of the public data tree prior to this transaction. | -| _new_public_data_tree_snapshot_ | _[TreeSnapshot](#treesnapshot)_ | Snapshot of the public data tree after this transaction. | - -> The above **C**s represent constants defined by the protocol. Each **C** might have a different value from the others. - -### _TransientAccumulatedData_ - -| Field | Type | Description | -| --------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------ | -| _note_hash_contexts_ | [_[NoteHashContext](./private-kernel-initial.md#notehashcontext)_; _C_] | Note hashes with extra data aiding verification. | -| _nullifier_contexts_ | [_[NullifierContext](./private-kernel-initial.md#nullifiercontext)_; _C_] | Nullifiers with extra data aiding verification. | -| _l2_to_l1_message_contexts_ | [_[L2toL1MessageContext](./private-kernel-initial.md#l2tol1messagecontext)_; _C_] | L2-to-l1 messages with extra data aiding verification. | -| _storage_reads_ | [_[StorageRead](#storageread)_; _C_] | Reads of the public data. | -| _storage_writes_ | [_[StorageWrite](#storagewrite)_; _C_] | Writes of the public data. | -| _public_call_requests_ | [_[CallRequest](./private-kernel-initial.md#callrequest)_; _C_] | Requests to call publics functions. | - -> The above **C**s represent constants defined by the protocol. Each **C** might have a different value from the others. +| Field | Type | Description | +| ---------------------------------- | --------------------------------- | ----------------------------------------------------------- | +| `note_hashes` | `[field; C]` | Note hashes created in the transaction. | +| `nullifiers` | `[field; C]` | Nullifiers created in the transaction. | +| `l2_to_l1_messages` | `[field; C]` | L2-to-L1 messages created in the transaction. | +| `unencrypted_logs_hash` | `field` | Hash of the accumulated unencrypted logs. | +| `unencrypted_log_preimages_length` | `field` | Length of all unencrypted log preimages. | +| `encrypted_logs_hash` | `field` | Hash of the accumulated encrypted logs. | +| `encrypted_log_preimages_length` | `field` | Length of all encrypted log preimages. | +| `encrypted_note_preimages_hash` | `field` | Hash of the accumulated encrypted note preimages. | +| `encrypted_note_preimages_length` | `field` | Length of all encrypted note preimages. | +| `old_public_data_tree_snapshot` | [`[TreeSnapshot]`](#treesnapshot) | Snapshot of the public data tree prior to this transaction. | +| `new_public_data_tree_snapshot` | [`[TreeSnapshot]`](#treesnapshot) | Snapshot of the public data tree after this transaction. | + +> The above `C`s represent constants defined by the protocol. Each `C` might have a different value from the others. + +### `TransientAccumulatedData` + +| Field | Type | Description | +| --------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------ | +| `note_hash_contexts` | [`[NoteHashContext; C]`](./private-kernel-initial.mdx#notehashcontext) | Note hashes with extra data aiding verification. | +| `nullifier_contexts` | [`[NullifierContext; C]`](./private-kernel-initial.mdx#nullifiercontext) | Nullifiers with extra data aiding verification. | +| `l2_to_l1_message_contexts` | [`[L2toL1MessageContext; C]`](./private-kernel-initial.mdx#l2tol1messagecontext) | L2-to-l1 messages with extra data aiding verification. | +| `storage_reads` | [`[StorageRead; C]`](#storageread) | Reads of the public data. | +| `storage_writes` | [`[StorageWrite; C]`](#storagewrite) | Writes of the public data. | +| `public_call_requests` | [`[CallRequest; C]`](./private-kernel-initial.mdx#callrequest) | Requests to call publics functions. | + +> The above `C`s represent constants defined by the protocol. Each `C` might have a different value from the others. ## Types -### _TreeSnapshot_ +### `TreeSnapshot` -| Field | Type | Description | -| --------------------------- | ----- | --------------------------------- | -| _root_ | field | Root of the tree. | -| _next_available_leaf_index_ | field | The index to insert new value to. | +| Field | Type | Description | +| --------------------------- | ------- | --------------------------------- | +| `root` | `field` | Root of the tree. | +| `next_available_leaf_index` | `field` | The index to insert new value to. | -### _StorageRead_ +### `StorageRead` | Field | Type | Description | | ------------------ | -------------- | ----------------------------------- | -| _contract_address_ | _AztecAddress_ | Address of the contract. | -| _storage_slot_ | field | Storage slot. | -| _value_ | field | Value read from the storage slot. | -| _counter_ | _field_ | Counter at which the read happened. | +| `contract_address` | `AztecAddress` | Address of the contract. | +| `storage_slot` | `field` | Storage slot. | +| `value` | `field` | Value read from the storage slot. | +| `counter` | `field` | Counter at which the read happened. | -### _StorageWrite_ +### `StorageWrite` | Field | Type | Description | | ------------------ | -------------- | -------------------------------------- | -| _contract_address_ | _AztecAddress_ | Address of the contract. | -| _storage_slot_ | field | Storage slot. | -| _value_ | field | New value written to the storage slot. | -| _counter_ | _field_ | Counter at which the write happened. | +| `contract_address` | `AztecAddress` | Address of the contract. | +| `storage_slot` | `field` | Storage slot. | +| `value` | `field` | New value written to the storage slot. | +| `counter` | `field` | Counter at which the write happened. | -### _StorageReadContext_ +### `StorageReadContext` | Field | Type | Description | | ------------------ | -------------- | ----------------------------------- | -| _contract_address_ | _AztecAddress_ | Address of the contract. | -| _storage_slot_ | field | Storage slot. | -| _value_ | field | Value read from the storage slot. | -| _counter_ | _field_ | Counter at which the read happened. | +| `contract_address` | `AztecAddress` | Address of the contract. | +| `storage_slot` | `field` | Storage slot. | +| `value` | `field` | Value read from the storage slot. | +| `counter` | `field` | Counter at which the read happened. | -### _StorageWriteContext_ +### `StorageWriteContext` | Field | Type | Description | | ------------------ | -------------- | ---------------------------------------------------------------------- | -| _contract_address_ | _AztecAddress_ | Address of the contract. | -| _storage_slot_ | field | Storage slot. | -| _value_ | field | New value written to the storage slot. | -| _counter_ | _field_ | Counter at which the write happened. | -| _prev_counter_ | _field_ | Counter of the previous write to the storage slot. | -| _next_counter_ | _field_ | Counter of the next write to the storage slot. | -| _exists_ | _bool_ | A flag indicating whether the storage slot is in the public data tree. | +| `contract_address` | `AztecAddress` | Address of the contract. | +| `storage_slot` | `field` | Storage slot. | +| `value` | `field` | New value written to the storage slot. | +| `counter` | `field` | Counter at which the write happened. | +| `prev_counter` | `field` | Counter of the previous write to the storage slot. | +| `next_counter` | `field` | Counter of the next write to the storage slot. | +| `exists` | `bool` | A flag indicating whether the storage slot is in the public data tree. | -### _PublicDataSnap_ +### `PublicDataSnap` | Field | Type | Description | | ------------------ | ------- | ------------------------------------------------------------------------ | -| _storage_slot_ | field | Storage slot. | -| _value_ | field | Value of the storage slot. | -| _override_counter_ | _field_ | Counter at which the _storage_slot_ is first written in the transaction. | -| _exists_ | _bool_ | A flag indicating whether the storage slot is in the public data tree. | +| `storage_slot` | `field` | Storage slot. | +| `value` | `field` | Value of the storage slot. | +| `override_counter` | `field` | Counter at which the `storage_slot` is first written in the transaction. | +| `exists` | `bool` | A flag indicating whether the storage slot is in the public data tree. | -### _PublicDataLeafPreimage_ +### `PublicDataLeafPreimage` | Field | Type | Description | | -------------- | ------- | ------------------------------ | -| _storage_slot_ | field | Storage slot. | -| _value_ | field | Value of the storage slot. | -| _next_slot_ | _field_ | Storage slot of the next leaf. | -| _next_index_ | _field_ | Index of the next leaf. | +| `storage_slot` | `field` | Storage slot. | +| `value` | `field` | Value of the storage slot. | +| `next_slot` | `field` | Storage slot of the next leaf. | +| `next_index` | `field` | Index of the next leaf. | diff --git a/yellow-paper/docs/constants.md b/yellow-paper/docs/constants.md new file mode 100644 index 00000000000..496eb93b1c5 --- /dev/null +++ b/yellow-paper/docs/constants.md @@ -0,0 +1,124 @@ +--- +title: Constants +--- + +:::warning Draft +All of these constants are subject to change, pending benchmarking, optimizations, and general protocol changes. +::: + +## Tree Constants + +See also: [state](./state/index.md). + +:::warning Tree Epochs +Note: we might introduce tree epochs, which will reduce the height of each epoch's tree, and means we don't need to estimate future network state growth in advance. +::: + + +| Name | Value | Description | +|---|---|---| +| `ARCHIVE_TREE_HEIGHT` | `27` | Prudent justification: 1 block/min \* 200 years ~= 2^27 blocks | +| `NOTE_HASH_TREE_HEIGHT` | `39` | Prudent justification: 10 tx/s \* 8 notes/tx \* 200 years. | +| `NULLIFIER_TREE_HEIGHT` | `42` | Prudent justification: \[Number of notes _ 2 (to allow a huge buffer for initialization nullifiers and other nullifier usage)] + \[ 2 _ Estimated number of contracts (to allow a huge buffer for contract class & instance nullifiers) ]. An estimate for the number of contracts ever to be deployed is debatable. | +| `PUBLIC_DATA_TREE_HEIGHT` | `39` | Prudent justification: 10 tx/s \* 8 storage slots/tx \* 200 years. | +| `L1_TO_L2_MESSAGE_TREE` | `33` | Prudent justification: 10 tx/s \* 10% of txs consuming a message \* 200 years. | +| `PRIVATE_FUNCTION_TREE_HEIGHT` | `5` | Note: this means a smart contract can only declare `2 ** 5 = 32` private functions. | + +For all trees, an empty leaf has value `0`. + +For all indexed merkle trees, the 0th leaf is the "zero predecessor leaf" with leaf preimage `{ value: 0, next_index: 0, next_value: 0}`. + +## Circuit Constants + +:::warning +Note: "per call" values might be much more flexible, once the data bus is introduced. These numbers are finger-in-the-air estimates of values that might be possible with the data bus. Benchmarking will be needed. +::: + +The statically-sized nature the kernel & rollup circuits will restrict the quantity of 'side effects' that a single call or transaction can create. + +### Per Call + + +| Name | Value | Description | +|---|---|---| +| `MAX_NEW_COMMITMENTS_PER_CALL` | 128 | +| `MAX_NEW_NULLIFIERS_PER_CALL` | 128 | +| `MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL` | 32 | +| `MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL` | 32 | +| `MAX_NEW_L2_TO_L1_MSGS_PER_CALL` | 4 | +| `MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL` | 128 | +| `MAX_PUBLIC_DATA_READS_PER_CALL` | 128 | +| `MAX_READ_REQUESTS_PER_CALL` | 128 | +| `MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL` | 1 | TODO: we shouldn't need this, given the reset circuit. | + +### Per Tx + + +| Name | Value | Description | +|---|---|---| +| `MAX_NEW_COMMITMENTS_PER_TX` | 128 | +| `MAX_NEW_NULLIFIERS_PER_TX` | 128 | +| `MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX` | 32 | +| `MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX` | 32 | +| `MAX_NEW_L2_TO_L1_MSGS_PER_TX` | 16 | +| `MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX` | 16 | +| `MAX_PUBLIC_DATA_READS_PER_TX` | 16 | +| `MAX_OPTIONALLY_REVEALED_DATA_LENGTH_PER_TX` | 4 | +| `MAX_READ_REQUESTS_PER_TX` | 128 | TODO: we shouldn't need this, given the reset circuit. | +| `MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX` | 4 | TODO: we shouldn't need this, given the reset circuit. | +| `NUM_ENCRYPTED_LOGS_HASHES_PER_TX` | 1 | +| `NUM_UNENCRYPTED_LOGS_HASHES_PER_TX` | 1 | + +## Block constants + +## Genesis Constants + +### Genesis Addresses + +:::warning +TODO: consider whether these addresses should be 'nice', small values, or whether these addresses should be derived in the same way as all other addresses (but with a deployer_address of `0x00`). + +TODO: some of these contracts will be baked into genesis. Some of them might need to be deployed as part of the first network 'upgrade', and so might end up being removed from this section. This is still being discussed. +::: + +| Name | Value | Description | +| --------------------------------------- | ------- | ------------------------------------------------------ | +| Space reserved for precompile addresses | | | +| `CONTRACT_CLASS_REGISTERER_ADDRESS` | 0x10000 | See [here](./contract-deployment/classes.md#genesis) | +| `CONTRACT_INSTANCE_DEPLOYER_ADDRESS` | 0x10001 | See [here](./contract-deployment/instances.md#genesis) | +| `FEE_JUICE_ADDRESS` | 0x10002 | TODO: consider at what stage this should be deployed. | + +### Genesis Archive Tree + +The 0th leaf of the archive tree will be hard-coded at genesis to be an empty collection of state trees and an empty previous header. + + + +### Genesis Nullifiers + +| Name | Value | Description | +| ------------------------------------------------------------------------------------ | ----- | ----------------------------------------------------- | +| The zero predecessor leaf. | TODO | Needed to make an indexed merkle tree work. | +| The zero predecessor leaf index. | `0` | Needed to make an indexed merkle tree work. | +| `GENESIS_NULLIFIER_LEAF_INDEX_OF_CLASS_ID_NULLIFIER_OF_CONTRACT_CLASS_REGISTERER` | `1` | See [here](./contract-deployment/classes.md#genesis). | +| `GENESIS_NULLIFIER_LEAF_INDEX_OF_DEPLOYMENT_NULLIFIER_OF_CONTRACT_CLASS_REGISTERER` | `2` | See [here](./contract-deployment/classes.md#genesis). | +| `GENESIS_NULLIFIER_LEAF_INDEX_OF_CLASS_ID_NULLIFIER_OF_CONTRACT_INSTANCE_DEPLOYER` | `3` | See [here](./contract-deployment/classes.md#genesis). | +| `GENESIS_NULLIFIER_LEAF_INDEX_OF_DEPLOYMENT_NULLIFIER_OF_CONTRACT_INSTANCE_DEPLOYER` | `4` | See [here](./contract-deployment/classes.md#genesis). | +| `GENESIS_NULLIFIER_LEAF_INDEX_OF_CLASS_ID_NULLIFIER_OF_FEE_JUICE_CONTRACT` | `5` | See [here](./contract-deployment/classes.md#genesis). | +| `GENESIS_NULLIFIER_LEAF_INDEX_OF_DEPLOYMENT_NULLIFIER_OF_FEE_JUICE_CONTRACT` | `6` | See [here](./contract-deployment/classes.md#genesis). | + +:::warning +TODO: hard-code the actual nullifier values, once the code has been frozen. +::: + + + +These verbose names are designed to get more specific from left to right. + +## Precompile Constants + +See the [precompiles](./addresses-and-keys/precompiles.md#constants) section. + +## Hashing constants + +See the [hashing] section. diff --git a/yellow-paper/docs/contract-deployment/classes.md b/yellow-paper/docs/contract-deployment/classes.md index 7069d497479..aff9bdc64ad 100644 --- a/yellow-paper/docs/contract-deployment/classes.md +++ b/yellow-paper/docs/contract-deployment/classes.md @@ -12,9 +12,9 @@ Read the following discussions for additional context: - [Abstracting contract deployment](https://forum.aztec.network/t/proposal-abstracting-contract-deployment/2576) - [Implementing contract upgrades](https://forum.aztec.network/t/implementing-contract-upgrades/2570) - [Contract classes, upgrades, and default accounts](https://forum.aztec.network/t/contract-classes-upgrades-and-default-accounts/433) -::: + ::: -## Structure +## `ContractClass` The structure of a contract class is defined as: @@ -41,7 +41,7 @@ public_bytecode_commitment = calculate_commitment(packed_public_bytecode) contract_class_id = pedersen([artifact_hash, private_functions_root, public_bytecode_commitment], GENERATOR__CLASS_IDENTIFIER_V1) ``` -Private Functions are hashed into Function Leaves before being merkleized into a tree of height `FUNCTION_TREE_HEIGHT=5`. Empty leaves have value `0`. A poseidon hash is used. The AVM public bytecode commitment is calculated as [defined in the Public VM section](../public-vm/bytecode-validation-circuit.md#committed-representation). +Private Functions are hashed into Function Leaves before being merkleized into a tree of height [`FUNCTION_TREE_HEIGHT`](../constants.md#tree-constants). Empty leaves have value `0`. A poseidon hash is used. The AVM public bytecode commitment is calculated as [defined in the Public VM section](../public-vm/bytecode-validation-circuit.md#committed-representation). ### Private Function @@ -62,19 +62,19 @@ Also note the lack of commitment to the function compilation artifact. Even thou Even though not enforced by the protocol, it is suggested for the `artifact_hash` to follow this general structure, in order to be compatible with the definition of the [`broadcast` function below](#broadcast). ``` -private_functions_artifact_leaves = artifact.private_functions.map(fn => +private_functions_artifact_leaves = artifact.private_functions.map(fn => sha256(fn.selector, fn.metadata_hash, sha256(fn.private_bytecode)) ) private_functions_artifact_tree_root = merkleize(private_functions_artifact_leaves) -unconstrained_functions_artifact_leaves = artifact.unconstrained_functions.map(fn => +unconstrained_functions_artifact_leaves = artifact.unconstrained_functions.map(fn => sha256(fn.selector, fn.metadata_hash, sha256(fn.unconstrained_bytecode)) ) unconstrained_functions_artifact_tree_root = merkleize(unconstrained_functions_artifact_leaves) artifact_hash = sha256( private_functions_artifact_tree_root, - unconstrained_functions_artifact_tree_root, + unconstrained_functions_artifact_tree_root, artifact_metadata, ) ``` @@ -125,7 +125,7 @@ function register( artifact_hash: Field, private_functions_root: Field, packed_public_bytecode: Field[], -) +) assert is_valid_packed_public_bytecode(packed_public_bytecode) version = 1 @@ -142,15 +142,13 @@ Note that emitting the `contract_class_id` as a nullifier (the `contract_class_i ### Genesis -The `ContractClassRegisterer` will need to exist from the genesis of the Aztec Network, otherwise nothing will ever be publicly deployable to the network. The Class Nullifier for the `ContractClassRegisterer` contract will be pre-inserted into the genesis nullifier tree at leaf index `GENESIS_NULLIFIER_LEAF_INDEX_OF_CONTRACT_CLASS_REGISTERER_CLASS_ID_NULLIFIER=1`. The canonical instance will be deployed at `CONTRACT_CLASS_REGISTERER_ADDRESS=0x10000`, and its Deployment Nullifier will be inserted at `GENESIS_NULLIFIER_LEAF_INDEX_OF_CONTRACT_CLASS_REGISTERER_DEPLOYMENT_NULLIFIER=2`. - - +The `ContractClassRegisterer` will need to exist from the genesis of the Aztec Network, otherwise nothing will ever be publicly deployable to the network. The Class Nullifier for the `ContractClassRegisterer` contract will be pre-inserted into the genesis nullifier tree at leaf index [`GENESIS_NULLIFIER_LEAF_INDEX_OF_CONTRACT_CLASS_REGISTERER_CLASS_ID_NULLIFIER`](../constants.md#genesis-constants). The canonical instance will be deployed at [`CONTRACT_CLASS_REGISTERER_ADDRESS`](../constants.md#genesis-constants), and its Deployment Nullifier will be inserted at [`GENESIS_NULLIFIER_LEAF_INDEX_OF_CONTRACT_CLASS_REGISTERER_DEPLOYMENT_NULLIFIER`](../constants.md#genesis-constants). ### Broadcast -The `ContractClassRegisterer` has an additional private `broadcast` functions that can be used for broadcasting on-chain the bytecode, both ACIR and Brillig, for private functions and unconstrained in the contract. Any user can freely call this function. Given that ACIR and Brillig [do not have a circuit-friendly commitment](../bytecode/index.md), it is left up to nodes to perform this check. +The `ContractClassRegisterer` has an additional private `broadcast` functions that can be used for broadcasting on-chain the bytecode, both ACIR and Brillig, for private functions and unconstrained in the contract. Any user can freely call this function. Given that ACIR and Brillig [do not have a circuit-friendly commitment](../bytecode/index.md), it is left up to nodes to perform this check. Broadcasted contract artifacts that do not match with their corresponding `artifact_hash`, or that reference a `contract_class_id` that has not been broadcasted, can be safely discarded. diff --git a/yellow-paper/docs/contract-deployment/instances.md b/yellow-paper/docs/contract-deployment/instances.md index 9d5e333824d..eb31836761f 100644 --- a/yellow-paper/docs/contract-deployment/instances.md +++ b/yellow-paper/docs/contract-deployment/instances.md @@ -1,5 +1,7 @@ # Contract instances + + A contract instance is a concrete deployment of a [contract class](./classes.md). A contract instance always references a contract class, which dictates what code it executes when called. A contract instance has state (both private and public), as well as an address that acts as its identifier. A contract instance can be called into. ## Requirements @@ -9,7 +11,7 @@ A contract instance is a concrete deployment of a [contract class](./classes.md) - A user calling into an address must be able to prove that it has not been deployed. This allows the executor to prove that a given call in a transaction is unsatisfiable and revert accordingly. - A user should be able to privately call into a contract without publicly deploying it. This allows private applications to deploy contracts without leaking information about their usage. -## `ContractInstance` structure +## `ContractInstance` The structure of a contract instance is defined as: @@ -32,7 +34,7 @@ Contract instances have a `version` field that identifies the schema of the inst ### Address -The address of the contract instance is computed as the hash of the elements in the structure above, as defined in [the addresses and keys section](../addresses-and-keys/specification.md#address). This computation is deterministic, which allows any user to precompute the expected deployment address of their contract, including account contracts. +The address of the contract instance is computed as the hash of the elements in the structure above, as defined in [the addresses and keys section](../addresses-and-keys/address.md#address). This computation is deterministic, which allows any user to precompute the expected deployment address of their contract, including account contracts. ### Deployer @@ -75,7 +77,7 @@ Contract constructors are not enshrined in the protocol, but handled at the appl These checks are embedded in the application circuits themselves. The constructor emits an Initialization Nullifier when it is invoked, which prevents it from being called more than once. The constructor code must also check that its own selector and the arguments for the call match the ones in the address preimage, which are supplied via an oracle call. -All non-constructor functions in the contract should require a merkle membership proof for the Initialization Nullifier, to prevent them from being called before the constructor is invoked. Nevertheless, a contract may choose to allow some functions to be called before initialization, such as in the case of [Diversified and Stealth account contracts](../addresses-and-keys/diversified-and-stealth.md). +All non-constructor functions in the contract should require a merkle membership proof for the Initialization Nullifier, to prevent them from being called before the constructor is invoked. Nevertheless, a contract may choose to allow some functions to be called before initialization, such as in the case of [Diversified and Stealth account contracts](../addresses-and-keys/diversified-and-stealth.md). Removing constructors from the protocol itself simplifies the kernel circuit, and decoupling Initialization from Public Deployments allows users to keep contract instances private if they wish to do so. @@ -101,16 +103,16 @@ The pseudocode for the process described above is the following: ``` function deploy ( - salt: Field, - contract_class_id: Field, - initialization_hash: Field, - portal_contract_address: Field, - public_keys_hash: Field, + salt: Field, + contract_class_id: Field, + initialization_hash: Field, + portal_contract_address: Field, + public_keys_hash: Field, universal_deploy?: boolean, ) assert nullifier_exists silo(contract_class_id, ContractClassRegisterer) assert is_valid_eth_address(portal_contract_address) - + deployer = if universal_deploy then zero else msg_sender version = 1 address = compute_address(version, salt, deployer, contract_class_id, initialization_hash, portal_contract_address, public_keys_hash) @@ -126,9 +128,7 @@ The `ContractInstanceDeployer` contract provides two implementations of the `dep ### Genesis -The `ContractInstanceDeployer` will need to exist from the genesis of the Aztec Network, otherwise nothing will ever be deployable to the network. The Class Nullifier for the `ContractInstanceDeployer` contract will be pre-inserted into the genesis nullifier tree at leaf index `GENESIS_NULLIFIER_LEAF_INDEX_OF_CONTRACT_INSTANCE_DEPLOYER_CLASS_ID_NULLIFIER=3`. The canonical instance will be deployed at `CONTRACT_INSTANCE_DEPLOYER_ADDRESS=0x10001`, and its Deployment Nullifier will be inserted at `GENESIS_NULLIFIER_LEAF_INDEX_OF_CONTRACT_INSTANCE_DEPLOYER_DEPLOYMENT_NULLIFIER=4`. - - +The `ContractInstanceDeployer` will need to exist from the genesis of the Aztec Network, otherwise nothing will ever be deployable to the network. The Class Nullifier for the `ContractInstanceDeployer` contract will be pre-inserted into the genesis nullifier tree at leaf index [`GENESIS_NULLIFIER_LEAF_INDEX_OF_CONTRACT_INSTANCE_DEPLOYER_CLASS_ID_NULLIFIER`](../constants.md#genesis-constants). The canonical instance will be deployed at [`CONTRACT_INSTANCE_DEPLOYER_ADDRESS`](../constants.md#genesis-constants), and its Deployment Nullifier will be inserted at [`GENESIS_NULLIFIER_LEAF_INDEX_OF_CONTRACT_INSTANCE_DEPLOYER_DEPLOYMENT_NULLIFIER`](../constants.md#genesis-constants). @@ -140,9 +140,11 @@ The Kernel Circuit, both private and public, is responsible for verifying that t - The [function selector](./classes.md#private-function) being executed is part of the `contract_class_id`, verified via a Merkle membership proof of the selector in the functions tree of the Contract Class. Specific to private functions: + - The hash of the `verification_key` matches the `vk_hash` defined in the corresponding [Private Function](./classes.md#private-function) for the Contract Class. Specific to public functions: + - The bytecode loaded by the [AVM](../public-vm/avm.md) for the contract matches the `bytecode_commitment` in the contract class, verified using the [bytecode validation circuit](../public-vm/bytecode-validation-circuit.md). - The contract Deployment Nullifier has been emitted, or prove that it hasn't, in which case the transaction is expected to revert. This check is done via a merkle (non-)membership proof of the Deployment Nullifier. Note that a public function should be callable in the same transaction in which its contract Deployment Nullifier was emitted. @@ -172,4 +174,4 @@ While it is appealing to allow a user to privately create a new contract instanc This approach requires that constructors are only invoked as part of Public Deployment, so constructors would require an additional check for `msg_sender` being the canonical `ContractInstanceDeployer`. Furthermore, to ensure that an instance constructor is properly run, the `ContractInstanceDeployer` would need to know the selector for the instance constructor, which now needs to be part of the Contract Class, re-enshrining it into the protocol. Last, being able to keep agreements (contracts) private among their parties is commonplace in the traditional world, so there is a compelling argument for keeping this requirement. -Alternatively, we could remove constructor abstraction altogether, and have the Private Kernel Circuit check for the Deployment Nullifier, much like the Public Kernel Circuit does. However, this hurts Diversified and Stealth account contracts, which now require an explicit deployment and cannot be used directly. \ No newline at end of file +Alternatively, we could remove constructor abstraction altogether, and have the Private Kernel Circuit check for the Deployment Nullifier, much like the Public Kernel Circuit does. However, this hurts Diversified and Stealth account contracts, which now require an explicit deployment and cannot be used directly. diff --git a/yellow-paper/docs/cryptography/hashing/hashing.md b/yellow-paper/docs/cryptography/hashing/hashing.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/yellow-paper/docs/cryptography/hashing/pedersen.md b/yellow-paper/docs/cryptography/hashing/pedersen.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/yellow-paper/docs/cryptography/index.md b/yellow-paper/docs/cryptography/index.md new file mode 100644 index 00000000000..6cebf78ff71 --- /dev/null +++ b/yellow-paper/docs/cryptography/index.md @@ -0,0 +1,8 @@ +# Cryptography + +:::note +This section only comes at the beginning because it contains foundational cryptographic definitions that other sections use. +It's not recommended that you read this section first, because you'll probably give up before getting to the interesting sections. +::: + + diff --git a/yellow-paper/docs/cryptography/merkle-trees.md b/yellow-paper/docs/cryptography/merkle-trees.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/yellow-paper/docs/cryptography/proving-system/data-bus.md b/yellow-paper/docs/cryptography/proving-system/data-bus.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/yellow-paper/docs/cryptography/protocol-overview.md b/yellow-paper/docs/cryptography/proving-system/overview.md similarity index 99% rename from yellow-paper/docs/cryptography/protocol-overview.md rename to yellow-paper/docs/cryptography/proving-system/overview.md index 565da914b17..f7f786a257e 100644 --- a/yellow-paper/docs/cryptography/protocol-overview.md +++ b/yellow-paper/docs/cryptography/proving-system/overview.md @@ -122,4 +122,4 @@ To batch-verify multiple opening proofs, we use the technique articulated in the The following block diagrams describe the components used by the client-side and server-side Provers when computing client proofs and rollup proofs respectively. -![proof-system-components](../cryptography/images/proof-system-components.png) +![proof-system-components](../images/proof-system-components.png) diff --git a/yellow-paper/docs/cryptography/performance-targets.md b/yellow-paper/docs/cryptography/proving-system/performance-targets.md similarity index 95% rename from yellow-paper/docs/cryptography/performance-targets.md rename to yellow-paper/docs/cryptography/proving-system/performance-targets.md index 27a549310de..e6d79df5028 100644 --- a/yellow-paper/docs/cryptography/performance-targets.md +++ b/yellow-paper/docs/cryptography/proving-system/performance-targets.md @@ -22,7 +22,7 @@ The following is a list of the relevant properties that affect the performance o - Time to compute a 2-to-1 rollup proof - Memory required to compute a 2-to-1 rollup proof - | process | percent of block production time allocated to process | @@ -62,7 +62,7 @@ These are very rough estimates that could use further evaluation and validation! ### Proof size -The MVP wishes to target a tx through put of 10 tx per second. +The MVP wishes to target a tx throughput of 10 txs per second. Each Aztec node (not sequencer/prover, just a regular node that is sending transactions) needs to download `10*proof_size` bytes of data to keep track of the mempool. However, this is the _best case_ scenario. @@ -84,7 +84,7 @@ To support 100 transactions per second we would require a proof size of $8$ kilo The critical UX factor. To measure prover time for a transaction, we must first define a baseline transaction we wish to measure and the execution environment of the Prover. -As we build+refine our MVP, we want to avoid optimising the best-case scenario (i.e. the most basic tx type, a token transfer). Instead we want to ensure that transactions of a "moderate" complexity are possible with consuer hardware. +As we build+refine our MVP, we want to avoid optimising the best-case scenario (i.e. the most basic tx type, a token transfer). Instead we want to ensure that transactions of a "moderate" complexity are possible with consumer hardware. As a north star, we consider a private swap, and transpose it into an Aztec contract. diff --git a/yellow-paper/docs/cross-chain-communication/da.md b/yellow-paper/docs/data-publication-and-availability/index.md similarity index 99% rename from yellow-paper/docs/cross-chain-communication/da.md rename to yellow-paper/docs/data-publication-and-availability/index.md index fe94d32b4f3..4e5aed122a5 100644 --- a/yellow-paper/docs/cross-chain-communication/da.md +++ b/yellow-paper/docs/data-publication-and-availability/index.md @@ -22,7 +22,7 @@ Security is often used quite in an unspecific manner, "good" security etc, witho - **Liveness**: Eventually something good will happen. - **Safety**: Nothing bad will happen. -::: + ::: In the context of blockchain, this _security_ is defined by the confirmation rule, while this can be chosen individually by the user, our validating light node (L1 bridge) can be seen as a user, after all, it's "just" another node. For the case of a validity proof based blockchain, a good confirmation rule should satisfy the following sub-properties (inspired by [Sreeram's framing](https://twitter.com/sreeramkannan/status/1683735050897207296)): @@ -43,6 +43,8 @@ In particular, we will be looking at what is required to give observers (nodes) ## Quick Catch-up + + A rollup is broadly speaking a blockchain that put its blocks on some other chain (the host) to make them available to its nodes. Most rollups have a contract on this host blockchain which validates its state transitions (through fault proofs or validity proofs) taking the role of a full-validating light-node, increasing the accessibility of running a node on the rollup chain, making any host chain node indirectly validate its state. With its state being validated by the host chain, the security properties can eventually be enforced by the host-chain if the rollup chain itself is not progressing. Bluntly, the rollup is renting security from the host. The essential difference between a L1 and a rollup then comes down to who are required for block production (liveness) and to convince the validating light-node (security), for the L1 it is the nodes of the L1, and for the Rollup the nodes of its host (eventually). This in practice means that we can get some better properties for how easy it is to get sufficient assurance that no trickery is happening. @@ -134,7 +136,7 @@ While the committee is small, it seems like they can ensure honesty through the It is not fully clear how often blocks would be relayed to the hotshot contract for consumption by our rollup, but the team says it should be frequent. Cost is estimated to be ~400K gas. -## Aztec Specific Data +## Aztec-specific Data As part of figuring out the data throughput requirements, we need to know what data we need to publish. In Aztec we have a bunch of data with varying importance; some being important to **everyone** and some being important to **someone**. @@ -216,6 +218,8 @@ For every throughput column, we insert 3 marks, for everyone, someone and the to > **Disclaimer**: Remember that these fractions for available space are pulled out of my ass. + + With these numbers at hand, we can get an estimate of our throughput in transactions based on our storage medium. ## One or multiple data layers? diff --git a/yellow-paper/docs/decentralization/block-production.md b/yellow-paper/docs/decentralization/block-production.md index 88195545bb3..f554c304865 100644 --- a/yellow-paper/docs/decentralization/block-production.md +++ b/yellow-paper/docs/decentralization/block-production.md @@ -27,9 +27,9 @@ We should probably introduce the PXE somewhere - **CPU**: Help - **Network**: 40KB for a transaction with proof (see [P2P network](./p2p-network.md#network-bandwidth)). Assuming gossiping grows the data upload/download 10x, ~400KB per tx. With 10 tx/s that's 4MB/s or 32mb/s. -- **Storage**: [~1548 bytes per transaction](../cross-chain-communication/da.md#aztec-specific-data) + tree overhead, ~ 0.4 TB per year. +- **Storage**: [~1548 bytes per transaction](../data-publication-and-availability/index.md#aztec-specific-data) + tree overhead, ~ 0.4 TB per year. - **RAM**: Help -::: + ::: ### Sequencers @@ -65,9 +65,9 @@ An Aztec Prover is a full node that is producing Aztec-specific zero knowledge ( Mostly as full nodes. The compute and memory requirements might be larger since it needs to actually build the large proofs. Note, that the prover don't directly need to be a full node, merely have access to one. ::: -### Other types of network nodes +### Other types of network node -- [Validating Light nodes](../cross-chain-communication/index.md) +- [Validating Light nodes](../l1-smart-contracts/index.md) - Maintain a state root and process block headers (validate proofs), but do not store the full state. - The L1 bridge is a validating light node. - Can be used to validate correctness of information received from a data provider. @@ -113,7 +113,7 @@ Anyone ->> Network: eligible as a sequencer - In Diagram - add a dedicated timeline from the block production's PoV - get rid of "pre-confirmed" -::: + ::: ![Governance Summary Image](./images/Aztec-Block-Production-1.png) @@ -258,11 +258,11 @@ Future Aztec versions will receive rewards based on their staked amount, as dete With the rest of the protocol _mostly_ well defined, Aztec Labs now expects to begin a series of sprints dedicated towards economic analysis and modeling with [Blockscience](https://block.science/) throughout Q1-Q2 2024. This will result in a public report and associated changes to this documentation to reflect the latest thinking. ::: -## Mev-boost +## MEV-boost :::success -##### About MEV on Aztec +### About MEV on Aztec Within the Aztec Network, "MEV" (Maximal Extractable Value) can be considered "mitigated", compared to "public" blockchains where all transaction contents and their resulting state transitions are public. In Aztec's case, MEV is _generally_ only applicable to [public functions](#) and those transactions that touch publicly viewable state. ::: @@ -281,7 +281,7 @@ Initially it's expected that the negotiations and commitment could be facilitate ## Diagrams -#### Happy path +### Happy path :::danger TODO I'm not fully understanding the different groups, is the aztec network just the node software or 👀? Maybe coloring is nice to mark what is contracts and entities or groups of entities. Otherwise seems quite nice. @@ -318,7 +318,7 @@ Sequencers ->> Contract: exit() Sequencers --> Sequencers: wait 7 days ``` -#### Voting on upgrades +### Voting on upgrades In the initial implementation of Aztec, sequencers may vote on upgrades alongside block proposals. If they wish to vote alongside an upgrade, they signal by updating their client software or an environment configuration variable. If they wish to vote no or abstain, they do nothing. Because the "election" is randomized, the voting acts as a random sampling throughout the current sequencer set. This implies that the specific duration of the vote must be sufficiently long and RANDAO sufficiently randomized to ensure that the sampling is reasonably distributed. @@ -347,7 +347,7 @@ loop Happy Path Block Production end ``` -#### Backup mode +### Backup mode In the event that no one submits a valid block proposal, we introduce a "backup" mode which enables a first come first serve race to submit the first proof to the L1 smart contracts. diff --git a/yellow-paper/docs/decentralization/governance.md b/yellow-paper/docs/decentralization/governance.md index 0a66ea4e472..249b3149edf 100644 --- a/yellow-paper/docs/decentralization/governance.md +++ b/yellow-paper/docs/decentralization/governance.md @@ -4,9 +4,9 @@ This is a first draft which articulates the latest thinking on governance & upgrades. It is subject to change and further review - ultimately needing team-wide understanding and approval. Please take this as a proposal, not as truth. ::: -### Summary +## Summary -We propose an immutable governance & upgrade mechanism for The Aztec Network ("Aztec") that is comprised of a version registry, which points to deployments ("instances", used interchangeably) of Aztec. +We propose an immutable governance & upgrade mechanism for The Aztec Network ("Aztec") that comprises a version registry, which points to deployments ("instances", used interchangeably) of Aztec. These instances may choose to be immutable themselves, or have governance that evolves over time alongside the community. The governance contract will keep track of governance votes, from the current version of Aztec, as well as direct token votes from the community, in order to provide some form of checks and balances. @@ -14,7 +14,7 @@ The version registry will keep track of all historical versions of Aztec & provi ![Governance Summary Image](./images/Aztec-Governance-Summary-1.png) -### Rewards +## Rewards We propose introducing a governance "version registry" which keeps track of a) which deployments of Aztec have been canonical, and b) which instances currently have tokens staked to them, specifically in order to issue a consistent, single new token in the form of _incentives_ or "rollup/block rewards". @@ -24,11 +24,16 @@ Given that deployments may be immutable, it is necessary to ensure that there ar Beyond making it easier to understand for users, having a single token across all deployments is necessary to ensure that all instances are all utilizing the same token due to ecosystem cohesive and business development efforts, for example, having reliable onramps and wallets. -### Initial deployment + + +## Initial deployment Upon initial deployment, there will be an immutable set of governance contracts which maintain the version registry, and an initial immutable instance of the rollup which will be the first "canonical" deployment. -The initial instance will be called "Aztec v0" and (the current thinking is that v0) will not include the ability to process user transactions. Sequencers can register for Fernet's sequencer selection algorithm by staking tokens to that particular instance, and practice proposing blocks on mainnet prior to deciding to "go live" with v1, which _does_ enable the processing of user transactions. This instance would then _"restake"_ these tokens within the governance contract, to have a voting weight equal to the amount of tokens staked by it's sequencer set. This is in order to ensure that the sequencer selection algorithm is working properly and the community of operators themselves can decide what happens to the network next, i.e., if it's ready to actually "go live" with transactions. It will also serve as a production readiness test of the upgradeability. In the event that these v0 tests are unable to be successfully completed as expected, the community (with potential foundation approval) may need to redeploy and try again. +The initial instance will be called "Aztec v0" and (the current thinking is that v0) will not include the ability to process user transactions. Sequencers can register for Fernet's sequencer selection algorithm by staking tokens to that particular instance, and practice proposing blocks on mainnet prior to deciding to "go live" with v1, which _does_ enable the processing of user transactions. This instance would then _"restake"_ these tokens within the governance contract, to have a voting weight equal to the amount of tokens staked by its sequencer set. This is in order to ensure that the sequencer selection algorithm is working properly and the community of operators themselves can decide what happens to the network next, i.e., if it's ready to actually "go live" with transactions. It will also serve as a production readiness test of the upgradeability. In the event that these v0 tests are unable to be successfully completed as expected, the community (with potential foundation approval) may need to redeploy and try again. ![Initial Deployment Image](./images/Aztec-Governance-Summary-3.png) @@ -36,26 +41,42 @@ The ability to upgrade to v1 is articulated below, and should follow a "happy pa ![Version 1 Deployment Image](./images/Aztec-Governance-Summary-4.png) -### Proposing a new version +## Proposing a new version + + The current canonical rollup ("current rollup") can at any point propose voting on a new instance to become canonical and added to the governance version registry contracts. It can have it's own logic for determining when it makes sense to do so, and trigger the formal governance vote. In the initial deployment it's expected to be done as articulated in the empire stakes back, where a sequencer must flag a desire to upgrade signal as part of Fernet's proposal phase, i.e., they won a random leader election, and a majority of sequencers must do so over a specific time horizon, e.g., 7 days. In addition to the current rollup implementation deciding to propose a vote, token holders can lock a sufficient amount of tokens for a sufficient amount of time in order to bypass the current rollup and propose a new version to become canonical next. This can be used in the scenario that the rollup implementation is so buggy it is unable to propose a new rollup to replace itself, or is due to potential community disagreement. In this scenario of disagreement, it is likely to be a very contentious action - as it implies a large token holder actively disagrees with the current rollup's sequencer set. + + - Current thinking is this would require locking 1% of _total supply_ for 2 years. - These tokens must be eligible for voting, as defined below. In a worst case scenario, the rollup's sequencer set could be malicious and censor potentially honest upgrade proposals from going through. In this scenario, there needs to be the ability to add a proposal "to the queue" via the token locking mechanism articulated above which is guaranteed to be executed when the previous vote completes. -#### Quorum +### Quorum For any proposal to be considered valid and ready for voting, there must be a quorum of voting power eligible to participate. The purpose of this is to ensure that the network cannot be upgraded without a minimum of participation, keeping the governance more protected from takeover at genesis. The exact amount of voting power required is to be determined through modelling, but we expect that around 5% of total supply. Assuming that 20% of the supply is circulating at launch and that 25% of this is staked towards the initial instance, this would allow the initial instance to reach quorum on its own. -### Voting +## Voting -#### Participation +### Participation Aztec's governance voting occurs within the governance contract, and the tokens being utilized must be "locked within governance" i.e., non-transferable. @@ -63,7 +84,7 @@ Any token holder is able to directly vote via an interaction with the governance The current canonical rollup can choose to implement its internal voting however it would like, with the weight of the tokens staked in that instance. This is likely to be a majority of voting weight, which we can reliably assume will vote each time. Generally this addresses the problems of low token holder participation! In the initial instance, we envision a version of the Empire Stakes back, where sequencers are voting during part of their block proposal phases. Not all sequencers will win a block proposal/election during the time period of the vote, this leads it to being a randomized sampling of the current sequencer set. -#### Exiting +### Exiting The duration of the token lock depends on the action a user participated in. Tokens that have been locked to vote "yes" to changing the canonical instance are locked within the governance contract until the "upgrade" has been performed _or_ when the voting period ends without the proposal gaining sufficient traction to reach quorum. @@ -71,7 +92,7 @@ Tokens whose power did not vote "yes" are free to leave whenever they chose. Thi Rollup instances themselves will need to deposit their stake into the governance, in order to earn rewards and participate within the vote. Further, they can apply their own enter/exit delays on top of the governance contract's. For example to ensure stability of the sequencer set over short timeframes, if using $AZTC stake as a requirement for sequencing, they may wish to impose longer entry and exit queues. -#### Results +### Results A vote is defined as passing if a majority of the voting weight votes "yes" to the proposal. @@ -83,17 +104,17 @@ If the vote passes, and a new rollup has been determined to be the next canonica Portals that blindly follow governance inherently assume that the new inbox/outbox will always be backwards compatible. If it is not, it might break the portals. ::: -### Timing +## Timing -#### Phase 1 - Setup +### Phase 1 - Setup After the current canonical rollup, or a sufficient number of tokens are locked in governance, there is a ~3-7 day preparation period where users get their tokens "ready" for voting. i.e., withdraw & deposit/lock for the vote, or choose a suitable delegate. -#### Phase 2 - Voting +### Phase 2 - Voting After setup has completed, there is a 7-30 day (TBD) period during which votes can land on the governance contract. In practice, we envision a majority of this voting happening in the current canonical instance and the voting weight of the current canonical instance being sufficient to reach quorum without any additional token delegation. -#### Phase 3 - Execution Delay (Timelock) +### Phase 3 - Execution Delay (Timelock) If a vote passes, there is a timelocked period before it becomes the new canonical rollup. This specific time period must be more than a minimum, e.g., 3 days, but is defined by the current rollup and in v1 may be controlled by both the sequencers in a happy path, and an emergency security council in a worst case scenario (articulated [below](#Emergency-mode)). In a typical happy path scenario, we suggest this is at least 30 days, and in an emergency, the shortest period possible. A maximum period may also be defined, e.g., 60 days to ensure that the network cannot be kept from upgrading by having a delay of 200 years. @@ -101,11 +122,11 @@ If a vote passes, there is a timelocked period before it becomes the new canonic It is worth acknowledging that this configurability on upgrade delay windows will likely be flagged on L2 beat as a "medium" centralization risk, due to the ability to quickly upgrade the software (e.g., a vacation attack). Explicitly this decision could cause us to be labeled a "stage 1" rather than "stage 2" rollup. However, if a vote is reasonably long, then it should be fine as you can argue that the "upgrade period" is the aggregate of all 3 periods. ::: -### Diagrams +## Diagrams -Importantly we differentiate between `Aztec Governance`, and the governance of a particular instance of Aztec. This diagram articulates the high level of Aztec Governance, specifically how the network can deploy new versions overtime which will be part of a cohesive ecosystem, sharing a single token. In this case, we are not concerned with how the current canonical rollup chooses to implement it's decision to propose a new version, nor how it implements voting. It can be reasonably assumed that this is a version of The Empire Stakes back, where a majority of the current rollup sequencers are agreeing to propose and want to upgrade. +Importantly we differentiate between `Aztec Governance`, and the governance of a particular instance of Aztec. This diagram articulates the high level of Aztec Governance, specifically how the network can deploy new versions over time which will be part of a cohesive ecosystem, sharing a single token. In this case, we are not concerned with how the current canonical rollup chooses to implement its decision to propose a new version, nor how it implements voting. It can be reasonably assumed that this is a version of The Empire Stakes back, where a majority of the current rollup sequencers are agreeing to propose and want to upgrade. -#### Happy path +### Happy path ```mermaid sequenceDiagram @@ -133,7 +154,7 @@ Next Rollup ->> Version Registry: markCanonical(nextRollup) Sequencers ->> Next Rollup: Proposing new blocks here! ``` -#### "Bricked" rollup proposals +### "Bricked" rollup proposals In this diagram, we articulate the scenario in which the current canonical rollup contains bugs that result in it being unable to produce not only a block, but a vote of any kind. In this scenario, someone or a group (Lasse refers to as the "unbrick DAO") may lock 1% (specific # TBD) of total supply in order to propose a new canonical rollup. It is expected that this scenario is very unlikely, however, we believe it to be a nice set of checks and balances between the token holders and the decisions of the current rollup implementation. @@ -199,27 +220,27 @@ Next Rollup ->> Version Registry: markCanonical(nextRollup) Sequencers ->> Next Rollup: Proposing new blocks here! ``` -### Emergency mode +## Emergency mode Emergency mode is proposed to be introduced to the initial instance "v0" or "v1" of Aztec, whatever the first instance or deployment is. Emergency mode **will not be included as part of the canonical governance contracts or registry**. If future deployments wish to have a similar security council, they can choose to do so. In this design, the current rollup can determine the timelock period as articulated above, within some predefined constraints, e.g., 3-30 days. Explicitly, the current rollup can give a security council the ability to define what this timelock period may be, and in the case of a potential vulnerability or otherwise, may be well within it's rights to choose the smallest value defined by the immutable governance contract to ensure that the network is able to recover and come back online as quickly as possible. ![Emergency Mode Image](./images/Aztec-Governance-Summary-4.png) -#### Unpausing by default +### Unpausing by default In the first instance, it's expected that this security council can _only_ pause the rollup instance, not make any other changes to the instance's functionality. It is important that after N days (e.g., 180), or after another rollup has been marked canonical and Y days (e.g., 60), this rollup _must_ become unpaused eventually - otherwise it's practically bricked from the perspective of those users choosing immutable portals, and could leave funds or other things belonging to users (e.g., identity credentials or something wacky) permanently inside of it. The same is true for all future instances that have pause functionalities. -#### Removing the emergency mode +### Removing the emergency mode The emergency mode articulated here may be implemented as part of the next instance of Aztec - "v1" or whatever it ends up being called, when mainnet blocks are enabled. The current sequencer set on v0 (the initial instance) would then need to vote as outlined above on marking this new deployment as the "canonical v1" or predecessor to the initial instance. This would then have all of the portal contracts follow v1, which may or may not have other [training wheels](https://discourse.aztec.network/t/aztec-upgrade-training-wheels/641). If the community wishes, they can always deploy a new instance of the rollup which removes the emergency mode and therefore the pause-only multisig. -### Contract implementation +## Contract implementation :::danger TO DO ::: -### Glossary +## Glossary :::danger TO DO diff --git a/yellow-paper/docs/decentralization/p2p-network.md b/yellow-paper/docs/decentralization/p2p-network.md index addea0c233e..791fb1346d7 100644 --- a/yellow-paper/docs/decentralization/p2p-network.md +++ b/yellow-paper/docs/decentralization/p2p-network.md @@ -1,8 +1,10 @@ # P2P Network + + ## Requirements for a P2P Network -When a rollup is successfully published, the state transitions it produces are published along with it, making them publicly available. This broadcasted state does not depend on the Aztec network for its persistence or distribution. Transient data however, such as pending user transactions for inclusion in future rollups, does rely on the network for this. It is important that the network provides a performant, permissionless and censorship resistant mechanism for the effective propagation of these transactions to all network participants. Without this, transactions may be disadvantaged and the throughput of the network will deteriorate. +When a rollup is successfully published, the state transitions it produces are published along with it, making them publicly available. This broadcasted state does not depend on the Aztec network for its persistence or distribution. Transient data however, such as pending user transactions for inclusion in future rollups, does rely on the network for availability. It is important that the network provides a performant, permissionless and censorship resistant mechanism for the effective propagation of these transactions to all network participants. Without this, transactions may be disadvantaged and the throughput of the network will deteriorate. Other data that may be transmitted over the network are the final rollup proofs to be submitted to the rollup contract, however the size and rate of these payloads should not make any meaningful impact on the bandwidth requirements. @@ -39,16 +41,16 @@ Aztec Node instances will offer a JSON RPC interface for consumption by a user's ### Network Bandwidth -Transactions are composed of several data elements and can vary in size. The transaction size is determined largely by the private kernel proof and whether the transaction deloys any public bytecode. A typical transaction that emits a private note and an unencrypted log, makes a public call and contains a valid proof would consume approximately 40Kb of data. A transaction that additionally deploys a contract would need to transmit the public bytecode on top of this. +Transactions are composed of several data elements and can vary in size. The transaction size is determined largely by the private kernel proof and whether the transaction deploys any public bytecode. A typical transaction that: emits a private note and an unencrypted log, makes a public call and contains a valid proof, would consume approximately 40Kb of data. A transaction that additionally deploys a contract would need to transmit the public bytecode on top of this. | Element | Size | | -------------------------------------------- | ----- | -| Public Inputs, Public Calls and Emitted Logs | ~8Kb | -| Private Kernel Proof | ~32Kb | +| Public Inputs, Public Calls and Emitted Logs | ~8KB | +| Private Kernel Proof | ~32KB | -If we take 2 values of transaction throughput of 10 and 100 transactions per second, we can arrive at average network bandwidth requirements of 400Kb and 4000Kb per second respectively. +If we take 2 values of transaction throughput of 10 and 100 transactions per second, we can arrive at average network bandwidth requirements of 400KB and 4000KB per second respectively. -### Sequencer to Prover Communication +### Sequencer-to-Prover Communication Proving is an out-of-protocol activity. The nature of the communication between sequencers and provers will depend entirely on the prover/s selected by the sequencer. Provers may choose to run their own Transaction Pool Node infrastructure so that they are prepared for generating proofs and don't need to receive this data out-of-band. diff --git a/yellow-paper/docs/gas-and-fees/gas-and-fees.md b/yellow-paper/docs/gas-and-fees/index.md similarity index 67% rename from yellow-paper/docs/gas-and-fees/gas-and-fees.md rename to yellow-paper/docs/gas-and-fees/index.md index 02cfaf82c37..2ebf5d2e4e8 100644 --- a/yellow-paper/docs/gas-and-fees/gas-and-fees.md +++ b/yellow-paper/docs/gas-and-fees/index.md @@ -1,16 +1,32 @@ # Gas and Fees + + ## Requirements -Private state transition execution and proving is performed by the end user. However, once a transaction is submitted to the network, further resource is required to verify the private proofs, effect public state transitions and include the transaction within a rollup. This comes at the expense of the sequencer selected for the current slot. These resources include, but are not limited to: +Private state transition execution and proving is performed by the end user. However, once a transaction is submitted to the network, further resource is required to verify private kernel proofs, effect public state transitions and include the transaction within a rollup. This comes at the expense of the sequencer selected for the current slot. These resources include, but are not limited to: +1. Transaction [validation](../transactions/validity.md) 1. Execution of public function bytecode -2. Generation of initial witnesses and proving of public and rollup circuits -3. Storage of world state and computation of merkle proofs -4. Finalization of state transition functions on Ethereum -5. Storage of private notes +1. Generation of initial witnesses and proving of public and rollup circuits +1. Storage of world state and computation of merkle proofs +1. Finalization of state transition functions on Ethereum +1. Storage of private notes -Sequencers will need compensating for their efforts leading to requirements for the provision of payments to the sequencer. Note, some of the computation may be outsourced to third parties as part of the prover selection mechanism, the cost of this is borne by the sequencer outside of the protocol. +Sequencers will need compensating for their efforts, leading to requirements for the provision of payments to the sequencer. Note, some of the computation may be outsourced to third parties as part of the prover selection mechanism, the cost of this is borne by the sequencer outside of the protocol. We can define a number of requirements that serve to provide a transparent and fair mechanism of fee payments between transaction senders and sequencers. @@ -26,17 +42,19 @@ We can define a number of requirements that serve to provide a transparent and f ## High Level Concepts and Design 1. We will use concepts of L1, L2 and DA gas to universally define units of resource for the Ethereum and Aztec networks respectively. L1 gas directly mirrors the actual gas specification as defined by Ethereum, L2 gas covers all resource expended on the L2 network. Finally, DA gas accounts for the data stored on the network's Data Availability solution. -2. We will deterministically quantify all resource consumption of a transaction into 7 values. We will define these values later but essentially they represent the amortized and transaction specific quantities of each of L1, L2 and DA gas. +2. We will deterministically quantify all resource consumption of a transaction into 7 values. We will define these values later but essentially they represent the amortized and transaction-specific quantities of each of L1, L2 and DA gas. 3. The transaction sender will provide a single fee for the transaction. This will be split into 3 components to cover each of the L1, L2 and DA gas costs. The sender will specify `feePerGas` and `gasLimit` for each component. Doing so provides protection to the sender that the amount of fee applicable to any component is constrained. -4. We will constrain the sequencer to apply the correct amortized and transaction specific fees ensuring the sender can not be charged arbitrarily. +4. We will constrain the sequencer to apply the correct amortized and transaction-specific fees; ensuring the sender can not be charged arbitrarily. 5. We will define a method by which fees can be paid in any asset, either publicly or privately, on Ethereum or Aztec but where the sequencer retains agency as to what assets and fee payment methods they are willing to accept. 6. Upon accepting a transaction, we will constrain the sequencer to receive payment and provide any refund owing via the methods specified by the sender. ## Gas Metering -Broadly speaking, resource consumption incurred by the sequencer falls into categories of transaction specific consumption and amortized, per-rollup consumption. Each operation performed by the sequencer can be attributed with a fixed amount of gas per unit representing its level of resource consumption. The unit will differ between operations, for example in some operations it may be per-byte whilst in others it could be per-opcode. What matters is that we are able to determine the total gas consumption of any given transaction. +Broadly speaking, resource consumption incurred by the sequencer falls into categories of transaction-specific consumption and amortized, per-rollup consumption. Each operation performed by the sequencer can be attributed with a fixed amount of gas per unit, representing its level of resource consumption. The unit will differ between operations, for example in some operations it may be per-byte whilst in others it could be per-opcode. What matters is that we are able to determine the total gas consumption of any given transaction. + +### Examples of Gas-Consuming Operations -### Examples of Gas Consuming Operations + Examples of operations for which we want to measure gas consumption are: @@ -53,32 +71,94 @@ Some operations are specific to a transaction, such as public function execution ### Measuring Gas Before Submission -All of the operations listed in the transaction specific table can provide us with deterministic gas values for a transaction. The transaction can be simulated and appropriate gas figures can be calculated before the transaction is sent to the network. The transaction will also need to provide a fee to cover its portion of the amortized cost. This can be done by deciding on a value of `N`, the number of transactions in a rollup. Of course, the transaction sender can't know in advance how many other transactions will be included in the same rollup but the sender will be able to see how many transactions were included in prior rollups and decide on a value that will give them some certainty of inclusion without overpaying for insufficient amortization. As with all costs, any additional amortization will be refunded to the sender. +All of the operations listed in the transaction-specific table (below) can provide us with deterministic gas values for a transaction. The transaction can be simulated and appropriate gas figures can be calculated before the transaction is sent to the network. The transaction will also need to provide a fee to cover its portion of the amortized cost. This can be done by deciding on a value of `N`, the number of transactions in a rollup. Of course, the transaction sender can't know in advance how many other transactions will be included in the same rollup, but the sender will be able to see how many transactions were included in prior rollups and decide on a value that will give them some certainty of inclusion without overpaying for insufficient amortization. As with all costs, any additional amortization will be refunded to the sender. + +For example, if the previous 10 rollups each consist of an average of 5000 transactions, the sender could decide on a value of 5000 for `N` in its amortization. If the transaction is included in a rollup with > `N` transactions, the fee saved by the additional amortization will be refunded to the sender. If the sequencer chooses to include the transaction in a rollup with < `N` transactions, the sequencer will effectively subsidize that reduced amortization. -For example, if the previous 10 rollups consist of an average of 5000 transactions, the sender could decide on a value of 1000 for `N` in its amortization. If the transaction is included in a rollup with > `N` transactions, the fee saved by the additional amortization will be refunded to the sender. If the sequencer chooses to include the transaction in a rollup with < `N` transactions, the sequencer will effectively subsidize that reduced amortization. + The transaction will be provided with 7 gas values: + + + + | Value | Description | | -------- | -------- | | `L1BaseGasLimit` | The maximum quantity of gas permitted for use in amortized L1 operations | -| `L1TxGasLimit` | The maximum quantity of gas permitted for use in transaction specific L1 operations | +| `L1TxGasLimit` | The maximum quantity of gas permitted for use in transaction-specific L1 operations | | `L2BaseGasLimit` | The maximum quantity of gas permitted for use in amortized L2 operations | -| `L2TxGasLimit` | The maximum quantity of gas permitted for use in transaction specific L2 operations | +| `L2TxGasLimit` | The maximum quantity of gas permitted for use in transaction-specific L2 operations | | `L2FeeDistributionGas` | The quantity of L2 gas the sequencer can charge for executing the fee distribution function | -| `DAFeeDistributionGas` | The quantity of DA gas the sequencer can charge for storing state updates produced as part of fee distribution | +| `DAFeeDistributionGas` | The quantity of DA gas the sequencer can charge for publishing state updates and events, which are produced as part of fee distribution | | `DATxGasLimit` | The maximum quantity of DA gas permitted for use in transaction specific Data Availability functions | + + By constraining each of the above values individually, the transaction sender is protected from a dishonest sequencer allocating an unfairly high amount of gas to one category and leaving insufficient gas for other categories causing a transaction to erroneously be deemed 'out of gas' and a fee taken for improper execution. ### Gas Measurement By A Sequencer -When a transaction is received by a sequencer, it will want to determine if the transaction has been endowed with sufficient fee to be considered for inclusion in a rollup. Although the transaction contains information as to the gas limits and fees it provides, these may not be accurate either because the sender is dishonest or because the simulation of any public functions was performed on a system state that differs to that at the point of inclusion. Unlike transactions on Ethereum, it is not simply a case of linearly executing the transactions opcodes until completion of the transaction exceeds the provided gas. Rollup inclusion and public function execution and proving require significant resource investment on the part of the sequencer even for the most trivial transaction. +When a transaction is received by a sequencer, they will want to determine if the transaction has been endowed with sufficient fee to be considered for inclusion in a rollup. Although the transaction contains information as to the gas limits and fees it provides, these may not be accurate: either because the sender is dishonest, or because the simulation of any public functions was performed on a system state that differed to that at the point of inclusion. Unlike transactions on Ethereum, it is not simply a case of linearly executing the transaction's opcodes until the provided gas limit is exceeded. Rollup inclusion, public function execution, and proving, all require significant resource investment on the part of the sequencer, even for the most trivial transaction. -There are a series of steps the sequencer would wish to perform such that it incrementally increases it's commitment to processing the transaction as it becomes more confident of a successful outcome: +There are a series of steps the sequencer would wish to perform such that it incrementally increases its commitment to processing the transaction as it becomes more confident of a successful outcome: -1. Determine how much fee has been provided and verify that this is sufficient to cover the L1, L2 and DA gas limits specified in the transaction. Later on we will look at how this is done but it may involve simulation of a public function. The sequencer will have visibility over which function on which contract has been specified for this step and has agency to disregard the transaction if it is not willing to execute this step. +1. Determine how much fee has been provided and verify that this is sufficient to cover the L1, L2 and DA gas limits specified in the transaction. Later on we will look at how this is done but it may involve simulation of a public function. The sequencer will have visibility over which public function on which contract has been specified for this step and has agency to disregard the transaction if it is not willing to execute this step. 2. Once the fee is known, verify that enough fee exists to cover the transaction's requirements at this stage. This would include publishing the results of the private stage of the transaction and the amortized cost of rollup construction and verification. 3. If at least one public function is enqueued, verify that enough fee exists to cover at least 1 iteration of the public VM, 1 iteration of the public kernel circuit and a non-zero amount left for public function simulation. The sequencer here will seek to determine if it is worth proceeding with the transaction. Proceeding requires an investment at least covering the cost of the minimum public VM execution and an iteration of the public kernel circuit. The minimum cost could be described as the cost to simulate, execute and prove a function which reverts as soon as it enters the function. @@ -136,7 +216,7 @@ We will attempt to walk through the process by which a transaction is created wi ### User Simulation and Fee Preparation -Transactions begin on a user's device where a user opts to interact privately with a contract. This execution results in the generation of new notes and nullifiers and potentially some enqueued public function calls. Additionally, at some point during this execution, a fee will need to be generated. The state updates related to this fee will be added to a separate collection of notes, nullifiers and public calls where these state updates only revert on failure of the fee preparation or distribution logic. +Transactions begin on a user's device, where a user opts to interact privately with a contract. This execution results in the generation of new notes, nullifiers, and events, and potentially some enqueued public function calls. Additionally, at some point during this execution, a fee will need to be generated. The state updates relating to the preparation and distribution of this fee will be added to a separate collection of notes, nullifiers, events, and public calls, and these state updates will only revert on failure of the fee preparation or fee distribution logic. This would appear to introduce a circular dependency whereby an appropriate fee can't be produced without knowing the gas profile (the required quantities of L1, L2 and DA gas), but the gas profile can depend on the fee required. When simulating the transaction, we will introduce values to the global context: diff --git a/yellow-paper/docs/intro.md b/yellow-paper/docs/intro.md index 9f872b58b81..04279ac270f 100644 --- a/yellow-paper/docs/intro.md +++ b/yellow-paper/docs/intro.md @@ -16,7 +16,7 @@ In particular, can we explain the protocol to the wider cryptography team, whose This document should be considered the foundation of the protocol. It shouldn't need to refer to the implementation details elsewhere in this monorepo. (The protocol should inform the implementation; not the other way around). -The details should be sufficient for some other engineering team to implement the entire protocol (save for a few exact details, which are details (here)(LINK)). +The details should be sufficient for some other engineering team to implement the entire protocol. Some of the info we need to populate this document might have already been written in the top-level `docs/` dir of the monorepo. But the target audience is different. Reduce verbose prose. Remove monorepo code snippets (but note that simple pseudocode snippets to explain a protocol concept are fine). Don't describe components of the sandbox (that's an implementation detail and doesn't belong in this doc). diff --git a/yellow-paper/docs/cross-chain-communication/images/com-abs-6.png b/yellow-paper/docs/l1-smart-contracts/images/com-abs-6.png similarity index 100% rename from yellow-paper/docs/cross-chain-communication/images/com-abs-6.png rename to yellow-paper/docs/l1-smart-contracts/images/com-abs-6.png diff --git a/yellow-paper/docs/cross-chain-communication/index.md b/yellow-paper/docs/l1-smart-contracts/index.md similarity index 98% rename from yellow-paper/docs/cross-chain-communication/index.md rename to yellow-paper/docs/l1-smart-contracts/index.md index 61e15a9cc0b..9767dfc07e2 100644 --- a/yellow-paper/docs/cross-chain-communication/index.md +++ b/yellow-paper/docs/l1-smart-contracts/index.md @@ -11,7 +11,7 @@ The purpose of the L1 contracts are simple: - Facilitate cross-chain communication such that L1 liquidity can be used on L2 - Act as a validating light node for L2 that every L1 node implicitly run -::: + ::: ## Overview @@ -141,7 +141,9 @@ For a generic DA that publishes data commitments to Ethereum, the oracle could b By having the availability oracle be independent from state progression we can even do multi-transaction blocks, e.g., use multiple transactions or commitments from other DA layers to construct the `TxsHash` for a large block. -For more information around the requirements we have for the availability oracle, see [Data Availability](./da.md). +For more information around the requirements we have for the availability oracle, see [Data Availability](../data-publication-and-availability/index.md). + + ### Registry @@ -214,6 +216,8 @@ We are using the `secretHash` to ensure that the user can spend the message priv When we say inbox, we are generally referring to the L1 contract that handles the L1 to L2 messages. + + The inbox is logically a [multi-set](https://en.wikipedia.org/wiki/Multiset) that builds messages based on the caller and user-provided content (multi-set meaning that repetitions are allowed). While anyone can insert messages into the inbox, only the recipient state transitioner can consume messages from it (as specified by the version). When the state transitioner is consuming a message, it MUST insert it into the "L2 outbox" ([message tree](./../state/index.md)). When a message is inserted into the inbox, the inbox **MUST** fill in the `sender`: @@ -236,7 +240,7 @@ The contract that sent the message must decide how to handle the cancellation. I While we have ensured that the message either arrives to the L2 outbox or is cancelled, we have not ensured that the message is consumed by the L2 contract. This is up to the L2 contract to handle. If the L2 contract does not handle the message, it will be stuck in the outbox forever. Similarly, it is up to the L1 contract to handle the cancellation. If the L1 contract does not handle the cancellation, the user might have a message that is pending forever. Error handling is entirely on the contract developer. ::: -##### L2 Inbox +#### L2 Inbox While the L2 inbox is not a real contract, it is a logical contract that apply mutations to the data similar to the L1 inbox to ensure that the sender cannot fake his position. This logic is handled by the kernel and rollup circuits. @@ -251,6 +255,11 @@ In practice, this is done in the kernel circuit of the L2, and the message hash The outboxes are the location where a user can consume messages from. An outbox can only contain elements that have previously been removed from the paired inbox. + + Our L1 outbox is pretty simple, Like the L1 inbox, it is a multi-set. It should allow the state transitioner to insert messages and the recipient of the message can consume it (removing it from the outbox). :::info Checking sender diff --git a/yellow-paper/docs/logs/index.md b/yellow-paper/docs/logs/index.md index eeeaa07b9b5..343b4d980fe 100644 --- a/yellow-paper/docs/logs/index.md +++ b/yellow-paper/docs/logs/index.md @@ -2,14 +2,15 @@ title: Logs --- -Logs on Aztec are similar to logs on Ethereum, serving the purpose of enabling smart contracts to convey arbitrary data to external entities. This communication occurs through three distinct types of logs: + + + +Logs on Aztec are similar to logs on Ethereum, enabling smart contracts to convey arbitrary data to external entities. Offchain applications can use logs to interpret events that have occurred on-chain. There are three types of log: - [Unencrypted log](#unencrypted-log). - [Encrypted log](#encrypted-log). - [Encrypted note preimage](#encrypted-note-preimage). -These logs are generated in the course of contract function executions and play a pivotal role in aiding users to comprehend and leverage the received block data while facilitating interaction with the network. - ## Requirements 1. **Availability**: The logs get published. @@ -18,11 +19,11 @@ These logs are generated in the course of contract function executions and play 2. **Immutability**: A log cannot be modified once emitted. - The protocol ensures that once a proof is generated at any stage (for a function, transaction, or block), the emitted logs are finalized. In other words, only the original log preimages can generate the committed hashes in the proof. Any party can use this attribute to verify that the provided log preimages are not tempered. + The protocol ensures that once a proof is generated at any stage (for a function, transaction, or block), the emitted logs are tamper-proof. In other words, only the original log preimages can generate the committed hashes in the proof. 3. **Integrity**: A contract cannot impersonate another contract. - Every log is emitted by a specific contract, necessitating the identification of the contract address. This information is crucial for subsequent interactions with the contract or for interpreting the received data. The protocol ensures that the source contract's address for a log can be verified, while also preventing the forging of the addresses. + Every log is emitted by a specific contract, and users need assurances that a particular log was indeed generated by a particular contract (and not some malicious impersonator contract). The protocol ensures that the source contract's address for a log can be verified, while also preventing the forging of the address. ## Log Hash @@ -30,22 +31,28 @@ These logs are generated in the course of contract function executions and play The protocol uses **SHA256** as the hash function for logs, and then reduces the 256-bit result to 253 bits for representation as a field element. + + Throughout this page, `hash(value)` is an abbreviated form of: `truncate_to_field(SHA256(value))` ### Hashing -Regardless of the log type, the hash is derived from an array of fields, calculated as: +Regardless of the log type, the log hash is derived from an array of fields, calculated as: `hash(log_preimage[0], log_preimage[1], ..., log_preimage[N - 1])` -Here, _log_preimage_ is an array of field elements of length _N_, representing the data to be broadcasted. +Here, _log_preimage_ is an array of field elements of length `N`, representing the data to be broadcast. #### Emitting Logs from Function Circuits -A function can emit an arbitrary number of logs, provided they don't exceed the specified [limit]. The function circuits must compute a hash for each log, and push all the hashes into the public inputs for further processing by the protocol circuits. + + +A function can emit an arbitrary number of logs, provided they don't exceed the specified [limit] . The function circuits must compute a hash for each log, and push all the hashes into the public inputs for further processing by the protocol circuits. #### Aggregation in Protocol Circuits + + To minimize the on-chain verification data size, protocol circuits aggregate log hashes. The end result is a single hash within the root rollup proof, encompassing all logs of the same type. Each protocol circuit outputs two values for each log type: @@ -63,7 +70,7 @@ For private and public kernel circuits, beyond aggregating logs from a function ### Encoding -1. The encoded logs data of a transaction is a flatten array of all logs data within the transaction: +1. The encoded logs data of a transaction is a flattened array of all logs data within the transaction: _`tx_logs_data = [number_of_logs, ...log_data_0, ...log_data_1, ...]`_ @@ -76,7 +83,7 @@ For private and public kernel circuits, beyond aggregating logs from a function - _number_of_transactions_ is the number of leaves in the left-most branch, restricted to either _1_ or _2_. - _number_of_branches_ is the depth of the parent node of the left-most leaf. -Here is a step-by-step example to construct the _block_logs_data_: +Here is a step-by-step example to construct the _`block_logs_data`_: 1. A rollup, _R01_, merges two transactions: _tx0_ containing _tx_logs_data_0_, and _tx1_ containing _tx_logs_data_1_: diff --git a/yellow-paper/docs/private-message-delivery/encryption-and-decryption.md b/yellow-paper/docs/private-message-delivery/encryption-and-decryption.md index e42072de18d..a00e82bea19 100644 --- a/yellow-paper/docs/private-message-delivery/encryption-and-decryption.md +++ b/yellow-paper/docs/private-message-delivery/encryption-and-decryption.md @@ -1,6 +1,6 @@ # Encryption and Decryption -Applications should be able to provably encrypt data for a target user, as part of private message delivery. As stated on the Keys section, we define three types of encrypted data, based on the sender and the recipient, from the perspective of a user: +Applications should be able to provably encrypt data for a target user, as part of private message delivery. As stated in the Keys section, we define three types of encrypted data, based on the sender and the recipient, from the perspective of a user: - Incoming data: data created by someone else, encrypted for and sent to the user. - Outgoing data: data created by the user to be sent to someone else, encrypted for the user. diff --git a/yellow-paper/docs/private-message-delivery/index.md b/yellow-paper/docs/private-message-delivery/index.md index 679d3e9abb5..c15f4cb06cf 100644 --- a/yellow-paper/docs/private-message-delivery/index.md +++ b/yellow-paper/docs/private-message-delivery/index.md @@ -2,6 +2,12 @@ title: Private Message Delivery --- + + # Private Message Delivery Private message delivery encompasses the encryption, tagging, and broadcasting of private messages on the Aztec Network. diff --git a/yellow-paper/docs/private-message-delivery/note-discovery.md b/yellow-paper/docs/private-message-delivery/note-discovery.md index ac5776b0949..8f4bee16e39 100644 --- a/yellow-paper/docs/private-message-delivery/note-discovery.md +++ b/yellow-paper/docs/private-message-delivery/note-discovery.md @@ -1,3 +1,5 @@ + + # Note Discovery When users interact with contracts they will generate and publish encrypted notes for other network participants. In order for a user to consume notes that belong to them, they need to identify, retrieve and decrypt them. A simple, privacy-preserving approach to this would be to download all of the notes and attempt decryption. However, the total number of encrypted notes published by the network will be substantial, making it infeasible for some users to do this. Those users will want to utilize a note discovery protocol to privately identify their notes. diff --git a/yellow-paper/docs/private-message-delivery/private-msg-delivery.md b/yellow-paper/docs/private-message-delivery/private-msg-delivery.md index e82ab0f34c3..04ba5fcdd0e 100644 --- a/yellow-paper/docs/private-message-delivery/private-msg-delivery.md +++ b/yellow-paper/docs/private-message-delivery/private-msg-delivery.md @@ -1,32 +1,36 @@ # Private Message Delivery -Maintaining the core tenet of privacy within the Aztec Network imposes a number of requirements related to the transfer of notes from one user to another. If Alice executes a function that generates a note for Bob: +In Aztec, users need to pass private information between each other. Whilst Aztec enables users to share arbitrary private messages, we'll often frame the discussion towards a sender sharing the preimage of a private note with some recipient. + +If Alice executes a function that generates a note for Bob: 1. Alice will need to **encrypt** that note such that Bob, and only Bob is able to decrypt it. -2. Alice will need to **broadcast** the encrypted note so as to make it available for Bob to retrieve. -3. Alice will need to **broadcast a 'tag'** alongside the encrypted note. This tag must be identifiable by Bob's chosen [note discovery protocol](./note-discovery.md) but not identifiable by any third party. +2. Alice will need to **broadcast** the encrypted note ciphertext so as to make it available for Bob to retrieve. +3. Alice will need to **broadcast a 'tag'** alongside the encrypted note ciphertext. This tag must be identifiable by Bob's chosen [note discovery protocol](./note-discovery.md) but not identifiable by any third party as "intended for Bob". ## Requirements - **Users must be able to choose their note tagging mechanism**. We expect improved note discovery schemes to be designed over time. The protocol should be flexible enough to accommodate them and for users to opt in to using them as they become available. This flexibility should be extensible to encryption mechanisms as well as a soft requirement. - **Users must be able to receive notes before interacting with the network**. A user should be able to receive a note just by generating an address. It should not be necessary for them to deploy their account contract in order to receive a note. -- **Applications must be able to safely send notes to any address**. Sending a note to an account could potentially transfer control of the call to that account, allowing the account to control whether they want to accept the note or not, and potentially bricking an application, since there is no catching exceptions in private function execution. +- **Applications must be able to safely send notes to any address**. Sending a note to an account could potentially transfer control of the call to that account, allowing the account to control whether they want to accept the note or not, and potentially bricking an application, since there are no catching exceptions in private function execution. - **Addresses must be as small as possible**. Addresses will be stored and broadcasted constantly in applications. Larger addresses means more data usage, which is the main driver for cost. Addresses must fit in at most 256 bits, or ideally a single field element. - **Total number of function calls should be minimized**. Every function call requires an additional iteration of the private kernel circuit, which adds several seconds of proving time. - **Encryption keys should be rotatable**. Users should be able to rotate their encryption keys in the event their private keys are compromised, so that any further interactions with apps can be private again, without having to migrate to a new account. ## Constraining Message Delivery -The protocol will allow constraining: +The protocol will enable app developers to constrain the correctness of the following: 1. The encryption of a user's note. 2. The generation of the tag for that note. -3. The publication of that note to the correct data availability layer. +3. The publication of that note and tag to the correct data availability layer. -Each app will define whether to constrain each step in private message delivery. Encryption and tagging will be done through a set of precompiled contracts, each contract offering a different mechanism, and users will advertise their preferred mechanisms in a canonical registry. +Each app will define whether to constrain each such step. Encryption and tagging will be done through a set of [precompiled contracts](../addresses-and-keys/precompiles.md), each contract offering a different mechanism, and users will advertise their preferred mechanisms in a canonical [registry](./registry.md). The advantages of this approach are: + + 1. It enables a user to select their preferred [note discovery protocol](./note-discovery.md) and [encryption scheme](./encryption-and-decryption.md). 2. It ensures that notes are correctly encrypted with a user's public encryption key. 3. It ensures that notes are correctly tagged for a user's chosen [note discovery protocol](./note-discovery.md). diff --git a/yellow-paper/docs/private-message-delivery/registry.md b/yellow-paper/docs/private-message-delivery/registry.md index d23a717df3c..d2302aa5c83 100644 --- a/yellow-paper/docs/private-message-delivery/registry.md +++ b/yellow-paper/docs/private-message-delivery/registry.md @@ -1,22 +1,34 @@ # Registry -The protocol should allow users to express their preferences in terms of encryption and note tagging mechanisms, and also provably advertise their encryption public keys. A canonical registry contract provides an application-level solution to both problems. + + +The protocol should allow users to express their preferences in terms of encryption & tagging mechanisms, and also provably advertise their encryption & tagging public keys. A canonical registry contract provides an application-level solution to both problems. ## Overview and Usage -At the application level, a canonical singleton contract allows accounts register their public keys and their preference for encryption and tagging methods. This data is kept in public storage for anyone to check when they need to send a note to an account. +At the application level, a canonical singleton contract allows accounts to register their public keys and their preference for encryption & tagging methods. This data is kept in public storage for anyone to check when they need to send a note to an account. -An account can directly call the registry via a public function to set or update their public keys and encryption method. New accounts should register themselves on deployment. Alternatively, anyone can create an entry for a new account (but not update) if they show the public key and encryption method can be hashed to the address. This allows third party services to register addresses to improve usability. +An account can directly call the registry via a public function to set or update their public keys and their encryption & tagging preferences. New accounts should register themselves on deployment. Alternatively, anyone can create an entry for a new account (but not update) if they demonstrate that the public key and encryption & tagging method can be hashed to the new account's address. This allows third party services to register addresses to improve usability. -An app contract can provably read the registry during private execution via a merkle membership proof against the latest public state root. Rationale for not making a call to the registry to read is to reduce the number of function calls. When reading public state from private-land, apps must set a max-block-number for the current transaction to ensure the public state root is not more than N blocks old. This means that, if a user rotates their public key, for at most N blocks afterwards they may still receive notes encrypted using their old public key, which we consider to be acceptable. +An app contract can provably read the registry during private execution via a merkle membership proof against a recent public state root, using the [archive tree](../state/archive.md). The rationale for not making a call to the registry to read is to reduce the number of function calls. When reading public state from private-land, apps must set a `max_block_number` for the current transaction to ensure the public state root is not more than `N = max_block_number - current_block_number` blocks old. This means that, if a user rotates their public key, for at most `N` blocks afterwards they may still receive notes encrypted using their old public key, which we consider to be acceptable. An app contract can also prove that an address is not registered in the registry via a non-inclusion proof, since the public state tree is implemented as an indexed merkle tree. To prevent an app from proving that an address is not registered when in fact it was registered less than N blocks ago, we implement this check as a public function. This means that the transaction may leak that an undisclosed application attempted to interact with a non-registered address but failed. -Note that, if an account is not registered in the registry, a sender could choose to supply the public key along with the preimage of an address on-the-fly, if this preimage was shared with them off-chain. This allows a user to send notes to a recipient before the recipient has deployed their account contract. +Note that, if an account is not registered in the registry, a sender could choose to supply the public key along with the preimage of an address on-the-fly , if this preimage was shared with them off-chain. This allows a user to send notes to a recipient before the recipient has deployed their account contract. ## Pseudocode -The registry contract exposes functions for setting public keys and encryption methods, plus a public function for proving non-membership. Reads are meant to be done directly via storage proofs and not via calls to save on proving times. Encryption and tagging preferences are expressed via their associated precompile address. +The registry contract exposes functions for setting public keys and encryption methods, plus a public function for proving non-membership of some address. Reads are meant to be done directly via storage proofs and not via calls to save on proving times. Encryption and tagging preferences are expressed via their associated precompile address. + + + + + + + + ``` contract Registry @@ -29,6 +41,7 @@ contract Registry public fn set_from_preimage(address, keys, precompile_address, ...address_preimage) assert address not in registry assert hash(keys, precompile_address, ...address_preimage) == address + // Q: Shouldn't this be `this.do_set(address, keys, precompile_address)`? this.set(msg_sender, keys, precompile_address) public fn assert_non_membership(address) @@ -38,6 +51,7 @@ contract Registry assert precompile_address in ENCRYPTION_PRECOMPILE_ADDRESS_RANGE assert precompile_address.validate_keys(keys) assert keys.length < MAX_KEYS_LENGTH + // Q: Shouldn't this be `registry[address] = ... ?` registry[msg_sender] = { keys, precompile_address } ``` @@ -45,7 +59,7 @@ contract Registry The registry stores a struct for each user, which means that each entry requires multiple storage slots. Reading multiple storage slots requires multiple merkle membership proofs, which increase the total proving cost of any execution that needs access to the registry. -To reduce the number of merkle membership proofs, the registry keeps in storage only the hash of the data stored, and emits the preimage as an unencrypted event. Nodes are expected to store these preimages, so they can be returned when clients query for the public keys for an address. Clients then prove that the preimage hashes to the commitment stored in the public data tree via a single merkle membership proof. +To reduce the number of merkle membership proofs, the registry keeps in storage only the hash of the data stored, and emits the preimage as an unencrypted event. Nodes are expected to store these preimages, so they can be returned when clients query for the public keys for an address. Clients then prove that the preimage hashes to the commitment stored in the public data tree via a single merkle membership proof. Note that this optimization may also be included natively into the protocol, [pending this discussion](https://forum.aztec.network/t/storing-data-of-arbitrary-length-in-the-public-data-tree/2669). @@ -70,6 +84,8 @@ contract Sample ``` + + ## Discussion diff --git a/yellow-paper/docs/private-message-delivery/send-note-guidelines.md b/yellow-paper/docs/private-message-delivery/send-note-guidelines.md index 73355e0acbf..b3394085a37 100644 --- a/yellow-paper/docs/private-message-delivery/send-note-guidelines.md +++ b/yellow-paper/docs/private-message-delivery/send-note-guidelines.md @@ -6,7 +6,7 @@ In order to satisfy the requirements established for private message delivery, w ## Provably Sending a Note -To provably encrypt, tag, and send a note to a recipient, applications should first check the registry. This ensures that the latest preferences for the recipient are honored, in case they rotated their keys. The registry should be queried via a direct storage read and not a function call, in order to save an additional recursion which incurs in extra proving time. +To provably encrypt, tag, and send a note to a recipient, applications should first check the registry. This ensures that the latest preferences for the recipient are honored, in case they rotated their keys or updated their precompile preference. The registry should be queried via a direct storage read and not a function call, in order to save an additional recursion which incurs in extra proving time. If the recipient is not in the registry, then the app should allow the sender to provide the recipient's public key from the recipient's address preimage. This allows users who have never interacted with the chain to receive encrypted notes, though it requires a collaborative sender. @@ -16,7 +16,7 @@ Execution of the precompile that implements the recipient's choice for encryptio ## Pseudocode -The following pseudocode covers how to provably send a note to a recipient, given an `encryption_type` (incoming, outgoing, or internal incoming). Should the registry support [multiple entries for a given recipient](./registry.md#multiple-recipients-per-address), this method must execute a batched call per each entry recovered from the registry. +The following pseudocode covers how to provably send a note to a recipient, given an `encryption_type` (incoming, outgoing, or internal incoming). Should the registry support [multiple entries for a given recipient](./registry.md#multiple-recipients-per-address), this method must execute a batched call per each entry recovered from the registry. ``` fn provably_send_note(recipient, note, encryption_type) diff --git a/yellow-paper/docs/rollup-circuits/index.md b/yellow-paper/docs/rollup-circuits/index.md index 87e49fe6bcc..397b16a2941 100644 --- a/yellow-paper/docs/rollup-circuits/index.md +++ b/yellow-paper/docs/rollup-circuits/index.md @@ -4,14 +4,12 @@ title: Rollup Circuits ## Overview -Together with the [validating light node](../cross-chain-communication/index.md) the rollup circuits must ensure that incoming blocks are valid, that state is progressed correctly and that anyone can rebuild the state. +Together with the [validating light node](../l1-smart-contracts/index.md) the rollup circuits must ensure that incoming blocks are valid, that state is progressed correctly and that anyone can rebuild the state. To support this, we construct a single proof for the entire block, which is then verified by the validating light node. This single proof is constructed by recursively merging proofs together in a binary tree structure. This structure allows us to keep the workload of each individual proof small, while making it very parallelizable. This works very well for the case where we want many actors to be able to participate in the proof generation. -The tree structure is outlined below, but the general idea is that we have a tree where all the leaves are transactions (kernel proofs) and through $\log(n)$ steps we can then "compress" them down to just a single root proof. Note that we have three (3) different types of "merger" circuits, namely: +The tree structure is outlined below, but the general idea is that we have a tree where all the leaves are transactions (kernel proofs) and through $\log(n)$ steps we can then "compress" them down to just a single root proof. Note that we have two different types of "merger" circuit, namely: -- The base rollup - - Merges two kernel proofs - The merge rollup - Merges two base rollup proofs OR two merge rollup proofs - The root rollup @@ -93,6 +91,12 @@ To understand what the circuits are doing and what checks they need to apply it Below is a figure of the data structures thrown around for the block proof creation. Note that the diagram does not include much of the operations for kernels, but mainly the data structures that are used for the rollup circuits. + + + + + + ```mermaid classDiagram direction TB @@ -380,7 +384,7 @@ graph LR To ensure that state is made available, we could broadcast all of a block's input data as public inputs of the final root rollup proof, but a proof with so many public inputs would be very expensive to verify onchain. Instead we reduce the proof's public inputs by committing to the block's body by iteratively computing a `TxsHash` and `OutHash` at each rollup circuit iteration. AT the final iteration a `body_hash` is computed committing to the complete body. -To check that this body is published an Aztec node can reconstruct the `body_hash` from available data. Since we define finality as the point where the block is validated and included in the state of the [validating light node](../cross-chain-communication/index.md), we can define a block as being "available" if the validating light node can reconstruct the commitment `body_hash`. +To check that this body is published an Aztec node can reconstruct the `body_hash` from available data. Since we define finality as the point where the block is validated and included in the state of the [validating light node](../l1-smart-contracts/index.md), we can define a block as being "available" if the validating light node can reconstruct the commitment `body_hash`. Since we strive to minimize the compute requirements to prove blocks, we amortize the commitment cost across the full tree. We can do so by building merkle trees of partial "commitments", whose roots are ultimately computed in the final root rollup circuit. The `body_hash` is then computed from the roots of these trees, together with incoming messages. Below, we outline the `TxsHash` merkle tree that is based on the `TxEffect`s and a `OutHash` which is based on the `l2_to_l1_msgs` (cross-chain messages) for each transaction. While the `TxsHash` implicitly includes the `l2_to_l1_msgs` we construct it separately since the `l2_to_l1_msgs` must be available to the L1 contract directly and not just proven available. This is not a concern when using L1 calldata as the data layer, but is a concern when using alternative data layers such as [Celestia](https://celestia.org/) or [Blobs](https://eips.ethereum.org/EIPS/eip-4844). diff --git a/yellow-paper/docs/rollup-circuits/root-rollup.md b/yellow-paper/docs/rollup-circuits/root-rollup.md index d080f8ef5fd..c75efad220a 100644 --- a/yellow-paper/docs/rollup-circuits/root-rollup.md +++ b/yellow-paper/docs/rollup-circuits/root-rollup.md @@ -9,7 +9,7 @@ graph LR A[RootRollupInputs] --> C[RootRollupCircuit] --> B[RootRollupPublicInputs] --> D[ProvenBlock] --> E[Node] ``` -For rollup purposes, the node we want to convince of the correctness is the [validating light node](../cross-chain-communication/index.md) that we put on L1. We will cover it in more detail in the [cross-chain communication](../cross-chain-communication/index.md) section. +For rollup purposes, the node we want to convince of the correctness is the [validating light node](../l1-smart-contracts/index.md) that we put on L1. We will cover it in more detail in the [cross-chain communication](../l1-smart-contracts/index.md) section. :::info Squishers This might practically happen through a series of "squisher" circuits that will wrap the proof in another proof that is cheaper to verify on-chain. For example, wrapping a ultra-plonk proof in a standard plonk proof. @@ -214,4 +214,4 @@ def RootRollupCircuit( ) ``` -The `RootRollupPublicInputs` can then be used together with `Body` to build a `ProvenBlock` which can be used to convince the [validating light node](../cross-chain-communication/index.md) of state progression. +The `RootRollupPublicInputs` can then be used together with `Body` to build a `ProvenBlock` which can be used to convince the [validating light node](../l1-smart-contracts/index.md) of state progression. diff --git a/yellow-paper/docs/state/archive.md b/yellow-paper/docs/state/archive.md index e915e85779d..cd9e5feca77 100644 --- a/yellow-paper/docs/state/archive.md +++ b/yellow-paper/docs/state/archive.md @@ -1,14 +1,47 @@ --- -title: Archive +title: Archive Tree --- -# Archive +The Archive Tree is an [append-only Merkle tree](./tree-implementations.md#append-only-merkle-trees) that stores the Headers (see the diagram below) of all previous blocks in the chain as its leaves. -The Archive is an [append-only Merkle tree](./tree-implementations.md#append-only-merkle-trees) that stores the headers of all previous blocks in the chain as its leaves. +For most chains this is not required since they are always executing at the head of the chain. However, private execution relies on proofs generated by the user, and since users don't know the current head they must base their proofs on historical state. By including all prior headers (which include commitments to the state) the Archive Tree allows us to easily prove that the historic state that a transaction was proven upon is valid. -For most chains this is not required since they are always executing at the head of the chain. However, private execution relies on proofs generated by the user, and since users don't know the current head they must base their proofs on historical state. By including all prior headers (which include commitments to the state) the Archive allows us to easily prove that the historic state that a transaction was proven upon is valid. +Furthermore, since each Header includes a snapshot of the Archive Tree as at the time of insertion, as well as commitments to the Header's block content and global variables, we can use the Archive Tree to prove statements about the state at any given block or even transactions that occurred at specific blocks. -Furthermore, since the Headers include a snapshot of the Archive at the time of insertion, as well as commitments to the block content and global variables, we can use it to prove statements about the state at any given block or even transactions that occurred at specific blocks. + + + + + + + + + ```mermaid classDiagram @@ -81,9 +114,9 @@ class Body { } Body *-- "m" TxEffect -class Archive { +class ArchiveTree { type: AppendOnlyMerkleTree leaves: List~Header~ } -Archive *.. "m" Header : leaves +ArchiveTree *.. "m" Header : leaves ``` diff --git a/yellow-paper/docs/state/index.md b/yellow-paper/docs/state/index.md index ebe1b8782e6..e84259b8623 100644 --- a/yellow-paper/docs/state/index.md +++ b/yellow-paper/docs/state/index.md @@ -2,22 +2,26 @@ title: State --- + + # State The global state is the set of data that makes up Aztec - it is persistent and only updates when new blocks are added to the chain. -The state consists of multiple different categories of data with varying requirements. What all of the categories have in common is that they need strong integrity guarantees and efficient membership proofs. Like most other blockchains, this can be enforced by structuring the data as leafs in Merkle trees. +The state consists of multiple different categories of data with varying requirements. What all of the categories have in common is that they need strong integrity guarantees and efficient membership proofs. Like most other blockchains, this can be enforced by structuring the data as leaves in Merkle trees. -However, unlike most other blockchains, our contract state cannot use a Merkle tree as a key-value store for each contracts data. The reason for this is that we have both private and public state; while public state could be stored in a key-value tree, private state cannot, as doing so would leak information whenever the private state is updated, even if encrypted. +However, unlike most other blockchains, our contract state cannot use a Merkle tree as a key-value store for each contract's data. The reason for this is that we have both private and public state; while public state could be stored in a key-value tree, private state cannot, as doing so would leak information whenever the private state is updated, even if encrypted. -To work around this, we use a two-tree approach for state that can be used privately. Namely we have one (or more) tree(s) where data is added to (sometimes called a data tree), and a second tree where we "nullify" or mark the data as deleted. This allows us to "update" a leaf by adding a new leaf to the date trees, and add the nullifier of the old leaf to the second tree (the nullifier tree). That way we can show that the new leaf is the "active" one, and that the old leaf is "deleted". +To work around this, we use a two-tree approach for state that can be used privately. Namely we have one (or more) tree(s) where data is added to (sometimes called a data tree), and a second tree where we "nullify" or mark the data as deleted. This allows us to "update" a leaf by adding a new leaf to the data trees, and add the nullifier of the old leaf to the second tree (the nullifier tree). That way we can show that the new leaf is the "active" one, and that the old leaf is "deleted". -When dealing with private data, only the hash of the data is stored in the leaf in our data tree and we must setup a derivation mechanism that ensures nullifiers can be computed deterministically from the pre-image (the data that was hashed). This way, no-one can tell what data is stored in the leaf (unless they already know it), and therefore won't be able to derive the nullifier and tell if the leaf is active or deleted. +When dealing with private data, only the hash of the data is stored in the leaf in our data tree and we must set up a derivation mechanism that ensures nullifiers can be computed deterministically from the pre-image (the data that was hashed). This way, no-one can tell what data is stored in the leaf (unless they already know it), and therefore won't be able to derive the nullifier and tell if the leaf is active or deleted. Convincing someone that a piece of data is active can then be done by proving its membership in the data tree, and that it is not deleted by proving its non-membership in the nullifier tree. This ability to efficiently prove non-membership is one of the extra requirements we have for some parts of our state. To support the requirements most efficiently, we use two families of Merkle trees: - The [Append-only Merkle tree](./tree-implementations.md#append-only-merkle-trees), which supports efficient membership proofs, -- The [Indexed Merkle tree](./tree-implementations.md#indexed-merkle-trees), which supports efficient membership and non-membership proofs but increases the cost of adding leafs. +- The [Indexed Merkle tree](./tree-implementations.md#indexed-merkle-trees), which supports efficient membership and non-membership proofs but increases the cost of adding leaves. ### Private State Access @@ -43,16 +47,24 @@ A side-effect of this also means that if multiple users are "sharing" their note ## State Categories + + Below is a short description of the state catagories (trees) and why they have the type they have. -- [**Note Hashes**](./note-hash-tree.md): A set of hashes (commitments) of the individual blobs of contract data (we call these blobs of data notes). New notes can be created and their hashes inserted through contract execution. We need to support efficient membership proofs as any read will require one to prove validity. The set is represented as an [Append-only Merkle tree](./tree-implementations.md#append-only-merkle-trees), storing the note hashes as leafs. +- [**Note Hashes**](./note-hash-tree.md): A set of hashes (commitments) of the individual blobs of contract data (we call these blobs of data notes). New notes can be created and their hashes inserted through contract execution. We need to support efficient membership proofs as any read will require one to prove validity. The set is represented as an [Append-only Merkle tree](./tree-implementations.md#append-only-merkle-trees), storing the note hashes as leaves. - [**Nullifiers**](./nullifier-tree.md): A set of nullifiers for notes that have been spent. We need to support efficient non-membership proofs since we need to check that a note has not been spent before it can be used. The set is represented as an [Indexed Merkle tree](./tree-implementations.md#indexed-merkle-trees). - [**Public Data**](./public-data-tree.md): The key-value store for public contract state. We need to support both efficient membership and non-membership proofs! We require both, since the tree is "empty" from the start. Meaning that if the key is not already stored (non-membership), we need to insert it, and if it is already stored (membership) we need to just update the value. - **Contracts**: The set of deployed contracts. We need to support efficient membership proofs as we need to check that a contract is deployed before we can interact with it. The set is represented as an [Append-only Merkle tree](./tree-implementations.md#append-only-merkle-trees). - **L1 to L2 Messages**: The set of messages sent from L1 to L2. The set itself only needs to support efficient membership proofs, so we can ensure that the message was correctly sent from L1. However, it utilizes the Nullifier tree from above to ensure that the message cannot be processed twice. The set is represented as an [Append-only Merkle tree](./tree-implementations.md#append-only-merkle-trees). -- [**Archive**](./archive.md): The set of block headers that have been processed. We need to support efficient membership proofs as this is used in private execution to get the roots of the other trees. The set is represented as an [Append-only Merkle tree](./tree-implementations.md#append-only-merkle-trees). +- [**Archive Tree**](./archive.md): The set of block headers that have been processed. We need to support efficient membership proofs as this is used in private execution to get the roots of the other trees. The set is represented as an [Append-only Merkle tree](./tree-implementations.md#append-only-merkle-trees). + +To recall, the global state in Aztec is represented by a set of Merkle trees: the [Note Hash tree](./note-hash-tree.md), [Nullifier tree](./nullifier-tree.md), and [Public Data tree](./public-data-tree.md) reflect the latest state of the chain, while the L1 to L2 message tree allows for [cross-chain communication](../l1-smart-contracts/#l2-outbox) and the [Archive Tree](./archive.md) allows for historical state access. -To recall, the global state in Aztec is represented by a set of Merkle trees: the [Note Hash tree](./note-hash-tree.md), [Nullifier tree](./nullifier-tree.md), and [Public Data tree](./public-data-tree.md) reflect the latest state of the chain, while the L1 to L2 message tree allows for [cross-chain communication](../cross-chain-communication/#l2-outbox) and the [Archive](./archive.md) allows for historical state access. + ```mermaid classDiagram @@ -82,7 +94,7 @@ class GlobalVariables { } class Header { - last_archive: Snapshot + previous_archive_tree: Snapshot body_hash: Fr[2] state: StateReference global_variables: GlobalVariables @@ -125,11 +137,11 @@ class Body { } Body *-- "m" TxEffect -class Archive { +class ArchiveTree { type: AppendOnlyMerkleTree leaves: List~Header~ } -Archive *.. "m" Header : leaves +ArchiveTree *.. "m" Header : leaves class NoteHashTree { @@ -180,7 +192,7 @@ class NullifierTree { NullifierTree *.. "m" NullifierPreimage : leaves class State { - archive: Archive + archive_tree: ArchiveTree note_hash_tree: NoteHashTree nullifier_tree: NullifierTree public_data_tree: PublicDataTree @@ -188,7 +200,7 @@ class State { l1_to_l2_message_tree: L1ToL2MessageTree } State *-- L1ToL2MessageTree : l1_to_l2_message_tree -State *-- Archive : archive +State *-- ArchiveTree : archive_tree State *-- NoteHashTree : note_hash_tree State *-- NullifierTree : nullifier_tree State *-- PublicDataTree : public_data_tree diff --git a/yellow-paper/docs/state/note-hash-tree.md b/yellow-paper/docs/state/note-hash-tree.md index 5ff8c0ad3fb..78bab7bb6e3 100644 --- a/yellow-paper/docs/state/note-hash-tree.md +++ b/yellow-paper/docs/state/note-hash-tree.md @@ -1,12 +1,12 @@ # Note Hash Tree -The Note Hash tree is an [append-only Merkle tree](./tree-implementations.md#append-only-merkle-trees) that stores siloed note hashes as its elements. Each element in the tree is a 254-bit altBN-254 scalar field element. This tree is part of the global state, and allows to prove existence of private notes via Merkle membership proofs. +The Note Hash tree is an [append-only Merkle tree](./tree-implementations.md#append-only-merkle-trees) that stores [siloed](./tree-implementations.md#siloing-leaves) note hashes as its elements. Each element in the tree is a 254-bit altBN-254 scalar field element. This tree is part of the global state, and is used to prove existence of private notes via Merkle membership proofs. -Note commitments are immutable once created, since notes cannot be modified. Still, notes can be consumed, which means they can no longer be used. To preserve privacy, a consumed note is not removed from the tree, otherwise it would be possible to link the transaction that created a note with the one that consumed it. Instead, a note is consumed by emitting a deterministic [nullifier](./nullifier-tree.md). +Note commitments are immutable once created. Still, notes can be consumed ("read") by functions. To preserve privacy, a consumed note is not removed from the tree, otherwise it would be possible to link the transaction that created a note with the one that consumed it. Instead, a note is consumed by emitting a deterministic [nullifier](./nullifier-tree.md). -Contracts emit new note commitments via the `new_commitments` in the `CircuitPublicInputs`, which are subsequently [siloed](./tree-implementations.md#siloing-leaves) per contract by the Kernel circuit. Siloing the commitment ensures that a contract cannot emit a commitment for a note that could be used for a different contract. +Contracts emit new note commitments via the `new_commitments` in the `CircuitPublicInputs` , which are subsequently [siloed](./tree-implementations.md#siloing-leaves) by contract address by the Kernel circuit. Siloing the commitment ensures that a malicious contract cannot create notes for (that is, modify the state of) another contract. -The Kernel circuit also guarantees uniqueness of commitments by further hashing them with a nonce, derived from the transaction identifier and the index of the commitment within the transaction. Uniqueness means that a note with the same contents can be emitted more than once, and each instance can be independently nullified. Without uniqueness, two notes with the same content would yield the same commitment and nullifier, so nullifying one of them would flag the second one as nullified as well. +The Kernel circuit also guarantees uniqueness of commitments by further hashing them with a nonce, derived from the transaction identifier and the index of the commitment within the transaction's array of newly-created note hashes. Uniqueness means that a note with the same contents can be emitted more than once, and each instance can be independently nullified. Without uniqueness, two notes with the same content would yield the same commitment and nullifier, so nullifying one of them would render the second one as nullified as well. The pseudocode for siloing and making a commitment unique is the following, where each `hash` operation is a Pedersen hash with a unique generator index, indicated by the constant in all caps. @@ -18,8 +18,8 @@ fn compute_unique_siloed_commitment(commitment, contract, transaction): return hash([nonce, siloed_commitment], UNIQUE_COMMITMENT) ``` -The unique siloed commitment of a note is included in the [transaction `data`](../transactions/tx-object.md), and then included into the Note Hash tree by the sequencer as the transaction is included in a block. +The unique siloed commitment of a note is included in the [transaction `data`](../transactions/tx-object.md), and then inserted into the Note Hash tree by the sequencer as the transaction is included in a block. -The protocol does not enforce any constraints to the commitment emitted by an application. This means that applications are responsible for including a `randomness` field in the note hash to make the commitment _hiding_ in addition to _binding_. If an application does not include randomness, and the note preimage can be guessed by an attacker, it makes the note vulnerable to preimage attacks, since the siloing and uniqueness steps do not provide hiding. +The protocol does not enforce any constraints on any note hashes emitted by an application. This means that applications are responsible for including a `randomness` field in the note hash to make the commitment _hiding_ in addition to _binding_. If an application does not include randomness, and the note preimage can be guessed by an attacker, it makes the note vulnerable to preimage attacks, since the siloing and uniqueness steps do not provide hiding. Furthermore, since there are no constraints to the commitment emitted by an application, an application can emit any value whatsoever as a `new_commitment`, including values that do not map to a note hash. diff --git a/yellow-paper/docs/state/nullifier-tree.md b/yellow-paper/docs/state/nullifier-tree.md index 08806e2cb26..036b69cff01 100644 --- a/yellow-paper/docs/state/nullifier-tree.md +++ b/yellow-paper/docs/state/nullifier-tree.md @@ -1,10 +1,22 @@ # Nullifier Tree -The Nullifier tree is an [indexed Merkle tree](./tree-implementations.md#indexed-merkle-trees) that stores nullifier values. Each value stored in the tree is a 254-bit altBN-254 scalar field element. This tree is part of the global state, and allows to prove non-existence of a nullifier when a note is consumed. +The Nullifier tree is an [indexed Merkle tree](./tree-implementations.md#indexed-merkle-trees) that stores nullifier values. Each value stored in the tree is a 254-bit altBN-254 scalar field element. This tree is part of the global state, and is primarily used to prove non-existence of a nullifier when a note is consumed (as a way of preventing double-spend). -Nullifiers are asserted to be unique during insertion, by checking that the inserted value is not equal to the value and next-value stored in the prior node in the indexed tree. Any attempt to insert a duplicated value is rejected. +In addition to storing nullifiers of notes, the nullifier tree is more generally useful to prevent any action from being repeated twice. This includes preventing re-initialization of state variables, and [re-deployment of contracts](../contract-deployment/instances.md). -Contracts emit new nullifiers via the `new_nullifiers` in the `CircuitPublicInputs`. Same as elements in the [Note Hash tree](./note-hash-tree.md), nullifiers are [siloed](./tree-implementations.md#siloing-leaves) per contract by the Kernel circuit before being inserted in the tree, which ensures that a contract cannot emit nullifiers that affect other contracts. +Nullifiers are asserted to be unique during insertion, by checking that the inserted value is not equal to the value and next-value stored in the prior leaf in the indexed tree. Any attempt to insert a duplicated value is rejected. + +Contracts emit new nullifiers via the `new_nullifiers` field of the `CircuitPublicInputs` ABI . Similarly to elements in the [Note Hash tree](./note-hash-tree.md), nullifiers are [siloed](./tree-implementations.md#siloing-leaves) by contract address, by the Kernel circuit, before being inserted into the tree. This ensures that a contract cannot emit the nullifiers of other contracts' state variables! + + + + ``` fn compute_siloed_nullifier(nullifier, contract): @@ -18,3 +30,17 @@ Nullifiers provide privacy by being computed using a deterministic secret value, Applications are not constrained by the protocol on how the nullifier for a note is computed. It is responsibility of the application to guarantee determinism in calculating a nullifier, otherwise the same note could be spent multiple times. Furthermore, nullifiers can be emitted by an application just to ensure that an action can be executed only once, such as initializing a value, and are not required to be linked to a note commitment. + + diff --git a/yellow-paper/docs/state/public-data-tree.md b/yellow-paper/docs/state/public-data-tree.md index 671455a5a6a..1c48c23a4d5 100644 --- a/yellow-paper/docs/state/public-data-tree.md +++ b/yellow-paper/docs/state/public-data-tree.md @@ -1,10 +1,15 @@ # Public Data Tree -The Public Data tree is an [indexed Merkle tree](./tree-implementations.md#indexed-merkle-trees) that stores public-state key-value data. Each item stored in the tree is a key-value pair, where both key and value are 254-bit altBN-254 scalar field elements. Items are sorted based on their key, so each indexed tree leaf contains a tuple with the key, the value, the next higher key, and the index in the tree for the next higher key. This tree is part of the global state, and is updated by the sequencer during the execution of public functions. +The Public Data tree is an [indexed Merkle tree](./tree-implementations.md#indexed-merkle-trees) that stores public-state. Each item stored in the tree is a key-value pair, where both key and value are 254-bit altBN-254 scalar field elements. Items are sorted based on their key, so each indexed tree leaf contains a tuple with the key, the value, the next-highest key, and the index in the tree for the next-highest key. This tree is part of the global state, and is updated by the sequencer during the execution of public functions. -The Public Data tree is implemented using an indexed Merkle tree instead of a sparse Merkle tree in order to reduce the tree height. A lower height means shorter membership proofs. +An indexed Merkle tree is ued instead of a sparse Merkle tree in order to reduce the tree height. A lower height means shorter membership proofs. -Keys in the Public Data tree are [siloed](./tree-implementations.md#siloing-leaves) using the contract address, to prevent a contract from overwriting public state for another contract. +Keys in the Public Data tree are [siloed](./tree-implementations.md#siloing-leaves) using the contract address, to prevent a contract from overwriting the public state of another contract. + + ``` fn compute_siloed_public_data_item(key, value, contract): @@ -12,8 +17,27 @@ fn compute_siloed_public_data_item(key, value, contract): return [siloed_key, value] ``` -When reading a key from the Public Data tree, the key may or may not be present. If the key is not present, then a non-membership proof is produced, and the value is assumed to be zero. When a key is written to, either a new node is appended to the tree if the key was not present, or its value is overwritten if it was. +When attempting to read a key from the Public Data tree, the key may or may not be present. If the key is not present, then a non-membership proof can be produced. When a key is written to, either a new node is appended to the tree if the key was not present, or its value is overwritten if it was. Public functions can read from or write to the Public Data tree by emitting `contract_storage_read` and `contract_storage_update_requests` in the `PublicCircuitPublicInputs`. The Kernel circuit then siloes these requests per contract. Contracts can store arbitrary data at a given key, which is always stored as a single field element. Applications are responsible for interpreting this data. Should an application need to store data larger than a single field element, they are responsible for partitioning it across multiple keys. + + diff --git a/yellow-paper/docs/state/tree-implementations.md b/yellow-paper/docs/state/tree-implementations.md index 0a698eb2de4..20d30aaccba 100644 --- a/yellow-paper/docs/state/tree-implementations.md +++ b/yellow-paper/docs/state/tree-implementations.md @@ -4,15 +4,15 @@ Aztec relies on two Merkle tree implementations in the protocol: append-only and ## Append-only Merkle trees -In an append-only Merkle tree new leaves are inserted in order from left to right. Existing leaf values are immutable and cannot be modified. These tree are useful to represent historic data, as new entries are added as new transactions and blocks are processed, and historic data is not altered. +In an append-only Merkle tree, new leaves are inserted in order from left to right. Existing leaf values are immutable and cannot be modified. These trees are useful to represent historical data, as historical data is not altered, and new entries can be added as new transactions and blocks are processed. -Append-only trees allow for more efficient syncing than sparse trees, since clients can sync from left to right starting with their last known value. Updates to the tree root from new leaves can be computed just by keeping the rightmost boundary of the tree, and batch insertions can be computed with fewer hashes than in a sparse tree. Append-only trees also provide cheap historic snapshots, as older roots can be computed by completing the merkle path from a past left subtree with an empty right subtree. +Append-only trees allow for more efficient syncing than sparse trees, since clients can sync from left to right starting with their last known value. Updates to the tree root, when inserting new leaves, can be computed from the rightmost "frontier" of the tree (i.e., from the sibling path of the rightmost nonzero leaf). Batch insertions can be computed with fewer hashes than in a sparse tree. The historical snapshots of append-only trees also enable efficient membership proofs; as older roots can be computed by completing the merkle path from a past left subtree with an empty right subtree. ## Indexed Merkle trees -Indexed Merkle trees, introduced [here](https://eprint.iacr.org/2021/1263.pdf), allow for proofs of non-inclusion more efficiently than sparse Merkle trees. Each leaf in the tree is a tuple with the leaf value, the next higher value in the tree, and the index of the leaf where that value is stored. New nodes are inserted left to right, as in the append-only tree, but existing nodes can be modified to update the next value and its pointer. Indexed Merkle trees behave as a Merkle tree over a sorted linked list. +Indexed Merkle trees, introduced [here](https://eprint.iacr.org/2021/1263.pdf), allow for proofs of non-inclusion more efficiently than sparse Merkle trees. Each leaf in the tree is a tuple of: the leaf value, the next-highest value in the tree, and the index of the leaf where that next-highest value is stored. New leaves are inserted from left to right, as in the append-only tree, but existing leaves can be _modified_ to update the next-highest value and next-highest index (a.k.a. the "pointer") if a new leaf with a "closer value" is added to the tree. An Indexed Merkle trees behaves as a Merkle tree over a sorted linked list. -Assuming the indexed Merkle tree invariants hold, proving non-membership of a value `x` then requires a membership proof of the node with value lower than `x` and a next higher value greater than `x`. The cost of this proof is proportional to the height of the tree, which can be set according to the expected number of elements to be stored in the tree. For comparison, a non-membership proof in a sparse tree requires a tree with height proportional to the size of the elements, so when working with 256-bit elements, 256 hashes are required for a proof. +With an Indexed Merkle tree, proving non-membership of a value `x` then requires a membership proof of the node with value lower than `x` and a next-highest value greater than `x`. The cost of this proof is proportional to the height of the tree, which can be set according to the expected number of elements to be stored in the tree. For comparison, a non-membership proof in a sparse tree requires a tree with height proportional to the size of the elements, so when working with 256-bit elements, 256 hashes are required for a proof. Refer to [this page](https://docs.aztec.network/concepts/advanced/data_structures/indexed_merkle_tree) for more details on how insertions, updates, and membership proofs are executed on an Indexed Merkle tree. @@ -20,6 +20,6 @@ Refer to [this page](https://docs.aztec.network/concepts/advanced/data_structure ## Siloing leaves -In several trees in the protocol we indicate that its leaves are "siloed". This refers to hashing the leaf value with a siloing value before inserting it in the tree. The siloing value is typically an identifier of the contract that produced the value. This allows us to store disjoint "domains" within the same tree, ensuring that a value emitted from one domain cannot affect others. +In several trees in the protocol we indicate that its leaves are "siloed". This refers to hashing the leaf value with some other "siloing" value before inserting it into the tree. The siloing value is typically the contract address of the contract that produced the value. This allows us to store disjoint "domains" within the same tree, ensuring that a value emitted from one domain cannot affect others. -To guarantee the siloing of leaf values, siloing is performed by a trusted protocol circuit, such as the kernel or rollup circuits, and not by an application circuit. Siloing is performed by Pedersen hashing the contract address and the value. +To guarantee the siloing of leaf values, siloing is performed by a trusted protocol circuit, such as a kernel or rollup circuit, and not by an application circuit. diff --git a/yellow-paper/docs/transactions/index.md b/yellow-paper/docs/transactions/index.md index 732cfea62c5..efd5ae8b468 100644 --- a/yellow-paper/docs/transactions/index.md +++ b/yellow-paper/docs/transactions/index.md @@ -6,9 +6,9 @@ title: Transactions A transaction is the minimal action that changes the state of the network. Transactions in Aztec have a private and a public component, where the former is executed in the user's private execution environment (PXE) and the latter by the sequencer. -A transaction is also split into three phases to [support authorization abstraction and fee payments](../gas-and-fees/gas-and-fees.md#fees): a validation and fee preparation phase, a main execution phase, and fee distribution phase. +A transaction is also split into three phases to [support authorization abstraction and fee payments](../gas-and-fees/index.md#fees): a validation and fee preparation phase, a main execution phase, and fee distribution phase. -Users initiate a transaction by sending a _transaction request_ to their local PXE, which [locally simulates and proves the transaction](./local-execution.md) and returns a [_transaction_ object](./tx-object.md) identified by a [_transaction hash_](./tx-object.md#transaction-hash). This transaction object is then broadcasted to the network via an Aztec Node, which checks its [validity](./validity.md), and eventually picked up by a sequencer who [executes the public component of the transaction](./public-execution.md) and includes it in a block. +Users initiate a transaction by sending a `transaction_request` to their local PXE, which [locally simulates and proves the transaction](./local-execution.md) and returns a [`transaction_object`](./tx-object.md#transaction-object-struct) identified by a [`transaction_hash`](./tx-object.md#transaction-hash). This transaction object is then broadcast to the network via an Aztec Node, which checks its [validity](./validity.md), and is eventually picked up by a sequencer who [executes the public component of the transaction](./public-execution.md) and includes it in a block. import DocCardList from '@theme/DocCardList'; diff --git a/yellow-paper/docs/transactions/local-execution.md b/yellow-paper/docs/transactions/local-execution.md index bca926136da..77f7a02ec7e 100644 --- a/yellow-paper/docs/transactions/local-execution.md +++ b/yellow-paper/docs/transactions/local-execution.md @@ -2,6 +2,18 @@ Transactions are initiated via a _transaction execution request_ sent from the user to their local _private execution environment_ (PXE). The PXE first executes the transaction locally in a _simulation_ step, and then generates a _zero-knowledge proof_ of correct execution. The PXE is then responsible for converting a _transaction execution request_ into a [_transaction_](./tx-object.md) ready to be broadcasted to the network. + + ## Execution request A transaction execution request has the following structure. Note that, since Aztec uses full native account abstraction where every account is backed by a contract, a transaction execution request only needs to provide the contract address, function, and arguments of the initial call; nonces and signatures are arguments to the call, and thus opaque to the protocol. @@ -9,18 +21,18 @@ A transaction execution request has the following structure. Note that, since Az | Field | Type | Description | |----------|----------|----------| -| origin | AztecAddress | Address of the contract where the transaction is initiated. | -| functionSelector | Field | Selector (identifier) of the function to be called as entrypoint in the origin contract. | -| argsHash | Field | Hash of the arguments to be used for calling the entrypoint function. | -| txContext | TxContext | Includes contract deployment data (if this tx is used to deploy a contract), chain id, and protocol version. | -| packedArguments | PackedArguments[] | Preimages for argument hashes. When executing a function call with the hash of the arguments, the PXE will look for the preimage of that hash in this list, and expand the arguments to execute the call. | -| authWitnesses | AuthWitness[] | Authorization witnesses. When authorizing an action identified by a hash, the PXE will look for the authorization witness identified by that hash and provide that value to the account contract. | +| `origin` | `AztecAddress` | Address of the contract where the transaction is initiated. | +| `functionSelector` | u32 | Selector (identifier) of the function to be called as entrypoint in the origin contract. | +| `argsHash` | `Field` | Hash of the arguments to be used for calling the entrypoint function. | +| `txContext` | `TxContext` | Includes chain id, and protocol version. | +| `packedArguments` | `PackedArguments[]` | Preimages for argument hashes. When executing a function call with the hash of the arguments, the PXE will look for the preimage of that hash in this list, and expand the arguments to execute the call. | +| `authWitnesses` | `AuthWitness[]` | Authorization witnesses. When authorizing an action identified by a hash, the PXE will look for the authorization witness identified by that hash and provide that value to the account contract. | ## Simulation step -Upon receiving a transaction execution request to _simulate_, the PXE will locally execute the function identified by the given `functionSelector` in the given `origin` contract with the arguments committed to by `argsHash`. We refer to this function as the _entrypoint_. During execution, contracts may request authorization witnesses or expanded arguments from the _execution oracle_, which are answered with the `packedArguments` and `authWitnesses` from the request. +Upon receiving a transaction execution request to _simulate_, the PXE will locally execute the function identified by the given `functionSelector` in the given `origin` contract with the arguments committed to by `argsHash`. We refer to this function as the _entrypoint_. During execution, contracts may request authorization witnesses or expanded arguments from the _execution oracle_ , which are answered with the `packedArguments` and `authWitnesses` from the request. -The _entrypoint_ may enqueue additional function calls, either private or public, and so forth. The simulation step will always execute all private functions in the call stack until emptied. The result of the simulation is a [_transaction_](./tx-object.md) object without an associated _proof_ which is returned to the application that requested the simulation. +The _entrypoint_ may enqueue additional function calls, either private or public. The simulation step will always execute all private functions in the call stack until emptied. The result of the simulation is a [_transaction_](./tx-object.md) object without an associated _proof_ which is returned to the application that requested the simulation. In terms of circuitry, the simulation step must execute all application circuits that correspond to private function calls, and then execute the private kernel circuit until the private call stack is empty. Note that circuits are only executed, there is no witness generation or proving involved. diff --git a/yellow-paper/docs/transactions/public-execution.md b/yellow-paper/docs/transactions/public-execution.md index 534f47f8b99..0fb55ab35a7 100644 --- a/yellow-paper/docs/transactions/public-execution.md +++ b/yellow-paper/docs/transactions/public-execution.md @@ -4,12 +4,12 @@ Transactions have a _public execution_ component. Once a transaction is picked u ## Bytecode -Unlike private functions, which are native circuits, public functions in the Aztec Network are specified in Brillig, a zkVM-friendly bytecode. This bytecode is executed and proven in the Brillig public virtual machine. Each function call is a run of the virtual machine, and a _public kernel circuit_ aggregates these calls and produces a final proof for the transaction, which also includes the _private kernel circuit_ proof of the transaction generated during [local execution](./local-execution.md). +Unlike private functions, which are native circuits, public functions in the Aztec Network are specified in AVM bytecode . This bytecode is executed and proven in the Aztec Virtual Machine. Each enqueued public function spawns a new instance of the AVM, and a _public kernel circuit_ aggregates these calls and produces a final proof of the transaction, which also includes the _private kernel circuit_ proof of the transaction generated during [local execution](./local-execution.md). ## State -Since public execution is run by the sequencer, it is run on the state of the chain as it is when the transaction is included in the block. Public functions operate on _public state_, an updateable key-value mapping, instead of notes. +Since public execution is run by the sequencer, it is run on the very-latest state of the chain as it is when the transaction is included in the block. Public functions operate on [_public state_](../state/public-data-tree.md), an updateable key-value mapping, instead of notes. ## Reverts -Note that, unlike local private execution, public execution can _revert_ due to a failed assertion, running out of gas, trying to call a non-existing function, or other failures. If this happens, the sequencer halts execution and discards all side effects from the [transaction payload phase](../gas-and-fees/gas-and-fees.md#transaction-payload). The transaction is still included in the block and pays fees, but is flagged as reverted. +Note that, unlike local private execution, public execution can _revert_ due to a failed assertion, running out of gas, trying to call a non-existing function, or other failures. If this happens, the sequencer halts execution and discards all side effects from the [transaction payload phase](../gas-and-fees/index.md#transaction-payload). The transaction is still included in the block and pays fees, but is flagged as reverted. diff --git a/yellow-paper/docs/transactions/tx-object.md b/yellow-paper/docs/transactions/tx-object.md index a5cca0f2485..29c6c9d33a6 100644 --- a/yellow-paper/docs/transactions/tx-object.md +++ b/yellow-paper/docs/transactions/tx-object.md @@ -2,18 +2,35 @@ The transaction object is the struct broadcasted to the p2p network, generated by [_local execution_](./local-execution.md) by the user's PXE. Sequencers pick up transactions from the p2p network to include in a block. + + ## Transaction object struct The fields of a transaction object are the following: + | Field | Type | Description | |----------|----------|----------| -| data | PrivateKernelPublicInputsFinal | Public inputs (ie output) of the last iteration of the private kernel circuit for this transaction. | -| proof | Buffer | Zero-knowledge honk proof for the last iteration of the private kernel circuit for this transaction. | -| encryptedLogs | Buffer[][] | Encrypted logs emitted per function in this transaction. Position `i` contains the encrypted logs emitted by the `i`-th function execution. | -| unencryptedLogs | Buffer[][] | Equivalent to the above but for unencrypted logs. | -| enqueuedPublicFunctionCalls | PublicCallRequest[] | List of public function calls to run during public execution. | -| newContracts | ExtendedContractData[] | List of new contracts to be deployed as part of this transaction. | +| data | PrivateKernelPublicInputsFinal | Public inputs (ie output) of the last iteration of the private kernel circuit for this transaction. | +| proof | Buffer | Zero-knowledge honk proof for the last iteration of the private kernel circuit for this transaction. | +| encryptedLogs | Buffer[][] | Encrypted logs emitted per function in this transaction. Position `i` contains the encrypted logs emitted by the `i`-th function execution. | +| unencryptedLogs | Buffer[][] | Equivalent to the above but for unencrypted logs. | +| enqueuedPublicFunctionCalls | PublicCallRequest[] | List of public function calls to run during public execution. | +| newContracts | ExtendedContractData[] | List of new contracts to be deployed as part of this transaction. | ### Private kernel public inputs final @@ -21,6 +38,7 @@ Output of the last iteration of the private kernel circuit. Includes _accumulate **Accumulated data** + | Field | Type | Description | |-------|------|-------------| | aggregationObject | AggregationObject | Aggregated proof of all the previous kernel iterations. | @@ -39,6 +57,7 @@ Output of the last iteration of the private kernel circuit. Includes _accumulate **Block header** + | Field | Type | Description | |-------|------|-------------| | noteHashTreeRoot | Field | Root of the note hash tree at the time of when this information was assembled. | @@ -52,22 +71,26 @@ Output of the last iteration of the private kernel circuit. Includes _accumulate ### Public call request + + Each _public call request_ is the preimage of a public call stack item in the transaction's `data`, and has the following fields: + | Field | Type | Description | |----------|----------|----------| -| contractAddress | AztecAddress | Address of the contract on which the function is invoked. | -| callContext | CallContext | Includes function selector and caller. | -| args | Field[] | Arguments to the function call. | +| contractAddress | AztecAddress | Address of the contract on which the function is invoked. | +| callContext | CallContext | Includes function selector and caller. | +| args | Field[] | Arguments to the function call. | | sideEffectCounter | number? | Optional counter for ordering side effects of this function call. | ### Extended contract data Each _extended contract data_ corresponds to a contract being deployed by the transaction, and has the following fields: + | Field | Type | Description | |----------|----------|----------| -| address | AztecAddress | Address where the contract is to be deployed. | +| address | AztecAddress | Address where the contract is to be deployed. | | portalAddress | EthereumAddress | Portal address on L1 for this contract (zero if none). | | bytecode | Buffer | Encoded Brillig bytecode for all public functions in the contract. | | publicKey | PublicKey | Master public encryption key for this contract (zero if none). | @@ -75,11 +98,16 @@ Each _extended contract data_ corresponds to a contract being deployed by the tr ## Transaction hash -A transaction is identified by its _transaction hash_. In order to be able to identify a transaction before it has been locally executed, the hash is computed from its [_transaction execution request_](./local-execution.md#execution-request) by hashing: +A transaction is identified by its `transaction_hash`. In order to be able to identify a transaction before it has been locally executed, the hash is computed from its [_transaction execution request_](./local-execution.md#execution-request) by hashing: + + - `origin` - `functionSelector` - `argsHash` -- `txContent` +- `txContent` -The resulting transaction hash is always emitted during local execution as the first nullifier of the transaction, in order to prevent replay attacks. This is enforced by the private kernel circuit. \ No newline at end of file +The resulting transaction hash is always emitted during local execution as the first nullifier of the transaction, in order to prevent replay attacks. This is enforced by the private kernel circuit. diff --git a/yellow-paper/docs/transactions/validity.md b/yellow-paper/docs/transactions/validity.md index 9d464e0d858..d109bded169 100644 --- a/yellow-paper/docs/transactions/validity.md +++ b/yellow-paper/docs/transactions/validity.md @@ -1,13 +1,19 @@ # Validity conditions -The _validity conditions_ of a transaction define when a [_transaction object_](./tx-object.md) is valid. Nodes should check the validity of a transaction when they receive it either directly or through the p2p pool, and if they found it invalid, should drop it immediately and not broadcast it. +The _validity conditions_ of a transaction define when a [_transaction object_](./tx-object.md) is valid. Nodes should check the validity of a transaction when they receive it either directly or through the p2p pool, and if they find it to be invalid, should drop it immediately and not broadcast it. In addition to being well-formed, the transaction object needs to pass the following checks: + + - **Proof is valid**: The `proof` for the given public `data` should be valid according to a protocol-wide verification key for the final private kernel circuit. -- **No double-spends**: No `nullifier` in the transaction `data` should be already present in the nullifier tree. +- **No duplicate nullifiers**: No `nullifier` in the transaction `data` should be already present in the nullifier tree. - **No pending private function calls**: The `data` private call stack should be empty. -- **Valid historic data**: The tree roots in the block header of `data` must match the tree roots of a block in the chain. +- **Valid historic data**: The tree roots in the block header of `data` must match the tree roots of a historical block in the chain. - **Maximum block number not exceeded**: The transaction must be included in a block with height no greater than the value specified in `maxBlockNum` within the transaction's `data`. - **Preimages must match commitments in `data`**: The expanded fields in the transaction object should match the commitments (hashes) to them in the public `data`. - The `encryptedLogs` should match the `encryptedLogsHash` and `encryptedLogPreimagesLength` in the transaction `data`. @@ -15,4 +21,4 @@ In addition to being well-formed, the transaction object needs to pass the follo - Each public call stack item in the transaction `data` should have a corresponding preimage in the `enqueuedPublicFunctionCalls`. - Each new contract data in transaction `data` should have a corresponding preimage in the `newContracts`. -Note that all checks but the last one are enforced by the base rollup circuit when the transaction is included in a block. \ No newline at end of file +Note that all checks but the last one are enforced by the base rollup circuit when the transaction is included in a block. diff --git a/yellow-paper/docusaurus.config.js b/yellow-paper/docusaurus.config.js index 522eac56374..42b316a995a 100644 --- a/yellow-paper/docusaurus.config.js +++ b/yellow-paper/docusaurus.config.js @@ -39,6 +39,7 @@ const config = { markdown: { mermaid: true, }, + themes: ["@docusaurus/theme-mermaid"], presets: [ @@ -106,6 +107,7 @@ const config = { }, ], }, + footer: { style: "dark", links: [ @@ -139,6 +141,7 @@ const config = { ], copyright: `Copyright © ${new Date().getFullYear()} Aztec Labs, Inc. Built with Docusaurus.`, }, + prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, diff --git a/yellow-paper/sidebars.js b/yellow-paper/sidebars.js index 5a114dbcf11..c86bebee755 100644 --- a/yellow-paper/sidebars.js +++ b/yellow-paper/sidebars.js @@ -20,12 +20,44 @@ const sidebars = { yellowPaperSidebar: [ "intro", + { + label: "Cryptography", + type: "category", + link: { type: "doc", id: "cryptography/index" }, + items: [ + { + label: "Proving System", + type: "category", + link: { + type: "doc", + id: "cryptography/proving-system/performance-targets", + }, + items: [ + "cryptography/proving-system/performance-targets", + "cryptography/proving-system/overview", + "cryptography/proving-system/data-bus", + ], + }, + { + label: "Hashing", + type: "category", + link: { type: "doc", id: "cryptography/hashing/hashing" }, + items: [ + "cryptography/hashing/hashing", + "cryptography/hashing/pedersen", + ], + }, + "cryptography/merkle-trees", + ], + }, { label: "Addresses & Keys", type: "category", link: { type: "doc", id: "addresses-and-keys/index" }, items: [ - "addresses-and-keys/specification", + "addresses-and-keys/address", + "addresses-and-keys/keys-requirements", + "addresses-and-keys/keys", "addresses-and-keys/precompiles", "addresses-and-keys/diversified-and-stealth", ], @@ -80,10 +112,16 @@ const sidebars = { ], }, { - label: "Cross-chain communication", + label: "L1 smart contracts", type: "category", - link: { type: "doc", id: "cross-chain-communication/index" }, - items: ["cross-chain-communication/da"], + link: { type: "doc", id: "l1-smart-contracts/index" }, + items: ["l1-smart-contracts/index"], + }, + { + label: "Data publication and availability", + type: "category", + link: { type: "doc", id: "data-publication-and-availability/index" }, + items: ["data-publication-and-availability/index"], }, { label: "Logs", @@ -106,7 +144,7 @@ const sidebars = { { label: "Gas & Fees", type: "category", - link: { type: "doc", id: "gas-and-fees/gas-and-fees" }, + link: { type: "doc", id: "gas-and-fees/index" }, items: [], }, { @@ -119,15 +157,6 @@ const sidebars = { "decentralization/p2p-network", ], }, - { - label: "Cryptography", - type: "category", - link: { type: "doc", id: "cryptography/performance-targets" }, - items: [ - "cryptography/performance-targets", - "cryptography/protocol-overview", - ], - }, // Protocol Statements? { label: "Circuits",