diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 874458dddfdb..e276945dceba 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.87.6" + ".": "0.88.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 47082dc49d8f..dcdf3c36a39c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,507 @@ # Changelog +## [0.88.0](https://github.com/AztecProtocol/aztec-packages/compare/v0.87.6...v0.88.0) (2025-06-23) + + +### ⚠ BREAKING CHANGES + +* Reconstruct discard in note hash trace builder ([#15187](https://github.com/AztecProtocol/aztec-packages/issues/15187)) +* **avm:** keccak gadget ([#14444](https://github.com/AztecProtocol/aztec-packages/issues/14444)) +* **AVM:** Note hash tree check ([#15150](https://github.com/AztecProtocol/aztec-packages/issues/15150)) +* adding the extra required constraints to databus and lookup ([#15007](https://github.com/AztecProtocol/aztec-packages/issues/15007)) +* epoch proof submission window ([#15109](https://github.com/AztecProtocol/aztec-packages/issues/15109)) +* purge forwarder ([#15067](https://github.com/AztecProtocol/aztec-packages/issues/15067)) +* update sequencer metrics ([#15094](https://github.com/AztecProtocol/aztec-packages/issues/15094)) +* public bytecode hashing does a standard poseidon2 hash. Unconstrain AVM bytecode hashing for now. ([#14918](https://github.com/AztecProtocol/aztec-packages/issues/14918)) +* reorder merge verification ([#14963](https://github.com/AztecProtocol/aztec-packages/issues/14963)) +* **avm:** reorder pil ([#15078](https://github.com/AztecProtocol/aztec-packages/issues/15078)) +* change register mem info behaviour ([#15072](https://github.com/AztecProtocol/aztec-packages/issues/15072)) +* minimum committee size ([#15019](https://github.com/AztecProtocol/aztec-packages/issues/15019)) +* staking entry queue ([#14986](https://github.com/AztecProtocol/aztec-packages/issues/14986)) +* auto-encoding array as BoundedVec ([#14891](https://github.com/AztecProtocol/aztec-packages/issues/14891)) +* **slash:** Make slashing function bounded by available balance ([#14983](https://github.com/AztecProtocol/aztec-packages/issues/14983)) +* **avm:** charge base addressing gas conditionally ([#15034](https://github.com/AztecProtocol/aztec-packages/issues/15034)) +* remove pxe network handling ([#15025](https://github.com/AztecProtocol/aztec-packages/issues/15025)) +* **avm:** update addressing gas ([#15018](https://github.com/AztecProtocol/aztec-packages/issues/15018)) +* make block number a u32 everywhere ([#15004](https://github.com/AztecProtocol/aztec-packages/issues/15004)) +* Add fee payment in the transaction trace ([#14929](https://github.com/AztecProtocol/aztec-packages/issues/14929)) +* introduce pagination and limits ([#14554](https://github.com/AztecProtocol/aztec-packages/issues/14554)) +* **avm:** execution id (clk) ([#14975](https://github.com/AztecProtocol/aztec-packages/issues/14975)) +* remove num_txs from content commitment ([#14947](https://github.com/AztecProtocol/aztec-packages/issues/14947)) +* Reward boosting ([#14868](https://github.com/AztecProtocol/aztec-packages/issues/14868)) +* **avm:** constrain addressing relative overflow ([#14901](https://github.com/AztecProtocol/aztec-packages/issues/14901)) +* diff in deposit amount and minimum stake ([#14833](https://github.com/AztecProtocol/aztec-packages/issues/14833)) +* **avm:** full addressing ([#14632](https://github.com/AztecProtocol/aztec-packages/issues/14632)) +* Error when re-assigning a mutable reference (https://github.com/noir-lang/noir/pull/8790) +* indexing arrays with non-u32 is now an error (https://github.com/noir-lang/noir/pull/8804) +* add side-effect "discard" flag to AVM execution component ([#14626](https://github.com/AztecProtocol/aztec-packages/issues/14626)) +* Fiat-Shamir the vk ([#14452](https://github.com/AztecProtocol/aztec-packages/issues/14452)) +* handle error from sending tx properly ([#14906](https://github.com/AztecProtocol/aztec-packages/issues/14906)) +* blob batching ([#14648](https://github.com/AztecProtocol/aztec-packages/issues/14648)) +* Goblin is responsible for the merge ([#14655](https://github.com/AztecProtocol/aztec-packages/issues/14655)) +* do not use dynamic types in interfaces ([#14203](https://github.com/AztecProtocol/aztec-packages/issues/14203)) +* hash only 20 bytes of the l1 recipient for the l2 to l1 message ([#14810](https://github.com/AztecProtocol/aztec-packages/issues/14810)) +* Make fee per gas uint128 across the protocol ([#14796](https://github.com/AztecProtocol/aztec-packages/issues/14796)) +* disallow casting numeric to bool (https://github.com/noir-lang/noir/pull/8703) +* disallow specifying associated items via generics (https://github.com/noir-lang/noir/pull/8756) +* **avm:** Decrease relation degree in ecc ([#14840](https://github.com/AztecProtocol/aztec-packages/issues/14840)) +* stricter vote call ([#14818](https://github.com/AztecProtocol/aztec-packages/issues/14818)) +* validate l1toL2Message tree snapshot ([#14721](https://github.com/AztecProtocol/aztec-packages/issues/14721)) +* do not expose the counter of l2toL1Message ([#14737](https://github.com/AztecProtocol/aztec-packages/issues/14737)) +* clarified nonce naming ([#14727](https://github.com/AztecProtocol/aztec-packages/issues/14727)) +* committing to events ([#14634](https://github.com/AztecProtocol/aztec-packages/issues/14634)) +* Defer computing effective gas fees to base rollup ([#14677](https://github.com/AztecProtocol/aztec-packages/issues/14677)) +* p2p status handshake ([#14543](https://github.com/AztecProtocol/aztec-packages/issues/14543)) +* tx trace ([#14418](https://github.com/AztecProtocol/aztec-packages/issues/14418)) + +### Features + +* add cluster of public open telemetry collectors ([#15160](https://github.com/AztecProtocol/aztec-packages/issues/15160)) ([a923b0d](https://github.com/AztecProtocol/aztec-packages/commit/a923b0dd4c74291fcefa86c41b3b5dffca7e4178)) +* Add fee payment in the transaction trace ([#14929](https://github.com/AztecProtocol/aztec-packages/issues/14929)) ([fa23733](https://github.com/AztecProtocol/aztec-packages/commit/fa23733be8278348dff0959c8094bcfd89eb44d5)) +* add more archiver metrics ([#15133](https://github.com/AztecProtocol/aztec-packages/issues/15133)) ([9071986](https://github.com/AztecProtocol/aztec-packages/commit/9071986bfe3af58c70d2c80c10f523e22bfe4cb4)) +* add proposeWithLock to GSE ([#15159](https://github.com/AztecProtocol/aztec-packages/issues/15159)) ([7e505bc](https://github.com/AztecProtocol/aztec-packages/commit/7e505bcd7fcd90a7d5fe893194272157cc9ec848)) +* Add RPC methods to fetch membership witnesses by value ([#14990](https://github.com/AztecProtocol/aztec-packages/issues/14990)) ([1650b3d](https://github.com/AztecProtocol/aztec-packages/commit/1650b3d017a2a4fa5182c8787e79cc108e24e1f4)), closes [#14302](https://github.com/AztecProtocol/aztec-packages/issues/14302) +* add side-effect "discard" flag to AVM execution component ([#14626](https://github.com/AztecProtocol/aztec-packages/issues/14626)) ([a90d900](https://github.com/AztecProtocol/aztec-packages/commit/a90d90057331518bf0fbceb93dcf4abba561eb35)) +* add slashing benchmark and optimize proposer lookup ([#15136](https://github.com/AztecProtocol/aztec-packages/issues/15136)) ([2fb1dd1](https://github.com/AztecProtocol/aztec-packages/commit/2fb1dd1c3a73b872ba2c6e24ff0303eb2f7ac0fc)) +* add test call interface traits for txe call flows ([#14366](https://github.com/AztecProtocol/aztec-packages/issues/14366)) ([8403054](https://github.com/AztecProtocol/aztec-packages/commit/840305452ba6613c3b85ee775f6f4a0ed348a1df)) +* add vkey formuli and tests ([#14682](https://github.com/AztecProtocol/aztec-packages/issues/14682)) ([1162c21](https://github.com/AztecProtocol/aztec-packages/commit/1162c212a8266ca3fc5a991a90ba5f5e77b8d64b)) +* adding support for recursive verification for ZK UltraHonk ([#14642](https://github.com/AztecProtocol/aztec-packages/issues/14642)) ([ca296ec](https://github.com/AztecProtocol/aztec-packages/commit/ca296ec45c9a884ef5989939c3d782a81de4b859)) +* allow printing each SSA interpreter definition (https://github.com/noir-lang/noir/pull/8865) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* auto-encoding array as BoundedVec ([#14891](https://github.com/AztecProtocol/aztec-packages/issues/14891)) ([e56baa7](https://github.com/AztecProtocol/aztec-packages/commit/e56baa7f24bac54baf9e2f22f6f33ae6fa8b8c0f)) +* auto-update exported metrics ([#15154](https://github.com/AztecProtocol/aztec-packages/issues/15154)) ([28bf32b](https://github.com/AztecProtocol/aztec-packages/commit/28bf32bfc2566751e8baa0a9125a9db20d474bee)) +* **avm:** cd_copy, rd_copy and cd_hashing ([#14161](https://github.com/AztecProtocol/aztec-packages/issues/14161)) ([7d22378](https://github.com/AztecProtocol/aztec-packages/commit/7d223783d91db15002a09abc1b52d1455eb3e3da)) +* **avm:** constrain addressing relative overflow ([#14901](https://github.com/AztecProtocol/aztec-packages/issues/14901)) ([3313701](https://github.com/AztecProtocol/aztec-packages/commit/3313701590a3c752f7cd28463630b434e92d156f)) +* **avm:** debug permutation builder ([#15099](https://github.com/AztecProtocol/aztec-packages/issues/15099)) ([a9f91dd](https://github.com/AztecProtocol/aztec-packages/commit/a9f91dd1e5946aecfeb192ce869eb3e8eca2742e)) +* **avm:** execution id (clk) ([#14975](https://github.com/AztecProtocol/aztec-packages/issues/14975)) ([1faede1](https://github.com/AztecProtocol/aztec-packages/commit/1faede1b877a148b46fe6c8ad03986e139b38e95)) +* **avm:** execution temporality groups init ([#14631](https://github.com/AztecProtocol/aztec-packages/issues/14631)) ([3c40138](https://github.com/AztecProtocol/aztec-packages/commit/3c401381af0c217c2d1fc4ef8a78e0a4ea3b61f4)) +* **avm:** full addressing ([#14632](https://github.com/AztecProtocol/aztec-packages/issues/14632)) ([0accb13](https://github.com/AztecProtocol/aztec-packages/commit/0accb13179c69d42073e71b5dd12c085ffb66449)) +* **avm:** interaction definitions ([#15130](https://github.com/AztecProtocol/aztec-packages/issues/15130)) ([a9bb2f6](https://github.com/AztecProtocol/aztec-packages/commit/a9bb2f683797c8e631b892c5f539a2867bddaabf)) +* **avm:** internal call stack ([#14178](https://github.com/AztecProtocol/aztec-packages/issues/14178)) ([5ca4385](https://github.com/AztecProtocol/aztec-packages/commit/5ca43853c40a2b8bfbc2148d5fdd46c6f82a74eb)) +* **avm:** keccak gadget ([#14444](https://github.com/AztecProtocol/aztec-packages/issues/14444)) ([80d8004](https://github.com/AztecProtocol/aztec-packages/commit/80d800435dd8dfa325e38593d83b405488449dba)) +* **AVM:** Note hash tree check ([#15150](https://github.com/AztecProtocol/aztec-packages/issues/15150)) ([cb89edc](https://github.com/AztecProtocol/aztec-packages/commit/cb89edc02f7d386e48ee81660565d91895a3464b)) +* batch partial note completion processing ([#15051](https://github.com/AztecProtocol/aztec-packages/issues/15051)) ([aac426c](https://github.com/AztecProtocol/aztec-packages/commit/aac426c9418162aeee8162ed45014b9393d605b8)) +* **bb:** automatic sol hpp generation ([#15164](https://github.com/AztecProtocol/aztec-packages/issues/15164)) ([5d81047](https://github.com/AztecProtocol/aztec-packages/commit/5d81047d880386c72ab2e6701b0c86ae607c3618)) +* **bench:** browser proving benches ([#15042](https://github.com/AztecProtocol/aztec-packages/issues/15042)) ([d5a262a](https://github.com/AztecProtocol/aztec-packages/commit/d5a262aabc20f5384a720a0d831e2ec28badc7f9)) +* better CIVC standalone vk test ([#14765](https://github.com/AztecProtocol/aztec-packages/issues/14765)) ([1142ab2](https://github.com/AztecProtocol/aztec-packages/commit/1142ab229f2eb9e0603457dcb0ca7b52de9fab23)) +* blob batching ([#14648](https://github.com/AztecProtocol/aztec-packages/issues/14648)) ([fe72cd2](https://github.com/AztecProtocol/aztec-packages/commit/fe72cd2d84eefb85ced0bd4bdca61eea0359c871)) +* capture aztec attributes from the env ([#15155](https://github.com/AztecProtocol/aztec-packages/issues/15155)) ([2f9d286](https://github.com/AztecProtocol/aztec-packages/commit/2f9d2861e2863437732f136f554fc8275c1bb1e7)) +* committing to events ([#14634](https://github.com/AztecProtocol/aztec-packages/issues/14634)) ([639e98f](https://github.com/AztecProtocol/aztec-packages/commit/639e98fe3dedbf394bfe94a44d7eadc6aaf36502)) +* completing partial notes in private ([#14533](https://github.com/AztecProtocol/aztec-packages/issues/14533)) ([2078cf0](https://github.com/AztecProtocol/aztec-packages/commit/2078cf0f44d6e71ea41d87876804c3d8d0a63cd9)) +* Defer computing effective gas fees to base rollup ([#14677](https://github.com/AztecProtocol/aztec-packages/issues/14677)) ([581185c](https://github.com/AztecProtocol/aztec-packages/commit/581185c1b7bc16a02f97cfa73b91807062d774a5)) +* detecting event selector collisions ([#14885](https://github.com/AztecProtocol/aztec-packages/issues/14885)) ([35feb89](https://github.com/AztecProtocol/aztec-packages/commit/35feb8949202015ec80dd3349f35380ea988ce1a)) +* diff in deposit amount and minimum stake ([#14833](https://github.com/AztecProtocol/aztec-packages/issues/14833)) ([cc83d05](https://github.com/AztecProtocol/aztec-packages/commit/cc83d05ac635652ff50bcbc370c2840184e16fec)) +* **docs:** nits ([#14212](https://github.com/AztecProtocol/aztec-packages/issues/14212)) ([281d4a8](https://github.com/AztecProtocol/aztec-packages/commit/281d4a8fb487cc88c7287da116d6fbfb4f3cd5be)) +* executeTimeout provides AbortSignal ([#14799](https://github.com/AztecProtocol/aztec-packages/issues/14799)) ([77a0537](https://github.com/AztecProtocol/aztec-packages/commit/77a05378784eff824ab908c42f0db24fda49acd1)) +* Fiat-Shamir the vk ([#14452](https://github.com/AztecProtocol/aztec-packages/issues/14452)) ([2f46e63](https://github.com/AztecProtocol/aztec-packages/commit/2f46e6340e31e111f2277a5c5fde978b7780816e)) +* **fuzz:** Add comptime_vs_brillig_direct target (https://github.com/noir-lang/noir/pull/8924) ([4608046](https://github.com/AztecProtocol/aztec-packages/commit/4608046e462faf9ff3fbee265ba34dbecb5d0b31)) +* **fuzz:** Generate arbitrary constraints (https://github.com/noir-lang/noir/pull/8820) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* **fuzz:** Generate references in the AST fuzzer (https://github.com/noir-lang/noir/pull/8728) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* hide commitments and evaluations in (most of) the wires in translator ([#14797](https://github.com/AztecProtocol/aztec-packages/issues/14797)) ([a92ed80](https://github.com/AztecProtocol/aztec-packages/commit/a92ed8048a5482e3f111a2942546aec54c902edf)) +* introduce pagination and limits ([#14554](https://github.com/AztecProtocol/aztec-packages/issues/14554)) ([15c1214](https://github.com/AztecProtocol/aztec-packages/commit/15c121440ebf929fded7bd0c31960fb154a3fa7a)) +* ivc verification benchmark ([#15075](https://github.com/AztecProtocol/aztec-packages/issues/15075)) ([1e338a3](https://github.com/AztecProtocol/aztec-packages/commit/1e338a3fb2e2077f1feaee8b86c42644ff8a5352)) +* kernelless simulation ([#15066](https://github.com/AztecProtocol/aztec-packages/issues/15066)) ([f17855f](https://github.com/AztecProtocol/aztec-packages/commit/f17855f94a58189a98dd378743e67b774ea38144)) +* Limit the number of concurrent IVC verifiers ([#14756](https://github.com/AztecProtocol/aztec-packages/issues/14756)) ([2cff025](https://github.com/AztecProtocol/aztec-packages/commit/2cff02519cd1ac1e4134ccdf1f99d4715a9ec4c4)) +* make reward config updatable ([#15016](https://github.com/AztecProtocol/aztec-packages/issues/15016)) ([517501b](https://github.com/AztecProtocol/aztec-packages/commit/517501b8b3c82d562e946c782c8fe75426951f62)) +* message processing ([#14922](https://github.com/AztecProtocol/aztec-packages/issues/14922)) ([420e0a5](https://github.com/AztecProtocol/aztec-packages/commit/420e0a54fb4bfe8b04d7dd479f59ed0fe3ad3b74)) +* minimum committee size ([#15019](https://github.com/AztecProtocol/aztec-packages/issues/15019)) ([10badd2](https://github.com/AztecProtocol/aztec-packages/commit/10badd24359b04680068afd9ca24407383374db1)) +* offchain msg oracle and tx effect ([#14771](https://github.com/AztecProtocol/aztec-packages/issues/14771)) ([375f503](https://github.com/AztecProtocol/aztec-packages/commit/375f5033965c9e89f8bc5de14879501971ad42e1)) +* optimize `create_stub_base` to use arrays instead of slices ([#14985](https://github.com/AztecProtocol/aztec-packages/issues/14985)) ([9d6fb0b](https://github.com/AztecProtocol/aztec-packages/commit/9d6fb0bd1594726370a5f3a56591963452264642)) +* p2p gossipsub metrics ([#14689](https://github.com/AztecProtocol/aztec-packages/issues/14689)) ([1690dfd](https://github.com/AztecProtocol/aztec-packages/commit/1690dfd98a800d56f86779d8a745e0a0e7edf32b)) +* p2p status handshake ([#14543](https://github.com/AztecProtocol/aztec-packages/issues/14543)) ([cb95d27](https://github.com/AztecProtocol/aztec-packages/commit/cb95d27aa853fa62a5dc3a515984c7a63bb09046)) +* prevent secret values from leaking in log messages ([#12986](https://github.com/AztecProtocol/aztec-packages/issues/12986)) ([056e523](https://github.com/AztecProtocol/aztec-packages/commit/056e5230c330d6ad19900135ecf2a4787181496a)) +* print memory usage on every bb print ([#14728](https://github.com/AztecProtocol/aztec-packages/issues/14728)) ([c4a6583](https://github.com/AztecProtocol/aztec-packages/commit/c4a6583b5c26874ccdda9481f413b6e4cbf6e82f)) +* properly handle transfer of random values from interleaved to ordered polynomials in translator ([#14961](https://github.com/AztecProtocol/aztec-packages/issues/14961)) ([77a0068](https://github.com/AztecProtocol/aztec-packages/commit/77a00686be4080a71b03d68671da6c8b270b62aa)) +* Prover is constructed with VK ([#14822](https://github.com/AztecProtocol/aztec-packages/issues/14822)) ([184d5f7](https://github.com/AztecProtocol/aztec-packages/commit/184d5f7d956dd7251670a2dd6728f313cfdf3caf)) +* public bytecode hashing does a standard poseidon2 hash. Unconstrain AVM bytecode hashing for now. ([#14918](https://github.com/AztecProtocol/aztec-packages/issues/14918)) ([eb7990c](https://github.com/AztecProtocol/aztec-packages/commit/eb7990c89ac679ce7fdfc2e985670cbf85c04be2)) +* public function selectors collision comptime check ([#14886](https://github.com/AztecProtocol/aztec-packages/issues/14886)) ([d9c516e](https://github.com/AztecProtocol/aztec-packages/commit/d9c516e4776eeec3c206f86f8c3a9fe507bc3bf7)) +* public metrics ([#15140](https://github.com/AztecProtocol/aztec-packages/issues/15140)) ([69eacbd](https://github.com/AztecProtocol/aztec-packages/commit/69eacbd6f18ad2a9ac646c4ffaf0133b0cdf856a)) +* Reconstruct discard in note hash trace builder ([#15187](https://github.com/AztecProtocol/aztec-packages/issues/15187)) ([69efc9d](https://github.com/AztecProtocol/aztec-packages/commit/69efc9daf98e823f486416900de52fe8a44f25d9)) +* reduced memory consumption of Pippenger algorithm ([#14907](https://github.com/AztecProtocol/aztec-packages/issues/14907)) ([372980b](https://github.com/AztecProtocol/aztec-packages/commit/372980b2f09de871ee7152879dcda8bdfe254b9d)) +* respect bb's CRS_PATH config ([#15095](https://github.com/AztecProtocol/aztec-packages/issues/15095)) ([1542a83](https://github.com/AztecProtocol/aztec-packages/commit/1542a83b8e029762e119474de1725d84a1aee3d2)), closes [#14207](https://github.com/AztecProtocol/aztec-packages/issues/14207) +* Reward boosting ([#14868](https://github.com/AztecProtocol/aztec-packages/issues/14868)) ([61e4f05](https://github.com/AztecProtocol/aztec-packages/commit/61e4f05f8f8cb05b9c5765b5acb01bf1cf01d73d)) +* set zero knowledge to be the default in `bb-cli` ([#14858](https://github.com/AztecProtocol/aztec-packages/issues/14858)) ([3c1b9d5](https://github.com/AztecProtocol/aztec-packages/commit/3c1b9d57eadcc8fb03a7ed42610cf0a1ae4a32eb)) +* short circuit creation of `Type::InfixExpr` containing errors (https://github.com/noir-lang/noir/pull/8826) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* simplify apply function cfg immediately (https://github.com/noir-lang/noir/pull/8895) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* slash proposers of invalid state root ([#14791](https://github.com/AztecProtocol/aztec-packages/issues/14791)) ([17a9f05](https://github.com/AztecProtocol/aztec-packages/commit/17a9f0575768591a65f0ab16b6b465b0278679fa)), closes [#14409](https://github.com/AztecProtocol/aztec-packages/issues/14409) +* slash proposers properly on failed epoch ([#14873](https://github.com/AztecProtocol/aztec-packages/issues/14873)) ([dd8277b](https://github.com/AztecProtocol/aztec-packages/commit/dd8277bc6c015d3410ac1884ace6fba583681565)), closes [#14408](https://github.com/AztecProtocol/aztec-packages/issues/14408) +* SSA interpreter checks return value (https://github.com/noir-lang/noir/pull/8883) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* **ssa_fuzzer:** branching + constrains (https://github.com/noir-lang/noir/pull/8599) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* **ssa_fuzzer:** loops + signed (https://github.com/noir-lang/noir/pull/8881) ([4608046](https://github.com/AztecProtocol/aztec-packages/commit/4608046e462faf9ff3fbee265ba34dbecb5d0b31)) +* staking entry queue ([#14986](https://github.com/AztecProtocol/aztec-packages/issues/14986)) ([daebab2](https://github.com/AztecProtocol/aztec-packages/commit/daebab2c4bace59ef25ddb29040a040926f095e6)) +* **staking_asset_handler:** address binding ([#14888](https://github.com/AztecProtocol/aztec-packages/issues/14888)) ([f041e8d](https://github.com/AztecProtocol/aztec-packages/commit/f041e8d0a8dd85e2848bd9304fd4436d578d7ee4)) +* **staking_asset_handler:** merkle check in next ([#15179](https://github.com/AztecProtocol/aztec-packages/issues/15179)) ([2868628](https://github.com/AztecProtocol/aztec-packages/commit/2868628b6ae039db35b4267ab3975abc64a5d9b4)) +* stricter vote call ([#14818](https://github.com/AztecProtocol/aztec-packages/issues/14818)) ([c4fde2f](https://github.com/AztecProtocol/aztec-packages/commit/c4fde2f95c99868146b6791132889f479a24f6fa)) +* **testnet:** anti sybil staking asset handler ([#14422](https://github.com/AztecProtocol/aztec-packages/issues/14422)) ([752c6f2](https://github.com/AztecProtocol/aztec-packages/commit/752c6f2b42cab876b8df778db64ecd79e5222cf5)) +* tx collector stats ([#14795](https://github.com/AztecProtocol/aztec-packages/issues/14795)) ([a4afc97](https://github.com/AztecProtocol/aztec-packages/commit/a4afc9752a8785972f97edeb38325be817964c91)) +* tx trace ([#14418](https://github.com/AztecProtocol/aztec-packages/issues/14418)) ([47ade67](https://github.com/AztecProtocol/aztec-packages/commit/47ade6718923ef6ae2e7666d298565e345dd4c6e)) +* update sequencer metrics ([#15094](https://github.com/AztecProtocol/aztec-packages/issues/15094)) ([3b58351](https://github.com/AztecProtocol/aztec-packages/commit/3b5835195491b53670250790b18c14d922718574)) +* updating TXE with timestamp tracking ([#15014](https://github.com/AztecProtocol/aztec-packages/issues/15014)) ([ef6780e](https://github.com/AztecProtocol/aztec-packages/commit/ef6780e77e5f82f98ddfe92fd387cfd5fa7682e8)) +* use `asm` feature flag in arkworks (https://github.com/noir-lang/noir/pull/8792) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* Validate block proposal in hash ([#15146](https://github.com/AztecProtocol/aztec-packages/issues/15146)) ([88a99fb](https://github.com/AztecProtocol/aztec-packages/commit/88a99fb5fb938035dfeff0d9ed0cfd81b9eb2c5a)) + + +### Bug Fixes + +* (SSA interpreter) check requires_acir_gen_predicate for enable_side_effects (https://github.com/noir-lang/noir/pull/8869) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* (SSA interpreter) to_le_bits returns [u1; _], not [u8; _] (https://github.com/noir-lang/noir/pull/8837) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* `Attempted to read beyond buffer length` in failed p2p handshake ([#15006](https://github.com/AztecProtocol/aztec-packages/issues/15006)) ([b608f93](https://github.com/AztecProtocol/aztec-packages/commit/b608f9359219731d3c03407689a4fadaaa5a0d45)) +* `Cannot read properties of undefined (reading 'txHash')` in reqresp ([#14974](https://github.com/AztecProtocol/aztec-packages/issues/14974)) ([029d412](https://github.com/AztecProtocol/aztec-packages/commit/029d412798db97a7951f1ceca058cfd834cb5be8)) +* Add a aliased check for last stores (https://github.com/noir-lang/noir/pull/8955) ([4608046](https://github.com/AztecProtocol/aztec-packages/commit/4608046e462faf9ff3fbee265ba34dbecb5d0b31)) +* Add missing cases for finding function values in `find_functions_as_values` (https://github.com/noir-lang/noir/pull/8738) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* adding the extra required constraints to databus and lookup ([#15007](https://github.com/AztecProtocol/aztec-packages/issues/15007)) ([17ea18a](https://github.com/AztecProtocol/aztec-packages/commit/17ea18ade041a1bb437806e047c30b29da124094)) +* always error if integer literal doesn't fit its type on the fron… (https://github.com/noir-lang/noir/pull/8885) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* assorted SSA interpreter fixes (https://github.com/noir-lang/noir/pull/8893) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **avm:** charge base addressing gas conditionally ([#15034](https://github.com/AztecProtocol/aztec-packages/issues/15034)) ([c51485c](https://github.com/AztecProtocol/aztec-packages/commit/c51485c507bcf668c6e55129eb2c1794af4a0732)) +* await rollup registration ([#14825](https://github.com/AztecProtocol/aztec-packages/issues/14825)) ([aa2b59f](https://github.com/AztecProtocol/aztec-packages/commit/aa2b59f9451f1dedfa160726e1d9f732d7aef9c1)) +* bb readme ([#14955](https://github.com/AztecProtocol/aztec-packages/issues/14955)) ([fbfcb80](https://github.com/AztecProtocol/aztec-packages/commit/fbfcb80764d66d23b88e86193785a64b42205056)) +* **bb.js:** don't set maximum wasm memory >1GB on ios web, limit SRS ([#15001](https://github.com/AztecProtocol/aztec-packages/issues/15001)) ([082468f](https://github.com/AztecProtocol/aztec-packages/commit/082468f0c1e7e5424b3654b5be0f3560eed7e487)) +* **bb:** handle point at infinity with msgpack ([#14933](https://github.com/AztecProtocol/aztec-packages/issues/14933)) ([ea52d9f](https://github.com/AztecProtocol/aztec-packages/commit/ea52d9fc29bea28af45baa21864df078574ce211)) +* better package name validation and error messages for nargo new/init (https://github.com/noir-lang/noir/pull/8978) ([4608046](https://github.com/AztecProtocol/aztec-packages/commit/4608046e462faf9ff3fbee265ba34dbecb5d0b31)) +* bind self generic type in trait calls via a concrete type (https://github.com/noir-lang/noir/pull/8825) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* bind self generic type in trait calls via a concrete type in more cases (https://github.com/noir-lang/noir/pull/8827) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* box flake regex ([#14871](https://github.com/AztecProtocol/aztec-packages/issues/14871)) ([e211c61](https://github.com/AztecProtocol/aztec-packages/commit/e211c61a399f0baac3241ef5c146927456086cfc)) +* boxes test regex in test_patterns ([#14931](https://github.com/AztecProtocol/aztec-packages/issues/14931)) ([f5b6193](https://github.com/AztecProtocol/aztec-packages/commit/f5b61935364dbd0036174f06c9be3c2ff03d97ae)) +* **box:** remove codegenCache during clean ([#15045](https://github.com/AztecProtocol/aztec-packages/issues/15045)) ([76ca48a](https://github.com/AztecProtocol/aztec-packages/commit/76ca48a2187e3506bb464eae574e49476c2876ca)) +* bump archiver and WS schema versions ([#15128](https://github.com/AztecProtocol/aztec-packages/issues/15128)) ([d5c48d8](https://github.com/AztecProtocol/aztec-packages/commit/d5c48d855d83e19712c6b72281801c6fce176ec1)) +* catch unbound type variables during frontend compilation (https://github.com/noir-lang/noir/pull/8686) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* change register mem info behaviour ([#15072](https://github.com/AztecProtocol/aztec-packages/issues/15072)) ([9a37be7](https://github.com/AztecProtocol/aztec-packages/commit/9a37be7dc4208f0eeb44ec50194c00ca3f5c4cc6)) +* check "negate with overflow" in comptime code + allow u1 to be used in comptime code (https://github.com/noir-lang/noir/pull/8969) ([4608046](https://github.com/AztecProtocol/aztec-packages/commit/4608046e462faf9ff3fbee265ba34dbecb5d0b31)) +* close prover broker in e2e tests ([#14895](https://github.com/AztecProtocol/aztec-packages/issues/14895)) ([a2f9827](https://github.com/AztecProtocol/aztec-packages/commit/a2f982788faec144e05584a30e757b60c4f8acd6)) +* Compression benchmark fixes ([#15081](https://github.com/AztecProtocol/aztec-packages/issues/15081)) ([60e60f9](https://github.com/AztecProtocol/aztec-packages/commit/60e60f99f2634ad75a76b6e103858ff9ccd041d5)) +* Comptime field division should error when the rhs is zero (https://github.com/noir-lang/noir/pull/8845) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* comptime friendly Serialize imp of BoundedVec ([#14817](https://github.com/AztecProtocol/aztec-packages/issues/14817)) ([2b4257b](https://github.com/AztecProtocol/aztec-packages/commit/2b4257b3f86aac2df084aab58c43e4cc0b19d0ea)) +* **comptime:** Overflow on shl (https://github.com/noir-lang/noir/pull/8829) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* configurable json rpc server body limit ([#15047](https://github.com/AztecProtocol/aztec-packages/issues/15047)) ([bbf33e6](https://github.com/AztecProtocol/aztec-packages/commit/bbf33e6534f745d2f5a11c2f359583e881120fc4)) +* configurable l1tol2 message timeout ([#15026](https://github.com/AztecProtocol/aztec-packages/issues/15026)) ([3066026](https://github.com/AztecProtocol/aztec-packages/commit/30660269b33bab8cca354c41659533acf4d48e07)) +* correct bitsize in signed division (https://github.com/noir-lang/noir/pull/8733) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* Count array copies for slice functions (https://github.com/noir-lang/noir/pull/8867) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* Create calls to `apply` before function values are changed to fields in defunctionalize (https://github.com/noir-lang/noir/pull/8916) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* Create SSA interpreter arguments from scratch for each invocation (https://github.com/noir-lang/noir/pull/8762) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* **defunctionalize:** Create a placeholder function for first-class function calls with no variants (https://github.com/noir-lang/noir/pull/8697) ([4e3f550](https://github.com/AztecProtocol/aztec-packages/commit/4e3f550332c81006b8d3cdbeb646f4c567473f9d)) +* delay associated constants resolution (https://github.com/noir-lang/noir/pull/8744) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* deploy l1 contracts ([#14838](https://github.com/AztecProtocol/aztec-packages/issues/14838)) ([843d2e2](https://github.com/AztecProtocol/aztec-packages/commit/843d2e2f8fa28972560ffd921605e372bd85383a)) +* disallow casting numeric to bool (https://github.com/noir-lang/noir/pull/8703) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* disallow specifying associated items via generics (https://github.com/noir-lang/noir/pull/8756) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* do not expose the counter of l2toL1Message ([#14737](https://github.com/AztecProtocol/aztec-packages/issues/14737)) ([dc14f28](https://github.com/AztecProtocol/aztec-packages/commit/dc14f283f92755eefc0f5950457ef4bf43def8cd)) +* do not hoist control dependent cast (https://github.com/noir-lang/noir/pull/8886) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* Do not use stream ids as unique identifiers ([#15054](https://github.com/AztecProtocol/aztec-packages/issues/15054)) ([fa159c5](https://github.com/AztecProtocol/aztec-packages/commit/fa159c5d52a45358479f951a547fed37f141a6ed)) +* **docs:** Fix Update Docs for the release-please workflow ([#14834](https://github.com/AztecProtocol/aztec-packages/issues/14834)) ([03994b1](https://github.com/AztecProtocol/aztec-packages/commit/03994b1074bd42eb5451a5555e0e5c74e4f1af62)) +* **docs:** Run Update Docs release-please job ([#14730](https://github.com/AztecProtocol/aztec-packages/issues/14730)) ([7f1f25d](https://github.com/AztecProtocol/aztec-packages/commit/7f1f25d51bf4323ff3260a411cfa028bb8d2771c)) +* e2e boxes CI flake ([#14827](https://github.com/AztecProtocol/aztec-packages/issues/14827)) ([5532c0a](https://github.com/AztecProtocol/aztec-packages/commit/5532c0a96bd40b43f1adf7776caabc6a5822c000)) +* Error when re-assigning a mutable reference (https://github.com/noir-lang/noir/pull/8790) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* **expand:** show references to ModuleDefId recursing on parents (https://github.com/noir-lang/noir/pull/8977) ([4608046](https://github.com/AztecProtocol/aztec-packages/commit/4608046e462faf9ff3fbee265ba34dbecb5d0b31)) +* filter undefined out ([#14702](https://github.com/AztecProtocol/aztec-packages/issues/14702)) ([220070a](https://github.com/AztecProtocol/aztec-packages/commit/220070a286bb4212d4dbdf2426c0eceb0171fc10)) +* Finish sequencer flushing only on successful publish ([#14778](https://github.com/AztecProtocol/aztec-packages/issues/14778)) ([d91ddc2](https://github.com/AztecProtocol/aztec-packages/commit/d91ddc2ee1e6e71f4d23a118a019624775c2c1e8)) +* Fix comptime casts of negative integer to field (https://github.com/noir-lang/noir/pull/8696) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* Fix if/match tracking in last uses pass (https://github.com/noir-lang/noir/pull/8935) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* fix overflow mechanism and add robustness testing ([#14707](https://github.com/AztecProtocol/aztec-packages/issues/14707)) ([2bcb994](https://github.com/AztecProtocol/aztec-packages/commit/2bcb994d636bec06f862782bffad4bc0ae7d46d7)) +* fix test_pattern regex for boxes ([#14892](https://github.com/AztecProtocol/aztec-packages/issues/14892)) ([93c7f51](https://github.com/AztecProtocol/aztec-packages/commit/93c7f51c9bf7c1fff73a23a821235c1f694a1fdb)) +* Fixes the tracking of pending tx count ([#14792](https://github.com/AztecProtocol/aztec-packages/issues/14792)) ([84038f8](https://github.com/AztecProtocol/aztec-packages/commit/84038f81c601fb9eb66f54438740a2ff187fc566)) +* flake in L1 TX utils test ([#14688](https://github.com/AztecProtocol/aztec-packages/issues/14688)) ([35ff74e](https://github.com/AztecProtocol/aztec-packages/commit/35ff74e2738eed9c53d8f4762c4d8e857b6b3945)), closes [#13721](https://github.com/AztecProtocol/aztec-packages/issues/13721) +* flaky p2p integration tests ([#14794](https://github.com/AztecProtocol/aztec-packages/issues/14794)) ([5af2a5e](https://github.com/AztecProtocol/aztec-packages/commit/5af2a5ec5c862033dd59d31fbed9d3d8bbec7032)) +* Fork world-state from the correct block when building a new one ([#14763](https://github.com/AztecProtocol/aztec-packages/issues/14763)) ([7dee7fd](https://github.com/AztecProtocol/aztec-packages/commit/7dee7fdd080c8d030619ca37304f6438875edb62)) +* **formatter:** reset indetnation after group changed it (https://github.com/noir-lang/noir/pull/8966) ([4e3f550](https://github.com/AztecProtocol/aztec-packages/commit/4e3f550332c81006b8d3cdbeb646f4c567473f9d)) +* **fuzz:** Avoid negating `i8::MIN` into `i8::MAX+1` which won't compile (https://github.com/noir-lang/noir/pull/8972) ([4608046](https://github.com/AztecProtocol/aztec-packages/commit/4608046e462faf9ff3fbee265ba34dbecb5d0b31)) +* **fuzz:** Consider values returned from Brillig to ACIR as dynamic (https://github.com/noir-lang/noir/pull/8931) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **fuzz:** Do not take a mutable reference over immutable vars which contain a mutable ref (https://github.com/noir-lang/noir/pull/8971) ([4608046](https://github.com/AztecProtocol/aztec-packages/commit/4608046e462faf9ff3fbee265ba34dbecb5d0b31)) +* **fuzz:** Fix env var name in fuzzing workflow (https://github.com/noir-lang/noir/pull/8929) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **fuzz:** Use an inline block to circumvent negation with overflow (https://github.com/noir-lang/noir/pull/8911) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* handle error from sending tx properly ([#14906](https://github.com/AztecProtocol/aztec-packages/issues/14906)) ([b0d5545](https://github.com/AztecProtocol/aztec-packages/commit/b0d55451382d9c8937005e571b5aa4fc03655e9e)) +* Handle missed l1-to-l2 messages in archiver ([#15116](https://github.com/AztecProtocol/aztec-packages/issues/15116)) ([4b0e4dc](https://github.com/AztecProtocol/aztec-packages/commit/4b0e4dc09fa55f0bc808fa0c7c3e95e8fa997069)) +* handle return_data in the interpreter SSA CLI (https://github.com/noir-lang/noir/pull/8914) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* Handle spot eviction from host, not container. ([#15162](https://github.com/AztecProtocol/aztec-packages/issues/15162)) ([be1fa8a](https://github.com/AztecProtocol/aztec-packages/commit/be1fa8aacb33d330cf496352bb1446439e34c51a)) +* handle unconditional break during SSA codegen in all cases (https://github.com/noir-lang/noir/pull/8855) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* handling of no block proposer ([#14837](https://github.com/AztecProtocol/aztec-packages/issues/14837)) ([d7a0a7e](https://github.com/AztecProtocol/aztec-packages/commit/d7a0a7e4e223f599afc212f096581203f0d6cca0)) +* hash only 20 bytes of the l1 recipient for the l2 to l1 message ([#14810](https://github.com/AztecProtocol/aztec-packages/issues/14810)) ([313c5b1](https://github.com/AztecProtocol/aztec-packages/commit/313c5b177607f1e9031e057cf5d9b74235cbe602)) +* impl of Serialize and Deserialize for BoundedVec ([#14844](https://github.com/AztecProtocol/aztec-packages/issues/14844)) ([0e87ca2](https://github.com/AztecProtocol/aztec-packages/commit/0e87ca291c46f48e3b8d95e1ad86c7e4f98d5c8b)) +* Increase gas limit for propose and vote bundle ([#15135](https://github.com/AztecProtocol/aztec-packages/issues/15135)) ([a37c3cd](https://github.com/AztecProtocol/aztec-packages/commit/a37c3cdcd562df234bc44cc52cdce68d105ff2f2)) +* increment reference counts when deduplicating calls which return arrays (https://github.com/noir-lang/noir/pull/8757) ([4608046](https://github.com/AztecProtocol/aztec-packages/commit/4608046e462faf9ff3fbee265ba34dbecb5d0b31)) +* indexing arrays with non-u32 is now an error (https://github.com/noir-lang/noir/pull/8804) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* Inline global arrays with functions at their call site (https://github.com/noir-lang/noir/pull/8905) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **interpreter:** Do not overflow on signed checked ops (https://github.com/noir-lang/noir/pull/8806) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* **interpreter:** Return -1 for negative shr signed overflow or 0 for positive shr signed overflow (https://github.com/noir-lang/noir/pull/8828) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* introduce batching limits ([#15039](https://github.com/AztecProtocol/aztec-packages/issues/15039)) ([7339c26](https://github.com/AztecProtocol/aztec-packages/commit/7339c264ad706420ee9babb0dd1b6ed793e97402)) +* **licm:** Account for negative bounds when checking whether a loop executes (https://github.com/noir-lang/noir/pull/8889) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **LICM:** Consider negative loop bounds before hoisting `div ` (https://github.com/noir-lang/noir/pull/8986) ([4608046](https://github.com/AztecProtocol/aztec-packages/commit/4608046e462faf9ff3fbee265ba34dbecb5d0b31)) +* **licm:** Preserve semantic ordering of side-effectual instructions when hoisting (https://github.com/noir-lang/noir/pull/8724) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* Limit RPC request batch size ([#14738](https://github.com/AztecProtocol/aztec-packages/issues/14738)) ([d96a11a](https://github.com/AztecProtocol/aztec-packages/commit/d96a11a0fbfe3ef786a4aa9b6c79bbaba142c75f)) +* Limit the number of function names stored in archiver ([#14989](https://github.com/AztecProtocol/aztec-packages/issues/14989)) ([a907d3a](https://github.com/AztecProtocol/aztec-packages/commit/a907d3a9f40aa473deec5c77c658a80089184fa8)), closes [#14257](https://github.com/AztecProtocol/aztec-packages/issues/14257) +* **LSP:** suggest generic type methods (https://github.com/noir-lang/noir/pull/8948) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* Make fee per gas uint128 across the protocol ([#14796](https://github.com/AztecProtocol/aztec-packages/issues/14796)) ([e3a801d](https://github.com/AztecProtocol/aztec-packages/commit/e3a801d059d2481e6501e3e7ff5c8750c9aaa5a1)) +* make reward distributor updatable on registry ([#15167](https://github.com/AztecProtocol/aztec-packages/issues/15167)) ([08d3f55](https://github.com/AztecProtocol/aztec-packages/commit/08d3f55874836a573c4756b0cc7279160b0813a3)), closes [#15165](https://github.com/AztecProtocol/aztec-packages/issues/15165) +* make TXE fast again ([#15157](https://github.com/AztecProtocol/aztec-packages/issues/15157)) ([df137f9](https://github.com/AztecProtocol/aztec-packages/commit/df137f9440a970af2eb6e2ee94c48bf3bf47e207)) +* Match against all Value recursive types when checking for a function/closure in a global (https://github.com/noir-lang/noir/pull/8967) ([4e3f550](https://github.com/AztecProtocol/aztec-packages/commit/4e3f550332c81006b8d3cdbeb646f4c567473f9d)) +* **mem2reg:** Keep last store for a used nested array (https://github.com/noir-lang/noir/pull/8917) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **mem2reg:** Keep last store for reference in array used only in an array get (https://github.com/noir-lang/noir/pull/8877) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **mem2reg:** Keep last stores used in array returned from a function (https://github.com/noir-lang/noir/pull/8801) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* **mem2reg:** Keep last stores used in MakeArray (https://github.com/noir-lang/noir/pull/8743) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* **mem2reg:** Keep store when any aliased reference is kept (https://github.com/noir-lang/noir/pull/8960) ([4e3f550](https://github.com/AztecProtocol/aztec-packages/commit/4e3f550332c81006b8d3cdbeb646f4c567473f9d)) +* mempool stats ([#14729](https://github.com/AztecProtocol/aztec-packages/issues/14729)) ([f4baa2e](https://github.com/AztecProtocol/aztec-packages/commit/f4baa2edbabe0e8d6ad78d33a85933f05a3cf91c)) +* mempool stats ([#14862](https://github.com/AztecProtocol/aztec-packages/issues/14862)) ([fe573f0](https://github.com/AztecProtocol/aztec-packages/commit/fe573f0428aa15ec9b11c09653f05b985fc2df6c)) +* Merge `replacement_type` and `is_function_type` in `defunctionalization` (https://github.com/noir-lang/noir/pull/8784) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* more SSA interpreter fixes (https://github.com/noir-lang/noir/pull/8904) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* network deployments for multi-validator nodes ([#14835](https://github.com/AztecProtocol/aztec-packages/issues/14835)) ([f5bafa9](https://github.com/AztecProtocol/aztec-packages/commit/f5bafa93fe11ace056be5e0fcfd79ac020b0bdaf)) +* nightlies ([#14675](https://github.com/AztecProtocol/aztec-packages/issues/14675)) ([bb2a0c7](https://github.com/AztecProtocol/aztec-packages/commit/bb2a0c7d946660fa94dca9731ed6eaf9e7793824)) +* no slasher in nodes with no private key ([#14836](https://github.com/AztecProtocol/aztec-packages/issues/14836)) ([a5f062f](https://github.com/AztecProtocol/aztec-packages/commit/a5f062f6ffcfd57d00539fe770e0188d82effa3e)) +* **noir-projects:** segregate vks by contract hash (speculative) ([#14613](https://github.com/AztecProtocol/aztec-packages/issues/14613)) ([4f4fae4](https://github.com/AztecProtocol/aztec-packages/commit/4f4fae4dd76d17de272a66770bdbb7bb03612d64)) +* **noirc_evaluator:** U128 Binary::And simplification (https://github.com/noir-lang/noir/pull/8940) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* Only broadcast new txs ([#14717](https://github.com/AztecProtocol/aztec-packages/issues/14717)) ([b6225f6](https://github.com/AztecProtocol/aztec-packages/commit/b6225f67a988d44f98413536a09cc3bc202bfe75)) +* oopsie on pr_link ([fa44977](https://github.com/AztecProtocol/aztec-packages/commit/fa449771f161568004ab72e3bb8aeff7d88131f4)) +* p2p qol fixes ([#14900](https://github.com/AztecProtocol/aztec-packages/issues/14900)) ([4800d08](https://github.com/AztecProtocol/aztec-packages/commit/4800d08570523bc1b2a9e8ec0dfb09e326f4689a)) +* **parser:** let `as` have a lower precedence (https://github.com/noir-lang/noir/pull/8956) ([4e3f550](https://github.com/AztecProtocol/aztec-packages/commit/4e3f550332c81006b8d3cdbeb646f4c567473f9d)) +* pass --sandbox options to container ([#14274](https://github.com/AztecProtocol/aztec-packages/issues/14274)) ([e18950b](https://github.com/AztecProtocol/aztec-packages/commit/e18950b367d026907bcbef5fc7f30136fbe95c59)) +* pass CommitmentKey by value. ([#15009](https://github.com/AztecProtocol/aztec-packages/issues/15009)) ([47c7495](https://github.com/AztecProtocol/aztec-packages/commit/47c749527b1c3cce3edd9defedc46e89ea00c69e)) +* pass proposer addr from sequencer to validator when proposing ([#14686](https://github.com/AztecProtocol/aztec-packages/issues/14686)) ([a2cde85](https://github.com/AztecProtocol/aztec-packages/commit/a2cde8566e35d1ab4d65c7b3eeb2a8c48cb3c9fc)) +* pass pull/restart policies to docker ([#14950](https://github.com/AztecProtocol/aztec-packages/issues/14950)) ([8aa7a35](https://github.com/AztecProtocol/aztec-packages/commit/8aa7a355b620f789960ca13bd8f455f000f48043)) +* pass VerifierCommitmentKey by value ([#15101](https://github.com/AztecProtocol/aztec-packages/issues/15101)) ([a0a8617](https://github.com/AztecProtocol/aztec-packages/commit/a0a8617413ca6f4fba2ad190ce73dab59bd75a45)) +* prerelease staging ([54fc27a](https://github.com/AztecProtocol/aztec-packages/commit/54fc27adbce52a5aa04aeae6eec34ae4b1044f92)) +* preserve functions which are used in `array_set` instructions (https://github.com/noir-lang/noir/pull/8891) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* profiling in playground ([#14719](https://github.com/AztecProtocol/aztec-packages/issues/14719)) ([b87ac33](https://github.com/AztecProtocol/aztec-packages/commit/b87ac33788e309039fb3a1c4a5259a60df4ca102)) +* proper voteWithSig test + update event ([#15125](https://github.com/AztecProtocol/aztec-packages/issues/15125)) ([3aafa23](https://github.com/AztecProtocol/aztec-packages/commit/3aafa23f28c401f1c88d60325230f1ce801d72a6)) +* Receipt transaction hash mismatch ([#14870](https://github.com/AztecProtocol/aztec-packages/issues/14870)) ([254faae](https://github.com/AztecProtocol/aztec-packages/commit/254faae7d330e160b14c6eaaa099dc92d4a9cac7)) +* redo-typo-pr ([#14867](https://github.com/AztecProtocol/aztec-packages/issues/14867)) ([84794f3](https://github.com/AztecProtocol/aztec-packages/commit/84794f3bf49ce6c8b089ffad4e6ccc91766dc201)) +* reenable protocol vk caching. fix bad l1-contracts cache ([#15119](https://github.com/AztecProtocol/aztec-packages/issues/15119)) ([e56623c](https://github.com/AztecProtocol/aztec-packages/commit/e56623c3f6400dc164642e49255b211d6fb03ac6)) +* reenable vk hash ([4daeccc](https://github.com/AztecProtocol/aztec-packages/commit/4daeccc4dab245cefb82235b08b6d4fd3b09160e)) +* remove cmake from profile_tracy_capture_mainframe_view_local.sh ([#15163](https://github.com/AztecProtocol/aztec-packages/issues/15163)) ([3c9c343](https://github.com/AztecProtocol/aztec-packages/commit/3c9c3432af5707445b58094692f04efc45600b5f)) +* remove pxe network handling ([#15025](https://github.com/AztecProtocol/aztec-packages/issues/15025)) ([d96baf1](https://github.com/AztecProtocol/aztec-packages/commit/d96baf1c44329e8b2e3a432ad803f702f5184a62)) +* requiring blake3 inputs to have less than 1024 bytes ([#14628](https://github.com/AztecProtocol/aztec-packages/issues/14628)) ([12be10e](https://github.com/AztecProtocol/aztec-packages/commit/12be10e304227dd8fbc648b50bbd3d36e6f7bd8b)) +* revert [#14810](https://github.com/AztecProtocol/aztec-packages/issues/14810) and [#14721](https://github.com/AztecProtocol/aztec-packages/issues/14721). Simpler civc vk generation. ([#14775](https://github.com/AztecProtocol/aztec-packages/issues/14775)) ([289a553](https://github.com/AztecProtocol/aztec-packages/commit/289a5533897bf234dc0942c86db13a8fade1009c)) +* revert if coinbase is zero ([#14928](https://github.com/AztecProtocol/aztec-packages/issues/14928)) ([0d487c1](https://github.com/AztecProtocol/aztec-packages/commit/0d487c1ca6a9f9b296a83346b559a1e4d72aed76)) +* right shift overflow to 0 (https://github.com/noir-lang/noir/pull/8772) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* Rollup invalid archive error ([#14785](https://github.com/AztecProtocol/aztec-packages/issues/14785)) ([2145465](https://github.com/AztecProtocol/aztec-packages/commit/21454652a2cac99680d476e4a81794a219c58c2a)) +* run shout tests on staging ([#15188](https://github.com/AztecProtocol/aztec-packages/issues/15188)) ([f0619dd](https://github.com/AztecProtocol/aztec-packages/commit/f0619dd82429a5973f3e1da8d7eb0877264908e3)) +* scream test only master ([ff5fb4c](https://github.com/AztecProtocol/aztec-packages/commit/ff5fb4c890066b04492da15878e9211fad013347)) +* seeded randomness for pi tests ([#14681](https://github.com/AztecProtocol/aztec-packages/issues/14681)) ([d84cfa1](https://github.com/AztecProtocol/aztec-packages/commit/d84cfa10eccf599ef3439a33a57cce14da77659f)) +* signed right shift overflows to 0 or -1 (https://github.com/noir-lang/noir/pull/8805) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* Sleep on slasher client stop ([#14903](https://github.com/AztecProtocol/aztec-packages/issues/14903)) ([7267995](https://github.com/AztecProtocol/aztec-packages/commit/726799575b22b50cad6c8101147914c8b3ab2ba9)) +* solidity crypto libs ([#15080](https://github.com/AztecProtocol/aztec-packages/issues/15080)) ([47bd934](https://github.com/AztecProtocol/aztec-packages/commit/47bd93499e6606131b6ececa3536632cf5e84409)) +* specify generic argument N of several structs ([#14967](https://github.com/AztecProtocol/aztec-packages/issues/14967)) ([f880f14](https://github.com/AztecProtocol/aztec-packages/commit/f880f14c946fb8464186d4178c5d49e391041988)) +* **ssa:** Signed cast simplification (https://github.com/noir-lang/noir/pull/8862) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **ssa:** Swap Brillig index shift and DIE in minimal pipeline (https://github.com/noir-lang/noir/pull/8946) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **ssa:** Validate field to integer cast (https://github.com/noir-lang/noir/pull/8799) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* stop inserting instructions after break and continue (https://github.com/noir-lang/noir/pull/8712) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* support recursive call to main function in SSA parser (https://github.com/noir-lang/noir/pull/8760) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* Temp disable vk cache to see if issues resolve. ([#14908](https://github.com/AztecProtocol/aztec-packages/issues/14908)) ([2ea9a78](https://github.com/AztecProtocol/aztec-packages/commit/2ea9a78987e84c03b73a729ba53592635c739e04)) +* **test:** bbup retry on curl fail ([#14865](https://github.com/AztecProtocol/aztec-packages/issues/14865)) ([68fe4c1](https://github.com/AztecProtocol/aztec-packages/commit/68fe4c12505bc00f98458cd6a386b4db1f357f7c)) +* Thread errors through remove_if_else instead of panicing when the value merger finds reference values (https://github.com/noir-lang/noir/pull/8783) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* track passed in right list ([87a83c2](https://github.com/AztecProtocol/aztec-packages/commit/87a83c23acd60d8ffb5e4088d42414baf36df381)) +* type unification tests, and try moving constants to the other side (https://github.com/noir-lang/noir/pull/8807) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* unify infix expressions by isolating unbound type variables (https://github.com/noir-lang/noir/pull/8796) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* update external lib commit tdd.nr (https://github.com/noir-lang/noir/pull/8823) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* update testnet compat ([fe25e81](https://github.com/AztecProtocol/aztec-packages/commit/fe25e8151d865a744220512c782713e46482165a)) +* use bc for balances scripts ([#14262](https://github.com/AztecProtocol/aztec-packages/issues/14262)) ([0001a9c](https://github.com/AztecProtocol/aztec-packages/commit/0001a9cca3466f6b60d2004221a14a5e7cef1a0e)) +* use value predicated by range checks (https://github.com/noir-lang/noir/pull/8778) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* validate l1toL2Message tree snapshot ([#14721](https://github.com/AztecProtocol/aztec-packages/issues/14721)) ([1f715bf](https://github.com/AztecProtocol/aztec-packages/commit/1f715bf43bc76dd77da10d05b90c4b2f296647d7)) +* when macro parse error happens, discard warnings; also preserve unquoted token locations (https://github.com/noir-lang/noir/pull/8944) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* Yarn lock ([#14846](https://github.com/AztecProtocol/aztec-packages/issues/14846)) ([6f5bcee](https://github.com/AztecProtocol/aztec-packages/commit/6f5bceedb1e6ff07163cd89db3901029b131a3cc)) + + +### Miscellaneous + +* abandon custom solhint ([#14927](https://github.com/AztecProtocol/aztec-packages/issues/14927)) ([0468ab2](https://github.com/AztecProtocol/aztec-packages/commit/0468ab2582dd9875f5ada4cc87085956c779a625)) +* add a failing test for [#8780](https://github.com/AztecProtocol/aztec-packages/issues/8780) (https://github.com/noir-lang/noir/pull/8785) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* add a regression test for [#8727](https://github.com/AztecProtocol/aztec-packages/issues/8727) (https://github.com/noir-lang/noir/pull/8851) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* Add boxes flake ([#14978](https://github.com/AztecProtocol/aztec-packages/issues/14978)) ([e553a08](https://github.com/AztecProtocol/aztec-packages/commit/e553a08e2cd4145c7343d82e3d0d70364d2b07d0)) +* add browser flakes ([#14887](https://github.com/AztecProtocol/aztec-packages/issues/14887)) ([c3adc14](https://github.com/AztecProtocol/aztec-packages/commit/c3adc14e880e2664f50cfdaed7eb88199cad1abe)) +* Add flake for nonce too high on eth cheat codes ([#14897](https://github.com/AztecProtocol/aztec-packages/issues/14897)) ([1fb2f42](https://github.com/AztecProtocol/aztec-packages/commit/1fb2f42cbfc843133c47ffc5f1300be2d5974d41)) +* Add p2p flakes ([#14772](https://github.com/AztecProtocol/aztec-packages/issues/14772)) ([327123d](https://github.com/AztecProtocol/aztec-packages/commit/327123dccda70f0847e712ac6805944e2d59931f)) +* Add public-payments flake ([#14774](https://github.com/AztecProtocol/aztec-packages/issues/14774)) ([4cca814](https://github.com/AztecProtocol/aztec-packages/commit/4cca8147782d31458e394a7e4f9129c5595c3cc1)) +* add queue to staking asset handler ([#14780](https://github.com/AztecProtocol/aztec-packages/issues/14780)) ([406a372](https://github.com/AztecProtocol/aztec-packages/commit/406a372af6ce0c5da10729e0c49b716b8d6a3e9d)) +* add return value to every execution_success program that produces an output (https://github.com/noir-lang/noir/pull/8882) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* Add sumcheck failed in amm_flow as flake ([#14909](https://github.com/AztecProtocol/aztec-packages/issues/14909)) ([4a474a8](https://github.com/AztecProtocol/aztec-packages/commit/4a474a834d8b99e22a28ad809d390856e29b0b2a)) +* add time to build-images ([#14866](https://github.com/AztecProtocol/aztec-packages/issues/14866)) ([87c4738](https://github.com/AztecProtocol/aztec-packages/commit/87c47383386b85776865beca65dc332bfe028fd7)) +* Add USE_CIRCUITS_CACHE flag to bootstrap ([#14965](https://github.com/AztecProtocol/aztec-packages/issues/14965)) ([5164099](https://github.com/AztecProtocol/aztec-packages/commit/5164099598988738f03178be2ff4c09dccfc2d5f)) +* Add various regression tests for mutable references (https://github.com/noir-lang/noir/pull/8915) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* Additonal logging for transaction retrieval ([#14823](https://github.com/AztecProtocol/aztec-packages/issues/14823)) ([0f8c04c](https://github.com/AztecProtocol/aztec-packages/commit/0f8c04c4ac39096e3652958a6b23e2c883225a11)) +* address misc coverage ([#15126](https://github.com/AztecProtocol/aztec-packages/issues/15126)) ([92ef6e2](https://github.com/AztecProtocol/aztec-packages/commit/92ef6e28f2b718bb1009392a60b8311979c782d2)) +* address various clippy issues (https://github.com/noir-lang/noir/pull/8942) ([4e3f550](https://github.com/AztecProtocol/aztec-packages/commit/4e3f550332c81006b8d3cdbeb646f4c567473f9d)) +* Adjust the frequency of 'for' statements in ACIR fuzz generation (https://github.com/noir-lang/noir/pull/8788) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* alpha testnet tf ([#14973](https://github.com/AztecProtocol/aztec-packages/issues/14973)) ([9b8ea5a](https://github.com/AztecProtocol/aztec-packages/commit/9b8ea5acda76687342ddb62595e293281661f029)) +* assume the withdrawer is not zero ([#14787](https://github.com/AztecProtocol/aztec-packages/issues/14787)) ([a58dccd](https://github.com/AztecProtocol/aztec-packages/commit/a58dccd02540c0f9dc9eb996d93bef912514a314)) +* avm benchmark of nested call with large array as calldata ([#14896](https://github.com/AztecProtocol/aztec-packages/issues/14896)) ([befc8c4](https://github.com/AztecProtocol/aztec-packages/commit/befc8c4c9ab253911288e1ef4bdd56c5ae8eeb9b)) +* **avm:** Decrease relation degree in ecc ([#14840](https://github.com/AztecProtocol/aztec-packages/issues/14840)) ([3bbebfa](https://github.com/AztecProtocol/aztec-packages/commit/3bbebfabde8d4052a9957cd47ee0b43f72bb6a00)) +* **avm:** deduplicate bytecode hints ([#15065](https://github.com/AztecProtocol/aztec-packages/issues/15065)) ([97080f1](https://github.com/AztecProtocol/aztec-packages/commit/97080f178c0a3e244c64b3c17bf255bed63fc3cf)) +* **avm:** more execution temporality work ([#14816](https://github.com/AztecProtocol/aztec-packages/issues/14816)) ([b809497](https://github.com/AztecProtocol/aztec-packages/commit/b80949775e5c27908a6055ad24592f7d98bdc45a)) +* **avm:** proper testing lookups ([#15185](https://github.com/AztecProtocol/aztec-packages/issues/15185)) ([64d1ec3](https://github.com/AztecProtocol/aztec-packages/commit/64d1ec347b1cea9fa1b04ff6fc2984f8ee073b2b)) +* **avm:** reorder pil ([#15078](https://github.com/AztecProtocol/aztec-packages/issues/15078)) ([58b0677](https://github.com/AztecProtocol/aztec-packages/commit/58b067708e8ff927bf26b2994203134853a7eb71)) +* **avm:** update addressing gas ([#15018](https://github.com/AztecProtocol/aztec-packages/issues/15018)) ([a45107e](https://github.com/AztecProtocol/aztec-packages/commit/a45107e7f95b675cb2768b6bcb06483b511141f4)) +* **avm:** use column values in permutations ([#15114](https://github.com/AztecProtocol/aztec-packages/issues/15114)) ([433ba62](https://github.com/AztecProtocol/aztec-packages/commit/433ba6218aed45169029991a493e4232b30b8ec6)) +* avoid casting signed integers to Field directly ([#14855](https://github.com/AztecProtocol/aztec-packages/issues/14855)) ([3100b19](https://github.com/AztecProtocol/aztec-packages/commit/3100b19c3fe2da8ed35ab214b189483ee5900b12)) +* await validating before withdrawing ([#14788](https://github.com/AztecProtocol/aztec-packages/issues/14788)) ([5316644](https://github.com/AztecProtocol/aztec-packages/commit/5316644c4f7da372847d1639cab23da394fce7fe)) +* await validators in set with some leeway ([#14789](https://github.com/AztecProtocol/aztec-packages/issues/14789)) ([63897fd](https://github.com/AztecProtocol/aztec-packages/commit/63897fd93846f6f9104b7d30300ee5a7f604c90b)) +* backport script ([#14764](https://github.com/AztecProtocol/aztec-packages/issues/14764)) ([3e9b871](https://github.com/AztecProtocol/aztec-packages/commit/3e9b8715e933f3cda4624cd766a3f8061950d9cc)) +* backport test ([#14773](https://github.com/AztecProtocol/aztec-packages/issues/14773)) ([a48a5a5](https://github.com/AztecProtocol/aztec-packages/commit/a48a5a51a15164c7187ee54c3c9c1a9fb379766d)) +* Ban dynamic array indices with reference elements (https://github.com/noir-lang/noir/pull/8888) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **Barretenberg:** rollback fix in shplemini ([#13526](https://github.com/AztecProtocol/aztec-packages/issues/13526)) ([ed6ef6b](https://github.com/AztecProtocol/aztec-packages/commit/ed6ef6b905de98877d19d34a6697e7ce5e105e36)) +* **bb-pilcom:** support aliases in skippable ([#15073](https://github.com/AztecProtocol/aztec-packages/issues/15073)) ([fa4d5ef](https://github.com/AztecProtocol/aztec-packages/commit/fa4d5ef48c8de52059b5658bbd42e4514386f7db)) +* **bb:** use shared cache for pippenger_runtime_state ([#14753](https://github.com/AztecProtocol/aztec-packages/issues/14753)) ([224c76a](https://github.com/AztecProtocol/aztec-packages/commit/224c76afe526ecb9ceaab8e2a2c12bccc0ec85b1)) +* benchmark IPA MSM ([#15083](https://github.com/AztecProtocol/aztec-packages/issues/15083)) ([96a4262](https://github.com/AztecProtocol/aztec-packages/commit/96a426245731b3f48fcb6dd5e2a683c858706602)) +* Benchmarks for LMDB stores ([#15076](https://github.com/AztecProtocol/aztec-packages/issues/15076)) ([ee3655e](https://github.com/AztecProtocol/aztec-packages/commit/ee3655e025191bc85c5b3d3122eeedb44e38ffb2)) +* Benchmarks for real world IVC proof verification ([#15129](https://github.com/AztecProtocol/aztec-packages/issues/15129)) ([e050b78](https://github.com/AztecProtocol/aztec-packages/commit/e050b787f6dead84c4ef7e5bdb8dcc8cf81d5f65)) +* Benchmarks for transaction compression ([#15059](https://github.com/AztecProtocol/aztec-packages/issues/15059)) ([eeab628](https://github.com/AztecProtocol/aztec-packages/commit/eeab628a4e44477cb6796c5a461ee0c0bb332d75)) +* bigfield audit + documentation: part 1 ([#15093](https://github.com/AztecProtocol/aztec-packages/issues/15093)) ([92d685c](https://github.com/AztecProtocol/aztec-packages/commit/92d685c3a802eb31897b6f13d28b9ee428c73744)) +* bump dependencies (https://github.com/noir-lang/noir/pull/8838) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* bump external pinned commits (https://github.com/noir-lang/noir/pull/8747) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* bump external pinned commits (https://github.com/noir-lang/noir/pull/8834) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* bump external pinned commits (https://github.com/noir-lang/noir/pull/8920) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* bump some JS packages (https://github.com/noir-lang/noir/pull/8771) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* bump version ([#14691](https://github.com/AztecProtocol/aztec-packages/issues/14691)) ([d9232e9](https://github.com/AztecProtocol/aztec-packages/commit/d9232e90533ca613082c575dedd6bf752ff28c96)) +* Casting ACIR vs Brillig regression tests (https://github.com/noir-lang/noir/pull/8903) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* Catch exceptions during test teardown ([#14861](https://github.com/AztecProtocol/aztec-packages/issues/14861)) ([c21d48b](https://github.com/AztecProtocol/aztec-packages/commit/c21d48ba5c6e4fd6f5f09486988752e5c2fffd32)) +* change spelling from offence to offense ([#14786](https://github.com/AztecProtocol/aztec-packages/issues/14786)) ([71638d8](https://github.com/AztecProtocol/aztec-packages/commit/71638d819a9a5436734b948ff379d70ff6068fdb)) +* **ci:** `cargo clippy` CI script to save time (https://github.com/noir-lang/noir/pull/8787) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* **ci:** action for fuzzing docker build ([#14860](https://github.com/AztecProtocol/aztec-packages/issues/14860)) ([761f539](https://github.com/AztecProtocol/aztec-packages/commit/761f5399355e30c3df13c5f8126f687355942093)) +* **ci:** avoid locally-poisoned cache ([#15216](https://github.com/AztecProtocol/aztec-packages/issues/15216)) ([6e2f55a](https://github.com/AztecProtocol/aztec-packages/commit/6e2f55a89973b55c8c3c210b7f3a692b4d3d6473)) +* **ci:** cancel stale workflows ([#14624](https://github.com/AztecProtocol/aztec-packages/issues/14624)) ([72adea8](https://github.com/AztecProtocol/aztec-packages/commit/72adea8c86d5156a24785e2813df662bf9157942)) +* **ci:** disable all fuzz tests in aztec CI ([#14776](https://github.com/AztecProtocol/aztec-packages/issues/14776)) ([d465c9a](https://github.com/AztecProtocol/aztec-packages/commit/d465c9a0ba17523f7c14d9874f23e72c9fbc2188)) +* **ci:** push in fuzzing-docker-build.yml ([#15199](https://github.com/AztecProtocol/aztec-packages/issues/15199)) ([5c56d41](https://github.com/AztecProtocol/aztec-packages/commit/5c56d41aea0a97d800fc842dbcab1ca0d285e360)) +* **ci:** rename fuzzing-docker-build ([#15181](https://github.com/AztecProtocol/aztec-packages/issues/15181)) ([c2e2757](https://github.com/AztecProtocol/aztec-packages/commit/c2e27576e66fad0caaa136e905b8439d70c05e37)) +* **ci:** shell-new takes command ([#14935](https://github.com/AztecProtocol/aztec-packages/issues/14935)) ([f757692](https://github.com/AztecProtocol/aztec-packages/commit/f75769214c6892fb5c000065b9ff9a9d422459d0)) +* clarified nonce naming ([#14727](https://github.com/AztecProtocol/aztec-packages/issues/14727)) ([168936b](https://github.com/AztecProtocol/aztec-packages/commit/168936b62b2d09ab1c9489d0aa94aaf2f6321811)) +* clean up slasher config ([#14684](https://github.com/AztecProtocol/aztec-packages/issues/14684)) ([059dcae](https://github.com/AztecProtocol/aztec-packages/commit/059dcaeeb3e0e504590f5b82b48b6a8304e583b2)), closes [#14425](https://github.com/AztecProtocol/aztec-packages/issues/14425) +* compute sorted steps in translator only once ([#15000](https://github.com/AztecProtocol/aztec-packages/issues/15000)) ([314d762](https://github.com/AztecProtocol/aztec-packages/commit/314d7622c6bacc8333ae49fe9583967cab78349b)) +* disable AVM build on ARM64 ([#14784](https://github.com/AztecProtocol/aztec-packages/issues/14784)) ([86fa846](https://github.com/AztecProtocol/aztec-packages/commit/86fa8463f228a7886c7378d9b59640287d4460aa)) +* disable noncritical workflow steps which fail on external prs (https://github.com/noir-lang/noir/pull/8939) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* Disable status handshake on feature flag ([#15040](https://github.com/AztecProtocol/aztec-packages/issues/15040)) ([00cd67e](https://github.com/AztecProtocol/aztec-packages/commit/00cd67e98e76d3202a0219baf301d59ef4211872)) +* Do not mutate config object when starting aztec node ([#14981](https://github.com/AztecProtocol/aztec-packages/issues/14981)) ([fe99728](https://github.com/AztecProtocol/aztec-packages/commit/fe9972873ecbf165d5caaaa2a10c61db0d7189c6)) +* Do not pass trace into sha256 constructor ([#14850](https://github.com/AztecProtocol/aztec-packages/issues/14850)) ([729dedb](https://github.com/AztecProtocol/aztec-packages/commit/729dedbb4e39724c6937f9abe12624801c04ff70)) +* do not use dynamic types in interfaces ([#14203](https://github.com/AztecProtocol/aztec-packages/issues/14203)) ([403b1a8](https://github.com/AztecProtocol/aztec-packages/commit/403b1a8fce35fc61d9faa13d36705231c3ea0eee)) +* **docs:** add 0.87.9 from master to next ([#15193](https://github.com/AztecProtocol/aztec-packages/issues/15193)) ([cf10794](https://github.com/AztecProtocol/aztec-packages/commit/cf10794ebb7367fb6a26d1d402fd83c2aef3d888)) +* **docs:** Add experimental warning in Debugger docs (https://github.com/noir-lang/noir/pull/8824) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* **docs:** Add links to awesome-noir in sidebar (https://github.com/noir-lang/noir/pull/8854) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* **docs:** Copy attribute docs into versioned docs (https://github.com/noir-lang/noir/pull/8777) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* **docs:** Reorder tooling docs (https://github.com/noir-lang/noir/pull/8742) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* **docs:** Update noirjs app page to use to beta.6 (https://github.com/noir-lang/noir/pull/8853) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* **docs:** Updates docs from master ([#15013](https://github.com/AztecProtocol/aztec-packages/issues/15013)) ([4e03f15](https://github.com/AztecProtocol/aztec-packages/commit/4e03f15ca8099f25d50eddd19c4914764b84fe7b)) +* document `allow(dead_code)` and reorganize attributes (https://github.com/noir-lang/noir/pull/8766) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* Don't publish txs with proposals ([#15064](https://github.com/AztecProtocol/aztec-packages/issues/15064)) ([4110b08](https://github.com/AztecProtocol/aztec-packages/commit/4110b08ca823c6af4399e58e54e0483d6e3459ed)) +* dont enable manifest in places where it could lead to fragmentation ([#14614](https://github.com/AztecProtocol/aztec-packages/issues/14614)) ([ae79e3e](https://github.com/AztecProtocol/aztec-packages/commit/ae79e3e0778efb8be2c933c1170eb9502189c9bd)) +* enable nextnet deployments ([#14869](https://github.com/AztecProtocol/aztec-packages/issues/14869)) ([7158c95](https://github.com/AztecProtocol/aztec-packages/commit/7158c95d3299e6556c7ddec5f4248c365c71b6c5)) +* epoch proof submission window ([#15109](https://github.com/AztecProtocol/aztec-packages/issues/15109)) ([3c671e0](https://github.com/AztecProtocol/aztec-packages/commit/3c671e0822075208f705135771771f5a47e9652e)) +* Extra logging for p2p integration test ([#14968](https://github.com/AztecProtocol/aztec-packages/issues/14968)) ([0894618](https://github.com/AztecProtocol/aztec-packages/commit/0894618ded24dc2deab9e92e1888aa081f5f585e)) +* fancier coverage command ([#14987](https://github.com/AztecProtocol/aztec-packages/issues/14987)) ([2b9c409](https://github.com/AztecProtocol/aztec-packages/commit/2b9c409698cf0f475a7a9f5884117c8ad2a4f79a)) +* field_t `pow` changes ([#14680](https://github.com/AztecProtocol/aztec-packages/issues/14680)) ([a48851a](https://github.com/AztecProtocol/aztec-packages/commit/a48851a8c6884f4b9615a154f8862a549722d25e)) +* fix jest peer deps ([#15096](https://github.com/AztecProtocol/aztec-packages/issues/15096)) ([215be54](https://github.com/AztecProtocol/aztec-packages/commit/215be54ba7d05741f58f591612a9e5454104b24d)) +* Flag all p2p `CodeError` as flakes ([#14830](https://github.com/AztecProtocol/aztec-packages/issues/14830)) ([7e4af2d](https://github.com/AztecProtocol/aztec-packages/commit/7e4af2ddffbe664d26024240800c1f11e8d6d1bc)) +* Flag timeouts in l1 reorg test as flake ([#14831](https://github.com/AztecProtocol/aztec-packages/issues/14831)) ([3954762](https://github.com/AztecProtocol/aztec-packages/commit/3954762d9d3e23b59379cf53af0b350ff264238b)) +* full node block builder ([#14485](https://github.com/AztecProtocol/aztec-packages/issues/14485)) ([09735f2](https://github.com/AztecProtocol/aztec-packages/commit/09735f2a07bbb0ab2ee08e1f0e94ce2f05d27869)), closes [#14576](https://github.com/AztecProtocol/aztec-packages/issues/14576) +* **fuzz:** Add nightly fuzz workflow with 5 minute budget (https://github.com/noir-lang/noir/pull/8925) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **fuzz:** Compare print output on failed programs (https://github.com/noir-lang/noir/pull/8921) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **fuzz:** Consider `RangeCheckFailed` equivalent to `ConstantDoesNotFitType` if the value matches (https://github.com/noir-lang/noir/pull/8958) ([4e3f550](https://github.com/AztecProtocol/aztec-packages/commit/4e3f550332c81006b8d3cdbeb646f4c567473f9d)) +* **fuzz:** Display nightly fuzz status badge (https://github.com/noir-lang/noir/pull/8932) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **fuzz:** Enable SSA Interpreter fuzzing on all passes (https://github.com/noir-lang/noir/pull/8610) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* **fuzz:** Handle overflows in comptime fuzzing (https://github.com/noir-lang/noir/pull/8847) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* **fuzz:** Increase the number of deterministic test cases (https://github.com/noir-lang/noir/pull/8872) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **fuzz:** More metamorphic rules (https://github.com/noir-lang/noir/pull/8857) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **fuzz:** Remove unreachable functions in the AST fuzzer (https://github.com/noir-lang/noir/pull/8782) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* **fuzz:** Run fuzzing deterministically on CI (https://github.com/noir-lang/noir/pull/8868) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* **fuzz:** Tool to minimize Noir programs with `cvise` (https://github.com/noir-lang/noir/pull/8789) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* **fuzz:** Use `nextest` to run nightly fuzz test (https://github.com/noir-lang/noir/pull/8962) ([4e3f550](https://github.com/AztecProtocol/aztec-packages/commit/4e3f550332c81006b8d3cdbeb646f4c567473f9d)) +* Goblin is responsible for the merge ([#14655](https://github.com/AztecProtocol/aztec-packages/issues/14655)) ([348941e](https://github.com/AztecProtocol/aztec-packages/commit/348941e0f3c97c203660bacd4a2a6f32b6d6f481)) +* Ignore whitelisted unhandled rejections in jest teardowns ([#14904](https://github.com/AztecProtocol/aztec-packages/issues/14904)) ([4e6a383](https://github.com/AztecProtocol/aztec-packages/commit/4e6a3831f22e0da1db672433c62a6b6d718c5c54)) +* import blockchain node engine ([#15186](https://github.com/AztecProtocol/aztec-packages/issues/15186)) ([107a625](https://github.com/AztecProtocol/aztec-packages/commit/107a625e4a5b124b87c02fa9ff2ae12b5778baa9)) +* Improve access to data required to extract Noir (https://github.com/noir-lang/noir/pull/8793) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* Incorrect clippy warning blocking PRs (https://github.com/noir-lang/noir/pull/8861) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* interpret all execution_success programs (https://github.com/noir-lang/noir/pull/8887) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* introduce staging branch ([#15156](https://github.com/AztecProtocol/aztec-packages/issues/15156)) ([7d1abcb](https://github.com/AztecProtocol/aztec-packages/commit/7d1abcb337009939ee47da8445b5fee80f39fb58)) +* let there be gse tests ([#15124](https://github.com/AztecProtocol/aztec-packages/issues/15124)) ([a1fcc6d](https://github.com/AztecProtocol/aztec-packages/commit/a1fcc6d25d2ef5242bc875b44f6b8c627357a0d1)) +* Limit IVC verification concurrency ([#14921](https://github.com/AztecProtocol/aztec-packages/issues/14921)) ([8cc8a56](https://github.com/AztecProtocol/aztec-packages/commit/8cc8a56892d36191eefb49254ebbe49ea99ef4b8)) +* Logging for rediscovery flake ([#14824](https://github.com/AztecProtocol/aztec-packages/issues/14824)) ([bcd1f44](https://github.com/AztecProtocol/aztec-packages/commit/bcd1f44eb5cf2b2e6744123d43bf8f39ece42465)) +* make block number a u32 everywhere ([#15004](https://github.com/AztecProtocol/aztec-packages/issues/15004)) ([106f897](https://github.com/AztecProtocol/aztec-packages/commit/106f897363bb5aee27ddd091b5be9641b011c5bb)) +* make space ([#15015](https://github.com/AztecProtocol/aztec-packages/issues/15015)) ([75d7928](https://github.com/AztecProtocol/aztec-packages/commit/75d792847d8434a0c504e7adf5c102a913065272)) +* Mark all of p2p client integration as flake ([#14790](https://github.com/AztecProtocol/aztec-packages/issues/14790)) ([06cf6fd](https://github.com/AztecProtocol/aztec-packages/commit/06cf6fd15721d03ede3794bff93ac4ed524e7264)) +* mark sha512 as non-critical (https://github.com/noir-lang/noir/pull/8776) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* **master:** release 0.87.7 ([#14683](https://github.com/AztecProtocol/aztec-packages/issues/14683)) ([42910d5](https://github.com/AztecProtocol/aztec-packages/commit/42910d57e2cd31daecb732a172cacf4d6c79d162)) +* merge conflicts ([#14722](https://github.com/AztecProtocol/aztec-packages/issues/14722)) ([8adab31](https://github.com/AztecProtocol/aztec-packages/commit/8adab31c13d6030d7891fdeaf6d9b61d4c3861c1)) +* Merge master into next ([0891627](https://github.com/AztecProtocol/aztec-packages/commit/0891627e2ba9aa5e0d1dc8393b0af7b940f3a7c5)) +* Merge master into next ([7ac6e8c](https://github.com/AztecProtocol/aztec-packages/commit/7ac6e8c4366703311da7a7b750c4c5d567fd226f)) +* Merge master into next ([edf9357](https://github.com/AztecProtocol/aztec-packages/commit/edf9357712a309d422622613ee09524d9bafd31f)) +* Merge master into next ([a983e31](https://github.com/AztecProtocol/aztec-packages/commit/a983e31056dbcdd9a040c363cac6ee63131e1357)) +* Merge master into next ([2d5884c](https://github.com/AztecProtocol/aztec-packages/commit/2d5884cf404d4479aeb7128f8f42e0c1686f264c)) +* Merge master into next ([635bf1f](https://github.com/AztecProtocol/aztec-packages/commit/635bf1f9965553a2d0a4e7edfc0765ecf30fd969)) +* merge master into next manually ([#14690](https://github.com/AztecProtocol/aztec-packages/issues/14690)) ([3f96248](https://github.com/AztecProtocol/aztec-packages/commit/3f96248b208dd01677eb9fd753b55cd2c0b3b8b6)) +* minor code quality issues (https://github.com/noir-lang/noir/pull/8961) ([4e3f550](https://github.com/AztecProtocol/aztec-packages/commit/4e3f550332c81006b8d3cdbeb646f4c567473f9d)) +* Monitor sequencer is behaving properly ([#14877](https://github.com/AztecProtocol/aztec-packages/issues/14877)) ([d3bba2d](https://github.com/AztecProtocol/aztec-packages/commit/d3bba2d69dbc070d51bcd50607354193573876ba)) +* more blobs pls ([#15046](https://github.com/AztecProtocol/aztec-packages/issues/15046)) ([b70544b](https://github.com/AztecProtocol/aztec-packages/commit/b70544b6d50e3ecb6598361f1990bd51745c7b55)) +* More flakes ([#14821](https://github.com/AztecProtocol/aztec-packages/issues/14821)) ([769a7d2](https://github.com/AztecProtocol/aztec-packages/commit/769a7d2adcdcb6de1af848a20ccfdf3279341c3f)) +* more vkey hashing tests, move flavor files ([#15121](https://github.com/AztecProtocol/aztec-packages/issues/15121)) ([cbd66b5](https://github.com/AztecProtocol/aztec-packages/commit/cbd66b52b44f3aad75bf6e52a67987ed206598aa)) +* move gov around ([#15041](https://github.com/AztecProtocol/aztec-packages/issues/15041)) ([3b44f82](https://github.com/AztecProtocol/aztec-packages/commit/3b44f825dcd3249b2c8a1105905fa3394aa7213d)) +* no row disabling in translator ([#14640](https://github.com/AztecProtocol/aztec-packages/issues/14640)) ([1932ab0](https://github.com/AztecProtocol/aztec-packages/commit/1932ab07c0016a5ec385807513724f290d3fc586)) +* offchain effects ([#15151](https://github.com/AztecProtocol/aztec-packages/issues/15151)) ([f29a038](https://github.com/AztecProtocol/aztec-packages/commit/f29a038c58fc1f3ad95ad05d18aea321f0f598b2)) +* only follow bindings on interface to `arithmetic` module (https://github.com/noir-lang/noir/pull/8822) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* Only validate txs taken from proposals ([#14757](https://github.com/AztecProtocol/aztec-packages/issues/14757)) ([7fa2424](https://github.com/AztecProtocol/aztec-packages/commit/7fa2424e12c9d2c03a4bff5ead8d172d3f816d6e)) +* ordering events by commitment index ([#14919](https://github.com/AztecProtocol/aztec-packages/issues/14919)) ([78b8ff0](https://github.com/AztecProtocol/aztec-packages/commit/78b8ff098c781b15b77cf1c93742c277d173e74c)) +* post blob batching cleanup ([#14637](https://github.com/AztecProtocol/aztec-packages/issues/14637)) ([d836aa4](https://github.com/AztecProtocol/aztec-packages/commit/d836aa4bde10a2379a91aeb3fa405d7a46328a0b)) +* Properly separate ci runs into master/next/releases. ([#14843](https://github.com/AztecProtocol/aztec-packages/issues/14843)) ([772047a](https://github.com/AztecProtocol/aztec-packages/commit/772047abbf3d8695a44c1696dc044d64193ef175)) +* Prune changelog older than ~1 year (<0.32.0) (https://github.com/noir-lang/noir/pull/8856) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* public telemetry tf fixes ([#15206](https://github.com/AztecProtocol/aztec-packages/issues/15206)) ([328bbab](https://github.com/AztecProtocol/aztec-packages/commit/328bbab47c7c4669218f45f56840c0c5959f16a8)) +* purge forwarder ([#15067](https://github.com/AztecProtocol/aztec-packages/issues/15067)) ([af80410](https://github.com/AztecProtocol/aztec-packages/commit/af8041048ee98abe05402607256df4b4cac51dd8)) +* purge of custom note delivery ([#14994](https://github.com/AztecProtocol/aztec-packages/issues/14994)) ([cf8be0f](https://github.com/AztecProtocol/aztec-packages/commit/cf8be0f9e81e248048560619de041e90d9d6990a)) +* purging unused functions from TestContract ([#14995](https://github.com/AztecProtocol/aztec-packages/issues/14995)) ([26c5a39](https://github.com/AztecProtocol/aztec-packages/commit/26c5a39fe03723d11540f721293d7aebd1f478d9)) +* re-enable skipped tests ([#15122](https://github.com/AztecProtocol/aztec-packages/issues/15122)) ([5d01731](https://github.com/AztecProtocol/aztec-packages/commit/5d017313171ed95ebc6dbf88c1dffe207497338e)) +* reapply [#14810](https://github.com/AztecProtocol/aztec-packages/issues/14810) [#14721](https://github.com/AztecProtocol/aztec-packages/issues/14721) ([#14875](https://github.com/AztecProtocol/aztec-packages/issues/14875)) ([32e0882](https://github.com/AztecProtocol/aztec-packages/commit/32e08827cb0cf7e125cfc45e64fa7ee4fc981338)) +* redo typo PR by jinjingroad ([#15180](https://github.com/AztecProtocol/aztec-packages/issues/15180)) ([5869652](https://github.com/AztecProtocol/aztec-packages/commit/58696523cc93fda453138a4f2c2dff72b21f0bfa)) +* redo typo PR by osrm (https://github.com/noir-lang/noir/pull/8840) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* reenable prover-full ([#14859](https://github.com/AztecProtocol/aztec-packages/issues/14859)) ([0e7678c](https://github.com/AztecProtocol/aztec-packages/commit/0e7678c54088b0f941345049aa515f5a6bd46f3c)) +* Release Noir(1.0.0-beta.7) (https://github.com/noir-lang/noir/pull/8520) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* remove cheatcodes. set verifier in constructor ([#14819](https://github.com/AztecProtocol/aztec-packages/issues/14819)) ([eac9b8f](https://github.com/AztecProtocol/aztec-packages/commit/eac9b8f9ad5c78618d0cdb07e5bebbdd829fd00b)) +* remove duplicate orchestrator test ([#14946](https://github.com/AztecProtocol/aztec-packages/issues/14946)) ([831bf13](https://github.com/AztecProtocol/aztec-packages/commit/831bf136802a7b187e5cef28c94ed02ea458a146)) +* Remove flakes from test patterns ([#14913](https://github.com/AztecProtocol/aztec-packages/issues/14913)) ([a0c0a14](https://github.com/AztecProtocol/aztec-packages/commit/a0c0a14af6a010dce5a210c30384cc52b37a2725)) +* remove noir-lang/noir_rsa from external libraries (https://github.com/noir-lang/noir/pull/8752) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* remove num_txs from content commitment ([#14947](https://github.com/AztecProtocol/aztec-packages/issues/14947)) ([3aa9c4e](https://github.com/AztecProtocol/aztec-packages/commit/3aa9c4e3b347055e2d9ea55936d7c416ae903709)) +* remove repeated occurences of computation of sorted steps for delta range constraint ([#14731](https://github.com/AztecProtocol/aztec-packages/issues/14731)) ([42d5984](https://github.com/AztecProtocol/aztec-packages/commit/42d5984235d2ffcaf5545e88e77eeb698e147416)) +* remove target version string ([#14714](https://github.com/AztecProtocol/aztec-packages/issues/14714)) ([3639bf8](https://github.com/AztecProtocol/aztec-packages/commit/3639bf899b3df54d8de1089a2f9f752b5a2ae74e)) +* remove time-based input from validateHeader ([#14847](https://github.com/AztecProtocol/aztec-packages/issues/14847)) ([df0c3e8](https://github.com/AztecProtocol/aztec-packages/commit/df0c3e8efd6211cf15eedf67d2a591cc54099ac8)) +* renaming living to zombie ([#15048](https://github.com/AztecProtocol/aztec-packages/issues/15048)) ([90e495a](https://github.com/AztecProtocol/aztec-packages/commit/90e495a77534c1d250949dbc053161959c9aadcc)) +* reorder merge verification ([#14963](https://github.com/AztecProtocol/aztec-packages/issues/14963)) ([9b80175](https://github.com/AztecProtocol/aztec-packages/commit/9b801754ba98479ee645488c29d2fda1eecbfd85)) +* Reqresp logging and non-punishable errors ([#14872](https://github.com/AztecProtocol/aztec-packages/issues/14872)) ([08adfed](https://github.com/AztecProtocol/aztec-packages/commit/08adfed9a23d1cd4c9e00b53067f43b4d896bcfe)) +* showcase issue + cleanup slasher slightly ([#15062](https://github.com/AztecProtocol/aztec-packages/issues/15062)) ([23ff13c](https://github.com/AztecProtocol/aztec-packages/commit/23ff13c6b16a0c1333bf26aa5c213054c92403c3)) +* **slash:** Make slashing function bounded by available balance ([#14983](https://github.com/AztecProtocol/aztec-packages/issues/14983)) ([ddabb45](https://github.com/AztecProtocol/aztec-packages/commit/ddabb45f79cc171e41a0a5330544775de9fdf7fd)) +* small fix for outdated docs (https://github.com/noir-lang/noir/pull/8821) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* specify project ([#15208](https://github.com/AztecProtocol/aztec-packages/issues/15208)) ([301dce0](https://github.com/AztecProtocol/aztec-packages/commit/301dce04331dcfb583b7508b91491de618d4acf4)) +* **ssa_fuzzer:** Add SSA validation to the SSA fuzzer (https://github.com/noir-lang/noir/pull/8901) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **ssa:** Initial validation module (https://github.com/noir-lang/noir/pull/8765) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* **SSA:** restrict `shr` and `shl` right-hand side to u8 (https://github.com/noir-lang/noir/pull/8753) ([e13571c](https://github.com/AztecProtocol/aztec-packages/commit/e13571cce022a4d905123fbaf5e7e168a5ba7c70)) +* **ssa:** Simplify truncate when the bit size we are truncating to is greater than the value's max bit size (https://github.com/noir-lang/noir/pull/8875) ([ada0319](https://github.com/AztecProtocol/aztec-packages/commit/ada03190841b1923625905b0e6ea3bdc4a3b3e84)) +* **SSA:** validate that constrain values have the same type (https://github.com/noir-lang/noir/pull/8850) ([e62d4ac](https://github.com/AztecProtocol/aztec-packages/commit/e62d4ac050d4ab298db17f62a0389ab80ba8fb97)) +* **staking_asset_handler:** explicit scope checks ([#14874](https://github.com/AztecProtocol/aztec-packages/issues/14874)) ([ca401b9](https://github.com/AztecProtocol/aztec-packages/commit/ca401b98a83f8c7b886a35dc14968599d2f8682c)) +* **staking_asset_handler:** remove queue in next ([#15176](https://github.com/AztecProtocol/aztec-packages/issues/15176)) ([c245b3e](https://github.com/AztecProtocol/aztec-packages/commit/c245b3e704a64008c8dea40ee7b26418fd59554d)) +* **stakingAssetHandler:** allow verified people to reenter themselves ([#14751](https://github.com/AztecProtocol/aztec-packages/issues/14751)) ([9180822](https://github.com/AztecProtocol/aztec-packages/commit/9180822e17ea6031fd45d55bcfb1551fd481884f)) +* **stakingAssetHandler:** include queue position in added to queue event ([#14752](https://github.com/AztecProtocol/aztec-packages/issues/14752)) ([a94221f](https://github.com/AztecProtocol/aztec-packages/commit/a94221fadcef232a3a437006a7c137438f3fe123)) +* stdlib field pre-audit pt.2 ([#14604](https://github.com/AztecProtocol/aztec-packages/issues/14604)) ([a725813](https://github.com/AztecProtocol/aztec-packages/commit/a725813bf05acb2aba88831bc80ef97b7c558c2b)) +* stdlib field pre-audit pt.3 ([#14770](https://github.com/AztecProtocol/aztec-packages/issues/14770)) ([5867520](https://github.com/AztecProtocol/aztec-packages/commit/5867520ed258adecfee692b3694d693b2c7964ca)) +* stdlib field pre-audit pt.4 ([#15008](https://github.com/AztecProtocol/aztec-packages/issues/15008)) ([91b55c5](https://github.com/AztecProtocol/aztec-packages/commit/91b55c58278a7d091339ac513d633fa070d5276b)) +* store reward distributor in reward config ([#15214](https://github.com/AztecProtocol/aztec-packages/issues/15214)) ([ffc8af0](https://github.com/AztecProtocol/aztec-packages/commit/ffc8af0c47dad3be6fb4bfb9f3f5af6cd3a05a67)) +* Store tx effects individually ([#15055](https://github.com/AztecProtocol/aztec-packages/issues/15055)) ([9222c77](https://github.com/AztecProtocol/aztec-packages/commit/9222c7777effe26aa04b5367a455d25be0e7f62e)), closes [#14299](https://github.com/AztecProtocol/aztec-packages/issues/14299) +* Support ci-no-fail-fast label ([#15138](https://github.com/AztecProtocol/aztec-packages/issues/15138)) ([d71b7a6](https://github.com/AztecProtocol/aztec-packages/commit/d71b7a63300e898a3d40f7da703f7cde545afb41)) +* test noir-ecdsa library in CI (https://github.com/noir-lang/noir/pull/8859) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* **test:** Add `--minimal-ssa` to integration tests (https://github.com/noir-lang/noir/pull/8938) ([78d87cb](https://github.com/AztecProtocol/aztec-packages/commit/78d87cb88633aee537d022d6fd8e57ceb74a3a95)) +* timestamp should be a u64 everywhere ([#14910](https://github.com/AztecProtocol/aztec-packages/issues/14910)) ([1681cb5](https://github.com/AztecProtocol/aztec-packages/commit/1681cb5bd7c7fb85bd5eb6edb329f577b188bc70)) +* transcripts can only be loaded and exported ([#15112](https://github.com/AztecProtocol/aztec-packages/issues/15112)) ([78a5deb](https://github.com/AztecProtocol/aztec-packages/commit/78a5deb1c3aa24503935bbda20f9374ba35aa5c4)) +* Try deflake l1-reorg test ([#14705](https://github.com/AztecProtocol/aztec-packages/issues/14705)) ([ccb2655](https://github.com/AztecProtocol/aztec-packages/commit/ccb26553f2e31b011324ed710abc501fd43d77b9)) +* update cpp bootstrap bench_ivc cmd ([#15120](https://github.com/AztecProtocol/aztec-packages/issues/15120)) ([4c695be](https://github.com/AztecProtocol/aztec-packages/commit/4c695be4bff40a4a2ca2118c7e14c83a104e93aa)) +* update foundry ([#14857](https://github.com/AztecProtocol/aztec-packages/issues/14857)) ([7a7287e](https://github.com/AztecProtocol/aztec-packages/commit/7a7287e945ee2468e3c1d16156948c46cc475f06)) +* update schnorr to v0.1.3 ([#14726](https://github.com/AztecProtocol/aztec-packages/issues/14726)) ([4c3c8fa](https://github.com/AztecProtocol/aztec-packages/commit/4c3c8fa5f9ebcc6a40e9eade93f3191a6254833f)) +* update test pattern + add logging ([#14805](https://github.com/AztecProtocol/aztec-packages/issues/14805)) ([2fa00fa](https://github.com/AztecProtocol/aztec-packages/commit/2fa00fa9c1db49025867d723e98165315d3929eb)) +* update to jest30 ([#15068](https://github.com/AztecProtocol/aztec-packages/issues/15068)) ([3cbe794](https://github.com/AztecProtocol/aztec-packages/commit/3cbe7945c84145b8646cf5cf8fb730b915c0dc93)) +* update vanilla box ([#14539](https://github.com/AztecProtocol/aztec-packages/issues/14539)) ([b9f63e0](https://github.com/AztecProtocol/aztec-packages/commit/b9f63e086a00464c0ad1800566372d3b7ddc6746)) +* Updated bucket data ([#15152](https://github.com/AztecProtocol/aztec-packages/issues/15152)) ([308037a](https://github.com/AztecProtocol/aztec-packages/commit/308037ad9b1489540aedbb57a9dac3d71282010e)) +* Updated tx pool limit ([#15209](https://github.com/AztecProtocol/aztec-packages/issues/15209)) ([227fd75](https://github.com/AztecProtocol/aztec-packages/commit/227fd751f8d3e07a96e4c1564665f0676f8222af)) +* Updated version number in testnet bucket ([#14899](https://github.com/AztecProtocol/aztec-packages/issues/14899)) ([7435643](https://github.com/AztecProtocol/aztec-packages/commit/7435643676f84e2202d73d61151ad644f93e5a00)) +* use bitmap in outbox ([#14988](https://github.com/AztecProtocol/aztec-packages/issues/14988)) ([2731d9a](https://github.com/AztecProtocol/aztec-packages/commit/2731d9a8aebf63d05db1a786296abdf897c49490)) +* use eth address in sentinel store ([#14743](https://github.com/AztecProtocol/aztec-packages/issues/14743)) ([d04e170](https://github.com/AztecProtocol/aztec-packages/commit/d04e170e799e9bb8e30688c5e848ab96a51ebb2b)) +* Use minimal tx as the minimal trace in cpp tests ([#14851](https://github.com/AztecProtocol/aztec-packages/issues/14851)) ([4ff8ee5](https://github.com/AztecProtocol/aztec-packages/commit/4ff8ee540976f321547f84655a7b673902ab8e08)) +* Use the native call to find sibling path ([#15017](https://github.com/AztecProtocol/aztec-packages/issues/15017)) ([a8c553f](https://github.com/AztecProtocol/aztec-packages/commit/a8c553f203c98e5ca274e763c8e51daaaaf760d6)) +* Use the native call to find sibling path in more places ([#15020](https://github.com/AztecProtocol/aztec-packages/issues/15020)) ([a0944d6](https://github.com/AztecProtocol/aztec-packages/commit/a0944d6ab9d5708daa7ff219a932e7b3784c6637)) +* **validation:** Ban function pointers in SSA globals (https://github.com/noir-lang/noir/pull/8947) ([4e3f550](https://github.com/AztecProtocol/aztec-packages/commit/4e3f550332c81006b8d3cdbeb646f4c567473f9d)) +* World State benchmark fixes ([#14923](https://github.com/AztecProtocol/aztec-packages/issues/14923)) ([7ce46bf](https://github.com/AztecProtocol/aztec-packages/commit/7ce46bfb2506a0ff6d49d5aa46045abdfd02f96a)) +* writing delegation lib tests ([#15077](https://github.com/AztecProtocol/aztec-packages/issues/15077)) ([484694b](https://github.com/AztecProtocol/aztec-packages/commit/484694b997ac4ff62ed6d008cc3f12e0c60720ff)) + + +### Documentation + +* link to performant noir doc ([#14620](https://github.com/AztecProtocol/aztec-packages/issues/14620)) ([0ef97ee](https://github.com/AztecProtocol/aztec-packages/commit/0ef97ee7b93af4ae1be1c266aaab45290c2bfe4a)) + ## [0.87.6](https://github.com/AztecProtocol/aztec-packages/compare/v0.87.5...v0.87.6) (2025-06-02) diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/_category_.json b/docs/versioned_docs/version-v0.88.0/aztec/concepts/_category_.json new file mode 100644 index 000000000000..88de50877b40 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Concepts", + "position": 1, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/accounts/_category_.json b/docs/versioned_docs/version-v0.88.0/aztec/concepts/accounts/_category_.json new file mode 100644 index 000000000000..ddbe59a365c3 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/accounts/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Accounts", + "position": 5, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/accounts/index.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/accounts/index.md new file mode 100644 index 000000000000..42d2a3f6ca0a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/accounts/index.md @@ -0,0 +1,172 @@ +--- +title: Accounts +sidebar_position: 1 +tags: [accounts] +--- + +Aztec has native account abstraction. Every account in Aztec is a smart contract. + +In this section, you'll learn about Aztec's account abstraction, Aztec accounts and address derivation, how wallets relate to accounts, and how the entrypoints are defined. + +## Account Abstraction (AA) + +With account abstraction, the identity of a user is usually represented by a smart contract. That makes user's on-chain identity more flexible than simply using private/public keys. For example, Bitcoin has rigid accounts that must be a private key, whereas a user might want their on-chain identity to be controlled by a physical passport. + +Among the account parts to be abstracted are authentication (“Who I am”), authorization (“What I am allowed to do”), replay protection, fee payment, and execution. + +Some account features unlocked by account abstraction are account recovery, gas sponsorship, and support of signatures other than ECDSA, such as more efficient signatures (e.g. Schnorr, BLS), or more user-friendly ones (e.g. smartphone secure enclave). + +### Protocol vs application level + +AA can be implemented at the protocol level is called native Account Abstraction. In this case, all the accounts on the network are smart contracts. AA can also be implemented at the smart-contract level, then we call it non-native Account Abstraction. In this case, there might be both EOAs and accounts controlled by smart contracts. + +In the case of Aztec, we have native Account Abstraction. + +## Aztec Account Abstraction + +### Authorization abstraction and DoS attacks + +While we talk about “arbitrary verification logic” describing the intuition behind AA, the logic is usually not really arbitrary. The verification logic (i.e. what is checked as an authorization) is limited to make the verification time fast and bounded. If it is not bounded, an attacker can flood the mempool with expensive invalid transactions, clogging the network. That is the case for all chains where transaction validity is checked by the sequencer. + +On Aztec, there is no limitation on the complexity of verification logic (what does it mean for the transaction to be valid). Whatever conditions it checks, the proof (that the sequencer needs to verify) is independent of its complexity. + +This unlocks a whole universe of new use cases and optimization of existing ones. Whenever the dapp can benefit from moving expensive computations off-chain, Aztec will provide a unique chance for an optimization. That is to say, on traditional chains users pay for each executed opcode, hence more complex operations (e.g. alternative signature verification) are quite expensive. In the case of Aztec, it can be moved off-chain so that it becomes almost free. The user pays for the operations in terms of client-side prover time. However, this refers to Aztec's client-side proving feature and not directly AA. + +Couple of examples: + +- Multisig contract with an arbitrary number of parties that can verify any number of signatures for free. +- Oracle contract with an arbitrary number of data providers that can verify any number of data entries for free. + +## Aztec account + +Smart contracts on Aztec are represented by an "address", which is a hexadecimal number that uniquely represents an entity on the Aztec network. An address is derived by hashing information specific to the entity represented by the address. This information includes contract bytecode and the public keys used in private execution for encryption and nullification. This means addresses are deterministic. + +Aztec has no concept of EOAs (Externally Owned Accounts). Every account is implemented as a contract. + +### Entrypoints + +Account contracts usually have a specific function called `entrypoint`. It serves as the interface for interaction with the smart contract and can be called by external users or other smart contracts. + +An `entrypoint` function receives the actions to be carried out and an authentication payload. In pseudocode: + +``` +publicKey: PublicKey; + +def entryPoint(payload): + let { privateCalls, publicCalls, nonce, signature } = payload; + let payloadHash = hash(privateCalls, publicCalls, nonce); + validateSignature(this.publicKey, signature, payloadHash); + + foreach privateCall in privateCalls: + let { to, data, value } = privateCall; + call(to, data, value); + + foreach publicCall in publicCalls: + let { to, data, value, gasLimit } = publicCall; + enqueueCall(to, data, value, gasLimit); +``` + +A request for executing an action requires: + +- The `origin` contract to execute as the first step. +- The initial function to call (usually `entrypoint`). +- The arguments (which encode the private and public calls to run as well as any signatures). + +Read more about how to write an account contract [here](../../../developers/tutorials/codealong/contract_tutorials/write_accounts_contract.md). + +### Non-standard entrypoints + +Since the `entrypoint` interface is not enshrined, there is nothing that differentiates an account contract from an application contract. This allows implementing functions that can be called by any user and are just intended to advance the state of a contract. + +For example, a lottery contract, where at some point a prize needs to be paid out to its winners. This `pay` action does not require authentication and does not need to be executed by any user in particular, so anyone could submit a transaction that defines the lottery contract itself as `origin` and `pay` as `entrypoint` function. However, it's on the contract to define how fees for the prize claim will be paid as they won't be paid by the account contract. + +For an example of this behavior see our [e2e_crowdfunding_and_claim test](https://github.com/AztecProtocol/aztec-packages/blob/88b5878dd4b95d691b855cd84153ba884adf25f8/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts#L322) and the [SignerLess wallet](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec.js/src/wallet/signerless_wallet.ts) implementation. Notice that the Signerless wallet doesn't invoke an `entrypoint` function of an account contract but instead invokes the target contract function directly. + +:::info + +Entrypoints for the following cases: + +- If no contract `entrypoint` is used `msg_sender` is set to `Field.max`. +- In a private to public `entrypoint`, `msg_sender` is the contract making the private to public call. +- When calling the `entrypoint` on an account contract, `msg_sender` is set to the account contract address. + +::: + +### Account contracts and wallets + +Account contracts are tightly coupled to the wallet software that users use to interact with the protocol. Dapps submit to the wallet software one or more function calls to be executed (e.g. "call swap in X contract"), and the wallet encodes and signs the request as a valid payload for the user's account contract. The account contract then validates the request encoded and signed by the wallet, and executes the function calls requested by the dapp. + +### Account Initialization + +When a user wants to interact with the network's **public** state, they need to deploy their account contract. A contract instance is considered to be publicly deployed when it has been broadcasted to the network via the canonical `ContractInstanceDeployer` contract, which also emits a deployment nullifier associated to the deployed instance. + +However, to send fully **private** transactions, it's enough to initialize the account contract (public deployment is not needed). The default state for any given address is to be uninitialized, meaning a function with the [initializer annotation](../../../developers/tutorials/codealong/contract_tutorials/nft_contract.md#initializer) has not been called. The contract is initialized when one of the functions marked with the `#[initializer]` annotation has been invoked. Multiple functions in the contract can be marked as initializers. Contracts may have functions that skip the initialization check (marked with `#[noinitcheck]`). + +Account deployment and initialization are not required to receive notes. The user address is deterministically derived from the encryption public key and the account contract they intend to deploy, so that funds can be sent to an account that hasn't been deployed yet. + +Users will need to pay transaction fees in order to deploy their account contract. This can be done by sending fee juice to their account contract address (which can be derived deterministically, as mentioned above), so that the account has funds to pay for its own deployment. Alternatively, the fee can be paid for by another account, using [fee abstraction](#fee-abstraction). + +## What is an account address + +Address is derived from the [address keys](keys.md#address-keys). While the AddressPublicKey is an elliptic curve point of the form (x,y) on the [Grumpkin elliptic curve](https://github.com/AztecProtocol/aztec-connect/blob/9374aae687ec5ea01adeb651e7b9ab0d69a1b33b/markdown/specs/aztec-connect/src/primitives.md), the address is its x coordinate. The corresponding y coordinate can be derived if needed. For x to be a legitimate address, address there should exist a corresponding y that satisfies the curve equation. Any field element cannot work as an address. + +### Complete address + +Because of the contract address derivation scheme, you can check that a given set of public [keys](keys.md) corresponds to a given address by trying to recompute it. + +If Alice wants Bob to send her a note, it's enough to share with him her address (x coordinate of the AddressPublicKey). + +However, if Alice wants to spend her notes (i.e. to prove that the nullifier key inside her address is correct) she needs her complete address. It is represented by: + +- all the user's public keys, +- [partial address](keys.md#address-keys), +- contract address. + +## Authorizing actions + +Account contracts are also expected, though not required by the protocol, to implement a set of methods for authorizing actions on behalf of the user. During a transaction, a contract may call into the account contract and request the user authorization for a given action, identified by a hash. This pattern is used, for instance, for transferring tokens from an account that is not the caller. + +When executing a private function, this authorization is checked by requesting an authentication witness from the execution oracle, which is usually a signed message. Authentication Witness is a scheme for authenticating actions on Aztec, so users can allow third-parties (e.g. contracts) to execute an action on their behalf. + +The user's [Private eXecution Environment (PXE)](../pxe/index.md) is responsible for storing these auth witnesses and returning them to the requesting account contract. Auth witnesses can belong to the current user executing the local transaction, or to another user who shared it out-of-band. + +However, during a public function execution, it is not possible to retrieve a value from the local [oracle](../../smart_contracts/oracles/index.md). To support authorizations in public functions, account contracts should save in a public authwit registry what actions have been pre-authorized by their owner. + +These two patterns combined allow an account contract to answer whether an action `is_valid_impl` for a given user both in private and public contexts. + +You can read more about authorizing actions with authorization witnesses on [this page](../advanced/authwit.md). + +:::info + +Transaction simulations in the PXE are not currently simulated, this is future work described [here](https://github.com/AztecProtocol/aztec-packages/issues/9133). This means that any transaction simulations that call into a function requiring an authwit will require the user to provide an authwit. Without simulating simulations, the PXE can't anticipate what authwits a transaction may need, so developers will need to manually request these authwits from users. In the future, transactions requiring authwits will be smart enough to ask the user for the correct authwits automatically. + +::: + +## Nonce and fee abstraction + +Beyond the authentication logic abstraction, there are nonce abstraction and fee abstraction. + +### Nonce abstraction + +Nonce is a unique number and it is utilized for replay protection (i.e. preventing users from executing a transaction more than once and unauthorized reordering). + +In particular, nonce management defines what it means for a transaction to be canceled, the rules of transaction ordering, and replay protection. In Ethereum, nonce is enshrined into the protocol. On the Aztec network, nonce is abstracted i.e. if a developer wants to customize it, they get to decide how they handle replay protection, transaction cancellation, as well as ordering. + +Take as an example the transaction cancellation logic. It can be done through managing nullifiers. Even though we usually refer to a nullifier as a creature utilized to consume a note, in essence, a nullifier is an emitted value whose uniqueness is guaranteed by the protocol. If we want to cancel a transaction before it was mined, we can send another transaction with higher gas price that emits the same nullifier (i.e. nullifier with the same value, for example, 5). The second transaction will invalidate the original one, since nullifiers cannot be repeated. + +Nonce abstraction is mostly relevant to those building wallets. For example, a developer can design a wallet that allows sending big transactions with very low priority fees because the transactions are not time sensitive (i.e. the preference is that a transaction is cheap and doesn't matter if it is slow). If one tries to apply this logic today on Ethereum (under sequential nonces), when they send a large, slow transaction they can't send any other transactions until that first large, slow transaction is processed. + +### Fee abstraction + +It doesn't have to be the transaction sender who pays the transaction fees. Wallets or dapp developers can choose any payment logic they want using a paymaster. To learn more about fees on Aztec – check [this page](../fees.md). + +Paymaster is a contract that can pay for transactions on behalf of users. It is invoked during the private execution stage and set as the fee payer. + +- It can be managed by a dapp itself (e.g. a DEX can have its own paymaster) or operate as a third party service available for everyone. +- Fees can be paid publicly or privately. +- Fees can be paid in any token that a paymaster accepts. + +Fee abstraction unlocks use cases like: + +- Sponsored transactions (e.g. the dapp's business model might assume revenue from other streams besides transaction fees or the dapp might utilize sponsored transaction mechanics for marketing purposes). For example, sponsoring the first ten transactions for every user. +- Flexibility in the currency used in transaction payments (e.g. users can pay for transactions in ERC-20 token). diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/accounts/keys.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/accounts/keys.md new file mode 100644 index 000000000000..d58930c2d923 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/accounts/keys.md @@ -0,0 +1,181 @@ +--- +title: Keys +tags: [accounts, keys] +--- + +import Image from "@theme/IdealImage"; + +In this section, you will learn what keys are used in Aztec, and how the addresses are derived. + +## Types of keys + +Each Aztec account is backed by four key pairs: + +- Nullifier keys – used to spend notes. +- Address keys – this is an auxiliary key used for the address derivation; it’s internally utilized by the protocol and does not require any action from developers. +- Incoming viewing keys – used to encrypt a note for the recipient. +- Signing keys – an optional key pair used for account authorization. + +The first three pairs are embedded into the protocol while the signing key is abstracted up to the account contract developer. + +### Nullifier keys + +Nullifier keys are presented as a pair of the master nullifier public key (`Npk_m`) and the master nullifier secret key (`nsk_m`). + +To spend a note, the user computes a nullifier corresponding to this note. A nullifier is a hash of the note hash and app-siloed nullifier secret key, the latter is derived using the nullifier master secret key. To compute the nullifier, the protocol checks that the app-siloed key is derived from the master key for this contract and that master nullifier public key is linked to the note owner's address. + +### Address keys + +Address keys are used for account [address derivation](../accounts/index.md). + + + +Address keys are a pair of keys `AddressPublicKey` and `address_sk` where `address_sk` is a scalar defined as `address_sk = pre_address + ivsk` and `AddressPublicKey` is an elliptic curve point defined as `AddressPublicKey = address_sk * G`. This is useful for encrypting notes for the recipient with only their address. + +`pre_address` can be thought of as a hash of all account’s key pairs and functions in the account contract. + +In particular, + +``` +pre_address := poseidon2(public_keys_hash, partial_address) +public_keys_hash := poseidon2(Npk_m, Ivpk_m, Ovpk_m, Tpk_m) +partial_address := poseidon2(contract_class_id, salted_initialization_hash) +contract_class_id := poseidon2(artifact_hash, fn_tree_root, public bytecode commitment) +salted_initialization_hash := poseidon2(deployer_address, salt, constructor_hash) +``` + +where + +- `artifact_hash` – hashes data from the Contract Artifact file that contains the data needed to interact with a specific contract, including its name, functions that can be executed, and the interface and code of those functions. +- `fn_tree_root` – hashes pairs of verification keys and function selector (`fn_selector`) of each private function in the contract. +- `fn_selector` – the first four bytes of the hashed `function signature` where the last one is a string consisting of the function's name followed by the data types of its parameters. +- `public bytecode commitment` – takes contract's code as an input and returns short commitment. +- `deployer_address` – account address of the contract deploying the contract. +- `salt` – a user-specified 32-byte value that adds uniqueness to the deployment. +- `constructor_hash` – hashes `constructor_fn_selector` and `constructor_args` where the last one means public inputs of the contract. + +:::note +Under the current design Aztec protocol does not use `Ovpk` (outgoing viewing key) and `Tpk` (tagging key). However, formally they still exist and can be used by developers for some non-trivial design choices if needed. +::: + +### Incoming viewing keys + +The incoming viewing public key (`Ivpk`) is used by the sender to encrypt a note for the recipient. The corresponding incoming viewing secret key (`ivsk`) is used by the recipient to decrypt the note. + +When it comes to notes encryption and decryption: + +- For each note, there is a randomly generated ephemeral key pair (`esk`, `Epk`) where `Epk = esk * G`. +- The `AddressPublicKey` (derived from the `ivsk`) together with `esk` are encrypted as a secret `S`, `S = esk * AddressPublicKey`. +- `symmetric_encryption_key = hash(S)` +- `Ciphertext = aes_encrypt(note, symmetric_encryption_key)` +- The recipient gets a pair (`Epk`, `Ciphertext`) +- The recipient uses the `address_sk` to decrypt the secret: `S = Epk * address_sk`. +- The recipient uses the decrypted secret to decrypt the ciphertext. + +### Signing keys + +Thanks to the native [account abstraction](../accounts/index.md), authorization logic can be implemented in an alternative way that is up to the developer (e.g. using Google authorization credentials, vanilla password logic or Face ID mechanism). In these cases, signing keys may not be relevant. + +However if one wants to implement authorization logic containing signatures (e.g. ECDSA or Shnorr) they will need signing keys. Usually, an account contract will validate a signature of the incoming payload against a known signing public key. + +This is a snippet of our Schnorr Account contract implementation, which uses Schnorr signatures for authentication: + +```rust title="is_valid_impl" showLineNumbers +// Load public key from storage +let storage = Storage::init(context); +let public_key = storage.signing_public_key.get_note(); + +// Load auth witness +// Safety: The witness is only used as a "magical value" that makes the signature verification below pass. +// Hence it's safe. +let witness: [Field; 64] = unsafe { get_auth_witness(outer_hash) }; +let mut signature: [u8; 64] = [0; 64]; +for i in 0..64 { + signature[i] = witness[i] as u8; +} + +let pub_key = std::embedded_curve_ops::EmbeddedCurvePoint { + x: public_key.x, + y: public_key.y, + is_infinite: false, +}; +// Verify signature of the payload bytes +schnorr::verify_signature(pub_key, signature, outer_hash.to_be_bytes::<32>()) +``` +> Source code: noir-projects/noir-contracts/contracts/account/schnorr_account_contract/src/main.nr#L65-L86 + + +### Storing signing keys + +Since signatures are fully abstracted, how the public key is stored in the contract is abstracted as well and left to the developer of the account contract. Among a few common approaches are storing the key in a private note, in an immutable private note, using shared mutable state, reusing other in-protocol keys, or a separate keystore. Below, we elaborate on these approaches. + +#### Using a private note​ + +Storing the signing public key in a private note makes it accessible from the `entrypoint` function, which is required to be a private function, and allows for rotating the key when needed. However, keep in mind that reading a private note requires nullifying it to ensure it is up-to-date, so each transaction you send will destroy and recreate the public key so the protocol circuits can be sure that the notes are not stale. This incurs cost for every transaction. + +#### Using an immutable private note​ + +Using an immutable private note removes the need to nullify the note on every read. This generates no nullifiers or new commitments per transaction. However, it does not allow the user to rotate their key. + +```rust title="public_key" showLineNumbers +signing_public_key: PrivateImmutable, +``` +> Source code: noir-projects/noir-contracts/contracts/account/schnorr_account_contract/src/main.nr#L28-L30 + + +:::note +When it comes to storing the signing key in a private note, there are several details that rely on the wallets: + +- A note with a key is managed similar to any other private note. Wallets are expected to backup all the notes so that they can be restored on another device (e.g. if the user wants to move to another device). +- The note with the key might exist locally only (in PXE) or it can be broadcasted as an encrypted note by the wallet to itself. In the second case, this note will also exist on Aztec. + ::: + +#### Using Shared Mutable state + +By [Shared Mutable](../../../developers/reference/smart_contract_reference/storage/shared_state.md#sharedmutable) we mean privately readable publicly mutable state. + +To make public state accessible privately, there is a delay window in public state updates. One needs this window to be able to generate proofs client-side. This approach would not generate additional nullifiers and commitments for each transaction while allowing the user to rotate their key. However, this causes every transaction to now have a time-to-live determined by the frequency of the mutable shared state, as well as imposing restrictions on how fast keys can be rotated due to minimum delays. + +#### Reusing some of the in-protocol keys + +It is possible to use some of the key pairs defined in protocol (e.g. incoming viewing keys) as the signing key. Since this key is part of the address preimage, it can be validated against the account contract address rather than having to store it. However, this approach is not recommended since it reduces the security of the user's account. + +#### Using a separate keystore + +Since there are no restrictions on the actions that an account contract may execute for authenticating a transaction (as long as these are all private function executions), the signing public keys can be stored in a separate keystore contract that is checked on every call. In this case, each user could keep a single contract that acts as a keystore, and have multiple account contracts that check against that keystore for authorization. This will incur a higher proving time for each transaction, but has no additional cost in terms of fees. + +### Keys generation + +All key pairs (except for the signing keys) are generated in the [Private Execution Environment](../pxe/index.md) (PXE) when a user creates an account. PXE is also responsible for the further key management (oracle access to keys, app siloed keys derivation, etc.) + +### Keys derivation + +All key pairs are derived using elliptic curve public-key cryptography on the [Grumpkin curve](https://github.com/AztecProtocol/aztec-connect/blob/9374aae687ec5ea01adeb651e7b9ab0d69a1b33b/markdown/specs/aztec-connect/src/primitives.md), where the secret key is represented as a scalar and the public key is represented as an elliptic curve point multiplied by that scalar. + +The address private key is an exception and derived in a way described above in the [Address keys](#address-keys) section. + +### The special case of escrow contracts + +Typically, for account contracts the public keys will be non-zero and for non-account contracts zero. + +An exception (a non-account contract which would have some of the keys non-zero) is an escrow contract. Escrow contract is a type of contract which on its own is an "owner" of a note meaning that it has a `Npk_m` registered and the notes contain this `Npk_m`. + +Participants in this escrow contract would then somehow get a hold of the escrow's `nsk_m` and nullify the notes based on the logic of the escrow. An example of an escrow contract is a betting contract. In this scenario, both parties involved in the bet would be aware of the escrow's `nsk_m`. The escrow would then release the reward only to the party that provides a "proof of winning". + +### App-siloed keys + +Nullifier keys and Incoming view keys are app-siloed meaning they are scoped to the contract that requests them. This means that the keys used for the same user in two different application contracts will be different. + +App-siloed keys allow to minimize damage of potential key leaks as a leak of the scoped keys would only affect one application. + +App-siloed keys are derived from the corresponding master keys and the contract address. For example, for the app-siloed nullifier secret key: `nsk_app = hash(nsk_m, app_contract_address)`. + +App-siloed keys [are derived](../advanced/storage/storage_slots.md#implementation) in PXE every time the user interacts with the application. + +App-siloed incoming viewing key also allows per-application auditability. A user may choose to disclose this key for a given application to an auditor or regulator (or for 3rd party interfaces, e.g. giving access to a block explorer to display my activity), as a means to reveal all their activity within that context, while retaining privacy across all other applications in the network. + +### Key rotation + +Key rotation is the process of creating new signing keys to replace existing keys. By rotating encryption keys on a regular schedule or after specific events, you can reduce the potential consequences of the key being compromised. + +On Aztec, key rotation is impossible for nullifier keys, incoming viewing keys and address keys as all of them are embedded into the address and address is unchangeable. In the meanwhile, signing keys can be rotated. diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/authwit.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/authwit.md new file mode 100644 index 000000000000..01f8997bacec --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/authwit.md @@ -0,0 +1,143 @@ +--- +title: Authentication Witness (Authwit) +tags: [accounts, authwit] +sidebar_position: 2 +keywords: [authwit, authentication witness, accounts] +--- + +import Image from "@theme/IdealImage"; + +Authentication Witness is a scheme for authenticating actions on Aztec, so users can allow third-parties (eg protocols or other users) to execute an action on their behalf. + +## Background + +When building DeFi or other smart contracts, it is often desired to interact with other contracts to execute some action on behalf of the user. For example, when you want to deposit funds into a lending protocol, the protocol wants to perform a transfer of [ERC20](https://eips.ethereum.org/EIPS/eip-20) tokens from the user's account to the protocol's account. + +In the EVM world, this is often accomplished by having the user `approve` the protocol to transfer funds from their account, and then calling a `deposit` function on it afterwards. + + + +This flow makes it rather simple for the application developer to implement the deposit function, but does not come without its downsides. + +One main downside, which births a bunch of other issues, is that the user needs to send two transactions to make the deposit - first the `approve` and then the `deposit`. + +To limit the annoyance for return-users, some front-ends will use the `approve` function with an infinite amount, which means that the user will only have to sign the `approve` transaction once, and every future `deposit` will then use some of that "allowance" to transfer funds from the user's account to the protocol's account. + +This can lead to a series of issues though, eg: + +- The user is not aware of how much they have allowed the protocol to transfer. +- The protocol can transfer funds from the user's account at any time. This means that if the protocol is rugged or exploited, it can transfer funds from the user's account without the user having to sign any transaction. This is especially an issue if the protocol is upgradable, as it could be made to steal the user's approved funds at any time in the future. + +To avoid this, many protocols implement the `permit` flow, which uses a meta-transaction to let the user sign the approval off-chain, and pass it as an input to the `deposit` function, that way the user only has to send one transaction to make the deposit. + + + +This is a great improvement to infinite approvals, but still has its own sets of issues. For example, if the user is using a smart-contract wallet (such as Argent or Gnosis Safe), they will not be able to sign the permit message since the usual signature validation does not work well with contracts. [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) was proposed to give contracts a way to emulate this, but it is not widely adopted. + +Separately, the message that the user signs can seem opaque to the user and they might not understand what they are signing. This is generally an issue with `approve` as well. + +All of these issues have been discussed in the community for a while, and there are many proposals to solve them. However, none of them have been widely adopted - ERC20 is so commonly used and changing a standard is hard. + +## In Aztec + +Adopting ERC20 for Aztec is not as simple as it might seem because of private state. + +If you recall from the [Hybrid State model](../storage/state_model.md), private state is generally only known by its owner and those they have shared it with. Because it relies on secrets, private state might be "owned" by a contract, but it needs someone with knowledge of these secrets to actually spend it. You might see where this is going. + +If we were to implement the `approve` with an allowance in private, you might know the allowance, but unless you also know about the individual notes that make up the user's balances, it would be of no use to you! It is private after all. To spend the user's funds you would need to know the decryption key, see [keys for more](../accounts/keys.md). + +While this might sound limiting in what we can actually do, the main use of approvals have been for simplifying contract interactions that the user is doing. In the case of private transactions, this is executed on the user device, so it is not a blocker that the user need to tell the executor a secret - the user is the executor! + +### So what can we do? + +A few more things we need to remember about private execution: + +- To stay private, it all happens on the user device. +- Because it happens on the user device, additional user-provided information can be passed to the contract mid-execution via an oracle call. + +For example, when executing a private transfer, the wallet will be providing the notes that the user wants to transfer through one of these oracle calls instead of the function arguments. This allows us to keep the function signature simple, and have the user provide the notes they want to transfer through the oracle call. + +For a transfer, it could be the notes provided, but we could also use the oracle to provide any type of data to the contract. So we can borrow the idea from `permit` that the user can provide a signature (or witness) to the contract which allows it to perform some action on behalf of the user. + +:::info Witness or signature? +The doc refers to a witness instead of a signature because it is not necessarily a signature that is required to convince the account contract that we are allowed to perform the action. It depends on the contract implementation, and could also be a password or something similar. +::: + +Since the witness is used to authenticate that someone can execute an action on behalf of the user, we call it an Authentication Witness or `AuthWit` for short. An "action", in this meaning, is a blob of data that specifies what call is approved, what arguments it is approved with, and the actor that is authenticated to perform the call. + +In practice, this blob is currently outlined to be a hash of the content mentioned, but it might change over time to make ["simulating simulations"](https://discourse.aztec.network/t/simulating-simulations/2218) easier. + +Outlined more clearly, we have the following, where the `H` is a SNARK-friendly hash function and `argsHash` is the hash of function arguments: + +```rust +authentication_witness_action = H( + caller: AztecAddress, + contract: AztecAddress, + selector: Field, + argsHash: Field +); +``` + +To outline an example as mentioned earlier, let's say that we have a token that implements `AuthWit` such that transfer funds from A to B is valid if A is doing the transfer, or there is a witness that authenticates the caller to transfer funds from A's account. While this specifies the spending rules, one must also know of the notes to use them for anything. This means that a witness in itself is only half the information. + +Creating the authentication action for the transfer of funds to the Defi contract would look like this: + +```rust +action = H(defi, token, transfer_selector, H(alice_account, defi, 1000)); +``` + +This can be read as "defi is allowed to call token transfer function with the arguments (alice_account, defi, 1000)". + +With this out of the way, let's look at how this would work in the graph below. The exact contents of the witness will differ between implementations as mentioned before, but for the sake of simplicity you can think of it as a signature, which the account contract can then use to validate if it really should allow the action. + + + +:::info Static call for AuthWit checks +The call to the account contract for checking authentication should be a static call, meaning that it cannot change state or make calls that change state. If this call is not static, it could be used to re-enter the flow and change the state of the contract. +::: + +:::danger Re-entries +The above flow could be re-entered at token transfer. It is mainly for show to illustrate a logic outline. +::: + +### What about public + +As noted earlier, we could use the ERC20 standard for public. But this seems like a waste when we have the ability to try righting some wrongs. Instead, we can expand our AuthWit scheme to also work in public. This is actually quite simple, instead of asking an oracle (which we can't do as easily because not private execution) we can just store the AuthWit in a shared registry, and look it up when we need it. While this needs the storage to be updated ahead of time (can be same tx), we can quite easily do so by batching the AuthWit updates with the interaction - a benefit of Account Contracts. A shared registry is used such that execution from the sequencers point of view will be more straight forward and predictable. Furthermore, since we have the authorization data directly in public state, if they are both set and unset (authorized and then used) in the same transaction, there will be no state effect after the transaction for the authorization which saves gas ⛽. + + + +### Replays + +To ensure that the authentication witness can only be used once, we can emit the action itself as a nullifier. This way, the authentication witness can only be used once. This is similar to how notes are used, and we can use the same nullifier scheme for this. + +Note however, that it means that the same action cannot be authenticated twice, so if you want to allow the same action to be authenticated multiple times, we should include a nonce in the arguments, such that the action is different each time. + +For the transfer, this could be done simply by appending a nonce to the arguments. + +```rust +action = H(defi, token, transfer_selector, H(alice_account, defi, 1000, nonce)); +``` + +Beware that the account contract will be unable to emit the nullifier since it is checked with a static call, so the calling contract must do it. This is similar to nonces in ERC20 tokens today. We provide a small library that handles this. + +### Differences to approval + +The main difference is that we are not setting up an allowance, but allowing the execution of a specific action. We decided on this option as the default since it is more explicit and the user can agree exactly what they are signing. + +Also, most uses of the approvals are for contracts where the following interactions are called by the user themselves, so it is not a big issue that they are not as easily "transferrable" as the `permit`s. + +:::note + +Authwits only work for a single user to authorize actions on contracts that their account is calling. You cannot authorize other users to take actions on your behalf. + +::: + +In order for another user to be able to take actions on your behalf, they would need access to your nullifier secret key so that they could nullify notes for you, but they should not have access to your nullifier secret key. + +### Other use-cases + +We don't need to limit ourselves to the `transfer` function, we can use the same scheme for any function that requires authentication. For example, for authenticating to burn, transferring assets from public to private, or to vote in a governance contract or perform an operation on a lending protocol. + +### Next Steps + +Check out the [developer documentation](../../../developers/guides/smart_contracts/writing_contracts/authwit.md) to see how to implement this in your own contracts. diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/_category_.json b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/_category_.json new file mode 100644 index 000000000000..611f5ed6919f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Circuits", + "position": 3, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/index.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/index.md new file mode 100644 index 000000000000..79f0a9aeaa18 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/index.md @@ -0,0 +1,66 @@ +--- +title: Circuits +sidebar_position: 2 +tags: [protocol, circuits] +--- + +Central to Aztec's operations are 'circuits' derived both from the core protocol and the developer-written Aztec.nr contracts. + +The core circuits enhance privacy by adding additional security checks and preserving transaction details - a characteristic Ethereum lacks. + +On this page, you’ll learn a bit more about these circuits and their integral role in promoting secure and efficient transactions within Aztec's privacy-centric framework. + +## Motivation + +In Aztec, circuits come from two sources: + +1. Core protocol circuits +2. User-written circuits (written as Aztec.nr Contracts and deployed to the network) + +This page focuses on the core protocol circuits. These circuits check that the rules of the protocol are being adhered to. + +When a function in an Ethereum smart contract is executed, the EVM performs checks to ensure that Ethereum's transaction rules are being adhered-to correctly. Stuff like: + +- "Does this tx have a valid signature?" +- "Does this contract address contain deployed code?" +- "Does this function exist in the requested contract?" +- "Is this function allowed to call this function?" +- "How much gas has been paid, and how much is left?" +- "Is this contract allowed to read/update this state variable?" +- "Perform the state read / state write" +- "Execute these opcodes" + +All of these checks have a computational cost, for which users are charged gas. + +Many existing L2s move this logic off-chain, as a way of saving their users gas costs, and as a way of increasing tx throughput. + +zk-Rollups, in particular, move these checks off-chain by encoding them in zk-S(N/T)ARK circuits. Rather than paying a committee of Ethereum validators to perform the above kinds of checks, L2 users instead pay a sequencer to execute these checks via the circuit(s) which encode them. The sequencer can then generate a zero-knowledge proof of having executed the circuit(s) correctly, which they can send to a rollup contract on Ethereum. The Ethereum validators then verify this zk-S(N/T)ARK. It often turns out to be much cheaper for users to pay the sequencer to do this, than to execute a smart contract on Ethereum directly. + +But there's a problem. + +Ethereum (and the EVM) doesn't have a notion of privacy. + +- There is no notion of a private state variable in the EVM. +- There is no notion of a private function in the EVM. + +So users cannot keep private state variables' values private from Ethereum validators, nor from existing (non-private) L2 sequencers. Nor can users keep the details of which function they've executed private from validators or sequencers. + +How does Aztec add privacy? + +Well, we just encode _extra_ checks in our zk-Rollup's zk-SNARK circuits! These extra checks introduce the notions of private state and private functions, and enforce privacy-preserving constraints on every transaction being sent to the network. + +In other words, since neither the EVM nor other rollups have rules for how to preserve privacy, we've written a new rollup which introduces such rules, and we've written circuits to enforce those rules! + +What kind of extra rules / checks does a rollup need, to enforce notions of private states and private functions? Stuff like: + +- "Perform state reads and writes using new tree structures which prevent tx linkability" (see [indexed merkle tree](../storage/indexed_merkle_tree.mdx). +- "Hide which function was just executed, by wrapping it in a zk-snark" +- "Hide all functions which were executed as part of this tx's stack trace, by wrapping the whole tx in a zk-snark" + +## Aztec core protocol circuits + +So what kinds of core protocol circuits does Aztec have? + +### Kernel, Rollup, and Squisher Circuits + +The specs of these have recently been updated. Eg for squisher circuits since Honk and Goblin Plonk schemes are still being improved! But we'll need some extra circuit(s) to squish a Honk proof (as produced by the Root Rollup Circuit) into a Standard Plonk or Fflonk proof, for cheap verification on Ethereum. diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/kernels/_category_.json b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/kernels/_category_.json new file mode 100644 index 000000000000..aa6ead3d71df --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/kernels/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Kernel Circuits", + "position": 0, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/kernels/private_kernel.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/kernels/private_kernel.md new file mode 100644 index 000000000000..10f8880efbf1 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/kernels/private_kernel.md @@ -0,0 +1,14 @@ +--- +title: Private Kernel Circuit +tags: [protocol, circuits] +--- + +This circuit is executed by the user, on their own device. This is to ensure private inputs to the circuit remain private! + +:::note + +**This is the only core protocol circuit which actually needs to be "zk" (zero-knowledge)!!!** That's because this is the only core protocol circuit which handles private data, and hence the only circuit for which proofs must not leak any information about witnesses! (The private data being handled includes: details of the Aztec.nr Contract function which has been executed; the address of the user who executed the function; the intelligible inputs and outputs of that function). + +Most so-called "zk-Rollups" do not make use of this "zero-knowledge" property. Their snarks are "snarks"; with no need for zero-knowledge, because they don't seek privacy; they only seek the 'succinct' computation-compression properties of snarks. Aztec's "zk-Rollup" actually makes use of "zero-knowledge" snarks. That's why we sometimes call it a "zk-zk-Rollup", or "_actual_ zk-Rollup". + +::: diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/kernels/public_kernel.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/kernels/public_kernel.md new file mode 100644 index 000000000000..a8b8945ffc0f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/kernels/public_kernel.md @@ -0,0 +1,6 @@ +--- +title: Public Kernel Circuit +tags: [protocol, circuits] +--- + +This circuit is executed by a Sequencer, since only a Sequencer knows the current state of the [public data tree](../../../storage/state_model.md#public-state) at any time. A Sequencer might choose to delegate proof generation to the Prover pool. diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/rollup_circuits/index.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/rollup_circuits/index.md new file mode 100644 index 000000000000..db7a11e0b9bc --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/circuits/rollup_circuits/index.md @@ -0,0 +1,17 @@ +--- +title: Rollup Circuits +tags: [protocol, circuits] +--- + +The primary purpose of the Rollup Circuits is to 'squish' all of the many thousands of transactions in a rollup into a single SNARK, which can then be efficiently and verified on Ethereum. + +These circuits are executed by a Sequencer, since their primary role is to order transactions. A Sequencer might choose to delegate proof generation to the Prover pool. + +The way we 'squish' all this data is in a 'binary tree of proofs' topology. + +> Example: If there were 16 txs in a rollup, we'd arrange the 16 kernel proofs into 8 pairs and merge each pair into a single proof (using zk-snark recursion techniques), resulting in 8 output proofs. We'd then arrange those 8 proofs into pairs and again merge each pair into a single proof, resulting in 4 output proofs. And so on until we'd be left with a single proof, which represents the correctness of the original 16 txs. +> This 'binary tree of proofs' topology allows proof generation to be greatly parallelized across prover instances. Each layer of the tree can be computed in parallel. Or alternatively, subtrees can be coordinated to be computed in parallel. + +> Note: 'binary tree of proofs' is actually an oversimplification. The Rollup Circuits are designed so that a Sequencer can actually deviate from a neat, symmetrical tree, for the purposes of efficiency, and instead sometimes create wonky trees. + +Some of the Rollup Circuits also do some protocol checks and computations, for efficiency reasons. We might rearrange which circuit does what computation, as we discover opportunities for efficiency. diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/index.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/index.md new file mode 100644 index 000000000000..6153750cb08c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/index.md @@ -0,0 +1,11 @@ +--- +title: Advanced Concepts +sidebar_position: 9 +tags: [protocol] +--- + +In this section, you'll learn about the more advanced concepts of the Aztec Network. It is not required to understand these in order to start building on Aztec. + +import DocCardList from '@theme/DocCardList'; + + diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/_category_.json b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/_category_.json new file mode 100644 index 000000000000..537ab73d1a05 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Advanced Storage Concepts", + "position": 1, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/indexed_merkle_tree.mdx b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/indexed_merkle_tree.mdx new file mode 100644 index 000000000000..2452782e609c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/indexed_merkle_tree.mdx @@ -0,0 +1,417 @@ +--- +title: Indexed Merkle Tree (Nullifier Tree) +tags: [storage, concepts, advanced] +sidebar_position: 2 +--- + +import Image from "@theme/IdealImage"; + +## Overview + +This article will introduce the concept of an indexed merkle tree, and how it can be used to improve the performance of nullifier trees in circuits. + +This page will answer: + +- Why we need nullifier trees at all +- How indexed merkle trees work +- How they can be used for membership exclusion proofs +- How they can leverage batch insertions +- Tradeoffs of using indexed merkle trees + +The content was also covered in a presentation for the [Privacy + Scaling Explorations team at the Ethereum Foundation](https://pse.dev/). + + + +## Primer on Nullifier Trees + +Currently the only feasible way to get privacy in public blockchains is via a UTXO model. In this model, state is stored in encrypted UTXO's in merkle trees. However, to maintain privacy, state cannot be updated. The very act of performing an update leaks information. In order to simulate "updating" the state, we "destroy" old UTXO's and create new ones for each state update. Resulting in a merkle tree that is append-only. + +A classic merkle tree: + + + +To destroy state, the concept of a "nullifier" tree is introduced. Typically represented as a sparse Merkle Tree, the structure is utilized to store notes deterministically linked to values in the append-only tree of encrypted notes. + +A sparse merkle tree (not every leaf stores a value): + + + +In order to spend / modify a note in the private state tree, one must create a nullifier for it, and prove that the nullifier does not already exist in the nullifier tree. As nullifier trees are modeled as sparse merkle trees, non membership checks are (conceptually) trivial. + +Data is stored at the leaf index corresponding to its value. E.g. if I have a sparse tree that can contain $2^{256}$ values and want to prove non membership of the value $2^{128}$. I can prove via a merkle membership proof that $tree\_values[2^{128}] = 0$, conversely if I can prove that $tree\_values[2^{128}] == 1$ I can prove that the item exists. + +## Problems introduced by using Sparse Merkle Trees for Nullifier Trees + +While sparse Merkle Trees offer a simple and elegant solution, their implementation comes with some drawbacks. A sparse nullifier tree must have an index for $e \in \mathbb{F}_p$, which for the bn254 curve means that the sparse tree will need to have a depth of 254. +If you're familiar with hashing in circuits the alarm bells have probably started ringing. A tree of depth 254 means 254 hashes per membership proof. +In routine nullifier insertions, a non membership check for a value is performed, then an insertion of said value. This amounts to two trips from leaf to root, hashing all the way up. This means that there are $254*2$ hashes per tree insertion. As the tree is sparse, insertions are random and must be performed in sequence. This means the number of hashes performed in the circuit scales linearly with the number of nullifiers being inserted. As a consequence number of constraints in a rollup circuit (where these checks are performed) will sky rocket, leading to long rollup proving times. + +## Indexed Merkle Tree Constructions + +As it turns out, we can do better. [This paper](https://eprint.iacr.org/2021/1263.pdf) (page 6) introduces the idea of an indexed merkle tree. A Merkle Tree that allows us to perform efficient non-membership proofs. It achieves this by extending each node to include a specialized data structure. Each node not only stores some value $v \in \mathbb{F}_p$ but also some pointers to the leaf with the next higher value. The data structure is as follows: + +$$ +\textsf{leaf} = \{v, i_{\textsf{next}}, v_{\textsf{next}}\}. +$$ + +Based on the tree's insertion rules, we can assume that there are no leaves in the tree that exist between the range $(v, v_{\textsf{next}})$. + +More simply put, the merkle tree pretty much becomes a linked list of increasing size, where once inserted the pointers at a leaf can change, but the nullifier value cannot. + +Despite being a minor modification, the performance implications are massive. We no longer position leaves in place $(index == value)$ therefore we no longer need a deep tree, rather we can use an arbitrarily small tree (32 levels should suffice). Some quick back of the napkin maths can show that insertions can be improved by a factor of 8 $(256 / 32)$. + +_For the remainder of this article I will refer to the node that provides the non membership check as a "low nullifier"._ + +The insertion protocol is described below: + +1. Look for a nullifier's corresponding low_nullifier where: + + $$ + low\_nullifier_{\textsf{next\_value}} > new\_nullifier + $$ + + > if $new\_nullifier$ is the largest use the leaf: + + $$ + low\_nullifier_{\textsf{next\_value}} == 0 + $$ + +2. Perform membership check of the low nullifier. +3. Perform a range check on the low nullifier's value and next_value fields: + +$$ +new\_nullifier > low\_nullifier_{\textsf{value}} \: \&\& \: ( new\_nullifier < low\_nullifier_{\textsf{next\_value}} \: || \: low\_nullifier_{\textsf{next\_value}} == 0 ) +$$ + +4. Update the low nullifier pointers + + $$ + low\_nullifier_{\textsf{next\_index}} = new\_insertion\_index + $$ + + $$ + low\_nullifier_{\textsf{next\_value}} = new\_nullifier + $$ + +5. Perform insertion of new updated low nullifier (yields new root) +6. Update pointers on new leaf. Note: low_nullifier is from before update in step 4 + +$$ +new\_nullifier\_leaf_{\textsf{value}} = new\_nullifier +$$ + +$$ +new\_nullifier\_leaf_{\textsf{next\_value}} = low\_nullifier_{\textsf{next\_value}} +$$ + +$$ +new\_nullifier\_leaf_{\textsf{next\_index}} = low\_nullifier_{\textsf{next\_index}} +$$ + +7. Perform insertion of new leaf (yields new root) + +#### Number of insertion constraints, in total: + +- `3n` hashes of 2 field elements (where `n` is the height of the tree). +- `3` hashes of 3 field elements. +- `2` range checks. +- A handful of equality constraints. + +**Special cases** +You'll notice at step 3 the $low\_nullifier_{\textsf{next\_value}}$ can be 0. This is a special case as if a value is the max, it will not point to anything larger (as it does not exist). Instead it points to zero. By doing so we close the loop, so we are always inserting into a ring, if we could insert outside the ring we could cause a split. + +A visual aid for insertion is presented below: + +1. Initial state + + + +2. Add a new value $v=30$ + + + +3. Add a new value $v=10$ + + + +4. Add a new value $v=20$ + + + +5. Add a new value $v=50$ + + + +By studying the transitions between each diagram you can see how the pointers are updated between each insertion. + +A further implementation detail is that we assume the first 0 node is pre-populated. As a consequence, the first insertion into the tree will be made into the second index. + +### Non-membership proof + +Suppose we want to show that the value `20` doesn't exist in the tree. We just reveal the leaf which 'steps over' `20`. I.e. the leaf whose value is less than `20`, but whose next value is greater than `20`. Call this leaf the `low_nullifier`. + +- hash the low nullifier: $low\_nullifier = h(10, 1, 30)$. +- Prove the low leaf exists in the tree: `n` hashes. +- Check the new value 'would have' belonged in the range given by the low leaf: `2` range checks. + - If ($low\_nullifier_{\textsf{next\_index}} == 0$): + - Special case, the low leaf is at the very end, so the new_value must be higher than all values in the tree: + - $assert(low\_nullifier_{\textsf{value}} < new\_value_{\textsf{value}})$ + - Else: + - $assert(low\_nullifier_{\textsf{value}} < new\_value_{\textsf{value}})$ + - $assert(low\_nullifier_{\textsf{next\_value}} > new\_value_{\textsf{value}})$ + +This is already a massive performance improvement, however we can go further, as this tree is not sparse. We can perform batch insertions. + +## Batch insertions + +As our nullifiers will all be inserted deterministically (append only), we can insert entire subtrees into our tree rather than appending nodes one by one, this optimization is globally applied to append only merkle trees. I wish this was it, but for every node we insert, we must also update low nullifier pointers, this introduces a bit of complexity while performing subtree insertions, as the low nullifier itself may exist within the subtree we are inserting - we must be careful how we prove these sort of insertions are correct (addressed later). +We must update all of the impacted low nullifiers before. + +First we will go over batch insertions in an append only merkle tree. + +1. First we prove that the subtree we are inserting into consists of all empty values. +1. We work out the root of an empty subtree, and perform an inclusion proof for an empty root, which proves that there is nothing within our subtree. +1. We re-create our subtree within our circuit. +1. We then use the same sibling path the get the new root of the tree after we insert the subtree. + +In the following example we insert a subtree of size 4 into our tree at step 4. above. Our subtree is greyed out as it is "pending". + +**Legend**: + +- Green: New Inserted Value +- Orange: Low Nullifier + +**Example** + +1. Prepare to insert subtree $[35,50,60,15]$ + + + +2. Update low nullifier for new nullifier $35$. + + + +3. Update low nullifier for new nullifier $50$. (Notice how the low nullifier exists within our pending insertion subtree, this becomes important later). + + + +4. Update low nullifier for new nullifier $60$. + + + +5. Update low nullifier for new nullifier $15$. + + + +6. Update pointers for new nullifier $15$. + + + +7. Insert subtree. + + + +### Performance gains from subtree insertion + +Let's go back over the numbers: +Insertions into a sparse nullifier tree involve 1 non membership check (254 hashes) and 1 insertion (254 hashes). If we were performing insertion for 4 values that would entail 2032 hashes. +In the depth 32 indexed tree construction, each subtree insertion costs 1 non membership check (32 hashes), 1 pointer update (32 hashes) for each value as well as the cost of constructing and inserting a subtree (~67 hashes. Which is 327 hashes, an incredible efficiency increase.) + +_I am ignoring range check constraint costs as they a negligible compared to the costs of a hash_. + +## Performing subtree insertions in a circuit context + +Some fun engineering problems occur when we inserting a subtree in circuits when the low nullifier for a value exists within the subtree we are inserting. In this case we cannot perform a non membership check against the root of the tree, as our leaf that we would use for non membership has NOT yet been inserted into the tree. We need another protocol to handle such cases, we like to call these "pending" insertions. + +**Circuit Inputs** + +- `new_nullifiers`: `fr[]` +- `low_nullifier_leaf_preimages`: `tuple of {value: fr, next_index: fr, next_value: fr}` +- `low_nullifier_membership_witnesses`: A sibling path and a leaf index of low nullifier +- `current_nullifier_tree_root`: Current root of the nullifier tree +- `next_insertion_index`: fr, the tip our nullifier tree +- `subtree_insertion_sibling_path`: A sibling path to check our subtree against the root + +Protocol without batched insertion: +Before adding a nullifier to the pending insertion tree, we check for its non membership using the previously defined protocol by consuming the circuit inputs: +Pseudocode: + +```cpp + +auto empty_subtree_hash = SOME_CONSTANT_EMPTY_SUBTREE; +auto pending_insertion_subtree = []; +auto insertion_index = inputs.next_insertion_index; +auto root = inputs.current_nullifier_tree_root; + +// Check nothing exists where we would insert our subtree +assert(membership_check(root, empty_subtree_hash, insertion_index >> subtree_depth, inputs.subtree_insertion_sibling_path)); + +for (i in len(new_nullifiers)) { + auto new_nullifier = inputs.new_nullifiers[i]; + auto low_nullifier_leaf_preimage = inputs.low_nullifier_leaf_preimages[i]; + auto low_nullifier_membership_witness = inputs.low_nullifier_membership_witnesses[i]; + + // Membership check for low nullifier + assert(perform_membership_check(root, hash(low_nullifier_leaf_preimage), low_nullifier_membership_witness)); + + // Range check low nullifier against new nullifier + assert(new_nullifier < low_nullifier_leaf_preimage.next_value || low_nullifier_leaf.next_value == 0); + assert(new_nullifier > low_nullifier_leaf_preimage.value); + + // Update new nullifier pointers + auto new_nullifier_leaf = { + .value = new_nullifier, + .next_index = low_nullifier_preimage.next_index, + .next_value = low_nullifier_preimage.next_value + }; + + // Update low nullifier pointers + low_nullifier_preimage.next_index = next_insertion_index; + low_nullifier_preimage.next_value = new_nullifier; + + // Update state vals for next iteration + root = update_low_nullifier(low_nullifier, low_nullifier_membership_witness); + pending_insertion_subtree.push(new_nullifier_leaf); + next_insertion_index += 1; +} + +// insert subtree +root = insert_subtree(root, inputs.next_insertion_index >> subtree_depth, pending_insertion_subtree); + +``` + +From looking at the code above we can probably deduce why we need pending insertion. If the low nullifier does not yet exist in the tree, all of our membership checks will fail, we cannot produce a non membership proof. + +To perform batched insertions, our circuit must keep track of all values that are pending insertion. + +- If the `low_nullifier_membership_witness` is identified to be nonsense ( all zeros, or has a leaf index of -1 ) we will know that this is a pending low nullifier read request and we will have to look within our pending subtree for the nearest low nullifier. + - Loop back through all "pending_insertions" + - If the pending insertion value is lower than the nullifier we are trying to insert + - If the pending insertion value is NOT found, then out circuit is invalid and should self abort. + +The updated pseudocode is as follows: + +```cpp + +auto empty_subtree_hash = SOME_CONSTANT_EMPTY_SUBTREE; +auto pending_insertion_subtree = []; +auto insertion_index = inputs.next_insertion_index; +auto root = inputs.current_nullifier_tree_root; + +// Check nothing exists where we would insert our subtree +assert(membership_check(root, empty_subtree_hash, insertion_index >> subtree_depth, inputs.subtree_insertion_sibling_path)); + +for (i in len(new_nullifiers)) { + auto new_nullifier = inputs.new_nullifiers[i]; + auto low_nullifier_leaf_preimage = inputs.low_nullifier_leaf_preimages[i]; + auto low_nullifier_membership_witness = inputs.low_nullifier_membership_witnesses[i]; + + if (low_nullifier_membership_witness is garbage) { + bool matched = false; + + // Search for the low nullifier within our pending insertion subtree + for (j in range(0, i)) { + auto pending_nullifier = pending_insertion_subtree[j]; + + if (pending_nullifier.is_garbage()) continue; + if (pending_nullifier[j].value < new_nullifier && (pending_nullifier[j].next_value > new_nullifier || pending_nullifier[j].next_value == 0)) { + + // bingo + matched = true; + + // Update pointers + auto new_nullifier_leaf = { + .value = new_nullifier, + .next_index = pending_nullifier.next_index, + .next_value = pending_nullifier.next_value + } + + // Update pending subtree + pending_nullifier.next_index = insertion_index; + pending_nullifier.next_value = new_nullifier; + + pending_insertion_subtree.push(new_nullifier_leaf); + break; + } + } + + // could not find a matching low nullifier in the pending insertion subtree + assert(matched); + + } else { + // Membership check for low nullifier + assert(perform_membership_check(root, hash(low_nullifier_leaf_preimage), low_nullifier_membership_witness)); + + // Range check low nullifier against new nullifier + assert(new_nullifier < low_nullifier_leaf_preimage.next_value || low_nullifier_leaf.next_value == 0); + assert(new_nullifier > low_nullifier_leaf_preimage.value); + + // Update new nullifier pointers + auto new_nullifier_leaf = { + .value = new_nullifier, + .next_index = low_nullifier_preimage.next_index, + .next_value = low_nullifier_preimage.next_value + }; + + // Update low nullifier pointers + low_nullifier_preimage.next_index = next_insertion_index; + low_nullifier_preimage.next_value = new_nullifier; + + // Update state vals for next iteration + root = update_low_nullifier(low_nullifier, low_nullifier_membership_witness); + + pending_insertion_subtree.push(new_nullifier_leaf); + + } + + next_insertion_index += 1; +} + +// insert subtree +root = insert_subtree(root, inputs.next_insertion_index >> subtree_depth, pending_insertion_subtree); + +``` + +#### Drawbacks + +Despite offering large performance improvements within the circuits, these come at the expense of increased computation / storage performed by the node. To provide a non membership proof we must find the "low nullifier" for it. In a naive implementation this entails a brute force search against the existing nodes in the tree. This performance can be increased by the node maintaining a sorted data structure of existing nullifiers, increasing its storage footprint. + +#### Closing Notes + +We have been working with these new trees in order to reduce the proving time for our rollups in Aztec, however we think EVERY protocol leveraging nullifier trees should know about these trees as their performance benefit is considerable. diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/note_discovery.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/note_discovery.md new file mode 100644 index 000000000000..c916d6c0c40c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/note_discovery.md @@ -0,0 +1,45 @@ +--- +title: Note Discovery +tags: [storage, concepts, advanced, notes] +sidebar_position: 3 +--- + +Note discovery refers to the process of a user identifying and decrypting the [encrypted notes](../../storage/notes.md) that belong to them. + +## Existing solutions + +### Brute force / trial-decrypt + +In some existing protocols, the user downloads all possible notes and tries to decrypt each one. If the decryption succeeds, the user knows they own that note. However, this approach becomes exponentially more expensive as the network grows and more notes are created. It also introduces a third-party server to gather and trial-decrypt notes, which is an additional point of failure. Note that this note discovery technique is not currently implemented for Aztec. + +### Off-chain communication + +Another proposed solution is having the sender give the note content to the recipient via some off-chain communication. While it solves the brute force issue, it introduces reliance on side channels which we don't want in a self-sufficient network. This option incurs lower transaction costs because fewer logs needs to be posted on-chain. Aztec apps will be able to choose this method if they wish. + +## Aztec's solution: Note tagging + +Aztec introduces an approach that allows users to identify which notes are relevant to them by having the sender *tag* the log in which the note is created. This is known as note tagging. The tag is generated in such a way that only the sender and recipient can identify it. + +### How it works + +#### Every log has a tag + +In Aztec, each emitted log is an array of fields, eg `[x, y, z]`. The first field (`x`) is a *tag* field used to index and identify logs. The Aztec node exposes an API `getLogsByTags()` that can retrieve logs matching specific tags. + +#### Tag generation + +The sender and recipient share a predictable scheme for generating tags. The tag is derived from a shared secret and an index (a shared counter that increments each time the sender creates a note for the recipient). + +#### Discovering notes in Aztec contracts + +This note discovery scheme will be implemented by Aztec contracts rather than by the PXE. This means that users can update or use other types of note discovery to suit their needs. + +### Limitations + +- **You cannot receive notes from an unknown sender**. If you do not know the sender’s address, you cannot create the shared secret, and therefore cannot create the note tag. There are potential ways around this, such as senders adding themselves to a contract and then recipients searching for note tags from all the senders in the contract. However this is out of scope at this point in time. + +- **Index synchronization can be complicated**. If transactions are reverted or mined out of order, the recipient may stop searching after receiving a tag with the latest index they expect. This means they can miss notes that belong to them. We cannot redo a reverted a transaction with the same index, because then the tag will be the same and the notes will be linked, leaking privacy. We can solve this by widening the search window (not too much, or it becomes brute force) and implementing a few restrictions on sending notes. A user will not be able to send a large amount of notes from the same contract to the same recipient within a short time frame. + +## Further reading + +- [How partial notes are discovered](./partial_notes.md#note-discovery) \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/partial_notes.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/partial_notes.md new file mode 100644 index 000000000000..150926943cf7 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/partial_notes.md @@ -0,0 +1,215 @@ +--- +title: Partial Notes [OUTDATED DOCS] +description: Describes how partial notes are used in Aztec +tags: [notes, storage] +sidebar_position: 4 +--- + +:::warning OUTDATED DOCUMENTATION +This documentation is outdated and may not reflect the current state of the Aztec protocol. This is to be updated when tackling [this issue](https://github.com/AztecProtocol/aztec-packages/issues/12414). +TODO(#12414): UPDATE THIS +::: + +Partial notes are a concept that allows users to commit to an encrypted value, and allows a counterparty to update that value without knowing the specific details of the encrypted value. + +## Use cases + +Why is this useful? + +Consider the case where a user wants to pay for a transaction fee, using a fee-payment contract and they want to do this privately. They can't be certain what the transaction fee will be because the state of the network will have progressed by the time the transaction is processed by the sequencer, and transaction fees are dynamic. So the user can commit to a value for the transaction fee, publicly post this commitment, the fee payer (aka paymaster) can update the public commitment, deducting the final cost of the transaction from the commitment and returning the unused value to the user. + +So, in general, the user is: + +- doing some computation in private +- encrypting/compressing that computation with a point +- passing that point as an argument to a public function + +And the paymaster is: + +- updating that point in public +- treating/emitting the result(s) as a note hash(es) + +The idea of committing to a value and allowing a counterparty to update that value without knowing the specific details of the encrypted value is a powerful concept that can be used in many different applications. For example, this could be used for updating timestamp values in private, without revealing the exact timestamp, which could be useful for many defi applications. + +To do this, we leverage the following properties of elliptic curve operations: + +1. `x_1 * G + x_2 * G` equals `(x_1 + x_2) * G` and +2. `f(x) = x * G` being a one-way function. + +Property 1 allows us to be continually adding to a point on elliptic curve and property 2 allows us to pass the point to a public realm without revealing anything about the point preimage. + +### DEXes + +Currently private swaps require 2 transactions. One to start the swap and another to claim the swapped token from the DEX. With partial notes, you can create a note with zero value for the received amount and have another party complete it later from a public function, with the final swapped amount. This reduces the number of transactions needed to swap privately. + +Comparing to the flow above, the user is doing some private computation to stage the swap, encrypting the computation with a point and passing the point as an argument to a public function. Then another party is updating that point in public and emitting the result as a note hash for the user doing the swap. + +### Lending + +A similar pattern can be used for a lending protocol. The user can deposit a certain amount of a token to the lending contract and create a partial note for the borrowed token that will be completed by another party. This reduces the number of required transactions from 2 to 1. + +### Private Refunds + +Private transaction refunds from paymasters are the original inspiration for partial notes. Without partial notes, you have to claim your refund note. But the act of claiming itself needs gas! What if you overpaid fees on the refund tx? Then you have another 2nd order refund that you need to claim. This creates a never ending cycle! Partial notes allow paymasters to refund users without the user needing to claim the refund. + +Before getting to partial notes let's recap what is the flow of standard notes. + +## Note lifecycle recap + +The standard note flow is as follows: + +1. Create a note in your contract, +2. compute the note hash, +3. emit the note hash, +4. emit the note (note hash preimage) as an encrypted note log, +5. sequencer picks up the transaction, includes it in a block (note hash gets included in a note hash tree) and submits the block on-chain, +6. nodes and PXEs following the network pick up the new block, update its internal state and if they have accounts attached they search for relevant encrypted note logs, +7. if a users PXE finds a log it stores the note in its database, +8. later on when we want to spend a note, a contract obtains it via oracle and stores a note hash read request within the function context (note hash read request contains a newly computed note hash), +9. based on the note and a nullifier secret key a nullifier is computed and emitted, +10. protocol circuits check that the note is a valid note by checking that the note hash read request corresponds to a real note in the note hash tree and that the new nullifier does not yet exist in the nullifier tree, +11. if the conditions in point 10. are satisfied the nullifier is inserted into the nullifier tree and the note is at the end of its life. + +Now let's do the same for partial notes. + +## Partial notes life cycle + +1. Create a partial/unfinished note in a private function of your contract --> partial here means that the values within the note are not yet considered finalized (e.g. `amount` in a `UintNote`), +2. compute a partial note commitment of the partial note using a multi scalar multiplication on an elliptic curve. For `UintNote` this would be done as `G_amt * amount0 + G_npk * npk_m_hash + G_rnd * randomness + G_slot * slot`, where each `G_` is a generator point for a specific field in the note, +3. emit partial note log, +4. pass the partial note commitment to a public function, +5. in a public function determine the value you want to add to the note (e.g. adding a value to an amount) and add it to the partial note commitment (e.g. `NOTE_HIDING_POINT + G_amt * amount`), +6. get the note hash by finalizing the partial note commitment (the note hash is the x coordinate of the point), +7. emit the note hash, +8. emit the value added to the note in public as an unencrypted log (PXE then matches it with encrypted partial note log emitted from private), +9. from this point on the flow of partial notes is the same as for normal notes. + +### Private Fee Payment Example + +Alice wants to use a fee-payment contract for fee abstraction, and wants to use private balances. That is, she wants to pay the FPC (fee-payment contract) some amount in an arbitrary token privately (e.g. a stablecoin), and have the FPC pay the `transaction_fee`. + +Alice also wants to get her refund privately in the same token (e.g. the stablecoin). + +The trouble is that the FPC doesn't know if Alice is going to run public functions, in which case it doesn't know what refund is due until the end of public execution. + +And we can't use the normal flow to create a transaction fee refund note for Alice, since that demands we have Alice's address in public. + +So we define a new type of note with its `compute_partial_commitment` defined as: + +$$ +\text{amount}*G_{amount} + \text{address}*G_{address} + \text{randomness}*G_{randomness} + \text{slot}*G_{slot} +$$ + +Suppose Alice is willing to pay up to a set amount in stablecoins for her transaction. (Note, this amount gets passed into public so that when `transaction_fee` is known the FPC can verify that it isn't losing money. Wallets are expected to choose common values here, e.g. powers of 10). + +Then we can subtract the set amount from Alice's balance of private stablecoins, and create a point in private like: + +$$ +P_a' := \text{alice address}*G_{address} + \text{rand}_a*G_{randomness} + \text{Alice note slot}*G_{slot} +$$ + +We also need to create a point for the owner of the FPC (whom we call Bob) to receive the transaction fee, which will also need randomness. + +So in the contract we compute $\text{rand}_b := h(\text{rand}_a, \text{msg sender})$. + +:::warning +We need to use different randomness for Bob's note here to avoid potential privacy leak (see [description](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L491) of `setup_refund` function) +::: + +$$ +P_b' := \text{bob address}*G_{address} + \text{rand}_b*G_{randomness} + \text{Bob note slot}*G_{slot} +$$ + +Here, the $P'$s "partially encode" the notes that we are _going to create_ for Alice and Bob. So we can use points as "Partial Notes". + +We pass these points and the funded amount to public, and at the end of public execution, we compute tx fee point $P_{fee} := (\text{transaction fee}) * G_{amount}$ and refund point $P_{refund} := (\text{funded amount} - \text{transaction fee}) * G_{amount}$ + +Then, we arrive at the point that corresponds to the complete note by + +$$ +P_a := P_a'+P_{refund} = (\text{funded amount} - \text{transaction fee})*G_{amount} + \text{alice address}*G_{address} +\text{rand}_a*G_{randomness} + \text{Alice note slot}*G_{slot} +$$ + +$$ +P_b := P_b'+P_{fee} = (\text{transaction fee})*G_{amount} + \text{bob address}*G_{address} +\text{rand}_b*G_{randomness} + \text{Bob note slot}*G_{slot} +$$ + +Then we just emit `P_a.x` and `P_b.x` as a note hashes, and we're done! + +### Private Fee Payment Implementation + +TODO(#12414): `setup_refund` no longer exists. + +We can see the complete implementation of creating and completing partial notes in an Aztec contract in the `setup_refund` and `complete_refund` functions. + +#### `fee_entrypoint_private` + +```rust title="fee_entrypoint_private" showLineNumbers +#[private] +fn fee_entrypoint_private(max_fee: u128, authwit_nonce: Field) { + let accepted_asset = storage.config.read().accepted_asset; + + let user = context.msg_sender(); + let token = Token::at(accepted_asset); + + // TODO(#10805): Here we should check that `max_fee` converted to fee juice is enough to cover the tx + // fee juice/mana/gas limit. Currently the fee juice/AA exchange rate is fixed 1:1. + + // Pull the max fee from the user's balance of the accepted asset to the public balance of this contract. + token.transfer_to_public(user, context.this_address(), max_fee, authwit_nonce).call( + &mut context, + ); + + // Prepare a partial note for the refund for the user. + let partial_note = token.prepare_private_balance_increase(user, user).call(&mut context); + + // Set a public teardown function in which the refund will be paid back to the user by finalizing the partial note. + FPC::at(context.this_address()) + ._complete_refund(accepted_asset, partial_note, max_fee) + .set_as_teardown(&mut context); + + // Set the FPC as the fee payer of the tx. + context.set_as_fee_payer(); +} +``` +> Source code: noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr#L78-L105 + + +The `fee_entrypoint_private` function sets the `complete_refund` function to be called at the end of the public function execution (`set_public_teardown_function`). +This ensures that the refund partial note will be completed for the user. + +#### `complete_refund` + +```rust title="complete_refund" showLineNumbers +#[public] +#[internal] +fn _complete_refund( + accepted_asset: AztecAddress, + partial_note: PartialUintNote, + max_fee: u128, +) { + let tx_fee = safe_cast_to_u128(context.transaction_fee()); + + // 1. Check that user funded the fee payer contract with at least the transaction fee. + // TODO(#10805): Nuke this check once we have a proper max_fee check in the fee_entrypoint_private. + assert(max_fee >= tx_fee, "max fee not enough to cover tx fee"); + + // 2. Compute the refund amount as the difference between funded amount and the tx fee. + // TODO(#10805): Introduce a real exchange rate + let refund_amount = max_fee - tx_fee; + + Token::at(accepted_asset).finalize_transfer_to_private(refund_amount, partial_note).call( + &mut context, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr#L109-L131 + + +## Note discovery + +Note discovery is detailed [here](./note_discovery.md). Partial notes are handled very similarly, where the partial note creator will add a tag to the beginning of the encrypted log, like a regular note, but the contract knows that it is a partial note and is missing a public component. The contract puts the unfinished note and the public log that will be emitted to complete the note into the PXE's database. When the public log is emitted, the recipient's PXE has all of the info about the note and the partial note is completed. This method of discovery allows partial notes to be started and completed in separate transactions. + +## Future work + +This pattern of making public commitments to notes that can be modified by another party, privately, can be generalized to work with different kinds of applications. The Aztec labs team is working on adding libraries and tooling to make this easier to implement in your own contracts. diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/storage_slots.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/storage_slots.md new file mode 100644 index 000000000000..3b86951dea8f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/advanced/storage/storage_slots.md @@ -0,0 +1,60 @@ +--- +title: Storage Slots +tags: [storage, concepts, advanced] +sidebar_position: 1 +--- + +# Storage Slots + +## Public State Slots + +As mentioned in [State Model](../../storage/state_model.md), Aztec public state behaves similarly to public state on Ethereum from the point of view of the developer. Behind the scenes however, the storage is managed differently. As mentioned, public state has just one large sparse tree in Aztec - so we silo slots of public data by hashing it together with its contract address. + +The mental model is that we have a key-value store, where the siloed slot is the key, and the value is the data stored in that slot. You can think of the `real_storage_slot` identifying its position in the tree, and the `logical_storage_slot` identifying the position in the contract storage. + +```rust +real_storage_slot = H(contract_address, logical_storage_slot) +``` + +The siloing is performed by the [Kernel circuits](../circuits/kernels/private_kernel.md). + +For structs and arrays, we are logically using a similar storage slot computation to ethereum, e.g., as a struct with 3 fields would be stored in 3 consecutive slots. However, because the "actual" storage slot is computed as a hash of the contract address and the logical storage slot, the actual storage slot is not consecutive. + +## Private State Slots + +Private storage is a different beast. As you might remember from [Hybrid State Model](../../storage/state_model.md), private state is stored in encrypted logs and the corresponding private state commitments in append-only tree, called the note hash tree where each leaf is a commitment. Append-only means that leaves are never updated or deleted; instead a nullifier is emitted to signify that some note is no longer valid. A major reason we used this tree, is that updates at a specific storage slot would leak information in the context of private state, even if the value is encrypted. That is not good privacy. + +Following this, the storage slot as we know it doesn't really exist. The leaves of the note hashes tree are just commitments to content (think of it as a hash of its content). + +Nevertheless, the concept of a storage slot is very useful when writing applications, since it allows us to reason about distinct and disjoint pieces of data. For example we can say that the balance of an account is stored in a specific slot and that the balance of another account is stored in another slot with the total supply stored in some third slot. By making sure that these slots are disjoint, we can be sure that the balances are not mixed up and that someone cannot use the total supply as their balance. + +### Implementation + +If we include the storage slot, as part of the note whose commitment is stored in the note hashes tree, we can _logically link_ all the notes that make up the storage slot. For the case of a balance, we can say that the balance is the sum of all the notes that have the same storage slot - in the same way that your physical wallet balance is the sum of all the physical notes in your wallet. + +Similarly to how we siloed the public storage slots, we can silo our private storage by hashing the packed note together with the logical storage slot. + +```rust +note_hash = H([...packed_note, logical_storage_slot]); +``` + +Note hash siloing is done in the application circuit, since it is not necessary for security of the network (but only the application). +:::info +The private variable wrappers `PrivateSet` and `PrivateMutable` in Aztec.nr include the `logical_storage_slot` in the commitments they compute, to make it easier for developers to write contracts without having to think about how to correctly handle storage slots. +::: + +When reading the values for these notes, the application circuit can then constrain the values to only read notes with a specific logical storage slot. + +To ensure that contracts can only modify their own logical storage, we do a second siloing by hashing the `commitment` with the contract address. + +```rust +siloed_note_hash = H(contract_address, note_hash); +``` + +By doing this address-siloing at the kernel circuit we _force_ the inserted commitments to include and not lie about the `contract_address`. + +:::info +To ensure that nullifiers don't collide across contracts we also force this contract siloing at the kernel level. +::: + +For an example of this see [developer documentation on storage](../../../../developers/reference/smart_contract_reference/storage/index.md). diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/call_types.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/call_types.md new file mode 100644 index 000000000000..f22b05cc8c6a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/call_types.md @@ -0,0 +1,280 @@ +--- +title: Call Types +sidebar_position: 6 +tags: [calls, contracts, execution] +--- + +## What is a Call + +We say that a smart contract is called when one of its functions is invoked and its code is run. This means there'll be: + +- a caller +- arguments +- return values +- a call status (successful or failed) + +There are multiple types of calls, and some of the naming can make things **very** confusing. This page lists the different call types and execution modes, pointing out key differences between them. + +## Ethereum Call Types + +Even though we're discussing Aztec, its design is heavily influenced by Ethereum and many of the APIs and concepts are quite similar. It is therefore worthwhile to briefly review how things work there and what naming conventions are used to provide context to the Aztec-specific concepts. + +Broadly speaking, Ethereum contracts can be thought of as executing as a result of three different things: running certain EVM opcodes, running Solidity code (which compiles to EVM opcodes), or via the node JSON-RPC interface (e.g. when executing transactions). + +### EVM + +Certain opcodes allow contracts to make calls to other contracts, each with different semantics. We're particularly interested in `CALL` and `STATICCALL`, and how those relate to contract programming languages and client APIs. + +#### `CALL` + +This is the most common and basic type of call. It grants execution control to the caller until it eventually returns. No special semantics are in play here. Most Ethereum transactions spend the majority of their time in `CALL` contexts. + +#### `STATICCALL` + +This behaves almost exactly the same as `CALL`, with one key difference: any state-changing operations are forbidden and will immediately cause the call to fail. This includes writing to storage, emitting logs, or deploying new contracts. This call is used to query state on an external contract, e.g. to get data from a price oracle, check for access control permissions, etc. + +#### Others + +The `CREATE` and `CREATE2` opcodes (for contract deployment) also result in something similar to a `CALL` context, but all that's special about them has to do with how deployments work. `DELEGATECALL` (and `CALLCODE`) are somewhat complicated to understand but don't have any Aztec equivalents, so they are not worth covering. + +### Solidity + +Solidity (and other contract programming languages such as Vyper) compile down to EVM opcodes, but it is useful to understand how they map language concepts to the different call types. + +#### Mutating External Functions + +These are functions marked `payable` (which can receive ETH, which is a state change) or with no mutability declaration (sometimes called `nonpayable`). When one of these functions is called on a contract, the `CALL` opcode is emitted, meaning the callee can perform state changes, make further `CALL`s, etc. + +It is also possible to call such a function with `STATICCALL` manually (e.g. using assembly), but the execution will fail as soon as a state-changing opcode is executed. + +#### `view` + +An external function marked `view` will not be able to mutate state (write to storage, etc.), it can only _view_ the state. Solidity will emit the `STATICCALL` opcode when calling these functions, since its restrictions provide added safety to the caller (e.g. no risk of reentrancy). + +Note that it is entirely possible to use `CALL` to call a `view` function, and the result will be the exact same as if `STATICCALL` had been used. The reason why `STATICCALL` exists is so that _untrusted or unknown_ contracts can be called while still being able to reason about correctness. From the [EIP](https://eips.ethereum.org/EIPS/eip-214): + +> '`STATICCALL` adds a way to call other contracts and restrict what they can do in the simplest way. It can be safely assumed that the state of all accounts is the same before and after a static call.' + +### JSON-RPC + +From outside the EVM, calls to contracts are made via [JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/) methods, typically from some client library that is aware of contract ABIs, such as [ethers.js](https://docs.ethers.org/v5) or [viem](https://viem.sh/). + +#### `eth_sendTransaction` + +This method is how transactions are sent to a node to get them to be broadcast and eventually included in a block. The specified `to` address will be called in a `CALL` context, with some notable properties: + +- there are no return values, even if the contract function invoked does return some data +- there is no explicit caller: it is instead derived from a provided signature + +Some client libraries choose to automatically issue `eth_sendTransaction` when calling functions from a contract ABI that are not marked as `view` - [ethers is a good example](https://docs.ethers.org/v5/getting-started/#getting-started--writing). Notably, this means that any return value is lost and not available to the calling client - the library typically returns a transaction receipt instead. If the return value is required, then the only option is to simulate the call `eth_call`. + +Note that it is possible to call non state-changing functions (i.e. `view`) with `eth_sendTransaction` - this is always meaningless. What transactions do is change the blockchain state, so all calling such a function achieves is for the caller to lose funds by paying for gas fees. The sole purpose of a `view` function is to return data, and `eth_sendTransaction` does not make the return value available. + +#### `eth_call` + +This method is the largest culprit of confusion around calls, but unfortunately requires understanding of all previous concepts in order to be explained. Its name is also quite unhelpful. + +What `eth_call` does is simulate a transaction (a call to a contract) given the current blockchain state. The behavior will be the exact same as `eth_sendTransaction`, except: + +- no actual transaction will be created +- while gas _will_ be measured, there'll be no transaction fees of any kind +- no signature is required: the `from` address is passed directly, and can be set to any value (even if the private key is unknown, or if they are contract addresses!) +- the return value of the called contract is available + +`eth_call` is typically used for one of the following: + +- query blockchain data, e.g. read token balances +- preview the state changes produced by a transaction, e.g. the transaction cost, token balance changes, etc + +Because some libraries ([such as ethers](https://docs.ethers.org/v5/getting-started/#getting-started--reading)) automatically use `eth_call` for `view` functions (which when called via Solidity result in the `STATICCALL` opcode), these concepts can be hard to tell apart. The following bears repeating: **an `eth_call`'s call context is the same as `eth_sendTransaction`, and it is a `CALL` context, not `STATICCALL`.** + +## Aztec Call Types + +Large parts of the Aztec Network's design are still not finalized, and the nitty-gritty of contract calls is no exception. This section won't therefore contain a thorough review of these, but rather list some of the main ways contracts can currently be interacted with, with analogies to Ethereum call types when applicable. + +While Ethereum contracts are defined by bytecode that runs on the EVM, Aztec contracts have multiple modes of execution depending on the function that is invoked. + +### Private Execution + +Contract functions marked with `#[private]` can only be called privately, and as such 'run' in the user's device. Since they're circuits, their 'execution' is actually the generation of a zk-SNARK proof that'll later be sent to the sequencer for verification. + +#### Private Calls + +Private functions from other contracts can be called either regularly or statically by using the `.call()` and `.static_call` functions. They will also be 'executed' (i.e. proved) in the user's device, and `static_call` will fail if any state changes are attempted (like the EVM's `STATICCALL`). + +```rust title="private_call" showLineNumbers +let _ = Token::at(stable_coin).burn_private(from, amount, authwit_nonce).call(&mut context); +``` +> Source code: noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L254-L256 + + +Unlike the EVM however, private execution doesn't revert in the traditional way: in case of error (e.g. a failed assertion, a state changing operation in a static context, etc.) the proof generation simply fails and no transaction request is generated, spending no network gas or user funds. + +#### Public Calls + +Since public execution can only be performed by the sequencer, public functions cannot be executed in a private context. It is possible however to _enqueue_ a public function call during private execution, requesting the sequencer to run it during inclusion of the transaction. It will be [executed in public](#public-execution) normally, including the possibility to enqueue static public calls. + +Since the public call is made asynchronously, any return values or side effects are not available during private execution. If the public function fails once executed, the entire transaction is reverted including state changes caused by the private part, such as new notes or nullifiers. Note that this does result in gas being spent, like in the case of the EVM. + +```rust title="enqueue_public" showLineNumbers +Lending::at(context.this_address()) + ._deposit(AztecAddress::from_field(on_behalf_of), amount, collateral_asset) + .enqueue(&mut context); +``` +> Source code: noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L119-L123 + + +It is also possible to create public functions that can _only_ be invoked by privately enqueueing a call from the same contract, which can be very useful to update public state after private execution (e.g. update a token's supply after privately minting). This is achieved by annotating functions with `#[internal]`. + +A common pattern is to enqueue public calls to check some validity condition on public state, e.g. that a deadline has not expired or that some public value is set. + +```rust title="enqueueing" showLineNumbers +Router::at(ROUTER_ADDRESS).check_block_number(operation, value).call(context); +``` +> Source code: noir-projects/noir-contracts/contracts/protocol/router_contract/src/utils.nr#L17-L19 + + +Note that this reveals what public function is being called on what contract, and perhaps more importantly which contract enqueued the call during private execution. +For this reason we've created a canonical router contract which implements some of the checks commonly performed: this conceals the calling contract, as the `context.msg_sender()` in the public function will be the router itself (since it is the router that enqueues the public call). + +An example of how a deadline can be checked using the router contract follows: + +```rust title="call-check-deadline" showLineNumbers +privately_check_timestamp(Comparator.LT, config.deadline, &mut context); +``` +> Source code: noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr#L64-L66 + + +`privately_check_timestamp` and `privately_check_block_number` are helper functions around the call to the router contract: + +```rust title="helper_router_functions" showLineNumbers +/// Asserts that the current timestamp in the enqueued public call enqueued by `check_timestamp` satisfies +/// the `operation` with respect to the `value. Preserves privacy by performing the check via the router contract. +/// This conceals an address of the calling contract by setting `context.msg_sender` to the router contract address. +pub fn privately_check_timestamp(operation: u8, value: u64, context: &mut PrivateContext) { + Router::at(ROUTER_ADDRESS).check_timestamp(operation, value).call(context); +} + +/// Asserts that the current block number in the enqueued public call enqueued by `check_block_number` satisfies +/// the `operation` with respect to the `value. Preserves privacy by performing the check via the router contract. +/// This conceals an address of the calling contract by setting `context.msg_sender` to the router contract address. +pub fn privately_check_block_number(operation: u8, value: u32, context: &mut PrivateContext) { + Router::at(ROUTER_ADDRESS).check_block_number(operation, value).call(context); +} +``` +> Source code: noir-projects/noir-contracts/contracts/protocol/router_contract/src/utils.nr#L5-L21 + + +This is what the implementation of the check timestamp functionality looks like: + +```rust title="check_timestamp" showLineNumbers +/// Asserts that the current timestamp in the enqueued public call satisfies the `operation` with respect +/// to the `value. +#[private] +fn check_timestamp(operation: u8, value: u64) { + Router::at(context.this_address())._check_timestamp(operation, value).enqueue_view( + &mut context, + ); +} + +#[public] +#[internal] +#[view] +fn _check_timestamp(operation: u8, value: u64) { + let lhs_field = context.timestamp() as Field; + let rhs_field = value as Field; + assert(compare(lhs_field, operation, rhs_field), "Timestamp mismatch."); +} +``` +> Source code: noir-projects/noir-contracts/contracts/protocol/router_contract/src/main.nr#L13-L31 + + +:::note +Note that the router contract is not currently part of the [aztec-nr repository](https://github.com/AztecProtocol/aztec-nr). +To add it as a dependency point to the aztec-packages repository instead: + +```toml +[dependencies] +aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.88.0", directory = "noir-projects/noir-contracts/contracts/protocol/router_contract/src" } +``` + +::: + +Even with the router contract achieving good privacy is hard. +For example, if the value being checked against is unique and stored in the contract's public storage, it's then simple to find private transactions that are using that value in the enqueued public reads, and therefore link them to this contract. +For this reason it is encouraged to try to avoid public function calls and instead privately read [Shared State](../../developers/reference/smart_contract_reference/storage/shared_state.md) when possible. + +### Public Execution + +Contract functions marked with `#[public]` can only be called publicly, and are executed by the sequencer. The computation model is very similar to the EVM: all state, parameters, etc. are known to the entire network, and no data is private. Static execution like the EVM's `STATICCALL` is possible too, with similar semantics (state can be accessed but not modified, etc.). + +Since private calls are always run in a user's device, it is not possible to perform any private execution from a public context. A reasonably good mental model for public execution is that of an EVM in which some work has already been done privately, and all that is know about it is its correctness and side-effects (new notes and nullifiers, enqueued public calls, etc.). A reverted public execution will also revert the private side-effects. + +Public functions in other contracts can be called both regularly and statically, just like on the EVM. + +```rust title="public_call" showLineNumbers +Token::at(config.accepted_asset) + .transfer_in_public(context.msg_sender(), context.this_address(), max_fee, authwit_nonce + ) + .enqueue(&mut context); +``` +> Source code: noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr#L158-L163 + + +:::note +This is the same function that was called by privately enqueuing a call to it! Public functions can be called either directly in a public context, or asynchronously by enqueuing in a private context. +::: + +### Utility + +Contract functions marked with `#[utility]` cannot be called as part of a transaction, and are only invoked by applications that interact with contracts to perform state queries from an off-chain client (from both private and public state!) or to modify local contract-related PXE state (e.g. when processing logs in Aztec.nr). No guarantees are made on the correctness of the result since the entire execution is unconstrained and heavily reliant on oracle calls. It is possible however to verify that the bytecode being executed is the correct one, since a contract's address includes a commitment to all of its utility functions. + +### aztec.js + +There are three different ways to execute an Aztec contract function using the `aztec.js` library, with close similarities to their [JSON-RPC counterparts](#json-rpc). + +#### `simulate` + +This is used to get a result out of an execution, either private or public. It creates no transaction and spends no gas. The mental model is fairly close to that of [`eth_call`](#eth_call), in that it can be used to call any type of function, simulate its execution and get a result out of it. `simulate` is also the only way to run [utility functions](#utility). + +```rust title="public_getter" showLineNumbers +#[public] +#[view] +fn get_authorized() -> AztecAddress { + storage.authorized.get_current_value() +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L42-L50 + + +```typescript title="simulate_function" showLineNumbers +const balance = await contract.methods.balance_of_public(newWallet.getAddress()).simulate(); +expect(balance).toEqual(1n); +``` +> Source code: yarn-project/end-to-end/src/composed/docs_examples.test.ts#L54-L57 + + +:::warning +No correctness is guaranteed on the result of `simulate`! Correct execution is entirely optional and left up to the client that handles this request. +::: + +#### `prove` + +This creates and returns a transaction request, which includes proof of correct private execution and side-effects. The request is not broadcast however, and no gas is spent. It is typically used in testing contexts to inspect transaction parameters or to check for execution failure. + +```typescript title="local-tx-fails" showLineNumbers +const call = token.methods.transfer(recipient.getAddress(), 200n); +await expect(call.simulate()).rejects.toThrow(/Balance too low/); +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L123-L126 + + +#### `send` + +This is the same as [`prove`](#prove) except it also broadcasts the transaction and returns a receipt. This is how transactions are sent, getting them to be included in blocks and spending gas. It is similar to [`eth_sendTransaction`](#eth_sendtransaction), except it also performs some work on the user's device, namely the production of the proof for the private part of the transaction. + +```typescript title="send_tx" showLineNumbers +await contract.methods.buy_pack(seed).send().wait(); +``` +> Source code: yarn-project/end-to-end/src/e2e_card_game.test.ts#L129-L131 + diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/communication/cross_chain_calls.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/communication/cross_chain_calls.md new file mode 100644 index 000000000000..b252e6b8c6da --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/communication/cross_chain_calls.md @@ -0,0 +1,152 @@ +--- +id: portals +title: L1 <--> L2 communication (Portals) +description: "This page is a conceptual introduction to Portals, how Aztec communicates with L1 (Ethereum)" +keywords: [portals] +tags: [portals, protocol, ethereum] +importance: 1 +--- + +# L1-L2 Communication (Portals) + +import Image from "@theme/IdealImage"; + +In Aztec, what we call _portals_ are the key element in facilitating communication between L1 and L2. While typical L2 solutions rely on synchronous communication with L1, Aztec's privacy-first nature means this is not possible. You can learn more about why in the previous section. + +Traditional L1 \<-\> L2 communication might involve direct calls between L2 and L1 contracts. However, in Aztec, due to the privacy components and the way transactions are processed (kernel proofs built on historical data), direct calls between L1 and L2 would not be possible if we want to maintain privacy. + +Portals are the solution to this problem, acting as bridges for communication between the two layers. These portals can transmit messages from public functions in L1 to private functions in L2 and vice versa, thus enabling messaging while maintaining privacy. + +This page covers: + +- How portals enable privacy communication between L1 and L2 +- How messages are sent, received, and processed +- Message Boxes and how they work +- How and why linking of contracts between L1 and L2 occurs + +## Objective + +The goal is to set up a minimal-complexity mechanism, that will allow a base-layer (L1) and the Aztec Network (L2) to communicate arbitrary messages such that: + +- L2 functions can `call` L1 functions. +- L1 functions can `call` L2 functions. +- The rollup-block size have a limited impact by the messages and their size. + +## High Level Overview + +This document will contain communication abstractions that we use to support interaction between _private_ functions, _public_ functions and Layer 1 portal contracts. + +Fundamental restrictions for Aztec: + +- L1 and L2 have very different execution environments, stuff that is cheap on L1 is most often expensive on L2 and vice versa. As an example, `keccak256` is cheap on L1, but very expensive on L2. +- _Private_ function calls are fully "prepared" and proven by the user, which provides the kernel proof along with commitments and nullifiers to the sequencer. +- _Public_ functions altering public state (updatable storage) must be executed at the current "head" of the chain, which only the sequencer can ensure, so these must be executed separately to the _private_ functions. +- _Private_ and _public_ functions within Aztec are therefore ordered such that first _private_ functions are executed, and then _public_. For a more detailed description of why, see above. +- Messages are consumables, and can only be consumed by the recipient. See [Message Boxes](#message-boxes) for more information. + +With the aforementioned restrictions taken into account, cross-chain messages can be operated in a similar manner to when _public_ functions must transmit information to _private_ functions. In such a scenario, a "message" is created and conveyed to the recipient for future use. It is worth noting that any call made between different domains (_private, public, cross-chain_) is unilateral in nature. In other words, the caller is unaware of the outcome of the initiated call until told when some later rollup is executed (if at all). This can be regarded as message passing, providing us with a consistent mental model across all domains, which is convenient. + +As an illustration, suppose a private function adds a cross-chain call. In such a case, the private function would not have knowledge of the result of the cross-chain call within the same rollup (since it has yet to be executed). + +Similarly to the ordering of private and public functions, we can also reap the benefits of intentionally ordering messages between L1 and L2. When a message is sent from L1 to L2, it has been "emitted" by an action in the past (an L1 interaction), allowing us to add it to the list of consumables at the "beginning" of the block execution. This practical approach means that a message could be consumed in the same block it is included. In a sophisticated setup, rollup $n$ could send an L2 to L1 message that is then consumed on L1, and the response is added already in $n+1$. However, messages going from L2 to L1 will be added as they are emitted. + +:::info +Because everything is unilateral and async, the application developer have to explicitly handle failure cases such that user can gracefully recover. Example where recovering is of utmost importance is token bridges, where it is very inconvenient if the locking of funds on one domain occur, but never the minting or unlocking on the other. +::: + +## Components + +### Portal + +A "portal" refers to the part of an application residing on L1, which is associated with a particular L2 address (the confidential part of the application). It could be a contract or even an EOA on L1. + +### Message Boxes + +In a logical sense, a Message Box functions as a one-way message passing mechanism with two ends, one residing on each side of the divide, i.e., one component on L1 and another on L2. Essentially, these boxes are utilized to transmit messages between L1 and L2 via the rollup contract. The boxes can be envisaged as multi-sets that enable the same message to be inserted numerous times, a feature that is necessary to accommodate scenarios where, for instance, "deposit 10 eth to A" is required multiple times. The diagram below provides a detailed illustration of how one can perceive a message box in a logical context. + + + +- Here, a `sender` will insert a message into the `pending` set, the specific constraints of the actions depend on the implementation domain, but for now, say that anyone can insert into the pending set. +- At some point, a rollup will be executed, in this step messages are "moved" from pending on Domain A, to ready on Domain B. Note that consuming the message is "pulling & deleting" (or nullifying). The action is atomic, so a message that is consumed from the pending set MUST be added to the ready set, or the state transition should fail. A further constraint on moving messages along the way, is that only messages where the `sender` and `recipient` pair exists in a leaf in the contracts tree are allowed! +- When the message has been added to the ready set, the `recipient` can consume the message as part of a function call. + +A difference when compared to other cross-chain setups, is that Aztec is "pulling" messages, and that the message doesn't need to be calldata for a function call. For other rollups, execution is happening FROM the "message bridge", which then calls the L1 contract. For Aztec, you call the L1 contract, and it should then consume messages from the message box. + +Why? _Privacy_! When pushing, we would be needing full `calldata`. Which for functions with private inputs is not really something we want as that calldata for L1 -> L2 transactions are committed to on L1, e.g., publicly sharing the inputs to a private function. + +By instead pulling, we can have the "message" be something that is derived from the arguments instead. This way, a private function to perform second half of a deposit, leaks the "value" deposited and "who" made the deposit (as this is done on L1), but the new owner can be hidden on L2. + +To support messages in both directions we require two of these message boxes (one in each direction). However, due to the limitations of each domain, the message box for sending messages into the rollup and sending messages out are not fully symmetrical. In reality, the setup looks closer to the following: + + + +:::info +The L2 -> L1 pending messages set only exist logically, as it is practically unnecessary. For anything to happen to the L2 state (e.g., update the pending messages), the state will be updated on L1, meaning that we could just as well insert the messages directly into the ready set. +::: + +### Rollup Contract + +The rollup contract has a few very important responsibilities. The contract must keep track of the _L2 rollup state root_, perform _state transitions_ and ensure that the data is available for anyone else to synchronize to the current state. + +To ensure that _state transitions_ are performed correctly, the contract will derive public inputs for the **rollup circuit** based on the input data, and then use a _verifier_ contract to validate that inputs correctly transition the current state to the next. All data needed for the public inputs to the circuit must be from the rollup block, ensuring that the block is available. For a valid proof, the _rollup state root_ is updated and it will emit an _event_ to make it easy for anyone to find the data. + +As part of _state transitions_ where cross-chain messages are included, the contract must "move" messages along the way, e.g., from "pending" to "ready". + +### Kernel Circuit + +For L2 to L1 messages, the public inputs of a user-proof will contain a dynamic array of messages to be added, of size at most `MAX_MESSAGESTACK_DEPTH`, limited to ensure it is not impossible to include the transaction. The circuit must ensure, that all messages have a `sender/recipient` pair, and that those pairs exist in the contracts tree and that the `sender` is the L2 contract that actually emitted the message. +For consuming L1 to L2 messages the circuit must create proper nullifiers. + +### Rollup Circuit + +The rollup circuit must ensure that, provided two states $S$ and $S'$ and the rollup block $B$, applying $B$ to $S$ using the transition function must give us $S'$, e.g., $T(S, B) \mapsto S'$. If this is not the case, the constraints are not satisfied. + +For the sake of cross-chain messages, this means inserting and nullifying L1 $\rightarrow$ L2 in the trees, and publish L2 $\rightarrow$ L1 messages on chain. These messages should only be inserted if the `sender` and `recipient` match an entry in the contracts leaf (as checked by the kernel). + +### Messages + +While a message could theoretically be arbitrarily long, we want to limit the cost of the insertion on L1 as much as possible. Therefore, we allow the users to send 32 bytes of "content" between L1 and L2. If 32 suffices, no packing required. If the 32 is too "small" for the message directly, the sender should simply pass along a `sha256(content)` instead of the content directly (note that this hash should fit in a field element which is ~254 bits. More info on this below). The content can then either be emitted as an event on L2 or kept by the sender, who should then be the only entity that can "unpack" the message. +In this manner, there is some way to "unpack" the content on the receiving domain. + +The message that is passed along, require the `sender/recipient` pair to be communicated as well (we need to know who should receive the message and be able to check). By having the pending messages be a contract on L1, we can ensure that the `sender = msg.sender` and let only `content` and `recipient` be provided by the caller. Summing up, we can use the structs seen below, and only store the commitment (`sha256(LxToLyMsg)`) on chain or in the trees, this way, we need only update a single storage slot per message. + +```solidity +struct L1Actor { + address: actor, + uint256: chainId, +} + +struct L2Actor { + bytes32: actor, + uint256: version, +} + +struct L1ToL2Msg { + L1Actor: sender, + L2Actor: recipient, + bytes32: content, + bytes32: secretHash, +} + +struct L2ToL1Msg { + L2Actor: sender, + L1Actor: recipient, + bytes32: content, +} +``` + +:::info +The `bytes32` elements for `content` and `secretHash` hold values that must fit in a field element (~ 254 bits). +::: + +:::info +The nullifier computation should include the index of the message in the message tree to ensure that it is possible to send duplicate messages (e.g., 2 x deposit of 500 dai to the same account). + +To make it possible to hide when a specific message is consumed, the `L1ToL2Msg` is extended with a `secretHash` field, where the `secretPreimage` is used as part of the nullifier computation. This way, it is not possible for someone just seeing the `L1ToL2Msg` on L1 to know when it is consumed on L2. +::: + +## Combined Architecture + +The following diagram shows the overall architecture, combining the earlier sections. + + diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/communication/index.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/communication/index.md new file mode 100644 index 000000000000..56cb9c93c3fa --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/communication/index.md @@ -0,0 +1,12 @@ +--- +title: Communication +sidebar_position: 7 +--- + +## Cross-chain communication + +See [L1 \<--\> L2 communication (Portals)](./cross_chain_calls.md) for information about how Aztec communications with L1 (Ethereum) through Portals. + +## Private / Public execution + +For in-depth information about how private and public functions can call each other, read the [Smart Contracts section](../../smart_contracts/functions/public_private_calls.md). diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/fees.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/fees.md new file mode 100644 index 000000000000..2a565228c2d1 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/fees.md @@ -0,0 +1,97 @@ +--- +title: Fees +sidebar_position: 4 +tags: [fees] +--- + +import { Why_Fees } from '@site/src/components/Snippets/general_snippets'; + + + +In a nutshell, the pricing of transactions transparently accounts for: + +- L1 costs, including L1 execution of a block, and data availability via blobs, +- L2 node operating costs, including proving + +This is done via multiple variables and calculations explained in detail in the protocol specifications. + +## Terminology and factors + +Familiar terms from Ethereum mainnet as referred to on the Aztec network: + +| Ethereum Mainnet | Aztec | Description | +| ---------------- | ------------------ | ---------------------------------------------- | +| gas | mana | indication of effort in transaction operations | +| fee per gas | fee-juice per mana | cost per unit of effort | +| fee (wei) | fee-juice | amount to be paid | + +An oracle informs the price of fee-juice per wei, which can be used to calculate a transaction's fee-juice in the units of wei. + +Also Aztec borrows ideas from EIP-1559 including: congestion multipliers, and the ability to specify base and priority fee per mana. + +### Aztec-specific fields + +There are many other fields used in mana and fee calculations, and below shows the ways these fields are determined: + +- hard-coded constants (eg congestion update fraction) +- values assumed constant (eg L1 gas cost of publishing a block, blobs per block) +- informed from previous block header and/or L1 rollup contract (eg base_fee_juice_per_mana) +- informed via an oracle (eg wei per mana) + +Most of the constants are defined by the protocol, several others are part of the rollup contract on L1. + +More information about the design/choices can be found in the fees section of the protocol specification. + +### User selected factors + +As part of a transaction the follow gas settings are available to be defined by the user. + +import { Gas_Settings_Components, Gas_Settings, Tx_Teardown_Phase } from '@site/src/components/Snippets/general_snippets'; + + + +These are: + +```javascript title="gas_settings_vars" showLineNumbers +/** Gas usage and fees limits set by the transaction sender for different dimensions and phases. */ +export class GasSettings { + constructor( + public readonly gasLimits: Gas, + public readonly teardownGasLimits: Gas, + public readonly maxFeesPerGas: GasFees, + public readonly maxPriorityFeesPerGas: GasFees, + ) {} +``` +> Source code: yarn-project/stdlib/src/gas/gas_settings.ts#L11-L20 + + + + +## Fee payment + +A fee payer will have bridged fee-juice from L1. On Aztec this fee asset is non-transferrable, and only deducted by the protocol to pay for fees. A user can claim bridged fee juice and use it to pay for transaction fees in the same transaction. + +The mechanisms for bridging is the same as any other token. For more on this concept see the start of the [Token Bridge Tutorial](../../developers/tutorials/codealong/js_tutorials/token_bridge.md) where it describes the components and how bridging works (under the hood this makes use of [portals](https://docs.aztec.network/aztec/concepts/communication/portals)). + +### Payment methods + +An account with fee-juice can pay for its transactions, including deployment of a new account, if fee juice has been bridged the that address at which the account will be deployed. + +An account making a transaction can also refer to "fee-paying contracts" (FPCs) to pay for its transactions. FPCs are contracts that accept a token and pay for transactions in fee juice. This means a user doesn't need to hold fee juice, they only need the token that the FPC accepts. FPCs can contain arbitrary logic to determine how they want to authorize transaction fee payments. They can be used for paying transaction fees privately or publicly. + +### Teardown phase + + + +### Operator rewards + +The calculated fee-juice of a transaction is deducted from the fee payer (nominated account or fee-paying contract), then pooled together each transaction, block, and epoch. +Once the epoch is proven, the total fee-juice (minus any burnt congestion amount), is distributed to provers and block validators/sequencers that contributed to the epoch. + +The fees section of the protocol specification explains this distribution of fee-juice between proposers and provers. + +## Next steps + +More comprehensive technical details for implementers will be available from the updated protocol specifications soon. + +For a guide showing ways to pay fees programmatically, see [here](../../developers/guides/js_apps/pay_fees). diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/pxe/acir_simulator.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/pxe/acir_simulator.md new file mode 100644 index 000000000000..6e8e2abc9f2f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/pxe/acir_simulator.md @@ -0,0 +1,35 @@ +--- +title: ACIR Simulator +--- + +The ACIR Simulator is responsible for simulation Aztec smart contract function execution. This component helps with correct execution of Aztec transactions. + +Simulating a function implies generating the partial witness and the public inputs of the function, as well as collecting all the data (such as created notes or nullifiers, or state changes) that are necessary for components upstream. + +## Simulating functions + +It simulates all [three types of contract functions](../call_types.md#aztec-call-types): + +### Private Functions + +Private functions are simulated and proved client-side, and verified client-side in the private kernel circuit. + +They are run with the assistance of a DB oracle that provides any private data requested by the function. You can read more about oracle functions in the smart contract section [here](../../smart_contracts/oracles/index.md). + +Private functions can call other private functions and can request to call a public function. The public function execution will be performed by the sequencer asynchronously, so private functions don't have direct access to the return values of public functions. + +### Public Functions + +Public functions are simulated and proved on the sequencer side, and verified by the [public kernel circuit](../../concepts/advanced/circuits/kernels/public_kernel.md). + +They are run with the assistance of an oracle that provides any value read from the public state tree. + +Public functions can call other public functions as well as private functions. Public function composability can happen atomically, but public to private function calls happen asynchronously (the private function call must happen in a future block). + +### Utility Functions + +Utility functions are used to extract useful data for users, such as the user balance. They are not proven, and are simulated client-side. + +They are run with the assistance of an [oracle resolver](https://noir-lang.org/docs/explainers/explainer-oracle) that provides any private data requested by the function. + +At the moment, utility functions cannot call any other function. It is not possible for them to call private or public functions, but it is on the roadmap to allow them to call other utility functions. diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/pxe/index.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/pxe/index.md new file mode 100644 index 000000000000..35581096af32 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/pxe/index.md @@ -0,0 +1,108 @@ +--- +title: Private Execution Environment (PXE) +sidebar_position: 8 +tags: [PXE] +keywords: [pxe, private execution environment] +importance: 1 +--- + +import Image from "@theme/IdealImage"; + +This page describes the Private Execution Environment (PXE, pronounced "pixie"), a client-side library for the execution of private operations. It is a TypeScript library that can be run within Node.js, inside wallet software or a browser. + +The PXE generates proofs of private function execution, and sends these proofs along with public function execution requests to the sequencer. Private inputs never leave the client-side PXE. + +The PXE is responsible for: + +- storing secrets (e.g. encryption keys, notes, tagging secrets for note discovery) and exposing an interface for safely accessing them +- orchestrating private function (circuit) execution and proof generation, including implementing [oracles](../../smart_contracts/oracles/index.md) needed for transaction execution +- syncing users' relevant network state, obtained from an Aztec node +- safely handling multiple accounts with siloed data and permissions + +One PXE can handle data and secrets for multiple accounts, while also providing isolation between them as required. + +## System architecture + +```mermaid +flowchart TB + User --Interacts with--> Wallet + Wallet --Prompts--> User + subgraph Browser + Dapp + end + Dapp --Calls (requires auth)--> Wallet + subgraph Client-side + Wallet --Scoped--> PXE + PXE --Execute/Prove--> Circuits + Circuits --Oracle--> PXE + end + PXE --Queries world-state (causes privacy leaks)--> Node + Wallet --Track tx state (may be handled via PXE)--> Node +``` + +## Components + +### Transaction Simulator + +An application will prompt the users PXE to execute a transaction (e.g. execute X function, with Y arguments, from Z account). The application or the wallet may handle gas estimation. + +The ACIR (Abstract Circuit Intermediate Representation) simulator handles the execution of smart contract functions by simulating transactions. It generates the required data and inputs for these functions. You can find more details about how it works [here](./acir_simulator.md). + +Until there are simulated simulations ([#9133](https://github.com/AztecProtocol/aztec-packages/issues/9133)), authwits are required for simulation, before attempting to prove. + +### Proof Generation + +After simulation, the wallet calls `proveTx` on the PXE with all of the data generated during simulation and any [authentication witnesses](../advanced/authwit.md) (for allowing contracts to act on behalf of the users' account contract). + +Once proven, the wallet sends the transaction to the network and sends the transaction hash back to the application. + +### Database + +The database stores transactional data and notes within the user's PXE. + +The database stores various types of data, including: + +- **Notes**: Data representing users' private state. These are often stored on-chain, encrypted to a user. A contract will parse on-chain data to find notes relevant for users' accounts and they are stored in the PXE. +- **Authentication Witnesses**: Data used to approve others for executing transactions on your behalf. The PXE provides this data to transactions on-demand during transaction simulation via oracles. +- **Capsules**: External data or data injected into the system via [oracles](#oracles). +- **Address Book**: A list of expected addresses that a PXE may encrypt notes for, or received encrypted notes from. This list helps the PXE reduce the amount of work required to find notes relevant to it's registered accounts. + +The PXE is not in charge of note discovery, ie finding the notes that are owned by the user. This is handled by Aztec contracts, and you can learn more [here](../advanced/storage/note_discovery.md) + +### Authorization + +The PXE handles access rights by: + +1. action +2. domain +3. contract +4. account + +For example, uniswap.com (**domain**) can query (**action**, involves execution that has access to an accounts' private state) on these five token **contracts** for these two **accounts** of mine, that are registered in the PXE. + +Available actions include: + +- Seeing that the accounts exist in the PXE +- Running queries, simulations, accessing logs, registering contracts, etc at a given a contract address +- Manually adding notes + +Providing an application with an empty scopes array (e.g. `scopes: []`) to the PXE means that no information can be accessed, but no scopes (e.g. `scopes: undefined`) defaults to _all_ scopes being available. + +### Contract management + +Applications can add contract code required for a user to interact with the application to the users PXE. The PXE will check whether the required contracts have already been registered in users PXE. There are no getters to check whether the contract has been registered, as this could leak privacy (e.g. a dapp could check whether specific contracts have been registered in a users PXE and infer information about their interaction history). + +### Keystore + +The keystore is a secure storage for private and public keys. + +### Oracles + +Oracles are pieces of data that are injected into a smart contract function from the client side. You can read more about why and how they work in the [smart contracts section](../../smart_contracts/oracles/index.md). + +## For developers + +To learn how to develop on top of the PXE, refer to these guides: + +- [Run more than one PXE on your local machine](../../../developers/guides/local_env/run_more_than_one_pxe_sandbox.md) +- [Use in-built oracles including oracles for arbitrary data](../../../developers/guides/smart_contracts/writing_contracts/how_to_use_capsules.md) diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/storage/_category_.json b/docs/versioned_docs/version-v0.88.0/aztec/concepts/storage/_category_.json new file mode 100644 index 000000000000..0c73c07e5639 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/storage/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Storage", + "position": 1, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/storage/index.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/storage/index.md new file mode 100644 index 000000000000..d440a19d8d41 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/storage/index.md @@ -0,0 +1,12 @@ +--- +title: Storage +description: How are storage slots derived for public and private state +sidebar_position: 0 +tags: [protocol, storage] +--- + +In Aztec, private data and public data are stored in two trees; a public data tree and a note hashes tree. + +These trees have in common that they store state for _all_ accounts on the Aztec network directly as leaves. This is different from Ethereum, where a state trie contains smaller tries that hold the individual accounts' storage. + +It also means that we need to be careful about how we allocate storage to ensure that they don't collide! We say that storage should be _siloed_ to its contract. The exact way of siloing differs a little for public and private storage. Which we will see in the following sections. diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/storage/notes.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/storage/notes.md new file mode 100644 index 000000000000..e69e8d35eb06 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/storage/notes.md @@ -0,0 +1,45 @@ +--- +title: Notes (UTXOs) +sidebar_position: 5 +tags: [notes, storage] +--- + +import Image from "@theme/IdealImage"; + +The [state model page](./state_model.md) explains that there is a difference between public and private state. Private state uses UTXOs (unspent transaction ouputs), also known as notes. This page introduces the concept of UTXOs and how notes are abstracted on Aztec. + +## What are notes? + +In an account-based model such as Ethereum, each account is typically associated with a specific location in the data tree. In a UTXO model, each note specifies its owner and there is no relationship between an account and data's location in the data tree. Notes are encrypted pieces of data that can only be decrypted by their owner. + +Rather than storing entire notes in a data tree, note commitments (hashes of the notes) are stored in a merkle tree, aptly named the note hash tree. Users will prove that they have the note pre-image information when they update private state in a contract. + +When a note is updated, Aztec nullifies the original commitment in the note hash tree by creating a nullifier from the note data, and may create a new note with the updated information, encrypted to a new owner if necessary. This helps to decouple actions of creating, updating and deleting private state. + + + +Notes are comparable to cash, with some slight differences. When you want to spend \$3.50 USD in real life, you give your \$5 note to a cashier who will keep \$3.50 and give you separate notes that add up to \$1.50. Using private notes on Aztec, when you want to spend a \$5 note, you nullify it and create a \$1.5 note with yourself as the owner and a \$3.5 note with the recipient as the owner. Only you and the recipient are aware of \$3.5 transaction, they are not aware that you "split" the \$5 note. + +## Sending notes + +When creating notes for a recipient, you need a way to send the note to them. There are a few ways to do this: + +### On-chain (encrypted logs): + +This is the common method and works well for most use cases. You can emit an encrypted log as part of a transaction. The encrypted note data will be posted onchain, allowing the recipient to find the note through [note discovery](../advanced/storage/note_discovery.md). + +### Off-chain (out of band): + +In some cases, if you know the recipient off-chain, you might choose to share the note data directly with them. The recipient can store that note in their PXE and later spend it. + +### Self-created notes (not emitted): + +If you create a note for yourself, you don’t need to broadcast it to the network or share anything. You will only need to keep the note somewhere, such as in your PXE, so you can prove ownership and spend it in future transactions. + +## Abstracting notes from apps & users + +When using the Aztec protocol, users may not be aware of the specific notes that they own. Their experience should be similar to Ethereum, and should instead see the amount of their assets inside their account. + +This is accomplished through the smart contract library, Aztec.nr, which abstracts notes by allowing developers to specify custom note types. This means they can specify how notes are interacted with, nullified, transferred, and displayed. Aztec.nr also helps users discover all of the notes that have been encrypted to their account and posted to the chain, known as [note discovery](../advanced/storage/note_discovery.md). + +To understand note abstraction in Aztec.nr, you can read the [Build section](../../../developers/guides/smart_contracts/writing_contracts/notes/index.md). diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/storage/state_model.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/storage/state_model.md new file mode 100644 index 000000000000..6ef55640ac5c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/storage/state_model.md @@ -0,0 +1,27 @@ +--- +title: State Model +sidebar_position: 4 +tags: [state] +--- + +Aztec has a hybrid public/private state model. Aztec contract developers can specify which data is public and which data is private, as well as the functions that can operate on that data. + +## Public State + +Aztec has public state that will be familiar to developers coming that have worked on other blockchains. Public state is transparent and is managed by the associated smart contract logic. + +Internal to the Aztec network, public state is stored and updated by the sequencer. The sequencer executes state transitions, generates proofs of correct execution (or delegates proof generation to the prover network), and publishes the associated data to Ethereum. + +## Private State + +Private state must be treated differently from public state. Private state is encrypted and therefore is "owned" by a user or a set of users (via shared secrets) that are able to decrypt the state. + +Private state is represented in an append-only database since updating a record would leak information about the transaction graph. + +The act of "deleting" a private state variable can be represented by adding an associated nullifier to a nullifier set. The nullifier is generated such that, without knowing the decryption key of the owner, an observer cannot link a state record with a nullifier. + +Modification of state variables can be emulated by nullifying the state record and creating a new record to represent the variable. Private state has an intrinsic UTXO structure. + +## Further reading + +Read more about how to leverage the Aztec state model in Aztec contracts [here](../../../developers/reference/smart_contract_reference/storage/index.md). diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/transactions.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/transactions.md new file mode 100644 index 000000000000..89d013e32639 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/transactions.md @@ -0,0 +1,227 @@ +--- +title: Transactions +sidebar_position: 3 +tags: [protocol] +--- + +import Image from '@theme/IdealImage'; + +On this page you'll learn: + +- The step-by-step process of sending a transaction on Aztec +- The role of components like PXE, Aztec Node, ACIR simulator, and the sequencer +- The Aztec Kernel and its two circuits: private and public, and how they execute function calls +- The call stacks for private & public functions and how they determine a transaction's completion + +## Simple Example of the (Private) Transaction Lifecycle + +The transaction lifecycle for an Aztec transaction is fundamentally different from the lifecycle of an Ethereum transaction. + +The introduction of the Private eXecution Environment (PXE) provides a safe environment for the execution of sensitive operations, ensuring that decrypted data are not accessible to unauthorized applications. However, the PXE exists client-side on user devices, which creates a different model for imagining what the lifecycle of a typical transaction might look like. The existence of a sequencing network also introduces some key differences between the Aztec transaction model and the transaction model used for other networks. + +The accompanying diagram illustrates the flow of interactions between a user, their wallet, the PXE, the node operators (sequencers / provers), and the L1 chain. + + + +1. **The user initiates a transaction** – In this example, the user decides to privately send 10 DAI to gudcause.eth. After inputting the amount and the receiving address, the user clicks the confirmation button on their wallet. + +_The transaction has not been broadcasted to the sequencer network yet. For now, the transaction exists solely within the context of the PXE._ + +2. **The PXE executes transfer locally** – The PXE, running locally on the user's device, executes the transfer method on the DAI token contract on Aztec and computes the state difference based on the user’s intention. + +_The transaction has still not been broadcasted to the sequencer network yet and continues to live solely within the context of the PXE._ + +3. **The PXE proves correct execution** – At this point, the PXE proves correct execution (via zero-knowledge proofs) of the authorization and of the private transfer method. Once the proofs have been generated, the PXE sends the proofs and required inputs (inputs are new note commitments, stored in the note hash tree and nullifiers stored in the nullifiers tree) to the sequencer. Nullifiers are data that invalidate old commitments, ensuring that commitments can only be used once. + +_The sequencer has received the transaction proof and can begin to process the transaction - verifying proofs and applying updates to the relevant data trees - alongside other public and private transactions._ + +4. **The sequencer has the necessary information to act** – the randomly-selected sequencer (based on the Fernet sequencer selection protocol) validates the transaction proofs along with required inputs (e.g. the note commitments and nullifiers) for this private transfer. The sequencer also executes public functions and requests proofs of public execution from a prover network. The sequencer updates the corresponding data trees and does the same for other private transactions. When the sequencer receives proofs from the prover network, the proofs will be bundled into a final rollup proof. + +_The sequencer has passed the transaction information – proofs of correct execution and authorization, or public function execution information – to the prover, who will submit the new state root to Ethereum._ + +5. **The transaction settles to L1** – the verifier contract on Ethereum can now validate the rollup proof and record a new state root. The state root is submitted to the rollup smart contract. Once the state root is verified in an Ethereum transaction, the private transfer has settled and the transaction is considered final. + +### Detailed Diagram + +Transactions on Aztec start with a call from Aztec.js, which creates a request containing transaction details. This request moves to the Private Execution Environment (PXE) which simulates and processes it. Then the PXE interacts with the Aztec Node which uses the sequencer to ensure that all the transaction details are enqueued properly. The sequencer then submits the block to the rollup contract, and the transaction is successfully mined. + + + +See [this diagram](https://raw.githubusercontent.com/AztecProtocol/aztec-packages/2fa143e4d88b3089ebbe2a9e53645edf66157dc8/docs/static/img/sandbox_sending_a_tx.svg) for a more detailed overview of the transaction execution process. It highlights 3 different types of transaction execution: contract deployments, private transactions and public transactions. + +See the page on [contract communication](../smart_contracts/functions/public_private_calls.md) for more context on transaction execution. + +### Transaction Requests + +Transaction requests are how transactions are constructed and sent to the network. + +In Aztec.js: + +```javascript title="constructor" showLineNumbers +constructor( + /** Sender. */ + public origin: AztecAddress, + /** Function data representing the function to call. */ + public functionData: FunctionData, + /** Pedersen hash of function arguments. */ + public argsHash: Fr, + /** Transaction context. */ + public txContext: TxContext, + /** A salt to make the hash difficult to predict. The hash is used as the first nullifier if there is no nullifier emitted throughout the tx. */ + public salt: Fr, +) {} +``` +> Source code: yarn-project/stdlib/src/tx/tx_request.ts#L15-L28 + + +Where: + +- `origin` is the account contract where the transaction is initiated from. +- `functionData` contains the function selector and indicates whether the function is private or public. +- `argsHash` is the hash of the arguments of all of the calls to be executed. The complete set of arguments is passed to the PXE as part of the [TxExecutionRequest](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/stdlib/src/tx/tx_execution_request.ts) and checked against this hash. +- `txContext` contains the chain id, version, and gas settings. + +The `functionData` includes an `AppPayload`, which includes information about the application functions and arguments, and a `FeePayload`, which includes info about how to pay for the transaction. + +An account contract validates that the transaction request has been authorized via its specified authorization mechanism, via the `is_valid_impl` function (e.g. [an ECDSA signature](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/noir-contracts/contracts/account/ecdsa_k_account_contract/src/main.nr#L56-L57), generated [in JS](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/accounts/src/ecdsa/ecdsa_k/account_contract.ts#L30)). + +Transaction requests are simulated in the PXE in order to generate the necessary inputs for generating proofs. Once transactions are proven, a [transaction object](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/stdlib/src/tx/tx.ts#L26) is created and can be sent to the network to be included in a block. + +#### Contract Interaction Methods + +Most transaction requests are created as interactions with specific contracts. The exception is transactions that deploy contracts. Here are the main methods for interacting with contracts related to transactions. + +1. [`create`](#create) +2. [`simulate`](#simulate) +3. [`prove`](#prove) +4. [`send`](#send) + +And fee utilities: + +- [`estimateGas`](#estimategas) +- [`getFeeOptions`](#getfeeoptions) + +##### `create` + +```javascript title="create" showLineNumbers +/** + * Create a transaction execution request that represents this call, encoded and authenticated by the + * user's wallet, ready to be simulated. + * @param options - An optional object containing additional configuration for the transaction. + * @returns A Promise that resolves to a transaction instance. + */ +public override async create(options: SendMethodOptions = {}): Promise { +``` +> Source code: yarn-project/aztec.js/src/contract/contract_function_interaction.ts#L67-L75 + + +##### `simulate` + +```javascript title="simulate" showLineNumbers +/** + * Simulate a transaction and get its return values + * Differs from prove in a few important ways: + * 1. It returns the values of the function execution + * 2. It supports `utility`, `private` and `public` functions + * + * @param options - An optional object containing additional configuration for the transaction. + * @returns The result of the transaction as returned by the contract function. + */ +public async simulate(options?: T): Promise>; +// eslint-disable-next-line jsdoc/require-jsdoc +public async simulate( + options: SimulateMethodOptions = {}, +): Promise> { +``` +> Source code: yarn-project/aztec.js/src/contract/contract_function_interaction.ts#L117-L132 + + +##### `prove` + +```javascript title="prove" showLineNumbers +/** + * Proves a transaction execution request and returns a tx object ready to be sent. + * @param options - optional arguments to be used in the creation of the transaction + * @returns The resulting transaction + */ +public async prove(options: SendMethodOptions = {}): Promise { +``` +> Source code: yarn-project/aztec.js/src/contract/base_contract_interaction.ts#L55-L62 + + +##### `send` + +```javascript title="send" showLineNumbers +/** + * Sends a transaction to the contract function with the specified options. + * This function throws an error if called on a utility function. + * It creates and signs the transaction if necessary, and returns a SentTx instance, + * which can be used to track the transaction status, receipt, and events. + * @param options - An optional object containing 'from' property representing + * the AztecAddress of the sender. If not provided, the default address is used. + * @returns A SentTx instance for tracking the transaction status and information. + */ +public send(options: SendMethodOptions = {}): SentTx { +``` +> Source code: yarn-project/aztec.js/src/contract/base_contract_interaction.ts#L72-L83 + + +##### `estimateGas` + +```javascript title="estimateGas" showLineNumbers +/** + * Estimates gas for a given tx request and returns gas limits for it. + * @param opts - Options. + * @param pad - Percentage to pad the suggested gas limits by, if empty, defaults to 10%. + * @returns Gas limits. + */ +public async estimateGas( + opts?: Omit, +): Promise> { +``` +> Source code: yarn-project/aztec.js/src/contract/base_contract_interaction.ts#L91-L101 + + +##### `getFeeOptions` + +```javascript title="getFeeOptions" showLineNumbers +/** + * Return fee options based on the user opts, estimating tx gas if needed. + * @param executionPayload - Execution payload to get the fee for + * @param fee - User-provided fee options. + * @param options - Additional options for the transaction. They must faithfully represent the tx to get accurate fee estimates + * @returns Fee options for the actual transaction. + */ +protected async getFeeOptions( + executionPayload: ExecutionPayload, + fee: UserFeeOptions = {}, + options: TxExecutionOptions, +): Promise { +``` +> Source code: yarn-project/aztec.js/src/contract/base_contract_interaction.ts#L129-L142 + + +### Batch Transactions + +Batched transactions are a way to send multiple transactions in a single call. They are created by the [`BatchCall` class in Aztec.js](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/aztec.js/src/contract/batch_call.ts). This allows a batch of function calls from a single wallet to be sent as a single transaction through a wallet. + +### Enabling Transaction Semantics + +There are two kernel circuits in Aztec, the private kernel and the public kernel. Each circuit validates the correct execution of a particular function call. + +A transaction is built up by generating proofs for multiple recursive iterations of kernel circuits. Each call in the call stack is modeled as new iteration of the kernel circuit and are managed by a [FIFO]() queue containing pending function calls. There are two call stacks, one for private calls and one for public calls. + +One iteration of a kernel circuit will pop a call off of the stack and execute the call. If the call triggers subsequent contract calls, these are pushed onto the stack. + +Private kernel proofs are generated first. The transaction is ready to move to the next phase when the private call stack is empty. + +The public kernel circuit takes in proof of a public/private kernel circuit with an empty private call stack, and operates recursively until the public call stack is also empty. + +A transaction is considered complete when both call stacks are empty. + +The only information leaked about the transaction is: + +1. The number of private state updates triggered +2. The set of public calls generated + +The addresses of all private calls are hidden from observers. diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/wallets/architecture.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/wallets/architecture.md new file mode 100644 index 000000000000..c28a18484138 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/wallets/architecture.md @@ -0,0 +1,390 @@ +--- +title: Wallet Architecture +tags: [protocol, accounts] +--- + +This page talks about the architecture of a wallet in Aztec. Wallets expose to dapps an interface that allows them to act on behalf of the user, such as querying private state or sending transactions. Bear in mind that, as in Ethereum, wallets should require user confirmation whenever carrying out a potentially sensitive action requested by a dapp. + +## Overview + +Architecture-wise, a wallet is an instance of an **Private Execution Environment (PXE)** which manages user keys and private state. +The PXE also communicates with an **Aztec Node** for retrieving public information or broadcasting transactions. +Note that the PXE requires a local database for keeping private state, and is also expected to be continuously syncing new blocks for trial-decryption of user notes. + +Additionally, a wallet must be able to handle one or more account contract implementation. When a user creates a new account, the account is represented on-chain by an account contract. The wallet is responsible for deploying and interacting with this contract. A wallet may support multiple flavours of accounts, such as an account that uses ECDSA signatures, or one that relies on WebAuthn, or one that requires multi-factor authentication. For a user, the choice of what account implementation to use is then determined by the wallet they interact with. + +In code, this translates to a wallet implementing an **AccountInterface** interface that defines [how to create an _execution request_ out of an array of _function calls_](./index.md#transaction-lifecycle) for the specific implementation of an account contract and [how to generate an _auth witness_](./index.md#authorizing-actions) for authorizing actions on behalf of the user. Think of this interface as the Javascript counterpart of an account contract, or the piece of code that knows how to format a transaction and authenticate an action based on the rules defined by the user's account contract implementation. + +## Account interface + +The account interface is used for creating an _execution request_ out of one or more _function calls_ requested by a dapp, as well as creating an _auth witness_ for a given message hash. Account contracts are expected to handle multiple function calls per transaction, since dapps may choose to batch multiple actions into a single request to the wallet. + +```typescript title="account-interface" showLineNumbers + +/** + * Handler for interfacing with an account. Knows how to create transaction execution + * requests and authorize actions for its corresponding account. + */ +export interface AccountInterface extends EntrypointInterface, AuthWitnessProvider { + /** Returns the complete address for this account. */ + getCompleteAddress(): CompleteAddress; + + /** Returns the address for this account. */ + getAddress(): AztecAddress; + + /** Returns the chain id for this account */ + getChainId(): Fr; + + /** Returns the rollup version for this account */ + getVersion(): Fr; +} +``` +> Source code: yarn-project/aztec.js/src/account/interface.ts#L6-L25 + + +## PXE interface + +A wallet exposes the PXE interface to dapps by running a PXE instance. The PXE requires a keystore and a database implementation for storing keys, private state, and recipient encryption public keys. + +```typescript title="pxe-interface" showLineNumbers +/** + * Private eXecution Environment (PXE) runs locally for each user, providing functionality for all the operations + * needed to interact with the Aztec network, including account management, private data management, + * transaction local simulation, and access to an Aztec node. This interface, as part of a Wallet, + * is exposed to dapps for interacting with the network on behalf of the user. + */ +export interface PXE { + /** + * Returns whether an L1 to L2 message is synced by archiver and if it's ready to be included in a block. + * @param l1ToL2Message - The L1 to L2 message to check. + * @returns Whether the message is synced and ready to be included in a block. + */ + isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise; + + /** + * Registers a user account in PXE given its master encryption private key. + * Once a new account is registered, the PXE Service will trial-decrypt all published notes on + * the chain and store those that correspond to the registered account. Will do nothing if the + * account is already registered. + * + * @param secretKey - Secret key of the corresponding user master public key. + * @param partialAddress - The partial address of the account contract corresponding to the account being registered. + * @returns The complete address of the account. + */ + registerAccount(secretKey: Fr, partialAddress: PartialAddress): Promise; + + /** + * Retrieves the user accounts registered on this PXE Service. + * @returns An array of the accounts registered on this PXE Service. + */ + getRegisteredAccounts(): Promise; + + /** + * Registers a user contact in PXE. + * + * Once a new contact is registered, the PXE Service will be able to receive notes tagged from this contact. + * Will do nothing if the account is already registered. + * + * @param address - Address of the user to add to the address book + * @returns The address address of the account. + */ + registerSender(address: AztecAddress): Promise; + + /** + * Retrieves the addresses stored as senders on this PXE Service. + * @returns An array of the senders on this PXE Service. + */ + getSenders(): Promise; + + /** + * Removes a sender in the address book. + */ + removeSender(address: AztecAddress): Promise; + + /** + * Registers a contract class in the PXE without registering any associated contract instance with it. + * + * @param artifact - The build artifact for the contract class. + */ + registerContractClass(artifact: ContractArtifact): Promise; + + /** + * Adds deployed contracts to the PXE Service. Deployed contract information is used to access the + * contract code when simulating local transactions. This is automatically called by aztec.js when + * deploying a contract. Dapps that wish to interact with contracts already deployed should register + * these contracts in their users' PXE Service through this method. + * + * @param contract - A contract instance to register, with an optional artifact which can be omitted if the contract class has already been registered. + */ + registerContract(contract: { instance: ContractInstanceWithAddress; artifact?: ContractArtifact }): Promise; + + /** + * Updates a deployed contract in the PXE Service. This is used to update the contract artifact when + * an update has happened, so the new code can be used in the simulation of local transactions. + * This is called by aztec.js when instantiating a contract in a given address with a mismatching artifact. + * @param contractAddress - The address of the contract to update. + * @param artifact - The updated artifact for the contract. + */ + updateContract(contractAddress: AztecAddress, artifact: ContractArtifact): Promise; + + /** + * Retrieves the addresses of contracts added to this PXE Service. + * @returns An array of contracts addresses registered on this PXE Service. + */ + getContracts(): Promise; + + /** + * Proves the private portion of a simulated transaction, ready to send to the network + * (where validators prove the public portion). + * + * @param txRequest - An authenticated tx request ready for proving + * @param privateExecutionResult - (optional) The result of the private execution of the transaction. The txRequest + * will be executed if not provided + * @returns A result containing the proof and public inputs of the tail circuit. + * @throws If contract code not found, or public simulation reverts. + * Also throws if simulatePublic is true and public simulation reverts. + */ + proveTx(txRequest: TxExecutionRequest, privateExecutionResult?: PrivateExecutionResult): Promise; + + /** + * Simulates a transaction based on the provided preauthenticated execution request. + * This will run a local simulation of private execution (and optionally of public as well), run the + * kernel circuits to ensure adherence to protocol rules (without generating a proof), and return the + * simulation results . + * + * + * Note that this is used with `ContractFunctionInteraction::simulateTx` to bypass certain checks. + * In that case, the transaction returned is only potentially ready to be sent to the network for execution. + * + * + * @param txRequest - An authenticated tx request ready for simulation + * @param simulatePublic - Whether to simulate the public part of the transaction. + * @param skipTxValidation - (Optional) If false, this function throws if the transaction is unable to be included in a block at the current state. + * @param skipFeeEnforcement - (Optional) If false, fees are enforced. + * @param overrides - (Optional) State overrides for the simulation, such as msgSender, contract instances and artifacts. + * @param scopes - (Optional) The accounts whose notes we can access in this call. Currently optional and will default to all. + * @returns A simulated transaction result object that includes public and private return values. + * @throws If the code for the functions executed in this transaction have not been made available via `addContracts`. + * Also throws if simulatePublic is true and public simulation reverts. + */ + simulateTx( + txRequest: TxExecutionRequest, + simulatePublic: boolean, + skipTxValidation?: boolean, + skipFeeEnforcement?: boolean, + overrides?: SimulationOverrides, + scopes?: AztecAddress[], + ): Promise; + + /** + * Profiles a transaction, reporting gate counts (unless disabled) and returns an execution trace. + * + * @param txRequest - An authenticated tx request ready for simulation + * @param msgSender - (Optional) The message sender to use for the simulation. + * @param skipTxValidation - (Optional) If false, this function throws if the transaction is unable to be included in a block at the current state. + * @returns A trace of the program execution with gate counts. + * @throws If the code for the functions executed in this transaction have not been made available via `addContracts`. + */ + profileTx( + txRequest: TxExecutionRequest, + profileMode: 'gates' | 'execution-steps' | 'full', + skipProofGeneration?: boolean, + msgSender?: AztecAddress, + ): Promise; + + /** + * Sends a transaction to an Aztec node to be broadcasted to the network and mined. + * @param tx - The transaction as created via `proveTx`. + * @returns A hash of the transaction, used to identify it. + */ + sendTx(tx: Tx): Promise; + + /** + * Fetches a transaction receipt for a given transaction hash. Returns a mined receipt if it was added + * to the chain, a pending receipt if it's still in the mempool of the connected Aztec node, or a dropped + * receipt if not found in the connected Aztec node. + * + * @param txHash - The transaction hash. + * @returns A receipt of the transaction. + */ + getTxReceipt(txHash: TxHash): Promise; + + /** + * Gets a tx effect. + * @param txHash - The hash of the tx corresponding to the tx effect. + * @returns The requested tx effect with block info (or undefined if not found). + */ + getTxEffect(txHash: TxHash): Promise; + + /** + * Gets the storage value at the given contract storage slot. + * + * @remarks The storage slot here refers to the slot as it is defined in Noir not the index in the merkle tree. + * Aztec's version of `eth_getStorageAt`. + * + * @param contract - Address of the contract to query. + * @param slot - Slot to query. + * @returns Storage value at the given contract slot. + * @throws If the contract is not deployed. + */ + getPublicStorageAt(contract: AztecAddress, slot: Fr): Promise; + + /** + * Gets notes registered in this PXE based on the provided filter. + * @param filter - The filter to apply to the notes. + * @returns The requested notes. + */ + getNotes(filter: NotesFilter): Promise; + + /** + * Fetches an L1 to L2 message from the node. + * @param contractAddress - Address of a contract by which the message was emitted. + * @param messageHash - Hash of the message. + * @param secret - Secret used to compute a nullifier. + * @dev Contract address and secret are only used to compute the nullifier to get non-nullified messages + * @returns The l1 to l2 membership witness (index of message in the tree and sibling path). + */ + getL1ToL2MembershipWitness( + contractAddress: AztecAddress, + messageHash: Fr, + secret: Fr, + ): Promise<[bigint, SiblingPath]>; + + /** + * Gets the membership witness for a message that was emitted at a particular block + * @param blockNumber - The block number in which to search for the message + * @param l2Tol1Message - The message to search for + * @returns The membership witness for the message + */ + getL2ToL1MembershipWitness(blockNumber: number, l2Tol1Message: Fr): Promise<[bigint, SiblingPath]>; + + /** + * Get the given block. + * @param number - The block number being requested. + * @returns The blocks requested. + */ + getBlock(number: number): Promise; + + /** + * Method to fetch the current base fees. + * @returns The current base fees. + */ + getCurrentBaseFees(): Promise; + + /** + * Simulate the execution of a contract utility function. + * + * @param functionName - The name of the utility contract function to be called. + * @param args - The arguments to be provided to the function. + * @param to - The address of the contract to be called. + * @param authwits - (Optional) The authentication witnesses required for the function call. + * @param from - (Optional) The msg sender to set for the call. + * @param scopes - (Optional) The accounts whose notes we can access in this call. Currently optional and will + * default to all. + * @returns The result of the utility function call, structured based on the function ABI. + */ + simulateUtility( + functionName: string, + args: any[], + to: AztecAddress, + authwits?: AuthWitness[], + from?: AztecAddress, + scopes?: AztecAddress[], + ): Promise; + + /** + * Gets public logs based on the provided filter. + * @param filter - The filter to apply to the logs. + * @returns The requested logs. + */ + getPublicLogs(filter: LogFilter): Promise; + + /** + * Gets contract class logs based on the provided filter. + * @param filter - The filter to apply to the logs. + * @returns The requested logs. + */ + getContractClassLogs(filter: LogFilter): Promise; + + /** + * Fetches the current block number. + * @returns The block number. + */ + getBlockNumber(): Promise; + + /** + * Fetches the current proven block number. + * @returns The block number. + */ + getProvenBlockNumber(): Promise; + + /** + * Returns the information about the server's node. Includes current Node version, compatible Noir version, + * L1 chain identifier, rollup version, and L1 address of the rollup contract. + * @returns - The node information. + */ + getNodeInfo(): Promise; + + /** + * Returns information about this PXE. + */ + getPXEInfo(): Promise; + + /** + * Returns the contract metadata given an address. + * The metadata consists of its contract instance, which includes the contract class identifier, + * initialization hash, deployment salt, and public keys hash; whether the contract instance has been initialized; + * and whether the contract instance with the given address has been publicly deployed. + * @remark - it queries the node to check whether the contract instance has been initialized / publicly deployed through a node. + * This query is not dependent on the PXE. + * @param address - The address that the contract instance resides at. + * @returns - It returns the contract metadata + * TODO(@spalladino): Should we return the public keys in plain as well here? + */ + getContractMetadata(address: AztecAddress): Promise; + + /** + * Returns the contract class metadata given a contract class id. + * The metadata consists of its contract class, whether it has been publicly registered, and its artifact. + * @remark - it queries the node to check whether the contract class with the given id has been publicly registered. + * @param id - Identifier of the class. + * @param includeArtifact - Identifier of the class. + * @returns - It returns the contract class metadata, with the artifact field being optional, and will only be returned if true is passed in + * for `includeArtifact` + * TODO(@spalladino): The PXE actually holds artifacts and not classes, what should we return? Also, + * should the pxe query the node for contract public info, and merge it with its own definitions? + * TODO(@spalladino): This method is strictly needed to decide whether to publicly register a class or not + * during a public deployment. We probably want a nicer and more general API for this, but it'll have to + * do for the time being. + */ + getContractClassMetadata(id: Fr, includeArtifact?: boolean): Promise; + + /** + * Returns the private events given search parameters. + * @param contractAddress - The address of the contract to get events from. + * @param eventMetadata - Metadata of the event. This should be the class generated from the contract. e.g. Contract.events.Event + * @param from - The block number to search from. + * @param numBlocks - The amount of blocks to search. + * @param recipients - The addresses that decrypted the logs. + * @returns - The deserialized events. + */ + getPrivateEvents( + contractAddress: AztecAddress, + eventMetadata: EventMetadataDefinition, + from: number, + numBlocks: number, + recipients: AztecAddress[], + ): Promise; + + /** + * Returns the public events given search parameters. + * @param eventMetadata - Metadata of the event. This should be the class generated from the contract. e.g. Contract.events.Event + * @param from - The block number to search from. + * @param limit - The amount of blocks to search. + * @returns - The deserialized events. + */ + getPublicEvents(eventMetadata: EventMetadataDefinition, from: number, limit: number): Promise; +} +``` +> Source code: yarn-project/stdlib/src/interfaces/pxe.ts#L50-L389 + diff --git a/docs/versioned_docs/version-v0.88.0/aztec/concepts/wallets/index.md b/docs/versioned_docs/version-v0.88.0/aztec/concepts/wallets/index.md new file mode 100644 index 000000000000..a3eac6b3721b --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/concepts/wallets/index.md @@ -0,0 +1,75 @@ +--- +title: Wallets +sidebar_position: 2 +tags: [accounts] +--- + +On this page we will cover the main responsibilities of a wallet in the Aztec network. + +Refer to [writing an account contract](../../../developers/tutorials/codealong/contract_tutorials/write_accounts_contract.md) for a tutorial on how to write a contract to back a user's account. + +Go to [wallet architecture](./architecture.md) for an overview of its architecture and a reference on the interface a wallet must implement. + +Wallets are the applications through which users manage their accounts. Users rely on wallets to browse through their accounts, monitor their balances, and create new accounts. Wallets also store seed phrases and private keys, or interact with external keystores such as hardware wallets. + +Wallets also provide an interface for dapps. Dapps may request access to see the user accounts, in order to show the state of those accounts in the context of the application, and request to send transactions from those accounts as the user interacts with the dapp. + +In addition to these usual responsibilities, wallets in Aztec also need to track private state. This implies keeping a local database of all private notes encrypted for any of the user's accounts, so dapps and contracts can query the user's private state. Aztec wallets are also responsible for producing local proofs of execution for private functions. + +## Account setup + +The first step for any wallet is to let the user set up their [accounts](../accounts/index.md). An account in Aztec is represented on-chain by its corresponding account contract that the user must deploy to begin interacting with the network. This account contract dictates how transactions are authenticated and executed. + +A wallet must support at least one specific account contract implementation, which means being able to deploy such a contract, as well as interacting with it when sending transactions. Code-wise, this requires [implementing the `AccountContract` interface](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/aztec.js/src/account/interface.ts). + +Note that users must be able to receive funds in Aztec before deploying their account. A wallet should let a user generate a [deterministic complete address](../accounts/keys.md#address-keys) without having to interact with the network, so they can share it with others to receive funds. This requires that the wallet pins a specific contract implementation, its initialization arguments, a deployment salt, and a privacy key. These values yield a deterministic address, so when the account contract is actually deployed, it is available at the precalculated address. Once the account contract is deployed, the user can start sending transactions using it as the transaction origin. + +## Transaction lifecycle + +Every transaction in Aztec is broadcast to the network as a zero-knowledge proof of correct execution, in order to preserve privacy. This means that transaction proofs are generated on the wallet and not on a remote node. This is one of the biggest differences with regard to EVM chain wallets. + +A wallet is responsible for **creating** an _execution request_ out of one or more _function calls_ requested by a dapp. For example, a dapp may request a wallet to "invoke the `transfer` function on the contract at `0x1234` with the following arguments", in response to a user action. The wallet turns that into an execution request with the signed instructions to execute that function call from the user's account contract. In an [ECDSA-based account](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/noir-contracts/contracts/account/ecdsa_k_account_contract/src/main.nr), for instance, this is an execution request that encodes the function call in the _entrypoint payload_, and includes its ECDSA signature with the account's signing private key. + +Once the _execution request_ is created, the wallet is responsible for **simulating** and **proving** the execution of its private functions. The simulation yields an execution trace, which can be used to provide the user with a list of side effects of the private execution of the transaction. During this simulation, the wallet is responsible of providing data to the virtual machine, such as private notes, encryption keys, or nullifier secrets. This execution trace is fed into the prover, which returns a zero-knowledge proof that guarantees correct execution and hides all private information. The output of this process is a _transaction object_. + +:::info +Since private functions rely on a UTXO model, the private execution trace of a transaction is determined exclusively by the notes used as inputs. Since these notes are immutable, the trace of a transaction is always the same, so any effects observed during simulation will be exactly the same when the transaction is mined. However, the transaction may be dropped if it attempts to consume a private note that another transaction nullified before it gets mined. Note that this applies only to private function execution. Public functions rely on an account model, similar to Ethereum, so their execution trace depends on the chain's public state at the point they are included in a block, which may have changed since the transaction was simulated locally. +::: + +Finally, the wallet **sends** the resulting _transaction_ object, which includes the proof of execution, to an Aztec Node. The transaction is then broadcasted through the peer-to-peer network, to be eventually picked up by a sequencer and included in a block. + +:::warning +There are no proofs generated as of the Sandbox release. This will be included in a future release before testnet. +::: + +## Authorizing actions + +Account contracts in Aztec expose an interface for other contracts to validate [whether an action is authorized by the account or not](../accounts/index.md#authorizing-actions). For example, an application contract may want to transfer tokens on behalf of a user, in which case the token contract will check with the account contract whether the application is authorized to do so. These actions may be carried out in private or in public functions, and in transactions originated by the user or by someone else. + +Wallets should manage these authorizations, prompting the user when they are requested by an application. Authorizations in private executions come in the form of _auth witnesses_, which are usually signatures over an identifier for an action. Applications can request the wallet to produce an auth witness via the `createAuthWit` call. In public functions, authorizations are pre-stored in the account contract storage, which is handled by a call to an internal function in the account contract implementation. + +## Key management + +As in EVM-based chains, wallets are expected to manage user keys, or provide an interface to hardware wallets or alternative key stores. Keep in mind that in Aztec each account requires [two sets of keys](../accounts/keys.md): privacy keys and authentication keys. Privacy keys are mandated by the protocol and used for encryption and nullification, whereas authentication keys are dependent on the account contract implementation rolled out by the wallet. Should the account contract support it, wallets must provide the user with the means to rotate or recover their authentication keys. + +:::info +Due to limitations in the current architecture, privacy keys need to be available in the wallet software itself and cannot be punted to an external keystore. This restriction may be lifted in a future release. +::: + +## Recipient encryption keys + +Wallets are also expected to manage the public encryption keys of any recipients of local transactions. When creating an encrypted note for a recipient given their address, the wallet needs to provide their [complete address](../accounts/keys.md#address-keys). Recipients broadcast their complete addresses when deploying their account contracts, and wallets collect this information and save it in a local registry for easy access when needed. + +Note that, in order to interact with a recipient who has not yet deployed their account contract (and thus not broadcasted their complete address), it must also be possible to manually add an entry to a wallet's local registry of complete addresses. + +## Private state + +Last but not least, wallets also store the user's private state. Wallets currently rely on brute force decryption, where every new block is downloaded and its encrypted data blobs are attempted to be decrypted with the user decryption keys. Whenever a blob is decrypted properly, it is added to the corresponding account's private state. Note that wallets must also scan for private state in blocks prior to the deployment of a user's account contract, since users may have received private state before deployment. + +:::info +At the time of this writing, all private state is encrypted and broadcasted through the network, and eventually committed to L1. This means that a wallet can reconstruct its entire private state out of its encryption keys in the event of local data loss. +::: + +Encrypted data blobs do not carry any public information as to whom their recipient is. Therefore, it is not possible for a remote node to identify the notes that belong to a user, and it is not possible for a wallet to query a remote node for its private state. As such, wallets need to keep a local database of their accounts private state, in order to be able to answer any queries on their private state. + +Dapps may require access to the user's private state, in order to show information relevant to the current application. For instance, a dapp for a token may require access to the user's private notes in the token contract in order to display the user's balance. It is responsibility of the wallet to require authorization from the user before disclosing private state to a dapp. diff --git a/docs/versioned_docs/version-v0.88.0/aztec/how_to_participate.md b/docs/versioned_docs/version-v0.88.0/aztec/how_to_participate.md new file mode 100644 index 000000000000..220d8adeb842 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/how_to_participate.md @@ -0,0 +1,22 @@ +--- +title: How to Participate? +sidebar_position: 4 +--- + +Decentralization is one of our core values, so we want to encourage participation as much as possible and in any way you can. + +## Improve the protocol + +- Join us at our [Discourse forum](https://discourse.aztec.network/) or [Discord server](https://discord.com/invite/aztec) to discuss all things related to Aztec and share your feedback + +## Contribute code + +- Check out the monorepo on GitHub [here](https://github.com/AztecProtocol/aztec-packages) +- We have some [good first issues](https://github.com/AztecProtocol/aztec-packages/labels/good%20first%20issue) for newcomers +- Anyone can open an issue, so please feel free to create one +- If you've built something for the ecosystem that others should see, add it to the [Awesome-Aztec Repo](https://github.com/AztecProtocol/awesome-aztec) + +## Grants + +- The Aztec Labs Grants Program supports developers building with, and contributing to, the Noir programming language and the Aztec network. Applications can be submitted on the [Grants page](https://aztec.network/grants/) of the Aztec website. +- We are currently operating with a retroactive grants funding model, and we strive to respond back to grants applications with a decision within a few days. Check out our [grants page](https://aztec.network/grants/) for more information diff --git a/docs/versioned_docs/version-v0.88.0/aztec/index.md b/docs/versioned_docs/version-v0.88.0/aztec/index.md new file mode 100644 index 000000000000..bab715dbda1e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/index.md @@ -0,0 +1,156 @@ +--- +title: Aztec Overview +sidebar_position: 0 +tags: [protocol] +--- + +import Image from "@theme/IdealImage"; + +This page outlines Aztec's fundamental technical concepts. It is recommended to read this before diving into building on Aztec. + +## What is Aztec? + +Aztec is a privacy-first Layer 2 on Ethereum. It supports smart contracts with both private & public state and private & public execution. + + + +## High level view + + + +1. A user interacts with Aztec through Aztec.js (like web3js or ethersjs) +2. Private functions are executed in the PXE, which is client-side +3. Proofs and tree updates are sent to the Public VM (running on an Aztec node) +4. Public functions are executed in the Public VM +5. The Public VM rolls up the transactions that include private and public state updates into blocks +6. The block data and proof of a correct state transition are submitted to Ethereum for verification + +## Private and public execution + +Private functions are executed client side, on user devices to maintain maximum privacy. Public functions are executed by a remote network of nodes, similar to other blockchains. These distinct execution environments create a directional execution flow for a single transaction--a transaction begins in the private context on the user's device then moves to the public network. This means that private functions executed by a transaction can enqueue public functions to be executed later in the transaction life cycle, but public functions cannot call private functions. + +### Private Execution Environment (PXE) + +Private functions are executed on the user's device in the Private Execution Environment (PXE, pronounced 'pixie'), then it generates proofs for onchain verification. It is a client-side library for execution and proof-generation of private operations. It holds keys, notes, and generates proofs. It is included in aztec.js, a TypeScript library, and can be run within Node or the browser. + +Note: It is easy for private functions to be written in a detrimentally unoptimized way, because many intuitions of regular program execution do not apply to proving. For more about writing performant private functions in Noir, see [this page](https://noir-lang.org/docs/explainers/explainer-writing-noir) of the Noir documentation. + +### Aztec Virtual Machine (AVM) + +Public functions are executed by the Aztec Virtual Machine (AVM), which is conceptually similar to the Ethereum Virtual Machine (EVM). As such, writing efficient public functions follow the same intuition as gas-efficient solidity contracts. + +The PXE is unaware of the Public VM. And the Public VM is unaware of the PXE. They are completely separate execution environments. This means: + +- The PXE and the Public VM cannot directly communicate with each other +- Private transactions in the PXE are executed first, followed by public transactions + +## Private and public state + +Private state works with UTXOs, which are chunks of data that we call notes. To keep things private, notes are stored in an [append-only UTXO tree](./concepts/advanced/storage/indexed_merkle_tree.mdx), and a nullifier is created when notes are invalidated (aka deleted). Nullifiers are stored in their own [nullifier tree](./concepts/advanced/storage/indexed_merkle_tree.mdx). + +Public state works similarly to other chains like Ethereum, behaving like a public ledger. Public data is stored in a public data tree. + +![Public vs private state](@site/static/img/public-and-private-state-diagram.png) + +Aztec [smart contract](./smart_contracts_overview.md) developers should keep in mind that different data types are used when manipulating private or public state. Working with private state is creating commitments and nullifiers to state, whereas working with public state is directly updating state. + +## Accounts and keys + +### Account abstraction + +Every account in Aztec is a smart contract (account abstraction). This allows implementing different schemes for authorizing transactions, nonce management, and fee payments. + +Developers can write their own account contract to define the rules by which user transactions are authorized and paid for, as well as how user keys are managed. + +Learn more about account contracts [here](./concepts/accounts/index.md). + +### Key pairs + +Each account in Aztec is backed by 3 key pairs: + +- A **nullifier key pair** used for note nullifier computation +- A **incoming viewing key pair** used to encrypt a note for the recipient +- A **outgoing viewing key pair** used to encrypt a note for the sender + +As Aztec has native account abstraction, accounts do not automatically have a signing key pair to authenticate transactions. This is up to the account contract developer to implement. + +## Noir + +Noir is a zero-knowledge domain specific language used for writing smart contracts for the Aztec network. It is also possible to write circuits with Noir that can be verified on or offchain. For more in-depth docs into the features of Noir, go to the [Noir documentation](https://noir-lang.org/). + +## What's next? + +### Start coding + +
+ + +

Developer Getting Started Guide

+
+ + Follow the getting started guide to start developing with the Aztec Sandbox + +
+
+ +### Dive deeper into how Aztec works + +Explore the Concepts for a deeper understanding into the components that make up Aztec: + +
+ + + +

Accounts

+
+ + Learn about Aztec's native account abstraction - every account in Aztec is a smart contract which defines the rules for whether a transaction is or is not valid + +
+ + + +

PXE (pronounced 'pixie')

+
+ + The Private Execution Environment (or PXE) is a client-side library for the execution of private operations + +
+ + + +

State model

+
+ + Aztec has a hybrid public/private state model + +
+ + + +

Storage

+
+ + In Aztec, private data and public data are stored in two trees: a public data tree and a note hashes tree + +
+ + + +

Wallets

+
+ + Wallets expose to dapps an interface that allows them to act on behalf of the user, such as querying private state or sending transactions + +
+ + + +

Protocol Circuits

+
+ + Central to Aztec's operations are circuits in the core protocol and the developer-written Aztec.nr contracts + +
+ +
diff --git a/docs/versioned_docs/version-v0.88.0/aztec/network/_category_.json b/docs/versioned_docs/version-v0.88.0/aztec/network/_category_.json new file mode 100644 index 000000000000..ea3bb9ef6617 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/network/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 8, + "collapsible": true, + "collapsed": true, + "label": "Nodes and Clients" +} diff --git a/docs/versioned_docs/version-v0.88.0/aztec/network/sequencer/index.md b/docs/versioned_docs/version-v0.88.0/aztec/network/sequencer/index.md new file mode 100644 index 000000000000..e541d1fdd544 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/network/sequencer/index.md @@ -0,0 +1,45 @@ +--- +title: Sequencer +tags: [protocol, sequencer] +draft: true +--- + +The sequencer is a module responsible for creating and publishing new rollup blocks. This involves fetching txs from the P2P pool, ordering them, executing any public functions, running them through the rollup circuits, assembling the L2 block, and posting it to the L1 rollup contract along with any contract deployment public data. + +On every new block assembled, it modifies the world state database to reflect the txs processed, but these changes are only committed once the world state synchronizer sees the new block on L1. + +## Components + +The **block builder** is responsible for assembling an L2 block out of a set of processed transactions (we say a tx has been processed if all its function calls have been executed). This involves running the txs through the base, merge, and rollup circuits, updating the world state trees, and building the L2 block object. + +The **prover** generates proofs for every circuit used. For the time being, no proofs are being actually generated, so the only implementation is an empty one. + +The **publisher** deals with sending L1 transactions to the rollup and contract deployment emitter contracts. It is responsible for assembling the Ethereum tx, choosing reasonable gas settings, and monitoring the tx until it gets mined. Note that the current implementation does not handle unstable network conditions (gas price spikes, reorgs, etc). + +The **public processor** executes any public function calls in the transactions. Unlike private function calls, which are resolved in the client, public functions require access to the latest data trees, so they are executed by the sequencer, much like in any non-private L2. + +The **simulator** is an interface to the wasm implementations of the circuits used by the sequencer. + +The **sequencer** pulls txs from the P2P pool, orchestrates all the components above to assemble and publish a block, and updates the world state database. + +## Circuits + +What circuits does the sequencer depend on? + +The **public circuit** is responsible for proving the execution of Brillig (public function bytecode). At the moment, we are using a fake version that actually runs ACIR (intermediate representation for private functions) and does not emit any proofs. + +The **public kernel circuit** then validates the output of the public circuit, and outputs a set of changes to the world state in the same format as the private kernel circuit, meaning we get a standard representation for all txs, regardless of whether public or private functions (or both) were run. The kernel circuits are run iteratively for every recursive call in the transaction. + +The **base rollup circuit** aggregates the changes from two txs (more precisely, the outputs from their kernel circuits once all call stacks are emptied) into a single output. + +The **merge rollup circuit** aggregates two outputs from base rollup circuits into a single one. This circuit is executed recursively until only two outputs are left. This setup means that an L2 block needs to contain always a power-of-two number of txs; if there are not enough, then empty txs are added. + +The **root rollup circuit** consumes two outputs from base or merge rollups and outputs the data to assemble an L2 block. The L1 rollup contract then verifies the proof from this circuit, which implies that all txs included in it were correct. + +## Source code + +You can view the current implementation on Github [here](https://github.com/AztecProtocol/aztec-packages/tree/master/yarn-project/sequencer-client). + +## Further Reading + +- [Sequencer Selection](sequencer_selection.md) diff --git a/docs/versioned_docs/version-v0.88.0/aztec/network/sequencer/sequencer_selection.md b/docs/versioned_docs/version-v0.88.0/aztec/network/sequencer/sequencer_selection.md new file mode 100644 index 000000000000..4fb9ad736644 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/network/sequencer/sequencer_selection.md @@ -0,0 +1,102 @@ +--- +title: Sequencer Selection +tags: [protocol, sequencer] +draft: true +--- + +## Fernet + +_A protocol for random sequencer selection for the Aztec Network. Prior versions:_ + +- [Fernet 52 (Aug 2023)](https://hackmd.io/0cI_xVsaSVi7PToCJ9A2Ew?view) +- [Sequencer Selection: Fernet (Jun 2023)](https://hackmd.io/0FwyoEjKSUiHQsmowXnJPw?both) +- [Sequencer Selection: Fernet (Jun 2023, Forum)](https://discourse.aztec.network/t/proposal-sequencer-selection-fernet/533) + +## Introduction + +_Fair Election Randomized Natively on Ethereum Trustlessly_ (**Fernet**) is a protocol for random _sequencer_ selection. In each iteration, it relies on a VRF to assign a random score to each sequencer in order to rank them. The sequencer with the highest score can propose an ordering for transactions and the block they build upon, and then reveal its contents for the chain to advance under soft finality. _Provers_ must then assemble a proof for this block and submit it to L1 for the block to be finalized. + +## Staking + +Sequencers are required to stake on L1 in order to participate in the protocol. Each sequencer registers a public key when they stake, which will be used to verify their VRF submission. After staking, a sequencer needs to wait for an activation period of N L1 blocks until they can start proposing new blocks. Unstaking also requires a delay to allow for slashing of dishonest behavior. + +## Randomness + +We use a verifiable random function to rank each sequencer. We propose a SNARK of a hash over the sequencer private key and a public input, borrowing [this proposal from the Espresso team](https://discourse.aztec.network/t/proposal-sequencer-selection-irish-coffee/483#vrf-specification-4). The public input is the current block number and a random beacon value from RANDAO. The value sourced from RANDAO should be old enough to prevent L1 reorgs from affecting sequencer selection on L2. This approach allows each individual proposer to secretly calculate the likelihood of being elected for a given block with enough anticipation. + +Alternatively, we can compute the VRF over the _public_ key of each sequencer. This opens the door to DoS attacks, since the leader for each block becomes public in advance, but it also provides clarity to all sequencers as to who the expected leader is, and facilitates off-protocol PBS. + +## Protocol phases + +Each block goes through three main phases in L1: proposal, reveal, and proving. Transactions can achieve soft finality at the end of the reveal phase. + +![](https://hackmd.io/_uploads/SyReMn1An.png) + +### Proposal phase + +During the initial proposal phase, proposers submit to L1 a **block commitment**, which includes a commitment to the transaction ordering in the proposed block, the previous block being built upon, and any additional metadata required by the protocol. + +**Block commitment contents:** + +- Hash of the ordered list of transaction identifiers for the block (with an optional salt). +- Identifier of the previous block in the chain. +- The output of the VRF for this sequencer. + +At the end of the proposal phase, the sequencer with the highest score submitted becomes the leader for this cycle, and has exclusive rights to decide the contents of the block. Note that this plays nicely with private mempools, since having exclusive rights allows the leader to disclose private transaction data in the reveal phase. + +> _In the original version of Fernet, multiple competing proposals could enter the proving phase. Read more about the rationale for this change [here](https://hackmd.io/0cI_xVsaSVi7PToCJ9A2Ew?both#Mitigation-Elect-single-leader-after-proposal-phase)._ + +### Reveal phase + +The sequencer with the highest score in the proposal phase must then upload the block contents to either L1 or a verifiable DA layer. This guarantees that the next sequencer will have all data available to start building the next block, and clients will have the updated state to create new txs upon. It should be safe to assume that, in the happy path, this block would be proven and become final, so this provides early soft finality to transactions in the L2. + +> _This phase is a recent addition and a detour from the original version of Fernet. Read more about the rationale for this addition [here](https://hackmd.io/0cI_xVsaSVi7PToCJ9A2Ew?both#Mitigation-Block-reveal-phase)._ + +Should the leading sequencer fail to reveal the block contents, we flag that block as skipped, and the next sequencer is expected to build from the previous one. We could consider this to be a slashing condition for the sequencer. + +![](https://hackmd.io/_uploads/B1njcnJCn.png) + +### Proving phase + +During this phase, provers work to assemble an aggregated proof of the winning block. Before the end of this phase, it is expected for the block proof to be published to L1 for verification. + +> Prover selection is still being worked on and out of scope of this sequencer selection protocol. + +Once the proof for the winning block is submitted to L1, the block becomes final, assuming its parent block in the chain is also final. This triggers payouts to sequencer and provers (if applicable depending on the proving network design). + +**Canonical block selection:** + +- Has been proven during the proving phase. +- Its contents have been submitted to the DA layer in the reveal phase. +- It had the highest score on the proposal phase. +- Its referenced previous block is also canonical. + +## Next block + +The cycle for block N+1 (ie from the start of the proposal phase until the end of the proving phase) can start at the end of block N reveal phase, where the network has all data available on L1 or a DA to construct the next block. + +![](https://hackmd.io/_uploads/SJbPKJe0n.png) + +The only way to trigger an L2 reorg (without an L1 one) is if block N is revealed but doesn't get proven. In this case, all subsequent blocks become invalidated and the chain needs to restart from block N-1. + +![](https://hackmd.io/_uploads/HkMDHxxC2.png) + +To mitigate the effect of wasted effort by all sequencers from block N+1 until the reorg, we could implement uncle rewards for these sequencers. And if we are comfortable with slashing, take those rewards out of the pocket of the sequencer that failed to finalize their block. + +## Batching + +> _Read more approaches to batching [here](https://hackmd.io/0cI_xVsaSVi7PToCJ9A2Ew?both#Batching)._ + +As an extension to the protocol, we can bake in batching of multiple blocks. Rather than creating one proof per block, we can aggregate multiple blocks into a single proof, in order to amortize the cost of verifying the root rollup ZKP on L1, thus reducing fees. + +The tradeoff in batching is delayed finalization: if we are not posting proofs to L1 for every block, then the network needs to wait until the batch proof is submitted for finalization. This can also lead to deeper L2 reorgs. + +In a batching model, proving for each block happens immediately as the block is revealed, same as usual. But the resulting proof is not submitted to L1: instead, it is aggregated into the proof of the next block. + +![](https://hackmd.io/_uploads/H1Y61ABJT.png) + +Here all individual block proofs are valid as candidates to finalise the current batch. This opens the door to dynamic batch sizes, so the proof could be verified on L1 when it's economically convenient. + +## Resources + +- [Excalidraw diagrams](https://excalidraw.com/#json=DZcYDUKVImApNjj17KhAf,fMbieqJpOysX9obVitUDEA) diff --git a/docs/versioned_docs/version-v0.88.0/aztec/network_overview.md b/docs/versioned_docs/version-v0.88.0/aztec/network_overview.md new file mode 100644 index 000000000000..787a59d2a96a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/network_overview.md @@ -0,0 +1,12 @@ +--- +title: Overview +sidebar_position: 0 +tags: [protocol] +draft: true +--- + +# Aztec Network Infrastructure + +Explore this section to learn about [sequencers](../aztec/network/sequencer/index.md) and the [sequencer selection process](../aztec/network/sequencer/sequencer_selection.md). + +More information will be added here as we develop the protocol. \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/aztec/roadmap/_category_.json b/docs/versioned_docs/version-v0.88.0/aztec/roadmap/_category_.json new file mode 100644 index 000000000000..231aa372f13e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/roadmap/_category_.json @@ -0,0 +1,7 @@ +{ + "label": "Roadmap", + "collapsible": true, + "collapsed": true, + "link": { "type": "doc", "id": "roadmap/index" } + +} diff --git a/docs/versioned_docs/version-v0.88.0/aztec/roadmap/features_initial_ldt.md b/docs/versioned_docs/version-v0.88.0/aztec/roadmap/features_initial_ldt.md new file mode 100644 index 000000000000..2ccd80366216 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/roadmap/features_initial_ldt.md @@ -0,0 +1,22 @@ +--- +title: Sandbox Features Roadmap +sidebar_position: 0 +tags: [sandbox] +--- + +The Aztec Sandbox is intended to provide developers with a lightweight and fast local node, running alongside a PXE. + +You can learn more about running the Sandbox [here](../../developers/reference/environment_reference/sandbox-reference.md). + +Developers should be able to quickly spin up local, emulated instances of an Ethereum blockchain and an Aztec encrypted rollup, and start deploying private contracts and submitting private txs. + +The sandbox allows developers to: + +- Write and deploy Aztec contracts +- Leverage private and public state variables in contracts +- Write private and public functions in contracts +- Call private and public functions on other Aztec contracts (contract composability) +- Send messages between Aztec and Ethereum contracts +- Interact with the Aztec network using a familiar Typescript SDK ([aztec.js](https://github.com/AztecProtocol/aztec-packages/tree/master/yarn-project/aztec.js)) +- Start only a local PXE or Aztec node individually. +- Start a P2P bootstrap node for Aztec nodes to connect and discover each other. diff --git a/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/contract_creation.md b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/contract_creation.md new file mode 100644 index 000000000000..eaeebaf2b37d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/contract_creation.md @@ -0,0 +1,86 @@ +--- +title: Contract Deployment +sidebar_position: 1 +tags: [contracts, protocol] +--- + +In the Aztec protocol, contracts are deployed as _instances_ of contract _classes_. The deployment process consists of two main steps: first registering the contract _class_ (if not already registered), and then creating a contract _instance_ that references this class. + +## Contract Classes + +A contract class is a collection of state variable declarations, and related private, public and utility functions. Contract classes don't have state, they just define code (storage structure and function logic). A contract class cannot be called; only a contract instance can be called. + +### Key Benefits of Contract Classes + +Contract classes simplify code reuse by making implementations a first-class citizen in the protocol. With a single class registration, multiple contract instances can be deployed that reference it, reducing deployment costs. Classes also facilitate upgradability by decoupling state from code, making it easier for an instance to switch to different code while retaining its state. + +### Structure of a Contract Class + +A contract class includes: + +- `artifact_hash`: Hash of the contract artifact +- `private_functions`: List of individual private functions, including constructors +- `packed_public_bytecode`: Packed bytecode representation of the AVM bytecode for all public functions + +The specification of the artifact hash is not enforced by the protocol. It should include commitments to utility functions code and compilation metadata. It is intended to be used by clients to verify that an off-chain fetched artifact matches a registered class. + +### Contract Class Registration + +A contract class is registered by calling a private `register` function in a canonical `ContractClassRegisterer` contract, which emits a Registration Nullifier. This process guarantees that the public bytecode for a contract class is publicly available, which is required for deploying contract instances. + +Contract class registration can be skipped if there are no public functions in the contract class, and the contract will still be usable privately. However, the contract class must be registered if it contains public functions, as these functions need to be publicly verifiable. + +If you have a contract with public functions, you must either register the contract class to deploy or a contract, or skip the public deployment step, in which case only the private functions will be callable. + +## Contract Instances + +A deployed contract is effectively an instance of a contract class. It always references a contract class, which determines what code it executes when called. A contract instance has both private and public state, as well as an address that serves as its identifier. + +### Structure of a Contract Instance + +A contract instance includes: + +- `version`: Version identifier, initially one +- `salt`: User-generated pseudorandom value for uniqueness +- `deployer`: Optional address of the contract deployer. Zero for universal deployment +- `contract_class_id`: Identifier of the contract class for this instance +- `initialization_hash`: Hash of the selector and arguments to the constructor +- `public_keys_hash`: Optional hash of public keys used for encryption and nullifying + +### Instance Address + +The address of a contract instance is computed as the hash of the elements in its structure. This computation is deterministic, allowing users to precompute the expected deployment address of their contract, including account contracts. + +### Contract Initialization vs. Public Deployment + +Aztec makes an important distinction between initialization and public deployment: + +1. **Initialization**: A contract instance is considered Initialized once it emits an initialization nullifier, meaning it can only be initialized once. The default state for any address is to be uninitialized. A user who knows the preimage of the address can still issue a private call into a function in the contract, as long as that function doesn't assert that the contract has been initialized. +2. **Public Deployment**: A Contract Instance is considered to be publicly deployed when it has been broadcast to the network via a canonical `ContractInstanceDeployer` contract, which also emits a deployment nullifier. All public function calls to an undeployed address must fail, since the contract class for it is not known to the network. + +### Initialization + +Contract constructors are not enshrined in the protocol, but handled at the application circuit level. Constructors are methods used for initializing a contract, either private or public, and contract classes may declare more than a single constructor. They can be declared by the `#[initializer]` macro. You can read more about how to use them on the [Defining Initializer Functions](../../developers/guides/smart_contracts/writing_contracts/initializers.md) page. + +A contract must ensure: + +- It is initialized at most once +- It is initialized using the method and arguments defined in its address preimage +- It is initialized by its deployer (if non-zero) +- Functions dependent on initialization cannot be invoked until the contract is initialized + +Functions in a contract may skip the initialization check. + +## Verification of Executed Code + +The protocol circuits, both private and public, are responsible for verifying that the code loaded for a given function execution matches the expected one. This includes checking that the `contract_class_id` of the called address is the expected one and that the function selector being executed is part of the `contract_class_id`. + +## Genesis Contracts + +The `ContractInstanceDeployer` and `ContractClassRegisterer` contracts exist from the genesis of the Aztec Network, as they are necessary for deploying other contracts to the network. Their nullifiers are pre-inserted into the genesis nullifier tree. + +This modular approach to contract deployment creates a flexible system that supports diverse use cases, from public applications to private contract interactions, while maintaining the security and integrity of the Aztec protocol. + +## Further reading + +To see how to deploy a contract in practice, check out the [dapp development tutorial](../../developers/tutorials/codealong/js_tutorials/simple_dapp/index.md). diff --git a/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/contract_structure.md b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/contract_structure.md new file mode 100644 index 000000000000..cb792aff1956 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/contract_structure.md @@ -0,0 +1,41 @@ +--- +title: Contract Structure +sidebar_position: 0 +tags: [contracts] +--- + +A contract is a collection of persistent state variables and [functions](./functions/index.md) which may manipulate these variables. Functions and state variables within a contract's scope are said to belong to that contract. A contract can only access and modify its own state. If a contract wishes to access or modify another contract's state, it must make a call to an external function of the other contract. For anything to happen on the Aztec network, an external function of a contract needs to be called. + +## Contract + +A contract may be declared and given a name using the `contract` keyword (see snippet below). By convention, contracts are named in `PascalCase`. + +```rust title="contract keyword" +// highlight-next-line +contract MyContract { + + // Imports + + // Storage + + // Functions +} +``` + +:::info A note for vanilla Noir devs +There is no [`main()`](https://noir-lang.org/docs/getting_started/project_breakdown/#mainnr) function within a Noir `contract` scope. More than one function can be an entrypoint. +::: + +## Directory structure + +Here's a common layout for a basic Aztec.nr Contract project: + +```title="layout of an aztec contract project" +─── my_aztec_contract_project + ├── src + │ ├── main.nr <-- your contract + └── Nargo.toml <-- package and dependency management +``` + +- See the vanilla Noir docs for [more info on packages](https://noir-lang.org/docs/noir/modules_packages_crates/crates_and_packages). +- You can review the structure of a complete contract in the NFT contract tutorial [here](../../developers/tutorials/codealong/contract_tutorials/nft_contract.md). diff --git a/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/contract_upgrades.md b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/contract_upgrades.md new file mode 100644 index 000000000000..9aed952bff4d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/contract_upgrades.md @@ -0,0 +1,185 @@ +--- +title: Contract Upgrades +sidebar_position: 2 +tags: [contracts] +--- + +For familiarity we've used terminology like "deploying a contract instance of a contract class". When considering how it works with contract upgrades it helps to be more specific. + +Each deployed contract holds its state, and refers to a class id for its code. Upgrading a contracts implementation is achieved by updating its "current class id" to the new class id, whilst retaining the "original class id" for reasons explained below. + +## Original class id + +A contract keeps track of the original contract class that it was deployed with, which is the "original" class id. It is this original class that is used when calculating and verifying the contract's [address](contract_creation#instance-address). +This variable remains unchanged even if a contract is upgraded. + +## Current class id + +When a contract is first deployed, its current class ID is set equal to its original class ID. The current class ID determines which code implementation the contract actually executes. + +During an upgrade: + +- The original class ID remains unchanged +- The current class ID is updated to refer to the new implementation +- All contract state/data is preserved + +## How to upgrade + +Contract upgrades in Aztec have to be performed by the contract that wishes to be upgraded calling the Contract instance deployer: + +```rust +use dep::aztec::protocol_types::contract_class_id::ContractClassId; +use contract_instance_deployer::ContractInstanceDeployer; + +#[private] +fn update_to(new_class_id: ContractClassId) { + ContractInstanceDeployer::at(DEPLOYER_CONTRACT_ADDRESS) + .update(new_class_id) + .enqueue(&mut context); +} +``` + +The update function in the deployer is a public function, so you can enqueue it from a private function like the example or call it from a public function directly. + +:::note +Recall that `#[private]` means calling this function preserves privacy, and it still CAN be called externally by anyone. +So the `update_to` function above allows anyone to update the contract that implements it. A more complete implementation should have a proper authorization systems to secure contracts from malicious upgrades. +::: + +Contract upgrades are implemented using a SharedMutable storage variable in the deployer protocol contract, since the upgrade applies to both public and private functions. +This means that they have a delay before entering into effect. The default delay is `3600` blocks but can be configured by the contract: + +```rust +use dep::aztec::protocol_types::contract_class_id::ContractClassId; +use contract_instance_deployer::ContractInstanceDeployer; + +#[private] +fn set_update_delay(new_delay: u32) { + ContractInstanceDeployer::at(DEPLOYER_CONTRACT_ADDRESS) + .set_update_delay(new_delay) + .enqueue(&mut context); +} +``` + +Where `new_delay` is denominated in blocks. However, take into account that changing the update delay also has as its delay that is the previous delay. So the first delay change will take 3600 blocks to take into effect. + +:::info +The update delay cannot be set lower than `25` blocks +::: + +When sending a transaction, the max_block_number of your TX will be the current block number you're simulating with + the minimum of the update delays that you're interacting with. +If your TX interacts with a contract that can be upgraded in 30 blocks and another one that can be upgraded in 300 blocks, the max_block_number will be current block + 30. +Note that this can be even lower if there is an upgrade pending in one of the contracts you're interacting with. +If the contract you interacted with will upgrade in 2 blocks, the max block number of your tx will be current + 1 blocks. +Other SharedMutable storage variables read in your tx might reduce this max_block_number further. + +:::note +Only deployed contract instances can upgrade or change its upgrade delay currently. This restriction might be lifted in the future. +::: + +### Upgrade Process + +1. **Register New Implementation** + + - First, register the new contract class if it contains public functions + - The new implementation must maintain state variable compatibility with the original contract + +2. **Perform Upgrade** + + - Call the update function with the new contract class ID + - The contract's original class ID remains unchanged + - The current class ID is updated to the new implementation + - All contract state and data are preserved + +3. **Verify Upgrade** + - After upgrade, the contract will execute functions from the new implementation + - The contract's address remains the same since it's based on the original class ID + - Existing state variables and their values are preserved + +### How to interact with an upgraded contract + +The PXE stores the contract instances and classes in a local database. When a contract is updated, in order to interact with it we need to pass the new artifact to the PXE, since the protocol doesn't publish artifacts. +Consider this contract as an example: + +```rust +#[aztec] +contract Updatable { +... + + #[private] + fn update_to(new_class_id: ContractClassId) { + ContractInstanceDeployer::at(DEPLOYER_CONTRACT_ADDRESS).update(new_class_id).enqueue( + &mut context, + ); + } +... +``` + +You'd upgrade it in aztec.js doing something similar to this: + +```typescript +const contract = await UpdatableContract.deploy(wallet, ...args) + .send() + .deployed(); +const updatedContractClassId = ( + await getContractClassFromArtifact(UpdatedContractArtifact) +).id; +await contract.methods.update_to(updatedContractClassId).send().wait(); +``` + +Now, when the update has happened, calling `at` with the new contract artifact will automatically update the contract instance in the PXE if it's outdated: + +```typescript +// 'at' will call PXE updateContract if outdated +const updatedContract = await UpdatedContract.at(address, wallet); +``` + +If you try to call `at` with a different contract that is not the current version, it'll fail + +```typescript +// throws when trying to update the PXE instance to RandomContract +// since the current one is UpdatedContract +await RandomContract.at(address, wallet); +``` + +### Security Considerations + +1. **Access Control** + + - Implement proper access controls for upgrade functions + - Consider customizing the upgrades delay for your needs using `set_update_delay` + +2. **State Compatibility** + + - Ensure new implementation is compatible with existing state + - Maintain the same storage layout to prevent data corruption + +3. **Testing** + + - Test upgrades thoroughly in a development environment + - Verify all existing functionality works with the new implementation + +### Example + +```rust +contract Updatable { + #[private] + fn update_to(new_class_id: ContractClassId) { + // TODO: Add access control + assert(context.msg_sender() == owner, "Unauthorized"); + + // Perform the upgrade + ContractInstanceDeployer::at(DEPLOYER_CONTRACT_ADDRESS) + .update(new_class_id) + .enqueue(&mut context); + } + + #[private] + fn set_update_delay(new_delay: u32) { + // TODO: Add access control + ContractInstanceDeployer::at(DEPLOYER_CONTRACT_ADDRESS) + .set_update_delay(new_delay) + .enqueue(&mut context); + } +} +``` diff --git a/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/attributes.md b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/attributes.md new file mode 100644 index 000000000000..84523c9a10b9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/attributes.md @@ -0,0 +1,483 @@ +--- +title: Function Attributes and Macros +sidebar_position: 4 +tags: [functions] +--- + +On this page you will learn about function attributes and macros. + +If you are looking for a reference of function macros, go [here](../../../developers/reference/smart_contract_reference/macros.md). + +## Private functions #[private] + +A private function operates on private information, and is executed by the user on their device. Annotate the function with the `#[private]` attribute to tell the compiler it's a private function. This will make the [private context](./context.md#the-private-context) available within the function's execution scope. The compiler will create a circuit to define this function. + +`#[private]` is just syntactic sugar. At compile time, the Aztec.nr framework inserts code that allows the function to interact with the [kernel](../../../aztec/concepts/advanced/circuits/kernels/private_kernel.md). + +To help illustrate how this interacts with the internals of Aztec and its kernel circuits, we can take an example private function, and explore what it looks like after Aztec.nr's macro expansion. + +#### Before expansion + +```rust title="simple_macro_example" showLineNumbers +#[private] +fn simple_macro_example(a: Field, b: Field) -> Field { + a + b +} +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L156-L161 + + +#### After expansion + +```rust title="simple_macro_example_expanded" showLineNumbers +fn simple_macro_example_expanded( + // ************************************************************ + // The private context inputs are made available to the circuit by the kernel + inputs: PrivateContextInputs, + // ************************************************************ + // Our original inputs! + a: Field, + b: Field, // The actual return type of our circuit is the PrivateCircuitPublicInputs struct, this will be the + // input to our kernel! +) -> PrivateCircuitPublicInputs { + // ************************************************************ + // The hasher is a structure used to generate a hash of the circuits inputs. + let mut args_hasher = dep::aztec::hash::ArgsHasher::new(); + args_hasher.add(a); + args_hasher.add(b); + // The context object is created with the inputs and the hash of the inputs + let mut context = PrivateContext::new(inputs, args_hasher.hash()); + let mut storage = Storage::init(&mut context); + // ************************************************************ + // Our actual program + let result = a + b; + // ************************************************************ + // Return values are pushed into the context + let mut return_hasher = dep::aztec::hash::ArgsHasher::new(); + return_hasher.add(result); + context.set_return_hash(return_hasher); + // The context is returned to be consumed by the kernel circuit! + context.finish() + // ************************************************************ +} +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L167-L212 + + +#### The expansion broken down + +Viewing the expanded Aztec contract uncovers a lot about how Aztec contracts interact with the kernel. To aid with developing intuition, we will break down each inserted line. + +**Receiving context from the kernel.** +```rust title="context-example-inputs" showLineNumbers +inputs: PrivateContextInputs, +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L171-L173 + + +Private function calls are able to interact with each other through orchestration from within the kernel circuits. The kernel circuit forwards information to each contract function (recall each contract function is a circuit). This information then becomes part of the private context. +For example, within each private function we can access some global variables. To access them we can call on the `context`, e.g. `context.chain_id()`. The value of the chain ID comes from the values passed into the circuit from the kernel. + +The kernel checks that all of the values passed to each circuit in a function call are the same. + +**Returning the context to the kernel.** +```rust title="context-example-return" showLineNumbers +) -> PrivateCircuitPublicInputs { +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L179-L181 + + +The contract function must return information about the execution back to the kernel. This is done through a rigid structure we call the `PrivateCircuitPublicInputs`. + +> _Why is it called the `PrivateCircuitPublicInputs`?_ +> When verifying zk programs, return values are not computed at verification runtime, rather expected return values are provided as inputs and checked for correctness. Hence, the return values are considered public inputs. + +This structure contains a host of information about the executed program. It will contain any newly created nullifiers, any messages to be sent to l2 and most importantly it will contain the return values of the function. + +**Hashing the function inputs.** +```rust title="context-example-hasher" showLineNumbers +let mut args_hasher = dep::aztec::hash::ArgsHasher::new(); +args_hasher.add(a); +args_hasher.add(b); +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L184-L188 + + +_What is the hasher and why is it needed?_ + +Inside the kernel circuits, the inputs to functions are reduced to a single value; the inputs hash. This prevents the need for multiple different kernel circuits; each supporting differing numbers of inputs. The hasher abstraction that allows us to create an array of all of the inputs that can be reduced to a single value. + +**Creating the function's context.** +```rust title="context-example-context" showLineNumbers +let mut context = PrivateContext::new(inputs, args_hasher.hash()); +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L190-L192 + + +Each Aztec function has access to a [context](context) object. This object, although labelled a global variable, is created locally on a users' device. It is initialized from the inputs provided by the kernel, and a hash of the function's inputs. + +```rust title="context-example-context-return" showLineNumbers +let mut return_hasher = dep::aztec::hash::ArgsHasher::new(); +return_hasher.add(result); +context.set_return_hash(return_hasher); +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L201-L205 + + +We use the kernel to pass information between circuits. This means that the return values of functions must also be passed to the kernel (where they can be later passed on to another function). +We achieve this by pushing return values to the execution context, which we then pass to the kernel. + +**Making the contract's storage available** +```rust title="storage-example-context" showLineNumbers +let mut storage = Storage::init(&mut context); +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L193-L195 + + +When a `Storage` struct is declared within a contract, the `storage` keyword is made available. As shown in the macro expansion above, this calls the init function on the storage struct with the current function's context. + +Any state variables declared in the `Storage` struct can now be accessed as normal struct members. + +**Returning the function context to the kernel.** +```rust title="context-example-finish" showLineNumbers +context.finish() +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L207-L209 + + +This function takes the application context, and converts it into the `PrivateCircuitPublicInputs` structure. This structure is then passed to the kernel circuit. + +## Utility functions #[utility] + +Contract functions marked with `#[utility]` are used to perform state queries from an off-chain client (from both private and public state!) or to modify local contract-related PXE state (e.g. when processing logs in Aztec.nr), and are never included in any transaction. No guarantees are made on the correctness of the result since the entire execution is unconstrained and heavily reliant on [oracle calls](https://noir-lang.org/docs/explainers/explainer-oracle). + +Any programming language could be used to construct these queries, since all they do is perform arbitrary computation on data that is either publicly available from any node, or locally available from the PXE. Utility functions exist as Noir contract code because they let developers utilize the rest of the contract code directly by being part of the same Noir crate, and e.g. use the same libraries, structs, etc. instead of having to rely on manual computation of storage slots, struct layout and padding, and so on. + +A reasonable mental model for them is that of a Solidity `view` function that can never be called in any transaction, and is only ever invoked via `eth_call`. Note that in these the caller assumes that the node is acting honestly by executing the true contract bytecode with correct blockchain state, the same way the Aztec version assumes the oracles are returning legitimate data. Unlike `view` functions however, `utility` functions can modify local off-chain PXE state via oracle calls - this can be leveraged for example to process messages delivered off-chain and then notify PXE of newly discovered notes. + +When a utility function is called, it prompts the ACIR simulator to + +1. generate the execution environment +2. execute the function within this environment + +To generate the environment, the simulator gets the block header from the [PXE database](../../concepts/pxe/index.md#database) and passes it along with the contract address to `UtilityExecutionOracle`. This creates a context that simulates the state of the blockchain at a specific block, allowing the utility function to access and interact with blockchain data as it would appear in that block, but without affecting the actual blockchain state. + +Once the execution environment is created, `runUtility` function is invoked on the simulator: + +```typescript title="execute_utility_function" showLineNumbers +/** + * Runs a utility function. + * @param call - The function call to execute. + * @param authwits - Authentication witnesses required for the function call. + * @param scopes - Optional array of account addresses whose notes can be accessed in this call. Defaults to all + * accounts if not specified. + * @returns A decoded ABI value containing the function's return data. + */ +public async runUtility(call: FunctionCall, authwits: AuthWitness[], scopes?: AztecAddress[]): Promise { + await verifyCurrentClassId(call.to, this.executionDataProvider); + + const entryPointArtifact = await this.executionDataProvider.getFunctionArtifact(call.to, call.selector); + + if (entryPointArtifact.functionType !== FunctionType.UTILITY) { + throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`); + } + + const oracle = new UtilityExecutionOracle(call.to, authwits, [], this.executionDataProvider, undefined, scopes); + + try { + this.log.verbose(`Executing utility function ${entryPointArtifact.name}`, { + contract: call.to, + selector: call.selector, + }); + + const initialWitness = toACVMWitness(0, call.args); + const acirExecutionResult = await this.simulator + .executeUserCircuit(initialWitness, entryPointArtifact, new Oracle(oracle).toACIRCallback()) + .catch((err: Error) => { + err.message = resolveAssertionMessageFromError(err, entryPointArtifact); + throw new ExecutionError( + err.message, + { + contractAddress: call.to, + functionSelector: call.selector, + }, + extractCallStack(err, entryPointArtifact.debug), + { cause: err }, + ); + }); + + const returnWitness = witnessMapToFields(acirExecutionResult.returnWitness); + this.log.verbose(`Utility simulation for ${call.to}.${call.selector} completed`); + return decodeFromAbi(entryPointArtifact.returnTypes, returnWitness); + } catch (err) { + throw createSimulationError(err instanceof Error ? err : new Error('Unknown error during private execution')); + } +} +``` +> Source code: yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts#L178-L227 + + +This: + +1. Prepares the ACIR for execution +2. Converts `args` into a format suitable for the ACVM (Abstract Circuit Virtual Machine), creating an initial witness (witness = set of inputs required to compute the function). `args` might be an oracle to request a user's balance +3. Executes the function in the ACVM, which involves running the ACIR with the initial witness and the context. If requesting a user's balance, this would query the balance from the PXE database +4. Extracts the return values from the `partialWitness` and decodes them based on the artifact to get the final function output. The artifact is the compiled output of the contract, and has information like the function signature, parameter types, and return types + +Beyond using them inside your other functions, they are convenient for providing an interface that reads storage, applies logic and returns values to a UI or test. Below is a snippet from exposing the `balance_of_private` function from a token implementation, which allows a user to easily read their balance, similar to the `balanceOf` function in the ERC20 standard. + +```rust title="balance_of_private" showLineNumbers +#[utility] +pub(crate) unconstrained fn balance_of_private(owner: AztecAddress) -> u128 { + storage.balances.at(owner).balance_of() +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L720-L725 + + +:::info +Note, that utility functions can have access to both private and (historical) public data when executed on the user's device. This is possible since these functions are not invoked as part of transactions, so we don't need to worry about preventing a contract from e.g. accidentally using stale or unverified public state. +::: + +## Public functions #[public] + +A public function is executed by the sequencer and has access to a state model that is very similar to that of the EVM and Ethereum. Even though they work in an EVM-like model for public transactions, they are able to write data into private storage that can be consumed later by a private function. + +:::note +All data inserted into private storage from a public function will be publicly viewable (not private). +::: + +To create a public function you can annotate it with the `#[public]` attribute. This will make the public context available within the function's execution scope. + +```rust title="set_minter" showLineNumbers +#[public] +fn set_minter(minter: AztecAddress, approve: bool) { + assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); + storage.minters.at(minter).write(approve); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L178-L188 + + +Under the hood: + +- Context Creation: The macro inserts code at the beginning of the function to create a`PublicContext` object: + +```rust +let mut context = PublicContext::new(args_hasher); +``` + +This context provides access to public state and transaction information + +- Storage Access: If the contract has a storage struct defined, the macro inserts code to initialize the storage: + +```rust +let storage = Storage::init(&mut context); +``` + +- Function Body Wrapping: The original function body is wrapped in a new scope that handles the context and return value +- Visibility Control: The function is marked as pub, making it accessible from outside the contract. +- Unconstrained Execution: Public functions are marked as unconstrained, meaning they don't generate proofs and are executed directly by the sequencer. + +## Constrained `view` Functions #[view] + +The `#[view]` attribute can be applied to a `#[private]` or a `#[public]` function and it guarantees that the function cannot modify any contract state (just like `view` functions in Solidity). + +## `Initializer` Functions #[initializer] + +This is used to designate functions as initializers (or constructors) for an Aztec contract. These functions are responsible for setting up the initial state of the contract when it is first deployed. The macro does two important things: + +- `assert_initialization_matches_address_preimage(context)`: This checks that the arguments and sender to the initializer match the commitments from the address preimage +- `mark_as_initialized(&mut context)`: This is called at the end of the function to emit the initialization nullifier, marking the contract as fully initialized and ensuring this function cannot be called again + +Key things to keep in mind: + +- A contract can have multiple initializer functions defined, but only one initializer function should be called for the lifetime of a contract instance +- Other functions in the contract will have an initialization check inserted, ie they cannot be called until the contract is initialized, unless they are marked with [`#[noinitcheck]`](#noinitcheck) + +## #[noinitcheck] + +In normal circumstances, all functions in an Aztec contract (except initializers) have an initialization check inserted at the beginning of the function body. This check ensures that the contract has been initialized before any other function can be called. However, there may be scenarios where you want a function to be callable regardless of the contract's initialization state. This is when you would use `#[noinitcheck]`. + +When a function is annotated with `#[noinitcheck]`: + +- The Aztec macro processor skips the [insertion of the initialization check](#initializer-functions-initializer) for this specific function +- The function can be called at any time, even if the contract hasn't been initialized yet + +## `Internal` functions #[internal] + +This macro inserts a check at the beginning of the function to ensure that the caller is the contract itself. This is done by adding the following assertion: + +```rust +assert(context.msg_sender() == context.this_address(), "Function can only be called internally"); +``` + +## Implementing notes + +The `#[note]` attribute is used to define notes in Aztec contracts. Learn more about notes [here](../../concepts/storage/index.md). + +When a struct is annotated with `#[note]`, the Aztec macro applies a series of transformations and generates implementations to turn it into a note that can be used in contracts to store private data. + +1. **Note Interface Implementation**: The macro automatically implements the `NoteType`, `NoteHash` and `Packable` traits for the annotated struct. This includes the following methods: + + - `get_id` + - `compute_note_hash` + - `compute_nullifier` + - `pack` + - `unpack` + +2. **Property Metadata**: A separate struct is generated to describe the note's fields, which is used for efficient retrieval of note data + +3. **Export Information**: The note type and its ID are automatically exported + +### Before expansion + +Here is how you could define a custom note: + +```rust +#[note] +struct CustomNote { + data: Field, + owner: Address, +} +``` + +### After expansion + +```rust +impl NoteType for CustomNote { + fn get_id() -> Field { + // Assigned by macros by incrementing a counter + 2 + } +} + +impl NoteHash for CustomNote { + fn compute_note_hash(self, storage_slot: Field) -> Field { + let inputs = array_concat(self.pack(), [storage_slot]); + poseidon2_hash_with_separator(inputs, GENERATOR_INDEX__NOTE_HASH) + } + + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { + let owner_npk_m_hash = get_public_keys(self.owner).npk_m.hash(); + let secret = context.request_nsk_app(owner_npk_m_hash); + poseidon2_hash_with_separator( + [ + note_hash_for_nullify, + secret + ], + GENERATOR_INDEX__NOTE_NULLIFIER as Field + ) + } + + unconstrained fn compute_nullifier_unconstrained(self, storage_slot: Field, contract_address: AztecAddress, note_nonce: Field) -> Field { + // We set the note_hash_counter to 0 as the note is not transient and the concept of transient note does + // not make sense in an unconstrained context. + let retrieved_note = RetrievedNote { note: self, contract_address, nonce: note_nonce, note_hash_counter: 0 }; + let note_hash_for_nullify = compute_note_hash_for_nullify(retrieved_note, storage_slot); + let owner_npk_m_hash = get_public_keys(self.owner).npk_m.hash(); + let secret = get_nsk_app(owner_npk_m_hash); + poseidon2_hash_with_separator( + [ + note_hash_for_nullify, + secret + ], + GENERATOR_INDEX__NOTE_NULLIFIER as Field + ) + } +} + +impl CustomNote { + pub fn new(x: [u8; 32], y: [u8; 32], owner: AztecAddress) -> Self { + CustomNote { x, y, owner } + } +} + +impl Packable<2> for CustomNote { + fn pack(self) -> [Field; 2] { + [self.data, self.owner.to_field()] + } + + fn unpack(packed_content: [Field; 2]) -> CustomNote { + CustomNote { data: packed_content[0], owner: AztecAddress { inner: packed_content[1] } } + } +} + +struct CustomNoteProperties { + data: aztec::note::note_getter_options::PropertySelector, + owner: aztec::note::note_getter_options::PropertySelector, +} +``` + +Key things to keep in mind: + +- Developers can override any of the auto-generated methods by specifying a note interface +- The note's fields are automatically serialized and deserialized in the order they are defined in the struct + +## Storage struct #[storage] + +The `#[storage]` attribute is used to define the storage structure for an Aztec contract. + +When a struct is annotated with `#[storage]`, the macro does this under the hood: + +1. **Context Injection**: injects a `Context` generic parameter into the storage struct and all its fields. This allows the storage to interact with the Aztec context, eg when using `context.msg_sender()` + +2. **Storage Implementation Generation**: generates an `impl` block for the storage struct with an `init` function. The developer can override this by implementing a `impl` block themselves + +3. **Storage Slot Assignment**: automatically assigns storage slots to each field in the struct based on their serialized length + +4. **Storage Layout Generation**: a `StorageLayout` struct and a global variable are generated to export the storage layout information for use in the contract artifact + +### Before expansion + +```rust +#[storage] +struct Storage { + balance: PublicMutable, + owner: PublicMutable
, + token_map: Map, +} +``` + +### After expansion + +```rust +struct Storage { + balance: PublicMutable, + owner: PublicMutable, + token_map: Map, +} + +impl Storage { + fn init(context: Context) -> Self { + Storage { + balance: PublicMutable::new(context, 1), + owner: PublicMutable::new(context, 2), + token_map: Map::new(context, 3, |context, slot| Field::new(context, slot)), + } + } +} + +struct StorageLayout { + balance: dep::aztec::prelude::Storable, + owner: dep::aztec::prelude::Storable, + token_map: dep::aztec::prelude::Storable, +} + +#[abi(storage)] +global CONTRACT_NAME_STORAGE_LAYOUT = StorageLayout { + balance: dep::aztec::prelude::Storable { slot: 1 }, + owner: dep::aztec::prelude::Storable { slot: 2 }, + token_map: dep::aztec::prelude::Storable { slot: 3 }, +}; +``` + +Key things to keep in mind: + +- Only one storage struct can be defined per contract +- `Map` types and private `Note` types always occupy a single storage slot + +## Further reading + +- [Macros reference](../../../developers/reference/smart_contract_reference/macros.md) +- [How do macros work](./attributes.md) diff --git a/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/context.md b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/context.md new file mode 100644 index 000000000000..0bb521108a93 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/context.md @@ -0,0 +1,221 @@ +--- +title: Understanding Function Context +sidebar_position: 1 +tags: [functions, context] +--- + +import Image from '@theme/IdealImage'; + +## What is the context + +The context is an object that is made available within every function in `Aztec.nr`. As mentioned in the [kernel circuit documentation](../../concepts/advanced/circuits/kernels/private_kernel.md). At the beginning of a function's execution, the context contains all of the kernel information that application needs to execute. During the lifecycle of a transaction, the function will update the context with each of its side effects (created notes, nullifiers etc.). At the end of a function's execution the mutated context is returned to the kernel to be checked for validity. + +Behind the scenes, Aztec.nr will pass data the kernel needs to and from a circuit, this is abstracted away from the developer. In a developer's eyes; the context is a useful structure that allows access and mutate the state of the `Aztec` blockchain. + +On this page, you'll learn + +- The details and functionalities of the private context in Aztec.nr +- Difference between the private and public contexts and their unified APIs +- Components of the private context, such as inputs and block header. +- Elements like return values, read requests, new note hashes, and nullifiers in transaction processing +- Differences between the private and public contexts, especially the unique features and variables in the public context + +## Two contexts, one API + +The `Aztec` blockchain contains two environments - public and private. + +- Private, for private transactions taking place on user's devices. +- Public, for public transactions taking place on the network's sequencers. + +As there are two distinct execution environments, they both require slightly differing execution contexts. Despite their differences; the API's for interacting with each are unified. Leading to minimal context switch when working between the two environments. + +The following section will cover both contexts. + +## The Private Context + +The code snippet below shows what is contained within the private context. +```rust title="private-context" showLineNumbers +pub inputs: PrivateContextInputs, +pub side_effect_counter: u32, + +pub min_revertible_side_effect_counter: u32, +pub is_fee_payer: bool, + +pub args_hash: Field, +pub return_hash: Field, + +pub max_block_number: MaxBlockNumber, + +pub note_hash_read_requests: BoundedVec, +pub nullifier_read_requests: BoundedVec, +key_validation_requests_and_generators: BoundedVec, + +pub note_hashes: BoundedVec, +pub nullifiers: BoundedVec, + +pub private_call_requests: BoundedVec, +pub public_call_requests: BoundedVec, MAX_ENQUEUED_CALLS_PER_CALL>, +pub public_teardown_call_request: PublicCallRequest, +pub l2_to_l1_msgs: BoundedVec, MAX_L2_TO_L1_MSGS_PER_CALL>, +``` +> Source code: noir-projects/aztec-nr/aztec/src/context/private_context.nr#L52-L75 + + +### Private Context Broken Down + +#### Inputs + +The context inputs includes all of the information that is passed from the kernel circuit into the application circuit. It contains the following values. + +```rust title="private-context-inputs" showLineNumbers +pub struct PrivateContextInputs { + pub call_context: CallContext, + pub historical_header: BlockHeader, + pub tx_context: TxContext, + pub start_side_effect_counter: u32, +} +``` +> Source code: noir-projects/aztec-nr/aztec/src/context/inputs/private_context_inputs.nr#L7-L14 + + +As shown in the snippet, the application context is made up of 4 main structures. The call context, the block header, and the private global variables. + +First of all, the call context. + +```rust title="call-context" showLineNumbers +pub struct CallContext { + pub msg_sender: AztecAddress, + pub contract_address: AztecAddress, + pub function_selector: FunctionSelector, + pub is_static_call: bool, +} +``` +> Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/call_context.nr#L9-L16 + + +The call context contains information about the current call being made: + +1. Msg Sender + - The message sender is the account (Aztec Contract) that sent the message to the current context. In the first call of the kernel circuit (often the account contract call), this value will be empty. For all subsequent calls the value will be the previous call. + +> The graphic below illustrates how the message sender changes throughout the kernel circuit iterations. + + + +2. Storage contract address + + - This value is the address of the current context's contract address. This value will be the value of the current contract that is being executed except for when the current call is a delegate call (Warning: This is yet to be implemented). In this case the value will be that of the sending contract. + +3. Flags + - Furthermore there are a series of flags that are stored within the application context: + - is_delegate_call: Denotes whether the current call is a delegate call. If true, then the storage contract address will be the address of the sender. + - is_static_call: This will be set if and only if the current call is a static call. In a static call, state changing altering operations are not allowed. + +### Block Header + +Another structure that is contained within the context is the `BlockHeader` object, which is the header of the block used to generate proofs against. + +```rust title="block-header" showLineNumbers +pub struct BlockHeader { + pub last_archive: AppendOnlyTreeSnapshot, + pub content_commitment: ContentCommitment, + pub state: StateReference, + pub global_variables: GlobalVariables, + pub total_fees: Field, + pub total_mana_used: Field, +} +``` +> Source code: noir-projects/noir-protocol-circuits/crates/types/src/block_header.nr#L11-L20 + + +### Transaction Context + +The private context provides access to the transaction context as well, which are user-defined values for the transaction in general that stay constant throughout its execution. + +```rust title="tx-context" showLineNumbers +pub struct TxContext { + pub chain_id: Field, + pub version: Field, + pub gas_settings: GasSettings, +} +``` +> Source code: noir-projects/noir-protocol-circuits/crates/types/src/transaction/tx_context.nr#L8-L14 + + +### Args Hash + +To allow for flexibility in the number of arguments supported by Aztec functions, all function inputs are reduced to a singular value which can be proven from within the application. + +The `args_hash` is the result of pedersen hashing all of a function's inputs. + +### Return Values + +The return values are a set of values that are returned from an applications execution to be passed to other functions through the kernel. Developers do not need to worry about passing their function return values to the `context` directly as `Aztec.nr` takes care of it for you. See the documentation surrounding `Aztec.nr` [macro expansion](./attributes.md#after-expansion) for more details. + +```rust +return_values : BoundedVec\, +``` +## Max Block Number + +Some data structures impose time constraints, e.g. they may make it so that a value can only be changed after a certain delay. Interacting with these in private involves creating proofs that are only valid as long as they are included before a certain future point in time. To achieve this, the `set_tx_max_block_number` function can be used to set this property: + +```rust title="max-block-number" showLineNumbers +pub fn set_tx_max_block_number(&mut self, max_block_number: u32) { +``` +> Source code: noir-projects/aztec-nr/aztec/src/context/private_context.nr#L228-L230 + + +A transaction that requests a maximum block number will never be included in a block with a block number larger than the requested value, since it would be considered invalid. This can also be used to make transactions automatically expire after some time if not included. + +### Read Requests + + + +### New Note Hashes + +New note hashes contains an array of all of the note hashes created in the current execution context. + +### New Nullifiers + +New nullifiers contains an array of the new nullifiers emitted from the current execution context. + +### Nullified Note Hashes + +Nullified note hashes is an optimization for introduced to help reduce state growth. There are often cases where note hashes are created and nullified within the same transaction. +In these cases there is no reason that these note hashes should take up space on the node's commitment/nullifier trees. Keeping track of nullified note hashes allows us to "cancel out" and prove these cases. + +### Private Call Stack + +The private call stack contains all of the external private function calls that have been created within the current context. Any function call objects are hashed and then pushed to the execution stack. +The kernel circuit will orchestrate dispatching the calls and returning the values to the current context. + +### Public Call Stack + +The public call stack contains all of the external function calls that are created within the current context. Like the private call stack above, the calls are hashed and pushed to this stack. Unlike the private call stack, these calls are not executed client side. Whenever the function is sent to the network, it will have the public call stack attached to it. At this point the sequencer will take over and execute the transactions. + +### New L2 to L1 msgs + +New L2 to L1 messages contains messages that are delivered to the l1 outbox on the execution of each rollup. + +## Public Context + +The Public Context includes all of the information passed from the `Public VM` into the execution environment. Its interface is very similar to the [Private Context](#the-private-context), however it has some minor differences (detailed below). + +### Public Global Variables + +The public global variables are provided by the rollup sequencer and consequently contain some more values than the private global variables. + +```rust title="global-variables" showLineNumbers +pub struct GlobalVariables { + pub chain_id: Field, + pub version: Field, + pub block_number: u32, + pub slot_number: Field, + pub timestamp: u64, + pub coinbase: EthAddress, + pub fee_recipient: AztecAddress, + pub gas_fees: GasFees, +} +``` +> Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/global_variables.nr#L9-L20 + diff --git a/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/function_transforms.md b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/function_transforms.md new file mode 100644 index 000000000000..ffe8b02b3941 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/function_transforms.md @@ -0,0 +1,239 @@ +--- +title: Inner Workings of Functions +sidebar_position: 2 +tags: [functions] +--- + +Below, we go more into depth of what is happening under the hood when you create a function in an Aztec contract. The [next page](./attributes.md) will give you more information about what the attributes are really doing. + + +## Function transformation + +When you define a function in an Aztec contract, it undergoes several transformations when it is compiled. These transformations prepare the function for execution. These transformations include: + +- [Creating a context for the function](#context-creation) +- [Handling function inputs](#private-and-public-input-injection) +- [Processing return values](#return-value-handling) +- [Generating function signatures](#function-signature-generation) +- [Generating contract artifacts](#contract-artifacts) + +Let's explore each of these transformations in detail. + +## Context creation + +Every function in an Aztec contract operates within a specific context which provides some extra information and functionality. This is either a `PrivateContext` or `PublicContext` object, depending on whether it is a private or public function. For private functions, it creates a hash of all input parameters to ensure privacy. + +### Private functions + +For private functions, the context creation involves hashing all input parameters: + +```rust +let mut args_hasher = ArgsHasher::new(); +// Hash each parameter +args_hasher.add(param1); +args_hasher.add(param2); +// add all parameters + +let mut context = PrivateContext::new(inputs, args_hasher.hash()); +``` + +This hashing process is important because it is used to verify the function's execution without exposing the input data. + +### Public functions + +For public functions, context creation is simpler: + +```rust +let mut context = PublicContext::new(inputs); +``` + +These `inputs` are explained in the [private and public input injection](#private-and-public-input-injection) further down on this page. + +### Using the context in functions + +Once created, the context object provides various useful methods. Here are some common use cases: + +#### Accessing storage + +The context allows you to interact with contract storage. eg if you have a function that calls storage like this: + +```rust +let sender_balance = storage.balances.at(owner); +``` + +This calls the context to read from the appropriate storage slot. + +#### Interacting with other contracts + +The context provides methods to call other contracts: + +```rust +let token_contract = TokenContract::at(token); +``` + +Under the hood, this creates a new instance of the contract interface with the specified address. + +## Private and public input injection + +An additional parameter is automatically added to every function. + +The injected input is always the first parameter of the transformed function and is of type `PrivateContextInputs` for private functions or `PublicContextInputs` for public functions. + +Original function definition + ```rust + fn my_function(param1: Type1, param2: Type2) { ... } + ``` + +Transformed function with injected input + ```rust + fn my_function(inputs: PrivateContextInputs, param1: Type1, param2: Type2) { ... } + ``` + +The `inputs` parameter includes: + +- msg sender, ie the address of the account calling the function +- contract address +- chain ID +- block context, eg the block number & timestamp +- function selector of the function being called + +This makes these inputs available to be consumed within private annotated functions. + +## Return value handling + +Return values in Aztec contracts are processed differently from traditional smart contracts when using private functions. + +### Private functions + +- The original return value is assigned to a special variable: + ```rust + let macro__returned__values = original_return_expression; + ``` + +- A new `ArgsHasher` is created for the return values: + ```rust + let mut returns_hasher = ArgsHasher::new(); + ``` + +- The hash of the return value is set in the context: + ```rust + context.set_return_hash(returns_hasher); + ``` + +- The function's return type is changed to `PrivateCircuitPublicInputs`, which is returned by calling `context.finish()` at the end of the function. + +This process allows the return values to be included in the function's computation result while maintaining privacy. + +### Public functions + +In public functions, the return value is directly used, and the function's return type remains as specified by the developer. + +## Function signature generation + +Unique function signatures are generated for each contract function. + +The function signature is computed like this: + +```rust +fn compute_fn_signature_hash(fn_name: &str, parameters: &[Type]) -> u32 { + let signature = format!( + "{}({})", + fn_name, + parameters.iter().map(signature_of_type).collect::>().join(",") + ); + let mut keccak = Keccak::v256(); + let mut result = [0u8; 32]; + keccak.update(signature.as_bytes()); + keccak.finalize(&mut result); + // Take the first 4 bytes of the hash and convert them to an integer + // If you change the following value you have to change NUM_BYTES_PER_NOTE_TYPE_ID in l1_note_payload.ts as well + let num_bytes_per_note_type_id = 4; + u32::from_be_bytes(result[0..num_bytes_per_note_type_id].try_into().unwrap()) +} +``` + +- A string representation of the function is created, including the function name and parameter types +- This signature string is then hashed using Keccak-256 +- The first 4 bytes of the resulting hash are converted to a u32 integer + +### Integration into contract interface + +The computed function signatures are integrated into the contract interface like this: + +- During contract compilation, placeholder values (0) are initially used for function selectors + +- After type checking, the `update_fn_signatures_in_contract_interface()` function is called to replace these placeholders with the actual computed signatures + +- For each function in the contract interface: + - The function's parameters are extracted + - The signature hash is computed using `compute_fn_signature_hash` + - The placeholder in the contract interface is replaced with the computed hash + +This process ensures that each function in the contract has a unique, deterministic signature based on its name and parameter types. They are inspired by Solidity's function selector mechanism. + +## Contract artifacts + +Contract artifacts in Aztec are automatically generated structures that describe the contract's interface. They provide information about the contract's functions, their parameters, and return types. + +### Contract artifact generation process + +For each function in the contract, an artifact is generated like this: + +- A struct is created to represent the function's parameters: + + ```rust + struct {function_name}_parameters { + // Function parameters are listed here + } + ``` + + This struct is only created if the function has parameters. + +- An ABI struct is generated for the function: + +```rust + let export_struct_source = format!( + " + #[abi(functions)] + struct {}_abi {{ + {}{} + }}", + func.name(), + parameters, + return_type + ); +``` + +- These structs are added to the contract's types. + +### Content of artifacts + +The artifacts contain: + +- Function name +- Parameters (if any), including their names and types +- Return type (if the function has returns) + +For example, for a function `transfer(recipient: Address, amount: Field) -> bool`, the artifact would look like: + +```rust +struct transfer_parameters { + recipient: Address, + amount: Field, +} + +#[abi(functions)] +struct transfer_abi { + parameters: transfer_parameters, + return_type: bool, +} +``` + +Contract artifacts are important because: + +- They provide a machine-readable description of the contract +- They can be used to generate bindings for interacting with the contract (read [here](../../../developers/guides/smart_contracts/how_to_compile_contract.md) to learn how to create TypeScript bindings) +- They help decode function return values in the simulator + +## Further reading +- [Function attributes and macros](./attributes.md) diff --git a/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/index.md b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/index.md new file mode 100644 index 000000000000..300fa4abe66d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/index.md @@ -0,0 +1,30 @@ +--- +title: Defining Functions +sidebar_position: 2 +tags: [functions] +--- + +Functions serve as the building blocks of smart contracts. Functions can be either **public**, ie they are publicly available for anyone to see and can directly interact with public state, or **private**, meaning they are executed completely client-side in the [PXE](../../concepts/pxe/index.md). Read more about how private functions work [here](./attributes.md#private-functions-private). + +For a more practical guide of using multiple types of functions, follow the [NFT tutorial](../../../developers/tutorials/codealong/contract_tutorials/nft_contract.md). + +Currently, any function is "mutable" in the sense that it might alter state. However, we also support static calls, similarly to EVM. A static call is essentially a call that does not alter state (it keeps state static). + +## Initializer functions + +Smart contracts may have one, or many, initializer functions which are called when the contract is deployed. + +Initializers are regular functions that set an "initialized" flag (a nullifier) for the contract. A contract can only be initialized once, and contract functions can only be called after the contract has been initialized, much like a constructor. However, if a contract defines no initializers, it can be called at any time. Additionally, you can define as many initializer functions in a contract as you want, both private and public. + +## Oracles + +There are also special oracle functions, which can get data from outside of the smart contract. In the context of Aztec, oracles are often used to get user-provided inputs. + +## Learn more about functions + +- [How function visibility works in Aztec](./visibility.md) +- How to write an [initializer function](../../../developers/guides/smart_contracts/writing_contracts/initializers.md) +- [Oracles](../oracles/index.md) and how Aztec smart contracts might use them +- [How functions work under the hood](./attributes.md) + +Find a function macros reference [here](../../../developers/reference/smart_contract_reference/macros.md) diff --git a/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/public_private_calls.md b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/public_private_calls.md new file mode 100644 index 000000000000..0e4c5667b5c4 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/public_private_calls.md @@ -0,0 +1,92 @@ +--- +title: Private <> Public Communication +sidebar_position: 3 +tags: [functions] +--- + + +import Image from "@theme/IdealImage"; + +import Disclaimer from "@site/src/components/Disclaimers/\_wip_disclaimer.mdx"; + + + +Aztec operates on a model of private and public functions that are able to work together. Private functions work by providing evidence of correct execution generated locally through kernel proofs. Public functions, on the other hand, are able to utilize the latest state to manage updates and perform alterations. + +On this page, you’ll learn: + +- How private and public functions work +- The role of public functions in managing state alterations and updates +- Communication and interactions between private and public functions +- How the sequencer manages the order of operations of private functions + +### Objectives + +The goal for L2 communication is to setup the most simple mechanism that will support + +- _private_ and _public_ functions +- _private_ functions that can call _private_ or _public_ functions +- _public_ functions that can call _private_ or _public_ functions + +Before diving into the communication abstracts for Aztec, we need to understand some of our limitations. One being that public functions (as known from Ethereum) must operate on the current state to provide meaningful utility, e.g., at the tip. +This works fine when there is only one builder (sequencer) executing it first, and then others verifying as the builder always knows the tip. On the left in the diagram below, we see a block where the transactions are applied one after another each building on the state before it. For example, if Tx 1 update storage `a = 5`, then in Tx 2 reading `a` will return `5`. + +This works perfectly well when everything is public and a single builder is aware of all changes. However, in a private setting, we require the user to present evidence of correct execution as part of their transaction in the form of a kernel proof (generated locally on user device ahead of time). This way, the builder doesn't need to have knowledge of everything happening in the transaction, only the results. If we were to build this proof on the latest state, we would encounter problems. How can two different users build proofs at the same time, given that they will be executed one after the other by the sequencer? The simple answer is that they cannot, as race conditions would arise where one of the proofs would be invalidated by the other due to a change in the state root (which would nullify Merkle paths). + +To avoid this issue, we permit the use of historical data as long as the data has not been nullified previously. Note, that because this must include nullifiers that were inserted after the proof generation, but before execution we need to nullify (and insert the data again) to prove that it was not nullified. Without emitting the nullifier we would need our proof to point to the current head of the nullifier tree to have the same effect, e.g., back to the race conditions we were trying to avoid. + +In this model, instead of informing the builder of our intentions, we construct the proof $\pi$ and then provide them with the transaction results (new note hashes and nullifiers, contract deployments and cross-chain messages) in addition to $\pi$. The builder will then be responsible for inserting these new note hashes and nullifiers into the state. They will be aware of the intermediates and can discard transactions that try to produce existing nullifiers (double spend), as doing so would invalidate the rollup proof. + +On the left-hand side of the diagram below, we see the fully public world where storage is shared, while on the right-hand side, we see the private world where all reads are historical. + + + +Given that Aztec will comprise both private and public functions, it is imperative that we determine the optimal ordering for these functions. From a logical standpoint, it is reasonable to execute the private functions 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$. Prioritizing the private functions would also afford us the added convenience of enabling them to invoke the public functions, which is particularly advantageous when implementing a peer-to-pool architecture such as that employed by Uniswap. + +Transactions that involve both private and public functions will follow a specific order of execution, wherein the private functions will be executed first, followed by the public functions, and then moving on to the next transaction. + +It is important to note that the execution of private functions is prioritized before executing any public functions. This means that private functions cannot "wait" on the results of any of their calls to public functions. Stated differently, any calls made across domains are unilateral in nature, akin to shouting into the void with the hope that something will occur at a later time. The figure below illustrates 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. + + + +Multiple of these transactions are then ordered into a L2 block by the sequencer, who will also be executing the public functions (as they require the current head). Example seen below. + + + +:::info +Be mindful that if part of a transaction is reverting, say the public part of a call, it will revert the entire transaction. Similarly to Ethereum, it might be possible for the block builder to create a block such that your valid transaction reverts because of altered state, e.g., trade incurring too much slippage or the like. +::: + +To summarize: + +- _Private_ function calls are fully "prepared" and proven by the user, which provides the kernel proof along with new note hashes and nullifiers to the sequencer. +- _Public_ functions altering public state (updatable storage) must be executed at the current "head" of the chain, which only the sequencer can ensure, so these must be executed separately to the _private_ functions. +- _Private_ and _public_ functions within an Aztec transaction are therefore ordered such that first _private_ functions are executed, and then _public_. + +A more comprehensive overview of the interplay between private and public functions and their ability to manipulate data is presented 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 note hashes, potentially as part of transferring funds from the public domain to the private domain). + + + +:::info +You can think of private and public functions as being executed by two actors that can only communicate to each other by mailbox. +::: + +So, with private functions being able to call public functions (unilaterally) we had a way to go from private to public, what about the other way? Recall that public functions CANNOT call private functions directly. Instead, you can use the 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. + +Given that private functions have the capability of calling public functions unilaterally, it is feasible to transition from a private to public function within the same transaction. However, the converse is not possible. To achieve this, the append-only merkle tree can be employed to save messages from a public function call, which can then be executed by a private function at a later point in time. It is crucial to reiterate that this can only occur at a later stage and cannot take place within the same rollup because the proof cannot be generated by the user. + +:::info +Theoretically the builder has all the state trees after the public function has inserted a message in the public tree, and is able to create a proof consuming those messages in the same block. But it requires pending UTXO's on a block-level. +::: + +From the above, we should have a decent idea about what private and public functions can do inside the L2, and how they might interact. + +## A note on L2 access control + +Many applications rely on some form of access control to function well. USDC have a blacklist, where only parties not on the list should be able to transfer. And other systems such as Aave have limits such that only the pool contract is able to mint debt tokens and transfers held funds. + +Access control like this cannot easily be enforced in the private domain, as reading is also nullifying (to ensure data is up to date). However, as it is possible to read historical public state, one can combine private and public functions to get the desired effect. + +This concept is known as shared state, and relies on using delays when changing public data so that it can also be read in private with currentness guarantees. Since values cannot be immediately modified but instead require delays to elapse, it is possible to privately prove that an application is using the current value _as long as the transaction gets included before some time in the future_, which would be the earliest the value could possibly change. + +If the public state is only changed infrequently, and it is acceptable to have delays when doing so, then shared state is a good solution to this problem. diff --git a/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/visibility.md b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/visibility.md new file mode 100644 index 000000000000..006891f75c30 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/functions/visibility.md @@ -0,0 +1,27 @@ +--- +title: Visibility +sidebar_position: 0 +tags: [functions] +--- + +In Aztec there are multiple different types of visibility that can be applied to functions. Namely we have `data visibility` and `function visibility`. This page explains these types of visibility. + +For a practical guide of using multiple types of data and function visibility, follow the [NFT tutorial](../../../developers/tutorials/codealong/contract_tutorials/nft_contract.md). + +### Data Visibility + +Data visibility is used to describe whether the data (or state) used in a function is generally accessible (public) or on a need to know basis (private). + +### Function visibility + +This is the kind of visibility you are more used to seeing in Solidity and more traditional programming languages. It is used to describe whether a function is callable from other contracts, or only from within the same contract. + +By default, all functions are callable from other contracts, similarly to the Solidity `public` visibility. To make them only callable from the contract itself, you can mark them as `internal`. Contrary to solidity, we don't have the `external` nor `private` keywords. `external` since it is limited usage when we don't support inheritance, and `private` since we don't support inheritance and it would also be confusing with multiple types of `private`. + +A good place to use `internal` is when you want a private function to be able to alter public state. As mentioned above, private functions cannot do this directly. They are able to call public functions and by making these internal we can ensure that this state manipulating function is only callable from our private function. + +:::danger +Note that non-internal functions could be used directly as an entry-point, which currently means that the `msg_sender` would be `0`, so for now, using address `0` as a burn address is not recommended. You can learn more about this in the [Accounts concept page](../../concepts/accounts/keys.md). +::: + +To understand how visibility works under the hood, check out the [Inner Workings page](./attributes.md). diff --git a/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/oracles/index.md b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/oracles/index.md new file mode 100644 index 000000000000..175c54dcf0ef --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts/oracles/index.md @@ -0,0 +1,39 @@ +--- +title: Oracle Functions +sidebar_position: 3 +tags: [functions, oracles] +--- + +This page goes over what oracles are in Aztec and how they work. + +Looking for a hands-on guide? You can learn how to use oracles in a smart contract [here](../../../developers/guides/smart_contracts/writing_contracts/how_to_use_capsules.md). + +An oracle is something that allows us to get data from the outside world into our contracts. The most widely-known types of oracles in blockchain systems are probably Chainlink price feeds, which allow us to get the price of an asset in USD taking non-blockchain data into account. + +While this is one type of oracle, the more general oracle, allows us to get any data into the contract. In the context of oracle functions or oracle calls in Aztec, it can essentially be seen as user-provided arguments, that can be fetched at any point in the circuit, and don't need to be an input parameter. + +**Why is this useful? Why don't just pass them as input parameters?** +In the world of EVM, you would just read the values directly from storage and call it a day. However, when we are working with circuits for private execution, this becomes more tricky as you cannot just read the storage directly from your state tree, because there are only commitments (e.g. hashes) there. The pre-images (content) of your commitments need to be provided to the function to prove that you actually allowed to modify them. + +If we fetch the notes using an oracle call, we can keep the function signature independent of the underlying data and make it easier to use. A similar idea, applied to the authentication mechanism is used for the Authentication Witnesses that allow us to have a single function signature for any wallet implementation, see [AuthWit](../../concepts/advanced/authwit.md) for more information on this. + +Oracles introduce **non-determinism** into a circuit, and thus are `unconstrained`. It is important that any information that is injected into a circuit through an oracle is later constrained for correctness. Otherwise, the circuit will be **under-constrained** and potentially insecure! + +`Aztec.nr` has a module dedicated to its oracles. If you are interested, you can view them by following the link below: +```rust title="oracles-module" showLineNumbers +/// Oracles module +``` +> Source code: noir-projects/aztec-nr/aztec/src/oracle/mod.nr#L1-L3 + + +## Inbuilt oracles + +- [`debug_log`](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/aztec-nr/aztec/src/oracle/debug_log.nr) - Provides a couple of debug functions that can be used to log information to the console. Read more about debugging [here](../../../developers/reference/debugging/index.md). +- [`auth_witness`](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/aztec-nr/authwit/src/auth_witness.nr) - Provides a way to fetch the authentication witness for a given address. This is useful when building account contracts to support approve-like functionality. +- [`get_l1_to_l2_message`](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/aztec-nr/aztec/src/oracle/get_l1_to_l2_message.nr) - Useful for application that receive messages from L1 to be consumed on L2, such as token bridges or other cross-chain applications. +- [`notes`](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/aztec-nr/aztec/src/oracle/notes.nr) - Provides a lot of functions related to notes, such as fetches notes from storage etc, used behind the scenes for value notes and other pre-build note implementations. +- [`logs`](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/aztec-nr/aztec/src/oracle/logs.nr) - Provides the to log encrypted and unencrypted data. + +Find a full list [on GitHub](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/aztec-nr/aztec/src/oracle). + +Please note that it is **not** possible to write a custom oracle for your dapp. Oracles are implemented in the PXE, so all users of your dapp would have to use a PXE service with your custom oracle included. If you want to inject some arbitrary data that does not have a dedicated oracle, you can use [capsules](../../../developers/guides/smart_contracts/writing_contracts/how_to_use_capsules.md). diff --git a/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts_overview.md b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts_overview.md new file mode 100644 index 000000000000..cd6ac1e5c391 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec/smart_contracts_overview.md @@ -0,0 +1,28 @@ +--- +title: Smart Contracts +sidebar_position: 3 +tags: [contracts] +--- + +Smart contracts in Aztec are privacy-first, and can include both public and private elements. They are written in Noir framework called Aztec.nr, and allow high-level programs to be converted into ZK circuits. + +On this page, you’ll learn how Aztec executes smart contracts for privacy and efficiency: + +- Role and structure of smart contracts within Aztec +- Intro into Noir programming language and how it converts to circuits +- The Aztec Kernel +- Transaction flow and confidentiality + +## Defining Aztec smart contracts + +A "smart contract" is defined as a set of public and private functions written as Noir circuits. These functions operate on public and private state stored by a contract. Each function is represented as a ZK SNARK verification key, where the contract is uniquely described by the set of its verification keys, and stored in the Aztec Contracts tree. + +[Noir](https://noir-lang.org) is a programming language designed for converting high-level programs into ZK circuits. Based on Rust, the goal is to present an idiomatic way of writing private smart contracts that is familiar to Ethereum developers. Noir is under active development adding features such as contracts, functions and storage variables. + +The end goal is a language that is intuitive to use for developers with no cryptographic knowledge and empowers developers to easily write programmable private smart contracts. + +There are no plans for EVM compatibility or to support Solidity in Aztec. The privacy-first nature of Aztec is fundamentally incompatible with the EVM architecture and Solidity's semantics. In addition, the heavy use of client-side proof construction makes this impractical. + +## Further reading + +Jump in and write your first smart contract [here](../developers/tutorials/codealong/contract_tutorials/counter_contract.md) diff --git a/docs/versioned_docs/version-v0.88.0/aztec_connect_sunset.mdx b/docs/versioned_docs/version-v0.88.0/aztec_connect_sunset.mdx new file mode 100644 index 000000000000..0861f5fcc721 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/aztec_connect_sunset.mdx @@ -0,0 +1,93 @@ +--- +title: Aztec Connect Sunset +--- + +import Image from "@theme/IdealImage"; + +:::danger Deprecated + +Aztec Connect is no longer being actively developed. + +::: + +The rollup instance operated by Aztec stopped accepting deposits on March 21st, 2023. Read the full announcement [here](https://medium.com/aztec-protocol/sunsetting-aztec-connect-a786edce5cae). + +We will continue to process transactions and withdrawals for funds that are already in the rollup until March 31st, 2024, at which point we will stop running the sequencer. Users should withdraw funds immediately. See the [zk.money](#zkmoney) section below for details on how to withdraw funds. + +## Run your own AC + +All of the infrastructure and associated code required to run and interact with the Aztec Connect rollup is open source, so anyone can publish blocks after we stop, or run their own instance of the rollup software. + +You can find the old documentation site that includes all of the pertinent information on the [`aztec-connect` branch](https://github.com/AztecProtocol/docs/tree/aztec-connect) of the docs repository. + +The code has been open sourced and you can find the relevant repositories linked below. + +### Source Code + +Follow the links for more information about each package. + +- [Running the rollup service](https://github.com/AztecProtocol/aztec-connect/blob/master/yarn-project/README.md) +- [Sequencer](https://github.com/AztecProtocol/aztec-connect/tree/master/yarn-project/falafel) +- [Contracts](https://github.com/AztecProtocol/aztec-connect/tree/master/contracts) +- [SDK](https://github.com/AztecProtocol/aztec-connect/tree/master/yarn-project/sdk) +- [Block Explorer](https://github.com/AztecProtocol/aztec-connect-explorer) +- [Alpha SDK](https://github.com/AztecProtocol/aztec-connect/tree/master/yarn-project/alpha-sdk) +- [Wallet UI](https://github.com/AztecProtocol/wallet-ui) + +## Zk.money + +### Exiting Defi Positions + +1. Navigate to your zk.money homepage and click “Wallet”. +2. Scroll down to “Tokens” and “Earn Positions”. +3. Click “Earn Positions”. +4. Click “Claim & Exit” on the position you wish to exit. +5. All exit transactions are free in “Batched Mode” proceed to step 6 to get a free transaction. +6. Click “Max” to exit the full amount, and then select a speed for your transaction. +7. Once you have done so, click “Next”. +8. Review the amount you will receive is correct, tick the disclaimer, and click “Confirm Transaction”. +9. After clicking confirm transaction, sign the signature request using your connected wallet (e.g. Metamask in this example). +10. Wait until your transaction is confirmed. +11. Navigate back to your wallet homepage and click “Earn Positions”. +12. The status of your exit will be displayed here, as shown by “Exiting” (1 tick). +13. To the left, click the transaction hash icon to be taken to the block explorer page to see the transaction status. +14. Your funds will appear in your dashboard once the transaction has settled. + +### Exiting LUSD Borrowing + +Your LUSD debt is repaid using a flash loan. Part of your ETH collateral then repays the flash loan, and the remaining ETH is returned to your account. Your total TB-275 tokens represents the entirety of your share of the collateral. Spending all your TB-275 will release your entire share of the collateral (minus the market value of the debt to be repaid). + +Liquity: https://docs.liquity.org/ + +1. Navigate to your zk.money homepage and click “Wallet”. +2. Scroll down to “Tokens” and “Earn Positions”. +3. Click “Earn Positions”. +4. On your Liquity Trove position, click “Repay & Exit”. +5. Click “Max” to exit the full amount, then select a speed for your transaction. +6. Once you have done so, click “Next”. +7. Review the amount you will receive is correct, tick the disclaimer, and click “Confirm Transaction”. +8. After clicking confirm transaction, sign the signature request using your connected wallet (e.g. Metamask). +9. Wait until your transaction is confirmed. +10. Navigate to your zk.money wallet homepage and click “Earn Positions”. +11. The status of your exit will be displayed here, as shown by “Exiting” (1 tick). +12. Click the transaction hash icon to be taken to the block explorer page to see the transaction status. +13. Your funds will appear in your dashboard once the transaction has settled. + +### Withdrawing Assets + +How to withdraw ETH, DAI and LUSD. + +1. Navigate to your zk.money homepage and click “Wallet”. +2. Scroll down to “Tokens” and “Earn Positions”. +3. Click “Tokens”. +4. Click “Exit” on the desired token you would like to withdraw. +5. Click “Withdraw to L1”. +6. Enter your recipient address. +7. Click “Max” to withdraw the full amount. +8. Select a speed for your transaction (transactions are free in “Batched Mode”). +9. Click “Next”. +10. Review the amount you are withdrawing is correct, tick the disclaimer, and click “Confirm Transaction”. +11. Sign the signature request using your connected wallet (e.g. Metamask). +12. Wait until your transaction is confirmed. +13. Navigate back to your wallet homepage, under Transaction History. Click the transaction hash to check the status of your transaction on the block explorer. +14. Your funds will appear in your recipient wallet once the transaction has settled. diff --git a/docs/versioned_docs/version-v0.88.0/building_in_public.md b/docs/versioned_docs/version-v0.88.0/building_in_public.md new file mode 100644 index 000000000000..627e9d2b65ef --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/building_in_public.md @@ -0,0 +1,49 @@ +--- +title: Building in Public +sidebar_position: 0 +id: building_in_public +--- + +import Disclaimer from "@site/src/components/Disclaimers/\_wip_disclaimer.mdx"; +import ReactPlayer from "react-player/youtube"; + +## How Aztec is being built + +Aztec will launch as a credibly neutral, decentralized network. The protocol is being developed as open source software by Aztec Labs and the community. Together we are designing, building and auditing much of the software that will be run by Aztec network stakeholders, such as infrastructure providers, wallets, and other core services. + +Contributors to Aztec uphold many of the values of the Ethereum community: + +- building in public +- having a rigorous commitment to open source +- believe in a permissionless, compliant, scalable and censorship-resistant system. + +## Our Cryptography + +Aztec is inspired by Ethereum. We believe in transparency for the protocol, but privacy for the user. This programmability is achieved through Smart Contracts, which are in fact Zero-Knowledge circuits. + +To allow for this, we focus on two main components: + +- **Noir** - We started developing Noir long before Aztec came into being. As an easy, open-source domain specific programming language for writing zero-knowledge circuits, it became the perfect language for writing [Aztec Smart Contracts](aztec/smart_contracts_overview.md). Read about standalone Noir in the [Noir Lang Documentation](https://noir-lang.org). +- **Honk** - A collection of cutting-edge cryptography, from proving systems, to compilers, and other sidecars. These will support Aztec's rollup and allow for fast, private, client-side proving. + +## Media + +### Privacy Preserving Smart Contracts + + + +### Public-private Composability + + + + diff --git a/docs/versioned_docs/version-v0.88.0/developers/getting_started.md b/docs/versioned_docs/version-v0.88.0/developers/getting_started.md new file mode 100644 index 000000000000..48d229ef4379 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/getting_started.md @@ -0,0 +1,255 @@ +--- +title: Getting Started +sidebar_position: 0 +tags: [sandbox. testnet] +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Get started on your local environment using the sandbox. If you'd rather jump into testnet, read the [getting started on testnet guide](./guides/getting_started_on_testnet.md). + +The Sandbox is an local development Aztec network running fully on your machine, and interacting with a development Ethereum node. You can develop and deploy on it just like on a testnet or mainnet (when the time comes). The sandbox makes it faster and easier to develop and test your Aztec applications. + +What's included in the sandbox: + +- Local Ethereum network (Anvil) +- Deployed Aztec protocol contracts (for L1 and L2) +- A set of test accounts with some test tokens to pay fees +- Development tools to compile contracts and interact with the network (`aztec-nargo` and `aztec-wallet`) + +All of this comes packages in a Docker container to make it easy to install and run. + +This guide will teach you how to install the Aztec sandbox, run it using the Aztec CLI, and interact with contracts using the wallet CLI. To jump right into the testnet instead, click the `Testnet` tab. + +## Prerequisites + +import { General, Fees } from '@site/src/components/Snippets/general_snippets'; + +You need two global dependencies on your machine: + +- +- Docker (visit [this page of the Docker docs](https://docs.docker.com/get-docker/) on how to install it) + +## Install and run the sandbox + +### Start Docker + +Docker needs to be running in order to install the sandbox. Find instructions on the [Docker website](https://docs.docker.com/get-started/). + +### Install the sandbox + +Run: + +```bash +bash -i <(curl -s https://install.aztec.network) +``` + +This will install the following tools: + +- **aztec** - launches various infrastructure subsystems (full sandbox, sequencer, prover, pxe, etc) and provides utility commands to interact with the network +- **aztec-nargo** - aztec's build of nargo, the noir compiler toolchain. +- **aztec-up** - a tool to upgrade the aztec toolchain to the latest, or specific versions. +- **aztec-wallet** - a tool for interacting with the aztec network + +### Start the sandbox + +Once these have been installed, to start the sandbox, run: + +```bash +aztec start --sandbox +``` + +**Congratulations, you have just installed and run the Aztec Sandbox!** + +```bash + /\ | | + / \ ___| |_ ___ ___ + / /\ \ |_ / __/ _ \/ __| + / ____ \ / /| || __/ (__ + /_/___ \_\/___|\__\___|\___| + +``` + +In the terminal, you will see some logs: + +1. Sandbox version +2. Contract addresses of rollup contracts +3. PXE (private execution environment) setup logs +4. Initial accounts that are shipped with the sandbox and can be used in tests + +You'll know the sandbox is ready to go when you see something like this: + +```bash +[INFO] Aztec Server listening on port 8080 +``` + +## Using the sandbox test accounts + +import { CLI_Add_Test_Accounts } from '@site/src/components/Snippets/general_snippets'; + + + +To add the test accounts in the PXE, run this in another terminal: + +```bash +aztec-wallet import-test-accounts +``` + +We'll use the first test account, `test0`, throughout to pay for transactions. + +## Creating an account in the sandbox + +```bash +aztec-wallet create-account -a my-wallet --payment method=fee_juice,feePayer=test0 +``` + +This will create a new wallet with an account and give it the alias `my-wallet`. Accounts can be referenced with `accounts:`. You will see logs telling you the address, public key, secret key, and more. + +On successful depoyment of the account, you should see something like this: + +```bash +New account: + +Address: 0x066108a2398e3e2ff53ec4b502e4c2e778c6de91bb889de103d5b4567530d99c +Public key: 0x007343da506ea513e6c05ba4d5e92e3c682333d97447d45db357d05a28df0656181e47a6257e644c3277c0b11223b28f2b36c94f9b0a954523de61ac967b42662b60e402f55e3b7384ba61261335040fe4cd52cb0383f559a36eeea304daf67d1645b06c38ee6098f90858b21b90129e7e1fdc4666dd58d13ef8fab845b2211906656d11b257feee0e91a42cb28f46b80aabdc70baad50eaa6bb2c5a7acff4e30b5036e1eb8bdf96fad3c81e63836b8aa39759d11e1637bd71e3fc76e3119e500fbcc1a22e61df8f060004104c5a75b52a1b939d0f315ac29013e2f908ca6bc50529a5c4a2604c754d52c9e7e3dee158be21b7e8008e950991174e2765740f58 +Secret key: 0x1c94f8b19e91d23fd3ab6e15f7891fde7ba7cae01d3fa94e4c6afb4006ec0cfb +Partial address: 0x2fd6b540a6bb129dd2c05ff91a9c981fb5aa2ac8beb4268f10b3aa5fb4a0fcd1 +Salt: 0x0000000000000000000000000000000000000000000000000000000000000000 +Init hash: 0x28df95b579a365e232e1c63316375c45a16f6a6191af86c5606c31a940262db2 +Deployer: 0x0000000000000000000000000000000000000000000000000000000000000000 + +Waiting for account contract deployment... +Deploy tx hash: 0a632ded6269bda38ad6b54cd49bef033078218b4484b902e326c30ce9dc6a36 +Deploy tx fee: 200013616 +Account stored in database with aliases last & my-wallet +``` + +You may need to scroll up as there are some other logs printed after it. + +You can double check by running `aztec-wallet get-alias accounts:my-wallet`. + +For simplicity we'll keep using the test account, let's deploy our own test token! + +## Deploying a contract + +The sandbox comes with some contracts that you can deploy and play with. One of these is an example token contract. + +Deploy it with this: + +```bash +aztec-wallet deploy TokenContractArtifact --from accounts:test0 --args accounts:test0 TestToken TST 18 -a testtoken +``` + +This takes + +- the contract artifact as the argument, which is `TokenContractArtifact` +- the deployer account, which we used `test0` +- the args that the contract constructor takes, which is the `admin` (`accounts:test0`), `name` (`TestToken`), `symbol` (`TST`), and `decimals` (`18`). +- an alias `testtoken` (`-a`) so we can easily reference it later with `contracts:testtoken` + +On successful deployment, you should see something like this: + +```bash +aztec:wallet [INFO] Using wallet with address 0x066108a2398e3e2ff53ec4b502e4c2e778c6de91bb889de103d5b4567530d99c +0ms +Contract deployed at 0x15ce68d4be65819fe9c335132f10643b725a9ebc7d86fb22871f6eb8bdbc3abd +Contract partial address 0x25a91e546590d77108d7b184cb81b0a0999e8c0816da1a83a2fa6903480ea138 +Contract init hash 0x0abbaf0570bf684da355bd9a9a4b175548be6999625b9c8e0e9775d140c78506 +Deployment tx hash: 0a8ccd1f4e28092a8fa4d1cb85ef877f8533935c4e94b352a38af73eee17944f +Deployment salt: 0x266295eb5da322aba96fbb24f9de10b2ba01575dde846b806f884f749d416707 +Deployment fee: 200943060 +Contract stored in database with aliases last & testtoken +``` + +In the next step, let's mint some tokens! + +## Minting public tokens + +Call the public mint function like this: + +```bash +aztec-wallet send mint_to_public --from accounts:test0 --contract-address contracts:testtoken --args accounts:test0 100 +``` + +This takes + +- the function name as the argument, which is `mint_to_public` +- the `from` account (caller) which is `accounts:test0` +- the contract address, which is aliased as `contracts:testtoken` (or simply `testtoken`) +- the args that the function takes, which is the account to mint the tokens into (`test0`), and `amount` (`100`). + +This only works because we are using the secret key of the admin who has permissions to mint. + +A successful call should print something like this: + +```bash +aztec:wallet [INFO] Using wallet with address 0x066108a2398e3e2ff53ec4b502e4c2e778c6de91bb889de103d5b4567530d99c +0ms +Maximum total tx fee: 1161660 +Estimated total tx fee: 116166 +Estimated gas usage: da=1127,l2=115039,teardownDA=0,teardownL2=0 + +Transaction hash: 2ac383e8e2b68216cda154b52e940207a905c1c38dadba7a103c81caacec403d +Transaction has been mined + Tx fee: 200106180 + Status: success + Block number: 17 + Block hash: 1e27d200600bc45ab94d467c230490808d1e7d64f5ee6cee5e94a08ee9580809 +Transaction hash stored in database with aliases last & mint_to_public-9044 +``` + +You can double-check by calling the function that checks your public account balance: + +```bash +aztec-wallet simulate balance_of_public --from test0 --contract-address testtoken --args accounts:test0 +``` + +This should print + +```bash +Simulation result: 100n +``` + +## Playing with hybrid state and private functions + +In the following steps, we'll moving some tokens from public to private state, and check our private and public balance. + +```bash +aztec-wallet send transfer_to_private --from accounts:test0 --contract-address testtoken --args accounts:test0 25 +``` + +The arguments for `transfer_to_private` function are: + +- the account address to transfer to +- the amount of tokens to send to private + +A successful call should print something similar to what you've seen before. + +Now when you call `balance_of_public` again you will see 75! + +```bash +aztec-wallet simulate balance_of_public --from test0 --contract-address testtoken --args accounts:test0 +``` + +This should print + +```bash +Simulation result: 75n +``` + +And then call `balance_of_private` to check that you have your tokens! + +```bash +aztec-wallet simulate balance_of_private --from test0 --contract-address testtoken --args accounts:test0 +``` + +This should print + +```bash +Simulation result: 25n +``` + +**Congratulations, you now know the fundamentals of working with the Aztec sandbox!** You are ready to move onto the more fun stuff. + +## What's next? + +Click the Next button below to continue on your journey and write your first Aztec smart contract. \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/guides/_category_.json new file mode 100644 index 000000000000..3d8f3e3bf4c7 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Guides", + "link": { "type": "doc", "id": "guides/index" } +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/getting_started/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/guides/getting_started/_category_.json new file mode 100644 index 000000000000..4b7c1cbad3b2 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/getting_started/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 1, + "collapsible": true, + "collapsed": false, + "label": "Getting Started" +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/getting_started/codespaces.md b/docs/versioned_docs/version-v0.88.0/developers/guides/getting_started/codespaces.md new file mode 100644 index 000000000000..10658c856c2f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/getting_started/codespaces.md @@ -0,0 +1,25 @@ +--- +title: Codespaces +sidebar_position: 1 +draft: true +--- + +If you do not want to run the sandbox locally, or if your machine is unsupported (eg Windows), it is possible to run it within a GitHub Codespace. + +[GitHub Codespaces](https://github.com/features/codespaces) are a quick way to develop: they provision a remote machine with all tooling you need for Aztec in just a few minutes. You can use some prebuilt images to make it easier and faster. + +Choose a boilerplate and click "create new codespace": + +[![One-Click React Starter](/img/codespaces_badges/react_cta_badge.svg)](https://codespaces.new/AztecProtocol/aztec-packages?devcontainer_path=.devcontainer%2Freact%2Fdevcontainer.json) [![One-Click HTML/TS Starter](/img/codespaces_badges/vanilla_cta_badge.svg)](https://codespaces.new/AztecProtocol/aztec-packages?devcontainer_path=.devcontainer%2Fvanilla%2Fdevcontainer.json) [![One-Click Token Starter](/img/codespaces_badges/token_cta_badge.svg)](https://codespaces.new/AztecProtocol/aztec-packages?devcontainer_path=.devcontainer%2Ftoken%2Fdevcontainer.json) + +This creates a codespace with a prebuilt image containing one of the "Aztec Boxes" and a development network (sandbox). +- You can develop directly on the codespace, push it to a repo, make yourself at home. +- You can also just use the sandbox that comes with it. The URL will be logged, you just need to use it as your `PXE_URL`. + +You can then start, stop, or see the logs of your sandbox just by calling `sandbox` or `npx aztec-app sandbox`. Run `sandbox -h` for a list of commands. + +## More about codespaces + +Codespaces are way more powerful than you may initially think. For example, you can connect your local `vscode` to a remote codespace, for a fully contained development environment that doesn't use any of your computer resources! + +Visit the [codespaces documentation](https://docs.github.com/en/codespaces/overview) for more specific documentation around codespaces. diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/getting_started_on_testnet.md b/docs/versioned_docs/version-v0.88.0/developers/guides/getting_started_on_testnet.md new file mode 100644 index 000000000000..dea1452d6cc0 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/getting_started_on_testnet.md @@ -0,0 +1,183 @@ +--- +title: Getting Started on Testnet +sidebar_position: 0 +tags: [testnet] +--- + +import { AztecTestnetVersion } from '@site/src/components/Snippets/general_snippets'; + +This guide will walk you through setting up and using the Aztec testnet. By the end, you'll have created an account, deployed a contract, and performed some basic operations. + +If you already have an app on sandbox, you might want to check out the [sandbox to testnet guide](../../sandbox_to_testnet_guide.md). + +## Key Terms + +In this guide you will see these terms: + +- **aztec**: a command-line tool for interacting with aztec testnet (& sandbox local environments) +- **aztec-nargo**: a command-line tool for compiling contracts +- **aztec.nr**: a Noir library used for writing Aztec smart contracts +- **aztec-wallet**: A tool for creating and interacting with Aztec wallets +- **sandbox**: A local development environment + +## Prerequisites + +Before you begin, you'll need to install: + +1. [Docker](https://docs.docker.com/get-started/get-docker/) + +## Install Aztec CLI + +Run this: + +```sh +bash -i <(curl -s https://install.aztec.network) +``` + +Then install the version of the network running the testnet: + +```bash +aztec-up -v latest +``` + +:::warning + +The testnet is most likely running the latest released version. Updates are backwards compatible so regularly check for updates when interacting with the testnet to reduce errors. + +::: + +## Step 1: Deploy an account to testnet + +Aztec uses account abstraction, which means: + +- All accounts are smart contracts (no EOAs) +- Account signature schemes are private +- Accounts only need deployment if they interact with public components +- Private contract interactions don't require account deployment + +0. Set some variables that we need: + +```bash +export NODE_URL=https://aztec-alpha-testnet-fullnode.zkv.xyz +export SPONSORED_FPC_ADDRESS=0x1260a43ecf03e985727affbbe3e483e60b836ea821b6305bea1c53398b986047 +``` + +1. Create a new account: + +```bash +aztec-wallet create-account \ + --register-only \ + --node-url $NODE_URL \ + --alias my-wallet +``` + +You should see the account information displayed in your terminal. + +2. Register your account with the fee sponsor contract: + +```bash +aztec-wallet register-contract \ + --node-url $NODE_URL \ + --from my-wallet \ + --alias sponsoredfpc \ + $SPONSORED_FPC_ADDRESS SponsoredFPC \ + --salt 0 +``` + +This means you won't have to pay fees - a sponsor contract will pay them for you. Fees on Aztec are abstracted, so you can pay publicly or privately (even without the sequencer knowing who you are). + +You should see that the contract `SponsoredFPC` was added at a specific address. + +3. Deploy your account (required as we will be using public functions): + +```bash +aztec-wallet deploy-account \ + --node-url $NODE_URL \ + --from my-wallet \ + --payment method=fpc-sponsored,fpc=contracts:sponsoredfpc \ + --register-class +``` + +Note: The first time you run these commands, it will take longer as some binaries are installed. This command is generating a client-side proof! + +You should see the tx hash in your terminal. + +If you see an error like `Timeout awaiting isMined` please note this is not an actual error. The transaction has still been sent and is simply waiting to be mined. You may see this if the network is more congested than normal. You can proceed to the next step. + +## Step 2: Deploy and interact with a token contract + +1. Deploy a token contract: + +```bash +aztec-wallet deploy \ + --node-url $NODE_URL \ + --from accounts:my-wallet \ + --payment method=fpc-sponsored,fpc=contracts:sponsoredfpc \ + --alias token \ + TokenContract \ + --args accounts:my-wallet Token TOK 18 --no-wait +``` + +You should see confirmation that the token contract is stored in the database. + +Wait for the transaction to be mined on testnet. You can check the transaction status with the transaction hash on [aztecscan](https://aztecscan.xyz) or [aztecexplorer](https://aztecexplorer.xyz). + +2. Mint 10 private tokens to yourself: + +```bash +aztec-wallet send mint_to_private \ + --node-url $NODE_URL \ + --from accounts:my-wallet \ + --payment method=fpc-sponsored,fpc=contracts:sponsoredfpc \ + --contract-address token \ + --args accounts:my-wallet accounts:my-wallet 10 +``` + +You should see confirmation that the tx hash is stored in the database. + +3. Send 2 private tokens to public: + +```bash +aztec-wallet send transfer_to_public \ + --node-url $NODE_URL \ + --from accounts:my-wallet \ + --payment method=fpc-sponsored,fpc=contracts:sponsoredfpc \ + --contract-address token \ + --args accounts:my-wallet accounts:my-wallet 2 0 +``` + +You should see confirmation that the tx hash is stored in the database. + +4. Check your balances + +Private balance: + +```bash +aztec-wallet simulate balance_of_private \ + --node-url $NODE_URL \ + --from my-wallet \ + --contract-address token \ + --args accounts:my-wallet +``` + +You should see `8n`. + +Public balance: + +```bash +aztec-wallet simulate balance_of_public \ + --node-url $NODE_URL \ + --from my-wallet \ + --contract-address token \ + --args accounts:my-wallet +``` + +You should see `2n`. + +## Next Steps + +Congratulations! You've now learned the fundamentals of working with the Aztec testnet. Here are some resources to continue your journey: + +- [Aztec Playground](https://play.aztec.network/) +- [Tutorials](../tutorials/codealong/contract_tutorials/counter_contract.md) +- [Guide to run a node](../../the_aztec_network/index.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/_category_.json new file mode 100644 index 000000000000..5e0931f01e5e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 3, + "collapsible": true, + "collapsed": true, + "label": "Aztec.js" +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/authwit.md b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/authwit.md new file mode 100644 index 000000000000..b76d6417782a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/authwit.md @@ -0,0 +1,197 @@ +--- +title: How to use authentication witnesses (authwit) +tags: [accounts, authwit] +sidebar_position: 5 +--- + +This page assumes you have authwit set up correctly in your contract. To learn how to do that, [go here](../smart_contracts/writing_contracts/authwit.md). + +For an introduction to authentication witnesses on Aztec, [read this explainer](../../../aztec/concepts/advanced/authwit.md). + +## Import libraries + +These are all the libraries you might need for using authwits in Aztec.js: + +```typescript +import { + computeAuthWitMessageHash, + computeInnerAuthWitHash, +} from "@aztec/aztec.js"; +``` + +You may not need all of these. + +## Publicly deploy accounts + +:::note +This is only required if you are using authwits in public +::: + +If you are using public authwit (ie using `assert_current_call_valid_authwit_public` in your contract), you will need to deploy the following accounts publicly: + +1. The account that is giving permission to an account to act on behalf of it (authwit giver) +2. The account that does the action (authwit receiver) + +Here is an example implementation: + +```typescript title="public_deploy_accounts" showLineNumbers +export async function ensureAccountsPubliclyDeployed(sender: Wallet, accountsToDeploy: Wallet[]) { + // We have to check whether the accounts are already deployed. This can happen if the test runs against + // the sandbox and the test accounts exist + const accountsAndAddresses = await Promise.all( + accountsToDeploy.map(async account => { + const address = account.getAddress(); + return { + address, + deployed: (await sender.getContractMetadata(address)).isContractPubliclyDeployed, + }; + }), + ); + const instances = ( + await Promise.all( + accountsAndAddresses + .filter(({ deployed }) => !deployed) + .map(({ address }) => sender.getContractMetadata(address)), + ) + ).map(contractMetadata => contractMetadata.contractInstance); + const contractClass = await getContractClassFromArtifact(SchnorrAccountContractArtifact); + if (!(await sender.getContractClassMetadata(contractClass.id, true)).isContractClassPubliclyRegistered) { + await (await registerContractClass(sender, SchnorrAccountContractArtifact)).send().wait(); + } + const requests = await Promise.all(instances.map(async instance => await deployInstance(sender, instance!))); + const batch = new BatchCall(sender, requests); + await batch.send().wait(); +} +``` +> Source code: yarn-project/end-to-end/src/fixtures/utils.ts#L677-L705 + + +You would then call this like so: + +```typescript title="public_deploy_accounts" showLineNumbers +await ensureAccountsPubliclyDeployed(wallets[0], wallets.slice(0, 2)); +``` +> Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L24-L26 + + +## Define the action + +When creating an authwit, you will need to pass the authwit giver, the authwit receiver (who will perform the action), and the action that is being authorized. The action can be a smart contract function call, or alternatively, arbitrary data. + +### When the action is a function call + +You can define the action like this: + +```typescript title="authwit_computeAuthWitMessageHash" showLineNumbers +const action = asset + .withWallet(wallets[1]) + .methods.transfer(wallets[0].getAddress(), wallets[1].getAddress(), amount, authwitNonce); +``` +> Source code: yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_private.test.ts#L55-L59 + + +In this example, + +- `asset` refers to a token contract +- `withWallet(wallets[1])` is specifying the authwit receiver (`wallets[1]`) will do this action +- `.methods.transfer()` is specifying that the action is calling the `transfer` method on the token contract +- `(wallets[0].getAddress(), wallets[1].getAddress(), amount, nonce);` are the args of this method - it will send the `amount` from `wallets[0]` to `wallets[1]` + +### Arbitrary message + +You can hash your own authwit message by creating an inner hash with the data, like this: + +```typescript title="compute_inner_authwit_hash" showLineNumbers +const innerHash = await computeInnerAuthWitHash([Fr.fromHexString('0xdead')]); +``` +> Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L45-L47 + + +Then create the message hash by hashing the inner hash with the authwit receiver address, chainId, and version: + +```typescript title="compute_arbitrary_authwit_hash" showLineNumbers + +const intent = { consumer: auth.address, innerHash }; +``` +> Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L48-L51 + + +## Create the authwit + +There are slightly different interfaces depending on whether your contract is checking the authwit in private or public. + +Public authwits are stored in the account contract and batched with the authwit action call, so a user must send a transaction to update their account contract, authorizing an action before the authorized contract's public call will succeed. + +Private execution uses oracles and are executed locally by the PXE, so the authwit needs to be created by the authwit giver and then added to the authwit receiver's PXE. + +### Private + +This is expected to be used alongside [private authwits in Aztec.nr contract](../smart_contracts/writing_contracts/authwit.md#private-functions). + +Create a private authwit like this: + +```typescript title="create_authwit" showLineNumbers +const witness = await wallets[0].createAuthWit({ caller: wallets[1].getAddress(), action }); +``` +> Source code: yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_private.test.ts#L60-L62 + + +In this example, + +- `wallets[0]` is the authwit giver +- `wallets[1]` is the authwit receiver and caller of the function +- `action` was [defined previously](#define-the-action) + +If you created an arbitrary message, you can create the authwit by replacing these params with the outer hash: + +```typescript title="compute_arbitrary_authwit_hash" showLineNumbers + +const intent = { consumer: auth.address, innerHash }; +``` +> Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L48-L51 + + +Then add it to the wallet of the authwit receiver (the caller of the function): + +```typescript title="add_authwit" showLineNumbers +await action.send({ authWitnesses: [witness] }).wait(); +``` +> Source code: yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_private.test.ts#L66-L68 + + +### Public + +This is expected to be used alongside [public authwits in Aztec.nr contract](../smart_contracts/writing_contracts/authwit.md#public-functions). + +Set a public authwit like this: + +```typescript title="set_public_authwit" showLineNumbers +const validateActionInteraction = await wallets[0].setPublicAuthWit( + { caller: wallets[1].getAddress(), action }, + true, +); +await validateActionInteraction.send().wait(); +``` +> Source code: yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_public.test.ts#L123-L129 + + +Remember it is a transaction and calls a method in the account contract. In this example, + +- `wallets[0]` is the authwit giver +- `wallets[1]` is the authwit receiver and caller of the function +- `action` was [defined previously](#define-the-action) +- `true` sets the `authorized` boolean (`false` would revoke this authwit) + +If you created an arbitrary message, you would replace the first param struct with the outer hash: + +```typescript title="set_public_authwit" showLineNumbers +const validateActionInteraction = await wallets[0].setPublicAuthWit(intent, true); +await validateActionInteraction.send().wait(); +``` +> Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L155-L158 + + +## Further reading + +- [An explainer of authentication witnesses](../../../aztec/concepts/advanced/authwit.md) +- [Authwits in Aztec.nr](../smart_contracts/writing_contracts/authwit.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/call_view_function.md b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/call_view_function.md new file mode 100644 index 000000000000..0f20a45a3242 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/call_view_function.md @@ -0,0 +1,51 @@ +--- +title: How to Simulate a Function Call +tags: [functions] +sidebar_position: 2 +--- + +This guide explains how to `simulate` a function call using Aztec.js. + +## Prerequisites + +You should have a wallet to act as the caller, and a contract that has been deployed. + +You can learn how to create wallets from [this guide](./create_account.md). + +You can learn how to deploy a contract [here](./deploy_contract.md). + +## Relevant imports + +You will need to import this from Aztec.js: + +```typescript title="import_contract" showLineNumbers +import { Contract } from '@aztec/aztec.js'; +``` +> Source code: yarn-project/end-to-end/src/composed/docs_examples.test.ts#L7-L9 + + +## Define contract + +Get a previously deployed contract like this: + +```typescript title="get_contract" showLineNumbers +const contract = await Contract.at(deployedContract.address, TokenContractArtifact, wallet); +``` +> Source code: yarn-project/end-to-end/src/composed/docs_examples.test.ts#L45-L47 + + +## Simulating function calls + +Call the `simulate` function on the typescript contract wrapper like this: + +```typescript title="simulate_function" showLineNumbers +const balance = await contract.methods.balance_of_public(newWallet.getAddress()).simulate(); +expect(balance).toEqual(1n); +``` +> Source code: yarn-project/end-to-end/src/composed/docs_examples.test.ts#L54-L57 + + +:::info Note +- If the simulated function is `utility` you will get a properly typed value. +- If the simulated function is `public` or `private` it will return a Field array of size 4. +::: diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/create_account.md b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/create_account.md new file mode 100644 index 000000000000..815fe84d4c2a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/create_account.md @@ -0,0 +1,44 @@ +--- +title: How to Create a New Account +tags: [accounts] +sidebar_position: 0 +--- + +This guide explains how to create a new account using Aztec.js. + +## Relevant imports + +You will need to import these libraries: + +```typescript title="create_account_imports" showLineNumbers +import { getSchnorrAccount } from '@aztec/accounts/schnorr'; +import { getDeployedTestAccountsWallets } from '@aztec/accounts/testing'; +import { Fr, GrumpkinScalar, createPXEClient } from '@aztec/aztec.js'; +``` +> Source code: yarn-project/end-to-end/src/composed/docs_examples.test.ts#L2-L6 + + +## Define arguments needed + +```typescript title="define_account_vars" showLineNumbers +const PXE_URL = process.env.PXE_URL || 'http://localhost:8080'; +const pxe = createPXEClient(PXE_URL); +const secretKey = Fr.random(); +const signingPrivateKey = GrumpkinScalar.random(); +``` +> Source code: yarn-project/end-to-end/src/composed/docs_examples.test.ts#L18-L23 + + +## Create the wallet with these args + +```typescript title="create_wallet" showLineNumbers +// Use a pre-funded wallet to pay for the fees for the deployments. +const wallet = (await getDeployedTestAccountsWallets(pxe))[0]; +const newAccount = await getSchnorrAccount(pxe, secretKey, signingPrivateKey); +await newAccount.deploy({ deployWallet: wallet }).wait(); +const newWallet = await newAccount.getWallet(); +``` +> Source code: yarn-project/end-to-end/src/composed/docs_examples.test.ts#L25-L31 + + +Now you have a new wallet in your PXE! To learn how to use this wallet to deploy a contract, read [this guide](./deploy_contract.md). diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/deploy_contract.md b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/deploy_contract.md new file mode 100644 index 000000000000..3d51cfa5790e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/deploy_contract.md @@ -0,0 +1,139 @@ +--- +title: How to Deploy a Contract +tags: [contracts] +sidebar_position: 1 +--- + +Once you have [compiled](../smart_contracts/how_to_compile_contract.md) your contracts you can proceed to deploying them using Aztec.js. + +You can use this method to deploy your contracts to the sandbox or to a remote network. + +## Prerequisites + +- Contract artifacts ready (go to [How to Compile Contract](../smart_contracts/how_to_compile_contract.md) for instructions on how to compile contracts) +- Aztec Sandbox running (go to [Getting Started](../../getting_started.md) for instructions on how to install and run the sandbox) + +## Deploy + +Contracts can be deployed using the `aztec.js` library. + +### Generate the typescript artifact + +Compile the contract: + +```bash +aztec-nargo compile +``` + +Generate the typescript class: + +```bash +aztec codegen ./aztec-nargo/output/target/path -o src/artifacts +``` + +This would create a typescript file like `Example.ts` in `./src/artifacts`. + +### Deploying + +Import the typescript artifact into your file. + +```typescript title="import_artifact" showLineNumbers +import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; +``` +> Source code: yarn-project/end-to-end/src/sample-dapp/deploy.mjs#L5-L7 + + +Then you can use the `Contract` class **or** the [generated contract class](#using-generated-contract-class) to deploy the contract. + +To use the `Contract` class to deploy a contract: + +```typescript title="dapp-deploy" showLineNumbers +const { PXE_URL = 'http://localhost:8080' } = process.env; + +async function main() { + const pxe = createPXEClient(PXE_URL); + await waitForPXE(pxe); + + const [ownerWallet] = await getInitialTestAccountsWallets(pxe); + const ownerAddress = ownerWallet.getAddress(); + + const token = await Contract.deploy(ownerWallet, TokenContractArtifact, [ownerAddress, 'TokenName', 'TKN', 18]) + .send() + .deployed(); + + console.log(`Token deployed at ${token.address.toString()}`); + + const addresses = { token: token.address.toString() }; + writeFileSync('addresses.json', JSON.stringify(addresses, null, 2)); +} +``` +> Source code: yarn-project/end-to-end/src/sample-dapp/deploy.mjs#L13-L32 + + +#### Deploy Arguments + +There are several optional arguments that can be passed: + +The `deploy(...)` method is generated automatically with the typescript class representing your contract. + +Additionally the `.send()` method can have a few optional arguments too, which are specified in an optional object: + +```typescript title="deploy_options" showLineNumbers +export type DeployOptions = { + /** An optional salt value used to deterministically calculate the contract address. */ + contractAddressSalt?: Fr; + /** Set to true to *not* include the sender in the address computation. */ + universalDeploy?: boolean; + /** Skip contract class registration. */ + skipClassRegistration?: boolean; + /** Skip public deployment, instead just privately initialize the contract. */ + skipPublicDeployment?: boolean; + /** Skip contract initialization. */ + skipInitialization?: boolean; +} & SendMethodOptions; +``` +> Source code: yarn-project/aztec.js/src/contract/deploy_method.ts#L32-L45 + + +### Using generated contract class + +As a more complete example, here a `Token` contract deployment whose artifacts are included in the `@aztec/noir-contracts.js` package. You can use similar deployment syntax with your own contract by importing the TS artifact generated with `aztec codegen`. This example uses the generated `TokenContract` to deploy. + +```ts +import { getSchnorrAccount } from '@aztec/accounts/schnorr'; +import { getDeployedTestAccountsWallets } from '@aztec/accounts/testing'; +import { Fr, GrumpkinScalar, createPXEClient } from '@aztec/aztec.js'; +import { Contract } from '@aztec/aztec.js'; +import { TokenContract, TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; + +async function main(){ + +const PXE_URL = process.env.PXE_URL || 'http://localhost:8080'; +const pxe = createPXEClient(PXE_URL); +const secretKey = Fr.random(); +const signingPrivateKey = GrumpkinScalar.random(); + +// Use a pre-funded wallet to pay for the fees for the deployments. +const wallet = (await getDeployedTestAccountsWallets(pxe))[0]; +const newAccount = await getSchnorrAccount(pxe, secretKey, signingPrivateKey); +await newAccount.deploy({ deployWallet: wallet }).wait(); +const newWallet = await newAccount.getWallet(); + +const deployedContract = await TokenContract.deploy( + wallet, // wallet instance + wallet.getAddress(), // account + 'TokenName', // constructor arg1 + 'TokenSymbol', // constructor arg2 + 18, +) + .send() + .deployed(); + +const contract = await Contract.at(deployedContract.address, TokenContractArtifact, wallet); + +} +``` + +:::note +You can try running the deployment with the same salt the second time in which case the transaction will fail because the address has been already deployed to. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/index.md b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/index.md new file mode 100644 index 000000000000..ec8e40b39e76 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/index.md @@ -0,0 +1,34 @@ +--- +title: Aztec.js +tags: [aztec.js, javascript, typescript] +--- + +import DocCardList from "@theme/DocCardList"; + +Aztec.js is a library that provides APIs for managing accounts and interacting with contracts on the Aztec network. It communicates with the [Private eXecution Environment (PXE)](../../../aztec/concepts/pxe/index.md) through a `PXE` implementation, allowing developers to easily register new accounts, deploy contracts, view functions, and send transactions. + +## Installing + +``` +npm install @aztec/aztec.js +``` + +## Importing + +At the top of your JavaScript file, you can import what you need, eg: + +```typescript title="import_aztecjs" showLineNumbers +import { type AztecAddress, type AztecNode, Fr, type Logger, type PXE, type Wallet, sleep } from '@aztec/aztec.js'; +import { TokenContract } from '@aztec/noir-contracts.js/Token'; +``` +> Source code: yarn-project/end-to-end/src/e2e_2_pxes.test.ts#L3-L6 + + +## Flow + +These are some of the important functions you'll need to use in your Aztec.js: + +- [Create an account with `@aztec/accounts`](./create_account.md) +- [Deploy a contract](./deploy_contract.md) +- [Simulate a function call](./call_view_function.md) +- [Send a transaction](./send_transaction.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/pay_fees.md b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/pay_fees.md new file mode 100644 index 000000000000..e84dff5b6309 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/pay_fees.md @@ -0,0 +1,320 @@ +--- +title: How to Pay Fees +tags: [fees, transactions, developers] +sidebar_position: 3 +--- + +There a several ways to pay for transaction fees, thanks to fee abstraction implemented in the Aztec protocol. + +This guide shows the various ways this can be done. For more of a background see [Fees on Aztec](../../../aztec/concepts/fees). + +## Methods to pay a tx fee + +1. Natively via an existing balance of fee juice + +Either your own account or someone else's address has a balance of fee juice, and the transaction is paid for from this balance. + +2. Natively via a balance of fee juice bridged from L1 to be claimed on L2 + +Processing this transaction first claims bridged fee juice then is paid for from the balance, all in the same public/private tx. + +3. Privately or publicly via a Fee Paying Contract + +A fee paying contract (FPC) is created and nominates a token that it accepts to then pay for transactions in fee juice. So a user doesn't need to hold fee juice, they only need the token that the FPC accepts. + +The FPC can also decide to refund users in cases of overpayment. For private FPCs, this incurrs a substantial gate count (proving time) and DA costs so in some cases it might be cheaper to not get the refund. The sample FPCs we show here refund the users but a FPC deployer or user should think carefully if asking for refunds is a smart option. + +The most straightforward way to pay for a transaction is via the sponsored fee payment method, bootstrapping some transactions by skipping the need to bridge fee juice to the account. This method uses a type of fee paying contract configured to pay for a number of transactions without requiring payment, but also requires that there is a sponsor to pay for the transactions. **This only exists on the sandbox as a convenience to get started quickly, though you could deploy it on testnet too for your own use case** + +The most straightforward way to pay for a transaction is via the sponsored fee payment method, bootstrapping some transactions by skipping the need to bridge fee juice to the account. This method uses a type of fee paying contract configured to pay for a number of transactions without requiring payment, but also requires that there is a sponsor to pay for the transactions. + +import { General } from '@site/src/components/Snippets/general_snippets'; + +:::tip Use a block explorer + + +::: + +## Bridging Fee Juice + +### Sandbox + +To first get fee juice into an account it needs to be bridged from L1. You can skip this section if you want to first make a transaction via the SponsoredFPC. + +:::note + +See here to [Bridge Fee Juice](../../../developers/reference/environment_reference/cli_wallet_reference#bridge-fee-juice) via the CLI wallet. + +::: + +One way of bridging of tokens is described fully [here](../../../developers/tutorials/codealong/js_tutorials/token_bridge.md#deposit-to-aztec). Below summarises specifically bridging fee juice on the sandbox. + +First get the node info and create a public client pointing to the sandbox's anvil L1 node (from foundry): + +```javascript title="get_node_info_pub_client" showLineNumbers +const info = await pxe.getNodeInfo(); +const publicClient = getPublicClient({ + l1RpcUrls: ['http://localhost:8545'], + l1ChainId: foundry.id, +}); +``` +> Source code: yarn-project/end-to-end/src/spartan/smoke.test.ts#L52-L58 + + +After importing: + +```ts +import { L1FeeJuicePortalManager } from "@aztec/aztec.js"; +``` + +Create a new fee juice portal manager and bridge fee juice publicly to Aztec: + +```javascript title="bridge_fee_juice" showLineNumbers +const portal = await L1FeeJuicePortalManager.new(pxe, l1Client, log); +const claim = await portal.bridgeTokensPublic(recipient, amount, true /* mint */); +``` +> Source code: yarn-project/end-to-end/src/spartan/setup_test_wallets.ts#L110-L113 + + +For the mechanisms to complete bridging between L1 and Aztec, we have to wait for 2 Aztec blocks to progress. This can be triggered manually by sending 2 transactions in the sandbox, or by waiting for 2 blocks on a public network. After this, an account should have its fee juice ready to use in transactions. + +Alternatively, the resulting claim object can be used to create a payment method to claim and pay for a transaction in one, where the transaction is the contract's deployment. + +### Testnet + +The `L1FeeJuicePortalManager` will not be able to mint assets for you on testnet. You will need to call the fee asset's `mint` function on L1 to mint fee juice, e.g.: + +```bash +cast call $FEE_ASSET_HANDLER_CONTRACT "mint(address)" $MY_L1_ADDRESS --rpc-url +``` + +Then bridge it to L2, using the the `L1FeeJuicePortalManager` as described above. + +## Examples + +### Pay with FeeJuice + +To send a transaction from an already deployed account already holding fee juice. A user's fee juice balance is public, so paying for a transaction with fee juice will reveal information about which account is paying for the transaction and how much they paid. + +Note: this example is a public token transfer call, but can equally be a private function call + +```ts +import { FeeJuicePaymentMethod } from "@aztec/aztec.js"; +``` + +```javascript title="pay_fee_juice_send" showLineNumbers +const paymentMethod = new FeeJuicePaymentMethod(aliceAddress); +const { transactionFee } = await bananaCoin.methods + .transfer_in_public(aliceAddress, bobAddress, 1n, 0n) + .send({ fee: { gasSettings, paymentMethod } }) + .wait(); +``` +> Source code: yarn-project/end-to-end/src/e2e_fees/fee_juice_payments.test.ts#L96-L102 + + +**The equivalent to specify fees via CLI...** + +import { CLI_Fees } from '@site/src/components/Snippets/general_snippets'; + + + +### Claim and deploy + +Here we will use the `claim` object previously from the bridging section, and the corresponding `wallet`, to create the fee payment method. The payment method is then used to claim fee juice and pay for account contract deployment. Note the function used to bridge fee juice (private/public) should correspond to how the fee juice is claimed. + +```ts +import { FeeJuicePaymentMethodWithClaim } from "@aztec/aztec.js"; +``` + +```javascript title="claim_and_deploy" showLineNumbers +const wallet = await account.getWallet(); +const paymentMethod = new FeeJuicePaymentMethodWithClaim(wallet, claim); +const sentTx = account.deploy({ fee: { paymentMethod } }); +const txHash = await sentTx.getTxHash(); +``` +> Source code: yarn-project/bot/src/factory.ts#L125-L130 + + +**The equivalent to specify fees via CLI...** + +The CLI tool `aztec-wallet` takes the fee payment method via the param: `--payment method=fee_juice,claim` + +#### Claim and Pay + +Claiming bridged fee juice and using it to pay for transaction fees results in fees being paid publicly. + +Calling a function, in this case checking the balance of the fee juice contract: + +```javascript title="claim_and_pay" showLineNumbers +const paymentMethod = new FeeJuicePaymentMethodWithClaim(bobWallet, claim); +const receipt = await feeJuiceContract + .withWallet(bobWallet) + .methods.check_balance(0n) + .send({ fee: { gasSettings, paymentMethod } }) + .wait(); +``` +> Source code: yarn-project/end-to-end/src/e2e_fees/fee_juice_payments.test.ts#L77-L84 + + +### Fee Paying Contract + +Users can pay fees privately or publicly via FPCs. A FPC must be deployed and funded with fee juice before it can be used to pay for transactions. Users must have the token that the FPC accepts to pay for transactions. They must have a private balance of the token if they want to pay for fees privately, and a public balance if they want to pay for fees publicly. + +Import the appropriate method from aztec.js. + +```ts +import { + PrivateFeePaymentMethod, + PublicFeePaymentMethod, +} from "@aztec/aztec.js"; +``` + +A FPC contract must be registered in a users PXE before it can be used. This will automatically happen if you deploy a FPC to your sandbox, but must be added manually if you are using a standalone PXE. + +```ts +import { FPCContract } from "@aztec/noir-contracts.js/FPC"; +import { getContractInstanceFromDeployParams } from "@aztec/aztec.js"; + +// ... (set up the wallet and PXE) + +// get the deployed FPC contract instance +const fpcContractInstance = getContractInstanceFromDeployParams( + FPCContract.artifact, + fpcDeployParams // the params used to deploy the FPC +); +// register the already deployed FPC contract in users PXE +await pxe.registerContract({ + instance: fpcContractInstance, + artifact: FPCContract.artifact, +}); +``` + +The fee payment method is created and used as follows, with similar syntax for private or public fee payments: + +```javascript title="fpc" showLineNumbers +const tx = await bananaCoin.methods + .transfer_in_public(aliceAddress, bobAddress, bananasToSendToBob, 0) + .send({ + fee: { + gasSettings, + paymentMethod: new PublicFeePaymentMethod(bananaFPC.address, aliceWallet), + }, + }) + .wait(); +``` +> Source code: yarn-project/end-to-end/src/e2e_fees/public_payments.test.ts#L58-L68 + + +In this example, thanks to this FPC's `accepted_asset` being banana tokens, Alice only needs to hold this token and not fee juice. The asset that a FPC accepts for paying fees is determined when the FPC is deployed. The function being called happens to also be a transfer of banana tokens to Bob. + +More on FPCs [here](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr) + +See this [section](../../reference/environment_reference/cli_wallet_reference.md#fee-paying-contract) for paying fees via an FPC using the CLI wallet. + +### Sponsored Fee Paying Contract + +This method of fee payment will only work for environments where a sponsored fee paying contract is deployed. +The sandbox comes with a sponsored fee paying contract deployed, so this can be used to pay for transactions without needing to bridge fee juice. +To use sponsored FPCs in other environments, they will need to be deployed and funded with fee juice. +Using a SponsoredFPC payment method is as simple as importing it, registering it and passing it the PXE: + +#### Sandbox with PXE + +```ts +import { SponsoredFeePaymentMethod } from "@aztec/aztec.js/fee/testing"; +``` + +```ts +const paymentMethod = new SponsoredFeePaymentMethod(deployedSponsoredFPC); +``` + +#### Standalone PXE (e.g. Testnet) + +Register the default SponsoredFPC in the PXE: + +```ts +import { SponsoredFPCContract } from "@aztec/noir-contracts.js/SponsoredFPC"; + +// ... (set up the wallet and PXE) + +// register the already deployed SponsoredFPC contract in users PXE +const sponseredFPC = await getSponsoredFPCInstance(); +await pxe.registerContract({ + instance: sponsoredFPC, + artifact: SponsoredFPCContract.artifact, +}); +const paymentMethod = new SponsoredFeePaymentMethod(sponseredFPC.address); +``` + +You can see an example implementation for `getSponsoredFPCInstance()` [here](https://github.com/AztecProtocol/aztec-packages/blob/360a5f628b4edaf1ea9b328d9e9231f60fdc81a0/yarn-project/aztec/src/sandbox/sponsored_fpc.ts#L5). + +Once this is set up, a transaction can specify this as the `paymentMethod` in the fee object. +You can see an example of how to get a deployed instance of a sponsored FPC in the sandbox [here](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/aztec/src/sandbox/sponsored_fpc.ts#L15). +For example, a contract can be deployed with an fpc as follows: + +```ts +const paymentMethod = new SponsoredFeePaymentMethod(deployedSponsoredFPC); +myAccountManager.deploy({ fee: { paymentMethod } }); +``` + +You can find the corresponding CLI command info [here](../../reference/environment_reference/cli_wallet_reference#sponsored-fee-paying-contract) + +## Fee Options + +Functions pertaining to sending a transaction, such as `deploy` and `send`, each include a `fee` variable defined with the following (optional) parameters: + +```javascript title="user_fee_options" showLineNumbers +/** Fee options as set by a user. */ +export type UserFeeOptions = { + /** The fee payment method to use */ + paymentMethod?: FeePaymentMethod; + /** The gas settings */ + gasSettings?: Partial>; + /** Percentage to pad the base fee by, if empty, defaults to 0.5 */ + baseFeePadding?: number; + /** Whether to run an initial simulation of the tx with high gas limit to figure out actual gas settings. */ + estimateGas?: boolean; + /** Percentage to pad the estimated gas limits by, if empty, defaults to 0.1. Only relevant if estimateGas is set. */ + estimatedGasPadding?: number; +}; +``` +> Source code: yarn-project/entrypoints/src/interfaces.ts#L83-L97 + + +### Fee Payment Method + +The `paymentMethod` is an object for the type of payment. Each of the implementations can be found [here](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/aztec.js/src/fee). For example: + +```javascript title="fee_juice_method" showLineNumbers +/** + * Pay fee directly in the Fee Juice. + */ +export class FeeJuicePaymentMethod implements FeePaymentMethod { +``` +> Source code: yarn-project/aztec.js/src/fee/fee_juice_payment_method.ts#L6-L11 + + +### Gas Settings + +```javascript title="gas_settings_vars" showLineNumbers +/** Gas usage and fees limits set by the transaction sender for different dimensions and phases. */ +export class GasSettings { + constructor( + public readonly gasLimits: Gas, + public readonly teardownGasLimits: Gas, + public readonly maxFeesPerGas: GasFees, + public readonly maxPriorityFeesPerGas: GasFees, + ) {} +``` +> Source code: yarn-project/stdlib/src/gas/gas_settings.ts#L11-L20 + + +import { Gas_Settings_Components, Gas_Settings } from '@site/src/components/Snippets/general_snippets'; + + + + + +### Other params + +Fee and gas padding params can be left to their default values, and the estimateGas boolean can be used when simulating a tx to calculate gas. diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/send_transaction.md b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/send_transaction.md new file mode 100644 index 000000000000..db95af106a70 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/send_transaction.md @@ -0,0 +1,44 @@ +--- +title: How to Send a Transaction +sidebar_position: 4 +--- + +This guide explains how to send a transaction using Aztec.js. + +## Prerequisites + +You should have a wallet to act as the transaction sender, a contract that has been deployed, and fee juice to pay for transactions. + +You can learn how to create wallets from [this guide](./create_account.md). + +You can learn how to deploy a contract [here](./deploy_contract.md). + +You can learn how to use fee juice [here](./pay_fees.md). + +## Relevant imports + +You will need to import this library: + +```typescript title="import_contract" showLineNumbers +import { Contract } from '@aztec/aztec.js'; +``` +> Source code: yarn-project/end-to-end/src/composed/docs_examples.test.ts#L7-L9 + + +## Define contract + +Get a previously deployed contract like this: + +```typescript title="get_contract" showLineNumbers +const contract = await Contract.at(deployedContract.address, TokenContractArtifact, wallet); +``` +> Source code: yarn-project/end-to-end/src/composed/docs_examples.test.ts#L45-L47 + + +## Call method + +```typescript title="send_transaction" showLineNumbers +const _tx = await contract.methods.mint_to_public(newWallet.getAddress(), 1).send().wait(); +``` +> Source code: yarn-project/end-to-end/src/composed/docs_examples.test.ts#L50-L52 + diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/test.md b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/test.md new file mode 100644 index 000000000000..577689dddea5 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/js_apps/test.md @@ -0,0 +1,259 @@ +--- +title: Testing Aztec.nr contracts with TypeScript +tags: [contracts, tests] +sidebar_position: 6 +--- + +In this guide we will cover how to interact with your Aztec.nr smart contracts in a testing environment to write automated tests for your apps. + +## Prerequisites + +- A compiled contract with TS interface (read [how to compile](../smart_contracts/how_to_compile_contract.md)) +- Your sandbox running (read [getting started](../../getting_started.md)) + +## Create TS file and install libraries + +Pick where you'd like your tests to live and create a Typescript project. + +You will need to install Aztec.js: + +```bash +yarn add @aztec/aztecjs +``` + +You can use `aztec.js` to write assertions about transaction statuses, about chain state both public and private, and about logs. + +## Import relevant libraries + +Import `aztecjs`. This is an example of some functions and types you might need in your test: + +```typescript title="imports" showLineNumbers +import { getDeployedTestAccountsWallets } from '@aztec/accounts/testing'; +import { type AccountWallet, Fr, type PXE, TxStatus, createPXEClient, waitForPXE } from '@aztec/aztec.js'; +import { CheatCodes } from '@aztec/aztec.js/testing'; +import { TokenContract } from '@aztec/noir-contracts.js/Token'; +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L1-L6 + + +You should also import the [Typescript class you generated](../smart_contracts/how_to_compile_contract.md#typescript-interfaces): + +```typescript title="import_contract" showLineNumbers +import { TestContract } from '@aztec/noir-test-contracts.js/Test'; +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L7-L10 + + +## Create a PXE client + +Currently, testing Aztec.nr smart contracts means testing them against the PXE that runs in the local sandbox. Create a PXE client: + +```typescript title="create_pxe_client" showLineNumbers +const pxe = createPXEClient(PXE_URL); +await waitForPXE(pxe); +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L19-L22 + + +and use the accounts that are initialized with it: + +```typescript title="use-existing-wallets" showLineNumbers +pxe = createPXEClient(PXE_URL); +[owner, recipient] = await getDeployedTestAccountsWallets(pxe); +token = await TokenContract.deploy(owner, owner.getCompleteAddress(), 'TokenName', 'TokenSymbol', 18) + .send() + .deployed(); +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L32-L38 + + +Alternatively, you can [create a new account.](./create_account.md). + +## Write tests + +### Calling and sending transactions + +You can send transactions within your tests with Aztec.js. Read how to do that in these guides: + +- [Simulate a function](./call_view_function.md) +- [Send a transaction](./send_transaction.md) + +### Using debug options + +You can use the `debug` option in the `wait` method to get more information about the effects of the transaction. This includes information about new note hashes added to the note hash tree, new nullifiers, public data writes, new L2 to L1 messages, new contract information, and newly visible notes. + +This debug information will be populated in the transaction receipt. You can log it to the console or use it to make assertions about the transaction. + +```typescript title="debug" showLineNumbers +const tx = await asset.methods.transfer(accounts[1].address, totalBalance).send().wait(); +const txEffects = await node.getTxEffect(tx.txHash); +``` +> Source code: yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts#L25-L28 + + +You can also log directly from Aztec contracts. Read [this guide](../../reference/debugging/index.md#logging-in-aztecnr) for some more information. + +### Examples + +#### A private call fails + +We can check that a call to a private function would fail by simulating it locally and expecting a rejection. Remember that all private function calls are only executed locally in order to preserve privacy. As an example, we can try transferring more tokens than we have, which will fail an assertion with the `Balance too low` error message. + +```typescript title="local-tx-fails" showLineNumbers +const call = token.methods.transfer(recipient.getAddress(), 200n); +await expect(call.simulate()).rejects.toThrow(/Balance too low/); +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L123-L126 + + +Under the hood, the `send()` method executes a simulation, so we can just call the usual `send().wait()` to catch the same failure. + +```typescript title="local-tx-fails" showLineNumbers +const call = token.methods.transfer(recipient.getAddress(), 200n); +await expect(call.simulate()).rejects.toThrow(/Balance too low/); +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L123-L126 + + +#### A transaction is dropped + +We can have private transactions that work fine locally, but are dropped by the sequencer when tried to be included due to a double-spend. In this example, we simulate two different transfers that would succeed individually, but not when both are tried to be mined. Here we need to `send()` the transaction and `wait()` for it to be mined. + +```typescript title="tx-dropped" showLineNumbers +const call1 = token.methods.transfer(recipient.getAddress(), 80n); +const call2 = token.methods.transfer(recipient.getAddress(), 50n); + +const provenCall1 = await call1.prove(); +const provenCall2 = await call2.prove(); + +await provenCall1.send().wait(); +await expect(provenCall2.send().wait()).rejects.toThrow(/dropped|nullifier/i); +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L130-L139 + + +#### A public call fails locally + +Public function calls can be caught failing locally similar to how we catch private function calls. For this example, we use a [`TokenContract` (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr) instead of a private one. + +```typescript title="local-pub-fails" showLineNumbers +const call = token.methods.transfer_in_public(owner.getAddress(), recipient.getAddress(), 1000n, 0); +await expect(call.simulate()).rejects.toThrow(U128_UNDERFLOW_ERROR); +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L143-L146 + + +#### A public call fails on the sequencer + +This will submit a failing call to the sequencer, who will include the transaction, but without any side effects from our application logic. Requesting the receipt for the transaction will also show it has a reverted status. + +```typescript title="pub-reverted" showLineNumbers +const call = token.methods.transfer_in_public(owner.getAddress(), recipient.getAddress(), 1000n, 0); +const receipt = await call.send().wait({ dontThrowOnRevert: true }); +expect(receipt.status).toEqual(TxStatus.APP_LOGIC_REVERTED); +const ownerPublicBalanceSlot = await cheats.aztec.computeSlotInMap( + TokenContract.storage.public_balances.slot, + owner.getAddress(), +); +const balance = await pxe.getPublicStorageAt(token.address, ownerPublicBalanceSlot); +expect(balance.value).toEqual(100n); +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L150-L160 + + +``` +WARN Error processing tx 06dc87c4d64462916ea58426ffcfaf20017880b353c9ec3e0f0ee5fab3ea923f: Assertion failed: Balance too low. +``` + +### Querying state + +We can check private or public state directly rather than going through view-only methods, as we did in the initial example by calling `token.methods.balance().simulate()`. + +To query storage directly, you'll need to know the slot you want to access. This can be checked in the [contract's `Storage` definition](../../reference/smart_contract_reference/storage/index.md) directly for most data types. However, when it comes to mapping types, as in most EVM languages, we'll need to calculate the slot for a given key. To do this, we'll use the [`CheatCodes`](../../reference/environment_reference/cheat_codes.md) utility class: + +```typescript title="calc-slot" showLineNumbers +cheats = await CheatCodes.create(ETHEREUM_HOSTS.split(','), pxe); +// The balances mapping is indexed by user address +ownerSlot = await cheats.aztec.computeSlotInMap(TokenContract.storage.balances.slot, ownerAddress); +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L74-L78 + + +#### Querying private state + +Private state in the Aztec is represented via sets of [private notes](../../../aztec/concepts/storage/state_model.md#private-state). We can query the Private Execution Environment (PXE) for all notes encrypted for a given user in a contract slot. For example, this gets all notes encrypted for the `owner` user that are stored on the token contract address and on the slot that was calculated earlier. To calculate the actual balance, it extracts the `value` of each note, which is the third element, and sums them up. + +```typescript title="private-storage" showLineNumbers +await token.methods.sync_private_state().simulate(); +const notes = await pxe.getNotes({ + recipient: owner.getAddress(), + contractAddress: token.address, + storageSlot: ownerSlot, + scopes: [owner.getAddress()], +}); +// TODO(#12694): Do not rely on the ordering of members in a struct / check notes manually +const values = notes.map(note => note.note.items[2]); +const balance = values.reduce((sum, current) => sum + current.toBigInt(), 0n); +expect(balance).toEqual(100n); +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L82-L94 + + +#### Querying public state + +Public state behaves as a key-value store, much like in the EVM. We can directly query the target slot and get the result back as a buffer. Note that we use the [`TokenContract` (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr) in this example, which defines a mapping of public balances on slot 6. + +```typescript title="public-storage" showLineNumbers +await token.methods.mint_to_public(owner.getAddress(), 100n).send().wait(); +const ownerPublicBalanceSlot = await cheats.aztec.computeSlotInMap( + TokenContract.storage.public_balances.slot, + owner.getAddress(), +); +const balance = await pxe.getPublicStorageAt(token.address, ownerPublicBalanceSlot); +expect(balance.value).toEqual(100n); +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L98-L106 + + +### Logs + +You can check the logs of events emitted by contracts. Contracts in Aztec can emit both encrypted and unencrypted events. + +#### Querying public logs + +We can query the PXE for the public logs emitted in the block where our transaction is mined. + +```typescript title="public-logs" showLineNumbers +const value = Fr.fromHexString('ef'); // Only 1 bytes will make its way in there :( so no larger stuff +const tx = await testContract.methods.emit_public(value).send().wait(); +const filter = { + fromBlock: tx.blockNumber!, + limit: 1, // 1 log expected +}; +const logs = (await pxe.getPublicLogs(filter)).logs; +expect(logs[0].log.getEmittedFields()).toEqual([value]); +``` +> Source code: yarn-project/end-to-end/src/guides/dapp_testing.test.ts#L110-L119 + + +## Cheats + +The [`CheatCodes`](../../reference/environment_reference/cheat_codes.md) class, which we used for [calculating the storage slot above](#querying-state), also includes a set of cheat methods for modifying the chain state that can be handy for testing. + +### Set next block timestamp + +Since the rollup time is dependent on what "slot" the block is included in, time can be progressed by progressing slots. +The duration of a slot is available by calling `getSlotDuration()` on the Rollup (code in Rollup.sol). + +You can then use the `warp` function on the EthCheatCodes to progress the underlying chain. + + + +## Further reading + +- [How to call a view transactions in Aztec.js](./call_view_function.md) +- [How to send a transactions in Aztec.js](./send_transaction.md) +- [How to deploy a contract in Aztec.js](./deploy_contract.md) +- [How to create an account in Aztec.js](./create_account.md) +- [Cheat codes](../../reference/environment_reference/cheat_codes.md) +- [How to compile a contract](../smart_contracts/how_to_compile_contract.md). diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/_category_.json new file mode 100644 index 000000000000..5c8df7e68a18 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 2, + "collapsible": true, + "collapsed": true, + "label": "Development Environment" +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/cloning_a_box.md b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/cloning_a_box.md new file mode 100644 index 000000000000..5d0d950e61ec --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/cloning_a_box.md @@ -0,0 +1,10 @@ +--- +title: Cloning a box +sidebar_position: 4 +tags: [accounts] +--- +You can use the `npx aztec-app` command to clone a ready-to-go "aztec box" with a sample contract or frontend. Assuming you have [node](https://nodejs.org/en/) installed, run: + +```bash +npx aztec-app +``` \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/creating_schnorr_accounts.md b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/creating_schnorr_accounts.md new file mode 100644 index 000000000000..dc70e39fd809 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/creating_schnorr_accounts.md @@ -0,0 +1,118 @@ +--- +title: Creating Schnorr Accounts +sidebar_position: 2 +draft: true +tags: [accounts] +--- + + + +## Introduction + +This section shows how to create schnorr account wallets on the Aztec Sandbox. + +An in-depth explainer about accounts on aztec can be found [here](../../../aztec/concepts/accounts/index.md). But creating an account on the Sandbox does 2 things: + +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 Private eXecution Environment (PXE) allowing it to decrypt and manage your private state. + +## Pre-requisites + +Have a running Sandbox and a repository that interacts with it as explained [in the quickstart](../../getting_started.md). + +Let's assume you have a file `src/index.ts` from the example used in the Sandbox page. + +## Create accounts on the sandbox + +1. Import relevant modules: + +```typescript title="imports1" showLineNumbers +import { getSchnorrAccount } from '@aztec/accounts/schnorr'; +import { getDeployedTestAccountsWallets } from '@aztec/accounts/testing'; +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts#L54-L57 + + +2. Code to create an account. You must run this inside of a function: + +```typescript title="create_accounts" showLineNumbers +////////////// CREATE SOME ACCOUNTS WITH SCHNORR SIGNERS ////////////// + +// Use one of the pre-funded accounts to pay for the deployments. +const [fundedWallet] = await getDeployedTestAccountsWallets(pxe); + +// Creates new accounts using an account contract that verifies schnorr signatures +// Returns once the deployment transactions have settled +const createSchnorrAccounts = async (numAccounts: number, pxe: PXE) => { + const accountManagers = await timesParallel(numAccounts, () => + getSchnorrAccount( + pxe, + Fr.random(), // secret key + GrumpkinScalar.random(), // signing private key + ), + ); + + return await Promise.all( + accountManagers.map(async x => { + await x.deploy({ deployWallet: fundedWallet }).wait(); + return x; + }), + ); +}; + +// Create 2 accounts and wallets to go with each +logger.info(`Creating accounts using schnorr signers...`); +const accounts = await createSchnorrAccounts(2, pxe); +const [aliceWallet, bobWallet] = await Promise.all(accounts.map(a => a.getWallet())); +const [alice, bob] = (await Promise.all(accounts.map(a => a.getCompleteAddress()))).map(a => a.address); + +////////////// VERIFY THE ACCOUNTS WERE CREATED SUCCESSFULLY ////////////// +const registeredAccounts = (await pxe.getRegisteredAccounts()).map(x => x.address); +for (const [account, name] of [ + [alice, 'Alice'], + [bob, 'Bob'], +] as const) { + if (registeredAccounts.find(acc => acc.equals(account))) { + logger.info(`Created ${name}'s account at ${account.toString()}`); + continue; + } + logger.info(`Failed to create account for ${name}!`); +} +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts#L201-L244 + + +3. Running `yarn start` should now output: + +``` + token Aztec Sandbox Info { + version: 1, + chainId: 31337, + rollupAddress: EthAddress { + buffer: + }, + client: 'pxe@0.1.0', + compatibleNargoVersion: '0.11.1-aztec.0' + } + token Creating accounts using schnorr signers... +3ms + token Created Alice's account at 0x1509b252...0027 +10s + token Created Bob's account at 0x031862e8...e7a3 +0ms +``` + +That might seem like a lot to digest but it can be broken down into the following steps: + +1. We create 2 `Account` objects in Typescript. This object heavily abstracts away the mechanics of configuring and deploying an account contract and setting up a 'wallet' for signing transactions. If you aren't interested in building new types of account contracts or wallets then you don't need to be too concerned with it. In this example we have constructed account contracts and corresponding wallets that sign/verify transactions using schnorr signatures. +2. We wait for the deployment of the 2 account contracts to complete. +3. We retrieve the expected account addresses from the `Account` objects and ensure that they are present in the set of account addresses registered on the Sandbox. + +Note, we use the `getRegisteredAccounts` API to verify that the addresses computed as part of the +account contract deployment have been successfully added to the Sandbox. + +If you were looking at your terminal that is running the Sandbox you should have seen a lot of activity. +This is because the Sandbox will have simulated the deployment of both contracts, executed the private kernel circuit for each account deployment and later on submitted the 2 transactions to the pool. +The sequencer will have picked them up and inserted them into an L2 block and executed the recursive rollup circuits before publishing the L2 block on L1 (in our case Anvil). +Once this has completed, the L2 block is retrieved and pulled down to the PXE so that any new account state can be decrypted. + +## Next Steps + +Check out our section on [Writing your own Account Contract](../../tutorials/codealong/contract_tutorials/write_accounts_contract.md) leveraging our account abstraction. diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/installing_noir_lsp.md b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/installing_noir_lsp.md new file mode 100644 index 000000000000..52bf1b28d18b --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/installing_noir_lsp.md @@ -0,0 +1,17 @@ +--- +title: Installing the Noir LSP +sidebar_position: 1 +tags: [sandbox] +--- + +Install the [Noir Language Support extension](https://marketplace.visualstudio.com/items?itemName=noir-lang.vscode-noir) to get syntax highlighting, syntax error detection and go-to definitions for your Aztec contracts. + +Once the extension is installed, check your nargo binary by hovering over Nargo in the status bar on the bottom right of the application window. Click to choose the path to aztec-nargo (or regular nargo, if you have that installed). + +You can print the path of your `aztec-nargo` executable by running: + +```bash +which aztec-nargo +``` + +To specify a custom nargo executable, go to the VSCode settings and search for "noir", or click extension settings on the `noir-lang` LSP plugin. Update the `Noir: Nargo Path` field to point to your desired `aztec-nargo` executable. \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/run_more_than_one_pxe_sandbox.md b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/run_more_than_one_pxe_sandbox.md new file mode 100644 index 000000000000..be2d3bd0f419 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/run_more_than_one_pxe_sandbox.md @@ -0,0 +1,39 @@ +--- +title: Running Multiple PXEs in the Sandbox +sidebar_position: 3 +tags: [PXE] +--- + +When you run the sandbox, the Aztec node and PXE have their own http server. This makes it possible to run two PXEs on your local machine, which can be useful for testing that notes are accurately stored and remaining private in their respective PXEs. + +We are working on a better solution for this so expect an update soon, but currently you can follow this guide. + +## Run the sandbox in one terminal + +Rather than use the usual command, run: + +```bash +NO_PXE=true aztec start --sandbox +``` + +This removes any other arguments, allowing you to ensure an isolated environment for the sandbox so it doesn't interfere with another PXE. By default, the sandbox will run on port `8080`. + +## Run PXE mode in another terminal + +In another terminal, run: + +```bash +aztec start --port 8081 --pxe --pxe.nodeUrl=http://localhost:8080/ +``` + +This command uses the default ports, so they might need to be changed depending on your configuration. It will run the PXE on port `8081`. + +You should see something like this: + +```bash +[14:01:53.181] INFO: pxe:data:lmdb Starting data store with maxReaders 16 +[14:01:53.677] INFO: pxe:service Started PXE connected to chain 31337 version 1 +[14:01:53.681] INFO: cli Aztec Server listening on port 8081 {"l1ChainId":31337,"l2ChainVersion":1,"l2ProtocolContractsTreeRoot":"0x093cc9324e5a7b44883f515ac490e7294ef8cb1e6d2d8c503255b1b3a9409262","l2CircuitsVkTreeRoot":"0x007c3b32ae1b8b3ed235f158e554d92710b5f126a8b2ed38a0874f6294299b95"} +``` + +You can learn more about custom commands in the [sandbox reference](../../reference/environment_reference/sandbox-reference.md). diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/sandbox_proving.md b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/sandbox_proving.md new file mode 100644 index 000000000000..0084a5308b11 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/sandbox_proving.md @@ -0,0 +1,51 @@ +--- +title: Sandbox PXE Proving +tags: [sandbox, PXE] +--- + +The Sandbox does not have client-side proving in the PXE enabled by default. This reduces testing times and increases development speed by allowing for rapid iteration. + +You may want to enable client-side proving in the Sandbox to better understand how long it takes to execute Aztec transactions. There are 2 ways of doing this: + +1. Run the sandbox in proving mode (every transaction wil be proved) or +2. Use `aztec-wallet` cli to prove a one-off transaction + +:::note +Proving is much slower and should only be used sparingly to analyze real proving times of executing private functions of a contract. +::: + +## Sandbox in Proving Mode + +Here every transaction, contract deployment will be proved. If you want to just prove a single transaction, follow [proving with aztec-wallet cli](#proving-with-aztec-wallet). + +### Usage + +To enable client-side proving: + +```bash +PXE_PROVER_ENABLED=1 aztec start --sandbox +``` + +The sandbox will take much longer to start. The first time it starts, it will need to download a large crs file, which can take several minutes even on a fast internet connection. This is a one-time operation, you will not need to download it again until you update to a new Aztec version. + +The sandbox will also deploy 3 Schnorr account contracts on startup. The sandbox will need to generate transaction proofs for deployment, which will take additional time. + +Once everything has been set up, you will see that the PXE is listening on `localhost:8080` as you would see with the sandbox running in the default mode. At this point you can use the sandbox as you would without client-side proving enabled. + +## Proving with `aztec-wallet` + +You can enable proving on a per-transaction basis using the `aztec-wallet` CLI by setting the `PXE_PROVER_ENABLED` environment variable to `1`. This will use your local `bb` binary to prove the transaction. + +```bash +PXE_PROVER_ENABLED=1 aztec-wallet create-account -a test +``` + +Check the [Quickstart](../../getting_started.md) for a refresher on how to send transactions using `aztec-wallet` or check the [reference here](../../reference/environment_reference/cli_wallet_reference.md) + +Note that you do not need to restart the sandbox in order to start sending proven transactions. You can optionally set this for one-off transactions. + +If this is the first time you are sending transactions with proving enabled, it will take a while to download a CRS file (which is several MBs) that is required for proving. + +:::note +You can also profile your transactions to get gate count, if you don't want to prove your transactions but check how many constraints it is. Follow the [guide here](../../guides/smart_contracts/profiling_transactions.md) +::: diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/versions-updating.md b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/versions-updating.md new file mode 100644 index 000000000000..350dfb4bdb67 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/local_env/versions-updating.md @@ -0,0 +1,157 @@ +--- +title: Updating the Sandbox +sidebar_position: 0 +tags: [sandbox] +--- + +- Current version: `v0.88.0` +- Update with `aztec-up` + +On this page you will find + +- [Understanding versions](#versions) +- [How to automatically update Aztec sandbox and aztec-nargo](#updating) +- [How to update Aztec.nr packages](#updating-aztecnr-packages) +- [How to update Aztec.js packages](#updating-aztecjs-packages) + +## Versions + +Aztec tools (sandbox, nargo), dependencies (Aztec.nr), and sample contracts are constantly being improved. +When developing and referring to example .nr files/snippets, it is helpful to verify the versions of different components (below), and if required keep them in lock-step by [updating](#updating). + +### Checking tool versions + +:::note +The `aztec-nargo` versions follow `nargo` versions, which is different to the Aztec tool versions. +::: + +### Dependency versions + +Dependency versions in a contract's `Nargo.toml` file correspond to the `aztec-packages` repository tag `aztec-packages` (filter tags by `aztec`...) + +If you get an error like: `Cannot read file ~/nargo/github.com/AztecProtocol/aztec-packages/...` +Check the `git=` github url, tag, and directory. + +### Example contract versions + +Example contracts serve as a helpful reference between versions of the Aztec.nr framework since they are strictly maintained with each release. + +Code referenced in the documentation is sourced from contracts within [this directory (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/noir-contracts/contracts). + +As in the previous section, the location of the noir contracts moved at version `0.24.0`, from `yarn-project/noir-contracts` before, to `noir-projects/noir-contracts`. + +:::tip +Notice the difference between the sample Counter contract from `0.23.0` to `0.24.0` shows the `note_type_id` was added. + +```shell +diff ~/nargo/github.com/AztecProtocol/v0.23.0/yarn-project/noir-contracts/contracts/test/counter_contract/src/main.nr ~/nargo/github.com/AztecProtocol/v0.24.0/noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr +``` + +``` +57a58 +> note_type_id: Field, +``` + +::: + +### Language server version (aztec-nargo) + +The [Noir LSP](../local_env/installing_noir_lsp.md) uses your local version of `aztec-nargo`, and thus also `aztec-nargo compile`. +The path of the former (once installed) can be seen by hovering over "Nargo" in the bottom status bar of VS Code, and the latter via the `which aztec-nargo` command. + +:::caution +For Aztec contract files, this should be `aztec-nargo` and for noir-only files this should be `nargo`. Mismatching tools and file types will generate misleading syntax and compiler errors. +::: + +This can present confusion when opening older contracts (and dependencies) written in older version of noir, such as: + +- Logs filled with errors from the dependencies +- Or the LSP fails (re-runs automatically then stops) + The second point requires a restart of the extension, which you can trigger with the command palette (Ctrl + Shift + P) and typing "Reload Window". + +## Updating + +### Steps to keep up to date + +1. Update the Aztec sandbox to the latest version (includes `aztec-nargo`, pxe, etc): + +```shell +aztec-up +``` + +To update to a specific version, pass the version number after the `aztec-up` command, or set `VERSION` for a particular git tag, eg for [v**0.77.0**](https://github.com/AztecProtocol/aztec-packages/tree/v0.77.0) + +```shell +aztec-up 0.77.0 +# or +VERSION=0.77.0 aztec-up +``` + +2. Update Aztec.nr and individual @aztec dependencies: + +Inside your project run: + +```shell +cd your/aztec/project +aztec update . --contract src/contract1 --contract src/contract2 +``` + +The sandbox must be running for the update command to work. Make sure it is [installed and running](../../reference/environment_reference/sandbox-reference.md). + +Follow [updating Aztec.nr packages](#updating-aztecnr-packages) and [updating JavaScript packages](#updating-aztecjs-packages) guides. + +3. Refer to [Migration Notes](../../../migration_notes.md) on any breaking changes that might affect your dapp + +--- + +There are four components whose versions need to be kept compatible: + +1. Aztec Sandbox +2. aztec-nargo +3. `Aztec.nr`, the Noir framework for writing Aztec contracts + +First three are packaged together in docker and are kept compatible by running `aztec-up`. +But you need to update your Aztec.nr version manually or using `aztec update`. + +## Updating Aztec.nr packages + +### Automatic update + +You can update your Aztec.nr packages to the appropriate version with the `aztec update` command. Run this command from the root of your project and pass the paths to the folders containing the Nargo.toml files for your projects like so: + +```shell +aztec update . --contract src/contract1 --contract src/contract2 +``` + +### Manual update + +To update the aztec.nr packages manually, update the tags of the `aztec.nr` dependencies in the `Nargo.toml` file. + +```diff +[dependencies] +-aztec = { git="https://github.com/AztecProtocol/aztec-packages", tag="v0.7.5", directory="noir-projects/aztec-nr/aztec" } ++aztec = { git="https://github.com/AztecProtocol/aztec-packages", tag="v0.88.0", directory="noir-projects/aztec-nr/aztec" } +-value_note = { git="https://github.com/AztecProtocol/aztec-packages", tag="v0.7.5", directory="noir-projects/aztec-nr/value-note" } ++value_note = { git="https://github.com/AztecProtocol/aztec-packages", tag="v0.88.0", directory="noir-projects/aztec-nr/value-note" } +``` + +Go to the contract directory and try compiling it with `aztec-nargo compile` to verify that the update was successful: + +```shell +cd /your/contract/directory +aztec-nargo compile +``` + +If the dependencies fail to resolve ensure that the tag matches a tag in the [aztec-packages repository (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tags). + +## Updating Aztec.js packages + +To update Aztec.js packages, go to your `package.json` and replace the versions in the dependencies. + +```diff +[dependencies] +-"@aztec/accounts": "0.7.5", ++"@aztec/accounts": "v0.88.0", +-"@aztec/noir-contracts.js": "0.35.1", ++"@aztec/accounts": "v0.88.0", +``` diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/_category_.json new file mode 100644 index 000000000000..d0bf29833011 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 4, + "collapsible": true, + "collapsed": true, + "label": "Aztec.nr" +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/how_to_compile_contract.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/how_to_compile_contract.md new file mode 100644 index 000000000000..8a4a10599537 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/how_to_compile_contract.md @@ -0,0 +1,92 @@ +--- +title: How to Compile a Contract +sidebar_position: 3 +tags: [contracts] +--- + +Once you have written a contract in Aztec.nr, you will need to compile it into an artifact in order to use it. + +In this guide we will cover how to do so, both using the `aztec-nargo` command and programmatically. + +We'll also cover how to generate a helper [TypeScript interface](#typescript-interfaces) and an [Aztec.nr interface](#aztecnr-interfaces) for easily interacting with your contract from your typescript app and from other Aztec.nr contracts, respectively. + +## Compile using aztec-nargo + +To compile a contract using the Aztec's build of nargo. + +Run the `aztec-nargo compile` command within your contract project folder (the one that contains the `Nargo.toml` file): + +```bash +aztec-nargo compile +``` + +This will output a JSON artifact for each contract in the project to a `target` folder containing the Noir ABI artifacts. + +:::note +This command looks for `Nargo.toml` files by ascending up the parent directories, and will compile the top-most Nargo.toml file it finds. +Eg: if you are in `/hobbies/cool-game/contracts/easter-egg/`, and both `cool-game` and `easter-egg` contain a Nargo.toml file, then `aztec-nargo compile` will be performed on `cool-game/Nargo.toml` and compile the project(s) specified within it. Eg + +``` +[workspace] +members = [ + "contracts/easter-egg", +] +``` + +::: + +### Typescript Interfaces + +You can use the code generator to autogenerate type-safe typescript classes for each of your contracts. These classes define type-safe methods for deploying and interacting with your contract based on their artifact. + +```bash +aztec codegen ./aztec-nargo/output/target/path -o src/artifacts +``` + +Read more about interacting with contracts using `aztec.js` [by following this tutorial](../../tutorials/codealong/js_tutorials/aztecjs-getting-started.md). + +### Aztec.nr interfaces + +An Aztec.nr contract can [call a function](./writing_contracts/call_contracts.md) in another contract via `context.call_private_function` or `context.call_public_function`. However, this requires manually assembling the function selector and manually serializing the arguments, which is not type-safe. + +To make this easier, the compiler automatically generates interface structs that expose a convenience method for each function listed in a given contract artifact. These structs are intended to be used from another contract project that calls into the current one. + +Below is an example of interface usage generated from the [Token (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr) contract, used from the [FPC (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr): + +```rust +contract FPC { + + ... + + use dep::token::Token; + + ... + + + #[private] + fn fee_entrypoint_private(amount: Field, asset: AztecAddress, secret_hash: Field, nonce: Field) { + assert(asset == storage.other_asset.read()); + Token::at(asset).transfer_to_public(context.msg_sender(), context.this_address(), amount, nonce).call(&mut context); + FPC::at(context.this_address()).pay_fee_with_shielded_rebate(amount, asset, secret_hash).enqueue(&mut context); + } + + #[private] + fn fee_entrypoint_public(amount: Field, asset: AztecAddress, nonce: Field) { + FPC::at(context.this_address()).prepare_fee(context.msg_sender(), amount, asset, nonce).enqueue(&mut context); + FPC::at(context.this_address()).pay_fee(context.msg_sender(), amount, asset).enqueue(&mut context); + } + + ... + +} +``` + +Read more about how to use the Aztec.nr interfaces [here](../../../aztec/smart_contracts/functions/index.md). + +:::info +At the moment, the compiler generates these interfaces from already compiled ABIs, and not from source code. This means that you should not import a generated interface from within the same project as its source contract, or you risk circular references. +::: + +## Next steps + +Once you have compiled your contracts, you can use the generated artifacts via the `Contract` class in the `aztec.js` package to deploy and interact with them, or rely on the type-safe typescript classes directly. diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/index.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/index.md new file mode 100644 index 000000000000..6b2653857b34 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/index.md @@ -0,0 +1,39 @@ +--- +title: Aztec.nr +tags: [aztec.nr] +--- + +import DocCardList from "@theme/DocCardList"; + +Aztec.nr is the smart contract development framework for Aztec. It is a set of utilities that +help you write Noir programs to deploy on the Aztec network. + +## Contract Development + +### Prerequisites + +- Install [Aztec Sandbox and tooling](../../getting_started.md) +- Install the [Noir LSP](../local_env/installing_noir_lsp.md) for your editor. + +### Flow + +1. Write your contract and specify your contract dependencies. Every contract written for Aztec will have + aztec-nr as a dependency. Add it to your `Nargo.toml` with + +```toml +# Nargo.toml +[dependencies] +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/aztec" } +``` + +2. [Write your contracts](./writing_contracts/index.mdx). +3. [Profile](./profiling_transactions.md) the private functions in your contract to get + a sense of how long generating client side proofs will take +4. Write unit tests [using the TXE](testing.md) and end-to-end + tests [with typescript](../js_apps/test.md) +5. [Compile](how_to_compile_contract.md) your contract +6. [Deploy](../js_apps/deploy_contract.md) your contract with Aztec.js + +## Section Contents + + diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/profiling_transactions.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/profiling_transactions.md new file mode 100644 index 000000000000..81dc54e16ff7 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/profiling_transactions.md @@ -0,0 +1,121 @@ +--- +title: Profiling Transactions +sidebar_position: 1 +tags: [contracts, profiling] +--- + +An Aztec transaction typically consists of a private and a public part. The private part is where the user executes contract logic within the PXE and generates a proof of execution, which is then sent to the sequencer. + +Since proof generation is an expensive operation that needs to be done on the client side, it is important to optimize the private contract logic. It is desirable to keep the gate count of circuits representing the private contract logic as low as possible. + +A private transaction can involve multiple function calls. It starts with an account `entrypoint()` which may call several private functions to execute the application logic, which in turn might call other functions. Moreover, every private function call has to go through a round of kernel circuits. Read more about the transaction lifecycle [here](../../../aztec/concepts/transactions.md). + +In this guide, we will look at how to profile the private execution of a transaction, allowing you to get the gate count of each private function within the transaction, including the kernel circuits. + +## Prerequisites + +- `aztec-nargo` installed (go to [Sandbox section](../../reference/environment_reference/sandbox-reference.md) for installation instructions) +- `aztec-wallet` installed (installed as part of the Sandbox) + +## Profiling using aztec-wallet + +The profiling tool is integrated into the `aztec-wallet`. + +In this example, we will profile a simple "private token transfer" transaction which uses the [transfer](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L263) method in the token contract. +Let us start by deploying the token contarct (included in the Sandbox) and minting some tokens to the test account. + +```bash +# Import some test accounts included in cli-wallet +aztec-wallet import-test-accounts + +# Deploy a token contract. +aztec-wallet deploy TokenContractArtifact --from accounts:test0 --args accounts:test0 TestToken TST 18 -a token + +# Mint some tokens to the test0 account +aztec-wallet send mint_to_private -ca token --args accounts:test0 accounts:test0 100 -f test0 +``` + +Now, the `test0` account can transfer tokens by running: + +```bash +# Send 40 tokens from test0 to test1 +aztec-wallet send transfer -ca token --args accounts:test1 40 -f accounts:test0 +``` + +Instead of sending the above transaction, you can profile it by running the `profile` command with the same parameters. + + +```bash +aztec-wallet profile transfer -ca token --args accounts:test1 40 -f accounts:test0 +``` + +This will print the following results after some time: + +```bash +Gate count per circuit: + SchnorrAccount:entrypoint Gates: 21,724 Acc: 21,724 + private_kernel_init Gates: 45,351 Acc: 67,075 + Token:transfer Gates: 31,559 Acc: 98,634 + private_kernel_inner Gates: 78,452 Acc: 177,086 + private_kernel_reset Gates: 91,444 Acc: 268,530 + private_kernel_tail Gates: 31,201 Acc: 299,731 + +Total gates: 299,731 +``` + +Here you can see the gate count of each private function call in the transaction along with the kernel circuits needed in between, and the total gate count. + +This will help you understand which parts of your transaction are bottlenecks and optimize the contract logic accordingly. + +## Profiling in aztec.js + +Call the `.profile` method on a contract interaction or deployment, specifying the `ProfileMethodOptions`: + +```javascript title="profile-method-options" showLineNumbers +export type ProfileMethodOptions = SimulateMethodOptions & { + /** Whether to return gates information or the bytecode/witnesses. */ + profileMode: 'gates' | 'execution-steps' | 'full'; + /** Whether to generate a ClientIVC proof or not */ + skipProofGeneration?: boolean; +}; +``` +> Source code: yarn-project/aztec.js/src/contract/interaction_options.ts#L60-L67 + + +It will return a `TxProfileResult`: + +```## title="tx-profile-result" showLineNumbers +export class TxProfileResult { + constructor( + public executionSteps: PrivateExecutionStep[], + public stats: ProvingStats, + ) {} +``` +> Source code: yarn-project/stdlib/src/tx/profiling.ts#L81-L87 + + +## Flamegraph + +While the `aztec-wallet` provides a way to profile the gate count of each private function in a transaction, flamegraph tool lets you visualize the gate count of each operation within a private function. + +You can run the flamegraph tool by running the following command: + +```bash +aztec flamegraph +``` + +For example, if you want to flamegraph the `cast_vote` function [aztec-starter](https://github.com/AztecProtocol/aztec-starter/blob/main/src/main.nr), you can do + +```bash +aztec-nargo compile +aztec flamegraph target/easy_private_voting_contract-EasyPrivateVoting.json cast_vote +``` + +This will generate a flamegraph of the `cast_vote` function and save the output svg to the `target` directory. You can open the svg file in your browser to visualize the flamegraph. + +You can also run the same command with `SERVE=1` to serve the flamegraph on a local server. + +```bash +SERVE=1 aztec flamegraph target/easy_private_voting_contract-EasyPrivateVoting.json cast_vote +``` +This will serve the flamegraph on `http://localhost:8000`. diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/testing.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/testing.md new file mode 100644 index 000000000000..8a22dde5eb77 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/testing.md @@ -0,0 +1,402 @@ +--- +title: Testing Contracts +tags: [contracts, tests, testing, txe] +keywords: [tests, testing, txe] +sidebar_position: 2 +importance: 1 +--- + +Aztec contracts can be tested in a variety of ways depending on the needs of a particular application and the complexity of the interactions they must support. + +To test individual contract functions, you can use the Testing eXecution Environment (TXE) described below. For more complex interactions that require checking that the protocol rules are enforced, you should [write end-to-end tests using TypeScript](../js_apps/test.md). + +## Pure Noir tests + +Noir supports the `#[test]` annotation which can be used to write simple logic tests on isolated utility functions. These tests only make assertions on algorithms and cannot interact with protocol-specific constructs such as `storage` or `context`, but are extremely fast and can be useful in certain scenarios. + +```rust title="pure_noir_testing" showLineNumbers +#[test] +fn test_to_from_field() { + let field = 1234567890; + let card = Card::from_field(field); + assert(card.to_field() == field); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/card_game_contract/src/cards.nr#L40-L47 + + +To learn more about Noir testing, please refer to [the Noir docs](https://Noir-lang.org/docs/tooling/testing/). + +## TXE (pronounced "trixie") + +In order to interact with the protocol, Aztec contracts leverage the power of oracles: functions that reach out to the outside world and are able to query and manipulate data outside of itself. The values returned by oracles are then constrained inside Noir and modifications to the blockchain state are later verified to adhere to the protocol rules by our kernel circuits. + +However, all of this is often not necessary to ensure the contract logic itself is sound. All that we need is an entity to provide values consistent with real execution. This is where our TXE (Testing eXecution Environment, pronounced "trixie") comes in! + +TXE is a JSON RPC server much like PXE, but provides an extra set of oracle functions called `cheatcodes` that allow developers to manipulate the state of the chain and simulate contract execution. Since TXE skips most of the checks, block building and other intricacies of the Aztec protocol, it is much faster to run than simulating everything in the sandbox. + +## TXE vs End-to-end tests + +End-to-end tests are written in typescripts and use compiled Aztec contracts and generated Typescript interfaces, a private execution environment (PXE) and a simulated execution environment to process transactions, create blocks and apply state updates. This allows for advanced checks on state updates like generation the of logs, cross-chain messages and checking transaction status and also enforce the rules of the protocol (e.g. checks in our rollup circuits). If you need the rules of the protocol to be enforced or require complex interactions (such as with L1 contracts), please refer to [Testing Aztec.nr contracts with Typescript](../js_apps/test.md). + +The TXE is a super fast framework in Noir to quickly test your smart contract code. + +So to summarize: + +- End-to-end tests are written in Typescript. TXE in Noir. +- End-to-end tests are most similar to using mocha + ethers.js to test Solidity Contracts. TXE is like foundry (fast tests in solidity) + +### Running TXE + +If you have [the sandbox](../../getting_started.md) installed, you can run TXE tests using: + +`aztec test` + +The complete process for running tests: + +1. Compile contracts +2. Start the sandbox +3. Run `aztec test` + +In order to use the TXE, it must be running on a known address. + +:::warning +Since TXE tests are written in Noir and executed with `aztec-nargo`, they all run in parallel. This also means every test creates their own isolated environment, so state modifications are local to each one of them. +::: + +### Writing TXE tests + +`aztec-nr` provides an utility class called `TestEnvironment`, that should take care of the most common operations needed to setup contract testing. Setting up a new test environment with `TestEnvironment::new()` **will reset the current test's TXE state**. + +:::tip +You can find all of the methods available in the `TestEnvironment` [here (Github link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr). +::: + +```rust title="txe_test_increment" showLineNumbers +#[test] +unconstrained fn test_increment() { + // Setup env, generate keys + let mut env = TestEnvironment::new(); + let owner = env.create_account(1); + let sender = env.create_account(2); + let initial_value: Field = 5; + + // Deploy contract and initialize + let initializer = Counter::interface().initialize(initial_value as u64, owner); + let contract_address = + env.deploy_self("Counter").with_private_initializer(owner, initializer).to_address(); + + // Read the stored value in the note + let initial_counter = + env.simulate_utility(Counter::at(contract_address)._experimental_get_counter(owner)); + assert( + initial_counter == initial_value, + f"Expected {initial_value} but got {initial_counter}", + ); + + // Increment the counter + let _ = + env.call_private_void(owner, Counter::at(contract_address).increment(owner, sender)); + + let incremented_counter = + env.simulate_utility(Counter::at(contract_address)._experimental_get_counter(owner)); + let expected_current_value = initial_value + 1; + assert( + expected_current_value == incremented_counter, + f"Expected {expected_current_value} but got {incremented_counter}", + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr#L132-L168 + + +:::warning +Tests run significantly faster as `unconstrained` functions. This means we generate bytecode (Brillig) and not circuits (ACIR), which _should_ yield exactly the same results. Any other behavior is considered a bug. +::: + +### Imports + +Writing tests in contracts requires importing additional modules from Aztec.nr. Here are the modules that are needed for testing the increment function in the counter contract. + +```rust title="test_imports" showLineNumbers +use crate::test; +use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; +use dep::aztec::note::note_viewer_options::NoteViewerOptions; +use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; +use dep::aztec::test::helpers::test_environment::TestEnvironment; +``` +> Source code: noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr#L124-L131 + + +### Deploying contracts + +```rust + +// Deploy the contract we're currently on + +let deployer = env.deploy_self("ContractName"); + +// Deploy a standalone contract in a path relative to the current one (always from the location of Nargo.toml) + +let deployer = env.deploy("path_to_contract_root_folder_where_nargo_toml_is", "ContractName"); + +// Deploy a contract in a workspace + +let deployer = env.deploy("path_to_workspace_root_folder_where_main_nargo_toml_is@package_name", "ContractName"); + +// Now one of these can be called, depending on the contract and their possible initialization options. +// Remember a contract can only be initialized once. + +let my_private_initializer_call_interface = MyContract::interface().private_constructor(...); +let my_contract_instance = deployer.with_private_initializer(my_private_initializer_call_interface); + +// or + +let my_public_initializer_call_interface = MyContract::interface().public_constructor(...); +let my_contract_instance = deployer.with_public_initializer(my_public_initializer_call_interface); + +// or + +let my_contract_instance = deployer.without_initializer(); +``` + +:::warning +It is always necessary to deploy a contract in order to test it. **It is important to keep them up to date**, as TXE cannot recompile them on changes. Think of it as regenerating the bytecode and ABI so it becomes accessible externally. +::: + +### Calling functions + +Our test environment is capable of utilizing the autogenerated contract interfaces to abstract calls, but without going through the usual external call flow (meaning much faster execution). + +#### Private + +For example, to call the private `transfer` function on the token contract: + +```rust title="txe_test_transfer_private" showLineNumbers +// Transfer tokens +let transfer_amount = 1000 as u128; +Token::at(token_contract_address).transfer(recipient, transfer_amount).call(&mut env.private()); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/test/transfer.nr#L11-L15 + + +#### Public + +To call the public `transfer_in_public` function: + +```rust title="call_public" showLineNumbers +Token::at(token_contract_address).transfer_in_public(owner, owner, transfer_amount, 0).call( + &mut env.public(), +); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/test/transfer_in_public.nr#L29-L33 + + +#### Utility + +Utility functions can be directly called from the contract interface. Notice that we need to set the contract address to the specific token contract that we are calling before making the call. This is to ensure that `view_notes` works properly. + +```rust title="txe_test_call_utility" showLineNumbers +pub unconstrained fn check_private_balance( + token_contract_address: AztecAddress, + address: AztecAddress, + address_amount: u128, +) { + let current_contract_address = get_contract_address(); + cheatcodes::set_contract_address(token_contract_address); + // Direct call to a utility function + let balance_of_private = Token::balance_of_private(address); + assert(balance_of_private == address_amount, "Private balance is not correct"); + cheatcodes::set_contract_address(current_contract_address); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/test/utils.nr#L135-L148 + + +### Creating accounts + +The test environment provides two different ways of creating accounts, depending on the testing needs. For most cases, it is only necessary to obtain a valid `AztecAddress` that represents the user's account contract. For this, is is enough to do: + +```rust +let mocked_account_address = env.create_account(secret); +``` + +These accounts also create the necessary keys to ensure notes can be created/nullified, etc. + +For more advanced flows, such as authwits, it is necessary to create a real `AccountContract`, with valid signing keys that gets actually deployed to TXE. For that you can use: + +```rust +let real_account_address = env.create_account_contract(secret); +``` + +Besides deploying a complete `SchnorrAccountContract`, key derivation is performed so that authwits can be signed. It is slightly slower than the mocked version. + +Once accounts have been created, you can impersonate them in your test by calling: + +```rust +env.impersonate(account_address); +// or (these are equivalent) +cheatcodes::set_contract_address(contract_address); +``` + +### Checking state + +It is possible to use the regular oracles in tests in order to retrieve public and private state and make assertions about them. + +:::warning +Remember to switch to the current contract's address in order to be able to read it's siloed state! +::: + +Reading public state: +```rust title="txe_test_read_public" showLineNumbers +pub unconstrained fn check_public_balance( + token_contract_address: AztecAddress, + address: AztecAddress, + address_amount: u128, +) { + let current_contract_address = get_contract_address(); + cheatcodes::set_contract_address(token_contract_address); + let block_number = get_block_number(); + + let balances_slot = Token::storage_layout().public_balances.slot; + let address_slot = derive_storage_slot_in_map(balances_slot, address); + let amount: u128 = storage_read(token_contract_address, address_slot, block_number); + assert(amount == address_amount, "Public balance is not correct"); + cheatcodes::set_contract_address(current_contract_address); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/test/utils.nr#L88-L104 + + +Reading notes: +```rust title="txe_test_read_notes" showLineNumbers +// Read the stored value in the note +let initial_counter = + env.simulate_utility(Counter::at(contract_address)._experimental_get_counter(owner)); +assert( + initial_counter == initial_value, + f"Expected {initial_value} but got {initial_counter}", +); +``` +> Source code: noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr#L146-L154 + + +### Authwits + +#### Private + +You can add [authwits](writing_contracts/authwit.md) to the TXE. Here is an example of testing a private token transfer using authwits: + +```rust title="private_authwit" showLineNumbers +let transfer_amount = 1000 as u128; +let transfer_private_from_call_interface = + Token::at(token_contract_address).transfer_in_private(owner, recipient, transfer_amount, 1); +authwit_cheatcodes::add_private_authwit_from_call_interface( + owner, + recipient, + transfer_private_from_call_interface, +); +// Impersonate recipient to perform the call +env.impersonate(recipient); +// Transfer tokens +transfer_private_from_call_interface.call(&mut env.private()); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/test/transfer_in_private.nr#L11-L24 + + +#### Public + +```rust title="public_authwit" showLineNumbers +let public_transfer_in_private_call_interface = + Token::at(token_contract_address).transfer_in_public(owner, recipient, transfer_amount, 1); +authwit_cheatcodes::add_public_authwit_from_call_interface( + owner, + recipient, + public_transfer_in_private_call_interface, +); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/test/transfer_in_public.nr#L115-L123 + + +### Storing notes in cache + +Sometimes we have to tell TXE about notes that are not generated by ourselves, but someone else. This allows us to check if we are able to decrypt them: + +```rust title="txe_test_add_note" showLineNumbers +let balances_owner_slot = + derive_storage_slot_in_map(Token::storage_layout().balances.slot, owner); + +env.add_note( + UintNote { value: amount, owner: owner, randomness: note_randomness }, + balances_owner_slot, + token_contract_address, +); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/test/utils.nr#L169-L178 + + +### Time traveling + +TXE can force the generation of "new blocks" very quickly using: + +```rust +env.advance_block_by(n_blocks); +``` + +This will effectively consolidate state transitions into TXE's internal trees, allowing things such as reading "historical state" from private, generating inclusion proofs, etc. + +### Failing cases + +You can test functions that you expect to fail generically, with the `#[test(should_fail)]` annotation, or that it should fail with a specific message with `#[test(should_fail_with = "Failure message")]`. + +For example: + +```rust title="fail_with_message" showLineNumbers +#[test(should_fail_with = "invalid authwit nonce")] +unconstrained fn transfer_private_failure_on_behalf_of_self_non_zero_nonce() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient, _) = + utils::setup_and_mint_to_private(/* with_account_contracts */ false); + // Add authwit + let transfer_amount = 1000 as u128; + let transfer_private_from_call_interface = + Token::at(token_contract_address).transfer_in_private(owner, recipient, transfer_amount, 1); + authwit_cheatcodes::add_private_authwit_from_call_interface( + owner, + recipient, + transfer_private_from_call_interface, + ); + // Transfer tokens + transfer_private_from_call_interface.call(&mut env.private()); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/test/transfer_in_private.nr#L30-L48 + + +You can also use the `assert_public_call_fails` or `assert_private_call_fails` methods on the `TestEnvironment` to check that a call fails. + +```rust title="assert_public_fail" showLineNumbers +// Try to set ourselves as admin, fail miserably +let set_admin_call_interface = Token::at(token_contract_address).set_admin(recipient); +env.assert_public_call_fails(set_admin_call_interface); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/test/access_control.nr#L34-L38 + + +### Logging + +You can use `aztec.nr`'s oracles as usual for debug logging, as explained [here](../../reference/debugging/index.md) + + +:::warning +Remember to set the following environment variables to activate debug logging: + +```bash +export LOG_LEVEL="debug" +``` + +::: + +### All Cheatcodes + +You can find the full list of cheatcodes available in the TXE [here](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr) diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/_category_.json new file mode 100644 index 000000000000..7e21bf8e7ac4 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 0, + "collapsible": true, + "collapsed": true, + "label": "Writing Contracts" +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/authwit.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/authwit.md new file mode 100644 index 000000000000..d38cdb520215 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/authwit.md @@ -0,0 +1,354 @@ +--- +title: Authentication Witness +description: Developer Documentation to use Authentication Witness for authentication actions on Aztec. +tags: [accounts, authwit] +--- + +This page introduces the authwit library and how you can use it in your Aztec.nr smart contracts. [Skip to the usage](#usage). + +For a guide on using authwit in Aztec.js, [read this](../../js_apps/authwit.md). + +## Prerequisite reading + +- [Authwit](../../../../aztec/concepts/advanced/authwit.md) + +## Introduction + +Authentication Witness (authwit) is a scheme for authentication actions on Aztec, so users can allow third-parties (eg other contracts) to execute an action on their behalf. Authwits can only authorize actions for contracts that your account is calling, they cannot be used to permit other users to take actions on your behalf. + +How it works logically is explained in the [concepts](../../../../aztec/concepts/advanced/authwit.md) but we will do a short recap here. + +An authentication witness is defined for a specific action, such as allowing a Defi protocol to transfer funds on behalf of the user. An action is here something that could be explained as `A is allowed to perform X operation on behalf of B` and we define it as a hash computed as such: + +```rust +authentication_witness_action = H( + caller: AztecAddress, + contract: AztecAddress, + selector: Field, + argsHash: Field +); + +// Example action that authenticates: +// defi contract to transfer 1000 tokens to itself on behalf of alice_account +authentication_witness_action = H( + defi, + token, + transfer_selector, + H(alice_account, defi, 1000) +); +``` + +Given the action, the developer can ask the `on_behalf_of` account contract if the action is authenticated or not. + +```mermaid +sequenceDiagram + actor Alice + participant AC as Alice Account + participant Token + Alice->>AC: Defi.deposit(Token, 1000); + activate AC + AC->>Defi: deposit(Token, 1000); + activate Defi + Defi->>Token: transfer(Alice, Defi, 1000); + activate Token + Token->>AC: Check if Defi may call transfer(Alice, Defi, 1000); + AC-->>Alice: Please give me AuthWit for DeFi
calling transfer(Alice, Defi, 1000); + activate Alice + Alice-->>Alice: Produces Authentication witness + Alice-->>AC: AuthWit for transfer(Alice, Defi, 1000); + AC->>Token: AuthWit validity + deactivate Alice + Token->>Token: throw if invalid AuthWit + Token->>Token: transfer(Alice, Defi, 1000); + Token->>Defi: success + deactivate Token + Defi->>Defi: deposit(Token, 1000); + deactivate Defi + deactivate AC +``` + +:::info +Note in particular that the request for a witness is done by the token contract, and the user will have to provide it to the contract before it can continue execution. Since the request is made all the way into the contract where it is to be used, we don't need to pass it along as an extra input to the functions before it which gives us a cleaner interface. +::: + +As part of `AuthWit` we are assuming that the `on_behalf_of` implements the private function: + +```rust +#[private] +fn verify_private_authwit(inner_hash: Field) -> Field; +``` + +For public authwit, we have a shared registry that is used, there we are using a `consume` function. + +Both return the value `0xabf64ad4` (`IS_VALID` selector) for a successful authentication, and `0x00000000` for a failed authentication. You might be wondering why we are expecting the return value to be a selector instead of a boolean. This is mainly to account for a case of selector collisions where the same selector is used for different functions, and we don't want an account to mistakenly allow a different function to be called on its behalf - it is hard to return the selector by mistake, but you might have other functions returning a bool. + +## The `AuthWit` library. + +As part of Aztec.nr, we are providing a library that can be used to implement authentication witness for your contracts. + +This library also provides a basis for account implementations such that these can more easily implement authentication witness. + +For our purposes here (not building a wallet), the most important part of the library is the `auth` utility which exposes a couple of helper methods for computing the action hash, retrieving witnesses, validating them and emitting the nullifier. + +### General utilities + +The primary general utility is the `compute_authwit_message_hash_from_call` function which computes the action hash from its components. This is useful for when you need to generate a hash that is not for the current call, such as when you want to update a public approval state value that is later used for [authentication in public](#updating-approval-state-in-noir). You can view the implementation of this function [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/aztec-nr/authwit/src/auth.nr). + +#### TypeScript utilities + +To make it convenient to compute the message hashes in TypeScript, the `aztec.js` package includes a `computeAuthWitMessageHash` function that you can use. Implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec.js/src/utils/authwit.ts). + +### Utilities for private calls + +For private calls where we allow execution on behalf of others, we generally want to check if the current call is authenticated by `on_behalf_of`. To easily do so, we can use the `assert_current_call_valid_authwit` which fetches information from the current context without us needing to provide much beyond the `on_behalf_of`. + +This function will then make a call to `on_behalf_of` to execute the `verify_private_authwit` function which validates that the call is authenticated. +The `on_behalf_of` should assert that we are indeed authenticated and then return the `IS_VALID` selector. If the return value is not as expected, we throw an error. This is to cover the case where the `on_behalf_of` might implemented some function with the same selector as the `verify_private_authwit` that could be used to authenticate unintentionally. + +#### Example + +```rust title="assert_current_call_valid_authwit" showLineNumbers +if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); +} else { + assert(authwit_nonce == 0, "invalid authwit nonce"); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L416-L422 + + +### Utilities for public calls + +Very similar to the above, we have variations that work in the public domain (`assert_current_call_valid_authwit_public`). These functions are wrapped to give a similar flow for both cases, but behind the scenes the logic is slightly different since the public goes to the auth registry, while the private flow calls the account contract. + +#### Example + +```rust title="assert_current_call_valid_authwit_public" showLineNumbers +if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit_public(&mut context, from); +} else { + assert(authwit_nonce == 0, "invalid authwit nonce"); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L226-L232 + + +## Usage + +Ok, enough talking, how do we use this? + +### Importing it + +To add it to your project, add the `authwit` library to your `Nargo.toml` file. + +```toml +[dependencies] +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/aztec" } +authwit = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/authwit"} +``` + +Then you will be able to import it into your contracts as follows. + +```rust title="import_authwit" showLineNumbers +use dep::authwit::auth::{ + assert_current_call_valid_authwit, assert_current_call_valid_authwit_public, + compute_authwit_nullifier, +}; +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L36-L41 + + +### Private Functions + +#### Checking if the current call is authenticated + +Based on the diagram earlier on this page let's take a look at how we can implement the `transfer` function such that it checks if the tokens are to be transferred `from` the caller or needs to be authenticated with an authentication witness. + +```rust title="transfer_in_private" showLineNumbers +#[private] +fn transfer_in_private( + from: AztecAddress, + to: AztecAddress, + amount: u128, + authwit_nonce: Field, +) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + + storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note( + &mut context, + from, + from, + )); + storage.balances.at(to).add(to, amount).emit(encode_and_encrypt_note(&mut context, to, from)); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L408-L433 + + +The first thing we see in the snippet above, is that if `from` is not the call we are calling the `assert_current_call_valid_authwit` function from [earlier](#private-functions). If the call is not throwing, we are all good and can continue with the transfer. + +In the snippet we are constraining the `else` case such that only `nonce = 0` is supported. This is not strictly necessary, but because I can't stand dangling useless values. By making it constrained, we can limit what people guess it does, I hope. + +#### Authenticating an action in TypeScript + +Cool, so we have a function that checks if the current call is authenticated, but how do we actually authenticate it? Well, assuming that we use a wallet that is following the spec, we import `computeAuthWitMessageHash` from `aztec.js` to help us compute the hash, and then we simply `addAuthWitness` to the wallet. Behind the scenes this will make the witness available to the oracle. + +```typescript title="authwit_transfer_example" showLineNumbers +const action = asset + .withWallet(wallets[1]) + .methods.transfer_in_private(accounts[0].address, accounts[1].address, amount, authwitNonce); + +const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); +expect( + await wallets[0].lookupValidity(wallets[0].getAddress(), { caller: accounts[1].address, action }, witness), +).toEqual({ + isValidInPrivate: true, + isValidInPublic: false, +}); +``` +> Source code: yarn-project/end-to-end/src/e2e_token_contract/transfer_in_private.test.ts#L32-L44 + + +Learn more about authwits in Aztec.js by [following this guide](../../js_apps/authwit.md). + +### Public Functions + +With private functions covered, how can we use this in a public function? Well, the answer is that we simply change one name of a function and then we are good to go :eyes: (almost). + +#### Checking if the current call is authenticated + +```rust title="transfer_in_public" showLineNumbers +#[public] +fn transfer_in_public( + from: AztecAddress, + to: AztecAddress, + amount: u128, + authwit_nonce: Field, +) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit_public(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + let from_balance = storage.public_balances.at(from).read().sub(amount); + storage.public_balances.at(from).write(from_balance); + let to_balance = storage.public_balances.at(to).read().add(amount); + storage.public_balances.at(to).write(to_balance); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L203-L221 + + +#### Authenticating an action in TypeScript + +Authenticating an action in the public domain is slightly different from the private domain, since we are executing a function on the auth registry contract to add the witness flag. As you might recall, this was to ensure that we don't need to call into the account contract from public, which is a potential DOS vector. + +In the snippet below, this is done as a separate contract call, but can also be done as part of a batch as mentioned in the [Accounts concepts](../../../../aztec/concepts/advanced/authwit.md#what-about-public). + +```typescript title="authwit_public_transfer_example" showLineNumbers +const action = asset + .withWallet(wallets[1]) + .methods.transfer_in_public(accounts[0].address, accounts[1].address, amount, authwitNonce); + +const validateActionInteraction = await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true); +await validateActionInteraction.send().wait(); +``` +> Source code: yarn-project/end-to-end/src/e2e_token_contract/transfer_in_public.test.ts#L69-L76 + + +#### Updating approval state in Noir + +We have cases where we need a non-wallet contract to approve an action to be executed by another contract. One of the cases could be when making more complex defi where funds are passed along. When doing so, we need the intermediate contracts to support approving of actions on their behalf. + +This is fairly straight forward to do using the `auth` library which includes logic for updating values in the public auth registry. Namely, you can prepare the `message_hash` using `compute_authwit_message_hash_from_call` and then simply feed it into the `set_authorized` function (both are in `auth` library) to update the value. + +When another contract later is consuming the authwit using `assert_current_call_valid_authwit_public` it will be calling the registry, and spend that authwit. + +An example of this would be our Uniswap example which performs a cross chain swap on L1. In here, we both do private and public auth witnesses, where the public is set by the uniswap L2 contract itself. In the below snippet, you can see that we compute the action hash and update the value in the registry. When we then call the `token_bridge` to execute afterwards, it reads this value, burns the tokens, and consumes the authentication. + +```rust title="authwit_uniswap_set" showLineNumbers +// This helper method approves the bridge to burn this contract's funds and exits the input asset to L1 +// Assumes contract already has funds. +// Assume `token` relates to `token_bridge` (ie token_bridge.token == token) +// Note that private can't read public return values so created an internal public that handles everything +// this method is used for both private and public swaps. +#[public] +#[internal] +fn _approve_bridge_and_exit_input_asset_to_L1( + token: AztecAddress, + token_bridge: AztecAddress, + amount: u128, +) { + // Since we will authorize and instantly spend the funds, all in public, we can use the same nonce + // every interaction. In practice, the authwit should be squashed, so this is also cheap! + let authwit_nonce = 0xdeadbeef; + + let selector = FunctionSelector::from_signature("burn_public((Field),u128,Field)"); + let message_hash = compute_authwit_message_hash_from_call( + token_bridge, + token, + context.chain_id(), + context.version(), + selector, + [context.this_address().to_field(), amount as Field, authwit_nonce], + ); + + // We need to make a call to update it. + set_authorized(&mut context, message_hash, true); + + let this_portal_address = storage.portal_address.read(); + // Exit to L1 Uniswap Portal ! + TokenBridge::at(token_bridge) + .exit_to_l1_public(this_portal_address, amount, this_portal_address, authwit_nonce) + .call(&mut context) +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/uniswap_contract/src/main.nr#L185-L221 + + +Outlining more of the `swap` flow: this simplified diagram shows how it will look for contracts that are not wallets but also need to support authentication witnesses. + +```mermaid +sequenceDiagram + actor A as Alice + participant AC as Alice Account + participant CC as Crosschain Swap + participant TB as Token Bridge + participant T as Token + + A->>AC: Swap 1000 token A to B on Uniswap L1 + activate AC; + AC->>CC: Swap 1000 token A to B + activate CC; + CC->>T: Transfer to public 1000 tokens from Alice Account to CCS + activate T; + T->>AC: Have you approved this?? + AC-->>A: Please give me an AuthWit + A-->>AC: Here is AuthWit + AC-->>AC: Validate AuthWit + AC->>T: Yes + deactivate T; + CC-->>CC: Setting flag to true + CC->>TB: Exit 1000 tokens to CCS + activate TB; + TB->>T: Burn 1000 tokens from CCS + activate T; + T->>CC: Have you approved this? + CC->>T: Yes + T-->>T: Burn + Token->>Defi: success + deactivate T; + TB-->>TB: Emit L2->L1 message + deactivate TB; + CC-->>CC: Emit L2->L1 message + deactivate CC; + deactivate AC; +``` + +:::info **TODO** +Add a link to the blog-posts. +::: diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/call_contracts.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/call_contracts.md new file mode 100644 index 000000000000..72b60a954208 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/call_contracts.md @@ -0,0 +1,76 @@ +--- +title: Calling Other Contracts +sidebar_position: 4 +tags: [functions, contracts] +--- + +A contract is a collection of persistent state variables and functions which may manipulate these variables. + +Functions and state variables within a contract's scope are said to belong to that contract. A contract can only access and modify its own state. + +If a contract wishes to access or modify another contract's state, it must make a call to an external function of the other contract. For anything to happen on the Aztec network, an external function of a contract needs to be called. + +### Add Contract as a Dependency + +Import the contract that you want to call into your `Nargo.toml` under `dependencies` like this: + +```toml +token = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/noir-contracts/contracts/app/token_contract" } +``` + +### Import into your contract + +At the top of your contract, import the contract you want to call like this: + +```rust +use token::Token; +``` + +### Call the function + +To call the function, you need to + +- Specify the address of the contract with `Contract::at(contract_address)` +- Call the function name with `.function_name()` +- Pass the parameters into the function call, like `.function_name(param1,param2)` +- Specify the type of call you want to make and pass a mut reference to the context, like `.call(&mut context)` + +#### Private calls + +To call a private function, you can just use `call()` like this: + +```rust title="call_function" showLineNumbers +Token::at(token).transfer(recipient, amount).call(&mut context); +``` +> Source code: noir-projects/noir-contracts/contracts/app/escrow_contract/src/main.nr#L45-L47 + + +#### Public -> Public calls + +To call a public function from a public function, it is the same as above. You can just use `call()` like this: + +```rust title="public_to_public_call" showLineNumbers +let _ = Token::at(collateral_asset) + .transfer_in_public(context.msg_sender(), context.this_address(), amount, authwit_nonce) + .call(&mut context); +``` +> Source code: noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L133-L137 + + +#### Private -> Public calls + +To call a public function from private, you will need to enqueue it like this: + +```rust title="enqueue_public" showLineNumbers +Lending::at(context.this_address()) + ._deposit(AztecAddress::from_field(on_behalf_of), amount, collateral_asset) + .enqueue(&mut context); +``` +> Source code: noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L119-L123 + + +Public functions are always executed after private execution. To learn why, read the [concepts overview](../../../../aztec/index.md). + +#### Other call types + +There are other call types, for example to ensure no state changes are made. You can learn more about them in the [call types glossary](../../../../aztec/concepts/call_types.md). diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/call_functions.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/call_functions.md new file mode 100644 index 000000000000..72b60a954208 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/call_functions.md @@ -0,0 +1,76 @@ +--- +title: Calling Other Contracts +sidebar_position: 4 +tags: [functions, contracts] +--- + +A contract is a collection of persistent state variables and functions which may manipulate these variables. + +Functions and state variables within a contract's scope are said to belong to that contract. A contract can only access and modify its own state. + +If a contract wishes to access or modify another contract's state, it must make a call to an external function of the other contract. For anything to happen on the Aztec network, an external function of a contract needs to be called. + +### Add Contract as a Dependency + +Import the contract that you want to call into your `Nargo.toml` under `dependencies` like this: + +```toml +token = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/noir-contracts/contracts/app/token_contract" } +``` + +### Import into your contract + +At the top of your contract, import the contract you want to call like this: + +```rust +use token::Token; +``` + +### Call the function + +To call the function, you need to + +- Specify the address of the contract with `Contract::at(contract_address)` +- Call the function name with `.function_name()` +- Pass the parameters into the function call, like `.function_name(param1,param2)` +- Specify the type of call you want to make and pass a mut reference to the context, like `.call(&mut context)` + +#### Private calls + +To call a private function, you can just use `call()` like this: + +```rust title="call_function" showLineNumbers +Token::at(token).transfer(recipient, amount).call(&mut context); +``` +> Source code: noir-projects/noir-contracts/contracts/app/escrow_contract/src/main.nr#L45-L47 + + +#### Public -> Public calls + +To call a public function from a public function, it is the same as above. You can just use `call()` like this: + +```rust title="public_to_public_call" showLineNumbers +let _ = Token::at(collateral_asset) + .transfer_in_public(context.msg_sender(), context.this_address(), amount, authwit_nonce) + .call(&mut context); +``` +> Source code: noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L133-L137 + + +#### Private -> Public calls + +To call a public function from private, you will need to enqueue it like this: + +```rust title="enqueue_public" showLineNumbers +Lending::at(context.this_address()) + ._deposit(AztecAddress::from_field(on_behalf_of), amount, collateral_asset) + .enqueue(&mut context); +``` +> Source code: noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr#L119-L123 + + +Public functions are always executed after private execution. To learn why, read the [concepts overview](../../../../aztec/index.md). + +#### Other call types + +There are other call types, for example to ensure no state changes are made. You can learn more about them in the [call types glossary](../../../../aztec/concepts/call_types.md). diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md new file mode 100644 index 000000000000..4423831416f9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md @@ -0,0 +1,260 @@ +--- +title: Common Patterns +sidebar_position: 7 +--- + +There are many common patterns have been devised by the Aztec core engineering team and the work of the external community as we build Aztec.nr contracts internally (see some of them [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-contracts)). + +This doc aims to summarize some of them! + +Similarly we have discovered some anti-patterns too (like privacy leakage) that we will point out here! + +## Common Patterns + +### Approving another user/contract to execute an action on your behalf + +We call this the "authentication witness" pattern or authwit for short. + +- Approve someone in private domain: +```typescript title="authwit_to_another_sc" showLineNumbers +// 4. Give approval to bridge to burn owner's funds: +const withdrawAmount = 9n; +const authwitNonce = Fr.random(); +const burnAuthwit = await user1Wallet.createAuthWit({ + caller: l2Bridge.address, + action: l2Token.methods.burn_private(ownerAddress, withdrawAmount, authwitNonce), +}); +``` +> Source code: yarn-project/end-to-end/src/e2e_cross_chain_messaging/token_bridge_private.test.ts#L71-L79 + + +Here you approve a contract to burn funds on your behalf. + +- Approve in public domain: +```typescript title="authwit_public_transfer_example" showLineNumbers +const action = asset + .withWallet(wallets[1]) + .methods.transfer_in_public(accounts[0].address, accounts[1].address, amount, authwitNonce); + +const validateActionInteraction = await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true); +await validateActionInteraction.send().wait(); +``` +> Source code: yarn-project/end-to-end/src/e2e_token_contract/transfer_in_public.test.ts#L69-L76 + + +Here you approve someone to transfer funds publicly on your behalf + +### Prevent the same user flow from happening twice using nullifiers + +E.g. you don't want a user to subscribe once they have subscribed already. Or you don't want them to vote twice once they have done that. How do you prevent this? + +Emit a nullifier in your function. By adding this nullifier into the tree, you prevent another nullifier from being added again. This is also why in authwit, we emit a nullifier, to prevent someone from reusing their approval. + +```rust title="verify_private_authwit" showLineNumbers +pub fn verify_private_authwit(self, inner_hash: Field) -> Field { + // The `inner_hash` is "siloed" with the `msg_sender` to ensure that only it can + // consume the message. + // This ensures that contracts cannot consume messages that are not intended for them. + let message_hash = compute_authwit_message_hash( + self.context.msg_sender(), + self.context.chain_id(), + self.context.version(), + inner_hash, + ); + let valid_fn = self.is_valid_impl; + assert(valid_fn(self.context, message_hash) == true, "Message not authorized by account"); + IS_VALID_SELECTOR +} +``` +> Source code: noir-projects/aztec-nr/authwit/src/account.nr#L73-L88 + + +Note be careful to ensure that the nullifier is not deterministic and that no one could do a preimage analysis attack. More in [the anti pattern section on deterministic nullifiers](#deterministic-nullifiers) + +Note - you could also create a note and send it to the user. The problem is there is nothing stopping the user from not presenting this note when they next interact with the function. + +### Reading public storage in private + +You can read public storage in private domain by leveraging the private getters of `PublicImmutable` (for values that never change) and `SharedMutable` (for values that change infrequently, see [shared state](../../../../reference/smart_contract_reference/storage/shared_state.md) for details) state variables. +Values that change frequently (`PublicMutable`) cannot be read in private as for those we need access to the tip of the chain and only a sequencer has access to that (and sequencer executes only public functions). + +E.g. when using `PublicImmutable` + +```rust +#[storage] +struct Storage { + config: PublicImmutable, +} + +contract Bridge { + + #[private] + fn burn_token_private( + token: AztecAddress, // pass token here since this is a private method but can't access public storage + amount: Field, + ) -> Field { + ... +let config = storage.config.read(); + +// Assert that user provided token address is same as seen in storage. +assert_eq(config.token, token, "Token address is not the same as seen in storage"); + } +} +``` + +:::danger +This leaks information about the private function being called and the data which has been read. +::: + +### Writing public storage from private + +When calling a private function, you can update public state by calling a public function. + +In this situation, try to mark the public function as `internal`. This ensures your flow works as intended and that no one can call the public function without going through the private function first! + +### Moving public data into the private domain + +See [partial notes](../../../../../aztec/concepts/advanced/storage/partial_notes.md). Partial notes are how public balances are transferred to private [in the NFT contract](../../../../tutorials/codealong/contract_tutorials/nft_contract.md). + +### Discovering my notes + +When you send someone a note, the note hash gets added to the note hash tree. To spend the note, the receiver needs to get the note itself (the note hash preimage). There are two ways you can get a hold of your notes: + +1. When sending someone a note, emit the note log to the recipient (the function encrypts the log in such a way that only a recipient can decrypt it). PXE then tries to decrypt all the encrypted logs, and stores the successfully decrypted one. [More info here](../how_to_emit_event.md) +2. Manually delivering it via a custom contract method, if you choose to not emit logs to save gas or when creating a note in the public domain and want to consume it in private domain (`encrypt_and_emit_note` shouldn't be called in the public domain because everything is public), like in the previous section where we created a note in public that doesn't have a designated owner. + +```typescript title="offchain_delivery" showLineNumbers +const txEffects = await pxe.getTxEffect(txHash); +await contract.methods + .deliver_transparent_note( + contract.address, + amount, + secretHash, + txHash.hash, + txEffects!.data.noteHashes, + txEffects!.data.nullifiers[0], + recipient, + ) + .simulate(); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_persistence.test.ts#L359-L372 + + +Note that this requires your contract to have a utility function that processes these notes and adds them to PXE. + +```rust title="deliver_note_contract_method" showLineNumbers +// We cannot replace this function with the standard `process_message` function because the transparent note +// originates in public and hence we cannot emit it as an offchain message. We could construct the offchain message +// "manually" and then pass it to the `process_message` function, but this doesn't seem to be worth the effort +// given that the TransparentNote flow is deprecated and kept around only for testing purposes. +#[utility] +unconstrained fn deliver_transparent_note( + contract_address: AztecAddress, + amount: u128, + secret_hash: Field, + tx_hash: Field, + unique_note_hashes_in_tx: BoundedVec, + first_nullifier_in_tx: Field, + recipient: AztecAddress, +) { +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/main.nr#L304-L319 + + +### Revealing encrypted logs conditionally + +An encrypted log can contain any information for a recipient, typically in the form of a note. One could think this log is emitted as part of the transaction execution, so it wouldn't be revealed if the transaction fails. + +This is not true for Aztec, as the encrypted log is part of the transaction object broadcasted to the network. So if a transaction with an encrypted log and a note commitment is broadcasted, there could be a situation where the transaction is not mined or reorg'd out, so the commitment is never added to the note hash tree, but the recipient could still have read the encrypted log from the transaction in the mempool. + +Example: + +> Alice and Bob agree to a trade, where Alice sends Bob a passcode to collect funds from a web2 app, in exchange of on-chain tokens. Alice should only send Bob the passcode if the trade is successful. But just sending the passcode as an encrypted log doesn't work, since Bob could see the encrypted log from the transaction as soon as Alice broadcasts it, decrypt it to get the passcode, and withdraw his tokens from the trade to make the transaction fail. + +### Randomness in notes + +Notes are hashed and stored in the merkle tree. While notes do have a header with a `nonce` field that ensure two exact notes still can be added to the note hash tree (since hashes would be different), preimage analysis can be done to reverse-engineer the contents of the note. + +Hence, it's necessary to add a "randomness" field to your note to prevent such attacks. + +```rust title="address_note_def" showLineNumbers +#[note] +#[derive(Eq)] +pub struct AddressNote { + address: AztecAddress, + owner: AztecAddress, + randomness: Field, +} + +impl AddressNote { + pub fn new(address: AztecAddress, owner: AztecAddress) -> Self { + // Safety: we use the randomness to preserve the privacy of the note recipient by preventing brute-forcing, so a + // malicious sender could use non-random values to make the note less private. But they already know the full + // note pre-image anyway, and so the recipient already trusts them to not disclose this information. We can + // therefore assume that the sender will cooperate in the random value generation. + let randomness = unsafe { random() }; + AddressNote { address, owner, randomness } + } +} +``` +> Source code: noir-projects/aztec-nr/address-note/src/address_note.nr#L5-L24 + + +### L1 -- L2 interactions + +Refer to [Token Portal codealong tutorial on bridging tokens between L1 and L2](../../../../tutorials/codealong/js_tutorials/token_bridge.md) and/or [Uniswap smart contract example that shows how to swap on L1 using funds on L2](../../../../tutorials/codealong/js_tutorials/uniswap/index.md). Both examples show how to: + +1. L1 -> L2 message flow +2. L2 -> L1 message flow +3. Cancelling messages from L1 -> L2. +4. For both L1->L2 and L2->L1, how to operate in the private and public domain + +### Sending notes to a contract/Escrowing notes between several parties in a contract + +To send a note to someone, they need to have a key which we can encrypt the note with. But often contracts may not have a key. And even if they do, how does it make use of it autonomously? + +There are several patterns here: + +1. Give the contract a key and share it amongst all participants. This leaks privacy, as anyone can see all the notes in the contract. +2. `transfer_to_public` funds into the contract - this is used in the [Uniswap smart contract example where a user sends private funds into a Uniswap Portal contract which eventually withdraws to L1 to swap on L1 Uniswap](../../../../tutorials/codealong/js_tutorials/uniswap/index.md). This works like Ethereum - to achieve contract composability, you move funds into the public domain. This way the contract doesn't even need keys. + +There are several other designs we are discussing through [in this discourse post](https://discourse.aztec.network/t/how-to-handle-private-escrows-between-two-parties/2440) but they need some changes in the protocol or in our demo contract. If you are interested in this discussion, please participate in the discourse post! + +### Share Private Notes + +If you have private state that needs to be handled by more than a single user (but no more than a handful), you can add the note commitment to the note hash tree, and then encrypt the note once for each of the users that need to see it. And if any of those users should be able to consume the note, you can generate a random nullifier on creation and store it in the encrypted note, instead of relying on the user secret. + +## Anti Patterns + +There are mistakes one can make to reduce their privacy set and therefore make it trivial to do analysis and link addresses. Some of them are: + +### Passing along your address when calling a public function from private + +If you have a private function which calls a public function, remember that sequencer can see any parameters passed to the public function. So try to not pass any parameter that might leak privacy (e.g. `from` address) + +PS: when calling from private to public, `msg_sender` is the contract address which is calling the public function. + +### Deterministic nullifiers + +In the [Prevent the same user flow from happening twice using nullifier](#prevent-the-same-user-flow-from-happening-twice-using-nullifiers), we recommended using nullifiers. But what you put in the nullifier is also as important. + +E.g. for a voting contract, if your nullifier simply emits just the `user_address`, then privacy can easily be leaked via a preimage attack as nullifiers are deterministic (have no randomness), especially if there are few users of the contract. So you need some kind of randomness. You can add the user's secret key into the nullifier to add randomness. We call this "nullifier secrets" as explained [here](../../../../../aztec/concepts/accounts/keys.md#nullifier-keys). + +Here is an example from the voting contract: + +```rust title="cast_vote" showLineNumbers +#[private] +// annotation to mark function as private and expose private context +fn cast_vote(candidate: Field) { + let msg_sender_npk_m_hash = get_public_keys(context.msg_sender()).npk_m.hash(); + + let secret = context.request_nsk_app(msg_sender_npk_m_hash); // get secret key of caller of function + let nullifier = std::hash::pedersen_hash([context.msg_sender().to_field(), secret]); // derive nullifier from sender and secret + context.push_nullifier(nullifier); + EasyPrivateVoting::at(context.this_address()).add_to_tally_public(candidate).enqueue( + &mut context, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L38-L51 + diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/how_to_emit_event.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/how_to_emit_event.md new file mode 100644 index 000000000000..9cab1ffc49e0 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/how_to_emit_event.md @@ -0,0 +1,88 @@ +--- +title: Emitting Events +sidebar_position: 4 +tags: [contracts] +--- + +Events in Aztec work similarly to Ethereum events in the sense that they are a way for contracts to communicate with the outside world. + +Events are structured pieces of data that can be emitted privately, from private functions, or publicly, from public functions. They include metadata about the event type, so people can query events to look for specific information. + +There are also public logs, which are similar to events, but are unstructured data. + +## Private Events + +To emit encrypted logs you can import the `encode_and_encrypt_event` or `encode_and_encrypt_event_unconstrained` functions and pass them into the `emit` function. An example can be seen in the reference token contract's transfer function: + +```rust title="encrypted_unconstrained" showLineNumbers +emit_event_in_private_log( + Transfer { from, to, amount }, + &mut context, + from, + to, + PrivateLogContent.NO_CONSTRAINTS, +); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L334-L342 + + +- `encode_and_encrypt_event` Sends an encrypted message to `recipient` with the content of the event, which they will discover when processing private logs. +- `encode_and_encrypt_event_unconstrained` is the same as `encode_and_encrypt_event`, except encryption is unconstrained. This means that the sender is free to make the log contents be whatever they wish, so the recipient is trusting the sender of the event. This could also potentially result in scenarios in which the recipient is unable to decrypt and process the payload, **leading to the event being lost**. Only use this function in scenarios where the recipient not receiving the event is an acceptable outcome. + +:::note +Developer can choose whether to emit encrypted events or not. Emitting the events means that they will be posted to Ethereum, in blobs, and will inherit the availability guarantees of Ethereum. Developers may choose not to emit events and to share information with recipients off-chain, or through alternative mechanisms that are to be developed (e.g. alternative, cheaper data availability solutions). +::: + +You can find the implementation of event logging [here](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/aztec/src/messages/logs/event.nr) + +### Processing encrypted events + +Contracts created using aztec-nr will try to discover newly created events by searching for logs emitted for any of the accounts registered inside PXE, decrypting their contents and notifying PXE of any events found. This process is automatic and occurs whenever a contract function is invoked. + +## Public Events + +You can emit public events by calling the `emit` function on the event type that you would like to emit. For example: + +```rust title="emit_public" showLineNumbers +emit_event_in_public_log( + ExampleEvent0 { value0: preimages[0], value1: preimages[1] }, + &mut context, +); +``` +> Source code: noir-projects/noir-contracts/contracts/test/test_log_contract/src/main.nr#L69-L74 + + +## Public Logs + +Public logs are unstructured data which can be read by anyone. They can be emitted **only** by public functions. + +### Call emit_public_log + +To emit public logs you don't need to import any library. You call the context method `emit_public_log`: + +```rust title="emit_public" showLineNumbers +context.emit_public_log(/*message=*/ value); +context.emit_public_log(/*message=*/ [10, 20, 30]); +context.emit_public_log(/*message=*/ "Hello, world!"); +``` +> Source code: noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr#L332-L336 + + +### Querying the unencrypted event + +Once emitted, unencrypted events are stored in AztecNode and can be queried by anyone: + +```typescript title="get_logs" showLineNumbers +const fromBlock = await pxe.getBlockNumber(); +const logFilter = { + fromBlock, + toBlock: fromBlock + 1, +}; +const publicLogs = (await pxe.getPublicLogs(logFilter)).logs; +``` +> Source code: yarn-project/end-to-end/src/e2e_ordering.test.ts#L23-L30 + + +## Costs + +Event data is pushed to Ethereum, in blobs, by the sequencer and for this reason the cost of emitting an event can be non-trivial. As mentioned above, emitting encrypted events is optional and there will likely be alternative options for developers to choose from in the future. diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/how_to_prove_history.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/how_to_prove_history.md new file mode 100644 index 000000000000..a0c271ae6837 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/how_to_prove_history.md @@ -0,0 +1,120 @@ +--- +title: Using the Archive Tree +sidebar_position: 4 +tags: [contracts] +--- + +The Aztec Protocol uses an append-only Merkle tree to store hashes of the headers of all previous blocks in the chain as its leaves. This is known as the Archive tree. + +This page is a quick introductory guide to creating historical proofs proofs from the archive tree. + +For a reference, go [here](../../../reference/smart_contract_reference/aztec-nr/aztec/history/contract_inclusion.md). + +## Inclusion and non-inclusion proofs + +Inclusion and non-inclusion proofs refer to proving the inclusion (or absence) of a specific piece of information within a specific Aztec block with a block header. You can prove any of the following at a given block height before the current height: + +- Note inclusion +- Nullifier inclusion +- Note validity +- Existence of public value +- Contract inclusion + +Using this library, you can check that specific notes or nullifiers were part of Aztec network state at specific blocks. This can be useful for things such as: + +- Verifying a minimum timestamp from a private context +- Checking eligibility based on historical events (e.g. for an airdrop by proving that you knew the nullifier key for a note) +- Verifying historic ownership / relinquishing of assets +- Proving existence of a value in public data tree at a given contract slot +- Proving that a contract was deployed in a given block with some parameters + +**In this guide you will learn how to** + +- Prove a note was included in a specified block +- Create a nullifier and prove it was not included in a specified block + +## Create a note to prove inclusion of + +In general you will likely have the note you want to prove inclusion of. But if you are just experimenting you can create a note with a function like below: + +```rust title="create_note" showLineNumbers +#[private] +fn call_create_note( + value: Field, + owner: AztecAddress, + sender: AztecAddress, + storage_slot: Field, +) { + let note = ValueNote::new(value, owner); + create_note(&mut context, storage_slot, note).emit(encode_and_encrypt_note( + &mut context, + owner, + sender, + )); +} +``` +> Source code: noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr#L128-L143 + + +## Get the note from the PXE + +Retrieve the note from the user's PXE. + +```rust title="get_note_from_pxe" showLineNumbers +let (retrieved_notes, _): (BoundedVec, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL>, BoundedVec) = + get_notes(&mut context, storage_slot, options); +``` +> Source code: noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr#L152-L155 + + +In this example, we fetch notes located in the storage slot that we pass in from the function parameters. The notes also must match the criteria specified by the getter options that we declare and are able to customize. + +## Prove that a note was included in a specified block + +To prove that a note existed in a specified block, call `prove_note_inclusion` on the `header` as shown in this example: + +```rust title="prove_note_inclusion" showLineNumbers +context.historical_header.prove_note_inclusion(retrieved_note, test::NOTE_STORAGE_SLOT); +``` +> Source code: noir-projects/aztec-nr/aztec/src/history/note_inclusion/test.nr#L10-L12 + + +This will only prove the note existed at the specific block number, not whether or not the note has been nullified. You can prove that a note existed and had not been nullified in a specified block by using `prove_note_validity` on the block header which takes the following arguments: + +```rust title="prove_note_validity" showLineNumbers +context.historical_header.prove_note_validity(retrieved_note, test::NOTE_STORAGE_SLOT, context); +``` +> Source code: noir-projects/aztec-nr/aztec/src/history/note_validity/test.nr#L25-L27 + + +## Create a nullifier to prove inclusion of + +You can easily nullify a note like so: + +```rust title="nullify_note" showLineNumbers +destroy_note_unsafe(&mut context, retrieved_note, note_hash); +``` +> Source code: noir-projects/noir-contracts/contracts/test/test_contract/src/main.nr#L209-L211 + + +This function gets a note from the PXE and nullifies it with `remove()`. + +You can then compute this nullifier with `note.compute_nullifier(&mut context)`. + +## Prove that a nullifier was included in a specified block + +Call `prove_nullifier_inclusion` on a block header like so: + +```rust title="prove_nullifier_inclusion" showLineNumbers +context.historical_header.prove_nullifier_inclusion(siloed_nullifier); +``` +> Source code: noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion/test.nr#L60-L62 + + +It takes the nullifier as an argument. + +You can also prove that a note was not nullified in a specified block by using `prove_note_not_nullified` which takes the note and a reference to the private context. + +## Prove contract inclusion, public value inclusion, and use current state in lookups + +To see what else you can do with historical proofs, check out the [reference](../../../reference/smart_contract_reference/aztec-nr/aztec/history/contract_inclusion.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/how_to_use_capsules.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/how_to_use_capsules.md new file mode 100644 index 000000000000..ba39ba87b49a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/how_to_use_capsules.md @@ -0,0 +1,68 @@ +--- +title: Using Capsules +sidebar_position: 5 +tags: [functions, oracles] +--- + +Capsules are a per-contract non-volatile database. +It can be used for storing arbitrary data that can be retrieved later. +The data is stored locally in PXE and it is scoped per contract address, so external contracts cannot access it. +The capsule (data stored under a storage slot in the capsules database) persists until explicitly deleted with `delete`. + +The capsules module provides these main functions: + +- `store` - Stores arbitrary data at a slot, overwriting any existing data +- `load` - Retrieves previously stored data from a slot +- `delete` - Deletes data at a slot +- `copy` - Efficiently copies contiguous entries between slots + +### 1. Import capsules into your smart contract + +Import the capsules module: + +```rust title="import_capsules" showLineNumbers +use dep::aztec::oracle::capsules; +``` +> Source code: noir-projects/noir-contracts/contracts/protocol/contract_class_registerer_contract/src/main.nr#L37-L39 + + +### 2. Store and load data + +You can store any type that implements `Serialize` and `Deserialize`: + +```rust title="load_capsule" showLineNumbers +let mut packed_public_bytecode: [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS] = unsafe { + capsules::load( + context.this_address(), + REGISTERER_CONTRACT_BYTECODE_CAPSULE_SLOT, + ) + .unwrap() +}; +``` +> Source code: noir-projects/noir-contracts/contracts/protocol/contract_class_registerer_contract/src/main.nr#L48-L56 + + +The data is stored per contract address and slot. When loading, you'll get back an `Option` - `None` if no data exists at that slot. + +### 3. Copying data + +You can use `copy` to move contiguous entries between slots without repeated loads and stores. +This supports overlapping source and destination regions. + +Note that all values are scoped per contract address, so external contracts cannot access them. + +### 4. Using CapsuleArray + +The `CapsuleArray` type provides a dynamically sized array backed by capsules. +It handles the storage layout and management automatically. +The array stores its length at a base slot, with elements stored in consecutive slots after it. + +Key functions: + +- `at(contract_address, base_slot)` - Creates/connects to an array at the given base slot +- `len()` - Returns the number of elements in the array +- `push(value)` - Appends a value to the end of the array +- `get(index)` - Retrieves the value at the given index +- `remove(index)` - Removes an element, shifting subsequent elements to maintain contiguous storage + + diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/index.mdx b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/index.mdx new file mode 100644 index 000000000000..964c27490c9c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/index.mdx @@ -0,0 +1,88 @@ +--- +title: Writing Contracts +tags: [aztec.nr] +--- + +import DocCardList from "@theme/DocCardList"; + +## Overview + +To write a contract: + +1. Import aztec.nr and declare your contract + +```rust +use dep::aztec::macros::aztec; + +#[aztec] +pub contract EasyPrivateVoting { +``` + +2. Define imports in your contract block + +```rust title="imports" showLineNumbers +use dep::aztec::{ + keys::getters::get_public_keys, + macros::{functions::{initializer, internal, private, public, utility}, storage::storage}, +}; +use dep::aztec::prelude::{AztecAddress, Map, PublicImmutable, PublicMutable}; +use dep::aztec::protocol_types::traits::{Hash, ToField}; +``` +> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L8-L16 + + +3. Declare your contract storage below your imports + +```rust title="storage_struct" showLineNumbers +#[storage] +struct Storage { + admin: PublicMutable, // admin can end vote + tally: Map, Context>, // we will store candidate as key and number of votes as value + vote_ended: PublicMutable, // vote_ended is boolean + active_at_block: PublicImmutable, // when people can start voting +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L17-L25 + + +4. Declare a constructor with `#[initializer]`. Constructors can be private or public functions. + +```rust title="constructor" showLineNumbers +#[public] +#[initializer] +// annotation to mark function as a constructor +fn constructor(admin: AztecAddress) { + storage.admin.write(admin); + storage.vote_ended.write(false); + storage.active_at_block.initialize(context.block_number()); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L27-L36 + + +5. Declare your contract functions + +```rust title="cast_vote" showLineNumbers +#[private] +// annotation to mark function as private and expose private context +fn cast_vote(candidate: Field) { + let msg_sender_npk_m_hash = get_public_keys(context.msg_sender()).npk_m.hash(); + + let secret = context.request_nsk_app(msg_sender_npk_m_hash); // get secret key of caller of function + let nullifier = std::hash::pedersen_hash([context.msg_sender().to_field(), secret]); // derive nullifier from sender and secret + context.push_nullifier(nullifier); + EasyPrivateVoting::at(context.this_address()).add_to_tally_public(candidate).enqueue( + &mut context, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L38-L51 + + +There is a lot more detail and nuance to writing contracts, but this should give you a good starting point. +Read contents of this section for more details about authorizing contract to act on your behalf (authenticaion witnesses), +emitting events, calling functions on other contracts and other common patterns. + +## Section Contents + + diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/initializers.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/initializers.md new file mode 100644 index 000000000000..eb9f44f8b6da --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/initializers.md @@ -0,0 +1,61 @@ +--- +title: Defining Initializer Functions +sidebar_position: 1 +tags: [functions, contracts] +--- + +This page explains how to write an initializer function, also known as a constructor. + +Initializers are regular functions that set an "initialized" flag (a nullifier) for the contract. A contract can only be initialized once, and contract functions can only be called after the contract has been initialized, much like a constructor. However, if a contract defines no initializers, it can be called at any time. Additionally, you can define as many initializer functions in a contract as you want, both private and public. + +## Annotate with `#[initializer]` + +Define your initializer like so: + +```rust +#[initializer] +fn constructor(){ + // function logic here +} +``` + +## Public or private + +Aztec supports both public and private initializers. Use the appropriate macro, for example for a private initializer: + +```rust +#[private] +#[initializer] +fn constructor(){ + // function logic here +} +``` + +## Initializer with logic + +Initializers are commonly used to set an admin, such as this example: + +```rust title="constructor" showLineNumbers +#[public] +#[initializer] +fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) { + assert(!admin.is_zero(), "invalid admin"); + storage.admin.write(admin); + storage.minters.at(admin).write(true); + storage.name.initialize(FieldCompressedString::from_string(name)); + storage.symbol.initialize(FieldCompressedString::from_string(symbol)); + storage.decimals.initialize(decimals); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L85-L98 + + +Here, the initializer is writing to storage. It can also call another function. Learn more about calling functions from functions [here](./call_contracts.md). + +## Multiple initializers + +You can set multiple functions as an initializer function simply by annotating each of them with `#[initializer]`. You can then decide which one to call when you are deploying the contract. + +Calling any one of the functions annotated with `#[initializer]` will mark the contract as initialized. + +To see an initializer in action, follow the [Counter codealong tutorial](../../../tutorials/codealong/contract_tutorials/counter_contract.md). diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/notes/address_note.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/notes/address_note.md new file mode 100644 index 000000000000..fa51568de187 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/notes/address_note.md @@ -0,0 +1,72 @@ +--- +title: Using Address Note in Aztec.nr +tags: [contracts, notes] +--- + +Address notes hold one main property of the type `AztecAddress`. It also holds `owner` and `randomness`, similar to other note types. + +## AddressNote + +This is the AddressNote: + +```rust title="address_note_def" showLineNumbers +#[note] +#[derive(Eq)] +pub struct AddressNote { + address: AztecAddress, + owner: AztecAddress, + randomness: Field, +} + +impl AddressNote { + pub fn new(address: AztecAddress, owner: AztecAddress) -> Self { + // Safety: we use the randomness to preserve the privacy of the note recipient by preventing brute-forcing, so a + // malicious sender could use non-random values to make the note less private. But they already know the full + // note pre-image anyway, and so the recipient already trusts them to not disclose this information. We can + // therefore assume that the sender will cooperate in the random value generation. + let randomness = unsafe { random() }; + AddressNote { address, owner, randomness } + } +} +``` +> Source code: noir-projects/aztec-nr/address-note/src/address_note.nr#L5-L24 + + +## Importing AddressNote + +### In Nargo.toml + +```toml +address_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/address-note" } +``` + +### In your contract + +```rust title="addressnote_import" showLineNumbers +use dep::address_note::address_note::AddressNote; +``` +> Source code: noir-projects/noir-contracts/contracts/app/escrow_contract/src/main.nr#L14-L16 + + +## Working with AddressNote + +### Creating a new note + +Creating a new `AddressNote` takes the following args: + +- `address` (`AztecAddress`): the address to store in the AddressNote +- `owner` (`AztecAddress`): owner is the party whose nullifying key can be used to spend the note + +```rust title="addressnote_new" showLineNumbers +let note = AddressNote::new(owner, owner); +``` +> Source code: noir-projects/noir-contracts/contracts/app/escrow_contract/src/main.nr#L28-L30 + + +In this example, `owner` is the `address` and the `npk_m_hash` of the donor was computed earlier. + +## Learn more + +- [Keys, including nullifier keys and outgoing viewer](../../../../../aztec/concepts/accounts/keys.md) +- [How to implement a note](./implementing_a_note.md) +- [AddressNote reference](../../../../reference/smart_contract_reference/aztec-nr/address-note/address_note.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/notes/implementing_a_note.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/notes/implementing_a_note.md new file mode 100644 index 000000000000..800b81721945 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/notes/implementing_a_note.md @@ -0,0 +1,54 @@ +--- +title: Implementing a note in Aztec.nr +tags: [contracts, notes] +keywords: [implementing note, note] +--- + +You may want to create your own note type if you need to use a specific type of private data or struct that is not already implemented in Aztec.nr, or if you want to experiment with custom note hashing and nullifier schemes. For custom hashing and nullifier schemes, use the `#[custom_note]` macro instead of `#[note]`, as it does not automatically derive the `NoteHash` trait. + +For example, if you are developing a card game, you may want to store multiple pieces of data in each card. Rather than storing each piece of data in its own note, you can define a card note type that contains all the data, and then nullify (or exchange ownership of) the card when it has been used. + +If you want to work with values, addresses or integers, you can check out [ValueNote](./value_note.md), [AddressNote](./address_note.md) or `UintNote`. + +## Define a note type + +You will likely want to define your note in a new file and import it into your contract. + +A note type can be defined with the macro `#[note]` used on a struct: + +```rust title="state_vars-CardNote" showLineNumbers +// We derive the Serialize trait because this struct is returned from a contract function. When returned, +// the struct is serialized using the Serialize trait and added to a hasher via the `add_to_hasher` utility. +// We use a hash rather than the serialized struct itself to keep circuit inputs constant. +#[note] +#[derive(Eq, Serialize, Deserialize)] +pub struct CardNote { + points: u8, + randomness: Field, + owner: AztecAddress, +} +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/types/card_note.nr#L6-L17 + + +In this example, we are implementing a card note that holds a number of `points` as `u8`. + +`randomness` is not enforced by the protocol and should be implemented by the application developer. If you do not include `randomness`, and the note preimage can be guessed by an attacker, it makes the note vulnerable to preimage attacks. + +`owner` is used when nullifying the note to obtain a nullifer secret key. +It ensures that when a note is spent, only the owner can spend it and the note sender cannot figure out that the note has been spent! +Providing the `owner` with improved privacy. + +Why is it delivering privacy from sender? + +Because a sender cannot derive a note nullifier. +We could derive the nullifier based solely on the note itself (for example, by computing `hash([note.points, note.owner, note.randomness], NULLIFIER_SEPARATOR)`). +This would work since the nullifier would be unique and only the note recipient could spend it (as contract logic typically only allows the note owner to obtain a note, e.g. from a `Map<...>`). +However, if we did this, the sender could also derive the nullifier off-chain and monitor the nullifier tree for its inclusion, allowing them to determine when a note has been spent. +This would leak privacy. + +## Further reading + +- [What is `#[note]` actually doing? + functions such as serialize() and deserialize()](../../../../../aztec/smart_contracts/functions/attributes.md#implementing-notes) +- [Macros reference](../../../../reference/smart_contract_reference/macros.md) +- [Keys, including npk_m_hash (nullifier public key master)](../../../../../aztec/concepts/accounts/keys.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/notes/index.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/notes/index.md new file mode 100644 index 000000000000..c26dc38e0791 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/notes/index.md @@ -0,0 +1,10 @@ +--- +title: Notes +sidebar_position: 3 +tags: [contracts, notes] +--- + +Notes are the fundamental data structure in Aztec when working with private state. Using Aztec.nr, developers can define note types which allow flexibility in how notes are stored and nullified. + +In this section there are guides about how to work with `AddressNote`, `ValueNote` from Aztec.nr and how to implement your own notes. +You can learn more about notes in the [concepts section](../../../../../aztec/concepts/storage/notes.md). diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/notes/value_note.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/notes/value_note.md new file mode 100644 index 000000000000..c1af3bb9cffc --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/notes/value_note.md @@ -0,0 +1,95 @@ +--- +title: Using Value Notes in Aztec.nr +tags: [contracts, notes] +--- + +ValueNotes hold one main property - a `value` - and have utils useful for manipulating this value, such as incrementing and decrementing it similarly to an integer. + +## ValueNote + +This is the ValueNote struct: + +```rust title="value-note-def" showLineNumbers +#[note] +#[derive(Eq)] +pub struct ValueNote { + value: Field, + owner: AztecAddress, + randomness: Field, +} +``` +> Source code: noir-projects/aztec-nr/value-note/src/value_note.nr#L3-L11 + + +## Importing ValueNote + +### In Nargo.toml + +```toml +value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/value-note" } +``` + +### In your contract + +```rust title="import_valuenote" showLineNumbers +use dep::value_note::value_note::ValueNote; +``` +> Source code: noir-projects/noir-contracts/contracts/test/child_contract/src/main.nr#L14-L16 + + +## Working with ValueNote + +### Creating a new note + +Creating a new `ValueNote` takes the following args: + +- `value` (`Field`): the value of the ValueNote +- `owner` (`AztecAddress`): owner is the party whose nullifying key can be used to spend the note + +```rust title="valuenote_new" showLineNumbers +let note = ValueNote::new(new_value, owner); +``` +> Source code: noir-projects/noir-contracts/contracts/test/child_contract/src/main.nr#L60-L62 + + +### Getting a balance + +A user may have multiple notes in a set that all refer to the same content (e.g. a set of notes representing a single token balance). By using the `ValueNote` type to represent token balances, you do not have to manually add each of these notes and can instead use a helper function `get_balance()`. + +It takes one argument - the set of notes. + +```rust title="get_balance" showLineNumbers +// Return the sum of all notes in the set. +balance_utils::get_balance(owner_balance) +``` +> Source code: noir-projects/noir-contracts/contracts/test/stateful_test_contract/src/main.nr#L105-L108 + + +This can only be used in an unconstrained function. + +### Incrementing and decrementing + +Both `increment` and `decrement` functions take the same args: + +```rust title="increment_args" showLineNumbers +balance: PrivateSet, +amount: Field, +``` +> Source code: noir-projects/aztec-nr/value-note/src/utils.nr#L27-L30 + + +Note that this will create a new note in the set of notes passed as the first argument. +For example: +```rust title="increment_valuenote" showLineNumbers +increment(storage.notes.at(owner), value, owner, sender); +``` +> Source code: noir-projects/noir-contracts/contracts/test/benchmarking_contract/src/main.nr#L24-L26 + + +The `decrement` function works similarly except the `amount` is the number that the value will be decremented by, and it will fail if the sum of the selected notes is less than the amount. + +## Learn more + +- [Keys, including nullifier keys and outgoing viewer](../../../../../aztec/concepts/accounts/keys.md) +- [How to implement a note](./implementing_a_note.md) +- [ValueNote reference](../../../../reference/smart_contract_reference/aztec-nr/value-note/value_note.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/portals/communicate_with_portal.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/portals/communicate_with_portal.md new file mode 100644 index 000000000000..f3becb91bc20 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/portals/communicate_with_portal.md @@ -0,0 +1,379 @@ +--- +title: Communicating with L1 +tags: [contracts, portals] +--- + +Follow the [token bridge tutorial](../../../../../developers/tutorials/codealong/js_tutorials/token_bridge.md) for hands-on experience writing and deploying a Portal contract. + +## Passing data to the rollup + +Whether it is tokens or other information being passed to the rollup, the portal should use the `Inbox` to do it. + +The `Inbox` can be seen as a mailbox to the rollup, portals put messages into the box, and the sequencer then consumes a batch of messages from the box and include it in their blocks. + +When sending messages, we need to specify quite a bit of information beyond just the content that we are sharing. Namely we need to specify: + +| Name | Type | Description | +| ----------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Recipient | `L2Actor` | The message recipient. This **MUST** match the rollup version and an Aztec contract that is **attached** to the contract making this call. If the recipient is not attached to the caller, the message cannot be consumed by it. | +| Secret Hash | `field` (~254 bits) | A hash of a secret that is used when consuming the message on L2. Keep this preimage a secret to make the consumption private. To consume the message the caller must know the pre-image (the value that was hashed) - so make sure your app keeps track of the pre-images! Use `computeSecretHash` to compute it from a secret. | +| Content | `field` (~254 bits) | The content of the message. This is the data that will be passed to the recipient. The content is limited to be a single field. If the content is small enough it can just be passed along, otherwise it should be hashed and the hash passed along (you can use our [`Hash` (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/core/libraries/Hash.sol) utilities with `sha256ToField` functions) | + +With all that information at hand, we can call the `sendL2Message` function on the Inbox. The function will return a `field` (inside `bytes32`) that is the hash of the message. This hash can be used as an identifier to spot when your message has been included in a rollup block. + +```solidity title="send_l1_to_l2_message" showLineNumbers +/** + * @notice Inserts a new message into the Inbox + * @dev Emits `MessageSent` with data for easy access by the sequencer + * @param _recipient - The recipient of the message + * @param _content - The content of the message (application specific) + * @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2) + * @return The key of the message in the set and its leaf index in the tree + */ +function sendL2Message( + DataStructures.L2Actor memory _recipient, + bytes32 _content, + bytes32 _secretHash +) external returns (bytes32, uint256); +``` +> Source code: l1-contracts/src/core/interfaces/messagebridge/IInbox.sol#L35-L49 + + +A sequencer will consume the message batch your message was included in and include it in their block. +Upon inclusion, it is made available to be consumed on L2 via the L2 outbox. + +To consume the message, we can use the `consume_l1_to_l2_message` function within the `context` struct. + +- The `msg_key` is the hash of the message returned by the `sendL2Message` call and is used to help the RPC find the correct message. +- The `content` is the content of the message, limited to one Field element. For content larger than one Field, we suggest using the `sha256` hash function truncated to a single Field element. `sha256` is suggested as it is cheap on L1 while still being manageable on L2. +- The `secret` is the pre-image hashed using Pedersen to compute the `secretHash`. +- If the `content` or `secret` does not match the entry at `msg_key` the message will not be consumed, and the transaction will revert. + +:::info +Note that while the `secret` and the `content` are both hashed, they are actually hashed with different hash functions! +::: + +```rust title="context_consume_l1_to_l2_message" showLineNumbers +pub fn consume_l1_to_l2_message( + &mut self, + content: Field, + secret: Field, + sender: EthAddress, + leaf_index: Field, +) { +``` +> Source code: noir-projects/aztec-nr/aztec/src/context/private_context.nr#L292-L301 + + +### Token bridge example + +Computing the `content` must currently be done manually, as we are still adding a number of bytes utilities. A good example exists within the [Token bridge example (codealong tutorial)](../../../../../developers/tutorials/codealong/js_tutorials/token_bridge.md). + +```rust title="claim_public" showLineNumbers +// Consumes a L1->L2 message and calls the token contract to mint the appropriate amount publicly +#[public] +fn claim_public(to: AztecAddress, amount: u128, secret: Field, message_leaf_index: Field) { + let content_hash = get_mint_to_public_content_hash(to, amount); + + let config = storage.config.read(); + + // Consume message and emit nullifier + context.consume_l1_to_l2_message(content_hash, secret, config.portal, message_leaf_index); + + // Mint tokens + Token::at(config.token).mint_to_public(to, amount).call(&mut context); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr#L57-L71 + + +:::info +The `content_hash` is a sha256 truncated to a field element (~ 254 bits). In Aztec.nr, you can use our `sha256_to_field()` to do a sha256 hash which fits in one field element +::: + +### Token portal hash library + +```rust title="mint_to_public_content_hash_nr" showLineNumbers +use dep::aztec::prelude::{AztecAddress, EthAddress}; +use dep::aztec::protocol_types::{hash::sha256_to_field, traits::ToField}; + +// Computes a content hash of a deposit/mint_to_public message. +// Refer TokenPortal.sol for reference on L1. +pub fn get_mint_to_public_content_hash(owner: AztecAddress, amount: u128) -> Field { + let mut hash_bytes = [0; 68]; + let recipient_bytes: [u8; 32] = owner.to_field().to_be_bytes(); + let amount_bytes: [u8; 32] = (amount as Field).to_be_bytes(); + + // The purpose of including the following selector is to make the message unique to that specific call. Note that + // it has nothing to do with calling the function. + let selector = + comptime { keccak256::keccak256("mint_to_public(bytes32,uint256)".as_bytes(), 31) }; + + for i in 0..4 { + hash_bytes[i] = selector[i]; + } + + for i in 0..32 { + hash_bytes[i + 4] = recipient_bytes[i]; + hash_bytes[i + 36] = amount_bytes[i]; + } + + let content_hash = sha256_to_field(hash_bytes); + content_hash +} +``` +> Source code: noir-projects/noir-contracts/contracts/libs/token_portal_content_hash_lib/src/lib.nr#L1-L29 + + +### Token Portal contract + +In Solidity, you can use our `Hash.sha256ToField()` method: + +```solidity title="content_hash_sol_import" showLineNumbers +import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; +``` +> Source code: l1-contracts/test/portals/TokenPortal.sol#L12-L14 + + +```solidity title="deposit_public" showLineNumbers +/** + * @notice Deposit funds into the portal and adds an L2 message which can only be consumed publicly on Aztec + * @param _to - The aztec address of the recipient + * @param _amount - The amount to deposit + * @param _secretHash - The hash of the secret consumable message. The hash should be 254 bits (so it can fit in a Field element) + * @return The key of the entry in the Inbox and its leaf index + */ +function depositToAztecPublic(bytes32 _to, uint256 _amount, bytes32 _secretHash) + external + returns (bytes32, uint256) +``` +> Source code: l1-contracts/test/portals/TokenPortal.sol#L55-L66 + + +The `secret_hash` uses the pederson hash which fits in a field element. You can use the utility method `computeSecretHash()`in `@aztec/aztec.js` npm package to generate a secret and its corresponding hash. + +After the transaction has been mined, the message is consumed, a nullifier is emitted and the tokens have been minted on Aztec and are ready for claiming. + +Since the message consumption is emitting a nullifier, the same message cannot be consumed again. The index in the message tree is used as part of the nullifier computation, ensuring that the same content and secret being inserted will be distinct messages that can each be consumed. Without the index in the nullifier, it would be possible to perform a kind of attack known as `Faerie Gold` attacks where two seemingly good messages are inserted, but only one of them can be consumed later. + +## Passing data to L1 + +To pass data to L1, we use the `Outbox`. The `Outbox` is the mailbox for L2 to L1 messages. This is the location on L1 where all the messages from L2 will live, and where they can be consumed from. + +:::danger + +Similarly to messages going to L2 from L1, a message can only be consumed by the specified recipient. But it is up to the portal contract to ensure that the sender is as expected! Any L2 contract can send a message to a portal contract on L1, but the portal contract should only consume messages from the expected sender. + +::: + +The portal must ensure that the sender is as expected. One flexible solution is to have an `initialize` function in the portal contract which can be used to set the address of the Aztec contract. In this model, the portal contract can check that the sender matches the value it has in storage. + +To send a message to L1 from your Aztec contract, you must use the `message_portal` function on the `context`. When messaging to L1, only the `content` is required (as a `Field`). + +```rust title="context_message_portal" showLineNumbers +pub fn message_portal(&mut self, recipient: EthAddress, content: Field) { +``` +> Source code: noir-projects/aztec-nr/aztec/src/context/private_context.nr#L285-L287 + + +When sending a message from L2 to L1 we don't need to pass in a secret. + +:::danger +Access control on the L1 portal contract is essential to prevent consumption of messages sent from the wrong L2 contract. +::: + +### Token bridge + +As earlier, we can use a token bridge as an example. In this case, we are burning tokens on L2 and sending a message to the portal to free them on L1. + +```rust title="exit_to_l1_private" showLineNumbers +// Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message privately +// Requires `msg.sender` (caller of the method) to give approval to the bridge to burn tokens on their behalf using witness signatures +#[private] +fn exit_to_l1_private( + token: AztecAddress, + recipient: EthAddress, // ethereum address to withdraw to + amount: u128, + caller_on_l1: EthAddress, // ethereum address that can call this function on the L1 portal (0x0 if anyone can call) + authwit_nonce: Field, // nonce used in the approval message by `msg.sender` to let bridge burn their tokens on L2 +) { + let config = storage.config.read(); + + // Assert that user provided token address is same as seen in storage. + assert_eq(config.token, token, "Token address is not the same as seen in storage"); + + // Send an L2 to L1 message + let content = get_withdraw_content_hash(recipient, amount, caller_on_l1); + context.message_portal(config.portal, content); + + // Burn tokens + Token::at(token).burn_private(context.msg_sender(), amount, authwit_nonce).call(&mut context); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr#L127-L152 + + +When the transaction is included in a rollup block and published to Ethereum the message will be inserted into the `Outbox` on Ethereum, where the recipient portal can consume it from. When consuming, the `msg.sender` must match the `recipient` meaning that only portal can actually consume the message. + +```solidity title="l2_to_l1_msg" showLineNumbers +/** + * @notice Struct containing a message from L2 to L1 + * @param sender - The sender of the message + * @param recipient - The recipient of the message + * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. + * @dev Not to be confused with L2ToL1Message in Noir circuits + */ +struct L2ToL1Msg { + DataStructures.L2Actor sender; + DataStructures.L1Actor recipient; + bytes32 content; +} +``` +> Source code: l1-contracts/src/core/libraries/DataStructures.sol#L53-L66 + + +#### Outbox `consume` + +```solidity title="outbox_consume" showLineNumbers +/** + * @notice Consumes an entry from the Outbox + * @dev Only useable by portals / recipients of messages + * @dev Emits `MessageConsumed` when consuming messages + * @param _message - The L2 to L1 message + * @param _l2BlockNumber - The block number specifying the block that contains the message we want to consume + * @param _leafIndex - The index inside the merkle tree where the message is located + * @param _path - The sibling path used to prove inclusion of the message, the _path length directly depends + * on the total amount of L2 to L1 messages in the block. i.e. the length of _path is equal to the depth of the + * L1 to L2 message tree. + */ +function consume( + DataStructures.L2ToL1Msg calldata _message, + uint256 _l2BlockNumber, + uint256 _leafIndex, + bytes32[] calldata _path +) external; +``` +> Source code: l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L34-L52 + + +#### Withdraw + +As noted earlier, the portal contract should check that the sender is as expected. In the example below, we support only one sender contract (stored in `l2TokenAddress`) so we can just pass it as the sender, that way we will only be able to consume messages from that contract. + +It is possible to support multiple senders from L2. You could use a have `mapping(address => bool) allowed` and check that `allowed[msg.sender]` is `true`. + +```solidity title="token_portal_withdraw" showLineNumbers +/** + * @notice Withdraw funds from the portal + * @dev Second part of withdraw, must be initiated from L2 first as it will consume a message from outbox + * @param _recipient - The address to send the funds to + * @param _amount - The amount to withdraw + * @param _withCaller - Flag to use `msg.sender` as caller, otherwise address(0) + * @param _l2BlockNumber - The address to send the funds to + * @param _leafIndex - The amount to withdraw + * @param _path - Flag to use `msg.sender` as caller, otherwise address(0) + * Must match the caller of the message (specified from L2) to consume it. + */ +function withdraw( + address _recipient, + uint256 _amount, + bool _withCaller, + uint256 _l2BlockNumber, + uint256 _leafIndex, + bytes32[] calldata _path +) external { + // The purpose of including the function selector is to make the message unique to that specific call. Note that + // it has nothing to do with calling the function. + DataStructures.L2ToL1Msg memory message = DataStructures.L2ToL1Msg({ + sender: DataStructures.L2Actor(l2Bridge, rollupVersion), + recipient: DataStructures.L1Actor(address(this), block.chainid), + content: Hash.sha256ToField( + abi.encodeWithSignature( + "withdraw(address,uint256,address)", + _recipient, + _amount, + _withCaller ? msg.sender : address(0) + ) + ) + }); + + outbox.consume(message, _l2BlockNumber, _leafIndex, _path); + + underlying.transfer(_recipient, _amount); +} +``` +> Source code: l1-contracts/test/portals/TokenPortal.sol#L122-L161 + + +## Considerations + +### Structure of messages + +Application developers should consider creating messages that follow a function call structure e.g., using a function signature and arguments. This will make it easier to prevent producing messages that could be misinterpreted by the recipient. + +An example of a bad format would be using `amount, token_address, recipient_address` as the message for a withdraw function and `amount, token_address, on_behalf_of_address` for a deposit function. Any deposit could then also be mapped to a withdraw or vice versa. + +```solidity +// Don't to this! +bytes memory message = abi.encode( + _amount, + _token, + _to +); + +// Do this! +bytes memory message abi.encodeWithSignature( + "withdraw(uint256,address,address)", + _amount, + _token, + _to +); +``` + +### Error Handling + +Handling error when moving cross chain can quickly get tricky. Since the L1 and L2 calls are async and independent of each other, the L1 part of a deposit might execute just fine, with the L2 part failing. If this is not handled well, the funds may be lost forever! Developers should consider ways their application can fail cross chain, and handle all cases explicitly. + +First, entries in the outboxes **SHOULD** only be consumed if the execution is successful. For an L2 -> L1 call, the L1 execution can revert the transaction completely if anything fails. As the tx is atomic, the failure also reverts consumption. + +If it is possible to enter a state where the second part of the execution fails forever, the application builder should consider including additional failure mechanisms (for token withdraws this could be depositing them again etc). + +Generally it is good practice to keep cross-chain calls simple to avoid too many edge cases and state reversions. + +:::info +Error handling for cross chain messages is handled by the application contract and not the protocol. The protocol only delivers the messages, it does not ensure that they are executed successfully. +::: + +### Designated caller + +Designating a caller grants the ability to specify who should be able to call a function that consumes a message. This is useful for ordering of batched messages. + +When performing multiple cross-chain calls in one action it is important to consider the order of the calls. Say for example, that you want to perform a uniswap trade on L1. You would withdraw funds from the rollup, swap them on L1, and then deposit the swapped funds back into the rollup. This is a straightforward process, but it requires that the calls are done in the correct order (e.g. if the swap is called before the funds are withdrawn, the swap will fail). + +The message boxes (Inbox and Outbox) will only allow the recipient portal to consume the message, and we can use this to ensure that the calls are done in the correct order. Say that we include a designated "caller" in the messages, and that the portal contract checks that the caller matches the designated caller or designated as `address(0)` (if anyone can call). When the messages are to be consumed on L1, it can compute the message as seen below: + +```solidity +bytes memory message = abi.encodeWithSignature( + "withdraw(uint256,address,address)", + _amount, + _to, + _withCaller ? msg.sender : address(0) +); +``` + +This way, the message can be consumed by the portal contract, but only if the caller is the specified caller. In the logic of the contract that is the designated caller, we can ensure that the calls are done in the correct order. + +For example, we could require that the Uniswap portal is the caller of the withdrawal, and ensure that the uniswap portal contract implementation is executing the withdrawal before the swap. +The order of execution can be specified in the contract. Since all of the messages are emitted to L1 in the same transaction, we can leverage transaction atomicity to ensure success of failure of all messages. + +Note, that crossing the L1/L2 chasm is asynchronous, so there could be a situation where the user has burned their assets on L2 but the swap fails on L1! This could be due to major price movements for example. In such a case, the user could be stuck with funds on L1 that they cannot get back to L2 unless the portal contract implements a way to properly handle such errors. + +:::caution +Designated callers are enforced at the contract level for contracts that are not the rollup itself, and should not be trusted to implement the contract correctly. The user should always be aware that it is possible for the developer to implement something that looks like designated caller without providing the abilities to the user. +::: + +## Examples of portals + +- Token bridge (Portal contract built for L1 -> L2, i.e., a non-native L2 asset) + - [Portal contract (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/l1-contracts/test/portals/TokenPortal.sol) + - [Aztec contract (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr) diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/portals/index.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/portals/index.md new file mode 100644 index 000000000000..d9292f2848e8 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/portals/index.md @@ -0,0 +1,6 @@ +--- +title: Portals +sidebar_position: 7 +--- + +A portal is a point of contact between L1 and a contract on Aztec. For applications such as token bridges, this is the point where the tokens are held on L1 while used in L2. Note, that a portal doesn't actually need to be a contract, it could be any address on L1. diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/storage/index.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/storage/index.md new file mode 100644 index 000000000000..622df9645037 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/storage/index.md @@ -0,0 +1,21 @@ +--- +title: Declaring Storage +sidebar_position: 2 +tags: [contracts, storage] +--- + +On this page, you will learn how to define storage in your smart contract. + +To learn more about how storage works in Aztec, read [the concepts](./storage_slots.md). + +[See the storage reference](../../../../reference/smart_contract_reference/storage/index.md). + +```rust +#[storage] +struct Storage { + // public state variables + // private state variables +} +``` + +If you have defined a struct and annotated it as `#[storage]`, then it will be made available to you through the reserved `storage` keyword within your contract functions. diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/storage/notes.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/storage/notes.md new file mode 100644 index 000000000000..3ca9ab327c7d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/storage/notes.md @@ -0,0 +1,169 @@ +--- +title: Writing Notes +description: Core knowledge of Notes and how they work +useful-for: dev +tags: [contracts, storage, notes] +--- + +Most prominent blockchain networks don't have privacy at the protocol level. Aztec contracts can define public and private functions, that can read/write public and private state. + +To be clear, "private" here is referring to the opposite of how things work on a public blockchain network, not the conventional syntax for visibility within code. + +For private state we need encryption and techniques to hide information about state changes. For private functions, we need local execution and proof of correct execution. + +### Some context + +- Public functions and storage work much like other blockchains in terms of having dedicated storage slots and being publicly visible +- Private functions are executed locally with proofs generated for sound execution, and commitments to private variable updates are stored using append-only trees +- "Note" types are part of Aztec.nr, a framework that facilitates use of Aztec's different storage trees to achieve things such as private variables + +This page will focus on how private variables are implemented with Notes and storage trees. + +#### Side-note about execution + +Under the hood, the Aztec protocol handles some important details around public and private function calls. Calls between them are asynchronous due to different execution contexts (local execution vs. node execution). +A detailed explanation of the transaction lifecycle can be found [here](../../../../../aztec/concepts/transactions.md#simple-example-of-the-private-transaction-lifecycle). + +## Private state variables in Aztec + +State variables in an Aztec contract are defined inside a struct specifically named `Storage`, and must satisfy the [Note Interface (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/aztec-nr/aztec/src/note/note_interface.nr) and contain a [Note header (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/aztec-nr/aztec/src/note/note_header.nr). + +The Note header struct contains the contract address which the value is effectively siloed to, a nonce to ensure unique Note hashes, and a storage "slot" (or ID) to associate multiple notes. + +A couple of things to unpack here: + +#### Storage "slot" + +Storage slots are more literal for public storage, a place where a value is stored. For private storage, a storage slot is logical (more [here](../../../../../aztec/concepts/advanced/storage/storage_slots.md)). + +#### Silos + +The address of the contract is included in a Note's data to ensure that different contracts don't arrive at the same hash with an identical variable. This is handled in the protocol's execution. + +### Note types + +There is more than one Note type, such as the `PrivateSet` type is used for private variables. There are also `PrivateMutable` and `PrivateImmutable` types. + +Furthermore, notes can be completely custom types, storing any value or set of values that are desired by an application. + +### Initialization + +Private state variables are stored locally when the contract is created. Depending on the application, values may be privately shared by the creator with others via encrypted logs onchain. +A hash of a note is stored in the append-only note hash tree on the network so as to prove existence of the current state of the note in a privacy preserving way. + +#### Note Hash Tree + +By virtue of being append only, notes are not edited. If two transactions amend a private value, multiple notes will be inserted into the tree to the note hash tree and the nullifier tree. The header will contain the same logical storage slot. + +### Reading Notes + +:::info + +Only those with appropriate keys/information will be able to successfully read private values that they have permission to. Notes can be read outside of a transaction or "off-chain" with no changes to data structures on-chain. + +::: + +When a note is read in a transaction, a subsequent read from another transaction of the same note would reveal a link between the two. So to preserve privacy, notes that are read in a transaction are said to be "consumed" (defined below), and new note(s) are then created with a unique hash. + +With type `PrviateSet`, a private variable's value is interpreted as the sum of values of notes with the same logical storage slot. + +Consuming, deleting, or otherwise "nullifying" a note is NOT done by deleting the Note hash; this would leak information. Rather a nullifier is created deterministically linked to the value. This nullifier is inserted into another the nullifier storage tree. + +When reading a value, the local private execution checks that its notes (of the corresponding storage slot/ID) have not been nullified. + +### Updating + +:::note +Only those with appropriate keys/information will be able to successfully nullify a value that they have permission to. +::: + +To update a value, its previous note hash(es) are nullified. The new note value is updated in the user's private execution environment (PXE), and the updated note hash inserted into the note hash tree. + +## Supplementary components + +Some optional background resources on notes can be found here: + +- [High level network architecture](../../../../../aztec/index.md), specifically the Private Execution Environment +- [Transaction lifecycle (simple diagram)](../../../../../aztec/concepts/transactions.md#simple-example-of-the-private-transaction-lifecycle) +- [Public and Private state](../../../../../aztec/concepts/storage/state_model.md) + +Notes touch several core components of the protocol, but we will focus on a the essentials first. + +### Some code context + +The way Aztec benefits from the Noir language is via three important components: + +- `Aztec.nr` - a Noir framework enabling contracts on Aztec, written in Noir. Includes useful Note implementations +- `noir contracts` - example Aztec contracts +- `noir-protocol-circuits` - a crate containing essential circuits for the protocol (public circuits and private wrappers) + +A lot of what we will look at will be in [aztec-nr/aztec/src/note (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/aztec-nr/aztec/src/note), specifically the lifecycle and note interface. + +Looking at the noir circuits in these components, you will see references to the distinction between public/private execution and state. + +### Lifecycle functions + +Inside the [lifecycle (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr) circuits we see the functions to create and destroy a note, implemented as insertions of note hashes and nullifiers respectively. This is helpful for regular private variables. + +We also see a function to create a note hash from the public context, a way of creating a private variable from a public call (run in the sequencer). This could be used in application contracts to give private digital assets to users. + +### Note Interface functions + +To see a [note_interface (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/aztec-nr/aztec/src/note/note_interface.nr) implementation, we will look at a simple [ValueNote GitHub link](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/aztec-nr/value-note/src/value_note.nr). + +The interface is required to work within an Aztec contract's storage, and a ValueNote is a specific type of note to hold a number (as a `Field`). + +#### Computing hashes and nullifiers + +A few key functions in the note interface are around computing the note hash and nullifier, with logic to get/use secret keys from the private context. + +In the ValueNote implementation you'll notice that it uses the `pedersen_hash` function. This is currently required by the protocol, but may be updated to another hashing function, like poseidon. + +As a convenience, the outer [note/utils.nr (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/aztec-nr/aztec/src/note/utils.nr) contains implementations of functions that may be needed in Aztec contracts, for example computing note hashes. + +#### Serialization and deserialization + +Serialization/deserialization of content is used to convert between the Note's variables and a generic array of Field elements. The Field type is understood and used by lower level crypographic libraries. +This is analogous to the encoding/decoding between variables and bytes in solidity. + +For example in ValueNote, the `serialize_content` function simply returns: the value, nullifying public key hash (as a field) and the note randomness; as an array of Field elements. + +### Value as a sum of Notes + +We recall that multiple notes are associated with a "slot" (or ID), and so the value of a numerical note (like ValueNote) is the sum of each note's value. +The helper function in [balance_utils (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_/noir-projects/aztec-nr/value-note/src/balance_utils.nr) implements this logic taking a `PrivateSet` of `ValueNotes`. + +A couple of things worth clarifying: + +- A `PrivateSet` takes a Generic type, specified here as `ValueNote`, but can be any `Note` type (for all notes in the set) +- A `PrivateSet` of notes also specifies _the_ slot of all Notes that it holds + +### Example - Notes in action + +The Aztec.nr framework includes examples of high-level states [easy_private_uint (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr) for use in contracts. + +The struct (`EasyPrivateUint`) contains a Context, Set of ValueNotes, and storage_slot (used when setting the Set). + +Notice how the `add` function shows the simplicity of appending a new note to all existing ones. On the other hand, `sub` (subtraction), needs to first add up all existing values (consuming them in the process), and then insert a single new value of the difference between the sum and parameter. + +--- + +### Apply + +Try the [NFT tutorial](../../../../tutorials/codealong/contract_tutorials/nft_contract.md) to see what notes can achieve. In this section you will also find other tutorials using notes in different ways. + +### Further reading + +- [Proof of prior notes](../how_to_prove_history.md) - public/private reading of public/private proof of state (public or private) + +If you're curious about any of the following related topics, search the documentation for... + +- Private and public contexts +- Encryption keys and events +- Oracle's role in using notes +- Value Serialization/Deserialization + +### References + +- ["Stable" state variable (GitHub link)](https://github.com/AztecProtocol/aztec-packages/issues/4130) +- [Code: Aztec-Patterns (GitHub link)](https://github.com/defi-wonderland/aztec-patterns) diff --git a/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/storage/storage_slots.md b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/storage/storage_slots.md new file mode 100644 index 000000000000..92c45aa0febd --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/guides/smart_contracts/writing_contracts/storage/storage_slots.md @@ -0,0 +1,68 @@ +--- +title: Storage slots +tags: [contracts, storage] +--- + +From the description of storage slots [in the Concepts](../../../../../aztec/concepts/advanced/storage/storage_slots.md) you will get an idea around the logic of storage slots. In this section we will go into more detail and walk through an entire example of how storage slots are computed for private state to improve our storage slot intuition. Recall, that storage slots in the private domain is just a logical construct, and are not "actually" used for lookups, but rather just as a value to constrain against. + +For the case of the example, we will look at what is inserted into the note hashes tree when adding a note in the Token contract. Specifically, we are looking at the last part of the `transfer` function: + +```rust title="increase_private_balance" showLineNumbers +storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note( + &mut context, + from, + from, +)); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L424-L430 + + +This function is creating a new note and inserting it into the balance set of the recipient `to`. Recall that to ensure privacy, only the note hash is really inserted into the note hashes tree. To share the contents of the note with `to` the contract can emit an encrypted log (which this one does), or it can require an out-of-band data transfer sharing the information. Below, we will walk through the steps of how the note hash is computed and inserted into the tree. For this, we don't care about the encrypted log, so we are going to ignore that part of the function call for now. + +Outlining it in more detail below as a sequence diagram, we can see how the calls make their way down the stack. +In the end a siloed note hash is computed in the kernel. + +:::info +Some of the syntax below is a little butchered to make it easier to follow variables without the full code. +::: + +```mermaid +sequenceDiagram + alt Call + Token->>BalanceMap: Map::new(map_slot); + Token->>Token: to_bal = storage.balances.at(to) + Token->>BalanceMap: BalanceMap.at(to) + BalanceMap->>BalanceMap: derived_slot = H(map_slot, to) + BalanceMap->>BalanceSet: BalanceSet::new(to, derived_slot) + Token->>BalanceSet: to_bal.add(amount) + BalanceSet->>BalanceSet: note = UintNote::new(amount, to) + BalanceSet->>Set: insert(note) + Set->>LifeCycle: create_note(derived_slot, note) + LifeCycle->>LifeCycle: note.header = NoteHeader { contract_address,
storage_slot: derived_slot, nonce: 0, note_hash_counter } + UintPartialNotePrivateContent->>UintNote: note_hash = compute_partial_commitment(storage_slot).x + LifeCycle->>Context: push_note_hash(note_hash) + end + Context->>Kernel: unique_note_hash = H(nonce, note_hash) + Context->>Kernel: siloed_note_hash = H(contract_address, unique_note_hash) +``` + +Notice the `siloed_note_hash` at the very end. It's a hash that will be inserted into the note hashes tree. To clarify what this really is, we "unroll" the values to their simplest components. This gives us a better idea around what is actually inserted into the tree. + +```rust +siloed_note_hash = H(contract_address, unique_note_hash) +siloed_note_hash = H(contract_address, H(nonce, note_hash)) +siloed_note_hash = H(contract_address, H(H(tx_hash, note_index_in_tx), note_hash)) +siloed_note_hash = H(contract_address, H(H(tx_hash, note_index_in_tx), MSM([G_amt, G_to, G_rand, G_slot], [amount, to, randomness, derived_slot]).x)) +``` + +MSM is a multi scalar multiplication on a grumpkin curve and G_* values are generators. + +And `to` is the actor who receives the note, `amount` of the note and `randomness` is the randomness used to make the note hiding. Without the `randomness` the note could just as well be plaintext (computational cost of a preimage attack would be trivial in such a case). + +:::info +Beware that this hash computation is what the aztec.nr library is doing, and not strictly required by the network (only the kernel computation is). +::: + +With this note structure, the contract can require that only notes sitting at specific storage slots can be used by specific operations, e.g., if transferring funds from `from` to `to`, the notes to destroy should be linked to `H(map_slot, from)` and the new notes (except the change-note) should be linked to `H(map_slot, to)`. + +That way, we can have logical storage slots, without them really existing. This means that knowing the storage slot for a note is not enough to actually figure out what is in there (whereas it would be for looking up public state). diff --git a/docs/versioned_docs/version-v0.88.0/developers/index.md b/docs/versioned_docs/version-v0.88.0/developers/index.md new file mode 100644 index 000000000000..df67b4f2eba6 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/index.md @@ -0,0 +1,79 @@ +--- +id: index +sidebar_position: 0 +title: Build +--- + +# Build + +## Get Started + +
+ + +

Getting Started

+
+ + Get started on Aztec by installing the sandbox or interacting with the testnet + +
+
+ +## Build applications + +
+ + +

Contract Tutorials

+
+ + Go from zero to hero by following these tutorials in order, starting with a counter contract + +
+ + + +

Full stack app on Aztec

+
+ + Learn how everything works together by building an app in JavaScript that connects to a contract + +
+
+ +## Clone a repo + + + + GitHub Icon + + +

Full stack app on Aztec

+
+ + Learn how everything works together by building an app in JavaScript that connects to a contract + +
+ +## Get inspired + +
+ + +

What to build on Aztec

+
+ + Find requests for applications, potential designs, and existing ecosystem projects + +
+
diff --git a/docs/versioned_docs/version-v0.88.0/developers/inspiration.md b/docs/versioned_docs/version-v0.88.0/developers/inspiration.md new file mode 100644 index 000000000000..6f89bd191338 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/inspiration.md @@ -0,0 +1,98 @@ +--- +id: inspiration +title: Inspiration and Ideas +--- + +This page covers requests for apps on Aztec, designs that some teams are exploring, existing contract examples, and open source ecosystem projects. + +## App ideas + +### Finance + +- Undercollateralized Lending +- Private order matching +- Hidden liquidity aggregation +- Batch Clearing +- Escrow Systems +- Private Bill Splitting +- Fee Payment Contracts (FPCs) +- Private Auction Systems +- Private Betting with Commit-Reveal Schemes +- Stablecoins +- Stablecoin Swap Portal +- L1-L2 Lending Portal (supply L1, borrow L2) +- Mint DAI on L2 via L1 ETH +- Recurring Payments with Account Abstraction +- Token Streaming + +### Identity & Access Management + +- Private Glassdoor +- Private Yelp +- Onchain Loyalty System +- History-based Gating +- NFT Verification +- Multisig Implementation +- Verifiable Random Functions (VRFs) +- Bridge to connect Aztec with Other L2s +- L1-L2 Activity Verification, eg prove L1 degeneracy score on L2 +- Storage proof libraries and tooling +- Private Creator Payment Systems + +### Gaming & Governance + +- DAO Infrastructure +- DAO Voting via Portals (threshold-triggered) +- Private Voting Systems +- Private Airdrop Systems + +### Infrastructure & Tooling + +- Privacy-Preserving Oracle Systems +- Privacy-Preserving RPC Proxies + +There are countless other ideas. Join the [developer Discord](https://discord.com/invite/aztec) if you're interested in building any of these or more. + +## Design explorations + +Many of these are not official resources, may be outdated, or are works in progress. + +- [Blog: Can blockchains and zero-knowledge help humanity survive? 47 real-world use cases](https://aztec.network/blog/can-blockchains-and-zero-knowledge-help-humanity-survive-47-real-world-use-cases) +- [Alpha Build: DEXes](https://docs.google.com/document/d/1J0i2ciIHFN2bJJxLRgEdJnI6hd7FxkedSd78qMC7ziM/edit?usp=sharing) - A Google doc with a deep dive of how various DEXes could work on Aztec + +## Contract examples + +Explore the [tutorials section](../developers/tutorials/codealong/contract_tutorials/counter_contract.md) for some contract and full-stack project examples. + +There is also a list of up-to-date contract examples in the [aztec-packages Github repo](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-contracts/contracts). + +## Ecosystem projects + +- [ShieldSwap](https://app.shieldswap.org/?type=market) +- [AztecScan](https://aztecscan.xyz/) +- [Azguard](https://azguardwallet.io/terms) + +Explore the [Aztec Network ecosystem page](https://aztec.network/ecosystem) to discover more. + +### Proof of concepts + +Many of these are outdated. + +- [Ikegai](https://github.com/resurgencelabs/ikigai_backend) +- [Aztec private oracle](https://github.com/defi-wonderland/aztec-private-oracle) +- [Zybil](https://github.com/mach-34/zybil) +- [Aztec poker](https://github.com/zobront/aztec-poker/) +- [Alpha Build winners](https://aztec.network/blog/shaping-the-future-of-payments-meet-the-winners-of-alpha-build-one) + +### Noir + +This is a list of open source Noir projects, libraries, or primitives that can be integrated into Aztec projects. + +- [ZKEmail](https://github.com/zkemail/zkemail.nr/tree/main) +- [ZKPassport](https://github.com/zkpassport) +- [StealthNote](https://github.com/saleel/stealthnote) +- [Anoncast](https://github.com/anondotworld/anonworld) + +## Independent projects with open issues + +Explore independent projects on [OnlyDust](https://app.onlydust.com/ecosystems/aztec). diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/_category_.yml b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/_category_.yml new file mode 100644 index 000000000000..57dc01ecdd54 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/_category_.yml @@ -0,0 +1 @@ +label: "AztecJS" diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/_category_.yml b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/_category_.yml new file mode 100644 index 000000000000..08af16b98be8 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/_category_.yml @@ -0,0 +1 @@ +label: "Accounts" \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/_category_.yml b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/_category_.yml new file mode 100644 index 000000000000..55c7980a4644 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/_category_.yml @@ -0,0 +1,2 @@ +label: "Classes" +position: 3 \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/defaults.DefaultAccountContract.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/defaults.DefaultAccountContract.md new file mode 100644 index 000000000000..11d775807e59 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/defaults.DefaultAccountContract.md @@ -0,0 +1,94 @@ +--- +id: "defaults.DefaultAccountContract" +title: "Class: DefaultAccountContract" +sidebar_label: "DefaultAccountContract" +custom_edit_url: null +--- + +[defaults](../modules/defaults.md).DefaultAccountContract + +Base class for implementing an account contract. Requires that the account uses the +default entrypoint method signature. + +## Implements + +- `AccountContract` + +## Constructors + +### constructor + +• **new DefaultAccountContract**(): [`DefaultAccountContract`](defaults.DefaultAccountContract.md) + +#### Returns + +[`DefaultAccountContract`](defaults.DefaultAccountContract.md) + +## Methods + +### getAuthWitnessProvider + +▸ **getAuthWitnessProvider**(`address`): `AuthWitnessProvider` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `address` | `CompleteAddress` | + +#### Returns + +`AuthWitnessProvider` + +#### Implementation of + +AccountContract.getAuthWitnessProvider + +___ + +### getContractArtifact + +▸ **getContractArtifact**(): `Promise`\<`ContractArtifact`\> + +#### Returns + +`Promise`\<`ContractArtifact`\> + +#### Implementation of + +AccountContract.getContractArtifact + +___ + +### getDeploymentFunctionAndArgs + +▸ **getDeploymentFunctionAndArgs**(): `Promise`\<`undefined` \| \{ `constructorArgs`: `any`[] ; `constructorName`: `string` }\> + +#### Returns + +`Promise`\<`undefined` \| \{ `constructorArgs`: `any`[] ; `constructorName`: `string` }\> + +#### Implementation of + +AccountContract.getDeploymentFunctionAndArgs + +___ + +### getInterface + +▸ **getInterface**(`address`, `nodeInfo`): `AccountInterface` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `address` | `CompleteAddress` | +| `nodeInfo` | `NodeInfo` | + +#### Returns + +`AccountInterface` + +#### Implementation of + +AccountContract.getInterface diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/defaults.DefaultAccountInterface.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/defaults.DefaultAccountInterface.md new file mode 100644 index 000000000000..b55ab89a094d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/defaults.DefaultAccountInterface.md @@ -0,0 +1,161 @@ +--- +id: "defaults.DefaultAccountInterface" +title: "Class: DefaultAccountInterface" +sidebar_label: "DefaultAccountInterface" +custom_edit_url: null +--- + +[defaults](../modules/defaults.md).DefaultAccountInterface + +Default implementation for an account interface. Requires that the account uses the default +entrypoint signature, which accept an AppPayload and a FeePayload as defined in noir-libs/aztec-noir/src/entrypoint module + +## Implements + +- `AccountInterface` + +## Constructors + +### constructor + +• **new DefaultAccountInterface**(`authWitnessProvider`, `address`, `nodeInfo`): [`DefaultAccountInterface`](defaults.DefaultAccountInterface.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `authWitnessProvider` | `AuthWitnessProvider` | +| `address` | `CompleteAddress` | +| `nodeInfo` | `Pick`\<`NodeInfo`, ``"l1ChainId"`` \| ``"rollupVersion"``\> | + +#### Returns + +[`DefaultAccountInterface`](defaults.DefaultAccountInterface.md) + +## Properties + +### address + +• `Private` **address**: `CompleteAddress` + +___ + +### authWitnessProvider + +• `Private` **authWitnessProvider**: `AuthWitnessProvider` + +___ + +### chainId + +• `Private` **chainId**: `Fr` + +___ + +### entrypoint + +• `Protected` **entrypoint**: `EntrypointInterface` + +___ + +### version + +• `Private` **version**: `Fr` + +## Methods + +### createAuthWit + +▸ **createAuthWit**(`messageHash`): `Promise`\<`AuthWitness`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `messageHash` | `Fr` | + +#### Returns + +`Promise`\<`AuthWitness`\> + +#### Implementation of + +AccountInterface.createAuthWit + +___ + +### createTxExecutionRequest + +▸ **createTxExecutionRequest**(`exec`, `fee`, `options`): `Promise`\<`TxExecutionRequest`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `exec` | `ExecutionPayload` | +| `fee` | `FeeOptions` | +| `options` | `TxExecutionOptions` | + +#### Returns + +`Promise`\<`TxExecutionRequest`\> + +#### Implementation of + +AccountInterface.createTxExecutionRequest + +___ + +### getAddress + +▸ **getAddress**(): `AztecAddress` + +#### Returns + +`AztecAddress` + +#### Implementation of + +AccountInterface.getAddress + +___ + +### getChainId + +▸ **getChainId**(): `Fr` + +#### Returns + +`Fr` + +#### Implementation of + +AccountInterface.getChainId + +___ + +### getCompleteAddress + +▸ **getCompleteAddress**(): `CompleteAddress` + +#### Returns + +`CompleteAddress` + +#### Implementation of + +AccountInterface.getCompleteAddress + +___ + +### getVersion + +▸ **getVersion**(): `Fr` + +#### Returns + +`Fr` + +#### Implementation of + +AccountInterface.getVersion diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/ecdsa.EcdsaKAccountContract.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/ecdsa.EcdsaKAccountContract.md new file mode 100644 index 000000000000..d3b53c1f12a3 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/ecdsa.EcdsaKAccountContract.md @@ -0,0 +1,107 @@ +--- +id: "ecdsa.EcdsaKAccountContract" +title: "Class: EcdsaKAccountContract" +sidebar_label: "EcdsaKAccountContract" +custom_edit_url: null +--- + +[ecdsa](../modules/ecdsa.md).EcdsaKAccountContract + +Account contract that authenticates transactions using ECDSA signatures +verified against a secp256k1 public key stored in an immutable encrypted note. +Eagerly loads the contract artifact + +## Hierarchy + +- `EcdsaKBaseAccountContract` + + ↳ **`EcdsaKAccountContract`** + +## Constructors + +### constructor + +• **new EcdsaKAccountContract**(`signingPrivateKey`): [`EcdsaKAccountContract`](ecdsa.EcdsaKAccountContract.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `signingPrivateKey` | `Buffer`\<`ArrayBufferLike`\> | + +#### Returns + +[`EcdsaKAccountContract`](ecdsa.EcdsaKAccountContract.md) + +#### Overrides + +EcdsaKBaseAccountContract.constructor + +## Methods + +### getAuthWitnessProvider + +▸ **getAuthWitnessProvider**(`_address`): `AuthWitnessProvider` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `_address` | `CompleteAddress` | + +#### Returns + +`AuthWitnessProvider` + +#### Inherited from + +EcdsaKBaseAccountContract.getAuthWitnessProvider + +___ + +### getContractArtifact + +▸ **getContractArtifact**(): `Promise`\<`ContractArtifact`\> + +#### Returns + +`Promise`\<`ContractArtifact`\> + +#### Overrides + +EcdsaKBaseAccountContract.getContractArtifact + +___ + +### getDeploymentFunctionAndArgs + +▸ **getDeploymentFunctionAndArgs**(): `Promise`\<\{ `constructorArgs`: `Buffer`\<`ArrayBufferLike`\>[] ; `constructorName`: `string` = 'constructor' }\> + +#### Returns + +`Promise`\<\{ `constructorArgs`: `Buffer`\<`ArrayBufferLike`\>[] ; `constructorName`: `string` = 'constructor' }\> + +#### Inherited from + +EcdsaKBaseAccountContract.getDeploymentFunctionAndArgs + +___ + +### getInterface + +▸ **getInterface**(`address`, `nodeInfo`): `AccountInterface` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `address` | `CompleteAddress` | +| `nodeInfo` | `NodeInfo` | + +#### Returns + +`AccountInterface` + +#### Inherited from + +EcdsaKBaseAccountContract.getInterface diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/ecdsa.EcdsaRAccountContract.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/ecdsa.EcdsaRAccountContract.md new file mode 100644 index 000000000000..caa361538ef0 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/ecdsa.EcdsaRAccountContract.md @@ -0,0 +1,107 @@ +--- +id: "ecdsa.EcdsaRAccountContract" +title: "Class: EcdsaRAccountContract" +sidebar_label: "EcdsaRAccountContract" +custom_edit_url: null +--- + +[ecdsa](../modules/ecdsa.md).EcdsaRAccountContract + +Account contract that authenticates transactions using ECDSA signatures +verified against a secp256k1 public key stored in an immutable encrypted note. +Eagerly loads the contract artifact + +## Hierarchy + +- `EcdsaRBaseAccountContract` + + ↳ **`EcdsaRAccountContract`** + +## Constructors + +### constructor + +• **new EcdsaRAccountContract**(`signingPrivateKey`): [`EcdsaRAccountContract`](ecdsa.EcdsaRAccountContract.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `signingPrivateKey` | `Buffer`\<`ArrayBufferLike`\> | + +#### Returns + +[`EcdsaRAccountContract`](ecdsa.EcdsaRAccountContract.md) + +#### Overrides + +EcdsaRBaseAccountContract.constructor + +## Methods + +### getAuthWitnessProvider + +▸ **getAuthWitnessProvider**(`_address`): `AuthWitnessProvider` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `_address` | `CompleteAddress` | + +#### Returns + +`AuthWitnessProvider` + +#### Inherited from + +EcdsaRBaseAccountContract.getAuthWitnessProvider + +___ + +### getContractArtifact + +▸ **getContractArtifact**(): `Promise`\<`ContractArtifact`\> + +#### Returns + +`Promise`\<`ContractArtifact`\> + +#### Overrides + +EcdsaRBaseAccountContract.getContractArtifact + +___ + +### getDeploymentFunctionAndArgs + +▸ **getDeploymentFunctionAndArgs**(): `Promise`\<\{ `constructorArgs`: `Buffer`\<`ArrayBufferLike`\>[] ; `constructorName`: `string` = 'constructor' }\> + +#### Returns + +`Promise`\<\{ `constructorArgs`: `Buffer`\<`ArrayBufferLike`\>[] ; `constructorName`: `string` = 'constructor' }\> + +#### Inherited from + +EcdsaRBaseAccountContract.getDeploymentFunctionAndArgs + +___ + +### getInterface + +▸ **getInterface**(`address`, `nodeInfo`): `AccountInterface` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `address` | `CompleteAddress` | +| `nodeInfo` | `NodeInfo` | + +#### Returns + +`AccountInterface` + +#### Inherited from + +EcdsaRBaseAccountContract.getInterface diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/ecdsa.EcdsaRSSHAccountContract.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/ecdsa.EcdsaRSSHAccountContract.md new file mode 100644 index 000000000000..88a0c22852a5 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/ecdsa.EcdsaRSSHAccountContract.md @@ -0,0 +1,110 @@ +--- +id: "ecdsa.EcdsaRSSHAccountContract" +title: "Class: EcdsaRSSHAccountContract" +sidebar_label: "EcdsaRSSHAccountContract" +custom_edit_url: null +--- + +[ecdsa](../modules/ecdsa.md).EcdsaRSSHAccountContract + +Account contract that authenticates transactions using ECDSA signatures +verified against a secp256r1 public key stored in an immutable encrypted note. +Since this implementation relays signatures to an SSH agent, we provide the +public key here not for signature verification, but to identify actual identity +that will be used to sign authwitnesses. +Eagerly loads the contract artifact + +## Hierarchy + +- `EcdsaRSSHBaseAccountContract` + + ↳ **`EcdsaRSSHAccountContract`** + +## Constructors + +### constructor + +• **new EcdsaRSSHAccountContract**(`signingPrivateKey`): [`EcdsaRSSHAccountContract`](ecdsa.EcdsaRSSHAccountContract.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `signingPrivateKey` | `Buffer`\<`ArrayBufferLike`\> | + +#### Returns + +[`EcdsaRSSHAccountContract`](ecdsa.EcdsaRSSHAccountContract.md) + +#### Overrides + +EcdsaRSSHBaseAccountContract.constructor + +## Methods + +### getAuthWitnessProvider + +▸ **getAuthWitnessProvider**(`_address`): `AuthWitnessProvider` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `_address` | `CompleteAddress` | + +#### Returns + +`AuthWitnessProvider` + +#### Inherited from + +EcdsaRSSHBaseAccountContract.getAuthWitnessProvider + +___ + +### getContractArtifact + +▸ **getContractArtifact**(): `Promise`\<`ContractArtifact`\> + +#### Returns + +`Promise`\<`ContractArtifact`\> + +#### Overrides + +EcdsaRSSHBaseAccountContract.getContractArtifact + +___ + +### getDeploymentFunctionAndArgs + +▸ **getDeploymentFunctionAndArgs**(): `Promise`\<\{ `constructorArgs`: `Buffer`\<`ArrayBufferLike`\>[] ; `constructorName`: `string` = 'constructor' }\> + +#### Returns + +`Promise`\<\{ `constructorArgs`: `Buffer`\<`ArrayBufferLike`\>[] ; `constructorName`: `string` = 'constructor' }\> + +#### Inherited from + +EcdsaRSSHBaseAccountContract.getDeploymentFunctionAndArgs + +___ + +### getInterface + +▸ **getInterface**(`address`, `nodeInfo`): `AccountInterface` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `address` | `CompleteAddress` | +| `nodeInfo` | `NodeInfo` | + +#### Returns + +`AccountInterface` + +#### Inherited from + +EcdsaRSSHBaseAccountContract.getInterface diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/schnorr.SchnorrAccountContract.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/schnorr.SchnorrAccountContract.md new file mode 100644 index 000000000000..ba045b038046 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/schnorr.SchnorrAccountContract.md @@ -0,0 +1,107 @@ +--- +id: "schnorr.SchnorrAccountContract" +title: "Class: SchnorrAccountContract" +sidebar_label: "SchnorrAccountContract" +custom_edit_url: null +--- + +[schnorr](../modules/schnorr.md).SchnorrAccountContract + +Account contract that authenticates transactions using Schnorr signatures +verified against a Grumpkin public key stored in an immutable encrypted note. +Eagerly loads the contract artifact + +## Hierarchy + +- `SchnorrBaseAccountContract` + + ↳ **`SchnorrAccountContract`** + +## Constructors + +### constructor + +• **new SchnorrAccountContract**(`signingPrivateKey`): [`SchnorrAccountContract`](schnorr.SchnorrAccountContract.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `signingPrivateKey` | `Fq` | + +#### Returns + +[`SchnorrAccountContract`](schnorr.SchnorrAccountContract.md) + +#### Overrides + +SchnorrBaseAccountContract.constructor + +## Methods + +### getAuthWitnessProvider + +▸ **getAuthWitnessProvider**(`_address`): `AuthWitnessProvider` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `_address` | `CompleteAddress` | + +#### Returns + +`AuthWitnessProvider` + +#### Inherited from + +SchnorrBaseAccountContract.getAuthWitnessProvider + +___ + +### getContractArtifact + +▸ **getContractArtifact**(): `Promise`\<`ContractArtifact`\> + +#### Returns + +`Promise`\<`ContractArtifact`\> + +#### Overrides + +SchnorrBaseAccountContract.getContractArtifact + +___ + +### getDeploymentFunctionAndArgs + +▸ **getDeploymentFunctionAndArgs**(): `Promise`\<\{ `constructorArgs`: `Fr`[] ; `constructorName`: `string` = 'constructor' }\> + +#### Returns + +`Promise`\<\{ `constructorArgs`: `Fr`[] ; `constructorName`: `string` = 'constructor' }\> + +#### Inherited from + +SchnorrBaseAccountContract.getDeploymentFunctionAndArgs + +___ + +### getInterface + +▸ **getInterface**(`address`, `nodeInfo`): `AccountInterface` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `address` | `CompleteAddress` | +| `nodeInfo` | `NodeInfo` | + +#### Returns + +`AccountInterface` + +#### Inherited from + +SchnorrBaseAccountContract.getInterface diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/single_key.SingleKeyAccountContract.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/single_key.SingleKeyAccountContract.md new file mode 100644 index 000000000000..d9b230516512 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/classes/single_key.SingleKeyAccountContract.md @@ -0,0 +1,107 @@ +--- +id: "single_key.SingleKeyAccountContract" +title: "Class: SingleKeyAccountContract" +sidebar_label: "SingleKeyAccountContract" +custom_edit_url: null +--- + +[single\_key](../modules/single_key.md).SingleKeyAccountContract + +Account contract that authenticates transactions using Schnorr signatures verified against +the note encryption key, relying on a single private key for both encryption and authentication. +Eagerly loads the contract artifact + +## Hierarchy + +- `SingleKeyBaseAccountContract` + + ↳ **`SingleKeyAccountContract`** + +## Constructors + +### constructor + +• **new SingleKeyAccountContract**(`signingPrivateKey`): [`SingleKeyAccountContract`](single_key.SingleKeyAccountContract.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `signingPrivateKey` | `Fq` | + +#### Returns + +[`SingleKeyAccountContract`](single_key.SingleKeyAccountContract.md) + +#### Overrides + +SingleKeyBaseAccountContract.constructor + +## Methods + +### getAuthWitnessProvider + +▸ **getAuthWitnessProvider**(`account`): `AuthWitnessProvider` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `account` | `CompleteAddress` | + +#### Returns + +`AuthWitnessProvider` + +#### Inherited from + +SingleKeyBaseAccountContract.getAuthWitnessProvider + +___ + +### getContractArtifact + +▸ **getContractArtifact**(): `Promise`\<`ContractArtifact`\> + +#### Returns + +`Promise`\<`ContractArtifact`\> + +#### Overrides + +SingleKeyBaseAccountContract.getContractArtifact + +___ + +### getDeploymentFunctionAndArgs + +▸ **getDeploymentFunctionAndArgs**(): `Promise`\<`undefined`\> + +#### Returns + +`Promise`\<`undefined`\> + +#### Inherited from + +SingleKeyBaseAccountContract.getDeploymentFunctionAndArgs + +___ + +### getInterface + +▸ **getInterface**(`address`, `nodeInfo`): `AccountInterface` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `address` | `CompleteAddress` | +| `nodeInfo` | `NodeInfo` | + +#### Returns + +`AccountInterface` + +#### Inherited from + +SingleKeyBaseAccountContract.getInterface diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/index.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/index.md new file mode 100644 index 000000000000..0c19cb267f7e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/index.md @@ -0,0 +1,16 @@ +--- +id: "index" +title: "@aztec/accounts" +sidebar_label: "Table of Contents" +sidebar_position: 0.5 +hide_table_of_contents: true +custom_edit_url: null +--- + +## Modules + +- [defaults](modules/defaults.md) +- [ecdsa](modules/ecdsa.md) +- [schnorr](modules/schnorr.md) +- [single\_key](modules/single_key.md) +- [testing](modules/testing.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/interfaces/_category_.yml b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/interfaces/_category_.yml new file mode 100644 index 000000000000..43bec88cfa0a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/interfaces/_category_.yml @@ -0,0 +1,2 @@ +label: "Interfaces" +position: 4 \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/interfaces/testing.InitialAccountData.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/interfaces/testing.InitialAccountData.md new file mode 100644 index 000000000000..7b2757593317 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/interfaces/testing.InitialAccountData.md @@ -0,0 +1,42 @@ +--- +id: "testing.InitialAccountData" +title: "Interface: InitialAccountData" +sidebar_label: "InitialAccountData" +custom_edit_url: null +--- + +[testing](../modules/testing.md).InitialAccountData + +Data for generating an initial account. + +## Properties + +### address + +• **address**: `AztecAddress` + +Address of the schnorr account contract. + +___ + +### salt + +• **salt**: `Fr` + +Contract address salt. + +___ + +### secret + +• **secret**: `Fr` + +Secret to derive the keys for the account. + +___ + +### signingKey + +• **signingKey**: `Fq` + +Signing key od the account. diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/_category_.yml b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/_category_.yml new file mode 100644 index 000000000000..63f9c4e40160 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/_category_.yml @@ -0,0 +1,2 @@ +label: "Modules" +position: 1 \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/defaults.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/defaults.md new file mode 100644 index 000000000000..9252b70f72ae --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/defaults.md @@ -0,0 +1,16 @@ +--- +id: "defaults" +title: "Module: defaults" +sidebar_label: "defaults" +sidebar_position: 0 +custom_edit_url: null +--- + +The `@aztec/accounts/defaults` export provides the base class [DefaultAccountContract](../classes/defaults.DefaultAccountContract.md) for implementing account contracts that use the default entrypoint payload module. + +Read more in [Writing an account contract](https://docs.aztec.network/developers/tutorials/codealong/contract_tutorials/write_accounts_contract). + +## Classes + +- [DefaultAccountContract](../classes/defaults.DefaultAccountContract.md) +- [DefaultAccountInterface](../classes/defaults.DefaultAccountInterface.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/ecdsa.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/ecdsa.md new file mode 100644 index 000000000000..98a99dcece1b --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/ecdsa.md @@ -0,0 +1,160 @@ +--- +id: "ecdsa" +title: "Module: ecdsa" +sidebar_label: "ecdsa" +sidebar_position: 0 +custom_edit_url: null +--- + +## Classes + +- [EcdsaKAccountContract](../classes/ecdsa.EcdsaKAccountContract.md) +- [EcdsaRAccountContract](../classes/ecdsa.EcdsaRAccountContract.md) +- [EcdsaRSSHAccountContract](../classes/ecdsa.EcdsaRSSHAccountContract.md) + +## Variables + +### EcdsaKAccountContractArtifact + +• `Const` **EcdsaKAccountContractArtifact**: `ContractArtifact` + +___ + +### EcdsaRAccountContractArtifact + +• `Const` **EcdsaRAccountContractArtifact**: `ContractArtifact` + +## Functions + +### getEcdsaKAccount + +▸ **getEcdsaKAccount**(`pxe`, `secretKey`, `signingPrivateKey`, `salt?`): `Promise`\<`AccountManager`\> + +Creates an Account that relies on an ECDSA signing key for authentication. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | An PXE server instance. | +| `secretKey` | `Fr` | Secret key used to derive all the keystore keys. | +| `signingPrivateKey` | `Buffer`\<`ArrayBufferLike`\> | Secp256k1 key used for signing transactions. | +| `salt?` | `Salt` | Deployment salt. | + +#### Returns + +`Promise`\<`AccountManager`\> + +An account manager initialized with the account contract and its deployment params + +___ + +### getEcdsaKWallet + +▸ **getEcdsaKWallet**(`pxe`, `address`, `signingPrivateKey`): `Promise`\<`AccountWallet`\> + +Gets a wallet for an already registered account using ECDSA signatures. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | An PXE server instance. | +| `address` | `AztecAddress` | Address for the account. | +| `signingPrivateKey` | `Buffer`\<`ArrayBufferLike`\> | ECDSA key used for signing transactions. | + +#### Returns + +`Promise`\<`AccountWallet`\> + +A wallet for this account that can be used to interact with a contract instance. + +___ + +### getEcdsaRAccount + +▸ **getEcdsaRAccount**(`pxe`, `secretKey`, `signingPrivateKey`, `salt?`): `Promise`\<`AccountManager`\> + +Creates an Account that relies on an ECDSA signing key for authentication. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | An PXE server instance. | +| `secretKey` | `Fr` | Secret key used to derive all the keystore keys. | +| `signingPrivateKey` | `Buffer`\<`ArrayBufferLike`\> | Secp256k1 key used for signing transactions. | +| `salt?` | `Salt` | Deployment salt. | + +#### Returns + +`Promise`\<`AccountManager`\> + +An account manager initialized with the account contract and its deployment params + +___ + +### getEcdsaRSSHAccount + +▸ **getEcdsaRSSHAccount**(`pxe`, `secretKey`, `signingPublicKey`, `salt?`): `Promise`\<`AccountManager`\> + +Creates an Account that relies on an ECDSA signing key for authentication. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | An PXE server instance. | +| `secretKey` | `Fr` | Secret key used to derive all the keystore keys. | +| `signingPublicKey` | `Buffer`\<`ArrayBufferLike`\> | Secp2561 key used to identify its corresponding private key in the SSH Agent. | +| `salt?` | `Salt` | Deployment salt. | + +#### Returns + +`Promise`\<`AccountManager`\> + +An account manager initialized with the account contract and its deployment params + +___ + +### getEcdsaRSSHWallet + +▸ **getEcdsaRSSHWallet**(`pxe`, `address`, `signingPublicKey`): `Promise`\<`AccountWallet`\> + +Gets a wallet for an already registered account using ECDSA signatures. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | An PXE server instance. | +| `address` | `AztecAddress` | Address for the account. | +| `signingPublicKey` | `Buffer`\<`ArrayBufferLike`\> | - | + +#### Returns + +`Promise`\<`AccountWallet`\> + +A wallet for this account that can be used to interact with a contract instance. + +___ + +### getEcdsaRWallet + +▸ **getEcdsaRWallet**(`pxe`, `address`, `signingPrivateKey`): `Promise`\<`AccountWallet`\> + +Gets a wallet for an already registered account using ECDSA signatures. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | An PXE server instance. | +| `address` | `AztecAddress` | Address for the account. | +| `signingPrivateKey` | `Buffer`\<`ArrayBufferLike`\> | ECDSA key used for signing transactions. | + +#### Returns + +`Promise`\<`AccountWallet`\> + +A wallet for this account that can be used to interact with a contract instance. diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/schnorr.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/schnorr.md new file mode 100644 index 000000000000..cbd6dcd7734e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/schnorr.md @@ -0,0 +1,108 @@ +--- +id: "schnorr" +title: "Module: schnorr" +sidebar_label: "schnorr" +sidebar_position: 0 +custom_edit_url: null +--- + +The `@aztec/accounts/schnorr` export provides an account contract implementation that uses Schnorr signatures with a Grumpkin key for authentication, and a separate Grumpkin key for encryption. +This is the suggested account contract type for most use cases within Aztec. + +## Classes + +- [SchnorrAccountContract](../classes/schnorr.SchnorrAccountContract.md) + +## Variables + +### SchnorrAccountContractArtifact + +• `Const` **SchnorrAccountContractArtifact**: `ContractArtifact` + +## Functions + +### getSchnorrAccount + +▸ **getSchnorrAccount**(`pxe`, `secretKey`, `signingPrivateKey`, `salt?`): `Promise`\<`AccountManager`\> + +Creates an Account Manager that relies on a Grumpkin signing key for authentication. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | An PXE server instance. | +| `secretKey` | `Fr` | Secret key used to derive all the keystore keys. | +| `signingPrivateKey` | `Fq` | Grumpkin key used for signing transactions. | +| `salt?` | `Salt` | Deployment salt. | + +#### Returns + +`Promise`\<`AccountManager`\> + +An account manager initialized with the account contract and its deployment params + +___ + +### getSchnorrAccountContractAddress + +▸ **getSchnorrAccountContractAddress**(`secret`, `salt`, `signingPrivateKey?`): `Promise`\<`AztecAddress`\> + +Compute the address of a schnorr account contract. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `secret` | `Fr` | A seed for deriving the signing key and public keys. | +| `salt` | `Fr` | The contract address salt. | +| `signingPrivateKey?` | `Fq` | A specific signing private key that's not derived from the secret. | + +#### Returns + +`Promise`\<`AztecAddress`\> + +___ + +### getSchnorrWallet + +▸ **getSchnorrWallet**(`pxe`, `address`, `signingPrivateKey`): `Promise`\<`AccountWallet`\> + +Gets a wallet for an already registered account using Schnorr signatures. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | An PXE server instance. | +| `address` | `AztecAddress` | Address for the account. | +| `signingPrivateKey` | `Fq` | Grumpkin key used for signing transactions. | + +#### Returns + +`Promise`\<`AccountWallet`\> + +A wallet for this account that can be used to interact with a contract instance. + +___ + +### getSchnorrWalletWithSecretKey + +▸ **getSchnorrWalletWithSecretKey**(`pxe`, `secretKey`, `signingPrivateKey`, `salt`): `Promise`\<`AccountWalletWithSecretKey`\> + +Gets a wallet for an already registered account using Schnorr signatures. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | An PXE server instance. | +| `secretKey` | `Fr` | Secret key used to derive all the keystore keys. | +| `signingPrivateKey` | `Fq` | Grumpkin key used for signing transactions. | +| `salt` | `Salt` | Deployment salt. | + +#### Returns + +`Promise`\<`AccountWalletWithSecretKey`\> + +A wallet for this account that can be used to interact with a contract instance. diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/single_key.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/single_key.md new file mode 100644 index 000000000000..e376e50fbb03 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/single_key.md @@ -0,0 +1,64 @@ +--- +id: "single_key" +title: "Module: single_key" +sidebar_label: "single_key" +sidebar_position: 0 +custom_edit_url: null +--- + +The `@aztec/accounts/single_key` export provides a testing account contract implementation that uses a single Grumpkin key for both authentication and encryption. +It is not recommended to use this account type in production. + +## Classes + +- [SingleKeyAccountContract](../classes/single_key.SingleKeyAccountContract.md) + +## Variables + +### SchnorrSingleKeyAccountContractArtifact + +• `Const` **SchnorrSingleKeyAccountContractArtifact**: `ContractArtifact` + +## Functions + +### getUnsafeSchnorrAccount + +▸ **getUnsafeSchnorrAccount**(`pxe`, `secretKey`, `salt?`): `Promise`\<`AccountManager`\> + +Creates an Account that uses the same Grumpkin key for encryption and authentication. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | An PXE server instance. | +| `secretKey` | `Fr` | Secret key used to derive all the keystore keys (in this case also used to get signing key). | +| `salt?` | `Salt` | Deployment salt. | + +#### Returns + +`Promise`\<`AccountManager`\> + +An account manager initialized with the account contract and its deployment params + +___ + +### getUnsafeSchnorrWallet + +▸ **getUnsafeSchnorrWallet**(`pxe`, `address`, `signingPrivateKey`): `Promise`\<`AccountWallet`\> + +Gets a wallet for an already registered account using Schnorr signatures with a single key for encryption and authentication. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | An PXE server instance. | +| `address` | `AztecAddress` | Address for the account. | +| `signingPrivateKey` | `Fq` | Grumpkin key used for note encryption and signing transactions. | + +#### Returns + +`Promise`\<`AccountWallet`\> + +A wallet for this account that can be used to interact with a contract instance. diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/testing.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/testing.md new file mode 100644 index 000000000000..604fc19f388f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/accounts/modules/testing.md @@ -0,0 +1,193 @@ +--- +id: "testing" +title: "Module: testing" +sidebar_label: "testing" +sidebar_position: 0 +custom_edit_url: null +--- + +The `@aztec/accounts/testing` export provides utility methods for testing, in particular in a Sandbox environment. + +Use [getInitialTestAccountsWallets](testing.md#getinitialtestaccountswallets) to obtain a list of wallets for the Sandbox pre-seeded accounts. + +## Interfaces + +- [InitialAccountData](../interfaces/testing.InitialAccountData.md) + +## Variables + +### INITIAL\_TEST\_ACCOUNT\_SALTS + +• `Const` **INITIAL\_TEST\_ACCOUNT\_SALTS**: `Fr`[] + +___ + +### INITIAL\_TEST\_ENCRYPTION\_KEYS + +• `Const` **INITIAL\_TEST\_ENCRYPTION\_KEYS**: `Fq`[] + +___ + +### INITIAL\_TEST\_SECRET\_KEYS + +• `Const` **INITIAL\_TEST\_SECRET\_KEYS**: `Fr`[] + +___ + +### INITIAL\_TEST\_SIGNING\_KEYS + +• `Const` **INITIAL\_TEST\_SIGNING\_KEYS**: `Fq`[] = `INITIAL_TEST_ENCRYPTION_KEYS` + +## Functions + +### deployFundedSchnorrAccount + +▸ **deployFundedSchnorrAccount**(`pxe`, `account`, `opts?`, `waitForProvenOptions?`): `Promise`\<`AccountManager`\> + +Deploy schnorr account contract. +It will pay for the fee for the deployment itself. So it must be funded with the prefilled public data. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `pxe` | `PXE` | +| `account` | `DeployAccountData` | +| `opts` | `WaitOpts` & \{ `skipClassRegistration?`: `boolean` } | +| `waitForProvenOptions?` | `WaitForProvenOpts` | + +#### Returns + +`Promise`\<`AccountManager`\> + +___ + +### deployFundedSchnorrAccounts + +▸ **deployFundedSchnorrAccounts**(`pxe`, `accounts`, `opts?`, `waitForProvenOptions?`): `Promise`\<`AccountManager`[]\> + +Deploy schnorr account contracts. +They will pay for the fees for the deployment themselves. So they must be funded with the prefilled public data. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `pxe` | `PXE` | +| `accounts` | `DeployAccountData`[] | +| `opts` | `WaitOpts` & \{ `skipClassRegistration?`: `boolean` } | +| `waitForProvenOptions?` | `WaitForProvenOpts` | + +#### Returns + +`Promise`\<`AccountManager`[]\> + +___ + +### generateSchnorrAccounts + +▸ **generateSchnorrAccounts**(`numberOfAccounts`): `Promise`\<\{ `address`: `AztecAddress` ; `salt`: `Fr` ; `secret`: `Fr` ; `signingKey`: `Fq` }[]\> + +Generate a fixed amount of random schnorr account contract instance. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `numberOfAccounts` | `number` | + +#### Returns + +`Promise`\<\{ `address`: `AztecAddress` ; `salt`: `Fr` ; `secret`: `Fr` ; `signingKey`: `Fq` }[]\> + +___ + +### getDeployedTestAccounts + +▸ **getDeployedTestAccounts**(`pxe`): `Promise`\<[`InitialAccountData`](../interfaces/testing.InitialAccountData.md)[]\> + +Queries a PXE for it's registered accounts. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | PXE instance. | + +#### Returns + +`Promise`\<[`InitialAccountData`](../interfaces/testing.InitialAccountData.md)[]\> + +A set of key data for each of the initial accounts. + +___ + +### getDeployedTestAccountsWallets + +▸ **getDeployedTestAccountsWallets**(`pxe`): `Promise`\<`AccountWalletWithSecretKey`[]\> + +Queries a PXE for it's registered accounts and returns wallets for those accounts using keys in the initial test accounts. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | PXE instance. | + +#### Returns + +`Promise`\<`AccountWalletWithSecretKey`[]\> + +A set of AccountWallet implementations for each of the initial accounts. + +___ + +### getInitialTestAccounts + +▸ **getInitialTestAccounts**(): `Promise`\<[`InitialAccountData`](../interfaces/testing.InitialAccountData.md)[]\> + +Gets the basic information for initial test accounts. + +#### Returns + +`Promise`\<[`InitialAccountData`](../interfaces/testing.InitialAccountData.md)[]\> + +___ + +### getInitialTestAccountsManagers + +▸ **getInitialTestAccountsManagers**(`pxe`): `Promise`\<`AccountManager`[]\> + +Gets a collection of account managers for the Aztec accounts that are initially stored in the test environment. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | PXE instance. | + +#### Returns + +`Promise`\<`AccountManager`[]\> + +A set of AccountManager implementations for each of the initial accounts. + +___ + +### getInitialTestAccountsWallets + +▸ **getInitialTestAccountsWallets**(`pxe`): `Promise`\<`AccountWalletWithSecretKey`[]\> + +Gets a collection of wallets for the Aztec accounts that are initially stored in the test environment. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pxe` | `PXE` | PXE instance. | + +#### Returns + +`Promise`\<`AccountWalletWithSecretKey`[]\> + +A set of AccountWallet implementations for each of the initial accounts. diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/_category_.yml b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/_category_.yml new file mode 100644 index 000000000000..f5c77d17cdcf --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/_category_.yml @@ -0,0 +1 @@ +label: "Aztec.js" \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/index.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/index.md new file mode 100644 index 000000000000..2961da2dd54c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/index.md @@ -0,0 +1,46 @@ +--- +id: "index" +title: "@aztec/aztec.js" +sidebar_label: "Table of Contents" +sidebar_position: 0.5 +hide_table_of_contents: true +custom_edit_url: null +--- + +The `account` module provides utilities for managing accounts. The AccountManager class +allows to deploy and register a fresh account, or to obtain a `Wallet` instance out of an account +already deployed. Use the `@aztec/accounts` package to load default account implementations that rely +on ECDSA or Schnorr signatures. + +## Interfaces + +- [AccountContract](interfaces/AccountContract.md) +- [AccountInterface](interfaces/AccountInterface.md) + +## Type Aliases + +### Salt + +Ƭ **Salt**: `Fr` \| `number` \| `bigint` + +A contract deployment salt. + +## Functions + +### getAccountContractAddress + +▸ **getAccountContractAddress**(`accountContract`, `secret`, `salt`): `Promise`\<`AztecAddress`\> + +Compute the address of an account contract from secret and salt. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `accountContract` | [`AccountContract`](interfaces/AccountContract.md) | +| `secret` | `Fr` | +| `salt` | `Fr` | + +#### Returns + +`Promise`\<`AztecAddress`\> diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/interfaces/AccountContract.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/interfaces/AccountContract.md new file mode 100644 index 000000000000..8e86c77e9efa --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/interfaces/AccountContract.md @@ -0,0 +1,75 @@ +--- +id: "AccountContract" +title: "Interface: AccountContract" +sidebar_label: "AccountContract" +sidebar_position: 0 +custom_edit_url: null +--- + +An account contract instance. Knows its artifact, deployment arguments, how to create +transaction execution requests out of function calls, and how to authorize actions. + +## Methods + +### getAuthWitnessProvider + +▸ **getAuthWitnessProvider**(`address`): `AuthWitnessProvider` + +Returns the auth witness provider for the given address. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `address` | `CompleteAddress` | Address for which to create auth witnesses. | + +#### Returns + +`AuthWitnessProvider` + +___ + +### getContractArtifact + +▸ **getContractArtifact**(): `Promise`\<`ContractArtifact`\> + +Returns the artifact of this account contract. + +#### Returns + +`Promise`\<`ContractArtifact`\> + +___ + +### getDeploymentFunctionAndArgs + +▸ **getDeploymentFunctionAndArgs**(): `Promise`\<`undefined` \| \{ `constructorArgs`: `any`[] ; `constructorName`: `string` }\> + +Returns the deployment function name and arguments for this instance, or undefined if this contract does not require deployment. + +#### Returns + +`Promise`\<`undefined` \| \{ `constructorArgs`: `any`[] ; `constructorName`: `string` }\> + +___ + +### getInterface + +▸ **getInterface**(`address`, `nodeInfo`): [`AccountInterface`](AccountInterface.md) + +Returns the account interface for this account contract given a deployment at the provided address. +The account interface is responsible for assembling tx requests given requested function calls, and +for creating signed auth witnesses given action identifiers (message hashes). + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `address` | `CompleteAddress` | Address where this account contract is deployed. | +| `nodeInfo` | `NodeInfo` | Info on the chain where it is deployed. | + +#### Returns + +[`AccountInterface`](AccountInterface.md) + +An account interface instance for creating tx requests and authorizing actions. diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/interfaces/AccountInterface.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/interfaces/AccountInterface.md new file mode 100644 index 000000000000..4fd453878807 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/interfaces/AccountInterface.md @@ -0,0 +1,116 @@ +--- +id: "AccountInterface" +title: "Interface: AccountInterface" +sidebar_label: "AccountInterface" +sidebar_position: 0 +custom_edit_url: null +--- + +Handler for interfacing with an account. Knows how to create transaction execution +requests and authorize actions for its corresponding account. + +## Hierarchy + +- `EntrypointInterface` + +- `AuthWitnessProvider` + + ↳ **`AccountInterface`** + +## Methods + +### createAuthWit + +▸ **createAuthWit**(`messageHash`): `Promise`\<`AuthWitness`\> + +Computes an authentication witness from either a message hash + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `messageHash` | `Fr` \| `Buffer`\<`ArrayBufferLike`\> | The message hash to approve | + +#### Returns + +`Promise`\<`AuthWitness`\> + +The authentication witness + +#### Inherited from + +AuthWitnessProvider.createAuthWit + +___ + +### createTxExecutionRequest + +▸ **createTxExecutionRequest**(`exec`, `fee`, `options`): `Promise`\<`TxExecutionRequest`\> + +Generates an execution request out of set of function calls. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `exec` | `ExecutionPayload` | The execution intents to be run. | +| `fee` | `FeeOptions` | The fee options for the transaction. | +| `options` | `TxExecutionOptions` | Transaction nonce and whether the transaction is cancellable. | + +#### Returns + +`Promise`\<`TxExecutionRequest`\> + +The authenticated transaction execution request. + +#### Inherited from + +EntrypointInterface.createTxExecutionRequest + +___ + +### getAddress + +▸ **getAddress**(): `AztecAddress` + +Returns the address for this account. + +#### Returns + +`AztecAddress` + +___ + +### getChainId + +▸ **getChainId**(): `Fr` + +Returns the chain id for this account + +#### Returns + +`Fr` + +___ + +### getCompleteAddress + +▸ **getCompleteAddress**(): `CompleteAddress` + +Returns the complete address for this account. + +#### Returns + +`CompleteAddress` + +___ + +### getVersion + +▸ **getVersion**(): `Fr` + +Returns the rollup version for this account + +#### Returns + +`Fr` diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/interfaces/_category_.yml b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/interfaces/_category_.yml new file mode 100644 index 000000000000..43bec88cfa0a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/aztec-js/interfaces/_category_.yml @@ -0,0 +1,2 @@ +label: "Interfaces" +position: 4 \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/_category_.yml b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/_category_.yml new file mode 100644 index 000000000000..3095e3e72ff8 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/_category_.yml @@ -0,0 +1 @@ +label: "Private Execution Environment (PXE)" \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/enums/EventType.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/enums/EventType.md new file mode 100644 index 000000000000..338e46d1034e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/enums/EventType.md @@ -0,0 +1,21 @@ +--- +id: "EventType" +title: "Enumeration: EventType" +sidebar_label: "EventType" +sidebar_position: 0 +custom_edit_url: null +--- + +This is used in getting events via the filter + +## Enumeration Members + +### Encrypted + +• **Encrypted** = ``"Encrypted"`` + +___ + +### Unencrypted + +• **Unencrypted** = ``"Unencrypted"`` diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/enums/_category_.yml b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/enums/_category_.yml new file mode 100644 index 000000000000..1687a9e03fd7 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/enums/_category_.yml @@ -0,0 +1,2 @@ +label: "Enumerations" +position: 2 \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/index.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/index.md new file mode 100644 index 000000000000..390faf79398f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/index.md @@ -0,0 +1,38 @@ +--- +id: "index" +title: "@aztec/stdlib" +sidebar_label: "Exports" +sidebar_position: 0.5 +custom_edit_url: null +--- + +## Enumerations + +- [EventType](enums/EventType.md) + +## Interfaces + +- [ContractClassMetadata](interfaces/ContractClassMetadata.md) +- [ContractMetadata](interfaces/ContractMetadata.md) +- [PXE](interfaces/PXE.md) +- [PXEInfo](interfaces/PXEInfo.md) + +## Type Aliases + +### EventMetadataDefinition + +Ƭ **EventMetadataDefinition**: `Object` + +#### Type declaration + +| Name | Type | +| :------ | :------ | +| `abiType` | `AbiType` | +| `eventSelector` | `EventSelector` | +| `fieldNames` | `string`[] | + +## Variables + +### PXESchema + +• `Const` **PXESchema**: `ApiSchemaFor`\<[`PXE`](interfaces/PXE.md)\> diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/ContractClassMetadata.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/ContractClassMetadata.md new file mode 100644 index 000000000000..4a152c2a34a9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/ContractClassMetadata.md @@ -0,0 +1,25 @@ +--- +id: "ContractClassMetadata" +title: "Interface: ContractClassMetadata" +sidebar_label: "ContractClassMetadata" +sidebar_position: 0 +custom_edit_url: null +--- + +## Properties + +### artifact + +• `Optional` **artifact**: `ContractArtifact` + +___ + +### contractClass + +• `Optional` **contractClass**: `ContractClassWithId` + +___ + +### isContractClassPubliclyRegistered + +• **isContractClassPubliclyRegistered**: `boolean` diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/ContractMetadata.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/ContractMetadata.md new file mode 100644 index 000000000000..7bd09ee675a8 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/ContractMetadata.md @@ -0,0 +1,25 @@ +--- +id: "ContractMetadata" +title: "Interface: ContractMetadata" +sidebar_label: "ContractMetadata" +sidebar_position: 0 +custom_edit_url: null +--- + +## Properties + +### contractInstance + +• `Optional` **contractInstance**: `ContractInstanceWithAddress` + +___ + +### isContractInitialized + +• **isContractInitialized**: `boolean` + +___ + +### isContractPubliclyDeployed + +• **isContractPubliclyDeployed**: `boolean` diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/PXE.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/PXE.md new file mode 100644 index 000000000000..91358b1f67a5 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/PXE.md @@ -0,0 +1,723 @@ +--- +id: "PXE" +title: "Interface: PXE" +sidebar_label: "PXE" +sidebar_position: 0 +custom_edit_url: null +--- + +Private eXecution Environment (PXE) runs locally for each user, providing functionality for all the operations +needed to interact with the Aztec network, including account management, private data management, +transaction local simulation, and access to an Aztec node. This interface, as part of a Wallet, +is exposed to dapps for interacting with the network on behalf of the user. + +## Methods + +### getBlock + +▸ **getBlock**(`number`): `Promise`\<`undefined` \| `L2Block`\> + +Get the given block. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `number` | `number` | The block number being requested. | + +#### Returns + +`Promise`\<`undefined` \| `L2Block`\> + +The blocks requested. + +___ + +### getBlockNumber + +▸ **getBlockNumber**(): `Promise`\<`number`\> + +Fetches the current block number. + +#### Returns + +`Promise`\<`number`\> + +The block number. + +___ + +### getContractClassLogs + +▸ **getContractClassLogs**(`filter`): `Promise`\<`GetContractClassLogsResponse`\> + +Gets contract class logs based on the provided filter. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `filter` | `LogFilter` | The filter to apply to the logs. | + +#### Returns + +`Promise`\<`GetContractClassLogsResponse`\> + +The requested logs. + +___ + +### getContractClassMetadata + +▸ **getContractClassMetadata**(`id`, `includeArtifact?`): `Promise`\<[`ContractClassMetadata`](ContractClassMetadata.md)\> + +Returns the contract class metadata given a contract class id. +The metadata consists of its contract class, whether it has been publicly registered, and its artifact. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `id` | `Fr` | Identifier of the class. | +| `includeArtifact?` | `boolean` | Identifier of the class. | + +#### Returns + +`Promise`\<[`ContractClassMetadata`](ContractClassMetadata.md)\> + +- It returns the contract class metadata, with the artifact field being optional, and will only be returned if true is passed in +for `includeArtifact` +TODO(@spalladino): The PXE actually holds artifacts and not classes, what should we return? Also, +should the pxe query the node for contract public info, and merge it with its own definitions? +TODO(@spalladino): This method is strictly needed to decide whether to publicly register a class or not +during a public deployment. We probably want a nicer and more general API for this, but it'll have to +do for the time being. + +**`Remark`** + +- it queries the node to check whether the contract class with the given id has been publicly registered. + +___ + +### getContractMetadata + +▸ **getContractMetadata**(`address`): `Promise`\<[`ContractMetadata`](ContractMetadata.md)\> + +Returns the contract metadata given an address. +The metadata consists of its contract instance, which includes the contract class identifier, +initialization hash, deployment salt, and public keys hash; whether the contract instance has been initialized; +and whether the contract instance with the given address has been publicly deployed. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `address` | `AztecAddress` | The address that the contract instance resides at. | + +#### Returns + +`Promise`\<[`ContractMetadata`](ContractMetadata.md)\> + +- It returns the contract metadata +TODO(@spalladino): Should we return the public keys in plain as well here? + +**`Remark`** + +- it queries the node to check whether the contract instance has been initialized / publicly deployed through a node. +This query is not dependent on the PXE. + +___ + +### getContracts + +▸ **getContracts**(): `Promise`\<`AztecAddress`[]\> + +Retrieves the addresses of contracts added to this PXE Service. + +#### Returns + +`Promise`\<`AztecAddress`[]\> + +An array of contracts addresses registered on this PXE Service. + +___ + +### getCurrentBaseFees + +▸ **getCurrentBaseFees**(): `Promise`\<`GasFees`\> + +Method to fetch the current base fees. + +#### Returns + +`Promise`\<`GasFees`\> + +The current base fees. + +___ + +### getL1ToL2MembershipWitness + +▸ **getL1ToL2MembershipWitness**(`contractAddress`, `messageHash`, `secret`): `Promise`\<[`bigint`, `SiblingPath`\<``39``\>]\> + +Fetches an L1 to L2 message from the node. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `contractAddress` | `AztecAddress` | Address of a contract by which the message was emitted. | +| `messageHash` | `Fr` | Hash of the message. | +| `secret` | `Fr` | Secret used to compute a nullifier. | + +#### Returns + +`Promise`\<[`bigint`, `SiblingPath`\<``39``\>]\> + +The l1 to l2 membership witness (index of message in the tree and sibling path). + +**`Dev`** + +Contract address and secret are only used to compute the nullifier to get non-nullified messages + +___ + +### getL2ToL1MembershipWitness + +▸ **getL2ToL1MembershipWitness**(`blockNumber`, `l2Tol1Message`): `Promise`\<[`bigint`, `SiblingPath`\<`number`\>]\> + +Gets the membership witness for a message that was emitted at a particular block + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `blockNumber` | `number` | The block number in which to search for the message | +| `l2Tol1Message` | `Fr` | The message to search for | + +#### Returns + +`Promise`\<[`bigint`, `SiblingPath`\<`number`\>]\> + +The membership witness for the message + +___ + +### getNodeInfo + +▸ **getNodeInfo**(): `Promise`\<`NodeInfo`\> + +Returns the information about the server's node. Includes current Node version, compatible Noir version, +L1 chain identifier, rollup version, and L1 address of the rollup contract. + +#### Returns + +`Promise`\<`NodeInfo`\> + +- The node information. + +___ + +### getNotes + +▸ **getNotes**(`filter`): `Promise`\<`UniqueNote`[]\> + +Gets notes registered in this PXE based on the provided filter. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `filter` | `NotesFilter` | The filter to apply to the notes. | + +#### Returns + +`Promise`\<`UniqueNote`[]\> + +The requested notes. + +___ + +### getPXEInfo + +▸ **getPXEInfo**(): `Promise`\<[`PXEInfo`](PXEInfo.md)\> + +Returns information about this PXE. + +#### Returns + +`Promise`\<[`PXEInfo`](PXEInfo.md)\> + +___ + +### getPrivateEvents + +▸ **getPrivateEvents**\<`T`\>(`contractAddress`, `eventMetadata`, `from`, `numBlocks`, `recipients`): `Promise`\<`T`[]\> + +Returns the private events given search parameters. + +#### Type parameters + +| Name | +| :------ | +| `T` | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `contractAddress` | `AztecAddress` | The address of the contract to get events from. | +| `eventMetadata` | [`EventMetadataDefinition`](../#eventmetadatadefinition) | Metadata of the event. This should be the class generated from the contract. e.g. Contract.events.Event | +| `from` | `number` | The block number to search from. | +| `numBlocks` | `number` | The amount of blocks to search. | +| `recipients` | `AztecAddress`[] | The addresses that decrypted the logs. | + +#### Returns + +`Promise`\<`T`[]\> + +- The deserialized events. + +___ + +### getProvenBlockNumber + +▸ **getProvenBlockNumber**(): `Promise`\<`number`\> + +Fetches the current proven block number. + +#### Returns + +`Promise`\<`number`\> + +The block number. + +___ + +### getPublicEvents + +▸ **getPublicEvents**\<`T`\>(`eventMetadata`, `from`, `limit`): `Promise`\<`T`[]\> + +Returns the public events given search parameters. + +#### Type parameters + +| Name | +| :------ | +| `T` | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `eventMetadata` | [`EventMetadataDefinition`](../#eventmetadatadefinition) | Metadata of the event. This should be the class generated from the contract. e.g. Contract.events.Event | +| `from` | `number` | The block number to search from. | +| `limit` | `number` | The amount of blocks to search. | + +#### Returns + +`Promise`\<`T`[]\> + +- The deserialized events. + +___ + +### getPublicLogs + +▸ **getPublicLogs**(`filter`): `Promise`\<`GetPublicLogsResponse`\> + +Gets public logs based on the provided filter. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `filter` | `LogFilter` | The filter to apply to the logs. | + +#### Returns + +`Promise`\<`GetPublicLogsResponse`\> + +The requested logs. + +___ + +### getPublicStorageAt + +▸ **getPublicStorageAt**(`contract`, `slot`): `Promise`\<`Fr`\> + +Gets the storage value at the given contract storage slot. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `contract` | `AztecAddress` | Address of the contract to query. | +| `slot` | `Fr` | Slot to query. | + +#### Returns + +`Promise`\<`Fr`\> + +Storage value at the given contract slot. + +**`Remarks`** + +The storage slot here refers to the slot as it is defined in Noir not the index in the merkle tree. +Aztec's version of `eth_getStorageAt`. + +**`Throws`** + +If the contract is not deployed. + +___ + +### getRegisteredAccounts + +▸ **getRegisteredAccounts**(): `Promise`\<`CompleteAddress`[]\> + +Retrieves the user accounts registered on this PXE Service. + +#### Returns + +`Promise`\<`CompleteAddress`[]\> + +An array of the accounts registered on this PXE Service. + +___ + +### getSenders + +▸ **getSenders**(): `Promise`\<`AztecAddress`[]\> + +Retrieves the addresses stored as senders on this PXE Service. + +#### Returns + +`Promise`\<`AztecAddress`[]\> + +An array of the senders on this PXE Service. + +___ + +### getTxEffect + +▸ **getTxEffect**(`txHash`): `Promise`\<`undefined` \| `IndexedTxEffect`\> + +Gets a tx effect. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `txHash` | `TxHash` | The hash of the tx corresponding to the tx effect. | + +#### Returns + +`Promise`\<`undefined` \| `IndexedTxEffect`\> + +The requested tx effect with block info (or undefined if not found). + +___ + +### getTxReceipt + +▸ **getTxReceipt**(`txHash`): `Promise`\<`TxReceipt`\> + +Fetches a transaction receipt for a given transaction hash. Returns a mined receipt if it was added +to the chain, a pending receipt if it's still in the mempool of the connected Aztec node, or a dropped +receipt if not found in the connected Aztec node. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `txHash` | `TxHash` | The transaction hash. | + +#### Returns + +`Promise`\<`TxReceipt`\> + +A receipt of the transaction. + +___ + +### isL1ToL2MessageSynced + +▸ **isL1ToL2MessageSynced**(`l1ToL2Message`): `Promise`\<`boolean`\> + +Returns whether an L1 to L2 message is synced by archiver and if it's ready to be included in a block. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `l1ToL2Message` | `Fr` | The L1 to L2 message to check. | + +#### Returns + +`Promise`\<`boolean`\> + +Whether the message is synced and ready to be included in a block. + +___ + +### profileTx + +▸ **profileTx**(`txRequest`, `profileMode`, `skipProofGeneration?`, `msgSender?`): `Promise`\<`TxProfileResult`\> + +Profiles a transaction, reporting gate counts (unless disabled) and returns an execution trace. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `txRequest` | `TxExecutionRequest` | An authenticated tx request ready for simulation | +| `profileMode` | ``"gates"`` \| ``"execution-steps"`` \| ``"full"`` | - | +| `skipProofGeneration?` | `boolean` | - | +| `msgSender?` | `AztecAddress` | (Optional) The message sender to use for the simulation. | + +#### Returns + +`Promise`\<`TxProfileResult`\> + +A trace of the program execution with gate counts. + +**`Throws`** + +If the code for the functions executed in this transaction have not been made available via `addContracts`. + +___ + +### proveTx + +▸ **proveTx**(`txRequest`, `privateExecutionResult?`): `Promise`\<`TxProvingResult`\> + +Proves the private portion of a simulated transaction, ready to send to the network +(where validators prove the public portion). + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `txRequest` | `TxExecutionRequest` | An authenticated tx request ready for proving | +| `privateExecutionResult?` | `PrivateExecutionResult` | (optional) The result of the private execution of the transaction. The txRequest will be executed if not provided | + +#### Returns + +`Promise`\<`TxProvingResult`\> + +A result containing the proof and public inputs of the tail circuit. + +**`Throws`** + +If contract code not found, or public simulation reverts. +Also throws if simulatePublic is true and public simulation reverts. + +___ + +### registerAccount + +▸ **registerAccount**(`secretKey`, `partialAddress`): `Promise`\<`CompleteAddress`\> + +Registers a user account in PXE given its master encryption private key. +Once a new account is registered, the PXE Service will trial-decrypt all published notes on +the chain and store those that correspond to the registered account. Will do nothing if the +account is already registered. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `secretKey` | `Fr` | Secret key of the corresponding user master public key. | +| `partialAddress` | `Fr` | The partial address of the account contract corresponding to the account being registered. | + +#### Returns + +`Promise`\<`CompleteAddress`\> + +The complete address of the account. + +___ + +### registerContract + +▸ **registerContract**(`contract`): `Promise`\<`void`\> + +Adds deployed contracts to the PXE Service. Deployed contract information is used to access the +contract code when simulating local transactions. This is automatically called by aztec.js when +deploying a contract. Dapps that wish to interact with contracts already deployed should register +these contracts in their users' PXE Service through this method. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `contract` | `Object` | A contract instance to register, with an optional artifact which can be omitted if the contract class has already been registered. | +| `contract.artifact?` | `ContractArtifact` | - | +| `contract.instance` | `ContractInstanceWithAddress` | - | + +#### Returns + +`Promise`\<`void`\> + +___ + +### registerContractClass + +▸ **registerContractClass**(`artifact`): `Promise`\<`void`\> + +Registers a contract class in the PXE without registering any associated contract instance with it. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `artifact` | `ContractArtifact` | The build artifact for the contract class. | + +#### Returns + +`Promise`\<`void`\> + +___ + +### registerSender + +▸ **registerSender**(`address`): `Promise`\<`AztecAddress`\> + +Registers a user contact in PXE. + +Once a new contact is registered, the PXE Service will be able to receive notes tagged from this contact. +Will do nothing if the account is already registered. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `address` | `AztecAddress` | Address of the user to add to the address book | + +#### Returns + +`Promise`\<`AztecAddress`\> + +The address address of the account. + +___ + +### removeSender + +▸ **removeSender**(`address`): `Promise`\<`void`\> + +Removes a sender in the address book. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `address` | `AztecAddress` | + +#### Returns + +`Promise`\<`void`\> + +___ + +### sendTx + +▸ **sendTx**(`tx`): `Promise`\<`TxHash`\> + +Sends a transaction to an Aztec node to be broadcasted to the network and mined. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `tx` | `Tx` | The transaction as created via `proveTx`. | + +#### Returns + +`Promise`\<`TxHash`\> + +A hash of the transaction, used to identify it. + +___ + +### simulateTx + +▸ **simulateTx**(`txRequest`, `simulatePublic`, `skipTxValidation?`, `skipFeeEnforcement?`, `overrides?`, `scopes?`): `Promise`\<`TxSimulationResult`\> + +Simulates a transaction based on the provided preauthenticated execution request. +This will run a local simulation of private execution (and optionally of public as well), run the +kernel circuits to ensure adherence to protocol rules (without generating a proof), and return the +simulation results . + +Note that this is used with `ContractFunctionInteraction::simulateTx` to bypass certain checks. +In that case, the transaction returned is only potentially ready to be sent to the network for execution. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `txRequest` | `TxExecutionRequest` | An authenticated tx request ready for simulation | +| `simulatePublic` | `boolean` | Whether to simulate the public part of the transaction. | +| `skipTxValidation?` | `boolean` | (Optional) If false, this function throws if the transaction is unable to be included in a block at the current state. | +| `skipFeeEnforcement?` | `boolean` | (Optional) If false, fees are enforced. | +| `overrides?` | `SimulationOverrides` | (Optional) State overrides for the simulation, such as msgSender, contract instances and artifacts. | +| `scopes?` | `AztecAddress`[] | (Optional) The accounts whose notes we can access in this call. Currently optional and will default to all. | + +#### Returns + +`Promise`\<`TxSimulationResult`\> + +A simulated transaction result object that includes public and private return values. + +**`Throws`** + +If the code for the functions executed in this transaction have not been made available via `addContracts`. +Also throws if simulatePublic is true and public simulation reverts. + +___ + +### simulateUtility + +▸ **simulateUtility**(`functionName`, `args`, `to`, `authwits?`, `from?`, `scopes?`): `Promise`\<`UtilitySimulationResult`\> + +Simulate the execution of a contract utility function. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `functionName` | `string` | The name of the utility contract function to be called. | +| `args` | `any`[] | The arguments to be provided to the function. | +| `to` | `AztecAddress` | The address of the contract to be called. | +| `authwits?` | `AuthWitness`[] | (Optional) The authentication witnesses required for the function call. | +| `from?` | `AztecAddress` | (Optional) The msg sender to set for the call. | +| `scopes?` | `AztecAddress`[] | (Optional) The accounts whose notes we can access in this call. Currently optional and will default to all. | + +#### Returns + +`Promise`\<`UtilitySimulationResult`\> + +The result of the utility function call, structured based on the function ABI. + +___ + +### updateContract + +▸ **updateContract**(`contractAddress`, `artifact`): `Promise`\<`void`\> + +Updates a deployed contract in the PXE Service. This is used to update the contract artifact when +an update has happened, so the new code can be used in the simulation of local transactions. +This is called by aztec.js when instantiating a contract in a given address with a mismatching artifact. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `contractAddress` | `AztecAddress` | The address of the contract to update. | +| `artifact` | `ContractArtifact` | The updated artifact for the contract. | + +#### Returns + +`Promise`\<`void`\> diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/PXEInfo.md b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/PXEInfo.md new file mode 100644 index 000000000000..b34b5866adef --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/PXEInfo.md @@ -0,0 +1,25 @@ +--- +id: "PXEInfo" +title: "Interface: PXEInfo" +sidebar_label: "PXEInfo" +sidebar_position: 0 +custom_edit_url: null +--- + +Provides basic information about the running PXE. + +## Properties + +### protocolContractAddresses + +• **protocolContractAddresses**: `ProtocolContractAddresses` + +Protocol contract addresses + +___ + +### pxeVersion + +• **pxeVersion**: `string` + +Version as tracked in the aztec-packages repository. diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/_category_.yml b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/_category_.yml new file mode 100644 index 000000000000..43bec88cfa0a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/aztecjs/pxe/interfaces/_category_.yml @@ -0,0 +1,2 @@ +label: "Interfaces" +position: 4 \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/considerations/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/reference/considerations/_category_.json new file mode 100644 index 000000000000..3d16e4d09376 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/considerations/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Considerations and Limitations", + "position": 4, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/considerations/limitations.md b/docs/versioned_docs/version-v0.88.0/developers/reference/considerations/limitations.md new file mode 100644 index 000000000000..4708d66ab289 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/considerations/limitations.md @@ -0,0 +1,228 @@ +--- +title: Limitations +sidebar_position: 6 +--- + +The Aztec stack is a work in progress. Packages have been released early, to gather feedback on the capabilities of the protocol and user experiences. + +## What to expect? + +- Regular Breaking Changes; +- Missing features; +- Bugs; +- An 'unpolished' UX; +- Missing information. + +## Why participate? + +Front-run the future! + +Help shape and define: + +- Previously-impossible smart contracts and applications +- Network tooling; +- Network standards; +- Smart contract syntax; +- Educational content; +- Core protocol improvements; + +## Limitations developers need to know about + +- It is a testing environment, it is insecure, and unaudited. It is only for testing purposes. +- `msg_sender` is currently leaking when doing private -> public calls + - The `msg_sender` will always be set, if you call a public function from the private world, the `msg_sender` will be set to the private caller's address. + - There are patterns that can mitigate this. +- The initial `msg_sender` is `-1`, which can be problematic for some contracts. +- The number of side-effects attached to a tx (when sending the tx to the mempool) is leaky. At this stage of development, this is _intentional_, so that we can gauge appropriate choices for privacy sets. We have always had clear plans to implement privacy sets so that side effects are much less leaky, and these will be in place come mainnet. +- A transaction can only emit a limited number of side-effects (notes, nullifiers, logs, l2->l1 messages), see [circuit limitations](#circuit-limitations). + - We haven't settled on the final constants, since we're still in a testing phase. But users could find that certain compositions of nested private function calls (e.g. call stacks that are dynamic in size, based on runtime data) could accumulate so many side-effects as to exceed tx limits. Such txs would then be unprovable. We would love for you to open an issue if you encounter this, as it will help us decide on adequate sizes for our constants. +- There are lots of features that we still want to implement. Checkout github and the forum for details. If you would like a feature, please open an issue on github! + +## WARNING + +Do not use real, meaningful secrets in Aztec's testnets. Some privacy features are still being worked on, including ensuring a secure "zk" property. Since the Aztec stack is still being worked on, there are no guarantees that real secrets will remain secret. + +## Limitations + +There are plans to resolve all of the below. + +### It is not audited + +None of the Aztec stack is audited. It's being iterated-on every day. It will not be audited for quite some time. + +### Under-constrained + +Some of our more-complex circuits are still being worked on, so they will still be be underconstrained. + +#### What are the consequences? + +Sound proofs are really only needed as a protection against malicious behavior, which we're not testing for at this stage. + +### Keys and Addresses are subject to change + +The way in which keypairs and addresses are derived is still being iterated on as we receive feedback. + +#### What are the consequences? + +This will impact the kinds of apps that you can build with the Sandbox, as it is today: + +Please open new discussions on [discourse](http://discourse.aztec.network) or open issues on [github](http://github.com/AztecProtocol/aztec-packages), if you have requirements that aren't-yet being met by the Sandbox's current key derivation scheme. + +### No privacy-preserving queries to nodes + +Ethereum has a notion of a 'full node' which keeps-up with the blockchain and stores the full chain state. Many users don't wish to run full nodes, so rely on 3rd-party 'full-node-as-a-service' infrastructure providers, who service blockchain queries from their users. + +This pattern is likely to develop in Aztec as well, except there's a problem: privacy. If a privacy-seeking user makes a query to a 3rd-party 'full node', that user might leak data about who they are, or about their historical network activity, or about their future intentions. One solution to this problem is "always run a full node", but pragmatically, not everyone will. To protect less-advanced users' privacy, research is underway to explore how a privacy-seeking user may request and receive data from a 3rd-party node without revealing what that data is, nor who is making the request. + +### No private data authentication + +Private data should not be returned to an app, unless the user authorizes such access to the app. An authorization layer is not-yet in place. + +#### What are the consequences? + +Any app can request and receive any private user data relating to any other private app. Obviously this sounds bad. But the Sandbox is a sandbox, and no meaningful value or credentials should be stored there; only test values and test credentials. + +An auth layer will be added in due course. + +### No bytecode validation + +For safety reasons, bytecode should not be executed unless the PXE/Wallet has validated that the user's intentions (the function signature and contract address) match the bytecode. + +#### What are the consequences? + +Without such 'bytecode validation', if the incorrect bytecode is executed, and that bytecode is malicious, it could read private data from some other contract and emit that private data to the world. Obviously this would be bad in production. But the Sandbox is a sandbox, and no meaningful value or credentials should be stored there; only test values and test credentials. + +There are plans to add bytecode validation soon. + +### Insecure hashes + +We are planning a full assessment of the protocol's hashes, including rigorous domain separation. + +#### What are the consequences? + +Collisions and other hash-related attacks might be possible in the Sandbox. Obviously that would be bad in production. But it's unlikely to cause problems at this early stage of testing. + +### `msg_sender` is leaked when making a private -> public call + +There are ongoing discussions [here](https://forum.aztec.network/t/what-is-msg-sender-when-calling-private-public-plus-a-big-foray-into-stealth-addresses/7527 (and some more recent discussions that need to be documented) around how to address this. + +### New Privacy Standards are required + +There are many [patterns](../../reference/considerations/privacy_considerations.md) which can leak privacy, even on Aztec. Standards haven't been developed yet, to encourage best practices when designing private smart contracts. + +#### What are the consequences? + +For example, until community standards are developed to reduce the uniqueness of ['Tx Fingerprints'](../../reference/considerations/privacy_considerations.md#function-fingerprints-and-tx-fingerprints) app developers might accidentally forfeit some function privacy. + +## Smart Contract limitations + +We will never be done with all the yummy features we want to add to aztec.nr. We have lots of features that we still want to implement. Please check out github, and please open new issues with any feature requests you might have. + +## Circuit limitations + +### Upper limits on function outputs and tx outputs + +Due to the rigidity of zk-SNARK circuits, there are upper bounds on the amount of computation a circuit can perform, and on the amount of data that can be passed into and out of a function. + +> Blockchain developers are no stranger to restrictive computational environments. Ethereum has gas limits, local variable stack limits, call stack limits, contract deployment size limits, log size limits, etc. + +Here are the current constants: + +```rust title="constants" showLineNumbers +// "PER CALL" CONSTANTS +pub global MAX_NOTE_HASHES_PER_CALL: u32 = 16; +pub global MAX_NULLIFIERS_PER_CALL: u32 = 16; +pub global MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL: u32 = 5; +pub global MAX_ENQUEUED_CALLS_PER_CALL: u32 = 16; +pub global MAX_L2_TO_L1_MSGS_PER_CALL: u32 = 2; +pub global MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL: u32 = 63; +pub global MAX_PUBLIC_DATA_READS_PER_CALL: u32 = 64; +pub global MAX_NOTE_HASH_READ_REQUESTS_PER_CALL: u32 = 16; +pub global MAX_NULLIFIER_READ_REQUESTS_PER_CALL: u32 = 16; +pub global MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL: u32 = 16; +pub global MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_CALL: u32 = 16; +pub global MAX_KEY_VALIDATION_REQUESTS_PER_CALL: u32 = 16; +pub global MAX_PRIVATE_LOGS_PER_CALL: u32 = 16; +pub global MAX_PUBLIC_LOGS_PER_CALL: u32 = 4; +pub global MAX_CONTRACT_CLASS_LOGS_PER_CALL: u32 = 1; + +// TREES RELATED CONSTANTS +pub global ARCHIVE_HEIGHT: u32 = 29; +pub global VK_TREE_HEIGHT: u32 = 6; +pub global PROTOCOL_CONTRACT_TREE_HEIGHT: u32 = 3; +pub global FUNCTION_TREE_HEIGHT: u32 = 5; +pub global NOTE_HASH_TREE_HEIGHT: u32 = 40; +pub global PUBLIC_DATA_TREE_HEIGHT: u32 = 40; +pub global NULLIFIER_TREE_HEIGHT: u32 = 40; +pub global L1_TO_L2_MSG_TREE_HEIGHT: u32 = 39; +pub global ARTIFACT_FUNCTION_TREE_MAX_HEIGHT: u32 = 5; +pub global NULLIFIER_TREE_ID: Field = 0; +pub global NOTE_HASH_TREE_ID: Field = 1; +pub global PUBLIC_DATA_TREE_ID: Field = 2; +pub global L1_TO_L2_MESSAGE_TREE_ID: Field = 3; +pub global ARCHIVE_TREE_ID: Field = 4; + +// SUB-TREES RELATED CONSTANTS +pub global NOTE_HASH_SUBTREE_HEIGHT: u32 = 6; +pub global NULLIFIER_SUBTREE_HEIGHT: u32 = 6; +// Deprecated: to be removed after removal of legacy ts trees +pub global PUBLIC_DATA_SUBTREE_HEIGHT: u32 = 6; +pub global L1_TO_L2_MSG_SUBTREE_HEIGHT: u32 = 4; +pub global NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH: u32 = + NOTE_HASH_TREE_HEIGHT - NOTE_HASH_SUBTREE_HEIGHT; +pub global NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH: u32 = + NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT; +pub global L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH: u32 = + L1_TO_L2_MSG_TREE_HEIGHT - L1_TO_L2_MSG_SUBTREE_HEIGHT; + +// "PER TRANSACTION" CONSTANTS +pub global MAX_NOTE_HASHES_PER_TX: u32 = (1 as u8 << NOTE_HASH_SUBTREE_HEIGHT as u8) as u32; +pub global MAX_NULLIFIERS_PER_TX: u32 = (1 as u8 << NULLIFIER_SUBTREE_HEIGHT as u8) as u32; +pub global MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX: u32 = 8; +pub global MAX_ENQUEUED_CALLS_PER_TX: u32 = 32; +pub global PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX: u32 = 1; +pub global MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX: u32 = + (1 as u8 << PUBLIC_DATA_SUBTREE_HEIGHT as u8) as u32; +pub global MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX: u32 = + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX; +pub global MAX_PUBLIC_DATA_READS_PER_TX: u32 = 64; +pub global MAX_L2_TO_L1_MSGS_PER_TX: u32 = 8; +pub global MAX_NOTE_HASH_READ_REQUESTS_PER_TX: u32 = 64; +pub global MAX_NULLIFIER_READ_REQUESTS_PER_TX: u32 = 64; +pub global MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX: u32 = 64; +pub global MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX: u32 = 64; +// TODO: for large multisends we might run out of key validation requests here but not dealing with this now as +// databus will hopefully make the issue go away. +pub global MAX_KEY_VALIDATION_REQUESTS_PER_TX: u32 = 64; +pub global MAX_PRIVATE_LOGS_PER_TX: u32 = 32; +pub global MAX_PUBLIC_LOGS_PER_TX: u32 = 8; +pub global MAX_CONTRACT_CLASS_LOGS_PER_TX: u32 = 1; +``` +> Source code: noir-projects/noir-protocol-circuits/crates/types/src/constants.nr#L29-L98 + + +#### What are the consequences? + +When you write an Aztec.nr function, there will be upper bounds on the following: + +- The number of public state reads and writes; +- The number of note reads and nullifications; +- The number of new notes that may be created; +- The number of encrypted logs that may be emitted; +- The number of unencrypted logs that may be emitted; +- The number of L1->L2 messages that may be consumed; +- The number of L2->L1 messages that may be submitted to L1; +- The number of private function calls; +- The number of public function calls that may be enqueued; + +Not only are there limits on a _per function_ basis, there are also limits on a _per transaction_ basis. + +**In particular, these _per-transaction_ limits will limit transaction call stack depths**. That means if a function call results in a cascade of nested function calls, and each of those function calls outputs lots of state reads and writes, or logs (etc.), then all of that accumulated output data might exceed the per-transaction limits that we currently have. This would cause such transactions to fail. + +There are plans to relax some of this rigidity, by providing many 'sizes' of circuit. + +> **In the mean time**, if you encounter a per-transaction limit when testing, please do open an issue to explain what you were trying to do; we'd love to hear about it. And if you're feeling adventurous, you could 'hack' the PXE to increase the limits. **However**, the limits cannot be increased indefinitely. So although we do anticipate that we'll be able to increase them a little bit, don't go mad and provide yourself with 1 million state transitions per transaction. That would be as unrealistic as artificially increasing Ethereum gas limits to 1 trillion. + +## There's more + +See the [GitHub issues (GitHub link)](https://github.com/AztecProtocol/aztec-packages/issues) for all known bugs fixes and features currently being worked on. diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/considerations/privacy_considerations.md b/docs/versioned_docs/version-v0.88.0/developers/reference/considerations/privacy_considerations.md new file mode 100644 index 000000000000..fabfaf17781f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/considerations/privacy_considerations.md @@ -0,0 +1,145 @@ +--- +title: Privacy Considerations +sidebar_position: 5 +tags: [protocol, PXE] +--- + +Privacy is important. + +Keeping information private is difficult. + +Once information is leaked, it cannot be unleaked. + +--- + +## What can Aztec keep private? + +Aztec provides a set of tools to enable developers to build private smart contracts. The following can be kept private: + +**Private persistent state** + +Store state variables in an encrypted form, so that no one can see what those variables are, except those with the decryption key. + +**Private events and messages** + +Emit encrypted events, or encrypted messages from a private smart contract function. Only those with the decryption key will learn the message. + +**Private function execution** + +Execute a private function without the world knowing which function you've executed. + +**Private bytecode** + +The bytecode of private functions does not need to be distributed to the world; much like real-world contracts. + +:::danger +Privacy is not guaranteed without care. +Although Aztec provides the tools for private smart contracts, information can still be leaked unless the dapp developer is careful. +Aztec is still under development, so real-world, meaningful, valuable secrets _should not_ be entrusted to the system. +This page outlines some best practices to aid dapp developers. +::: + +--- + +## Leaky practices + +There are many caveats to the above. Since Aztec also enables interaction with the _public_ world (public L2 functions and L1 functions), private information can be accidentally leaked if developers aren't careful. + +### Crossing the private -> public boundary + +Any time a private function makes a call to a public function, information is leaked. Now, that might be perfectly fine in some use cases (it's up to the smart contract developer). Indeed, most interesting apps will require some public state. But let's have a look at some leaky patterns: + +- Calling a public function from a private function. The public function execution will be publicly visible. +- Calling a public function from a private function, without revealing the `msg_sender` of that call. (Otherwise the `msg_sender` will be publicly visible). +- Passing arguments to a public function from a private function. All of those arguments will be publicly visible. +- Calling an internal public function from a private function. The fact that the call originated from a private function of that same contract will be trivially known. +- Emitting unencrypted events from a private function. The unencrypted event name and arguments will be publicly visible. +- Sending L2->L1 messages from a private function. The entire message, and the resulting L1 function execution will all be publicly visible. + +### Crossing the public -> private boundary + +If a public function sends a message to be consumed by a private function, the act of consuming that message might be leaked if not following recommended patterns. + +### Timing of transactions + +Information about the nature of a transaction can be leaked based on the timing of that transaction. + +If a transaction is executed at 8am GMT, it's much less likely to have been made by someone in the USA. + +If there's a spike in transactions on the last day of every month, those might be salaries. + +These minor details are information that can disclose much more information about a user than the user might otherwise expect. + +Suppose that every time Alice sends Bob a private token, 1 minute later a transaction is always submitted to the tx pool with the same kind of 'fingerprint'. Alice might deduce that these transactions are automated reactions by Bob. (Here, 'fingerprint' is an intentionally vague term. It could be a public function call, or a private tx proof with a particular number of nonzero public inputs, or some other discernible pattern that Alice sees). + +TL;DR: app developers should think about the _timing_ of user transactions, and how this might leak information. + +### Function Fingerprints and Tx Fingerprints + +A 'Function Fingerprint' is any data which is exposed by a function to the outside world. A 'Tx Fingerprint' is any data which is exposed by a tx to the outside world. We're interested in minimizing leakages of information from private txs. The leakiness of a Tx Fingerprint depends on the leakiness of its constituent functions' Function Fingerprints _and_ on the appearance of the tx's Tx Fingerprint as a whole. For a private function (and by extension, for a private tx), the following information _could_ be leaked (depending on the function, of course): + +- All calls to public functions. + - The contract address of the private function (if it calls an internal public function). + - This could be the address of the transactor themselves, if the calling contract is an account contract. + - All arguments which are passed to public functions. +- All calls to L1 functions (in the form of L2 -> L1 messages). + - The contents of L2 -> L1 messages. +- All public logs (topics and arguments). +- The roots of all trees which have been read from. +- The _number_ of ['side effects'](): + - \# new note hashes + - \# new nullifiers + - \# bytes of encrypted logs + - \# public function calls + - \# L2->L1 messages + - \# nonzero roots[^1] + +> Note: many of these were mentioned in the ["Crossing the private -> public boundary"](#crossing-the-private---public-boundary) section. + +> Note: the transaction effects submitted to L1 is [encoded (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/core/libraries/Decoder.sol) but not garbled with other transactions: the distinct Tx Fingerprint of each tx can is publicly visible when a tx is submitted to the L2 tx pool. + +#### Standardizing Fingerprints + +If each private function were to have a unique Fingerprint, then all private functions would be distinguishable from each-other, and all of the efforts of the Aztec protocol to enable 'private function execution' would have been pointless. Standards need to be developed, to encourage smart contract developers to adhere to a restricted set of Tx Fingerprints. For example, a standard might propose that the number of new note hashes, nullifiers, logs, etc. must always be equal, and must always equal a power of two. Such a standard would effectively group private functions/txs into 'privacy sets', where all functions/txs in a particular 'privacy set' would look indistinguishable from each-other, when executed. + +### Data queries + +It's not just the broadcasting of transactions to the network that can leak data. + +Ethereum has a notion of a 'full node' which keeps-up with the blockchain and stores the full chain state. Many users don't wish to run full nodes, so rely on 3rd-party 'full-node-as-a-service' infrastructure providers, who service blockchain queries from their users. + +This pattern is likely to develop in Aztec as well, except there's a problem: privacy. If a privacy-seeking user makes a query to a 3rd-party 'full node', that user might leak data about who they are; about their historical network activity; or about their future intentions. One solution to this problem is "always run a full node", but pragmatically, not everyone will. To protect less-advanced users' privacy, research is underway to explore how a privacy-seeking user may request and receive data from a 3rd-party node without revealing what that data is, nor who is making the request. + +App developers should be aware of this avenue for private data leakage. **Whenever an app requests information from a node, the entity running that node is unlikely to be your user!** + +#### What kind of queries can be leaky? + +##### Querying for up-to-date note sibling paths + +To read a private state is to read a note from the note hash tree. To read a note is to prove existence of that note in the note hash tree. And to prove existence is to re-compute the root of the note hash tree using the leaf value, the leaf index, and the sibling path of that leaf. This computed root is then exposed to the world, as a way of saying "This note exists", or more precisely "This note has existed at least since this historical snapshot time". + +If an old historical snapshot is used, then that old historical root will be exposed, and this leaks some information about the nature of your transaction: it leaks that your note was created before the snapshot date. It shrinks the 'privacy set' of the transaction to a smaller window of time than the entire history of the network. + +So for maximal privacy, it's in a user's best interest to read from the very-latest snapshot of the data tree. + +Naturally, the note hash tree is continuously changing as new transactions take place and their new notes are appended. Most notably, the sibling path for every leaf in the tree changes every time a new leaf is appended. + +If a user runs their own node, there's no problem: they can query the latest sibling path for their note(s) from their own machine without leaking any information to the outside world. + +But if a user is not running their own node, they would need to query the very-latest sibling path of their note(s) from some 3rd-party node. In order to query the sibling path of a leaf, the leaf's index needs to be provided as an argument. Revealing the leaf's index to a 3rd-party trivially reveals exactly the note(s) you're about to read. And since those notes were created in some prior transaction, the 3rd-party will be able to link you with that prior transaction. Suppose then that the 3rd-party also serviced the creator of said prior transaction: the 3rd-party will slowly be able to link more and more transactions, and gain more and more insight into a network which is meant to be private! + +We're researching cryptographic ways to enable users to retrieve sibling paths from 3rd-parties without revealing leaf indices. + +> \* Note: due to the non-uniformity of Aztec transactions, the 'privacy set' of a transaction might not be the entire set of transactions that came before. + +##### Any query + +Any query to a node leaks information to that node. + +We're researching cryptographic ways to enable users to query any data privately. + +--- + +Footnotes + +[^1]: All txs should set the kernel circuit public inputs for all roots to _valid_, _up-to-date_ nonzero values, so as to mask which trees have _actually_ been read from. The Sandbox will eventually automate this (see this [issue (GitHub link)](https://github.com/AztecProtocol/aztec-packages/issues/1676)). diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/_category_.json new file mode 100644 index 000000000000..99db708f09cb --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Debugging", + "position": 3, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/aztecnr-errors.md b/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/aztecnr-errors.md new file mode 100644 index 000000000000..438c5cbc9b3a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/aztecnr-errors.md @@ -0,0 +1,75 @@ +--- +title: Aztec.nr Errors +tags: [contracts] +--- + +This section contains some errors that you may encounter when writing and compiling contracts in Aztec.nr. If you run into an error that is not listed here, please [create an issue (GitHub link)](https://github.com/AztecProtocol/aztec-packages/issues/new). + +#### `Aztec dependency not found. Please add aztec as a dependency in your Nargo.toml` + +All smart contracts written in Aztec.nr need the `aztec` dependency. In your `Nargo.toml` under `[dependencies]`, add this: + +```toml +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/aztec" } +``` + +You can learn more about dependencies and their paths [here](../smart_contract_reference/dependencies.md). + +#### `backend has encountered an error` + +This is likely due to a version mismatch or bad install of barretenberg. Try [reinstalling nargo](../../guides/local_env/versions-updating.md) or uninstalling barretenberg: + +```bash +nargo backend uninstall acvm-backend-barretenberg +``` + +It will then reinstall when you compile. + +#### `Oracle callback {} not found` & `Oracle callback pedersenHash not found` + +This can occasionally happen when there are breaking releases. Make sure that your dependencies in `Nargo.toml` are [updated to the latest release](../../guides/local_env/versions-updating.md#dependency-versions). + +#### `error: Failed constraint: 'Public state writes only supported in public functions` + +Reading and writing to public state from private functions is currently not supported. +This is because public values may change before the private function execution is posted on-chain. + +This may change in future versions. + +#### `Simulation error: Assertion failed:...` + +This is an assertion error that is thrown when a condition is not met. + +To address the error. find the line in the contract that is throwing the error and investigate why the condition is not met. + +#### `Unknown contract 0x0: add it to PXE by calling server.addContracts(...)` + +This error occurs when you are trying to interact with a smart contract via an Private Execution Environment (PXE) that does not have the necessary information to execute a transaction. + +To execute a transaction, the PXE needs to know the complete address of a contract and contract artifacts. + +To address the error, add the contract to the PXE by calling [`pxe.addContracts(...)`](../../../aztec/concepts/pxe/index.md). + +#### `Simulation error: No public key registered for address 0x0. Register it by calling pxe.registerRecipient(...) or pxe.registerAccount(...)` + +This error occurs when your contract is trying to get a public key via the `get_public_key` oracle call, but the PXE does not have the Complete Address (Complete Address contains the public key). + +Your contract typically needs a note recipient's public key when it wants to send a note to because the public key is used to encrypt notes. + +:::info +Manually adding the recipient to the PXE should not be required in case the recipient contract has already been deployed and the PXE is fully synced. +This is because this information is submitted on-chain when the recipient contract is deployed. +::: + +#### `Could not process note because of "Error: Unknown account.". Skipping note...` + +This error occurs when your contract is trying to get a secret via the `get_secret` oracle call, but the PXE does not have the secret for the public key. + +This error might occur when you register an account only as a recipient and not as an account. +To address the error, register the account by calling `server.registerAccount(...)`. + +#### `Failed to solve brillig function 'self._is_some` + +You may encounter this error when trying to send a transaction that is using an invalid contract. The contract may compile without errors and you only encounter this when sending the transaction. + +This error may arise when function parameters are not properly formatted, when trying to "double-spend" a note, or it may indicate that there is a bug deeper in the stack (e.g. a bug in the Aztec.nr library or deeper). If you hit this error, double-check your contract implementation, but also consider [opening an issue (GitHub link)](https://github.com/AztecProtocol/aztec-packages/issues/new). diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/index.md b/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/index.md new file mode 100644 index 000000000000..f020a71f846a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/index.md @@ -0,0 +1,67 @@ +--- +title: Debugging +sidebar_position: 1 +--- + +## Logging in Aztec.nr + +On this section you can learn how to debug your Aztec.nr smart contracts and common errors that you may run into. + +You can log statements from Aztec.nr contracts that will show ups in the Sandbox. + +:::info + +The Noir standard library `std::println` function will not work in Aztec contracts. You must use the `debug_log` and `debug_log_format` defined below. + +::: + +### Import `debug_log` + +Import the `debug_log` dependency from Aztec oracles: + +```rust +use dep::aztec::oracle::debug_log::{ debug_log }; +``` + +### Write log + +Write `debug_log()` in the appropriate place in your contract. + +```rust +debug_log("here"); +``` + +Other methods for logging include: + +`debug_log_format()`: for logging Field values along arbitrary strings. + +```rust +debug_log_format("get_2(slot:{0}) =>\n\t0:{1}\n\t1:{2}", [storage_slot, note0_hash, note1_hash]); +``` + +`debug_log_field()`: for logging Fields. + +```rust +debug_log_field(my_field); +``` + +`debug_log_array()`: for logging array types. + +```rust +debug_log_array(my_array); +``` + +### Start Sandbox in debug mode + +Set `LOG_LEVEL` to `verbose` or `debug`: + +```bash +# Options are 'fatal', 'error', 'warn', 'info', 'verbose', 'debug', 'trace' +LOG_LEVEL=debug aztec start --sandbox +``` + +and start the sandbox normally. + +You can specify different log levels for different services. + +For example: `LOG_LEVEL="verbose;info:sequencer"` will use verbose logging for everything except the `sequencer` service, which will use the `info` level. diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/sandbox-errors.md b/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/sandbox-errors.md new file mode 100644 index 000000000000..a43faed0196f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/sandbox-errors.md @@ -0,0 +1,200 @@ +--- +title: Aztec Sandbox Errors +tags: [sandbox] +--- + +import Disclaimer from '@site/src/components/Disclaimers/\_wip_disclaimer.mdx'; + + + +This section contains a list of errors you may encounter when using Aztec Sandbox and an explanation of each of them. + +## Circuit Errors + +**To prevent bloating this doc, here is a list of some of the common errors.** + +### Kernel Circuits + +We have several versions of public and private kernels as explained in [the circuits section in the concepts](../../../aztec/concepts/advanced/circuits/index.md). Certain things are only possible in certain versions of the circuits. So always ensure that the right version is being used for proof generation. For example, there is a specific version of the public kernel that only works if the previous kernel iteration was a private kernel. Similarly there is one that only works if the previous kernel was public. + +Remember that for each function call (i.e. each item in the call stack), there is a new kernel iteration that gets run. + +#### 2002 - PRIVATE_KERNEL\_\_INVALID_CONTRACT_ADDRESS + +Cannot call contract at address(0x0) privately. +This error may also happen when you deploy a new contract and the contract data hash is inconsistent to the expected contract address. + +#### 2005 - PRIVATE_KERNEL\_\_NEW_NOTE_HASHES_PROHIBITED_IN_STATIC_CALL + +For static calls, new note hashes aren't allowed + +#### 2006 - PRIVATE_KERNEL\_\_NEW_NULLIFIERS_PROHIBITED_IN_STATIC_CALL + +For static calls, new nullifiers aren't allowed + +#### 2009 - PRIVATE_KERNEL\_\_NON_PRIVATE_FUNCTION_EXECUTED_WITH_PRIVATE_KERNEL + +You cannot execute a public Aztec.nr function in the private kernel + +#### 2011 - PRIVATE_KERNEL\_\_UNSUPPORTED_OP + +You are trying to do something that is currently unsupported in the private kernel. If this is a blocker feel free to open up an issue on our monorepo [aztec-packages (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/master) or reach out to us on discord + +Note that certain operations are unsupported on certain versions of the private kernel. Eg static calls are allowed for all but the initial iteration of the private kernel (which initializes the kernel for subsequent function calls). + +#### 2012 - PRIVATE_KERNEL\_\_CONTRACT_ADDRESS_MISMATCH + +For the initial iteration of the private kernel, only the expected Aztec.nr contract should be the entrypoint. Static and delegate calls are not allowed in the initial iteration. + +#### 2013 - PRIVATE_KERNEL\_\_NON_PRIVATE_KERNEL_VERIFIED_WITH_PRIVATE_KERNEL + +The previous kernel iteration within the private kernel must also be private + +#### 2014 - PRIVATE_KERNEL\_\_CONSTRUCTOR_EXECUTED_IN_RECURSION + +A constructor must be executed as the first tx in the recursion i.e. a constructor call must be the first item in the call stack i.e. it can be executed in the first kernel iteration but not in subsequent ones. This also means you can't have a contract deploy another contract yet on Aztec. + +#### 2017 - PRIVATE_KERNEL\_\_USER_INTENT_MISMATCH_BETWEEN_TX_REQUEST_AND_CALL_STACK_ITEM + +Confirms that the TxRequest (user's intent) matches the private call being executed. This error may happen when: + +- origin address of tx_request doesn't match call_stack_item's contract_address +- tx_request.function_data doesn't match call_stack_item.function_data +- Aztec.nr function args passed to tx_request doesn't match args in the call_stack_item + +#### 2018 - PRIVATE_KERNEL\_\_READ_REQUEST_NOTE_HASH_TREE_ROOT_MISMATCH + +Given a read request and provided witness, we check that the merkle root obtained from the witness' sibling path and it's leaf is similar to the historical state root we want to read against. This is a sanity check to ensure we are reading from the right state. +For a non transient read, we fetch the merkle root from the membership witnesses and the leaf index + +#### 2019 - PRIVATE_KERNEL\_\_TRANSIENT_READ_REQUEST_NO_MATCH + +A pending note hash is the one that is not yet added to note hash tree. +A transient read is when we try to "read" a pending note hash. +This error happens when you try to read a pending note hash that doesn't exist. + +#### 2021 - PRIVATE_KERNEL\_\_UNRESOLVED_NON_TRANSIENT_READ_REQUEST + +For a transient read request we skip merkle membership checks since pending note hashes aren't inserted into the note hash tree yet. +But for non transient reads, we do a merkle membership check. Reads are done at the kernel circuit. So this checks that there are no already unresolved reads from a previous kernel iteration (other than non transient ones). + +#### 3001 - PUBLIC_KERNEL\_\_UNSUPPORTED_OP + +You are trying to do something that is currently unsupported in the public kernel. If this is a blocker feel free to open up an issue on our monorepo [aztec-packages (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/master) or reach out to us on discord + +#### 3002 - PUBLIC_KERNEL\_\_PRIVATE_FUNCTION_NOT_ALLOWED + +Calling a private Aztec.nr function in a public kernel is not allowed. + +#### 3005 - PUBLIC_KERNEL\_\_NON_EMPTY_PRIVATE_CALL_STACK + +Public functions are executed after all the private functions are (see [private-public execution](../../../aztec/smart_contracts/functions/public_private_calls.md)). As such, private call stack must be empty when executing in the public kernel. + +#### 3011 - PUBLIC_KERNEL\_\_CALCULATED_PRIVATE_CALL_HASH_AND_PROVIDED_PRIVATE_CALL_HASH_MISMATCH + +When the hash stored at the top most of the call stack is different to the call stack item expected by the public kernel's inputs. + +#### 3012 - PUBLIC_KERNEL\_\_PUBLIC_CALL_STACK_MISMATCH + +Similar to above, except here we actually have the preimages to the call stack and hash to ensure they match. + +#### 3013 - PUBLIC_KERNEL\_\_CONTRACT_DEPLOYMENT_NOT_ALLOWED + +Public kernel doesn't allow contract deployments + +#### 3014 - PUBLIC_KERNEL\_\_CONSTRUCTOR_NOT_ALLOWED + +Aztec doesn't support public constructors. + +#### 3015 - PUBLIC_KERNEL\_\_CONTRACT_ADDRESS_INVALID + +Calling `address(0x0)` publicly is not permitted. + +#### 3016 - PUBLIC_KERNEL\_\_FUNCTION_SIGNATURE_INVALID + +Cannot call a contract with no function (i.e. function signature of 0) publicly. + +#### 3022 - PUBLIC_KERNEL\_\_PUBLIC_CALL_STACK_CONTRACT_STORAGE_UPDATES_PROHIBITED_FOR_STATIC_CALL + +For static calls, no contract storage change requests are allowed. + +#### 3024 - PUBLIC_KERNEL\_\_CALL_CONTEXT_CONTRACT_STORAGE_UPDATE_REQUESTS_PROHIBITED_FOR_STATIC_CALL + +Same as [3022](#3022---public_kernel__public_call_stack_contract_storage_updates_prohibited_for_static_call), no contract changes are allowed for static calls. + +#### 3026 - PUBLIC_KERNEL\_\_NOTE_HASHES_PROHIBITED_IN_STATIC_CALL + +For static calls, no new note hashes or nullifiers can be added to the state. + +#### 3027 - PUBLIC_KERNEL\_\_NEW_NULLIFIERS_PROHIBITED_IN_STATIC_CALL + +For static calls, no new note hashes or nullifiers can be added to the state. + +### Rollup circuit errors + +These are errors that occur when kernel proofs (transaction proofs) are sent to the rollup circuits to create an L2 block. See [rollup circuits](../../../aztec/concepts/advanced/circuits/rollup_circuits/index.md) for more information. + +#### 4007 - BASE\_\_INVALID_CHAIN_ID + +The L1 chain ID you used in your proof generation (for your private transaction) is different to what the rollup circuits expected. Double check against the global variables passed to noir and the config set in [Aztec's rollup contract (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/core/Rollup.sol) which are [read in by sequencer GitHub link](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts#L32) and subsequently passed in as inputs to the base rollup. When the sequencer submits the block to the rollup contracts, this is again sanity checked so ensure this is the same everywhere. + +#### 4008 - BASE\_\_INVALID_VERSION + +Same as [section 4007](#4007---base__invalid_chain_id) except the `version` refers to the version of the Aztec L2 instance. + +Some scary bugs like `4003 - BASE__INVALID_NULLIFIER_SUBTREE` and `4004 - BASE__INVALID_NULLIFIER_RANGE` which are to do malformed nullifier trees (see [Indexed Merkle Trees](../../../aztec/concepts/advanced/storage/indexed_merkle_tree.mdx)) etc may seem unrelated at a glance, but at a closer look may be because of some bug in an application's Aztec.nr code. Same is true for certain instances of `7008 - MEMBERSHIP_CHECK_FAILED`. + +### Generic circuit errors + +#### 7009 - ARRAY_OVERFLOW + +Circuits work by having a fixed size array. As such, we have limits on how many UTXOs can be created (aka "commitments") or destroyed/nullified (aka "nullifiers") in a transaction. Similarly we have limits on many reads or writes you can do, how many contracts you can create in a transaction. This error typically says that you have reached the current limits of what you can do in a transaction. Some examples when you may hit this error are: + +- too many new note hashes in one tx +- too many new nullifiers in one tx + - Note: Nullifiers may be created even outside the context of your Aztec.nr code. Eg, when creating a contract, we add a nullifier for its address to prevent same address from ever occurring. Similarly, we add a nullifier for your transaction hash too. +- too many private function calls in one tx (i.e. call stack size exceeded) +- too many public function calls in one tx (i.e. call stack size exceeded) +- too many new L2 to L1 messages in one tx +- too many contracts created in one tx +- too many public data update requests in one tx +- too many public data reads in one tx +- too many transient read requests in one tx +- too many transient read request membership witnesses in one tx + +You can have a look at our current constants/limitations in [constants.nr (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr) + +#### 7008 - MEMBERSHIP_CHECK_FAILED + +Users may create a proof against a historical state in Aztec. The rollup circuits performs a merkle membership check to ensure this state existed at some point. If the historical state doesn't exist, you get this error. Some examples when you may hit this error are: + +- using invalid historical note hash tree state (aka historical commitments tree) +- using invalid historical contracts data tree state +- using invalid historical L1 to L2 message data tree state +- inserting a subtree into the greater tree + - we make a smaller merkle tree of all the new note hashes/nullifiers etc that were created in a transaction or in a rollup and add it to the bigger state tree. Before inserting, we do a merkle membership check to ensure that the index to insert at is indeed an empty subtree (otherwise we would be overwriting state). This can happen when `next_available_leaf_index` in the state tree's snapshot is wrong (it is fetched by the sequencer from the archiver). The error message should reveal which tree is causing this issue + - nullifier tree related errors - The nullifier tree uses an [Indexed Merkle Tree](../../../aztec/concepts/advanced/storage/indexed_merkle_tree.mdx). It requires additional data from the archiver to know which is the nullifier in the tree that is just below the current nullifier before it can perform batch insertion. If the low nullifier is wrong, or the nullifier is in incorrect range, you may receive this error. + +--- + +## Archiver Errors + +- "No non-nullified L1 to L2 message found for message hash \$\{messageHash.toString()\}"/"Tried to consume nonexistent L1-to-L2 message" - happens when the L1 to L2 message doesn't exist or is "pending", when the user has sent a message on L1 via the Inbox contract but it has yet to be included in an L2 block by the sequencer - the user has to wait for enough blocks to progress and for the archiver to sync the respective L2 block. You can get the sequencer to pick it up by doing 2 arbitrary transactions on L2 (eg. send DAI to yourself 2 times). This would give the sequencer a transaction to process and as a side effect it would consume 2 subtrees of new messages from the Inbox contract. 2 subtrees need to be consumed and not just 1 because there is a 1 block lag to prevent the subtree from changing when the sequencer is proving. + +- "Block number mismatch: expected \$\{l2BlockNum\} but got \$\{block.number\}" - The archiver keeps track of the next expected L2 block number. It throws this error if it got a different one when trying to sync with the rollup contract's events on L1. + +## Sequencer Errors + +- "\$\{treeName\} tree root mismatch" - The sequencer validates that the root of the tree matches the output of the circuit simulation. The tree name could be Public data tree, Note Hash Tree, Contract tree, Nullifier tree or the L1ToL2Message tree, + +- "\$\{treeName\} tree next available leaf index mismatch" - validating a tree's root is not enough. It also checks that the `next_available_leaf_index` is as expected. This is the next index we can insert new values into. Note that for the public data tree, this test is skipped since as it is a sparse tree unlike the others. + +- "Public call stack size exceeded" - In Aztec, the sequencer executes all enqueued public functions in a transaction (to prevent race conditions - see [private-public execution](../../../aztec/smart_contracts/functions/public_private_calls.md)). This error says there are too many public functions requested. + +- "Array size exceeds target length" - happens if you add more items than allowed by the constants set due to our circuit limitations (eg sending too many L2 to L1 messages or creating a function that exceeds the call stack length or returns more values than what Aztec.nr functions allow) + +- "Failed to publish block" - Happens when sequencer tries to submit its L2 block + proof to the rollup contract. + +## L1 Aztec Contract Errors + +Aztec's L1 contracts use custom errors in solidity. While it saves gas, it has a side effect of making it harder to decode when things go wrong. If you get an error when submitting an L2Block into our rollup contract or when interacting with our Inbox/Outbox contracts, you can use the [Errors.sol library (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/core/libraries/Errors.sol) to match the hex encoded error to the error name. diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/wasm-errors.md b/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/wasm-errors.md new file mode 100644 index 000000000000..afdc4e1d5a9b --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/debugging/wasm-errors.md @@ -0,0 +1,55 @@ +--- +title: WASM Errors +tags: [bb.js, wasm, errors, applications, wallets] +--- + +Developers have limited visibility on bb.js errors due to the WASM having the debug symbols stripped out. + +We have several tools available to help make it easier to debug and report issues. + +### Specify WASM + +The environment variable `BB_WASM_PATH` allows you to replace the WASM that gets loaded into `bb.js`. You can provide an URL that will "hot swap" the wasm to the debug version in order to get stacktraces. You can see an example of how this is done in the playground here: + +```javascript title="bb-wasm-path" showLineNumbers +// The path to a custom WASM file for bb.js. +// Only the single-threaded file name is needed, the multithreaded file name will be inferred +// by adding the -threads suffix: e.g: /assets/barretenberg.wasm.gz -> /assets/barretenberg-threads.wasm.gz +// Files can be compressed or uncompressed, but must be gzipped if compressed. +BB_WASM_PATH: env.BB_WASM_PATH, +``` +> Source code: playground/vite.config.ts#L71-L77 + + +### Transaction Profiling + +Transaction profiling combined with `serializePrivateExecutionSteps` allows devs to generate a `msgpack` file that can be shared to reproduce issues. + +:::warning + +This file may contain private information. This is intended for development and debugging only and should not be exposed to end users. Anyone you share this information with may have access to your secrets, so it is recommended that you do not use sensitive data when developing. + +::: + +To do this, start by [profiling your transaction](../../guides/smart_contracts/profiling_transactions.md#profiling-in-aztecjs), getting the `execution-steps` from the resulting `TxProfileResult`. + +Pass the `execution-steps` to `serializePrivateExecutionSteps` from the `@aztec/stdlib` to get the `ivcMessagePack`, and download this. + +For example: + +```ts +const profileTx = await tokenInstance.methods + .transfer_private_to_private(sender, recipient, amount, nonce) + .profile({ profileMode: "execution-steps" }); + +const ivcMessagePack = serializePrivateExecutionSteps(profileTx.executionSteps) +const url = window.URL.createObjectURL(new Blob([ivcMessagePack])) + +const link = document.createElement("a") +link.href = url +link.download = "ivc-inputs.msgpack" + +document.body.appendChild(link) + +link.click() +``` diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/_category_.json new file mode 100644 index 000000000000..e51f342c74a7 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Developer Environment", + "position": 0, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/cheat_codes.md b/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/cheat_codes.md new file mode 100644 index 000000000000..a17a128475f0 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/cheat_codes.md @@ -0,0 +1,558 @@ +--- +title: Cheat Codes +tags: [sandbox] +sidebar_position: 4 +--- + +import Disclaimer from "@site/src/components/Disclaimers/\_wip_disclaimer.mdx"; + +## Introduction + +To help with testing, the sandbox is shipped with a set of cheatcodes. + +Cheatcodes allow you to change the time of the Aztec block, load certain state or more easily manipulate Ethereum instead of having to write dedicated RPC calls to anvil or hardhat. + +:::info Prerequisites +If you aren't familiar with [Anvil (Foundry)](https://book.getfoundry.sh/anvil/), we recommend reading up on that since Aztec Sandbox uses Anvil as the local Ethereum instance. +::: + +### Aims + +The guide will cover how to manipulate the state of the: + +- Ethereum blockchain; +- Aztec network. + +### Dependencies + +For this guide, the following Aztec packages are used: + +- @aztec/aztec.js + +### Initialization + +```ts +import { createPXEClient, CheatCodes } from "@aztec/aztec.js"; +const pxeRpcUrl = "http://localhost:8080"; +const ethRpcUrl = "http://localhost:8545"; +const pxe = createPXEClient(pxeRpcUrl); +const cc = await CheatCodes.create(ethRpcUrl, pxe); +``` + +There are two properties of the CheatCodes class - `eth` and `aztec` for cheatcodes relating to the Ethereum blockchain (L1) and the Aztec network (L2) respectively. + +## Ethereum related cheatcodes + +These are cheatcodes exposed from anvil/hardhat conveniently wrapped for ease of use in the Sandbox. + +### Interface + +```ts +// Fetch current block number of Ethereum +public async blockNumber(): Promise + +// Fetch chain ID of the local Ethereum instance +public async chainId(): Promise + +// Fetch current timestamp on Ethereum +public async timestamp(): Promise + +// Mine a given number of blocks on Ethereum. Mines 1 block by default +public async mine(numberOfBlocks = 1): Promise + +// Set the timestamp for the next block on Ethereum. +public async setNextBlockTimestamp(timestamp: number): Promise + +// Dumps the current Ethereum chain state to a given file. +public async dumpChainState(fileName: string): Promise + +// Loads the Ethereum chain state from a file. You may use `dumpChainState()` to save the state of the Ethereum chain to a file and later load it. +public async loadChainState(fileName: string): Promise + +// Load the value at a storage slot of a contract address on Ethereum +public async load(contract: EthAddress, slot: bigint): Promise + +// Set the value at a storage slot of a contract address on Ethereum (e.g. modify a storage variable on your portal contract or even the rollup contract). +public async store(contract: EthAddress, slot: bigint, value: bigint): Promise + +// Computes the slot value for a given map and key on Ethereum. A convenient wrapper to find the appropriate storage slot to load or overwrite the state. +public keccak256(baseSlot: bigint, key: bigint): bigint + +// Let you send transactions on Ethereum impersonating an externally owned or contract, without knowing the private key. +public async startImpersonating(who: EthAddress): Promise + +// Stop impersonating an account on Ethereum that you are currently impersonating. +public async stopImpersonating(who: EthAddress): Promise + +// Set the bytecode for a Ethereum contract +public async etch(contract: EthAddress, bytecode: `0x${string}`): Promise + +// Get the bytecode for a Ethereum contract +public async getBytecode(contract: EthAddress): Promise<`0x${string}`> +``` + +### blockNumber + +#### Function Signature + +```ts +public async blockNumber(): Promise +``` + +#### Description + +Fetches the current Ethereum block number. + +#### Example + +```ts +const blockNumber = await cc.eth.blockNumber(); +``` + +### chainId + +#### Function Signature + +```ts +public async chainId(): Promise +``` + +#### Description + +Fetches the Ethereum chain ID + +#### Example + +```ts +const chainId = await cc.eth.chainId(); +``` + +### timestamp + +#### Function Signature + +```ts +public async timestamp(): Promise +``` + +#### Description + +Fetches the current Ethereum timestamp. + +#### Example + +```ts +const timestamp = await cc.eth.timestamp(); +``` + +### mine + +#### Function Signature + +```ts +public async mine(numberOfBlocks = 1): Promise +``` + +#### Description + +Mines the specified number of blocks on Ethereum (default 1). + +#### Example + +```ts +const blockNum = await cc.eth.blockNumber(); +await cc.eth.mine(10); // mines 10 blocks +const newBlockNum = await cc.eth.blockNumber(); // = blockNum + 10. +``` + +### setNextBlockTimestamp + +#### Function Signature + +```ts +public async setNextBlockTimestamp(timestamp: number): Promise +``` + +#### Description + +Sets the timestamp (unix format in seconds) for the next mined block on Ethereum. +Time can only be set in the future. +If you set the timestamp to a time in the past, this method will throw an error. + +#### Example + +```ts +// // Set next block timestamp to 16 Aug 2023 10:54:30 GMT +await cc.eth.setNextBlockTimestamp(1692183270); +// next transaction you will do will have the timestamp as 1692183270 +``` + +### dumpChainState + +#### Function Signature + +```ts +public async dumpChainState(fileName: string): Promise +``` + +#### Description + +Dumps the current Ethereum chain state to a file. +Stores a hex string representing the complete state of the chain in a file with the provided path. Can be re-imported into a fresh/restarted instance of Anvil to reattain the same state. +When combined with `loadChainState()` cheatcode, it can be let you easily import the current state of mainnet into the Anvil instance of the sandbox. + +#### Example + +```ts +await cc.eth.dumpChainState("chain-state.json"); +``` + +### loadChainState + +#### Function Signature + +```ts +public async loadChainState(fileName: string): Promise +``` + +#### Description + +Loads the Ethereum chain state from a file which contains a hex string representing an Ethereum state. +When given a file previously written to by `cc.eth.dumpChainState()`, it merges the contents into the current chain state. Will overwrite any colliding accounts/storage slots. + +#### Example + +```ts +await cc.eth.loadChainState("chain-state.json"); +``` + +### load + +#### Function Signature + +```ts +public async load(contract: EthAddress, slot: bigint): Promise +``` + +#### Description + +Loads the value at a storage slot of a Ethereum contract. + +#### Example + +```solidity +contract LeetContract { + uint256 private leet = 1337; // slot 0 +} +``` + +```ts +const leetContractAddress = EthAddress.fromString("0x1234..."); +const value = await cc.eth.load(leetContractAddress, BigInt(0)); +console.log(value); // 1337 +``` + +### store + +#### Function Signature + +```ts +public async store(contract: EthAddress, slot: bigint, value: bigint): Promise +``` + +#### Description + +Stores the value in storage slot on a Ethereum contract. + +#### Example + +```solidity +contract LeetContract { + uint256 private leet = 1337; // slot 0 +} +``` + +```ts +const leetContractAddress = EthAddress.fromString("0x1234..."); +await cc.eth.store(leetContractAddress, BigInt(0), BigInt(1000)); +const value = await cc.eth.load(leetContractAddress, BigInt(0)); +console.log(value); // 1000 +``` + +### keccak256 + +#### Function Signature + +```ts +public keccak256(baseSlot: bigint, key: bigint): bigint +``` + +#### Description + +Computes the storage slot for a map key. + +#### Example + +```solidity +contract LeetContract { + uint256 private leet = 1337; // slot 0 + mapping(address => uint256) public balances; // base slot 1 +} +``` + +```ts +// find the storage slot for key `0xdead` in the balance map. +const address = BigInt("0x000000000000000000000000000000000000dead"); +const slot = cc.eth.keccak256(1n, address); +// store balance of 0xdead as 100 +await cc.eth.store(contractAddress, slot, 100n); +``` + +### startImpersonating + +#### Function Signature + +```ts +public async startImpersonating(who: EthAddress): Promise +``` + +#### Description + +Start impersonating an Ethereum account. +This allows you to use this address as a sender. + +#### Example + +```ts +await cc.eth.startImpersonating(EthAddress.fromString(address)); +``` + +### stopImpersonating + +#### Function Signature + +```ts +public async stopImpersonating(who: EthAddress): Promise +``` + +#### Description + +Stop impersonating an Ethereum account. +Stops an active impersonation started by startImpersonating. + +#### Example + +```ts +await cc.eth.stopImpersonating(EthAddress.fromString(address)); +``` + +### getBytecode + +#### Function Signature + +```ts +public async getBytecode(contract: EthAddress): Promise<`0x${string}`> +``` + +#### Description + +Get the bytecode for an Ethereum contract. + +#### Example + +```ts +const bytecode = await cc.eth.getBytecode(contract); // 0x6080604052348015610010... +``` + +### etch + +#### Function Signature + +```ts +public async etch(contract: EthAddress, bytecode: `0x${string}`): Promise +``` + +#### Description + +Set the bytecode for an Ethereum contract. + +#### Example + +```ts +const bytecode = `0x6080604052348015610010...`; +await cc.eth.etch(contract, bytecode); +console.log(await cc.eth.getBytecode(contract)); // 0x6080604052348015610010... +``` + +## Aztec related cheatcodes + +These are cheatcodes specific to manipulating the state of Aztec rollup. + +### Interface + +```ts +// Get the current aztec block number +public async blockNumber(): Promise + +// Set time of the next execution on aztec. It also modifies time on Ethereum for next execution and stores this time as the last rollup block on the rollup contract. +public async warp(to: number): Promise + +// Loads the value stored at the given slot in the public storage of the given contract. +public async loadPublic(who: AztecAddress, slot: Fr | bigint): Promise + +// Loads the value stored at the given slot in the private storage of the given contract. +public async loadPrivate(owner: AztecAddress, contract: AztecAddress, slot: Fr | bigint): Promise + +// Computes the slot value for a given map and key. +public computeSlotInMap(baseSlot: Fr | bigint, key: Fr | bigint): Fr +``` + +### blockNumber + +#### Function Signature + +```ts +public async blockNumber(): Promise +``` + +#### Description + +Get the current aztec block number. + +#### Example + +```ts +const blockNumber = await cc.aztec.blockNumber(); +``` + +### warp + +#### Function Signature + +```ts +public async warp(to: number): Promise +``` + +#### Description + +Sets the time on Ethereum and the time of the next block on Aztec. +Like with the corresponding Ethereum cheatcode, time can only be set in the future, not the past. +Otherwise, it will throw an error. + +#### Example + +```ts +const timestamp = await cc.eth.timestamp(); +const newTimestamp = timestamp + 100_000_000; +await cc.aztec.warp(newTimestamp); +// any Aztec.nr contract calls that make use of current timestamp +// and is executed in the next rollup block will now read `newTimestamp` +``` + +### computeSlotInMap + +#### Function Signature + +```ts +public computeSlotInMap(baseSlot: Fr | bigint, key: Fr | bigint): Fr +``` + +#### Description + +Compute storage slot for a map key. +The baseSlot is specified in the Aztec.nr contract. + +#### Example + +```rust +#[storage] +struct Storage { + balances: Map>, +} + +contract Token { + ... +} +``` + +```ts +const slot = cc.aztec.computeSlotInMap(1n, key); +``` + +### loadPublic + +#### Function Signature + +```ts +public async loadPublic(who: AztecAddress, slot: Fr | bigint): Promise +``` + +#### Description + +Loads the value stored at the given slot in the public storage of the given contract. + +Note: One Field element occupies a storage slot. Hence, structs with multiple field elements will be spread over multiple sequential slots. Using loadPublic will only load a single field of the struct (depending on the size of the attributes within it). + +#### Example + +```rust +#[storage] +struct Storage { + balances: Map>, +} + +contract Token { + ... +} +``` + +```ts +const address = AztecAddress.fromString("0x123..."); +const slot = cc.aztec.computeSlotInMap(1n, key); +const value = await cc.aztec.loadPublic(address, slot); +``` + +### loadPrivate + +#### Function Signature + +```ts +public async loadPrivate(owner: AztecAddress, contract: AztecAddress, slot: Fr | bigint): Promise +``` + +#### Description + +Loads the value stored at the given slot in the private storage of the given contract. + +Note: One Field element occupies a storage slot. Hence, structs with multiple field elements will be spread over multiple sequential slots. Using loadPublic will only load a single field of the struct (depending on the size of the attributes within it). + +#### Example + +```typescript title="load_private_cheatcode" showLineNumbers +const mintAmount = 100n; + +await mintTokensToPrivate(token, wallet, admin, mintAmount); +await token.methods.sync_private_state().simulate(); + +const balancesAdminSlot = await cc.aztec.computeSlotInMap(TokenContract.storage.balances.slot, admin); + +// check if note was added to pending shield: +const notes = await cc.aztec.loadPrivate(admin, token.address, balancesAdminSlot); + +// @note If you get pain for dinner, this guys is the reason. +// Assuming that it is still testing the token contract, you need to look at the balances, +// and then the type of note, currently a `UintNote` which stores fields: [owner, randomness, amount] +const values = notes.map(note => note.items[2]); +const balance = values.reduce((sum, current) => sum + current.toBigInt(), 0n); +expect(balance).toEqual(mintAmount); +``` +> Source code: yarn-project/end-to-end/src/e2e_cheat_codes.test.ts#L181-L198 + + +## Participate + +Keep up with the latest discussion and join the conversation in the [Aztec forum](https://discourse.aztec.network). + +You can also use the above link to request more cheatcodes. + + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/cli_reference.md b/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/cli_reference.md new file mode 100644 index 000000000000..22cfcdd424d0 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/cli_reference.md @@ -0,0 +1,1394 @@ +--- +title: CLI Reference +tags: [sandbox] +sidebar_position: 2 +--- +import { AztecTestnetVersion } from '@site/src/components/Snippets/general_snippets'; + +This reference guide provides documentation for the Aztec CLI commands (`aztec`) and their options. The CLI is a powerful tool for interacting with the Aztec network, managing accounts, deploying contracts, and more. + +Consider using the [`aztec-wallet`](./cli_wallet_reference.md) for account related or interacting with an existing network (e.g. testnet). + +## Overview + +The Aztec CLI provides commands for: + +- **Starting and Testing**: Starting the Aztec Sandbox and running tests +- **Account Management**: Creating, deploying, and managing Aztec accounts +- **Contract Operations**: Deploying, interacting with, and managing smart contracts +- **Network Information**: Querying node and network status +- **Transaction Management**: Sending, canceling, and querying transactions +- **Data Retrieval**: Accessing logs and contract data +- **Development Tools**: Profiling, debugging, and code generation +- **L1 Integration**: Managing L1 contracts and bridges +- **Governance**: Participating in protocol governance +- **P2P Network**: Managing peer-to-peer network configuration +- **Utilities**: Various helper commands for development + +Each command section includes detailed options and examples of usage. The documentation is organized to help you quickly find the commands you need for your specific use case. + +Note: Most commands accept a `--rpc-url` option to specify the Aztec node URL, and many accept fee-related options for gas limit and price configuration. + +## Common Commands + +- [`aztec get-node-info`](#get-node-info) +- [`aztec get-l1-addresses`](#get-l1-addresses) +- [`aztec get-block`](#get-block) +- [`aztec start --pxe`](#pxe-options) + +Example usage: + +```bash +# Start the sandbox +aztec start --sandbox + +# Start with custom ports +aztec start --sandbox --port 8081 + +# Start specific components +aztec start --pxe + +# Start with Ethereum options +aztec start --port 8081 --pxe --pxe.nodeUrl=$BOOTNODE --pxe.proverEnabled false --l1-chain-id 31337 + +# Start with storage options +aztec start --node --data-directory /path/to/data --data-store-map-size-kb 134217728 --registry-address +``` + +## Starting + +### start + +Initiates various Aztec modules. It can be used to start individual components or the entire Aztec Sandbox. + +```bash +aztec start [options] +``` + +Options: + +#### Sandbox Options + +- `-sb, --sandbox`: Starts the Aztec Sandbox. +- `--sandbox.noPXE [value]`: Do not expose PXE service on sandbox start. (default: false) + +#### Network Options + +- `--network `: Network to run Aztec on, e.g. `alpha-testnet`. By default connects to sandbox (local network) + +#### API Options + +- `-p, --port `: Port to run the Aztec Services on (default: 8080). +- `--admin-port `: Port to run admin APIs of Aztec Services on (default: 8880). +- `--api-prefix `: Prefix for API routes on any service that is started. + +#### Ethereum Options + +- `--l1-rpc-urls `: List of URLs of Ethereum RPC nodes that services will connect to (comma separated) (default: http://localhost:8545). +- `--l1-chain-id `: The L1 chain ID (default: 31337). +- `--l1-mnemonic `: Mnemonic for L1 accounts. Will be used if no publisher private keys are provided (default: test test test test test test test test test test test junk). +- `--l1-consensus-host-urls `: List of URLs of the Ethereum consensus nodes that services will connect to (comma separated). +- `--l1-consensus-host-api-keys `: List of API keys for the corresponding Ethereum consensus nodes. +- `--l1-consensus-host-api-key-headers `: List of API key headers for the corresponding Ethereum consensus nodes. If not set, the api key for the corresponding node will be appended to the URL as `?key=`. + +#### Storage Options + +- `--data-directory `: Where to store data for services. If not set, will store temporarily. +- `--data-store-map-size-kb `: The maximum possible size of the data store DB in KB. Can be overridden by component-specific options. + +#### L1 Contract Addresses + +- `--rollup-address `: The deployed L1 rollup contract address. +- `--registry-address `: The deployed L1 registry contract address. +- `--inbox-address `: The deployed L1 -> L2 inbox contract address. +- `--outbox-address `: The deployed L2 -> L1 outbox contract address. +- `--fee-juice-address `: The deployed L1 Fee Juice contract address. +- `--staking-asset-address `: The deployed L1 Staking Asset contract address. +- `--fee-juice-portal-address `: The deployed L1 Fee Juice portal contract address. + +#### Aztec Node Options + +- `--node`: Starts Aztec Node with options. +- `--node.archiver-url `: URL for an archiver service. +- `--node.deploy-aztec-contracts`: Deploys L1 Aztec contracts before starting the node. Needs mnemonic or private key to be set. +- `--node.deploy-aztec-contracts-salt `: Numeric salt for deploying L1 Aztec contracts before starting the node. Needs mnemonic or private key to be set. Implies --node.deploy-aztec-contracts. +- `--node.assume-proven-through-block-number `: Cheats the rollup contract into assuming every block until this one is proven. Useful for speeding up bootstraps. +- `--node.publisher-private-key `: Private key of account for publishing L1 contracts. +- `--node.world-state-block-check-interval-ms `: Frequency in which to check for blocks in ms (default: 100). +- `--node.sync-mode `: Set sync mode to `full` to always sync via L1, `snapshot` to download a snapshot if there is no local data, `force-snapshot` to download even if there is local data (default: snapshot). +- `--node.snapshots-url `: Base URL for downloading snapshots for snapshot sync. + +##### Example Usage + +Here is an example of how to start a node that connects to the alpha-testnet. + +```bash +aztec-up alpha-testnet + +export DATA_DIRECTORY=/any/directory/to/store/node/data +export BLOB_SINK_URL= +export LOG_LEVEL=info +export IP=Your_IP_address_here + +aztec start --node --network alpha-testnet + --l1-rpc-urls ... + --l1-consensus-host-urls ... + --l1-consensus-host-api-keys ... + --l1-consensus-host-api-key-headers X... + --p2p.p2pIp $IP + --archiver +``` + +#### P2P Subsystem Options + +- `--p2p-enabled [value]`: Enable P2P subsystem. +- `--p2p.block-check-interval-ms `: The frequency in which to check for new L2 blocks (default: 100). +- `--p2p.debug-disable-colocation-penalty `: DEBUG: Disable colocation penalty - NEVER set to true in production. +- `--p2p.peer-check-interval-ms `: The frequency in which to check for new peers (default: 30000). +- `--p2p.l2-queue-size `: Size of queue of L2 blocks to store (default: 1000). +- `--p2p.listen-address `: The listen address. ipv4 address (default: 0.0.0.0). +- `--p2p.p2p-port `: The port for the P2P service (default: 40400). +- `--p2p.p2p-ip `: The IP address for the P2P service. ipv4 address. +- `--p2p.peer-id-private-key `: An optional peer id private key. If blank, will generate a random key. +- `--p2p.peer-id-private-key-path `: An optional path to store generated peer id private keys. +- `--p2p.bootstrap-nodes `: A list of bootstrap peer ENRs to connect to. Separated by commas. +- `--p2p.bootstrap-node-enr-version-check `: Whether to check the version of the bootstrap node ENR. +- `--p2p.bootstrap-nodes-as-full-peers `: Whether to consider our configured bootnodes as full peers. +- `--p2p.max-peer-count `: The maximum number of peers to connect to (default: 100). +- `--p2p.query-for-ip `: If announceUdpAddress or announceTcpAddress are not provided, query for the IP address of the machine. Default is false. +- `--p2p.keep-proven-txs-in-pool-for `: How many blocks have to pass after a block is proven before its txs are deleted (zero to delete immediately once proven). +- `--p2p.keep-attestations-in-pool-for `: How many slots to keep attestations for (default: 96). +- `--p2p.gossipsub-interval `: The interval of the gossipsub heartbeat to perform maintenance tasks (default: 700). +- `--p2p.gossipsub-d `: The D parameter for the gossipsub protocol (default: 8). +- `--p2p.gossipsub-dlo `: The Dlo parameter for the gossipsub protocol (default: 4). +- `--p2p.gossipsub-dhi `: The Dhi parameter for the gossipsub protocol (default: 12). +- `--p2p.gossipsub-dlazy `: The Dlazy parameter for the gossipsub protocol (default: 8). +- `--p2p.gossipsub-flood-publish `: Whether to flood publish messages. - For testing purposes only (default: true). +- `--p2p.gossipsub-mcache-length `: The number of gossipsub interval message cache windows to keep (default: 6). +- `--p2p.gossipsub-mcache-gossip `: How many message cache windows to include when gossiping with other pears (default: 3). +- `--p2p.gossipsub-tx-topic-weight `: The weight of the tx topic for the gossipsub protocol (default: 1). +- `--p2p.gossipsub-tx-invalid-message-deliveries-weight `: The weight of the tx invalid message deliveries for the gossipsub protocol (default: -20). +- `--p2p.gossipsub-tx-invalid-message-deliveries-decay `: Determines how quickly the penalty for invalid message deliveries decays over time. Between 0 and 1 (default: 0.5). +- `--p2p.peer-penalty-values `: The values for the peer scoring system. Passed as a comma separated list of values in order: low, mid, high tolerance errors (default: 2,10,50). +- `--p2p.double-spend-severe-peer-penalty-window `: The "age" (in L2 blocks) of a tx after which we heavily penalize a peer for sending it (default: 30). +- `--p2p.block-request-batch-size `: The number of blocks to fetch in a single batch (default: 20). +- `--p2p.archived-tx-limit `: The number of transactions that will be archived. If the limit is set to 0 then archiving will be disabled. +- `--p2p.trusted-peers `: A list of trusted peers ENRs. Separated by commas. +- `--p2p.p2p-store-map-size-kb `: The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKB. +- `--p2p.tx-public-setup-allow-list `: The list of functions calls allowed to run in setup. +- `--p2p.max-tx-pool-size `: The maximum cumulative tx size of pending txs (in bytes) before evicting lower priority txs (default: 100000000). +- `--p2p.overall-request-timeout-ms `: The overall timeout for a request response operation (default: 4000). +- `--p2p.individual-request-timeout-ms `: The timeout for an individual request response peer interaction (default: 2000). +- `--p2p.rollup-version `: The version of the rollup. + +#### Telemetry Options + +- `--tel.metrics-collector-url `: The URL of the telemetry collector for metrics. +- `--tel.traces-collector-url `: The URL of the telemetry collector for traces. +- `--tel.logs-collector-url `: The URL of the telemetry collector for logs. +- `--tel.otel-collect-interval-ms `: The interval at which to collect metrics (default: 60000). +- `--tel.otel-export-timeout-ms `: The timeout for exporting metrics (default: 30000). +- `--tel.otel-exclude-metrics `: A list of metric prefixes to exclude from export. + +#### PXE Options + +- `--pxe`: Starts Aztec PXE with options. +- `--pxe.data-store-map-size-kb `: DB mapping size to be applied to all key/value stores (default: 134217728). +- `--pxe.rollup-version `: The version of the rollup. +- `--pxe.l2-block-batch-size `: Maximum amount of blocks to pull from the stream in one request when synchronizing (default: 200). +- `--pxe.bb-binary-path `: Path to the BB binary. +- `--pxe.bb-working-directory `: Working directory for the BB binary. +- `--pxe.bb-skip-cleanup `: True to skip cleanup of temporary files for debugging purposes. +- `--pxe.prover-enabled `: Enable real proofs (default: true). +- `--pxe.network `: External Aztec network to connect to (e.g. devnet). +- `--pxe.api-key `: API Key required by the external network's node. +- `--pxe.node-url `: Custom Aztec Node URL to connect to. + +##### Example Usage + +```bash +aztec start --port 8081 --pxe --pxe.nodeUrl=$BOOTNODE --pxe.proverEnabled true --l1-chain-id $L1_CHAIN_ID +``` + +#### Archiver Options + +- `--archiver`: Starts Aztec Archiver with options. +- `--archiver.blob-sink-url `: The URL of the blob sink. +- `--archiver.archive-api-url `: The URL of the archive API. +- `--archiver.archiver-polling-interval-ms `: The polling interval in ms for retrieving new L2 blocks and encrypted logs (default: 500). +- `--archiver.archiver-batch-size `: The number of L2 blocks the archiver will attempt to download at a time (default: 100). +- `--archiver.max-logs `: The max number of logs that can be obtained in 1 "getPublicLogs" call (default: 1000). +- `--archiver.archiver-store-map-size-kb `: The maximum possible size of the archiver DB in KB. Overwrites the general dataStoreMapSizeKB. +- `--archiver.rollup-version `: The version of the rollup. +- `--archiver.viem-polling-interval-ms `: The polling interval viem uses in ms (default: 1000). +- `--archiver.ethereum-slot-duration `: How many seconds an L1 slot lasts (default: 12). +- `--archiver.aztec-slot-duration `: How many seconds an L2 slots lasts (must be multiple of ethereum slot duration) (default: 24). +- `--archiver.aztec-epoch-duration `: How many L2 slots an epoch lasts (maximum AZTEC_MAX_EPOCH_DURATION) (default: 16). +- `--archiver.aztec-target-committee-size `: The target validator committee size (default: 48). +- `--archiver.aztec-proof-submission-window `: The number of L2 slots that a proof for an epoch can be submitted in, starting from the beginning of the epoch (default: 31). +- `--archiver.minimum-stake `: The minimum stake for a validator (default: 100000000000000000000). +- `--archiver.slashing-quorum `: The slashing quorum (default: 6). +- `--archiver.slashing-round-size `: The slashing round size (default: 10). +- `--archiver.governance-proposer-quorum `: The governance proposing quorum (default: 51). +- `--archiver.governance-proposer-round-size `: The governance proposing round size (default: 100). +- `--archiver.mana-target `: The mana target for the rollup (default: 10000000000). +- `--archiver.proving-cost-per-mana `: The proving cost per mana (default: 100). +- `--archiver.gas-limit-buffer-percentage `: How much to increase calculated gas limit by (percentage) (default: 20). +- `--archiver.max-gwei `: Maximum gas price in gwei (default: 500). +- `--archiver.max-blob-gwei `: Maximum blob fee per gas in gwei (default: 1500). +- `--archiver.priority-fee-bump-percentage `: How much to increase priority fee by each attempt (percentage) (default: 20). +- `--archiver.priority-fee-retry-bump-percentage `: How much to increase priority fee by each retry attempt (percentage) (default: 50). +- `--archiver.fixed-priority-fee-per-gas `: Fixed priority fee per gas in Gwei. Overrides any priority fee bump percentage. +- `--archiver.max-attempts `: Maximum number of speed-up attempts (default: 3). +- `--archiver.check-interval-ms `: How often to check tx status (default: 1000). +- `--archiver.stall-time-ms `: How long before considering tx stalled (default: 45000). +- `--archiver.tx-timeout-ms `: How long to wait for a tx to be mined before giving up. Set to 0 to disable (default: 300000). +- `--archiver.tx-propagation-max-query-attempts `: How many attempts will be done to get a tx after it was sent (default: 3). + +#### Sequencer Options + +- `-n, --node [options]`: Starts the Aztec Node with specified options. +- `-px, --pxe [options]`: Starts the PXE (Private eXecution Environment) with specified options. +- `-a, --archiver [options]`: Starts the Archiver with specified options. +- `-s, --sequencer [options]`: Starts the Sequencer with specified options. +- `-r, --prover [options]`: Starts the Prover Agent with specified options. +- `-o, --prover-node [options]`: Starts the Prover Node with specified options. +- `-p2p, --p2p-bootstrap [options]`: Starts the P2P Bootstrap node with specified options. +- `-t, --txe [options]`: Starts the TXE (Transaction Execution Environment) with specified options. +- `--faucet [options]`: Starts the Aztec faucet service with specified options. +- `--sequencer.validator-private-key `: The private key of the validator participating in attestation duties. +- `--sequencer.disable-validator `: Do not run the validator. +- `--sequencer.attestation-polling-interval-ms `: Interval between polling for new attestations (default: 200). +- `--sequencer.validator-reexecute `: Re-execute transactions before attesting (default: true). +- `--sequencer.transaction-polling-interval-ms `: The number of ms to wait between polling for pending txs (default: 500). +- `--sequencer.max-txs-per-block `: The maximum number of txs to include in a block (default: 32). +- `--sequencer.min-txs-per-block `: The minimum number of txs to include in a block (default: 1). +- `--sequencer.max-l2-block-gas `: The maximum L2 block gas (default: 10000000000). +- `--sequencer.max-da-block-gas `: The maximum DA block gas (default: 10000000000). +- `--sequencer.coinbase `: Recipient of block reward. +- `--sequencer.fee-recipient `: Address to receive fees. +- `--sequencer.acvm-working-directory `: The working directory to use for simulation/proving. +- `--sequencer.acvm-binary-path `: The path to the ACVM binary. +- `--sequencer.max-block-size-in-bytes `: Max block size (default: 1048576). +- `--sequencer.enforce-time-table `: Whether to enforce the time table when building blocks (default: true). +- `--sequencer.governance-proposer-payload `: The address of the payload for the governanceProposer (default: 0x0000000000000000000000000000000000000000). +- `--sequencer.max-l1-tx-inclusion-time-into-slot `: How many seconds into an L1 slot we can still send a tx and get it mined. +- `--sequencer.tx-public-setup-allow-list `: The list of functions calls allowed to run in setup. +- `--sequencer.viem-polling-interval-ms `: The polling interval viem uses in ms (default: 1000). +- `--sequencer.custom-forwarder-contract-address `: The address of the custom forwarder contract (default: 0x0000000000000000000000000000000000000000). +- `--sequencer.publisher-private-key `: The private key to be used by the publisher (default: 0x0000000000000000000000000000000000000000000000000000000000000000). +- `--sequencer.l1-publish-retry-interval-ms `: The interval to wait between publish retries (default: 1000). +- `--sequencer.gas-limit-buffer-percentage `: How much to increase calculated gas limit by (percentage) (default: 20). +- `--sequencer.max-gwei `: Maximum gas price in gwei (default: 500). +- `--sequencer.max-blob-gwei `: Maximum blob fee per gas in gwei (default: 1500). +- `--sequencer.priority-fee-bump-percentage `: How much to increase priority fee by each attempt (percentage) (default: 20). +- `--sequencer.priority-fee-retry-bump-percentage `: How much to increase priority fee by each retry attempt (percentage) (default: 50). +- `--sequencer.fixed-priority-fee-per-gas `: Fixed priority fee per gas in Gwei. Overrides any priority fee bump percentage. +- `--sequencer.max-attempts `: Maximum number of speed-up attempts (default: 3). +- `--sequencer.check-interval-ms `: How often to check tx status (default: 1000). +- `--sequencer.stall-time-ms `: How long before considering tx stalled (default: 45000). +- `--sequencer.tx-timeout-ms `: How long to wait for a tx to be mined before giving up. Set to 0 to disable (default: 300000). +- `--sequencer.tx-propagation-max-query-attempts `: How many attempts will be done to get a tx after it was sent (default: 3). +- `--sequencer.blob-sink-url `: The URL of the blob sink. +- `--sequencer.archive-api-url `: The URL of the archive API. +- `--sequencer.rollup-version `: The version of the rollup. +- `--sequencer.ethereum-slot-duration `: How many seconds an L1 slot lasts (default: 12). +- `--sequencer.aztec-slot-duration `: How many seconds an L2 slots lasts (must be multiple of ethereum slot duration) (default: 24). +- `--sequencer.aztec-epoch-duration `: How many L2 slots an epoch lasts (maximum AZTEC_MAX_EPOCH_DURATION) (default: 16). +- `--sequencer.aztec-proof-submission-window `: The number of L2 slots that a proof for an epoch can be submitted in, starting from the beginning of the epoch (default: 31). + +#### Example Usage + +```bash +aztec start --network alpha-testnet --l1-rpc-urls https://example.com --l1-consensus-host-urls https://example.com --sequencer.blobSinkUrl http://34.82.117.158:5052 --sequencer.validatorPrivateKey 0xYourPrivateKey --sequencer.coinbase 0xYourAddress --p2p.p2pIp 999.99.999.99 +``` + +#### Blob Sink Options + +- `--blob-sink`: Starts Aztec Blob Sink with options. +- `--blob-sink.port `: The port to run the blob sink server on. +- `--blob-sink.archive-api-url `: The URL of the archive API. +- `--blob-sink.data-store-map-size-kb `: DB mapping size to be applied to all key/value stores (default: 134217728). +- `--blob-sink.rollup-version `: The version of the rollup. +- `--blob-sink.viem-polling-interval-ms `: The polling interval viem uses in ms (default: 1000). + +#### Prover Node Options + +- `--prover-node`: Starts Aztec Prover Node with options. +- `--prover-node.archiver-url `: URL for an archiver service. +- `--prover-node.acvm-working-directory `: The working directory to use for simulation/proving. +- `--prover-node.acvm-binary-path `: The path to the ACVM binary. +- `--prover-node.bb-working-directory `: The working directory to use for proving. +- `--prover-node.bb-binary-path `: The path to the bb binary. +- `--prover-node.bb-skip-cleanup `: Whether to skip cleanup of bb temporary files. +- `--prover-node.node-url `: The URL to the Aztec node to take proving jobs from. +- `--prover-node.prover-id `: Hex value that identifies the prover. Defaults to the address used for submitting proofs if not set. +- `--prover-node.failed-proof-store `: Store for failed proof inputs. Google cloud storage is only supported at the moment. Set this value as gs://bucket-name/path/to/store. +- `--prover-node.world-state-block-check-interval-ms `: The frequency in which to check (default: 100). +- `--prover-node.world-state-proven-blocks-only `: Whether to follow only the proven chain. +- `--prover-node.world-state-block-request-batch-size `: Size of the batch for each get-blocks request from the synchronizer to the archiver. +- `--prover-node.world-state-db-map-size-kb `: The maximum possible size of the world state DB in KB. Overwrites the general dataStoreMapSizeKB. +- `--prover-node.world-state-data-directory `: Optional directory for the world state database. +- `--prover-node.world-state-block-history `: The number of historic blocks to maintain. Values less than 1 mean all history is maintained (default: 64). +- `--prover-node.l1-publish-retry-interval-ms `: The interval to wait between publish retries (default: 1000). +- `--prover-node.custom-forwarder-contract-address `: The address of the custom forwarder contract (default: 0x0000000000000000000000000000000000000000). +- `--prover-node.publisher-private-key `: The private key to be used by the publisher (default: 0x0000000000000000000000000000000000000000000000000000000000000000). +- `--prover-node.prover-coordination-node-url `: The URL of the tx provider node. +- `--prover-node.prover-node-max-pending-jobs `: The maximum number of pending jobs for the prover node (default: 10). +- `--prover-node.prover-node-polling-interval-ms `: The interval in milliseconds to poll for new jobs (default: 1000). +- `--prover-node.prover-node-max-parallel-blocks-per-epoch `: The Maximum number of blocks to process in parallel while proving an epoch (default: 32). +- `--prover-node.tx-gathering-timeout-ms `: The maximum amount of time to wait for tx data to be available (default: 60000). +- `--prover-node.tx-gathering-interval-ms `: How often to check that tx data is available (default: 1000). +- `--prover-node.tx-gathering-max-parallel-requests `: How many txs to load up a time (default: 100). +- `--prover-node.test-accounts `: Whether to populate the genesis state with initial fee juice for the test accounts. +- `--prover-node.sponsored-fpc `: Whether to populate the genesis state with initial fee juice for the sponsored FPC. +- `--prover-node.sync-mode `: Set sync mode to `full` to always sync via L1, `snapshot` to download a snapshot if there is no local data, `force-snapshot` to download even if there is local data (default: snapshot). +- `--prover-node.snapshots-url `: Base URL for snapshots index. + +#### Prover Broker Options + +- `--prover-broker`: Starts Aztec proving job broker. +- `--prover-broker.prover-broker-job-timeout-ms `: Jobs are retried if not kept alive for this long (default: 30000). +- `--prover-broker.prover-broker-poll-interval-ms `: The interval to check job health status (default: 1000). +- `--prover-broker.prover-broker-job-max-retries `: If starting a prover broker locally, the max number of retries per proving job (default: 3). +- `--prover-broker.prover-broker-batch-size `: The prover broker writes jobs to disk in batches (default: 100). +- `--prover-broker.prover-broker-batch-interval-ms `: How often to flush batches to disk (default: 50). +- `--prover-broker.prover-broker-max-epochs-to-keep-results-for `: The maximum number of epochs to keep results for (default: 1). +- `--prover-broker.prover-broker-store-map-size-kb `: The size of the prover broker's database. Will override the dataStoreMapSizeKB if set. +- `--prover-broker.data-store-map-size-kb `: DB mapping size to be applied to all key/value stores (default: 134217728). +- `--prover-broker.viem-polling-interval-ms `: The polling interval viem uses in ms (default: 1000). +- `--prover-broker.rollup-version `: The version of the rollup. + +#### Prover Agent Options + +- `--prover-agent`: Starts Aztec Prover Agent with options. +- `--prover-agent.prover-agent-count `: Whether this prover has a local prover agent (default: 1). +- `--prover-agent.prover-agent-poll-interval-ms `: The interval agents poll for jobs at (default: 100). +- `--prover-agent.prover-agent-proof-types `: The types of proofs the prover agent can generate. +- `--prover-agent.prover-broker-url `: The URL where this agent takes jobs from. +- `--prover-agent.real-proofs `: Whether to construct real proofs (default: true). +- `--prover-agent.prover-test-delay-type `: The type of artificial delay to introduce (default: fixed). +- `--prover-agent.prover-test-delay-ms `: Artificial delay to introduce to all operations to the test prover. +- `--prover-agent.prover-test-delay-factor `: If using realistic delays, what percentage of realistic times to apply (default: 1). + +#### P2P Bootstrap Options + +- `--p2p-bootstrap`: Starts Aztec P2P Bootstrap with options. +- `--p2p-bootstrap.peer-id-private-key-path `: An optional path to store generated peer id private keys. +- `--p2p-bootstrap.data-store-map-size-kb `: DB mapping size to be applied to all key/value stores (default: 134217728). + +#### Bot Options + +- `--bot`: Starts Aztec Bot with options. +- `--bot.node-url `: The URL to the Aztec node to check for tx pool status. +- `--bot.node-admin-url `: The URL to the Aztec node admin API to force-flush txs if configured. +- `--bot.pxe-url `: URL to the PXE for sending txs, or undefined if an in-proc PXE is used. +- `--bot.l1-mnemonic `: The mnemonic for the account to bridge fee juice from L1. +- `--bot.l1-private-key `: The private key for the account to bridge fee juice from L1. +- `--bot.sender-private-key `: Signing private key for the sender account. +- `--bot.sender-salt `: The salt to use to deploys the sender account. +- `--bot.recipient-encryption-secret `: Encryption secret for a recipient account (default: 0x00000000000000000000000000000000000000000000000000000000cafecafe). +- `--bot.token-salt `: Salt for the token contract deployment (default: 0x0000000000000000000000000000000000000000000000000000000000000001). +- `--bot.tx-interval-seconds `: Every how many seconds should a new tx be sent (default: 60). +- `--bot.private-transfers-per-tx `: How many private token transfers are executed per tx (default: 1). +- `--bot.public-transfers-per-tx `: How many public token transfers are executed per tx (default: 1). +- `--bot.fee-payment-method `: How to handle fee payments. (Options: fee_juice) (default: fee_juice). +- `--bot.no-start `: True to not automatically setup or start the bot on initialization. +- `--bot.tx-mined-wait-seconds `: How long to wait for a tx to be mined before reporting an error (default: 180). +- `--bot.follow-chain `: Which chain the bot follows (default: NONE). +- `--bot.max-pending-txs `: Do not send a tx if the node's tx pool already has this many pending txs (default: 128). +- `--bot.flush-setup-transactions `: Make a request for the sequencer to build a block after each setup transaction. +- `--bot.skip-public-simulation `: Whether to skip public simulation of txs before sending them. +- `--bot.l2-gas-limit `: L2 gas limit for the tx (empty to have the bot trigger an estimate gas). +- `--bot.da-gas-limit `: DA gas limit for the tx (empty to have the bot trigger an estimate gas). +- `--bot.contract `: Token contract to use (default: TokenContract). +- `--bot.max-consecutive-errors `: The maximum number of consecutive errors before the bot shuts down. +- `--bot.stop-when-unhealthy `: Stops the bot if service becomes unhealthy. +- `--bot.amm-txs `: Deploy an AMM and send swaps to it. + +#### TXE Options + +- `--txe`: Starts Aztec TXE with options. + +#### Faucet Options + +- `--faucet`: Starts the Aztec faucet. +- `--faucet.api-server`: Starts a simple HTTP server to access the faucet (default: true). +- `--faucet.api-server-port `: The port on which to start the api server on (default: 8080). +- `--faucet.viem-polling-interval-ms `: The polling interval viem uses in ms (default: 1000). +- `--faucet.l1-mnemonic `: The mnemonic for the faucet account. +- `--faucet.mnemonic-account-index `: The account to use. +- `--faucet.interval `: How often the faucet can be dripped (default: 3600000). +- `--faucet.eth-amount `: How much eth the faucet should drip per call (default: 1.0). +- `--faucet.l1-assets `: Which other L1 assets the faucet is able to drip. + +### Test + +Runs tests written in contracts. + +```bash +aztec test [options] +``` + +Options: + +- `-e, --env `: Set environment variables (can be used multiple times). +- `--no-tty`: Run the container without a TTY. +- `--rm`: Automatically remove the container when it exits. +- `-i, --interactive`: Keep STDIN open even if not attached. +- `-t, --tty`: Allocate a pseudo-TTY. + +## Account Management + +Consider using the [`aztec-wallet`](./cli_wallet_reference.md) for account management (or contract interaction) related actions, since it has a PXE internally and manages aliases to get you started quicker. + +`aztec` cli requires you to have a PXE running already (either as part of when you run the sandbox by default or just a separate PXE) + +### create-account + +Creates an Aztec account for sending transactions. + +```bash +aztec create-account [options] +``` + +Options: + +- `--skip-initialization`: Skip initializing the account contract. Useful for publicly deploying an existing account. +- `--public-deploy`: Publicly deploys the account and registers the class if needed. +- `-p, --public-key `: Public key that identifies a private signing key stored outside of the wallet. Used for ECDSA SSH accounts over the secp256r1 curve. +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) +- `-sk, --secret-key `: Secret key for account. Uses random by default. (env: SECRET_KEY) +- `-t, --type `: Type of account to create (choices: "schnorr", "ecdsasecp256r1", "ecdsasecp256r1ssh", "ecdsasecp256k1", default: "schnorr") +- `--register-only`: Just register the account on the PXE. Do not deploy or initialize the account contract. +- `--json`: Emit output as json +- `--no-wait`: Skip waiting for the contract to be deployed. Print the hash of deployment transaction +- `--payment `: Fee payment method and arguments. + Parameters: + - `method`: Valid values: "fee_juice", "fpc-public", "fpc-private", "fpc-sponsored" Default: fee_juice + - `feePayer`: The account paying the fee. + - `asset`: The asset used for fee payment. Required for "fpc-public" and "fpc-private". + - `fpc`: The FPC contract that pays in fee juice. Not required for the "fee_juice" method. + - `claim`: Whether to use a previously stored claim to bridge fee juice. + - `claimSecret`: The secret to claim fee juice on L1. + - `claimAmount`: The amount of fee juice to be claimed. + - `messageLeafIndex`: The index of the claim in the l1toL2Message tree. + - `feeRecipient`: Recipient of the fee. + - Format: --payment method=name,feePayer=address,asset=address ... +- `--gas-limits `: Gas limits for the tx. +- `--max-fees-per-gas `: Maximum fees per gas unit for DA and L2 computation. +- `--max-priority-fees-per-gas `: Maximum priority fees per gas unit for DA and L2 computation. +- `--no-estimate-gas`: Whether to automatically estimate gas limits for the tx. +- `--estimate-gas-only`: Only report gas estimation for the tx, do not send it. + +### deploy-account + +Deploys an already registered aztec account that can be used for sending transactions. + +```bash +aztec deploy-account [options] +``` + +Options: + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) +- `--json`: Emit output as json +- `--no-wait`: Skip waiting for the contract to be deployed. Print the hash of deployment transaction +- `--register-class`: Register the contract class (useful for when the contract class has not been deployed yet). +- `--payment `: Fee payment method and arguments. + Parameters: + - `method`: Valid values: "fee_juice", "fpc-public", "fpc-private", "fpc-sponsored" Default: fee_juice + - `feePayer`: The account paying the fee. + - `asset`: The asset used for fee payment. Required for "fpc-public" and "fpc-private". + - `fpc`: The FPC contract that pays in fee juice. Not required for the "fee_juice" method. + - `claim`: Whether to use a previously stored claim to bridge fee juice. + - `claimSecret`: The secret to claim fee juice on L1. + - `claimAmount`: The amount of fee juice to be claimed. + - `messageLeafIndex`: The index of the claim in the l1toL2Message tree. + - `feeRecipient`: Recipient of the fee. + - Format: --payment method=name,feePayer=address,asset=address ... +- `--gas-limits `: Gas limits for the tx. +- `--max-fees-per-gas `: Maximum fees per gas unit for DA and L2 computation. +- `--max-priority-fees-per-gas `: Maximum priority fees per gas unit for DA and L2 computation. +- `--no-estimate-gas`: Whether to automatically estimate gas limits for the tx. +- `--estimate-gas-only`: Only report gas estimation for the tx, do not send it. + +### get-accounts + +Retrieves all Aztec accounts stored in the PXE. + +```bash +aztec get-accounts [options] +``` + +Options: + +- `--json`: Emit output as JSON. + +### get-account + +Retrieves an account given its Aztec address. + +```bash +aztec get-account
[options] +``` + +Arguments: + +- `address`: The Aztec address to get account for + +Options: + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) + +### register-sender + +Registers a sender's address in the wallet, so the note synching process will look for notes sent by them. + +```bash +aztec register-sender [options] [address] +``` + +Arguments: + +- `address`: The address of the sender to register + +Options: + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) + +### create-authwit + +Creates an authorization witness that can be privately sent to a caller so they can perform an action on behalf of the provided account. + +```bash +aztec create-authwit [options] +``` + +Arguments: + +- `functionName`: Name of function to authorize +- `caller`: Account to be authorized to perform the action + +Options: + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) +- `--args [args...]`: Function arguments (default: []) +- `-ca, --contract-address
`: Aztec address of the contract. +- `-c, --contract-artifact `: Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package@contract +- `-sk, --secret-key `: The sender's secret key (env: SECRET_KEY) + +### authorize-action + +Authorizes a public call on the caller, so they can perform an action on behalf of the provided account. + +```bash +aztec authorize-action [options] +``` + +Arguments: + +- `functionName`: Name of function to authorize +- `caller`: Account to be authorized to perform the action + +Options: + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) +- `--args [args...]`: Function arguments (default: []) +- `-ca, --contract-address
`: Aztec address of the contract. +- `-c, --contract-artifact `: Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package@contract +- `-sk, --secret-key `: The sender's secret key (env: SECRET_KEY) + +## Contract Deployment and Interaction + +### deploy + +Deploys a compiled Aztec.nr contract to Aztec. + +```bash +aztec deploy [options] +``` + +Arguments: + +- `artifact`: Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package@contract + +Options: + +- `--init `: The contract initializer function to call (default: "constructor"). +- `--no-init`: Leave the contract uninitialized. +- `-a, --args `: Contract constructor arguments. +- `-k, --public-key `: Optional encryption public key for this address. +- `-s, --salt `: Optional deployment salt for generating the deployment address. +- `-sk, --secret-key `: The sender's secret key (env: SECRET_KEY) +- `--universal`: Do not mix the sender address into the deployment. +- `--json`: Emit output as JSON. +- `--no-wait`: Skip waiting for the contract deployment. +- `--no-class-registration`: Don't register this contract class. +- `--no-public-deployment`: Don't emit this contract's public bytecode. +- `--payment `: Fee payment method and arguments. + Parameters: + - `method`: Valid values: "fee_juice", "fpc-public", "fpc-private", "fpc-sponsored" Default: fee_juice + - `feePayer`: The account paying the fee. + - `asset`: The asset used for fee payment. Required for "fpc-public" and "fpc-private". + - `fpc`: The FPC contract that pays in fee juice. Not required for the "fee_juice" method. + - `claim`: Whether to use a previously stored claim to bridge fee juice. + - `claimSecret`: The secret to claim fee juice on L1. + - `claimAmount`: The amount of fee juice to be claimed. + - `messageLeafIndex`: The index of the claim in the l1toL2Message tree. + - `feeRecipient`: Recipient of the fee. + - Format: --payment method=name,feePayer=address,asset=address ... +- `--gas-limits `: Gas limits for the tx. +- `--max-fees-per-gas `: Maximum fees per gas unit for DA and L2 computation. +- `--max-priority-fees-per-gas `: Maximum priority fees per gas unit for DA and L2 computation. +- `--no-estimate-gas`: Whether to automatically estimate gas limits for the tx. +- `--estimate-gas-only`: Only report gas estimation for the tx, do not send it. + +### send + +Calls a function on an Aztec contract. + +```bash +aztec send [options] +``` + +Arguments: + +- `functionName`: Name of function to execute + +Options: + +- `-a, --args [functionArgs...]`: Function arguments. +- `-c, --contract-artifact `: Compiled Aztec.nr contract's ABI. +- `-ca, --contract-address
`: Aztec address of the contract. +- `-sk, --secret-key `: The sender's secret key (env: SECRET_KEY) +- `--no-wait`: Print transaction hash without waiting for it to be mined. +- `--payment `: Fee payment method and arguments. + Parameters: + - `method`: Valid values: "fee_juice", "fpc-public", "fpc-private", "fpc-sponsored" Default: fee_juice + - `feePayer`: The account paying the fee. + - `asset`: The asset used for fee payment. Required for "fpc-public" and "fpc-private". + - `fpc`: The FPC contract that pays in fee juice. Not required for the "fee_juice" method. + - `claim`: Whether to use a previously stored claim to bridge fee juice. + - `claimSecret`: The secret to claim fee juice on L1. + - `claimAmount`: The amount of fee juice to be claimed. + - `messageLeafIndex`: The index of the claim in the l1toL2Message tree. + - `feeRecipient`: Recipient of the fee. + - Format: --payment method=name,feePayer=address,asset=address ... +- `--gas-limits `: Gas limits for the tx. +- `--max-fees-per-gas `: Maximum fees per gas unit for DA and L2 computation. +- `--max-priority-fees-per-gas `: Maximum priority fees per gas unit for DA and L2 computation. +- `--no-estimate-gas`: Whether to automatically estimate gas limits for the tx. +- `--estimate-gas-only`: Only report gas estimation for the tx, do not send it. + +### simulate + +Simulates the execution of a function on an Aztec contract. + +```bash +aztec simulate [options] +``` + +Arguments: + +- `functionName`: Name of function to simulate + +Options: + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) +- `--args [args...]`: Function arguments (default: []) +- `-ca, --contract-address
`: Aztec address of the contract. +- `-c, --contract-artifact `: Path to a compiled Aztec contract's artifact in JSON format. If executed + inside a nargo workspace, a package and contract name can be specified as +- `-sk, --secret-key `: The sender's secret key (env: SECRET_KEY) + +### add-contract + +Adds an existing contract to the PXE. This is useful if you have deployed a contract outside of the PXE and want to use it with the PXE. + +```bash +aztec add-contract [options] +``` + +Required options: + +- `-c, --contract-artifact `: Compiled Aztec.nr contract's ABI. +- `-ca, --contract-address
`: Aztec address of the contract. +- `--init-hash `: Initialization hash. + +Optional: + +- `--salt `: Optional deployment salt. +- `-p, --public-key `: Optional public key for this contract. +- `--portal-address
`: Optional address to a portal contract on L1. +- `--deployer-address
`: Optional address of the contract deployer. + +### register-contract + +Registers a contract in this wallet's PXE. + +```bash +aztec register-contract [options] [address] [artifact] +``` + +Arguments: + +- `address`: The address of the contract to register +- `artifact`: Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo + workspace, a package and contract name can be specified as package@contract + +Options: + +- `--init `: The contract initializer function to call (default: "constructor") +- `-k, --public-key `: Optional encryption public key for this address. Set this value only if this contract is + expected to receive private notes, which will be encrypted using this public key. +- `-s, --salt `: Optional deployment salt as a hex string for generating the deployment address. +- `--deployer `: The address of the account that deployed the contract +- `--args [args...]`: Constructor arguments (default: []) + +### inspect-contract + +Shows a list of external callable functions for a contract. + +```bash +aztec inspect-contract +``` + +Arguments: + +- `contractArtifactFile`: A compiled Noir contract's artifact in JSON format or name of a contract artifact exported by + @aztec/noir-contracts.js + +### parse-parameter-struct + +Helper for parsing an encoded string into a contract's parameter struct. + +```bash +aztec parse-parameter-struct [options] +``` + +Arguments: + +- `encodedString`: The encoded hex string + +Required options: + +- `-c, --contract-artifact `: Compiled Aztec.nr contract's ABI. +- `-p, --parameter `: The name of the struct parameter to decode into. + +## Network and Node Information + +### get-node-info + +Retrieves information about an Aztec node at a URL. + +```bash +aztec get-node-info [options] +``` + +Options: + +- `--node-url `: URL of the node. +- `--json`: Emit output as JSON. + +### get-pxe-info + +Retrieves information about a PXE at a URL. + +```bash +aztec get-pxe-info [options] +``` + +### block-number + +Retrieves the current Aztec L2 block number. + +```bash +aztec block-number [options] +``` + +### get-contract-data + +Gets information about the Aztec contract deployed at the specified address. + +```bash +aztec get-contract-data [options] +``` + +Arguments: + +- `contractAddress`: Aztec address of the contract. + +Options: + +- `-b, --include-bytecode`: Include the contract's public function bytecode, if any. + +## Transaction and Block Querying + +### get-tx + +Retrieves the receipt for a specified transaction hash. + +```bash +aztec get-tx [options] +``` + +Arguments: + +- `txHash`: A transaction hash to get the receipt for. + +Options: + +- `-u, --rpc-url ` URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) +- `-p, --page ` The page number to display (default: 1) + `-s, --page-size ` The number of transactions to display per page (default: 10) +- `-h, --help` display help for command + +### cancel-tx + +Cancels a pending tx by reusing its nonce with a higher fee and an empty payload. + +```bash +aztec cancel-tx [options] +``` + +Arguments: + +- `txHash`: A transaction hash to cancel. + +Options: + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) +- `-sk, --secret-key `: The sender's secret key (env: SECRET_KEY) +- `--payment `: Fee payment method and arguments. + Parameters: + - `method`: Valid values: "fee_juice", "fpc-public", "fpc-private", "fpc-sponsored" Default: fee_juice + - `asset`: The asset used for fee payment. Required for "fpc-public" and "fpc-private". + - `fpc`: The FPC contract that pays in fee juice. Not required for the "fee_juice" method. + - `claim`: Whether to use a previously stored claim to bridge fee juice. + - `claimSecret`: The secret to claim fee juice on L1. + - `claimAmount`: The amount of fee juice to be claimed. + - `messageLeafIndex`: The index of the claim in the l1toL2Message tree. + - `feeRecipient`: Recipient of the fee. + - Format: --payment method=name,asset=address,fpc=address ... (default: "method=fee_juice") +- `-i, --increased-fees `: The amounts by which the fees are increased (default: "feePerDaGas":"0x0000000000000000000000000000000000000000000000000000000000000001","feePerL2Gas":"0x0000000000000000000000000000000000000000000000000000000000000001") +- `--max-fees-per-gas `: Maximum fees per gas unit for DA and L2 computation. + +### get-block + +Retrieves information for a given block or the latest block. + +```bash +aztec get-block [blockNumber] [options] +``` + +Arguments: + +- `blockNumber`: Block height + +Options: + +- `-f, --follow`: Keep polling for new blocks. + +## Logging and Data Retrieval + +### get-logs + +Retrieves unencrypted logs based on filter parameters. + +```bash +aztec get-logs [options] +``` + +Options: + +- `-tx, --tx-hash `: Transaction hash to get the receipt for. +- `-fb, --from-block `: Initial block number for getting logs. +- `-tb, --to-block `: Up to which block to fetch logs. +- `-al --after-log `: ID of a log after which to fetch the logs. +- `-ca, --contract-address
`: Contract address to filter logs by. +- `--follow`: Keep polling for new logs until interrupted. + +## Development and Debugging Tools + +### flamegraph + +Generates a flamegraph of the gate counts of a private function call. + +```bash +[SERVE=1] aztec flamegraph +``` + +### codegen + +Validates and generates an Aztec Contract ABI from Noir ABI. + +```bash +aztec codegen [options] +``` + +Arguments: + +- `noir-abi-path`: Path to the Noir ABI or project dir. + +Options: + +- `-o, --outdir `: Output folder for the generated code. +- `-f, --force`: Force code generation even when the contract has not changed. + +### update + +Updates Nodejs and Noir dependencies. + +```bash +aztec update [projectPath] [options] +``` + +Arguments: + +- `projectPath`: Path to the project directory (default: "/home/josh") + +Options: + +- `--contract [paths...]`: Paths to contracts to update dependencies. +- `--aztec-version `: The version to update Aztec packages to (default: latest). + +### profile + +Profiles a private function by counting the unconditional operations in its execution steps. + +```bash +aztec profile [options] +``` + +Arguments: + +- `functionName`: Name of function to simulate + +Options: + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) +- `--args [args...]`: Function arguments (default: []) +- `-ca, --contract-address
`: Aztec address of the contract. +- `-c, --contract-artifact `: Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package@contract +- `--debug-execution-steps-dir
`: Directory to write execution step artifacts for bb profiling/debugging. +- `-sk, --secret-key `: The sender's secret key (env: SECRET_KEY) + +### generate-secret-and-hash + +Generates an arbitrary secret (Fr), and its hash (using aztec-nr defaults). + +```bash +aztec generate-secret-and-hash +``` + +## L1 Contract Management + +### deploy-l1-contracts + +Deploys all necessary Ethereum contracts for Aztec. + +```bash +aztec deploy-l1-contracts [options] +``` + +Options: + +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"], env: ETHEREUM_HOSTS) +- `-pk, --private-key `: The private key to use for deployment +- `--validators `: Comma separated list of validators +- `-m, --mnemonic `: The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") +- `-i, --mnemonic-index `: The index of the mnemonic to use in deployment (default: 0) +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) +- `--salt `: The optional salt to use in deployment +- `--json`: Output the contract addresses in JSON format +- `--test-accounts`: Populate genesis state with initial fee juice for test accounts +- `--sponsored-fpc`: Populate genesis state with a testing sponsored FPC contract +- `--accelerated-test-deployments`: Fire and forget deployment transactions, use in testing only (default: false) + +### deploy-l1-verifier + +Deploys the rollup verifier contract. + +```bash +aztec deploy-l1-verifier [options] +``` + +Options: + +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"], env: ETHEREUM_HOSTS) +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) +- `--rollup-address `: The address of the rollup contract (env: ROLLUP_CONTRACT_ADDRESS) +- `--l1-private-key `: The L1 private key to use for deployment +- `-m, --mnemonic `: The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") +- `--verifier `: Either mock or real (default: "real") + +### deploy-new-rollup + +Deploys a new rollup contract and adds it to the registry (if you are the owner). + +```bash +aztec deploy-new-rollup [options] +``` + +Options: + +- `-r, --registry-address `: The address of the registry contract +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"], env: ETHEREUM_HOSTS) +- `-pk, --private-key `: The private key to use for deployment +- `--validators `: Comma separated list of validators +- `-m, --mnemonic `: The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") +- `-i, --mnemonic-index `: The index of the mnemonic to use in deployment (default: 0) +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) +- `--salt `: The optional salt to use in deployment +- `--json`: Output the contract addresses in JSON format +- `--test-accounts`: Populate genesis state with initial fee juice for test accounts +- `--sponsored-fpc`: Populate genesis state with a testing sponsored FPC contract + +### get-l1-addresses + +Gets the addresses of the L1 contracts. + +```bash +aztec get-l1-addresses [options] +``` + +Options: + +- `-r, --registry-address `: The address of the registry contract +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"], env: ETHEREUM_HOSTS) +- `-v, --rollup-version `: The version of the rollup +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) +- `--json`: Output the addresses in JSON format + +### get-l1-balance + +Gets the balance of an ERC token in L1 for the given Ethereum address. + +```bash +aztec get-l1-balance [options] +``` + +Arguments: + +- `who`: Ethereum address to check. + +Options: + +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"], env: ETHEREUM_HOSTS) +- `-t, --token `: The address of the token to check the balance of +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) +- `--json`: Output the balance in JSON format + +### debug-rollup + +Debugs the rollup contract. + +```bash +aztec debug-rollup [options] +``` + +Options: + +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"], env: ETHEREUM_HOSTS) +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) +- `--rollup
`: ethereum address of the rollup contract + +### prune-rollup + +Prunes the pending chain on the rollup contract. + +```bash +aztec prune-rollup [options] +``` + +Options: + +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"], env: ETHEREUM_HOSTS) +- `-pk, --private-key `: The private key to use for deployment +- `-m, --mnemonic `: The mnemonic to use in deployment (default: "test test test test test test test test test test test junk") +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) +- `--rollup
`: ethereum address of the rollup contract + +## Governance Commands + +### deposit-governance-tokens + +Deposits governance tokens to the governance contract. + +```bash +aztec deposit-governance-tokens [options] +``` + +Options: + +- `-r, --registry-address `: The address of the registry contract +- `--recipient `: The recipient of the tokens +- `-a, --amount `: The amount of tokens to deposit +- `--mint`: Mint the tokens on L1 (default: false) +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"], env: ETHEREUM_HOSTS) +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) +- `-p, --private-key `: The private key to use to deposit +- `-m, --mnemonic `: The mnemonic to use to deposit (default: "test test test test test test test test test test test junk") +- `-i, --mnemonic-index `: The index of the mnemonic to use to deposit (default: 0) + +### execute-governance-proposal + +Executes a governance proposal. + +```bash +aztec execute-governance-proposal [options] +``` + +Options: + +- `-p, --proposal-id `: The ID of the proposal +- `-r, --registry-address `: The address of the registry contract +- `--wait `: Whether to wait until the proposal is executable +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"], env: ETHEREUM_HOSTS) +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) +- `-pk, --private-key `: The private key to use to vote +- `-m, --mnemonic `: The mnemonic to use to vote (default: "test test test test test test test test test test test junk") +- `-i, --mnemonic-index `: The index of the mnemonic to use to vote (default: 0) + +### propose-with-lock + +Makes a proposal to governance with a lock. + +```bash +aztec propose-with-lock [options] +``` + +Options: + +- `-r, --registry-address `: The address of the registry contract +- `-p, --payload-address `: The address of the payload contract +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"], env: ETHEREUM_HOSTS) +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) +- `-pk, --private-key `: The private key to use to propose +- `-m, --mnemonic `: The mnemonic to use to propose (default: "test test test test test test test test test test test junk") +- `-i, --mnemonic-index `: The index of the mnemonic to use to propose (default: 0) +- `--json`: Output the proposal ID in JSON format + +### vote-on-governance-proposal + +Votes on a governance proposal. + +```bash +aztec vote-on-governance-proposal [options] +``` + +Options: + +- `-p, --proposal-id `: The ID of the proposal +- `-a, --vote-amount `: The amount of tokens to vote +- `--in-favor `: Whether to vote in favor of the proposal. Use "yea" for true, any other value for false. +- `--wait `: Whether to wait until the proposal is active +- `-r, --registry-address `: The address of the registry contract +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"], env: ETHEREUM_HOSTS) +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) +- `-pk, --private-key `: The private key to use to vote +- `-m, --mnemonic `: The mnemonic to use to vote (default: "test test test test test test test test test test test junk") +- `-i, --mnemonic-index `: The index of the mnemonic to use to vote (default: 0) + +## L1-L2 Bridge Commands + +### bridge-erc20 + +Bridges ERC20 tokens to L2. + +```bash +aztec bridge-erc20 [options] +``` + +Arguments: + +- `amount`: The amount of Fee Juice to mint and bridge. +- `recipient`: Aztec address of the recipient. + +Options: + +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"], env: ETHEREUM_HOSTS) +- `-m, --mnemonic `: The mnemonic to use for deriving the Ethereum address that will mint and bridge (default: "test test test test test test test test test test test junk") +- `--mint`: Mint the tokens on L1 (default: false) +- `--private`: If the bridge should use the private flow (default: false) +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) +- `-t, --token `: The address of the token to bridge +- `-p, --portal `: The address of the portal contract +- `-f, --faucet `: The address of the faucet contract (only used if minting) +- `--l1-private-key `: The private key to use for deployment +- `--json`: Output the claim in JSON format + +### bridge-fee-juice + +Mints L1 Fee Juice and bridges them to L2. + +```bash +aztec bridge-fee-juice [options] +``` + +Arguments: + +- `amount`: The amount of Fee Juice to mint and bridge. +- `recipient`: Aztec address of the recipient. + +Options: + +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"]) +- `-m, --mnemonic `: The mnemonic to use for deriving the Ethereum address that will mint and bridge (default: "test test test test test test test test test test test junk") +- `--mint`: Mint the tokens on L1 (default: false) +- `--l1-private-key `: The private key to the eth account bridging +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) +- `--json`: Output the claim in JSON format +- `--no-wait`: Wait for the bridged funds to be available in L2, polling every 60 seconds +- `--interval `: The polling interval in seconds for the bridged funds (default: "60") + +### get-l1-to-l2-message-witness + +Gets a L1 to L2 message witness. + +```bash +aztec get-l1-to-l2-message-witness [options] +``` + +Options: + +- `-ca, --contract-address
`: Aztec address of the contract. +- `--message-hash `: The L1 to L2 message hash. +- `--secret `: The secret used to claim the L1 to L2 message +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) + +## P2P Network Commands + +### generate-p2p-private-key + +Generates a LibP2P peer private key. + +```bash +aztec generate-p2p-private-key +``` + +### generate-bootnode-enr + +Generates the encoded ENR record for a bootnode. + +```bash +aztec generate-bootnode-enr [options] +``` + +### decode-enr + +Decodes an ENR record. + +```bash +aztec decode-enr [options] +``` + +Arguments: + +- `enr`: The encoded ENR string + +## Utility Commands + +### generate-keys + +Generates encryption and signing private keys. + +```bash +aztec generate-keys [options] +``` + +Option: + +- `-m, --mnemonic`: Optional mnemonic string for private key generation. + +### example-contracts + +Lists the example contracts available to deploy from @aztec/noir-contracts.js. + +```bash +aztec example-contracts +``` + +### compute-selector + +Computes a selector for a given function signature. + +```bash +aztec compute-selector [options] +``` + +Arguments: + +- `functionSignature`: Function signature to compute selector for e.g. foo(Field) + +### setup-protocol-contracts + +Bootstrap the blockchain by initializing all the protocol contracts. + +```bash +aztec setup-protocol-contracts [options] +``` + +Options: + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) +- `--testAccounts`: Deploy funded test accounts. +- `--sponsoredFPC`: Deploy a sponsored FPC. +- `--json`: Output the contract addresses in JSON format +- `--skipProofWait`: Don't wait for proofs to land. + +### sequencers + +Manages or queries registered sequencers on the L1 rollup contract. + +```bash +aztec sequencers [options] [who] +``` + +Arguments: + +- `command`: Command to run: list, add, remove, who-next +- `who`: Who to add/remove + +Options: + +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"]) +- `-m, --mnemonic `: The mnemonic for the sender of the tx (default: "test test test test test test test test test test test junk") +- `--block-number `: Block number to query next sequencer for +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: PXE_URL) +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: L1_CHAIN_ID) + +### import-test-accounts + +Import test accounts from pxe. + +```bash +aztec import-test-accounts [options] +``` + +### preload-crs + +Preload the points data needed for proving and verifying. + +```bash +aztec preload-crs +``` + +### get-canonical-sponsored-fpc-address + +Gets the canonical SponsoredFPC address for current testnet running on the same version as this CLI. + +```bash +aztec get-canonical-sponsored-fpc-address +``` + +### get-current-base-fee + +Gets the current base fee. + +```bash +aztec get-current-base-fee [options] +``` diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/cli_wallet_reference.md b/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/cli_wallet_reference.md new file mode 100644 index 000000000000..5d1474471460 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/cli_wallet_reference.md @@ -0,0 +1,711 @@ +--- +title: CLI Wallet +tags: [sandbox, wallet, cli] +keywords: [wallet, cli wallet] +sidebar_position: 3 +--- + +For development, it may be useful to deploy, transact, or create notes in a non-programmatic way. You can use the CLI wallet (`aztec-wallet`) for thing such as: + +- Deploying contracts +- Sending transactions +- Bridging L1 "Fee Juice" into Aztec +- Pushing arbitrary [notes](../../guides/smart_contracts/writing_contracts/notes/index.md) to your PXE +- Creating [authwits](../../guides/smart_contracts/writing_contracts/authwit.md) +- Aliasing info and secrets for further usage +- Proving your transactions and profile gate counts + +`aztec-wallet` functions as a user wallet. It runs a PXE and has persistent storage to remember user accounts, notes and registered contracts. + +:::info + +At any time, you can get an updated version of the existing commands and subcommands by adding `-h`. For example: + +```bash +aztec-wallet create-account -h +``` + +::: + +### Global Options + +The CLI wallet supports several global options that can be used with any command: + +- `-V, --version`: Output the version number +- `-d, --data-dir `: Storage directory for wallet data (default: "~/.aztec/wallet") +- `-p, --prover `: The type of prover the wallet uses (choices: "wasm", "native", "none", default: "native", env: `PXE_PROVER`) +- `--remote-pxe`: Connect to an external PXE RPC server instead of the local one (env: `REMOTE_PXE`) +- `-n, --node-url `: URL of the Aztec node to connect to (default: "http://host.docker.internal:8080", env: `AZTEC_NODE_URL`) +- `-h, --help`: Display help for command + +:::info + +Many options can be set using environment variables. For example: + +- `PXE_PROVER`: Set the prover type +- `REMOTE_PXE`: Enable remote PXE connection +- `AZTEC_NODE_URL`: Set the node URL +- `SECRET_KEY`: Set the secret key for account operations + +::: + +### Proving transactions + +You can prove a transaction using the aztec-wallet with a running sandbox. Follow the guide [here](../../guides/local_env/sandbox_proving.md#proving-with-aztec-wallet). Proving transactions is required when interacting with the testnet. + +## Aliases + +The CLI wallet makes extensive use of aliases, that is, when an address, artifact, secret, or other information is given a name that can be later used to reference it. + +Aliases have different types like `address` or `artifact` or `contract`. You can see a list of these types by running the help command `aztec-wallet alias -h`. You can then specify a type with the `:` character whenever needed. For example `accounts:master_yoda` or `artifacts:light_saber`. + +:::tip + +The wallet writes to the `last` alias if it's likely that you use that same alias in the next command. + +It will also try to determine which type is expected. For example, if the alias `master_yoda` is an account, you don't need to prepend `account:` if, for example, you're deploying a contract. + +You can create arbitrary aliases with the `alias` command. For example `aztec-wallet alias accounts test_alias 0x2c37902cdade7710bd2355e5949416dc5e43a16e0b13a5560854d2451d92d289`. + +::: + +## Paying Fees + +import { Why_Fees, CLI_Fees } from '@site/src/components/Snippets/general_snippets'; + + + +Below are all the payment methods available to pay transaction fees on Aztec, starting with the simplest. + +### Fee Paying Contract + +Fee paying contracts specify their own criteria of payment in exchange for paying the fee juice of a transaction, e.g. an FPC +be written to accept some banana tokens to pay for another's transaction fee. + +Before using a fee paying contract, you need to register it in the PXE, passing the address of the contract and specifying the `from` account (in this case `main`). For example: + +```bash +aztec-wallet register-contract $FPC_ADDRESS FPCContract -f main +``` + +With an alias corresponding to the FPC's address (`bananaFPC`) this would be: + +```bash +aztec-wallet --payment method=fpc,fpc-contract=contracts:bananaFPC +``` + +### Sponsored Fee Paying Contract + +Before using a Sponsored Fee Paying Contract (FPC), you need to register it in the PXE, passing the address of the contract and specifying the `from` account (in this case `main`). For example: + +```bash +aztec-wallet register-contract $FPC_ADDRESS SponsoredFPC -f main +``` + +This is a special type of FPC that can be used to pay for account deployment and regular txs. +Eg: to create an account paid for by the sponsoredFPC: + +```bash +aztec-wallet create-account -a main --payment method=fpc-sponsored,fpc=$FPC_ADDRESS +``` + +:::note +In the sandbox, the sponsored FPC address is printed at the end of its initial logs. +::: + +### Fee Juice from Sandbox Test accounts + +In the sandbox pre-loaded test accounts can be used to cover fee juice when deploying contracts. + +First import them: + +```bash title="import-test-accounts" showLineNumbers +aztec-wallet import-test-accounts +``` +> Source code: yarn-project/cli-wallet/test/flows/basic.sh#L9-L11 + + +Then use the alias (test0, test1...) when paying in fee juice. Eg to create accounts: + +```bash title="declare-accounts" showLineNumbers +aztec-wallet create-account -a alice --payment method=fee_juice,feePayer=test0 +aztec-wallet create-account -a bob --payment method=fee_juice,feePayer=test0 +``` +> Source code: yarn-project/end-to-end/src/guides/up_quick_start.sh#L21-L24 + + +### Mint and Bridge Fee Juice + +#### On Sandbox + +First register an account, mint the fee asset on L1 and bridge it to fee juice: + +```bash title="bridge-fee-juice" showLineNumbers +aztec-wallet create-account -a main --register-only +aztec-wallet bridge-fee-juice 1000000000000000000 main --mint --no-wait +``` +> Source code: yarn-project/cli-wallet/test/flows/create_account_pay_native.sh#L8-L11 + + +You'll have to wait for two blocks to pass for bridged fee juice to be ready on Aztec. +For the sandbox you do this by putting through two arbitrary transactions. Eg: + + + +```bash title="force-two-blocks" showLineNumbers +aztec-wallet import-test-accounts # if you haven't already imported the test accounts +aztec-wallet deploy Counter --init initialize --args 0 accounts:test0 -f test0 -a counter +aztec-wallet send increment -ca counter --args accounts:test0 accounts:test0 -f test0 +``` + +Now the funded account can deploy itself with the bridged fees, claiming the bridged fee juice and deploying the contract in one transaction: + +```bash title="claim-deploy-account" showLineNumbers +aztec-wallet deploy-account -f main --payment method=fee_juice,claim +``` +> Source code: yarn-project/cli-wallet/test/flows/create_account_pay_native.sh#L25-L27 + + +#### Minting on Testnet + +This will mint the specified amount of fee juice on L1 and bridge it to L2. + +```bash +aztec-wallet bridge-fee-juice -n --mint --l1-rpc-urls --l1-private-key --l1-chain-id 11155111 # sepolia +``` + +## Connect to the Testnet + +To connect to the testnet, pass the `AZTEC_NODE_URL` to the wallet with the `--node-url` (`-n`) option. + +```bash +export AZTEC_NODE_URL= +export SPONSORED_FPC_ADDRESS=0x1260a43ecf03e985727affbbe3e483e60b836ea821b6305bea1c53398b986047 +# Register a new account +aztec-wallet create-account --register-only -a main -n $AZTEC_NODE_URL +aztec-wallet register-contract $SPONSORED_FPC_ADDRESS SponsoredFPC --from main -n $AZTEC_NODE_URL --salt 0 -a sponsoredfpc +aztec-wallet create-account -n $AZTEC_NODE_URL --payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS +``` + +## Payment Options + +Many commands support payment options for transaction fees: + +```bash +--payment method=,feePayer=
,asset=
,fpc=
,claim=,claimSecret=,claimAmount=,messageLeafIndex=,feeRecipient=
+``` + +Valid payment methods: + +- `fee_juice`: Pay with fee juice (default) +- `fpc-public`: Pay with a public FPC +- `fpc-private`: Pay with a private FPC +- `fpc-sponsored`: Pay with a sponsored FPC + +## Gas Options + +Commands that send transactions support gas-related options: + +```bash +--gas-limits +--max-fees-per-gas +--max-priority-fees-per-gas +--no-estimate-gas +--estimate-gas-only +``` + +## Account Management + +The wallet comes with some options for account deployment and management. You can register and deploy accounts, or only register them, and pass different options to serve your workflow. + +### Create Account + +Generates a secret key and deploys an account contract. Uses a Schnorr single-key account which uses the same key for encryption and authentication (not secure for production usage). + +#### Options + +- `--skip-initialization`: Skip initializing the account contract. Useful for publicly deploying an existing account. +- `--public-deploy`: Publicly deploys the account and registers the class if needed. +- `-p, --public-key `: Public key that identifies a private signing key stored outside of the wallet. Used for ECDSA SSH accounts over the secp256r1 curve. +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080") +- `-sk, --secret-key `: Secret key for account. Uses random by default. (env: `SECRET_KEY`) +- `-a, --alias `: Alias for the account. Used for easy reference in subsequent commands. +- `-t, --type `: Type of account to create (choices: "schnorr", "ecdsasecp256r1", "ecdsasecp256r1ssh", "ecdsasecp256k1", default: "schnorr") +- `--register-only`: Just register the account on the PXE. Do not deploy or initialize the account contract. +- `--json`: Emit output as json +- `--no-wait`: Skip waiting for the contract to be deployed. Print the hash of deployment transaction +- `--payment `: Fee payment method and arguments (see Payment Options section) +- `--gas-limits `: Gas limits for the tx +- `--max-fees-per-gas `: Maximum fees per gas unit for DA and L2 computation +- `--max-priority-fees-per-gas `: Maximum priority fees per gas unit for DA and L2 computation +- `--no-estimate-gas`: Whether to automatically estimate gas limits for the tx +- `--estimate-gas-only`: Only report gas estimation for the tx, do not send it + +#### Example + +```bash +aztec-wallet create-account -a master_yoda +``` + +#### Testnet Example + +```bash +aztec-wallet create-account --register-only -a main -n $AZTEC_NODE_URL +aztec-wallet register-contract $SPONSORED_FPC_ADDRESS SponsoredFPC --from main -n $AZTEC_NODE_URL --salt 0 -a sponsoredfpc +aztec-wallet create-account -n $AZTEC_NODE_URL --payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS +``` + +### Deploy account + +Deploys an already registered aztec account that can be used for sending transactions. + +```bash +aztec-wallet deploy-account [options] +``` + +#### Options + +- `-f, --from `: Alias or address of the account to deploy +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: `PXE_URL`) +- `--json`: Emit output as json +- `--no-wait`: Skip waiting for the contract to be deployed. Print the hash of deployment transaction +- `--register-class`: Register the contract class (useful for when the contract class has not been deployed yet) +- `--payment `: Fee payment method and arguments + - Parameters: + - `method`: Valid values: "fee_juice", "fpc-public", "fpc-private", "fpc-sponsored" (Default: fee_juice) + - `feePayer`: The account paying the fee + - `asset`: The asset used for fee payment (Required for "fpc-public" and "fpc-private") + - `fpc`: The FPC contract that pays in fee juice (Not required for the "fee_juice" method) + - `claim`: Whether to use a previously stored claim to bridge fee juice + - `claimSecret`: The secret to claim fee juice on L1 + - `claimAmount`: The amount of fee juice to be claimed + - `messageLeafIndex`: The index of the claim in the l1toL2Message tree + - `feeRecipient`: Recipient of the fee + - Format: `--payment method=name,feePayer=address,asset=address ...` +- `--gas-limits `: Gas limits for the tx +- `--max-fees-per-gas `: Maximum fees per gas unit for DA and L2 computation +- `--max-priority-fees-per-gas `: Maximum priority fees per gas unit for DA and L2 computation +- `--no-estimate-gas`: Whether to automatically estimate gas limits for the tx +- `--estimate-gas-only`: Only report gas estimation for the tx, do not send it + +#### Example + +```bash +$ aztec-wallet create-account --register-only -a master_yoda +... +$ aztec-wallet deploy-account -f master_yoda +``` + +When you are deploying an account on testnet, you need to either bridge fee juice or pay for the account deployment with an FPC to pay for the deployment. When using an FPC, you need to create an account, regsiter the FPC, and then you can use it. For example: + +```bash +aztec-wallet create-account --register-only -a main -n $AZTEC_NODE_URL +aztec-wallet register-contract $SPONSORED_FPC_ADDRESS SponsoredFPC --from main -n $AZTEC_NODE_URL --salt 0 -a sponsoredfpc +aztec-wallet deploy-account -n $AZTEC_NODE_URL --payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS +``` + +## Contracts Actions + +### Deploy Contract + +Deploys a compiled Aztec.nr contract to Aztec. + +```bash +aztec-wallet deploy [options] [artifact] +``` + +#### Arguments + +- `artifact`: Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package@contract + +#### Options + +- `--init `: The contract initializer function to call (default: "constructor") +- `--no-init`: Leave the contract uninitialized +- `-k, --public-key `: Optional encryption public key for this address. Set this value only if this contract is expected to receive private notes, which will be encrypted using this public key +- `-s, --salt `: Optional deployment salt as a hex string for generating the deployment address +- `--universal`: Do not mix the sender address into the deployment +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: `PXE_URL`) +- `--args [args...]`: Constructor arguments (default: []) +- `-sk, --secret-key `: The sender's secret key (env: `SECRET_KEY`) +- `-f, --from `: Alias or address of the account to deploy from +- `-a, --alias `: Alias for the contract. Used for easy reference subsequent commands +- `--json`: Emit output as json +- `--no-wait`: Skip waiting for the contract to be deployed. Print the hash of deployment transaction +- `--no-class-registration`: Don't register this contract class +- `--no-public-deployment`: Don't emit this contract's public bytecode +- `--payment `: Fee payment method and arguments + - Parameters: + - `method`: Valid values: "fee_juice", "fpc-public", "fpc-private", "fpc-sponsored" (Default: fee_juice) + - `asset`: The asset used for fee payment (Required for "fpc-public" and "fpc-private") + - `fpc`: The FPC contract that pays in fee juice (Not required for the "fee_juice" method) + - `claim`: Whether to use a previously stored claim to bridge fee juice + - `claimSecret`: The secret to claim fee juice on L1 + - `claimAmount`: The amount of fee juice to be claimed + - `messageLeafIndex`: The index of the claim in the l1toL2Message tree + - `feeRecipient`: Recipient of the fee + - Format: `--payment method=name,asset=address,fpc=address ...` +- `--gas-limits `: Gas limits for the tx +- `--max-fees-per-gas `: Maximum fees per gas unit for DA and L2 computation +- `--max-priority-fees-per-gas `: Maximum priority fees per gas unit for DA and L2 computation +- `--no-estimate-gas`: Whether to automatically estimate gas limits for the tx +- `--estimate-gas-only`: Only report gas estimation for the tx, do not send it + +#### Example + +```bash +aztec-wallet deploy ./target/jedi_code.nr --arg accounts:master_yoda --from master_yoda --alias jedi_order +``` + +### Register Contract + +Registers a contract in this wallet's PXE. A contract must be registered in the user's PXE in order to interact with it. + +```bash +aztec-wallet register-contract [options] [address] [artifact] +``` + +#### Arguments + +- `address`: The address of the contract to register +- `artifact`: Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package@contract + +#### Options + +- `--init `: The contract initializer function to call (default: "constructor") +- `-k, --public-key `: Optional encryption public key for this address. Set this value only if this contract is expected to receive private notes, which will be encrypted using this public key +- `-s, --salt `: Optional deployment salt as a hex string for generating the deployment address + Sends a transaction by calling a function on an Aztec contract. +- `--args [args...]`: Constructor arguments (default: []) +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: `PXE_URL`) +- `-f, --from `: Alias or address of the account to simulate from +- `-a, --alias `: Alias for the contact. Used for easy reference in subsequent commands + +#### Example + +```bash +aztec-wallet register-contract
-a +``` + +### Send Transaction + +Sends a transaction by calling a function on an Aztec contract. + +```bash +aztec-wallet send [options] +``` + +#### Arguments + +- `functionName`: Name of function to execute + +#### Options + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: `PXE_URL`) +- `--args [args...]`: Function arguments (default: []) +- `-c, --contract-artifact `: Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package@contract +- `-ca, --contract-address
`: Aztec address of the contract +- `-a, --alias `: Alias for the transaction hash. Used for easy reference in subsequent commands +- `-sk, --secret-key `: The sender's secret key (env: `SECRET_KEY`) +- `-aw, --auth-witness `: Authorization witness to use for the transaction. If using multiple, pass a comma separated string +- `-f, --from `: Alias or address of the account to send the transaction from +- `--no-wait`: Print transaction hash without waiting for it to be mined +- `--no-cancel`: Do not allow the transaction to be cancelled. This makes for cheaper transactions +- `--payment `: Fee payment method and arguments + - Parameters: + - `method`: Valid values: "fee_juice", "fpc-public", "fpc-private", "fpc-sponsored" (Default: fee_juice) + - `asset`: The asset used for fee payment (Required for "fpc-public" and "fpc-private") + - `fpc`: The FPC contract that pays in fee juice (Not required for the "fee_juice" method) + - `claim`: Whether to use a previously stored claim to bridge fee juice + - `claimSecret`: The secret to claim fee juice on L1 + - `claimAmount`: The amount of fee juice to be claimed + - `messageLeafIndex`: The index of the claim in the l1toL2Message tree + - `feeRecipient`: Recipient of the fee + - Format: `--payment method=name,asset=address,fpc=address ...` +- `--gas-limits `: Gas limits for the tx +- `--max-fees-per-gas `: Maximum fees per gas unit for DA and L2 computation +- `--max-priority-fees-per-gas `: Maximum priority fees per gas unit for DA and L2 computation +- `--no-estimate-gas`: Whether to automatically estimate gas limits for the tx +- `--estimate-gas-only`: Only report gas estimation for the tx, do not send it + +#### Example + +```bash +aztec-wallet send --from master_yoda --contract-address jedi_order --args "luke skywalker" train_jedi +``` + +:::note + +On testnet, you might sometimes see a `transaction failed: timeout error`. This is not an actual failure - your transaction has been sent to the mempool and it is just timed out waiting to be mined. You can use `aztec-wallet get-tx ` to check status. + +::: + +### Simulate Transaction + +Simulates the execution of a function on an Aztec contract. + +```bash +aztec-wallet simulate [options] +``` + +#### Arguments + +- `functionName`: Name of function to simulate + +#### Options + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: `PXE_URL`) +- `--args [args...]`: Function arguments (default: []) +- `-ca, --contract-address
`: Aztec address of the contract +- `-c, --contract-artifact `: Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package@contract +- `-sk, --secret-key `: The sender's secret key (env: `SECRET_KEY`) +- `-aw, --auth-witness `: Authorization witness to use for the simulation +- `-f, --from `: Alias or address of the account to simulate from + +#### Example + +```bash +aztec-wallet simulate --from master_yoda --contract-address jedi_order --args "luke_skywalker" train_jedi +``` + +### Profile Transaction + +Profiles a private function by counting the unconditional operations in its execution steps. + +```bash +aztec-wallet profile [options] +``` + +#### Arguments + +- `functionName`: Name of function to simulate + +#### Options + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: `PXE_URL`) +- `--args [args...]`: Function arguments (default: []) +- `-ca, --contract-address
`: Aztec address of the contract +- `-c, --contract-artifact `: Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package@contract +- `--debug-execution-steps-dir
`: Directory to write execution step artifacts for bb profiling/debugging +- `-sk, --secret-key `: The sender's secret key (env: `SECRET_KEY`) +- `-aw, --auth-witness `: Authorization witness to use for the simulation +- `-f, --from `: Alias or address of the account to simulate from + +#### Example + +```bash +aztec-wallet profile --from master_yoda --contract-address jedi_order --args "luke_skywalker" train_jedi +``` + +### Create AuthWit + +Creates an authorization witness that can be privately sent to a caller so they can perform an action on behalf of the provided account. + +```bash +aztec-wallet create-authwit [options] +``` + +#### Arguments + +- `functionName`: Name of function to authorize +- `caller`: Account to be authorized to perform the action + +#### Options + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: `PXE_URL`) +- `--args [args...]`: Function arguments (default: []) +- `-ca, --contract-address
`: Aztec address of the contract +- `-c, --contract-artifact `: Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package@contract +- `-sk, --secret-key `: The sender's secret key (env: `SECRET_KEY`) +- `-f, --from `: Alias or address of the account to simulate from +- `-a, --alias `: Alias for the authorization witness. Used for easy reference in subsequent commands + +#### Private AuthWit Example + +The authwit management in private is a two-step process: create and add. It's not too different from a `send` command, but providing the caller that can privately execute the action on behalf of the caller. + +An example for authorizing an operator (ex. a DeFi protocol) to call the transfer_in_private action (transfer on the user's behalf): + +```bash +# Create the authorization witness +aztec-wallet create-authwit transfer_in_private accounts:main -ca contracts:token --args accounts:jedi_master accounts:main 20 secrets:auth_nonce -f accounts:jedi_master + +# Add the authorization witness +aztec-wallet add-authwit authwits:secret_trade accounts:jedi_master -f accounts:main +``` + +### Authorize Action + +Authorizes a public call on the caller, so they can perform an action on behalf of the provided account. + +```bash +aztec-wallet authorize-action [options] +``` + +#### Arguments + +- `functionName`: Name of function to authorize +- `caller`: Account to be authorized to perform the action + +#### Options + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: `PXE_URL`) +- `--args [args...]`: Function arguments (default: []) +- `-ca, --contract-address
`: Aztec address of the contract +- `-c, --contract-artifact `: Path to a compiled Aztec contract's artifact in JSON format. If executed inside a nargo workspace, a package and contract name can be specified as package@contract +- `-sk, --secret-key `: The sender's secret key (env: `SECRET_KEY`) +- `-f, --from `: Alias or address of the account to simulate from + +#### Public AuthWit Example + +A similar call to the above, but in public: + +```bash +aztec-wallet authorize-action transfer_in_public accounts:coruscant_trader -ca contracts:token --args accounts:jedi_master accounts:coruscant_trader 20 secrets:auth_nonce -f accounts:jedi_master +``` + +### Bridge Fee Juice + +Mints L1 Fee Juice and pushes them to L2. + +The wallet provides an easy way to mint the fee-paying asset on L1 and +bridging it to L2. Current placeholder-name "fee juice". + +Using the sandbox, there's already a Fee Juice contract that manages this +enshrined asset. You can optionally mint more Juice before bridging it. + +```bash +aztec-wallet bridge-fee-juice [options] +``` + +#### Arguments + +- `amount`: The amount of Fee Juice to mint and bridge +- `recipient`: Aztec address of the recipient + +#### Options + +- `--l1-rpc-urls `: List of Ethereum host URLs. Chain identifiers localhost and testnet can be used (comma separated) (default: ["http://host.docker.internal:8545"]) +- `-m, --mnemonic `: The mnemonic to use for deriving the Ethereum address that will mint and bridge (default: "test test test test test test test test test test test junk") +- `--mint`: Mint the tokens on L1 (default: false) +- `--l1-private-key `: The private key to the eth account bridging +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: `PXE_URL`) +- `-c, --l1-chain-id `: Chain ID of the ethereum host (default: 31337, env: `L1_CHAIN_ID`) +- `--json`: Output the claim in JSON format +- `--no-wait`: Wait for the bridged funds to be available in L2, polling every 60 seconds +- `--interval `: The polling interval in seconds for the bridged funds (default: "60") + +#### Example + +This simple sandbox example mints an amount of fee juice and bridges it to the `master_yoda` recipient on L2. For testnet, you will need to specify relevant L1 options listed above. + +```bash +aztec-wallet bridge-fee-juice --mint master_yoda +``` + +### Get Transaction + +Gets the status of the recent txs, or a detailed view if a specific transaction hash is provided. + +```bash +aztec-wallet get-tx [options] [txHash] +``` + +#### Arguments + +- `txHash`: A transaction hash to get the receipt for + +#### Options + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: `PXE_URL`) +- `-p, --page `: The page number to display (default: 1) +- `-s, --page-size `: The number of transactions to display per page (default: 10) + +#### Example + +```bash +# Get status of recent transactions +aztec-wallet get-tx + +# Get detailed view of a specific transaction +aztec-wallet get-tx +``` + +### Cancel Transaction + +Cancels a pending tx by reusing its nonce with a higher fee and an empty payload. + +```bash +aztec-wallet cancel-tx [options] +``` + +#### Arguments + +- `txHash`: A transaction hash to cancel + +#### Options + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: `PXE_URL`) +- `-sk, --secret-key `: The sender's secret key (env: `SECRET_KEY`) +- `-f, --from `: Alias or address of the account to simulate from +- `--payment `: Fee payment method and arguments + - Parameters: + - `method`: Valid values: "fee_juice", "fpc-public", "fpc-private", "fpc-sponsored" (Default: fee_juice) + - `asset`: The asset used for fee payment (Required for "fpc-public" and "fpc-private") + - `fpc`: The FPC contract that pays in fee juice (Not required for the "fee_juice" method) + - `claim`: Whether to use a previously stored claim to bridge fee juice + - `claimSecret`: The secret to claim fee juice on L1 + - `claimAmount`: The amount of fee juice to be claimed + - `messageLeafIndex`: The index of the claim in the l1toL2Message tree + - `feeRecipient`: Recipient of the fee + - Format: `--payment method=name,asset=address,fpc=address ...` (default: "method=fee_juice") +- `-i, --increased-fees [da=1,l2=1]`: The amounts by which the fees are increased (default: "feePerDaGas":"0x0000000000000000000000000000000000000000000000000000000000000001","feePerL2Gas":"0x0000000000000000000000000000000000000000000000000000000000000001") +- `--max-fees-per-gas `: Maximum fees per gas unit for DA and L2 computation + +#### Example + +```bash +aztec-wallet cancel-tx +``` + +### Register Sender + +Registers a sender's address in the wallet, so the note syncing process will look for notes sent by them. + +```bash +aztec-wallet register-sender [options] [address] +``` + +#### Arguments + +- `address`: The address of the sender to register + +#### Options + +- `-u, --rpc-url `: URL of the PXE (default: "http://host.docker.internal:8080", env: `PXE_URL`) +- `-f, --from `: Alias or address of the account to simulate from +- `-a, --alias `: Alias for the sender. Used for easy reference in subsequent commands + +#### Example + +```bash +aztec-wallet register-sender
-a +``` + +### Create Secret + +Creates an aliased secret to use in other commands. + +```bash +aztec-wallet create-secret [options] +``` + +#### Options + +- `-a, --alias `: Key to alias the secret with + +#### Example + +```bash +aztec-wallet create-secret -a +``` diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/index.md b/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/index.md new file mode 100644 index 000000000000..669c0329faf6 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/index.md @@ -0,0 +1,19 @@ +--- +title: Developer Environment +tags: [sandbox] +sidebar_position: 0 +--- + +The Aztec Sandbox is an environment for local development on the Aztec Network. It's easy to get setup with just a single, simple command, and contains all the components needed to develop and test Aztec contracts and applications. + +## What's in the Sandbox? + +The sandbox contains a local Ethereum instance running [Anvil](https://book.getfoundry.sh/anvil/), a local instance of the Aztec rollup and an aztec private execution client for handling user transactions and state. + +These provide a self contained environment which deploys Aztec on a local (empty) Ethereum network, creates 3 smart contract wallet accounts on the rollup, and allows transactions to be processed on the local Aztec sequencer. + +The current sandbox does not generate or verify proofs, but provides a working end to end developer flow for writing and interacting with Aztec.nr smart contracts. + +## Command line tools + +Aztec-nargo and aztec CLI are command-line tool allowing you to compile smart contracts. See the [compiling contracts guide](../../guides/local_env/run_more_than_one_pxe_sandbox.md) for more information. diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/sandbox-reference.md b/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/sandbox-reference.md new file mode 100644 index 000000000000..1dee020fea0e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/environment_reference/sandbox-reference.md @@ -0,0 +1,165 @@ +--- +title: Sandbox Reference +tags: [sandbox] +sidebar_position: 1 +--- + +:::tip + +For a quick start, follow the [guide](../../getting_started.md) to install the sandbox. + +::: + +## Environment Variables + +There are various environment variables you can use when running the whole sandbox or when running on of the available modes. + +**Sandbox** + +```sh +LOG_LEVEL=debug # Options are 'fatal', 'error', 'warn', 'info', 'verbose', 'debug', 'trace' +HOST_WORKDIR='${PWD}' # The location to store log outputs. Will use ~/.aztec where the binaries are stored by default. +ETHEREUM_HOSTS=http://127.0.0.1:8545 # List of Ethereum JSON RPC URLs. We use an anvil instance that runs in parallel to the sandbox on docker by default. +ANVIL_PORT=8545 # The port that docker will forward to the anvil instance (default: 8545) +L1_CHAIN_ID=31337 # The Chain ID that the Ethereum host is using. +TEST_ACCOUNTS='true' # Option to deploy 3 test account when sandbox starts. (default: true) +MODE='sandbox' # Option to start the sandbox or a standalone part of the system. (default: sandbox) +PXE_PORT=8080 # The port that the PXE will be listening to (default: 8080) +AZTEC_NODE_PORT=8080 # The port that Aztec Node will be listening to (default: 8080) + +## Polling intervals ## +ARCHIVER_POLLING_INTERVAL_MS=50 +P2P_BLOCK_CHECK_INTERVAL_MS=50 +SEQ_TX_POLLING_INTERVAL_MS=50 +WS_BLOCK_CHECK_INTERVAL_MS=50 +ARCHIVER_VIEM_POLLING_INTERVAL_MS=500 +``` + +**Aztec Node** + +Variables like `DEPLOY_AZTEC_CONTRACTS` & `AZTEC_NODE_PORT` are valid here as described above. +`TEST_ACCOUNTS` cannot be used here because the Aztec node does not control an Aztec account to deploy contracts from. + +```sh +# P2P config # +# Configuration variables for connecting a Node to the Aztec Node P2P network. You'll need a running P2P-Bootstrap node to connect to. +P2P_ENABLED='false' # A flag to enable P2P networking for this node. (default: false) +P2P_BLOCK_CHECK_INTERVAL_MS=100 # The frequency in which to check for new L2 blocks. +P2P_PEER_CHECK_INTERVAL_MS=1000 # The frequency in which to check for peers. +P2P_L2_BLOCK_QUEUE_SIZE=1000 # Size of queue of L2 blocks to store. +P2P_IP='' # Announce IP of the peer. Defaults to working it out using discV5, otherwise set P2P_QUERY_FOR_IP if you are behind a NAT +P2P_LISTEN_ADDR=0.0.0.0 # The address on which the P2P service should listen for connections.(default: 0.0.0.0) +P2P_PORT=40400 # The Port that will be used for sending & listening p2p messages (default: 40400) +PEER_ID_PRIVATE_KEY='' # An optional peer id private key. If blank, will generate a random key. +BOOTSTRAP_NODES='' # A list of bootstrap peers to connect to, separated by commas +P2P_ANNOUNCE_PORT='' # Port to announce to the p2p network +P2P_NAT_ENABLED='false' # Whether to enable NAT from libp2p +P2P_MAX_PEERS=100 # The maximum number of peers (a peer count above this will cause the node to refuse connection attempts) + +## Aztec Contract Addresses ## +# When running a standalone node, you need to have deployed Aztec contracts on your Ethereum host, then declare their addresses as env variables. +REGISTRY_CONTRACT_ADDRESS=0x01234567890abcde01234567890abcde +INBOX_CONTRACT_ADDRESS=0x01234567890abcde01234567890abcde +OUTBOX_CONTRACT_ADDRESS=0x01234567890abcde01234567890abcde +ROLLUP_CONTRACT_ADDRESS=0x01234567890abcde01234567890abcde + +## Sequencer variables ## +SEQ_PUBLISHER_PRIVATE_KEY=0x01234567890abcde01234567890abcde # Private key of an ethereum account that will be used by the sequencer to publish blocks. +SEQ_MAX_TX_PER_BLOCK=32 # Maximum txs to go on a block. (default: 32) +SEQ_MIN_TX_PER_BLOCK=1 # Minimum txs to go on a block. (default: 1) + +## Validator variables ## +VALIDATOR_PRIVATE_KEY=0x01234567890abcde01234567890abcde # Private key of the ethereum account that will be used to perform validator duties +``` + +**PXE** + +Variables like `TEST_ACCOUNTS` & `PXE_PORT` are valid here as described above. + +```sh +AZTEC_NODE_URL='http://localhost:8079' # The address of an Aztec Node URL that the PXE will connect to (default: http://localhost:8079) +PXE_PORT=8080 # The port that the PXE will be listening to (default: 8080) +TEST_ACCOUNTS='true' # Option to deploy 3 test account when sandbox starts. (default: true) +``` + +**P2P Bootstrap Node** + +The P2P Bootstrap node is a standalone app whose purpose is to assist new P2P network participants in acquiring peers. + +```sh +PEER_ID_PRIVATE_KEY='' # The private key to be used by the peer for secure communications with other peers. This key will also be used to derive the Peer ID. +P2P_IP='' # Announce IP of the peer. Defaults to working it out using discV5, otherwise set P2P_QUERY_FOR_IP if you are behind a NAT +P2P_LISTEN_ADDR=0.0.0.0 # The address on which the P2P service should listen for connections.(default: 0.0.0.0) +P2P_PORT=40400 # The Port that will be used for sending & listening p2p messages (default: 40400) +``` + +## Cheat Codes + +To help with testing, the sandbox is shipped with a set of cheatcodes. + +Cheatcodes allow you to change the time of the Aztec block, load certain state or more easily manipulate Ethereum instead of having to write dedicated RPC calls to anvil or hardhat. + +You can find the cheat code reference [here](./cheat_codes.md). + +## Contracts + +We have shipped a number of example contracts in the `@aztec/noir-contracts.js` [npm package](https://www.npmjs.com/package/@aztec/noir-contracts.js). This is included with the sandbox by default so you are able to use these contracts to test with. + +```bash +AppSubscriptionContractArtifact +AuthContractArtifact +BenchmarkingContractArtifact +CardGameContractArtifact +ChildContractArtifact +ClaimContractArtifact +ContractClassRegistererContractArtifact +ContractInstanceDeployerContractArtifact +CounterContractArtifact +CrowdfundingContractArtifact +DocsExampleContractArtifact +EasyPrivateTokenContractArtifact +EasyPrivateVotingContractArtifact +EcdsaAccountContractArtifact +EscrowContractArtifact +FPCContractArtifact +FeeJuiceContractArtifact +ImportTestContractArtifact +LendingContractArtifact +MultiCallEntrypointContractArtifact +ParentContractArtifact +PendingNoteHashesContractArtifact +PriceFeedContractArtifact +ReaderContractArtifact +SchnorrAccountContractArtifact +SchnorrHardcodedAccountContractArtifact +SchnorrSingleKeyAccountContractArtifact +SlowTreeContractArtifact +StatefulTestContractArtifact +TestContractArtifact +TokenBlacklistContractArtifact +TokenBridgeContractArtifact +TokenContractArtifact +UniswapContractArtifact +``` + +> Source code: /yarn-project/end-to-end/src/composed/cli_docs_sandbox.test.ts#L95-L118 + +You can see all of our example contracts in the monorepo [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-contracts/contracts). + +## Running Aztec PXE / Node / P2P-Bootstrap node individually + +If you wish to run components of the Aztec network stack separately, you can use the `aztec start` command with various options for enabling components. + +```bash +aztec start --node [nodeOptions] --pxe [pxeOptions] --archiver [archiverOptions] --sequencer [sequencerOptions] --prover [proverOptions] --p2p-bootstrap [p2pOptions] +``` + +Starting the aztec node alongside a PXE, sequencer or archiver, will attach the components to the node. Eg if you want to run a PXE separately to a node, you can [read this guide](../../guides/local_env/run_more_than_one_pxe_sandbox.md). + +## Update the sandbox + +To update the sandbox, run: + +```bash +aztec-up +``` diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/_category_.json new file mode 100644 index 000000000000..968f9bcf0a6b --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Smart Contract Reference", + "position": 1, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/address-note/address_note.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/address-note/address_note.md new file mode 100644 index 000000000000..e16195153664 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/address-note/address_note.md @@ -0,0 +1,23 @@ +# AddressNote + +## Fields +| Field | Type | +| --- | --- | +| address | AztecAddress | +| owner | AztecAddress | +| randomness | Field | + +## Methods + +### new + +```rust +AddressNote::new(address, owner); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | +| owner | AztecAddress | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/account.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/account.md new file mode 100644 index 000000000000..0fcca4b241f4 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/account.md @@ -0,0 +1,43 @@ +## Standalone Functions + +### init + +```rust +init(context, is_valid_impl, Field); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | Context | +| is_valid_impl | fn(&mut PrivateContext | +| Field | | + +### entrypoint + +```rust +entrypoint(self, app_payload, fee_payload, cancellable); +``` + +docs:start:entrypoint + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| app_payload | AppPayload | +| fee_payload | FeePayload | +| cancellable | bool | + +### verify_private_authwit + +```rust +verify_private_authwit(self, inner_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| inner_hash | Field | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/auth.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/auth.md new file mode 100644 index 000000000000..468b57445dd2 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/auth.md @@ -0,0 +1,138 @@ +## Standalone Functions + +### assert_current_call_valid_authwit + +```rust +assert_current_call_valid_authwit(context, on_behalf_of); +``` + +docs:start:assert_current_call_valid_authwit + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | +| on_behalf_of | AztecAddress | + +### assert_inner_hash_valid_authwit + +```rust +assert_inner_hash_valid_authwit(context, on_behalf_of, inner_hash, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | +| on_behalf_of | AztecAddress | +| inner_hash | Field | +| | | + +### assert_current_call_valid_authwit_public + +```rust +assert_current_call_valid_authwit_public(context, on_behalf_of, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PublicContext | +| on_behalf_of | AztecAddress | +| | | + +### assert_inner_hash_valid_authwit_public + +```rust +assert_inner_hash_valid_authwit_public(context, on_behalf_of, inner_hash, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PublicContext | +| on_behalf_of | AztecAddress | +| inner_hash | Field | +| | | + +### compute_authwit_message_hash_from_call + +```rust +compute_authwit_message_hash_from_call(caller, consumer, chain_id, version, selector, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| caller | AztecAddress | +| consumer | AztecAddress | +| chain_id | Field | +| version | Field | +| selector | FunctionSelector | +| args | [Field; N] | +| | | + +### compute_inner_authwit_hash + +```rust +compute_inner_authwit_hash(args); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| args | [Field; N] | + +### compute_authwit_nullifier + +```rust +compute_authwit_nullifier(on_behalf_of, inner_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| on_behalf_of | AztecAddress | +| inner_hash | Field | + +### compute_authwit_message_hash + +```rust +compute_authwit_message_hash(consumer, chain_id, version, inner_hash, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| consumer | AztecAddress | +| chain_id | Field | +| version | Field | +| inner_hash | Field | +| | | + +### set_authorized + +```rust +set_authorized(context, message_hash, authorize, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PublicContext | +| message_hash | Field | +| authorize | bool | +| | | + +### set_reject_all + +```rust +set_reject_all(context, reject); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PublicContext | +| reject | bool | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/auth_witness.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/auth_witness.md new file mode 100644 index 000000000000..11338206f5cd --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/auth_witness.md @@ -0,0 +1,24 @@ +## Standalone Functions + +### get_auth_witness_oracle + +```rust +get_auth_witness_oracle(_message_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _message_hash | Field | + +### get_auth_witness + +```rust +get_auth_witness(message_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| message_hash | Field | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/cheatcodes.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/cheatcodes.md new file mode 100644 index 000000000000..5203a24f03fa --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/cheatcodes.md @@ -0,0 +1,30 @@ +## Standalone Functions + +### add_private_authwit_from_call_interface + +```rust +add_private_authwit_from_call_interface(on_behalf_of, caller, call_interface, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| on_behalf_of | AztecAddress | +| caller | AztecAddress | +| call_interface | C | +| | | + +### add_public_authwit_from_call_interface + +```rust +add_public_authwit_from_call_interface(on_behalf_of, caller, call_interface, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| on_behalf_of | AztecAddress | +| caller | AztecAddress | +| call_interface | C | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/entrypoint/app.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/entrypoint/app.md new file mode 100644 index 000000000000..1899521832d5 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/entrypoint/app.md @@ -0,0 +1,48 @@ +# AppPayload + +## Fields +| Field | Type | +| --- | --- | +| function_calls | FunctionCall; ACCOUNT_MAX_CALLS] | +| pub tx_nonce | Field | + +## Methods + +### to_be_bytes + +Serializes the payload as an array of bytes. Useful for hashing with sha256. + +```rust +AppPayload::to_be_bytes(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### execute_calls + +```rust +AppPayload::execute_calls(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +## Standalone Functions + +### hash + +```rust +hash(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/entrypoint/fee.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/entrypoint/fee.md new file mode 100644 index 000000000000..5551b34d93cf --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/entrypoint/fee.md @@ -0,0 +1,47 @@ +# FeePayload + +## Fields +| Field | Type | +| --- | --- | +| function_calls | FunctionCall; MAX_FEE_FUNCTION_CALLS] | +| tx_nonce | Field | +| is_fee_payer | bool | + +## Methods + +### to_be_bytes + +```rust +FeePayload::to_be_bytes(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### execute_calls + +```rust +FeePayload::execute_calls(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +## Standalone Functions + +### hash + +```rust +hash(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/entrypoint/function_call.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/entrypoint/function_call.md new file mode 100644 index 000000000000..57848a06db89 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/authwit/entrypoint/function_call.md @@ -0,0 +1,24 @@ +# FunctionCall + +## Fields +| Field | Type | +| --- | --- | +| pub args_hash | Field, /* args_hash is calldata_hash if is_public is true */ | +| pub function_selector | FunctionSelector | +| pub target_address | AztecAddress | +| pub is_public | bool | +| pub is_static | bool | + +## Methods + +### to_be_bytes + +```rust +FunctionCall::to_be_bytes(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/capsules/mod.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/capsules/mod.md new file mode 100644 index 000000000000..840aacd1c8db --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/capsules/mod.md @@ -0,0 +1,182 @@ +## Standalone Functions + +### at + +```rust +at(contract_address, base_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| base_slot | Field | + +### len + +```rust +len(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### push + +```rust +push(self, value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| value | T | + +### get + +```rust +get(self, index); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| index | u32 | + +### remove + +```rust +remove(self, index); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| index | u32 | + +### for_each + +```rust +for_each(self, f, T); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| f | unconstrained fn[Env](u32 | +| T | | + +### slot_at + +```rust +slot_at(self, index); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| index | u32 | + +### setup + +```rust +setup(); +``` + +Takes no parameters. + +### empty_array + +```rust +empty_array(); +``` + +Takes no parameters. + +### empty_array_read + +```rust +empty_array_read(); +``` + +Takes no parameters. + +### array_push + +```rust +array_push(); +``` + +Takes no parameters. + +### read_past_len + +```rust +read_past_len(); +``` + +Takes no parameters. + +### array_remove_last + +```rust +array_remove_last(); +``` + +Takes no parameters. + +### array_remove_some + +```rust +array_remove_some(); +``` + +Takes no parameters. + +### array_remove_all + +```rust +array_remove_all(); +``` + +Takes no parameters. + +### for_each_called_with_all_elements + +```rust +for_each_called_with_all_elements(); +``` + +Takes no parameters. + +### for_each_remove_some + +```rust +for_each_remove_some(); +``` + +Takes no parameters. + +### for_each_remove_all + +```rust +for_each_remove_all(); +``` + +Takes no parameters. + +### for_each_remove_all_no_copy + +```rust +for_each_remove_all_no_copy(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/call_interfaces.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/call_interfaces.md new file mode 100644 index 000000000000..fb16951c6487 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/call_interfaces.md @@ -0,0 +1,1278 @@ +## Standalone Functions + +### get_args + +```rust +get_args(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_selector + +```rust +get_selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_name + +```rust +get_name(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_contract_address + +```rust +get_contract_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_is_static + +```rust +get_is_static(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(target_contract, selector, name, args, is_static, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_contract | AztecAddress | +| selector | FunctionSelector | +| name | str<M> | +| args | [Field] | +| is_static | bool | +| | | + +### call + +```rust +call(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +### view + +```rust +view(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +### get_args + +```rust +get_args(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_selector + +```rust +get_selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_name + +```rust +get_name(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_contract_address + +```rust +get_contract_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_is_static + +```rust +get_is_static(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(target_contract, selector, name, args, is_static, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_contract | AztecAddress | +| selector | FunctionSelector | +| name | str<M> | +| args | [Field] | +| is_static | bool | +| | | + +### call + +```rust +call(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +### view + +```rust +view(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +### get_args + +```rust +get_args(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_selector + +```rust +get_selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_name + +```rust +get_name(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_contract_address + +```rust +get_contract_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_is_static + +```rust +get_is_static(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(target_contract, selector, name, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_contract | AztecAddress | +| selector | FunctionSelector | +| name | str<M> | +| args | [Field] | +| | | + +### view + +```rust +view(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +### get_args + +```rust +get_args(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_selector + +```rust +get_selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_name + +```rust +get_name(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_contract_address + +```rust +get_contract_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_is_static + +```rust +get_is_static(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(target_contract, selector, name, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_contract | AztecAddress | +| selector | FunctionSelector | +| name | str<M> | +| args | [Field] | +| | | + +### view + +```rust +view(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +### get_args + +```rust +get_args(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_selector + +```rust +get_selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_name + +```rust +get_name(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_contract_address + +```rust +get_contract_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_is_static + +```rust +get_is_static(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(target_contract, selector, name, args, is_static, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_contract | AztecAddress | +| selector | FunctionSelector | +| name | str<M> | +| args | [Field] | +| is_static | bool | +| | | + +### with_gas + +```rust +with_gas(self, gas_opts); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | &mut Self | +| gas_opts | GasOpts | + +### call + +```rust +call(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PublicContext | + +### view + +```rust +view(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PublicContext | + +### enqueue + +```rust +enqueue(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +### enqueue_view + +```rust +enqueue_view(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +### get_args + +```rust +get_args(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_selector + +```rust +get_selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_name + +```rust +get_name(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_contract_address + +```rust +get_contract_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_is_static + +```rust +get_is_static(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(target_contract, selector, name, args, is_static, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_contract | AztecAddress | +| selector | FunctionSelector | +| name | str<M> | +| args | [Field] | +| is_static | bool | +| | | + +### with_gas + +```rust +with_gas(self, gas_opts); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | &mut Self | +| gas_opts | GasOpts | + +### call + +```rust +call(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PublicContext | + +### view + +```rust +view(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PublicContext | + +### enqueue + +```rust +enqueue(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +### enqueue_view + +```rust +enqueue_view(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +### set_as_teardown + +```rust +set_as_teardown(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +### get_args + +```rust +get_args(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_selector + +```rust +get_selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_name + +```rust +get_name(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_contract_address + +```rust +get_contract_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_is_static + +```rust +get_is_static(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(target_contract, selector, name, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_contract | AztecAddress | +| selector | FunctionSelector | +| name | str<M> | +| args | [Field] | +| | | + +### with_gas + +```rust +with_gas(self, gas_opts); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | &mut Self | +| gas_opts | GasOpts | + +### view + +```rust +view(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PublicContext | + +### enqueue_view + +```rust +enqueue_view(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +### get_args + +```rust +get_args(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_selector + +```rust +get_selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_name + +```rust +get_name(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_contract_address + +```rust +get_contract_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_is_static + +```rust +get_is_static(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(target_contract, selector, name, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_contract | AztecAddress | +| selector | FunctionSelector | +| name | str<M> | +| args | [Field] | +| | | + +### with_gas + +```rust +with_gas(self, gas_opts); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | &mut Self | +| gas_opts | GasOpts | + +### view + +```rust +view(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PublicContext | + +### enqueue_view + +```rust +enqueue_view(self, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | + +### get_args + +```rust +get_args(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_selector + +```rust +get_selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_name + +```rust +get_name(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_contract_address + +```rust +get_contract_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_is_static + +```rust +get_is_static(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_args + +```rust +get_args(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_selector + +```rust +get_selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_name + +```rust +get_name(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_contract_address + +```rust +get_contract_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_is_static + +```rust +get_is_static(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(target_contract, selector, name, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_contract | AztecAddress | +| selector | FunctionSelector | +| name | str<M> | +| args | [Field] | +| | | + +### get_args + +```rust +get_args(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_selector + +```rust +get_selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_name + +```rust +get_name(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_contract_address + +```rust +get_contract_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_args + +```rust +get_args(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_selector + +```rust +get_selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_name + +```rust +get_name(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_contract_address + +```rust +get_contract_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_is_static + +```rust +get_is_static(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(target_contract, selector, name, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_contract | AztecAddress | +| selector | FunctionSelector | +| name | str<M> | +| args | [Field] | +| | | + +### get_args + +```rust +get_args(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_selector + +```rust +get_selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_name + +```rust +get_name(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_contract_address + +```rust +get_contract_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### call_with_txe + +```rust +call_with_txe(self, from); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| from | AztecAddress | + +### call_with_txe + +```rust +call_with_txe(self, from); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| from | AztecAddress | + +### call_with_txe + +```rust +call_with_txe(self, from); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| from | AztecAddress | + +### call_with_txe + +```rust +call_with_txe(self, from); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| from | AztecAddress | + +### call_with_txe + +```rust +call_with_txe(self, from); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| from | AztecAddress | + +### call_with_txe + +```rust +call_with_txe(self, from); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| from | AztecAddress | + +### call_with_txe + +```rust +call_with_txe(self, from); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| from | AztecAddress | + +### call_with_txe + +```rust +call_with_txe(self, from); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| from | AztecAddress | + +### call_with_txe + +```rust +call_with_txe(self, from); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| from | AztecAddress | + +### call_with_txe + +```rust +call_with_txe(self, from); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| from | AztecAddress | + +### call_with_txe + +```rust +call_with_txe(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### call_with_txe + +```rust +call_with_txe(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### call_with_txe + +```rust +call_with_txe(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/gas.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/gas.md new file mode 100644 index 000000000000..0aaba279d6c6 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/gas.md @@ -0,0 +1,30 @@ +# GasOpts + +## Fields +| Field | Type | +| --- | --- | +| pub l2_gas | Option<u32> | +| pub da_gas | Option<u32> | + +## Methods + +### default + +```rust +GasOpts::default(); +``` + +Takes no parameters. + +### new + +```rust +GasOpts::new(l2_gas, da_gas); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| l2_gas | u32 | +| da_gas | u32 | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/inputs/private_context_inputs.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/inputs/private_context_inputs.md new file mode 100644 index 000000000000..b9c4f97350ad --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/inputs/private_context_inputs.md @@ -0,0 +1,22 @@ +# PrivateContextInputs + +PrivateContextInputs are expected to be provided to each private function + +## Fields +| Field | Type | +| --- | --- | +| pub call_context | CallContext | +| pub historical_header | BlockHeader | +| pub tx_context | TxContext | +| pub start_side_effect_counter | u32 | + +## Standalone Functions + +### empty + +```rust +empty(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/private_context.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/private_context.md new file mode 100644 index 000000000000..82e99e0d8dc1 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/private_context.md @@ -0,0 +1,556 @@ +# PrivateContext + +When finished, one can call .finish() to convert back to the abi + +## Fields +| Field | Type | +| --- | --- | +| pub inputs | PrivateContextInputs | +| pub side_effect_counter | u32 | +| pub min_revertible_side_effect_counter | u32 | +| pub is_fee_payer | bool | +| pub args_hash | Field | +| pub return_hash | Field | +| pub max_block_number | MaxBlockNumber | +| pub note_hash_read_requests | BoundedVec<ReadRequest, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL> | +| pub nullifier_read_requests | BoundedVec<ReadRequest, MAX_NULLIFIER_READ_REQUESTS_PER_CALL> | +| key_validation_requests_and_generators | BoundedVec<KeyValidationRequestAndGenerator, MAX_KEY_VALIDATION_REQUESTS_PER_CALL> | +| pub note_hashes | BoundedVec<NoteHash, MAX_NOTE_HASHES_PER_CALL> | +| pub nullifiers | BoundedVec<Nullifier, MAX_NULLIFIERS_PER_CALL> | +| pub private_call_requests | BoundedVec<PrivateCallRequest, MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL> | +| pub public_call_requests | BoundedVec<Counted<PublicCallRequest>, MAX_ENQUEUED_CALLS_PER_CALL> | +| pub public_teardown_call_request | PublicCallRequest | +| pub l2_to_l1_msgs | BoundedVec<Counted<L2ToL1Message>, MAX_L2_TO_L1_MSGS_PER_CALL> | +| pub historical_header | BlockHeader | +| pub private_logs | BoundedVec<PrivateLogData, MAX_PRIVATE_LOGS_PER_CALL> | +| pub contract_class_logs_hashes | BoundedVec<Counted<LogHash>, MAX_CONTRACT_CLASS_LOGS_PER_CALL> | +| pub last_key_validation_requests | Option<KeyValidationRequest>; NUM_KEY_TYPES] | + +## Methods + +### new + +```rust +PrivateContext::new(inputs, args_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| inputs | PrivateContextInputs | +| args_hash | Field | + +### msg_sender + +```rust +PrivateContext::msg_sender(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### this_address + +```rust +PrivateContext::this_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### chain_id + +```rust +PrivateContext::chain_id(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### version + +```rust +PrivateContext::version(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### gas_settings + +```rust +PrivateContext::gas_settings(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### selector + +```rust +PrivateContext::selector(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_args_hash + +```rust +PrivateContext::get_args_hash(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### push_note_hash + +```rust +PrivateContext::push_note_hash(&mut self, note_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| note_hash | Field | + +### push_nullifier + +```rust +PrivateContext::push_nullifier(&mut self, nullifier); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| nullifier | Field | + +### push_nullifier_for_note_hash + +```rust +PrivateContext::push_nullifier_for_note_hash(&mut self, nullifier, nullified_note_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| nullifier | Field | +| nullified_note_hash | Field | + +### get_block_header + +Returns the header of a block whose state is used during private execution (not the block the transaction is included in). + +```rust +PrivateContext::get_block_header(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_block_header_at + +Returns the header of an arbitrary block whose block number is less than or equal to the block number of historical header. + +```rust +PrivateContext::get_block_header_at(self, block_number); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| block_number | u32 | + +### set_return_hash + +```rust +PrivateContext::set_return_hash(&mut self, returns_hasher); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| returns_hasher | ArgsHasher | + +### finish + +```rust +PrivateContext::finish(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### set_as_fee_payer + +```rust +PrivateContext::set_as_fee_payer(&mut self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | + +### end_setup + +```rust +PrivateContext::end_setup(&mut self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | + +### set_tx_max_block_number + +```rust +PrivateContext::set_tx_max_block_number(&mut self, max_block_number); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| max_block_number | u32 | + +### push_note_hash_read_request + +```rust +PrivateContext::push_note_hash_read_request(&mut self, note_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| note_hash | Field | + +### push_nullifier_read_request + +```rust +PrivateContext::push_nullifier_read_request(&mut self, nullifier); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| nullifier | Field | + +### request_nsk_app + +```rust +PrivateContext::request_nsk_app(&mut self, npk_m_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| npk_m_hash | Field | + +### request_ovsk_app + +```rust +PrivateContext::request_ovsk_app(&mut self, ovpk_m_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| ovpk_m_hash | Field | + +### request_sk_app + +```rust +PrivateContext::request_sk_app(&mut self, pk_m_hash, key_index); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| pk_m_hash | Field | +| key_index | Field | + +### message_portal + +```rust +PrivateContext::message_portal(&mut self, recipient, content); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| recipient | EthAddress | +| content | Field | + +### consume_l1_to_l2_message + +```rust +PrivateContext::consume_l1_to_l2_message(&mut self, content, secret, sender, leaf_index, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| content | Field | +| secret | Field | +| sender | EthAddress | +| leaf_index | Field | +| | | + +### emit_private_log + +```rust +PrivateContext::emit_private_log(&mut self, log, length); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| log | [Field; PRIVATE_LOG_SIZE_IN_FIELDS] | +| length | u32 | + +### emit_raw_note_log + +```rust +PrivateContext::emit_raw_note_log(&mut self, log, length, note_hash_counter, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| log | [Field; PRIVATE_LOG_SIZE_IN_FIELDS] | +| length | u32 | +| note_hash_counter | u32 | +| | | + +### call_private_function + +```rust +PrivateContext::call_private_function(&mut self, contract_address, function_selector, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| args | [Field; ARGS_COUNT] | +| | | + +### static_call_private_function + +```rust +PrivateContext::static_call_private_function(&mut self, contract_address, function_selector, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| args | [Field; ARGS_COUNT] | +| | | + +### call_private_function_no_args + +```rust +PrivateContext::call_private_function_no_args(&mut self, contract_address, function_selector, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| | | + +### static_call_private_function_no_args + +```rust +PrivateContext::static_call_private_function_no_args(&mut self, contract_address, function_selector, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| | | + +### call_private_function_with_args_hash + +```rust +PrivateContext::call_private_function_with_args_hash(&mut self, contract_address, function_selector, args_hash, is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| args_hash | Field | +| is_static_call | bool | +| | | + +### call_public_function + +```rust +PrivateContext::call_public_function(&mut self, contract_address, function_selector, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| args | [Field; ARGS_COUNT] | +| | | + +### static_call_public_function + +```rust +PrivateContext::static_call_public_function(&mut self, contract_address, function_selector, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| args | [Field; ARGS_COUNT] | +| | | + +### call_public_function_no_args + +```rust +PrivateContext::call_public_function_no_args(&mut self, contract_address, function_selector, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| | | + +### static_call_public_function_no_args + +```rust +PrivateContext::static_call_public_function_no_args(&mut self, contract_address, function_selector, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| | | + +### call_public_function_with_calldata_hash + +```rust +PrivateContext::call_public_function_with_calldata_hash(&mut self, contract_address, calldata_hash, is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| contract_address | AztecAddress | +| calldata_hash | Field | +| is_static_call | bool | +| | | + +### set_public_teardown_function + +```rust +PrivateContext::set_public_teardown_function(&mut self, contract_address, function_selector, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| args | [Field; ARGS_COUNT] | +| | | + +### set_public_teardown_function_with_calldata_hash + +```rust +PrivateContext::set_public_teardown_function_with_calldata_hash(&mut self, contract_address, calldata_hash, is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| contract_address | AztecAddress | +| calldata_hash | Field | +| is_static_call | bool | +| | | + +### next_counter + +```rust +PrivateContext::next_counter(&mut self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | + +## Standalone Functions + +### empty + +```rust +empty(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/public_context.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/public_context.md new file mode 100644 index 000000000000..e1ba16602136 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/public_context.md @@ -0,0 +1,805 @@ +# PublicContext + +## Fields +| Field | Type | +| --- | --- | +| pub args_hash | Option<Field> | +| pub compute_args_hash | fn() -> Field | + +## Methods + +### new + +```rust +PublicContext::new(compute_args_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| compute_args_hash | fn( | + +### emit_public_log + +```rust +PublicContext::emit_public_log(_self, log); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | &mut Self | +| log | T | + +### note_hash_exists + +```rust +PublicContext::note_hash_exists(_self, note_hash, leaf_index); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| note_hash | Field | +| leaf_index | Field | + +### l1_to_l2_msg_exists + +```rust +PublicContext::l1_to_l2_msg_exists(_self, msg_hash, msg_leaf_index); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| msg_hash | Field | +| msg_leaf_index | Field | + +### nullifier_exists + +```rust +PublicContext::nullifier_exists(_self, unsiloed_nullifier, address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| unsiloed_nullifier | Field | +| address | AztecAddress | + +### consume_l1_to_l2_message + +```rust +PublicContext::consume_l1_to_l2_message(&mut self, content, secret, sender, leaf_index, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| content | Field | +| secret | Field | +| sender | EthAddress | +| leaf_index | Field | +| | | + +### message_portal + +```rust +PublicContext::message_portal(_self, recipient, content); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | &mut Self | +| recipient | EthAddress | +| content | Field | + +### call_public_function + +```rust +PublicContext::call_public_function(_self, contract_address, function_selector, args, gas_opts, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | &mut Self | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| args | [Field] | +| gas_opts | GasOpts | +| | | + +### static_call_public_function + +```rust +PublicContext::static_call_public_function(_self, contract_address, function_selector, args, gas_opts, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | &mut Self | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| args | [Field] | +| gas_opts | GasOpts | +| | | + +### push_note_hash + +```rust +PublicContext::push_note_hash(_self, note_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | &mut Self | +| note_hash | Field | + +### push_nullifier + +```rust +PublicContext::push_nullifier(_self, nullifier); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | &mut Self | +| nullifier | Field | + +### this_address + +```rust +PublicContext::this_address(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### msg_sender + +```rust +PublicContext::msg_sender(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### selector + +```rust +PublicContext::selector(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### get_args_hash + +```rust +PublicContext::get_args_hash(mut self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| mut self | | + +### transaction_fee + +```rust +PublicContext::transaction_fee(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### chain_id + +```rust +PublicContext::chain_id(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### version + +```rust +PublicContext::version(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### block_number + +```rust +PublicContext::block_number(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### timestamp + +```rust +PublicContext::timestamp(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### fee_per_l2_gas + +```rust +PublicContext::fee_per_l2_gas(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### fee_per_da_gas + +```rust +PublicContext::fee_per_da_gas(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### l2_gas_left + +```rust +PublicContext::l2_gas_left(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### da_gas_left + +```rust +PublicContext::da_gas_left(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### is_static_call + +```rust +PublicContext::is_static_call(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### raw_storage_read + +```rust +PublicContext::raw_storage_read(_self, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| storage_slot | Field | + +### storage_read + +```rust +PublicContext::storage_read(self, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| storage_slot | Field | + +### raw_storage_write + +```rust +PublicContext::raw_storage_write(_self, storage_slot, values); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| storage_slot | Field | +| values | [Field; N] | + +### storage_write + +```rust +PublicContext::storage_write(self, storage_slot, value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| storage_slot | Field | +| value | T | + +## Standalone Functions + +### address + +```rust +address(); +``` + +Takes no parameters. + +### sender + +```rust +sender(); +``` + +Takes no parameters. + +### emit_note_hash + +```rust +emit_note_hash(note_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| note_hash | Field | + +### emit_nullifier + +```rust +emit_nullifier(nullifier); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| nullifier | Field | + +### send_l2_to_l1_msg + +```rust +send_l2_to_l1_msg(recipient, content); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| recipient | EthAddress | +| content | Field | + +### call + +```rust +call(l2_gas_allocation, da_gas_allocation, address, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| l2_gas_allocation | u32 | +| da_gas_allocation | u32 | +| address | AztecAddress | +| args | [Field] | +| | | + +### call_static + +```rust +call_static(l2_gas_allocation, da_gas_allocation, address, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| l2_gas_allocation | u32 | +| da_gas_allocation | u32 | +| address | AztecAddress | +| args | [Field] | +| | | + +### calldata_copy + +```rust +calldata_copy(cdoffset, copy_size); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| cdoffset | u32 | +| copy_size | u32 | + +### success_copy + +```rust +success_copy(); +``` + +Takes no parameters. + +### returndata_size + +```rust +returndata_size(); +``` + +Takes no parameters. + +### returndata_copy + +```rust +returndata_copy(rdoffset, copy_size); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| rdoffset | u32 | +| copy_size | u32 | + +### avm_return + +```rust +avm_return(returndata); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| returndata | [Field] | + +### avm_revert + +```rust +avm_revert(revertdata); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| revertdata | [Field] | + +### empty + +```rust +empty(); +``` + +Takes no parameters. + +### address_opcode + +```rust +address_opcode(); +``` + +Takes no parameters. + +### sender_opcode + +```rust +sender_opcode(); +``` + +Takes no parameters. + +### transaction_fee_opcode + +```rust +transaction_fee_opcode(); +``` + +Takes no parameters. + +### chain_id_opcode + +```rust +chain_id_opcode(); +``` + +Takes no parameters. + +### version_opcode + +```rust +version_opcode(); +``` + +Takes no parameters. + +### block_number_opcode + +```rust +block_number_opcode(); +``` + +Takes no parameters. + +### timestamp_opcode + +```rust +timestamp_opcode(); +``` + +Takes no parameters. + +### fee_per_l2_gas_opcode + +```rust +fee_per_l2_gas_opcode(); +``` + +Takes no parameters. + +### fee_per_da_gas_opcode + +```rust +fee_per_da_gas_opcode(); +``` + +Takes no parameters. + +### l2_gas_left_opcode + +```rust +l2_gas_left_opcode(); +``` + +Takes no parameters. + +### da_gas_left_opcode + +```rust +da_gas_left_opcode(); +``` + +Takes no parameters. + +### is_static_call_opcode + +```rust +is_static_call_opcode(); +``` + +Takes no parameters. + +### note_hash_exists_opcode + +```rust +note_hash_exists_opcode(note_hash, leaf_index); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| note_hash | Field | +| leaf_index | Field | + +### emit_note_hash_opcode + +```rust +emit_note_hash_opcode(note_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| note_hash | Field | + +### nullifier_exists_opcode + +```rust +nullifier_exists_opcode(nullifier, address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| nullifier | Field | +| address | Field | + +### emit_nullifier_opcode + +```rust +emit_nullifier_opcode(nullifier); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| nullifier | Field | + +### emit_public_log_opcode + +```rust +emit_public_log_opcode(message); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| message | [Field] | + +### l1_to_l2_msg_exists_opcode + +```rust +l1_to_l2_msg_exists_opcode(msg_hash, msg_leaf_index); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| msg_hash | Field | +| msg_leaf_index | Field | + +### send_l2_to_l1_msg_opcode + +```rust +send_l2_to_l1_msg_opcode(recipient, content); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| recipient | EthAddress | +| content | Field | + +### calldata_copy_opcode + +```rust +calldata_copy_opcode(cdoffset, copy_size); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| cdoffset | u32 | +| copy_size | u32 | + +### returndata_size_opcode + +```rust +returndata_size_opcode(); +``` + +Takes no parameters. + +### returndata_copy_opcode + +```rust +returndata_copy_opcode(rdoffset, copy_size); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| rdoffset | u32 | +| copy_size | u32 | + +### return_opcode + +```rust +return_opcode(returndata); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| returndata | [Field] | + +### revert_opcode + +```rust +revert_opcode(revertdata); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| revertdata | [Field] | + +### call_opcode + +```rust +call_opcode(l2_gas_allocation, da_gas_allocation, address, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| l2_gas_allocation | u32 | +| da_gas_allocation | u32 | +| address | AztecAddress | +| args | [Field] | +| | | + +### call_static_opcode + +```rust +call_static_opcode(l2_gas_allocation, da_gas_allocation, address, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| l2_gas_allocation | u32 | +| da_gas_allocation | u32 | +| address | AztecAddress | +| args | [Field] | +| | | + +### success_copy_opcode + +```rust +success_copy_opcode(); +``` + +Takes no parameters. + +### storage_read_opcode + +```rust +storage_read_opcode(storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| storage_slot | Field | + +### storage_write_opcode + +```rust +storage_write_opcode(storage_slot, value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| storage_slot | Field | +| value | Field | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/returns_hash.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/returns_hash.md new file mode 100644 index 000000000000..d96ce2193610 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/returns_hash.md @@ -0,0 +1,66 @@ +# ReturnsHash + +## Fields +| Field | Type | +| --- | --- | +| hash | Field | + +## Methods + +### new + +```rust +ReturnsHash::new(hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| hash | Field | + +### assert_empty + +```rust +ReturnsHash::assert_empty(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### raw + +```rust +ReturnsHash::raw(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_preimage + +/ This is only used during private execution, since in public it is the VM itself that keeps track of return / values. + +```rust +ReturnsHash::get_preimage(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_preimage_and_assert_empty + +```rust +ReturnsHash::get_preimage_and_assert_empty(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/utility_context.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/utility_context.md new file mode 100644 index 000000000000..ce065e17c24d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/context/utility_context.md @@ -0,0 +1,124 @@ +# UtilityContext + +## Fields +| Field | Type | +| --- | --- | +| block_number | u32 | +| timestamp | u64 | +| contract_address | AztecAddress | +| version | Field | +| chain_id | Field | + +## Methods + +### new + +```rust +UtilityContext::new(); +``` + +Takes no parameters. + +### at + +```rust +UtilityContext::at(contract_address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | + +### at_historical + +```rust +UtilityContext::at_historical(contract_address, block_number); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| block_number | u32 | + +### block_number + +```rust +UtilityContext::block_number(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### timestamp + +```rust +UtilityContext::timestamp(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### this_address + +```rust +UtilityContext::this_address(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### version + +```rust +UtilityContext::version(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### chain_id + +```rust +UtilityContext::chain_id(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### raw_storage_read + +```rust +UtilityContext::raw_storage_read(self, storage_slot, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | Self | +| storage_slot | Field | +| | | + +### storage_read + +```rust +UtilityContext::storage_read(self, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| storage_slot | Field | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/deploy.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/deploy.md new file mode 100644 index 000000000000..b250ece82dd1 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/deploy.md @@ -0,0 +1,16 @@ +## Standalone Functions + +### deploy_contract + +```rust +deploy_contract(context, target); +``` + +Calls `deploy` on the deployer contract to deploy a new instance. + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | +| target | AztecAddress | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/event/event_interface.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/event/event_interface.md new file mode 100644 index 000000000000..ea5664efcd5a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/event/event_interface.md @@ -0,0 +1,64 @@ +# PrivateLogContentConstraintsEnum + +## Fields +| Field | Type | +| --- | --- | +| pub NO_CONSTRAINTS | u8 | +| pub CONSTRAINED_ENCRYPTION | u8 | + +## Standalone Functions + +### emit_event_in_private_log + +```rust +emit_event_in_private_log(event, context, sender, recipient, constraints, ); +``` + +/ each value in `PrivateLogContentConstraintsEnum` to learn more about the different variants. + +#### Parameters +| Name | Type | +| --- | --- | +| event | Event | +| context | &mut PrivateContext | +| sender | AztecAddress | +| recipient | AztecAddress | +| constraints | u8 | +| | | + +### emit_event_as_offchain_message + +```rust +emit_event_as_offchain_message(event, context, recipient, ); +``` + +/ valuable. If guaranteed delivery is required, the `emit_event_in_private_log` function should be used instead. + +#### Parameters +| Name | Type | +| --- | --- | +| event | Event | +| context | &mut PrivateContext | +| recipient | AztecAddress | +| | | + +### emit_event_in_public_log + +```rust +emit_event_in_public_log(event, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| event | Event | +| context | &mut PublicContext | + +### get_event_type_id + +```rust +get_event_type_id(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/hash.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/hash.md new file mode 100644 index 000000000000..edcb222c2183 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/hash.md @@ -0,0 +1,181 @@ +# ArgsHasher + +## Fields +| Field | Type | +| --- | --- | +| pub fields | Field] | + +## Methods + +### new + +```rust +ArgsHasher::new(); +``` + +Takes no parameters. + +### add + +```rust +ArgsHasher::add(&mut self, field); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| field | Field | + +### add_multiple + +```rust +ArgsHasher::add_multiple(&mut self, fields); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| fields | [Field; N] | + +## Standalone Functions + +### pedersen_commitment + +```rust +pedersen_commitment(inputs, hash_index); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| inputs | [Field; N] | +| hash_index | u32 | + +### compute_secret_hash + +```rust +compute_secret_hash(secret); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| secret | Field | + +### compute_l1_to_l2_message_hash + +```rust +compute_l1_to_l2_message_hash(sender, chain_id, recipient, version, content, secret_hash, leaf_index, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| sender | EthAddress | +| chain_id | Field | +| recipient | AztecAddress | +| version | Field | +| content | Field | +| secret_hash | Field | +| leaf_index | Field | +| | | + +### compute_l1_to_l2_message_nullifier + +```rust +compute_l1_to_l2_message_nullifier(message_hash, secret); +``` + +The nullifier of a l1 to l2 message is the hash of the message salted with the secret + +#### Parameters +| Name | Type | +| --- | --- | +| message_hash | Field | +| secret | Field | + +### hash + +```rust +hash(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### hash_args_array + +```rust +hash_args_array(args); +``` + +Computes the hash of input arguments or return values for private functions, or for authwit creation. + +#### Parameters +| Name | Type | +| --- | --- | +| args | [Field; N] | + +### hash_args + +```rust +hash_args(args); +``` + +Same as `hash_args_array`, but takes a slice instead of an array. + +#### Parameters +| Name | Type | +| --- | --- | +| args | [Field] | + +### hash_calldata_array + +```rust +hash_calldata_array(calldata); +``` + +Computes the hash of calldata for public functions. + +#### Parameters +| Name | Type | +| --- | --- | +| calldata | [Field; N] | + +### hash_calldata + +```rust +hash_calldata(calldata); +``` + +Same as `hash_calldata_array`, but takes a slice instead of an array. + +#### Parameters +| Name | Type | +| --- | --- | +| calldata | [Field] | + +### compute_public_bytecode_commitment + +```rust +compute_public_bytecode_commitment(mut packed_public_bytecode, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| mut packed_public_bytecode | [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS] | +| | | + +### compute_var_args_hash + +```rust +compute_var_args_hash(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/contract_inclusion.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/contract_inclusion.md new file mode 100644 index 000000000000..99fad72c761e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/contract_inclusion.md @@ -0,0 +1,98 @@ +## Standalone Functions + +### prove_contract_deployment + +```rust +prove_contract_deployment(header, contract_address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| header | BlockHeader | +| contract_address | AztecAddress | + +### prove_contract_deployment + +```rust +prove_contract_deployment(self, contract_address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| contract_address | AztecAddress | + +### prove_contract_non_deployment + +```rust +prove_contract_non_deployment(header, contract_address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| header | BlockHeader | +| contract_address | AztecAddress | + +### prove_contract_non_deployment + +```rust +prove_contract_non_deployment(self, contract_address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| contract_address | AztecAddress | + +### prove_contract_initialization + +```rust +prove_contract_initialization(header, contract_address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| header | BlockHeader | +| contract_address | AztecAddress | + +### prove_contract_initialization + +```rust +prove_contract_initialization(self, contract_address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| contract_address | AztecAddress | + +### prove_contract_non_initialization + +```rust +prove_contract_non_initialization(header, contract_address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| header | BlockHeader | +| contract_address | AztecAddress | + +### prove_contract_non_initialization + +```rust +prove_contract_non_initialization(self, contract_address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| contract_address | AztecAddress | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/note_inclusion.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/note_inclusion.md new file mode 100644 index 000000000000..17ae76551001 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/note_inclusion.md @@ -0,0 +1,29 @@ +## Standalone Functions + +### prove_note_inclusion + +```rust +prove_note_inclusion(header, retrieved_note, storage_slot, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| header | BlockHeader | +| retrieved_note | RetrievedNote<Note> | +| storage_slot | Field | +| | | + +### prove_note_inclusion + +```rust +prove_note_inclusion(self, retrieved_note, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| retrieved_note | RetrievedNote<Note> | +| storage_slot | Field | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/note_inclusion/test.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/note_inclusion/test.md new file mode 100644 index 000000000000..d2cd51877bd0 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/note_inclusion/test.md @@ -0,0 +1,18 @@ +## Standalone Functions + +### note_inclusion + +```rust +note_inclusion(); +``` + +Takes no parameters. + +### note_inclusion_fails + +```rust +note_inclusion_fails(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/note_validity.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/note_validity.md new file mode 100644 index 000000000000..77e745b3d35b --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/note_validity.md @@ -0,0 +1,32 @@ +## Standalone Functions + +### prove_note_validity + +```rust +prove_note_validity(header, retrieved_note, storage_slot, context, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| header | BlockHeader | +| retrieved_note | RetrievedNote<Note> | +| storage_slot | Field | +| context | &mut PrivateContext | +| | | + +### prove_note_validity + +```rust +prove_note_validity(self, retrieved_note, storage_slot, context, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| retrieved_note | RetrievedNote<Note> | +| storage_slot | Field | +| context | &mut PrivateContext | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/note_validity/test.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/note_validity/test.md new file mode 100644 index 000000000000..5048efe761e7 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/note_validity/test.md @@ -0,0 +1,26 @@ +## Standalone Functions + +### note_not_valid_due_to_non_inclusion + +```rust +note_not_valid_due_to_non_inclusion(); +``` + +Takes no parameters. + +### note_is_valid + +```rust +note_is_valid(); +``` + +Takes no parameters. + +### note_not_valid_due_to_nullification + +```rust +note_not_valid_due_to_nullification(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/nullifier_inclusion.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/nullifier_inclusion.md new file mode 100644 index 000000000000..12767f6427d0 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/nullifier_inclusion.md @@ -0,0 +1,58 @@ +## Standalone Functions + +### prove_nullifier_inclusion + +```rust +prove_nullifier_inclusion(header, nullifier); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| header | BlockHeader | +| nullifier | Field | + +### prove_nullifier_inclusion + +```rust +prove_nullifier_inclusion(self, nullifier); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| nullifier | Field | + +### prove_note_is_nullified + +```rust +prove_note_is_nullified(header, retrieved_note, storage_slot, context, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| header | BlockHeader | +| retrieved_note | RetrievedNote<Note> | +| storage_slot | Field | +| context | &mut PrivateContext | +| | | + +### prove_note_is_nullified + +```rust +prove_note_is_nullified(self, retrieved_note, storage_slot, context, ); +``` + +docs:start:prove_note_is_nullified + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| retrieved_note | RetrievedNote<Note> | +| storage_slot | Field | +| context | &mut PrivateContext | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/nullifier_inclusion/test.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/nullifier_inclusion/test.md new file mode 100644 index 000000000000..eb17eb928337 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/nullifier_inclusion/test.md @@ -0,0 +1,34 @@ +## Standalone Functions + +### note_is_nullified + +```rust +note_is_nullified(); +``` + +Takes no parameters. + +### note_is_not_nullified + +```rust +note_is_not_nullified(); +``` + +Takes no parameters. + +### nullifier_inclusion + +```rust +nullifier_inclusion(); +``` + +Takes no parameters. + +### nullifier_inclusion_fails + +```rust +nullifier_inclusion_fails(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/nullifier_non_inclusion.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/nullifier_non_inclusion.md new file mode 100644 index 000000000000..003fe67433aa --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/nullifier_non_inclusion.md @@ -0,0 +1,58 @@ +## Standalone Functions + +### prove_nullifier_non_inclusion + +```rust +prove_nullifier_non_inclusion(header, nullifier); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| header | BlockHeader | +| nullifier | Field | + +### prove_nullifier_non_inclusion + +```rust +prove_nullifier_non_inclusion(self, nullifier); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| nullifier | Field | + +### prove_note_not_nullified + +```rust +prove_note_not_nullified(header, retrieved_note, storage_slot, context, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| header | BlockHeader | +| retrieved_note | RetrievedNote<Note> | +| storage_slot | Field | +| context | &mut PrivateContext | +| | | + +### prove_note_not_nullified + +```rust +prove_note_not_nullified(self, retrieved_note, storage_slot, context, ); +``` + +docs:start:prove_note_not_nullified + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| retrieved_note | RetrievedNote<Note> | +| storage_slot | Field | +| context | &mut PrivateContext | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/nullifier_non_inclusion/test.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/nullifier_non_inclusion/test.md new file mode 100644 index 000000000000..1049861dcd79 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/nullifier_non_inclusion/test.md @@ -0,0 +1,34 @@ +## Standalone Functions + +### note_not_nullified + +```rust +note_not_nullified(); +``` + +Takes no parameters. + +### note_not_nullified_fails + +```rust +note_not_nullified_fails(); +``` + +Takes no parameters. + +### nullifier_non_inclusion + +```rust +nullifier_non_inclusion(); +``` + +Takes no parameters. + +### nullifier_non_inclusion_fails + +```rust +nullifier_non_inclusion_fails(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/public_storage.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/public_storage.md new file mode 100644 index 000000000000..f7cd599a8f70 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/public_storage.md @@ -0,0 +1,30 @@ +## Standalone Functions + +### public_storage_historical_read + +```rust +public_storage_historical_read(header, storage_slot, contract_address, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| header | BlockHeader | +| storage_slot | Field | +| contract_address | AztecAddress | +| | | + +### public_storage_historical_read + +```rust +public_storage_historical_read(self, storage_slot, contract_address, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| storage_slot | Field | +| contract_address | AztecAddress | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/public_storage/test.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/public_storage/test.md new file mode 100644 index 000000000000..84a551e9f2c1 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/public_storage/test.md @@ -0,0 +1,26 @@ +## Standalone Functions + +### setup + +```rust +setup(); +``` + +Takes no parameters. + +### public_storage_read + +```rust +public_storage_read(); +``` + +Takes no parameters. + +### public_storage_read_empty + +```rust +public_storage_read_empty(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/test.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/test.md new file mode 100644 index 000000000000..a60a191e3bd8 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/history/test.md @@ -0,0 +1,18 @@ +## Standalone Functions + +### create_note + +```rust +create_note(); +``` + +Takes no parameters. + +### create_note_and_nullify_it + +```rust +create_note_and_nullify_it(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/keys/ecdh_shared_secret.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/keys/ecdh_shared_secret.md new file mode 100644 index 000000000000..6208b7f6505a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/keys/ecdh_shared_secret.md @@ -0,0 +1,55 @@ +## Standalone Functions + +### derive_ecdh_shared_secret + +```rust +derive_ecdh_shared_secret(secret, public_key); +``` + +See also: https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman + +#### Parameters +| Name | Type | +| --- | --- | +| secret | Scalar | +| public_key | Point | + +### derive_ecdh_shared_secret_using_aztec_address + +```rust +derive_ecdh_shared_secret_using_aztec_address(ephemeral_secret, recipient_address, ); +``` + +/ given the address of their intended recipient. + +#### Parameters +| Name | Type | +| --- | --- | +| ephemeral_secret | Scalar | +| recipient_address | AztecAddress | +| | | + +### test_consistency_with_typescript + +```rust +test_consistency_with_typescript(); +``` + +Takes no parameters. + +### test_shared_secret_computation_in_both_directions + +```rust +test_shared_secret_computation_in_both_directions(); +``` + +Takes no parameters. + +### test_shared_secret_computation_from_address_in_both_directions + +```rust +test_shared_secret_computation_from_address_in_both_directions(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/keys/ephemeral.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/keys/ephemeral.md new file mode 100644 index 000000000000..e1c05843a2da --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/keys/ephemeral.md @@ -0,0 +1,10 @@ +## Standalone Functions + +### generate_ephemeral_key_pair + +```rust +generate_ephemeral_key_pair(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/keys/getters/mod.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/keys/getters/mod.md new file mode 100644 index 000000000000..9bf0f20e23a2 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/keys/getters/mod.md @@ -0,0 +1,37 @@ +## Standalone Functions + +### get_nsk_app + +```rust +get_nsk_app(npk_m_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| npk_m_hash | Field | + +### get_ovsk_app + +```rust +get_ovsk_app(ovpk_m_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| ovpk_m_hash | Field | + +### get_public_keys + +```rust +get_public_keys(account); +``` + +read keys that are not required by the caller can simply be discarded. + +#### Parameters +| Name | Type | +| --- | --- | +| account | AztecAddress | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/keys/getters/test.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/keys/getters/test.md new file mode 100644 index 000000000000..35208cbe0faa --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/keys/getters/test.md @@ -0,0 +1,26 @@ +## Standalone Functions + +### setup + +```rust +setup(); +``` + +Takes no parameters. + +### test_get_public_keys_unknown + +```rust +test_get_public_keys_unknown(); +``` + +Takes no parameters. + +### test_get_public_keys_known + +```rust +test_get_public_keys_known(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/aztec.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/aztec.md new file mode 100644 index 000000000000..8c49c0c6e6b4 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/aztec.md @@ -0,0 +1,158 @@ +## Standalone Functions + +### aztec + +```rust +aztec(m); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| m | Module | + +### generate_contract_interface + +```rust +generate_contract_interface(m); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| m | Module | + +### storage_layout + +```rust +storage_layout(); +``` + +Takes no parameters. + +### at + +```rust +at(addr); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| addr | aztec | + +### interface + +```rust +interface(); +``` + +Takes no parameters. + +### at + +```rust +at(addr); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| addr | aztec | + +### interface + +```rust +interface(); +``` + +Takes no parameters. + +### generate_contract_library_method_compute_note_hash_and_nullifier + +```rust +generate_contract_library_method_compute_note_hash_and_nullifier(); +``` + +Takes no parameters. + +### _compute_note_hash_and_nullifier + +```rust +_compute_note_hash_and_nullifier(packed_note, aztec, storage_slot, note_type_id, contract_address, note_nonce, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| packed_note | BoundedVec<Field | +| aztec | | +| storage_slot | Field | +| note_type_id | Field | +| contract_address | aztec | +| note_nonce | Field | +| | | + +### _compute_note_hash_and_nullifier + +```rust +_compute_note_hash_and_nullifier(_packed_note, aztec, _storage_slot, _note_type_id, _contract_address, _nonce, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _packed_note | BoundedVec<Field | +| aztec | | +| _storage_slot | Field | +| _note_type_id | Field | +| _contract_address | aztec | +| _nonce | Field | +| | | + +### generate_note_exports + +```rust +generate_note_exports(); +``` + +Takes no parameters. + +### generate_sync_private_state + +```rust +generate_sync_private_state(); +``` + +Takes no parameters. + +### sync_private_state + +```rust +sync_private_state(); +``` + +Takes no parameters. + +### generate_process_message + +```rust +generate_process_message(); +``` + +Takes no parameters. + +### process_message + +```rust +process_message(message_ciphertext, aztec, message_context, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| message_ciphertext | BoundedVec<Field | +| aztec | | +| message_context | aztec | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/dispatch.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/dispatch.md new file mode 100644 index 000000000000..2d48941a625c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/dispatch.md @@ -0,0 +1,43 @@ +## Standalone Functions + +### public_dispatch + +```rust +public_dispatch(.); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| . | | + +### generate_public_dispatch + +```rust +generate_public_dispatch(m); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| m | Module | + +### public_dispatch + +```rust +public_dispatch(selector); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| selector | Field | + +### get_type + +```rust +get_type(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/events.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/events.md new file mode 100644 index 000000000000..62866062a554 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/events.md @@ -0,0 +1,55 @@ +## Standalone Functions + +### generate_event_interface_and_get_selector + +```rust +generate_event_interface_and_get_selector(s); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | + +### get_event_type_id + +```rust +get_event_type_id(); +``` + +Takes no parameters. + +### derive_serialize_if_not_implemented + +```rust +derive_serialize_if_not_implemented(s); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | + +### register_event_selector + +```rust +register_event_selector(event_selector, event_name); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| event_selector | Field | +| event_name | Quoted | + +### event + +```rust +event(s); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/abi_export.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/abi_export.md new file mode 100644 index 000000000000..b559bf7092be --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/abi_export.md @@ -0,0 +1,13 @@ +## Standalone Functions + +### create_fn_abi_export + +```rust +create_fn_abi_export(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/call_interface_stubs.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/call_interface_stubs.md new file mode 100644 index 000000000000..799f04eef034 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/call_interface_stubs.md @@ -0,0 +1,135 @@ +## Standalone Functions + +### stub_fn + +```rust +stub_fn(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_stub_base + +```rust +create_stub_base(f, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | +| | | + +### create_private_stub + +```rust +create_private_stub(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_private_static_stub + +```rust +create_private_static_stub(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_private_void_stub + +```rust +create_private_void_stub(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_private_static_void_stub + +```rust +create_private_static_void_stub(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_public_stub + +```rust +create_public_stub(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_public_static_stub + +```rust +create_public_static_stub(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_public_void_stub + +```rust +create_public_void_stub(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_public_static_void_stub + +```rust +create_public_static_void_stub(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_utility_stub + +```rust +create_utility_stub(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_utility_void_stub + +```rust +create_utility_void_stub(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/initialization_utils.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/initialization_utils.md new file mode 100644 index 000000000000..c3a8e5393fd3 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/initialization_utils.md @@ -0,0 +1,94 @@ +## Standalone Functions + +### mark_as_initialized_public + +```rust +mark_as_initialized_public(context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PublicContext | + +### mark_as_initialized_private + +```rust +mark_as_initialized_private(context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | + +### assert_is_initialized_public + +```rust +assert_is_initialized_public(context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PublicContext | + +### assert_is_initialized_private + +```rust +assert_is_initialized_private(context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | + +### compute_unsiloed_contract_initialization_nullifier + +```rust +compute_unsiloed_contract_initialization_nullifier(address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | + +### assert_initialization_matches_address_preimage_public + +```rust +assert_initialization_matches_address_preimage_public(context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | PublicContext | + +### assert_initialization_matches_address_preimage_private + +```rust +assert_initialization_matches_address_preimage_private(context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | PrivateContext | + +### compute_initialization_hash + +```rust +compute_initialization_hash(init_selector, init_args_hash, ); +``` + +/ initialized with the correct constructor arguments. Don't hide this unless you implement factory functionality. + +#### Parameters +| Name | Type | +| --- | --- | +| init_selector | FunctionSelector | +| init_args_hash | Field | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/mod.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/mod.md new file mode 100644 index 000000000000..34ee91f8d8b7 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/mod.md @@ -0,0 +1,79 @@ +## Standalone Functions + +### initializer + +```rust +initializer(_f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _f | FunctionDefinition | + +### noinitcheck + +```rust +noinitcheck(_f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _f | FunctionDefinition | + +### internal + +```rust +internal(_f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _f | FunctionDefinition | + +### view + +```rust +view(_f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _f | FunctionDefinition | + +### private + +```rust +private(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### public + +```rust +public(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### utility + +```rust +utility(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/stub_registry.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/stub_registry.md new file mode 100644 index 000000000000..b8d43a524603 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/stub_registry.md @@ -0,0 +1,25 @@ +## Standalone Functions + +### register + +```rust +register(m, stub); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| m | Module | +| stub | Quoted | + +### get + +```rust +get(m); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| m | Module | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/utils.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/utils.md new file mode 100644 index 000000000000..335d1c3bd9f1 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/functions/utils.md @@ -0,0 +1,109 @@ +## Standalone Functions + +### transform_private + +```rust +transform_private(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### transform_public + +```rust +transform_public(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### transform_utility + +```rust +transform_utility(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_internal_check + +```rust +create_internal_check(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_view_check + +```rust +create_view_check(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_assert_correct_initializer_args + +```rust +create_assert_correct_initializer_args(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_mark_as_initialized + +```rust +create_mark_as_initialized(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_init_check + +```rust +create_init_check(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### create_message_discovery_call + +```rust +create_message_discovery_call(); +``` + +Takes no parameters. + +### check_each_fn_macroified + +```rust +check_each_fn_macroified(m); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| m | Module | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/notes.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/notes.md new file mode 100644 index 000000000000..18cc24210af5 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/notes.md @@ -0,0 +1,284 @@ +# TokenNoteProperties + +/ Generates note properties struct for a given note struct `s`. / / Example: / ``` + +# TokenNoteFields_5695262104 + +/ Generates note export for a given note struct `s`. The export is a global variable that contains note type id, / note name and information about note fields (field name, index and whether the field is nullable or not). / / Example: / ``` + +# CustomNote + +/ Generates code for a custom note implementation that requires specialized note hash or nullifier computation. / / # Generated Code / - NoteTypeProperties: Defines the structure and properties of note fields / - NoteType trait implementation: Provides the note type ID / - Packable implementation: Enables serialization/deserialization of the note / / # Registration / Registers the note in the global `NOTES` map with: / - Note type ID / - Packed length / - Field indices and nullability / / # Use Cases / Use this macro when implementing a note that needs custom: / - Note hash computation logic / - Nullifier computation logic / / The macro omits generating default NoteHash trait implementation, allowing you to provide your own. / / # Example / ``` / #[custom_note] + +## Standalone Functions + +### get_next_note_type_id + +```rust +get_next_note_type_id(); +``` + +Takes no parameters. + +### derive_packable_if_not_implemented_and_get_len + +```rust +derive_packable_if_not_implemented_and_get_len(s); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | + +### get_id + +```rust +get_id(); +``` + +Takes no parameters. + +### generate_note_interface + +```rust +generate_note_interface(s, note_type_id); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | +| note_type_id | Field | + +### get_id + +```rust +get_id(); +``` + +Takes no parameters. + +### compute_note_hash + +```rust +compute_note_hash(self, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| storage_slot | Field | + +### compute_nullifier + +```rust +compute_nullifier(self, context, note_hash_for_nullify); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | +| note_hash_for_nullify | Field | + +### compute_nullifier_unconstrained + +```rust +compute_nullifier_unconstrained(note_hash_for_nullify); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| note_hash_for_nullify | Field | + +### generate_note_hash_trait_impl + +```rust +generate_note_hash_trait_impl(s); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | + +### compute_note_hash + +```rust +compute_note_hash(self, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| storage_slot | Field | + +### compute_nullifier + +```rust +compute_nullifier(self, context, note_hash_for_nullify, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut aztec | +| note_hash_for_nullify | Field | +| | | + +### compute_nullifier_unconstrained + +```rust +compute_nullifier_unconstrained(self, note_hash_for_nullify, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| note_hash_for_nullify | Field | +| | | + +### properties + +```rust +properties(); +``` + +Takes no parameters. + +### generate_note_properties + +```rust +generate_note_properties(s); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | + +### properties + +```rust +properties(); +``` + +Takes no parameters. + +### generate_note_export + +```rust +generate_note_export(s, note_type_id, fields, u32, bool); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | +| note_type_id | Field | +| fields | [(Quoted | +| u32 | | +| bool | | + +### register_note + +```rust +register_note(note, note_packed_len, note_type_id, fixed_fields, Type, u32); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| note | TypeDefinition | +| note_packed_len | u32 | +| note_type_id | Field | +| fixed_fields | [(Quoted | +| Type | | +| u32 | | + +### index_note_fields + +```rust +index_note_fields(s, nullable_fields, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | +| nullable_fields | [Quoted] | +| | | + +### note + +```rust +note(s); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | + +### compute_note_hash + +```rust +compute_note_hash(.); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| . | | + +### compute_nullifier + +```rust +compute_nullifier(.); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| . | | + +### compute_nullifier_unconstrained + +```rust +compute_nullifier_unconstrained(.); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| . | | + +### custom_note + +```rust +custom_note(s); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | + +### assert_has_owner + +```rust +assert_has_owner(note); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| note | TypeDefinition | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/storage.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/storage.md new file mode 100644 index 000000000000..e1bec46fece5 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/storage.md @@ -0,0 +1,95 @@ +# StorageLayoutFields + +## Standalone Functions + +### storage + +```rust +storage(s); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | + +### init + +```rust +init(context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | Context | + +### init + +```rust +init(context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | Context | + +### storage_no_init + +```rust +storage_no_init(s); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | + +### generate_storage_field_constructor + +```rust +generate_storage_field_constructor(typ, slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | +| slot | Quoted | + +### new + +```rust +new(context, slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | Context | +| slot | Field | + +### is_storage_map + +```rust +is_storage_map(typ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | + +### add_context_generic + +```rust +add_context_generic(typ, context_generic); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | +| context_generic | Type | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/utils.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/utils.md new file mode 100644 index 000000000000..ed2266111e92 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/macros/utils.md @@ -0,0 +1,370 @@ +# Foo + +The event selector is computed from the type signature of the struct in the event, similar to how one might type the constructor function. For example, given: + +## Standalone Functions + +### get_fn_visibility + +```rust +get_fn_visibility(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### is_fn_private + +```rust +is_fn_private(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### is_fn_public + +```rust +is_fn_public(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### is_fn_utility + +```rust +is_fn_utility(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### is_fn_contract_library_method + +```rust +is_fn_contract_library_method(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### is_fn_test + +```rust +is_fn_test(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### is_fn_view + +```rust +is_fn_view(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### is_fn_internal + +```rust +is_fn_internal(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### is_fn_initializer + +```rust +is_fn_initializer(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### fn_has_noinitcheck + +```rust +fn_has_noinitcheck(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### modify_fn_body + +```rust +modify_fn_body(body, prepend, append); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| body | [Expr] | +| prepend | Quoted | +| append | Quoted | + +### add_to_field_array + +```rust +add_to_field_array(array_name, index_name, name, typ, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| array_name | Quoted | +| index_name | Quoted | +| name | Quoted | +| typ | Type | +| | | + +### add_to_hasher + +```rust +add_to_hasher(hasher_name, name, typ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| hasher_name | Quoted | +| name | Quoted | +| typ | Type | + +### signature_of_type + +```rust +signature_of_type(typ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | + +### as_str_quote + +```rust +as_str_quote(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### as_str_quote + +```rust +as_str_quote(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### compute_fn_selector + +```rust +compute_fn_selector(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | FunctionDefinition | + +### foo + +```rust +foo(a, b); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| a | Field | +| b | AztecAddress | + +### compute_event_selector + +```rust +compute_event_selector(s); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| s | TypeDefinition | + +### get_storage_size + +```rust +get_storage_size(typ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | + +### module_has_storage + +```rust +module_has_storage(m); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| m | Module | + +### module_has_initializer + +```rust +module_has_initializer(m); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| m | Module | + +### get_trait_impl_method + +```rust +get_trait_impl_method(typ, target_trait, target_method, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | +| target_trait | Quoted | +| target_method | Quoted | +| | | + +### size_in_fields + +```rust +size_in_fields(typ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | + +### array_size_in_fields + +```rust +array_size_in_fields(typ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | + +### bool_size_in_fields + +```rust +bool_size_in_fields(typ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | + +### field_size_in_fields + +```rust +field_size_in_fields(typ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | + +### int_size_in_fields + +```rust +int_size_in_fields(typ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | + +### constant_size_in_fields + +```rust +constant_size_in_fields(typ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | + +### str_size_in_fields + +```rust +str_size_in_fields(typ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | + +### struct_size_in_fields + +```rust +struct_size_in_fields(typ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | + +### tuple_size_in_fields + +```rust +tuple_size_in_fields(typ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| typ | Type | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/mod.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/mod.md new file mode 100644 index 000000000000..44476bc58c20 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/mod.md @@ -0,0 +1,23 @@ +# NoteHashAndNullifier + +## Fields +| Field | Type | +| --- | --- | +| pub note_hash | Field | +| pub inner_nullifier | Field | + +## Standalone Functions + +### discover_new_messages + +```rust +discover_new_messages(contract_address, compute_note_hash_and_nullifier, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| compute_note_hash_and_nullifier | ComputeNoteHashAndNullifier<Env> | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/nonce_discovery.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/nonce_discovery.md new file mode 100644 index 000000000000..0e535ad351f8 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/nonce_discovery.md @@ -0,0 +1,105 @@ +# DiscoveredNoteInfo + +/ A struct with the discovered information of a complete note, required for delivery to PXE. Note that this is *not* / the complete note information, since it does not include content, storage slot, etc. + +## Fields +| Field | Type | +| --- | --- | +| pub note_nonce | Field | +| pub note_hash | Field | +| pub inner_nullifier | Field | + +# NoteAndData + +## Fields +| Field | Type | +| --- | --- | +| note | MockNote | +| note_nonce | Field | +| note_hash | Field | +| unique_note_hash | Field | +| inner_nullifier | Field | + +## Standalone Functions + +### attempt_note_nonce_discovery + +```rust +attempt_note_nonce_discovery(unique_note_hashes_in_tx, MAX_NOTE_HASHES_PER_TX>, first_nullifier_in_tx, compute_note_hash_and_nullifier, contract_address, storage_slot, note_type_id, packed_note, MAX_NOTE_PACKED_LEN>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| unique_note_hashes_in_tx | BoundedVec<Field | +| MAX_NOTE_HASHES_PER_TX> | | +| first_nullifier_in_tx | Field | +| compute_note_hash_and_nullifier | ComputeNoteHashAndNullifier<Env> | +| contract_address | AztecAddress | +| storage_slot | Field | +| note_type_id | Field | +| packed_note | BoundedVec<Field | +| MAX_NOTE_PACKED_LEN> | | +| | | + +### compute_note_hash_and_nullifier + +```rust +compute_note_hash_and_nullifier(packed_note, MAX_NOTE_PACKED_LEN>, storage_slot, note_type_id, contract_address, note_nonce, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| packed_note | BoundedVec<Field | +| MAX_NOTE_PACKED_LEN> | | +| storage_slot | Field | +| note_type_id | Field | +| contract_address | AztecAddress | +| note_nonce | Field | +| | | + +### no_note_hashes + +```rust +no_note_hashes(); +``` + +Takes no parameters. + +### failed_hash_computation + +```rust +failed_hash_computation(); +``` + +Takes no parameters. + +### construct_note + +```rust +construct_note(value, note_index_in_tx); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| value | Field | +| note_index_in_tx | u32 | + +### single_note + +```rust +single_note(); +``` + +Takes no parameters. + +### multiple_notes_same_preimage + +```rust +multiple_notes_same_preimage(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/partial_notes.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/partial_notes.md new file mode 100644 index 000000000000..9e68028ffb34 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/partial_notes.md @@ -0,0 +1,56 @@ +# DeliveredPendingPartialNote + +## Fields +| Field | Type | +| --- | --- | +| pub(crate) note_completion_log_tag | Field | +| pub(crate) storage_slot | Field | +| pub(crate) note_type_id | Field | +| pub(crate) packed_private_note_content | BoundedVec<Field, MAX_PARTIAL_NOTE_PRIVATE_PACKED_LEN> | +| pub(crate) recipient | AztecAddress | + +## Standalone Functions + +### process_partial_note_private_msg + +```rust +process_partial_note_private_msg(contract_address, recipient, msg_metadata, msg_content, MAX_MESSAGE_CONTENT_LEN>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| recipient | AztecAddress | +| msg_metadata | u64 | +| msg_content | BoundedVec<Field | +| MAX_MESSAGE_CONTENT_LEN> | | +| | | + +### fetch_and_process_partial_note_completion_logs + +```rust +fetch_and_process_partial_note_completion_logs(contract_address, compute_note_hash_and_nullifier, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| compute_note_hash_and_nullifier | ComputeNoteHashAndNullifier<Env> | +| | | + +### decode_partial_note_private_msg + +```rust +decode_partial_note_private_msg(msg_metadata, msg_content, MAX_MESSAGE_CONTENT_LEN>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| msg_metadata | u64 | +| msg_content | BoundedVec<Field | +| MAX_MESSAGE_CONTENT_LEN> | | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/private_events.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/private_events.md new file mode 100644 index 000000000000..64430c8446fd --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/private_events.md @@ -0,0 +1,19 @@ +## Standalone Functions + +### process_private_event_msg + +```rust +process_private_event_msg(contract_address, recipient, msg_metadata, msg_content, MAX_MESSAGE_CONTENT_LEN>, tx_hash, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| recipient | AztecAddress | +| msg_metadata | u64 | +| msg_content | BoundedVec<Field | +| MAX_MESSAGE_CONTENT_LEN> | | +| tx_hash | Field | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/private_notes.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/private_notes.md new file mode 100644 index 000000000000..ab45092bc0a4 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/private_notes.md @@ -0,0 +1,59 @@ +## Standalone Functions + +### process_private_note_msg + +```rust +process_private_note_msg(contract_address, tx_hash, unique_note_hashes_in_tx, MAX_NOTE_HASHES_PER_TX>, first_nullifier_in_tx, recipient, compute_note_hash_and_nullifier, msg_metadata, msg_content, MAX_MESSAGE_CONTENT_LEN>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| tx_hash | Field | +| unique_note_hashes_in_tx | BoundedVec<Field | +| MAX_NOTE_HASHES_PER_TX> | | +| first_nullifier_in_tx | Field | +| recipient | AztecAddress | +| compute_note_hash_and_nullifier | ComputeNoteHashAndNullifier<Env> | +| msg_metadata | u64 | +| msg_content | BoundedVec<Field | +| MAX_MESSAGE_CONTENT_LEN> | | +| | | + +### attempt_note_discovery + +```rust +attempt_note_discovery(contract_address, tx_hash, unique_note_hashes_in_tx, MAX_NOTE_HASHES_PER_TX>, first_nullifier_in_tx, recipient, compute_note_hash_and_nullifier, storage_slot, note_type_id, packed_note, MAX_NOTE_PACKED_LEN>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| tx_hash | Field | +| unique_note_hashes_in_tx | BoundedVec<Field | +| MAX_NOTE_HASHES_PER_TX> | | +| first_nullifier_in_tx | Field | +| recipient | AztecAddress | +| compute_note_hash_and_nullifier | ComputeNoteHashAndNullifier<Env> | +| storage_slot | Field | +| note_type_id | Field | +| packed_note | BoundedVec<Field | +| MAX_NOTE_PACKED_LEN> | | +| | | + +### decode_private_note_msg + +```rust +decode_private_note_msg(msg_metadata, msg_content, MAX_MESSAGE_CONTENT_LEN>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| msg_metadata | u64 | +| msg_content | BoundedVec<Field | +| MAX_MESSAGE_CONTENT_LEN> | | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/process_message.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/process_message.md new file mode 100644 index 000000000000..c06145209301 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/discovery/process_message.md @@ -0,0 +1,18 @@ +## Standalone Functions + +### do_process_message + +```rust +do_process_message(contract_address, compute_note_hash_and_nullifier, message_ciphertext, PRIVATE_LOG_CIPHERTEXT_LEN>, message_context, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| compute_note_hash_and_nullifier | ComputeNoteHashAndNullifier<Env> | +| message_ciphertext | BoundedVec<Field | +| PRIVATE_LOG_CIPHERTEXT_LEN> | | +| message_context | MessageContext | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/encoding.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/encoding.md new file mode 100644 index 000000000000..ddbc84bc7b61 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/encoding.md @@ -0,0 +1,122 @@ +## Standalone Functions + +### encode_message + +```rust +encode_message(msg_type, msg_metadata, msg_content, ); +``` + +/ this smaller variable to achieve higher data efficiency. + +#### Parameters +| Name | Type | +| --- | --- | +| msg_type | u64 | +| msg_metadata | u64 | +| msg_content | [Field; N] | +| | | + +### decode_message + +```rust +decode_message(message, MAX_MESSAGE_LEN>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| message | BoundedVec<Field | +| MAX_MESSAGE_LEN> | | +| | | + +### to_expanded_metadata + +```rust +to_expanded_metadata(msg_type, msg_metadata); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| msg_type | u64 | +| msg_metadata | u64 | + +### from_expanded_metadata + +```rust +from_expanded_metadata(input); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| input | Field | + +### encode_decode_empty_message + +```rust +encode_decode_empty_message(msg_type, msg_metadata); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| msg_type | u64 | +| msg_metadata | u64 | + +### encode_decode_short_message + +```rust +encode_decode_short_message(msg_type, msg_metadata, msg_content, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| msg_type | u64 | +| msg_metadata | u64 | +| msg_content | [Field; MAX_MESSAGE_CONTENT_LEN / 2] | +| | | + +### encode_decode_full_message + +```rust +encode_decode_full_message(msg_type, msg_metadata, msg_content, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| msg_type | u64 | +| msg_metadata | u64 | +| msg_content | [Field; MAX_MESSAGE_CONTENT_LEN] | +| | | + +### to_expanded_metadata_packing + +```rust +to_expanded_metadata_packing(); +``` + +Takes no parameters. + +### from_expanded_metadata_packing + +```rust +from_expanded_metadata_packing(); +``` + +Takes no parameters. + +### to_from_expanded_metadata + +```rust +to_from_expanded_metadata(original_msg_type, original_msg_metadata); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| original_msg_type | u64 | +| original_msg_metadata | u64 | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/encryption/aes128.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/encryption/aes128.md new file mode 100644 index 000000000000..e25aca927fa3 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/encryption/aes128.md @@ -0,0 +1,105 @@ +# AES128 + +## Standalone Functions + +### extract_close_to_uniformly_random_256_bits_from_ecdh_shared_secret_using_poseidon2 + +```rust +extract_close_to_uniformly_random_256_bits_from_ecdh_shared_secret_using_poseidon2(shared_secret, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| shared_secret | Point | +| | | + +### extract_close_to_uniformly_random_256_bits_from_ecdh_shared_secret_using_sha256 + +```rust +extract_close_to_uniformly_random_256_bits_from_ecdh_shared_secret_using_sha256(shared_secret, ); +``` + +TODO(#10537): Consider nuking this function. + +#### Parameters +| Name | Type | +| --- | --- | +| shared_secret | Point | +| | | + +### derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret + +```rust +derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret(shared_secret, randomness_extraction_fn); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| shared_secret | Point | +| randomness_extraction_fn | fn(Point | + +### derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_sha256 + +```rust +derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_sha256(shared_secret, ); +``` + +TODO(#10537): Consider nuking this function. + +#### Parameters +| Name | Type | +| --- | --- | +| shared_secret | Point | +| | | + +### derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2 + +```rust +derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2(shared_secret, ); +``` + +TODO(#10537): This function is currently unused. Consider using it instead of the sha256 one. + +#### Parameters +| Name | Type | +| --- | --- | +| shared_secret | Point | +| | | + +### encrypt_log + +```rust +encrypt_log(plaintext, recipient, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| plaintext | [Field; PLAINTEXT_LEN] | +| recipient | AztecAddress | +| | | + +### decrypt_log + +```rust +decrypt_log(ciphertext, PRIVATE_LOG_CIPHERTEXT_LEN>, recipient, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| ciphertext | BoundedVec<Field | +| PRIVATE_LOG_CIPHERTEXT_LEN> | | +| recipient | AztecAddress | +| | | + +### encrypt_decrypt_log + +```rust +encrypt_decrypt_log(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/encryption/log_encryption.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/encryption/log_encryption.md new file mode 100644 index 000000000000..d3b17654b5a2 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/encryption/log_encryption.md @@ -0,0 +1,31 @@ +## Standalone Functions + +### encrypt_log + +```rust +encrypt_log(plaintext, recipient, ); +``` + +/ Fixed-size array of encrypted Field elements representing the private log + +#### Parameters +| Name | Type | +| --- | --- | +| plaintext | [Field; PLAINTEXT_LEN] | +| recipient | AztecAddress | +| | | + +### decrypt_log + +```rust +decrypt_log(ciphertext, PRIVATE_LOG_CIPHERTEXT_LEN>, recipient, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| ciphertext | BoundedVec<Field | +| PRIVATE_LOG_CIPHERTEXT_LEN> | | +| recipient | AztecAddress | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/encryption/poseidon2.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/encryption/poseidon2.md new file mode 100644 index 000000000000..e0cfd5d64164 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/encryption/poseidon2.md @@ -0,0 +1,87 @@ +## Standalone Functions + +### poseidon2_encrypt + +```rust +poseidon2_encrypt(msg, shared_secret, encryption_nonce, ); +``` + +/// @param encryption_nonce is only needed if your use case needs to protect against replay attacks. + +#### Parameters +| Name | Type | +| --- | --- | +| msg | [Field; L] | +| shared_secret | Point | +| encryption_nonce | Field | +| | | + +### poseidon2_decrypt + +```rust +poseidon2_decrypt(ciphertext); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| ciphertext | [Field; ((L + 3 - 1 | + +### encrypt_then_decrypt + +```rust +encrypt_then_decrypt(msg); +``` + +Helper function that allows us to test encryption, then decryption, for various sizes of message. + +#### Parameters +| Name | Type | +| --- | --- | +| msg | [Field; N] | + +### poseidon2_encryption + +```rust +poseidon2_encryption(); +``` + +Takes no parameters. + +### test_poseidon2_decryption_with_bad_secret_fails + +```rust +test_poseidon2_decryption_with_bad_secret_fails(); +``` + +Takes no parameters. + +### encrypt_and_return_ct_length + +```rust +encrypt_and_return_ct_length(msg); +``` + +Helper function with encryption boilerplate + +#### Parameters +| Name | Type | +| --- | --- | +| msg | [Field; N] | + +### test_ciphertext_lengths + +```rust +test_ciphertext_lengths(); +``` + +Takes no parameters. + +### test_2_pow_128 + +```rust +test_2_pow_128(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/logs/arithmetic_generics_utils.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/logs/arithmetic_generics_utils.md new file mode 100644 index 000000000000..b1acbeabaf22 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/logs/arithmetic_generics_utils.md @@ -0,0 +1,100 @@ +## Standalone Functions + +### get_arr_of_size__full_plaintext + +```rust +get_arr_of_size__full_plaintext(); +``` + +|full_pt| = |pt| = (N * 32) + 64 + +Takes no parameters. + +### get_arr_of_size__plaintext_aes_padding + +```rust +get_arr_of_size__plaintext_aes_padding(_full_pt, ); +``` + +|pt_aes_padding| = 16 - (|full_pt| % 16) + +#### Parameters +| Name | Type | +| --- | --- | +| _full_pt | [u8; FULL_PT] | +| | | + +### get_arr_of_size__ciphertext + +```rust +get_arr_of_size__ciphertext(_full_pt, _pt_aes_padding, ); +``` + +|ct| = |full_pt| + |pt_aes_padding| + +#### Parameters +| Name | Type | +| --- | --- | +| _full_pt | [u8; FULL_PT] | +| _pt_aes_padding | [u8; PT_AES_PADDING] | +| | | + +### get_arr_of_size__log_bytes_without_padding + +```rust +get_arr_of_size__log_bytes_without_padding(_ct, ); +``` + +Let lbwop = 1 + HEADER_CIPHERTEXT_SIZE_IN_BYTES + |ct| // aka log bytes without padding + +#### Parameters +| Name | Type | +| --- | --- | +| _ct | [u8; CT] | +| | | + +### get_arr_of_size__log_bytes_padding + +```rust +get_arr_of_size__log_bytes_padding(_lbwop, ); +``` + +(because ceil(x / y) = (x + y - 1) // y ). + +#### Parameters +| Name | Type | +| --- | --- | +| _lbwop | [u8; LBWOP] | +| | | + +### get_arr_of_size__log_bytes + +```rust +get_arr_of_size__log_bytes(_lbwop, _p, ); +``` + +p is the padding + +#### Parameters +| Name | Type | +| --- | --- | +| _lbwop | [u8; LBWOP] | +| _p | [u8; P] | +| | | + +### get_arr_of_size__log_bytes_padding__from_PT + +```rust +get_arr_of_size__log_bytes_padding__from_PT(); +``` + +Takes no parameters. + +### get_arr_of_size__log_bytes__from_PT + +```rust +get_arr_of_size__log_bytes__from_PT(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/logs/event.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/logs/event.md new file mode 100644 index 000000000000..5ee9bdae5ce4 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/logs/event.md @@ -0,0 +1,17 @@ +## Standalone Functions + +### to_encrypted_private_event_message + +```rust +to_encrypted_private_event_message(event, recipient, ); +``` + +/ of the event and then encrypting them for `recipient`. + +#### Parameters +| Name | Type | +| --- | --- | +| event | Event | +| recipient | AztecAddress | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/logs/note.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/logs/note.md new file mode 100644 index 000000000000..1149cdac8b21 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/logs/note.md @@ -0,0 +1,113 @@ +## Standalone Functions + +### assert_note_exists + +```rust +assert_note_exists(context, note_hash_counter); +``` + +TODO: it feels like this existence check is in the wrong place. In fact, why is it needed at all? Under what circumstances have we found a non-existent note being emitted accidentally? + +#### Parameters +| Name | Type | +| --- | --- | +| context | PrivateContext | +| note_hash_counter | u32 | + +### compute_note_log + +```rust +compute_note_log(note, storage_slot, recipient, sender, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| note | Note | +| storage_slot | Field | +| recipient | AztecAddress | +| sender | AztecAddress | +| | | + +### compute_partial_note_log + +```rust +compute_partial_note_log(note, storage_slot, recipient, sender, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| note | Note | +| storage_slot | Field | +| recipient | AztecAddress | +| sender | AztecAddress | +| | | + +### compute_log + +```rust +compute_log(note, storage_slot, recipient, sender, msg_type, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| note | Note | +| storage_slot | Field | +| recipient | AztecAddress | +| sender | AztecAddress | +| msg_type | u64 | +| | | + +### encode_and_encrypt_note + +```rust +encode_and_encrypt_note(context, recipient, // We need this because to compute a tagging secret, we require a sender, ); +``` + +/ private logs. + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | +| recipient | AztecAddress | +| // We need this because to compute a tagging secret | | +| we require a sender | sender | +| | | + +### encode_and_encrypt_note_unconstrained + +```rust +encode_and_encrypt_note_unconstrained(context, recipient, // We need this because to compute a tagging secret, we require a sender, ); +``` + +/// Only use this function in scenarios where the recipient not receiving the note is an acceptable outcome. + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | +| recipient | AztecAddress | +| // We need this because to compute a tagging secret | | +| we require a sender | sender | +| | | + +### encode_and_encrypt_note_and_emit_as_offchain_message + +```rust +encode_and_encrypt_note_and_emit_as_offchain_message(context, recipient, // We need this because to compute a tagging secret, we require a sender, ); +``` + +/ `messages::offchain_message::emit_offchain_message` for more details on delivery guarantees. + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | +| recipient | AztecAddress | +| // We need this because to compute a tagging secret | | +| we require a sender | sender | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/logs/utils.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/logs/utils.md new file mode 100644 index 000000000000..63c8c35f7726 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/logs/utils.md @@ -0,0 +1,24 @@ +## Standalone Functions + +### prefix_with_tag + +```rust +prefix_with_tag(log_without_tag, sender, recipient, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| log_without_tag | [Field; L] | +| sender | AztecAddress | +| recipient | AztecAddress | +| | | + +### prefixing_with_tag + +```rust +prefixing_with_tag(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/offchain_messages.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/offchain_messages.md new file mode 100644 index 000000000000..a331f754b40c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/offchain_messages.md @@ -0,0 +1,16 @@ +## Standalone Functions + +### emit_offchain_message + +```rust +emit_offchain_message(message, recipient); +``` + +/ * `recipient` - The address of the recipient. + +#### Parameters +| Name | Type | +| --- | --- | +| message | T | +| recipient | AztecAddress | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/event_validation_request.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/event_validation_request.md new file mode 100644 index 000000000000..b7925c4a8cd9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/event_validation_request.md @@ -0,0 +1,22 @@ +# EventValidationRequest + +## Fields +| Field | Type | +| --- | --- | +| pub contract_address | AztecAddress | +| pub event_type_id | EventSelector | +| pub serialized_event | BoundedVec<Field, MAX_EVENT_SERIALIZED_LEN> | +| pub event_commitment | Field | +| pub tx_hash | Field | +| pub recipient | AztecAddress | + +## Standalone Functions + +### serialization_matches_typescript + +```rust +serialization_matches_typescript(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/log_retrieval_request.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/log_retrieval_request.md new file mode 100644 index 000000000000..e94e6abee05e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/log_retrieval_request.md @@ -0,0 +1,20 @@ +# LogRetrievalRequest + +/ A request for the `bulk_retrieve_logs` oracle to fetch either: / - a public log emitted by `contract_address` with `unsiloed_tag` / - a private log with tag equal to `silo_private_log(unsiloed_tag, contract_address)`. + +## Fields +| Field | Type | +| --- | --- | +| pub contract_address | AztecAddress | +| pub unsiloed_tag | Field | + +## Standalone Functions + +### serialization_matches_typescript + +```rust +serialization_matches_typescript(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/log_retrieval_response.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/log_retrieval_response.md new file mode 100644 index 000000000000..671b2fd203aa --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/log_retrieval_response.md @@ -0,0 +1,20 @@ +# LogRetrievalResponse + +## Fields +| Field | Type | +| --- | --- | +| pub log_payload | BoundedVec<Field, MAX_LOG_CONTENT_LEN> | +| pub tx_hash | Field | +| pub unique_note_hashes_in_tx | BoundedVec<Field, MAX_NOTE_HASHES_PER_TX> | +| pub first_nullifier_in_tx | Field | + +## Standalone Functions + +### deserialization_matches_typescript + +```rust +deserialization_matches_typescript(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/message_context.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/message_context.md new file mode 100644 index 000000000000..b06c730ec15e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/message_context.md @@ -0,0 +1,20 @@ +# MessageContext + +## Fields +| Field | Type | +| --- | --- | +| pub tx_hash | Field | +| pub unique_note_hashes_in_tx | BoundedVec<Field, MAX_NOTE_HASHES_PER_TX> | +| pub first_nullifier_in_tx | Field | +| pub recipient | AztecAddress | + +## Standalone Functions + +### message_context_serialization_matches_typescript + +```rust +message_context_serialization_matches_typescript(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/mod.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/mod.md new file mode 100644 index 000000000000..19c7145859a4 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/mod.md @@ -0,0 +1,76 @@ +## Standalone Functions + +### get_private_logs + +```rust +get_private_logs(contract_address, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| | | + +### enqueue_note_for_validation + +```rust +enqueue_note_for_validation(contract_address, storage_slot, note_nonce, packed_note, MAX_NOTE_PACKED_LEN>, note_hash, nullifier, tx_hash, recipient, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| storage_slot | Field | +| note_nonce | Field | +| packed_note | BoundedVec<Field | +| MAX_NOTE_PACKED_LEN> | | +| note_hash | Field | +| nullifier | Field | +| tx_hash | Field | +| recipient | AztecAddress | +| | | + +### enqueue_event_for_validation + +```rust +enqueue_event_for_validation(contract_address, event_type_id, serialized_event, MAX_EVENT_SERIALIZED_LEN>, event_commitment, tx_hash, recipient, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| event_type_id | EventSelector | +| serialized_event | BoundedVec<Field | +| MAX_EVENT_SERIALIZED_LEN> | | +| event_commitment | Field | +| tx_hash | Field | +| recipient | AztecAddress | +| | | + +### validate_enqueued_notes_and_events + +```rust +validate_enqueued_notes_and_events(contract_address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | + +### get_pending_partial_notes_completion_logs + +```rust +get_pending_partial_notes_completion_logs(contract_address, pending_partial_notes, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| pending_partial_notes | CapsuleArray<DeliveredPendingPartialNote> | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/note_validation_request.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/note_validation_request.md new file mode 100644 index 000000000000..82b24c7d7434 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/note_validation_request.md @@ -0,0 +1,24 @@ +# NoteValidationRequest + +## Fields +| Field | Type | +| --- | --- | +| pub contract_address | AztecAddress | +| pub storage_slot | Field | +| pub note_nonce | Field | +| pub packed_note | BoundedVec<Field, MAX_NOTE_PACKED_LEN> | +| pub note_hash | Field | +| pub nullifier | Field | +| pub tx_hash | Field | +| pub recipient | AztecAddress | + +## Standalone Functions + +### serialization_matches_typescript + +```rust +serialization_matches_typescript(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/pending_tagged_log.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/pending_tagged_log.md new file mode 100644 index 000000000000..da589f8faca4 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messages/processing/pending_tagged_log.md @@ -0,0 +1,18 @@ +# PendingTaggedLog + +## Fields +| Field | Type | +| --- | --- | +| pub log | BoundedVec<Field, PRIVATE_LOG_SIZE_IN_FIELDS> | +| pub context | MessageContext | + +## Standalone Functions + +### pending_tagged_log_serialization_matches_typescript + +```rust +pending_tagged_log_serialization_matches_typescript(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messaging.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messaging.md new file mode 100644 index 000000000000..0e616e73807b --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/messaging.md @@ -0,0 +1,21 @@ +## Standalone Functions + +### process_l1_to_l2_message + +```rust +process_l1_to_l2_message(l1_to_l2_root, contract_address, portal_contract_address, chain_id, version, content, secret, leaf_index, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| l1_to_l2_root | Field | +| contract_address | AztecAddress | +| portal_contract_address | EthAddress | +| chain_id | Field | +| version | Field | +| content | Field | +| secret | Field | +| leaf_index | Field | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/lifecycle.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/lifecycle.md new file mode 100644 index 000000000000..33c1f7e97622 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/lifecycle.md @@ -0,0 +1,46 @@ +## Standalone Functions + +### create_note + +```rust +create_note(context, storage_slot, note, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | +| storage_slot | Field | +| note | Note | +| | | + +### destroy_note + +```rust +destroy_note(context, retrieved_note, storage_slot, ); +``` + +Note: This function is currently totally unused. + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | +| retrieved_note | RetrievedNote<Note> | +| storage_slot | Field | +| | | + +### destroy_note_unsafe + +```rust +destroy_note_unsafe(context, retrieved_note, note_hash_for_read_request, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | +| retrieved_note | RetrievedNote<Note> | +| note_hash_for_read_request | Field | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_emission.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_emission.md new file mode 100644 index 000000000000..8cd2242b4175 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_emission.md @@ -0,0 +1,72 @@ +## Standalone Functions + +### new + +```rust +new(note, storage_slot, note_hash_counter); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| note | Note | +| storage_slot | Field | +| note_hash_counter | u32 | + +### emit + +```rust +emit(self, _emit); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| _emit | fn[Env](Self | + +### discard + +```rust +discard(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### new + +```rust +new(emission); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| emission | Option<NoteEmission<Note>> | + +### emit + +```rust +emit(self, _emit); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| _emit | fn[Env](NoteEmission<Note> | + +### discard + +```rust +discard(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_field.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_field.md new file mode 100644 index 000000000000..742217a673e3 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_field.md @@ -0,0 +1,10 @@ +# NoteField + +Used by macros when generating note export. + +## Fields +| Field | Type | +| --- | --- | +| pub index | u32 | +| pub nullable | bool | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_getter.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_getter.md new file mode 100644 index 000000000000..d20a71e3d8cf --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_getter.md @@ -0,0 +1,162 @@ +## Standalone Functions + +### extract_property_value_from_selector + +```rust +extract_property_value_from_selector(packed_note, selector, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| packed_note | [Field; N] | +| selector | PropertySelector | +| | | + +### check_packed_note + +```rust +check_packed_note(packed_note, selects, N>); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| packed_note | [Field; N] | +| selects | BoundedVec<Option<Select> | +| N> | | + +### check_notes_order + +```rust +check_notes_order(fields_0, fields_1, sorts, N>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| fields_0 | [Field; N] | +| fields_1 | [Field; N] | +| sorts | BoundedVec<Option<Sort> | +| N> | | +| | | + +### get_note + +```rust +get_note(context, storage_slot, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | +| storage_slot | Field | +| | | + +### get_notes + +```rust +get_notes(context, storage_slot, options, N, PREPROCESSOR_ARGS, FILTER_ARGS>, ); +``` + +/ abstractions such as aztec-nr's state variables should be used instead. + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | +| storage_slot | Field | +| options | NoteGetterOptions<Note | +| N | | +| PREPROCESSOR_ARGS | | +| FILTER_ARGS> | | +| | | + +### apply_preprocessor + +```rust +apply_preprocessor(notes, preprocessor, PREPROCESSOR_ARGS); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| notes | [Option<Note>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] | +| preprocessor | fn([Option<Note>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] | +| PREPROCESSOR_ARGS | | + +### constrain_get_notes_internal + +```rust +constrain_get_notes_internal(context, storage_slot, opt_notes, options, N, PREPROCESSOR_ARGS, FILTER_ARGS>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | &mut PrivateContext | +| storage_slot | Field | +| opt_notes | [Option<RetrievedNote<Note>>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] | +| options | NoteGetterOptions<Note | +| N | | +| PREPROCESSOR_ARGS | | +| FILTER_ARGS> | | +| | | + +### get_note_internal + +```rust +get_note_internal(storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| storage_slot | Field | + +### get_notes_internal + +```rust +get_notes_internal(storage_slot, options, N, PREPROCESSOR_ARGS, FILTER_ARGS>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| storage_slot | Field | +| options | NoteGetterOptions<Note | +| N | | +| PREPROCESSOR_ARGS | | +| FILTER_ARGS> | | +| | | + +### view_notes + +```rust +view_notes(storage_slot, options, N>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| storage_slot | Field | +| options | NoteViewerOptions<Note | +| N> | | +| | | + +### flatten_options + +```rust +flatten_options(selects, N>, sorts, N>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| selects | BoundedVec<Option<Select> | +| N> | | +| sorts | BoundedVec<Option<Sort> | +| N> | | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_getter/test.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_getter/test.md new file mode 100644 index 000000000000..71318a2a7526 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_getter/test.md @@ -0,0 +1,115 @@ +## Standalone Functions + +### setup + +```rust +setup(); +``` + +Takes no parameters. + +### build_valid_retrieved_note + +```rust +build_valid_retrieved_note(value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| value | Field | + +### assert_equivalent_vec_and_array + +```rust +assert_equivalent_vec_and_array(vec, N>, arr, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| vec | BoundedVec<T | +| N> | | +| arr | [Option<T>; N] | +| | | + +### processes_single_note + +```rust +processes_single_note(); +``` + +Takes no parameters. + +### processes_many_notes + +```rust +processes_many_notes(); +``` + +Takes no parameters. + +### collapses_notes_at_the_beginning_of_the_array + +```rust +collapses_notes_at_the_beginning_of_the_array(); +``` + +Takes no parameters. + +### can_return_zero_notes + +```rust +can_return_zero_notes(); +``` + +Takes no parameters. + +### rejects_mote_notes_than_limit + +```rust +rejects_mote_notes_than_limit(); +``` + +Takes no parameters. + +### applies_filter_before_constraining + +```rust +applies_filter_before_constraining(); +``` + +Takes no parameters. + +### rejects_mismatched_address + +```rust +rejects_mismatched_address(); +``` + +Takes no parameters. + +### rejects_mismatched_selector + +```rust +rejects_mismatched_selector(); +``` + +Takes no parameters. + +### rejects_mismatched_desc_sort_order + +```rust +rejects_mismatched_desc_sort_order(); +``` + +Takes no parameters. + +### rejects_mismatched_asc_sort_order + +```rust +rejects_mismatched_asc_sort_order(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_getter_options.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_getter_options.md new file mode 100644 index 000000000000..312d395739fb --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_getter_options.md @@ -0,0 +1,178 @@ +# PropertySelector + +## Fields +| Field | Type | +| --- | --- | +| pub index | u8, // index of the field in the serialized note array | +| pub offset | u8, // offset in the byte representation of the field (selected with index above) from which to reading | +| pub length | u8, // number of bytes to read after the offset | + +# Select + +## Fields +| Field | Type | +| --- | --- | +| pub(crate) property_selector | PropertySelector | +| pub(crate) comparator | u8 | +| pub(crate) value | Field | + +## Methods + +### new + +The selected property will be the left hand side and value the right hand side of the operation, so e.g. the object created by new(property, Comparator.GT, value) represents 'property > value'. + +```rust +Select::new(property_selector, comparator, value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| property_selector | PropertySelector | +| comparator | u8 | +| value | Field | + +# SortOrderEnum + +## Fields +| Field | Type | +| --- | --- | +| pub DESC | u8 | +| pub ASC | u8 | + +# Sort + +## Fields +| Field | Type | +| --- | --- | +| pub(crate) property_selector | PropertySelector | +| pub(crate) order | u8 | + +## Methods + +### new + +```rust +Sort::new(property_selector, order); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| property_selector | PropertySelector | +| order | u8 | + +# NoteStatusEnum + +## Fields +| Field | Type | +| --- | --- | +| pub ACTIVE | u8 | +| pub ACTIVE_OR_NULLIFIED | u8 | + +## Standalone Functions + +### return_all_notes + +```rust +return_all_notes(notes, _p, ); +``` + +This is the default filter and preprocessor, which does nothing + +#### Parameters +| Name | Type | +| --- | --- | +| notes | [Option<RetrievedNote<Note>>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] | +| _p | Field | +| | | + +### select + +```rust +select(&mut self, property_selector, comparator, value, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| property_selector | PropertySelector | +| comparator | u8 | +| value | T | +| | | + +### sort + +```rust +sort(&mut self, property_selector, order); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| property_selector | PropertySelector | +| order | u8 | + +### set_limit + +```rust +set_limit(&mut self, limit); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| limit | u32 | + +### set_offset + +```rust +set_offset(&mut self, offset); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| offset | u32 | + +### set_status + +```rust +set_status(&mut self, status); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| status | u8 | + +### with_preprocessor + +```rust +with_preprocessor(preprocessor, PREPROCESSOR_ARGS); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| preprocessor | fn([Option<RetrievedNote<Note>>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] | +| PREPROCESSOR_ARGS | | + +### with_filter + +```rust +with_filter(filter, FILTER_ARGS); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| filter | fn([Option<RetrievedNote<Note>>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] | +| FILTER_ARGS | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_interface.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_interface.md new file mode 100644 index 000000000000..e504c595eb76 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_interface.md @@ -0,0 +1,71 @@ +## Standalone Functions + +### get_id + +```rust +get_id(); +``` + +Takes no parameters. + +### compute_note_hash + +```rust +compute_note_hash(self, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| storage_slot | Field | + +### compute_nullifier + +```rust +compute_nullifier(self, context, note_hash_for_nullify); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | +| note_hash_for_nullify | Field | + +### compute_nullifier_unconstrained + +```rust +compute_nullifier_unconstrained(self, note_hash_for_nullify); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| note_hash_for_nullify | Field | + +### properties + +```rust +properties(); +``` + +Takes no parameters. + +### setup_payload + +```rust +setup_payload(); +``` + +Takes no parameters. + +### finalization_payload + +```rust +finalization_payload(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_metadata.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_metadata.md new file mode 100644 index 000000000000..f0e8ab6acaea --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_metadata.md @@ -0,0 +1,226 @@ +# NoteStageEnum + +## Fields +| Field | Type | +| --- | --- | +| PENDING_SAME_PHASE | u8 | +| PENDING_PREVIOUS_PHASE | u8 | +| SETTLED | u8 | + +# NoteMetadata + +## Fields +| Field | Type | +| --- | --- | +| stage | u8 | +| maybe_note_nonce | Field | + +## Methods + +### from_raw_data + +/ Constructs a `NoteMetadata` object from optional note hash counter and nonce. Both a zero note hash counter and / a zero nonce are invalid, so those are used to signal non-existent values. + +```rust +NoteMetadata::from_raw_data(nonzero_note_hash_counter, maybe_note_nonce); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| nonzero_note_hash_counter | bool | +| maybe_note_nonce | Field | + +### is_pending_same_phase + +/ Returns true if the note is pending **and** from the same phase, i.e. if it's been created in the current / transaction during the current execution phase (either non-revertible or revertible). + +```rust +NoteMetadata::is_pending_same_phase(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### is_pending_previous_phase + +/ Returns true if the note is pending **and** from the previous phase, i.e. if it's been created in the current / transaction during an execution phase prior to the current one. Because private execution only has two phases / with strict ordering, this implies that the note was created in the non-revertible phase, and that the current / phase is the revertible phase. + +```rust +NoteMetadata::is_pending_previous_phase(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### is_settled + +/ Returns true if the note is settled, i.e. if it's been created in a prior transaction and is therefore already / in the note hash tree. + +```rust +NoteMetadata::is_settled(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### to_pending_same_phase + +/ Asserts that the metadata is that of a pending note from the same phase and converts it accordingly. + +```rust +NoteMetadata::to_pending_same_phase(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### to_pending_previous_phase + +/ Asserts that the metadata is that of a pending note from a previous phase and converts it accordingly. + +```rust +NoteMetadata::to_pending_previous_phase(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### to_settled + +/ Asserts that the metadata is that of a settled note and converts it accordingly. + +```rust +NoteMetadata::to_settled(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +# PendingSamePhaseNoteMetadata + +/ The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel / read requests, as well as the correct nullifier to avoid double-spends. / / This represents a pending same phase note, i.e. a note that was created in the transaction that is currently being / executed during the current execution phase (either non-revertible or revertible). + +## Methods + +### new + +```rust +PendingSamePhaseNoteMetadata::new(); +``` + +Takes no parameters. + +# PendingPreviousPhaseNoteMetadata + +/ The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel / read requests, as well as the correct nullifier to avoid double-spends. / / This represents a pending previous phase note, i.e. a note that was created in the transaction that is currently / being executed, during the previous execution phase. Because there are only two phases and their order is always the / same (first non-revertible and then revertible) this implies that the note was created in the non-revertible phase, / and that the current phase is the revertible phase. + +## Fields +| Field | Type | +| --- | --- | +| note_nonce | Field | + +## Methods + +### new + +```rust +PendingPreviousPhaseNoteMetadata::new(note_nonce); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| note_nonce | Field | + +### note_nonce + +```rust +PendingPreviousPhaseNoteMetadata::note_nonce(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +# SettledNoteMetadata + +/ The metadata required to both prove a note's existence and destroy it, by computing the correct note hash for kernel / read requests, as well as the correct nullifier to avoid double-spends. / / This represents a settled note, i.e. a note that was created in a prior transaction and is therefore already in the / note hash tree. + +## Fields +| Field | Type | +| --- | --- | +| note_nonce | Field | + +## Methods + +### new + +```rust +SettledNoteMetadata::new(note_nonce); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| note_nonce | Field | + +### note_nonce + +```rust +SettledNoteMetadata::note_nonce(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +## Standalone Functions + +### from + +```rust +from(_value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _value | PendingSamePhaseNoteMetadata | + +### from + +```rust +from(value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| value | PendingPreviousPhaseNoteMetadata | + +### from + +```rust +from(value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| value | SettledNoteMetadata | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_viewer_options.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_viewer_options.md new file mode 100644 index 000000000000..ca361ad5b74c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/note_viewer_options.md @@ -0,0 +1,74 @@ +## Standalone Functions + +### new + +```rust +new(); +``` + +Takes no parameters. + +### select + +```rust +select(&mut self, property_selector, comparator, value, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| property_selector | PropertySelector | +| comparator | u8 | +| value | T | +| | | + +### sort + +```rust +sort(&mut self, property_selector, order); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| property_selector | PropertySelector | +| order | u8 | + +### set_limit + +```rust +set_limit(&mut self, limit); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| limit | u32 | + +### set_offset + +```rust +set_offset(&mut self, offset); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| offset | u32 | + +### set_status + +```rust +set_status(&mut self, status); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| status | u8 | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/retrieved_note.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/retrieved_note.md new file mode 100644 index 000000000000..2ed49e5cac27 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/retrieved_note.md @@ -0,0 +1,27 @@ +## Standalone Functions + +### serialize + +```rust +serialize(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### unpack_retrieved_note + +```rust +unpack_retrieved_note(packed_retrieved_note, ); +``` + +functionality resides in TS (oracle.ts and txe_service.ts). + +#### Parameters +| Name | Type | +| --- | --- | +| packed_retrieved_note | [Field; N + RETRIEVED_NOTE_OVERHEAD] | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/utils.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/utils.md new file mode 100644 index 000000000000..845eb9ed08d8 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/note/utils.md @@ -0,0 +1,63 @@ +## Standalone Functions + +### compute_note_hash_for_read_request + +```rust +compute_note_hash_for_read_request(retrieved_note, storage_slot, ); +``` + +/ Returns the note hash that must be used to issue a private kernel read request for a note. + +#### Parameters +| Name | Type | +| --- | --- | +| retrieved_note | RetrievedNote<Note> | +| storage_slot | Field | +| | | + +### compute_note_hash_for_nullify + +```rust +compute_note_hash_for_nullify(retrieved_note, storage_slot, ); +``` + +/ `NoteHash::compute_nullifier_unconstrained`. + +#### Parameters +| Name | Type | +| --- | --- | +| retrieved_note | RetrievedNote<Note> | +| storage_slot | Field | +| | | + +### compute_note_hash_for_nullify_from_read_request + +```rust +compute_note_hash_for_nullify_from_read_request(retrieved_note, note_hash_for_read_request, ); +``` + +/ computed to reduce constraints by reusing this value. + +#### Parameters +| Name | Type | +| --- | --- | +| retrieved_note | RetrievedNote<Note> | +| note_hash_for_read_request | Field | +| | | + +### compute_siloed_note_nullifier + +```rust +compute_siloed_note_nullifier(retrieved_note, storage_slot, context, ); +``` + +/ Computes a note's siloed nullifier, i.e. the one that will be inserted into the nullifier tree. + +#### Parameters +| Name | Type | +| --- | --- | +| retrieved_note | RetrievedNote<Note> | +| storage_slot | Field | +| context | &mut PrivateContext | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/aes128_decrypt.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/aes128_decrypt.md new file mode 100644 index 000000000000..f10540e4696a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/aes128_decrypt.md @@ -0,0 +1,33 @@ +## Standalone Functions + +### aes128_decrypt_oracle + +```rust +aes128_decrypt_oracle(ciphertext, N>, iv, sym_key, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| ciphertext | BoundedVec<u8 | +| N> | | +| iv | [u8; 16] | +| sym_key | [u8; 16] | +| | | + +### aes_encrypt_then_decrypt + +```rust +aes_encrypt_then_decrypt(); +``` + +Takes no parameters. + +### aes_encrypt_then_decrypt_with_bad_sym_key_is_caught + +```rust +aes_encrypt_then_decrypt_with_bad_sym_key_is_caught(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/block_header.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/block_header.md new file mode 100644 index 000000000000..1998a8e9c487 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/block_header.md @@ -0,0 +1,59 @@ +## Standalone Functions + +### get_block_header_at_oracle + +```rust +get_block_header_at_oracle(_block_number); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _block_number | u32 | + +### get_block_header_at_internal + +```rust +get_block_header_at_internal(block_number); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| block_number | u32 | + +### get_block_header_at + +```rust +get_block_header_at(block_number, context); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| block_number | u32 | +| context | PrivateContext | + +### constrain_get_block_header_at_internal + +```rust +constrain_get_block_header_at_internal(header_hint, block_number, last_archive_block_number, last_archive_root, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| header_hint | BlockHeader | +| block_number | u32 | +| last_archive_block_number | u32 | +| last_archive_root | Field | +| | | + +### fetching_a_valid_but_different_header_should_fail + +```rust +fetching_a_valid_but_different_header_should_fail(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/call_private_function.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/call_private_function.md new file mode 100644 index 000000000000..c6b7fff67fbe --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/call_private_function.md @@ -0,0 +1,34 @@ +## Standalone Functions + +### call_private_function_oracle + +```rust +call_private_function_oracle(_contract_address, _function_selector, _args_hash, _start_side_effect_counter, _is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _contract_address | AztecAddress | +| _function_selector | FunctionSelector | +| _args_hash | Field | +| _start_side_effect_counter | u32 | +| _is_static_call | bool | +| | | + +### call_private_function_internal + +```rust +call_private_function_internal(contract_address, function_selector, args_hash, start_side_effect_counter, is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| args_hash | Field | +| start_side_effect_counter | u32 | +| is_static_call | bool | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/capsules.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/capsules.md new file mode 100644 index 000000000000..e786b2c4ea10 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/capsules.md @@ -0,0 +1,221 @@ +## Standalone Functions + +### store + +```rust +store(contract_address, slot, value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| slot | Field | +| value | T | + +### load + +```rust +load(contract_address, slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| slot | Field | + +### delete + +```rust +delete(contract_address, slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| slot | Field | + +### copy + +```rust +copy(contract_address, src_slot, dst_slot, num_entries, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| src_slot | Field | +| dst_slot | Field | +| num_entries | u32 | +| | | + +### store_oracle + +```rust +store_oracle(contract_address, slot, values, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| slot | Field | +| values | [Field; N] | +| | | + +### load_oracle + +```rust +load_oracle(contract_address, slot, array_len, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| slot | Field | +| array_len | u32 | +| | | + +### delete_oracle + +```rust +delete_oracle(contract_address, slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| slot | Field | + +### copy_oracle + +```rust +copy_oracle(contract_address, src_slot, dst_slot, num_entries, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| src_slot | Field | +| dst_slot | Field | +| num_entries | u32 | +| | | + +### setup + +```rust +setup(); +``` + +Takes no parameters. + +### stores_and_loads + +```rust +stores_and_loads(); +``` + +Takes no parameters. + +### store_overwrites + +```rust +store_overwrites(); +``` + +Takes no parameters. + +### loads_empty_slot + +```rust +loads_empty_slot(); +``` + +Takes no parameters. + +### deletes_stored_value + +```rust +deletes_stored_value(); +``` + +Takes no parameters. + +### deletes_empty_slot + +```rust +deletes_empty_slot(); +``` + +Takes no parameters. + +### copies_non_overlapping_values + +```rust +copies_non_overlapping_values(); +``` + +Takes no parameters. + +### copies_overlapping_values_with_src_ahead + +```rust +copies_overlapping_values_with_src_ahead(); +``` + +Takes no parameters. + +### copies_overlapping_values_with_dst_ahead + +```rust +copies_overlapping_values_with_dst_ahead(); +``` + +Takes no parameters. + +### cannot_copy_empty_values + +```rust +cannot_copy_empty_values(); +``` + +Takes no parameters. + +### cannot_store_other_contract + +```rust +cannot_store_other_contract(); +``` + +Takes no parameters. + +### cannot_load_other_contract + +```rust +cannot_load_other_contract(); +``` + +Takes no parameters. + +### cannot_delete_other_contract + +```rust +cannot_delete_other_contract(); +``` + +Takes no parameters. + +### cannot_copy_other_contract + +```rust +cannot_copy_other_contract(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/enqueue_public_function_call.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/enqueue_public_function_call.md new file mode 100644 index 000000000000..422c1ec7bb92 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/enqueue_public_function_call.md @@ -0,0 +1,125 @@ +## Standalone Functions + +### notify_enqueued_public_function_call_oracle + +```rust +notify_enqueued_public_function_call_oracle(_contract_address, _calldata_hash, _side_effect_counter, _is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _contract_address | AztecAddress | +| _calldata_hash | Field | +| _side_effect_counter | u32 | +| _is_static_call | bool | +| | | + +### notify_enqueued_public_function_call_wrapper + +```rust +notify_enqueued_public_function_call_wrapper(contract_address, calldata_hash, side_effect_counter, is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| calldata_hash | Field | +| side_effect_counter | u32 | +| is_static_call | bool | +| | | + +### notify_enqueued_public_function_call + +```rust +notify_enqueued_public_function_call(contract_address, calldata_hash, side_effect_counter, is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| calldata_hash | Field | +| side_effect_counter | u32 | +| is_static_call | bool | +| | | + +### notify_set_public_teardown_function_call_oracle + +```rust +notify_set_public_teardown_function_call_oracle(_contract_address, _calldata_hash, _side_effect_counter, _is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _contract_address | AztecAddress | +| _calldata_hash | Field | +| _side_effect_counter | u32 | +| _is_static_call | bool | +| | | + +### notify_set_public_teardown_function_call_wrapper + +```rust +notify_set_public_teardown_function_call_wrapper(contract_address, calldata_hash, side_effect_counter, is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| calldata_hash | Field | +| side_effect_counter | u32 | +| is_static_call | bool | +| | | + +### notify_set_public_teardown_function_call + +```rust +notify_set_public_teardown_function_call(contract_address, calldata_hash, side_effect_counter, is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| calldata_hash | Field | +| side_effect_counter | u32 | +| is_static_call | bool | +| | | + +### notify_set_min_revertible_side_effect_counter + +```rust +notify_set_min_revertible_side_effect_counter(counter); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| counter | u32 | + +### notify_set_min_revertible_side_effect_counter_oracle_wrapper + +```rust +notify_set_min_revertible_side_effect_counter_oracle_wrapper(counter); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| counter | u32 | + +### notify_set_min_revertible_side_effect_counter_oracle + +```rust +notify_set_min_revertible_side_effect_counter_oracle(_counter); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _counter | u32 | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/execution.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/execution.md new file mode 100644 index 000000000000..7093cb24bfa0 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/execution.md @@ -0,0 +1,82 @@ +## Standalone Functions + +### get_contract_address_oracle + +```rust +get_contract_address_oracle(); +``` + +Takes no parameters. + +### get_block_number_oracle + +```rust +get_block_number_oracle(); +``` + +Takes no parameters. + +### get_timestamp_oracle + +```rust +get_timestamp_oracle(); +``` + +Takes no parameters. + +### get_chain_id_oracle + +```rust +get_chain_id_oracle(); +``` + +Takes no parameters. + +### get_version_oracle + +```rust +get_version_oracle(); +``` + +Takes no parameters. + +### get_contract_address + +```rust +get_contract_address(); +``` + +Takes no parameters. + +### get_block_number + +```rust +get_block_number(); +``` + +Takes no parameters. + +### get_timestamp + +```rust +get_timestamp(); +``` + +Takes no parameters. + +### get_chain_id + +```rust +get_chain_id(); +``` + +Takes no parameters. + +### get_version + +```rust +get_version(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/execution_cache.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/execution_cache.md new file mode 100644 index 000000000000..83aed7fd746d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/execution_cache.md @@ -0,0 +1,62 @@ +## Standalone Functions + +### store + +```rust +store(values, hash); +``` + +/ Stores values represented as slice in execution cache to be later obtained by its hash. + +#### Parameters +| Name | Type | +| --- | --- | +| values | [Field] | +| hash | Field | + +### store_in_execution_cache_oracle_wrapper + +```rust +store_in_execution_cache_oracle_wrapper(values, hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| values | [Field] | +| hash | Field | + +### load + +```rust +load(hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| hash | Field | + +### store_in_execution_cache_oracle + +```rust +store_in_execution_cache_oracle(_values, _hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _values | [Field] | +| _hash | Field | + +### load_from_execution_cache_oracle + +```rust +load_from_execution_cache_oracle(_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _hash | Field | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_contract_instance.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_contract_instance.md new file mode 100644 index 000000000000..56bd30cff916 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_contract_instance.md @@ -0,0 +1,150 @@ +# GetContractInstanceResult + +## Fields +| Field | Type | +| --- | --- | +| exists | bool | +| member | Field | + +## Standalone Functions + +### get_contract_instance_oracle + +```rust +get_contract_instance_oracle(_address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _address | AztecAddress | + +### get_contract_instance_internal + +```rust +get_contract_instance_internal(address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | + +### get_contract_instance + +```rust +get_contract_instance(address); +``` + +NOTE: this is for use in private only + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | + +### get_contract_instance_deployer_oracle_avm + +```rust +get_contract_instance_deployer_oracle_avm(_address, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _address | AztecAddress | +| | | + +### get_contract_instance_class_id_oracle_avm + +```rust +get_contract_instance_class_id_oracle_avm(_address, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _address | AztecAddress | +| | | + +### get_contract_instance_initialization_hash_oracle_avm + +```rust +get_contract_instance_initialization_hash_oracle_avm(_address, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _address | AztecAddress | +| | | + +### get_contract_instance_deployer_internal_avm + +```rust +get_contract_instance_deployer_internal_avm(address, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | +| | | + +### get_contract_instance_class_id_internal_avm + +```rust +get_contract_instance_class_id_internal_avm(address, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | +| | | + +### get_contract_instance_initialization_hash_internal_avm + +```rust +get_contract_instance_initialization_hash_internal_avm(address, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | +| | | + +### get_contract_instance_deployer_avm + +```rust +get_contract_instance_deployer_avm(address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | + +### get_contract_instance_class_id_avm + +```rust +get_contract_instance_class_id_avm(address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | + +### get_contract_instance_initialization_hash_avm + +```rust +get_contract_instance_initialization_hash_avm(address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_l1_to_l2_membership_witness.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_l1_to_l2_membership_witness.md new file mode 100644 index 000000000000..b4fec4a6095c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_l1_to_l2_membership_witness.md @@ -0,0 +1,30 @@ +## Standalone Functions + +### get_l1_to_l2_membership_witness + +```rust +get_l1_to_l2_membership_witness(contract_address, message_hash, secret, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| message_hash | Field | +| secret | Field | +| | | + +### get_l1_to_l2_membership_witness_oracle + +```rust +get_l1_to_l2_membership_witness_oracle(_contract_address, _message_hash, _secret, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _contract_address | AztecAddress | +| _message_hash | Field | +| _secret | Field | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_membership_witness.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_membership_witness.md new file mode 100644 index 000000000000..a27411385a60 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_membership_witness.md @@ -0,0 +1,42 @@ +## Standalone Functions + +### get_membership_witness + +```rust +get_membership_witness(_block_number, _tree_id, _leaf_value, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _block_number | u32 | +| _tree_id | Field | +| _leaf_value | Field | +| | | + +### get_note_hash_membership_witness + +```rust +get_note_hash_membership_witness(block_number, leaf_value, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| block_number | u32 | +| leaf_value | Field | +| | | + +### get_archive_membership_witness + +```rust +get_archive_membership_witness(block_number, leaf_value, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| block_number | u32 | +| leaf_value | Field | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_nullifier_membership_witness.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_nullifier_membership_witness.md new file mode 100644 index 000000000000..b75e60b13bf9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_nullifier_membership_witness.md @@ -0,0 +1,76 @@ +# NullifierMembershipWitness + +## Fields +| Field | Type | +| --- | --- | +| pub index | Field | +| pub leaf_preimage | NullifierLeafPreimage | +| pub path | Field; NULLIFIER_TREE_HEIGHT] | + +## Methods + +### deserialize + +```rust +NullifierMembershipWitness::deserialize(fields); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| fields | [Field; NULLIFIER_MEMBERSHIP_WITNESS] | + +## Standalone Functions + +### get_low_nullifier_membership_witness_oracle + +```rust +get_low_nullifier_membership_witness_oracle(_block_number, _nullifier, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _block_number | u32 | +| _nullifier | Field | +| | | + +### get_low_nullifier_membership_witness + +```rust +get_low_nullifier_membership_witness(block_number, nullifier, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| block_number | u32 | +| nullifier | Field | +| | | + +### get_nullifier_membership_witness_oracle + +```rust +get_nullifier_membership_witness_oracle(_block_number, _nullifier, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _block_number | u32 | +| _nullifier | Field | +| | | + +### get_nullifier_membership_witness + +```rust +get_nullifier_membership_witness(block_number, nullifier, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| block_number | u32 | +| nullifier | Field | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_public_data_witness.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_public_data_witness.md new file mode 100644 index 000000000000..87e6d269adc7 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/get_public_data_witness.md @@ -0,0 +1,37 @@ +# PublicDataWitness + +## Fields +| Field | Type | +| --- | --- | +| pub index | Field | +| pub leaf_preimage | PublicDataTreeLeafPreimage | +| pub path | Field; PUBLIC_DATA_TREE_HEIGHT] | + +## Standalone Functions + +### get_public_data_witness_oracle + +```rust +get_public_data_witness_oracle(_block_number, _public_data_tree_index, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _block_number | u32 | +| _public_data_tree_index | Field | +| | | + +### get_public_data_witness + +```rust +get_public_data_witness(block_number, public_data_tree_index, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| block_number | u32 | +| public_data_tree_index | Field | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/key_validation_request.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/key_validation_request.md new file mode 100644 index 000000000000..692ba8ccb0a9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/key_validation_request.md @@ -0,0 +1,28 @@ +## Standalone Functions + +### get_key_validation_request_oracle + +```rust +get_key_validation_request_oracle(_pk_m_hash, _key_index, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _pk_m_hash | Field | +| _key_index | Field | +| | | + +### get_key_validation_request + +```rust +get_key_validation_request(pk_m_hash, key_index, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| pk_m_hash | Field | +| key_index | Field | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/keys.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/keys.md new file mode 100644 index 000000000000..0425c51f9d5c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/keys.md @@ -0,0 +1,25 @@ +## Standalone Functions + +### get_public_keys_and_partial_address_oracle + +```rust +get_public_keys_and_partial_address_oracle(_address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _address | AztecAddress | + +### get_public_keys_and_partial_address + +```rust +get_public_keys_and_partial_address(address, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/logs.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/logs.md new file mode 100644 index 000000000000..536b877ed25b --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/logs.md @@ -0,0 +1,32 @@ +## Standalone Functions + +### notify_created_contract_class_log + +```rust +notify_created_contract_class_log(contract_address, message, length, counter, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| message | [Field; N] | +| length | u32 | +| counter | u32 | +| | | + +### notify_created_contract_class_log_private_oracle + +```rust +notify_created_contract_class_log_private_oracle(contract_address, message, length, counter, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| message | [Field; N] | +| length | u32 | +| counter | u32 | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/message_processing.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/message_processing.md new file mode 100644 index 000000000000..f0f35cc69258 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/message_processing.md @@ -0,0 +1,80 @@ +## Standalone Functions + +### fetch_tagged_logs + +```rust +fetch_tagged_logs(pending_tagged_log_array_base_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| pending_tagged_log_array_base_slot | Field | + +### fetch_tagged_logs_oracle + +```rust +fetch_tagged_logs_oracle(pending_tagged_log_array_base_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| pending_tagged_log_array_base_slot | Field | + +### validate_enqueued_notes_and_events + +```rust +validate_enqueued_notes_and_events(contract_address, note_validation_requests_array_base_slot, event_validation_requests_array_base_slot, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| note_validation_requests_array_base_slot | Field | +| event_validation_requests_array_base_slot | Field | +| | | + +### validate_enqueued_notes_and_events_oracle + +```rust +validate_enqueued_notes_and_events_oracle(contract_address, note_validation_requests_array_base_slot, event_validation_requests_array_base_slot, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| note_validation_requests_array_base_slot | Field | +| event_validation_requests_array_base_slot | Field | +| | | + +### bulk_retrieve_logs + +```rust +bulk_retrieve_logs(contract_address, log_retrieval_requests_array_base_slot, log_retrieval_responses_array_base_slot, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| log_retrieval_requests_array_base_slot | Field | +| log_retrieval_responses_array_base_slot | Field | +| | | + +### bulk_retrieve_logs_oracle + +```rust +bulk_retrieve_logs_oracle(contract_address, log_retrieval_requests_array_base_slot, log_retrieval_responses_array_base_slot, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| log_retrieval_requests_array_base_slot | Field | +| log_retrieval_responses_array_base_slot | Field | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/notes.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/notes.md new file mode 100644 index 000000000000..789b91da9ece --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/notes.md @@ -0,0 +1,274 @@ +## Standalone Functions + +### notify_created_note + +```rust +notify_created_note(storage_slot, note_type_id, packed_note, note_hash, counter, ); +``` + +/ transaction. This note should only be added to the non-volatile database if found in an actual block. + +#### Parameters +| Name | Type | +| --- | --- | +| storage_slot | Field | +| note_type_id | Field | +| packed_note | [Field; N] | +| note_hash | Field | +| counter | u32 | +| | | + +### notify_nullified_note + +```rust +notify_nullified_note(nullifier, note_hash, counter); +``` + +/ actual block. + +#### Parameters +| Name | Type | +| --- | --- | +| nullifier | Field | +| note_hash | Field | +| counter | u32 | + +### notify_created_nullifier + +```rust +notify_created_nullifier(nullifier); +``` + +/ Notifies the simulator that a non-note nullifier has been created, so that it can be used for note nonces. + +#### Parameters +| Name | Type | +| --- | --- | +| nullifier | Field | + +### notify_created_note_oracle_wrapper + +```rust +notify_created_note_oracle_wrapper(storage_slot, note_type_id, packed_note, note_hash, counter, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| storage_slot | Field | +| note_type_id | Field | +| packed_note | [Field; N] | +| note_hash | Field | +| counter | u32 | +| | | + +### notify_created_note_oracle + +```rust +notify_created_note_oracle(_storage_slot, _note_type_id, _packed_note, _note_hash, _counter, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _storage_slot | Field | +| _note_type_id | Field | +| _packed_note | [Field; N] | +| _note_hash | Field | +| _counter | u32 | +| | | + +### notify_nullified_note_oracle_wrapper + +```rust +notify_nullified_note_oracle_wrapper(nullifier, note_hash, counter, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| nullifier | Field | +| note_hash | Field | +| counter | u32 | +| | | + +### notify_nullified_note_oracle + +```rust +notify_nullified_note_oracle(_nullifier, _note_hash, _counter); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _nullifier | Field | +| _note_hash | Field | +| _counter | u32 | + +### notify_created_nullifier_oracle_wrapper + +```rust +notify_created_nullifier_oracle_wrapper(nullifier); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| nullifier | Field | + +### notify_created_nullifier_oracle + +```rust +notify_created_nullifier_oracle(_nullifier); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _nullifier | Field | + +### get_notes_oracle + +```rust +get_notes_oracle(_storage_slot, _num_selects, _select_by_indexes, _select_by_offsets, _select_by_lengths, _select_values, _select_comparators, _sort_by_indexes, _sort_by_offsets, _sort_by_lengths, _sort_order, _limit, _offset, _status, // This is always set to MAX_NOTES. We need to pass it to TS in order to correctly construct the BoundedVec + _max_notes, // This is always set to NOTE_PCKD_LEN + RETRIEVED_NOTE_OVERHEAD. We need to pass it to TS in order to be able to + // correctly construct the BoundedVec there. + _packed_retrieved_note_length, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _storage_slot | Field | +| _num_selects | u8 | +| _select_by_indexes | [u8; M] | +| _select_by_offsets | [u8; M] | +| _select_by_lengths | [u8; M] | +| _select_values | [Field; M] | +| _select_comparators | [u8; M] | +| _sort_by_indexes | [u8; M] | +| _sort_by_offsets | [u8; M] | +| _sort_by_lengths | [u8; M] | +| _sort_order | [u8; M] | +| _limit | u32 | +| _offset | u32 | +| _status | u8 | +| // This is always set to MAX_NOTES. We need to pass it to TS in order to correctly construct the BoundedVec + _max_notes | u32 | +| // This is always set to NOTE_PCKD_LEN + RETRIEVED_NOTE_OVERHEAD. We need to pass it to TS in order to be able to + // correctly construct the BoundedVec there. + _packed_retrieved_note_length | u32 | +| | | + +### get_notes + +```rust +get_notes(storage_slot, num_selects, select_by_indexes, select_by_offsets, select_by_lengths, select_values, select_comparators, sort_by_indexes, sort_by_offsets, sort_by_lengths, sort_order, limit, offset, status, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| storage_slot | Field | +| num_selects | u8 | +| select_by_indexes | [u8; M] | +| select_by_offsets | [u8; M] | +| select_by_lengths | [u8; M] | +| select_values | [Field; M] | +| select_comparators | [u8; M] | +| sort_by_indexes | [u8; M] | +| sort_by_offsets | [u8; M] | +| sort_by_lengths | [u8; M] | +| sort_order | [u8; M] | +| limit | u32 | +| offset | u32 | +| status | u8 | +| | | + +### check_nullifier_exists + +```rust +check_nullifier_exists(inner_nullifier); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| inner_nullifier | Field | + +### check_nullifier_exists_oracle + +```rust +check_nullifier_exists_oracle(_inner_nullifier); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _inner_nullifier | Field | + +### get_app_tag_as_sender + +```rust +get_app_tag_as_sender(sender, recipient); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| sender | AztecAddress | +| recipient | AztecAddress | + +### get_indexed_tagging_secret_as_sender_oracle + +```rust +get_indexed_tagging_secret_as_sender_oracle(_sender, _recipient, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _sender | AztecAddress | +| _recipient | AztecAddress | +| | | + +### increment_app_tagging_secret_index_as_sender + +```rust +increment_app_tagging_secret_index_as_sender(sender, recipient); +``` + +/ that are not found by the recipient. + +#### Parameters +| Name | Type | +| --- | --- | +| sender | AztecAddress | +| recipient | AztecAddress | + +### increment_app_tagging_secret_index_as_sender_wrapper + +```rust +increment_app_tagging_secret_index_as_sender_wrapper(sender, recipient, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| sender | AztecAddress | +| recipient | AztecAddress | +| | | + +### increment_app_tagging_secret_index_as_sender_oracle + +```rust +increment_app_tagging_secret_index_as_sender_oracle(_sender, _recipient, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _sender | AztecAddress | +| _recipient | AztecAddress | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/offchain_effect.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/offchain_effect.md new file mode 100644 index 000000000000..fee6af21b6be --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/offchain_effect.md @@ -0,0 +1,37 @@ +## Standalone Functions + +### emit_offchain_effect + +```rust +emit_offchain_effect(data); +``` + +/// * `data` - The data to emit. + +#### Parameters +| Name | Type | +| --- | --- | +| data | T | + +### emit_offchain_effect_oracle_wrapper + +```rust +emit_offchain_effect_oracle_wrapper(data); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| data | T | + +### emit_offchain_effect_oracle + +```rust +emit_offchain_effect_oracle(data); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| data | [Field; N] | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/random.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/random.md new file mode 100644 index 000000000000..93e89332780f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/random.md @@ -0,0 +1,18 @@ +## Standalone Functions + +### random + +```rust +random(); +``` + +Takes no parameters. + +### rand_oracle + +```rust +rand_oracle(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/shared_secret.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/shared_secret.md new file mode 100644 index 000000000000..a7c2273cf25d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/shared_secret.md @@ -0,0 +1,26 @@ +## Standalone Functions + +### get_shared_secret_oracle + +```rust +get_shared_secret_oracle(address, ephPk); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | +| ephPk | Point | + +### get_shared_secret + +```rust +get_shared_secret(address, ephPk); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | +| ephPk | Point | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/simulate_utility_function.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/simulate_utility_function.md new file mode 100644 index 000000000000..d02bfbe66604 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/simulate_utility_function.md @@ -0,0 +1,30 @@ +## Standalone Functions + +### simulate_utility_function + +```rust +simulate_utility_function(contract_address, function_selector, args_hash, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| args_hash | Field | +| | | + +### simulate_utility_function_oracle + +```rust +simulate_utility_function_oracle(_contract_address, _function_selector, _args_hash, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _contract_address | AztecAddress | +| _function_selector | FunctionSelector | +| _args_hash | Field | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/storage.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/storage.md new file mode 100644 index 000000000000..19bbde995b9c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/oracle/storage.md @@ -0,0 +1,61 @@ +## Standalone Functions + +### storage_read_oracle + +```rust +storage_read_oracle(address, storage_slot, block_number, length, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | Field | +| storage_slot | Field | +| block_number | u32 | +| length | u32 | +| | | + +### raw_storage_read + +```rust +raw_storage_read(address, storage_slot, block_number, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | +| storage_slot | Field | +| block_number | u32 | +| | | + +### storage_read + +```rust +storage_read(address, storage_slot, block_number, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | +| storage_slot | Field | +| block_number | u32 | +| | | + +### test_raw_storage_read + +```rust +test_raw_storage_read(); +``` + +Takes no parameters. + +### test_storage_read + +```rust +test_storage_read(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/map.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/map.md new file mode 100644 index 000000000000..1bea61829427 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/map.md @@ -0,0 +1,39 @@ +## Standalone Functions + +### get_storage_slot + +```rust +get_storage_slot(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(context, storage_slot, state_var_constructor, Field); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | Context | +| storage_slot | Field | +| state_var_constructor | fn(Context | +| Field | | + +### at + +```rust +at(self, key); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| key | K | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_immutable.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_immutable.md new file mode 100644 index 000000000000..0891934dae61 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_immutable.md @@ -0,0 +1,81 @@ +## Standalone Functions + +### get_storage_slot + +```rust +get_storage_slot(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(context, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | Context | +| storage_slot | Field | + +### compute_initialization_nullifier + +```rust +compute_initialization_nullifier(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### initialize + +```rust +initialize(self, note); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| note | Note | + +### get_note + +```rust +get_note(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### is_initialized + +```rust +is_initialized(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### view_note + +```rust +view_note(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_mutable.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_mutable.md new file mode 100644 index 000000000000..ceacadf858f7 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_mutable.md @@ -0,0 +1,105 @@ +## Standalone Functions + +### get_storage_slot + +```rust +get_storage_slot(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(context, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | Context | +| storage_slot | Field | + +### compute_initialization_nullifier + +```rust +compute_initialization_nullifier(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### initialize + +```rust +initialize(self, note); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| note | Note | + +### replace + +```rust +replace(self, new_note); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| new_note | Note | + +### initialize_or_replace + +```rust +initialize_or_replace(self, note); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| note | Note | + +### get_note + +```rust +get_note(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### is_initialized + +```rust +is_initialized(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### view_note + +```rust +view_note(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_mutable/test.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_mutable/test.md new file mode 100644 index 000000000000..ac54d649bf66 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_mutable/test.md @@ -0,0 +1,38 @@ +## Standalone Functions + +### setup + +```rust +setup(); +``` + +Takes no parameters. + +### in_private + +```rust +in_private(env, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| env | &mut TestEnvironment | +| | | + +### test_initialize_or_replace_without_nullifier + +```rust +test_initialize_or_replace_without_nullifier(); +``` + +Takes no parameters. + +### test_initialize_or_replace_with_nullifier + +```rust +test_initialize_or_replace_with_nullifier(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_set.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_set.md new file mode 100644 index 000000000000..2deb70102a4e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_set.md @@ -0,0 +1,95 @@ +## Standalone Functions + +### get_storage_slot + +```rust +get_storage_slot(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(context, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | Context | +| storage_slot | Field | + +### insert + +```rust +insert(self, note); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| note | Note | + +### pop_notes + +```rust +pop_notes(self, options, N, PREPROCESSOR_ARGS, FILTER_ARGS>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| options | NoteGetterOptions<Note | +| N | | +| PREPROCESSOR_ARGS | | +| FILTER_ARGS> | | +| | | + +### remove + +```rust +remove(self, retrieved_note); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| retrieved_note | RetrievedNote<Note> | + +### get_notes + +```rust +get_notes(self, options, N, PREPROCESSOR_ARGS, FILTER_ARGS>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| options | NoteGetterOptions<Note | +| N | | +| PREPROCESSOR_ARGS | | +| FILTER_ARGS> | | +| | | + +### view_notes + +```rust +view_notes(self, options, N>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| options | NoteViewerOptions<Note | +| N> | | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_set/test.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_set/test.md new file mode 100644 index 000000000000..8a61e6b26767 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/private_set/test.md @@ -0,0 +1,116 @@ +## Standalone Functions + +### setup + +```rust +setup(); +``` + +Takes no parameters. + +### in_private + +```rust +in_private(env, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| env | &mut TestEnvironment | +| | | + +### in_utility + +```rust +in_utility(env); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| env | TestEnvironment | + +### get_empty + +```rust +get_empty(); +``` + +Takes no parameters. + +### view_empty + +```rust +view_empty(); +``` + +Takes no parameters. + +### insert_side_effects + +```rust +insert_side_effects(); +``` + +Takes no parameters. + +### insert_note + +```rust +insert_note(env); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| env | &mut TestEnvironment | + +### insert_and_get_pending + +```rust +insert_and_get_pending(); +``` + +Takes no parameters. + +### insert_and_view_pending + +```rust +insert_and_view_pending(); +``` + +Takes no parameters. + +### insert_and_pop_pending + +```rust +insert_and_pop_pending(); +``` + +Takes no parameters. + +### insert_pop_and_read_again_pending + +```rust +insert_pop_and_read_again_pending(); +``` + +Takes no parameters. + +### insert_and_remove_pending + +```rust +insert_and_remove_pending(); +``` + +Takes no parameters. + +### insert_remove_and_read_again_pending + +```rust +insert_remove_and_read_again_pending(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/public_immutable.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/public_immutable.md new file mode 100644 index 000000000000..8a22b6fb5039 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/public_immutable.md @@ -0,0 +1,102 @@ +## Standalone Functions + +### get_storage_slot + +```rust +get_storage_slot(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(// Note); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| // Note | Passing the contexts to new(.. | + +### compute_initialization_nullifier + +```rust +compute_initialization_nullifier(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### initialize + +```rust +initialize(self, value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| value | T | + +### read + +```rust +read(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### read_unsafe + +```rust +read_unsafe(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### is_initialized + +```rust +is_initialized(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### read + +```rust +read(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### read + +```rust +read(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/public_immutable/test.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/public_immutable/test.md new file mode 100644 index 000000000000..16d7b2df228d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/public_immutable/test.md @@ -0,0 +1,53 @@ +## Standalone Functions + +### setup + +```rust +setup(); +``` + +Takes no parameters. + +### in_public + +```rust +in_public(env); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| env | TestEnvironment | + +### test_uninitialized_by_default + +```rust +test_uninitialized_by_default(); +``` + +Takes no parameters. + +### test_initialize_uninitialized + +```rust +test_initialize_uninitialized(); +``` + +Takes no parameters. + +### test_initialize_already_initialized + +```rust +test_initialize_already_initialized(); +``` + +Takes no parameters. + +### test_read_uninitialized + +```rust +test_read_uninitialized(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/public_mutable.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/public_mutable.md new file mode 100644 index 000000000000..582851052aad --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/public_mutable.md @@ -0,0 +1,58 @@ +## Standalone Functions + +### get_storage_slot + +```rust +get_storage_slot(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(// Note); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| // Note | Passing the contexts to new(.. | + +### read + +```rust +read(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### write + +```rust +write(self, value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| value | T | + +### read + +```rust +read(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/shared_mutable.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/shared_mutable.md new file mode 100644 index 000000000000..9ed1c44ad50a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/shared_mutable.md @@ -0,0 +1,176 @@ +## Standalone Functions + +### get_storage_slot + +```rust +get_storage_slot(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(context, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | Context | +| storage_slot | Field | + +### schedule_value_change + +```rust +schedule_value_change(self, new_value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| new_value | T | + +### schedule_and_return_value_change + +```rust +schedule_and_return_value_change(self, new_value, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| new_value | T | +| | | + +### schedule_delay_change + +```rust +schedule_delay_change(self, new_delay); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| new_delay | u32 | + +### get_current_value + +```rust +get_current_value(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_current_delay + +```rust +get_current_delay(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_scheduled_value + +```rust +get_scheduled_value(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_scheduled_delay + +```rust +get_scheduled_delay(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### read_value_change + +```rust +read_value_change(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### read_delay_change + +```rust +read_delay_change(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### write + +```rust +write(self, value_change, delay_change, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| value_change | ScheduledValueChange<T> | +| delay_change | ScheduledDelayChange<INITIAL_DELAY> | +| | | + +### get_current_value + +```rust +get_current_value(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### historical_read_from_public_storage + +```rust +historical_read_from_public_storage(self, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| | | + +### get_current_value + +```rust +get_current_value(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/shared_mutable/test.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/shared_mutable/test.md new file mode 100644 index 000000000000..97c6af4a46ac --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/shared_mutable/test.md @@ -0,0 +1,207 @@ +## Standalone Functions + +### setup + +```rust +setup(); +``` + +Takes no parameters. + +### in_public + +```rust +in_public(env, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| env | TestEnvironment | +| | | + +### in_private + +```rust +in_private(env, historical_block_number, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| env | &mut TestEnvironment | +| historical_block_number | u32 | +| | | + +### in_utility + +```rust +in_utility(env, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| env | TestEnvironment | +| | | + +### test_get_current_value_in_public_initial + +```rust +test_get_current_value_in_public_initial(); +``` + +Takes no parameters. + +### test_get_scheduled_value_in_public + +```rust +test_get_scheduled_value_in_public(); +``` + +Takes no parameters. + +### test_get_current_value_in_public_before_scheduled_change + +```rust +test_get_current_value_in_public_before_scheduled_change(); +``` + +Takes no parameters. + +### test_get_current_value_in_public_at_scheduled_change + +```rust +test_get_current_value_in_public_at_scheduled_change(); +``` + +Takes no parameters. + +### test_get_current_value_in_public_after_scheduled_change + +```rust +test_get_current_value_in_public_after_scheduled_change(); +``` + +Takes no parameters. + +### test_get_current_delay_in_public_initial + +```rust +test_get_current_delay_in_public_initial(); +``` + +Takes no parameters. + +### test_get_scheduled_delay_in_public + +```rust +test_get_scheduled_delay_in_public(); +``` + +Takes no parameters. + +### test_get_current_delay_in_public_before_scheduled_change + +```rust +test_get_current_delay_in_public_before_scheduled_change(); +``` + +Takes no parameters. + +### test_get_current_delay_in_public_at_scheduled_change + +```rust +test_get_current_delay_in_public_at_scheduled_change(); +``` + +Takes no parameters. + +### test_get_current_delay_in_public_after_scheduled_change + +```rust +test_get_current_delay_in_public_after_scheduled_change(); +``` + +Takes no parameters. + +### test_get_current_value_in_private_initial + +```rust +test_get_current_value_in_private_initial(); +``` + +Takes no parameters. + +### test_get_current_value_in_private_before_change + +```rust +test_get_current_value_in_private_before_change(); +``` + +Takes no parameters. + +### test_get_current_value_in_private_immediately_before_change + +```rust +test_get_current_value_in_private_immediately_before_change(); +``` + +Takes no parameters. + +### test_get_current_value_in_private_at_change + +```rust +test_get_current_value_in_private_at_change(); +``` + +Takes no parameters. + +### test_get_current_value_in_private_after_change + +```rust +test_get_current_value_in_private_after_change(); +``` + +Takes no parameters. + +### test_get_current_value_in_private_with_non_initial_delay + +```rust +test_get_current_value_in_private_with_non_initial_delay(); +``` + +Takes no parameters. + +### test_get_current_value_in_utility_initial + +```rust +test_get_current_value_in_utility_initial(); +``` + +Takes no parameters. + +### test_get_current_value_in_utility_before_scheduled_change + +```rust +test_get_current_value_in_utility_before_scheduled_change(); +``` + +Takes no parameters. + +### test_get_current_value_in_utility_at_scheduled_change + +```rust +test_get_current_value_in_utility_at_scheduled_change(); +``` + +Takes no parameters. + +### test_get_current_value_in_utility_after_scheduled_change + +```rust +test_get_current_value_in_utility_after_scheduled_change(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/storage.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/storage.md new file mode 100644 index 000000000000..4ba439af50c8 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/state_vars/storage.md @@ -0,0 +1,22 @@ +# Storable + +Struct representing an exportable storage variable in the contract Every entry in the storage struct will be exported in the compilation artifact as a Storable entity, containing the storage slot + +## Fields +| Field | Type | +| --- | --- | +| pub slot | Field | + +## Standalone Functions + +### get_storage_slot + +```rust +get_storage_slot(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/helpers/cheatcodes.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/helpers/cheatcodes.md new file mode 100644 index 000000000000..d1362fc34d35 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/helpers/cheatcodes.md @@ -0,0 +1,468 @@ +## Standalone Functions + +### reset + +```rust +reset(); +``` + +Takes no parameters. + +### get_side_effects_counter + +```rust +get_side_effects_counter(); +``` + +Takes no parameters. + +### get_contract_address + +```rust +get_contract_address(); +``` + +Takes no parameters. + +### set_contract_address + +```rust +set_contract_address(address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | + +### advance_blocks_by + +```rust +advance_blocks_by(blocks); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| blocks | u32 | + +### advance_timestamp_by + +```rust +advance_timestamp_by(duration); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| duration | u64 | + +### get_private_context_inputs + +```rust +get_private_context_inputs(); +``` + +Takes no parameters. + +### get_private_context_inputs_at_block + +```rust +get_private_context_inputs_at_block(historical_block_number, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| historical_block_number | u32 | +| | | + +### get_private_context_inputs_at_timestamp + +```rust +get_private_context_inputs_at_timestamp(historical_timestamp, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| historical_timestamp | u64 | +| | | + +### deploy + +```rust +deploy(path, name, initializer, args, secret, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| path | str<N> | +| name | str<M> | +| initializer | str<P> | +| args | [Field] | +| secret | Field | +| | | + +### direct_storage_write + +```rust +direct_storage_write(contract_address, storage_slot, fields, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| contract_address | AztecAddress | +| storage_slot | Field | +| fields | [Field; N] | +| | | + +### create_account + +```rust +create_account(secret); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| secret | Field | + +### add_account + +```rust +add_account(secret); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| secret | Field | + +### derive_keys + +```rust +derive_keys(secret); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| secret | Field | + +### add_authwit + +```rust +add_authwit(address, message_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | +| message_hash | Field | + +### assert_public_call_fails + +```rust +assert_public_call_fails(target_address, function_selector, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_address | AztecAddress | +| function_selector | FunctionSelector | +| args | [Field] | +| | | + +### assert_private_call_fails + +```rust +assert_private_call_fails(target_address, function_selector, argsHash, sideEffectsCounter, isStaticCall, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_address | AztecAddress | +| function_selector | FunctionSelector | +| argsHash | Field | +| sideEffectsCounter | Field | +| isStaticCall | bool | +| | | + +### private_call_new_flow + +```rust +private_call_new_flow(from, contract_address, function_selector, args, args_hash, is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| from | AztecAddress | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| args | [Field] | +| args_hash | Field | +| is_static_call | bool | +| | | + +### public_call_new_flow + +```rust +public_call_new_flow(from, contract_address, function_selector, args, is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| from | AztecAddress | +| contract_address | AztecAddress | +| function_selector | FunctionSelector | +| args | [Field] | +| is_static_call | bool | +| | | + +### disable_oracles + +```rust +disable_oracles(); +``` + +Takes no parameters. + +### enable_oracles + +```rust +enable_oracles(); +``` + +Takes no parameters. + +### oracle_reset + +```rust +oracle_reset(); +``` + +Takes no parameters. + +### oracle_set_contract_address + +```rust +oracle_set_contract_address(address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | + +### oracle_get_contract_address + +```rust +oracle_get_contract_address(); +``` + +Takes no parameters. + +### oracle_get_side_effects_counter + +```rust +oracle_get_side_effects_counter(); +``` + +Takes no parameters. + +### oracle_advance_blocks_by + +```rust +oracle_advance_blocks_by(blocks); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| blocks | u32 | + +### oracle_advance_timestamp_by + +```rust +oracle_advance_timestamp_by(duration); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| duration | u64 | + +### oracle_get_private_context_inputs + +```rust +oracle_get_private_context_inputs(historical_block_number, historical_timestamp, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| historical_block_number | Option<u32> | +| historical_timestamp | Option<u64> | +| | | + +### oracle_deploy + +```rust +oracle_deploy(path, name, initializer, args, secret, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| path | str<N> | +| name | str<M> | +| initializer | str<P> | +| args | [Field] | +| secret | Field | +| | | + +### direct_storage_write_oracle + +```rust +direct_storage_write_oracle(_contract_address, _storage_slot, _values, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _contract_address | AztecAddress | +| _storage_slot | Field | +| _values | [Field; N] | +| | | + +### oracle_create_account + +```rust +oracle_create_account(secret); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| secret | Field | + +### oracle_add_account + +```rust +oracle_add_account(secret); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| secret | Field | + +### oracle_derive_keys + +```rust +oracle_derive_keys(secret); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| secret | Field | + +### oracle_add_authwit + +```rust +oracle_add_authwit(address, message_hash); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| address | AztecAddress | +| message_hash | Field | + +### oracle_assert_public_call_fails + +```rust +oracle_assert_public_call_fails(target_address, function_selector, args, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_address | AztecAddress | +| function_selector | FunctionSelector | +| args | [Field] | +| | | + +### oracle_assert_private_call_fails + +```rust +oracle_assert_private_call_fails(target_address, function_selector, argsHash, sideEffectsCounter, isStaticCall, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| target_address | AztecAddress | +| function_selector | FunctionSelector | +| argsHash | Field | +| sideEffectsCounter | Field | +| isStaticCall | bool | +| | | + +### oracle_private_call_new_flow + +```rust +oracle_private_call_new_flow(_from, _contract_address, _function_selector, _args, _args_hash, _is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _from | AztecAddress | +| _contract_address | AztecAddress | +| _function_selector | FunctionSelector | +| _args | [Field] | +| _args_hash | Field | +| _is_static_call | bool | +| | | + +### oracle_disable_oracles + +```rust +oracle_disable_oracles(); +``` + +Takes no parameters. + +### oracle_enable_oracles + +```rust +oracle_enable_oracles(); +``` + +Takes no parameters. + +### oracle_public_call_new_flow + +```rust +oracle_public_call_new_flow(from, contract_address, calldata, is_static_call, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| from | AztecAddress | +| contract_address | AztecAddress | +| calldata | [Field] | +| is_static_call | bool | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/helpers/test_environment.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/helpers/test_environment.md new file mode 100644 index 000000000000..33057af78cc7 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/helpers/test_environment.md @@ -0,0 +1,469 @@ +# TestEnvironment + +## Methods + +### new + +```rust +TestEnvironment::new(); +``` + +Takes no parameters. + +### _new + +```rust +TestEnvironment::_new(); +``` + +Takes no parameters. + +### pending_block_number + +```rust +TestEnvironment::pending_block_number(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### pending_timestamp + +```rust +TestEnvironment::pending_timestamp(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### block_number + +```rust +TestEnvironment::block_number(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | Self | + +### committed_block_number + +```rust +TestEnvironment::committed_block_number(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | Self | + +### committed_timestamp + +```rust +TestEnvironment::committed_timestamp(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | Self | + +### contract_address + +```rust +TestEnvironment::contract_address(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### impersonate + +```rust +TestEnvironment::impersonate(_self, address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| address | AztecAddress | + +### advance_block_to + +```rust +TestEnvironment::advance_block_to(&mut self, block_number); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| block_number | u32 | + +### advance_timestamp_to + +```rust +TestEnvironment::advance_timestamp_to(&mut self, timestamp); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| timestamp | u64 | + +### advance_block_by + +```rust +TestEnvironment::advance_block_by(_self, blocks); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | &mut Self | +| blocks | u32 | + +### advance_timestamp_by + +```rust +TestEnvironment::advance_timestamp_by(_self, duration); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | &mut Self | +| duration | u64 | + +### public + +```rust +TestEnvironment::public(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### public_with_args_hash + +```rust +TestEnvironment::public_with_args_hash(_self, args); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| args | [Field] | + +### private + +```rust +TestEnvironment::private(&mut self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | + +### utility + +```rust +TestEnvironment::utility(_self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | + +### private_at + +```rust +TestEnvironment::private_at(&mut self, historical_block_number); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| historical_block_number | u32 | + +### private_at_timestamp + +```rust +TestEnvironment::private_at_timestamp(&mut self, historical_timestamp, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| historical_timestamp | u64 | +| | | + +### create_account + +```rust +TestEnvironment::create_account(_self, secret); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| secret | Field | + +### create_account_contract + +```rust +TestEnvironment::create_account_contract(&mut self, secret); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| secret | Field | + +### deploy + +```rust +TestEnvironment::deploy(_self, path, name, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| path | str<N> | +| name | str<M> | +| | | + +### deploy_self + +```rust +TestEnvironment::deploy_self(_self, name); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| name | str<M> | + +### deploy_with_public_keys + +```rust +TestEnvironment::deploy_with_public_keys(_self, path, name, secret, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| path | str<N> | +| name | str<M> | +| secret | Field | +| | | + +### deploy_self_with_public_keys + +```rust +TestEnvironment::deploy_self_with_public_keys(_self, name, secret, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| name | str<M> | +| secret | Field | +| | | + +### assert_public_call_fails + +```rust +TestEnvironment::assert_public_call_fails(_self, call_interface); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| call_interface | C | + +### assert_private_call_fails + +```rust +TestEnvironment::assert_private_call_fails(_self, call_interface); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| call_interface | C | + +### call_private + +```rust +TestEnvironment::call_private(_self, from, call_interface, T, N>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| from | AztecAddress | +| call_interface | PrivateCallInterface<M | +| T | | +| N> | | +| | | + +### call_private_void + +```rust +TestEnvironment::call_private_void(_self, from, call_interface, T, N>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| from | AztecAddress | +| call_interface | PrivateVoidCallInterface<M | +| T | | +| N> | | +| | | + +### simulate_utility + +```rust +TestEnvironment::simulate_utility(_self, call_interface, T, N>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| call_interface | UtilityCallInterface<M | +| T | | +| N> | | +| | | + +### simulate_void_utility + +```rust +TestEnvironment::simulate_void_utility(_self, call_interface, T, N>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| call_interface | UtilityCallInterface<M | +| T | | +| N> | | +| | | + +### call_public + +```rust +TestEnvironment::call_public(_self, from, call_interface, T, N>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| from | AztecAddress | +| call_interface | PublicCallInterface<M | +| T | | +| N> | | +| | | + +### call_public_void + +```rust +TestEnvironment::call_public_void(_self, from, call_interface, T, N>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| from | AztecAddress | +| call_interface | PublicVoidCallInterface<M | +| T | | +| N> | | +| | | + +### call_private_test + +```rust +TestEnvironment::call_private_test(_self, from, call_interface, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| from | AztecAddress | +| call_interface | X | +| | | + +### simulate_utility_test + +```rust +TestEnvironment::simulate_utility_test(_self, call_interface, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| call_interface | X | +| | | + +### call_public_test + +```rust +TestEnvironment::call_public_test(_self, from, call_interface, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| from | AztecAddress | +| call_interface | X | +| | | + +### add_note + +```rust +TestEnvironment::add_note(_self, note, storage_slot, contract_address, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| note | Note | +| storage_slot | Field | +| contract_address | AztecAddress | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/helpers/utils.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/helpers/utils.md new file mode 100644 index 000000000000..1cbf5c90edde --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/helpers/utils.md @@ -0,0 +1,63 @@ +# TestAccount + +## Fields +| Field | Type | +| --- | --- | +| pub address | AztecAddress | +| pub keys | PublicKeys | + +## Standalone Functions + +### with_private_initializer + +```rust +with_private_initializer(self, from, call_interface, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| from | AztecAddress | +| call_interface | C | +| | | + +### with_public_void_initializer + +```rust +with_public_void_initializer(self, from, call_interface, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| from | AztecAddress | +| call_interface | C | +| | | + +### with_public_initializer + +```rust +with_public_initializer(self, from, call_interface, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| from | AztecAddress | +| call_interface | C | +| | | + +### without_initializer + +```rust +without_initializer(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/mocks/mock_note.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/mocks/mock_note.md new file mode 100644 index 000000000000..7210aa8d8cce --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/mocks/mock_note.md @@ -0,0 +1,136 @@ +# MockNote + +## Fields +| Field | Type | +| --- | --- | +| pub(crate) value | Field | + +## Methods + +### new + +```rust +MockNote::new(value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| value | Field | + +# MockNoteBuilder + +## Fields +| Field | Type | +| --- | --- | +| value | Field | +| contract_address | Option<AztecAddress> | +| note_metadata | Option<NoteMetadata> | + +## Methods + +### new + +```rust +MockNoteBuilder::new(value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| value | Field | + +### contract_address + +```rust +MockNoteBuilder::contract_address(&mut self, contract_address); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| contract_address | AztecAddress | + +### note_metadata + +```rust +MockNoteBuilder::note_metadata(&mut self, note_metadata); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| &mut self | | +| note_metadata | NoteMetadata | + +### build_note + +```rust +MockNoteBuilder::build_note(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### build_retrieved_note + +```rust +MockNoteBuilder::build_retrieved_note(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +## Standalone Functions + +### get_id + +```rust +get_id(); +``` + +Takes no parameters. + +### compute_note_hash + +```rust +compute_note_hash(self, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | Self | +| storage_slot | Field | + +### compute_nullifier + +```rust +compute_nullifier(_self, _context, note_hash_for_nullify, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| _self | Self | +| _context | &mut PrivateContext | +| note_hash_for_nullify | Field | +| | | + +### compute_nullifier_unconstrained + +```rust +compute_nullifier_unconstrained(self, note_hash_for_nullify); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| note_hash_for_nullify | Field | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/mocks/mock_struct.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/mocks/mock_struct.md new file mode 100644 index 000000000000..3536ea15d81d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/test/mocks/mock_struct.md @@ -0,0 +1,104 @@ +# MockStruct + +## Fields +| Field | Type | +| --- | --- | +| pub a | Field | +| pub b | Field | + +## Methods + +### new + +```rust +MockStruct::new(a, b); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| a | Field | +| b | Field | + +## Standalone Functions + +### eq + +```rust +eq(self, other); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| other | Self | + +### empty + +```rust +empty(); +``` + +Takes no parameters. + +### serialize + +```rust +serialize(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### deserialize + +```rust +deserialize(fields); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| fields | [Field; 2] | + +### pack + +```rust +pack(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### unpack + +```rust +unpack(fields); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| fields | [Field; 2] | + +### test_serde + +```rust +test_serde(); +``` + +Takes no parameters. + +### test_packable + +```rust +test_packable(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/array/append.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/array/append.md new file mode 100644 index 000000000000..cee6e53dddb4 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/array/append.md @@ -0,0 +1,43 @@ +## Standalone Functions + +### append + +```rust +append(a, A_LEN>, b, B_LEN>, ); +``` + +/ large enough to fit all of the elements of both the first and second vectors. + +#### Parameters +| Name | Type | +| --- | --- | +| a | BoundedVec<T | +| A_LEN> | | +| b | BoundedVec<T | +| B_LEN> | | +| | | + +### append_empty_vecs + +```rust +append_empty_vecs(); +``` + +Takes no parameters. + +### append_non_empty_vecs + +```rust +append_non_empty_vecs(); +``` + +Takes no parameters. + +### append_non_empty_vecs_insufficient_max_len + +```rust +append_non_empty_vecs_insufficient_max_len(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/array/collapse.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/array/collapse.md new file mode 100644 index 000000000000..4aadedd2a54a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/array/collapse.md @@ -0,0 +1,147 @@ +## Standalone Functions + +### collapse + +```rust +collapse(input); +``` + +/ `collapsed: [3, 1]` + +#### Parameters +| Name | Type | +| --- | --- | +| input | [Option<T>; N] | + +### verify_collapse_hints + +```rust +verify_collapse_hints(input, collapsed, N>, collapsed_to_input_index_mapping, N>, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| input | [Option<T>; N] | +| collapsed | BoundedVec<T | +| N> | | +| collapsed_to_input_index_mapping | BoundedVec<u32 | +| N> | | +| | | + +### get_collapse_hints + +```rust +get_collapse_hints(input, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| input | [Option<T>; N] | +| | | + +### collapse_empty_array + +```rust +collapse_empty_array(); +``` + +Takes no parameters. + +### collapse_non_sparse_array + +```rust +collapse_non_sparse_array(); +``` + +Takes no parameters. + +### collapse_sparse_array + +```rust +collapse_sparse_array(); +``` + +Takes no parameters. + +### collapse_front_padding + +```rust +collapse_front_padding(); +``` + +Takes no parameters. + +### collapse_back_padding + +```rust +collapse_back_padding(); +``` + +Takes no parameters. + +### verify_collapse_hints_good_hints + +```rust +verify_collapse_hints_good_hints(); +``` + +Takes no parameters. + +### verify_collapse_hints_wrong_length + +```rust +verify_collapse_hints_wrong_length(); +``` + +Takes no parameters. + +### verify_collapse_hints_hint_length_mismatch + +```rust +verify_collapse_hints_hint_length_mismatch(); +``` + +Takes no parameters. + +### verify_collapse_hints_out_of_bounds_index_hint + +```rust +verify_collapse_hints_out_of_bounds_index_hint(); +``` + +Takes no parameters. + +### verify_collapse_hints_hint_to_none + +```rust +verify_collapse_hints_hint_to_none(); +``` + +Takes no parameters. + +### verify_collapse_hints_wrong_vec_content + +```rust +verify_collapse_hints_wrong_vec_content(); +``` + +Takes no parameters. + +### verify_collapse_hints_wrong_vec_order + +```rust +verify_collapse_hints_wrong_vec_order(); +``` + +Takes no parameters. + +### verify_collapse_hints_dirty_storage + +```rust +verify_collapse_hints_dirty_storage(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/array/subarray.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/array/subarray.md new file mode 100644 index 000000000000..ae6406eead89 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/array/subarray.md @@ -0,0 +1,57 @@ +## Standalone Functions + +### subarray + +```rust +subarray(src, offset, ); +``` + +/ ``` + +#### Parameters +| Name | Type | +| --- | --- | +| src | [T; SRC_LEN] | +| offset | u32 | +| | | + +### subarray_into_empty + +```rust +subarray_into_empty(); +``` + +Takes no parameters. + +### subarray_complete + +```rust +subarray_complete(); +``` + +Takes no parameters. + +### subarray_different_end_sizes + +```rust +subarray_different_end_sizes(); +``` + +Takes no parameters. + +### subarray_offset_too_large + +```rust +subarray_offset_too_large(); +``` + +Takes no parameters. + +### subarray_bad_return_value + +```rust +subarray_bad_return_value(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/array/subbvec.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/array/subbvec.md new file mode 100644 index 000000000000..645350dd2f10 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/array/subbvec.md @@ -0,0 +1,82 @@ +## Standalone Functions + +### subbvec + +```rust +subbvec(bvec, SRC_MAX_LEN>, offset, ); +``` + +/ ``` + +#### Parameters +| Name | Type | +| --- | --- | +| bvec | BoundedVec<T | +| SRC_MAX_LEN> | | +| offset | u32 | +| | | + +### subbvec_empty + +```rust +subbvec_empty(); +``` + +Takes no parameters. + +### subbvec_complete + +```rust +subbvec_complete(); +``` + +Takes no parameters. + +### subbvec_partial + +```rust +subbvec_partial(); +``` + +Takes no parameters. + +### subbvec_into_empty + +```rust +subbvec_into_empty(); +``` + +Takes no parameters. + +### subbvec_offset_past_len + +```rust +subbvec_offset_past_len(); +``` + +Takes no parameters. + +### subbvec_insufficient_dst_len + +```rust +subbvec_insufficient_dst_len(); +``` + +Takes no parameters. + +### subbvec_dst_len_causes_enlarge + +```rust +subbvec_dst_len_causes_enlarge(); +``` + +Takes no parameters. + +### subbvec_dst_len_too_large_for_offset + +```rust +subbvec_dst_len_too_large_for_offset(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/comparison.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/comparison.md new file mode 100644 index 000000000000..2755f026ba81 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/comparison.md @@ -0,0 +1,35 @@ +# ComparatorEnum + +## Fields +| Field | Type | +| --- | --- | +| pub EQ | u8 | +| pub NEQ | u8 | +| pub LT | u8 | +| pub LTE | u8 | +| pub GT | u8 | +| pub GTE | u8 | + +## Standalone Functions + +### compare + +```rust +compare(lhs, operation, rhs); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| lhs | Field | +| operation | u8 | +| rhs | Field | + +### test_compare + +```rust +test_compare(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/conversion/bytes_to_fields.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/conversion/bytes_to_fields.md new file mode 100644 index 000000000000..f7a08f0e9953 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/conversion/bytes_to_fields.md @@ -0,0 +1,48 @@ +## Standalone Functions + +### bytes_to_fields + +```rust +bytes_to_fields(bytes); +``` + +/ Note: N must be a multiple of 31 bytes + +#### Parameters +| Name | Type | +| --- | --- | +| bytes | [u8; N] | + +### bytes_from_fields + +```rust +bytes_from_fields(fields, N>); +``` + +/ back together in the order of the original fields. + +#### Parameters +| Name | Type | +| --- | --- | +| fields | BoundedVec<Field | +| N> | | + +### random_bytes_to_fields_and_back + +```rust +random_bytes_to_fields_and_back(input); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| input | [u8; 93] | + +### bytes_to_fields_input_length_not_multiple_of_31 + +```rust +bytes_to_fields_input_length_not_multiple_of_31(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/conversion/fields_to_bytes.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/conversion/fields_to_bytes.md new file mode 100644 index 000000000000..8d68f95f9ddc --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/conversion/fields_to_bytes.md @@ -0,0 +1,67 @@ +## Standalone Functions + +### fields_to_bytes + +```rust +fields_to_bytes(fields); +``` + +/ indistinguishable from random bytes. + +#### Parameters +| Name | Type | +| --- | --- | +| fields | [Field; N] | + +### fields_from_bytes + +```rust +fields_from_bytes(bytes, N>); +``` + +/ Note 2: The max value check code was taken from std::field::to_be_bytes function. + +#### Parameters +| Name | Type | +| --- | --- | +| bytes | BoundedVec<u8 | +| N> | | + +### random_fields_to_bytes_and_back + +```rust +random_fields_to_bytes_and_back(input); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| input | [Field; 3] | + +### to_fields_assert + +```rust +to_fields_assert(); +``` + +Takes no parameters. + +### fields_from_bytes_max_value + +```rust +fields_from_bytes_max_value(); +``` + +Takes no parameters. + +### fields_from_bytes_overflow + +```rust +fields_from_bytes_overflow(random_value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| random_value | u8 | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/field.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/field.md new file mode 100644 index 000000000000..28beaf1b4a45 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/field.md @@ -0,0 +1,166 @@ +## Standalone Functions + +### pow + +```rust +pow(x, y); +``` + +Adapted from std::field::pow_32. + +#### Parameters +| Name | Type | +| --- | --- | +| x | Field | +| y | Field | + +### is_square + +```rust +is_square(x); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| x | Field | + +### tonelli_shanks_sqrt + +```rust +tonelli_shanks_sqrt(x); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| x | Field | + +### __sqrt + +```rust +__sqrt(x); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| x | Field | + +### sqrt + +```rust +sqrt(x); +``` + +Returns (true, sqrt) if there is a square root. + +#### Parameters +| Name | Type | +| --- | --- | +| x | Field | + +### validate_sqrt_hint + +```rust +validate_sqrt_hint(x, hint); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| x | Field | +| hint | Field | + +### validate_not_sqrt_hint + +```rust +validate_not_sqrt_hint(x, hint); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| x | Field | +| hint | Field | + +### test_sqrt + +```rust +test_sqrt(); +``` + +Takes no parameters. + +### test_non_square + +```rust +test_non_square(); +``` + +Takes no parameters. + +### test_known_non_residue_is_actually_a_non_residue_in_the_field + +```rust +test_known_non_residue_is_actually_a_non_residue_in_the_field(); +``` + +Takes no parameters. + +### test_sqrt_0 + +```rust +test_sqrt_0(); +``` + +Takes no parameters. + +### test_sqrt_1 + +```rust +test_sqrt_1(); +``` + +Takes no parameters. + +### test_bad_sqrt_hint_fails + +```rust +test_bad_sqrt_hint_fails(); +``` + +Takes no parameters. + +### test_bad_not_sqrt_hint_fails + +```rust +test_bad_not_sqrt_hint_fails(); +``` + +Takes no parameters. + +### test_0_not_sqrt_hint_fails + +```rust +test_0_not_sqrt_hint_fails(); +``` + +Takes no parameters. + +### test_is_square + +```rust +test_is_square(); +``` + +Takes no parameters. + +### test_is_not_square + +```rust +test_is_not_square(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/point.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/point.md new file mode 100644 index 000000000000..2c25553784d2 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/point.md @@ -0,0 +1,75 @@ +## Standalone Functions + +### point_to_bytes + +```rust +point_to_bytes(p); +``` + +/ to waste the extra byte (encrypted log). + +#### Parameters +| Name | Type | +| --- | --- | +| p | Point | + +### get_sign_of_point + +```rust +get_sign_of_point(p); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| p | Point | + +### point_from_x_coord + +```rust +point_from_x_coord(x); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| x | Field | + +### point_from_x_coord_and_sign + +```rust +point_from_x_coord_and_sign(x, sign); +``` + +/ @param sign - The "sign" of the y coordinate - determines whether y <= (Fr.MODULUS - 1) / 2 + +#### Parameters +| Name | Type | +| --- | --- | +| x | Field | +| sign | bool | + +### test_point_to_bytes_positive_sign + +```rust +test_point_to_bytes_positive_sign(); +``` + +Takes no parameters. + +### test_point_to_bytes_negative_sign + +```rust +test_point_to_bytes_negative_sign(); +``` + +Takes no parameters. + +### test_point_from_x_coord_and_sign + +```rust +test_point_from_x_coord_and_sign(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/random.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/random.md new file mode 100644 index 000000000000..f9c8315ce4fe --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/random.md @@ -0,0 +1,10 @@ +## Standalone Functions + +### get_random_bytes + +```rust +get_random_bytes(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/remove_constraints.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/remove_constraints.md new file mode 100644 index 000000000000..6f383ab942bc --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/remove_constraints.md @@ -0,0 +1,75 @@ +## Standalone Functions + +### remove_constraints + +```rust +remove_constraints(f); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| f | fn[Env]( | + +### remove_constraints_if + +```rust +remove_constraints_if(condition, f); +``` + +/// Requires `condition` to be a compile time constant. + +#### Parameters +| Name | Type | +| --- | --- | +| condition | bool | +| f | fn[Env]( | + +### return_unit + +```rust +return_unit(); +``` + +Takes no parameters. + +### return_field + +```rust +return_field(); +``` + +Takes no parameters. + +### returns_unit + +```rust +returns_unit(); +``` + +Takes no parameters. + +### returns_original_value + +```rust +returns_original_value(); +``` + +Takes no parameters. + +### returns_unit_unconstrained + +```rust +returns_unit_unconstrained(); +``` + +Takes no parameters. + +### returns_original_value_unconstrained + +```rust +returns_original_value_unconstrained(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/secrets.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/secrets.md new file mode 100644 index 000000000000..9cc5d82705d7 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/secrets.md @@ -0,0 +1,18 @@ +# SecretAndHash + +## Fields +| Field | Type | +| --- | --- | +| secret | Field | +| hash | Field | + +## Methods + +### random + +```rust +SecretAndHash::random(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/to_bytes.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/to_bytes.md new file mode 100644 index 000000000000..215eb5c7d159 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/to_bytes.md @@ -0,0 +1,26 @@ +## Standalone Functions + +### arr_to_be_bytes_arr + +```rust +arr_to_be_bytes_arr(fields); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| fields | [Field; L] | + +### str_to_be_bytes_arr + +```rust +str_to_be_bytes_arr(string); +``` + +then an ACVM field via the oracle => we recreate here + +#### Parameters +| Name | Type | +| --- | --- | +| string | str<L> | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/with_hash.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/with_hash.md new file mode 100644 index 000000000000..b79fa68ddf93 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/aztec/utils/with_hash.md @@ -0,0 +1,136 @@ +## Standalone Functions + +### new + +```rust +new(value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| value | T | + +### get_value + +```rust +get_value(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### get_hash + +```rust +get_hash(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### public_storage_read + +```rust +public_storage_read(context, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | PublicContext | +| storage_slot | Field | + +### utility_public_storage_read + +```rust +utility_public_storage_read(context, storage_slot, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | UtilityContext | +| storage_slot | Field | +| | | + +### historical_public_storage_read + +```rust +historical_public_storage_read(header, address, storage_slot, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| header | BlockHeader | +| address | AztecAddress | +| storage_slot | Field | +| | | + +### pack + +```rust +pack(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### unpack + +```rust +unpack(packed); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| packed | [Field; N + 1] | + +### create_and_recover + +```rust +create_and_recover(); +``` + +Takes no parameters. + +### read_uninitialized_value + +```rust +read_uninitialized_value(); +``` + +Takes no parameters. + +### read_initialized_value + +```rust +read_initialized_value(); +``` + +Takes no parameters. + +### test_bad_hint_uninitialized_value + +```rust +test_bad_hint_uninitialized_value(); +``` + +Takes no parameters. + +### test_bad_hint_initialized_value + +```rust +test_bad_hint_initialized_value(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/compressed-string/compressed_string.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/compressed-string/compressed_string.md new file mode 100644 index 000000000000..d4716b947670 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/compressed-string/compressed_string.md @@ -0,0 +1,99 @@ +## Standalone Functions + +### from_string + +```rust +from_string(input_string); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| input_string | str<M> | + +### to_bytes + +```rust +to_bytes(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### eq + +```rust +eq(self, other, M>); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| other | CompressedString<N | +| M> | | + +### serialize + +```rust +serialize(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### deserialize + +```rust +deserialize(input); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| input | [Field; N] | + +### test_short_string + +```rust +test_short_string(); +``` + +Takes no parameters. + +### test_long_string + +```rust +test_long_string(); +``` + +Takes no parameters. + +### test_long_string_work_with_too_many_fields + +```rust +test_long_string_work_with_too_many_fields(); +``` + +Takes no parameters. + +### test_serde + +```rust +test_serde(); +``` + +Takes no parameters. + +### test_long_string_fail_with_too_few_fields + +```rust +test_long_string_fail_with_too_few_fields(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/compressed-string/field_compressed_string.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/compressed-string/field_compressed_string.md new file mode 100644 index 000000000000..5d30c34e5eaf --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/compressed-string/field_compressed_string.md @@ -0,0 +1,54 @@ +# FieldCompressedString + +## Fields +| Field | Type | +| --- | --- | +| value | Field | + +## Methods + +### is_eq + +```rust +FieldCompressedString::is_eq(self, other); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| other | FieldCompressedString | + +### from_field + +```rust +FieldCompressedString::from_field(input_field); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| input_field | Field | + +### from_string + +```rust +FieldCompressedString::from_string(input_string); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| input_string | str<31> | + +### to_bytes + +```rust +FieldCompressedString::to_bytes(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/easy-private-state/easy_private_uint.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/easy-private-state/easy_private_uint.md new file mode 100644 index 000000000000..4471f1fc1512 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/easy-private-state/easy_private_uint.md @@ -0,0 +1,55 @@ +## Standalone Functions + +### get_storage_slot + +```rust +get_storage_slot(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### new + +```rust +new(context, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| context | Context | +| storage_slot | Field | + +### add + +```rust +add(self, addend, owner, sender); +``` + +Very similar to `value_note::utils::increment`. + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| addend | u64 | +| owner | AztecAddress | +| sender | AztecAddress | + +### sub + +```rust +sub(self, subtrahend, owner, sender); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| subtrahend | u64 | +| owner | AztecAddress | +| sender | AztecAddress | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/macro_compilation_failure_tests/failure_contracts/marked_private_unconstrained/main.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/macro_compilation_failure_tests/failure_contracts/marked_private_unconstrained/main.md new file mode 100644 index 000000000000..df0bdc4a3ba5 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/macro_compilation_failure_tests/failure_contracts/marked_private_unconstrained/main.md @@ -0,0 +1,10 @@ +## Standalone Functions + +### unconstrained_private_function + +```rust +unconstrained_private_function(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/macro_compilation_failure_tests/failure_contracts/marked_public_unconstrained/main.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/macro_compilation_failure_tests/failure_contracts/marked_public_unconstrained/main.md new file mode 100644 index 000000000000..8b5844a94f17 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/macro_compilation_failure_tests/failure_contracts/marked_public_unconstrained/main.md @@ -0,0 +1,10 @@ +## Standalone Functions + +### unconstrained_public_function + +```rust +unconstrained_public_function(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/uint-note/uint_note.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/uint-note/uint_note.md new file mode 100644 index 000000000000..bec25318bb34 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/uint-note/uint_note.md @@ -0,0 +1,265 @@ +# UintNote + +## Fields +| Field | Type | +| --- | --- | +| owner | AztecAddress | +| randomness | Field | +| value | u128 | + +## Methods + +### new + +```rust +UintNote::new(value, owner); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| value | u128 | +| owner | AztecAddress | + +### get_value + +```rust +UintNote::get_value(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### partial + +/ Creates a partial note that will hide the owner and storage slot but not the value, since the note will be later / completed in public. This is a powerful technique for scenarios in which the value cannot be known in private / (e.g. because it depends on some public state, such as a DEX). /// This function inserts a partial note validity commitment into the nullifier tree to be later on able to verify / that the partial note and completer are legitimate. See function docs of `compute_validity_commitment` for more / details. /// Each partial note should only be used once, since otherwise multiple notes would be linked together and known to / belong to the same owner. /// As part of the partial note creation process, a log will be sent to `recipient` from `sender` so that they can / discover the note. `recipient` will typically be the same as `owner`. + +```rust +UintNote::partial(owner, storage_slot, context, recipient, sender, completer, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| owner | AztecAddress | +| storage_slot | Field | +| context | &mut PrivateContext | +| recipient | AztecAddress | +| sender | AztecAddress | +| completer | AztecAddress | +| | | + +# UintPartialNotePrivateContent + +## Fields +| Field | Type | +| --- | --- | +| owner | AztecAddress | +| randomness | Field | + +## Methods + +### compute_partial_commitment + +```rust +UintPartialNotePrivateContent::compute_partial_commitment(self, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| storage_slot | Field | + +# PrivateUintPartialNotePrivateLogContent + +## Fields +| Field | Type | +| --- | --- | +| public_log_tag | Field | +| owner | AztecAddress | +| randomness | Field | + +# PartialUintNote + +## Fields +| Field | Type | +| --- | --- | +| commitment | Field | + +## Methods + +### complete + +/ Completes the partial note, creating a new note that can be used like any other UintNote. + +```rust +PartialUintNote::complete(self, context, completer, value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PublicContext | +| completer | AztecAddress | +| value | u128 | + +### complete_from_private + +/ Completes the partial note, creating a new note that can be used like any other UintNote. Same as `complete` / function but works from private context. + +```rust +PartialUintNote::complete_from_private(self, context, completer, value, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | +| completer | AztecAddress | +| value | u128 | +| | | + +### compute_validity_commitment + +/ Computes a validity commitment for this partial note. The commitment cryptographically binds the note's private / data with the designated completer address. When the note is later completed in public execution, we can load / this commitment from the nullifier tree and verify that both the partial note (e.g. that the storage slot / corresponds to the correct owner, and that we're using the correct state variable) and completer are / legitimate. + +```rust +PartialUintNote::compute_validity_commitment(self, completer); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| completer | AztecAddress | + +### compute_note_completion_log + +```rust +PartialUintNote::compute_note_completion_log(self, value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| value | u128 | + +### compute_note_completion_log_padded_for_private_log + +```rust +PartialUintNote::compute_note_completion_log_padded_for_private_log(self, value, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| value | u128 | +| | | + +### compute_complete_note_hash + +```rust +PartialUintNote::compute_complete_note_hash(self, value); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| value | u128 | + +## Standalone Functions + +### compute_note_hash + +```rust +compute_note_hash(self, storage_slot); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| storage_slot | Field | + +### compute_nullifier + +```rust +compute_nullifier(self, context, note_hash_for_nullify, ); +``` + +#[note] macro. + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| context | &mut PrivateContext | +| note_hash_for_nullify | Field | +| | | + +### compute_nullifier_unconstrained + +```rust +compute_nullifier_unconstrained(self, note_hash_for_nullify); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | +| note_hash_for_nullify | Field | + +### get_id + +```rust +get_id(); +``` + +Takes no parameters. + +### to_field + +```rust +to_field(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + +### from_field + +```rust +from_field(field); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| field | Field | + +### note_hash_matches_completed_partial_note_hash + +```rust +note_hash_matches_completed_partial_note_hash(); +``` + +Takes no parameters. + +### unpack_from_partial_note_encoding + +```rust +unpack_from_partial_note_encoding(); +``` + +Takes no parameters. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/value-note/balance_utils.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/value-note/balance_utils.md new file mode 100644 index 000000000000..1adffdc0294f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/value-note/balance_utils.md @@ -0,0 +1,28 @@ +## Standalone Functions + +### get_balance + +```rust +get_balance(set, UtilityContext>); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| set | PrivateSet<ValueNote | +| UtilityContext> | | + +### get_balance_with_offset + +```rust +get_balance_with_offset(set, UtilityContext>, offset, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| set | PrivateSet<ValueNote | +| UtilityContext> | | +| offset | u32 | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/value-note/filter.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/value-note/filter.md new file mode 100644 index 000000000000..8302a1a0be48 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/value-note/filter.md @@ -0,0 +1,15 @@ +## Standalone Functions + +### filter_notes_min_sum + +```rust +filter_notes_min_sum(notes, min_sum, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| notes | [Option<RetrievedNote<ValueNote>>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] | +| min_sum | Field | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/value-note/utils.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/value-note/utils.md new file mode 100644 index 000000000000..7c3c3db432bc --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/value-note/utils.md @@ -0,0 +1,66 @@ +## Standalone Functions + +### create_note_getter_options_for_decreasing_balance + +```rust +create_note_getter_options_for_decreasing_balance(amount, ); +``` + +Pick the fewest notes whose sum is equal to or greater than `amount`. + +#### Parameters +| Name | Type | +| --- | --- | +| amount | Field | +| | | + +### increment + +```rust +increment(// docs, &mut PrivateContext>, amount, recipient, // docs, ); +``` + +Inserts it to the recipient's set of notes. + +#### Parameters +| Name | Type | +| --- | --- | +| // docs | start | +| &mut PrivateContext> | | +| amount | Field | +| recipient | AztecAddress | +| // docs | end | +| | | + +### decrement + +```rust +decrement(balance, &mut PrivateContext>, amount, owner, sender, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| balance | PrivateSet<ValueNote | +| &mut PrivateContext> | | +| amount | Field | +| owner | AztecAddress | +| sender | AztecAddress | +| | | + +### decrement_by_at_most + +```rust +decrement_by_at_most(balance, &mut PrivateContext>, max_amount, owner, sender, ); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| balance | PrivateSet<ValueNote | +| &mut PrivateContext> | | +| max_amount | Field | +| owner | AztecAddress | +| sender | AztecAddress | +| | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/value-note/value_note.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/value-note/value_note.md new file mode 100644 index 000000000000..b6166af3bf68 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/aztec-nr/value-note/value_note.md @@ -0,0 +1,34 @@ +# ValueNote + +## Fields +| Field | Type | +| --- | --- | +| value | Field | +| owner | AztecAddress | +| randomness | Field | + +## Methods + +### new + +```rust +ValueNote::new(value, owner); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| value | Field | +| owner | AztecAddress | + +### value + +```rust +ValueNote::value(self); +``` + +#### Parameters +| Name | Type | +| --- | --- | +| self | | + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/contract_artifact.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/contract_artifact.md new file mode 100644 index 000000000000..54cd58696518 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/contract_artifact.md @@ -0,0 +1,110 @@ +--- +title: "Contract Artifact Reference" +tags: [contracts] +--- + +After compiling a contract you'll get a Contract Artifact file, that contains the data needed to interact with a specific contract, including its name, functions that can be executed, and the interface and code of those functions. Since private functions are not published in the Aztec network, you'll need this artifact file to be able to call private functions of contracts. + +The artifact file can be used with `aztec.js` to instantiate contract objects and interact with them. + +## Contract Artifact Structure + +The structure of a contract artifact is as follows: +```json +{ + "name": "CardGame", + "functions": [ + { + "name": "constructor", + "functionType": "private", + "isInternal": false, + "parameters": [], + "returnTypes": [], + "bytecode": "...", + "verificationKey": "..." + }, + { + "name": "on_card_played", + "functionType": "public", + "isInternal": true, + "parameters": [ + { + "name": "game", + "type": { + "kind": "integer", + "sign": "unsigned", + "width": 32 + }, + "visibility": "private" + }, + { + "name": "player", + "type": { + "kind": "field" + }, + "visibility": "private" + }, + { + "name": "card_as_field", + "type": { + "kind": "field" + }, + "visibility": "private" + } + ], + "returnTypes": [ + ... + ], + "bytecode": "...", + "verificationKey": "..." + }, + ... + ] +} + +``` + +### `name` +It is a simple string that matches the name that the contract developer used for this contract in noir. It's used for logs and errors. + +### `functions` +A contract is a collection of several functions that can be called. Each function has the following properties: + +#### `function.name` +A simple string that matches the name that the contract developer used for this function in noir. For logging and debugging purposes. + +#### `function.functionType` +The function type can have one of the following values: + +- Private: The function is ran and proved locally by the clients, and its bytecode not published to the network. +- Public: The function is ran and proved by the sequencer, and its bytecode is published to the network. +- Utility: The function is ran locally by the clients to generate digested information useful for the user. It cannot be called in a transaction. + +#### `function.isInternal` +The is internal property is a boolean that indicates whether the function is internal to the contract and cannot be called from outside. + +#### `function.parameters` +Each function can have multiple parameters that are arguments to execute the function. Parameters have a name, and type (like integers, strings, or complex types like arrays and structures). + +#### `function.returnTypes` +The return types property defines the types of values that the function returns after execution. + +#### `function.bytecode` +The bytecode is a string representing the compiled ACIR of the function, ready for execution on the network. + +#### `function.verificationKey` +The verification key is an optional property that contains the verification key of the function. This key is used to verify the proof of the function execution. + +### `debug` (Optional) +Although not significant for non-developer users, it is worth mentioning that there is a debug section in the contract artifact which helps contract developers to debug and test their contracts. This section mainly contains debug symbols and file maps that link back to the original source code. + +## Understanding Parameter and Return Types +To make the most of the functions, it's essential to understand the types of parameters and return values. Here are some common types you might encounter: + + - `field`: A basic type representing a field element in the finite field of the curve used in the Aztec protocol. + - `boolean`: A simple true/false value. + - `integer`: Represents whole numbers. It has attributes defining its sign (positive or negative) and width (the number of bits representing the integer). + - `array`: Represents a collection of elements, all of the same type. It has attributes defining its length and the type of elements it holds. + - `string`: Represents a sequence of characters with a specified length. + - `struct`: A complex type representing a structure with various fields, each having a specific type and name. + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/dependencies.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/dependencies.md new file mode 100644 index 000000000000..5559a52542cf --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/dependencies.md @@ -0,0 +1,55 @@ +--- +title: Importing Aztec.nr +tags: [contracts] +sidebar_position: 5 +--- + +On this page you will find information about Aztec.nr libraries and up-to-date paths for use in your `Nargo.toml`. + +## Aztec + +```toml +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/aztec" } +``` + +This is the core Aztec library that is required for every Aztec.nr smart contract. + +## Authwit + +```toml +authwit = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/authwit"} +``` + +This allows you to use authentication witnesses in your contract. Read a guide of how to use it [here](../../guides/smart_contracts/writing_contracts/authwit.md). + +## Address note + +```toml +address_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/address-note" } +``` + +This is a library for utilizing notes that hold addresses. Find it on [GitHub](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/aztec-nr/address-note/src). + +## Easy private state + +```toml +easy_private_state = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/easy-private-state" } +``` + +This is an abstraction library for using private variables like [`EasyPrivateUint` (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/6c20b45993ee9cbd319ab8351e2722e0c912f427/noir-projects/aztec-nr/easy-private-state/src/easy_private_state.nr#L17). + +## Protocol Types + +```toml +protocol_types = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/noir-protocol-circuits/crates/types"} +``` + +This library contains types that are used in the Aztec protocol. Find it on [GitHub](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-protocol-circuits/crates/types/src). + +## Value note + +```toml +value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/value-note" } +``` + +This is a library for a note that stores one arbitrary value. You can see an example of how it might be used in the [crowdfunding contract codealong tutorial](../../tutorials/codealong/contract_tutorials/crowdfunding_contract.md). diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/globals.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/globals.md new file mode 100644 index 000000000000..0f3a19d05541 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/globals.md @@ -0,0 +1,86 @@ +--- +title: Global Variables +description: Documentation of Aztec's Global Variables in the Public and Private Contexts +sidebar_position: 2 +--- + +# Global Variables + +For developers coming from solidity, this concept will be similar to how the global `block` variable exposes a series of block values. The idea is the same in Aztec. Developers can access a namespace of values made available in each function. + +`Aztec` has two execution environments, Private and Public. Each execution environment contains a different global variables object. + +## Private Global Variables + +```rust title="tx-context" showLineNumbers +pub struct TxContext { + pub chain_id: Field, + pub version: Field, + pub gas_settings: GasSettings, +} +``` +> Source code: noir-projects/noir-protocol-circuits/crates/types/src/transaction/tx_context.nr#L8-L14 + + +The private global variables are equal to the transaction context and contain: + +### Chain Id + +The chain id differs depending on which Aztec instance you are on ( NOT the Ethereum hardfork that the rollup is settling to ). On original deployment of the network, this value will be 1. + +```rust +context.chain_id(); +``` + +### Version + +The version number indicates which Aztec hardfork you are on. The Genesis block of the network will have the version number 1. + +```rust +context.version(); +``` + +### Gas Settings + +The gas limits set by the user for the transaction, the max fee per gas, and the inclusion fee. + +## Public Global Variables + +```rust title="global-variables" showLineNumbers +pub struct GlobalVariables { + pub chain_id: Field, + pub version: Field, + pub block_number: u32, + pub slot_number: Field, + pub timestamp: u64, + pub coinbase: EthAddress, + pub fee_recipient: AztecAddress, + pub gas_fees: GasFees, +} +``` +> Source code: noir-projects/noir-protocol-circuits/crates/types/src/abis/global_variables.nr#L9-L20 + + +The public global variables contain the values present in the `private global variables` described above, with the addition of: + +### Timestamp + +The timestamp is the unix timestamp in which the block has been executed. The value is provided by the block's proposer (therefore can have variance). This value will always increase. + +```rust +context.timestamp(); +``` + +### Block Number + +The block number is a sequential identifier that labels each individual block of the network. This value will be the block number of the block the accessing transaction is included in. +The block number of the genesis block will be 1, with the number increasing by 1 for every block after. + +```rust +context.block_number(); +``` + +:::info _Why do the available global variables differ per execution environment?_ +The global variables are constrained by the proving environment. In the case of public functions, they are executed on a sequencer that will know the timestamp and number of the next block ( as they are the block producer ). +In the case of private functions, we cannot be sure which block our transaction will be included in, hence we can not guarantee values for the timestamp or block number. +::: diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/macros.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/macros.md new file mode 100644 index 000000000000..9ea4d2b3cb43 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/macros.md @@ -0,0 +1,22 @@ +--- +title: Aztec macros +sidebar_position: 6 +tags: [contracts, functions] +--- + +## All Aztec macros + +In addition to the function macros in Noir, Aztec also has its own macros for specific functions. An Aztec contract function can be annotated with more than 1 macro. +It is also worth mentioning Noir's `unconstrained` function type [here (Noir docs page)](https://noir-lang.org/docs/noir/concepts/unconstrained/). + +- `#[aztec]` - Defines a contract, placed above `contract ContractName{}` +- `#[public]`, `#[private]` or `#[utility]` - Whether the function is to be executed from a public, private or utility context (see Further Reading) +- `#[initializer]` - If one or more functions are marked as an initializer, then one of them must be called before any non-initializer functions +- `#[noinitcheck]` - The function is able to be called before an initializer (if one exists) +- `#[view]` - Makes calls to the function static +- `#[internal]` - Function can only be called from within the contract +- `#[note]` - Creates a custom note +- `#[storage]` - Defines contract storage + +## Further reading +[How do Aztec macros work?](../../../aztec/smart_contracts/functions/function_transforms.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/_category_.json new file mode 100644 index 000000000000..3a04a4bcda85 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Portals", + "position": 4, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/data_structures.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/data_structures.md new file mode 100644 index 000000000000..e52232d95504 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/data_structures.md @@ -0,0 +1,114 @@ +--- +title: Data Structures +--- + +The `DataStructures` are structs that we are using throughout the message infrastructure and registry. + +**Links**: [Implementation (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/core/libraries/DataStructures.sol). + +## `L1Actor` + +An entity on L1, specifying the address and the chainId for the entity. Used when specifying sender/recipient with an entity that is on L1. + +```solidity title="l1_actor" showLineNumbers +/** + * @notice Actor on L1. + * @param actor - The address of the actor + * @param chainId - The chainId of the actor + */ +struct L1Actor { + address actor; + uint256 chainId; +} +``` +> Source code: l1-contracts/src/core/libraries/DataStructures.sol#L11-L21 + + +| Name | Type | Description | +| -------------- | ------- | ----------- | +| `actor` | `address` | The L1 address of the actor | +| `chainId` | `uint256` | The chainId of the actor. Defines the blockchain that the actor lives on. | + + +## `L2Actor` + +An entity on L2, specifying the address and the version for the entity. Used when specifying sender/recipient with an entity that is on L2. + +```solidity title="l2_actor" showLineNumbers +/** + * @notice Actor on L2. + * @param actor - The aztec address of the actor + * @param version - Ahe Aztec instance the actor is on + */ +struct L2Actor { + bytes32 actor; + uint256 version; +} +``` +> Source code: l1-contracts/src/core/libraries/DataStructures.sol#L23-L33 + + +| Name | Type | Description | +| -------------- | ------- | ----------- | +| `actor` | `bytes32` | The aztec address of the actor. | +| `version` | `uint256` | The version of Aztec that the actor lives on. | + +## `L1ToL2Message` + +A message that is sent from L1 to L2. + +```solidity title="l1_to_l2_msg" showLineNumbers +/** + * @notice Struct containing a message from L1 to L2 + * @param sender - The sender of the message + * @param recipient - The recipient of the message + * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. + * @param secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2). + * @param index - Global leaf index on the L1 to L2 messages tree. + */ +struct L1ToL2Msg { + L1Actor sender; + L2Actor recipient; + bytes32 content; + bytes32 secretHash; + uint256 index; +} +``` +> Source code: l1-contracts/src/core/libraries/DataStructures.sol#L35-L51 + + +| Name | Type | Description | +| -------------- | ------- | ----------- | +| `sender` | `L1Actor` | The actor on L1 that is sending the message. | +| `recipient` | `L2Actor` | The actor on L2 that is to receive the message. | +| `content` | `field (~254 bits)` | The field element containing the content to be sent to L2. | +| `secretHash` | `field (~254 bits)` | The hash of a secret pre-image that must be known to consume the message on L2. Use [`computeSecretHash` (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec.js/src/utils/secrets.ts) to compute it from a secret. | + +## `L2ToL1Message` + +A message that is sent from L2 to L1. + +```solidity title="l2_to_l1_msg" showLineNumbers +/** + * @notice Struct containing a message from L2 to L1 + * @param sender - The sender of the message + * @param recipient - The recipient of the message + * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. + * @dev Not to be confused with L2ToL1Message in Noir circuits + */ +struct L2ToL1Msg { + DataStructures.L2Actor sender; + DataStructures.L1Actor recipient; + bytes32 content; +} +``` +> Source code: l1-contracts/src/core/libraries/DataStructures.sol#L53-L66 + + +| Name | Type | Description | +| -------------- | ------- | ----------- | +| `sender` | `L2Actor` | The actor on L2 that is sending the message. | +| `recipient` | `L1Actor` | The actor on L1 that is to receive the message. | +| `content` | `field (~254 bits)` | The field element containing the content to be consumed by the portal on L1. | + + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/inbox.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/inbox.md new file mode 100644 index 000000000000..73d25a9906f5 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/inbox.md @@ -0,0 +1,72 @@ +--- +title: Inbox +tags: [portals, contracts] +--- + +The `Inbox` is a contract deployed on L1 that handles message passing from L1 to the rollup (L2) + +**Links**: [Interface (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol), [Implementation (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/core/messagebridge/Inbox.sol). + +## `sendL2Message()` + +Sends a message from L1 to L2. + +```solidity title="send_l1_to_l2_message" showLineNumbers +/** + * @notice Inserts a new message into the Inbox + * @dev Emits `MessageSent` with data for easy access by the sequencer + * @param _recipient - The recipient of the message + * @param _content - The content of the message (application specific) + * @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2) + * @return The key of the message in the set and its leaf index in the tree + */ +function sendL2Message( + DataStructures.L2Actor memory _recipient, + bytes32 _content, + bytes32 _secretHash +) external returns (bytes32, uint256); +``` +> Source code: l1-contracts/src/core/interfaces/messagebridge/IInbox.sol#L35-L49 + + + +| Name | Type | Description | +| -------------- | ------- | ----------- | +| Recipient | `L2Actor` | The recipient of the message. This **MUST** match the rollup version and an Aztec contract that is **attached** to the contract making this call. If the recipient is not attached to the caller, the message cannot be consumed by it. | +| Content | `field` (~254 bits) | The content of the message. This is the data that will be passed to the recipient. The content is limited to be a single field for rollup purposes. If the content is small enough it can just be passed along, otherwise it should be hashed and the hash passed along (you can use our [`Hash` (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/core/libraries/Hash.sol) utilities with `sha256ToField` functions) | +| Secret Hash | `field` (~254 bits) | A hash of a secret that is used when consuming the message on L2. Keep this preimage a secret to make the consumption private. To consume the message the caller must know the pre-image (the value that was hashed) - so make sure your app keeps track of the pre-images! Use [`computeSecretHash` (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec.js/src/utils/secrets.ts) to compute it from a secret. | +| ReturnValue | `bytes32` | The message hash, used as an identifier | + +#### Edge cases + +- Will revert with `Inbox__ActorTooLarge(bytes32 actor)` if the recipient is larger than the field size (~254 bits). +- Will revert with `Inbox__ContentTooLarge(bytes32 content)` if the content is larger than the field size (~254 bits). +- Will revert with `Inbox__SecretHashTooLarge(bytes32 secretHash)` if the secret hash is larger than the field size (~254 bits). + +## `consume()` + +Allows the `Rollup` to consume multiple messages in a single transaction. + +```solidity title="consume" showLineNumbers +/** + * @notice Consumes the current tree, and starts a new one if needed + * @dev Only callable by the rollup contract + * @dev In the first iteration we return empty tree root because first block's messages tree is always + * empty because there has to be a 1 block lag to prevent sequencer DOS attacks + * + * @param _toConsume - The block number to consume + * + * @return The root of the consumed tree + */ +function consume(uint256 _toConsume) external returns (bytes32); +``` +> Source code: l1-contracts/src/core/interfaces/messagebridge/IInbox.sol#L51-L63 + + +| Name | Type | Description | +| -------------- | ----------- | -------------------------- | +| ReturnValue | `bytes32` | Root of the consumed tree. | + +#### Edge cases + +- Will revert with `Inbox__Unauthorized()` if `msg.sender != ROLLUP` (rollup contract is sometimes referred to as state transitioner in the docs). diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/outbox.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/outbox.md new file mode 100644 index 000000000000..f9f96b8b2b7a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/outbox.md @@ -0,0 +1,112 @@ +--- +title: Outbox +tags: [portals, contracts] +--- + +The `Outbox` is a contract deployed on L1 that handles message passing from the rollup and to L1. + +**Links**: [Interface (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol), [Implementation (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/core/messagebridge/Outbox.sol). + +## `insert()` + +Inserts the root of a merkle tree containing all of the L2 to L1 messages in a block specified by _l2BlockNumber. + +```solidity title="outbox_insert" showLineNumbers +/** + * @notice Inserts the root of a merkle tree containing all of the L2 to L1 messages in + * a block specified by _l2BlockNumber. + * @dev Only callable by the rollup contract + * @dev Emits `RootAdded` upon inserting the root successfully + * @param _l2BlockNumber - The L2 Block Number in which the L2 to L1 messages reside + * @param _root - The merkle root of the tree where all the L2 to L1 messages are leaves + */ +function insert(uint256 _l2BlockNumber, bytes32 _root) external; +``` +> Source code: l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L22-L32 + + + +| Name | Type | Description | +| -------------- | ------- | ----------- | +| `_l2BlockNumber` | `uint256` | The L2 Block Number in which the L2 to L1 messages reside | +| `_root` | `bytes32` | The merkle root of the tree where all the L2 to L1 messages are leaves | +| `_minHeight` | `uint256` | The minimum height of the merkle tree that the root corresponds to | + +#### Edge cases + +- Will revert with `Outbox__Unauthorized()` if `msg.sender != ROLLUP_CONTRACT`. +- Will revert with `Errors.Outbox__RootAlreadySetAtBlock(uint256 l2BlockNumber)` if the root for the specific block has already been set. +- Will revert with `Errors.Outbox__InsertingInvalidRoot()` if the rollup is trying to insert bytes32(0) as the root. + +## `consume()` + +Allows a recipient to consume a message from the `Outbox`. + +```solidity title="outbox_consume" showLineNumbers +/** + * @notice Consumes an entry from the Outbox + * @dev Only useable by portals / recipients of messages + * @dev Emits `MessageConsumed` when consuming messages + * @param _message - The L2 to L1 message + * @param _l2BlockNumber - The block number specifying the block that contains the message we want to consume + * @param _leafIndex - The index inside the merkle tree where the message is located + * @param _path - The sibling path used to prove inclusion of the message, the _path length directly depends + * on the total amount of L2 to L1 messages in the block. i.e. the length of _path is equal to the depth of the + * L1 to L2 message tree. + */ +function consume( + DataStructures.L2ToL1Msg calldata _message, + uint256 _l2BlockNumber, + uint256 _leafIndex, + bytes32[] calldata _path +) external; +``` +> Source code: l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L34-L52 + + + +| Name | Type | Description | +| -------------- | ------- | ----------- | +| `_message` | `L2ToL1Msg` | The L2 to L1 message we want to consume | +| `_l2BlockNumber` | `uint256` | The block number specifying the block that contains the message we want to consume | +| `_leafIndex` | `uint256` | The index inside the merkle tree where the message is located | +| `_path` | `bytes32[]` | The sibling path used to prove inclusion of the message, the _path length directly depends | + +#### Edge cases + +- Will revert with `Outbox__InvalidRecipient(address expected, address actual);` if `msg.sender != _message.recipient.actor`. +- Will revert with `Outbox__InvalidChainId()` if `block.chainid != _message.recipient.chainId`. +- Will revert with `Outbox__NothingToConsumeAtBlock(uint256 l2BlockNumber)` if the root for the block has not been set yet. +- Will revert with `Outbox__AlreadyNullified(uint256 l2BlockNumber, uint256 leafIndex)` if the message at leafIndex for the block has already been consumed. +- Will revert with `Outbox__InvalidPathLength(uint256 expected, uint256 actual)` if the supplied height is less than the existing minimum height of the L2 to L1 message tree, or the supplied height is greater than the maximum (minimum height + log2(maximum messages)). +- Will revert with `MerkleLib__InvalidRoot(bytes32 expected, bytes32 actual, bytes32 leaf, uint256 leafIndex)` if unable to verify the message existence in the tree. It returns the message as a leaf, as well as the index of the leaf to expose more info about the error. + + +## `hasMessageBeenConsumedAtBlockAndIndex()` + +Checks to see if an index of the L2 to L1 message tree for a specific block has been consumed. + +```solidity title="outbox_has_message_been_consumed_at_block_and_index" showLineNumbers +/** + * @notice Checks to see if an index of the L2 to L1 message tree for a specific block has been consumed + * @dev - This function does not throw. Out-of-bounds access is considered valid, but will always return false + * @param _l2BlockNumber - The block number specifying the block that contains the index of the message we want to check + * @param _leafIndex - The index of the message inside the merkle tree + */ +function hasMessageBeenConsumedAtBlockAndIndex(uint256 _l2BlockNumber, uint256 _leafIndex) + external + view + returns (bool); +``` +> Source code: l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol#L54-L65 + + + +| Name | Type | Description | +| -------------- | ------- | ----------- | +| `_l2BlockNumber` | `uint256` | The block number specifying the block that contains the index of the message we want to check | +| `_leafIndex` | `uint256` | The index of the message inside the merkle tree | + +#### Edge cases + +- This function does not throw. Out-of-bounds access is considered valid, but will always return false. diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/registry.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/registry.md new file mode 100644 index 000000000000..a26a191e95a9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/portals/registry.md @@ -0,0 +1,50 @@ +--- +title: Registry +tags: [portals, contracts] +--- + +The registry is a contract deployed on L1, that contains addresses for the `Rollup`. It also keeps track of the different versions that have been deployed and let you query prior deployments easily. + +**Links**: [Interface (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/interfaces/IRegistry.sol), [Implementation (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/governance/Registry.sol). + +## `numberOfVersions()` + +Retrieves the number of versions that have been deployed. + +```solidity title="registry_number_of_versions" showLineNumbers +function numberOfVersions() external view returns (uint256); +``` +> Source code: l1-contracts/src/governance/interfaces/IRegistry.sol#L27-L29 + + +| Name | Description | +| ----------- | ---------------------------------------------- | +| ReturnValue | The number of versions that have been deployed | + +## `getCanonicalRollup()` + +Retrieves the current rollup contract. + +```solidity title="registry_get_canonical_rollup" showLineNumbers +function getCanonicalRollup() external view returns (IHaveVersion); +``` +> Source code: l1-contracts/src/governance/interfaces/IRegistry.sol#L19-L21 + + +| Name | Description | +| ----------- | ------------------ | +| ReturnValue | The current rollup | + +## `getRollup(uint256 _version)` + +Retrieves the rollup contract for a specfic version. + +```solidity title="registry_get_rollup" showLineNumbers +function getRollup(uint256 _chainId) external view returns (IHaveVersion); +``` +> Source code: l1-contracts/src/governance/interfaces/IRegistry.sol#L23-L25 + + +| Name | Description | +| ----------- | ------------------ | +| ReturnValue | The current rollup | diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/_category_.json new file mode 100644 index 000000000000..25edf7aa1c2e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 1, + "collapsible": true, + "collapsed": true, + "label": "Storage" +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/index.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/index.md new file mode 100644 index 000000000000..4d449c741423 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/index.md @@ -0,0 +1,99 @@ +--- +title: Storage +--- + +Smart contracts rely on storage, acting as the persistent memory on the blockchain. In Aztec, because of its hybrid, privacy-first architecture, the management of this storage is more complex than other blockchains like Ethereum. + +To learn how to define a storage struct, read [this guide](../../../guides/smart_contracts/writing_contracts/storage/index.md). +To learn more about storage slots, read [this explainer in the Concepts section](../../../../aztec/concepts/storage/index.md). + +You control this storage in Aztec using a struct annotated with `#[storage]`. This struct serves as the housing unit for all your smart contract's state variables - the data it needs to keep track of and maintain. + +These state variables come in two forms: [public](./public_state.md) and [private](./private_state.md). Public variables are visible to anyone, and private variables remain hidden within the contract. A state variable with both public and private components is said to be [shared](./shared_state.md). + +Aztec.nr has a few abstractions to help define the type of data your contract holds. These include PrivateMutable, PrivateImmutable, PublicMutable, PublicImmutable, PrivateSet, and SharedMutable. + +On this and the following pages in this section, you’ll learn: + +- How to manage a smart contract's storage structure +- The distinctions and applications of public and private state variables +- How to use PrivateMutable, PrivateImmutable, PrivateSet, PublicMutable, SharedMutable and Map +- An overview of 'notes' and the UTXO model +- Practical implications of Storage in real smart contracts + In an Aztec.nr contract, storage is to be defined as a single struct, that contains both public and private state variables. + +## The `Context` parameter + +Aztec contracts have three different modes of execution: private, public, and utility. How storage is accessed depends on the execution mode: for example, `PublicImmutable` can be read in all execution modes but only initialized in public, while `PrivateMutable` is entirely unavailable in public. + +Aztec.nr prevents developers from calling functions unavailable in the current execution mode via the `Context` variable that is injected into all contract functions. Its type indicates the current execution mode: + +- `&mut PrivateContext` for private execution +- `&mut PublicContext` for public execution +- `UtilityContext` for utility execution + +All state variables are generic over this `Context` type, and expose different methods in each execution mode. + +## Map + +A `map` is a state variable that "maps" a key to a value. It can be used with private or public storage variables. + +:::info +In Aztec.nr, keys are always `Field`s, or types that can be serialized as Fields, and values can be any type - even other maps. `Field`s are finite field elements, but you can think of them as integers. +::: + +It includes a `Context` to specify the private or public domain, a `storage_slot` to specify where in storage the map is stored, and a `start_var_constructor` which tells the map how it should operate on the underlying type. This includes how to serialize and deserialize the type, as well as how commitments and nullifiers are computed for the type if it's private. + +You can view the implementation in the Aztec.nr library [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/aztec-nr). + +You can have multiple `map`s in your contract that each have a different underlying note type, due to note type IDs. These are identifiers for each note type that are unique within a contract. + +#### As private storage + +When declaring a mapping in private storage, we have to specify which type of Note to use. In the example below, we are specifying that we want to use the `PrivateMutable` note which will hold `ValueNote` types. + +In the Storage struct: + +```rust +numbers: Map>, +``` + +#### Public Example + +When declaring a public mapping in Storage, we have to specify that the type is public by declaring it as `PublicState` instead of specifying a note type like with private storage above. + +```rust title="storage_minters" showLineNumbers +minters: Map, Context>, +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L69-L71 + + +### `at` + +When dealing with a Map, we can access the value at a given key using the `::at` method. This takes the key as an argument and returns the value at that key. + +This function behaves similarly for both private and public maps. An example could be if we have a map with `minters`, which is mapping addresses to a flag for whether they are allowed to mint tokens or not. + +```rust title="read_minter" showLineNumbers +assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L193-L195 + + +Above, we are specifying that we want to get the storage in the Map `at` the `msg_sender()`, read the value stored and check that `msg_sender()` is indeed a minter. Doing a similar operation in Solidity code would look like: + +```solidity +require(minters[msg.sender], "caller is not minter"); +``` + +## Further Reading + +- [Public State](./public_state.md) +- [Private State](./private_state.md) +- [Shared State](./shared_state.md) + +## Concepts mentioned + +- [State Model](../../../../aztec/concepts/storage/state_model.md) +- [Public-private execution](../../../../aztec/smart_contracts/functions/public_private_calls.md) +- [Function Contexts](../../../../aztec/smart_contracts/functions/context.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/private_state.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/private_state.md new file mode 100644 index 000000000000..279f3ebea372 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/private_state.md @@ -0,0 +1,575 @@ +--- +title: Private State +--- + +On this page we will look at how to manage private state in Aztec contracts. We will look at how to declare private state, how to read and write to it, and how to use it in your contracts. + +For a higher level overview of the state model in Aztec, see the [hybrid state model](../../../../aztec/concepts/storage/state_model.md) page. + +## Overview + +In contrast to public state, private state is persistent state that is **not** visible to the whole world. Depending on the logic of the smart contract, a private state variable's current value will only be known to one entity, or a closed group of entities. + +The value of a private state variable can either be shared via an encrypted log, or offchain via web2, or completely offline: it's up to the app developer. + +Aztec private state follows a [UTXO](https://en.wikipedia.org/wiki/Unspent_transaction_output)-based model. That is, a private state's current value is represented as one or many notes. + +To greatly simplify the experience of writing private state, Aztec.nr provides three different types of private state variable: + +- [PrivateMutable\](#privatemutablenotetype) +- [PrivateImmutable\](#privateimmutablenotetype) +- [PrivateSet\](#privatesetnotetype) + +These three structs abstract-away many of Aztec's protocol complexities, by providing intuitive methods to modify notes in the utxo tree in a privacy-preserving way. + +:::info +An app can also choose to emit data via unencrypted log, or to define a note whose data is easy to figure out, then the information is technically not private and could be visible to anyone. +::: + +### Notes + +Unlike public state variables, which can be arbitrary types, private state variables operate on `NoteType`. + +Notes are the fundamental elements in the private world. + +A note has to implement the following traits: + +```rust title="note_interfaces" showLineNumbers +pub trait NoteType { + /// Returns the unique identifier for the note type. This is typically used when processing note logs. + fn get_id() -> Field; +} + +pub trait NoteHash { + /// Returns the non-siloed note hash, i.e. the inner hash computed by the contract during private execution. Note + /// hashes are later siloed by contract address and hashed with note nonce by the kernels before being committed to + /// the state tree. + /// + /// This should be a commitment to the packed note, including the storage slot (for indexing) and some random + /// value (to prevent brute force trial-hashing attacks). + fn compute_note_hash(self, storage_slot: Field) -> Field; + + /// Returns the non-siloed nullifier (also called inner-nullifier), which will be later siloed by contract address + /// by the kernels before being committed to the state tree. + /// + /// This function MUST be called with the correct note hash for consumption! It will otherwise silently fail and + /// compute an incorrect value. The reason why we receive this as an argument instead of computing it ourselves + /// directly is because the caller will typically already have computed this note hash, and we can reuse that value + /// to reduce the total gate count of the circuit. + /// + /// This function receives the context since nullifier computation typically involves proving nullifying keys, and + /// we require the kernel's assistance to do this in order to prevent having to reveal private keys to application + /// circuits. + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field; + + /// Like `compute_nullifier`, except this variant is unconstrained: there are no guarantees on the returned value + /// being correct. Because of that it doesn't need to take a context (since it won't perform any kernel key + /// validation requests). + unconstrained fn compute_nullifier_unconstrained(self, note_hash_for_nullify: Field) -> Field; +} +``` +> Source code: noir-projects/aztec-nr/aztec/src/note/note_interface.nr#L5-L38 + + +The interplay between a private state variable and its notes can be confusing. Here's a summary to aid intuition: + +A private state variable (of type `PrivateMutable`, `PrivateImmutable` or `PrivateSet`) may be declared in storage and the purpose of private state variables is to manage notes (inserting their note hashes into the note hash tree, obtaining the notes, grouping the notes together using the storage slot etc.). + +:::info +Note that storage slots in private state are not real. +They do not point to a specific leaf in a merkle tree (as is the case in public). +Instead, in the case of notes they can be understood only as a tag that is used to associate notes with a private state variable. +The state variable storage slot can commonly represent an owner, as is the case when using the `at(...)` function of a `Map<>` with an `AztecAddress` as the key. +::: + +A private state variable points to one or many notes (depending on the type). The note(s) are all valid private state if the note(s) haven't yet been nullified. + +An `PrivateImmutable` will point to _one_ note over the lifetime of the contract. This note is a struct of information that is persisted forever. + +A `PrivateMutable` may point to _one_ note at a time. But since it's not "immutable", the note that it points to may be [replaced](#replace) by functions of the contract. The current value of a `PrivateMutable` is interpreted as the one note which has not-yet been nullified. The act of replacing a PrivateMutable's note is how a `PrivateMutable` state may be modified by functions. + +`PrivateMutable` is a useful type when declaring a private state variable which may only ever be modified by those who are privy to the current value of that state. + +A `PrivateSet` may point to _multiple_ notes at a time. The "current value" of a private state variable of type `PrivateSet` is some accumulation of all not-yet nullified notes which belong to the `PrivateSet`. + +:::note +The term "some accumulation" is intentionally vague. The interpretation of the "current value" of a `PrivateSet` must be expressed by the smart contract developer. A common use case for a `PrivateSet` is to represent the sum of a collection of values (in which case 'accumulation' is 'summation'). + +Think of a ZCash balance (or even a Bitcoin balance). The "current value" of a user's ZCash balance is the sum of all unspent (not-yet nullified) notes belonging to that user. To modify the "current value" of a `PrivateSet` state variable, is to [`insert`](#insert) new notes into the `PrivateSet`, or [`remove`](#remove) notes from that set. +::: + +Interestingly, if a developer requires a private state to be modifiable by users who _aren't_ privy to the value of that state, a `PrivateSet` is a very useful type. The `insert` method allows new notes to be added to the `PrivateSet` without knowing any of the other notes in the set! (Like posting an envelope into a post box, you don't know what else is in there!). + +## `PrivateMutable` + +PrivateMutable (formerly known as `Singleton`) is a private state variable that is unique in a way. When a PrivateMutable is initialized, a note is created to represent its value. And the way to update the value is to destroy the current note, and create a new one with the updated value. + +Like for public state, we define the struct to have context and a storage slot. You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr). + +An example of `PrivateMutable` usage in the account contracts is keeping track of public keys. The `PrivateMutable` is added to the `Storage` struct as follows: + +```rust title="storage-private-mutable-declaration" showLineNumbers +legendary_card: PrivateMutable, +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L30-L32 + + +### `new` + +As part of the initialization of the `Storage` struct, the `PrivateMutable` is created as follows at the specified storage slot. + +```rust title="start_vars_private_mutable" showLineNumbers +legendary_card: PrivateMutable::new(context, 3), +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L57-L59 + + +### `initialize` + +As mentioned, the PrivateMutable is initialized to create the first note and value. + +When this function is called, a nullifier of the storage slot is created, preventing this PrivateMutable from being initialized again. + +:::danger Privacy-Leak +Beware that because this nullifier is created only from the storage slot without randomness it leaks privacy. This means that it is possible for an external observer to determine when the note is nullified. + +For example, if the storage slot depends on the an address then it is possible to link the nullifier to the address. If the PrivateMutable is part of a `map` with an `AztecAddress` as the key then the nullifier will be linked to the address. +::: + +Unlike public states, which have a default initial value of `0` (or many zeros, in the case of a struct, array or map), a private state (of type `PrivateMutable`, `PrivateImmutable` or `PrivateSet`) does not have a default initial value. The `initialize` method (or `insert`, in the case of a `PrivateSet`) must be called. + +:::info +Extend on what happens if you try to use non-initialized state. +::: + +### `is_initialized` + +An unconstrained method to check whether the PrivateMutable has been initialized or not. It takes an optional owner and returns a boolean. You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr). + +```rust title="private_mutable_is_initialized" showLineNumbers +#[utility] +unconstrained fn is_legendary_initialized() -> bool { + storage.legendary_card.is_initialized() +} +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L139-L144 + + +### `replace` + +To update the value of a `PrivateMutable`, we can use the `replace` method. The method takes a new note as input, and replaces the current note with the new one. It emits a nullifier for the old value, and inserts the new note into the data tree. + +An example of this is seen in a example card game, where we create a new note (a `CardNote`) containing some new data, and replace the current note with it: + +```rust title="state_vars-PrivateMutableReplace" showLineNumbers +storage.legendary_card.replace(new_card).emit(encode_and_encrypt_note( + &mut context, + context.msg_sender(), + context.msg_sender(), +)); +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L130-L136 + + +If two people are trying to modify the PrivateMutable at the same time, only one will succeed as we don't allow duplicate nullifiers! Developers should put in place appropriate access controls to avoid race conditions (unless a race is intended!). + +### `get_note` + +This function allows us to get the note of a PrivateMutable, essentially reading the value. + +```rust title="state_vars-PrivateMutableGet" showLineNumbers +let card = storage.legendary_card.get_note().note; +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L124-L126 + + +#### Nullifying Note reads + +To ensure that a user's private execution always uses the latest value of a PrivateMutable, the `get_note` function will nullify the note that it is reading. This means that if two people are trying to use this function with the same note, only one will succeed (no duplicate nullifiers allowed). + +This also makes read operations indistinguishable from write operations and allows the sequencer to verifying correct execution without learning anything about the value of the note. + +### `view_note` + +Functionally similar to [`get_note`](#get_note), but executed in unconstrained functions and can be used by the wallet to fetch notes for use by front-ends etc. + +## `PrivateImmutable` + +`PrivateImmutable` (formerly known as `ImmutableSingleton`) represents a unique private state variable that, as the name suggests, is immutable. Once initialized, its value cannot be altered. You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr). + +### `new` + +As part of the initialization of the `Storage` struct, the `PrivateMutable` is created as follows, here at storage slot 1. + +```rust title="storage-private-immutable-declaration" showLineNumbers +private_immutable: PrivateImmutable, +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L38-L40 + + +### `initialize` + +When this function is invoked, it creates a nullifier for the storage slot, ensuring that the PrivateImmutable cannot be initialized again. + +:::danger Privacy-Leak +Beware that because this nullifier is created only from the storage slot without randomness it leaks privacy. This means that it is possible for an external observer to determine when the note is nullified. + +For example, if the storage slot depends on the an address then it is possible to link the nullifier to the address. If the PrivateImmutable is part of a `map` with an `AztecAddress` as the key then the nullifier will be linked to the address. +::: + +Set the value of an PrivateImmutable by calling the `initialize` method: + +```rust title="initialize-private-mutable" showLineNumbers +#[private] +fn initialize_private_immutable(randomness: Field, points: u8) { + let new_card = CardNote::new(points, randomness, context.msg_sender()); + + storage.private_immutable.initialize(new_card).emit(encode_and_encrypt_note( + &mut context, + context.msg_sender(), + context.msg_sender(), + )); +} +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L99-L110 + + +Once initialized, an PrivateImmutable's value remains unchangeable. This method can only be called once. + +### `is_initialized` + +An unconstrained method to check if the PrivateImmutable has been initialized. Takes an optional owner and returns a boolean. You can find the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr). + +### `get_note` + +Similar to the `PrivateMutable`, we can use the `get_note` method to read the value of an PrivateImmutable. + +Use this method to retrieve the value of an initialized PrivateImmutable. + +```rust title="get_note-private-immutable" showLineNumbers +#[private] +fn get_imm_card() -> CardNote { + storage.private_immutable.get_note() +} +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L146-L151 + + +Unlike a `PrivateMutable`, the `get_note` function for an PrivateImmutable doesn't nullify the current note in the background. This means that multiple accounts can concurrently call this function to read the value. + +This function will throw if the `PrivateImmutable` hasn't been initialized. + +### `view_note` + +Functionally similar to `get_note`, but executed unconstrained and can be used by the wallet to fetch notes for use by front-ends etc. + +## `PrivateSet` + +`PrivateSet` is used for managing a collection of notes. All notes in a `PrivateSet` are of the same `NoteType`. But whether these notes all belong to one entity, or are accessible and editable by different entities, is up to the developer. The set is a collection of notes inserted into the data-tree, but notes are never removed from the tree itself, they are only nullified. + +You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr). + +And can be added to the `Storage` struct as follows. Here adding a set for a custom note. + +```rust title="storage-set-declaration" showLineNumbers +set: PrivateSet, +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L35-L37 + + +### `new` + +The `new` method tells the contract how to operate on the underlying storage. + +We can initialize the set as follows: + +```rust title="storage-set-init" showLineNumbers +set: PrivateSet::new(context, 5), +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L68-L70 + + +### `insert` + +Allows us to modify the storage by inserting a note into the `PrivateSet`. + +A hash of the note will be generated, and inserted into the note hash tree, allowing us to later use in contract interactions. Recall that the content of the note should be shared with the owner to allow them to use it, as mentioned this can be done via an encrypted log or offchain via web2, or completely offline. + +```rust title="insert" showLineNumbers +self.set.insert(addend_note).emit(encode_and_encrypt_note(self.context, owner, sender)); +``` +> Source code: noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr#L36-L38 + + +### `insert_from_public` + +The `insert_from_public` allow public function to insert notes into private storage. This is very useful when we want to support private function calls that have been initiated in public. + +The usage is similar to using the `insert` method with the difference that this one is called in public functions. + +### `pop_notes` + +This function pops (gets, removes and returns) the notes the account has access to based on the provided filter. + +The kernel circuits are constrained to a maximum number of notes this function can return at a time. Check [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr) and look for `MAX_NOTE_HASH_READ_REQUESTS_PER_CALL` for the up-to-date number. + +Because of this limit, we should always consider using the second argument `NoteGetterOptions` to limit the number of notes we need to read and constrain in our programs. This is quite important as every extra call increases the time used to prove the program and we don't want to spend more time than necessary. + +An example of such options is using the [filter_notes_min_sum (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/value-note/src/filter.nr) to get "enough" notes to cover a given value. Essentially, this function will return just enough notes to cover the amount specified such that we don't need to read all our notes. For users with a lot of notes, this becomes increasingly important. + +```rust title="pop_notes" showLineNumbers +let options = NoteGetterOptions::with_filter(filter_notes_min_sum, subtrahend as Field); +let notes = self.set.pop_notes(options); +``` +> Source code: noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr#L43-L46 + + +### `get_notes` + +This function has the same behavior as `pop_notes` above but it does not delete the notes. + +### `remove` + +Will remove a note from the `PrivateSet` if it previously has been read from storage, e.g. you have fetched it through a `get_notes` call. This is useful when you want to remove a note that you have previously read from storage and do not have to read it again. + +Note that if you obtained the note you are about to remove via `get_notes` it's much better to use `pop_notes` as `pop_notes` results in significantly fewer constraints since it doesn't need to check that the note has been previously read, as it reads and deletes at once. + +### `view_notes` + +Functionally similar to [`get_notes`](#get_notes), but executed unconstrained and can be used by the wallet to fetch notes for use by front-ends etc. + +```rust title="view_notes" showLineNumbers +let mut options = NoteViewerOptions::new(); +let notes = set.view_notes(options.set_offset(offset)); +``` +> Source code: noir-projects/aztec-nr/value-note/src/balance_utils.nr#L15-L18 + + +There's also a limit on the maximum number of notes that can be returned in one go. To find the current limit, refer to [this file (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/aztec/src/note/constants.nr) and look for `MAX_NOTES_PER_PAGE`. + +The key distinction is that this method is unconstrained. It does not perform a check to verify if the notes actually exist, which is something the [`get_notes`](#get_notes) method does under the hood. Therefore, it should only be used in an unconstrained contract function. + +This function requires a `NoteViewerOptions`. The `NoteViewerOptions` is essentially similar to the [`NoteGetterOptions`](#notegetteroptions), except that it doesn't take a custom filter. + +## `NoteGetterOptions` + +`NoteGetterOptions` encapsulates a set of configurable options for filtering and retrieving a selection of notes from a data oracle. Developers can design instances of `NoteGetterOptions`, to determine how notes should be filtered and returned to the functions of their smart contracts. + +You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr). + +### `selects: BoundedVec, N>` + +`selects` is a collection of filtering criteria, specified by `Select { property_selector: PropertySelector, comparator: u8, value: Field }` structs. It instructs the data oracle to find notes whose serialized field (as specified by the `PropertySelector`) matches the provided `value`, according to the `comparator`. The PropertySelector is in turn specified as having an `index` (nth position of the selected field in the serialized note), an `offset` (byte offset inside the selected serialized field) and `length` (bytes to read of the field from the offset). These values are not expected to be manually computed, but instead specified by passing functions autogenerated from the note definition. + +### `sorts: BoundedVec, N>` + +`sorts` is a set of sorting instructions defined by `Sort { property_selector: PropertySelector, order: u2 }` structs. This directs the data oracle to sort the matching notes based on the value of the specified PropertySelector and in the indicated order. The value of order is **1** for _DESCENDING_ and **2** for _ASCENDING_. + +### `limit: u32` + +When the `limit` is set to a non-zero value, the data oracle will return a maximum of `limit` notes. + +### `offset: u32` + +This setting enables us to skip the first `offset` notes. It's particularly useful for pagination. + +### `preprocessor: fn ([Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], PREPROCESSOR_ARGS) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]` + +Developers have the option to provide a custom preprocessor. +This allows specific logic to be applied to notes that meet the criteria outlined above. +The preprocessor takes the notes returned from the oracle and `preprocessor_args` as its parameters. + +An important distinction from the filter function described below is that preprocessor is applied first and unlike filter it is applied in an unconstrained context. + +### `preprocessor_args: PREPROCESSOR_ARGS` + +`preprocessor_args` provides a means to furnish additional data or context to the custom preprocessor. + +### `filter: fn ([Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], FILTER_ARGS) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]` + +Just like preprocessor just applied in a constrained context (correct execution is proven) and applied after the preprocessor. + +### `filter_args: FILTER_ARGS` + +`filter_args` provides a means to furnish additional data or context to the custom filter. + +### `status: u2` + +`status` allows the caller to retrieve notes that have been nullified, which can be useful to prove historical data. Note that when querying for both active and nullified notes the caller cannot know if each note retrieved has or has not been nullified. + +### Methods + +Several methods are available on `NoteGetterOptions` to construct the options in a more readable manner: + +### `fn new() -> NoteGetterOptions` + +This function initializes a `NoteGetterOptions` that simply returns the maximum number of notes allowed in a call. + +### `fn with_filter(filter, filter_args) -> NoteGetterOptions` + +This function initializes a `NoteGetterOptions` with a [`filter`](#filter-fn-optionnote-max_note_hash_read_requests_per_call-filter_args---optionnote-max_note_hash_read_requests_per_call) and [`filter_args`](#filter_args-filter_args). + +### `.select` + +This method adds a [`Select`](#selects-boundedvecoptionselect-n) criterion to the options. + +### `.sort` + +This method adds a [`Sort`](#sorts-boundedvecoptionsort-n) criterion to the options. + +### `.set_limit` + +This method lets you set a limit for the maximum number of notes to be retrieved. + +### `.set_offset` + +This method sets the offset value, which determines where to start retrieving notes. + +### `.set_status` + +This method sets the status of notes to retrieve (active or nullified). + +### Examples + +#### Example 1 + +The following code snippet creates an instance of `NoteGetterOptions`, which has been configured to find the cards that belong to an account with nullifying key hash equal to `account_npk_m_hash`. The returned cards are sorted by their points in descending order, and the first `offset` cards with the highest points are skipped. + +```rust title="state_vars-NoteGetterOptionsSelectSortOffset" showLineNumbers +pub fn create_npk_card_getter_options( + account: AztecAddress, + offset: u32, +) -> NoteGetterOptions +where + CardNote: Packable, +{ + let mut options = NoteGetterOptions::new(); + options + .select(CardNote::properties().owner, Comparator.EQ, account) + .sort(CardNote::properties().points, SortOrder.DESC) + .set_offset(offset) +} +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr#L14-L28 + + +The first value of `.select` and `.sort` indicates the property of the note we're looking for. For this we use helper functions that are autogenerated from the note definition. `CardNote` that has the following fields: + +```rust title="state_vars-CardNote" showLineNumbers +// We derive the Serialize trait because this struct is returned from a contract function. When returned, +// the struct is serialized using the Serialize trait and added to a hasher via the `add_to_hasher` utility. +// We use a hash rather than the serialized struct itself to keep circuit inputs constant. +#[note] +#[derive(Eq, Serialize, Deserialize)] +pub struct CardNote { + points: u8, + randomness: Field, + owner: AztecAddress, +} +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/types/card_note.nr#L6-L17 + + +`CardNote::properties()` will return a struct with the values to pass for each field, which are related to their indices inside the `CardNote` struct, internal offset and length. + +In the example, `.select(CardNote::properties().npk_m_hash, Comparator.EQ, account_npk_m_hash)` matches notes which have the `npk_m_hash` field set to `account_npk_m_hash`. In this case we're using the equality comparator, but other operations exist in the `Comparator` utility struct. + +`.sort(0, SortOrder.DESC)` sorts the 0th field of `CardNote`, which is `points`, in descending order. + +There can be as many conditions as the number of fields a note type has. The following example finds cards whose fields match the three given values: + +```rust title="state_vars-NoteGetterOptionsMultiSelects" showLineNumbers +pub fn create_exact_card_getter_options( + points: u8, + secret: Field, + account: AztecAddress, +) -> NoteGetterOptions +where + CardNote: Packable, +{ + let mut options = NoteGetterOptions::new(); + options + .select(CardNote::properties().points, Comparator.EQ, points as Field) + .select(CardNote::properties().randomness, Comparator.EQ, secret) + .select(CardNote::properties().owner, Comparator.EQ, account) +} +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr#L30-L45 + + +While `selects` lets us find notes with specific values, `filter` lets us find notes in a more dynamic way. The function below picks the cards whose points are at least `min_points`, although this now can be done by using the select function with a GTE comparator: + +```rust title="state_vars-OptionFilter" showLineNumbers +pub fn filter_min_points( + cards: [Option>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], + min_points: u8, +) -> [Option>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] { + let mut selected_cards = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; + let mut num_selected = 0; + for i in 0..cards.len() { + if cards[i].is_some() & cards[i].unwrap_unchecked().note.points >= min_points { + selected_cards[num_selected] = cards[i]; + num_selected += 1; + } + } + selected_cards +} +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr#L47-L62 + + +We can use it as a filter to further reduce the number of the final notes: + +```rust title="state_vars-NoteGetterOptionsFilter" showLineNumbers +pub fn create_cards_with_min_points_getter_options( + min_points: u8, +) -> NoteGetterOptions +where + CardNote: Packable, +{ + NoteGetterOptions::with_filter(filter_min_points, min_points).sort( + CardNote::properties().points, + SortOrder.ASC, + ) +} +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr#L64-L76 + + +One thing to remember is, `filter` will be applied on the notes after they are picked from the database, so it is more efficient to use select with comparators where possible. Another side effect of this is that it's possible that the actual notes we end up getting are fewer than the limit. + +The limit is `MAX_NOTE_HASH_READ_REQUESTS_PER_CALL` by default. But we can set it to any value **smaller** than that: + +```rust title="state_vars-NoteGetterOptionsPickOne" showLineNumbers +pub fn create_largest_card_getter_options() -> NoteGetterOptions +where + CardNote: Packable, +{ + let mut options = NoteGetterOptions::new(); + options.sort(CardNote::properties().points, SortOrder.DESC).set_limit(1) +} +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr#L78-L86 + + +#### Example 2 + +An example of how we can use a Comparator to select notes when calling a Noir contract from aztec.js is below. + +```typescript title="state_vars-NoteGetterOptionsComparatorExampleTs" showLineNumbers +contract.methods.read_note_values(Comparator.GTE, 5).simulate(), +``` +> Source code: yarn-project/end-to-end/src/e2e_note_getter.test.ts#L49-L51 + + +In this example, we use the above typescript code to invoke a call to our Noir contract below. This Noir contract function takes an input to match with, and a comparator to use when fetching and selecting notes from storage. + +```rust title="state_vars-NoteGetterOptionsComparatorExampleNoir" showLineNumbers +#[utility] +unconstrained fn read_note(comparator: u8, amount: Field) -> BoundedVec { + let mut options = NoteViewerOptions::new(); + storage.set.view_notes(options.select(CardNote::properties().points, comparator, amount)) +} +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L112-L118 + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/public_state.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/public_state.md new file mode 100644 index 000000000000..1cbda30b23ab --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/public_state.md @@ -0,0 +1,215 @@ +--- +title: Public State +--- + +On this page we will look at how to manage public state in Aztec contracts. We will look at how to declare public state, how to read and write to it, and how to use it in your contracts. + +For a higher level overview of the state model in Aztec, see the [state model](../../../../aztec/concepts/storage/state_model.md) concepts page. + +## `PublicMutable` + +The `PublicMutable` (formerly known as `PublicState`) struct is generic over the variable type `T`. The type _must_ implement Serialize and Deserialize traits, as specified here: + +```rust title="serialize" showLineNumbers +/// Trait for serializing Noir types into arrays of Fields. +/// +/// An implementation of the Serialize trait has to follow Noir's intrinsic serialization (each member of a struct +/// converted directly into one or more Fields without any packing or compression). This trait (and Deserialize) are +/// typically used to communicate between Noir and TypeScript (via oracles and function arguments). +/// +/// # On Following Noir's Intrinsic Serialization +/// When calling a Noir function from TypeScript (TS), first the function arguments are serialized into an array +/// of fields. This array is then included in the initial witness. Noir's intrinsic serialization is then used +/// to deserialize the arguments from the witness. When the same Noir function is called from Noir this Serialize trait +/// is used instead of the serialization in TS. For this reason we need to have a match between TS serialization, +/// Noir's intrinsic serialization and the implementation of this trait. If there is a mismatch, the function calls +/// fail with an arguments hash mismatch error message. +/// +/// # Type Parameters +/// * `N` - The length of the output Field array, known at compile time +/// +/// # Example +/// ``` +/// impl Serialize for str { +/// fn serialize(self) -> [Field; N] { +/// let bytes = self.as_bytes(); +/// let mut fields = [0; N]; +/// for i in 0..bytes.len() { +/// fields[i] = bytes[i] as Field; // Each byte gets its own Field +/// } +/// fields +/// } +/// } +/// ``` +#[derive_via(derive_serialize)] +pub trait Serialize { + fn serialize(self) -> [Field; N]; +} +``` +> Source code: noir-projects/noir-protocol-circuits/crates/types/src/traits.nr#L195-L230 + +```rust title="deserialize" showLineNumbers +/// Trait for deserializing Noir types from arrays of Fields. +/// +/// An implementation of the Deserialize trait has to follow Noir's intrinsic serialization (each member of a struct +/// converted directly into one or more Fields without any packing or compression). This trait is typically used when +/// deserializing return values from function calls in Noir. Since the same function could be called from TypeScript +/// (TS), in which case the TS deserialization would get used, we need to have a match between the 2. +/// +/// # Type Parameters +/// * `N` - The length of the input Field array, known at compile time +/// +/// # Example +/// ``` +/// impl Deserialize for str { +/// fn deserialize(fields: [Field; N]) -> Self { +/// str::from(fields.map(|value| value as u8)) +/// } +/// } +/// ``` +#[derive_via(derive_deserialize)] +pub trait Deserialize { + fn deserialize(fields: [Field; N]) -> Self; +} +``` +> Source code: noir-projects/noir-protocol-circuits/crates/types/src/traits.nr#L309-L332 + + +The struct contains a `storage_slot` which, similar to Ethereum, is used to figure out _where_ in storage the variable is located. Notice that while we don't have the exact same state model as EVM chains it will look similar from the contract developers point of view. + +You can find the details of `PublicMutable` in the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr). + +For a version of `PublicMutable` that can also be read in private, head to [`SharedMutable`](./shared_state.md#sharedmutable). + +:::info +An example using a larger struct can be found in the [lending example (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-contracts/contracts/app/lending_contract)'s use of an [`Asset` (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/noir-contracts/contracts/app/lending_contract/src/asset.nr). +::: + +### `new` + +When declaring the storage for `T` as a persistent public storage variable, we use the `PublicMutable::new()` constructor. As seen below, this takes the `storage_slot` and the `serialization_methods` as arguments along with the `Context`, which in this case is used to share interface with other structures. You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr). + +#### Single value example + +Say that we wish to add `admin` public state variable into our storage struct. In the struct we can define it as: + +```rust title="storage-leader-declaration" showLineNumbers +leader: PublicMutable, +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L27-L29 + + +#### Mapping example + +Say we want to have a group of `minters` that are able to mint assets in our contract, and we want them in public storage, because access control in private is quite cumbersome. In the `Storage` struct we can add it as follows: + +```rust title="storage-minters-declaration" showLineNumbers +minters: Map, Context>, +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L44-L46 + + +### `read` + +On the `PublicMutable` structs we have a `read` method to read the value at the location in storage. + +#### Reading from our `admin` example + +For our `admin` example from earlier, this could be used as follows to check that the stored value matches the `msg_sender()`. + +```rust title="read_admin" showLineNumbers +assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L181-L183 + + +#### Reading from our `minters` example + +As we saw in the Map earlier, a very similar operation can be done to perform a lookup in a map. + +```rust title="read_minter" showLineNumbers +assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L193-L195 + + +### `write` + +We have a `write` method on the `PublicMutable` struct that takes the value to write as an input and saves this in storage. It uses the serialization method to serialize the value which inserts (possibly multiple) values into storage. + +#### Writing to our `admin` example + +```rust title="write_admin" showLineNumbers +storage.admin.write(new_admin); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L104-L106 + + +#### Writing to our `minters` example + +```rust title="write_minter" showLineNumbers +storage.minters.at(minter).write(approve); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L184-L186 + + +--- + +## `PublicImmutable` + +`PublicImmutable` is a type that is initialized from public once, typically during a contract deployment, but which can later be read from public, private and utility execution contexts. This state variable is useful for stuff that you would usually have in `immutable` values in Solidity, e.g. this can be the name of a token or its number of decimals. + +Just like the `PublicMutable` it is generic over the variable type `T`. The type `MUST` implement the `Serialize` and `Deserialize` traits. + +```rust title="storage-public-immutable-declaration" showLineNumbers +public_immutable: PublicImmutable, +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L41-L43 + + +You can find the details of `PublicImmutable` in the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr). + +### `new` + +Is done exactly like the `PublicMutable` struct, but with the `PublicImmutable` struct. + +```rust title="storage-public-immutable-declaration" showLineNumbers +public_immutable: PublicImmutable, +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L41-L43 + + +### `initialize` + +This function sets the immutable value. It can only be called once. + +```rust title="initialize_decimals" showLineNumbers +storage.decimals.initialize(decimals); +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L94-L96 + + +:::warning +A `PublicImmutable`'s storage **must** only be set once via `initialize`. Attempting to override this by manually accessing the underlying storage slots breaks all properties of the data structure, rendering it useless. +::: + +```rust title="initialize_public_immutable" showLineNumbers +#[public] +fn initialize_public_immutable(points: u8) { + let mut new_leader = Leader { account: context.msg_sender(), points }; + storage.public_immutable.initialize(new_leader); +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L84-L89 + + +### `read` + +Returns the stored immutable value. This function is available in public, private and utility contexts. + +```rust title="read_public_immutable" showLineNumbers +#[utility] +unconstrained fn get_public_immutable() -> Leader { + storage.public_immutable.read() +``` +> Source code: noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr#L92-L96 + diff --git a/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/shared_state.md b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/shared_state.md new file mode 100644 index 000000000000..b30ff8218a36 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/reference/smart_contract_reference/storage/shared_state.md @@ -0,0 +1,125 @@ +--- +title: Shared State +--- + +This page covers an advanced type of state called shared state, which is public state that can also be read in private (and hence _shared_ by both domains). It is highly recommended that you're familiar with both [private](./private_state.md) and [public](./public_state.md) state before reading this page. + +## Overview and Motivation + +A typical example of shared state is some kind of system configuration, such as a protocol fee or access control permissions. These values are public (known by everyone) and mutable. Reading them in private however is tricky: private execution is always asynchronous and performed over _historical_ state, and hence one cannot easily prove that a given public value is current. + +A naive way to solve this is to enqueue a public call that will assert the current public value, but this leaks _which_ public value is being read, severely reducing privacy. Even if the value itself is already public, the fact that we're using it because we're interacting with some related contract is not. For example, we may leak that we're interacting with a certain DeFi protocol by reading its fee. + +An alternative approach is to create notes in public that are then nullified in private, but this introduces contention: only a single user may use the note and therefore read the state, since nullifying it will prevent all others from doing the same. In some schemes there's only one account that will read the state anyway, but this is not the general case. + +Shared state works around this by introducing **delays**: while public values are mutable, they cannot change _immediately_. Instead, a value change must be scheduled ahead of time, and some minimum amount of time must pass between the scheduling and the new value taking effect. This means that we can privately prove that a historical public value cannot possibly change before some point in the future (due to the minimum delay), and therefore that our transaction will be valid **as long as it gets included before this future time**. + +This results in the following key properties of shared state: + +- shared values can only be changed after a certain delay has passed, never immediately +- the scheduling of value changes is itself public, including both the new value and the time at which the change will take effect +- transactions that read shared state become invalid after some time if not included in a block + +## Privacy Considerations + +While shared state variables are much less leaky than the assertion in public approach, they do reveal some information to external observers by setting the `max_block_number` property of the transaction request. The impact of this can be mitigated with proper selection of the delay value and schedule times. + +### Choosing Delays + +The `max_block_number` transaction property will be set to a value close to the current block number plus the duration of the delay in blocks. The exact value depends on the historical block over which the private proof is constructed. For example, if the current block number is 100 and a shared state variable has a delay of 20 blocks, then transactions that read this value privately will set `max_block_number` to a value close to 120 (clients building proofs on older state will select a lower `max_block_number`). This implicitly leaks the duration of the delay. + +Applications using similar delays will therefore be part of the same privacy set. It is expected for social coordination to result in small set of predetermined delays that developers choose from depending on their needs, as an example a viable set might be: 12 hours (for time-sensitive operations, such as emergency mechanisms), 5 days (for middle-of-the-road operations) and 2 weeks (for operations that require lengthy public scrutiny). These delays can be changed during the contract lifetime as the application's needs evolve. + +Additionally, users might choose to coordinate and constrain their transactions to set `max_block_number` to a value lower than would be strictly needed by the applications they interact with (if any!) using some common delay, and by doing so prevent privacy leakage. + +### Choosing Epochs + +If a value change is scheduled in the near future, then transactions that access this shared state will be forced to set a lower `max_block_number` right before the value change. For example, if the current block number is 100 and a shared state variable with a delay of 20 blocks has a value change scheduled for block 105, then transactions that read this value privately will set `max_block_number` to 104. Since the blocks at which shared state values change are public, it might be deduced that transactions with a `max_block_number` value close to the current block number are reading some state variable with a changed scheduled at `max_block_number + 1`. + +Applications that schedule value changes at the same time will therefore be part of the same privacy set. It is expected for social coordination to result in ways to achieve this, e.g. by scheduling value changes so that they land on blocks that are multiples of some value - we call these epochs. + +There is a tradeoff between frequent and infrequent epochs: frequent epochs means more of them, and therefore fewer updates on each, shrinking the privacy set. But infrequent epochs result in the effective delay of value changes being potentially larger than desired - though an application can always choose to do an out-of-epoch update if needed. + +:::note +Shared state variables do not allow selection of the value change block number, but there are plans to make this configurable. +::: + +Note that wallets can also warn users that a value change will soon take place and that sending a transaction at that time might result in reduced privacy, allowing them to choose to wait until after the epoch. + +### Network Cooperation + +Even though only transactions that interact with shared state _need_ to set the `max_block_number` property, there is no reason why transactions that do not wouldn't also set this value. If indeed most applications converge on a small set of delays, then wallets could opt to select any of those to populate the `max_block_number` field, as if they were interacting with a shared state variable with that delay. + +This prevents the network-wide privacy set from being split between transactions that read shared state and those that don't, which is beneficial to everyone. + +## `SharedMutable` + +`SharedMutable` is a shared state variable for mutable state. It provides capabilities to read the same state both in private and public, and to schedule value changes after a delay. You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr). + +Unlike other state variables, `SharedMutable` receives not only a type parameter for the underlying datatype, but also a `DELAY` type parameter with the value change delay as a number of blocks. + +```rust title="shared_mutable_storage" showLineNumbers +authorized: SharedMutable, +``` +> Source code: noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L22-L24 + + +:::note +`SharedMutable` requires that the underlying type `T` implements both the `ToField` and `FromField` traits, meaning it must fit in a single `Field` value. There are plans to extend support by requiring instead an implementation of the `Serialize` and `Deserialize` traits, therefore allowing for multi-field variables, such as complex structs. +::: + +Since `SharedMutable` lives in public storage, by default its contents are zeroed-out. Intialization is performed by calling `schedule_value_change`, resulting in initialization itself being delayed. + +### `schedule_value_change` + +This is the means by which a `SharedMutable` variable mutates its contents. It schedules a value change for the variable at a future block after the `DELAY` has elapsed from the current block, at which point the scheduled value becomes the current value automatically and without any further action, both in public and in private. If a pending value change was scheduled but not yet effective (because insufficient blocks had elapsed), then the previous schedule value change is replaced with the new one and eliminated. There can only be one pending value change at a time. + +This function can only be called in public, typically after some access control check: + +```rust title="shared_mutable_schedule" showLineNumbers +#[public] +fn set_authorized(authorized: AztecAddress) { + assert_eq(storage.admin.read(), context.msg_sender(), "caller is not admin"); + storage.authorized.schedule_value_change(authorized); +``` +> Source code: noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L34-L39 + + +If one wishes to schedule a value change from private, simply enqueue a public call to a public `internal` contract function. Recall that **all scheduled value changes, including the new value and scheduled block are public**. + +:::warning +A `SharedMutable`'s storage **must** only be mutated via `schedule_value_change`. Attempting to override this by manually accessing the underlying storage slots breaks all properties of the data structure, rendering it useless. +::: + +### `get_current_value` + +Returns the current value in a public, private or utility execution context. Once a value change is scheduled via `schedule_value_change` and a number of blocks equal to the delay passes, this automatically returns the new value. + +```rust title="shared_mutable_get_current_public" showLineNumbers +storage.authorized.get_current_value() +``` +> Source code: noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L46-L48 + + +Calling this function in a private execution context will have a 1 block delay, as compared to calling in the public context. This is because calling `get_current_value` in private constructs a historical state proof, using the latest proven block, for the public value, so the "current value" in private execution will be delayed by 1 block when compared to what the public value is. + +Also, calling in private will set the `max_block_number` property of the transaction request, introducing a new validity condition to the entire transaction: it cannot be included in any block with a block number larger than `max_block_number`. This could [potentially leak some privacy](#privacy-considerations). + +```rust title="shared_mutable_get_current_private" showLineNumbers +let authorized = storage.authorized.get_current_value(); +``` +> Source code: noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L78-L80 + + +### `get_scheduled_value` + +Returns the last scheduled value change, along with the block number at which the scheduled value becomes the current value. This may either be a pending change, if the block number is in the future, or the last executed scheduled change if the block number is in the past (in which case there are no pending changes). + +```rust title="shared_mutable_get_scheduled_public" showLineNumbers +let (scheduled_value, _block_of_change): (AztecAddress, u32) = + storage.authorized.get_scheduled_value(); +``` +> Source code: noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr#L55-L58 + + +It is not possible to call this function in private: doing so would not be very useful at it cannot be asserted that a scheduled value change will not be immediately replaced if `shcedule_value_change` where to be called. diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/tutorials/_category_.json new file mode 100644 index 000000000000..02c7361fd8a5 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Tutorials", + "position": 1, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/_category_.json new file mode 100644 index 000000000000..3900e03c64d8 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Contract Tutorials", + "position": 1, + "collapsible": true, + "collapsed": true +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/counter_contract.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/counter_contract.md new file mode 100644 index 000000000000..7c4c1b791366 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/counter_contract.md @@ -0,0 +1,258 @@ +--- +title: Counter Contract +sidebar_position: 0 +--- + +import Image from "@theme/IdealImage"; + +In this guide, we will create our first Aztec.nr smart contract. We will build a simple private counter, where you can keep your own private counter - so no one knows what ID you are at or when you increment! This contract will get you started with the basic setup and syntax of Aztec.nr, but doesn't showcase all of the awesome stuff Aztec is capable of. + +This tutorial is compatible with the Aztec version `v0.88.0`. Install the correct version with `aztec-up -v 0.88.0`. Or if you'd like to use a different version, you can find the relevant tutorial by clicking the version dropdown at the top of the page. + +## Prerequisites + +- You have followed the [quickstart](../../../getting_started.md) +- Running Aztec Sandbox +- Installed [Noir LSP](../../../guides/local_env/installing_noir_lsp.md) (optional) + +## Set up a project + +Run this to create a new contract project: + +```bash +aztec-nargo new --contract counter +``` + +Your structure should look like this: + +```tree +. +|-counter +| |-src +| | |-main.nr +| |-Nargo.toml +``` + +The file `main.nr` will soon turn into our smart contract! + +Add the following dependencies to `Nargo.toml` under the autogenerated content: + +```toml +[dependencies] +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/aztec" } +value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/value-note"} +easy_private_state = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/easy-private-state"} +``` + +## Define the functions + +Go to `main.nr`, and replace the boilerplate code with this contract initialization: + +```rust +use dep::aztec::macros::aztec; + +#[aztec] +pub contract Counter { +} +``` + +This defines a contract called `Counter`. + +## Imports + +We need to define some imports. + +Write this inside your contract, ie inside these brackets: + +```rust +pub contract Counter { + // imports go here! +} +``` + +```rust title="imports" showLineNumbers +use aztec::macros::{functions::{initializer, private, public, utility}, storage::storage}; +use aztec::prelude::{AztecAddress, Map}; +use aztec::protocol_types::{ + abis::function_selector::FunctionSelector, + traits::{FromField, ToField}, +}; +use easy_private_state::EasyPrivateUint; +use value_note::{balance_utils, value_note::ValueNote}; +``` +> Source code: noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr#L8-L17 + + +- `use aztec::macros::{functions::{initializer, private, utility}, storage::storage};` + Imports the macros needed to define function types (`initializer`, `private`, and `utility`) and the `storage` macro for declaring contract storage structures. + +- `use aztec::prelude::{AztecAddress, Map};` + Brings in `AztecAddress` (used to identify accounts/contracts) and `Map` (used for creating state mappings, like our counters). + +- `use aztec::protocol_types::traits::{FromField, ToField};` + Provides traits for converting values to and from field elements, necessary for serialization and formatting inside Aztec. + +- `use easy_private_state::EasyPrivateUint;` + Imports a wrapper to manage private integer-like state variables (ie our counter), abstracting away notes. + +- `use value_note::{balance_utils, value_note::ValueNote};` + Brings in `ValueNote`, which represents a private value stored as a note, and `balance_utils`, which makes working with notes feel like working with simple balances. + +## Declare storage + +Add this below the imports. It declares the storage variables for our contract. We are going to store a mapping of values for each `AztecAddress`. + +```rust title="storage_struct" showLineNumbers +#[storage] +struct Storage { + counters: Map, Context>, +} +``` +> Source code: noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr#L19-L24 + + +## Keep the counter private + +Now we’ve got a mechanism for storing our private state, we can start using it to ensure the privacy of balances. + +Let’s create a constructor method to run on deployment that assigns an initial count to a specified owner. This function is called `initialize`, but behaves like a constructor. It is the `#[initializer]` decorator that specifies that this function behaves like a constructor. Write this: + +```rust title="constructor" showLineNumbers +#[initializer] +#[private] +// We can name our initializer anything we want as long as it's marked as aztec(initializer) +fn initialize(headstart: u64, owner: AztecAddress) { + let counters = storage.counters; + counters.at(owner).add(headstart, owner, context.msg_sender()); +} +``` +> Source code: noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr#L26-L34 + + +This function accesses the counts from storage. Then it assigns the passed initial counter to the `owner`'s counter privately using `at().add()`. + +We have annotated this and other functions with `#[private]` which are ABI macros so the compiler understands it will handle private inputs. + +## Incrementing our counter + +Now let’s implement the `increment` function we defined in the first step. + +```rust title="increment" showLineNumbers +#[private] +fn increment(owner: AztecAddress, sender: AztecAddress) { + unsafe { + dep::aztec::oracle::debug_log::debug_log_format( + "Incrementing counter for owner {0}", + [owner.to_field()], + ); + } + + Counter::at(context.this_address()).emit_in_public(12345).enqueue(&mut context); + + let counters = storage.counters; + counters.at(owner).add(1, owner, sender); +} +``` +> Source code: noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr#L36-L51 + + +The `increment` function works very similarly to the `constructor`, but instead directly adds 1 to the counter rather than passing in an initial count parameter. + +## Prevent double spending + +Because our counters are private, the network can't directly verify if a note was spent or not, which could lead to double-spending. To solve this, we use a nullifier - a unique identifier generated from each spent note and its nullifier key. You can learn more about nullifiers and private state in the [Learn section](../../../../aztec/index.md#private-and-public-state). + +## Getting a counter + +The last thing we need to implement is the function in order to retrieve a counter. In the `getCounter` we defined in the first step, write this: + +```rust title="get_counter" showLineNumbers +#[utility] +unconstrained fn get_counter(owner: AztecAddress) -> Field { + let counters = storage.counters; + balance_utils::get_balance(counters.at(owner).set) +} + +#[private] +fn increment_self_and_other( + other_counter: AztecAddress, + owner: AztecAddress, + sender: AztecAddress, +) { + unsafe { + dep::aztec::oracle::debug_log::debug_log_format( + "Incrementing counter for other {0}", + [owner.to_field()], + ); + } + + let counters = storage.counters; + counters.at(owner).add(1, owner, sender); + + Counter::at(context.this_address()).emit_in_public(9876).enqueue(&mut context); + Counter::at(other_counter).increment(owner, sender).call(&mut context); +} + +#[public] +fn emit_in_public(n: Field) { + context.push_note_hash(n); +} +``` +> Source code: noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr#L91-L123 + + +This is a `utility` function which is used to obtain the counter information outside of a transaction. We retrieve a reference to the `owner`'s `counter` from the `counters` Map. The `get_balance` function then operates on the owner's counter. This yields a private counter that only the private key owner can decrypt. + +## Compile + +Now we've written a simple Aztec.nr smart contract, we can compile it with `aztec-nargo`. + +### Compile the smart contract + +In `./counter/` directory, run this: + +```bash +aztec-nargo compile +``` + +This will compile the smart contract and create a `target` folder with a `.json` artifact inside. Do not worry if you see some warnings - Aztec is in fast development and it is likely you will see some irrelevant warning messages. + +After compiling, you can generate a typescript class using `aztec codegen` command. + +In the same directory, run this: + +```bash +aztec codegen -o src/artifacts target +``` + +You can now use the artifact and/or the TS class in your Aztec.js! + +## Investigate the `increment` function + +Private functions in Aztec contracts are executed client-side, to maintain privacy. Developers need to be mindful of how computationally expensive it is to generate client side proofs for the private functions in the contract they write. To help understand the cost, we can use the Aztec flamegraph tool. The tool takes a contract artifact and function and generates an SVG file that shows the constraint count of each step in the function. + +Run it for the `increment` function: + +```bash +SERVE=1 aztec flamegraph target/counter-Counter.json increment +``` + +`SERVE=1` will start a local server to view the flamegraph in the browser. You can also run it without this flag and open the generated SVG file in your browser manually. + + + +Note the total gate count at the bottom of the image. The image is interactive; you can hover over different parts of the graph to see the full function name of the execution step and its gate count. This tool also provides insight into the low-level operations that are performed in the private function. Don't worry about the details of the internals of the function right now, just be aware that the more complex the function, the more gates it will use and try out the flamegraph tool on your own functions. + +Read more about [profiling transactions with the flamegraph tool](../../../guides/smart_contracts/profiling_transactions.md). + +For more information about writing efficient private functions, see [this page](https://noir-lang.org/docs/explainers/explainer-writing-noir) of the Noir documentation. + +## Next Steps + +### Write a slightly more complex Aztec contract + +Follow the private voting contract tutorial on the [next page](./private_voting_contract.md). + +### Optional: Learn more about concepts mentioned here + +- [Functions and annotations like `#[private]`](../../../../aztec/smart_contracts/functions/function_transforms.md#private-functions) diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/crowdfunding_contract.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/crowdfunding_contract.md new file mode 100644 index 000000000000..260e9bff0b44 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/crowdfunding_contract.md @@ -0,0 +1,330 @@ +--- +title: "Crowdfunding contract" +sidebar_position: 3 +tags: [developers, tutorial, example] +--- + +# Write a donations contract + +In this tutorial we'll create two contracts related to crowdfunding: + +- A crowdfunding contract with two core components + - Fully private donations + - Verifiable withdrawals to the operator +- A reward contract for anyone else to anonymously reward donors + +Along the way you will: + +- Install Aztec developer tools +- Setup a new Noir contract project +- Add base Aztec dependencies +- Call between private and public contexts +- Wrap an address with its interface (token) +- Create custom private value notes + +This tutorial is compatible with the Aztec version `v0.88.0`. Install the correct version with `aztec-up -v 0.88.0`. Or if you'd like to use a different version, you can find the relevant tutorial by clicking the version dropdown at the top of the page. + +## Setup + +### Install tools + +Please ensure that you already have [Installed the Sandbox](../../../getting_started) + +### Create an Aztec project + +Use `aztec-nargo` in a terminal to create a new Aztec contract project named "crowdfunding": + +```sh +aztec-nargo new --contract crowdfunding +``` + +Inside the new `crowdfunding` directory you will have a base to implement the Aztec smart contract. + +Use `aztec-nargo --help` to see other commands. + +## Private donations + +1. An "Operator" begins a Crowdfunding campaign (contract), specifying: + +- an existing token address +- their account address +- a deadline timestamp + +2. Any address can donate (in private context) + +- private transfer token from sender to contract +- transaction receipts allow private claims via another contract + +3. Only the operator can withdraw from the fund + +### 1. Create a campaign + +#### Initialize + +Open the project in your preferred editor. If using VSCode and the LSP, you'll be able to select the `aztec-nargo` binary to use (instead of `nargo`). + +In `main.nr`, rename the contract from `Main`, to `Crowdfunding`. + +```rust +mod config; + +use dep::aztec::macros::aztec; + +#[aztec] +pub contract Crowdfunding { +} +``` + +Replace the example functions with an initializer that takes the required campaign info as parameters. Notice use of `#[aztec(...)]` macros inform the compiler that the function is a public initializer. + +```rust +#[public] +#[initializer] +fn init(donation_token: AztecAddress, operator: AztecAddress, deadline: u64) { + //... +} +``` + +#### Dependencies + +When you compile the contracts by running `aztec-nargo compile` in your project directory, you'll notice it cannot resolve `AztecAddress`. (Or hovering over in VSCode) + +```rust +#[public] +#[initializer] +// this-will-error +fn init(donation_token: AztecAddress, operator: AztecAddress, deadline: u64) { + //... +} +``` + +Add the required dependency by going to your project's `Nargo.toml` file, and adding `aztec` from the `aztec-nr` framework. It resides in the `aztec-packages` mono-repo: + +```rust +[dependencies] +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/aztec" } +uint_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/uint-note" } +router = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/noir-contracts/contracts/protocol/router_contract" } +``` + +This snippet also imports some of the other dependencies we will be using. + +A word about versions: + +- Choose the aztec packages version to match your aztec sandbox version +- Check that your `compiler_version` in Nargo.toml is satisfied by your aztec compiler - `aztec-nargo -V` + +Inside the Crowdfunding contract definition, use the dependency that defines the address type `AztecAddress` (same syntax as Rust) + +```rust +use dep::aztec::protocol_types::address::AztecAddress; +``` + +The `aztec::protocol_types` can be browsed [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/noir-protocol-circuits/crates/types/src). And like rust dependencies, the relative path inside the dependency corresponds to `address::AztecAddress`. + +This contract uses another file called `config.nr`. Create this in the same directory as `main.nr` and paste this in: + +```rust title="config.nr" showLineNumbers +use dep::aztec::protocol_types::{address::AztecAddress, traits::Packable}; +use std::meta::derive; + +// PublicImmutable has constant read cost in private regardless of the size of what it stores, so we put all immutable +// values in a single struct +#[derive(Eq, Packable)] +pub struct Config { + pub donation_token: AztecAddress, // Token used for donations (e.g. DAI) + pub operator: AztecAddress, // Crowdfunding campaign operator + pub deadline: u64, // End of the crowdfunding campaign after which no more donations are accepted +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/config.nr#L1-L13 + + +#### Storage + +To retain the initializer parameters in the contract's Storage, we'll need to declare them in a preceding `Storage` struct in our `main.nr`: + +```rust title="storage" showLineNumbers +#[storage] +struct Storage { + config: PublicImmutable, + // Notes emitted to donors when they donate (can be used as proof to obtain rewards, eg in Claim contracts) + donation_receipts: PrivateSet, +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr#L34-L41 + + +Now complete the initializer by setting the storage variables with the parameters: + +```rust title="init" showLineNumbers +#[public] +#[initializer] +fn init(donation_token: AztecAddress, operator: AztecAddress, deadline: u64) { + storage.config.initialize(Config { donation_token, operator, deadline }); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr#L44-L55 + + +### 2. Taking private donations + +#### Checking campaign duration against the timestamp + +To check that the donation occurs before the campaign deadline, we must access the public `timestamp`. It is one of several public global variables. + +We read the deadline from public storage in private and use the router contract to assert that the current `timestamp` is before the deadline. + +```rust title="call-check-deadline" showLineNumbers +privately_check_timestamp(Comparator.LT, config.deadline, &mut context); +``` +> Source code: noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr#L64-L66 + + +We perform this check via the router contract to not reveal which contract is performing the check - this is achieved by calling a private function on the router contract which then enqueues a call to a public function on the router contract. The result is that `msg_sender` in the public call will then be the router contract. +Note that the privacy here is dependent upon what deadline value is chosen by the Crowdfunding contract deployer. +If it's unique to this contract, then there'll be a privacy leak regardless, as third parties will be able to observe a deadline check against the Crowdfunding deadline, and therefore infer that the associated transaction is interacting with it. + +Now conclude adding all dependencies to the `Crowdfunding` contract: + +```rust title="all-deps" showLineNumbers +use crate::config::Config; +use dep::aztec::{ + event::event_interface::emit_event_in_public_log, + macros::{ + events::event, + functions::{initializer, internal, private, public}, + storage::storage, + }, + messages::logs::note::encode_and_encrypt_note, + prelude::{AztecAddress, PrivateSet, PublicImmutable}, + utils::comparison::Comparator, +}; +use dep::uint_note::uint_note::UintNote; +use router::utils::privately_check_timestamp; +use token::Token; +``` +> Source code: noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr#L9-L25 + + +Like before, you can find these and other `aztec::protocol_types` [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/noir-protocol-circuits/crates/types/src). + +#### Interfacing with another contract + +The token being used for donations is stored simply as an `AztecAddress` (named `donation_token`). so to easily use it as a token, we let the compiler know that we want the address to have a Token interface. Here we will use a maintained example Token contract. + +Add this `Token` contract to Nargo.toml: + +``` +token = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/noir-contracts/contracts/app/token_contract" } +``` + +With the dependency already `use`d at the start of the contract, the token contract can be called to make the transfer from msg sender to this contract. + +#### Creating and storing a private receipt note + +The last thing to do is create a new value note and add it to the `donation_receipts`. So the full donation function is now + +```rust title="donate" showLineNumbers +#[private] +fn donate(amount: u128) { + let config = storage.config.read(); + + // 1) Check that the deadline has not passed --> we do that via the router contract to conceal which contract + // is performing the check. + privately_check_timestamp(Comparator.LT, config.deadline, &mut context); + + // 2) Transfer the donation tokens from donor to this contract + let donor = context.msg_sender(); + Token::at(config.donation_token) + .transfer_in_private(donor, context.this_address(), amount, 0) + .call(&mut context); + + // 3) Create a value note for the donor so that he can later on claim a rewards token in the Claim + // contract by proving that the hash of this note exists in the note hash tree. + let note = UintNote::new(amount, donor); + + storage.donation_receipts.insert(note).emit(encode_and_encrypt_note( + &mut context, + donor, + donor, + )); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr#L57-L86 + + +### 3. Operator withdrawals + +The remaining function to implement, `withdraw`, is reasonably straight-forward: + +1. make sure the address calling is the operator address +2. transfer tokens from the contract to the operator +3. reveal that an amount has been withdrawn to the operator + +The last point is achieved by emitting an unencrypted event log. + +Copy the last function into your Crowdfunding contract: + +```rust title="operator-withdrawals" showLineNumbers +// Withdraws balance to the operator. Requires that msg_sender() is the operator. +#[private] +fn withdraw(amount: u128) { + let config = storage.config.read(); + let operator_address = config.operator; + + // 1) Check that msg_sender() is the operator + assert(context.msg_sender() == operator_address, "Not an operator"); + + // 2) Transfer the donation tokens from this contract to the operator + Token::at(config.donation_token).transfer(operator_address, amount).call(&mut context); + // 3) Emit a public event so that anyone can audit how much the operator has withdrawn + Crowdfunding::at(context.this_address()) + ._publish_donation_receipts(amount, operator_address) + .enqueue(&mut context); +} + +#[public] +#[internal] +fn _publish_donation_receipts(amount: u128, to: AztecAddress) { + emit_event_in_public_log(WithdrawalProcessed { amount, who: to }, &mut context); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr#L88-L111 + + +This is emitting an event, which we will need to create. Paste this earlier in our contract after our `Storage` declaration: + +```rust title="withdrawal-processed-event" showLineNumbers +#[event] +struct WithdrawalProcessed { + who: AztecAddress, + amount: u128, +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr#L27-L33 + + +You should be able to compile successfully with `aztec-nargo compile`. + +**Congratulations,** you have just built a multi-contract project on Aztec! + +## Conclusion + +For comparison, the full Crowdfunding contract can be found [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/noir-contracts/contracts/app/crowdfunding_contract). + +If a new token wishes to honour donors with free tokens based on donation amounts, this is possible via the donation_receipts (a `PrivateSet`). +See [claim_contract (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/noir-contracts/contracts/claim_contract). + +## Next steps + +### Build an accounts contract + +Follow the account contract tutorial on the [next page](./write_accounts_contract.md) and learn more about account abstraction. + +### Optional: Learn more about concepts mentioned here + +- [Initializer functions](../../../guides/smart_contracts/writing_contracts/initializers.md) +- [Versions](../../../guides/local_env/versions-updating.md). +- [Authorizing actions](../../../../aztec/concepts/advanced/authwit.md) +- [Public logs](../../../guides/smart_contracts/writing_contracts/how_to_emit_event.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/nft_contract.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/nft_contract.md new file mode 100644 index 000000000000..fe01dad0f0ad --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/nft_contract.md @@ -0,0 +1,690 @@ +--- +title: "NFT contract" +sidebar_position: 5 +--- + +In this tutorial we will go through writing an L2 native NFT token contract +for the Aztec Network, using the Aztec.nr contract libraries. + +This tutorial is intended to help you get familiar with the Aztec.nr library, Aztec contract syntax and some of the underlying structure of the Aztec network. + +In this tutorial you will learn how to: + +- Write public functions that update public state +- Write private functions that update private state +- Implement access control on public and private functions +- Handle math operations safely +- Handle different private note types +- Pass data between private and public state + +This tutorial is compatible with the Aztec version `v0.88.0`. Install the correct version with `aztec-up -v 0.88.0`. Or if you'd like to use a different version, you can find the relevant tutorial by clicking the version dropdown at the top of the page. + +We are going to start with a blank project and fill in the token contract source code defined [here (GitHub Link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr), and explain what is being added as we go. + +## Requirements + +You will need to have `aztec-nargo` installed in order to compile Aztec.nr contracts. + +## Project setup + +Create a new project with: + +```bash +aztec-nargo new --contract nft_contract +``` + +Your file structure should look something like this: + +```tree +. +|--nft_contract +| |--src +| | |--main.nr +| |--Nargo.toml +``` + +Inside `Nargo.toml` paste the following: + +```toml +[dependencies] +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/aztec" } +authwit={ git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/authwit"} +compressed_string = {git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/compressed-string"} +``` + +We will be working within `main.nr` for the rest of the tutorial. + +### Execution contexts + +Before we go further, a quick note about execution contexts. + +Transactions are initiated in the private context (executed client-side), then move to the L2 public context (executed remotely by an Aztec sequencer), then to the Ethereum L1 context (executed by an Ethereum node). + +Step 1. Private Execution + +Users provide inputs and execute locally on their device for privacy reasons. Outputs of the private execution are commitment and nullifier updates, a proof of correct execution and any return data to pass to the public execution context. + +Step 2. Public Execution + +This happens remotely by the sequencer, which takes inputs from the private execution and runs the public code in the network virtual machine, similar to any other public blockchain. + +Step 3. Ethereum execution + +Aztec transactions can pass messages to Ethereum contracts through the rollup via the outbox. The data can be consumed by Ethereum contracts at a later time, but this is not part of the transaction flow for an Aztec transaction. The technical details of this are beyond the scope of this tutorial, but we will cover them in an upcoming piece. + +## How this will work + +Before writing the functions, let's go through them to see how this contract will work: + +### Initializer + +There is one `initializer` function in this contract, and it will be selected and executed once when the contract is deployed, similar to a constructor in Solidity. This is marked `public`, so the function logic will be transparent. + +### Public functions + +These are functions that have transparent logic, will execute in a publicly verifiable context and can update public storage. + +- [`constructor`](#constructor) - executed when the contract instance is deployed +- [`set_admin`](#set_admin) - updates the `admin` of the contract +- [`set_minter`](#set_minter) - adds a minter to the `minters` mapping +- [`mint`](#mint) - mints an NFT with a specified `token_id` to the recipient +- [`transfer_in_public`](#transfer_in_public) - publicly transfer the specified token +- [`finalize_transfer_to_private`](#finalize_transfer_to_private) - finalize the transfer of the NFT from public to private context by completing the [partial note](../../../../aztec/concepts/advanced/storage/partial_notes.md)(more on this below) + +#### Public `view` functions + +These functions are useful for getting contract information for use in other contracts, in the public context. + +- [`public_get_name`](#public_get_name) - returns name of the NFT contract +- [`public_get_symbol`](#public_get_symbol) - returns the symbols of the NFT contract +- [`get_admin`](#get_admin) - returns the `admin` account address +- [`is_minter`](#is_minter) - returns a boolean, indicating whether the provided address is a minter +- [`owner_of`](#owner_of) - returns the owner of the provided `token_id` + +### Private functions + +These are functions that have private logic and will be executed on user devices to maintain privacy. The only data that is submitted to the network is a proof of correct execution, new data commitments and nullifiers, so users will not reveal which contract they are interacting with or which function they are executing. The only information that will be revealed publicly is that someone executed a private transaction on Aztec. + +- [`transfer_to_private`](#transfer_to_private) - privately initiates the transfer of an NFT from the public context to the private context by creating a [partial note](../../../../aztec/concepts/advanced/storage/partial_notes.md) +- [`prepare_private_balance_increase`](#prepare_private_balance_increase) - creates a [partial note](../../../../aztec/concepts/advanced/storage/partial_notes.md) to transfer an NFT from the public context to the private context. +- [`cancel_authwit`](#cancel_authwit) - emits a nullifier to cancel a private authwit +- [`transfer_in_private`](#transfer_in_private) - transfers an NFT to another account, privately +- [`transfer_to_public`](#transfer_to_public) - transfers a NFT from private to public context + +#### Private `view` functions + +These functions are useful for getting contract information in another contract in the private context. + +- [`private_get_symbol`](#private_get_symbol) - returns the NFT contract symbol +- [`private_get_name`](#private_get_name) - returns the NFT contract name + +### Internal functions + +Internal functions are functions that can only be called by the contract itself. These can be used when the contract needs to call one of it's public functions from one of it's private functions. + +- [`finalize_transfer_to_private_unsafe`](#_finalize_transfer_to_private_unsafe) - finalizes a transfer from public to private state + +### Utility functions + +The contract contains a single [utility function](../../../../aztec/smart_contracts/functions/attributes.md#utility-functions-utility): + +- [`get_private_nfts`](#get_private_nfts) - Returns an array of token IDs owned by the passed `AztecAddress` in private and a flag indicating whether a page limit was reached. + +## Contract dependencies + +Before we can implement the functions, we need set up the contract storage, and before we do that we need to import the appropriate dependencies. + +:::info Copy required files + +We will be going over the code in `main.nr` [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/noir-contracts/contracts/app/nft_contract/src). If you are following along and want to compile `main.nr` yourself, you need to add the other files in the directory as they contain imports that are used in `main.nr`. + +::: + +Paste these imports: + +```rust +mod types; +mod test; + +use aztec::macros::aztec; + +// Minimal NFT implementation with `AuthWit` support that allows minting in public-only and transfers in both public +// and private. +#[aztec] +pub contract NFT { + use crate::types::nft_note::{NFTNote, PartialNFTNote}; + use authwit::auth::{ + assert_current_call_valid_authwit, assert_current_call_valid_authwit_public, + compute_authwit_nullifier, + }; + use aztec::{ + macros::{ + events::event, + functions::{initializer, internal, private, public, utility, view}, + storage::storage, + }, + messages::logs::note::encode_and_encrypt_note, + note::{constants::MAX_NOTES_PER_PAGE, note_interface::NoteProperties}, + prelude::{ + AztecAddress, Map, NoteGetterOptions, NoteViewerOptions, PrivateContext, PrivateSet, + PublicContext, PublicImmutable, PublicMutable, + }, + protocol_types::traits::ToField, + utils::comparison::Comparator, + }; + use compressed_string::FieldCompressedString; +} +``` + +We are importing: + +- `CompressedString` to hold the token symbol +- Types from `aztec::prelude` +- Types for storing note types + +### Types files + +We are also importing types from a `types.nr` file, which imports types from the `types` folder. You can view them [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/noir-contracts/contracts/app/nft_contract/src). + +:::note + +Private state in Aztec is all [UTXOs](../../../../aztec/concepts/storage/state_model.md). + +::: + +## Contract Storage + +Now that we have dependencies imported into our contract we can define the storage for the contract. + +Below the dependencies, paste the following Storage struct: + +```rust title="storage_struct" showLineNumbers +#[storage] +struct Storage { + // The symbol of the NFT + symbol: PublicImmutable, + // The name of the NFT + name: PublicImmutable, + // The admin of the contract + admin: PublicMutable, + // Addresses that can mint + minters: Map, Context>, + // Contains the NFTs owned by each address in private. + private_nfts: Map, Context>, + // A map from token ID to a boolean indicating if the NFT exists. + nft_exists: Map, Context>, + // A map from token ID to the public owner of the NFT. + public_owners: Map, Context>, +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L44-L62 + + +## Custom Notes + +The contract storage uses a [custom note](../../../guides/smart_contracts/writing_contracts/notes/implementing_a_note.md) implementation. Custom notes are useful for defining your own data types. You can think of a custom note as a "chunk" of private data, the entire thing is added, updated or nullified (deleted) together. This NFT note is very simple and stores only the owner and the `token_id` and uses `randomness` to hide its contents. + +Randomness is required because notes are stored as commitments (hashes) in the note hash tree. Without randomness, the contents of a note may be derived through brute force (e.g. without randomness, if you know my Aztec address, you may be able to figure out which note hash in the tree is mine by hashing my address with many potential `token_id`s). + +```rust title="nft_note" showLineNumbers +/// A private note representing a token id associated to an account. +#[custom_note] +#[derive(Eq, Serialize)] +pub struct NFTNote { + // The ordering of these fields is important given that it must: + // a) match that of NFTPartialNotePrivateContent, and + // b) have the public field at the end + // Correct ordering is checked by the tests in this module. + + /// The owner of the note, i.e. the account whose nullifier secret key is required to compute the nullifier. + owner: AztecAddress, + /// Random value, protects against note hash preimage attacks. + randomness: Field, + /// The ID of the token represented by this note. + token_id: Field, +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/types/nft_note.nr#L26-L43 + + +## Functions + +Copy and paste the body of each function into the appropriate place in your project if you are following along. + +### Constructor + +This function sets the admin and makes them a minter, and sets the name and symbol. + +```rust title="constructor" showLineNumbers +#[public] +#[initializer] +fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>) { + assert(!admin.is_zero(), "invalid admin"); + storage.admin.write(admin); + storage.minters.at(admin).write(true); + storage.name.initialize(FieldCompressedString::from_string(name)); + storage.symbol.initialize(FieldCompressedString::from_string(symbol)); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L64-L74 + + +### Public function implementations + +Public functions are declared with the `#[public]` macro above the function name. + +As described in the [execution contexts section above](#execution-contexts), public function logic and transaction information is transparent to the world. Public functions update public state, but can be used to finalize notes prepared in a private context ([partial notes flow](../../../../aztec/concepts/advanced/storage/partial_notes.md)). + +Storage is referenced as `storage.variable`. + +#### `set_admin` + +The function checks that the `msg_sender` is the `admin`. If not, the transaction will fail. If it is, the `new_admin` is saved as the `admin`. + +```rust title="set_admin" showLineNumbers +#[public] +fn set_admin(new_admin: AztecAddress) { + assert(storage.admin.read().eq(context.msg_sender()), "caller is not an admin"); + storage.admin.write(new_admin); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L76-L82 + + +#### `set_minter` + +This function allows the `admin` to add or a remove a `minter` from the public `minters` mapping. It checks that `msg_sender` is the `admin` and finally adds the `minter` to the `minters` mapping. + +```rust title="set_minter" showLineNumbers +#[public] +fn set_minter(minter: AztecAddress, approve: bool) { + assert(storage.admin.read().eq(context.msg_sender()), "caller is not an admin"); + storage.minters.at(minter).write(approve); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L84-L90 + + +#### `mint` + +This public function checks that the `token_id` is not 0 and does not already exist and the `msg_sender` is authorized to mint. Then it indicates that the `token_id` exists, which is useful for verifying its existence if it gets transferred to private, and updates the owner in the `public_owners` mapping. + +```rust title="mint" showLineNumbers +#[public] +fn mint(to: AztecAddress, token_id: Field) { + assert(token_id != 0, "zero token ID not supported"); + assert(storage.minters.at(context.msg_sender()).read(), "caller is not a minter"); + assert(storage.nft_exists.at(token_id).read() == false, "token already exists"); + + storage.nft_exists.at(token_id).write(true); + + storage.public_owners.at(token_id).write(to); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L92-L103 + + +#### `transfer_in_public` + +```rust title="transfer_in_public" showLineNumbers +#[public] +fn transfer_in_public( + from: AztecAddress, + to: AztecAddress, + token_id: Field, + authwit_nonce: Field, +) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit_public(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + + let public_owners_storage = storage.public_owners.at(token_id); + assert(public_owners_storage.read().eq(from), "invalid owner"); + + public_owners_storage.write(to); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L145-L164 + + +##### Authorizing token spends (via authwits) + +If the `msg_sender` is **NOT** the same as the account to debit from, the function checks that the account has authorized the `msg_sender` contract to debit tokens on its behalf. This check is done by computing the function selector that needs to be authorized, computing the hash of the message that the account contract has approved. This is a hash of the contract that is approved to spend (`context.msg_sender`), the token contract that can be spent from (`context.this_address()`), the `selector`, the account to spend from (`from`), the `amount` and a `nonce` to prevent multiple spends. This hash is passed to `assert_inner_hash_valid_authwit_public` to ensure that the Account Contract has approved tokens to be spent on it's behalf. + +If the `msg_sender` is the same as the account to debit from, the authorization check is bypassed and the function proceeds to update the public owner. + +#### `finalize_transfer_to_private` + +This public function finalizes a transfer that has been set up by a call to [`prepare_private_balance_increase`](#prepare_private_balance_increase) by reducing the public balance of the associated account and emitting the note for the intended recipient. + +```rust title="finalize_transfer_to_private" showLineNumbers +#[public] +fn finalize_transfer_to_private(token_id: Field, partial_note: PartialNFTNote) { + // Completer is the entity that can complete the partial note. In this case, it's the same as the account + // `from` from whose account the token is being transferred. + let from_and_completer = context.msg_sender(); + _finalize_transfer_to_private( + from_and_completer, + token_id, + partial_note, + &mut context, + storage, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L226-L240 + + +### Private function implementations + +Private functions are declared with the `#[private]` macro above the function name like so: + +```rust + #[private] + fn transfer_in_private( +``` + +As described in the [execution contexts section above](#execution-contexts), private function logic and transaction information is hidden from the world and is executed on user devices. Private functions update private state, but can pass data to the public execution context (e.g. see the [`transfer_to_public`](#transfer_to_public) function). + +Storage is referenced as `storage.variable`. + +#### `transfer_to_private` + +Transfers token with `token_id` from public balance of the sender to a private balance of `to`. Calls [`_prepare_private_balance_increase`](#prepare_private_balance_increase) to get the hiding point slot (a transient storage slot where we can keep the partial note) and then calls [`_finalize_transfer_to_private_unsafe`](#_finalize_transfer_to_private_unsafe) to finalize the transfer in the public context. + +```rust title="transfer_to_private" showLineNumbers +#[private] +fn transfer_to_private(to: AztecAddress, token_id: Field) { + let from = context.msg_sender(); + + let nft = NFT::at(context.this_address()); + + // We prepare the private balance increase. + let partial_note = _prepare_private_balance_increase(to, &mut context, storage); + + // At last we finalize the transfer. Usage of the `unsafe` method here is safe because we set the `from` + // function argument to a message sender, guaranteeing that he can transfer only his own NFTs. + nft._finalize_transfer_to_private_unsafe(from, token_id, partial_note).enqueue(&mut context); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L167-L181 + + +#### `prepare_private_balance_increase` + +This function prepares a [partial note](../../../../aztec/concepts/advanced/storage/partial_notes.md) to transfer an NFT from the public context to the private context. The caller specifies an `AztecAddress` that will receive the NFT in private storage. + +:::note + +This function calls `_prepare_private_balance_increase` which is marked as `#[contract_library_method]`, which means the compiler will inline the `_prepare_private_balance_increase` function. Click through to the source to see the implementation. + +::: + +```rust title="prepare_private_balance_increase" showLineNumbers +#[private] +fn prepare_private_balance_increase(to: AztecAddress) -> PartialNFTNote { + _prepare_private_balance_increase(to, &mut context, storage) +} + +/// This function exists separately from `prepare_private_balance_increase` solely as an optimization as it allows +/// us to have it inlined in the `transfer_to_private` function which results in one less kernel iteration. +/// +/// TODO(#9180): Consider adding macro support for functions callable both as an entrypoint and as an internal +/// function. +#[contract_library_method] +fn _prepare_private_balance_increase( + to: AztecAddress, + context: &mut PrivateContext, + storage: Storage<&mut PrivateContext>, +) -> PartialNFTNote { + let sender_and_completer = context.msg_sender(); + + // We setup a partial note with unpopulated/zero token id for 'to'. + let partial_note = NFTNote::partial( + to, + storage.private_nfts.at(to).storage_slot, + context, + to, + sender_and_completer, + sender_and_completer, + ); + + partial_note +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L185-L216 + + +#### `cancel_authwit` + +Cancels a private authwit by emitting the corresponding nullifier. + +```rust title="cancel_authwit" showLineNumbers +#[private] +fn cancel_authwit(inner_hash: Field) { + let on_behalf_of = context.msg_sender(); + let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); + context.push_nullifier(nullifier); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L287-L294 + + +#### `transfer_in_private` + +Transfers an NFT between two addresses in the private context. Uses [authwits](../../../../aztec/concepts/advanced/authwit.md) to allow contracts to transfer NFTs on behalf of other accounts. + +```rust title="transfer_in_private" showLineNumbers +#[private] +fn transfer_in_private( + from: AztecAddress, + to: AztecAddress, + token_id: Field, + authwit_nonce: Field, +) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + + let nfts = storage.private_nfts; + + let notes = nfts.at(from).pop_notes(NoteGetterOptions::new() + .select(NFTNote::properties().token_id, Comparator.EQ, token_id) + .set_limit(1)); + assert(notes.len() == 1, "NFT not found when transferring"); + + let new_note = NFTNote::new(token_id, to); + + nfts.at(to).insert(new_note).emit(encode_and_encrypt_note(&mut context, to, from)); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L296-L321 + + +#### `transfer_to_public` + +Transfers and NFT from private storage to public storage. The private call enqueues a call to [`_finish_transfer_to_public`](#_finish_transfer_to_public) which updates the public owner of the `token_id`. + +```rust title="transfer_to_public" showLineNumbers +#[private] +fn transfer_to_public( + from: AztecAddress, + to: AztecAddress, + token_id: Field, + authwit_nonce: Field, +) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + + let notes = storage.private_nfts.at(from).pop_notes(NoteGetterOptions::new() + .select(NFTNote::properties().token_id, Comparator.EQ, token_id) + .set_limit(1)); + assert(notes.len() == 1, "NFT not found when transferring to public"); + + NFT::at(context.this_address())._finish_transfer_to_public(to, token_id).enqueue( + &mut context, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L323-L346 + + +### Internal function implementations + +Internal functions are functions that can only be called by this contract. The following 3 functions are public functions that are called from the [private execution context](#execution-contexts). Marking these as `internal` ensures that only the desired private functions in this contract are able to call them. Private functions defer execution to public functions because private functions cannot update public state directly. + +#### `_finalize_transfer_to_private_unsafe` + +This function is labeled as unsafe because the sender is not enforced in this function, but it is safe because the sender is enforced in the execution of the private function that calls this function. + +```rust title="finalize_transfer_to_private_unsafe" showLineNumbers +/// This is a wrapper around `_finalize_transfer_to_private` placed here so that a call +/// to `_finalize_transfer_to_private` can be enqueued. Called unsafe as it does not check `from_and_completer` +/// (this has to be done in the calling function). +#[public] +#[internal] +fn _finalize_transfer_to_private_unsafe( + from_and_completer: AztecAddress, + token_id: Field, + partial_note: PartialNFTNote, +) { + _finalize_transfer_to_private( + from_and_completer, + token_id, + partial_note, + &mut context, + storage, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L242-L261 + + +#### `_finish_transfer_to_public` + +Updates the public owner of the `token_id` to the `to` address. + +```rust title="finish_transfer_to_public" showLineNumbers +#[public] +#[internal] +fn _finish_transfer_to_public(to: AztecAddress, token_id: Field) { + storage.public_owners.at(token_id).write(to); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L348-L354 + + +### View function implementations + +NFT implements the following `view` functions: + +#### `get_admin` + +A getter function for reading the public `admin` value. + +```rust title="admin" showLineNumbers +#[public] +#[view] +fn get_admin() -> Field { + storage.admin.read().to_field() +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L129-L135 + + +#### `is_minter` + +A getter function for checking the value of associated with a `minter` in the public `minters` mapping. + +```rust title="is_minter" showLineNumbers +#[public] +#[view] +fn is_minter(minter: AztecAddress) -> bool { + storage.minters.at(minter).read() +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L137-L143 + + +#### `owner_of` + +Returns the owner of the provided `token_id`. Reverts if the `token_id` does not exist. Returns the zero address if the `token_id` does not have a public owner. + +#### `public_get_name` + +Returns the name of the NFT contract in the public context. + +#### `public_get_symbol` + +Returns the symbol of the NFT contract in the public context. + +#### `private_get_name` + +Returns the name of the NFT contract in the private context. + +#### `private_get_symbol` + +Returns the symbol of the NFT contract in the private context. + +### Utility function implementations + +The NFT implements the following [utility](../../../../aztec/concepts/call_types.md#utility) function: + +#### `get_private_nfts` + +A getter function for checking the private balance of the provided Aztec account. Returns an array of token IDs owned by `owner` in private and a flag indicating whether a page limit was reached. + +```rust title="get_private_nfts" showLineNumbers +#[utility] +unconstrained fn get_private_nfts( + owner: AztecAddress, + page_index: u32, +) -> ([Field; MAX_NOTES_PER_PAGE], bool) { + let offset = page_index * MAX_NOTES_PER_PAGE; + let mut options = NoteViewerOptions::new(); + let notes = storage.private_nfts.at(owner).view_notes(options.set_offset(offset)); + + let mut owned_nft_ids = [0; MAX_NOTES_PER_PAGE]; + for i in 0..options.limit { + if i < notes.len() { + owned_nft_ids[i] = notes.get_unchecked(i).token_id; + } + } + + let page_limit_reached = notes.len() == options.limit; + (owned_nft_ids, page_limit_reached) +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr#L367-L387 + + +## Compiling + +Now that the contract is complete, you can compile it with `aztec-nargo`. See the [Sandbox reference page](../../../reference/environment_reference/index.md) for instructions on setting it up. + +Run the following command in the directory where your `Nargo.toml` file is located: + +```bash +aztec-nargo compile +``` + +Once your contract is compiled, optionally generate a typescript interface with the following command: + +```bash +aztec codegen target -o src/artifacts +``` + +### Optional: Dive deeper into this contract and concepts mentioned here + +- Review [the end to end tests (Github link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/end-to-end/src/e2e_nft.test.ts) for reference. +- [Nullifier tree](../../../../aztec/concepts/advanced/storage/indexed_merkle_tree.mdx) +- [Public / Private function calls](../../../../aztec/smart_contracts/functions/public_private_calls.md). +- [Contract Storage](../../../../aztec/concepts/storage/index.md) +- [Authwit](../../../../aztec/concepts/advanced/authwit.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/private_voting_contract.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/private_voting_contract.md new file mode 100644 index 000000000000..9ed60ca5b32e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/private_voting_contract.md @@ -0,0 +1,262 @@ +--- +title: "Private voting contract" +sidebar_position: 1 +--- + +import Image from '@theme/IdealImage'; + +# Writing a private voting smart contract in Aztec.nr + +In this tutorial we will go through writing a very simple private voting smart contract in Aztec.nr. You will learn about private functions, public functions, composability between them, state management and creatively using nullifiers to prevent people from voting twice! + +This tutorial is compatible with the Aztec version `v0.88.0`. Install the correct version with `aztec-up -v 0.88.0`. Or if you'd like to use a different version, you can find the relevant tutorial by clicking the version dropdown at the top of the page. + +We will build this: + + + +- The contract will be initialized with an admin, stored publicly +- A voter can vote privately, which will call a public function and update the votes publicly +- The admin can end the voting period, which is a public boolean + +To keep things simple, we won't create ballots or allow for delegate voting. + +## Prerequisites + +- You have followed the [quickstart](../../../getting_started.md) to install `aztec-nargo` and `aztec`. +- Running Aztec Sandbox + +## Set up a project + +First, create a new contract project with `aztec-nargo`. + +```bash +aztec-nargo new --contract private_voting +``` + +Your file structure should look something like this: + +```tree +. +| | |--private_voting +| | | |--src +| | | | |--main.nr +| | | |--Nargo.toml +``` + +The file `main.nr` will soon turn into our smart contract! + +We will need the Aztec library to create this contract. In your `Nargo.toml` you should see `[dependencies]` - paste this below it. + +```toml +[dependencies] +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/aztec" } +``` + +## Initiate the contract and define imports + +Go to `main.nr` and delete the sample code. Replace it with this contract initialization: + +```rust +use dep::aztec::macros::aztec; + +#[aztec] +pub contract EasyPrivateVoting { +} +``` + +This defines a contract called `Voter`. Everything will sit inside this block. + +Inside this, paste these imports: + +```rust title="imports" showLineNumbers +use dep::aztec::{ + keys::getters::get_public_keys, + macros::{functions::{initializer, internal, private, public, utility}, storage::storage}, +}; +use dep::aztec::prelude::{AztecAddress, Map, PublicImmutable, PublicMutable}; +use dep::aztec::protocol_types::traits::{Hash, ToField}; +``` +> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L8-L16 + + +We are using various utils within the Aztec `prelude` library: + +- `use dep::aztec::keys::getters::get_public_keys;` + Imports a helper to retrieve public keys associated with the caller, used for computing a secure nullifier during voting. + +- `use dep::aztec::macros::{functions::{initializer, internal, private, public, utility}, storage::storage};` + Brings in macros for defining different function types (`initializer`, `internal`, `private`, `public`, `utility`) and for declaring contract storage via `storage`. + +- `use dep::aztec::prelude::{AztecAddress, Map, PublicImmutable, PublicMutable};` + Imports: + + - `AztecAddress`: a type for account/contract addresses, + - `Map`: a key-value storage structure, + - `PublicMutable`: public state that can be updated, + - `PublicImmutable`: public state that is read-only after being set once. + +- `use dep::aztec::protocol_types::traits::{Hash, ToField};` + Provides the `Hash` and `ToField` traits, used for hashing values and converting them to a Field, used for nullifier creation and other computations. + +## Set up storage + +Under these imports, we need to set up our contract storage. +Define the storage struct like so: + +```rust title="storage_struct" showLineNumbers +#[storage] +struct Storage { + admin: PublicMutable, // admin can end vote + tally: Map, Context>, // we will store candidate as key and number of votes as value + vote_ended: PublicMutable, // vote_ended is boolean + active_at_block: PublicImmutable, // when people can start voting +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L17-L25 + + +In this contract, we will store three vars: + +1. `admin`, as an Aztec address held in public state +2. `tally`, as a map with key as the persona and value as the number (in Field) held in public state +3. `vote_ended`, as a boolean held in public state +4. `active_at_block` specifies which block people can start voting. This variable specifies the block at which people must use their nullifier secret key to vote. Because nullifier keys are rotatable, if this is not included the same account would be able to vote more than once. + +## Constructor + +The next step is to initialize the contract with a constructor. The constructor will take an address as a parameter and set the admin. + +```rust title="constructor" showLineNumbers +#[public] +#[initializer] +// annotation to mark function as a constructor +fn constructor(admin: AztecAddress) { + storage.admin.write(admin); + storage.vote_ended.write(false); + storage.active_at_block.initialize(context.block_number()); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L27-L36 + + +This function takes the admin argument and writes it to the storage. We are also using this function to set the `vote_ended` boolean as false in the same way. + +## Casting a vote privately + +For the sake of simplicity, we will have three requirements: + +1. Everyone with an Aztec account gets a vote +2. They can only vote once in this contract +3. Who they are is private, but their actual vote is not + +To ensure someone only votes once, we will create a nullifier as part of the function call. If they try to vote again, the function will revert as it creates the same nullifier again, which can't be added to the nullifier tree (as that indicates a double spend). + +Create a private function called `cast_vote`: + +```rust title="cast_vote" showLineNumbers +#[private] +// annotation to mark function as private and expose private context +fn cast_vote(candidate: Field) { + let msg_sender_npk_m_hash = get_public_keys(context.msg_sender()).npk_m.hash(); + + let secret = context.request_nsk_app(msg_sender_npk_m_hash); // get secret key of caller of function + let nullifier = std::hash::pedersen_hash([context.msg_sender().to_field(), secret]); // derive nullifier from sender and secret + context.push_nullifier(nullifier); + EasyPrivateVoting::at(context.this_address()).add_to_tally_public(candidate).enqueue( + &mut context, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L38-L51 + + +In this function, we do not create a nullifier with the address directly. This would leak privacy as it would be easy to reverse-engineer. We must add some randomness or some form of secret, like nullifier secrets. + +To do this, we make an oracle call to fetch the caller's secret key, hash it to create a nullifier, and push the nullifier to Aztec. + +After pushing the nullifier, we update the `tally` to reflect this vote. As we know from before, a private function cannot update public state directly, so we are calling a public function. + +Create this new public function like this: + +```rust title="add_to_tally_public" showLineNumbers +#[public] +#[internal] +fn add_to_tally_public(candidate: Field) { + assert(storage.vote_ended.read() == false, "Vote has ended"); // assert that vote has not ended + let new_tally = storage.tally.at(candidate).read() + 1; + storage.tally.at(candidate).write(new_tally); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L53-L61 + + +The first thing we do here is assert that the vote has not ended. + +`assert()` takes two arguments: the assertion, in this case that `storage.vote_ended` is not false, and the error thrown if the assertion fails. + +The code after the assertion will only run if the assertion is true. In this snippet, we read the current vote tally at the `candidate`, add 1 to it, and write this new number to the `candidate`. The `Field` element allows us to use `+` to add to an integer. + +## Getting the number of votes + +We will create a function that anyone can call that will return the number of votes at a given vote Id. Paste this in your contract: + +```rust title="get_vote" showLineNumbers +#[utility] +unconstrained fn get_vote(candidate: Field) -> Field { + storage.tally.at(candidate).read() +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L70-L75 + + +We set it as `utility` because we don't intend to call this as part of a transaction: we want to call it from our application code to e.g. display the result in a UI. + +## Allowing an admin to end a voting period + +To ensure that only an `admin` can end a voting period, we can use another `assert()` statement. + +Paste this function in your contract: + +```rust title="end_vote" showLineNumbers +#[public] +fn end_vote() { + assert(storage.admin.read().eq(context.msg_sender()), "Only admin can end votes"); // assert that caller is admin + storage.vote_ended.write(true); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L63-L69 + + +Here, we are asserting that the `msg_sender()` is equal to the `admin` stored in public state. + +## Compiling and codegen + +The easiest way to compile the contract is with `aztec-nargo`. Run the following command in the directory with your Nargo.toml file: + +```bash +aztec-nargo compile +``` + +This will create a new directory called `target` and a JSON artifact inside it. + +Use `aztec codegen` to generate the Typescript artifact for the contract: + +```bash +aztec codegen target --outdir src/artifacts +``` + +**Congratulations, you have written and compiled a private voting smart contract!** Once it is compiled you can deploy it to the sandbox! + +## Next steps + +### Learn how contracts can work together + +Follow the crowdfunding contracts tutorial on the [next page](./crowdfunding_contract.md). + +### Optional: Learn more about concepts mentioned here + +- [Utility functions](../../../../aztec/smart_contracts/functions/attributes.md#utility-functions-utility). +- [Oracles](../../../../aztec/smart_contracts/oracles/index.md) +- [Nullifier secrets](../../../../aztec/concepts/accounts/keys.md#nullifier-keys). +- [How to deploy a contract to the sandbox](../../../guides/js_apps/deploy_contract.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/token_contract.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/token_contract.md new file mode 100644 index 000000000000..f56330b5ffce --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/token_contract.md @@ -0,0 +1,868 @@ +--- +title: "Private & Public token contract" +draft: true +--- + +In this tutorial we will go through writing an L2 native token contract +for the Aztec Network, using the Aztec.nr contract libraries. + +This tutorial is intended to help you get familiar with the Aztec.nr library, Aztec contract syntax and some of the underlying structure of the Aztec network. + +In this tutorial you will learn how to: + +- Write public functions that update public state +- Write private functions that update private state +- Implement access control on public and private functions +- Handle math operations safely +- Handle different private note types +- Pass data between private and public state + +We are going to start with a blank project and fill in the token contract source code defined [here (GitHub Link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr), and explain what is being added as we go. + +This tutorial is compatible with the Aztec version `v0.88.0`. Install the correct version with `aztec-up -v 0.88.0`. Or if you'd like to use a different version, you can find the relevant tutorial by clicking the version dropdown at the top of the page. + +## Requirements + +You will need to have `aztec-nargo` installed in order to compile Aztec.nr contracts. + +## Project setup + +Create a new project with: + +```bash +aztec-nargo new --contract token_contract +``` + +Your file structure should look something like this: + +```tree +. +|--private_voting +| |--src +| | |--main.nr +| |--Nargo.toml +``` + +Inside `Nargo.toml` paste the following: + +```toml +[dependencies] +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/aztec" } +authwit={ git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/authwit"} +compressed_string = {git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/compressed-string"} +uint_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/uint-note" } +``` + +We will be working within `main.nr` for the rest of the tutorial. + +## How this will work + +Before writing the functions, let's go through them to see how this contract will work: + +### Initializer + +There is one `initializer` function in this contract, and it will be selected and executed once when the contract is deployed, similar to a constructor in Solidity. This is marked private, so the function logic will not be transparent. To execute public function logic in the constructor, this function will call `_initialize` (marked internal, more detail below). + +### Public functions + +These are functions that have transparent logic, will execute in a publicly verifiable context and can update public storage. + +- [`set_admin`](#set_admin) enables the admin to be updated +- [`set_minter](#set_minter)` enables accounts to be added / removed from the approved minter list +- [`mint_to_public`](#mint_to_public) enables tokens to be minted to the public balance of an account +- [`transfer_in_public`](#transfer_in_public) enables users to transfer tokens from one account's public balance to another account's public balance +- [`burn_public`](#burn_public) enables users to burn tokens +- [`finalize_mint_to_private`](#finalize_mint_to_private) finalizes a `prepare_private_balance_increase` call +- [`finalize_transfer_to_private`](#finalize_transfer_to_private) finalizes a `prepare_private_balance_increase` call + +### Private functions + +These are functions that have private logic and will be executed on user devices to maintain privacy. The only data that is submitted to the network is a proof of correct execution, new data commitments and nullifiers, so users will not reveal which contract they are interacting with or which function they are executing. The only information that will be revealed publicly is that someone executed a private transaction on Aztec. + +- [`transfer`](#transfer) enables an account to send tokens from their private balance to another account's private balance +- [`transfer_in_private`](#transfer_in_private) enables an account to send tokens from another account's private balance to another account's private balance +- [`transfer_to_private`](#transfer_to_private) transfers a specified `amount` from an accounts public balance to a designated recipient's private balance. This flow starts in private, but will be completed in public. +- [`transfer_to_public`](#transfer_to_public) transfers tokens from the private balance of another account, to a (potentially different account's) public balance +- [`mint_to_private`](#mint_to_private) enables an authorized minter to mint tokens to a specified address +- [`cancel_authwit`](#cancel_authwit) enables an account to cancel an authorization to spend tokens +- [`burn_private`](#burn_private) enables tokens to be burned privately +- [`setup_refund`](#setup_refund) allows users using a fee paying contract to receive unspent transaction fees +- [`prepare_private_balance_increase`](#prepare_private_balance_increase) is used to set up a [partial note](../../../../aztec/concepts/advanced/storage/partial_notes.md) to be completed in public + +#### Private `view` functions + +These functions provide an interface to allow other contracts to read state variables in private: + +- `private_get_name` +- `private_get_symbol` +- `private_get_decimals` + +### Internal functions + +Internal functions are functions that can only be called by the contract itself. These can be used when the contract needs to call one of it's public functions from one of it's private functions. + +- [`_increase_public_balance`](#_increase_public_balance) increases the public balance of an account when `transfer_to_public` is called +- [`_reduce_total_supply`](#_reduce_total_supply) reduces the total supply of tokens when a token is privately burned +- [`complete_refund`](#complete_refund) used in the fee payment flow. There is more detail on the [partial note](../../../../aztec/concepts/advanced/storage/partial_notes.md#complete_refund) page. +- [`_finalize_transfer_to_private_unsafe`](#_finalize_transfer_to_private_unsafe) is the public component for finalizing a transfer from a public balance to private balance. It is considered `unsafe` because `from` is not enforced in this function, but it is in enforced the private function that calls this one (so it's safe). +- [`_finalize_mint_to_private_unsafe`](#_finalize_mint_to_private_unsafe) finalizes a private mint. Like the function above, it is considered `unsafe` because `from` is not enforced in this function, but it is in enforced the private function that calls this one (so it's safe). + +To clarify, let's review some details of the Aztec transaction lifecycle, particularly how a transaction "moves through" these contexts. + +#### Execution contexts + +Transactions are initiated in the private context (executed client-side), then move to the L2 public context (executed remotely by an Aztec sequencer), then to the Ethereum L1 context (executed by an Ethereum node). + +Step 1. Private Execution + +Users provide inputs and execute locally on their device for privacy reasons. Outputs of the private execution are commitment and nullifier updates, a proof of correct execution and any return data to pass to the public execution context. + +Step 2. Public Execution + +This happens remotely by the sequencer, which takes inputs from the private execution and runs the public code in the network virtual machine, similar to any other public blockchain. + +Step 3. Ethereum execution + +Aztec transactions can pass messages to Ethereum contracts through the rollup via the outbox. The data can be consumed by Ethereum contracts at a later time, but this is not part of the transaction flow for an Aztec transaction. The technical details of this are beyond the scope of this tutorial, but we will cover them in an upcoming piece. + +## Contract dependencies + +Before we can implement the functions, we need set up the contract storage, and before we do that we need to import the appropriate dependencies. + +:::info Copy required files + +We will be going over the code in `main.nr` [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/noir-contracts/contracts/app/token_contract/src). If you are following along and want to compile `main.nr` yourself, you need to add the other files in the directory as they contain imports that are used in `main.nr`. + +::: + +Paste these imports: + +```rust +mod types; +mod test; + +use dep::aztec::macros::aztec; + +// Minimal token implementation that supports `AuthWit` accounts. +// The auth message follows a similar pattern to the cross-chain message and includes a designated caller. +// The designated caller is ALWAYS used here, and not based on a flag as cross-chain. +// message hash = H([caller, contract, selector, ...args]) +// To be read as `caller` calls function at `contract` defined by `selector` with `args` +// Including a nonce in the message hash ensures that the message can only be used once. +#[aztec] +pub contract Token { + // Libs + use std::ops::{Add, Sub}; + + use dep::compressed_string::FieldCompressedString; + + use dep::aztec::{ + context::{PrivateCallInterface, PrivateContext}, + event::event_interface::{emit_event_in_private_log, PrivateLogContent}, + macros::{ + events::event, + functions::{initializer, internal, private, public, utility, view}, + storage::storage, + }, + messages::logs::note::{encode_and_encrypt_note, encode_and_encrypt_note_unconstrained}, + prelude::{AztecAddress, Map, PublicContext, PublicImmutable, PublicMutable}, + }; + + use dep::uint_note::uint_note::{PartialUintNote, UintNote}; + use aztec::protocol_types::traits::ToField; + + use dep::authwit::auth::{ + assert_current_call_valid_authwit, assert_current_call_valid_authwit_public, + compute_authwit_nullifier, + }; + + use crate::types::balance_set::BalanceSet; +} +``` + +We are importing: + +- `CompressedString` to hold the token symbol +- Types from `aztec::prelude` +- Types for storing note types + +### Types files + +We are also importing types from a `types.nr` file, which imports types from the `types` folder. You can view them [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/noir-contracts/contracts/app/token_contract/src). + +:::note + +Private state in Aztec is all [UTXOs](../../../../aztec/concepts/storage/index.md). + +::: + +## Contract Storage + +Now that we have dependencies imported into our contract we can define the storage for the contract. + +Below the dependencies, paste the following Storage struct: + +```rust title="storage_struct" showLineNumbers +#[storage] +struct Storage { + admin: PublicMutable, + minters: Map, Context>, + balances: Map, Context>, + total_supply: PublicMutable, + public_balances: Map, Context>, + symbol: PublicImmutable, + name: PublicImmutable, + decimals: PublicImmutable, +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L63-L83 + + +Reading through the storage variables: + +- `admin` an Aztec address stored in public state. +- `minters` is a mapping of Aztec addresses in public state. This will store whether an account is an approved minter on the contract. +- `balances` is a mapping of private balances. Private balances are stored in a `PrivateSet` of `UintNote`s. The balance is the sum of all of an account's `UintNote`s. +- `total_supply` is an unsigned integer (max 128 bit value) stored in public state and represents the total number of tokens minted. +- `public_balances` is a mapping of Aztec addresses in public state and represents the publicly viewable balances of accounts. +- `symbol`, `name`, and `decimals` are similar in meaning to ERC20 tokens on Ethereum. + +## Functions + +Copy and paste the body of each function into the appropriate place in your project if you are following along. + +### Constructor + +This function sets the creator of the contract (passed as `msg_sender` from the constructor) as the admin and makes them a minter, and sets name, symbol, and decimals. + +```rust title="constructor" showLineNumbers +#[public] +#[initializer] +fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) { + assert(!admin.is_zero(), "invalid admin"); + storage.admin.write(admin); + storage.minters.at(admin).write(true); + storage.name.initialize(FieldCompressedString::from_string(name)); + storage.symbol.initialize(FieldCompressedString::from_string(symbol)); + storage.decimals.initialize(decimals); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L85-L98 + + +### Public function implementations + +Public functions are declared with the `#[public]` macro above the function name. + +As described in the [execution contexts section above](#execution-contexts), public function logic and transaction information is transparent to the world. Public functions update public state, but can be used to finalize notes prepared in a private context ([partial notes flow](../../../../aztec/concepts/advanced/storage/partial_notes.md)). + +Storage is referenced as `storage.variable`. + +#### `set_admin` + +After storage is initialized, the contract checks that the `msg_sender` is the `admin`. If not, the transaction will fail. If it is, the `new_admin` is saved as the `admin`. + +```rust title="set_admin" showLineNumbers +#[public] +fn set_admin(new_admin: AztecAddress) { + assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); + storage.admin.write(new_admin); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L100-L108 + + +#### `set_minter` + +This function allows the `admin` to add or a remove a `minter` from the public `minters` mapping. It checks that `msg_sender` is the `admin` and finally adds the `minter` to the `minters` mapping. + +```rust title="set_minter" showLineNumbers +#[public] +fn set_minter(minter: AztecAddress, approve: bool) { + assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); + storage.minters.at(minter).write(approve); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L178-L188 + + +#### `mint_to_public` + +This function allows an account approved in the public `minters` mapping to create new public tokens owned by the provided `to` address. + +First, storage is initialized. Then the function checks that the `msg_sender` is approved to mint in the `minters` mapping. If it is, a new `U128` value is created of the `amount` provided. The function reads the recipients public balance and then adds the amount to mint, saving the output as `new_balance`, then reads to total supply and adds the amount to mint, saving the output as `supply`. `new_balance` and `supply` are then written to storage. + +```rust title="mint_to_public" showLineNumbers +#[public] +fn mint_to_public(to: AztecAddress, amount: u128) { + assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); + let new_balance = storage.public_balances.at(to).read().add(amount); + let supply = storage.total_supply.read().add(amount); + storage.public_balances.at(to).write(new_balance); + storage.total_supply.write(supply); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L190-L201 + + +#### `transfer_in_public` + +This public function enables public transfers between Aztec accounts. The sender's public balance will be debited the specified `amount` and the recipient's public balances will be credited with that amount. + +##### Authorizing token spends + +If the `msg_sender` is **NOT** the same as the account to debit from, the function checks that the account has authorized the `msg_sender` contract to debit tokens on its behalf. This check is done by computing the function selector that needs to be authorized, computing the hash of the message that the account contract has approved. This is a hash of the contract that is approved to spend (`context.msg_sender`), the token contract that can be spent from (`context.this_address()`), the `selector`, the account to spend from (`from`), the `amount` and a `nonce` to prevent multiple spends. This hash is passed to `assert_inner_hash_valid_authwit_public` to ensure that the Account Contract has approved tokens to be spent on it's behalf. + +If the `msg_sender` is the same as the account to debit tokens from, the authorization check is bypassed and the function proceeds to update the account's `public_balance`. + +```rust title="transfer_in_public" showLineNumbers +#[public] +fn transfer_in_public( + from: AztecAddress, + to: AztecAddress, + amount: u128, + authwit_nonce: Field, +) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit_public(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + let from_balance = storage.public_balances.at(from).read().sub(amount); + storage.public_balances.at(from).write(from_balance); + let to_balance = storage.public_balances.at(to).read().add(amount); + storage.public_balances.at(to).write(to_balance); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L203-L221 + + +#### `burn_public` + +This public function enables public burning (destroying) of tokens from the sender's public balance. + +After storage is initialized, the [authorization flow specified above](#authorizing-token-spends) is checked. Then the sender's public balance and the `total_supply` are updated and saved to storage. + +```rust title="burn_public" showLineNumbers +#[public] +fn burn_public(from: AztecAddress, amount: u128, authwit_nonce: Field) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit_public(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + let from_balance = storage.public_balances.at(from).read().sub(amount); + storage.public_balances.at(from).write(from_balance); + let new_supply = storage.total_supply.read().sub(amount); + storage.total_supply.write(new_supply); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L223-L238 + + +#### `finalize_mint_to_private` + +This public function finalizes a transfer that has been set up by a call to `prepare_private_balance_increase` by reducing the public balance of the associated account and emitting the note for the intended recipient. + +```rust title="finalize_mint_to_private" showLineNumbers +/// Finalizes a mint of token `amount` to a private balance of `to`. The mint must be prepared by calling +/// `prepare_private_balance_increase` first and the resulting +/// `partial_note` must be passed as an argument to this function. +/// +/// Note: This function is only an optimization as it could be replaced by a combination of `mint_to_public` +/// and `finalize_transfer_to_private`. It is however used very commonly so it makes sense to optimize it +/// (e.g. used during token bridging, in AMM liquidity token etc.). +#[public] +fn finalize_mint_to_private(amount: u128, partial_note: PartialUintNote) { + // Completer is the entity that can complete the partial note. In this case, it's the same as the minter + // account. + let minter_and_completer = context.msg_sender(); + assert(storage.minters.at(minter_and_completer).read(), "caller is not minter"); + + _finalize_mint_to_private( + minter_and_completer, + amount, + partial_note, + &mut context, + storage, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L625-L648 + + +#### `finalize_transfer_to_private` + +Similar to `finalize_mint_to_private`, this public function finalizes a transfer that has been set up by a call to `prepare_private_balance_increase` by reducing the public balance of the associated account and emitting the note for the intended recipient. + +```rust title="finalize_transfer_to_private" showLineNumbers +/// Finalizes a transfer of token `amount` from public balance of `msg_sender` to a private balance of `to`. +/// The transfer must be prepared by calling `prepare_private_balance_increase` from `msg_sender` account and +/// the resulting `partial_note` must be passed as an argument to this function. +/// +/// Note that this contract does not protect against a `partial_note` being used multiple times and it is up to +/// the caller of this function to ensure that it doesn't happen. If the same `partial_note` is used multiple +/// times, the token `amount` would most likely get lost (the partial note log processing functionality would fail +/// to find the pending partial note when trying to complete it). +#[public] +fn finalize_transfer_to_private(amount: u128, partial_note: PartialUintNote) { + // Completer is the entity that can complete the partial note. In this case, it's the same as the account + // `from` from whose balance we're subtracting the `amount`. + let from_and_completer = context.msg_sender(); + _finalize_transfer_to_private( + from_and_completer, + amount, + partial_note, + &mut context, + storage, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L506-L528 + + +### Private function implementations + +Private functions are declared with the `#[private]` macro above the function name like so: + +```rust + #[private] + fn transfer_to_public( +``` + +As described in the [execution contexts section above](#execution-contexts), private function logic and transaction information is hidden from the world and is executed on user devices. Private functions update private state, but can pass data to the public execution context (e.g. see the [`transfer_to_public`](#transfer_to_public) function). + +Storage is referenced as `storage.variable`. + +#### `transfer_to_public` + +This private function enables transferring of private balance (`UintNote` stored in `balances`) to any Aztec account's `public_balance`. + +After initializing storage, the function checks that the `msg_sender` is authorized to spend tokens. See [the Authorizing token spends section](#authorizing-token-spends) above for more detail--the only difference being that `assert_inner_hash_valid_authwit` in the authwit check is modified to work specifically in the private context. After the authorization check, the sender's private balance is decreased using the `decrement` helper function for the `value_note` library. Then it stages a public function call on this contract ([`_increase_public_balance`](#_increase_public_balance)) to be executed in the [public execution phase](#execution-contexts) of transaction execution. `_increase_public_balance` is marked as an `internal` function, so can only be called by this token contract. + +The function returns `1` to indicate successful execution. + +```rust title="transfer_to_public" showLineNumbers +#[private] +fn transfer_to_public( + from: AztecAddress, + to: AztecAddress, + amount: u128, + authwit_nonce: Field, +) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + + storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note( + &mut context, + from, + from, + )); + Token::at(context.this_address())._increase_public_balance(to, amount).enqueue(&mut context); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L240-L261 + + +#### `transfer` + +This private function enables private token transfers between Aztec accounts. + +After initializing storage, the function checks that the `msg_sender` is authorized to spend tokens. See [the Authorizing token spends section](#authorizing-token-spends) above for more detail--the only difference being that `assert_valid_message_for` is modified to work specifically in the private context. After authorization, the function gets the current balances for the sender and recipient and decrements and increments them, respectively, using the `value_note` helper functions. + +```rust title="transfer" showLineNumbers +#[private] +fn transfer(to: AztecAddress, amount: u128) { + let from = context.msg_sender(); + + // We reduce `from`'s balance by amount by recursively removing notes over potentially multiple calls. This + // method keeps the gate count for each individual call low - reading too many notes at once could result in + // circuits in which proving is not feasible. + // Since the sum of the amounts in the notes we nullified was potentially larger than amount, we create a new + // note for `from` with the change amount, e.g. if `amount` is 10 and two notes are nullified with amounts 8 and + // 5, then the change will be 3 (since 8 + 5 - 10 = 3). + let change = subtract_balance( + &mut context, + storage, + from, + amount, + INITIAL_TRANSFER_CALL_MAX_NOTES, + ); + storage.balances.at(from).add(from, change).emit(encode_and_encrypt_note_unconstrained( + &mut context, + from, + from, + )); + storage.balances.at(to).add(to, amount).emit(encode_and_encrypt_note_unconstrained( + &mut context, + to, + from, + )); + + // We don't constrain encryption of the note log in `transfer` (unlike in `transfer_in_private`) because the transfer + // function is only designed to be used in situations where the event is not strictly necessary (e.g. payment to + // another person where the payment is considered to be successful when the other party successfully decrypts a + // note). + emit_event_in_private_log( + Transfer { from, to, amount }, + &mut context, + from, + to, + PrivateLogContent.NO_CONSTRAINTS, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L301-L344 + + +#### `transfer_in_private` + +This private function enables an account to transfer tokens on behalf of another account. The account that tokens are being debited from must have authorized the `msg_sender` to spend tokens on its behalf. + +```rust title="transfer_in_private" showLineNumbers +#[private] +fn transfer_in_private( + from: AztecAddress, + to: AztecAddress, + amount: u128, + authwit_nonce: Field, +) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + + storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note( + &mut context, + from, + from, + )); + storage.balances.at(to).add(to, amount).emit(encode_and_encrypt_note(&mut context, to, from)); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L408-L433 + + +#### `transfer_to_private` + +This function execution flow starts in the private context and is completed with a call to a public internal function. It enables an account to send tokens from its `public_balance` to a private balance of an arbitrary recipient. + +First a partial note is prepared then a call to the public, internal `_finalize_transfer_to_private_unsafe` is enqueued. The enqueued public call subtracts the `amount` from public balance of `msg_sender` and finalizes the partial note with the `amount`. + +```rust title="transfer_to_private" showLineNumbers +// Transfers token `amount` from public balance of message sender to a private balance of `to`. +#[private] +fn transfer_to_private(to: AztecAddress, amount: u128) { + // `from` is the owner of the public balance from which we'll subtract the `amount`. + let from = context.msg_sender(); + let token = Token::at(context.this_address()); + + // We prepare the private balance increase (the partial note). + let partial_note = _prepare_private_balance_increase(from, to, &mut context, storage); + + // At last we finalize the transfer. Usage of the `unsafe` method here is safe because we set the `from` + // function argument to a message sender, guaranteeing that he can transfer only his own tokens. + token._finalize_transfer_to_private_unsafe(from, amount, partial_note).enqueue(&mut context); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L452-L467 + + +#### `mint_to_private` + +This private function prepares a partial `UintNote` at the recipients storage slot in the contract and enqueues a public call to `_finalize_mint_to_private_unsafe`, which asserts that the `msg_sender` is an authorized minter and finalized the mint by incrementing the total supply and emitting the complete, encrypted `UintNote` to the intended recipient. Note that the `amount` and the minter (`from`) are public, but the recipient is private. + +```rust title="mint_to_private" showLineNumbers +/// Mints token `amount` to a private balance of `to`. Message sender has to have minter permissions (checked +/// in the enqueued call). +#[private] +fn mint_to_private( + // TODO(benesjan): This allows minter to set arbitrary `from`. That seems undesirable. Will nuke it in a followup PR. + from: AztecAddress, // sender of the tag + to: AztecAddress, + amount: u128, +) { + let token = Token::at(context.this_address()); + + // We prepare the partial note to which we'll "send" the minted amount. + let partial_note = _prepare_private_balance_increase(from, to, &mut context, storage); + + // At last we finalize the mint. Usage of the `unsafe` method here is safe because we set + // the `minter_and_completer` function argument to a message sender, guaranteeing that only a message sender + // with minter permissions can successfully execute the function. + token._finalize_mint_to_private_unsafe(context.msg_sender(), amount, partial_note).enqueue( + &mut context, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L601-L623 + + +#### `cancel_authwit` + +This private function allows a user to cancel an authwit that was previously granted. This is achieved by emitting the corresponding nullifier before it is used. + +```rust title="cancel_authwit" showLineNumbers +#[private] +fn cancel_authwit(inner_hash: Field) { + let on_behalf_of = context.msg_sender(); + let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); + context.push_nullifier(nullifier); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L399-L406 + + +#### `burn_private` + +This private function enables accounts to privately burn (destroy) tokens. + +After initializing storage, the function checks that the `msg_sender` is authorized to spend tokens. Then it gets the sender's current balance and decrements it. Finally it stages a public function call to [`_reduce_total_supply`](#_reduce_total_supply). + +```rust title="burn_private" showLineNumbers +#[private] +fn burn_private(from: AztecAddress, amount: u128, authwit_nonce: Field) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note( + &mut context, + from, + from, + )); + Token::at(context.this_address())._reduce_total_supply(amount).enqueue(&mut context); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L435-L450 + + +#### `prepare_private_balance_increase` + +TODO: update from `prepare_transfer_to_private` + +This private function prepares to transfer from a public balance to a private balance by setting up a partial note for the recipient. The function returns the `hiding_point_slot`. After this, the public [`finalize_transfer_to_private`](#finalize_transfer_to_private) must be called, passing the amount and the hiding point slot. + +```rust title="prepare_private_balance_increase" showLineNumbers +/// Prepares an increase of private balance of `to` (partial note). The increase needs to be finalized by calling +/// some of the finalization functions (`finalize_transfer_to_private`, `finalize_mint_to_private`) with the +/// returned partial note. +#[private] +fn prepare_private_balance_increase(to: AztecAddress, from: AztecAddress) -> PartialUintNote { + // ideally we'd not have `from` here, but we do need a `from` address to produce a tagging secret with `to`. + _prepare_private_balance_increase(from, to, &mut context, storage) +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L469-L478 + + +### Internal function implementations + +Internal functions are functions that can only be called by this contract. The following 3 functions are public functions that are called from the [private execution context](#execution-contexts). Marking these as `internal` ensures that only the desired private functions in this contract are able to call them. Private functions defer execution to public functions because private functions cannot update public state directly. + +#### `_increase_public_balance` + +This function is called from [`transfer_to_public`](#transfer_to_public). The account's private balance is decremented in `transfer_to_public` and the public balance is increased in this function. + +```rust title="increase_public_balance" showLineNumbers +/// TODO(#9180): Consider adding macro support for functions callable both as an entrypoint and as an internal +/// function. +#[public] +#[internal] +fn _increase_public_balance(to: AztecAddress, amount: u128) { + _increase_public_balance_inner(to, amount, storage); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L690-L698 + + +#### `_reduce_total_supply` + +This function is called from [`burn`](#burn). The account's private balance is decremented in `burn` and the public `total_supply` is reduced in this function. + +```rust title="reduce_total_supply" showLineNumbers +#[public] +#[internal] +fn _reduce_total_supply(amount: u128) { + // Only to be called from burn. + let new_supply = storage.total_supply.read().sub(amount); + storage.total_supply.write(new_supply); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L710-L718 + + +#### `_finalize_transfer_to_private_unsafe` + +This public internal function decrements the public balance of the `from` account and finalizes the partial note for the recipient, which is hidden in the `hiding_point_slot`. + +This function is called by the private function [`transfer_to_private`](#transfer_to_private) to finalize the transfer. The `transfer_to_private` enforces the `from` argument, which is why using it `unsafe` is okay. + +```rust title="finalize_transfer_to_private_unsafe" showLineNumbers +/// This is a wrapper around `_finalize_transfer_to_private` placed here so that a call +/// to `_finalize_transfer_to_private` can be enqueued. Called unsafe as it does not check `from_and_completer` +/// (this has to be done in the calling function). +#[public] +#[internal] +fn _finalize_transfer_to_private_unsafe( + from_and_completer: AztecAddress, + amount: u128, + partial_note: PartialUintNote, +) { + _finalize_transfer_to_private( + from_and_completer, + amount, + partial_note, + &mut context, + storage, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L561-L580 + + +#### `_finalize_mint_to_private_unsafe` + +Similar to `_finalize_transfer_to_private_unsafe`, this public internal function increments the private balance of the recipient by finalizing the partial note and emitting the encrypted note. It also increments the public total supply and ensures that the sender of the transaction is authorized to mint tokens on the contract. + +```rust title="finalize_mint_to_private_unsafe" showLineNumbers +/// This is a wrapper around `_finalize_mint_to_private` placed here so that a call +/// to `_finalize_mint_to_private` can be enqueued. Called unsafe as it does not check `minter_and_completer` (this +/// has to be done in the calling function). +#[public] +#[internal] +fn _finalize_mint_to_private_unsafe( + minter_and_completer: AztecAddress, + amount: u128, + partial_note: PartialUintNote, +) { + // We check the minter permissions as it was not done in `mint_to_private` function. + assert(storage.minters.at(minter_and_completer).read(), "caller is not minter"); + _finalize_mint_to_private( + minter_and_completer, + amount, + partial_note, + &mut context, + storage, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L650-L671 + + +### View function implementations + +View functions in Aztec are similar to `view` functions in Solidity in that they only return information from the contract storage or compute and return data without modifying contract storage. These functions are different from utility functions in that the return values are constrained by their definition in the contract. + +Public view calls that are part of a transaction will be executed by the sequencer when the transaction is being executed, so they are not private and will reveal information about the transaction. Private view calls can be safely used in private transactions for getting the same information. + +#### `admin` + +A getter function for reading the public `admin` value. + +```rust title="admin" showLineNumbers +#[public] +#[view] +fn get_admin() -> Field { + storage.admin.read().to_field() +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L146-L152 + + +#### `is_minter` + +A getter function for checking the value of associated with a `minter` in the public `minters` mapping. + +```rust title="is_minter" showLineNumbers +#[public] +#[view] +fn is_minter(minter: AztecAddress) -> bool { + storage.minters.at(minter).read() +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L154-L160 + + +#### `total_supply` + +A getter function for checking the token `total_supply`. + +```rust title="total_supply" showLineNumbers +#[public] +#[view] +fn total_supply() -> u128 { + storage.total_supply.read() +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L162-L168 + + +#### `balance_of_public` + +A getter function for checking the public balance of the provided Aztec account. + +```rust title="balance_of_public" showLineNumbers +#[public] +#[view] +fn balance_of_public(owner: AztecAddress) -> u128 { + storage.public_balances.at(owner).read() +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L170-L176 + + +### Utility function implementations + +[Utility](../../../../aztec/smart_contracts/functions/attributes.md#utility-functions-utility) functions can be used to get contract information, both private and public, from an application - they are not callable inside a transaction. + +#### `balance_of_private` + +A getter function for checking the private balance of the provided Aztec account. Note that the [Private Execution Environment (PXE) (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/yarn-project/pxe) must have `ivsk` ([incoming viewing secret key](../../../../aztec/concepts/accounts/keys.md#incoming-viewing-keys)) in order to decrypt the notes. + +```rust title="balance_of_private" showLineNumbers +#[utility] +pub(crate) unconstrained fn balance_of_private(owner: AztecAddress) -> u128 { + storage.balances.at(owner).balance_of() +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L720-L725 + + +## Compiling + +Now that the contract is complete, you can compile it with `aztec-nargo`. See the [Sandbox reference page](../../../reference/environment_reference/index.md) for instructions on setting it up. + +Run the following command in the directory where your `Nargo.toml` file is located: + +```bash +aztec-nargo compile +``` + +Once your contract is compiled, optionally generate a typescript interface with the following command: + +```bash +aztec codegen target -o src/artifacts +``` + +## Next Steps + +### Token Bridge Contract + +The [token bridge tutorial](../js_tutorials/token_bridge) is a great follow up to this one. + +It builds on the Token contract described here and goes into more detail about Aztec contract composability and Ethereum (L1) and Aztec (L2) cross-chain messaging. + +### Optional: Dive deeper into this contract and concepts mentioned here + +- Review [the end to end tests (Github link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/end-to-end/src/e2e_token_contract/) for reference. +- [Commitments (Wikipedia link)](https://en.wikipedia.org/wiki/Commitment_scheme) +- [Nullifier tree](../../../../aztec/concepts/advanced/storage/indexed_merkle_tree.mdx) +- [Public / Private function calls](../../../../aztec/smart_contracts/functions/public_private_calls.md). +- [Contract Storage](../../../../aztec/concepts/storage/index.md) +- [Authwit](../../../../aztec/concepts/advanced/authwit.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/write_accounts_contract.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/write_accounts_contract.md new file mode 100644 index 000000000000..13dbcfdccbaf --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/contract_tutorials/write_accounts_contract.md @@ -0,0 +1,361 @@ +--- +title: Account Contract +sidebar_position: 4 +tags: [accounts] +--- + +This tutorial will take you through the process of writing your own account contract in Aztec.nr, along with the Typescript glue code required for using it within a wallet. + +You will learn: + +- How to write a custom account contract in Aztec.nr +- The entrypoint function for transaction authentication and call execution +- The AccountActions module and entrypoint payload structs, necessary inclusions for any account contract +- Customizing authorization validation within the `is_valid` function (using Schnorr signatures as an example) +- Typescript glue code to format and authenticate transactions +- Deploying and testing the account contract + +This tutorial is compatible with the Aztec version `v0.88.0`. Install the correct version with `aztec-up -v 0.88.0`. Or if you'd like to use a different version, you can find the relevant tutorial by clicking the version dropdown at the top of the page. + +Writing your own account contract allows you to define the rules by which user transactions are authorized and paid for, as well as how user keys are managed (including key rotation and recovery). In other words, writing an account contract lets you make the most out of account abstraction in the Aztec network. + +It is highly recommended that you understand how an [account](../../../../aztec/concepts/accounts/index.md) is defined in Aztec, as well as the differences between privacy and authentication [keys](../../../../aztec/concepts/accounts/keys.md). You will also need to know how to write a contract in Noir, as well as some basic [Typescript](https://www.typescriptlang.org/). + +For this tutorial, we will write an account contract that uses Schnorr signatures for authenticating transaction requests. + +Every time a transaction payload is passed to this account contract's `entrypoint` function, the account contract requires a valid Schnorr signature, whose signed message matches the transaction payload, and whose signer matches the account contract owner's public key. If the signature fails, the transaction will fail. + +For the sake of simplicity, we will hardcode the signing public key into the contract, but you could store it [in a private note](../../../../aztec/concepts/accounts/keys.md#using-a-private-note), [in an immutable note](../../../../aztec/concepts/accounts/keys.md#using-an-immutable-private-note), or [on a separate keystore](../../../../aztec/concepts/accounts/keys.md#using-a-separate-keystore), to mention a few examples. + +## Contract + +Let's start with the account contract itself in Aztec.nr. Create a new Aztec.nr contract project that will contain a file with the code for the account contract, with a hardcoded public key: + +```rust title="contract" showLineNumbers +// Account contract that uses Schnorr signatures for authentication using a hardcoded public key. +use dep::aztec::macros::aztec; + +#[aztec] +pub contract SchnorrHardcodedAccount { + use dep::authwit::{ + account::AccountActions, + auth_witness::get_auth_witness, + entrypoint::{app::AppPayload, fee::FeePayload}, + }; + use dep::aztec::prelude::PrivateContext; + + use dep::aztec::macros::functions::{private, view}; + use std::embedded_curve_ops::EmbeddedCurvePoint; + + global public_key: EmbeddedCurvePoint = EmbeddedCurvePoint { + x: 0x16b93f4afae55cab8507baeb8e7ab4de80f5ab1e9e1f5149bf8cd0d375451d90, + y: 0x208d44b36eb6e73b254921134d002da1a90b41131024e3b1d721259182106205, + is_infinite: false, + }; + + // Note: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts + #[private] + fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) { + let actions = AccountActions::init(&mut context, is_valid_impl); + actions.entrypoint(app_payload, fee_payload, cancellable); + } + + #[private] + #[view] + fn verify_private_authwit(inner_hash: Field) -> Field { + let actions = AccountActions::init(&mut context, is_valid_impl); + actions.verify_private_authwit(inner_hash) + } + + #[contract_library_method] + fn is_valid_impl(_context: &mut PrivateContext, outer_hash: Field) -> bool { + // Load auth witness and format as an u8 array + + // Safety: The witness is only used as a "magical value" that makes the signature verification below pass. + // Hence it's safe. + let witness: [Field; 64] = unsafe { get_auth_witness(outer_hash) }; + let mut signature: [u8; 64] = [0; 64]; + for i in 0..64 { + signature[i] = witness[i] as u8; + } + + // Verify signature using hardcoded public key + schnorr::verify_signature(public_key, signature, outer_hash.to_be_bytes::<32>()) + } +} +``` +> Source code: noir-projects/noir-contracts/contracts/account/schnorr_hardcoded_account_contract/src/main.nr#L1-L55 + + +For this to compile, you will need to add the following dependencies to your `Nargo.toml`: + +```toml +[dependencies] +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/aztec" } +authwit = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/authwit" } +schnorr = { git = "https://github.com/noir-lang/schnorr", tag = "v0.1.1" } +``` + +The important part of this contract is the `entrypoint` function, which will be the first function executed in any transaction originated from this account. This function has two main responsibilities: authenticating the transaction and executing calls. It receives a `payload` with the list of function calls to execute, and requests a corresponding authentication witness from an oracle to validate it. Authentication witnesses are used for authorizing actions for an account, whether it is just checking a signature, like in this case, or granting authorization for another account to act on an accounts behalf (e.g. token approvals). You will find this logic implemented in the `AccountActions` module, which use the `AppPayload` and `FeePayload` structs: + +```rust title="entrypoint" showLineNumbers +pub fn entrypoint(self, app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) { + let valid_fn = self.is_valid_impl; + + let combined_payload_hash = poseidon2_hash_with_separator( + [app_payload.hash(), fee_payload.hash()], + GENERATOR_INDEX__COMBINED_PAYLOAD, + ); + assert(valid_fn(self.context, combined_payload_hash)); + + fee_payload.execute_calls(self.context); + self.context.end_setup(); + app_payload.execute_calls(self.context); + if cancellable { + let tx_nullifier = poseidon2_hash_with_separator( + [app_payload.tx_nonce], + GENERATOR_INDEX__TX_NULLIFIER, + ); + self.context.push_nullifier(tx_nullifier); + } +} +``` +> Source code: noir-projects/aztec-nr/authwit/src/account.nr#L40-L61 + + +```rust title="app-payload-struct" showLineNumbers +#[derive(Serialize)] +pub struct AppPayload { + function_calls: [FunctionCall; ACCOUNT_MAX_CALLS], + // A nonce that enables transaction cancellation. When the cancellable flag is enabled, this nonce is used to + // compute a nullifier that is then emitted. This guarantees that we can cancel the transaction by using the same + // nonce. + pub tx_nonce: Field, +} +``` +> Source code: noir-projects/aztec-nr/authwit/src/entrypoint/app.nr#L19-L28 + + +```rust title="fee-payload-struct" showLineNumbers +#[derive(Serialize)] +pub struct FeePayload { + function_calls: [FunctionCall; MAX_FEE_FUNCTION_CALLS], + tx_nonce: Field, + is_fee_payer: bool, +} +``` +> Source code: noir-projects/aztec-nr/authwit/src/entrypoint/fee.nr#L17-L24 + + +:::info +Using the `AccountActions` module and the payload structs is not mandatory. You can package the instructions to be carried out by your account contract however you want. However, using these modules can save you a lot of time when writing a new account contract, both in Noir and in Typescript. +::: + +The `AccountActions` module provides default implementations for most of the account contract methods needed, but it requires a function for validating an auth witness. In this function you will customize how your account validates an action: whether it is using a specific signature scheme, a multi-party approval, a password, etc. + +```rust title="is-valid" showLineNumbers +#[contract_library_method] +fn is_valid_impl(_context: &mut PrivateContext, outer_hash: Field) -> bool { + // Load auth witness and format as an u8 array + + // Safety: The witness is only used as a "magical value" that makes the signature verification below pass. + // Hence it's safe. + let witness: [Field; 64] = unsafe { get_auth_witness(outer_hash) }; + let mut signature: [u8; 64] = [0; 64]; + for i in 0..64 { + signature[i] = witness[i] as u8; + } + + // Verify signature using hardcoded public key + schnorr::verify_signature(public_key, signature, outer_hash.to_be_bytes::<32>()) +} +``` +> Source code: noir-projects/noir-contracts/contracts/account/schnorr_hardcoded_account_contract/src/main.nr#L37-L53 + + +For our account contract, we will take the hash of the action to authorize, request the corresponding auth witness from the oracle, and validate it against our hardcoded public key. If the signature is correct, we authorize the action. + +:::info + +Transaction simulations in the PXE are not currently simulated, this is future work described [here](https://github.com/AztecProtocol/aztec-packages/issues/9133). This means that any transaction simulations that call into a function requiring an authwit will require the user to provide an authwit. Without simulating simulations, the PXE can't anticipate what authwits a transaction may need, so developers will need to manually request these authwits from users. In the future, transactions requiring authwits will be smart enough to ask the user for the correct authwits automatically. + +::: + +### Fee Abstraction + +The `FeePayload`, being distinct from the `AppPayload`, allows for fee abstraction, meaning the account paying the fee for the transaction can be different than the account that is initiating the transaction. This is also useful for maintaining privacy, as fee payments on the network must be public. For example, Alice could pay a relayer transaction fees in private, and the relayer could pay the transaction fee in public. This also allows for accounts without Fee Juice to use another asset to pay for fees, provided they can find a relayer willing to accept the asset as payment (or do it for free). + +### Nonce Abstraction + +The protocol enforces uniqueness of transactions by checking that the transaction hash is unique. Transactions with the same transaction hash will be rejected. Handling transaction ordering via nonces is left to the account contract implementation. Account contracts can require incremental nonces, or have no requirements at all and not enforce transaction ordering. + +A side-effect of not having nonces at the protocol level is that it is not possible to cancel pending transactions by submitting a new transaction with higher fees and the same nonce. + +## Typescript + +Now that we have a valid account contract, we need to write the typescript glue code that will take care of formatting and authenticating transactions so they can be processed by our contract, as well as deploying the contract during account setup. This takes the form of implementing the `AccountContract` interface from `@aztec/aztec.js`: + +```typescript title="account-contract-interface" showLineNumbers +/** + * An account contract instance. Knows its artifact, deployment arguments, how to create + * transaction execution requests out of function calls, and how to authorize actions. + */ +export interface AccountContract { + /** + * Returns the artifact of this account contract. + */ + getContractArtifact(): Promise; + + /** + * Returns the deployment function name and arguments for this instance, or undefined if this contract does not require deployment. + */ + getDeploymentFunctionAndArgs(): Promise< + | { + /** The name of the function used to deploy the contract */ + constructorName: string; + /** The args to the function used to deploy the contract */ + constructorArgs: any[]; + } + | undefined + >; + + /** + * Returns the account interface for this account contract given a deployment at the provided address. + * The account interface is responsible for assembling tx requests given requested function calls, and + * for creating signed auth witnesses given action identifiers (message hashes). + * @param address - Address where this account contract is deployed. + * @param nodeInfo - Info on the chain where it is deployed. + * @returns An account interface instance for creating tx requests and authorizing actions. + */ + getInterface(address: CompleteAddress, nodeInfo: NodeInfo): AccountInterface; + + /** + * Returns the auth witness provider for the given address. + * @param address - Address for which to create auth witnesses. + */ + getAuthWitnessProvider(address: CompleteAddress): AuthWitnessProvider; +} +``` +> Source code: yarn-project/aztec.js/src/account/account_contract.ts#L10-L50 + + +However, if you are using the default `AccountActions` module, then you can leverage the `DefaultAccountContract` class from `@aztec/accounts` and just implement the logic for generating an auth witness that matches the one you wrote in Noir: + +```typescript title="account-contract" showLineNumbers +const PRIVATE_KEY = GrumpkinScalar.fromHexString('0xd35d743ac0dfe3d6dbe6be8c877cb524a00ab1e3d52d7bada095dfc8894ccfa'); + +/** Account contract implementation that authenticates txs using Schnorr signatures. */ +class SchnorrHardcodedKeyAccountContract extends DefaultAccountContract { + constructor(private privateKey = PRIVATE_KEY) { + super(); + } + + override getContractArtifact(): Promise { + return Promise.resolve(SchnorrHardcodedAccountContractArtifact); + } + + getDeploymentFunctionAndArgs() { + // This contract has no constructor + return Promise.resolve(undefined); + } + + getAuthWitnessProvider(_address: CompleteAddress): AuthWitnessProvider { + const privateKey = this.privateKey; + return { + async createAuthWit(messageHash: Fr): Promise { + const signer = new Schnorr(); + const signature = await signer.constructSignature(messageHash.toBuffer(), privateKey); + return Promise.resolve(new AuthWitness(messageHash, [...signature.toBuffer()])); + }, + }; + } +} +``` +> Source code: yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts#L17-L46 + + +As you can see in the snippet above, to fill in this base class, we need to define three things: + +- The build artifact for the corresponding account contract. +- The deployment arguments. +- How to create an auth witness. + +In our case, the auth witness will be generated by Schnorr-signing over the message identifier using the hardcoded key. To do this, we are using the `Schnorr` signer from the `@aztec/stdlib` package to sign over the payload hash. This signer maps to exactly the same signing scheme that Noir's standard library expects in `schnorr::verify_signature`. + +:::info +More signing schemes are available in case you want to experiment with other types of keys. Check out Noir's [documentation on cryptographic primitives](https://noir-lang.org/docs/noir/standard_library/cryptographic_primitives). +::: + +## Trying it out + +Let's try creating a new account backed by our account contract, and interact with a simple token contract to test it works. + +To create and deploy the account, we will use the `AccountManager` class, which takes an instance of an Private Execution Environment (PXE), a [privacy private key](../../../../aztec/concepts/accounts/keys.md#incoming-viewing-keys), and an instance of our `AccountContract` class: + +```typescript title="account-contract-deploy" showLineNumbers +const secretKey = Fr.random(); +const account = await AccountManager.create(pxe, secretKey, new SchnorrHardcodedKeyAccountContract()); + +if (await account.isDeployable()) { + // The account has no funds. Use a funded wallet to pay for the fee for the deployment. + await account.deploy({ deployWallet: fundedWallet }).wait(); +} else { + // The contract has no constructor. Deployment is not required. + // Register it in the PXE Service to start using it. + await account.register(); +} + +const wallet = await account.getWallet(); +const address = wallet.getAddress(); +``` +> Source code: yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts#L60-L75 + + +Note that we used a funded wallet to deploy the account contract and pay for the transaction fee. The new account doesn't have any funds yet. We will continue using the funded wallet to deploy the token contract: + +```typescript title="token-contract-deploy" showLineNumbers +const token = await TokenContract.deploy(fundedWallet, fundedWallet.getAddress(), 'TokenName', 'TokenSymbol', 18) + .send() + .deployed(); +logger.info(`Deployed token contract at ${token.address}`); + +const mintAmount = 50n; +const from = fundedWallet.getAddress(); // we are setting from here because we need a sender to calculate the tag +await token.methods.mint_to_private(from, address, mintAmount).send().wait(); + +const balance = await token.methods.balance_of_private(address).simulate(); +logger.info(`Balance of wallet is now ${balance}`); +``` +> Source code: yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts#L78-L90 + + +If we run this, we get `Balance of wallet is now 150`, which shows that the `mint` call was successfully executed for our account contract. + +To make sure that we are actually validating the provided signature in our account contract, we can try signing with a different key. To do this, we will set up a new `Account` instance pointing to the contract we already deployed but using a wrong signing key: + +```typescript title="account-contract-fails" showLineNumbers +const wrongKey = GrumpkinScalar.random(); +const wrongAccountContract = new SchnorrHardcodedKeyAccountContract(wrongKey); +const wrongAccount = await AccountManager.create(pxe, secretKey, wrongAccountContract, account.salt); +const wrongWallet = await wrongAccount.getWallet(); +const tokenWithWrongWallet = token.withWallet(wrongWallet); + +try { + await tokenWithWrongWallet.methods.mint_to_public(address, 200).prove(); +} catch (err) { + logger.info(`Failed to send tx: ${err}`); +} +``` +> Source code: yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts#L93-L105 + + +Lo and behold, we get `Error: Assertion failed: 'verification == true'` when running the snippet above, pointing to the line in our account contract where we verify the Schnorr signature. + +## Next Steps + +### Optional: Learn more about concepts mentioned here + +- [ECDSA signer account contract (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/main.nr) +- [Schnorr signer account contract (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/noir-contracts/contracts/account/schnorr_account_contract) +- [Account abstraction](../../../../aztec/concepts/accounts/index.md#account-abstraction-aa) +- [Authentication witness](../../../../aztec/concepts/advanced/authwit.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/faceid_wallet.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/faceid_wallet.md new file mode 100644 index 000000000000..5557cc0d0f56 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/faceid_wallet.md @@ -0,0 +1,122 @@ +--- +title: Using FaceID to Sign Transactions (Mac Only) +tags: [sandbox, wallet, cli] +keywords: [wallet, cli wallet, faceid] +sidebar_position: 5 +--- + +In this tutorial, we will use Apple Mac's Secure Enclave to store the private key, and use it in Aztec's [CLI Wallet](../../reference/environment_reference/cli_wallet_reference.md). This enables fully private, native, and seedless account abstraction! + +:::warning + +Aztec is in active development and this has only been tested on MacOS. Please reach out if this tutorial does not work for you, and let us know your operating system. + +::: + +:::note +This tutorial is for the sandbox and will need adjustments if you want to use it on testnet. Install the sandbox [here](../../getting_started.md). +::: + +## Prerequisites + +For this tutorial, we will need to have the [Sandbox](../../reference/environment_reference/sandbox-reference.md) installed. + +We also need to install Secretive, a nice open-source package that allows us to store keys on the Secure Enclave. You can head to the [secretive releases page](https://github.com/maxgoedjen/secretive/releases) and get the last release's `zip`, unzip and move to Applications, or use [Homebrew](https://brew.sh/): + +```bash +brew install secretive +``` + +Open it from the Applications folder and copy the provided Socket Path (the one it tells you to add to your .ssh config). Export it as a terminal environment variable. For example: + +```bash +export SSH_AUTH_SOCK="/Users/your_user/Library/Containers/com.maxgoedjen.Secretive.SecretAgent/Data/socket.ssh" +``` + +Let's also install `socat` which helps us manage the socket connections. If using Homebrew: + +```bash +brew install socat +``` + +### Creating a key + +We will create our private key, which will be stored in the Secure Enclave. Open Secretive, click the "+" sign and create a key with authentication. You can give it any name you like. Secretive will then store it in the Secure Enclave. + +Make sure Secretive's "Secret Agent" is running. + +:::info + +The Secure Enclave is a protected chip on most recent iPhones and Macs and it's meant to be airgapped. It is not safe to use in production. + +Fortunately, Aztec implements [Account Abstraction](../../../aztec/concepts/accounts/index.md#account-abstraction-aa) at the protocol level. You could write logic to allow someone else to recover your account, or use a different key or keys for recovery. + +::: + +### Using the wallet + +Now we can use the key in our wallet. Every account on Aztec is a contract, so you can write your own contract with its own account logic. + +The Aztec team already wrote some account contract boilerplates we can use. One of them is an account that uses the `secp256r1` elliptic curve (the one the Secure Enclave uses). + +Let's create an account in our wallet: + +```bash +aztec-wallet create-account -a my-wallet -t ecdsasecp256r1ssh +``` + +This command creates an account using the `ecdsasecp256r1ssh` type and aliases it to `my-wallet`. + +You should see a prompt like `? What public key to use?` with the public key you created in Secretive. Select this. If you see the message `Account stored in database with aliases last & my-wallet` then you have successfully created the account! + +You can find other accounts by running `aztec-wallet create-account -h`. + +### Using the wallet + +You can now use it as you would use any other wallet. Let's create a simple token contract example and mint ourselves some tokens with this. + +Create a new Aztec app with `npx aztec-app`: + +```bash +npx aztec-app new -s -t contract -n token_contract token +``` + +This creates a new project, skips running the sandbox (`-s`), and clones the contract-only box (`-t`) called token_contract (`-n`). You should now have a `token_contract` folder. Let's compile our contract: + +```bash +cd token_contract +aztec-nargo compile +``` + +Great, our contract is ready to deploy with our TouchID wallet: + +```bash +aztec-wallet deploy --from accounts:my-wallet token_contract@Token --args accounts:my-wallet DevToken DTK 18 -a devtoken + +You should get prompted to sign with TouchID or password. Once authorized, you should see `Contract stored in database with aliases last & devtoken` +``` + +Check [the reference](../../reference/environment_reference/cli_wallet_reference.md) for the whole set of commands, but these mean: + +- --from is the sender: our account `my-wallet`. We use the alias because it's easier than writing the key stored in our Secure Enclave. The wallet resolves the alias and knows where to grab it. +- token_contract@Token is a shorthand to look in the `target` folder for our contract `token_contract-Token` +- --args are the arguments for our token contract: owner, name, ticker and decimals. +- -a tells the wallet to store its address with the "devtoken" alias, this way we can just use it later like `contracts:devtoken` + +You should get a prompt to sign this transaction. You can now mint, transfer, and do anything you want with it: + +```bash +aztec-wallet create-account -a new_recipient # creating a schnorr account +aztec-wallet send mint_to_public -ca last --args accounts:my-wallet 10 -f accounts:my-wallet # minting some tokens in public +aztec-wallet simulate balance_of_public -ca contracts:devtoken --args accounts:my-wallet -f my-wallet # checking that my-wallet has 10 tokens +aztec-wallet send transfer_in_public -ca contracts:devtoken --args accounts:my-wallet accounts:new_recipient 10 0 -f accounts:my-wallet # transferring some tokens in public +aztec-wallet simulate balance_of_public -ca contracts:devtoken --args accounts:new_recipient -f my-wallet # checking that new_recipient has 10 tokens +``` + +### What next + +In this tutorial, we created an account with the Aztec's [CLI Wallet](../../reference/environment_reference/cli_wallet_reference.md), using the Apple Mac's Secure Enclave to store the private key. + +You can use a multitude of authentication methods, for example with RSA you could use a passport as a recovery, or even as a signer in a multisig. All of this is based on the account contract. + +Next step is then to [code your own account contract!](./contract_tutorials/write_accounts_contract.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/first_fees.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/first_fees.md new file mode 100644 index 000000000000..a5d32ca2fecb --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/first_fees.md @@ -0,0 +1,542 @@ +--- +title: All About Fees +sidebar_position: 4 +tags: [fees, accounts, transactions, cli, contracts] +--- + +import { General, Fees } from '@site/src/components/Snippets/general_snippets'; +import { Glossary } from '@site/src/components/Snippets/glossary_snippets'; + +The Aztec network is a privacy preserving layer 2 secured by Ethereum, and for the consensus mechanism to work, Aztec makes use of an asset to pay for transactions called, "Fee juice". + +With account and fee abstraction on Aztec, there are several options for users and dApps to pay fees for themselves or their users. + +By the end of this tutorial you will... + +- Connect to the Aztec sandbox and/or testnet +- Use different payment methods to deploy accounts and make transactions via: + - various `aztec-wallet` CLI commands + - the `aztec.js` library +- Understand the pros/cons of different payment methods + +**Note:** + + +## Background + +For this tutorial the following definitions will come in handy... + +**PXE**: + +**Aztec Node**: + +**Sandbox**: + +**`aztec-wallet`**: + +## Connect to the network + +Use of the `aztec-wallet` cli wallet is shown alongside use via the `aztec.js` library, and the choice of network can be the Sandbox or the Aztec testnet which rolls up to Sepolia. + +### Tools + + + +With docker running, test the cli wallet with: `aztec-wallet --help` + +#### Sandbox (skip if using testnet) + +By default the sandbox runs everything including a PXE locally. For most paths in this tutorial, and more realistically when using testnet, we don't want this PXE. + +Start the sandbox (L1, L2, but not the PXE) via: `NO_PXE=true aztec start --sandbox` + +:::note Sandbox + aztec.js? +If you are specifically wanting to test aztec.js with the sandbox, then you will need to use the default command which includes the PXE: + +- `aztec start --sandbox` + ::: + +### Specifying the network URL for your PXE + +#### CLI Wallet + +Testing locally on the sandbox vs using the testnet, you may need to specify which node you would like to connect with. +When using the PXE in `aztec-wallet`, the Aztec network node to connect to can be specified in each relevant command: + +```bash +aztec-wallet --node-url ... +``` + +If the node url is not provided, the command defaults to the the local sandbox: `http://host.docker.internal:8080`. These should also be forwarded to `localhost` (you can check with `docker ps` when running the sandbox). + +:::info Tip +If specifying the node url for every command, it is convenient to make an alias for it with your node url: + +``` +NODE_URL=http://x.x.x.x +alias aztec-wallet-node=aztec-wallet -n $NODE_URL +``` + +Now you can use the alias for future commands: `aztec-wallet-node ...` + +::: + +#### Aztec.js + + + +If you are using the sandbox for this step, it should be running with it's pxe. Otherwise if connecting to testnet, start a local PXE that your app connects to. The parameters for the `aztec start` command are: + +- the local port to serve on: `--port 8081` +- tell aztec to start the pxe: `--pxe` + - use this node url for transactions: `--pxe.nodeUrl=$NODE_URL` + - don't perform local proving: `--pxe.proverEnabled false` + +``` +NODE_URL=http://x.x.x.x +aztec start --port 8081 --pxe --pxe.nodeUrl=$NODE_URL --pxe.proverEnabled false +``` + +Now init a project with your preferred node package manager (yarn, pnpm, ...) then you can create a PXE client to connect to your local pxe: + +```javascript +// remember to pnpm install any new libs as they appear in these snippets, +// @ the specific version (ie, no preceding `^`) of your `aztec` tools. +import { createPXEClient, waitForPXE, PXE } from "@aztec/aztec.js"; +async function main() { + const pxe = await createPXEClient("http://localhost:8081"); + await waitForPXE(pxe); + // use pxe... + console.log(await pxe.getNodeInfo()); +} +``` + +## Create Account Contract in a PXE + + + +For convenience, Aztec Labs has implemented an account contract that authenticates transactions using Schnorr signatures. The contract class for a Schnorr account is pre-registered on Aztec networks (eg sandbox, testnet) to bootstrap first use. Ordinarily for a contract to be deployed, its class would have to be registered with the network first. + +When a PXE creates a Schnorr account, there are three key things that occur: + +- generation of keys privately +- registeration of a Schnorr account locally (effectively an instance of the Schnorr account class) + - at this stage the address of the account is known, even though it is not yet deployed +- initialization and deployment of the account contract to the network + +**Create the keys and account in the PXE** + +Lets first create the account but only register the instance in the PXE. Its address will be calculated, and in a later step we can deploy it to the network. + +```bash +aztec-wallet create-account --register-only -a main +``` + +The `-a main` sets this new account's alias to, "main". Use `aztec-wallet get-alias` (with no params) to see all aliases. + +The equivalent using aztec.js - create a random account locally (we will deploy it in the next step): + +```javascript +import { Fr } from "@aztec/aztec.js"; +import { getSchnorrAccount } from "@aztec/accounts/schnorr"; +import { deriveSigningKey } from "@aztec/stdlib/keys"; +//... building upon the previous section +let secretKey = Fr.random(); +let salt = Fr.random(); +let schnorrAccount = await getSchnorrAccount( + pxe, + secretKey, + deriveSigningKey(secretKey), + salt +); +``` + +Your PXE now has keys for an account that can be deployed on Aztec network(s). + +## Payment option overview + +To get a quick overview of the payment options currently supported... + +- **Sponsored Fee Paying Contract (Sponsored FPC)**: A small amount of funds from a limited faucet-like contract. Useful for bootstraping first transactions, e.g. deloying an account + +- **Fee juice from an account**, which is: + + - Claimed from the fee asset bridged from L1 just before use. + - Paid by the account that holds fee juice that is sending a transaction (default) + - Special case with account deployment - paid for by a different account holding fee juice (not possible with other transaction types) + +- **General Fee Paying Contracts**: These enable accounts to pay in one asset (publicly or privately) for the FPC to then (publicly) pay for the transaction. Great for privacy when an account is paying privately, and interacting with a private contract function. + +We will go into these in the following sections. + +## Paying for an account deployment transaction + +To make transactions on the network, your account contract will need to specify a payment method of the enshrined asset, "Fee Juice". + +### Sponsored FPC + +To bootstrap first use, a sponsored fee paying contract (the canonical sponsored FPC) can be used to deploy your first account. + + + +In the case of the canonical sponsored FPC, the only criteria is an upper bound on how much it sponsors an account's transactions. This will be enough to at least deploy an account. + +The PXE can be queried for the canonical sponsored FPC address, and then specified as the payment method. +For testnet this is `0x1260a43ecf03e985727affbbe3e483e60b836ea821b6305bea1c53398b986047`, which can be verified with the command: `aztec get-canonical-sponsored-fpc-address` + +Via the CLI: + +The alias set earlier can be confirmed using: `aztec-wallet get-alias accounts:main`, this is specified here in `--from main`. + +```bash +SPONSORED_FPC_ADDRESS=<0x...aztec_address...> +aztec-wallet register-contract $SPONSORED_FPC_ADDRESS SponsoredFPC --salt 0 --from main # Need to specify account that wishes to register the contract +aztec-wallet deploy-account --from main --payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS +``` + +The equivalent using aztec.js - get sponsored fpc address (helper functions [here](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/aztec/src/sandbox/sponsored_fpc.ts)) and use payment method: + +```javascript +import { getSponsoredFPCInstance } from "../src/utils/sponsored_fpc.js"; // helper functions linked above +import { SponsoredFPCContract } from "@aztec/noir-contracts.js/SponsoredFPC"; +import { SponsoredFeePaymentMethod } from "@aztec/aztec.js/fee/testing"; +//... building upon the previous section +const sponseredFPC = await getSponsoredFPCInstance(); // get address of pre-deployed Sponsored FPC contract +await pxe.registerContract({ + instance: sponseredFPC, + artifact: SponsoredFPCContract.artifact, +}); // register the Sponsord FPC contract class with the pxe +const sponsoredPaymentMethod = new SponsoredFeePaymentMethod( + sponseredFPC.address +); // create payment method +let tx = await schnorrAccount + .deploy({ fee: { paymentMethod: sponsoredPaymentMethod } }) + .wait(); +let wallet = await schnorrAccount.getWallet(); +let address = wallet.getAddress(); +``` + +:::note Payment: Sponsored Fee Paying Contract +Options for payment via the sponsored fpc that can be used in multiple commands: + +CLI: `--payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS` + +.js: `{ fee: { paymentMethod: new SponsoredFeePaymentMethod(sponseredFPCAddress) }}` + +::: + +Once proofs have been generated locally, you will need to wait for the transaction to be included in a block. +**Congratulations! You have successfully created an account on Aztec!** + +This contract now exists in the sandbox network, or on testnet if you specified a node url. + +### Fee Juice + +Apart from the FPC payment methods, the default method for paying for transactions is via fee juice direct from the sender of the transaction. +When specifying fee juice, and only in the case of deploying an account, a different funded account may be specified to pay for the deployment transaction of an unfunded account. +Another way for an accounts deployment to be funded is via claiming bridged fee juice (in a later section). + +#### Sandbox pre-funded test accounts + +The sandbox starts with 3 test accounts, providing another way to bootstrap initial testing with accounts. To add these to the PXE... + +For the CLI: + +```bash +aztec-wallet import-test-accounts +``` + +Confirm with: `aztec-wallet get-alias`, to see all aliases + +The equivalent using aztec.js - get the test accounts + +```javascript +import { getInitialTestAccountsWallets } from "@aztec/accounts/testing"; +//... +const testWallets = await getInitialTestAccountsWallets(pxe); +``` + +Note: The test account addresses can also be seen in the sandbox logs when it starts up. + +**Create and deploy a new account, paid for by another account** + +For the sandbox only, you can use the test accounts provided at aliases: `test0`, `test1`, `test2` + +```bash +aztec-wallet create-account -a alice --payment method=fee_juice,feePayer=test0 +``` + +:::warning +Specifying the `feePayer` is only an option for commands that deploy an account. +::: + +The equivalent using aztec.js - Create a fee juice payment method with a test account as sender, then deploy an account: + +```javascript +import { FeeJuicePaymentMethod } from "@aztec/aztec.js"; +//... building upon the previous section, duplicate account creation using `getSchnorrAccount` +// and an incremented salt value: `salt.add(Fr.fromString("1"))` +// Below we'll deploy the new account (eg schnorrAccount2) with fee juice from test wallet + +const useFeeJuice = new FeeJuicePaymentMethod(testWallets[0].getAddress()); +await schnorrAccount2 + .deploy({ fee: { deployWallet: testWallets[0], paymentMethod: useFeeJuice } }) + .wait(); +``` + +:::note Payment: Fee Juice +Options for explicitly stating fee_juice from the sender which is the default payment method: + +CLI: `--payment method=fee_juice` (default) + +.js: `{ fee: { paymentMethod: new FeeJuicePaymentMethod() }` + +::: + +### Bridging Fee Juice + +First register a new account `accBFJ` that we will bridge fee-juice to on deployment. + +```bash +aztec-wallet create-account -a accBFJ --register-only +``` + +(Note: it is worth securing the account info so you can restore it again if needed) + +If using the Sandbox, free-minting is allowed from it's anvil L1 to be bridged and claimed on its Aztec node: + +```bash +aztec-wallet bridge-fee-juice 1000000000000000000 accBFJ --mint --no-wait +``` + +If using Aztec testnet, you'll first need an L1 account with sepolia, and additional params for the bridge-fee-juice command: + +```bash +aztec-wallet bridge-fee-juice 1000000000000000000 accBFJ --mint --no-wait \ + --l1-rpc-urls \ # eg https://rpc.sepolia.ethpandaops.io + --l1-chain-id 11155111 \ + --l1-private-key +``` + +You'll have to wait for two blocks to pass for bridged fee juice to be ready on Aztec. For the sandbox you can do this by putting through two arbitrary transactions. Eg: + +```bash +aztec-wallet deploy Counter --init initialize --args 0 accounts:test0 --from test0 -a counter +aztec-wallet send increment -ca counter --args accounts:test0 accounts:test0 --from test0 +``` + +Now the funded account can deploy itself with the bridged fees, claiming the bridged fee juice and deploying the contract in one transaction: + +```bash +aztec-wallet deploy-account --from accBFJ --payment method=fee_juice,claim +``` + +The equivalent using aztec.js - bridge fee juice, (pass two txs), create and use payment method: + +(See also the [aztec-wallet](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/cli-wallet/src/cmds/bridge_fee_juice.ts#L32) implementation to initialise a fee juice portal manager) + +```javascript +import { + FeeJuicePaymentMethod, + PrivateFeePaymentMethod, + PublicFeePaymentMethod, +} from "@aztec/aztec.js"; +import { createEthereumChain, createL1Clients } from "@aztec/ethereum"; +import { L1FeeJuicePortalManager } from "@aztec/aztec.js/ethereum"; +//... building upon the previous section, duplicate account creation using `getSchnorrAccount` +// and an incremented salt value: `salt.add(Fr.fromString("2"))` +// Below we'll deploy the new account (eg schnorrAccount3) via a claim to bridged fee juice + +const { l1ChainId } = await pxe.getNodeInfo(); // foundry chainid 31337 for sandbox use +const l1RpcUrls = ["http://localhost:8545]"]; // for sandbox use, or see https://chainlist.org/chain/11155111> eg ['https://rpc.sepolia.ethpandaops.io'] +const chain = createEthereumChain(l1RpcUrls, l1ChainId); + +// eg l1 private key, or for sandbox... +const mnemonicOrPrivateKey = + "test test test test test test test test test test test junk"; + +const { publicClient, walletClient } = createL1Clients( + chain.rpcUrls, + mnemonicOrPrivateKey, + chain.chainInfo +); + +const feeJuicePortalManager = await L1FeeJuicePortalManager.new( + pxe, + publicClient, + walletClient, + log +); + +const newWallet = await schnorrAccount3.getWallet(); +const feeJuiceReceipient = schnorrAccount3.getAddress(); + +const claim = await feeJuicePortalManager.bridgeTokensPublic( + feeJuiceReceipient, + 1000000000000000000n, + true +); +// ...(wait for two blocks to pass or perform two txs in sandbox, eg deploy two other accounts) +const claimAndPay = new FeeJuicePaymentMethodWithClaim(newWallet, claim); +await schnorrAccount.deploy({ fee: { paymentMethod: claimAndPay } }).wait(); +``` + +For testnet: chose your prefered sepolia rpc provider, chainid is sepolia 11155111, and private key of the account with sepolia eth to mint and bridge fee juice to Aztec. + +:::note Payment: Fee Juice with claim from bridge +Options for claim+pay with bridged funds that can be used in multiple commands: + +CLI: `--from --payment method=fee_juice,claim` + +.js: `{ fee: { paymentMethod: new FeeJuicePaymentMethodWithClaim(newWallet, claim) }}` + +::: + +:::tip Use a block explorer + + +::: + +### Fee Paying Contract payment (public/private) - Advanced + +Setting up your own FPC and authorising the asset is an involved process outside the scope of this tutorial, so below we will only look at the syntax for understanding. + +In this example, a fee paying contract exists that takes a token called bananaCoin in exchange for paying the fee asset of transactions. Note: An example implementation exists in [aztec-starter](https://github.com/AztecProtocol/aztec-starter/blob/main/scripts/fees.ts#L94). + +First register the FPC address in your PXE. In reality this might be an application funding users' transactions via their token. + +```bash +aztec-wallet register-contract $FPC_ADDRESS FPCContract --from main +aztec-wallet --payment method=fpc-public,fpc=$FPC_ADDRESS,asset=$ASSET_ADDRESS +``` + +The second line can be any command that takes a `--payment` parameter. See `aztec-wallet --help` and the help of corresponding commands to check. + +Notice the specified address of both the FPC contract, and the asset address. + +The equivalent using aztec.js: + +```javascript +import { FPCContract } from "@aztec/noir-contracts.js/FPC"; +//... +const fpc = await FPCContract.deploy( + wallets[0], + bananaCoin.address, + wallets[0].getAddress() +) + .send() + .deployed(); +// ... (setup token authorisation, and fpc balance here) +const publicFee = new PublicFeePaymentMethod(fpc.address, newWallet); +``` + +:::note Payment: Fee Paying Contract (public/private) +Options for payment via FPC that can be used in multiple commands: + +CLI: `--payment method=fpc-public,fpc=$FPC_ADDRESS,asset=$ASSET_ADDRESS` (`fpc-private` for private) + +.js: `{ fee: { paymentMethod: new PublicFeePaymentMethod(fpc.address, newWallet) }` (`PrivateFeePaymentMethod` for private) + +::: + +:::info Private fee payment +Note: using a private FPC method is the only way for transactions to be paid for privately. + +Public and private refer to how the FPC will claim its tokens from the sender to then (publicly) spend fee juice. The visibility of the function being called is unchanged. +::: + +## Summary of fee payment options + +The two key ways of paying for transactions: fee juice from an account or via an FPC. Both of which are contracts on the aztec network. + +### Fee juice from an account (default) + +- from the sender of a transaction (default) + - when making transactions from an account funded with fee juice + - users will require the fee asset on L1 then bridge/claim on deployment +- specifying a different fee-payer account (new account creation/deployment only) + - using a funded account to deploy another account, or rapid testing on the sandbox + - need to already have a funded account +- claiming fee juice from already-bridged L1 asset and immediately using + - great for bootstrapping an account on Aztec + - need to already have aztec fee asset on an L1 account + +### Fee juice from a Fee Paying Contract + +- via public/private payment + - can pay in other assets, and not hold fee juice + - can privately spend alternative assets, to make private transactions +- sponsored payment + - enables new users to bootstrap their first account + +### Example payment options + +```bash +# default fee juice from sender of transaction +aztec-wallet --payment method=fee_juice + +# fee juice from another account (create/deploy txs only) +aztec-wallet --payment method=fee_juice,feePayer=$FEE_PAYER_ADDRESS + +# claim bridged fee juice and pay +aztec-wallet --payment method=fee_juice,claim + +# sponsored fpc +aztec-wallet --payment method=fpc-sponsored,fpc=$SPONSORED_FPC_ADDRESS + +# asset for fee juice via fpc public +aztec-wallet --payment method=fpc-public,fpc=$FPC_ADDRESS,asset=$ASSET_ADDRESS + +# asset for fee juice via fpc private +aztec-wallet --payment method=fpc-private,fpc=$FPC_ADDRESS,asset=$ASSET_ADDRESS + +``` + +```javascript +// default fee juice from sender of transaction +command({ fee: { paymentMethod: new FeeJuicePaymentMethod() }}) + +// fee juice from different sender (create/deploy txs only) +command({ fee: { paymentMethod: new FeeJuicePaymentMethod() }}) + +// claim bridged fee juice and pay +command({ fee: { paymentMethod: new FeeJuicePaymentMethodWithClaim(newWallet, claim) }}) + +// sponsored fpc +command({ fee: { paymentMethod: new SponsoredFeePaymentMethod(sponseredFPCAddress) }}) + +// asset for fee juice via fpc public +command({ fee: { paymentMethod: new PublicFeePaymentMethod(fpc.address, newWallet) }}) + +// asset for fee juice via fpc private +command({ fee: { paymentMethod: new PrivateFeePaymentMethod(fpc.address, newWallet) }}) +``` + +:::warning +Lists above are provided as a convenient reference and are not automatically maintained. +Please refer to the snippets in the sections above, and report any discrepancies. + +::: + +### Tabulated payment methods and options + +`aztec-wallet` method and option parameters. + +- The rows correspond to the method, eg `--payment method=fee_juice` +- The columns are the options, eg `--payment method=fpc-sponsored,fpc=
` + +| method\options | `feePayer` | `asset` | `fpc` | `claim` | +| --------------- | -------------------------- | -------------------------- | ---------------- | ---------- | +| `fee_juice` | create/deploy account only | NA | NA | if bridged | +| `fpc-public` | NA | FPC accepted asset address | contract address | NA | +| `fpc-private` | NA | FPC accepted asset address | contract address | NA | +| `fpc-sponsored` | NA | NA | NA | NA | + +## Useful resources and further reading + +- [`aztec` CLI tool](../../reference/environment_reference/cli_reference) +- [`aztec-wallet` CLI tool](../../reference/environment_reference/cli_wallet_reference) +- [`aztec.js` source](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/aztec.js) +- [Glossary](../../../glossary) +- Search bar and AI above +- Tags below :) diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/_category_.json new file mode 100644 index 000000000000..d497430c8a5e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Full-Stack Tutorials", + "position": 2, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/aztecjs-getting-started.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/aztecjs-getting-started.md new file mode 100644 index 000000000000..7619a60aee79 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/aztecjs-getting-started.md @@ -0,0 +1,398 @@ +--- +title: Getting started with Aztec.js +sidebar_position: 0 +--- + +import Image from "@theme/IdealImage"; + +In this guide, we will retrieving the Sandbox and deploy a pre-written token contract to it using Aztec.js. We will then use Aztec.js to interact with this contract and transfer tokens. + +This guide assumes you have followed the [quickstart](../../../../developers/getting_started.md). + +:::note +This tutorial is for the sandbox and will need adjustments if deploying to testnet. Install the sandbox [here](../../../getting_started.md). +::: + +## Prerequisites + +- A running Aztec sandbox at version 0.88.0. Install with `aztec-up 0.88.0`. + +## Set up the project + +We will deploy a pre-compiled token contract, and send tokens privately, using the Sandbox. + +We will create a `yarn` TypeScript project called `token` (although `npm` works fine too). + +1. Initialize a yarn project + +```sh +mkdir token +cd token +yarn init -yp +``` + +2. Create a `src` folder inside your new `token` directory: + +```sh +mkdir src +``` + +3. Add necessary yarn packages + +```sh +yarn add @aztec/aztec.js@0.88.0 @aztec/accounts@0.88.0 @aztec/noir-contracts.js@0.88.0 typescript @types/node +``` + +:::note Match tool and dependency versions +The version returned from `aztec -V` should match the `@aztec/...` dependencies in package.json + +::: + +and yarn config: + +```sh +echo "nodeLinker: node-modules" > .yarnrc.yml +``` + +Then run: `yarn install` + +4. Add a `tsconfig.json` file into the project root and paste this: + +```json +{ + "compilerOptions": { + "outDir": "dest", + "rootDir": "src", + "target": "es2020", + "lib": ["dom", "esnext", "es2017.object"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "declaration": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "downlevelIteration": true, + "inlineSourceMap": true, + "declarationMap": true, + "importHelpers": true, + "resolveJsonModule": true, + "composite": true, + "skipLibCheck": true + }, + "references": [], + "include": ["src", "src/*.json"] +} +``` + +5. Add this to your `package.json`: + +```json + "type": "module", + "scripts": { + "build": "yarn clean && tsc -b", + "build:dev": "tsc -b --watch", + "clean": "rm -rf ./dest tsconfig.tsbuildinfo", + "start": "yarn build && node ./dest/index.js" + }, +``` + +6. Create an `index.ts` file in the `src` directory with the following sandbox connection setup: + +```ts +import { getSchnorrAccount } from '@aztec/accounts/schnorr'; +import { getDeployedTestAccountsWallets } from '@aztec/accounts/testing'; +import { + Fr, + GrumpkinScalar, + type PXE, + PrivateFeePaymentMethod, + createLogger, + createPXEClient, + getFeeJuiceBalance, + waitForPXE, +} from '@aztec/aztec.js'; +import { TokenContract } from '@aztec/noir-contracts.js/Token'; + +import { format } from 'util'; +import type { AztecAddress, Logger, Wallet } from '@aztec/aztec.js'; + +export async function deployToken(adminWallet: Wallet, initialAdminBalance: bigint, logger: Logger) { + logger.info(`Deploying Token contract...`); + const contract = await TokenContract.deploy(adminWallet, adminWallet.getAddress(), 'TokenName', 'TokenSymbol', 18) + .send() + .deployed(); + + if (initialAdminBalance > 0n) { + // Minter is minting to herself so contract as minter is the same as contract as recipient + await mintTokensToPrivate(contract, adminWallet, adminWallet.getAddress(), initialAdminBalance); + } + + logger.info('L2 contract deployed'); + + return contract; +} + +export async function mintTokensToPrivate( + token: TokenContract, + minterWallet: Wallet, + recipient: AztecAddress, + amount: bigint, +) { + const tokenAsMinter = await TokenContract.at(token.address, minterWallet); + const from = minterWallet.getAddress(); // we are setting from to minter here because we need a sender to calculate the tag + await tokenAsMinter.methods.mint_to_private(from, recipient, amount).send().wait(); +} + +const { PXE_URL = 'http://localhost:8080' } = process.env; + +async function main() { +////////////// CREATE THE CLIENT INTERFACE AND CONTACT THE SANDBOX ////////////// +const logger = createLogger('e2e:token'); + +// We create PXE client connected to the sandbox URL +const pxe = createPXEClient(PXE_URL); +// Wait for sandbox to be ready +await waitForPXE(pxe, logger); + +const nodeInfo = await pxe.getNodeInfo(); + +logger.info(format('Aztec Sandbox Info ', nodeInfo)); +} + +main(); +``` + +7. Finally, run the package: + +In the project root, run + +```sh +yarn start +``` + +A successful run should show something like this: + +``` +[21:21:57.641] INFO: e2e:token Aztec Sandbox Info { + enr: undefined, + nodeVersion: '0.82.0', + l1ChainId: 31337, + rollupVersion: 1, + l1ContractAddresses: { + rollupAddress: EthAddress<0x759f145841f36282f23e0935697c7b2e00401902>, + registryAddress: EthAddress<0xd5448148ccca5b2f27784c72265fc37201741778>, + inboxAddress: EthAddress<0x7ba2d0f3a856cd7156a4e88d8c06e5f5cb3b7dd6>, + outboxAddress: EthAddress<0x6ab41414235e8e9d0e5ac42cdf430432dd6bdd02>, + feeJuiceAddress: EthAddress<0x5a08d997c9284780330208ecf0112f80f9e6c472>, + stakingAssetAddress: EthAddress<0xc34806e86bcc34feab846712e57edf6086696377>, + feeJuicePortalAddress: EthAddress<0x7d2ec00c17a6988c6dbf17a3ee825614cb4aaa4c>, + coinIssuerAddress: EthAddress<0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266>, + rewardDistributorAddress: EthAddress<0x5c29eba61f19c908dc3e559a80753794ab812e45>, + governanceProposerAddress: EthAddress<0x0b3761732e242dbf9491250b8db3b68f61dc6352>, + governanceAddress: EthAddress<0xeeab717ebb2dfdb1d19a6638dffe141e4111c9e2>, + slashFactoryAddress: EthAddress<0xbca51eb257b56ee0b3ef2bbdf865b2cc32f9b39b>, + feeAssetHandlerAddress: EthAddress<0xc3181f43e89f2db4949ec0dddb4e332ef188f66c> + }, + protocolContractAddresses: { + classRegisterer: AztecAddress<0x0000000000000000000000000000000000000000000000000000000000000003>, + feeJuice: AztecAddress<0x0000000000000000000000000000000000000000000000000000000000000005>, + instanceDeployer: AztecAddress<0x0000000000000000000000000000000000000000000000000000000000000002>, + multiCallEntrypoint: AztecAddress<0x0000000000000000000000000000000000000000000000000000000000000004> + } +} +``` + +Great! The Sandbox is running and we are able to interact with it. + +## Load accounts + +The sandbox is preloaded with multiple accounts so you don't have to sit and create them. Let's load these accounts. Add this code to the `main()` function in `index.ts` below the code that's there: + +```typescript title="load_accounts" showLineNumbers +////////////// LOAD SOME ACCOUNTS FROM THE SANDBOX ////////////// +// The sandbox comes with a set of created accounts. Load them +const accounts = await getDeployedTestAccountsWallets(pxe); +const aliceWallet = accounts[0]; +const bobWallet = accounts[1]; +const alice = aliceWallet.getAddress(); +const bob = bobWallet.getAddress(); +logger.info(`Loaded alice's account at ${alice.toString()}`); +logger.info(`Loaded bob's account at ${bob.toString()}`); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts#L110-L120 + + +An explanation on accounts on Aztec can be found [here](../../../../aztec/concepts/accounts/index.md). + +## Deploy a contract + +Now that we have our accounts loaded, let's move on to deploy our pre-compiled token smart contract. You can find the full code for the contract [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-contracts/contracts/app/token_contract/src). Add this to `index.ts` below the code you added earlier: + +```typescript title="Deployment" showLineNumbers +////////////// DEPLOY OUR TOKEN CONTRACT ////////////// + +const initialSupply = 1_000_000n; + +const tokenContractAlice = await deployToken(aliceWallet, initialSupply, logger); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts#L122-L128 + + +`yarn start` will now give something like this: + +``` +[21:35:17.434] INFO: e2e:token Loaded alice's account at 0x2a4c7cded97e40031f16c917ab8da8852a1b6da7bf136b32c163d8b16a80acba +[21:35:17.434] INFO: e2e:token Loaded bob's account at 0x0da6dbf69a48f02b09dbdb18fa77bfa526771d3df2ab75b66cb9c69de9002648 +[21:35:17.434] INFO: e2e:token Deploying Token contract... +[21:35:20.646] INFO: aztecjs:deploy_sent_tx Contract 0x05db83a57befe646e5ce1dcd9bccce4e3af64e5c1628f69be520e527863805dd successfully deployed. +[21:35:23.995] INFO: e2e:token L2 contract deployed +``` + +We can break this down as follows: + +1. We create and send a contract deployment transaction to the network. +2. We wait for it to be successfully mined. +3. We retrieve the transaction receipt containing the transaction status and contract address. +4. We connect to the contract with Alice +5. Alice initialize the contract with herself as the admin and a minter. +6. Alice privately mints 1,000,000 tokens to herself + +## View the balance of an account + +A token contract wouldn't be very useful if you aren't able to query the balance of an account. As part of the deployment, tokens were minted to Alice. We can now call the contract's `balance_of_private()` function to retrieve the balances of the accounts. + +Call the `balance_of_private` function using the following code (paste this): + +```typescript title="Balance" showLineNumbers + +////////////// QUERYING THE TOKEN BALANCE FOR EACH ACCOUNT ////////////// + +// Bob wants to mint some funds, the contract is already deployed, create an abstraction and link it his wallet +// Since we already have a token link, we can simply create a new instance of the contract linked to Bob's wallet +const tokenContractBob = tokenContractAlice.withWallet(bobWallet); + +let aliceBalance = await tokenContractAlice.methods.balance_of_private(alice).simulate(); +logger.info(`Alice's balance ${aliceBalance}`); + +let bobBalance = await tokenContractBob.methods.balance_of_private(bob).simulate(); +logger.info(`Bob's balance ${bobBalance}`); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts#L133-L147 + + +Running now should yield output: + +``` +[21:38:42.789] INFO: e2e:token Alice's balance 1000000 +[21:38:42.901] INFO: e2e:token Bob's balance 0 +``` + +Above, we created a second instance of the `TokenContract` contract class. +This time pertaining to Bob. +This class offers a TypeScript bindings of our `Token` contract.. +We then call `balance_of_private()` as a `view` method. +View methods can be thought as read-only. +No transaction is submitted as a result but a user's state can be queried. + +We can see that each account has the expected balance of tokens. + +### Calling a view function + + +Unconstrained function call + + +## Create and submit a transaction + +### Transfer + +Now lets transfer some funds from Alice to Bob by calling the `transfer` function on the contract. This function takes 2 arguments: + +1. The recipient. +2. The quantity of tokens to be transferred. + +Here is the Typescript code to call the `transfer` function, add this to your `index.ts` at the bottom of the `main` function: + +```typescript title="Transfer" showLineNumbers +////////////// TRANSFER FUNDS FROM ALICE TO BOB ////////////// + +// We will now transfer tokens from ALice to Bob +const transferQuantity = 543n; +logger.info(`Transferring ${transferQuantity} tokens from Alice to Bob...`); +await tokenContractAlice.methods.transfer(bob, transferQuantity).send().wait(); + +// Check the new balances +aliceBalance = await tokenContractAlice.methods.balance_of_private(alice).simulate(); +logger.info(`Alice's balance ${aliceBalance}`); + +bobBalance = await tokenContractBob.methods.balance_of_private(bob).simulate(); +logger.info(`Bob's balance ${bobBalance}`); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts#L152-L166 + + +Our output should now look like this: + +``` +[21:38:42.901] INFO: e2e:token Transferring 543 tokens from Alice to Bob... +[21:38:46.384] INFO: e2e:token Alice's balance 999457 +[21:38:46.644] INFO: e2e:token Bob's balance 543 +``` + +Here, we used the same contract abstraction as was previously used for reading Alice's balance. But this time we called `send()` generating and sending a transaction to the network. After waiting for the transaction to settle we were able to check the new balance values. + +### Mint + +Finally, the contract has several `mint` functions that can be used to generate new tokens for an account. +We will focus only on `mint_to_private`. +This function has private and public execution components, but it mints tokens privately. +This function takes: + +1. A minter (`from`) +2. A recipient +3. An amount of tokens to mint + +This function starts as private to set up the creation of a [partial note](../../../../aztec/concepts/advanced/storage/partial_notes.md). The private function calls a public function that checks that the minter is authorized to mint new tokens an increments the public total supply. The recipient of the tokens remains private, but the minter and the amount of tokens minted are public. + +Let's now use these functions to mint some tokens to Bob's account using Typescript, add this to `index.ts`: + +```typescript title="Mint" showLineNumbers +////////////// MINT SOME MORE TOKENS TO BOB'S ACCOUNT ////////////// + +// Now mint some further funds for Bob + +// Alice is nice and she adds Bob as a minter +await tokenContractAlice.methods.set_minter(bob, true).send().wait(); + +const mintQuantity = 10_000n; +await mintTokensToPrivate(tokenContractBob, bobWallet, bob, mintQuantity); + +// Check the new balances +aliceBalance = await tokenContractAlice.methods.balance_of_private(alice).simulate(); +logger.info(`Alice's balance ${aliceBalance}`); + +bobBalance = await tokenContractBob.methods.balance_of_private(bob).simulate(); +logger.info(`Bob's balance ${bobBalance}`); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts#L171-L188 + + +Our complete output should now be something like: + +``` +[21:40:29.635] INFO: e2e:token Alice's balance 999457 +[21:40:29.927] INFO: e2e:token Bob's balance 10543 +``` + +That's it! We have successfully deployed a token contract to an instance of the Aztec network and mined private state-transitioning transactions. We have also queried the resulting state all via the interfaces provided by the contract. To see exactly what has happened here, you can learn about the transaction flow [on the Concepts page here](../../../../aztec/concepts/transactions.md). + +## Next Steps + +### Build a fullstack Aztec project + +Follow the [dapp tutorial](./simple_dapp/index.md). + +### Optional: Learn more about concepts mentioned here + +- [Authentication witness](../../../../aztec/concepts/advanced/authwit.md) +- [Functions under the hood](../../../../aztec/smart_contracts/functions/function_transforms.md) diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/0_project_setup.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/0_project_setup.md new file mode 100644 index 000000000000..e5e85b3fbf8d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/0_project_setup.md @@ -0,0 +1,41 @@ +# Setting up your project + +Let's start by setting up a regular Javascript NodeJS project. Feel free to skip this part if you're already familiar with project setup and head directly to connecting to the Sandbox. + +## Create a new project + +We'll use [`yarn`](https://yarnpkg.com/) for managing our project and dependencies, though you can also use `npm` or your Javascript package manager of choice. + +1. Ensure your node version is compatible with the [prerequisites](../../../../getting_started.md#prerequisites) + +```sh +node -v +``` + +and ensure that you are running sandbox version 0.88.0. + +```bash +aztec-up 0.88.0 +``` + +2. Create a new folder and initialize a new project. + +```sh +mkdir sample-dapp +cd sample-dapp +yarn init -yp +``` + +3. Add the `aztec.js` and `accounts` libraries as dependencies. Also add `noir-contracts.js` for quick use of example contracts: + +```sh +yarn add @aztec/aztec.js@0.88.0 @aztec/accounts@0.88.0 @aztec/noir-contracts.js@0.88.0 +``` + +and yarn config: + +```sh +echo "nodeLinker: node-modules" > .yarnrc.yml +``` + +Then run: `yarn install` diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/1_pxe_service.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/1_pxe_service.md new file mode 100644 index 000000000000..4fa667fb43af --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/1_pxe_service.md @@ -0,0 +1,82 @@ +# Connecting to the Private eXecution Environment (PXE) + +PXE is a component of the Aztec Protocol that provides a private execution environment for your application. + +As an app developer, the PXE interface provides you with access to the user's accounts and their private state, as well as a connection to the network for accessing public global state. + +The Aztec Sandbox (reference [here](../../../../reference/environment_reference/sandbox-reference.md)) runs a local PXE and an Aztec Node, both connected to a local Ethereum development node like Anvil. + +The Sandbox also includes a set of pre-initialized accounts that you can use from your app. + +In this section, we'll connect to the Sandbox from our project. + +## Create PXE client + +We'll use the `createPXEClient` function from `aztec.js` to connect to the Sandbox. +Sandbox exposes a HTTP JSON-RPC interface of PXE. +By default it runs on `localhost:8080`. +To test the connection works, we'll request and print the node's chain id. + +Let's create our first file `src/index.mjs` with the following contents: + +```javascript title="all" showLineNumbers +import { createPXEClient } from '@aztec/aztec.js'; + +const { PXE_URL = 'http://localhost:8080' } = process.env; + +async function main() { + const pxe = await createPXEClient(PXE_URL); + const { l1ChainId } = await pxe.getNodeInfo(); + console.log(`Connected to chain ${l1ChainId}`); +} + +main().catch(err => { + console.error(`Error in app: ${err}`); + process.exit(1); +}); +``` +> Source code: yarn-project/end-to-end/src/sample-dapp/connect.mjs#L1-L16 + + +Make sure the [Sandbox is running](../../../../getting_started.md) and run the example + +```bash +node src/index.mjs +``` + +and you should see the following output: + +``` +Connected to chain 31337 +``` + +:::info +Should the above fail due to a connection error, make sure the Sandbox is running locally and on port 8080. +::: + +## Load user accounts + +In sandbox PXE comes with a set of pre-initialized accounts that you can use from your app. +Let's try loading the accounts: + +```javascript title="showAccounts" showLineNumbers +async function showAccounts(pxe) { + const accounts = await pxe.getRegisteredAccounts(); + console.log(`User accounts:\n${accounts.map(a => a.address).join('\n')}`); +} +``` +> Source code: yarn-project/end-to-end/src/sample-dapp/index.mjs#L12-L17 + + +Call the `showAccounts` function from `main`, run again the above, and you should see something like: + +``` +User accounts: +0x0c8a6673d7676cc80aaebe7fa7504cf51daa90ba906861bfad70a58a98bf5a7d +0x226f8087792beff8d5009eb94e65d2a4a505b70baf4a9f28d33c8d620b0ba972 +0x0e1f60e8566e2c6d32378bdcadb7c63696e853281be798c107266b8c3a88ea9b +``` + +## Next steps + +With a working connection to PXE, let's now setup our application by [compiling and deploying our contracts](./2_contract_deployment.md). diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/2_contract_deployment.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/2_contract_deployment.md new file mode 100644 index 000000000000..9d227b1ee97e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/2_contract_deployment.md @@ -0,0 +1,889 @@ +# Contract Deployment + +To add contracts to your application, we'll start by creating a new `aztec-nargo` project. We'll then compile the contracts, and write a simple script to deploy them to our Sandbox. + +:::info +Follow the instructions [here](../../../../getting_started.md) to install `aztec-nargo` if you haven't done so already. +::: + +## Initialize Aztec project + +Create a new `contracts` folder, and from there, initialize a new project called `token`: + +```sh +mkdir contracts && cd contracts +aztec-nargo new --contract token +``` + +Then, open the `contracts/token/Nargo.toml` configuration file, and add the `aztec.nr` and `value_note` libraries as dependencies: + +```toml +[dependencies] +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/aztec" } +authwit = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/authwit"} +uint_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/uint-note" } +compressed_string = {git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="noir-projects/aztec-nr/compressed-string"} +``` + +Last, copy-paste the code from the `Token` contract into `contracts/token/main.nr`: + +```rust title="token_all" showLineNumbers +mod types; +mod test; + +use dep::aztec::macros::aztec; + +// Minimal token implementation that supports `AuthWit` accounts. +// The auth message follows a similar pattern to the cross-chain message and includes a designated caller. +// The designated caller is ALWAYS used here, and not based on a flag as cross-chain. +// message hash = H([caller, contract, selector, ...args]) +// To be read as `caller` calls function at `contract` defined by `selector` with `args` +// Including a nonce in the message hash ensures that the message can only be used once. +#[aztec] +pub contract Token { + // Libs + use std::ops::{Add, Sub}; + + use dep::compressed_string::FieldCompressedString; + + use dep::aztec::{ + context::{PrivateCallInterface, PrivateContext}, + event::event_interface::{emit_event_in_private_log, PrivateLogContent}, + macros::{ + events::event, + functions::{initializer, internal, private, public, utility, view}, + storage::storage, + }, + messages::logs::note::{encode_and_encrypt_note, encode_and_encrypt_note_unconstrained}, + prelude::{AztecAddress, Map, PublicContext, PublicImmutable, PublicMutable}, + }; + + use dep::uint_note::uint_note::{PartialUintNote, UintNote}; + use aztec::protocol_types::traits::ToField; + + use dep::authwit::auth::{ + assert_current_call_valid_authwit, assert_current_call_valid_authwit_public, + compute_authwit_nullifier, + }; + + use crate::types::balance_set::BalanceSet; + + + // In the first transfer iteration we are computing a lot of additional information (validating inputs, retrieving + // keys, etc.), so the gate count is already relatively high. We therefore only read a few notes to keep the happy + // case with few constraints. + global INITIAL_TRANSFER_CALL_MAX_NOTES: u32 = 2; + // All the recursive call does is nullify notes, meaning the gate count is low, but it is all constant overhead. We + // therefore read more notes than in the base case to increase the efficiency of the overhead, since this results in + // an overall small circuit regardless. + global RECURSIVE_TRANSFER_CALL_MAX_NOTES: u32 = 8; + + #[event] + struct Transfer { + from: AztecAddress, + to: AztecAddress, + amount: u128, + } + + #[storage] + struct Storage { + admin: PublicMutable, + minters: Map, Context>, + balances: Map, Context>, + total_supply: PublicMutable, + public_balances: Map, Context>, + symbol: PublicImmutable, + name: PublicImmutable, + decimals: PublicImmutable, + } + + #[public] + #[initializer] + fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) { + assert(!admin.is_zero(), "invalid admin"); + storage.admin.write(admin); + storage.minters.at(admin).write(true); + storage.name.initialize(FieldCompressedString::from_string(name)); + storage.symbol.initialize(FieldCompressedString::from_string(symbol)); + storage.decimals.initialize(decimals); + } + + #[public] + fn set_admin(new_admin: AztecAddress) { + assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); + storage.admin.write(new_admin); + } + + #[public] + #[view] + fn public_get_name() -> FieldCompressedString { + storage.name.read() + } + + #[private] + #[view] + fn private_get_name() -> FieldCompressedString { + storage.name.read() + } + + #[public] + #[view] + fn public_get_symbol() -> pub FieldCompressedString { + storage.symbol.read() + } + + #[private] + #[view] + fn private_get_symbol() -> pub FieldCompressedString { + storage.symbol.read() + } + + #[public] + #[view] + fn public_get_decimals() -> pub u8 { + storage.decimals.read() + } + + #[private] + #[view] + fn private_get_decimals() -> pub u8 { + storage.decimals.read() + } + + #[public] + #[view] + fn get_admin() -> Field { + storage.admin.read().to_field() + } + + #[public] + #[view] + fn is_minter(minter: AztecAddress) -> bool { + storage.minters.at(minter).read() + } + + #[public] + #[view] + fn total_supply() -> u128 { + storage.total_supply.read() + } + + #[public] + #[view] + fn balance_of_public(owner: AztecAddress) -> u128 { + storage.public_balances.at(owner).read() + } + + #[public] + fn set_minter(minter: AztecAddress, approve: bool) { + assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); + storage.minters.at(minter).write(approve); + } + + #[public] + fn mint_to_public(to: AztecAddress, amount: u128) { + assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); + let new_balance = storage.public_balances.at(to).read().add(amount); + let supply = storage.total_supply.read().add(amount); + storage.public_balances.at(to).write(new_balance); + storage.total_supply.write(supply); + } + + #[public] + fn transfer_in_public( + from: AztecAddress, + to: AztecAddress, + amount: u128, + authwit_nonce: Field, + ) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit_public(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + let from_balance = storage.public_balances.at(from).read().sub(amount); + storage.public_balances.at(from).write(from_balance); + let to_balance = storage.public_balances.at(to).read().add(amount); + storage.public_balances.at(to).write(to_balance); + } + + #[public] + fn burn_public(from: AztecAddress, amount: u128, authwit_nonce: Field) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit_public(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + let from_balance = storage.public_balances.at(from).read().sub(amount); + storage.public_balances.at(from).write(from_balance); + let new_supply = storage.total_supply.read().sub(amount); + storage.total_supply.write(new_supply); + } + + #[private] + fn transfer_to_public( + from: AztecAddress, + to: AztecAddress, + amount: u128, + authwit_nonce: Field, + ) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + + storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note( + &mut context, + from, + from, + )); + Token::at(context.this_address())._increase_public_balance(to, amount).enqueue(&mut context); + } + + /// Transfers tokens from private balance of `from` to public balance of `to` and prepares a partial note for + /// receiving change for `from`. + /// + /// This is an optimization that combines two operations into one to reduce contract calls: + /// 1. Transfers `amount` tokens from `from`'s private balance to `to`'s public balance + /// 2. Creates a partial note that can later be used to receive change back to `from`'s private balance + /// + /// This pattern is useful when interacting with contracts that: + /// - Receive tokens from a user's private balance + /// - Need to wait until public execution to determine how many tokens to return (e.g. AMM, FPC) + /// - Will return tokens to the user's private balance + /// + /// The contract can use the returned partial note to complete the transfer back to private + /// once the final amount is known during public execution. + #[private] + fn transfer_to_public_and_prepare_private_balance_increase( + from: AztecAddress, + to: AztecAddress, + amount: u128, + authwit_nonce: Field, + ) -> PartialUintNote { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + + storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note( + &mut context, + from, + from, + )); + Token::at(context.this_address())._increase_public_balance(to, amount).enqueue(&mut context); + + // We prepare the private balance increase (the partial note for the change). + _prepare_private_balance_increase(from, from, &mut context, storage) + } + + #[private] + fn transfer(to: AztecAddress, amount: u128) { + let from = context.msg_sender(); + + // We reduce `from`'s balance by amount by recursively removing notes over potentially multiple calls. This + // method keeps the gate count for each individual call low - reading too many notes at once could result in + // circuits in which proving is not feasible. + // Since the sum of the amounts in the notes we nullified was potentially larger than amount, we create a new + // note for `from` with the change amount, e.g. if `amount` is 10 and two notes are nullified with amounts 8 and + // 5, then the change will be 3 (since 8 + 5 - 10 = 3). + let change = subtract_balance( + &mut context, + storage, + from, + amount, + INITIAL_TRANSFER_CALL_MAX_NOTES, + ); + storage.balances.at(from).add(from, change).emit(encode_and_encrypt_note_unconstrained( + &mut context, + from, + from, + )); + storage.balances.at(to).add(to, amount).emit(encode_and_encrypt_note_unconstrained( + &mut context, + to, + from, + )); + + // We don't constrain encryption of the note log in `transfer` (unlike in `transfer_in_private`) because the transfer + // function is only designed to be used in situations where the event is not strictly necessary (e.g. payment to + // another person where the payment is considered to be successful when the other party successfully decrypts a + // note). + emit_event_in_private_log( + Transfer { from, to, amount }, + &mut context, + from, + to, + PrivateLogContent.NO_CONSTRAINTS, + ); + } + + #[contract_library_method] + fn subtract_balance( + context: &mut PrivateContext, + storage: Storage<&mut PrivateContext>, + account: AztecAddress, + amount: u128, + max_notes: u32, + ) -> u128 { + let subtracted = storage.balances.at(account).try_sub(amount, max_notes); + // Failing to subtract any amount means that the owner was unable to produce more notes that could be nullified. + // We could in some cases fail early inside try_sub if we detected that fewer notes than the maximum were + // returned and we were still unable to reach the target amount, but that'd make the code more complicated, and + // optimizing for the failure scenario is not as important. + assert(subtracted > 0 as u128, "Balance too low"); + if subtracted >= amount { + // We have achieved our goal of nullifying notes that add up to more than amount, so we return the change + subtracted - amount + } else { + // try_sub failed to nullify enough notes to reach the target amount, so we compute the amount remaining + // and try again. + let remaining = amount - subtracted; + compute_recurse_subtract_balance_call(*context, account, remaining).call(context) + } + } + + // TODO(#7729): apply no_predicates to the contract interface method directly instead of having to use a wrapper + // like we do here. + #[no_predicates] + #[contract_library_method] + fn compute_recurse_subtract_balance_call( + context: PrivateContext, + account: AztecAddress, + remaining: u128, + ) -> PrivateCallInterface<25, u128, 1> { + Token::at(context.this_address())._recurse_subtract_balance(account, remaining) + } + + #[internal] + #[private] + fn _recurse_subtract_balance(account: AztecAddress, amount: u128) -> u128 { + subtract_balance( + &mut context, + storage, + account, + amount, + RECURSIVE_TRANSFER_CALL_MAX_NOTES, + ) + } + + /** + * Cancel a private authentication witness. + * @param inner_hash The inner hash of the authwit to cancel. + */ + #[private] + fn cancel_authwit(inner_hash: Field) { + let on_behalf_of = context.msg_sender(); + let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); + context.push_nullifier(nullifier); + } + + #[private] + fn transfer_in_private( + from: AztecAddress, + to: AztecAddress, + amount: u128, + authwit_nonce: Field, + ) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + + storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note( + &mut context, + from, + from, + )); + storage.balances.at(to).add(to, amount).emit(encode_and_encrypt_note(&mut context, to, from)); + } + + #[private] + fn burn_private(from: AztecAddress, amount: u128, authwit_nonce: Field) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note( + &mut context, + from, + from, + )); + Token::at(context.this_address())._reduce_total_supply(amount).enqueue(&mut context); + } + + // Transfers token `amount` from public balance of message sender to a private balance of `to`. + #[private] + fn transfer_to_private(to: AztecAddress, amount: u128) { + // `from` is the owner of the public balance from which we'll subtract the `amount`. + let from = context.msg_sender(); + let token = Token::at(context.this_address()); + + // We prepare the private balance increase (the partial note). + let partial_note = _prepare_private_balance_increase(from, to, &mut context, storage); + + // At last we finalize the transfer. Usage of the `unsafe` method here is safe because we set the `from` + // function argument to a message sender, guaranteeing that he can transfer only his own tokens. + token._finalize_transfer_to_private_unsafe(from, amount, partial_note).enqueue(&mut context); + } + + /// Prepares an increase of private balance of `to` (partial note). The increase needs to be finalized by calling + /// some of the finalization functions (`finalize_transfer_to_private`, `finalize_mint_to_private`) with the + /// returned partial note. + #[private] + fn prepare_private_balance_increase(to: AztecAddress, from: AztecAddress) -> PartialUintNote { + // ideally we'd not have `from` here, but we do need a `from` address to produce a tagging secret with `to`. + _prepare_private_balance_increase(from, to, &mut context, storage) + } + + /// This function exists separately from `prepare_private_balance_increase` solely as an optimization as it allows + /// us to have it inlined in the `transfer_to_private` function which results in one fewer kernel iteration. Note + /// that in this case we don't pass `completer` as an argument to this function because in all the callsites we + /// want to use the message sender as the completer anyway. + /// + /// TODO(#9180): Consider adding macro support for functions callable both as an entrypoint and as an internal + /// function. + #[contract_library_method] + fn _prepare_private_balance_increase( + from: AztecAddress, // sender of the tag + to: AztecAddress, + context: &mut PrivateContext, + storage: Storage<&mut PrivateContext>, + ) -> PartialUintNote { + let partial_note = UintNote::partial( + to, + storage.balances.at(to).set.storage_slot, + context, + to, + from, + context.msg_sender(), + ); + + partial_note + } + + /// Finalizes a transfer of token `amount` from public balance of `msg_sender` to a private balance of `to`. + /// The transfer must be prepared by calling `prepare_private_balance_increase` from `msg_sender` account and + /// the resulting `partial_note` must be passed as an argument to this function. + /// + /// Note that this contract does not protect against a `partial_note` being used multiple times and it is up to + /// the caller of this function to ensure that it doesn't happen. If the same `partial_note` is used multiple + /// times, the token `amount` would most likely get lost (the partial note log processing functionality would fail + /// to find the pending partial note when trying to complete it). + #[public] + fn finalize_transfer_to_private(amount: u128, partial_note: PartialUintNote) { + // Completer is the entity that can complete the partial note. In this case, it's the same as the account + // `from` from whose balance we're subtracting the `amount`. + let from_and_completer = context.msg_sender(); + _finalize_transfer_to_private( + from_and_completer, + amount, + partial_note, + &mut context, + storage, + ); + } + + /// Finalizes a transfer of token `amount` from private balance of `from` to a private balance of `to`. + /// The transfer must be prepared by calling `prepare_private_balance_increase` from `from` account and + /// the resulting `partial_note` must be passed as an argument to this function. + /// + /// Note that this contract does not protect against a `partial_note` being used multiple times and it is up to + /// the caller of this function to ensure that it doesn't happen. If the same `partial_note` is used multiple + /// times, the token `amount` would most likely get lost (the partial note log processing functionality would fail + /// to find the pending partial note when trying to complete it). + #[private] + fn finalize_transfer_to_private_from_private( + from: AztecAddress, + partial_note: PartialUintNote, + amount: u128, + authwit_nonce: Field, + ) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(authwit_nonce == 0, "invalid authwit nonce"); + } + + // First we subtract the `amount` from the private balance of `from` + storage.balances.at(from).sub(from, amount).emit(encode_and_encrypt_note( + &mut context, + from, + from, + )); + + partial_note.complete_from_private(&mut context, context.msg_sender(), amount); + } + + /// This is a wrapper around `_finalize_transfer_to_private` placed here so that a call + /// to `_finalize_transfer_to_private` can be enqueued. Called unsafe as it does not check `from_and_completer` + /// (this has to be done in the calling function). + #[public] + #[internal] + fn _finalize_transfer_to_private_unsafe( + from_and_completer: AztecAddress, + amount: u128, + partial_note: PartialUintNote, + ) { + _finalize_transfer_to_private( + from_and_completer, + amount, + partial_note, + &mut context, + storage, + ); + } + + // In all the flows in this contract, `from` (the account from which we're subtracting the `amount`) and + // `completer` (the entity that can complete the partial note) are the same so we represent them with a single + // argument. + #[contract_library_method] + fn _finalize_transfer_to_private( + from_and_completer: AztecAddress, + amount: u128, + partial_note: PartialUintNote, + context: &mut PublicContext, + storage: Storage<&mut PublicContext>, + ) { + // First we subtract the `amount` from the public balance of `from_and_completer` + let from_balance = storage.public_balances.at(from_and_completer).read().sub(amount); + storage.public_balances.at(from_and_completer).write(from_balance); + + // We finalize the transfer by completing the partial note. + partial_note.complete(context, from_and_completer, amount); + } + + /// Mints token `amount` to a private balance of `to`. Message sender has to have minter permissions (checked + /// in the enqueued call). + #[private] + fn mint_to_private( + // TODO(benesjan): This allows minter to set arbitrary `from`. That seems undesirable. Will nuke it in a followup PR. + from: AztecAddress, // sender of the tag + to: AztecAddress, + amount: u128, + ) { + let token = Token::at(context.this_address()); + + // We prepare the partial note to which we'll "send" the minted amount. + let partial_note = _prepare_private_balance_increase(from, to, &mut context, storage); + + // At last we finalize the mint. Usage of the `unsafe` method here is safe because we set + // the `minter_and_completer` function argument to a message sender, guaranteeing that only a message sender + // with minter permissions can successfully execute the function. + token._finalize_mint_to_private_unsafe(context.msg_sender(), amount, partial_note).enqueue( + &mut context, + ); + } + + /// Finalizes a mint of token `amount` to a private balance of `to`. The mint must be prepared by calling + /// `prepare_private_balance_increase` first and the resulting + /// `partial_note` must be passed as an argument to this function. + /// + /// Note: This function is only an optimization as it could be replaced by a combination of `mint_to_public` + /// and `finalize_transfer_to_private`. It is however used very commonly so it makes sense to optimize it + /// (e.g. used during token bridging, in AMM liquidity token etc.). + #[public] + fn finalize_mint_to_private(amount: u128, partial_note: PartialUintNote) { + // Completer is the entity that can complete the partial note. In this case, it's the same as the minter + // account. + let minter_and_completer = context.msg_sender(); + assert(storage.minters.at(minter_and_completer).read(), "caller is not minter"); + + _finalize_mint_to_private( + minter_and_completer, + amount, + partial_note, + &mut context, + storage, + ); + } + + /// This is a wrapper around `_finalize_mint_to_private` placed here so that a call + /// to `_finalize_mint_to_private` can be enqueued. Called unsafe as it does not check `minter_and_completer` (this + /// has to be done in the calling function). + #[public] + #[internal] + fn _finalize_mint_to_private_unsafe( + minter_and_completer: AztecAddress, + amount: u128, + partial_note: PartialUintNote, + ) { + // We check the minter permissions as it was not done in `mint_to_private` function. + assert(storage.minters.at(minter_and_completer).read(), "caller is not minter"); + _finalize_mint_to_private( + minter_and_completer, + amount, + partial_note, + &mut context, + storage, + ); + } + + #[contract_library_method] + fn _finalize_mint_to_private( + completer: AztecAddress, // entity that can complete the partial note + amount: u128, + partial_note: PartialUintNote, + context: &mut PublicContext, + storage: Storage<&mut PublicContext>, + ) { + // First we increase the total supply by the `amount` + let supply = storage.total_supply.read().add(amount); + storage.total_supply.write(supply); + + // We finalize the transfer by completing the partial note. + partial_note.complete(context, completer, amount); + } + + /// Internal /// + /// TODO(#9180): Consider adding macro support for functions callable both as an entrypoint and as an internal + /// function. + #[public] + #[internal] + fn _increase_public_balance(to: AztecAddress, amount: u128) { + _increase_public_balance_inner(to, amount, storage); + } + + #[contract_library_method] + fn _increase_public_balance_inner( + to: AztecAddress, + amount: u128, + storage: Storage<&mut PublicContext>, + ) { + let new_balance = storage.public_balances.at(to).read().add(amount); + storage.public_balances.at(to).write(new_balance); + } + + #[public] + #[internal] + fn _reduce_total_supply(amount: u128) { + // Only to be called from burn. + let new_supply = storage.total_supply.read().sub(amount); + storage.total_supply.write(new_supply); + } + + #[utility] + pub(crate) unconstrained fn balance_of_private(owner: AztecAddress) -> u128 { + storage.balances.at(owner).balance_of() + } +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L1-L728 + + +### Helper files + +:::info +Remove the `mod test;` line from `contracts/token/src/main.nr` as we will not be using TXE tests in this tutorial. +::: + +The `Token` contract also requires some helper files. You can view the files [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/v0.88.0/noir-projects/noir-contracts/contracts/app/token_contract/src). Copy the `types.nr` and the `types` folder into `contracts/token/src`. + +Add this `balance_set.nr` file at `token/src/types/balance_set.nr`. + +```rust title="balance_set" showLineNumbers +use dep::aztec::{ + context::{PrivateContext, UtilityContext}, + note::{ + note_emission::OuterNoteEmission, note_getter_options::SortOrder, + note_interface::NoteProperties, retrieved_note::RetrievedNote, + }, + protocol_types::{address::AztecAddress, constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL}, + state_vars::storage::Storage, +}; +use dep::aztec::prelude::{NoteGetterOptions, NoteViewerOptions, PrivateSet}; +use dep::uint_note::uint_note::UintNote; +use std::ops::Add; + +pub struct BalanceSet { + pub set: PrivateSet, +} + +// TODO(#13824): remove this impl once we allow structs to hold state variables. +impl Storage<1> for BalanceSet { + fn get_storage_slot(self) -> Field { + self.set.get_storage_slot() + } +} + +impl BalanceSet { + pub fn new(context: Context, storage_slot: Field) -> Self { + assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); + Self { set: PrivateSet::new(context, storage_slot) } + } +} + +impl BalanceSet { + pub unconstrained fn balance_of(self: Self) -> u128 { + self.balance_of_with_offset(0) + } + + pub unconstrained fn balance_of_with_offset(self: Self, offset: u32) -> u128 { + let mut balance = 0 as u128; + let mut options = NoteViewerOptions::new(); + let notes = self.set.view_notes(options.set_offset(offset)); + for i in 0..options.limit { + if i < notes.len() { + balance = balance + notes.get_unchecked(i).get_value(); + } + } + if (notes.len() == options.limit) { + balance = balance + self.balance_of_with_offset(offset + options.limit); + } + + balance + } +} + +impl BalanceSet<&mut PrivateContext> { + pub fn add(self: Self, owner: AztecAddress, addend: u128) -> OuterNoteEmission { + if addend == 0 as u128 { + OuterNoteEmission::new(Option::none()) + } else { + // We fetch the nullifier public key hash from the registry / from our PXE + let mut addend_note = UintNote::new(addend, owner); + + OuterNoteEmission::new(Option::some(self.set.insert(addend_note))) + } + } + + pub fn sub(self: Self, owner: AztecAddress, amount: u128) -> OuterNoteEmission { + let subtracted = self.try_sub(amount, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL); + + // try_sub may have substracted more or less than amount. We must ensure that we subtracted at least as much as + // we needed, and then create a new note for the owner for the change (if any). + assert(subtracted >= amount, "Balance too low"); + self.add(owner, subtracted - amount) + } + + // Attempts to remove 'target_amount' from the owner's balance. try_sub returns how much was actually subtracted + // (i.e. the sum of the value of nullified notes), but this subtracted amount may be more or less than the target + // amount. + // This may seem odd, but is unfortunately unavoidable due to the number of notes available and their amounts being + // unknown. What try_sub does is a best-effort attempt to consume as few notes as possible that add up to more than + // `target_amount`. + // The `max_notes` parameter is used to fine-tune the number of constraints created by this function. The gate count + // scales relatively linearly with `max_notes`, but a lower `max_notes` parameter increases the likelihood of + // `try_sub` subtracting an amount smaller than `target_amount`. + pub fn try_sub(self: Self, target_amount: u128, max_notes: u32) -> u128 { + // We are using a preprocessor here (filter applied in an unconstrained context) instead of a filter because + // we do not need to prove correct execution of the preprocessor. + // Because the `min_sum` notes is not constrained, users could choose to e.g. not call it. However, all this + // might result in is simply higher DA costs due to more nullifiers being emitted. Since we don't care + // about proving optimal note usage, we can save these constraints and make the circuit smaller. + let options = NoteGetterOptions::with_preprocessor(preprocess_notes_min_sum, target_amount) + .sort(UintNote::properties().value, SortOrder.DESC) + .set_limit(max_notes); + let notes = self.set.pop_notes(options); + + let mut subtracted = 0 as u128; + for i in 0..options.limit { + if i < notes.len() { + let note = notes.get_unchecked(i); + subtracted = subtracted + note.get_value(); + } + } + + subtracted + } +} + +// Computes the partial sum of the notes array, stopping once 'min_sum' is reached. This can be used to minimize the +// number of notes read that add to some value, e.g. when transferring some amount of tokens. +// The preprocessor (a filter applied in an unconstrained context) does not check if total sum is larger or equal to +// 'min_sum' - all it does is remove extra notes if it does reach that value. +// Note that proper usage of this preprocessor requires for notes to be sorted in descending order. +pub fn preprocess_notes_min_sum( + notes: [Option>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], + min_sum: u128, +) -> [Option>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] { + let mut selected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; + let mut sum = 0 as u128; + for i in 0..notes.len() { + // Because we process notes in retrieved order, notes need to be sorted in descending amount order for this + // filter to be useful. Consider a 'min_sum' of 4, and a set of notes with amounts [3, 2, 1, 1, 1, 1, 1]. If + // sorted in descending order, the filter will only choose the notes with values 3 and 2, but if sorted in + // ascending order it will choose 4 notes of value 1. + if notes[i].is_some() & sum < min_sum { + let retrieved_note = notes[i].unwrap_unchecked(); + selected[i] = Option::some(retrieved_note); + sum = sum.add(retrieved_note.note.get_value()); + } + } + selected +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/types/balance_set.nr#L1-L136 + + +## Compile your contract + +We'll now use `aztec-nargo` to compile. + +Now run the following from your contract folder (containing Nargo.toml): + +```sh +aztec-nargo compile +``` + +## Deploy your contracts + +Let's now write a script for deploying your contracts to the Sandbox. We'll create a Private eXecution Environment (PXE) client, and then use the `ContractDeployer` class to deploy our contracts, and store the deployment address to a local JSON file. + +Create a new file `src/deploy.mjs`. We import the contract artifacts we have generated plus the dependencies we'll need, and then we can deploy the contracts by adding the following code to the `src/deploy.mjs` file. + +```js +// src/deploy.mjs +import { getInitialTestAccountsWallets } from '@aztec/accounts/testing'; +import { Contract, createPXEClient, loadContractArtifact, waitForPXE } from '@aztec/aztec.js'; +import TokenContractJson from "../contracts/token/target/token-Token.json" with { type: "json" }; +import { writeFileSync } from 'fs'; + +const TokenContractArtifact = loadContractArtifact(TokenContractJson); + +const { PXE_URL = 'http://localhost:8080' } = process.env; + +async function main() { + const pxe = createPXEClient(PXE_URL); + await waitForPXE(pxe); + + const [ownerWallet] = await getInitialTestAccountsWallets(pxe); + const ownerAddress = ownerWallet.getAddress(); + + const token = await Contract.deploy(ownerWallet, TokenContractArtifact, [ownerAddress, 'TokenName', 'TKN', 18]) + .send() + .deployed(); + + console.log(`Token deployed at ${token.address.toString()}`); + + const addresses = { token: token.address.toString() }; + writeFileSync('addresses.json', JSON.stringify(addresses, null, 2)); +} + +main().catch((err) => { + console.error(`Error in deployment script: ${err}`); + process.exit(1); +}); +``` + +Here, we are using the `Contract` class with the compiled artifact to send a new deployment transaction. The `deployed` method will block execution until the transaction is successfully mined, and return a receipt with the deployed contract address. + +Note that the token's `constructor()` method expects an `owner` address to set as the contract `admin`. We are using the first account from the Sandbox for this. + +:::info +If you are using the generated typescript classes, you can drop the generic `ContractDeployer` in favor of using the `deploy` method of the generated class, which will automatically load the artifact for you and type-check the constructor arguments. See the [How to deploy a contract](../../../../guides/js_apps/deploy_contract.md) page for more info. +::: + +Run the snippet above as `node src/deploy.mjs`, and you should see the following output, along with a new `addresses.json` file in your project root: + +```text +Token deployed to 0x2950b0f290422ff86b8ee8b91af4417e1464ddfd9dda26de8af52dac9ea4f869 +``` + +## Next steps + +Now that we have our contracts set up, it's time to actually [start writing our application that will be interacting with them](./3_contract_interaction.md). diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/3_contract_interaction.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/3_contract_interaction.md new file mode 100644 index 000000000000..14d604d4e515 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/3_contract_interaction.md @@ -0,0 +1,229 @@ +--- +title: Contract Interaction +--- + +In this section, we'll write the logic in our app that will interact with the contract we have previously deployed. We'll be using the accounts already seeded in the Sandbox. + +## Showing user balance + +Let's start by showing our user's private balance for the token across their accounts. To do this, we can leverage the `balance_of_private` utility function of the token contract: + +```rust title="balance_of_private" showLineNumbers +#[utility] +pub(crate) unconstrained fn balance_of_private(owner: AztecAddress) -> u128 { + storage.balances.at(owner).balance_of() +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr#L720-L725 + + +:::info +Note that this function will only return a valid response for accounts registered in the Private eXecution Environment (PXE), since it requires access to the [user's private state](../../../../../aztec/concepts/wallets/index.md#private-state). In other words, you cannot query the private balance of another user for the token contract. +::: + +To do this, let's first initialize a new `Contract` instance using `aztec.js` that represents our deployed token contracts. Create a new `src/contracts.mjs` file with the imports for our artifacts and other dependencies: + +```js +// src/contracts.mjs +import { AztecAddress, Contract, loadContractArtifact } from "@aztec/aztec.js"; +import TokenContractJson from "../contracts/token/target/token-Token.json" with { type: "json" }; + +import { readFileSync } from "fs"; +const TokenContractArtifact = loadContractArtifact(TokenContractJson); + +export async function getToken(wallet) { + const addresses = JSON.parse(readFileSync('addresses.json')); + return Contract.at(AztecAddress.fromString(addresses.token), TokenContractArtifact, wallet); +} +``` + +We can now get the token instance in our main code in `src/index.mjs`, by importing the function from `src/contracts.mjs`. Update the imports in `src/index.mjs` to look like this: + +```js +// src/index.mjs +import { getInitialTestAccountsWallets } from '@aztec/accounts/testing'; +import { createPXEClient, waitForPXE } from '@aztec/aztec.js'; +import { fileURLToPath } from '@aztec/foundation/url'; + +import { getToken } from './contracts.mjs'; +``` + +and query the private balance for each of the user accounts. To query a function, without sending a transaction, use the `simulate` function of the method: + +```javascript title="showPrivateBalances" showLineNumbers +async function showPrivateBalances(pxe) { + const [owner] = await getInitialTestAccountsWallets(pxe); + const token = await getToken(owner); + + const accounts = await pxe.getRegisteredAccounts(); + + for (const account of accounts) { + // highlight-next-line + const balance = await token.methods.balance_of_private(account.address).simulate(); + console.log(`Balance of ${account.address}: ${balance}`); + } +} +``` +> Source code: yarn-project/end-to-end/src/sample-dapp/index.mjs#L19-L32 + + +Call the function in `main` and run this with `node src/index.mjs` and you should now see the following output: + +``` +Balance of 0x0c8a6673d7676cc80aaebe7fa7504cf51daa90ba906861bfad70a58a98bf5a7d: 0 +Balance of 0x226f8087792beff8d5009eb94e65d2a4a505b70baf4a9f28d33c8d620b0ba972: 0 +Balance of 0x0e1f60e8566e2c6d32378bdcadb7c63696e853281be798c107266b8c3a88ea9b: 0 +``` + +## Mint tokens + +Before we can transfer tokens, we need to mint some tokens to our user accounts. Add the following function to `src/index.mjs`: + +```javascript title="mintPrivateFunds" showLineNumbers +async function mintPrivateFunds(pxe) { + const [ownerWallet] = await getInitialTestAccountsWallets(pxe); + const token = await getToken(ownerWallet); + + await showPrivateBalances(pxe); + + // We mint tokens to the owner + const mintAmount = 20n; + const from = ownerWallet.getAddress(); // we are setting from to owner here because we need a sender to calculate the tag + await token.methods.mint_to_private(from, ownerWallet.getAddress(), mintAmount).send().wait(); + + await showPrivateBalances(pxe); +} +``` +> Source code: yarn-project/end-to-end/src/sample-dapp/index.mjs#L34-L48 + + +Call the function in `main`, run the script and after printing the balances of each account it will then privately mint tokens. After that completes, you should then see 20 tokens in the balance of the first account. + +```text +Balance of 0x0c8a6673d7676cc80aaebe7fa7504cf51daa90ba906861bfad70a58a98bf5a7d: 20 +Balance of 0x226f8087792beff8d5009eb94e65d2a4a505b70baf4a9f28d33c8d620b0ba972: 0 +Balance of 0x0e1f60e8566e2c6d32378bdcadb7c63696e853281be798c107266b8c3a88ea9b: 0 +``` + +## Transferring private tokens + +Now that we can see the balance for each user, let's transfer tokens from one account to another. To do this, we will first need access to a `Wallet` object. This wraps access to an PXE and also provides an interface to craft and sign transactions on behalf of one of the user accounts. + +For ease of use, `@aztec/accounts` also ships with a helper `getInitialTestAccountsWallets` method that returns a wallet for each of the pre-initialized accounts in the Sandbox, so you can send transactions as any of them. Import it in `index.mjs`. + +```js +import { getInitialTestAccountsWallets } from "@aztec/accounts/testing"; +``` + +We'll use one of these wallets to initialize the `TokenContract` instance that represents our private token contract, so every transaction sent through it will be sent through that wallet. + +```javascript title="transferPrivateFunds" showLineNumbers +async function transferPrivateFunds(pxe) { + const [owner, recipient] = await getInitialTestAccountsWallets(pxe); + const token = await getToken(owner); + + await showPrivateBalances(pxe); + console.log(`Sending transaction, awaiting transaction to be mined`); + const receipt = await token.methods.transfer(recipient.getAddress(), 1).send().wait(); + + console.log(`Transaction ${receipt.txHash} has been mined on block ${receipt.blockNumber}`); + await showPrivateBalances(pxe); +} +``` +> Source code: yarn-project/end-to-end/src/sample-dapp/index.mjs#L50-L62 + + +Let's go step-by-step on this snippet. We first get wallets for two of the Sandbox accounts, and name them `owner` and `recipient`. Then, we initialize the private token `Contract` instance using the `owner` wallet, meaning that any transactions sent through it will have the `owner` as sender. + +Next, we send a transfer transaction, moving 1 unit of balance to the `recipient` account address. This has no immediate effect, since the transaction first needs to be simulated locally and then submitted and mined. Only once this has finished we can query the balances again and see the effect of our transaction. We are using a `showPrivateBalances` helper function here which has the code we wrote in the section above. + +Run this new snippet and you should see the following: + +```text +Sent transfer transaction 16025a7c4f6c44611d7ac884a5c27037d85d9756a4924df6d97fb25f6e83a0c8 + +Balance of 0x0c8a6673d7676cc80aaebe7fa7504cf51daa90ba906861bfad70a58a98bf5a7d: 20 +Balance of 0x226f8087792beff8d5009eb94e65d2a4a505b70baf4a9f28d33c8d620b0ba972: 0 +Balance of 0x0e1f60e8566e2c6d32378bdcadb7c63696e853281be798c107266b8c3a88ea9b: 0 + +Awaiting transaction to be mined +Transaction has been mined on block 4 + +Balance of 0x0c8a6673d7676cc80aaebe7fa7504cf51daa90ba906861bfad70a58a98bf5a7d: 19 +Balance of 0x226f8087792beff8d5009eb94e65d2a4a505b70baf4a9f28d33c8d620b0ba972: 1 +Balance of 0x0e1f60e8566e2c6d32378bdcadb7c63696e853281be798c107266b8c3a88ea9b: 0 +``` + +:::info +At the time of this writing, there are no events emitted when new private notes are received, so the only way to detect of a change in a user's private balance is via polling on every new block processed. This will change in a future release. +::: + +## Working with public state + +While [private and public state](../../../../../aztec/concepts/storage/index.md) are fundamentally different, the API for working with private and public functions and state from `aztec.js` is equivalent. To query the balance in public tokens for our user accounts, we can just call the `balance_of_public` view function in the contract: + +```javascript title="showPublicBalances" showLineNumbers +async function showPublicBalances(pxe) { + const [owner] = await getInitialTestAccountsWallets(pxe); + const token = await getToken(owner); + + const accounts = await pxe.getRegisteredAccounts(); + + for (const account of accounts) { + // highlight-next-line + const balance = await token.methods.balance_of_public(account.address).simulate(); + console.log(`Balance of ${account.address}: ${balance}`); + } +} +``` +> Source code: yarn-project/end-to-end/src/sample-dapp/index.mjs#L64-L77 + + +:::info +Since this we are working with public balances, we can now query the balance for any address, not just those registered in our local PXE. We can also send funds to addresses for which we don't know their [public encryption key](../../../../../aztec/concepts/accounts/keys.md#keys-generation). +::: + +Here, since the token contract does not mint any initial funds upon deployment, the balances for all of our user's accounts will be zero. +But we can send a transaction to mint tokens, using very similar code to the one for sending private funds: + +```javascript title="mintPublicFunds" showLineNumbers +async function mintPublicFunds(pxe) { + const [owner] = await getInitialTestAccountsWallets(pxe); + const token = await getToken(owner); + + await showPublicBalances(pxe); + + console.log(`Sending transaction, awaiting transaction to be mined`); + const receipt = await token.methods.mint_to_public(owner.getAddress(), 100).send().wait(); + console.log(`Transaction ${receipt.txHash} has been mined on block ${receipt.blockNumber}`); + + await showPublicBalances(pxe); + + const blockNumber = await pxe.getBlockNumber(); + const logs = (await pxe.getPublicLogs({ fromBlock: blockNumber - 1 })).logs; + const textLogs = logs.map(extendedLog => extendedLog.toHumanReadable().slice(0, 200)); + for (const log of textLogs) console.log(`Log emitted: ${log}`); +} +``` +> Source code: yarn-project/end-to-end/src/sample-dapp/index.mjs#L79-L99 + + +And get the expected results: + +```text +Sent mint transaction 041d5b4cc68bcb5c6cb45cd4c79f893d94f0df0792f66e6fddd7718c049fe925 +Balance of 0x0c8a6673d7676cc80aaebe7fa7504cf51daa90ba906861bfad70a58a98bf5a7d: 0 +Balance of 0x226f8087792beff8d5009eb94e65d2a4a505b70baf4a9f28d33c8d620b0ba972: 0 +Balance of 0x0e1f60e8566e2c6d32378bdcadb7c63696e853281be798c107266b8c3a88ea9b: 0 + +Awaiting transaction to be mined +Transaction has been mined on block 5 + +Balance of 0x0c8a6673d7676cc80aaebe7fa7504cf51daa90ba906861bfad70a58a98bf5a7d: 100 +Balance of 0x226f8087792beff8d5009eb94e65d2a4a505b70baf4a9f28d33c8d620b0ba972: 0 +Balance of 0x0e1f60e8566e2c6d32378bdcadb7c63696e853281be798c107266b8c3a88ea9b: 0 +``` + +## Next steps + +In the next and final section, we'll [set up automated tests for our application](./4_testing.md). diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/4_testing.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/4_testing.md new file mode 100644 index 000000000000..182b1bc79139 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/4_testing.md @@ -0,0 +1,90 @@ +--- +title: Testing +--- + +To wrap up this tutorial, we'll set up a simple automated test for our dapp contracts. We will be using [jest](https://jestjs.io/), but any nodejs test runner works fine. + +Here we'll only test the happy path for a `transfer` on our private token contract, but in a real application you should be testing both happy and unhappy paths, as well as both your contracts and application logic. + +## Dependencies + +Start by installing our test runner, in this case jest: + +```sh +yarn add -D jest +``` + +We'll need to [install and run the Sandbox](../../../../getting_started.md). + +## Test setup + +Create a new file `src/index.test.mjs` with the imports we'll be using and an empty test suite to begin with: + +```js +import { getDeployedTestAccountsWallets } from '@aztec/accounts/testing'; +import { createPXEClient, waitForPXE } from '@aztec/aztec.js'; +import { TokenContract } from '@aztec/noir-contracts.js/Token'; + +const { + PXE_URL = "http://localhost:8080", + ETHEREUM_HOSTS = "http://localhost:8545", +} = process.env; + +describe("token contract", () => { + // +}); +``` + +Let's set up our test suite. We'll make sure the Sandbox is running, create two fresh accounts to test with, and deploy an instance of our contract. `aztec.js` provides the helper functions we need to do this: + +```javascript title="setup" showLineNumbers +let owner, recipient, token; + +beforeAll(async () => { + const pxe = createPXEClient(PXE_URL); + await waitForPXE(pxe); + [owner, recipient] = await getDeployedTestAccountsWallets(pxe); + + const initialBalance = 69; + token = await TokenContract.deploy(owner, owner.getAddress(), 'TokenName', 'TokenSymbol', 18).send().deployed(); + await token.methods.mint_to_private(owner.getAddress(), owner.getAddress(), initialBalance).send().wait(); +}, 120_000); +``` +> Source code: yarn-project/end-to-end/src/sample-dapp/index.test.mjs#L13-L25 + + +:::tip +Instead of creating new accounts in our test suite, we can use the ones already initialized by the Sandbox upon startup. This can provide a speed boost to your tests setup. However, bear in mind that you may accidentally introduce an interdependency across test suites by reusing the same accounts. +::: + +## Writing our test + +Now that we have a working test environment, we can write our first test for exercising the `transfer` function on the token contract. We will use the same `aztec.js` methods we used when building our dapp: + +```javascript title="test" showLineNumbers +it('increases recipient funds on transfer', async () => { + expect(await token.withWallet(recipient).methods.balance_of_private(recipient.getAddress()).simulate()).toEqual(0n); + await token.methods.transfer(recipient.getAddress(), 20).send().wait(); + expect(await token.withWallet(recipient).methods.balance_of_private(recipient.getAddress()).simulate()).toEqual( + 20n, + ); +}, 30_000); +``` +> Source code: yarn-project/end-to-end/src/sample-dapp/index.test.mjs#L27-L35 + + +In this example, we assert that the `recipient`'s balance is increased by the amount transferred. We could also test that the `owner`'s funds are decremented by the same amount, or that a transaction that attempts to send more funds than those available would fail. + +## Running our tests + +We can run our `jest` tests using `yarn`. The quirky syntax is due to [jest limitations in ESM support](https://jestjs.io/docs/ecmascript-modules), as well as not picking up `mjs` file by default: + +```sh +yarn node --experimental-vm-modules $(yarn bin jest) --testRegex '.*\.test\.mjs$' +``` + +## Next steps + +Have you written a contract from scratch? If not, follow a tutorial for [writing contracts with Noir](../../contract_tutorials/counter_contract.md) + +Or read about the [fundamental concepts behind Aztec Network](../../../../../aztec) and dive deeper into how things work. diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/index.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/index.md new file mode 100644 index 000000000000..edf3c55c4d27 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/simple_dapp/index.md @@ -0,0 +1,24 @@ +--- +title: Node.js app that interacts with contracts +sidebar_position: 1 +--- + +In this tutorial we'll go through the steps for building a simple application that interacts with the Aztec Sandbox. We'll be building a console application using Javascript and NodeJS, but you may reuse the same concepts here for a web-based app. All Aztec libraries are written in Typescript and fully typed, so you can use Typescript instead of Javascript to make the most out of its type checker. + +:::note +This tutorial is for the sandbox and will need adjustments if deploying to testnet. Install the sandbox [here](../../../../getting_started.md). +::: + +This tutorial will focus on environment setup, including creating accounts and deployments, as well as interacting with your contracts. It will not cover [how to write contracts in Noir](../../../../../aztec/smart_contracts_overview.md). + +The full code for this tutorial is [available on the `aztec-packages` repository](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/end-to-end/src/sample-dapp). + +## Dependencies + +- Linux or OSX environment +- [NodeJS](https://nodejs.org/) version 22.15.0 (minimum ver 20) +- [Aztec Sandbox](../../../../getting_started.md) + +## Prerequisites + +Basic understanding of NodeJS and Javascript should be enough to follow this tutorial. Along the way, we'll provide links to dig deeper into Aztec concepts as we introduce them. diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/token_bridge.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/token_bridge.md new file mode 100644 index 000000000000..f785607302b2 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/token_bridge.md @@ -0,0 +1,640 @@ +--- +title: "Token Bridge Tutorial" +sidebar_position: 4 +--- + +This tutorial goes over how to create the contracts necessary to create a portal (aka token bridge) and how a developer can use it. + +In this tutorial, we will go over the components of a token bridge and how to deploy them, as well as show how to bridge tokens publicly from L1 to L2 and back, using aztec.js. + +:::note +The JavaScript in this tutorial is for the sandbox and will need adjustments if deploying to testnet. Install the sandbox [here](../../../getting_started.md). +::: + +The first half of this page reviews the process and contracts for bridging token from Ethereum (L1) to Aztec (L2). The second half the page (starting with [Running with Aztec.js](#running-with-aztecjs)) goes over writing your own Typescript script for: + +- deploying and initializing contracts to L1 and L2 +- minting tokens on L1 +- sending tokens into the portal on L1 +- minting tokens on L2 +- sending tokens from L2 back to L1 +- withdrawing tokens from the L1 portal + +This tutorial is compatible with the Aztec version `v0.88.0`. Install the correct version with `aztec-up -v 0.88.0`. Or if you'd like to use a different version, you can find the relevant tutorial by clicking the version dropdown at the top of the page. + +## Components + +Bridges in Aztec involve several components across L1 and L2: + +- L1 contracts: + - `ERC20.sol`: An ERC20 contract that represents assets on L1 + - `TokenPortal.sol`: Manages the passing of messages from L1 to L2. It is deployed on L1, is linked to a specific token on L1 and a corresponding contract on L2. The `registry` is used to find the rollup and the corresponding `inbox` and `outbox` contracts. +- L2 contracts: + - `Token`: Manages the tokens on L2 + - `TokenBridge`: Manages the bridging of tokens between L2 and L1 + +`TokenPortal.sol` is the contract that manages the passing of messages from L1 to L2. It is deployed on L1, is linked to a specific token on L1 and a corresponding contract on L2. The `registry` is used to find the rollup and the corresponding `inbox` and `outbox` contracts. + +## How it works + +### Deposit to Aztec + +`TokenPortal.sol` passes messages to Aztec both publicly and privately. + +This diagram shows the logical flow of information among components involved in depositing to Aztec. + +```mermaid +sequenceDiagram + participant L1 User + participant L1 TokenPortal + participant L1 Aztec Inbox + participant L2 Bridge Contract + participant L2 Token Contract + + L1 User->>L1 TokenPortal: Deposit Tokens + + Note over L1 TokenPortal: 1. Encode mint message
(recipient + amount)
2. Hash message to field
element (~254 bits) + + L1 TokenPortal->>L1 Aztec Inbox: Send message + Note over L1 Aztec Inbox: Validates:
1. Recipient Aztec address
2. Aztec version
3. Message content hash
4. Secret hash + + L1 Aztec Inbox-->>L2 Bridge Contract: Forward message + Note over L2 Bridge Contract: 1. Verify message
2. Process secret
3. Decode mint parameters + + L2 Bridge Contract->>L2 Token Contract: Call mint function + Note over L2 Token Contract: Mints tokens to
specified recipient +``` + +Message content that is passed to Aztec is limited to a single field element (~254 bits), so if the message content is larger than that, it is hashed, and the message hash is passed and verified on the receiving contract. There is a utility function in the `Hash` library to hash messages (using `sha256`) to field elements. + +The Aztec message Inbox expects a recipient Aztec address that can consume the message (the corresponding L2 bridge contract), the Aztec version (similar to Ethereum's `chainId`), the message content hash (which includes the token recipient and amount in this case), and a `secretHash`, where the corresponding `secret` is used to consume the message on the receiving contract. + +So in summary, it deposits tokens to the portal, encodes a mint message, hashes it, and sends it to the Aztec rollup via the Inbox. The L2 token contract can then mint the tokens when the corresponding L2 bridge contract processes the message. + +Note that because L1 is public, everyone can inspect and figure out the contentHash and the recipient contract address. + +#### `depositToAztecPublic` (TokenPortal.sol) + +```solidity title="deposit_public" showLineNumbers +/** + * @notice Deposit funds into the portal and adds an L2 message which can only be consumed publicly on Aztec + * @param _to - The aztec address of the recipient + * @param _amount - The amount to deposit + * @param _secretHash - The hash of the secret consumable message. The hash should be 254 bits (so it can fit in a Field element) + * @return The key of the entry in the Inbox and its leaf index + */ +function depositToAztecPublic(bytes32 _to, uint256 _amount, bytes32 _secretHash) + external + returns (bytes32, uint256) +``` +> Source code: l1-contracts/test/portals/TokenPortal.sol#L55-L66 + + +#### `depositToAztecPrivate` (TokenPortal.sol) + +```solidity title="deposit_private" showLineNumbers +/** + * @notice Deposit funds into the portal and adds an L2 message which can only be consumed privately on Aztec + * @param _amount - The amount to deposit + * @param _secretHashForL2MessageConsumption - The hash of the secret consumable L1 to L2 message. The hash should be 254 bits (so it can fit in a Field element) + * @return The key of the entry in the Inbox and its leaf index + */ +function depositToAztecPrivate(uint256 _amount, bytes32 _secretHashForL2MessageConsumption) + external + returns (bytes32, uint256) +``` +> Source code: l1-contracts/test/portals/TokenPortal.sol#L89-L99 + + +**So how do we privately consume the message on Aztec?** + +On Aztec, anytime something is consumed (i.e. deleted), we emit a nullifier hash and add it to the nullifier tree. This prevents double-spends. The nullifier hash is a hash of the message that is consumed. So without the secret, one could reverse engineer the expected nullifier hash that might be emitted on L2 upon message consumption. To consume the message on L2, the user provides a secret to the private function, which computes the hash and asserts that it matches to what was provided in the L1->L2 message. This secret is included in the nullifier hash computation and the nullifier is added to the nullifier tree. Anyone inspecting the blockchain won’t know which nullifier hash corresponds to the L1->L2 message consumption. + +### Minting on Aztec + +The previous code snippets moved funds to the bridge and created a L1->L2 message. Upon building the next rollup block, the sequencer asks the L1 inbox contract for any incoming messages and adds them to the Aztec block's L1->L2 message tree, so an application on L2 can prove that the message exists and can consume it. + +This happens inside the `TokenBridge` contract on Aztec. + +```rust title="claim_public" showLineNumbers +// Consumes a L1->L2 message and calls the token contract to mint the appropriate amount publicly +#[public] +fn claim_public(to: AztecAddress, amount: u128, secret: Field, message_leaf_index: Field) { + let content_hash = get_mint_to_public_content_hash(to, amount); + + let config = storage.config.read(); + + // Consume message and emit nullifier + context.consume_l1_to_l2_message(content_hash, secret, config.portal, message_leaf_index); + + // Mint tokens + Token::at(config.token).mint_to_public(to, amount).call(&mut context); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr#L57-L71 + + +What's happening here? + +1. compute the content hash of the message +2. consume the message +3. mint the tokens + +:::note + +The Aztec `TokenBridge` contract should be an authorized minter in the corresponding Aztec `Token` contract so that it is able to complete mints to the intended recipient. + +::: + +The token bridge also allows tokens to be withdrawn back to L1 from L2. You can withdraw part of a public or private balance to L1, but the amount and the recipient on L1 will be public. + +Sending tokens to L1 involves burning the tokens on L2 and creating a L2->L1 message. The message content is the `amount` to burn, the recipient address, and who can execute the withdraw on the L1 portal on behalf of the user. It can be `0x0` for anyone, or a specified address. + +For both the public and private flow, we use the same mechanism to determine the content hash. This is because on L1, things are public anyway. The only difference between the two functions is that in the private domain we have to nullify user’s notes whereas in the public domain we subtract the balance from the user. + +#### `exit_to_L1_public` (TokenBridge.nr) + +```rust title="exit_to_l1_public" showLineNumbers +// Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message publicly +// Requires `msg.sender` to give approval to the bridge to burn tokens on their behalf using witness signatures +#[public] +fn exit_to_l1_public( + recipient: EthAddress, // ethereum address to withdraw to + amount: u128, + caller_on_l1: EthAddress, // ethereum address that can call this function on the L1 portal (0x0 if anyone can call) + authwit_nonce: Field, // nonce used in the approval message by `msg.sender` to let bridge burn their tokens on L2 +) { + let config = storage.config.read(); + + // Send an L2 to L1 message + let content = get_withdraw_content_hash(recipient, amount, caller_on_l1); + context.message_portal(config.portal, content); + + // Burn tokens + Token::at(config.token).burn_public(context.msg_sender(), amount, authwit_nonce).call( + &mut context, + ); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr#L73-L94 + + +#### `exit_to_L1_private` (TokenBridge.nr) + +This function works very similarly to the public version, except here we burn user’s private notes. + +```rust title="exit_to_l1_private" showLineNumbers +// Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message privately +// Requires `msg.sender` (caller of the method) to give approval to the bridge to burn tokens on their behalf using witness signatures +#[private] +fn exit_to_l1_private( + token: AztecAddress, + recipient: EthAddress, // ethereum address to withdraw to + amount: u128, + caller_on_l1: EthAddress, // ethereum address that can call this function on the L1 portal (0x0 if anyone can call) + authwit_nonce: Field, // nonce used in the approval message by `msg.sender` to let bridge burn their tokens on L2 +) { + let config = storage.config.read(); + + // Assert that user provided token address is same as seen in storage. + assert_eq(config.token, token, "Token address is not the same as seen in storage"); + + // Send an L2 to L1 message + let content = get_withdraw_content_hash(recipient, amount, caller_on_l1); + context.message_portal(config.portal, content); + + // Burn tokens + Token::at(token).burn_private(context.msg_sender(), amount, authwit_nonce).call(&mut context); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr#L127-L152 + + +Since this is a private method, it can't read what token is publicly stored. So instead the user passes a token address, and `_assert_token_is_same()` checks that this user provided address is same as the one in storage. + +Because public functions are executed by the sequencer while private methods are executed locally, all public calls are always done _after_ all private calls are completed. So first the burn would happen and only later the sequencer asserts that the token is same. The sequencer just sees a request to `execute_assert_token_is_same` and therefore has no context on what the appropriate private method was. If the assertion fails, then the kernel circuit will fail to create a proof and hence the transaction will be dropped. + +A user must sign an approval message to let the contract burn tokens on their behalf. The nonce refers to this approval message. + +### Claiming on L1 + +After the transaction is completed on L2, the portal must call the outbox to successfully transfer funds to the user on L1. Like with deposits, things can be complex here. For example, what happens if the transaction was done on L2 to burn tokens but can’t be withdrawn to L1? Then the funds are lost forever! How do we prevent this? + +```solidity title="token_portal_withdraw" showLineNumbers +/** + * @notice Withdraw funds from the portal + * @dev Second part of withdraw, must be initiated from L2 first as it will consume a message from outbox + * @param _recipient - The address to send the funds to + * @param _amount - The amount to withdraw + * @param _withCaller - Flag to use `msg.sender` as caller, otherwise address(0) + * @param _l2BlockNumber - The address to send the funds to + * @param _leafIndex - The amount to withdraw + * @param _path - Flag to use `msg.sender` as caller, otherwise address(0) + * Must match the caller of the message (specified from L2) to consume it. + */ +function withdraw( + address _recipient, + uint256 _amount, + bool _withCaller, + uint256 _l2BlockNumber, + uint256 _leafIndex, + bytes32[] calldata _path +) external { + // The purpose of including the function selector is to make the message unique to that specific call. Note that + // it has nothing to do with calling the function. + DataStructures.L2ToL1Msg memory message = DataStructures.L2ToL1Msg({ + sender: DataStructures.L2Actor(l2Bridge, rollupVersion), + recipient: DataStructures.L1Actor(address(this), block.chainid), + content: Hash.sha256ToField( + abi.encodeWithSignature( + "withdraw(address,uint256,address)", + _recipient, + _amount, + _withCaller ? msg.sender : address(0) + ) + ) + }); + + outbox.consume(message, _l2BlockNumber, _leafIndex, _path); + + underlying.transfer(_recipient, _amount); +} +``` +> Source code: l1-contracts/test/portals/TokenPortal.sol#L122-L161 + + +#### `token_portal_withdraw` (TokenPortal.sol) + +Here we reconstruct the L2 to L1 message and check that this message exists on the outbox. If so, we consume it and transfer the funds to the recipient. As part of the reconstruction, the content hash looks similar to what we did in our bridge contract on Aztec where we pass the amount and recipient to the hash. This way a malicious actor can’t change the recipient parameter to the address and withdraw funds to themselves. + +We also use a `_withCaller` parameter to determine the appropriate party that can execute this function on behalf of the recipient. If `withCaller` is false, then anyone can call the method and hence we use address(0), otherwise only msg.sender should be able to execute. This address should match the `callerOnL1` address we passed in aztec when withdrawing from L2. + +We call this pattern _designed caller_ which enables a new paradigm **where we can construct other such portals that talk to the token portal and therefore create more seamless crosschain legos** between L1 and L2. + +## Running with Aztec.js + +Let's run through the entire process of depositing, minting and withdrawing tokens in Typescript, so you can see how it works in practice. + +Make sure you are using version v0.88.0 of the sandbox. Install with `aztec-up 0.88.0`. + +### Prerequisites + +Same prerequisites as the [getting started guide](../../../../developers/getting_started.md#prerequisites) and the sandbox. + +### ProjectSetup + +Create a new directory for the tutorial and install the dependencies: + +```bash +mkdir token-bridge-tutorial +cd token-bridge-tutorial +yarn init -y +echo "nodeLinker: node-modules" > .yarnrc.yml +yarn add @aztec/aztec.js@0.88.0 @aztec/noir-contracts.js@0.88.0 @aztec/l1-artifacts@0.88.0 @aztec/accounts@0.88.0 @aztec/ethereum@0.88.0 @types/node typescript@^5.0.4 viem@^2.22.8 tsx +touch tsconfig.json +touch index.ts +``` + +Add this to your `tsconfig.json`: + +```json +{ + "compilerOptions": { + "rootDir": ".", + "outDir": "./dest", + "target": "es2020", + "lib": ["dom", "esnext", "es2017.object"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "declaration": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "downlevelIteration": true, + "inlineSourceMap": true, + "declarationMap": true, + "importHelpers": true, + "resolveJsonModule": true, + "composite": true, + "skipLibCheck": true + } +} +``` + +and add this to your `package.json`: + +```json + // ... + "type": "module", + "scripts": { + "start": "node --import tsx index.ts" + }, + // ... +``` + +You can run the script we will build in `index.ts` at any point with `yarn start`. + +### Imports + +Add the following imports to your `index.ts`: + +```typescript title="imports" showLineNumbers +import { getInitialTestAccountsWallets } from '@aztec/accounts/testing'; +import { + EthAddress, + Fr, + L1TokenManager, + L1TokenPortalManager, + createLogger, + createPXEClient, + waitForPXE, +} from '@aztec/aztec.js'; +import { createExtendedL1Client, deployL1Contract } from '@aztec/ethereum'; +import { + FeeAssetHandlerAbi, + FeeAssetHandlerBytecode, + TestERC20Abi, + TestERC20Bytecode, + TokenPortalAbi, + TokenPortalBytecode, +} from '@aztec/l1-artifacts'; +import { TokenContract } from '@aztec/noir-contracts.js/Token'; +import { TokenBridgeContract } from '@aztec/noir-contracts.js/TokenBridge'; + +import { getContract } from 'viem'; +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts#L2-L27 + + +### Utility functions + +Add the following utility functions to your `index.ts` below the imports: + +```typescript title="utils" showLineNumbers +const MNEMONIC = 'test test test test test test test test test test test junk'; +const { ETHEREUM_HOSTS = 'http://localhost:8545' } = process.env; + +const l1Client = createExtendedL1Client(ETHEREUM_HOSTS.split(','), MNEMONIC); +const ownerEthAddress = l1Client.account.address; + +const MINT_AMOUNT = BigInt(1e15); + +const setupSandbox = async () => { + const { PXE_URL = 'http://localhost:8080' } = process.env; + // eslint-disable-next-line @typescript-eslint/await-thenable + const pxe = await createPXEClient(PXE_URL); + await waitForPXE(pxe); + return pxe; +}; + +async function deployTestERC20(): Promise { + const constructorArgs = ['Test Token', 'TEST', l1Client.account.address]; + + return await deployL1Contract(l1Client, TestERC20Abi, TestERC20Bytecode, constructorArgs).then( + ({ address }) => address, + ); +} + +async function deployFeeAssetHandler(l1TokenContract: EthAddress): Promise { + const constructorArgs = [l1Client.account.address, l1TokenContract.toString(), MINT_AMOUNT]; + return await deployL1Contract(l1Client, FeeAssetHandlerAbi, FeeAssetHandlerBytecode, constructorArgs).then( + ({ address }) => address, + ); +} + +async function deployTokenPortal(): Promise { + return await deployL1Contract(l1Client, TokenPortalAbi, TokenPortalBytecode, []).then(({ address }) => address); +} + +async function addMinter(l1TokenContract: EthAddress, l1TokenHandler: EthAddress) { + const contract = getContract({ + address: l1TokenContract.toString(), + abi: TestERC20Abi, + client: l1Client, + }); + await contract.write.addMinter([l1TokenHandler.toString()]); +} +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts#L28-L72 + + +### Sandbox Setup + +Start the sandbox with: + +```bash +aztec start --sandbox +``` + +And add the following code to your `index.ts`: + +```ts +async function main() { +const logger = createLogger('aztec:token-bridge-tutorial'); +const pxe = await setupSandbox(); +const wallets = await getInitialTestAccountsWallets(pxe); +const ownerWallet = wallets[0]; +const ownerAztecAddress = wallets[0].getAddress(); +const l1ContractAddresses = (await pxe.getNodeInfo()).l1ContractAddresses; +logger.info('L1 Contract Addresses:'); +logger.info(`Registry Address: ${l1ContractAddresses.registryAddress}`); +logger.info(`Inbox Address: ${l1ContractAddresses.inboxAddress}`); +logger.info(`Outbox Address: ${l1ContractAddresses.outboxAddress}`); +logger.info(`Rollup Address: ${l1ContractAddresses.rollupAddress}`); +} + +main(); +``` + +The rest of the code in the tutorial will go inside the `main()` function. + +Run the script with `yarn start` and you should see the L1 contract addresses printed out. + +### Deploying the contracts + +Add the following code to `index.ts` to deploy the L2 token contract: + +```typescript title="deploy-l2-token" showLineNumbers +const l2TokenContract = await TokenContract.deploy(ownerWallet, ownerAztecAddress, 'L2 Token', 'L2', 18) + .send() + .deployed(); +logger.info(`L2 token contract deployed at ${l2TokenContract.address}`); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts#L91-L96 + + +Add the following code to `index.ts` to deploy the L1 token contract and set up the `L1TokenManager` (a utility class to interact with the L1 token contract): + +```typescript title="deploy-l1-token" showLineNumbers +const l1TokenContract = await deployTestERC20(); +logger.info('erc20 contract deployed'); + +const feeAssetHandler = await deployFeeAssetHandler(l1TokenContract); +await addMinter(l1TokenContract, feeAssetHandler); + +const l1TokenManager = new L1TokenManager(l1TokenContract, feeAssetHandler, l1Client, logger); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts#L99-L107 + + +Add the following code to `index.ts` to deploy the L1 portal contract: + +```typescript title="deploy-portal" showLineNumbers +const l1PortalContractAddress = await deployTokenPortal(); +logger.info('L1 portal contract deployed'); + +const l1Portal = getContract({ + address: l1PortalContractAddress.toString(), + abi: TokenPortalAbi, + client: l1Client, +}); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts#L110-L119 + + +Add the following code to `index.ts` to deploy the L2 bridge contract: + +```typescript title="deploy-l2-bridge" showLineNumbers +const l2BridgeContract = await TokenBridgeContract.deploy( + ownerWallet, + l2TokenContract.address, + l1PortalContractAddress, +) + .send() + .deployed(); +logger.info(`L2 token bridge contract deployed at ${l2BridgeContract.address}`); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts#L121-L130 + + +Run `yarn start` to confirm that all of the contracts are deployed. + +### Setup contracts + +Add the following code to `index.ts` to authorize the L2 bridge contract to mint tokens on the L2 token contract: + +```typescript title="authorize-l2-bridge" showLineNumbers +await l2TokenContract.methods.set_minter(l2BridgeContract.address, true).send().wait(); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts#L133-L135 + + +Add the following code to `index.ts` to set up the L1 portal contract and `L1TokenPortalManager` (a utility class to interact with the L1 portal contract): + +```typescript title="setup-portal" showLineNumbers +await l1Portal.write.initialize( + [l1ContractAddresses.registryAddress.toString(), l1TokenContract.toString(), l2BridgeContract.address.toString()], + {}, +); +logger.info('L1 portal contract initialized'); + +const l1PortalManager = new L1TokenPortalManager( + l1PortalContractAddress, + l1TokenContract, + feeAssetHandler, + l1ContractAddresses.outboxAddress, + l1Client, + logger, +); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts#L138-L153 + + +### Bridge tokens + +Add the following code to `index.ts` to bridge tokens from L1 to L2: + +```typescript title="l1-bridge-public" showLineNumbers +const claim = await l1PortalManager.bridgeTokensPublic(ownerAztecAddress, MINT_AMOUNT, true); + +// Do 2 unrleated actions because +// https://github.com/AztecProtocol/aztec-packages/blob/7e9e2681e314145237f95f79ffdc95ad25a0e319/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts#L354-L355 +await l2TokenContract.methods.mint_to_public(ownerAztecAddress, 0n).send().wait(); +await l2TokenContract.methods.mint_to_public(ownerAztecAddress, 0n).send().wait(); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts#L155-L162 + + +We have to send two additional transactions because the network must process 2 blocks for the message to be processed by the archiver. We need to progress by 2 because there is a 1 block lag between when the message is sent to Inbox and when the subtree containing the message is included in the block. Then when it's included it becomes available for consumption in the next block. + +### Claim on Aztec + +Add the following code to `index.ts` to claim the tokens publicly on Aztec: + +```typescript title="claim" showLineNumbers +await l2BridgeContract.methods + .claim_public(ownerAztecAddress, MINT_AMOUNT, claim.claimSecret, claim.messageLeafIndex) + .send() + .wait(); +const balance = await l2TokenContract.methods.balance_of_public(ownerAztecAddress).simulate(); +logger.info(`Public L2 balance of ${ownerAztecAddress} is ${balance}`); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts#L165-L172 + + +Run `yarn start` to confirm that tokens are claimed on Aztec. + +### Withdraw + +Add the following code to `index.ts` to start the withdraw the tokens to L1: + +```typescript title="setup-withdrawal" showLineNumbers +const withdrawAmount = 9n; +const authwitNonce = Fr.random(); + +// Give approval to bridge to burn owner's funds: +const authwit = await ownerWallet.setPublicAuthWit( + { + caller: l2BridgeContract.address, + action: l2TokenContract.methods.burn_public(ownerAztecAddress, withdrawAmount, authwitNonce), + }, + true, +); +await authwit.send().wait(); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts#L176-L189 + + +We have to send a public authwit to allow the bridge contract to burn tokens on behalf of the user. + +Add the following code to `index.ts` to start the withdraw process on Aztec: + +```typescript title="l2-withdraw" showLineNumbers +const l2ToL1Message = await l1PortalManager.getL2ToL1MessageLeaf( + withdrawAmount, + EthAddress.fromString(ownerEthAddress), + l2BridgeContract.address, + EthAddress.ZERO, +); +const l2TxReceipt = await l2BridgeContract.methods + .exit_to_l1_public(EthAddress.fromString(ownerEthAddress), withdrawAmount, EthAddress.ZERO, authwitNonce) + .send() + .wait(); + +const newL2Balance = await l2TokenContract.methods.balance_of_public(ownerAztecAddress).simulate(); +logger.info(`New L2 balance of ${ownerAztecAddress} is ${newL2Balance}`); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts#L191-L205 + + +Add the following code to `index.ts` to complete the withdraw process on L1: + +```typescript title="l1-withdraw" showLineNumbers +const [l2ToL1MessageIndex, siblingPath] = await pxe.getL2ToL1MembershipWitness( + await pxe.getBlockNumber(), + l2ToL1Message, +); +await l1PortalManager.withdrawFunds( + withdrawAmount, + EthAddress.fromString(ownerEthAddress), + BigInt(l2TxReceipt.blockNumber!), + l2ToL1MessageIndex, + siblingPath, +); +const newL1Balance = await l1TokenManager.getL1TokenBalance(ownerEthAddress); +logger.info(`New L1 balance of ${ownerEthAddress} is ${newL1Balance}`); +``` +> Source code: yarn-project/end-to-end/src/composed/e2e_token_bridge_tutorial_test.test.ts#L207-L221 + + +Run `yarn start` to run the script and see the entire process in action. diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/_category_.json b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/_category_.json new file mode 100644 index 000000000000..d105325f1163 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Uniswap Bridge", + "position": 7, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/e2e_tests.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/e2e_tests.md new file mode 100644 index 000000000000..6d87e665dd03 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/e2e_tests.md @@ -0,0 +1,440 @@ +--- +title: e2e tests (TypeScript) +sidebar_position: 3 +--- + +## Private flow test + +```typescript title="uniswap_private" showLineNumbers +it('should uniswap trade on L1 from L2 funds privately (swaps WETH -> DAI)', async () => { + const wethL1BeforeBalance = await wethCrossChainHarness.getL1BalanceOf(ownerEthAddress); + + // 1. Approve and deposit weth to the portal and move to L2 + const wethDepositClaim = await wethCrossChainHarness.sendTokensToPortalPrivate(wethAmountToBridge); + + // funds transferred from owner to token portal + expect(await wethCrossChainHarness.getL1BalanceOf(ownerEthAddress)).toEqual( + wethL1BeforeBalance - wethAmountToBridge, + ); + expect(await wethCrossChainHarness.getL1BalanceOf(wethCrossChainHarness.tokenPortalAddress)).toEqual( + wethAmountToBridge, + ); + + await wethCrossChainHarness.makeMessageConsumable(Fr.fromHexString(wethDepositClaim.messageHash)); + + // 2. Claim WETH on L2 + logger.info('Minting weth on L2'); + await wethCrossChainHarness.consumeMessageOnAztecAndMintPrivately(wethDepositClaim); + await wethCrossChainHarness.expectPrivateBalanceOnL2(ownerAddress, wethAmountToBridge); + + // Store balances + const wethL2BalanceBeforeSwap = await wethCrossChainHarness.getL2PrivateBalanceOf(ownerAddress); + const daiL2BalanceBeforeSwap = await daiCrossChainHarness.getL2PrivateBalanceOf(ownerAddress); + + // 3. Owner gives uniswap approval to transfer the funds to public to self on its behalf + logger.info('Approving uniswap to transfer funds to public to self on my behalf'); + const nonceForWETHTransferToPublicApproval = new Fr(1n); + const transferToPublicAuhtwit = await ownerWallet.createAuthWit({ + caller: uniswapL2Contract.address, + action: wethCrossChainHarness.l2Token.methods.transfer_to_public( + ownerAddress, + uniswapL2Contract.address, + wethAmountToBridge, + nonceForWETHTransferToPublicApproval, + ), + }); + + // 4. Swap on L1 - sends L2 to L1 message to withdraw WETH to L1 and another message to swap assets. + logger.info('Withdrawing weth to L1 and sending message to swap to dai'); + const [secretForDepositingSwappedDai, secretHashForDepositingSwappedDai] = await generateClaimSecret(); + + const l2UniswapInteractionReceipt = await uniswapL2Contract.methods + .swap_private( + wethCrossChainHarness.l2Token.address, + wethCrossChainHarness.l2Bridge.address, + wethAmountToBridge, + daiCrossChainHarness.l2Bridge.address, + nonceForWETHTransferToPublicApproval, + uniswapFeeTier, + minimumOutputAmount, + secretHashForDepositingSwappedDai, + ownerEthAddress, + ) + .send({ authWitnesses: [transferToPublicAuhtwit] }) + .wait(); + + const swapPrivateFunction = 'swap_private(address,uint256,uint24,address,uint256,bytes32,address)'; + const swapPrivateContent = sha256ToField([ + Buffer.from(toFunctionSelector(swapPrivateFunction).substring(2), 'hex'), + wethCrossChainHarness.tokenPortalAddress.toBuffer32(), + new Fr(wethAmountToBridge), + new Fr(uniswapFeeTier), + daiCrossChainHarness.tokenPortalAddress.toBuffer32(), + new Fr(minimumOutputAmount), + secretHashForDepositingSwappedDai, + ownerEthAddress.toBuffer32(), + ]); + + const swapPrivateLeaf = computeL2ToL1MessageHash({ + l2Sender: uniswapL2Contract.address, + l1Recipient: EthAddress.fromString(uniswapPortal.address), + content: swapPrivateContent, + rollupVersion: new Fr(version), + chainId: new Fr(l1Client.chain.id), + }); + + const withdrawContent = sha256ToField([ + Buffer.from(toFunctionSelector('withdraw(address,uint256,address)').substring(2), 'hex'), + uniswapPortalAddress.toBuffer32(), + new Fr(wethAmountToBridge), + uniswapPortalAddress.toBuffer32(), + ]); + + const withdrawLeaf = computeL2ToL1MessageHash({ + l2Sender: wethCrossChainHarness.l2Bridge.address, + l1Recipient: wethCrossChainHarness.tokenPortalAddress, + content: withdrawContent, + rollupVersion: new Fr(version), + chainId: new Fr(l1Client.chain.id), + }); + + // ensure that user's funds were burnt + await wethCrossChainHarness.expectPrivateBalanceOnL2(ownerAddress, wethL2BalanceBeforeSwap - wethAmountToBridge); + // ensure that uniswap contract didn't eat the funds. + await wethCrossChainHarness.expectPublicBalanceOnL2(uniswapL2Contract.address, 0n); + + // Since the outbox is only consumable when the block is proven, we need to set the block to be proven + await cheatCodes.rollup.markAsProven(await rollup.getBlockNumber()); + + // 5. Consume L2 to L1 message by calling uniswapPortal.swap_private() + logger.info('Execute withdraw and swap on the uniswapPortal!'); + const daiL1BalanceOfPortalBeforeSwap = await daiCrossChainHarness.getL1BalanceOf( + daiCrossChainHarness.tokenPortalAddress, + ); + const swapResult = await computeL2ToL1MembershipWitness( + aztecNode, + l2UniswapInteractionReceipt.blockNumber!, + swapPrivateLeaf, + ); + const withdrawResult = await computeL2ToL1MembershipWitness( + aztecNode, + l2UniswapInteractionReceipt.blockNumber!, + withdrawLeaf, + ); + + const swapPrivateL2MessageIndex = swapResult!.l2MessageIndex; + const swapPrivateSiblingPath = swapResult!.siblingPath; + + const withdrawL2MessageIndex = withdrawResult!.l2MessageIndex; + const withdrawSiblingPath = withdrawResult!.siblingPath; + + const withdrawMessageMetadata = { + _l2BlockNumber: BigInt(l2UniswapInteractionReceipt.blockNumber!), + _leafIndex: BigInt(withdrawL2MessageIndex), + _path: withdrawSiblingPath + .toBufferArray() + .map((buf: Buffer) => `0x${buf.toString('hex')}`) as readonly `0x${string}`[], + }; + + const swapPrivateMessageMetadata = { + _l2BlockNumber: BigInt(l2UniswapInteractionReceipt.blockNumber!), + _leafIndex: BigInt(swapPrivateL2MessageIndex), + _path: swapPrivateSiblingPath + .toBufferArray() + .map((buf: Buffer) => `0x${buf.toString('hex')}`) as readonly `0x${string}`[], + }; + + const swapArgs = [ + wethCrossChainHarness.tokenPortalAddress.toString(), + wethAmountToBridge, + Number(uniswapFeeTier), + daiCrossChainHarness.tokenPortalAddress.toString(), + minimumOutputAmount, + secretHashForDepositingSwappedDai.toString(), + true, + [withdrawMessageMetadata, swapPrivateMessageMetadata], + ] as const; + + // this should also insert a message into the inbox. + const txReceipt = await daiCrossChainHarness.l1Client.waitForTransactionReceipt({ + hash: await uniswapPortal.write.swapPrivate(swapArgs), + }); + + // We get the msg leaf from event so that we can later wait for it to be available for consumption + const inboxAddress = daiCrossChainHarness.l1ContractAddresses.inboxAddress.toString(); + const txLog = extractEvent(txReceipt.logs, inboxAddress, InboxAbi, 'MessageSent'); + const tokenOutMsgHash = Fr.fromHexString(txLog.args.hash); + const tokenOutMsgIndex = txLog.args.index; + + // weth was swapped to dai and send to portal + const daiL1BalanceOfPortalAfter = await daiCrossChainHarness.getL1BalanceOf( + daiCrossChainHarness.tokenPortalAddress, + ); + expect(daiL1BalanceOfPortalAfter).toBeGreaterThan(daiL1BalanceOfPortalBeforeSwap); + const daiAmountToBridge = BigInt(daiL1BalanceOfPortalAfter - daiL1BalanceOfPortalBeforeSwap); + + // Wait for the message to be available for consumption + await daiCrossChainHarness.makeMessageConsumable(tokenOutMsgHash); + + // 6. claim dai on L2 + logger.info('Consuming messages to mint dai on L2'); + await daiCrossChainHarness.consumeMessageOnAztecAndMintPrivately({ + claimAmount: daiAmountToBridge, + claimSecret: secretForDepositingSwappedDai, + messageLeafIndex: tokenOutMsgIndex, + recipient: ownerAddress, + }); + await daiCrossChainHarness.expectPrivateBalanceOnL2(ownerAddress, daiL2BalanceBeforeSwap + daiAmountToBridge); + + const wethL2BalanceAfterSwap = await wethCrossChainHarness.getL2PrivateBalanceOf(ownerAddress); + const daiL2BalanceAfterSwap = await daiCrossChainHarness.getL2PrivateBalanceOf(ownerAddress); + + logger.info('WETH balance before swap: ' + wethL2BalanceBeforeSwap.toString()); + logger.info('DAI balance before swap : ' + daiL2BalanceBeforeSwap.toString()); + logger.info('***** 🧚‍♀️ SWAP L2 assets on L1 Uniswap 🧚‍♀️ *****'); + logger.info('WETH balance after swap : ', wethL2BalanceAfterSwap.toString()); + logger.info('DAI balance after swap : ', daiL2BalanceAfterSwap.toString()); +}); +``` +> Source code: yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts#L176-L366 + + +## Public flow test + +```typescript title="uniswap_public" showLineNumbers +// it('should uniswap trade on L1 from L2 funds publicly (swaps WETH -> DAI)', async () => { +// const wethL1BeforeBalance = await wethCrossChainHarness.getL1BalanceOf(ownerEthAddress); + +// // 1. Approve and deposit weth to the portal and move to L2 +// const [secretForMintingWeth, secretHashForMintingWeth] = wethCrossChainHarness.generateClaimSecret(); + +// const wethDepositMsgHash = await wethCrossChainHarness.sendTokensToPortalPublic( +// wethAmountToBridge, +// secretHashForMintingWeth, +// ); +// // funds transferred from owner to token portal +// expect(await wethCrossChainHarness.getL1BalanceOf(ownerEthAddress)).toBe( +// wethL1BeforeBalance - wethAmountToBridge, +// ); +// expect(await wethCrossChainHarness.getL1BalanceOf(wethCrossChainHarness.tokenPortalAddress)).toBe( +// wethAmountToBridge, +// ); + +// // Wait for the message to be available for consumption +// await wethCrossChainHarness.makeMessageConsumable(wethDepositMsgHash); + +// // Get message leaf index, needed for claiming in public +// const wethDepositMaybeIndexAndPath = await aztecNode.getL1ToL2MessageMembershipWitness( +// 'latest', +// wethDepositMsgHash, +// 0n, +// ); +// assert(wethDepositMaybeIndexAndPath !== undefined, 'Message not found in tree'); +// const wethDepositMessageLeafIndex = wethDepositMaybeIndexAndPath[0]; + +// // 2. Claim WETH on L2 +// logger.info('Minting weth on L2'); +// await wethCrossChainHarness.consumeMessageOnAztecAndMintPublicly( +// wethAmountToBridge, +// secretForMintingWeth, +// wethDepositMessageLeafIndex, +// ); +// await wethCrossChainHarness.expectPublicBalanceOnL2(ownerAddress, wethAmountToBridge); + +// // Store balances +// const wethL2BalanceBeforeSwap = await wethCrossChainHarness.getL2PublicBalanceOf(ownerAddress); +// const daiL2BalanceBeforeSwap = await daiCrossChainHarness.getL2PublicBalanceOf(ownerAddress); + +// // 3. Owner gives uniswap approval to transfer funds on its behalf +// const nonceForWETHTransferApproval = new Fr(1n); + +// await ownerWallet +// .setPublicAuthWit( +// { +// caller: uniswapL2Contract.address, +// action: wethCrossChainHarness.l2Token.methods +// .transfer_in_public( +// ownerAddress, +// uniswapL2Contract.address, +// wethAmountToBridge, +// nonceForWETHTransferApproval, +// ) +// .request(), +// }, +// true, +// ) +// .send() +// .wait(); + +// // 4. Swap on L1 - sends L2 to L1 message to withdraw WETH to L1 and another message to swap assets. +// const [secretForDepositingSwappedDai, secretHashForDepositingSwappedDai] = +// daiCrossChainHarness.generateClaimSecret(); + +// // 4.1 Owner approves user to swap on their behalf: +// const nonceForSwap = new Fr(3n); +// const action = uniswapL2Contract +// .withWallet(sponsorWallet) +// .methods.swap_public( +// ownerAddress, +// wethCrossChainHarness.l2Bridge.address, +// wethAmountToBridge, +// daiCrossChainHarness.l2Bridge.address, +// nonceForWETHTransferApproval, +// uniswapFeeTier, +// minimumOutputAmount, +// ownerAddress, +// secretHashForDepositingSwappedDai, +// ownerEthAddress, +// nonceForSwap, +// ); +// await ownerWallet.setPublicAuthWit({ caller: sponsorAddress, action }, true).send().wait(); + +// // 4.2 Call swap_public from user2 on behalf of owner +// const uniswapL2Interaction = await action.send().wait(); + +// const swapPublicContent = sha256ToField([ +// Buffer.from( +// toFunctionSelector('swap_public(address,uint256,uint24,address,uint256,bytes32,bytes32,address)').substring( +// 2, +// ), +// 'hex', +// ), +// wethCrossChainHarness.tokenPortalAddress.toBuffer32(), +// new Fr(wethAmountToBridge), +// new Fr(uniswapFeeTier), +// daiCrossChainHarness.tokenPortalAddress.toBuffer32(), +// new Fr(minimumOutputAmount), +// ownerAddress, +// secretHashForDepositingSwappedDai, +// ownerEthAddress.toBuffer32(), +// ]); + +// const swapPublicLeaf = sha256ToField([ +// uniswapL2Contract.address, +// new Fr(1), // aztec version +// EthAddress.fromString(uniswapPortal.address).toBuffer32(), +// new Fr(publicClient.chain.id), // chain id +// swapPublicContent, +// ]); + +// const withdrawContent = sha256ToField([ +// Buffer.from(toFunctionSelector('withdraw(address,uint256,address)').substring(2), 'hex'), +// uniswapPortalAddress.toBuffer32(), +// new Fr(wethAmountToBridge), +// uniswapPortalAddress.toBuffer32(), +// ]); + +// const withdrawLeaf = sha256ToField([ +// wethCrossChainHarness.l2Bridge.address, +// new Fr(1), // aztec version +// wethCrossChainHarness.tokenPortalAddress.toBuffer32(), +// new Fr(publicClient.chain.id), // chain id +// withdrawContent, +// ]); + +// // check weth balance of owner on L2 (we first bridged `wethAmountToBridge` into L2 and now withdrew it!) +// await wethCrossChainHarness.expectPublicBalanceOnL2(ownerAddress, wethL2BalanceBeforeSwap - wethAmountToBridge); + +// // 5. Perform the swap on L1 with the `uniswapPortal.swap_private()` (consuming L2 to L1 messages) +// logger.info('Execute withdraw and swap on the uniswapPortal!'); +// const daiL1BalanceOfPortalBeforeSwap = await daiCrossChainHarness.getL1BalanceOf( +// daiCrossChainHarness.tokenPortalAddress, +// ); + +// const [swapPrivateL2MessageIndex, swapPrivateSiblingPath] = await aztecNode.getL2ToL1MessageMembershipWitness( +// uniswapL2Interaction.blockNumber!, +// swapPublicLeaf, +// ); +// const [withdrawL2MessageIndex, withdrawSiblingPath] = await aztecNode.getL2ToL1MessageMembershipWitness( +// uniswapL2Interaction.blockNumber!, +// withdrawLeaf, +// ); + +// const withdrawMessageMetadata = { +// _l2BlockNumber: BigInt(uniswapL2Interaction.blockNumber!), +// _leafIndex: BigInt(withdrawL2MessageIndex), +// _path: withdrawSiblingPath +// .toBufferArray() +// .map((buf: Buffer) => `0x${buf.toString('hex')}`) as readonly `0x${string}`[], +// }; + +// const swapPrivateMessageMetadata = { +// _l2BlockNumber: BigInt(uniswapL2Interaction.blockNumber!), +// _leafIndex: BigInt(swapPrivateL2MessageIndex), +// _path: swapPrivateSiblingPath +// .toBufferArray() +// .map((buf: Buffer) => `0x${buf.toString('hex')}`) as readonly `0x${string}`[], +// }; + +// const swapArgs = [ +// wethCrossChainHarness.tokenPortalAddress.toString(), +// wethAmountToBridge, +// Number(uniswapFeeTier), +// daiCrossChainHarness.tokenPortalAddress.toString(), +// minimumOutputAmount, +// ownerAddress.toString(), +// secretHashForDepositingSwappedDai.toString(), +// true, +// [withdrawMessageMetadata, swapPrivateMessageMetadata], +// ] as const; + +// // this should also insert a message into the inbox. +// const txHash = await uniswapPortal.write.swapPublic(swapArgs, {} as any); + +// // We get the msg leaf from event so that we can later wait for it to be available for consumption +// let outTokenDepositMsgHash: Fr; +// { +// const txReceipt = await daiCrossChainHarness.publicClient.waitForTransactionReceipt({ +// hash: txHash, +// }); + +// const txLog = txReceipt.logs[9]; +// const topics = decodeEventLog({ +// abi: InboxAbi, +// data: txLog.data, +// topics: txLog.topics, +// }); +// outTokenDepositMsgHash = Fr.fromHexString(topics.args.hash); +// } + +// // weth was swapped to dai and send to portal +// const daiL1BalanceOfPortalAfter = await daiCrossChainHarness.getL1BalanceOf( +// daiCrossChainHarness.tokenPortalAddress, +// ); +// expect(daiL1BalanceOfPortalAfter).toBeGreaterThan(daiL1BalanceOfPortalBeforeSwap); +// const daiAmountToBridge = BigInt(daiL1BalanceOfPortalAfter - daiL1BalanceOfPortalBeforeSwap); + +// // Wait for the message to be available for consumption +// await daiCrossChainHarness.makeMessageConsumable(outTokenDepositMsgHash); + +// // Get message leaf index, needed for claiming in public +// const outTokenDepositMaybeIndexAndPath = await aztecNode.getL1ToL2MessageMembershipWitness( +// 'latest', +// outTokenDepositMsgHash, +// 0n, +// ); +// assert(outTokenDepositMaybeIndexAndPath !== undefined, 'Message not found in tree'); +// const outTokenDepositMessageLeafIndex = outTokenDepositMaybeIndexAndPath[0]; + +// // 6. claim dai on L2 +// logger.info('Consuming messages to mint dai on L2'); +// await daiCrossChainHarness.consumeMessageOnAztecAndMintPublicly( +// daiAmountToBridge, +// secretForDepositingSwappedDai, +// outTokenDepositMessageLeafIndex, +// ); +// await daiCrossChainHarness.expectPublicBalanceOnL2(ownerAddress, daiL2BalanceBeforeSwap + daiAmountToBridge); + +// const wethL2BalanceAfterSwap = await wethCrossChainHarness.getL2PublicBalanceOf(ownerAddress); +// const daiL2BalanceAfterSwap = await daiCrossChainHarness.getL2PublicBalanceOf(ownerAddress); + +// logger.info('WETH balance before swap: ', wethL2BalanceBeforeSwap.toString()); +// logger.info('DAI balance before swap : ', daiL2BalanceBeforeSwap.toString()); +// logger.info('***** 🧚‍♀️ SWAP L2 assets on L1 Uniswap 🧚‍♀️ *****'); +// logger.info('WETH balance after swap : ', wethL2BalanceAfterSwap.toString()); +// logger.info('DAI balance after swap : ', daiL2BalanceAfterSwap.toString()); +// }); +``` +> Source code: yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts#L369-L602 + + diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/index.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/index.md new file mode 100644 index 000000000000..0555761a6e08 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/index.md @@ -0,0 +1,35 @@ +--- +title: Swap on L1 from L2s +sidebar_position: 7 +--- + +import Image from "@theme/IdealImage"; + +# Swap on L1 Uniswap from L2 + +This smart contract example allows someone with funds on L2 to be able to swap using L1 Uniswap and then get the swapped assets back to L2. In this example, L1 will refer to Ethereum and L2 will refer to Aztec. + +:::note +This JavaScript in this tutorial is for the sandbox and will need adjustments if deploying to testnet. Install the sandbox [here](../../../../getting_started.md). +::: + +The flow will be: + +1. The user withdraws their “input” assets to L1 (i.e. burn them on L2 and create a L2 to L1 message to withdraw) +2. We create an L2 → L1 message to swap on L1 +3. On L1, the user gets their input tokens, consumes the swap message, and executes the swap +4. The user deposits the “output” tokens to the output token portal so it can be deposited into L2 +5. We will assume that token portals and token bridges for the input and output tokens must exist. These are what we built in the [token bridge tutorial](../token_bridge.md). + +The execution of swap on L1 should be designed such that any 3rd party can execute the swap on behalf of the user. This helps maintain user privacy by not requiring links between L1 and L2 activity. + +This reference will cover: + +1. Uniswap Portal - a contract on L1 that talks to the input token portal to withdraw the assets, executes the swap, and deposits the swapped tokens back to L2 +2. Uniswap L2 contract - a contract on L2 that creates the needed messages to perform the swap on L1 + + + +This diagram describes the private flow. + +This code works alongside a token portal that you can learn to build [in this codealong tutorial](../token_bridge.md). diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/l1_contract.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/l1_contract.md new file mode 100644 index 000000000000..c73aa6fd28bd --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/l1_contract.md @@ -0,0 +1,285 @@ +--- +title: L1 contracts (EVM) +sidebar_position: 2 +--- + +This page goes over the code in the L1 contract for Uniswap, which works alongside a [token portal (codealong tutorial)](../token_bridge.md). + +## Setup + +```solidity title="setup" showLineNumbers +import {TokenPortal} from "./TokenPortal.sol"; +import {ISwapRouter} from "../external/ISwapRouter.sol"; + +/** + * @title UniswapPortal + * @author Aztec Labs + * @notice A minimal portal that allow an user inside L2, to withdraw asset A from the Rollup + * swap asset A to asset B, and deposit asset B into the rollup again. + * Relies on Uniswap for doing the swap, TokenPortals for A and B to get and send tokens + * and the message boxes (inbox & outbox). + */ +contract UniswapPortal { + ISwapRouter public constant ROUTER = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); + + IRegistry public registry; + bytes32 public l2UniswapAddress; + IRollup public rollup; + IOutbox public outbox; + uint256 public rollupVersion; + + function initialize(address _registry, bytes32 _l2UniswapAddress) external { + registry = IRegistry(_registry); + l2UniswapAddress = _l2UniswapAddress; + + rollup = IRollup(address(registry.getCanonicalRollup())); + outbox = rollup.getOutbox(); + rollupVersion = rollup.getVersion(); + } + + // Using a struct here to avoid stack too deep errors + struct LocalSwapVars { + IERC20 inputAsset; + IERC20 outputAsset; + bytes32 contentHash; + } +``` +> Source code: l1-contracts/test/portals/UniswapPortal.sol#L12-L48 + + +## Public swap + +```solidity title="solidity_uniswap_swap_public" showLineNumbers +/** + * @notice Exit with funds from L2, perform swap on L1 and deposit output asset to L2 again publicly + * @dev `msg.value` indicates fee to submit message to inbox. Currently, anyone can call this method on your behalf. + * They could call it with 0 fee causing the sequencer to never include in the rollup. + * In this case, you will have to cancel the message and then make the deposit later + * @param _inputTokenPortal - The ethereum address of the input token portal + * @param _inAmount - The amount of assets to swap (same amount as withdrawn from L2) + * @param _uniswapFeeTier - The fee tier for the swap on UniswapV3 + * @param _outputTokenPortal - The ethereum address of the output token portal + * @param _amountOutMinimum - The minimum amount of output assets to receive from the swap (slippage protection) + * @param _aztecRecipient - The aztec address to receive the output assets + * @param _secretHashForL1ToL2Message - The hash of the secret consumable message. The hash should be 254 bits (so it can fit in a Field element) + * @param _withCaller - When true, using `msg.sender` as the caller, otherwise address(0) + * @return A hash of the L1 to L2 message inserted in the Inbox + */ +function swapPublic( + address _inputTokenPortal, + uint256 _inAmount, + uint24 _uniswapFeeTier, + address _outputTokenPortal, + uint256 _amountOutMinimum, + bytes32 _aztecRecipient, + bytes32 _secretHashForL1ToL2Message, + bool _withCaller, + // Avoiding stack too deep + PortalDataStructures.OutboxMessageMetadata[2] calldata _outboxMessageMetadata +) public returns (bytes32, uint256) { + LocalSwapVars memory vars; + + vars.inputAsset = TokenPortal(_inputTokenPortal).underlying(); + vars.outputAsset = TokenPortal(_outputTokenPortal).underlying(); + + // Withdraw the input asset from the portal + { + TokenPortal(_inputTokenPortal).withdraw( + address(this), + _inAmount, + true, + _outboxMessageMetadata[0]._l2BlockNumber, + _outboxMessageMetadata[0]._leafIndex, + _outboxMessageMetadata[0]._path + ); + } + + { + // prevent stack too deep errors + // The purpose of including the function selector is to make the message unique to that specific call. Note that + // it has nothing to do with calling the function. + vars.contentHash = Hash.sha256ToField( + abi.encodeWithSignature( + "swap_public(address,uint256,uint24,address,uint256,bytes32,bytes32,address)", + _inputTokenPortal, + _inAmount, + _uniswapFeeTier, + _outputTokenPortal, + _amountOutMinimum, + _aztecRecipient, + _secretHashForL1ToL2Message, + _withCaller ? msg.sender : address(0) + ) + ); + } + + // Consume the message from the outbox + { + outbox.consume( + DataStructures.L2ToL1Msg({ + sender: DataStructures.L2Actor(l2UniswapAddress, rollupVersion), + recipient: DataStructures.L1Actor(address(this), block.chainid), + content: vars.contentHash + }), + _outboxMessageMetadata[1]._l2BlockNumber, + _outboxMessageMetadata[1]._leafIndex, + _outboxMessageMetadata[1]._path + ); + } + + // Perform the swap + ISwapRouter.ExactInputSingleParams memory swapParams; + { + swapParams = ISwapRouter.ExactInputSingleParams({ + tokenIn: address(vars.inputAsset), + tokenOut: address(vars.outputAsset), + fee: _uniswapFeeTier, + recipient: address(this), + deadline: block.timestamp, + amountIn: _inAmount, + amountOutMinimum: _amountOutMinimum, + sqrtPriceLimitX96: 0 + }); + } + // Note, safeApprove was deprecated from Oz + vars.inputAsset.approve(address(ROUTER), _inAmount); + uint256 amountOut = ROUTER.exactInputSingle(swapParams); + + // approve the output token portal to take funds from this contract + // Note, safeApprove was deprecated from Oz + vars.outputAsset.approve(address(_outputTokenPortal), amountOut); + + // Deposit the output asset to the L2 via its portal + return TokenPortal(_outputTokenPortal).depositToAztecPublic( + _aztecRecipient, amountOut, _secretHashForL1ToL2Message + ); +} +``` +> Source code: l1-contracts/test/portals/UniswapPortal.sol#L50-L155 + + +1. It fetches the input and output tokens we are swapping. The Uniswap portal only needs to know the portal addresses of the input and output as they store the underlying ERC20 token address. +2. Consumes the `withdraw` message to get input tokens on L1 to itself. This is needed to execute the swap. + + Before it actually can swap, it checks if the provided swap parameters were what the user actually wanted by creating a message content hash (similar to what we did in the L2 contract) to ensure the right parameters are used. + +3. Executes the swap and receives the output funds to itself. + + The deadline by which the funds should be swapped is `block.timestamp` i.e. this block itself. This makes things atomic on the L1 side. + +4. The portal must deposit the output funds back to L2 using the output token’s portal. For this we first approve the token portal to move Uniswap funds, and then call the portal’s `depositToAztecPublic()` method to transfer funds to the portal and create a L1 → l2 message to mint the right amount of output tokens on L2. + +To incentivize the sequencer to pick up this message, we pass a fee to the deposit message. + +You can find the corresponding function on the [L2 contracts page](./l2_contract.md#public-swap). + +## Private swap + +This works very similarly to the public flow. + +```solidity title="solidity_uniswap_swap_private" showLineNumbers + /** + * @notice Exit with funds from L2, perform swap on L1 and deposit output asset to L2 again privately + * @dev `msg.value` indicates fee to submit message to inbox. Currently, anyone can call this method on your behalf. + * They could call it with 0 fee causing the sequencer to never include in the rollup. + * In this case, you will have to cancel the message and then make the deposit later + * @param _inputTokenPortal - The ethereum address of the input token portal + * @param _inAmount - The amount of assets to swap (same amount as withdrawn from L2) + * @param _uniswapFeeTier - The fee tier for the swap on UniswapV3 + * @param _outputTokenPortal - The ethereum address of the output token portal + * @param _amountOutMinimum - The minimum amount of output assets to receive from the swap (slippage protection) + * @param _secretHashForL1ToL2Message - The hash of the secret consumable message. The hash should be 254 bits (so it can fit in a Field element) + * @param _withCaller - When true, using `msg.sender` as the caller, otherwise address(0) + * @return A hash of the L1 to L2 message inserted in the Inbox + */ + function swapPrivate( + address _inputTokenPortal, + uint256 _inAmount, + uint24 _uniswapFeeTier, + address _outputTokenPortal, + uint256 _amountOutMinimum, + bytes32 _secretHashForL1ToL2Message, + bool _withCaller, + // Avoiding stack too deep + PortalDataStructures.OutboxMessageMetadata[2] calldata _outboxMessageMetadata + ) public returns (bytes32, uint256) { + LocalSwapVars memory vars; + + vars.inputAsset = TokenPortal(_inputTokenPortal).underlying(); + vars.outputAsset = TokenPortal(_outputTokenPortal).underlying(); + + { + TokenPortal(_inputTokenPortal).withdraw( + address(this), + _inAmount, + true, + _outboxMessageMetadata[0]._l2BlockNumber, + _outboxMessageMetadata[0]._leafIndex, + _outboxMessageMetadata[0]._path + ); + } + + { + // prevent stack too deep errors + // The purpose of including the function selector is to make the message unique to that specific call. Note that + // it has nothing to do with calling the function. + vars.contentHash = Hash.sha256ToField( + abi.encodeWithSignature( + "swap_private(address,uint256,uint24,address,uint256,bytes32,address)", + _inputTokenPortal, + _inAmount, + _uniswapFeeTier, + _outputTokenPortal, + _amountOutMinimum, + _secretHashForL1ToL2Message, + _withCaller ? msg.sender : address(0) + ) + ); + } + + // Consume the message from the outbox + { + outbox.consume( + DataStructures.L2ToL1Msg({ + sender: DataStructures.L2Actor(l2UniswapAddress, rollupVersion), + recipient: DataStructures.L1Actor(address(this), block.chainid), + content: vars.contentHash + }), + _outboxMessageMetadata[1]._l2BlockNumber, + _outboxMessageMetadata[1]._leafIndex, + _outboxMessageMetadata[1]._path + ); + } + + // Perform the swap + ISwapRouter.ExactInputSingleParams memory swapParams; + { + swapParams = ISwapRouter.ExactInputSingleParams({ + tokenIn: address(vars.inputAsset), + tokenOut: address(vars.outputAsset), + fee: _uniswapFeeTier, + recipient: address(this), + deadline: block.timestamp, + amountIn: _inAmount, + amountOutMinimum: _amountOutMinimum, + sqrtPriceLimitX96: 0 + }); + } + // Note, safeApprove was deprecated from Oz + vars.inputAsset.approve(address(ROUTER), _inAmount); + uint256 amountOut = ROUTER.exactInputSingle(swapParams); + + // approve the output token portal to take funds from this contract + // Note, safeApprove was deprecated from Oz + vars.outputAsset.approve(address(_outputTokenPortal), amountOut); + + // Deposit the output asset to the L2 via its portal + return + TokenPortal(_outputTokenPortal).depositToAztecPrivate(amountOut, _secretHashForL1ToL2Message); + } +} +``` +> Source code: l1-contracts/test/portals/UniswapPortal.sol#L157-L258 + + +You can find the corresponding function on the [L2 contracts page](./l2_contract.md#private-swap). diff --git a/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/l2_contract.md b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/l2_contract.md new file mode 100644 index 000000000000..547d30ddda48 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/developers/tutorials/codealong/js_tutorials/uniswap/l2_contract.md @@ -0,0 +1,409 @@ +--- +title: L2 Contracts (Aztec) +sidebar_position: 1 +--- + +This page goes over the code in the L2 contract for Uniswap, which works alongside a [token bridge (codealong tutorial)](../token_bridge.md). + +## Main.nr + +### Setup and constructor + +```rust title="uniswap_setup" showLineNumbers +mod util; + +// Demonstrates how to use portal contracts to swap on L1 Uniswap with funds on L2 +// Has two separate flows for private and public respectively +// Uses the token bridge contract, which tells which input token we need to talk to and handles the exit funds to L1 +use dep::aztec::macros::aztec; + +#[aztec] +pub contract Uniswap { + use dep::aztec::prelude::{AztecAddress, EthAddress, FunctionSelector, PublicImmutable}; + + use dep::authwit::auth::{ + assert_current_call_valid_authwit_public, compute_authwit_message_hash_from_call, + set_authorized, + }; + + use crate::util::{compute_swap_private_content_hash, compute_swap_public_content_hash}; + use dep::aztec::macros::{functions::{initializer, internal, private, public}, storage::storage}; + use dep::token::Token; + use dep::token_bridge::TokenBridge; + + use dep::aztec::protocol_types::traits::ToField; + + #[storage] + struct Storage { + portal_address: PublicImmutable, + } + + #[public] + #[initializer] + fn constructor(portal_address: EthAddress) { + storage.portal_address.initialize(portal_address); + } +``` +> Source code: noir-projects/noir-contracts/contracts/app/uniswap_contract/src/main.nr#L1-L35 + +We just need to store the portal address for the token that we want to swap. + +### Public swap + +```rust title="swap_public" showLineNumbers +#[public] +fn swap_public( + sender: AztecAddress, + input_asset_bridge: AztecAddress, + input_amount: u128, + output_asset_bridge: AztecAddress, + // params for using the transfer approval + nonce_for_transfer_approval: Field, + // params for the swap + uniswap_fee_tier: Field, + minimum_output_amount: u128, + // params for the depositing output_asset back to Aztec + recipient: AztecAddress, + secret_hash_for_L1_to_l2_message: Field, + caller_on_L1: EthAddress, + // nonce for someone to call swap on sender's behalf + nonce_for_swap_approval: Field, +) { + if (!sender.eq(context.msg_sender())) { + assert_current_call_valid_authwit_public(&mut context, sender); + } + + let input_asset_bridge_config = + TokenBridge::at(input_asset_bridge).get_config_public().view(&mut context); + + let input_asset = input_asset_bridge_config.token; + let input_asset_bridge_portal_address = input_asset_bridge_config.portal; + + // Transfer funds to this contract + Token::at(input_asset) + .transfer_in_public( + sender, + context.this_address(), + input_amount, + nonce_for_transfer_approval, + ) + .call(&mut context); + + // Approve bridge to burn this contract's funds and exit to L1 Uniswap Portal + Uniswap::at(context.this_address()) + ._approve_bridge_and_exit_input_asset_to_L1( + input_asset, + input_asset_bridge, + input_amount, + ) + .call(&mut context); + // Create swap message and send to Outbox for Uniswap Portal + // this ensures the integrity of what the user originally intends to do on L1. + let output_asset_bridge_portal_address = + TokenBridge::at(output_asset_bridge).get_config_public().view(&mut context).portal; + // ensure portal exists - else funds might be lost + assert( + !input_asset_bridge_portal_address.is_zero(), + "L1 portal address of input_asset's bridge is 0", + ); + assert( + !output_asset_bridge_portal_address.is_zero(), + "L1 portal address of output_asset's bridge is 0", + ); + + let content_hash = compute_swap_public_content_hash( + input_asset_bridge_portal_address, + input_amount, + uniswap_fee_tier, + output_asset_bridge_portal_address, + minimum_output_amount, + recipient, + secret_hash_for_L1_to_l2_message, + caller_on_L1, + ); + context.message_portal(storage.portal_address.read(), content_hash); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/uniswap_contract/src/main.nr#L37-L110 + + +1. We check that `msg.sender()` has appropriate approval to call this on behalf of the sender by constructing an authwit message and checking if `from` has given the approval (read more about authwit [here](../../../../../aztec/concepts/advanced/authwit.md)). +2. We fetch the underlying aztec token that needs to be swapped. +3. We transfer the user’s funds to the Uniswap contract. Like with Ethereum, the user must have provided approval to the Uniswap contract to do so. The user must provide the nonce they used in the approval for transfer, so that Uniswap can send it to the token contract, to prove it has appropriate approval. +4. Funds are added to the Uniswap contract. +5. Uniswap must exit the input tokens to L1. For this it has to approve the bridge to burn its tokens on its behalf and then actually exit the funds. We call the [`exit_to_l1_public()` method on the token bridge](../token_bridge.md). We use the public flow for exiting since we are operating on public state. +6. It is not enough for us to simply emit a message to withdraw the funds. We also need to emit a message to display our swap intention. If we do not do this, there is nothing stopping a third party from calling the Uniswap portal with their own parameters and consuming our message. + +So the Uniswap portal (on L1) needs to know: + +- The token portals for the input and output token (to withdraw the input token to L1 and later deposit the output token to L2) +- The amount of input tokens they want to swap +- The Uniswap fee tier they want to use +- The minimum output amount they can accept (for slippage protection) + +The Uniswap portal must first withdraw the input tokens, then check that the swap message exists in the outbox, execute the swap, and then call the output token to deposit the swapped tokens to L2. So the Uniswap portal must also be pass any parameters needed to complete the deposit of swapped tokens to L2. From the tutorial on building token bridges we know these are: + +- The address on L2 which must receive the output tokens (remember this is public flow) +- The secret hash for consume the L1 to L2 message. Since this is the public flow the preimage doesn’t need to be a secret. + +You can find the corresponding function on the [L1 contracts page](./l1_contract.md). + +### Private swap + +```rust title="swap_private" showLineNumbers +#[private] +fn swap_private( + input_asset: AztecAddress, // since private, we pass here and later assert that this is as expected by input_bridge + input_asset_bridge: AztecAddress, + input_amount: u128, + output_asset_bridge: AztecAddress, + // params for using the transfer_to_public approval + nonce_for_transfer_to_public_approval: Field, + // params for the swap + uniswap_fee_tier: Field, // which uniswap tier to use (eg 3000 for 0.3% fee) + minimum_output_amount: u128, // minimum output amount to receive (slippage protection for the swap) + // params for the depositing output_asset back to Aztec + secret_hash_for_L1_to_l2_message: Field, // for when l1 uniswap portal inserts the message to consume output assets on L2 + caller_on_L1: EthAddress, // ethereum address that can call this function on the L1 portal (0x0 if anyone can call) +) { + let input_asset_bridge_config = + TokenBridge::at(input_asset_bridge).get_config().view(&mut context); + let output_asset_bridge_config = + TokenBridge::at(output_asset_bridge).get_config().view(&mut context); + + // Assert that user provided token address is same as expected by token bridge. + // we can't directly use `input_asset_bridge.token` because that is a public method and public can't return data to private + assert( + input_asset.eq(input_asset_bridge_config.token), + "input_asset address is not the same as seen in the bridge contract", + ); + + // Transfer funds to this contract + Token::at(input_asset) + .transfer_to_public( + context.msg_sender(), + context.this_address(), + input_amount, + nonce_for_transfer_to_public_approval, + ) + .call(&mut context); + + // Approve bridge to burn this contract's funds and exit to L1 Uniswap Portal + Uniswap::at(context.this_address()) + ._approve_bridge_and_exit_input_asset_to_L1( + input_asset, + input_asset_bridge, + input_amount, + ) + .enqueue(&mut context); + + // Create swap message and send to Outbox for Uniswap Portal + // this ensures the integrity of what the user originally intends to do on L1. + + // ensure portal exists - else funds might be lost + assert( + !input_asset_bridge_config.portal.is_zero(), + "L1 portal address of input_asset's bridge is 0", + ); + assert( + !output_asset_bridge_config.portal.is_zero(), + "L1 portal address of output_asset's bridge is 0", + ); + + let content_hash = compute_swap_private_content_hash( + input_asset_bridge_config.portal, + input_amount, + uniswap_fee_tier, + output_asset_bridge_config.portal, + minimum_output_amount, + secret_hash_for_L1_to_l2_message, + caller_on_L1, + ); + context.message_portal(storage.portal_address.read(), content_hash); +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/uniswap_contract/src/main.nr#L112-L183 + + +This uses a util function `compute_swap_private_content_hash()` - find that [here](#utils) + +This flow works similarly to the public flow with a few notable changes: + +- Notice how in the `swap_private()`, user has to pass in `token` address which they didn't in the public flow? Since `swap_private()` is a private method, it can't read what token is publicly stored on the token bridge, so instead the user passes a token address, and `_assert_token_is_same()` checks that this user provided address is same as the one in storage. Note that because public functions are executed by the sequencer while private methods are executed locally, all public calls are always done after all private calls are done. So first the burn would happen and only later the sequencer asserts that the token is same. Note that the sequencer just sees a request to `execute_assert_token_is_same` and therefore has no context on what the appropriate private method was. If the assertion fails, then the kernel circuit will fail to create a proof and hence the transaction will be dropped. +- In the public flow, the user calls `transfer_in_public()`. Here instead, the user calls `transfer_to_public()`. Why? The user can't directly transfer their private tokens (their notes) to the uniswap contract, because later the Uniswap contract has to approve the bridge to burn these notes and withdraw to L1. The authwit flow for the private domain requires a signature from the `sender`, which in this case would be the Uniswap contract. For the contract to sign, it would need a private key associated to it. But who would operate this key? +- To work around this, the user can transfer to public their private tokens into Uniswap L2 contract. Transferring to public would convert user's private notes to public balance. It is a private method on the token contract that reduces a user’s private balance and then calls a public method to increase the recipient’s (ie Uniswap) public balance. **Remember that first all private methods are executed and then later all public methods will be - so the Uniswap contract won’t have the funds until public execution begins.** +- Now uniswap has public balance (like with the public flow). Hence, `swap_private()` calls the internal public method which approves the input token bridge to burn Uniswap’s tokens and calls `exit_to_l1_public` to create an L2 → L1 message to exit to L1. +- Constructing the message content for swapping works exactly as the public flow except instead of specifying who would be the Aztec address that receives the swapped funds, we specify a secret hash. Only those who know the preimage to the secret can later redeem the minted notes to themselves. + +### Approve the bridge to burn this contract's funds + +Both public and private swap functions call this function: + +```rust title="authwit_uniswap_set" showLineNumbers +// This helper method approves the bridge to burn this contract's funds and exits the input asset to L1 +// Assumes contract already has funds. +// Assume `token` relates to `token_bridge` (ie token_bridge.token == token) +// Note that private can't read public return values so created an internal public that handles everything +// this method is used for both private and public swaps. +#[public] +#[internal] +fn _approve_bridge_and_exit_input_asset_to_L1( + token: AztecAddress, + token_bridge: AztecAddress, + amount: u128, +) { + // Since we will authorize and instantly spend the funds, all in public, we can use the same nonce + // every interaction. In practice, the authwit should be squashed, so this is also cheap! + let authwit_nonce = 0xdeadbeef; + + let selector = FunctionSelector::from_signature("burn_public((Field),u128,Field)"); + let message_hash = compute_authwit_message_hash_from_call( + token_bridge, + token, + context.chain_id(), + context.version(), + selector, + [context.this_address().to_field(), amount as Field, authwit_nonce], + ); + + // We need to make a call to update it. + set_authorized(&mut context, message_hash, true); + + let this_portal_address = storage.portal_address.read(); + // Exit to L1 Uniswap Portal ! + TokenBridge::at(token_bridge) + .exit_to_l1_public(this_portal_address, amount, this_portal_address, authwit_nonce) + .call(&mut context) +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/uniswap_contract/src/main.nr#L185-L221 + + +## Utils + +### Compute content hash for public + +```rust title="uniswap_public_content_hash" showLineNumbers +use dep::aztec::prelude::{AztecAddress, EthAddress}; +use dep::aztec::protocol_types::{hash::sha256_to_field, traits::ToField}; + +// This method computes the L2 to L1 message content hash for the public +// refer `l1-contracts/test/portals/UniswapPortal.sol` on how L2 to L1 message is expected +pub fn compute_swap_public_content_hash( + input_asset_bridge_portal_address: EthAddress, + input_amount: u128, + uniswap_fee_tier: Field, + output_asset_bridge_portal_address: EthAddress, + minimum_output_amount: u128, + aztec_recipient: AztecAddress, + secret_hash_for_L1_to_l2_message: Field, + caller_on_L1: EthAddress, +) -> Field { + let mut hash_bytes = [0; 260]; // 8 fields of 32 bytes each + 4 bytes fn selector + let input_token_portal_bytes: [u8; 32] = + input_asset_bridge_portal_address.to_field().to_be_bytes(); + let in_amount_bytes: [u8; 32] = input_amount.to_field().to_be_bytes(); + let uniswap_fee_tier_bytes: [u8; 32] = uniswap_fee_tier.to_be_bytes(); + let output_token_portal_bytes: [u8; 32] = + output_asset_bridge_portal_address.to_field().to_be_bytes(); + let amount_out_min_bytes: [u8; 32] = minimum_output_amount.to_field().to_be_bytes(); + let aztec_recipient_bytes: [u8; 32] = aztec_recipient.to_field().to_be_bytes(); + let secret_hash_for_L1_to_l2_message_bytes: [u8; 32] = + secret_hash_for_L1_to_l2_message.to_be_bytes(); + let caller_on_L1_bytes: [u8; 32] = caller_on_L1.to_field().to_be_bytes(); + + // The purpose of including the following selector is to make the message unique to that specific call. Note that + // it has nothing to do with calling the function. + let selector = comptime { + keccak256::keccak256( + "swap_public(address,uint256,uint24,address,uint256,bytes32,bytes32,address)".as_bytes(), + 75, + ) + }; + + hash_bytes[0] = selector[0]; + hash_bytes[1] = selector[1]; + hash_bytes[2] = selector[2]; + hash_bytes[3] = selector[3]; + + for i in 0..32 { + hash_bytes[i + 4] = input_token_portal_bytes[i]; + hash_bytes[i + 36] = in_amount_bytes[i]; + hash_bytes[i + 68] = uniswap_fee_tier_bytes[i]; + hash_bytes[i + 100] = output_token_portal_bytes[i]; + hash_bytes[i + 132] = amount_out_min_bytes[i]; + hash_bytes[i + 164] = aztec_recipient_bytes[i]; + hash_bytes[i + 196] = secret_hash_for_L1_to_l2_message_bytes[i]; + hash_bytes[i + 228] = caller_on_L1_bytes[i]; + } + + let content_hash = sha256_to_field(hash_bytes); + content_hash +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/uniswap_contract/src/util.nr#L1-L58 + + +This method computes the L2 to L1 message content hash for the public. To find out how it is consumed on L1, view the [L1 contracts page](./l1_contract.md) + +### Compute content hash for private + +```rust title="compute_swap_private_content_hash" showLineNumbers +// This method computes the L2 to L1 message content hash for the private +// refer `l1-contracts/test/portals/UniswapPortal.sol` on how L2 to L1 message is expected +pub fn compute_swap_private_content_hash( + input_asset_bridge_portal_address: EthAddress, + input_amount: u128, + uniswap_fee_tier: Field, + output_asset_bridge_portal_address: EthAddress, + minimum_output_amount: u128, + secret_hash_for_L1_to_l2_message: Field, + caller_on_L1: EthAddress, +) -> Field { + let mut hash_bytes = [0; 228]; // 7 fields of 32 bytes each + 4 bytes fn selector + let input_token_portal_bytes: [u8; 32] = + input_asset_bridge_portal_address.to_field().to_be_bytes(); + let in_amount_bytes: [u8; 32] = input_amount.to_field().to_be_bytes(); + let uniswap_fee_tier_bytes: [u8; 32] = uniswap_fee_tier.to_be_bytes(); + let output_token_portal_bytes: [u8; 32] = + output_asset_bridge_portal_address.to_field().to_be_bytes(); + let amount_out_min_bytes: [u8; 32] = minimum_output_amount.to_field().to_be_bytes(); + let secret_hash_for_L1_to_l2_message_bytes: [u8; 32] = + secret_hash_for_L1_to_l2_message.to_be_bytes(); + let caller_on_L1_bytes: [u8; 32] = caller_on_L1.to_field().to_be_bytes(); + + // The purpose of including the following selector is to make the message unique to that specific call. Note that + // it has nothing to do with calling the function. + let selector = comptime { + keccak256::keccak256( + "swap_private(address,uint256,uint24,address,uint256,bytes32,address)".as_bytes(), + 68, + ) + }; + + hash_bytes[0] = selector[0]; + hash_bytes[1] = selector[1]; + hash_bytes[2] = selector[2]; + hash_bytes[3] = selector[3]; + + for i in 0..32 { + hash_bytes[i + 4] = input_token_portal_bytes[i]; + hash_bytes[i + 36] = in_amount_bytes[i]; + hash_bytes[i + 68] = uniswap_fee_tier_bytes[i]; + hash_bytes[i + 100] = output_token_portal_bytes[i]; + hash_bytes[i + 132] = amount_out_min_bytes[i]; + hash_bytes[i + 164] = secret_hash_for_L1_to_l2_message_bytes[i]; + hash_bytes[i + 196] = caller_on_L1_bytes[i]; + } + let content_hash = sha256_to_field(hash_bytes); + content_hash +} +``` +> Source code: noir-projects/noir-contracts/contracts/app/uniswap_contract/src/util.nr#L60-L110 + + +This method computes the L2 to L1 message content hash for the private. To find out how it is consumed on L1, view the [L1 contracts page](./l1_contract.md). + +## Redeeming assets + +So you emitted a message to withdraw input tokens to L1 and a message to swap. Then you or someone on your behalf can swap on L1 and emit a message to deposit swapped assets to L2. diff --git a/docs/versioned_docs/version-v0.88.0/glossary.md b/docs/versioned_docs/version-v0.88.0/glossary.md new file mode 100644 index 000000000000..3ffab7fa478b --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/glossary.md @@ -0,0 +1,128 @@ +--- +title: Glossary +tags: [protocol, glossary] +--- + +import { Glossary } from '@site/src/components/Snippets/glossary_snippets'; + +### Aztec + + + +Full reference [here](./developers/reference/environment_reference/cli_reference). + +### Aztec Wallet + + + +Full reference [here](./developers/reference/environment_reference/cli_wallet_reference). + +### `aztec-nargo` + + + +You can read more about `nargo` [here](#nargo). + +### `aztec-up` + + + +### Aztec.js + + + +Read more and review the source code [here](https://github.com/AztecProtocol/aztec-packages/blob/v0.88.0/yarn-project/aztec.js). + +### Aztec.nr + + + +Read more and review the source code [here](https://aztec.nr). + +### Barretenberg + + + +### bb / bb.js + +`bb` (CLI) and its corresponding `bb.js` (node module) are tools that prove and verify circuits. It also has helpful functions such as: writing solidity verifier contracts, checking a witness, and viewing a circuit's gate count. + +### `nargo` + +With `nargo`, you can start new projects, compile, execute, and test your Noir programs. + +You can find more information in the nargo installation docs [here](https://noir-lang.org/docs/getting_started/installation/) and the nargo command reference [here](https://noir-lang.org/docs/reference/nargo_commands). + +### Noir + + + +### Noir Language Server + +The Noir Language Server can be used in vscode to facilitate writing programs in Noir by providing syntax highlighting, circuit introspection and an execution interface. The Noir LSP addon allows the dev to choose their tool, nargo or aztec-nargo, when writing a pure Noir program or an Aztec smart contract. + +You can find more info about the LSP [in the Noir docs](https://noir-lang.org/docs/tooling/language_server). + +### Node + + + +To run your own node see [here](./the_aztec_network/guides/run_nodes/index.md). + +### Note + +In Aztec, a Note is encrypted information stored by nodes in the network. Data in a note (once decrypted) may represent some variable's state at a point in time. + +### Provers + +Aztec will be launched with a fully permissionless proving network that anyone can participate in. + +How this works will be discussed via a future RFP process on Discourse, similarly to the Sequencer RFP. + +### Private Execution Environment (PXE) + + + +Read more [here](./aztec/concepts/pxe/index.md). + +### Sandbox + + + +### Sequencer + +Aztec will be launched with a fully permissionless sequencer network that anyone can participate in. + +How this works is being discussed actively in the [Discourse forum](https://discourse.aztec.network/t/request-for-proposals-decentralized-sequencer-selection/350/). Once this discussion process is completed, we will update the glossary and documentation with specifications and instructions for how to run. + +Sequencers are generally responsible for: + +- Selecting pending transactions from the mempool +- Ordering transactions into a block +- Verifying all private transaction proofs and execute all public transactions to check their validity +- Computing the ROLLUP_BLOCK_REQUEST_DATA +- Computing state updates for messages between L2 & L1 +- Broadcasting the ROLLUP_BLOCK_REQUEST_DATA to the prover network via the proof pool for parallelizable computation. +- Building a rollup proof from completed proofs in the proof pool +- Tagging the pending block with an upgrade signal to facilitate forks +- Publishing completed block with proofs to Ethereum as an ETH transaction + +Previously in [Aztec Connect](https://medium.com/aztec-protocol/sunsetting-aztec-connect-a786edce5cae) there was a single sequencer, and you can find the Typescript reference implementation called Falafel [here](https://github.com/AztecProtocol/aztec-connect/tree/master/yarn-project/falafel). + +### Smart Contracts + +Programs that run on the Aztec network are called smart contracts, similar to [programs](https://ethereum.org/en/developers/docs/smart-contracts/) that run on Ethereum. + +However, these will be written in the [Noir](https://noir-lang.org/index.html) programming language, and may optionally include private state and private functions. + +### Testing eXecution Environment (TXE) + + + +### Proving Key + +A key that is used to generate a proof. In the case of Aztec, these are compiled from Noir smart contracts. + +### Verification Key + +A key that is used to verify the validity of a proof generated from a proving key from the same smart contract. diff --git a/docs/versioned_docs/version-v0.88.0/index.mdx b/docs/versioned_docs/version-v0.88.0/index.mdx new file mode 100644 index 000000000000..47d3c000147a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/index.mdx @@ -0,0 +1,50 @@ +--- +title: Welcome +description: "Aztec introduces a privacy-centric zkRollup solution for Ethereum, enhancing confidentiality and scalability within the Ethereum ecosystem." +displayed_sidebar: sidebar +sidebar_position: 0 +--- + +# Aztec Documentation + +## What is Aztec? + +### Aztec is a Privacy-First L2 on Ethereum + +On Ethereum today, everything is publicly visible, by everyone. In the real world, people enjoy privacy. Aztec brings privacy to Ethereum. + +- private functions, executed and proved on a user's device +- public functions, executed in the Aztec Virtual Machine +- private state, stored as UTXOs that only the owner can decrypt +- public state, stored in a public merkle tree +- composability between private/public execution and private/public state +- public and private messaging with Ethereum + +To make this possible, Aztec is _not EVM compatible_ and is extending the Ethereum ecosystem by creating a new alt-VM! + +To learn more about how Aztec achieves these things, check out the [Aztec concepts overview](/aztec). + +## Start coding + +
+ + +

Developer Getting Started Guide

+
+ + Follow the getting started guide to start developing with the Aztec + Sandbox + +
+
+ +## Learn how Aztec works + +
+ + +

Aztec Overview

+
+ Learn the core concepts that make up the Aztec Protocol +
+
diff --git a/docs/versioned_docs/version-v0.88.0/migration_notes.md b/docs/versioned_docs/version-v0.88.0/migration_notes.md new file mode 100644 index 000000000000..aa62efa66b98 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/migration_notes.md @@ -0,0 +1,3232 @@ +--- +title: Migration notes +description: Read about migration notes from previous versions, which could solve problems while updating +keywords: [sandbox, aztec, notes, migration, updating, upgrading] +tags: [migration, updating, sandbox] +--- + +Aztec is in full-speed development. Literally every version breaks compatibility with the previous ones. This page attempts to target errors and difficulties you might encounter when upgrading, and how to resolve them. + +## 0.87.0 + +## [Aztec.js/TS libraries] + +We've bumped our minimum supported node version to v20, as v18 is now EOL. As a consequence, the deprecated type assertion syntax has been replaced with modern import attributes whenever contract artifact JSONs are loaded: + + +```diff +-import ArtifactJson from '../artifacts/contract-Contract.json' assert { type: 'json' }; ++import ArtifactJson from '../artifacts/contract-Contract.json' with { type: 'json' }; +``` + +## [Aztec.js/PXE] `simulateUtility` return type + +`pxe.simulateUtility()` now returns a complex object (much like `.simulateTx()`) so extra information can be provided such as simulation timings. + +This information can be accessed setting the `includeMetadata` flag in `SimulateMethodOptions` to `true`, but not providing it (which is the default) will NOT change the behavior of the current code. + +```diff +-const result = await pxe.simulateUtility(...); ++const { meta, result } = await pxe.simulateUtility(...); + +const result = await Contract.methods.myFunction(...).simulate(); +const { result, meta} = await Contract.methods.myFunction(...).simulate({ includeMetadata: true }); + +``` + +## [Aztec.js] Removed mandatory simulation before proving in contract interfaces + +Previously, our autogenerated contract classes would perform a simulation when calling `.prove` or `.send` on them. This could potentially catch errors earlier, but took away control from the app/wallets on how to handle network interactions. Now this process has to be triggered manually, which means just proving an interaction (or proving and sending it to the network in one go via `.send`) is much faster. + +*WARNING:* This means users can incurr in network fees if a transaction that would otherwise be invalid is sent without sanity checks. To ensure this, it is recommended to do: + +```diff ++await Contract.method.simulate(); +await Contract.method.send().wait(); +``` + +## 0.86.0 + +### [PXE] Removed PXE_L2_STARTING_BLOCK environment variable + +PXE now fast-syncs by skipping finalized blocks and never downloads all blocks, so there is no longer a need to specify a starting block. + +### [Aztec.nr] Logs and messages renaming + +The following renamings have taken place: + +- `encrypted_logs` to `messages`: this module now handles much more than just encrypted logs (including unconstrained message delivery, message encoding, etc.) +- `log_assembly_strategies` to `logs` +- `discovery` moved to `messages`: given that what is discovered are messages +- `default_aes128` removed + +Most contracts barely used these modules, the only frequent imports are the `encode_and_encrypt` functions: + +```diff +- use dep::aztec::messages::logs::note::encode_and_encrypt_note; ++ use dep::aztec::messages::logs::note::encode_and_encrypt_note; +``` + +### [noir-contracts] Reference Noir contracts directory structure change + +`noir-projects/noir-contracts/contracts` directory became too cluttered so we grouped contracts into `account`, `app`, `docs`, `fees`, `libs`, `protocol` and `test` dirs. +If you import contract from the directory make sure to update the paths accordingly. +E.g. for a token contract: + +```diff +#[dependencies] +-token = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.83.0", directory = "noir-projects/noir-contracts/contracts/src/token_contract" } ++token = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.83.0", directory = "noir-projects/noir-contracts/contracts/app/src/token_contract" } +``` + +### [Aztec.nr] #[utility] contract functions + +Aztec contracts have three kinds of functions: `#[private]`, `#[public]` and what was sometimes called 'top-level unconstrained': an unmarked unconstrained function in the contract module. These are now called `[#utility]` functions, and must be explicitly marked as such: + +```diff ++ #[utility] + unconstrained fn balance_of_private(owner: AztecAddress) -> u128 { + storage.balances.at(owner).balance_of() + } +``` + +Utility functions are standalone unconstrained functions that cannot be called from private or public functions: they are meant to be called by _applications_ to perform auxiliary tasks: query contract state (e.g. a token balance), process messages received off-chain, etc. + +All functions in a `contract` block must now be marked as one of either `#[private]`, `#[public]`, `#[utility]`, `#[contract_library_method]`, or `#[test]`. + +Additionally, the `UnconstrainedContext` type has been renamed to `UtilityContext`. This led us to rename the `unkonstrained` method on `TestEnvironment` to `utility`, so any tests using it also need updating: + +```diff +- SharedMutable::new(env.unkonstrained(), storage_slot) ++ SharedMutable::new(env.utility(), storage_slot) +``` + +### [AuthRegistry] function name change + +As part of the broader transition from "top-level unconstrained" to "utility" name (detailed in the note above), the `unconstrained_is_consumable` function in AuthRegistry has been renamed to `utility_is_consumable`. The function's signature and behavior remain unchanged - only the name has been updated to align with the new convention. If you're currently using this function, a simple rename in your code will suffice. + +## 0.83.0 + +### [aztec.js] AztecNode.getPrivateEvents API change + +The `getPrivateEvents` method signature has changed to require an address of a contract that emitted the event and use recipient addresses instead of viewing public keys: + +```diff +- const events = await wallet.getPrivateEvents(TokenContract.events.Transfer, 1, 1, [recipient.getCompleteAddress().publicKeys.masterIncomingViewingPublicKey()]); ++ const events = await wallet.getPrivateEvents(token.address, TokenContract.events.Transfer, 1, 1, [recipient.getAddress()]); +``` + +### [portal contracts] Versions and Non-following message boxes + +The version number is no longer hard-coded to be `1` across all deployments (it not depends on where it is deployed to and with what genesis and logic). +This means that if your portal were hard-coding `1` it will now fail when inserting into the `inbox` or consuming from the `outbox` because of a version mismatch. +Instead you can get the real version (which don't change for a deployment) by reading the `VERSION` on inbox and outbox, or using `getVersion()` on the rollup. + +New Deployments of the protocol do not preserve former state/across each other. +This means that after a new deployment, any "portal" following the registry would try to send messages into this empty rollup to non-existant contracts. +To solve, the portal should be linked to a specific deployment, e.g., a specific inbox. +This can be done by storing the inbox/outbox/version at the time of deployment or initialize and not update them. + +Both of these issues were in the token portal and the uniswap portal, so if you used them as a template it is very likely that you will also have it. + +## 0.82.0 + +### [aztec.js] AztecNode.findLeavesIndexes returns indexes with block metadata + +It's common that we need block metadata of a block in which leaves where inserted when querying indexes of these tree leaves. +For this reason we now return that information along with the indexes. +This allows us to reduce the number of individual AztecNode queries. + +Along this change `findNullifiersIndexesWithBlock` and `findBlockNumbersForIndexes` functions wer removed as all its uses can now be replaced with the newly modified `findLeavesIndexes` function. + +### [aztec.js] AztecNode.getPublicDataTreeWitness renamed as AztecNode.getPublicDataWitness + +This change was done to have consistent naming across codebase. + +### [aztec.js] Wallet interface and Authwit management + +The `Wallet` interface in `aztec.js` is undergoing transformations, trying to be friendlier to wallet builders and reducing the surface of its API. This means `Wallet` no longer extends `PXE`, and instead just implements a subset of the methods of the former. This is NOT going to be its final form, but paves the way towards better interfaces and starts to clarify what the responsibilities of the wallet are: + +```typescript +/** + * The wallet interface. + */ +export type Wallet = AccountInterface & + Pick< + PXE, + // Simulation + | "simulateTx" + | "simulateUnconstrained" + | "profileTx" + // Sending + | "sendTx" + // Contract management (will probably be collapsed in the future to avoid instance and class versions) + | "getContractClassMetadata" + | "getContractMetadata" + | "registerContract" + | "registerContractClass" + // Likely to be removed + | "proveTx" + // Will probably be collapsed + | "getNodeInfo" + | "getPXEInfo" + // Fee info + | "getCurrentBaseFees" + // Still undecided, kept for the time being + | "updateContract" + // Sender management + | "registerSender" + | "getSenders" + | "removeSender" + // Tx status + | "getTxReceipt" + // Events. Kept since events are going to be reworked and changes will come when that's done + | "getPrivateEvents" + | "getPublicEvents" + > & { + createAuthWit(intent: IntentInnerHash | IntentAction): Promise; + }; +``` + +As a side effect, a few debug only features have been removed + +```diff +// Obtain tx effects +const { txHash, debugInfo } = await contract.methods + .set_constant(value) + .send() +-- .wait({ interval: 0.1, debug: true }); +++ .wait({ interval: 0.1 }) + +-- // check that 1 note hash was created +-- expect(debugInfo!.noteHashes.length).toBe(1); +++ const txEffect = await aztecNode.getTxEffect(txHash); +++ const noteHashes = txEffect?.data.noteHashes; +++ // check that 1 note hash was created +++ expect(noteHashes?.length).toBe(1); + +// Wait for a tx to be proven +-- tx.wait({ timeout: 300, interval: 10, proven: true, provenTimeout: 3000 }))); +++ const receipt = await tx.wait({ timeout: 300, interval: 10 }); +++ await waitForProven(aztecNode, receipt, { provenTimeout: 3000 }); +``` + +Authwit management has changed, and PXE no longer stores them. This is unnecessary because now they can be externally provided to simulations and transactions, making sure no stale authorizations are kept inside PXE's db. + +```diff +const witness = await wallet.createAuthWit({ caller, action }); +--await callerWallet.addAuthWitness(witness); +--await action.send().wait(); +++await action.send({ authWitnesses: [witness] }).wait(); +``` + +Another side effect of this is that the interface of the `lookupValidity` method has changed, and now the authwitness has to be provided: + +```diff +const witness = await wallet.createAuthWit({ caller, action }); +--await callerWallet.addAuthWitness(witness); +--await wallet.lookupValidity(wallet.getAddress(), { caller, action }); +++await wallet.lookupValidity(wallet.getAddress(), { caller, action }, witness); +``` + +## 0.80.0 + +### [PXE] Concurrent contract function simulation disabled + +PXE is no longer be able to execute contract functions concurrently (e.g. by collecting calls to `simulateTx` and then using `await Promise.all`). They will instead be put in a job queue and executed sequentially in order of arrival. + +## 0.79.0 + +### [aztec.js] Changes to `BatchCall` and `BaseContractInteraction` + +The constructor arguments of `BatchCall` have been updated to improve usability. Previously, it accepted an array of `FunctionCall`, requiring users to manually set additional data such as `authwit` and `capsules`. Now, `BatchCall` takes an array of `BaseContractInteraction`, which encapsulates all necessary information. + +```diff +class BatchCall extends BaseContractInteraction { +- constructor(wallet: Wallet, protected calls: FunctionCall[]) { ++ constructor(wallet: Wallet, protected calls: BaseContractInteraction[]) { + ... + } +``` + +The `request` method of `BaseContractInteraction` now returns `ExecutionPayload`. This object includes all the necessary data to execute one or more functions. `BatchCall` invokes this method on all interactions to aggregate the required information. It is also used internally in simulations for fee estimation. + +Declaring a `BatchCall`: + +```diff +new BatchCall(wallet, [ +- await token.methods.transfer(alice, amount).request(), +- await token.methods.transfer_to_private(bob, amount).request(), ++ token.methods.transfer(alice, amount), ++ token.methods.transfer_to_private(bob, amount), +]) +``` + +## 0.77.0 + +### [aztec-nr] `TestEnvironment::block_number()` refactored + +The `block_number` function from `TestEnvironment` has been expanded upon with two extra functions, the first being `pending_block_number`, and the second being `committed_block_number`. `pending_block_number` now returns what `block_number` does. In other words, it returns the block number of the block we are currently building. `committed_block_number` returns the block number of the last committed block, i.e. the block number that gets used to execute the private part of transactions when your PXE is successfully synced to the tip of the chain. + +```diff ++ `TestEnvironment::pending_block_number()` ++ `TestEnvironment::committed_block_number()` +``` + +### [aztec-nr] `compute_nullifier_without_context` renamed + +The `compute_nullifier_without_context` function from `NoteHash` (ex `NoteInterface`) is now called `compute_nullifier_unconstrained`, and instead of taking storage slot, contract address and nonce it takes a note hash for nullification (same as `compute_note_hash`). This makes writing this +function simpler: + +```diff +- unconstrained fn compute_nullifier_without_context(self, storage_slot: Field, contract_address: AztecAddress, nonce: Field) -> Field { +- let note_hash_for_nullify = ...; ++ unconstrained fn compute_nullifier_unconstrained(self, note_hash_for_nullify: Field) -> Field { + ... + } +``` + +### `U128` type replaced with native `u128` + +The `U128` type has been replaced with the native `u128` type. This means that you can no longer use the `U128` type in your code. Instead, you should use the `u128` type. +Doing the changes is as straightforward as: + +```diff + #[public] + #[view] +- fn balance_of_public(owner: AztecAddress) -> U128 { ++ fn balance_of_public(owner: AztecAddress) -> u128 { + storage.public_balances.at(owner).read() + } +``` + +`UintNote` has also been updated to use the native `u128` type. + +### [aztec-nr] Removed `compute_note_hash_and_optionally_a_nullifer` + +This function is no longer mandatory for contracts, and the `#[aztec]` macro no longer injects it. + +### [PXE] Removed `addNote` and `addNullifiedNote` + +These functions have been removed from PXE and the base `Wallet` interface. If you need to deliver a note manually because its creation is not being broadcast in an encrypted log, then create an unconstrained contract function to process it and simulate execution of it. The `aztec::discovery::private_logs::do_process_log` function can be used to perform note discovery and add to it to PXE. + +See an example of how to handle a `TransparentNote`: + +```rust + unconstrained fn deliver_transparent_note( + contract_address: AztecAddress, + amount: Field, + secret_hash: Field, + tx_hash: Field, + unique_note_hashes_in_tx: BoundedVec, + first_nullifier_in_tx: Field, + recipient: AztecAddress, + ) { + // do_process_log expects a standard aztec-nr encoded note, which has the following shape: + // [ storage_slot, note_type_id, ...packed_note ] + let note = TransparentNote::new(amount, secret_hash); + let log_plaintext = BoundedVec::from_array(array_concat( + [ + MyContract::storage_layout().my_state_variable.slot, + TransparentNote::get_note_type_id(), + ], + note.pack(), + )); + + do_process_log( + contract_address, + log_plaintext, + tx_hash, + unique_note_hashes_in_tx, + first_nullifier_in_tx, + recipient, + _compute_note_hash_and_nullifier, + ); + } +``` + +The note is then processed by calling this function: + +```typescript +const txEffects = await wallet.getTxEffect(txHash); +await contract.methods + .deliver_transparent_note( + contract.address, + new Fr(amount), + secretHash, + txHash.hash, + toBoundedVec(txEffects!.data.noteHashes, MAX_NOTE_HASHES_PER_TX), + txEffects!.data.nullifiers[0], + wallet.getAddress() + ) + .simulate(); +``` + +### Fee is mandatory + +All transactions must now pay fees. Previously, the default payment method was `NoFeePaymentMethod`; It has been changed to `FeeJuicePaymentMethod`, with the wallet owner as the fee payer. + +For example, the following code will still work: + +``` +await TokenContract.at(address, wallet).methods.transfer(recipient, 100n).send().wait(); +``` + +However, the wallet owner must have enough fee juice to cover the transaction fee. Otherwise, the transaction will be rejected. + +The 3 test accounts deployed in the sandbox are pre-funded with 10 ^ 22 fee juice, allowing them to send transactions right away. + +In addition to the native fee juice, users can pay the transaction fees using tokens that have a corresponding FPC contract. The sandbox now includes `BananaCoin` and `BananaFPC`. Users can use a funded test account to mint banana coin for a new account. The new account can then start sending transactions and pay fees with banana coin. + +```typescript +import { getDeployedTestAccountsWallets } from "@aztec/accounts/testing"; +import { + getDeployedBananaCoinAddress, + getDeployedBananaFPCAddress, +} from "@aztec/aztec"; + +// Fetch the funded test accounts. +const [fundedWallet] = await getDeployedTestAccountsWallets(pxe); + +// Create a new account. +const secret = Fr.random(); +const signingKey = GrumpkinScalar.random(); +const alice = await getSchnorrAccount(pxe, secret, signingKey); +const aliceWallet = await alice.getWallet(); +const aliceAddress = alice.getAddress(); + +// Deploy the new account using the pre-funded test account. +await alice.deploy({ deployWallet: fundedWallet }).wait(); + +// Mint banana coin for the new account. +const bananaCoinAddress = await getDeployedBananaCoinAddress(pxe); +const bananaCoin = await TokenContract.at(bananaCoinAddress, fundedWallet); +const mintAmount = 10n ** 20n; +await bananaCoin.methods + .mint_to_private(fundedWallet.getAddress(), aliceAddress, mintAmount) + .send() + .wait(); + +// Use the new account to send a tx and pay with banana coin. +const transferAmount = 100n; +const bananaFPCAddress = await getDeployedBananaFPCAddress(pxe); +const paymentMethod = new PrivateFeePaymentMethod( + bananaFPCAddress, + aliceWallet +); +const receipt = await bananaCoin + .withWallet(aliceWallet) + .methods.transfer(recipient, transferAmount) + .send({ fee: { paymentMethod } }) + .wait(); +const transactionFee = receipt.transactionFee!; + +// Check the new account's balance. +const aliceBalance = await bananaCoin.methods + .balance_of_private(aliceAddress) + .simulate(); +expect(aliceBalance).toEqual(mintAmount - transferAmount - transactionFee); +``` + +### The tree of protocol contract addresses is now an indexed tree + +This is to allow for non-membership proofs for non-protocol contract addresses. As before, the canonical protocol contract addresses point to the index of the leaf of the 'real' computed protocol address. + +For example, the canonical `DEPLOYER_CONTRACT_ADDRESS` is a constant `= 2`. This is used in the kernels as the `contract_address`. We calculate the `computed_address` (currently `0x1665c5fbc1e58ba19c82f64c0402d29e8bbf94b1fde1a056280d081c15b0dac1`) and check that this value exists in the indexed tree at index `2`. This check already existed and ensures that the call cannot do 'special' protocol contract things unless it is a real protocol contract. + +The new check an indexed tree allows is non-membership of addresses of non protocol contracts. This ensures that if a call is from a protocol contract, it must use the canonical address. For example, before this check a call could be from the deployer contract and use `0x1665c5fbc1e58ba19c82f64c0402d29e8bbf94b1fde1a056280d081c15b0dac1` as the `contract_address`, but be incorrectly treated as a 'normal' call. + +```diff +- let computed_protocol_contract_tree_root = if is_protocol_contract { +- 0 +- } else { +- root_from_sibling_path( +- computed_address.to_field(), +- protocol_contract_index, +- private_call_data.protocol_contract_sibling_path, +- ) +- }; + ++ conditionally_assert_check_membership( ++ computed_address.to_field(), ++ is_protocol_contract, ++ private_call_data.protocol_contract_leaf, ++ private_call_data.protocol_contract_membership_witness, ++ protocol_contract_tree_root, ++ ); +``` + +### [Aztec.nr] Changes to note interfaces and note macros + +In this releases we decided to do a large refactor of notes which resulted in the following changes: + +1. We removed `NoteHeader` and we've introduced a `RetrievedNote` struct that contains a note and the information originally stored in the `NoteHeader`. +2. We removed the `pack_content` and `unpack_content` functions from the `NoteInterface`and made notes implement the standard `Packable` trait. +3. We renamed the `NullifiableNote` trait to `NoteHash` and we've moved the `compute_note_hash` function to this trait from the `NoteInterface` trait. +4. We renamed `NoteInterface` trait as `NoteType` and `get_note_type_id` function as `get_id`. +5. The `#[note]` and `#[partial_note]` macros now generate both the `NoteType` and `NoteHash` traits. +6. `#[custom_note_interface]` macro has been renamed to `#[custom_note]` and it now implements the `NoteInterface` trait. + +This led us to do the following changes to the interfaces: + +```diff +-pub trait NoteInterface { ++pub trait NoteType { + fn get_id() -> Field; +- fn pack_content(self) -> [Field; N]; +- fn unpack_content(fields: [Field; N]) -> Self; +- fn get_header(self) -> NoteHeader; +- fn set_header(&mut self, header: NoteHeader) -> (); +- fn compute_note_hash(self) -> Field; +} + +pub trait NoteHash { ++ fn compute_note_hash(self, storage_slot: Field) -> Field; + + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field; + +- unconstrained fn compute_nullifier_without_context(self) -> Field; ++ unconstrained fn fn compute_nullifier_without_context(self, storage_slot: Field, contract_address: AztecAddress, note_nonce: Field) -> Field; +} +``` + +If you are using `#[note]` or `#[partial_note(...)]` macros you will need to delete the implementations of the `NullifiableNote` (now `NoteHash`) trait as it now gets auto-generated. +Your note will also need to have an `owner` (a note struct field called owner) as its used in the auto-generated nullifier functions. + +If you need a custom implementation of the `NoteHash` interface use the `#[custom_note]` macro. + +If you used `#[note_custom_interface]` macro before you will need to update your notes by using the `#[custom_note]` macro and implementing the `compute_note_hash` function. +If you have no need for a custom implementation of the `compute_note_hash` function copy the default one: + +``` +fn compute_note_hash(self, storage_slot: Field) -> Field { + let inputs = aztec::protocol_types::utils::arrays::array_concat(self.pack(), [storage_slot]); + aztec::protocol_types::hash::poseidon2_hash_with_separator(inputs, aztec::protocol_types::constants::GENERATOR_INDEX__NOTE_HASH) +} +``` + +If you need to keep the custom implementation of the packing functionality, manually implement the `Packable` trait: + +```diff ++ use dep::aztec::protocol_types::traits::Packable; + ++impl Packable for YourNote { ++ fn pack(self) -> [Field; N] { ++ ... ++ } ++ ++ fn unpack(fields: [Field; N]) -> Self { ++ ... ++ } ++} +``` + +If you don't provide a custom implementation of the `Packable` trait, a default one will be generated. + +### [Aztec.nr] Changes to state variables + +Since we've removed `NoteHeader` from notes we no longer need to modify the header in the notes when working with state variables. +This means that we no longer need to be passing a mutable note reference which led to the following changes in the API. + +#### PrivateImmutable + +For `PrivateImmutable` the changes are fairly straightforward. +Instead of passing in a mutable reference `&mut note` just pass in `note`. + +```diff +impl PrivateImmutable { +- pub fn initialize(self, note: &mut Note) -> NoteEmission ++ pub fn initialize(self, note: Note) -> NoteEmission + where + Note: NoteInterface + NullifiableNote, + { + ... + } +} +``` + +#### PrivateSet + +For `PrivateSet` the changes are a bit more involved than the changes in `PrivateImmutable`. +Instead of passing in a mutable reference `&mut note` to the `insert` function just pass in `note`. +The `remove` function now takes in a `RetrievedNote` instead of a `Note` and the `get_notes` function +now returns a vector `RetrievedNote`s instead of a vector `Note`s. +Note getters now generally return `RetrievedNote`s so getting a hold of the `RetrievedNote` for removal should be straightforward. + +```diff +impl PrivateSet +where + Note: NoteInterface + NullifiableNote + Eq, +{ +- pub fn insert(self, note: &mut Note) -> NoteEmission { ++ pub fn insert(self, note: Note) -> NoteEmission { + ... + } + +- pub fn remove(self, note: Note) { ++ pub fn remove(self, retrieved_note: RetrievedNote) { + ... + } + + pub fn get_notes( + self, + options: NoteGetterOptions, +- ) -> BoundedVec { ++ ) -> BoundedVec, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL> { + ... + } +} + +- impl PrivateSet +- where +- Note: NoteInterface + NullifiableNote, +- { +- pub fn insert_from_public(self, note: &mut Note) { +- create_note_hash_from_public(self.context, self.storage_slot, note); +- } +- } +``` + +#### PrivateMutable + +For `PrivateMutable` the changes are similar to the changes in `PrivateImmutable`. + +```diff +impl PrivateMutable +where + Note: NoteInterface + NullifiableNote, +{ +- pub fn initialize(self, note: &mut Note) -> NoteEmission { ++ pub fn initialize(self, note: Note) -> NoteEmission { + ... + } + +- pub fn replace(self, new_note: &mut Note) -> NoteEmission { ++ pub fn replace(self, new_note: Note) -> NoteEmission { + ... + } + +- pub fn initialize_or_replace(self, note: &mut Note) -> NoteEmission { ++ pub fn initialize_or_replace(self, note: Note) -> NoteEmission { + ... + } +} +``` + +## 0.75.0 + +### Changes to `TokenBridge` interface + +`get_token` and `get_portal_address` functions got merged into a single `get_config` function that returns a struct containing both the token and portal addresses. + +### [Aztec.nr] `SharedMutable` can store size of packed length larger than 1 + +`SharedMutable` has been modified such that now it can store type `T` which packs to a length larger than 1. +This is a breaking change because now `SharedMutable` requires `T` to implement `Packable` trait instead of `ToField` and `FromField` traits. + +To implement the `Packable` trait for your type you can use the derive macro: + +```diff ++ use std::meta::derive; + ++ #[derive(Packable)] +pub struct YourType { + ... +} +``` + +### [Aztec.nr] Introduction of `WithHash` + +`WithHash` is a struct that allows for efficient reading of value `T` from public storage in private. +This is achieved by storing the value with its hash, then obtaining the values via an oracle and verifying them against the hash. +This results in in a fewer tree inclusion proofs for values `T` that are packed into more than a single field. + +`WithHash` is leveraged by state variables like `PublicImmutable`. +This is a breaking change because now we require values stored in `PublicImmutable` and `SharedMutable` to implement the `Eq` trait. + +To implement the `Eq` trait you can use the `#[derive(Eq)]` macro: + +```diff ++ use std::meta::derive; + ++ #[derive(Eq)] +pub struct YourType { + ... +} +``` + +## 0.73.0 + +### [Token, FPC] Moving fee-related complexity from the Token to the FPC + +There was a complexity leak of fee-related functionality in the token contract. +We've came up with a way how to achieve the same objective with the general functionality of the Token contract. +This lead to the removal of `setup_refund` and `complete_refund` functions from the Token contract and addition of `complete_refund` function to the FPC. + +### [Aztec.nr] Improved storage slot allocation + +State variables are no longer assumed to be generic over a type that implements the `Serialize` trait: instead, they must implement the `Storage` trait with an `N` value equal to the number of slots they need to reserve. + +For the vast majority of state variables, this simply means binding the serialization length to this trait: + +```diff ++ impl Storage for MyStateVar where T: Serialize { }; +``` + +### [Aztec.nr] Introduction of `Packable` trait + +We have introduced a `Packable` trait that allows types to be serialized and deserialized with a focus on minimizing the size of the resulting Field array. +This is in contrast to the `Serialize` and `Deserialize` traits, which follows Noir's intrinsic serialization format. +This is a breaking change because we now require `Packable` trait implementation for any type that is to be stored in contract storage. + +Example implementation of Packable trait for `U128` type from `noir::std`: + +``` +use crate::traits::{Packable, ToField}; + +let U128_PACKED_LEN: u32 = 1; + +impl Packable for U128 { + fn pack(self) -> [Field; U128_PACKED_LEN] { + [self.to_field()] + } + + fn unpack(fields: [Field; U128_PACKED_LEN]) -> Self { + U128::from_integer(fields[0]) + } +} +``` + +### Logs for notes, partial notes, and events have been refactored. + +We're preparing to make log assembly more customisable. These paths have changed. + +```diff +- use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note, ++ use dep::aztec::messages::logs::note::encode_and_encrypt_note, +``` + +And similar paths for `encode_and_encrypt_note_unconstrained`, and for events and partial notes. + +The way in which logs are assembled in this "default_aes128" strategy is has also changed. I repeat: **Encrypted log layouts have changed**. The corresponding typescript for note discovery has also been changed, but if you've rolled your own functions for parsing and decrypting logs, those will be broken by this change. + +### `NoteInferface` and `EventInterface` no-longer have a `to_be_bytes` method. + +You can remove this method from any custom notes or events that you've implemented. + +### [Aztec.nr] Packing notes resulting in changes in `NoteInterface` + +Note interface implementation generated by our macros now packs note content instead of serializing it +With this change notes are being less costly DA-wise to emit when some of the note struct members implements the `Packable` trait (this is typically the `UintNote` which represents `value` as `U128` that gets serialized as 2 fields but packed as 1). +This results in the following changes in the `NoteInterface`: + +```diff +pub trait NoteInterface { +- fn serialize_content(self) -> [Field; N]; ++ fn pack_content(self) -> [Field; N]; + +- fn deserialize_content(fields: [Field; N]) -> Self; ++ fn unpack_content(fields: [Field; N]) -> Self; + + fn get_header(self) -> NoteHeader; + fn set_header(&mut self, header: NoteHeader) -> (); + fn get_note_type_id() -> Field; + fn compute_note_hash(self) -> Field; +} +``` + +### [PXE] Cleanup of Contract and ContractClass information getters + +```diff +- pxe.isContractInitialized +- pxe.getContractInstance +- pxe.isContractPubliclyDeployed ++ pxe.getContractMetadata +``` + +have been merged into getContractMetadata + +```diff +- pxe.getContractClass +- pxe.isContractClassPubliclyRegistered +- pxe.getContractArtifact ++ pxe.getContractClassMetadata +``` + +These functions have been merged into `pxe.getContractMetadata` and `pxe.getContractClassMetadata`. + +## 0.72.0 + +### Some functions in `aztec.js` and `@aztec/accounts` are now async + +In our efforts to make libraries more browser-friendly and providing with more bundling options for `bb.js` (like a non top-level-await version), some functions are being made async, in particular those that access our cryptographic functions. + +```diff +- AztecAddress.random(); ++ await AztecAddress.random(); + +- getSchnorrAccount(); ++ await getSchnorrAccount(); +``` + +### Public logs replace unencrypted logs + +Any log emitted from public is now known as a public log, rather than an unencrypted log. This means methods relating to these logs have been renamed e.g. in the pxe, archiver, txe: + +```diff +- getUnencryptedLogs(filter: LogFilter): Promise +- getUnencryptedEvents(eventMetadata: EventMetadataDefinition, from: number, limit: number): Promise ++ getPublicLogs(filter: LogFilter): Promise ++ getPublicEvents(eventMetadata: EventMetadataDefinition, from: number, limit: number): Promise +``` + +The context method in aztec.nr is now: + +```diff +- context.emit_unencrypted_log(log) ++ context.emit_public_log(log) +``` + +These logs were treated as bytes in the node and as hashes in the protocol circuits. Now, public logs are treated as fields everywhere: + +```diff +- unencryptedLogs: UnencryptedTxL2Logs +- unencrypted_logs_hashes: [ScopedLogHash; MAX_UNENCRYPTED_LOGS_PER_TX] ++ publicLogs: PublicLog[] ++ public_logs: [PublicLog; MAX_PUBLIC_LOGS_PER_TX] +``` + +A `PublicLog` contains the log (as an array of fields) and the app address. + +This PR also renamed encrypted events to private events: + +```diff +- getEncryptedEvents(eventMetadata: EventMetadataDefinition, from: number, limit: number, vpks: Point[]): Promise ++ getPrivateEvents(eventMetadata: EventMetadataDefinition, from: number, limit: number, vpks: Point[]): Promise +``` + +## 0.70.0 + +### [Aztec.nr] Removal of `getSiblingPath` oracle + +Use `getMembershipWitness` oracle instead that returns both the sibling path and index. + +## 0.68.0 + +### [archiver, node, pxe] Remove contract artifacts in node and archiver and store function names instead + +Contract artifacts were only in the archiver for debugging purposes. Instead function names are now (optionally) emitted +when registering contract classes + +Function changes in the Node interface and Contract Data source interface: + +```diff +- addContractArtifact(address: AztecAddress, artifact: ContractArtifact): Promise; ++ registerContractFunctionNames(address: AztecAddress, names: Record): Promise; +``` + +So now the PXE registers this when calling `registerContract()` + +``` +await this.node.registerContractFunctionNames(instance.address, functionNames); +``` + +Function changes in the Archiver + +```diff +- addContractArtifact(address: AztecAddress, artifact: ContractArtifact) +- getContractArtifact(address: AztecAddress) ++ registerContractFunctionNames(address: AztecAddress, names: Record): Promise +``` + +### [fees, fpc] Changes in setting up FPC as fee payer on AztecJS and method names in FPC + +On AztecJS, setting up `PrivateFeePaymentMethod` and `PublicFeePaymentMethod` are now the same. The don't need to specify a sequencer address or which coin to pay in. The coins are set up in the FPC contract! + +```diff +- paymentMethod: new PrivateFeePaymentMethod(bananaCoin.address,bananaFPC.address,aliceWallet,sequencerAddress), ++ paymentMethod: new PrivateFeePaymentMethod(bananaFPC.address, aliceWallet), + +- paymentMethod: new PublicFeePaymentMethod(bananaCoin.address, bananaFPC.address, aliceWallet), ++ paymentMethod: new PublicFeePaymentMethod(bananaFPC.address, aliceWallet), +``` + +Changes in `FeePaymentMethod` class in AztecJS + +```diff +- getAsset(): AztecAddress; ++ getAsset(): Promise; +``` + +Changes in the token contract: +FPC specific methods, `setup_refund()` and `complete_refund()` have minor args rename. + +Changes in FPC contract: +Rename of args in all of FPC functions as FPC now stores the accepted token address and admin and making it clearer the amounts are corresponding to the accepted token and not fee juice. +Also created a public function `pull_funds()` for admin to clawback any money in the FPC + +Expect more changes in FPC in the coming releases! + +### Name change from `contact` to `sender` in PXE API + +`contact` has been deemed confusing because the name is too similar to `contract`. +For this reason we've decided to rename it: + +```diff +- await pxe.registerContact(address); ++ await pxe.registerSender(address); +- await pxe.getContacts(); ++ await pxe.getSenders(); +- await pxe.removeContact(address); ++ await pxe.removeSender(address); +``` + +## 0.67.1 + +### Noir contracts package no longer exposes artifacts as default export + +To reduce loading times, the package `@aztec/noir-contracts.js` no longer exposes all artifacts as its default export. Instead, it exposes a `ContractNames` variable with the list of all contract names available. To import a given artifact, use the corresponding export, such as `@aztec/noir-contracts.js/FPC`. + +### Blobs + +We now publish the majority of DA in L1 blobs rather than calldata, with only contract class logs remaining as calldata. This replaces all code that touched the `txsEffectsHash`. +In the rollup circuits, instead of hashing each child circuit's `txsEffectsHash` to form a tree, we track tx effects by absorbing them into a sponge for blob data (hence the name: `spongeBlob`). This sponge is treated like the state trees in that we check each rollup circuit 'follows' the next: + +```diff +- let txs_effects_hash = sha256_to_field(left.txs_effects_hash, right.txs_effects_hash); ++ assert(left.end_sponge_blob.eq(right.start_sponge_blob)); ++ let start_sponge_blob = left.start_sponge_blob; ++ let end_sponge_blob = right.end_sponge_blob; +``` + +This sponge is used in the block root circuit to confirm that an injected array of all `txEffects` does match those rolled up so far in the `spongeBlob`. Then, the `txEffects` array is used to construct and prove opening of the polynomial representing the blob commitment on L1 (this is done efficiently thanks to the Barycentric formula). +On L1, we publish the array as a blob and verify the above proof of opening. This confirms that the tx effects in the rollup circuit match the data in the blob: + +```diff +- bytes32 txsEffectsHash = TxsDecoder.decode(_body); ++ bytes32 blobHash = _validateBlob(blobInput); +``` + +Where `blobInput` contains the proof of opening and evaluation calculated in the block root rollup circuit. It is then stored and used as a public input to verifying the epoch proof. + +## 0.67.0 + +### L2 Gas limit of 6M enforced for public portion of TX + +A 12M limit was previously enforced per-enqueued-public-call. The protocol now enforces a stricter limit that the entire public portion of a transaction consumes at most 6,000,000 L2 gas. + +### [aztec.nr] Renamed `Header` and associated helpers + +The `Header` struct has been renamed to `BlockHeader`, and the `get_header()` family of functions have been similarly renamed to `get_block_header()`. + +```diff +- let header = context.get_header_at(block_number); ++ let header = context.get_block_header_at(block_number); +``` + +### Outgoing Events removed + +Previously, every event which was emitted included: + +- Incoming Header (to convey the app contract address to the recipient) +- Incoming Ciphertext (to convey the note contents to the recipient) +- Outgoing Header (served as a backup, to convey the app contract address to the "outgoing viewer" - most likely the sender) +- Outgoing Ciphertext (served as a backup, encrypting the symmetric key of the incoming ciphertext to the "outgoing viewer" - most likely the sender) + +The latter two have been removed from the `.emit()` functions, so now only an Incoming Header and Incoming Ciphertext will be emitted. + +The interface for emitting a note has therefore changed, slightly. No more ovpk's need to be derived and passed into `.emit()` functions. + +```diff +- nfts.at(to).insert(&mut new_note).emit(encode_and_encrypt_note(&mut context, from_ovpk_m, to, from)); ++ nfts.at(to).insert(&mut new_note).emit(encode_and_encrypt_note(&mut context, to, from)); +``` + +The `getOutgoingNotes` function is removed from the PXE interface. + +Some aztec.nr library methods' arguments are simplified to remove an `outgoing_viewer` parameter. E.g. `ValueNote::increment`, `ValueNote::decrement`, `ValueNote::decrement_by_at_most`, `EasyPrivateUint::add`, `EasyPrivateUint::sub`. + +Further changes are planned, so that: + +- Outgoing ciphertexts (or any kind of abstract ciphertext) can be emitted by a contract, and on the other side discovered and then processed by the contract. +- Headers will be removed, due to the new tagging scheme. + +## 0.66 + +### DEBUG env var is removed + +The `DEBUG` variable is no longer used. Use `LOG_LEVEL` with one of `silent`, `fatal`, `error`, `warn`, `info`, `verbose`, `debug`, or `trace`. To tweak log levels per module, add a list of module prefixes with their overridden level. For example, LOG_LEVEL="info; verbose: aztec:sequencer, aztec:archiver; debug: aztec:kv-store" sets `info` as the default log level, `verbose` for the sequencer and archiver, and `debug` for the kv-store. Module name match is done by prefix. + +### `tty` resolve fallback required for browser bundling + +When bundling `aztec.js` for web, the `tty` package now needs to be specified as an empty fallback: + +```diff +resolve: { + plugins: [new ResolveTypeScriptPlugin()], + alias: { './node/index.js': false }, + fallback: { + crypto: false, + os: false, + fs: false, + path: false, + url: false, ++ tty: false, + worker_threads: false, + buffer: require.resolve('buffer/'), + util: require.resolve('util/'), + stream: require.resolve('stream-browserify'), + }, +}, +``` + +## 0.65 + +### [aztec.nr] Removed SharedImmutable + +The `SharedImmutable` state variable has been removed, since it was essentially the exact same as `PublicImmutable`, which now contains functions for reading from private: + +```diff +- foo: SharedImmutable. ++ foo: PublicImmutable. +``` + +### [aztec.nr] SharedImmutable renamings + +`SharedImmutable::read_private` and `SharedImmutable::read_public` were renamed to simply `read`, since only one of these versions is ever available depending on the current context. + +```diff +// In private +- let value = storage.my_var.read_private(); ++ let value = storage.my_var.read(); + +// In public +- let value = storage.my_var.read_public(); ++ let value = storage.my_var.read(); +``` + +### [aztec.nr] SharedMutable renamings + +`SharedMutable` getters (`get_current_value_in_public`, etc.) were renamed by dropping the `_in` suffix, since only one of these versions is ever available depending on the current context. + +```diff +// In private +- let value = storage.my_var.get_current_value_in_private(); ++ let value = storage.my_var.get_current_value(); + +// In public +- let value = storage.my_var.get_current_value_in_public(); ++ let value = storage.my_var.get_current_value(); +``` + +### [aztec.js] Random addresses are now valid + +The `AztecAddress.random()` function now returns valid addresses, i.e. addresses that can receive encrypted messages and therefore have notes be sent to them. `AztecAddress.isValid()` was also added to check for validity of an address. + +## 0.63.0 + +### [PXE] Note tagging and discovery + +PXE's trial decryption of notes has been replaced in favor of a tagging and discovery approach. It is much more efficient and should scale a lot better as the network size increases, since +notes can now be discovered on-demand. For the time being, this means that accounts residing _on different PXE instances_ should add senders to their contact list, so notes can be discovered +(accounts created on the same PXE instance will be added as senders for each other by default) + +```diff ++pxe.registerContact(senderAddress) +``` + +The note discovery process is triggered automatically whenever a contract invokes the `get_notes` oracle, meaning no contract changes are expected. Just in case, every contract has now a utility method +`sync_notes` that can trigger the process manually if necessary. This can be useful since now the `DebugInfo` object that can be obtained when sending a tx with the `debug` flag set to true +no longer contains the notes that were generated in the transaction: + +```diff +const receipt = await inclusionsProofsContract.methods.create_note(owner, 5n).send().wait({ debug: true }); +-const { visibleIncomingNotes } = receipt.debugInfo!; +-expect(visibleIncomingNotes.length).toEqual(1); ++await inclusionsProofsContract.methods.sync_notes().simulate(); ++const incomingNotes = await wallet.getIncomingNotes({ txHash: receipt.txHash }); ++expect(incomingNotes.length).toEqual(1); +``` + +### [Token contract] Partial notes related refactor + +We've decided to replace the old "shield" flow with one leveraging partial notes. +This led to a removal of `shield` and `redeem_shield` functions and an introduction of `transfer_to_private`. +An advantage of the new approach is that only 1 tx is required and the API of partial notes is generally nicer. +For more information on partial notes refer to docs. + +### [Token contract] Function naming changes + +There have been a few naming changes done for improved consistency. +These are the renamings: +`transfer_public` --> `transfer_in_public` +`transfer_from` --> `transfer_in_private` +`mint_public` --> `mint_to_public` +`burn` --> `burn_private` + +## 0.62.0 + +### [TXE] Single execution environment + +Thanks to recent advancements in Brillig TXE performs every single call as if it was a nested call, spawning a new ACVM or AVM simulator without performance loss. +This ensures every single test runs in a consistent environment and allows for clearer test syntax: + +```diff +-let my_call_interface = MyContract::at(address).my_function(args); +-env.call_private(my_contract_interface) ++MyContract::at(address).my_function(args).call(&mut env.private()); +``` + +This implies every contract has to be deployed before it can be tested (via `env.deploy` or `env.deploy_self`) and of course it has to be recompiled if its code was changed before TXE can use the modified bytecode. + +### Uniqueness of L1 to L2 messages + +L1 to L2 messages have been updated to guarantee their uniqueness. This means that the hash of an L1 to L2 message cannot be precomputed, and must be obtained from the `MessageSent` event emitted by the `Inbox` contract, found in the L1 transaction receipt that inserted the message: + +```solidity +event MessageSent(uint256 indexed l2BlockNumber, uint256 index, bytes32 indexed hash); +``` + +This event now also includes an `index`. This index was previously required to consume an L1 to L2 message in a public function, and now it is also required for doing so in a private function, since it is part of the message hash preimage. The `PrivateContext` in aztec-nr has been updated to reflect this: + +```diff +pub fn consume_l1_to_l2_message( + &mut self, + content: Field, + secret: Field, + sender: EthAddress, ++ leaf_index: Field, +) { +``` + +This change has also modified the internal structure of the archiver database, making it incompatible with previous ones. Last, the API for obtaining an L1 to L2 message membership witness has been simplified to leverage message uniqueness: + +```diff +getL1ToL2MessageMembershipWitness( + blockNumber: L2BlockNumber, + l1ToL2Message: Fr, +- startIndex: bigint, +): Promise<[bigint, SiblingPath] | undefined>; +``` + +### Address is now a point + +The address now serves as someone's public key to encrypt incoming notes. An address point has a corresponding address secret, which is used to decrypt the notes encrypted with the address point. + +### Notes no longer store a hash of the nullifier public keys, and now store addresses + +Because of removing key rotation, we can now store addresses as the owner of a note. Because of this and the above change, we can and have removed the process of registering a recipient, because now we do not need any keys of the recipient. + +example_note.nr + +```diff +-npk_m_hash: Field ++owner: AztecAddress +``` + +PXE Interface + +```diff +-registerRecipient(completeAddress: CompleteAddress) +``` + +## 0.58.0 + +### [l1-contracts] Inbox's MessageSent event emits global tree index + +Earlier `MessageSent` event in Inbox emitted a subtree index (index of the message in the subtree of the l2Block). But the nodes and Aztec.nr expects the index in the global L1_TO_L2_MESSAGES_TREE. So to make it easier to parse this, Inbox now emits this global index. + +## 0.57.0 + +### Changes to PXE API and `ContractFunctionInteraction`` + +PXE APIs have been refactored to better reflect the lifecycle of a Tx (`execute private -> simulate kernels -> simulate public (estimate gas) -> prove -> send`) + +- `.simulateTx`: Now returns a `TxSimulationResult`, containing the output of private execution, kernel simulation and public simulation (optional). +- `.proveTx`: Now accepts the result of executing the private part of a transaction, so simulation doesn't have to happen again. + +Thanks to this refactor, `ContractFunctionInteraction` has been updated to remove its internal cache and avoid bugs due to its mutable nature. As a result our type-safe interfaces now have to be used as follows: + +```diff +-const action = MyContract.at(address).method(args); +-await action.prove(); +-await action.send().wait(); ++const action = MyContract.at(address).method(args); ++const provenTx = await action.prove(); ++await provenTx.send().wait(); +``` + +It's still possible to use `.send()` as before, which will perform proving under the hood. + +More changes are coming to these APIs to better support gas estimation mechanisms and advanced features. + +### Changes to public calling convention + +Contracts that include public functions (that is, marked with `#[public]`), are required to have a function `public_dispatch(selector: Field)` which acts as an entry point. This will be soon the only public function registered/deployed in contracts. The calling convention is updated so that external calls are made to this function. + +If you are writing your contracts using Aztec-nr, there is nothing you need to change. The `public_dispatch` function is automatically generated by the `#[aztec]` macro. + +### [Aztec.nr] Renamed `unsafe_rand` to `random` + +Since this is an `unconstrained` function, callers are already supposed to include an `unsafe` block, so this function has been renamed for reduced verbosity. + +```diff +-use aztec::oracle::unsafe_rand::unsafe_rand; ++use aztec::oracle::random::random; + +-let random_value = unsafe { unsafe_rand() }; ++let random_value = unsafe { random() }; +``` + +### [Aztec.js] Removed `L2Block.fromFields` + +`L2Block.fromFields` was a syntactic sugar which is causing [issues](https://github.com/AztecProtocol/aztec-packages/issues/8340) so we've removed it. + +```diff +-const l2Block = L2Block.fromFields({ header, archive, body }); ++const l2Block = new L2Block(archive, header, body); +``` + +### [Aztec.nr] Removed `SharedMutablePrivateGetter` + +This state variable was deleted due to it being difficult to use safely. + +### [Aztec.nr] Changes to `NullifiableNote` + +The `compute_nullifier_without_context` function is now `unconstrained`. It had always been meant to be called in unconstrained contexts (which is why it did not receive the `context` object), but now that Noir supports trait functions being `unconstrained` this can be implemented properly. Users must add the `unconstrained` keyword to their implementations of the trait: + +```diff +impl NullifiableNote for MyCustomNote { +- fn compute_nullifier_without_context(self) -> Field { ++ unconstrained fn compute_nullifier_without_context(self) -> Field { +``` + +### [Aztec.nr] Make `TestEnvironment` unconstrained + +All of `TestEnvironment`'s functions are now `unconstrained`, preventing accidentally calling them in a constrained circuit, among other kinds of user error. Becuase they work with mutable references, and these are not allowed to cross the constrained/unconstrained barrier, tests that use `TestEnvironment` must also become `unconstrained`. The recommended practice is to make _all_ Noir tests and test helper functions be `unconstrained: + +```diff +#[test] +-fn test_my_function() { ++unconstrained fn test_my_function() { + let env = TestEnvironment::new(); +``` + +### [Aztec.nr] removed `encode_and_encrypt_note` and renamed `encode_and_encrypt_note_with_keys` to `encode_and_encrypt_note` + +```diff +contract XYZ { +- use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys; ++ use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note; +... + +- numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m, owner)); ++ numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note(&mut context, owner_ovpk_m, owner_ivpk_m, owner)); +} +``` + +## 0.56.0 + +### [Aztec.nr] Changes to contract definition + +We've migrated the Aztec macros to use the newly introduce meta programming Noir feature. Due to being Noir-based, the new macros are less obscure and can be more easily modified. + +As part of this transition, some changes need to be applied to Aztec contracts: + +- The top level `contract` block needs to have the `#[aztec]` macro applied to it. +- All `#[aztec(name)]` macros are renamed to `#[name]`. +- The storage struct (the one that gets the `#[storage]` macro applied) but be generic over a `Context` type, and all state variables receive this type as their last generic type parameter. + +```diff ++ use dep::aztec::macros::aztec; + +#[aztec] +contract Token { ++ use dep::aztec::macros::{storage::storage, events::event, functions::{initializer, private, view, public}}; + +- #[aztec(storage)] +- struct Storage { ++ #[storage] ++ struct Storage { +- admin: PublicMutable, ++ admin: PublicMutable, +- minters: Map>, ++ minters: Map, Context>, + } + +- #[aztec(public)] +- #[aztec(initializer)] ++ #[public] ++ #[initializer] + fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) { + ... + } + +- #[aztec(public)] +- #[aztec(view)] +- fn public_get_name() -> FieldCompressedString { ++ #[public] ++ #[view] + fn public_get_name() -> FieldCompressedString { + ... + } +``` + +### [Aztec.nr] Changes to `NoteInterface` + +The new macro model prevents partial trait auto-implementation: they either implement the entire trait or none of it. This means users can no longer implement part of `NoteInterface` and have the rest be auto-implemented. + +For this reason we've separated the methods which are auto-implemented and those which needs to be implemented manually into two separate traits: the auto-implemented ones stay in the `NoteInterface` trace and the manually implemented ones were moved to `NullifiableNote` (name likely to change): + +```diff +-#[aztec(note)] ++#[note] +struct AddressNote { + ... +} + +-impl NoteInterface for AddressNote { ++impl NullifiableNote for AddressNote { + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { + ... + } + + fn compute_nullifier_without_context(self) -> Field { + ... + } +} +``` + +### [Aztec.nr] Changes to contract interface + +The `Contract::storage()` static method has been renamed to `Contract::storage_layout()`. + +```diff +- let fee_payer_balances_slot = derive_storage_slot_in_map(Token::storage().balances.slot, fee_payer); +- let user_balances_slot = derive_storage_slot_in_map(Token::storage().balances.slot, user); ++ let fee_payer_balances_slot = derive_storage_slot_in_map(Token::storage_layout().balances.slot, fee_payer); ++ let user_balances_slot = derive_storage_slot_in_map(Token::storage_layout().balances.slot, user); +``` + +### Key rotation removed + +The ability to rotate incoming, outgoing, nullifying and tagging keys has been removed - this feature was easy to misuse and not worth the complexity and gate count cost. As part of this, the Key Registry contract has also been deleted. The API for fetching public keys has been adjusted accordingly: + +```diff +- let keys = get_current_public_keys(&mut context, account); ++ let keys = get_public_keys(account); +``` + +### [Aztec.nr] Rework `NoteGetterOptions::select` + +The `select` function in both `NoteGetterOptions` and `NoteViewerOptions` no longer takes an `Option` of a comparator, but instead requires an explicit comparator to be passed. Additionally, the order of the parameters has been changed so that they are `(lhs, operator, rhs)`. These two changes should make invocations of the function easier to read: + +```diff +- options.select(ValueNote::properties().value, amount, Option::none()) ++ options.select(ValueNote::properties().value, Comparator.EQ, amount) +``` + +## 0.53.0 + +### [Aztec.nr] Remove `OwnedNote` and create `UintNote` + +`OwnedNote` allowed having a U128 `value` in the custom note while `ValueNote` restricted to just a Field. + +We have removed `OwnedNote` but are introducing a more genric `UintNote` within aztec.nr + +``` +#[aztec(note)] +struct UintNote { + // The integer stored by the note + value: U128, + // The nullifying public key hash is used with the nsk_app to ensure that the note can be privately spent. + npk_m_hash: Field, + // Randomness of the note to hide its contents + randomness: Field, +} +``` + +### [TXE] logging + +You can now use `debug_log()` within your contract to print logs when using the TXE + +Remember to set the following environment variables to activate debug logging: + +```bash +export DEBUG="aztec:*" +export LOG_LEVEL="debug" +``` + +### [Account] no assert in is_valid_impl + +`is_valid_impl` method in account contract asserted if signature was true. Instead now we will return the verification to give flexibility to developers to handle it as they please. + +```diff +- let verification = std::ecdsa_secp256k1::verify_signature(public_key.x, public_key.y, signature, hashed_message); +- assert(verification == true); +- true ++ std::ecdsa_secp256k1::verify_signature(public_key.x, public_key.y, signature, hashed_message) +``` + +## 0.49.0 + +### Key Rotation API overhaul + +Public keys (ivpk, ovpk, npk, tpk) should no longer be fetched using the old `get_[x]pk_m` methods on the `Header` struct, but rather by calling `get_current_public_keys`, which returns a `PublicKeys` struct with all four keys at once: + +```diff ++use dep::aztec::keys::getters::get_current_public_keys; + +-let header = context.header(); +-let owner_ivpk_m = header.get_ivpk_m(&mut context, owner); +-let owner_ovpk_m = header.get_ovpk_m(&mut context, owner); ++let owner_keys = get_current_public_keys(&mut context, owner); ++let owner_ivpk_m = owner_keys.ivpk_m; ++let owner_ovpk_m = owner_keys.ovpk_m; +``` + +If using more than one key per account, this will result in very large circuit gate count reductions. + +Additionally, `get_historical_public_keys` was added to support reading historical keys using a historical header: + +```diff ++use dep::aztec::keys::getters::get_historical_public_keys; + +let historical_header = context.header_at(some_block_number); +-let owner_ivpk_m = header.get_ivpk_m(&mut context, owner); +-let owner_ovpk_m = header.get_ovpk_m(&mut context, owner); ++let owner_keys = get_historical_public_keys(historical_header, owner); ++let owner_ivpk_m = owner_keys.ivpk_m; ++let owner_ovpk_m = owner_keys.ovpk_m; +``` + +## 0.48.0 + +### NoteInterface changes + +`compute_note_hash_and_nullifier*` functions were renamed as `compute_nullifier*` and the `compute_nullifier` function now takes `note_hash_for_nullify` as an argument (this allowed us to reduce gate counts and the hash was typically computed before). Also `compute_note_hash_for_consumption` function was renamed as `compute_note_hash_for_nullify`. + +```diff +impl NoteInterface for ValueNote { +- fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { +- let note_hash_for_nullify = compute_note_hash_for_consumption(self); +- let secret = context.request_nsk_app(self.npk_m_hash); +- let nullifier = poseidon2_hash_with_separator([ +- note_hash_for_nullify, +- secret, +- ], +- GENERATOR_INDEX__NOTE_NULLIFIER as Field, +- ); +- (note_hash_for_nullify, nullifier) +- } +- fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { +- let note_hash_for_nullify = compute_note_hash_for_consumption(self); +- let secret = get_nsk_app(self.npk_m_hash); +- let nullifier = poseidon2_hash_with_separator([ +- note_hash_for_nullify, +- secret, +- ], +- GENERATOR_INDEX__NOTE_NULLIFIER as Field, +- ); +- (note_hash_for_nullify, nullifier) +- } + ++ fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { ++ let secret = context.request_nsk_app(self.npk_m_hash); ++ poseidon2_hash_with_separator([ ++ note_hash_for_nullify, ++ secret ++ ], ++ GENERATOR_INDEX__NOTE_NULLIFIER as Field, ++ ) ++ } ++ fn compute_nullifier_without_context(self) -> Field { ++ let note_hash_for_nullify = compute_note_hash_for_nullify(self); ++ let secret = get_nsk_app(self.npk_m_hash); ++ poseidon2_hash_with_separator([ ++ note_hash_for_nullify, ++ secret, ++ ], ++ GENERATOR_INDEX__NOTE_NULLIFIER as Field, ++ ) ++ } +} +``` + +### Fee Juice rename + +The name of the canonical Gas contract has changed to Fee Juice. Update noir code: + +```diff +-GasToken::at(contract_address) ++FeeJuice::at(contract_address) +``` + +Additionally, `NativePaymentMethod` and `NativePaymentMethodWithClaim` have been renamed to `FeeJuicePaymentMethod` and `FeeJuicePaymentMethodWithClaim`. + +### PrivateSet::pop_notes(...) + +The most common flow when working with notes is obtaining them from a `PrivateSet` via `get_notes(...)` and then removing them via `PrivateSet::remove(...)`. +This is cumbersome and it results in unnecessary constraints due to a redundant note read request checks in the remove function. + +For this reason we've implemented `pop_notes(...)` which gets the notes, removes them from the set and returns them. +This tight coupling of getting notes and removing them allowed us to safely remove the redundant read request check. + +Token contract diff: + +```diff +-let options = NoteGetterOptions::with_filter(filter_notes_min_sum, target_amount).set_limit(max_notes); +-let notes = self.map.at(owner).get_notes(options); +-let mut subtracted = U128::from_integer(0); +-for i in 0..options.limit { +- if i < notes.len() { +- let note = notes.get_unchecked(i); +- self.map.at(owner).remove(note); +- subtracted = subtracted + note.get_amount(); +- } +-} +-assert(minuend >= subtrahend, "Balance too low"); ++let options = NoteGetterOptions::with_filter(filter_notes_min_sum, target_amount).set_limit(max_notes); ++let notes = self.map.at(owner).pop_notes(options); ++let mut subtracted = U128::from_integer(0); ++for i in 0..options.limit { ++ if i < notes.len() { ++ let note = notes.get_unchecked(i); ++ subtracted = subtracted + note.get_amount(); ++ } ++} ++assert(minuend >= subtrahend, "Balance too low"); +``` + +Note that `pop_notes` may not have obtained and removed any notes! The caller must place checks on the returned notes, e.g. in the example above by checking a sum of balances, or by checking the number of returned notes (`assert_eq(notes.len(), expected_num_notes)`). + +## 0.47.0 + +# [Aztec sandbox] TXE deployment changes + +The way simulated deployments are done in TXE tests has changed to avoid relying on TS interfaces. It is now possible to do it by directly pointing to a Noir standalone contract or workspace: + +```diff +-let deployer = env.deploy("path_to_contract_ts_interface"); ++let deployer = env.deploy("path_to_contract_root_folder_where_nargo_toml_is", "ContractName"); +``` + +Extended syntax for more use cases: + +```rust +// The contract we're testing +env.deploy_self("ContractName"); // We have to provide ContractName since nargo it's ready to support multi-contract files + +// A contract in a workspace +env.deploy("../path/to/workspace@package_name", "ContractName"); // This format allows locating the artifact in the root workspace target folder, regardless of internal code organization +``` + +The deploy function returns a `Deployer`, which requires performing a subsequent call to `without_initializer()`, `with_private_initializer()` or `with_public_initializer()` just like before in order to **actually** deploy the contract. + +### [CLI] Command refactor and unification + `aztec test` + +Sandbox commands have been cleaned up and simplified. Doing `aztec-up` now gets you the following top-level commands: + +`aztec`: All the previous commands + all the CLI ones without having to prefix them with cli. Run `aztec` for help! +`aztec-nargo`: No changes + +**REMOVED/RENAMED**: + +- `aztec-sandbox` and `aztec sandbox`: now `aztec start --sandbox` +- `aztec-builder`: now `aztec codegen` and `aztec update` + +**ADDED**: + +- `aztec test [options]`: runs `aztec start --txe && aztec-nargo test --oracle-resolver http://aztec:8081 --silence-warnings [options]` via docker-compose allowing users to easily run contract tests using TXE + +## 0.45.0 + +### [Aztec.nr] Remove unencrypted logs from private + +They leak privacy so is a footgun! + +## 0.44.0 + +### [Aztec.nr] Autogenerate Serialize methods for events + +```diff +#[aztec(event)] +struct WithdrawalProcessed { + who: Field, + amount: Field, +} + +-impl Serialize<2> for WithdrawalProcessed { +- fn serialize(self: Self) -> [Field; 2] { +- [self.who.to_field(), self.amount as Field] +- } +} +``` + +### [Aztec.nr] rename `encode_and_encrypt_with_keys` to `encode_and_encrypt_note_with_keys` + +```diff +contract XYZ { +- use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_with_keys; ++ use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys; +.... + +- numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m)); ++ numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m)); +} +``` + +### [Aztec.nr] changes to `NoteInterface` + +`compute_nullifier` function was renamed to `compute_note_hash_and_nullifier` and now the function has to return not only the nullifier but also the note hash used to compute the nullifier. +The same change was done to `compute_nullifier_without_context` function. +These changes were done because having the note hash exposed allowed us to not having to re-compute it again in `destroy_note` function of Aztec.nr which led to significant decrease in gate counts (see the [optimization PR](https://github.com/AztecProtocol/aztec-packages/pull/7103) for more details). + +```diff +- impl NoteInterface for ValueNote { +- fn compute_nullifier(self, context: &mut PrivateContext) -> Field { +- let note_hash_for_nullify = compute_note_hash_for_consumption(self); +- let secret = context.request_nsk_app(self.npk_m_hash); +- poseidon2_hash([ +- note_hash_for_nullify, +- secret, +- GENERATOR_INDEX__NOTE_NULLIFIER as Field, +- ]) +- } +- +- fn compute_nullifier_without_context(self) -> Field { +- let note_hash_for_nullify = compute_note_hash_for_consumption(self); +- let secret = get_nsk_app(self.npk_m_hash); +- poseidon2_hash([ +- note_hash_for_nullify, +- secret, +- GENERATOR_INDEX__NOTE_NULLIFIER as Field, +- ]) +- } +- } ++ impl NoteInterface for ValueNote { ++ fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { ++ let note_hash_for_nullify = compute_note_hash_for_consumption(self); ++ let secret = context.request_nsk_app(self.npk_m_hash); ++ let nullifier = poseidon2_hash([ ++ note_hash_for_nullify, ++ secret, ++ GENERATOR_INDEX__NOTE_NULLIFIER as Field, ++ ]); ++ (note_hash_for_nullify, nullifier) ++ } ++ ++ fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { ++ let note_hash_for_nullify = compute_note_hash_for_consumption(self); ++ let secret = get_nsk_app(self.npk_m_hash); ++ let nullifier = poseidon2_hash([ ++ note_hash_for_nullify, ++ secret, ++ GENERATOR_INDEX__NOTE_NULLIFIER as Field, ++ ]); ++ (note_hash_for_nullify, nullifier) ++ } ++ } +``` + +### [Aztec.nr] `note_getter` returns `BoundedVec` + +The `get_notes` and `view_notes` function no longer return an array of options (i.e. `[Option, N_NOTES]`) but instead a `BoundedVec`. This better conveys the useful property the old array had of having all notes collapsed at the beginning of the array, which allows for powerful optimizations and gate count reduction when setting the `options.limit` value. + +A `BoundedVec` has a `max_len()`, which equals the number of elements it can hold, and a `len()`, which equals the number of elements it currently holds. Since `len()` is typically not knwon at compile time, iterating over a `BoundedVec` looks slightly different than iterating over an array of options: + +```diff +- let option_notes = get_notes(options); +- for i in 0..option_notes.len() { +- if option_notes[i].is_some() { +- let note = option_notes[i].unwrap_unchecked(); +- } +- } ++ let notes = get_notes(options); ++ for i in 0..notes.max_len() { ++ if i < notes.len() { ++ let note = notes.get_unchecked(i); ++ } ++ } +``` + +To further reduce gate count, you can iterate over `options.limit` instead of `max_len()`, since `options.limit` is guaranteed to be larger or equal to `len()`, and smaller or equal to `max_len()`: + +```diff +- for i in 0..notes.max_len() { ++ for i in 0..options.limit { +``` + +### [Aztec.nr] static private authwit + +The private authwit validation is now making a static call to the account contract instead of passing over control flow. This is to ensure that it cannot be used for re-entry. + +To make this change however, we cannot allow emitting a nullifier from the account contract, since that would break the static call. Instead, we will be changing the `spend_private_authwit` to a `verify_private_authwit` and in the `auth` library emit the nullifier. This means that the "calling" contract will now be emitting the nullifier, and not the account. For example, for a token contract, the nullifier is now emitted by the token contract. However, as this is done inside the `auth` library, the token contract doesn't need to change much. + +The biggest difference is related to "cancelling" an authwit. Since it is no longer in the account contract, you cannot just emit a nullifier from it anymore. Instead it must rely on the token contract providing functionality for cancelling. + +There are also a few general changes to how authwits are generated, namely to more easily support the data required for a validity lookup now. Previously we could lookup the `message_hash` directly at the account contract, now we instead need to use the `inner_hash` and the contract of the consumer to figure out if it have already been emitted. + +A minor extension have been made to the authwit creations to make it easier to sign a specific a hash with a specific caller, e.g., the `inner_hash` can be provided as `{consumer, inner_hash}` to the `createAuthWit` where it previously needed to do a couple of manual steps to compute the outer hash. The `computeOuterAuthWitHash` have been made internal and the `computeAuthWitMessageHash` can instead be used to compute the values similarly to other authwit computations. + +```diff +const innerHash = computeInnerAuthWitHash([Fr.ZERO, functionSelector.toField(), entrypointPackedArgs.hash]); +-const outerHash = computeOuterAuthWitHash( +- this.dappEntrypointAddress, +- new Fr(this.chainId), +- new Fr(this.version), +- innerHash, +-); ++const messageHash = computeAuthWitMessageHash( ++ { consumer: this.dappEntrypointAddress, innerHash }, ++ { chainId: new Fr(this.chainId), version: new Fr(this.version) }, ++); +``` + +If the wallet is used to compute the authwit, it will populate the chain id and version instead of requiring it to be provided by tha actor. + +```diff +const innerHash = computeInnerAuthWitHash([Fr.fromString('0xdead')]); +-const outerHash = computeOuterAuthWitHash(wallets[1].getAddress(), chainId, version, innerHash); +-const witness = await wallets[0].createAuthWit(outerHash); ++ const witness = await wallets[0].createAuthWit({ comsumer: accounts[1].address, inner_hash }); +``` + +## 0.43.0 + +### [Aztec.nr] break `token.transfer()` into `transfer` and `transferFrom` + +Earlier we had just one function - `transfer()` which used authwits to handle the case where a contract/user wants to transfer funds on behalf of another user. +To reduce circuit sizes and proof times, we are breaking up `transfer` and introducing a dedicated `transferFrom()` function like in the ERC20 standard. + +### [Aztec.nr] `options.limit` has to be constant + +The `limit` parameter in `NoteGetterOptions` and `NoteViewerOptions` is now required to be a compile-time constant. This allows performing loops over this value, which leads to reduced circuit gate counts when setting a `limit` value. + +### [Aztec.nr] canonical public authwit registry + +The public authwits are moved into a shared registry (auth registry) to make it easier for sequencers to approve for their non-revertible (setup phase) whitelist. Previously, it was possible to DOS a sequencer by having a very expensive authwit validation that fails at the end, now the whitelist simply need the registry. + +Notable, this means that consuming a public authwit will no longer emit a nullifier in the account contract but instead update STORAGE in the public domain. This means that there is a larger difference between private and public again. However, it also means that if contracts need to approve, and use the approval in the same tx, it is transient and don't need to go to DA (saving 96 bytes). + +For the typescript wallets this is handled so the APIs don't change, but account contracts should get rid of their current setup with `approved_actions`. + +```diff +- let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); ++ let actions = AccountActions::init(&mut context, is_valid_impl); +``` + +For contracts we have added a `set_authorized` function in the auth library that can be used to set values in the registry. + +```diff +- storage.approved_action.at(message_hash).write(true); ++ set_authorized(&mut context, message_hash, true); +``` + +### [Aztec.nr] emit encrypted logs + +Emitting or broadcasting encrypted notes are no longer done as part of the note creation, but must explicitly be either emitted or discarded instead. + +```diff ++ use dep::aztec::encrypted_logs::encrypted_note_emission::{encode_and_encrypt, encode_and_encrypt_with_keys}; + +- storage.balances.sub(from, amount); ++ storage.balances.sub(from, amount).emit(encode_and_encrypt_with_keys(&mut context, from, from)); ++ storage.balances.sub(from, amount).emit(encode_and_encrypt_with_keys(&mut context, from_ovpk, from_ivpk)); ++ storage.balances.sub(from, amount).discard(); +``` + +## 0.42.0 + +### [Aztec.nr] Unconstrained Context + +Top-level unconstrained execution is now marked by the new `UnconstrainedContext`, which provides access to the block number and contract address being used in the simulation. Any custom state variables that provided unconstrained functions should update their specialization parameter: + +```diff ++ use dep::aztec::context::UnconstrainedContext; + +- impl MyStateVariable<()> { ++ impl MyStateVariable { +``` + +### [Aztec.nr] Filtering is now constrained + +The `filter` argument of `NoteGetterOptions` (typically passed via the `with_filter()` function) is now applied in a constraining environment, meaning any assertions made during the filtering are guaranteed to hold. This mirrors the behavior of the `select()` function. + +### [Aztec.nr] Emitting encrypted notes and logs + +The `emit_encrypted_log` context function is now `encrypt_and_emit_log` or `encrypt_and_emit_note`. + +```diff +- context.emit_encrypted_log(log1); ++ context.encrypt_and_emit_log(log1); ++ context.encrypt_and_emit_note(note1); +``` + +Broadcasting a note will call `encrypt_and_emit_note` in the background. To broadcast a generic event, use `encrypt_and_emit_log` +with the same encryption parameters as notes require. Currently, only fields and arrays of fields are supported as events. + +By default, logs emitted via `encrypt_and_emit_log` will be siloed with a _masked_ contract address. To force the contract address to be revealed, so everyone can check it rather than just the log recipient, provide `randomness = 0`. + +## Public execution migrated to the Aztec Virtual Machine + +**What does this mean for me?** + +It should be mostly transparent, with a few caveats: + +- Not all Noir blackbox functions are supported by the AVM. Only `Sha256`, `PedersenHash`, `Poseidon2Permutation`, `Keccak256`, and `ToRadix` are supported. +- For public functions, `context.nullifier_exists(...)` will now also consider pending nullifiers. +- The following methods of `PublicContext` are not supported anymore: `fee_recipient`, `fee_per_da_gas`, `fee_per_l2_gas`, `call_public_function_no_args`, `static_call_public_function_no_args`, `delegate_call_public_function_no_args`, `call_public_function_with_packed_args`, `set_return_hash`, `finish`. However, in terms of functionality, the new context's interface should be equivalent (unless otherwise specified in this list). +- Delegate calls are not yet supported in the AVM. +- If you have types with custom serialization that you use across external contracts calls, you might need to modify its serialization to match how Noir would serialize it. This is a known problem unrelated to the AVM, but triggered more often when using it. +- A few error messages might change format, so you might need to change your test assertions. + +**Internal details** + +Before this change, public bytecode was executed using the same simulator as in private: the ACIR simulator (and internally, the Brillig VM). On the Aztec.nr side, public functions accessed the context through `PublicContext`. + +After this change, public bytecode will be run using the AVM simulator (the simulator for our upcoming zkVM). This bytecode is generated from Noir contracts in two steps: First, `nargo compile` produces an artifact which has Brillig bytecode for public functions, just as it did before. Second: the `avm-transpiler` takes that artifact, and it transpiles Brillig bytecode to AVM bytecode. This final artifact can now be deployed and used with the new public runtime. + +On the Aztec.nr side, public functions keep accessing the context using `PublicContext` but the underlying implementation is switch with what formerly was the `AvmContext`. + +## 0.41.0 + +### [Aztec.nr] State variable rework + +Aztec.nr state variables have been reworked so that calling private functions in public and vice versa is detected as an error during compilation instead of at runtime. This affects users in a number of ways: + +#### New compile time errors + +It used to be that calling a state variable method only available in public from a private function resulted in obscure runtime errors in the form of a failed `_is_some` assertion. + +Incorrect usage of the state variable methods now results in compile time errors. For example, given the following function: + +```rust +#[aztec(public)] +fn get_decimals() -> pub u8 { + storage.decimals.read_private() +} +``` + +The compiler will now error out with + +``` +Expected type SharedImmutable<_, &mut PrivateContext>, found type SharedImmutable +``` + +The key component is the second generic parameter: the compiler expects a `PrivateContext` (becuse `read_private` is only available during private execution), but a `PublicContext` is being used instead (because of the `#[aztec(public)]` attribute). + +#### Generic parameters in `Storage` + +The `Storage` struct (the one marked with `#[aztec(storage)]`) should now be generic over a `Context` type, which matches the new generic parameter of all Aztec.nr libraries. This parameter is always the last generic parameter. + +This means that, without any additional features, we'd end up with some extra boilerplate when declaring this struct: + +```diff +#[aztec(storage)] +- struct Storage { ++ struct Storage { +- nonce_for_burn_approval: PublicMutable, ++ nonce_for_burn_approval: PublicMutable, +- portal_address: SharedImmutable, ++ portal_address: SharedImmutable, +- approved_action: Map>, ++ approved_action: Map, Context>, +} +``` + +Because of this, the `#[aztec(storage)]` macro has been updated to **automatically inject** this `Context` generic parameter. The storage declaration does not require any changes. + +#### Removal of `Context` + +The `Context` type no longer exists. End users typically didn't use it, but if imported it needs to be deleted. + +### [Aztec.nr] View functions and interface navigation + +It is now possible to explicitly state a function doesn't perform any state alterations (including storage, logs, nullifiers and/or messages from L2 to L1) with the `#[aztec(view)]` attribute, similarly to solidity's `view` function modifier. + +```diff + #[aztec(public)] ++ #[aztec(view)] + fn get_price(asset_id: Field) -> Asset { + storage.assets.at(asset_id).read() + } +``` + +View functions only generate a `StaticCallInterface` that doesn't include `.call` or `.enqueue` methods. Also, the denomination `static` has been completely removed from the interfaces, in favor of the more familiar `view` + +```diff +- let price = PriceFeed::at(asset.oracle).get_price(0).static_call(&mut context).price; ++ let price = PriceFeed::at(asset.oracle).get_price(0).view(&mut context).price; +``` + +```diff +#[aztec(private)] +fn enqueue_public_get_value_from_child(target_contract: AztecAddress, value: Field) { +- StaticChild::at(target_contract).pub_get_value(value).static_enqueue(&mut context); ++ StaticChild::at(target_contract).pub_get_value(value).enqueue_view(&mut context); +} +``` + +Additionally, the Noir LSP will now honor "go to definitions" requests for contract interfaces (Ctrl+click), taking the user to the original function implementation. + +### [Aztec.js] Simulate changes + +- `.simulate()` now tracks closer the process performed by `.send().wait()`, specifically going through the account contract entrypoint instead of directly calling the intended function. +- `wallet.viewTx(...)` has been renamed to `wallet.simulateUnconstrained(...)` to better clarify what it does. + +### [Aztec.nr] Keys: Token note now stores an owner master nullifying public key hash instead of an owner address + +i.e. + +```diff +struct TokenNote { + amount: U128, +- owner: AztecAddress, ++ npk_m_hash: Field, + randomness: Field, +} +``` + +Creating a token note and adding it to storage now looks like this: + +```diff +- let mut note = ValueNote::new(new_value, owner); +- storage.a_private_value.insert(&mut note, true); ++ let owner_npk_m_hash = get_npk_m_hash(&mut context, owner); ++ let owner_ivpk_m = get_ivpk_m(&mut context, owner); ++ let mut note = ValueNote::new(new_value, owner_npk_m_hash); ++ storage.a_private_value.insert(&mut note, true, owner_ivpk_m); +``` + +Computing the nullifier similarly changes to use this master nullifying public key hash. + +## 0.40.0 + +### [Aztec.nr] Debug logging + +The function `debug_log_array_with_prefix` has been removed. Use `debug_log_format` with `{}` instead. The special sequence `{}` will be replaced with the whole array. You can also use `{0}`, `{1}`, ... as usual with `debug_log_format`. + +```diff +- debug_log_array_with_prefix("Prefix", my_array); ++ debug_log_format("Prefix {}", my_array); +``` + +## 0.39.0 + +### [Aztec.nr] Mutable delays in `SharedMutable` + +The type signature for `SharedMutable` changed from `SharedMutable` to `SharedMutable`. The behavior is the same as before, except the delay can now be changed after deployment by calling `schedule_delay_change`. + +### [Aztec.nr] get_public_key oracle replaced with get_ivpk_m + +When implementing changes according to a new key scheme we had to change oracles. +What used to be called encryption public key is now master incoming viewing public key. + +```diff +- use dep::aztec::oracles::get_public_key::get_public_key; ++ use dep::aztec::keys::getters::get_ivpk_m; + +- let encryption_pub_key = get_public_key(self.owner); ++ let ivpk_m = get_ivpk_m(context, self.owner); +``` + +## 0.38.0 + +### [Aztec.nr] Emitting encrypted logs + +The `emit_encrypted_log` function is now a context method. + +```diff +- use dep::aztec::log::emit_encrypted_log; +- use dep::aztec::logs::emit_encrypted_log; + +- emit_encrypted_log(context, log1); ++ context.emit_encrypted_log(log1); +``` + +## 0.36.0 + +### `FieldNote` removed + +`FieldNote` only existed for testing purposes, and was not a note type that should be used in any real application. Its name unfortunately led users to think that it was a note type suitable to store a `Field` value, which it wasn't. + +If using `FieldNote`, you most likely want to use `ValueNote` instead, which has both randomness for privacy and an owner for proper nullification. + +### `SlowUpdatesTree` replaced for `SharedMutable` + +The old `SlowUpdatesTree` contract and libraries have been removed from the codebase, use the new `SharedMutable` library instead. This will require that you add a global variable specifying a delay in blocks for updates, and replace the slow updates tree state variable with `SharedMutable` variables. + +```diff ++ global CHANGE_ROLES_DELAY_BLOCKS = 5; + +struct Storage { +- slow_update: SharedImmutable, ++ roles: Map>, +} +``` + +Reading from `SharedMutable` is much simpler, all that's required is to call `get_current_value_in_public` or `get_current_value_in_private`, depending on the domain. + +```diff +- let caller_roles = UserFlags::new(U128::from_integer(slow.read_at_pub(context.msg_sender().to_field()).call(&mut context))); ++ let caller_roles = storage.roles.at(context.msg_sender()).get_current_value_in_public(); +``` + +Finally, you can remove all capsule usage on the client code or tests, since those are no longer required when working with `SharedMutable`. + +### [Aztec.nr & js] Portal addresses + +Deployments have been modified. No longer are portal addresses treated as a special class, being immutably set on creation of a contract. They are no longer passed in differently compared to the other variables and instead should be implemented using usual storage by those who require it. One should use the storage that matches the usecase - likely shared storage to support private and public. + +This means that you will likely add the portal as a constructor argument + +```diff +- fn constructor(token: AztecAddress) { +- storage.token.write(token); +- } ++ struct Storage { + ... ++ portal_address: SharedImmutable, ++ } ++ fn constructor(token: AztecAddress, portal_address: EthAddress) { ++ storage.token.write(token); ++ storage.portal_address.initialize(portal_address); ++ } +``` + +And read it from storage whenever needed instead of from the context. + +```diff +- context.this_portal_address(), ++ storage.portal_address.read_public(), +``` + +### [Aztec.nr] Oracles + +Oracle `get_nullifier_secret_key` was renamed to `get_app_nullifier_secret_key` and `request_nullifier_secret_key` function on PrivateContext was renamed as `request_app_nullifier_secret_key`. + +```diff +- let secret = get_nullifier_secret_key(self.owner); ++ let secret = get_app_nullifier_secret_key(self.owner); +``` + +```diff +- let secret = context.request_nullifier_secret_key(self.owner); ++ let secret = context.request_app_nullifier_secret_key(self.owner); +``` + +### [Aztec.nr] Contract interfaces + +It is now possible to import contracts on another contracts and use their automatic interfaces to perform calls. The interfaces have the same name as the contract, and are automatically exported. Parameters are automatically serialized (using the `Serialize` trait) and return values are automatically deserialized (using the `Deserialize` trait). Serialize and Deserialize methods have to conform to the standard ACVM serialization schema for the interface to work! + +1. Only fixed length types are supported +2. All numeric types become Fields +3. Strings become arrays of Fields, one per char +4. Arrays become arrays of Fields following rules 2 and 3 +5. Structs become arrays of Fields, with every item defined in the same order as they are in Noir code, following rules 2, 3, 4 and 5 (recursive) + +```diff +- context.call_public_function( +- storage.gas_token_address.read_private(), +- FunctionSelector::from_signature("pay_fee(Field)"), +- [42] +- ); +- +- context.call_public_function( +- storage.gas_token_address.read_private(), +- FunctionSelector::from_signature("pay_fee(Field)"), +- [42] +- ); +- +- let _ = context.call_private_function( +- storage.subscription_token_address.read_private(), +- FunctionSelector::from_signature("transfer((Field),(Field),Field,Field)"), +- [ +- context.msg_sender().to_field(), +- storage.subscription_recipient_address.read_private().to_field(), +- storage.subscription_price.read_private(), +- nonce +- ] +- ); ++ use dep::gas_token::GasToken; ++ use dep::token::Token; ++ ++ ... ++ // Public call from public land ++ GasToken::at(storage.gas_token_address.read_private()).pay_fee(42).call(&mut context); ++ // Public call from private land ++ GasToken::at(storage.gas_token_address.read_private()).pay_fee(42).enqueue(&mut context); ++ // Private call from private land ++ Token::at(asset).transfer(context.msg_sender(), storage.subscription_recipient_address.read_private(), amount, nonce).call(&mut context); +``` + +It is also possible to use these automatic interfaces from the local contract, and thus enqueue public calls from private without having to rely on low level `context` calls. + +### [Aztec.nr] Rename max block number setter + +The `request_max_block_number` function has been renamed to `set_tx_max_block_number` to better reflect that it is not a getter, and that the setting is transaction-wide. + +```diff +- context.request_max_block_number(value); ++ context.set_tx_max_block_number(value); +``` + +### [Aztec.nr] Get portal address + +The `get_portal_address` oracle was removed. If you need to get the portal address of SomeContract, add the following methods to it + +``` +#[aztec(private)] +fn get_portal_address() -> EthAddress { + context.this_portal_address() +} + +#[aztec(public)] +fn get_portal_address_public() -> EthAddress { + context.this_portal_address() +} +``` + +and change the call to `get_portal_address` + +```diff +- let portal_address = get_portal_address(contract_address); ++ let portal_address = SomeContract::at(contract_address).get_portal_address().call(&mut context); +``` + +### [Aztec.nr] Required gas limits for public-to-public calls + +When calling a public function from another public function using the `call_public_function` method, you must now specify how much gas you're allocating to the nested call. This will later allow you to limit the amount of gas consumed by the nested call, and handle any out of gas errors. + +Note that gas limits are not yet enforced. For now, it is suggested you use `dep::aztec::context::gas::GasOpts::default()` which will forward all available gas. + +```diff ++ use dep::aztec::context::gas::GasOpts; + +- context.call_public_function(target_contract, target_selector, args); ++ context.call_public_function(target_contract, target_selector, args, GasOpts::default()); +``` + +Note that this is not required when enqueuing a public function from a private one, since top-level enqueued public functions will always consume all gas available for the transaction, as it is not possible to handle any out-of-gas errors. + +### [Aztec.nr] Emitting unencrypted logs + +The `emit_unencrypted_logs` function is now a context method. + +```diff +- use dep::aztec::log::emit_unencrypted_log; +- use dep::aztec::log::emit_unencrypted_log_from_private; + +- emit_unencrypted_log(context, log1); +- emit_unencrypted_log_from_private(context, log2); ++ context.emit_unencrypted_log(log1); ++ context.emit_unencrypted_log(log2); +``` + +## 0.33 + +### [Aztec.nr] Storage struct annotation + +The storage struct now identified by the annotation `#[aztec(storage)]`, instead of having to rely on it being called `Storage`. + +```diff +- struct Storage { +- ... +- } ++ #[aztec(storage)] ++ struct MyStorageStruct { ++ ... ++ } +``` + +### [Aztec.js] Storage layout and note info + +Storage layout and note information are now exposed in the TS contract artifact + +```diff +- const note = new Note([new Fr(mintAmount), secretHash]); +- const pendingShieldStorageSlot = new Fr(5n); // storage slot for pending_shields +- const noteTypeId = new Fr(84114971101151129711410111011678111116101n); // note type id for TransparentNote +- const extendedNote = new ExtendedNote( +- note, +- admin.address, +- token.address, +- pendingShieldStorageSlot, +- noteTypeId, +- receipt.txHash, +- ); +- await pxe.addNote(extendedNote); ++ const note = new Note([new Fr(mintAmount), secretHash]); ++ const extendedNote = new ExtendedNote( ++ note, ++ admin.address, ++ token.address, ++ TokenContract.storage.pending_shields.slot, ++ TokenContract.notes.TransparentNote.id, ++ receipt.txHash, ++ ); ++ await pxe.addNote(extendedNote); +``` + +### [Aztec.nr] rand oracle is now called unsafe_rand + +`oracle::rand::rand` has been renamed to `oracle::unsafe_rand::unsafe_rand`. +This change was made to communicate that we do not constrain the value in circuit and instead we just trust our PXE. + +```diff +- let random_value = rand(); ++ let random_value = unsafe_rand(); +``` + +### [AztecJS] Simulate and get return values for ANY call and introducing `prove()` + +Historically it have been possible to "view" `unconstrained` functions to simulate them and get the return values, but not for `public` nor `private` functions. +This has lead to a lot of bad code where we have the same function implemented thrice, once in `private`, once in `public` and once in `unconstrained`. +It is not possible to call `simulate` on any call to get the return values! +However, beware that it currently always returns a Field array of size 4 for private and public. +This will change to become similar to the return values of the `unconstrained` functions with proper return types. + +```diff +- #[aztec(private)] +- fn get_shared_immutable_constrained_private() -> pub Leader { +- storage.shared_immutable.read_private() +- } +- +- unconstrained fn get_shared_immutable() -> pub Leader { +- storage.shared_immutable.read_public() +- } + ++ #[aztec(private)] ++ fn get_shared_immutable_private() -> pub Leader { ++ storage.shared_immutable.read_private() ++ } + +- const returnValues = await contract.methods.get_shared_immutable().view(); ++ const returnValues = await contract.methods.get_shared_immutable_private().simulate(); +``` + +```diff +await expect( +- asset.withWallet(wallets[1]).methods.update_admin(newAdminAddress).simulate()).rejects.toThrow( ++ asset.withWallet(wallets[1]).methods.update_admin(newAdminAddress).prove()).rejects.toThrow( + "Assertion failed: caller is not admin 'caller_roles.is_admin'", +); +``` + +## 0.31.0 + +### [Aztec.nr] Public storage historical read API improvement + +`history::public_value_inclusion::prove_public_value_inclusion` has been renamed to `history::public_storage::public_storage_historical_read`, and its API changed slightly. Instead of receiving a `value` parameter it now returns the historical value stored at that slot. + +If you were using an oracle to get the value to pass to `prove_public_value_inclusion`, drop the oracle and use the return value from `public_storage_historical_read` instead: + +```diff +- let value = read_storage(); +- prove_public_value_inclusion(value, storage_slot, contract_address, context); ++ let value = public_storage_historical_read(storage_slot, contract_address, context); +``` + +If you were proving historical existence of a value you got via some other constrained means, perform an assertion against the return value of `public_storage_historical_read` instead: + +```diff +- prove_public_value_inclusion(value, storage_slot, contract_address, context); ++ assert(public_storage_historical_read(storage_slot, contract_address, context) == value); +``` + +## 0.30.0 + +### [AztecJS] Simplify authwit syntax + +```diff +- const messageHash = computeAuthWitMessageHash(accounts[1].address, action.request()); +- await wallets[0].setPublicAuth(messageHash, true).send().wait(); ++ await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); +``` + +```diff +const action = asset + .withWallet(wallets[1]) + .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce); +-const messageHash = computeAuthWitMessageHash(accounts[1].address, action.request()); +-const witness = await wallets[0].createAuthWitness(messageHash); ++const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); +await wallets[1].addAuthWitness(witness); +``` + +Also note some of the naming changes: +`setPublicAuth` -> `setPublicAuthWit` +`createAuthWitness` -> `createAuthWit` + +### [Aztec.nr] Automatic NoteInterface implementation and selector changes + +Implementing a note required a fair amount of boilerplate code, which has been substituted by the `#[aztec(note)]` attribute. + +```diff ++ #[aztec(note)] +struct AddressNote { + address: AztecAddress, + owner: AztecAddress, + randomness: Field, + header: NoteHeader +} + +impl NoteInterface for AddressNote { +- fn serialize_content(self) -> [Field; ADDRESS_NOTE_LEN]{ +- [self.address.to_field(), self.owner.to_field(), self.randomness] +- } +- +- fn deserialize_content(serialized_note: [Field; ADDRESS_NOTE_LEN]) -> Self { +- AddressNote { +- address: AztecAddress::from_field(serialized_note[0]), +- owner: AztecAddress::from_field(serialized_note[1]), +- randomness: serialized_note[2], +- header: NoteHeader::empty(), +- } +- } +- +- fn compute_note_content_hash(self) -> Field { +- pedersen_hash(self.serialize_content(), 0) +- } +- + fn compute_nullifier(self, context: &mut PrivateContext) -> Field { + let note_hash_for_nullify = compute_note_hash_for_consumption(self); + let secret = context.request_nullifier_secret_key(self.owner); + pedersen_hash([ + note_hash_for_nullify, + secret.low, + secret.high, + ],0) + } + + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_consumption(self); + let secret = get_nullifier_secret_key(self.owner); + pedersen_hash([ + note_hash_for_nullify, + secret.low, + secret.high, + ],0) + } + +- fn set_header(&mut self, header: NoteHeader) { +- self.header = header; +- } +- +- fn get_header(note: Self) -> NoteHeader { +- note.header +- } + + fn broadcast(self, context: &mut PrivateContext, slot: Field) { + let encryption_pub_key = get_public_key(self.owner); + emit_encrypted_log( + context, + (*context).this_address(), + slot, + Self::get_note_type_id(), + encryption_pub_key, + self.serialize_content(), + ); + } + +- fn get_note_type_id() -> Field { +- 6510010011410111511578111116101 +- } +} +``` + +Automatic note (de)serialization implementation also means it is now easier to filter notes using `NoteGetterOptions.select` via the `::properties()` helper: + +Before: + +```rust +let options = NoteGetterOptions::new().select(0, amount, Option::none()).select(1, owner.to_field(), Option::none()).set_limit(1); +``` + +After: + +```rust +let options = NoteGetterOptions::new().select(ValueNote::properties().value, amount, Option::none()).select(ValueNote::properties().owner, owner.to_field(), Option::none()).set_limit(1); +``` + +The helper returns a metadata struct that looks like this (if autogenerated) + +```rust +ValueNoteProperties { + value: PropertySelector { index: 0, offset: 0, length: 32 }, + owner: PropertySelector { index: 1, offset: 0, length: 32 }, + randomness: PropertySelector { index: 2, offset: 0, length: 32 }, +} +``` + +It can also be used for the `.sort` method. + +## 0.27.0 + +### `initializer` macro replaces `constructor` + +Before this version, every contract was required to have exactly one `constructor` private function, that was used for deployment. We have now removed this requirement, and made `constructor` a function like any other. + +To signal that a function can be used to **initialize** a contract, you must now decorate it with the `#[aztec(initializer)]` attribute. Initializers are regular functions that set an "initialized" flag (a nullifier) for the contract. A contract can only be initialized once, and contract functions can only be called after the contract has been initialized, much like a constructor. However, if a contract defines no initializers, it can be called at any time. Additionally, you can define as many initializer functions in a contract as you want, both private and public. + +To migrate from current code, simply add an initializer attribute to your constructor functions. + +```diff ++ #[aztec(initializer)] +#[aztec(private)] +fn constructor() { ... } +``` + +If your private constructor was used to just call a public internal initializer, then remove the private constructor and flag the public function as initializer. And if your private constructor was an empty one, just remove it. + +## 0.25.0 + +### [Aztec.nr] Static calls + +It is now possible to perform static calls from both public and private functions. Static calls forbid any modification to the state, including L2->L1 messages or log generation. Once a static context is set through a static all, every subsequent call will also be treated as static via context propagation. + +```rust +context.static_call_private_function(targetContractAddress, targetSelector, args); + +context.static_call_public_function(targetContractAddress, targetSelector, args); +``` + +### [Aztec.nr] Introduction to `prelude` + +A new `prelude` module to include common Aztec modules and types. +This simplifies dependency syntax. For example: + +```rust +use dep::aztec::protocol_types::address::AztecAddress; +use dep::aztec::{ + context::{PrivateContext, Context}, note::{note_header::NoteHeader, utils as note_utils}, + state_vars::Map +}; +``` + +Becomes: + +```rust +use dep::aztec::prelude::{AztecAddress, NoteHeader, PrivateContext, Map}; +use dep::aztec::context::Context; +use dep::aztec::notes::utils as note_utils; +``` + +This will be further simplified in future versions (See [4496](https://github.com/AztecProtocol/aztec-packages/pull/4496) for further details). + +The prelude consists of + +```rust title="prelude" showLineNumbers +pub use crate::{ + context::{PrivateContext, PublicContext, ReturnsHash}, + note::{ + note_getter_options::NoteGetterOptions, + note_interface::{NoteHash, NoteType}, + note_viewer_options::NoteViewerOptions, + retrieved_note::RetrievedNote, + }, + state_vars::{ + map::Map, private_immutable::PrivateImmutable, private_mutable::PrivateMutable, + private_set::PrivateSet, public_immutable::PublicImmutable, public_mutable::PublicMutable, + shared_mutable::SharedMutable, storage::Storable, + }, +}; +pub use dep::protocol_types::{ + abis::function_selector::FunctionSelector, + address::{AztecAddress, EthAddress}, + point::Point, + traits::{Deserialize, Serialize}, +}; +``` +> Source code: noir-projects/aztec-nr/aztec/src/prelude.nr#L1-L22 + + +### `internal` is now a macro + +The `internal` keyword is now removed from Noir, and is replaced by an `aztec(internal)` attribute in the function. The resulting behavior is exactly the same: these functions will only be callable from within the same contract. + +Before: + +```rust +#[aztec(private)] +internal fn double(input: Field) -> Field { + input * 2 +} +``` + +After: + +```rust +#[aztec(private)] +#[aztec(internal)] +fn double(input: Field) -> Field { + input * 2 +} +``` + +### [Aztec.nr] No SafeU120 anymore! + +Noir now have overflow checks by default. So we don't need SafeU120 like libraries anymore. + +You can replace it with `U128` instead + +Before: + +``` +SafeU120::new(0) +``` + +Now: + +``` +U128::from_integer(0) +``` + +### [Aztec.nr] `compute_note_hash_and_nullifier` is now autogenerated + +Historically developers have been required to include a `compute_note_hash_and_nullifier` function in each of their contracts. This function is now automatically generated, and all instances of it in contract code can be safely removed. + +It is possible to provide a user-defined implementation, in which case auto-generation will be skipped (though there are no known use cases for this). + +### [Aztec.nr] Updated naming of state variable wrappers + +We have decided to change the naming of our state variable wrappers because the naming was not clear. +The changes are as follows: + +1. `Singleton` -> `PrivateMutable` +2. `ImmutableSingleton` -> `PrivateImmutable` +3. `StablePublicState` -> `SharedImmutable` +4. `PublicState` -> `PublicMutable` + +This is the meaning of "private", "public" and "shared": +Private: read (R) and write (W) from private, not accessible from public +Public: not accessible from private, R/W from public +Shared: R from private, R/W from public + +Note: `SlowUpdates` will be renamed to `SharedMutable` once the implementation is ready. + +### [Aztec.nr] Authwit updates + +Authentication Witnesses have been updates such that they are now cancellable and scoped to a specific consumer. +This means that the `authwit` nullifier must be emitted from the account contract, which require changes to the interface. +Namely, the `assert_current_call_valid_authwit_public` and `assert_current_call_valid_authwit` in `auth.nr` will **NO LONGER** emit a nullifier. +Instead it will call a `spend_*_authwit` function in the account contract - which will emit the nullifier and perform a few checks. +This means that the `is_valid` functions have been removed to not confuse it for a non-mutating function (static). +Furthermore, the `caller` parameter of the "authwits" have been moved "further out" such that the account contract can use it in validation, allowing scoped approvals from the account POV. +For most contracts, this won't be changing much, but for the account contract, it will require a few changes. + +Before: + +```rust +#[aztec(public)] +fn is_valid_public(message_hash: Field) -> Field { + let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + actions.is_valid_public(message_hash) +} + +#[aztec(private)] +fn is_valid(message_hash: Field) -> Field { + let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + actions.is_valid(message_hash) +} +``` + +After: + +```rust +#[aztec(private)] +fn verify_private_authwit(inner_hash: Field) -> Field { + let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + actions.verify_private_authwit(inner_hash) +} + +#[aztec(public)] +fn spend_public_authwit(inner_hash: Field) -> Field { + let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + actions.spend_public_authwit(inner_hash) +} +``` + +## 0.24.0 + +### Introduce Note Type IDs + +Note Type IDs are a new feature which enable contracts to have multiple `Map`s with different underlying note types, something that was not possible before. This is done almost without any user intervention, though some minor changes are required. + +The mandatory `compute_note_hash_and_nullifier` now has a fifth parameter `note_type_id`. Use this instead of `storage_slot` to determine which deserialization function to use. + +Before: + +```rust +unconstrained fn compute_note_hash_and_nullifier( + contract_address: AztecAddress, + nonce: Field, + storage_slot: Field, + preimage: [Field; TOKEN_NOTE_LEN] +) -> pub [Field; 4] { + let note_header = NoteHeader::new(contract_address, nonce, storage_slot); + + if (storage_slot == storage.pending_shields.get_storage_slot()) { + note_utils::compute_note_hash_and_nullifier(TransparentNote::deserialize_content, note_header, preimage) + } else if (note_type_id == storage.slow_update.get_storage_slot()) { + note_utils::compute_note_hash_and_nullifier(FieldNote::deserialize_content, note_header, preimage) + } else { + note_utils::compute_note_hash_and_nullifier(TokenNote::deserialize_content, note_header, preimage) + } +``` + +Now: + +```rust +unconstrained fn compute_note_hash_and_nullifier( + contract_address: AztecAddress, + nonce: Field, + storage_slot: Field, + note_type_id: Field, + preimage: [Field; TOKEN_NOTE_LEN] +) -> pub [Field; 4] { + let note_header = NoteHeader::new(contract_address, nonce, storage_slot); + + if (note_type_id == TransparentNote::get_note_type_id()) { + note_utils::compute_note_hash_and_nullifier(TransparentNote::deserialize_content, note_header, preimage) + } else if (note_type_id == FieldNote::get_note_type_id()) { + note_utils::compute_note_hash_and_nullifier(FieldNote::deserialize_content, note_header, preimage) + } else { + note_utils::compute_note_hash_and_nullifier(TokenNote::deserialize_content, note_header, preimage) + } +``` + +The `NoteInterface` trait now has an additional `get_note_type_id()` function. This implementation will be autogenerated in the future, but for now providing any unique ID will suffice. The suggested way to do it is by running the Python command shown in the comment below: + +```rust +impl NoteInterface for MyCustomNote { + fn get_note_type_id() -> Field { + // python -c "print(int(''.join(str(ord(c)) for c in 'MyCustomNote')))" + 771216711711511611110978111116101 + } +} +``` + +### [js] Importing contracts in JS + +`@aztec/noir-contracts` is now `@aztec/noir-contracts.js`. You'll need to update your package.json & imports. + +Before: + +```js +import { TokenContract } from "@aztec/noir-contracts/Token"; +``` + +Now: + +```js +import { TokenContract } from "@aztec/noir-contracts.js/Token"; +``` + +### [Aztec.nr] Aztec.nr contracts location change in Nargo.toml + +Aztec contracts are now moved outside of the `yarn-project` folder and into `noir-projects`, so you need to update your imports. + +Before: + +```rust +easy_private_token_contract = {git = "https://github.com/AztecProtocol/aztec-packages/", tag ="v0.23.0", directory = "yarn-project/noir-contracts/contracts/easy_private_token_contract"} +``` + +Now, update the `yarn-project` folder for `noir-projects`: + +```rust +easy_private_token_contract = {git = "https://github.com/AztecProtocol/aztec-packages/", tag ="v0.24.0", directory = "noir-projects/noir-contracts/contracts/easy_private_token_contract"} +``` + +## 0.22.0 + +### `Note::compute_note_hash` renamed to `Note::compute_note_content_hash` + +The `compute_note_hash` function in of the `Note` trait has been renamed to `compute_note_content_hash` to avoid being confused with the actual note hash. + +Before: + +```rust +impl NoteInterface for CardNote { + fn compute_note_hash(self) -> Field { + pedersen_hash([ + self.owner.to_field(), + ], 0) + } +``` + +Now: + +```rust +impl NoteInterface for CardNote { + fn compute_note_content_hash(self) -> Field { + pedersen_hash([ + self.owner.to_field(), + ], 0) + } +``` + +### Introduce `compute_note_hash_for_consumption` and `compute_note_hash_for_insertion` + +Makes a split in logic for note hash computation for consumption and insertion. This is to avoid confusion between the two, and to make it clear that the note hash for consumption is different from the note hash for insertion (sometimes). + +`compute_note_hash_for_consumption` replaces `compute_note_hash_for_read_or_nullify`. +`compute_note_hash_for_insertion` is new, and mainly used in `lifecycle.nr`` + +### `Note::serialize_content` and `Note::deserialize_content` added to `NoteInterface + +The `NoteInterface` have been extended to include `serialize_content` and `deserialize_content` functions. This is to convey the difference between serializing the full note, and just the content. This change allows you to also add a `serialize` function to support passing in a complete note to a function. + +Before: + +```rust +impl Serialize for AddressNote { + fn serialize(self) -> [Field; ADDRESS_NOTE_LEN]{ + [self.address.to_field(), self.owner.to_field(), self.randomness] + } +} +impl Deserialize for AddressNote { + fn deserialize(serialized_note: [Field; ADDRESS_NOTE_LEN]) -> Self { + AddressNote { + address: AztecAddress::from_field(serialized_note[0]), + owner: AztecAddress::from_field(serialized_note[1]), + randomness: serialized_note[2], + header: NoteHeader::empty(), + } + } +``` + +Now + +```rust +impl NoteInterface for AddressNote { + fn serialize_content(self) -> [Field; ADDRESS_NOTE_LEN]{ + [self.address.to_field(), self.owner.to_field(), self.randomness] + } + + fn deserialize_content(serialized_note: [Field; ADDRESS_NOTE_LEN]) -> Self { + AddressNote { + address: AztecAddress::from_field(serialized_note[0]), + owner: AztecAddress::from_field(serialized_note[1]), + randomness: serialized_note[2], + header: NoteHeader::empty(), + } + } + ... +} +``` + +### [Aztec.nr] No storage.init() and `Serialize`, `Deserialize`, `NoteInterface` as Traits, removal of SerializationMethods and SERIALIZED_LEN + +Storage definition and initialization has been simplified. Previously: + +```rust +struct Storage { + leader: PublicState, + legendary_card: Singleton, + profiles: Map>, + test: Set, + imm_singleton: PrivateImmutable, +} + +impl Storage { + fn init(context: Context) -> Self { + Storage { + leader: PublicMutable::new( + context, + 1, + LeaderSerializationMethods, + ), + legendary_card: PrivateMutable::new(context, 2, CardNoteMethods), + profiles: Map::new( + context, + 3, + |context, slot| { + PrivateMutable::new(context, slot, CardNoteMethods) + }, + ), + test: Set::new(context, 4, CardNoteMethods), + imm_singleton: PrivateImmutable::new(context, 4, CardNoteMethods), + } + } + } +``` + +Now: + +```rust +struct Storage { + leader: PublicMutable, + legendary_card: Singleton, + profiles: Map>, + test: Set, + imm_singleton: PrivateImmutable, +} +``` + +For this to work, Notes must implement Serialize, Deserialize and NoteInterface Traits. Previously: + +```rust +use dep::aztec::protocol_types::address::AztecAddress; +use dep::aztec::{ + note::{ + note_header::NoteHeader, + note_interface::NoteInterface, + utils::compute_note_hash_for_read_or_nullify, + }, + oracle::{ + nullifier_key::get_nullifier_secret_key, + get_public_key::get_public_key, + }, + log::emit_encrypted_log, + hash::pedersen_hash, + context::PrivateContext, +}; + +// Shows how to create a custom note + +global CARD_NOTE_LEN: Field = 1; + +impl CardNote { + pub fn new(owner: AztecAddress) -> Self { + CardNote { + owner, + } + } + + pub fn serialize(self) -> [Field; CARD_NOTE_LEN] { + [self.owner.to_field()] + } + + pub fn deserialize(serialized_note: [Field; CARD_NOTE_LEN]) -> Self { + CardNote { + owner: AztecAddress::from_field(serialized_note[1]), + } + } + + pub fn compute_note_hash(self) -> Field { + pedersen_hash([ + self.owner.to_field(), + ],0) + } + + pub fn compute_nullifier(self, context: &mut PrivateContext) -> Field { + let note_hash_for_nullify = compute_note_hash_for_read_or_nullify(CardNoteMethods, self); + let secret = context.request_nullifier_secret_key(self.owner); + pedersen_hash([ + note_hash_for_nullify, + secret.high, + secret.low, + ],0) + } + + pub fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_read_or_nullify(CardNoteMethods, self); + let secret = get_nullifier_secret_key(self.owner); + pedersen_hash([ + note_hash_for_nullify, + secret.high, + secret.low, + ],0) + } + + pub fn set_header(&mut self, header: NoteHeader) { + self.header = header; + } + + // Broadcasts the note as an encrypted log on L1. + pub fn broadcast(self, context: &mut PrivateContext, slot: Field) { + let encryption_pub_key = get_public_key(self.owner); + emit_encrypted_log( + context, + (*context).this_address(), + slot, + encryption_pub_key, + self.serialize(), + ); + } +} + +fn deserialize(serialized_note: [Field; CARD_NOTE_LEN]) -> CardNote { + CardNote::deserialize(serialized_note) +} + +fn serialize(note: CardNote) -> [Field; CARD_NOTE_LEN] { + note.serialize() +} + +fn compute_note_hash(note: CardNote) -> Field { + note.compute_note_hash() +} + +fn compute_nullifier(note: CardNote, context: &mut PrivateContext) -> Field { + note.compute_nullifier(context) +} + +fn compute_nullifier_without_context(note: CardNote) -> Field { + note.compute_nullifier_without_context() +} + +fn get_header(note: CardNote) -> NoteHeader { + note.header +} + +fn set_header(note: &mut CardNote, header: NoteHeader) { + note.set_header(header) +} + +// Broadcasts the note as an encrypted log on L1. +fn broadcast(context: &mut PrivateContext, slot: Field, note: CardNote) { + note.broadcast(context, slot); +} + +global CardNoteMethods = NoteInterface { + deserialize, + serialize, + compute_note_hash, + compute_nullifier, + compute_nullifier_without_context, + get_header, + set_header, + broadcast, +}; +``` + +Now: + +```rust +use dep::aztec::{ + note::{ + note_header::NoteHeader, + note_interface::NoteInterface, + utils::compute_note_hash_for_read_or_nullify, + }, + oracle::{ + nullifier_key::get_nullifier_secret_key, + get_public_key::get_public_key, + }, + log::emit_encrypted_log, + hash::pedersen_hash, + context::PrivateContext, + protocol_types::{ + address::AztecAddress, + traits::{Serialize, Deserialize, Empty} + } +}; + +// Shows how to create a custom note + +global CARD_NOTE_LEN: Field = 1; + +impl CardNote { + pub fn new(owner: AztecAddress) -> Self { + CardNote { + owner, + } + } +} + +impl NoteInterface for CardNote { + fn compute_note_content_hash(self) -> Field { + pedersen_hash([ + self.owner.to_field(), + ],0) + } + + fn compute_nullifier(self, context: &mut PrivateContext) -> Field { + let note_hash_for_nullify = compute_note_hash_for_read_or_nullify(self); + let secret = context.request_nullifier_secret_key(self.owner); + pedersen_hash([ + note_hash_for_nullify, + secret.high, + secret.low, + ],0) + } + + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_read_or_nullify(self); + let secret = get_nullifier_secret_key(self.owner); + pedersen_hash([ + note_hash_for_nullify, + secret.high, + secret.low, + ],0) + } + + fn set_header(&mut self, header: NoteHeader) { + self.header = header; + } + + fn get_header(note: CardNote) -> NoteHeader { + note.header + } + + fn serialize_content(self) -> [Field; CARD_NOTE_LEN]{ + [self.owner.to_field()] + } + + fn deserialize_content(serialized_note: [Field; CARD_NOTE_LEN]) -> Self { + AddressNote { + owner: AztecAddress::from_field(serialized_note[0]), + header: NoteHeader::empty(), + } + } + + // Broadcasts the note as an encrypted log on L1. + fn broadcast(self, context: &mut PrivateContext, slot: Field) { + let encryption_pub_key = get_public_key(self.owner); + emit_encrypted_log( + context, + (*context).this_address(), + slot, + encryption_pub_key, + self.serialize(), + ); + } +} +``` + +Public state must implement Serialize and Deserialize traits. + +It is still possible to manually implement the storage initialization (for custom storage wrappers or internal types that don't implement the required traits). For the above example, the `impl Storage` section would look like this: + +```rust +impl Storage { + fn init(context: Context) -> Self { + Storage { + leader: PublicMutable::new( + context, + 1 + ), + legendary_card: PrivateMutable::new(context, 2), + profiles: Map::new( + context, + 3, + |context, slot| { + PrivateMutable::new(context, slot) + }, + ), + test: Set::new(context, 4), + imm_singleton: PrivateImmutable::new(context, 4), + } + } +} +``` + +## 0.20.0 + +### [Aztec.nr] Changes to `NoteInterface` + +1. Changing `compute_nullifier()` to `compute_nullifier(private_context: PrivateContext)` + + This API is invoked for nullifier generation within private functions. When using a secret key for nullifier creation, retrieve it through: + + `private_context.request_nullifier_secret_key(account_address)` + + The private context will generate a request for the kernel circuit to validate that the secret key does belong to the account. + + Before: + + ```rust + pub fn compute_nullifier(self) -> Field { + let secret = oracle.get_secret_key(self.owner); + pedersen_hash([ + self.value, + secret.low, + secret.high, + ]) + } + ``` + + Now: + + ```rust + pub fn compute_nullifier(self, context: &mut PrivateContext) -> Field { + let secret = context.request_nullifier_secret_key(self.owner); + pedersen_hash([ + self.value, + secret.low, + secret.high, + ]) + } + ``` + +2. New API `compute_nullifier_without_context()`. + + This API is used within unconstrained functions where the private context is not available, and using an unverified nullifier key won't affect the network or other users. For example, it's used in `compute_note_hash_and_nullifier()` to compute values for the user's own notes. + + ```rust + pub fn compute_nullifier_without_context(self) -> Field { + let secret = oracle.get_nullifier_secret_key(self.owner); + pedersen_hash([ + self.value, + secret.low, + secret.high, + ]) + } + ``` + + > Note that the `get_secret_key` oracle API has been renamed to `get_nullifier_secret_key`. + +## 0.18.0 + +### [Aztec.nr] Remove `protocol_types` from Nargo.toml + +The `protocol_types` package is now being reexported from `aztec`. It can be accessed through `dep::aztec::protocol_types`. + +```toml +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="yarn-project/aztec-nr/aztec" } +``` + +### [Aztec.nr] key type definition in Map + +The `Map` class now requires defining the key type in its declaration which _must_ implement the `ToField` trait. + +Before: + +```rust +struct Storage { + balances: Map> +} + +let user_balance = balances.at(owner.to_field()) +``` + +Now: + +```rust +struct Storage { + balances: Map> +} + +let user_balance = balances.at(owner) +``` + +### [js] Updated function names + +- `waitForSandbox` renamed to `waitForPXE` in `@aztec/aztec.js` +- `getSandboxAccountsWallets` renamed to `getInitialTestAccountsWallets` in `@aztec/accounts/testing` + +## 0.17.0 + +### [js] New `@aztec/accounts` package + +Before: + +```js +import { getSchnorrAccount } from "@aztec/aztec.js"; // previously you would get the default accounts from the `aztec.js` package: +``` + +Now, import them from the new package `@aztec/accounts` + +```js +import { getSchnorrAccount } from "@aztec/accounts"; +``` + +### Typed Addresses + +Address fields in Aztec.nr now is of type `AztecAddress` as opposed to `Field` + +Before: + +```rust +unconstrained fn compute_note_hash_and_nullifier(contract_address: Field, nonce: Field, storage_slot: Field, serialized_note: [Field; VALUE_NOTE_LEN]) -> [Field; 4] { + let note_header = NoteHeader::new(_address, nonce, storage_slot); + ... +``` + +Now: + +```rust +unconstrained fn compute_note_hash_and_nullifier( + contract_address: AztecAddress, + nonce: Field, + storage_slot: Field, + serialized_note: [Field; VALUE_NOTE_LEN] + ) -> pub [Field; 4] { + let note_header = NoteHeader::new(contract_address, nonce, storage_slot); +``` + +Similarly, there are changes when using aztec.js to call functions. + +To parse a `AztecAddress` to BigInt, use `.inner` +Before: + +```js +const tokenBigInt = await bridge.methods.token().simulate(); +``` + +Now: + +```js +const tokenBigInt = (await bridge.methods.token().simulate()).inner; +``` + +### [Aztec.nr] Add `protocol_types` to Nargo.toml + +```toml +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="yarn-project/aztec-nr/aztec" } +protocol_types = { git="https://github.com/AztecProtocol/aztec-packages/", tag="v0.88.0", directory="yarn-project/noir-protocol-circuits/crates/types"} +``` + +### [Aztec.nr] moving compute_address func to AztecAddress + +Before: + +```rust +let calculated_address = compute_address(pub_key_x, pub_key_y, partial_address); +``` + +Now: + +```rust +let calculated_address = AztecAddress::compute(pub_key_x, pub_key_y, partial_address); +``` + +### [Aztec.nr] moving `compute_selector` to FunctionSelector + +Before: + +```rust +let selector = compute_selector("_initialize((Field))"); +``` + +Now: + +```rust +let selector = FunctionSelector::from_signature("_initialize((Field))"); +``` + +### [js] Importing contracts in JS + +Contracts are now imported from a file with the type's name. + +Before: + +```js +import { TokenContract } from "@aztec/noir-contracts/types"; +``` + +Now: + +```js +import { TokenContract } from "@aztec/noir-contracts/Token"; +``` + +### [Aztec.nr] Aztec example contracts location change in Nargo.toml + +Aztec contracts are now moved outside of the `src` folder, so you need to update your imports. + +Before: + +```rust +easy_private_token_contract = {git = "https://github.com/AztecProtocol/aztec-packages/", tag ="v0.16.9", directory = "noir-projects/noir-contracts/contracts/easy_private_token_contract"} +``` + +Now, just remove the `src` folder,: + +```rust +easy_private_token_contract = {git = "https://github.com/AztecProtocol/aztec-packages/", tag ="v0.17.0", directory = "noir-projects/noir-contracts/contracts/easy_private_token_contract"} +``` diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/address.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/address.md new file mode 100644 index 000000000000..5cd2e3ac20d4 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/address.md @@ -0,0 +1,95 @@ +--- +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: + + + +:::warning +Some of these draft domain separators might be too many bits; they need to fit inside a single field element. Version numbers might not be needed until we roll the _next_ version. +::: + +```rust +address_crh( + version: Field, + salt: Field, + deployer: AztecAddress, + contract_class_id: Field, + initialization_hash: Field, + portal_contract_address: EthereumAddress, + public_keys_hash: Field, +) -> Field { + + let salted_initialization_hash: Field = poseidon2( + be_string_to_field("az_salted_initialization_hash_v1"), + + salt, + initialization_hash, + deployer.to_field(), + be_bits_to_field(portal_contract_address) + ); + + let partial_address: Field = poseidon2( + be_string_to_field("az_contract_partial_address_v1"), + + contract_class_id, + salted_initialization_hash + ); + + let address: Field = poseidon2( + be_string_to_field("az_contract_address_v1"), + + public_keys_hash, + partial_address + ); + + address +} +``` + +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). For example: + +```rust +let public_keys_hash: Field = poseidon2( + be_string_to_field("az_public_keys_hash"), // TODO: does this need some unique ID, to disambiguate from other approaches people might have for other public keys? + + 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 +); +``` + +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](../pre-compiled-contracts/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/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/diversified-and-stealth.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/diversified-and-stealth.md new file mode 100644 index 000000000000..51a274e12b44 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/diversified-and-stealth.md @@ -0,0 +1,47 @@ +--- +title: Diversified and Stealth Accounts +--- + +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 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. + +``` +contract DiversifiedAccount + + private fn entrypoint(payload: action[]) + assert msg_sender == get_owner_address() + execute(payload) + + private fn is_valid(message_hash: Field) + return get_owner_address().is_valid(message_hash) + + internal private get_owner_address() + let address_preimage = pxe.get_address_preimage(this) + assert hash(address_preimage) == this + return address_preimage.deployer_address +``` + +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/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/example-usage/diversified-and-stealth-keys.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/example-usage/diversified-and-stealth-keys.md new file mode 100644 index 000000000000..fed3329e8959 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/example-usage/diversified-and-stealth-keys.md @@ -0,0 +1,64 @@ +## 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. + +Basically, Alice must personally derive and provide Bob and Charlie with random-looking addresses (for Alice). Because Alice is the one deriving these Diversified Addresses (they can _only_ be derived by Alice), if Bob and Charlie chose to later collude, they would not be able to convince each-other that they'd interacted with Alice. + +This is not to be confused with 'Stealth Addresses', which 'flip' who derives: Bob and Charlie would each derive a random-looking Stealth Address for Alice. Alice would then discover her new Stealth Addresses through decryption. + +> All of the key information below is Alice's + +Alice derives a 'diversified' incoming viewing public key, and sends it to Bob: + + +| Thing | Derivation | Name | Comments | +|---|---|---|---| +$\d$ | $\stackrel{rand}{\leftarrow} \mathbb{F}$ |diversifier | +$\Gd$ | $\d \cdot G$ | diversified generator | +$\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. + +## Deriving stealth public keys + +> All of the key information below is Alice's + +Stealth Public Keys are used for generating Stealth Addresses. For Bob to derive a Stealth Address for Alice, Bob derives: + + +| Thing | Derivation | Name | Comments | +|---|---|---|---| +$\d$ | Given by Alice | (Diversifier) | Remember, in most cases, $\d=1$ is sufficient. +$\Gd$ | $\d \cdot G$ | (Diversified) generator | Remember, when $\d = 1$, $\Gd = G$. +$\esk_{stealth}$ | $\stackrel{rand}{\leftarrow} \mathbb{F}$ | ephemeral secret, for deriving the stealth key shared secret | +$\Epkd,_{stealth}$ | $\esk_{stealth} \cdot \Gd$ | (Diversified) Ephemeral public key, for deriving the stealth key shared secret | +$\sharedsecret_{m, stealth}$ | $\esk_{stealth} \cdot \Ivpkmd$ | Stealth key shared secret | +$\hstealth$ | $\text{pos2}(\text{``az\_stealth\_key''}, \sharedsecret_{m, stealth})$ | stealth key | +$\Ivpkmdstealth$ | $\hstealth \cdot \Gd + \Ivpkmd$ | (Diversified) Stealth viewing public key | + +Having derived a Stealth Address for Alice, Bob can now share it with Alice as follows: + + +| Thing | Derivation | Name | Comments | +|---|---|---|---| +$\tagg_{m, i}^{Bob \rightarrow Alice}$ | See earlier in this doc. | | Derive the next tag in the $Bob\rightarrow Alice$ sequence.
Note: we illustrate with a _master_ tag sequence, but an app-specific tag sequence could also be used (in which case an encryption of the app_address in a ciphertext header wouldn't be required; it could just be inferred from the tag used). | +$\esk_{header}$ | $\stackrel{rand}{\leftarrow} \mathbb{F}$ | ephemeral secret key, for deriving the ciphertext header shared secret | +$\Epkd,_{header}$ | $\esk_{header} \cdot \Gd$ | (Diversified) Ephemeral public key, for deriving the ciphertext header shared secret | +$\sharedsecret_{m,header}$ | $\esk_{header} \cdot \Ivpkm$ | Ciphertext header shared secret | TODO: we might need to use a different ephemeral keypair from the one used to derive the stealth address. | +$\hmencheader$ | $\text{pos2}(\text{``az\_enc\_key''}, \sharedsecret_{m,header})$ | ciphertext header encryption key +$\ciphertextheader$ | $\text{encrypt}^{\Ivpkm}_{\hmencheader}$(app\_address) | | TODO: diversify this? | +$\payload$ | [ $\tagg_{m, i}^{Bob \rightarrow Alice}$, $\Epkd,_{header}$, $\ciphertextheader$, $\Epkd,_{stealth}$ ] | + +Alice can learn about her new Stealth Address as follows. First, she would identify the transaction has intended for her, either by observing $\tagg_{m, i}^{Bob \rightarrow Alice}$ on-chain herself (and then downloading the rest of the payload which accompanies the tag), or by making a privacy-preserving request to a server, to retrieve the payload which accompanies the tag. Assuming the $\payload$ has been identified as Alice's, we proceed: + + +| Thing | Derivation | Name | +|---|---|---| +$\sharedsecret_{m,header}$ | $\ivskm \cdot \Epkd,_{header}$ | Ciphertext header shared secret | +$\hmencheader$ | $\text{pos2}(\text{``az\_enc\_key''}, \sharedsecret_{m,header})$ | ciphertext header encryption key | +app_address | $\text{decrypt}_{\hmencheader}^{\ivskm}(\ciphertextheader)$ | +$\ivskm$ | See derivations above. Use the decrypted app_address in the derivation. | app-specific incoming viewing secret key | +$\sharedsecret_{m, stealth}$ | $\ivskm \cdot \Epkd,_{stealth}$ | Stealth key shared secret | +$\hstealth$ | $\text{pos2}(\text{``az\_stealth\_key''}, \sharedsecret_{m, stealth})$ | stealth key | +$\ivskmstealth$ | $\hstealth + \ivskm$ | +$\Ivpkmdstealth$ | $\ivskmstealth \cdot \Gd$ | (Diversified) Stealth viewing public key | diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/example-usage/encrypt-and-tag.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/example-usage/encrypt-and-tag.md new file mode 100644 index 000000000000..c0b1b390fd7f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/example-usage/encrypt-and-tag.md @@ -0,0 +1,114 @@ +## Encrypt and tag an incoming message + +Bob wants to send Alice a private message, e.g. the contents of a note, which we'll refer to as the $\plaintext$. Bob and Alice are using a "tag hopping" scheme to help with note discovery. Let's assume they've already handshaked to establish a shared secret $\sharedsecret_{m,tagging}^{Bob \rightarrow Alice}$, from which a sequence of tags $\tagg_{m,i}^{Bob \rightarrow Alice}$ can be derived. + + +| Thing | Derivation | Name | Comments | +|---|---|---|---| +$\d$ | Given by Alice | (Diversifier) | Remember, in most cases, $\d=1$ is sufficient. +$\Gd$ | $\d \cdot G$ | (Diversified) generator | Remember, when $\d = 1$, $\Gd = G$. +$\eskheader$ | $\stackrel{rand}{\leftarrow} \mathbb{F}$ | ephemeral secret key | +$\Epkdheader$ | $\eskheader \cdot \Gd$ | (Diversified) Ephemeral public key | +$\sharedsecret_{m,header}$ | $\esk_{header} \cdot \Ivpkm$ | Shared secret, for ciphertext header encryption | TODO: can we use the same ephemeral keypair for both the ciphertext header and the ciphertext?
TODO: diversify the $\Ivpkm$? | +$\hmencheader$ | h("?", $\sharedsecret_{m,header}$) | Ciphertext header encryption key | +$\ciphertextheader$ | $enc^{\Ivpkm}_{\hmencheader}$(app\_address) | Ciphertext header | | +||||| +$\esk$ | $\stackrel{rand}{\leftarrow} \mathbb{F}$ | ephemeral secret key | +$\Epkd$ | $\esk \cdot \Gd$ | (Diversified) Ephemeral public key | +$\sharedsecret_{app,enc}$ | $\esk \cdot \Ivpkdstealth$ | Shared secret, for ciphertext encryption | +$\happenc$ | h("?", $\sharedsecret_{app,enc}$) | Incoming data encryption key | +$\ciphertext$ | $enc^{\Ivpkdstealth}_{\happenc}(\plaintext)$ | Ciphertext | +$\payload$ | [$\tagg_{m, i}^{Bob \rightarrow Alice}$, $\ciphertextheader$, $\ciphertext$, $\Epkdheader$, $\Epkd$] | Payload | + + + +Alice can learn about her new $\payload$ as follows. First, she would identify the transaction has intended for her, either by observing $\tagg_{m, i}^{Bob \rightarrow Alice}$ on-chain herself (and then downloading the rest of the payload which accompanies the tag), or by making a privacy-preserving request to a server, to retrieve the payload which accompanies the tag. Assuming the $\payload$ has been identified as Alice's, and retrieved by Alice, we proceed. + +> Given that the tag in this illustration was derived from Alice's master key, the tag itself doesn't convey which app_address to use, to derive the correct app-siloed incoming viewing secret key that would enable decryption of the ciphertext. So first Alice needs to decrypt the $\ciphertextheader$ using her master key: + + +| Thing | Derivation | Name | +|---|---|---| +$\sharedsecret_{m,header}$ | $\ivskm \cdot \Epkdheader$ | Shared secret, for encrypting the ciphertext header | +$\hmencheader$ | h("?", $\sharedsecret_{m,header}$) | Incoming encryption key | +app_address | $decrypt_{\hmencheader}^{\ivskm}(\ciphertextheader)$ | App address | +|||| +$\ivskstealth$ | See derivations above. Use the decrypted app_address. | Incoming viewing secret key | +$\sharedsecret_{app, enc}$ | $\ivskstealth \cdot \Epkd$ | Shared secret, for ciphertext encryption | +$\happenc$ | h("?", $\sharedsecret_{app, enc}$) | Ciphertext encryption key | +$\plaintext$ | $decrypt_{\happenc}^{\ivskstealth}(\ciphertext)$ | Plaintext | + +## Encrypt and tag an outgoing message + +Bob wants to send himself a private message (e.g. a record of the outgoing notes that he's created for other people) which we'll refer to as the $\plaintext$. Let's assume Bob has derived a sequence of tags $\tagg_{m,i}^{Bob \rightarrow Alice}$ for himself (see earlier). + +> Note: this illustration uses _master_ keys for tags, rather than app-specific keys for tags. App-specific keys for tags could be used instead, in which case a 'ciphertext header' wouldn't be needed for the 'app_address', since the address could be inferred from the tag. + +> Note: rather than copying the 'shared secret' approach of Bob sending to Alice, we can cut a corner (because Bob is the sender and recipient, and so knows his own secrets). + +> Note: if Bob has sent a private message to Alice, and he also wants to send himself a corresponding message: +> +> - he can likely re-use the ephemeral keypairs for himself. +> - he can include $\esk$ in the plaintext that he sends to himself, as a way of reducing the size of his $\ciphertext$ (since the $\esk$ will enable him to access all the information in the ciphertext that was sent to Alice). + + + +> Note: the violet symbols should actually be orange here. + + +| Thing | Derivation | Name | Comments | +|---|---|---|---| +$\d$ | Given by Alice | (Diversifier) | Remember, in most cases, $\d=1$ is sufficient. +$\Gd$ | $\d \cdot G$ | (Diversified) generator | Remember, when $\d = 1$, $\Gd = G$. +$\eskheader$ | $\stackrel{rand}{\leftarrow} \mathbb{F}$ | ephemeral secret key | +$\Epkdheader$ | $\eskheader \cdot \Gd$ | (Diversified) Ephemeral public key | +$\hmencheader$ | h("?", $\ovskm$, $\Epkdheader$) | Header encryption key | This uses a master secret key $\ovskm$, which MUST NOT be given to an app nor to an app circuit. However, it can still be given to a trusted precompile, which can handle this derivation securely. | +$\ciphertextheader$ | $enc_{\hmencheader}$(app\_address) | Ciphertext header encryption key | | +||||| +$\esk$ | $\stackrel{rand}{\leftarrow} \mathbb{F}$ | ephemeral secret key | +$\Epkd$ | $\esk \cdot \Gd$ | (Diversified) Ephemeral public key | +$\happenc$ | h("?", $\ovskapp$, $\Epkd$) | Outgoing data encryption key | Since $\ovskapp$ is a _hardened_ app-siloed secret key, it may be safely given to the dapp or passed into the app's circuit. | +$\ciphertext$ | $enc_{\happenc}(\plaintext)$ | Ciphertext | +$\payload$ | [$\tagg_{m, i}^{Bob \rightarrow Bob}$, $\ciphertextheader$, $\ciphertext$, $\Epkdheader$, $\Epkd$] | Payload | + +Alice can learn about her new $\payload$ as follows. First, she would identify the transaction has intended for her, either by observing $\tagg_{m, i}^{Bob \rightarrow Alice}$ on-chain herself (and then downloading the rest of the payload which accompanies the tag), or by making a privacy-preserving request to a server, to retrieve the payload which accompanies the tag. Assuming the $\payload$ has been identified as Alice's, and retrieved by Alice, we proceed. + +> Given that the tag in this illustration was derived from Alice's master key, the tag itself doesn't convey which app_address to use, to derive the correct app-siloed incoming viewing secret key that would enable decryption of the ciphertext. So first Alice needs to decrypt the $\ciphertextheader$ using her master key: + + +| Thing | Derivation | Name | +|---|---|---| +$\hmencheader$ | h("?", $\ovskm$, $\Epkdheader$) | | +app_address | $decrypt_{\hmencheader}(\ciphertextheader)$ | +|||| +$\ovskapp$ | See derivations above. Use the decrypted app_address. | | +$\happenc$ | h("?", $\ovskm$, $\Epkd$) | +$\plaintext$ | $decrypt_{\happenc}(\ciphertext)$ | + + + +### Doing this inside an app circuit + +Here's how an app circuit could constrain the app-siloed outgoing viewing secret key ($\ovskapp$) to be correct: + +The app circuit exposes, as public inputs, an "outgoing viewing key validation request": + + +| Thing | Derivation | Name | Comments | +|---|---|---|---| +`outgoing_viewing_key_validation_request` | app_address: app_address,
hardened_child_sk: $\nskapp$,
claimed_parent_pk: $\Npkm$ | + +The kernel circuit can then validate the request (having been given $\ovskm$ as a private input to the kernel circuit): + + +| Thing | Derivation | Name | Comments | +|---|---|---|---| +$\ovskapp$ | $h(\ovskm, \text{app\_address})$ | +$\Ovpkm$ | $\ovskm \cdot G$ | Outgoing viewing public key | +| | | | Copy-constrain $\ovskm$ with $\ovskm$. | + +If the kernel circuit succeeds in these calculations, then the $\ovskapp$ has been validated as the correct app-siled secret key for $\Ovpkm$. + +## Encrypt and tag an internal incoming message + +Internal incoming messages are handled analogously to outgoing messages, since in both cases the sender is the same as the recipient, who has access to the secret keys when encrypting and tagging the message. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/example-usage/nullifier.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/example-usage/nullifier.md new file mode 100644 index 000000000000..5c3419d8fbec --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/example-usage/nullifier.md @@ -0,0 +1,66 @@ +## Deriving a nullifier within an app contract + +Let's assume a developer wants a nullifier of a note to be derived as: + +`nullifier = h(note_hash, nullifier_key);` + +... where the `nullifier_key` ($\Nkapp$) belongs to the 'owner' of the note, and where the 'owner' is some $\address$. + +Here's example for how an app circuit _could_ constrain the nullifier key to be correct: + +### Diagram + +It's easiest to take a look at this first: + +![Alt text](/img/protocol-specs/addresses-and-keys/image.png) + +### Within the app circuit + +Within the app, we can prove links between: + +- the user's [$\nskapp$](../keys.md#app-siloed-nullifier-secret-key) and their [$\Nkapp$](../keys.md#app-siloed-nullifier-key); and between +- the user's [$\Npkm$](../keys.md#master-nullifier-public-key) and their [$\address$](../address.md). + +The link that's missing is to prove that $\Npkm$ relates to $\nskapp$. To compute this missing link requires the $\nskm$, which MUST NOT be passed into an app circuit, and may only be passed into a kernel circuit. See the next ['Within the kernel circuit'](#within-the-kernel-circuit) section for details of this logic. + +#### The logic + +$$ +\begin{aligned} +\Nkapp &= \text{poseidon2}(\nskapp) \\ +\text{nullifier} &= \text{poseidon2}(\text{note\_hash}, \Nkapp) \\ +\text{public\_keys\_hash} &= \text{poseidon2}(\text{be\_string\_to\_field}(``\text{az\_public\_keys\_hash}"), \Npkm, \Tpkm, \Ivpkm, \Ovpkm) \\ +\address &= \text{poseidon2}(\text{be\_string\_to\_field}(``\text{az\_contract\_address\_v1}"), \text{public\_keys\_hash}, \text{partial\_address}) +\end{aligned} +$$ + +> Note: the passing of points directly into the poseidon function is lazy notation: the keys would need to be serialized appropriately as fields into the poseidon function. + +> Recall an important point: the app circuit MUST NOT be given $\nskm$. Indeed, $\nskapp$ is derived (see earlier) as a _hardened_ child of $\nskm$, to prevent $\nskm$ from being reverse-derived by a malicious circuit. The linking of $\nskapp$ to $\nskm$ is deferred to the kernel circuit (which can be trusted moreso than an app). + +> Recall also: $\Nkapp$ is used (instead of $\nskapp$) solely as a way of giving the user the option of sharing $\Nkapp$ with a trusted 3rd party, to give them the ability to view when a note has been nullified (although I'm not sure how useful this is, given that it would require brute-force effort from that party to determine which note hash has been nullified, with very little additional information). + +The app circuit exposes, as public inputs, a "nullifier key validation request": + +```rust +let nullifier_validation_request = KeyValidationRequest { + app_address: app_address, + claimed_hardened_child_sk: nsk_app, + claimed_parent_pk: Npk_m, +} +``` + +### Within the Kernel Circuit + +The kernel circuit can then validate the request (having been given $\nskm$ as a private input to the kernel circuit): + +$$ +\begin{aligned} +\nskapp &= \text{derive\_hardened\_app\_siloed\_secret\_key}(\text{``az\_nsk\_app"}, \text{app\_address}, \nskm) \\ +\Npkm &= \nskm \cdot G \\ +\nskapp &== \text{claimed\_hardened\_child\_sk} \\ +\Npkm &== \text{claimed\_parent\_pk} \\ +\end{aligned} +$$ + +If the kernel circuit succeeds in these calculations, then the $\Nkapp$ has been validated as having a known secret key, and belonging to the $\address$. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/example-usage/tag-sequence-derivation.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/example-usage/tag-sequence-derivation.md new file mode 100644 index 000000000000..af5bc4af165c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/example-usage/tag-sequence-derivation.md @@ -0,0 +1,59 @@ +# Handshaking for tag-hopping + +Deriving a sequence of tags for tag-hopping. + +## Deriving a sequence of tags between Alice and Bob across all apps + +For Bob to derive a shared secret for Alice: + + +| Thing | Derivation | Name | Comments | +|---|---|---|---| +$\esk_{hs}$ | $\stackrel{rand}{\leftarrow} \mathbb{F}$ | ephemeral secret key, for handshaking | $hs$ = handshake. +$\Epk_{hs}$ | $\esk_{hs} \cdot G$ | Ephemeral public key, for handshaking | +$\sharedsecret_{m,tagging}^{Bob \rightarrow Alice}$ | $\esk_{hs} \cdot \Ivpkm$ | Shared secret, for tagging | Here, we're illustrating the derivation of a shared secret (for tagging) using _master_ keys. + +Having derived a Shared Secret, Bob can now share it with Alice as follows: + + +| Thing | Derivation | Name | Comments | +|---|---|---|---| +$\Taghs$ | $\esk_{hs} \cdot \Tpkm$ | Handshake message identification tag | Note: the tagging public key $\Tpkm$ exists as an optimization, seeking to make brute-force message identification as fast as possible. In many cases, handshakes can be performed offchain via traditional web2 means, but in the case of on-chain handshakes, we have no preferred alternative over simply brute-force attempting to reconcile every 'Handshake message identification tag'. Note: this optimization reduces the recipient's work by 1 cpu-friendly hash per message (at the cost of 255-bits to broadcast a compressed encoding of $\Taghs$). We'll need to decide whether this is the right speed/communication trade-off. | +$\payload$ | [$\Taghs$, $\Epk_{hs}$] | Payload | This can be broadcast via L1.
Curve points can be compressed in the payload. | + +Alice can identify she is the intended the handshake recipient as follows: + + +| Thing | Derivation | Name | Comments | +|---|---|---|---| +$\Taghs$ | $\tskm \cdot \Epk_{hs}$ | Handshake message identification tag | Alice can extract $\Taghs$ and $\Epk_{hs}$ from the $\payload$ and perform this scalar multiplication on _every_ handshake message. If the computed $\Taghs$ value matches that of the $\payload$, then the message is indented for Alice.
Clearly, handshake transactions will need to be identifiable as such (to save Alice time), e.g. by revealing the contract address of some canonical handshaking contract alongside the $\payload$.
Recall: this step is merely an optimization, to enable Alice to do a single scalar multiplication before moving on (in cases where she is not the intended recipient). | + +If Alice successfully identifies that she is the indended the handshake recipient, she can proceed with deriving the shared secret (for tagging) as follows: + + +| Thing | Derivation | Name | Comments | +|---|---|---|---| +$\sharedsecret_{m,tagging}^{Bob \rightarrow Alice}$ | $\ivskm \cdot \Epk_{hs}$ | Shared secret, for tagging | | + +A sequence of tags can then be derived by both Alice and Bob as: + + +| Thing | Derivation | Name | Comments | +|---|---|---|---| +$\tagg_{m,i}^{Bob \rightarrow Alice}$ | $\text{pos2}(\text{``az\_tag\_ss\_m''}, \sharedsecret_{m,tagging}^{Bob \rightarrow Alice}, i)$ | The i-th tag in the sequence. | | + +This tag can be used as the basis for note retreival schemes. Each time Bob sends Alice a $\ciphertext$, he can attach the next unused $\tagg_{m,i}^{Bob \rightarrow Alice}$ in the sequence. Alice - who is also able to derive the next $\tagg_{m,i}^{Bob \rightarrow Alice}$ in the sequence - can make privacy-preserving calls to a server, requesting the $\ciphertext$ associated with a particular $\tagg_{m,i}^{Bob \rightarrow Alice}$. + +> The colour key isn't quite clear for $\tagg_{m,i}^{Bob \rightarrow Alice}$. It will be a publicly-broadcast piece of information, but no one should learn that it relates to Bob nor Alice (except perhaps some trusted 3rd party whom Alice has entrusted with her $\ivskm$). + + + +## Deriving a sequence of tags from Bob to himself across all apps + +The benefit of Bob deriving a sequence of tags for himself, is that he can re-sync his _outgoing_ transaction data more quickly, if he ever needs to in future. + +This can be done by either: + +- Copying the approach used to derive a sequence of tags between Bob and Alice (but this time do it between Bob and Bob, and use Bob's outgoing keys). +- Generating a very basic sequence of tags $\tagg_{app, i}^{Bob \rightarrow Bob} = \text{pos2}(\text{``az\_tag\_ovsk\_app''}, \ovskapp, i)$ (at the app level) and $\tagg_{m, i}^{Bob \rightarrow Bob} = \text{pos2}(\text{``az\_tag\_ovsk\_m''}, \ovskm, i)$ (at the master level). + - 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_. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/index.md new file mode 100644 index 000000000000..886c6bb09bb0 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/index.md @@ -0,0 +1,21 @@ +--- +title: Addresses and Keys +--- +import DocCardList from '@theme/DocCardList'; + +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 enshrined encryption and tagging mechanisms, that can be leveraged by applications as they interact with accounts. + +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/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/keys-requirements.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/keys-requirements.md new file mode 100644 index 000000000000..307f04dc0830 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/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 be 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 [archive](./../state/archive.md) will enable a user to prove many things about their transaction history, including historical encrypted 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 sequence 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/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/keys.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/keys.md new file mode 100644 index 000000000000..ee1c6e62a76f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/keys.md @@ -0,0 +1,305 @@ +--- +title: Default Keys Specification +description: Specification for default privacy keys format and derivation, and nullifier derivation. +--- + +## Cheat Sheet + +import Image from "@theme/IdealImage"; + +The protocol does not enforce the usage of any of the following keys, and does not enforce the keys to conform to a particular length or algorithm. Users are expected to pick a set of keys valid for the encryption and tagging precompile they choose for their account. + + +| Cat. | Key | Derivation | Link | +| ------------------------------- | ---------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| Seed | $\seed$ | $$\stackrel{\$}{\leftarrow} \mathbb{F}$$ | [Seed](#seed) | +| | $\sk$ | $$\stackrel{\$}{\leftarrow} \mathbb{F}$$ | [Master Secret Key](#master-secret-key) | +| | | | | +| Master Secret Keys | $\nskm$ | $\text{poseidon2}(\text{``az\_nsk\_m''}, \sk)$ | [Master Nullifier Secret Key](#master-nullifier-secret-key) | +| | $\ovskm$ | $\text{poseidon2}(\text{``az\_ovsk\_m''}, \sk)$ | [Master Outgoing Viewing Secret Key](#master-outgoing-viewing-secret-key) | +| | $\ivskm$ | $\text{poseidon2}(\text{``az\_ivsk\_m''}, \sk)$ | [Master Incoming Viewing Secret Key](#master-incoming-viewing-secret-key) | +| | $\tskm$ | $\text{poseidon2}(\text{``az\_tsk\_m''}, \sk)$ | [Master Tagging Secret Key](#master-tagging-secret-key) | +| | | | | +| Master Public Keys | $\Npkm$ | $\nskm \cdot G$ | [Master Nullifier Public Key](#master-nullifier-public-key) | +| | $\Ovpkm$ | $\ovskm \cdot G$ | [Master Outgoing Viewing Public Key](#master-outgoing-viewing-public-key) | +| | $\Ivpkm$ | $\ivskm \cdot G$ | [Master Incoming Viewing Public Key](#master-incoming-viewing-public-key) | +| | $\Tpkm$ | $\tskm \cdot G$ | [Master Tagging Public Key](#master-tagging-public-key) | +| | | | +| Hardened App-Siloed Secret Keys | $\nskapp$ | $\text{poseidon2}(\text{``az\_nsk\_app''}, \text{app\_address}, \nskm)$ | [Hardened, App-siloed Nullifier Secret Key](#app-siloed-nullifier-secret-key) | +| | $\ovskapp$ | $\text{poseidon2}(\text{``az\_ovsk\_app''}, \text{app\_address}, \ovskm)$ | [Hardened, App-siloed Outgoing Viewing Secret Key](#app-siloed-outgoing-viewing-secret-key) | +| | | | | +| Other App-siloed Keys | $\Nkapp$ | $\text{poseidon2}(\text{``az\_nk\_app''}, \nskapp)$ | [App-siloed Nullifier Key](#app-siloed-nullifier-key) | + + +## Colour Key + +- $\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? +- $\color{orange}{orange}$ = Secret information. A user MAY elect to share this information with a _trusted_ 3rd party, but it MUST NOT be shared with the wider world. +- $\color{violet}{violet}$ = Secret information. Information that is shared between a sender and recipient (and possibly with a 3rd party who is entrusted with viewing rights by the recipient). + +## Diagrams + + + +:::danger +Diagram is out of date vs the content on this page +::: + + + +The red boxes are uncertainties, which are explained later in this doc. + +## Preliminaries + +$\mathbb{F}_r$ denotes the AltBN254 scalar field (i.e. the Grumpkin base field). + +$\mathbb{F}_q$ denotes the AltBN254 base field (i.e. the Grumpkin scalar field). + +Let $\mathbb{G}_{\text{Grumpkin}}$ be the Grumpkin elliptic curve group ($E(\mathbb{F}_r)$). + +Let $G \in \mathbb{G}_{\text{Grumpkin}}$ be a generator point for the public key cryptography outlined below. TODO: decide on how to generate this point. + +Elliptic curve operators $+$ and $\cdot$ are used to denote addition and scalar multiplication, respectively. + +$\text{poseidon2}: \mathbb{F}_r^t \rightarrow \mathbb{F}$ is the Poseidon2 hash function (and $t$ can take values as per the [Poseidon2 spec](https://eprint.iacr.org/2023/323.pdf)). + +Note that $q > r$. Below, we'll often define secret keys as an element of $\mathbb{F}_r$, as this is most efficient within a snark circuit. We'll then use such secret keys in scalar multiplications with Grumpkin points ($E(\mathbb{F}_r)$ whose affine points are of the form $\mathbb{F}_r \times \mathbb{F}_r$). Strictly speaking, such scalars in Grumpkin scalar multiplication should be in $\mathbb{F}_q$. +A potential consequence of using elements of $\mathbb{F}_r$ as secret keys could be that the resulting public keys are not uniformly-distributed in the Grumpkin group, so we should check this. The distribution of such public keys will have a statistical distance of $\frac{2(q - r)}{q}$ from uniform. It turns out that $\frac{1}{2^{126}} < \frac{2(q - r)}{q} < \frac{1}{2^{125}}$, so the statistical distance from uniform is broadly negligible, especially considering that the AltBN254 curve has fewer than 125-bits of security. + +## Key Derivation + +### Derive Master Secret Key from Secret Key + +$$ +\begin{aligned} +&\text{derive\_master\_secret\_key\_from\_secret\_key}: \text{string} \times \mathbb{F}_r \to \mathbb{F}_r \\ +&\text{derive\_master\_secret\_key\_from\_secret\_key}(\text{domain\_separator\_string}, \text{secret\_key}) \\ +&:= \text{poseidon2}(\text{be\_string\_to\_field(domain\_separator\_string)}, \text{secret\_key}) +\end{aligned} +$$ + +> Note: Here, $\text{poseidon2}$ is assumed to be a pseudo-random function. + +### Derive Hardened App-siloed Secret Key + +$$ +\begin{aligned} +&\text{derive\_hardened\_app\_siloed\_secret\_key}: \text{string} \times \mathbb{F}_r \times \mathbb{F}_r \to \mathbb{F}_r \\ +&\text{derive\_hardened\_app\_siloed\_secret\_key}(\text{domain\_separator\_string}, \text{app\_address}, \text{parent\_secret\_key}) \\ +&:= \text{poseidon2}(\text{be\_string\_to\_field(domain\_separator\_string)}, \text{app\_address}, \text{parent\_secret\_key}) +\end{aligned} +$$ + +> Note: Here, $\text{poseidon2}$ is assumed to be a pseudo-random function. + +> Note: this deviates significantly from the 'conventional' [BIP-32 style method for deriving a "hardened child secret key"](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#private-parent-key--private-child-key), to reduce complexity and as an optimization. Such a deviation will need to be validated as secure. +> In particular: +> +> - the notion of a "chain code" has been removed; +> - the notion of an "index" has been replaced by an app_address; +> - HMAC-SHA512 has been replaced with Poseidon2. Note: we don't need a 512-bit output, since we've removed the notion of a "chain code", and so we don't need to split the output of the Poseidon2 function into two outputs. + +### Derive Public Key (from Secret Key) + +$$ +\begin{aligned} +&\text{derive\_public\_key}: \mathbb{F}_r \to \mathbb{G}_{\text{Grumpkin}} \\ +&\text{derive\_public\_key}(\text{secret\_key}) := \text{secret\_key} \cdot G +\end{aligned} +$$ + +## Seed + +A seed secret from which all of a user's other keys may be derived. The $\seed$ can live on an offline device, such as a hardware wallet. + +$$\seed \stackrel{\$}{\leftarrow} \mathbb{F}_r$$ + +## Master Secret Key + +This $\sk$ must never enter a circuit. A user or wallet may wish to derive this $\sk$ from a cold wallet [$\seed$](#seed). + +$$\sk \stackrel{\$}{\leftarrow} \mathbb{F}_r$$ + +> Note: Often $\sk = hash(\seed)$ for some hardware-wallet-supported hash function, would be recommended. Although, care would need to be taken if the hardware wallet doesn't support hashing directly to $\mathbb{F}_r$, since a truncated hash output could be non-uniformly distributed in $\mathbb{F}_r$. +> For example, if the hardware wallet only supports sha256, then it would not be acceptable to compute $\sk$ as $\text{sha256}(\seed) \mod r$, since the resulting output (of reducing a 256-bit number modulo $r$) would be biased towards smaller values in $\mathbb{F_r}$. More uniformity might be achieved by instead computing $\sk$ as $( \text{sha256}(\seed, 1) || \text{sha256}(\seed, 2) ) \mod r$, for example, as a modulo reduction of a 512-bit number is closer to being uniformly distributed in $\mathbb{F_r}$. +> This note is informal, and expert advice should be sought before adopting this approach. + +## Nullifier Keys + +[App-siloed Nullifier Keys](#app-siloed-nullifier-key) can be used by app developers when deriving their apps' nullifiers. By inserting some secret nullifier key into a nullifier's preimage, it makes the resulting nullifier look random, meaning observers cannot determine which note has been nullified. + +> Note that not all nullifiers will require a secret key in their computation, e.g. plume nullifiers, or state variable initialization nullifiers. But the keys outlined in this section should prove useful to many app developers. + +### Master Nullifier Secret Key + +$$ +\begin{aligned} +& \nskm \in \mathbb{F}_r \\ +& \nskm = \text{derive\_master\_secret\_key\_from\_secret\_key}(\text{``az\_nsk\_m''}, \seed) +\end{aligned} +$$ + +> See [`derive_master_secret_key_from_secret_key`](#derive-master-secret-key-from-secret-key). + +> $\nskm$ MUST NOT enter an app circuit. +> $\nskm$ MAY enter the kernel circuit. + +### Master Nullifier Public Key + +The Master Nullifier Public Key is only included so that other people can derive the user's address from some public information (i.e. from $\Npkm$), in such a way that $\nskm$ is tied to the user's address. + +$$ +\begin{aligned} +& \Npkm \in \mathbb{G}_{\text{Grumpkin}} \\ +& \Npkm = \text{derive\_public\_key}(\nskm) +\end{aligned} +$$ + +> See [`derive_public_key`](#derive-public-key-from-secret-key). + +### App-siloed Nullifier Secret Key + +The App-siloed Nullifier Secret Key is a **hardened** child key, and so is only derivable by the owner of the master nullifier secret key. It is hardened so as to enable the $\nskapp$ to be passed into an app circuit, without the threat of $\nskm$ being reverse-derivable by a malicious app. Only when an app-siloed public key needs to be derivable by the general public is a normal (non-hardened) key derivation scheme used. + +$$ +\begin{aligned} +& \nskapp \in \mathbb{F}_r \\ +& \nskapp = \text{derive\_hardened\_app\_siloed\_secret\_key}(\text{``az\_nsk\_app''}, \text{app\_address}, \nskm) +\end{aligned} +$$ + +> See [`derive_hardened_app_siloed_secret_key`](#derive-hardened-app-siloed-secret-key). + +### App-siloed Nullifier Key + +If an app developer thinks some of their users might wish to have the option to enable some _trusted_ 3rd party to see when a particular user's notes are nullified, then this nullifier key might be of use. This $\Nkapp$ can be used in a nullifier's preimage, rather than $\nskapp$ in such cases, to enable said 3rd party to brute-force identify nullifications. + +> Note: this key can be optionally shared with a trusted 3rd party, and they would not be able to derive the user's secret keys. +> Note: knowledge of this key enables someone to identify when an emitted nullifier belongs to the user, and to identify which note hashes have been nullified. +> Note: knowledge of this key would not enable a 3rd party to view the contents of any notes; knowledge of the $\ivsk$ / $\ovskapp$ would be needed for that. +> Note: this is intentionally not named as a "public" key, since it must not be shared with the wider public. + +$$ +\begin{aligned} +& \Nkapp \in \mathbb{F}_r \\ +& \Nkapp = \text{poseidon2}(\text{``az\_nk\_app''}, \nskapp) +\end{aligned} +$$ + +:::warning TODO +We could also have derived $\Nkapp$ as $\nskapp \cdot G$, but this would have resulted in a Grumpkin point, which is more cumbersome to insert into the preimage of a nullifier. We might still change our minds to adopt this scalar-multiplication approach, since it might enable us to prove knowledge of $\nskm$ to the app circuit without having to add key derivation logic to the kernel circuit. +::: + +## Outgoing Viewing Keys + +[App-siloed Outgoing Viewing Secret Keys](#app-siloed-outgoing-viewing-secret-key) can be used to derive ephemeral symmetric encryption keys, which can then be used to encrypt/decrypt data which _the user has created for their own future consumption_. I.e. these keys are for decrypting "outgoing" data from the pov of a sender. This is useful if the user's DB is wiped, and they need to sync from scratch (starting with only $\seed$). + +### Master Outgoing Viewing Secret Key + +$$ +\begin{aligned} +& \ovskm \in \mathbb{F}_r \\ +& \ovskm = \text{derive\_master\_secret\_key\_from\_seed}(\text{``az\_ovsk\_m''}, \seed) +\end{aligned} +$$ + +> See [`derive_master_secret_key_from_seed`](#derive-master-secret-key-from-seed). + +> $\ovskm$ MUST NOT enter an app circuit. +> $\ovskm$ MAY enter the kernel circuit. + +### Master Outgoing Viewing Public Key + +The Master Outgoing Viewing Public Key is only included so that other people can derive the user's address from some public information (i.e. from $\Ovpkm$), in such a way that $\ovskm$ is tied to the user's address. + +$$ +\begin{aligned} +& \Ovpkm \in \mathbb{G}_{\text{Grumpkin}} \\ +& \Ovpkm = \text{derive\_public\_key}(\ovskm) +\end{aligned} +$$ + +> See [`derive_public_key`](#derive-public-key-from-secret-key). + +### App-siloed Outgoing Viewing Secret Key + +The App-siloed Outgoing Viewing Secret Key is a **hardened** child key, and so is only derivable by the owner of the Master Outgoing Viewing Secret Key. It is hardened so as to enable the $\ovskapp$ to be passed into an app circuit, without the threat of $\ovskm$ being reverse-derivable by a malicious app. Only when an app-siloed public key needs to be derivable by the general public is a normal (non-hardened) key derivation scheme used. + +$$ +\begin{aligned} +& \ovskapp \in \mathbb{F}_r \\ +& \ovskapp = \text{derive\_hardened\_app\_siloed\_secret\_key}(\text{``az\_ovsk\_app''}, \text{app\_address}, \ovskm) +\end{aligned} +$$ + +> See [`derive_hardened_app_siloed_secret_key`](#derive-hardened-app-siloed-secret-key). + +## Incoming Viewing Keys + +If a sender wants to send some recipient a private message or note, they can derive an ephemeral symmetric encryption key from the recipient's Master Incoming Viewing Public Key. I.e. these keys are for decrypting "incoming" data from the pov of a recipient. + +### Master Incoming Viewing Secret Key + +$$ +\begin{aligned} +& \ivskm \in \mathbb{F}_r \\ +& \ivskm = \text{derive\_master\_secret\_key\_from\_seed}(\text{``az\_ivsk\_m''}, \seed) +\end{aligned} +$$ + +> See [`derive_master_secret_key_from_seed`](#derive-master-secret-key-from-seed). + +> $\ivskm$ MUST NOT enter an app circuit. + +### Master Incoming Viewing Public Key + +The Master Incoming Viewing Public Key can be used by a sender to encrypt messages and notes to the owner of this key. + +$$ +\begin{aligned} +& \Ivpkm \in \mathbb{G}_{\text{Grumpkin}} \\ +& \Ivpkm = \text{derive\_public\_key}(\ivskm) +\end{aligned} +$$ + +> See [`derive_public_key`](#derive-public-key-from-secret-key). + +### App-siloed Incoming Viewing Secret Key + +An App-siloed Incoming Viewing Secret Key is not prescribed in this spec, because depending on how an app developer wishes to make use of such a key, it could have implications on the security of the Master Incoming Viewing Secret Key. + +> TODO: more discussion needed here, to explain everything we've thought about. + +## Tagging Keys + +The "tagging" key pair can be used to flag "this ciphertext is for you", without requiring decryption. + +### Master Tagging Secret Key + +$$ +\begin{aligned} +& \tskm \in \mathbb{F}_r \\ +& \tskm = \text{derive\_master\_secret\_key\_from\_seed}(\text{``az\_tvsk\_m''}, \seed) +\end{aligned} +$$ + +> See [`derive_master_secret_key_from_seed`](#derive-master-secret-key-from-seed). + +> $\ivskm$ MUST NOT enter an app circuit. + +### Master Tagging Public Key + +$$ +\begin{aligned} +& \Tpkm \in \mathbb{G}_{\text{Grumpkin}} \\ +& \Tpkm = \text{derive\_public\_key}(\tskm) +\end{aligned} +$$ + +> See [`derive_public_key`](#derive-public-key-from-secret-key). + +## Acknowledgements + +Much of this is inspired by the [ZCash Sapling and Orchard specs](https://zips.z.cash/protocol/protocol.pdf). diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/precompiles.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/precompiles.md new file mode 100644 index 000000000000..720e2c13d4de --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/addresses-and-keys/precompiles.md @@ -0,0 +1,172 @@ +--- +title: Precompiles +--- + + + +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/private-msg-delivery.md#encryption-and-decryption) and [tagging](../private-message-delivery/private-msg-delivery.md#note-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 an 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, 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_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 are 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. + +``` +struct PublicKeys: + nullifier_key: Field + incoming_encryption_key: PublicKey + outgoing_encryption_key: PublicKey + incoming_internal_encryption_key: PublicKey + tagging_key: PublicKey +``` + +To identify which public key to use in the encryption, precompiles also accept an enum: + +``` +enum EncryptionType: + incoming = 1 + outgoing = 2 + incoming_internal = 3 +``` + +Precompiles expose the following private functions: + +``` +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_CIPHERTEXT_LENGTH] +``` + +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_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_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. + +``` +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, ciphertext: Field[MAX_CIPHERTEXT_LENGTH]): Field[MAX_PLAINTEXT_LENGTH] +``` + +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 + +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 ciphertext and the ephemeral public key. + +Pseudocode for the encryption process: + +``` +encrypt(plaintext, recipient_public_key): + ephemeral_private_key, ephemeral_public_key = grumpkin_random_keypair() + shared_secret = recipient_public_key * ephemeral_private_key + [aes_key, aes_iv] = sha256(shared_secret ++ [0x01]) + return ephemeral_public_key ++ aes_encrypt(aes_key, aes_iv, plaintext) +``` + +Pseudocode for the decryption process: + +``` +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, ciphertext[64:]) +``` + + + +## Note tagging strategies + +List of note tagging strategies implemented by precompiles: + +### Trial decryption + +Trial decryption relies on the recipient to brute-force trial-decrypting every note emitted by the chain. Every note is attempted to be decrypted with the associated decryption scheme. If decryption is successful, then the note is added to the local database. This requires no note tags to be emitted along with a note. + +In AES encryption, the plaintext is prefixed with the first 8 bytes of the IV. Decryption is deemed successful if the first 8 bytes of the decrypted plaintext matches the first 8 bytes of the IV derived from the shared secret. + +This is the cheapest approach in terms of calldata cost, and the simplest to implement, but puts a significant burden on the user. Should not be used except for accounts tied to users running full nodes. + +### Delegated trial decryption + +Delegated trial decryption relies on a tag added to each note, generated using the recipient's tagging public key. The holder of the corresponding tagging private key can trial-decrypt each tag, and if decryption is successful, proceed to decrypt the contents of the note using the associated decryption scheme. + +This allows a user to share their tagging private key with a trusted service provider, who then proceeds to trial decrypt all possible note tags on their behalf. This scheme is simple for the user, but requires trust on a third party. + + + +### Tag hopping + +Tag hopping relies on establishing a one-time shared secret through a handshake between each sender-recipient pair, advertise the handshake through a trial-decrypted brute-forced channel, and then generate tags by combining the shared secret and an incremental counter. Recipients need to trial-decrypt events emitted by a canonical `Handshake` contract to detect new channels established with them, and then scan for the next tag for each open channel. Note that the handshake contract leaks whenever a new shared secret has been established, but the participants of the handshake are kept hidden. + +This method requires the recipient to be continuously trial-decrypting the handshake channel, and then scanning for a number of tags equivalent to the number of handshakes they had received. While this can get to too large amounts for particularly active addresses, it is still far more efficient than trial decryption. + +When Alice wants to send a message to Bob for the first time: + +1. Alice creates a note, and calls into Bob's encryption and tagging precompile. +2. The precompile makes an oracle call to `getSharedSecret(Alice, Bob)`. +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 ciphertext before broadcasting it. + +For all subsequent messages: + +1. Alice creates a note, and calls into Bob's encryption and tagging precompile. +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 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 | | | diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/bytecode/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/bytecode/index.md new file mode 100644 index 000000000000..68433b5d620d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/bytecode/index.md @@ -0,0 +1,172 @@ +--- +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. They are circuits that must be individually executed by the [ACVM](https://github.com/noir-lang/noir/blob/master/acvm-repo/acvm/src/pwg/mod.rs#L132) and proved by barretenberg. +- Public functions: The functions that are run by sequencers. They are aggregated in a bytecode block that must be executed and proven by the AVM. +- Utility functions: Helper functions that are run on users' machines but are not constrained. They are represented individually as bytecode that is executed by the ACVM. + - They can also be used to convey how dapps should handle a particular contract's data. + +When a contract is compiled, private and utility 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. + +If a private function needs unconstrained hints, the bytecode that generates the unconstrained hints is embedded in the private circuit. This allows the ACVM to compute the hints during witness generation. + +There are three different (but related) bytecode standards that are used in Aztec: AVM bytecode, Brillig bytecode and ACIR bytecode. + +# AVM Bytecode + +The AVM bytecode is the compilation target of the public functions of a contract. It's specified in the [AVM section](../public-vm/instruction-set.mdx). It allows control flow and uses a flat memory model which tracks bit sizes of values stored in memory via tagging of memory indexes. Sequencers run the AVM bytecode of the public functions of a contract using the AVM and prove the correct execution of it. + +# Brillig Bytecode + +Brillig bytecode is the compilation target of all the unconstrained code in a contract. Any unconstrained hint used by a private function is compiled to Brillig bytecode. Also, contracts' utility functions are entirely compiled to Brillig bytecode. In the case of Noir, it compiles public functions entirely to a single block of brillig bytecode that is then converted to AVM bytecode. Similarly to AVM bytecode, Brillig bytecode allows control flow. + +Brillig bytecode will be very similar to AVM bytecode. While AVM bytecode is specifically designed to be executed by the AVM, brillig bytecode is meant to be more general and allow the use of arbitrary oracles. + +[Oracles](https://noir-lang.org/docs/explainers/explainer-oracle) 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 utility 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. + +However, AVM bytecode doesn't allow arbitrary oracles, any nondeterminism introduced is done in a way that the protocol can ensure that the simulator entity (the sequencer) cannot manipulate the result of an oracle. As such, when transforming brillig bytecode to AVM bytecode, all the oracles are replaced by the specific opcodes that the AVM supports for nondeterminism, like [TIMESTAMP](../public-vm/instruction-set.mdx#isa-section-timestamp), [ADDRESS](../public-vm/instruction-set.mdx#isa-section-address), etc. Any opcode that requires the simulator entity to provide data external to the AVM memory is non-deterministic. + +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's actively being changed to become "AVM bytecode without arbitrary oracles" and right now the differences are handled by a transpiler. + +# ACIR Bytecode + +ACIR bytecode is the compilation target of 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: This opcode contains a block of brillig bytecode, witness indices of the parameters and witness indices of the return values. When ACIR bytecode needs an unconstrained hint, the bytecode that is able to generate the hint at runtime is embedded in a Brillig opcode, and the result of running the hint is assigned to the return witnesses specified in the 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 acir 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. + +This implies that a block of ACIR bytecode can represent more than one program, since it can contain any number of Brillig opcodes each one containing a full Brillig program that computes a hint that the circuit needs at runtime. + +# Usage of the bytecode + +## Compiling a contract + +When a contract is compiled, an artifact will be generated. This artifact needs to be hashed in a specific manner [detailed in the deployment section](../contract-deployment/classes.md#artifact-hash) for publishing. + +The exact form of the artifact is not specified by the protocol, but it needs at least the following information: + +### Contract artifact + + +| Field | Type | Description | +|----------|----------|----------| +| `name` | `string` | The name of the contract. | +| `compilerVersion` | `string` | Version of the compiler that generated the bytecode. This is a string to convey extra information like the version of Aztec.nr used. | +| `functions` | [FunctionEntry[]](#function-entry) | The functions of the contract. | +| `publicBytecode` | `string` | The AVM bytecode of the public functions, converted to base64. | +| `events` | [EventAbi[]](#event-abi) | The events of the contract. | + +### Event ABI + + +| Field | Type | Description | +|----------|----------|----------| +| `name` | `string` | The event name. | +| `fields` | [ABIVariable](#abi-variable) | The fields of the event. | + +### Function entry + +If the function is public, the entry will be its ABI. If the function is private or utility, the entry will be the ABI + the artifact. + +### Function artifact + + +| Field | Type | Description | +|----------|----------|----------| +| `bytecode` | `string` | The ACIR bytecode of the function, converted to base64. | + +### Function ABI + + +| Field | Type | Description | +|----------|----------|----------| +| `name` | `string` | The name of the function. | +| `functionType` | `string` | `private`, `public` or `utility`. | +| `parameters` | [ABIParameter[]](#abi-parameter) | Function parameters. | +| `returnTypes` | `AbiType[]` | The types of the return values. | + +### ABI Variable + + +| Field | Type | Description | +|----------|----------|----------| +| `name` | `string` | The name of the variable. | +| `type` | [AbiType](#abi-type) | The type of the variable. | + +### ABI Parameter + + +| Field | Type | Description | +|----------|----------|----------| +| `name` | `string` | The name of the variable. | +| `type` | [AbiType](#abi-type) | The type of the variable. | +| `visibility` | `string` | `public` or `secret`. | + +### ABI Type + + +| Field | Type | Description | +|----------|----------|----------| +| `kind` | `string` | `field`, `boolean`, `integer`, `array`, `string` or `struct` | +| `sign?` | `string` | The sign of the integer. Applies to integers only. | +| `width?` | `number` | The width of the integer in bits. Applies to integers only. | +| `length?` | `number` | The length of the array or string. Applies to arrays and strings only. | +| `type?` | [AbiType](#abi-type) | The types of the array elements. Applies to arrays only. | +| `fields?` | [ABIVariable[]](#abi-variable) | the fields of the struct. Applies to structs only. | + +### Bytecode in the artifact + +The protocol mandates that 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.md). + +The bytecode of private and utility 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 [supported but not required](../contract-deployment/classes.md#broadcast) by the protocol. 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. Also, contract classes contain an [artifact hash](../contract-deployment/classes.md#artifact-hash) so the PXE can verify that the artifact corresponds with the contract class. + +The encoding of private and utility functions is not specified by the protocol, but it's recommended to follow [the encoding](https://github.com/noir-lang/noir/blob/master/acvm-repo/acir/src/circuit/mod.rs#L157) that Barretenberg and the ACVM share that is serialization using bincode and gzip for compression. + +This implies that the encoding of private and utility functions does not need to be friendly to circuits, since when publishing it the protocol only sees a [generic array of field elements](../contract-deployment/classes.md#broadcast). + +## Executing a private function + +When executing a private function, its ACIR bytecode will be executed by the PXE using the ACVM. The ACVM will generate the witness of the execution. The proving system can be used to generate a proof of the correctness of the witness. + +The fact that the correct function was executed is checked by the protocol by verifying that the [contract class ID](../contract-deployment/classes.md#class-identifier) contains one leaf in the function tree with this selector and the verification key of the function. + +## Executing a utility function + +When executing a utility function, its Brillig bytecode will be executed by the PXE using the ACVM, similarly to private functions, but the PXE will not prove the execution. Instead, the PXE will return the result of the execution of the function to the user. + +## Executing a public function + +When executing a public function, its AVM bytecode will be executed by the sequencer with the specified selector and arguments. The sequencer will generate a public VM proof of the correct execution of the AVM bytecode. + +The fact that the correct bytecode was executed is checked by the protocol by verifying that the [contract class ID](../contract-deployment/classes.md#class-identifier) contains the [commitment](../public-vm/bytecode-validation-circuit.md#committed-representation) to the bytecode used. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/batched-calls.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/batched-calls.md new file mode 100644 index 000000000000..92ac74275b8d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/batched-calls.md @@ -0,0 +1,35 @@ +# 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`](../circuits/private-kernel-initial#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. + + + +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. + + + +In pseudocode, the kernel circuit executes the following logic: + +``` +loop: + if next_call_stack_item = context.private_call_stack.pop(): + execute(next_call_stack_item.address, next_call_stack_item.function_selector, next_call_stack_item.arguments) + else if next_batched_call = context.private_batched_queue.pop(): + let calls = context.private_batched_queue.filter(call => call.target == target) + context.private_batched_queue.delete_many(calls) + execute(target.address, target.function_selector, calls.map(call => [call.call_context, ...call.arguments])) + else: + 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 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/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/enqueued-calls.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/enqueued-calls.md new file mode 100644 index 000000000000..8b7cd09fe3db --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/enqueued-calls.md @@ -0,0 +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 -- 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. + +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/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/index.md new file mode 100644 index 000000000000..956e31b10808 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/index.md @@ -0,0 +1,26 @@ +--- +title: Calls +--- + + + + + +# 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), 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. + +import DocCardList from '@theme/DocCardList'; + + diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/public-private-messaging.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/public-private-messaging.md new file mode 100644 index 000000000000..3dc4ab83e5ea --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/public-private-messaging.md @@ -0,0 +1,84 @@ +# Inter-Layer Calls + + + +## Public-Private messaging + +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, 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. + +Since private functions execute first, they cannot 'wait' on the results of their calls to public functions. + +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. + +```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 +``` + +The order of execution will actually be: + +```mermaid +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) +``` + +And the order of proving will actually be: + +```mermaid +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. 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#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` + +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. + +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. + +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 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 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 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 note hashes, potentially as part of transferring funds from the public domain to the private domain). + +![Public - Private Messaging](/img/protocol-specs/calls/pub_pvt_messaging.png) diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/static-calls.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/static-calls.md new file mode 100644 index 000000000000..9d9adc19ec5c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/static-calls.md @@ -0,0 +1,20 @@ +# Static calls + +[Synchronous calls](./sync-calls.md), both private and public, can be executed as _static_ calls. This means that the called function, and all nested calls within, cannot emit any modifying side effects, such as creating or consuming notes, writing to storage, or emitting events. The purpose of a static call is to query another contract while ensuring that the call will not modify state. Static calls are based on [EIP214](https://eips.ethereum.org/EIPS/eip-214). + +In particular, the following fields of the returned `CallStackItem` must be zero or empty in a static call: + + + +- `new_note_hashes` +- `new_nullifiers` +- `nullified_commitments` +- `new_l2_to_l1_msgs` +- `private_logs` +- `public_logs` + +From the moment a static call is made, every subsequent nested call is forced to be static by setting a flag in the derived `CallContext`, which propagates through the call stack. + +At the protocol level, a static call is identified by a `is_static_call` flag in the `CircuitPublicInputs` of the `CallStackItem`. The kernel is responsible for asserting that the call and all nested calls do not emit any forbidden side effects. + +At the contract level, a caller can initiate a static call via a `staticCallPrivateFunction` or `staticCallPublicFunction` oracle call. The caller is responsible for asserting that the returned `CallStackItem` has the `is_static_call` flag correctly set. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/sync-calls.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/sync-calls.md new file mode 100644 index 000000000000..f85102b14f81 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/sync-calls.md @@ -0,0 +1,38 @@ +# 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`](../circuits/private-kernel-initial#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 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 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 +if is_private(): + call_stack = [top_level_execution] + kernel_public_inputs = empty_inputs + proof = empty_proof +else: + call_stack = tx.public_call_stack + kernel_public_inputs = tx.kernel_public_inputs + proof = tx.proof + +while call_stack is not empty: + let call_stack_item = call_stack.pop() + call_stack.push(...call_stack_item.call_stack) + kernel_public_inputs, proof = kernel_circuit(call_stack_item, kernel_public_inputs, proof) +``` + +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/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/unconstrained-calls.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/unconstrained-calls.md new file mode 100644 index 000000000000..9fe11f46600b --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/calls/unconstrained-calls.md @@ -0,0 +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. + +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/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/high-level-topology.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/high-level-topology.md new file mode 100644 index 000000000000..43f00cb05ea1 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/high-level-topology.md @@ -0,0 +1,139 @@ +# High Level Topology + +## Overview + +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 [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 protocol spec, 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]) + f0 --> f2([f2]) + f0 --> f3([f3]) + f1 -.-> F0 + F0 --> F1 + F0 --> F2 + F2 --> F3 + f3 --> f4([f4]) + f3 -.-> F4 + f3 --> f5([f5]) +``` + +This transaction contains 6 private functions (f0 to f5) and 5 public functions (F0 to F4), with `f0` being the entrypoint. The entire transaction is processed as follows: + +```mermaid +flowchart TB + subgraph Transaction A + subgraph Private Functions + f0([f0]) + f1([f1]) + f2([f2]) + f3([f3]) + f4([f4]) + f5([f5]) + end + subgraph Public Functions + F0 + F1 + F2 + F3 + F4 + end + end + subgraph Transaction C + init2(...) + tail2(Tail Private Kernel) + init2 -.-> tail2 + end + subgraph Transaction B + init1(...) + tail1(Tail Private Kernel) + init1 -.-> tail1 + end + subgraph Public Kernel + INIT0(Initial Public Kernel) + INNER0(Inner Public Kernel) + INNER1(Inner Public Kernel) + INNER2(Inner Public Kernel) + INNER3(Inner Public Kernel) + TAIL0(Tail Public Kernel) + INIT0 --> INNER0 + INNER0 --> INNER1 + INNER1 --> INNER2 + INNER2 --> INNER3 + INNER3 --> TAIL0 + end + subgraph Private Kernel + init0(Initial Private Kernel) + inner0(Inner Private Kernel) + inner1(Inner Private Kernel) + inner2(Inner Private Kernel) + reset0(Reset Private Kernel) + inner3(Inner Private Kernel) + inner4(Inner Private Kernel) + reset1(Reset Private Kernel) + tail0(Tail Private Kernel) + init0 --> inner0 + inner0 --> inner1 + inner1 --> inner2 + inner2 --> reset0 + reset0 --> inner3 + inner3 --> inner4 + inner4 --> reset1 + reset1 --> tail0 + end + f0 --> init0 + f1 --> inner0 + f2 --> inner1 + f3 --> inner2 + f4 --> inner3 + f5 --> inner4 + F0 --> INIT0 + F1 --> INNER0 + F2 --> INNER1 + F3 --> INNER2 + F4 --> INNER3 + subgraph Rollup + BR0(Base Rollup) + BR1(Base Rollup) + BR2(Base Rollup) + BR3(Base Rollup) + MR0(Merge Rollup) + MR1(Merge Rollup) + MR2(Merge Rollup) + MR3(Merge Rollup) + ROOT(Root Rollup) + end + tail0 --> INIT0 + TAIL0 --> BR0 + tail1 --> BR1 + tail2 --> BR2 + BR0 --> MR0 + BR1 --> MR0 + BR2 --> MR1 + BR3 --> MR1 + MR0 --> MR2 + MR1 --> MR2 + MR2 --> ROOT + MR3 --> ROOT +``` + +A few things to note: + +- 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. +- A [merge rollup circuit](../rollup-circuits/merge-rollup.md) can merge two base rollup circuits or two merge rollup circuits. +- The final step is the execution of the [root rollup circuit](../rollup-circuits/root-rollup.md), which combines two base rollup circuits or two merge rollup circuits. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-function.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-function.md new file mode 100644 index 000000000000..11d7407dcfb9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-function.md @@ -0,0 +1,184 @@ +# Private Function Circuit + +## Requirements + +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, 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 + +The private inputs of a private function circuit are customizable. + +## Public Inputs + + + + + +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`; [`RETURN_VALUES_LENGTH`](../constants.md#circuit-constants)] | Return values of this function call. | +| `note_hashes` | [[`NoteHash`](#notehash); [`MAX_NOTE_HASHES_PER_CALL`](../constants.md#circuit-constants)] | New note hashes created in this function call. | +| `nullifiers` | [[`Nullifier`](#nullifier); [`MAX_NULLIFIERS_PER_CALL`](../constants.md#circuit-constants)] | New nullifiers created in this function call. | +| `l2_to_l1_messages` | [[`L2toL1Message`](#l2tol1message); [`MAX_L2_TO_L1_MSGS_PER_CALL`](../constants.md#circuit-constants)] | New L2 to L1 messages created in this function call. | +| `unencrypted_log_hashes` | [[`UnencryptedLogHash`](#unencryptedloghash); [`MAX_UNENCRYPTED_LOG_HASHES_PER_CALL`](../constants.md#circuit-constants)] | Hashes of the unencrypted logs emitted in this function call. | +| `encrypted_log_hashes` | [[`EncryptedLogHash`](#encryptedloghash); [`MAX_ENCRYPTED_LOG_HASHES_PER_CALL`](../constants.md#circuit-constants)] | Hashes of the encrypted logs emitted in this function call. | +| `encrypted_note_preimage_hashes` | [[`EncryptedNotePreimageHash`](#encryptednotepreimagehash); [`MAX_ENCRYPTED_NOTE_PREIMAGE_HASHES_PER_CALL`](../constants.md#circuit-constants)] | Hashes of the encrypted note preimages emitted in this function call. | +| `note_hash_read_requests` | [[`ReadRequest`](#readrequest); [`MAX_NOTE_HASH_READ_REQUESTS_PER_CALL`](../constants.md#circuit-constants)] | Requests to prove the note hashes being read exist. | +| `nullifier_read_requests` | [[`ReadRequest`](#readrequest); [`MAX_NULLIFIER_READ_REQUESTS_PER_CALL`](../constants.md#circuit-constants)] | Requests to prove the nullifiers being read exist. | +| `key_validation_requests` | [[`ParentSecretKeyValidationRequest`](#parentsecretkeyvalidationrequest); [`MAX_KEY_VALIDATION_REQUESTS_PER_CALL`](../constants.md#circuit-constants)] | Requests to validate keys used in this function call. | +| `public_call_requests` | [[`PublicCallRequest`](#publiccallrequest); [`MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL`](../constants.md#circuit-constants)] | Requests to call public functions. | +| `private_call_requests` | [[`PrivateCallRequest`](#privatecallrequest); [`MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL`](../constants.md#circuit-constants)] | Requests to call Private functions. | +| `counter_start` | `u32` | Counter at which the function call was initiated. | +| `counter_end` | `u32` | Counter at which the function call ended. | +| `min_revertible_side_effect_counter` | `u32` | Counter below which the side effects are non-revertible. | +| `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. + +## Types + +### `CallContext` + +| Field | Type | Description | +| ------------------------- | -------------- | -------------------------------------------------------------------------------- | +| `msg_sender` | `AztecAddress` | Address of the caller contract. | +| `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_static_call` | `bool` | A flag indicating whether the call is a [static call](../calls/static-calls.md). | + +### `GasSettings` + +| Field | Type | Description | +| ----------------------- | ------- | -------------------------------------------------------------------- | +| `da.gas_limit` | `u32` | Total limit for DA gas for the transaction. | +| `da.teardown_gas_limit` | `u32` | Limit for DA gas specific to the teardown phase. | +| `da.max_fee_per_gas` | `field` | Maximum amount that the sender is willing to pay per unit of DA gas. | +| `l2.gas_limit` | `u32` | Total limit for L2 gas for the transaction. | +| `l2.teardown_gas_limit` | `u32` | Limit for L2 gas specific to the teardown phase. | +| `l2.max_fee_per_gas` | `field` | Maximum amount that the sender is willing to pay per unit of L2 gas. | +| `inclusion_fee` | `field` | Flat fee the user pays for inclusion. | + +### `NoteHash` + +| Field | Type | Description | +| --------- | ------- | ------------------------------------------- | +| `value` | `field` | Hash of the note. | +| `counter` | `u32` | Counter at which the note hash was created. | + +### `Nullifier` + +| Field | Type | Description | +| ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------ | +| `value` | `field` | Value of the nullifier. | +| `counter` | `u32` | Counter at which the nullifier was created. | +| `note_hash_counter` | `u32` | Counter of the transient note the nullifier is created for. 0 if the nullifier does not associate with a transient note. | + +### `L2toL1Message` + +| Field | Type | Description | +| --------- | ------- | ----------------------------------------- | +| `value` | `field` | L2-to-l2 message. | +| `counter` | `u32` | Counter at which the message was emitted. | + +### `UnencryptedLogHash` + + + +| Field | Type | Description | +| --------- | ------- | -------------------------------------- | +| `hash` | `field` | Hash of the unencrypted log. | +| `length` | `field` | Number of fields of the log preimage. | +| `counter` | `u32` | Counter at which the hash was emitted. | + +### `EncryptedLogHash` + +| Field | Type | Description | +| ------------ | ------- | -------------------------------------------- | +| `hash` | `field` | Hash of the encrypted log. | +| `length` | `field` | Number of fields of the log preimage. | +| `counter` | `u32` | Counter at which the hash was emitted. | +| `randomness` | `field` | A random value to hide the contract address. | + +### `EncryptedNotePreimageHash` + +| Field | Type | Description | +| ------------------- | ------- | --------------------------------------- | +| `hash` | `field` | Hash of the encrypted note preimage. | +| `length` | `field` | Number of fields of the note preimage. | +| `counter` | `u32` | Counter at which the hash was emitted. | +| `note_hash_counter` | `u32` | Counter of the corresponding note hash. | + +### `ReadRequest` + +| Field | Type | Description | +| ------------------ | -------------- | ---------------------------------------------- | +| `value` | `field` | Value being read. | +| `contract_address` | `AztecAddress` | Address of the contract the value was created. | +| `counter` | `u32` | Counter at which the request was made. | + +### `ParentSecretKeyValidationRequest` + +| Field | Type | Description | +| --------------------------- | --------------- | -------------------------------------------- | +| `parent_public_key` | `GrumpkinPoint` | Claimed parent public key of the secret key. | +| `hardened_child_secret_key` | `fq` | Secret key passed to the function. | + +### `PublicCallRequest` + +| Field | Type | Description | +| ---------------------- | ------- | -------------------------------------- | +| `call_stack_item_hash` | `field` | Hash of the call stack item. | +| `counter` | `u32` | Counter at which the request was made. | + + + +### `PrivateCallRequest` + +| Field | Type | Description | +| ---------------------- | ------- | ---------------------------------------- | +| `call_stack_item_hash` | `field` | Hash of the call stack item. | +| `counter_start` | `u32` | Counter at which the call was initiated. | +| `counter_end` | `u32` | Counter at which the call ended. | + +### `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. | + + + + diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-kernel-initial.mdx b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-kernel-initial.mdx new file mode 100644 index 000000000000..4d414ea9909e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-kernel-initial.mdx @@ -0,0 +1,934 @@ +# 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. + +#### 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 note 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. + +In ensuring the integrity of emitted data, counters play a crucial role in establishing the chronological order of elements generated during a transaction. This validation process not only guards against misinterpretations but also reinforces trust in the sequence of transactions. + +Counters are employed to validate the following aspects: + +1. **Read Requests**: Verify that a read request is reading a value created before the request is submitted. + - Refer to [Read Request Reset Private Kernel Circuit](./private-kernel-reset.md#read-request-reset-private-kernel-circuit) for verification details. +2. **Ordered Emission**: Ensure that side effects (note hashes, nullifiers, logs) are emitted in the same order as they were created. + - Refer to [Tail Private Kernel Circuit](./private-kernel-tail.md#verifying-and-splitting-ordered-data) for order enforcement. + +For these operations to be effective, specific requirements for the counters must be met: + +- Each counter for values of the same type must be unique, avoiding confusion about the order. +- Values emitted within a function call must not be mistaken for values emitted from another function call. + +The circuit undergoes the following validations for data within [`private_inputs`](#privateinputs).[`private_call`](#privatecall).[public_inputs](./private-function.md#public-inputs): + +1. Validate the counter range of [`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`. + + The counter range (`counter_start` to `counter_end`) is later used to restrict counters emitted within the call. + +2. Validate the counter ranges of non-empty requests in `private_call_requests`. + + - The `counter_end` of each request must be strictly greater than its `counter_start`. + - The `counter_start` of the first request must be strictly greater than the `counter_start` of the `call_stack_item`. + - The `counter_start` of the second and each of the subsequent requests must be strictly greater than the `counter_end` of the previous request. + - The `counter_end` of the last request must be strictly less than the `counter_end` of the `call_stack_item`. + + When a `request` is [popped](./private-kernel-inner.mdx#ensuring-the-current-call-matches-the-call-request) in a nested iteration, its counter range is checked against the `call_stack_item`, as described [here](./private-kernel-inner.mdx#verifying-the-counters). By enforcing that the counter ranges of all nested `private_call_requests` do not overlap with one another in this step, a function circuit will not be able to emit a value whose counter falls in the range of another call. + +3. Validate the counters of the non-empty elements in the following arrays: + + - `note_hashes` + - `nullifiers` + - `l2_to_l1_messages` + - `unencrypted_log_hashes` + - `encrypted_log_hashes` + - `encrypted_note_preimage_hashes` + - `note_hash_read_requests` + - `nullifier_read_requests` + - `public_call_requests` + + 1. For each of the above "ordered" array, the counters of the non-empty elements must be in a strictly-increasing order: + + - The `counter` of the first element must be strictly greater than the `counter_start` of the `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 `call_stack_item`. + + 2. Additionally, the counters must not fall within the counter range of any nested private call. + + Get the value `NE`, which is the number of non-empty requests in `private_call_requests`. If `NE` is greater than `0`, the circuit checks the following: + + For each `note_hash` at index `i` in `note_hashes`: + + - Find the `request_index` at [`hints`](#hints).`note_hash_range_hints[i]`, which is the index of the `private_call_requests` with the smallest `counter_start` that was emitted after the `note_hash`. + - If `request_index` equals `NE`, indicating no request was emitted after the `note_hash`, its counter must be greater than the `counter_end` of the last request. + - If `request_index` equals `0`, indicating no request was emitted before the `note_hash`. Its counter must be less than the `counter_start` of the first request. + - Otherwise, the request was emitted after the `note_hash`, and its immediate previous request was emitted before the `note_hash`. Its counter must fall between those two requests. + + The code simplifies as: + + ```rust + let NE = count_non_empty_elements(private_call_requests); + for i in 0..note_hashes.len() { + let note_hash = note_hashes[i]; + if !note_hash.is_empty() { + let request_index = note_hash_range_hints[i]; + if request_index != NE { + note_hash.counter < private_call_requests[request_index].counter_start; + } + if request_index != 0 { + note_hash.counter > private_call_requests[request_index - 1].counter_end; + } + } + } + ``` + + Repeat the above process for emitted data, including: + + - `nullifiers` + - `unencrypted_log_hashes` + - `encrypted_log_hashes` + - `encrypted_note_preimage_hashes` + + + + + +### 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`, `counter` +- `note_hash_read_requests` + - `value`, `contract_address`, `counter` +- `nullifier_read_requests` + - `value`, `contract_address`, `counter` +- `key_validation_request_contexts` + - `parent_public_key`, `hardened_child_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` +- `public_call_request_contexts` + - `call_stack_item_hash`, `counter` + +1. Check that the values in `private_call_request_stack` align with the values in `private_call_requests` within `private_function_public_inputs`, but in **reverse** order. + + > It's important that the `private_call_requests` are "pushed" to the `private_call_request_stack` in reverse order to ensure that they are executed in chronological order. + +2. For each non-empty call request in both `private_call_request_stack` and `public_call_request_contexts` within `public_inputs.transient_accumulated_data`: + + - 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. + +3. 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` + - `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. + +4. 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`. + + +5. 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). + +#### Verifying the `min_revertible_side_effect_counter`. + +It verifies that the `min_revertible_side_effect_counter` equals the value in the [`public_inputs`](./private-function.md#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 + gas_settings: GasSettings +} +TransactionRequest *-- FunctionData: function_data +TransactionRequest *-- TransactionContext: tx_context +TransactionRequest *-- GasSettings: gas_settings + +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 +} +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 .. PrivateCallRequestContext: 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 +} + +class GasSettings { + da.gas_limit: u32 + da.teardown_gas_limit: u32 + da.max_fee_per_gas: Fr + l1.gas_limit: u32 + l1.teardown_gas_limit: u32 + l1.max_fee_per_gas: Fr + l2.gas_limit: u32 + l2.teardown_gas_limit: u32 + l2.max_fee_per_gas: Fr + inclusion_fee: Fr +} + +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~ + note_hash_read_requests: List~ReadRequest~ + nullifier_read_requests: List~ReadRequest~ + key_validation_requests: List~ParentSecretKeyValidationRequest~ + unencrypted_log_hashes: List~UnencryptedLogHash~ + encrypted_log_hashes: List~EncryptedLogHash~ + encrypted_note_preimage_hashes: List~EncryptedNotePreimageHash~ + public_call_requests: List~PublicCallRequest~ + private_call_requests: List~PrivateCallRequest~ + counter_start: u32 + counter_end: u32 + min_revertible_side_effect_counter: u32 + block_header: BlockHeader + chain_id: field + version: field +} +PrivateFunctionPublicInputs *-- CallContext: call_context +PrivateFunctionPublicInputs *-- NoteHash: note_hashes +PrivateFunctionPublicInputs *-- Nullifier: nullifiers +PrivateFunctionPublicInputs *-- ReadRequest: note_hash_read_requests +PrivateFunctionPublicInputs *-- ReadRequest: nullifier_read_requests +PrivateFunctionPublicInputs *-- ParentSecretKeyValidationRequest: 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 .. ParentSecretKeyValidationRequestContext: 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 + contract_address: AztecAddress + counter: field +} + +class ParentSecretKeyValidationRequest { + parent_public_key: GrumpkinPoint + hardened_child_secret_key: fq +} +ParentSecretKeyValidationRequest ..> ParentSecretKeyValidationRequestContext: key_validation_requests\n->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 PublicCallRequestContext { + call_stack_item_hash: field + caller_contract_address: AztecAddress + caller_context: CallerContext + counter: field +} +CallerContext --* PublicCallRequestContext : caller_context + +PublicCallRequest ..> PublicCallRequestContext: public_call_requests->public_call_request_contexts + +class PrivateCallRequestContext { + call_stack_item_hash: field + caller_contract_address: AztecAddress + caller_context: CallerContext + counter_start: field + counter_end: field +} +CallerContext --* PrivateCallRequestContext : caller_context + +PrivateCallRequest ..> PrivateCallRequestContext: private_call_requests->private_call_request_stack + +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 ParentSecretKeyValidationRequestContext { + parent_public_key: GrumpkinPoint + hardened_child_secret_key: fq + 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~ + key_validation_request_contexts: List~ParentSecretKeyValidationRequestContext~ + unencrypted_log_hash_contexts: List~UnencryptedLogHashContext~ + encrypted_log_hash_contexts: List~EncryptedLogHashContext~ + encrypted_note_preimage_hash_contexts: List~EncryptedNotePreimageHashContext~ + note_hash_read_requests: List~ReadRequest~ + nullifier_read_requests: List~ReadRequest~ + public_call_requests: List~PublicCallRequestContext~ + private_call_request_stack: List~PrivateCallRequestContext~ +} +NoteHashContext --* TransientAccumulatedData: note_hash_contexts +NullifierContext --* TransientAccumulatedData: nullifier_contexts +L2ToL1MessageContext --* TransientAccumulatedData: l2_to_l1_message_contexts +ReadRequest --* TransientAccumulatedData: note_hash_read_requests +ReadRequest --* TransientAccumulatedData: nullifier_read_requests +ParentSecretKeyValidationRequestContext --* TransientAccumulatedData: key_validation_request_contexts +UnencryptedLogHashContext --* TransientAccumulatedData: unencrypted_log_hash_contexts +EncryptedLogHashContext --* TransientAccumulatedData: encrypted_log_hash_contexts +EncryptedNotePreimageHashContext --* TransientAccumulatedData: encrypted_note_preimage_hash_contexts +PublicCallRequestContext --* TransientAccumulatedData: public_call_requests +PrivateCallRequestContext --* TransientAccumulatedData: private_call_request_stack + +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` | Address of the entrypoint contract. | +| `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. | +| `gas_settings` | [`GasSettings`](#gassettings) | User-defined gas limits and max fees. | + +### `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. | + +### `Hints` + +| Field | Type | Description | +| ----------------------------- | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| `note_hash_range_hints` | [`field`, [`MAX_NOTE_HASHES_PER_CALL`](../constants.md#circuit-constants)] | Indices of the next emitted private call requests for note hashes. | +| `nullifier_range_hints` | [`field`, [`MAX_NULLIFIERS_PER_CALL`](../constants.md#circuit-constants)] | Indices of the next emitted private call requests for nullifiers. | +| `unencrypted_log_range_hints` | [`field`, [`MAX_UNENCRYPTED_LOG_HASHES_PER_CALL`](../constants.md#circuit-constants)] | Indices of the next emitted private call requests for unencrypted logs. | +| `encrypted_log_range_hints` | [`field`, [`MAX_ENCRYPTED_LOG_HASHES_PER_CALL`](../constants.md#circuit-constants)] | Indices of the next emitted private call requests for encrypted logs. | +| `encrypted_note_range_hints` | [`field`, [`MAX_ENCRYPTED_NOTE_PREIMAGE_HASHES_PER_CALL`](../constants.md#circuit-constants)] | Indices of the next emitted private call requests for encrypted notes. | + +## `PublicInputs` + +| Field | Type | Description | +| ------------------------------------ | ------------------------------------------------------- | ----------- | +| `constant_data` | [`ConstantData`](#constantdata) | | +| `transient_accumulated_data` | [`TransientAccumulatedData`](#transientaccumulateddata) | | +| `min_revertible_side_effect_counter` | `u32` | | + +### `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); [`MAX_NOTE_HASHES_PER_TX`](../constants.md#circuit-constants)] | Note hashes with extra data aiding verification. | +| `nullifier_contexts` | [[`NullifierContext`](#nullifiercontext); [`MAX_NULLIFIERS_PER_TX`](../constants.md#circuit-constants)] | Nullifiers with extra data aiding verification. | +| `l2_to_l1_message_contexts` | [[`L2toL1MessageContext`](#l2tol1messagecontext); [`MAX_L2_TO_L1_MSGS_PER_TX`](../constants.md#circuit-constants)] | L2-to-l1 messages with extra data aiding verification. | +| `unencrypted_log_hash_contexts` | [[`UnencryptedLogHashContext`](#unencryptedloghashcontext); [`MAX_UNENCRYPTED_LOG_HASHES_PER_TX`](../constants.md#circuit-constants)] | Hashes of the unencrypted logs with extra data aiding verification. | +| `encrypted_log_hash_contexts` | [[`EncryptedLogHashContext`](#encryptedloghashcontext); [`MAX_ENCRYPTED_LOG_HASHES_PER_TX`](../constants.md#circuit-constants)] | Hashes of the encrypted logs with extra data aiding verification. | +| `encrypted_note_preimage_hash_contexts` | [[`EncryptedNotePreimageHashContext`](#encryptednotepreimagehash); [`MAX_ENCRYPTED_NOTE_PREIMAGE_HASHES_PER_TX`](../constants.md#circuit-constants)] | Hashes of the encrypted note preimages with extra data aiding verification. | +| `note_hash_read_requests` | [[`ReadRequest`](./private-function#readrequest); [`MAX_NOTE_HASH_READ_REQUESTS_PER_TX`](../constants.md#circuit-constants)] | Requests to prove the note hashes being read exist. | +| `nullifier_read_requests` | [[`ReadRequest`](./private-function#readrequest); [`MAX_NULLIFIER_READ_REQUESTS_PER_TX`](../constants.md#circuit-constants)] | Requests to prove the nullifiers being read exist. | +| `key_validation_request_contexts` | [[`ParentSecretKeyValidationRequestContext`](#parentsecretkeyvalidationrequestcontext); [`MAX_KEY_VALIDATION_REQUESTS_PER_TX`](../constants.md#circuit-constants)] | Requests to validate nullifier keys. | +| `public_call_request_contexts` | [[`PublicCallRequestContext`](./public-kernel-tail.md#publiccallrequestcontext); [`MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX`](../constants.md#circuit-constants)] | Requests to call publics functions. | +| `private_call_request_stack` | [[`PrivateCallRequestContext`](#privatecallrequestcontext); [`MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX`](../constants.md#circuit-constants)] | Requests to call private functions. Pushed to the stack in reverse order so that they will be executed in chronological order. | + +## Types + +### `FunctionData` + +| Field | Type | Description | +| ------------------- | --------------------- | -------------------------------------- | +| `function_selector` | `u32` | Selector of the function being called. | +| `function_type` | `private` \| `public` | Type of the function being called. | + +### `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. | + + + +### `PrivateCallRequestContext` + + + +| Field | Type | Description | +| ------------------------- | --------------------------------- | --------------------------------------------- | +| `call_stack_item_hash` | `field` | Hash of the call stack item. | +| `counter_start` | `u32` | Counter at which the call was initiated. | +| `counter_end` | `u32` | Counter at which the call ended. | +| `caller_contract_address` | `AztecAddress` | Address of the contract calling the function. | +| `caller_context` | [`CallerContext`](#callercontext) | Context of the contract calling the function. | + +### `CallerContext` + + + +| Field | Type | Description | +| -------------------------- | -------------- | ---------------------------------------------------- | +| `msg_sender` | `AztecAddress` | Address of the caller contract. | +| `storage_contract_address` | `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` | `u32` | 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` | `u32` | Counter at which the nullifier was created. | +| `note_hash_counter` | `u32` | 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. | +| `counter` | `u32` | Counter at which the message was emitted. | +| `portal_contract_address` | `AztecAddress` | Address of the portal contract to the contract. | +| `contract_address` | `AztecAddress` | Address of the contract the message was created. | + +### `ParentSecretKeyValidationRequestContext` + +| Field | Type | Description | +| --------------------------- | --------------- | --------------------------------------------- | +| `parent_public_key` | `GrumpkinPoint` | Claimed parent public key of the secret key. | +| `hardened_child_secret_key` | `fq` | Secret key passed to the contract. | +| `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` | `u32` | 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` | `u32` | 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` | `u32` | 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/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-kernel-inner.mdx b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-kernel-inner.mdx new file mode 100644 index 000000000000..1aeb831e2e94 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-kernel-inner.mdx @@ -0,0 +1,580 @@ +# 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 current call matches the call request. + + + +The top item in the `private_call_request_stack` of the [`previous_kernel`](#previouskernel) must pertain to the current function call. + +This circuit will: + +1. Pop the call request from the stack: + + - `call_request = previous_kernel.public_inputs.transient_accumulated_data.private_call_request_stack.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())` + - 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` + +#### 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_request_stack` 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_request_stack` 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 other data. + +It verifies that the [`constant_data`](./private-kernel-initial.mdx#constantdata) and the `min_revertible_side_effect_counter` in the [`public_inputs`](#public-inputs) align with the corresponding values 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 + gas_settings: GasSettings +} +TransactionRequest *-- FunctionData: function_data +TransactionRequest *-- TransactionContext: tx_context +TransactionRequest *-- GasSettings: gas_settings + +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 +} +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 .. PrivateCallRequestContext: 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 +} + +class GasSettings { + da.gas_limit: u32 + da.teardown_gas_limit: u32 + da.max_fee_per_gas: Fr + l1.gas_limit: u32 + l1.teardown_gas_limit: u32 + l1.max_fee_per_gas: Fr + l2.gas_limit: u32 + l2.teardown_gas_limit: u32 + l2.max_fee_per_gas: Fr + inclusion_fee: Fr +} + +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~ + unencrypted_log_hashes: List~UnencryptedLogHash~ + encrypted_log_hashes: List~EncryptedLogHash~ + encrypted_note_preimage_hashes: List~EncryptedNotePreimageHash~ + note_hash_read_requests: List~ReadRequest~ + nullifier_read_requests: List~ReadRequest~ + key_validation_requests: List~ParentSecretKeyValidationRequest~ + public_call_requests: List~PublicCallRequest~ + private_call_requests: List~PrivateCallRequest~ + counter_start: u32 + counter_end: u32 + min_revertible_side_effect_counter: u32 + block_header: BlockHeader + chain_id: field + version: field +} +PrivateFunctionPublicInputs *-- CallContext: call_context +PrivateFunctionPublicInputs *-- NoteHash: note_hashes +PrivateFunctionPublicInputs *-- Nullifier: nullifiers +PrivateFunctionPublicInputs *-- ReadRequest: note_hash_read_requests +PrivateFunctionPublicInputs *-- ReadRequest: nullifier_read_requests +PrivateFunctionPublicInputs *-- ParentSecretKeyValidationRequest: key_validation_requests +PrivateFunctionPublicInputs *-- UnencryptedLogHash: unencrypted_log_hashes +PrivateFunctionPublicInputs *-- EncryptedLogHash: encrypted_log_hashes +PrivateFunctionPublicInputs *-- EncryptedNotePreimageHash: encrypted_note_preimage_hashes +PrivateFunctionPublicInputs *-- PublicCallRequest: public_call_requests +PrivateFunctionPublicInputs *-- PrivateCallRequest: private_call_requests +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 .. ParentSecretKeyValidationRequestContext: 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 + contract_address: AztecAddress + counter: field +} + +class PublicCallRequest { + call_stack_item_hash: field + counter: field +} + +class PrivateCallRequest { + call_stack_item_hash: field + counter_start: field + counter_end: field +} + +class ParentSecretKeyValidationRequest { + parent_public_key: GrumpkinPoint + hardened_child_secret_key: fq +} +ParentSecretKeyValidationRequest ..> ParentSecretKeyValidationRequestContext: key_validation_requests\n->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 PublicCallRequestContext { + call_stack_item_hash: field + caller_contract_address: AztecAddress + caller_context: CallerContext + counter: field +} +CallerContext --* PublicCallRequestContext : caller_context + +PublicCallRequest ..> PublicCallRequestContext: public_call_requests->public_call_request_contexts + +class PrivateCallRequestContext { + call_stack_item_hash: field + caller_contract_address: AztecAddress + caller_context: CallerContext + counter_start: field + counter_end: field +} +CallerContext --* PrivateCallRequestContext : caller_context + +PrivateCallRequest ..> PrivateCallRequestContext: private_call_requests->private_call_request_stack + +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 ParentSecretKeyValidationRequestContext { + parent_public_key: GrumpkinPoint + hardened_child_secret_key: fq + 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~ + unencrypted_log_hash_contexts: List~UnencryptedLogHashContext~ + encrypted_log_hash_contexts: List~EncryptedLogHashContext~ + encrypted_note_preimage_hash_contexts: List~EncryptedNotePreimageHashContext~ + note_hash_read_requests: List~ReadRequest~ + nullifier_read_requests: List~ReadRequest~ + key_validation_request_contexts: List~ParentSecretKeyValidationRequestContext~ + public_call_request_contexts: List~PublicCallRequestContext~ + private_call_request_stack: List~PrivateCallRequestContext~ +} +NoteHashContext --* TransientAccumulatedData: note_hash_contexts +NullifierContext --* TransientAccumulatedData: nullifier_contexts +L2ToL1MessageContext --* TransientAccumulatedData: l2_to_l1_message_contexts +ReadRequest --* TransientAccumulatedData: note_hash_read_requests +ReadRequest --* TransientAccumulatedData: nullifier_read_requests +ParentSecretKeyValidationRequestContext --* TransientAccumulatedData: key_validation_request_contexts +UnencryptedLogHashContext --* TransientAccumulatedData: unencrypted_log_hash_contexts +EncryptedLogHashContext --* TransientAccumulatedData: encrypted_log_hash_contexts +EncryptedNotePreimageHashContext --* TransientAccumulatedData: encrypted_note_preimage_hash_contexts +PublicCallRequestContext --* TransientAccumulatedData: public_call_request_contexts +PrivateCallRequestContext --* TransientAccumulatedData: private_call_request_stack +PublicCallRequestContext --* TransientAccumulatedData: public_call_request_contexts + +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/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-kernel-reset.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-kernel-reset.md new file mode 100644 index 000000000000..0b06176d7d90 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-kernel-reset.md @@ -0,0 +1,253 @@ +# 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 3 variations of reset circuits: + + + +- [Read Request Reset Private Kernel Circuit](#read-request-reset-private-kernel-circuit). +- [Parent Secret Key Validation Request Reset Private Kernel Circuit](#parent-secret-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 [`transient_accumulated_data`](./private-kernel-initial#transientaccumulateddata) within the [`public_inputs`](./private-kernel-initial#publicinputs) of the [`previous_kernel`](#previouskernel). + +Depending on the value specified in [`hints`](#hints-for-read-request-reset-private-kernel-circuit).`reset_type`, it can target different read requests for resetting: + +- For `reset_type == note_hash`: `target_read_requests = note_hash_read_requests` +- For `reset_type == nullifier`: `target_read_requests = nullifier_read_requests` + +A read request can pertain to one of two types of values: + +- A settled value: generated in a prior successful transaction and included in the tree. +- A pending value: created in the current transaction, not yet part of the tree. + +1. **To clear read requests for settled values**, the circuit performs membership checks for the target 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 `hints.persistent_read_indices`: + + 1. If the `persistent_read_index` equals the length of the `target_read_requests` array, there is no read request to be verified. Skip the rest. + 2. Locate the `read_request` using the index: + - `read_request = target_read_requests[persistent_read_index]` + 3. Perform a membership check on the value being read. Where: + - The leaf corresponds to the value: `read_request.value` + - The index and sibling path are in: `hints.read_request_membership_witnesses[i]`. + - The root is sourced from the [block_header](./private-function.md#header) within [`public_inputs`](#public-inputs).[`constant_data`](./private-kernel-initial#constantdata): + - For note hash: `note_hash_tree_root` + - For nullifier: `nullifier_tree_root` + + > 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 values**, the circuit ensures that the values were created before the corresponding read operations, utilizing the [hints](#hints-for-read-request-reset-private-kernel-circuit) provided via `private_inputs`. + + For each `transient_read_index` at index `i` in `hints.transient_read_indices`: + + 1. If the `transient_read_index` equals the length of the `target_read_requests` array, there is no read request to be verified. Skip the rest. + 2. Locate the `read_request` using the index: + - `read_request = target_read_requests[transient_read_index]` + 3. Locate the `target` being read using the index `hints.pending_value_indices[i]`: + - For note hash: `target = note_hash_contexts[index]` + - For nullifier: `target = nullifier_contexts[index]` + 4. Verify the following: + - `read_request.value == target.value` + - `read_request.contract_address == target.contract_address` + - `read_request.counter > target.counter` + 5. When resetting a note hash, verify that the target note hash is not nullified before the read happens: + - `(target.nullifier_counter > read_request.counter) | (target.nullifier_counter == 0)` + + > Given that a reset circuit can execute between two private kernel circuits, there's a possibility that the value being read is emitted in a nested execution and hasn't been included in 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 value has been aggregated to 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#transientaccumulateddata) within its `public_inputs`. + + For each `read_request` at index `i` in the `target_read_requests`, find its `status` at `hints.read_request_statuses[i]`. Verify the following: + + - 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.target_read_requests[status.index]`. + +### Parent Secret Key Validation Request Reset Private Kernel Circuit. + +This reset circuit validates the correct derivation of secret keys used in private functions, and subsequently removes them from the [`transient_accumulated_data`](./private-kernel-initial#transientaccumulateddata) within the `public_inputs` of the [`previous_kernel`](#previouskernel). + + + +Initialize `requests_kept` to `0`. + +For each `request` at index `i` in `key_validation_request_contexts`, locate the `master_secret_key` at `master_secret_keys[i]` and the relevant `app_secret_key` generator at `app_secret_keys_generators[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`.: + + - `public_inputs.transient_accumulated_data.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.parent_public_key == master_secret_key * G` + - Verify that the secret key was correctly derived for the contract: + `request.hardened_child_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. + + 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 nullifier 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#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`. + + > 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#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#transientaccumulateddata) in the `public_inputs`. + + 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): + + - If `nullifier_index_hints[i] == transient_nullifier_indices.len()`: + - Verify that the `nullifier` remains within the [`transient_accumulated_data`](./private-kernel-initial#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 `nullifier_contexts`. + + After these steps, ensure that all nullifiers associated with transient note hashes have been identified and 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#transientaccumulateddata)` in the `public_inputs`. + + 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): + + - 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. + +### Common Verifications + +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). + +The preceding proof can be: + +- [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#public-inputs). + +#### Verifying the transient accumulated data. + +All arrays in the `transient_accumulated_data` in the [`public_inputs`](#public-inputs) must equal their corresponding arrays in [`private_inputs`](#private-inputs).[`previous_kernel`](#previouskernel).[`public_inputs`](./private-kernel-initial#public-inputs).[`transient_accumulated_data`](./private-kernel-initial#transientaccumulateddata), with the exception of those modified by the reset circuits: + +1. [Read request reset circuit](#note-hash-read-request-reset-private-kernel-circuit) (for note hashes): `note_hash_read_requests` +2. [Read request reset circuit](#nullifier-read-request-reset-private-kernel-circuit) (for nullifiers): `nullifier_read_requests` +3. [Parent secret key validation request reset circuit](#nullifier-key-validation-request-reset-private-kernel-circuit) (for nullifier keys): `key_validation_request_contexts` +4. [Transient note reset circuit](#transient-note-reset-private-kernel-circuit): `note_hash_contexts` and `nullifier_contexts` + +#### Verifying other data. + +This section follows the same [process](./private-kernel-inner#verifying-other-data) as outlined in the inner private kernel circuit. + +## `PrivateInputs` + +### `PreviousKernel` + +The format aligns with the [`PreviousKernel`](./private-kernel-inner#previouskernel) of the inner private kernel circuit. + +### _Hints_ for [Read Request Reset Private Kernel Circuit](#read-request-reset-private-kernel-circuit) + +| Field | Type | Description | +| ----------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `reset_type` | `note_hash` \| `nullifier` | The type of read requests to be reset. | +| `transient_read_indices` | [`field`; `N`] | Indices of the read requests for transient values. | +| `pending_value_indices` | [`field`; `N`] | Indices of the values for transient reads. | +| `persistent_read_indices` | [`field`; `M`] | Indices of the read requests for settled values. | +| `read_request_membership_witnesses` | [[`MembershipWitness`](./private-kernel-initial#membershipwitness); `M`] | Membership witnesses for the settled values. | +| `read_request_statuses` | [[`ReadRequestStatus`](#readrequeststatus); `C`] | Statuses of the values being read. `C` equals [`MAX_NOTE_HASH_READ_REQUESTS_PER_TX`](../constants.md#circuit-constants) when `reset_type` is `note_hash`; [`MAX_NULLIFIER_READ_REQUESTS_PER_TX`](../constants.md#circuit-constants) when `reset_type` is `nullifier`. | + +> There can be multiple versions of the read request reset private kernel circuit, each with a different values of `N` and `M`. + +#### `ReadRequestStatus` + +| Field | Type | Description | +| ------- | ------------------------------------- | --------------------------------------- | +| `state` | `persistent` \| `transient` \| `nada` | State of the read request. | +| `index` | `field` | Index of the hint for the read request. | + +### _Hints_ for [Parent Secret Key Validation Request Reset Private Kernel Circuit](#parent-secret-key-validation-request-reset-private-kernel-circuit) + +| Field | Type | Description | +| -------------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------- | +| `master_secret_keys` | [`field`; [`MAX_KEY_VALIDATION_REQUESTS_PER_TX`](../constants.md#circuit-constants)] | Master secret to try to derive app secret keys and pub keys from. | +| `app_secret_keys_generators` | [`field`; [`MAX_KEY_VALIDATION_REQUESTS_PER_TX`](../constants.md#circuit-constants)] | App secret key generators to assist with ^. | + +### _Hints_ for [Transient Note Reset Private Kernel Circuit](#transient-note-reset-private-kernel-circuit) + +| Field | Type | Description | +| ------------------------------------------ | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| `transient_nullifier_indices` | [`field`; [`MAX_NOTE_HASHES_PER_TX`](../constants.md#circuit-constants)] | Indices of the nullifiers for transient notes. | +| `nullifier_index_hints` | [`field`; [`MAX_NULLIFIERS_PER_TX`](../constants.md#circuit-constants)] | Indices of the `transient_nullifier_indices` for transient nullifiers. | +| `encrypted_note_preimage_hash_index_hints` | [`field`; [`MAX_ENCRYPTED_NOTE_PREIMAGE_HASHES_PER_TX`](../constants.md#circuit-constants)] | Indices of the `encrypted_note_preimage_hash_contexts` for transient preimage hashes. | +| `log_note_hash_hints` | [`field`; [`MAX_NOTE_HASHES_PER_TX`](../constants.md#circuit-constants)] | Indices of the `note_hash_contexts` for transient preimage hashes. | + +## `PublicInputs` + +The format aligns with the [`PublicInputs`](./private-kernel-initial#publicinputs) of the initial private kernel circuit. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-kernel-tail.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-kernel-tail.md new file mode 100644 index 000000000000..67450a5b2815 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/private-kernel-tail.md @@ -0,0 +1,253 @@ +# Private Kernel Circuit - Tail + +## Requirements + +The **tail** circuit abstains from processing individual private function calls. Instead, it incorporates the outcomes of a private kernel circuit and conducts additional processing essential for generating the final public inputs suitable for submission to the transaction pool, subsequently undergoing processing by Sequencers and Provers. The final public inputs must safeguard against revealing any private information unnecessary for the execution of public kernel circuits and rollup circuits. + +### Verification of the 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). + +The preceding proof can be: + +- [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#public-inputs)[`.transient_accumulated_data`](./private-kernel-initial#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_request_stack` + +2. The following must be empty to ensure a comprehensive final reset: + + - `note_hash_read_requests` + - `nullifier_read_requests` + - `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. + +### Processing Final Outputs + +#### Siloing values. + +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`: + + 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)` + + > This process does not apply to `nullifier_contexts[0]`, which is the [hash of the transaction request](./private-kernel-initial#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`: + + `note_hash_contexts[i].value = hash(note_nonce, siloed_hash)` + + Where: + + - `note_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 `note_nonce` guarantees that each final note hash is a unique value in the note hash tree. + +3. Silo `l2_to_l1_messages`: + + For each `l2_to_l1_message` at index `i` in `l2_to_l1_message_contexts` within [`private_inputs`], if `l2_to_l1_message.value != 0`: + + `l2_to_l1_message_contexts[i].value = 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#constantdata)[`.tx_context`](./private-kernel-initial#transactioncontext). + +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`: + + `unencrypted_log_hash_contexts[i].value = hash(log_hash.hash, log_hash.contract_address)` + +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`: + + `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)` + + + +#### Verifying and splitting ordered data. + +The initial and inner kernel iterations may produce values in an unordered state due to the serial nature of the kernel, contrasting with the stack-based nature of code execution. + +This circuit ensures the correct ordering of the following: + +- `note_hashes` +- `nullifiers` +- `l2_to_l1_messages` +- `unencrypted_log_hashes` +- `encrypted_log_hashes` +- `encrypted_note_preimage_hashes` +- `public_call_requests` + +In addition, the circuit split the ordered data into `non_revertible_accumulated_data` and `revertible_accumulated_data` using `min_revertible_side_effect_counter`. + +1. Verify ordered `public_call_requests`: + + Initialize `num_non_revertible` and `num_revertible` to `0`. + + For each `request` at index `i` in the **unordered** `public_call_request_contexts` within `private_inputs.previous_kernel.public_inputs.transient_accumulated_data`: + + - Find its associated `mapped_request` in `public_call_requests[public_call_request_hints[i]]` within `public_inputs`. + - If `request.counter < min_revertible_side_effect_counter`: + - The `public_call_requests` is in `non_revertible_accumulated_data`. + - `num_added = num_non_revertible`. + - If `request.counter >= min_revertible_side_effect_counter`: + - The `public_call_requests` is in `revertible_accumulated_data`. + - `num_added = num_revertible`. + - If `request.call_stack_item_hash != 0`, verify that: + - `request == mapped_request` + - If `num_added > 0`, verify that: + - `public_call_requests[num_added].counter < public_call_requests[num_added - 1].counter` + - Increment `num_added` by `1`: `num_(non_)revertible += 1` + - Else: + - All the subsequent requests (`index >= i`) in `public_call_request_contexts` must be empty. + - All the subsequent requests (`index >= num_non_revertible`) in `non_revertible_accumulated_data.public_call_requests` must be empty. + - All the subsequent requests (`index >= num_revertible`) in `revertible_accumulated_data.public_call_requests` must be empty. + + > Note that requests in `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: + + Initialize `num_non_revertible` and `num_revertible` to `0`. + + For each `note_hash_context` at index `i` in the **unordered** `note_hash_contexts` within `private_inputs.previous_kernel.public_inputs.transient_accumulated_data`: + + - Find its associated `note_hash` in `note_hashes[note_hash_hints[i].index]` within `public_inputs`. + - If `note_hash_context.counter < min_revertible_side_effect_counter`: + - The `note_hashes` is in `non_revertible_accumulated_data`. + - `num_added = num_non_revertible`. + - If `note_hash_context.counter >= min_revertible_side_effect_counter`: + - The `note_hashes` is in `revertible_accumulated_data`. + - `num_added = num_revertible`. + - If `note_hash_context.value != 0`, verify that: + - `note_hash == note_hash_context.value` + - `note_hash_hints[note_hash_hints[i].index].counter_(non_)revertible == note_hash_context.counter` + - If `num_added > 0`, verify that: + - `note_hash_hints[num_added].counter_(non_)revertible > note_hash_hints[num_added - 1].counter_(non_)revertible` + - Increment `num_added` by `1`: `num_(non_)revertible += 1` + - Else: + - All the subsequent elements (index `>= i`) in `note_hash_contexts` must be empty. + - All the subsequent elements (index `>= num_non_revertible`) in `non_revertible_accumulated_data.note_hashes` must be empty. + - All the subsequent elements (index `>= num_revertible`) in `revertible_accumulated_data.note_hashes` must be empty. + + Repeat the same process for `nullifiers`, `l2_to_l1_messages`, `unencrypted_log_hashes`, `encrypted_log_hashes`, and `encrypted_note_preimage_hashes`, where: + + - Ordered `nullifiers` and `l2_to_l1_messages` are within [`public_inputs`](#public-inputs). + - `ordered_unencrypted_log_hashes_(non_)revertible`, `ordered_encrypted_log_hashes_(non_)revertible`, and `ordered_encrypted_note_preimage_hashes_(non_)revertible` are provided as [`hints`](#hints) through `private_inputs`. + +> 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` 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` values obtained from private function circuits may leak information. + +The requests in the `public_call_requests` within `public_inputs` have been [sorted in descending order](#verifying-and-splitting-ordered-data) in the previous step. This circuit recalibrates their counters through the following steps: + +- The `counter` of the last non-empty request is set to `1`. +- The `counter`s of the other non-empty requests are continuous values in descending order: + - `public_call_requests[i].counter = public_call_requests[i + 1].counter + 1` + +> It's crucial for the `counter` of the last request to be `1`, as it's assumed in the [tail public kernel circuit](./public-kernel-tail#grouping-storage-writes) that no storage writes have a counter `1`. + +### Validating Public Inputs + +#### Verifying the (non-)revertible accumulated data. + +1. The following must align with the results after ordering, as verified in a [previous step](#verifying-and-splitting-ordered-data): + + - `note_hashes` + - `nullifiers` + - `l2_to_l1_messages` + +2. The `public_call_requests` must [adhere to a specific order](#verifying-ordered-arrays) with [recalibrated counters](#recalibrating-counters), as verified in the previous steps. + +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_(non_)revertible`, which is provided as [hints](#hints), and the [ordering](#verifying-and-splitting-ordered-data) 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` + + Check the values in the `public_inputs` are correct: + + - `unencrypted_logs_hash == accumulated_logs_hash` + - `unencrypted_log_preimages_length == accumulated_logs_length` + + Repeat the same process for `encrypted_logs_hashes` and `encrypted_note_preimages_hashes`. + +#### Verifying the transient accumulated data. + +It ensures that all data in the [`transient_accumulated_data`](./public-kernel-tail#transientaccumulateddata) within [`public_inputs`](#public-inputs) is empty. + +#### Verifying other data. + +This section follows the same [process](./private-kernel-inner.mdx#verifying-other-data) as outlined in the inner private kernel circuit. + +In addition, it checks that the following are empty: + +- `old_public_data_tree_snapshot` +- `new_public_data_tree_snapshot` + +## `PrivateInputs` + +### `PreviousKernel` + +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` | [[`OrderHint`](#orderhint); [`MAX_NOTE_HASHES_PER_TX`](../constants.md#circuit-constants)] | Hints for ordering `note_hash_contexts`. | +| `nullifier_hints` | [[`OrderHint`](#orderhint); [`MAX_NULLIFIERS_PER_TX`](../constants.md#circuit-constants)] | Hints for ordering `nullifier_contexts`. | +| `public_call_request_hints` | [`field`; [`MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX`](../constants.md#circuit-constants)] | Indices of ordered `public_call_request_contexts`. | +| `unencrypted_log_hash_hints` | [[`OrderHint`](#orderhint); [`MAX_UNENCRYPTED_LOG_HASHES_PER_TX`](../constants.md#circuit-constants)] | Hints for ordering `unencrypted_log_hash_contexts`. | +| `ordered_unencrypted_log_hashes_revertible` | [`field`; [`MAX_UNENCRYPTED_LOG_HASHES_PER_TX`](../constants.md#circuit-constants)] | Ordered revertible `unencrypted_log_hashes`. | +| `ordered_unencrypted_log_hashes_non_revertible` | [`field`; [`MAX_UNENCRYPTED_LOG_HASHES_PER_TX`](../constants.md#circuit-constants)] | Ordered non-revertible `unencrypted_log_hashes`. | +| `encrypted_log_hash_hints` | [[`OrderHint`](#orderhint); [`MAX_ENCRYPTED_LOG_HASHES_PER_TX`](../constants.md#circuit-constants)] | Hints for ordering `encrypted_log_hash_contexts`. | +| `ordered_encrypted_log_hashes_revertible` | [`field`; [`MAX_ENCRYPTED_LOG_HASHES_PER_TX`](../constants.md#circuit-constants)] | Ordered revertible `encrypted_log_hashes`. | +| `ordered_encrypted_log_hashes_non_revertible` | [`field`; [`MAX_ENCRYPTED_LOG_HASHES_PER_TX`](../constants.md#circuit-constants)] | Ordered non-revertible `encrypted_log_hashes`. | +| `encrypted_note_preimage_hints` | [[`OrderHint`](#orderhint); [`MAX_ENCRYPTED_NOTE_PREIMAGE_HASHES_PER_TX`](../constants.md#circuit-constants)] | Hints for ordering `encrypted_note_preimage_hash_contexts`. | +| `ordered_encrypted_note_preimage_hashes_revertible` | [`field`; [`MAX_ENCRYPTED_NOTE_PREIMAGE_HASHES_PER_TX`](../constants.md#circuit-constants)] | Ordered revertible `encrypted_note_preimage_hashes`. | +| `ordered_encrypted_note_preimage_hashes_non_revertible` | [`field`; [`MAX_ENCRYPTED_NOTE_PREIMAGE_HASHES_PER_TX`](../constants.md#circuit-constants)] | Ordered non-revertible `encrypted_note_preimage_hashes`. | + +#### `OrderHint` + +| Field | Type | Description | +| ------------------------ | ------- | ---------------------------------------------------------------------- | +| `index` | `field` | Index of the mapped element in the ordered array. | +| `counter_revertible` | `u32` | Counter of the element at index i in the revertible ordered array. | +| `counter_non_revertible` | `u32` | Counter of the element at index i in the non-revertible ordered array. | + +## `PublicInputs` + +The format aligns with the [Public Inputs](./public-kernel-tail#public-inputs) of the tail public kernel circuit. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/public-kernel-initial.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/public-kernel-initial.md new file mode 100644 index 000000000000..3ebcc0d8d7f2 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/public-kernel-initial.md @@ -0,0 +1,61 @@ +# 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. + +### Verification of the 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). + +The preceding proof can only be: + +- [Tail private kernel proof](./private-kernel-tail.md). + +### Public Inputs Data Reset + +#### Recalibrating counters. + +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#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`. + +> 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#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#public-inputs). + +#### Verifying the transient accumulated data. + +It ensures that all data in the [`transient_accumulated_data`](./public-kernel-tail#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). + +#### Verifying the constant data. + +This section follows the same [process](./private-kernel-inner.mdx#verifying-the-constant-data) as outlined in the inner private kernel circuit. + +## `PrivateInputs` + +### `PreviousKernel` + +The format aligns with the [PreviousKernel](./private-kernel-tail.md#previouskernel)` of the tail public kernel circuit. + +## `PublicInputs` + +The format aligns with the [`PublicInputs`](./public-kernel-tail#public-inputs)` of the tail public kernel circuit. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/public-kernel-inner.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/public-kernel-inner.md new file mode 100644 index 000000000000..19141a131a2d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/public-kernel-inner.md @@ -0,0 +1,235 @@ +# 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. + +### Verification of the 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). + +The preceding proof can be: + +- [Initial public kernel proof](./public-kernel-initial.md). +- Inner public kernel proof. + +### Processing Public Function Call + +#### Ensuring the function being called exists in the contract. + +This section follows the same [process](./private-kernel-initial#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#constantdata). + +#### 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. + +This circuit will: + +1. Pop the request from the stack: + + - `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. + +#### Ensuring this function is called with the correct context. + +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. + +It verifies that the public function was executed with the provided proof data, verification key, and the public inputs of the VM circuit. The result of the execution is specified in the public inputs, which will be used in subsequent steps to enforce the conditions they must satisfy. + +#### 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): + +- The `header` must match the one in the [`constant_data`](./private-kernel-initial#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): + + - 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): + + - 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 ordered arrays include: + + - `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): + + - 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). + +### 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). + + - `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#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: + +- `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). + +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`: + + - `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`: + + - 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`: + + - `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#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_. + +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. + + > 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. + + > Zero serves as an indicator for an unchanged update, as this value can never act as the counter of a write. + +#### Verifying the constant data. + +This section follows the same [process](./private-kernel-inner.mdx#verifying-the-constant-data) as outlined in the inner private kernel circuit. + +## `PrivateInputs` + +### `PreviousKernel` + +The format aligns with the [`PreviousKernel`](./private-kernel-tail.md#previouskernel) of the tail public kernel circuit. + +### `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` | [`ContractClass`](./private-kernel-initial#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. | + +## `PublicInputs` + +The format aligns with the [`PublicInputs`](./public-kernel-tail#public-inputs) of the tail public kernel circuit. + +## Types + +### `PublicCallStackItem` + +| Field | Type | Description | +| ------------------ | ----------------------------------------------------------- | --------------------------------------------------------- | +| `contract_address` | `AztecAddress` | Address of the contract on which the function is invoked. | +| `function_data` | [`FunctionData`](./private-kernel-initial#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#storageread) | Data read from the public data tree. | +| `storage_writes` | [`[StorageWrite; C]`](./public-kernel-tail#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/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/public-kernel-tail.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/public-kernel-tail.md new file mode 100644 index 000000000000..3209d4d02900 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/circuits/public-kernel-tail.md @@ -0,0 +1,429 @@ +# 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. + +### Verification of the 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). + +The preceding proof can only be: + +- [Inner public kernel proof](./public-kernel-inner.md). + +#### Ensuring the previous iteration is the last. + +The following must be empty to ensure all the public function calls are processed: + +- `public_call_requests` in both `revertible_accumulated_data` and `non_revertible_accumulated_data` within [`private_inputs`](#private-inputs)[`.previous_kernel`](#previouskernel)[`.public_inputs`](./public-kernel-tail#public-inputs). + +### Processing Final Outputs + +#### Siloing values. + +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: + +- `storage_reads` +- `storage_writes` + +The siloed storage slot is computed as: `hash(contract_address, storage_slot)`. + +#### Verifying ordered arrays. + +The iterations of the public kernel may yield values in an unordered state due to the serial nature of the kernel, which contrasts with the stack-based nature of code execution. + +This circuit ensures the correct ordering of the following: + +- `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#public-inputs)[`.accumulated_data`](./public-kernel-tail#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: + + 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` + - Else: + - 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`. + +#### 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. + +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. + +This circuit ensures the uniqueness of each snap in `public_data_snaps`. It verifies that: + +For each snap at index `i`, where `i` > 0: + +- 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. + +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`: + + - 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` + + > 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`: + + - 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` + +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 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`. + +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`: + + - 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` + + 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: + - 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]`. + + > 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`: + + - 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)` + + > 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_. + +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. + +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: + + - 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`. + +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: + + - 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`. + - 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`. + +> 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`. +- 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` + +### Validating Public Inputs + +#### Verifying the accumulated data. + +1. The following must align with the results after siloing, as verified in a [previous step](#siloing-values): + + - `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` + +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). + + 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` + + Check the values in the `public_inputs` are correct: + + - `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` + +#### Verifying the transient accumulated data. + +It ensures that the transient accumulated data is empty. + +#### Verifying the constant data. + +This section follows the same [process](./private-kernel-inner.mdx#verifying-the-constant-data) as outlined in the inner private kernel circuit. + +## `PrivateInputs` + +### `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#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; 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#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#membershipwitness) | Membership witnesses for public data tree. `C` equals the length of `ordered_storage_writes`. | +| `subtree_membership_witnesses` | [`[MembershipWitness; C]`](./private-kernel-initial#membershipwitness) | Membership witnesses for the public data subtree. `C` equals the length of `ordered_storage_writes`. | + +## Public Inputs + +| Field | Type | Description | +| --------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------- | +| `constant_data` | [`ConstantData`](#constantdata) | | +| `revertible_accumulated_data` | [`RevertibleAccumulatedData`](#revertibleaccumulateddata) | | +| `non_revertible_accumulated_data` | [`NonRevertibleAccumulatedData`](#nonrevertibleaccumulateddata) | | +| `transient_accumulated_data` | [`TransientAccumulatedData`](#transientaccumulateddata) | | +| `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. | + +### `ConstantData` + +These are constants that remain the same throughout the entire transaction. Its format aligns with the [ConstantData](./private-kernel-initial#constantdata) of the initial private kernel circuit. + +### `RevertibleAccumulatedData` + +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. | +| `public_call_requests` | [`[PublicCallRequestContext; C]`](#publiccallrequestcontext) | Requests to call public functions. | + +> The above `C`s represent constants defined by the protocol. Each `C` might have a different value from the others. + +### `NonRevertibleAccumulatedData` + +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. | +| `public_call_requests` | [`[PublicCallRequestContext; C]`](#publiccallrequestcontext) | Requests to call public functions. | + +> 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#notehashcontext) | Note hashes with extra data aiding verification. | +| `nullifier_contexts` | [`[NullifierContext; C]`](./private-kernel-initial#nullifiercontext) | Nullifiers with extra data aiding verification. | +| `l2_to_l1_message_contexts` | [`[L2toL1MessageContext; C]`](./private-kernel-initial#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. | + +> The above `C`s represent constants defined by the protocol. Each `C` might have a different value from the others. + +## Types + +### `TreeSnapshot` + +| Field | Type | Description | +| --------------------------- | ------- | --------------------------------- | +| `root` | `field` | Root of the tree. | +| `next_available_leaf_index` | `field` | The index to insert new value to. | + +### `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` | `u32` | Counter at which the read happened. | + +### `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` | `u32` | Counter at which the write happened. | + +### `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` | `u32` | Counter at which the read happened. | + +### `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` | `u32` | 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` + +| 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. | + +### `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. | + +### `PublicCallRequestContext` + +| Field | Type | Description | +| ------------------------- | ------------------------------------------------------------- | --------------------------------------------- | +| `call_stack_item_hash` | `field` | Hash of the call stack item. | +| `counter` | `u32` | Counter at which the request was made. | +| `caller_contract_address` | `AztecAddress` | Address of the contract calling the function. | +| `caller_context` | [`CallerContext`](./private-kernel-initial#callercontext) | Context of the contract calling the function. | diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/constants.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/constants.md new file mode 100644 index 000000000000..1ca57a885157 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/constants.md @@ -0,0 +1,126 @@ +--- +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 | +|---|---|---| +| `RETURN_VALUES_LENGTH` | 4 | +| `MAX_NOTE_HASHES_PER_CALL` | 128 | +| `MAX_NULLIFIERS_PER_CALL` | 128 | +| `MAX_L2_TO_L1_MSGS_PER_CALL` | 4 | +| `MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL` | 128 | +| `MAX_PUBLIC_DATA_READS_PER_CALL` | 128 | +| `MAX_UNENCRYPTED_LOG_HASHES_PER_CALL` | 128 | +| `MAX_ENCRYPTED_LOG_HASHES_PER_CALL` | 128 | +| `MAX_ENCRYPTED_NOTE_PREIMAGE_HASHES_PER_CALL` | 128 | +| `MAX_NOTE_HASH_READ_REQUESTS_PER_CALL` | 128 | +| `MAX_NULLIFIER_READ_REQUESTS_PER_CALL` | 128 | +| `MAX_KEY_VALIDATION_REQUESTS_PER_CALL | 16 | TODO: we shouldn't need this, given the reset circuit. | +| `MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL` | 32 | +| `MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL` | 32 | + +### Per Tx + + +| Name | Value | Description | +|---|---|---| +| `MAX_NOTE_HASHES_PER_TX` | 128 | +| `MAX_NULLIFIERS_PER_TX` | 128 | +| `MAX_L2_TO_L1_MSGS_PER_TX` | 16 | +| `MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX` | 31 | +| `MAX_PUBLIC_DATA_READS_PER_TX` | 16 | +| `MAX_UNENCRYPTED_LOG_HASHES_PER_TX` | 128 | +| `MAX_ENCRYPTED_LOG_HASHES_PER_TX` | 128 | +| `MAX_ENCRYPTED_NOTE_PREIMAGE_HASHES_PER_TX` | 128 | +| `MAX_OPTIONALLY_REVEALED_DATA_LENGTH_PER_TX` | 4 | +| `MAX_NOTE_HASH_READ_REQUESTS_PER_TX` | 128 | TODO: we shouldn't need this, given the reset circuit. | +| `MAX_KEY_VALIDATION_REQUESTS_PER_TX` | 64 | TODO: we shouldn't need this, given the reset circuit. | +| `MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX` | 32 | +| `MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX` | 32 | + +## 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. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/contract-deployment/classes.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/contract-deployment/classes.md new file mode 100644 index 000000000000..dc77754f97c0 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/contract-deployment/classes.md @@ -0,0 +1,368 @@ +# Contract classes + +A contract class is a collection of state variable declarations, and related utility, private, and public functions. Contract classes don't have any initialized state, they just define code. A contract class cannot be called; only a contract instance can be called. + +## Rationale + +Contract classes simplify the process of reusing code by enshrining implementations as a first-class citizen at the protocol. Given multiple [contract instances](./instances.md) that rely on the same class, the class needs to be declared only once, reducing the deployment cost for all contract instances. Classes also simplify the process of upgradeability; classes decouple state from code, making it easier for an instance to switch to different code while retaining its state. + +:::info +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) + +::: + +## `ContractClass` + +The structure of a contract class is defined as: + + +| Field | Type | Description | +|----------|----------|----------| +| `version` | `u8` | Version identifier. Initially one, bumped for any changes to the contract class struct. | +| `artifact_hash` | `Field` | Hash of the contract artifact. The specification of this hash is not enforced by the protocol. Should include commitments to utility function code and compilation metadata. Intended to be used by clients to verify that an off-chain fetched artifact matches a registered class. | +| `private_functions` | [`PrivateFunction[]`](#private-function) | List of individual private functions, constructors included. | +| `packed_public_bytecode` | `Field[]` | [Packed bytecode representation](../public-vm/bytecode-validation-circuit.md#packed-bytecode-representation) of the AVM bytecode for all public functions in this contract. | + +The public function are sorted in ascending order by their function selector before being packed. This is to ensure consistent hashing later. + +Note that individual public functions are not first-class citizens in the protocol, so the contract entire public function bytecode is stored in the class, unlike private functions which are differentiated individual circuits recognized by the protocol. + +As for utility functions, these are not part of the protocol when it comes to contract classes, since they are never invoked in either private or public calls. However, commitments to them can (and should) be made in the `artifacts_hash`, so that third parties can validate the code they run and expose their secrets to. + +### `contract_class_id` + +Also known as `contract_class_id`, the Class Identifier is both a unique identifier and a commitment to the struct contents. It is computed as: + + + + + +```rust +contract_class_id_crh( + artifact_hash: Field + private_functions: PrivateFunction[], + packed_public_bytecode: bytes[], +) -> Field { + let private_function_leaves: Field[] = private_functions.map(|f| private_function_leaf_crh(f)); + + // Illustrative function, not defined. TODO. + let private_function_tree_root: Field = merkleize(private_function_leaves); + + // Illustrative function, not defined. TODO. + let public_bytecode_commitment: Point = calculate_commitment(packed_public_bytecode); + + let contract_class_id = poseidon2( + be_string_to_field("az_contract_class_id"), + + artifact_hash, + private_function_tree_root, + public_bytecode_commitment.x, + public_bytecode_commitment.y, + ); + + contract_class_id +} +``` + +> See below for `private_function_leaf_crh`. +> Private Functions are sorted in ascending order by their selector, and then hashed into Function Leaves, before being merkleized into a tree of height [`PRIVATE_FUNCTION_TREE_HEIGHT`](../constants.md#tree-constants). +> Empty leaves have value `0`. +> The AVM public bytecode commitment is calculated as [defined in the Public VM section](../public-vm/bytecode-validation-circuit.md#committed-representation). + +### `PrivateFunction` + +The structure of each private function within the protocol is the following: + + +| Field | Type | Description | +|----------|----------|----------| +| `function_selector` | `u32` | Selector of the function. Calculated as the hash of the method name and parameters. The specification of this is not enforced by the protocol. | +| `vk_hash` | `Field` | Hash of the verification key associated to this private function. | + +Note the lack of visibility modifiers. Internal functions are specified as a macro, and the check is handled at the application circuit level by verifying that the `context.msg_sender` equals the contract current address. + +Also note the lack of commitment to the function compilation artifact. Even though a commitment to a function is required so that the PXE can verify the execution of correct unconstrained Brillig code embedded within private functions, this is handled entirely out of protocol. As such, PXEs are expected to verify it against the `artifact_hash` in the containing contract class. + +#### Private Function Leaf Hash + + + +```rust +private_function_leaf_crh( + f: PrivateFunction +) -> Field { + let private_function_leaf = poseidon2( + be_string_to_field("az_private_function_leaf"), + + be_bits_to_field(f.function_selector), + f.vk_hash + ); + + private_function_leaf +} +``` + +### Artifact Hash + +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). + +Note: below, `sha256_modulo(x) = sha256(x) % FIELD_MODULUS`. This approach must not be used if seeking pseudo-randomness, but can be used for collision resistance. + + + +```rust +artifact_crh( + artifact // This type is out of protocol, e.g. the format output by Nargo +) -> Field { + + let private_functions_artifact_leaves: Field[] = artifact.private_functions.map(|f| + sha256_modulo( + VERSION, // 8-bits + f.selector, // 32-bits + f.metadata_hash, // 256-bits + sha256(f.private_bytecode) + ) + ); + let private_functions_artifact_tree_root: Field = merkleize(private_functions_artifact_leaves); + + let utility_functions_artifact_leaves: Field[] = artifact.utility_functions.map(|f| + sha256_modulo( + VERSION, // 8-bits + f.selector, // 32-bits + f.metadata_hash, // 256-bits + sha256(f.utility_bytecode) + ) + ); + let utility_functions_artifact_tree_root: Field = merkleize(utility_functions_artifact_leaves); + + let artifact_hash: Field = sha256_modulo( + VERSION, // 8-bits + private_functions_artifact_tree_root, // 256-bits + utility_functions_artifact_tree_root, // 256-bits + artifact_metadata_hash + ); + + let artifact_hash: Field = artifact_hash_256_bit % FIELD_MODULUS; + + artifact_hash +} +``` + +For the artifact hash merkleization and hashing is done using sha256, since it is computed and verified outside of circuits and does not need to be SNARK friendly, and then wrapped around the field's maximum value. Fields are left-padded with zeros to 256 bits before being hashed. + +Function leaves are sorted in ascending order before being merkleized, according to their function selectors. Note that a tree with dynamic height is built instead of having a tree with a fixed height, since the merkleization is done out of a circuit. + + + +Bytecode for private functions is a mix of ACIR and Brillig, whereas utility function bytecode is Brillig exclusively, as described on the [bytecode section](../bytecode/index.md). + +The metadata hash for each function is suggested to be computed as the sha256 (modulo) of all JSON-serialized ABI types of the function returns. Other function data is represented in the leaf hash by its bytecode and selector. + + + +```rust +function_metadata_crh( + function // This type is out of protocol, e.g. the format output by Nargo +) -> Field { + let function_metadata_hash: Field = sha256_modulo( + json_serialize(function.return_types) + ); + + function_metadata_hash +} +``` + + + +The artifact metadata stores all data that is not contained within the contract functions, is not debug specific, and is not derivable from other properties. This leaves only the `name` and `outputs` properties. Metadata is JSON-serialized in the same fashion as the function metadata. + +```rust +artifact_metadata_crh( + artifact // This type is out of protocol, e.g. the format output by Nargo +) -> Field { + let artifact_metadata = pick(artifact, "name", "outputs"); + + let artifact_metadata_hash: Field = sha256_modulo( + json_serialize(artifact_metadata) + ); + + artifact_metadata_hash +} +``` + +### Versioning + +A contract class has an implicit `version` field that identifies the schema of the struct. This allows to change the shape of a contract class in future upgrades to the protocol to include new fields or change existing ones, while preserving the structure for existing classes. Supporting new types of contract classes would require introducing new kernel circuits, and a transaction proof may require switching between different kernel circuits depending on the version of the contract class used for each function call. + +Note that the version field is not directly used when computing the contract class id, but is implicit in the generator index. Bumping the version of a contract class struct would involve using a different generator index for computing its id. + +## Canonical Contract Class Registerer + +A contract class is registered by calling a private `register` function in a canonical `ContractClassRegisterer` contract, which will emit a Registration Nullifier. The Registration Nullifier is defined as the `contract_class_id` itself of the class being registered. Note that the Private Kernel circuit will [silo](../circuits/private-kernel-tail.md#siloing-values) this value with the contract address of the `ContractClassRegisterer`, effectively storing the hash of the `contract_class_id` and `ContractClassRegisterer` address in the nullifier tree. As such, proving that a given contract class has been registered requires checking existence of this siloed nullifier. + +The rationale for the Registerer contract is to guarantee that the public bytecode for a contract class is publicly available. This is a requirement for publicly [deploying a contract instance](./instances.md#publicly_deployed), which ultimately prevents a sequencer from executing a public function for which other nodes in the network may not have the code. + +### Register Function + +The `register` function receives the artifact hash, private functions tree root, and packed public bytecode of a `ContractClass` struct as [defined above](#structure), and performs the following steps: + +- Assert that `packed_public_bytecode` is valid according to the definition in the [Public VM section](../public-vm/bytecode-validation-circuit.md#packed-bytecode-representation). +- Computes the `contract_class_id` as [defined above](#class-identifier). +- Emits the resulting `contract_class_id` as a nullifier to prevent the same class from being registered again. +- Emits an unencrypted event `ContractClassRegistered` with the contents of the contract class. + +In pseudocode: + +```rust +fn register( + artifact_hash: Field, + private_functions_root: Field, + public_bytecode_commitment: Point, + packed_public_bytecode: Field[], +) { + assert(is_valid_packed_public_bytecode(packed_public_bytecode)); + + let computed_bytecode_commitment: Point = calculate_commitment(packed_public_bytecode); + + assert(public_bytecode_commitment == computed_bytecode_commitment); + + let version: Field = 1; + let contract_class_id = contract_class_id_crh(version, artifact_hash, private_functions_root, bytecode_commitment); + + emit_nullifier(contract_class_id); + + emit_public_log(ContractClassRegistered::new( + contract_class_id, + version, + artifact_hash, + private_functions_root, + packed_public_bytecode + )); +} +``` + +Upon seeing a `ContractClassRegistered` event in a mined transaction, nodes are expected to store the contract class, so they can retrieve it when executing a public function for that class. Note that a class may be used for deploying a contract within the same transaction in which it is registered. + +Note that emitting the `contract_class_id` as a nullifier (the `contract_class_id_nullifier`), instead of as an entry in the note hashes tree, allows nodes to prove non-existence of a class. This is needed so a sequencer can provably revert a transaction that includes a call to an unregistered class. + +### 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`](../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 and utility functions 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 function 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. + +```rust +fn broadcast_private_function( + contract_class_id: Field, + artifact_metadata_hash: Field, + utility_functions_artifact_tree_root: Field, + private_function_tree_sibling_path: Field[], + private_function_tree_leaf_index: Field, + artifact_function_tree_sibling_path: Field[], + artifact_function_tree_leaf_index: Field, + function: { selector: Field, metadata_hash: Field, vk_hash: Field, bytecode: Field[] }, +) + emit_public_log ClassPrivateFunctionBroadcasted( + contract_class_id, + artifact_metadata_hash, + utility_functions_artifact_tree_root, + private_function_tree_sibling_path, + private_function_tree_leaf_index, + artifact_function_tree_sibling_path, + artifact_function_tree_leaf_index, + function, + ) +``` + +```rust +fn broadcast_utility_function( + contract_class_id: Field, + artifact_metadata_hash: Field, + private_functions_artifact_tree_root: Field, + artifact_function_tree_sibling_path: Field[], + artifact_function_tree_leaf_index: Field + function: { selector: Field, metadata_hash: Field, bytecode: Field[] }[], +) + emit_public_log ClassUtilityFunctionBroadcasted( + contract_class_id, + artifact_metadata_hash, + private_functions_artifact_tree_root, + artifact_function_tree_sibling_path, + artifact_function_tree_leaf_index, + function, + ) +``` + + + +The broadcast functions are split between private and utility to allow for private bytecode to be broadcasted, which is valuable for composability purposes, without having to also include utility functions, which could be costly to do due to data broadcasting costs. Additionally, note that each broadcast function must include enough information to reconstruct the `artifact_hash` from the Contract Class, so nodes can verify it against the one previously registered. + +A node that captures a `ClassPrivateFunctionBroadcasted` should perform the following validation steps before storing the private function information in its database: + +``` +// Load contract class from local db +contract_class = db.get_contract_class(contract_class_id) + +// Compute function leaf and assert it belongs to the private functions tree +function_leaf = pedersen([selector as Field, vk_hash], GENERATOR__FUNCTION_LEAF) +computed_private_function_tree_root = compute_root(function_leaf, private_function_tree_sibling_path, private_function_tree_leaf_index) +assert computed_private_function_tree_root == contract_class.private_function_root + +// Compute artifact leaf and assert it belongs to the artifact +artifact_function_leaf = sha256(selector, metadata_hash, sha256(bytecode)) +computed_artifact_private_function_tree_root = compute_root(artifact_function_leaf, artifact_function_tree_sibling_path, artifact_function_tree_leaf_index) +computed_artifact_hash = sha256(computed_artifact_private_function_tree_root, utility_functions_artifact_tree_root, artifact_metadata_hash) +assert computed_artifact_hash == contract_class.artifact_hash +``` + + + +The check for a utility function is similar: + +``` +// Load contract class from local db +contract_class = db.get_contract_class(contract_class_id) + +// Compute artifact leaf and assert it belongs to the artifact +artifact_function_leaf = sha256(selector, metadata_hash, sha256(bytecode)) +computed_artifact_utility_function_tree_root = compute_root(artifact_function_leaf, artifact_function_tree_sibling_path, artifact_function_tree_leaf_index) +computed_artifact_hash = sha256(private_functions_artifact_tree_root, computed_artifact_utility_function_tree_root, artifact_metadata_hash) +assert computed_artifact_hash == contract_class.artifact_hash +``` + +It is strongly recommended for developers registering new classes to broadcast the code for `compute_hash_and_nullifier`, so any private message recipients have the code available to process their incoming notes. However, the `ContractClassRegisterer` contract does not enforce this during registration, since it is difficult to check the multiple signatures for `compute_hash_and_nullifier` as they may evolve over time to account for new note sizes. + +### Encoding Bytecode + +The `register`, `broadcast_utility_function`, and `broadcast_private_function` functions all receive and emit variable-length bytecode in contract class logs. In every function, bytecode is encoded in a fixed-length array of field elements, which sets a maximum length for each: + +- `MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS`: 3000 field elements, used for a contract's public bytecode in the `register` function. +- `MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS`: 3000 field elements, used for the ACIR and Brillig bytecode of a broadcasted private function in `broadcast_private_function`. +- `MAX_PACKED_BYTECODE_SIZE_PER_UTILITY_FUNCTION_IN_FIELDS`: 3000 field elements, used for the Brillig bytecode of a broadcasted utility function in `broadcast_utility_function`. + +To encode the bytecode into a fixed-length array of Fields, the bytecode is first split into 31-byte chunks, and each chunk interpreted big-endian as a field element. The total length in bytes is then prepended as an initial element, and then right-padded with zeroes. + +The log itself is prepended by the address of the contract that emitted it. This is not strictly necessary because the only contract able to broadcast contract class logs is the `ContractClassRegisterer` (this is enforced in the kernel circuits), but exists to easily check and manage logs of published blocks. + +``` +chunks = chunk bytecode into 31 bytes elements, last element right-padded with zeroes +fields = right-align each chunk into 32 bytes and cast to a field element +padding = repeat a zero-value field MAX_SIZE - fields.count - 1 times +encoded = [bytecode.length as field, contract_address, ...fields, ...padding] +``` + +## Discarded Approaches + +### Bundling private function information into a single tree + +Data about private functions is split across two trees: one for the protocol, that deals only with selectors and verification keys, and one for the artifact, which deals with bytecode and metadata. While bundling together both trees would simplify the representation, it would also pollute the protocol circuits and require more hashing there. In order to minimize in-circuit hashing, we opted for keeping non-protocol info completely out of circuits. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/contract-deployment/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/contract-deployment/index.md new file mode 100644 index 000000000000..4f9def87393b --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/contract-deployment/index.md @@ -0,0 +1,11 @@ +--- +title: Contract Deployment +--- + +# Contract Deployment + +Contracts in Aztec are deployed as _instances_ of a contract _class_. Deploying a new contract then requires first registering the _class_, if it has not been registered before, and then creating an _instance_ that references the class. Both classes and instances are committed to in the nullifier tree in the global state, and are created via a call to a canonical class registry or instance deployer contract respectively. + +import DocCardList from '@theme/DocCardList'; + + diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/contract-deployment/instances.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/contract-deployment/instances.md new file mode 100644 index 000000000000..82c770a096a9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/contract-deployment/instances.md @@ -0,0 +1,192 @@ +# 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 + +- Users must be able to precompute the address of a given contract instance. This allows users to precompute their account contract addresses and receive funds before interacting with the chain, and also allows counterfactual deployments. +- An address must be linkable to its deployer address. This allows simple diversified and stealth account contracts. Related, a precomputed deployment may or may not be restricted to be executed by a given address. +- 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` + +The structure of a contract instance is defined as: + + +| Field | Type | Description | +|----------|----------|----------| +| `version` | `u8` | Version identifier. Initially one, bumped for any changes to the contract instance struct. | +| `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. | +| `public_keys_hash` | `Field` | Optional hash of the struct of public keys used for encryption and nullifying by this contract. | + + + + + +### Versioning + +Contract instances have a `version` field that identifies the schema of the instance, allowing for changes to the struct in future versions of the protocol, same as the contract class [version](./classes.md#versioning). + +### 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/address.md#address). This computation is deterministic, which allows any user to precompute the expected deployment address of their contract, including account contracts. + +### Deployer + +The `deployer` address of a contract instance is used to restrict who can initialize the contract (ie call its constructor) and who can publicly deploy it. Note that neither of these checks are enforced by the protocol: the initialization is checked by the constructor itself, and the deployment by the `ContractInstanceDeployer` (described below). Furthermore, a contract class may choose to not enforce this restriction by removing the check from the constructor. + +The `deployer` address can be set to zero to signal that anyone can initialize or publicly deploy an instance. + +## Initialization + +A contract instance at a given address can be either Initialized or not. An address by default is not initialized, and it is considered to be Initialized once it emits an Initialization Nullifier, meaning it can only be initialized once. + +### Uninitialized + +The default state for any given address is to be uninitialized, meaning its constructor has not been called. A user who knows the preimage of the address can still issue a private call into a function in the contract, as long as that function does not assert that the contract has been initialized by checking the Initialization Nullifier. + +All function calls to an Uninitialized contract that depend on the contract being initialized should fail, to prevent the contract from being used in an invalid state. + +This state allows using a contract privately before it has been initialized or deployed, which is used in [diversified and stealth accounts](../addresses-and-keys/diversified-and-stealth.md). + +### Initialized + +An instance is Initialized when a constructor for the instance has been invoked, and the constructor has emitted the instance's Initialization Nullifier. All private functions that require the contract to be initialized by checking the existence of the Initialization Nullifier can now be called by any user who knows the address preimage. + +The Initialization Nullifier is defined as the contract address itself. Note that the nullifier later gets [siloed by the Private Kernel Circuit](../circuits/private-kernel-tail.md#siloing-values) before it gets broadcasted in a transaction. + +:::warning +It may be the case that it is not possible to read a nullifier in the same transaction that it was emitted due to protocol limitations. That would lead to a contract not being callable in the same transaction as it is initialized. To work around this, we can emit an Initialization Commitment along with the Initialization Nullifier, which _can_ be read in the same transaction as it is emitted. If needed, the Initialization Commitment is defined exactly as the Initialization Nullifier. +::: + +### Constructors + +Contract constructors are not enshrined in the protocol, but handled at the application circuit level. Constructors are methods used for initializing a contract, either private or public, and a contract may have more than a single constructor. A contract must ensure the following requirements are met: + +- A contract may be initialized at most once +- A contract must be initialized using the method and arguments defined in its address preimage +- A contract must be initialized by its `deployer` (if it's non-zero) +- All functions that depend on contract initialization cannot be invoked until the contract is initialized + +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). + +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. + +## Public Deployment + +A Contract Instance is considered to be Publicly Deployed when it has been broadcasted to the network via a canonical `ContractInstanceDeployer` contract, which also emits a Deployment Nullifier associated to the deployed instance. + +All public function calls to an Undeployed address _must_ fail, since the Contract Class for it is not known to the network. If the Class is not known to the network, then an Aztec Node, whether it is the elected sequencer or a full node following the chain, may not be able to execute the bytecode for a public function call, which is undesirable. + +The failing of public function calls to Undeployed addresses is enforced by having the Public Kernel Circuit check that the Deployment Nullifier for the instance has been emitted. Note that makes Public Deployment a protocol-level concern, whereas Initialization is purely an application-level concern. Also, note that this requires hardcoding the address of the `ContractInstanceDeployer` contract in a protocol circuit. + +The Deployment Nullifier is defined as the address of the contract being deployed. Note that it later gets [siloed](../circuits/private-kernel-tail.md#siloing-values) using the `ContractInstanceDeployer` address by the Kernel Circuit, so this nullifier is effectively the hash of the deployed contract address and the `ContractInstanceDeployer` address. + +### Canonical Contract Instance Deployer + +A new contract instance can be _Publicly Deployed_ by calling a `deploy` function in a canonical `ContractInstanceDeployer` contract. This function receives the arguments for a `ContractInstance` struct as described [above](#contractinstance-structure): + +- Validates the referenced `contract_class_id` exists. This can be done via either a call to the `ClassRegisterer` contract, or by directly reading the corresponding nullifier. +- Set `deployer` to zero or `msg_sender` depending on whether the `universal_deploy` flag is set. +- Computes the resulting `new_contract_address`. +- Emits the resulting address as the Deployment Nullifier to signal the public deployment, so callers can prove that the contract has or has not been publicly deployed. +- Emits an unencrypted event `ContractInstanceDeployed` with the address preimage. + +The pseudocode for the process described above is the following: + + + +```rust +fn deploy ( + salt: Field, + contract_class_id: Field, + initialization_hash: Field, + public_keys_hash: Field, + universal_deploy?: boolean, +) + let contract_class_registerer: Contract = ContractClassRegisterer::at(CONTRACT_CLASS_REGISTERER_ADDRESS); + + assert(nullifier_exists(silo(contract_class_id, contract_class_registerer.address))); + + let deployer: Address = if universal_deploy { 0 } else { msg_sender }; + let version: Field = 1; + + let address = address_crh( + version, + salt, + deployer, + contract_class_id, + initialization_hash, + public_keys_hash + ); + + emit_nullifier(address); + + emit_public_log(ContractInstanceDeployed::new(address, version, salt, contract_class_id, initialization_hash, public_keys_hash)); +``` + +> See [address](../addresses-and-keys/address.md) for `address_crh`. + +Upon seeing a `ContractInstanceDeployed` event from the canonical `ContractInstanceDeployer` contract, nodes are expected to store the address and preimage, so they can verify executed code during public code execution as described in the next section. + +The `ContractInstanceDeployer` contract provides two implementations of the `deploy` function: a private and a public one. + +### 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`](../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). + + + +## Verification of Executed Code + +The Kernel Circuit, both private and public, is responsible for verifying that the code loaded for a given function execution matches the expected one. This requires the following checks: + +- The `contract_class_id` of the address called is the expected one, verified by hashing the address preimage that includes the `contract_class_id`. +- 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. Note that the `verification_key` must include an identifier of the proving system used to compute it. + + + +Specific to public functions: + +- The bytecode loaded by the [AVM](../public-vm/intro) for the contract matches the `bytecode_commitment` in the contract class, verified using the [bytecode validation circuit](../public-vm/bytecode-validation-circuit). +- 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. + +Note that, since constructors are handled at the application level, the kernel circuit is not required to check the Initialization Nullifier before executing code. + +### Verifying Brillig in Private Functions + +Private functions may have unconstrained code, inlined as Brillig bytecode. While unconstrained code, as it name implies, is not constrained within the protocol, a user PXE still needs a mechanism to verify that the code it has been delivered off-chain for a given function is correct. + +This verification is done via the [contract class `artifact_hash`](./classes.md#structure), which contains a commitment to all bytecode in the contract. The PXE should receive the entire contract artifact, or at least the relevant sections to execute along with the commitments for the others to reconstruct the original `artifact_hash`, and verify that the resulting `artifact_hash` matches the one declared on-chain for the class of the contract being run. + +## Discarded Approaches + +### Contracts Tree + +Earlier versions of the protocol relied on a dedicated contract tree, which required dedicated kernel code to process deployments, which had to be enshrined as new outputs from the application circuits. By abstracting contract deployment and storing deployments as nullifiers, the interface between the application and kernel circuits is simplified, and the kernel circuit has far fewer responsibilities. Furthermore, multiple contract deployments within a single transaction are now possible. + +### Requiring initialization for Public Deployment + +An earlier version of this draft required contracts to be Initialized in order to be Publicly Deployed. While this was useful for removing the initialization check in public functions, it caused a mix of concerns where the `ContractInstanceDeployer` needed to read a nullifier emitted from another contract. It also coupled the `ContractInstanceDeployer` to the convention decided for Initialization Nullifiers, and forced every contract to have a constructor in order to be publicly deployed even if they didn't need one. Furthermore, it required public constructors to be called via the `ContractInstanceDeployer` only. + +Fully separating Initialization and Public Deployment leads to a cleaner `ContractInstanceDeployer`, and allows more flexibility to applications in handling their own initialization. The main downsides are that this opens the door for a contract to be simultaneously Publicly Deployed and Uninitialized, which is a state that does not seem to map to a valid use case. And it requires public functions to check the Initialization Nullifier on every call, which in the current approach is not needed as the presence of the Deployment Nullifier checked by the Public Kernel is enough of a guarantee that the contract was initialized. + +### Execute Initialization during Public Deployment only + +While it is appealing to allow a user to privately create a new contract instance and not reveal it to the world, we have not yet validated this use case. We could simplify deployment by relying on a single nullifier to track Initialization, and couple it with Public Deployment. Private functions can check initialization via the Deployment Nullifier emitted by the `ContractInstanceDeployer`. + +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. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/hashing/hashing.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/hashing/hashing.md new file mode 100644 index 000000000000..bb093888eb67 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/hashing/hashing.md @@ -0,0 +1,29 @@ +## Desirable properties + +There are two main properties we might desire from the various hash functions defined in aztec: + +### Collision Resistance + +Poseidon2 is the predominant hash function in the Aztec protocol. It is assumed to be collision resistant. + +#### Domain Separators + +To minimize the potential for collisions between distinct hashing contexts, all hashes are domain-separated by passing a `string` which describes the context of the hash. Each such string in this spec begins with the prefix `az_`, to domain-separate all hashes specifically to the Aztec protocol. + +> The strings provided in this spec are mere suggestions at this stage; we might find that the strings should be smaller bit-lengths, to reduce in-circuit constraints. + +In the case of using Poseidon2 for hashing (which is the case for most hashing in the Aztec protocol), the string is converted from a big-endian byte representation into a `Field` element, and passed as a first argument into the hash. In the case of using non-algebraic hash functions (such as sha256), the string is converted from a big-endian byte representation into bits, and passed as the first bits into the hash. These details are conveyed more clearly as pseudocode in the relevant sections of the spec. + +For some hashes there is further domain-separation. For example, [Merkle tree hashing](../merkle-trees.md#hashing) of the tree. + +### Pseudo-randomness + +Sometimes we desire the output of a hash function to be pseudo-random. Throughout the Aztec protocol, it is assumed that Poseidon2 can be used as a pseudo-random function. + +Pseudo-randomness is required in cases such as: + +- Fiat-Shamir challenge generation. +- Expanding a random seed to generate additional randomness. + - See the derivation of [master secret keys](../../../aztec/concepts/accounts/keys.md#master-keys). +- Deriving a nullifier, and siloing a nullifier. + - See [deriving a nullifier](../../../aztec/concepts/accounts/keys.md#deriving-a-nullifier-within-an-app-contract). diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/hashing/pedersen.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/hashing/pedersen.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/hashing/poseidon2.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/hashing/poseidon2.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/index.md new file mode 100644 index 000000000000..6cebf78ff71c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/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/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/merkle-trees.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/merkle-trees.md new file mode 100644 index 000000000000..1fbf38e89a32 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/merkle-trees.md @@ -0,0 +1,125 @@ +# Merkle Trees + + + +## Notation + +### Orientation + +A tree is always visualised as a "triangle" with its point at the top (the root) and its base at the bottom (the leaves). Like this: $\Delta$. + +Hopefully this gives a clear intuition whenever the terms "left", "right", "up", "down", "above", "below" are used when describing trees. + +### Arity + +Trees in Aztec are currently all binary Merkle trees (2-ary). + + + +### Height + +The `height` of a tree with $l$ leaves is $\lceil \log_2(l) \rceil$. + +### Layers + +The `layers` of a tree are an enumerated from `0`. The leaves are at layer `0`; the root is at layer [`height`](#height). + +### Levels + +Synonymous with [layers](#layers). + +### Rows + +Synonymous with [layers](#layers) and [levels](#levels). + +### Leaf Index + +The leaves of a tree are indexed from `0`. The first, [left-most](#orientation) leaf is at `leaf_index = 0`. + +### Node Index + +All nodes of the tree (including the leaves) can be indexed. The method of indexing might depend on the algorithm being applied to the tree. + +### Path + +The path from (or "of") a particular node is a vector of that node's ancestors. That is, the node's parent, then its parent's parent, and so on, all the way up to and including the root. + +### Sibling Path + +The sibling path of a particular node is, loosely, a vector of the siblings of the nodes in its [path](#path), except it also includes the node's sibling, and excludes the root (which has no sibling). +The first element in the sibling path is the node's sibling. Then, the node's parent's sibling, then its parent's parent's sibling, and so on. + +### Membership Witness + +The membership witness for a particular leaf, is the minimum data needed to prove that leaf value's existence in the tree. That is: + +- The leaf's [leaf index](#leaf-index) +- The leaf's [sibling path](#sibling-path) + +(and the leaf value itself, of course, but we don't include that in this "membership witness" definition). + +## Hashing + +Used for computing the parent nodes of all merkle trees. + + + +```rust +enum TreeId { + Archive, + NoteHash, + Nullifier, + PrivateFunction, + L1ToL2Msgs, + PublicData +} + +fn merkle_crh( + tree_id: TreeId, + layer: u64, + left: Field, + right: Field +) -> Field { + let tree_id_domain_separator: string = match tree_id { + TreeId::Archive => "archive", + TreeId::NoteHash => "note_hash", + TreeId::Nullifier => "nullifier", + TreeId::PrivateFunction => "private_function", + TreeId::L2ToL2Msgs => "l1_to_l2_msgs", + TreeId::PublicData => "public_data", + }; + + let merkle_domain_separator: string = "az_merkle" + tree_id_domain_separator; + + let parent = poseidon2( + be_string_to_field(merkle_domain_separator), + int_to_field(layer), + + left, + right + ); + + parent +} +``` + +> `tree_id` reflects the various [trees in the protocol](../state/index.md). The `PrivateFunction` tree is discussed in the [contract classes](../contract-deployment/classes.md) section. +> `layer` is the [layer](#layers) of the `left` and `right` children being hashed. For example, when hashing two leaves, `layer = 0`. + +:::danger + +- Q: Do we need the domain separator "az_merkle" + tree_id, for each of the trees? +- Q: do we need domain separation between different layers of the tree? +- Q: Can we optimise the two domain separators to take up 1 Field, instead of 2, or does squashing them together add too many constraints? +- Note: if it helps with optimisation, we can reduce the bit-length of the domain separator strings. +- Q: Can we specify the arguments to Poseidon as Fields, or do we need to specify them as bit-sequences? + +::: + +## Append-only Merkle Tree + +TODO + +## Indexed Merkle Tree + +TODO diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/proving-system/data-bus.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/proving-system/data-bus.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/proving-system/overview.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/proving-system/overview.md new file mode 100644 index 000000000000..d5b10821b982 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/proving-system/overview.md @@ -0,0 +1,125 @@ +# Proving System Components + +# Interactive Proving Systems + +## Ultra Plonk + +UltraPlonk is a variant of the [PLONK](https://eprint.iacr.org/2019/953) protocol - a zkSNARK with a universal trusted setup. + +UltraPlonk utilizes the "Ultra" circuit arithmetisation. This is a configuration with four wires per-gate, and the following set of gate types: + +- arithmetic gate +- elliptic curve point addition/doubling gate +- range-check gate +- plookup table gate +- memory-checking gates +- non-native field arithmetic gates + +## Honk + +Honk is a variant of the PLONK protocol. Plonk performs polynomial testing via checking a polynomial relation is zero modulo the vanishing polynomial of a multiplicative subgroup. Honk performs the polynomial testing via checking, using a sumcheck protocol, that a relation over multilinear polynomials vanishes when summed over a boolean hypercube. + +The first protocol to combine Plonk and the sumcheck protocol was [HyperPlonk](https://eprint.iacr.org/2022/1355) + +Honk uses a custom arithmetisation that extends the Ultra circuit arithmetisation (not yet finalized, but includes efficient Poseidon2 hashing) + +# Incrementally Verifiable Computation Subprotocols + +An Incrementally Verifiable Computation (IVC) scheme describes a protocol that defines some concept of persistent state, and enables multiple successive proofs to evolve the state over time. + +IVC schemes are used by Aztec in two capacities: + +1. to compute a client-side proof of one transaction execution. +2. to compute a proof of a "rollup" circuit, that updates rollup state based on a block of user transactions + +Both use IVC schemes. Client-side, each function call in a transaction is a "step" in the IVC scheme. Rollup-side, aggregating two transaction proofs is a "step" in the IVC scheme. + +The client-side IVC scheme is substantially more complex than the rollup-side scheme due to performance requirements. + +Rollup-side, each "step" in the IVC scheme is a Honk proof, which are recursively verified. As a result, no protoocols other than Honk are required to execute rollup-side IVC. + +We perform one layer of ["proof-system compression"](https://medium.com/aztec-protocol/proof-compression-a318f478d575) in the rollup. The final proof of block-correctness is constructed as a Honk proof. An UltraPlonk circuit is used to verify the correctness of the Honk proof, so that the proof that is verified on-chain is an UltraPlonk proof. +Verification gas costs are lower for UltraPlonk vs Honk due to the following factors: + +1. Fewer precomputed selector polynomials, reducing Verifier G1 scalar multiplications +2. UltraPlonk does not use multilinear polynomials, which removes 1 pairing from the Verifier, as well as O(logn) G1 scalar multiplications. + +The following sections list the protocol components required to implement client-side IVC. We make heavy use of folding schemes to build an IVC scheme. A folding scheme enables instances of a relation to be folded into a single instance of the original relation, but in a "relaxed" form. Depending on the scheme, restrictions may be placed on the instances that can be folded. + +The main two families of folding schemes are derived from the [Nova](https://eprint.iacr.org/2021/370) protocol and the [Protostar](https://eprint.iacr.org/2023/620) protocol respectively. + +## Protogalaxy + +The [Protogalaxy](https://eprint.iacr.org/2023/1106) protocol efficiently supports the ability to fold multiple Honk instances (describing different circuits) into the same accumulator. To contrast, the Nova/Supernova/Hypernova family of folding schemes assume that a single circuit is being repeatedly folded (each Aztec function circuit is a distinct circuit, which breaks this assumption). + +It is a variant of [Protostar](https://eprint.iacr.org/2023/620). Unlike Protostar, Protogalaxy enables multiple instances to be efficiently folded into the same accumulator instance. + +The Protogalaxy protocol is split into two subprotocols, each modelled as interactive protocols between a Prover and a Verifier. + +#### Protogalaxy Fold + +The "Fold" Prover/Verifier validates that `k` instances of a defined relation (in our case the Honk relation) have been correctly folded into an accumulator instance. + +#### Protogalaxy Decider + +The "Decider" Prover/Verifier validate whether an accumulator instance correctly satisfies the accumulator relation. The accumulator being satisfiable inductively shows that all instances that have been folded were satisfied as well. (additional protocol checks are required to reason about _which_ instances have been folded into the accumulator. See the [IVC specification](https://hackmd.io/h0yTcOHiQWeeTXnxTQhTNQ?view) for more information. (note to zac: put this in the protocol specs!) + +## Goblin Plonk + +[Goblin Plonk](https://hackmd.io/@aztec-network/BkGNaHUJn/%2FGfNR5SE5ShyXXmLxNCsg3g) is a computation delegation scheme that improves Prover performance when evaluating complex algorithms. + +In the context of an IVC scheme, Goblin Plonk enables a Prover to defer non-native group operations required by a Verifier algorithm, across multiple recursive proofs, to a single step evaluated at the conclusion of the IVC Prover algorithm. + +Goblin Plonk is composed of three subcomponents: + +#### Transcript Aggregation Subprotocol + +This subprotocol aggregates deferred computations from two independent instances, into a single instance + +#### Elliptic Curve Virtual Machine (ECCVM) Subprotocol + +The ECCVM is a Honk circuit with a custom circuit arithmetisation, designed to optimally evaluate elliptic curve arithmetic computations that have been deferred. It is defined over the Grumpkin elliptic curve. + +#### Translator Subprotocol + +The Translator is a Honk circuit, defined over BN254, with a custom circuit arithmetisation, designed to validate that the input commitments of an ECCVM circuit align with the delegated computations described by a Goblin Plonk transcript commitment. + +## Plonk Data Bus + +When passing data between successive IVC steps, the canonical method is to do so via public inputs. This adds significant costs to an IVC folding verifier (or recursive verifier when not using a folding scheme). Public inputs must be hashed prior to generating Fiat-Shamir challenges. When this is performed in-circuit, this adds a cost linear in the number of public inputs (with unpleasant constants ~30 constraints per field element). + +The Data Bus protocol eliminates this cost by representing cross-step data via succinct commitments instead of raw field elements. + +The Plonk Data Bus protocol enables efficient data transfer between two Honk instances within a larger IVC protocol. + +# Polynomial Commitment Schemes + +The UltraPlonk, Honk, Goblin Plonk and Plonk Data Bus protocols utilize Polynomial Interactive Oracle Proofs as a core component, thus requiring the use of polynomial commitment schemes (PCS). + +UltraPlonk and Honk utilize multilinear PCS. The Plonk Data Bus and Goblin Plonk also utilize univariate PCS. + +For multilinear polynomial commitment schemes, we use the [ZeroMorph](https://eprint.iacr.org/2023/917) protocol, which itself uses a univariate PCS as a core component. + +Depending on context we use the following two univariate schemes within our cryptography stack. + +## KZG Commitments + +The [KZG](https://www.iacr.org/archive/asiacrypt2010/6477178/6477178.pdf) polynomial commitment scheme requires a universal setup and is instantiated over a pairing-friendly elliptic curve. + +Computing an opening proof of a degree-$n$ polynomial requires $n$ scalar multiplications, with a constant proof size and a constant verifier time. + +## Inner Product Argument + +The [IPA](https://eprint.iacr.org/2019/1177.pdf) PCS has worse asymptotics than KZG but can be instantiated over non-pairing friendly curves. + +We utilize the Grumpkin elliptic curve as part of the Goblin Plonk protocol, where we utilize the curve cycle formed between BN254 and Grumpkin to translate expensive non-native BN254 group operations in a BN254 circuit, into native group operations in a Grumpkin circuit. + +Computing an opening proof of a degree-$n$ polynomial requires $2n$ scalar multiplications, with a $O(logn)$ proof size and an $O(n)$ verifier time. + +To batch-verify multiple opening proofs, we use the technique articulated in the [Halo](https://eprint.iacr.org/2019/1021) protocol. To compute a proof of a single rollup block, only one linear-time PCS opening proof is verified despite multiple IPA proofs being generated as part of constructing the rollup proof. + +# Combined IVC + Proving System Protocol + +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](/img/protocol-specs/cryptography/proof-system-components.png) diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/proving-system/performance-targets.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/proving-system/performance-targets.md new file mode 100644 index 000000000000..9afcf35762d1 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/cryptography/proving-system/performance-targets.md @@ -0,0 +1,182 @@ +# Honk targets and win conditions + +## Introduction & context + +Aztec's cryptography tech stack and its associated implementation is an open-ended project with potential for many enhancements, optimisations and scope-creep. + +This document is designed to definitively answer the following questions: + +1. What are the metrics we care about when measuring our cryptography components? +1. What are minimum satisfiable values for these metrics? +1. What are the aspirational values for these metrics? + +# Important Metrics + +The following is a list of the relevant properties that affect the performance of the Aztec network: + +- Size of a user transaction (in kb) +- Time to generate a user transaction proof +- Memory required to generate a user transaction proof +- Time to generate an Aztec Virtual Machine proof +- Memory required to generate an Aztec Virtual Machine proof +- Time to compute a 2-to-1 rollup proof +- Memory required to compute a 2-to-1 rollup proof + +"MVP" = minimum standards that we can go to main-net with. + +Note: gb = gigabytes (not gigabits, gigibits or gigibytes) + +| metric | how to measure | MVP (10tps) | ideal (100tps) | +| --- | --- | --- | --- | +| proof size | total size of a user tx incl. goblin plonk proofs | 80kb | 8kb | +| prover time | A baseline "medium complexity" transaction (in web browser). Full description further down | 1 min | 10 seconds | +| verifier time | how long does it take the verifier to check a proof (incl. grumpkin IPA MSMs) | 20ms | 1ms | +| client memory consumption | fold 2^19 circuits into an accumulator an arbitrary number of times | 4gb | 1gb | +| size of the kernel circuit | number of gates | 2^17 | 2^15 | +| Aztec Virtual Machine prover time | 10,000 step VM circuit | 15 seconds | 1.5 seconds | +| Aztec Virtual Machine memory consumption | 1 million VM step circuit | 128gb | 16gb | +| 2-to-1 rollup proving time | 1 2-to-1 rollup proof | 7.4 seconds | 0.74 seconds | +| 2-to-1 rollup memory consumption | 1 2-to-1 rollup proof | 128gb | 16gb | + +To come up with the above estimates, we are targeting 10 transactions per second for the MVP and 100 tps for the "ideal" case. We are assuming both block producers and rollup Provers have access to 128-core machines with 128gb of RAM. Additionally, we assume that the various process required to produce a block consume the following: + + +| process | percent of block production time allocated to process | +| --- | --- | +| transaction validation | 10% | +| block building (tx simulation) | 20% | +| public VM proof construction time | 20% | +| rollup prover time | 40% | +| UltraPlonk proof compression time | 10% | + +These are very rough estimates that could use further evaluation and validation! + +### Proof size + +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. + +More practically, the data throughput of a p2p network will be less than the bandwidth of participants due to network coordination costs. +As a rough heuristic, we assume that network bandwidth will be 10% of p2p user bandwidth. +NOTE: can we find some high-quality information about p2p network throughput relative to the data consumed by p2p node operators? + +As a result, the MVP data throughput could scale up to `100 * proof_size` bytes of data per second. + +For an MVP we wish to target a maximum bandwidth of 8MB per second (i.e. a good broadband connection). This gives us a network bandwidth of 0.8MB/s. + +This sets the proof size limit to 819.2 kb per second per 100 transactions => 82 kilobytes of data per transaction. + +As a rough estimate, we can assume the non-proof tx data will be irrelevant compared to 82kb, so we target a proof size of $80$ kilobytes for the MVV. + +To support 100 transactions per second we would require a proof size of $8$ kilobytes. + +### Prover time + +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 consumer hardware. + +As a north star, we consider a private swap, and transpose it into an Aztec contract. + +To perform a private swap, the following must occur: + +1. Validate the user's account contract (1 kernel call) +2. Call a swap contract (1 kernel call) +3. The swap contract will initiate `transfer` calls on two token contracts (2 kernel calls) +4. A fee must be paid via our fee abstraction spec (1 kernel call) +5. A final "cleanup" proof is generated that evaluates state reads and processes the queues that have been constructed by previous kernel circuits (1 kernel call + 1 function call; the cleanup proof) + +In total we have 6 kernel calls and 6 function calls. + +We can further abstract the above by making the following assumption: + +1. The kernel circuit is $2^{17}$ constraints +2. The average number of constraints per function call is $2^{17}$ constraints, but the first function called has $2^{19}$ constraints + +Defining the first function to cost $2^{19}$ constraints is a conservative assumption due to the fact that the kernel circuit can support functions that have a max of $2^{19}$ constraints. We want to ensure that our benchmarks (and possible optimisations) capture the "heavy function" case and we don't just optimise for lightweight functions. + +#### Summary of what we are measuring to capture Prover time + +1. A mock kernel circuit has a size of $2^{17}$ constraints and folds _two_ Honk instances into an accumulator (the prev. kernel and the function being called) +2. The Prover must prove 5 mock function circuit proofs of size $2^{17}$ and one mock function proof of size $2^{19}$ +3. The Prover must iteratively prove 6 mock kernel circuit proofs + +#### Execution environment + +For the MVP we can assume the user has reasonable hardware. For the purpose we use a 2-year-old macbook with 16gb RAM. The proof must be generated in a web browser + +#### Performance targets + +For an MVP, we target a 1 minute proof generation time. This is a substantial amount of time to ask a user to wait and we are measuring on good hardware. + +In an ideal world, a 10 second proof generation time would be much better for UX. + +### Verifier time + +This matters because verifying a transaction is effectively free work being performed by sequencers and network nodes that propagate txns to the mempool. If verification time becomes too large it opens up potential DDOS attacks. + +If we reserve 10% of the block production time for verifying user proofs, at 10 transaction per seconds this gives us 0.01s per transaction. i.e. 10ms per proof. + +If the block producer has access to more than one physical machine that they can use to parallelise verification, we can extend the maximum tolerable verification time. For an MVP that requires 20ms to verify each proof, each block producer would require at least 2 physical machines to successfully build blocks. + +100tps with one physical machine would require a verification time of 1ms per proof. + +### Memory consumption + +This is _critical_. Users can tolerate slow proofs, but if Honk consumes too much memory, a user cannot make a proof at all. + +safari on iPhone will purge tabs that consume more than 1gb of RAM. The WASM memory cap is 4gb which defines the upper limit for an MVP. + +### Kernel circuit size + +Not a critical metric, but the prover time + prover memory metrics are predicated on a kernel circuit costing about 2^17 constraints! + +### AVM Prover time + +Our goal is to hit main-net with a network that can support 10 transactions per second. We need to estimate how many VM computation steps will be needed per transaction to determine the required speed of the VM Prover. The following uses very conservative estimations due to the difficulty of estimating this. + +An Ethereum block consists of approximately 1,000 transactions, with a block gas limit of roughly 10 million gas. Basic computational steps in the Ethereum Virtual Machine consume 3 gas. If the entire block gas limit is consumed with basic computation steps (not true but let's assume for a moment), this implies that 1,000 transactions consume 3.33 million computation steps. i.e. 10 transactions per second would require roughly 33,000 steps per second and 3,330 steps per transaction. + +As a conservative estimate, let us assume that every tx in a block will consume 10,000 AVM steps. + +Our AVM model is currently to evaluate a transaction's public function calls within a single AVM circuit. +This means that a block of `n` transactions will require `n` public kernel proofs and `n` AVM proofs to be generated (assuming all txns have a public component). + +If public VM proof construction consumes 20% of block time, we must generate 10 AVM proofs and 10 public kernel proofs in 2 seconds. + +When measuring 2-to-1 rollup prover time, we assume we have access to a Prover network with 500 physical devices available for computation. + +i.e. 10 proofs in 2 seconds across 500 devices => 1 AVM + public kernel proof in 25 seconds per physical device. + +If we assume that ~10 seconds is budgeted to the public kernel proof, this would give a 15 second prover time target for a 10,000 step AVM circuit. + +100 tps requires 1.5 seconds per proof. + +### AVM Memory consumption + +A large AWS instance can consume 128Gb of memory which puts an upper limit for AVM RAM consumption. Ideally consumer-grade hardware can be used to generate AVM proofs i.e. 16 Gb. + +### 2-to-1 rollup proving time + +For a rollup block containing $2^d$ transactions, we need to compute 2-to-1 rollup proofs across $d$ layers (i.e. $2^{d-1}$ 2-to-1 proofs, followed by $2^{d-2}$ proofs, followed by... etc down to requiring 1 2-to-1 proof). To hit 10tps, we must produce 1 block in $\frac{2^d}{10}$ seconds. + +Note: this excludes network coordination costs, latency costs, block construction costs, public VM proof construction costs (must be computed before the 2-to-1 rollup proofs), cost to compute the final UltraPlonk proof. + +To accommodate the above costs, we assume that we can budget 40% of block production time towards making proofs. Given these constraints, the following table describes maximum allowable proof construction times for a selection of block sizes. + +| block size | number of successive 2-to-1 rollup proofs | number of parallel Prover machines required for base layer proofs | time required to construct a rollup proof | +| --- | --- | --- | --- | +| $1,024$ | $10$ | $512$ | 4.1s | +| $2,048$ | $11$ | $1,024$ | 7.4s | +| $4,096$ | $12$ | $2,048$ | 13.6s | +| $8,192$ | $13$ | $4,096$ | 25.2s | +| $16,384$ | $14$ | $8,192$ | 46.8s | + +We must also define the maximum number of physical machines we can reasonably expect to be constructing proofs across the Prover network. If we can assume we can expect $1,024$ machines available, this caps the MVP proof construction time at 7.4 seconds. + +Supporting a proof construction time of 4.1s would enable us to reduce minimum hardware requirements for the Prover network to 512 physical machines. + +### 2-to-1 rollup memory consumption + +Same rationale as the public VM proof construction time. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/data-publication-and-availability/blobs.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/data-publication-and-availability/blobs.md new file mode 100644 index 000000000000..b888aadec254 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/data-publication-and-availability/blobs.md @@ -0,0 +1,198 @@ +--- +title: Blobs +--- + +## Implementation + +### Technical Background + +Essentially, we replace publishing all a tx's effects in calldata with publishing in a blob. Any data inside a blob is *not available* to the EVM so we cannot simply hash the same data on L1 and in the rollup circuits, and check the hash matches, as we do now. + +Instead, publishing a blob makes the `blobhash` available: + +```solidity +/** +* blobhash(i) returns the versioned_hash of the i-th blob associated with _this_ transaction. +* bytes[0:1]: 0x01 +* bytes[1:32]: the last 31 bytes of the sha256 hash of the kzg commitment C. +*/ +bytes32 blobHash; +assembly { + blobHash := blobhash(0) +} +``` + +Where the commitment $C$ is a KZG commitment to the data inside the blob over the BLS12-381 curve. There are more details [here](https://notes.ethereum.org/@vbuterin/proto_danksharding_faq#What-format-is-blob-data-in-and-how-is-it-committed-to) on exactly what this is, but briefly, given a set of 4096 data points inside a blob, $d_i$, we define the polynomial $p$ as: + +$$p(\omega^i) = d_i.$$ + +In the background, this polynomial is found by interpolating the $d_i$ s (evaluations) against the $\omega^i$ s (points), where $\omega^{4096} = 1$ (i.e. is a 4096th root of unity). + +This means our blob data $d_i$ is actually the polynomial $p$ given in evaluation form. Working in evaluation form, particularly when the polynomial is evaluated at roots of unity, gives us a [host of benefits](https://dankradfeist.de/ethereum/2021/06/18/pcs-multiproofs.html#evaluation-form). One of those is that we can commit to the polynomial (using a precomputed trusted setup for secret $s$ and BLS12-381 generator $G_1$) with a simple linear combination: + +$$ C = p(s)G_1 = p(sG_1) = \sum_{i = 0}^{4095} d_i l_i(sG_1),$$ + +where $l_i(x)$ are the [Lagrange polynomials](https://dankradfeist.de/ethereum/2021/06/18/pcs-multiproofs.html#lagrange-polynomials). The details for us are not important - the important part is that we can commit to our blob by simply multiplying each data point by the corresponding element of the Lagrange-basis trusted setup and summing the result! + +### Proving DA + +So to prove that we are publishing the correct tx effects, we just do this sum in the circuit, and check the final output is the same $C$ given by the EVM, right? Wrong. The commitment is over BLS12-381, so we would be calculating hefty wrong-field elliptic curve operations. + +Thankfully, there is a more efficient way, already implemented in the [`blob`](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-protocol-circuits/crates/blob) crate in aztec-packages. + +Our goal is to efficiently show that our tx effects accumulated in the rollup circuits are the same $d_i$ s in the blob committed to by $C$ on L1. To do this, we can provide an *opening proof* for $C$. In the circuit, we evaluate the polynomial at a challenge value $z$ and return the result: $p(z) = y$. We then construct a [KZG proof](https://dankradfeist.de/ethereum/2020/06/16/kate-polynomial-commitments.html#kate-proofs) in typescript of this opening (which is actually a commitment to the the quotient polynomial $q(x)$), and verify it on L1 using the [point evaluation precompile](https://eips.ethereum.org/EIPS/eip-4844#point-evaluation-precompile) added as part of EIP-4844. It has inputs: + +- `versioned_hash`: The `blobhash` for this $C$ +- `z`: The challenge value +- `y`: The claimed evaluation value at `z` +- `commitment`: The commitment $C$ +- `proof`: The KZG proof of opening + +It checks: + +- `assert kzg_to_versioned_hash(commitment) == versioned_hash` +- `assert verify_kzg_proof(commitment, z, y, proof)` + +As long as we use our tx effect fields as the $d_i$ values inside the circuit, and use the same $y$ and $z$ in the public inputs of the Honk L1 verification as input to the precompile, we have shown that $C$ indeed commits to our data. Note: I'm glossing over some details here which are explained in the links above (particularly the 'KZG Proof' and 'host of benefits' links). + +But isn't evaluating $p(z)$ in the circuit also a bunch of very slow wrong-field arithmetic? No! Well, yes, but not as much as you'd think! + +To evaluate $p$ in evalulation form at some value not in its domain (i.e. not one of the $\omega^i$ s), we use the [barycentric formula](https://dankradfeist.de/ethereum/2021/06/18/pcs-multiproofs.html#evaluating-a-polynomial-in-evaluation-form-on-a-point-outside-the-domain): + +$$p(z) = A(z)\sum_{i=0}^{4095} \frac{d_i}{A'(\omega^i)} \frac{1}{z - \omega^i}.$$ + +What's $A(x)$, you ask? Doesn't matter! One of the nice properties we get by defining $p$ as an interpolation over the roots of unity, is that the above formula is simplified to: + +$$p(z) = \frac{z^{4096} - 1}{4096} \sum_{i=0}^{4095} \frac{d_i\omega^i}{z - \omega^i}.$$ + +We can precompute all the $\omega^i$, $-\omega^i$ s and $4096^{-1}$, the $d_i$ s are our tx effects, and $z$ is the challenge point (discussed more below). This means computing $p(z)$ is threoretically 4096 wrong-field multiplications and 4096 wrong-field divisions, far fewer than would be required for BLS12-381 elliptic curve operations. + +### Rollup Circuits + +#### Base + +We need to pass up *something* encompassing the tx effects to the rollup circuits, so they can be used as $d_i$ s when we prove the blob opening. The simplest option would be to `poseidon2` hash the tx effects instead and pass those up, but that has some issues: + +- If we have one hash per base rollup (i.e. per tx), we have an ever increasing list of hashes to manage. +- If we hash these in pairs, then we need to recreate the rollup structure when we prove the blob. + +The latter is doable, but means encoding some maximum number of txs, `N`, to loop over and potentially wasting gates for blocks with fewer than `N` txs. For instance, if we chose `N = 96`, a block with only 2 txs would still have to loop 96 times. Plus, a block could never have more than 96 transactions without a fork. + +Instead, we manage state in the vein of `PartialStateReference`, where we provide a `start` and `end` state in each base and subsequent merge rollup circuits check that they follow on from one another. The base circuits themselves simply prove that adding the data of its tx indeed moves the state from `start` to `end`. + +To encompass all the tx effects, we use a `poseidon2` sponge and absorb each field. We also track the number of fields added to ensure we don't overflow the blobs (4096 BLS fields per blob, with configurable `BLOBS_PER_BLOCK`). Given that this struct is a sponge used for a blob, I have named it: + +```rs +global IV: Field = (FIELDS_PER_BLOB as Field) * 18446744073709551616; + +struct SpongeBlob { + sponge: Poseidon2, + fields: u32, +} + +impl SpongeBlob { + fn new() -> Self { + Self { + sponge: Poseidon2::new(IV), + fields: 0, + } + } + // Add fields to the sponge + fn absorb(&mut self, input: [Field; N], in_len: u32) { + // in_len is all non-0 input + for i in 0..in_len { + self.sponge.absorb(input[i]); + } + self.fields += in_len; + } + // Finalise the sponge and output poseidon2 hash of all fields absorbed + fn squeeze(&mut self) -> Field { + self.sponge.squeeze() + } +} +``` + +To summarise: each base circuit starts with a `start` `SpongeBlob` instance, which is either blank or from the preceding circuit, then calls `.absorb()` with the tx effects as input. Just like the output `BaseOrMergeRollupPublicInputs` has a `start` and `end` `PartialStateReference`, it will also have a `start` and `end` `SpongeBlob`. + +#### Merge + +We simply check that the `left`'s `end` `SpongeBlob` == the `right`'s `start` `SpongeBlob`, and assign the output's `start` `SpongeBlob` to be the `left`'s and the `end` `SpongeBlob` to be the `right`'s. + +#### Block Root + +The current route is to inline the blob functionality inside the block root circuit. + + +First, we must gather all our tx effects ($d_i$ s). These will be injected as private inputs to the circuit and checked against the `SpongeBlob`s from the pair of `BaseOrMergeRollupPublicInputs` that we know contain all the effects in the block's txs. Like the merge circuit, the block root checks that the `left`'s `end` `SpongeBlob` == the `right`'s `start` `SpongeBlob`. + +It then calls `squeeze()` on the `right`'s `end` `SpongeBlob` to produce the hash of all effects that will be in the block. Let's call this `h`. The raw injected tx effects are `poseidon2` hashed and we check that the result matches `h`. We now have our set of $d_i$ s. + +We now need to produce a challenge point `z`. This value must encompass the two 'commitments' used to represent the blob data: $C$ and `h` (see [here](https://notes.ethereum.org/@vbuterin/proto_danksharding_faq#Moderate-approach-works-with-any-ZK-SNARK) for more on the method). We simply provide $C$ as a public input to the block root circuit, and compute `z = poseidon2(h, C)`. + +Note that with multiple blobs per block, each blob uses the same `h` but has a unique `C`. Since `h` does encompass all fields in the blob (plus some more) and the uniqueness of `C` ensures the uniqueness of `z`, this is acceptable. + +The block root now has all the inputs required to call the blob functionality described above. Along with the usual `BlockRootOrBlockMergePublicInputs`, we also have `BlobPublicInputs`: $C$, $z$, and $y$. + +Each blob in the block has its own set of `BlobPublicInputs`. Currently, each are propagated up to the Root circuit and verified on L1 against each blob. In future, we want to combine each insteance of `BlobPublicInputs` so the contract only has to call the precompile once per block. + + + +### L1 Contracts + +#### Rollup + +The function `propose()` takes in these `BlobPublicInputs` and a ts generated `kzgProof` alongside its usual inputs for proposing a new L2 block. The transaction also includes our blob sidecar(s). We verify the `BlobPublicInputs` correspond to the sidecars by calling EVM's point evaluation precompile: + +```solidity + // input for the blob precompile + bytes32[] input; + // extract the blobhash from the one submitted earlier: + input[0] = blobHashes[blockHash]; + input[1] = z; + input[2] = y; + input[3] = C; + // the opening proof is computed in ts and inserted here + input[4] = kzgProof; + + // Staticcall the point eval precompile https://eips.ethereum.org/EIPS/eip-4844#point-evaluation-precompile : + (bool success, bytes memory data) = address(0x0a).staticcall(input); + require(success, "Point evaluation precompile failed"); +``` + +We have now linked the `BlobPublicInputs` ($C$, $z$, and $y$) to a published EVM blob. We still need to show that these inputs were generated in our rollup circuits corresponding to the blocks we claim. To avoid storing `BLOBS_PER_BLOCK * 4` fields per block, we hash all the `BlobPublicInputs` to `blobPublicInputsHash`. +For each proposed block, we store them: + +```solidity +rollupStore.blobPublicInputsHashes[blockNumber] = blobPublicInputsHash; +``` + +Then, when the epoch proof is submitted in `submitEpochRootProof()`, we inject the raw `BlobPublicInputs`, hash them, and check this matches each block's `blobPublicInputsHash`. We use these to verify the ZKP: + +```solidity + // blob_public_inputs + uint256 blobOffset = 0; + for (uint256 i = 0; i < _epochSize; i++) { + uint8 blobsInBlock = uint8(_blobPublicInputs[blobOffset++]); + for (uint256 j = 0; j < Constants.BLOBS_PER_BLOCK; j++) { + if (j < blobsInBlock) { + // z + publicInputs[offset++] = bytes32(_blobPublicInputs[blobOffset:blobOffset += 32]); + // y + (publicInputs[offset++], publicInputs[offset++], publicInputs[offset++]) = + bytes32ToBigNum(bytes32(_blobPublicInputs[blobOffset:blobOffset += 32])); + // c[0] + publicInputs[offset++] = + bytes32(uint256(uint248(bytes31(_blobPublicInputs[blobOffset:blobOffset += 31])))); + // c[1] + publicInputs[offset++] = + bytes32(uint256(uint136(bytes17(_blobPublicInputs[blobOffset:blobOffset += 17])))); + } else { + offset += Constants.BLOB_PUBLIC_INPUTS; + } + } + } +``` + +Notice that if a block needs less than `BLOBS_PER_BLOCK` blobs, we don't waste gas on calling the precompile or assigning public inputs for the unused blobs. If we incorrectly claim that (e.g.) the block used 2 blobs, when it actually used 3, the proof would not verify because `BlobPublicInputs` would exist for the third blob but they would not have been assigned in the above loop (see `offset += Constants.BLOB_PUBLIC_INPUTS`). + +Note that we do not need to check that our $C$ matches the `blobhash` - the precompile does this for us. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/data-publication-and-availability/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/data-publication-and-availability/index.md new file mode 100644 index 000000000000..d0a1a6c5181a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/data-publication-and-availability/index.md @@ -0,0 +1,5 @@ +# Data Publication and Availability + +import DocCardList from '@theme/DocCardList'; + + diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/data-publication-and-availability/overview.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/data-publication-and-availability/overview.md new file mode 100644 index 000000000000..d299edbe54d9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/data-publication-and-availability/overview.md @@ -0,0 +1,322 @@ +--- +title: DA (and Publication) +--- + +:::info +This page is heavily based on the Rollup and Data Ramblings documents. +As for that, we highly recommend reading [this very nice post](https://dba.xyz/do-rollups-inherit-security/) written by Jon Charbonneau. +::: + +- **Data Availability**: The data is available to anyone right now +- **Data Publication**: The data was available for a period when it was published. + +Essentially Data Publication $\subset$ Data Availability, since if it is available, it must also have been published. +This difference might be small but becomes important in a few moments. + +Progressing the state of the validating light node requires that we can convince it that the data was published - as it needs to compute the public inputs for the proof. +The exact method of computing these public inputs can vary depending on the data layer, but generally, it could be by providing the data directly or by using data availability sampling or a data availability committee. + +The exact mechanism greatly impacts the security and cost of the system, and will be discussed in the following sections. +Before that we need to get some definitions in place. + +## Definitions + +:::warning **Security** +Security is often used quite in an unspecific manner, "good" security etc, without specifying what security is. +From distributed systems, the _security_ of a protocol or system is defined by: + +- **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://x.com/sreeramkannan/status/1683735050897207296)): + +- **Liveness**: + - Data Availability - The chain data must be available for anyone to reconstruct the state and build blocks + - Ledger Growth - New blocks will be appended to the ledger + - Censorship Resistance - Honest transactions that are willing to pay will be included if the chain progresses. +- **Safety**: + - Re-org Resistance - Confirmed transactions won't be reverted + - Data Publication - The state changes of the block is published for validation check + - State Validity - State changes along with validity proof allow anyone to check that new state _ROOTS_ are correct. + +Notice, that safety relies on data publication rather than availability. +This might sound strange, but since the validity proof can prove that the state transition function was followed and what changes were made, we strictly don't need the entire state to be available for safety. + +With this out the way, we will later be able to reason about the choice of data storage/publication solutions. +But before we dive into that, let us take a higher level look at Aztec to get a understanding of our requirements. + +In particular, we will be looking at what is required to give observers (nodes) different guarantees similar to what Jon did in [his post](https://dba.xyz/do-rollups-inherit-security/). +This can be useful to get an idea around what we can do for data publication and availability later. + +## Rollup 101 + + + +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 an 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. + + +| |Security| Accessibility| +:-----------: | :-----------: | :-----------: | +Full node| 😃 | 😦 | +Full-verifier light node (L1 state transitioner)| 😃 | 😃 | + +With that out the way, we can draw out a model of the rollup as a two-chain system, what Jon calls the _dynamically available ledger_ and the _finalized prefix ledger_. +The point where we jump from one to the other depends on the confirmation rules applied. +In Ethereum the _dynamically available_ chain follows the [LMD-ghost](https://eth2book.info/capella/part2/consensus/lmd_ghost/) fork choice rule and is the one block builders are building on top of. +Eventually consensus forms and blocks from the _dynamic_ chain gets included in the _finalized_ chain ([Gasper](https://eth2book.info/capella/part2/consensus/casper_ffg/)). +Below image is from [Bridging and Finality: Ethereum](https://jumpcrypto.com/writing/bridging-and-finality-ethereum/). +![](https://jumpcrypto-com.ghost.io/content/images/2023/03/ZK-Bridging-4--1-.png) + +In rollup land, the _available_ chain will often live outside the host where it is built upon before blocks make their way onto the host DA and later get _finalized_ by the validating light node that lives on the host as a smart contract. + +> Depending on the rollup mechanism, rollup full nodes will be able to finalize their own view of the chain as soon as data is available on the host. + +Since the rollup cannot add invalid state transitions to the finalized chain due to the validating light node on the host, rollups can be built with or without a separate consensus mechanism for security. + +One of the places where the existence of consensus make a difference for the rollup chain is how far you can build ahead, and who can do it. + +### Consensus + +For a consensus based rollup you can run LMD-Ghost similarly to Ethereum, new blocks are built like Ethereum, and then eventually reach the host chain where the light client should also validate the consensus rules before progressing state. +In this world, you have a probability of re-orgs trending down as blocks are built upon while getting closer to the finalization. +Users can then rely on their own confirmation rules to decide when they deem their transaction confirmed. +You could say that the transactions are pre-confirmed until they convince the validating light-client on the host. + +### No-consensus + +If there is no explicit consensus for the Rollup, staking can still be utilized for leader selection, picking a distinct sequencer which will have a period to propose a block and convince the validating light-client. +The user can as earlier define his own confirmation rules and could decide that if the sequencer acknowledges his transaction, then he sees it as confirmed. +This has weaker guarantees than the consensus-based approach as the sequencer could be malicious and not uphold his part of the deal. +Nevertheless, the user could always do an out-of-protocol agreement with the sequencer, where the sequencer guarantees that he will include the transaction or the user will be able to slash him and get compensated. + +:::info Fernet +Fernet lives in this category if you have a single sequencer active from the proposal to proof inclusion stage. +::: + +Common for both consensus and no-consensus rollups is that the user can decide when he deems his transaction confirmed. +If the user is not satisfied with the guarantee provided by the sequencer, he can always wait for the block to be included in the host chain and get the guarantee from the host chain consensus rules. + +## Data Availability and Publication + +As alluded to earlier, we belong to the school of thought that Data Availability and Publication are different things. +Generally, what is often referred to as Data Availability is merely Data Publication, e.g., whether or not the data have been published somewhere. +For data published on Ethereum you will currently have no issues getting a hold of the data because there are many full nodes and they behave nicely, but they are not guaranteed to continue doing so. +New nodes are essentially bootstrapped by other friendly nodes. + +With that out the way, it would be prudent to elaborate on our definition from earlier: + +- **Data Availability**: The data is available to anyone right now +- **Data Publication**: The data was available for a period when it was published. + +With this split, we can map the methods of which we can include data for our rollup. +Below we have included only systems that are live or close to live where we have good ideas around the throughput and latency of the data. +The latency is based on using Ethereum L1 as the home of the validating light node, and will therefore be the latency between point in time when data is included on the data layer until a point when statements about the data can be included in the host chain. + + +|Method | Publication | Availability | Quantity | Latency | Description | +| ------- | :----------: | :----------: | :----------: | :-------: | :-------: | +|calldata| Eth L1 | Eth L1 | $78,125~\dfrac{byte}{s}$ | None | Part of the transaction payload required to execute history, if you can sync an Ethereum node from zero, this is available. Essentially, if Ethereum lives this is available. Have to compete against everything on Ethereum for blockspace. | +|blobs| Eth L1 | benevolent Eth L1 super full-nodes | x | None | New blob data, will be published but only commitments available from the execution environment. Content can be discarded later and doesn't have to be stored forever. Practically a "committee" of whoever wants can keep it, and you rely on someone from this set providing the data to you. | +^^| | | $31,744 \dfrac{byte}{s}$ | None | target of `3` blobs of size `4096` fields (`380,928` bytes per block) | +^^| | | $677,205 \dfrac{byte}{s}$ | None | target of `64` blobs of size `4096` fields (`8,126,464` bytes per block) | +|Celestia| Celestia + Blobstream bridge | Celestia Full Storage Nodes | $161,319~\dfrac{byte}{s}$ | ~100 mins | 2MB blocks. Can be used in proof after relay happens, with latency improvements expected.| + +### Data Layer outside host + +When using a data layer that is not the host chain, cost (and safety guarantees) are reduced, and we rely on some "bridge" to tell the host chain about the data. +This must happen before our validating light node can progress the block. +Therefore the block must be published, and the host must know about it before the host can use it as input to block validation. + +This influences how blocks can practically be built, since short "cycles" of publishing and then including blocks might not be possible for bridges with significant delay. +This means that a suitable data layer has both sufficient data throughput but also low (enough) latency at the bridge level. + +Briefly the concerns we must have for any supported data layer that is outside the host chain is: + +- What are the security assumptions of the data layer itself +- What are the security assumptions of the bridge +- What is the expected data throughput (kb/s) +- What is the expected delay (mins) of the bridge + +#### Celestia + +Celestia mainnet is starting with a limit of 2 mb/block with 12 second blocks supporting ~166 KB/s. +:::note +They are working on increasing this to 8 mb/block. +::: + +As Celestia has just recently launched, it is unclear how much competition there will be for the data throughput, and thereby how much we could expect to get a hold of. +Since the security assumptions differ greatly from the host chain (Ethereum) few L2s have been built on top of it yet, and the demand is to be gauged in the future. + +Beyond the pure data throughput, we also need Ethereum L1 to know that the data was made available on Celestia. +This will require the [blobstream](https://blog.celestia.org/introducing-blobstream/) (formerly the quantum gravity bridge) to relay data roots that the rollup contract can process. +This is currently done approximately every 100 minutes. +Note however, that a separate blobstream is being build by Succinct labs (live on goerli) which should make relays cheaper and more frequent. + +Neat structure of what the availability oracles will look like created by the Celestia team: +![image.png](https://lh7-us.googleusercontent.com/EB8CtN-MvqApiPSeulWS3zmix6VZP1EEjilx7cRPxaWzAp1QYQI0tclzn7SyfGwxe-VTuf68DYs83Rl9hVCiUzHYZuOvEpNmvoHEFfBu6_vVRIU45wmA4ZqWIp3gBXgiv32YIKiu1ZAYK04zri9M2CE) + +#### Espresso + +Espresso is not yet live, so the following section is very much in the air, it might be that the practical numbers will change when it is live. + +> Our knowledge of hotshot is limited here - keeping commentary limited until more educated in this matter. + +From their [benchmarks](https://docs.espressosys.com/sequencer/releases/doppio-testnet-release/benchmarks), it seems like the system can support 25-30MB/s of throughput by using small committees of 10 nodes. +The throughput further is impacted by the size of the node-set from where the committee is picked. + +While the committee is small, it seems like they can ensure honesty through the other nodes. +But the nodes active here might need a lot of bandwidth to handle both DA Proposals and VID chunks. + +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 + +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**. + +The things that are important to **everyone** are the things that we have directly in state, meaning the: + +- leaves of the note hash tree +- nullifiers +- public state leafs +- contracts +- L1 -> L2 +- L2 -> L1 + +Some of these can be moved around between layers, and others are hard-linked to live on the host. +For one, moving the cross-chain message L1 -> L2 and L2 -> L1 anywhere else than the host is fighting an up-hill battle. +Also, beware that the state for L2 -> L1 messages is split between the data layers, as the messages don't strictly need to be available from the L2 itself, but must be for consumption on L1. + +We need to know what these things are to be able to progress the state. +Without having the state, we don't know how the output of a state transition should look and cannot prove it. + +Beyond the above data that is important to everyone, we also have data that is important to _someone_. +These are encrypted and unencrypted logs. +Knowing the historic logs is not required to progress the chain, but they are important for the users to ensure that they learn about their notes etc. + +A few transaction examples based on our E2E tests have the following data footprints. +We will need a few more bytes to specify the sizes of these lists but it will land us in the right ball park. + +> These were made back in August 2023 and are a bit outdated. +> They should be updated to also include more complex transactions. + +``` +Tx ((Everyone, Someone) bytes). +Tx ((192, 1005) bytes): comms=4, nulls=2, pubs=0, l2_to_l1=0, e_logs=988, u_logs=17 +Tx ((672, 3980) bytes): comms=16, nulls=5, pubs=0, l2_to_l1=0, e_logs=3932, u_logs=48 +Tx ((480, 3980) bytes): comms=13, nulls=2, pubs=0, l2_to_l1=0, e_logs=3932, u_logs=48 +Tx ((640, 528) bytes): comms=4, nulls=16, pubs=0, l2_to_l1=0, e_logs=508, u_logs=20 +Tx ((64, 268) bytes): comms=1, nulls=1, pubs=0, l2_to_l1=0, e_logs=256, u_logs=12 +Tx ((128, 512) bytes): comms=2, nulls=2, pubs=0, l2_to_l1=0, e_logs=500, u_logs=12 +Tx ((96, 36) bytes): comms=0, nulls=1, pubs=1, l2_to_l1=0, e_logs=8, u_logs=28 +Tx ((128, 20) bytes): comms=0, nulls=2, pubs=1, l2_to_l1=0, e_logs=8, u_logs=12 +Tx ((128, 20) bytes): comms=1, nulls=1, pubs=1, l2_to_l1=0, e_logs=8, u_logs=12 +Tx ((96, 268) bytes): comms=1, nulls=2, pubs=0, l2_to_l1=0, e_logs=256, u_logs=12 +Tx ((224, 28) bytes): comms=1, nulls=2, pubs=2, l2_to_l1=0, e_logs=12, u_logs=16 +Tx ((480, 288) bytes): comms=1, nulls=2, pubs=6, l2_to_l1=0, e_logs=260, u_logs=28 +Tx ((544, 32) bytes): comms=0, nulls=1, pubs=8, l2_to_l1=0, e_logs=8, u_logs=24 +Tx ((480, 40) bytes): comms=0, nulls=1, pubs=7, l2_to_l1=0, e_logs=12, u_logs=28 + +Average bytes, (rounded up): +Everyone: 311 bytes +Someone: 787 bytes +Total: 1098 bytes +``` + +For a more liberal estimation, lets suppose we emit 4 nullifiers, 4 new note hashes, and 4 public data writes instead per transaction. + +```python +Tx ((512, 1036) bytes): comms=4, nulls=4, pubs=4, l2_to_l1=0, e_logs=988, u_logs=48 +``` + +Assuming that this is a decent guess, and we can estimate the data requirements at different transaction throughput. + +### Throughput Requirements + +Using the values from just above for transaction data requirements, we can get a ball park estimate of what we can expect to require at different throughput levels. + + +|Throughput | Everyone | Someone | Total | +|:-----:|:-----:|:-----:|:-----:| +| 1 TPS | $512 \dfrac{byte}{s}$ | $1036 \dfrac{byte}{s}$ | $1548 \dfrac{byte}{s}$ | +| 10 TPS | $5120 \dfrac{byte}{s}$ | $10360 \dfrac{byte}{s}$ | $15480 \dfrac{byte}{s}$ | +| 50 TPS | $25600 \dfrac{byte}{s}$ | $51800 \dfrac{byte}{s}$ | $77400 \dfrac{byte}{s}$ | +| 100 TPS | $51200 \dfrac{byte}{s}$ | $103600 \dfrac{byte}{s}$ | $154800 \dfrac{byte}{s}$ | + +Assuming that we are getting $\frac{1}{9}$ of the blob-space or $\frac{1}{20}$ of the calldata and amortize to the Aztec available space. + +For every throughput column, we insert 3 marks, for everyone, someone and the total; +✅✅✅ meaning that the throughput can be supported when publishing data for everyone, someone and the total. +💀💀💀 meaning that none of it can be supported. + + +|Space| Aztec Available | 1 TPS | 10 TPS | 50 TPS | 100 Tps | +|:---:|:---:|:---:|:---:|:---:|:---:| +|Calldata| $3,906 \frac{byte}{s}$ | ✅✅✅ |💀💀💀 | 💀💀💀 | 💀💀💀 +|Eip-4844 | $3,527 \dfrac{byte}{s}$ | ✅✅✅ | 💀💀💀 | 💀💀💀 | 💀💀💀 +|64 blob danksharding | $75,245 \dfrac{byte}{s}$ | ✅✅✅ | ✅✅✅ | ✅✅✅ | ✅✅💀 +|Celestia (2mb/12s blocks)| $17,924 \dfrac{byte}{s}$ | ✅✅✅ | ✅✅✅ | 💀💀💀 | 💀💀💀 +|Celestia (8mb/13s blocks)| $68,376 \dfrac{byte}{s}$ | ✅✅✅ | ✅✅✅ | ✅✅💀 | ✅💀💀 +|Espresso| Unclear but at least 1 mb per second | ✅✅✅ | ✅✅✅ | ✅✅✅| ✅✅✅ + +> **Disclaimer**: Remember that these fractions for available space are pulled out of thin air. + +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? + +From the above estimations, it is unlikely that our data requirements can be met by using only data from the host chain. +It is therefore to be considered whether data can be split across more than one data layer. + +The main concerns when investigating if multiple layers should be supported simultaneously are: + +- **Composability**: Applications should be able to integrate with one another seamlessly and synchronously. If this is not supported, they might as well be entirely separate deployments. +- **Ossification**: By ossification we mean changing the assumptions of the deployments, for example, if an application was deployed at a specific data layer, changing the layer underneath it would change the security assumptions. This is addressed through the [Upgrade mechanism](../decentralization/governance.md). +- **Security**: Applications that depend on multiple different data layers might rely on all its layers to work to progress its state. Mainly the different parts of the application might end up with different confirmation rules (as mentioned earlier) degrading it to the least secure possibly breaking the liveness of the application if one of the layers is not progressing. + +The security aspect in particular can become a problem if users deploy accounts to a bad data layer for cost savings, and then cannot access their funds (or other assets) because that data layer is not available. +This can be a problem, even though all the assets of the user lives on a still functional data layer. + +Since the individual user burden is high with multi-layer approach, we discard it as a viable option, as the probability of user failure is too high. + +Instead, the likely design, will be that an instance has a specific data layer, and that "upgrading" to a new instance allows for a new data layer by deploying an entire instance. +This ensures that composability is ensured as everything lives on the same data layer. +Ossification is possible hence the [upgrade mechanism](../decentralization/governance.md) doesn't "destroy" the old instance. +This means that applications can be built to reject upgrades if they believe the new data layer is not secure enough and simple continue using the old. + +## Privacy is Data Hungry - What choices do we really have? + +With the target of 10 transactions per second at launch, in which the transactions are likely to be more complex than the simple ones estimated here, some of the options simply cannot satisfy our requirements. + +For one, EIP-4844 is out of the picture, as it cannot support the data requirements for 10 TPS, neither for everyone or someone data. + +At Danksharding with 64 blobs, we could theoretically support 50 tps, but will not be able to address both the data for everyone and someone. +Additionally this is likely years in the making, and might not be something we can meaningfully count on to address our data needs. + +With the current target, data cannot fit on the host, and we must work to integrate with external data layers. +Of these, Celestia has the current best "out-the-box" solution, but Eigen-da and other alternatives are expected to come online in the future. + +## References + +- https://dba.xyz/do-rollups-inherit-security/ +- https://ethereum.org/en/roadmap/danksharding/ +- https://eips.ethereum.org/EIPS/eip-4844 +- https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/polynomial-commitments.md +- https://eth2book.info/capella/part2/consensus/lmd_ghost/ +- https://eth2book.info/capella/part2/consensus/casper_ffg/ +- https://notes.ethereum.org/cG-j3r7kRD6ChQyxjUdKkw +- https://forum.celestia.org/t/security-levels-for-data-availability-for-light-nodes/919 +- https://ethresear.ch/t/peerdas-a-simpler-das-approach-using-battle-tested-p2p-components/16541 +- https://jumpcrypto.com/writing/bridging-and-finality-ethereum/ +- https://x.com/sreeramkannan/status/1683735050897207296 +- https://blog.celestia.org/introducing-blobstream/ diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/data-publication-and-availability/published-data.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/data-publication-and-availability/published-data.md new file mode 100644 index 000000000000..d9ab988b1f61 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/data-publication-and-availability/published-data.md @@ -0,0 +1,38 @@ +--- +title: Published Data Format +--- + +The "Effects" of a transaction are the collection of state changes and metadata that resulted from executing a transaction. These include: + +| Field | Type | Description | +| -------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| `revertCode` | `RevertCode` | Indicates the reason for reverting in public application logic. 0 indicates success. | +| `transactionFee` | `Fr` | The transaction fee, denominated in FPA. | +| `noteHashes` | `Tuple` | The note hashes to be inserted into the note hash tree. | +| `nullifiers` | `Tuple` | The nullifiers to be inserted into the nullifier tree. | +| `l2ToL1Msgs` | `Tuple` | The L2 to L1 messages to be inserted into the messagebox on L1. | +| `publicDataWrites` | `Tuple` | Public data writes to be inserted into the public data tree | +| `noteEncryptedLogs` | `TxL2Logs` | Buffers containing the emitted note logs. | +| `encryptedLogs` | `TxL2Logs` | Buffers containing the emitted encrypted logs. | +| `unencryptedLogs` | `TxL2Logs` | Buffers containing the emitted unencrypted logs. | + +To publish the above data, we must convert it into arrays of BLS12 fields for EVM defined blobs. The encoding is defined as: + +| field start | num fields | name | contents | +| ----------------------------------------------------- | ---------- | ---------------------- | ---------------------------------------------------------------------------- | +| 0 | 1 | Tx Start | TX_START_PREFIX, total len, REVERT_CODE_PREFIX, revertCode | +| 1 | 1 | Tx Fee | TX_FEE_PREFIX, transactionFee | +| 2 | 1 | Notes Start | (If notes exist) NOTES_PREFIX, noteHashes.len() | +| 3 | n | Notes | (If notes exist) noteHashes | +| 3 + n | 1 | Nullifiers Start | (If nullifiers exist) NULLIFIERS_PREFIX, nullifiers.len() | +| 3 + n + 1 | m | Nullifiers | (If nullifiers exist) nullifiers | +| 3 + n + 1 + m | 1 | L2toL1Messages Start | (If msgs exist) L2_L1_MSGS_PREFIX, l2ToL1Msgs.len() | +| 3 + n + 1 + m + 1 | l | L2toL1Messages | (If msgs exist) l2ToL1Msgs | +| 3 + n + 1 + m + 1 + l | 1 | PublicDataWrites Start | (If writes exist) PUBLIC_DATA_UPDATE_REQUESTS_PREFIX, publicDataWrites.len() | +| 3 + n + 1 + m + 1 + l + 1 | p | PublicDataWrites | (If writes exist) publicDataWrites | +| 3 + n + 1 + m + 1 + l + 1 + p | 1 | Note Logs Start | (If note logs exist) NOTE_ENCRYPTED_LOGS_PREFIX, noteEncryptedLogs.len() | +| 3 + n + 1 + m + 1 + l + 1 + p + 1 | nl | Note Logs | (If note logs exist) noteEncryptedLogs | +| 3 + n + 1 + m + 1 + l + 1 + p + 1 + nl | 1 | Encrypted Logs Start | (If encrypted logs exist) ENCRYPTED_LOGS_PREFIX, encryptedLogs.len() | +| 3 + n + 1 + m + 1 + l + 1 + p + 1 + nl + 1 | el | Encrypted Logs | (If encrypted logs exist) encryptedLogs | +| 3 + n + 1 + m + 1 + l + 1 + p + 1 + nl + 1 + el | 1 | Unencrypted Logs Start | (If unencrypted logs exist) UNENCRYPTED_LOGS_PREFIX, unencryptedLogs.len() | +| 3 + n + 1 + m + 1 + l + 1 + p + 1 + nl + 1 + el + 1 | ul | Unencrypted Logs | (If unencrypted logs exist) unencryptedLogs | diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/decentralization/actors.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/decentralization/actors.md new file mode 100644 index 000000000000..dc4eba585135 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/decentralization/actors.md @@ -0,0 +1,182 @@ +--- +title: Nodes and Actors +--- + +To analyze the suitability of different node types for various actor types in the system, let's break down the node types and actor types, considering the characteristics, preferences, abilities, and potential motivations of each actor. +This will help determine which actors are likely to run which nodes and how they might interact with the Private eXecution Environment (PXE). + +## Background + +Before diving into Aztec specific data, we take a look at general blockchain nodes. +It should help us have a shared base, which will later be useful for comparing the Aztec setups, where multiple nodes are involved due to its construction. + +### Node Types + +The below node types are ordered in resource requirements, and exists for most blockchains. +All the nodes participate in the peer-to-peer (p2p) network but with varying capacity. + +1. **Light Node**: + - Download and validate headers from the p2p network. + - Sometimes an "ultra-light" node is mentioned, this is a node that don't validate the headers it receive but just accept it. These typically are connected to a third party trusted by the user to provide valid headers. + - Stores only the headers. + - Querying any state not in the header is done by requesting the data from a third party, e.g. Infura or other nodes in the p2p network. Responses are validated with the headers as a trust anchor. + - Storage requirements typically measured in MBs (< 1GB). + - Synchronization time is typically measured in minutes. +2. **Full Node**: + - Receive and validate blocks (header and body) from the p2p network. + - Stores the complete active state of the chain + - Typically stores recent state history (last couple of hours is common) + - Typically stores all blocks since genesis (some pruning might be done) + - Can respond to queries of current and recent state + - Storage requirements typically measured in GBs (< 1TB) + - Synchronization time is typically measured in hours/days. +3. **Archive Node**: + - Receive and validate blocks (header and body) from the p2p network + - Stores the full state history of the chain + - Stores all blocks since genesis + - Can respond to queries of state at any point in time + - Storage requirements typically measured in TBs + - Synchronization time is typically measured in hours/days. + +Beyond these node types, there are also nodes that participate in the block production who rely on full or archive nodes to extend the chain. +In Ethereum these are called validators, but really, any of the nodes above do some validation of the blocks or headers. +The Ethereum "validator" is really just their block producer. + +Block production can generally be split into two parts: 1) building the block and 2) proposing the block. + +:::info Proposer-Builder-Separation (PBS) +When these two parts are split between different parties it is often referred to as Proposer-Builder-Separation. +::: + +A proposer generally have to put up some value to be able to propose a block and earn rewards from the blocks proposed. +In PoW this value is burnt $ in electricity, and in PoS it is staked $ which can be "slashed" according to the rules of the chain. + +In the Ethereum world you can say the "validator" is a proposer, that either builds his own blocks or outsource it to someone else, such as [Flashbots](https://www.flashbots.net/). + +:::info Blobs +Blobs in Ethereum is a special kind of data that is proven to be available at time of publication and for a short period after. +After this period (~18 days), the blob is considered shared and can be pruned from the chain. +It is not needed to synchronize a new node. + +Blobs will likely be stored by certain "super-archive" nodes, but not by all archive nodes. +Meaning that the set of blob-serving nodes likely will be small. +As the blob-hash is part of the block header, it is easy to validate that some chunk of data was indeed the published blob. +Relies on an 1/n honesty assumption for access to the blob. +::: + +### Actor Types + +1. **Mainstream Users**: + - Currently don't care about technicalities, just want to use the system. + - Will likely not run any type of node, unless it is bundled with their wallet so they don't even know it is there. + - Generally don't care about trusting Infura or other third parties blindly. +2. **Enthusiast**: + - More knowledgeable and interested in technology than the mainstream user. + - Willing to invest time and resources but not at a professional level. + - Likely to run a light node for the extra security. +3. **Power Users**: + - Technically proficient and deeply engaged. + - Likely to have the resources and motivation to run more demanding nodes, and potentially participate in block production. +4. **Developers**: + - Highly knowledgeable with technical skills. + - Interested in detailed state and historical data for development and testing. +5. **Idealists**: + - Want maximal autonomy, and are willing to invest resources to achieve it. + - Will rely entirely on their own nodes and resources for serving their queries, current or historical by running an Archive Node. + - Likely to run nodes that contribute directly to the blockchain's operation as part of block production. + - Possibly willing to store all blobs. +6. **Institutions**: + - Have significant resources (and potentially technical) capabilities. + - Interested in robust participation, and potentially participate in block production for financial gain. + +## Aztec Relevant + +In the following section, we are following the assumption that Aztec is running on top of Ethereum and using some DA to publish its data. + +Beyond the [state](./../state/index.md) of the chain an Aztec node also maintains a database of encrypted data and their tags - an index of tags. +The index is used when a user wishes to retrieve notes that belong to them. +The index is built by the node as it receives new blocks from the network. + +If the node have the full index it can serve any user that wants to retrieve their notes. This will be elaborated further in [responding to queries](#responding-to-queries). +A node could be configured to only build and serve the index for a subset of the tags and notes. +For example, a personal node could be configured to only build the index for the notes that belong to the owner based on their [tag derivation](./../private-message-delivery/private-msg-delivery.md#note-tagging). + +If the node is intended only for block production, it can skip the index entirely. + +### Synchronizing An Aztec Node + +To synchronize an Aztec full- or archive-node, we need the blocks to build state and index. We have two main options for doing so: +- Aztec nodes which is running dependency-minimized (i.e. not relying on a third party for data), and agree with the bridge on what is canonical, can retrieve the headers directly from their Ethereum node and the matching bodies from their node on the DA network. If blobs are used for DA, both of these could be the same node. The node will build the state and index as it receives the blocks. +- Aztec nodes that are not running dependency-minimized can rely on third parties for retrieving state and block bodies and use Ethereum as a "trust-anchor". Here meaning that the Aztec node could retrieve the full state from IPFS and then validate it against the headers it receives from the Ethereum node - allowing a quick synchronization process. Since the index is not directly part of state, its validity cannot be validated as simple as the state. The index will have to be built from the blocks (validating individual decrypted messages can be done against the state). + +An Aztec light-node is a bit different, because it does not store the actual state *nor* index, only the headers. +This means that it will follow the same model as Ethereum light-nodes, where it can retrieve data by asking third parties for it and then validating that it is in the state (anchored by the headers stored). + +:::info Aztec-specific Ultra Light nodes +For users following the validating bridge as the canonical rollup, the bridge IS the light node, so they can get away with not storing anything, and just download the header data from the bridge. +Since the [archive](./../state/archive.md) is stored in the bridge, an Aztec ultra-light node can validate membership proofs against it without needing the exact header (given sufficient membership proofs are provided). +This essentially enable instant sync for ultra-light nodes -> if you either run an Ethereum node or trust a third party. +::: + +### Responding to Queries + +In the following, we will briefly outline how the different type of Aztec node will respond to different kinds of queries. Namely we will consider the following queries: +- Requesting current public state and membership proofs +- Requesting historical public state and membership proofs +- Requesting current membership proof of note hash, nullifier, l1 to l2 message or contract +- Requesting historical membership proof of note hash, nullifier, l1 to l2 message or contract +- Requesting encrypted notes for a given tag + +#### Light-Node + +As mentioned, light-nodes will be retrieving data from a third party, and then validating it against the headers at its disposal. +Note that if we have the latest archive hash, two extra inclusion proofs can be provided to validate the response against the archive as well without needing any other data. + +If we don't care about other people seeing what data we are retrieving, we can simply ask the third parties directly. +However, when dealing with private information (e.g. notes and their inclusion proofs), you need to be careful about how you retrieve it - you need to use some form of private information retrieval (PIR) service. + +The exact nature of the service can vary and have not been decided yet, but it could be some form of Oblivious Message Retrieval (OMR) service. +This is a service that allows you to retrieve a message from a database without revealing which message you are retrieving. +An OMR service could be run by a third party who runs a full or archive node and is essentially just a proxy for other nodes to fetch information from the database. +There might exists multiple OMR services, some with access to historical state and some without. + +Assuming that OMR is used, the user would occasionally use the light-node to request encrypted notes from the OMR service. +These notes can be decrypted by the user, and used to compute the note hash, for which they will need an inclusion proof to spend the note. +When the user wish to spend the note, they can request the inclusion proof from the OMR service, which they can then validate against the headers they have stored. + +:::info Note Discovery Issue +The issue of how to discover which notes belong to you is not solved by the Aztec protocol currently, and we have a [Request For Proposals](https://forum.aztec.network/t/request-for-proposals-note-discovery-protocol/2584) on the matter. +::: + +#### Full-Node +Can satisfy any query on current data, but will have to retrieve historical membership proofs from a third party or recompute the requested state based on snapshots and blocks. + +Can utilize the same protocol as the Light-node for retrieving data that it doesn't already have. + +#### Archive-Node +With a fully synched archive node you can respond to any query using your own data - no third party reliance. + +### Private eXecution Environment (PXE) + +The Aztec PXE is required to do anything meaningful for an end-user. +It is the interface between the user and the Aztec network and acts as a private enclave that runs on the end-user's machine. + +While it is responsible for executing contracts and building proofs for the user's transactions privately, it needs data from the node to do so. +It can be connected to any of the above types of nodes, inheriting their assumptions. +From the point of view of the PXE, it is connected to a node, and it does not really care which one. + +When requesting encrypted notes from the node the PXE will decrypt it and store the decrypted notes in its own database. +This database is used to build proofs for the user's transactions. + +From most end users points of view, the PXE will be very close to a wallet. + +### Bundling the PXE with an Aztec ultra-light node + +A deployment of the PXE could be bundled together with an aztec ultra-light node which in turn is connected to "some" Ethereum node. +The ethereum node could simply be infura, and then the ultra-light node handles its connections to the OMR service separately. + +This setup can be made very light-weight and easy to use for the end-user, who might not be aware that they are running a node at all. + +The reasoning that the ultra-light node should be bundled with an internal ultra-light node by default instead of simply using an RPC endpoint is to avoid users "missing" the OMR services and leak private information. + +If the user is running a node themselves, on a separate device or whatever, they could connect the PXE to that node instead of the bundled one. \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/decentralization/block-production.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/decentralization/block-production.md new file mode 100644 index 000000000000..a344e68efff9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/decentralization/block-production.md @@ -0,0 +1,406 @@ +--- +title: Block Production +draft: true +--- + + +:::info +This document aims to be the latest source of truth for the Fernet sequencer selection protocol, and reflect the decision to implement the [Sidecar](https://forum.aztec.network/t/proposal-prover-coordination-sidecar/2428) proving coordination protocol. Notably, this is written within the context of the first instance or deployment of Aztec. The definitions and protocol may evolve over time with each version. +::: + +## Overview + +This document outlines a proposal for Aztec's block production, integrating immutable smart contracts on Ethereum's mainnet (L1) to establish Aztec as a Layer-2 Ethereum network. Sequencers can register permissionlessly via Aztec's L1 contracts, entering a queue before becoming eligible for a random leader election ("Fernet"). Sequencers are free to leave, adhering to an exit queue or period. Roughly every 7-10 minutes (subject to reduction as proving and execution speeds stabilize and/or improve) sequencers create a random hash using [RANDAO](https://eth2book.info/capella/part2/building_blocks/randomness/#the-randao) and their public keys. The highest-ranking hash determines block proposal eligibility. Selected sequencers either collaborate with third-party proving services or self-prove their block. They commit to a prover's L1 address, which stakes an economic deposit. Failure to submit proofs on time results in deposit forfeiture. Once L1 contracts validate proofs and state transitions, the cycle repeats for subsequent block production (forever, and ever...). + +### Full Nodes + +Aztec full nodes are nodes that maintain a copy of the current state of the network. They fetch blocks from the data layer, verify and apply them to its local view of the state. They also participate in the [P2P network](./p2p-network.md) to disburse transactions and their proofs. Can be connected to a **PXE** which can build transaction witness using the data provided by the node (data membership proofs). + +:::info +We should probably introduce the PXE somewhere +::: + +| 🖥️ | Minimum | Recommended | +| ------- | ------- | ----------- | +| CPU | 16cores | 32cores | +| Network | 32 mb/s | 128 mb/s | +| Storage | 3TB | 5TB | +| RAM | 32gb | 64gb | + +:::info Estimates +- **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](../data-publication-and-availability/index.md#aztec-specific-data) + tree overhead, ~ 0.4 TB per year. +- **RAM**: Help +::: + +### Sequencers + +Aztec Sequencer's are full nodes that propose blocks, execute public functions and choose provers, within the Aztec Network. It is the actor coordinating state transitions and proof production. Aztec is currently planning on implementing a protocol called Fernet (Fair Election Randomized Natively on Ethereum trustlessly), which is permissionless and anyone can participate. Additionally, sequencers play a role participating within Aztec Governance, determining how to manage [protocol upgrades](./governance.md). + +#### Hardware requirements + +| 🖥️ | Minimum | Recommended | +| ------- | ------- | ----------- | +| CPU | 16cores | 32cores | +| Network | 32 mb/s | 128 mb/s | +| Storage | 3TB | 5TB | +| RAM | 32gb | 64gb | + +:::info Estimates +Mostly as full nodes. The network requirements might be larger since it needs to gossip the block proposal and coordinate with provers, depends on the number of peers and exact proving structure. +::: + +### Provers + +An Aztec Prover is a full node that is producing Aztec-specific zero knowledge (zk) proofs ([rollup proofs](./../rollup-circuits/index.md)). The current protocol, called [Sidecar](https://forum.aztec.network/t/proposal-prover-coordination-sidecar/2428), suggests facilitating out of protocol proving, similar to out of protocol [PBS](https://ethresear.ch/t/proposer-block-builder-separation-friendly-fee-market-designs/9725). Provers in this case are fully permissonless and could be anyone - such as a vertically integrated sequencer, or a proving marketplace such as [nil](https://nil.foundation/proof-market), [gevulot](https://www.gevulot.com/), or [kalypso](https://docs.marlin.org/user-guides/kalypso/), as long as they choose to support the latest version of Aztec's proving system. + +#### Hardware requirements + +| 🖥️ | Minimum | Recommended | +| ------- | ------- | ----------- | +| CPU | 16cores | 32cores | +| Network | 32 mb/s | 128 mb/s | +| Storage | 3TB | 5TB | +| RAM | 32gb | 64gb | + +:::info Estimates +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 node + +- [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. +- [Transaction Pool Nodes](./p2p-network.md#network-participants) + - Maintain a pool of transactions that are ready to be included in a block. +- Archival nodes + - A full node that also stores the full history of the network + - Used to provide historical membership proofs, e.g., prove that $x$ was included in block $n$. + - In the current model, it is expected that there are standardized interfaces by which well known sequencers, i.e., those operated by well respected community members or service providers, are frequently and regularly uploading historical copies of the Aztec state to immutable and decentralized storage providers such as: IPFS, Filecoin, Arweave, etc. The specific details of such is TBD and likely to be facilitated via RFP. +- Help figure it out by submitting a proposal on the [Aztec research forum](https://forum.aztec.network/)! + +## Registration + +Sequencers must stake an undetermined amount of a native token on Layer-1 to join the protocol, reflecting Aztec's economic security needs. For consensus based network, they enter an _entryPeriod_ before becoming active. This period aims to provide stability (and predictability) to the sequencer set over short time frames which is desirable for PoS based consensus networks when progressing blocks. For non-consensus based networks such as the initial Fernet implementation, an _entryPeriod_ can be used for limiting the ability to quickly get outsized influence over governance decisions, but is not strictly necessary. + +:::info +What is Aztec's economic security needs? Please clarify. +::: + +Currently, Provers don't need to register but must commit a bond during the `prover commitment phase` articulated below. This ensures economic guarantees for timely proof generation, and therefore short-term liveness. If the prover is unable or unwilling to produce a proof for which they committed to in the allotted time their bond will be slashed. + +```mermaid +sequenceDiagram + +participant Anyone +participant Contract as Aztec L1 Contract +participant Network as Aztec Network + +Anyone ->> Contract: register as a sequencer +Anyone --> Anyone: Wait epoch +Anyone ->> Network: eligible as a sequencer +``` + +## Block production + +:::danger **TODO** +- The diagram needs to be updated with respect to "VRF". +- In **Prover commitment** phase, it is not said what the signature is used for. I'm expecting that it is used to allow the prover to publish the message on behalf of the sequencer, but it is not made clear. +- In **Backup** phase, would be useful if we add a comment on the duration +- In Diagram + - add a dedicated timeline from the block production's PoV + - get rid of "pre-confirmed" +::: + +![Governance Summary Image](/img/protocol-specs/decentralization/Aztec-Block-Production-1.png) + +Every staked sequencers participate in the following phases, comprising an Aztec slot: + +1. **Proposal:** Sequencers generate a hash of every other sequencer's public keys and RANDAO values. They then compare and rank these, seeing if they possibly have a "high" ranking random hash. If they do, they may choose to submit a block proposal to Layer-1. The highest ranking proposal will become canonical. +2. **Prover commitment:** After an off-protocol negotiation with the winning sequencer, a prover submits a commitment to a particular Ethereum address that has intentions to prove the block. This commitment includes a signature from the sequencer and an amount X of funds that get slashed if the block is not finalized. +3. **Reveal:** Sequencer uploads the block contents required for progressing the chain to whatever DA layer is decided to be implemented, e.g., Ethereum's 4844 blobs. + - It is an active area of debate and research whether or not this phase is necessary, without intentions to implement "build ahead" or the ability to propose multiple blocks prior to the previous block being finalized. A possible implementation includes a block reward that incentivizes early reveal, but does not necessarily require it - turning the ability to reveal the block's data into another form of potential timing game. +4. **Proving:** The prover or prover network coordinates out of protocol to build the [recursive proof tree](./../rollup-circuits/index.md). After getting to the last, singular proof that reflects the entire blocks's state transitions they then upload the proof of the block to the L1 smart contracts. +5. **Finalization:** The smart contracts verify the block's proof, which triggers payouts to sequencer and prover, and the address which submits the proofs (likely the prover, but could be anyone such as a relay). Once finalized, the cycle continues! + - For data layers that is not on the host, the host must have learned of the publication from the **Reveal** before the **Finalization** can begin. +6. **Backup:** Should no prover commitment be put down, or should the block not get finalized, then an additional phase is opened where anyone can submit a block with its proof, in a based-rollup mode. In the backup phase, the first rollup verified will become canonical. + +```mermaid +sequenceDiagram + +participant Contract as Aztec L1 Contract +participant Network as Aztec Network +participant Sequencers +participant Provers + +loop Happy Path Block Production + Sequencers --> Sequencers: Generate random hashes and rank them + Sequencers ->> Contract: Highest ranking sequencers propose blocks + Note right of Contract: Proposal phase is over! + Contract ->> Network: calculates highest ranking proposal + Sequencers ->> Provers: negotiates the cost to prove + Sequencers ->> Contract: commits to a proverAddress + Provers ->> Contract: proverAddress deposits slashable stake + Note right of Network: "preconfirmed" this block is going to be next! + Sequencers --> Sequencers: executes public functions + Provers --> Provers: generates rollup proofs + Provers ->> Contract: submits proofs + Contract --> Contract: validates proofs and state transition + Note right of Contract: "block confirmed!" +end +``` + +### Constraining Randao + +The `RANDAO` values used in the score as part of the Proposal phase must be constrained by the L1 contract to ensure that the computation is stable throughout a block. This is to prevent a sequencer from proposing the same L2 block at multiple L1 blocks to increase their probability of being chosen. + +Furthermore, we wish to constrain the `RANDAO` ahead of time, such that sequencers will know whether they need to produce blocks or not. This is to ensure that the sequencer can ramp up their hardware in time for the block production. + +As only the last `RANDAO` value is available to Ethereum contracts we cannot simply read an old value. Instead, we must compute update it as storage in the contract. + +The simplest way to do so is by storing the `RANDAO` at every block, and then use the `RANDAO` for block number - $n$ when computing the score for block number $n$. For the first $n$ blocks, the value could pre-defined. + +:::info +Updating the `RANDAO` values used is a potential attack vector since it can be biased. By delaying blocks by an L1 block, it is possible to change the `RANDAO` value stored. Consider how big this issue is, and whether it is required to mitigate it. +::: + +## Exiting + +In order to leave the protocol sequencers can exit via another L1 transaction. After signaling their desire to exit, they will no longer be considered `active` and move to an `exiting` status. + +When a sequencer move to `exiting`, they might have to await for an additional delay before they can exit. This delay is up to the instance itself, and is dependent on whether consensus is used or not and what internal governance the instance supports. Beware that this delay is not the same as the exit delay in [Governance](./governance.md). + +:::danger +@lasse to clarify what semantics he would prefer to use here instead of exiting/active + +**Lasse Comment**: I'm unsure what you mean by "active" here. Is active that you are able to produce blocks? Is so, active seems fine. Also, not clear to me if `exiting` means that they be unable to propose blocks? If they are voting in them, sure put a delay in there, but otherwise I don't see why they should be unable to leave (when we don't have consensus for block production). +::: + +```mermaid +sequenceDiagram + +participant Anyone as Sequencer +participant Contract as Aztec L1 Contract +participant Network as Aztec Network + +Anyone ->> Contract: exit() from being a sequencer +Note left of Contract: Sequencer no longer eligible for Fernet elections +Anyone --> Anyone: Wait 7-21 days +Anyone ->> Network: exit successful, stake unlocked +``` + +## Confirmation rules + +There are various stages in the block production lifecycle that a user and/or application developer can gain insights into where their transaction is, and when it is considered confirmed. + +Notably there are no consistent, industry wide definitions for confirmation rules. Articulated here is an initial proposal for what the Aztec community could align on in order to best set expectations and built towards a consistent set of user experiences/interactions. Alternative suggestions encouraged! + +Below, we outline the stages of confirmation. + +1. Executed locally +2. Submitted to the network + - users no longer need to actively do anything +3. In the highest ranking proposed block +4. In the highest ranking proposed block, with a valid prover commitment +5. In the highest ranking proposed block with effects available on the DA Layer +6. In a proven block that has been verified / validated by the L1 rollup contracts +7. In a proven block that has been finalized on the L1 + +```mermaid +sequenceDiagram + +participant Anyone as User +participant P2P Network +participant Sequencer +participant Network as Aztec Network +participant Contract as Ethereum + +Anyone --> Anyone: generates proof locally +Anyone ->> P2P Network: send transaction +P2P Network --> Sequencer: picks up tx +Sequencer ->> Network: sequencer puts tx in a block +Network --> Network: executes and proves block +Network ->> Contract: submits to L1 for verification +Contract --> Contract: verifies block +Contract --> Contract: waits N more blocks +Contract --> Contract: finalizes block +Network --> Contract: updates state to reflect finality +Anyone ->> Network: confirms on their own node or block explorer +``` + + + +```mermaid +journey + title Wallet use case, basic transfer (tx confirmation happy path) + section User Device + Proof generated: 0: User + Broadcast Tx: 0: User + section Network + In highest ranking proposal: 2: Sequencer + Proposal with valid prover commitment: 3: Sequencer + Tx effects available on DA: 4: Sequencer + section L1 + Tx in block verified on L1: 6: Sequencer + Tx in block finalized on L1: 7: Sequencer +``` + +## Economics + +In the current Aztec model, it's expected that block rewards in the native token are allocated to the sequencer, the prover, and the entity submitting the rollup to L1 for verification. Sequencers retain the block's fees and MEV (Maximal Extractable Value). A potential addition in consideration is the implementation of MEV or fee burn. The ratio of the distribution is to be determined, via modeling and simulation. + +Future Aztec versions will receive rewards based on their staked amount, as determined by the Aztec governance and [incentives contracts](./governance.md). This ensures that early versions remain eligible for rewards, provided they have active stake. Changes to the network's economic structure, especially those affecting block production and sequencer burden, require thorough consideration due to the network's upgrade and governance model relying on an honest majority assumption and at a credibly neutral sequencer set for "easy" proposals. + +:::info +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 + +:::success + +### 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. +::: + +It is expected that any Aztec sequencer client software will initially ship with some form of first price or priority gas auction for transaction ordering. Meaning that in general, transactions paying higher fees will get included earlier in the network's transaction history. Similar to Layer-1, eventually an opt-in, open source implementation of "out of protocol proposer builder separation" (PBS) such as [mev-boost](https://boost.flashbots.net/) will likely emerge within the community, giving sequencers an easier to access way to earn more money during their periods as sequencers. This is an active area of research. + +## Proof-boost + +It is likely that this proving ecosystem will emerge around a [flashbots mev-boost][https://boost.flashbots.net/] like ecosystem, specifically tailored towards the needs of sequencers negotiating the cost for a specific proof or set of proofs. Currently referred to as `proof-boost` or `goblin-boost` (due to goblin plonk..). + +Specifically, Proof boost is expected to be open source software sequencers can optionally run alongside their clients that will facilitate a negotiation for the rights to prove this block, therefore earning block rewards in the form of the native protocol token. After the negotiation, the sequencer will commit to an address, and that address will need to put up an economic commitment (deposit) that will be slashed in the event that the block's proofs are not produced within the alloted timeframe. + +![image](/img/protocol-specs/decentralization/Aztec-Block-Production-3.png) + +Initially it's expected that the negotiations and commitment could be facilitated by a trusted relay, similar to L1 block building, but options such as onchain proving pools are under consideration. Due to the out of protocol nature of [Sidecar](https://forum.aztec.network/t/proposal-prover-coordination-sidecar/2428), these designs can be iterated and improved upon outside the scope of other Aztec related governance or upgrades - as long as they maintain compatibility with the currently utilized proving system(s). Eventually, any upgrade or governance mechanism may choose to enshrine a specific well adopted proving protocol, if it makes sense to do so. + +## Diagrams + +### 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. +::: + +```mermaid +sequenceDiagram + +participant Anyone +participant Contract as Aztec L1 Contract +participant Network as Aztec Network +participant Sequencers +participant Provers + +Anyone ->> Contract: register() +Anyone --> Anyone: Wait epoch +Anyone ->> Network: eligible as a sequencer +loop Happy Path Block Production + Sequencers --> Sequencers: Generate random hashes and rank them + Sequencers ->> Contract: Highest ranking sequencers propose blocks + Note right of Contract: Proposal phase is over! + Contract ->> Network: calculates highest ranking proposal + Sequencers ->> Provers: negotiates the cost to prove + Sequencers ->> Contract: commits to a proverAddress + Provers ->> Contract: proverAddress deposits slashable stake + Note right of Network: "preconfirmed" this block is going to be next! + Sequencers --> Sequencers: executes public functions + Provers --> Provers: generates rollup proofs + Provers ->> Contract: submits proofs + Contract --> Contract: validates proofs and state transition + Note right of Contract: "block confirmed!" +end +Sequencers ->> Contract: exit() +Sequencers --> Sequencers: wait epoch +``` + +### 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. + +```mermaid +sequenceDiagram + +participant Contract as Aztec L1 Contract +participant Network as Aztec Network +participant Sequencers +participant Provers + +loop Happy Path Block Production + Sequencers --> Sequencers: Generate random hashes and rank them + Sequencers ->> Contract: Propose block + indicate that they desire to upgrade + Note right of Contract: Proposal phase is over! + Contract ->> Network: calculates highest ranking proposal + vote + Sequencers ->> Provers: negotiates the cost to prove + Sequencers ->> Contract: commits to a proverAddress + Provers ->> Contract: proverAddress deposits slashable stake + Note right of Network: "preconfirmed" this block is going to be next! + Sequencers --> Sequencers: executes public functions + Provers --> Provers: generates rollup proofs + Provers ->> Contract: submits proofs + Contract --> Contract: validates proofs and state transition + Note right of Contract: "block confirmed! votes counted for upgrade!" +end +``` + +### 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. + +```mermaid +sequenceDiagram + +participant Anyone +participant Contract as Aztec L1 Contract +participant Network as Aztec Network +participant Sequencers + +loop Happy Path Block Production + Sequencers --> Sequencers: Generate random hashes and rank them + Contract --> Contract: Waited n L1 blocks.... Proposal phase is over + Contract --> Network: calculates highest ranking proposal + Note left of Network: No one proposed a block... backup mode enabled! + Anyone ->> Contract: submits a rollup... + Contract --> Contract: validates proofs and state transition + Note right of Contract: "block confirmed!" +end +``` + +:::danger +There is an outstanding concern that this may result in L1 censorship. L1 builders may choose to not allow block proposals to land on the L1 contracts within a sufficient amount of time, triggering "backup" mode - where they could have a block pre-built and proven, waiting L1 submission at their leisure. This scenario requires some careful consideration and modeling. A known and potential mitigiation includes a longer proposal phase, with a relatively long upper bounds to submit a proposal. Given that all sequencers are able to participate, it's effectively a "priority ranked race" within some form of ["timing game"](https://ethresear.ch/t/timing-games-implications-and-possible-mitigations/17612). +::: + +We also introduce a similar backup mode in the event that there is a valid proposal, but no valid prover commitment (deposit) by the end of the prover commitment phase. + +```mermaid +sequenceDiagram + +participant Anyone +participant Contract as Aztec L1 Contract +participant Network as Aztec Network +participant Sequencers +participant Provers + +loop Happy Path Block Production + Sequencers --> Sequencers: Generate random hashes and rank them + Sequencers ->> Contract: Highest ranking sequencers propose blocks + Note right of Contract: Proposal phase is over! + Contract ->> Network: calculates highest ranking proposal + Sequencers ->> Provers: negotiates the cost to prove + Contract --> Contract: Waited 5 L1 blocks.... Prover commitment phase is over + Note left of Network: No one committed to prove this block... backup mode enabled! + Anyone ->> Contract: submits a rollup... + Contract --> Contract: validates proofs and state transition + Note right of Contract: "block confirmed!" +end +``` + +## Glossary + +:::danger +TO DO - define the things +::: diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/decentralization/governance.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/decentralization/governance.md new file mode 100644 index 000000000000..ee7208b76f93 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/decentralization/governance.md @@ -0,0 +1,247 @@ +# Governance & Upgrades + +:::danger +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 + +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. + +The version registry will keep track of all historical versions of Aztec & provide them with incentives proportionate to their current stake. Additionally the governance contract will point to what the _current canonical_ version of Aztec is, particularly relevant for 3rd parties to follow, such as centralized exchanges, or portals that wish to follow Aztec governance. + +![Governance Summary Image](/img/protocol-specs/decentralization/Aztec-Governance-Summary-1.png) + +## 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". + +![Rewards Summary Image](/img/protocol-specs/decentralization/Aztec-Governance-Summary-2.png) + +Given that deployments may be immutable, it is necessary to ensure that there are operators, i.e., sequencers & provers, running the infrastructure for a given deployment as long as users are interested in it. Therefore we suggest a model where all previous canonical instances of Aztec are rewarded pro-rata to their current proportion of stake. + +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 + +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 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](/img/protocol-specs/decentralization/Aztec-Governance-Summary-3.png) + +The ability to upgrade to v1 is articulated below, and should follow a "happy path" upgrade where a majority of the v0 sequencer set must agree to upgrade by voting during their block proposals, similar to what was articulated in [the empire stakes back](https://forum.aztec.network/t/upgrade-proposal-the-empire-stakes-back/626). Additionally, token holders can directly participate in the vote, or choose to delegate a vote with the weight of their tokens to another address, including the v0 rollup. + +![Version 1 Deployment Image](/img/protocol-specs/decentralization/Aztec-Governance-Summary-4.png) + +## 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 + +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 + +### Participation + +Aztec's governance voting occurs within the governance contract, and the tokens being utilized must be "locked within governance" i.e., non-transferable. + +Any token holder is able to directly vote via an interaction with the governance contract. Specifically, this includes those with locked, non-circulating tokens. The "ballot" is a simple yes/no/abstain vote on a proposal, and the amount of tokens being voted with. Note that this allows the same actor to vote multiple times, and even vote both yes and no with shares of their power. This allows for a more nuanced vote for contracts that control power for multiple users, such as a DAO, a rollup instance or a portal. + +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 + +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. + +Tokens whose power did not vote "yes" are free to leave whenever they chose. This ensures that it is always possible to "ragequit" the governance if they disagree with an upgrade, and use or exit from the instance they are using. + +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 + +A vote is defined as passing if a majority of the voting weight votes "yes" to the proposal. + +If the vote fails, there is no action needed. + +If the vote passes, and a new rollup has been determined to be the next canonical instance, it will become canonical in the amount of days defined within the vote's timelock. It is likely there are defined limitations around this parameter, e.g., it must be a 3-30 day timelock. This is explained more in the timing section below. At this block height, portals that desire to follow governance should start referencing the new canonical instance to ensure as many bridged assets are backed on the latest version as possible. + +:::danger +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 + +### 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 + +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) + +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. + +:::info +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 + +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 + +```mermaid +sequenceDiagram + +participant Current Canonical Rollup as Current Rollup +participant Sequencers +participant Version Registry as Governance +participant Next Rollup +participant Anyone + +Current Canonical Rollup ->> Version Registry: proposeCanonicalRollup(nextRollup) +loop Voting + loop Canonical Rollup Voting + Sequencers ->> Current Canonical Rollup: canonicalVote(yes | no | abstain, amount) + Current Canonical Rollup --> Current Canonical Rollup: Count votes + end + Current Canonical Rollup ->> Version Registry: publishVoteResult(yes | no | abstain, amount) + Anyone ->> Version Registry: addVote(yes | no | abstain, amount) + Version Registry --> Version Registry: Count votes +end +Note right of Version Registry: Vote passed! +Version Registry ->> Version Registry: markPendingCanonical(nextRollup) +Note right of Version Registry: Wait at least 30 days! +Next Rollup ->> Version Registry: markCanonical(nextRollup) +Sequencers ->> Next Rollup: Proposing new blocks here! +``` + +### "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. + +```mermaid +sequenceDiagram + +participant Current Canonical Rollup as Current Rollup +participant Sequencers +participant Version Registry as Governance +participant Next Rollup +participant Anyone + +Anyone ->> Version Registry: lockTokensAndVote(1% of total supply, nextRollup) +loop Voting + Anyone ->> Version Registry: addVote(yes | no | abstain, amount) + Version Registry --> Version Registry: Count votes +end +Note right of Version Registry: Vote passed! +Version Registry ->> Version Registry: markPendingCanonical(nextRollup) +Note right of Version Registry: Wait at least 30 days! +Note left of Sequencers: Upgrade to new client +Next Rollup ->> Version Registry: markCanonical(nextRollup) +Sequencers ->> Next Rollup: Proposing new blocks here! +``` + +### Vote Delegation + +Any token holder can delegate their token's voting weight to another address, including the current canonical rollup's, if it wishes to follow along in that addresses' vote. The tokens being delegated will be locked, either within the governance contract or the vesting contract. + +:::info +:bulb: Locked, non-circulating tokens can be delegated! This "economic training wheel" enables Aztec Labs, Foundation, and potential investors to participate responsibly in governance while the protocol is getting off the ground. It is TBD if these locked, non-circulating, delegated tokens will be able to earn incentives, i.e., block rewards. +::: + +The diagram below articulates calling delegateTo(address) on both the governance contract and specifying a particular address. Additionally calling delegateTo() on the current canonical rollup if you wish to align with whatever voting mechanism that system currently as in place. + +```mermaid +sequenceDiagram + +participant Current Canonical Rollup as Current Rollup +participant Sequencers +participant Version Registry as Governance +participant Next Rollup +participant Anyone + +Current Canonical Rollup ->> Version Registry: proposeCanonicalRollup(nextRollup) +Note right of Version Registry: Vote starts in N days, e.g., 7 +Anyone ->> Version Registry: delegateTo(otherAddress) +Anyone ->> Current Canonical Rollup: delegateTo() +Note right of Version Registry: Must be delegated before vote starts +loop Voting + loop Canonical Rollup Voting + Sequencers ->> Current Canonical Rollup: canonicalVote(yes | no | abstain, amount) + Current Canonical Rollup --> Current Canonical Rollup: Count votes + end + Current Canonical Rollup ->> Version Registry: publishVoteResult(yes | no | abstain, amount) + Anyone ->> Version Registry: addVote(yes | no | abstain, amount) + Version Registry --> Version Registry: Count votes +end +Note right of Version Registry: Vote passed! +Version Registry ->> Version Registry: markPendingCanonical(nextRollup) +Note right of Version Registry: Wait at least 30 days! +Next Rollup ->> Version Registry: markCanonical(nextRollup) +Sequencers ->> Next Rollup: Proposing new blocks here! +``` + +## 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](/img/protocol-specs/decentralization/Aztec-Governance-Summary-4.png) + +### 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 + +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 + +:::danger +TO DO +::: + +## Glossary + +:::danger +TO DO +::: diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/decentralization/p2p-network.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/decentralization/p2p-network.md new file mode 100644 index 000000000000..971955e125c9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/decentralization/p2p-network.md @@ -0,0 +1,199 @@ +# 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 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. + +We can derive the following broad requirements of the P2P network. + +1. Support a node count up to approximately 10000. +2. Enable new participants to join the network in a permissionless fashion. +3. Propagate user transactions quickly and efficiently, throughout the network. +4. Provide protection against DoS, eclipse and sybil attacks. +5. Support a throughput of at least 10 transactions per second. +6. Support transaction sizes of ~40Kb. +7. Minimise bandwidth requirements overall and on any given node. + +### Network Participants + +For the purpose of this discussion, we define the 'Aztec Network' as the set of components required to ensure the continual distribution of user transactions and production of rollups. The participants in such a network are: + +- Sequencers - responsible for selecting transactions from the global pool and including them in rollups +- Provers - responsible for generating zk-proofs for the transaction and rollup circuits +- Transaction Pool Nodes - responsible for maintaining a local representation of the pending transaction pool +- Bootnodes - responsible for providing an entrypoint into the network for new participants + +These participants will likely operate an instance of the [Aztec Node](./actors.md) configured for their specific purpose. The Transaction Pool Node listed above is intended to describe a node with the minimum amount of functionality required to fulfill the needs of a PXE. Namely access to the global transaction pool and an up-to-date instance of the [state](../state/index.md). + +Anyone can operate an instance of the Aztec Node configured to serve their needs, providing increased privacy and censorship resistance. + +Client PXEs will interact with instances of the Aztec Node via it's JSON RPC interface. + +![P2P Network](/img/protocol-specs/decentralization/network.png) + +### Transaction Size + +[Transactions](../transactions/index.md) are composed of several data elements and can vary in size, determined largely by the private kernel proof and whether the transaction deploys any public bytecode. A typical transaction data footprint is shown in the following table. Any deployed contract bytecode would be in addition to this. + +| Element | Size | +| -------------------------------------------- | ----- | +| Public Inputs, Public Calls and Emitted Logs | ~8KB | +| Private Kernel Proof | ~32KB | + +### 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. + +## LibP2P + +Aztec will build it's network on [LibP2P](https://docs.libp2p.io/) and the suite of technologies that it contains. LibP2P has demonstrated it's capabilities as the set of protocols employed by Ethereum's consensus layer. Clients of the network will need to use a subset of LibP2P's protocols. + +There will be 2 primary communication domains within the network: + +1. Node Discovery +2. Transaction Gossip + +### Node Discovery + +When new participants join the network for the first time, they will need to locate peers. Ethereum's [DiscV5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md) is an adaptation of Kademlia, storing node records rather than content within its distributed hash table. From this, nodes are able to build what can be thought of as an address book of other participants. + +#### DiscV5 + +Whilst the DiscV5 specification is still under development, the protocol is currently in use by Ethereum's consensus layer with 100,000s of participants. Nodes maintain a DHT routing table of Ethereum Node Records (ENRs), periodically flushing nodes that are no longer responsive and searching for new nodes by requesting records from their neighbours. + +Neighbours in this sense are not necessarily in geographical proximity. Node distance is defined as the bitwise XOR of the nodes 32-bit IDs. + +``` +distance(Id1, Id2) = Id1 XOR Id2 +``` + +In some situations these distances are placed into buckets by taking the logarithmic distance. + +``` +log_distance(Id1, Id2) = log2(distance(Id1, Id2)) +``` + +In principle, an ENR is simply an arbitrary set of key/value pairs accompanied by a sequence number and signed by the author node's private key. In order to be included in and propagated around the DHT, the ENR must contain the node's dialable IP address and port. + +##### Transport + +The underlying transport for DiscV5 communication is UDP. Whilst UDP is not reliable and connectionless, it has much lower overhead than TCP or other similar protocols making it ideal for speculative communication with nodes over the discovery domain. It does mean that UDP communication is a requirement for nodes wishing to participate. + +##### Bootstrapping + +When a node wishes to join the network for the first time. It needs to locate at least 1 initial peer in order to 'discover' other nodes. This role is performed by known public 'bootnodes'. Bootnodes may not be full network participants, they may simply be entrypoints containing well populated routing tables for nodes to query. + +##### Topics + +Topics are part of the DiscV5 specification, though the spec is as yet unfinished and implementations do not yet exist. The intention of topics is for the Ethereum P2P network to efficiently support any number of applications under the same discovery scheme. To date, many other applications use Ethereum's discovery network but the only way to 'discover' other nodes for the same application is to query nodes at random and interrogate them. Topics will allow this to be done more efficiently with nodes being able to 'advertise' themselves as supporting specific applications across the network. + +##### DiscV5 on Aztec + +:::danger +The intention here is to use Ethereum's DiscV5 discovery network. This has not bees prototyped and is as yet untested. The alternative would be for Aztec nodes to form their own DiscV5 network, which would still work but wouldn't inherit the security proprties of Ethereum's. We need to more work to understand this. +::: + +Using Ethereum's DiscV5 network will have significant benefits for Aztec. Network security and resistance to censorship, sybil and eclipse attacks grows as the network gets larger. In the early days of the network, node discovery may be slow as the number of Aztec nodes will be small as a proportion of the network. This can be better understood with the deployment of testnets. Over time, as the network grows and we hopefully see the introduction of topics this node discovery process will improve. + +##### Aztec ENRs + +The node record for an Aztec node will contain the following key/value pairs. The network endpoints don't all need to be specified but nodes will require at least one ip address and port. The public key is required to verify the signature included with the node record. The id is the identity scheme with "v4" being that currently used by Ethereum. + +| key | value | +| --------- | ----------------------- | +| id | "v4" | +| secp256k1 | The node's public key | +| ip | ipv4 address | +| tcp | tcp port | +| ip6 | ipv6 address | +| tcp6 | tcp port for v6 address | +| aztec | The aztec chain id | +| eth | The ethereum chain id | + +### Transaction Gossip + +#### Transports + +LibP2P clients specify 1 or more types of transport for communicating with other nodes. Clients must specify at least the TCP transport for use within the Aztec Network. Clients may optionally specify other, more sophisticated transports but it is not guaranteed that other nodes will support them. + +Clients must accept connections on either IPV4, IPV6 or both. They must be able to dial both IPv4 and IPV6 addresses. + +Clients behind a NAT must be publically dialable and they must provide their publically dialable endpoint in their ENR. They must have their infrastructure configured to route traffic received at the dialable endpoint to the local listener. + +#### Multiplexing + +LibP2P supports the multiplexing of stream based transports such as TCP. There are 2 widely implemented multiplexing modules within LibP2P, [mplex](https://docs.libp2p.io/concepts/multiplex/mplex/) and the more sophisticated [yamux](https://docs.libp2p.io/concepts/multiplex/yamux/). Clients must be configured to support mplex and may choose to support yamux. + +#### Encryption handshake + +Communication between nodes within LibP2P is encrypted. This is important to protect individual nodes and the network at large from malicious actors. Establishing keys requires a secure handshake protocol. Client's must specify LibP2P's [noise](https://docs.libp2p.io/concepts/secure-comm/noise/) protocol for this purpose. + +#### GossipSub + +LibP2P's [GossipSub](https://docs.libp2p.io/concepts/pubsub/overview/) is a protocol that provides efficient propagation of transient messages to all participants of the gossip domain. Peers congregate around topics that they subscribe to and publish on the network. Each topic's network is further divided into 2 layers of peering. + +1. Full Message Peers - A sparsely connected network gossiping the complete contents of every message +2. Metadata Only Peers - A densely connected network gossiping only message metadata + +Peerings are bidirectional, meaning that for any two connected peers, both peers consider their connection to be full-message or both peers consider their connection to be metadata-only. + +Either peer can change the type of connection in either direction at any time. Peers periodically evaluate their peerings and attempt to balance the number of each type of peering to a configured range. The peering degree being the configured optimal number of full messages peers for each node. Generally speaking, a higher peering degree will result in faster message propagation to all peers at the expense of increased message duplication. + +These layers ensure that all messages are efficiently propagated throughout the network whilst significantly reducing redundant traffic and lowering bandwidth requirements. + +##### Peer Scoring + +To maintain the health of the network, peers are scored based on their behaviour. Peers found to misbehave are penalised on a sliding scale from a reluctance to convert them to full message peers to refusing to gossip with them altogether. + +##### Message Validation + +The gossipsub protocol requests message validation from the application. Messages deemed invalid are discarded and not propagated further. The application can specify whether the validation failure warrants the source peer being penalised for transmitting it. + +##### Message Cache + +Clients maintain a cache of recently seen messages from which other peers can request messages that they haven't received. Typically this would be used by metadata only peers who haven't received the complete message to do so. Messages are held in the cache for a configurable length of time, though this is usually just a few seconds. + +#### GossipSub on Aztec + +Aztec will use LibP2P's GossipSub protocol for transaction propagation. Nodes must support this protocol along with the v1.1 extensions and publish/subscribe to the topic `/aztec/{aztec-chainid}/tx/{message-version}`. The inclusion of `{message-version}` within the topic allows for the message specification to change and clients of the network will have to migrate to the new topic. We will aim to strike a balance between message propagation speed and lowering overall bandwidth requirements. Aztec block times will typically be 5-10 minutes so provided the network operates quickly enough for a user's transaction to be considered for inclusion in the 'next' block, the network can be optimised to reduce redundant gossiping. + +The table below contains some of the relevant configuration properties and their default values. These parameters can be validated on testnet but it is expected that for the Aztec network, clients would use similar values, perhaps reducing peering degree slightly to favour reduced bandwidth over message propagation latency. + +| Parameter | Description | Value | +| ------------------ | -------------------------------------------------------------------- | -------- | +| D | The desired peering degree | 6 | +| D_low | The peering degree low watermark | 4 | +| D_high | The peering degree high watermark | 12 | +| heartbeat_interval | The time between heartbeats\* | 1 second | +| mcache_len | The number of history windows before messages are ejected from cache | 5 | +| mcache_gossip | The number of history windows for messages to be gossiped | 3 | + +(\*)Several things happen at the heartbeat interval: + +1. The nature of peerings are evaluated and changed if necessary +2. Message IDs are gossiped to a randomly selected set of metadata only peers +3. The message cache is advanced by a history window + +##### Aztec Message Validation + +Because Aztec transactions are significant in size, it is important to ensure that invalid messages are not propagated. + +All of the [transaction validity conditions](../transactions//validity.md) must be verified at the point a message is received and reported to the protocol. + +Peers sending messages that breach any of the validity conditions should be penalised for doing so using the peer scoring system within the protocol. For nullifier validations, a grace period should be applied such that transactions containing nullifiers within very recently published blocks do not warrant a penalty being applied. It is important however that clients don't join the gossip protocol until they are fully synched with the chain, otherwise they risk being punished for unknowingly publishing invalid transactions. + +#### Aztec Message Encoding + +The [transaction object](../transactions/index.md) contains a considerable amount of data, much of it in the format of variable length vectors of fixed 32 byte wide fields. We will encode this object using [SSZ](https://ethereum.org/developers/docs/data-structures-and-encoding/ssz), the encoding used by Ethereum's consensus layer. This format requires a pre-defined schema but encodes the payload such that it is very efficient to deserialise reducing the burden on clients to validate messages at the point of receipt. + +Messages may then be compressed using [Snappy](https://github.com/google/snappy). Much of the payload may be uncompressable due to the random nature of it. We will validate this during testing. Whilst Snappy's compression is not as good as other algorithms such as zip, it offers an order of magnitude greater performance. + +#### Synchronising With The Transaction Pool + +GossipSub does not include a mechanism for synchronising the global set of messages at a given time. It is designed as a system to gossip transient data and messages are removed from caches after only a few seconds. We won't provide an additonal protocol to perform an immediate synchronisation of the transaction pool via the P2P network. Whilst this might be desirable, we have the following rationale for not facilitating this. + +1. Aztec transactions are large, approximately 40Kb. Downloading the entire pool would require transferring in the order of 100s of MB of data. At best this is undesirable and at worst it represents a DoS vector. + +2. It is largely redundant. At the point at which a node joins the network, it is likely that production of a block is already underway and many of the transactions that would be downloaded will be removed as soon as that block is published. + +3. Clients will naturally synchronise the transaction pool by joining the gossiping network and waiting for 1 or 2 blocks. New transactions will be received into the client's local pool and old transactions unknown to the client will be removed as blocks are published. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/fee-juice.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/fee-juice.md new file mode 100644 index 000000000000..175042f6f026 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/fee-juice.md @@ -0,0 +1,22 @@ +--- +title: Fee Juice +--- + +# Fee Juice + +Fee Juice is an enshrined asset in the Aztec network that is used to pay fees. + +It has several important properties: + +- It is fungible +- It cannot be transferred between accounts on the Aztec network +- It is obtained on Aztec via a bridge from Ethereum +- It only has public balances + +All transactions on the Aztec network have a [non-zero transaction_fee](./fee-schedule.md#da-gas), denominated in FPA, which must be paid for the transaction to be included in the block. + +When a block is successfully published on L1, the sequencer is paid on L1 the sum of all transaction fees in the block, denominated in FPA. + +:::danger +We need a definition of the L1 fee juice. +::: diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/fee-schedule.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/fee-schedule.md new file mode 100644 index 000000000000..a548bd540f8e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/fee-schedule.md @@ -0,0 +1,113 @@ +# Fee Schedule + +The [transaction fee](./specifying-gas-fee-info.md#transaction-fee) is comprised of a DA component, an L2 component, and an inclusion fee. The DA and L2 components are calculated by multiplying the gas consumed in each dimension by the respective `feePerGas` value. The inclusion fee is a fixed cost associated with the transaction, which is used to cover the cost of verifying the encompassing rollup proof on L1. + +## DA Gas + +DA gas is consumed to cover the costs associated with publishing data associated with a transaction. + +These data include: + +- new note hashes +- new nullifiers +- new l2 -> l1 message hashes +- new public data writes +- new logs +- protocol metadata (e.g. the amount of gas consumed, revert code, etc.) + +The DA gas used is then calculated as: + +``` +DA_BYTES_PER_FIELD = 32 +DA_GAS_PER_BYTE = 16 +FIXED_DA_GAS = 512 + +# FIXED_DA_GAS covers the protocol metadata, +# which should remain less than 512/16 = 32 bytes + +da_gas_per_field = DA_BYTES_PER_FIELD * DA_GAS_PER_BYTE + +note_hash_gas = da_gas_per_field * (number of notes) +nullifier_gas = da_gas_per_field * (number of nullifiers) +l2_to_l1_message_gas = da_gas_per_field * (number of l2_to_l1_messages) + +# public data writes specify a value and index +public_data_writes_gas = 2 * da_gas_per_field * (number of public_data_writes) + +log_gas = DA_GAS_PER_BYTE * (unencrypted_log_preimages_length + encrypted_log_preimages_length) + +da_gas_used = FIXED_DA_GAS + + note_hash_gas + + nullifier_gas + + l2_to_l1_message_gas + + public_data_writes_gas + + log_gas + + teardown_da_gas +``` + +:::note Non-zero `transaction_fees` +A side effect of the above calculation is that all transactions will have a non-zero `transaction_fee`. +::: + +## L2 Gas + +L2 gas is consumed to cover the costs associated with executing the public VM, proving the public VM circuit, and proving the public kernel circuit. + +It is also consumed to perform fixed, mandatory computation that must be performed per transaction by the sequencer, regardless of what the transaction actually does; examples are TX validation and updating state roots in trees. + +The public vm has an [instruction set](../public-vm/instruction-set.mdx) with opcode level gas metering to cover the cost of actions performed within the public VM. + +Additionally, there is a fixed cost associated with each iteration of the public VM (i.e. the number of enqueued public function calls, plus 1 if there is a teardown function), which is used to cover the cost of proving the public VM circuit. + +The L2 gas used is then calculated as: + +``` +FIXED_L2_GAS = 512 +FIXED_AVM_STARTUP_L2_GAS = 1024 + + +num_avm_invocations = (number of enqueued public function calls) + + (is there a teardown function ? 1 : 0) + +l2_gas_used = FIXED_L2_GAS + + FIXED_AVM_STARTUP_L2_GAS * num_avm_invocations + + teardown_l2_gas + + (gas reported as consumed by the public VM) +``` + +### L2 Gas from Private + +Private execution also consumes L2 gas, because there is still work that needs to be performed by the sequencer correspondent to the private outputs, which is effectively L2 gas. The following operations performed in private execution will consume L2 gas: + +- 32 L2 gas per note hash +- 64 L2 gas per nullifier +- 4 L2 gas per byte of logs (note encrypted, encrypted, and unencrypted) + +## Max Inclusion Fee + +Each transaction, and each block, has inescapable overhead costs associated with it which are not directly related to the amount of data or computation performed. + +These costs include: + +- verifying the private kernel proof of each transaction +- executing/proving the base/merge/root rollup circuits + - includes verifying that every new nullifier is unique across the tx/block + - includes processing l2->l1 messages of each transaction, even if they are empty (and thus have no DA gas cost) + - includes ingesting l1->l2 messages that were posted during the previous block + - injecting a public data write to levy the transaction fee on the `fee_payer` +- publishing the block header to the rollup contract on L1 + - includes verification of the rollup proof + - includes insertion of the new root of the l2->l1 message tree into the L1 Outbox + - consumes the pending messages in the L1 Inbox +- publishing the block header to DA + +See [the l1 contracts section](../l1-smart-contracts/index.md) for more information on the L1 Inbox and Outbox. + +Users cover these costs by [specifying an inclusion fee](./specifying-gas-fee-info.md#specifying-gas--fee-info), which is different from other parameters specified in that it is a fixed fee offered to the sequencer, denominated in [Fee Juice](./fee-juice.md). + +Even though these line items will be the same for every transaction in a block, the **cost** to the sequencer will vary, particularly based on: + +- congestion on L1 +- prevailing price of proof generation + +A price discovery mechanism is being developed to help users set the inclusion fee appropriately. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/index.md new file mode 100644 index 000000000000..469f69745045 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/index.md @@ -0,0 +1,20 @@ +--- +title: Gas & Fees +--- + +# Gas & Fees + +The Aztec network uses a fee system to incentivize sequencers to process transactions and publish blocks. + +This section breaks down: + +- [fee juice](./fee-juice.md) +- [how users specify gas/fee parameters in their transactions](./specifying-gas-fee-info.md) +- [fee abstraction](./tx-setup-and-teardown.md) +- [tracking gas/fee information in the kernel circuits](./kernel-tracking.md) +- [how gas/fees cover the costs of transaction execution](./fee-schedule.md) +- [published data pertaining to gas/fees](./published-gas-and-fee-data.md) + +import DocCardList from '@theme/DocCardList'; + + diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/kernel-tracking.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/kernel-tracking.md new file mode 100644 index 000000000000..738c4ba5ffe8 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/kernel-tracking.md @@ -0,0 +1,550 @@ +--- +title: Kernel Tracking +--- + +# Kernel Tracking + +Gas and fees are tracked throughout the kernel circuits to ensure that users are charged correctly for their transactions. + +## Private Kernel Circuits Overview + +On the private side, the ordering of the circuits is: + +1. PrivateKernelInit +2. PrivateKernelInner +3. PrivateKernelTail or PrivateKernelTailToPublic + +The structs are (irrelevant fields omitted): + +```mermaid +classDiagram + +class PrivateContextInputs { + +TxContext tx_context +} +PrivateContextInputs --> TxContext + +class PrivateCallData { + +PrivateCallStackItem call_stack_item + +CallRequest public_teardown_call_request +} +PrivateCallData --> PrivateCallStackItem + +class PrivateCallStackItem { + +AztecAddress contract_address + +PrivateCircuitPublicInputs public_inputs +} +PrivateCallStackItem --> PrivateCircuitPublicInputs + +class PrivateCircuitPublicInputs { + +TxContext tx_context + +bool is_fee_payer + +u32 min_revertible_side_effect_counter + +Field public_teardown_function_hash + +Header historical_header +} +PrivateCircuitPublicInputs --> TxContext +PrivateCircuitPublicInputs --> Header + +class PrivateKernelCircuitPublicInputs { + +u32 min_revertible_side_effect_counter + +AztecAddress fee_payer + +CallRequest public_teardown_call_request + +PrivateAccumulatedData end + +CombinedConstantData constants +} +PrivateKernelCircuitPublicInputs --> PrivateAccumulatedData +PrivateKernelCircuitPublicInputs --> CombinedConstantData + +class PrivateAccumulatedData { + +Field encrypted_log_preimages_length + +Field unencrypted_log_preimages_length + +Field[MAX_L2_TO_L1_MSGS_PER_TX] new_l2_to_l1_msgs + +SideEffect[MAX_ENCRYPTED_LOGS_PER_TX] encrypted_logs_hashes + +SideEffect[MAX_UNENCRYPTED_LOGS_PER_TX] unencrypted_logs_hashes + +SideEffect[MAX_NOTE_HASHES_PER_TX] new_note_hashes + +SideEffectLinkedToNoteHash[MAX_NULLIFIERS_PER_TX] new_nullifiers + +CallRequest[MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX] private_call_stack + +CallRequest[MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX] public_call_stack +} + +class CombinedConstantData { + +Header historical_header + +TxContext tx_context + +GlobalVariables global_variables +} +CombinedConstantData --> Header +CombinedConstantData --> TxContext + +class Header { + +GlobalVariables global_variables + +Fr total_fees +} +Header --> GlobalVariables + +class GlobalVariables { + +GasFees gas_fees +} +GlobalVariables --> GasFees + +class GasFees { + +Fr fee_per_da_gas + +Fr fee_per_l2_gas +} + +class TxContext { + +GasSettings gas_settings +} +TxContext --> GasSettings + +class GasSettings { + +Gas gas_limits + +Gas teardown_gas_allocations + +GasFees max_fees_per_gas + +Field max_inclusion_fee +} +GasSettings --> Gas +GasSettings --> GasFees + +class Gas { + +u32 l2_gas + +u32 da_gas +} + +class TxRequest { + +TxContext tx_context +} +TxRequest --> TxContext + +class PrivateKernelInitCircuitPrivateInputs { + +PrivateCallData private_call_data + +TxRequest tx_request +} +PrivateKernelInitCircuitPrivateInputs --> PrivateCallData +PrivateKernelInitCircuitPrivateInputs --> TxRequest + +class PrivateKernelInnerCircuitPrivateInputs { + +PrivateKernelData previous_kernel + +PrivateCallData private_call +} +PrivateKernelInnerCircuitPrivateInputs --> PrivateKernelData +PrivateKernelInnerCircuitPrivateInputs --> PrivateCallData + +class PrivateKernelData { + +PrivateKernelCircuitPublicInputs public_inputs +} +PrivateKernelData --> PrivateKernelCircuitPublicInputs + +class PrivateKernelTailCircuitPrivateInputs { + +PrivateKernelData previous_kernel +} +PrivateKernelTailCircuitPrivateInputs --> PrivateKernelData + +class PrivateKernelTailToPublicCircuitPrivateInputs { + +PrivateKernelData previous_kernel +} +PrivateKernelTailToPublicCircuitPrivateInputs --> PrivateKernelData + +``` + +## Private Context Initialization + +Whenever a private function is run, it has a `PrivateContext` associated with it, which is initialized in part from a `PrivateContextInputs` object. + +The [gas settings that users specify](./specifying-gas-fee-info.md) become part of the values in the `TxContext` within the `PrivateContextInputs` of the [entrypoint](./tx-setup-and-teardown.md#defining-setup). These values are copied to the `PrivateCircuitPublicInputs`. + +The same `TxContext` is provided as part of the `TxRequest` in the `PrivateKernelInitCircuitPrivateInputs`. This is done to ensure that the `TxContext` in the `PrivateCallData` (what was executed) matches the `TxContext` in the `TxRequest` (users' intent). + +## Private Kernel Init + +The PrivateKernelInit circuit takes in a `PrivateCallData` and a `TxRequest` and outputs a `PrivateKernelCircuitPublicInputs`. + +It must: + +- check that the `TxContext` provided as in the `TxRequest` input matches the `TxContext` in the `PrivateCallData` +- copy the `TxContext` from the `TxRequest` to the `PrivateKernelCircuitPublicInputs.constants.tx_context` +- copy the `Header` from the `PrivateCircuitPublicInputs` to the `PrivateKernelCircuitPublicInputs.constants.historical_header` +- set the min_revertible_side_effect_counter if it is present in the `PrivateCallData` +- set the `fee_payer` if the `is_fee_payer` flag is set in the `PrivateCircuitPublicInputs` +- set the `public_teardown_function_hash` if it is present in the `PrivateCircuitPublicInputs` +- set the `combined_constant_data.global_variables` to zero, since these are not yet known during private execution + +## Private Kernel Inner + +The PrivateKernelInner circuit takes in a `PrivateKernelData` and a `PrivateCallData` and ultimately outputs a `PrivateKernelCircuitPublicInputs`. + +It must: + +- set the `fee_payer` if the `is_fee_payer` flag is set in the `PrivateCircuitPublicInputs` (and is not set in the input `PrivateKernelData`) +- set the `public_teardown_function_hash` if it is present in the `PrivateCircuitPublicInputs` (and is not set in the input `PrivateKernelData`) +- copy the constants from the `PrivateKernelData` to the `PrivateKernelCircuitPublicInputs.constants` + +## Private Kernel Tail + +The PrivateKernelTail circuit takes in a `PrivateKernelData` and outputs a `KernelCircuitPublicInputs` (see diagram below). + +This is only used when there are no enqueued public functions or public teardown functions. + +It must: + +- check that there are no enqueued public functions or public teardown function +- compute the gas used + - this will only include DA gas _and_ any gas specified in the `teardown_gas_allocations` +- ensure the gas used is less than the gas limits +- ensure that `fee_payer` is set, and set it in the `KernelCircuitPublicInputs` +- copy the constants from the `PrivateKernelData` to the `KernelCircuitPublicInputs.constants` + +:::note +Transactions without a public component can safely set their teardown gas allocations to zero. They are included as part of the gas computation in the private kernel tail for consistency (limits always include teardown gas allocations) and future-compatibility if we have a need for private teardown functions. +::: + +## Private Kernel Tail to Public + +The PrivateKernelTailToPublic circuit takes in a `PrivateKernelData` and outputs a `PublicKernelCircuitPublicInputs` (see diagram below). + +This is only used when there are enqueued public functions or a public teardown function. + +It must: + +- check that there are enqueued public functions or a public teardown function +- partition the side effects produced during private execution into revertible and non-revertible sets of `PublicAccumulatedData` +- compute gas used for the revertible and non-revertible. Both sets can have a DA component, but the revertible set will also include the teardown gas allocations the user specified (if any). This ensures that the user effectively pre-pays for the gas consumed in teardown. +- ensure the gas used (across revertible and non-revertible) is less than the gas limits +- ensure that `fee_payer` is set, and set it in the `PublicKernelCircuitPublicInputs` +- set the `public_teardown_call_request` in the `PublicKernelCircuitPublicInputs` +- copy the constants from the `PrivateKernelData` to the `PublicKernelCircuitPublicInputs.constants` + +## Mempool/Node Validation + +A `Tx` broadcasted to the network has: + +``` +Tx { + /** + * Output of the private kernel circuit for this tx. + */ + data: PrivateKernelTailCircuitPublicInputs, + /** + * Proof from the private kernel circuit. + */ + proof: Proof, + /** + * Encrypted logs generated by the tx. + */ + encryptedLogs: EncryptedTxL2Logs, + /** + * Unencrypted logs generated by the tx. + */ + unencryptedLogs: UnencryptedTxL2Logs, + /** + * Enqueued public functions from the private circuit to be run by the sequencer. + * Preimages of the public call stack entries from the private kernel circuit output. + */ + enqueuedPublicFunctionCalls: PublicCallRequest[], + /** + * Public teardown function from the private circuit to be run by the sequencer. + * Preimage of the public teardown function hash from the private kernel circuit output. + */ + publicTeardownFunctionCall: PublicCallRequest, +} +``` + +Where the `PrivateKernelTailCircuitPublicInputs` may be destined for the base rollup (if there is no public component), or the public kernel circuits (if there is a public component). + +Regardless, it has a `fee_payer` set. + +When a node receives a transaction, it must check that: + +1. the `fee_payer` is set +2. the `fee_payer` has a balance of [Fee Juice](./fee-juice.md) greater than the computed [transaction fee](./specifying-gas-fee-info.md#transaction-fee) if the transaction has no public component +3. the `fee_payer` has a balance of FPA greater than the computed [max transaction fee](./specifying-gas-fee-info.md#maximum-transaction-fee) if the transaction has a public component + +See other [validity conditions](../transactions/validity.md). + +## Public Kernel Circuits + +On the public side, the order of the circuits is: + +1. PublicKernelSetup +2. PublicKernelAppLogic +3. PublicKernelTeardown +4. PublicKernelTail + +The structs are (irrelevant fields omitted): + +```mermaid +classDiagram + +class Gas { + +u32 l2_gas + +u32 da_gas +} + +class PublicKernelSetupCircuitPrivateInputs { + +PublicKernelData previous_kernel + +PublicCallData public_call +} +PublicKernelSetupCircuitPrivateInputs --> PublicKernelData +PublicKernelSetupCircuitPrivateInputs --> PublicCallData + +class PublicKernelData { + +PublicKernelCircuitPublicInputs public_inputs +} +PublicKernelData --> PublicKernelCircuitPublicInputs + +class PublicKernelCircuitPublicInputs { + +PublicAccumulatedData end_non_revertible + +PublicAccumulatedData end + +CombinedConstantData constants + +AztecAddress fee_payer + +CallRequest[MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX] public_teardown_call_stack + +u8 revert_code +} +PublicKernelCircuitPublicInputs --> PublicAccumulatedData +PublicKernelCircuitPublicInputs --> CombinedConstantData + +class CombinedConstantData { + +Header historical_header + +TxContext tx_context + +GlobalVariables global_variables +} + +class PublicAccumulatedData { + +Field encrypted_log_preimages_length + +Field unencrypted_log_preimages_length + +Field[MAX_L2_TO_L1_MSGS_PER_TX] new_l2_to_l1_msgs + +SideEffect[MAX_ENCRYPTED_LOGS_PER_TX] encrypted_logs_hashes + +SideEffect[MAX_UNENCRYPTED_LOGS_PER_TX] unencrypted_logs_hashes + +SideEffect[MAX_NOTE_HASHES_PER_TX] new_note_hashes + +SideEffectLinkedToNoteHash[MAX_NULLIFIERS_PER_TX] new_nullifiers + +CallRequest[MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX] public_call_stack + +PublicDataUpdateRequest[MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] public_data_update_requests + +Gas gas_used +} +PublicAccumulatedData --> Gas + +class PublicCallData { + +PublicCallStackItem call_stack_item +} +PublicCallData --> PublicCallStackItem + +class PublicCallStackItem { + +PublicCircuitPublicInputs public_inputs +} +PublicCallStackItem --> PublicCircuitPublicInputs + +class PublicCircuitPublicInputs { + +u8: revert_code + +Gas start_gas_left + +Gas end_gas_left + +Field transaction_fee + +GlobalVariables global_variables +} +PublicCircuitPublicInputs --> Gas +PublicCircuitPublicInputs --> GlobalVariables + +class PublicKernelAppLogicCircuitPrivateInputs { + +PublicKernelData previous_kernel + +PublicCallData public_call +} +PublicKernelAppLogicCircuitPrivateInputs --> PublicKernelData +PublicKernelAppLogicCircuitPrivateInputs --> PublicCallData + +class PublicKernelTeardownCircuitPrivateInputs { + +PublicKernelData previous_kernel + +PublicCallData public_call +} +PublicKernelTeardownCircuitPrivateInputs --> PublicKernelData +PublicKernelTeardownCircuitPrivateInputs --> PublicCallData + +class PublicKernelTailCircuitPrivateInputs { + +PublicKernelData previous_kernel +} +PublicKernelTailCircuitPrivateInputs --> PublicKernelData + +class KernelCircuitPublicInputs { + +CombinedAccumulatedData end + +CombinedConstantData constants + +PartialStateReference start_state + +u8 revert_code + +AztecAddress fee_payer +} +KernelCircuitPublicInputs --> CombinedAccumulatedData +KernelCircuitPublicInputs --> CombinedConstantData + + +class CombinedAccumulatedData { + +Field encrypted_log_preimages_length + +Field unencrypted_log_preimages_length + +Field encrypted_logs_hash + +Field unencrypted_logs_hash + +Field[MAX_L2_TO_L1_MSGS_PER_TX] new_l2_to_l1_msgs + +Field[MAX_NOTE_HASHES_PER_TX] new_note_hashes + +Field[MAX_NULLIFIERS_PER_TX] new_nullifiers + +PublicDataUpdateRequest[MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] public_data_update_requests + +Gas gas_used +} +CombinedAccumulatedData --> Gas + +class PublicContextInputs { + +Header historical_header + +GlobalVariables public_global_variables + +Gas gas_left + +Field transaction_fee +} +PublicContextInputs --> Header +PublicContextInputs --> GlobalVariables +PublicContextInputs --> Gas + +class GlobalVariables { + +GasFees gas_fees +} +GlobalVariables --> GasFees + +class GasFees { + +Fr fee_per_da_gas + +Fr fee_per_l2_gas +} + +``` + +## Public Context Initialization + +Whenever a public function is run, it has a `PublicContext` associated with it, which is initialized in part from a `PublicContextInputs` object. + +The sequencer must provide information including the current `gas_fees`, the current `gas_left`, and the `transaction_fee`, but we cannot trust these values to be correct: we must compute the correct values in the public kernel circuits, and validate that the sequencer provided the correct values. + +Further, the sequencer is only obligated to provide the `transaction_fee` to the teardown function, as that is the only point at which the transaction fee can be known. + +## Public Circuit Public Inputs + +The "outputs" of the public functions are coming from the public VM. + +Therefore, once we verify that the `start_gas_left` which the sequencer provided is correct, we can trust the `end_gas_left` that the public VM reports. + +Further, we can trust that the `transaction_fee` the public VM reported is the one which was made available to the public functions during teardown (though we must verify that the sequencer provided the correct value). + +The `PublicCircuitPublicInputs` include the `global_variables` as injected via the `PublicContextInputs`. The first public kernel circuit to run, regardless of whether it is a setup, app, or teardown kernel, is responsible for setting its `constant_data.global_variables` equal to these. All subsequent public kernel circuit runs must verify that the `global_variables` from the `PublicCircuitPublicInputs` match the ones from the previously set `constant_data.global_variables`. + +## Public Kernel Setup + +The PublicKernelSetup circuit takes in a `PublicKernelData` and a `PublicCallData` and outputs a `PublicKernelCircuitPublicInputs`. + +It must assert that the `revert_code` in the `PublicCircuitPublicInputs` is equal to zero. + +It must assert that the `public_call.call_stack_item.public_inputs.global_variables.gas_fees` are valid according to the [update rules defined](./published-gas-and-fee-data.md#updating-the-gasfees-object). + +It must compute the gas used in the `PublicKernelData` provided, and verify that the `gas_limits` in the `PublicKernelData`'s `TxContext` _minus_ the computed `gas_used` is equal to the `start_gas_left` specified on the `PublicCircuitPublicInputs`. + +This ensures that the public VM was provided with the correct starting gas values. + +It must update the gas used in `end_non_revertible` as: + +```rust +# assume the previous PublicKernelCircuitPublicInputs was copied to circuit_outputs +pub fn update_non_revertible_gas_used(public_call: PublicCallData, circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder) { + let tx_gas_limits = circuit_outputs.constants.tx_context.gas_settings.gas_limits; + let call_gas_left = public_call.call_stack_item.public_inputs.end_gas_left; + let accum_end_gas_used = circuit_outputs.end.gas_used; + + circuit_outputs.end_non_revertible.gas_used = tx_gas_limits + .sub(call_gas_left) + .sub(accum_end_gas_used); +} +``` + +:::note global gas limit for all enqueued public calls +**Within** the AVM, users may specify gas limits for each public function call. This **does not apply** to the "top-level" enqueued call: they all pull from the same global gas limit, and there is no way to "catch" an "out of gas" at this top-level apart from reverting. +::: + +## Public Kernel App Logic + +The PublicKernelAppLogic circuit takes in a `PublicKernelData` and a `PublicCallData` and outputs a `PublicKernelCircuitPublicInputs`. + +It must perform the same computation as the PublicKernelSetup regarding verification of the `start_gas_left` and the `gas_fees`. + +It must check the `revert_code` in the `PublicCircuitPublicInputs`. + +### If the `revert_code` is zero + +Instead of updating `end_non_revertible`, it must update `end` as: + +```rust +# assume the previous PublicKernelCircuitPublicInputs was copied to circuit_outputs +pub fn update_revertible_gas_used(public_call: PublicCallData, circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder) { + let tx_gas_limits = circuit_outputs.constants.tx_context.gas_settings.gas_limits; + let call_gas_left = public_call.call_stack_item.public_inputs.end_gas_left; + let accum_end_non_revertible_gas_used = circuit_outputs.end_non_revertible.gas_used; + + circuit_outputs.end.gas_used = tx_gas_limits + .sub(call_gas_left) + .sub(accum_end_non_revertible_gas_used); +} +``` + +### If the `revert_code` is non-zero + +All side effects from the `revertible` set are discarded. + +It consumes all the gas left: + +```rust +# assume the previous PublicKernelCircuitPublicInputs was copied to circuit_outputs +pub fn update_revertible_gas_used(public_call: PublicCallData, circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder) { + let tx_gas_limits = circuit_outputs.constants.tx_context.gas_settings.gas_limits; + let accum_end_non_revertible_gas_used = circuit_outputs.end_non_revertible.gas_used; + + circuit_outputs.end.gas_used = tx_gas_limits + .sub(accum_end_non_revertible_gas_used); +} +``` + +It sets the `revert_code` in `PublicKernelCircuitPublicInputs` to `1`. + +:::note Gas reserved for public teardown +Recall in the [Private Kernel Tail to Public](#private-kernel-tail-to-public) circuit, the gas allocated for the public teardown function was included in the `end` gas used. This ensures that we have gas available for teardown even though app logic consumed all gas. +::: + +:::warning +Consuming all gas left in the event of revert creates incentives for the sequencer to arrange transactions such that they revert, which is suboptimal. Future improvements will likely address this by only consuming the gas that was actually used, even in the event of revert. +::: + +## Public Kernel Teardown + +The PublicKernelTeardown circuit takes in a `PublicKernelData` and a `PublicCallData` and outputs a `PublicKernelCircuitPublicInputs`. + +It must perform the same computation as the PublicKernelSetup regarding verification of the `gas_fees`. + +It must assert that the `start_gas_left` is equal to the PublicKernelData's `public_inputs.constants.tx_context.gas_settings.teardown_gas_allocations` + +It must also compute the gas used in the `PublicKernelData` provided, and the [transaction fee](./specifying-gas-fee-info.md#transaction-fee) using this computed value, then verify that the `transaction_fee` in the `PublicCircuitPublicInputs` is equal to the computed transaction fee. + +This ensures that the public VM was provided with the correct transaction fee, and that teardown did not exceed the gas limits. + +### Handling reverts + +Teardown is attempted even if the app logic failed. + +The teardown kernel can see if the app logic failed by checking if `revert_code` in the `PublicKernelCircuitPublicInputs` is set to `1`. + +It also has access to the `revert_code` reported by the AVM of the current call within `PublicCircuitPublicInputs`. + +The interplay between these two `revert_code`s is as follows: + +| Kernel `revert_code` | current AVM `revert_code` | Resulting Kernel `revert_code` | +| -------------------- | ------------------------- | ------------------------------ | +| 0 | 0 | 0 | +| 1 | 0 | 1 | +| 0 | 1 | 2 | +| 1 | 1 | 3 | +| 2 or 3 | (any) | (unchanged) | + +## Rollup Kernel Circuits + +The base rollup kernel circuit takes in a `KernelData`, which contains a `KernelCircuitPublicInputs`, which it uses to compute the `transaction_fee`. + +Additionally, it verifies that the max fees per gas specified by the user are greater than the current block's fees per gas. It also verifies the `constant_data.global_variables.gas_fees` are correct. + +After the public data writes specific to this transaction have been processed, and a new tree root is produced, the kernel circuit injects an additional public data write based upon that root which deducts the transaction fee from the `fee_payer`'s balance. + +The calculated trasaction fee is set as output on the base rollup as `accumulated_fees`. Each subsequent merge rollup circuit sums this value from both of its inputs. The root rollup circuit then uses this value to set the `total_fees` in the `Header`. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/published-gas-and-fee-data.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/published-gas-and-fee-data.md new file mode 100644 index 000000000000..1aded7d8a3e6 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/published-gas-and-fee-data.md @@ -0,0 +1,34 @@ +--- +title: Published Gas & Fee Data +--- + +# Published Gas & Fee Data + +When a block is published to L1, it includes information about the gas and fees at a block-level, and at a transaction-level. + +## Block-level Data + +The block header contains a `GlobalVariables`, which contains a `GasFees` object. This object contains the following fields: + +- `feePerDaGas`: The fee in [Fee Juice](./fee-juice.md) per unit of DA gas consumed for transactions in the block. +- `feePerL2Gas`: The fee in FPA per unit of L2 gas consumed for transactions in the block. + +`GlobalVariables` also includes a `coinbase` field, which is the L1 address that receives the fees. + +The block header also contains a `totalFees` field, which is the total fees collected in the block in FPA. + +## Updating the `GasFees` Object + +Presently, the `feePerDaGas` and `feePerL2Gas` are fixed at `1` FPA per unit of DA gas and L2 gas consumed, respectively. + +In the future, these values may be updated dynamically based on network conditions. + +:::note Gas Targets +Should we move to a 1559-style fee market with block-level gas targets, there is an interesting point where gas "used" presently includes the entire [`teardown_gas_allocation`](./specifying-gas-fee-info.md) regardless of how much of that allocation was spent. In the future, if this becomes a concern, we can update our accounting to reflect the true gas used for the purposes of updating the `GasFees` object, though the user will be charged the full `teardown_gas_allocation` regardless. +::: + +## Transaction-level Data + +The transaction data which is published to L1 is a `TxEffects` object, which includes + +- `transaction_fee`: the fee paid by the transaction in FPA diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/specifying-gas-fee-info.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/specifying-gas-fee-info.md new file mode 100644 index 000000000000..274afac824fd --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/specifying-gas-fee-info.md @@ -0,0 +1,146 @@ +--- +title: Specifying Gas & Fee Info +--- + +# Specifying Gas & Fee Info + +When users submit a `TxExecutionRequest` on the Aztec Network, they provide a `TxContext`, which holds `GasSettings` for the transaction. + +An abridged version of the class diagram is shown below: + +```mermaid +classDiagram +class TxExecutionRequest { + +TxContext txContext +} + +class TxContext { + +GasSettings gasSettings +} + +class GasSettings { + +Gas gasLimits + +Gas teardownGasLimits + +GasFees maxFeesPerGas + +Fr maxInclusionFee +} + +class Gas { + +UInt32 daGas + +UInt32 l2Gas +} + +class GasFees { + +Fr feePerDaGas + +Fr feePerL2Gas +} + +TxContext --> GasSettings +GasSettings --> Gas +GasSettings --> GasFees +``` + +:::note +All fees are denominated in [Fee Juice](./fee-juice.md). +::: + +## Gas Dimensions and Max Inclusion Fee + +Transactions are metered for their gas consumption across two dimensions: + +1. **Data Availability (DA) Gas**: This dimension measures data usage by the transaction, e.g. creating/spending notes, emitting logs, etc. +2. **Layer 2 (L2) Gas**: This dimension measures computation usage of the public VM. + +This is similar to the gas model in Ethereum, where transaction consume gas to perform operations, and may also consume blob gas for storing data. + +Separately, every transaction has overhead costs associated with it, e.g. verifying its encompassing rollup proof on L1, which are captured in the `maxInclusionFee`, which is not tied to gas consumption on the transaction, but is specified in FPA. + +See the [Fee Schedule](./fee-schedule.md) for a detailed breakdown of costs associated with different actions. + +## `gasLimits` and `teardownGasLimits` + +Transactions can optionally have a "teardown" phase as part of their public execution, during which the "transaction fee" is available to public functions. This is useful to transactions/contracts that need to compute a "refund", e.g. contracts that facilitate [fee abstraction](./tx-setup-and-teardown.md). + +Because the transaction fee must be known at the time teardown is executed, transactions must effectively "prepay" for the teardown phase. Thus, the `teardownGasLimits` are portions of the `gasLimits` that are reserved for the teardown phase. + +For example, if a transaction has `gasLimits` of 1000 DA gas and 2000 L2 gas, and `teardownGasLimits` of 100 DA gas and 200 L2 gas, then the transaction will be able to consume 900 DA gas and 1800 L2 gas during the main execution phase, but 100 DA gas and 200 L2 gas **will be consumed** to cover the teardown phase: even if teardown does not consume that much gas, the transaction will still be charged for it; even if the transaction does not have a teardown phase, the gas will still be consumed. + +## `maxFeesPerGas` and `feePerGas` + +The `maxFeesPerGas` field specifies the maximum fees that the user is willing to pay per gas unit consumed in each dimension. + +Separately, the **protocol** specifies the current `feePerGas` for each dimension, which is used to calculate the transaction fee. + +These are held in the L2 blocks `Header` + +```mermaid +classDiagram +class Header { + +GlobalVariables globalVariables + +Fr totalFees + +Fr totalManaUsed +} + +class GlobalVariables { + +GasFees gasFees + +EthAddress coinbase +} + +class GasFees { + +Fr feePerDaGas + +Fr feePerL2Gas +} + +Header --> GlobalVariables +GlobalVariables --> GasFees +``` + +A transaction cannot be executed if the `maxFeesPerGas` is less than the `feePerGas` for any dimension. + +The `feePerGas` is presently held constant at `1` for both dimensions, but may be updated in future protocol versions. + +`totalFees` is the total fees collected in the block in FPA. + +`totalManaUsed` is the total mana used in the block and used to update the base fee. + +`coinbase` is the L1 address that receives the fees. + +## Transaction Fee + +The transaction fee is calculated as: + +``` +transactionFee = maxInclusionFee + (DA gas consumed * feePerDaGas) + (L2 gas consumed * feePerL2Gas) +``` + +:::note +Why is the "max" inclusion fee charged? We're working on a mechanism that will allow users to specify a maximum fee they are willing to pay, and the network will only charge them the actual fee. This is not yet implemented, so the "max" fee is always charged. +::: + +See more on how the "gas consumed" values are calculated in the [Fee Schedule](./fee-schedule.md). + +## Maximum Transaction Fee + +The final transaction fee cannot be calculated until all public function execution is complete. However, a maximum theoretical fee can be calculated as: + +``` +maxTransactionFee = maxInclusionFee + (gasLimits.daGas * maxFeesPerDaGas) + (gasLimits.l2Gas * maxFeesPerL2Gas) +``` + +This is useful for imposing [validity conditions](./kernel-tracking.md#mempoolnode-validation). + +## `fee_payer` + +The `fee_payer` is the entity that pays the transaction fee. + +It is effectively set in private by the contract that calls `context.set_as_fee_payer()`. + +This manifests as a boolean flag `is_fee_payer` in the `PrivateCircuitPublicInputs`. The private kernel circuits will check this flag for every call stack item. + +When a call stack item is found with `is_fee_payer` set, the kernel circuit will set `fee_payer` in its `PrivateKernelCircuitPublicInputs` to be the `callContext.contractAddress`. + +This is subsequently passed through the `PublicKernelCircuitPublicInputs` to the `KernelCircuitPublicInputs`. + +If the `fee_payer` is not set, the transaction will be considered invalid. + +If a transaction attempts to set `fee_payer` multiple times, the transaction will be considered invalid. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/tx-setup-and-teardown.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/tx-setup-and-teardown.md new file mode 100644 index 000000000000..8e5ef1e04684 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/gas-and-fees/tx-setup-and-teardown.md @@ -0,0 +1,99 @@ +--- +title: Transaction Setup and Teardown +--- + +# Transaction Setup and Teardown + +All transactions on the Aztec network have a private component, which is processed locally, and optionally have a public component, which is processed by sequencers using the [Public VM (AVM)](../public-vm/intro.md). + +Transactions are broken into distinct phases: + +1. Private setup +2. Private app logic +3. Public setup +4. Public app logic +5. Public teardown +6. Base rollup + +The private setup phase is used to specify what public function will be called for public teardown, and what entity will pay the transaction fee (i.e. the `fee_payer`). + +The "setup" phases are "non-revertible", meaning that if execution fails, the transaction is considered invalid and cannot be included in a block. + +If execution fails in the private app logic phase, the user will not be able to generate a valid proof of their private computation, so the transaction will not be included in a block. + +If the execution fails in the public app logic the _side effects_ from private app logic and public app logic will be reverted, but the transaction can still be included in a block. Execution then proceeds to the public teardown phase. + +If the execution fails in the public teardown phase, the _side effects_ from private app logic, public app logic, and public teardown will be reverted, but the transaction can still be included in a block. Execution then proceeds to the base rollup phase. + +In the event of a failure in public app logic or teardown, the user is charged their full [gas limit](./specifying-gas-fee-info.md#gaslimits-and-teardowngaslimits) for the transaction across all dimensions. + +The public teardown phase is the only phase where the final transaction fee is available to public functions. [See more](./specifying-gas-fee-info.md#gaslimits-and-teardowngaslimits). + +In the base rollup, the kernel circuit injects a public data write that levies the transaction fee on the `fee_payer`. + +## An example: Fee Abstraction + +Consider a user, Alice, who does not have FPA but wishes to interact with the network. Suppose she has a private balance of a fictitious asset "BananaCoin" that supports public and private balances. + +Suppose there is a Fee Payment Contract (FPC) that has been deployed by another user to the network. Alice can structure her transaction as follows: + +0. Before the transaction, Alice creates a private authwit in her wallet, allowing the FPC to transfer to public a specified amount of BananaCoin from Alice's private balance to the FPC's public balance. +1. Private setup: + - Alice calls a private function on the FPC which is exposed for public fee payment in BananaCoin. + - The FPC checks that the amount of teardown gas Alice has allocated is sufficient to cover the gas associated with the teardown function it will use to provide a refund to Alice. + - The FPC specifies its teardown function as the one the transaction will use. + - The FPC enqueues a public call to itself for the public setup phase. + - The FPC designates itself as the `fee_payer`. +2. Private app logic: + - Alice performs an arbitrary computation in private, potentially consuming DA gas. +3. Public setup: + - The FPC transfers the specified amount of BananaCoin from Alice to itself. +4. Public app logic: + - Alice performs an arbitrary computation in public, potentially consuming DA and L2 gas. +5. Public teardown: + - The FPC looks at `transaction_fee` to compute Alice's corresponding refund of BananaCoin. + - The FPC transfers the refund to Alice via a partial note. +6. Base rollup: + - The Base rollup kernel circuit injects a public data write that levies the transaction fee on the `fee_payer`. + +This illustrates the utility of the various phases. In particular, we see why the setup phase must not be revertible: if Alice's public app logic fails, the FPC is still going to pay the fee in the base rollup; if public setup were revertible, the transfer of Alice's BananaCoin would revert so the FPC would be losing money. + +## Sequencer Whitelisting + +Because a transaction is invalid if it fails in the public setup phase, sequencers are taking a risk by processing them. To mitigate this risk, it is expected that sequencers will only process transactions that use public functions that they have whitelisted. + +## Defining Setup + +The private function that is executed first is referred to as the "entrypoint". + +Tracking which side effects belong to setup versus app logic is done by keeping track of [side effect counters](../circuits/private-kernel-initial#processing-a-private-function-call), and storing the value of the counter at which the setup phase ends within the private context. + +This value is stored in the `PrivateContext` as the `min_revertible_side_effect_counter`, and is set by calling `context.end_setup()`. + +This is converted into the `PrivateCircuitPublicInputs` as `min_revertible_side_effect_counter`. + +Execution of the entrypoint is always verified/processed by the `PrivateKernelInit` circuit. + +It is only the `PrivateKernelInit` circuit that looks at the `min_revertible_side_effect_counter` as reported by `PrivateCirclePublicInputs`, and thus it is only the entrypoint that can effectively call `context.end_setup()`. + +## Defining Teardown + +At any point during private execution, a contract may call `context.set_public_teardown_function` to specify a public function that will be called during the public teardown phase. This function takes the same arguments as `context.call_public_function`, but does not have a side effect counter associated with it. + +Similar to `call_public_function`, this results in the hash of a `PublicCallStackItem` being set on `PrivateCircuitPublicInputs` as `public_teardown_function_hash`. + +The private kernel circuits will verify that this hash is set at most once. + +## Interpreting the `min_revertible_side_effect_counter` + +Notes, nullifiers, and logs are examples of side effects that are partitioned into setup and app logic. + +[Enqueueing a public function](../calls/enqueued-calls.md) from private is also a side effect: if the counter associated with an enqueued public function is less than the `min_revertible_side_effect_counter`, the public function will be executed during the public setup phase, otherwise it will be executed during the public app logic phase. + +As mentioned above, setting the public teardown function is not a side effect. + +If a transaction has enqueued public functions, or has a public teardown function, then during the PrivateKernelTailToPublic the `min_revertible_side_effect_counter` is used to partition the side effects produced during private execution into revertible and non-revertible sets on the `PublicKernelCircuitPublicInputs`, i.e. `end` and `end_non_revertible`. + +The public teardown function is set on the `PublicKernelCircuitPublicInputs` as `public_teardown_function_hash`. + +If a transaction does not have any enqueued public functions, and does not have a public teardown function, then the `PrivateKernelTail` is used instead of the `PrivateKernelTailToPublic`, and no partitioning is done. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/intro.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/intro.md new file mode 100644 index 000000000000..58966b25c73b --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/intro.md @@ -0,0 +1,102 @@ +# Editorial guidelines + +This protocol specification is a first attempt to describe the Aztec protocol in its entirety. + +It is still a work in progress. For example, we haven't settled on exact hashes, or encryption schemes, or gas metering schedules yet, so it won't contain such exact details as the Ethereum yellow paper. + +## Target audience + +Initially, the target audience is people at Aztec Labs. Can we explain the protocol requirements and the protocol itself _to ourselves_, without gaps? + +In particular, can we explain the protocol to the wider cryptography team, whose help we'll need to ensure the protocol is secure. + +(Naturally, if external people do stumble upon this work, they're by all means welcome to critique it and contribute!) + +## Editorial comments + +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. + +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). + +## Diagrams + +To increase the probability of diagrams being up-to-date we encourage you to write them in `mermaid`. Mermaid is a markdown-like language for generating diagrams and is supported by Docusaurus, so it will be rendered automatically for you. +You simply create a codeblock specifying the language as `mermaid` and write your diagram in the codeblock. For example: + +````txt +```mermaid +graph LR + A --> B + B --> C + C --> A +``` +```` + +```mermaid +graph LR + A --> B + B --> C + C --> A +``` + +Mermaid supports multiple types of diagrams, so finding one that suits your needs should be possible. Consult their [documentation](https://mermaid.js.org/intro/getting-started.html) or try out their [live editor](https://mermaid.live/) to see if they've got what you need. + +When writing class diagrams, we recommend using the `classDiagram` type and composition arrows `*--` to represent extensions. Also for the sake of readability, add all the components in the class itself, including composite types. For example: + +````txt +```mermaid +classDiagram + class A{ + foo: Bar + } + class B{ + hip: Hap + zap: Zip + } + class C{ + a: A + b: B + flip: Flap + } + C *-- A: a + C *-- B: b +``` +```` + +```mermaid +classDiagram + class A{ + foo: Bar + } + class B{ + hip: Hap + zap: Zip + } + class C{ + a: A + b: B + flip: Flap + } + C *-- A: a + C *-- B: b +``` + +### Mermaid doesn't cover my case, what should I do? + +If mermaid doesn't cover your case, please add both the rendered image and the source code to the documentation. Most of the tools for diagramming can export a non-rendered representation that can then be updated by other people. Please name it such that it is clear what tool was used. + +This should allow us to keep the diagrams up-to-date, by allowing others to update them. + +## For each protocol feature + +Describe the requirements. +Explain _why_ the requirements are requirements. Justify them. +Recall any discarded requirements. Explain that they were considered, and explain _why_ they were discarded. (Perhaps link to discourse, if applicable). + +Explain _what_ the protocol is, and explain _why_ it meets each requirement. +Provide links to discourse (if applicable), so that people can get context on protocol decisions. + +Flag any requirements that are not-yet being met by the protocol described within this doc. +Discuss what we've considered (or link to a discourse discussion). diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/l1-smart-contracts/frontier.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/l1-smart-contracts/frontier.md new file mode 100644 index 000000000000..885ae870e0ca --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/l1-smart-contracts/frontier.md @@ -0,0 +1,159 @@ +--- +title: Frontier Merkle Tree +--- + +The Frontier Merkle Tree is an append only Merkle tree that is optimized for minimal storage on chain. +By storing only the right-most non-empty node at each level of the tree we can always extend the tree with a new leaf or compute the root without needing to store the entire tree. +We call these values the frontier of the tree. +If we have the next index to insert at and the current frontier, we have everything needed to extend the tree or compute the root, with much less storage than a full merkle tree. +Note that we're not actually keeping track of the data in the tree: we only store what's minimally required in order to be able to compute the root after inserting a new element. + +We will go through a few diagrams and explanations to understand how this works. +And then a pseudo implementation is provided. + +## Insertion + +Whenever we are inserting, we need to update the "root" of the largest subtree possible. +This is done by updating the node at the level of the tree, where we have just inserted its right-most descendant. +This can sound a bit confusing, so we will go through a few examples. + +At first, say that we have the following tree, and that it is currently entirely empty. + +![alt text](/img/protocol-specs/l1-smart-contracts/frontier/image-1.png) + +### The first leaf + +When we are inserting the first leaf (lets call it A), the largest subtree is that leaf value itself (level 0). +In this case, we simply need to store the leaf value in `frontier[0]` and then we are done. +For the sake of visualization, we will be drawing the elements in the `frontier` in blue. + +![alt text](/img/protocol-specs/l1-smart-contracts/frontier/image-2.png) + +Notice that this will be the case whenever we are inserting a leaf at an even index. + +### The second leaf + +When we are inserting the second leaf (lets call it B), the largest subtree will not longer be at level 0. +Instead it will be level 1, since the entire tree below it is now filled! +Therefore, we will compute the root of this subtree, `H(frontier[0],B)` and store it in `frontier[1]`. + +Notice, that we don't need to store the leaf B itself, since we won't be needing it for any future computations. +This is what makes the frontier tree efficient - we get away with storing very little data. + +![alt text](/img/protocol-specs/l1-smart-contracts/frontier/image-3.png) + +### Third leaf + +When inserting the third leaf, we are again back to the largest subtree being filled by the insertion being itself at level 0. +The update will look similar to the first, where we only update `frontier[0]` with the new leaf. + +![alt text](/img/protocol-specs/l1-smart-contracts/frontier/image-4.png) + +### Fourth leaf + +When inserting the fourth leaf, things get a bit more interesting. +Now the largest subtree getting filled by the insertion is at level 2. + +To compute the new subtree root, we have to compute `F = H(frontier[0], E)` and then `G = H(frontier[1], F)`. +G is then stored in `frontier[2]`. + +As before, notice that we are only updating one value in the frontier. +![alt text](/img/protocol-specs/l1-smart-contracts/frontier/image-5.png) + +## Figuring out what to update + +To figure out which level to update in the frontier, we simply need to figure out what the height is of the largest subtree that is filled by the insertion. +While this might sound complex, it is actually quite simple. +Consider the following extension of the diagram. +We have added the level to update, along with the index of the leaf in binary. +Seeing any pattern? + +![alt text](/img/protocol-specs/l1-smart-contracts/frontier/image-6.png) + +The level to update is simply the number of trailing ones in the binary representation of the index. +For a binary tree, we have that every `1` in the binary index represents a "right turn" down the tree. +Walking up the tree from the leaf, we can simply count the number of right turns until we hit a left-turn. + +## How to compute the root + +Computing the root based on the frontier is also quite simple. +We can use the last index inserted a leaf at to figure out how high up the frontier we should start. +Then we know that anything that is at the right of the frontier has not yet been inserted, so all of these values are simply "zeros" values. +Zeros here are understood as the root for a subtree only containing zeros. + +For example, if we take the tree from above and compute the root for it, we would see that level 2 was updated last. +Meaning that we can simply compute the root as `H(frontier[2], zeros[2])`. + +![alt text](/img/protocol-specs/l1-smart-contracts/frontier/image-7.png) + +For cases where we have built further, we simply "walk" up the tree and use either the frontier value or the zero value for the level. + +## Pseudo implementation + +```python +class FrontierTree: + HEIGHT: immutable(uint256) + SIZE: immutable(uint256) + + frontier: HashMap[uint256, bytes32] # level => node + zeros: HashMap[uint256, uint256] # level => root of empty subtree of height level + + next_index: uint256 = 0 + + # Can entirely be removed with optimizations + def __init__(self, _height_: uint256): + self.HEIGHT = _height + self.SIZE = 2**_height + # Populate zeros + + def compute_level(_index: uint256) -> uint256: + ''' + We can get the right of the most filled subtree by + counting the number of trailing ones in the index + ''' + count = 0 + x = _index + while (x & 1 == 1): + count += 1 + x >>= 1 + return count + + def root() -> bytes32: + ''' + Compute the root of the tree + ''' + if self.next_index == 0: + return self.zeros[self.HEIGHT] + elif self.next_index == SIZE: + return self.frontier[self.HEIGHT] + else: + index = self.next_index - 1 + level = self.compute_level(index) + + temp: bytes32 = self.frontier[level] + + bits = index >> level + for i in range(level, self.HEIGHT): + is_right = bits & 1 == 1 + if is_right: + temp = sha256(frontier[i], temp) + else: + temp = sha256(temp, self.zeros[i]) + bits >>= 1 + return temp + + def insert(self, _leaf: bytes32): + ''' + Insert a leaf into the tree + ''' + level = self.compute_level(next_index) + right = _leaf + for i in range(0, level): + right = sha256(frontier[i], right) + self.frontier[level] = right + self.next_index += 1 +``` + +## Optimizations + +- The `zeros` can be pre-computed and stored in the `Inbox` directly, this way they can be shared across all of the trees. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/l1-smart-contracts/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/l1-smart-contracts/index.md new file mode 100644 index 000000000000..845c77e36aaa --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/l1-smart-contracts/index.md @@ -0,0 +1,584 @@ +--- +title: Cross-chain communication +--- + +This section describes what our L1 contracts do, what they are responsible for and how they interact with the circuits. + +Note that the only reason that we even have any contracts is to facilitate cross-chain communication. +The contracts are not required for the rollup to function, but required to bridge assets and to reduce the cost of light nodes. + +:::info Purpose of contracts +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 + +When presented with a new [`ProvenBlock`](../rollup-circuits/root-rollup.md) and its proof, an Aztec node can be convinced of its validity if the proof passes and the `Header.last_archive` matches the `archive` of the node (archive here represents a root of [archive tree](../state/archive.md)). +The `archive` used as public input is the archive after the new header is inserted (see [root rollup](./../rollup-circuits/root-rollup.md)). + +```python +def propose(block: ProvenBlock, proof: Proof): + header = block.header + block_number = header.global_variables.block_number + + # Ensure that the body is available + assert block.body.compute_commitment() == header.content_commitment + + assert self.archive == header.last_archive + assert proof.verify(header, block.archive) + assert self.inbox.consume() == header.in_hash + assert self.outbox.insert( + block_number, + header.content_commitment.out_hash, + header.content_commitment.tx_tree_height + math.ceil(log2(MAX_L2_TO_L1_MSGS_PER_TX)) + ) + self.archive = block.archive + + emit BlockProcessed(block_number) +``` + +:::info Why `math.ceil(log2(MAX_L2_TO_L1_MSGS_PER_TX))`? + +The argument to the `insert` function is the `outbox` is the heigh of the message tree. +Since every transaction can hold more than 1 message, it might add multiple layers to the tree. +For a binary tree, the number of extra layers to add is computed as `math.ceil(log2(MAX_L2_TO_L1_MSGS_PER_TX))`. +Currently, `MAX_L2_TO_L1_MSGS_PER_TX = 2` which means that we are simply adding 1 extra layer. + +::: + +While the `ProvenBlock` must be published and available for nodes to build the state of the rollup, we can build the validating light node (the contract) such that as long as the node can be _convinced_ that the data is available we can progress the state. +This means our light node can be built to only require a subset of the `ProvenBlock` to be published to Ethereum L1 as calldata and use a different data availability layer for most of the block body. +Namely, we need the cross-chain messages to be published to L1, but the rest of the block body can be published to a different data availability layer. + +:::info Validium or Rollup +If a different data availability layer than Ethereum is used for the block body, we are effectively building a Validium. +If we use Ethereum for the block body, we are building a Rollup. + +For more information around the requirements we have for the availability, see [Data Availability](../data-publication-and-availability/index.md). +::: + +Using the data structures defined throughout the [rollup circuits](./../rollup-circuits/index.md) section, we can outline the validating light node structure as follows: + +```mermaid +classDiagram + +class Inbox { + consume(): bytes32 +} + +class Outbox { + insert(block_number, out_hash, height) +} + +class Verifier { + verify(proof: Proof, header: Header, archive: Fr): bool +} + +class StateTransitioner { + archive: Snapshot + propose(header: Header, archive: Fr, proof: Proof, body: Body) +} +StateTransitioner --> Inbox: consume() +StateTransitioner --> Outbox: insert() +StateTransitioner --> Verifier: verify() +``` + +### State transitioner + +The state transitioner is the heart of the validating light node for the L2. +The contract keeps track of the current state of the L2 and progresses this state when a valid L2 block is received. +It also facilitates cross-chain communication (communication between the L1 inbox and outbox contracts). + +:::info +The following example shows a simplified case where proof and block are provided in the same transaction. +::: + +```python +class StateTransitioner: + + struct BlockLog: + archive: bytes32 + slot_number: uint128 + + VERIFIER: immutable(IVerifier) + INBOX: immutable(IInbox) + OUTBOX: immutable(IOutbox) + VERSION: immutable(uint256) + GENESIS_TIME: immutable(uint256) + SLOT_DURATION: immutable(uint256) + + blocks: BlockLog[] + + def __init__(self, ...): + ''' + Initialize the state transitioner + ''' + self.blocks.append(BlockLog({archive: bytes32(0), slot_number: 0})) + self.GENESIS_TIME = block.timestamp + + def propose( + self, + header: Header, + archive: Fr, + proof: Proof, + body: Body + ): + assert body.compute_commitment() == header.content_commitment + assert self.validate_header(header) + assert VERIFIER.verify(header, archive, proof) + assert self.INBOX.consume() == header.content_commitment.in_hash + assert self.OUTBOX.insert( + block_number, + header.content_commitment.out_hash, + header.content_commitment.tx_tree_height + math.ceil(log2(MAX_L2_TO_L1_MSGS_PER_TX)) + ) + self.blocks.append(BlockLog({ + archive: archive, + slot_number: header.global_variables.slot_number + })) + self.archive = archive + emit BlockProcessed(block_number) + + def validate_header( + self, + header: Header + ) -> bool: + assert header.global_variables.block_number = len(self.blocks) + assert header.global_variables.chain_id == block.chain_id + assert header.global_variables.version == self.VERSION + assert header.global_variables.timestamp == self.GENESIS_TIME + self.SLOT_DURATION * header.global_variables.slot_number + last_block = self.blocks[-1] + assert header.global_variables.slot_number > last_block.slot_number + assert header.archive == last_block.archive + return True +``` + +### Registry + +To keep one location where all the core rollup contracts can be found, we have a registry contract. +The registry is a contract that holds the current and historical addresses of the core rollup contracts. +The addresses of a rollup deployment are contained in a snapshot, and the registry is tracking version-snapshot pairs. +Depending on the upgrade scheme, it might be used to handle upgrades, or it could entirely be removed. +It is generally the one address that a node MUST know about, as it can then tell the node where to find the remainder of the contracts. +This is for example used when looking for the address new L2 blocks should be published to. + +## Message Bridges + +To let users communicate between L1 and the L2, we are using message bridges, namely an L1 inbox that is paired to an L2 outbox, and an L2 inbox that is paired to an L1 outbox. + +![Alt text](/img/protocol-specs/l1-smart-contracts/com-abs-6.png) + +:::info Naming is based from the PoV of the state transitioner. +::: + +While we logically have 4 boxes, we practically only require 3 of those. +The L2 inbox is not real - but only logical. +This is due to the fact that they are always inserted and then consumed in the same block! +Insertions require a L2 transaction, and it is then to be consumed and moved to the L1 outbox by the state transitioner in the same block. + +### Portals + +Some contracts on L2 might wish to talk to contracts on L1 - these recipients on L1 are called portals. + +Often it is desired to constrain where messages are sent to and received from, which can be done by keeping the portal address in the storage of the L2 contract, such that it can be loaded on demand. + +### Messages + +Messages that are communicated between the L1 and L2 need to contain a minimum of information to ensure that they can correctly consumed by users. Specifically the messages should be as described below: + +```solidity +struct L1Actor { + address: actor, + uint256: chainId, +} + +struct L2Actor { + bytes32: actor, + uint256: version, +} + +struct L1ToL2Msg { + L1Actor: sender, + L2Actor: recipient, + bytes32: content, + bytes32: secretHash, +} + +struct L2ToL1Msg { + L2Actor: sender, + L1Actor: recipient, + bytes32: content, +} +``` + +Beware, that while we speak of messages, we are practically passing around only their **hashes** to reduce cost. +The `version` value of the `L2Actor` is the version of the rollup, which is intended to be used to specify which version of the rollup the message is intended for or sent from. +This way, multiple rollup instances can use the same inbox/outbox contracts. + +:::info Why a single hash? +Compute on L1 is expensive, but storage is extremely expensive! +To reduce overhead, we trade storage for computation and only commit to the messages and then "open" these for consumption later. +However, since computation also bears significant cost we need to use a hash function that is relatively cheap on L1, while still being doable inside a snark. +For this purpose a modified SHA256 was chosen, modified by fitting the output value into a single field element using the modulo operator. +::: + +Some additional discussion/comments on the message structure can be found in the forum post, [The Republic](https://forum.aztec.network/t/the-republic-a-flexible-optional-governance-proposal-with-self-governed-portals/609/2#supporting-pending-messages-5). + +Since any data that is moving from one chain to the other at some point will live on L1, it will be public. While this is fine for L1 consumption (which is always public), we want to ensure that the L2 consumption can be private. +To support this, we use a nullifier scheme similar to what we are doing for the other [notes](./../state/note-hash-tree.md). +As part of the nullifier computation we use a `secret` which hashes to a `secretHash`, which ensures that only actors with knowledge of the `secret` will be able to see when it is spent on L2. + +Any message that is consumed on one side MUST be moved to the other side. +This is to ensure that the messages exist AND are only consumed once. +The L1 contracts handle one side and the circuits must handle the other. + +:::info Is `secretHash` required? +We are using the `secretHash` to ensure that the user can spend the message privately with a nullifier computation. +However, as the nullifier computation is almost entirely controlled by the Aztec contract (the application circuit, except the contract siloing - see [Nullifier Tree](./../state/nullifier-tree.md)). Contracts could compute a custom nullifier to have the `secretHash` included as part of the computation. +However, the chosen approach reduces the developer burden and reduces the likelihood of mistakes. +::: + + + +### Inbox + +When we say inbox, we are generally referring to the L1 contract that handles the L1 to L2 messages. + +The inbox takes messages from L1 contracts and inserts them into a series of message trees. +We build multiple "trees" instead of a single tree, since we are building one tree per block and not one large tree with all messages for all blocks. + +We need to split trees into epochs such that a sequencer can build a proof based on a tree that is not going to update in the middle of the proof building. Having one tree that updates across blocks would allow DOS attacks on the sequencer, which is undesirable. + +In practice, we introduce a "lag" between when trees are built and when they must be included. Whenever a new block is published, we start building a new tree, essentially meaning that at block $n$ we include tree $n$ which was created earlier (during block $n-1$). + +Below, tree $n$ is "fixed" when block $n$ needs to be published. Tree $n+1$ is being built upon until block $n$ is published. + +![Feeding trees into the blocks](/img/protocol-specs/l1-smart-contracts/tree-order.png) + +When the state transitioner processes a tree, it MUST insert the subtree into the "L2 outbox" ([message tree](./../state/index.md) included in global state). + +When a message is inserted into the inbox, the inbox **MUST** fill in the `sender`: + +- `L1Actor.actor`: The sender of the message (the caller), `msg.sender` +- `L1Actor.chainId`: The chainId of the L1 chain sending the message, `block.chainId` + +We MUST populate these values in the inbox, since we cannot rely on user input. +From the `L1ToL2Msg` we compute a hash of the message. +This hash is what is moved by the state transitioner to the L2 outbox. + +Since message from L1 to L2 can be inserted independently of the L2 block, the message transfer (moving from L1 inbox into L2 outbox) is not synchronous as it is for L2 to L1 messages. +This means that the message can be inserted into the inbox, but not yet moved to the outbox. +The message will be moved to the outbox when the state transitioner processes the message as part of a block. +Since sequencers are required to move the entire subtree at once, you can be sure that the message will be moved to the outbox. As mentioned earlier, segmenting updates is done to ensure that the messages are not used to DOS the state transitioner. + +The message tree is built on L1, so we need to use a gas-friendly hash-function such as SHA256. +However, we need to allow users to prove inclusion in this tree, so we cannot just insert the SHA256 tree into the rollup state, since it expensive to process in a zk circuit. +Therefore, we need to "convert" the SHA256 tree into a tree that uses a more snark-friendly hash. +This part is done in the [tree parity circuits](./../rollup-circuits/tree-parity.md). + +Furthermore, to build the tree on L1, we can optimize storage on L1 such that the insertions don't require a lot of merkle tree related data which could be cumbersome and prone to race-conditions (e.g., two insertions based on inclusion paths that are created at the same time will invalidate each other). + +The solution is to use a "frontier" merkle tree to store the messages. +This is a special kind of append-only merkle tree that allows us to store very few elements in storage, while still being able to extend it and compute the root of the tree. See the [Frontier Merkle Tree](#frontier-merkle-tree]) for more information on this. + +Assuming that we have these trees, we can build an `Inbox` as follows. +When a new block is published, we start building a new tree. +Notice however, that if we have entirely filled the current tree, we can start building a new one immediately, and the blocks can then "catch up". + +```python +class Inbox: + STATE_TRANSITIONER: immutable(address) + ZERO: immutable(bytes32) + + HEIGHT: immutable(uint256) + SIZE: immutable(uint256) + + trees: HashMap[uint256, FrontierTree] + + to_include: uint256 = 0 + in_progress: uint256 = 1 + + def __init__(self, _height: uint256, _zero: bytes32, _state_transitioner: address): + self.HEIGHT = _height + self.SIZE = 2**_height + self.ZERO = _zero + self.STATE_TRANSITIONER = _state_transitioner + + self.trees[1] = FrontierTree(self.HEIGHT) + + def insert(self, message: L1ToL2Message) -> bytes32: + ''' + Insert into the next FrontierTree. If the tree is full, creates a new one + ''' + if self.trees[self.in_progress].next_index == 2**self.HEIGHT: + self.in_progress += 1 + self.trees[self.in_progress] = FrontierTree(self.HEIGHT) + + message.sender.actor = msg.sender + message.sender.chain_id = block.chainid + + leaf = message.hash_to_field() + self.trees[self.in_progress].insert(leaf) + return leaf + + def consume(self) -> bytes32: + ''' + Consumes the current tree, and starts a new one if needed + ''' + assert msg.sender == self.STATE_TRANSITIONER + + root = self.ZERO + if self.to_include > 0: + root = self.trees[self.to_include].root() + + # If we are "catching up" we can skip the creation as it is already there + if self.to_include + 1 == self.in_progress: + self.in_progress += 1 + self.trees[self.in_progress] = FrontierTree(self.HEIGHT) + + self.to_include += 1 + + return root +``` + +#### L2 Inbox + +While the L2 inbox is not a contract, it is a logical concept 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. + +Just like the L1 variant, we must populate the `sender`: + +- `L2Actor.actor`: The sender of the message (the caller) +- `L2Actor.version`: The version of the L2 chain sending the message + +In practice, this is done in the kernel circuit of the L2, and the message hashes are then aggregated into a tree as outlined in the [Rollup Circuits section](./../rollup-circuits/index.md) before it is inserted into the L1 outbox. + +### Outbox + +The outboxes are the location where a user can consume messages from on the destination chain. +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 series of trees. +The trees are built from the messages of all the transactions in the block, and the root and height is then pushed to the L1 outbox. + +Whenever a portal wishes to consume a message, it proves that it is included in one of these roots and that it has not been consumed before. + +To address the nullifier (marking it is spent), we can simply use a bitmap and flip just 1 bit per message. This shares some of the cost of processing messages. + +This structure is used in many merkle airdrop contracts. Nevertheless, it requires some consideration from the developers side, as the portal needs to prepare the inclusion proof for the message before it can be consumed. The proof can be prepared based on the published data, so with good libraries it should be very straight forward for most cases. + +:::danger Checking sender +When consuming a message on L1, the portal contract must check that it was sent from the expected contract given that it is possible for multiple contracts on L2 to send to it. +If the check is not done this could go horribly wrong. +::: + +```python +class Outbox: + STATE_TRANSITIONER: immutable(address) + + struct RootData: + root: bytes32 + height: uint256 + nullified: HashMap[uint256, bool] + + roots: HashMap[uint256, RootData] + + def __init__(self, _state_transitioner: address): + self.STATE_TRANSITIONER = _state_transitioner + + def insert(index: uint256, root: bytes32, height: uint256): + assert msg.sender == self.STATE_TRANSITIONER + self.roots[index] = RootData(root, height, {}) + + def consume( + root_index: uint256, + leaf_index: uint256, + message: L2ToL1Message, + inclusion_proof: bytes[] + ): + leaf = message.hash_to_field() + assert msg_sender == message.recipient.actor + assert merkle_verify( + self.roots[root_index].root, + self.roots[root_index].height, + leaf, + inclusion_proof + ) + assert not(self.roots[root_index].nullified[leaf_index]) + self.roots[root_index].nullified[leaf_index] = True +``` + +#### L2 Outbox + +The L2 outbox is a merkle tree that is populated with the messages moved by the state transitioner through the converted tree as seen in [Rollup Circuits](./../rollup-circuits/index.md). +The messages are consumed on L2 by emitting a nullifier from the application circuit (Aztec contract). + +This means that all validation is done by the application circuit. +The application should: + +- Ensure that the message exists in the outbox (message tree) +- Ensure that the message sender is the expected contract +- Ensure that the message recipient is itself and that the version matches the expected version +- Ensure that the user knows `secret` that hashes to the `secretHash` of the message +- Compute a nullifier that includes the `secret` along with the message hash and the index of the message in the tree + - The index is included to ensure that the nullifier is unique for each message + +## Validity conditions + +While there are multiple contracts, they work in unison to ensure that the rollup is valid and that messages are correctly moved between the chains. +In practice this means that the contracts ensure that the following constraints are met in order for the validating light node to accept a block. + +Note that some conditions are marked as SHOULD, which is not strictly needed for security of the rollup, but need for the security of the individual applications or for UX. Some of the conditions are repetitions of what we saw earlier from the [state transitioner](#state-transitioner). + +- **Data Availability**: The block content MUST be available. To validate this, the `AvailabilityOracle` is used. +- **Header Validation**: See the checks from the [state transitioner](#state-transitioner) +- **Proof validation**: The proof MUST be validated with the header and archive. +- **Inserting messages**: for messages that are inserted into the inboxes: + - The `sender.actor` MUST be the caller + - The `(sender|recipient).chainId` MUST be the chainId of the L1 where the state transitioner is deployed + - The `(sender|recipient).version` MUST be the version of the state transitioner (the version of the L2 specified in the L1 contract) + - The `content` MUST fit within a field element + - For L1 to L2 messages: + - The `secretHash` MUST fit in a field element +- **Moving tree roots**: + - Moves MUST be atomic: + - Any message that is inserted into an outbox MUST be consumed from the matching inbox + - Any message that is consumed from an inbox MUST be inserted into the matching outbox +- **Consuming messages**: for messages that are consumed from the outboxes: + - L2 to L1 messages (on L1): + - The consumer (caller) MUST match the `recipient.actor` + - The consumer chainid MUST match the `recipient.chainId` + - The consumer SHOULD check the `sender` + - L1 to L2 messages (on L2): + - The consumer contract SHOULD check the `sender` details against the `portal` contract + - The consumer contract SHOULD check that the `secret` is known to the caller + - The consumer contract SHOULD check the `recipient` details against its own details + - The consumer contract SHOULD emit a nullifier to preventing double-spending + - The consumer contract SHOULD check that the message exists in the state + +:::info + +- For cost purposes, it can be useful to commit to the public inputs to just pass a single value into the circuit. +- Time constraints might change depending on the exact sequencer selection mechanism. + +::: + +## Logical Execution + +Below, we will outline the **LOGICAL** execution of a L2 block and how the contracts interact with the circuits. +We will be executing cross-chain communication before and after the block itself. +Note that the L2 inbox only exists conceptually and its functionality is handled by the kernel and the rollup circuits. + +```mermaid +sequenceDiagram + autonumber + title Logical Interactions of Crosschain Messages + + participant P2 as Portal (L2) + + participant I2 as Inbox (L2) + participant O2 as Outbox (L2) + participant R2 as Rollup (L2) + participant R as Validating Light Node (L1) + participant I as Inbox + participant O as Outbox + + participant P as Portal + + P->>I: Send msg to L2 + I->>I: Populate msg values + I->>I: Update state (insert) + + loop block in chain + + loop tx in txs + + loop msg in tx.l1ToL2Consume + P2->>O2: Consume msg + O2->>O2: Validate msg + O2->>O2: Update state (nullify) + end + + loop msg in tx.l2ToL1Msgs + P2->>I2: Add msg + I2->>I2: Populate msg values + I2->>I2: Update state (insert) + end + end + + loop msg in L2 inbox + R2->>I2: Consume msg + I2->>I2: Update state (delete) + end + + loop msg in l1ToL2Msgs + R2->>O2: Insert msg + O2->>O2: Update state (insert) + end + + R2->>R: Block (Proof + Data) + + R->>R: Verify proof + R->>R: Update State + + R->>I: Consume l1ToL2Msgs from L1 + I->>I: Update state (next tree) + + R->>O: Insert Messages from L2 + O->>O: Update state (insert root) + + end + + P->>O: Consume a msg + O->>O: Validate msg + O->>O: Update state (nullify) +``` + +We will walk briefly through the steps of the diagram above. +The numbering matches the numbering of nodes in the diagram, the start of the action. + +1. A portal contract on L1 wants to send a message for L2 +2. The L1 inbox populates the message with information of the `sender` (using `msg.sender` and `block.chainid`) +3. The L1 inbox contract inserts the message into its tree +4. On the L2, as part of a L2 block, a transaction consumes a message from the L2 outbox +5. The L2 outbox ensures that the message is included, and that the caller is the recipient and knows the secret to spend. (This is done by the application circuit) +6. The nullifier of the message is emitted to privately spend the message (This is done by the application circuit) +7. The L2 contract sends a message to L1 (specifying a recipient) +8. The L2 inbox populates the message with `sender` information +9. The L2 inbox inserts the message into its storage +10. The rollup circuit starts consuming the messages from the inbox +11. The L2 inbox deletes the messages from its storage +12. The L2 block includes messages from the L1 inbox that are to be inserted into the L2 outbox +13. The L2 outbox state is updated to include the messages +14. The L2 block is submitted to L1 +15. The state transitioner receives the block and verifies the proof + validates constraints on block +16. The state transitioner updates it's state to the ending state of the block +17. The state transitioner consumes the messages from the L1 inbox that was specified in the block. They have been inserted into the L2 outbox, ensuring atomicity. +18. The L1 inbox updates it local state by marking the message tree messages as consumed +19. The state transitioner inserts the messages tree root into the L1 Outbox. They have been consumed from the L2 inbox, ensuring atomicity. +20. The L1 outbox updates it local state by inserting the message root and height +21. The portal later consumes a message from the L1 outbox +22. The L1 outbox validates that the message exists and that the caller is indeed the recipient +23. The L1 outbox updates it local state by nullifying the message + +:::info L2 inbox is not real +The L2 inbox doesn't need to exist independently because it keeps no state between blocks. Every message created on L2 in a block will be consumed and added to the L1 outbox in the same block. +::: + +## Future work + +- Sequencer selection contract(s) + - Relies on the sequencer selection scheme being more explicitly defined + - Relies on being able to validate the sequencer selection scheme +- Governance/upgrade contract(s) + - Relies on the governance/upgrade scheme being more explicitly defined +- Forced transaction inclusion + - While we don't have an exact scheme, an outline was made in [hackmd](https://hackmd.io/@aztec-network/S1lRcMkvn?type=view) and the [forum](https://forum.aztec.network/t/forcing-transactions/606) diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/logs/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/logs/index.md new file mode 100644 index 000000000000..8d23a5f9d92f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/logs/index.md @@ -0,0 +1,202 @@ +--- +title: 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). + +## Requirements + +1. **Availability**: The logs get published. + + A rollup proof won't be accepted by the rollup contract if the log preimages are not available. Similarly, a sequencer cannot accept a transaction unless log preimages accompany the transaction data. + +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 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, 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 + +### Hash Function + +The protocol uses **SHA256** as the hash function for logs, and then reduces the 256-bit result to 248 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 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 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. + +#### 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 base rollup proof, encompassing all logs of the same type. + +Each protocol circuit outputs two values for each log type: + +- _`accumulated_logs_hash`_: A hash representing all logs. +- _`accumulated_logs_length`_: The total length of all log preimages. + +Both the `accumulated_logs_hash` and `accumulated_logs_length` for each type are included in the base rollup's `txs_effect_hash`. When rolling up to merge and root circuits, the two input proof's `txs_effect_hash`es are hashed together to form the new value of `txs_effect_hash`. + +When publishing a block on L1, the raw logs of each type and their lengths are provided (**Availability**), hashed and accumulated into each respective `accumulated_logs_hash` and `accumulated_logs_length`, then included in the on-chain recalculation of `txs_effect_hash`. If this value doesn't match the one from the rollup circuits, the block will not be valid (**Immutability**). + + + +For private and public kernel circuits, beyond aggregating logs from a function call, they ensure that the contract's address emitting the logs is linked to the _logs_hash_. For more details, refer to the "Hashing" sections in [Unencrypted Log](#hashing-1), [Encrypted Log](#hashing-2), and [Encrypted Note Preimage](#hashing-3). + +## Unencrypted Log + +Unencrypted logs are used to communicate public information out of smart contracts. They can be emitted from both public and private functions. + +:::info +Emitting unencrypted logs from private functions may pose a privacy leak. However, in-protocol restrictions are intentionally omitted to allow for potentially valuable use cases, such as custom encryption schemes utilizing Fully Homomorphic Encryption (FHE), and similar scenarios. +::: + +### Hashing + +Following the iterations for all private or public calls, the tail kernel circuits hash each log hash with the contract contract before computing the _accumulated_logs_hash_. + +1. Hash the _contract_address_ to each _log_hash_: + + - _`log_hash_a = hash(contract_address_a, log_hash_a)`_ + - Repeat the process for all _log_hashes_ in the transaction. + +2. Accumulate all the hashes and output the final hash to the public inputs: + + - `accumulated_logs_hash = hash(log_hash[0], log_hash[1], ..., log_hash[N - 1])` for N logs. + +### Encoding + +The following represents the encoded data for an unencrypted log: + +_`log_data = [log_preimage_length, contract_address, ...log_preimage]`_ + +### Verification + +```js +function hash_log_data(logs_data) { + const log_preimage_length = logs_data.read_u32(); + logs_data.accumulated_logs_length += log_preimage_length; + const contract_address = logs_data.read_field(); + const log_preimage = logs_data.read_fields(log_preimage_length); + const log_hash = hash(...log_preimage); + return hash(log_hash, contract_address); +} +``` + +## Encrypted Log + +Encrypted logs contain information encrypted using the recipient's key. They can only be emitted from private functions. This restriction is due to the necessity of obtaining a secret for log encryption, which is challenging to manage privately in a public domain. + +### Hashing + +Private kernel circuits ensure the association of the contract address with each encrypted _log_hash_. However, unlike unencrypted logs, submitting encrypted log preimages with their contract address poses a significant privacy risk. Therefore, instead of using the _contract_address_, a _masked_contract_address_ is generated for each encrypted _log_hash_. + +The _masked_contract_address_ is a hash of the _contract_address_ and a random value _randomness_, computed as: + +_`masked_contract_address = hash(contract_address, randomness)`_. + +Here, _randomness_ is generated in the private function circuit and supplied to the private kernel circuit. The value must be included in the preimage for encrypted log generation. The private function circuit is responsible for ensuring that the _randomness_ differs for every encrypted log to avoid potential information linkage based on identical _masked_contract_address_. + +After successfully decrypting an encrypted log, one can use the _randomness_ in the log preimage, hash it with the _contract_address_, and verify it against the _masked_contract_address_ to ascertain that the log originated from the specified contract. + +1. Hash the _contract_address_tag_ to each _log_hash_: + + - _`masked_contract_address_a = hash(contract_address_a, randomness)`_ + - _`log_hash_a = hash(contract_address_tag_a, log_hash_a)`_ + - Repeat the process for all _log_hashes_ in the transaction. + +2. Accumulate all the hashes in the tail and outputs the final hash to the public inputs: + + - `accumulated_logs_hash = hash(log_hash[0], log_hash[1], ..., log_hash[N - 1])` for N logs, with hashes defined above. + +Note that, in some cases, the user may want to reveal which contract address the encrypted log came from. Providing a `randomness` value of 0 signals that we should not mask the address, so in this case the log hash is simply: + +_`log_hash_a = hash(contract_address_a, log_hash_a)`_ + +### Encoding + +The following represents the encoded data for an encrypted log: + +_`log_data = [log_preimage_length, masked_contract_address, ...log_preimage]`_ + +### Verification + +```js +function hash_log_data(logs_data) { + const log_preimage_length = logs_data.read_u32(); + logs_data.accumulated_logs_length += log_preimage_length; + const contract_address_tag = logs_data.read_field(); + const log_preimage = logs_data.read_fields(log_preimage_length); + const log_hash = hash(...log_preimage); + return hash(log_hash, contract_address_tag); +} +``` + +## Encrypted Note Preimage + +Similar to [encrypted logs](#encrypted-log), encrypted note preimages are data that only entities possessing the keys can decrypt to view the plaintext. Unlike encrypted logs, each encrypted note preimage can be linked to a note, whose note hash can be found in the block data. + +> Note that a note can be "shared" to one or more recipients by emitting one or more encrypted note preimages. However, this is not mandatory, and there may be no encrypted preimages emitted for a note if the information can be obtain through alternative means. + +### Hashing + +As each encrypted note preimage can be associated with a note in the same transaction, enforcing a _contract_address_tag_ is unnecessary. Instead, by calculating the _note_hash_ using the decrypted note preimage, hashed with the _contract_address_, and verify it against the block data, the recipient can confirm that the note was emitted from the specified contract. + +The kernel circuit simply accumulates all the hashes: + +- `accumulated_logs_hash = hash(log_hash[0], log_hash[1], ..., log_hash[N - 1])` for N logs. + +### Encoding + +The following represents the encoded data for an unencrypted note preimage: + +_`log_data = [log_preimage_length, ...log_preimage]`_ + +### Verification + +```js +function hash_log_data(logs_data) { + const log_preimage_length = logs_data.read_u32(); + logs_data.accumulated_logs_length += log_preimage_length; + const log_preimage = logs_data.read_fields(log_preimage_length); + return hash(...log_preimage); +} +``` + +## Log Encryption + +Refer to [Private Message Delivery](../private-message-delivery/index.md) for detailed information on generating encrypted data. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/pre-compiled-contracts/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/pre-compiled-contracts/index.md new file mode 100644 index 000000000000..630273e4b443 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/pre-compiled-contracts/index.md @@ -0,0 +1,5 @@ +# Pre-Compiled Contracts + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/pre-compiled-contracts/registry.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/pre-compiled-contracts/registry.md new file mode 100644 index 000000000000..2516a97389f9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/pre-compiled-contracts/registry.md @@ -0,0 +1,92 @@ +# Registry + + + +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 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 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 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. + +## Pseudocode + +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. + + + + + + + + + +```rust +contract Registry + + public mapping(address => { keys, precompile_address }) registry + + public fn set(keys, precompile_address) + this.do_set(msg_sender, keys, precompile_address) + + 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) + assert address not in registry + + internal public fn do_set(address, keys, precompile_address) + 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 } +``` + +## Storage Optimizations + +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. + +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). + +## Multiple Recipients per Address + +While account contracts that belong to individual users have a clear set of public keys to announce, some private contracts may be shared by a group of users, like in a multisig or an escrow contract. In these scenarios, we want all messages intended for the shared contract to actually be delivered to all participants, using the encryption method selected by each. + +This can be achieved by having the registry support multiple sets of keys and precompiles for each entry. Applications can then query the registry and obtain a list of recipients, rather than a single one. + +The registry limits multi-recipient registrations to no more than `MAX_ENTRIES_PER_ADDRESS` to prevent abuse, since this puts an additional burden on the sender, who needs to emit the same note multiple times, increasing the cost of their transaction. + +Contracts that intend to register multiple recipients should account for those recipients eventually rotating their keys. To support this, contracts should include a method to refresh the registered addresses: + +```rust +contract Sample + + private address[] owners + + private fn register() + let to_register = owners.map(owner => read_registry(owner)) + registry.set(this, to_register) +``` + + + + + +## Discussion + +See [_Addresses, keys, and sending notes (Dec 2023 edition)_](https://forum.aztec.network/t/addresses-keys-and-sending-notes-dec-2023-edition/2633) for relevant discussions on this topic. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/private-message-delivery/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/private-message-delivery/index.md new file mode 100644 index 000000000000..679d3e9abb5e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/private-message-delivery/index.md @@ -0,0 +1,11 @@ +--- +title: Private Message Delivery +--- + +# Private Message Delivery + +Private message delivery encompasses the encryption, tagging, and broadcasting of private messages on the Aztec Network. + +import DocCardList from '@theme/DocCardList'; + + diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/private-message-delivery/private-msg-delivery.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/private-message-delivery/private-msg-delivery.md new file mode 100644 index 000000000000..e1738d1261e1 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/private-message-delivery/private-msg-delivery.md @@ -0,0 +1,71 @@ +# Private Message Delivery + +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 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](./private-msg-delivery.md#note-discovery-protocol-selection) 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 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 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 and tag to the correct data availability layer. + +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](../pre-compiled-contracts/registry.md). + +The advantages of this approach are: + +1. It enables a user to select their preferred [note discovery protocol](./private-msg-delivery.md#note-discovery-protocol-selection) and [encryption scheme](./private-msg-delivery.md#encryption-and-decryption). +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. +4. It provides scope for upgrading these functions or introducing new schemes as the field progresses. +5. It protects applications from malicious unprovable functions. + +## Note Discovery Protocol Selection + +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. + +Selection of the encryption and tagging mechanisms to use for a particular note are the responsibilty of a user's wallet rather than the application generating the note. This is to ensure the note is produced in a way compatible with the user's chosen note discovery scheme. Leaving this decision to applications could result in user's having to utilise multiple note discovery schemes, a situation we want to avoid. + +## User Handshaking + +Even if Alice correctly encrypts the note she creates for Bob and generates the correct tag to go with it, how does Bob know that Alice has sent him a note? Bob's note discovery protocol may require him to speculatively 'look' for notes with the tags that Alice (and his other counterparties) have generated. If Alice and Bob know each other then they can communicate out-of-protocol. But if they have no way of interacting then the network needs to provide a mechanism by which Bob can be alerted to the need to start searching for a specific sequence of tags. + +To facilitate this we will deploy a canonical 'handshake' contract that can be used to create a private note for a recipient containing the sender's information (e.g. public key). It should only be necessary for a single handshake to take place between two users. The notes generated by this contract will be easy to identify, enabling users to retrieve these notes, decrypt them and use the contents in any deterministic tag generation used by their chosen note discovery protocol. + +## Encryption and Decryption + +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. +Internal incoming data: data created by the user, encrypted for and sent to the user. +Encryption mechanisms support these three types of encryption, which may rely on different keys advertised by the user. + +### Key Abstraction +To support different kinds of encryption mechanisms, the protocol does not make any assumptions on the type of public keys advertised by each user. Validation of their public keys is handled by the precompile contract selected by the user. + +### Provable Decryption +While provable encryption is required to guarantee correct private message delivery, provable decryption is required for disclosing activity within an application. This allows auditability and compliance use cases, as well as being able to prove that a user did not execute certain actions. To support this, encryption precompiles also allow for provable decryption. + +## Note Tagging + +Note discovery schemes typically require notes to be accompanied by a stream of bytes generated specifically for the note discovery protocol. This 'tag' is then used in the procss of note identification. Whilst the tag itself is not sufficient to enable efficient note retrieval, the addition of it alongside the note enables the note discovery protocol to privately select a subset of the global set of notes to be returned to the user. This subset may still require some degree of trial-decryption but this is much more feasible given the reduced dataset. + +When applications produce notes, they will need to call a protocol defined contract chosen by the recipient and request that a tag be generated. From the protocol's perspective, this tag will simply be a stream of bytes relevant only to the recipient's note discovery protocol. It will be up to the precompile to constrain that the correct tag has been generated and from there the protocol circuits along with the rollup contract will ensure that the tag is correctly published along with the note. + +Constraining tag generation is not solely about ensuring that the generated tag is of the correct format. It is also necessary to constrain that tags are generated in the correct sequence. A tag sequence with duplicate or missing tags makes it much more difficult for the recipient to retrieve their notes. This will likely require tags to be nullified once used. \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/private-message-delivery/send-note-guidelines.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/private-message-delivery/send-note-guidelines.md new file mode 100644 index 000000000000..aa0b0a67edad --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/private-message-delivery/send-note-guidelines.md @@ -0,0 +1,63 @@ +# Guidelines + +Application contracts are in control of creating, encrypting, tagging, and broadcasting private notes to users. As such, each application is free to follow whatever scheme it prefers, choosing to override user preferences or use custom encryption and note tagging mechanisms. However, this may hinder composability, or not be compatible with existing wallet software. + +In order to satisfy the requirements established for private message delivery, we suggest the following guidelines when building applications, which leverage the canonical [registry](../pre-compiled-contracts/registry.md) contract. + +## 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 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. + +If the user is not in the registry and the sender cannot provide the address preimage, then the application must prove that the user was not in the registry, or a malicious sender could simply not submit a correct merkle membership proof for the read and grief the recipient. In this scenario, it is strongly recommended that the application skips the note for the recipient as opposed to failing. This prevents an unregistered address from accidentally or maliciously bricking an application, if there is a note delivery to them in a critical code path in the application. + +Execution of the precompile that implements the recipient's choice for encryption and tagging should be done using a batched delegated call, to amortize the cost of sending multiple notes using the same method, and to ensure the notes are broadcasted from the application contract's address. + +### 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](../pre-compiled-contracts/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) + + let block_number = context.latest_block_number + let public_state_root = context.roots[block_number].public_state + let storage_slot = calculate_slot(registry_address, registry_base_slot, recipient) + + let public_keys, precompile_address + if storage_slot in public_state_root + context.update_tx_max_valid_block_number(block_number + N) + public_keys, precompile_address = indexed_merkle_read(public_state_root, storage_slot) + else if recipient in pxe_oracle + address_preimage = pxe_oracle.get_preimage(recipient) + assert hash(address_preimage) == recipient + public_keys, precompile_address = address_preimage + else + registry_address.assert_non_membership(recipient) + return + + batch_private_delegate_call(precompile_address.encrypt_and_broadcast, { public_keys, encryption_type, recipient, note }) +``` + +## Unconstrained Message Delivery + +Applications may choose not to constrain proper message delivery, based on their requirements. In this case, the guidelines are the same as above, but without constraining correct execution, and without the need to assert non-membership when the recipient is not in the registry. Apps can achieve this by issuing a synchronous [unconstrained call](../calls//unconstrained-calls.md) to the encryption precompile `encrypt_and_tag` function, and emitting the resulting encrypted note. + +This flexibility is useful in scenarios where the sender can be trusted to make its best effort so the recipient receives their private messages, since it reduces total proving time. An example is a standalone direct value transfer, where the sender wants the recipient to access the funds sent to them. + +## Delivering Messages for Self + +Applications may encrypt, tag, and broadcast messages for the same user who's initiating a transaction, using the outgoing or the incoming internal encryption key. This allows a user to have an on-chain backup of their private transaction history, which they can use to recover state in case they lose their private database. In this scenario, unconstrained message delivery is recommended, since the sender is incentivized to correctly encrypt message for themselves. + +Applications may also choose to query the user wallet software via an oracle call, so the wallet can decide whether to broadcast the note to self on chain based on user preferences. This allows users to save on gas costs by avoiding unnecessary note broadcasts if they rely on other backup strategies. + +Last, applications with strong compliance and auditability requirements may choose to enforce provable encryption, tagging, and delivery to the sender user. This ensures that all user activity within the application is stored on-chain, so the user can later provably disclose their activity or repudiate actions they did not take. + +## Delivering Messages to Multiple Recipients via Shared Secrets + +As an alternative to registering [multiple recipients for a given address](../pre-compiled-contracts/registry.md#multiple-recipients-per-address), multisig participants may deploy a contract using a shared secret derived among them. This makes it cheaper to broadcast messages to the group, since every note does not need to be individually encrypted for each of them. However, it forces all recipients in the group to use the same encryption and tagging method, and adds an extra address they need to monitor for note discovery. + +## Discussions + +See [_Addresses, keys, and sending notes (Dec 2023 edition)_](https://forum.aztec.network/t/addresses-keys-and-sending-notes-dec-2023-edition/2633) and [_Broadcasting notes in token contracts_](https://forum.aztec.network/t/broadcasting-notes-in-token-contracts/2658) for relevant discussions on this topic. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/_nested-context.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/_nested-context.md new file mode 100644 index 000000000000..ca3193d61bf6 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/_nested-context.md @@ -0,0 +1,48 @@ +The nested call's execution context is derived from the caller's context and the call instruction's arguments. + +The following shorthand syntax is used to refer to nested context derivation in the ["Instruction Set"](./instruction-set) and other sections: + +```jsx +// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } + +isStaticCall = instr.opcode == STATICCALL + +nestedContext = deriveContext(context, instr.args, isStaticCall) +``` + +Nested context derivation is defined as follows: +```jsx +nestedExecutionEnvironment = ExecutionEnvironment { + address: M[addrOffset], + sender: context.address, + functionSelector: context.environment.functionSelector, + transactionFee: context.environment.transactionFee, + contractCallDepth: context.contractCallDepth + 1, + contractCallPointer: context.worldStateAccessTrace.contractCalls.length + 1, + globals: context.globals, + isStaticCall: isStaticCall, + calldata: context.memory[M[argsOffset]:M[argsOffset]+argsSize], +} + +nestedMachineState = MachineState { + l2GasLeft: context.machineState.memory[M[gasOffset]], + daGasLeft: context.machineState.memory[M[gasOffset+1]], + pc = 0, + internalCallStack = [], // initialized as empty + memory = [0, ..., 0], // all 2^32 entries are initialized to zero +} +``` + + +```jsx +nestedContext = AvmContext { + environment: nestedExecutionEnvironment, + machineState: nestedMachineState, + worldState: context.worldState, + worldStateAccessTrace: context.worldStateAccessTrace, + accruedSubstate: { [], ... [], }, // all empty + results: {reverted: false, output: []}, +} +``` + +> `M[offset]` notation is shorthand for `context.machineState.memory[offset]` diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/alu.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/alu.md new file mode 100644 index 000000000000..70a902406adc --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/alu.md @@ -0,0 +1,35 @@ +# Algebraic Logic Unit + +The algebraic logic unit performs operations analogous to an arithmetic unit in a CPU. + +This component of the VM circuit evaluates both base-2 arithmetic operations and prime-field operation. It takes its input/output from the intermediate registers in the state controller. + +The following block diagram maps out a draft of the internal components of the "ALU" + +![](/img/protocol-specs/public-vm/alu.png) + +Notes: + +For logic operations (e.g. AND/OR) we use lookup tables. The max. size of each lookup table cannot grow too large as the Prover pays a constant cost linear with the size of the lookup table. + +To this end we use lookup tables for logic operations that take _8-bit input operands_ for a total table size of 2^16. + +i.e. the table contains the output for every possible 8-bit combination of 2 input operands. + +#### Slice registers + +We need to slice our inputs into 8-bit chunks for logic operations, in order to index the lookup tables. + +As a simplification, we can say that _any_ operation that requires range-constraints will split the input operands into 8-bit slices, as we can then apply consistent range-checking logic. + +#### Carry flag + +Used to test for overflows. If we want the high-level instruction set to have "add with carry" we need to expose the carry flag to the state controller. + +## Example operation: 32-bit ADD(a, b, c) + +Assume we start with `a` in intermediate register `R1`, `b` in intermediate register `R2`, and `c` in intermediate register `R3` + +1. Store the first 32 bits of `a + b` in slice registers `s1, s2, s3, s4`, with the carry bit in `carry` +2. Validate $a + b = s_1 + 2^8s_2 + 2^{16}s_3 + 2^{24}s_4 + 2^{32}\text{carry}$ +3. Validate $c = s_1 + 2^8s_2 + 2^{16}s_3 + 2^{24}s_4$ diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/avm-circuit.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/avm-circuit.md new file mode 100644 index 000000000000..3b8cd10f4bd5 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/avm-circuit.md @@ -0,0 +1,253 @@ +# AVM Circuit + +The AVM circuit's purpose is to prove execution of a sequence of instructions for a public execution request. Regardless of whether execution succeeds or reverts, the circuit always generates a valid proof of execution. + +## Introduction + +### Circuit Architecture Outline + +The circuit is comprised of the following components: + +- **Bytecode Table**: includes bytecode for all calls, indexed by call pointer and program counter. +- **Instruction Controller**: fetches an instruction from the Bytecode Table. Decodes the instructions into sub-operations to be forwarded to other modules. +- **Intermediate Registers**: for staging sub-operation inputs and outputs. +- **Control Flow Unit**: maintains program counter and call pointer. Processes control-flow sub-operations (program counter increments, internal call stack operations, contract call operations). +- **Gas Controller**: tracks remaining gas for current call. Processes gas tracking sub-operations. +- **Memory Controller**: processes memory sub-operations to load and store data between memory and intermediate registers. +- **Storage Controller**: processes storage sub-operations to load and store data between storage and intermediate registers. +- **Side-effect Accumulator**: processes side-effect sub-operations by pushing to a side-effect vector. +- **Chiplets**: perform compute operations on intermediate registers. Some chiplets include the ALU, Conditional Unit, Type Converter, and Crypto/Hash Gadgets. +- **Circuit I/O**: data structures used to ingest circuit inputs and emit outputs. + +## Bytecode Table + +To review, the AVM circuit's primary purpose is to prove execution of the proper sequence of instructions given a contract call's bytecode and inputs. The circuit will prove correct execution of any nested contract calls as well. Each nested call will have its own bytecode and inputs, but will be processed within the same circuit. + +Prior to the VM circuit's execution, a vector is assembled to contain the bytecode for all of a request's contract calls (initial and nested). If a request's execution contains contract calls to contracts A, B, C, and D (in that order), the VM circuit's bytecode vector will contain A's bytecode, followed by B's, C's, and finally D's. Each one will be zero-padded to some constant length `MAX_PUBLIC_INSTRUCTIONS_PER_CONTRACT`. + +Each entry in the bytecode vector will be paired with a call pointer and program counter. This **Bytecode Table** maps a call pointer and program counter to an instruction, and is used by the Instruction Controller to fetch instructions. + +> Note: "call pointer" is expanded on in a later section. + +Each contract's public bytecode is committed to during contract deployment. As part of the AVM circuit verification algorithm, the bytecode vector (as a concatenation of all relevant contract bytecodes) is verified against the corresponding bytecode commitments. This is expanded on in ["Bytecode Validation Circuit"](./bytecode-validation-circuit). While the AVM circuit enforces that the correct instructions are executed according to its bytecode table, the verifier checks that bytecode table against the previously validated bytecode commitments. + +## Instruction Controller + +The Instruction Controller's responsibilities include instruction fetching and decoding. + +### Instruction fetching + +The Instruction Controller's **instruction fetch** mechanism makes use of the bytecode table to determine which instruction to execute based on the call pointer and program counter. Each instruction fetch corresponds to a circuit lookup to enforce that the correct instruction is processed for a given contract and program counter. + +The combination of the instruction fetch circuitry, the bytecode table, and the ["Bytecode Validation Circuit"](./bytecode-validation-circuit) ensure that VM circuit processes the proper sequence of instructions. + +### Instruction decoding and sub-operations + +An instruction (its opcode, flags, and arguments) represents some high-level VM operation. For example, an `ADD` instruction says "add two items from memory and store the result in memory". The Instruction Controller **instruction decode** mechanism decodes instructions into sub-operations. While an instruction likely requires many circuit components, a **sub-operation** is a smaller task that can be fed to just one VM circuit component for processing. By decoding an instruction into sub-operations, the VM circuit translates high-level instructions into smaller achievable tasks. To continue with the `ADD` example, it would translate "add two items from memory and store the result in memory" to "load an item from memory, load another item from memory, add them, and store the result to memory." + +A **pre-computed/hardcoded sub-operations table** maps instruction opcodes to sub-operations. This provides the Instruction Controller with everything it needs to decode an instruction. + +The Instruction Controller forwards sub-operations according to the following categorizations: + +- Control flow sub-operations are forwarded to the Control Flow Unit +- Gas tracking sub-operations are forwarded to the Gas Controller +- Memory sub-operations are forwarded to the Memory Controller +- Storage sub-operations are forwarded to the Storage Controller +- Side-effect sub-operations are forwarded to the Side-effect Controller +- A chiplet sub-operation is forwarded to the proper chiplet + +**TODO: table of all sub-operations by category (copy from hackmd with updates)** + +> Note: for simple instructions (like `ADD`), the instruction can be fetched and all sub-operations can be processed in a single clock cycle. Since the VM circuit has limited resources, some complex instructions (like `CALLDATACOPY`) involve too many sub-operations to be processed in one clock cycle. A "clock cycle" in the AVM circuit represents the smallest subdivision of time during which some parallel operations can be performed. A clock cycle corresponds to a row in the circuit's **operations trace**. Simple instructions correspond to only a single row in this trace, but complex instructions span multiple rows. A `CLK` column tracks the clock cycle for each row and its set of sub-operations. + +#### Decoding example + +The `ADD` instruction is decoded into two `LOAD` memory sub-operations, an `ADD` ALU (chiplet) sub-operation, and a `STORE` memory sub-operation. + +Take the following `ADD` instruction as an example: `ADD aOffset bOffset dstOffset`. Assuming this instruction is executed as part of contract call with pointer `C`, it is decoded into the following sub-operations: + +``` +// Load word from call's memory into register Ia (index 0) +LOAD 0 aOffset // Ia = M[aOffset] +// Load word from call's memory into register Ib (index 1) +LOAD 1 bOffset // Ib = M[aOffset] +// Use the ALU chiplet in ADD<32> mode to add registers Ia and Ib +// Place the results in Ic +ADD // Ic = ALU_ADD(Ia, Ib) +// Store results of addition from register Ic (index 2) to memory +STORE 2 dstOffset +``` + +> Note: the `ADD` instruction is an example of a "simple" instruction that can be fully processed in a single clock cycle. All four of the above-listed sub-operations happen in one clock cycle and therefore take up only a single row in the circuit's operations trace. + +## Intermediate Registers + +User code (AVM bytecode) has no concept of "registers", and so instructions often operate directly on user memory. Sub-operations on the other hand operate on intermediate registers. The only circuit component that has direct access to memory is the Memory Controller (further explained later), and therefore only memory sub-operations access memory. All other sub-operations operate on **intermediate registers** which serve as a staging ground between memory and the various processing components of the VM circuit. + +Three intermediate registers exist: $I_a$, $I_b$, and $I_c$. + +> Refer to ["AVM State Model"](./memory-model) for more details on the absence of "external registers" in the AVM. + +## Control Flow Unit + +Processes updates to the program counter and call pointer to ensure that execution proceeds properly from one instruction to the next. + +### Program Counter + +The Control Flow Unit's **program counter** is an index into the bytecode of the current call's contract. For most instructions, the Control Flow Unit will simply increment the program counter. Certain instructions (like `JUMP`) decode into control flow sub-operations (like `PCSTORE`). The Control Flow Unit processes such instructions to update the program counter. + +### Call Pointer + +A **contract call pointer** uniquely identifies a contract call among all contract calls processed by the current circuit. The Control Flow Unit tracks the currently active call pointer and the next available one. When a nested contract call is encountered, it assigns it the next available call pointer (`callPointer = nextCallPointer++`) and increments that next pointer value. It then sets the program counter to 0. + +A request's initial contract call is assigned call pointer of `1`. The first nested contract call encountered during execution is assigned call pointer of `2`. The Control Flow Unit assigns call pointers based on execution order. + +There is certain information that must be tracked by the VM circuit on a per-call basis. For example, each call will correspond to the execution of a different contract's bytecode, and each call will access call-specific memory. As a per-call unique identifier, the contract call pointer enables bytecode and memory lookups, among other things, on a per-call basis. + +#### "Input" and "output" call pointers + +It is important to note that the initial contract call's pointer is `1`, not `0`. The zero call pointer is a special case known as the "input" call pointer. + +As expanded on later, the VM circuit memory table has a separate section for each call pointer. The memory table section for the **input call pointer** is reserved for the initial call's `calldata`. This will be expanded on later. + +### Internal Call Stack + +**TODO** + +### Nested contract calls + +**TODO** + +#### Initializing nested call context + +**TODO** + +#### Snapshotting and restoring context + +**TODO** + +## Memory Controller + +The VM circuit's **Memory Controller** processes loads and stores between intermediate registers and memory. + +### Memory Sub-operations + +When decoded, instructions that operate on memory map to some Memory Controller sub-operations. A memory read maps to a `LOAD` sub-operation which loads a word from memory into an intermediate register. The memory offset for this sub-operation is generally specified by an instruction argument. Similarly, a memory write maps to a `STORE` sub-operation which stores a word from an intermediate register to memory. + +### User Memory + +This table tracks all memory `Read` or `Write` operations. As introduced in the ["Memory State Model"](./memory-model.md), a memory cell is indexed by a 32-bit unsigned integer (`u32`), i.e., the memory capacity is of $2^{32}$ words. Each word is associated with a tag defining its type (`uninitialized`, `u8`, `u16`, `u32`, `u64`, `u128`, `field`). At the beginning of a new call, each memory cell is of type `uninitialized` and has value 0. + +The main property enforcement of this table concerns read/write consistency of every memory cell. This must ensure: + +- Each initial read on a memory cell must have value 0 (`uninitialized` is compatible with any other type). +- Each read on a memory cell must have the same value and the same tag as those set by the last write on this memory cell. + +In addition, this table ensures that the instruction tag corresponding to a memory operation is the same as the memory cell tag. The instruction tag is passed to the memory controller and added to the pertaining row(s) of this table. Note that this is common for an instruction to generate several memory operations and thus several rows in this table. + +The user memory table essentially consists of the following colums: + +- `CALL_PTR`: call pointer uniquely identifying the contract call +- `CLK`: clock value of the memory operation +- `ADDR`: address (type `u32`) pertaining to the memory operation +- `VAL`: value which is read (resp. written) from (resp. to) the memory address +- `TAG`: tag associated to this memory address +- `IN_TAG`: tag of the pertaining instruction +- `RW`: boolean indicating whether memory operation is read or write +- `TAG_ERR`: boolean set to true if there is a mismatch between `TAG` and `IN_TAG` + +To facilitate consistency check, the rows are sorted by `CALL_PTR` then by `ADDR` and then by `CLK` in ascending (arrow of time) order. Any (non-initial) read operation row is constrained to have the same `VAL` and `TAG` than the previous row. A write operation does not need to be constrained. + +The tag consistency check can be performed within every row (order of rows does not matter). + +Note that `CLK` also plays the role of a foreign key to point to the corresponding sub-operation. This is crucial to enforce consistency of copied values between the sub-operations and memory table. + +### Calldata + +**TODO** + +#### Initial call's calldata + +Any lookup into calldata from a request's initial contract call must retrieve a value matching the `calldata` public inputs column. To enforce this, an equivalence check is applied between the `calldata` column and the memory trace for user memory accesses that use "input call pointer". + +## Storage Controller + +**TODO** + +## Side-effect Accumulator + +**TODO** + +## Chiplets + +A chiplet is essentially a sub-circuit for performing specialized sub-operations. A chiplet is defined as a dedicated table (a set of columns and relations) in the AVM circuit that is activated when the relevant sub-operation is used. The main rationale behind the use of chiplets is to offload specialized computations to a region of the circuit _outside the main operations trace and instruction controller_ where the computations might require many rows and/or additional dedicated columns. In addition, this approach offers strong modularity for the operations implemented as chiplets. + +The interaction between a chiplet and the instruction controller follows the following pattern: + +1. The **inputs** of a chiplet sub-operation are loaded to the respective intermediate registers (usually $I_a$, $I_b$). +2. The dedicated chiplet fetches/copies the content of the intermediate registers from the **operations trace** in its own table and executes the operation. +3. The output of the operation is copied back to the **operations trace** in a specific register (usually $I_c$). This register is usually involved in a write memory sub-operation. + +In addition to the mentioned inputs and output, some other relevant information such as the instruction tag might be copied as well to the chiplet. + +In the circuit, the transmission of the input/output between the **chiplet trace** and the **operations trace** are performed through lookup or permutation constraints, i.e., they ensure that all relevant intermediate registers have the same values between both traces. The unique key of this mapping is `CLK` which is basically used as a "DB foreign key" from the **chiplet trace** pointing to corresponding entry in the **operations trace**. + +The planned chiplets for the AVM are: + +- **ALU**: Arithmetic and bitwise operations such as addition, multiplication, XOR, etc... +- **Type Converter**: Dedicated to casting words between different types and/or type constraints. +- **Gadgets:** Relevant cryptographic operations or other computationally intensive operations. There will likely be multiple chiplets of this category, including `Poseidon2Permutation`, `Keccakf1600`, and `ECADD`. + +## Circuit I/O + +### How do "Public Inputs" work in the AVM circuit? + +ZK circuit proof systems generally define some mechanism for "public inputs" for which witness values must be communicated in full to a verifier. The AVM proof system defines its own mechanism for public inputs in which it flags certain trace columns as "public input columns". Any public input columns must be communicated in full to a verifier. + +### AVM public inputs structure + +The VM circuit's I/O (`AvmPublicInputs`) is defined below: + +``` +AvmSessionInputs { + // Initializes Execution Environment + feePerL2Gas: field, + feePerDaGas: field, + globals: PublicGlobalVariables, + address: AztecAddress, + sender: AztecAddress, + contractCallDepth: field, + isStaticCall: boolean, + transactionFee: field, + // Initializes Machine State + l2GasLeft: field, + daGasLeft: field, +} +AvmSessionResults { + l2GasLeft: field, + daGasLeft: field, + reverted: boolean, +} +AvmSessionPublicInputs { + sessionInputs: AvmSessionInputs, + calldata: [field; MAX_CALLDATA_LENGTH], + worldStateAccessTrace: WorldStateAccessTrace, + accruedSubstate: AccruedSubstate, + sessionResults: AvmSessionResults, +} +``` + +> The `WorldStateAccessTrace` and `AccruedSubstate` types are defined in ["State"](./state). Their vectors are assigned constant/maximum lengths when used as circuit inputs. + +### AVM public input columns + +The `AvmPublicInputs` structure is represented in the VM trace via the following public input columns: + +1. `sessionInputs` has a dedicated column and is used to initialize the initial call's `AvmContext.ExecutionEnvironment` and `AvmContext.MachineState`. +1. `calldata` occupies its own public input column as it is handled differently from the rest of the `ExecutionEnvironment`. It is used to initialize the initial call's `AvmContext.ExecutionEnvironment.calldata`. + - Equivalence is enforced between this `calldata` column and the "input call pointer"'s memory. Through this mechanism, the initial call's `calldata` is placed in a region memory that can be referenced via the `CALLDATACOPY` instruction from within the initial call. +1. `worldStateAccessTrace` is a trace of all world state accesses. Each of its component vectors has a dedicated set of public input columns (a sub-table). An instruction that reads or writes world state must match a trace entry. The [trace type definition in the "State" section] lists, for each trace vector, the instruction that populate its entries. +1. `accruedSubstate` contains the final `AccruedSubstate`. + - This includes the accrued substate of all _unreverted_ sub-contexts. + - Reverted substate is not present in the Circuit I/O as it does not require further validation/processing by downstream circuits. +1. `sessionResults` has a dedicated column and represents the core "results" of the AVM session processed by this circuit (remaining gas, reverted). diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/bytecode-validation-circuit.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/bytecode-validation-circuit.md new file mode 100644 index 000000000000..ccdd88cadc5b --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/bytecode-validation-circuit.md @@ -0,0 +1,190 @@ +# Bytecode Validation Circuit + +Goal: Validate that a polynomial commitment to AVM program opcodes maps to the bytecode representation of an AVM program of maximum size $n$. + +# Definitions - Curves and Fields + +The bytecode validation circuit is implemented over the BN254 elliptic curve with group elements defined via $\mathbb{G}_{bn254}$. + +The field $\mathbb{F}$ represents the finite field whose characteristic equals the number of points on the BN254 curve. + +# Bytecode representation + +Each opcode in the AVM can be described by an integer in the range $[0, \ldots, 256^{31}]$ (i.e. 31 bytes of data). All opcodes excluding `SET` require much less data than 31 bytes. + +In the AVM circuit architecture, multiple columns are used to define a VM operation. These columns describe the following quantities: + +1. the opcode to be executed (1 byte of data) +2. three parameter columns that define either literal values or memory indexes +3. three "flag" columns that define metadata associated with each parameter (e.g. whether the parameter a should be interpreted as a literal value or a memory index, or an indirect memory index) + +To both minimize the amount of information used to _define_ a given AVM program, the AVM posesses an additional column that describes the _packed_ opcode. i.e. the integer concatenation of all 6 of the above column values. We define this column via the vector of field elements $\mathbf{op} \in \mathbb{F}^n$ (where $n$ is the number of opcodes in the program). $\mathbf{op}$ is defined as the _column representation_ of an AVM program. + +## Packed bytecode representation + +When _broadcasting_ the data for AVM programs, we desire an encoding that minimizes the raw number of bytes broadcast, we call this the _packed representation_ of the program. + +The number of bytes required to represent an element in $\mathbf{op}$ in the AVM can be derived from the value of the 1st byte (e.g `ADD` requires 7 bytes of data - the ADD opcode (1 byte) and three memory indices (each of size 2 bytes)). + +See (ref: TODO!) for a table that describes the amount of data required for each opcode. + +Each field element in a BN254 circuit can represent _31_ bytes of bytecode data. The packed representation of an AVM program $\mathbf{b} \in \mathbb{F}^n$ is defined as the concatenation of $\mathbf{op}$ into 31-byte chunks, represented as field elements. + +There exists a mapping function $g$ that, given the packed representation $\mathbf{b}$, will produce the column representation $\mathbf{op}$. + +$$ +g(\mathbf{b}) = \mathbf{op} +$$ + +A full description of $g$ is provided [further down in this document](#Definition-of-mapping-function-g). + +## Committed representation + +The committed representation of an AVM program is an elliptic curve polynomial commitment $[P] \in \mathbb{G}_{bn254}$, created via the KZG polynomial commitment scheme (ref). + +$[P]$ is a commitment to $P(X) \in \mathbb{F}[X]^n$ where $P(X) = \sum_{i=0}^{n-1} op_i X^i$ + +# Bytecode validation logic + +Given inputs $\mathbf{b} \in \mathbb{F}^n$ and $[P] \in \mathbb{G}_{bn254}$, we must validate that $[P] = \text{commit}_{KZG}(g(\mathbf{b}))$. + +This requires the following _high level_ steps: + +1. For all $i \in [0, \ldots, n - 1]$, validate that $b_i < 256^{31} - 1$ +2. Compute $\mathbf{op} = g(\mathbf{b})$ +3. Perform a _polynomial consistency check_ between $\mathbb{op}$ and $[P]$ + +# Polynomial Consistency Check + +> The most straightforward way of validating $\mathbb{op}, [P]$ would be to directly construct $[P]$ from $\mathbb{op}$. +> We do not do this, as this would require a large multiscalar multiplication over the BN254 curve. This could only be performed efficiently over a Grumpkin SNARK circuit, which would add downstream complexity to the Aztec architecture (currently the only Grumpkin proofs being accumulated are elliptic-curve-virtual-machine circuits). The rollup circuit architecture already supports efficient recursive aggregation of BN254 proofs - the desire is for the bytecode validation circuit to be a canonical Honk SNARK over the BN254 field. + +To perform a polynomial consistency check between $\mathbb{op}$ and $[P]$, we perform the following: + +1. Generate a challenge $z \in \mathbb{F}$ by computing the Poseidon hash of $H(op_0, \ldots, op_{n-1}, [P])$ +2. Compute $\sum_{i=0}^{n-1} op_i z^i = r \in \mathbb{F}$ +3. Validate via a KZG opening proof that $[P]$ commits to a polynomial $P(X)$ such that $P(z) = r$ + +In the same manner that Honk pairings can be deferred via aggregating pairing inputs into an accumulator, the pairing required to validate the KZG opening proof can also be deferred. + +## Evaluating the polynomial consistency check within a circuit + +The direct computation of $r = \sum_{i=0}^{n-1} op_i z^i$ is trivial as the field is native to a BN254 SNARK circuit, and will require approx. 2 constraints per opcode. + +Validating a KZG opening proof will require approx. 3 non-native elliptic curve scalar multiplications, which will have a cost of approx. 30,000 constraints if using `stdlib::biggroup` from the PLONK standard library. + +The major cost of the consistency check is the Poseidon hash of the packed bytecode vector $\mathbb{b}$ and the commitment $[P]$ - this will incur approx. 22 constraints per element in $\mathbb{b}$ + +# Definition of mapping function $g$ + +The following is a pseudocode description of $g$, which can efficiently be described in a Honk circuit (i.e. no branches). + +We define a function `slice(element, idx, length)`. `element` is a field element interpreted as a length-31 byte array. `slice` computes the byte array `element[idx] : element[idx + length]`, converts into a field element and returns it. + +We define a size-256 lookup table `c` that maps an avm instruction byte to the byte length required to represent its respective opcode. + +``` +g(b) { + let i := 0; // index into bytecode array `b` + let j := 0; // byte offset of current bytecode element + let op := []; // vector of opcode values we need to populate + for k in [0, n]: + { + let f := b[i]; + let instruction_byte := f.slice(j, 1); + let opcode_length := c[instruction_byte]; + let bytes_remaining_in_f := 30 - j; + let op_split := opcode_length > bytes_remaining_in_f; + let bytes_from_f := op_split ? bytes_remaining_in_f : opcode_length; + let op_hi := f.slice(j, bytes_from_f); + + let f' := b[i+1]; + let bytes_from_f' := opcode_length - bytes_from_f; + let op_lo := f'.slice(0, bytes_in_f'); + + op[k] := op_lo + (op_hi << (bytes_in_f' * 8)); + i := i + op_split; + j := op_split ? bytes_in_f' : j + opcode_length; + } + return op; +} +``` + +Pseudocode definition of `slice` function constraints: + +We define `pow(x)` to be a size-31 lookup table that maps an input $x \in [0, \ldots, 31]$ into the value $2^{8x}$ + +We require the Prover has computed witness field elements `f_lo`, `f_hi`, `result` that satisfy the following constraints: + +``` +slice(f, index, length) +{ + assert(f_hi < pow(index)); + assert(f_lo < pow(31 - index - length)); + assert(result < pow(length)); + assert(f == f_lo + result * pow(31 - index - length) + f_hi * pow(31 - index)); + return result; +} +``` + +## Evaluating `g` within a Honk circuit + +The `g` function requires the contents of $\mathbb{b}$ be present via a lookup table. We can achieve this by instantiating elements of $\mathbb{b}$ via the ROM abstraction present in the Plonk standard library (table initialisation costs 2 constraints per element, table reads cost 2 constraints per element) + +We can instantiate tables `c` , `pow` as lookup tables via the same mechanism. + +The `slice` function requires 3 variable-length range checks. In Honk circuits we only can support fixed-length range checks. + +The following pseudocode defines how a variable-length range check can be composed of fixed-length range checks. Here we assume we have previously constrained all inputs to be less than $2^{248} - 1$ + +``` +less_than(a, b) { + // this block is not constrained and defines witness gneeration + let a_lo := a & (2^{124} - 1) + let b_lo := b & (2^{124} - 1) + let a_hi := (a >> 124) + let b_hi := (b >> 124) + let borrow := b_lo < a_lo + let r_lo := b_lo - a_lo + borrow*2^124 + let r_hi := b_hi - a_hi - borrow + + // this block defines constraints + assert(a_lo < 2^124) + assert(a_hi < 2^124) + assert(b_lo < 2^124) + assert(b_hi < 2^124) + assert(r_lo < 2^124) + assert(r_hi < 2^124) + assert(borrow*borrow - borrow = 0) // bool check + assert(a_lo + 2^{124}a_hi = a) + assert(b_lo + 2^{124}b_hi = b) + assert(r_lo = b_lo - a_lo + borrow*2^124) + assert(r_hi = b_hi - a_hi - borrow) +} +``` + +Each `slice` call requires three `less_than` calls, and each iteration of `g` requires 3 `slice` calls. In total this produces 36 size-124 range checks per iteration of `g`. Each size-124 range check requires approx. 5 constraints, producing 180 constraints of range checks per opcode processed. + +A rough estimate of the total constraints per opcode processed by the `g` function would be 200 constraints per opcdoe. + +# Bytecode Validation Circuit Summary + +The bytecode validation circuit takes, as public inputs, the packed bytecode array $\mathbf{b} \in \mathbb{F}$ and the bytecode commitment $[P] \in \mathbb{G}_{bn254}$ (represented via field elements). + +The circuit evaluates the following: + +1. For all $i \in [0, \ldots, n - 1]$, validate that $b_i < 256^{31} - 1$ +2. Compute $\mathbf{op} = g(\mathbf{b})$ +3. Perform a _polynomial consistency check_ between $\mathbf{op}$ and $[P]$ + +### Summary of main circuit costs + +The polynomial consistency check requires a Poseidon hash that includes the packed bytecode array $\mathbb{b}$. This requires approx. 22 Honk constraints per 31 bytes of bytecode. + +The `g` function will cost approx. 200 constraints per opcode. + +For a given length `n` , the approx. number of constraints required will be approx `222n`. + +A circuit of size 2^21 (2 million constraints) will be able to process a program containing approximately $n = 9,400$ steps. In contrast, a Soldity program can contain a maximum of 24kb of bytecode. + +Note: unless the efficiency of the validation circuit can be improved by a factor of ~4x, it will not be possible to construct bytecode validation proofs client-side in a web browser. Delegating proof construction to a 3rd party would be acceptable in this context because the 3rd party is untrusted and no secret information is leaked. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/circuit-index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/circuit-index.md new file mode 100644 index 000000000000..568f41f37234 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/circuit-index.md @@ -0,0 +1,3 @@ +# AVM Circuit + +The AVM circuit's purpose is to prove execution of a sequence of instructions for a public execution request. Regardless of whether execution succeeds or reverts, the circuit always generates a valid proof of execution. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/context.mdx b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/context.mdx new file mode 100644 index 000000000000..776d27337b24 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/context.mdx @@ -0,0 +1,115 @@ +# Execution Context + +:::note REMINDER +Many terms and definitions here are borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). +::: + +An **execution context** contains the information and state relevant to a contract call's execution. When a contract call is made, an execution context is [initialized](#context-initialization) before the contract code's execution begins. + +#### _AvmContext_ + +| Field | Type | +| --------------------------------------------------------- | ----------------------- | +| environment | `ExecutionEnvironment` | +| [machineState](./state#machine-state) | `MachineState` | +| [worldState](./state#avm-world-state) | `AvmWorldState` | +| [worldStateAccessTrace](./state#world-state-access-trace) | `WorldStateAccessTrace` | +| [accruedSubstate](./state#accrued-substate) | `AccruedSubstate` | +| results | `ContractCallResults` | + +## Execution Environment + +A context's **execution environment** remains constant throughout a contract call's execution. When a contract call initializes its execution context, it [fully specifies the execution environment](#context-initialization). + +### _ExecutionEnvironment_ + +| Field | Type | Description | +| ------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| address | `AztecAddress` | | +| sender | `AztecAddress` | | +| functionSelector | `u32` | | +| transactionFee | `field` | Computed transaction fee based on gas fees, inclusion fee, and gas usage. Zero in all phases but teardown. | +| contractCallDepth | `field` | Depth of the current call (how many nested calls deep is it). | +| contractCallPointer | `field` | Uniquely identifies each contract call processed by an AVM session. An initial call is assigned pointer value of 1 (expanded on in the AVM circuit section's ["Call Pointer"](./avm-circuit#call-pointer) subsection). | +| globals | `PublicGlobalVariables` | | +| isStaticCall | `boolean` | | +| calldata | `[field; ]` | | + +## Contract Call Results + +When a contract call halts, it sets the context's **contract call results** to communicate results to the caller. + +### _ContractCallResults_ + +| Field | Type | Description | +| -------- | -------------------------- | ----------- | +| reverted | `boolean` | | +| output | `[field; ]` | | + +## Context initialization + +### Initial contract calls + +An **initial contract call** initializes a new execution context from a public execution request. + +``` +context = AvmContext { + environment = INITIAL_EXECUTION_ENVIRONMENT, + machineState = INITIAL_MACHINE_STATE, + worldState = , + worldStateAccessTrace = INITIAL_WORLD_STATE_ACCESS_TRACE, + accruedSubstate = { [], ... [], }, // all substate vectors empty + results = INITIAL_CONTRACT_CALL_RESULTS, +} +``` + +> Since world state persists between transactions, the latest state is injected into a new AVM context. + +Given a [`PublicCallRequest`](../transactions/tx-object#public-call-request) and its parent [`TxRequest`](../transactions/local-execution#execution-request), these above-listed "`INITIAL_*`" entries are defined as follows: + +``` +INITIAL_EXECUTION_ENVIRONMENT = ExecutionEnvironment { + address: PublicCallRequest.contractAddress, + sender: PublicCallRequest.CallContext.msgSender, + functionelector: PublicCallRequest.functionSelector, + contractCallDepth: 0, + contractCallPointer: 1, + globals: + isStaticCall: PublicCallRequest.CallContext.isStaticCall, + calldata: PublicCallRequest.args, +} + +INITIAL_MACHINE_STATE = MachineState { + l2GasLeft: , + daGasLeft: , + pc: 0, + internalCallStack: [], // initialized as empty + memory: [0, ..., 0], // all 2^32 entries are initialized to zero +} + +INITIAL_WORLD_STATE_ACCESS_TRACE = WorldStateAccessTrace { + accessCounter: 1, + contractCalls: [ // initial contract call is traced + TracedContractCall { + callPointer: nestedContext.environment.callPointer, + address: nestedContext.address, + counter: 0, + endLifetime: 0, // The call's end-lifetime will be updated later if it or its caller reverts + } + ], + [], ... [], // remaining entries are empty +}, + +INITIAL_CONTRACT_CALL_RESULTS = ContractCallResults { + reverted = false, + output = [], // initialized as empty +} +``` + +### Nested contract calls + +> See the dedicated ["Nested Contract Calls"](./nested-calls) page for a detailed explanation of nested contract calls. + +import NestedContext from "./_nested-context.md"; + + diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/control-flow.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/control-flow.md new file mode 100644 index 000000000000..53d7c7520cc5 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/control-flow.md @@ -0,0 +1,32 @@ +# VM Control Flow + High-level architecture + +This document breaks down the VM into internal components and defines how these components interact with one another. + +This can be considered an intermediate abstraction layer between the high-level VM definition and the explicit circuit architecture. The intention is for this abstraction to be analogous to how CPU chips are broken down into discrete components. + +# Sub-operations + +> Notation note: I use the term "clock cycle" in a way that is analogous to "row in the execution trace". + +We wish to define a set of sub-operations our VM can execute. Multiple sub-operations can be executed per clock cycle. Each instruction in the instruction set exposed by the VM is composed of 1 or more sub-operations. + +The intention is for sub-operations to be implementable as independent VM circuit relations. + +# Control flow + +![](/img/protocol-specs/public-vm/avm-control-flow.png) + +> Notation note: whenever the VM "sends a signal" to one or more VM components, this is analogous to defining a boolean column in the execution trace that toggles on/off specific functionality + +- The instruction controller uses a program counter to read the current opcode to be executed, and send signals to downstream components to execute the opcode +- The state controller reads data from memory into the intermediate registers, using the opcode parameter values +- The VM "algebraic logic unit" executes the opcode given the intermediate register values, and writes output into the intermediate registers +- The state controller writes the output from an intermediate register into memory + +## Chiplets + +This borrows the chiplet nomenclature from the Miden VM - these components encapsulate functionality that can be effectively defined via an independent sub-circuit (analog in real world = specific part of a CPU chip die) + +Chiplets can be developed iteratively i.e. first draft of the AVM only needs a barebones algebraic logic unit. + +> We want apply the following design heuristic to chiplets: they are _loosely coupled_ to other AVM components. It should be possible to remove a chiplet and the AVM opcodes it implements, without requiring any upstream changes to the AVM architecture/implementation diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/execution.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/execution.md new file mode 100644 index 000000000000..4c4c9114c9a9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/execution.md @@ -0,0 +1,258 @@ +# Execution, Gas, Halting + +Execution of an AVM program, within a provided [execution context](./context), includes the following steps: + +1. Fetch contract bytecode and decode into a vector of [AVM instructions](./instruction-set) +1. Repeat the next step until a [halt](#halting) is reached +1. Execute the instruction at the index specified by the context's [program counter](#program-counter-and-control-flow) + - Instruction execution will update the program counter + +The following shorthand syntax is used to refer to this execution routine in the ["Instruction Set"](./instruction-set), ["Nested execution"](./nested-calls#nested-execution), and other sections: + +```jsx +execute(context); +``` + +## Bytecode fetch and decode + +Before execution begins, a contract's bytecode is retrieved. + +```jsx +bytecode = context.worldState.contracts[context.environment.address].bytecode; +``` + +> As described in ["Contract Deployment"](../contract-deployment), contracts are not stored in a dedicated tree. A [contract class](../contract-deployment/classes) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractClass` structure (which contains the bytecode) and a nullifier representing the class identifier. A [contract instance](../contract-deployment/instances) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractInstance` structure and a nullifier representing the contract address. + +> Thus, the syntax used above for bytecode retrieval is shorthand for: +> +> 1. Perform a membership check of the contract instance address nullifier +> 1. Retrieve the `ContractInstance` from a database that tracks all such unencrypted logs +> ```jsx +> contractInstance = contractInstances[context.environment.address]; +> ``` +> 1. Perform a membership check of the contract class identifier nullifier +> 1. Retrieve the `ContractClass` and its bytecode from a database that tracks all such unencrypted logs +> ```jsx +> contractClass = contractClasses[contractInstance.contract_class_id]; +> bytecode = contractClass.packed_public_bytecode; +> ``` + +The bytecode is then decoded into a vector of `instructions`. An instruction is referenced throughout this document according to the following interface: + +| Member | Description | +| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `opcode` | The 8-bit opcode value that identifies the operation an instruction is meant to perform. | +| `indirect` | Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. | +| `inTag` | The [tag/size](./memory-model.md#tags-and-tagged-memory) to check inputs against and/or tag the destination with. | +| `args` | Named arguments as specified for an instruction in the ["Instruction Set"](./instruction-set). As an example, `instr.args.aOffset` refers to an instructions argument named `aOffset`. | +| `execute` | Apply this instruction's transition function to an execution context (_e.g._ `instr.execute(context)`). | + +## Instruction execution + +Once bytecode has been fetched and decoded into the `instructions` vector, instruction execution begins. + +The AVM executes the instruction at the index specified by the context's program counter. + +```jsx +while (!halted) instr = instructions[machineState.pc]; +instr.execute(context); +``` + +An instruction's execution mutates the context's state as specified in the ["Instruction Set"](./instruction-set). + +## Program Counter and Control Flow + +A context is initialized with a program counter of zero, and so instruction execution always begins with a contract's the very first instruction. + +The program counter specifies which instruction the AVM will execute next, and each instruction's execution updates the program counter in some way. This allows the AVM to progress to the next instruction at each step. + +Most instructions simply increment the program counter by 1. This allows VM execution to flow naturally from instruction to instruction. Some instructions ([`JUMP`](./instruction-set#isa-section-jump), [`JUMPI`](./instruction-set#isa-section-jumpi), [`INTERNALCALL`](./instruction-set#isa-section-internalcall)) modify the program counter based on arguments. + +The `INTERNALCALL` instruction pushes `machineState.pc+1` to `machineState.internalCallStack` and then updates `pc` to the instruction's destination argument (`instr.args.loc`). The `INTERNALRETURN` instruction pops a destination from `machineState.internalCallStack` and assigns the result to `pc`. + +> An instruction will never assign program counter a value from memory (`machineState.memory`). A `JUMP`, `JUMPI`, or `INTERNALCALL` instruction's destination is a constant from the program bytecode. This property allows for easier static program analysis. + +## Gas checks and tracking + +> See ["Gas and Fees"](../gas-and-fees) for a deeper dive into Aztec's gas model and for definitions of each type of gas. + +Each instruction has an associated `l2GasCost` and `daGasCost`. The AVM uses these values to enforce that sufficient gas is available before executing an instruction, and to deduct the cost from the context's remaining gas. The process of checking and charging gas is referred to in other sections using the following shorthand: + +```jsx +chargeGas(context, l2GasCost, daGasCost); +``` + +### Checking gas + +Before an instruction is executed, the VM enforces that there is sufficient gas remaining via the following assertions: + +``` +assert machineState.l2GasLeft - instr.l2GasCost >= 0 +assert machineState.daGasLeft - instr.daGasCost >= 0 +``` + +> Many instructions (like arithmetic operations) have 0 `daGasCost`. Instructions only incur a DA cost if they modify the [world state](./state#avm-world-state) or [accrued substate](./state#accrued-substate). + +### Charging gas + +If these assertions pass, the machine state's gas left is decreased prior to the instruction's core execution: + +``` +machineState.l2GasLeft -= instr.l2GasCost +machineState.daGasLeft -= instr.daGasCost +``` + +If either of these assertions _fail_ for an instruction, this triggers an exceptional halt. The gas left is set to 0 and execution reverts. + +``` +machineState.l2GasLeft = 0 +machineState.daGasLeft = 0 +``` + +> Reverting and exceptional halts are covered in more detail in the ["Halting" section](#halting). + +### Gas cost notes and examples + +An instruction's gas cost is meant to reflect the computational cost of generating a proof of its correct execution. For some instructions, this computational cost changes based on inputs. Here are some examples and important notes: + +- All instructions have a base cost. [`JUMP`](./instruction-set/#isa-section-jump) is an example of an instruction with constant gas cost. Regardless of its inputs, the instruction always incurs the same `l2GasCost` and `daGasCost`. +- The [`SET`](./instruction-set/#isa-section-set) instruction operates on a different sized constant (based on its `dstTag`). Therefore, this instruction's gas cost increases with the size of its input. +- In addition to the base cost, the cost of an instruction increases with the number of reads and writes to memory. This is affected by the total number of input and outputs: the gas cost for [`AND`](./instruction-set/#isa-section-and) should be greater than that of [`NOT`](./instruction-set/#isa-section-not) since it takes one more input. +- Input parameters flagged as "indirect" require an extra memory access, so these should further increase the gas cost of the instruction. +- The base cost for instructions that operate on a data range of a specified "size" scale in cost with that size, but only if they perform an operation on the data other than copying. For example, [`CALLDATACOPY`](./instruction-set/#isa-section-calldatacopy) copies `copySize` words from `environment.calldata` to `machineState.memory`, so its increased cost is captured by the extra memory accesses. On the other hand, [`SSTORE`](./instruction-set#isa-section-sstore) requires accesses to persistent storage proportional to `srcSize`, so its base cost should also increase. +- The [`CALL`](./instruction-set#isa-section-call)/[`STATICCALL`](./instruction-set#isa-section-staticcall) instruction's gas cost is determined by its `*Gas` arguments, but any gas unused by the nested contract call's execution is refunded after its completion ([more on this later](./nested-calls#updating-the-calling-context-after-nested-call-halts)). + +> An instruction's gas cost will roughly align with the number of rows it corresponds to in the SNARK execution trace including rows in the sub-operation table, memory table, chiplet tables, etc. + +> An instruction's gas cost takes into account the costs of associated downstream computations. An instruction that triggers accesses to the public data tree (`SLOAD`/`SSTORE`) incurs a cost that accounts for state access validation in later circuits (public kernel or rollup). A contract call instruction (`CALL`/`STATICCALL`) incurs a cost accounting for the nested call's complete execution as well as any work required by the public kernel circuit for this additional call. + +## Halting + +A context's execution can end with a **normal halt** or **exceptional halt**. A halt ends execution within the current context and returns control flow to the calling context. + +### Normal halting + +A normal halt occurs when the VM encounters an explicit halting instruction ([`RETURN`](./instruction-set#isa-section-return) or [`REVERT`](./instruction-set#isa-section-revert)). Such instructions consume gas normally and optionally initialize some output data before finally halting the current context's execution. + +``` +machineState.l2GasLeft -= instr.l2GasCost +machineState.daGasLeft -= instr.daGasCost +results.reverted = instr.opcode == REVERT +results.output = machineState.memory[instr.args.retOffset:instr.args.retOffset+instr.args.retSize] +``` + +> Definitions: `retOffset` and `retSize` here are arguments to the [`RETURN`](./instruction-set/#isa-section-return) and [`REVERT`](./instruction-set#isa-section-revert) instructions. If `retSize` is 0, the context will have no output. Otherwise, these arguments point to a region of memory to output. + +> `results.output` is only relevant when the caller is a contract call itself. In other words, it is only relevant for [nested contract calls](./nested-calls). When an [initial contract call](./context#initial-contract-calls) (initiated by a public execution request) halts normally, its `results.output` is ignored. + +### Exceptional halting + +An exceptional halt is not explicitly triggered by an instruction but instead occurs when an exceptional condition is met. + +When an exceptional halt occurs, the context is flagged as consuming all of its allocated gas and is marked as `reverted` with _no output data_, and then execution within the current context ends. + +``` +machineState.l2GasLeft = 0 +machineState.daGasLeft = 0 +results.reverted = true +// results.output remains empty +``` + +The AVM's exceptional halting conditions area listed below: + +1. **Insufficient gas** + ``` + assert machineState.l2GasLeft - instr.l2GasCost >= 0 + assert machineState.daGasLeft - instr.l2GasCost >= 0 + ``` +1. **Invalid instruction encountered** + ``` + assert instructions[machineState.pc].opcode <= MAX_AVM_OPCODE + ``` +1. **Jump destination past end of program** + ``` + assert instructions[machineState.pc].opcode not in {JUMP, JUMPI, INTERNALCALL} + OR instr.args.loc < instructions.length + ``` +1. **Failed memory tag check** + - Defined per-instruction in the [Instruction Set](./instruction-set) +1. **Out of bounds memory access (max memory offset is $2^{32}-1$)** + ``` + for offset in instr.args.*Offset: + assert offset < 2^32 + ``` +1. **World state modification attempt during a static call** + ``` + assert !environment.isStaticCall + OR instructions[machineState.pc].opcode not in WS_AS_MODIFYING_OPS + ``` + > Definition: `WS_AS_MODIFYING_OPS` represents the list of all opcodes corresponding to instructions that modify world state or accrued substate. +1. **Maximum contract call depth (1024) exceeded** + ``` + assert environment.contractCallDepth <= 1024 + assert instructions[machineState.pc].opcode not in {CALL, STATICCALL} + OR environment.contractCallDepth < 1024 + ``` +1. **Maximum contract call calls per execution request (1024) exceeded** + ``` + assert worldStateAccessTrace.contractCalls.length <= 1024 + assert instructions[machineState.pc].opcode not in {CALL, STATICCALL} + OR worldStateAccessTrace.contractCalls.length < 1024 + ``` +1. **Maximum internal call depth (1024) exceeded** + ``` + assert machineState.internalCallStack.length <= 1024 + assert instructions[machineState.pc].opcode != INTERNALCALL + OR environment.contractCallDepth < 1024 + ``` +1. **Maximum world state accesses (1024-per-category) exceeded** + + ``` + assert worldStateAccessTrace.publicStorageReads.length <= 1024 + AND worldStateAccessTrace.publicStorageWrites.length <= 1024 + AND worldStateAccessTrace.noteHashChecks.length <= 1024 + AND worldStateAccessTrace.noteHashes.length <= 1024 + AND worldStateAccessTrace.nullifierChecks.length <= 1024 + AND worldStateAccessTrace.nullifiers.length <= 1024 + AND worldStateAccessTrace.l1ToL2MessageChecks.length <= 1024 + AND worldStateAccessTrace.archiveChecks.length <= 1024 + + // Storage + assert instructions[machineState.pc].opcode != SLOAD + OR worldStateAccessTrace.publicStorageReads.length < 1024 + assert instructions[machineState.pc].opcode != SSTORE + OR worldStateAccessTrace.publicStorageWrites.length < 1024 + + // Note hashes + assert instructions[machineState.pc].opcode != NOTEHASHEXISTS + OR noteHashChecks.length < 1024 + assert instructions[machineState.pc].opcode != EMITNOTEHASH + OR noteHashes.length < 1024 + + // Nullifiers + assert instructions[machineState.pc].opcode != NULLIFIEREXISTS + OR nullifierChecks.length < 1024 + assert instructions[machineState.pc].opcode != EMITNULLIFIER + OR nullifiers.length < 1024 + + // Read L1 to L2 messages + assert instructions[machineState.pc].opcode != L1TOL2MSGEXISTS + OR worldStateAccessTrace.l1ToL2MessagesChecks.length < 1024 + ``` + +1. **Maximum accrued substate entries (per-category) exceeded** + + ``` + assert accruedSubstate.unencryptedLogs.length <= MAX_UNENCRYPTED_LOGS + AND accruedSubstate.sentL2ToL1Messages.length <= MAX_SENT_L2_TO_L1_MESSAGES + + // Unencrypted logs + assert instructions[machineState.pc].opcode != EMITUNENCRYPTEDLOG + OR unencryptedLogs.length < MAX_UNENCRYPTED_LOGS + + // Sent L2 to L1 messages + assert instructions[machineState.pc].opcode != SENDL2TOL1MSG + OR sentL2ToL1Messages.length < MAX_SENT_L2_TO_L1_MESSAGES + ``` + + > Note that ideally the AVM should limit the _total_ accrued substate entries per-category instead of the entries per-call. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/gen/_instruction-set.mdx b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/gen/_instruction-set.mdx new file mode 100644 index 000000000000..b7de3caf1344 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/gen/_instruction-set.mdx @@ -0,0 +1,1603 @@ +[comment]: # (THIS IS A GENERATED FILE! DO NOT EDIT!) +[comment]: # (Generated via `yarn preprocess`) + +[comment]: # (Generated by genMarkdown.js, InstructionSet.js, InstructionSize.js) + +import Markdown from 'react-markdown' +import CodeBlock from '@theme/CodeBlock' + + +## Instructions Table + +Click on an instruction name to jump to its section. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OpcodeNameSummaryExpression
0x00\[\`ADD\`\](#isa-section-add)Addition (a + b){ + `M[dstOffset] = M[aOffset] + M[bOffset] mod 2^k` + }
0x01\[\`SUB\`\](#isa-section-sub)Subtraction (a - b){ + `M[dstOffset] = M[aOffset] - M[bOffset] mod 2^k` + }
0x02\[\`MUL\`\](#isa-section-mul)Multiplication (a * b){ + `M[dstOffset] = M[aOffset] * M[bOffset] mod 2^k` + }
0x03\[\`DIV\`\](#isa-section-div)Unsigned integer division (a / b){ + `M[dstOffset] = M[aOffset] / M[bOffset]` + }
0x04\[\`FDIV\`\](#isa-section-fdiv)Field division (a / b){ + `M[dstOffset] = M[aOffset] / M[bOffset]` + }
0x05\[\`EQ\`\](#isa-section-eq)Equality check (a == b){ + `M[dstOffset] = M[aOffset] == M[bOffset] ? 1 : 0` + }
0x06\[\`LT\`\](#isa-section-lt)Less-than check (a < b){ + `M[dstOffset] = M[aOffset] < M[bOffset] ? 1 : 0` + }
0x07\[\`LTE\`\](#isa-section-lte)Less-than-or-equals check (a <= b){ + `M[dstOffset] = M[aOffset] <= M[bOffset] ? 1 : 0` + }
0x08\[\`AND\`\](#isa-section-and)Bitwise AND (a & b){ + `M[dstOffset] = M[aOffset] AND M[bOffset]` + }
0x09\[\`OR\`\](#isa-section-or)Bitwise OR (a | b){ + `M[dstOffset] = M[aOffset] OR M[bOffset]` + }
0x0a\[\`XOR\`\](#isa-section-xor)Bitwise XOR (a ^ b){ + `M[dstOffset] = M[aOffset] XOR M[bOffset]` + }
0x0b\[\`NOT\`\](#isa-section-not)Bitwise NOT (inversion){ + `M[dstOffset] = NOT M[aOffset]` + }
0x0c\[\`SHL\`\](#isa-section-shl)Bitwise leftward shift (a << b){ + `M[dstOffset] = M[aOffset] << M[bOffset]` + }
0x0d\[\`SHR\`\](#isa-section-shr)Bitwise rightward shift (a >> b){ + `M[dstOffset] = M[aOffset] >> M[bOffset]` + }
0x0e\[\`CAST\`\](#isa-section-cast)Type cast{ + `M[dstOffset] = cast(M[aOffset])` + }
0x0f\[\`ADDRESS\`\](#isa-section-address)Get the address of the currently executing l2 contract{ + `M[dstOffset] = context.environment.address` + }
0x10\[\`SENDER\`\](#isa-section-sender)Get the address of the sender (caller of the current context){ + `M[dstOffset] = context.environment.sender` + }
0x11\[\`TRANSACTIONFEE\`\](#isa-section-transactionfee)Get the computed transaction fee during teardown phase, zero otherwise{ + `M[dstOffset] = context.environment.transactionFee` + }
0x12\[\`CHAINID\`\](#isa-section-chainid)Get this rollup's L1 chain ID{ + `M[dstOffset] = context.environment.globals.chainId` + }
0x13\[\`VERSION\`\](#isa-section-version)Get this rollup's L2 version ID{ + `M[dstOffset] = context.environment.globals.version` + }
0x14\[\`BLOCKNUMBER\`\](#isa-section-blocknumber)Get this L2 block's number{ + `M[dstOffset] = context.environment.globals.blocknumber` + }
0x15\[\`TIMESTAMP\`\](#isa-section-timestamp)Get this L2 block's timestamp{ + `M[dstOffset] = context.environment.globals.timestamp` + }
0x16\[\`FEEPERL2GAS\`\](#isa-section-feeperl2gas)Get the fee to be paid per "L2 gas" - constant for entire transaction{ + `M[dstOffset] = context.environment.globals.feePerL2Gas` + }
0x17\[\`FEEPERDAGAS\`\](#isa-section-feeperdagas)Get the fee to be paid per "DA gas" - constant for entire transaction{ + `M[dstOffset] = context.environment.globals.feePerDaGas` + }
0x18\[\`CALLDATACOPY\`\](#isa-section-calldatacopy)Copy calldata into memory{ + `M[dstOffset:dstOffset+copySize] = context.environment.calldata[cdOffset:cdOffset+copySize]` + }
0x19\[\`L2GASLEFT\`\](#isa-section-l2gasleft)Remaining "L2 gas" for this call (after this instruction){ + `M[dstOffset] = context.MachineState.l2GasLeft` + }
0x1a\[\`DAGASLEFT\`\](#isa-section-dagasleft)Remaining "DA gas" for this call (after this instruction){ + `M[dstOffset] = context.machineState.daGasLeft` + }
0x1b\[\`JUMP\`\](#isa-section-jump)Jump to a location in the bytecode{ + `context.machineState.pc = loc` + }
0x1c\[\`JUMPI\`\](#isa-section-jumpi)Conditionally jump to a location in the bytecode{ + `context.machineState.pc = M[condOffset] > 0 ? loc : context.machineState.pc` + }
0x1d\[\`INTERNALCALL\`\](#isa-section-internalcall)Make an internal call. Push the current PC to the internal call stack and jump to the target location. +{`context.machineState.internalCallStack.push(context.machineState.pc) +context.machineState.pc = loc`} +
0x1e\[\`INTERNALRETURN\`\](#isa-section-internalreturn)Return from an internal call. Pop from the internal call stack and jump to the popped location.{ + `context.machineState.pc = context.machineState.internalCallStack.pop()` + }
0x1f\[\`SET\`\](#isa-section-set)Set a memory word from a constant in the bytecode{ + `M[dstOffset] = const` + }
0x20\[\`MOV\`\](#isa-section-mov)Move a word from source memory location to destination{ + `M[dstOffset] = M[srcOffset]` + }
0x21\[\`CMOV\`\](#isa-section-cmov)Move a word (conditionally chosen) from one memory location to another (`d = cond > 0 ? a : b`){ + `M[dstOffset] = M[condOffset] > 0 ? M[aOffset] : M[bOffset]` + }
0x22\[\`SLOAD\`\](#isa-section-sload)Load a word from this contract's persistent public storage. Zero is loaded for unwritten slots. +{`M[dstOffset] = S[M[slotOffset]]`} +
0x23\[\`SSTORE\`\](#isa-section-sstore)Write a word to this contract's persistent public storage +{`S[M[slotOffset]] = M[srcOffset]`} +
0x24\[\`NOTEHASHEXISTS\`\](#isa-section-notehashexists)Check whether a note hash exists in the note hash tree (as of the start of the current block) +{`exists = context.worldState.noteHashes.has({ + leafIndex: M[leafIndexOffset] + leaf: hash(context.environment.address, M[noteHashOffset]), +}) +M[existsOffset] = exists`} +
0x25\[\`EMITNOTEHASH\`\](#isa-section-emitnotehash)Emit a new note hash to be inserted into the note hash tree +{`context.worldState.noteHashes.append( + hash(context.environment.address, M[noteHashOffset]) +)`} +
0x26\[\`NULLIFIEREXISTS\`\](#isa-section-nullifierexists)Check whether a nullifier exists in the nullifier tree (including nullifiers from earlier in the current transaction or from earlier in the current block) +{`exists = pendingNullifiers.has(M[addressOffset], M[nullifierOffset]) || context.worldState.nullifiers.has( + hash(M[addressOffset], M[nullifierOffset]) +) +M[existsOffset] = exists`} +
0x27\[\`EMITNULLIFIER\`\](#isa-section-emitnullifier)Emit a new nullifier to be inserted into the nullifier tree +{`context.worldState.nullifiers.append( + hash(context.environment.address, M[nullifierOffset]) +)`} +
0x28\[\`L1TOL2MSGEXISTS\`\](#isa-section-l1tol2msgexists)Check if a message exists in the L1-to-L2 message tree +{`exists = context.worldState.l1ToL2Messages.has({ + leafIndex: M[msgLeafIndexOffset], leaf: M[msgHashOffset] +}) +M[existsOffset] = exists`} +
0x29\[\`GETCONTRACTINSTANCE\`\](#isa-section-getcontractinstance)Copies contract instance data to memory +{`M[dstOffset:dstOffset+CONTRACT_INSTANCE_SIZE+1] = [ + instance_found_in_address, + instance.salt ?? 0, + instance.deployer ?? 0, + instance.contractClassId ?? 0, + instance.initializationHash ?? 0, + instance.portalContractAddress ?? 0, + instance.publicKeysHash ?? 0, +]`} +
0x2a\[\`EMITUNENCRYPTEDLOG\`\](#isa-section-emitunencryptedlog)Emit an unencrypted log +{`context.accruedSubstate.unencryptedLogs.append( + UnencryptedLog { + address: context.environment.address, + log: M[logOffset:logOffset+M[logSizeOffset]], + } +)`} +
0x2b\[\`SENDL2TOL1MSG\`\](#isa-section-sendl2tol1msg)Send an L2-to-L1 message +{`context.accruedSubstate.sentL2ToL1Messages.append( + SentL2ToL1Message { + address: context.environment.address, + recipient: M[recipientOffset], + message: M[contentOffset] + } +)`} +
0x2c\[\`CALL\`\](#isa-section-call)Call into another contract +{`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } +chargeGas(context, + l2GasCost=M[instr.args.gasOffset], + daGasCost=M[instr.args.gasOffset+1]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall=false) +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext)`} +
0x2d\[\`STATICCALL\`\](#isa-section-staticcall)Call into another contract, disallowing World State and Accrued Substate modifications +{`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } +chargeGas(context, + l2GasCost=M[instr.args.gasOffset], + daGasCost=M[instr.args.gasOffset+1]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall=true +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext)`} +
0x2e\[\`RETURN\`\](#isa-section-return)Halt execution within this context (without revert), optionally returning some data +{`context.contractCallResults.output = M[retOffset:retOffset+retSize] +halt`} +
0x2f\[\`REVERT\`\](#isa-section-revert)Halt execution within this context as `reverted`, optionally returning some data +{`context.contractCallResults.output = M[retOffset:retOffset+retSize] +context.contractCallResults.reverted = true +halt`} +
0x30\[\`TORADIXLE\`\](#isa-section-to_radix_le)Convert a word to an array of limbs in little-endian radix formTBD: Storage of limbs and if T[dstOffset] is constrained to U8
+ + +## Instructions + +### `ADD` +Addition (a + b) + +[See in table.](#isa-table-add) + +- **Opcode**: 0x00 +- **Category**: Compute - Arithmetic +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] + M[bOffset] mod 2^k` +- **Details**: Wraps on overflow +- **Tag checks**: `T[aOffset] == T[bOffset] == inTag` +- **Tag updates**: `T[dstOffset] = inTag` +- **Bit-size**: 128 + +[![](/img/protocol-specs/public-vm/bit-formats/ADD.png)](/img/protocol-specs/public-vm/bit-formats/ADD.png) + +### `SUB` +Subtraction (a - b) + +[See in table.](#isa-table-sub) + +- **Opcode**: 0x01 +- **Category**: Compute - Arithmetic +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] - M[bOffset] mod 2^k` +- **Details**: Wraps on undeflow +- **Tag checks**: `T[aOffset] == T[bOffset] == inTag` +- **Tag updates**: `T[dstOffset] = inTag` +- **Bit-size**: 128 + +[![](/img/protocol-specs/public-vm/bit-formats/SUB.png)](/img/protocol-specs/public-vm/bit-formats/SUB.png) + +### `MUL` +Multiplication (a * b) + +[See in table.](#isa-table-mul) + +- **Opcode**: 0x02 +- **Category**: Compute - Arithmetic +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] * M[bOffset] mod 2^k` +- **Details**: Wraps on overflow +- **Tag checks**: `T[aOffset] == T[bOffset] == inTag` +- **Tag updates**: `T[dstOffset] = inTag` +- **Bit-size**: 128 + +[![](/img/protocol-specs/public-vm/bit-formats/MUL.png)](/img/protocol-specs/public-vm/bit-formats/MUL.png) + +### `DIV` +Unsigned integer division (a / b) + +[See in table.](#isa-table-div) + +- **Opcode**: 0x03 +- **Category**: Compute - Arithmetic +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] / M[bOffset]` +- **Details**: If the input is a field, it will be interpreted as an integer +- **Tag checks**: `T[aOffset] == T[bOffset] == inTag` +- **Tag updates**: `T[dstOffset] = inTag` +- **Bit-size**: 128 + +[![](/img/protocol-specs/public-vm/bit-formats/DIV.png)](/img/protocol-specs/public-vm/bit-formats/DIV.png) + +### `FDIV` +Field division (a / b) + +[See in table.](#isa-table-fdiv) + +- **Opcode**: 0x04 +- **Category**: Compute - Arithmetic +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] / M[bOffset]` +- **Tag checks**: `T[aOffset] == T[bOffset] == field` +- **Tag updates**: `T[dstOffset] = field` +- **Bit-size**: 120 + + +### `EQ` +Equality check (a \=\= b) + +[See in table.](#isa-table-eq) + +- **Opcode**: 0x05 +- **Category**: Compute - Comparators +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] == M[bOffset] ? 1 : 0` +- **Tag checks**: `T[aOffset] == T[bOffset] == inTag` +- **Tag updates**: `T[dstOffset] = u8` +- **Bit-size**: 128 + +[![](/img/protocol-specs/public-vm/bit-formats/EQ.png)](/img/protocol-specs/public-vm/bit-formats/EQ.png) + +### `LT` +Less-than check (a \< b) + +[See in table.](#isa-table-lt) + +- **Opcode**: 0x06 +- **Category**: Compute - Comparators +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] < M[bOffset] ? 1 : 0` +- **Tag checks**: `T[aOffset] == T[bOffset] == inTag` +- **Tag updates**: `T[dstOffset] = u8` +- **Bit-size**: 128 + +[![](/img/protocol-specs/public-vm/bit-formats/LT.png)](/img/protocol-specs/public-vm/bit-formats/LT.png) + +### `LTE` +Less-than-or-equals check (a \<\= b) + +[See in table.](#isa-table-lte) + +- **Opcode**: 0x07 +- **Category**: Compute - Comparators +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] <= M[bOffset] ? 1 : 0` +- **Tag checks**: `T[aOffset] == T[bOffset] == inTag` +- **Tag updates**: `T[dstOffset] = u8` +- **Bit-size**: 128 + +[![](/img/protocol-specs/public-vm/bit-formats/LTE.png)](/img/protocol-specs/public-vm/bit-formats/LTE.png) + +### `AND` +Bitwise AND (a & b) + +[See in table.](#isa-table-and) + +- **Opcode**: 0x08 +- **Category**: Compute - Bitwise +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] AND M[bOffset]` +- **Tag checks**: `T[aOffset] == T[bOffset] == inTag` +- **Tag updates**: `T[dstOffset] = inTag` +- **Bit-size**: 128 + +[![](/img/protocol-specs/public-vm/bit-formats/AND.png)](/img/protocol-specs/public-vm/bit-formats/AND.png) + +### `OR` +Bitwise OR (a | b) + +[See in table.](#isa-table-or) + +- **Opcode**: 0x09 +- **Category**: Compute - Bitwise +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] OR M[bOffset]` +- **Tag checks**: `T[aOffset] == T[bOffset] == inTag` +- **Tag updates**: `T[dstOffset] = inTag` +- **Bit-size**: 128 + +[![](/img/protocol-specs/public-vm/bit-formats/OR.png)](/img/protocol-specs/public-vm/bit-formats/OR.png) + +### `XOR` +Bitwise XOR (a ^ b) + +[See in table.](#isa-table-xor) + +- **Opcode**: 0x0a +- **Category**: Compute - Bitwise +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] XOR M[bOffset]` +- **Tag checks**: `T[aOffset] == T[bOffset] == inTag` +- **Tag updates**: `T[dstOffset] = inTag` +- **Bit-size**: 128 + +[![](/img/protocol-specs/public-vm/bit-formats/XOR.png)](/img/protocol-specs/public-vm/bit-formats/XOR.png) + +### `NOT` +Bitwise NOT (inversion) + +[See in table.](#isa-table-not) + +- **Opcode**: 0x0b +- **Category**: Compute - Bitwise +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. +- **Args**: + - **aOffset**: memory offset of the operation's input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = NOT M[aOffset]` +- **Tag checks**: `T[aOffset] == inTag` +- **Tag updates**: `T[dstOffset] = inTag` +- **Bit-size**: 96 + +[![](/img/protocol-specs/public-vm/bit-formats/NOT.png)](/img/protocol-specs/public-vm/bit-formats/NOT.png) + +### `SHL` +Bitwise leftward shift (a \<\< b) + +[See in table.](#isa-table-shl) + +- **Opcode**: 0x0c +- **Category**: Compute - Bitwise +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] << M[bOffset]` +- **Tag checks**: `T[aOffset] == inTag`, `T[bOffset] == u8` +- **Tag updates**: `T[dstOffset] = inTag` +- **Bit-size**: 128 + +[![](/img/protocol-specs/public-vm/bit-formats/SHL.png)](/img/protocol-specs/public-vm/bit-formats/SHL.png) + +### `SHR` +Bitwise rightward shift (a \>\> b) + +[See in table.](#isa-table-shr) + +- **Opcode**: 0x0d +- **Category**: Compute - Bitwise +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] >> M[bOffset]` +- **Tag checks**: `T[aOffset] == inTag`, `T[bOffset] == u8` +- **Tag updates**: `T[dstOffset] = inTag` +- **Bit-size**: 128 + +[![](/img/protocol-specs/public-vm/bit-formats/SHR.png)](/img/protocol-specs/public-vm/bit-formats/SHR.png) + +### `CAST` +Type cast + +[See in table.](#isa-table-cast) + +- **Opcode**: 0x0e +- **Category**: Type Conversions +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **dstTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to tag the destination with but not to check inputs against. +- **Args**: + - **aOffset**: memory offset of word to cast + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = cast(M[aOffset])` +- **Details**: Cast a word in memory based on the `dstTag` specified in the bytecode. Truncates (`M[dstOffset] = M[aOffset] mod 2^dstsize`) when casting to a smaller type, left-zero-pads when casting to a larger type. See [here](./memory-model#cast-and-tag-conversions) for more details. +- **Tag updates**: `T[dstOffset] = dstTag` +- **Bit-size**: 96 + +[![](/img/protocol-specs/public-vm/bit-formats/CAST.png)](/img/protocol-specs/public-vm/bit-formats/CAST.png) + +### `ADDRESS` +Get the address of the currently executing l2 contract + +[See in table.](#isa-table-address) + +- **Opcode**: 0x0f +- **Category**: Execution Environment +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = context.environment.address` +- **Tag updates**: `T[dstOffset] = field` +- **Bit-size**: 56 + +[![](/img/protocol-specs/public-vm/bit-formats/ADDRESS.png)](/img/protocol-specs/public-vm/bit-formats/ADDRESS.png) + +### `SENDER` +Get the address of the sender (caller of the current context) + +[See in table.](#isa-table-sender) + +- **Opcode**: 0x10 +- **Category**: Execution Environment +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = context.environment.sender` +- **Tag updates**: `T[dstOffset] = field` +- **Bit-size**: 56 + +[![](/img/protocol-specs/public-vm/bit-formats/SENDER.png)](/img/protocol-specs/public-vm/bit-formats/SENDER.png) + +### `TRANSACTIONFEE` +Get the computed transaction fee during teardown phase, zero otherwise + +[See in table.](#isa-table-transactionfee) + +- **Opcode**: 0x11 +- **Category**: Execution Environment +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = context.environment.transactionFee` +- **Tag updates**: `T[dstOffset] = field` +- **Bit-size**: 56 + + +### `CHAINID` +Get this rollup's L1 chain ID + +[See in table.](#isa-table-chainid) + +- **Opcode**: 0x12 +- **Category**: Execution Environment - Globals +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = context.environment.globals.chainId` +- **Tag updates**: `T[dstOffset] = field` +- **Bit-size**: 56 + +[![](/img/protocol-specs/public-vm/bit-formats/CHAINID.png)](/img/protocol-specs/public-vm/bit-formats/CHAINID.png) + +### `VERSION` +Get this rollup's L2 version ID + +[See in table.](#isa-table-version) + +- **Opcode**: 0x13 +- **Category**: Execution Environment - Globals +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = context.environment.globals.version` +- **Tag updates**: `T[dstOffset] = field` +- **Bit-size**: 56 + +[![](/img/protocol-specs/public-vm/bit-formats/VERSION.png)](/img/protocol-specs/public-vm/bit-formats/VERSION.png) + +### `BLOCKNUMBER` +Get this L2 block's number + +[See in table.](#isa-table-blocknumber) + +- **Opcode**: 0x14 +- **Category**: Execution Environment - Globals +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = context.environment.globals.blocknumber` +- **Tag updates**: `T[dstOffset] = field` +- **Bit-size**: 56 + +[![](/img/protocol-specs/public-vm/bit-formats/BLOCKNUMBER.png)](/img/protocol-specs/public-vm/bit-formats/BLOCKNUMBER.png) + +### `TIMESTAMP` +Get this L2 block's timestamp + +[See in table.](#isa-table-timestamp) + +- **Opcode**: 0x15 +- **Category**: Execution Environment - Globals +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = context.environment.globals.timestamp` +- **Tag updates**: `T[dstOffset] = u64` +- **Bit-size**: 56 + +[![](/img/protocol-specs/public-vm/bit-formats/TIMESTAMP.png)](/img/protocol-specs/public-vm/bit-formats/TIMESTAMP.png) + +### `FEEPERL2GAS` +Get the fee to be paid per "L2 gas" - constant for entire transaction + +[See in table.](#isa-table-feeperl2gas) + +- **Opcode**: 0x16 +- **Category**: Execution Environment - Globals - Gas +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = context.environment.globals.feePerL2Gas` +- **Tag updates**: `T[dstOffset] = field` +- **Bit-size**: 56 + +[![](/img/protocol-specs/public-vm/bit-formats/FEEPERL2GAS.png)](/img/protocol-specs/public-vm/bit-formats/FEEPERL2GAS.png) + +### `FEEPERDAGAS` +Get the fee to be paid per "DA gas" - constant for entire transaction + +[See in table.](#isa-table-feeperdagas) + +- **Opcode**: 0x17 +- **Category**: Execution Environment - Globals - Gas +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = context.environment.globals.feePerDaGas` +- **Tag updates**: `T[dstOffset] = field` +- **Bit-size**: 56 + +[![](/img/protocol-specs/public-vm/bit-formats/FEEPERDAGAS.png)](/img/protocol-specs/public-vm/bit-formats/FEEPERDAGAS.png) + +### `CALLDATACOPY` +Copy calldata into memory + +[See in table.](#isa-table-calldatacopy) + +- **Opcode**: 0x18 +- **Category**: Execution Environment - Calldata +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **cdOffset**: offset into calldata to copy from + - **copySize**: number of words to copy + - **dstOffset**: memory offset specifying where to copy the first word to +- **Expression**: `M[dstOffset:dstOffset+copySize] = context.environment.calldata[cdOffset:cdOffset+copySize]` +- **Details**: Calldata is read-only and cannot be directly operated on by other instructions. This instruction moves words from calldata into memory so they can be operated on normally. +- **Tag updates**: `T[dstOffset:dstOffset+copySize] = field` +- **Bit-size**: 120 + +[![](/img/protocol-specs/public-vm/bit-formats/CALLDATACOPY.png)](/img/protocol-specs/public-vm/bit-formats/CALLDATACOPY.png) + +### `L2GASLEFT` +Remaining "L2 gas" for this call (after this instruction) + +[See in table.](#isa-table-l2gasleft) + +- **Opcode**: 0x19 +- **Category**: Machine State - Gas +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = context.MachineState.l2GasLeft` +- **Tag updates**: `T[dstOffset] = u32` +- **Bit-size**: 56 + +[![](/img/protocol-specs/public-vm/bit-formats/L2GASLEFT.png)](/img/protocol-specs/public-vm/bit-formats/L2GASLEFT.png) + +### `DAGASLEFT` +Remaining "DA gas" for this call (after this instruction) + +[See in table.](#isa-table-dagasleft) + +- **Opcode**: 0x1a +- **Category**: Machine State - Gas +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = context.machineState.daGasLeft` +- **Tag updates**: `T[dstOffset] = u32` +- **Bit-size**: 56 + +[![](/img/protocol-specs/public-vm/bit-formats/DAGASLEFT.png)](/img/protocol-specs/public-vm/bit-formats/DAGASLEFT.png) + +### `JUMP` +Jump to a location in the bytecode + +[See in table.](#isa-table-jump) + +- **Opcode**: 0x1b +- **Category**: Machine State - Control Flow +- **Args**: + - **loc**: target location to jump to +- **Expression**: `context.machineState.pc = loc` +- **Details**: Target location is an immediate value (a constant in the bytecode). +- **Bit-size**: 48 + +[![](/img/protocol-specs/public-vm/bit-formats/JUMP.png)](/img/protocol-specs/public-vm/bit-formats/JUMP.png) + +### `JUMPI` +Conditionally jump to a location in the bytecode + +[See in table.](#isa-table-jumpi) + +- **Opcode**: 0x1c +- **Category**: Machine State - Control Flow +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **loc**: target location conditionally jump to + - **condOffset**: memory offset of the operations 'conditional' input +- **Expression**: `context.machineState.pc = M[condOffset] > 0 ? loc : context.machineState.pc` +- **Details**: Target location is an immediate value (a constant in the bytecode). `T[condOffset]` is not checked because the greater-than-zero suboperation is the same regardless of type. +- **Bit-size**: 88 + +[![](/img/protocol-specs/public-vm/bit-formats/JUMPI.png)](/img/protocol-specs/public-vm/bit-formats/JUMPI.png) + +### `INTERNALCALL` +Make an internal call. Push the current PC to the internal call stack and jump to the target location. + +[See in table.](#isa-table-internalcall) + +- **Opcode**: 0x1d +- **Category**: Machine State - Control Flow +- **Args**: + - **loc**: target location to jump/call to +- **Expression**: + +{`context.machineState.internalCallStack.push(context.machineState.pc) +context.machineState.pc = loc`} + +- **Details**: Target location is an immediate value (a constant in the bytecode). +- **Bit-size**: 48 + + +### `INTERNALRETURN` +Return from an internal call. Pop from the internal call stack and jump to the popped location. + +[See in table.](#isa-table-internalreturn) + +- **Opcode**: 0x1e +- **Category**: Machine State - Control Flow +- **Expression**: `context.machineState.pc = context.machineState.internalCallStack.pop()` +- **Bit-size**: 16 + +[![](/img/protocol-specs/public-vm/bit-formats/INTERNALRETURN.png)](/img/protocol-specs/public-vm/bit-formats/INTERNALRETURN.png) + +### `SET` +Set a memory word from a constant in the bytecode + +[See in table.](#isa-table-set) + +- **Opcode**: 0x1f +- **Category**: Machine State - Memory +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **inTag**: The [type/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for SET. +- **Args**: + - **const**: an N-bit constant value from the bytecode to store in memory (any type except `field`) + - **dstOffset**: memory offset specifying where to store the constant +- **Expression**: `M[dstOffset] = const` +- **Details**: Set memory word at `dstOffset` to `const`'s immediate value. `const`'s bit-size (N) can be 8, 16, 32, 64, or 128 based on `inTag`. It _cannot be 254 (`field` type)_! +- **Tag updates**: `T[dstOffset] = inTag` +- **Bit-size**: 64+N + +[![](/img/protocol-specs/public-vm/bit-formats/SET.png)](/img/protocol-specs/public-vm/bit-formats/SET.png) + +### `MOV` +Move a word from source memory location to destination + +[See in table.](#isa-table-mov) + +- **Opcode**: 0x20 +- **Category**: Machine State - Memory +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **srcOffset**: memory offset of word to move + - **dstOffset**: memory offset specifying where to store that word +- **Expression**: `M[dstOffset] = M[srcOffset]` +- **Tag updates**: `T[dstOffset] = T[srcOffset]` +- **Bit-size**: 88 + +[![](/img/protocol-specs/public-vm/bit-formats/MOV.png)](/img/protocol-specs/public-vm/bit-formats/MOV.png) + +### `CMOV` +Move a word (conditionally chosen) from one memory location to another (`d \= cond \> 0 ? a : b`) + +[See in table.](#isa-table-cmov) + +- **Opcode**: 0x21 +- **Category**: Machine State - Memory +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **aOffset**: memory offset of word 'a' to conditionally move + - **bOffset**: memory offset of word 'b' to conditionally move + - **condOffset**: memory offset of the operations 'conditional' input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[condOffset] > 0 ? M[aOffset] : M[bOffset]` +- **Details**: One of two source memory locations is chosen based on the condition. `T[condOffset]` is not checked because the greater-than-zero suboperation is the same regardless of type. +- **Tag updates**: `T[dstOffset] = M[condOffset] > 0 ? T[aOffset] : T[bOffset]` +- **Bit-size**: 152 + +[![](/img/protocol-specs/public-vm/bit-formats/CMOV.png)](/img/protocol-specs/public-vm/bit-formats/CMOV.png) + +### `SLOAD` +Load a word from this contract's persistent public storage. Zero is loaded for unwritten slots. + +[See in table.](#isa-table-sload) + +- **Opcode**: 0x22 +- **Category**: World State - Public Storage +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **slotOffset**: memory offset of the storage slot to load from + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: + +{`M[dstOffset] = S[M[slotOffset]]`} + +- **Details**: + +{`// Expression is shorthand for +leafIndex = hash(context.environment.address, M[slotOffset]) +exists = context.worldState.publicStorage.has(leafIndex) // exists == previously-written +if exists: + value = context.worldState.publicStorage.get(leafIndex: leafIndex) +else: + value = 0 +M[dstOffset] = value`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.publicStorageReads.append( + TracedStorageRead { + callPointer: context.environment.callPointer, + slot: M[slotOffset], + exists: exists, // defined above + value: value, // defined above + counter: ++context.worldStateAccessTrace.accessCounter, + } +)`} + +- **Triggers downstream circuit operations**: Storage slot siloing (hash with contract address), public data tree membership check +- **Tag updates**: `T[dstOffset] = field` +- **Bit-size**: 88 + +[![](/img/protocol-specs/public-vm/bit-formats/SLOAD.png)](/img/protocol-specs/public-vm/bit-formats/SLOAD.png) + +### `SSTORE` +Write a word to this contract's persistent public storage + +[See in table.](#isa-table-sstore) + +- **Opcode**: 0x23 +- **Category**: World State - Public Storage +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **srcOffset**: memory offset of the word to store + - **slotOffset**: memory offset containing the storage slot to store to +- **Expression**: + +{`S[M[slotOffset]] = M[srcOffset]`} + +- **Details**: + +{`// Expression is shorthand for +context.worldState.publicStorage.set({ + leafIndex: hash(context.environment.address, M[slotOffset]), + leaf: M[srcOffset], +})`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.publicStorageWrites.append( + TracedStorageWrite { + callPointer: context.environment.callPointer, + slot: M[slotOffset], + value: M[srcOffset], + counter: ++context.worldStateAccessTrace.accessCounter, + } +)`} + +- **Triggers downstream circuit operations**: Storage slot siloing (hash with contract address), public data tree update +- **Bit-size**: 88 + +[![](/img/protocol-specs/public-vm/bit-formats/SSTORE.png)](/img/protocol-specs/public-vm/bit-formats/SSTORE.png) + +### `NOTEHASHEXISTS` +Check whether a note hash exists in the note hash tree (as of the start of the current block) + +[See in table.](#isa-table-notehashexists) + +- **Opcode**: 0x24 +- **Category**: World State - Notes & Nullifiers +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **noteHashOffset**: memory offset of the note hash + - **leafIndexOffset**: memory offset of the leaf index + - **existsOffset**: memory offset specifying where to store operation's result (whether the note hash leaf exists) +- **Expression**: + +{`exists = context.worldState.noteHashes.has({ + leafIndex: M[leafIndexOffset] + leaf: hash(context.environment.address, M[noteHashOffset]), +}) +M[existsOffset] = exists`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.noteHashChecks.append( + TracedNoteHashCheck { + callPointer: context.environment.callPointer, + leafIndex: M[leafIndexOffset] + noteHash: M[noteHashOffset], + exists: exists, // defined above + counter: ++context.worldStateAccessTrace.accessCounter, + } +)`} + +- **Triggers downstream circuit operations**: Note hash siloing (hash with storage contract address), note hash tree membership check +- **Tag updates**: `T[existsOffset] = u8` +- **Bit-size**: 120 + + +### `EMITNOTEHASH` +Emit a new note hash to be inserted into the note hash tree + +[See in table.](#isa-table-emitnotehash) + +- **Opcode**: 0x25 +- **Category**: World State - Notes & Nullifiers +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **noteHashOffset**: memory offset of the note hash +- **Expression**: + +{`context.worldState.noteHashes.append( + hash(context.environment.address, M[noteHashOffset]) +)`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.noteHashes.append( + TracedNoteHash { + callPointer: context.environment.callPointer, + noteHash: M[noteHashOffset], // unsiloed note hash + counter: ++context.worldStateAccessTrace.accessCounter, + } +)`} + +- **Triggers downstream circuit operations**: Note hash siloing (hash with contract address), note hash tree insertion. +- **Bit-size**: 56 + +[![](/img/protocol-specs/public-vm/bit-formats/EMITNOTEHASH.png)](/img/protocol-specs/public-vm/bit-formats/EMITNOTEHASH.png) + +### `NULLIFIEREXISTS` +Check whether a nullifier exists in the nullifier tree (including nullifiers from earlier in the current transaction or from earlier in the current block) + +[See in table.](#isa-table-nullifierexists) + +- **Opcode**: 0x26 +- **Category**: World State - Notes & Nullifiers +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **nullifierOffset**: memory offset of the unsiloed nullifier + - **addressOffset**: memory offset of the storage address + - **existsOffset**: memory offset specifying where to store operation's result (whether the nullifier exists) +- **Expression**: + +{`exists = pendingNullifiers.has(M[addressOffset], M[nullifierOffset]) || context.worldState.nullifiers.has( + hash(M[addressOffset], M[nullifierOffset]) +) +M[existsOffset] = exists`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.nullifierChecks.append( + TracedNullifierCheck { + callPointer: context.environment.callPointer, + nullifier: M[nullifierOffset], + address: M[addressOffset], + exists: exists, // defined above + counter: ++context.worldStateAccessTrace.accessCounter, + } +)`} + +- **Triggers downstream circuit operations**: Nullifier siloing (hash with storage contract address), nullifier tree membership check +- **Tag updates**: `T[existsOffset] = u8` +- **Bit-size**: 120 + + +### `EMITNULLIFIER` +Emit a new nullifier to be inserted into the nullifier tree + +[See in table.](#isa-table-emitnullifier) + +- **Opcode**: 0x27 +- **Category**: World State - Notes & Nullifiers +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **nullifierOffset**: memory offset of nullifier +- **Expression**: + +{`context.worldState.nullifiers.append( + hash(context.environment.address, M[nullifierOffset]) +)`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.nullifiers.append( + TracedNullifier { + callPointer: context.environment.callPointer, + nullifier: M[nullifierOffset], // unsiloed nullifier + counter: ++context.worldStateAccessTrace.accessCounter, + } +)`} + +- **Triggers downstream circuit operations**: Nullifier siloing (hash with contract address), nullifier tree non-membership-check and insertion. +- **Bit-size**: 56 + +[![](/img/protocol-specs/public-vm/bit-formats/EMITNULLIFIER.png)](/img/protocol-specs/public-vm/bit-formats/EMITNULLIFIER.png) + +### `L1TOL2MSGEXISTS` +Check if a message exists in the L1-to-L2 message tree + +[See in table.](#isa-table-l1tol2msgexists) + +- **Opcode**: 0x28 +- **Category**: World State - Messaging +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **msgHashOffset**: memory offset of the message hash + - **msgLeafIndexOffset**: memory offset of the message's leaf index in the L1-to-L2 message tree + - **existsOffset**: memory offset specifying where to store operation's result (whether the message exists in the L1-to-L2 message tree) +- **Expression**: + +{`exists = context.worldState.l1ToL2Messages.has({ + leafIndex: M[msgLeafIndexOffset], leaf: M[msgHashOffset] +}) +M[existsOffset] = exists`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.l1ToL2MessagesChecks.append( + L1ToL2Message { + callPointer: context.environment.callPointer, + leafIndex: M[msgLeafIndexOffset], + msgHash: M[msgHashOffset], + exists: exists, // defined above + } +)`} + +- **Triggers downstream circuit operations**: L1-to-L2 message tree membership check +- **Tag updates**: + +{`T[existsOffset] = u8,`} + +- **Bit-size**: 120 + + +### `GETCONTRACTINSTANCE` +Copies contract instance data to memory + +[See in table.](#isa-table-getcontractinstance) + +- **Opcode**: 0x29 +- **Category**: Other +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **addressOffset**: memory offset of the contract instance address + - **dstOffset**: location to write the contract instance information to +- **Expression**: + +{`M[dstOffset:dstOffset+CONTRACT_INSTANCE_SIZE+1] = [ + instance_found_in_address, + instance.salt ?? 0, + instance.deployer ?? 0, + instance.contractClassId ?? 0, + instance.initializationHash ?? 0, + instance.portalContractAddress ?? 0, + instance.publicKeysHash ?? 0, +]`} + +- **Additional AVM circuit checks**: TO-DO +- **Triggers downstream circuit operations**: TO-DO +- **Tag updates**: T[dstOffset:dstOffset+CONTRACT_INSTANCE_SIZE+1] = field +- **Bit-size**: 88 + + +### `EMITUNENCRYPTEDLOG` +Emit an unencrypted log + +[See in table.](#isa-table-emitunencryptedlog) + +- **Opcode**: 0x2a +- **Category**: Accrued Substate - Logging +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **logOffset**: memory offset of the data to log + - **logSizeOffset**: memory offset to number of words to log +- **Expression**: + +{`context.accruedSubstate.unencryptedLogs.append( + UnencryptedLog { + address: context.environment.address, + log: M[logOffset:logOffset+M[logSizeOffset]], + } +)`} + +- **Bit-size**: 88 + +[![](/img/protocol-specs/public-vm/bit-formats/EMITUNENCRYPTEDLOG.png)](/img/protocol-specs/public-vm/bit-formats/EMITUNENCRYPTEDLOG.png) + +### `SENDL2TOL1MSG` +Send an L2-to-L1 message + +[See in table.](#isa-table-sendl2tol1msg) + +- **Opcode**: 0x2b +- **Category**: Accrued Substate - Messaging +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **recipientOffset**: memory offset of the message recipient + - **contentOffset**: memory offset of the message content +- **Expression**: + +{`context.accruedSubstate.sentL2ToL1Messages.append( + SentL2ToL1Message { + address: context.environment.address, + recipient: M[recipientOffset], + message: M[contentOffset] + } +)`} + +- **Bit-size**: 88 + +[![](/img/protocol-specs/public-vm/bit-formats/SENDL2TOL1MSG.png)](/img/protocol-specs/public-vm/bit-formats/SENDL2TOL1MSG.png) + +### `CALL` +Call into another contract + +[See in table.](#isa-table-call) + +- **Opcode**: 0x2c +- **Category**: Control Flow - Contract Calls +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **gasOffset**: offset to two words containing `{l2GasLeft, daGasLeft}`: amount of gas to provide to the callee + - **addrOffset**: address of the contract to call + - **argsOffset**: memory offset to args (will become the callee's calldata) + - **argsSizeOffset**: memory offset for the number of words to pass via callee's calldata + - **retOffset**: destination memory offset specifying where to store the data returned from the callee + - **retSize**: number of words to copy from data returned by callee + - **successOffset**: destination memory offset specifying where to store the call's success (0: failure, 1: success) +- **Expression**: + +{`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } +chargeGas(context, + l2GasCost=M[instr.args.gasOffset], + daGasCost=M[instr.args.gasOffset+1]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall=false) +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext)`} + +- **Details**: Creates a new (nested) execution context and triggers execution within that context. + Execution proceeds in the nested context until it reaches a halt at which point + execution resumes in the current/calling context. + A non-existent contract or one with no code will return success. + ["Nested contract calls"](./nested-calls) provides a full explanation of this + instruction along with the shorthand used in the expression above. + The explanation includes details on charging gas for nested calls, + nested context derivation, world state tracing, and updating the parent context + after the nested call halts. +- **Tag checks**: `T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32` +- **Tag updates**: + +{`T[successOffset] = u8 +T[retOffset:retOffset+retSize] = field`} + +- **Bit-size**: 248 + +[![](/img/protocol-specs/public-vm/bit-formats/CALL.png)](/img/protocol-specs/public-vm/bit-formats/CALL.png) + +### `STATICCALL` +Call into another contract, disallowing World State and Accrued Substate modifications + +[See in table.](#isa-table-staticcall) + +- **Opcode**: 0x2d +- **Category**: Control Flow - Contract Calls +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **gasOffset**: offset to two words containing `{l2GasLeft, daGasLeft}`: amount of gas to provide to the callee + - **addrOffset**: address of the contract to call + - **argsOffset**: memory offset to args (will become the callee's calldata) + - **argsSizeOffset**: memory offset for the number of words to pass via callee's calldata + - **retOffset**: destination memory offset specifying where to store the data returned from the callee + - **retSize**: number of words to copy from data returned by callee + - **successOffset**: destination memory offset specifying where to store the call's success (0: failure, 1: success) +- **Expression**: + +{`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } +chargeGas(context, + l2GasCost=M[instr.args.gasOffset], + daGasCost=M[instr.args.gasOffset+1]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall=true +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext)`} + +- **Details**: Same as `CALL`, but disallows World State and Accrued Substate modifications. + ["Nested contract calls"](./nested-calls) provides a full explanation of this + instruction along with the shorthand used in the expression above. + The explanation includes details on charging gas for nested calls, + nested context derivation, world state tracing, and updating the parent context + after the nested call halts. +- **Tag checks**: `T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32` +- **Tag updates**: + +{`T[successOffset] = u8 +T[retOffset:retOffset+retSize] = field`} + +- **Bit-size**: 248 + +[![](/img/protocol-specs/public-vm/bit-formats/STATICCALL.png)](/img/protocol-specs/public-vm/bit-formats/STATICCALL.png) + +### `RETURN` +Halt execution within this context (without revert), optionally returning some data + +[See in table.](#isa-table-return) + +- **Opcode**: 0x2e +- **Category**: Control Flow - Contract Calls +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **retOffset**: memory offset of first word to return + - **retSize**: number of words to return +- **Expression**: + +{`context.contractCallResults.output = M[retOffset:retOffset+retSize] +halt`} + +- **Details**: Return control flow to the calling context/contract. Caller will accept World State and Accrued Substate modifications. See ["Halting"](./execution#halting) to learn more. See ["Nested contract calls"](./nested-calls) to see how the caller updates its context after the nested call halts. +- **Bit-size**: 88 + +[![](/img/protocol-specs/public-vm/bit-formats/RETURN.png)](/img/protocol-specs/public-vm/bit-formats/RETURN.png) + +### `REVERT` +Halt execution within this context as `reverted`, optionally returning some data + +[See in table.](#isa-table-revert) + +- **Opcode**: 0x2f +- **Category**: Control Flow - Contract Calls +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **retOffset**: memory offset of first word to return + - **retSize**: number of words to return +- **Expression**: + +{`context.contractCallResults.output = M[retOffset:retOffset+retSize] +context.contractCallResults.reverted = true +halt`} + +- **Details**: Return control flow to the calling context/contract. Caller will reject World State and Accrued Substate modifications. See ["Halting"](./execution#halting) to learn more. See ["Nested contract calls"](./nested-calls) to see how the caller updates its context after the nested call halts. +- **Bit-size**: 88 + +[![](/img/protocol-specs/public-vm/bit-formats/REVERT.png)](/img/protocol-specs/public-vm/bit-formats/REVERT.png) + +### `TORADIXLE` +Convert a word to an array of limbs in little-endian radix form + +[See in table.](#isa-table-to_radix_le) + +- **Opcode**: 0x30 +- **Category**: Conversions +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **srcOffset**: memory offset of word to convert. + - **dstOffset**: memory offset specifying where the first limb of the radix-conversion result is stored. + - **radix**: the maximum bit-size of each limb. + - **numLimbs**: the number of limbs the word will be converted into. +- **Expression**: TBD: Storage of limbs and if T[dstOffset] is constrained to U8 +- **Details**: The limbs will be stored in a contiguous memory block starting at `dstOffset`. +- **Tag checks**: `T[srcOffset] == field` +- **Bit-size**: 152 + diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/index.md new file mode 100644 index 000000000000..98fa1b94edaa --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/index.md @@ -0,0 +1,3 @@ +# Aztec (Public) Virtual Machine + +The Aztec Virtual Machine (AVM) executes the public section of a transaction. \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/instruction-set.mdx b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/instruction-set.mdx new file mode 100644 index 000000000000..b6ee8e5d8ba9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/instruction-set.mdx @@ -0,0 +1,17 @@ +import GeneratedInstructionSet from "./gen/_instruction-set.mdx"; + +# Instruction Set + +This page lists all of the instructions supported by the Aztec Virtual Machine (AVM). + +The following notes are relevant to the table and sections below: + +- `M[offset]` notation is shorthand for `context.machineState.memory[offset]` +- `S[slot]` notation is shorthand for an access to the specified slot in the current contract's public storage (`context.worldState.publicStorage`) after the slot has been siloed by the contract address (`hash(context.environment.address, slot)`) +- Any instruction whose description does not mention a program counter change simply increments it: `context.machineState.pc++` +- All instructions update `context.machineState.*GasLeft` as detailed in ["Gas limits and tracking"](./execution#gas-checks-and-tracking) +- Any instruction can lead to an exceptional halt as specified in ["Exceptional halting"](./execution#exceptional-halting) +- The term `hash` used in expressions below represents a Poseidon hash operation. +- Type structures used in world state tracing operations are defined in ["Type Definitions"](./type-structs) + + diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/intro.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/intro.md new file mode 100644 index 000000000000..94bf7d5d0dae --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/intro.md @@ -0,0 +1,45 @@ +# Introduction + +:::note reference +Many terms and definitions are borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). +::: + +An Aztec transaction may include one or more **public execution requests**. A public execution request is a request to execute a specified contract's public bytecode given some arguments. Execution of a contract's public bytecode is performed by the **Aztec Virtual Machine (AVM)**. + +> A public execution request may originate from a public call enqueued by a transaction's private segment ([`enqueuedPublicFunctionCalls`](../calls/enqueued-calls.md)), or from a public [fee preparation](../gas-and-fees#fee-preparation) or [fee distribution](../gas-and-fees#fee-distribution) call. + +In order to execute public contract bytecode, the AVM requires some context. An [**execution context**](./context) contains all information necessary to initiate AVM execution, including the relevant contract's bytecode and all state maintained by the AVM. A **contract call** initializes an execution context and triggers AVM execution within that context. + +Instruction-by-instruction, the AVM [executes](./execution) the bytecode specified in its context. An **instruction** is a decoded bytecode entry that, when executed, modifies the AVM's execution context (in particular its [state](./state)) according to the instruction's definition in the ["AVM Instruction Set"](./instruction-set). Execution within a context ends when the AVM encounters a [**halt**](./execution#halting). + +During execution, additional contract calls may be made. While an [**initial contract call**](./context#initial-contract-calls) initializes a new execution context directly from a public execution request, a [**nested contract call**](./nested-calls) occurs _during_ AVM execution and is triggered by a **contract call instruction** ([`CALL`](./instruction-set#isa-section-call), or [`STATICCALL`](./instruction-set#isa-section-staticcall)). It initializes a new execution context (**nested context**) from the current one (**calling context**) and triggers execution within it. When nested call's execution completes, execution proceeds in the calling context. + +A **caller** is a contract call's initiator. The caller of an initial contract call is an Aztec sequencer. The caller of a nested contract call is the AVM itself executing in the calling context. + +## Outline + +- [**State**](./state): the state maintained by the AVM +- [**Memory model**](./memory-model): the AVM's type-tagged memory model +- [**Execution context**](./context): the AVM's execution context and its initialization for initial contract calls +- [**Execution**](#execution): control flow, gas tracking, normal halting, and exceptional halting +- [**Nested contract calls**](./nested-calls): the initiation of a contract call from an instruction as well as the processing of nested execution results, gas refunds, and state reverts +- [**Instruction set**](./instruction-set): the list of all instructions supported by the AVM +- [**AVM Circuit**](./avm-circuit): the AVM as a SNARK circuit for proving execution + +> The sections prior to the "AVM Circuit" are meant to provide a high-level definition of the Aztec Virtual Machine as opposed to a specification of its SNARK implementation. They therefore mostly omit SNARK or circuit-centric verbiage except when particularly relevant to the high-level architecture. + +> For an explanation of the AVM's bytecode, refer to ["AVM Bytecode"](../bytecode#avm-bytecode). + +## Public contract bytecode + + + +A contract's public bytecode is a series of execution instructions for the AVM. Refer to the ["AVM Instruction Set"](./instruction-set) for the details of all supported instructions along with how they modify AVM state. + +The entirety of a contract's public code is represented as a single block of bytecode with a maximum of `MAX_PUBLIC_INSTRUCTIONS_PER_CONTRACT` ($2^{15} = 32768$) instructions. The mechanism used to distinguish between different "functions" in an AVM bytecode program is left as a higher-level abstraction (_e.g._ similar to Solidity's concept of a function selector). + +::: warning +Ultimately, function selectors _may_ be removed as an enshrined protocol mechanism as described above. For now, each public function on a contract has a distinct bytecode that can be selected for execution via a function selector. +::: + +> See the [Bytecode Validation Circuit](./bytecode-validation-circuit) to see how a contract's bytecode can be validated and committed to. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/memory-model.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/memory-model.md new file mode 100644 index 000000000000..1fea8c669856 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/memory-model.md @@ -0,0 +1,182 @@ +# Memory Model + +This section describes the AVM memory model, and in particular specifies "internal" VM abstractions that can be mapped to the VM's circuit architecture. + +## A memory-only state model + +The AVM possesses three distinct data regions, accessed via distinct VM instructions: memory, calldata and returndata + +![](/img/protocol-specs/public-vm/memory.png) + +All data regions are linear blocks of memory where each memory cell stores a finite field element. + +#### Main Memory + +Main memory stores the internal state of the current program being executed. +Can be written to as well as read. + +The main memory region stores [_type tags_](#types-and-tagged-memory) alongside data values. + +#### Calldata + +Read-only data structure that stores the input data when executing a public function. + +#### Returndata + +When a function is called from within the public VM, the return parameters of the called function are present in returndata. + +### Registers (and their absence in the AVM) + +The AVM does not have external registers. i.e. a register that holds a persistent value that can be operated on from one opcode to the next. + +For example, in the x86 architecture, there exist 8 registers (%rax, %rbx etc). Instructions can operate either directly on register values (e.g. `add %rax %rbx`) or on values in memory that the register values point to (e.g. `add (%rax) (%rbx)`). + +> The AVM does not support registers as this would require each register to exist as a column in the VM execution trace. "registers" can be implemented as a higher-level abstraction by a compiler producing AVM bytecode, by reserving fixed regions of memory to represent registers. + +### Memory addressing mode + +In the AVM, an instruction operand `X` can refer to one of three quantities: + +1. A literal value `X` +2. A memory address `M[X]` +3. An indirect memory address `M[M[X]]` + +Indirect memory addressing is required in order to support read/writes into dynamically-sized data structures (the address parameter `X` is part of the program bytecode, which is insufficient to describe the location in memory of a dynamically-sized data structure). + +Memory addresses must be tagged to be a `u32` type. + +## Types and Tagged Memory + +### Terminology/legend + +- `M[X]`: main memory cell at offset `X` +- `tag`: a value referring to a memory cell's type (its maximum potential value) +- `T[X]`: the tag associated with memory cell at offset `X` +- `inTag`: an instruction's tag to check input operands against. Present for many but not all instructions. +- `dstTag`: the target type of a `CAST` instruction, also used to tag the destination memory cell +- `ADD`: shorthand for an `ADD` instruction with `inTag = X` +- `ADD aOffset bOffset dstOffset`: an full `ADD` instruction with `inTag = X`. See [here](./instruction-set#isa-section-add) for more details. +- `CAST`: a `CAST` instruction with `dstTag`: `X`. `CAST` is the only instruction with a `dstTag`. See [here](./instruction-set#isa-section-cast) for more details. + +### Tags and tagged memory + +A `tag` refers to the maximum potential value of a cell of main memory. The following tags are supported: + +| tag value | maximum memory cell value | shorthand | +| --------- | ------------------------- | ------------- | +| 0 | 0 | uninitialized | +| 1 | $2^8 - 1$ | `u8` | +| 2 | $2^{16} - 1$ | `u16` | +| 3 | $2^{32} - 1$ | `u32` | +| 4 | $2^{64} - 1$ | `u64` | +| 5 | $2^{128} - 1$ | `u128` | +| 6 | $p - 1$ | `field` | +| 7 | reserved | reserved | + +> Note: $p$ describes the modulus of the finite field that the AVM circuit is defined over (i.e. number of points on the BN254 curve). +> Note: `u32` is used for offsets into the VM's 32-bit addressable main memory + +The purpose of a tag is to inform the VM of the maximum possible length of an operand value that has been loaded from memory. + +#### Checking input operand tags + +Many AVM instructions explicitly operate over range-constrained input parameters (e.g. `ADD`). The maximum allowable value for an instruction's input parameters is defined via an `inTag` (instruction/input tag). Two potential scenarios result: + +1. A VM instruction's tag value matches the input parameter tag values +2. A VM instruction's tag value does _not_ match the input parameter tag values + +If case 2 is triggered, an error flag is raised and the current call's execution reverts. + +#### Writing into memory + +It is required that all VM instructions that write into main memory explicitly define the tag of the destination value and ensure the value is appropriately constrained to be consistent with the assigned tag. You can see an instruction's "**Tag updates**" in its section of the instruction set document (see [here for `ADD`](./instruction-set#isa-section-add) and [here for `CAST`](./instruction-set#isa-section-cast)). + +#### Standard tagging example: `ADD` + +``` +# ADD aOffset bOffset dstOffset +assert T[aOffset] == T[bOffset] == u32 // check inputs against inTag, revert on mismatch +T[dstOffset] = u32 // tag destination with inTag +M[dstOffset] = M[aOffset] + M[bOffset] // perform the addition +``` + +#### `MOV` and tag preservation + +The `MOV` instruction copies data from one memory cell to another, preserving tags. In other words, the destination cell's tag will adopt the value of the source: + +``` +# MOV srcOffset dstOffset +T[dstOffset] = T[srcOffset] // preserve tag +M[dstOffset] = M[srcOffset] // perform the move +``` + +Note that `MOV` does not have an `inTag` and therefore does not need to make any assertions regarding the source memory cell's type. + +#### `CAST` and tag conversions + +The only VM instruction that can be used to cast between tags is `CAST`. Two potential scenarios result: + +1. The destination tag describes a maximum value that is _less than_ the source tag +2. The destination tag describes a maximum value that is _greater than or equal to_ the source tag + +For Case 1, range constraints must be applied to ensure the destination value is consistent with the source value after tag truncations have been applied. + +Case 2 is trivial as no additional consistency checks must be performed between source and destination values. + +``` +# CAST srcOffset dstOffset +T[dstOffset] = u64 // tag destination with dstTag +M[dstOffset] = cast(M[srcOffset]) // perform cast +``` + +#### Indirect `MOV` and extra tag checks + +A `MOV` instruction may flag its source and/or destination offsets as "indirect". An indirect memory access performs `M[M[offset]]` instead of the standard `M[offset]`. Memory offsets must be `u32`s since main memory is a 32-bit addressable space, and so indirect memory accesses include additional checks. + +Additional checks for a `MOV` with an indirect source offset: + +``` +# MOV srcOffset dstOffset // with indirect source +assert T[srcOffset] == u32 // enforce that `M[srcOffset]` is itself a valid memory offset +T[dstOffset] = T[T[srcOffset]] // tag destination to match indirect source tag +M[dstOffset] = M[M[srcOffset]] // perform move from indirect source +``` + +Additional checks for a `MOV` with an indirect destination offset: + +``` +# MOV srcOffset dstOffset // with indirect destination +assert T[dstOffset] == u32 // enforce that `M[dstOffset]` is itself a valid memory offset +T[T[dstOffset]] = T[srcOffset] // tag indirect destination to match source tag +M[M[dstOffset]] = M[srcOffset] // perform move to indirect destination +``` + +Additional checks for a `MOV` with both indirect source and destination offsets: + +``` +# MOV srcOffset dstOffset // with indirect source and destination +assert T[srcOffset] == T[dstOffset] == u32 // enforce that `M[*Offset]` are valid memory offsets +T[T[dstOffset]] = T[T[srcOffset]] // tag indirect destination to match indirect source tag +M[M[dstOffset]] = M[M[srcOffset]] // perform move to indirect destination +``` + +#### Calldata/returndata and tag conversions + +All elements in calldata/returndata are implicitly tagged as field elements (i.e. maximum value is $p - 1$). To perform a tag conversion, calldata/returndata must be copied into main memory (via [`CALLDATACOPY`](./instruction-set#isa-section-calldatacopy) or [`RETURN`'s `offset` and `size`](./instruction-set#isa-section-return)), followed by an appropriate `CAST` instruction. + +``` +# Copy calldata to memory and cast a word to u64 +CALLDATACOPY cdOffset size offsetA // copy calldata to memory at offsetA +CAST offsetA dstOffset // cast first copied word to a u64 +``` + +This would perform the following: + +``` +# CALLDATACOPY cdOffset size offsetA +T[offsetA:offsetA+size] = field // CALLDATACOPY assigns the field tag +M[offsetA:offsetA+size] = calldata[cdOffset:cdOffset+size] // copy calldata to memory +# CAST offsetA dstOffset +T[offsetA] = u64 // CAST assigns a new tag +M[dstOffset] = cast(offsetA) // perform the cast operation +``` diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/nested-calls.mdx b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/nested-calls.mdx new file mode 100644 index 000000000000..6c8f6c42b3bb --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/nested-calls.mdx @@ -0,0 +1,145 @@ +# Nested Contract Calls + +A **nested contract call** occurs _during_ AVM execution and is triggered by a **contract call instruction**. The AVM [instruction set](./instruction-set) includes three contract call instructions: [`CALL`](./instruction-set#isa-section-call), and [`STATICCALL`](./instruction-set#isa-section-staticcall). + +A nested contract call performs the following operations: + +1. [Charge gas](#gas-cost-of-call-instruction) for the nested call +1. [Trace the nested contract call](#tracing-nested-contract-calls) +1. [Derive the **nested context**](#context-initialization-for-nested-calls) from the calling context and the call instruction +1. Initiate [AVM execution](./execution) within the nested context until a halt is reached +1. [Update the **calling context**](#updating-the-calling-context-after-nested-call-halts) after the nested call halts + +Or, in pseudocode: + +```jsx +// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } + +isStaticCall = instr.opcode == STATICCALL; +l2GasCost = min(M[instr.args.gasOffset], context.machineState.l2GasLeft); +daGasCost = min(M[instr.args.gasOffset + 1], context.machineState.daGasLeft); + +chargeGas(context, l2GasCost, daGasCost); +traceNestedCall(context, instr.args.addrOffset); +nestedContext = deriveContext(context, instr.args, isStaticCall); +execute(nestedContext); +updateContextAfterNestedCall(context, instr.args, nestedContext); +``` + +These call instructions share the same argument definitions: `gasOffset`, `addrOffset`, `argsOffset`, `argsSize`, `retOffset`, `retSize`, and `successOffset` (defined in the [instruction set](./instruction-set)). These arguments will be referred to via those keywords below, and will often be used in conjunction with the `M[offset]` syntax which is shorthand for `context.machineState.memory[offset]`. + +## Tracing nested contract calls + +Before nested execution begins, the contract call is traced. + +```jsx +traceNestedCall(context, addrOffset) +// which is shorthand for +context.worldStateAccessTrace.contractCalls.append( + TracedContractCall { + callPointer: context.worldStateAccessTrace.contractCalls.length + 1, + address: M[addrOffset], + counter: ++context.worldStateAccessTrace.accessCounter, + endLifetime: 0, // The call's end-lifetime will be updated later if it or its caller reverts + } +) +``` + +## Context initialization for nested calls + +import NestedContext from "./_nested-context.md"; + + + +## Gas cost of call instruction + +A call instruction's gas cost is derived from its `gasOffset` argument. In other words, the caller "allocates" gas for a nested call via its `gasOffset` argument. + +As with all instructions, gas is checked and cost is deducted _prior_ to the instruction's execution. + +```jsx +chargeGas(context, l2GasCost, daGasCost); +``` + +> The shorthand `chargeGas` is defined in ["Gas checks and tracking"](./execution#gas-checks-and-tracking). + +As with all instructions, gas is checked and cost is deducted _prior_ to the instruction's execution. + +```jsx +assert context.machineState.l2GasLeft - l2GasCost >= 0 +assert context.machineState.daGasLeft - daGasCost >= 0 +context.l2GasLeft -= l2GasCost +context.daGasLeft -= daGasCost +``` + +When the nested call halts, it may not have used up its entire gas allocation. Any unused gas is refunded to the caller as expanded on in ["Updating the calling context after nested call halts"](#updating-the-calling-context-after-nested-call-halts). + +## Nested execution + +Once the nested call's context is initialized, execution within that context begins. + +```jsx +execute(nestedContext); +``` + +Execution (and the `execution` shorthand above) is detailed in ["Execution, Gas, Halting"](./execution). Note that execution mutates the nested context. + +## Updating the calling context after nested call halts + +After the nested call halts, the calling context is updated. The call's success is extracted, unused gas is refunded, output data can be copied to the caller's memory, world state and accrued substate are conditionally accepted, and the world state trace is updated. The following shorthand is used to refer to this process in the ["Instruction Set"](./instruction-set): + +```jsx +updateContextAfterNestedCall(context, instr.args, nestedContext); +``` + +The caller checks whether the nested call succeeded, and places the answer in memory. + +```jsx +context.machineState.memory[instr.args.successOffset] = + !nestedContext.results.reverted; +``` + +Any unused gas is refunded to the caller. + +```jsx +context.l2GasLeft += nestedContext.machineState.l2GasLeft; +context.daGasLeft += nestedContext.machineState.daGasLeft; +``` + +If the call instruction specifies non-zero `retSize`, the caller copies any returned output data to its memory. + +```jsx +if retSize > 0: + context.machineState.memory[retOffset:retOffset+retSize] = nestedContext.results.output +``` + +If the nested call succeeded, the caller accepts its world state and accrued substate modifications. + +```jsx +if !nestedContext.results.reverted: + context.worldState = nestedContext.worldState + context.accruedSubstate.append(nestedContext.accruedSubstate) +``` + +### Accepting nested call's World State access trace + +If the nested call reverted, the caller initializes the "end-lifetime" of all world state accesses made within the nested call. + +```jsx +if nestedContext.results.reverted: + // process all traces (this is shorthand) + for trace in nestedContext.worldStateAccessTrace: + for access in trace: + if access.callPointer >= nestedContext.environment.callPointer: + // don't override end-lifetime already set by a deeper nested call + if access.endLifetime == 0: + access.endLifetime = nestedContext.worldStateAccessTrace.accessCounter +``` + +> A world state access that was made in a deeper nested _reverted_ context will already have its end-lifetime initialized. The caller does _not_ overwrite this access' end-lifetime here as it already has a narrower lifetime. + +Regardless of whether the nested call reverted, the caller accepts its updated world state access trace (with updated end-lifetimes). + +```jsx +context.worldStateAccessTrace = nestedContext.worldStateAccessTrace; +``` diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/security.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/security.md new file mode 100644 index 000000000000..78199f872e47 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/security.md @@ -0,0 +1,4 @@ +# VM threat model, security requirements + +An honest Prover must always be able to construct a satisfiable proof for an AVM program, even if the program throws an error. +This implies constraints produced by the AVM **must** be satisfiable. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/state.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/state.md new file mode 100644 index 000000000000..9cb585bb92fb --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/state.md @@ -0,0 +1,103 @@ +# State + +This section describes the types of state maintained by the AVM. + +## Machine State + +**Machine state** is transformed on an instruction-per-instruction basis. Each execution context has its own machine state. + +### _MachineState_ + +| Field | Type | Description | +| ------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `l2GasLeft` | `field` | Tracks the amount of L2 gas remaining at any point during execution. Initialized from contract call arguments. | +| `daGasLeft` | `field` | Tracks the amount of DA gas remaining at any point during execution. Initialized from contract call arguments. | +| `pc` | `field` | Index into the contract's bytecode indicating which instruction to execute. Initialized to 0 during context initialization. | +| `internalCallStack` | `Vector` | A stack of program counters pushed to and popped from by `INTERNALCALL` and `INTERNALRETURN` instructions. Initialized as empty during context initialization. | +| `memory` | `[field; 2^32]` | A $2^{32}$ entry memory space accessible by user code (AVM instructions). All $2^{32}$ entries are assigned default value 0 during context initialization. See ["Memory Model"](./memory-model) for a complete description of AVM memory. | + + + +## World State + +### AVM's access to Aztec State + +[Aztec's global state](../state) is implemented as a few merkle trees. These trees are exposed to the AVM as follows: + +| State | Tree | Merkle Tree Type | AVM Access | +| ----------------- | --------------------- | ---------------- | ------------------------------------------- | +| Public Storage | Public Data Tree | Updatable | membership-checks (latest), reads, writes | +| Note Hashes | Note Hash Tree | Append-only | membership-checks (start-of-block), appends | +| Nullifiers | Nullifier Tree | Indexed | membership-checks (latest), appends | +| L1-to-L2 Messages | L1-to-L2 Message Tree | Append-only | membership-checks (start-of-block) | +| Headers | Archive Tree | Append-only | membership-checks, leaf-preimage-reads | +| Contracts\* | - | - | - | + +> \* As described in ["Contract Deployment"](../contract-deployment), contracts are not stored in a dedicated tree. A [contract class](../contract-deployment/classes) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractClass` structure (which contains the bytecode) and a nullifier representing the class identifier. A [contract instance](../contract-deployment/instances) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractInstance` structure and a nullifier representing the contract address. + +### AVM World State + +The AVM does not directly modify Aztec state. Instead, it stages modifications to be applied later pending successful execution. As part of each execution context, the AVM maintains **world state** which is a representation of Aztec state that includes _staged_ modifications. + +As the AVM executes contract code, instructions may read or modify the world state within the current context. If execution within a particular context reverts, staged world state modifications are rejected by the caller. If execution succeeds, staged world state modifications are accepted. + +#### _AvmWorldState_ + +The following table defines an AVM context's world state interface: + +| Field | AVM Instructions & Access | +| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `contracts` | [`*CALL`](./instruction-set#isa-section-call) (special case, see below\*) | +| `publicStorage` | [`SLOAD`](./instruction-set#isa-section-sload) (membership-checks (latest) & reads), [`SSTORE`](./instruction-set#isa-section-sstore) (writes) | +| `noteHashes` | [`NOTEHASHEXISTS`](./instruction-set#isa-section-notehashexists) (membership-checks (start-of-block)), [`EMITNOTEHASH`](./instruction-set#isa-section-emitnotehash) (appends) | +| `nullifiers` | [`NULLIFIERSEXISTS`](./instruction-set#isa-section-nullifierexists) membership-checks (latest), [`EMITNULLIFIER`](./instruction-set#isa-section-emitnullifier) (appends) | +| `l1ToL2Messages` | [`L1TOL2MSGEXISTS`](./instruction-set#isa-section-l1tol2msgexists) (membership-checks (start-of-block)) | + +> \* `*CALL` is short for `CALL`/`STATICCALL`. + +> \* For the purpose of the AVM, the world state's `contracts` member is readable for [bytecode fetching](./execution#bytecode-fetch-and-decode), and it is effectively updated when a new contract class or instance is created (along with a nullifier for the contract class identifier or contract address). + +### World State Access Trace + +**The circuit implementation of the AVM does _not_ prove that its world state accesses are valid and properly sequenced**, and does not perform actual tree updates. Thus, _all_ world state accesses, **regardless of whether they are rejected due to a revert**, must be traced and eventually handed off to downstream circuits (public kernel and rollup circuits) for comprehensive validation and tree updates. + +This trace of an AVM session's contract calls and world state accesses is named the **world state access trace**. + +> The world state access trace is also important for enforcing limitations on the maximum number of allowable world state accesses. + +#### _WorldStateAccessTrace_ + +Each entry in the world state access trace is listed below along with its type and the instructions that append to it: + +| Field | Relevant State | Type | Instructions | +| --------------------- | ----------------- | ---------------------------------- | ------------------------------------------------------------------- | +| `accessCounter` | all state | `field` | incremented by all instructions below | +| `contractCalls` | Contracts | `Vector` | [`*CALL`](./instruction-set#isa-section-call) | +| `publicStorageReads` | Public Storage | `Vector` | [`SLOAD`](./instruction-set#isa-section-sload) | +| `publicStorageWrites` | Public Storage | `Vector` | [`SSTORE`](./instruction-set#isa-section-sstore) | +| `noteHashChecks` | Note Hashes | `Vector` | [`NOTEHASHEXISTS`](./instruction-set#isa-section-notehashexists) | +| `noteHashes` | Note Hashes | `Vector` | [`EMITNOTEHASH`](./instruction-set#isa-section-emitnotehash) | +| `nullifierChecks` | Nullifiers | `Vector` | [`NULLIFIERSEXISTS`](./instruction-set#isa-section-nullifierexists) | +| `nullifiers` | Nullifiers | `Vector` | [`EMITNULLIFIER`](./instruction-set#isa-section-emitnullifier) | +| `l1ToL2MessageChecks` | L1-To-L2 Messages | `Vector` | [`L1TOL2MSGEXISTS`](./instruction-set#isa-section-l1tol2msgexists) | + +> The types tracked in these trace vectors are defined [here](./type-structs). + +> `*CALL` is short for `CALL`/`STATICCALL`. + +> Aztec tree operations like membership checks, appends, or leaf updates are performed in-circuit by downstream circuits (public kernel and rollup circuits), _after_ AVM execution. The world state access trace is a list of requests made by the AVM for later circuits to perform. + +## Accrued Substate + +> The term "accrued substate" is borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). + +**Accrued substate** is accrued throughout a context's execution, but updates to it are strictly never relevant to subsequent instructions, contract calls, or transactions. An execution context is always initialized with empty accrued substate. Its vectors are append-only, and the instructions listed below append to these vectors. If a contract call's execution succeeds, its accrued substate is appended to the caller's. If a contract's execution reverts, its accrued substate is ignored. + +#### _AccruedSubstate_ + +| Field | Type | Instructions | +| -------------------- | --------------------------- | ------------------------------------------------------------------------ | +| `unencryptedLogs` | `Vector` | [`EMITUNENCRYPTEDLOG`](./instruction-set#isa-section-emitunencryptedlog) | +| `sentL2ToL1Messages` | `Vector` | [`SENDL1TOL2MSG`](./instruction-set#isa-section-sendl2tol1msg) | + +> The types tracked in these vectors are defined [here](./type-structs). diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/type-structs.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/type-structs.md new file mode 100644 index 000000000000..9c53f4140f4a --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/public-vm/type-structs.md @@ -0,0 +1,106 @@ +# Type Definitions + +This section lists type definitions relevant to AVM State and Circuit I/O. + +#### _TracedContractCall_ + +| Field | Type | Description | +| ------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `callPointer` | `field` | The call pointer assigned to this call. | +| `address` | `field` | The called contract address. | +| `counter` | `field` | When did this occur relative to other world state accesses. | +| `endLifetime` | `field` | End lifetime of a call. Final `accessCounter` for reverted calls, `endLifetime` of parent for successful calls. Successful initial/top-level calls have infinite (max-value) `endLifetime`. | + +#### _TracedL1ToL2MessageCheck_ + +| Field | Type | Description | +| ------------- | ------- | ----------------------------------------------------------------------------------------------- | +| `callPointer` | `field` | Associates this item with a `TracedContractCall` entry in `worldStateAccessTrace.contractCalls` | +| `leafIndex` | `field` | | +| `msgHash` | `field` | The message hash which is also the tree leaf value. | +| `exists` | `field` | | +| `endLifetime` | `field` | Equivalent to `endLifetime` of the containing contract call. | + +#### _TracedStorageRead_ + +| Field | Type | Description | +| ------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `callPointer` | `field` | Associates this item with a `TracedContractCall` entry in `worldStateAccessTrace.contractCalls` | +| `slot` | `field` | | +| `exists` | `field` | Whether this slot has ever been previously written | +| `value` | `field` | | +| `counter` | `field` | | +| `endLifetime` | `field` | Equivalent to `endLifetime` of the containing contract call. The last `counter` at which this read/write should be considered to "exist" if this call or a parent reverted. | + +#### _TracedStorageWrite_ + +| Field | Type | Description | +| ------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `callPointer` | `field` | Associates this item with a `TracedContractCall` entry in `worldStateAccessTrace.contractCalls` | +| `slot` | `field` | | +| `value` | `field` | | +| `counter` | `field` | | +| `endLifetime` | `field` | Equivalent to `endLifetime` of the containing contract call. The last `counter` at which this read/write should be considered to "exist" if this call or a parent reverted. | + +#### _TracedNoteHashCheck_ + +| Field | Type | Description | +| ------------- | ------- | ----------------------------------------------------------------------------------------------- | +| `callPointer` | `field` | Associates this item with a `TracedContractCall` entry in `worldStateAccessTrace.contractCalls` | +| `leafIndex` | `field` | | +| `noteHash` | `field` | unsiloed | +| `exists` | `field` | | +| `counter` | `field` | | +| `endLifetime` | `field` | Equivalent to `endLifetime` of the containing contract call. | + +#### _TracedNoteHash_ + +| Field | Type | Description | +| ------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `callPointer` | `field` | Associates this item with a `TracedContractCall` entry in `worldStateAccessTrace.contractCalls` | +| `noteHash` | `field` | | +| `counter` | `field` | | +| `endLifetime` | `field` | Equivalent to `endLifetime` of the containing contract call. The last `counter` at which this object should be considered to "exist" if this call or a parent reverted. | + +> Note: `value` here is not siloed by contract address nor is it made unique with a nonce. Note hashes are siloed and made unique by the public kernel. + +#### _TracedNullifierCheck_ + +| Field | Type | Description | +| ------------- | ------- | ----------------------------------------------------------------------------------------------- | +| `callPointer` | `field` | Associates this item with a `TracedContractCall` entry in `worldStateAccessTrace.contractCalls` | +| `nullifier` | `field` | unsiloed | +| `exists` | `field` | | +| `counter` | `field` | | +| `endLifetime` | `field` | Equivalent to `endLifetime` of the containing contract call. | + +#### _TracedNullifier_ + +| Field | Type | Description | +| ------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `callPointer` | `field` | Associates this item with a `TracedContractCall` entry in `worldStateAccessTrace.contractCalls` | +| `nullifier` | `field` | | +| `counter` | `field` | | +| `endLifetime` | `field` | Equivalent to `endLifetime` of the containing contract call. The last `counter` at which this object should be considered to "exist" if this call or a parent reverted. | + +#### _TracedArchiveLeafCheck_ + +| Field | Type | Description | +| ----------- | ------- | ----------- | +| `leafIndex` | `field` | | +| `leaf` | `field` | | + +#### _UnencryptedLog_ + +| Field | Type | Description | +| --------- | ------------------------------------- | -------------------------------------- | +| `address` | `AztecAddress` | Contract address that emitted the log. | +| `log` | `[field; MAX_UNENCRYPTED_LOG_LENGTH]` | | + +#### _SentL2ToL1Message_ + +| Field | Type | Description | +| ----------- | -------------- | --------------------------------------------- | +| `address` | `AztecAddress` | L2 contract address that emitted the message. | +| `recipient` | `EthAddress` | L1 contract address to send the message to. | +| `content` | `field` | Message content. | diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/base-rollup.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/base-rollup.md new file mode 100644 index 000000000000..d93e923df5ea --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/base-rollup.md @@ -0,0 +1,341 @@ +--- +title: Base Rollup +--- + +The base rollup circuit is the most complex of the rollup circuits, as it has to interpret the output data of a kernel proof and perform the state updates and transaction validation. While this makes the data structures complex to follow, the goal of the circuit is fairly straight forward: + +Take `BaseRollupInputs` as an input value, and transform it to `BaseOrMergeRollupPublicInputs` as an output value while making sure that the validity conditions are met. + +```mermaid +graph LR +A[BaseRollupInputs] --> C[BaseRollupCircuit] --> B[BaseOrMergeRollupPublicInputs] +``` + +## Overview + +Below is a subset of the figure from [earlier](./index.md) (granted, not much is removed). The figure shows the data structures related to the Base Rollup circuit. + +```mermaid +classDiagram +direction TB + + +class PartialStateReference { + note_hash_tree: Snapshot + nullifier_tree: Snapshot + contract_tree: Snapshot + public_data_tree: Snapshot +} + +class StateReference { + l1_to_l2_message_tree: Snapshot + partial: PartialStateReference +} +StateReference *-- PartialStateReference: partial + +class GlobalVariables { + block_number: Fr + timestamp: Fr + version: Fr + chain_id: Fr + coinbase: EthAddress + fee_recipient: Address + gas_fees.fees_per_da_gas: Fr + gas_fees.fees_per_l2_gas: Fr +} + +class ContentCommitment { + tx_tree_height: Fr + txs_hash: Fr[2] + in_hash: Fr[2] + out_hash: Fr[2] +} + +class Header { + last_archive: Snapshot + content_commitment: ContentCommitment + state: StateReference + global_variables: GlobalVariables + total_fees: Fr +} +Header *.. Body : txs_hash +Header *-- ContentCommitment: content_commitment +Header *-- StateReference : state +Header *-- GlobalVariables : global_variables + +class ContractData { + leaf: Fr + address: Address + portal: EthAddress +} + +class Logs { + private: EncryptedLogs + public: UnencryptedLogs +} + +class PublicDataWrite { + index: Fr + value: Fr +} + +class TxEffect { + note_hashes: List~Fr~ + nullifiers: List~Fr~ + l2_to_l1_msgs: List~Fr~ + contracts: List~ContractData~ + public_writes: List~PublicDataWrite~ + logs: Logs +} +TxEffect *-- "m" ContractData: contracts +TxEffect *-- "m" PublicDataWrite: public_writes +TxEffect *-- Logs : logs + +class Body { + tx_effects: List~TxEffect~ +} +Body *-- "m" TxEffect + +class ProvenBlock { + archive: Snapshot + header: Header + body: Body +} + +ProvenBlock *-- Header : header +ProvenBlock *-- Body : body + +class ConstantRollupData { + last_archive: Snapshot + base_rollup_vk_hash: Fr, + merge_rollup_vk_hash: Fr, + global_variables: GlobalVariables +} +ConstantRollupData *-- GlobalVariables : global_variables + +class PublicDataUpdateRequest { + index: Fr + old_value: Fr + new_value: Fr +} + +class PublicDataRead { + index: Fr + value: Fr +} + +class CombinedAccumulatedData { + read_requests: List~Fr~ + pending_read_requests: List~Fr~ + note_hashes: List~Fr~ + nullifiers: List~Fr~ + nullified_note_hashes: List~Fr~ + + l2_to_l1_messages: List~Fr~ + public_update_requests: List~PublicDataUpdateRequest~ + public_reads: List~PublicDataRead~ + logs: Logs + + private_call_stack: List~CallRequest~ + public_call_stack: List~CallRequest~ + start_public_data_root: Fr + end_public_data_root: Fr + + gas_used.da_gas: u32 + gas_used.l2_gas: u32 +} +CombinedAccumulatedData *-- "m" PublicDataUpdateRequest: public_update_requests +CombinedAccumulatedData *-- "m" PublicDataRead: public_reads +CombinedAccumulatedData *-- Logs : logs + +class TxContext { + chain_id: Fr + version: Fr + gas_settings: GasSettings +} + +TxContext *-- GasSettings : gas_settings + +class CombinedConstantData { + historical_header: Header + tx_context: TxContext + global_variables: GlobalVariables +} +CombinedConstantData *-- Header : historical_header +CombinedConstantData *-- TxContext : tx_context +CombinedConstantData *-- GlobalVariables : global_variables + +class GasSettings { + da.gas_limit: u32 + da.teardown_gas_limit: u32 + da.max_fee_per_gas: Fr + l1.gas_limit: u32 + l1.teardown_gas_limit: u32 + l1.max_fee_per_gas: Fr + l2.gas_limit: u32 + l2.teardown_gas_limit: u32 + l2.max_fee_per_gas: Fr + inclusion_fee: Fr +} + +class KernelPublicInputs { + is_private: bool + end: CombinedAccumulatedData + constants: CombinedConstantData +} +KernelPublicInputs *-- CombinedAccumulatedData : end +KernelPublicInputs *-- CombinedConstantData : constants + +class KernelData { + proof: Proof + public_inputs: KernelPublicInputs +} +KernelData *-- KernelPublicInputs : public_inputs + +class StateDiffHints { + nullifier_predecessor_preimages: List~NullifierLeafPreimage~ + nullifier_predecessor_membership_witnesses: List~NullifierMembershipWitness~ + sorted_nullifiers: List~Fr~ + sorted_nullifier_indexes: List~Fr~ + note_hash_subtree_sibling_path: List~Fr~, + nullifier_subtree_sibling_path: List~Fr~, + contract_subtree_sibling_path: List~Fr~, + public_data_sibling_path: List~Fr~, +} + +class BaseRollupInputs { + historical_header_membership_witnesses: HeaderMembershipWitness + kernel_data: KernelData + partial: PartialStateReference + state_diff_hints: StateDiffHints +} +BaseRollupInputs *-- KernelData : kernelData +BaseRollupInputs *-- PartialStateReference : partial +BaseRollupInputs *-- StateDiffHints : state_diff_hints +BaseRollupInputs *-- ConstantRollupData : constants + +class BaseOrMergeRollupPublicInputs { + type: Fr + height_in_block_tree: Fr + txs_hash: Fr[2] + out_hash: Fr[2] + constants: ConstantRollupData + start: PartialStateReference + end: PartialStateReference +} +BaseOrMergeRollupPublicInputs *-- ConstantRollupData : constants +BaseOrMergeRollupPublicInputs *-- PartialStateReference : start +BaseOrMergeRollupPublicInputs *-- PartialStateReference : end +``` + +:::warning TODO +Fee structs and contract deployment structs will need to be revised, in line with newer ideas. +::: + +### Validity Conditions + +```python +def BaseRollupCircuit( + state_diff_hints: StateDiffHints, + historical_header_membership_witnesses: HeaderMembershipWitness, + kernel_data: KernelData, + partial: PartialStateReference, + constants: ConstantRollupData, +) -> BaseOrMergeRollupPublicInputs: + + public_data_tree_root = partial.public_data_tree + tx_hash, contracts, public_data_tree_root = kernel_checks( + kernel_data, + constants, + public_data_tree_root, + historical_header_membership_witnesses, + ) + + note_hash_subtree = MerkleTree(kernel_data.public_inputs.end.note_hashes) + note_hash_snapshot = merkle_insertion( + partial.note_hash_tree.root, + note_hash_subtree.root, + state_diff_hints.note_hash_subtree_sibling_path, + NOTE_HASH_SUBTREE_HEIGHT, + NOTE_HASH_TREE_HEIGHT, + ) + + # We can use the sorted nullifiers to simplify batch-insertion + # The sorting can be checked with a permutation + nullifier_snapshot = successor_merkle_batch_insertion( + partial.nullifier_tree.root, + kernel_data.public_inputs.end.nullifiers, + state_diff_hints.sorted_nullifiers, + state_diff_hints.sorted_nullifier_indexes, + state_diff_hints.nullifier_subtree_sibling_path, + state_diff.nullifier_predecessor_preimages, + state_diff.nullifier_predecessor_membership_witnesses, + NULLIFIER_SUBTREE_HEIGHT, + NULLIFIER_TREE_HEIGHT, + ) + + contract_sub_tree = MerkleTree(contracts) + contract_snapshot = merkle_insertion( + partial.note_hash_tree.root, + note_hash_subtree.root, + state_diff_hints.contract_subtree_sibling_path, + CONTRACTS_SUBTREE_HEIGHT, + CONTRACTS_TREE_HEIGHT, + ) + + out_hash = SHA256(kernel_data.public_inputs.end.l2_to_l1_messages) + + return BaseOrMergeRollupPublicInputs( + type=0, + height_in_block_tree=0, + points_accumulator= + txs_hash=tx_hash + out_hash=out_hash + start=partial, + end=PartialStateReference( + note_hash_tree=note_hash_snapshot, + nullifier_tree=nullifier_snapshot, + contract_tree=contract_snapshot, + public_data_tree=public_data_tree_root, + ), + ) + +def kernel_checks( + kernel: KernelData, + constants: ConstantRollupData, + public_data_tree_root: Fr, + historical_header_membership_witness: HeaderMembershipWitness +) -> (Fr[2], Fr[], Fr): + assert public_data_tree_root == kernel.public_inputs.end.start_public_data_root + assert kernel.proof.verify(kernel.public_inputs) + + tx_context = kernel.public_inputs.constants.tx_context + assert tx_context.chain_id == constants.global_variables.chain_id + assert tx_context.version == constants.global_variables.version + + assert len(kernel.public_inputs.end.private_call_stack) == 0 + assert len(kernel.public_inputs.end.public_call_stack) == 0 + + assert merkle_inclusion( + kernel.constants.historical_header.hash(), + kernel.constants.historical_header.global_variables.block_number, + historical_header_membership_witness, + constants.last_archive + ) + + contracts = [] + contract_datas = [] + for preimage in kernel.public_inputs.end.contracts: + to_push = preimage.hash() if preimage.address == 0 else 0: + contracts.push(to_push) + contract_datas.push(ContractData(to_push, preimage.address, preimage.portal)) + + tx_hash = SHA256( + kernel.public_inputs.end.note_hashes | + kernel.public_inputs.end.nullifiers | + contract_datas |  + kernel.public_inputs.end.public_data_writes | + kernel.public_inputs.end.l2_to_l1_messages + ) + return (tx_hash, contracts, kernel.public_inputs.end.end_public_data_root) +``` diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/index.md new file mode 100644 index 000000000000..3b7b8d5bbec7 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/index.md @@ -0,0 +1,563 @@ +--- +title: Rollup Circuits +--- + +## Overview + +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 consist of three main components: +It has **two** sub-trees for transactions, and **one** tree for L1 to L2 messages. +The two transaction trees are then merged into a single proof and combined with the roots of the message tree to form the final proof and output. +Each of these trees are built by recursively combining proofs from a lower level of the tree. +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. + +Note that we have two different types of "merger" circuits, depending on what they are combining. + +For transactions we have: + +- The `merge` rollup + - Merges two rollup proofs of either `base` or `merge` and constructs outputs for further proving +- The `root` rollup + - Merges two rollup proofs of either `base` or `merge` and constructs outputs for L1 + +And for the message parity we have: + +- The `root_parity` circuit + - Merges `N` `root` or `base_parity` proofs +- The `base_parity` circuit + - Merges `N` l1 to l2 messages in a subtree + +In the diagram the size of the tree is limited for demonstration purposes, but a larger tree would have more layers of merge rollups proofs. Exactly how many layers and what combination of `base` and/or `merge` circuits are consumed is based on filling a [wonky tree](../state/tree-implementations.md#wonky-merkle-trees) with N transactions. +Circles mark the different types of proofs, while squares mark the different circuit types. + +```mermaid +graph BT + R_p((Root)) + R_c[Root] + + R_c --> R_p + + M0_p((Merge 0)) + M1_p((Merge 1)) + M0_p --> R_c + M1_p --> R_c + + M0_c[Merge 0] + M1_c[Merge 1] + M0_c --> M0_p + M1_c --> M1_p + + B0_p((Base 0)) + B1_p((Base 1)) + B2_p((Base 2)) + B3_p((Base 3)) + B0_p --> M0_c + B1_p --> M0_c + B2_p --> M1_c + B3_p --> M1_c + + B0_c[Base 0] + B1_c[Base 1] + B2_c[Base 2] + B3_c[Base 3] + B0_c --> B0_p + B1_c --> B1_p + B2_c --> B2_p + B3_c --> B3_p + + K0((Kernel 0)) + K1((Kernel 1)) + K2((Kernel 2)) + K3((Kernel 3)) + K0 --> B0_c + K1 --> B1_c + K2 --> B2_c + K3 --> B3_c + + style R_p fill:#1976D2; + style M0_p fill:#1976D2; + style M1_p fill:#1976D2; + style B0_p fill:#1976D2; + style B1_p fill:#1976D2; + style B2_p fill:#1976D2; + style B3_p fill:#1976D2; + style K0 fill:#1976D2; + style K1 fill:#1976D2; + style K2 fill:#1976D2; + style K3 fill:#1976D2; + + R --> R_c + + R((RootParity)) + + T0[BaseParity] + T1[BaseParity] + T2[BaseParity] + T3[BaseParity] + + T0_P((RootParity 0)) + T1_P((RootParity 1)) + T2_P((RootParity 2)) + T3_P((RootParity 3)) + + T4[RootParity] + + I0 --> T0 + I1 --> T1 + I2 --> T2 + I3 --> T3 + + T0 --> T0_P + T1 --> T1_P + T2 --> T2_P + T3 --> T3_P + + T0_P --> T4 + T1_P --> T4 + T2_P --> T4 + T3_P --> T4 + + T4 --> R + + I0((MSG 0-3)) + I1((MSG 4-7)) + I2((MSG 8-11)) + I3((MSG 12-15)) + + style R fill:#1976D2; + style T0_P fill:#1976D2; + style T1_P fill:#1976D2; + style T2_P fill:#1976D2; + style T3_P fill:#1976D2; + style I0 fill:#1976D2; + style I1 fill:#1976D2; + style I2 fill:#1976D2; + style I3 fill:#1976D2; +``` + +To understand what the circuits are doing and what checks they need to apply it is useful to understand what data is going into the circuits and what data is coming out. + +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 + +class PartialStateReference { + note_hash_tree: Snapshot + nullifier_tree: Snapshot + public_data_tree: Snapshot +} + +class StateReference { + l1_to_l2_message_tree: Snapshot + partial: PartialStateReference +} +StateReference *-- PartialStateReference: partial + +class GlobalVariables { + block_number: Fr + timestamp: Fr + version: Fr + chain_id: Fr + coinbase: EthAddress + fee_recipient: Address + gas_fees.fees_per_da_gas: Fr + gas_fees.fees_per_l2_gas: Fr +} + +class ContentCommitment { + tx_tree_height: Fr + txs_hash: Fr[2] + in_hash: Fr[2] + out_hash: Fr[2] +} + +class Header { + last_archive: Snapshot + content_commitment: ContentCommitment + state: StateReference + global_variables: GlobalVariables + total_fees: Fr +} +Header *.. Body : txs_hash +Header *-- ContentCommitment: content_commitment +Header *-- StateReference : state +Header *-- GlobalVariables : global_variables + +class Logs { + private: EncryptedLogs + public: UnencryptedLogs +} + +class PublicDataWrite { + index: Fr + value: Fr +} + +class TxEffect { + note_hashes: List~Fr~ + nullifiers: List~Fr~ + l2_to_l1_msgs: List~Fr~ + public_writes: List~PublicDataWrite~ + logs: Logs +} +TxEffect *-- "m" PublicDataWrite: public_writes +TxEffect *-- Logs : logs + +class Body { + tx_effects: List~TxEffect~ +} +Body *-- "m" TxEffect + +class ProvenBlock { + archive: Snapshot + header: Header + body: Body +} + +ProvenBlock *-- Header : header +ProvenBlock *-- Body : body + +class ConstantRollupData { + last_archive: Snapshot + base_rollup_vk_hash: Fr, + merge_rollup_vk_hash: Fr, + global_variables: GlobalVariables +} +ConstantRollupData *-- GlobalVariables : global_variables + +class PublicDataUpdateRequest { + index: Fr + old_value: Fr + new_value: Fr +} + +class PublicDataRead { + index: Fr + value: Fr +} + +class CombinedAccumulatedData { + read_requests: List~Fr~ + pending_read_requests: List~Fr~ + note_hashes: List~Fr~ + nullifiers: List~Fr~ + nullified_note_hashes: List~Fr~ + + l2_to_l1_messages: List~Fr~ + public_update_requests: List~PublicDataUpdateRequest~ + public_reads: List~PublicDataRead~ + logs: Logs + + private_call_stack: List~CallRequest~ + public_call_stack: List~CallRequest~ + start_public_data_root: Fr + end_public_data_root: Fr + + gas_used.da_gas: u32 + gas_used.l2_gas: u32 +} +CombinedAccumulatedData *-- "m" PublicDataUpdateRequest: public_update_requests +CombinedAccumulatedData *-- "m" PublicDataRead: public_reads +CombinedAccumulatedData *-- Logs : logs + +class TxContext { + chain_id: Fr + version: Fr + gas_settings: GasSettings +} + +TxContext *-- GasSettings : gas_settings + +class CombinedConstantData { + historical_header: Header + tx_context: TxContext + global_variables: GlobalVariables +} +CombinedConstantData *-- Header : historical_header +CombinedConstantData *-- TxContext : tx_context +CombinedConstantData *-- GlobalVariables : global_variables + +class GasSettings { + da.gas_limit: u32 + da.teardown_gas_limit: u32 + da.max_fee_per_gas: Fr + l1.gas_limit: u32 + l1.teardown_gas_limit: u32 + l1.max_fee_per_gas: Fr + l2.gas_limit: u32 + l2.teardown_gas_limit: u32 + l2.max_fee_per_gas: Fr + inclusion_fee: Fr +} + +class KernelPublicInputs { + is_private: bool + end: CombinedAccumulatedData + constants: CombinedConstantData +} +KernelPublicInputs *-- CombinedAccumulatedData : end +KernelPublicInputs *-- CombinedConstantData : constants + +class KernelData { + proof: Proof + public_inputs: KernelPublicInputs +} +KernelData *-- KernelPublicInputs : public_inputs + +class StateDiffHints { + nullifier_predecessor_preimages: List~NullifierLeafPreimage~ + nullifier_predecessor_membership_witnesses: List~NullifierMembershipWitness~ + sorted_nullifiers: List~Fr~ + sorted_nullifier_indexes: List~Fr~ + note_hash_subtree_sibling_path: List~Fr~, + nullifier_subtree_sibling_path: List~Fr~, + public_data_sibling_path: List~Fr~, +} + +class BaseRollupInputs { + historical_header_membership_witnesses: HeaderMembershipWitness + kernel_data: KernelData + partial: PartialStateReference + state_diff_hints: StateDiffHints +} +BaseRollupInputs *-- KernelData : kernelData +BaseRollupInputs *-- PartialStateReference : partial +BaseRollupInputs *-- StateDiffHints : state_diff_hints +BaseRollupInputs *-- ConstantRollupData : constants + +class BaseOrMergeRollupPublicInputs { + type: Fr + height_in_block_tree: Fr + txs_hash: Fr[2] + out_hash: Fr[2] + constants: ConstantRollupData + start: PartialStateReference + end: PartialStateReference +} +BaseOrMergeRollupPublicInputs *-- ConstantRollupData : constants +BaseOrMergeRollupPublicInputs *-- PartialStateReference : start +BaseOrMergeRollupPublicInputs *-- PartialStateReference : end + +class ChildRollupData { + proof: Proof + public_inputs: BaseOrMergeRollupPublicInputs +} +ChildRollupData *-- BaseOrMergeRollupPublicInputs: public_inputs + +class MergeRollupInputs { + left: ChildRollupData + right: ChildRollupData +} +MergeRollupInputs *-- ChildRollupData: left +MergeRollupInputs *-- ChildRollupData: right + +class BaseParityInputs { + msgs: List~Fr[2]~ +} + +class ParityPublicInputs { + sha_root: Fr[2] + converted_root: Fr +} + +class RootParityInputs { + children: List~ParityPublicInputs~ +} +RootParityInputs *-- ParityPublicInputs: children + +class RootParityInput { + proof: Proof + public_inputs: ParityPublicInputs +} +RootParityInput *-- ParityPublicInputs: public_inputs + +class RootRollupInputs { + l1_to_l2_roots: RootParityInput + l1_to_l2_msgs_sibling_path: List~Fr~ + parent: Header, + parent_sibling_path: List~Fr~ + archive_sibling_path: List~Fr~ + left: ChildRollupData + right: ChildRollupData +} +RootRollupInputs *-- RootParityInput: l1_to_l2_roots +RootRollupInputs *-- ChildRollupData: left +RootRollupInputs *-- ChildRollupData: right +RootRollupInputs *-- Header : parent + +class RootRollupPublicInputs { + archive: Snapshot + header: Header +} +RootRollupPublicInputs *--Header : header +``` + +:::info CombinedAccumulatedData +Note that the `CombinedAccumulatedData` contains elements that we won't be using throughout the rollup circuits. +However, as the data is used for the kernel proofs (when it is build recursively), we will include it here anyway. +::: + +Since the diagram can be quite overwhelming, we will go through the different data structures and what they are used for along with the three (3) different rollup circuits. + +### Higher-level tasks + +Before looking at the circuits individually, it can however be a good idea to recall the reason we had them in the first place. +For this, we are especially interested in the tasks that span multiple circuits and proofs. + +#### State consistency + +While the individual kernels are validated on their own, they might rely on state changes earlier in the block. +For the block to be correctly validated, this means that when validating kernel $n$, it must be executed on top of the state after all kernels $ SM + K0 --> SM + SM --> S1 + + + SM_2[State Machine] + K1((Kernel n)) + S2((State n+1)) + + S1 --> SM_2 + K1 --> SM_2 + SM_2 --> S2 + + style K0 fill:#1976D2; + style K1 fill:#1976D2; +``` + +#### State availability + +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 can reduce the number of public inputs by committing to the block's body and iteratively "build" up the commitment at each rollup circuit iteration. +At the very end, we will have a commitment to the transactions that were included in the block (`txs_effects_hash`, calculated by squeezing a Poseidon2 sponge which iteratively absorbed each tx's effects), the messages that were sent from L2 to L1 (`OutHash`) and the messages that were sent from L1 to L2 (`InHash`). + +The block body is published on L1 in a `blob`. We link this `blob` (an array of fields of our data, committed to by the EVM) to the `txs_effects_hash` above by proving our effects, which make the preimage of the `txs_effects_hash`, make the same commitment as the one calculated in the EVM. + +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 hashes and validate the blob. + +Since the `InHash` is directly computed by the `Inbox` contract on L1, the data is obviously available to the contract without doing any more work. The `OutHash` is published and used to verify the final epoch proof as a public input. + +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. +Below, we outline the `OutHash` and `InHash` merkle trees that are based on the `l2_to_l1_msgs` (cross-chain messages) and `l1_to_l2_msgs` for each transaction respectively, with four transactions in this rollup. + +```mermaid +graph BT + R[OutHash] + M0[Hash 0-1] + M1[Hash 2-3] + B0[Hash 0.0-0.3] + B1[Hash 1.0-1.1] + B2[Hash 2.0-2.1] + B3[Hash 3.0-3.3] + K0[l2_to_l1_msgs 0.0-0.1] + K1[l2_to_l1_msgs 0.2-0.3] + K2[l2_to_l1_msgs 1.0] + K3[l2_to_l1_msgs 1.1] + K4[l2_to_l1_msgs 2.0] + K5[l2_to_l1_msgs 2.1] + K6[l2_to_l1_msgs 3.0-3.1] + K7[l2_to_l1_msgs 3.2-3.3] + K8[l2_to_l1_msgs 0.0] + K9[l2_to_l1_msgs 0.1] + K10[l2_to_l1_msgs 0.2] + K11[l2_to_l1_msgs 0.3] + K12[l2_to_l1_msgs 3.0] + K13[l2_to_l1_msgs 3.1] + K14[l2_to_l1_msgs 3.2] + K15[l2_to_l1_msgs 3.3] + + M0 --> R + M1 --> R + B0 --> M0 + B1 --> M0 + B2 --> M1 + B3 --> M1 + K0 --> B0 + K1 --> B0 + K2 --> B1 + K3 --> B1 + K4 --> B2 + K5 --> B2 + K6 --> B3 + K7 --> B3 + K8 --> K0 + K9 --> K0 + K10 --> K1 + K11 --> K1 + K12 --> K6 + K13 --> K6 + K14 --> K7 + K15 --> K7 +``` + +```mermaid +graph BT + R[InHash] + M0[Hash 0-1] + M1[Hash 2-3] + B0[Hash 0.0-0.1] + B1[Hash 1.0-1.1] + B2[Hash 2.0-2.1] + B3[Hash 3.0-3.1] + K0[l1_to_l2_msgs 0.0] + K1[l1_to_l2_msgs 0.1] + K2[l1_to_l2_msgs 1.0] + K3[l1_to_l2_msgs 1.1] + K4[l1_to_l2_msgs 2.0] + K5[l1_to_l2_msgs 2.1] + K6[l1_to_l2_msgs 3.0] + K7[l1_to_l2_msgs 3.1] + + M0 --> R + M1 --> R + B0 --> M0 + B1 --> M0 + B2 --> M1 + B3 --> M1 + K0 --> B0 + K1 --> B0 + K2 --> B1 + K3 --> B1 + K4 --> B2 + K5 --> B2 + K6 --> B3 + K7 --> B3 +``` + +The `InHash` and `OutHash` need to be efficiently computable on L1 while still being non-horrible inside a snark - leading us to rely on SHA256. + + +The L2 to L1 messages from each transaction form a variable height tree. In the diagram above, transactions 0 and 3 have four messages, so require a tree with two layers, whereas the others only have two messages and so require a single layer tree. The base rollup calculates the root of this tree and passes it as the to the next layer. Merge rollups simply hash both of these roots together and pass it up as the `OutHash`. + +## Next Steps + +import DocCardList from '@theme/DocCardList'; + + diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/merge-rollup.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/merge-rollup.md new file mode 100644 index 000000000000..61c88a86e9af --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/merge-rollup.md @@ -0,0 +1,100 @@ +--- +title: Merge Rollup +--- + +The Merge rollup circuit is our in-between circuit, it doesn't need to perform any state updates, but mainly check the consistency of its inputs. + +```mermaid +graph LR +A[MergeRollupInputs] --> C[MergeRollupCircuit] --> B[BaseOrMergeRollupPublicInputs] +``` + + + +## Overview + +Below is a subset of the data structures figure from earlier for easy reference. + +```mermaid +classDiagram +direction TB + +class PartialStateReference { + note_hash_tree: Snapshot + nullifier_tree: Snapshot + contract_tree: Snapshot + public_data_tree: Snapshot +} + +class GlobalVariables { + block_number: Fr + timestamp: Fr + version: Fr + chain_id: Fr + coinbase: EthAddress + fee_recipient: Address + gas_fees.fees_per_da_gas: Fr + gas_fees.fees_per_l2_gas: Fr +} + +class ConstantRollupData { + last_archive: Snapshot + base_rollup_vk_hash: Fr, + merge_rollup_vk_hash: Fr, + global_variables: GlobalVariables +} +ConstantRollupData *-- GlobalVariables : global_variables + +class BaseOrMergeRollupPublicInputs { + type: Fr + height_in_block_tree: Fr + txs_hash: Fr[2] + out_hash: Fr[2] + constants: ConstantRollupData + start: PartialStateReference + end: PartialStateReference +} +BaseOrMergeRollupPublicInputs *-- ConstantRollupData : constants +BaseOrMergeRollupPublicInputs *-- PartialStateReference : start +BaseOrMergeRollupPublicInputs *-- PartialStateReference : end + +class ChildRollupData { + proof: Proof + public_inputs: BaseOrMergeRollupPublicInputs +} +ChildRollupData *-- BaseOrMergeRollupPublicInputs: public_inputs + +class MergeRollupInputs { + left: ChildRollupData + right: ChildRollupData +} +MergeRollupInputs *-- ChildRollupData: left +MergeRollupInputs *-- ChildRollupData: right +``` + +### Validity Conditions + +```python +def MergeRollupCircuit( + left: ChildRollupData, + right: ChildRollupData +) -> BaseOrMergeRollupPublicInputs: + assert left.proof.is_valid(left.public_inputs) + assert right.proof.is_valid(right.public_inputs) + + assert left.public_inputs.constants == right.public_inputs.constants + assert left.public_inputs.end == right.public_inputs.start + assert left.public_inputs.num_txs >= right.public_inputs.num_txs + assert left.public_inputs.end_sponge == right.public_inputs.start_sponge + + return BaseOrMergeRollupPublicInputs( + type=1, + num_txs=left.public_inputs.num_txs + right.public_inputs.num_txs, + out_hash=SHA256(left.public_inputs.out_hash | right.public_inputs.out_hash), + start=left.public_inputs.start, + end=right.public_inputs.end, + start_sponge=left.public_inputs.start_sponge, + end_sponge=right.public_inputs.end_sponge, + constants=left.public_inputs.constants + ) +``` diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/root-rollup.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/root-rollup.md new file mode 100644 index 000000000000..df4d9ec799ba --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/root-rollup.md @@ -0,0 +1,241 @@ +--- +title: Root Rollup +--- + +The root rollup circuit is our top circuit, it applies the state changes passed through its children and the cross-chain messages. Essentially, it is the last step that allows us to prove that the state transition function $\mathcal{T}(S, B) \mapsto S'$ was applied correctly for a state $S$ and a block $B$. Note, that the root rollup circuit's public inputs do not comprise the block entirely as it would be too costly to verify. Given a `ProvenBlock` and proof a node can derive the public inputs and validate the correctness of the state progression. + +```mermaid +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](../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. +::: + + + +## Overview + +```mermaid +classDiagram +direction TB + +class PartialStateReference { + note_hash_tree: Snapshot + nullifier_tree: Snapshot + public_data_tree: Snapshot +} + +class StateReference { + l1_to_l2_message_tree: Snapshot + partial: PartialStateReference +} +StateReference *-- PartialStateReference: partial + +class GlobalVariables { + block_number: Fr + timestamp: Fr + version: Fr + chain_id: Fr + coinbase: EthAddress + fee_recipient: Address + gas_fees.fees_per_da_gas: Fr + gas_fees.fees_per_l2_gas: Fr +} + +class ContentCommitment { + tx_tree_height: Fr + txs_hash: Fr[2] + in_hash: Fr[2] + out_hash: Fr[2] +} + +class Header { + last_archive: Snapshot + content_commitment: ContentCommitment + state: StateReference + global_variables: GlobalVariables + total_fees: Fr +} +Header *.. Body : txs_hash +Header *-- ContentCommitment: content_commitment +Header *-- StateReference : state +Header *-- GlobalVariables : global_variables + +class Logs { + private: EncryptedLogs + public: UnencryptedLogs +} + +class PublicDataWrite { + index: Fr + value: Fr +} + +class TxEffect { + note_hashes: List~Fr~ + nullifiers: List~Fr~ + l2_to_l1_msgs: List~Fr~ + public_writes: List~PublicDataWrite~ + logs: Logs +} +TxEffect *-- "m" PublicDataWrite: public_writes +TxEffect *-- Logs : logs + +class Body { + tx_effects: List~TxEffect~ +} +Body *-- "m" TxEffect + +class ProvenBlock { + archive: Snapshot + header: Header + body: Body +} + +ProvenBlock *-- Header : header +ProvenBlock *-- Body : body + +class ConstantRollupData { + last_archive: Snapshot + base_rollup_vk_hash: Fr, + merge_rollup_vk_hash: Fr, + global_variables: GlobalVariables +} +ConstantRollupData *-- GlobalVariables : global_variables + +class BaseOrMergeRollupPublicInputs { + type: Fr + height_in_block_tree: Fr + txs_hash: Fr[2] + out_hash: Fr[2] + constants: ConstantRollupData + start: PartialStateReference + end: PartialStateReference +} +BaseOrMergeRollupPublicInputs *-- ConstantRollupData : constants +BaseOrMergeRollupPublicInputs *-- PartialStateReference : start +BaseOrMergeRollupPublicInputs *-- PartialStateReference : end + +class ChildRollupData { + proof: Proof + public_inputs: BaseOrMergeRollupPublicInputs +} +ChildRollupData *-- BaseOrMergeRollupPublicInputs: public_inputs + + +class BaseParityInputs { + msgs: List~Fr[2]~ +} + +class ParityPublicInputs { + sha_root: Fr[2] + converted_root: Fr +} + +class RootParityInputs { + children: List~ParityPublicInputs~ +} +RootParityInputs *-- ParityPublicInputs: children + +class RootParityInput { + proof: Proof + public_inputs: ParityPublicInputs +} +RootParityInput *-- ParityPublicInputs: public_inputs +class RootRollupInputs { + l1_to_l2_roots: RootParityInput + l1_to_l2_msgs_sibling_path: List~Fr~ + parent: Header, + parent_sibling_path: List~Fr~ + archive_sibling_path: List~Fr~ + left: ChildRollupData + right: ChildRollupData +} +RootRollupInputs *-- RootParityInput: l1_to_l2_roots +RootRollupInputs *-- ChildRollupData: left +RootRollupInputs *-- ChildRollupData: right +RootRollupInputs *-- Header : parent + +class RootRollupPublicInputs { + archive: Snapshot + header: Header +} +RootRollupPublicInputs *--Header : header +``` + +### Validity Conditions + +```python +def RootRollupCircuit( + l1_to_l2_roots: RootParityInput, + l1_to_l2_msgs_sibling_path: List[Fr], + parent: Header, + parent_sibling_path: List[Fr], + archive_sibling_path: List[Fr], + left: ChildRollupData, + right: ChildRollupData, +) -> RootRollupPublicInputs: + assert left.proof.is_valid(left.public_inputs) + assert right.proof.is_valid(right.public_inputs) + assert l1_to_l2_roots.proof.verify(l1_to_l2_roots.public_inputs) + + assert left.public_inputs.constants == right.public_inputs.constants + assert left.public_inputs.end == right.public_inputs.start + assert left.public_inputs.num_txs >= right.public_inputs.num_txs + assert left.public_inputs.end_sponge == right.public_inputs.start_sponge + + assert parent.state.partial == left.public_inputs.start + + # Check that the parent is a valid parent + assert merkle_inclusion( + parent.hash(), + parent_sibling_path, + left.public_inputs.constants.global_variables.block_number, + left.public_inputs.constants.last_archive.root + ) + + # Update the l1 to l2 msg tree + l1_to_l2_msg_tree = merkle_insertion( + parent.state.l1_to_l2_message_tree, + l1_to_l2_roots.public_inputs.converted_root, + l1_to_l2_msgs_sibling_path, + L1_TO_L2_MSG_SUBTREE_HEIGHT, + L1_TO_L2_MSG_TREE_HEIGHT + ) + + header = Header( + last_archive = left.public_inputs.constants.last_archive, + content_commitment: ContentCommitment( + num_txs=left.public_inputs.num_txs + right.public_inputs.num_txs, + in_hash = l1_to_l2_roots.public_inputs.sha_root, + out_hash = SHA256(left.public_inputs.out_hash | right.public_inputs.out_hash), + ), + state = StateReference( + l1_to_l2_message_tree = l1_to_l2_msg_tree, + partial = right.public_inputs.end, + ), + global_variables = left.public_inputs.constants.global_variables, + ) + + archive = merkle_insertion( + header.last_archive + header.hash(), + archive_sibling_path, + 0, + ARCHIVE_HEIGHT + ) + + return RootRollupPublicInputs( + points_accumulator = + left.public_inputs.points_accumulator + + right.public_inputs.points_accumulator, + archive = archive, + header: Header, + ) +``` + +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/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/tree-parity.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/tree-parity.md new file mode 100644 index 000000000000..77ec759a851f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/rollup-circuits/tree-parity.md @@ -0,0 +1,122 @@ +--- +title: L1 to L2 Message Parity +--- + +To support easy consumption of l1 to l2 messages inside the proofs, we need to convert the tree of messages to a snark-friendly format. + +If you recall back in [L1 smart contracts](./../l1-smart-contracts/index.md#inbox) we were building a message tree on the L1. +We used SHA256 to compute the tree which is cheap to compute on L1. +As SHA256 is not snark-friendly, weak devices would not be able to prove inclusion of messages in the tree. + +This circuit is responsible for converting the tree such that users can easily build the proofs. +We essentially use this circuit to front-load the work needed to prove the inclusion of messages in the tree. +As earlier we are using a tree-like structure. +Instead of having a `base`, `merge` and `root` circuits, we will have only `base` and `root` parity circuits. +We only need these two, since what would have been the `merge` is doing the same as the `root` for this case. + +```mermaid +graph BT + R((RootParity)) + + T0[BaseParity] + T1[BaseParity] + T2[BaseParity] + T3[BaseParity] + + T0_P((RootParity 0)) + T1_P((RootParity 1)) + T2_P((RootParity 2)) + T3_P((RootParity 3)) + + T4[RootParity] + + I0 --> T0 + I1 --> T1 + I2 --> T2 + I3 --> T3 + + T0 --> T0_P + T1 --> T1_P + T2 --> T2_P + T3 --> T3_P + + T0_P --> T4 + T1_P --> T4 + T2_P --> T4 + T3_P --> T4 + + T4 --> R + + I0((MSG 0-3)) + I1((MSG 4-7)) + I2((MSG 8-11)) + I3((MSG 12-15)) + +style R fill:#1976D2; +style T0_P fill:#1976D2; +style T1_P fill:#1976D2; +style T2_P fill:#1976D2; +style T3_P fill:#1976D2; +style I0 fill:#1976D2; +style I1 fill:#1976D2; +style I2 fill:#1976D2; +style I3 fill:#1976D2; +``` + +The output of the "combined" circuit will be the `converted_root` which is the root of the snark-friendly message tree. +And the `sha_root` which must match the root of the sha256 message tree from the L1 Inbox. +The circuit computes the two trees using the same inputs, and then we ensure that the elements of the trees match the inbox later in the [state transitioner](./../l1-smart-contracts/index.md#overview). +It proves parity of the leaves in the two trees. + +```mermaid +classDiagram +direction LR + +class ParityPublicInputs { + sha_root: Fr[2] + converted_root: Fr +} + +class RootParityInputs { + children: List~RootParityInput~ +} + +RootParityInputs *-- RootParityInput: children + +class RootParityInput { + proof: Proof + verification_key: VerificationKey + public_inputs: ParityPublicInputs +} +RootParityInput *-- ParityPublicInputs: public_inputs + +class BaseParityInputs { + msgs: List~Fr[2]~ +} +``` + +The logic of the circuits is quite simple - build both a SHA256 and a snark-friendly tree from the same inputs. +For optimization purposes, it can be useful to have the layers take more than 2 inputs to increase the task of every layer. +If each just take 2 inputs, the overhead of recursing through the layers might be higher than the actual work done. +Recall that all the inputs are already chosen by the L1, so we don't need to worry about which to chose. + +```python +def base_parity_circuit(inputs: BaseParityInputs) -> ParityPublicInputs: + sha_root = MERKLE_TREE(inputs.msgs, SHA256); + converted_root = MERKLE_TREE(inputs.msgs, SNARK_FRIENDLY_HASH_FUNCTION); + return ParityPublicInputs(sha_root, converted_root) + +def root_parity_circuit(inputs: RootParityInputs) -> ParityPublicInputs: + for msg in inputs.children: + assert msg.proof.verify(msg.public_inputs, msg.verification_key); + + sha_root = MERKLE_TREE( + [msg.public_inputs.sha_root for msg in inputs.children], + SHA256 + ); + converted_root = MERKLE_TREE( + [msg.public_inputs.converted_root for msg in inputs.children], + SNARK_FRIENDLY_HASH_FUNCTION + ); + return ParityPublicInputs(sha_root, converted_root) +``` diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/state/archive.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/archive.md new file mode 100644 index 000000000000..459cea3b924e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/archive.md @@ -0,0 +1,125 @@ +--- +title: Archive Tree +--- + +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. + +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. + +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. + + + + + + + + + + + +```mermaid +classDiagram +direction TB + + +class PartialStateReference { + note_hash_tree: Snapshot + nullifier_tree: Snapshot + contract_tree: Snapshot + public_data_tree: Snapshot +} + +class StateReference { + l1_to_l2_message_tree: Snapshot + partial: PartialStateReference +} +StateReference *-- PartialStateReference: partial + +class GlobalVariables { + block_number: Fr + timestamp: Fr + version: Fr + chain_id: Fr + coinbase: EthAddress + fee_recipient: Address + gas_fees.fees_per_da_gas: Fr + gas_fees.fees_per_l2_gas: Fr +} + +class Header { + last_archive: Snapshot + body_hash: Fr[2] + state: StateReference + global_variables: GlobalVariables + total_fees: Fr +} +Header *.. Body : body_hash +Header *-- StateReference : state +Header *-- GlobalVariables : global_variables + +class Logs { + private: EncryptedLogs + public: UnencryptedLogs +} + +class PublicDataWrite { + index: Fr + value: Fr +} + +class ContractData { + leaf: Fr + address: Address + portal: EthAddress +} + +class TxEffect { + note_hashes: List~Fr~ + nullifiers: List~Fr~ + l2_to_l1_msgs: List~Fr~ + contracts: List~ContractData~ + public_writes: List~PublicDataWrite~ + logs: Logs +} +TxEffect *-- "m" ContractData: contracts +TxEffect *-- "m" PublicDataWrite: public_writes +TxEffect *-- Logs : logs + +class Body { + l1_to_l2_messages: List~Fr~ + tx_effects: List~TxEffect~ +} +Body *-- "m" TxEffect + +class ArchiveTree { + type: AppendOnlyMerkleTree + leaves: List~Header~ +} +ArchiveTree *.. "m" Header : leaves +``` diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/state/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/index.md new file mode 100644 index 000000000000..4dd342cfb806 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/index.md @@ -0,0 +1,215 @@ +--- +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 leaves in Merkle trees. + +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 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 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 leaves. + +### Private State Access + +Whenever a user is to read or use data, they must then convince the "rollup" that the their data is active. +As mentioned above, they must prove that the data is in the data tree (membership proof) and that it is still active (non-membership proof). +However, there are nuances to this approach! + +One important aspect to consider is _when_ state can be accessed. +In most blockchains, state is always accessed at the head of the chain and changes are only made by the sequencer as new blocks are added. + +However, since private execution relies on proofs generated by the user, this would be very impractical - one user's transaction could invalidate everyone else's. + +While proving inclusion in the data tree can be done using historical state, the non-membership proof in the nullifier tree cannot. + +Membership can be proven using historical state because we are using an append-only tree, so anything that was there in the past must still be in the append-only tree now. + +However, this doesn't work for the non-membership proof, as it can only prove that the data was active at the time the proof was generated, not that it is still active today! +This would allow a user to create multiple transactions spending the same data and then send those transactions all at once, creating a double spend. + +To solve this, we need to perform the non-membership proofs at the head of the chain, which only the sequencer knows! +This means that instead of the user proving that the nullifier of the data is not in the nullifier tree, they provide the nullifier as part of their transaction, and the sequencer then proves non-membership **AND** inserts it into the nullifier tree. +This way, if multiple transactions include the same nullifier, only one of them will be included in the block as the others will fail the non-membership proof. + +**Why does it need to insert the nullifier if I'm reading?** Why can't it just prove that the nullifier is not in the tree? Well, this is a privacy concern. +If you just make the non-membership proof, you are leaking that you are reading data for nullifier $x$, so if you read that data again at a later point, $x$ is seen by the sequencer again and it can infer that it is the same actor reading data. +By emitting the nullifier the read is indistinguishable from a write, and the sequencer cannot tell what is happening and there will be no repetitions. + +This however also means, that whenever data is only to be read, a new note with the same data must be inserted into the data tree. +This note have new randomness, so anyone watching will be unable to tell if it is the same data inserted, or if it is new data. +This is good for privacy, but comes at an additional cost. + +A side-effect of this also means that if multiple users are "sharing" their notes, any one of them reading the data will cause the note to be updated, so pending transaction that require the note will fail. + +## State Categories + + + +Below is a short description of the state categories (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 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. +- **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). For more information on how the L1 to L2 messages are used, see the [L1 Smart Contracts](../l1-smart-contracts/index.md) page. +- [**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/index.md#l2-outbox) and the [Archive Tree](./archive.md) allows for historical state access. + + + +```mermaid +classDiagram +direction TB + +class PartialStateReference { + note_hash_tree: Snapshot + nullifier_tree: Snapshot + public_data_tree: Snapshot +} + +class StateReference { + l1_to_l2_message_tree: Snapshot + partial: PartialStateReference +} +StateReference *-- PartialStateReference: partial + +class GlobalVariables { + block_number: Fr + timestamp: Fr + version: Fr + chain_id: Fr + coinbase: EthAddress + fee_recipient: Address + gas_fees.fees_per_da_gas: Fr + gas_fees.fees_per_l2_gas: Fr +} + +class ContentCommitment { + tx_tree_height: Fr + txs_hash: Fr[2] + in_hash: Fr[2] + out_hash: Fr[2] +} + +class Header { + last_archive: Snapshot + content_commitment: ContentCommitment + state: StateReference + global_variables: GlobalVariables + total_fees: Fr +} +Header *.. Body : txs_hash +Header *-- ContentCommitment: content_commitment +Header *-- StateReference : state +Header *-- GlobalVariables : global_variables + +class Logs { + private: EncryptedLogs + public: UnencryptedLogs +} + +class PublicDataWrite { + index: Fr + value: Fr +} + +class TxEffect { + note_hashes: List~Fr~ + nullifiers: List~Fr~ + l2_to_l1_msgs: List~Fr~ + public_writes: List~PublicDataWrite~ + logs: Logs +} +TxEffect *-- "m" PublicDataWrite: public_writes +TxEffect *-- Logs : logs + +class Body { + tx_effects: List~TxEffect~ +} +Body *-- "m" TxEffect + +class ArchiveTree { + type: AppendOnlyMerkleTree + leaves: List~Header~ +} +ArchiveTree *.. "m" Header : leaves + + +class NoteHashTree { + type: AppendOnlyMerkleTree + leaves: List~Fr~ +} + +class PublicDataPreimage { + key: Fr + value: Fr + successor_index: Fr + successor_value: Fr +} + +class PublicDataTree { + type: SuccessorMerkleTree + leaves: List~PublicDataPreimage~ +} +PublicDataTree *.. "m" PublicDataPreimage : leaves + +class L1ToL2MessageTree { + type: AppendOnlyMerkleTree + leaves: List~Fr~ +} + +class NullifierPreimage { + value: Fr + successor_index: Fr + successor_value: Fr +} + +class NullifierTree { + type: SuccessorMerkleTree + leaves: List~NullifierPreimage~ +} +NullifierTree *.. "m" NullifierPreimage : leaves + +class State { + archive_tree: ArchiveTree + note_hash_tree: NoteHashTree + nullifier_tree: NullifierTree + public_data_tree: PublicDataTree + l1_to_l2_message_tree: L1ToL2MessageTree +} +State *-- L1ToL2MessageTree : l1_to_l2_message_tree +State *-- ArchiveTree : archive_tree +State *-- NoteHashTree : note_hash_tree +State *-- NullifierTree : nullifier_tree +State *-- PublicDataTree : public_data_tree +``` + +import DocCardList from '@theme/DocCardList'; + + diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/state/note-hash-tree.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/note-hash-tree.md new file mode 100644 index 000000000000..174328ae143c --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/note-hash-tree.md @@ -0,0 +1,25 @@ +# Note Hash Tree + +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. 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_note_hashes` 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 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. + +``` +fn compute_siloed_note_hash(commitment, contract, transaction): + let index = index_of(commitment, transaction.commitments) + let nonce = hash([transaction.tx_hash, index], NOTE_HASH_NONCE) + let unique_note_hash = hash([nonce, commitment], UNIQUE_NOTE_HASH); + return hash([contract, unique_note_hash], SILOED_NOTE_HASH) +``` + +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 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_note_hash`, including values that do not map to a note hash. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/state/nullifier-tree.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/nullifier-tree.md new file mode 100644 index 000000000000..e33c294643d0 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/nullifier-tree.md @@ -0,0 +1,46 @@ +# 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 is primarily used to prove non-existence of a nullifier when a note is consumed (as a way of preventing double-spend). + +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). + +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): + return hash([contract, nullifier], OUTER_NULLIFIER) +``` + +Nullifiers are primarily used for privately marking notes as consumed. When a note is consumed in an application, the application computes and emits a deterministic nullifier associated to the note. If a user attempts to consume the same note more than once, the same nullifier will be generated, and will be rejected on insertion by the nullifier tree. + +Nullifiers provide privacy by being computed using a deterministic secret value, such as the owner siloed nullifier secret key, or a random value stored in an encrypted note. This ensures that, without knowledge of the secret value, it is not possible to calculate the associated nullifier, and thus it is not possible to link a nullifier to its associated note commitment. + +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/docs/versioned_docs/version-v0.88.0/protocol-specs/state/public-data-tree.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/public-data-tree.md new file mode 100644 index 000000000000..e4cd49ec1e05 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/public-data-tree.md @@ -0,0 +1,43 @@ +# Public Data Tree + +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. + +An indexed Merkle tree is used 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 the public state of another contract. + + + +``` +fn compute_siloed_public_data_item(key, value, contract): + let siloed_key = hash([contract, key], PUBLIC_DATA_LEAF) + return [siloed_key, value] +``` + +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/docs/versioned_docs/version-v0.88.0/protocol-specs/state/tree-implementations.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/tree-implementations.md new file mode 100644 index 000000000000..dfeded6c8242 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/tree-implementations.md @@ -0,0 +1,31 @@ +# Tree implementations + +Aztec relies on two Merkle tree implementations in the protocol: append-only and indexed Merkle trees. + +## 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 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, 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. + +### Wonky Merkle Trees + +We also use a special type of append-only tree to structure the rollup circuits. Given `n` leaves, we fill from left to right and attempt to pair them to produce the next layer. If `n` is a power of 2, this tree looks exactly like a standard append-only merkle tree. Otherwise, once we reach an odd-sized row we shift the final node up until we reach another odd row to combine them. + +This results in an unbalanced tree where there are no empty leaves. For rollups, this means we don't have to pad empty transactions and process them through the rollup circuits. A full explanation is given [here](./wonky-tree.md). + +## 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 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. + +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](../state/tree-implementations.md#indexed-merkle-trees) for more details on how insertions, updates, and membership proofs are executed on an Indexed Merkle tree. + + + +## Siloing leaves + +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 a kernel or rollup circuit, and not by an application circuit. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/state/wonky-tree.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/wonky-tree.md new file mode 100644 index 000000000000..3fc0bd0c9b8e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/state/wonky-tree.md @@ -0,0 +1,426 @@ +# Wonky Tree + +A 'wonky' tree is an append-only unbalanced merkle tree, filled from left to right. It is used to construct [rollup](../rollup-circuits/index.md) proofs without padding empty transactions. + +For example, using a balanced merkle tree to rollup 5 transactions requires padding of 3 empty transactions: + +```mermaid +graph BT + R_c[Root] + + M4_c[Merge] + M5_c[Merge] + M4_c --> R_c + M5_c --> R_c + + + M0_c[Merge] + M1_c[Merge] + M0_c --> M4_c + M1_c --> M4_c + + B0_c[Base] + B1_c[Base] + B2_c[Base] + B3_c[Base] + B0_c --> M0_c + B1_c --> M0_c + B2_c --> M1_c + B3_c --> M1_c + + M2_c[Merge] + M3_c[Merge*] + M2_c --> M5_c + M3_c --> M5_c + + B4_c[Base] + B5_c[Base*] + B6_c[Base*] + B7_c[Base*] + B4_c --> M2_c + B5_c --> M2_c + B6_c --> M3_c + B7_c --> M3_c + + Tx0_c((Tx 0)) + Tx1_c((Tx 1)) + Tx2_c((Tx 2)) + Tx3_c((Tx 3)) + Tx4_c((Tx 4)) + + Tx0_c --> B0_c + Tx1_c --> B1_c + Tx2_c --> B2_c + Tx3_c --> B3_c + Tx4_c --> B4_c +``` + +Where each node marked with `*` indicates a circuit proving entirely empty information. While the above structure does allow us to easily construct balanced trees later on consisting of `out_hash`es and `tx_effects_hash`es, it will lead to wasted compute and higher block processing costs unless we provide a number of transactions equal to a power of 2. + +Our wonky tree implementation instead gives the below structure for 5 transactions: + +```mermaid +graph BT + R_c[Root] + + M4_c[Merge] + M4_c --> R_c + + + M0_c[Merge] + M1_c[Merge] + M0_c --> M4_c + M1_c --> M4_c + + B0_c[Base] + B1_c[Base] + B2_c[Base] + B3_c[Base] + B0_c --> M0_c + B1_c --> M0_c + B2_c --> M1_c + B3_c --> M1_c + + + B4_c[Base] + B4_c --> R_c + + Tx0_c((Tx 0)) + Tx1_c((Tx 1)) + Tx2_c((Tx 2)) + Tx3_c((Tx 3)) + Tx4_c((Tx 4)) + + Tx0_c --> B0_c + Tx1_c --> B1_c + Tx2_c --> B2_c + Tx3_c --> B3_c + Tx4_c --> B4_c +``` + +Here, each circuit is proving useful transaction information with no wasted compute. We can construct a tree like this one for any number of transactions by greedy filling from left to right. Given the required 5 base circuits: + +```mermaid +graph + B0_c[Base 0] + B1_c[Base 1] + B2_c[Base 2] + B3_c[Base 3] + B4_c[Base 4] +``` + +...we theh pair these base circuits up to form merges: + +```mermaid +graph BT + M0_c[Merge 0] + M1_c[Merge 1] + + B0_c[Base 0] + B1_c[Base 1] + B2_c[Base 2] + B3_c[Base 3] + B0_c --> M0_c + B1_c --> M0_c + B2_c --> M1_c + B3_c --> M1_c + + B4_c[Base 4] +``` + +Since we have an odd number of transactions, we cannot pair up the final base. Instead, we continue to pair the next layers until we reach a layer with an odd number of members. In this example, that's when we reach merge 2: + +```mermaid +graph BT + M0_c[Merge 0] + M1_c[Merge 1] + M2_c[Merge 2] + + B0_c[Base 0] + B1_c[Base 1] + B2_c[Base 2] + B3_c[Base 3] + B0_c --> M0_c + B1_c --> M0_c + B2_c --> M1_c + B3_c --> M1_c + + M0_c --> M2_c + M1_c --> M2_c + + B4_c[Base 4] +``` + +Once paired, the base layer has length 4, the next merge layer has 2, and the final merge layer has 1. After reaching a layer with odd length, the orchestrator can now pair base 4: + +```mermaid +graph BT + R_c[Root] + + M0_c[Merge 0] + M1_c[Merge 1] + M2_c[Merge 2] + + B0_c[Base 0] + B1_c[Base 1] + B2_c[Base 2] + B3_c[Base 3] + B0_c --> M0_c + B1_c --> M0_c + B2_c --> M1_c + B3_c --> M1_c + + M0_c --> M2_c + M1_c --> M2_c + + B4_c[Base 4] + M2_c --> R_c + B4_c --> R_c +``` + +Since we have processed all base circuits, this final pair will be input to a root circuit. + +Filling from left to right means that we can easily reconstruct the tree only from the number of transactions `n`. The above method ensures that the final tree is a combination of _balanced_ subtrees of descending size. The widths of these subtrees are given by the decomposition of `n` into powers of 2. For example, 5 transactions: + +``` +Subtrees: [4, 1] -> + left_subtree_root = balanced_tree(txs[0..4]) + right_subtree_root = balanced_tree(txs[4]) = txs[4] + root = left_subtree_root | right_subtree_root +``` + +For 31 transactions: + +``` +Subtrees: [16, 8, 4, 2, 1] -> + Merge D: left_subtree_root = balanced_tree(txs[0..16]) + right_subtree_root = Subtrees: [8, 4, 2, 1] --> { + Merge C: left_subtree_root = balanced_tree(txs[16..24]) + right_subtree_root = Subtrees: [4, 2, 1] --> { + Merge B: left_subtree_root = balanced_tree(txs[24..28]) + right_subtree_root = Subtrees: [2, 1] --> { + Merge A: left_subtree_root = balanced_tree(txs[28..30]) + right_subtree_root = balanced_tree(txs[30]) = txs[30] + Merge 0: root = left_subtree_root | right_subtree_root + } + Merge 1: root = left_subtree_root | right_subtree_root + } + Merge 2: root = left_subtree_root | right_subtree_root + } + root = left_subtree_root | right_subtree_root +``` + +An unrolled recursive algorithm is not the easiest thing to read. This diagram represents the 31 transactions rolled up in our wonky structure, where each `Merge ` is a 'subroot' above: + +```mermaid +graph BT + M2_c[Merge 2] + M3_c[Merge D + Subtree of 16 txs] + R_c[Root] + + + B4_c[Merge C + Subtree of 8 txs] + B5_c[Merge 1] + + B4_c --> M2_c + B5_c --> M2_c + + B6_c[Merge B + Subtree of 4 txs] + B7_c[Merge 0] + + B6_c --> B5_c + B7_c --> B5_c + + B8_c[Merge A + Subtree of 2 txs] + B9_c[Base 30] + + B8_c --> B7_c + B9_c --> B7_c + + + M3_c --> R_c + M2_c --> R_c +``` + +The tree is reconstructed to provide a membership path against the stored `out_hash` (= the root of a wonky tree given by leaves of each tx's L2 to L1 message tree root) for consuming a L2 to L1 message. + +Currently, this tree is built via the orchestrator given the number of transactions to rollup. Each 'node' is assigned a level (0 at the root) and index in that level. The below function finds the parent level: + +``` + // Calculates the index and level of the parent rollup circuit + public findMergeLevel(currentLevel: bigint, currentIndex: bigint) { + const moveUpMergeLevel = (levelSize: number, index: bigint, nodeToShift: boolean) => { + levelSize /= 2; + if (levelSize & 1) { + [levelSize, nodeToShift] = nodeToShift ? [levelSize + 1, false] : [levelSize - 1, true]; + } + index >>= 1n; + return { thisLevelSize: levelSize, thisIndex: index, shiftUp: nodeToShift }; + }; + let [thisLevelSize, shiftUp] = this.totalNumTxs & 1 ? [this.totalNumTxs - 1, true] : [this.totalNumTxs, false]; + const maxLevel = this.numMergeLevels + 1n; + let placeholder = currentIndex; + for (let i = 0; i < maxLevel - currentLevel; i++) { + ({ thisLevelSize, thisIndex: placeholder, shiftUp } = moveUpMergeLevel(thisLevelSize, placeholder, shiftUp)); + } + let thisIndex = currentIndex; + let mergeLevel = currentLevel; + while (thisIndex >= thisLevelSize && mergeLevel != 0n) { + mergeLevel -= 1n; + ({ thisLevelSize, thisIndex, shiftUp } = moveUpMergeLevel(thisLevelSize, thisIndex, shiftUp)); + } + return [mergeLevel - 1n, thisIndex >> 1n, thisIndex & 1n]; + } +``` + +For example, `Base 4` above starts with `level = 3` and `index = 4`. Since we have an odd number of transactions at this level, `thisLevelSize` is set to 4 with `shiftUp = true`. + +The while loop triggers and shifts up our node to `level = 2` and `index = 2`. This level (containing `Merge 0` and `Merge 1`) is of even length, so the loop continues. The next iteration shifts up to `level = 1` and `index = 1` - we now have an odd level, so the loop stops. The actual position of `Base 4` is therefore at `level = 1` and `index = 1`. This function returns the parent level of the input node, so we return `level = 0`, `index = 0`, correctly indicating that the parent of `Base 4` is the root. + +### Flexible wonky trees + +We can also encode the structure of _any_ binary merkle tree by tracking `number_of_branches` and `number_of_leaves` for each node in the tree. This encoding was originally designed for [logs](../logs/index.md), so the below explanation references the leaves stored in relation to logs and transactions. + +The benefit of this method as opposed to the one above is allowing for any binary structure and therefore allowing for 'skipping' leaves with no information. However, the encoding grows as the tree grows, by at least 2 bytes per node. The above implementation only requires the number of leaves to be encoded, which will likely only require a single field to store. + + + +#### Encoding + +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, ...]`_ + +2. The encoded logs data of a block is a flatten array of a collection of the above _tx_logs_data_, with hints facilitating hashing replay in a binary tree structure: + + _`block_logs_data = [number_of_branches, number_of_transactions, ...tx_logs_data_0, ...tx_logs_data_1, ...]`_ + + - _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`_: + +1. A rollup, _R01_, merges two transactions: _tx0_ containing _tx_logs_data_0_, and _tx1_ containing _tx_logs_data_1_: + + ```mermaid + flowchart BT + tx0((tx0)) + tx1((tx1)) + R01((R01)) + tx0 --- R01 + tx1 --- R01 + ``` + + _block_logs_data_: _`[0, 2, ...tx_logs_data_0, ...tx_logs_data_1]`_ + + Where _0_ is the depth of the node _R01_, and _2_ is the number of aggregated _tx_logs_data_ of _R01_. + +2. Another rollup, _R23_, merges two transactions: _tx3_ containing _tx_logs_data_3_, and _tx2_ without any logs: + + ```mermaid + flowchart BT + tx2((tx2)) + tx3((tx3)) + R23((R23)) + tx2 -. no logs .- R23 + tx3 --- R23 + ``` + + _block_logs_data_: _`[0, 1, ...tx_logs_data_3]`_ + + Here, the number of aggregated _tx_logs_data_ is _1_. + +3. A rollup, _RA_, merges the two rollups _R01_ and _R23_: + + ```mermaid + flowchart BT + tx0((tx0)) + tx1((tx1)) + R01((R01)) + tx0 --- R01 + tx1 --- R01 + tx2((tx2)) + tx3((tx3)) + R23((R23)) + tx2 -.- R23 + tx3 --- R23 + RA((RA)) + R01 --- RA + R23 --- RA + ``` + + _block_logs_data_: _`[1, 2, ...tx_logs_data_0, ...tx_logs_data_1, 0, 1, ...tx_logs_data_3]`_ + + The result is the _block_logs_data_ of _R01_ concatenated with the _block_logs_data_ of _R23_, with the _number_of_branches_ of _R01_ incremented by _1_. The updated value of _number_of_branches_ (_0 + 1_) is also the depth of the node _R01_. + +4. A rollup, _RB_, merges the above rollup _RA_ and another rollup _R45_: + + ```mermaid + flowchart BT + tx0((tx0)) + tx1((tx1)) + R01((R01)) + tx0 --- R01 + tx1 --- R01 + tx2((tx2)) + tx3((tx3)) + R23((R23)) + tx2 -.- R23 + tx3 --- R23 + RA((RA)) + R01 --- RA + R23 --- RA + tx4((tx4)) + tx5((tx5)) + R45((R45)) + tx4 --- R45 + tx5 --- R45 + RB((RB)) + RA --- RB + R45 --- RB + ``` + + _block_logs_data_: _`[2, 2, ...tx_logs_data_0, ...tx_logs_data_1, 0, 1, ...tx_logs_data_3, 0, 2, ...tx_logs_data_4, ...tx_logs_data_5]`_ + + The result is the concatenation of the _block_logs_data_ from both rollups, with the _number_of_branches_ of the left-side rollup, _RA_, incremented by _1_. + +#### Verification + +Upon receiving a proof and its encoded logs data, the entity can ensure the correctness of the provided _block_logs_data_ by verifying that the _accumulated_logs_hash_ in the proof can be derived from it: + +```js +const accumulated_logs_hash = compute_accumulated_logs_hash(block_logs_data); +assert(accumulated_logs_hash == proof.accumulated_logs_hash); +assert(block_logs_data.accumulated_logs_length == proof.accumulated_logs_length); + +function compute_accumulated_logs_hash(logs_data) { + const number_of_branches = logs_data.read_u32(); + + const number_of_transactions = logs_data.read_u32(); + let res = hash_tx_logs_data(logs_data); + if number_of_transactions == 2 { + res = hash(res, hash_tx_logs_data(logs_data)); + } + + for (let i = 0; i < number_of_branches; ++i) { + const res_right = compute_accumulated_logs_hash(logs_data); + res = hash(res, res_right); + } + + return res; +} + +function hash_tx_logs_data(logs_data) { + const number_of_logs = logs_data.read_u32(); + let res = hash_log_data(logs_data); + for (let i = 1; i < number_of_logs; ++i) { + const log_hash = hash_log_data(logs_data); + res = hash(res, log_hash); + } + return res; +} +``` diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/todo.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/todo.md new file mode 100644 index 000000000000..1722f08b3ab2 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/todo.md @@ -0,0 +1,55 @@ +TODO: + +- See cryptography/todo.md +- Describe what we _desire_ the protocol to be; not what it is today. +- Define/describe: + - an oracle +- More detail and precisely-named definitions relating to note hashes (inner, unique, siloed, ...) and nullifiers. +- Clear descriptions of the precompiles for handshaking & note tagging. Including the encoding of logs (header, body, ephemeral keys, etc.) +- Poseidon hash for all trees? +- Maybe describe all hashes on one page? +- For all hashes: Describe exact ordering of preimage values. Describe the encoding of each preimage value. State the hash to use. Include a domain separator. Give the hash a unique name. Is there anything weird like truncating an output to fit in a field? + If possible, describe the property(or properties) we want from the hash (collision resistance?, 2nd preimage resistance? pseudo-random function? hash to curve?). +- Consistently use the same name for each concept throughout. Link to it the first time it’s mentioned on a page. When introducing a ‘thing’, try to put it in a section with a subtitle which matches that thing’s name. +- Structs (or tables) (clearly named and typed, in a subsection) for everything whose layout we should know! +- Who is going to write the specs for: + - Data bus? + - RAM/ROM? + - Circuit arithmetisation + - Custom Gates + - The proving system (honk, goblin, protogalaxy, etc.) +- Spec logs better! + - Use data bus? + - Allow several (4 or 8 or something) fields to be emitted by a function, before needing to sha256 the data? + - Have a 'reset' circuit to sha256 logs? + - Will there be space in the data bus for logs? + - Resolve 'impersonation' discussions (see the forum) + +Contents: + +- Custom types +- Constants + - Subdivided into categories: + - Circuit constants + - Tree constants + - Seq Selection constants + - P2P constants + - Block constants +- Serialization & Deserialization + - aztec.nr +- Encodings +- Hashing +- Merkleization +- Key derivation algorithms + +Layout: highlight out-of-protocol information in a box. + +Abstraction & Standardisation: + +- Account abstraction +- Constructor abstraction +- Nonce abstraction +- Fee abstraction +- Tx Hash abstraction + +Every struct, constant, other definition, needs a corresponding subheading with that exact name, somewhere in the docs? Might get ugly... diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/index.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/index.md new file mode 100644 index 000000000000..efd5ae8b4683 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/index.md @@ -0,0 +1,15 @@ +--- +title: Transactions +--- + +# 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/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#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/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/local-execution.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/local-execution.md new file mode 100644 index 000000000000..01ed56e58679 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/local-execution.md @@ -0,0 +1,42 @@ +# Local Execution + +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. + + +| Field | Type | Description | +|----------|----------|----------| +| `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, protocol version, and gas settings. | +| `hashedArguments` | `HashedValues[]` | 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 `hashedArguments` and `authWitnesses` from the request. + +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. + +## Proving step + +The proving step is similar to the simulation step, though witnesses are generated for all circuits and proven. Note that it is not necessary to execute the simulation step before the proving step, though it is desirable in order to provide the user with info on their transaction and catch any failed assertions early. + +The output of the proving step is a [_transaction_](./tx-object.md) object with a valid _proof_ associated, ready to be broadcasted to the network. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/public-execution.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/public-execution.md new file mode 100644 index 000000000000..0fb55ab35a71 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/public-execution.md @@ -0,0 +1,15 @@ +# Public execution + +Transactions have a _public execution_ component. Once a transaction is picked up by a sequencer to be included in a block, the sequencer is responsible for executing all enqueued public function calls in the transaction. These are defined by the `data.accumulatedData.publicCallStack` field of the [transaction object](./tx-object.md), which are commitments to the preimages of the `enqueuedPublicFunctionCalls` in the transaction. The sequencer pops function calls from the stack, and pushes new ones as needed, until the public call stack is empty. + +## Bytecode + +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 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/index.md#transaction-payload). The transaction is still included in the block and pays fees, but is flagged as reverted. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/tx-object.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/tx-object.md new file mode 100644 index 000000000000..821927edb39e --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/tx-object.md @@ -0,0 +1,110 @@ +# Transaction object + +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. | + +### Private kernel public inputs final + +Output of the last iteration of the private kernel circuit. Includes _accumulated data_ after recursing through all private function calls, as well as _constant data_ composed of _block header_ reflecting the state of the chain when such functions were executed, and the global _transaction context_. Refer to the circuits section for more info. + +**Accumulated data** + + +| Field | Type | Description | +|-------|------|-------------| +| noteHashes | Field[] | The new note hashes made in this transaction. | +| nullifiers | Field[] | The new nullifiers made in this transaction. | +| nullifiedNoteHashes | Field[] | The note hashes which are nullified by a nullifier in the above list. | +| privateCallStack | Field[] | Current private call stack. | +| publicCallStack | Field[] | Current public call stack. | +| l2ToL1Msgs | Field[] | All the new L2 to L1 messages created in this transaction. | +| encryptedLogsHash | Field[] | Accumulated encrypted logs hash from all the previous kernel iterations. | +| unencryptedLogsHash | Field[] | Accumulated unencrypted logs hash from all the previous kernel iterations. | +| encryptedLogPreimagesLength | Field | Total accumulated length of the encrypted log preimages emitted in all the previous kernel iterations. | +| unencryptedLogPreimagesLength | Field | Total accumulated length of the unencrypted log preimages emitted in all the previous kernel iterations. | +| maxBlockNum | Field | Maximum block number (inclusive) for inclusion of this transaction in a block. | + +**Block header** + + +| Field | Type | Description | +|-------|------|-------------| +| noteHashTreeRoot | Field | Root of the note hash tree at the time of when this information was assembled. | +| nullifierTreeRoot | Field | Root of the nullifier tree at the time of when this information was assembled. | +| contractTreeRoot | Field | Root of the contract tree at the time of when this information was assembled. | +| l1ToL2MessageTreeRoot | Field | Root of the L1 to L2 message tree at the time of when this information was assembled. | +| archiveRoot | Field | Root of the archive at the time of when this information was assembled. | +| privateKernelVkTreeRoot | Field | Root of the private kernel VK tree at the time of when this information was assembled (future enhancement). | +| publicDataTreeRoot | Field | Current public state tree hash. | +| globalVariablesHash | Field | Previous globals hash, this value is used to recalculate the block hash. | + +### 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. | +| 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. | +| 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). | +| partialAddress | Field | Hash of the constructor arguments, salt, and bytecode. | + +## 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: + + + +- `origin` +- `functionSelector` +- `argsHash` +- `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. diff --git a/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/validity.md b/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/validity.md new file mode 100644 index 000000000000..5a179ee51840 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/protocol-specs/transactions/validity.md @@ -0,0 +1,25 @@ +# 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 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 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 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`. + - The `unencryptedLogs` should match the `unencryptedLogsHash` and `unencryptedLogPreimagesLength` in the transaction `data`. + - 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`. +- **Able to pay fee**: The [fee can be paid](../gas-and-fees/kernel-tracking.md#mempoolnode-validation). + +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/docs/versioned_docs/version-v0.88.0/sandbox_to_testnet_guide.md b/docs/versioned_docs/version-v0.88.0/sandbox_to_testnet_guide.md new file mode 100644 index 000000000000..06ceab812600 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/sandbox_to_testnet_guide.md @@ -0,0 +1,134 @@ +--- +title: Migrating from Sandbox to Testnet +tags: [sandbox, testnet] +--- + +import { AztecTestnetVersion } from '@site/src/components/Snippets/general_snippets'; + +This guide assumes you have an Aztec app on sandbox and you wish to deploy it onto testnet. If you have never worked with sandbox or testnet, you might want to check out the [getting started on testnet guide](./developers/getting_started.md). + +## Main differences + +- The testnet is a remote environment. The sandbox runs locally. You will be running your contracts on a network of sequencers and other contracts and connecting to them via the Private Execution Environment (PXE). +- The testnet always has proving enabled. Users will prove private transactions and network provers prove public execution of blocks. Proving may take longer on testnet +- The testnet always has fees enabled. You will need to pay fees, or sponsor fees, when sending a transaction +- Testnet block times are longer than sandbox (they are about 36 seconds on average, and much longer to settle on L1), so your transaction may take longer to mine and be included in a block + +:::warning + +The testnet is version dependent. It is currently running version `0.87.8`. Maintain version consistency when interacting with the testnet to reduce errors. + +::: + +## Sandbox, nodes, and PXE + +To connect a local PXE to testnet, install the testnet version of the sandbox. + +```sh +aztec-up -v latest +``` + +When you run `aztec-wallet` commands, make sure to include a `node-url` option. An example: + +```sh +export NODE_URL=https://aztec-alpha-testnet-fullnode.zkv.xyz +aztec-wallet create-account -a main --register-only --node-url $NODE_URL +``` + +You can find a full flow in the [getting started on testnet](./developers/getting_started.md) guide. + +Instead of running a PXE locally, you can also use one directly with AztecJS in your app. For this, you will need to connect to an Aztec node and initialize the PXE. + +In the browser: + +```javascript +import { createPXEService } from "@aztec/pxe/client/lazy"; +``` + +In Node.js + +```javascript +import { createPXEService } from "@aztec/pxe/server"; +``` + +Then initialize the PXE: + +```javascript +const pxe = await createPXEService(node, pxeConfig); +``` + +### PXE configuration + +In node.js, for example, you can initialize the PXE with the following code: + +```javascript +import { createAztecNodeClient } from "@aztec/aztec.js"; +import { getPXEServiceConfig } from "@aztec/pxe/server"; +import { createStore } from "@aztec/kv-store/lmdb"; + +const node = createAztecNodeClient(PXE_URL); +const l1Contracts = await node.getL1ContractAddresses(); +const config = getPXEServiceConfig(); +const fullConfig = { ...config, l1Contracts }; + +const store = await createStore("pxe1", { + dataDirectory: "store", + dataStoreMapSizeKB: 1e6, +}); + +const pxe = await createPXEService(node, fullConfig, { store }); +``` + +## Paying for fees + +There are multiple ways to pay for fees on testnet: + +- The user pays for their own (in which case you will need to send them tokens, or get them to use the faucet) +- It is sponsored by your own contract +- It is sponsored by the canonical sponsored fee payment contract (FPC) deployed to testnet. Read more about using a Sponsored FPC in Aztec.js [here](./developers/guides/js_apps/pay_fees.md#sponsored-fee-paying-contract) or via the [CLI here](./developers/reference/environment_reference/cli_wallet_reference#sponsored-fee-paying-contract). + +You can learn more about all of the ways to pay for transaction fees [here](./developers/guides/js_apps/pay_fees.md). + +You will need to specify the fee-payer for all transactions. An example using `aztec-wallet`: + +```sh +aztec-wallet create-account --payment method=fee_juice,feePayer=main --node-url $NODE_URL +``` + +An example using Aztec.js: + +```javascript +const receiptForBob = await bananaCoin + .withWallet(bobWallet) + .methods.transfer(alice, amountTransferToAlice) + .send({ fee: { paymentMethod: sponsoredPaymentMethod } }) + .wait(); +``` + +To learn more about using the faucet or the sponsored fee payment method, read the full fees guide [here](./developers/tutorials/codealong/first_fees.md). + +## Portals + +### L1 to L2 messages + +In the sandbox, an L1 to L2 message is available after two blocks have progressed on L2. This is often instigated by triggering two arbitrary transactions after the L1 transaction that creates the message. + +On testnet, waiting ~1.5-2 minutes should be enough to allow the message to be made available on L2. + +### L2 to L1 messages + +On testnet,L2 to L1 messages are only available to be consumed on L1 after a block has been finalized on L1. This typically takes ~30 minutes. + +## Some things to note + +- All contracts, including account contracts and the sponsored FPC, will need to be registered in the PXE +- There will be no 'test accounts' automatically deployed, so you may need to change your Aztec.js scripts and tests +- Transactions take longer to be mined on testnet than in sandbox, so you may see timeout errors. The transaction is still sent and is still visible on a block explorer - it just needs more time for it to be mined. It is worth noting this when handling errors in your apps, so that users do not think their transaction has failed + +## Next Steps + +To play more with the Aztec testnet, check out these: + +- [Aztec Playground](https://play.aztec.network/) +- [Ecosystem](https://www.aztec.network/ecosystem) +- [Guide to run a node](the_aztec_network/index.md) diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/_category_.json b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/_category_.json new file mode 100644 index 000000000000..19d79cf9e982 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 1, + "collapsible": true, + "collapsed": true, + "label": "Decentralization Concepts" +} diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/deployments/_category_.json b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/deployments/_category_.json new file mode 100644 index 000000000000..03142f5a9e87 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/deployments/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 2, + "collapsible": true, + "collapsed": true, + "label": "Deployments" +} diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/deployments/what_is_deployment.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/deployments/what_is_deployment.md new file mode 100644 index 000000000000..055422de16cf --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/deployments/what_is_deployment.md @@ -0,0 +1,244 @@ +--- +sidebar_position: 0 +title: What is a Deployment? +--- + +An Aztec deployment is a set of the following contracts: + +| Smart Contract | Immutability | +|--------------------------------|--------------| +| Hypothetical Asset | Immutable | +| Issuer Contract | Immutable | +| Registry Contract | Immutable | +| Reward Distribution Contract | Mutable | +| Proposals Contract | Mutable | +| Governance Contract | Immutable | +| Rollup Contract | Immutable | + +## Hypothetical Asset Contract + +Hypothetical Asset would live on Ethereum L1. It may have a minter which would be the only actor capable of calling the `mint` function on the Hypothetical Asset contract. + +This is brought up to the community for discussion purposes only to illustrate how proposed governance mechanisms could work with a Hypothetical Asset. + +- **Validators** must stake the Hypothetical Asset within the instance's contract to join that instance's validator set. +- **Holders** must lock Hypothetical Asset with the Governance contract to be able to vote on proposals. +- **Provers** must deposit Hypothetical Asset in the escrow contract in order to bid for the right to create a proof for an epoch. + +## Issuer Contract + +This contract will be the sole minter of Hypothetical Asset. It will itself have an owner, which is the only actor capable of calling the `mint` function on the Hypothetical Asset ERC20 contract. + +The `mint` function will limit the amount of Hypothetical Assets that could be minted in a given time period. The reasoning behind this limit is that it makes supply expansions more predictable since "infinite mints" cannot be made. + +```mermaid +flowchart LR + Issuer -->|Calls mint| HypotheticalAsset +``` + +```solidity +contract Issuer is Ownable { + ERC20 immutable public ASSET; // Hypothetical Asset + uint256 immutable public RATE; + uint256 public timeOfLastMint; + + constructor(ERC20 _asset, uint256 _rate, address _owner) Ownable(_owner) { + ASSET = _asset; + RATE = _rate; + timeOfLastMint = block.timestamp; + } + + function mint(address _to, uint256 _amount) external onlyOwner { + uint256 maxMint = RATE * (block.timestamp - timeOfLastMint); + require(_amount <= maxMint, 'Insufficient mint available'); + + timeOfLastMint = block.timestamp; + ASSET.mint(_to, _amount); + } +} +``` + +## Registry Contract + +The governance of Aztec will be community driven - the Aztec community will be able to decide whether to upgrade or migrate to a new rollup instance. Portals / apps donʼt need to follow along with the Aztec governance and can specify the specific instance (i.e. “versionˮ) of the rollup that they view as canonical. + +Therefore it will be necessary to keep track onchain of what versions of the rollup have existed as well as what version the Aztec governance views as the current canonical rollup. Only the current canonical rollup from the perspective of Aztec governance will be eligible to claim any further Hypothetical Asset rewards. + +```mermaid +flowchart LR + Registry --> RollupContract0["Rollup Contract
Instance 0"] + Registry --> RollupContract1["Rollup Contract
Instance 1"] + Registry --> |Returns latest instance|RollupContractN["Rollup Contract
Instance n"] +``` +In practice, the Registry is an array of rollup instances that can only be inserted into by the Registryʼs owner - the Governance contract. + +```solidity +contract Registry is IRegistry, Ownable { + struct Instance { + address rollup; + uint96 blockNumber; + } + + Instance[] public instances; + + constructor(address _gov) Ownable(_gov) { + instances.push( + Instance({address: address(0), blockNumber: block.number}) + ); + } + + function addInstance(address _rollup) external onlyOwner { + instances.push( + Instance({address: _rollup, blockNumber: block.number}) + ); + } + + function getRollup() external view returns (address) { + return instances[instances.length - 1].rollup; + } + + function getInstancesLength() external view returns (uint256) { + return instances.length; + } +} +``` + +## Reward Distribution Contract + +This contract distributes ERC20 rewards only to the instance the Registry contract says is canonical. This is separated from the Registry and the Issuer so that the distribution logic can be changed without replacing the Registry. + +In practice, the following flow is expected. Infrequently, the Aztec Governance votes for the Issuer smart contract to mint a quantity of Hypothetical Asset and send them to the Distribution contract. The rollup contract will call `claim(_to)` on the Distribution contract. This checks that the calling Rollup is the current canonical Rollup before releasing a Hypothetical Asset to the rollup. + +The Rollup smart contract implements custom logic for how to split `BLOCK_REWARDS` amongst the proposers/committee/provers who provide real work in the form of electricity and hardware intensive computational resources to the Rollup smart contract. + +```mermaid +flowchart TD + Issuer -->|1. Calls mint| HypotheticalAsset + Issuer -->|2. Transfer Hypothetical Asset| RewardDistribution + RollupContract -->|3. Calls claim| RewardDistribution +``` + +```solidity +contract RewardDistribution is Ownable { + uint256 public constant BLOCK_REWARD = xxx; + + IERC20 public immutable ASSET; + IRegistry public registry; + + // constructor etc + + function claim(address _to) external returns (uint256) { + address canonical = registry.getRollup(); + require(msg.sender == canonical); + ASSET.safeTransfer(_to, BLOCK_REWARD); + return BLOCK_REWARD; + } + + function updateRegistry(IRegistry _registry) external onlyOwner { + // ... + } +} +``` + +Rollup contacts implementations should not revert if the `claim()` call reverts because the rollup is no longer canonical. Otherwise, no one could sequence the rollup anymore. + +The separation of Distribution and Issuer is primarily for code hygiene purposes. + +The protocol inflation rate is defined in the Issuer smart contract as the constant `RATE`. It is not possible to change this inflation rate once the Issuer smart contract has been deployed. Aztec Governance can vote on a proposal to deploy a new Issuer smart contract that contains a new `RATE` + +The Aztec Governance will choose how often to call `mint()` on the Issuer smart contract which will send any Hypothetical Assets to the Distribution smart contract. The Distribution smart contract defines a `BLOCK_REWARD` constant value (again cannot be changed). Every epoch, the Rollup contract can call the Distribution smart contract to claim `BLOCK_REWARD` of Hypothetical Assets from the Distribution contract. + +Both `RATE` and `BLOCK_REWARD` will be set upon deployment of the Aztec Rollup by the Aztec Governance. Both values are immutable and cannot be changed without re-deploying a new smart contract and a successful vote by Aztec Governance to switch to the new smart contracts. + +## Proposals contract + +This is the only smart contract that is able to submit proposals to the Governance contract. + +The Proposals Contract will accept proposals only from sequencers of the current canonical instance, as indicated by the Registry. + +```solidity +contract Proposals is IProposals { + + // ... imports + + IGovernance public immutable GOVERNANCE; + IRegistry public immutable REGISTRY; + uint256 public immutable N; + uint256 public immutable M; + + constructor(IGovernance _governane, IRegistry _registry, uint256 _n, uint256 _m) { + // ... + + require(N > M / 2); + require(N <= M); + } + + function vote(address _proposal) external override(IProposals) returns (bool) { + require(_proposal.code.length > 0); + // ... + + Rollup instance = Rollup(REGISTRY.getRollup()); + address proposer = instance.getCurrentProposer(); + require(msg.sender == proposer); + } + // ... +} +``` +To vote to table a proposal, the current sequencer of the canonical rollup must deploy the contracts being proposed to upgrade / migrate to, to the L1. Then the current sequencer deploys the upgrade logic i.e. `_proposal`, then call `Proposals.vote(_proposal)`. + +The Proposals contract will then count votes specifying that same `_proposal`. For a proposal to be nominated for voting, it must garner at least N votes in a single round, where a round is defined as a M consecutive L2 slots. Round 1 is L2 slots 0 - M - 1, while Round 2 is L2 slots M - 2M - 1 and so on. + +Note that a sequencer’s ability to vote is not affected by the rollupʼs availability since voting happens on the L1. + +```mermaid +flowchart TD + Issuer[Issuer] -->|1. Calls mint| HypotheticalAsset[Hypothetical Asset] + Issuer -->|2. Transfer Hypothetical Asset| RewardDistribution[Reward Distribution] + RewardDistribution -->|3. Calls claim| RollupContract[RollupContract] +``` + +If the quorum has been reahed, anyone can call `pushProposal(uint256 _roundNumber)` on the Proposals contract to send the proposal to the Governance contract for voting. As a result, only one proposal can be nominated for voting at any given round. + +## Governance contract + +This contract is the “assembly of Aztec citizensˮ that is the final arbiter of whether to enact the proposals from the Proposals Contract or not. + +This contract decides what is the canonical instance which gets block rewards. + +The Proposals contract tables proposals for voting, Holders who lock their Hypothetical Assets with the Governance contract may vote once for each Hypothetical Asset locked. They can vote either Yea or Nea. + +Once a proposal garners the minimum number of votes, and the Yay votes exceed Nay by at least the `quorum%` , the proposal can be executed by the Governance contract. + +```solidity +contract Governance is IGovernance { // ... imports + IERC20 public immutable ASSET; address public proposalsContract; // ... + constructor(IERC20 _asset, address _proposalsContract, ui nt256 _votingDelay, uint256 _votingDuration, uint256 _gracePeriod, uint256 _quorum, uin t256 _voteDifferential, uint256 _minimumVotes) { // ... + configuration = DataStructures.Configuration({ votingDelay: Timestamp.wrap(_votingDelay), // Min time between proposal creation and when voting starts votingDuration: Timestamp.wrap(_votingDuration), // Max duration of voting period + executionDelay: Timestamp.wrap(_executionDelay), // Min time between voting passing and proposal execution gracePeriod: Timestamp.wrap(_gracePeriod), // max time between proposal creation and proposal execution. + quorum: _quorum, // % of deposited ASSET that mus t participate in a vote (could be Yes or No) voteDifferential: _voteDifferential, // Yea must outweight Nea by this % to pass vote minimumVotes: _minimumVotes, // users with this much cummulative deposited ASSET must participate in the vote }) + } +// ... +function deposit(address _onBehalfOf, uint256 _amount) external override(IGovernance) { + // deposits are allowed on behalf of other addresses + users[_onBehalfOf].add(_amount); + // ... +} + +function initiateWithdraw(address _to, uint256 _amount) external override(IGovernance) returns (uint256) { + // ... + // No one can withdraw on behalf of someone else + users[msg.sender].sub(_amount); + // ... +} + +function propose(address _payload) external override(IGovernance) returns (bool) { + require(msg.sender == proposalsContract); + // ... +} + +function vote(uint256 _proposalId, uint256 _amount, bool _support) external override(IGovernance) returns (bool) {} + +function execute(uint256 _proposalId) external override(IGovernance) returns (bool) { + // execute proposal via `call()` +} +``` diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/_category_.json b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/_category_.json new file mode 100644 index 000000000000..69efe5a14964 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 1, + "collapsible": true, + "collapsed": true, + "label": "Governance" +} diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/governance.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/governance.md new file mode 100644 index 000000000000..7fef2e4e2b19 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/governance.md @@ -0,0 +1,17 @@ +--- +id: governance +sidebar_position: 3 +title: Governance Overview +--- + +import Image from "@theme/IdealImage"; + +This diagram outlines how governance works on Aztec: + + + +Sequencers put forward, or “nominate”, proposals for voting by the Aztec citizens. To do this, sequencers interact with the Governance Proposer smart contract. Nominations are “signals” by sequencers that they wish to put up for vote the execution of certain code by the Governance smart contract. + +If the Governance Proposer smart contract records a certain number of nominations/signals from sequencers, then the Governance Proposer smart contract initiates a voting process where any holders of any Hypothetical Assets can participate. + +All voting and signalling happen on the L1. diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/putting_forward_proposals.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/putting_forward_proposals.md new file mode 100644 index 000000000000..d371897e4475 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/putting_forward_proposals.md @@ -0,0 +1,17 @@ +--- +sidebar_position: 0 +title: Putting forward Proposals +--- + +Sequencers of the _current_ canonical rollup (as indicated by the Registry) can propose changes to the Aztec community for voting. In order for a proposal to be voted on through the governance process, _N_ sequencers must nominate the proposal in any given round. A round is defined as a sequence of contiguous _M_ L2 blocks. Both _N_ and _M_ are governance defined parameters. + +Sequencers can only nominate a proposal during an L2 slot for which they’ve been assigned proposer duties. This minimizes timing games and provides a lower bound on the time required to successfully bring about a vote by governance. + +A mechanism is also proposed whereby any Hypothetical Asset holder (“Holderˮ) can burn a large quantity of Hypothetical Asset to trigger a vote on a proposal, without having the sequencers nominating the proposal. Note that Hypothetical Asset holders would still need to vote to approve any proposals nominated via this mechanism. + +To nominate a proposal, a validator of the current canonical rollup would deploy two sets of contracts: + +1. The upgraded contracts they wish to upgrade to +2. `code` which can be executed by governance to upgrade into these contracts + +Then when it is their turn as the proposer, they call `vote(address _proposal)` on the `Proposals` contract, where `_proposal ` is the address of the `code` payload. Alternatively, validators can set the `GOVERNANCE_PROPOSAL_PAYLOAD=_proposal` env variable which will call `vote(address _proposal)` during a slot they're eligible to signal in. diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/upgrades.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/upgrades.md new file mode 100644 index 000000000000..c7de1d5b9e76 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/upgrades.md @@ -0,0 +1,40 @@ +--- +sidebar_position: 4 +title: Upgrades +--- + +Upgrades involve transitioning the network to a new instance of the Rollup contract. They might fix vulnerabilities, introduce new features, or enhance performance. + +## AZIP + +It is expected that the community will coordinate upgrade proposals via an AZIP process, which is a design document outlining the upgrade rationale and one that allows for collecting technical input from and by the community. + +Once developers of client software agree to support the upgrade, sequencers can begin signaling to table this proposal from a certain block height. + +## Initial Contract Deployment + +The initial deployment creates a set of contracts, as described in the [Deployment section](../deployments/what_is_deployment.md). + +## Upgrading the Rollup Contract + +1. **Proposal Creation:** + + - A new Rollup contract is deployed to the network + - Proposal code to execute the upgrade is deployed separately + +2. **Sequencer Participation:** + + - Sequencers must signal their readiness by voting through the Proposals contract. + - This vote occurs during their assigned L2 slot, as dictated by the L1 Rollup smart contract. + +3. **Governance Approval:** + - Hypothetical Asset holders vote to approve or reject the proposal. Votes are proportional to the amount of Hypothetical Asset locked in the Governance contract. + +## Proposal Execution + +After governance approval and a delay period, the proposal becomes executable: + +- Any Ethereum account can call `execute(_proposalId)` on the Governance contract. +- The `execute` function calls the proposal code, transitioning the network to the new Rollup instance. + +For a more hands-on guide to reacting to upgrades as a sequencer/validators, read [this](../../guides/reacting_to_upgrades.md). diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/voting.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/voting.md new file mode 100644 index 000000000000..911e88269457 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/governance/voting.md @@ -0,0 +1,10 @@ +--- +sidebar_position: 1 +title: Voting on Proposals +--- + +Holders have the ability to vote on proposals as long as they lock any Hypothetical Assets within the Governance contract. The act of locking the funds can be thought of as “activatingˮ the voting power of Hypothetical Asset. Locked Hypothetical Assets used to vote on a proposal must wait a delay before being withdrawn to prevent malicious governance attacks. + +Hypothetical Assets locked in the Governance contract are simply locked and not “at stakeˮ i.e. there are no slashing conditions. + +Since sequencers may be able to stake Hypothetical Assets with the rollup instances in order to join the validator set, the rollup instance could in turn lock those Hypothetical Assets in the Governance contract and vote on behalf of the sequencers. This is expected behavior. diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/proof_of_stake/_category_.json b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/proof_of_stake/_category_.json new file mode 100644 index 000000000000..b4fac2f6e6ab --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/proof_of_stake/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 3, + "collapsible": true, + "collapsed": true, + "label": "Proof of Stake" +} diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/proof_of_stake/index.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/proof_of_stake/index.md new file mode 100644 index 000000000000..1d5498bc3b6d --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/proof_of_stake/index.md @@ -0,0 +1,7 @@ +--- +sidebar_position: 1 +title: Proof of Stake system +draft: true +--- + +This doc will be updated soon. \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/proof_of_stake/slashing.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/proof_of_stake/slashing.md new file mode 100644 index 000000000000..61e0752bd431 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/proof_of_stake/slashing.md @@ -0,0 +1,52 @@ +--- +sidebar_position: 2 +title: Slashing +draft: true +--- + +We need to make sure that the chain is always (eventually) finalizing new blocks. +The following conditions are required for the chain to finalize new blocks: + +1. More than 2/3 of the committee is making attestations +2. Provers are producing proofs. + +## Avoiding network halt + +There are some actions that impact the chainʼs ability to finalize new blocks: + +### Insufficient quorum + +In the event that a significant portion of the validator set goes offline (i.e. large internet outage) and proposers are unable to get enough attestations on block proposals, the Aztec Rollup will be unable to finalize new blocks. This will require the community to engage to fix the issue and make sure new blocks are being finalized. + +In the event of a prolonged period where the Aztec Rollup is not finalizing new blocks, it may enter Based Fallback mode. The conditions that lead to [Based Fallback (forum link)](https://forum.aztec.network/t/request-for-comments-aztecs-block-production-system/6155) mode are expected to be well defined by the community of sequencers, provers, client teams and all other Aztec Rollup stakeholders and participants. + +During Based Fallback mode, anyone can propose blocks if they supply proofs for these blocks alongside them. This is in contrast to the usual condition that only the sequencer assigned to a particular slot can propose blocks during that slot. This means that the inactive validator set is bypassed and anyone can advance the chain in the event of an inactive / non-participating validator set. + +But terminally inactive validators must be removed from the validator set or otherwise we end up in Based Fallback too often. Slashing is a method whereby the validator set votes to “slash” the stake of inactive validators down to a point where they are kicked off the validator set. For example, if we set `MINIMUM_STAKING_BALANCE=50%` then as soon as 50% or more of a validator’s balance is slashed, they will be kicked out of the set. + +### Committee withholding data from the provers + +Provers need the transaction data (i.e. `TxObjects`) plus the client-side generated proofs to produce the final rollup proof, none of which are posted onchain. Client side proofs + transaction data are gossiped on the p2p instead so that committee members can re-execute block proposals and verify that the proposed state root is correct. + +Recall from the [RFC (forum link)](https://forum.aztec.network/t/request-for-comments-aztecs-block-production-system/6155) on block production that the committee is a subset of validators, randomly sampled from the entire validator set. Block proposers are sampled from this committee. ⅔ + 1 of this committee must attest to L2 block proposals before they are posted to the L1 by the block proposer. + +A malicious committee may not gossip the transaction data or proofs to the rest of the validator set, nor to the provers. As a result, no prover can produce the proof and the epoch in question will reorg by design. Recall from the RFC on block production that if no proof for epoch N is submitted to L1 by the end of epoch N+1, then epoch N + any blocks built in epoch N+1 are reorged. + +### Committee proposing an invalid state root + +Committee members who receive a block proposal from a proposer, must execute the block’s transaction to compare the resulting state root with that contained in the proposal. In theory, a committee member should only attest to a block proposal’s validity after checking for the correctness of the state root. + +A committee member could skip re-executing block proposals, for a number of reasons including saving compute, faulty client software or maliciousness. This opens up the possibility of a malicious proposer posting an invalid state transition to L1. Or a malicious entity that bribes ⅔ + 1 of the committee can obtain the required signatures to post an invalid state transition. +The epoch will not be proven and will be re-orged. + +### L1 congestion has made it impossible for provers to land proofs on time + +An honest prover who has funds at stake (i.e. posted a bond to produce the epoch proof), could be unable to post the proof on L1 due to congestion, an L1 reorg or just an inactivity of the L1 proposers (i.e. inactivity leak). + +## Slashing mechanism + +In all the previous cases, it is very hard to verify and automatically slash for these events onchain. This is mainly due to the fact that neither TxObjects nor client side proofs are posted onchain. Committee members also gossip attestations on the p2p and do not post them directly on chain. + +Therefore a slashing mechanism is required as a deterrence against the malicious behaviour by validators and to make sure that the Aztec Rollup retains liveness. The validator set votes to slash dishonest validators based on evidence that is collected onchain + offchain, and discussed and analyzed offchain (i.e. on a forum). + +A validator must aggregate BLS signatures on slashing proposals and post them to the L1 for slash execution. diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/provers-and-sequencers/_category_.json b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/provers-and-sequencers/_category_.json new file mode 100644 index 000000000000..24bb8c8ac104 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/provers-and-sequencers/_category_.json @@ -0,0 +1,7 @@ +{ + "position": 4, + "collapsible": true, + "collapsed": true, + "label": "Provers and Sequencers" +} + diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/provers-and-sequencers/index.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/provers-and-sequencers/index.md new file mode 100644 index 000000000000..5b7a43f8c724 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/provers-and-sequencers/index.md @@ -0,0 +1,15 @@ +--- +sidebar_position: 0 +title: Provers and Sequencers +draft: true +--- + +## Block Production Overview + +Both sequencing and proving in the Aztec Network are intended to be fully decentralized. + +Sequencers will be chosen via a random election, while provers will be selected by sequencers via an out-of-protocol coordination mechanism. + +The proposers in the first `C=13` slots in epoch `N+1` will accept quotes to prove epoch N from provers. The winning prover will have until the end of epoch `N+1` to produce and submit the proof to L1. + +If are you interested in running a validator node (also known as a sequencer node) or a prover node, you can refer to [the guides section](./../../guides/run_nodes/index.md). diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/provers-and-sequencers/proving_coordination_workflow.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/provers-and-sequencers/proving_coordination_workflow.md new file mode 100644 index 000000000000..53b427cc3783 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/concepts/provers-and-sequencers/proving_coordination_workflow.md @@ -0,0 +1,108 @@ +--- +sidebar_position: 1 +title: Prover Coordination Workflow +draft: true +--- + +Proposers run RFQs to obtain quotes from provers. Quotes are binding promises from provers to prove an entire epoch. The exact channel over which provers send quotes to proposers is **NOT** enshrined by the protocol. + +However, Aztec Nodes will support two optional mechanisms that provers can use to submit quotes to proposers: + +- Gossip quotes via the P2P +- Send a quote directly via HTTP (i.e. http://aztec-node:8000) + +To send a quote via the P2P, do not set the environment variable `PROVER_COORDINATION_NODE_URL` and make sure that `P2P_ENABLED` is set to `true`. + +:::note +For S&P Testnet, please make sure that you are gossiping quotes via the P2P. Set `P2P_ENABLED` to `true` and do not use `PROVER_COORDINATION_NODE_URL`. +::: + + +```rust +struct EpochProofQuote { + Signature signature; + address prover; + uint256 epochNumber; + uint256 epochInitBlock; + uint256 epochFinalBlock; + uint256 totalFees; + address rollupAddress; + uint32 basisPointFee; + uint256 bondAmount; +} +``` + +To accomplish this coordination through the Aztec node software, we extend both the `P2PClient` and `ProverNode`. + +## P2P client + +The `P2PClient` will be extended by: + +```typescript +class P2PClient { + //... + + async addEpochProofQuote(quote: EpochProofQuote): Promise { + // Add quote to quote memory pool + this.epochProofQuotePool.addQuote(quote); + + // Propagate quote via P2P + this.broadcastEpochProofQuote(quote); + } +} +``` + +This is called by the Prover Node inside `ProverNode.sendEpochProofQuote()` after it detects an epoch has ended. + +## Prover Node + +As for the Prover Node, we add `QuoteProvider` and `BondManager` interfaces. Also an `EpochMonitor` which sits on the main start loop of the Prover Node. It fetches the most recent completed epochs and checks whether the proposer accepted an `EpochProofQuote`. + +If no quote has been accepted yet, the `EpochMonitor` will call on `BondManager` and `QuoteProvider` to provide a valid quote. If the claim detected belongs to the prover, the monitor will kick off a `handleCall()` to create proving jobs. + +```typescript +interface BondManager { + ensureBond(amount: number): Promise; +} +interface QuoteProvider { + getQuote(epoch: number): Promise; +} +``` + +When the prover node first starts up, it will call `BondManager.ensureBond` to ensure it has the minimum deposit amount `PROVER_MINIMUM_ESCROW_AMOUNT` deposited in the escrow contract. If it does not, it will top up to the target deposit amount `PROVER_TARGET_ESCROW_AMOUNT`. + +Both `PROVER_MINIMUM_ESCROW_AMOUNT` and `PROVER_TARGET_ESCROW_AMOUNT` are customizable environment variables. + +The `EpochMonitor` will then get the last completed, unproven epoch and will call the `QuoteProvider` to generate a quote if the epoch has not been claimed by any provers yet. The `QuoteProvider` will be provided with all the blocks in the unproven epoch so it could perform any custom logic to determine the quote parameters, i.e., `bondAmount`, `basisPointFee`, etc. + +Alternatively, the quote provider can issue an HTTP POST to a configurable `QUOTE_PROVIDER_URL` to get the quote. The request body is JSON-encoded and contains the following fields: + +- `epochNumber`: The epoch number to prove +- `fromBlock`: The first block number of the epoch to prove +- `endBlock`: The last block number (inclusive) of the epoch to prove +- `txCount`: The total number of txs in the epoch +- `totalFees`: The accumulated total fees across all txs in the epoch + +The response is also expected in JSON and to contain `basisPointFee` and `bondAmount` fields. Optionally, the request can include a `validUntilSlot` parameter, which specifies for how many slots the quote remains valid. For example, an `EpochProofQuote` with parameters `epochProofQuote#80` and `validUntilSlot#5` means that any of the first 5 proposers in epoch 101 can “claim” this quote. + +If no `QUOTE_PROVIDER_URL` is passed along to the Prover Node, then a `SimpleQuoteProvider` is used, which always returns the same `basisPointFee` and `bondAmount` as set in the `QUOTE_PROVIDER_BASIS_POINT_FEE` and `QUOTE_PROVIDER_BOND_AMOUNT` environment variables. + +:::warning +If the `QuoteProvider` does not return a `bondAmount` or a `basisPointFee`, the Prover Node will not generate nor submit a quote to the proposer. +::: + +Separately, the Prover Node needs a watcher on L1 to detect if its quote has been selected. + +To this end, the `L1Publisher` will be extended with a new method to retrieve proof claims. + +```typescript +interface L1Publisher { + getProofClaim(): Promise; +} +``` + +The Prover Node will call this method at least once per L2 slot to check for unclaimed accepted quotes if its quotes have been accepted. You can update the polling interval using the environment variable `PROVER_NODE_POLLING_INTERVAL_MS`. + +## Run a prover + +Go to the [Prover Guide](../../guides/run_nodes/how_to_run_prover.md) to run a prover. diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/_category_.json b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/_category_.json new file mode 100644 index 000000000000..5dc689496822 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 2, + "collapsible": true, + "collapsed": true, + "label": "Guides" +} diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/reacting_to_upgrades.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/reacting_to_upgrades.md new file mode 100644 index 000000000000..9f0eb085b2a1 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/reacting_to_upgrades.md @@ -0,0 +1,20 @@ +--- +sidebar_position: 4 +title: Reacting to upgrades +--- + +This is a guide for sequencer operators to understand how to react to protocol upgrades. To learn about how upgrades work, read the [concept section](../concepts/governance/upgrades.md). + +## Sequencers signal for governance upgrades + +To signal for governance upgrades, sequencers must set their `GOVERNANCE_PROPOSER_PAYLOAD` on their sequencer node to the address of a `payload`. This will register their signal with the GovernanceProposer contract. + +:::info +the `payload` is a contract on L1 that specifies the address of the new rollup contract to be upgraded to. The payloads to be voted on during alpha-testnet will be communicated to sequencers on the forum and on community channels like discord. +::: + +This signalling phase will pass once `N` sequencers in a round of `M` L2 blocks have signalled for the same payload. Once the quorum is met, anyone can call the `executeProposal(roundNumber)` function on the Governance Proposer contract to advance the upgrade into the next stage. + +:::info +The `N` and `M` are public variables on the Governance Proposer contract, and can be read by anyone (i.e. using a `cast call`). To get the round number, you can call the `computeRound(slotNumber)` on the Governance Proposer contract. +::: diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/_category_.json b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/_category_.json new file mode 100644 index 000000000000..bab7be349b11 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 2, + "collapsible": true, + "collapsed": true, + "label": "Running nodes" +} diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/cli_reference.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/cli_reference.md new file mode 100644 index 000000000000..3efa5a9226b7 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/cli_reference.md @@ -0,0 +1,881 @@ +--- +sidebar_position: 5 +title: Cli Reference +description: A reference of the --help output when running aztec start. +keywords: + [ + aztec, + prover, + node, + blockchain, + L2, + scaling, + ethereum, + zero-knowledge, + ZK, + setup, + ] +tags: + - prover + - node + - tutorial + - infrastructure +--- + +:::note +The environment variable name corresponding to each flag is shown as $ENV_VAR on the right hand side. + +If two subsystems can contain the same configuration option, only one needs to be provided. e.g. `--archiver.blobSinkUrl` and `--sequencer.blobSinkUrl` point to the same value if the node is started with both the `--archiver` and `--sequencer` options. +::: + +```bash + MISC + + --network ($NETWORK) + Network to run Aztec on + + --auto-update (default: disabled) ($AUTO_UPDATE) + Configure auto updates + + --auto-update-url ($AUTO_UPDATE_URL) + Configure where to get updates from + + SANDBOX + + --sandbox + Starts Aztec Sandbox + + --sandbox.noPXE [value] ($NO_PXE) + Do not expose PXE service on sandbox start + + API + + --port (default: 8080) ($AZTEC_PORT) + Port to run the Aztec Services on + + --admin-port (default: 8880) ($AZTEC_ADMIN_PORT) + Port to run admin APIs of Aztec Services on on + + --api-prefix ($API_PREFIX) + Prefix for API routes on any service that is started + + ETHEREUM + + --l1-rpc-urls (default: http://localhost:8545) ($ETHEREUM_HOSTS) + List of URLs of the Ethereum RPC nodes that services will connect to (comma separated) + + --l1-chain-id (default: 31337) ($L1_CHAIN_ID) + The L1 chain ID + + --l1-mnemonic (default: test test test test test test test test test test test junk)($MNEMONIC) + Mnemonic for L1 accounts. Will be used if no publisher private keys are provided + + --l1-consensus-host-urls (default: ) ($L1_CONSENSUS_HOST_URLS) + List of URLs of the Ethereum consensus nodes that services will connect to (comma separated) + + --l1-consensus-host-api-keys (default: ) ($L1_CONSENSUS_HOST_API_KEYS) + List of API keys for the corresponding Ethereum consensus nodes + + --l1-consensus-host-api-key-headers (default: ) ($L1_CONSENSUS_HOST_API_KEY_HEADERS) + List of API key headers for the corresponding Ethereum consensus nodes. If not set, the api key for the corresponding node will be appended to the URL as ?key= + + STORAGE + + --data-directory ($DATA_DIRECTORY) + Where to store data for services. If not set, will store temporarily + + --data-store-map-size-kb ($DATA_STORE_MAP_SIZE_KB) + The maximum possible size of the data store DB in KB. Can be overridden by component-specific options. + + L1 CONTRACT ADDRESSES + + --rollup-address ($ROLLUP_CONTRACT_ADDRESS) + The deployed L1 rollup contract address + + --registry-address ($REGISTRY_CONTRACT_ADDRESS) + The deployed L1 registry contract address + + --inbox-address ($INBOX_CONTRACT_ADDRESS) + The deployed L1 -> L2 inbox contract address + + --outbox-address ($OUTBOX_CONTRACT_ADDRESS) + The deployed L2 -> L1 outbox contract address + + --fee-juice-address ($FEE_JUICE_CONTRACT_ADDRESS) + The deployed L1 Fee Juice contract address + + --staking-asset-address ($STAKING_ASSET_CONTRACT_ADDRESS) + The deployed L1 Staking Asset contract address + + --fee-juice-portal-address ($FEE_JUICE_PORTAL_CONTRACT_ADDRESS) + The deployed L1 Fee Juice portal contract address + + AZTEC NODE + + --node + Starts Aztec Node with options + + --node.archiverUrl ($ARCHIVER_URL) + URL for an archiver service + + --node.deployAztecContracts ($DEPLOY_AZTEC_CONTRACTS) + Deploys L1 Aztec contracts before starting the node. Needs mnemonic or private key to be set. + + --node.deployAztecContractsSalt ($DEPLOY_AZTEC_CONTRACTS_SALT) + Numeric salt for deploying L1 Aztec contracts before starting the node. Needs mnemonic or private key to be set. Implies --node.deployAztecContracts. + + --node.assumeProvenThroughBlockNumber ($ASSUME_PROVEN_THROUGH_BLOCK_NUMBER) + Cheats the rollup contract into assuming every block until this one is proven. Useful for speeding up bootstraps. + + --node.publisherPrivateKey ($L1_PRIVATE_KEY) + Private key of account for publishing L1 contracts + + --node.worldStateBlockCheckIntervalMS (default: 100) ($WS_BLOCK_CHECK_INTERVAL_MS) + Frequency in which to check for blocks in ms + + --node.syncMode (default: snapshot) ($SYNC_MODE) + Set sync mode to `full` to always sync via L1, `snapshot` to download a snapshot if there is no local data, `force-snapshot` to download even if there is local data. + + --node.snapshotsUrl ($SYNC_SNAPSHOTS_URL) + Base URL for downloading snapshots for snapshot sync. + + P2P SUBSYSTEM + + --p2p-enabled [value] ($P2P_ENABLED) + Enable P2P subsystem + + --p2p.blockCheckIntervalMS (default: 100) ($P2P_BLOCK_CHECK_INTERVAL_MS) + The frequency in which to check for new L2 blocks. + + --p2p.debugDisableColocationPenalty ($DEBUG_P2P_DISABLE_COLOCATION_PENALTY) + DEBUG: Disable colocation penalty - NEVER set to true in production + + --p2p.peerCheckIntervalMS (default: 30000) ($P2P_PEER_CHECK_INTERVAL_MS) + The frequency in which to check for new peers. + + --p2p.l2QueueSize (default: 1000) ($P2P_L2_QUEUE_SIZE) + Size of queue of L2 blocks to store. + + --p2p.listenAddress (default: 0.0.0.0) ($P2P_LISTEN_ADDR) + The listen address. ipv4 address. + + --p2p.p2pPort (default: 40400) ($P2P_PORT) + The port for the P2P service. Defaults to 40400 + + --p2p.p2pBroadcastPort ($P2P_BROADCAST_PORT) + The port to broadcast the P2P service on (included in the node's ENR). Defaults to P2P_PORT. + + --p2p.p2pIp ($P2P_IP) + The IP address for the P2P service. ipv4 address. + + --p2p.peerIdPrivateKey ($PEER_ID_PRIVATE_KEY) + An optional peer id private key. If blank, will generate a random key. + + --p2p.peerIdPrivateKeyPath ($PEER_ID_PRIVATE_KEY_PATH) + An optional path to store generated peer id private keys. If blank, will default to storing any generated keys in the root of the data directory. + + --p2p.bootstrapNodes (default: ) ($BOOTSTRAP_NODES) + A list of bootstrap peer ENRs to connect to. Separated by commas. + + --p2p.bootstrapNodeEnrVersionCheck ($P2P_BOOTSTRAP_NODE_ENR_VERSION_CHECK) + Whether to check the version of the bootstrap node ENR. + + --p2p.bootstrapNodesAsFullPeers ($P2P_BOOTSTRAP_NODES_AS_FULL_PEERS) + Whether to consider our configured bootnodes as full peers + + --p2p.maxPeerCount (default: 100) ($P2P_MAX_PEERS) + The maximum number of peers to connect to. + + --p2p.queryForIp ($P2P_QUERY_FOR_IP) + If announceUdpAddress or announceTcpAddress are not provided, query for the IP address of the machine. Default is false. + + --p2p.gossipsubInterval (default: 700) ($P2P_GOSSIPSUB_INTERVAL_MS) + The interval of the gossipsub heartbeat to perform maintenance tasks. + + --p2p.gossipsubD (default: 8) ($P2P_GOSSIPSUB_D) + The D parameter for the gossipsub protocol. + + --p2p.gossipsubDlo (default: 4) ($P2P_GOSSIPSUB_DLO) + The Dlo parameter for the gossipsub protocol. + + --p2p.gossipsubDhi (default: 12) ($P2P_GOSSIPSUB_DHI) + The Dhi parameter for the gossipsub protocol. + + --p2p.gossipsubDLazy (default: 8) ($P2P_GOSSIPSUB_DLAZY) + The Dlazy parameter for the gossipsub protocol. + + --p2p.gossipsubFloodPublish (default: true) ($P2P_GOSSIPSUB_FLOOD_PUBLISH) + Whether to flood publish messages. - For testing purposes only + + --p2p.gossipsubMcacheLength (default: 6) ($P2P_GOSSIPSUB_MCACHE_LENGTH) + The number of gossipsub interval message cache windows to keep. + + --p2p.gossipsubMcacheGossip (default: 3) ($P2P_GOSSIPSUB_MCACHE_GOSSIP) + How many message cache windows to include when gossiping with other peers. + + --p2p.gossipsubSeenTTL (default: 1200000) ($P2P_GOSSIPSUB_SEEN_TTL) + How long to keep message IDs in the seen cache. + + --p2p.gossipsubTxTopicWeight (default: 1) ($P2P_GOSSIPSUB_TX_TOPIC_WEIGHT) + The weight of the tx topic for the gossipsub protocol. + + --p2p.gossipsubTxInvalidMessageDeliveriesWeight (default: -20) ($P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_WEIGHT) + The weight of the tx invalid message deliveries for the gossipsub protocol. + + --p2p.gossipsubTxInvalidMessageDeliveriesDecay (default: 0.5) ($P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_DECAY) + Determines how quickly the penalty for invalid message deliveries decays over time. Between 0 and 1. + + --p2p.peerPenaltyValues (default: 2,10,50) ($P2P_PEER_PENALTY_VALUES) + The values for the peer scoring system. Passed as a comma separated list of values in order: low, mid, high tolerance errors. + + --p2p.doubleSpendSeverePeerPenaltyWindow (default: 30) ($P2P_DOUBLE_SPEND_SEVERE_PEER_PENALTY_WINDOW) + The "age" (in L2 blocks) of a tx after which we heavily penalize a peer for sending it. + + --p2p.blockRequestBatchSize (default: 20) ($P2P_BLOCK_REQUEST_BATCH_SIZE) + The number of blocks to fetch in a single batch. + + --p2p.archivedTxLimit ($P2P_ARCHIVED_TX_LIMIT) + The number of transactions that will be archived. If the limit is set to 0 then archiving will be disabled. + + --p2p.trustedPeers (default: ) ($P2P_TRUSTED_PEERS) + A list of trusted peer ENRs that will always be persisted. Separated by commas. + + --p2p.privatePeers (default: ) ($P2P_PRIVATE_PEERS) + A list of private peer ENRs that will always be persisted and not be used for discovery. Separated by commas. + + --p2p.p2pStoreMapSizeKb ($P2P_STORE_MAP_SIZE_KB) + The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKB. + + --p2p.txPublicSetupAllowList ($TX_PUBLIC_SETUP_ALLOWLIST) + The list of functions calls allowed to run in setup + + --p2p.maxTxPoolSize (default: 100000000) ($P2P_MAX_TX_POOL_SIZE) + The maximum cumulative tx size of pending txs (in bytes) before evicting lower priority txs. + + --p2p.overallRequestTimeoutMs (default: 4000) ($P2P_REQRESP_OVERALL_REQUEST_TIMEOUT_MS) + The overall timeout for a request response operation. + + --p2p.individualRequestTimeoutMs (default: 2000) ($P2P_REQRESP_INDIVIDUAL_REQUEST_TIMEOUT_MS) + The timeout for an individual request response peer interaction. + + --p2p.rollupVersion ($ROLLUP_VERSION) + The version of the rollup. + + TELEMETRY + + --tel.metricsCollectorUrl ($OTEL_EXPORTER_OTLP_METRICS_ENDPOINT) + The URL of the telemetry collector for metrics + + --tel.tracesCollectorUrl ($OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) + The URL of the telemetry collector for traces + + --tel.logsCollectorUrl ($OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) + The URL of the telemetry collector for logs + + --tel.otelCollectIntervalMs (default: 60000) ($OTEL_COLLECT_INTERVAL_MS) + The interval at which to collect metrics + + --tel.otelExportTimeoutMs (default: 30000) ($OTEL_EXPORT_TIMEOUT_MS) + The timeout for exporting metrics + + --tel.otelExcludeMetrics (default: ) ($OTEL_EXCLUDE_METRICS) + A list of metric prefixes to exclude from export + + PXE + + --pxe + Starts Aztec PXE with options + + --pxe.dataStoreMapSizeKB (default: 134217728) ($DATA_STORE_MAP_SIZE_KB) + DB mapping size to be applied to all key/value stores + + --pxe.rollupVersion ($ROLLUP_VERSION) + The version of the rollup. + + --pxe.l2BlockBatchSize (default: 200) ($PXE_L2_BLOCK_BATCH_SIZE) + Maximum amount of blocks to pull from the stream in one request when synchronizing + + --pxe.bbBinaryPath ($BB_BINARY_PATH) + Path to the BB binary + + --pxe.bbWorkingDirectory ($BB_WORKING_DIRECTORY) + Working directory for the BB binary + + --pxe.bbSkipCleanup ($BB_SKIP_CLEANUP) + True to skip cleanup of temporary files for debugging purposes + + --pxe.proverEnabled (default: true) ($PXE_PROVER_ENABLED) + Enable real proofs + + --pxe.network ($NETWORK) + External Aztec network to connect to. e.g. devnet + + --pxe.apiKey ($API_KEY) + API Key required by the external network's node + + --pxe.nodeUrl ($AZTEC_NODE_URL) + Custom Aztec Node URL to connect to + + ARCHIVER + + --archiver + Starts Aztec Archiver with options + + --archiver.blobSinkUrl ($BLOB_SINK_URL) + The URL of the blob sink + + --archiver.blobSinkMapSizeKb ($BLOB_SINK_MAP_SIZE_KB) + The maximum possible size of the blob sink DB in KB. Overwrites the general dataStoreMapSizeKB. + + --archiver.archiveApiUrl ($BLOB_SINK_ARCHIVE_API_URL) + The URL of the archive API + + --archiver.archiverPollingIntervalMS (default: 500) ($ARCHIVER_POLLING_INTERVAL_MS) + The polling interval in ms for retrieving new L2 blocks and encrypted logs. + + --archiver.archiverBatchSize (default: 100) ($ARCHIVER_BATCH_SIZE) + The number of L2 blocks the archiver will attempt to download at a time. + + --archiver.maxLogs (default: 1000) ($ARCHIVER_MAX_LOGS) + The max number of logs that can be obtained in 1 "getPublicLogs" call. + + --archiver.archiverStoreMapSizeKb ($ARCHIVER_STORE_MAP_SIZE_KB) + The maximum possible size of the archiver DB in KB. Overwrites the general dataStoreMapSizeKB. + + --archiver.rollupVersion ($ROLLUP_VERSION) + The version of the rollup. + + --archiver.viemPollingIntervalMS (default: 1000) ($ARCHIVER_VIEM_POLLING_INTERVAL_MS) + The polling interval viem uses in ms + + --archiver.ethereumSlotDuration (default: 12) ($ETHEREUM_SLOT_DURATION) + How many seconds an L1 slot lasts. + + --archiver.aztecSlotDuration (default: 24) ($AZTEC_SLOT_DURATION) + How many seconds an L2 slots lasts (must be multiple of ethereum slot duration). + + --archiver.aztecEpochDuration (default: 16) ($AZTEC_EPOCH_DURATION) + How many L2 slots an epoch lasts (maximum AZTEC_MAX_EPOCH_DURATION). + + --archiver.aztecTargetCommitteeSize (default: 48) ($AZTEC_TARGET_COMMITTEE_SIZE) + The target validator committee size. + + --archiver.aztecProofSubmissionWindow (default: 31) ($AZTEC_PROOF_SUBMISSION_WINDOW) + The number of L2 slots that a proof for an epoch can be submitted in, starting from the beginning of the epoch. + + --archiver.minimumStake (default: 100000000000000000000) ($AZTEC_MINIMUM_STAKE) + The minimum stake for a validator. + + --archiver.slashingQuorum (default: 6) ($AZTEC_SLASHING_QUORUM) + The slashing quorum + + --archiver.slashingRoundSize (default: 10) ($AZTEC_SLASHING_ROUND_SIZE) + The slashing round size + + --archiver.governanceProposerQuorum (default: 51) ($AZTEC_GOVERNANCE_PROPOSER_QUORUM) + The governance proposing quorum + + --archiver.governanceProposerRoundSize (default: 100) ($AZTEC_GOVERNANCE_PROPOSER_ROUND_SIZE) + The governance proposing round size + + --archiver.manaTarget (default: 10000000000) ($AZTEC_MANA_TARGET) + The mana target for the rollup + + --archiver.provingCostPerMana (default: 100) ($AZTEC_PROVING_COST_PER_MANA) + The proving cost per mana + + --archiver.gasLimitBufferPercentage (default: 20) ($L1_GAS_LIMIT_BUFFER_PERCENTAGE) + How much to increase calculated gas limit by (percentage) + + --archiver.maxGwei (default: 500) ($L1_GAS_PRICE_MAX) + Maximum gas price in gwei + + --archiver.maxBlobGwei (default: 1500) ($L1_BLOB_FEE_PER_GAS_MAX) + Maximum blob fee per gas in gwei + + --archiver.priorityFeeBumpPercentage (default: 20) ($L1_PRIORITY_FEE_BUMP_PERCENTAGE) + How much to increase priority fee by each attempt (percentage) + + --archiver.priorityFeeRetryBumpPercentage (default: 50) ($L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE) + How much to increase priority fee by each retry attempt (percentage) + + --archiver.fixedPriorityFeePerGas ($L1_FIXED_PRIORITY_FEE_PER_GAS) + Fixed priority fee per gas in Gwei. Overrides any priority fee bump percentage + + --archiver.maxAttempts (default: 3) ($L1_TX_MONITOR_MAX_ATTEMPTS) + Maximum number of speed-up attempts + + --archiver.checkIntervalMs (default: 1000) ($L1_TX_MONITOR_CHECK_INTERVAL_MS) + How often to check tx status + + --archiver.stallTimeMs (default: 45000) ($L1_TX_MONITOR_STALL_TIME_MS) + How long before considering tx stalled + + --archiver.txTimeoutMs (default: 300000) ($L1_TX_MONITOR_TX_TIMEOUT_MS) + How long to wait for a tx to be mined before giving up. Set to 0 to disable. + + --archiver.txPropagationMaxQueryAttempts (default: 3) ($L1_TX_PROPAGATION_MAX_QUERY_ATTEMPTS) + How many attempts will be done to get a tx after it was sent + + SEQUENCER + + --sequencer + Starts Aztec Sequencer with options + + --sequencer.validatorPrivateKey ($VALIDATOR_PRIVATE_KEY) + The private key of the validator participating in attestation duties + + --sequencer.disableValidator ($VALIDATOR_DISABLED) + Do not run the validator + + --sequencer.attestationPollingIntervalMs (default: 200) ($VALIDATOR_ATTESTATIONS_POLLING_INTERVAL_MS) + Interval between polling for new attestations + + --sequencer.validatorReexecute (default: true) ($VALIDATOR_REEXECUTE) + Re-execute transactions before attesting + + --sequencer.transactionPollingIntervalMS (default: 500) ($SEQ_TX_POLLING_INTERVAL_MS) + The number of ms to wait between polling for pending txs. + + --sequencer.maxTxsPerBlock (default: 32) ($SEQ_MAX_TX_PER_BLOCK) + The maximum number of txs to include in a block. + + --sequencer.minTxsPerBlock (default: 1) ($SEQ_MIN_TX_PER_BLOCK) + The minimum number of txs to include in a block. + + --sequencer.publishTxsWithProposals ($SEQ_PUBLISH_TXS_WITH_PROPOSALS) + Whether to publish txs with proposals. + + --sequencer.maxL2BlockGas (default: 10000000000) ($SEQ_MAX_L2_BLOCK_GAS) + The maximum L2 block gas. + + --sequencer.maxDABlockGas (default: 10000000000) ($SEQ_MAX_DA_BLOCK_GAS) + The maximum DA block gas. + + --sequencer.coinbase ($COINBASE) + Recipient of block reward. + + --sequencer.feeRecipient ($FEE_RECIPIENT) + Address to receive fees. + + --sequencer.acvmWorkingDirectory ($ACVM_WORKING_DIRECTORY) + The working directory to use for simulation/proving + + --sequencer.acvmBinaryPath ($ACVM_BINARY_PATH) + The path to the ACVM binary + + --sequencer.maxBlockSizeInBytes (default: 1048576) ($SEQ_MAX_BLOCK_SIZE_IN_BYTES) + Max block size + + --sequencer.enforceTimeTable (default: true) ($SEQ_ENFORCE_TIME_TABLE) + Whether to enforce the time table when building blocks + + --sequencer.governanceProposerPayload (default: 0x0000000000000000000000000000000000000000) ($GOVERNANCE_PROPOSER_PAYLOAD_ADDRESS) + The address of the payload for the governanceProposer + + --sequencer.maxL1TxInclusionTimeIntoSlot ($SEQ_MAX_L1_TX_INCLUSION_TIME_INTO_SLOT) + How many seconds into an L1 slot we can still send a tx and get it mined. + + --sequencer.txPublicSetupAllowList ($TX_PUBLIC_SETUP_ALLOWLIST) + The list of functions calls allowed to run in setup + + --sequencer.viemPollingIntervalMS (default: 1000) ($L1_READER_VIEM_POLLING_INTERVAL_MS) + The polling interval viem uses in ms + + --sequencer.customForwarderContractAddress (default: 0x0000000000000000000000000000000000000000) ($CUSTOM_FORWARDER_CONTRACT_ADDRESS) + The address of the custom forwarder contract. + + --sequencer.publisherPrivateKey (default: 0x0000000000000000000000000000000000000000000000000000000000000000)($SEQ_PUBLISHER_PRIVATE_KEY) + The private key to be used by the publisher. + + --sequencer.l1PublishRetryIntervalMS (default: 1000) ($SEQ_PUBLISH_RETRY_INTERVAL_MS) + The interval to wait between publish retries. + + --sequencer.gasLimitBufferPercentage (default: 20) ($L1_GAS_LIMIT_BUFFER_PERCENTAGE) + How much to increase calculated gas limit by (percentage) + + --sequencer.maxGwei (default: 500) ($L1_GAS_PRICE_MAX) + Maximum gas price in gwei + + --sequencer.maxBlobGwei (default: 1500) ($L1_BLOB_FEE_PER_GAS_MAX) + Maximum blob fee per gas in gwei + + --sequencer.priorityFeeBumpPercentage (default: 20) ($L1_PRIORITY_FEE_BUMP_PERCENTAGE) + How much to increase priority fee by each attempt (percentage) + + --sequencer.priorityFeeRetryBumpPercentage (default: 50) ($L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE) + How much to increase priority fee by each retry attempt (percentage) + + --sequencer.fixedPriorityFeePerGas ($L1_FIXED_PRIORITY_FEE_PER_GAS) + Fixed priority fee per gas in Gwei. Overrides any priority fee bump percentage + + --sequencer.maxAttempts (default: 3) ($L1_TX_MONITOR_MAX_ATTEMPTS) + Maximum number of speed-up attempts + + --sequencer.checkIntervalMs (default: 1000) ($L1_TX_MONITOR_CHECK_INTERVAL_MS) + How often to check tx status + + --sequencer.stallTimeMs (default: 45000) ($L1_TX_MONITOR_STALL_TIME_MS) + How long before considering tx stalled + + --sequencer.txTimeoutMs (default: 300000) ($L1_TX_MONITOR_TX_TIMEOUT_MS) + How long to wait for a tx to be mined before giving up. Set to 0 to disable. + + --sequencer.txPropagationMaxQueryAttempts (default: 3) ($L1_TX_PROPAGATION_MAX_QUERY_ATTEMPTS) + How many attempts will be done to get a tx after it was sent + + --sequencer.blobSinkUrl ($BLOB_SINK_URL) + The URL of the blob sink + + --sequencer.blobSinkMapSizeKb ($BLOB_SINK_MAP_SIZE_KB) + The maximum possible size of the blob sink DB in KB. Overwrites the general dataStoreMapSizeKB. + + --sequencer.archiveApiUrl ($BLOB_SINK_ARCHIVE_API_URL) + The URL of the archive API + + --sequencer.rollupVersion ($ROLLUP_VERSION) + The version of the rollup. + + --sequencer.ethereumSlotDuration (default: 12) ($ETHEREUM_SLOT_DURATION) + How many seconds an L1 slot lasts. + + --sequencer.aztecSlotDuration (default: 24) ($AZTEC_SLOT_DURATION) + How many seconds an L2 slots lasts (must be multiple of ethereum slot duration). + + --sequencer.aztecEpochDuration (default: 16) ($AZTEC_EPOCH_DURATION) + How many L2 slots an epoch lasts (maximum AZTEC_MAX_EPOCH_DURATION). + + --sequencer.aztecProofSubmissionWindow (default: 31) ($AZTEC_PROOF_SUBMISSION_WINDOW) + The number of L2 slots that a proof for an epoch can be submitted in, starting from the beginning of the epoch. + + BLOB SINK + + --blob-sink + Starts Aztec Blob Sink with options + + --blobSink.port ($BLOB_SINK_PORT) + The port to run the blob sink server on + + --blobSink.blobSinkUrl ($BLOB_SINK_URL) + The URL of the blob sink + + --blobSink.blobSinkMapSizeKb ($BLOB_SINK_MAP_SIZE_KB) + The maximum possible size of the blob sink DB in KB. Overwrites the general dataStoreMapSizeKB. + + --blobSink.archiveApiUrl ($BLOB_SINK_ARCHIVE_API_URL) + The URL of the archive API + + --blobSink.dataStoreMapSizeKB (default: 134217728) ($DATA_STORE_MAP_SIZE_KB) + DB mapping size to be applied to all key/value stores + + --blobSink.rollupVersion ($ROLLUP_VERSION) + The version of the rollup. + + --blobSink.viemPollingIntervalMS (default: 1000) ($L1_READER_VIEM_POLLING_INTERVAL_MS) + The polling interval viem uses in ms + + PROVER NODE + + --prover-node + Starts Aztec Prover Node with options + + --proverNode.archiverUrl ($ARCHIVER_URL) + URL for an archiver service + + --proverNode.acvmWorkingDirectory ($ACVM_WORKING_DIRECTORY) + The working directory to use for simulation/proving + + --proverNode.acvmBinaryPath ($ACVM_BINARY_PATH) + The path to the ACVM binary + + --proverNode.bbWorkingDirectory ($BB_WORKING_DIRECTORY) + The working directory to use for proving + + --proverNode.bbBinaryPath ($BB_BINARY_PATH) + The path to the bb binary + + --proverNode.bbSkipCleanup ($BB_SKIP_CLEANUP) + Whether to skip cleanup of bb temporary files + + --proverNode.nodeUrl ($AZTEC_NODE_URL) + The URL to the Aztec node to take proving jobs from + + --proverNode.proverId ($PROVER_ID) + Hex value that identifies the prover. Defaults to the address used for submitting proofs if not set. + + --proverNode.failedProofStore ($PROVER_FAILED_PROOF_STORE) + Store for failed proof inputs. Google cloud storage is only supported at the moment. Set this value as gs://bucket-name/path/to/store. + + --proverNode.worldStateBlockCheckIntervalMS (default: 100) ($WS_BLOCK_CHECK_INTERVAL_MS) + The frequency in which to check. + + --proverNode.worldStateProvenBlocksOnly ($WS_PROVEN_BLOCKS_ONLY) + Whether to follow only the proven chain. + + --proverNode.worldStateBlockRequestBatchSize ($WS_BLOCK_REQUEST_BATCH_SIZE) + Size of the batch for each get-blocks request from the synchronizer to the archiver. + + --proverNode.worldStateDbMapSizeKb ($WS_DB_MAP_SIZE_KB) + The maximum possible size of the world state DB in KB. Overwrites the general dataStoreMapSizeKB. + + --proverNode.archiveTreeMapSizeKb ($ARCHIVE_TREE_MAP_SIZE_KB) + The maximum possible size of the world state archive tree in KB. Overwrites the general worldStateDbMapSizeKb. + + --proverNode.nullifierTreeMapSizeKb ($NULLIFIER_TREE_MAP_SIZE_KB) + The maximum possible size of the world state nullifier tree in KB. Overwrites the general worldStateDbMapSizeKb. + + --proverNode.noteHashTreeMapSizeKb ($NOTE_HASH_TREE_MAP_SIZE_KB) + The maximum possible size of the world state note hash tree in KB. Overwrites the general worldStateDbMapSizeKb. + + --proverNode.messageTreeMapSizeKb ($MESSAGE_TREE_MAP_SIZE_KB) + The maximum possible size of the world state message tree in KB. Overwrites the general worldStateDbMapSizeKb. + + --proverNode.publicDataTreeMapSizeKb ($PUBLIC_DATA_TREE_MAP_SIZE_KB) + The maximum possible size of the world state public data tree in KB. Overwrites the general worldStateDbMapSizeKb. + + --proverNode.worldStateDataDirectory ($WS_DATA_DIRECTORY) + Optional directory for the world state database + + --proverNode.worldStateBlockHistory (default: 64) ($WS_NUM_HISTORIC_BLOCKS) + The number of historic blocks to maintain. Values less than 1 mean all history is maintained + + --proverNode.l1PublishRetryIntervalMS (default: 1000) ($PROVER_PUBLISH_RETRY_INTERVAL_MS) + The interval to wait between publish retries. + + --proverNode.customForwarderContractAddress (default: 0x0000000000000000000000000000000000000000) ($CUSTOM_FORWARDER_CONTRACT_ADDRESS) + The address of the custom forwarder contract. + + --proverNode.publisherPrivateKey (default: 0x0000000000000000000000000000000000000000000000000000000000000000)($PROVER_PUBLISHER_PRIVATE_KEY) + The private key to be used by the publisher. + + --proverNode.proverCoordinationNodeUrls (default: ) ($PROVER_COORDINATION_NODE_URLS) + The URLs of the tx provider nodes + + --proverNode.proverNodeMaxPendingJobs (default: 10) ($PROVER_NODE_MAX_PENDING_JOBS) + The maximum number of pending jobs for the prover node + + --proverNode.proverNodePollingIntervalMs (default: 1000) ($PROVER_NODE_POLLING_INTERVAL_MS) + The interval in milliseconds to poll for new jobs + + --proverNode.proverNodeMaxParallelBlocksPerEpoch (default: 32) ($PROVER_NODE_MAX_PARALLEL_BLOCKS_PER_EPOCH) + The Maximum number of blocks to process in parallel while proving an epoch + + --proverNode.proverNodeFailedEpochStore ($PROVER_NODE_FAILED_EPOCH_STORE) + File store where to upload node state when an epoch fails to be proven + + --proverNode.txGatheringIntervalMs (default: 1000) ($PROVER_NODE_TX_GATHERING_INTERVAL_MS) + How often to check that tx data is available + + --proverNode.txGatheringBatchSize (default: 10) ($PROVER_NODE_TX_GATHERING_BATCH_SIZE) + How many transactions to gather from a node in a single request + + --proverNode.txGatheringMaxParallelRequestsPerNode (default: 100) ($PROVER_NODE_TX_GATHERING_MAX_PARALLEL_REQUESTS_PER_NODE) + How many tx requests to make in parallel to each node + + --proverNode.testAccounts ($TEST_ACCOUNTS) + Whether to populate the genesis state with initial fee juice for the test accounts. + + --proverNode.sponsoredFPC ($SPONSORED_FPC) + Whether to populate the genesis state with initial fee juice for the sponsored FPC. + + --proverNode.syncMode (default: snapshot) ($SYNC_MODE) + Set sync mode to `full` to always sync via L1, `snapshot` to download a snapshot if there is no local data, `force-snapshot` to download even if there is local data. + + --proverNode.snapshotsUrl ($SYNC_SNAPSHOTS_URL) + Base URL for snapshots index. + + --proverNode.autoUpdate (default: disabled) ($AUTO_UPDATE) + The auto update mode for this node + + --proverNode.autoUpdateUrl ($AUTO_UPDATE_URL) + Base URL to check for updates + + PROVER BROKER + + --prover-broker + Starts Aztec proving job broker + + --proverBroker.proverBrokerJobTimeoutMs (default: 30000) ($PROVER_BROKER_JOB_TIMEOUT_MS) + Jobs are retried if not kept alive for this long + + --proverBroker.proverBrokerPollIntervalMs (default: 1000) ($PROVER_BROKER_POLL_INTERVAL_MS) + The interval to check job health status + + --proverBroker.proverBrokerJobMaxRetries (default: 3) ($PROVER_BROKER_JOB_MAX_RETRIES) + If starting a prover broker locally, the max number of retries per proving job + + --proverBroker.proverBrokerBatchSize (default: 100) ($PROVER_BROKER_BATCH_SIZE) + The prover broker writes jobs to disk in batches + + --proverBroker.proverBrokerBatchIntervalMs (default: 50) ($PROVER_BROKER_BATCH_INTERVAL_MS) + How often to flush batches to disk + + --proverBroker.proverBrokerMaxEpochsToKeepResultsFor (default: 1) ($PROVER_BROKER_MAX_EPOCHS_TO_KEEP_RESULTS_FOR) + The maximum number of epochs to keep results for + + --proverBroker.proverBrokerStoreMapSizeKB ($PROVER_BROKER_STORE_MAP_SIZE_KB) + The size of the prover broker's database. Will override the dataStoreMapSizeKB if set. + + --proverBroker.dataStoreMapSizeKB (default: 134217728) ($DATA_STORE_MAP_SIZE_KB) + DB mapping size to be applied to all key/value stores + + --proverBroker.viemPollingIntervalMS (default: 1000) ($L1_READER_VIEM_POLLING_INTERVAL_MS) + The polling interval viem uses in ms + + --proverBroker.rollupVersion ($ROLLUP_VERSION) + The version of the rollup. + + PROVER AGENT + + --prover-agent + Starts Aztec Prover Agent with options + + --proverAgent.proverAgentCount (default: 1) ($PROVER_AGENT_COUNT) + Whether this prover has a local prover agent + + --proverAgent.proverAgentPollIntervalMs (default: 100) ($PROVER_AGENT_POLL_INTERVAL_MS) + The interval agents poll for jobs at + + --proverAgent.proverAgentProofTypes ($PROVER_AGENT_PROOF_TYPES) + The types of proofs the prover agent can generate + + --proverAgent.proverBrokerUrl ($PROVER_BROKER_HOST) + The URL where this agent takes jobs from + + --proverAgent.realProofs (default: true) ($PROVER_REAL_PROOFS) + Whether to construct real proofs + + --proverAgent.proverTestDelayType (default: fixed) ($PROVER_TEST_DELAY_TYPE) + The type of artificial delay to introduce + + --proverAgent.proverTestDelayMs ($PROVER_TEST_DELAY_MS) + Artificial delay to introduce to all operations to the test prover. + + --proverAgent.proverTestDelayFactor (default: 1) ($PROVER_TEST_DELAY_FACTOR) + If using realistic delays, what percentage of realistic times to apply. + + P2P BOOTSTRAP + + --p2p-bootstrap + Starts Aztec P2P Bootstrap with options + + --p2pBootstrap.p2pBroadcastPort ($P2P_BROADCAST_PORT) + The port to broadcast the P2P service on (included in the node's ENR). Defaults to P2P_PORT. + + --p2pBootstrap.peerIdPrivateKeyPath ($PEER_ID_PRIVATE_KEY_PATH) + An optional path to store generated peer id private keys. If blank, will default to storing any generated keys in the root of the data directory. + + --p2pBootstrap.dataStoreMapSizeKB (default: 134217728) ($DATA_STORE_MAP_SIZE_KB) + DB mapping size to be applied to all key/value stores + + BOT + + --bot + Starts Aztec Bot with options + + --bot.nodeUrl ($AZTEC_NODE_URL) + The URL to the Aztec node to check for tx pool status. + + --bot.nodeAdminUrl ($AZTEC_NODE_ADMIN_URL) + The URL to the Aztec node admin API to force-flush txs if configured. + + --bot.pxeUrl ($BOT_PXE_URL) + URL to the PXE for sending txs, or undefined if an in-proc PXE is used. + + --bot.l1Mnemonic ($BOT_L1_MNEMONIC) + The mnemonic for the account to bridge fee juice from L1. + + --bot.l1PrivateKey ($BOT_L1_PRIVATE_KEY) + The private key for the account to bridge fee juice from L1. + + --bot.senderPrivateKey ($BOT_PRIVATE_KEY) + Signing private key for the sender account. + + --bot.senderSalt ($BOT_ACCOUNT_SALT) + The salt to use to deploys the sender account. + + --bot.recipientEncryptionSecret (default: 0x00000000000000000000000000000000000000000000000000000000cafecafe)($BOT_RECIPIENT_ENCRYPTION_SECRET) + Encryption secret for a recipient account. + + --bot.tokenSalt (default: 0x0000000000000000000000000000000000000000000000000000000000000001)($BOT_TOKEN_SALT) + Salt for the token contract deployment. + + --bot.txIntervalSeconds (default: 60) ($BOT_TX_INTERVAL_SECONDS) + Every how many seconds should a new tx be sent. + + --bot.privateTransfersPerTx (default: 1) ($BOT_PRIVATE_TRANSFERS_PER_TX) + How many private token transfers are executed per tx. + + --bot.publicTransfersPerTx (default: 1) ($BOT_PUBLIC_TRANSFERS_PER_TX) + How many public token transfers are executed per tx. + + --bot.feePaymentMethod (default: fee_juice) ($BOT_FEE_PAYMENT_METHOD) + How to handle fee payments. (Options: fee_juice) + + --bot.noStart ($BOT_NO_START) + True to not automatically setup or start the bot on initialization. + + --bot.txMinedWaitSeconds (default: 180) ($BOT_TX_MINED_WAIT_SECONDS) + How long to wait for a tx to be mined before reporting an error. + + --bot.followChain (default: NONE) ($BOT_FOLLOW_CHAIN) + Which chain the bot follows + + --bot.maxPendingTxs (default: 128) ($BOT_MAX_PENDING_TXS) + Do not send a tx if the node's tx pool already has this many pending txs. + + --bot.flushSetupTransactions ($BOT_FLUSH_SETUP_TRANSACTIONS) + Make a request for the sequencer to build a block after each setup transaction. + + --bot.l2GasLimit ($BOT_L2_GAS_LIMIT) + L2 gas limit for the tx (empty to have the bot trigger an estimate gas). + + --bot.daGasLimit ($BOT_DA_GAS_LIMIT) + DA gas limit for the tx (empty to have the bot trigger an estimate gas). + + --bot.contract (default: TokenContract) ($BOT_TOKEN_CONTRACT) + Token contract to use + + --bot.maxConsecutiveErrors ($BOT_MAX_CONSECUTIVE_ERRORS) + The maximum number of consecutive errors before the bot shuts down + + --bot.stopWhenUnhealthy ($BOT_STOP_WHEN_UNHEALTHY) + Stops the bot if service becomes unhealthy + + --bot.ammTxs ($BOT_AMM_TXS) + Deploy an AMM and send swaps to it + + TXE + + --txe + Starts Aztec TXE with options + + FAUCET + + --faucet + Starts the Aztec faucet + + --faucet.apiServer (default: true) + Starts a simple HTTP server to access the faucet + + --faucet.apiServerPort (default: 8080) + The port on which to start the api server on + + --faucet.viemPollingIntervalMS (default: 1000) ($L1_READER_VIEM_POLLING_INTERVAL_MS) + The polling interval viem uses in ms + + --faucet.l1Mnemonic ($MNEMONIC) + The mnemonic for the faucet account + + --faucet.mnemonicAccountIndex ($FAUCET_MNEMONIC_ACCOUNT_INDEX) + The account to use + + --faucet.interval (default: 3600000) ($FAUCET_INTERVAL_MS) + How often the faucet can be dripped + + --faucet.ethAmount (default: 1.0) ($FAUCET_ETH_AMOUNT) + How much eth the faucet should drip per call + + --faucet.l1Assets ($FAUCET_L1_ASSETS) + Which other L1 assets the faucet is able to drip +``` diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/how_to_run_full_node.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/how_to_run_full_node.md new file mode 100644 index 000000000000..201237b3e2aa --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/how_to_run_full_node.md @@ -0,0 +1,121 @@ +--- +sidebar_position: 1 +title: How to Run a Full Node +description: A comprehensive guide to setting up and running an Aztec full node on testnet, including infrastructure requirements, configuration options, and troubleshooting tips. +keywords: [aztec, node, blockchain, L2, scaling, ethereum, setup, tutorial] +tags: + - node + - tutorial + - infrastructure +--- + +## Background + +The Aztec full node is a critical infrastructure component that allows users to interact with the Aztec network. Full nodes provide a connection to the network and allow users to send and receive transactions and state updates without relying on a third party. Running a full node is a great way to support the Aztec network and get involved with the community. Running your own full node is the most privacy preserving way to interact with the Aztec network. + +## Prerequisites + +Before following this guide, make sure you: + +- Have the `aztec` tool [installed](../../../developers/getting_started.md#install-the-sandbox) +- You are using the latest version for the testnet by running `aztec-up -v latest` +- Are running a Linux or MacOS machine with access to a terminal + +Join the [Discord](https://discord.gg/aztec) to connect with the community and get help with your setup. + +## Setting Up Your Full Node + +This guide will describe how to setup your sequencer using the `aztec start` command. For more advanced setups, refer to the Advanced Configuration section below. + +The `aztec start` tool is a one-stop-shop for running your sequencer on any Aztec Network. It assigns default values to several config variables based on a `--network` flag and launches a docker container running the sequencer software. + +To use the `aztec start` command, you need to obtain the following: + +#### RPCs + +- An L1 execution client (for reading transactions and state). It can be specified via the `--l1-rpc-urls` flag when using `aztec start` or via the env var `ETHEREUM_HOSTS`. + +- An L1 consensus client (for blobs). It can be specified via the `--l1-consensus-host-urls` flag when using `aztec start` or via the env var `L1_CONSENSUS_HOST_URLS`. You can provide fallback URLs by separating them with commas. + +- To reduce load on your consensus endpoint, the Aztec sequencer supports an optional remote server that serves blobs to the client. You can pass your own or use one provided by a trusted party via the `--sequencer.blobSinkUrl` flag when using `aztec start`, or via the env var `BLOB_SINK_URL`. + +#### Networking + +You MUST forward your ports. Your router must send UDP and TCP traffic on port `40400` (unless you changed the default) to your IP address on your local network. Failure to do so may result in your sequencer not participating on the p2p network. + +As a tip, configure your router to give your MAC address the same IP address every time it does a DHCP refresh. + +You also need to grab your external IP address and pass it along to the `--p2p.p2pIp` when using `aztec start`. + +## Starting Your Full Node + +```bash +aztec start --node --archiver \ + --network alpha-testnet \ + --l1-rpc-urls https://example.com \ + --l1-consensus-host-urls https://example.com \ + --p2p.p2pIp 999.99.999.99 +``` + +:::tip + +For a full overview of all available commands, check out the [CLI reference sheet](./cli_reference.md). +::: + +:::tip + +If you are unable to determine your public ip. Running the command `curl ifconfig.me` can retrieve it for you. +::: + +## Advanced Configuration + +### Using Environment Variables + +Every flag in the `aztec start` command corresponds to an environment variable. You can see the variable names by running `aztec start --help`. A reference is provided [here](./cli_reference.md). + +For example: + +- `--l1-rpc-urls` maps to `ETHEREUM_HOSTS` +- `--l1-consensus-host-urls` maps to `L1_CONSENSUS_HOSTS_URLS` + +You can create a `.env` file with these variables: + +```bash +ETHEREUM_HOSTS=https://example.com +L1_CONSENSUS_HOST_URLS=https://example.com +# Add other configuration variables as needed +``` + +Then source this file before running your command: + +```bash +source .env +aztec start --network alpha-testnet --archiver --node # other flags... +``` + +### Using a Docker Compose + +If you would like to run in a docker compose, you can use a configuration like the one below: + +```yml +name: aztec-node +services: + node: + network_mode: host # Optional, run with host networking + image: aztecprotocol/aztec:latest + environment: + ETHEREUM_HOSTS: "" # update with L1 execution client URL + L1_CONSENSUS_HOST_URLS: "" # update with L1 consensus client URL + DATA_DIRECTORY: /data + P2P_IP: $P2P_IP + LOG_LEVEL: debug + entrypoint: > + sh -c 'node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --network alpha-testnet start --node --archiver' + ports: + - 40400:40400/tcp + - 40400:40400/udp + - 8080:8080 + + volumes: + - /home/my-node/node:/data # Local directory +``` diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/how_to_run_prover.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/how_to_run_prover.md new file mode 100644 index 000000000000..e085b1306b35 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/how_to_run_prover.md @@ -0,0 +1,137 @@ +--- +sidebar_position: 3 +title: How to Run a Prover Node +description: A comprehensive guide to setting up and running an Aztec Prover node on testnet or mainnet, including hardware requirements, configuration options, and performance optimization tips. +keywords: + [ + aztec, + prover, + node, + blockchain, + L2, + scaling, + ethereum, + zero-knowledge, + ZK, + setup, + tutorial, + ] +tags: + - prover + - node + - tutorial + - infrastructure +--- + +import { AztecTestnetVersion } from '@site/src/components/Snippets/general_snippets'; + +## Background + +Prover nodes are a critical part of the Aztec network's infrastructure. They generate cryptographic proofs that attest to the correctness of public transactions, ultimately producing a single rollup proof that is submitted to Ethereum. + +Operating a prover node requires a solid grasp of blockchain protocols, cryptographic systems, DevOps best practices, and high-performance hardware. It’s a resource-intensive role typically undertaken by experienced engineers or specialized teams due to its technical and operational complexity. + +## Prerequisites + +Before following this guide, make sure you: + +- Have the `aztec` tool [installed](../../../developers/getting_started.md#install-the-sandbox) +- Have sufficient hardware resources for proving operations +- Your confidence level is expected to be around "I'd be able to run a Prover _without_ this guide" + +## Understanding Prover Architecture + +The Aztec prover involves three key components: the Prover Node, the Proving Broker, and the Proving Agent. + +#### Prover Node + +The Prover Node is responsible for polling the L1 for unproven epochs and initiating the proof process. When an epoch is ready to be proven, the prover node creates proving jobs and distributes them to the broker. The Prover Node is also responsible for submitting the final rollup proof to the rollup contract. + +- **Resources**: Up to 8 cores, 16GB RAM, ~1TB disk for storing state. + +#### Proving Broker + +Manages a queue of proving jobs, distributing them to available agents and forwarding results back to the node. + +- **Resources**: Up to 4 cores, 16GB RAM, ~1GB disk. + +#### Proving Agents + +Executes the actual proof jobs. Agents are stateless, fetch work from the broker, and return the results. + +- **Resources**: Each agent may use up to 16 cores and 128GB RAM. + +## Setting Up Your Prover + +### Using Docker Compose + +```yml +name: aztec-prover +services: + prover-node: + image: aztecprotocol/aztec:latest # Always refer to the docs to check that you're using the correct image. + command: + - node + - --no-warnings + - /usr/src/yarn-project/aztec/dest/bin/index.js + - start + - --prover-node + - --archiver + - --network + - alpha-testnet + depends_on: + broker: + condition: service_started + required: true + environment: + # PROVER_COORDINATION_NODE_URL: "http://:8080" # this can point to your own validator - using this replaces the need for the prover node to be on the P2P network and uses your validator as a sentry node of some sort. + # P2P_ENABLED: "false" # Switch to false if you provide a PROVER_COORDINATION_NODE_URL + DATA_DIRECTORY: /data + DATA_STORE_MAP_SIZE_KB: "134217728" + ETHEREUM_HOSTS: # EL RPC endpoint + L1_CONSENSUS_HOST_URLS: # CL RPC endpoint + LOG_LEVEL: info + PROVER_BROKER_HOST: http://broker:8080 + PROVER_PUBLISHER_PRIVATE_KEY: # The node needs to publish proofs to L1. Replace with your private key + ports: + - "8080:8080" + - "40400:40400" + - "40400:40400/udp" + volumes: + - /home/my-node/node:/data # Local directory + + agent: + image: aztecprotocol/aztec:latest # Always refer to the docs to check that you're using the correct image. + command: + - node + - --no-warnings + - /usr/src/yarn-project/aztec/dest/bin/index.js + - start + - --prover-agent + - --network + - alpha-testnet + environment: + PROVER_AGENT_COUNT: "1" + PROVER_AGENT_POLL_INTERVAL_MS: "10000" # Just to reduce the log spamming if you're using debug logging. + PROVER_BROKER_HOST: http://broker:8080 + PROVER_ID: # this should be the address corresponding to the PROVER_PUBLISHER_PRIVATE_KEY you set on the node. + pull_policy: always + restart: unless-stopped + + broker: + image: aztecprotocol/aztec:latest # Always refer to the docs to check that you're using the correct image. + command: + - node + - --no-warnings + - /usr/src/yarn-project/aztec/dest/bin/index.js + - start + - --prover-broker + - --network + - alpha-testnet + environment: + DATA_DIRECTORY: /data + ETHEREUM_HOSTS: # Your EL RPC endpoint + LOG_LEVEL: info + volumes: + - /home/my-node/node:/data # Local directory +``` diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/how_to_run_sequencer.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/how_to_run_sequencer.md new file mode 100644 index 000000000000..923e8ce5b121 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/how_to_run_sequencer.md @@ -0,0 +1,303 @@ +--- +sidebar_position: 2 +title: How to Run a Sequencer Node +description: A comprehensive guide to setting up and running an Aztec Sequencer node on testnet, including infrastructure requirements, configuration options, and troubleshooting tips. +keywords: + [ + aztec, + sequencer, + node, + blockchain, + L2, + scaling, + ethereum, + validator, + setup, + tutorial, + ] +tags: + - sequencer + - node + - tutorial + - infrastructure +--- + +## Background + +The Aztec sequencer node is critical infrastructure responsible for ordering transactions and producing blocks. + +The sequencer node takes part in three key actions: + +1. Assemble unprocessed transactions and propose the next block +2. Attest to correct execution of txs in the proposed block (if part of validator committee) +3. Submit the successfully attested block to L1 + +When transactions are sent to the Aztec network, sequencer nodes bundles them into blocks, checking various constraints such as gas limits, block size, and transaction validity. Before a block can be published, it must be validated by a committee of other sequencer nodes (validators in this context) who re-execute public transactions and verify private function proofs so they can attest to correct execution. These validators attest to the block's validity by signing it, and once enough attestations are collected (two-thirds of the committee plus one), the sequencer can submit the block to L1. + +The archiver component complements this process by maintaining historical chain data. It continuously monitors L1 for new blocks, processes them, and maintains a synchronized view of the chain state. This includes managing contract data, transaction logs, and L1-to-L2 messages, making it essential for network synchronization and data availability. + +## Setup + +### Requirements + +A computer running Linux or MacOS with the following specifictions: + +- CPU: 8-cores +- RAM: 16 GiB +- Storage: 1 TB SSD + +A Network connection of at least 25 Mbps up/down. + +### Installation + +import { General, Fees } from '@site/src/components/Snippets/general_snippets'; + + + +Now install the latest testnet version of aztec: `aztec-up -v latest` + +Join the [Discord](https://discord.gg/aztec) to connect with the community and get help with your setup. + +## Sequencer Quickstart + +With the alpha-testnet version of the aztec tools, you now need to define required variables for your node. + +The following variable names are specific to the `aztec start` command, set them as variables in the terminal or inline before the command. + +- `ETHEREUM_HOSTS=`: One or more comma-separated public rpc provider url(s). NB - don't share your access token +- `L1_CONSENSUS_HOST_URLS=`: One or more comma-separated public rpc provider url(s) that supports consensus client requests +- `VALIDATOR_PRIVATE_KEY="Ox"`: Private key of testnet L1 EOA that holds Sepolia ETH (0.01 Sepolia ETH can get you started) +- `COINBASE="0x"`: Recipient of block rewards (for node security on mainnet, this should be a different address to the validator eoa) +- `P2P_IP="x.x.x.x"`: IP address of computer running the node (you can get this by running, `curl api.ipify.org`, on your node) + +Now in a terminal start your node as a sequencer and archiver: + +If the above variables are set you can simply use: `aztec start --node --archiver --sequencer --network alpha-testnet` + +Otherwise you can specify values via the CLI flags (using values in place of the variable names): + +```bash +aztec start --node --archiver --sequencer \ + --network alpha-testnet \ + --l1-rpc-urls $ETHEREUM_HOSTS \ + --l1-consensus-host-urls $L1_CONSENSUS_HOST_URLS \ + --sequencer.validatorPrivateKey $VALIDATOR_PRIVATE_KEY \ + --sequencer.coinbase $COINBASE \ + --p2p.p2pIp $P2P_IP +``` + +**Additional Parameters**: The comprehensive list of parameters can be seen via: `aztec help start`. For example: + +``` +--p2p.p2pPort (default: 40400) ($P2P_PORT) + The port for the P2P service. +``` + +### Port forwarding + +For some restricted environments, you may need to explicity forward the p2p port (default: 40400) to your local node ip address. + +This is often in a router's advanced network settings if required. + +### Next steps + +To add your sequencer you'll need the following few values, as well as `ETHEREUM_HOSTS` from before: + +- `STAKING_ASSET_HANDLER="0xF739D03e98e23A7B65940848aBA8921fF3bAc4b2"`: Constant L1 contract address +- `L1_CHAIN_ID="11155111"`: Sepolia chainid +- `PRIVATE_KEY="0x`: private key of account with sepolia eth to make transaction (eg can use funded validator key) + +Then run the aztec command to add your address as an L1 validator, with rpc url(s) for Etheruem L1 execution requests: + +```bash +aztec add-l1-validator --staking-asset-handler=0xF739D03e98e23A7B65940848aBA8921fF3bAc4b2 \ + --l1-rpc-urls $ETHEREUM_HOSTS \ + --l1-chain-id 11155111 \ + --private-key "0x" \ + --attester "0x" \ + --proposer-eoa "0x" +``` + +**Tip**: Use `aztec help add-l1-validator` for further parameter details. + +:::note Validator Quota Filled + +In the absence of real-world staking incentives, becoming a validator is throttled with time, so you may see `ValidatorQuotaFilledUntil(uint256 _timestamp)` at the beginning of the text returned. + +The timestamp is when the next round of sequencers can be added as validators, so try again right after that. + +::: + +## Deeper dive + +This guide will describe how to setup your sequencer using the `aztec start` command. For more advanced setups, refer to the Advanced Configuration section below. + +The `aztec start` tool is a one-stop-shop for running your sequencer on any Aztec Network. It assigns default values to several config variables based on a `--network` flag and launches a docker container running the sequencer software. + +To use the `aztec start` command, you need to obtain the following: + +#### RPCs + +- An L1 execution client (for reading transactions and state). It can be specified via the `--l1-rpc-urls` flag when using `aztec start` or via the env var `ETHEREUM_HOSTS`. Popular execution clients include [Geth](https://geth.ethereum.org/) or [Nethermind](https://nethermind.io/). You can run your own node or use a service like [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/). + +- An L1 consensus client (for blobs). It can be specified via the `--l1-consensus-host-urls` flag when using `aztec start` or via the env var `L1_CONSENSUS_HOST_URLS`. Popular consensus clients include [Lighthouse](https://lighthouse.sigmaprime.io/) or [Prysm](https://prysmaticlabs.com/). Not all RPC providers support consensus endpoints, [Quicknode](https://www.quicknode.com/) and [dRPC](https://drpc.org/) have been known to work for consensus endpoints. + +- To reduce load on your consensus endpoint, the Aztec sequencer supports an optional remote server that serves blobs to the client. This is often called a "blob sink" or "blob storage service". You can pass your own or use one provided by a trusted party via the `--sequencer.blobSinkUrl` flag when using `aztec start`, or via the env var `BLOB_SINK_URL`. Some providers like [Alchemy](https://www.alchemy.com/) offer blob storage services as part of their infrastructure offerings. + +#### Ethereum Keys + +You will need an Ethereum private key and the corresponding public address. The private key is set via the `--sequencer.validatorPrivateKey` flag while the public address should be specified via the `--sequencer.coinbase ` flag. + +The private key is needed as your validator will post blocks to Ethereum, and the public address will be the recipient of any block rewards. + +Disclaimer: you may want to generate and use a new Ethereum private key. + +#### Networking + +You MUST forward your ports. Your router must send UDP and TCP traffic on port `40400` (unless you changed the default) to your IP address on your local network. Failure to do so may result in your sequencer not participating on the p2p network. + +As a tip, configure your router to give your MAC address the same IP address every time it does a DHCP refresh. + +You also need to grab your external IP address and pass it along to the `--p2p.p2pIp` when using `aztec start`. + +#### Sepolia ETH + +You'll need Sepolia ETH to cover gas costs. Here are some options: + +- Use a PoW faucet like [Sepolia PoW Faucet](https://sepolia-faucet.pk910.de/) +- Ask in our Discord community (and remember to pay it forward when you can!) + +### Now Start Your Sequencer + +To boot up a sequencer using `aztec start`, run the following command: + +```bash +aztec start --node --archiver --sequencer \ + --network alpha-testnet \ + --l1-rpc-urls https://example.com \ + --l1-consensus-host-urls https://example.com \ + --sequencer.validatorPrivateKey 0xYourPrivateKey \ + --sequencer.coinbase 0xYourAddress \ + --p2p.p2pIp 999.99.999.99 \ + --p2p.maxTxPoolSize 1000000000 +``` + +:::tip + +For a full overview of all available commands, check out the [CLI reference sheet](./cli_reference.md). +::: + +:::tip + +If you are unable to determine your public ip. Running the command `curl ipv4.icanhazip.com` can retrieve it for you. +::: + +### Register as a Validator + +Once your node is fully synced, you can register as a validator using the `add-l1-validator` command: + +```bash +aztec add-l1-validator \ + --l1-rpc-urls https://eth-sepolia.g.example.com/example/your-key \ + --private-key your-private-key \ + --attester your-validator-address \ + --proposer-eoa your-validator-address \ + --staking-asset-handler 0xF739D03e98e23A7B65940848aBA8921fF3bAc4b2 \ + --l1-chain-id 11155111 +``` + +:::warning + +You may see a warning when trying to register as a validator. To maintain network health there is a daily quota for validators to join the validator set. If you are not able to join, it could mean that today's quota of validators has already been added to the set. If you see this, you can try again later. Read [our blog post](https://aztec.network/blog/what-is-aztec-testnet) for more info. + +::: + +## Advanced Configuration + +### Using Environment Variables + +Every flag in the `aztec start` command corresponds to an environment variable. You can see the variable names by running `aztec start --help`. A reference is provided [here](./cli_reference.md). + +For example: + +- `--l1-rpc-urls` maps to `ETHEREUM_HOSTS` +- `--l1-consensus-host-urls` maps to `L1_CONSENSUS_HOSTS_URLS` + +You can create a `.env` file with these variables: + +```bash +ETHEREUM_HOSTS=https://example.com +L1_CONSENSUS_HOST_URLS=https://example.com +# Add other configuration variables as needed +``` + +Then source this file before running your command: + +```bash +source .env +aztec start --network alpha-testnet --archiver --node --sequencer # other flags... +``` + +### Using a Docker Compose + +If you would like to run in a docker compose, you can use a configuration like the one below: + +```yml +name: aztec-node +services: + node: + network_mode: host # Optional, run with host networking + image: aztecprotocol/aztec:latest + environment: + ETHEREUM_HOSTS: "" + L1_CONSENSUS_HOST_URLS: "" + DATA_DIRECTORY: /data + VALIDATOR_PRIVATE_KEY: $VALIDATOR_PRIVATE_KEY + P2P_IP: $P2P_IP + LOG_LEVEL: debug + entrypoint: > + sh -c 'node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --network alpha-testnet start --node --archiver --sequencer' + ports: + - 40400:40400/tcp + - 40400:40400/udp + - 8080:8080 + + volumes: + - /home/my-node/node:/data # Local directory +``` + +## Troubleshooting + +### L1 Access + +If you're hosting your own Ethereum execution or consensus client locally (rather than using an external RPC like Alchemy), you need to ensure that the prover node inside Docker can reach it. + +By default, Docker runs containers on a bridge network that isolates them from the host machine's network interfaces. This means localhost inside the container won't point to the host's localhost. + +To fix this: + +Option 1: Use the special hostname host.docker.internal +This tells Docker to route traffic from the container to the host machine. For example: + +```bash +--l1-rpc-urls http://host.docker.internal:8545 +``` + +Option 2: Add a host network entry to your Docker Compose file (advanced users) +This gives your container direct access to the host's network stack, but removes Docker's network isolation. Add to your `docker-compose.yml` + +```yaml +network_mode: "host" +``` + +⚠️ Note: network_mode: "host" only works on Linux. On macOS and Windows, use `host.docker.internal`. + +:::info + +You can run your own Sepolia ETH Node. However, at the moment only [`geth`](https://github.com/ethereum/go-ethereum) and [`reth`](https://github.com/paradigmxyz/reth) nodes are confirmed to work reliably with the Aztec client. + +::: + +Once you have your node running, head to the [Aztec Discord](https://discord.gg/aztec) to interact with other network operators. + +Happy sequencing! diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/index.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/index.md new file mode 100644 index 000000000000..9b045360b97f --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/index.md @@ -0,0 +1,42 @@ +--- +id: index +sidebar_position: 0 +title: Run a Node, Sequencer, or Prover +--- + +## Running Aztec Nodes + +
+ + +

Run Aztec Sequencer Nodes

+
+ + Participate in the Aztec protocol as a sequencer that orders transactions in a block and proposed them to the L1. Runs on consumer hardware. + +
+ + +

Run Aztec Full Node

+
+ + Participate in the Aztec protocol by running a full node that allows you to connect with the network. Runs on consumer hardware. + +
+ + +

Run Aztec Prover Nodes

+
+ + Participate in the Aztec protocol as a prover, proving the rollup integrity that is pivotal to the protocol. Runs on hardware fit for data centers. + +
+ + +

FAQs & Common Issues/Resolutions

+
+ + Frequently asked questions and common issues that a node operators may face, and how to resolve them. + +
+
diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/operator_faq.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/operator_faq.md new file mode 100644 index 000000000000..08251ccfc9ae --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/run_nodes/operator_faq.md @@ -0,0 +1,47 @@ +--- +sidebar_position: 4 +title: FAQs & Common Issues/Resolutions +description: Frequently asked questions and common issues that a node operators may face, and how to resolve them. +keywords: [aztec, sequencer, node, validator, setup] +tags: + - sequencer + - node + - tutorial + - infrastructure +--- + +Here is a list of common issues node operators may face. If you don't find your issue here, please check the [Aztec discord server](https://discord.gg/aztec), namely the `# operator | faq` channel. + +## "Error getting slot number" + +If it is regarding a beacon call, it has failed to the beacon rpc call. If it is regarding the execution endpoint, then it is likely just reporting. + +## Update aztec alpha-testnet version + +To make sure you're using the latest version, run: `aztec-up alpha-testnet`, then restart your node. + +## "rpc rate", "quota limit" + +Registering with your rpc url provider will give you a token that may permit more requests. + +## "No blob bodies found", "Unable to get blob sidecar, Gateway Time-out (504)" + +Check `L1_CONSENSUS_HOST_URLS` (for the beacon chain), if you see it regularly likely also a rate/limit issue. + +## "Insufficient L1 funds" + +EOA needs sepolia eth, use faucet. + +## "CodeError: stream reset" + +Seen occasionally in logs. Reason: ... +Ignore. + +## "SYNC_BLOCK failed" + +`ERROR: world-state:database Call SYNC_BLOCK failed: Error: Can't synch block: block state does not match world state` + +- Stop aztec +- Delete current snapshot: `rm -rf ~/.aztec/0.87.8/data/archiver` +- Update to latest version: `aztec-up -v latest` +- Start aztec diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/useful_commands.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/useful_commands.md new file mode 100644 index 000000000000..108bd05d76e9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/guides/useful_commands.md @@ -0,0 +1,90 @@ +--- +sidebar_position: 5 +title: Useful commands +--- + +These commands are useful to sequencer operators. If you're trying to do something that is not listed here, ask in the appropriate discord channel. + +## Prerequisites + +- You'll need a way to query the L1 contracts, this guide assumes you have `foundry` installed. +- Have the `aztec` tool [installed](../../developers/getting_started.md#install-the-sandbox) +- Ethereum EL RPC endpoint + +## Basics + +### Get the Registry contract address + +The Registry contract is your entrypoint into almost all other contracts for a particular deployment of the Aztec Network. Armed with this address, you can retrieve almost all other useful contracts. + +Assume there are two "deployments" of Aztec i.e. an `alpha-testnet` and a `ignition-testnet`. Then each deployment will have a unique Registry contract that does not change with upgrades. If a governance upgrade on `alpha-testnet` deploys a new rollup contract, the Registry contract for the `alpha-testnet` deployment will not change. + + + +### Get the Rollup contract + +Run + +```bash +cast call "getCanonicalRollup()" --rpc-url https://example.com +``` + +:::info +The rest of the guide will omit the `--rpc-url` flag for legibility. +::: + +## Query the Validator Set + +### Retrieve a list of all validators + +Run + +```bash +cast call "getAttesters()" --rpc-url +``` + +### What is the status of a particular validator? + +Run + +```bash +cast call "getInfo(address)" +``` + +This will return in order: 1) the validator's balance 2) their `withdrawer` address 3) their `proposer` address and 4) their current status. + +| Status | Meaning | +| ------ | -------------------------------------------------------------------------------------------------------------------- | +| 0 | The validator is not in the validator set | +| 1 | The validator is currently in the validator set. | +| 2 | The validator is not active in the set. This could mean that they initiated a withdrawal and are awaiting final exit | +| 3 | The validator has completed their exit delay and can be exited from the validator set | + +## Governance Upgrades + +### Get the GovernanceProposer contract + +First get the Governance contract from the Registry, and query it for the GovernanceProposer contract. + +```bash +cast call "getGovernance()" +cast call "governanceProposer()" +``` + +### Check what the governance quorums are + +Run + +```bash +cast call "M()" # The size of any signalling round in L2 blocks. +cast call "N()" # The number of signals that must be received in any single signalling round. +``` + +### Check how many signals a payload has received + +You can check how many signals a `payload` has received in a specific round. To do so, you need the rollup address, the payload address and the round number. To find the round number at a specific point in time, you can compute it using an L2 slot number. + +```bash +cast call "computeRound(uint256)" # returns a round number (in hex) +cast call "yeaCount(address,uint256,address)" # round number should is an integer +``` diff --git a/docs/versioned_docs/version-v0.88.0/the_aztec_network/index.md b/docs/versioned_docs/version-v0.88.0/the_aztec_network/index.md new file mode 100644 index 000000000000..0b822da884c9 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/the_aztec_network/index.md @@ -0,0 +1,62 @@ +--- +id: index +sidebar_position: 0 +title: The Aztec Network +--- + +In this section, you can explore how governance works on Aztec, the types of nodes that can be run, and how to contribute to decentralization on the Aztec Testnet. + +## Learn about governance on Aztec + +
+ + +

Governance

+
+ + An introduction to Aztec's governance model + +
+
+ + + +## Run a node + +
+ + +

Run a validator node (sequencer)

+
+ +
+ + + +

Run a prover node

+
+ +
+
+ +## FAQs + +
+ + +

FAQs & Common Issues/Resolutions

+
+ +
+
diff --git a/docs/versioned_docs/version-v0.88.0/try_testnet.md b/docs/versioned_docs/version-v0.88.0/try_testnet.md new file mode 100644 index 000000000000..6072d1997d58 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/try_testnet.md @@ -0,0 +1,66 @@ +--- +title: Try Testnet +keywords: [testnet, aztec, migration] +tags: [testnet, migration] +id: try_testnet +--- + +## Explore testnet + +- [Read the announcement in our blog](https://aztec.network/blog) +- [Check out our growing ecosystem of explorers, bridges, wallets, apps, and more](https://aztec.network/ecosystem) + +## Take part + +- [Run a node](./the_aztec_network/guides/run_nodes/how_to_run_sequencer.md) +- [Interact with testnet using Playground, a tool for deploying & interacting with contracts](https://play.aztec.network/) +- [Get inspiration for what you could build](./developers/inspiration.md) + +## Develop on Aztec Testnet + +- Follow the [getting started on testnet guide](./developers/guides/getting_started_on_testnet.md) +- Try the [`testnet` branch of the Aztec Starter template Github repo](https://github.com/AztecProtocol/aztec-starter/tree/testnet) +- Follow our [tutorials](./developers/tutorials/codealong/contract_tutorials/counter_contract.md) in order to write your first contract, deploy it, and interact with it using Aztec CLI and Aztec.js + +## Chain Information + +**Version**: `0.87.8` + +**Node URL**: `https://aztec-alpha-testnet-fullnode.zkv.xyz` + +**L1 Chain ID**: `11155111` + +**Rollup Version**: `4189337207` + +**Node ENR**: `enr:-MK4QGMeY-XZOb8V-1f6rDbm_E3SGk8JqvlveqFtQ80WLUbnOapCMv3fh4UAh1SeYw5KfcqPFfk_F99cnO8jDIfS_AwGhWF6dGVjsDAwLTExMTU1MTExLThkMWNjNzAyLTE1MDEwMzQzOS0wYjZiNWY2Yy0wZWE1MWRhNIJpZIJ2NIJpcIQia0KqiXNlY3AyNTZrMaEDmQTk2B2UCjSyvQfvs1SSSYE3namSE7IzmWU_vFrHP5KDdGNwgp3Qg3VkcIKd0A` + +## Core L1 and L2 Precompiles and Contracts + +### L1 Contract Addresses + +| Contract Name | Address | +| --------------------- | -------------------------------------------- | +| Rollup | `0xee6d4e937f0493fb461f28a75cf591f1dba8704e` | +| Registry | `0x4d2cc1d5fb6be65240e0bfc8154243e69c0fb19e` | +| L1 → L2 Inbox | `0xf8e143bf3333ecdeaeaa29744bfe226b94f124a8` | +| L2 → L1 Outbox | `0x572725ffb3af63745098576152d8756c333d4525` | +| Fee Juice | `0x487ff89a8bdaefea2ad10d3e23727ccda8f845b9` | +| Staking Asset | `0x5c30c66847866a184ccb5197cbe31fce7a92eb26` | +| Fee Juice Portal | `0xa07a3be3f0b17594b394effb4ce579f54e76aba2` | +| Coin Issuer | `0xf8d7d601759cbcfb78044ba7ca9b0c0d6301a54f` | +| Reward Distributor | `0x169aa723de66698d6cfa97ae576ab2546e8465a2` | +| Governance Proposer | `0xf4bf5df1c3b2dd67a0525fc600e98ca51143a67d` | +| Governance | `0xee63e102e35f24c34b9ea09b597acfb491c94e78` | +| Slash Factory | `0x3c9ccf55a8ac3c2eeedf2ee2aa1722188fd676be` | +| Fee Asset Handler | `0x80d848dc9f52df56789e2d62ce66f19555ff1019` | +| Staking Asset Handler | `0xF739D03e98e23A7B65940848aBA8921fF3bAc4b2` | + +### L2 Contract Addresses + +| Contract Name | Address | +| ----------------- | -------------------------------------------------------------------- | +| Class Registerer | `0x0000000000000000000000000000000000000000000000000000000000000003` | +| Fee Juice | `0x0000000000000000000000000000000000000000000000000000000000000005` | +| Instance Deployer | `0x0000000000000000000000000000000000000000000000000000000000000002` | +| MultiCall | `0x0000000000000000000000000000000000000000000000000000000000000004` | +| Sponsored FPC | `0x1260a43ecf03e985727affbbe3e483e60b836ea821b6305bea1c53398b986047` | diff --git a/docs/versioned_docs/version-v0.88.0/vision.mdx b/docs/versioned_docs/version-v0.88.0/vision.mdx new file mode 100644 index 000000000000..855805e9e013 --- /dev/null +++ b/docs/versioned_docs/version-v0.88.0/vision.mdx @@ -0,0 +1,40 @@ +--- +title: Aztec's Vision +sidebar_label: Vision +sidebar_position: 0 +--- + +import Disclaimer from "@site/src/components/Disclaimers/_wip_disclaimer.mdx"; + +We are building the [Aztec Network](https://aztec.network/), a fully programmable private [ZK-rollup](https://ethereum.org/en/developers/docs/scaling/zk-rollups/) on [Ethereum](https://ethereum.org/) to enable developers to create decentralized applications with encryption and scale. + +These are our core values. + +### Privacy + +The only true zero-knowledge rollup, built with a privacy-first UTXO architecture to allow developers to build privacy preserving programmable applications. + +It refers to the ability of Aztec smart contracts to have private (encrypted) state. Aztec abstracts away many of the complexities associated with managing private state, providing developers with an interface that feels familiar, but is much more powerful. + +Aztec provides a secure, private environment for the execution of sensitive operations, ensuring private information and decrypted data are not accessible to unauthorized applications. + +When a user sends a private transaction on the network, the only information that an external observer can infer is that a transaction was sent. + +## Accessibility + +Proving transaction validity via recursive aggregation of zero-knowledge proofs, significantly reduces transaction costs, keeping the protocol accessible to the masses. + +## Trustlessness + +We believe decentralization is premised on individual rights — without widely accessible encryption, we compromise our ability to choose how we live our lives and earn our livelihoods. + +Aztec is building a permissionless, censorship-resistant, peer-to-peer network. It aims to be credibly neutral, where the same transparent rules apply to everyone, enforced by the protocol. + +Aztec will have a network of sequencers that stake tokens to participate in the network. Sequencers are responsible for aggregating transactions into a block, generating proofs of the state updates (or delegating proof generation to the prover network) and posting it to the rollup contract on Ethereum, along with any required public data for data availability. + +## Compliance + +The programmable nature of Aztec smart contracts, enables dApp developers to code privacy-preserving audit and compliance checks into apps, while fully preserving a credible neutral protocol layer. +goals, we are pioneering the cryptography and research needed to bring our next generation, privacy-preserving zk-roll-up to mainnet. + + diff --git a/docs/versioned_sidebars/version-v0.88.0-sidebars.json b/docs/versioned_sidebars/version-v0.88.0-sidebars.json new file mode 100644 index 000000000000..eacf4112f8a0 --- /dev/null +++ b/docs/versioned_sidebars/version-v0.88.0-sidebars.json @@ -0,0 +1,177 @@ +{ + "sidebar": [ + { + "type": "doc", + "id": "aztec/index", + "label": "Aztec Overview" + }, + { + "type": "category", + "label": "Concepts", + "items": [ + { + "type": "autogenerated", + "dirName": "aztec/concepts" + } + ] + }, + { + "type": "html", + "value": "Smart Contracts", + "className": "sidebar-title" + }, + { + "type": "doc", + "id": "aztec/smart_contracts_overview", + "label": "What are Smart Contracts?" + }, + { + "type": "category", + "label": "Technical Details", + "items": [ + { + "type": "autogenerated", + "dirName": "aztec/smart_contracts" + } + ] + }, + { + "type": "html", + "value": "" + }, + { + "type": "doc", + "label": "Glossary", + "id": "glossary" + }, + { + "type": "doc", + "id": "vision" + }, + { + "type": "doc", + "label": "Building in Public", + "id": "building_in_public" + }, + { + "type": "doc", + "id": "aztec_connect_sunset" + } + ], + "buildSidebar": [ + { + "type": "doc", + "id": "developers/index", + "label": "Build" + }, + { + "type": "doc", + "label": "Get Inspired", + "id": "developers/inspiration" + }, + { + "type": "html", + "value": "Getting Started", + "className": "sidebar-title" + }, + { + "type": "doc", + "label": "Quickstart", + "id": "developers/getting_started" + }, + { + "type": "link", + "label": "Aztec Starter GitHub repo", + "href": "https://github.com/AztecProtocol/aztec-starter" + }, + { + "type": "html", + "value": "" + }, + { + "type": "html", + "value": "Code-along Tutorials", + "className": "sidebar-title" + }, + { + "type": "autogenerated", + "dirName": "developers/tutorials/codealong" + }, + { + "type": "html", + "value": "" + }, + { + "type": "html", + "value": "How-to Guides", + "className": "sidebar-title" + }, + { + "type": "autogenerated", + "dirName": "developers/guides" + }, + { + "type": "html", + "value": "" + }, + { + "type": "html", + "value": "References", + "className": "sidebar-title" + }, + { + "type": "autogenerated", + "dirName": "developers/reference" + }, + { + "type": "html", + "value": "" + }, + { + "type": "doc", + "id": "try_testnet" + }, + { + "type": "doc", + "id": "migration_notes" + }, + { + "type": "doc", + "id": "sandbox_to_testnet_guide" + } + ], + "roadmapSidebar": [ + { + "type": "autogenerated", + "dirName": "aztec/roadmap" + } + ], + "nodesSidebar": [ + { + "type": "doc", + "id": "the_aztec_network/index" + }, + { + "type": "html", + "value": "Concepts", + "className": "sidebar-title" + }, + { + "type": "autogenerated", + "dirName": "the_aztec_network/concepts" + }, + { + "type": "html", + "value": "" + }, + { + "type": "html", + "value": "Guides", + "className": "sidebar-title" + }, + { + "type": "autogenerated", + "dirName": "the_aztec_network/guides" + } + ] +} diff --git a/docs/versions.json b/docs/versions.json index 2fb9e1a853e0..401db765ddd0 100644 --- a/docs/versions.json +++ b/docs/versions.json @@ -1,3 +1,3 @@ [ - "v0.87.9" + "v0.88.0" ] diff --git a/l1-contracts/package.json b/l1-contracts/package.json index 1441227b904f..215695c6dfc7 100644 --- a/l1-contracts/package.json +++ b/l1-contracts/package.json @@ -14,5 +14,6 @@ "lint:fix": "solhint --config ./.solhint.json --fix --noPrompt \"src/**/*.sol\"; forge fmt", "slither": "forge clean && forge build --build-info --skip '*/test/**' --force && slither . --checklist --ignore-compile --show-ignored-findings --config-file ./slither.config.json | tee slither_output.md", "slither-has-diff": "./slither_has_diff.sh" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" }