@@ -31,6 +31,7 @@ use crate::{
31
31
U8Native ,
32
32
} ,
33
33
} ;
34
+ use rayon:: iter:: { IntoParallelIterator , IntoParallelRefIterator , ParallelIterator } ;
34
35
use snarkvm_console:: prelude:: { FromFields , Itertools , Network , Visibility } ;
35
36
36
37
use indexmap:: IndexMap ;
@@ -210,6 +211,48 @@ impl EncryptionToolkit {
210
211
pub fn decrypt_transition_with_vk ( transition : & Transition , transition_vk : & Field ) -> Result < Transition , String > {
211
212
transition. decrypt_transition ( transition_vk)
212
213
}
214
+
215
+ /// Decrypts a set of record ciphertexts in parallel and stores successful decryptions.
216
+ ///
217
+ /// @param {ViewKey} view_key The view key of the owner of the records.
218
+ /// @param {Vec<RecordCiphertext>} records The record ciphertexts to decrypt.
219
+ ///
220
+ /// @returns {vec<RecordPlaintext>} The decrypted record plaintexts.
221
+ #[ wasm_bindgen( js_name = "decryptOwnedRecords" ) ]
222
+ pub fn decrypt_owned_records (
223
+ view_key : & ViewKey ,
224
+ records : Vec < RecordCiphertext > ,
225
+ ) -> Result < Vec < RecordPlaintext > , String > {
226
+ // Use Rayon to parallelize the decryption process and store successful decryptions.
227
+ let decrypted_records: Vec < RecordPlaintext > = records
228
+ . par_iter ( )
229
+ . filter_map ( |record| {
230
+ let record_vk = Self :: generate_record_view_key ( view_key, record) . ok ( ) ?;
231
+ let decrypted_record = Self :: decrypt_record_symmetric_unchecked ( & record_vk, record) . ok ( ) ?;
232
+ Some ( decrypted_record)
233
+ } )
234
+ . collect ( ) ;
235
+
236
+ Ok ( decrypted_records)
237
+ }
238
+
239
+ /// Checks if a record ciphertext is owned by the given view key.
240
+ ///
241
+ /// @param {ViewKey} view_key View key of the owner of the records.
242
+ /// @param {Vec<RecordCiphertext>} records The record ciphertexts for which to check ownership.
243
+ ///
244
+ /// @returns {Vec<RecordCiphertext>} The record ciphertexts that are owned by the view key.
245
+ #[ wasm_bindgen( js_name = "checkOwnedRecords" ) ]
246
+ pub fn check_owned_records (
247
+ view_key : & ViewKey ,
248
+ records : Vec < RecordCiphertext > ,
249
+ ) -> Result < Vec < RecordCiphertext > , String > {
250
+ // Use Rayon to parallelize the ownership check.
251
+ let owned_records: Vec < RecordCiphertext > =
252
+ records. into_par_iter ( ) . filter ( |record| record. is_owner ( view_key) ) . collect ( ) ;
253
+
254
+ Ok ( owned_records)
255
+ }
213
256
}
214
257
215
258
#[ cfg( test) ]
@@ -221,6 +264,8 @@ mod tests {
221
264
222
265
const NON_OWNER_VIEW_KEY : & str = "AViewKey1e2WyreaH5H4RBcioLL2GnxvHk5Ud46EtwycnhTdXLmXp" ;
223
266
const OWNER_CIPHERTEXT : & str = "record1qyqsqpe2szk2wwwq56akkwx586hkndl3r8vzdwve32lm7elvphh37rsyqyxx66trwfhkxun9v35hguerqqpqzqrtjzeu6vah9x2me2exkgege824sd8x2379scspmrmtvczs0d93qttl7y92ga0k0rsexu409hu3vlehe3yxjhmey3frh2z5pxm5cmxsv4un97q" ;
267
+ const NON_OWNED_CIPHERTEXT_1 : & str = "RECORD1QVQSQ5H8YT5682E73ZT7PYNJGPL29MWTSETRVS9VHCKFHJRNX9RX94CFQYXX66TRWFHKXUN9V35HGUERQQPQZQZ6KMY7S5HPKKF02L6R46QM8RQCW9X0K4RQ6GT234AMJ2UG3LMTQT5NY4UG8SXJY3U8D05K4Q3E9F54VX67ZMD3G6JYQQ7KXRWS0R0SWM6P833" ;
268
+ const NON_OWNED_CIPHERTEXT_2 : & str = "RECORD1QVQSP37HJE4CEU8EFZE8XMAHE5TDTXCZ0K534WQPKVN6C9R629X3C4Q8QYRXZMT0W4H8GGCQQGQSPVUJYCN0K7HYFHENXA40HXTFSX68092WMVJ4E3XSEXR2DY0FMCCXT0DS42W5MAASZFJV930QVQRKATQJ900AKU4K777UMH2K54ZHLUGQC2AFJD" ;
224
269
const OWNER_PLAINTEXT : & str = r"{
225
270
owner: aleo1j7qxyunfldj2lp8hsvy7mw5k8zaqgjfyr72x2gh3x4ewgae8v5gscf5jh3.private,
226
271
microcredits: 1500000000000000u64.private,
@@ -284,4 +329,41 @@ mod tests {
284
329
let result = EncryptionToolkit :: decrypt_record_symmetric_unchecked ( & record_vk, & owner_ciphertext) ;
285
330
assert ! ( result. is_err( ) , "Decryption should fail with a non-owner's view key" ) ;
286
331
}
332
+
333
+ #[ wasm_bindgen_test]
334
+ fn test_bulk_record_decryption ( ) {
335
+ let owned_ciphertext = RecordCiphertext :: from_str ( OWNER_CIPHERTEXT ) . unwrap ( ) ;
336
+ //Need to add these two non-owned ciphertexts for testing
337
+ let nonowned_ciphertext_1 = RecordCiphertext :: from_str ( NON_OWNED_CIPHERTEXT_1 ) . unwrap ( ) ;
338
+ let nonowned_ciphertext_2 = RecordCiphertext :: from_str ( NON_OWNED_CIPHERTEXT_2 ) . unwrap ( ) ;
339
+
340
+ let records: Vec < RecordCiphertext > = vec ! [ owned_ciphertext, nonowned_ciphertext_1, nonowned_ciphertext_2] ;
341
+ let owner_view_key = ViewKey :: from_str ( OWNER_VIEW_KEY ) . unwrap ( ) ;
342
+
343
+ // Decrypt the owned records
344
+ let decrypted_records = EncryptionToolkit :: decrypt_owned_records ( & owner_view_key, records) . unwrap ( ) ;
345
+ // Verify that only the owned record was decrypted
346
+ assert_eq ! ( decrypted_records. len( ) , 1 , "Only one record should be decrypted" ) ;
347
+ assert_eq ! (
348
+ decrypted_records[ 0 ] . to_string( ) ,
349
+ OWNER_PLAINTEXT ,
350
+ "Decrypted record should match the owner's plaintext"
351
+ ) ;
352
+ }
353
+
354
+ #[ wasm_bindgen_test]
355
+ fn test_check_owned_records ( ) {
356
+ let owned_ciphertext = RecordCiphertext :: from_str ( OWNER_CIPHERTEXT ) . unwrap ( ) ;
357
+ // Need to add these two non-owned ciphertexts for testing
358
+ let nonowned_ciphertext_1 = RecordCiphertext :: from_str ( NON_OWNED_CIPHERTEXT_1 ) . unwrap ( ) ;
359
+ let nonowned_ciphertext_2 = RecordCiphertext :: from_str ( NON_OWNED_CIPHERTEXT_2 ) . unwrap ( ) ;
360
+ let records: Vec < RecordCiphertext > = vec ! [ owned_ciphertext, nonowned_ciphertext_1, nonowned_ciphertext_2] ;
361
+ let owner_view_key = ViewKey :: from_str ( OWNER_VIEW_KEY ) . unwrap ( ) ;
362
+
363
+ // Check owned records
364
+ let owned_records = EncryptionToolkit :: check_owned_records ( & owner_view_key, records) . unwrap ( ) ;
365
+ // Verify that only the owned record is returned
366
+ assert_eq ! ( owned_records. len( ) , 1 , "Only one owned record should be returned" ) ;
367
+ assert_eq ! ( owned_records[ 0 ] . to_string( ) , OWNER_CIPHERTEXT , "Owned record should match the owner's ciphertext" ) ;
368
+ }
287
369
}
0 commit comments