Skip to content

Commit e369b2a

Browse files
Merge pull request #1050 from ProvableHQ/rr/add-tests-and-methods-for-tvk-and-rvk-decryption
Add methods and tests for tvk and rvk decryption in SDK
2 parents 250b119 + bb0a34a commit e369b2a

File tree

8 files changed

+10285
-14774
lines changed

8 files changed

+10285
-14774
lines changed

docs/api_reference/sdk-src_account.md

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,61 @@ const decryptedRecords = account.decryptRecords(records);
285285

286286
---
287287

288+
### `generateRecordViewKey(recordCiphertext) ► Field`
289+
290+
![modifier: public](images/badges/modifier-public.svg)
291+
292+
Generates a record view key from the account owner's view key and the record ciphertext.
293+
This key can be used to decrypt the record without revealing the account's view key.
294+
295+
Parameters | Type | Description
296+
--- | --- | ---
297+
__recordCiphertext__ | [RecordCiphertext](sdk-src_wasm.md) | *The record ciphertext to generate the view key for*
298+
__*return*__ | [Field](sdk-src_wasm.md) | *The record view key*
299+
300+
#### Examples
301+
302+
```javascript
303+
// Import the Account class
304+
import { Account } from "@provablehq/sdk/testnet.js";
305+
306+
// Create an account object from a previously encrypted ciphertext and password.
307+
const account = Account.fromCiphertext(process.env.ciphertext!, process.env.password!);
308+
309+
// Generate a record view key from the account's view key and a record ciphertext
310+
const recordCiphertext = RecordCiphertext.fromString("your_record_ciphertext_here");
311+
const recordViewKey = account.generateRecordViewKey(recordCiphertext);
312+
```
313+
314+
---
315+
316+
### `generateTransitionViewKey(tpk) ► Field`
317+
318+
![modifier: public](images/badges/modifier-public.svg)
319+
320+
Generates a transition view key from the account owner's view key and the transition public key.
321+
This key can be used to decrypt the private inputs and outputs of a the transition without
322+
revealing the account's view key.
323+
324+
Parameters | Type | Description
325+
--- | --- | ---
326+
__tpk__ | `string` | *The transition public key*
327+
__*return*__ | [Field](sdk-src_wasm.md) | *The transition view key*
328+
329+
#### Examples
330+
331+
```javascript
332+
// Import the Account class
333+
import { Account } from "@provablehq/sdk/testnet.js";
334+
335+
// Generate a transition view key from the account's view key and a transition public key
336+
const tpk = Group.fromString("your_transition_public_key_here");
337+
338+
const transitionViewKey = account.generateTransitionViewKey(tpk);
339+
```
340+
341+
---
342+
288343
### `ownsRecordCiphertext(ciphertext) ► boolean`
289344

290345
![modifier: public](images/badges/modifier-public.svg)
@@ -449,23 +504,21 @@ const viewKey = account.viewKey();
449504

450505
---
451506

452-
### `computeKey() ► ComputeKey`
507+
### `computeKey() ► Field`
453508

454509
![modifier: public](images/badges/modifier-public.svg)
455510

456-
Returns the ComputeKey associated with the account.
511+
Returns the Transition View Key associated with the transition public key.
457512

458513
Parameters | Type | Description
459514
--- | --- | ---
460-
__*return*__ | `ComputeKey` | *The compute key of the account*
515+
__*return*__ | [Field](sdk-src_wasm.md) | *The transition view key*
461516

462517
#### Examples
463518

464519
```javascript
465520
import { Account } from "@provablehq/sdk/testnet.js";
466-
467-
const account = new Account();
468-
const computeKey = account.computeKey();
521+
const account =
469522
```
470523

471524
---
@@ -614,6 +667,61 @@ const decryptedRecords = account.decryptRecords(records);
614667

615668
---
616669

