Initial import

This commit is contained in:
Nicholas Dudfield
2015-06-17 19:57:21 +07:00
parent 869e6e2553
commit 0198b2603f
6 changed files with 501 additions and 0 deletions

63
packages/ripple-keypairs/.gitignore vendored Normal file
View File

@@ -0,0 +1,63 @@
# .gitignore
# Ignore vim swap files.
*.swp
# Ignore SCons support files.
.sconsign.dblite
# Ignore python compiled files.
*.pyc
# Ignore Macintosh Desktop Services Store files.
.DS_Store
# Ignore backup/temps
*~
# Ignore object files.
*.o
build/
tags
bin/rippled
Debug/*.*
Release/*.*
# Ignore locally installed node_modules
node_modules
!test/node_modules
# Ignore tmp directory.
tmp
# Ignore database directory.
db/*.db
db/*.db-*
# Ignore customized configs
rippled.cfg
validators.txt
test/config.js
# Ignore coverage files
/lib-cov
/src-cov
/coverage.html
/coverage
# Ignore IntelliJ files
.idea
# Ignore npm-debug
npm-debug.log
# Ignore dist folder, build for bower
dist/
# Ignore flow output directory
out/
# Ignore perf test cache
scripts/cache
eslintrc

View File

@@ -0,0 +1,247 @@
'use strict';
/* -------------------------------- REQUIRES -------------------------------- */
const util = require('util');
const elliptic = require('elliptic');
const bnjs = require('bn.js');
const hashjs = require('hash.js');
const codec = require('ripple-address-codec');
// elliptic
const secp256k1 = elliptic.ec('secp256k1');
const Ed25519 = elliptic.eddsa('ed25519');
const {
arrayToHex,
bytesToHex,
hasCachedProperty,
isVirtual,
Sha512,
toGenericArray
} = 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 */
function compressedPoint(p) {
const x = p.getX().toArray();
const y = p.getY();
while (x.length < 32) x.unshift(0)
return [y.isEven() ? 0x02 : 0x03].concat(x);
}
/**
* @param {Object} [options] -
* @param {Number} [options.accountIndex=0] - the account number to generate
* @param {Boolean} [options.root=false] - generate root key-pair,
* as used by validators.
* @return {new bn.js} -
*
*/
/* eslint-enable valid-jsdoc */
function derivek256Secret(seed, opts={}) {
const root = opts.root;
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(compressedPoint(publicGen), 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() {}
/*
@param {Array} message
@virtual
*/
KeyPair.prototype.sign = isVirtual;
/*
@param {Array<Byte>} message
@param {Array<Byte>} signature
@virtual
*/
KeyPair.prototype.verify = isVirtual;
/*
@return {Array<Byte>} 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(key) {
KeyPair.apply(this, arguments);
this.key = key;
this.type = KeyType.ed25519;
}
util.inherits(Ed25519Pair, KeyPair);
/**
* @param {Seed} seed - A 128 bit seed
* @return {Ed25519Pair} key pair
*/
Ed25519Pair.fromSeed = function(seed) {
const seed256 = deriveEdKeyPairSeed(seed);
const derived = Ed25519.keyFromSecret(seed256);
return new Ed25519Pair(derived);
};
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 Secp256k1Pair(key) {
KeyPair.apply(this, arguments);
this.type = KeyType.secp256k1;
this.key = key
}
util.inherits(Secp256k1Pair, KeyPair);
Secp256k1Pair.fromSeed = function(seed) {
return new Secp256k1Pair(secp256k1.keyFromPrivate(derivek256Secret(seed)));
};
hasCachedProperty(Secp256k1Pair, 'pubKeyCanonicalBytes', function() {
return this.key.getPublic(/*compact*/ true, /*enc*/ 'bytes');
});
/*
@param {Array<Byte>} message (bytes)
*/
Secp256k1Pair.prototype.sign = function(message) {
return this.key.sign(this.hashMessage(message), {canonical: true}).toDER();
};
/*
@param {Array<Byte>} message (bytes)
*/
Secp256k1Pair.prototype.signMessage = function(message) {
return this.key.sign(this.hashMessage(message), {canonical: true});
};
/*
@param {Array<Byte>} message (bytes)
@return {Array<Byte>} 256 bit hash of the message
*/
Secp256k1Pair.prototype.hashMessage = function(message) {
return hashjs.sha512().update(message).digest().slice(0, 32);
};
/*
@param {Array<Byte>} message - bytes
@param {Array<Byte>} signature - DER encoded signature bytes
*/
Secp256k1Pair.prototype.verify = function(message, signature) {
try {
return this.key.verify(this.hashMessage(message), signature);
} catch (e) {
return false;
}
};
module.exports = {
Secp256k1Pair,
Ed25519Pair,
KeyType,
seedFromPhrase
}

View File

@@ -0,0 +1,73 @@
{
"name": "ripple-keypairs",
"version": "0.1.0",
"description": "ripple key pairs",
"files": [
"dist/npm/*",
"bin/*",
"build/*",
"test/*",
"Gulpfile.js"
],
"main": "dist/npm/",
"directories": {
"test": "test"
},
"dependencies": {
"babel-runtime": "^5.3.2",
"bn.js": "^2.0.5",
"hash.js": "^1.0.3",
"ripple-address-codec": "github:sublimator/ripple-address-codec"
},
"devDependencies": {
"assert-diff": "^1.0.1",
"babel": "^5.3.3",
"babel-core": "^5.3.2",
"lodash": "^3.9.3",
"babel-loader": "^5.0.0",
"coveralls": "~2.10.0",
"eslint": "^0.18.0",
"eventemitter2": "^0.4.14",
"gulp": "~3.8.10",
"gulp-bump": "~0.1.13",
"gulp-clean-dest": "^0.1.0",
"gulp-filelog": "^0.4.1",
"gulp-flowtype": "^0.4.1",
"gulp-plumber": "^0.6.6",
"gulp-react": "^2.0.0",
"gulp-rename": "~1.2.0",
"gulp-uglify": "~1.1.0",
"gulp-util": "^3.0.3",
"gulp-watch": "^4.1.0",
"istanbul": "~0.3.5",
"map-stream": "~0.1.0",
"mocha": "~2.1.0",
"nock": "^0.34.1",
"webpack": "~1.5.3",
"yargs": "~1.3.1",
"hash.js": "^1.0.3",
"bn.js": "^2.0.5"
},
"scripts": {
"build": "gulp",
"compile": "babel -i runtime -d dist/npm/ src/",
"compile-with-source-maps": "babel -i runtime -s -t -d dist/npm/ src/",
"prepublish": "npm run compile",
"test": "istanbul test _mocha",
"coveralls": "cat ./coverage/lcov.info | coveralls",
"lint": "if ! [ -f eslintrc ]; then curl -o eslintrc 'https://raw.githubusercontent.com/ripple/javascript-style-guide/es6/eslintrc'; fi; eslint --reset -c eslintrc src/*.js"
},
"repository": {
"type": "git",
"url": "git://github.com/ripple/ripple-keypairs.git"
},
"engines": {
"node": ">=0.10.0"
},
"bugs": {
"url": "https://github.com/ripple/ripple-keypairs/issues"
},
"homepage": "https://github.com/ripple/ripple-keypairs#readme",
"author": "",
"license": "ISC"
}

View File

@@ -0,0 +1,49 @@
const assert = require('assert');
const _ = require('lodash');
const codec = require('ripple-address-codec');
const {Secp256k1Pair, seedFromPhrase, Ed25519Pair} = require('..');
const ZEROES = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0];
describe('KeyPair', function() {
let pair;
it('can be created from a passphrase', function() {
const seed = seedFromPhrase('niq');
const key = Ed25519Pair.fromSeed(seed);
assert.equal(key.accountID(), 'rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN');
assert.equal(codec.encodeSeed(seed), 'shQUG1pmPYrcnSUGeuJFJTA1b3JSL');
})
before(function function_name () {
pair = Secp256k1Pair.fromSeed(ZEROES);
});
it('can be instantiated', function() {
const sig = pair.sign(ZEROES);
assert(pair.verify(ZEROES, sig));
});
});
describe('generated tests', function() {
const expected = [
'30440220312b2e0894b81a2e070ace566c5dfc70cdd18e67d44e2cfef2eb5495f7de2dac02205e155c0019502948c265209dfdd7d84c4a05bd2c38cee6ecd7c33e9c9b12bec2',
'304402202a5860a12c15ebb8e91aa83f8e19d85d4ac05b272fc0c4083519339a7a76f2b802200852f9889e1284cf407dc7f73d646e62044c5ab432eaef3fff3f6f8ee9a0f24c',
'3045022100b1658c88d1860d9f8beb25b79b3e5137bbc2c382d08fe7a068ffc6ab8978c8040220644f64b97ea144ee7d5ccb71c2372dd730fa0a659e4c18241a80d6c915350263',
'3045022100f3e541330ff79ffc42eb0491ede1e47106d94ecfe3cdb2d9dd3bc0e8861f6d45022013f62942dd626d6c9731e317f372ec5c1f72885c4727fdbee9d9321bc530d7b2',
'3045022100998abe378f4119d8bee9843482c09f0d5ce5c6012921548182454c610c57a269022036bd8eb71235c4b2c67339de6a59746b1f7e5975987b7ab99b313d124a69bb9f'
];
const key = Secp256k1Pair.fromSeed(seedFromPhrase('niq'));
function test_factory(i) {
it('can deterministically sign/verify message [' + i +
']', function() {
const message = [i];
const sig = key.sign(message);
// assert.equal(utils.arrayToHex(sig), expected[i]);
assert(key.verify(message, sig));
});
}
for (let n = 0; n < 5; n++) {
test_factory(n);
}
});

View File

@@ -0,0 +1 @@
--reporter spec --timeout 5000 --slow 500 --compilers js:babel/register

View File

@@ -0,0 +1,68 @@
/* -------------------------------- REQUIRES -------------------------------- */
const bnjs = require('bn.js');
const hashjs = require('hash.js');
function isVirtual() {
throw new Error('virtual method not implemented ');
}
function hasCachedProperty(obj, name, computer) {
const key = name + '__';
obj.prototype[name] = function() {
let cached = this[key];
if(cached === undefined) {
cached = this[key] = computer.apply(this, arguments);
}
return cached;
};
}
function arrayToHex(a) {
return a.map(function(byteValue) {
const hex = byteValue.toString(16).toUpperCase();
return hex.length > 1 ? hex : '0' + hex;
}).join('');
}
function toGenericArray(typedArray) {
const generic = [];
Array.prototype.push.apply(generic, typedArray);
return generic;
}
function bytesToHex(bytes) {
return arrayToHex(bytes);
}
class Sha512 {
constructor () {
this.hash = hashjs.sha512();
}
add (bytes) {
this.hash.update(bytes);
return this;
}
addU32 (i) {
return this.add([(i >>> 24) & 0xFF, (i >>> 16) & 0xFF,
(i >>> 8) & 0xFF, i & 0xFF ]);
}
finish () {
return this.hash.digest();
}
finish256 () {
return this.finish().slice(0, 32);
}
finish256BN () {
return new bnjs(this.finish256());
}
}
module.exports = {
arrayToHex,
bytesToHex,
hasCachedProperty,
isVirtual,
Sha512,
toGenericArray
}