From 6b99527881345d7aa0dc90cfc61832432d817587 Mon Sep 17 00:00:00 2001 From: Miranda Wood Date: Thu, 2 May 2024 14:39:05 +0100 Subject: [PATCH] feat: hash logs inside circuit (#5934) ## A follow-up to #5718: - [x] - Hash logs inside the circuit (closing #1165) Complete, with some caveats. In most cases, whatever log is being broadcast can be converted to bytes with one of the traits (in `log_traits`) and sha hashed. Note we now need these traits because `sha256_slice` has (understandably) been removed, so we must define a fixed length input for generic types. The one exception to this is broadcasting a contract class which ends up emitting up to 518,400 bytes. This is still hashed in ts via a new oracle method `emit_contract_class_unencrypted_log`. I believe it's fine to hash this outside of the circuit, because correctness will be guaranteed by the bytecode commitment (as opposed to a generic log, where we need the hash to come from the circuit to trust it). - [x] - Accumulate logs length inside the circuit Complete, perhaps we should track lengths of each log to more easily split them into revertible/non-revertible later on? - [x] - Flat hash the logs in `tail` + update documentation Now that `sha256_slice` is removed, we would have to flat hash all the empty space up to max logs - unsure whether this would give us any benefit? EDIT: After some testing, it is more efficient for larger numbers of logs (~9+) in both the circuit and L1, so I have implemented flat hashing. - [x] - Add a `logsCache`, like `noteCache`, to track logs + ordering in ts in nested executions Note that the `logsCache` is only implemented (and required) in the private context for now. Public calls don't require squashing and removal of logs representing nullified notes, so an array (`allUnencryptedLogs`) will do. It currently just keeps track of ordering when we have nested calls, but will be useful for removing transient logs (#1641). - [x] - Investigate + solve issue with `tx.ts` error not throwing I'm not sure why this check exists - the comment: ``` // This check is present because each private function invocation creates encrypted FunctionL2Logs object and // both public and private function invocations create unencrypted FunctionL2Logs object. Hence "num unencrypted" // >= "num encrypted". ``` implies that functions must emit both types of logs? A tx with one private call, which emits one encrypted log, should fail here, and I don't see why. EDIT: Have removed the check as it seems redundant. --- Note that in nested calls that have more than one side effect, logs will have duplicate side effect counters and so cannot be sorted correctly (#6052). Currently the logs collected in `allUnencryptedLogs` are the only place that have correctly ordered logs in this case. --- .../writing_contracts/events/emit_event.md | 8 +- docs/docs/misc/migration_notes.md | 16 +- docs/docs/protocol-specs/logs/index.md | 17 +- .../core/libraries/decoders/TxsDecoder.sol | 19 +- l1-contracts/test/decoders/Decoders.t.sol | 27 +- .../aztec-nr/address-note/src/address_note.nr | 6 +- .../aztec-nr/aztec/src/context/avm_context.nr | 2 +- .../aztec-nr/aztec/src/context/interface.nr | 2 +- .../aztec/src/context/private_context.nr | 144 +++++--- .../aztec/src/context/public_context.nr | 43 +-- noir-projects/aztec-nr/aztec/src/hash.nr | 130 +++++++ noir-projects/aztec-nr/aztec/src/lib.nr | 1 - noir-projects/aztec-nr/aztec/src/log.nr | 21 -- noir-projects/aztec-nr/aztec/src/oracle.nr | 1 + .../aztec-nr/aztec/src/oracle/logs.nr | 21 +- .../aztec-nr/aztec/src/oracle/logs_traits.nr | 319 ++++++++++++++++++ .../aztec-nr/aztec/src/oracle/public_call.nr | 2 +- noir-projects/aztec-nr/aztec/src/prelude.nr | 2 +- .../aztec-nr/value-note/src/value_note.nr | 5 +- .../src/subscription_note.nr | 5 +- .../contracts/child_contract/src/main.nr | 10 + .../src/main.nr | 6 +- .../src/types/card_note.nr | 5 +- .../src/ecdsa_public_key_note.nr | 6 +- .../pending_note_hashes_contract/src/main.nr | 3 +- .../src/public_key_note.nr | 5 +- .../contracts/test_contract/src/main.nr | 18 + .../src/types/token_note.nr | 5 +- .../token_contract/src/types/balances_map.nr | 2 +- .../token_contract/src/types/token_note.nr | 5 +- .../src/private_kernel_tail.nr | 18 +- .../src/public_kernel_tail.nr | 20 +- .../private_accumulated_data_builder.nr | 2 +- .../crates/types/src/hash.nr | 48 ++- yarn-project/circuit-types/src/body.ts | 6 +- .../circuit-types/src/l2_block.test.ts | 6 +- .../circuit-types/src/logs/tx_l2_logs.ts | 19 +- yarn-project/circuit-types/src/mocks.ts | 4 +- yarn-project/circuit-types/src/tx/tx.ts | 12 - yarn-project/circuit-types/src/tx_effect.ts | 11 +- .../end-to-end/src/e2e_block_building.test.ts | 57 ++++ .../end-to-end/src/e2e_ordering.test.ts | 6 +- .../src/kernel_prover/kernel_prover.test.ts | 6 +- .../pxe/src/pxe_service/pxe_service.ts | 67 +--- .../simulator/src/acvm/oracle/oracle.ts | 42 ++- .../simulator/src/acvm/oracle/typed_oracle.ts | 9 +- .../src/client/client_execution_context.ts | 61 +++- .../src/client/execution_result.test.ts | 222 ------------ .../simulator/src/client/execution_result.ts | 54 ++- .../simulator/src/client/logs_cache.ts | 65 ++++ .../src/client/private_execution.test.ts | 94 +++++- .../simulator/src/client/private_execution.ts | 4 - .../simulator/src/client/simulator.ts | 2 + yarn-project/simulator/src/mocks/fixtures.ts | 2 + .../src/public/abstract_phase_manager.ts | 9 +- .../simulator/src/public/execution.ts | 9 + yarn-project/simulator/src/public/executor.ts | 6 + .../simulator/src/public/index.test.ts | 58 +++- .../src/public/public_execution_context.ts | 16 +- .../src/public/tail_phase_manager.ts | 36 +- .../src/public/transitional_adaptors.ts | 3 + 61 files changed, 1222 insertions(+), 608 deletions(-) delete mode 100644 noir-projects/aztec-nr/aztec/src/log.nr create mode 100644 noir-projects/aztec-nr/aztec/src/oracle/logs_traits.nr delete mode 100644 yarn-project/simulator/src/client/execution_result.test.ts create mode 100644 yarn-project/simulator/src/client/logs_cache.ts diff --git a/docs/docs/developers/contracts/writing_contracts/events/emit_event.md b/docs/docs/developers/contracts/writing_contracts/events/emit_event.md index 001d48f7824..b2de2bd3c53 100644 --- a/docs/docs/developers/contracts/writing_contracts/events/emit_event.md +++ b/docs/docs/developers/contracts/writing_contracts/events/emit_event.md @@ -47,15 +47,9 @@ In the future we will allow emitting arbitrary information. (If you currently emit arbitrary information, PXE will fail to decrypt, process and store this data, so it will not be queryable). ::: -### Import library - -To emit encrypted logs first import the `emit_encrypted_log` utility function which wraps an [oracle](../oracles/main.md): - -#include_code encrypted_import /noir-projects/aztec-nr/address-note/src/address_note.nr rust - ### Call emit_encrypted_log -After importing, you can call the function: +To emit encrypted logs you don't need to import any library. You call the context method `emit_encrypted_log`: #include_code encrypted /noir-projects/aztec-nr/address-note/src/address_note.nr rust diff --git a/docs/docs/misc/migration_notes.md b/docs/docs/misc/migration_notes.md index 6a93becce8c..c792470b0b5 100644 --- a/docs/docs/misc/migration_notes.md +++ b/docs/docs/misc/migration_notes.md @@ -6,6 +6,20 @@ keywords: [sandbox, cli, aztec, notes, migration, updating, upgrading] 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.38.0 + +### [Aztec.nr] Emmiting 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 @@ -171,7 +185,7 @@ Note that gas limits are not yet enforced. For now, it is suggested you use `dep 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] Emmiting unencrypted logs +### [Aztec.nr] Emitting unencrypted logs The `emit_unencrypted_logs` function is now a context method. diff --git a/docs/docs/protocol-specs/logs/index.md b/docs/docs/protocol-specs/logs/index.md index ab805085834..4d51971e6ef 100644 --- a/docs/docs/protocol-specs/logs/index.md +++ b/docs/docs/protocol-specs/logs/index.md @@ -29,7 +29,7 @@ Logs on Aztec are similar to logs on Ethereum, enabling smart contracts to conve ### Hash Function -The protocol uses **SHA256** as the hash function for logs, and then reduces the 256-bit result to 253 bits for representation as a field element. +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. @@ -227,10 +227,7 @@ Following the iterations for all private or public calls, the tail kernel circui 2. Accumulate all the hashes and output the final hash to the public inputs: - - _`accumulated_logs_hash = hash(logs_hash_a, logs_hash_b)`_ - - For tail public kernel circuit, it begins with _`accumulated_logs_hash = hash(accumulated_logs_hash, logs_hash_a)`_ if the _accumulated_logs_hash_ outputted from the tail private kernel circuit is not empty. - - _`accumulated_logs_hash = hash(accumulated_logs_hash, logs_hash_c)`_ - - Repeat the process until all _logs_hashes_ are collectively hashed. + - `accumulated_logs_hash = hash(log_hash[0], log_hash[1], ..., log_hash[N - 1])` for N logs. ### Encoding @@ -273,11 +270,9 @@ After successfully decrypting an encrypted log, one can use the _randomness_ in - _`log_hash_a = hash(log_hash_a, contract_address_tag_a)`_ - Repeat the process for all _log_hashes_ in the transaction. -2. Accumulate all the hashes and outputs the final hash to the public inputs: +2. Accumulate all the hashes in the tail and outputs the final hash to the public inputs: - - _`accumulated_logs_hash = hash(log_hash_a, log_hash_b)`_ - - _`accumulated_logs_hash = hash(accumulated_logs_hash, log_hash_c)`_ - - Repeat the process until all _logs_hashes_ are collectively hashed. + - `accumulated_logs_hash = hash(log_hash[0], log_hash[1], ..., log_hash[N - 1])` for N logs, with hashes defined above. ### Encoding @@ -310,9 +305,7 @@ As each encrypted note preimage can be associated with a note in the same transa The kernel circuit simply accumulates all the hashes: -- _`accumulated_logs_hash = hash(log_hash_a, log_hash_b)`_ -- _`accumulated_logs_hash = hash(accumulated_logs_hash, log_hash_c)`_ -- Repeat the process until all _logs_hashes_ are collectively hashed. +- `accumulated_logs_hash = hash(log_hash[0], log_hash[1], ..., log_hash[N - 1])` for N logs. ### Encoding diff --git a/l1-contracts/src/core/libraries/decoders/TxsDecoder.sol b/l1-contracts/src/core/libraries/decoders/TxsDecoder.sol index 747c446279a..43ea6244750 100644 --- a/l1-contracts/src/core/libraries/decoders/TxsDecoder.sol +++ b/l1-contracts/src/core/libraries/decoders/TxsDecoder.sol @@ -227,7 +227,7 @@ library TxsDecoder { uint256 remainingLogsLength = read4(_body, offset); offset += 0x4; - bytes32 kernelPublicInputsLogsHash; // The hash on the output of kernel iteration + bytes memory flattenedLogHashes; // The hash input // Iterate until all the logs were processed while (remainingLogsLength > 0) { @@ -245,13 +245,26 @@ library TxsDecoder { bytes32 singleLogHash = Hash.sha256ToField(slice(_body, offset, singleCallLogsLength)); offset += singleCallLogsLength; - kernelPublicInputsLogsHash = - Hash.sha256ToField(bytes.concat(kernelPublicInputsLogsHash, singleLogHash)); + flattenedLogHashes = bytes.concat(flattenedLogHashes, singleLogHash); privateCircuitPublicInputLogsLength -= (singleCallLogsLength + 0x4); } } + // Not having a 0 value hash for empty logs causes issues with empty txs used for padding. + if (flattenedLogHashes.length == 0) { + return (0, offset); + } + + // padded to MAX_LOGS * 32 bytes + // NB: this assumes MAX_ENCRYPTED_LOGS_PER_TX == MAX_UNENCRYPTED_LOGS_PER_TX + flattenedLogHashes = bytes.concat( + flattenedLogHashes, + new bytes(Constants.MAX_ENCRYPTED_LOGS_PER_TX * 32 - flattenedLogHashes.length) + ); + + bytes32 kernelPublicInputsLogsHash = Hash.sha256ToField(flattenedLogHashes); + return (kernelPublicInputsLogsHash, offset); } diff --git a/l1-contracts/test/decoders/Decoders.t.sol b/l1-contracts/test/decoders/Decoders.t.sol index accec36ab51..74ae5449f31 100644 --- a/l1-contracts/test/decoders/Decoders.t.sol +++ b/l1-contracts/test/decoders/Decoders.t.sol @@ -5,15 +5,11 @@ pragma solidity >=0.8.18; import {DecoderBase} from "./Base.sol"; import {Hash} from "../../src/core/libraries/Hash.sol"; -import {DataStructures} from "../../src/core/libraries/DataStructures.sol"; import {HeaderLibHelper} from "./helpers/HeaderLibHelper.sol"; import {TxsDecoderHelper} from "./helpers/TxsDecoderHelper.sol"; import {HeaderLib} from "../../src/core/libraries/HeaderLib.sol"; - -import {TxsDecoder} from "../../src/core/libraries/decoders/TxsDecoder.sol"; - -import {AvailabilityOracle} from "../../src/core/availability_oracle/AvailabilityOracle.sol"; +import {Constants} from "../../src/core/libraries/ConstantsGen.sol"; /** * Blocks are generated using the `integration_l1_publisher.test.ts` tests. @@ -196,13 +192,12 @@ contract DecodersTest is DecoderBase { abi.encodePacked(hex"0000000c00000008", hex"00000004", firstFunctionCallLogs); (bytes32 logsHash, uint256 bytesAdvanced) = txsHelper.computeKernelLogsHash(encodedLogs); - // Zero because this is the first iteration - bytes32 previousKernelPublicInputsLogsHash = bytes32(0); bytes32 privateCircuitPublicInputsLogsHashFirstCall = Hash.sha256ToField(firstFunctionCallLogs); bytes32 referenceLogsHash = Hash.sha256ToField( abi.encodePacked( - previousKernelPublicInputsLogsHash, privateCircuitPublicInputsLogsHashFirstCall + privateCircuitPublicInputsLogsHashFirstCall, + new bytes(Constants.MAX_ENCRYPTED_LOGS_PER_TX * 32 - 32) ) ); @@ -229,15 +224,16 @@ contract DecodersTest is DecoderBase { ); (bytes32 logsHash, uint256 bytesAdvanced) = txsHelper.computeKernelLogsHash(encodedLogs); - bytes32 referenceLogsHashFromIteration1 = - Hash.sha256ToField(abi.encodePacked(bytes32(0), Hash.sha256ToField(firstFunctionCallLogs))); + bytes32 referenceLogsHashFromIteration1 = Hash.sha256ToField(firstFunctionCallLogs); bytes32 privateCircuitPublicInputsLogsHashSecondCall = Hash.sha256ToField(secondFunctionCallLogs); bytes32 referenceLogsHashFromIteration2 = Hash.sha256ToField( abi.encodePacked( - referenceLogsHashFromIteration1, privateCircuitPublicInputsLogsHashSecondCall + referenceLogsHashFromIteration1, + privateCircuitPublicInputsLogsHashSecondCall, + new bytes(Constants.MAX_ENCRYPTED_LOGS_PER_TX * 32 - 64) ) ); @@ -269,8 +265,7 @@ contract DecodersTest is DecoderBase { ); (bytes32 logsHash, uint256 bytesAdvanced) = txsHelper.computeKernelLogsHash(encodedLogs); - bytes32 referenceLogsHashFromIteration1 = - Hash.sha256ToField(abi.encodePacked(bytes32(0), Hash.sha256ToField(firstFunctionCallLogs))); + bytes32 referenceLogsHashFromIteration1 = Hash.sha256ToField(firstFunctionCallLogs); // Note: as of resolving #5017, we now hash logs inside the circuits // Following the YP, we skip any zero length logs, hence no use of secondFunctionCallLogs here @@ -278,7 +273,11 @@ contract DecodersTest is DecoderBase { bytes32 privateCircuitPublicInputsLogsHashThirdCall = Hash.sha256ToField(thirdFunctionCallLogs); bytes32 referenceLogsHashFromIteration3 = Hash.sha256ToField( - abi.encodePacked(referenceLogsHashFromIteration1, privateCircuitPublicInputsLogsHashThirdCall) + abi.encodePacked( + referenceLogsHashFromIteration1, + privateCircuitPublicInputsLogsHashThirdCall, + new bytes(Constants.MAX_ENCRYPTED_LOGS_PER_TX * 32 - 64) + ) ); assertEq(bytesAdvanced, encodedLogs.length, "Advanced by an incorrect number of bytes"); diff --git a/noir-projects/aztec-nr/address-note/src/address_note.nr b/noir-projects/aztec-nr/address-note/src/address_note.nr index 982b9ba0053..d784a0fb7e7 100644 --- a/noir-projects/aztec-nr/address-note/src/address_note.nr +++ b/noir-projects/aztec-nr/address-note/src/address_note.nr @@ -1,6 +1,3 @@ -// docs:start:encrypted_import -use dep::aztec::log::emit_encrypted_log; -// docs:end:encrypted_import use dep::aztec::{ protocol_types::{address::AztecAddress, traits::Empty, constants::GENERATOR_INDEX__NOTE_NULLIFIER}, note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_consumption}, @@ -45,8 +42,7 @@ impl NoteInterface for AddressNote { fn broadcast(self, context: &mut PrivateContext, slot: Field) { let encryption_pub_key = get_public_key(self.owner); // docs:start:encrypted - emit_encrypted_log( - context, + context.emit_encrypted_log( (*context).this_address(), slot, Self::get_note_type_id(), diff --git a/noir-projects/aztec-nr/aztec/src/context/avm_context.nr b/noir-projects/aztec-nr/aztec/src/context/avm_context.nr index 6c3e855d712..cb2c3d26e89 100644 --- a/noir-projects/aztec-nr/aztec/src/context/avm_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/avm_context.nr @@ -82,7 +82,7 @@ impl PublicContextInterface for AvmContext { nullifier_exists(unsiloed_nullifier, address.to_field()) == 1 } - fn emit_unencrypted_log(&mut self, log: T) { + fn emit_unencrypted_log(&mut self, log: T) { let event_selector = 5; // Matches current PublicContext. self.emit_unencrypted_log(event_selector, log); } diff --git a/noir-projects/aztec-nr/aztec/src/context/interface.nr b/noir-projects/aztec-nr/aztec/src/context/interface.nr index a42d342c48c..175d93cc2c4 100644 --- a/noir-projects/aztec-nr/aztec/src/context/interface.nr +++ b/noir-projects/aztec-nr/aztec/src/context/interface.nr @@ -32,7 +32,7 @@ trait PublicContextInterface { fn fee_per_l2_gas(self) -> Field; fn message_portal(&mut self, recipient: EthAddress, content: Field); fn consume_l1_to_l2_message(&mut self, content: Field, secret: Field, sender: EthAddress, leaf_index: Field); - fn emit_unencrypted_log(&mut self, log: T); + fn emit_unencrypted_log(&mut self, log: T); fn call_public_function( self: &mut Self, contract_address: AztecAddress, diff --git a/noir-projects/aztec-nr/aztec/src/context/private_context.nr b/noir-projects/aztec-nr/aztec/src/context/private_context.nr index 40fe7613575..9641090481c 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -1,9 +1,11 @@ use crate::{ context::{inputs::PrivateContextInputs, interface::ContextInterface}, - messaging::process_l1_to_l2_message, hash::{hash_args_array, ArgsHasher}, + messaging::process_l1_to_l2_message, + hash::{hash_args_array, ArgsHasher, compute_encrypted_log_hash, compute_unencrypted_log_hash}, oracle::{ arguments, returns, call_private_function::call_private_function_internal, enqueue_public_function_call::enqueue_public_function_call_internal, header::get_header_at, + logs::emit_encrypted_log, logs_traits::{LensForEncryptedLog, ToBytesForUnencryptedLog}, nullifier_key::{get_nullifier_keys, NullifierKeys} } }; @@ -28,14 +30,11 @@ use dep::protocol_types::{ MAX_ENCRYPTED_LOGS_PER_CALL, MAX_UNENCRYPTED_LOGS_PER_CALL }, contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, - grumpkin_private_key::GrumpkinPrivateKey, header::Header, + grumpkin_private_key::GrumpkinPrivateKey, grumpkin_point::GrumpkinPoint, header::Header, messaging::l2_to_l1_message::L2ToL1Message, utils::reader::Reader, traits::{is_empty, Deserialize, Empty} }; -// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1165) -// use dep::std::collections::vec::Vec; - // When finished, one can call .finish() to convert back to the abi struct PrivateContext { // docs:start:private-context @@ -44,8 +43,8 @@ struct PrivateContext { min_revertible_side_effect_counter: u32, - args_hash : Field, - return_hash : Field, + args_hash: Field, + return_hash: Field, max_block_number: MaxBlockNumber, @@ -64,11 +63,10 @@ struct PrivateContext { // Header of a block whose state is used during private execution (not the block the transaction is included in). historical_header: Header, - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1165) encrypted_logs_hashes: BoundedVec, unencrypted_logs_hashes: BoundedVec, - // encrypted_logs_preimages: Vec, - // unencrypted_logs_preimages: Vec, + encrypted_log_preimages_length: Field, + unencrypted_log_preimages_length: Field, nullifier_key: Option, } @@ -132,11 +130,10 @@ impl PrivateContext { private_call_stack_hashes: BoundedVec::new(), public_call_stack_hashes: BoundedVec::new(), new_l2_to_l1_msgs: BoundedVec::new(), - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1165) encrypted_logs_hashes: BoundedVec::new(), unencrypted_logs_hashes: BoundedVec::new(), - // encrypted_logs_preimages: Vec::new(), - // unencrypted_logs_preimages: Vec::new(), + encrypted_log_preimages_length: 0, + unencrypted_log_preimages_length: 0, nullifier_key: Option::none() } } @@ -159,11 +156,7 @@ impl PrivateContext { } pub fn finish(self) -> PrivateCircuitPublicInputs { - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1165) - let encrypted_log_preimages_length = 0; - let unencrypted_log_preimages_length = 0; - - let priv_circuit_pub_inputs = PrivateCircuitPublicInputs { + PrivateCircuitPublicInputs { call_context: self.inputs.call_context, args_hash: self.args_hash, returns_hash: self.return_hash, @@ -181,13 +174,11 @@ impl PrivateContext { end_side_effect_counter: self.side_effect_counter, encrypted_logs_hashes: self.encrypted_logs_hashes.storage, unencrypted_logs_hashes: self.unencrypted_logs_hashes.storage, - encrypted_log_preimages_length, - unencrypted_log_preimages_length, + encrypted_log_preimages_length: self.encrypted_log_preimages_length + 4, + unencrypted_log_preimages_length: self.unencrypted_log_preimages_length + 4, historical_header: self.historical_header, tx_context: self.inputs.tx_context - }; - - priv_circuit_pub_inputs + } } pub fn end_setup(&mut self) { @@ -258,25 +249,76 @@ impl PrivateContext { } // docs:end:consume_l1_to_l2_message - pub fn push_encrypted_log(&mut self, log_hash: Field) { - let side_effect = SideEffect { value: log_hash, counter: self.side_effect_counter }; - self.encrypted_logs_hashes.push(side_effect); - self.side_effect_counter = self.side_effect_counter + 1; - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1165) - } - // TODO: We might want to remove this since emitting unencrypted logs from private functions is violating privacy. // --> might be a better approach to force devs to make a public function call that emits the log if needed then // it would be less easy to accidentally leak information. // If we decide to keep this function around would make sense to wait for traits and then merge it with emit_unencrypted_log. - pub fn emit_unencrypted_log(&mut self, log: T) { + pub fn emit_unencrypted_log(&mut self, log: T) where T: ToBytesForUnencryptedLog { let event_selector = 5; // TODO: compute actual event selector. - let log_hash = emit_unencrypted_log_private_internal(self.this_address(), event_selector, log); - + let contract_address = self.this_address(); + let log_slice = log.to_be_bytes_arr(); + let log_hash = compute_unencrypted_log_hash( + contract_address, + event_selector, + log, + ); + let side_effect = SideEffect { value: log_hash, counter: self.side_effect_counter }; + self.unencrypted_logs_hashes.push(side_effect); + self.side_effect_counter = self.side_effect_counter + 1; + // 44 = addr (32) + selector (4) + raw log len (4) + processed log len (4) + self.unencrypted_log_preimages_length += 44 + log_slice.len().to_field(); + // call oracle + let _void = emit_unencrypted_log_private_internal(contract_address, event_selector, log, side_effect.counter); + } + + // This fn exists separately from emit_unencrypted_log because sha hashing the preimage + // is too large to compile (16,200 fields, 518,400 bytes) => the oracle hashes it + // It is ONLY used with contract_class_registerer_contract since we already assert correctness: + // - Contract class -> we will commit to the packed bytecode (currently a TODO) + // - Private function -> we provide a membership proof + // - Unconstrained function -> we provide a membership proof + // Ordinary logs are not protected by the above so this fn shouldn't be called by anything else + pub fn emit_contract_class_unencrypted_log(&mut self, log: [Field; N]) { + let event_selector = 5; // TODO: compute actual event selector. + let contract_address = self.this_address(); + let log_hash = emit_contract_class_unencrypted_log_private_internal( + contract_address, + event_selector, + log, + self.side_effect_counter + ); let side_effect = SideEffect { value: log_hash, counter: self.side_effect_counter }; self.unencrypted_logs_hashes.push(side_effect); self.side_effect_counter = self.side_effect_counter + 1; - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1165) + // 44 = addr (32) + selector (4) + raw log len (4) + processed log len (4) + self.unencrypted_log_preimages_length += 44 + N*32; + } + + pub fn emit_encrypted_log( + &mut self, + contract_address: AztecAddress, + storage_slot: Field, + note_type_id: Field, + encryption_pub_key: GrumpkinPoint, + preimage: [Field; N] + ) where [Field; N]: LensForEncryptedLog { + // TODO(1139): perform encryption in the circuit + // The oracle call should come last, but we require the encrypted value for now + let encrypted_log: [Field; M] = emit_encrypted_log( + contract_address, + storage_slot, + note_type_id, + encryption_pub_key, + preimage, + self.side_effect_counter + ); + let log_hash = compute_encrypted_log_hash(encrypted_log); + let side_effect = SideEffect { value: log_hash, counter: self.side_effect_counter }; + self.encrypted_logs_hashes.push(side_effect); + self.side_effect_counter = self.side_effect_counter + 1; + let encrypted_log_byte_len = 112 + 32*(N + 3); + // + processed log len (4) + self.encrypted_log_preimages_length += encrypted_log_byte_len + 4; } pub fn call_private_function( @@ -546,8 +588,8 @@ impl Empty for PrivateContext { inputs: PrivateContextInputs::empty(), side_effect_counter: 0 as u32, min_revertible_side_effect_counter: 0 as u32, - args_hash : 0, - return_hash : 0, + args_hash: 0, + return_hash: 0, max_block_number: MaxBlockNumber::empty(), note_hash_read_requests: BoundedVec::new(), nullifier_read_requests: BoundedVec::new(), @@ -560,6 +602,8 @@ impl Empty for PrivateContext { historical_header: Header::empty(), encrypted_logs_hashes: BoundedVec::new(), unencrypted_logs_hashes: BoundedVec::new(), + encrypted_log_preimages_length: 0, + unencrypted_log_preimages_length: 0, nullifier_key: Option::none(), } } @@ -598,14 +642,34 @@ impl PackedReturns { fn emit_unencrypted_log_oracle_private( _contract_address: AztecAddress, _event_selector: Field, - _message: T + _message: T, + _counter: u32 ) -> Field {} unconstrained pub fn emit_unencrypted_log_private_internal( contract_address: AztecAddress, event_selector: Field, - message: T + message: T, + counter: u32 +) -> Field { + emit_unencrypted_log_oracle_private(contract_address, event_selector, message, counter) +} + +#[oracle(emitContractClassUnencryptedLog)] +fn emit_contract_class_unencrypted_log_private( + contract_address: AztecAddress, + event_selector: Field, + message: [Field; N], + counter: u32 +) -> Field {} + + +unconstrained pub fn emit_contract_class_unencrypted_log_private_internal( + contract_address: AztecAddress, + event_selector: Field, + message: [Field; N], + counter: u32 ) -> Field { - // https://github.com/AztecProtocol/aztec-packages/issues/885 - emit_unencrypted_log_oracle_private(contract_address, event_selector, message) + emit_contract_class_unencrypted_log_private(contract_address, event_selector, message, counter) } + diff --git a/noir-projects/aztec-nr/aztec/src/context/public_context.nr b/noir-projects/aztec-nr/aztec/src/context/public_context.nr index 4e39f85944d..33765f8f3ec 100644 --- a/noir-projects/aztec-nr/aztec/src/context/public_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/public_context.nr @@ -4,8 +4,8 @@ use crate::{ gas::GasOpts }, messaging::process_l1_to_l2_message, - oracle::{arguments, public_call::call_public_function_internal, returns}, - hash::{hash_args, ArgsHasher} + oracle::{arguments, public_call::call_public_function_internal, returns, logs_traits::ToBytesForUnencryptedLog}, + hash::{hash_args, ArgsHasher, compute_unencrypted_log_hash} }; use dep::protocol_types::{ abis::{ @@ -46,7 +46,7 @@ struct PublicContext { new_l2_to_l1_msgs: BoundedVec, unencrypted_logs_hashes: BoundedVec, - unencrypted_logs_preimages_length: Field, + unencrypted_log_preimages_length: Field, // Header of a block whose state is used during public execution. Set by sequencer to be a header of a block // previous to the one in which the tx is included. @@ -70,11 +70,9 @@ impl PublicContext { new_nullifiers: BoundedVec::new(), new_l2_to_l1_msgs: BoundedVec::new(), unencrypted_logs_hashes: BoundedVec::new(), - unencrypted_logs_preimages_length: 0, + unencrypted_log_preimages_length: 0, historical_header: inputs.historical_header, - prover_address: AztecAddress::zero() // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1165) - // encrypted_logs_preimages: Vec::new(), - // unencrypted_logs_preimages: Vec::new(), + prover_address: AztecAddress::zero(), } } @@ -111,7 +109,7 @@ impl PublicContext { is_delegate_call: bool ) -> FunctionReturns { let side_effect_counter = self.side_effect_counter; - // TODO get next value from output of `call_public_function_internal` + // TODO(6052): get next value from output of `call_public_function_internal` self.side_effect_counter += 1; let raw_returns = call_public_function_internal( @@ -146,8 +144,6 @@ impl PublicContext { } pub fn finish(self) -> PublicCircuitPublicInputs { - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1165) - let unencrypted_log_preimages_length = 0; // Compute the public call stack hashes let pub_circuit_pub_inputs = PublicCircuitPublicInputs { @@ -165,7 +161,7 @@ impl PublicContext { start_side_effect_counter: self.inputs.start_side_effect_counter, end_side_effect_counter: self.side_effect_counter, unencrypted_logs_hashes: self.unencrypted_logs_hashes.storage, - unencrypted_log_preimages_length, + unencrypted_log_preimages_length: self.unencrypted_log_preimages_length + 4, historical_header: self.inputs.historical_header, global_variables: self.inputs.public_global_variables, prover_address: self.prover_address, @@ -273,16 +269,22 @@ impl PublicContextInterface for PublicContext { self.push_new_nullifier(nullifier, 0) } - fn emit_unencrypted_log(&mut self, log: T) { - let event_selector = 5; - let log_hash = emit_unencrypted_log_oracle(self.this_address(), event_selector, log); - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1165) - // Once we hash inside circuits, this replaces push_unencrypted_log - // For now we need an oracle to get the hash + fn emit_unencrypted_log(&mut self, log: T) where T: ToBytesForUnencryptedLog { + let event_selector = 5; // TODO: compute actual event selector. + let contract_address = self.this_address(); + let log_slice = log.to_be_bytes_arr(); + let log_hash = compute_unencrypted_log_hash( + contract_address, + event_selector, + log + ); let side_effect = SideEffect { value: log_hash, counter: self.side_effect_counter }; self.unencrypted_logs_hashes.push(side_effect); self.side_effect_counter = self.side_effect_counter + 1; - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1165) + // 44 = addr (32) + selector (4) + raw log len (4) + processed log len (4) + self.unencrypted_log_preimages_length = self.unencrypted_log_preimages_length + 44 + log_slice.len().to_field(); + // Call oracle to broadcast log + let _void = emit_unencrypted_log_oracle(contract_address, event_selector, log, side_effect.counter); } fn call_public_function( @@ -337,7 +339,7 @@ impl Empty for PublicContext { new_nullifiers: BoundedVec::new(), new_l2_to_l1_msgs: BoundedVec::new(), unencrypted_logs_hashes: BoundedVec::new(), - unencrypted_logs_preimages_length: 0, + unencrypted_log_preimages_length: 0, historical_header: Header::empty(), prover_address: AztecAddress::zero(), } @@ -351,7 +353,8 @@ fn nullifier_exists_oracle(nullifier: Field) -> Field {} fn emit_unencrypted_log_oracle( _contract_address: AztecAddress, _event_selector: Field, - _message: T + _message: T, + _counter: u32, ) -> Field {} struct FunctionReturns { diff --git a/noir-projects/aztec-nr/aztec/src/hash.nr b/noir-projects/aztec-nr/aztec/src/hash.nr index 435df049fa2..db600f4f542 100644 --- a/noir-projects/aztec-nr/aztec/src/hash.nr +++ b/noir-projects/aztec-nr/aztec/src/hash.nr @@ -6,11 +6,61 @@ use dep::protocol_types::{ }, traits::Hash, hash::{pedersen_hash, poseidon2_hash, silo_nullifier, sha256_to_field} }; +use crate::oracle::logs_traits::{LensForEncryptedLog, ToBytesForUnencryptedLog}; pub fn compute_secret_hash(secret: Field) -> Field { pedersen_hash([secret], GENERATOR_INDEX__SECRET_HASH) } +pub fn compute_encrypted_log_hash( + encrypted_log: [Field; M] +) -> Field where [Field; N]: LensForEncryptedLog { + let mut bytes = [0; L]; + // Note that bytes.append(encrypted_log[i].to_be_bytes(31)) results in bound error + for i in 0..M-1 { + let to_add = encrypted_log[i].to_be_bytes(31); + for j in 0..31 { + bytes[i*31 + j] = to_add[j]; + } + } + // can't assign as L - not in scope error for: L-31*(M-1) + let num_bytes = bytes.len() as u32 - 31*(M-1); + let to_add_final = encrypted_log[M-1].to_be_bytes(num_bytes); + for j in 0..num_bytes { + bytes[(M-1)*31 + j] = to_add_final[j]; + } + sha256_to_field(bytes) +} + +pub fn compute_unencrypted_log_hash( + contract_address: AztecAddress, + event_selector: Field, + log: T, +) -> Field where T: ToBytesForUnencryptedLog { + let message_bytes: [u8; N] = log.to_be_bytes_arr(); + // can't use N - not in scope error + let n = message_bytes.len(); + let mut hash_bytes = [0; M]; + // Address is converted to 32 bytes in ts + let address_bytes = contract_address.to_be_bytes_arr(); + for i in 0..32 { + hash_bytes[i] = address_bytes[i]; + } + let event_bytes = event_selector.to_be_bytes(4); + for i in 0..4 { + hash_bytes[32 + i] = event_bytes[i]; + } + let len_bytes = (n as Field).to_be_bytes(4); + for i in 0..4 { + hash_bytes[36 + i] = len_bytes[i]; + } + for i in 0..n { + hash_bytes[40 + i] = message_bytes[i]; + } + + sha256_to_field(hash_bytes) +} + pub fn compute_message_hash( sender: EthAddress, chain_id: Field, @@ -118,3 +168,83 @@ fn compute_var_args_hash() { let hash = input.hash(); assert(hash == 0x05a1023fef839ac88731f49ae983e172c1b600a3c8f3393ad0ac25d819ac0f0f); } + +#[test] +fn compute_enc_log_hash_304() { + let input = [ + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0021a0d4aa9989656b592187cf6da1965df53ab2ff2277421e663465cf20d3e9, + 0x00c3969cc350f3474f8187a33ac1317181961f5f94043b07ce888d85a5d20cb5, + 0x0058198041ed1547b056955b5141a5a8a1551b0c8d094255ec9daaf3604d9348, + 0x00247ad96df2e4d984cf795ed7316234743a681f824a45c46253de8bfde48850, + 0x007fc251f4ce44f4e9aba3dbf6567228be28fac85660156f2825ddb0b0577457, + 0x009315851323c6bc2aaa42e23fe5f3be97208f2d8167eafdfc5742d94f2f4dd4, + 0x00b938289e563b0fe01982cd9b8d9e33e3069046768ad01c0fb05e429e7b7909, + 0x00fbcc257a3211f705b471eee763b0f43876a2b2178fab6d2b09bd2b7e086584, + 0x000000000000008c3289b5793b7448f4d45ecde039d004b6f037cad10b5c2336 + ]; + let hash = compute_encrypted_log_hash(input); + assert(hash == 0x001e3c013994947fe28957a876bf1b2c3a69ac69cc92909efd4f2ae9b972f893); +} + +#[test] +fn compute_enc_log_hash_368() { + let input = [ + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x002190697d2a50e229a7a077e0951073f7d51e46679f10466153c308b63b1ea9, + 0x00543e346facc6799b94514c9d461bcc836c04b083b13c2e4544a39130473c1e, + 0x000df76d59526f8f953bcc7d9f77cdaefd36435931f0d7348f794bc275b42ded, + 0x00a6d390ee1723af7f7ac1ae4fc81a266b2370fe07040a36d06dbe242e02413e, + 0x00acbce15b6af1fbe94bd0f7b70f11768265dff77bfe63398f2a053efdfdf26d, + 0x00b8b131b9f42c689beb095ba4f4a836d4d15c9068d0422e9add6ca82b786329, + 0x00661a6a654b38f0f97d404ef5553e0efea9ed670561ae86685b31bbb2824fac, + 0x00113a6b58edfaec0065b365f66ba8d8aa68254b8690035e8d671a17a843f0a1, + 0x0023f2d2eae8c4449bac8f268a3e62a3faace1fe1401f0efdc8b0ccfbc8fb271, + 0x00cf6603f8c61993dd2f662c719671c61727a2f4e925fb988b23d31feccd77d9, + 0x0000000000a402a84b7294671799c38dd805f6a827a3a12633fdf91a57debe1f + ]; + let hash = compute_encrypted_log_hash(input); + assert(hash == 0x00a0d651ac0cbc01b72430fa6a05d91738595af6e0229347b4c9968223387aeb); +} + +#[test] +fn compute_unenc_log_hash_array() { + let contract_address = AztecAddress::from_field(0x233a3e0df23b2b15b324194cb4a151f26c0b7333250781d34cc269d85dc334c6); + let event_selector = 5; + let log = [ + 0x20660de09f35f876e3e69d227b2a35166ad05f09d82d06366ec9b6f65a51fec2, + 0x1b52bfe3b8689761916f76dc3d38aa8810860db325cd39ca611eed980091f01c, + 0x2e559c4045c378a56ad13b9edb1e8de4e7ad3b3aa35cc7ba9ec77f7a68fa43a4, + 0x25d0f689c4a4178a29d59306f2675824d19be6d25e44fa03b03f49c263053dd2, + 0x2d513a722d6f352dc0961f156afdc5e31495b9f0e35cb069261a8e55e2df67fd + ]; + let hash = compute_unencrypted_log_hash(contract_address, event_selector, log); + assert(hash == 0x00846d6969c8c2f61d39cd2762efcb0abb14f88d59c2675910251ef2bcffe9a7); +} + +#[test] +fn compute_unenc_log_hash_addr() { + let contract_address = AztecAddress::from_field(0x233a3e0df23b2b15b324194cb4a151f26c0b7333250781d34cc269d85dc334c6); + let event_selector = 5; + let log = AztecAddress::from_field(0x26aa302d4715fd8a687453cb26d616b0768027bd54bcae56b09d908ecd9f8303); + let hash = compute_unencrypted_log_hash(contract_address, event_selector, log); + assert(hash == 0x00880a801230ea08c98a802a11b4786cba474513875f0fc69a615e81c5f9f21c); +} + +#[test] +fn compute_unenc_log_hash_str() { + let contract_address = AztecAddress::from_field(0x1b401e1146c5c507962287065c81f0ef7590adae3802c533d7549d6bf0a41bd8); + let event_selector = 5; + let log = "dummy"; + let hash = compute_unencrypted_log_hash(contract_address, event_selector, log); + assert(hash == 0x00a78b5347813624ecfd26e5b8bc6146f418b0cfcc8296b5112d09b8ebba9496); +} + +#[test] +fn compute_unenc_log_hash_longer_str() { + let contract_address = AztecAddress::from_field(0x1b401e1146c5c507962287065c81f0ef7590adae3802c533d7549d6bf0a41bd8); + let event_selector = 5; + let log = "Hello this is a string"; + let hash = compute_unencrypted_log_hash(contract_address, event_selector, log); + assert(hash == 0x001f3390ea242afee7ce46dafdbdc4bd4f1cf20cd63850d12d60ff9956712c4f); +} diff --git a/noir-projects/aztec-nr/aztec/src/lib.nr b/noir-projects/aztec-nr/aztec/src/lib.nr index 67a3c8c55b4..5043c7e5135 100644 --- a/noir-projects/aztec-nr/aztec/src/lib.nr +++ b/noir-projects/aztec-nr/aztec/src/lib.nr @@ -4,7 +4,6 @@ mod hash; mod history; mod initializer; mod keys; -mod log; mod messaging; mod note; mod oracle; diff --git a/noir-projects/aztec-nr/aztec/src/log.nr b/noir-projects/aztec-nr/aztec/src/log.nr deleted file mode 100644 index c3e3f361f59..00000000000 --- a/noir-projects/aztec-nr/aztec/src/log.nr +++ /dev/null @@ -1,21 +0,0 @@ -use crate::context::{PrivateContext, PublicContext}; -use crate::oracle; -use dep::protocol_types::{address::AztecAddress, grumpkin_point::GrumpkinPoint}; - -pub fn emit_encrypted_log( - context: &mut PrivateContext, - contract_address: AztecAddress, - storage_slot: Field, - note_type_id: Field, - encryption_pub_key: GrumpkinPoint, - log: [Field; N] -) { - let log_hash = oracle::logs::emit_encrypted_log( - contract_address, - storage_slot, - note_type_id, - encryption_pub_key, - log - ); - context.push_encrypted_log(log_hash); -} diff --git a/noir-projects/aztec-nr/aztec/src/oracle.nr b/noir-projects/aztec-nr/aztec/src/oracle.nr index 97811426bab..64c449d61f8 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle.nr @@ -21,6 +21,7 @@ mod public_call; mod notes; mod storage; mod logs; +mod logs_traits; mod returns; // debug_log oracle is used by both noir-protocol-circuits and this crate and for this reason we just re-export it diff --git a/noir-projects/aztec-nr/aztec/src/oracle/logs.nr b/noir-projects/aztec-nr/aztec/src/oracle/logs.nr index 849a5726c9f..48df110c32a 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/logs.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/logs.nr @@ -1,27 +1,32 @@ use dep::protocol_types::{address::AztecAddress, grumpkin_point::GrumpkinPoint}; -// TODO: Should take encrypted data. +// TODO(1139): Should take encrypted data. +// Currently returns encrypted data to be hashed +// = 112 + 32 * (N + 3) bytes = N + 7 fields #[oracle(emitEncryptedLog)] -fn emit_encrypted_log_oracle( +fn emit_encrypted_log_oracle( _contract_address: AztecAddress, _storage_slot: Field, _note_type_id: Field, _encryption_pub_key: GrumpkinPoint, - _preimage: [Field; N] -) -> Field {} + _preimage: [Field; N], + _counter: u32, +) -> [Field; M] {} -unconstrained pub fn emit_encrypted_log( +unconstrained pub fn emit_encrypted_log( contract_address: AztecAddress, storage_slot: Field, note_type_id: Field, encryption_pub_key: GrumpkinPoint, - preimage: [Field; N] -) -> Field { + preimage: [Field; N], + counter: u32 +) -> [Field; M] { emit_encrypted_log_oracle( contract_address, storage_slot, note_type_id, encryption_pub_key, - preimage + preimage, + counter ) } diff --git a/noir-projects/aztec-nr/aztec/src/oracle/logs_traits.nr b/noir-projects/aztec-nr/aztec/src/oracle/logs_traits.nr new file mode 100644 index 00000000000..267979da364 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/oracle/logs_traits.nr @@ -0,0 +1,319 @@ +use dep::protocol_types::address::AztecAddress; + +// TODO: this is awful but since we can't have a fn that maps [Field; N] -> [Field; N+7] +// (where N is encrypted log preimage size and N+7 is encryption output size) +// and can't return slices from oracles, this at least compiles and runs +// The fns for LensForEncryptedLog are never used, it's just to tell the compiler what the lens are + +// The to_bytes fn for ToBytesForUnencryptedLog is used to allow us to hash some generic T + +// I could have omitted N from the trait, but wanted to keep it strictly for field arrs +// TODO(1139): Once we enc inside the circuit, we will no longer need the oracle to return +// anything, so we can remove this trait +trait LensForEncryptedLog { + // N = note preimage input in fields + // M = encryption output len in fields (= N + 7 = N + 3 fields for addr, slot, type + 3.5 fields for AES data) + // L = encryption output len in bytes (= 32*M - 16) + fn output_fields(self: [Field; N]) -> [Field; M]; + fn output_bytes(self: [Field; N]) -> [u8; L]; +} + +impl LensForEncryptedLog<1, 8, 240> for [Field; 1] { + fn output_fields(self) -> [Field; 8] {[self[0]; 8]} + fn output_bytes(self) -> [u8; 240] {[self[0] as u8; 240]} +} +impl LensForEncryptedLog<2, 9, 272> for [Field; 2] { + fn output_fields(self) -> [Field; 9] {[self[0]; 9]} + fn output_bytes(self) -> [u8; 272] {[self[0] as u8; 272]} +} +impl LensForEncryptedLog<3, 10, 304> for [Field; 3] { + fn output_fields(self) -> [Field; 10] {[self[0]; 10]} + fn output_bytes(self) -> [u8; 304] {[self[0] as u8; 304]} +} +impl LensForEncryptedLog<4, 11, 336> for [Field; 4] { + fn output_fields(self) -> [Field; 11] {[self[0]; 11]} + fn output_bytes(self) -> [u8; 336] {[self[0] as u8; 336]} +} +impl LensForEncryptedLog<5, 12, 368> for [Field; 5] { + fn output_fields(self) -> [Field; 12] {[self[0]; 12]} + fn output_bytes(self) -> [u8; 368] {[self[0] as u8; 368]} +} +impl LensForEncryptedLog<6, 13, 400> for [Field; 6] { + fn output_fields(self) -> [Field; 13] {[self[0]; 13]} + fn output_bytes(self) -> [u8; 400] {[self[0] as u8; 400]} +} + +// This trait defines the length of the inputs in bytes to +// the unencrypted log hash fn, where the log can be any type T +// as long as the ACVM can convert to fields. +trait ToBytesForUnencryptedLog { + // N = preimage input in bytes (32 * num fields or chars) + // M = full log input in bytes ( = N + 40 = N + 32 for addr, + 4 for selector, + 4 for len) + fn to_be_bytes_arr(self) -> [u8; N]; + fn output_bytes(self) -> [u8; M]; +} + +impl ToBytesForUnencryptedLog<32, 72> for Field { + fn to_be_bytes_arr(self) -> [u8; 32] { + self.to_be_bytes(32).as_array() + } + fn output_bytes(self) -> [u8; 72] {[self as u8; 72]} +} + +impl ToBytesForUnencryptedLog<32, 72> for AztecAddress { + fn to_be_bytes_arr(self) -> [u8; 32] { + self.to_field().to_be_bytes(32).as_array() + } + fn output_bytes(self) -> [u8; 72] {[self.to_field() as u8; 72]} +} + +fn arr_to_be_bytes_arr(fields: [Field; L]) -> [u8; N] { + let mut bytes: [u8] = &[]; + for i in 0..L { + // Note that bytes.append() results in bound error + let to_add = fields[i].to_be_bytes(32); + for j in 0..32 { + bytes = bytes.push_back(to_add[j]); + } + } + bytes.as_array() +} + +// each character of a string is converted into a byte +// then an ACVM field via the oracle => we recreate here +fn str_to_be_bytes_arr(string: str) -> [u8; N] { + let chars_bytes = string.as_bytes(); + let mut bytes: [u8] = &[]; + for i in 0..L { + let to_add = (chars_bytes[i] as Field).to_be_bytes(32); + for j in 0..32 { + bytes = bytes.push_back(to_add[j]); + } + } + bytes.as_array() +} + +impl ToBytesForUnencryptedLog<32, 72> for [Field; 1] { + fn to_be_bytes_arr(self) -> [u8; 32] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 72] { + [self[0] as u8; 72] + } +} + +impl ToBytesForUnencryptedLog<64, 104> for [Field; 2] { + fn to_be_bytes_arr(self) -> [u8; 64] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 104] { + [self[0] as u8; 104] + } +} + +impl ToBytesForUnencryptedLog<96, 136> for [Field; 3] { + fn to_be_bytes_arr(self) -> [u8; 96] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 136] { + [self[0] as u8; 136] + } +} + +impl ToBytesForUnencryptedLog<128, 168> for [Field; 4] { + fn to_be_bytes_arr(self) -> [u8; 128] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 168] { + [self[0] as u8; 168] + } +} + +impl ToBytesForUnencryptedLog<160, 200> for [Field; 5] { + fn to_be_bytes_arr(self) -> [u8; 160] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 200] { + [self[0] as u8; 200] + } +} + +impl ToBytesForUnencryptedLog<192, 232> for [Field; 6] { + fn to_be_bytes_arr(self) -> [u8; 192] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 232] { + [self[0] as u8; 232] + } +} + +impl ToBytesForUnencryptedLog<224, 264> for [Field; 7] { + fn to_be_bytes_arr(self) -> [u8; 224] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 264] { + [self[0] as u8; 264] + } +} + +impl ToBytesForUnencryptedLog<256, 296> for [Field; 8] { + fn to_be_bytes_arr(self) -> [u8; 256] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 296] { + [self[0] as u8; 296] + } +} + +impl ToBytesForUnencryptedLog<288, 328> for [Field; 9] { + fn to_be_bytes_arr(self) -> [u8; 288] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 328] { + [self[0] as u8; 328] + } +} + +impl ToBytesForUnencryptedLog<320, 360> for [Field; 10] { + fn to_be_bytes_arr(self) -> [u8; 320] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 360] { + [self[0] as u8; 360] + } +} + +impl ToBytesForUnencryptedLog<352, 392> for [Field; 11] { + fn to_be_bytes_arr(self) -> [u8; 352] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 392] { + [self[0] as u8; 392] + } +} + +impl ToBytesForUnencryptedLog<384, 424> for [Field; 12] { + fn to_be_bytes_arr(self) -> [u8; 384] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 424] { + [self[0] as u8; 424] + } +} + +impl ToBytesForUnencryptedLog<416, 456> for [Field; 13] { + fn to_be_bytes_arr(self) -> [u8; 416] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 456] { + [self[0] as u8; 456] + } +} + +impl ToBytesForUnencryptedLog<448, 488> for [Field; 14] { + fn to_be_bytes_arr(self) -> [u8; 448] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 488] { + [self[0] as u8; 488] + } +} + +impl ToBytesForUnencryptedLog<480, 520> for [Field; 15] { + fn to_be_bytes_arr(self) -> [u8; 480] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 520] { + [self[0] as u8; 520] + } +} + +impl ToBytesForUnencryptedLog<512, 552> for [Field; 16] { + fn to_be_bytes_arr(self) -> [u8; 512] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 552] { + [self[0] as u8; 552] + } +} + +impl ToBytesForUnencryptedLog<544, 584> for [Field; 17] { + fn to_be_bytes_arr(self) -> [u8; 544] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 584] { + [self[0] as u8; 584] + } +} + +impl ToBytesForUnencryptedLog<576, 616> for [Field; 18] { + fn to_be_bytes_arr(self) -> [u8; 576] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 616] { + [self[0] as u8; 616] + } +} + +impl ToBytesForUnencryptedLog<608, 648> for [Field; 19] { + fn to_be_bytes_arr(self) -> [u8; 608] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 648] { + [self[0] as u8; 648] + } +} + +impl ToBytesForUnencryptedLog<640, 680> for [Field; 20] { + fn to_be_bytes_arr(self) -> [u8; 640] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 680] { + [self[0] as u8; 680] + } +} + +impl ToBytesForUnencryptedLog<672, 712> for [Field; 21] { + fn to_be_bytes_arr(self) -> [u8; 672] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 712] { + [self[0] as u8; 712] + } +} + +impl ToBytesForUnencryptedLog<704, 744> for [Field; 22] { + fn to_be_bytes_arr(self) -> [u8; 704] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 744] { + [self[0] as u8; 744] + } +} + +impl ToBytesForUnencryptedLog<736, 776> for [Field; 23] { + fn to_be_bytes_arr(self) -> [u8; 736] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 776] { + [self[0] as u8; 776] + } +} + +impl ToBytesForUnencryptedLog<768, 808> for [Field; 24] { + fn to_be_bytes_arr(self) -> [u8; 768] { + arr_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; 808] { + [self[0] as u8; 808] + } +} + +impl ToBytesForUnencryptedLog for str where [Field; L]: ToBytesForUnencryptedLog { + fn to_be_bytes_arr(self) -> [u8; N] { + str_to_be_bytes_arr(self) + } + fn output_bytes(self) -> [u8; M] { + [0; M] + } +} diff --git a/noir-projects/aztec-nr/aztec/src/oracle/public_call.nr b/noir-projects/aztec-nr/aztec/src/oracle/public_call.nr index 682c917cd09..6aecbdf6104 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/public_call.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/public_call.nr @@ -1,5 +1,5 @@ use dep::protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress}; - +// TODO(6052): get new side effect counter from this call #[oracle(callPublicFunction)] fn call_public_function_oracle( _contract_address: AztecAddress, diff --git a/noir-projects/aztec-nr/aztec/src/prelude.nr b/noir-projects/aztec-nr/aztec/src/prelude.nr index 8a9809fa2b1..15f3fdc7577 100644 --- a/noir-projects/aztec-nr/aztec/src/prelude.nr +++ b/noir-projects/aztec-nr/aztec/src/prelude.nr @@ -9,7 +9,7 @@ use crate::{ public_immutable::PublicImmutable, public_mutable::PublicMutable, private_set::PrivateSet, shared_immutable::SharedImmutable, shared_mutable::SharedMutable, storage::Storable }, - log::emit_encrypted_log, context::{PrivateContext, PackedReturns, FunctionReturns}, + context::{PrivateContext, PackedReturns, FunctionReturns}, note::{ note_header::NoteHeader, note_interface::NoteInterface, note_getter_options::NoteGetterOptions, note_viewer_options::NoteViewerOptions, diff --git a/noir-projects/aztec-nr/value-note/src/value_note.nr b/noir-projects/aztec-nr/value-note/src/value_note.nr index b67cd8a98dc..d6597caa352 100644 --- a/noir-projects/aztec-nr/value-note/src/value_note.nr +++ b/noir-projects/aztec-nr/value-note/src/value_note.nr @@ -5,7 +5,7 @@ use dep::aztec::{ }, note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_consumption}, oracle::{unsafe_rand::unsafe_rand, nullifier_key::get_app_nullifier_secret_key, get_public_key::get_public_key}, - log::emit_encrypted_log, hash::poseidon2_hash, context::PrivateContext + hash::poseidon2_hash, context::PrivateContext }; global VALUE_NOTE_LEN: Field = 3; // 3 plus a header. @@ -47,8 +47,7 @@ impl NoteInterface for ValueNote { // 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.emit_encrypted_log( (*context).this_address(), slot, Self::get_note_type_id(), diff --git a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr index 435f61191b3..18a9123e1d4 100644 --- a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr +++ b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr @@ -1,4 +1,4 @@ -use dep::aztec::prelude::{AztecAddress, PrivateContext, NoteHeader, emit_encrypted_log, NoteInterface}; +use dep::aztec::prelude::{AztecAddress, PrivateContext, NoteHeader, NoteInterface}; use dep::aztec::{ protocol_types::constants::GENERATOR_INDEX__NOTE_NULLIFIER, note::utils::compute_note_hash_for_consumption, hash::poseidon2_hash, @@ -40,8 +40,7 @@ impl NoteInterface for SubscriptionNote { // 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.emit_encrypted_log( (*context).this_address(), slot, Self::get_note_type_id(), diff --git a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr index ff8e270a2a0..75cfe1a1673 100644 --- a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr @@ -102,4 +102,14 @@ contract Child { context.emit_unencrypted_log(20); let _result = Child::at(context.this_address()).pub_set_value(10).call(&mut context); } + + #[aztec(public)] + // TODO(6052): The logs emitted are currently in the wrong order as we don't update + // counters for nested public calls + fn set_value_with_two_nested_calls() { + Child::at(context.this_address()).set_value_twice_with_nested_first().call(&mut context); + Child::at(context.this_address()).set_value_twice_with_nested_last().call(&mut context); + storage.current_value.write(20); + context.emit_unencrypted_log(20); + } } diff --git a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr index 8cb01885ccd..0dbf1fbb2c9 100644 --- a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr @@ -52,7 +52,7 @@ contract ContractClassRegisterer { public_bytecode_commitment ] ); - context.emit_unencrypted_log(event.serialize()); + context.emit_contract_class_unencrypted_log(event.serialize()); } #[aztec(private)] @@ -87,7 +87,7 @@ contract ContractClassRegisterer { function_data.metadata_hash ] ); - context.emit_unencrypted_log(event.serialize()); + context.emit_contract_class_unencrypted_log(event.serialize()); } #[aztec(private)] @@ -117,6 +117,6 @@ contract ContractClassRegisterer { function_data.metadata_hash ] ); - context.emit_unencrypted_log(event.serialize()); + context.emit_contract_class_unencrypted_log(event.serialize()); } } diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr index efab4af4892..684314291fb 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr @@ -1,4 +1,4 @@ -use dep::aztec::prelude::{AztecAddress, NoteInterface, NoteHeader, PrivateContext, emit_encrypted_log}; +use dep::aztec::prelude::{AztecAddress, NoteInterface, NoteHeader, PrivateContext}; use dep::aztec::{ note::{utils::compute_note_hash_for_consumption}, oracle::{nullifier_key::get_app_nullifier_secret_key, get_public_key::get_public_key}, @@ -48,8 +48,7 @@ impl NoteInterface for CardNote { // 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.emit_encrypted_log( (*context).this_address(), slot, Self::get_note_type_id(), diff --git a/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr b/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr index b97ff8bad97..6804c0f483a 100644 --- a/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr +++ b/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr @@ -1,6 +1,5 @@ use dep::aztec::prelude::{ - AztecAddress, FunctionSelector, NoteHeader, NoteInterface, NoteGetterOptions, PrivateContext, - emit_encrypted_log + AztecAddress, FunctionSelector, NoteHeader, NoteInterface, NoteGetterOptions, PrivateContext }; use dep::aztec::{ @@ -89,8 +88,7 @@ impl NoteInterface for EcdsaPublicKeyNote { // 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.emit_encrypted_log( (*context).this_address(), slot, Self::get_note_type_id(), diff --git a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr index 09675c3f409..f49828140bf 100644 --- a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr @@ -5,8 +5,7 @@ contract PendingNoteHashes { // Libs use dep::aztec::prelude::{ - AztecAddress, FunctionSelector, NoteHeader, NoteGetterOptions, PrivateContext, Map, PrivateSet, - emit_encrypted_log + AztecAddress, FunctionSelector, NoteHeader, NoteGetterOptions, PrivateContext, Map, PrivateSet }; use dep::value_note::{balance_utils, filter::filter_notes_min_sum, value_note::{VALUE_NOTE_LEN, ValueNote}}; use dep::aztec::context::{PublicContext, Context}; diff --git a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr index c0031833c26..c7061cac998 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr @@ -1,4 +1,4 @@ -use dep::aztec::prelude::{AztecAddress, NoteHeader, NoteInterface, PrivateContext, emit_encrypted_log}; +use dep::aztec::prelude::{AztecAddress, NoteHeader, NoteInterface, PrivateContext}; use dep::aztec::{ note::utils::compute_note_hash_for_consumption, hash::poseidon2_hash, oracle::{nullifier_key::get_app_nullifier_secret_key, get_public_key::get_public_key}, @@ -40,8 +40,7 @@ impl NoteInterface for PublicKeyNote { // 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.emit_encrypted_log( (*context).this_address(), slot, Self::get_note_type_id(), diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr index 74f0e049512..b73da67a097 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -236,6 +236,24 @@ contract Test { context.emit_unencrypted_log(fields); } + #[aztec(private)] + fn emit_unencrypted_logs_nested(fields: [Field; 5]) { + Test::at(context.this_address()).emit_msg_sender().call(&mut context); + Test::at(context.this_address()).emit_array_as_unencrypted_log(fields).call(&mut context); + context.emit_unencrypted_log("test"); + } + + #[aztec(private)] + fn emit_encrypted_logs_nested(value: Field, owner: AztecAddress) { + let mut storage_slot = storage.example_constant.get_storage_slot() + 1; + Test::at(context.this_address()).call_create_note(value, owner, storage_slot).call(&mut context); + storage_slot += 1; + let mut note = ValueNote::new(value + 1, owner); + create_note(&mut context, storage_slot, &mut note, true); + storage_slot += 1; + Test::at(context.this_address()).call_create_note(value + 2, owner, storage_slot).call(&mut context); + } + // docs:start:is-time-equal #[aztec(public)] fn is_time_equal(time: u64) -> u64 { diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr index 5f6edf94d5f..798a9cfe174 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr @@ -1,5 +1,5 @@ use dep::aztec::{ - prelude::{AztecAddress, NoteHeader, NoteInterface, PrivateContext, emit_encrypted_log}, + prelude::{AztecAddress, NoteHeader, NoteInterface, PrivateContext}, protocol_types::constants::GENERATOR_INDEX__NOTE_NULLIFIER, note::utils::compute_note_hash_for_consumption, hash::poseidon2_hash, oracle::{unsafe_rand::unsafe_rand, nullifier_key::get_app_nullifier_secret_key, get_public_key::get_public_key} @@ -53,8 +53,7 @@ impl NoteInterface for TokenNote { // We only bother inserting the note if non-empty to save funds on gas. if !(self.amount == U128::from_integer(0)) { let encryption_pub_key = get_public_key(self.owner); - emit_encrypted_log( - context, + context.emit_encrypted_log( (*context).this_address(), slot, Self::get_note_type_id(), diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr index 0a71bd9891e..3862e9d0f38 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr @@ -1,6 +1,6 @@ use dep::aztec::prelude::{ AztecAddress, NoteGetterOptions, NoteViewerOptions, NoteHeader, NoteInterface, PrivateContext, - PrivateSet, Map, emit_encrypted_log + PrivateSet, Map }; use dep::aztec::{ context::{PublicContext, Context}, hash::pedersen_hash, diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr index 5f6edf94d5f..798a9cfe174 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr @@ -1,5 +1,5 @@ use dep::aztec::{ - prelude::{AztecAddress, NoteHeader, NoteInterface, PrivateContext, emit_encrypted_log}, + prelude::{AztecAddress, NoteHeader, NoteInterface, PrivateContext}, protocol_types::constants::GENERATOR_INDEX__NOTE_NULLIFIER, note::utils::compute_note_hash_for_consumption, hash::poseidon2_hash, oracle::{unsafe_rand::unsafe_rand, nullifier_key::get_app_nullifier_secret_key, get_public_key::get_public_key} @@ -53,8 +53,7 @@ impl NoteInterface for TokenNote { // We only bother inserting the note if non-empty to save funds on gas. if !(self.amount == U128::from_integer(0)) { let encryption_pub_key = get_public_key(self.owner); - emit_encrypted_log( - context, + context.emit_encrypted_log( (*context).this_address(), slot, Self::get_note_type_id(), diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr index 27fc6b7c721..ef7e3b369f2 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr @@ -90,6 +90,7 @@ mod tests { use dep::types::constants::{ MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NEW_NOTE_HASHES_PER_TX, MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, + MAX_ENCRYPTED_LOGS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, DA_BYTES_PER_FIELD, DA_GAS_PER_BYTE }; use dep::types::{ @@ -98,7 +99,7 @@ mod tests { note_hash::NoteHashContext, nullifier::Nullifier, side_effect::{SideEffect, Ordered}, gas::Gas }, grumpkin_private_key::GrumpkinPrivateKey, - hash::{compute_note_hash_nonce, compute_unique_siloed_note_hash, accumulate_sha256}, + hash::{compute_note_hash_nonce, compute_unique_siloed_note_hash, sha256_to_field}, tests::{fixture_builder::FixtureBuilder, sort::sort_get_sorted_hints}, utils::{arrays::{array_eq, array_length}}, traits::{Empty, is_empty, is_empty_array} }; @@ -319,10 +320,19 @@ mod tests { public_inputs.end.unencrypted_log_preimages_length, unencrypted_log_preimages_length + prev_unencrypted_log_preimages_length ); - let expected_encrypted_logs_hash = accumulate_sha256([0, prev_encrypted_logs_hash]); + let hash_bytes: [u8; MAX_ENCRYPTED_LOGS_PER_TX * 32] = prev_encrypted_logs_hash + .to_be_bytes(32) + .append(&[0; MAX_ENCRYPTED_LOGS_PER_TX * 32 - 32]) + .as_array(); + let expected_encrypted_logs_hash = sha256_to_field(hash_bytes); assert_eq(public_inputs.end.encrypted_logs_hash, expected_encrypted_logs_hash); - let mut expected_unencrypted_logs_hash = accumulate_sha256([0, prev_unencrypted_logs_hash]); - expected_unencrypted_logs_hash = accumulate_sha256([expected_unencrypted_logs_hash, unencrypted_logs_hash]); + + let hash_bytes: [u8; MAX_UNENCRYPTED_LOGS_PER_TX * 32] = prev_unencrypted_logs_hash + .to_be_bytes(32) + .append(unencrypted_logs_hash.to_be_bytes(32)) + .append(&[0; MAX_UNENCRYPTED_LOGS_PER_TX * 32 - 64]) + .as_array(); + let expected_unencrypted_logs_hash = sha256_to_field(hash_bytes); assert_eq(public_inputs.end.unencrypted_logs_hash, expected_unencrypted_logs_hash); } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr index c2804fd67bb..ed4cd1fd49d 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr @@ -121,9 +121,10 @@ mod tests { MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_PUBLIC_DATA_HINTS, MAX_PUBLIC_DATA_READS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT, NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_HEIGHT, PUBLIC_DATA_SUBTREE_HEIGHT, - PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, PUBLIC_DATA_TREE_HEIGHT + PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, PUBLIC_DATA_TREE_HEIGHT, MAX_ENCRYPTED_LOGS_PER_TX, + MAX_UNENCRYPTED_LOGS_PER_TX, }, - hash::{silo_nullifier, accumulate_sha256}, + hash::{silo_nullifier, sha256_to_field}, public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, tests::{fixture_builder::FixtureBuilder, merkle_tree_utils::NonEmptyMerkleTree}, partial_state_reference::PartialStateReference, utils::arrays::array_merge @@ -359,10 +360,19 @@ mod tests { public_inputs.end.unencrypted_log_preimages_length, unencrypted_log_preimages_length + prev_unencrypted_log_preimages_length ); - let expected_encrypted_logs_hash = accumulate_sha256([0, prev_encrypted_logs_hash]); + let hash_bytes: [u8; MAX_ENCRYPTED_LOGS_PER_TX * 32] = prev_encrypted_logs_hash + .to_be_bytes(32) + .append(&[0; MAX_ENCRYPTED_LOGS_PER_TX * 32 - 32]) + .as_array(); + let expected_encrypted_logs_hash = sha256_to_field(hash_bytes); assert_eq(public_inputs.end.encrypted_logs_hash, expected_encrypted_logs_hash); - let mut expected_unencrypted_logs_hash = accumulate_sha256([0, prev_unencrypted_logs_hash]); - expected_unencrypted_logs_hash = accumulate_sha256([expected_unencrypted_logs_hash, unencrypted_logs_hash]); + + let hash_bytes: [u8; MAX_UNENCRYPTED_LOGS_PER_TX * 32] = prev_unencrypted_logs_hash + .to_be_bytes(32) + .append(unencrypted_logs_hash.to_be_bytes(32)) + .append(&[0; MAX_UNENCRYPTED_LOGS_PER_TX * 32 - 64]) + .as_array(); + let expected_unencrypted_logs_hash = sha256_to_field(hash_bytes); assert_eq(public_inputs.end.unencrypted_logs_hash, expected_unencrypted_logs_hash); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/private_accumulated_data_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/private_accumulated_data_builder.nr index 14041908fb5..984a1a292a1 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/private_accumulated_data_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/private_accumulated_data_builder.nr @@ -181,7 +181,7 @@ impl PrivateAccumulatedDataBuilder { } revertible_builder.new_l2_to_l1_msgs = self.new_l2_to_l1_msgs; - // TODO(1165): Once we have individual lens, split here + // TODO(1641) & TODO(4712): Once we track logs with more info, including individual lens, split here revertible_builder.encrypted_log_preimages_length = self.encrypted_log_preimages_length; revertible_builder.unencrypted_log_preimages_length = self.unencrypted_log_preimages_length; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr index ef7bc7f62bd..31740a66be7 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr @@ -8,7 +8,8 @@ use crate::traits::is_empty; use crate::utils::{uint256::U256, field::field_from_bytes_32_trunc}; use crate::constants::{ FUNCTION_TREE_HEIGHT, GENERATOR_INDEX__SILOED_NOTE_HASH, GENERATOR_INDEX__OUTER_NULLIFIER, - GENERATOR_INDEX__VK, GENERATOR_INDEX__NOTE_HASH_NONCE, GENERATOR_INDEX__UNIQUE_NOTE_HASH + GENERATOR_INDEX__VK, GENERATOR_INDEX__NOTE_HASH_NONCE, GENERATOR_INDEX__UNIQUE_NOTE_HASH, + MAX_ENCRYPTED_LOGS_PER_TX }; use crate::traits::Hash; use crate::messaging::l2_to_l1_message::L2ToL1Message; @@ -118,33 +119,28 @@ pub fn accumulate_sha256(input: [Field; 2]) -> Field { sha256_to_field(hash_input_flattened) } -pub fn compute_tx_logs_hash(logs: [SideEffect; MAX_ENCRYPTED_LOGS_PER_TX]) -> Field { - // TODO(Miranda): Below is flat hashing which would reduce constraints (we now only hash once in tail) - convert to this? - - // // Convert each field element into a byte array and append the bytes to `hash_input_flattened` - // // Ideally we would define a new global here but for now we assert in case MAX_LOGS changes - // assert(MAX_ENCRYPTED_LOGS_PER_TX * 32 == 256); - // let mut hash_input_flattened = [0; 256]; - // for offset in 0..MAX_ENCRYPTED_LOGS_PER_TX { - // let input_as_bytes = logs[offset].value.to_be_bytes(32); - // for byte_index in 0..32 { - // hash_input_flattened[offset * 32 + byte_index] = input_as_bytes[byte_index]; - // } - // } - // // This differs from accumulate_sha256 as we could increase MAX_LOGS and - // // ideally we would push to a slice then hash, but in practice compilation was very slow - // // Hardcode to 256 bytes for now - // sha256_to_field(hash_input_flattened) - - // Assuming logs are pre-sorted - let mut accumulated_logs_hash = 0; - for i in 0..MAX_ENCRYPTED_LOGS_PER_TX { - if !is_empty(logs[i]) { - accumulated_logs_hash = accumulate_sha256([accumulated_logs_hash, logs[i].value]); +// Computes the final logs hash for a tx. +// NB: this assumes MAX_ENCRYPTED_LOGS_PER_TX == MAX_UNENCRYPTED_LOGS_PER_TX +// to avoid doubling code, since we can't define the byte len to be 32*N directly. +pub fn compute_tx_logs_hash(logs: [SideEffect; MAX_ENCRYPTED_LOGS_PER_TX]) -> Field { + // Convert each field element into a byte array and append the bytes to `hash_input_flattened` + let mut hash_input_flattened = [0; MAX_ENCRYPTED_LOGS_PER_TX * 32]; + for offset in 0..MAX_ENCRYPTED_LOGS_PER_TX { + let input_as_bytes = logs[offset].value.to_be_bytes(32); + for byte_index in 0..32 { + hash_input_flattened[offset * 32 + byte_index] = input_as_bytes[byte_index]; } } - - accumulated_logs_hash + // Ideally we would push to a slice then hash, but there is no sha_slice + // Hardcode to 256 bytes for now + let mut hash = sha256_to_field(hash_input_flattened); + // Not having a 0 value hash for empty logs causes issues with empty txs + // used for padding. Returning early is currently unsupported. + // We always provide sorted logs here, so 0 being empty means all are empty. + if is_empty(logs[0]) { + hash = 0; + } + hash } pub fn compute_note_hash_nonce(first_nullifier: Field, commitment_index: u64) -> Field { diff --git a/yarn-project/circuit-types/src/body.ts b/yarn-project/circuit-types/src/body.ts index 03a983d95da..6bb1146c395 100644 --- a/yarn-project/circuit-types/src/body.ts +++ b/yarn-project/circuit-types/src/body.ts @@ -1,7 +1,7 @@ import { EncryptedL2BlockL2Logs, TxEffect, UnencryptedL2BlockL2Logs } from '@aztec/circuit-types'; import { padArrayEnd } from '@aztec/foundation/collection'; -import { sha256 } from '@aztec/foundation/crypto'; -import { BufferReader, serializeToBuffer, truncateAndPad } from '@aztec/foundation/serialize'; +import { sha256Trunc } from '@aztec/foundation/crypto'; +import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { inspect } from 'util'; @@ -56,7 +56,7 @@ export class Body { const left = layers[activeLayer][i]; const right = layers[activeLayer][i + 1]; - layer.push(truncateAndPad(sha256(Buffer.concat([left, right])))); + layer.push(sha256Trunc(Buffer.concat([left, right]))); } layers.push(layer); diff --git a/yarn-project/circuit-types/src/l2_block.test.ts b/yarn-project/circuit-types/src/l2_block.test.ts index 5a1a39c0699..64546f8ed37 100644 --- a/yarn-project/circuit-types/src/l2_block.test.ts +++ b/yarn-project/circuit-types/src/l2_block.test.ts @@ -27,7 +27,7 @@ describe('L2Block', () => { // The following 2 values are copied from `testComputeKernelLogs1Iteration` in `Decoder.t.sol` const encodedLogs = Buffer.from('0000000c000000080000000493e78a70', 'hex'); const logs = EncryptedTxL2Logs.fromBuffer(encodedLogs, true); - const referenceLogsHash = Buffer.from('0020f9217a7218a377a78d0e8929b87d31c32d270817fe8f5fe876c61b741024', 'hex'); + const referenceLogsHash = Buffer.from('0044339f3cafeb22de0d76423142797f1d4520c6cad559de5d1390bb7ab4c812', 'hex'); const logsHash = logs.hash(); expect(logsHash).toEqual(referenceLogsHash); @@ -41,7 +41,7 @@ describe('L2Block', () => { 'hex', ); const logs = EncryptedTxL2Logs.fromBuffer(encodedLogs, true); - const referenceLogsHash = Buffer.from('007e066525b587fdfb3704301ffcfa4b6a585d95491926d0fd5698f3ae603b18', 'hex'); + const referenceLogsHash = Buffer.from('00ebc16f83abc50c57496375353bf377b06bef23880bd3e9975ea1f7f5a0e8b1', 'hex'); const logsHash = logs.hash(); expect(logsHash).toEqual(referenceLogsHash); @@ -56,7 +56,7 @@ describe('L2Block', () => { 'hex', ); const logs = EncryptedTxL2Logs.fromBuffer(encodedLogs, true); - const referenceLogsHash = Buffer.from('007e066525b587fdfb3704301ffcfa4b6a585d95491926d0fd5698f3ae603b18', 'hex'); + const referenceLogsHash = Buffer.from('00ebc16f83abc50c57496375353bf377b06bef23880bd3e9975ea1f7f5a0e8b1', 'hex'); const logsHash = logs.hash(); expect(logsHash).toEqual(referenceLogsHash); diff --git a/yarn-project/circuit-types/src/logs/tx_l2_logs.ts b/yarn-project/circuit-types/src/logs/tx_l2_logs.ts index e0814eafa4e..c18bec7c3c5 100644 --- a/yarn-project/circuit-types/src/logs/tx_l2_logs.ts +++ b/yarn-project/circuit-types/src/logs/tx_l2_logs.ts @@ -84,18 +84,21 @@ export abstract class TxL2Logs { * for more details. */ public hash(): Buffer { - const logsHashes: [Buffer, Buffer] = [Buffer.alloc(32), Buffer.alloc(32)]; - let kernelPublicInputsLogsHash = Buffer.alloc(32); + if (this.unrollLogs().length == 0) { + return Buffer.alloc(32); + } + let flattenedLogs = Buffer.alloc(0); for (const logsFromSingleFunctionCall of this.unrollLogs()) { - logsHashes[0] = kernelPublicInputsLogsHash; - logsHashes[1] = logsFromSingleFunctionCall.hash(); // privateCircuitPublicInputsLogsHash - - // Hash logs hash from the public inputs of previous kernel iteration and logs hash from private circuit public inputs - kernelPublicInputsLogsHash = sha256Trunc(Buffer.concat(logsHashes)); + flattenedLogs = Buffer.concat([flattenedLogs, logsFromSingleFunctionCall.hash()]); + } + // pad the end of logs with 0s + // NB - This assumes MAX_ENCRYPTED_LOGS_PER_TX == MAX_UNENCRYPTED_LOGS_PER_TX + for (let i = 0; i < MAX_ENCRYPTED_LOGS_PER_TX - this.unrollLogs().length; i++) { + flattenedLogs = Buffer.concat([flattenedLogs, Buffer.alloc(32)]); } - return kernelPublicInputsLogsHash; + return sha256Trunc(flattenedLogs); } } diff --git a/yarn-project/circuit-types/src/mocks.ts b/yarn-project/circuit-types/src/mocks.ts index 8adb5686257..395346f4a76 100644 --- a/yarn-project/circuit-types/src/mocks.ts +++ b/yarn-project/circuit-types/src/mocks.ts @@ -103,8 +103,8 @@ export const mockTx = ( ); } else { data.forRollup!.end.newNullifiers[0] = firstNullifier.value; - data.forRollup!.end.encryptedLogsHash = hasLogs ? Fr.fromBuffer(encryptedLogs.hash()) : Fr.ZERO; - data.forRollup!.end.unencryptedLogsHash = hasLogs ? Fr.fromBuffer(unencryptedLogs.hash()) : Fr.ZERO; + data.forRollup!.end.encryptedLogsHash = Fr.fromBuffer(encryptedLogs.hash()); + data.forRollup!.end.unencryptedLogsHash = Fr.fromBuffer(unencryptedLogs.hash()); } const tx = new Tx(data, new Proof(Buffer.alloc(0)), encryptedLogs, unencryptedLogs, publicCallRequests); diff --git a/yarn-project/circuit-types/src/tx/tx.ts b/yarn-project/circuit-types/src/tx/tx.ts index 1bf14f809be..63e2c62fdd7 100644 --- a/yarn-project/circuit-types/src/tx/tx.ts +++ b/yarn-project/circuit-types/src/tx/tx.ts @@ -39,18 +39,6 @@ export class Tx { */ public readonly enqueuedPublicFunctionCalls: PublicCallRequest[], ) { - if (this.unencryptedLogs.functionLogs.length < this.encryptedLogs.functionLogs.length) { - // TODO(Miranda): This error was not throwing in some cases, as logs are nested objects which would show len 1 even if no logs existed - // Many tests produce enc logs and no unenc logs, so this error should have been throwing even in good cases - // This check is present because each private function invocation creates encrypted FunctionL2Logs object and - // both public and private function invocations create unencrypted FunctionL2Logs object. Hence "num unencrypted" - // >= "num encrypted". - throw new Error( - `Number of function logs in unencrypted logs (${this.unencryptedLogs.functionLogs.length}) has to be equal - or larger than number function logs in encrypted logs (${this.encryptedLogs.functionLogs.length})`, - ); - } - const kernelPublicCallStackSize = data.numberOfPublicCallRequests(); if (kernelPublicCallStackSize !== enqueuedPublicFunctionCalls.length) { throw new Error( diff --git a/yarn-project/circuit-types/src/tx_effect.ts b/yarn-project/circuit-types/src/tx_effect.ts index 7d2e991602f..ad70f4e2974 100644 --- a/yarn-project/circuit-types/src/tx_effect.ts +++ b/yarn-project/circuit-types/src/tx_effect.ts @@ -8,13 +8,8 @@ import { RevertCode, } from '@aztec/circuits.js'; import { makeTuple } from '@aztec/foundation/array'; -import { sha256 } from '@aztec/foundation/crypto'; -import { - BufferReader, - serializeArrayOfBufferableToVector, - serializeToBuffer, - truncateAndPad, -} from '@aztec/foundation/serialize'; +import { sha256Trunc } from '@aztec/foundation/crypto'; +import { BufferReader, serializeArrayOfBufferableToVector, serializeToBuffer } from '@aztec/foundation/serialize'; import { inspect } from 'util'; @@ -157,7 +152,7 @@ export class TxEffect { unencryptedLogsHashKernel0, ]); - return truncateAndPad(sha256(inputValue)); + return sha256Trunc(inputValue); } static random( diff --git a/yarn-project/end-to-end/src/e2e_block_building.test.ts b/yarn-project/end-to-end/src/e2e_block_building.test.ts index 31698d5516b..3ad1d2c9f2f 100644 --- a/yarn-project/end-to-end/src/e2e_block_building.test.ts +++ b/yarn-project/end-to-end/src/e2e_block_building.test.ts @@ -1,3 +1,4 @@ +import { getSchnorrAccount } from '@aztec/accounts/schnorr'; import { type AztecAddress, type AztecNode, @@ -11,6 +12,7 @@ import { type TxReceipt, TxStatus, type Wallet, + deriveKeys, } from '@aztec/aztec.js'; import { times } from '@aztec/foundation/collection'; import { pedersenHash } from '@aztec/foundation/crypto'; @@ -18,6 +20,7 @@ import { StatefulTestContractArtifact } from '@aztec/noir-contracts.js'; import { TestContract } from '@aztec/noir-contracts.js/Test'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; +import { TaggedNote } from '../../circuit-types/src/logs/l1_note_payload/tagged_note.js'; import { setup } from './fixtures/utils.js'; describe('e2e_block_building', () => { @@ -184,6 +187,60 @@ describe('e2e_block_building', () => { }); }); }); + + describe('logs in nested calls are ordered as expected', () => { + // This test was originally writted for e2e_nested, but it was refactored + // to not use TestContract. + let testContract: TestContract; + + beforeEach(async () => { + ({ teardown, pxe, logger, wallet: owner } = await setup(1)); + logger.info(`Deploying test contract`); + testContract = await TestContract.deploy(owner).send().deployed(); + }, 30_000); + + it('calls a method with nested unencrypted logs', async () => { + const tx = await testContract.methods.emit_unencrypted_logs_nested([1, 2, 3, 4, 5]).send().wait(); + const logs = (await pxe.getUnencryptedLogs({ txHash: tx.txHash })).logs.map(l => l.log); + + // First log should be contract address + expect(logs[0].data).toEqual(testContract.address.toBuffer()); + + // Second log should be array of fields + let expectedBuffer = Buffer.concat([1, 2, 3, 4, 5].map(num => new Fr(num).toBuffer())); + expect(logs[1].data.subarray(-32 * 5)).toEqual(expectedBuffer); + + // Third log should be string "test" + expectedBuffer = Buffer.concat( + ['t', 'e', 's', 't'].map(num => Buffer.concat([Buffer.alloc(31), Buffer.from(num)])), + ); + expect(logs[2].data.subarray(-32 * 5)).toEqual(expectedBuffer); + }, 30_000); + + it('calls a method with nested encrypted logs', async () => { + // account setup + const privateKey = new Fr(7n); + const keys = deriveKeys(privateKey); + const account = getSchnorrAccount(pxe, privateKey, keys.masterIncomingViewingSecretKey); + await account.deploy().wait(); + const thisWallet = await account.getWallet(); + + // call test contract + const action = testContract.methods.emit_encrypted_logs_nested(10, thisWallet.getAddress()); + const tx = await action.prove(); + const rct = await action.send().wait(); + + // compare logs + expect(rct.status).toEqual('mined'); + const decryptedLogs = tx.encryptedLogs + .unrollLogs() + .map(l => TaggedNote.fromEncryptedBuffer(l.data, keys.masterIncomingViewingSecretKey)); + const notevalues = decryptedLogs.map(l => l?.notePayload.note.items[0]); + expect(notevalues[0]).toEqual(new Fr(10)); + expect(notevalues[1]).toEqual(new Fr(11)); + expect(notevalues[2]).toEqual(new Fr(12)); + }, 30_000); + }); }); /** diff --git a/yarn-project/end-to-end/src/e2e_ordering.test.ts b/yarn-project/end-to-end/src/e2e_ordering.test.ts index 0f777d7ea2f..e5ad26a3868 100644 --- a/yarn-project/end-to-end/src/e2e_ordering.test.ts +++ b/yarn-project/end-to-end/src/e2e_ordering.test.ts @@ -90,8 +90,11 @@ describe('e2e_ordering', () => { const expectedOrders = { set_value_twice_with_nested_first: [nestedValue, directValue] as bigint[], // eslint-disable-line camelcase set_value_twice_with_nested_last: [directValue, nestedValue] as bigint[], // eslint-disable-line camelcase + // TODO(6052) + // set_value_with_two_nested_calls: [nestedValue, directValue, directValue, nestedValue, directValue] as bigint[], // eslint-disable-line camelcase } as const; + // TODO(6052): Once resolved, add 'set_value_with_nested_calls' it.each(['set_value_twice_with_nested_first', 'set_value_twice_with_nested_last'] as const)( 'orders public state updates in %s (and ensures final state value is correct)', async method => { @@ -100,7 +103,7 @@ describe('e2e_ordering', () => { await child.methods[method]().send().wait(); const value = await pxe.getPublicStorageAt(child.address, new Fr(1)); - expect(value.value).toBe(expectedOrder[1]); // final state should match last value set + expect(value.value).toBe(expectedOrder[expectedOrder.length - 1]); // final state should match last value set }, ); @@ -111,6 +114,7 @@ describe('e2e_ordering', () => { // in reverse order. More info in this thread: https://discourse.aztec.network/t/identifying-the-ordering-of-state-access-across-contract-calls/382/12#transition-counters-for-private-calls-2 // The below only works due to a hack which sorts the logs in ts // See tail_phase_manager.ts + // TODO(6052): Once resolved, add 'set_value_with_two_nested_calls' it.each(['set_value_twice_with_nested_first', 'set_value_twice_with_nested_last'] as const)( 'orders unencrypted logs in %s', async method => { diff --git a/yarn-project/pxe/src/kernel_prover/kernel_prover.test.ts b/yarn-project/pxe/src/kernel_prover/kernel_prover.test.ts index 0179add5ec3..bf95927e70d 100644 --- a/yarn-project/pxe/src/kernel_prover/kernel_prover.test.ts +++ b/yarn-project/pxe/src/kernel_prover/kernel_prover.test.ts @@ -1,4 +1,4 @@ -import { EncryptedFunctionL2Logs, Note, UnencryptedFunctionL2Logs } from '@aztec/circuit-types'; +import { Note } from '@aztec/circuit-types'; import { FunctionData, FunctionSelector, @@ -77,8 +77,8 @@ describe('Kernel Prover', () => { acir: Buffer.alloc(0), partialWitness: new Map(), enqueuedPublicFunctionCalls: [], - encryptedLogs: EncryptedFunctionL2Logs.empty(), - unencryptedLogs: UnencryptedFunctionL2Logs.empty(), + encryptedLogs: [], + unencryptedLogs: [], }; }; diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index c1747a2d6c4..dc75deb293b 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -1,8 +1,6 @@ import { type AuthWitness, type AztecNode, - EncryptedFunctionL2Logs, - type EncryptedL2Log, EncryptedTxL2Logs, ExtendedNote, type FunctionCall, @@ -20,8 +18,6 @@ import { type TxExecutionRequest, type TxHash, type TxReceipt, - UnencryptedFunctionL2Logs, - type UnencryptedL2Log, UnencryptedTxL2Logs, isNoirCallStackUnresolved, } from '@aztec/circuit-types'; @@ -35,7 +31,6 @@ import { type PartialAddress, type PrivateKernelTailCircuitPublicInputs, type PublicCallRequest, - type SideEffect, computeContractClassId, getContractClassFromArtifact, } from '@aztec/circuits.js'; @@ -49,9 +44,9 @@ import { Timer } from '@aztec/foundation/timer'; import { type AcirSimulator, type ExecutionResult, - collectEncryptedLogs, collectEnqueuedPublicFunctionCalls, - collectUnencryptedLogs, + collectSortedEncryptedLogs, + collectSortedUnencryptedLogs, resolveOpcodeLocations, } from '@aztec/simulator'; import { type ContractClassWithId, type ContractInstanceWithAddress } from '@aztec/types/contracts'; @@ -670,7 +665,8 @@ export class PXEService implements PXE { this.log.debug(`Executing kernel prover...`); const { proof, publicInputs } = await kernelProver.prove(txExecutionRequest.toTxRequest(), executionResult); - const { encryptedLogs, unencryptedLogs } = this.patchLogsOrdering(executionResult); + const unencryptedLogs = new UnencryptedTxL2Logs([collectSortedUnencryptedLogs(executionResult)]); + const encryptedLogs = new EncryptedTxL2Logs([collectSortedEncryptedLogs(executionResult)]); const enqueuedPublicFunctions = collectEnqueuedPublicFunctionCalls(executionResult); // HACK(#1639): Manually patches the ordering of the public call stack @@ -788,61 +784,6 @@ export class PXEService implements PXE { ); } - // As above, this is a hack for encrypted/unencrypted logs ordering, now they are sorted. Since the private kernel - // cannot keep track of side effects that happen after or before a nested call, we override the gathered logs. - // As a sanity check, we at least verify that the elements are the same, so we are only tweaking their ordering. - // See yarn-project/end-to-end/src/e2e_ordering.test.ts - // See https://github.com/AztecProtocol/aztec-packages/issues/1641 - // Added as part of resolving #5017 - private patchLogsOrdering(execResult: ExecutionResult) { - const encLogs = collectEncryptedLogs(execResult).flatMap(l => l.logs); - const unencLogs = collectUnencryptedLogs(execResult).flatMap(l => l.logs); - const getLogs = (res: ExecutionResult, enc: boolean) => { - const logs: SideEffect[] = enc - ? res.callStackItem.publicInputs.encryptedLogsHashes.concat(res.nestedExecutions.flatMap(e => getLogs(e, true))) - : res.callStackItem.publicInputs.unencryptedLogsHashes.concat( - res.nestedExecutions.flatMap(e => getLogs(e, false)), - ); - - return logs; - }; - - const sortSEs = (a: SideEffect, b: SideEffect) => { - if (a.isEmpty()) { - return 1; - } else if (b.isEmpty()) { - return -1; - } else { - return Number(a.counter.toBigInt() - b.counter.toBigInt()); - } - }; - - const sortedEncLogs = getLogs(execResult, true).sort(sortSEs); - const sortedUnencLogs = getLogs(execResult, false).sort(sortSEs); - - const finalEncLogs: EncryptedL2Log[] = []; - sortedEncLogs.forEach((sideEffect: SideEffect) => { - if (!sideEffect.isEmpty()) { - const isLog = (log: EncryptedL2Log) => Fr.fromBuffer(log.hash()).equals(sideEffect.value); - const thisLogIndex = encLogs.findIndex(isLog); - finalEncLogs.push(encLogs[thisLogIndex]); - } - }); - - const finalUnencLogs: UnencryptedL2Log[] = []; - sortedUnencLogs.forEach((sideEffect: SideEffect) => { - if (!sideEffect.isEmpty()) { - const isLog = (log: UnencryptedL2Log) => Fr.fromBuffer(log.hash()).equals(sideEffect.value); - const thisLogIndex = unencLogs.findIndex(isLog); - finalUnencLogs.push(unencLogs[thisLogIndex]); - } - }); - - const encryptedLogs = new EncryptedTxL2Logs([new EncryptedFunctionL2Logs(finalEncLogs)]); - const unencryptedLogs = new UnencryptedTxL2Logs([new UnencryptedFunctionL2Logs(finalUnencLogs)]); - return { encryptedLogs, unencryptedLogs }; - } - public async isGlobalStateSynchronized() { return await this.synchronizer.isGlobalStateSynchronized(); } diff --git a/yarn-project/simulator/src/acvm/oracle/oracle.ts b/yarn-project/simulator/src/acvm/oracle/oracle.ts index f46990d5ca9..b599e32aead 100644 --- a/yarn-project/simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/oracle.ts @@ -317,19 +317,51 @@ export class Oracle { [publicKeyX]: ACVMField[], [publicKeyY]: ACVMField[], log: ACVMField[], - ): ACVMField { + [counter]: ACVMField[], + ): ACVMField[] { const publicKey = new Point(fromACVMField(publicKeyX), fromACVMField(publicKeyY)); - const logHash = this.typedOracle.emitEncryptedLog( + const encLog = this.typedOracle.emitEncryptedLog( AztecAddress.fromString(contractAddress), Fr.fromString(storageSlot), Fr.fromString(noteTypeId), publicKey, log.map(fromACVMField), + +counter, ); - return toACVMField(logHash); + // TODO(1139): We should encrypt in the circuit, but instead we inject here + // encryption output is 112 + 32 * (N + 3) bytes, for log len N + // so split into N + 7 fields (gross but avoids 300+ ACVMFields) + const encLogFields = []; + for (let i = 0; i < Math.ceil(encLog.length / 31); i++) { + encLogFields.push(toACVMField(encLog.subarray(31 * i, Math.min(31 * (i + 1), encLog.length)))); + } + + return encLogFields; + } + + emitUnencryptedLog( + [contractAddress]: ACVMField[], + [eventSelector]: ACVMField[], + message: ACVMField[], + [counter]: ACVMField[], + ): ACVMField { + const logPayload = Buffer.concat(message.map(fromACVMField).map(f => f.toBuffer())); + const log = new UnencryptedL2Log( + AztecAddress.fromString(contractAddress), + EventSelector.fromField(fromACVMField(eventSelector)), + logPayload, + ); + + this.typedOracle.emitUnencryptedLog(log, +counter); + return toACVMField(0); } - emitUnencryptedLog([contractAddress]: ACVMField[], [eventSelector]: ACVMField[], message: ACVMField[]): ACVMField { + emitContractClassUnencryptedLog( + [contractAddress]: ACVMField[], + [eventSelector]: ACVMField[], + message: ACVMField[], + [counter]: ACVMField[], + ): ACVMField { const logPayload = Buffer.concat(message.map(fromACVMField).map(f => f.toBuffer())); const log = new UnencryptedL2Log( AztecAddress.fromString(contractAddress), @@ -337,7 +369,7 @@ export class Oracle { logPayload, ); - const logHash = this.typedOracle.emitUnencryptedLog(log); + const logHash = this.typedOracle.emitContractClassUnencryptedLog(log, +counter); return toACVMField(logHash); } diff --git a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts index af2be1de22c..a4ce826b13a 100644 --- a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts @@ -197,14 +197,19 @@ export abstract class TypedOracle { _noteTypeId: Fr, _publicKey: PublicKey, _log: Fr[], - ): Fr { + _counter: number, + ): Buffer { throw new OracleMethodNotAvailableError('emitEncryptedLog'); } - emitUnencryptedLog(_log: UnencryptedL2Log): Fr { + emitUnencryptedLog(_log: UnencryptedL2Log, _counter: number): void { throw new OracleMethodNotAvailableError('emitUnencryptedLog'); } + emitContractClassUnencryptedLog(_log: UnencryptedL2Log, _counter: number): Fr { + throw new OracleMethodNotAvailableError('emitContractClassUnencryptedLog'); + } + callPrivateFunction( _targetContractAddress: AztecAddress, _functionSelector: FunctionSelector, diff --git a/yarn-project/simulator/src/client/client_execution_context.ts b/yarn-project/simulator/src/client/client_execution_context.ts index 22beba44aa1..e6ed679910c 100644 --- a/yarn-project/simulator/src/client/client_execution_context.ts +++ b/yarn-project/simulator/src/client/client_execution_context.ts @@ -32,7 +32,13 @@ import { type NoteData, toACVMWitness } from '../acvm/index.js'; import { type PackedValuesCache } from '../common/packed_values_cache.js'; import { type DBOracle } from './db_oracle.js'; import { type ExecutionNoteCache } from './execution_note_cache.js'; -import { type ExecutionResult, type NoteAndSlot, type NullifiedNoteHashCounter } from './execution_result.js'; +import { + CountedLog, + type ExecutionResult, + type NoteAndSlot, + type NullifiedNoteHashCounter, +} from './execution_result.js'; +import { type LogsCache } from './logs_cache.js'; import { pickNotes } from './pick_notes.js'; import { executePrivateFunction } from './private_execution.js'; import { ViewDataOracle } from './view_data_oracle.js'; @@ -60,8 +66,8 @@ export class ClientExecutionContext extends ViewDataOracle { */ private gotNotes: Map = new Map(); private nullifiedNoteHashCounters: NullifiedNoteHashCounter[] = []; - private encryptedLogs: EncryptedL2Log[] = []; - private unencryptedLogs: UnencryptedL2Log[] = []; + private encryptedLogs: CountedLog[] = []; + private unencryptedLogs: CountedLog[] = []; private nestedExecutions: ExecutionResult[] = []; private enqueuedPublicFunctionCalls: PublicCallRequest[] = []; @@ -76,6 +82,7 @@ export class ClientExecutionContext extends ViewDataOracle { authWitnesses: AuthWitness[], private readonly packedValuesCache: PackedValuesCache, private readonly noteCache: ExecutionNoteCache, + private readonly logsCache: LogsCache, db: DBOracle, private node: AztecNode, protected sideEffectCounter: number = 0, @@ -147,14 +154,28 @@ export class ClientExecutionContext extends ViewDataOracle { * Return the encrypted logs emitted during this execution. */ public getEncryptedLogs() { - return new EncryptedFunctionL2Logs(this.encryptedLogs); + return this.encryptedLogs; + } + + /** + * Return the encrypted logs emitted during this execution and nested executions. + */ + public getAllEncryptedLogs() { + return new EncryptedFunctionL2Logs(this.logsCache.getEncryptedLogs()); } /** * Return the encrypted logs emitted during this execution. */ public getUnencryptedLogs() { - return new UnencryptedFunctionL2Logs(this.unencryptedLogs); + return this.unencryptedLogs; + } + + /** + * Return the unencrypted logs emitted during this execution and nested executions. + */ + public getAllUnencryptedLogs() { + return new UnencryptedFunctionL2Logs(this.logsCache.getUnencryptedLogs()); } /** @@ -340,24 +361,45 @@ export class ClientExecutionContext extends ViewDataOracle { noteTypeId: Fr, publicKey: Point, log: Fr[], + counter: number, ) { const note = new Note(log); const l1NotePayload = new L1NotePayload(note, contractAddress, storageSlot, noteTypeId); const taggedNote = new TaggedNote(l1NotePayload); const encryptedNote = taggedNote.toEncryptedBuffer(publicKey); const encryptedLog = new EncryptedL2Log(encryptedNote); - this.encryptedLogs.push(encryptedLog); - return Fr.fromBuffer(encryptedLog.hash()); + this.encryptedLogs.push(new CountedLog(encryptedLog, counter)); + this.logsCache.addEncryptedLog(encryptedLog); + return encryptedNote; } /** * Emit an unencrypted log. * @param log - The unencrypted log to be emitted. */ - public override emitUnencryptedLog(log: UnencryptedL2Log) { - this.unencryptedLogs.push(log); + public override emitUnencryptedLog(log: UnencryptedL2Log, counter: number) { + this.unencryptedLogs.push(new CountedLog(log, counter)); + this.logsCache.addUnencryptedLog(log); const text = log.toHumanReadable(); this.log.verbose(`Emitted unencrypted log: "${text.length > 100 ? text.slice(0, 100) + '...' : text}"`); + } + + /** + * Emit a contract class unencrypted log. + * This fn exists separately from emitUnencryptedLog because sha hashing the preimage + * is too large to compile (16,200 fields, 518,400 bytes) => the oracle hashes it. + * See private_context.nr + * @param log - The unencrypted log to be emitted. + */ + public override emitContractClassUnencryptedLog(log: UnencryptedL2Log, counter: number) { + this.unencryptedLogs.push(new CountedLog(log, counter)); + this.logsCache.addUnencryptedLog(log); + const text = log.toHumanReadable(); + this.log.verbose( + `Emitted unencrypted log from ContractClassRegisterer: "${ + text.length > 100 ? text.slice(0, 100) + '...' : text + }"`, + ); return Fr.fromBuffer(log.hash()); } @@ -419,6 +461,7 @@ export class ClientExecutionContext extends ViewDataOracle { this.authWitnesses, this.packedValuesCache, this.noteCache, + this.logsCache, this.db, this.node, sideEffectCounter, diff --git a/yarn-project/simulator/src/client/execution_result.test.ts b/yarn-project/simulator/src/client/execution_result.test.ts deleted file mode 100644 index a3cc96e6cc9..00000000000 --- a/yarn-project/simulator/src/client/execution_result.test.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { - EncryptedFunctionL2Logs, - EncryptedL2Log, - UnencryptedFunctionL2Logs, - UnencryptedL2Log, -} from '@aztec/circuit-types'; -import { AztecAddress, PrivateCallStackItem } from '@aztec/circuits.js'; -import { EventSelector } from '@aztec/foundation/abi'; - -import { type ExecutionResult, collectEncryptedLogs, collectUnencryptedLogs } from './execution_result.js'; - -function emptyExecutionResult(): ExecutionResult { - return { - acir: Buffer.from(''), - vk: Buffer.from(''), - partialWitness: new Map(), - callStackItem: PrivateCallStackItem.empty(), - noteHashReadRequestPartialWitnesses: [], - newNotes: [], - nullifiedNoteHashCounters: [], - returnValues: [], - nestedExecutions: [], - enqueuedPublicFunctionCalls: [], - encryptedLogs: EncryptedFunctionL2Logs.empty(), - unencryptedLogs: UnencryptedFunctionL2Logs.empty(), - }; -} - -describe('Execution Result test suite - collect encrypted logs', () => { - function emptyExecutionResultWithEncryptedLogs(encryptedLogs = EncryptedFunctionL2Logs.empty()): ExecutionResult { - const executionResult = emptyExecutionResult(); - executionResult.encryptedLogs = encryptedLogs; - return executionResult; - } - - function makeEncryptedFunctionLogs(contents: string[]) { - return new EncryptedFunctionL2Logs(contents.map(s => new EncryptedL2Log(Buffer.from(s)))); - } - - it('collect encrypted logs with nested fn calls', () => { - /* - Create the following executionResult object: - fnA (log1) - |---------->fnB (log2) - |---------->fnC (log3) -> fnD (log4) - |---------->fnE (log5) - |-------->fnF (log6) - |-------->fnG (log7) - Circuits and ACVM process in a DFS + stack like format: [fnA, fnE, fnG, fnF, fnC, fnD, fnB] - */ - const executionResult: ExecutionResult = emptyExecutionResultWithEncryptedLogs( - makeEncryptedFunctionLogs(['Log 1']), - ); - const fnB = emptyExecutionResultWithEncryptedLogs(makeEncryptedFunctionLogs(['Log 2'])); - const fnC = emptyExecutionResultWithEncryptedLogs(makeEncryptedFunctionLogs(['Log 3'])); - const fnD = emptyExecutionResultWithEncryptedLogs(makeEncryptedFunctionLogs(['Log 4'])); - const fnE = emptyExecutionResultWithEncryptedLogs(makeEncryptedFunctionLogs(['Log 5'])); - const fnF = emptyExecutionResultWithEncryptedLogs(makeEncryptedFunctionLogs(['Log 6'])); - const fnG = emptyExecutionResultWithEncryptedLogs(makeEncryptedFunctionLogs(['Log 7'])); - - fnE.nestedExecutions.push(fnF, fnG); - - fnC.nestedExecutions.push(fnD); - - executionResult.nestedExecutions.push(fnB, fnC, fnE); - - const encryptedLogs = collectEncryptedLogs(executionResult); - expect(encryptedLogs).toEqual([ - makeEncryptedFunctionLogs(['Log 1']), - makeEncryptedFunctionLogs(['Log 5']), - makeEncryptedFunctionLogs(['Log 7']), - makeEncryptedFunctionLogs(['Log 6']), - makeEncryptedFunctionLogs(['Log 3']), - makeEncryptedFunctionLogs(['Log 4']), - makeEncryptedFunctionLogs(['Log 2']), - ]); - }); - - it('collect encrypted logs with multiple logs each function call', () => { - /* - Create the following executionResult object: - fnA (log1, log2) - |---------->fnB (log3, log4) - |---------->fnC (log5) -> fnD (log6) - Circuits and ACVM process in a DFS + stack like format: [fnA, fnC, fnD, fnB] - */ - const executionResult: ExecutionResult = emptyExecutionResultWithEncryptedLogs( - makeEncryptedFunctionLogs(['Log 1', 'Log 2']), - ); - const fnB = emptyExecutionResultWithEncryptedLogs(makeEncryptedFunctionLogs(['Log 3', 'Log 4'])); - const fnC = emptyExecutionResultWithEncryptedLogs(makeEncryptedFunctionLogs(['Log 5'])); - const fnD = emptyExecutionResultWithEncryptedLogs(makeEncryptedFunctionLogs(['Log 6'])); - fnC.nestedExecutions.push(fnD); - executionResult.nestedExecutions.push(fnB, fnC); - const encryptedLogs = collectEncryptedLogs(executionResult); - expect(encryptedLogs).toEqual([ - makeEncryptedFunctionLogs(['Log 1', 'Log 2']), - makeEncryptedFunctionLogs(['Log 5']), - makeEncryptedFunctionLogs(['Log 6']), - makeEncryptedFunctionLogs(['Log 3', 'Log 4']), - ]); - }); - - it('collect encrypted logs with nested functions where some have no logs', () => { - /* - Create the following executionResult object: - fnA () - |----------> fnB (log1) -> fnC () - Circuits and ACVM process in a DFS + stack like format: [fnA, fnB, fnC] - */ - const executionResult: ExecutionResult = emptyExecutionResult(); - const fnB = emptyExecutionResultWithEncryptedLogs(makeEncryptedFunctionLogs(['Log 1'])); - const fnC = emptyExecutionResult(); - fnB.nestedExecutions.push(fnC); - executionResult.nestedExecutions.push(fnB); - const encryptedLogs = collectEncryptedLogs(executionResult); - expect(encryptedLogs).toEqual([ - EncryptedFunctionL2Logs.empty(), - makeEncryptedFunctionLogs(['Log 1']), - EncryptedFunctionL2Logs.empty(), - ]); - }); - - it('collect encrypted logs with no logs in any nested calls', () => { - /* - Create the following executionResult object: - fnA () - |----------> fnB () -> fnC () - |----------> fnD () -> fnE () - Circuits and ACVM process in a DFS + stack like format: [fnA, fnD, fnE, fnB, fnC] - */ - const executionResult: ExecutionResult = emptyExecutionResult(); - const fnB = emptyExecutionResult(); - const fnC = emptyExecutionResult(); - const fnD = emptyExecutionResult(); - const fnE = emptyExecutionResult(); - - fnB.nestedExecutions.push(fnC); - fnD.nestedExecutions.push(fnE); - - executionResult.nestedExecutions.push(fnB, fnD); - - const encryptedLogs = collectEncryptedLogs(executionResult); - expect(encryptedLogs).toEqual([ - EncryptedFunctionL2Logs.empty(), - EncryptedFunctionL2Logs.empty(), - EncryptedFunctionL2Logs.empty(), - EncryptedFunctionL2Logs.empty(), - EncryptedFunctionL2Logs.empty(), - ]); - }); -}); - -describe('collect unencrypted logs', () => { - // collection of unencrypted logs work similar to encrypted logs, so lets write other kinds of test cases: - - function emptyExecutionResultWithUnencryptedLogs( - unencryptedLogs = UnencryptedFunctionL2Logs.empty(), - ): ExecutionResult { - const executionResult = emptyExecutionResult(); - executionResult.unencryptedLogs = unencryptedLogs; - return executionResult; - } - - function makeUnencryptedFunctionLogs(contents: string[]) { - return new UnencryptedFunctionL2Logs( - contents.map(s => new UnencryptedL2Log(AztecAddress.ZERO, EventSelector.empty(), Buffer.from(s))), - ); - } - - it('collect unencrypted logs even when no logs and no recursion', () => { - // fnA() - const executionResult: ExecutionResult = emptyExecutionResult(); - const unencryptedLogs = collectUnencryptedLogs(executionResult); - expect(unencryptedLogs).toEqual([UnencryptedFunctionL2Logs.empty()]); - }); - - it('collect unencrypted logs with no logs in some nested calls', () => { - /* - Create the following executionResult object: - fnA () - |----------> fnB () -> fnC (log1, log2, log3) - Circuits and ACVM process in a DFS + stack like format: [fnA, fnC, fnB] - */ - const executionResult: ExecutionResult = emptyExecutionResult(); - const fnB = emptyExecutionResult(); - const fnC = emptyExecutionResultWithUnencryptedLogs(makeUnencryptedFunctionLogs(['Log 1', 'Log 2', 'Log 3'])); - - executionResult.nestedExecutions.push(fnB, fnC); - - const unencryptedLogs = collectUnencryptedLogs(executionResult); - expect(unencryptedLogs).toEqual([ - UnencryptedFunctionL2Logs.empty(), - makeUnencryptedFunctionLogs(['Log 1', 'Log 2', 'Log 3']), - UnencryptedFunctionL2Logs.empty(), - ]); - }); - - it('collect unencrypted logs with multiple logs in each function call leaves', () => { - /* - Create the following executionResult object: - fnA() - |->fnB - |->fnC(log1, log2, log3) - |->fnD(log4, log5, log6) - Circuits and ACVM process in a DFS + stack like format: [fnA, fnB, fnD, fnC] - */ - const executionResult: ExecutionResult = emptyExecutionResult(); - const fnB = emptyExecutionResult(); - const fnC = emptyExecutionResultWithUnencryptedLogs(makeUnencryptedFunctionLogs(['Log 1', 'Log 2', 'Log 3'])); - const fnD = emptyExecutionResultWithUnencryptedLogs(makeUnencryptedFunctionLogs(['Log 4', 'Log 5', 'Log 6'])); - fnB.nestedExecutions.push(fnC, fnD); - executionResult.nestedExecutions.push(fnB); - const unencryptedLogs = collectUnencryptedLogs(executionResult); - expect(unencryptedLogs).toEqual([ - UnencryptedFunctionL2Logs.empty(), - UnencryptedFunctionL2Logs.empty(), - makeUnencryptedFunctionLogs(['Log 4', 'Log 5', 'Log 6']), - makeUnencryptedFunctionLogs(['Log 1', 'Log 2', 'Log 3']), - ]); - }); -}); diff --git a/yarn-project/simulator/src/client/execution_result.ts b/yarn-project/simulator/src/client/execution_result.ts index b2dc1eb3fe4..f2572878e0f 100644 --- a/yarn-project/simulator/src/client/execution_result.ts +++ b/yarn-project/simulator/src/client/execution_result.ts @@ -1,8 +1,16 @@ -import { type EncryptedFunctionL2Logs, type Note, type UnencryptedFunctionL2Logs } from '@aztec/circuit-types'; import { + EncryptedFunctionL2Logs, + type EncryptedL2Log, + type Note, + UnencryptedFunctionL2Logs, + type UnencryptedL2Log, +} from '@aztec/circuit-types'; +import { + type IsEmpty, type NoteHashReadRequestMembershipWitness, type PrivateCallStackItem, type PublicCallRequest, + sortByCounter, } from '@aztec/circuits.js'; import { type Fr } from '@aztec/foundation/fields'; @@ -20,6 +28,14 @@ export interface NoteAndSlot { noteTypeId: Fr; } +export class CountedLog implements IsEmpty { + constructor(public log: TLog, public counter: number) {} + + isEmpty(): boolean { + return !this.log.data.length && !this.counter; + } +} + export interface NullifiedNoteHashCounter { noteHashCounter: number; nullifierCounter: number; @@ -54,12 +70,12 @@ export interface ExecutionResult { * Encrypted logs emitted during execution of this function call. * Note: These are preimages to `encryptedLogsHashes`. */ - encryptedLogs: EncryptedFunctionL2Logs; + encryptedLogs: CountedLog[]; /** * Unencrypted logs emitted during execution of this function call. * Note: These are preimages to `unencryptedLogsHashes`. */ - unencryptedLogs: UnencryptedFunctionL2Logs; + unencryptedLogs: CountedLog[]; } export function collectNullifiedNoteHashCounters(execResult: ExecutionResult): NullifiedNoteHashCounter[] { @@ -74,9 +90,19 @@ export function collectNullifiedNoteHashCounters(execResult: ExecutionResult): N * @param execResult - The topmost execution result. * @returns All encrypted logs. */ -export function collectEncryptedLogs(execResult: ExecutionResult): EncryptedFunctionL2Logs[] { - // without the .reverse(), the logs will be in a queue like fashion which is wrong as the kernel processes it like a stack. - return [execResult.encryptedLogs, ...[...execResult.nestedExecutions].reverse().flatMap(collectEncryptedLogs)]; +function collectEncryptedLogs(execResult: ExecutionResult): CountedLog[] { + return [execResult.encryptedLogs, ...[...execResult.nestedExecutions].flatMap(collectEncryptedLogs)].flat(); +} + +/** + * Collect all encrypted logs across all nested executions and sorts by counter. + * @param execResult - The topmost execution result. + * @returns All encrypted logs. + */ +export function collectSortedEncryptedLogs(execResult: ExecutionResult): EncryptedFunctionL2Logs { + const allLogs = collectEncryptedLogs(execResult); + const sortedLogs = sortByCounter(allLogs); + return new EncryptedFunctionL2Logs(sortedLogs.map(l => l.log)); } /** @@ -84,9 +110,19 @@ export function collectEncryptedLogs(execResult: ExecutionResult): EncryptedFunc * @param execResult - The topmost execution result. * @returns All unencrypted logs. */ -export function collectUnencryptedLogs(execResult: ExecutionResult): UnencryptedFunctionL2Logs[] { - // without the .reverse(), the logs will be in a queue like fashion which is wrong as the kernel processes it like a stack. - return [execResult.unencryptedLogs, ...[...execResult.nestedExecutions].reverse().flatMap(collectUnencryptedLogs)]; +function collectUnencryptedLogs(execResult: ExecutionResult): CountedLog[] { + return [execResult.unencryptedLogs, ...[...execResult.nestedExecutions].flatMap(collectUnencryptedLogs)].flat(); +} + +/** + * Collect all unencrypted logs across all nested executions and sorts by counter. + * @param execResult - The topmost execution result. + * @returns All unencrypted logs. + */ +export function collectSortedUnencryptedLogs(execResult: ExecutionResult): UnencryptedFunctionL2Logs { + const allLogs = collectUnencryptedLogs(execResult); + const sortedLogs = sortByCounter(allLogs); + return new UnencryptedFunctionL2Logs(sortedLogs.map(l => l.log)); } /** diff --git a/yarn-project/simulator/src/client/logs_cache.ts b/yarn-project/simulator/src/client/logs_cache.ts new file mode 100644 index 00000000000..c1e52ae7d8e --- /dev/null +++ b/yarn-project/simulator/src/client/logs_cache.ts @@ -0,0 +1,65 @@ +import { type EncryptedL2Log, type UnencryptedL2Log } from '@aztec/circuit-types'; + +/** + * Log data that's accessible by all the function calls in an execution. + * This class exists to: + * 1. Keep track of logs emitted through nested calls in the correct order. + * 2. TODO(1641): Remove encrypted logs based on notes nullified in the same scope. + */ +export class LogsCache { + /** + * Logs notes created in this transaction. + */ + private encryptedLogs: EncryptedL2Log[] = []; + private unencryptedLogs: UnencryptedL2Log[] = []; + + // TODO Separate encrypted logs linked to note hashes and arbitrary logs: + + // Maps from note hash to encrypted log - useful for removing transient logs + // private encryptedLogsLinkedToNotes: Map = new Map(); + + // /** + // * Remove the encrypted log for a nullified note. + // * This fn should only be called if the note's innerNoteHash != 0. + // * @param noteHashCounter - Side effect counter of the note. + // */ + // public nullifyNote(noteHashCounter: Fr) { + // // Find and remove the matching new note if the emitted innerNoteHash is not empty. + // const log = this.encryptedLogsLinkedToNotes.get(noteHashCounter.toBigInt()) ?? false; + // // TODO: throw here? Will the log always be here? + // if (!log) { + // throw new Error('Attempt to remove a pending note log that does not exist.'); + // } + // this.encryptedLogsLinkedToNotes.delete(noteHashCounter.toBigInt()); + // } + + /** + * Add a new encrypted log to cache. + * @param log - New log created during execution. + */ + public addEncryptedLog(log: EncryptedL2Log) { + this.encryptedLogs.push(log); + } + + /** + * Add a new unencrypted log to cache. + * @param log - New log created during execution. + */ + public addUnencryptedLog(log: UnencryptedL2Log) { + this.unencryptedLogs.push(log); + } + + /** + * Return the encrypted logs. + */ + public getEncryptedLogs() { + return this.encryptedLogs; + } + + /** + * Return the encrypted logs. + */ + public getUnencryptedLogs() { + return this.unencryptedLogs; + } +} diff --git a/yarn-project/simulator/src/client/private_execution.test.ts b/yarn-project/simulator/src/client/private_execution.test.ts index 5dbd922a9c6..f82a4c265c8 100644 --- a/yarn-project/simulator/src/client/private_execution.test.ts +++ b/yarn-project/simulator/src/client/private_execution.test.ts @@ -1,4 +1,11 @@ -import { type AztecNode, type L1ToL2Message, Note, PackedValues, TxExecutionRequest } from '@aztec/circuit-types'; +import { + type AztecNode, + EncryptedFunctionL2Logs, + type L1ToL2Message, + Note, + PackedValues, + TxExecutionRequest, +} from '@aztec/circuit-types'; import { AppendOnlyTreeSnapshot, CallContext, @@ -56,7 +63,7 @@ import { MessageLoadOracleInputs } from '../acvm/index.js'; import { buildL1ToL2Message } from '../test/utils.js'; import { computeSlotForMapping } from '../utils.js'; import { type DBOracle } from './db_oracle.js'; -import { collectUnencryptedLogs } from './execution_result.js'; +import { type ExecutionResult, collectSortedUnencryptedLogs } from './execution_result.js'; import { AcirSimulator } from './simulator.js'; jest.setTimeout(60_000); @@ -164,6 +171,11 @@ describe('Private Execution test suite', () => { return trees[name]; }; + const getEncryptedSerializedLength = (result: ExecutionResult) => { + const fnLogs = new EncryptedFunctionL2Logs(result.encryptedLogs.map(l => l.log)); + return fnLogs.getSerializedLength(); + }; + beforeAll(() => { logger = createDebugLogger('aztec:test:private_execution'); @@ -212,8 +224,18 @@ describe('Private Execution test suite', () => { it('emits a field as an unencrypted log', async () => { const artifact = getFunctionArtifact(TestContractArtifact, 'emit_msg_sender'); const result = await runSimulator({ artifact, msgSender: owner }); - const [functionLogs] = collectUnencryptedLogs(result); + + const newUnencryptedLogs = getNonEmptyItems(result.callStackItem.publicInputs.unencryptedLogsHashes); + expect(newUnencryptedLogs).toHaveLength(1); + + const functionLogs = collectSortedUnencryptedLogs(result); expect(functionLogs.logs).toHaveLength(1); + + const [unencryptedLog] = newUnencryptedLogs; + expect(unencryptedLog.value).toEqual(Fr.fromBuffer(functionLogs.logs[0].hash())); + expect(result.callStackItem.publicInputs.unencryptedLogPreimagesLength).toEqual( + new Fr(functionLogs.getSerializedLength()), + ); // Test that the log payload (ie ignoring address, selector, and header) matches what we emitted expect(functionLogs.logs[0].data.subarray(-32).toString('hex')).toEqual(owner.toBuffer().toString('hex')); }); @@ -222,8 +244,17 @@ describe('Private Execution test suite', () => { const artifact = getFunctionArtifact(TestContractArtifact, 'emit_array_as_unencrypted_log'); const args = [times(5, () => Fr.random())]; const result = await runSimulator({ artifact, msgSender: owner, args }); - const [functionLogs] = collectUnencryptedLogs(result); + + const newUnencryptedLogs = getNonEmptyItems(result.callStackItem.publicInputs.unencryptedLogsHashes); + expect(newUnencryptedLogs).toHaveLength(1); + const functionLogs = collectSortedUnencryptedLogs(result); expect(functionLogs.logs).toHaveLength(1); + + const [unencryptedLog] = newUnencryptedLogs; + expect(unencryptedLog.value).toEqual(Fr.fromBuffer(functionLogs.logs[0].hash())); + expect(result.callStackItem.publicInputs.unencryptedLogPreimagesLength).toEqual( + new Fr(functionLogs.getSerializedLength()), + ); // Test that the log payload (ie ignoring address, selector, and header) matches what we emitted const expected = Buffer.concat(args[0].map(arg => arg.toBuffer())).toString('hex'); expect(functionLogs.logs[0].data.subarray(-32 * 5).toString('hex')).toEqual(expected); @@ -303,6 +334,15 @@ describe('Private Execution test suite', () => { newNote.note, ), ); + + const newEncryptedLogs = getNonEmptyItems(result.callStackItem.publicInputs.encryptedLogsHashes); + expect(newEncryptedLogs).toHaveLength(1); + + const [encryptedLog] = newEncryptedLogs; + expect(encryptedLog.value).toEqual(Fr.fromBuffer(result.encryptedLogs[0].log.hash())); + expect(result.callStackItem.publicInputs.encryptedLogPreimagesLength).toEqual( + new Fr(getEncryptedSerializedLength(result)), + ); }); it('should run the create_note function', async () => { @@ -325,6 +365,15 @@ describe('Private Execution test suite', () => { newNote.note, ), ); + + const newEncryptedLogs = getNonEmptyItems(result.callStackItem.publicInputs.encryptedLogsHashes); + expect(newEncryptedLogs).toHaveLength(1); + + const [encryptedLog] = newEncryptedLogs; + expect(encryptedLog.value).toEqual(Fr.fromBuffer(result.encryptedLogs[0].log.hash())); + expect(result.callStackItem.publicInputs.encryptedLogPreimagesLength).toEqual( + new Fr(getEncryptedSerializedLength(result)), + ); }); it('should run the destroy_and_create function', async () => { @@ -373,6 +422,16 @@ describe('Private Execution test suite', () => { expect(recipientNote.note.items[0]).toEqual(new Fr(amountToTransfer)); expect(changeNote.note.items[0]).toEqual(new Fr(40n)); + const newEncryptedLogs = getNonEmptyItems(result.callStackItem.publicInputs.encryptedLogsHashes); + expect(newEncryptedLogs).toHaveLength(2); + + const [encryptedChangeLog, encryptedRecipientLog] = newEncryptedLogs; + expect(encryptedChangeLog.value).toEqual(Fr.fromBuffer(result.encryptedLogs[0].log.hash())); + expect(encryptedRecipientLog.value).toEqual(Fr.fromBuffer(result.encryptedLogs[1].log.hash())); + expect(result.callStackItem.publicInputs.encryptedLogPreimagesLength).toEqual( + new Fr(getEncryptedSerializedLength(result)), + ); + const readRequests = getNonEmptyItems(result.callStackItem.publicInputs.noteHashReadRequests).map(r => r.value); expect(readRequests).toHaveLength(consumedNotes.length); expect(readRequests).toEqual(expect.arrayContaining(consumedNotes.map(n => n.uniqueSiloedNoteHash))); @@ -404,6 +463,15 @@ describe('Private Execution test suite', () => { const [changeNote, recipientNote] = result.newNotes; expect(recipientNote.note.items[0]).toEqual(new Fr(amountToTransfer)); expect(changeNote.note.items[0]).toEqual(new Fr(balance - amountToTransfer)); + + const newEncryptedLogs = getNonEmptyItems(result.callStackItem.publicInputs.encryptedLogsHashes); + expect(newEncryptedLogs).toHaveLength(2); + const [encryptedChangeLog, encryptedRecipientLog] = newEncryptedLogs; + expect(encryptedChangeLog.value).toEqual(Fr.fromBuffer(result.encryptedLogs[0].log.hash())); + expect(encryptedRecipientLog.value).toEqual(Fr.fromBuffer(result.encryptedLogs[1].log.hash())); + expect(result.callStackItem.publicInputs.encryptedLogPreimagesLength).toEqual( + new Fr(getEncryptedSerializedLength(result)), + ); }); }); @@ -849,6 +917,15 @@ describe('Private Execution test suite', () => { ); expect(noteHash).toEqual(innerNoteHash); + const newEncryptedLogs = getNonEmptyItems(result.callStackItem.publicInputs.encryptedLogsHashes); + expect(newEncryptedLogs).toHaveLength(1); + + const [encryptedLog] = newEncryptedLogs; + expect(encryptedLog.value).toEqual(Fr.fromBuffer(result.encryptedLogs[0].log.hash())); + expect(result.callStackItem.publicInputs.encryptedLogPreimagesLength).toEqual( + new Fr(getEncryptedSerializedLength(result)), + ); + // read request should match innerNoteHash for pending notes (there is no nonce, so can't compute "unique" hash) const readRequest = getNonEmptyItems(result.callStackItem.publicInputs.noteHashReadRequests)[0]; expect(readRequest.value).toEqual(innerNoteHash); @@ -919,6 +996,15 @@ describe('Private Execution test suite', () => { ); expect(noteHash).toEqual(innerNoteHash); + const newEncryptedLogs = getNonEmptyItems(execInsert.callStackItem.publicInputs.encryptedLogsHashes); + expect(newEncryptedLogs).toHaveLength(1); + + const [encryptedLog] = newEncryptedLogs; + expect(encryptedLog.value).toEqual(Fr.fromBuffer(execInsert.encryptedLogs[0].log.hash())); + expect(result.callStackItem.publicInputs.encryptedLogPreimagesLength).toEqual( + new Fr(getEncryptedSerializedLength(result)), + ); + // read request should match innerNoteHash for pending notes (there is no nonce, so can't compute "unique" hash) const readRequest = execGetThenNullify.callStackItem.publicInputs.noteHashReadRequests[0]; expect(readRequest.value).toEqual(innerNoteHash); diff --git a/yarn-project/simulator/src/client/private_execution.ts b/yarn-project/simulator/src/client/private_execution.ts index 8c3174f0add..75509e6578c 100644 --- a/yarn-project/simulator/src/client/private_execution.ts +++ b/yarn-project/simulator/src/client/private_execution.ts @@ -1,7 +1,6 @@ import { type FunctionData, PrivateCallStackItem, PrivateCircuitPublicInputs } from '@aztec/circuits.js'; import { type FunctionArtifactWithDebugMetadata } from '@aztec/foundation/abi'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; -import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; import { witnessMapToFields } from '../acvm/deserialize.js'; @@ -45,9 +44,6 @@ export async function executePrivateFunction( const encryptedLogs = context.getEncryptedLogs(); const unencryptedLogs = context.getUnencryptedLogs(); - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1165) --> set this in Noir - publicInputs.encryptedLogPreimagesLength = new Fr(encryptedLogs.getSerializedLength()); - publicInputs.unencryptedLogPreimagesLength = new Fr(unencryptedLogs.getSerializedLength()); const callStackItem = new PrivateCallStackItem(contractAddress, functionData, publicInputs); diff --git a/yarn-project/simulator/src/client/simulator.ts b/yarn-project/simulator/src/client/simulator.ts index cb767ea4d24..3afb657a725 100644 --- a/yarn-project/simulator/src/client/simulator.ts +++ b/yarn-project/simulator/src/client/simulator.ts @@ -19,6 +19,7 @@ import { ClientExecutionContext } from './client_execution_context.js'; import { type DBOracle } from './db_oracle.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; import { type ExecutionResult } from './execution_result.js'; +import { LogsCache } from './logs_cache.js'; import { executePrivateFunction } from './private_execution.js'; import { executeUnconstrainedFunction } from './unconstrained_execution.js'; import { ViewDataOracle } from './view_data_oracle.js'; @@ -100,6 +101,7 @@ export class AcirSimulator { request.authWitnesses, PackedValuesCache.create(request.argsOfCalls), new ExecutionNoteCache(), + new LogsCache(), this.db, this.node, startSideEffectCounter, diff --git a/yarn-project/simulator/src/mocks/fixtures.ts b/yarn-project/simulator/src/mocks/fixtures.ts index f7fd2d72562..1ff924ed85c 100644 --- a/yarn-project/simulator/src/mocks/fixtures.ts +++ b/yarn-project/simulator/src/mocks/fixtures.ts @@ -116,6 +116,8 @@ export class PublicExecutionResultBuilder { contractStorageReads: [], unencryptedLogsHashes: [], unencryptedLogs: UnencryptedFunctionL2Logs.empty(), + unencryptedLogPreimagesLength: new Fr(4n), // empty logs have len 4 + allUnencryptedLogs: UnencryptedFunctionL2Logs.empty(), startSideEffectCounter: Fr.ZERO, endSideEffectCounter: Fr.ZERO, reverted: this._reverted, diff --git a/yarn-project/simulator/src/public/abstract_phase_manager.ts b/yarn-project/simulator/src/public/abstract_phase_manager.ts index 00997d4acd2..b499163bc25 100644 --- a/yarn-project/simulator/src/public/abstract_phase_manager.ts +++ b/yarn-project/simulator/src/public/abstract_phase_manager.ts @@ -246,6 +246,7 @@ export abstract class AbstractPhaseManager { while (executionStack.length) { const current = executionStack.pop()!; const isExecutionRequest = !isPublicExecutionResult(current); + // TODO(6052): Extract correct new counter from nested calls const sideEffectCounter = lastSideEffectCounter(tx) + 1; const availableGas = this.getAvailableGas(tx, previousPublicKernelOutput); @@ -270,7 +271,9 @@ export abstract class AbstractPhaseManager { throw result.revertReason; } - newUnencryptedFunctionLogs.push(result.unencryptedLogs); + if (isExecutionRequest) { + newUnencryptedFunctionLogs.push(result.allUnencryptedLogs); + } this.log.debug( `Running public kernel circuit for ${result.execution.contractAddress.toString()}:${functionSelector}`, @@ -382,8 +385,6 @@ export abstract class AbstractPhaseManager { MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, ); - const unencryptedLogPreimagesLength = new Fr(result.unencryptedLogs.getSerializedLength()); - const publicCircuitPublicInputs = PublicCircuitPublicInputs.from({ callContext: result.execution.callContext, proverAddress: AztecAddress.ZERO, @@ -420,7 +421,7 @@ export abstract class AbstractPhaseManager { SideEffect.empty(), MAX_UNENCRYPTED_LOGS_PER_CALL, ), - unencryptedLogPreimagesLength, + unencryptedLogPreimagesLength: result.unencryptedLogPreimagesLength, historicalHeader: this.historicalHeader, globalVariables: this.globalVariables, startGasLeft: Gas.from(result.startGasLeft), diff --git a/yarn-project/simulator/src/public/execution.ts b/yarn-project/simulator/src/public/execution.ts index 11db9dd0b5e..2aaccc9ffd4 100644 --- a/yarn-project/simulator/src/public/execution.ts +++ b/yarn-project/simulator/src/public/execution.ts @@ -54,6 +54,15 @@ export interface PublicExecutionResult { * Note: These are preimages to `unencryptedLogsHashes`. */ unencryptedLogs: UnencryptedFunctionL2Logs; + /** + * Length of the unencrypted log preimages emitted in this function call. + */ + unencryptedLogPreimagesLength: Fr; + /** + * Unencrypted logs emitted during this call AND any nested calls. + * Useful for maintaining correct ordering in ts. + */ + allUnencryptedLogs: UnencryptedFunctionL2Logs; /** * Whether the execution reverted. */ diff --git a/yarn-project/simulator/src/public/executor.ts b/yarn-project/simulator/src/public/executor.ts index a5fd6a08177..d4ead2ce03a 100644 --- a/yarn-project/simulator/src/public/executor.ts +++ b/yarn-project/simulator/src/public/executor.ts @@ -159,6 +159,8 @@ async function executePublicFunctionAcvm( nestedExecutions: [], unencryptedLogsHashes: [], unencryptedLogs: UnencryptedFunctionL2Logs.empty(), + unencryptedLogPreimagesLength: new Fr(4n), // empty logs have len 4 + allUnencryptedLogs: UnencryptedFunctionL2Logs.empty(), reverted, revertReason, startGasLeft: context.availableGas, @@ -182,6 +184,7 @@ async function executePublicFunctionAcvm( startSideEffectCounter, endSideEffectCounter, unencryptedLogsHashes: unencryptedLogsHashesPadded, + unencryptedLogPreimagesLength, } = PublicCircuitPublicInputs.fromFields(returnWitness); const returnValues = await context.unpackReturns(returnsHash); @@ -207,6 +210,7 @@ async function executePublicFunctionAcvm( const nestedExecutions = context.getNestedExecutions(); const unencryptedLogs = context.getUnencryptedLogs(); + const allUnencryptedLogs = context.getAllUnencryptedLogs(); // TODO(palla/gas): We should be loading these values from the returned PublicCircuitPublicInputs const startGasLeft = context.availableGas; @@ -227,6 +231,8 @@ async function executePublicFunctionAcvm( nestedExecutions, unencryptedLogsHashes, unencryptedLogs, + unencryptedLogPreimagesLength, + allUnencryptedLogs, reverted: false, revertReason: undefined, startGasLeft, diff --git a/yarn-project/simulator/src/public/index.test.ts b/yarn-project/simulator/src/public/index.test.ts index a41b2e24d0b..14995aa727a 100644 --- a/yarn-project/simulator/src/public/index.test.ts +++ b/yarn-project/simulator/src/public/index.test.ts @@ -252,25 +252,32 @@ describe('ACIR public execution simulator', () => { }); describe('Parent/Child contracts', () => { - it('calls the public entry point in the parent', async () => { - const parentContractAddress = AztecAddress.random(); - const parentEntryPointFn = ParentContractArtifact.functions.find(f => f.name === 'pub_entry_point')!; - const parentEntryPointFnSelector = FunctionSelector.fromNameAndParameters( + let parentContractAddress: AztecAddress; + let childContractAddress: AztecAddress; + let parentEntryPointFn: FunctionArtifact; + let parentEntryPointFnSelector: FunctionSelector; + let functionData: FunctionData; + let callContext: CallContext; + + beforeEach(() => { + parentContractAddress = AztecAddress.random(); + parentEntryPointFn = ParentContractArtifact.functions.find(f => f.name === 'pub_entry_point')!; + parentEntryPointFnSelector = FunctionSelector.fromNameAndParameters( parentEntryPointFn.name, parentEntryPointFn.parameters, ); + functionData = new FunctionData(parentEntryPointFnSelector, false); + childContractAddress = AztecAddress.random(); + callContext = makeCallContext(parentContractAddress); + }, 10000); - const childContractAddress = AztecAddress.random(); + it('calls the public entry point in the parent to get value', async () => { const childValueFn = ChildContractArtifact.functions.find(f => f.name === 'pub_get_value')!; const childValueFnSelector = FunctionSelector.fromNameAndParameters(childValueFn.name, childValueFn.parameters); const initialValue = 3n; - - const functionData = new FunctionData(parentEntryPointFnSelector, false); const args = encodeArguments(parentEntryPointFn, [childContractAddress, childValueFnSelector, initialValue]); - const callContext = makeCallContext(parentContractAddress); - // eslint-disable-next-line require-await publicContracts.getBytecode.mockImplementation(async (addr: AztecAddress, selector: FunctionSelector) => { if (addr.equals(parentContractAddress) && selector.equals(parentEntryPointFnSelector)) { @@ -304,6 +311,39 @@ describe('ACIR public execution simulator', () => { ), ); }, 20_000); + + it('calls the public entry point in the parent to set value', async () => { + const childValueFn = ChildContractArtifact.functions.find(f => f.name === 'pub_set_value')!; + const childValueFnSelector = FunctionSelector.fromNameAndParameters(childValueFn.name, childValueFn.parameters); + + const newValue = 5n; + + const args = encodeArguments(parentEntryPointFn, [childContractAddress, childValueFnSelector, newValue]); + + // eslint-disable-next-line require-await + publicContracts.getBytecode.mockImplementation(async (addr: AztecAddress, selector: FunctionSelector) => { + if (addr.equals(parentContractAddress) && selector.equals(parentEntryPointFnSelector)) { + return parentEntryPointFn.bytecode; + } else if (addr.equals(childContractAddress) && selector.equals(childValueFnSelector)) { + return childValueFn.bytecode; + } else { + return undefined; + } + }); + + const execution: PublicExecution = { contractAddress: parentContractAddress, functionData, args, callContext }; + + const result = await simulate(execution, globalVariables); + const childExecutionResult = result.nestedExecutions[0]; + expect(Fr.fromBuffer(childExecutionResult.unencryptedLogs.logs[0].data)).toEqual(new Fr(newValue)); + expect(Fr.fromBuffer(childExecutionResult.unencryptedLogs.logs[0].hash())).toEqual( + childExecutionResult.unencryptedLogsHashes[0].value, + ); + expect(childExecutionResult.unencryptedLogPreimagesLength).toEqual( + new Fr(childExecutionResult.unencryptedLogs.getSerializedLength()), + ); + expect(result.returnValues[0]).toEqual(new Fr(newValue)); + }, 20_000); }); describe('Public -> Private / Cross Chain messaging', () => { diff --git a/yarn-project/simulator/src/public/public_execution_context.ts b/yarn-project/simulator/src/public/public_execution_context.ts index 7f16fd7a19b..5998df35997 100644 --- a/yarn-project/simulator/src/public/public_execution_context.ts +++ b/yarn-project/simulator/src/public/public_execution_context.ts @@ -44,6 +44,9 @@ export class PublicExecutionContext extends TypedOracle { public readonly availableGas: Gas, public readonly transactionFee: Fr, public readonly gasSettings: GasSettings, + // Unencrypted logs emitted during this call AND any nested calls + // Useful for maintaining correct ordering in ts + private allUnencryptedLogs: UnencryptedL2Log[] = [], private log = createDebugLogger('aztec:simulator:public_execution_context'), ) { super(); @@ -87,6 +90,13 @@ export class PublicExecutionContext extends TypedOracle { return new UnencryptedFunctionL2Logs(this.unencryptedLogs); } + /** + * Return the encrypted logs emitted during this execution, including nested calls. + */ + public getAllUnencryptedLogs() { + return new UnencryptedFunctionL2Logs(this.allUnencryptedLogs); + } + /** * Return the data read and updated during this execution. */ @@ -135,11 +145,10 @@ export class PublicExecutionContext extends TypedOracle { * Emit an unencrypted log. * @param log - The unencrypted log to be emitted. */ - public override emitUnencryptedLog(log: UnencryptedL2Log) { - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/885) + public override emitUnencryptedLog(log: UnencryptedL2Log, _counter: number) { this.unencryptedLogs.push(log); + this.allUnencryptedLogs.push(log); this.log.verbose(`Emitted unencrypted log: "${log.toHumanReadable()}"`); - return Fr.fromBuffer(log.hash()); } /** @@ -229,6 +238,7 @@ export class PublicExecutionContext extends TypedOracle { this.availableGas, this.transactionFee, this.gasSettings, + this.allUnencryptedLogs, this.log, ); diff --git a/yarn-project/simulator/src/public/tail_phase_manager.ts b/yarn-project/simulator/src/public/tail_phase_manager.ts index e60b1d42a06..ab1d67421e2 100644 --- a/yarn-project/simulator/src/public/tail_phase_manager.ts +++ b/yarn-project/simulator/src/public/tail_phase_manager.ts @@ -1,10 +1,4 @@ -import { - type PublicKernelRequest, - PublicKernelType, - type Tx, - UnencryptedFunctionL2Logs, - type UnencryptedL2Log, -} from '@aztec/circuit-types'; +import { type PublicKernelRequest, PublicKernelType, type Tx } from '@aztec/circuit-types'; import { Fr, type GlobalVariables, @@ -57,8 +51,6 @@ export class TailPhaseManager extends AbstractPhaseManager { throw err; }, ); - // Temporary hack. Should sort them in the tail circuit. - this.patchLogsOrdering(tx, previousPublicKernelOutput); // commit the state updates from this transaction await this.publicStateDB.commit(); @@ -163,33 +155,9 @@ export class TailPhaseManager extends AbstractPhaseManager { } private sortLogsHashes(unencryptedLogsHashes: Tuple): Tuple { + // TODO(6052): logs here may have duplicate counters from nested calls return sortByCounter( unencryptedLogsHashes.map(n => ({ ...n, counter: n.counter.toNumber(), isEmpty: () => n.isEmpty() })), ).map(h => new SideEffect(h.value, new Fr(h.counter))) as Tuple; } - - // As above, this is a hack for unencrypted logs ordering, now they are sorted. Since the public kernel - // cannot keep track of side effects that happen after or before a nested call, we override the gathered logs. - // As a sanity check, we at least verify that the elements are the same, so we are only tweaking their ordering. - // See same fn in pxe_service.ts - // Added as part of resolving #5017 - private patchLogsOrdering(tx: Tx, publicInputs: PublicKernelCircuitPublicInputs) { - const unencLogs = tx.unencryptedLogs.unrollLogs(); - const sortedUnencLogs = publicInputs.end.unencryptedLogsHashes; - - const finalUnencLogs: UnencryptedL2Log[] = []; - sortedUnencLogs.forEach((sideEffect: SideEffect) => { - if (!sideEffect.isEmpty()) { - const isLog = (log: UnencryptedL2Log) => Fr.fromBuffer(log.hash()).equals(sideEffect.value); - const thisLogIndex = unencLogs.findIndex(isLog); - finalUnencLogs.push(unencLogs[thisLogIndex]); - } - }); - const unencryptedLogs = new UnencryptedFunctionL2Logs(finalUnencLogs); - - tx.unencryptedLogs.functionLogs[0] = unencryptedLogs; - for (let i = 1; i < tx.unencryptedLogs.functionLogs.length; i++) { - tx.unencryptedLogs.functionLogs[i] = UnencryptedFunctionL2Logs.empty(); - } - } } diff --git a/yarn-project/simulator/src/public/transitional_adaptors.ts b/yarn-project/simulator/src/public/transitional_adaptors.ts index 3091deb1476..2bbd079ad89 100644 --- a/yarn-project/simulator/src/public/transitional_adaptors.ts +++ b/yarn-project/simulator/src/public/transitional_adaptors.ts @@ -153,6 +153,7 @@ export async function convertAvmResults( // TODO: Support nested executions. const nestedExecutions: PublicExecutionResult[] = []; + const allUnencryptedLogs = unencryptedLogs; // TODO keep track of side effect counters const startSideEffectCounter = Fr.ZERO; const endSideEffectCounter = Fr.ZERO; @@ -172,6 +173,8 @@ export async function convertAvmResults( nestedExecutions, unencryptedLogsHashes, unencryptedLogs, + unencryptedLogPreimagesLength: new Fr(unencryptedLogs.getSerializedLength()), + allUnencryptedLogs, reverted: result.reverted, revertReason: result.revertReason ? createSimulationError(result.revertReason) : undefined, startGasLeft: executionContext.availableGas,