diff --git a/.gitignore b/.gitignore index 6078f6b1f..309d1e914 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ yarn-debug.log* yarn-error.log* store.db store.db.compacted +.idea +.vscode diff --git a/packages/util-crypto/src/nacl/box/fromSecret.spec.ts b/packages/util-crypto/src/nacl/box/fromSecret.spec.ts new file mode 100644 index 000000000..376632378 --- /dev/null +++ b/packages/util-crypto/src/nacl/box/fromSecret.spec.ts @@ -0,0 +1,28 @@ +// Copyright 2017-2019 @polkadot/util-crypto authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { naclBoxKeypairFromSecret } from '..'; + +describe('naclBoxKeypairFromSecret', (): void => { + const secretKey = new Uint8Array([ + 18, 52, 86, 120, 144, 18, 52, 86, + 120, 144, 18, 52, 86, 120, 144, 18, + 18, 52, 86, 120, 144, 18, 52, 86, + 120, 144, 18, 52, 86, 120, 144, 18 + ]); + + it('generates a valid publicKey/secretKey pair', (): void => { + expect( + naclBoxKeypairFromSecret(secretKey) + ).toEqual({ + publicKey: new Uint8Array([ + 206, 110, 228, 222, 155, 43, 229, 254, + 38, 117, 157, 116, 224, 87, 139, 171, + 17, 195, 144, 167, 106, 36, 161, 28, + 25, 232, 127, 47, 219, 165, 44, 102 + ]), + secretKey + }); + }); +}); diff --git a/packages/util-crypto/src/nacl/box/fromSecret.ts b/packages/util-crypto/src/nacl/box/fromSecret.ts new file mode 100644 index 000000000..220b2d880 --- /dev/null +++ b/packages/util-crypto/src/nacl/box/fromSecret.ts @@ -0,0 +1,25 @@ +// Copyright 2017-2019 @polkadot/util-crypto authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { Keypair } from '../../types'; + +import nacl from 'tweetnacl'; + +/** + * @name naclBoxKeypairFromSecret + * @summary Creates a new public/secret box keypair from a secret. + * @description + * Returns a object containing a box `publicKey` & `secretKey` generated from the supplied secret. + * @example + *
+ * + * ```javascript + * import { naclBoxKeypairFromSecret } from '@polkadot/util-crypto'; + * + * naclBoxKeypairFromSecret(...); // => { secretKey: [...], publicKey: [...] } + * ``` + */ +export default function naclBoxKeypairFromSecret (secret: Uint8Array): Keypair { + return nacl.box.keyPair.fromSecretKey(secret.slice(0, 32)); +} diff --git a/packages/util-crypto/src/nacl/index.ts b/packages/util-crypto/src/nacl/index.ts index e246737d3..6dad09818 100644 --- a/packages/util-crypto/src/nacl/index.ts +++ b/packages/util-crypto/src/nacl/index.ts @@ -14,3 +14,6 @@ export { default as naclKeypairFromSeed } from './keypair/fromSeed'; export { default as naclKeypairFromString } from './keypair/fromString'; export { default as naclSign } from './sign'; export { default as naclVerify } from './verify'; +export { default as naclBoxKeypairFromSecret } from './box/fromSecret'; +export { default as naclOpen } from './open'; +export { default as naclSeal } from './seal'; diff --git a/packages/util-crypto/src/nacl/open.spec.ts b/packages/util-crypto/src/nacl/open.spec.ts new file mode 100644 index 000000000..4dbe0be59 --- /dev/null +++ b/packages/util-crypto/src/nacl/open.spec.ts @@ -0,0 +1,90 @@ +// Copyright 2017-2019 @polkadot/util-crypto authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { naclBoxKeypairFromSecret, naclKeypairFromString, naclOpen, naclSeal } from '.'; +import fromPath from '@polkadot/util-crypto/key/fromPath'; +import { keyExtractPath } from '@polkadot/util-crypto/key'; + +describe('naclOpen', (): void => { + it('opens a sealed message by the sender', (): void => { + const message = new Uint8Array([1, 2, 3, 4, 5, 4, 3, 2, 1]); + const sender = naclKeypairFromString('sender'); + const receiver = naclKeypairFromString('receiver'); + const senderBox = naclBoxKeypairFromSecret(sender.secretKey); + const receiverBox = naclBoxKeypairFromSecret(receiver.secretKey); + + const { sealed, nonce } = naclSeal(message, senderBox.secretKey, receiverBox.publicKey); + expect( + naclOpen(sealed, nonce, senderBox.publicKey, receiverBox.secretKey) + ).toEqual( + message + ); + }); + + it('polkadot does double ratchet', () => { + const sender = naclKeypairFromString('sender'); + const receiver = naclKeypairFromString('receiver'); + + // Make id key for identifying each other + const senderIdKey = fromPath(sender, keyExtractPath('//1//1//1//1').path, 'ed25519'); + const receiverIdKey = fromPath(receiver, keyExtractPath('//2//2//2//2').path, 'ed25519'); + + // Receiver sends encrypting public key to receive message to decrypt with his private key + const receiverIdBoxKey = naclBoxKeypairFromSecret(receiverIdKey.secretKey); + console.log(`Receiver sends receiver's public key to sender ${receiverIdBoxKey.publicKey}`); + + // Sender encrypts message to send with the public key the receiver sent and send it to receiver + const senderIdBoxKey = naclBoxKeypairFromSecret(senderIdKey.secretKey); + const message = new Uint8Array([1, 2, 3, 4, 5, 4, 3, 2, 1]); + const { sealed, nonce } = naclSeal(message, senderIdBoxKey.secretKey, receiverIdBoxKey.publicKey); + console.log(`Sender sends encrypted message to receiver ${sealed}, ${nonce}`); + + // Receiver opens encrypted message from the sender + let opened = naclOpen(sealed, nonce, senderIdBoxKey.publicKey, receiverIdBoxKey.secretKey); + console.log(`The sealed message is ${opened}`); + + // Oh no the Id key is hacked, derive Id key again from another index + const senderIdKey2 = fromPath(sender, keyExtractPath('//1//1//1//2').path, 'ed25519'); + const receiverIdKey2 = fromPath(receiver, keyExtractPath('//2//2//2//3').path, 'ed25519'); + + // Receiver sends encrypting public key to receive message to decrypt with his private key + const receiverIdBoxKey2 = naclBoxKeypairFromSecret(receiverIdKey2.secretKey); + console.log(`Receiver sends receiver's public key to sender ${receiverIdBoxKey2.publicKey}`); + + // Sender encrypts message to send with the public key the receiver sent and send it to receiver + const senderIdBoxKey2 = naclBoxKeypairFromSecret(senderIdKey2.secretKey); + const message2 = new Uint8Array([1, 2, 3, 4, 5, 4, 3, 2, 1]); + let ctx = naclSeal(message2, senderIdBoxKey2.secretKey, receiverIdBoxKey2.publicKey); + const sealed2 = ctx.sealed; + const nonce2 = ctx.nonce; + console.log(`Sender sends encrypted message to receiver ${sealed}, ${nonce}`); + + // Receiver opens encrypted message from the sender + opened = naclOpen(sealed2, nonce2, senderIdBoxKey2.publicKey, receiverIdBoxKey2.secretKey); + console.log(`The sealed message is ${opened}`); + + // This time derive key for each message to send and receive + // Receiver sends encrypting public key to receive message to decrypt with his private key + const receiverIdBoxKey2One = naclBoxKeypairFromSecret(receiverIdBoxKey2.secretKey); + console.log(`Receiver sends receiver's public key to sender ${receiverIdBoxKey2One.publicKey}`); + + // Sender encrypts message to send with the public key the receiver sent and send it to receiver + const senderIdBoxKey2One = naclBoxKeypairFromSecret(senderIdBoxKey2.secretKey); + const message3 = new Uint8Array([1, 2, 3, 4, 5, 4, 3, 2, 1]); + ctx = naclSeal(message3, senderIdBoxKey2One.secretKey, receiverIdBoxKey2One.publicKey); + const sealed3 = ctx.sealed; + const nonce3 = ctx.nonce; + console.log(`Sender sends encrypted message to receiver ${sealed3}, ${nonce3}`); + + // Receiver opens encrypted message from the sender + opened = naclOpen(sealed2, nonce2, senderIdBoxKey2One.publicKey, receiverIdBoxKey2One.secretKey); + console.log(`The sealed message is ${opened}`); + }); + + it('returns null on invalid', (): void => { + expect( + naclOpen(new Uint8Array(), new Uint8Array(24), new Uint8Array(32), new Uint8Array(32)) + ).toEqual(null); + }); +}); diff --git a/packages/util-crypto/src/nacl/open.ts b/packages/util-crypto/src/nacl/open.ts new file mode 100644 index 000000000..8506a85d2 --- /dev/null +++ b/packages/util-crypto/src/nacl/open.ts @@ -0,0 +1,23 @@ +// Copyright 2017-2019 @polkadot/util-crypto authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import nacl from 'tweetnacl'; + +/** + * @name naclOpen + * @summary Opens a message using the receiver's secretKey and nonce + * @description + * Returns a message sealed by the sender, using the receiver's `secret` and `nonce`. + * @example + *
+ * + * ```javascript + * import { naclOpen } from '@polkadot/util-crypto'; + * + * naclOpen([...], [...], [...]); // => [...] + * ``` + */ +export default function naclOpen (sealed: Uint8Array, nonce: Uint8Array, senderBoxPublic: Uint8Array, receiverBoxSecret: Uint8Array): Uint8Array | null { + return nacl.box.open(sealed, nonce, senderBoxPublic, receiverBoxSecret) || null; +} diff --git a/packages/util-crypto/src/nacl/seal.spec.ts b/packages/util-crypto/src/nacl/seal.spec.ts new file mode 100644 index 000000000..ffc916e2e --- /dev/null +++ b/packages/util-crypto/src/nacl/seal.spec.ts @@ -0,0 +1,23 @@ +// Copyright 2017-2019 @polkadot/util-crypto authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { naclSeal, naclKeypairFromString, naclBoxKeypairFromSecret } from '.'; + +describe('naclSeal', (): void => { + it('seals a message', (): void => { + const message = new Uint8Array([1, 2, 3, 4, 5, 4, 3, 2, 1]); + const sender = naclKeypairFromString('sender'); + const receiver = naclKeypairFromString('receiver'); + const senderBox = naclBoxKeypairFromSecret(sender.secretKey); + const receiverBox = naclBoxKeypairFromSecret(receiver.secretKey); + const senderBoxSecret = senderBox.secretKey; + const receiverBoxPublic = receiverBox.publicKey; + expect( + naclSeal(message, senderBoxSecret, receiverBoxPublic, new Uint8Array(24)) + ).toEqual({ + sealed: new Uint8Array([137, 195, 104, 255, 67, 199, 40, 235, 148, 177, 180, 73, 239, 131, 215, 80, 61, 191, 167, 213, 152, 138, 5, 47, 135]), + nonce: new Uint8Array(24) + }); + }); +}); diff --git a/packages/util-crypto/src/nacl/seal.ts b/packages/util-crypto/src/nacl/seal.ts new file mode 100644 index 000000000..eff7a178d --- /dev/null +++ b/packages/util-crypto/src/nacl/seal.ts @@ -0,0 +1,33 @@ +// Copyright 2017-2019 @polkadot/util-crypto authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import nacl from 'tweetnacl'; + +import randomAsU8a from '../random/asU8a'; + +interface Sealed { + sealed: Uint8Array; + nonce: Uint8Array; +} + +/** + * @name naclSeal + * @summary Seals a message using the sender's encrypting secretKey, receiver's public key, and nonce + * @description + * Returns an encrypted message which can be open only by receiver's secretKey. If the `nonce` was not supplied, a random value is generated. + * @example + *
+ * + * ```javascript + * import { naclEncrypt } from '@polkadot/util-crypto'; + * + * naclSeal([...], [...], [...], [...]); // => [...] + * ``` + */ +export default function naclSeal (message: Uint8Array, senderBoxSecret: Uint8Array, receiverBoxPublic: Uint8Array, nonce: Uint8Array = randomAsU8a(24)): Sealed { + return { + sealed: nacl.box(message, nonce, receiverBoxPublic, senderBoxSecret), + nonce + }; +}