From c6c92b18ccc10566ba52012caf93ea96b2e1a84b Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Fri, 31 Jul 2015 11:23:34 +0700 Subject: [PATCH] Break up index.js into logical files --- packages/ripple-keypairs/.travis.yml | 1 + packages/ripple-keypairs/README.md | 4 +- packages/ripple-keypairs/package.json | 2 +- packages/ripple-keypairs/src/ed25519.js | 69 +++++ packages/ripple-keypairs/src/index.js | 255 +----------------- packages/ripple-keypairs/src/keypair.js | 60 +++++ packages/ripple-keypairs/src/secp256k1.js | 124 +++++++++ packages/ripple-keypairs/src/utils.js | 26 +- .../ripple-keypairs/test/keypairs-test.js | 3 +- 9 files changed, 283 insertions(+), 261 deletions(-) create mode 100644 packages/ripple-keypairs/src/ed25519.js create mode 100644 packages/ripple-keypairs/src/keypair.js create mode 100644 packages/ripple-keypairs/src/secp256k1.js diff --git a/packages/ripple-keypairs/.travis.yml b/packages/ripple-keypairs/.travis.yml index 659718a7..2a3bc66d 100644 --- a/packages/ripple-keypairs/.travis.yml +++ b/packages/ripple-keypairs/.travis.yml @@ -4,6 +4,7 @@ node_js: script: - npm test --coverage - npm run-script coveralls +- npm run-script lint env: global: secure: VzkZvd0TM1m40yfahQKc5ITa/843l8BB0Sr8fA7O0RcMECTg/xBlWU4Paby2V+JWumgCJDx5YvxLlAdHeUaCWDkGlbJLPu8uPSRMZHRNH3PN2TNogNtruXmb9Z1ORZc5CwLNstWdK4MzMqErevPTxCxJ03scuzZxMiiEZpTOv+aQLZ7jEqTiA20q696QfsYpMvVB0XSpMEYqqFRdpMxGhf0QZ+bNlRN8h1SGw6ehw7jFKubQ1DozSKug10RU6aAGUHyaBisjc9tsSB1atJraeJUXSMucYD1L1oXWl0agE2RBSO8PAozwdJMoifHrVvQtW9cSuw9hPgiwd2qSNoH6IvhoIxeSEhqdVaS/0d22YKUxY+HkhYeIKCm+6kd0tcDfZ8+FqB7654ZhyIgCeOf1NcENu1OMaehbtKcALSBUi8yrDDVVntJFjBoKmPtdCAMq4iTL+28oBB7aDndUTjuyqEtPyN4qMY1R9bGcn/DMIwhBrRzIcM3MwPnn2S/+RUpr/NwyXXy7MgYXeVJE61sps/vA1JklwnRebL3c2nl/JZZrrWSaHquTMVDbkoxQHJhUR4vOQ/KzfQEQoqRZY9w/CO7HmVFMOo6RH3i5ZSNRN6YtOndGvUHFRmqIBf5iRaZiEgDb9xX1XmS4LzOR0dhHI3Gl/y3YAx8pbc7TeNrq4UQ= diff --git a/packages/ripple-keypairs/README.md b/packages/ripple-keypairs/README.md index e7fc4139..56dfc5c3 100644 --- a/packages/ripple-keypairs/README.md +++ b/packages/ripple-keypairs/README.md @@ -1,8 +1,6 @@ # ripple-keypairs -[![NPM](https://nodei.co/npm/ripple-keypairs.png)](https://npmjs.org/package/ripple-keypairs) - -[![Build Status](https://travis-ci.org/sublimator/ripple-keypairs.svg?branch=master)](https://travis-ci.org/sublimator/ripple-keypairs) [![Coverage Status](https://coveralls.io/repos/sublimator/ripple-keypairs/badge.svg?branch=master&service=github&v=1)](https://coveralls.io/github/sublimator/ripple-keypairs?branch=master) +[![NPM](https://img.shields.io/npm/v/ripple-keypairs.svg)](https://npmjs.org/package/ripple-keypairs) [![Build Status](https://img.shields.io/travis/sublimator/ripple-keypairs/master.svg)](https://travis-ci.org/sublimator/ripple-keypairs) [![Coverage Status](https://img.shields.io/coveralls/sublimator/ripple-keypairs/master.svg)](https://coveralls.io/github/sublimator/ripple-keypairs?branch=master) An implementation of ripple keypairs & wallet generation using [elliptic](https://github.com/indutny/elliptic) which supports rfc6979 and diff --git a/packages/ripple-keypairs/package.json b/packages/ripple-keypairs/package.json index b2324d84..43881566 100644 --- a/packages/ripple-keypairs/package.json +++ b/packages/ripple-keypairs/package.json @@ -1,6 +1,6 @@ { "name": "ripple-keypairs", - "version": "0.5.1", + "version": "0.5.2", "description": "ripple key pairs", "files": [ "dist/npm/*", diff --git a/packages/ripple-keypairs/src/ed25519.js b/packages/ripple-keypairs/src/ed25519.js new file mode 100644 index 00000000..7adfc52b --- /dev/null +++ b/packages/ripple-keypairs/src/ed25519.js @@ -0,0 +1,69 @@ +'use strict'; + +const util = require('util'); +const elliptic = require('elliptic'); +const {utils: {parseBytes}} = elliptic; +const Ed25519 = elliptic.eddsa('ed25519'); +const {KeyPair, KeyType} = require('./keypair'); +const { + Sha512, + hasCachedProperty, +} = require('./utils'); + +/* +@private +@param {Array} seed bytes + */ +function deriveEdKeyPairSeed(seed) { + return new Sha512().add(seed).first256(); +} + +/* +* @class +* @private +* @param {Object} - key +*/ +function Ed25519Pair() { + KeyPair.apply(this, arguments); + this.type = KeyType.ed25519; +} + +util.inherits(Ed25519Pair, KeyPair); + +/** +* @param {Array} seedBytes - A 128 bit seed +* @return {Ed25519Pair} key pair +*/ +Ed25519Pair.fromSeed = function(seedBytes) { + return new Ed25519Pair({seedBytes}); +}; + +/** +* @param {Seed} publicKey - public key in canonical form (0xED + 32 bytes) +* @return {Ed25519Pair} key pair +*/ +Ed25519Pair.fromPublic = function(publicKey) { + return new Ed25519Pair({pubBytes: parseBytes(publicKey)}); +}; + +hasCachedProperty(Ed25519Pair, 'key', function() { + if (this.seedBytes) { + const seed256 = deriveEdKeyPairSeed(this.seedBytes); + return Ed25519.keyFromSecret(seed256); + } + return Ed25519.keyFromPublic(this.pubKeyCanonicalBytes().slice(1)); +}); + +hasCachedProperty(Ed25519Pair, 'pubKeyCanonicalBytes', function() { + return [0xED].concat(this.key().pubBytes()); +}); + +Ed25519Pair.prototype.sign = function(message) { + return this.key().sign(message).toBytes(); +}; + +Ed25519Pair.prototype.verify = function(message, signature) { + return this.key().verify(message, signature); +}; + +module.exports = {Ed25519Pair}; diff --git a/packages/ripple-keypairs/src/index.js b/packages/ripple-keypairs/src/index.js index 9e52d502..327c2e04 100644 --- a/packages/ripple-keypairs/src/index.js +++ b/packages/ripple-keypairs/src/index.js @@ -2,259 +2,14 @@ /* -------------------------------- REQUIRES -------------------------------- */ -const util = require('util'); const assert = require('assert'); -const elliptic = require('elliptic'); -const hashjs = require('hash.js'); const codec = require('ripple-address-codec'); const rand = require('brorand'); -// elliptic -const secp256k1 = elliptic.ec('secp256k1'); -const Ed25519 = elliptic.eddsa('ed25519'); -const {utils: {parseBytes}} = elliptic; - -const { - bytesToHex, - hasCachedProperty, - isVirtual, - Sha512 -} = require('./utils'); - -/* ---------------------------------- ENUMS --------------------------------- */ - -const KeyType = { - secp256k1: 'secp256k1', - ed25519: 'ed25519' -}; - -/* ----------------------------- KEY DERIVATION ----------------------------- */ - -function seedFromPhrase(phrase) { - return hashjs.sha512().update(phrase).digest().slice(0, 16); -} - -function findk256Key(bytes, discrim) { - const order = secp256k1.curve.n; - for (let i = 0; i <= 0xFFFFFFFF; i++) { - // We hash the bytes to find a 256 bit number, looping until we are sure it - // is less than the order of the curve. - const hasher = new Sha512().add(bytes); - // If the optional discriminator index was passed in, update the hash. - if (discrim !== undefined) { - hasher.addU32(discrim); - } - hasher.addU32(i); - const key = hasher.finish256BN(); - if (key.cmpn(0) > 0 && key.cmp(order) < 0) { - return key; - } - } - throw new Error('impossible unicorn ;)'); -} - -/* eslint-disable valid-jsdoc */ - -/** -* @param {Object} [options] - -* @param {Number} [options.accountIndex=0] - the account number to generate -* @param {Boolean} [options.validator=false] - generate root key-pair, -* as used by validators. -* @return {new bn.js} - -* -*/ - -/* eslint-enable valid-jsdoc */ -function derivek256Secret(seed, opts={}) { - const root = opts.validator; - const order = secp256k1.curve.n; - - // This private generator represents the `root` private key, and is what's - // used by validators for signing when a keypair is generated from a seed. - const privateGen = findk256Key(seed); - if (root) { - // As returned by validation_create for a given seed - return privateGen; - } - const publicGen = secp256k1.g.mul(privateGen); - // A seed can generate many keypairs as a function of the seed and a uint32. - // Almost everyone just uses the first account, `0`. - const accountIndex = opts.accountIndex || 0; - return findk256Key(publicGen.encodeCompressed(), accountIndex) - .add(privateGen).mod(order); -} - -function createAccountID(pubKeyBytes) { - const hash256 = hashjs.sha256().update(pubKeyBytes).digest(); - const hash160 = hashjs.ripemd160().update(hash256).digest(); - return hash160; -} - - -/* -@private -@param {Array} seed bytes - */ -function deriveEdKeyPairSeed(seed) { - return new Sha512().add(seed).finish256(); -} - -/* --------------------------------- KEYPAIR -------------------------------- */ - -function KeyPair({seedBytes, pubBytes}) { - this.seedBytes = seedBytes; - this.pubKeyCanonicalBytes__ = pubBytes; -} - -/* -@param {Array} message -@virtual - */ -KeyPair.prototype.sign = isVirtual; - - -/* -@param {Array} message -@param {Array} signature -@virtual - */ -KeyPair.prototype.verify = isVirtual; - -/* -@return {Array} of bytes, in canonical form, for signing -@virtual -*/ -KeyPair.prototype.pubKeyCanonicalBytes = isVirtual; - -hasCachedProperty(KeyPair, 'pubKeyHex', function() { - return bytesToHex(this.pubKeyCanonicalBytes()); -}); - -hasCachedProperty(KeyPair, 'accountBytes', function() { - return createAccountID(this.pubKeyCanonicalBytes()); -}); - -hasCachedProperty(KeyPair, 'accountID', function() { - return codec.encodeAccountID(this.accountBytes()); -}); - -KeyPair.prototype.signHex = function(message) { - return bytesToHex(this.sign(message)); -}; - -/* ---------------------------- SECP256K1 KEYPAIR --------------------------- */ - -/* -* @class -* @private -* @param {Object} - key -*/ -function Ed25519Pair() { - KeyPair.apply(this, arguments); - this.type = KeyType.ed25519; -} - -util.inherits(Ed25519Pair, KeyPair); - -/** -* @param {Array} seedBytes - A 128 bit seed -* @return {Ed25519Pair} key pair -*/ -Ed25519Pair.fromSeed = function(seedBytes) { - return new Ed25519Pair({seedBytes}); -}; - -/** -* @param {Seed} publicKey - public key in canonical form (0xED + 32 bytes) -* @return {Ed25519Pair} key pair -*/ -Ed25519Pair.fromPublic = function (publicKey) { - return new Ed25519Pair({pubBytes: parseBytes(publicKey)}); -}; - -hasCachedProperty(Ed25519Pair, 'key', function() { - if (this.seedBytes) { - const seed256 = deriveEdKeyPairSeed(this.seedBytes); - return Ed25519.keyFromSecret(seed256); - } else { - return Ed25519.keyFromPublic(this.pubKeyCanonicalBytes().slice(1)); - } -}); - -hasCachedProperty(Ed25519Pair, 'pubKeyCanonicalBytes', function() { - return [0xED].concat(this.key().pubBytes()); -}); - -Ed25519Pair.prototype.sign = function(message) { - return this.key().sign(message).toBytes(); -}; - -Ed25519Pair.prototype.verify = function(message, signature) { - return this.key().verify(message, signature); -}; - -/* ---------------------------- SECP256K1 KEYPAIR --------------------------- */ - -/* -* @class -* @private -*/ -function K256Pair({validator}) { - KeyPair.apply(this, arguments); - this.type = KeyType.secp256k1; - this.validator = validator; -} - -util.inherits(K256Pair, KeyPair); - -K256Pair.fromSeed = function(seedBytes, opts={}) { - return new K256Pair({seedBytes, validator: opts.validator}); -}; - -hasCachedProperty(K256Pair, 'key', function() { - if (this.seedBytes) { - const options = {validator: this.validator}; - return secp256k1.keyFromPrivate(derivek256Secret(this.seedBytes, options)); - } else { - return secp256k1.keyFromPublic(this.pubKeyCanonicalBytes()); - } -}); - -hasCachedProperty(K256Pair, 'pubKeyCanonicalBytes', function() { - return this.key().getPublic().encodeCompressed(); -}); - -/* -@param {Array} message (bytes) - */ -K256Pair.prototype.sign = function(message) { - return this._createSignature(message).toDER(); -}; - -K256Pair.prototype._createSignature = function(message) { - // The key.sign message silently discards options - return this.key().sign(this.hashMessage(message), {canonical: true}); -}; - -/* -@param {Array} message - (bytes) -@return {Array} - 256 bit hash of the message - */ -K256Pair.prototype.hashMessage = function(message) { - return hashjs.sha512().update(message).digest().slice(0, 32); -}; - -/* -@param {Array} message - bytes -@param {Array} signature - DER encoded signature bytes - */ -K256Pair.prototype.verify = function(message, signature) { - try { - return this.key().verify(this.hashMessage(message), signature); - } catch (e) { - return false; - } -}; +const {seedFromPhrase, createAccountID} = require('./utils'); +const {KeyPair, KeyType} = require('./keypair'); +const {Ed25519Pair} = require('./ed25519'); +const {K256Pair} = require('./secp256k1'); function keyPairFromSeed(seedString, options) { const decoded = codec.decodeSeed(seedString); @@ -309,7 +64,7 @@ function generateValidatorKeys(opts={}) { function validatorKeysFromSeed(seed) { const {type, bytes} = codec.decodeSeed(seed); - assert(type == KeyType.secp256k1); + assert(type === KeyType.secp256k1); return deriveValidator(bytes); } diff --git a/packages/ripple-keypairs/src/keypair.js b/packages/ripple-keypairs/src/keypair.js new file mode 100644 index 00000000..d946133d --- /dev/null +++ b/packages/ripple-keypairs/src/keypair.js @@ -0,0 +1,60 @@ +'use strict'; + +const codec = require('ripple-address-codec'); +const { + bytesToHex, + hasCachedProperty, + isVirtual, + createAccountID +} = require('./utils'); + +const KeyType = { + secp256k1: 'secp256k1', + ed25519: 'ed25519' +}; + +function KeyPair({seedBytes, pubBytes}) { + this.seedBytes = seedBytes; + this.pubKeyCanonicalBytes__ = pubBytes; +} + +/* +@param {Array} message +@virtual + */ +KeyPair.prototype.sign = isVirtual; + + +/* +@param {Array} message +@param {Array} signature +@virtual + */ +KeyPair.prototype.verify = isVirtual; + +/* +@return {Array} of bytes, in canonical form, for signing +@virtual +*/ +KeyPair.prototype.pubKeyCanonicalBytes = isVirtual; + +hasCachedProperty(KeyPair, 'pubKeyHex', function() { + return bytesToHex(this.pubKeyCanonicalBytes()); +}); + +hasCachedProperty(KeyPair, 'accountBytes', function() { + return createAccountID(this.pubKeyCanonicalBytes()); +}); + +hasCachedProperty(KeyPair, 'accountID', function() { + return codec.encodeAccountID(this.accountBytes()); +}); + +KeyPair.prototype.signHex = function(message) { + return bytesToHex(this.sign(message)); +}; + +module.exports = { + KeyPair, + KeyType +}; diff --git a/packages/ripple-keypairs/src/secp256k1.js b/packages/ripple-keypairs/src/secp256k1.js new file mode 100644 index 00000000..0921a153 --- /dev/null +++ b/packages/ripple-keypairs/src/secp256k1.js @@ -0,0 +1,124 @@ +'use strict'; + +const util = require('util'); +const elliptic = require('elliptic'); +const secp256k1 = elliptic.ec('secp256k1'); +const hashjs = require('hash.js'); +const {KeyPair, KeyType} = require('./keypair'); +const { + Sha512, + hasCachedProperty +} = require('./utils'); + +function findk256Key(bytes, discrim) { + const order = secp256k1.curve.n; + for (let i = 0; i <= 0xFFFFFFFF; i++) { + // We hash the bytes to find a 256 bit number, looping until we are sure it + // is less than the order of the curve. + const hasher = new Sha512().add(bytes); + // If the optional discriminator index was passed in, update the hash. + if (discrim !== undefined) { + hasher.addU32(discrim); + } + hasher.addU32(i); + const key = hasher.first256BN(); + if (key.cmpn(0) > 0 && key.cmp(order) < 0) { + return key; + } + } + throw new Error('impossible unicorn ;)'); +} + +/** +* @param {Array} seed - bytes +* @param {Object} [opts] - object +* @param {Number} [opts.accountIndex=0] - the account number to generate +* @param {Boolean} [opts.validator=false] - generate root key-pair, +* as used by validators. +* @return {bn.js} - bignumber +* +*/ +function derivek256Secret(seed, opts={}) { + const root = opts.validator; + const order = secp256k1.curve.n; + + // This private generator represents the `root` private key, and is what's + // used by validators for signing when a keypair is generated from a seed. + const privateGen = findk256Key(seed); + if (root) { + // As returned by validation_create for a given seed + return privateGen; + } + const publicGen = secp256k1.g.mul(privateGen); + // A seed can generate many keypairs as a function of the seed and a uint32. + // Almost everyone just uses the first account, `0`. + const accountIndex = opts.accountIndex || 0; + return findk256Key(publicGen.encodeCompressed(), accountIndex) + .add(privateGen).mod(order); +} + +/* +* @class +* @private +*/ +function K256Pair({validator}) { + KeyPair.apply(this, arguments); + this.type = KeyType.secp256k1; + this.validator = validator; +} + +util.inherits(K256Pair, KeyPair); + +K256Pair.fromSeed = function(seedBytes, opts={}) { + return new K256Pair({seedBytes, validator: opts.validator}); +}; + +hasCachedProperty(K256Pair, 'key', function() { + if (this.seedBytes) { + const options = {validator: this.validator}; + return secp256k1.keyFromPrivate(derivek256Secret(this.seedBytes, options)); + } + return secp256k1.keyFromPublic(this.pubKeyCanonicalBytes()); +}); + +hasCachedProperty(K256Pair, 'pubKeyCanonicalBytes', function() { + return this.key().getPublic().encodeCompressed(); +}); + +/* +@param {Array} message (bytes) + */ +K256Pair.prototype.sign = function(message) { + return this._createSignature(message).toDER(); +}; + +K256Pair.prototype._createSignature = function(message) { + // The key.sign message silently discards options + return this.key().sign(this.hashMessage(message), {canonical: true}); +}; + +/* +@param {Array} message - (bytes) +@return {Array} - 256 bit hash of the message + */ +K256Pair.prototype.hashMessage = function(message) { + return hashjs.sha512().update(message).digest().slice(0, 32); +}; + +/* +@param {Array} message - bytes +@param {Array} signature - DER encoded signature bytes + */ +K256Pair.prototype.verify = function(message, signature) { + try { + return this.key().verify(this.hashMessage(message), signature); + } catch (e) { + return false; + } +}; + +module.exports = { + derivek256Secret, + findk256Key, + K256Pair +}; diff --git a/packages/ripple-keypairs/src/utils.js b/packages/ripple-keypairs/src/utils.js index dc835e97..df744297 100644 --- a/packages/ripple-keypairs/src/utils.js +++ b/packages/ripple-keypairs/src/utils.js @@ -25,9 +25,11 @@ function arrayToHex(a) { }).join(''); } -function toGenericArray(typedArray) { +function toGenericArray(sequence) { const generic = []; - Array.prototype.push.apply(generic, typedArray); + for (let i = 0; i < sequence.length; i++) { + generic.push(sequence[i]); + } return generic; } @@ -50,19 +52,31 @@ class Sha512 { finish() { return this.hash.digest(); } - finish256() { + first256() { return this.finish().slice(0, 32); } - finish256BN() { - return new BigNum(this.finish256()); + first256BN() { + return new BigNum(this.first256()); } } +function seedFromPhrase(phrase) { + return hashjs.sha512().update(phrase).digest().slice(0, 16); +} + +function createAccountID(pubKeyBytes) { + const hash256 = hashjs.sha256().update(pubKeyBytes).digest(); + const hash160 = hashjs.ripemd160().update(hash256).digest(); + return hash160; +} + module.exports = { arrayToHex, bytesToHex, hasCachedProperty, isVirtual, Sha512, - toGenericArray + toGenericArray, + seedFromPhrase, + createAccountID }; diff --git a/packages/ripple-keypairs/test/keypairs-test.js b/packages/ripple-keypairs/test/keypairs-test.js index 2fdcb213..dfa64ff8 100644 --- a/packages/ripple-keypairs/test/keypairs-test.js +++ b/packages/ripple-keypairs/test/keypairs-test.js @@ -141,7 +141,8 @@ describe('generateValidatorKeys', function() { "result" : { "status" : "success", "validation_key" : "A A A A A A A A A A A A", - "validation_public_key" : "n9LPxYzbDpWBZ1bC3J3Fdkgqoa3FEhVKCnS8yKp7RFQFwuvd8Q2c", + "validation_public_key" : + "n9LPxYzbDpWBZ1bC3J3Fdkgqoa3FEhVKCnS8yKp7RFQFwuvd8Q2c", "validation_seed" : "sp6JS7f14BuwFY8Mw6bTtLKWauoUs" } }