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
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
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;
Loading