Skip to content

Commit

Permalink
feat: hash logs inside circuit (#5934)
Browse files Browse the repository at this point in the history
## 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.
  • Loading branch information
MirandaWood authored May 2, 2024
1 parent b39f1db commit 6b99527
Show file tree
Hide file tree
Showing 61 changed files with 1,222 additions and 608 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
16 changes: 15 additions & 1 deletion docs/docs/misc/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
17 changes: 5 additions & 12 deletions docs/docs/protocol-specs/logs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<!-- TODO: explicitly detail how the truncation is being done, so that we can check that it is secure. -->

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
19 changes: 16 additions & 3 deletions l1-contracts/src/core/libraries/decoders/TxsDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
}

Expand Down
27 changes: 13 additions & 14 deletions l1-contracts/test/decoders/Decoders.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
)
);

Expand All @@ -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)
)
);

Expand Down Expand Up @@ -269,16 +265,19 @@ 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

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");
Expand Down
6 changes: 1 addition & 5 deletions noir-projects/aztec-nr/address-note/src/address_note.nr
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -45,8 +42,7 @@ impl NoteInterface<ADDRESS_NOTE_LEN> 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(),
Expand Down
2 changes: 1 addition & 1 deletion noir-projects/aztec-nr/aztec/src/context/avm_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ impl PublicContextInterface for AvmContext {
nullifier_exists(unsiloed_nullifier, address.to_field()) == 1
}

fn emit_unencrypted_log<T>(&mut self, log: T) {
fn emit_unencrypted_log<T,N,M>(&mut self, log: T) {
let event_selector = 5; // Matches current PublicContext.
self.emit_unencrypted_log(event_selector, log);
}
Expand Down
2 changes: 1 addition & 1 deletion noir-projects/aztec-nr/aztec/src/context/interface.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(&mut self, log: T);
fn emit_unencrypted_log<T,N,M>(&mut self, log: T);
fn call_public_function<RETURNS_COUNT>(
self: &mut Self,
contract_address: AztecAddress,
Expand Down
Loading

0 comments on commit 6b99527

Please sign in to comment.