mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 04:05:52 +00:00
Updates: * Add Pair#fromPublic(canonicalPubBytes) enabling verification only * Add generateValidatorKeys, validatorKeysFromSeed * Add seedBytes member to Pair
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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*/
|
||||||
|
|||||||
Reference in New Issue
Block a user