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
15 changes: 7 additions & 8 deletions noir-projects/aztec-nr/aztec/src/discovery/mod.nr
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use dep::protocol_types::{
address::AztecAddress, constants::PRIVATE_LOG_SIZE_IN_FIELDS, debug_log::debug_log,
};
// TODO(#12750): don't make this value assume we're using AES.
use crate::encrypted_logs::log_assembly_strategies::default_aes128::note::encryption::PRIVATE_LOG_PLAINTEXT_SIZE_IN_FIELDS;
Comment thread
benesjan marked this conversation as resolved.
use dep::protocol_types::{address::AztecAddress, debug_log::debug_log};

pub mod private_logs;
pub mod partial_notes;
Expand All @@ -10,11 +10,10 @@ pub mod nonce_discovery;
/// one for the combined log and note type ID.
global NOTE_PRIVATE_LOG_RESERVED_FIELDS: u32 = 2;

/// The maximum length of the packed representation of a note's contents. This is limited by private log size and extra
/// fields in the log (e.g. the combined log and note type ID).
// TODO (#11634): we're assuming here that the entire log is plaintext, which is not true due to headers, encryption
// padding, etc. Notes can't actually be this large.
Comment thread
nventuro marked this conversation as resolved.
pub global MAX_NOTE_PACKED_LEN: u32 = PRIVATE_LOG_SIZE_IN_FIELDS - NOTE_PRIVATE_LOG_RESERVED_FIELDS;
/// 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;

pub struct NoteHashAndNullifier {
/// The result of NoteHash::compute_note_hash
Expand Down
35 changes: 23 additions & 12 deletions noir-projects/aztec-nr/aztec/src/discovery/private_logs.nr
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ use crate::discovery::{
DELIVERED_PENDING_PARTIAL_NOTE_ARRAY_LENGTH_CAPSULES_SLOT, DeliveredPendingPartialNote,
},
};
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.
use crate::encrypted_logs::log_assembly_strategies::default_aes128::note::encryption::PRIVATE_LOG_PLAINTEXT_SIZE_IN_FIELDS;
Comment thread
nventuro marked this conversation as resolved.

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.
Expand All @@ -42,30 +45,32 @@ pub unconstrained fn fetch_and_process_private_tagged_logs<Env>(
sync_notes();
}

