From 5a38cea3f7c1567a8eea3d6c2c58cad6f79b05f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bene=C5=A1?= Date: Thu, 24 Aug 2023 11:36:47 +0200 Subject: [PATCH] docs: events (#1768) Fixes #1756 --- docs/docs/dev_docs/contracts/events.md | 107 +++++++++++++++++- docs/docs/dev_docs/getting_started/cli.md | 2 +- docs/docs/dev_docs/getting_started/sandbox.md | 2 +- .../src/e2e_public_token_contract.test.ts | 4 + .../private_token_contract/src/main.nr | 12 ++ .../noir-libs/value-note/src/utils.nr | 4 + 6 files changed, 125 insertions(+), 6 deletions(-) diff --git a/docs/docs/dev_docs/contracts/events.md b/docs/docs/dev_docs/contracts/events.md index f6a0c5666e7..5eb174bbf47 100644 --- a/docs/docs/dev_docs/contracts/events.md +++ b/docs/docs/dev_docs/contracts/events.md @@ -1,17 +1,116 @@ ## Events +Events in Aztec work similarly to Ethereum events in the sense that they are a way for contracts to communicate with the outside world. +They are emitted by contracts and stored inside each instance of an AztecNode. +> Aztec events are currently represented as raw data and are not ABI encoded. +> ABI encoded events are a feature that will be added in the future. + +Unlike on Ethereum, there are 2 types of events supported by Aztec: encrypted and unencrypted. + +### Encrypted Events +Encrypted events can only be emitted by private functions and are encrypted using a public key of a recipient. +For this reason it is necessary to register a recipient in the Aztec RPC Server before encrypting the events for them. +Recipients can be registered using the Aztec CLI or Aztec.js: + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + +```bash +aztec-cli register-recipient --address 0x147392a39e593189902458f4303bc6e0a39128c5a1c1612f76527a162d36d529 --public-key 0x26e193aef4f83c70651485b5526c6d01a36d763223ab24efd1f9ff91b394ac0c20ad99d0ef669dc0dde8d5f5996c63105de8e15c2c87d8260b9e6f02f72af622 --partial-address 0x200e9a6c2d2e8352012e51c6637659713d336405c29386c7c4ac56779ab54fa7 +``` + + + + +```ts +const aztecAddress = AztecAddress.fromString("0x147392a39e593189902458f4303bc6e0a39128c5a1c1612f76527a162d36d529"); +const publicKey = Point.fromString("0x26e193aef4f83c70651485b5526c6d01a36d763223ab24efd1f9ff91b394ac0c20ad99d0ef669dc0dde8d5f5996c63105de8e15c2c87d8260b9e6f02f72af622"); +const partialAddress = Fr.fromString("0x200e9a6c2d2e8352012e51c6637659713d336405c29386c7c4ac56779ab54fa7"); + +const completeAddress = CompleteAddress.create(aztecAddress, publicKey, partialKey); +await aztecRpc.registerRecipient(completeAddress); +``` + + + + +> **NOTE**: If a note recipient is one of the accounts inside the Aztec RPC Server, we don't need to register it as a recipient because we already have the public key available. + +> At this point the Sandbox only enables the emitting of encrypted note preimages through encrypted events. +> In the future we will allow emitting arbitrary information. +> (If you currently emit arbitrary information, Aztec RPC Server will fail to decrypt, process and store this data, so it will not be queryable). + +To emit encrypted logs first import the `emit_encrypted_log` utility function inside your contract: + +#include_code encrypted_import /yarn-project/noir-libs/value-note/src/utils.nr rust + +Then you can call the function: + +#include_code encrypted /yarn-project/noir-libs/value-note/src/utils.nr rust -### Constraining events ### Unencrypted Events +Unencrypted events are events which can be read by anyone. +They can be emitted by both public and private functions. -### Encrypted Events +:::danger + +Emitting unencrypted events from private function is a significant privacy leak and it should be considered by the developer whether it is acceptable. + +::: + +Once emitted, unencrypted events are stored in AztecNode and can be queried by anyone: + + + +```bash +aztec-cli get-logs --from 5 --limit 1 +``` + + + + +#include_code logs /yarn-project/end-to-end/src/e2e_public_token_contract.test.ts typescript + + + + +To emit unencrypted logs first import the `emit_unencrypted_log` utility function inside your contract: + +#include_code unencrypted_import /yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr rust + +Then you can call the function: + +#include_code unencrypted /yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr rust ### Costs -Explain L1 cost to emit an event. +All event data is pushed to Ethereum as calldata by the sequencer and for this reason the cost of emitting an event is non-trivial. + +> Note: the cost of submitting calldata to Ethereum is currently 4 gas per byte. Currently, in the Sandbox, an encypted note has a fixed overhead of 4 field elements (to broadcast an ephemeral public key, a contract address, and a storage slot); plus a variable number of field elements depending on the type of note being emitted. +> A `ValueNote`, for example, currently uses 3 fields elements (plus the fixed overhead of 4). That's roughly `7 * 32 = 224` bytes of information, costing roughly 896 gas. + +> There are plans to compress encrypted note data further. +> There are plans to adopt EIP-4844 blobs to reduce the cost of data submission further. ## Processing events +Both the encrypted and unencrypted events are stored in AztecNode. +Unencrypted logs can be queried by anyone as we described above in the [Unencrypted Events](#unencrypted-events) section. + +Encrypted logs need to first be decrypted: ### Decrypting +One function of Aztec RPC Server is constantly loading encrypted logs from AztecNode and trying to decrypt them. +When new encrypted logs are obtained, the Aztec RPC Server will try to decrypt them using the private encryption key of all the accounts registered inside Aztec RPC Server. +If the decryption is successful, the Aztec RPC Server will store the decrypted note inside a database. +If the decryption fails, the specific log will be discarded. + +For the Aztec RPC Server to successfully process the decrypted note we need to compute the note's 'note hash' and 'nullifier'. +Aztec.nr enables smart contract developers to design custom notes, meaning developers can also customise how a note's note hash and nullifier should be computed. Because of this customisability, and because there will be a potentially-unlimited number of smart contracts deployed to Aztec, an Aztec RPR Server needs to be 'taught' how to compute the custom note hashes and nullifiers for a particular contract. Therefore, developers will need to implement a `compute_note_hash_and_nullifier` function inside their contracts. +Every time a new note is successfully decrypted, the Aztec RPC Server will expect the existence of a `compute_note_hash_and_nullifier` function, which must teach it how to correctly process the new note. + +This is an example implementation inside the `PrivateTokenContract`: -### Stev \ No newline at end of file +#include_code compute_note_hash_and_nullifier /yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr rust \ No newline at end of file diff --git a/docs/docs/dev_docs/getting_started/cli.md b/docs/docs/dev_docs/getting_started/cli.md index b7244e958c6..6272d5f9463 100644 --- a/docs/docs/dev_docs/getting_started/cli.md +++ b/docs/docs/dev_docs/getting_started/cli.md @@ -222,7 +222,7 @@ View result: [ Finally, we can use the CLI's `get-logs` command to retrieve unencrypted logs emitted by the contract: ``` -% aztec-cli get-logs 5 1 +% aztec-cli get-logs --from 5 --limit 1 Logs found: Coins transferred diff --git a/docs/docs/dev_docs/getting_started/sandbox.md b/docs/docs/dev_docs/getting_started/sandbox.md index 9fde1de0c4b..adf2dbdfd40 100644 --- a/docs/docs/dev_docs/getting_started/sandbox.md +++ b/docs/docs/dev_docs/getting_started/sandbox.md @@ -197,7 +197,7 @@ Great!. The Sandbox is running and we are able to interact with it. The next step is to create some accounts. An in-depth explaining about accounts on aztec can be found [here](../../concepts/foundation/accounts/main.md). But creating an account on the Sandbox does 2 things: -1. Deploys an account contract reprepresenting you allowing you to perform actions on the network (deploy contracts, call functions etc). +1. Deploys an account contract -- representing you -- allowing you to perform actions on the network (deploy contracts, call functions etc). 2. Adds your encryption keys to the RPC Server allowing it to decrypt and manage your private state. Continue with adding the following to the `index.ts` file in our example: diff --git a/yarn-project/end-to-end/src/e2e_public_token_contract.test.ts b/yarn-project/end-to-end/src/e2e_public_token_contract.test.ts index 6a1e3b79658..ad40149332b 100644 --- a/yarn-project/end-to-end/src/e2e_public_token_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_public_token_contract.test.ts @@ -27,8 +27,12 @@ describe('e2e_public_token_contract', () => { }; const expectLogsFromLastBlockToBe = async (logMessages: string[]) => { + // docs:start:logs + const l2BlockNum = await aztecRpcServer.getBlockNumber(); const unencryptedLogs = await aztecRpcServer.getUnencryptedLogs(l2BlockNum, 1); + + // docs:end:logs const unrolledLogs = L2BlockL2Logs.unrollLogs(unencryptedLogs); const asciiLogs = unrolledLogs.map(log => log.toString('ascii')); diff --git a/yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr index 514377b95a8..4e052976675 100644 --- a/yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr @@ -15,8 +15,13 @@ contract PrivateToken { note_header::NoteHeader, utils as note_utils, }; + + // docs:start:unencrypted_import + use dep::aztec::log::emit_unencrypted_log; + // docs:end:unencrypted_import + // docs:start:storage-import use crate::storage::Storage; // docs:end:storage-import @@ -62,8 +67,13 @@ contract PrivateToken { // Insert new note to a set of user notes and emit the newly created encrypted note preimage via oracle call. let owner_balance = storage.balances.at(owner); send_note(&mut context, owner_balance, amount, owner); + + // docs:start:unencrypted + emit_unencrypted_log(&mut context, "Coins minted"); + // docs:end:unencrypted + // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel.. context.finish() } @@ -116,8 +126,10 @@ contract PrivateToken { // Computes note hash and nullifier. // Note 1: Needs to be defined by every contract producing logs. // Note 2: Having it in all the contracts gives us the ability to compute the note hash and nullifier differently for different kind of notes. + // docs:start:compute_note_hash_and_nullifier unconstrained fn compute_note_hash_and_nullifier(contract_address: Field, nonce: Field, storage_slot: Field, preimage: [Field; VALUE_NOTE_LEN]) -> [Field; 4] { let note_header = NoteHeader { contract_address, nonce, storage_slot }; note_utils::compute_note_hash_and_nullifier(ValueNoteMethods, note_header, preimage) } + // docs:end:compute_note_hash_and_nullifier } diff --git a/yarn-project/noir-libs/value-note/src/utils.nr b/yarn-project/noir-libs/value-note/src/utils.nr index 23705ba43be..e231a71bf3b 100644 --- a/yarn-project/noir-libs/value-note/src/utils.nr +++ b/yarn-project/noir-libs/value-note/src/utils.nr @@ -1,5 +1,9 @@ use dep::aztec::context::PrivateContext; +// docs:start:encrypted_import + use dep::aztec::log::emit_encrypted_log; + +// docs:end:encrypted_import use dep::aztec::note::note_getter_options::NoteGetterOptions; use dep::aztec::oracle::get_public_key::get_public_key; use dep::aztec::state_vars::set::Set;