From 3720ebc9207f6088291a167c46b8014942d96dc2 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 31 Jan 2018 14:50:20 +0100 Subject: [PATCH] Add ed25519 keypair, sign & verify (#71) --- flow-typed/tweetnacl.js | 42 ++++++++- packages/util-crypto/docs/nacl.md | 91 +++++++++++++++++++ packages/util-crypto/src/nacl/index.js | 11 +++ packages/util-crypto/src/nacl/keypair.js | 23 +++++ packages/util-crypto/src/nacl/keypair.spec.js | 25 +++++ .../util-crypto/src/nacl/keypairFromSecret.js | 23 +++++ .../src/nacl/keypairFromSecret.spec.js | 32 +++++++ .../util-crypto/src/nacl/keypairFromSeed.js | 23 +++++ .../src/nacl/keypairFromSeed.spec.js | 37 ++++++++ packages/util-crypto/src/nacl/sign.js | 22 +++++ packages/util-crypto/src/nacl/sign.spec.js | 33 +++++++ packages/util-crypto/src/nacl/verify.js | 21 +++++ packages/util-crypto/src/nacl/verify.spec.js | 63 +++++++++++++ packages/util-crypto/src/types.js | 8 ++ 14 files changed, 452 insertions(+), 2 deletions(-) create mode 100644 packages/util-crypto/src/nacl/keypair.js create mode 100644 packages/util-crypto/src/nacl/keypair.spec.js create mode 100644 packages/util-crypto/src/nacl/keypairFromSecret.js create mode 100644 packages/util-crypto/src/nacl/keypairFromSecret.spec.js create mode 100644 packages/util-crypto/src/nacl/keypairFromSeed.js create mode 100644 packages/util-crypto/src/nacl/keypairFromSeed.spec.js create mode 100644 packages/util-crypto/src/nacl/sign.js create mode 100644 packages/util-crypto/src/nacl/sign.spec.js create mode 100644 packages/util-crypto/src/nacl/verify.js create mode 100644 packages/util-crypto/src/nacl/verify.spec.js create mode 100644 packages/util-crypto/src/types.js diff --git a/flow-typed/tweetnacl.js b/flow-typed/tweetnacl.js index 09ea9563a..2f9bc6e01 100644 --- a/flow-typed/tweetnacl.js +++ b/flow-typed/tweetnacl.js @@ -1,8 +1,46 @@ // @flow declare module 'tweetnacl' { + declare type Tweetnacl$KeypairType = { + publicKey: Uint8Array, + secretKey: Uint8Array + }; + + declare type Tweetnacl$Hash = { + (message: Uint8Array): Uint8Array, + + hashLength: number + }; + + declare type Tweetnacl$Sign$Detached = { + (message: Uint8Array, secretKey: Uint8Array): Uint8Array, + + verify: (message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array) => boolean + }; + + declare type Tweetnacl$Sign$Keypair = { + (): Tweetnacl$KeypairType, + + fromSecretKey: (secretKey: Uint8Array) => Tweetnacl$KeypairType, + fromSeed: (seed: Uint8Array) => Tweetnacl$KeypairType, + }; + + declare type Tweetnacl$Sign = { + (message: Uint8Array, secretKey: Uint8Array): Uint8Array, + detached: Tweetnacl$Sign$Detached, + + open: (signedMsg: Uint8Array, publicKey: Uint8Array) => Uint8Array, + keyPair: Tweetnacl$Sign$Keypair, + + publicKeyLength: number, + secretKeyLength: number, + seedLength: number, + signatureLength: number, + }; + declare module.exports: { - hash: (data: Uint8Array) => Uint8Array, - randomBytes: (length: number) => Uint8Array + randomBytes: (length: number) => Uint8Array, + hash: Tweetnacl$Hash, + sign: Tweetnacl$Sign } } diff --git a/packages/util-crypto/docs/nacl.md b/packages/util-crypto/docs/nacl.md index f0759ba37..ec53ca679 100644 --- a/packages/util-crypto/docs/nacl.md +++ b/packages/util-crypto/docs/nacl.md @@ -1,3 +1,94 @@ # nacl Implements [NaCl](http://nacl.cr.yp.to/) secret-key authenticated encryption, public-key authenticated encryption, hashing, and public-key signatures + +- [naclKeypair](#naclkeypair) Creates a new public/secret keypair. +- [naclKeypairFromSecret](#naclkeypairfromsecret) Creates a new public/secret keypair from a secret. +- [naclKeypairFromSeed](#naclkeypairfromseed) Creates a new public/secret keypair from a seed. +- [naclSign](#naclsign) Signs a message using the supplied secretKey +- [naclSign](#naclsign) Verifies the signature on the supplied message. + +## naclKeypair + +Creates a new public/secret keypair. + +```js +naclKeypair (): { secretKey: Uint8Array, publicKey: Uint8Array } +``` + + +Returns a new generate object containing a `publicKey` & `secretKey`. + +```js +import { naclKeypair } from '@polkadot/util-crypto'; + +naclKeypair() // => { secretKey: [...], publicKey: [...] } +``` + +## naclKeypairFromSecret + +Creates a new public/secret keypair from a secret. + +```js +naclKeypairFromSecret (secretKey: Uint8Array): { secretKey: Uint8Array, publicKey: Uint8Array } +``` + + +Returns a object containing a `publicKey` & `secretKey` generated from the supplied secret. + +```js +import { naclKeypairFromSecret } from '@polkadot/util-crypto'; + +naclKeypairFromSecret(...) // => { secretKey: [...], publicKey: [...] } +``` + +## naclKeypairFromSeed + +Creates a new public/secret keypair from a seed. + +```js +naclKeypairFromSeed (seed: Uint8Array): { secretKey: Uint8Array, publicKey: Uint8Array } +``` + + +Returns a object containing a `publicKey` & `secretKey` generated from the supplied seed. + +```js +import { naclKeypairFromSeed } from '@polkadot/util-crypto'; + +naclKeypairFromSeed(...) // => { secretKey: [...], publicKey: [...] } +``` + +## naclSign + +Signs a message using the supplied secretKey + +```js +naclSign (message: Uint8Array, secretKey: Uint8Array): Uint8Array +``` + + +Returns message signature of `message`, using the `secretKey`. + +```js +import { naclSign } from '@polkadot/util-crypto'; + +naclSign([...], [...]) // => [...] +``` + +## naclSign + +Verifies the signature on the supplied message. + +```js +naclVerify (message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean +``` + + +Verifies the `signature` on `message` with the supplied `plublicKey`. Returns `true` on sucess, `false` otherwise. + +```js +import { naclVerify } from '@polkadot/util-crypto'; + +naclVerify([...], [...], [...]) // => true/false +``` \ No newline at end of file diff --git a/packages/util-crypto/src/nacl/index.js b/packages/util-crypto/src/nacl/index.js index 5e74a4f01..9f2be76a9 100644 --- a/packages/util-crypto/src/nacl/index.js +++ b/packages/util-crypto/src/nacl/index.js @@ -3,8 +3,19 @@ // of the ISC license. See the LICENSE file for details. // @flow +const naclKeypair = require('./keypair'); +const naclKeypairFromSecret = require('./keypairFromSecret'); +const naclKeypairFromSeed = require('./keypairFromSeed'); +const naclSign = require('./sign'); +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 = { + naclKeypair, + naclKeypairFromSecret, + naclKeypairFromSeed, + naclSign, + naclVerify }; diff --git a/packages/util-crypto/src/nacl/keypair.js b/packages/util-crypto/src/nacl/keypair.js new file mode 100644 index 000000000..d7b6bf1ae --- /dev/null +++ b/packages/util-crypto/src/nacl/keypair.js @@ -0,0 +1,23 @@ +// 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 nacl = require('tweetnacl'); + +/** + @name naclKeypair + @signature naclKeypair (): { secretKey: Uint8Array, publicKey: Uint8Array } + @summary Creates a new public/secret keypair. + @description + Returns a new generate object containing a `publicKey` & `secretKey`. + @example + import { naclKeypair } from '@polkadot/util-crypto'; + + naclKeypair() // => { secretKey: [...], publicKey: [...] } +*/ +module.exports = function naclKeypair (): KeypairType { + return nacl.sign.keyPair(); +}; diff --git a/packages/util-crypto/src/nacl/keypair.spec.js b/packages/util-crypto/src/nacl/keypair.spec.js new file mode 100644 index 000000000..7101a38cb --- /dev/null +++ b/packages/util-crypto/src/nacl/keypair.spec.js @@ -0,0 +1,25 @@ +// 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 { naclKeypair } = require('./index'); + +describe('naclKeypair', () => { + let keypair; + + beforeEach(() => { + keypair = naclKeypair(); + }); + + it('generates a valid publicKey', () => { + expect( + keypair.publicKey + ).toHaveLength(32); + }); + + it('generates a valid secretKey', () => { + expect( + keypair.secretKey + ).toHaveLength(64); + }); +}); diff --git a/packages/util-crypto/src/nacl/keypairFromSecret.js b/packages/util-crypto/src/nacl/keypairFromSecret.js new file mode 100644 index 000000000..7062d7a70 --- /dev/null +++ b/packages/util-crypto/src/nacl/keypairFromSecret.js @@ -0,0 +1,23 @@ +// 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 nacl = require('tweetnacl'); + +/** + @name naclKeypairFromSecret + @signature naclKeypairFromSecret (secretKey: Uint8Array): { secretKey: Uint8Array, publicKey: Uint8Array } + @summary Creates a new public/secret keypair from a secret. + @description + Returns a object containing a `publicKey` & `secretKey` generated from the supplied secret. + @example + import { naclKeypairFromSecret } from '@polkadot/util-crypto'; + + naclKeypairFromSecret(...) // => { secretKey: [...], publicKey: [...] } +*/ +module.exports = function naclKeypairFromSecret (secretKey: Uint8Array): KeypairType { + return nacl.sign.keyPair.fromSecretKey(secretKey); +}; diff --git a/packages/util-crypto/src/nacl/keypairFromSecret.spec.js b/packages/util-crypto/src/nacl/keypairFromSecret.spec.js new file mode 100644 index 000000000..e827a3406 --- /dev/null +++ b/packages/util-crypto/src/nacl/keypairFromSecret.spec.js @@ -0,0 +1,32 @@ +// 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 { naclKeypairFromSecret } = require('./index'); + +describe('naclKeypairFromSecret', () => { + 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, + 180, 114, 93, 155, 165, 255, 217, 82, + 16, 250, 209, 11, 193, 10, 88, 218, + 190, 190, 41, 193, 236, 252, 1, 152, + 216, 214, 0, 41, 45, 138, 13, 53 + ]); + + it('generates a valid publicKey/secretKey pair', () => { + expect( + naclKeypairFromSecret(secretKey) + ).toEqual({ + publicKey: new Uint8Array([ + 180, 114, 93, 155, 165, 255, 217, 82, + 16, 250, 209, 11, 193, 10, 88, 218, + 190, 190, 41, 193, 236, 252, 1, 152, + 216, 214, 0, 41, 45, 138, 13, 53 + ]), + secretKey + }); + }); +}); diff --git a/packages/util-crypto/src/nacl/keypairFromSeed.js b/packages/util-crypto/src/nacl/keypairFromSeed.js new file mode 100644 index 000000000..a76d3a175 --- /dev/null +++ b/packages/util-crypto/src/nacl/keypairFromSeed.js @@ -0,0 +1,23 @@ +// 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 nacl = require('tweetnacl'); + +/** + @name naclKeypairFromSeed + @signature naclKeypairFromSeed (seed: Uint8Array): { secretKey: Uint8Array, publicKey: Uint8Array } + @summary Creates a new public/secret keypair from a seed. + @description + Returns a object containing a `publicKey` & `secretKey` generated from the supplied seed. + @example + import { naclKeypairFromSeed } from '@polkadot/util-crypto'; + + naclKeypairFromSeed(...) // => { secretKey: [...], publicKey: [...] } +*/ +module.exports = function naclKeypairFromSeed (seed: Uint8Array): KeypairType { + return nacl.sign.keyPair.fromSeed(seed); +}; diff --git a/packages/util-crypto/src/nacl/keypairFromSeed.spec.js b/packages/util-crypto/src/nacl/keypairFromSeed.spec.js new file mode 100644 index 000000000..bb3e8196a --- /dev/null +++ b/packages/util-crypto/src/nacl/keypairFromSeed.spec.js @@ -0,0 +1,37 @@ +// 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 { naclKeypairFromSeed } = require('./index'); + +describe('naclKeypairFromSeed', () => { + it('generates a valid publicKey/secretKey pair', () => { + expect( + naclKeypairFromSeed( + new Uint8Array([ + 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, + 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, + 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, + 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12 + ]) + ) + ).toEqual({ + publicKey: new Uint8Array([ + 180, 114, 93, 155, 165, 255, 217, 82, + 16, 250, 209, 11, 193, 10, 88, 218, + 190, 190, 41, 193, 236, 252, 1, 152, + 216, 214, 0, 41, 45, 138, 13, 53 + ]), + 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, + 180, 114, 93, 155, 165, 255, 217, 82, + 16, 250, 209, 11, 193, 10, 88, 218, + 190, 190, 41, 193, 236, 252, 1, 152, + 216, 214, 0, 41, 45, 138, 13, 53 + ]) + }); + }); +}); diff --git a/packages/util-crypto/src/nacl/sign.js b/packages/util-crypto/src/nacl/sign.js new file mode 100644 index 000000000..e3154cd69 --- /dev/null +++ b/packages/util-crypto/src/nacl/sign.js @@ -0,0 +1,22 @@ +// 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 naclSign + @signature naclSign (message: Uint8Array, secretKey: Uint8Array): Uint8Array + @summary Signs a message using the supplied secretKey + @description + Returns message signature of `message`, using the `secretKey`. + @example + import { naclSign } from '@polkadot/util-crypto'; + + naclSign([...], [...]) // => [...] +*/ +module.exports = function naclSign (message: Uint8Array, secretKey: Uint8Array): Uint8Array { + // NOTE: Original message appended to the signature, slice result only + return nacl.sign(message, secretKey).subarray(0, 64); +}; diff --git a/packages/util-crypto/src/nacl/sign.spec.js b/packages/util-crypto/src/nacl/sign.spec.js new file mode 100644 index 000000000..1d6d86855 --- /dev/null +++ b/packages/util-crypto/src/nacl/sign.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 { naclSign, naclKeypairFromSeed } = require('./index'); + +describe('naclSign', () => { + let secretKey; + + beforeEach(() => { + secretKey = naclKeypairFromSeed( + new Uint8Array([ + 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, + 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, + 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, + 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12 + ]) + ).secretKey; + }); + + it('returns a valid signature for the message', () => { + expect( + naclSign( + new Uint8Array([0x61, 0x62, 0x63, 0x64]), + secretKey + ) + ).toEqual( + new Uint8Array([ + 209, 234, 164, 44, 182, 218, 103, 16, 205, 238, 97, 222, 123, 112, 2, 240, 24, 192, 26, 134, 11, 170, 167, 153, 141, 108, 187, 171, 241, 125, 226, 179, 244, 232, 131, 61, 44, 68, 87, 41, 141, 131, 88, 36, 175, 173, 57, 29, 12, 112, 26, 200, 247, 89, 14, 64, 224, 188, 211, 198, 233, 119, 158, 6 + ]) + ); + }); +}); diff --git a/packages/util-crypto/src/nacl/verify.js b/packages/util-crypto/src/nacl/verify.js new file mode 100644 index 000000000..85abced35 --- /dev/null +++ b/packages/util-crypto/src/nacl/verify.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 naclSign + @signature naclVerify (message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean + @summary Verifies the signature on the supplied message. + @description + Verifies the `signature` on `message` with the supplied `plublicKey`. Returns `true` on sucess, `false` otherwise. + @example + import { naclVerify } from '@polkadot/util-crypto'; + + naclVerify([...], [...], [...]) // => true/false +*/ +module.exports = function naclVerify (message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean { + return nacl.sign.detached.verify(message, signature, publicKey); +}; diff --git a/packages/util-crypto/src/nacl/verify.spec.js b/packages/util-crypto/src/nacl/verify.spec.js new file mode 100644 index 000000000..7f69e1992 --- /dev/null +++ b/packages/util-crypto/src/nacl/verify.spec.js @@ -0,0 +1,63 @@ +// 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 { naclVerify, naclKeypairFromSeed } = require('./index'); + +describe('naclSign', () => { + let publicKey; + let signature; + + beforeEach(() => { + publicKey = naclKeypairFromSeed( + new Uint8Array([ + 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, + 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, + 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, + 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12 + ]) + ).publicKey; + signature = new Uint8Array([ + 209, 234, 164, 44, 182, 218, 103, 16, + 205, 238, 97, 222, 123, 112, 2, 240, + 24, 192, 26, 134, 11, 170, 167, 153, + 141, 108, 187, 171, 241, 125, 226, 179, + 244, 232, 131, 61, 44, 68, 87, 41, + 141, 131, 88, 36, 175, 173, 57, 29, + 12, 112, 26, 200, 247, 89, 14, 64, + 224, 188, 211, 198, 233, 119, 158, 6 + ]); + }); + + it('validates a correctly signed message', () => { + expect( + naclVerify( + new Uint8Array([0x61, 0x62, 0x63, 0x64]), + signature, + publicKey + ) + ).toEqual(true); + }); + + it('fails a correctly signed message (message changed)', () => { + expect( + naclVerify( + new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]), + signature, + publicKey + ) + ).toEqual(false); + }); + + it('fails a correctly signed message (signature changed)', () => { + signature[0] = 0xff; + + expect( + naclVerify( + new Uint8Array([0x61, 0x62, 0x63, 0x64]), + signature, + publicKey + ) + ).toEqual(false); + }); +}); diff --git a/packages/util-crypto/src/types.js b/packages/util-crypto/src/types.js new file mode 100644 index 000000000..24976622c --- /dev/null +++ b/packages/util-crypto/src/types.js @@ -0,0 +1,8 @@ +// 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 { Tweetnacl$KeypairType } from 'tweetnacl'; + +export type KeypairType = Tweetnacl$KeypairType;