Skip to content

Commit 81272ef

Browse files
Add functionality for decryption of sender ciphertexts
1 parent 3d5f9e2 commit 81272ef

File tree

7 files changed

+164
-3
lines changed

7 files changed

+164
-3
lines changed

sdk/tests/data/records.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
/// V1 credits.aleo record.
2+
const CREDITS_RECORD_V1 = "{ owner: aleo12a4wll9ax6w5355jph0dr5wt2vla5sss2t4cnch0tc3vzh643v8qcfvc7a.private, microcredits: 1000000u64.private, _nonce: 3634848344765318974603121890869676775499130077229666060613233255327643175219group.public, _version: 1u8.public }";
3+
4+
/// Record view key for the V1 credits.aleo record.
5+
const CREDITS_RECORD_VIEW_KEY = "5237002936265850807349726649400053591020997883662246784632368923777787639801field";
6+
7+
/// Sender ciphertext of the credits.aleo record.
8+
const CREDITS_SENDER_CIPHERTEXT = "1182590395568997043375432557467567048762179115999922880321493200728848194550field";
9+
10+
/// Sender plaintext of the credits.aleo record.
11+
const CREDITS_SENDER_PLAINTEXT = "aleo1j92w9mhqznj2hvufad796y8suykjppk7f6n6xmncmktfm95vggzqx4sjlh";
12+
113
// Ciphertext records
214
const RECORD_CIPHERTEXT_STRING = "record1qyqsqpe2szk2wwwq56akkwx586hkndl3r8vzdwve32lm7elvphh37rsyqyxx66trwfhkxun9v35hguerqqpqzqrtjzeu6vah9x2me2exkgege824sd8x2379scspmrmtvczs0d93qttl7y92ga0k0rsexu409hu3vlehe3yxjhmey3frh2z5pxm5cmxsv4un97q";
315
const RECORD_CIPHERTEXT_STRING_COPY = "record1qyqsqpe2szk2wwwq56akkwx586hkndl3r8vzdwve32lm7elvphh37rsyqyxx66trwfhkxun9v35hguerqqpqzqrtjzeu6vah9x2me2exkgege824sd8x2379scspmrmtvczs0d93qttl7y92ga0k0rsexu409hu3vlehe3yxjhmey3frh2z5pxm5cmxsv4un97q";
@@ -16,6 +28,10 @@ const VIEW_KEY_STRING = "AViewKey1ccEt8A2Ryva5rxnKcAbn7wgTaTsb79tzkKHFpeKsm9NX";
1628
const RECORD_VIEW_KEY_STRING = "4445718830394614891114647247073357094867447866913203502139893824059966201724field";
1729

1830
export {
31+
CREDITS_RECORD_V1,
32+
CREDITS_RECORD_VIEW_KEY,
33+
CREDITS_SENDER_CIPHERTEXT,
34+
CREDITS_SENDER_PLAINTEXT,
1935
RECORD_CIPHERTEXT_STRING,
2036
RECORD_CIPHERTEXT_STRING_COPY,
2137
RECORD_CIPHERTEXT_STRING_NOT_OWNED,

sdk/tests/network-client.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ describe("NodeConnection", () => {
368368
}
369369
});
370370

