From 880671d2a2b3a60952dc3e64f95d1d881df0c278 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Fri, 27 Feb 2026 15:29:58 +0000 Subject: [PATCH 1/2] feat: remove epk sign from message payload (backport #20926) Raw cherry-pick of 2ba3311e8a onto v4 with unresolved conflict markers in encoding.nr and aes128.nr. --- .../aztec-nr/aztec/src/keys/ephemeral.nr | 56 +++++++- .../aztec-nr/aztec/src/messages/encoding.nr | 11 +- .../aztec/src/messages/encryption/aes128.nr | 120 ++++++++++++++++-- .../logs/arithmetic_generics_utils.nr | 16 +-- 4 files changed, 181 insertions(+), 22 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/keys/ephemeral.nr b/noir-projects/aztec-nr/aztec/src/keys/ephemeral.nr index e8d59d42be8c..0e24bb42a526 100644 --- a/noir-projects/aztec-nr/aztec/src/keys/ephemeral.nr +++ b/noir-projects/aztec-nr/aztec/src/keys/ephemeral.nr @@ -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 @@ -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)); + } + } +} diff --git a/noir-projects/aztec-nr/aztec/src/messages/encoding.nr b/noir-projects/aztec-nr/aztec/src/messages/encoding.nr index d810aac2abf2..fb8b22047662 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/encoding.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/encoding.nr @@ -14,15 +14,24 @@ pub global MESSAGE_CIPHERTEXT_LEN: u32 = PRIVATE_LOG_CIPHERTEXT_LEN; pub(crate) global HEADER_CIPHERTEXT_SIZE_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; +<<<<<<< HEAD // (17 - 1) * 31 - 16 - 1 = 479 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 +>>>>>>> 2ba3311e8a (feat: remove epk sign from message payload (#20926)) // bytes_to_fields, which packs 31 bytes per field (since a Field is ~254 bits and can safely store 31 whole bytes). global MESSAGE_PLAINTEXT_SIZE_IN_BYTES: u32 = (MESSAGE_CIPHERTEXT_LEN - EPH_PK_X_SIZE_IN_FIELDS) * 31 - HEADER_CIPHERTEXT_SIZE_IN_BYTES +<<<<<<< HEAD - EPH_PK_SIGN_BYTE_SIZE_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. 479 / 32 = 14 +======= + - 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. 402 / 32 = 12 +>>>>>>> 2ba3311e8a (feat: remove epk sign from message payload (#20926)) pub global MESSAGE_PLAINTEXT_LEN: u32 = MESSAGE_PLAINTEXT_SIZE_IN_BYTES / 32; pub global MESSAGE_EXPANDED_METADATA_LEN: u32 = 1; diff --git a/noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr b/noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr index 97f32377be8f..b647314333f5 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr @@ -7,11 +7,16 @@ use crate::protocol::{ }; use crate::{ - keys::{ecdh_shared_secret::derive_ecdh_shared_secret, ephemeral::generate_ephemeral_key_pair}, + keys::{ecdh_shared_secret::derive_ecdh_shared_secret, ephemeral::generate_positive_ephemeral_key_pair}, messages::{ encoding::{ +<<<<<<< HEAD EPH_PK_SIGN_BYTE_SIZE_IN_BYTES, EPH_PK_X_SIZE_IN_FIELDS, HEADER_CIPHERTEXT_SIZE_IN_BYTES, MESSAGE_CIPHERTEXT_LEN, MESSAGE_PLAINTEXT_LEN, +======= + EPH_PK_X_SIZE_IN_FIELDS, HEADER_CIPHERTEXT_SIZE_IN_BYTES, MESSAGE_CIPHERTEXT_LEN, MESSAGE_PLAINTEXT_LEN, + MESSAGE_PLAINTEXT_SIZE_IN_BYTES, +>>>>>>> 2ba3311e8a (feat: remove epk sign from message payload (#20926)) }, encryption::message_encryption::MessageEncryption, logs::arithmetic_generics_utils::{ @@ -25,7 +30,7 @@ use crate::{ bytes_to_fields::{bytes_from_fields, bytes_to_fields}, fields_to_bytes::{fields_from_bytes, fields_to_bytes}, }, - point::{get_sign_of_point, point_from_x_coord_and_sign}, + point::point_from_x_coord_and_sign, random::get_random_bytes, }, }; @@ -150,6 +155,90 @@ pub fn derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_u pub struct AES128 {} impl MessageEncryption for AES128 { +<<<<<<< HEAD +======= + + /// AES128-CBC encryption for Aztec protocol messages. + /// + /// ## Overview + /// + /// The plaintext is an array of up to `MESSAGE_PLAINTEXT_LEN` (12) fields. The output is always exactly + /// `MESSAGE_CIPHERTEXT_LEN` (15) fields, regardless of plaintext size. Unused trailing fields are filled with + /// random data so that all encrypted messages are indistinguishable by size. + /// + /// ## PKCS#7 Padding + /// + /// AES operates on 16-byte blocks, so the plaintext must be padded to a multiple of 16. PKCS#7 padding always + /// adds at least 1 byte (so the receiver can always detect and strip it), which means: + /// - 1 B plaintext -> 15 B padding -> 16 B total + /// - 15 B plaintext -> 1 B padding -> 16 B total + /// - 16 B plaintext -> 16 B padding -> 32 B total (full extra block) + /// + /// In general: if the plaintext is already a multiple of 16, a full 16-byte padding block is appended. + /// + /// ## Encryption Steps + /// + /// **1. Body encryption.** The plaintext fields are serialized to bytes (32 bytes per field) and AES-128-CBC + /// encrypted. Since 32 is a multiple of 16, PKCS#7 always adds a full 16-byte padding block (see above): + /// + /// ```text + /// +---------------------------------------------+ + /// | body ct | + /// | PlaintextLen*32 + 16 B | + /// +-------------------------------+--------------+ + /// | encrypted plaintext fields | PKCS#7 (16B) | + /// | (serialized at 32 B each) | | + /// +-------------------------------+--------------+ + /// ``` + /// + /// **2. Header encryption.** The byte length of `body_ct` is stored as a 2-byte big-endian integer. This 2-byte + /// header plaintext is then AES-encrypted; PKCS#7 pads the remaining 14 bytes to fill one 16-byte AES block, + /// producing a 16-byte header ciphertext: + /// + /// ```text + /// +---------------------------+ + /// | header ct | + /// | 16 B | + /// +--------+------------------+ + /// | body ct| PKCS#7 (14B) | + /// | length | | + /// | (2 B) | | + /// +--------+------------------+ + /// ``` + /// + /// ## Wire Format + /// + /// Messages are transmitted as fields, not bytes. A field is ~254 bits and can safely store 31 whole bytes, so + /// we need to pack our byte data into 31-byte chunks. This packing drives the wire format. + /// + /// **Step 1 -- Assemble bytes.** The ciphertexts are laid out in a byte array, padded with random bytes to a + /// multiple of 31 so it divides evenly into fields: + /// + /// ```text + /// +------------+-------------------------+---------+ + /// | header ct | body ct | byte pad| + /// | 16 B | PlaintextLen*32 + 16 B | (random)| + /// +------------+-------------------------+---------+ + /// |<-------- padded to a multiple of 31 B -------->| + /// ``` + /// + /// **Step 2 -- Pack into fields.** The byte array is split into 31-byte chunks, each stored in one field. The + /// ephemeral public key x-coordinate is prepended as its own field. Any remaining fields (up to 15 total) are + /// filled with random data so that all messages are the same size: + /// + /// ```text + /// +----------+-------------------------+-------------------+ + /// | eph_pk.x | message-byte fields | random field pad | + /// | | (packed 31 B per field) | (fills to 15) | + /// +----------+-------------------------+-------------------+ + /// |<---------- MESSAGE_CIPHERTEXT_LEN = 15 fields ------->| + /// ``` + /// + /// ## Key Derivation + /// + /// Two (key, IV) pairs are derived from the ECDH shared secret via Poseidon2 hashing with different domain + /// separators: one pair for the body ciphertext and one for the header ciphertext. +>>>>>>> 2ba3311e8a (feat: remove epk sign from message payload (#20926)) fn encrypt( plaintext: [Field; PlaintextLen], recipient: AztecAddress, @@ -158,12 +247,17 @@ impl MessageEncryption for AES128 { // reversed when processing the message in `process_message_ciphertext`) let plaintext_bytes = fields_to_bytes(plaintext); +<<<<<<< HEAD // ***************************************************************************** Compute the shared secret // ***************************************************************************** let (eph_sk, eph_pk) = generate_ephemeral_key_pair(); let eph_pk_sign_byte: u8 = get_sign_of_point(eph_pk) as u8; +======= + // Derive ECDH shared secret with recipient using a fresh ephemeral keypair. + let (eph_sk, eph_pk) = generate_positive_ephemeral_key_pair(); +>>>>>>> 2ba3311e8a (feat: remove epk sign from message payload (#20926)) // (not to be confused with the tagging shared secret) TODO (#17158): Currently we unwrap the Option returned // by derive_ecdh_shared_secret. We need to handle the case where the ephemeral public key is invalid to @@ -239,10 +333,15 @@ impl MessageEncryption for AES128 { "unexpected ciphertext header length", ); +<<<<<<< HEAD // ***************************************************************************** Prepend / append more bytes of // data to the ciphertext, before converting back to fields. // ***************************************************************************** +======= + // Assemble the message byte array: + // [header_ct (16B)] [body_ct] [padding to mult of 31] +>>>>>>> 2ba3311e8a (feat: remove epk sign from message payload (#20926)) let mut message_bytes_padding_to_mult_31 = get_arr_of_size__message_bytes_padding__from_PT::(); // Safety: this randomness won't be constrained to be random. It's in the interest of the executor of this fn @@ -256,8 +355,7 @@ impl MessageEncryption for AES128 { "Unexpected error: message_bytes.len() should be divisible by 31, by construction.", ); - message_bytes[0] = eph_pk_sign_byte; - let mut offset = 1; + let mut offset = 0; for i in 0..header_ciphertext_bytes.len() { message_bytes[offset + i] = header_ciphertext_bytes[i]; } @@ -279,7 +377,7 @@ impl MessageEncryption for AES128 { // computation used to obtain the offset computes the expected value (which we _can_ do in a static check), and // then add a cheap runtime check to also validate that the offset matches this. std::static_assert( - 1 + header_ciphertext_bytes.len() + ciphertext_bytes.len() + message_bytes_padding_to_mult_31.len() + header_ciphertext_bytes.len() + ciphertext_bytes.len() + message_bytes_padding_to_mult_31.len() == message_bytes.len(), "unexpected message length", ); @@ -334,12 +432,10 @@ impl MessageEncryption for AES128 { // Convert the ciphertext represented as fields to a byte representation (its original format) let ciphertext_without_eph_pk_x = bytes_from_fields(ciphertext_without_eph_pk_x_fields); - // First byte of the ciphertext represents the ephemeral public key sign - let eph_pk_sign_bool = ciphertext_without_eph_pk_x.get(0) != 0; - - // With the sign and the x-coordinate of the ephemeral public key, we can reconstruct the point. This may fail - // however, as not all x-coordinates are on the curve. In that case, we simply return `Option::none`. - point_from_x_coord_and_sign(eph_pk_x, eph_pk_sign_bool).map(|eph_pk| { + // With the x-coordinate of the ephemeral public key we can reconstruct the point as we know that the + // y-coordinate must be positive. This may fail however, as not all x-coordinates are on the curve. In that + // case, we simply return `Option::none`. + point_from_x_coord_and_sign(eph_pk_x, true).map(|eph_pk| { // Derive shared secret let ciphertext_shared_secret = get_shared_secret(recipient, eph_pk); @@ -351,7 +447,7 @@ impl MessageEncryption for AES128 { let (header_sym_key, header_iv) = pairs[1]; // Extract the header ciphertext - let header_start = EPH_PK_SIGN_BYTE_SIZE_IN_BYTES; // Skip eph_pk_sign byte + let header_start = 0; let header_ciphertext: [u8; HEADER_CIPHERTEXT_SIZE_IN_BYTES] = array::subarray(ciphertext_without_eph_pk_x.storage(), header_start); // We need to convert the array to a BoundedVec because the oracle expects a BoundedVec as it's designed to diff --git a/noir-projects/aztec-nr/aztec/src/messages/logs/arithmetic_generics_utils.nr b/noir-projects/aztec-nr/aztec/src/messages/logs/arithmetic_generics_utils.nr index f9b304e80cd4..6bd7e79a12e4 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/logs/arithmetic_generics_utils.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/logs/arithmetic_generics_utils.nr @@ -31,16 +31,16 @@ fn get_arr_of_size__ciphertext( [0; FullPt + PtAesPadding] } -// Ok, so we have the following bytes: eph_pk_sign, header_ciphertext, ciphertext: Let mbwop = 1 + +// Ok, so we have the following bytes: header_ciphertext, ciphertext: Let mbwop = // HEADER_CIPHERTEXT_SIZE_IN_BYTES + |ct| // aka message bytes without padding fn get_arr_of_size__message_bytes_without_padding( _ct: [u8; Ct], -) -> [u8; 1 + HEADER_CIPHERTEXT_SIZE_IN_BYTES + Ct] { - [0; 1 + HEADER_CIPHERTEXT_SIZE_IN_BYTES + Ct] +) -> [u8; HEADER_CIPHERTEXT_SIZE_IN_BYTES + Ct] { + [0; HEADER_CIPHERTEXT_SIZE_IN_BYTES + Ct] } // Recall: -// mbwop := 1 + HEADER_CIPHERTEXT_SIZE_IN_BYTES + |ct| // aka message bytes without padding +// mbwop := HEADER_CIPHERTEXT_SIZE_IN_BYTES + |ct| // aka message 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(mbwop // / 31) - mbwop // = 31 * ((mbwop + 30) // 31) - mbwop @@ -51,16 +51,16 @@ fn get_arr_of_size__message_bytes_padding( [0; (31 * ((Mbwop + 30) / 31)) - Mbwop] } -// |message_bytes| = 1 + HEADER_CIPHERTEXT_SIZE_IN_BYTES + |ct| + p // aka message bytes (with +// |message_bytes| = HEADER_CIPHERTEXT_SIZE_IN_BYTES + |ct| + p // aka message bytes (with // padding) Recall: -// mbwop := 1 + HEADER_CIPHERTEXT_SIZE_IN_BYTES + |ct| p is the padding +// mbwop := HEADER_CIPHERTEXT_SIZE_IN_BYTES + |ct| p is the padding fn get_arr_of_size__message_bytes(_mbwop: [u8; MBWOP], _p: [u8; P]) -> [u8; MBWOP + P] { [0; MBWOP + 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__message_bytes_padding__from_PT() -> [u8; ((((((Pt + (16 - (Pt % 16))) + HEADER_CIPHERTEXT_SIZE_IN_BYTES + 1) + 30) / 31) * 31) - ((Pt + (16 - (Pt % 16))) + HEADER_CIPHERTEXT_SIZE_IN_BYTES + 1))] { +pub(crate) fn get_arr_of_size__message_bytes_padding__from_PT() -> [u8; ((((((Pt + (16 - (Pt % 16))) + HEADER_CIPHERTEXT_SIZE_IN_BYTES) + 30) / 31) * 31) - ((Pt + (16 - (Pt % 16))) + HEADER_CIPHERTEXT_SIZE_IN_BYTES))] { let full_pt = get_arr_of_size__full_plaintext::(); 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); @@ -71,7 +71,7 @@ pub(crate) fn get_arr_of_size__message_bytes_padding__from_PT() -> // 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__message_bytes__from_PT() -> [u8; (((Pt + (16 - (Pt % 16))) + HEADER_CIPHERTEXT_SIZE_IN_BYTES + 1) + ((((((Pt + (16 - (Pt % 16))) + HEADER_CIPHERTEXT_SIZE_IN_BYTES + 1) + 30) / 31) * 31) - ((Pt + (16 - (Pt % 16))) + HEADER_CIPHERTEXT_SIZE_IN_BYTES + 1)))] { +pub(crate) fn get_arr_of_size__message_bytes__from_PT() -> [u8; (((Pt + (16 - (Pt % 16))) + HEADER_CIPHERTEXT_SIZE_IN_BYTES) + ((((((Pt + (16 - (Pt % 16))) + HEADER_CIPHERTEXT_SIZE_IN_BYTES) + 30) / 31) * 31) - ((Pt + (16 - (Pt % 16))) + HEADER_CIPHERTEXT_SIZE_IN_BYTES)))] { let full_pt = get_arr_of_size__full_plaintext::(); 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); From a5eccb05aeae58cd6a072f9db8966d44d7f2655b Mon Sep 17 00:00:00 2001 From: AztecBot Date: Fri, 27 Feb 2026 15:33:12 +0000 Subject: [PATCH 2/2] fix: resolve cherry-pick conflicts for v4 --- .../aztec-nr/aztec/src/messages/encoding.nr | 17 ++------ .../aztec/src/messages/encryption/aes128.nr | 41 ++++--------------- 2 files changed, 11 insertions(+), 47 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/messages/encoding.nr b/noir-projects/aztec-nr/aztec/src/messages/encoding.nr index fb8b22047662..ab03587bce0e 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/encoding.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/encoding.nr @@ -15,23 +15,12 @@ pub(crate) global HEADER_CIPHERTEXT_SIZE_IN_BYTES: u32 = 16; pub global EPH_PK_X_SIZE_IN_FIELDS: u32 = 1; -<<<<<<< HEAD -// (17 - 1) * 31 - 16 - 1 = 479 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 ->>>>>>> 2ba3311e8a (feat: remove epk sign from message payload (#20926)) +// (17 - 1) * 31 - 16 = 480. 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). global MESSAGE_PLAINTEXT_SIZE_IN_BYTES: u32 = (MESSAGE_CIPHERTEXT_LEN - EPH_PK_X_SIZE_IN_FIELDS) * 31 - - HEADER_CIPHERTEXT_SIZE_IN_BYTES -<<<<<<< HEAD - - EPH_PK_SIGN_BYTE_SIZE_IN_BYTES; + - HEADER_CIPHERTEXT_SIZE_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. 479 / 32 = 14 -======= - - 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. 402 / 32 = 12 ->>>>>>> 2ba3311e8a (feat: remove epk sign from message payload (#20926)) +// each Field to 32 bytes. To convert the plaintext bytes back to fields, we divide by 32. 480 / 32 = 15 pub global MESSAGE_PLAINTEXT_LEN: u32 = MESSAGE_PLAINTEXT_SIZE_IN_BYTES / 32; pub global MESSAGE_EXPANDED_METADATA_LEN: u32 = 1; diff --git a/noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr b/noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr index b647314333f5..c3e6adf151ec 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr @@ -10,13 +10,7 @@ use crate::{ keys::{ecdh_shared_secret::derive_ecdh_shared_secret, ephemeral::generate_positive_ephemeral_key_pair}, messages::{ encoding::{ -<<<<<<< HEAD - EPH_PK_SIGN_BYTE_SIZE_IN_BYTES, EPH_PK_X_SIZE_IN_FIELDS, HEADER_CIPHERTEXT_SIZE_IN_BYTES, - MESSAGE_CIPHERTEXT_LEN, MESSAGE_PLAINTEXT_LEN, -======= EPH_PK_X_SIZE_IN_FIELDS, HEADER_CIPHERTEXT_SIZE_IN_BYTES, MESSAGE_CIPHERTEXT_LEN, MESSAGE_PLAINTEXT_LEN, - MESSAGE_PLAINTEXT_SIZE_IN_BYTES, ->>>>>>> 2ba3311e8a (feat: remove epk sign from message payload (#20926)) }, encryption::message_encryption::MessageEncryption, logs::arithmetic_generics_utils::{ @@ -155,15 +149,13 @@ pub fn derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_u pub struct AES128 {} impl MessageEncryption for AES128 { -<<<<<<< HEAD -======= /// AES128-CBC encryption for Aztec protocol messages. /// /// ## Overview /// - /// The plaintext is an array of up to `MESSAGE_PLAINTEXT_LEN` (12) fields. The output is always exactly - /// `MESSAGE_CIPHERTEXT_LEN` (15) fields, regardless of plaintext size. Unused trailing fields are filled with + /// The plaintext is an array of up to `MESSAGE_PLAINTEXT_LEN` fields. The output is always exactly + /// `MESSAGE_CIPHERTEXT_LEN` fields, regardless of plaintext size. Unused trailing fields are filled with /// random data so that all encrypted messages are indistinguishable by size. /// /// ## PKCS#7 Padding @@ -223,22 +215,21 @@ impl MessageEncryption for AES128 { /// ``` /// /// **Step 2 -- Pack into fields.** The byte array is split into 31-byte chunks, each stored in one field. The - /// ephemeral public key x-coordinate is prepended as its own field. Any remaining fields (up to 15 total) are - /// filled with random data so that all messages are the same size: + /// ephemeral public key x-coordinate is prepended as its own field. Any remaining fields (up to + /// `MESSAGE_CIPHERTEXT_LEN` total) are filled with random data so that all messages are the same size: /// /// ```text /// +----------+-------------------------+-------------------+ /// | eph_pk.x | message-byte fields | random field pad | - /// | | (packed 31 B per field) | (fills to 15) | + /// | | (packed 31 B per field) | | /// +----------+-------------------------+-------------------+ - /// |<---------- MESSAGE_CIPHERTEXT_LEN = 15 fields ------->| + /// |<------------ MESSAGE_CIPHERTEXT_LEN fields ----------->| /// ``` /// /// ## Key Derivation /// /// Two (key, IV) pairs are derived from the ECDH shared secret via Poseidon2 hashing with different domain /// separators: one pair for the body ciphertext and one for the header ciphertext. ->>>>>>> 2ba3311e8a (feat: remove epk sign from message payload (#20926)) fn encrypt( plaintext: [Field; PlaintextLen], recipient: AztecAddress, @@ -247,17 +238,8 @@ impl MessageEncryption for AES128 { // reversed when processing the message in `process_message_ciphertext`) let plaintext_bytes = fields_to_bytes(plaintext); -<<<<<<< HEAD - // ***************************************************************************** Compute the shared secret - // ***************************************************************************** - - let (eph_sk, eph_pk) = generate_ephemeral_key_pair(); - - let eph_pk_sign_byte: u8 = get_sign_of_point(eph_pk) as u8; -======= // Derive ECDH shared secret with recipient using a fresh ephemeral keypair. let (eph_sk, eph_pk) = generate_positive_ephemeral_key_pair(); ->>>>>>> 2ba3311e8a (feat: remove epk sign from message payload (#20926)) // (not to be confused with the tagging shared secret) TODO (#17158): Currently we unwrap the Option returned // by derive_ecdh_shared_secret. We need to handle the case where the ephemeral public key is invalid to @@ -333,15 +315,8 @@ impl MessageEncryption for AES128 { "unexpected ciphertext header length", ); -<<<<<<< HEAD - // ***************************************************************************** Prepend / append more bytes of - // data to the ciphertext, before converting back to fields. - // ***************************************************************************** - -======= // Assemble the message byte array: // [header_ct (16B)] [body_ct] [padding to mult of 31] ->>>>>>> 2ba3311e8a (feat: remove epk sign from message payload (#20926)) let mut message_bytes_padding_to_mult_31 = get_arr_of_size__message_bytes_padding__from_PT::(); // Safety: this randomness won't be constrained to be random. It's in the interest of the executor of this fn @@ -464,9 +439,9 @@ impl MessageEncryption for AES128 { // Extract and decrypt main ciphertext let ciphertext_start = header_start + HEADER_CIPHERTEXT_SIZE_IN_BYTES; - let ciphertext_with_padding: [u8; (MESSAGE_CIPHERTEXT_LEN - EPH_PK_X_SIZE_IN_FIELDS) * 31 - HEADER_CIPHERTEXT_SIZE_IN_BYTES - EPH_PK_SIGN_BYTE_SIZE_IN_BYTES] = + let ciphertext_with_padding: [u8; (MESSAGE_CIPHERTEXT_LEN - EPH_PK_X_SIZE_IN_FIELDS) * 31 - HEADER_CIPHERTEXT_SIZE_IN_BYTES] = array::subarray(ciphertext_without_eph_pk_x.storage(), ciphertext_start); - let ciphertext: BoundedVec = + let ciphertext: BoundedVec = BoundedVec::from_parts(ciphertext_with_padding, ciphertext_length); // Decrypt main ciphertext and return it