Updates: * Add Pair#fromPublic(canonicalPubBytes) enabling verification only * Add generateValidatorKeys, validatorKeysFromSeed * Add seedBytes member to Pair

This commit is contained in:
Nicholas Dudfield
2015-07-22 14:20:00 +07:00
parent f43a2e1201
commit 848c65e43a
3 changed files with 126 additions and 26 deletions

View File

@@ -18,6 +18,22 @@
publicKey: 'ED5F5AC8B98974A3CA843326D9B88CEBD0560177B973EE0B149F782CFAA06DC66A' }') publicKey: 'ED5F5AC8B98974A3CA843326D9B88CEBD0560177B973EE0B149F782CFAA06DC66A' }')
``` ```
## Generate random validator keys
```js
> var generateValidatorKeys = require('ripple-keypairs').generateValidatorKeys;
> generateValidatorKeys();
{ seed: 'ssC7Y9LMKhuzFMVueaj2fnTuGLftA',
publicKey: 'n9MU2RsULUayZnWeLssjbMzVRPeVUUMgiPYTwe8eMgpdGDWp5t8C' }
```
## Derive validator keys from a seed
```js
> var validatorKeysFromSeed = require('ripple-keypairs').validatorKeysFromSeed;
> validatorKeysFromSeed('ssC7Y9LMKhuzFMVueaj2fnTuGLftA');
{ seed: 'ssC7Y9LMKhuzFMVueaj2fnTuGLftA',
publicKey: 'n9MU2RsULUayZnWeLssjbMzVRPeVUUMgiPYTwe8eMgpdGDWp5t8C' }
```
## Sign a transaction ## Sign a transaction
see [examples/sign-transaction.js](examples/sign-transaction.js) see [examples/sign-transaction.js](examples/sign-transaction.js)
```js ```js

View File

