Skip to content

Commit 4252ac2

Browse files
committed
feat!: sdk encryption to support binary
1 parent d1bc6d0 commit 4252ac2

File tree

4 files changed

+158
-20
lines changed

4 files changed

+158
-20
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Encryption } from '../src/encryption';
2+
3+
const ExampleKeys = {
4+
private: `-----BEGIN PGP PRIVATE KEY BLOCK-----
5+
6+
xVgEZyOGRhYJKwYBBAHaRw8BAQdA6soD3CBjRnPfsYIxOGObykL8X8y+dZlf
7+
IzAI7mk40E4AAQCPRLKy/1n/QxTRk/Ql+Dt2VYADqDmczBxhWELCbz9Wvw/J
8+
zRxleGFtcGxlIDxzaW1wbGVAZXhhbXBsZS5jb20+wowEEBYKAD4FgmcjhkYE
9+
CwkHCAmQZ4KBmVeekE0DFQgKBBYAAgECGQECmwMCHgEWIQQkzuPtNIUkzz4M
10+
CFhngoGZV56QTQAA89oBAKm5Tai1Ynx4BCU6PSLp0QMEE2ImV/LzwHkLMz52
11+
mzeJAP9NyyNyIMNH1SpSezy309UhEdJVapGoXQwO1eQR4B+LCMddBGcjhkYS
12+
CisGAQQBl1UBBQEBB0BykPL7zxjKQpZMYuEnIDBz7vshngm4zJMNOsE6pDSH
13+
NgMBCAcAAP9b+n3/5QKVd0UP/ow3uyH0X44gc2U4fKV8IhZBJLiSEA9ZwngE
14+
GBYKACoFgmcjhkYJkGeCgZlXnpBNApsMFiEEJM7j7TSFJM8+DAhYZ4KBmVee
15+
kE0AAE0WAP9FE0I9dHToxLAkMKiM9tTzL43GVl6K8Lvn9nrLJcfLgQEAtFXL
16+
39GhAUKNbHMpdeEmxukrdF+rXzUfvOsYGPLIgwI=
17+
=GfVY
18+
-----END PGP PRIVATE KEY BLOCK-----`,
19+
public: `-----BEGIN PGP PUBLIC KEY BLOCK-----
20+
21+
xjMEZyOGRhYJKwYBBAHaRw8BAQdA6soD3CBjRnPfsYIxOGObykL8X8y+dZlf
22+
IzAI7mk40E7NHGV4YW1wbGUgPHNpbXBsZUBleGFtcGxlLmNvbT7CjAQQFgoA
23+
PgWCZyOGRgQLCQcICZBngoGZV56QTQMVCAoEFgACAQIZAQKbAwIeARYhBCTO
24+
4+00hSTPPgwIWGeCgZlXnpBNAADz2gEAqblNqLVifHgEJTo9IunRAwQTYiZX
25+
8vPAeQszPnabN4kA/03LI3Igw0fVKlJ7PLfT1SER0lVqkahdDA7V5BHgH4sI
26+
zjgEZyOGRhIKKwYBBAGXVQEFAQEHQHKQ8vvPGMpClkxi4ScgMHPu+yGeCbjM
27+
kw06wTqkNIc2AwEIB8J4BBgWCgAqBYJnI4ZGCZBngoGZV56QTQKbDBYhBCTO
28+
4+00hSTPPgwIWGeCgZlXnpBNAABNFgD/RRNCPXR06MSwJDCojPbU8y+NxlZe
29+
ivC75/Z6yyXHy4EBALRVy9/RoQFCjWxzKXXhJsbpK3Rfq181H7zrGBjyyIMC
30+
=wH1v
31+
-----END PGP PUBLIC KEY BLOCK-----`,
32+
};
33+
34+
const exampleRound = async () => {
35+
const encryption = await Encryption.build(ExampleKeys.private);
36+
37+
const message = Buffer.from(
38+
JSON.stringify(
39+
{
40+
date: new Date().toISOString(),
41+
random: Math.random(),
42+
text: 'Hello from example!',
43+
},
44+
null,
45+
2
46+
)
47+
);
48+
49+
const encrypted = await encryption.signAndEncrypt(message, [
50+
ExampleKeys.public,
51+
]);
52+
53+
const decrypted = await encryption.decrypt(encrypted, ExampleKeys.public);
54+
console.log('Decrypted', JSON.parse(Buffer.from(decrypted).toString()));
55+
};
56+
57+
(async () => {
58+
await exampleRound();
59+
})();

