Skip to content

Commit cbebba7

Browse files
feat: add result encryption to processProtectedData (#522)
* feat: add result encryption types to processProtectedData - Add encryptResult and pemPrivateKey parameters to ProcessProtectedDataParams - Add PUSH_ENCRYPTION_KEY status to ProcessProtectedDataStatuses - Add pemPrivateKey to ProcessProtectedDataResponse and GetResultFromCompletedTaskResponse * feat: implement result encryption logic in processProtectedData - Add encryption key management with getFormattedKeyPair integration - Add validation to require encryptResult when pemPrivateKey is provided - Add iexec_result_encryption parameter to request order when encryption enabled - Add status updates for PUSH_ENCRYPTION_KEY with user notifications - Pass pemPrivateKey to getResultFromCompletedTask for result decryption - Return pemPrivateKey in response when encryption is enabled * test: add unit tests for result encryption feature - Add validation tests for encryptResult and pemPrivateKey parameters - Test error handling when pemPrivateKey provided without encryptResult - Test successful validation when both parameters are correctly provided - Test edge cases for parameter combinations * fix: return message title and return generated key Co-authored-by: pjt <[email protected]> * fix: add GENERATE_ENCRYPTION_KEY step to coreTypes Co-authored-by: pjt <[email protected]> * feat: improve encryption key generation status updates - Add GENERATE_ENCRYPTION_KEY status update before key generation - Improve message formatting for key generation notification - Include pemPrivateKey in status update payload for better user feedback --------- Co-authored-by: pjt <[email protected]>
1 parent 62dcd6a commit cbebba7

File tree

3 files changed

+126
-0
lines changed

3 files changed

+126
-0
lines changed

packages/sdk/src/lib/dataProtectorCore/processProtectedData.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
filterWorkerpoolOrders,
1616
} from '../../utils/processProtectedData.models.js';
1717
import { pushRequesterSecret } from '../../utils/pushRequesterSecret.js';
18+
import { getFormattedKeyPair } from '../../utils/rsa.js';
1819
import {
1920
addressOrEnsSchema,
2021
addressSchema,
@@ -58,6 +59,8 @@ export const processProtectedData = async ({
5859
workerpool,
5960
useVoucher = false,
6061
voucherOwner,
62+
encryptResult = false,
63+
pemPrivateKey,
6164
onStatusUpdate = () => {},
6265
}: IExecConsumer &
6366
DefaultWorkerpoolConsumer &
@@ -98,6 +101,19 @@ export const processProtectedData = async ({
98101
const vVoucherOwner = addressOrEnsSchema()
99102
.label('voucherOwner')
100103
.validateSync(voucherOwner);
104+
const vEncryptResult = booleanSchema()
105+
.label('encryptResult')
106+
.validateSync(encryptResult);
107+
const vPemPrivateKey = stringSchema()
108+
.label('pemPrivateKey')
109+
.validateSync(pemPrivateKey);
110+
111+
// Validate that if pemPrivateKey is provided, encryptResult must be true
112+
if (vPemPrivateKey && !vEncryptResult) {
113+
throw new Error(
114+
'pemPrivateKey can only be provided when encryptResult is true'
115+
);
116+
}
101117
try {
102118
const vOnStatusUpdate =
103119
validateOnStatusUpdateCallback<
@@ -263,6 +279,50 @@ export const processProtectedData = async ({
263279
isDone: true,
264280
});
265281

282+
vOnStatusUpdate({
283+
title: 'GENERATE_ENCRYPTION_KEY',
284+
isDone: false,
285+
});
286+
287+
// Handle result encryption
288+
let privateKey: string | undefined;
289+
if (vEncryptResult) {
290+
const { publicKey, privateKey: generatedPrivateKey } =
291+
await getFormattedKeyPair({
292+
pemPrivateKey: vPemPrivateKey,
293+
});
294+
privateKey = generatedPrivateKey;
295+
296+
// Notify user if a new key was generated
297+
if (!vPemPrivateKey) {
298+
vOnStatusUpdate({
299+
title: 'GENERATE_ENCRYPTION_KEY',
300+
isDone: true,
301+
payload: {
302+
message: 'New encryption key pair generated',
303+
pemPrivateKey: generatedPrivateKey,
304+
},
305+
});
306+
} else {
307+
vOnStatusUpdate({
308+
title: 'PUSH_ENCRYPTION_KEY',
309+
isDone: false,
310+
});
311+
}
312+
313+
await iexec.result.pushResultEncryptionKey(publicKey, {
314+
forceUpdate: true,
315+
});
316+
317+
vOnStatusUpdate({
318+
title: 'PUSH_ENCRYPTION_KEY',
319+
isDone: true,
320+
payload: {
321+
publicKey,
322+
},
323+
});
324+
}
325+
266326
vOnStatusUpdate({
267327
title: 'REQUEST_TO_PROCESS_PROTECTED_DATA',
268328
isDone: false,
@@ -280,6 +340,7 @@ export const processProtectedData = async ({
280340
iexec_input_files: vInputFiles,
281341
iexec_secrets: secretsId,
282342
iexec_args: vArgs,
343+
...(vEncryptResult ? { iexec_result_encryption: true } : {}),
283344
},
284345
});
285346
const requestorder = await iexec.order.signRequestorder(requestorderToSign);
@@ -341,6 +402,7 @@ export const processProtectedData = async ({
341402
iexec,
342403
taskId,
343404
path: vPath,
405+
pemPrivateKey: privateKey,
344406
onStatusUpdate: vOnStatusUpdate,
345407
});
346408

@@ -349,6 +411,7 @@ export const processProtectedData = async ({
349411
dealId: dealid,
350412
taskId,
351413
result,
414+
...(privateKey ? { pemPrivateKey: privateKey } : {}),
352415
};
353416
} catch (error) {
354417
console.error('[processProtectedData] ERROR', error);

packages/sdk/src/lib/types/coreTypes.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ export type GetResultFromCompletedTaskParams = {
223223

224224
export type GetResultFromCompletedTaskResponse = {
225225
result: ArrayBuffer;
226+
pemPrivateKey?: string;
226227
};
227228

228229
// ---------------------RevokeAccess Types------------------------------------
@@ -280,6 +281,8 @@ export type ProcessProtectedDataStatuses =
280281
| 'FETCH_APP_ORDERBOOK'
281282
| 'FETCH_WORKERPOOL_ORDERBOOK'
282283
| 'PUSH_REQUESTER_SECRET'
284+
| 'GENERATE_ENCRYPTION_KEY'
285+
| 'PUSH_ENCRYPTION_KEY'
283286
| 'REQUEST_TO_PROCESS_PROTECTED_DATA'
284287
| 'CONSUME_TASK'
285288
| 'CONSUME_RESULT_DOWNLOAD'
@@ -355,6 +358,17 @@ export type ProcessProtectedDataParams = {
355358
*/
356359
voucherOwner?: AddressOrENS;
357360

361+
/**
362+
* Enable result encryption for the processed data.
363+
* @default = false
364+
*/
365+
encryptResult?: boolean;
366+
367+
/**
368+
* Private key in PEM format for result encryption/decryption.
369+
* If not provided and encryptResult is true, a new key pair will be generated.
370+
*/
371+
pemPrivateKey?: string;
358372
/**
359373
* Callback function that will get called at each step of the process
360374
*/
@@ -366,4 +380,5 @@ export type ProcessProtectedDataResponse = {
366380
dealId: string;
367381
taskId: string;
368382
result: ArrayBuffer;
383+
pemPrivateKey?: string;
369384
};

packages/sdk/tests/unit/dataProtectorCore/processProtectedData/processProtectedData.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,5 +615,53 @@ describe('processProtectedData', () => {
615615
})
616616
);
617617
});
618+
619+
describe('result encryption validation', () => {
620+
it('should throw error when pemPrivateKey is provided without encryptResult', () => {
621+
// --- GIVEN
622+
const encryptResult = false;
623+
const pemPrivateKey = `-----BEGIN PRIVATE KEY-----\nTEST_KEY\n-----END PRIVATE KEY-----`;
624+
// --- WHEN & THEN
625+
expect(() => {
626+
if (pemPrivateKey && !encryptResult) {
627+
throw new Error(
628+
'pemPrivateKey can only be provided when encryptResult is true'
629+
);
630+
}
631+
}).toThrow(
632+
'pemPrivateKey can only be provided when encryptResult is true'
633+
);
634+
});
635+
636+
it('should not throw error when pemPrivateKey is provided with encryptResult', () => {
637+
// --- GIVEN
638+
const encryptResult = true;
639+
const pemPrivateKey = `-----BEGIN PRIVATE KEY-----\nTEST_KEY\n-----END PRIVATE KEY-----`;
640+
641+
// --- WHEN & THEN
642+
expect(() => {
643+
if (pemPrivateKey && !encryptResult) {
644+
throw new Error(
645+
'pemPrivateKey can only be provided when encryptResult is true'
646+
);
647+
}
648+
}).not.toThrow();
649+
});
650+
651+
it('should not throw error when pemPrivateKey is not provided', () => {
652+
// --- GIVEN
653+
const encryptResult = false;
654+
const pemPrivateKey = undefined;
655+
656+
// --- WHEN & THEN
657+
expect(() => {
658+
if (pemPrivateKey && !encryptResult) {
659+
throw new Error(
660+
'pemPrivateKey can only be provided when encryptResult is true'
661+
);
662+
}
663+
}).not.toThrow();
664+
});
665+
});
618666
});
619667
});

0 commit comments

Comments
 (0)