diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr index 1ee263153e6..ceee4602591 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr @@ -75,7 +75,10 @@ fn generate_ephemeral_key_pair() -> (Scalar, Point) { // @todo Need to draw randomness from the full domain of Fq not only Fr // We use the unsafe version of `fr_to_fq` because multi_scalar_mul (called by derive_public_key) will constrain // the scalars. - let eph_sk = fr_to_fq_unsafe(unsafe_rand()); + let randomness = unsafe { + unsafe_rand() + }; + let eph_sk = fr_to_fq_unsafe(randomness); let eph_pk = derive_public_key(eph_sk); (eph_sk, eph_pk) @@ -141,17 +144,16 @@ pub fn compute_outgoing_body_ciphertext( mod test { use crate::{ encrypted_logs::payload::{compute_encrypted_log, compute_incoming_body_ciphertext, compute_outgoing_body_ciphertext}, - keys::public_keys::{OvpkM, IvpkM}, test::mocks::mock_note::MockNoteBuilder + keys::public_keys::{OvpkM, IvpkM} }; use std::embedded_curve_ops::fixed_base_scalar_mul as derive_public_key; use dep::protocol_types::{address::AztecAddress, point::Point, scalar::Scalar}; use std::test::OracleMock; #[test] - unconstrained fn test_encrypted_note_log_matches_typescript() { + unconstrained fn test_encrypted_log_matches_typescript() { // All the values in this test were copied over from `tagged_log.test.ts` let contract_address = AztecAddress::from_field(0x10f48cd9eff7ae5b209c557c70de2e657ee79166868676b787e9417e19260e04); - let storage_slot = 0x0fe46be583b71f4ab5b70c2657ff1d05cccf1d292a9369628d1a194f944e6599; let ovsk_app = 0x03a6513d6def49f41d20373d2cec894c23e7492794b08fc50c0e8a1bd2512612; let ovpk_m = OvpkM { inner: Point { @@ -168,9 +170,9 @@ mod test { } }; - let note_value = 0x301640ceea758391b2e161c92c0513f129020f4125256afdae2646ce31099f5c; - let note = MockNoteBuilder::new(note_value).contract_address(contract_address).storage_slot(storage_slot).build(); - let plaintext = note.to_be_bytes(storage_slot); + let plaintext = [ + 0, 0, 0, 1, 48, 22, 64, 206, 234, 117, 131, 145, 178, 225, 97, 201, 44, 5, 19, 241, 41, 2, 15, 65, 37, 37, 106, 253, 174, 38, 70, 206, 49, 9, 159, 92, 16, 244, 140, 217, 239, 247, 174, 91, 32, 156, 85, 124, 112, 222, 46, 101, 126, 231, 145, 102, 134, 134, 118, 183, 135, 233, 65, 126, 25, 38, 14, 4, 15, 228, 107, 229, 131, 183, 31, 74, 181, 183, 12, 38, 87, 255, 29, 5, 204, 207, 29, 41, 42, 147, 105, 98, 141, 26, 25, 79, 148, 78, 101, 153, 0, 0, 16, 39 + ]; let eph_sk = 0x1358d15019d4639393d62b97e1588c095957ce74a1c32d6ec7d62fe6705d9538; let _ = OracleMock::mock("getRandomField").returns(eph_sk); @@ -181,10 +183,10 @@ mod test { // The following value was generated by `tagged_log.test.ts` // --> Run the test with AZTEC_GENERATE_TEST_DATA=1 flag to update test data. - let encrypted_note_log_from_typescript = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 141, 70, 12, 14, 67, 77, 132, 110, 193, 234, 40, 110, 64, 144, 235, 86, 55, 111, 242, 123, 221, 193, 170, 202, 225, 216, 86, 84, 159, 112, 31, 167, 126, 79, 51, 186, 47, 71, 253, 172, 99, 112, 241, 59, 197, 241, 107, 186, 232, 87, 187, 230, 171, 62, 228, 234, 42, 51, 145, 146, 238, 242, 42, 71, 206, 13, 244, 66, 111, 195, 20, 203, 98, 148, 204, 242, 145, 183, 156, 29, 141, 54, 44, 220, 194, 35, 229, 16, 32, 204, 211, 49, 142, 112, 82, 202, 116, 241, 254, 146, 42, 217, 20, 189, 70, 228, 182, 171, 205, 104, 27, 99, 171, 28, 91, 244, 21, 30, 130, 240, 5, 72, 174, 124, 97, 197, 157, 248, 193, 23, 193, 76, 46, 141, 144, 70, 211, 45, 67, 167, 218, 129, 140, 104, 190, 41, 110, 249, 209, 68, 106, 135, 164, 80, 235, 63, 101, 80, 32, 13, 38, 99, 145, 91, 11, 173, 151, 231, 247, 65, 153, 117, 229, 167, 64, 239, 182, 126, 235, 83, 4, 169, 8, 8, 160, 4, 235, 252, 21, 96, 84, 161, 69, 145, 145, 215, 254, 161, 117, 246, 198, 65, 89, 179, 194, 90, 19, 121, 12, 202, 114, 80, 195, 14, 60, 128, 105, 142, 100, 86, 90, 108, 157, 219, 22, 172, 20, 121, 195, 25, 159, 236, 2, 70, 75, 42, 37, 34, 2, 17, 149, 20, 176, 32, 18, 204, 56, 117, 121, 34, 15, 3, 88, 123, 64, 68, 74, 233, 63, 59, 131, 222, 194, 192, 167, 110, 217, 10, 128, 73, 129, 172, 205, 103, 212, 60, 151, 141, 10, 151, 222, 151, 180, 43, 91, 148, 201, 110, 165, 10, 238, 32, 134, 235, 99, 216, 200, 182, 31, 22, 156, 18, 209, 222, 172, 239, 193, 212, 86, 99, 62, 70, 182, 45, 175, 241, 91, 202, 179, 225, 236, 95, 71, 66, 151, 225, 203, 53, 216, 85, 102, 130, 6, 8, 25, 180, 86, 58, 140, 198, 105, 102, 177, 42, 94, 115, 247, 145, 147, 24, 231, 39, 73, 27, 10, 219, 130, 115, 188, 74, 114, 5, 177, 199, 83, 183, 106, 87, 204, 238, 231, 72, 45, 240, 39, 174, 25, 98, 53, 187, 156, 159, 244, 38 + let encrypted_log_from_typescript = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 141, 70, 12, 14, 67, 77, 132, 110, 193, 234, 40, 110, 64, 144, 235, 86, 55, 111, 242, 123, 221, 193, 170, 202, 225, 216, 86, 84, 159, 112, 31, 167, 126, 79, 51, 186, 47, 71, 253, 172, 99, 112, 241, 59, 197, 241, 107, 186, 232, 87, 187, 230, 171, 62, 228, 234, 42, 51, 145, 146, 238, 242, 42, 71, 206, 13, 244, 66, 111, 195, 20, 203, 98, 148, 204, 242, 145, 183, 156, 29, 141, 54, 44, 220, 194, 35, 229, 16, 32, 204, 211, 49, 142, 112, 82, 202, 116, 241, 254, 146, 42, 217, 20, 189, 70, 228, 182, 171, 205, 104, 27, 99, 171, 28, 91, 244, 21, 30, 130, 240, 5, 72, 174, 124, 97, 197, 157, 248, 193, 23, 193, 76, 46, 141, 144, 70, 211, 45, 67, 167, 218, 129, 140, 104, 190, 41, 110, 249, 209, 68, 106, 135, 164, 80, 235, 63, 101, 80, 32, 13, 38, 99, 145, 91, 11, 173, 151, 231, 247, 65, 153, 117, 229, 167, 64, 239, 182, 126, 235, 83, 4, 169, 8, 8, 160, 4, 235, 252, 21, 96, 84, 161, 69, 145, 145, 215, 254, 161, 117, 246, 198, 65, 89, 179, 194, 90, 19, 121, 12, 202, 114, 80, 195, 14, 60, 128, 105, 142, 100, 86, 90, 108, 157, 219, 22, 172, 20, 121, 195, 25, 159, 236, 2, 70, 75, 42, 37, 34, 2, 17, 149, 20, 176, 32, 18, 204, 56, 117, 121, 34, 15, 3, 88, 123, 64, 68, 74, 233, 63, 59, 131, 222, 194, 192, 167, 110, 217, 10, 128, 73, 129, 172, 61, 43, 12, 98, 165, 203, 191, 154, 161, 150, 4, 239, 95, 48, 60, 159, 33, 222, 142, 102, 73, 193, 236, 145, 197, 160, 216, 254, 113, 243, 25, 244, 251, 192, 222, 35, 7, 114, 101, 35, 152, 151, 112, 24, 32, 94, 138, 71, 160, 91, 68, 131, 217, 117, 140, 19, 147, 37, 197, 192, 21, 43, 172, 239, 239, 205, 15, 110, 76, 26, 211, 42, 117, 4, 15, 135, 145, 247, 37, 73, 84, 164, 149, 250, 35, 0, 205, 105, 178, 143, 104, 98, 100, 250, 193, 154, 136, 175, 177, 109, 225, 207, 252, 147, 250, 250, 189, 117, 147, 101, 230, 132 ]; - assert_eq(encrypted_note_log_from_typescript, log); + assert_eq(encrypted_log_from_typescript, log); } #[test] diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr index 79cfbca89e0..e0df663feaa 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -13,10 +13,7 @@ contract Test { use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note; use dep::aztec::encrypted_logs::encrypted_event_emission::encode_and_encrypt_event_with_randomness_unconstrained; - use dep::aztec::protocol_types::{constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, traits::Serialize, point::Point, scalar::Scalar}; - - use dep::aztec::encrypted_logs::header::EncryptedLogHeader; - use dep::aztec::encrypted_logs::payload::{compute_incoming_body_ciphertext, compute_outgoing_body_ciphertext}; + use dep::aztec::protocol_types::{constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, traits::Serialize, point::Point}; use dep::aztec::note::constants::MAX_NOTES_PER_PAGE; use dep::aztec::keys::getters::get_public_keys; @@ -34,9 +31,8 @@ contract Test { use dep::token_portal_content_hash_lib::{get_mint_private_content_hash, get_mint_public_content_hash}; use dep::value_note::value_note::ValueNote; // TODO investigate why the macros require EmbeddedCurvePoint and EmbeddedCurveScalar - use std::embedded_curve_ops::{EmbeddedCurveScalar, EmbeddedCurvePoint, fixed_base_scalar_mul as derive_public_key}; + use std::embedded_curve_ops::{EmbeddedCurveScalar, EmbeddedCurvePoint}; use std::meta::derive; - use std::aes128::aes128_encrypt; use crate::test_note::TestNote; @@ -400,45 +396,6 @@ contract Test { assert(context.version() == version, "Invalid version"); } - #[private] - fn encrypt(input: [u8; 64], iv: [u8; 16], key: [u8; 16]) -> [u8; 80] { - aes128_encrypt(input, iv, key).as_array() - } - - #[private] - fn encrypt_with_padding(input: [u8; 65], iv: [u8; 16], key: [u8; 16]) -> [u8; 80] { - aes128_encrypt(input, iv, key).as_array() - } - - #[private] - fn compute_note_header_ciphertext(secret: Scalar, ivpk: IvpkM) -> [u8; 48] { - EncryptedLogHeader::new(context.this_address()).compute_ciphertext(secret, ivpk) - } - - // 64 bytes + 32 * #fields + 16 = 112 bytes - #[private] - fn compute_incoming_log_body_ciphertext( - secret: Scalar, - ivpk: IvpkM, - storage_slot: Field, - value: Field - ) -> [u8; 112] { - let note = TestNote::new(value); - let plaintext = note.to_be_bytes(storage_slot); - compute_incoming_body_ciphertext(plaintext, secret, ivpk).as_array() - } - - #[private] - fn compute_outgoing_log_body_ciphertext( - eph_sk: Scalar, - recipient: AztecAddress, - recipient_ivpk: IvpkM, - ovsk_app: Scalar - ) -> [u8; 144] { - let eph_pk = derive_public_key(eph_sk); - compute_outgoing_body_ciphertext(recipient, recipient_ivpk, ovsk_app, eph_sk, eph_pk) - } - #[public] fn assert_public_global_vars( chain_id: Field, diff --git a/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr index 8a648fedfef..4c685a0a93c 100644 --- a/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr @@ -4,10 +4,9 @@ use dep::aztec::macros::aztec; contract TestLog { use std::meta::derive; use dep::aztec::prelude::PrivateSet; - use dep::aztec::protocol_types::{scalar::Scalar, address::AztecAddress, traits::Serialize}; - use dep::aztec::keys::{getters::get_public_keys, public_keys::IvpkM}; + use dep::aztec::protocol_types::{address::AztecAddress, traits::Serialize}; + use dep::aztec::keys::getters::get_public_keys; use dep::value_note::value_note::ValueNote; - use dep::aztec::encrypted_logs::payload::compute_incoming_body_ciphertext; use dep::aztec::encrypted_logs::encrypted_event_emission::encode_and_encrypt_event_with_randomness; use dep::aztec::unencrypted_logs::unencrypted_event_emission::encode_event; use dep::aztec::macros::{storage::storage, events::event, functions::{private, public}}; @@ -36,19 +35,6 @@ contract TestLog { // EXAMPLE_EVENT_0_BYTES_LEN + 16 global EXAMPLE_EVENT_0_CIPHERTEXT_BYTES_LEN = 144; - #[private] - fn compute_incoming_log_body_ciphertext( - secret: Scalar, - ivpk: IvpkM, - randomness: Field, - event_type_id: Field, - preimage: [Field; 2] - ) -> [u8; EXAMPLE_EVENT_0_CIPHERTEXT_BYTES_LEN] { - let event = ExampleEvent0 { value0: preimage[0], value1: preimage[1] }; - let plaintext = event.private_to_be_bytes(randomness); - compute_incoming_body_ciphertext(plaintext, secret, ivpk).as_array() - } - #[private] fn emit_encrypted_events(other: AztecAddress, randomness: [Field; 2], preimages: [Field; 4]) { let event0 = ExampleEvent0 { value0: preimages[0], value1: preimages[1] }; diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index 08f9c3b460c..d1eec6cabc4 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -99,9 +99,6 @@ export { Comparator, CompleteAddress, EncryptedL2BlockL2Logs, - EncryptedLogHeader, - EncryptedNoteLogIncomingBody, - EncryptedLogOutgoingBody, EventType, ExtendedNote, UniqueNote, @@ -134,7 +131,7 @@ export { merkleTreeIds, mockTx, mockEpochProofQuote, - TaggedLog, + EncryptedLogPayload, L1NotePayload, L1EventPayload, EpochProofQuote, diff --git a/yarn-project/circuit-types/src/logs/l1_payload/browserify-cipher.d.ts b/yarn-project/circuit-types/src/logs/l1_payload/browserify-cipher.d.ts deleted file mode 100644 index 5d393110202..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/browserify-cipher.d.ts +++ /dev/null @@ -1,78 +0,0 @@ -declare module 'browserify-cipher' { - import { type Cipher } from 'crypto'; - - /** - * Type representing supported cipher algorithms for encryption and decryption. - */ - type CipherTypes = 'aes-128-cbc'; - - /** - * Represents the options for cipher operations. - * Provides optional configuration settings to customize cipher behavior during encryption and decryption processes. - */ - interface CipherOptions { - /** - * Initialization vector used for encryption/decryption process. - */ - iv?: Buffer; - } - - /** - * Create a Cipher instance using the specified algorithm, key, and optional options. - * The function supports a limited set of algorithms defined by CipherTypes, such as 'aes-128-cbc'. - * The 'key' parameter must be a Buffer containing the secret key for encryption. - * The optional 'options' parameter can include an initialization vector (iv) as a Buffer. - * Throws an error if the specified algorithm is not supported or the provided key/iv are invalid. - * - * @param algorithm - The encryption algorithm to be used, as defined in CipherTypes. - * @param key - A Buffer containing the secret key for encryption. - * @param options - Optional configuration object with properties like 'iv' for initialization vector. - * @returns A Cipher instance configured with the specified algorithm, key, and options. - */ - function createCipher(algorithm: CipherTypes, key: Buffer, options?: CipherOptions): Cipher; - /** - * Create a Cipher instance with an explicit initialization vector (IV) for the specified algorithm and key. - * The 'algorithm' should be one of the supported cipher types, such as 'aes-128-cbc'. - * The 'key' and 'iv' must be provided as Buffers. The IV length should match the block size of the chosen algorithm. - * Throws an error if the provided algorithm is not supported, or the key or IV lengths are invalid. - * - * @param algorithm - The cipher algorithm to be used, such as 'aes-128-cbc'. - * @param key - A Buffer containing the encryption key. - * @param iv - A Buffer containing the initialization vector. - * @returns A Cipher instance initialized with the specified algorithm, key, and IV. - */ - function createCipheriv(algorithm: CipherTypes, key: Buffer, iv: Buffer): Cipher; - /** - * Create a Decipher object for the given algorithm and key, which can be used to decrypt data. - * The 'algorithm' must be one of the supported cipher types (e.g., 'aes-128-cbc'). - * The 'key' should be a Buffer containing the secret key for decryption. - * An optional 'options' object can be provided to specify additional properties such as IV (initialization vector). - * Throws an error if the inputs are invalid or the specified algorithm is not supported. - * - * @param algorithm - The cipher type to be used for decryption. - * @param key - A Buffer containing the secret key for decryption. - * @param options - An optional CipherOptions object with additional properties. - * @returns A Decipher object that can be used to decrypt data. - */ - function createDecipher(algorithm: CipherTypes, key: Buffer, options?: CipherOptions): Cipher; - /** - * Create a decipher object using the specified algorithm, key, and initialization vector (iv). - * The function allows for creating a custom decryption stream with the specific algorithm - * and provided parameters. It is useful for decrypting data that was encrypted with a custom - * initialization vector. Throws an error if the algorithm is not supported or invalid parameters are provided. - * - * @param algorithm - The encryption algorithm to be used, e.g., 'aes-128-cbc'. - * @param key - The encryption key in the form of a Buffer. - * @param iv - The initialization vector as a Buffer. - * @returns A Decipher object which can be used to decrypt data. - */ - function createDecipheriv(algorithm: CipherTypes, key: Buffer, iv: Buffer): Cipher; - /** - * Retrieves the list of supported cipher algorithms. - * This function returns an array of strings containing the names of all currently available - * cipher algorithms that can be used for encryption and decryption operations. - * - * @returns An array of strings representing the supported cipher algorithms. - */ - function getCiphers(): CipherTypes[]; -} diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypt_buffer.test.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypt_buffer.test.ts deleted file mode 100644 index ab318001fa2..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypt_buffer.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Fq, GrumpkinScalar } from '@aztec/circuits.js'; -import { Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { randomBytes } from '@aztec/foundation/crypto'; -import { updateInlineTestData } from '@aztec/foundation/testing'; - -import { decryptBuffer, encryptBuffer } from './encrypt_buffer.js'; -import { deriveAESSecret } from './encryption_utils.js'; - -describe('encrypt buffer', () => { - let grumpkin: Grumpkin; - - beforeAll(() => { - grumpkin = new Grumpkin(); - }); - - it('derive shared secret', () => { - // The following 2 are arbitrary fixed values - fixed in order to test a match with Noir - const ownerSecretKey = new Fq(0x23b3127c127b1f29a7adff5cccf8fb06649e7ca01d9de27b21624098b897babdn); - const ephSecretKey = new Fq(0x1fdd0dd8c99b21af8e00d2d130bdc263b36dadcbea84ac5ec9293a0660deca01n); - - const ownerPubKey = grumpkin.mul(Grumpkin.generator, ownerSecretKey); - const ephPubKey = grumpkin.mul(Grumpkin.generator, ephSecretKey); - - const secretBySender = deriveAESSecret(ephSecretKey, ownerPubKey); - const secretByReceiver = deriveAESSecret(ownerSecretKey, ephPubKey); - expect(secretBySender.toString('hex')).toEqual(secretByReceiver.toString('hex')); - - const byteArrayString = `[${secretBySender - .toString('hex') - .match(/.{1,2}/g)! - .map(byte => parseInt(byte, 16))}]`; - // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data - updateInlineTestData( - 'noir-projects/aztec-nr/aztec/src/keys/point_to_symmetric_key.nr', - 'key_from_typescript', - byteArrayString, - ); - }); - - it('convert to and from encrypted buffer', () => { - const data = randomBytes(253); - const ownerSecretKey = GrumpkinScalar.random(); - const ownerPubKey = grumpkin.mul(Grumpkin.generator, ownerSecretKey); - const ephSecretKey = GrumpkinScalar.random(); - const encrypted = encryptBuffer(data, ephSecretKey, ownerPubKey); - const decrypted = decryptBuffer(encrypted, ownerSecretKey); - expect(decrypted).not.toBeUndefined(); - expect(decrypted).toEqual(data); - }); - - it('decrypting gibberish returns undefined', () => { - const data = randomBytes(253); - const ownerSecretKey = GrumpkinScalar.random(); - const ephSecretKey = GrumpkinScalar.random(); - const ownerPubKey = grumpkin.mul(Grumpkin.generator, ownerSecretKey); - const encrypted = encryptBuffer(data, ephSecretKey, ownerPubKey); - - // Introduce gibberish. - const gibberish = Buffer.concat([randomBytes(8), encrypted.subarray(8)]); - - const decrypted = decryptBuffer(gibberish, ownerSecretKey); - expect(decrypted).toBeUndefined(); - }); -}); diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypt_buffer.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypt_buffer.ts deleted file mode 100644 index 10afdcf9deb..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypt_buffer.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { type GrumpkinScalar, type PublicKey } from '@aztec/circuits.js'; -import { Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { Point } from '@aztec/foundation/fields'; - -import { createCipheriv, createDecipheriv } from 'browserify-cipher'; - -import { deriveAESSecret } from './encryption_utils.js'; - -/** - * Encrypt a given data buffer using the owner's public key and an ephemeral private key. - * The encrypted data includes the original data, AES secret derived from ECDH shared secret, - * and the ephemeral public key. The encryption is done using the 'aes-128-cbc' algorithm - * with the provided curve instance for elliptic curve operations. - * - * @param data - The data buffer to be encrypted. - * @param ephSecretKey - The ephemeral secret key.. - * @param incomingViewingPublicKey - The note owner's incoming viewing public key. - * @returns A Buffer containing the encrypted data and the ephemeral public key. - */ -export function encryptBuffer(data: Buffer, ephSecretKey: GrumpkinScalar, incomingViewingPublicKey: PublicKey): Buffer { - const aesSecret = deriveAESSecret(ephSecretKey, incomingViewingPublicKey); - const aesKey = aesSecret.subarray(0, 16); - const iv = aesSecret.subarray(16, 32); - const cipher = createCipheriv('aes-128-cbc', aesKey, iv); - const plaintext = Buffer.concat([iv.subarray(0, 8), data]); - const curve = new Grumpkin(); - const ephPubKey = curve.mul(curve.generator(), ephSecretKey); - - // We encrypt eth pub key without the isInfinite flag because infinite point is not a valid pub key - return Buffer.concat([cipher.update(plaintext), cipher.final(), ephPubKey.toCompressedBuffer()]); -} - -/** - * Decrypts the given encrypted data buffer using the provided secret key. - * @param data - The encrypted data buffer to be decrypted. - * @param incomingViewingSecretKey - The secret key used for decryption. - * @returns The decrypted plaintext as a Buffer or undefined if decryption fails. - */ -export function decryptBuffer(data: Buffer, incomingViewingSecretKey: GrumpkinScalar): Buffer | undefined { - // Extract the ephemeral public key from the end of the data - const ephPubKey = Point.fromCompressedBuffer(data.subarray(-Point.COMPRESSED_SIZE_IN_BYTES)); - // Derive the AES secret key using the secret key and the ephemeral public key - const aesSecret = deriveAESSecret(incomingViewingSecretKey, ephPubKey); - const aesKey = aesSecret.subarray(0, 16); - const iv = aesSecret.subarray(16, 32); - const cipher = createDecipheriv('aes-128-cbc', aesKey, iv); - try { - const plaintext = Buffer.concat([cipher.update(data.subarray(0, -Point.COMPRESSED_SIZE_IN_BYTES)), cipher.final()]); - if (plaintext.subarray(0, 8).equals(iv.subarray(0, 8))) { - return plaintext.subarray(8); - } - } catch (e) { - return; - } -} diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_header.test.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_header.test.ts deleted file mode 100644 index 93df3fee728..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_header.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { AztecAddress, GrumpkinScalar } from '@aztec/circuits.js'; -import { Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { updateInlineTestData } from '@aztec/foundation/testing'; - -import { EncryptedLogHeader } from './encrypted_log_header.js'; - -describe('encrypt log header', () => { - let grumpkin: Grumpkin; - - beforeAll(() => { - grumpkin = new Grumpkin(); - }); - - it('encrypt and decrypt a log header', () => { - const ephSecretKey = GrumpkinScalar.random(); - const viewingSecretKey = GrumpkinScalar.random(); - - const ephPubKey = grumpkin.mul(Grumpkin.generator, ephSecretKey); - const viewingPubKey = grumpkin.mul(Grumpkin.generator, viewingSecretKey); - - const header = new EncryptedLogHeader(AztecAddress.random()); - - const encrypted = header.computeCiphertext(ephSecretKey, viewingPubKey); - - const recreated = EncryptedLogHeader.fromCiphertext(encrypted, viewingSecretKey, ephPubKey); - - expect(recreated.toBuffer()).toEqual(header.toBuffer()); - }); - - it('encrypt a log header, generate input for noir test', () => { - // The following 2 are arbitrary fixed values - fixed in order to test a match with Noir - const viewingSecretKey = new GrumpkinScalar(0x23b3127c127b1f29a7adff5cccf8fb06649e7ca01d9de27b21624098b897babdn); - const ephSecretKey = new GrumpkinScalar(0x1fdd0dd8c99b21af8e00d2d130bdc263b36dadcbea84ac5ec9293a0660deca01n); - - const viewingPubKey = grumpkin.mul(Grumpkin.generator, viewingSecretKey); - - const header = new EncryptedLogHeader(AztecAddress.fromBigInt(BigInt('0xdeadbeef'))); - - const encrypted = header.computeCiphertext(ephSecretKey, viewingPubKey); - - const byteArrayString = `[${encrypted - .toString('hex') - .match(/.{1,2}/g)! - .map(byte => parseInt(byte, 16))}]`; - - // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data - updateInlineTestData( - 'noir-projects/aztec-nr/aztec/src/encrypted_logs/header.nr', - 'expected_header_ciphertext_from_typescript', - byteArrayString, - ); - }); -}); diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_header.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_header.ts deleted file mode 100644 index a66e5d99167..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_header.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { AztecAddress, type GrumpkinScalar, type PublicKey } from '@aztec/circuits.js'; -import { Aes128 } from '@aztec/circuits.js/barretenberg'; - -import { deriveAESSecret } from './encryption_utils.js'; - -/** - * An encrypted log header, containing the address of the log along with utility - * functions to compute and decrypt its ciphertext. - * - * Using AES-128-CBC for encryption. - * Can be used for both incoming and outgoing logs. - * - */ -export class EncryptedLogHeader { - constructor(public readonly address: AztecAddress) {} - - /** - * Serializes the log header to a buffer - * - * @returns The serialized log header - */ - public toBuffer(): Buffer { - return this.address.toBuffer(); - } - - public static fromBuffer(buf: Buffer): EncryptedLogHeader { - return new EncryptedLogHeader(AztecAddress.fromBuffer(buf)); - } - - /** - * Computes the ciphertext of the encrypted log header - * - * @param secret - An ephemeral secret key - * @param publicKey - The incoming or outgoing viewing key of the "recipient" of this log - * @returns The ciphertext of the encrypted log header - */ - public computeCiphertext(secret: GrumpkinScalar, publicKey: PublicKey) { - const aesSecret = deriveAESSecret(secret, publicKey); - const key = aesSecret.subarray(0, 16); - const iv = aesSecret.subarray(16, 32); - - const aes128 = new Aes128(); - const buffer = this.toBuffer(); - return aes128.encryptBufferCBC(buffer, iv, key); - } - - /** - * - * @param ciphertext - The ciphertext buffer - * @param secret - The private key matching the public key used in encryption - * @param publicKey - The public key generated with the ephemeral secret key used in encryption - * e.g., eph_sk * G - * @returns - */ - public static fromCiphertext( - ciphertext: Buffer | bigint[], - secret: GrumpkinScalar, - publicKey: PublicKey, - ): EncryptedLogHeader { - const input = Buffer.isBuffer(ciphertext) ? ciphertext : Buffer.from(ciphertext.map((x: bigint) => Number(x))); - - const aesSecret = deriveAESSecret(secret, publicKey); - const key = aesSecret.subarray(0, 16); - const iv = aesSecret.subarray(16, 32); - - const aes128 = new Aes128(); - const buffer = aes128.decryptBufferCBC(input, iv, key); - return EncryptedLogHeader.fromBuffer(buffer); - } -} diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_event_log_incoming_body.test.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_event_log_incoming_body.test.ts deleted file mode 100644 index ffac80e9f94..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_event_log_incoming_body.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Fr, GrumpkinScalar } from '@aztec/circuits.js'; -import { Grumpkin } from '@aztec/circuits.js/barretenberg'; - -import { Event } from '../payload.js'; -import { EncryptedEventLogIncomingBody } from './encrypted_event_log_incoming_body.js'; - -describe('encrypt log incoming body', () => { - let grumpkin: Grumpkin; - - beforeAll(() => { - grumpkin = new Grumpkin(); - }); - - it('encrypt and decrypt an event log incoming body', () => { - const ephSecretKey = GrumpkinScalar.random(); - const viewingSecretKey = GrumpkinScalar.random(); - - const ephPubKey = grumpkin.mul(Grumpkin.generator, ephSecretKey); - const viewingPubKey = grumpkin.mul(Grumpkin.generator, viewingSecretKey); - - const event = Event.random(); - const randomness = Fr.random(); - const eventTypeId = Fr.random(); - - const body = new EncryptedEventLogIncomingBody(randomness, eventTypeId, event); - - const encrypted = body.computeCiphertext(ephSecretKey, viewingPubKey); - - const recreated = EncryptedEventLogIncomingBody.fromCiphertext(encrypted, viewingSecretKey, ephPubKey); - - expect(recreated.toBuffer()).toEqual(body.toBuffer()); - }); -}); diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_event_log_incoming_body.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_event_log_incoming_body.ts deleted file mode 100644 index 549d454ae11..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_event_log_incoming_body.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Fr, type GrumpkinScalar, type PublicKey } from '@aztec/circuits.js'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; - -import { Event } from '../payload.js'; -import { EncryptedLogIncomingBody } from './encrypted_log_incoming_body.js'; - -export class EncryptedEventLogIncomingBody extends EncryptedLogIncomingBody { - constructor(public randomness: Fr, public eventTypeId: Fr, public event: Event) { - super(); - } - - /** - * Serializes the log body to a buffer WITHOUT the length of the event buffer - * - * @returns The serialized log body - */ - public toBuffer(): Buffer { - const eventBufferWithoutLength = this.event.toBuffer().subarray(4); - return serializeToBuffer(this.randomness, this.eventTypeId, eventBufferWithoutLength); - } - - /** - * Deserialized the log body from a buffer WITHOUT the length of the event buffer - * - * @param buf - The buffer to deserialize - * @returns The deserialized log body - */ - public static fromBuffer(buf: Buffer): EncryptedEventLogIncomingBody { - const reader = BufferReader.asReader(buf); - const randomness = Fr.fromBuffer(reader); - const eventTypeId = Fr.fromBuffer(reader); - - // 2 Fields (randomness and event type id) are not included in the event buffer - const fieldsInEvent = reader.getLength() / 32 - 2; - const event = new Event(reader.readArray(fieldsInEvent, Fr)); - - return new EncryptedEventLogIncomingBody(randomness, eventTypeId, event); - } - - /** - * Decrypts a log body - * - * @param ciphertext - The ciphertext buffer - * @param ivskOrEphSk - The private key matching the public key used in encryption (the viewing secret key or ephemeral secret key) - * @param ephPkOrIvpk - The public key generated with the ephemeral secret key used in encryption - * - * The "odd" input stems from ivsk * ephPk == ivpk * ephSk producing the same value. - * This is used to allow for the same decryption function to be used by both the sender and the recipient. - * - * @returns The decrypted log body - */ - public static fromCiphertext( - ciphertext: Buffer | bigint[], - ivskOrEphSk: GrumpkinScalar, - ephPkOrIvpk: PublicKey, - ): EncryptedEventLogIncomingBody { - const buffer = super.fromCiphertextToBuffer(ciphertext, ivskOrEphSk, ephPkOrIvpk); - return EncryptedEventLogIncomingBody.fromBuffer(buffer); - } -} diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_log_incoming_body.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_log_incoming_body.ts deleted file mode 100644 index 587530882c0..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_log_incoming_body.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { type GrumpkinScalar, type PublicKey } from '@aztec/circuits.js'; -import { Aes128 } from '@aztec/circuits.js/barretenberg'; - -import { deriveAESSecret } from '../encryption_utils.js'; - -export abstract class EncryptedLogIncomingBody { - public abstract toBuffer(): Buffer; - - /** - * Decrypts a log body - * - * @param ciphertext - The ciphertext buffer - * @param ivskOrEphSk - The private key matching the public key used in encryption (the viewing secret key or ephemeral secret key) - * @param ephPkOrIvpk - The public key generated with the ephemeral secret key used in encryption - * - * The "odd" input stems from ivsk * ephPk == ivpk * ephSk producing the same value. - * This is used to allow for the same decryption function to be used by both the sender and the recipient. - * - * @returns The decrypted log body as a buffer - */ - protected static fromCiphertextToBuffer( - ciphertext: Buffer | bigint[], - ivskOrEphSk: GrumpkinScalar, - ephPkOrIvpk: PublicKey, - ): Buffer { - const input = Buffer.isBuffer(ciphertext) ? ciphertext : Buffer.from(ciphertext.map((x: bigint) => Number(x))); - - const aesSecret = deriveAESSecret(ivskOrEphSk, ephPkOrIvpk); - const key = aesSecret.subarray(0, 16); - const iv = aesSecret.subarray(16, 32); - - const buffer = new Aes128().decryptBufferCBC(input, iv, key); - return buffer; - } - - /** - * Encrypts a log body - * - * @param ephSk - The ephemeral secret key - * @param ivpk - The application scoped incoming viewing key for the recipient of this log - * - * @returns The ciphertext of the encrypted log body - */ - public computeCiphertext(ephSk: GrumpkinScalar, ivpk: PublicKey) { - const aesSecret = deriveAESSecret(ephSk, ivpk); - const key = aesSecret.subarray(0, 16); - const iv = aesSecret.subarray(16, 32); - - const aes128 = new Aes128(); - const buffer = this.toBuffer(); - - return aes128.encryptBufferCBC(buffer, iv, key); - } -} diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_note_log_incoming_body.test.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_note_log_incoming_body.test.ts deleted file mode 100644 index fa27ba06ca4..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_note_log_incoming_body.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Fr, GrumpkinScalar } from '@aztec/circuits.js'; -import { Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { NoteSelector } from '@aztec/foundation/abi'; -import { updateInlineTestData } from '@aztec/foundation/testing'; - -import { Note } from '../payload.js'; -import { EncryptedNoteLogIncomingBody } from './encrypted_note_log_incoming_body.js'; - -describe('encrypt log incoming body', () => { - let grumpkin: Grumpkin; - - beforeAll(() => { - grumpkin = new Grumpkin(); - }); - - it('encrypt and decrypt a note log incoming body', () => { - const ephSecretKey = GrumpkinScalar.random(); - const viewingSecretKey = GrumpkinScalar.random(); - - const ephPubKey = grumpkin.mul(Grumpkin.generator, ephSecretKey); - const viewingPubKey = grumpkin.mul(Grumpkin.generator, viewingSecretKey); - - const note = Note.random(); - const storageSlot = Fr.random(); - const noteTypeId = NoteSelector.random(); - - const body = new EncryptedNoteLogIncomingBody(storageSlot, noteTypeId, note); - - const encrypted = body.computeCiphertext(ephSecretKey, viewingPubKey); - - const recreated = EncryptedNoteLogIncomingBody.fromCiphertext(encrypted, viewingSecretKey, ephPubKey); - - expect(recreated.toBuffer()).toEqual(body.toBuffer()); - }); - - it('encrypt a note log incoming body, generate input for noir test', () => { - // All the values in this test were arbitrarily set and copied over to `incoming_body.nr` - const ephSecretKey = new GrumpkinScalar(0x23b3127c127b1f29a7adff5cccf8fb06649e7ca01d9de27b21624098b897babdn); - const viewingSecretKey = new GrumpkinScalar(0x1fdd0dd8c99b21af8e00d2d130bdc263b36dadcbea84ac5ec9293a0660deca01n); - - const viewingPubKey = grumpkin.mul(Grumpkin.generator, viewingSecretKey); - - const note = new Note([new Fr(1), new Fr(2), new Fr(3)]); - const noteTypeId = new NoteSelector(1); - const storageSlot = new Fr(2); - - const body = new EncryptedNoteLogIncomingBody(storageSlot, noteTypeId, note); - - const encrypted = body.computeCiphertext(ephSecretKey, viewingPubKey); - - const byteArrayString = `[${encrypted - .toString('hex') - .match(/.{1,2}/g)! - .map(byte => parseInt(byte, 16))}]`; - - // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data - updateInlineTestData( - 'noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr', - 'note_body_ciphertext_from_typescript', - byteArrayString, - ); - }); -}); diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_note_log_incoming_body.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_note_log_incoming_body.ts deleted file mode 100644 index 344b84105ee..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/encrypted_note_log_incoming_body.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Fr, type GrumpkinScalar, type PublicKey } from '@aztec/circuits.js'; -import { NoteSelector } from '@aztec/foundation/abi'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; - -import { Note } from '../payload.js'; -import { EncryptedLogIncomingBody } from './encrypted_log_incoming_body.js'; - -export class EncryptedNoteLogIncomingBody extends EncryptedLogIncomingBody { - constructor(public storageSlot: Fr, public noteTypeId: NoteSelector, public note: Note) { - super(); - } - - /** - * Serializes the log body to a buffer WITHOUT the length of the note buffer - * - * @returns The serialized log body - */ - public toBuffer(): Buffer { - const noteBufferWithoutLength = this.note.toBuffer().subarray(4); - // Note: We serialize note type to field first because that's how it's done in Noir - return serializeToBuffer(this.storageSlot, this.noteTypeId.toField(), noteBufferWithoutLength); - } - - /** - * Deserialized the log body from a buffer WITHOUT the length of the note buffer - * - * @param buf - The buffer to deserialize - * @returns The deserialized log body - */ - public static fromBuffer(buf: Buffer): EncryptedNoteLogIncomingBody { - const reader = BufferReader.asReader(buf); - const storageSlot = Fr.fromBuffer(reader); - const noteTypeId = NoteSelector.fromField(Fr.fromBuffer(reader)); - - // 2 Fields (storage slot and note type id) are not included in the note buffer - const fieldsInNote = reader.getLength() / 32 - 2; - const note = new Note(reader.readArray(fieldsInNote, Fr)); - - return new EncryptedNoteLogIncomingBody(storageSlot, noteTypeId, note); - } - - /** - * Decrypts a log body - * - * @param ciphertext - The ciphertext buffer - * @param ivskOrEphSk - The private key matching the public key used in encryption (the viewing secret key or ephemeral secret key) - * @param ephPkOrIvpk - The public key generated with the ephemeral secret key used in encryption - * - * The "odd" input stems from ivsk * ephPk == ivpk * ephSk producing the same value. - * This is used to allow for the same decryption function to be used by both the sender and the recipient. - * - * @returns The decrypted log body - */ - public static fromCiphertext( - ciphertext: Buffer | bigint[], - ivskOrEphSk: GrumpkinScalar, - ephPkOrIvpk: PublicKey, - ): EncryptedNoteLogIncomingBody { - const buffer = super.fromCiphertextToBuffer(ciphertext, ivskOrEphSk, ephPkOrIvpk); - return EncryptedNoteLogIncomingBody.fromBuffer(buffer); - } -} diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/index.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/index.ts deleted file mode 100644 index 6a8e480e25f..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_incoming_body/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './encrypted_event_log_incoming_body.js'; -export * from './encrypted_note_log_incoming_body.js'; -export * from './encrypted_log_incoming_body.js'; diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_outgoing_body.test.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_outgoing_body.test.ts deleted file mode 100644 index ccda71df38d..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_outgoing_body.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { AztecAddress, GrumpkinScalar } from '@aztec/circuits.js'; -import { Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { updateInlineTestData } from '@aztec/foundation/testing'; - -import { EncryptedLogOutgoingBody } from './encrypted_log_outgoing_body.js'; - -describe('encrypt log outgoing body', () => { - let grumpkin: Grumpkin; - - beforeAll(() => { - grumpkin = new Grumpkin(); - }); - - it('encrypt and decrypt a log outgoing body', () => { - const ephSk = GrumpkinScalar.random(); - const recipientIvsk = GrumpkinScalar.random(); - const senderOvskApp = GrumpkinScalar.random(); - - const ephPk = grumpkin.mul(Grumpkin.generator, ephSk); - const recipientIvpk = grumpkin.mul(Grumpkin.generator, recipientIvsk); - - const recipientAddress = AztecAddress.random(); - - const body = new EncryptedLogOutgoingBody(ephSk, recipientAddress, recipientIvpk); - - const encrypted = body.computeCiphertext(senderOvskApp, ephPk); - - const recreated = EncryptedLogOutgoingBody.fromCiphertext(encrypted, senderOvskApp, ephPk); - - expect(recreated.toBuffer()).toEqual(body.toBuffer()); - }); - - it('encrypt a log outgoing body, generate input for noir test', () => { - const ephSk = new GrumpkinScalar(0x0f096b423017226a18461115fa8d34bbd0d302ee245dfaf2807e604eec4715fen); - const recipientIvsk = new GrumpkinScalar(0x0f4d97c25d578f9348251a71ca17ae314828f8f95676ebb481df163f87fd4022n); - const senderOvskApp = new GrumpkinScalar(0x089c6887cb1446d86c64e81afc78048b74d2e28c6bc5176ac02cf7c7d36a444en); - - const ephPk = grumpkin.mul(Grumpkin.generator, ephSk); - const recipientIvpk = grumpkin.mul(Grumpkin.generator, recipientIvsk); - - const recipientAddress = AztecAddress.fromBigInt(BigInt('0xdeadbeef')); - - const body = new EncryptedLogOutgoingBody(ephSk, recipientAddress, recipientIvpk); - - const encrypted = body.computeCiphertext(senderOvskApp, ephPk); - - const recreated = EncryptedLogOutgoingBody.fromCiphertext(encrypted, senderOvskApp, ephPk); - - expect(recreated.toBuffer()).toEqual(body.toBuffer()); - - const byteArrayString = `[${encrypted - .toString('hex') - .match(/.{1,2}/g)! - .map(byte => parseInt(byte, 16))}]`; - - // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data - updateInlineTestData( - 'noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr', - 'outgoing_body_ciphertext_from_typescript', - byteArrayString, - ); - }); -}); diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_outgoing_body.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_outgoing_body.ts deleted file mode 100644 index 69507f1a9c2..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_outgoing_body.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { AztecAddress, Fr, GeneratorIndex, GrumpkinScalar, Point, type PublicKey } from '@aztec/circuits.js'; -import { Aes128 } from '@aztec/circuits.js/barretenberg'; -import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; - -export class EncryptedLogOutgoingBody { - constructor(public ephSk: GrumpkinScalar, public recipient: AztecAddress, public recipientIvpk: PublicKey) {} - - /** - * Serializes the log body - * - * @returns The serialized log body - */ - public toBuffer(): Buffer { - // The serialization of Fq is [high, low] check `outgoing_body.nr` - const ephSkBytes = serializeToBuffer([this.ephSk.hi, this.ephSk.lo]); - return serializeToBuffer(ephSkBytes, this.recipient, this.recipientIvpk.toCompressedBuffer()); - } - - /** - * Deserialized the log body from a buffer - * - * @param buf - The buffer to deserialize - * @returns The deserialized log body - */ - public static fromBuffer(buf: Buffer): EncryptedLogOutgoingBody { - const reader = BufferReader.asReader(buf); - const high = reader.readObject(Fr); - const low = reader.readObject(Fr); - const ephSk = GrumpkinScalar.fromHighLow(high, low); - const recipient = reader.readObject(AztecAddress); - const recipientIvpk = Point.fromCompressedBuffer(reader.readBytes(Point.COMPRESSED_SIZE_IN_BYTES)); // PublicKey = Point - - return new EncryptedLogOutgoingBody(ephSk, recipient, recipientIvpk); - } - - /** - * Encrypts a log body - * - * @param ovskApp - The app siloed outgoing viewing secret key - * @param ephPk - The ephemeral public key - * - * @returns The ciphertext of the encrypted log body - */ - public computeCiphertext(ovskApp: GrumpkinScalar, ephPk: PublicKey) { - // We could use `ephSk` and compute `ephPk` from it. - // We mainly provide it to keep the same api and potentially slight optimization as we can reuse it. - - const aesSecret = EncryptedLogOutgoingBody.derivePoseidonAESSecret(ovskApp, ephPk); - - const key = aesSecret.subarray(0, 16); - const iv = aesSecret.subarray(16, 32); - - const aes128 = new Aes128(); - const buffer = this.toBuffer(); - - return aes128.encryptBufferCBC(buffer, iv, key); - } - - /** - * Decrypts a log body - * - * @param ciphertext - The ciphertext buffer - * @param ovskApp - The app siloed outgoing viewing secret key - * @param ephPk - The ephemeral public key - * - * @returns The decrypted log body - */ - public static fromCiphertext( - ciphertext: Buffer | bigint[], - ovskApp: GrumpkinScalar, - ephPk: PublicKey, - ): EncryptedLogOutgoingBody { - const input = Buffer.isBuffer(ciphertext) ? ciphertext : Buffer.from(ciphertext.map((x: bigint) => Number(x))); - - const aesSecret = EncryptedLogOutgoingBody.derivePoseidonAESSecret(ovskApp, ephPk); - const key = aesSecret.subarray(0, 16); - const iv = aesSecret.subarray(16, 32); - - const aes128 = new Aes128(); - const buffer = aes128.decryptBufferCBC(input, iv, key); - - return EncryptedLogOutgoingBody.fromBuffer(buffer); - } - - /** - * Derives an AES symmetric key from the app siloed outgoing viewing secret key - * and the ephemeral public key using poseidon. - * - * @param ovskApp - The app siloed outgoing viewing secret key - * @param ephPk - The ephemeral public key - * @returns The derived AES symmetric key - */ - private static derivePoseidonAESSecret(ovskApp: GrumpkinScalar, ephPk: PublicKey) { - // For performance reasons, we do NOT use the usual `deriveAESSecret` function here and instead we compute it using - // poseidon. Note that we can afford to use poseidon here instead of deriving shared secret using Diffie-Hellman - // because for outgoing we are encrypting for ourselves and hence we don't need to perform a key exchange. - return poseidon2HashWithSeparator( - [ovskApp.hi, ovskApp.lo, ephPk.x, ephPk.y], - GeneratorIndex.SYMMETRIC_KEY, - ).toBuffer(); - } -} diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.test.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.test.ts new file mode 100644 index 00000000000..2c17b670f58 --- /dev/null +++ b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.test.ts @@ -0,0 +1,92 @@ +import { AztecAddress, KeyValidationRequest, computeOvskApp, derivePublicKeyFromSecretKey } from '@aztec/circuits.js'; +import { randomBytes } from '@aztec/foundation/crypto'; +import { Fr, GrumpkinScalar } from '@aztec/foundation/fields'; +import { updateInlineTestData } from '@aztec/foundation/testing'; + +import { EncryptedLogPayload } from './encrypted_log_payload.js'; + +// placeholder value until tagging is implemented +const PLACEHOLDER_TAG = new Fr(33); + +describe('EncryptedLogPayload', () => { + describe('encrypt and decrypt a full log', () => { + let ovskM: GrumpkinScalar; + let ivskM: GrumpkinScalar; + + let original: EncryptedLogPayload; + let encrypted: Buffer; + + beforeAll(() => { + const incomingBodyPlaintext = randomBytes(128); + const contract = AztecAddress.random(); + original = new EncryptedLogPayload(PLACEHOLDER_TAG, PLACEHOLDER_TAG, contract, incomingBodyPlaintext); + + ovskM = GrumpkinScalar.random(); + ivskM = GrumpkinScalar.random(); + + const ovKeys = getKeyValidationRequest(ovskM, contract); + + const ephSk = GrumpkinScalar.random(); + + const recipientAddress = AztecAddress.random(); + const ivpk = derivePublicKeyFromSecretKey(ivskM); + + encrypted = original.encrypt(ephSk, recipientAddress, ivpk, ovKeys); + }); + + it('decrypt a log as incoming', () => { + const recreated = EncryptedLogPayload.decryptAsIncoming(encrypted, ivskM); + + expect(recreated?.toBuffer()).toEqual(original.toBuffer()); + }); + + it('decrypt a log as outgoing', () => { + const recreated = EncryptedLogPayload.decryptAsOutgoing(encrypted, ovskM); + + expect(recreated?.toBuffer()).toEqual(original.toBuffer()); + }); + }); + + it('encrypted tagged log matches Noir', () => { + // All the values in this test were arbitrarily set and copied over to `payload.nr` + const contract = AztecAddress.fromString('0x10f48cd9eff7ae5b209c557c70de2e657ee79166868676b787e9417e19260e04'); + const plaintext = Buffer.from( + '00000001301640ceea758391b2e161c92c0513f129020f4125256afdae2646ce31099f5c10f48cd9eff7ae5b209c557c70de2e657ee79166868676b787e9417e19260e040fe46be583b71f4ab5b70c2657ff1d05cccf1d292a9369628d1a194f944e659900001027', + 'hex', + ); + const log = new EncryptedLogPayload(new Fr(0), new Fr(0), contract, plaintext); + + const ovskM = new GrumpkinScalar(0x06b76394ac57b8a18ceb08b14ed15b5b778d5c506b4cfb7edc203324eab27c05n); + const ivskM = new GrumpkinScalar(0x03fd94b1101e834e829cda4f227043f60490b5c7b3073875f5bfbe5028ed05ccn); + const ovKeys = getKeyValidationRequest(ovskM, contract); + const ephSk = new GrumpkinScalar(0x1358d15019d4639393d62b97e1588c095957ce74a1c32d6ec7d62fe6705d9538n); + + const recipientAddress = AztecAddress.fromString( + '0x10ee41ee4b62703b16f61e03cb0d88c4b306a9eb4a6ceeb2aff13428541689a2', + ); + + const ivpk = derivePublicKeyFromSecretKey(ivskM); + + const encrypted = log.encrypt(ephSk, recipientAddress, ivpk, ovKeys).toString('hex'); + + expect(encrypted).toMatchInlineSnapshot( + `"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d460c0e434d846ec1ea286e4090eb56376ff27bddc1aacae1d856549f701fa77e4f33ba2f47fdac6370f13bc5f16bbae857bbe6ab3ee4ea2a339192eef22a47ce0df4426fc314cb6294ccf291b79c1d8d362cdcc223e51020ccd3318e7052ca74f1fe922ad914bd46e4b6abcd681b63ab1c5bf4151e82f00548ae7c61c59df8c117c14c2e8d9046d32d43a7da818c68be296ef9d1446a87a450eb3f6550200d2663915b0bad97e7f7419975e5a740efb67eeb5304a90808a004ebfc156054a1459191d7fea175f6c64159b3c25a13790cca7250c30e3c80698e64565a6c9ddb16ac1479c3199fec02464b2a252202119514b02012cc387579220f03587b40444ae93f3b83dec2c0a76ed90a804981ac3d2b0c62a5cbbf9aa19604ef5f303c9f21de8e6649c1ec91c5a0d8fe71f319f4fbc0de230772652398977018205e8a47a05b4483d9758c139325c5c0152bacefefcd0f6e4c1ad32a75040f8791f7254954a495fa2300cd69b28f686264fac19a88afb16de1cffc93fafabd759365e684"`, + ); + + const byteArrayString = `[${encrypted.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16))}]`; + + // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data + updateInlineTestData( + 'noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr', + 'encrypted_log_from_typescript', + byteArrayString, + ); + }); + + const getKeyValidationRequest = (ovskM: GrumpkinScalar, app: AztecAddress) => { + const ovskApp = computeOvskApp(ovskM, app); + const ovpkM = derivePublicKeyFromSecretKey(ovskM); + + return new KeyValidationRequest(ovpkM, ovskApp); + }; +}); diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.ts new file mode 100644 index 00000000000..4deecd47968 --- /dev/null +++ b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.ts @@ -0,0 +1,210 @@ +import { + AztecAddress, + Fr, + GrumpkinScalar, + type KeyValidationRequest, + NotOnCurveError, + Point, + type PublicKey, + computeOvskApp, + derivePublicKeyFromSecretKey, +} from '@aztec/circuits.js'; +import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; + +import { decrypt, encrypt } from './encryption_util.js'; +import { derivePoseidonAESSecret } from './shared_secret_derivation.js'; + +// Both the incoming and the outgoing header are 48 bytes../shared_secret_derivation.js +// 32 bytes for the address, and 16 bytes padding to follow PKCS#7 +const HEADER_SIZE = 48; + +// The outgoing body is constant size of 144 bytes. +// 128 bytes for the secret key, address and public key, and 16 bytes padding to follow PKCS#7 +const OUTGOING_BODY_SIZE = 144; + +/** + * Encrypted log payload with a tag used for retrieval by clients. + */ +export class EncryptedLogPayload { + constructor( + public readonly incomingTag: Fr, + public readonly outgoingTag: Fr, + public readonly contractAddress: AztecAddress, + public readonly incomingBodyPlaintext: Buffer, + ) {} + + public encrypt( + ephSk: GrumpkinScalar, + recipient: AztecAddress, + ivpk: PublicKey, + ovKeys: KeyValidationRequest, + ): Buffer { + if (ivpk.isZero()) { + throw new Error(`Attempting to encrypt an event log with a zero ivpk.`); + } + + const ephPk = derivePublicKeyFromSecretKey(ephSk); + const incomingHeaderCiphertext = encrypt(this.contractAddress.toBuffer(), ephSk, ivpk); + const outgoingHeaderCiphertext = encrypt(this.contractAddress.toBuffer(), ephSk, ovKeys.pkM); + + if (incomingHeaderCiphertext.length !== HEADER_SIZE) { + throw new Error(`Invalid incoming header size: ${incomingHeaderCiphertext.length}`); + } + if (outgoingHeaderCiphertext.length !== HEADER_SIZE) { + throw new Error(`Invalid outgoing header size: ${outgoingHeaderCiphertext.length}`); + } + + const incomingBodyCiphertext = encrypt(this.incomingBodyPlaintext, ephSk, ivpk); + // The serialization of Fq is [high, low] check `outgoing_body.nr` + const outgoingBodyPlaintext = serializeToBuffer(ephSk.hi, ephSk.lo, recipient, ivpk.toCompressedBuffer()); + const outgoingBodyCiphertext = encrypt( + outgoingBodyPlaintext, + ovKeys.skAppAsGrumpkinScalar, + ephPk, + derivePoseidonAESSecret, + ); + + if (outgoingBodyCiphertext.length !== OUTGOING_BODY_SIZE) { + throw new Error(`Invalid outgoing body size: ${outgoingBodyCiphertext.length}`); + } + + return serializeToBuffer( + this.incomingTag, + this.outgoingTag, + ephPk.toCompressedBuffer(), + incomingHeaderCiphertext, + outgoingHeaderCiphertext, + outgoingBodyCiphertext, + incomingBodyCiphertext, + ); + } + + /** + * Decrypts a ciphertext as an incoming log. + * + * This is executable by the recipient of the note, and uses the ivsk to decrypt the payload. + * The outgoing parts of the log are ignored entirely. + * + * Produces the same output as `decryptAsOutgoing`. + * + * @param ciphertext - The ciphertext for the log + * @param ivsk - The incoming viewing secret key, used to decrypt the logs + * @returns The decrypted log payload + */ + public static decryptAsIncoming( + ciphertext: Buffer | BufferReader, + ivsk: GrumpkinScalar, + ): EncryptedLogPayload | undefined { + const reader = BufferReader.asReader(ciphertext); + + try { + const incomingTag = reader.readObject(Fr); + const outgoingTag = reader.readObject(Fr); + + const ephPk = Point.fromCompressedBuffer(reader.readBytes(Point.COMPRESSED_SIZE_IN_BYTES)); + + const incomingHeader = decrypt(reader.readBytes(HEADER_SIZE), ivsk, ephPk); + + // Skipping the outgoing header and body + reader.readBytes(HEADER_SIZE); + reader.readBytes(OUTGOING_BODY_SIZE); + + // The incoming can be of variable size, so we read until the end + const incomingBodyPlaintext = decrypt(reader.readToEnd(), ivsk, ephPk); + + return new EncryptedLogPayload( + incomingTag, + outgoingTag, + AztecAddress.fromBuffer(incomingHeader), + incomingBodyPlaintext, + ); + } catch (e: any) { + // Following error messages are expected to occur when decryption fails + if ( + !(e instanceof NotOnCurveError) && + !e.message.endsWith('is greater or equal to field modulus.') && + !e.message.startsWith('Invalid AztecAddress length') && + !e.message.startsWith('Selector must fit in') && + !e.message.startsWith('Attempted to read beyond buffer length') + ) { + // If we encounter an unexpected error, we rethrow it + throw e; + } + return; + } + } + + /** + * Decrypts a ciphertext as an outgoing log. + * + * This is executable by the sender of the event, and uses the ovsk to decrypt the payload. + * The outgoing parts are decrypted to retrieve information that allows the sender to + * decrypt the incoming log, and learn about the event contents. + * + * Produces the same output as `decryptAsIncoming`. + * + * @param ciphertext - The ciphertext for the log + * @param ovsk - The outgoing viewing secret key, used to decrypt the logs + * @returns The decrypted log payload + */ + public static decryptAsOutgoing( + ciphertext: Buffer | BufferReader, + ovsk: GrumpkinScalar, + ): EncryptedLogPayload | undefined { + const reader = BufferReader.asReader(ciphertext); + + try { + const incomingTag = reader.readObject(Fr); + const outgoingTag = reader.readObject(Fr); + + const ephPk = Point.fromCompressedBuffer(reader.readBytes(Point.COMPRESSED_SIZE_IN_BYTES)); + + // We skip the incoming header + reader.readBytes(HEADER_SIZE); + + const outgoingHeader = decrypt(reader.readBytes(HEADER_SIZE), ovsk, ephPk); + const contractAddress = AztecAddress.fromBuffer(outgoingHeader); + + const ovskApp = computeOvskApp(ovsk, contractAddress); + + let ephSk: GrumpkinScalar; + let recipientIvpk: PublicKey; + { + const outgoingBody = decrypt(reader.readBytes(OUTGOING_BODY_SIZE), ovskApp, ephPk, derivePoseidonAESSecret); + const obReader = BufferReader.asReader(outgoingBody); + + // From outgoing body we extract ephSk, recipient and recipientIvpk + ephSk = GrumpkinScalar.fromHighLow(obReader.readObject(Fr), obReader.readObject(Fr)); + const _recipient = obReader.readObject(AztecAddress); + recipientIvpk = Point.fromCompressedBuffer(obReader.readBytes(Point.COMPRESSED_SIZE_IN_BYTES)); + } + + // Now we decrypt the incoming body using the ephSk and recipientIvpk + const incomingBody = decrypt(reader.readToEnd(), ephSk, recipientIvpk); + + return new EncryptedLogPayload(incomingTag, outgoingTag, contractAddress, incomingBody); + } catch (e: any) { + // Following error messages are expected to occur when decryption fails + if ( + !(e instanceof NotOnCurveError) && + !e.message.endsWith('is greater or equal to field modulus.') && + !e.message.startsWith('Invalid AztecAddress length') && + !e.message.startsWith('Selector must fit in') && + !e.message.startsWith('Attempted to read beyond buffer length') + ) { + // If we encounter an unexpected error, we rethrow it + throw e; + } + return; + } + } + + public toBuffer() { + return serializeToBuffer( + this.incomingTag, + this.outgoingTag, + this.contractAddress.toBuffer(), + this.incomingBodyPlaintext, + ); + } +} diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encryption_util.ts b/yarn-project/circuit-types/src/logs/l1_payload/encryption_util.ts new file mode 100644 index 00000000000..ed10ad06ff0 --- /dev/null +++ b/yarn-project/circuit-types/src/logs/l1_payload/encryption_util.ts @@ -0,0 +1,49 @@ +import { type GrumpkinScalar, type PublicKey } from '@aztec/circuits.js'; +import { Aes128 } from '@aztec/circuits.js/barretenberg'; + +import { deriveDiffieHellmanAESSecret } from './shared_secret_derivation.js'; + +/** + * Encrypts the plaintext using the secret key and public key + * + * @param plaintext - The plaintext buffer + * @param secret - The secret key used to derive the AES secret + * @param publicKey - Public key used to derived the AES secret + * @param deriveSecret - Function to derive the AES secret from the ephemeral secret key and public key + * @returns The ciphertext + */ +export function encrypt( + plaintext: Buffer, + secret: GrumpkinScalar, + publicKey: PublicKey, + deriveSecret: (secret: GrumpkinScalar, publicKey: PublicKey) => Buffer = deriveDiffieHellmanAESSecret, +): Buffer { + const aesSecret = deriveSecret(secret, publicKey); + const key = aesSecret.subarray(0, 16); + const iv = aesSecret.subarray(16, 32); + + const aes128 = new Aes128(); + return aes128.encryptBufferCBC(plaintext, iv, key); +} + +/** + * Decrypts the ciphertext using the secret key and public key + * @param ciphertext - The ciphertext buffer + * @param secret - The secret key used to derive the AES secret + * @param publicKey - The public key used to derive the AES secret + * @param deriveSecret - Function to derive the AES secret from the ephemeral secret key and public key + * @returns + */ +export function decrypt( + ciphertext: Buffer, + secret: GrumpkinScalar, + publicKey: PublicKey, + deriveSecret: (secret: GrumpkinScalar, publicKey: PublicKey) => Buffer = deriveDiffieHellmanAESSecret, +): Buffer { + const aesSecret = deriveSecret(secret, publicKey); + const key = aesSecret.subarray(0, 16); + const iv = aesSecret.subarray(16, 32); + + const aes128 = new Aes128(); + return aes128.decryptBufferCBC(ciphertext, iv, key); +} diff --git a/yarn-project/circuit-types/src/logs/l1_payload/index.ts b/yarn-project/circuit-types/src/logs/l1_payload/index.ts index a8fe71dbc5d..da47398610e 100644 --- a/yarn-project/circuit-types/src/logs/l1_payload/index.ts +++ b/yarn-project/circuit-types/src/logs/l1_payload/index.ts @@ -1,8 +1,4 @@ -export * from './encrypt_buffer.js'; -export * from './payload.js'; -export * from './l1_event_payload.js'; +export * from './encrypted_log_payload.js'; export * from './l1_note_payload.js'; -export * from './tagged_log.js'; -export * from './encrypted_log_incoming_body/index.js'; -export * from './encrypted_log_header.js'; -export * from './encrypted_log_outgoing_body.js'; +export * from './l1_event_payload.js'; +export * from './payload.js'; diff --git a/yarn-project/circuit-types/src/logs/l1_payload/l1_event_payload.test.ts b/yarn-project/circuit-types/src/logs/l1_payload/l1_event_payload.test.ts deleted file mode 100644 index e92dfb757f8..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/l1_event_payload.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { AztecAddress, KeyValidationRequest, computeOvskApp, derivePublicKeyFromSecretKey } from '@aztec/circuits.js'; -import { EventSelector } from '@aztec/foundation/abi'; -import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto'; -import { Fr, GrumpkinScalar } from '@aztec/foundation/fields'; - -import { EncryptedL2Log } from '../encrypted_l2_log.js'; -import { L1EventPayload } from './l1_event_payload.js'; -import { Event } from './payload.js'; - -describe('L1 Event Payload', () => { - it('convert to and from buffer', () => { - const payload = L1EventPayload.random(); - const buf = payload.toBuffer(); - expect(L1EventPayload.fromBuffer(buf)).toEqual(payload); - }); - - describe('encrypt and decrypt a full log', () => { - let ovskM: GrumpkinScalar; - let ivskM: GrumpkinScalar; - - let payload: L1EventPayload; - let encrypted: Buffer; - let encryptedL2Log: EncryptedL2Log; - let maskedContractAddress: AztecAddress; - let contractAddress: AztecAddress; - let randomness: Fr; - - beforeAll(() => { - contractAddress = AztecAddress.random(); - randomness = Fr.random(); - maskedContractAddress = poseidon2HashWithSeparator([contractAddress, randomness], 0); - - payload = new L1EventPayload(Event.random(), contractAddress, randomness, EventSelector.random()); - - ovskM = GrumpkinScalar.random(); - ivskM = GrumpkinScalar.random(); - - const ovKeys = getKeyValidationRequest(ovskM, payload.contractAddress); - - const ephSk = GrumpkinScalar.random(); - - const recipientAddress = AztecAddress.random(); - const ivpk = derivePublicKeyFromSecretKey(ivskM); - - encrypted = payload.encrypt(ephSk, recipientAddress, ivpk, ovKeys); - const tag = Fr.random().toBuffer(); - encryptedL2Log = new EncryptedL2Log(Buffer.concat([tag, tag, encrypted]), maskedContractAddress); - }); - - it('decrypt a log as incoming', () => { - const recreated = L1EventPayload.decryptAsIncoming(encryptedL2Log, ivskM); - - expect(recreated.toBuffer()).toEqual(payload.toBuffer()); - }); - - it('decrypt a log as outgoing', () => { - const recreated = L1EventPayload.decryptAsOutgoing(encryptedL2Log, ovskM); - - expect(recreated.toBuffer()).toEqual(payload.toBuffer()); - }); - }); - - const getKeyValidationRequest = (ovskM: GrumpkinScalar, app: AztecAddress) => { - const ovskApp = computeOvskApp(ovskM, app); - const ovpkM = derivePublicKeyFromSecretKey(ovskM); - return new KeyValidationRequest(ovpkM, ovskApp); - }; -}); diff --git a/yarn-project/circuit-types/src/logs/l1_payload/l1_event_payload.ts b/yarn-project/circuit-types/src/logs/l1_payload/l1_event_payload.ts index 09586a355fe..429695236a0 100644 --- a/yarn-project/circuit-types/src/logs/l1_payload/l1_event_payload.ts +++ b/yarn-project/circuit-types/src/logs/l1_payload/l1_event_payload.ts @@ -1,24 +1,24 @@ -import { AztecAddress, type GrumpkinScalar, type KeyValidationRequest, type PublicKey } from '@aztec/circuits.js'; +import { AztecAddress } from '@aztec/circuits.js'; import { EventSelector } from '@aztec/foundation/abi'; -import { Fr } from '@aztec/foundation/fields'; +import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto'; +import { type Fq, Fr } from '@aztec/foundation/fields'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { type EncryptedL2Log } from '../encrypted_l2_log.js'; -import { EncryptedEventLogIncomingBody } from './encrypted_log_incoming_body/index.js'; -import { L1Payload } from './l1_payload.js'; +import { EncryptedLogPayload } from './encrypted_log_payload.js'; import { Event } from './payload.js'; /** * A class which wraps event data which is pushed on L1. */ -export class L1EventPayload extends L1Payload { +export class L1EventPayload { constructor( /** - * An encrypted event as emitted from Noir contract. + * A event as emitted from Noir contract. Can be used along with private key to compute nullifier. */ public event: Event, /** - * Address of the contract that emitted this event log. + * Address of the contract this tx is interacting with. */ public contractAddress: AztecAddress, /** @@ -26,25 +26,59 @@ export class L1EventPayload extends L1Payload { */ public randomness: Fr, /** - * Type identifier for the underlying event. + * Type identifier for the underlying event, required to determine how to compute its hash and nullifier. */ public eventTypeId: EventSelector, - ) { - super(); + ) {} + + static #fromIncomingBodyPlaintextAndContractAddress( + plaintext: Buffer, + contractAddress: AztecAddress, + maskedContractAddress: Fr, + ): L1EventPayload | undefined { + let payload: L1EventPayload; + try { + const reader = BufferReader.asReader(plaintext); + const fields = reader.readArray(plaintext.length / Fr.SIZE_IN_BYTES, Fr); + + const randomness = fields[0]; + const eventTypeId = EventSelector.fromField(fields[1]); + + const event = new Event(fields.slice(2)); + + payload = new L1EventPayload(event, contractAddress, randomness, eventTypeId); + } catch (e) { + return undefined; + } + + ensureMatchedMaskedContractAddress(contractAddress, payload.randomness, maskedContractAddress); + + return payload; } - /** - * Deserializes the L1EventPayload object from a Buffer. - * @param buffer - Buffer or BufferReader object to deserialize. - * @returns An instance of L1EventPayload. - */ - static fromBuffer(buffer: Buffer | BufferReader): L1EventPayload { - const reader = BufferReader.asReader(buffer); - return new L1EventPayload( - reader.readObject(Event), - reader.readObject(AztecAddress), - Fr.fromBuffer(reader), - reader.readObject(EventSelector), + static decryptAsIncoming(log: EncryptedL2Log, sk: Fq): L1EventPayload | undefined { + const decryptedLog = EncryptedLogPayload.decryptAsIncoming(log.data, sk); + if (!decryptedLog) { + return undefined; + } + + return this.#fromIncomingBodyPlaintextAndContractAddress( + decryptedLog.incomingBodyPlaintext, + decryptedLog.contractAddress, + log.maskedContractAddress, + ); + } + + static decryptAsOutgoing(log: EncryptedL2Log, sk: Fq): L1EventPayload | undefined { + const decryptedLog = EncryptedLogPayload.decryptAsOutgoing(log.data, sk); + if (!decryptedLog) { + return undefined; + } + + return this.#fromIncomingBodyPlaintextAndContractAddress( + decryptedLog.incomingBodyPlaintext, + decryptedLog.contractAddress, + log.maskedContractAddress, ); } @@ -52,96 +86,34 @@ export class L1EventPayload extends L1Payload { * Serializes the L1EventPayload object into a Buffer. * @returns Buffer representation of the L1EventPayload object. */ - toBuffer() { - return serializeToBuffer([this.event, this.contractAddress, this.randomness, this.eventTypeId]); + toIncomingBodyPlaintext() { + const fields = [this.randomness, this.eventTypeId.toField(), ...this.event.items]; + return serializeToBuffer(fields); } /** * Create a random L1EventPayload object (useful for testing purposes). + * @param contract - The address of a contract the event was emitted from. * @returns A random L1EventPayload object. */ - static random() { - return new L1EventPayload(Event.random(), AztecAddress.random(), Fr.random(), EventSelector.random()); + static random(contract = AztecAddress.random()) { + return new L1EventPayload(Event.random(), contract, Fr.random(), EventSelector.random()); } - public encrypt(ephSk: GrumpkinScalar, recipient: AztecAddress, ivpk: PublicKey, ovKeys: KeyValidationRequest) { - return super._encrypt( - this.contractAddress, - ephSk, - recipient, - ivpk, - ovKeys, - new EncryptedEventLogIncomingBody(this.randomness, this.eventTypeId.toField(), this.event), + public equals(other: L1EventPayload) { + return ( + this.event.equals(other.event) && + this.contractAddress.equals(other.contractAddress) && + this.randomness.equals(other.randomness) && + this.eventTypeId.equals(other.eventTypeId) ); } +} - /** - * Decrypts a ciphertext as an incoming log. - * - * This is executable by the recipient of the event, and uses the ivsk to decrypt the payload. - * The outgoing parts of the log are ignored entirely. - * - * Produces the same output as `decryptAsOutgoing`. - * - * @param encryptedLog - The encrypted log. This encrypted log is assumed to always have tags. - * @param ivsk - The incoming viewing secret key, used to decrypt the logs - * @returns The decrypted log payload - * @remarks The encrypted log is assumed to always have tags. - */ - public static decryptAsIncoming(encryptedLog: EncryptedL2Log, ivsk: GrumpkinScalar) { - const reader = BufferReader.asReader(encryptedLog.data); - - // We skip the tags - Fr.fromBuffer(reader); - Fr.fromBuffer(reader); - - const [address, incomingBody] = super._decryptAsIncoming( - reader.readToEnd(), - ivsk, - EncryptedEventLogIncomingBody.fromCiphertext, - ); - - // We instantiate selector before checking the address because instantiating the selector validates that - // the selector is valid (and that's the preferred way of detecting decryption failure). - const selector = EventSelector.fromField(incomingBody.eventTypeId); - - this.ensureMatchedMaskedContractAddress(address, incomingBody.randomness, encryptedLog.maskedContractAddress); - - return new L1EventPayload(incomingBody.event, address, incomingBody.randomness, selector); - } - - /** - * Decrypts a ciphertext as an outgoing log. - * - * This is executable by the sender of the event, and uses the ovsk to decrypt the payload. - * The outgoing parts are decrypted to retrieve information that allows the sender to - * decrypt the incoming log, and learn about the event contents. - * - * Produces the same output as `decryptAsIncoming`. - * - * @param ciphertext - The ciphertext for the log - * @param ovsk - The outgoing viewing secret key, used to decrypt the logs - * @returns The decrypted log payload - */ - public static decryptAsOutgoing(encryptedLog: EncryptedL2Log, ovsk: GrumpkinScalar) { - const reader = BufferReader.asReader(encryptedLog.data); - - // Skip the tags - Fr.fromBuffer(reader); - Fr.fromBuffer(reader); - - const [address, incomingBody] = super._decryptAsOutgoing( - reader.readToEnd(), - ovsk, - EncryptedEventLogIncomingBody.fromCiphertext, +function ensureMatchedMaskedContractAddress(contractAddress: AztecAddress, randomness: Fr, maskedContractAddress: Fr) { + if (!poseidon2HashWithSeparator([contractAddress, randomness], 0).equals(maskedContractAddress)) { + throw new Error( + 'The provided masked contract address does not match with the incoming address from header and randomness from body', ); - - // We instantiate selector before checking the address because instantiating the selector validates that - // the selector is valid (and that's the preferred way of detecting decryption failure). - const selector = EventSelector.fromField(incomingBody.eventTypeId); - - this.ensureMatchedMaskedContractAddress(address, incomingBody.randomness, encryptedLog.maskedContractAddress); - - return new L1EventPayload(incomingBody.event, address, incomingBody.randomness, selector); } } diff --git a/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.test.ts b/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.test.ts deleted file mode 100644 index 5a0bddc2081..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { AztecAddress, KeyValidationRequest, computeOvskApp, derivePublicKeyFromSecretKey } from '@aztec/circuits.js'; -import { GrumpkinScalar } from '@aztec/foundation/fields'; - -import { L1NotePayload } from './l1_note_payload.js'; - -describe('L1 Note Payload', () => { - it('convert to and from buffer', () => { - const payload = L1NotePayload.random(); - const buf = payload.toBuffer(); - expect(L1NotePayload.fromBuffer(buf)).toEqual(payload); - }); - - describe('encrypt and decrypt a full log', () => { - let ovskM: GrumpkinScalar; - let ivskM: GrumpkinScalar; - - let payload: L1NotePayload; - let encrypted: Buffer; - - beforeAll(() => { - payload = L1NotePayload.random(); - - ovskM = GrumpkinScalar.random(); - ivskM = GrumpkinScalar.random(); - - const ovKeys = getKeyValidationRequest(ovskM, payload.contractAddress); - - const ephSk = GrumpkinScalar.random(); - - const recipientAddress = AztecAddress.random(); - const ivpk = derivePublicKeyFromSecretKey(ivskM); - - encrypted = payload.encrypt(ephSk, recipientAddress, ivpk, ovKeys); - }); - - it('decrypt a log as incoming', () => { - const recreated = L1NotePayload.decryptAsIncoming(encrypted, ivskM); - - expect(recreated.toBuffer()).toEqual(payload.toBuffer()); - }); - - it('decrypt a log as outgoing', () => { - const recreated = L1NotePayload.decryptAsOutgoing(encrypted, ovskM); - - expect(recreated.toBuffer()).toEqual(payload.toBuffer()); - }); - }); - - const getKeyValidationRequest = (ovskM: GrumpkinScalar, app: AztecAddress) => { - const ovskApp = computeOvskApp(ovskM, app); - const ovpkM = derivePublicKeyFromSecretKey(ovskM); - return new KeyValidationRequest(ovpkM, ovskApp); - }; -}); diff --git a/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.ts b/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.ts index 1f53c1e00c1..c3f20cc0b10 100644 --- a/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.ts +++ b/yarn-project/circuit-types/src/logs/l1_payload/l1_note_payload.ts @@ -1,10 +1,10 @@ -import { AztecAddress, type GrumpkinScalar, type KeyValidationRequest, type PublicKey } from '@aztec/circuits.js'; +import { AztecAddress } from '@aztec/circuits.js'; import { NoteSelector } from '@aztec/foundation/abi'; -import { Fr } from '@aztec/foundation/fields'; +import { type Fq, Fr } from '@aztec/foundation/fields'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; -import { EncryptedNoteLogIncomingBody } from './encrypted_log_incoming_body/index.js'; -import { L1Payload } from './l1_payload.js'; +import { type EncryptedL2NoteLog } from '../encrypted_l2_note_log.js'; +import { EncryptedLogPayload } from './encrypted_log_payload.js'; import { Note } from './payload.js'; /** @@ -12,7 +12,7 @@ import { Note } from './payload.js'; * @remarks This data is required to compute a nullifier/to spend a note. Along with that this class contains * the necessary functionality to encrypt and decrypt the data. */ -export class L1NotePayload extends L1Payload { +export class L1NotePayload { constructor( /** * A note as emitted from Noir contract. Can be used along with private key to compute nullifier. @@ -30,22 +30,48 @@ export class L1NotePayload extends L1Payload { * Type identifier for the underlying note, required to determine how to compute its hash and nullifier. */ public noteTypeId: NoteSelector, - ) { - super(); + ) {} + + static fromIncomingBodyPlaintextAndContractAddress( + plaintext: Buffer, + contractAddress: AztecAddress, + ): L1NotePayload | undefined { + try { + const reader = BufferReader.asReader(plaintext); + const fields = reader.readArray(plaintext.length / Fr.SIZE_IN_BYTES, Fr); + + const storageSlot = fields[0]; + const noteTypeId = NoteSelector.fromField(fields[1]); + + const note = new Note(fields.slice(2)); + + return new L1NotePayload(note, contractAddress, storageSlot, noteTypeId); + } catch (e) { + return undefined; + } } - /** - * Deserializes the L1NotePayload object from a Buffer. - * @param buffer - Buffer or BufferReader object to deserialize. - * @returns An instance of L1NotePayload. - */ - static fromBuffer(buffer: Buffer | BufferReader): L1NotePayload { - const reader = BufferReader.asReader(buffer); - return new L1NotePayload( - reader.readObject(Note), - reader.readObject(AztecAddress), - Fr.fromBuffer(reader), - reader.readObject(NoteSelector), + static decryptAsIncoming(log: EncryptedL2NoteLog, sk: Fq): L1NotePayload | undefined { + const decryptedLog = EncryptedLogPayload.decryptAsIncoming(log.data, sk); + if (!decryptedLog) { + return undefined; + } + + return this.fromIncomingBodyPlaintextAndContractAddress( + decryptedLog.incomingBodyPlaintext, + decryptedLog.contractAddress, + ); + } + + static decryptAsOutgoing(log: EncryptedL2NoteLog, sk: Fq): L1NotePayload | undefined { + const decryptedLog = EncryptedLogPayload.decryptAsOutgoing(log.data, sk); + if (!decryptedLog) { + return undefined; + } + + return this.fromIncomingBodyPlaintextAndContractAddress( + decryptedLog.incomingBodyPlaintext, + decryptedLog.contractAddress, ); } @@ -53,8 +79,9 @@ export class L1NotePayload extends L1Payload { * Serializes the L1NotePayload object into a Buffer. * @returns Buffer representation of the L1NotePayload object. */ - toBuffer() { - return serializeToBuffer([this.note, this.contractAddress, this.storageSlot, this.noteTypeId]); + toIncomingBodyPlaintext() { + const fields = [this.storageSlot, this.noteTypeId.toField(), ...this.note.items]; + return serializeToBuffer(fields); } /** @@ -66,68 +93,6 @@ export class L1NotePayload extends L1Payload { return new L1NotePayload(Note.random(), contract, Fr.random(), NoteSelector.random()); } - public encrypt(ephSk: GrumpkinScalar, recipient: AztecAddress, ivpk: PublicKey, ovKeys: KeyValidationRequest) { - return super._encrypt( - this.contractAddress, - ephSk, - recipient, - ivpk, - ovKeys, - new EncryptedNoteLogIncomingBody(this.storageSlot, this.noteTypeId, this.note), - ); - } - - /** - * Decrypts a ciphertext as an incoming log. - * - * This is executable by the recipient of the note, and uses the ivsk to decrypt the payload. - * The outgoing parts of the log are ignored entirely. - * - * Produces the same output as `decryptAsOutgoing`. - * - * @param ciphertext - The ciphertext for the log - * @param ivsk - The incoming viewing secret key, used to decrypt the logs - * @returns The decrypted log payload - */ - public static decryptAsIncoming(ciphertext: Buffer | bigint[], ivsk: GrumpkinScalar) { - const input = Buffer.isBuffer(ciphertext) ? ciphertext : Buffer.from(ciphertext.map((x: bigint) => Number(x))); - const reader = BufferReader.asReader(input); - - const [address, incomingBody] = super._decryptAsIncoming( - reader.readToEnd(), - ivsk, - EncryptedNoteLogIncomingBody.fromCiphertext, - ); - - return new L1NotePayload(incomingBody.note, address, incomingBody.storageSlot, incomingBody.noteTypeId); - } - - /** - * Decrypts a ciphertext as an outgoing log. - * - * This is executable by the sender of the note, and uses the ovsk to decrypt the payload. - * The outgoing parts are decrypted to retrieve information that allows the sender to - * decrypt the incoming log, and learn about the note contents. - * - * Produces the same output as `decryptAsIncoming`. - * - * @param ciphertext - The ciphertext for the log - * @param ovsk - The outgoing viewing secret key, used to decrypt the logs - * @returns The decrypted log payload - */ - public static decryptAsOutgoing(ciphertext: Buffer | bigint[], ovsk: GrumpkinScalar) { - const input = Buffer.isBuffer(ciphertext) ? ciphertext : Buffer.from(ciphertext.map((x: bigint) => Number(x))); - const reader = BufferReader.asReader(input); - - const [address, incomingBody] = super._decryptAsOutgoing( - reader.readToEnd(), - ovsk, - EncryptedNoteLogIncomingBody.fromCiphertext, - ); - - return new L1NotePayload(incomingBody.note, address, incomingBody.storageSlot, incomingBody.noteTypeId); - } - public equals(other: L1NotePayload) { return ( this.note.equals(other.note) && diff --git a/yarn-project/circuit-types/src/logs/l1_payload/l1_payload.ts b/yarn-project/circuit-types/src/logs/l1_payload/l1_payload.ts deleted file mode 100644 index d8e12624c63..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/l1_payload.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { - type AztecAddress, - type GrumpkinScalar, - type KeyValidationRequest, - type PublicKey, - computeOvskApp, - derivePublicKeyFromSecretKey, -} from '@aztec/circuits.js'; -import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto'; -import { type Fr, Point } from '@aztec/foundation/fields'; -import { BufferReader } from '@aztec/foundation/serialize'; - -import { EncryptedLogHeader } from './encrypted_log_header.js'; -import { type EncryptedLogIncomingBody } from './encrypted_log_incoming_body/index.js'; -import { EncryptedLogOutgoingBody } from './encrypted_log_outgoing_body.js'; - -// Both the incoming and the outgoing header are 48 bytes. -// 32 bytes for the address, and 16 bytes padding to follow PKCS#7 -const HEADER_SIZE = 48; - -// The outgoing body is constant size of 144 bytes. -// 128 bytes for the secret key, address and public key, and 16 bytes padding to follow PKCS#7 -const OUTGOING_BODY_SIZE = 144; -/** - * A class which wraps event data which is pushed on L1. - */ -export abstract class L1Payload { - /** - * Serializes the L1EventPayload object into a Buffer. - * @returns Buffer representation of the L1EventPayload object. - */ - abstract toBuffer(): Buffer; - - /** - * Encrypts an event payload for a given recipient and sender. - * Creates an incoming log the recipient using the recipient's ivsk, and - * an outgoing log for the sender using the sender's ovsk. - * - * @param ephSk - An ephemeral secret key used for the encryption - * @param recipient - The recipient address, retrievable by the sender for his logs - * @param ivpk - The incoming viewing public key of the recipient - * @param ovKeys - The outgoing viewing keys of the sender - * @returns A buffer containing the encrypted log payload - * @throws If the ivpk is zero. - */ - protected _encrypt( - contractAddress: AztecAddress, - ephSk: GrumpkinScalar, - recipient: AztecAddress, - ivpk: PublicKey, - ovKeys: KeyValidationRequest, - incomingBody: T, - ) { - if (ivpk.isZero()) { - throw new Error(`Attempting to encrypt an event log with a zero ivpk.`); - } - - const ephPk = derivePublicKeyFromSecretKey(ephSk); - - const header = new EncryptedLogHeader(contractAddress); - - const incomingHeaderCiphertext = header.computeCiphertext(ephSk, ivpk); - const outgoingHeaderCiphertext = header.computeCiphertext(ephSk, ovKeys.pkM); - - const incomingBodyCiphertext = incomingBody.computeCiphertext(ephSk, ivpk); - - const outgoingBodyCiphertext = new EncryptedLogOutgoingBody(ephSk, recipient, ivpk).computeCiphertext( - ovKeys.skAppAsGrumpkinScalar, - ephPk, - ); - - return Buffer.concat([ - ephPk.toCompressedBuffer(), - incomingHeaderCiphertext, - outgoingHeaderCiphertext, - outgoingBodyCiphertext, - incomingBodyCiphertext, - ]); - } - - /** - * Decrypts a ciphertext as an incoming log. - * - * This is executable by the recipient of the event, and uses the ivsk to decrypt the payload. - * The outgoing parts of the log are ignored entirely. - * - * Produces the same output as `decryptAsOutgoing`. - * - * @param encryptedLog - The encrypted log. This encrypted log is assumed to always have tags. - * @param ivsk - The incoming viewing secret key, used to decrypt the logs - * @returns The decrypted log payload - */ - protected static _decryptAsIncoming( - data: Buffer, - ivsk: GrumpkinScalar, - fromCiphertext: (incomingBodySlice: Buffer, ivsk: GrumpkinScalar, ephPk: Point) => T, - ): [AztecAddress, T] { - const reader = BufferReader.asReader(data); - - const ephPk = Point.fromCompressedBuffer(reader.readBytes(Point.COMPRESSED_SIZE_IN_BYTES)); - - const incomingHeader = EncryptedLogHeader.fromCiphertext(reader.readBytes(HEADER_SIZE), ivsk, ephPk); - - // Skipping the outgoing header and body - reader.readBytes(HEADER_SIZE); - reader.readBytes(OUTGOING_BODY_SIZE); - - // The incoming can be of variable size, so we read until the end - const incomingBodySlice = reader.readToEnd(); - - const incomingBody = fromCiphertext(incomingBodySlice, ivsk, ephPk); - - return [incomingHeader.address, incomingBody]; - } - - /** - * Decrypts a ciphertext as an outgoing log. - * - * This is executable by the sender of the event, and uses the ovsk to decrypt the payload. - * The outgoing parts are decrypted to retrieve information that allows the sender to - * decrypt the incoming log, and learn about the event contents. - * - * Produces the same output as `decryptAsIncoming`. - * - * @param ciphertext - The ciphertext for the log - * @param ovsk - The outgoing viewing secret key, used to decrypt the logs - * @returns The decrypted log payload - */ - protected static _decryptAsOutgoing( - data: Buffer, - ovsk: GrumpkinScalar, - fromCiphertext: (incomingBodySlice: Buffer, ivsk: GrumpkinScalar, ephPk: Point) => T, - ): [AztecAddress, T] { - const reader = BufferReader.asReader(data); - - const ephPk = Point.fromCompressedBuffer(reader.readBytes(Point.COMPRESSED_SIZE_IN_BYTES)); - - reader.readBytes(HEADER_SIZE); - - const outgoingHeader = EncryptedLogHeader.fromCiphertext(reader.readBytes(HEADER_SIZE), ovsk, ephPk); - - const ovskApp = computeOvskApp(ovsk, outgoingHeader.address); - const outgoingBody = EncryptedLogOutgoingBody.fromCiphertext(reader.readBytes(OUTGOING_BODY_SIZE), ovskApp, ephPk); - - // The incoming can be of variable size, so we read until the end - const incomingBodySlice = reader.readToEnd(); - - const incomingBody = fromCiphertext(incomingBodySlice, outgoingBody.ephSk, outgoingBody.recipientIvpk); - - return [outgoingHeader.address, incomingBody]; - } - - protected static ensureMatchedMaskedContractAddress( - contractAddress: AztecAddress, - randomness: Fr, - maskedContractAddress: Fr, - ) { - if (!poseidon2HashWithSeparator([contractAddress, randomness], 0).equals(maskedContractAddress)) { - throw new Error( - 'The provided masked contract address does not match with the incoming address from header and randomness from body', - ); - } - } -} diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encryption_utils.ts b/yarn-project/circuit-types/src/logs/l1_payload/shared_secret_derivation.ts similarity index 57% rename from yarn-project/circuit-types/src/logs/l1_payload/encryption_utils.ts rename to yarn-project/circuit-types/src/logs/l1_payload/shared_secret_derivation.ts index 0367f87cb4c..e9e3ea5caff 100644 --- a/yarn-project/circuit-types/src/logs/l1_payload/encryption_utils.ts +++ b/yarn-project/circuit-types/src/logs/l1_payload/shared_secret_derivation.ts @@ -1,6 +1,6 @@ import { GeneratorIndex, type GrumpkinScalar, type PublicKey } from '@aztec/circuits.js'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { sha256 } from '@aztec/foundation/crypto'; +import { poseidon2HashWithSeparator, sha256 } from '@aztec/foundation/crypto'; import { numToUInt8 } from '@aztec/foundation/serialize'; /** @@ -16,7 +16,7 @@ import { numToUInt8 } from '@aztec/foundation/serialize'; * TODO(#5726): This function is called point_to_symmetric_key in Noir. I don't like that name much since point is not * the only input of the function. Unify naming once we have a better name. */ -export function deriveAESSecret(secretKey: GrumpkinScalar, publicKey: PublicKey): Buffer { +export function deriveDiffieHellmanAESSecret(secretKey: GrumpkinScalar, publicKey: PublicKey): Buffer { if (publicKey.isZero()) { throw new Error( `Attempting to derive AES secret with a zero public key. You have probably passed a zero public key in your Noir code somewhere thinking that the note won't broadcasted... but it was.`, @@ -28,3 +28,21 @@ export function deriveAESSecret(secretKey: GrumpkinScalar, publicKey: PublicKey) const hash = sha256(secretBuffer); return hash; } + +/** + * Derives an AES symmetric key from the app siloed outgoing viewing secret key + * and the ephemeral public key using poseidon. + * + * @param ovskApp - The app siloed outgoing viewing secret key + * @param ephPk - The ephemeral public key + * @returns The derived AES symmetric key + */ +export function derivePoseidonAESSecret(ovskApp: GrumpkinScalar, ephPk: PublicKey) { + // For performance reasons, we do NOT use the usual `deriveAESSecret` function here and instead we compute it using + // poseidon. Note that we can afford to use poseidon here instead of deriving shared secret using Diffie-Hellman + // because for outgoing we are encrypting for ourselves and hence we don't need to perform a key exchange. + return poseidon2HashWithSeparator( + [ovskApp.hi, ovskApp.lo, ephPk.x, ephPk.y], + GeneratorIndex.SYMMETRIC_KEY, + ).toBuffer(); +} diff --git a/yarn-project/circuit-types/src/logs/l1_payload/tagged_log.test.ts b/yarn-project/circuit-types/src/logs/l1_payload/tagged_log.test.ts deleted file mode 100644 index 05cf0b20921..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/tagged_log.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { AztecAddress, KeyValidationRequest, computeOvskApp, derivePublicKeyFromSecretKey } from '@aztec/circuits.js'; -import { EventSelector, NoteSelector } from '@aztec/foundation/abi'; -import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto'; -import { Fr, GrumpkinScalar } from '@aztec/foundation/fields'; -import { updateInlineTestData } from '@aztec/foundation/testing'; - -import { EncryptedL2Log } from '../encrypted_l2_log.js'; -import { L1EventPayload } from './l1_event_payload.js'; -import { L1NotePayload } from './l1_note_payload.js'; -import { Event, Note } from './payload.js'; -import { TaggedLog } from './tagged_log.js'; - -describe('L1 Note Payload', () => { - it('convert to and from buffer', () => { - const payload = L1NotePayload.random(); - const taggedLog = new TaggedLog(payload); - const buf = taggedLog.toBuffer(); - expect(TaggedLog.fromBuffer(buf).payload).toEqual(taggedLog.payload); - }); - - describe('encrypt and decrypt a full log', () => { - let ovskM: GrumpkinScalar; - let ivskM: GrumpkinScalar; - - let taggedLog: TaggedLog; - let encrypted: Buffer; - - beforeAll(() => { - const payload = L1NotePayload.random(); - - ovskM = GrumpkinScalar.random(); - ivskM = GrumpkinScalar.random(); - - const ovKeys = getKeyValidationRequest(ovskM, payload.contractAddress); - - const ephSk = GrumpkinScalar.random(); - - const recipientAddress = AztecAddress.random(); - const ivpk = derivePublicKeyFromSecretKey(ivskM); - - taggedLog = new TaggedLog(payload); - - encrypted = taggedLog.encrypt(ephSk, recipientAddress, ivpk, ovKeys); - }); - - it('decrypt a log as incoming', () => { - const recreated = TaggedLog.decryptAsIncoming(encrypted, ivskM); - - expect(recreated?.toBuffer()).toEqual(taggedLog.toBuffer()); - }); - - it('decrypt a log as outgoing', () => { - const recreated = TaggedLog.decryptAsOutgoing(encrypted, ovskM); - - expect(recreated?.toBuffer()).toEqual(taggedLog.toBuffer()); - }); - }); - - it('encrypted tagged log matches Noir', () => { - // All the values in this test were arbitrarily set and copied over to `payload.nr` - const contract = AztecAddress.fromString('0x10f48cd9eff7ae5b209c557c70de2e657ee79166868676b787e9417e19260e04'); - const storageSlot = new Fr(0x0fe46be583b71f4ab5b70c2657ff1d05cccf1d292a9369628d1a194f944e6599n); - const noteValue = new Fr(0x301640ceea758391b2e161c92c0513f129020f4125256afdae2646ce31099f5cn); - const noteTypeId = new NoteSelector(4135); // note type id of mock_note.nr - - const payload = new L1NotePayload(new Note([noteValue]), contract, storageSlot, noteTypeId); - - const ovskM = new GrumpkinScalar(0x06b76394ac57b8a18ceb08b14ed15b5b778d5c506b4cfb7edc203324eab27c05n); - const ivskM = new GrumpkinScalar(0x03fd94b1101e834e829cda4f227043f60490b5c7b3073875f5bfbe5028ed05ccn); - - const ovKeys = getKeyValidationRequest(ovskM, payload.contractAddress); - - const ephSk = new GrumpkinScalar(0x1358d15019d4639393d62b97e1588c095957ce74a1c32d6ec7d62fe6705d9538n); - - const recipientAddress = AztecAddress.fromString( - '0x10ee41ee4b62703b16f61e03cb0d88c4b306a9eb4a6ceeb2aff13428541689a2', - ); - - const ivpk = derivePublicKeyFromSecretKey(ivskM); - - const taggedLog = new TaggedLog(payload, new Fr(0), new Fr(0)); - - const encrypted = taggedLog.encrypt(ephSk, recipientAddress, ivpk, ovKeys).toString('hex'); - - expect(encrypted).toMatchInlineSnapshot( - `"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d460c0e434d846ec1ea286e4090eb56376ff27bddc1aacae1d856549f701fa77e4f33ba2f47fdac6370f13bc5f16bbae857bbe6ab3ee4ea2a339192eef22a47ce0df4426fc314cb6294ccf291b79c1d8d362cdcc223e51020ccd3318e7052ca74f1fe922ad914bd46e4b6abcd681b63ab1c5bf4151e82f00548ae7c61c59df8c117c14c2e8d9046d32d43a7da818c68be296ef9d1446a87a450eb3f6550200d2663915b0bad97e7f7419975e5a740efb67eeb5304a90808a004ebfc156054a1459191d7fea175f6c64159b3c25a13790cca7250c30e3c80698e64565a6c9ddb16ac1479c3199fec02464b2a252202119514b02012cc387579220f03587b40444ae93f3b83dec2c0a76ed90a804981accd67d43c978d0a97de97b42b5b94c96ea50aee2086eb63d8c8b61f169c12d1deacefc1d456633e46b62daff15bcab3e1ec5f474297e1cb35d8556682060819b4563a8cc66966b12a5e73f7919318e727491b0adb8273bc4a7205b1c753b76a57cceee7482df027ae196235bb9c9ff426"`, - ); - - const byteArrayString = `[${encrypted.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16))}]`; - - // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data - updateInlineTestData( - 'noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr', - 'encrypted_note_log_from_typescript', - byteArrayString, - ); - }); - - const getKeyValidationRequest = (ovskM: GrumpkinScalar, app: AztecAddress) => { - const ovskApp = computeOvskApp(ovskM, app); - const ovpkM = derivePublicKeyFromSecretKey(ovskM); - - return new KeyValidationRequest(ovpkM, ovskApp); - }; -}); - -describe('L1 Event Payload', () => { - it('convert to and from buffer', () => { - const payload = L1EventPayload.random(); - const taggedLog = new TaggedLog(payload); - const buf = taggedLog.toBuffer(); - expect(TaggedLog.fromBuffer(buf, L1EventPayload).payload).toEqual(taggedLog.payload); - }); - - describe('encrypt and decrypt a full log', () => { - let ovskM: GrumpkinScalar; - let ivskM: GrumpkinScalar; - - let taggedLog: TaggedLog; - let encrypted: Buffer; - let maskedContractAddress: AztecAddress; - let contractAddress: AztecAddress; - let randomness: Fr; - let encryptedL2Log: EncryptedL2Log; - - beforeAll(() => { - contractAddress = AztecAddress.random(); - randomness = Fr.random(); - maskedContractAddress = poseidon2HashWithSeparator([contractAddress, randomness], 0); - - const payload = new L1EventPayload(Event.random(), contractAddress, randomness, EventSelector.random()); - - ovskM = GrumpkinScalar.random(); - ivskM = GrumpkinScalar.random(); - - const ovKeys = getKeyValidationRequest(ovskM, payload.contractAddress); - - const ephSk = GrumpkinScalar.random(); - - const recipientAddress = AztecAddress.random(); - const ivpk = derivePublicKeyFromSecretKey(ivskM); - - taggedLog = new TaggedLog(payload); - - encrypted = taggedLog.encrypt(ephSk, recipientAddress, ivpk, ovKeys); - encryptedL2Log = new EncryptedL2Log(encrypted, maskedContractAddress); - }); - - it('decrypt a log as incoming', () => { - const recreated = TaggedLog.decryptAsIncoming(encryptedL2Log, ivskM, L1EventPayload); - - expect(recreated?.toBuffer()).toEqual(taggedLog.toBuffer()); - }); - - it('decrypt a log as outgoing', () => { - const recreated = TaggedLog.decryptAsOutgoing(encryptedL2Log, ovskM, L1EventPayload); - - expect(recreated?.toBuffer()).toEqual(taggedLog.toBuffer()); - }); - }); - - const getKeyValidationRequest = (ovskM: GrumpkinScalar, app: AztecAddress) => { - const ovskApp = computeOvskApp(ovskM, app); - const ovpkM = derivePublicKeyFromSecretKey(ovskM); - - return new KeyValidationRequest(ovpkM, ovskApp); - }; -}); diff --git a/yarn-project/circuit-types/src/logs/l1_payload/tagged_log.ts b/yarn-project/circuit-types/src/logs/l1_payload/tagged_log.ts deleted file mode 100644 index 70568c08ad6..00000000000 --- a/yarn-project/circuit-types/src/logs/l1_payload/tagged_log.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { AztecAddress, type GrumpkinScalar, type KeyValidationRequest, type PublicKey } from '@aztec/circuits.js'; -import { Fr, NotOnCurveError } from '@aztec/foundation/fields'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; - -import { type EncryptedL2Log } from '../encrypted_l2_log.js'; -import { L1EventPayload } from './l1_event_payload.js'; -import { L1NotePayload } from './l1_note_payload.js'; - -// placeholder value until tagging is implemented -const PLACEHOLDER_TAG = new Fr(33); - -/** - * Encrypted log payload with a tag used for retrieval by clients. - */ -export class TaggedLog { - constructor(public payload: Payload, public incomingTag = PLACEHOLDER_TAG, public outgoingTag = PLACEHOLDER_TAG) {} - - /** - * Deserializes the TaggedLog object from a Buffer. - * @param buffer - Buffer or BufferReader object to deserialize. - * @returns An instance of TaggedLog. - */ - static fromBuffer(buffer: Buffer | BufferReader, payloadType: typeof L1EventPayload): TaggedLog; - static fromBuffer(buffer: Buffer | BufferReader, payloadType?: typeof L1NotePayload): TaggedLog; - static fromBuffer( - buffer: Buffer | BufferReader, - payloadType: typeof L1NotePayload | typeof L1EventPayload = L1NotePayload, - ) { - const reader = BufferReader.asReader(buffer); - const incomingTag = Fr.fromBuffer(reader); - const outgoingTag = Fr.fromBuffer(reader); - const payload = - payloadType === L1NotePayload ? L1NotePayload.fromBuffer(reader) : L1EventPayload.fromBuffer(reader); - - return new TaggedLog(payload, incomingTag, outgoingTag); - } - - /** - * Serializes the TaggedLog object into a Buffer. - * @returns Buffer representation of the TaggedLog object (unencrypted). - */ - public toBuffer(): Buffer { - return serializeToBuffer(this.incomingTag, this.outgoingTag, this.payload); - } - - static random(payloadType?: typeof L1NotePayload, contract?: AztecAddress): TaggedLog; - static random(payloadType: typeof L1EventPayload): TaggedLog; - static random( - payloadType: typeof L1NotePayload | typeof L1EventPayload = L1NotePayload, - contract = AztecAddress.random(), - ): TaggedLog { - return payloadType === L1NotePayload - ? new TaggedLog(L1NotePayload.random(contract)) - : new TaggedLog(L1EventPayload.random()); - } - - public encrypt( - ephSk: GrumpkinScalar, - recipient: AztecAddress, - ivpk: PublicKey, - ovKeys: KeyValidationRequest, - ): Buffer { - return serializeToBuffer(this.incomingTag, this.outgoingTag, this.payload.encrypt(ephSk, recipient, ivpk, ovKeys)); - } - - static decryptAsIncoming( - encryptedLog: EncryptedL2Log, - ivsk: GrumpkinScalar, - payloadType: typeof L1EventPayload, - ): TaggedLog | undefined; - static decryptAsIncoming( - data: Buffer | bigint[], - ivsk: GrumpkinScalar, - payloadType?: typeof L1NotePayload, - ): TaggedLog | undefined; - static decryptAsIncoming( - data: Buffer | bigint[] | EncryptedL2Log, - ivsk: GrumpkinScalar, - payloadType: typeof L1NotePayload | typeof L1EventPayload = L1NotePayload, - ): TaggedLog | undefined { - try { - if (payloadType === L1EventPayload) { - const reader = BufferReader.asReader((data as EncryptedL2Log).data); - const incomingTag = Fr.fromBuffer(reader); - const outgoingTag = Fr.fromBuffer(reader); - // We must pass the entire encrypted log in. The tags are not stripped here from the original data - const payload = L1EventPayload.decryptAsIncoming(data as EncryptedL2Log, ivsk); - return new TaggedLog(payload, incomingTag, outgoingTag); - } else { - const input = Buffer.isBuffer(data) ? data : Buffer.from((data as bigint[]).map((x: bigint) => Number(x))); - const reader = BufferReader.asReader(input); - const incomingTag = Fr.fromBuffer(reader); - const outgoingTag = Fr.fromBuffer(reader); - const payload = L1NotePayload.decryptAsIncoming(reader.readToEnd(), ivsk); - return new TaggedLog(payload, incomingTag, outgoingTag); - } - } catch (e: any) { - // Following error messages are expected to occur when decryption fails - if ( - !(e instanceof NotOnCurveError) && - !e.message.endsWith('is greater or equal to field modulus.') && - !e.message.startsWith('Invalid AztecAddress length') && - !e.message.startsWith('Selector must fit in') && - !e.message.startsWith('Attempted to read beyond buffer length') - ) { - // If we encounter an unexpected error, we rethrow it - throw e; - } - return; - } - } - - static decryptAsOutgoing( - encryptedLog: EncryptedL2Log, - ivsk: GrumpkinScalar, - payloadType: typeof L1EventPayload, - ): TaggedLog | undefined; - static decryptAsOutgoing( - data: Buffer | bigint[], - ivsk: GrumpkinScalar, - payloadType?: typeof L1NotePayload, - ): TaggedLog | undefined; - static decryptAsOutgoing( - data: Buffer | bigint[] | EncryptedL2Log, - ovsk: GrumpkinScalar, - payloadType: typeof L1NotePayload | typeof L1EventPayload = L1NotePayload, - ) { - try { - if (payloadType === L1EventPayload) { - const reader = BufferReader.asReader((data as EncryptedL2Log).data); - const incomingTag = Fr.fromBuffer(reader); - const outgoingTag = Fr.fromBuffer(reader); - const payload = L1EventPayload.decryptAsOutgoing(data as EncryptedL2Log, ovsk); - return new TaggedLog(payload, incomingTag, outgoingTag); - } else { - const input = Buffer.isBuffer(data) ? data : Buffer.from((data as bigint[]).map((x: bigint) => Number(x))); - const reader = BufferReader.asReader(input); - const incomingTag = Fr.fromBuffer(reader); - const outgoingTag = Fr.fromBuffer(reader); - const payload = L1NotePayload.decryptAsOutgoing(reader.readToEnd(), ovsk); - return new TaggedLog(payload, incomingTag, outgoingTag); - } - } catch (e: any) { - // Following error messages are expected to occur when decryption fails - if ( - !(e instanceof NotOnCurveError) && - !e.message.endsWith('is greater or equal to field modulus.') && - !e.message.startsWith('Invalid AztecAddress length') && - !e.message.startsWith('Selector must fit in') && - !e.message.startsWith('Attempted to read beyond buffer length') - ) { - // If we encounter an unexpected error, we rethrow it - throw e; - } - return; - } - } -} diff --git a/yarn-project/end-to-end/Earthfile b/yarn-project/end-to-end/Earthfile index 3c6d50a62f1..453edba5cd8 100644 --- a/yarn-project/end-to-end/Earthfile +++ b/yarn-project/end-to-end/Earthfile @@ -72,10 +72,6 @@ e2e-deploy-contract: LOCALLY RUN ./scripts/e2e_test.sh ./src/e2e_deploy_contract -e2e-encryption: - LOCALLY - RUN ./scripts/e2e_test.sh ./src/e2e_encryption.test.ts - e2e-escrow-contract: LOCALLY RUN ./scripts/e2e_test.sh ./src/e2e_escrow_contract.test.ts diff --git a/yarn-project/end-to-end/src/e2e_block_building.test.ts b/yarn-project/end-to-end/src/e2e_block_building.test.ts index 0cb5ea031b5..c528e1a8e06 100644 --- a/yarn-project/end-to-end/src/e2e_block_building.test.ts +++ b/yarn-project/end-to-end/src/e2e_block_building.test.ts @@ -22,7 +22,6 @@ import { TokenContract } from '@aztec/noir-contracts.js/Token'; import 'jest-extended'; -import { TaggedLog } from '../../circuit-types/src/logs/l1_payload/tagged_log.js'; import { DUPLICATE_NULLIFIER_ERROR } from './fixtures/fixtures.js'; import { setup } from './fixtures/utils.js'; @@ -293,13 +292,13 @@ describe('e2e_block_building', () => { // compare logs expect(rct.status).toEqual('success'); - const decryptedLogs = tx.noteEncryptedLogs - .unrollLogs() - .map(l => TaggedLog.decryptAsIncoming(l.data, keys.masterIncomingViewingSecretKey, L1NotePayload)); - const notevalues = decryptedLogs.map(l => l?.payload.note.items[0]); - expect(notevalues[0]).toEqual(new Fr(10)); - expect(notevalues[1]).toEqual(new Fr(11)); - expect(notevalues[2]).toEqual(new Fr(12)); + const noteValues = tx.noteEncryptedLogs.unrollLogs().map(l => { + const notePayload = L1NotePayload.decryptAsIncoming(l, keys.masterIncomingViewingSecretKey); + return notePayload?.note.items[0]; + }); + expect(noteValues[0]).toEqual(new Fr(10)); + expect(noteValues[1]).toEqual(new Fr(11)); + expect(noteValues[2]).toEqual(new Fr(12)); }, 30_000); it('calls a method with nested encrypted logs', async () => { diff --git a/yarn-project/end-to-end/src/e2e_encryption.test.ts b/yarn-project/end-to-end/src/e2e_encryption.test.ts deleted file mode 100644 index f3ff1e03632..00000000000 --- a/yarn-project/end-to-end/src/e2e_encryption.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { - AztecAddress, - EncryptedLogHeader, - EncryptedLogOutgoingBody, - EncryptedNoteLogIncomingBody, - Fr, - GrumpkinScalar, - Note, - type Wallet, -} from '@aztec/aztec.js'; -import { Aes128, Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { TestContract } from '@aztec/noir-contracts.js'; - -import { randomBytes } from 'crypto'; - -import { setup } from './fixtures/utils.js'; - -describe('e2e_encryption', () => { - const aes128 = new Aes128(); - let grumpkin: Grumpkin; - - let wallet: Wallet; - let teardown: () => Promise; - - let contract: TestContract; - - beforeAll(async () => { - ({ teardown, wallet } = await setup()); - contract = await TestContract.deploy(wallet).send().deployed(); - grumpkin = new Grumpkin(); - }, 120_000); - - afterAll(() => teardown()); - - it('encrypts 🔒📄🔑💻', async () => { - const input = randomBytes(64); - const iv = randomBytes(16); - const key = randomBytes(16); - - const expectedCiphertext = aes128.encryptBufferCBC(input, iv, key); - - const ciphertextAsBigInts = await contract.methods - .encrypt(Array.from(input), Array.from(iv), Array.from(key)) - .simulate(); - const ciphertext = Buffer.from(ciphertextAsBigInts.map((x: bigint) => Number(x))); - - expect(ciphertext).toEqual(expectedCiphertext); - }); - - it('encrypts with padding 🔒📄🔑💻 ➕ 📦', async () => { - const input = randomBytes(65); - const iv = randomBytes(16); - const key = randomBytes(16); - - const expectedCiphertext = aes128.encryptBufferCBC(input, iv, key); - // AES 128 CBC with PKCS7 is padding to multiples of 16 bytes so from 65 bytes long input we get 80 bytes long output - expect(expectedCiphertext.length).toBe(80); - - const ciphertextAsBigInts = await contract.methods - .encrypt_with_padding(Array.from(input), Array.from(iv), Array.from(key)) - .simulate(); - const ciphertext = Buffer.from(ciphertextAsBigInts.map((x: bigint) => Number(x))); - - expect(ciphertext).toEqual(expectedCiphertext); - }); - - it('encrypts log header', async () => { - const ephSecretKey = GrumpkinScalar.random(); - const viewingSecretKey = GrumpkinScalar.random(); - - const ephPubKey = grumpkin.mul(Grumpkin.generator, ephSecretKey); - const viewingPubKey = grumpkin.mul(Grumpkin.generator, viewingSecretKey); - const header = new EncryptedLogHeader(contract.address); - - const encrypted = await contract.methods - .compute_note_header_ciphertext(ephSecretKey, viewingPubKey.toWrappedNoirStruct()) - .simulate(); - expect(Buffer.from(encrypted.map((x: bigint) => Number(x)))).toEqual( - header.computeCiphertext(ephSecretKey, viewingPubKey), - ); - - const recreated = EncryptedLogHeader.fromCiphertext(encrypted, viewingSecretKey, ephPubKey); - - expect(recreated.address).toEqual(contract.address); - }); - - it('encrypts log incoming body', async () => { - const ephSecretKey = GrumpkinScalar.random(); - const viewingSecretKey = GrumpkinScalar.random(); - - const ephPubKey = grumpkin.mul(Grumpkin.generator, ephSecretKey); - const viewingPubKey = grumpkin.mul(Grumpkin.generator, viewingSecretKey); - - const storageSlot = new Fr(1); - const noteTypeId = TestContract.artifact.notes['TestNote'].id; - const value = Fr.random(); - const note = new Note([value]); - - const body = new EncryptedNoteLogIncomingBody(storageSlot, noteTypeId, note); - - const encrypted = await contract.methods - .compute_incoming_log_body_ciphertext(ephSecretKey, viewingPubKey.toWrappedNoirStruct(), storageSlot, value) - .simulate(); - - expect(Buffer.from(encrypted.map((x: bigint) => Number(x)))).toEqual( - body.computeCiphertext(ephSecretKey, viewingPubKey), - ); - - const recreated = EncryptedNoteLogIncomingBody.fromCiphertext(encrypted, viewingSecretKey, ephPubKey); - - expect(recreated.toBuffer()).toEqual(body.toBuffer()); - }); - - it('encrypts log outgoing body', async () => { - const ephSk = GrumpkinScalar.random(); - const recipientIvsk = GrumpkinScalar.random(); - const senderOvskApp = GrumpkinScalar.random(); - - const ephPk = grumpkin.mul(Grumpkin.generator, ephSk); - const recipientIvpk = grumpkin.mul(Grumpkin.generator, recipientIvsk); - - const recipientAddress = AztecAddress.fromBigInt(BigInt('0xdeadbeef')); - - const body = new EncryptedLogOutgoingBody(ephSk, recipientAddress, recipientIvpk); - - const encrypted = await contract.methods - .compute_outgoing_log_body_ciphertext(ephSk, recipientAddress, recipientIvpk.toWrappedNoirStruct(), senderOvskApp) - .simulate(); - - expect(Buffer.from(encrypted.map((x: bigint) => Number(x)))).toEqual(body.computeCiphertext(senderOvskApp, ephPk)); - - const recreated = EncryptedLogOutgoingBody.fromCiphertext(encrypted, senderOvskApp, ephPk); - - expect(recreated.toBuffer()).toEqual(body.toBuffer()); - }); -}); diff --git a/yarn-project/end-to-end/src/e2e_event_logs.test.ts b/yarn-project/end-to-end/src/e2e_event_logs.test.ts index f436552613f..a6ed7b351bc 100644 --- a/yarn-project/end-to-end/src/e2e_event_logs.test.ts +++ b/yarn-project/end-to-end/src/e2e_event_logs.test.ts @@ -5,7 +5,6 @@ import { Fr, L1EventPayload, type PXE, - TaggedLog, } from '@aztec/aztec.js'; import { deriveMasterIncomingViewingSecretKey } from '@aztec/circuits.js'; import { EventSelector } from '@aztec/foundation/abi'; @@ -56,44 +55,37 @@ describe('Logs', () => { const encryptedLogs = txEffect!.encryptedLogs.unrollLogs(); expect(encryptedLogs.length).toBe(3); - const decryptedLog0 = TaggedLog.decryptAsIncoming( + const decryptedEvent0 = L1EventPayload.decryptAsIncoming( encryptedLogs[0], deriveMasterIncomingViewingSecretKey(wallets[0].getSecretKey()), - L1EventPayload, - ); + )!; - expect(decryptedLog0?.payload.contractAddress).toStrictEqual(testLogContract.address); - expect(decryptedLog0?.payload.randomness).toStrictEqual(randomness[0]); - expect(decryptedLog0?.payload.eventTypeId).toStrictEqual( - EventSelector.fromSignature('ExampleEvent0(Field,Field)'), - ); + expect(decryptedEvent0.contractAddress).toStrictEqual(testLogContract.address); + expect(decryptedEvent0.randomness).toStrictEqual(randomness[0]); + expect(decryptedEvent0.eventTypeId).toStrictEqual(EventSelector.fromSignature('ExampleEvent0(Field,Field)')); // We decode our event into the event type - const event0 = TestLogContract.events.ExampleEvent0.decode(decryptedLog0!.payload); + const event0 = TestLogContract.events.ExampleEvent0.decode(decryptedEvent0); // We check that the event was decoded correctly expect(event0?.value0).toStrictEqual(preimage[0].toBigInt()); expect(event0?.value1).toStrictEqual(preimage[1].toBigInt()); // We check that an event that does not match, is not decoded correctly due to an event type id mismatch - const badEvent0 = TestLogContract.events.ExampleEvent1.decode(decryptedLog0!.payload); + const badEvent0 = TestLogContract.events.ExampleEvent1.decode(decryptedEvent0); expect(badEvent0).toBe(undefined); - const decryptedLog1 = TaggedLog.decryptAsIncoming( - // We want to skip the second emitted log as it is irrelevant in this test. + const decryptedEvent1 = L1EventPayload.decryptAsIncoming( encryptedLogs[2], deriveMasterIncomingViewingSecretKey(wallets[0].getSecretKey()), - L1EventPayload, - ); + )!; - expect(decryptedLog1?.payload.contractAddress).toStrictEqual(testLogContract.address); - expect(decryptedLog1?.payload.randomness).toStrictEqual(randomness[1]); - expect(decryptedLog1?.payload.eventTypeId).toStrictEqual( - EventSelector.fromSignature('ExampleEvent1((Field),u8)'), - ); + expect(decryptedEvent1.contractAddress).toStrictEqual(testLogContract.address); + expect(decryptedEvent1.randomness).toStrictEqual(randomness[1]); + expect(decryptedEvent1.eventTypeId).toStrictEqual(EventSelector.fromSignature('ExampleEvent1((Field),u8)')); // We check our second event, which is a different type - const event1 = TestLogContract.events.ExampleEvent1.decode(decryptedLog1!.payload); + const event1 = TestLogContract.events.ExampleEvent1.decode(decryptedEvent1); // We expect the fields to have been populated correctly expect(event1?.value2).toStrictEqual(preimage[2]); @@ -101,7 +93,7 @@ describe('Logs', () => { expect(event1?.value3).toStrictEqual(BigInt(preimage[3].toBuffer().subarray(31).readUint8())); // Again, trying to decode another event with mismatching data does not yield anything - const badEvent1 = TestLogContract.events.ExampleEvent0.decode(decryptedLog1!.payload); + const badEvent1 = TestLogContract.events.ExampleEvent0.decode(decryptedEvent1); expect(badEvent1).toBe(undefined); }); diff --git a/yarn-project/pxe/src/note_processor/note_processor.test.ts b/yarn-project/pxe/src/note_processor/note_processor.test.ts index 2a694b16708..18abf9faaea 100644 --- a/yarn-project/pxe/src/note_processor/note_processor.test.ts +++ b/yarn-project/pxe/src/note_processor/note_processor.test.ts @@ -1,4 +1,4 @@ -import { type AztecNode, EncryptedL2NoteLog, L1NotePayload, L2Block, TaggedLog } from '@aztec/circuit-types'; +import { type AztecNode, EncryptedL2NoteLog, EncryptedLogPayload, L1NotePayload, L2Block } from '@aztec/circuit-types'; import { AztecAddress, CompleteAddress, @@ -31,8 +31,8 @@ const NUM_NOTE_HASHES_PER_BLOCK = TXS_PER_BLOCK * MAX_NOTE_HASHES_PER_TX; /** A wrapper containing info about a note we want to mock and insert into a block. */ class MockNoteRequest { constructor( - /** Note we want to insert into a block. */ - public readonly note: TaggedLog, + /** Log payload corresponding to a note we want to insert into a block. */ + public readonly logPayload: EncryptedLogPayload, /** Block number this note corresponds to. */ public readonly blockNumber: number, /** Index of a tx within a block this note corresponds to. */ @@ -58,7 +58,7 @@ class MockNoteRequest { encrypt(): EncryptedL2NoteLog { const ephSk = GrumpkinScalar.random(); const recipient = AztecAddress.random(); - const log = this.note.encrypt(ephSk, recipient, this.ivpk, this.ovKeys); + const log = this.logPayload.encrypt(ephSk, recipient, this.ivpk, this.ovKeys); return new EncryptedL2NoteLog(log); } @@ -67,6 +67,13 @@ class MockNoteRequest { (this.blockNumber - 1) * NUM_NOTE_HASHES_PER_BLOCK + this.txIndex * MAX_NOTE_HASHES_PER_TX + this.noteHashIndex, ); } + + get notePayload(): L1NotePayload | undefined { + return L1NotePayload.fromIncomingBodyPlaintextAndContractAddress( + this.logPayload.incomingBodyPlaintext, + this.logPayload.contractAddress, + ); + } } describe('Note Processor', () => { @@ -103,8 +110,8 @@ describe('Note Processor', () => { // Then we update the relevant note hashes to match the note requests for (const request of noteRequestsForBlock) { - const note = request.note; - const noteHash = pedersenHash(note.payload.note.items); + const notePayload = request.notePayload; + const noteHash = pedersenHash(notePayload!.note.items); block.body.txEffects[request.txIndex].noteHashes[request.noteHashIndex] = noteHash; // Now we populate the log - to simplify we say that there is only 1 function invocation in each tx @@ -180,7 +187,7 @@ describe('Note Processor', () => { it('should store an incoming note that belongs to us', async () => { const request = new MockNoteRequest( - TaggedLog.random(L1NotePayload, app), + getRandomNoteLogPayload(app), 4, 0, 2, @@ -195,7 +202,7 @@ describe('Note Processor', () => { expect(addNotesSpy).toHaveBeenCalledWith( [ expect.objectContaining({ - ...request.note.payload, + ...request.notePayload, index: request.indexWithinNoteHashTree, }), ], @@ -205,23 +212,23 @@ describe('Note Processor', () => { }, 25_000); it('should store an outgoing note that belongs to us', async () => { - const request = new MockNoteRequest(TaggedLog.random(L1NotePayload, app), 4, 0, 2, Point.random(), ownerOvKeys); + const request = new MockNoteRequest(getRandomNoteLogPayload(app), 4, 0, 2, Point.random(), ownerOvKeys); const blocks = mockBlocks([request]); await noteProcessor.process(blocks); expect(addNotesSpy).toHaveBeenCalledTimes(1); // For outgoing notes, the resulting DAO does not contain index. - expect(addNotesSpy).toHaveBeenCalledWith([], [expect.objectContaining(request.note.payload)], account.address); + expect(addNotesSpy).toHaveBeenCalledWith([], [expect.objectContaining(request.notePayload)], account.address); }, 25_000); it('should store multiple notes that belong to us', async () => { const requests = [ - new MockNoteRequest(TaggedLog.random(L1NotePayload, app), 1, 1, 1, ownerIvpkM, ownerOvKeys), - new MockNoteRequest(TaggedLog.random(L1NotePayload, app), 2, 3, 0, Point.random(), ownerOvKeys), - new MockNoteRequest(TaggedLog.random(L1NotePayload, app), 6, 3, 2, ownerIvpkM, KeyValidationRequest.random()), - new MockNoteRequest(TaggedLog.random(L1NotePayload, app), 9, 3, 2, Point.random(), KeyValidationRequest.random()), - new MockNoteRequest(TaggedLog.random(L1NotePayload, app), 12, 3, 2, ownerIvpkM, ownerOvKeys), + new MockNoteRequest(getRandomNoteLogPayload(app), 1, 1, 1, ownerIvpkM, ownerOvKeys), + new MockNoteRequest(getRandomNoteLogPayload(app), 2, 3, 0, Point.random(), ownerOvKeys), + new MockNoteRequest(getRandomNoteLogPayload(app), 6, 3, 2, ownerIvpkM, KeyValidationRequest.random()), + new MockNoteRequest(getRandomNoteLogPayload(app), 9, 3, 2, Point.random(), KeyValidationRequest.random()), + new MockNoteRequest(getRandomNoteLogPayload(app), 12, 3, 2, ownerIvpkM, ownerOvKeys), ]; const blocks = mockBlocks(requests); @@ -232,23 +239,23 @@ describe('Note Processor', () => { // Incoming should contain notes from requests 0, 2, 4 because in those requests we set owner ivpk. [ expect.objectContaining({ - ...requests[0].note.payload, + ...requests[0].notePayload, index: requests[0].indexWithinNoteHashTree, }), expect.objectContaining({ - ...requests[2].note.payload, + ...requests[2].notePayload, index: requests[2].indexWithinNoteHashTree, }), expect.objectContaining({ - ...requests[4].note.payload, + ...requests[4].notePayload, index: requests[4].indexWithinNoteHashTree, }), ], // Outgoing should contain notes from requests 0, 1, 4 because in those requests we set owner ovKeys. [ - expect.objectContaining(requests[0].note.payload), - expect.objectContaining(requests[1].note.payload), - expect.objectContaining(requests[4].note.payload), + expect.objectContaining(requests[0].notePayload), + expect.objectContaining(requests[1].notePayload), + expect.objectContaining(requests[4].notePayload), ], account.address, ); @@ -257,8 +264,8 @@ describe('Note Processor', () => { it('should not store notes that do not belong to us', async () => { // Both notes should be ignored because the encryption keys do not belong to owner (they are random). const blocks = mockBlocks([ - new MockNoteRequest(TaggedLog.random(), 2, 1, 1, Point.random(), KeyValidationRequest.random()), - new MockNoteRequest(TaggedLog.random(), 2, 3, 0, Point.random(), KeyValidationRequest.random()), + new MockNoteRequest(getRandomNoteLogPayload(), 2, 1, 1, Point.random(), KeyValidationRequest.random()), + new MockNoteRequest(getRandomNoteLogPayload(), 2, 3, 0, Point.random(), KeyValidationRequest.random()), ]); await noteProcessor.process(blocks); @@ -266,8 +273,8 @@ describe('Note Processor', () => { }); it('should be able to recover two note payloads containing the same note', async () => { - const note = TaggedLog.random(L1NotePayload, app); - const note2 = TaggedLog.random(L1NotePayload, app); + const note = getRandomNoteLogPayload(app); + const note2 = getRandomNoteLogPayload(app); // All note payloads except one have the same contract address, storage slot, and the actual note. const requests = [ new MockNoteRequest(note, 3, 0, 0, ownerIvpkM, ownerOvKeys), @@ -284,11 +291,11 @@ describe('Note Processor', () => { { const addedIncoming: IncomingNoteDao[] = addNotesSpy.mock.calls[0][0]; expect(addedIncoming.map(dao => dao)).toEqual([ - expect.objectContaining({ ...requests[0].note.payload, index: requests[0].indexWithinNoteHashTree }), - expect.objectContaining({ ...requests[1].note.payload, index: requests[1].indexWithinNoteHashTree }), - expect.objectContaining({ ...requests[2].note.payload, index: requests[2].indexWithinNoteHashTree }), - expect.objectContaining({ ...requests[3].note.payload, index: requests[3].indexWithinNoteHashTree }), - expect.objectContaining({ ...requests[4].note.payload, index: requests[4].indexWithinNoteHashTree }), + expect.objectContaining({ ...requests[0].notePayload, index: requests[0].indexWithinNoteHashTree }), + expect.objectContaining({ ...requests[1].notePayload, index: requests[1].indexWithinNoteHashTree }), + expect.objectContaining({ ...requests[2].notePayload, index: requests[2].indexWithinNoteHashTree }), + expect.objectContaining({ ...requests[3].notePayload, index: requests[3].indexWithinNoteHashTree }), + expect.objectContaining({ ...requests[4].notePayload, index: requests[4].indexWithinNoteHashTree }), ]); // Check that every note has a different nonce. @@ -301,11 +308,11 @@ describe('Note Processor', () => { { const addedOutgoing: OutgoingNoteDao[] = addNotesSpy.mock.calls[0][1]; expect(addedOutgoing.map(dao => dao)).toEqual([ - expect.objectContaining(requests[0].note.payload), - expect.objectContaining(requests[1].note.payload), - expect.objectContaining(requests[2].note.payload), - expect.objectContaining(requests[3].note.payload), - expect.objectContaining(requests[4].note.payload), + expect.objectContaining(requests[0].notePayload), + expect.objectContaining(requests[1].notePayload), + expect.objectContaining(requests[2].notePayload), + expect.objectContaining(requests[3].notePayload), + expect.objectContaining(requests[4].notePayload), ]); // Outgoing note daos do not have a nonce so we don't check it. @@ -313,7 +320,7 @@ describe('Note Processor', () => { }); it('advances the block number', async () => { - const request = new MockNoteRequest(TaggedLog.random(), 6, 0, 2, ownerIvpkM, ownerOvKeys); + const request = new MockNoteRequest(getRandomNoteLogPayload(), 6, 0, 2, ownerIvpkM, ownerOvKeys); const blocks = mockBlocks([request]); await noteProcessor.process(blocks); @@ -322,7 +329,14 @@ describe('Note Processor', () => { }); it('should restore the last block number processed and ignore the starting block', async () => { - const request = new MockNoteRequest(TaggedLog.random(), 6, 0, 2, Point.random(), KeyValidationRequest.random()); + const request = new MockNoteRequest( + getRandomNoteLogPayload(), + 6, + 0, + 2, + Point.random(), + KeyValidationRequest.random(), + ); const blocks = mockBlocks([request]); await noteProcessor.process(blocks); @@ -338,4 +352,8 @@ describe('Note Processor', () => { expect(newNoteProcessor.status).toEqual(noteProcessor.status); }); + + function getRandomNoteLogPayload(app = AztecAddress.random()): EncryptedLogPayload { + return new EncryptedLogPayload(Fr.random(), Fr.random(), app, L1NotePayload.random(app).toIncomingBodyPlaintext()); + } }); diff --git a/yarn-project/pxe/src/note_processor/note_processor.ts b/yarn-project/pxe/src/note_processor/note_processor.ts index 13ed58e5227..ac9bf48b691 100644 --- a/yarn-project/pxe/src/note_processor/note_processor.ts +++ b/yarn-project/pxe/src/note_processor/note_processor.ts @@ -1,4 +1,4 @@ -import { type AztecNode, L1NotePayload, type L2Block, TaggedLog } from '@aztec/circuit-types'; +import { type AztecNode, L1NotePayload, type L2Block } from '@aztec/circuit-types'; import { type NoteProcessorStats } from '@aztec/circuit-types/stats'; import { type AztecAddress, INITIAL_L2_BLOCK_NUM, MAX_NOTE_HASHES_PER_TX, type PublicKey } from '@aztec/circuits.js'; import { type Fr } from '@aztec/foundation/fields'; @@ -142,30 +142,26 @@ export class NoteProcessor { for (const functionLogs of txFunctionLogs) { for (const log of functionLogs.logs) { this.stats.seen++; - const incomingTaggedNote = TaggedLog.decryptAsIncoming(log.data, ivskM)!; - const outgoingTaggedNote = TaggedLog.decryptAsOutgoing(log.data, ovskM)!; - - if (incomingTaggedNote || outgoingTaggedNote) { - if ( - incomingTaggedNote && - outgoingTaggedNote && - !incomingTaggedNote.payload.equals(outgoingTaggedNote.payload) - ) { + const incomingNotePayload = L1NotePayload.decryptAsIncoming(log, ivskM); + const outgoingNotePayload = L1NotePayload.decryptAsOutgoing(log, ovskM); + + if (incomingNotePayload || outgoingNotePayload) { + if (incomingNotePayload && outgoingNotePayload && !incomingNotePayload.equals(outgoingNotePayload)) { throw new Error( `Incoming and outgoing note payloads do not match. Incoming: ${JSON.stringify( - incomingTaggedNote.payload, - )}, Outgoing: ${JSON.stringify(outgoingTaggedNote.payload)}`, + incomingNotePayload, + )}, Outgoing: ${JSON.stringify(outgoingNotePayload)}`, ); } - const payload = incomingTaggedNote?.payload || outgoingTaggedNote?.payload; + const payload = incomingNotePayload || outgoingNotePayload; const txHash = block.body.txEffects[indexOfTxInABlock].txHash; const { incomingNote, outgoingNote, incomingDeferredNote, outgoingDeferredNote } = await produceNoteDaos( this.simulator, - incomingTaggedNote ? this.ivpkM : undefined, - outgoingTaggedNote ? this.ovpkM : undefined, - payload, + incomingNotePayload ? this.ivpkM : undefined, + outgoingNotePayload ? this.ovpkM : undefined, + payload!, txHash, noteHashes, dataStartIndexForTx, diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index c18a036d590..a0b3de442b6 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -20,7 +20,6 @@ import { type SiblingPath, SimulatedTx, SimulationError, - TaggedLog, Tx, type TxEffect, type TxExecutionRequest, @@ -944,11 +943,10 @@ export class PXEService implements PXE { const visibleEvents = encryptedLogs.flatMap(encryptedLog => { for (const sk of vsks) { - const decryptedLog = - TaggedLog.decryptAsIncoming(encryptedLog, sk, L1EventPayload) ?? - TaggedLog.decryptAsOutgoing(encryptedLog, sk, L1EventPayload); - if (decryptedLog !== undefined) { - return [decryptedLog]; + const decryptedEvent = + L1EventPayload.decryptAsIncoming(encryptedLog, sk) ?? L1EventPayload.decryptAsOutgoing(encryptedLog, sk); + if (decryptedEvent !== undefined) { + return [decryptedEvent]; } } @@ -957,19 +955,19 @@ export class PXEService implements PXE { const decodedEvents = visibleEvents .map(visibleEvent => { - if (visibleEvent.payload === undefined) { + if (visibleEvent === undefined) { return undefined; } - if (!visibleEvent.payload.eventTypeId.equals(eventMetadata.eventSelector)) { + if (!visibleEvent.eventTypeId.equals(eventMetadata.eventSelector)) { return undefined; } - if (visibleEvent.payload.event.items.length !== eventMetadata.fieldNames.length) { + if (visibleEvent.event.items.length !== eventMetadata.fieldNames.length) { throw new Error( 'Something is weird here, we have matching EventSelectors, but the actual payload has mismatched length', ); } - return eventMetadata.decode(visibleEvent.payload); + return eventMetadata.decode(visibleEvent); }) .filter(visibleEvent => visibleEvent !== undefined) as T[];