Switch to new API

This commit is contained in:
Chris Clark
2015-09-21 15:09:54 -07:00
parent 5198a98736
commit de8ea104d9
11 changed files with 135 additions and 819 deletions

View File

@@ -4,111 +4,43 @@ An implementation of ripple keypairs & wallet generation using
[elliptic](https://github.com/indutny/elliptic) which supports rfc6979 and
eddsa deterministic signatures.
## Generate a random wallet
```js
> var generateWallet = require('ripple-keypairs').generateWallet;
> generateWallet({type: 'ed25519'});
{ seed: 'sEd7t79mzn2dwy3vvpvRmaaLbLhvme6',
accountID: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
publicKey: 'ED5F5AC8B98974A3CA843326D9B88CEBD0560177B973EE0B149F782CFAA06DC66A' }
```
## Derive a wallet from a seed
```js
> var walletFromSeed = require('ripple-keypairs').walletFromSeed;
> walletFromSeed('sEd7t79mzn2dwy3vvpvRmaaLbLhvme6');
{ seed: 'sEd7t79mzn2dwy3vvpvRmaaLbLhvme6',
accountID: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
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' }
```
## Derive accountID matching a validator public key (aka public generator)
```js
> var nodePublicAccountID = require('ripple-keypairs').nodePublicAccountID;
> nodePublicAccountID('n9MXXueo837zYH36DvMc13BwHcqtfAWNJY5czWVbp7uYTj7x17TH')
'rhcfR9Cg98qCxHpCcPBmMonbDBXo84wyTn'
```
## Sign a transaction
see [examples/sign-transaction.js](examples/sign-transaction.js)
```js
var Transaction = require('ripple-lib').Transaction;
var keyPairFromSeed = require('../').keyPairFromSeed;
var SIGNING_PREFIX = [0x53, 0x54, 0x58, 0x00];
function prettyJSON(obj) {
return JSON.stringify(obj, undefined, 2);
}
function signingData(tx) {
return SIGNING_PREFIX.concat(tx.serialize().buffer);
}
function signTxJson(seed, json) {
var keyPair = keyPairFromSeed(seed);
var tx = Transaction.from_json(json);
var tx_json = tx.tx_json;
tx_json.SigningPubKey = keyPair.pubKeyHex();
tx_json.TxnSignature = keyPair.signHex(signingData(tx));
var serialized = tx.serialize();
var id = tx.hash('HASH_TX_ID', /* Uint256: */ false , /* pre: */ serialized);
return {
hash: id,
tx_blob: serialized.to_hex(),
tx_json: tx_json
};
}
var seed = 'sEd7t79mzn2dwy3vvpvRmaaLbLhvme6';
var tx_json = {
Account: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
Amount: '1000',
Destination: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
Fee: '10',
Flags: 2147483648,
Sequence: 1,
TransactionType: 'Payment',
};
console.log(prettyJSON(signTxJson(seed, tx_json)));
## API Methods
```
```json
{
"hash": "1B6B9652F95D826C9D9C3FD30F208130433CBC7C48C10F6EC2CC5E4A85D167FF",
"tx_blob": "120000228000000024000000016140000000000003E868400000000000000A7321ED5F5AC8B98974A3CA843326D9B88CEBD0560177B973EE0B149F782CFAA06DC66A74407D0825105229923B261C716F225181E5A66A34C9480446ABE64818A673954CC34D42946CD82172814F037976AE3800BDE983624A45FCDBED4A548C4650BF900D81145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8",
"tx_json": {
"Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ",
"Amount": "1000",
"Destination": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Fee": "10",
"Flags": 2147483648,
"Sequence": 1,
"TransactionType": "Payment",
"SigningPubKey": "ED5F5AC8B98974A3CA843326D9B88CEBD0560177B973EE0B149F782CFAA06DC66A",
"TxnSignature": "7D0825105229923B261C716F225181E5A66A34C9480446ABE64818A673954CC34D42946CD82172814F037976AE3800BDE983624A45FCDBED4A548C4650BF900D"
}
}
generateSeed({entropy?: Array<integer>, algorithm?: string}) -> string
```
Generate a seed that can be used to generate keypairs. Entropy can be provided as an array of 32-bit integers. If provided, it must be at least 16 words long. If not provided, entropy will be automatically generated. The "algorithm" defaults to "secp256k1", but can also be set to "ed25519". The result is a seed encoded in base58, starting with "s".
```
deriveKeypair(seed: string) -> {privateKey: string, publicKey: string}
```
Derive a public and private key from a seed. The keys are represented as hexadecimal strings. A single metadata byte is prepended so the keys are 33 bytes long.
```
sign(message: string, privateKey: string) -> string
```
Sign an arbitrary message with a private key. Returns the signature as a hexadecimal string.
```
verify(message: string, signature: string, publicKey: string) -> boolean
```
Verify a signature for a given message and public key. Returns true if the signature is valid, false otherwise.
```
deriveAddress(publicKey: string) -> string
```
Derive a Ripple address from a public key.
```
deriveNodeAddress(publicKey: string) -> string
```
Derive a node address from a public key.
## Generate a random Ripple address
```
const seed = generateSeed();
const keypair = deriveKeypair(seed);
const address = deriveAddress(keypair.publicKey);
```

View File

@@ -1,45 +0,0 @@
var Transaction = require('ripple-lib').Transaction;
var keyPairFromSeed = require('../').keyPairFromSeed;
var SIGNING_PREFIX = [0x53, 0x54, 0x58, 0x00];
function prettyJSON(obj) {
return JSON.stringify(obj, undefined, 2);
}
function signingData(tx) {
return SIGNING_PREFIX.concat(tx.serialize().buffer);
}
function signTxJson(seed, json) {
var keyPair = keyPairFromSeed(seed);
var tx = Transaction.from_json(json);
var tx_json = tx.tx_json;
tx_json.SigningPubKey = keyPair.pubKeyHex();
tx_json.TxnSignature = keyPair.signHex(signingData(tx));
var serialized = tx.serialize();
var id = tx.hash('HASH_TX_ID', /* Uint256: */ false , /* pre: */ serialized);
return {
hash: id,
tx_blob: serialized.to_hex(),
tx_json: tx_json
};
}
var seed = 'sEd7t79mzn2dwy3vvpvRmaaLbLhvme6';
var tx_json = {
Account: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
Amount: '1000',
Destination: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
Fee: '10',
Flags: 2147483648,
Sequence: 1,
TransactionType: 'Payment',
};
console.log('unsigned', prettyJSON(tx_json));
console.log('signed', prettyJSON(signTxJson(seed, tx_json)));

View File

@@ -1,13 +1,12 @@
{
"name": "ripple-keypairs",
"version": "0.8.1",
"version": "0.9.0",
"description": "ripple key pairs",
"files": [
"distrib/npm/*",
"bin/*",
"build/*",
"test/*",
"Gulpfile.js"
"test/*"
],
"main": "distrib/npm/",
"directories": {

View File

@@ -1,113 +0,0 @@
'use strict';
const assert = require('assert');
const brorand = require('brorand');
const hashjs = require('hash.js');
const elliptic = require('elliptic');
const Ed25519 = elliptic.eddsa('ed25519');
const Secp256k1 = elliptic.ec('secp256k1');
const addressCodec = require('ripple-address-codec');
const derivePrivateKey = require('./secp256k1').derivePrivateKey;
const accountPublicFromPublicGenerator = require('./secp256k1')
.accountPublicFromPublicGenerator;
const utils = require('./utils');
const hexToBytes = utils.hexToBytes;
const bytesToHex = utils.bytesToHex;
function generateSeed(options = {}) {
assert(!options.entropy || options.entropy.length >= 16, 'entropy too short');
const entropy = options.entropy ? options.entropy.slice(0, 16) : brorand(16);
const type = options.algorithm === 'ed25519' ? 'ed25519' : 'secp256k1';
return addressCodec.encodeSeed(entropy, type);
}
function hash(message) {
return hashjs.sha512().update(message).digest().slice(0, 32);
}
const secp256k1 = {
deriveKeypair: function(entropy, options) {
const prefix = '00';
const privateKey = prefix + derivePrivateKey(entropy, options)
.toString(16, 64).toUpperCase();
const publicKey = bytesToHex(Secp256k1.keyFromPrivate(
privateKey.slice(2)).getPublic().encodeCompressed());
return {privateKey, publicKey};
},
sign: function(message, privateKey) {
return bytesToHex(Secp256k1.sign(hash(message),
hexToBytes(privateKey), {canonical: true}).toDER());
},
verify: function(message, signature, publicKey) {
return Secp256k1.verify(hash(message), signature, hexToBytes(publicKey));
}
};
const ed25519 = {
deriveKeypair: function(entropy) {
const prefix = 'ED';
const rawPrivateKey = hash(entropy);
const privateKey = prefix + bytesToHex(rawPrivateKey);
const publicKey = prefix + bytesToHex(
Ed25519.keyFromSecret(rawPrivateKey).pubBytes());
return {privateKey, publicKey};
},
sign: function(message, privateKey) {
return bytesToHex(Ed25519.sign(
message, hexToBytes(privateKey).slice(1)).toBytes());
},
verify: function(message, signature, publicKey) {
return Ed25519.verify(message, hexToBytes(signature),
hexToBytes(publicKey).slice(1));
}
};
function select(algorithm) {
const methods = {'ecdsa-secp256k1': secp256k1, ed25519};
return methods[algorithm];
}
function deriveKeypair(seed, options) {
const decoded = addressCodec.decodeSeed(seed);
const algorithm = decoded.type === 'ed25519' ? 'ed25519' : 'ecdsa-secp256k1';
return select(algorithm).deriveKeypair(decoded.bytes, options);
}
function getAlgorithmFromKey(key) {
const bytes = hexToBytes(key);
return (bytes.length === 33 && bytes[0] === 0xED) ?
'ed25519' : 'ecdsa-secp256k1';
}
function sign(message, privateKey) {
const algorithm = getAlgorithmFromKey(privateKey);
return select(algorithm).sign(message, privateKey);
}
function verify(message, signature, publicKey) {
const algorithm = getAlgorithmFromKey(publicKey);
return select(algorithm).verify(message, signature, publicKey);
}
function deriveAddressFromBytes(publicKeyBytes) {
return addressCodec.encodeAccountID(
utils.computePublicKeyHash(publicKeyBytes));
}
function deriveAddress(publicKey) {
return deriveAddressFromBytes(hexToBytes(publicKey));
}
function deriveNodeAddress(publicKey) {
const generatorBytes = addressCodec.decodeNodePublic(publicKey);
const accountPublicBytes = accountPublicFromPublicGenerator(generatorBytes);
return deriveAddressFromBytes(accountPublicBytes);
}
module.exports = {
generateSeed,
deriveKeypair,
sign,
verify,
deriveAddress,
deriveNodeAddress
};

View File

@@ -1,63 +0,0 @@
'use strict';
const elliptic = require('elliptic');
const {utils: {parseBytes}} = elliptic;
const Ed25519 = elliptic.eddsa('ed25519');
const {KeyPair, KeyType} = require('./keypair');
const {Sha512, cached} = require('./utils');
/*
@param {Array} seed bytes
*/
function deriveEdKeyPairSecret(seed) {
return new Sha512().add(seed).first256();
}
class Ed25519Pair extends KeyPair {
constructor(options) {
super(options);
this.type = KeyType.ed25519;
}
/**
* @param {String|Array} publicKey - public key in canonical form
* (0xED + 32 bytes)
* @return {Ed25519Pair} key pair
*/
static fromPublic(publicKey) {
return new Ed25519Pair({pubBytes: parseBytes(publicKey)});
}
/**
* @param {Array<Number>} seedBytes - A 128 bit seed
* @return {Ed25519Pair} key pair
*/
static fromSeed(seedBytes) {
return new Ed25519Pair({seedBytes});
}
sign(message) {
return this.key().sign(message).toBytes();
}
verify(message, signature) {
return this.key().verify(message, signature);
}
@cached
pubKeyCanonicalBytes() {
return [0xED].concat(this.key().pubBytes());
}
@cached
key() {
if (this.seedBytes) {
return Ed25519.keyFromSecret(deriveEdKeyPairSecret(this.seedBytes));
}
return Ed25519.keyFromPublic(this.pubKeyCanonicalBytes().slice(1));
}
}
module.exports = {
Ed25519Pair
};

View File

@@ -1,105 +1,113 @@
'use strict';
const assert = require('assert');
const brorand = require('brorand');
const codec = require('ripple-address-codec');
const hashjs = require('hash.js');
const elliptic = require('elliptic');
const Ed25519 = elliptic.eddsa('ed25519');
const Secp256k1 = elliptic.ec('secp256k1');
const addressCodec = require('ripple-address-codec');
const derivePrivateKey = require('./secp256k1').derivePrivateKey;
const accountPublicFromPublicGenerator = require('./secp256k1')
.accountPublicFromPublicGenerator;
const utils = require('./utils');
const hexToBytes = utils.hexToBytes;
const bytesToHex = utils.bytesToHex;
const {seedFromPhrase, computePublicKeyHash} = require('./utils');
const {KeyPair, KeyType} = require('./keypair');
const {Ed25519Pair} = require('./ed25519');
const {K256Pair, accountPublicFromPublicGenerator} = require('./secp256k1');
const {decodeSeed, encodeNodePublic, decodeNodePublic, encodeAccountID} = codec;
function parseSeed(seed, type = KeyType.secp256k1) {
if (typeof seed !== 'string') {
return {bytes: seed, type};
}
return decodeSeed(seed);
function generateSeed(options = {}) {
assert(!options.entropy || options.entropy.length >= 16, 'entropy too short');
const entropy = options.entropy ? options.entropy.slice(0, 16) : brorand(16);
const type = options.algorithm === 'ed25519' ? 'ed25519' : 'secp256k1';
return addressCodec.encodeSeed(entropy, type);
}
KeyPair.fromSeed = function(seed, type = KeyType.secp256k1, options) {
if (typeof seed === 'string') {
const decoded = decodeSeed(seed);
const optionsArg = type;
return this.fromSeed(decoded.bytes, decoded.type, optionsArg);
}
function hash(message) {
return hashjs.sha512().update(message).digest().slice(0, 32);
}
assert(type === KeyType.secp256k1 || type === KeyType.ed25519);
const Pair = type === 'ed25519' ? Ed25519Pair : K256Pair;
return Pair.fromSeed(seed, options);
const secp256k1 = {
deriveKeypair: function(entropy, options) {
const prefix = '00';
const privateKey = prefix + derivePrivateKey(entropy, options)
.toString(16, 64).toUpperCase();
const publicKey = bytesToHex(Secp256k1.keyFromPrivate(
privateKey.slice(2)).getPublic().encodeCompressed());
return {privateKey, publicKey};
},
sign: function(message, privateKey) {
return bytesToHex(Secp256k1.sign(hash(message),
hexToBytes(privateKey), {canonical: true}).toDER());
},
verify: function(message, signature, publicKey) {
return Secp256k1.verify(hash(message), signature, hexToBytes(publicKey));
}
};
function deriveWallet(seedBytes, type) {
const pair = KeyPair.fromSeed(seedBytes, type);
const ed25519 = {
deriveKeypair: function(entropy) {
const prefix = 'ED';
const rawPrivateKey = hash(entropy);
const privateKey = prefix + bytesToHex(rawPrivateKey);
const publicKey = prefix + bytesToHex(
Ed25519.keyFromSecret(rawPrivateKey).pubBytes());
return {privateKey, publicKey};
},
sign: function(message, privateKey) {
return bytesToHex(Ed25519.sign(
message, hexToBytes(privateKey).slice(1)).toBytes());
},
verify: function(message, signature, publicKey) {
return Ed25519.verify(message, hexToBytes(signature),
hexToBytes(publicKey).slice(1));
}
};
return {
seed: pair.seed(),
accountID: pair.accountID(),
publicKey: pair.pubKeyHex()
};
function select(algorithm) {
const methods = {'ecdsa-secp256k1': secp256k1, ed25519};
return methods[algorithm];
}
function deriveValidator(seedBytes) {
const pair = K256Pair.fromSeed(seedBytes, {validator: true});
return {
seed: pair.seed(),
publicKey: encodeNodePublic(pair.pubKeyCanonicalBytes())
};
function deriveKeypair(seed, options) {
const decoded = addressCodec.decodeSeed(seed);
const algorithm = decoded.type === 'ed25519' ? 'ed25519' : 'ecdsa-secp256k1';
return select(algorithm).deriveKeypair(decoded.bytes, options);
}
function generateWallet(opts = {}) {
const {type = 'secp256k1', random = brorand} = opts;
const seedBytes = random(16);
return deriveWallet(seedBytes, type);
function getAlgorithmFromKey(key) {
const bytes = hexToBytes(key);
return (bytes.length === 33 && bytes[0] === 0xED) ?
'ed25519' : 'ecdsa-secp256k1';
}
function walletFromSeed(seed, seedType) {
const {type, bytes} = parseSeed(seed, seedType);
return deriveWallet(bytes, type);
function sign(message, privateKey) {
const algorithm = getAlgorithmFromKey(privateKey);
return select(algorithm).sign(message, privateKey);
}
function walletFromPhrase(phrase, type) {
return walletFromSeed(seedFromPhrase(phrase), type);
function verify(message, signature, publicKey) {
const algorithm = getAlgorithmFromKey(publicKey);
return select(algorithm).verify(message, signature, publicKey);
}
function generateValidatorKeys(opts = {}) {
const {random = brorand} = opts;
return deriveValidator(random(16));
function deriveAddressFromBytes(publicKeyBytes) {
return addressCodec.encodeAccountID(
utils.computePublicKeyHash(publicKeyBytes));
}
function nodePublicAccountID(publicKey) {
const generatorBytes = decodeNodePublic(publicKey);
function deriveAddress(publicKey) {
return deriveAddressFromBytes(hexToBytes(publicKey));
}
function deriveNodeAddress(publicKey) {
const generatorBytes = addressCodec.decodeNodePublic(publicKey);
const accountPublicBytes = accountPublicFromPublicGenerator(generatorBytes);
return encodeAccountID(computePublicKeyHash(accountPublicBytes));
}
function validatorKeysFromSeed(seed, seedType) {
const {type, bytes} = parseSeed(seed, seedType);
assert(type === KeyType.secp256k1);
return deriveValidator(bytes);
}
function validatorKeysFromPhrase(phrase) {
return deriveValidator(seedFromPhrase(phrase));
}
function keyPairFromSeed(seedString, options) {
return KeyPair.fromSeed(seedString, options);
return deriveAddressFromBytes(accountPublicBytes);
}
module.exports = {
KeyPair,
K256Pair,
Ed25519Pair,
KeyType,
seedFromPhrase,
computePublicKeyHash,
keyPairFromSeed,
generateWallet,
generateValidatorKeys,
walletFromSeed,
walletFromPhrase,
validatorKeysFromSeed,
validatorKeysFromPhrase,
nodePublicAccountID
generateSeed,
deriveKeypair,
sign,
verify,
deriveAddress,
deriveNodeAddress
};

View File

@@ -1,69 +0,0 @@
'use strict';
const codec = require('ripple-address-codec');
const {
bytesToHex,
cached,
isVirtual,
computePublicKeyHash
} = require('./utils');
const KeyType = {
secp256k1: 'secp256k1',
ed25519: 'ed25519'
};
class KeyPair {
constructor({seedBytes, pubBytes}) {
this.seedBytes = seedBytes;
this._pubKeyCanonicalBytes = pubBytes;
}
/*
* @param {Array} message
*/
@isVirtual
sign() {}
/*
* @param {Array<Byte>} message
* @param {Array<Byte>} signature
*/
@isVirtual
verify() {}
/*
* @return {Array<Byte>} of bytes, in canonical form, for signing
*/
@isVirtual
pubKeyCanonicalBytes() {}
@cached
pubKeyHex() {
return bytesToHex(this.pubKeyCanonicalBytes());
}
@cached
accountBytes() {
return computePublicKeyHash(this.pubKeyCanonicalBytes());
}
@cached
accountID() {
return codec.encodeAccountID(this.accountBytes());
}
@cached
seed() {
return codec.encodeSeed(this.seedBytes, this.type);
}
signHex(message) {
return bytesToHex(this.sign(message));
}
}
module.exports = {
KeyPair,
KeyType
};

View File

@@ -2,9 +2,7 @@
const elliptic = require('elliptic');
const secp256k1 = elliptic.ec('secp256k1');
const hashjs = require('hash.js');
const {KeyPair, KeyType} = require('./keypair');
const {Sha512, cached} = require('./utils');
const Sha512 = require('./sha512');
function deriveScalar(bytes, discrim) {
const order = secp256k1.curve.n;
@@ -61,69 +59,7 @@ function accountPublicFromPublicGenerator(publicGenBytes) {
return offset.encodeCompressed();
}
class K256Pair extends KeyPair {
constructor(options) {
super(options);
this.type = KeyType.secp256k1;
this.validator = options.validator;
}
static fromSeed(seedBytes, opts = {}) {
return new K256Pair({seedBytes, validator: opts.validator});
}
/*
@param {Array<Byte>} message (bytes)
*/
sign(message) {
return this._createSignature(message).toDER();
}
/*
@param {Array<Byte>} message - bytes
@param {Array<Byte>} signature - DER encoded signature bytes
*/
verify(message, signature) {
try {
return this.key().verify(this.hashMessage(message), signature);
/* eslint-disable no-catch-shadow */
} catch (e) {
/* eslint-enable no-catch-shadow */
return false;
}
}
@cached
pubKeyCanonicalBytes() {
return this.key().getPublic().encodeCompressed();
}
_createSignature(message) {
return this.key().sign(this.hashMessage(message), {canonical: true});
}
/*
@param {Array<Byte>} message - (bytes)
@return {Array<Byte>} - 256 bit hash of the message
*/
hashMessage(message) {
return hashjs.sha512().update(message).digest().slice(0, 32);
}
@cached
key() {
if (this.seedBytes) {
const options = {validator: this.validator};
return secp256k1.keyFromPrivate(
derivePrivateKey(this.seedBytes, options));
}
return secp256k1.keyFromPublic(this.pubKeyCanonicalBytes());
}
}
module.exports = {
K256Pair,
derivePrivateKey,
accountPublicFromPublicGenerator
};

View File

@@ -2,39 +2,6 @@
const assert = require('assert');
const hashjs = require('hash.js');
const BN = require('bn.js');
const Sha512 = require('./sha512');
function unused() {}
function isVirtual(_, __, descriptor) {
unused(_, __);
descriptor.value = function() {
throw new Error('virtual method not implemented ');
};
}
function cached(_, name, descriptor) {
unused(_);
const computer = descriptor.value;
const key = '_' + name;
descriptor.value = function() {
let value = this[key];
if (value === undefined) {
value = this[key] = computer.call(this);
}
return value;
};
}
function toGenericArray(sequence) {
const generic = [];
for (let i = 0; i < sequence.length; i++) {
generic.push(sequence[i]);
}
return generic;
}
function bytesToHex(a) {
return a.map(function(byteValue) {
@@ -59,12 +26,8 @@ function seedFromPhrase(phrase) {
}
module.exports = {
cached,
bytesToHex,
hexToBytes,
computePublicKeyHash,
isVirtual,
seedFromPhrase,
Sha512,
toGenericArray
seedFromPhrase
};

View File

@@ -1,7 +1,7 @@
'use strict';
const assert = require('assert');
const fixtures = require('./fixtures/api.json');
const api = require('../src/api');
const api = require('../src');
const decodeSeed = require('ripple-address-codec').decodeSeed;
const entropy = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
@@ -82,4 +82,11 @@ describe('api', () => {
const y = 'rU7bM9ENDkybaxNrefAVjdLTyNLuue1KaJ';
assert.strictEqual(api.deriveNodeAddress(x), y);
});
it('Random Address', () => {
const seed = api.generateSeed();
const keypair = api.deriveKeypair(seed);
const address = api.deriveAddress(keypair.publicKey);
assert(address[0] === 'r');
});
});

View File

@@ -1,239 +0,0 @@
'use strict';
const codec = require('ripple-address-codec');
const assert = require('assert-diff');
const utils = require('../src/utils');
const keypairs = require('../src');
const _ = require('lodash');
const {
KeyType,
K256Pair,
seedFromPhrase,
Ed25519Pair,
keyPairFromSeed,
generateWallet,
walletFromSeed,
walletFromPhrase,
generateValidatorKeys,
validatorKeysFromSeed,
validatorKeysFromPhrase,
nodePublicAccountID
} = keypairs;
const {SerializedObject} = require('ripple-lib');
const TX_HASH_PREFIX_SIGN = [0x53, 0x54, 0x58, 0x00];
const FIXTURES = {
message: [0xB, 0xE, 0xE, 0xF],
tx_json: {
Account: 'rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN',
Amount: '1000',
Destination: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
Fee: '10',
Flags: 2147483648,
Sequence: 1,
SigningPubKey: 'EDD3993CDC6647896C455F136648B7750' +
'723B011475547AF60691AA3D7438E021D',
TransactionType: 'Payment',
expected_sig: 'C3646313B08EED6AF4392261A31B961F' +
'10C66CB733DB7F6CD9EAB079857834C8' +
'B0334270A2C037E63CDCCC1932E08328' +
'82B7B7066ECD2FAEDEB4A83DF8AE6303'
}
};
describe('ED25519Pair', function() {
let pair;
before(function() {
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',
function() {
assert.equal(pair.type, KeyType.ed25519);
});
it('has a public key representation beginning with ED', function() {
const pub_hex = pair.pubKeyHex();
assert(pub_hex.length === 66);
assert(pub_hex.slice(0, 2) === 'ED');
});
it('derives the same keypair for a given passphrase as rippled', function() {
const pub_hex = pair.pubKeyHex();
const target_hex = 'EDD3993CDC6647896C455F136648B7750' +
'723B011475547AF60691AA3D7438E021D';
assert.equal(pub_hex, target_hex);
});
it('generates the same account_id as rippled for a given keypair',
function() {
assert.equal(pair.accountID(),
'rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN');
});
it('creates signatures that are a function of secret/message', function() {
const signature = pair.sign(FIXTURES.message);
assert(Array.isArray(signature));
assert(pair.verify(FIXTURES.message, signature));
});
it('signs transactions exactly as rippled', function() {
const so = SerializedObject.from_json(FIXTURES.tx_json);
const message = TX_HASH_PREFIX_SIGN.concat(so.buffer);
const sig = pair.signHex(message);
assert.equal(sig, FIXTURES.tx_json.expected_sig);
});
});
describe('keyPairFromSeed', function() {
it('returns an Ed25519Pair from an ed25519 seed', function() {
const pair = keyPairFromSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2');
assert(pair instanceof Ed25519Pair);
});
it('returns a K256Pair from an secp256k1 (default) seed', function() {
const pair = keyPairFromSeed('sn259rEFXrQrWyx3Q7XneWcwV6dfL');
assert(pair instanceof K256Pair);
});
it('can be intantiated with an array of bytes', function() {
const seed = 'sn259rEFXrQrWyx3Q7XneWcwV6dfL';
const {bytes} = codec.decodeSeed(seed);
const pair = keyPairFromSeed(bytes);
assert(pair instanceof K256Pair);
assert.equal(pair.seed(), seed);
});
});
describe('walletFromPhrase', function() {
it('can gan generate ed25519 wallets', function() {
const expected = {
seed: 'sEd7rBGm5kxzauRTAV2hbsNz7N45X91',
accountID: 'rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN',
publicKey:
'ED' +
'D3993CDC6647896C455F136648B7750723B011475547AF60691AA3D7438E021D'
};
const wallet = walletFromPhrase('niq', 'ed25519');
assert.deepEqual(wallet, expected);
});
it('generates secp256k1 wallets by default', function() {
const expected = {
seed: 'shQUG1pmPYrcnSUGeuJFJTA1b3JSL',
accountID: 'rNvfq2SVbCiio1zkN5WwLQW8CHgy2dUoQi',
publicKey:
'02' +
'1E788CDEB9104C9179C3869250A89999C1AFF92D2C3FF7925A1696835EA3D840'
};
const wallet = walletFromPhrase('niq');
assert.deepEqual(wallet, expected);
});
});
describe('validatorKeysFromPhrase', function() {
it('generates keys used by peer nodes/validators', function() {
const expected = {
seed: 'shQUG1pmPYrcnSUGeuJFJTA1b3JSL',
publicKey: 'n9KNees3ippJvi7ZT1GqHMCmEmmkCVPxQRPfU5tPzmg9MtWevpjP'
};
const wallet = validatorKeysFromPhrase('niq');
assert.deepEqual(wallet, expected);
});
});
describe('generateWallet', function() {
function random(len) {
return _.fill(Array(len), 0);
}
it('can generate ed25519 wallets', function() {
const expected = {
seed: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
accountID: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
publicKey:
'ED' +
'1A7C082846CFF58FF9A892BA4BA2593151CCF1DBA59F37714CC9ED39824AF85F'
};
const actual = generateWallet({type: 'ed25519', random});
assert.deepEqual(actual, expected);
assert.deepEqual(walletFromSeed(actual.seed), expected);
});
it('can generate secp256k1 wallets (by default)', function() {
const expected = {
seed: 'sp6JS7f14BuwFY8Mw6bTtLKWauoUs',
accountID: 'rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f',
publicKey:
'03' +
'90A196799EE412284A5D80BF78C3E84CBB80E1437A0AECD9ADF94D7FEAAFA284'
};
const actual = generateWallet({type: undefined, random});
assert.deepEqual(actual, expected);
assert.deepEqual(walletFromSeed(actual.seed), expected);
});
});
describe('generateValidatorKeys', function() {
function random(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({random});
assert.deepEqual(actual, expected);
assert.deepEqual(validatorKeysFromSeed(actual.seed), expected);
});
it('can generate the correct accountID from validator public key', () => {
const accountID = 'rhcfR9Cg98qCxHpCcPBmMonbDBXo84wyTn';
const validatorPublic =
'n9MXXueo837zYH36DvMc13BwHcqtfAWNJY5czWVbp7uYTj7x17TH';
assert.equal(nodePublicAccountID(validatorPublic), accountID);
});
});
describe('K256Pair', function() {
describe('generated tests', function() {
/* eslint-disable max-len */
const expected = [
'30440220312B2E0894B81A2E070ACE566C5DFC70CDD18E67D44E2CFEF2EB5495F7DE2DAC02205E155C0019502948C265209DFDD7D84C4A05BD2C38CEE6ECD7C33E9C9B12BEC2',
'304402202A5860A12C15EBB8E91AA83F8E19D85D4AC05B272FC0C4083519339A7A76F2B802200852F9889E1284CF407DC7F73D646E62044C5AB432EAEF3FFF3F6F8EE9A0F24C',
'3045022100B1658C88D1860D9F8BEB25B79B3E5137BBC2C382D08FE7A068FFC6AB8978C8040220644F64B97EA144EE7D5CCB71C2372DD730FA0A659E4C18241A80D6C915350263',
'3045022100F3E541330FF79FFC42EB0491EDE1E47106D94ECFE3CDB2D9DD3BC0E8861F6D45022013F62942DD626D6C9731E317F372EC5C1F72885C4727FDBEE9D9321BC530D7B2',
'3045022100998ABE378F4119D8BEE9843482C09F0D5CE5C6012921548182454C610C57A269022036BD8EB71235C4B2C67339DE6A59746B1F7E5975987B7AB99B313D124A69BB9F'
];
/* eslint-enable max-len */
const key = K256Pair.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.bytesToHex(sig), expected[i]);
assert(key.verify(message, sig));
});
}
for (let n = 0; n < 5; n++) {
test_factory(n);
}
});
});