@@ -12,6 +12,7 @@ const rand = require('brorand');
// elliptic // elliptic
const secp256k1 = elliptic.ec('secp256k1'); const secp256k1 = elliptic.ec('secp256k1');
const Ed25519 = elliptic.eddsa('ed25519'); const Ed25519 = elliptic.eddsa('ed25519');
const {utils: {parseBytes}} = elliptic;
const { const {
bytesToHex, bytesToHex,
@@ -57,7 +58,7 @@ function findk256Key(bytes, discrim) {
/** /**
* @param {Object} [options] - * @param {Object} [options] -
* @param {Number} [options.accountIndex=0] - the account number to generate * @param {Number} [options.accountIndex=0] - the account number to generate
* @param {Boolean} [options.root=false] - generate root key-pair, * @param {Boolean} [options.validator=false] - generate root key-pair,
* as used by validators. * as used by validators.
* @return {new bn.js} - * @return {new bn.js} -
* *
@@ -65,7 +66,7 @@ function findk256Key(bytes, discrim) {
/* eslint-enable valid-jsdoc */ /* eslint-enable valid-jsdoc */
function derivek256Secret(seed, opts={}) { function derivek256Secret(seed, opts={}) {
const root = opts.root; const root = opts.validator;
const order = secp256k1.curve.n; const order = secp256k1.curve.n;
// This private generator represents the `root` private key, and is what's // This private generator represents the `root` private key, and is what's
@@ -100,7 +101,10 @@ function deriveEdKeyPairSeed(seed) {
/* --------------------------------- KEYPAIR -------------------------------- */ /* --------------------------------- KEYPAIR -------------------------------- */
function KeyPair() {} function KeyPair({seedBytes, pubBytes}) {
this.seedBytes = seedBytes;
this.pubKeyCanonicalBytes__ = pubBytes;
}
/* /*
@param {Array} message @param {Array} message
@@ -145,34 +149,48 @@ KeyPair.prototype.signHex = function(message) {
* @private * @private
* @param {Object} - key * @param {Object} - key
*/ */
function Ed25519Pair(key) { function Ed25519Pair() {
KeyPair.apply(this, arguments); KeyPair.apply(this, arguments);
this.key = key;
this.type = KeyType.ed25519; this.type = KeyType.ed25519;
} }
util.inherits(Ed25519Pair, KeyPair); util.inherits(Ed25519Pair, KeyPair);
/** /**
* @param {Seed} seed - A 128 bit seed * @param {Array<Number>} seedBytes - A 128 bit seed
* @return {Ed25519Pair} key pair * @return {Ed25519Pair} key pair
*/ */
Ed25519Pair.fromSeed = function(seed) { Ed25519Pair.fromSeed = function(seedBytes) {
const seed256 = deriveEdKeyPairSeed(seed); return new Ed25519Pair({seedBytes});
const derived = Ed25519.keyFromSecret(seed256);
return new Ed25519Pair(derived);
}; };
/**
* @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() { hasCachedProperty(Ed25519Pair, 'pubKeyCanonicalBytes', function() {
return [0xED].concat(this.key.pubBytes()); return [0xED].concat(this.key().pubBytes());
}); });
Ed25519Pair.prototype.sign = function(message) { Ed25519Pair.prototype.sign = function(message) {
return this.key.sign(message).toBytes(); return this.key().sign(message).toBytes();
}; };
Ed25519Pair.prototype.verify = function(message, signature) { Ed25519Pair.prototype.verify = function(message, signature) {
return this.key.verify(message, signature); return this.key().verify(message, signature);
}; };
/* ---------------------------- SECP256K1 KEYPAIR --------------------------- */ /* ---------------------------- SECP256K1 KEYPAIR --------------------------- */
@@ -181,20 +199,29 @@ Ed25519Pair.prototype.verify = function(message, signature) {
* @class * @class
* @private * @private
*/ */
function K256Pair(key) { function K256Pair({validator}) {
KeyPair.apply(this, arguments); KeyPair.apply(this, arguments);
this.type = KeyType.secp256k1; this.type = KeyType.secp256k1;
this.key = key; this.validator = validator;
} }
util.inherits(K256Pair, KeyPair); util.inherits(K256Pair, KeyPair);
K256Pair.fromSeed = function(seed) { K256Pair.fromSeed = function(seedBytes, opts={}) {
return new K256Pair(secp256k1.keyFromPrivate(derivek256Secret(seed))); 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() { hasCachedProperty(K256Pair, 'pubKeyCanonicalBytes', function() {
return this.key.getPublic(/*compact*/ true, /*enc*/ 'bytes'); return this.key().getPublic().encodeCompressed();
}); });
/* /*
@@ -206,7 +233,7 @@ K256Pair.prototype.sign = function(message) {
K256Pair.prototype._createSignature = function(message) { K256Pair.prototype._createSignature = function(message) {
// The key.sign message silently discards options // The key.sign message silently discards options
return secp256k1.sign(this.hashMessage(message), this.key, {canonical: true}); return this.key().sign(this.hashMessage(message), {canonical: true});
}; };
/* /*
@@ -223,16 +250,16 @@ K256Pair.prototype.hashMessage = function(message) {
*/ */
K256Pair.prototype.verify = function(message, signature) { K256Pair.prototype.verify = function(message, signature) {
try { try {
return this.key.verify(this.hashMessage(message), signature); return this.key().verify(this.hashMessage(message), signature);
} catch (e) { } catch (e) {
return false; return false;
} }
}; };
function keyPairFromSeed(seedString) { function keyPairFromSeed(seedString, options) {
const decoded = codec.decodeSeed(seedString); const decoded = codec.decodeSeed(seedString);
const pair = decoded.type === 'ed25519' ? Ed25519Pair : K256Pair; const Pair = decoded.type === 'ed25519' ? Ed25519Pair : K256Pair;
return pair.fromSeed(decoded.bytes); return Pair.fromSeed(decoded.bytes, options);
} }
function deriveWallet(type, seedBytes) { function deriveWallet(type, seedBytes) {
@@ -256,7 +283,8 @@ function deriveWallet(type, seedBytes) {
}; };
} }
function generateWallet({type='secp256k1', randGen=rand}) { function generateWallet(opts={}) {
const {type='secp256k1', randGen=rand} = opts;
const seedBytes = randGen(16); const seedBytes = randGen(16);
return deriveWallet(type, seedBytes); return deriveWallet(type, seedBytes);
} }
@@ -266,6 +294,25 @@ function walletFromSeed(seed) {
return deriveWallet(type, bytes); return deriveWallet(type, bytes);
} }
function deriveValidator(seedBytes) {
const pair = K256Pair.fromSeed(seedBytes, {validator: true});
return {
seed: codec.encodeK256Seed(seedBytes),
publicKey: codec.encodeNodePublic(pair.pubKeyCanonicalBytes())
};
}
function generateValidatorKeys(opts={}) {
const {randGen=rand} = opts;
return deriveValidator(randGen(16));
}
function validatorKeysFromSeed(seed) {
const {type, bytes} = codec.decodeSeed(seed);
assert(type == KeyType.secp256k1);
return deriveValidator(bytes);
}
module.exports = { module.exports = {
KeyPair, KeyPair,
K256Pair, K256Pair,
@@ -275,5 +322,7 @@ module.exports = {
createAccountID, createAccountID,
keyPairFromSeed, keyPairFromSeed,
generateWallet, generateWallet,
walletFromSeed generateValidatorKeys,
walletFromSeed,
validatorKeysFromSeed
}; };

View File

@@ -12,7 +12,9 @@ const {
Ed25519Pair, Ed25519Pair,
keyPairFromSeed, keyPairFromSeed,
generateWallet, generateWallet,
walletFromSeed walletFromSeed,
generateValidatorKeys,
validatorKeysFromSeed
} = keypairs; } = keypairs;
const {SerializedObject} = require('ripple-lib'); const {SerializedObject} = require('ripple-lib');
@@ -45,6 +47,13 @@ describe('ED25519Pair', function() {
pair = Ed25519Pair.fromSeed(seedFromPhrase('niq')); pair = Ed25519Pair.fromSeed(seedFromPhrase('niq'));
}); });
it('can be constructed from a pulbic key to verify a txn', function() {
const sig = pair.sign(FIXTURES.message);
const key = Ed25519Pair.fromPublic(pair.pubKeyCanonicalBytes());
assert(key.verify(FIXTURES.message, sig));
assert(!key.verify(FIXTURES.message.concat(0), sig));
});
it('has a String member `type` equal to KeyPair.ed25519 constant', it('has a String member `type` equal to KeyPair.ed25519 constant',
function() { function() {
assert.equal(pair.type, KeyType.ed25519); assert.equal(pair.type, KeyType.ed25519);
@@ -121,6 +130,32 @@ describe('generateWallet', function() {
}); });
}); });
describe('generateValidatorKeys', function() {
function randGen(len) {
return _.fill(Array(len), 0);
}
it('can generate secp256k1 validator keys', function() {
/*
rippled validation_create 00000000000000000000000000000000
{
"result" : {
"status" : "success",
"validation_key" : "A A A A A A A A A A A A",
"validation_public_key" : "n9LPxYzbDpWBZ1bC3J3Fdkgqoa3FEhVKCnS8yKp7RFQFwuvd8Q2c",
"validation_seed" : "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
}
}
*/
const expected = {
seed: 'sp6JS7f14BuwFY8Mw6bTtLKWauoUs',
publicKey: 'n9LPxYzbDpWBZ1bC3J3Fdkgqoa3FEhVKCnS8yKp7RFQFwuvd8Q2c'
};
const actual = generateValidatorKeys({randGen});
assert.deepEqual(actual, expected);
assert.deepEqual(validatorKeysFromSeed(actual.seed), expected);
});
});
describe('K256Pair', function() { describe('K256Pair', function() {
describe('generated tests', function() { describe('generated tests', function() {
/*eslint-disable max-len*/ /*eslint-disable max-len*/