From 995dfecf61dbf96646200b4702847bc9014e9900 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 13 Mar 2018 17:04:44 +0100 Subject: [PATCH] Initial pair encryption (xsalsa20-poly1305) (#107) * Allow keypair generation from input string * Add keyfile encryption/decryption * Pass secret through n rounds * Split definition types * Additional cleanups * Pass salt as blake2b input * Move kdf hashing to util-cypto * Dangling comma * Pull through secret as string | Uint8Array * Update documentation --- package.json | 2 +- packages/util-crypto/docs/README.md | 1 + packages/util-crypto/docs/kdf.md | 22 +++++++++ packages/util-crypto/docs/nacl.md | 36 +++++++++++++++ packages/util-crypto/src/blake2/asU8a.js | 4 +- packages/util-crypto/src/blake2/asU8a256.js | 4 +- .../util-crypto/src/blake2/blake2b/asU8a.js | 4 +- packages/util-crypto/src/index.js | 3 +- packages/util-crypto/src/kdf/asU8a.js | 46 +++++++++++++++++++ packages/util-crypto/src/kdf/asU8a.spec.js | 33 +++++++++++++ packages/util-crypto/src/kdf/index.js | 13 ++++++ packages/util-crypto/src/nacl/decrypt.js | 21 +++++++++ packages/util-crypto/src/nacl/decrypt.spec.js | 19 ++++++++ packages/util-crypto/src/nacl/encrypt.js | 33 +++++++++++++ packages/util-crypto/src/nacl/encrypt.spec.js | 19 ++++++++ packages/util-crypto/src/nacl/index.js | 6 +++ .../src/nacl/keypair/fromString.js | 30 ++++++++++++ .../src/nacl/keypair/fromString.spec.js | 16 +++++++ packages/util-keyring/docs/README.md | 1 + packages/util-keyring/docs/pair.md | 3 ++ packages/util-keyring/src/decrypt.js | 19 ++++++++ packages/util-keyring/src/decrypt.spec.js | 29 ++++++++++++ packages/util-keyring/src/encrypt.js | 14 ++++++ packages/util-keyring/src/encrypt.spec.js | 29 ++++++++++++ packages/util-keyring/src/index.js | 33 ++++++------- packages/util-keyring/src/pair/decrypt.js | 20 ++++++++ packages/util-keyring/src/pair/encrypt.js | 28 +++++++++++ .../src/{pair.js => pair/index.js} | 10 +++- .../src/{pair.spec.js => pair/index.spec.js} | 2 +- packages/util-keyring/src/pairs.js | 27 +++++++++++ packages/util-keyring/src/types.js | 26 +++++++++++ yarn.lock | 6 +-- 32 files changed, 527 insertions(+), 32 deletions(-) create mode 100644 packages/util-crypto/docs/kdf.md create mode 100644 packages/util-crypto/src/kdf/asU8a.js create mode 100644 packages/util-crypto/src/kdf/asU8a.spec.js create mode 100644 packages/util-crypto/src/kdf/index.js create mode 100644 packages/util-crypto/src/nacl/decrypt.js create mode 100644 packages/util-crypto/src/nacl/decrypt.spec.js create mode 100644 packages/util-crypto/src/nacl/encrypt.js create mode 100644 packages/util-crypto/src/nacl/encrypt.spec.js create mode 100644 packages/util-crypto/src/nacl/keypair/fromString.js create mode 100644 packages/util-crypto/src/nacl/keypair/fromString.spec.js create mode 100644 packages/util-keyring/docs/pair.md create mode 100644 packages/util-keyring/src/decrypt.js create mode 100644 packages/util-keyring/src/decrypt.spec.js create mode 100644 packages/util-keyring/src/encrypt.js create mode 100644 packages/util-keyring/src/encrypt.spec.js create mode 100644 packages/util-keyring/src/pair/decrypt.js create mode 100644 packages/util-keyring/src/pair/encrypt.js rename packages/util-keyring/src/{pair.js => pair/index.js} (62%) rename packages/util-keyring/src/{pair.spec.js => pair/index.spec.js} (97%) create mode 100644 packages/util-keyring/src/pairs.js diff --git a/package.json b/package.json index 0165c3a38..1bcb30621 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "test": "jest --coverage" }, "devDependencies": { - "@polkadot/dev": "^0.16.14", + "@polkadot/dev": "^0.16.15", "lerna": "^2.5.1" } } diff --git a/packages/util-crypto/docs/README.md b/packages/util-crypto/docs/README.md index b2cdcd9a5..67e48296e 100644 --- a/packages/util-crypto/docs/README.md +++ b/packages/util-crypto/docs/README.md @@ -3,6 +3,7 @@ Utility methods for this package are split into groups - [blake2](blake2.md) Create blake2b values with specified bitlengths +- [kdf](kdf.md) Create Keys for use in encryption (rounds & salt) based on inputs - [keccak](keccak.md) Create Keccak256 values as hex & Uint8Array output - [nacl](nacl.md) Implements [NaCl](http://nacl.cr.yp.to/) secret-key authenticated encryption, public-key authenticated encryption, hashing, and public-key signatures - [random](random.md) Returns a sequence of secure random bytes in a variety of formats diff --git a/packages/util-crypto/docs/kdf.md b/packages/util-crypto/docs/kdf.md new file mode 100644 index 000000000..8d9048da4 --- /dev/null +++ b/packages/util-crypto/docs/kdf.md @@ -0,0 +1,22 @@ +# kdf + +Create Keys for use in encryption (rounds & salt) based on inputs + +- [kdfAsU8a](#kdfasu8a) Create Uint8Array key based on inputs + +## kdfAsU8a + +Create Uint8Array key based on inputs + +```js +kdfAsU8a (secret: Uint8Array | string, rounds?: number, salt?: Uint8Array): { key: Uint8Array, rounds: number, salt: Uint8Array } +``` + + +From either a `string` or a `Uint8Array` input, create a key with an optional (or random) salt based on the number of rounds with Blake2. Return return the result key as a `Uint8Array` in addition to the salt used and rounds employed. + +```js +import { kdfAsU8a } from '@polkadot/util-crypto'; + +kdfAsU8a('123') // => { key: Uint8Array, rounds: number, salt: Uint8Array } +``` \ No newline at end of file diff --git a/packages/util-crypto/docs/nacl.md b/packages/util-crypto/docs/nacl.md index 6fd6f2cf5..7a69ad6a2 100644 --- a/packages/util-crypto/docs/nacl.md +++ b/packages/util-crypto/docs/nacl.md @@ -2,9 +2,45 @@ Implements [NaCl](http://nacl.cr.yp.to/) secret-key authenticated encryption, public-key authenticated encryption, hashing, and public-key signatures +- [naclDecrypt](#nacldecrypt) Decrypts a message using the supplied secretKey and nonce +- [naclEncrypt](#naclencrypt) Encrypts a message using the supplied secretKey and nonce - [naclSign](#naclsign) Signs a message using the supplied secretKey - [naclSign](#naclsign) Verifies the signature on the supplied message. +## naclDecrypt + +Decrypts a message using the supplied secretKey and nonce + +```js +naclDecrypt (message: Uint8Array, secretKey: Uint8Array, nonce: Uint8Array): Uint8Array +``` + + +Returns an decrypted message, using the `secretKey` and `nonce`. + +```js +import { naclDecrypt } from '@polkadot/util-crypto'; + +naclDecrypt([...], [...], [...]) // => [...] +``` + +## naclEncrypt + +Encrypts a message using the supplied secretKey and nonce + +```js +naclEncrypt (message: Uint8Array, secretKey: Uint8Array, nonce?: Uint8Array): { encrypted: Uint8Array, nonce: Uint8Array } +``` + + +Returns an encrypted message, using the `secretKey` and `nonce`. If the `nonce` was not supplied, a random value is generated. + +```js +import { naclEncrypt } from '@polkadot/util-crypto'; + +naclSign([...], [...]) // => [...] +``` + ## naclSign Signs a message using the supplied secretKey diff --git a/packages/util-crypto/src/blake2/asU8a.js b/packages/util-crypto/src/blake2/asU8a.js index 552a1d9d9..4b7410127 100644 --- a/packages/util-crypto/src/blake2/asU8a.js +++ b/packages/util-crypto/src/blake2/asU8a.js @@ -16,6 +16,6 @@ const blake2bAsU8a = require('./blake2b/asU8a'); blake2AsU8a('abc') // => [0xba, 0x80, 0xa53, 0xf98, 0x1c, 0x4d, 0x0d] */ -module.exports = function blake2AsU8a (data: Uint8Array, bitLength: number = 256): Uint8Array { - return blake2bAsU8a(data, bitLength); +module.exports = function blake2AsU8a (data: Uint8Array, bitLength: number = 256, key?: Uint8Array): Uint8Array { + return blake2bAsU8a(data, bitLength, key); }; diff --git a/packages/util-crypto/src/blake2/asU8a256.js b/packages/util-crypto/src/blake2/asU8a256.js index 494cb53fb..41819e3a9 100644 --- a/packages/util-crypto/src/blake2/asU8a256.js +++ b/packages/util-crypto/src/blake2/asU8a256.js @@ -16,6 +16,6 @@ const blake2AsU8a = require('./asU8a'); blake2AsU8a256('abc') // => 0x44bc2cf5ad770999 */ -module.exports = function blake2AsU8a256 (data: Uint8Array): Uint8Array { - return blake2AsU8a(data, 256); +module.exports = function blake2AsU8a256 (data: Uint8Array, key?: Uint8Array): Uint8Array { + return blake2AsU8a(data, 256, key); }; diff --git a/packages/util-crypto/src/blake2/blake2b/asU8a.js b/packages/util-crypto/src/blake2/blake2b/asU8a.js index 5f31aa630..bf0520cf1 100644 --- a/packages/util-crypto/src/blake2/blake2b/asU8a.js +++ b/packages/util-crypto/src/blake2/blake2b/asU8a.js @@ -17,6 +17,6 @@ const blakejs = require('blakejs'); blake2bAsU8a('abc') // => Uint8Array('508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982') */ -module.exports = function blake2bAsU8a (data: Uint8Array, bitLength: number = 512): Uint8Array { - return blakejs.blake2b(data, null, bitLength / 8); +module.exports = function blake2bAsU8a (data: Uint8Array, bitLength: number = 512, key: ?Uint8Array = null): Uint8Array { + return blakejs.blake2b(data, key, bitLength / 8); }; diff --git a/packages/util-crypto/src/index.js b/packages/util-crypto/src/index.js index 82334939f..06cdf6199 100644 --- a/packages/util-crypto/src/index.js +++ b/packages/util-crypto/src/index.js @@ -4,6 +4,7 @@ // @flow const blake2 = require('./blake2'); +const kdf = require('./kdf'); const keccak = require('./keccak'); const nacl = require('./nacl'); const random = require('./random'); @@ -14,5 +15,5 @@ const xxhash = require('./xxhash'); @summary Utility methods for this package are split into groups */ module.exports = Object.assign( - {}, blake2, keccak, nacl, random, sha512, xxhash + {}, blake2, kdf, keccak, nacl, random, sha512, xxhash ); diff --git a/packages/util-crypto/src/kdf/asU8a.js b/packages/util-crypto/src/kdf/asU8a.js new file mode 100644 index 000000000..d45c29e55 --- /dev/null +++ b/packages/util-crypto/src/kdf/asU8a.js @@ -0,0 +1,46 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +type KdfU8a = { + key: Uint8Array, + rounds: number, + salt: Uint8Array +}; + +const isU8a = require('@polkadot/util/is/u8a'); +const u8aFromUtf8 = require('@polkadot/util/u8a/fromUtf8'); + +const blake2AsU8a256 = require('../blake2/asU8a256'); +const randomAsU8a = require('../random/asU8a'); + +/** + @name kdfAsU8a + @signature kdfAsU8a (secret: Uint8Array | string, rounds?: number, salt?: Uint8Array): { key: Uint8Array, rounds: number, salt: Uint8Array } + @summary Create Uint8Array key based on inputs + @description + From either a `string` or a `Uint8Array` input, create a key with an optional (or random) salt based on the number of rounds with Blake2. Return return the result key as a `Uint8Array` in addition to the salt used and rounds employed. + @example + import { kdfAsU8a } from '@polkadot/util-crypto'; + + kdfAsU8a('123') // => { key: Uint8Array, rounds: number, salt: Uint8Array } +*/ +module.exports = function kdfAsU8a (secret: Uint8Array | string, _rounds: number = 16, salt: Uint8Array = randomAsU8a(32)): KdfU8a { + const rounds = Math.max(1, _rounds); + let key: Uint8Array = isU8a(secret) + // flowlint-next-line unclear-type:off + ? ((secret: any): Uint8Array) + // flowlint-next-line unclear-type:off + : u8aFromUtf8(((secret: any): string)); + + for (let count = 0; count < rounds; count++) { + key = blake2AsU8a256(key, salt); + } + + return { + key, + rounds, + salt + }; +}; diff --git a/packages/util-crypto/src/kdf/asU8a.spec.js b/packages/util-crypto/src/kdf/asU8a.spec.js new file mode 100644 index 000000000..956818b4b --- /dev/null +++ b/packages/util-crypto/src/kdf/asU8a.spec.js @@ -0,0 +1,33 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +const { kdfAsU8a } = require('./index'); + +describe('kdfAsU8a', () => { + it('generates known via specified salt & rounds', () => { + const kdf = kdfAsU8a('test', 4, new Uint8Array(32)); + + expect( + kdf + ).toEqual({ + key: new Uint8Array([103, 101, 136, 180, 146, 65, 105, 190, 91, 158, 187, 202, 45, 7, 115, 229, 215, 76, 8, 12, 185, 45, 25, 148, 98, 156, 53, 167, 51, 15, 26, 150]), + rounds: 4, + salt: new Uint8Array(32) + }); + }); + + it('defaults with rounds & random salt', () => { + const kdf = kdfAsU8a(new Uint8Array(1, 2, 3, 4)); + + expect(kdf.key).toHaveLength(32); + expect(kdf.rounds).toEqual(16); + expect(kdf.salt).not.toEqual(new Uint8Array(32)); + }); + + it('does at least 1 round', () => { + const kdf = kdfAsU8a(new Uint8Array(1, 2, 3, 4), -10); + + expect(kdf.rounds).toEqual(1); + }); +}); diff --git a/packages/util-crypto/src/kdf/index.js b/packages/util-crypto/src/kdf/index.js new file mode 100644 index 000000000..2f41ab55d --- /dev/null +++ b/packages/util-crypto/src/kdf/index.js @@ -0,0 +1,13 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +const kdfAsU8a = require('./asU8a'); + +/** + @summary Create Keys for use in encryption (rounds & salt) based on inputs +*/ +module.exports = { + kdfAsU8a +}; diff --git a/packages/util-crypto/src/nacl/decrypt.js b/packages/util-crypto/src/nacl/decrypt.js new file mode 100644 index 000000000..827d9d34c --- /dev/null +++ b/packages/util-crypto/src/nacl/decrypt.js @@ -0,0 +1,21 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +const nacl = require('tweetnacl'); + +/** + @name naclDecrypt + @signature naclDecrypt (message: Uint8Array, secretKey: Uint8Array, nonce: Uint8Array): Uint8Array + @summary Decrypts a message using the supplied secretKey and nonce + @description + Returns an decrypted message, using the `secretKey` and `nonce`. + @example + import { naclDecrypt } from '@polkadot/util-crypto'; + + naclDecrypt([...], [...], [...]) // => [...] +*/ +module.exports = function naclDecrypt (message: Uint8Array, secret: Uint8Array, nonce: Uint8Array): ?Uint8Array { + return nacl.secretbox.open(message, nonce, secret); +}; diff --git a/packages/util-crypto/src/nacl/decrypt.spec.js b/packages/util-crypto/src/nacl/decrypt.spec.js new file mode 100644 index 000000000..fae6915f4 --- /dev/null +++ b/packages/util-crypto/src/nacl/decrypt.spec.js @@ -0,0 +1,19 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +const { naclDecrypt, naclEncrypt } = require('./index'); + +describe('naclDecrypt', () => { + it('decrypts a encrypted message', () => { + const secret = new Uint8Array(32); + const message = new Uint8Array([1, 2, 3, 4, 5, 4, 3, 2, 1]); + const { encrypted, nonce } = naclEncrypt(message, secret); + + expect( + naclDecrypt(encrypted, secret, nonce) + ).toEqual( + message + ); + }); +}); diff --git a/packages/util-crypto/src/nacl/encrypt.js b/packages/util-crypto/src/nacl/encrypt.js new file mode 100644 index 000000000..1ed8c67f9 --- /dev/null +++ b/packages/util-crypto/src/nacl/encrypt.js @@ -0,0 +1,33 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +type Encrypted = { + encrypted: Uint8Array, + nonce: Uint8Array +}; + +const nacl = require('tweetnacl'); + +const randomAsU8a = require('../random/asU8a'); + +/** + @name naclEncrypt + @signature naclEncrypt (message: Uint8Array, secretKey: Uint8Array, nonce?: Uint8Array): { encrypted: Uint8Array, nonce: Uint8Array } + @summary Encrypts a message using the supplied secretKey and nonce + @description + Returns an encrypted message, using the `secretKey` and `nonce`. If the `nonce` was not supplied, a random value is generated. + @example + import { naclEncrypt } from '@polkadot/util-crypto'; + + naclSign([...], [...]) // => [...] +*/ +module.exports = function naclEncrypt (message: Uint8Array, secret: Uint8Array, nonce: Uint8Array = randomAsU8a(24)): Encrypted { + const encrypted = nacl.secretbox(message, nonce, secret); + + return { + encrypted, + nonce + }; +}; diff --git a/packages/util-crypto/src/nacl/encrypt.spec.js b/packages/util-crypto/src/nacl/encrypt.spec.js new file mode 100644 index 000000000..f98348395 --- /dev/null +++ b/packages/util-crypto/src/nacl/encrypt.spec.js @@ -0,0 +1,19 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +const { naclEncrypt } = require('./index'); + +describe('naclEncrypt', () => { + it('encrypts a message', () => { + const secret = new Uint8Array(32); + const message = new Uint8Array([1, 2, 3, 4, 5, 4, 3, 2, 1]); + + expect( + naclEncrypt(message, secret, new Uint8Array(24)) + ).toEqual({ + encrypted: new Uint8Array([94, 21, 20, 69, 68, 221, 140, 245, 200, 67, 77, 188, 129, 85, 227, 141, 199, 60, 184, 251, 251, 129, 205, 46, 234]), + nonce: new Uint8Array(24) + }); + }); +}); diff --git a/packages/util-crypto/src/nacl/index.js b/packages/util-crypto/src/nacl/index.js index 68becc6e9..b7b745300 100644 --- a/packages/util-crypto/src/nacl/index.js +++ b/packages/util-crypto/src/nacl/index.js @@ -3,9 +3,12 @@ // of the ISC license. See the LICENSE file for details. // @flow +const naclDecrypt = require('./decrypt'); +const naclEncrypt = require('./encrypt'); const naclKeypairFromRandom = require('./keypair/fromRandom'); const naclKeypairFromSecret = require('./keypair/fromSecret'); const naclKeypairFromSeed = require('./keypair/fromSeed'); +const naclKeypairFromString = require('./keypair/fromString'); const naclSign = require('./sign'); const naclVerify = require('./verify'); @@ -13,9 +16,12 @@ const naclVerify = require('./verify'); @summary Implements [NaCl](http://nacl.cr.yp.to/) secret-key authenticated encryption, public-key authenticated encryption, hashing, and public-key signatures */ module.exports = { + naclDecrypt, + naclEncrypt, naclKeypairFromRandom, naclKeypairFromSecret, naclKeypairFromSeed, + naclKeypairFromString, naclSign, naclVerify }; diff --git a/packages/util-crypto/src/nacl/keypair/fromString.js b/packages/util-crypto/src/nacl/keypair/fromString.js new file mode 100644 index 000000000..19fc35dbf --- /dev/null +++ b/packages/util-crypto/src/nacl/keypair/fromString.js @@ -0,0 +1,30 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +import type { KeypairType } from '../../types'; + +const u8aFromUtf8 = require('@polkadot/util/u8a/fromUtf8'); + +const blake2AsU8a256 = require('../../blake2/asU8a256'); +const fromSeed = require('./fromSeed'); + +/** + @name naclKeypairFromString + @signature naclKeypairFromString (value: string): { secretKey: Uint8Array, publicKey: Uint8Array } + @summary Creates a new public/secret keypair from a string. + @description + Returns a object containing a `publicKey` & `secretKey` generated from the supplied string. The string is hashed and the value used as the input seed. + @example + import { naclKeypairFromString } from '@polkadot/util-crypto'; + + naclKeypairFromString('test') // => { secretKey: [...], publicKey: [...] } +*/ +module.exports = function naclKeypairFromString (value: string): KeypairType { + return fromSeed( + blake2AsU8a256( + u8aFromUtf8(value) + ) + ); +}; diff --git a/packages/util-crypto/src/nacl/keypair/fromString.spec.js b/packages/util-crypto/src/nacl/keypair/fromString.spec.js new file mode 100644 index 000000000..b89682967 --- /dev/null +++ b/packages/util-crypto/src/nacl/keypair/fromString.spec.js @@ -0,0 +1,16 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +const { naclKeypairFromString } = require('../index'); + +describe('naclKeypairFromSeed', () => { + it('generates a valid publicKey/secretKey pair', () => { + expect( + naclKeypairFromString('test') + ).toEqual({ + publicKey: new Uint8Array([188, 108, 179, 142, 36, 142, 76, 87, 77, 193, 147, 139, 254, 110, 196, 217, 117, 233, 167, 165, 250, 150, 247, 237, 198, 68, 129, 4, 211, 209, 136, 48]), + secretKey: new Uint8Array([146, 139, 32, 54, 105, 67, 226, 175, 209, 30, 188, 14, 174, 46, 83, 169, 59, 241, 119, 164, 252, 243, 91, 204, 100, 213, 3, 112, 78, 101, 226, 2, 188, 108, 179, 142, 36, 142, 76, 87, 77, 193, 147, 139, 254, 110, 196, 217, 117, 233, 167, 165, 250, 150, 247, 237, 198, 68, 129, 4, 211, 209, 136, 48]) + }); + }); +}); diff --git a/packages/util-keyring/docs/README.md b/packages/util-keyring/docs/README.md index 3b7b91d37..4cd04ce2d 100644 --- a/packages/util-keyring/docs/README.md +++ b/packages/util-keyring/docs/README.md @@ -2,5 +2,6 @@ +- [pair](pair.md) # Available methods \ No newline at end of file diff --git a/packages/util-keyring/docs/pair.md b/packages/util-keyring/docs/pair.md new file mode 100644 index 000000000..6f4489c48 --- /dev/null +++ b/packages/util-keyring/docs/pair.md @@ -0,0 +1,3 @@ +# pair + + diff --git a/packages/util-keyring/src/decrypt.js b/packages/util-keyring/src/decrypt.js new file mode 100644 index 000000000..c64b6116f --- /dev/null +++ b/packages/util-keyring/src/decrypt.js @@ -0,0 +1,19 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +import type { KeyringPairEncrypted, KeyringPair } from './types'; + +const createPair = require('./pair'); + +module.exports = function decrypt (box: KeyringPairEncrypted, secret: Uint8Array | string): KeyringPair { + const pair = createPair({ + publicKey: box.publicKey, + secretKey: new Uint8Array([]) + }); + + pair.decryptSelf(box, secret); + + return pair; +}; diff --git a/packages/util-keyring/src/decrypt.spec.js b/packages/util-keyring/src/decrypt.spec.js new file mode 100644 index 000000000..5e6ca948d --- /dev/null +++ b/packages/util-keyring/src/decrypt.spec.js @@ -0,0 +1,29 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +const index = require('./index'); + +describe('decrypt', () => { + const publicKey = new Uint8Array([47, 140, 97, 41, 216, 22, 207, 81, 195, 116, 188, 127, 8, 195, 230, 62, 209, 86, 207, 120, 174, 251, 74, 101, 80, 217, 123, 135, 153, 121, 119, 238]); + const seedOne = '12345678901234567890123456789012'; + let keypair; + let encrypted; + + beforeEach(() => { + keypair = index(); + + keypair.addFromSeed(seedOne); + encrypted = keypair.encrypt(publicKey, 'test'); + + keypair = index(); + }); + + it('decrypts a previously encrypted', () => { + expect( + keypair.decrypt(encrypted, 'test') + ).toMatchObject({ + publicKey + }); + }); +}); diff --git a/packages/util-keyring/src/encrypt.js b/packages/util-keyring/src/encrypt.js new file mode 100644 index 000000000..ceba75d36 --- /dev/null +++ b/packages/util-keyring/src/encrypt.js @@ -0,0 +1,14 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +import type { KeyringPairEncrypted, KeyringPair } from './types'; + +module.exports = function encrypt (pair: ?KeyringPair, secret: Uint8Array | string): ?KeyringPairEncrypted { + if (!pair) { + return null; + } + + return pair.encryptSelf(secret); +}; diff --git a/packages/util-keyring/src/encrypt.spec.js b/packages/util-keyring/src/encrypt.spec.js new file mode 100644 index 000000000..99792dd6c --- /dev/null +++ b/packages/util-keyring/src/encrypt.spec.js @@ -0,0 +1,29 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +const index = require('./index'); + +describe('encrypt', () => { + const publicKey = new Uint8Array([47, 140, 97, 41, 216, 22, 207, 81, 195, 116, 188, 127, 8, 195, 230, 62, 209, 86, 207, 120, 174, 251, 74, 101, 80, 217, 123, 135, 153, 121, 119, 238]); + const seedOne = '12345678901234567890123456789012'; + let keypair; + + beforeEach(() => { + keypair = index(); + + keypair.addFromSeed(seedOne); + }); + + it('encrypts the key', () => { + expect( + keypair.encrypt(publicKey, 'test').publicKey + ).toEqual(publicKey); + }); + + it('returns null for non-existing', () => { + expect( + keypair.encrypt(new Uint8Array([]), 'test') + ).toEqual(null); + }); +}); diff --git a/packages/util-keyring/src/index.js b/packages/util-keyring/src/index.js index a44d228fb..cf7de8464 100644 --- a/packages/util-keyring/src/index.js +++ b/packages/util-keyring/src/index.js @@ -3,34 +3,29 @@ // of the ISC license. See the LICENSE file for details. // @flow -import type { KeyringInstance, KeyringPair } from './types'; - -type KeyringPairMap = { - [Uint8Array]: KeyringPair -}; +import type { KeyringPairEncrypted, KeyringInstance, KeyringPair } from './types'; const naclKeypairFromSeed = require('@polkadot/util-crypto/nacl/keypair/fromSeed'); +const decrypt = require('./decrypt'); +const encrypt = require('./encrypt'); const createPair = require('./pair'); +const createPairs = require('./pairs'); module.exports = function keyring (): KeyringInstance { - const pairs: KeyringPairMap = {}; - const getPairs = (): Array => - // flowlint-next-line unclear-type:off - ((Object.values(pairs): any): Array); + const pairs = createPairs(); return { - addFromSeed: (seed: Uint8Array | string): KeyringPair => { - const pair = createPair(naclKeypairFromSeed(seed)); - - pairs[pair.publicKey] = pair; - - return pair; - }, + addFromSeed: (seed: Uint8Array | string): KeyringPair => + pairs.add(createPair(naclKeypairFromSeed(seed))), + decrypt: (encrypted: KeyringPairEncrypted, secret: Uint8Array | string): KeyringPair => + pairs.add(decrypt(encrypted, secret)), + encrypt: (publicKey: Uint8Array, secret: Uint8Array | string): ?KeyringPairEncrypted => + encrypt(pairs.get(publicKey), secret), getPair: (publicKey: Uint8Array): ?KeyringPair => - pairs[publicKey], - getPairs, + pairs.get(publicKey), + getPairs: pairs.all, getPublicKeys: (): Array => - getPairs().map(({ publicKey }) => publicKey) + pairs.all().map(({ publicKey }) => publicKey) }; }; diff --git a/packages/util-keyring/src/pair/decrypt.js b/packages/util-keyring/src/pair/decrypt.js new file mode 100644 index 000000000..d8635e62c --- /dev/null +++ b/packages/util-keyring/src/pair/decrypt.js @@ -0,0 +1,20 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +import type { KeyringPairEncrypted } from '../types'; + +const kdf = require('@polkadot/util-crypto/kdf/asU8a'); +const naclDecrypt = require('@polkadot/util-crypto/nacl/decrypt'); +const assert = require('@polkadot/util/assert'); + +module.exports = function decrypt ({ crypto: { params: { nonce }, kdf: { rounds, salt }, text } }: KeyringPairEncrypted, secret: Uint8Array | string): Uint8Array { + const { key } = kdf(secret, rounds, salt); + const secretKey = naclDecrypt(text, key, nonce); + + assert(secretKey, 'Unable to decrypt pair using secret'); + + // $FlowFixMe checking for type just above + return ((secretKey: any): Uint8Array); +}; diff --git a/packages/util-keyring/src/pair/encrypt.js b/packages/util-keyring/src/pair/encrypt.js new file mode 100644 index 000000000..b5ba52d36 --- /dev/null +++ b/packages/util-keyring/src/pair/encrypt.js @@ -0,0 +1,28 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +import type { KeyringPairEncrypted } from '../types'; + +const kdf = require('@polkadot/util-crypto/kdf/asU8a'); +const naclEncrypt = require('@polkadot/util-crypto/nacl/encrypt'); + +module.exports = function encrypt (message: Uint8Array, secret: Uint8Array | string, publicKey: Uint8Array): KeyringPairEncrypted { + const { key, rounds, salt } = kdf(secret); + const { encrypted, nonce } = naclEncrypt(message, key); + + return { + crypto: { + params: { + nonce + }, + kdf: { + rounds, + salt + }, + text: encrypted + }, + publicKey + }; +}; diff --git a/packages/util-keyring/src/pair.js b/packages/util-keyring/src/pair/index.js similarity index 62% rename from packages/util-keyring/src/pair.js rename to packages/util-keyring/src/pair/index.js index 386895303..66c811713 100644 --- a/packages/util-keyring/src/pair.js +++ b/packages/util-keyring/src/pair/index.js @@ -4,14 +4,22 @@ // @flow import type { KeypairType } from '@polkadot/util-crypto/types'; -import type { KeyringPair } from './types'; +import type { KeyringPairEncrypted, KeyringPair } from '../types'; const naclSign = require('@polkadot/util-crypto/nacl/sign'); const naclVerify = require('@polkadot/util-crypto/nacl/verify'); +const decrypt = require('./decrypt'); +const encrypt = require('./encrypt'); + module.exports = function pair ({ publicKey, secretKey }: KeypairType): KeyringPair { return { publicKey, + decryptSelf: (encrypted: KeyringPairEncrypted, secret: Uint8Array | string): void => { + secretKey = decrypt(encrypted, secret); + }, + encryptSelf: (secret: Uint8Array | string): KeyringPairEncrypted => + encrypt(secretKey, secret, publicKey), sign: (message: Uint8Array): Uint8Array => naclSign(message, secretKey), verify: (message: Uint8Array, signature: Uint8Array): boolean => diff --git a/packages/util-keyring/src/pair.spec.js b/packages/util-keyring/src/pair/index.spec.js similarity index 97% rename from packages/util-keyring/src/pair.spec.js rename to packages/util-keyring/src/pair/index.spec.js index 69d36149d..14ee0c35c 100644 --- a/packages/util-keyring/src/pair.spec.js +++ b/packages/util-keyring/src/pair/index.spec.js @@ -2,7 +2,7 @@ // This software may be modified and distributed under the terms // of the ISC license. See the LICENSE file for details. -const keyring = require('./testing')(); +const keyring = require('../testing')(); // aligned with @polkadot/util-crypto tests describe('pair', () => { diff --git a/packages/util-keyring/src/pairs.js b/packages/util-keyring/src/pairs.js new file mode 100644 index 000000000..46a0323a1 --- /dev/null +++ b/packages/util-keyring/src/pairs.js @@ -0,0 +1,27 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +import type { KeyringPairs, KeyringPair } from './types'; + +type KeyringPairMap = { + [Uint8Array]: KeyringPair +}; + +module.exports = function pairs (): KeyringPairs { + const self: KeyringPairMap = {}; + + return { + add: (pair: KeyringPair): KeyringPair => { + self[pair.publicKey] = pair; + + return pair; + }, + all: (): Array => + // flowlint-next-line unclear-type:off + ((Object.values(self): any): Array), + get: (publicKey: Uint8Array): ?KeyringPair => + self[publicKey] + }; +}; diff --git a/packages/util-keyring/src/types.js b/packages/util-keyring/src/types.js index a641837e6..5213c8e2f 100644 --- a/packages/util-keyring/src/types.js +++ b/packages/util-keyring/src/types.js @@ -3,15 +3,41 @@ // of the ISC license. See the LICENSE file for details. // @flow +export type KeyringPairEncrypted$Cipher = 'xsalsa20-poly1305'; + +export type KeyringPairEncrypted = { + crypto: { + params: { + nonce: Uint8Array + }, + kdf: { + rounds: number, + salt: Uint8Array + }, + text: Uint8Array + }, + publicKey: Uint8Array +}; + export type KeyringPair = { publicKey: Uint8Array, + decryptSelf: (box: KeyringPairEncrypted, secret: Uint8Array | string) => void, + encryptSelf: (secret: Uint8Array | string) => KeyringPairEncrypted, sign (message: Uint8Array): Uint8Array, verify (message: Uint8Array, signature: Uint8Array): boolean }; +export type KeyringPairs = { + add: (pair: KeyringPair) => KeyringPair, + all: () => Array, + get: (publicKey: Uint8Array) => ?KeyringPair +}; + export type KeyringInstance = { addFromSeed (seed: Uint8Array | string): KeyringPair, + decrypt (encrypted: KeyringPairEncrypted, secret: Uint8Array | string): KeyringPair, + encrypt (publicKey: Uint8Array, secret: Uint8Array | string): ?KeyringPairEncrypted, getPair (publicKey: Uint8Array): ?KeyringPair, getPairs (): Array, getPublicKeys (): Array diff --git a/yarn.lock b/yarn.lock index 2ac0aac0b..d489873de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -85,9 +85,9 @@ dependencies: babel-runtime "^6.26.0" -"@polkadot/dev@^0.16.14": - version "0.16.14" - resolved "https://registry.yarnpkg.com/@polkadot/dev/-/dev-0.16.14.tgz#69884091bf9ee93137a18d0543629d50a5b8480b" +"@polkadot/dev@^0.16.15": + version "0.16.15" + resolved "https://registry.yarnpkg.com/@polkadot/dev/-/dev-0.16.15.tgz#98bd325ac8de7859d208069b3cc1202f4f6ab829" dependencies: babel-cli "^6.26.0" babel-core "^6.26.0"