@aztec/accounts 4.0.4 → 4.1.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -3966,8 +3966,8 @@
|
|
|
3966
3966
|
}
|
|
3967
3967
|
}
|
|
3968
3968
|
},
|
|
3969
|
-
"bytecode": "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",
|
|
3970
|
-
"debug_symbols": "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"
|
|
3969
|
+
"bytecode": "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",
|
|
3970
|
+
"debug_symbols": "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"
|
|
3971
3971
|
},
|
|
3972
3972
|
{
|
|
3973
3973
|
"name": "public_dispatch",
|
|
@@ -4302,11 +4302,11 @@
|
|
|
4302
4302
|
},
|
|
4303
4303
|
"131": {
|
|
4304
4304
|
"path": "/home/aztec-dev/aztec-packages/noir-projects/aztec-nr/aztec/src/messages/encoding.nr",
|
|
4305
|
-
"source": "// TODO(#12750): don't make these values assume we're using AES.\nuse crate::protocol::constants::PRIVATE_LOG_CIPHERTEXT_LEN;\nuse crate::utils::array;\n\n// We reassign to the constant here to communicate the distinction between a log and a message. In Aztec.nr, unlike in\n// protocol circuits, we have a concept of a message that can be emitted either as a private log or as an offchain\n// message. Message is a piece of data that is to be eventually delivered to a contract via the `process_message(...)`\n// utility function function that is injected by the #[aztec] macro. Note: PRIVATE_LOG_CIPHERTEXT_LEN is an amount of\n// fields, so MESSAGE_CIPHERTEXT_LEN is the size of the message in fields.\npub global MESSAGE_CIPHERTEXT_LEN: u32 = PRIVATE_LOG_CIPHERTEXT_LEN;\n\n// TODO(#12750): The global variables below should not be here as they are AES128 specific. ciphertext_length (2) + 14\n// bytes pkcs#7 AES padding.\npub(crate) global HEADER_CIPHERTEXT_SIZE_IN_BYTES: u32 = 16;\n\npub global EPH_PK_X_SIZE_IN_FIELDS: u32 = 1;\npub global EPH_PK_SIGN_BYTE_SIZE_IN_BYTES: u32 = 1;\n\n// (17 - 1) * 31 - 16 - 1 = 479 Note: We multiply by 31 because ciphertext bytes are stored in fields using\n// bytes_to_fields, which packs 31 bytes per field (since a Field is ~254 bits and can safely store 31 whole bytes).\nglobal MESSAGE_PLAINTEXT_SIZE_IN_BYTES: u32 = (MESSAGE_CIPHERTEXT_LEN - EPH_PK_X_SIZE_IN_FIELDS) * 31\n - HEADER_CIPHERTEXT_SIZE_IN_BYTES\n - EPH_PK_SIGN_BYTE_SIZE_IN_BYTES;\n// The plaintext bytes represent Field values that were originally serialized using fields_to_bytes, which converts\n// each Field to 32 bytes. To convert the plaintext bytes back to fields, we divide by 32. 479 / 32 = 14\npub global MESSAGE_PLAINTEXT_LEN: u32 = MESSAGE_PLAINTEXT_SIZE_IN_BYTES / 32;\n\npub global MESSAGE_EXPANDED_METADATA_LEN: u32 = 1;\n\n// The standard message layout is composed of:\n// - an initial field called the 'expanded metadata'\n// - an arbitrary number of fields following that called the 'message content'\n//\n// ```\n// message: [ msg_expanded_metadata, ...msg_content ]\n// ```\n//\n// The expanded metadata itself is interpreted as a u128, of which:\n// - the upper 64 bits are the message type id\n// - the lower 64 bits are called the 'message metadata'\n//\n// ```\n// msg_expanded_metadata: [ msg_type_id | msg_metadata ]\n// <--- 64 bits --->|<--- 64 bits --->\n// ```\n//\n// The meaning of the message metadata and message content depend on the value of the message type id. Note that there\n// is nothing special about the message metadata, it _can_ be considered part of the content. It just has a different\n// name to make it distinct from the message content given that it is not a full field.\n\n/// The maximum length of a message's content, i.e. not including the expanded message metadata.\npub global MAX_MESSAGE_CONTENT_LEN: u32 = MESSAGE_PLAINTEXT_LEN - MESSAGE_EXPANDED_METADATA_LEN;\n\n/// Encodes a message following aztec-nr's standard message encoding. This message can later be decoded with\n/// `decode_message` to retrieve the original values.\n///\n/// - The `msg_type` is an identifier that groups types of messages that are all processed the same way, e.g. private\n/// notes or events. Possible values are defined in `aztec::messages::msg_type`.\n/// - The `msg_metadata` and `msg_content` are the values stored in the message, whose meaning depends on the\n/// `msg_type`. The only special thing about `msg_metadata` that separates it from `msg_content` is that it is a u64\n/// instead of a full Field (due to details of how messages are encoded), allowing applications that can fit values\n/// into this smaller variable to achieve higher data efficiency.\npub fn encode_message<let N: u32>(\n msg_type: u64,\n msg_metadata: u64,\n msg_content: [Field; N],\n) -> [Field; (N + MESSAGE_EXPANDED_METADATA_LEN)] {\n std::static_assert(\n msg_content.len() <= MAX_MESSAGE_CONTENT_LEN,\n \"Invalid message content: it must have a length of at most MAX_MESSAGE_CONTENT_LEN\",\n );\n\n // If MESSAGE_EXPANDED_METADATA_LEN is changed, causing the assertion below to fail, then the destructuring of the\n // message encoding below must be updated as well.\n std::static_assert(\n MESSAGE_EXPANDED_METADATA_LEN == 1,\n \"unexpected value for MESSAGE_EXPANDED_METADATA_LEN\",\n );\n let mut message: [Field; (N + MESSAGE_EXPANDED_METADATA_LEN)] = std::mem::zeroed();\n\n message[0] = to_expanded_metadata(msg_type, msg_metadata);\n for i in 0..msg_content.len() {\n message[MESSAGE_EXPANDED_METADATA_LEN + i] = msg_content[i];\n }\n\n message\n}\n\n/// Decodes a standard aztec-nr message, i.e. one created via `encode_message`, returning the original encoded values.\n///\n/// Note that `encode_message` returns a fixed size array while this function takes a `BoundedVec`: this is because\n/// prior to decoding the message type is unknown, and consequentially not known at compile time. If working with\n/// fixed-size messages, consider using `BoundedVec::from_array` to convert them.\npub unconstrained fn decode_message(\n message: BoundedVec<Field, MESSAGE_PLAINTEXT_LEN>,\n) -> (u64, u64, BoundedVec<Field, MAX_MESSAGE_CONTENT_LEN>) {\n assert(\n message.len() >= MESSAGE_EXPANDED_METADATA_LEN,\n f\"Invalid message: it must have at least {MESSAGE_EXPANDED_METADATA_LEN} fields\",\n );\n\n // If MESSAGE_EXPANDED_METADATA_LEN is changed, causing the assertion below to fail, then the destructuring of the\n // message encoding below must be updated as well.\n std::static_assert(\n MESSAGE_EXPANDED_METADATA_LEN == 1,\n \"unexpected value for MESSAGE_EXPANDED_METADATA_LEN\",\n );\n\n let msg_expanded_metadata = message.get(0);\n let (msg_type_id, msg_metadata) = from_expanded_metadata(msg_expanded_metadata);\n let msg_content = array::subbvec(message, MESSAGE_EXPANDED_METADATA_LEN);\n\n (msg_type_id, msg_metadata, msg_content)\n}\n\nglobal U64_SHIFT_MULTIPLIER: Field = 2.pow_32(64);\n\nfn to_expanded_metadata(msg_type: u64, msg_metadata: u64) -> Field {\n // We use multiplication instead of bit shifting operations to shift the type bits as bit shift operations are\n // expensive in circuits.\n let type_field: Field = (msg_type as Field) * U64_SHIFT_MULTIPLIER;\n let msg_metadata_field = msg_metadata as Field;\n\n type_field + msg_metadata_field\n}\n\nfn from_expanded_metadata(input: Field) -> (u64, u64) {\n input.assert_max_bit_size::<128>();\n let msg_metadata = (input as u64);\n let msg_type = ((input - (msg_metadata as Field)) / U64_SHIFT_MULTIPLIER) as u64;\n // Use division instead of bit shift since bit shifts are expensive in circuits\n (msg_type, msg_metadata)\n}\n\nmod tests {\n use crate::utils::array::subarray::subarray;\n use super::{decode_message, encode_message, from_expanded_metadata, MAX_MESSAGE_CONTENT_LEN, to_expanded_metadata};\n\n global U64_MAX: u64 = (2.pow_32(64) - 1) as u64;\n global U128_MAX: Field = (2.pow_32(128) - 1);\n\n #[test]\n unconstrained fn encode_decode_empty_message(msg_type: u64, msg_metadata: u64) {\n let encoded = encode_message(msg_type, msg_metadata, []);\n let (decoded_msg_type, decoded_msg_metadata, decoded_msg_content) =\n decode_message(BoundedVec::from_array(encoded));\n\n assert_eq(decoded_msg_type, msg_type);\n assert_eq(decoded_msg_metadata, msg_metadata);\n assert_eq(decoded_msg_content.len(), 0);\n }\n\n #[test]\n unconstrained fn encode_decode_short_message(\n msg_type: u64,\n msg_metadata: u64,\n msg_content: [Field; MAX_MESSAGE_CONTENT_LEN / 2],\n ) {\n let encoded = encode_message(msg_type, msg_metadata, msg_content);\n let (decoded_msg_type, decoded_msg_metadata, decoded_msg_content) =\n decode_message(BoundedVec::from_array(encoded));\n\n assert_eq(decoded_msg_type, msg_type);\n assert_eq(decoded_msg_metadata, msg_metadata);\n assert_eq(decoded_msg_content.len(), msg_content.len());\n assert_eq(subarray(decoded_msg_content.storage(), 0), msg_content);\n }\n\n #[test]\n unconstrained fn encode_decode_full_message(\n msg_type: u64,\n msg_metadata: u64,\n msg_content: [Field; MAX_MESSAGE_CONTENT_LEN],\n ) {\n let encoded = encode_message(msg_type, msg_metadata, msg_content);\n let (decoded_msg_type, decoded_msg_metadata, decoded_msg_content) =\n decode_message(BoundedVec::from_array(encoded));\n\n assert_eq(decoded_msg_type, msg_type);\n assert_eq(decoded_msg_metadata, msg_metadata);\n assert_eq(decoded_msg_content.len(), msg_content.len());\n assert_eq(subarray(decoded_msg_content.storage(), 0), msg_content);\n }\n\n #[test]\n unconstrained fn to_expanded_metadata_packing() {\n // Test case 1: All bits set\n let packed = to_expanded_metadata(U64_MAX, U64_MAX);\n let (msg_type, msg_metadata) = from_expanded_metadata(packed);\n assert_eq(msg_type, U64_MAX);\n assert_eq(msg_metadata, U64_MAX);\n\n // Test case 2: Only log type bits set\n let packed = to_expanded_metadata(U64_MAX, 0);\n let (msg_type, msg_metadata) = from_expanded_metadata(packed);\n assert_eq(msg_type, U64_MAX);\n assert_eq(msg_metadata, 0);\n\n // Test case 3: Only msg_metadata bits set\n let packed = to_expanded_metadata(0, U64_MAX);\n let (msg_type, msg_metadata) = from_expanded_metadata(packed);\n assert_eq(msg_type, 0);\n assert_eq(msg_metadata, U64_MAX);\n\n // Test case 4: No bits set\n let packed = to_expanded_metadata(0, 0);\n let (msg_type, msg_metadata) = from_expanded_metadata(packed);\n assert_eq(msg_type, 0);\n assert_eq(msg_metadata, 0);\n }\n\n #[test]\n unconstrained fn from_expanded_metadata_packing() {\n // Test case 1: All bits set\n let input = U128_MAX as Field;\n let (msg_type, msg_metadata) = from_expanded_metadata(input);\n assert_eq(msg_type, U64_MAX);\n assert_eq(msg_metadata, U64_MAX);\n\n // Test case 2: Only log type bits set\n let input = (U128_MAX - U64_MAX as Field);\n let (msg_type, msg_metadata) = from_expanded_metadata(input);\n assert_eq(msg_type, U64_MAX);\n assert_eq(msg_metadata, 0);\n\n // Test case 3: Only msg_metadata bits set\n let input = U64_MAX as Field;\n let (msg_type, msg_metadata) = from_expanded_metadata(input);\n assert_eq(msg_type, 0);\n assert_eq(msg_metadata, U64_MAX);\n\n // Test case 4: No bits set\n let input = 0;\n let (msg_type, msg_metadata) = from_expanded_metadata(input);\n assert_eq(msg_type, 0);\n assert_eq(msg_metadata, 0);\n }\n\n #[test]\n unconstrained fn to_from_expanded_metadata(original_msg_type: u64, original_msg_metadata: u64) {\n let packed = to_expanded_metadata(original_msg_type, original_msg_metadata);\n let (unpacked_msg_type, unpacked_msg_metadata) = from_expanded_metadata(packed);\n\n assert_eq(original_msg_type, unpacked_msg_type);\n assert_eq(original_msg_metadata, unpacked_msg_metadata);\n }\n}\n"
|
|
4305
|
+
"source": "// TODO(#12750): don't make these values assume we're using AES.\nuse crate::protocol::constants::PRIVATE_LOG_CIPHERTEXT_LEN;\nuse crate::utils::array;\n\n// We reassign to the constant here to communicate the distinction between a log and a message. In Aztec.nr, unlike in\n// protocol circuits, we have a concept of a message that can be emitted either as a private log or as an offchain\n// message. Message is a piece of data that is to be eventually delivered to a contract via the `process_message(...)`\n// utility function function that is injected by the #[aztec] macro. Note: PRIVATE_LOG_CIPHERTEXT_LEN is an amount of\n// fields, so MESSAGE_CIPHERTEXT_LEN is the size of the message in fields.\npub global MESSAGE_CIPHERTEXT_LEN: u32 = PRIVATE_LOG_CIPHERTEXT_LEN;\n\n// TODO(#12750): The global variables below should not be here as they are AES128 specific.\n// The header plaintext is 2 bytes (ciphertext length), padded to the 16-byte AES block size by PKCS#7.\npub(crate) global HEADER_CIPHERTEXT_SIZE_IN_BYTES: u32 = 16;\n// AES PKCS#7 always adds at least one byte of padding. Since each plaintext field is 32 bytes (a multiple of the\n// 16-byte AES block size), a full 16-byte padding block is always appended.\npub(crate) global AES128_PKCS7_EXPANSION_IN_BYTES: u32 = 16;\n\npub global EPH_PK_X_SIZE_IN_FIELDS: u32 = 1;\n\n// (15 - 1) * 31 - 16 - 16 = 402. Note: We multiply by 31 because ciphertext bytes are stored in fields using\n// bytes_to_fields, which packs 31 bytes per field (since a Field is ~254 bits and can safely store 31 whole bytes).\npub(crate) global MESSAGE_PLAINTEXT_SIZE_IN_BYTES: u32 = (MESSAGE_CIPHERTEXT_LEN - EPH_PK_X_SIZE_IN_FIELDS) * 31\n - HEADER_CIPHERTEXT_SIZE_IN_BYTES\n - AES128_PKCS7_EXPANSION_IN_BYTES;\n// The plaintext bytes represent Field values that were originally serialized using fields_to_bytes, which converts\n// each Field to 32 bytes. To convert the plaintext bytes back to fields, we divide by 32. 402 / 32 = 12\npub global MESSAGE_PLAINTEXT_LEN: u32 = MESSAGE_PLAINTEXT_SIZE_IN_BYTES / 32;\n\npub global MESSAGE_EXPANDED_METADATA_LEN: u32 = 1;\n\n// The standard message layout is composed of:\n// - an initial field called the 'expanded metadata'\n// - an arbitrary number of fields following that called the 'message content'\n//\n// ```\n// message: [ msg_expanded_metadata, ...msg_content ]\n// ```\n//\n// The expanded metadata itself is interpreted as a u128, of which:\n// - the upper 64 bits are the message type id\n// - the lower 64 bits are called the 'message metadata'\n//\n// ```\n// msg_expanded_metadata: [ msg_type_id | msg_metadata ]\n// <--- 64 bits --->|<--- 64 bits --->\n// ```\n//\n// The meaning of the message metadata and message content depend on the value of the message type id. Note that there\n// is nothing special about the message metadata, it _can_ be considered part of the content. It just has a different\n// name to make it distinct from the message content given that it is not a full field.\n\n/// The maximum length of a message's content, i.e. not including the expanded message metadata.\npub global MAX_MESSAGE_CONTENT_LEN: u32 = MESSAGE_PLAINTEXT_LEN - MESSAGE_EXPANDED_METADATA_LEN;\n\n/// Encodes a message following aztec-nr's standard message encoding. This message can later be decoded with\n/// `decode_message` to retrieve the original values.\n///\n/// - The `msg_type` is an identifier that groups types of messages that are all processed the same way, e.g. private\n/// notes or events. Possible values are defined in `aztec::messages::msg_type`.\n/// - The `msg_metadata` and `msg_content` are the values stored in the message, whose meaning depends on the\n/// `msg_type`. The only special thing about `msg_metadata` that separates it from `msg_content` is that it is a u64\n/// instead of a full Field (due to details of how messages are encoded), allowing applications that can fit values\n/// into this smaller variable to achieve higher data efficiency.\npub fn encode_message<let N: u32>(\n msg_type: u64,\n msg_metadata: u64,\n msg_content: [Field; N],\n) -> [Field; (N + MESSAGE_EXPANDED_METADATA_LEN)] {\n std::static_assert(\n msg_content.len() <= MAX_MESSAGE_CONTENT_LEN,\n \"Invalid message content: it must have a length of at most MAX_MESSAGE_CONTENT_LEN\",\n );\n\n // If MESSAGE_EXPANDED_METADATA_LEN is changed, causing the assertion below to fail, then the destructuring of the\n // message encoding below must be updated as well.\n std::static_assert(\n MESSAGE_EXPANDED_METADATA_LEN == 1,\n \"unexpected value for MESSAGE_EXPANDED_METADATA_LEN\",\n );\n let mut message: [Field; (N + MESSAGE_EXPANDED_METADATA_LEN)] = std::mem::zeroed();\n\n message[0] = to_expanded_metadata(msg_type, msg_metadata);\n for i in 0..msg_content.len() {\n message[MESSAGE_EXPANDED_METADATA_LEN + i] = msg_content[i];\n }\n\n message\n}\n\n/// Decodes a standard aztec-nr message, i.e. one created via `encode_message`, returning the original encoded values.\n///\n/// Note that `encode_message` returns a fixed size array while this function takes a `BoundedVec`: this is because\n/// prior to decoding the message type is unknown, and consequentially not known at compile time. If working with\n/// fixed-size messages, consider using `BoundedVec::from_array` to convert them.\npub unconstrained fn decode_message(\n message: BoundedVec<Field, MESSAGE_PLAINTEXT_LEN>,\n) -> (u64, u64, BoundedVec<Field, MAX_MESSAGE_CONTENT_LEN>) {\n assert(\n message.len() >= MESSAGE_EXPANDED_METADATA_LEN,\n f\"Invalid message: it must have at least {MESSAGE_EXPANDED_METADATA_LEN} fields\",\n );\n\n // If MESSAGE_EXPANDED_METADATA_LEN is changed, causing the assertion below to fail, then the destructuring of the\n // message encoding below must be updated as well.\n std::static_assert(\n MESSAGE_EXPANDED_METADATA_LEN == 1,\n \"unexpected value for MESSAGE_EXPANDED_METADATA_LEN\",\n );\n\n let msg_expanded_metadata = message.get(0);\n let (msg_type_id, msg_metadata) = from_expanded_metadata(msg_expanded_metadata);\n let msg_content = array::subbvec(message, MESSAGE_EXPANDED_METADATA_LEN);\n\n (msg_type_id, msg_metadata, msg_content)\n}\n\nglobal U64_SHIFT_MULTIPLIER: Field = 2.pow_32(64);\n\nfn to_expanded_metadata(msg_type: u64, msg_metadata: u64) -> Field {\n // We use multiplication instead of bit shifting operations to shift the type bits as bit shift operations are\n // expensive in circuits.\n let type_field: Field = (msg_type as Field) * U64_SHIFT_MULTIPLIER;\n let msg_metadata_field = msg_metadata as Field;\n\n type_field + msg_metadata_field\n}\n\nfn from_expanded_metadata(input: Field) -> (u64, u64) {\n input.assert_max_bit_size::<128>();\n let msg_metadata = (input as u64);\n let msg_type = ((input - (msg_metadata as Field)) / U64_SHIFT_MULTIPLIER) as u64;\n // Use division instead of bit shift since bit shifts are expensive in circuits\n (msg_type, msg_metadata)\n}\n\nmod tests {\n use crate::utils::array::subarray::subarray;\n use super::{decode_message, encode_message, from_expanded_metadata, MAX_MESSAGE_CONTENT_LEN, to_expanded_metadata};\n\n global U64_MAX: u64 = (2.pow_32(64) - 1) as u64;\n global U128_MAX: Field = (2.pow_32(128) - 1);\n\n #[test]\n unconstrained fn encode_decode_empty_message(msg_type: u64, msg_metadata: u64) {\n let encoded = encode_message(msg_type, msg_metadata, []);\n let (decoded_msg_type, decoded_msg_metadata, decoded_msg_content) =\n decode_message(BoundedVec::from_array(encoded));\n\n assert_eq(decoded_msg_type, msg_type);\n assert_eq(decoded_msg_metadata, msg_metadata);\n assert_eq(decoded_msg_content.len(), 0);\n }\n\n #[test]\n unconstrained fn encode_decode_short_message(\n msg_type: u64,\n msg_metadata: u64,\n msg_content: [Field; MAX_MESSAGE_CONTENT_LEN / 2],\n ) {\n let encoded = encode_message(msg_type, msg_metadata, msg_content);\n let (decoded_msg_type, decoded_msg_metadata, decoded_msg_content) =\n decode_message(BoundedVec::from_array(encoded));\n\n assert_eq(decoded_msg_type, msg_type);\n assert_eq(decoded_msg_metadata, msg_metadata);\n assert_eq(decoded_msg_content.len(), msg_content.len());\n assert_eq(subarray(decoded_msg_content.storage(), 0), msg_content);\n }\n\n #[test]\n unconstrained fn encode_decode_full_message(\n msg_type: u64,\n msg_metadata: u64,\n msg_content: [Field; MAX_MESSAGE_CONTENT_LEN],\n ) {\n let encoded = encode_message(msg_type, msg_metadata, msg_content);\n let (decoded_msg_type, decoded_msg_metadata, decoded_msg_content) =\n decode_message(BoundedVec::from_array(encoded));\n\n assert_eq(decoded_msg_type, msg_type);\n assert_eq(decoded_msg_metadata, msg_metadata);\n assert_eq(decoded_msg_content.len(), msg_content.len());\n assert_eq(subarray(decoded_msg_content.storage(), 0), msg_content);\n }\n\n #[test]\n unconstrained fn to_expanded_metadata_packing() {\n // Test case 1: All bits set\n let packed = to_expanded_metadata(U64_MAX, U64_MAX);\n let (msg_type, msg_metadata) = from_expanded_metadata(packed);\n assert_eq(msg_type, U64_MAX);\n assert_eq(msg_metadata, U64_MAX);\n\n // Test case 2: Only log type bits set\n let packed = to_expanded_metadata(U64_MAX, 0);\n let (msg_type, msg_metadata) = from_expanded_metadata(packed);\n assert_eq(msg_type, U64_MAX);\n assert_eq(msg_metadata, 0);\n\n // Test case 3: Only msg_metadata bits set\n let packed = to_expanded_metadata(0, U64_MAX);\n let (msg_type, msg_metadata) = from_expanded_metadata(packed);\n assert_eq(msg_type, 0);\n assert_eq(msg_metadata, U64_MAX);\n\n // Test case 4: No bits set\n let packed = to_expanded_metadata(0, 0);\n let (msg_type, msg_metadata) = from_expanded_metadata(packed);\n assert_eq(msg_type, 0);\n assert_eq(msg_metadata, 0);\n }\n\n #[test]\n unconstrained fn from_expanded_metadata_packing() {\n // Test case 1: All bits set\n let input = U128_MAX as Field;\n let (msg_type, msg_metadata) = from_expanded_metadata(input);\n assert_eq(msg_type, U64_MAX);\n assert_eq(msg_metadata, U64_MAX);\n\n // Test case 2: Only log type bits set\n let input = (U128_MAX - U64_MAX as Field);\n let (msg_type, msg_metadata) = from_expanded_metadata(input);\n assert_eq(msg_type, U64_MAX);\n assert_eq(msg_metadata, 0);\n\n // Test case 3: Only msg_metadata bits set\n let input = U64_MAX as Field;\n let (msg_type, msg_metadata) = from_expanded_metadata(input);\n assert_eq(msg_type, 0);\n assert_eq(msg_metadata, U64_MAX);\n\n // Test case 4: No bits set\n let input = 0;\n let (msg_type, msg_metadata) = from_expanded_metadata(input);\n assert_eq(msg_type, 0);\n assert_eq(msg_metadata, 0);\n }\n\n #[test]\n unconstrained fn to_from_expanded_metadata(original_msg_type: u64, original_msg_metadata: u64) {\n let packed = to_expanded_metadata(original_msg_type, original_msg_metadata);\n let (unpacked_msg_type, unpacked_msg_metadata) = from_expanded_metadata(packed);\n\n assert_eq(original_msg_type, unpacked_msg_type);\n assert_eq(original_msg_metadata, unpacked_msg_metadata);\n }\n\n #[test]\n unconstrained fn encode_decode_max_size_message() {\n let msg_type_id: u64 = 42;\n let msg_metadata: u64 = 99;\n let mut msg_content = [0; MAX_MESSAGE_CONTENT_LEN];\n for i in 0..MAX_MESSAGE_CONTENT_LEN {\n msg_content[i] = i as Field;\n }\n\n let encoded = encode_message(msg_type_id, msg_metadata, msg_content);\n let (decoded_type_id, decoded_metadata, decoded_content) = decode_message(BoundedVec::from_array(encoded));\n\n assert_eq(decoded_type_id, msg_type_id);\n assert_eq(decoded_metadata, msg_metadata);\n assert_eq(decoded_content, BoundedVec::from_array(msg_content));\n }\n\n #[test(should_fail_with = \"Invalid message content: it must have a length of at most MAX_MESSAGE_CONTENT_LEN\")]\n fn encode_oversized_message_fails() {\n let msg_content = [0; MAX_MESSAGE_CONTENT_LEN + 1];\n let _ = encode_message(0, 0, msg_content);\n }\n}\n"
|
|
4306
4306
|
},
|
|
4307
4307
|
"132": {
|
|
4308
4308
|
"path": "/home/aztec-dev/aztec-packages/noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr",
|
|
4309
|
-
"source": "use crate::protocol::{\n address::AztecAddress,\n constants::{DOM_SEP__SYMMETRIC_KEY, DOM_SEP__SYMMETRIC_KEY_2},\n hash::poseidon2_hash_with_separator,\n point::Point,\n public_keys::AddressPoint,\n};\n\nuse crate::{\n keys::{ecdh_shared_secret::derive_ecdh_shared_secret, ephemeral::generate_ephemeral_key_pair},\n messages::{\n encoding::{\n EPH_PK_SIGN_BYTE_SIZE_IN_BYTES, EPH_PK_X_SIZE_IN_FIELDS, HEADER_CIPHERTEXT_SIZE_IN_BYTES,\n MESSAGE_CIPHERTEXT_LEN, MESSAGE_PLAINTEXT_LEN,\n },\n encryption::message_encryption::MessageEncryption,\n logs::arithmetic_generics_utils::{\n get_arr_of_size__message_bytes__from_PT, get_arr_of_size__message_bytes_padding__from_PT,\n },\n },\n oracle::{aes128_decrypt::aes128_decrypt_oracle, random::random, shared_secret::get_shared_secret},\n utils::{\n array,\n conversion::{\n bytes_to_fields::{bytes_from_fields, bytes_to_fields},\n fields_to_bytes::{fields_from_bytes, fields_to_bytes},\n },\n point::{get_sign_of_point, point_from_x_coord_and_sign},\n random::get_random_bytes,\n },\n};\n\nuse std::aes128::aes128_encrypt;\n\n/// Computes N close-to-uniformly-random 256 bits from a given ECDH shared_secret.\n///\n/// NEVER re-use the same iv and sym_key. DO NOT call this function more than once with the same shared_secret.\n///\n/// This function is only known to be safe if shared_secret is computed by combining a random ephemeral key with an\n/// address point. See big comment within the body of the function. See big comment within the body of the function.\nfn extract_many_close_to_uniformly_random_256_bits_from_ecdh_shared_secret_using_poseidon2_unsafe<let N: u32>(\n shared_secret: Point,\n) -> [[u8; 32]; N] {\n /*\n * Unsafe because of https://eprint.iacr.org/2010/264.pdf Page 13, Lemma 2 (and the * two\n paragraphs below it).\n *\n * If you call this function, you need to be careful and aware of how the arg\n * `shared_secret` has been derived.\n *\n * The paper says that the way you derive aes keys and IVs should be fine with poseidon2\n * (modelled as a RO), as long as you _don't_ use Poseidon2 as a PRG to generate the * two\n exponents x & y which multiply to the shared secret S:\n *\n * S = [x*y]*G.\n *\n * (Otherwise, you would have to \"key\" poseidon2, i.e. generate a uniformly string K\n * which can be public and compute Hash(x) as poseidon(K,x)).\n * In that lemma, k would be 2*254=508, and m would be the number of points on the * grumpkin\n curve (which is close to r according to the Hasse bound).\n *\n * Our shared secret S is [esk * address_sk] * G, and the question is: * Can we compute hash(S)\n using poseidon2 instead of sha256?\n *\n * Well, esk is random and not generated with poseidon2, so that's good.\n * What about address_sk?\n * Well, address_sk = poseidon2(stuff) + ivsk, so there was some\n * discussion about whether address_sk is independent of poseidon2.\n * Given that ivsk is random and independent of poseidon2, the address_sk is also\n * independent of poseidon2.\n *\n * Tl;dr: we believe it's safe to hash S = [esk * address_sk] * G using poseidon2,\n * in order to derive a symmetric key.\n *\n * If you're calling this function for a differently-derived `shared_secret`, be\n * careful.\n *\n */\n \n\n /* The output of this function needs to be 32 random bytes.\n * A single field won't give us 32 bytes of entropy.\n * So we compute two \"random\" fields, by poseidon-hashing with two different\n * generators.\n * We then extract the last 16 (big endian) bytes of each \"random\" field.\n * Note: we use to_be_bytes because it's slightly more efficient. But we have to\n * be careful not to take bytes from the \"big end\", because the \"big\" byte is\n * not uniformly random over the byte: it only has < 6 bits of randomness, because\n * it's the big end of a 254-bit field element.\n */\n\n let mut all_bytes: [[u8; 32]; N] = std::mem::zeroed();\n // We restrict N to be < 2^8, because of how we compute the domain separator from k below (where k <= N must be 8\n // bits). In practice, it's extremely unlikely that an app will want to compute >= 256 ciphertexts.\n std::static_assert(N < 256, \"N too large\");\n for k in 0..N {\n // We augment the domain separator with the loop index, so that we can generate N lots of randomness.\n let k_shift = (k << 8);\n let separator_1 = k_shift + DOM_SEP__SYMMETRIC_KEY;\n let separator_2 = k_shift + DOM_SEP__SYMMETRIC_KEY_2;\n\n let rand1: Field = poseidon2_hash_with_separator([shared_secret.x, shared_secret.y], separator_1);\n let rand2: Field = poseidon2_hash_with_separator([shared_secret.x, shared_secret.y], separator_2);\n\n let rand1_bytes: [u8; 32] = rand1.to_be_bytes();\n let rand2_bytes: [u8; 32] = rand2.to_be_bytes();\n\n let mut bytes: [u8; 32] = [0; 32];\n for i in 0..16 {\n // We take bytes from the \"little end\" of the be-bytes arrays:\n let j = 32 - i - 1;\n bytes[i] = rand1_bytes[j];\n bytes[16 + i] = rand2_bytes[j];\n }\n\n all_bytes[k] = bytes;\n }\n\n all_bytes\n}\n\nfn derive_aes_symmetric_key_and_iv_from_uniformly_random_256_bits<let N: u32>(\n many_random_256_bits: [[u8; 32]; N],\n) -> [([u8; 16], [u8; 16]); N] {\n // Many (sym_key, iv) pairs:\n let mut many_pairs: [([u8; 16], [u8; 16]); N] = std::mem::zeroed();\n for k in 0..N {\n let random_256_bits = many_random_256_bits[k];\n let mut sym_key = [0; 16];\n let mut iv = [0; 16];\n for i in 0..16 {\n sym_key[i] = random_256_bits[i];\n iv[i] = random_256_bits[i + 16];\n }\n many_pairs[k] = (sym_key, iv);\n }\n\n many_pairs\n}\n\npub fn derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe<let N: u32>(\n shared_secret: Point,\n) -> [([u8; 16], [u8; 16]); N] {\n let many_random_256_bits: [[u8; 32]; N] =\n extract_many_close_to_uniformly_random_256_bits_from_ecdh_shared_secret_using_poseidon2_unsafe(shared_secret);\n\n derive_aes_symmetric_key_and_iv_from_uniformly_random_256_bits(many_random_256_bits)\n}\n\npub struct AES128 {}\n\nimpl MessageEncryption for AES128 {\n fn encrypt<let PlaintextLen: u32>(\n plaintext: [Field; PlaintextLen],\n recipient: AztecAddress,\n ) -> [Field; MESSAGE_CIPHERTEXT_LEN] {\n // AES 128 operates on bytes, not fields, so we need to convert the fields to bytes. (This process is then\n // reversed when processing the message in `process_message_ciphertext`)\n let plaintext_bytes = fields_to_bytes(plaintext);\n\n // ***************************************************************************** Compute the shared secret\n // *****************************************************************************\n\n let (eph_sk, eph_pk) = generate_ephemeral_key_pair();\n\n let eph_pk_sign_byte: u8 = get_sign_of_point(eph_pk) as u8;\n\n // (not to be confused with the tagging shared secret) TODO (#17158): Currently we unwrap the Option returned\n // by derive_ecdh_shared_secret. We need to handle the case where the ephemeral public key is invalid to\n // prevent potential DoS vectors.\n let ciphertext_shared_secret = derive_ecdh_shared_secret(\n eph_sk,\n recipient\n .to_address_point()\n .unwrap_or(\n // Safety: if the recipient is an invalid address, then it is not possible to encrypt a message for\n // them because we cannot establish a shared secret. This is never expected to occur during normal\n // operation. However, it is technically possible for us to receive an invalid address, and we must\n // therefore handle it. We could simply fail, but that'd introduce a potential security issue in\n // which an attacker forces a contract to encrypt a message for an invalid address, resulting in an\n // impossible transaction - this is sometimes called a 'king of the hill' attack. We choose instead\n // to not fail and encrypt the plaintext regardless using the shared secret that results from a\n // random valid address. The sender is free to choose this address and hence shared secret, but\n // this has no security implications as they already know not only the full plaintext but also the\n // ephemeral private key anyway.\n unsafe { random_address_point() },\n )\n .inner,\n );\n // TODO: also use this shared secret for deriving note randomness.\n\n // ***************************************************************************** Convert the plaintext into\n // whatever format the encryption function expects\n // *****************************************************************************\n\n // Already done for this strategy: AES expects bytes.\n\n // ***************************************************************************** Encrypt the plaintext\n // *****************************************************************************\n\n // It is safe to call the `unsafe` function here, because we know the `shared_secret` was derived using an\n // AztecAddress (the recipient). See the block comment at the start of this unsafe target function for more\n // info.\n let pairs = derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe::<2>(\n ciphertext_shared_secret,\n );\n let (body_sym_key, body_iv) = pairs[0];\n let (header_sym_key, header_iv) = pairs[1];\n\n let ciphertext_bytes = aes128_encrypt(plaintext_bytes, body_iv, body_sym_key);\n\n // |full_pt| = |pt_length| + |pt|\n // |pt_aes_padding| = 16 - (|full_pt| % 16)\n // or... since a % b is the same as a - b * (a // b) (integer division), so:\n // |pt_aes_padding| = 16 - (|full_pt| - 16 * (|full_pt| // 16))\n // |ct| = |full_pt| + |pt_aes_padding|\n // = |full_pt| + 16 - (|full_pt| - 16 * (|full_pt| // 16)) = 16 + 16 * (|full_pt| // 16) = 16 * (1 +\n // |full_pt| // 16)\n std::static_assert(\n ciphertext_bytes.len() == 16 * (1 + (PlaintextLen * 32) / 16),\n \"unexpected ciphertext length\",\n );\n\n // ***************************************************************************** Compute the header ciphertext\n // *****************************************************************************\n\n // Header contains only the length of the ciphertext stored in 2 bytes.\n let mut header_plaintext: [u8; 2] = [0 as u8; 2];\n let ciphertext_bytes_length = ciphertext_bytes.len();\n header_plaintext[0] = (ciphertext_bytes_length >> 8) as u8;\n header_plaintext[1] = ciphertext_bytes_length as u8;\n\n // Note: the aes128_encrypt builtin fn automatically appends bytes to the input, according to pkcs#7; hence why\n // the output `header_ciphertext_bytes` is 16 bytes larger than the input in this case.\n let header_ciphertext_bytes = aes128_encrypt(header_plaintext, header_iv, header_sym_key);\n // I recall that converting a slice to an array incurs constraints, so I'll check the length this way instead:\n std::static_assert(\n header_ciphertext_bytes.len() == HEADER_CIPHERTEXT_SIZE_IN_BYTES,\n \"unexpected ciphertext header length\",\n );\n\n // ***************************************************************************** Prepend / append more bytes of\n // data to the ciphertext, before converting back to fields.\n // *****************************************************************************\n\n let mut message_bytes_padding_to_mult_31 =\n get_arr_of_size__message_bytes_padding__from_PT::<PlaintextLen * 32>();\n // Safety: this randomness won't be constrained to be random. It's in the interest of the executor of this fn\n // to encrypt with random bytes.\n message_bytes_padding_to_mult_31 = unsafe { get_random_bytes() };\n\n let mut message_bytes = get_arr_of_size__message_bytes__from_PT::<PlaintextLen * 32>();\n\n std::static_assert(\n message_bytes.len() % 31 == 0,\n \"Unexpected error: message_bytes.len() should be divisible by 31, by construction.\",\n );\n\n message_bytes[0] = eph_pk_sign_byte;\n let mut offset = 1;\n for i in 0..header_ciphertext_bytes.len() {\n message_bytes[offset + i] = header_ciphertext_bytes[i];\n }\n offset += header_ciphertext_bytes.len();\n\n for i in 0..ciphertext_bytes.len() {\n message_bytes[offset + i] = ciphertext_bytes[i];\n }\n offset += ciphertext_bytes.len();\n\n for i in 0..message_bytes_padding_to_mult_31.len() {\n message_bytes[offset + i] = message_bytes_padding_to_mult_31[i];\n }\n offset += message_bytes_padding_to_mult_31.len();\n\n // Ideally we would be able to have a static assert where we check that the offset would be such that we've\n // written to the entire log_bytes array, but we cannot since Noir does not treat the offset as a comptime\n // value (despite the values that it goes through being known at each stage). We instead check that the\n // computation used to obtain the offset computes the expected value (which we _can_ do in a static check), and\n // then add a cheap runtime check to also validate that the offset matches this.\n std::static_assert(\n 1 + header_ciphertext_bytes.len() + ciphertext_bytes.len() + message_bytes_padding_to_mult_31.len()\n == message_bytes.len(),\n \"unexpected message length\",\n );\n assert(offset == message_bytes.len(), \"unexpected encrypted message length\");\n\n // ***************************************************************************** Convert bytes back to fields\n // *****************************************************************************\n\n // TODO(#12749): As Mike pointed out, we need to make messages produced by different encryption schemes\n // indistinguishable from each other and for this reason the output here and in the last for-loop of this\n // function should cover a full field.\n let message_bytes_as_fields = bytes_to_fields(message_bytes);\n\n // ***************************************************************************** Prepend / append fields, to\n // create the final message *****************************************************************************\n\n let mut ciphertext: [Field; MESSAGE_CIPHERTEXT_LEN] = [0; MESSAGE_CIPHERTEXT_LEN];\n\n ciphertext[0] = eph_pk.x;\n\n let mut offset = 1;\n for i in 0..message_bytes_as_fields.len() {\n ciphertext[offset + i] = message_bytes_as_fields[i];\n }\n offset += message_bytes_as_fields.len();\n\n for i in offset..MESSAGE_CIPHERTEXT_LEN {\n // We need to get a random value that fits in 31 bytes to not leak information about the size of the\n // message (all the \"real\" message fields contain at most 31 bytes because of the way we convert the bytes\n // to fields). TODO(#12749): Long term, this is not a good solution.\n\n // Safety: we assume that the sender wants for the message to be private - a malicious one could simply\n // reveal its contents publicly. It is therefore fine to trust the sender to provide random padding.\n let field_bytes = unsafe { get_random_bytes::<31>() };\n ciphertext[i] = Field::from_be_bytes::<31>(field_bytes);\n }\n\n ciphertext\n }\n\n unconstrained fn decrypt(\n ciphertext: BoundedVec<Field, MESSAGE_CIPHERTEXT_LEN>,\n recipient: AztecAddress,\n ) -> Option<BoundedVec<Field, MESSAGE_PLAINTEXT_LEN>> {\n let eph_pk_x = ciphertext.get(0);\n\n let ciphertext_without_eph_pk_x_fields = array::subbvec::<Field, MESSAGE_CIPHERTEXT_LEN, MESSAGE_CIPHERTEXT_LEN - EPH_PK_X_SIZE_IN_FIELDS>(\n ciphertext,\n EPH_PK_X_SIZE_IN_FIELDS,\n );\n\n // Convert the ciphertext represented as fields to a byte representation (its original format)\n let ciphertext_without_eph_pk_x = bytes_from_fields(ciphertext_without_eph_pk_x_fields);\n\n // First byte of the ciphertext represents the ephemeral public key sign\n let eph_pk_sign_bool = ciphertext_without_eph_pk_x.get(0) != 0;\n\n // With the sign and the x-coordinate of the ephemeral public key, we can reconstruct the point. This may fail\n // however, as not all x-coordinates are on the curve. In that case, we simply return `Option::none`.\n point_from_x_coord_and_sign(eph_pk_x, eph_pk_sign_bool).map(|eph_pk| {\n // Derive shared secret\n let ciphertext_shared_secret = get_shared_secret(recipient, eph_pk);\n\n // Derive symmetric keys:\n let pairs = derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe::<2>(\n ciphertext_shared_secret,\n );\n let (body_sym_key, body_iv) = pairs[0];\n let (header_sym_key, header_iv) = pairs[1];\n\n // Extract the header ciphertext\n let header_start = EPH_PK_SIGN_BYTE_SIZE_IN_BYTES; // Skip eph_pk_sign byte\n let header_ciphertext: [u8; HEADER_CIPHERTEXT_SIZE_IN_BYTES] =\n array::subarray(ciphertext_without_eph_pk_x.storage(), header_start);\n // We need to convert the array to a BoundedVec because the oracle expects a BoundedVec as it's designed to\n // work with messages with unknown length at compile time. This would not be necessary here as the header\n // ciphertext length is fixed. But we do it anyway to not have to have duplicate oracles.\n let header_ciphertext_bvec =\n BoundedVec::<u8, HEADER_CIPHERTEXT_SIZE_IN_BYTES>::from_array(header_ciphertext);\n\n // Decrypt header\n let header_plaintext = aes128_decrypt_oracle(header_ciphertext_bvec, header_iv, header_sym_key);\n\n // Extract ciphertext length from header (2 bytes, big-endian)\n let ciphertext_length = ((header_plaintext.get(0) as u32) << 8) | (header_plaintext.get(1) as u32);\n\n // Extract and decrypt main ciphertext\n let ciphertext_start = header_start + HEADER_CIPHERTEXT_SIZE_IN_BYTES;\n 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] =\n array::subarray(ciphertext_without_eph_pk_x.storage(), ciphertext_start);\n let ciphertext: BoundedVec<u8, (MESSAGE_CIPHERTEXT_LEN - EPH_PK_X_SIZE_IN_FIELDS) * 31 - HEADER_CIPHERTEXT_SIZE_IN_BYTES - EPH_PK_SIGN_BYTE_SIZE_IN_BYTES> =\n BoundedVec::from_parts(ciphertext_with_padding, ciphertext_length);\n\n // Decrypt main ciphertext and return it\n let plaintext_bytes = aes128_decrypt_oracle(ciphertext, body_iv, body_sym_key);\n\n // Each field of the original note message was serialized to 32 bytes so we convert the bytes back to\n // fields.\n fields_from_bytes(plaintext_bytes)\n })\n }\n}\n\n/// Produces a random valid address point, i.e. one that is on the curve. This is equivalent to calling\n/// [`AztecAddress::to_address_point`] on a random valid address.\nunconstrained fn random_address_point() -> AddressPoint {\n let mut result = std::mem::zeroed();\n\n loop {\n // We simply produce random x coordinates until we find one that is on the curve. About half of the x\n // coordinates fulfill this condition, so this should only take a few iterations at most.\n let x_coord = random();\n let point = point_from_x_coord_and_sign(x_coord, true);\n if point.is_some() {\n result = AddressPoint { inner: point.unwrap() };\n break;\n }\n }\n\n result\n}\n\nmod test {\n use crate::{\n keys::ecdh_shared_secret::derive_ecdh_shared_secret,\n messages::{encoding::MESSAGE_PLAINTEXT_LEN, encryption::message_encryption::MessageEncryption},\n test::helpers::test_environment::TestEnvironment,\n };\n use crate::protocol::{address::AztecAddress, traits::FromField};\n use super::{AES128, random_address_point};\n use std::{embedded_curve_ops::EmbeddedCurveScalar, test::OracleMock};\n\n #[test]\n unconstrained fn encrypt_decrypt_deterministic() {\n let env = TestEnvironment::new();\n\n // Message decryption requires oracles that are only available during private execution\n env.private_context(|_| {\n let plaintext = [1, 2, 3];\n\n let recipient = AztecAddress::from_field(\n 0x25afb798ea6d0b8c1618e50fdeafa463059415013d3b7c75d46abf5e242be70c,\n );\n\n // Mock random values for deterministic test\n let eph_sk = 0x1358d15019d4639393d62b97e1588c095957ce74a1c32d6ec7d62fe6705d9538;\n let _ = OracleMock::mock(\"utilityGetRandomField\").returns(eph_sk).times(1);\n\n let randomness = 0x0101010101010101010101010101010101010101010101010101010101010101;\n let _ = OracleMock::mock(\"utilityGetRandomField\").returns(randomness).times(1000000);\n\n let _ = OracleMock::mock(\"privateGetNextAppTagAsSender\").returns(42);\n\n // Encrypt the message\n let encrypted_message = BoundedVec::from_array(AES128::encrypt(plaintext, recipient));\n\n // Mock shared secret for deterministic test\n let shared_secret = derive_ecdh_shared_secret(\n EmbeddedCurveScalar::from_field(eph_sk),\n recipient.to_address_point().unwrap().inner,\n );\n\n let _ = OracleMock::mock(\"utilityGetSharedSecret\").returns(shared_secret);\n\n // Decrypt the message\n let decrypted = AES128::decrypt(encrypted_message, recipient).unwrap();\n\n // The decryption function spits out a BoundedVec because it's designed to work with messages with unknown\n // length at compile time. For this reason we need to convert the original input to a BoundedVec.\n let plaintext_bvec = BoundedVec::<Field, MESSAGE_PLAINTEXT_LEN>::from_array(plaintext);\n\n // Verify decryption matches original plaintext\n assert_eq(decrypted, plaintext_bvec, \"Decrypted bytes should match original plaintext\");\n\n // The following is a workaround of \"struct is never constructed\" Noir compilation error (we only ever use\n // static methods of the struct).\n let _ = AES128 {};\n });\n }\n\n #[test]\n unconstrained fn encrypt_decrypt_random() {\n // Same as `encrypt_decrypt_deterministic`, except we don't mock any of the oracles and rely on\n // `TestEnvironment` instead.\n let mut env = TestEnvironment::new();\n\n let recipient = env.create_light_account();\n\n env.private_context(|_| {\n let plaintext = [1, 2, 3];\n let ciphertext = AES128::encrypt(plaintext, recipient);\n\n assert_eq(\n AES128::decrypt(BoundedVec::from_array(ciphertext), recipient).unwrap(),\n BoundedVec::from_array(plaintext),\n );\n });\n }\n\n #[test]\n unconstrained fn encrypt_to_invalid_address() {\n // x = 3 is a non-residue for this curve, resulting in an invalid address\n let invalid_address = AztecAddress { inner: 3 };\n\n // We just test that we produced some output and did not crash - the result is gibberish as it is encrypted\n // using a public key for which we do not know the private key.\n let _ = AES128::encrypt([1, 2, 3, 4], invalid_address);\n }\n\n #[test]\n unconstrained fn random_address_point_produces_valid_points() {\n // About half of random addresses are invalid, so testing just a couple gives us high confidence that\n // `random_address_point` is indeed producing valid addresses.\n for _ in 0..10 {\n let random_address = AztecAddress { inner: random_address_point().inner.x };\n assert(random_address.to_address_point().is_some());\n }\n }\n\n #[test]\n unconstrained fn decrypt_invalid_ephemeral_public_key() {\n let mut env = TestEnvironment::new();\n\n let recipient = env.create_light_account();\n\n env.private_context(|_| {\n let plaintext = [1, 2, 3, 4];\n let ciphertext = AES128::encrypt(plaintext, recipient);\n\n // The first field of the ciphertext is the x-coordinate of the ephemeral public key. We set it to a known\n // non-residue (3), causing `decrypt` to fail to produce a decryption shared secret.\n let mut bad_ciphertext = BoundedVec::from_array(ciphertext);\n bad_ciphertext.set(0, 3);\n\n assert(AES128::decrypt(bad_ciphertext, recipient).is_none());\n });\n }\n}\n"
|
|
4309
|
+
"source": "use crate::protocol::{\n address::AztecAddress,\n constants::{DOM_SEP__SYMMETRIC_KEY, DOM_SEP__SYMMETRIC_KEY_2},\n hash::poseidon2_hash_with_separator,\n point::Point,\n public_keys::AddressPoint,\n};\n\nuse crate::{\n keys::{ecdh_shared_secret::derive_ecdh_shared_secret, ephemeral::generate_positive_ephemeral_key_pair},\n messages::{\n encoding::{\n EPH_PK_X_SIZE_IN_FIELDS, HEADER_CIPHERTEXT_SIZE_IN_BYTES, MESSAGE_CIPHERTEXT_LEN, MESSAGE_PLAINTEXT_LEN,\n MESSAGE_PLAINTEXT_SIZE_IN_BYTES,\n },\n encryption::message_encryption::MessageEncryption,\n logs::arithmetic_generics_utils::{\n get_arr_of_size__message_bytes__from_PT, get_arr_of_size__message_bytes_padding__from_PT,\n },\n },\n oracle::{aes128_decrypt::aes128_decrypt_oracle, random::random, shared_secret::get_shared_secret},\n utils::{\n array,\n conversion::{\n bytes_to_fields::{bytes_from_fields, bytes_to_fields},\n fields_to_bytes::{fields_from_bytes, fields_to_bytes},\n },\n point::point_from_x_coord_and_sign,\n random::get_random_bytes,\n },\n};\n\nuse std::aes128::aes128_encrypt;\n\n/// Computes N close-to-uniformly-random 256 bits from a given ECDH shared_secret.\n///\n/// NEVER re-use the same iv and sym_key. DO NOT call this function more than once with the same shared_secret.\n///\n/// This function is only known to be safe if shared_secret is computed by combining a random ephemeral key with an\n/// address point. See big comment within the body of the function. See big comment within the body of the function.\nfn extract_many_close_to_uniformly_random_256_bits_from_ecdh_shared_secret_using_poseidon2_unsafe<let N: u32>(\n shared_secret: Point,\n) -> [[u8; 32]; N] {\n /*\n * Unsafe because of https://eprint.iacr.org/2010/264.pdf Page 13, Lemma 2 (and the * two\n paragraphs below it).\n *\n * If you call this function, you need to be careful and aware of how the arg\n * `shared_secret` has been derived.\n *\n * The paper says that the way you derive aes keys and IVs should be fine with poseidon2\n * (modelled as a RO), as long as you _don't_ use Poseidon2 as a PRG to generate the * two\n exponents x & y which multiply to the shared secret S:\n *\n * S = [x*y]*G.\n *\n * (Otherwise, you would have to \"key\" poseidon2, i.e. generate a uniformly string K\n * which can be public and compute Hash(x) as poseidon(K,x)).\n * In that lemma, k would be 2*254=508, and m would be the number of points on the * grumpkin\n curve (which is close to r according to the Hasse bound).\n *\n * Our shared secret S is [esk * address_sk] * G, and the question is: * Can we compute hash(S)\n using poseidon2 instead of sha256?\n *\n * Well, esk is random and not generated with poseidon2, so that's good.\n * What about address_sk?\n * Well, address_sk = poseidon2(stuff) + ivsk, so there was some\n * discussion about whether address_sk is independent of poseidon2.\n * Given that ivsk is random and independent of poseidon2, the address_sk is also\n * independent of poseidon2.\n *\n * Tl;dr: we believe it's safe to hash S = [esk * address_sk] * G using poseidon2,\n * in order to derive a symmetric key.\n *\n * If you're calling this function for a differently-derived `shared_secret`, be\n * careful.\n *\n */\n \n\n /* The output of this function needs to be 32 random bytes.\n * A single field won't give us 32 bytes of entropy.\n * So we compute two \"random\" fields, by poseidon-hashing with two different\n * generators.\n * We then extract the last 16 (big endian) bytes of each \"random\" field.\n * Note: we use to_be_bytes because it's slightly more efficient. But we have to\n * be careful not to take bytes from the \"big end\", because the \"big\" byte is\n * not uniformly random over the byte: it only has < 6 bits of randomness, because\n * it's the big end of a 254-bit field element.\n */\n\n let mut all_bytes: [[u8; 32]; N] = std::mem::zeroed();\n // We restrict N to be < 2^8, because of how we compute the domain separator from k below (where k <= N must be 8\n // bits). In practice, it's extremely unlikely that an app will want to compute >= 256 ciphertexts.\n std::static_assert(N < 256, \"N too large\");\n for k in 0..N {\n // We augment the domain separator with the loop index, so that we can generate N lots of randomness.\n let k_shift = (k << 8);\n let separator_1 = k_shift + DOM_SEP__SYMMETRIC_KEY;\n let separator_2 = k_shift + DOM_SEP__SYMMETRIC_KEY_2;\n\n let rand1: Field = poseidon2_hash_with_separator([shared_secret.x, shared_secret.y], separator_1);\n let rand2: Field = poseidon2_hash_with_separator([shared_secret.x, shared_secret.y], separator_2);\n\n let rand1_bytes: [u8; 32] = rand1.to_be_bytes();\n let rand2_bytes: [u8; 32] = rand2.to_be_bytes();\n\n let mut bytes: [u8; 32] = [0; 32];\n for i in 0..16 {\n // We take bytes from the \"little end\" of the be-bytes arrays:\n let j = 32 - i - 1;\n bytes[i] = rand1_bytes[j];\n bytes[16 + i] = rand2_bytes[j];\n }\n\n all_bytes[k] = bytes;\n }\n\n all_bytes\n}\n\nfn derive_aes_symmetric_key_and_iv_from_uniformly_random_256_bits<let N: u32>(\n many_random_256_bits: [[u8; 32]; N],\n) -> [([u8; 16], [u8; 16]); N] {\n // Many (sym_key, iv) pairs:\n let mut many_pairs: [([u8; 16], [u8; 16]); N] = std::mem::zeroed();\n for k in 0..N {\n let random_256_bits = many_random_256_bits[k];\n let mut sym_key = [0; 16];\n let mut iv = [0; 16];\n for i in 0..16 {\n sym_key[i] = random_256_bits[i];\n iv[i] = random_256_bits[i + 16];\n }\n many_pairs[k] = (sym_key, iv);\n }\n\n many_pairs\n}\n\npub fn derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe<let N: u32>(\n shared_secret: Point,\n) -> [([u8; 16], [u8; 16]); N] {\n let many_random_256_bits: [[u8; 32]; N] =\n extract_many_close_to_uniformly_random_256_bits_from_ecdh_shared_secret_using_poseidon2_unsafe(shared_secret);\n\n derive_aes_symmetric_key_and_iv_from_uniformly_random_256_bits(many_random_256_bits)\n}\n\npub struct AES128 {}\n\nimpl MessageEncryption for AES128 {\n\n /// AES128-CBC encryption for Aztec protocol messages.\n ///\n /// ## Overview\n ///\n /// The plaintext is an array of up to `MESSAGE_PLAINTEXT_LEN` (12) fields. The output is always exactly\n /// `MESSAGE_CIPHERTEXT_LEN` (15) fields, regardless of plaintext size. Unused trailing fields are filled with\n /// random data so that all encrypted messages are indistinguishable by size.\n ///\n /// ## PKCS#7 Padding\n ///\n /// AES operates on 16-byte blocks, so the plaintext must be padded to a multiple of 16. PKCS#7 padding always\n /// adds at least 1 byte (so the receiver can always detect and strip it), which means:\n /// - 1 B plaintext -> 15 B padding -> 16 B total\n /// - 15 B plaintext -> 1 B padding -> 16 B total\n /// - 16 B plaintext -> 16 B padding -> 32 B total (full extra block)\n ///\n /// In general: if the plaintext is already a multiple of 16, a full 16-byte padding block is appended.\n ///\n /// ## Encryption Steps\n ///\n /// **1. Body encryption.** The plaintext fields are serialized to bytes (32 bytes per field) and AES-128-CBC\n /// encrypted. Since 32 is a multiple of 16, PKCS#7 always adds a full 16-byte padding block (see above):\n ///\n /// ```text\n /// +---------------------------------------------+\n /// | body ct |\n /// | PlaintextLen*32 + 16 B |\n /// +-------------------------------+--------------+\n /// | encrypted plaintext fields | PKCS#7 (16B) |\n /// | (serialized at 32 B each) | |\n /// +-------------------------------+--------------+\n /// ```\n ///\n /// **2. Header encryption.** The byte length of `body_ct` is stored as a 2-byte big-endian integer. This 2-byte\n /// header plaintext is then AES-encrypted; PKCS#7 pads the remaining 14 bytes to fill one 16-byte AES block,\n /// producing a 16-byte header ciphertext:\n ///\n /// ```text\n /// +---------------------------+\n /// | header ct |\n /// | 16 B |\n /// +--------+------------------+\n /// | body ct| PKCS#7 (14B) |\n /// | length | |\n /// | (2 B) | |\n /// +--------+------------------+\n /// ```\n ///\n /// ## Wire Format\n ///\n /// Messages are transmitted as fields, not bytes. A field is ~254 bits and can safely store 31 whole bytes, so\n /// we need to pack our byte data into 31-byte chunks. This packing drives the wire format.\n ///\n /// **Step 1 -- Assemble bytes.** The ciphertexts are laid out in a byte array, padded with random bytes to a\n /// multiple of 31 so it divides evenly into fields:\n ///\n /// ```text\n /// +------------+-------------------------+---------+\n /// | header ct | body ct | byte pad|\n /// | 16 B | PlaintextLen*32 + 16 B | (random)|\n /// +------------+-------------------------+---------+\n /// |<-------- padded to a multiple of 31 B -------->|\n /// ```\n ///\n /// **Step 2 -- Pack into fields.** The byte array is split into 31-byte chunks, each stored in one field. The\n /// ephemeral public key x-coordinate is prepended as its own field. Any remaining fields (up to 15 total) are\n /// filled with random data so that all messages are the same size:\n ///\n /// ```text\n /// +----------+-------------------------+-------------------+\n /// | eph_pk.x | message-byte fields | random field pad |\n /// | | (packed 31 B per field) | (fills to 15) |\n /// +----------+-------------------------+-------------------+\n /// |<---------- MESSAGE_CIPHERTEXT_LEN = 15 fields ------->|\n /// ```\n ///\n /// ## Key Derivation\n ///\n /// Two (key, IV) pairs are derived from the ECDH shared secret via Poseidon2 hashing with different domain\n /// separators: one pair for the body ciphertext and one for the header ciphertext.\n fn encrypt<let PlaintextLen: u32>(\n plaintext: [Field; PlaintextLen],\n recipient: AztecAddress,\n ) -> [Field; MESSAGE_CIPHERTEXT_LEN] {\n std::static_assert(\n PlaintextLen <= MESSAGE_PLAINTEXT_LEN,\n \"Plaintext length exceeds MESSAGE_PLAINTEXT_LEN\",\n );\n\n // AES 128 operates on bytes, not fields, so we need to convert the fields to bytes. (This process is then\n // reversed when processing the message in `process_message_ciphertext`)\n let plaintext_bytes = fields_to_bytes(plaintext);\n\n // Derive ECDH shared secret with recipient using a fresh ephemeral keypair.\n let (eph_sk, eph_pk) = generate_positive_ephemeral_key_pair();\n\n let ciphertext_shared_secret = derive_ecdh_shared_secret(\n eph_sk,\n recipient\n .to_address_point()\n .unwrap_or(\n // Safety: if the recipient is an invalid address, then it is not possible to encrypt a message for\n // them because we cannot establish a shared secret. This is never expected to occur during normal\n // operation. However, it is technically possible for us to receive an invalid address, and we must\n // therefore handle it. We could simply fail, but that'd introduce a potential security issue in\n // which an attacker forces a contract to encrypt a message for an invalid address, resulting in an\n // impossible transaction - this is sometimes called a 'king of the hill' attack. We choose instead\n // to not fail and encrypt the plaintext regardless using the shared secret that results from a\n // random valid address. The sender is free to choose this address and hence shared secret, but\n // this has no security implications as they already know not only the full plaintext but also the\n // ephemeral private key anyway.\n unsafe { random_address_point() },\n )\n .inner,\n );\n\n // AES128-CBC encrypt the plaintext bytes.\n // It is safe to call the `unsafe` function here, because we know the `shared_secret` was derived using an\n // AztecAddress (the recipient). See the block comment at the start of this unsafe target function for more\n // info.\n let pairs = derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe::<2>(\n ciphertext_shared_secret,\n );\n let (body_sym_key, body_iv) = pairs[0];\n let (header_sym_key, header_iv) = pairs[1];\n\n let ciphertext_bytes = aes128_encrypt(plaintext_bytes, body_iv, body_sym_key);\n\n // Each plaintext field is 32 bytes (a multiple of the 16-byte AES block\n // size), so PKCS#7 always appends a full 16-byte padding block:\n // |ciphertext| = PlaintextLen*32 + 16 = 16 * (1 + PlaintextLen*32 / 16)\n std::static_assert(\n ciphertext_bytes.len() == 16 * (1 + (PlaintextLen * 32) / 16),\n \"unexpected ciphertext length\",\n );\n\n // Encrypt a 2-byte header containing the body ciphertext length.\n let mut header_plaintext: [u8; 2] = [0 as u8; 2];\n let ciphertext_bytes_length = ciphertext_bytes.len();\n header_plaintext[0] = (ciphertext_bytes_length >> 8) as u8;\n header_plaintext[1] = ciphertext_bytes_length as u8;\n\n // Note: the aes128_encrypt builtin fn automatically appends bytes to the input, according to pkcs#7; hence why\n // the output `header_ciphertext_bytes` is 16 bytes larger than the input in this case.\n let header_ciphertext_bytes = aes128_encrypt(header_plaintext, header_iv, header_sym_key);\n // Verify expected header ciphertext size at compile time.\n std::static_assert(\n header_ciphertext_bytes.len() == HEADER_CIPHERTEXT_SIZE_IN_BYTES,\n \"unexpected ciphertext header length\",\n );\n\n // Assemble the message byte array:\n // [header_ct (16B)] [body_ct] [padding to mult of 31]\n let mut message_bytes_padding_to_mult_31 =\n get_arr_of_size__message_bytes_padding__from_PT::<PlaintextLen * 32>();\n // Safety: this randomness won't be constrained to be random. It's in the interest of the executor of this fn\n // to encrypt with random bytes.\n message_bytes_padding_to_mult_31 = unsafe { get_random_bytes() };\n\n let mut message_bytes = get_arr_of_size__message_bytes__from_PT::<PlaintextLen * 32>();\n\n std::static_assert(\n message_bytes.len() % 31 == 0,\n \"Unexpected error: message_bytes.len() should be divisible by 31, by construction.\",\n );\n\n let mut offset = 0;\n for i in 0..header_ciphertext_bytes.len() {\n message_bytes[offset + i] = header_ciphertext_bytes[i];\n }\n offset += header_ciphertext_bytes.len();\n\n for i in 0..ciphertext_bytes.len() {\n message_bytes[offset + i] = ciphertext_bytes[i];\n }\n offset += ciphertext_bytes.len();\n\n for i in 0..message_bytes_padding_to_mult_31.len() {\n message_bytes[offset + i] = message_bytes_padding_to_mult_31[i];\n }\n offset += message_bytes_padding_to_mult_31.len();\n\n // Ideally we would be able to have a static assert where we check that the offset would be such that we've\n // written to the entire log_bytes array, but we cannot since Noir does not treat the offset as a comptime\n // value (despite the values that it goes through being known at each stage). We instead check that the\n // computation used to obtain the offset computes the expected value (which we _can_ do in a static check), and\n // then add a cheap runtime check to also validate that the offset matches this.\n std::static_assert(\n header_ciphertext_bytes.len() + ciphertext_bytes.len() + message_bytes_padding_to_mult_31.len()\n == message_bytes.len(),\n \"unexpected message length\",\n );\n assert(offset == message_bytes.len(), \"unexpected encrypted message length\");\n\n // Pack message bytes into fields (31 bytes per field) and prepend eph_pk.x.\n // TODO(#12749): As Mike pointed out, we need to make messages produced by different encryption schemes\n // indistinguishable from each other and for this reason the output here and in the last for-loop of this\n // function should cover a full field.\n let message_bytes_as_fields = bytes_to_fields(message_bytes);\n\n let mut ciphertext: [Field; MESSAGE_CIPHERTEXT_LEN] = [0; MESSAGE_CIPHERTEXT_LEN];\n\n ciphertext[0] = eph_pk.x;\n\n let mut offset = 1;\n for i in 0..message_bytes_as_fields.len() {\n ciphertext[offset + i] = message_bytes_as_fields[i];\n }\n offset += message_bytes_as_fields.len();\n\n for i in offset..MESSAGE_CIPHERTEXT_LEN {\n // We need to get a random value that fits in 31 bytes to not leak information about the size of the\n // message (all the \"real\" message fields contain at most 31 bytes because of the way we convert the bytes\n // to fields). TODO(#12749): Long term, this is not a good solution.\n\n // Safety: we assume that the sender wants for the message to be private - a malicious one could simply\n // reveal its contents publicly. It is therefore fine to trust the sender to provide random padding.\n let field_bytes = unsafe { get_random_bytes::<31>() };\n ciphertext[i] = Field::from_be_bytes::<31>(field_bytes);\n }\n\n ciphertext\n }\n\n unconstrained fn decrypt(\n ciphertext: BoundedVec<Field, MESSAGE_CIPHERTEXT_LEN>,\n recipient: AztecAddress,\n ) -> Option<BoundedVec<Field, MESSAGE_PLAINTEXT_LEN>> {\n let eph_pk_x = ciphertext.get(0);\n\n let ciphertext_without_eph_pk_x_fields = array::subbvec::<Field, MESSAGE_CIPHERTEXT_LEN, MESSAGE_CIPHERTEXT_LEN - EPH_PK_X_SIZE_IN_FIELDS>(\n ciphertext,\n EPH_PK_X_SIZE_IN_FIELDS,\n );\n\n // Convert the ciphertext represented as fields to a byte representation (its original format)\n let ciphertext_without_eph_pk_x = bytes_from_fields(ciphertext_without_eph_pk_x_fields);\n\n // With the x-coordinate of the ephemeral public key we can reconstruct the point as we know that the\n // y-coordinate must be positive. This may fail however, as not all x-coordinates are on the curve. In that\n // case, we simply return `Option::none`.\n point_from_x_coord_and_sign(eph_pk_x, true).map(|eph_pk| {\n // Derive shared secret\n let ciphertext_shared_secret = get_shared_secret(recipient, eph_pk);\n\n // Derive symmetric keys:\n let pairs = derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe::<2>(\n ciphertext_shared_secret,\n );\n let (body_sym_key, body_iv) = pairs[0];\n let (header_sym_key, header_iv) = pairs[1];\n\n // Extract the header ciphertext\n let header_start = 0;\n let header_ciphertext: [u8; HEADER_CIPHERTEXT_SIZE_IN_BYTES] =\n array::subarray(ciphertext_without_eph_pk_x.storage(), header_start);\n // We need to convert the array to a BoundedVec because the oracle expects a BoundedVec as it's designed to\n // work with messages with unknown length at compile time. This would not be necessary here as the header\n // ciphertext length is fixed. But we do it anyway to not have to have duplicate oracles.\n let header_ciphertext_bvec =\n BoundedVec::<u8, HEADER_CIPHERTEXT_SIZE_IN_BYTES>::from_array(header_ciphertext);\n\n // Decrypt header\n let header_plaintext = aes128_decrypt_oracle(header_ciphertext_bvec, header_iv, header_sym_key);\n\n // Extract ciphertext length from header (2 bytes, big-endian)\n let ciphertext_length = ((header_plaintext.get(0) as u32) << 8) | (header_plaintext.get(1) as u32);\n\n // Extract and decrypt main ciphertext\n let ciphertext_start = header_start + HEADER_CIPHERTEXT_SIZE_IN_BYTES;\n let ciphertext_with_padding: [u8; MESSAGE_PLAINTEXT_SIZE_IN_BYTES] =\n array::subarray(ciphertext_without_eph_pk_x.storage(), ciphertext_start);\n let ciphertext: BoundedVec<u8, MESSAGE_PLAINTEXT_SIZE_IN_BYTES> =\n BoundedVec::from_parts(ciphertext_with_padding, ciphertext_length);\n\n // Decrypt main ciphertext and return it\n let plaintext_bytes = aes128_decrypt_oracle(ciphertext, body_iv, body_sym_key);\n\n // Each field of the original message was serialized to 32 bytes so we convert\n // the bytes back to fields.\n fields_from_bytes(plaintext_bytes)\n })\n }\n}\n\n/// Produces a random valid address point, i.e. one that is on the curve. This is equivalent to calling\n/// [`AztecAddress::to_address_point`] on a random valid address.\nunconstrained fn random_address_point() -> AddressPoint {\n let mut result = std::mem::zeroed();\n\n loop {\n // We simply produce random x coordinates until we find one that is on the curve. About half of the x\n // coordinates fulfill this condition, so this should only take a few iterations at most.\n let x_coord = random();\n let point = point_from_x_coord_and_sign(x_coord, true);\n if point.is_some() {\n result = AddressPoint { inner: point.unwrap() };\n break;\n }\n }\n\n result\n}\n\nmod test {\n use crate::{\n keys::ecdh_shared_secret::derive_ecdh_shared_secret,\n messages::{encoding::MESSAGE_PLAINTEXT_LEN, encryption::message_encryption::MessageEncryption},\n test::helpers::test_environment::TestEnvironment,\n };\n use crate::protocol::{address::AztecAddress, traits::FromField};\n use super::{AES128, random_address_point};\n use std::{embedded_curve_ops::EmbeddedCurveScalar, test::OracleMock};\n\n #[test]\n unconstrained fn encrypt_decrypt_deterministic() {\n let env = TestEnvironment::new();\n\n // Message decryption requires oracles that are only available during private execution\n env.private_context(|_| {\n let plaintext = [1, 2, 3];\n\n let recipient = AztecAddress::from_field(\n 0x25afb798ea6d0b8c1618e50fdeafa463059415013d3b7c75d46abf5e242be70c,\n );\n\n // Mock random values for deterministic test\n let eph_sk = 0x1358d15019d4639393d62b97e1588c095957ce74a1c32d6ec7d62fe6705d9538;\n let _ = OracleMock::mock(\"utilityGetRandomField\").returns(eph_sk).times(1);\n\n let randomness = 0x0101010101010101010101010101010101010101010101010101010101010101;\n let _ = OracleMock::mock(\"utilityGetRandomField\").returns(randomness).times(1000000);\n\n let _ = OracleMock::mock(\"privateGetNextAppTagAsSender\").returns(42);\n\n // Encrypt the message\n let encrypted_message = BoundedVec::from_array(AES128::encrypt(plaintext, recipient));\n\n // Mock shared secret for deterministic test\n let shared_secret = derive_ecdh_shared_secret(\n EmbeddedCurveScalar::from_field(eph_sk),\n recipient.to_address_point().unwrap().inner,\n );\n\n let _ = OracleMock::mock(\"utilityGetSharedSecret\").returns(shared_secret);\n\n // Decrypt the message\n let decrypted = AES128::decrypt(encrypted_message, recipient).unwrap();\n\n // The decryption function spits out a BoundedVec because it's designed to work with messages with unknown\n // length at compile time. For this reason we need to convert the original input to a BoundedVec.\n let plaintext_bvec = BoundedVec::<Field, MESSAGE_PLAINTEXT_LEN>::from_array(plaintext);\n\n // Verify decryption matches original plaintext\n assert_eq(decrypted, plaintext_bvec, \"Decrypted bytes should match original plaintext\");\n\n // The following is a workaround of \"struct is never constructed\" Noir compilation error (we only ever use\n // static methods of the struct).\n let _ = AES128 {};\n });\n }\n\n #[test]\n unconstrained fn encrypt_decrypt_random() {\n // Same as `encrypt_decrypt_deterministic`, except we don't mock any of the oracles and rely on\n // `TestEnvironment` instead.\n let mut env = TestEnvironment::new();\n\n let recipient = env.create_light_account();\n\n env.private_context(|_| {\n let plaintext = [1, 2, 3];\n let ciphertext = AES128::encrypt(plaintext, recipient);\n\n assert_eq(\n AES128::decrypt(BoundedVec::from_array(ciphertext), recipient).unwrap(),\n BoundedVec::from_array(plaintext),\n );\n });\n }\n\n #[test]\n unconstrained fn encrypt_to_invalid_address() {\n // x = 3 is a non-residue for this curve, resulting in an invalid address\n let invalid_address = AztecAddress { inner: 3 };\n\n // We just test that we produced some output and did not crash - the result is gibberish as it is encrypted\n // using a public key for which we do not know the private key.\n let _ = AES128::encrypt([1, 2, 3, 4], invalid_address);\n }\n\n // Documents the PKCS#7 padding behavior that `encrypt` relies on (see its static_assert).\n #[test]\n fn pkcs7_padding_always_adds_at_least_one_byte() {\n let key = [0 as u8; 16];\n let iv = [0 as u8; 16];\n\n // 1 byte input + 15 bytes padding = 16 bytes\n assert_eq(std::aes128::aes128_encrypt([0; 1], iv, key).len(), 16);\n\n // 15 bytes input + 1 byte padding = 16 bytes\n assert_eq(std::aes128::aes128_encrypt([0; 15], iv, key).len(), 16);\n\n // 16 bytes input (block-aligned) + full 16-byte padding block = 32 bytes\n assert_eq(std::aes128::aes128_encrypt([0; 16], iv, key).len(), 32);\n }\n\n #[test]\n unconstrained fn encrypt_decrypt_max_size_plaintext() {\n let mut env = TestEnvironment::new();\n let recipient = env.create_light_account();\n\n env.private_context(|_| {\n let mut plaintext = [0; MESSAGE_PLAINTEXT_LEN];\n for i in 0..MESSAGE_PLAINTEXT_LEN {\n plaintext[i] = i as Field;\n }\n let ciphertext = AES128::encrypt(plaintext, recipient);\n\n assert_eq(\n AES128::decrypt(BoundedVec::from_array(ciphertext), recipient).unwrap(),\n BoundedVec::from_array(plaintext),\n );\n });\n }\n\n #[test(should_fail_with = \"Plaintext length exceeds MESSAGE_PLAINTEXT_LEN\")]\n unconstrained fn encrypt_oversized_plaintext() {\n let address = AztecAddress { inner: 3 };\n let plaintext: [Field; MESSAGE_PLAINTEXT_LEN + 1] = [0; MESSAGE_PLAINTEXT_LEN + 1];\n let _ = AES128::encrypt(plaintext, address);\n }\n\n #[test]\n unconstrained fn random_address_point_produces_valid_points() {\n // About half of random addresses are invalid, so testing just a couple gives us high confidence that\n // `random_address_point` is indeed producing valid addresses.\n for _ in 0..10 {\n let random_address = AztecAddress { inner: random_address_point().inner.x };\n assert(random_address.to_address_point().is_some());\n }\n }\n\n #[test]\n unconstrained fn decrypt_invalid_ephemeral_public_key() {\n let mut env = TestEnvironment::new();\n\n let recipient = env.create_light_account();\n\n env.private_context(|_| {\n let plaintext = [1, 2, 3, 4];\n let ciphertext = AES128::encrypt(plaintext, recipient);\n\n // The first field of the ciphertext is the x-coordinate of the ephemeral public key. We set it to a known\n // non-residue (3), causing `decrypt` to fail to produce a decryption shared secret.\n let mut bad_ciphertext = BoundedVec::from_array(ciphertext);\n bad_ciphertext.set(0, 3);\n\n assert(AES128::decrypt(bad_ciphertext, recipient).is_none());\n });\n }\n}\n"
|
|
4310
4310
|
},
|
|
4311
4311
|
"137": {
|
|
4312
4312
|
"path": "/home/aztec-dev/aztec-packages/noir-projects/aztec-nr/aztec/src/messages/logs/event.nr",
|
|
@@ -4314,7 +4314,7 @@
|
|
|
4314
4314
|
},
|
|
4315
4315
|
"139": {
|
|
4316
4316
|
"path": "/home/aztec-dev/aztec-packages/noir-projects/aztec-nr/aztec/src/messages/logs/note.nr",
|
|
4317
|
-
"source": "use crate::{\n messages::{\n encoding::{encode_message, MAX_MESSAGE_CONTENT_LEN, MESSAGE_EXPANDED_METADATA_LEN},\n msg_type::PRIVATE_NOTE_MSG_TYPE_ID,\n },\n note::note_interface::NoteType,\n utils::array,\n};\nuse crate::protocol::{address::AztecAddress, traits::{FromField, Packable, ToField}};\n\n/// The number of fields in a private note message content that are not the note's packed representation.\npub(crate) global PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN: u32 = 3;\n\npub(crate) global PRIVATE_NOTE_MSG_PLAINTEXT_OWNER_INDEX: u32 = 0;\npub(crate) global PRIVATE_NOTE_MSG_PLAINTEXT_STORAGE_SLOT_INDEX: u32 = 1;\npub(crate) global PRIVATE_NOTE_MSG_PLAINTEXT_RANDOMNESS_INDEX: u32 = 2;\n\n/// The maximum length of the packed representation of a note's contents. This is limited by private log size,\n/// encryption overhead and extra fields in the message (e.g. message type id, storage slot, randomness, etc.).\npub global MAX_NOTE_PACKED_LEN: u32 = MAX_MESSAGE_CONTENT_LEN - PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN;\n\n/// Creates the plaintext for a private note message (i.e. one of type [`PRIVATE_NOTE_MSG_TYPE_ID`]).\n///\n/// This plaintext is meant to be decoded via [`decode_private_note_message`].\npub fn encode_private_note_message<Note>(\n note: Note,\n owner: AztecAddress,\n storage_slot: Field,\n randomness: Field,\n) -> [Field; PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN + <Note as Packable>::N + MESSAGE_EXPANDED_METADATA_LEN]\nwhere\n Note: NoteType + Packable,\n{\n let packed_note = note.pack();\n\n // If PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN is changed, causing the assertion below to fail, then the\n // encoding below must be updated as well.\n std::static_assert(\n PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN == 3,\n \"unexpected value for PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN\",\n );\n\n let mut msg_content = [0; PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN + <Note as Packable>::N];\n msg_content[PRIVATE_NOTE_MSG_PLAINTEXT_OWNER_INDEX] = owner.to_field();\n msg_content[PRIVATE_NOTE_MSG_PLAINTEXT_STORAGE_SLOT_INDEX] = storage_slot;\n msg_content[PRIVATE_NOTE_MSG_PLAINTEXT_RANDOMNESS_INDEX] = randomness;\n for i in 0..packed_note.len() {\n msg_content[PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN + i] = packed_note[i];\n }\n\n // Notes use the note type id for metadata\n encode_message(PRIVATE_NOTE_MSG_TYPE_ID, Note::get_id() as u64, msg_content)\n}\n\n/// Decodes the plaintext from a private note message (i.e. one of type [`PRIVATE_NOTE_MSG_TYPE_ID`]).\n///\n/// This plaintext is meant to have originated from [`encode_private_note_message`].\n///\n/// Note that while [`encode_private_note_message`] returns a fixed-size array, this function takes a [`BoundedVec`]\n/// instead. This is because when decoding we're typically processing runtime-sized plaintexts, more specifically,\n/// those that originate from [`crate::messages::encryption::message_encryption::MessageEncryption::decrypt`].\npub(crate) unconstrained fn decode_private_note_message(\n msg_metadata: u64,\n msg_content: BoundedVec<Field, MAX_MESSAGE_CONTENT_LEN>,\n) -> (Field, AztecAddress, Field, Field, BoundedVec<Field, MAX_NOTE_PACKED_LEN>) {\n let note_type_id = msg_metadata as Field; // TODO: make note type id not be a full field\n\n assert(\n msg_content.len() > PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN,\n f\"Invalid private note message: all private note messages must have at least {PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN} fields\",\n );\n\n // If PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN is changed, causing the assertion below to fail, then the\n // decoding below must be updated as well.\n std::static_assert(\n PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN == 3,\n \"unexpected value for PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN\",\n );\n\n let owner = AztecAddress::from_field(msg_content.get(PRIVATE_NOTE_MSG_PLAINTEXT_OWNER_INDEX));\n let storage_slot = msg_content.get(PRIVATE_NOTE_MSG_PLAINTEXT_STORAGE_SLOT_INDEX);\n let randomness = msg_content.get(PRIVATE_NOTE_MSG_PLAINTEXT_RANDOMNESS_INDEX);\n let packed_note = array::subbvec(msg_content, PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN);\n\n (note_type_id, owner, storage_slot, randomness, packed_note)\n}\n\nmod test {\n use crate::{\n messages::{\n encoding::decode_message,\n logs::note::{decode_private_note_message, encode_private_note_message},\n msg_type::PRIVATE_NOTE_MSG_TYPE_ID,\n },\n note::note_interface::NoteType,\n };\n use crate::protocol::{address::AztecAddress, traits::{FromField, Packable}};\n use crate::test::mocks::mock_note::MockNote;\n\n global VALUE: Field = 7;\n global OWNER: AztecAddress = AztecAddress::from_field(8);\n global STORAGE_SLOT: Field = 9;\n global RANDOMNESS: Field = 10;\n\n #[test]\n unconstrained fn encode_decode() {\n let note = MockNote::new(VALUE).build_note();\n\n let message_plaintext = encode_private_note_message(note, OWNER, STORAGE_SLOT, RANDOMNESS);\n\n let (msg_type_id, msg_metadata, msg_content) = decode_message(BoundedVec::from_array(message_plaintext));\n\n assert_eq(msg_type_id, PRIVATE_NOTE_MSG_TYPE_ID);\n\n let (note_type_id, owner, storage_slot, randomness, packed_note) =\n decode_private_note_message(msg_metadata, msg_content);\n\n assert_eq(note_type_id, MockNote::get_id());\n assert_eq(owner, OWNER);\n assert_eq(storage_slot, STORAGE_SLOT);\n assert_eq(randomness, RANDOMNESS);\n assert_eq(packed_note, BoundedVec::from_array(note.pack()));\n }\n}\n"
|
|
4317
|
+
"source": "use crate::{\n messages::{\n encoding::{encode_message, MAX_MESSAGE_CONTENT_LEN, MESSAGE_EXPANDED_METADATA_LEN},\n msg_type::PRIVATE_NOTE_MSG_TYPE_ID,\n },\n note::note_interface::NoteType,\n utils::array,\n};\nuse crate::protocol::{address::AztecAddress, traits::{FromField, Packable, ToField}};\n\n/// The number of fields in a private note message content that are not the note's packed representation.\npub(crate) global PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN: u32 = 3;\n\npub(crate) global PRIVATE_NOTE_MSG_PLAINTEXT_OWNER_INDEX: u32 = 0;\npub(crate) global PRIVATE_NOTE_MSG_PLAINTEXT_STORAGE_SLOT_INDEX: u32 = 1;\npub(crate) global PRIVATE_NOTE_MSG_PLAINTEXT_RANDOMNESS_INDEX: u32 = 2;\n\n/// The maximum length of the packed representation of a note's contents. This is limited by private log size,\n/// encryption overhead and extra fields in the message (e.g. message type id, storage slot, randomness, etc.).\npub global MAX_NOTE_PACKED_LEN: u32 = MAX_MESSAGE_CONTENT_LEN - PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN;\n\n/// Creates the plaintext for a private note message (i.e. one of type [`PRIVATE_NOTE_MSG_TYPE_ID`]).\n///\n/// This plaintext is meant to be decoded via [`decode_private_note_message`].\npub fn encode_private_note_message<Note>(\n note: Note,\n owner: AztecAddress,\n storage_slot: Field,\n randomness: Field,\n) -> [Field; PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN + <Note as Packable>::N + MESSAGE_EXPANDED_METADATA_LEN]\nwhere\n Note: NoteType + Packable,\n{\n let packed_note = note.pack();\n\n // If PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN is changed, causing the assertion below to fail, then the\n // encoding below must be updated as well.\n std::static_assert(\n PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN == 3,\n \"unexpected value for PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN\",\n );\n\n let mut msg_content = [0; PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN + <Note as Packable>::N];\n msg_content[PRIVATE_NOTE_MSG_PLAINTEXT_OWNER_INDEX] = owner.to_field();\n msg_content[PRIVATE_NOTE_MSG_PLAINTEXT_STORAGE_SLOT_INDEX] = storage_slot;\n msg_content[PRIVATE_NOTE_MSG_PLAINTEXT_RANDOMNESS_INDEX] = randomness;\n for i in 0..packed_note.len() {\n msg_content[PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN + i] = packed_note[i];\n }\n\n // Notes use the note type id for metadata\n encode_message(PRIVATE_NOTE_MSG_TYPE_ID, Note::get_id() as u64, msg_content)\n}\n\n/// Decodes the plaintext from a private note message (i.e. one of type [`PRIVATE_NOTE_MSG_TYPE_ID`]).\n///\n/// This plaintext is meant to have originated from [`encode_private_note_message`].\n///\n/// Note that while [`encode_private_note_message`] returns a fixed-size array, this function takes a [`BoundedVec`]\n/// instead. This is because when decoding we're typically processing runtime-sized plaintexts, more specifically,\n/// those that originate from [`crate::messages::encryption::message_encryption::MessageEncryption::decrypt`].\npub(crate) unconstrained fn decode_private_note_message(\n msg_metadata: u64,\n msg_content: BoundedVec<Field, MAX_MESSAGE_CONTENT_LEN>,\n) -> (Field, AztecAddress, Field, Field, BoundedVec<Field, MAX_NOTE_PACKED_LEN>) {\n let note_type_id = msg_metadata as Field; // TODO: make note type id not be a full field\n\n assert(\n msg_content.len() > PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN,\n f\"Invalid private note message: all private note messages must have at least {PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN} fields\",\n );\n\n // If PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN is changed, causing the assertion below to fail, then the\n // decoding below must be updated as well.\n std::static_assert(\n PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN == 3,\n \"unexpected value for PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN\",\n );\n\n let owner = AztecAddress::from_field(msg_content.get(PRIVATE_NOTE_MSG_PLAINTEXT_OWNER_INDEX));\n let storage_slot = msg_content.get(PRIVATE_NOTE_MSG_PLAINTEXT_STORAGE_SLOT_INDEX);\n let randomness = msg_content.get(PRIVATE_NOTE_MSG_PLAINTEXT_RANDOMNESS_INDEX);\n let packed_note = array::subbvec(msg_content, PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN);\n\n (note_type_id, owner, storage_slot, randomness, packed_note)\n}\n\nmod test {\n use crate::{\n messages::{\n encoding::decode_message,\n logs::note::{decode_private_note_message, encode_private_note_message, MAX_NOTE_PACKED_LEN},\n msg_type::PRIVATE_NOTE_MSG_TYPE_ID,\n },\n note::note_interface::NoteType,\n };\n use crate::protocol::{address::AztecAddress, traits::{FromField, Packable}};\n use crate::test::mocks::mock_note::MockNote;\n\n global VALUE: Field = 7;\n global OWNER: AztecAddress = AztecAddress::from_field(8);\n global STORAGE_SLOT: Field = 9;\n global RANDOMNESS: Field = 10;\n\n #[test]\n unconstrained fn encode_decode() {\n let note = MockNote::new(VALUE).build_note();\n\n let message_plaintext = encode_private_note_message(note, OWNER, STORAGE_SLOT, RANDOMNESS);\n\n let (msg_type_id, msg_metadata, msg_content) = decode_message(BoundedVec::from_array(message_plaintext));\n\n assert_eq(msg_type_id, PRIVATE_NOTE_MSG_TYPE_ID);\n\n let (note_type_id, owner, storage_slot, randomness, packed_note) =\n decode_private_note_message(msg_metadata, msg_content);\n\n assert_eq(note_type_id, MockNote::get_id());\n assert_eq(owner, OWNER);\n assert_eq(storage_slot, STORAGE_SLOT);\n assert_eq(randomness, RANDOMNESS);\n assert_eq(packed_note, BoundedVec::from_array(note.pack()));\n }\n\n #[derive(Packable)]\n struct MaxSizeNote {\n data: [Field; MAX_NOTE_PACKED_LEN],\n }\n\n impl NoteType for MaxSizeNote {\n fn get_id() -> Field {\n 0\n }\n }\n\n #[test]\n unconstrained fn encode_decode_max_size_note() {\n let mut data = [0; MAX_NOTE_PACKED_LEN];\n for i in 0..MAX_NOTE_PACKED_LEN {\n data[i] = i as Field;\n }\n let note = MaxSizeNote { data };\n\n let encoded = encode_private_note_message(note, OWNER, STORAGE_SLOT, RANDOMNESS);\n let (msg_type_id, msg_metadata, msg_content) = decode_message(BoundedVec::from_array(encoded));\n\n assert_eq(msg_type_id, PRIVATE_NOTE_MSG_TYPE_ID);\n\n let (note_type_id, owner, storage_slot, randomness, packed_note) =\n decode_private_note_message(msg_metadata, msg_content);\n\n assert_eq(note_type_id, MaxSizeNote::get_id());\n assert_eq(owner, OWNER);\n assert_eq(storage_slot, STORAGE_SLOT);\n assert_eq(randomness, RANDOMNESS);\n assert_eq(packed_note, BoundedVec::from_array(data));\n }\n\n #[derive(Packable)]\n struct OversizedNote {\n data: [Field; MAX_NOTE_PACKED_LEN + 1],\n }\n\n impl NoteType for OversizedNote {\n fn get_id() -> Field {\n 0\n }\n }\n\n #[test(should_fail_with = \"Invalid message content: it must have a length of at most MAX_MESSAGE_CONTENT_LEN\")]\n fn encode_oversized_note_fails() {\n let note = OversizedNote { data: [0; MAX_NOTE_PACKED_LEN + 1] };\n let _ = encode_private_note_message(note, OWNER, STORAGE_SLOT, RANDOMNESS);\n }\n}\n"
|
|
4318
4318
|
},
|
|
4319
4319
|
"140": {
|
|
4320
4320
|
"path": "/home/aztec-dev/aztec-packages/noir-projects/aztec-nr/aztec/src/messages/logs/partial_note.nr",
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@aztec/accounts",
|
|
3
3
|
"homepage": "https://github.com/AztecProtocol/aztec-packages/tree/master/yarn-project/accounts",
|
|
4
4
|
"description": "Implementation of sample account contracts for Aztec Network",
|
|
5
|
-
"version": "4.0.
|
|
5
|
+
"version": "4.1.0-rc.2",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
"./defaults": "./dest/defaults/index.js",
|
|
@@ -82,11 +82,11 @@
|
|
|
82
82
|
]
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
|
-
"@aztec/aztec.js": "4.0.
|
|
86
|
-
"@aztec/entrypoints": "4.0.
|
|
87
|
-
"@aztec/ethereum": "4.0.
|
|
88
|
-
"@aztec/foundation": "4.0.
|
|
89
|
-
"@aztec/stdlib": "4.0.
|
|
85
|
+
"@aztec/aztec.js": "4.1.0-rc.2",
|
|
86
|
+
"@aztec/entrypoints": "4.1.0-rc.2",
|
|
87
|
+
"@aztec/ethereum": "4.1.0-rc.2",
|
|
88
|
+
"@aztec/foundation": "4.1.0-rc.2",
|
|
89
|
+
"@aztec/stdlib": "4.1.0-rc.2",
|
|
90
90
|
"tslib": "^2.4.0"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|