@@ -23,7 +23,11 @@ use crate::{
2323 input_to_js_value,
2424 object,
2525 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,
2731} ;
2832
2933use js_sys:: { Array , Reflect , Uint8Array } ;
@@ -191,6 +195,73 @@ impl Transition {
191195 pub fn scm ( & self ) -> Field {
192196 Field :: from ( self . 0 . scm ( ) )
193197 }
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+ }
194265}
195266
196267impl Deref for Transition {
@@ -251,6 +322,11 @@ mod tests {
251322 "3771264214823666953346974490700157125043441681812666085949968314967709800215field" ;
252323 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"}"# ;
253324 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" ;
254330
255331 #[ test]
256332 fn transition_to_and_from_serialization ( ) {
@@ -334,7 +410,7 @@ mod tests {
334410
335411 #[ wasm_bindgen_test]
336412 fn test_input_correctness ( ) {
337- let transition = Transition :: from_string ( TRANSITION ) . unwrap ( ) ;
413+ let transition = Transition :: from_str ( TRANSITION ) . unwrap ( ) ;
338414 let inputs = transition. inputs ( true ) ;
339415 let input_1 = Object :: from ( inputs. get ( 0 ) ) ;
340416 let input_2 = inputs. get ( 1 ) ;
@@ -406,4 +482,78 @@ mod tests {
406482 "5783861720504029593520331872442756678068735468923730684279741068753131773333field"
407483 ) ;
408484 }
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+ }
409559}
0 commit comments