diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr index 16deb152483..cf982b8c4ad 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr @@ -11,7 +11,8 @@ fn compute_raw_note_log( ovsk_app: Field, ovpk: OvpkM, ivpk: IvpkM, - recipient: AztecAddress + recipient: AztecAddress, + num_public_values: u8 // Number of values to be appended to the log in public (used in partial note flow). ) -> (u32, [u8; M], Field) where Note: NoteInterface, [Field; N]: LensForEncryptedLog { let note_header = note.get_header(); let note_hash_counter = note_header.note_hash_counter; @@ -30,7 +31,8 @@ fn compute_raw_note_log( ovpk, ivpk, recipient, - note + note, + num_public_values ); let log_hash = sha256_to_field(encrypted_log); @@ -42,10 +44,11 @@ unconstrained fn compute_raw_note_log_unconstrained (u32, [u8; M], Field) where Note: NoteInterface, [Field; N]: LensForEncryptedLog { let ovsk_app = get_ovsk_app(ovpk.hash()); - compute_raw_note_log(context, note, ovsk_app, ovpk, ivpk, recipient) + compute_raw_note_log(context, note, ovsk_app, ovpk, ivpk, recipient, num_public_values) } pub fn encode_and_encrypt_note( @@ -58,7 +61,10 @@ pub fn encode_and_encrypt_note( let ivpk = get_current_public_keys(context, iv).ivpk_m; let ovsk_app: Field = context.request_ovsk_app(ovpk.hash()); - let (note_hash_counter, encrypted_log, log_hash) = compute_raw_note_log(*context, e.note, ovsk_app, ovpk, ivpk, iv); + // Number of public values is always 0 here because `encode_and_encrypt_note(...)` is only called + // in the non-partial note flow. + let num_public_values = 0; + let (note_hash_counter, encrypted_log, log_hash) = compute_raw_note_log(*context, e.note, ovsk_app, ovpk, ivpk, iv, num_public_values); context.emit_raw_note_log(note_hash_counter, encrypted_log, log_hash); } } @@ -74,10 +80,14 @@ pub fn encode_and_encrypt_note_unconstrained | { let ovsk_app: Field = context.request_ovsk_app(ovpk.hash()); - let (note_hash_counter, encrypted_log, log_hash) = compute_raw_note_log(*context, e.note, ovsk_app, ovpk, ivpk, recipient); + // Number of public values is always 0 here because `encode_and_encrypt_note_unconstrained(...)` is only called + // in the non-partial note flow. + let num_public_values = 0; + + let (note_hash_counter, encrypted_log, log_hash) = compute_raw_note_log(*context, e.note, ovsk_app, ovpk, ivpk, recipient, num_public_values); context.emit_raw_note_log(note_hash_counter, encrypted_log, log_hash); } } @@ -104,6 +118,10 @@ pub fn encode_and_encrypt_note_with_keys_unconstrained fn[(&mut PrivateContext, OvpkM, IvpkM, AztecAddress)](NoteEmission) -> () where Note: NoteInterface, [Field; N]: LensForEncryptedLog { | e: NoteEmission | { + // Number of public values is always 0 here because `encode_and_encrypt_note_with_keys_unconstrained(...)` is only called + // in the non-partial note flow. + let num_public_values = 0; + // Having the log hash be unconstrained here is fine because the way this works is we send the log hash // to the kernel, and it gets included as part of its public inputs. Then we send the tx to the sequencer, // which includes the kernel proof and the log preimages. The sequencer computes the hashes of the logs @@ -124,7 +142,7 @@ pub fn encode_and_encrypt_note_with_keys_unconstrained( ovpk: OvpkM, ivpk: IvpkM, recipient: AztecAddress, - note: Note + note: Note, + num_public_values: u8 // Number of values to be appended to the log in public (used in partial note flow). ) -> [u8; M] where Note: NoteInterface { let (eph_sk, eph_pk) = generate_ephemeral_key_pair(); @@ -86,27 +87,29 @@ pub fn compute_encrypted_note_log( let mut encrypted_bytes: [u8; M] = [0; M]; // @todo We ignore the tags for now + encrypted_bytes[64] = num_public_values; // TODO(#8558): This can be just a single bit if we store info about partial fields in ABI let eph_pk_bytes = point_to_bytes(eph_pk); for i in 0..32 { - encrypted_bytes[64 + i] = eph_pk_bytes[i]; + encrypted_bytes[65 + i] = eph_pk_bytes[i]; } for i in 0..48 { - encrypted_bytes[96 + i] = incoming_header_ciphertext[i]; - encrypted_bytes[144 + i] = outgoing_header_ciphertext[i]; + encrypted_bytes[97 + i] = incoming_header_ciphertext[i]; + encrypted_bytes[145 + i] = outgoing_header_ciphertext[i]; } for i in 0..144 { - encrypted_bytes[192 + i] = outgoing_body_ciphertext[i]; + encrypted_bytes[193 + i] = outgoing_body_ciphertext[i]; } // Then we fill in the rest as the incoming body ciphertext - let size = M - 336; + let size = M - 337; assert_eq(size, incoming_body_ciphertext.len(), "ciphertext length mismatch"); for i in 0..size { - encrypted_bytes[336 + i] = incoming_body_ciphertext[i]; + encrypted_bytes[337 + i] = incoming_body_ciphertext[i]; } // Current unoptimized size of the encrypted log // incoming_tag (32 bytes) // outgoing_tag (32 bytes) + // num_public_values (1 byte) // eph_pk (32 bytes) // incoming_header (48 bytes) // outgoing_header (48 bytes) @@ -170,25 +173,88 @@ mod test { let _ = OracleMock::mock("getRandomField").returns(eph_sk); let recipient = AztecAddress::from_field(0x10ee41ee4b62703b16f61e03cb0d88c4b306a9eb4a6ceeb2aff13428541689a2); + let num_public_values: u8 = 0; - let log: [u8; 448] = compute_encrypted_note_log( + let log: [u8; 449] = compute_encrypted_note_log( contract_address, storage_slot, ovsk_app, ovpk_m, ivpk_m, recipient, - note + note, + num_public_values ); // 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 + 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, 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 ]; - for i in 0..encrypted_note_log_from_typescript.len() { - assert_eq(log[i], encrypted_note_log_from_typescript[i]); + assert_eq(encrypted_note_log_from_typescript, log); + } + + #[test] + fn test_encrypted_note_log_of_finalized_partial_note_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 { + x: 0x1961448682803198631f299340e4206bb12809d4bebbf012b30f59af73ba1a15, + y: 0x133674060c3925142aceb4f1dcd9f9137d0217d37ff8729ee5ceaa6e2790353d, + is_infinite: false + } + }; + let ivpk_m = IvpkM { + inner: Point { + x: 0x260cd3904f6df16e974c29944fdc839e40fb5cf293f03df2eb370851d3a527bc, + y: 0x0eef2964fe6640e84c82b5d2915892409b38e9e25d39f68dd79edb725c55387f, + is_infinite: false + } + }; + + let note_value = 0x301640ceea758391b2e161c92c0513f129020f4125256afdae2646ce31099f5c; + let note_public_value1 = 0x14172339287e8d281545c177313f02b6aa2fedfd628cfd8b7f11a136fd0d6557; + let note_public_value2 = 0x0834d81e3f73c7e2809b08ae38600ffc76a2554473eeab6de7bff4b33a84feac; + let note = MockNoteBuilder::new(note_value).contract_address(contract_address).storage_slot(storage_slot).build(); + + let eph_sk = 0x1358d15019d4639393d62b97e1588c095957ce74a1c32d6ec7d62fe6705d9538; + let _ = OracleMock::mock("getRandomField").returns(eph_sk); + + let recipient = AztecAddress::from_field(0x10ee41ee4b62703b16f61e03cb0d88c4b306a9eb4a6ceeb2aff13428541689a2); + let num_public_values: u8 = 2; + + // First we compute the encrypted log without the public values + let log_without_public_values: [u8; 449] = compute_encrypted_note_log( + contract_address, + storage_slot, + ovsk_app, + ovpk_m, + ivpk_m, + recipient, + note, + num_public_values + ); + + // Then we "append" the public values to the log by copying both the original log and the current log into a new byte array + let mut log: [u8; 513] = [0; 513]; + for i in 0..449 { + log[i] = log_without_public_values[i]; + } + let note_public_value1_bytes: [u8; 32] = note_public_value1.to_be_bytes(); + let note_public_value2_bytes: [u8; 32] = note_public_value2.to_be_bytes(); + for i in 0..32 { + log[449 + i] = note_public_value1_bytes[i]; + log[481 + i] = note_public_value2_bytes[i]; } - assert_eq(encrypted_note_log_from_typescript.len(), log.len()); + + // 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_of_finalized_partial_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, 2, 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, 20, 23, 35, 57, 40, 126, 141, 40, 21, 69, 193, 119, 49, 63, 2, 182, 170, 47, 237, 253, 98, 140, 253, 139, 127, 17, 161, 54, 253, 13, 101, 87, 8, 52, 216, 30, 63, 115, 199, 226, 128, 155, 8, 174, 56, 96, 15, 252, 118, 162, 85, 68, 115, 238, 171, 109, 231, 191, 244, 179, 58, 132, 254, 172 + ]; + assert_eq(encrypted_note_log_of_finalized_partial_from_typescript, log); } } diff --git a/noir-projects/aztec-nr/aztec/src/oracle/logs_traits.nr b/noir-projects/aztec-nr/aztec/src/oracle/logs_traits.nr index 9417dfe6224..4640068d054 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/logs_traits.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/logs_traits.nr @@ -1,5 +1,6 @@ use dep::protocol_types::address::AztecAddress; +// TODO(#8548): We have arithmetics over generics now so this file can be nuked. // TODO: this is awful but since we can't have a fn that maps [Field; N] -> [u8; 416 + N * 32] // (where N is the note pre-image size and 416 + N * 32 is the encryption output size) // The fns for LensForEncryptedLog are never used, it's just to tell the compiler what the lens are @@ -11,105 +12,105 @@ use dep::protocol_types::address::AztecAddress; // anything, so we can remove this trait trait LensForEncryptedLog { // N = note preimage input in fields - // M = encryption output len in bytes (= 416 + N * 32) + // M = encryption output len in bytes (= 417 + N * 32) fn output_fields(self: [Field; N]) -> [Field; N]; fn output_bytes(self: [Field; N]) -> [u8; M]; } -impl LensForEncryptedLog<1, 448> for [Field; 1] { +impl LensForEncryptedLog<1, 449> for [Field; 1] { fn output_fields(self) -> [Field; 1] { [self[0]; 1] } - fn output_bytes(self) -> [u8; 448] { - [self[0] as u8; 448] + fn output_bytes(self) -> [u8; 449] { + [self[0] as u8; 449] } } -impl LensForEncryptedLog<2, 480> for [Field; 2] { +impl LensForEncryptedLog<2, 481> for [Field; 2] { fn output_fields(self) -> [Field; 2] { [self[0]; 2] } - fn output_bytes(self) -> [u8; 480] { - [self[0] as u8; 480] + fn output_bytes(self) -> [u8; 481] { + [self[0] as u8; 481] } } -impl LensForEncryptedLog<3, 512> for [Field; 3] { +impl LensForEncryptedLog<3, 513> for [Field; 3] { fn output_fields(self) -> [Field; 3] { [self[0]; 3] } - fn output_bytes(self) -> [u8; 512] { - [self[0] as u8; 512] + fn output_bytes(self) -> [u8; 513] { + [self[0] as u8; 513] } } -impl LensForEncryptedLog<4, 544> for [Field; 4] { +impl LensForEncryptedLog<4, 545> for [Field; 4] { fn output_fields(self) -> [Field; 4] { [self[0]; 4] } - fn output_bytes(self) -> [u8; 544] { - [self[0] as u8; 544] + fn output_bytes(self) -> [u8; 545] { + [self[0] as u8; 545] } } -impl LensForEncryptedLog<5, 576> for [Field; 5] { +impl LensForEncryptedLog<5, 577> for [Field; 5] { fn output_fields(self) -> [Field; 5] { [self[0]; 5] } - fn output_bytes(self) -> [u8; 576] { - [self[0] as u8; 576] + fn output_bytes(self) -> [u8; 577] { + [self[0] as u8; 577] } } -impl LensForEncryptedLog<6, 608> for [Field; 6] { +impl LensForEncryptedLog<6, 609> for [Field; 6] { fn output_fields(self) -> [Field; 6] { [self[0]; 6] } - fn output_bytes(self) -> [u8; 608] { - [self[0] as u8; 608] + fn output_bytes(self) -> [u8; 609] { + [self[0] as u8; 609] } } -impl LensForEncryptedLog<7, 640> for [Field; 7] { +impl LensForEncryptedLog<7, 641> for [Field; 7] { fn output_fields(self) -> [Field; 7] { [self[0]; 7] } - fn output_bytes(self) -> [u8; 640] { - [self[0] as u8; 640] + fn output_bytes(self) -> [u8; 641] { + [self[0] as u8; 641] } } -impl LensForEncryptedLog<8, 672> for [Field; 8] { +impl LensForEncryptedLog<8, 673> for [Field; 8] { fn output_fields(self) -> [Field; 8] { [self[0]; 8] } - fn output_bytes(self) -> [u8; 672] { - [self[0] as u8; 672] + fn output_bytes(self) -> [u8; 673] { + [self[0] as u8; 673] } } -impl LensForEncryptedLog<9, 704> for [Field; 9] { +impl LensForEncryptedLog<9, 705> for [Field; 9] { fn output_fields(self) -> [Field; 9] { [self[0]; 9] } - fn output_bytes(self) -> [u8; 704] { - [self[0] as u8; 704] + fn output_bytes(self) -> [u8; 705] { + [self[0] as u8; 705] } } -impl LensForEncryptedLog<10, 736> for [Field; 10] { +impl LensForEncryptedLog<10, 737> for [Field; 10] { fn output_fields(self) -> [Field; 10] { [self[0]; 10] } - fn output_bytes(self) -> [u8; 736] { - [self[0] as u8; 736] + fn output_bytes(self) -> [u8; 737] { + [self[0] as u8; 737] } } -impl LensForEncryptedLog<11, 768> for [Field; 11] { +impl LensForEncryptedLog<11, 769> for [Field; 11] { fn output_fields(self) -> [Field; 11] { [self[0]; 11] } - fn output_bytes(self) -> [u8; 768] { - [self[0] as u8; 768] + fn output_bytes(self) -> [u8; 769] { + [self[0] as u8; 769] } } -impl LensForEncryptedLog<12, 800> for [Field; 12] { +impl LensForEncryptedLog<12, 801> for [Field; 12] { fn output_fields(self) -> [Field; 12] { [self[0]; 12] } - fn output_bytes(self) -> [u8; 800] { - [self[0] as u8; 800] + fn output_bytes(self) -> [u8; 801] { + [self[0] as u8; 801] } } diff --git a/noir-projects/aztec-nr/aztec/src/utils/point.nr b/noir-projects/aztec-nr/aztec/src/utils/point.nr index 633bd837ebb..bf06a87fec2 100644 --- a/noir-projects/aztec-nr/aztec/src/utils/point.nr +++ b/noir-projects/aztec-nr/aztec/src/utils/point.nr @@ -48,10 +48,7 @@ mod test { 154, 244, 31, 93, 233, 100, 70, 220, 55, 118, 161, 235, 45, 152, 187, 149, 107, 122, 205, 153, 121, 166, 120, 84, 190, 198, 250, 124, 41, 115, 189, 115 ]; - assert_eq(expected_compressed_point_positive_sign.len(), compressed_point.len()); - for i in 0..expected_compressed_point_positive_sign.len() { - assert_eq(compressed_point[i], expected_compressed_point_positive_sign[i]); - } + assert_eq(expected_compressed_point_positive_sign, compressed_point); } #[test] @@ -68,9 +65,6 @@ mod test { 36, 115, 113, 101, 46, 85, 221, 116, 201, 175, 141, 190, 159, 180, 73, 49, 186, 41, 169, 34, 153, 148, 56, 75, 215, 7, 119, 150, 193, 78, 226, 181 ]; - assert_eq(expected_compressed_point_negative_sign.len(), compressed_point.len()); - for i in 0..expected_compressed_point_negative_sign.len() { - assert_eq(compressed_point[i], expected_compressed_point_negative_sign[i]); - } + assert_eq(expected_compressed_point_negative_sign, compressed_point); } } diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index 62a3afd057d..d454c047f6e 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -39,7 +39,7 @@ contract Counter { // docs:end:get_counter // docs:start:test_imports - use dep::aztec::test::{helpers::{cheatcodes, test_environment::TestEnvironment}}; + use dep::aztec::test::helpers::test_environment::TestEnvironment; use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; use dep::aztec::note::note_viewer_options::NoteViewerOptions; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/field.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/field.nr index 954ef602f82..d4115e5743d 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/field.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/field.nr @@ -52,9 +52,7 @@ unconstrained fn bytes_field_test() { ]; let field = field_from_bytes(inputs, true); let return_bytes: [u8; 31] = field.to_be_bytes(); - for i in 0..31 { - assert_eq(inputs[i], return_bytes[i]); - } + assert_eq(inputs, return_bytes); // 32 bytes - we remove the final byte, and check it matches the field let inputs2 = [ 84, 62, 10, 102, 66, 255, 235, 128, 57, 41, 104, 97, 118, 90, 83, 64, 123, 186, 98, 189, 28, 151, 202, 67, 55, 77, 233, 80, 187, 224, 167, 158 @@ -62,9 +60,7 @@ unconstrained fn bytes_field_test() { let field2 = field_from_bytes_32_trunc(inputs2); let return_bytes2: [u8; 31] = field.to_be_bytes(); - for i in 0..31 { - assert_eq(return_bytes2[i], return_bytes[i]); - } + assert_eq(return_bytes2, return_bytes); assert_eq(field2, field); } diff --git a/yarn-project/circuit-types/src/logs/encrypted_l2_note_log.ts b/yarn-project/circuit-types/src/logs/encrypted_l2_note_log.ts index 3202155a858..3cc334a028b 100644 --- a/yarn-project/circuit-types/src/logs/encrypted_l2_note_log.ts +++ b/yarn-project/circuit-types/src/logs/encrypted_l2_note_log.ts @@ -61,9 +61,15 @@ export class EncryptedL2NoteLog { * @returns A random log. */ public static random(): EncryptedL2NoteLog { + const numPublicValues = 0; const randomEphPubKey = Point.random(); const randomLogContent = randomBytes(144 - Point.COMPRESSED_SIZE_IN_BYTES); - const data = Buffer.concat([Fr.random().toBuffer(), randomLogContent, randomEphPubKey.toCompressedBuffer()]); + const data = Buffer.concat([ + Buffer.alloc(1, numPublicValues), + Fr.random().toBuffer(), + randomLogContent, + randomEphPubKey.toCompressedBuffer(), + ]); return new EncryptedL2NoteLog(data); } 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..29d53578dd5 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 @@ -67,7 +67,11 @@ export class L1NotePayload extends L1Payload { } public encrypt(ephSk: GrumpkinScalar, recipient: AztecAddress, ivpk: PublicKey, ovKeys: KeyValidationRequest) { - return super._encrypt( + // TODO(#8558): numPublicValues could occupy just a single bit if we store info about partial fields + // in the ABI + // We always set the value to 0 here as we don't need partial notes encryption support in TS + const numPublicValues = 0; + const encryptedPayload = super._encrypt( this.contractAddress, ephSk, recipient, @@ -75,35 +79,38 @@ export class L1NotePayload extends L1Payload { ovKeys, new EncryptedNoteLogIncomingBody(this.storageSlot, this.noteTypeId, this.note), ); + return Buffer.concat([Buffer.alloc(1, numPublicValues), encryptedPayload]); } /** - * Decrypts a ciphertext as an incoming log. + * Extracts public values and 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 content - Content of the log. Contains ciphertext and public values. * @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); + public static decryptAsIncoming(content: Buffer | bigint[], ivsk: GrumpkinScalar) { + const [ciphertext, publicValues] = this.#getCiphertextAndPublicValues(content); const [address, incomingBody] = super._decryptAsIncoming( - reader.readToEnd(), + ciphertext, ivsk, EncryptedNoteLogIncomingBody.fromCiphertext, ); - return new L1NotePayload(incomingBody.note, address, incomingBody.storageSlot, incomingBody.noteTypeId); + // Partial fields are expected to be at the end of the note + const note = new Note([...incomingBody.note.items, ...publicValues]); + + return new L1NotePayload(note, address, incomingBody.storageSlot, incomingBody.noteTypeId); } /** - * Decrypts a ciphertext as an outgoing log. + * Extracts public values and 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 @@ -111,21 +118,23 @@ export class L1NotePayload extends L1Payload { * * Produces the same output as `decryptAsIncoming`. * - * @param ciphertext - The ciphertext for the log + * @param content - Content of the log. Contains ciphertext and public values. * @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); + public static decryptAsOutgoing(content: Buffer | bigint[], ovsk: GrumpkinScalar) { + const [ciphertext, publicValues] = this.#getCiphertextAndPublicValues(content); const [address, incomingBody] = super._decryptAsOutgoing( - reader.readToEnd(), + ciphertext, ovsk, EncryptedNoteLogIncomingBody.fromCiphertext, ); - return new L1NotePayload(incomingBody.note, address, incomingBody.storageSlot, incomingBody.noteTypeId); + // Partial fields are expected to be at the end of the note + const note = new Note([...incomingBody.note.items, ...publicValues]); + + return new L1NotePayload(note, address, incomingBody.storageSlot, incomingBody.noteTypeId); } public equals(other: L1NotePayload) { @@ -136,4 +145,34 @@ export class L1NotePayload extends L1Payload { this.noteTypeId.equals(other.noteTypeId) ); } + + /** + * Extracts the ciphertext and the public values from the log content. + * Input byte layout: + * +-----------------------------------+ + * | Byte | Description | + * |------|----------------------------| + * | 0 | num_pub_vals | + * |------|----------------------------| + * | 1 to | Ciphertext | + * | N | (N = total_length - 1 | + * | | - num_pub_vals * 32) | + * |------|----------------------------| + * | N+1 | Public values | + * | to | (num_pub_vals * 32 bytes) | + * | end | | + * +-----------------------------------+ + */ + static #getCiphertextAndPublicValues(content: Buffer | bigint[]): [Buffer, Fr[]] { + const input = Buffer.isBuffer(content) ? content : Buffer.from(content.map((x: bigint) => Number(x))); + + const reader = BufferReader.asReader(input); + const numPublicValues = reader.readUInt8(); + const ciphertextLength = reader.getLength() - numPublicValues * Fr.SIZE_IN_BYTES - 1; + + const ciphertext = reader.readBytes(ciphertextLength); + const publicValues = reader.readArray(numPublicValues, Fr); + + return [ciphertext, publicValues]; + } } 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 index 05cf0b20921..b97d84705d2 100644 --- 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 @@ -83,7 +83,7 @@ describe('L1 Note Payload', () => { const encrypted = taggedLog.encrypt(ephSk, recipientAddress, ivpk, ovKeys).toString('hex'); expect(encrypted).toMatchInlineSnapshot( - `"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d460c0e434d846ec1ea286e4090eb56376ff27bddc1aacae1d856549f701fa77e4f33ba2f47fdac6370f13bc5f16bbae857bbe6ab3ee4ea2a339192eef22a47ce0df4426fc314cb6294ccf291b79c1d8d362cdcc223e51020ccd3318e7052ca74f1fe922ad914bd46e4b6abcd681b63ab1c5bf4151e82f00548ae7c61c59df8c117c14c2e8d9046d32d43a7da818c68be296ef9d1446a87a450eb3f6550200d2663915b0bad97e7f7419975e5a740efb67eeb5304a90808a004ebfc156054a1459191d7fea175f6c64159b3c25a13790cca7250c30e3c80698e64565a6c9ddb16ac1479c3199fec02464b2a252202119514b02012cc387579220f03587b40444ae93f3b83dec2c0a76ed90a804981accd67d43c978d0a97de97b42b5b94c96ea50aee2086eb63d8c8b61f169c12d1deacefc1d456633e46b62daff15bcab3e1ec5f474297e1cb35d8556682060819b4563a8cc66966b12a5e73f7919318e727491b0adb8273bc4a7205b1c753b76a57cceee7482df027ae196235bb9c9ff426"`, + `"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d460c0e434d846ec1ea286e4090eb56376ff27bddc1aacae1d856549f701fa77e4f33ba2f47fdac6370f13bc5f16bbae857bbe6ab3ee4ea2a339192eef22a47ce0df4426fc314cb6294ccf291b79c1d8d362cdcc223e51020ccd3318e7052ca74f1fe922ad914bd46e4b6abcd681b63ab1c5bf4151e82f00548ae7c61c59df8c117c14c2e8d9046d32d43a7da818c68be296ef9d1446a87a450eb3f6550200d2663915b0bad97e7f7419975e5a740efb67eeb5304a90808a004ebfc156054a1459191d7fea175f6c64159b3c25a13790cca7250c30e3c80698e64565a6c9ddb16ac1479c3199fec02464b2a252202119514b02012cc387579220f03587b40444ae93f3b83dec2c0a76ed90a804981accd67d43c978d0a97de97b42b5b94c96ea50aee2086eb63d8c8b61f169c12d1deacefc1d456633e46b62daff15bcab3e1ec5f474297e1cb35d8556682060819b4563a8cc66966b12a5e73f7919318e727491b0adb8273bc4a7205b1c753b76a57cceee7482df027ae196235bb9c9ff426"`, ); const byteArrayString = `[${encrypted.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16))}]`; @@ -96,6 +96,62 @@ describe('L1 Note Payload', () => { ); }); + it('encrypted tagged log of finalized partial note matches Noir', () => { + const numPublicValues = 2; + + // 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 notePublicValue1 = new Fr(0x14172339287e8d281545c177313f02b6aa2fedfd628cfd8b7f11a136fd0d6557n); + const notePublicValue2 = new Fr(0x0834d81e3f73c7e2809b08ae38600ffc76a2554473eeab6de7bff4b33a84feacn); + 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)); + + // We manually update the log because modifying encrypt func to support public values would just polute the code + // because it is never used in prod --> the encrypt functionality is only evner used without the public values. + const encryptedBuf = taggedLog.encrypt(ephSk, recipientAddress, ivpk, ovKeys); + + // We update the encrypted buffer with the public values + encryptedBuf[64] = numPublicValues; + const encryptedBufWithPublicValues = Buffer.concat([ + encryptedBuf, + notePublicValue1.toBuffer(), + notePublicValue2.toBuffer(), + ]); + + const encrypted = encryptedBufWithPublicValues.toString('hex'); + + expect(encrypted).toMatchInlineSnapshot( + `"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028d460c0e434d846ec1ea286e4090eb56376ff27bddc1aacae1d856549f701fa77e4f33ba2f47fdac6370f13bc5f16bbae857bbe6ab3ee4ea2a339192eef22a47ce0df4426fc314cb6294ccf291b79c1d8d362cdcc223e51020ccd3318e7052ca74f1fe922ad914bd46e4b6abcd681b63ab1c5bf4151e82f00548ae7c61c59df8c117c14c2e8d9046d32d43a7da818c68be296ef9d1446a87a450eb3f6550200d2663915b0bad97e7f7419975e5a740efb67eeb5304a90808a004ebfc156054a1459191d7fea175f6c64159b3c25a13790cca7250c30e3c80698e64565a6c9ddb16ac1479c3199fec02464b2a252202119514b02012cc387579220f03587b40444ae93f3b83dec2c0a76ed90a804981accd67d43c978d0a97de97b42b5b94c96ea50aee2086eb63d8c8b61f169c12d1deacefc1d456633e46b62daff15bcab3e1ec5f474297e1cb35d8556682060819b4563a8cc66966b12a5e73f7919318e727491b0adb8273bc4a7205b1c753b76a57cceee7482df027ae196235bb9c9ff42614172339287e8d281545c177313f02b6aa2fedfd628cfd8b7f11a136fd0d65570834d81e3f73c7e2809b08ae38600ffc76a2554473eeab6de7bff4b33a84feac"`, + ); + + 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_of_finalized_partial_from_typescript', + byteArrayString, + ); + }); + const getKeyValidationRequest = (ovskM: GrumpkinScalar, app: AztecAddress) => { const ovskApp = computeOvskApp(ovskM, app); const ovpkM = derivePublicKeyFromSecretKey(ovskM);