@@ -23,7 +23,11 @@ use crate::{
23
23
input_to_js_value,
24
24
object,
25
25
output_to_js_value,
26
- types:: native:: { FromBytes , ToBytes , TransitionNative } ,
26
+ types:: native:: { CurrentNetwork , FieldNative , InputNative , OutputNative , TransitionNative , U16Native } ,
27
+ } ;
28
+ use snarkvm_console:: {
29
+ prelude:: { FromBytes , Network , ToBytes } ,
30
+ program:: compute_function_id,
27
31
} ;
28
32
29
33
use js_sys:: { Array , Reflect , Uint8Array } ;
@@ -191,6 +195,73 @@ impl Transition {
191
195
pub fn scm ( & self ) -> Field {
192
196
Field :: from ( self . 0 . scm ( ) )
193
197
}
198
+
199
+ /// Decrypt the transition using the transition view key.
200
+ ///
201
+ /// @param {Field} tvk The transition view key.
202
+ ///
203
+ /// @returns {Transition} The transition with public values for inputs and outputs.
204
+ #[ wasm_bindgen( js_name = decryptTransition) ]
205
+ pub fn decrypt_transition ( & self , tvk : & Field ) -> Result < Self , String > {
206
+ let function_id =
207
+ compute_function_id ( & U16Native :: new ( CurrentNetwork :: ID ) , self . 0 . program_id ( ) , self . 0 . function_name ( ) )
208
+ . map_err ( |e| e. to_string ( ) ) ?;
209
+
210
+ // Create a vector that will be populated with decrypted private inputs and
211
+ // non-private inputs.
212
+ let mut decrypted_inputs = Vec :: with_capacity ( self . 0 . inputs ( ) . len ( ) ) ;
213
+
214
+ // Iterate over the inputs and decrypt if they are private. Non-private inputs
215
+ // such as public inputs and records are copied and added to the inputs vector.
216
+ for ( index, input) in self . 0 . inputs ( ) . iter ( ) . enumerate ( ) {
217
+ decrypted_inputs. push ( match input {
218
+ InputNative :: Private ( input_id, Some ( ciphertext) ) => {
219
+ let index_field = FieldNative :: from_u16 ( index as u16 ) ;
220
+ let input_view_key = CurrentNetwork :: hash_psd4 ( & [ function_id, * * tvk, index_field] )
221
+ . map_err ( |_| "Could not create input view key" . to_string ( ) ) ?;
222
+ let plaintext = ciphertext. decrypt_symmetric ( input_view_key) . map_err ( |e| e. to_string ( ) ) ?;
223
+ InputNative :: Public ( * input_id, Some ( plaintext) )
224
+ }
225
+ _ => input. clone ( ) ,
226
+ } ) ;
227
+ }
228
+
229
+ let outputs = self . 0 . outputs ( ) ;
230
+ let num_inputs = self . 0 . inputs ( ) . len ( ) ;
231
+
232
+ // Create a vector that will be populated with decrypted private outputs and
233
+ // non-private outputs.
234
+ let mut decrypted_outputs = Vec :: with_capacity ( outputs. len ( ) ) ;
235
+
236
+ // Iterate over the outputs and decrypt if they are private. Non-private outputs
237
+ // are copied and added to the outputs vector.
238
+ for ( index, output) in outputs. iter ( ) . enumerate ( ) {
239
+ decrypted_outputs. push ( match output {
240
+ OutputNative :: Private ( output_id, Some ( ciphertext) ) => {
241
+ let index_field = FieldNative :: from_u16 ( ( num_inputs + index) as u16 ) ;
242
+ let output_view_key = CurrentNetwork :: hash_psd4 ( & [ function_id, * * tvk, index_field] )
243
+ . map_err ( |_| "Could not create output view key" . to_string ( ) ) ?;
244
+ let plaintext = ciphertext. decrypt_symmetric ( output_view_key) . map_err ( |e| e. to_string ( ) ) ?;
245
+ OutputNative :: Public ( * output_id, Some ( plaintext) )
246
+ }
247
+ _ => output. clone ( ) ,
248
+ } ) ;
249
+ }
250
+
251
+ // The Transition struct is reconstructed with the decrypted inputs and outputs.
252
+ Ok ( Self (
253
+ TransitionNative :: new (
254
+ * self . 0 . program_id ( ) ,
255
+ * self . 0 . function_name ( ) ,
256
+ decrypted_inputs,
257
+ decrypted_outputs,
258
+ * self . 0 . tpk ( ) ,
259
+ * self . 0 . tcm ( ) ,
260
+ * self . 0 . scm ( ) ,
261
+ )
262
+ . map_err ( |_| "failed to construct decrypted transition" . to_string ( ) ) ?,
263
+ ) )
264
+ }
194
265
}
195
266
196
267
impl Deref for Transition {
@@ -251,6 +322,11 @@ mod tests {
251
322
"3771264214823666953346974490700157125043441681812666085949968314967709800215field" ;
252
323
pub const TRANSITION : & str = r#"{"id":"au1naeu56spz0x0zct003sa8qgpzndy6nxj8rrcm7n0fehy9llcl5yscflt0k","program":"token_registry.aleo","function":"burn_private","inputs":[{"type":"record","id":"4569194627311410524427044648350523511369013276760031398859310110870190258038field","tag":"4584393733726099907383249165298083023636530416018938077800083356406243497342field"},{"type":"public","id":"4155661860779318196369465902681808025430867777096367712868886959018716227815field","value":"2853086u128"}],"outputs":[{"type":"record","id":"3771264214823666953346974490700157125043441681812666085949968314967709800215field","checksum":"17461704767783030875142836237730678349755524657182224909428747180538982740field","value":"record1qyqspwnlv6gfxx05yj7aw7z2dl44gyh06jrvgf42jux0dep33cy7jlsvqsrxzmt0w4h8ggcqqgqsqwdwr889h9fhnyclazs8yt26t6r5ua4qk7yksj7p40rz9846mzgrpp6x76m9de0kjezrqqpqyq9sj8x3qdmz6nal4j470a0wwcray54lffe3ya5u2zlpeq45lg4up3na8gul0vgrn3eced6dka4ax2ja85xzds4pmqf8csrn8ku5cv3qz8m90p6x2unwv9k97ct4w35x7unf0fshg6t0de0hyet3w45hyetyyvqqyqgq8djhghnte2w86qsdjaumy4zcux2fxszm3ej2956af8cpl2w95g9pqct4w35x7unf0fjkghm4de6xjmprqqpqzqxd6c782j0ny65ed2ckzp3vlx7cv8drslasq8kqpdzmjeyzal38qemw38x0axnz5t53fj6ttavh8l4jlfjdryc6mesd4w6uvpmzfsqqjvtu0xd"},{"type":"future","id":"2177527202823505610844479366424698260670813913152550670302738921219693374616field","value":"{\n program_id: token_registry.aleo,\n function_name: burn_private,\n arguments: [\n 3443843282313283355522573239085696902919850365217539366784739393210722344986field,\n 2853086u128,\n aleo1tjkv7vquk6yldxz53ecwsy5csnun43rfaknpkjc97v5223dlnyxsglv7nm,\n 5783861720504029593520331872442756678068735468923730684279741068753131773333field\n ]\n}"}],"tpk":"8426225807947287980879824833030089440060785195861154519084544916641544071836group","tcm":"3226339871444496417979841037237975848011574524309845233165930705339306709897field","scm":"6845182532650964173356391969005331370591444046632036068754797772530920467754field"}"# ;
253
324
pub const TEST_PRIVATE_KEY : & str = "APrivateKey1zkp6rE5FSWGD3jxrsAT64aZutFs3w6xvF8uQzGZKJEKsN8j" ;
325
+ pub const TRANSITION_TESTNET : & str = r#"{"id":"au1u62jasyx78x9hktak24awyj38fz73aseq8g9cx98u8egd9pj9uxq3u6s2z","program":"hello_hello.aleo","function":"hello","inputs":[{"type":"public","id":"3748790614260807060977840590007893602934308327222309419419577452790958781330field","value":"1u32"},{"type":"private","id":"5954208307642819953251922459490586292095132973876550778604572231610245257004field","value":"ciphertext1qyq0m5mp0d2gzh2pv9p25z70gz2avhqdt3dp8y8thzwf3aq6g35zcqcuyptz3"}],"outputs":[{"type":"private","id":"1557506318887190915592751299113729867877933642317637206076176689093854281418field","value":"ciphertext1qyqzmhw8ln9r6uuyh0n5jrsqlt25wdggqp3d9yqyttpr3g7g00k2sysdf9rmv"}],"tpk":"7532444547840484531569841377269810017844130178606467837628364672670182422388group","tcm":"7292056195970541935877520517416922164990366931599720071937561392936678536563field","scm":"8283770351301010771186520129040704279224805960417079922462917369178354050332field"}"# ;
326
+ pub const TRANSITION_TESTNET_DECRYPTED : & str = r#"{"id":"au1mhdz6jqm973v5vfkz2pwgv63p340c9tpvydxha2zs8w03746qcpqvx3yye","program":"hello_hello.aleo","function":"hello","inputs":[{"type":"public","id":"3748790614260807060977840590007893602934308327222309419419577452790958781330field","value":"1u32"},{"type":"public","id":"5954208307642819953251922459490586292095132973876550778604572231610245257004field","value":"2u32"}],"outputs":[{"type":"public","id":"1557506318887190915592751299113729867877933642317637206076176689093854281418field","value":"3u32"}],"tpk":"7532444547840484531569841377269810017844130178606467837628364672670182422388group","tcm":"7292056195970541935877520517416922164990366931599720071937561392936678536563field","scm":"8283770351301010771186520129040704279224805960417079922462917369178354050332field"}"# ;
327
+ pub const TRANSITION_MAINNET : & str = r#"{"id":"au1mguuz0dh20f78802m4z0py7n08xhl0pz60llzck63mhl8pc8l5xqxpwgtn","program":"hello_hello.aleo","function":"main","inputs":[{"type":"public","id":"6393584049543470937057043098611271993206122889317039351966319038535020834557field","value": "1u32"},{"type":"private","id":"8207446256045172951742235001162005156507562935942883128759030124682934277495field","value":"ciphertext1qyqqgz9qnupeld9vr4vuwp6yrpmhgtkvmgag5m7mmrruw0r6je666qgqdswk3"}],"outputs":[{"type":"private","id":"127469473292952941321346770257126666363371158501875622169294663492714835110field","value":"ciphertext1qyqyapkjuxm9dcslgyjf7hkr2k3dek500z40gjspnwvll0uawj23vzgggc405"}],"tpk":"7647553513996966044119163122930125808381703910407273818947266861843062002251group","tcm":"4479413938380109857414238205380483440836495997450846894155088299187217672609field","scm":"6461007226176477784737642021400489186736987671609840640950580467598882134642field"}"# ;
328
+ pub const TRANSITION_MAINNET_DECRYPTED : & str = r#"{"id":"au1jl2ur42sj7hwe4r0alv6gnklqxj0fszrvu3q82gjcls5x6q9pyzqdgmu2k","program":"hello_hello.aleo","function":"main","inputs":[{"type":"public","id":"6393584049543470937057043098611271993206122889317039351966319038535020834557field","value":"1u32"},{"type":"public","id":"8207446256045172951742235001162005156507562935942883128759030124682934277495field","value":"2u32"}],"outputs":[{"type":"public","id":"127469473292952941321346770257126666363371158501875622169294663492714835110field","value":"3u32"}],"tpk":"7647553513996966044119163122930125808381703910407273818947266861843062002251group","tcm":"4479413938380109857414238205380483440836495997450846894155088299187217672609field","scm":"6461007226176477784737642021400489186736987671609840640950580467598882134642field"}"# ;
329
+ pub const PRIVATE_KEY : & str = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" ;
254
330
255
331
#[ test]
256
332
fn transition_to_and_from_serialization ( ) {
@@ -334,7 +410,7 @@ mod tests {
334
410
335
411
#[ wasm_bindgen_test]
336
412
fn test_input_correctness ( ) {
337
- let transition = Transition :: from_string ( TRANSITION ) . unwrap ( ) ;
413
+ let transition = Transition :: from_str ( TRANSITION ) . unwrap ( ) ;
338
414
let inputs = transition. inputs ( true ) ;
339
415
let input_1 = Object :: from ( inputs. get ( 0 ) ) ;
340
416
let input_2 = inputs. get ( 1 ) ;
@@ -406,4 +482,78 @@ mod tests {
406
482
"5783861720504029593520331872442756678068735468923730684279741068753131773333field"
407
483
) ;
408
484
}
485
+
486
+ #[ wasm_bindgen_test]
487
+ #[ cfg( feature = "testnet" ) ]
488
+ fn test_transition_decryption ( ) {
489
+ // Create a view key from the test private key.
490
+ let private_key = PrivateKey :: from_str ( PRIVATE_KEY ) . unwrap ( ) ;
491
+ let view_key = ViewKey :: from_private_key ( & private_key) ;
492
+
493
+ // Get a transition with records.
494
+ let transition = Transition :: from_string ( TRANSITION_TESTNET ) . unwrap ( ) ;
495
+
496
+ // Get the transition view key.
497
+ let tvk = transition. tvk ( & view_key) ;
498
+
499
+ // Decrypt the transition using the transition view key.
500
+ let decrypted_transition = transition. decrypt_transition ( & tvk) . unwrap ( ) ;
501
+
502
+ assert_eq ! ( decrypted_transition. to_string( ) , TRANSITION_TESTNET_DECRYPTED ) ;
503
+ }
504
+
505
+ #[ wasm_bindgen_test]
506
+ #[ cfg( feature = "mainnet" ) ]
507
+ fn test_transition_decryption_mainnet ( ) {
508
+ // Create a view key from the test private key.
509
+ let private_key = PrivateKey :: from_str ( PRIVATE_KEY ) . unwrap ( ) ;
510
+ let view_key = ViewKey :: from_private_key ( & private_key) ;
511
+
512
+ // Get a transition with records.
513
+ let transition = Transition :: from_string ( TRANSITION_MAINNET ) . unwrap ( ) ;
514
+
515
+ // Get the transition view key.
516
+ let tvk = transition. tvk ( & view_key) ;
517
+
518
+ // Decrypt the transition using the transition view key.
519
+ let decrypted_transition = transition. decrypt_transition ( & tvk) . unwrap ( ) ;
520
+
521
+ assert_eq ! ( decrypted_transition. to_string( ) , TRANSITION_MAINNET_DECRYPTED ) ;
522
+ }
523
+
524
+ #[ wasm_bindgen_test]
525
+ #[ cfg( feature = "testnet" ) ]
526
+ fn test_transition_decrypt_testnet_invalid_vk ( ) {
527
+ // Create a view key from the test private key.
528
+ let private_key = PrivateKey :: from_str ( TEST_PRIVATE_KEY ) . unwrap ( ) ;
529
+ let view_key = ViewKey :: from_private_key ( & private_key) ;
530
+
531
+ // Get a transition with records.
532
+ let transition = Transition :: from_string ( TRANSITION_TESTNET ) . unwrap ( ) ;
533
+
534
+ // Create an invalid transition view key.
535
+ let invalid_tvk = transition. tvk ( & view_key) ;
536
+
537
+ // Attempt to decrypt the transition using the invalid transition view key.
538
+ let result = transition. decrypt_transition ( & invalid_tvk) ;
539
+ assert ! ( result. is_err( ) ) ;
540
+ }
541
+
542
+ #[ wasm_bindgen_test]
543
+ #[ cfg( feature = "mainnet" ) ]
544
+ fn test_transition_decrypt_testnet_invalid_vk ( ) {
545
+ // Create a view key from the test private key.
546
+ let private_key = PrivateKey :: from_str ( TEST_PRIVATE_KEY ) . unwrap ( ) ;
547
+ let view_key = ViewKey :: from_private_key ( & private_key) ;
548
+
549
+ // Get a transition with records.
550
+ let transition = Transition :: from_string ( TRANSITION_MAINNET ) . unwrap ( ) ;
551
+
552
+ // Create an invalid transition view key.
553
+ let invalid_tvk = transition. tvk ( & view_key) ;
554
+
555
+ // Attempt to decrypt the transition using the invalid transition view key.
556
+ let result = transition. decrypt_transition ( & invalid_tvk) ;
557
+ assert ! ( result. is_err( ) ) ;
558
+ }
409
559
}
0 commit comments