Skip to content
8 changes: 6 additions & 2 deletions noir-projects/aztec-nr/aztec/src/capsules/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,12 @@ impl<T> CapsuleArray<T> {
capsules::store(self.contract_address, self.base_slot, current_length - 1);
}

/// Iterates over the entire array, calling the callback with all values and their array index. The order in which
/// values are processed is arbitrary.
/// Calls a function on each element of the array.
///
/// The function `f` is called once with each array value and its corresponding index. The order in which values
/// are processed is arbitrary.
///
/// ## Array Mutation
///
/// It is safe to delete the current element (and only the current element) from inside the callback via `remove`:
/// ```noir
Expand Down
56 changes: 55 additions & 1 deletion noir-projects/aztec-nr/aztec/src/keys/ephemeral.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use std::embedded_curve_ops::{EmbeddedCurveScalar, fixed_base_scalar_mul};

use crate::protocol::{point::Point, scalar::Scalar};

use crate::oracle::random::random;
use crate::{oracle::random::random, utils::point::get_sign_of_point};

/// Generates a random ephemeral key pair.
pub fn generate_ephemeral_key_pair() -> (Scalar, Point) {
// @todo Need to draw randomness from the full domain of Fq not only Fr

Expand All @@ -20,3 +21,56 @@ pub fn generate_ephemeral_key_pair() -> (Scalar, Point) {

(eph_sk, eph_pk)
}

/// Generates a random ephemeral key pair with a positive y-coordinate.
///
/// Unlike [`generate_ephemeral_key_pair`], the y-coordinate of the public key is guaranteed to be a positive value
/// (i.e. [`crate::utils::point::get_sign_of_point`] will return `true`).
///
/// This is useful as it means it is possible to just broadcast the x-coordinate as a single `Field` and then
/// reconstruct the original public key using [`crate::utils::point::point_from_x_coord_and_sign`] with `sign: true`.
pub fn generate_positive_ephemeral_key_pair() -> (Scalar, Point) {
// Safety: we use the randomness to preserve the privacy of both the sender and recipient via encryption, so a
// malicious sender could use non-random values to reveal the plaintext. But they already know it themselves
// anyway, and so the recipient already trusts them to not disclose this information. We can therefore assume that
// the sender will cooperate in the random value generation.
let eph_sk = unsafe { generate_secret_key_for_positive_public_key() };
let eph_pk = fixed_base_scalar_mul(eph_sk);

assert(get_sign_of_point(eph_pk), "Got an ephemeral public key with a negative y coordinate");

(eph_sk, eph_pk)
}

unconstrained fn generate_secret_key_for_positive_public_key() -> EmbeddedCurveScalar {
let mut sk = std::mem::zeroed();

loop {
// We simply produce random secret keys until we find one that has results in a positive public key. About half
// of all public keys fulfill this condition, so this should only take a few iterations at most.

// @todo Need to draw randomness from the full domain of Fq not only Fr
sk = EmbeddedCurveScalar::from_field(random());
let pk = fixed_base_scalar_mul(sk);
if get_sign_of_point(pk) {
break;
}
}

sk
}

mod test {
use crate::utils::point::get_sign_of_point;
use super::generate_positive_ephemeral_key_pair;

#[test]
fn generate_positive_ephemeral_key_pair_produces_positive_keys() {
// About half of random points are negative, so testing just a couple gives us high confidence that
// `generate_positive_ephemeral_key_pair` is indeed producing positive ones.
for _ in 0..10 {
let (_, pk) = generate_positive_ephemeral_key_pair();
assert(get_sign_of_point(pk));
}
}
}
60 changes: 40 additions & 20 deletions noir-projects/aztec-nr/aztec/src/messages/discovery/mod.nr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::protocol::{address::AztecAddress, logging::{debug_log, debug_log_format}};

pub mod nonce_discovery;
pub mod partial_notes;
pub mod private_events;
pub(crate) mod nonce_discovery;
pub(crate) mod partial_notes;
pub(crate) mod private_events;
pub mod private_notes;
pub mod process_message;

Expand All @@ -20,22 +20,38 @@ use crate::{
};

pub struct NoteHashAndNullifier {
/// The result of NoteHash::compute_note_hash
/// The result of [`crate::note::note_interface::NoteHash::compute_note_hash`].
pub note_hash: Field,
/// The result of NoteHash::compute_nullifier_unconstrained (since all of message discovery is unconstrained).
/// This is `None` if the nullifier cannot be computed (e.g., because the nullifier hiding key is not available).
/// The result of [`crate::note::note_interface::NoteHash::compute_nullifier_unconstrained`].
///
/// This value is unconstrained, as all of message discovery is unconstrained. It is `None` if the nullifier
/// cannot be computed (e.g. because the nullifier hiding key is not available).
pub inner_nullifier: Option<Field>,
}

/// A function which takes a note's packed content, address of the emitting contract, note nonce, storage slot and note
/// type ID and attempts to compute its note hash (not hashed by note nonce nor siloed by address) and inner nullifier
/// (not siloed by address).
/// A contract's way of computing note hashes and nullifiers.
///
/// This function must be user-provided as its implementation requires knowledge of how note type IDs are allocated in
/// a contract. The `#[aztec]` macro automatically creates such a contract library method called
/// `_compute_note_hash_and_nullifier`, which looks something like this:
/// Each contract in the network is free to compute their note's hash and nullifiers as they see fit - the hash
/// function itself and the nullifier derivation are not enshrined or standardized. Some aztec-nr functions however do
/// need to know the details of this computation (e.g. when finding new notes), which is what this type represents.
///
/// ```
/// This function takes a note's packed content, storage slot, note type ID, address of the emitting contract,
/// randomness and note nonce, and attempts to compute its inner note hash (not siloed by address nor uniqued by nonce)
/// and inner nullifier (not siloed by address).
///
/// ## Transient Notes
///
/// This function is meant to always be used on **settled** notes, i.e. those that have been inserted into the trees
/// and for which the nonce is known. It is never invoked in the context of a transient note, as those are not involved
/// in message processing.
///
/// ## Automatic Implementation
///
/// The [`[#aztec]`](crate::macros::aztec::aztec) macro automatically creates a correct implementation of this function
/// for each contract by inspecting all note types in use and the storage layout. This injected function is a
/// `#[contract_library_function]` called `_compute_note_hash_and_nullifier`, and it looks something like this:
///
/// ```noir
/// |packed_note, owner, storage_slot, note_type_id, contract_address, randomness, note_nonce| {
/// if note_type_id == MyNoteType::get_id() {
/// assert(packed_note.len() == MY_NOTE_TYPE_SERIALIZATION_LENGTH);
Expand Down Expand Up @@ -78,15 +94,14 @@ pub type CustomMessageHandler<Env> = unconstrained fn[Env](
/* msg_content */ BoundedVec<Field, MAX_MESSAGE_CONTENT_LEN>,
/* message_context */ MessageContext);

/// Performs the state synchronization process, in which private logs 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.
/// This is the mechanism via which a contract updates its knowledge of its private state.
/// Synchronizes the contract's private state with the network.
///
/// Note that the state is synchronized up to the latest block synchronized by PXE (referred to as "anchor block").
/// That should be close to the chain tip as block synchronization is performed before contract function simulation is
/// done.
/// As blocks are mined, it is possible for a contract's private state to change (e.g. with new notes being created),
/// but because these changes are private they will be invisible to most actors. This is the function that processes
/// new transactions in order to discover new notes, events, and other kinds of private state changes.
///
/// Receives the address of the contract on which discovery is performed along with the contract's configuration.
/// The private state will be synchronized up to the block that will be used for private transactions (i.e. the anchor
/// block. This will typically be close to the tip of the chain.
pub unconstrained fn do_sync_state<ComputeNoteHashAndNullifierEnv, CustomMessageHandlerEnv>(
contract_address: AztecAddress,
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<ComputeNoteHashAndNullifierEnv>,
Expand All @@ -113,6 +128,11 @@ pub unconstrained fn do_sync_state<ComputeNoteHashAndNullifierEnv, CustomMessage
message_ciphertext,
pending_tagged_log.context,
);

// We need to delete each log from the array so that we won't process them again. `CapsuleArray::for_each`
// allows deletion of the current element during iteration, so this is safe.
// Note that this (and all other database changes) will only be committed if contract execution succeeds,
// including any enqueued validation requests.
logs.remove(i);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ use crate::protocol::{

/// A struct with the discovered information of a complete note, required for delivery to PXE. Note that this is *not*
/// the complete note information, since it does not include content, storage slot, etc.
pub struct DiscoveredNoteInfo {
pub note_nonce: Field,
pub note_hash: Field,
pub inner_nullifier: Field,
pub(crate) struct DiscoveredNoteInfo {
pub(crate) note_nonce: Field,
pub(crate) note_hash: Field,
pub(crate) inner_nullifier: Field,
}

/// Searches for note nonces that will result in a note that was emitted in a transaction. While rare, it is possible
Expand All @@ -23,7 +23,7 @@ pub struct DiscoveredNoteInfo {
///
/// Due to how nonces are computed, this function requires knowledge of the transaction in which the note was created,
/// more specifically the list of all unique note hashes in it plus the value of its first nullifier.
pub unconstrained fn attempt_note_nonce_discovery<Env>(
pub(crate) unconstrained fn attempt_note_nonce_discovery<Env>(
unique_note_hashes_in_tx: BoundedVec<Field, MAX_NOTE_HASHES_PER_TX>,
first_nullifier_in_tx: Field,
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<Env>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::protocol::{
};

/// The slot in the PXE capsules where we store a `CapsuleArray` of `DeliveredPendingPartialNote`.
pub global DELIVERED_PENDING_PARTIAL_NOTE_ARRAY_LENGTH_CAPSULES_SLOT: Field = sha256_to_field(
pub(crate) global DELIVERED_PENDING_PARTIAL_NOTE_ARRAY_LENGTH_CAPSULES_SLOT: Field = sha256_to_field(
"AZTEC_NR::DELIVERED_PENDING_PARTIAL_NOTE_ARRAY_LENGTH_CAPSULES_SLOT".as_bytes(),
);

Expand All @@ -37,7 +37,7 @@ pub(crate) struct DeliveredPendingPartialNote {
pub(crate) recipient: AztecAddress,
}

pub unconstrained fn process_partial_note_private_msg(
pub(crate) unconstrained fn process_partial_note_private_msg(
contract_address: AztecAddress,
recipient: AztecAddress,
msg_metadata: u64,
Expand Down Expand Up @@ -67,7 +67,7 @@ pub unconstrained fn process_partial_note_private_msg(

/// Searches for logs that would result in the completion of pending partial notes, ultimately resulting in the notes
/// being delivered to PXE if completed.
pub unconstrained fn fetch_and_process_partial_note_completion_logs<Env>(
pub(crate) unconstrained fn fetch_and_process_partial_note_completion_logs<Env>(
contract_address: AztecAddress,
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<Env>,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
};
use crate::protocol::{address::AztecAddress, traits::ToField};

pub unconstrained fn process_private_event_msg(
pub(crate) unconstrained fn process_private_event_msg(
contract_address: AztecAddress,
recipient: AztecAddress,
msg_metadata: u64,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::messages::{
};
use crate::protocol::{address::AztecAddress, constants::MAX_NOTE_HASHES_PER_TX, logging::debug_log_format};

pub unconstrained fn process_private_note_msg<Env>(
pub(crate) unconstrained fn process_private_note_msg<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
Expand Up @@ -44,13 +44,13 @@ pub unconstrained fn process_message_ciphertext<ComputeNoteHashAndNullifierEnv,
);
} else {
debug_log_format(
"Found invalid message from tx {0}, ignoring",
"Could not decrypt message ciphertext from tx {0}, ignoring",
[message_context.tx_hash],
);
}
}

pub unconstrained fn process_message_plaintext<ComputeNoteHashAndNullifierEnv, CustomMessageHandlerEnv>(
pub(crate) unconstrained fn process_message_plaintext<ComputeNoteHashAndNullifierEnv, CustomMessageHandlerEnv>(
contract_address: AztecAddress,
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<ComputeNoteHashAndNullifierEnv>,
process_custom_message: Option<CustomMessageHandler<CustomMessageHandlerEnv>>,
Expand Down
6 changes: 2 additions & 4 deletions noir-projects/aztec-nr/aztec/src/messages/encoding.nr
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,14 @@ pub(crate) global HEADER_CIPHERTEXT_SIZE_IN_BYTES: u32 = 16;
pub(crate) global AES128_PKCS7_EXPANSION_IN_BYTES: u32 = 16;

pub global EPH_PK_X_SIZE_IN_FIELDS: u32 = 1;
pub global EPH_PK_SIGN_BYTE_SIZE_IN_BYTES: u32 = 1;

// (15 - 1) * 31 - 16 - 1 - 16 = 401. Note: We multiply by 31 because ciphertext bytes are stored in fields using
// (15 - 1) * 31 - 16 - 16 = 402. Note: We multiply by 31 because ciphertext bytes are stored in fields using
// bytes_to_fields, which packs 31 bytes per field (since a Field is ~254 bits and can safely store 31 whole bytes).
pub(crate) global MESSAGE_PLAINTEXT_SIZE_IN_BYTES: u32 = (MESSAGE_CIPHERTEXT_LEN - EPH_PK_X_SIZE_IN_FIELDS) * 31
- HEADER_CIPHERTEXT_SIZE_IN_BYTES
- EPH_PK_SIGN_BYTE_SIZE_IN_BYTES
- AES128_PKCS7_EXPANSION_IN_BYTES;
// The plaintext bytes represent Field values that were originally serialized using fields_to_bytes, which converts
// each Field to 32 bytes. To convert the plaintext bytes back to fields, we divide by 32. 401 / 32 = 12
// each Field to 32 bytes. To convert the plaintext bytes back to fields, we divide by 32. 402 / 32 = 12
pub global MESSAGE_PLAINTEXT_LEN: u32 = MESSAGE_PLAINTEXT_SIZE_IN_BYTES / 32;

pub global MESSAGE_EXPANDED_METADATA_LEN: u32 = 1;
Expand Down
Loading
Loading