371-
it.only("should throw for a malformed tx ID", async () => {
371+
it("should throw for a malformed tx ID", async () => {
372372
const connection = new AleoNetworkClient(host);
373373
try {
374374
await connection.waitForTransactionConfirmation(invalidTx);

sdk/tests/wasm.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import {
1212
beaconPrivateKeyString
1313
} from "./data/account-data.js";
1414
import {
15+
CREDITS_RECORD_V1,
16+
CREDITS_RECORD_VIEW_KEY,
17+
CREDITS_SENDER_CIPHERTEXT,
18+
CREDITS_SENDER_PLAINTEXT,
1519
RECORD_CIPHERTEXT_STRING,
1620
RECORD_CIPHERTEXT_STRING_COPY,
1721
RECORD_CIPHERTEXT_STRING_NOT_OWNED,
@@ -20,6 +24,7 @@ import {
2024
RECORD_VIEW_KEY_STRING,
2125
VIEW_KEY_STRING,
2226
} from "./data/records.js";
27+
import process from "node:process";
2328

2429
describe('WASM Objects', () => {
2530
describe('Address', () => {
@@ -483,6 +488,29 @@ describe('WASM Objects', () => {
483488
// Ensure the decrypted record is the same as the plaintext
484489
expect(decryptedRecords[0].toString()).equal(recordPlaintextCopy.toString());
485490
});
491+
it('can decrypt sender ciphertexts', () => {
492+
// Get the private key corresponding to the record.
493+
const private_key = PrivateKey.from_string(<string>process.env["PUZZLE_PK"]);
494+
const view_key = ViewKey.from_private_key(private_key);
495+
496+
// Construct the record and the sender ciphertext.
497+
const record = RecordPlaintext.fromString(CREDITS_RECORD_V1);
498+
const record_view_key = Field.fromString(CREDITS_RECORD_VIEW_KEY);
499+
const sender_ciphertext = Field.fromString(CREDITS_SENDER_CIPHERTEXT);
500+
501+
// Decrypt the sender ciphertext using the record object and ensure it's from the expected address.
502+
let sender = record.decryptSender(view_key, sender_ciphertext);
503+
expect(sender.to_string()).to.equal(CREDITS_SENDER_PLAINTEXT);
504+
505+
// Decrypt the sender ciphertext using the EncryptionToolkit function and ensure it's from the expected address.
506+
sender = EncryptionToolkit.decryptSender(view_key, record, sender_ciphertext);
507+
expect(sender.to_string()).to.equal(CREDITS_SENDER_PLAINTEXT);
508+
509+
// Decrypt the sender ciphertext using only the record view key and ensure it's from the expected address.
510+
sender = EncryptionToolkit.decryptSenderWithRvk(record_view_key, sender_ciphertext);
511+
expect(sender.to_string()).to.equal(CREDITS_SENDER_PLAINTEXT);
512+
})
513+
it('can decryption')
486514
});
487515
describe('VerifyingKey', () => {
488516
it('can get the number of constraints', async () => {

wasm/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@provablehq/wasm",
33
"version": "0.9.8",
4-
"type": "module",
4+
"type":"module",
55
"description": "SnarkVM WASM binaries with javascript bindings",
66
"collaborators": [
77
"The Provable Team"

wasm/src/record/record_plaintext.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use crate::{
1818
Address,
1919
Credits,
20+
EncryptionToolkit,
2021
GraphKey,
2122
Plaintext,
2223
PrivateKey,
@@ -266,6 +267,18 @@ impl RecordPlaintext {
266267
Group::from_string(&self.nonce()).unwrap().scalar_multiply(&view_key.to_scalar()).to_x_coordinate()
267268
}
268269

270+
/// Decrypt the sender ciphertext associated with the record.
271+
///
272+
/// @param {ViewKey} view_key View key associated with the record.
273+
/// @param {Field} sender_ciphertext Sender ciphertext associated with the record.
274+
///
275+
/// @returns {Address} address of the sender.
276+
#[wasm_bindgen(js_name = decryptSender)]
277+
pub fn decrypt_sender(&self, view_key: &ViewKey, sender_ciphertext: &Field) -> Result<Address, String> {
278+
let record_view_key = self.record_view_key(view_key);
279+
EncryptionToolkit::decrypt_sender_with_rvk(&record_view_key, sender_ciphertext)
280+
}
281+
269282
/// Clone the RecordPlaintext WASM object.
270283
///
271284
/// @returns {RecordPlaintext} A clone of the RecordPlaintext WASM object.
@@ -319,6 +332,11 @@ impl FromStr for RecordPlaintext {
319332
mod tests {
320333
use super::*;
321334

335+
use crate::{
336+
types::native::{PrivateKeyNative, ViewKeyNative},
337+
utilities::test::{CREDITS_RECORD_V1, CREDITS_SENDER_CIPHERTEXT, CREDITS_SENDER_PLAINTEXT, get_env},
338+
};
339+
322340
use wasm_bindgen_test::*;
323341

324342
const CREDITS_RECORD: &str = r"{
@@ -449,4 +467,19 @@ mod tests {
449467
);
450468
assert!(RecordPlaintext::from_string(invalid_bech32).is_err());
451469
}
470+
471+
#[wasm_bindgen_test]
472+
fn test_record_decrypt_record_sender_ciphertext() {
473+
// Get the private key corresponding to the record.
474+
let private_key = PrivateKeyNative::from_str(&get_env("PUZZLE_PK")).unwrap();
475+
let view_key = ViewKey::from(ViewKeyNative::try_from(private_key).unwrap());
476+
477+
// Construct the record and the sender ciphertext.
478+
let record = RecordPlaintext::from_string(CREDITS_RECORD_V1).unwrap();
479+
let sender_ciphertext = Field::from_string(CREDITS_SENDER_CIPHERTEXT).unwrap();
480+
481+
// Decrypt the sender ciphertext and ensure it's from the expected address.
482+
let sender = record.decrypt_sender(&view_key, &sender_ciphertext).unwrap();
483+
assert_eq!(sender.to_string(), CREDITS_SENDER_PLAINTEXT.to_string());
484+
}
452485
}

wasm/src/utilities/encrypt.rs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,27 @@
1515
// along with the Provable SDK library. If not, see <https://www.gnu.org/licenses/>.
1616

1717
use crate::{
18+
Address,
1819
Field,
1920
Group,
2021
RecordCiphertext,
2122
RecordPlaintext,
2223
Transition,
2324
ViewKey,
2425
types::native::{
26+
AddressNative,
2527
CiphertextEntryNative,
2628
CurrentNetwork,
2729
FieldNative,
30+
GroupNative,
2831
PlaintextEntryNative,
2932
PlaintextNative,
3033
RecordPlaintextNative,
3134
U8Native,
3235
},
3336
};
3437
use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
35-
use snarkvm_console::prelude::{FromFields, Itertools, Network, Visibility};
38+
use snarkvm_console::prelude::{FromField, FromFields, Itertools, Network, One, Visibility};
3639

3740
use indexmap::IndexMap;
3841
use wasm_bindgen::prelude::wasm_bindgen;
@@ -253,12 +256,58 @@ impl EncryptionToolkit {
253256

254257
Ok(owned_records)
255258
}
259+
260+
/// Decrypt the sender ciphertext associated with a record.
261+
///
262+
/// @param {ViewKey} view_key View key associated with the record.
263+
/// @param {RecordPlaintext} record Record plaintext associated with a sender.
264+
/// @param {Field} sender_ciphertext Sender ciphertext associated with the record.
265+
///
266+
/// @returns {Address} address of the sender.
267+
#[wasm_bindgen(js_name = decryptSender)]
268+
pub fn decrypt_sender(
269+
view_key: &ViewKey,
270+
record: &RecordPlaintext,
271+
sender_ciphertext: &Field,
272+
) -> Result<Address, String> {
273+
let record_view_key = record.record_view_key(view_key);
274+
Self::decrypt_sender_with_rvk(&record_view_key, sender_ciphertext)
275+
}
276+
277+
/// Decrypt the sender ciphertext associated with the record with the record view key.
278+
///
279+
/// @param {Field} record_view_key Record view key associated with the record.
280+
/// @param {Field} sender_ciphertext Sender ciphertext associated with the record.
281+
///
282+
/// @return {Address} the address of the sender.
283+
#[wasm_bindgen(js_name = decryptSenderWithRvk)]
284+
pub fn decrypt_sender_with_rvk(record_view_key: &Field, sender_ciphertext: &Field) -> Result<Address, String> {
285+
let encryption_domain = <CurrentNetwork as Network>::encryption_domain();
286+
let record_view_key = FieldNative::from(record_view_key);
287+
let randomizer =
288+
<CurrentNetwork as Network>::hash_psd4(&[encryption_domain, record_view_key, FieldNative::one()])
289+
.map_err(|e| e.to_string())?;
290+
let address_x_coordinate = FieldNative::from(sender_ciphertext) - randomizer;
291+
Ok(Address::from(AddressNative::new(
292+
GroupNative::from_field(&address_x_coordinate).map_err(|e| e.to_string())?,
293+
)))
294+
}
256295
}
257296

258297
#[cfg(test)]
259298
mod tests {
260299
use super::*;
261300

301+
use crate::{
302+
test::{
303+
CREDITS_RECORD_V1,
304+
CREDITS_RECORD_VIEW_KEY,
305+
CREDITS_SENDER_CIPHERTEXT,
306+
CREDITS_SENDER_PLAINTEXT,
307+
get_env,
308+
},
309+
types::native::{PrivateKeyNative, ViewKeyNative},
310+
};
262311
use std::str::FromStr;
263312
use wasm_bindgen_test::wasm_bindgen_test;
264313

@@ -366,4 +415,25 @@ mod tests {
366415
assert_eq!(owned_records.len(), 1, "Only one owned record should be returned");
367416
assert_eq!(owned_records[0].to_string(), OWNER_CIPHERTEXT, "Owned record should match the owner's ciphertext");
368417
}
418+
419+
#[wasm_bindgen_test]
420+
fn test_encryption_toolkit_decrypt_record_sender_ciphertext() {
421+
// Get the private key corresponding to the record.
422+
let private_key = PrivateKeyNative::from_str(&get_env("PUZZLE_PK")).unwrap();
423+
let view_key = ViewKey::from(ViewKeyNative::try_from(private_key).unwrap());
424+
425+
// Construct the record and the sender ciphertext.
426+
let record = RecordPlaintext::from_string(CREDITS_RECORD_V1).unwrap();
427+
let record_view_key = Field::from_string(CREDITS_RECORD_VIEW_KEY).unwrap();
428+
let sender_ciphertext = Field::from_string(CREDITS_SENDER_CIPHERTEXT).unwrap();
429+
430+
// Decrypt the sender ciphertext using the view and record and ensure it's from the expected address.
431+
let credits_sender = CREDITS_SENDER_PLAINTEXT.to_string();
432+
let sender = EncryptionToolkit::decrypt_sender(&view_key, &record, &sender_ciphertext).unwrap();
433+
assert_eq!(sender.to_string(), credits_sender);
434+
435+
// Decrypt the sender ciphertext using only the record view key and ensure it's from the expected address.
436+
let sender = EncryptionToolkit::decrypt_sender_with_rvk(&record_view_key, &sender_ciphertext).unwrap();
437+
assert_eq!(sender.to_string(), credits_sender);
438+
}
369439
}

wasm/src/utilities/test/programs.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,20 @@ use crate::{array, object};
1818

1919
use js_sys::{Array, Object};
2020

21+
/// V1 credits.aleo record.
22+
pub const CREDITS_RECORD_V1: &str = "{ owner: aleo12a4wll9ax6w5355jph0dr5wt2vla5sss2t4cnch0tc3vzh643v8qcfvc7a.private, microcredits: 1000000u64.private, _nonce: 3634848344765318974603121890869676775499130077229666060613233255327643175219group.public, _version: 1u8.public }";
23+
24+
/// Record view key for the V1 credits.aleo record.
25+
pub const CREDITS_RECORD_VIEW_KEY: &str =
26+
"5237002936265850807349726649400053591020997883662246784632368923777787639801field";
27+
28+
/// Sender ciphertext of the credits.aleo record.
29+
pub const CREDITS_SENDER_CIPHERTEXT: &str =
30+
"1182590395568997043375432557467567048762179115999922880321493200728848194550field";
31+
32+
/// Sender plaintext of the credits.aleo record.
33+
pub const CREDITS_SENDER_PLAINTEXT: &str = "aleo1j92w9mhqznj2hvufad796y8suykjppk7f6n6xmncmktfm95vggzqx4sjlh";
34+
2135
pub const HELLO_PROGRAM: &str = r#"program hello.aleo;
2236
function main:
2337
input r0 as u32.public;

0 commit comments

Comments
 (0)