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
8 changes: 7 additions & 1 deletion noir-projects/aztec-nr/aztec/src/authwit/account.nr
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@ impl AccountActions<&mut PrivateContext> {
pub fn entrypoint(self, app_payload: AppPayload, fee_payment_method: u8, cancellable: bool) {
let valid_fn = self.is_valid_impl;

assert(valid_fn(self.context, app_payload.hash()));
let message_hash = compute_authwit_message_hash(
self.context.this_address(),
self.context.chain_id(),
self.context.version(),
app_payload.hash(),
);
assert(valid_fn(self.context, message_hash));

if fee_payment_method == AccountFeePaymentMethodOptions.PREEXISTING_FEE_JUICE {
self.context.set_as_fee_payer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{
get_arr_of_size__message_bytes__from_PT, get_arr_of_size__message_bytes_padding__from_PT,
},
},
oracle::{aes128_decrypt::aes128_decrypt_oracle, random::random, shared_secret::get_shared_secret},
oracle::{aes128_decrypt::aes128_decrypt, random::random, shared_secret::get_shared_secret},
utils::{
array,
conversion::{
Expand Down Expand Up @@ -425,7 +425,7 @@ impl MessageEncryption for AES128 {
BoundedVec::<u8, HEADER_CIPHERTEXT_SIZE_IN_BYTES>::from_array(header_ciphertext);

// Decrypt header
let header_plaintext = aes128_decrypt_oracle(header_ciphertext_bvec, header_iv, header_sym_key);
let header_plaintext = aes128_decrypt(header_ciphertext_bvec, header_iv, header_sym_key);

// Extract ciphertext length from header (2 bytes, big-endian)
extract_ciphertext_length(header_plaintext)
Expand All @@ -439,7 +439,7 @@ impl MessageEncryption for AES128 {
BoundedVec::from_parts(ciphertext_with_padding, ciphertext_length);

// Decrypt main ciphertext and return it
let plaintext_bytes = aes128_decrypt_oracle(ciphertext, body_iv, body_sym_key);
let plaintext_bytes = aes128_decrypt(ciphertext, body_iv, body_sym_key);

// Convert bytes back to fields (32 bytes per field). Returns None if the actual bytes are
// not valid.
Expand Down
24 changes: 18 additions & 6 deletions noir-projects/aztec-nr/aztec/src/oracle/aes128_decrypt.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
use crate::utils::array::assert_bounded_vec_trimmed;

#[oracle(aztec_utl_aes128Decrypt)]
unconstrained fn aes128_decrypt_oracle<let N: u32>(
ciphertext: BoundedVec<u8, N>,
iv: [u8; 16],
sym_key: [u8; 16],
) -> BoundedVec<u8, N> {}

/// Decrypts a ciphertext, using AES128.
///
/// Returns a BoundedVec containing the plaintext.
Expand All @@ -8,20 +17,23 @@
/// Note that we accept ciphertext as a BoundedVec, not as an array. This is because this function is typically used
/// when processing logs and at that point we don't have a comptime information about the length of the ciphertext as
/// the log is not specific to any individual note.
#[oracle(aztec_utl_aes128Decrypt)]
pub unconstrained fn aes128_decrypt_oracle<let N: u32>(
pub unconstrained fn aes128_decrypt<let N: u32>(
ciphertext: BoundedVec<u8, N>,
iv: [u8; 16],
sym_key: [u8; 16],
) -> BoundedVec<u8, N> {}
) -> BoundedVec<u8, N> {
let result = aes128_decrypt_oracle(ciphertext, iv, sym_key);
assert_bounded_vec_trimmed(result);
result
}

mod test {
use crate::{
messages::encryption::aes128::derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe,
utils::{array::subarray::subarray, point::point_from_x_coord},
};
use crate::test::helpers::test_environment::TestEnvironment;
use super::aes128_decrypt_oracle;
use super::aes128_decrypt;
use poseidon::poseidon2::Poseidon2;
use std::aes128::aes128_encrypt;

Expand Down Expand Up @@ -49,7 +61,7 @@ mod test {
// ciphertext length is fixed. But we do it anyway to not have to have duplicate oracles.
let ciphertext_bvec = BoundedVec::<u8, TEST_CIPHERTEXT_LENGTH>::from_array(ciphertext);

let received_plaintext = aes128_decrypt_oracle(ciphertext_bvec, iv, sym_key);
let received_plaintext = aes128_decrypt(ciphertext_bvec, iv, sym_key);

assert_eq(received_plaintext.len(), TEST_PLAINTEXT_LENGTH);
assert_eq(received_plaintext.max_len(), TEST_CIPHERTEXT_LENGTH);
Expand Down Expand Up @@ -123,7 +135,7 @@ mod test {
// We need to convert the array to a BoundedVec because the oracle expects a BoundedVec as it's designed to
// work with logs of unknown length.
let ciphertext_bvec = BoundedVec::<u8, 48>::from_array(ciphertext);
let received_plaintext = aes128_decrypt_oracle(ciphertext_bvec, iv, bad_sym_key);
let received_plaintext = aes128_decrypt(ciphertext_bvec, iv, bad_sym_key);

let extracted_mac_as_bytes: [u8; TEST_MAC_LENGTH] =
subarray(received_plaintext.storage(), TEST_PLAINTEXT_LENGTH);
Expand Down
102 changes: 99 additions & 3 deletions noir-projects/aztec-nr/aztec/src/oracle/block_header.nr
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,107 @@ fn constrain_get_block_header_at_internal(
}

mod test {
use crate::protocol::traits::Hash;
use crate::test::helpers::test_environment::TestEnvironment;
use super::{constrain_get_block_header_at_internal, get_block_header_at_internal};
use super::{constrain_get_block_header_at_internal, get_block_header_at, get_block_header_at_internal};

#[test(should_fail_with = "Proving membership of a block in archive failed")]
unconstrained fn fetching_header_with_mismatched_block_number_should_fail() {
#[test]
unconstrained fn fetching_earliest_block_header_succeeds() {
let env = TestEnvironment::new();

env.mine_block();
env.mine_block();
env.mine_block();
env.mine_block();

env.private_context(|context| {
let anchor_block_header = context.anchor_block_header;

let header = get_block_header_at_internal(1);
constrain_get_block_header_at_internal(header, 1, anchor_block_header);

assert_eq(header.block_number(), 1);
});
}

#[test]
unconstrained fn fetching_past_block_header_succeeds() {
let env = TestEnvironment::new();

env.mine_block();
env.mine_block();
env.mine_block();
env.mine_block();

env.private_context(|context| {
let anchor_block_header = context.anchor_block_header;
let target_block_number = anchor_block_header.block_number() - 2;

let header = get_block_header_at_internal(target_block_number);
constrain_get_block_header_at_internal(header, target_block_number, anchor_block_header);

assert_eq(header.block_number(), target_block_number);
});
}

#[test]
unconstrained fn fetching_header_immediately_before_anchor_succeeds() {
let env = TestEnvironment::new();

env.mine_block();
env.mine_block();
env.mine_block();
env.mine_block();

// Block N-1 is the boundary case: last_archive covers exactly up to block N-1.
env.private_context(|context| {
let anchor_block_header = context.anchor_block_header;
let target_block_number = anchor_block_header.block_number() - 1;

let header = get_block_header_at_internal(target_block_number);
constrain_get_block_header_at_internal(header, target_block_number, anchor_block_header);

assert_eq(header.block_number(), target_block_number);
});
}

#[test]
unconstrained fn fetching_anchor_block_header_works() {
let env = TestEnvironment::new();

env.mine_block();
env.mine_block();
env.mine_block();
env.mine_block();

env.private_context(|context| {
let anchor_block_number = context.anchor_block_header.block_number();

let header = get_block_header_at(anchor_block_number, *context);

assert_eq(header.block_number(), anchor_block_number);
assert_eq(header.hash(), context.anchor_block_header.hash());
});
}

#[test(should_fail_with = "Last archive block number is smaller than the block number")]
unconstrained fn fetching_future_block_header_fails() {
let env = TestEnvironment::new();

env.mine_block();
env.mine_block();
env.mine_block();
env.mine_block();

env.private_context(|context| {
let anchor_block_number = context.anchor_block_header.block_number();

let _header = get_block_header_at(anchor_block_number + 1, *context);
});
}

#[test(should_fail_with = "Block number provided is not the same as the block number from the header hint")]
unconstrained fn fetching_header_with_mismatched_block_number_fails() {
let env = TestEnvironment::new();

env.mine_block();
Expand Down
2 changes: 2 additions & 0 deletions noir-projects/aztec-nr/aztec/src/oracle/notes.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::note::{HintedNote, note_interface::NoteType};
use crate::utils::array::assert_bounded_vec_trimmed;

use crate::protocol::{address::AztecAddress, traits::Packable};

Expand Down Expand Up @@ -142,6 +143,7 @@ where
MaxNotes,
<HintedNote<Note> as Packable>::N,
);
assert_bounded_vec_trimmed(packed_hinted_notes);

let mut notes = BoundedVec::<_, MaxNotes>::new();
for i in 0..packed_hinted_notes.len() {
Expand Down
66 changes: 66 additions & 0 deletions noir-projects/aztec-nr/aztec/src/utils/array/assert_trimmed.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/// Asserts that elements past `len()` in a `BoundedVec` are zeroed.
///
/// Oracle functions may return `BoundedVec` values with dirty trailing storage (non-zero elements past `len()`).
/// This is problematic because `BoundedVec`'s `Eq` implementation and other operations assume trailing elements
/// are zeroed.
///
/// This function should be called on any `BoundedVec` obtained from an oracle to guard against malformed data.
///
/// TODO(https://github.com/noir-lang/noir/issues/4218): Remove once Noir natively validates `BoundedVec` returned
/// from unconstrained functions.
pub(crate) unconstrained fn assert_bounded_vec_trimmed<T, let MaxLen: u32>(vec: BoundedVec<T, MaxLen>)
where
T: Eq,
{
let storage = vec.storage();
let len = vec.len();
for i in 0..MaxLen {
if i >= len {
assert_eq(storage[i], std::mem::zeroed(), "BoundedVec has non-zero trailing elements");
}
}
}

mod test {
use super::assert_bounded_vec_trimmed;

#[test]
unconstrained fn trimmed_empty_vec() {
let vec: BoundedVec<Field, 3> = BoundedVec::new();
assert_bounded_vec_trimmed(vec);
}

#[test]
unconstrained fn trimmed_full_vec() {
let vec = BoundedVec::<Field, 3>::from_array([1, 2, 3]);
assert_bounded_vec_trimmed(vec);
}

#[test]
unconstrained fn trimmed_partial_vec() {
let vec = BoundedVec::<Field, 5>::from_array([1, 2, 3]);
assert_bounded_vec_trimmed(vec);
}

#[test(should_fail_with = "BoundedVec has non-zero trailing elements")]
unconstrained fn dirty_trailing_element_fails() {
let mut vec = BoundedVec::<Field, 3>::from_array([1]);
// We use the unchecked setter to write past the length, knowingly breaking the invariant.
vec.set_unchecked(1, 42);
assert_bounded_vec_trimmed(vec);
}

#[test(should_fail_with = "BoundedVec has non-zero trailing elements")]
unconstrained fn dirty_last_element_fails() {
let mut vec = BoundedVec::<Field, 3>::from_array([1, 2]);
vec.set_unchecked(2, 99);
assert_bounded_vec_trimmed(vec);
}

#[test]
unconstrained fn trimmed_array_elements() {
// Test with array element type (like get_notes_oracle returns BoundedVec<[Field; N], MaxNotes>).
let vec = BoundedVec::<[Field; 2], 3>::from_array([[1, 2], [3, 4]]);
assert_bounded_vec_trimmed(vec);
}
}
2 changes: 2 additions & 0 deletions noir-projects/aztec-nr/aztec/src/utils/array/mod.nr
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
pub mod append;
pub mod assert_trimmed;
pub mod collapse;
pub mod subarray;
pub mod subbvec;

pub use append::append;
pub(crate) use assert_trimmed::assert_bounded_vec_trimmed;
pub use collapse::collapse;
pub use subarray::subarray;
pub use subbvec::subbvec;
Original file line number Diff line number Diff line change
@@ -1,8 +1,50 @@
use aztec::{macros::notes::note, protocol::traits::Packable};

#[derive(Eq, Packable)]
#[derive(Eq)]
#[note]
pub struct SubscriptionNote {
pub expiry_block_number: u32,
pub remaining_txs: u32,
}

impl Packable for SubscriptionNote {
let N: u32 = 1;

fn pack(self) -> [Field; Self::N] {
[(self.expiry_block_number as Field) * 2.pow_32(32) + (self.remaining_txs as Field)]
}

fn unpack(packed: [Field; Self::N]) -> Self {
let remaining_txs = packed[0] as u32;
let expiry_block_number = ((packed[0] - remaining_txs as Field) / 2.pow_32(32)) as u32;
Self { expiry_block_number, remaining_txs }
}
}

mod test {
use super::{Packable, SubscriptionNote};

#[test]
fn test_pack_unpack_subscription_note() {
let note = SubscriptionNote { expiry_block_number: 1000, remaining_txs: 5 };
let unpacked = SubscriptionNote::unpack(note.pack());
assert_eq(unpacked.expiry_block_number, note.expiry_block_number);
assert_eq(unpacked.remaining_txs, note.remaining_txs);
}

#[test]
fn test_pack_unpack_subscription_note_zeros() {
let note = SubscriptionNote { expiry_block_number: 0, remaining_txs: 0 };
let unpacked = SubscriptionNote::unpack(note.pack());
assert_eq(unpacked.expiry_block_number, 0);
assert_eq(unpacked.remaining_txs, 0);
}

#[test]
fn test_pack_unpack_subscription_note_max() {
let note = SubscriptionNote { expiry_block_number: 0xffffffff, remaining_txs: 0xffffffff };
let unpacked = SubscriptionNote::unpack(note.pack());
assert_eq(unpacked.expiry_block_number, note.expiry_block_number);
assert_eq(unpacked.remaining_txs, note.remaining_txs);
}
}
Loading
Loading