/// Processes a log's plaintext, searching for private notes or partial notes. Private notes result in nonce discovery
/// being performed prior to delivery, which requires knowledge of the transaction hash in which the notes would've been
/// created (typically the same transaction in which the log was emitted), along with the list of unique note hashes in
/// said transaction and the `compute_note_hash_and_nullifier` function.
/// Processes a log's ciphertext by decrypting it and then searching the plaintext for private notes or partial notes. Private
/// notes result in nonce discovery being performed prior to delivery, which requires knowledge of the transaction hash in
/// which the notes would've been created (typically the same transaction in which the log was emitted), along with the
/// list of unique note hashes in said transaction and the `compute_note_hash_and_nullifier` function.
pub unconstrained fn do_process_log<Env>(
contract_address: AztecAddress,
log_plaintext: BoundedVec<Field, PRIVATE_LOG_SIZE_IN_FIELDS>,
log: BoundedVec<Field, PRIVATE_LOG_SIZE_IN_FIELDS>,
tx_hash: Field,
unique_note_hashes_in_tx: BoundedVec<Field, MAX_NOTE_HASHES_PER_TX>,
first_nullifier_in_tx: Field,
recipient: AztecAddress,
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<Env>,
) {
// The first thing to do is to determine what type of private log we're processing. We 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 log_plaintext = decrypt_log(log, recipient);

// The first thing to do after decrypting the log is to determine what type of private log we're processing. We
// 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);

if log_type_id == 0 {
debug_log("Processing private note log");

process_private_note_log(
attempt_note_discovery(
contract_address,
tx_hash,
unique_note_hashes_in_tx,
Expand All @@ -87,12 +92,16 @@ pub unconstrained fn do_process_log<Env>(
recipient,
);
} else {
panic(f"Unknown log type id {log_type_id}");
// TODO(#11569): handle events
debug_log_format(
"Unknown log type id {0} (probably belonging to an event log)",
[log_type_id],
);
}
}

unconstrained fn destructure_log_plaintext(
log_plaintext: BoundedVec<Field, PRIVATE_LOG_SIZE_IN_FIELDS>,
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);

Expand All @@ -119,7 +128,9 @@ unconstrained fn destructure_log_plaintext(
(storage_slot, note_type_id, log_type_id, log_payload)
}

unconstrained fn process_private_note_log<Env>(
/// Attempts discovery of a note given information about its contents and the transaction in which it is
/// suspected the note was created.
pub unconstrained fn attempt_note_discovery<Env>(
contract_address: AztecAddress,
tx_hash: Field,
unique_note_hashes_in_tx: BoundedVec<Field, MAX_NOTE_HASHES_PER_TX>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/********************************************************/
Comment thread
nventuro marked this conversation as resolved.
// Disgusting arithmetic on generics
/********************************************************/

// In this section, instead of initialising arrays with very complicated generic
// arithmetic, such as:
// let my_arr: [u8; (((PT + (16 - (PT % 16))) + 49) + ((((((PT + (16 - (PT % 16))) + 49) + 30) / 31) * 31) - ((PT + (16 - (PT % 16))) + 49)))] = [0; (((PT + (16 - (PT % 16))) + 49) + ((((((PT + (16 - (PT % 16))) + 49) + 30) / 31) * 31) - ((PT + (16 - (PT % 16))) + 49)))];
//... we instead do the arithmetic a little bit at a time, so that the computation
// can be audited and understood. Now, we can't do arithmetic on generics in the body
// of a function, so we abusing functions in the following way:

// |full_pt| = |pt| = (N * 32) + 64
fn get_arr_of_size__full_plaintext<let PT: u32>() -> [u8; PT] {
[0; PT]
}

// |pt_aes_padding| = 16 - (|full_pt| % 16)
fn get_arr_of_size__plaintext_aes_padding<let FULL_PT: u32>(
_full_pt: [u8; FULL_PT],
) -> [u8; 16 - (FULL_PT % 16)] {
[0; 16 - (FULL_PT % 16)]
}

// |ct| = |full_pt| + |pt_aes_padding|
fn get_arr_of_size__ciphertext<let FULL_PT: u32, let PT_AES_PADDING: u32>(
_full_pt: [u8; FULL_PT],
_pt_aes_padding: [u8; PT_AES_PADDING],
) -> [u8; FULL_PT + PT_AES_PADDING] {
[0; FULL_PT + PT_AES_PADDING]
}

// Ok, so we have the following bytes:
// eph_pk_sign, header_ciphertext, ciphertext:
// Let lbwop = 1 + 48 + |ct| // aka log bytes without padding
fn get_arr_of_size__log_bytes_without_padding<let CT: u32>(_ct: [u8; CT]) -> [u8; 1 + 48 + CT] {
[0; 1 + 48 + CT]
}

// Recall:
// lbwop := 1 + 48 + |ct| // aka log bytes without padding
// We now want to pad b to the next multiple of 31, so as to "fill" fields.
// Let p be that padding.
// p = 31 * ceil(lbwop / 31) - lbwop
// = 31 * ((lbwop + 30) // 31) - lbwop
// (because ceil(x / y) = (x + y - 1) // y ).
fn get_arr_of_size__log_bytes_padding<let LBWOP: u32>(
_lbwop: [u8; LBWOP],
) -> [u8; (31 * ((LBWOP + 30) / 31)) - LBWOP] {
[0; (31 * ((LBWOP + 30) / 31)) - LBWOP]
}

// |log_bytes| = 1 + 48 + |ct| + p // aka log bytes (with padding)
// Recall:
// lbwop := 1 + 48 + |ct|
// p is the padding
fn get_arr_of_size__log_bytes<let LBWOP: u32, let P: u32>(
_lbwop: [u8; LBWOP],
_p: [u8; P],
) -> [u8; LBWOP + P] {
[0; LBWOP + P]
}

// The return type is pasted from the LSP's expectation, because it was too difficult
// to match its weird way of doing algebra. It doesn't know all rules of arithmetic.
// PT is the plaintext length.
pub(crate) fn get_arr_of_size__log_bytes_padding__from_PT<let PT: u32>() -> [u8; ((((((PT + (16 - (PT % 16))) + 49) + 30) / 31) * 31) - ((PT + (16 - (PT % 16))) + 49))] {
let full_pt = get_arr_of_size__full_plaintext::<PT>();
let pt_aes_padding = get_arr_of_size__plaintext_aes_padding(full_pt);
let ct = get_arr_of_size__ciphertext(full_pt, pt_aes_padding);
let lbwop = get_arr_of_size__log_bytes_without_padding(ct);
let p = get_arr_of_size__log_bytes_padding(lbwop);
p
}

// The return type is pasted from the LSP's expectation, because it was too difficult
// to match its weird way of doing algebra. It doesn't know all rules of arithmetic.
pub(crate) fn get_arr_of_size__log_bytes__from_PT<let PT: u32>() -> [u8; (((PT + (16 - (PT % 16))) + 49) + ((((((PT + (16 - (PT % 16))) + 49) + 30) / 31) * 31) - ((PT + (16 - (PT % 16))) + 49)))] {
let full_pt = get_arr_of_size__full_plaintext::<PT>();
let pt_aes_padding = get_arr_of_size__plaintext_aes_padding(full_pt);
let ct = get_arr_of_size__ciphertext(full_pt, pt_aes_padding);
let lbwop = get_arr_of_size__log_bytes_without_padding(ct);
let p = get_arr_of_size__log_bytes_padding(lbwop);
let log_bytes = get_arr_of_size__log_bytes(lbwop, p);
log_bytes
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@ use crate::{
context::PrivateContext,
encrypted_logs::{
encrypt::aes128::derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_sha256,
log_assembly_strategies::default_aes128::note::{
get_arr_of_size__log_bytes__from_PT, get_arr_of_size__log_bytes_padding__from_PT,
HEADER_CIPHERTEXT_SIZE_IN_BYTES,
log_assembly_strategies::default_aes128::{
arithmetic_generics_utils::{
get_arr_of_size__log_bytes__from_PT, get_arr_of_size__log_bytes_padding__from_PT,
},
note::encryption::HEADER_CIPHERTEXT_SIZE_IN_BYTES,
},
},
event::event_interface::EventInterface,
keys::{
ecdh_shared_secret::derive_ecdh_shared_secret_using_aztec_address,
ephemeral::generate_ephemeral_key_pair,
},
oracle::{
notes::{get_app_tag_as_sender, increment_app_tagging_secret_index_as_sender},
random::{get_random_bytes, random},
oracle::notes::{get_app_tag_as_sender, increment_app_tagging_secret_index_as_sender},
utils::{
conversion::{bytes_to_fields::bytes_to_fields, fields_to_bytes::fields_to_bytes},
point::get_sign_of_point,
random::get_random_bytes,
},
utils::{conversion::bytes_to_fields::bytes_to_fields, point::get_sign_of_point},
};
use dep::protocol_types::{
address::AztecAddress,
Expand Down Expand Up @@ -60,31 +63,26 @@ use std::aes128::aes128_encrypt;
/// This particular log assembly strategy (AES 128) requires the event (and the
/// event_type_id) to be converted into bytes, because the aes function
/// operates on bytes; not fields.
/// NB: The extra `+ 32` bytes is for the event_type_id:
fn compute_event_plaintext_for_this_strategy<Event, let N: u32>(event: Event) -> [u8; N * 32 + 32]
/// NB: The extra `+ 1` is for the event_type_id:
fn compute_event_plaintext_for_this_strategy<Event, let N: u32>(event: Event) -> [u8; (N + 1) * 32]
where
Event: EventInterface<N>,
{
let serialized_event = Serialize::<N>::serialize(event);

let event_type_id_bytes: [u8; 32] = Event::get_event_type_id().to_field().to_be_bytes();

let mut plaintext_bytes = [0 as u8; N * 32 + 32];

for i in 0..32 {
plaintext_bytes[i] = event_type_id_bytes[i];
}

let mut fields = [0; N + 1];
fields[0] = Event::get_event_type_id().to_field();
Comment on lines +73 to +74

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

for i in 0..serialized_event.len() {
let bytes: [u8; 32] = serialized_event[i].to_be_bytes();
for j in 0..32 {
plaintext_bytes[32 + i * 32 + j] = bytes[j];
}
fields[i + 1] = serialized_event[i];
}

plaintext_bytes
fields_to_bytes(fields)
}

// Note: This function is basically a copy of ./note/encryption.nr::encrypt_log. TODO: Merge the functions once
// the final note and event log layout is clear. Seems to me that the functions should be the same as encrypt_log
// is quite general and takes in an arbitrary plaintext. The note specific thing seems to be that in that function
// we perform some note-specific log length assertions.
fn compute_log<Event, let N: u32>(
context: PrivateContext,
event: Event,
Expand Down Expand Up @@ -210,6 +208,9 @@ where
// Convert the encrypted bytes to fields, because logs are field-based
// *****************************************************************************

// TODO(#12749): As Mike pointed out, we need to make logs produced by different encryption schemes
// indistinguishable from each other and for this reason the output here and in the last for-loop of this function
// should cover a full field.
let log_bytes_as_fields = bytes_to_fields(log_bytes);

// *****************************************************************************
Expand All @@ -236,9 +237,14 @@ where
offset += log_bytes_as_fields.len();

for i in offset..PRIVATE_LOG_SIZE_IN_FIELDS {
// We need to get a random value that fits in 31 bytes to not leak information about the size of the log
// (all the "real" log fields contain at most 31 bytes because of the way we convert the bytes to fields).
// TODO(#12749): Long term, this is not a good solution.

// Safety: we assume that the sender wants for the log to be private - a malicious one could simply reveal its
// contents publicly. It is therefore fine to trust the sender to provide random padding.
final_log[i] = unsafe { random() };
let field_bytes = unsafe { get_random_bytes::<31>() };
final_log[i] = Field::from_be_bytes::<31>(field_bytes);
}

final_log
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod arithmetic_generics_utils;
pub mod event;
pub mod note;
Loading