mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-30 17:15:49 +00:00
Initial import
This commit is contained in:
63
packages/ripple-keypairs/.gitignore
vendored
Normal file
63
packages/ripple-keypairs/.gitignore
vendored
Normal 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
|
||||
247
packages/ripple-keypairs/index.js
Normal file
247
packages/ripple-keypairs/index.js
Normal 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
|
||||
}
|
||||
73
packages/ripple-keypairs/package.json
Normal file
73
packages/ripple-keypairs/package.json
Normal 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"
|
||||
}
|
||||
49
packages/ripple-keypairs/test/keypairs-test.js
Normal file
49
packages/ripple-keypairs/test/keypairs-test.js
Normal 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);
|
||||
}
|
||||
});
|
||||
1
packages/ripple-keypairs/test/mocha.opts
Normal file
1
packages/ripple-keypairs/test/mocha.opts
Normal file
@@ -0,0 +1 @@
|
||||
--reporter spec --timeout 5000 --slow 500 --compilers js:babel/register
|
||||
68
packages/ripple-keypairs/utils.js
Normal file
68
packages/ripple-keypairs/utils.js
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user