670+
### `generateRecordViewKey(recordCiphertext) ► Field`
671+
672+
![modifier: public](images/badges/modifier-public.svg)
673+
674+
Generates a record view key from the account owner's view key and the record ciphertext.
675+
This key can be used to decrypt the record without revealing the account's view key.
676+
677+
Parameters | Type | Description
678+
--- | --- | ---
679+
__recordCiphertext__ | [RecordCiphertext](sdk-src_wasm.md) | *The record ciphertext to generate the view key for*
680+
__*return*__ | [Field](sdk-src_wasm.md) | *The record view key*
681+
682+
#### Examples
683+
684+
```javascript
685+
// Import the Account class
686+
import { Account } from "@provablehq/sdk/testnet.js";
687+
688+
// Create an account object from a previously encrypted ciphertext and password.
689+
const account = Account.fromCiphertext(process.env.ciphertext!, process.env.password!);
690+
691+
// Generate a record view key from the account's view key and a record ciphertext
692+
const recordCiphertext = RecordCiphertext.fromString("your_record_ciphertext_here");
693+
const recordViewKey = account.generateRecordViewKey(recordCiphertext);
694+
```
695+
696+
---
697+
698+
### `generateTransitionViewKey(tpk) ► Field`
699+
700+
![modifier: public](images/badges/modifier-public.svg)
701+
702+
Generates a transition view key from the account owner's view key and the transition public key.
703+
This key can be used to decrypt the private inputs and outputs of a the transition without
704+
revealing the account's view key.
705+
706+
Parameters | Type | Description
707+
--- | --- | ---
708+
__tpk__ | `string` | *The transition public key*
709+
__*return*__ | [Field](sdk-src_wasm.md) | *The transition view key*
710+
711+
#### Examples
712+
713+
```javascript
714+
// Import the Account class
715+
import { Account } from "@provablehq/sdk/testnet.js";
716+
717+
// Generate a transition view key from the account's view key and a transition public key
718+
const tpk = Group.fromString("your_transition_public_key_here");
719+
720+
const transitionViewKey = account.generateTransitionViewKey(tpk);
721+
```
722+
723+
---
724+
617725
### `ownsRecordCiphertext(ciphertext) ► boolean`
618726

619727
![modifier: public](images/badges/modifier-public.svg)

docs/guide/09_private_program_state.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,30 @@ if (RecordCiphertext.is_owner(account.viewKey())) {
217217
console.log(recordPlaintext.toString());
218218
}
219219
```
220+
221+
A record can be decrypted by a non-owner using a record view key for use cases requiring inspection of the data contained within a
222+
record by a third party. This approach enables a user to maintain privacy over the rest of their records and ciphertext data,
223+
ensuring that only the desired record can be decrypted a third party.
224+
225+
```typescript
226+
import {Account, EncryptionToolkit, Field, RecordCiphertext, RecordPlaintext} from '@provablehq/sdk';
227+
228+
// Create an account from an existing private key
229+
const account = Account.from_string({privateKey: "existingPrivateKey"});
230+
231+
// Record value received as a string from program output or found on the Aleo network
232+
const record = "record1qyqsq4r7mcd3ystjvjqda0v2a6dxnyzg9mk2daqjh0wwh359h396k7c9qyxx66trwfhkxun9v35hguerqqpqzqzshsw8dphxlzn5frh8pknsm5zlvhhee79xnhfesu68nkw75dt2qgrye03xqm4zf5xg5n6nscmmzh7ztgptlrzxq95syrzeaqaqu3vpzqf03s6";
233+
234+
const recordCiphertext = RecordCiphertext.fromString(record);
235+
236+
// The owner generates the record view key for a specific record
237+
const recordViewKey = recordCiphertext.recordViewKey(account.viewKey());
238+
239+
// Alternatively, the record view key can be generarted using a method from the EncryptionToolkit object
240+
const recordViewKeyAlt = EncryptionToolkit::gnerateRecordViewKey(accoutn.viewKey(), recordCiphertext);
241+
242+
// A third party can now decrypt the record using the record view key
243+
const recordPlaintext = recordciphertext.decryptWithRecordViewKey(recordViewKey);
244+
245+
// Alternatively, the record can be decrypted using a method from the EncryptionToolkit object
246+
const recordPlaintextAlt = EncryptionToolkit::decryptRecordWithRVk(recordViewKeyAlt, reocrdCiphertext);

