Skip to content

Commit f2dee8f

Browse files
authored
Merge pull request #1049 from ProvableHQ/rr/add-record-decrypt-with-tvk
Add transition view key and record view key decryption methods for transitions and records.
2 parents dd3bff4 + 214c055 commit f2dee8f

File tree

12 files changed

+508
-43
lines changed

12 files changed

+508
-43
lines changed

wasm/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wasm/src/algorithms/bhp/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ mod tests {
3737
Scalar,
3838
Signature,
3939
native::{FieldNative, GroupNative, LiteralNative, PlaintextNative, ScalarNative, SignatureNative},
40-
test::{TEST_STRUCT, create_native_field_vector},
4140
types::native::{BHP256Native, BHP512Native, BHP768Native, BHP1024Native},
41+
utilities::test::{TEST_STRUCT, create_native_field_vector},
4242
};
4343
use snarkvm_console::algorithms::{Commit, CommitUncompressed, Hash, HashUncompressed, ToBits};
4444

wasm/src/algorithms/poseidon/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ mod tests {
3535
Signature,
3636
js_array_from_fields,
3737
native::{GroupNative, LiteralNative, PlaintextNative, ScalarNative, SignatureNative},
38-
test::TEST_STRUCT,
3938
types::native::{FieldNative, Poseidon2Native, Poseidon4Native, Poseidon8Native},
40-
utilities::test::create_native_field_vector,
39+
utilities::test::{TEST_STRUCT, create_native_field_vector},
4140
};
4241
use snarkvm_console::{
4342
algorithms::{Hash, HashMany, HashToGroup, HashToScalar},

wasm/src/ledger/transition.rs

Lines changed: 152 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

2933
use 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

196267
impl 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
}

wasm/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,9 @@ pub use types::{Field, Group, Scalar};
174174
mod thread_pool;
175175

176176
mod utilities;
177+
pub use utilities::EncryptionToolkit;
177178
#[cfg(test)]
178-
pub use utilities::*;
179+
pub use utilities::test::*;
179180

180181
#[cfg(test)]
181182
mod thread_pool {

wasm/src/programs/data/helpers/record.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use crate::{
1818
insert_plaintext,
1919
object,
20-
types::native::{EntryNative, RecordPlaintextNative},
20+
types::native::{PlaintextEntryNative, RecordPlaintextNative},
2121
};
2222
use js_sys::{Object, Reflect};
2323

@@ -35,13 +35,13 @@ pub fn record_to_js_object(record: &RecordPlaintextNative) -> Result<Object, Str
3535
// Get the metadata from the record and insert it into the javascript object.
3636
record.data().iter().for_each(|(key, value)| {
3737
match value {
38-
EntryNative::Public(plaintext) => {
38+
PlaintextEntryNative::Public(plaintext) => {
3939
insert_plaintext(&js_record, key, plaintext);
4040
}
41-
EntryNative::Constant(plaintext) => {
41+
PlaintextEntryNative::Constant(plaintext) => {
4242
insert_plaintext(&js_record, key, plaintext);
4343
}
44-
EntryNative::Private(plaintext) => {
44+
PlaintextEntryNative::Private(plaintext) => {
4545
insert_plaintext(&js_record, key, plaintext);
4646
}
4747
};

wasm/src/programs/execution.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,7 @@ use crate::{
2020
Transition,
2121
log,
2222
native::ProgramIDNative,
23-
types::native::{
24-
CurrentNetwork,
25-
ExecutionNative,
26-
IdentifierNative,
27-
ProcessNative,
28-
ProgramID,
29-
ProgramNative,
30-
VerifyingKeyNative,
31-
},
23+
types::native::{ExecutionNative, IdentifierNative, ProcessNative, ProgramNative, VerifyingKeyNative},
3224
};
3325
use snarkvm_algorithms::snark::varuna::VarunaVersion;
3426

0 commit comments

Comments
 (0)