@@ -20,6 +20,7 @@ mod test {
2020 messages::encryption::aes128:: derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe ,
2121 utils ::{array::subarray:: subarray , point:: point_from_x_coord },
2222 };
23+ use crate::test::helpers::test_environment::TestEnvironment ;
2324 use super::aes128_decrypt_oracle ;
2425 use poseidon::poseidon2::Poseidon2 ;
2526 use std::aes128::aes128_encrypt ;
@@ -30,108 +31,116 @@ mod test {
3031
3132 #[test]
3233 unconstrained fn aes_encrypt_then_decrypt () {
33- let ciphertext_shared_secret = point_from_x_coord (1 );
34-
35- let (sym_key , iv ) = derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe ::<1 >(
36- ciphertext_shared_secret ,
37- )[0 ];
38-
39- let plaintext : [u8 ; TEST_PLAINTEXT_LENGTH ] = [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ];
40-
41- let ciphertext : [u8 ; TEST_CIPHERTEXT_LENGTH ] = aes128_encrypt (plaintext , iv , sym_key );
42-
43- // We need to convert the array to a BoundedVec because the oracle expects a BoundedVec as it's designed to work
44- // with logs with unknown length at compile time. This would not be necessary here as the header ciphertext length
45- // is fixed. But we do it anyway to not have to have duplicate oracles.
46- let ciphertext_bvec = BoundedVec ::<u8 , TEST_CIPHERTEXT_LENGTH >::from_array (ciphertext );
47-
48- let received_plaintext = aes128_decrypt_oracle (ciphertext_bvec , iv , sym_key );
49-
50- assert_eq (received_plaintext .len (), TEST_PLAINTEXT_LENGTH );
51- assert_eq (received_plaintext .max_len (), TEST_CIPHERTEXT_LENGTH );
52- assert_eq (
53- subarray ::<_ , _ , TEST_PLAINTEXT_LENGTH >(received_plaintext .storage (), 0 ),
54- plaintext ,
55- );
56- assert_eq (
57- subarray ::<_ , _ , TEST_PADDING_LENGTH >(
58- received_plaintext .storage (),
59- TEST_PLAINTEXT_LENGTH ,
60- ),
61- [0 as u8 ; TEST_PADDING_LENGTH ],
62- );
34+ let env = TestEnvironment ::new ();
35+
36+ env .utility_context (|_ | {
37+ let ciphertext_shared_secret = point_from_x_coord (1 );
38+
39+ let (sym_key , iv ) = derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe ::<1 >(
40+ ciphertext_shared_secret ,
41+ )[0 ];
42+
43+ let plaintext : [u8 ; TEST_PLAINTEXT_LENGTH ] = [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ];
44+
45+ let ciphertext : [u8 ; TEST_CIPHERTEXT_LENGTH ] = aes128_encrypt (plaintext , iv , sym_key );
46+
47+ // We need to convert the array to a BoundedVec because the oracle expects a BoundedVec as it's designed to work
48+ // with logs with unknown length at compile time. This would not be necessary here as the header ciphertext length
49+ // is fixed. But we do it anyway to not have to have duplicate oracles.
50+ let ciphertext_bvec = BoundedVec ::<u8 , TEST_CIPHERTEXT_LENGTH >::from_array (ciphertext );
51+
52+ let received_plaintext = aes128_decrypt_oracle (ciphertext_bvec , iv , sym_key );
53+
54+ assert_eq (received_plaintext .len (), TEST_PLAINTEXT_LENGTH );
55+ assert_eq (received_plaintext .max_len (), TEST_CIPHERTEXT_LENGTH );
56+ assert_eq (
57+ subarray ::<_ , _ , TEST_PLAINTEXT_LENGTH >(received_plaintext .storage (), 0 ),
58+ plaintext ,
59+ );
60+ assert_eq (
61+ subarray ::<_ , _ , TEST_PADDING_LENGTH >(
62+ received_plaintext .storage (),
63+ TEST_PLAINTEXT_LENGTH ,
64+ ),
65+ [0 as u8 ; TEST_PADDING_LENGTH ],
66+ );
67+ })
6368 }
6469
6570 global TEST_MAC_LENGTH : u32 = 32 ;
6671
6772 #[test(should_fail_with = "mac does not match")]
6873 unconstrained fn aes_encrypt_then_decrypt_with_bad_sym_key_is_caught () {
69- // The AES decryption oracle will not fail for any ciphertext; it will always
70- // return some data. As for whether the decryption was successful, it's up
71- // to the app to check this in a custom way.
72- //
73- // E.g. if it's a note that's been encrypted, then upon decryption, the app
74- // can check to see if the note hash exists onchain. If it doesn't exist
75- // onchain, then that's a strong indicator that decryption has failed.
76- //
77- // E.g. for non-note messages, the plaintext could include a MAC
78- // (https://en.wikipedia.org/wiki/Message_authentication_code). We
79- // demonstrate what this could look like in this test.
80- //
81- // We compute a MAC and we include that MAC in the plaintext. We then encrypt
82- // this plaintext to get a ciphertext. We broadcast the [ciphertext, mac]
83- // tuple. The eventual decryptor will expect the mac in the decrypted plaintext
84- // to match the mac that was broadcast. If not, the recipient knows that
85- // decryption has failed.
86- let ciphertext_shared_secret = point_from_x_coord (1 );
87-
88- let (sym_key , iv ) = derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe ::<1 >(
89- ciphertext_shared_secret ,
90- )[0 ];
91-
92- let mac_preimage = 0x42 ;
93- let mac = Poseidon2 ::hash ([mac_preimage ], 1 );
94- let mac_as_bytes = mac .to_be_bytes ::<TEST_MAC_LENGTH >();
95-
96- let plaintext : [u8 ; TEST_PLAINTEXT_LENGTH ] = [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ];
97-
98- // We append the mac to the plaintext. It doesn't necessarily have to be 32 bytes;
99- // that's quite an extreme length. 16 bytes or 8 bytes might be sufficient, and would
100- // save on data broadcasting costs. The shorter the mac, the more possibility
101- // of false positive decryptions (decryption seemingly succeeding, but the
102- // decrypted plaintext being garbage).
103- // Some projects use the `iv` (all 16 bytes or the first 8 bytes) as a mac.
104- let mut plaintext_with_mac = [0 as u8 ; TEST_PLAINTEXT_LENGTH + TEST_MAC_LENGTH ];
105- for i in 0 ..TEST_PLAINTEXT_LENGTH {
106- plaintext_with_mac [i ] = plaintext [i ];
107- }
108- for i in 0 ..TEST_MAC_LENGTH {
109- plaintext_with_mac [TEST_PLAINTEXT_LENGTH + i ] = mac_as_bytes [i ];
110- }
111-
112- let ciphertext = aes128_encrypt (plaintext_with_mac , iv , sym_key );
113-
114- // We now would broadcast the tuple [ciphertext, mac] to the network.
115- // The recipient will then decrypt the ciphertext, and if the mac inside the
116- // received plaintext matches the mac that was broadcast, then the recipient
117- // knows that decryption was successful.
118-
119- // For this test, we intentionally mutate the sym_key to a bad one, so that
120- // decryption fails. This allows us to explore how the recipient can detect
121- // failed decryption by checking the decrypted mac against the broadcasted
122- // mac.
123- let mut bad_sym_key = sym_key ;
124- bad_sym_key [0 ] = 0 ;
125-
126- // We need to convert the array to a BoundedVec because the oracle expects a BoundedVec as it's designed to work
127- // with logs of unknown length.
128- let ciphertext_bvec = BoundedVec ::<u8 , 48 >::from_array (ciphertext );
129- let received_plaintext = aes128_decrypt_oracle (ciphertext_bvec , iv , bad_sym_key );
130-
131- let extracted_mac_as_bytes : [u8 ; TEST_MAC_LENGTH ] =
132- subarray (received_plaintext .storage (), TEST_PLAINTEXT_LENGTH );
133-
134- // We expect this assertion to fail, because we used a bad sym key.
135- assert_eq (mac_as_bytes , extracted_mac_as_bytes , "mac does not match" );
74+ let env = TestEnvironment ::new ();
75+
76+ env .utility_context (|_ | {
77+ // The AES decryption oracle will not fail for any ciphertext; it will always
78+ // return some data. As for whether the decryption was successful, it's up
79+ // to the app to check this in a custom way.
80+ //
81+ // E.g. if it's a note that's been encrypted, then upon decryption, the app
82+ // can check to see if the note hash exists onchain. If it doesn't exist
83+ // onchain, then that's a strong indicator that decryption has failed.
84+ //
85+ // E.g. for non-note messages, the plaintext could include a MAC
86+ // (https://en.wikipedia.org/wiki/Message_authentication_code). We
87+ // demonstrate what this could look like in this test.
88+ //
89+ // We compute a MAC and we include that MAC in the plaintext. We then encrypt
90+ // this plaintext to get a ciphertext. We broadcast the [ciphertext, mac]
91+ // tuple. The eventual decryptor will expect the mac in the decrypted plaintext
92+ // to match the mac that was broadcast. If not, the recipient knows that
93+ // decryption has failed.
94+ let ciphertext_shared_secret = point_from_x_coord (1 );
95+
96+ let (sym_key , iv ) = derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe ::<1 >(
97+ ciphertext_shared_secret ,
98+ )[0 ];
99+
100+ let mac_preimage = 0x42 ;
101+ let mac = Poseidon2 ::hash ([mac_preimage ], 1 );
102+ let mac_as_bytes = mac .to_be_bytes ::<TEST_MAC_LENGTH >();
103+
104+ let plaintext : [u8 ; TEST_PLAINTEXT_LENGTH ] = [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ];
105+
106+ // We append the mac to the plaintext. It doesn't necessarily have to be 32 bytes;
107+ // that's quite an extreme length. 16 bytes or 8 bytes might be sufficient, and would
108+ // save on data broadcasting costs. The shorter the mac, the more possibility
109+ // of false positive decryptions (decryption seemingly succeeding, but the
110+ // decrypted plaintext being garbage).
111+ // Some projects use the `iv` (all 16 bytes or the first 8 bytes) as a mac.
112+ let mut plaintext_with_mac = [0 as u8 ; TEST_PLAINTEXT_LENGTH + TEST_MAC_LENGTH ];
113+ for i in 0 ..TEST_PLAINTEXT_LENGTH {
114+ plaintext_with_mac [i ] = plaintext [i ];
115+ }
116+ for i in 0 ..TEST_MAC_LENGTH {
117+ plaintext_with_mac [TEST_PLAINTEXT_LENGTH + i ] = mac_as_bytes [i ];
118+ }
119+
120+ let ciphertext = aes128_encrypt (plaintext_with_mac , iv , sym_key );
121+
122+ // We now would broadcast the tuple [ciphertext, mac] to the network.
123+ // The recipient will then decrypt the ciphertext, and if the mac inside the
124+ // received plaintext matches the mac that was broadcast, then the recipient
125+ // knows that decryption was successful.
126+
127+ // For this test, we intentionally mutate the sym_key to a bad one, so that
128+ // decryption fails. This allows us to explore how the recipient can detect
129+ // failed decryption by checking the decrypted mac against the broadcasted
130+ // mac.
131+ let mut bad_sym_key = sym_key ;
132+ bad_sym_key [0 ] = 0 ;
133+
134+ // We need to convert the array to a BoundedVec because the oracle expects a BoundedVec as it's designed to work
135+ // with logs of unknown length.
136+ let ciphertext_bvec = BoundedVec ::<u8 , 48 >::from_array (ciphertext );
137+ let received_plaintext = aes128_decrypt_oracle (ciphertext_bvec , iv , bad_sym_key );
138+
139+ let extracted_mac_as_bytes : [u8 ; TEST_MAC_LENGTH ] =
140+ subarray (received_plaintext .storage (), TEST_PLAINTEXT_LENGTH );
141+
142+ // We expect this assertion to fail, because we used a bad sym key.
143+ assert_eq (mac_as_bytes , extracted_mac_as_bytes , "mac does not match" );
144+ });
136145 }
137146}
0 commit comments