sdk/src/account.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import {
22
Address,
33
ComputeKey,
4+
EncryptionToolkit,
5+
Field,
6+
Group,
47
PrivateKey,
8+
Transition,
59
Signature,
610
ViewKey,
711
PrivateKeyCiphertext,
@@ -250,6 +254,56 @@ export class Account {
250254
return ciphertexts.map((ciphertext) => this._viewKey.decrypt(ciphertext));
251255
}
252256

257+
/**
258+
* Generates a record view key from the account owner's view key and the record ciphertext.
259+
* This key can be used to decrypt the record without revealing the account's view key.
260+
* @param {RecordCiphertext | string} recordCiphertext The record ciphertext to generate the view key for
261+
* @returns {Field} The record view key
262+
*
263+
* @example
264+
* // Import the Account class
265+
* import { Account } from "@provablehq/sdk/testnet.js";
266+
*
267+
* // Create an account object from a previously encrypted ciphertext and password.
268+
* const account = Account.fromCiphertext(process.env.ciphertext!, process.env.password!);
269+
*
270+
* // Generate a record view key from the account's view key and a record ciphertext
271+
* const recordCiphertext = RecordCiphertext.fromString("your_record_ciphertext_here");
272+
* const recordViewKey = account.generateRecordViewKey(recordCiphertext);
273+
*/
274+
generateRecordViewKey(recordCiphertext: RecordCiphertext | string): Field {
275+
if (typeof recordCiphertext === 'string') {
276+
recordCiphertext = RecordCiphertext.fromString(recordCiphertext);
277+
}
278+
if (!(recordCiphertext.isOwner(this._viewKey))) {
279+
throw new Error("The record ciphertext does not belong to this account");
280+
}
281+
return EncryptionToolkit.generateRecordViewKey(this._viewKey, recordCiphertext);
282+
}
283+
284+
/**
285+
* Generates a transition view key from the account owner's view key and the transition public key.
286+
* This key can be used to decrypt the private inputs and outputs of a the transition without
287+
* revealing the account's view key.
288+
* @param {string | Group} tpk The transition public key
289+
* @returns {Field} The transition view key
290+
*
291+
* @example
292+
* // Import the Account class
293+
* import { Account } from "@provablehq/sdk/testnet.js";
294+
*
295+
* // Generate a transition view key from the account's view key and a transition public key
296+
* const tpk = Group.fromString("your_transition_public_key_here");
297+
*
298+
* const transitionViewKey = account.generateTransitionViewKey(tpk);
299+
*/
300+
generateTransitionViewKey(tpk: string | Group): Field {
301+
if (typeof tpk === 'string') {
302+
tpk = Group.fromString(tpk);
303+
}
304+
return EncryptionToolkit.generateTvk(this._viewKey, tpk);
305+
}
306+
253307
/**
254308
* Determines whether the account owns a ciphertext record.
255309
* @param {RecordCiphertext | string} ciphertext The record ciphertext to check ownership of

sdk/src/browser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export {
6969
Execution as FunctionExecution,
7070
ExecutionRequest,
7171
ExecutionResponse,
72+
EncryptionToolkit,
7273
Field,
7374
Group,
7475
OfflineQuery,

sdk/src/wasm.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export {
77
BHP1024,
88
Ciphertext,
99
ComputeKey,
10+
EncryptionToolkit,
1011
ExecutionRequest,
1112
Execution,
1213
ExecutionResponse,

sdk/tests/wasm.test.ts

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from "chai";
2-
import { Address, PrivateKey, ViewKey, Signature, RecordCiphertext, RecordPlaintext, PrivateKeyCiphertext } from "../src/node.js";
2+
import { Address, Field, PrivateKey, ViewKey, Signature, RecordCiphertext, RecordPlaintext, PrivateKeyCiphertext, EncryptionToolkit, Transition } from "../src/node.js";
33
import {
44
seed,
55
message,
@@ -351,6 +351,22 @@ describe('WASM Objects', () => {
351351
// Ensure the record ciphertext cannot be decrypted with a foreign view key
352352
expect(() => ciphertext.decrypt(foreignViewKey)).throw();
353353
});
354+
355+
it('can be decrypted with a valid record view key', () => {
356+
const recordViewKey = ciphertext.recordViewKey(viewKey);
357+
const plaintext = ciphertext.decryptWithRecordViewKey(recordViewKey);
358+
const isOwner = ciphertext.isOwner(viewKey);
359+
360+
// Ensure the record ciphertext is decrypted correctly
361+
expect(plaintext.toString()).equal(recordPlaintextString);
362+
expect(isOwner).equal(true);
363+
})
364+
365+
it('cannot be decrypted with an invalid record view key', () => {
366+
const badRecordViewKey = ciphertext.recordViewKey(ViewKey.from_string(foreignViewKeyString));
367+
// Ensure the record ciphertext cannot be decrypted with an invalid record view key
368+
expect(() => ciphertext.decryptWithRecordViewKey(badRecordViewKey)).throw();
369+
})
354370
});
355371

356372
describe('RecordPlaintext', () => {
@@ -364,5 +380,63 @@ describe('WASM Objects', () => {
364380
});
365381
});
366382

383+
describe('Transition', () => {
384+
const transitionString = `{"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"}`;
385+
const transition = Transition.fromString(transitionString);
386+
const transitionDecryptedString = `{"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"}`
387+
const transitionDecrypted = Transition.fromString(transitionDecryptedString);
388+
const invalidTransitionViewKeyString = "5089075468761042335883809641276568724119791331127957254389204093712358605127field"
389+
const invalidTransitionViewKey = Field.fromString(invalidTransitionViewKeyString);
390+
const privateKey = PrivateKey.from_string("APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH");
391+
const viewKey = privateKey.to_view_key();
392+
const tvk = transition.tvk(viewKey);
393+
394+
it('can be decrypted with a valid transition view key', () => {
395+
const transitionDecryptedWithTVK = transition.decryptTransition(tvk);
396+
// Ensure the transition is valid
397+
expect(transitionDecryptedWithTVK.toString()).equal(transitionDecrypted.toString());
398+
});
399+
400+
it('cannot be decrypted with an invalid transition view key', () => {
401+
expect(() => transition.decryptTransition(invalidTransitionViewKey).toThrow());
402+
});
367403

368-
});
404+
it('can generate a transition view key from a valid view key', () => {
405+
const generatedTransitionViewKey = transition.tvk(viewKey);
406+
407+
const generatedTransitionViewKeyFromEncryptionToolkit = EncryptionToolkit.generateTvk(viewKey, transition.tpk());
408+
// Ensure the generated transition view key is the same as the one used to decrypt
409+
expect(generatedTransitionViewKey.toString()).equal(generatedTransitionViewKeyFromEncryptionToolkit.toString());
410+
});
411+
});
412+
413+
describe('EncryptionToolkit', () => {
414+
const recordCiphertextString = "record1qyqsqpe2szk2wwwq56akkwx586hkndl3r8vzdwve32lm7elvphh37rsyqyxx66trwfhkxun9v35hguerqqpqzqrtjzeu6vah9x2me2exkgege824sd8x2379scspmrmtvczs0d93qttl7y92ga0k0rsexu409hu3vlehe3yxjhmey3frh2z5pxm5cmxsv4un97q";
415+
const recordCiphertext = RecordCiphertext.fromString(recordCiphertextString);
416+
const recordPlaintextString = `{
417+
owner: aleo1j7qxyunfldj2lp8hsvy7mw5k8zaqgjfyr72x2gh3x4ewgae8v5gscf5jh3.private,
418+
microcredits: 1500000000000000u64.private,
419+
_nonce: 3077450429259593211617823051143573281856129402760267155982965992208217472983group.public
420+
}`;
421+
const recordPlaintext = RecordPlaintext.fromString(recordPlaintextString);
422+
const viewKeyString = "AViewKey1ccEt8A2Ryva5rxnKcAbn7wgTaTsb79tzkKHFpeKsm9NX";
423+
const viewKey = ViewKey.from_string(viewKeyString);
424+
const recordViewKeyString = "4445718830394614891114647247073357094867447866913203502139893824059966201724field";
425+
const recordViewKey = Field.fromString(recordViewKeyString);
426+
427+
it('can generate a record view key from a view key and a record ciphertext', () => {
428+
const generatedRecordViewKey = EncryptionToolkit.generateRecordViewKey(viewKey, recordCiphertext);
429+
// Ensure the generated record view key is the same as the one used to decrypt
430+
expect(generatedRecordViewKey.toString()).equal(recordViewKey.toString());
431+
});
432+
it('can decrypt a record ciphertext with the record view key', () => {
433+
const decryptedRecord = EncryptionToolkit.decryptRecordWithRVk(recordViewKey, recordCiphertext);
434+
// Ensure the decrypted record is the same as the plaintext
435+
expect(decryptedRecord.toString()).equal(recordPlaintext.toString());
436+
});
437+
it('cannot decrypt a record ciphertext with an invalid record view key', () => {
438+
const invalidRecordViewKey = Field.fromString("4445718830394614891114647247073357114867447866913203502139893824059966201724field");
439+
expect(() => EncryptionToolkit.decryptRecordWithRVk(invalidRecordViewKey, recordCiphertext)).to.throw();
440+
});
441+
});
442+
});

wasm/src/utilities/encrypt.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ impl EncryptionToolkit {
151151

152152
/// Creates a record view key from the view key. This can be later be used to decrypt a
153153
// record without revealing an account's view key.
154-
#[wasm_bindgen(js_name = "generateRecordViewkey")]
154+
#[wasm_bindgen(js_name = "generateRecordViewKey")]
155155
pub fn generate_record_view_key(view_key: &ViewKey, record_ciphertext: &RecordCiphertext) -> Result<Field, String> {
156156
let record_nonce = record_ciphertext.nonce();
157157
Ok(record_nonce.scalar_multiply(&view_key.to_scalar()).to_x_coordinate())

0 commit comments

Comments
 (0)