Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions src/encryption.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {
decrypt,
decryptSafely,
decryptWithSharedSecret,
encrypt,
encryptSafely,
EthEncryptedData,
getEncryptionPublicKey,
} from './encryption';

Expand Down Expand Up @@ -73,6 +75,86 @@ describe('encryption', function () {
expect(plaintext).toBe(secretMessage);
});

// shared secret decryption test
it('bob decrypts message that Alice sent to him with his shared secret', function () {
const encryptedDataWithHW: EthEncryptedData = {
version: 'x25519-xsalsa20-poly1305',
nonce: 'QjBaLWLlYeIDUcMjqUpDkIxoaBIck/lh',
ephemPublicKey: '/uwH3xIXDBG8ARritky4dSh9DXFNo1Jw2lSgq+Prdx0=',
ciphertext: 'pT7dEopOHWZgFQZ0cK2ia/9Ewz03xq6db/vU8glwg+deI4WiyP2lTY0s',
};
const sharedSecret = 'Pplxc07fb6IiXAmtDJ9ebL4KRGXF9qeic0ZmktOdHCk=';

const result = decryptWithSharedSecret({
encryptedData: encryptedDataWithHW,
sharedSecret,
});
expect(result).toBe(secretMessage);
});

it('bob decrypts invalid message that Alice sent to him with his shared secret', function () {
const encryptedDataWithHW: EthEncryptedData = {
version: 'x25519-xsalsa20-poly1305',
nonce: 'QjBaLWLlYeIDUcMjqUpDkIxoaBIck/lh',
ephemPublicKey: '/uwH3xIXDBG8ARritky4dSh9DXFNo1Jw2lSgq+Prdx0=',
ciphertext: 'pT7dEopOHWZgFQZ0cK2ia/9Ewz03xq6db/vU8glgg+deI4WiyP2lTY0s',
};
const sharedSecret = 'Pplxc07fb6IiXAmtDJ9ebL4KRGXF9qeic0ZmktOdHCk=';

expect(() =>
decryptWithSharedSecret({
encryptedData: encryptedDataWithHW,
sharedSecret,
}),
).toThrow('Decryption failed.');
});

it('bob decrypts unknown version message that Alice sent to him with his shared secret', function () {
const encryptedDataWithHW: EthEncryptedData = {
version: 'x25519-xsalsa21-poly1305',
nonce: 'QjBaLWLlYeIDUcMjqUpDkIxoaBIck/lh',
ephemPublicKey: '/uwH3xIXDBG8ARritky4dSh9DXFNo1Jw2lSgq+Prdx0=',
ciphertext: 'pT7dEopOHWZgFQZ0cK2ia/9Ewz03xq6db/vU8glgg+deI4WiyP2lTY0s',
};
const sharedSecret = 'Pplxc07fb6IiXAmtDJ9ebL4KRGXF9qeic0ZmktOdHCk=';

expect(() =>
decryptWithSharedSecret({
encryptedData: encryptedDataWithHW,
sharedSecret,
}),
).toThrow('Encryption type/version not supported.');
});

it('bob decrypts null encrypted that Alice sent to him with his shared secret', function () {
const encryptedDataWithHW: EthEncryptedData = null;
const sharedSecret = 'Pplxc07fb6IiXAmtDJ9ebL4KRGXF9qeic0ZmktOdHCk=';

expect(() =>
decryptWithSharedSecret({
encryptedData: encryptedDataWithHW,
sharedSecret,
}),
).toThrow('Missing encryptedData parameter');
});

it('bob decrypts message that Alice sent to him with null shared secret', function () {
const encryptedDataWithHW: EthEncryptedData = {
version: 'x25519-xsalsa21-poly1305',
nonce: 'QjBaLWLlYeIDUcMjqUpDkIxoaBIck/lh',
ephemPublicKey: '/uwH3xIXDBG8ARritky4dSh9DXFNo1Jw2lSgq+Prdx0=',
ciphertext: 'pT7dEopOHWZgFQZ0cK2ia/9Ewz03xq6db/vU8glgg+deI4WiyP2lTY0s',
};
const sharedSecret = null;

expect(() =>
decryptWithSharedSecret({
encryptedData: encryptedDataWithHW,
sharedSecret,
}),
).toThrow('Missing sharedSecret parameter');
});

// decryption test
it('bob decrypts message that Alice sent to him', function () {
const result = decrypt({
Expand Down
72 changes: 72 additions & 0 deletions src/encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,78 @@ export function decrypt({
}
}

/**
* Decrypt a message using a shared secret rather than a private key.
*
* @param options - The decryption options.
* @param options.encryptedData - The encrypted data.
* @param options.sharedSecret - The shared secret to decrypt with.
* @returns The decrypted message.
*/
export function decryptWithSharedSecret({
encryptedData,
sharedSecret,
}: {
encryptedData: EthEncryptedData;
sharedSecret: string;
}): string {
if (isNullish(encryptedData)) {
throw new Error('Missing encryptedData parameter');
} else if (isNullish(sharedSecret)) {
throw new Error('Missing sharedSecret parameter');
}

switch (encryptedData.version) {
case 'x25519-xsalsa20-poly1305': {
// assemble decryption parameters
const nonce = naclUtil.decodeBase64(encryptedData.nonce);
const ciphertext = naclUtil.decodeBase64(encryptedData.ciphertext);
const secret = naclUtil.decodeBase64(sharedSecret);

// Hsalsa20 constant
const sigma = new Uint8Array([
101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107,
]);
const _0 = new Uint8Array(16);

// Hsalsa20 result
const privateKey = new Uint8Array(32);

// Calculate private key
// We use any conversion here because lowlevel function of nacl are not typed
(nacl as any).lowlevel.crypto_core_hsalsa20(
privateKey,
_0,
secret,
sigma,
);

// decrypt
const decryptedMessage = nacl.secretbox.open(
ciphertext,
nonce,
privateKey,
);

// return decrypted msg data
let output;
try {
output = naclUtil.encodeUTF8(decryptedMessage);
} catch (err) {
throw new Error('Decryption failed.');
}

if (output) {
return output;
}
throw new Error('Decryption failed.');
}

default:
throw new Error('Encryption type/version not supported.');
}
}

/**
* Decrypt a message that has been encrypted using `encryptSafely`.
*
Expand Down
1 change: 1 addition & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Array [
"encrypt",
"encryptSafely",
"decrypt",
"decryptWithSharedSecret",
"decryptSafely",
"getEncryptionPublicKey",
]
Expand Down