Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions noir-projects/aztec-nr/aztec/src/discovery/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ pub mod nonce_discovery;

/// We reserve two fields in the note private log that are not part of the note content: one for the storage slot, and
/// one for the combined log and note type ID.
global NOTE_PRIVATE_LOG_RESERVED_FIELDS: u32 = 2;
global PRIVATE_LOG_EXPANDED_METADATA_LEN: u32 = 1;

/// The maximum length of the packed representation of a note's contents. This is limited by private log size, encryption
/// overhead and extra fields in the log (e.g. the combined log and note type ID).
pub global MAX_NOTE_PACKED_LEN: u32 =
PRIVATE_LOG_PLAINTEXT_SIZE_IN_FIELDS - NOTE_PRIVATE_LOG_RESERVED_FIELDS;
/// The maximum length of the log's content, i.e. after log type ID and metadata extraction.
pub global MAX_LOG_CONTENT_LEN: u32 =
PRIVATE_LOG_PLAINTEXT_SIZE_IN_FIELDS - PRIVATE_LOG_EXPANDED_METADATA_LEN;

use private_notes::MAX_NOTE_PACKED_LEN;

pub struct NoteHashAndNullifier {
/// The result of NoteHash::compute_note_hash
Expand All @@ -32,9 +33,9 @@ pub struct NoteHashAndNullifier {
/// `_compute_note_hash_and_nullifier`, which looks something like this:
///
/// ```
/// |packed_note_content, contract_address, nonce, storage_slot, note_type_id| {
/// |packed_note, contract_address, nonce, storage_slot, note_type_id| {
/// if note_type_id == MyNoteType::get_id() {
/// assert(packed_note_content.len() == MY_NOTE_TYPE_SERIALIZATION_LENGTH);
/// assert(packed_note.len() == MY_NOTE_TYPE_SERIALIZATION_LENGTH);
///
/// let note = MyNoteType::unpack(aztec::utils::array::subarray(packed_note.storage(), 0));
///
Expand All @@ -58,7 +59,7 @@ pub struct NoteHashAndNullifier {
/// };
/// }
/// ```
type ComputeNoteHashAndNullifier<Env> = unconstrained fn[Env](/* packed_note_content */BoundedVec<Field, MAX_NOTE_PACKED_LEN>, /* storage_slot */ Field, /* note_type_id */ Field, /* contract_address */ AztecAddress, /* nonce */ Field) -> Option<NoteHashAndNullifier>;
type ComputeNoteHashAndNullifier<Env> = unconstrained fn[Env](/* packed_note */BoundedVec<Field, MAX_NOTE_PACKED_LEN>, /* storage_slot */ Field, /* note_type_id */ Field, /* contract_address */ AztecAddress, /* nonce */ Field) -> Option<NoteHashAndNullifier>;

/// Performs the message discovery process, in which private are downloaded and inspected to find new private notes,
/// partial notes and events, etc., and pending partial notes are processed to search for their completion logs.
Expand Down
9 changes: 6 additions & 3 deletions noir-projects/aztec-nr/aztec/src/discovery/nonce_discovery.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::{discovery::{ComputeNoteHashAndNullifier, MAX_NOTE_PACKED_LEN}, utils::array};
use crate::{
discovery::{ComputeNoteHashAndNullifier, private_notes::MAX_NOTE_PACKED_LEN},
utils::array,
};

use dep::protocol_types::{
address::AztecAddress,
Expand Down Expand Up @@ -30,7 +33,7 @@ pub unconstrained fn attempt_note_nonce_discovery<Env>(
contract_address: AztecAddress,
storage_slot: Field,
note_type_id: Field,
packed_note_content: BoundedVec<Field, MAX_NOTE_PACKED_LEN>,
packed_note: BoundedVec<Field, MAX_NOTE_PACKED_LEN>,
) -> BoundedVec<DiscoveredNoteInfo, MAX_NOTE_HASHES_PER_TX> {
let discovered_notes = &mut BoundedVec::new();

Expand All @@ -52,7 +55,7 @@ pub unconstrained fn attempt_note_nonce_discovery<Env>(
// the note hash at the array index we're currently processing.
// TODO(#11157): handle failed note_hash_and_nullifier computation
let hashes = compute_note_hash_and_nullifier(
packed_note_content,
packed_note,
storage_slot,
note_type_id,
contract_address,
Expand Down
64 changes: 45 additions & 19 deletions noir-projects/aztec-nr/aztec/src/discovery/partial_notes.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
capsules::CapsuleArray,
discovery::{
ComputeNoteHashAndNullifier,
MAX_NOTE_PACKED_LEN,
MAX_LOG_CONTENT_LEN,
nonce_discovery::{attempt_note_nonce_discovery, DiscoveredNoteInfo},
},
oracle::message_discovery::{deliver_note, get_log_by_tag},
Expand All @@ -16,11 +16,12 @@ use dep::protocol_types::{
traits::{Deserialize, Serialize, ToField},
};

pub global PARTIAL_NOTE_COMPLETION_LOG_TAG_LEN: u32 = 1;
/// Partial notes have a maximum packed length of their private fields bound by extra content in their private log (i.e.
/// the note completion log tag).
global PARTIAL_NOTE_PRIVATE_LOG_CONTENT_NON_NOTE_FIELDS_LEN: u32 = 2;

/// Partial notes have a maximum packed length of their private fields bound by extra content in their private log (e.g.
/// the storage slot, note completion log tag, etc.).
pub global MAX_PARTIAL_NOTE_PRIVATE_PACKED_LEN: u32 =
MAX_NOTE_PACKED_LEN - PARTIAL_NOTE_COMPLETION_LOG_TAG_LEN;
MAX_LOG_CONTENT_LEN - PARTIAL_NOTE_PRIVATE_LOG_CONTENT_NON_NOTE_FIELDS_LEN;

/// The slot in the PXE capsules where we store a `CapsuleArray` of `DeliveredPendingPartialNote`.
// TODO(#11630): come up with some sort of slot allocation scheme.
Expand Down Expand Up @@ -50,23 +51,20 @@ pub(crate) struct DeliveredPendingPartialNote {

pub unconstrained fn process_partial_note_private_log(
contract_address: AztecAddress,
storage_slot: Field,
note_type_id: Field,
log_payload: BoundedVec<Field, MAX_NOTE_PACKED_LEN>,
recipient: AztecAddress,
log_metadata: u64,
log_content: BoundedVec<Field, MAX_LOG_CONTENT_LEN>,
) {
// We store the information of the partial note we found so that we can later search for the public log that will
// complete it. The tag is the first value in the payload, with the packed note content taking up the rest of it.
std::static_assert(
PARTIAL_NOTE_COMPLETION_LOG_TAG_LEN == 1,
"unexpected value for PARTIAL_NOTE_COMPLETION_LOG_TAG_LEN",
);
let (note_type_id, storage_slot, note_completion_log_tag, packed_private_note_content) =
decode_partial_note_private_log(log_metadata, log_content);

// We store the information of the partial note we found in a persistent capsule in PXE, so that we can later search
// for the public log that will complete it.
let pending = DeliveredPendingPartialNote {
note_completion_log_tag: log_payload.get(0),
note_completion_log_tag,
storage_slot,
note_type_id,
packed_private_note_content: array::subbvec(log_payload, 1),
packed_private_note_content,
recipient,
};

Expand Down Expand Up @@ -128,7 +126,7 @@ pub unconstrained fn fetch_and_process_public_partial_note_completion_logs<Env>(
// complete packed content.
let packed_public_note_content: BoundedVec<_, MAX_PUBLIC_PARTIAL_NOTE_PACKED_CONTENT_LENGTH> =
array::subbvec(log.log_content, NON_PACKED_CONTENT_FIELDS_IN_PUBLIC_LOG);
let complete_packed_note_content = array::append(
let complete_packed_note = array::append(
pending_partial_note.packed_private_note_content,
packed_public_note_content,
);
Expand All @@ -140,7 +138,7 @@ pub unconstrained fn fetch_and_process_public_partial_note_completion_logs<Env>(
contract_address,
pending_partial_note.storage_slot,
pending_partial_note.note_type_id,
complete_packed_note_content,
complete_packed_note,
);

debug_log_format(
Expand All @@ -158,7 +156,7 @@ pub unconstrained fn fetch_and_process_public_partial_note_completion_logs<Env>(
contract_address,
pending_partial_note.storage_slot,
discovered_note.nonce,
complete_packed_note_content,
complete_packed_note,
discovered_note.note_hash,
discovered_note.inner_nullifier,
log.tx_hash,
Expand All @@ -180,3 +178,31 @@ pub unconstrained fn fetch_and_process_public_partial_note_completion_logs<Env>(
}
}
}

fn decode_partial_note_private_log(
log_metadata: u64,
log_content: BoundedVec<Field, MAX_LOG_CONTENT_LEN>,
) -> (Field, Field, Field, BoundedVec<Field, MAX_PARTIAL_NOTE_PRIVATE_PACKED_LEN>) {
let note_type_id = log_metadata as Field; // TODO: make note type id not be a full field

assert(
log_content.len() > PARTIAL_NOTE_PRIVATE_LOG_CONTENT_NON_NOTE_FIELDS_LEN,
f"Invalid private note log: all partial note private logs must have at least {PARTIAL_NOTE_PRIVATE_LOG_CONTENT_NON_NOTE_FIELDS_LEN} fields",
);

// If PARTIAL_NOTE_PRIVATE_LOG_CONTENT_NON_NOTE_FIELDS_LEN is changed, causing the assertion below to fail, then the
// destructuring of the partial note private log encoding below must be updated as well.
std::static_assert(
PARTIAL_NOTE_PRIVATE_LOG_CONTENT_NON_NOTE_FIELDS_LEN == 2,
"unexpected value for PARTIAL_NOTE_PRIVATE_LOG_CONTENT_NON_NOTE_FIELDS_LEN",
);

// We currently have two fields that are not the partial note's packed representation, which are the storage slot
// and the note completion log tag.
let storage_slot = log_content.get(0);
let note_completion_log_tag = log_content.get(1);

let packed_private_note_content = array::subbvec(log_content, 2);

(note_type_id, storage_slot, note_completion_log_tag, packed_private_note_content)
}
85 changes: 48 additions & 37 deletions noir-projects/aztec-nr/aztec/src/discovery/private_logs.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::static_assert;

use crate::{oracle::message_discovery::sync_notes, utils::array};

use dep::protocol_types::{
Expand All @@ -9,8 +7,9 @@ use dep::protocol_types::{
};

use crate::discovery::{
ComputeNoteHashAndNullifier, MAX_NOTE_PACKED_LEN, NOTE_PRIVATE_LOG_RESERVED_FIELDS,
partial_notes::process_partial_note_private_log, private_notes::process_private_note_log,
ComputeNoteHashAndNullifier, MAX_LOG_CONTENT_LEN,
partial_notes::process_partial_note_private_log, PRIVATE_LOG_EXPANDED_METADATA_LEN,
private_notes::process_private_note_log,
};
use crate::encrypted_logs::log_assembly_strategies::default_aes128::note::encryption::decrypt_log;
// TODO(#12750): don't make this value assume we're using AES.
Expand Down Expand Up @@ -54,8 +53,7 @@ pub unconstrained fn do_process_log<Env>(
// currently just have two log types: 0 for private notes and 1 for partial notes. This will likely be expanded and
// improved upon in the future to also handle events, etc.

let (storage_slot, note_type_id, log_type_id, log_payload) =
destructure_log_plaintext(log_plaintext);
let (log_type_id, log_metadata, log_content) = decode_log_plaintext(log_plaintext);

if log_type_id == 0 {
debug_log("Processing private note log");
Expand All @@ -67,53 +65,66 @@ pub unconstrained fn do_process_log<Env>(
first_nullifier_in_tx,
recipient,
compute_note_hash_and_nullifier,
storage_slot,
note_type_id,
log_payload,
log_metadata,
log_content,
);
} else if log_type_id == 1 {
debug_log("Processing partial note private log");

process_partial_note_private_log(
contract_address,
storage_slot,
note_type_id,
log_payload,
recipient,
);
process_partial_note_private_log(contract_address, recipient, log_metadata, log_content);
} else {
// TODO(#11569): handle events
debug_log_format(
"Unknown log type id {0} (probably belonging to an event log)",
[log_type_id],
[log_type_id as Field],
);
}
}

unconstrained fn destructure_log_plaintext(
/// Decodes a log's plaintext following aztec-nr's standard log encoding.
///
/// The standard private log layout is composed of:
/// - an initial field called the 'expanded metadata'
/// - an arbitrary number of fields following that called the 'log content'
///
/// ```
/// log_plainext: [ log_expanded_metadata, ...log_content ]
/// ```
///
/// The expanded metadata itself is (currently) interpreted as a u64, of which:
/// - the upper 57 bits are the log type id
/// - the remaining 7 bits are called the 'log metadata'
///
/// ```
/// log_expanded_metadata: [ log_type_id | log_metadata ]
/// <--- 57 bits --->|<--- 7 bits --->
/// ```
///
/// The meaning of the log metadata and log content depend on the value of the log type id. Note that there is
/// nothing special about the log metadata, it _can_ be considered part of the content. It just has a different name
/// to make it distinct from the log content given that it is not a full field.
unconstrained fn decode_log_plaintext(
log_plaintext: BoundedVec<Field, PRIVATE_LOG_PLAINTEXT_SIZE_IN_FIELDS>,
) -> (Field, Field, Field, BoundedVec<Field, MAX_NOTE_PACKED_LEN>) {
assert(log_plaintext.len() >= NOTE_PRIVATE_LOG_RESERVED_FIELDS);
) -> (u64, u64, BoundedVec<Field, MAX_LOG_CONTENT_LEN>) {
assert(
log_plaintext.len() >= PRIVATE_LOG_EXPANDED_METADATA_LEN,
f"Invalid log plaintext: all logs must be decrypted into at least {PRIVATE_LOG_EXPANDED_METADATA_LEN} fields",
);

// If NOTE_PRIVATE_LOG_RESERVED_FIELDS is changed, causing the assertion below to fail, then the declarations for
// `storage_slot` and `note_type_id` must be updated as well.
static_assert(
NOTE_PRIVATE_LOG_RESERVED_FIELDS == 2,
"unexpected value for NOTE_PRIVATE_LOG_RESERVED_FIELDS",
// If PRIVATE_LOG_EXPANDED_METADATA_LEN is changed, causing the assertion below to fail, then the destructuring of
// the log encoding below must be updated as well.
std::static_assert(
PRIVATE_LOG_EXPANDED_METADATA_LEN == 1,
"unexpected value for PRIVATE_LOG_EXPANDED_METADATA_LEN",
);
let storage_slot = log_plaintext.get(0);

// We currently identify log types by packing the log type ID and note type ID into a single field, called the
// combined type ID. We can do this because the note type ID is only 7 bits long, and so use an 8th bit to
// distinguish private note logs and partial note logs.
// This abuses the fact that the encoding of both of these logs is extremely similar, and will need improving and
// more formalization once we introduce other dissimilar log types, such as events. Ideally we'd be able to
// leverage enums and tagged unions to achieve this goal.
let combined_type_id = log_plaintext.get(1);
let note_type_id = ((combined_type_id as u64) % 128) as Field;
let log_type_id = ((combined_type_id as u64) / 128) as Field;
// See the documentation of this function for a description of the log layout
let expanded_log_metadata = log_plaintext.get(0);

let log_type_id = ((expanded_log_metadata as u64) / 128);
let log_metadata = ((expanded_log_metadata as u64) % 128);

let log_payload = array::subbvec(log_plaintext, NOTE_PRIVATE_LOG_RESERVED_FIELDS);
let log_content = array::subbvec(log_plaintext, PRIVATE_LOG_EXPANDED_METADATA_LEN);

(storage_slot, note_type_id, log_type_id, log_payload)
(log_type_id, log_metadata, log_content)
}
Loading