packages/sdk/typescript/human-protocol-sdk/src/encryption.ts

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import * as openpgp from 'openpgp';
22
import { IKeyPair } from './interfaces';
33

4+
type MessageDataType = string | Uint8Array;
5+
6+
function makeMessageDataBinary(message: MessageDataType): Uint8Array {
7+
if (typeof message === 'string') {
8+
return Buffer.from(message);
9+
}
10+
11+
return message;
12+
}
13+
414
/**
515
* ## Introduction
616
*
@@ -126,20 +136,21 @@ export class Encryption {
126136
* ```
127137
*/
128138
public async signAndEncrypt(
129-
message: string,
139+
message: MessageDataType,
130140
publicKeys: string[]
131141
): Promise<string> {
132-
const plaintext = message;
133-
134142
const pgpPublicKeys = await Promise.all(
135143
publicKeys.map((armoredKey) => openpgp.readKey({ armoredKey }))
136144
);
137145

138-
const pgpMessage = await openpgp.createMessage({ text: plaintext });
146+
const pgpMessage = await openpgp.createMessage({
147+
binary: makeMessageDataBinary(message),
148+
});
139149
const encrypted = await openpgp.encrypt({
140150
message: pgpMessage,
141151
encryptionKeys: pgpPublicKeys,
142152
signingKeys: this.privateKey,
153+
format: 'armored',
143154
});
144155

145156
return encrypted as string;
@@ -176,23 +187,43 @@ export class Encryption {
176187
* const resultMessage = await encription.decrypt('message');
177188
* ```
178189
*/
179-
public async decrypt(message: string, publicKey?: string): Promise<string> {
180-
const pgpMessage = await openpgp.readMessage({ armoredMessage: message });
190+
public async decrypt(
191+
message: string,
192+
publicKey?: string
193+
): Promise<Uint8Array> {
194+
const pgpMessage = await openpgp.readMessage({
195+
armoredMessage: message,
196+
});
181197

182198
const decryptionOptions: openpgp.DecryptOptions = {
183199
message: pgpMessage,
184200
decryptionKeys: this.privateKey,
185-
expectSigned: !!publicKey,
201+
format: 'binary',
186202
};
187203

188-
if (publicKey) {
204+
const shouldVerifySignature = !!publicKey;
205+
if (shouldVerifySignature) {
189206
const pgpPublicKey = await openpgp.readKey({ armoredKey: publicKey });
190207
decryptionOptions.verificationKeys = pgpPublicKey;
191208
}
192209

193-
const { data: decrypted } = await openpgp.decrypt(decryptionOptions);
210+
const { data: decrypted, signatures } =
211+
await openpgp.decrypt(decryptionOptions);
212+
213+
/**
214+
* There is an option to automatically verify signatures - `expectSigned`,
215+
* but atm it has a bug - https://github.com/openpgpjs/openpgpjs/issues/1803,
216+
* so we have to verify it manually till it's fixed.
217+
*/
218+
try {
219+
if (shouldVerifySignature) {
220+
await signatures[0].verified;
221+
}
222+
} catch {
223+
throw new Error('Signature could not be verified');
224+
}
194225

195-
return decrypted as string;
226+
return decrypted as Uint8Array;
196227
}
197228

198229
/**
@@ -419,19 +450,20 @@ export class EncryptionUtils {
419450
* ```
420451
*/
421452
public static async encrypt(
422-
message: string,
453+
message: MessageDataType,
423454
publicKeys: string[]
424455
): Promise<string> {
425-
const plaintext = message;
426-
427456
const pgpPublicKeys = await Promise.all(
428457
publicKeys.map((armoredKey) => openpgp.readKey({ armoredKey }))
429458
);
430459

431-
const pgpMessage = await openpgp.createMessage({ text: plaintext });
460+
const pgpMessage = await openpgp.createMessage({
461+
binary: makeMessageDataBinary(message),
462+
});
432463
const encrypted = await openpgp.encrypt({
433464
message: pgpMessage,
434465
encryptionKeys: pgpPublicKeys,
466+
format: 'armored',
435467
});
436468

437469
return encrypted as string;

packages/sdk/typescript/human-protocol-sdk/test/encryption.test.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
import { describe, test, expect, beforeAll } from 'vitest';
33
import { Encryption, EncryptionUtils } from '../src/encryption';
44
import {
5+
BINARY_MESSAGE_CONTENT,
56
ENCRYPTEDMESSAGE,
67
KEYPAIR1,
78
KEYPAIR2,
89
KEYPAIR3,
910
MESSAGE,
11+
SIGNEDENCRYPTEDBINARYMESSAGE,
1012
SIGNEDENCRYPTEDLOCKEDMESSAGE,
1113
SIGNEDENCRYPTEDMESSAGE,
1214
SIGNEDMESSAGE,
@@ -249,6 +251,20 @@ describe('Encryption', async () => {
249251
expect(encryptedMessage.includes(expectedMessageFooter)).toBeTruthy();
250252
});
251253

254+
test('should sign and ecnrypt a binary message', async () => {
255+
const encryption = await Encryption.build(KEYPAIR1.privateKey);
256+
const encryptedMessage = await encryption.signAndEncrypt(
257+
Buffer.from(JSON.stringify(BINARY_MESSAGE_CONTENT)),
258+
[KEYPAIR2.publicKey, KEYPAIR3.publicKey]
259+
);
260+
console.log('en', encryptedMessage);
261+
const expectedMessageHeader = '-----BEGIN PGP MESSAGE-----\n';
262+
const expectedMessageFooter = '-----END PGP MESSAGE-----\n';
263+
264+
expect(encryptedMessage.includes(expectedMessageHeader)).toBeTruthy();
265+
expect(encryptedMessage.includes(expectedMessageFooter)).toBeTruthy();
266+
});
267+
252268
test('should throw an error when an invalid public key is provided', async () => {
253269
const encryption = await Encryption.build(
254270
KEYPAIR1.privateKey,
@@ -285,8 +301,7 @@ describe('Encryption', async () => {
285301
SIGNEDENCRYPTEDMESSAGE,
286302
KEYPAIR1.publicKey
287303
);
288-
289-
expect(decryptedMessage).toBe(MESSAGE);
304+
expect(Buffer.from(decryptedMessage).toString()).toBe(MESSAGE);
290305

291306
const encryption3 = await Encryption.build(
292307
KEYPAIR3.privateKey,
@@ -296,7 +311,18 @@ describe('Encryption', async () => {
296311
SIGNEDENCRYPTEDMESSAGE,
297312
KEYPAIR1.publicKey
298313
);
299-
expect(decryptedMessage2).toBe(MESSAGE);
314+
expect(Buffer.from(decryptedMessage2).toString()).toBe(MESSAGE);
315+
});
316+
317+
test('should decrypt and verify binary message', async () => {
318+
const encryption2 = await Encryption.build(KEYPAIR2.privateKey);
319+
const decryptedMessage = await encryption2.decrypt(
320+
SIGNEDENCRYPTEDBINARYMESSAGE,
321+
KEYPAIR1.publicKey
322+
);
323+
expect(JSON.parse(Buffer.from(decryptedMessage).toString())).toEqual(
324+
BINARY_MESSAGE_CONTENT
325+
);
300326
});
301327

302328
test('should decrypt and verify a message encrypted with a locked private key', async () => {
@@ -306,7 +332,7 @@ describe('Encryption', async () => {
306332
KEYPAIR3.publicKey
307333
);
308334

309-
expect(decryptedMessage).toBe(MESSAGE);
335+
expect(Buffer.from(decryptedMessage).toString()).toBe(MESSAGE);
310336
});
311337

312338
test('should throw an error when no encrypted message is provided', async () => {
@@ -332,7 +358,7 @@ describe('Encryption', async () => {
332358
);
333359
const decryptedMessage = await encryption2.decrypt(ENCRYPTEDMESSAGE);
334360

335-
expect(decryptedMessage).toBe(MESSAGE);
361+
expect(Buffer.from(decryptedMessage).toString()).toBe(MESSAGE);
336362
});
337363

338364
test('should fail when decrypting and verifying an unsigned message', async () => {
@@ -343,7 +369,7 @@ describe('Encryption', async () => {
343369

344370
await expect(
345371
encryption2.decrypt(ENCRYPTEDMESSAGE, KEYPAIR1.publicKey)
346-
).rejects.toThrowError('Error decrypting message: Message is not signed');
372+
).rejects.toThrowError('Signature could not be verified');
347373
});
348374
});
349375

packages/sdk/typescript/human-protocol-sdk/test/utils/constants.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ WXs/I9nhKcx2pAAA5VwA/R6fW20uAR7ItubxVhQa+PLEVSiUKgYOU9jp75av
140140
};
141141

142142
export const MESSAGE = 'Human Protocol';
143+
export const BINARY_MESSAGE_CONTENT = {
144+
number: 1,
145+
boolean: true,
146+
null: null,
147+
string: MESSAGE,
148+
} as const;
143149

144150
export const SIGNEDMESSAGE = `-----BEGIN PGP SIGNED MESSAGE-----
145151
Hash: SHA512
@@ -167,6 +173,21 @@ cii1NVsBkAg4xxsZHrjXKqXzHnnhseNGEW5eNYUZWZJZTZLSMGWV031Kwjm1
167173
=pawu
168174
-----END PGP MESSAGE-----`;
169175

176+
export const SIGNEDENCRYPTEDBINARYMESSAGE = `-----BEGIN PGP MESSAGE-----
177+
178+
wV4D62ta7QqCH7ASAQdA1Fu+j1ROzZfOhWFonXU5nqN8SS7lZ9PcOH5R0EsJ
179+
WwMwVqgc90AQPe6QqHXADnmWRcmlMa60UqZXp6RM117IA61U3eMWZMvZpm8w
180+
WchAe+/YwV4DM57hZqoRiU8SAQdAjBxr7VwdHExxR16YJRfl6jmS/+jVf7Ed
181+
a1M134lkWFQwzGk4dbVx/PVlH3QbwzSuwh4EvdTku/5eA6TTAG8SsPnjeYwe
182+
+bOn6blgHitbCyRg0sA4ARd/h3Cv3SPLv8TLLY1+oZKCxj8JixvRJ7pdQFtX
183+
uQj4/3ebFN3Wz4Bc22og61V0bRwbItkvAkdPytAAohMSdvtmB/afqN5KleH7
184+
Q+hftw7iUpLxGEhykh4lHy9dt/ylzPFYP1MVAm9aTdNMo0YEeCji8EPbbxl4
185+
hdPtck+uT1OpewbrhZNxg5Mtzc+t5tf/TfNYllAO6u/XTTWsOBKGcLhsituV
186+
j7PegOO0npgohXmOxKFMmjJZhJOEbDOwCxTb72lZHwOXP5f95xMuSWoUGFpO
187+
lk9n7jB3p5HYtUDSriV2YSI+G3NWmqGHG4/XTdodK2DQb8/Hp88=
188+
=qQI9
189+
-----END PGP MESSAGE-----`;
190+
170191
export const SIGNEDENCRYPTEDLOCKEDMESSAGE = `-----BEGIN PGP MESSAGE-----
171192
172193
wV4D62ta7QqCH7ASAQdAlbHVdZeqqWEy2toJlJWa/2N+L6zdYJKzyiItdWZz

0 commit comments

Comments
 (0)