Add simplified API

This commit is contained in:
Chris Clark
2015-08-24 13:34:42 -07:00
parent aca6cb96b7
commit 7308a84c52
8 changed files with 253 additions and 11 deletions

View File

@@ -0,0 +1,113 @@
'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

@@ -4,7 +4,7 @@ const assert = require('assert');
const brorand = require('brorand');
const codec = require('ripple-address-codec');
const {seedFromPhrase, createAccountID} = require('./utils');
const {seedFromPhrase, computePublicKeyHash} = require('./utils');
const {KeyPair, KeyType} = require('./keypair');
const {Ed25519Pair} = require('./ed25519');
const {K256Pair, accountPublicFromPublicGenerator} = require('./secp256k1');
@@ -70,7 +70,7 @@ function generateValidatorKeys(opts = {}) {
function nodePublicAccountID(publicKey) {
const generatorBytes = decodeNodePublic(publicKey);
const accountPublicBytes = accountPublicFromPublicGenerator(generatorBytes);
return encodeAccountID(createAccountID(accountPublicBytes));
return encodeAccountID(computePublicKeyHash(accountPublicBytes));
}
function validatorKeysFromSeed(seed, seedType) {
@@ -93,7 +93,7 @@ module.exports = {
Ed25519Pair,
KeyType,
seedFromPhrase,
createAccountID,
computePublicKeyHash,
keyPairFromSeed,
generateWallet,
generateValidatorKeys,

View File

@@ -5,7 +5,7 @@ const {
bytesToHex,
cached,
isVirtual,
createAccountID
computePublicKeyHash
} = require('./utils');
const KeyType = {
@@ -45,7 +45,7 @@ class KeyPair {
@cached
accountBytes() {
return createAccountID(this.pubKeyCanonicalBytes());
return computePublicKeyHash(this.pubKeyCanonicalBytes());
}
@cached

View File

@@ -34,7 +34,7 @@ function deriveScalar(bytes, discrim) {
* @return {bn.js} - 256 bit scalar value
*
*/
function deriveSecret(seed, opts = {}) {
function derivePrivateKey(seed, opts = {}) {
const root = opts.validator;
const order = secp256k1.curve.n;
@@ -114,7 +114,8 @@ class K256Pair extends KeyPair {
key() {
if (this.seedBytes) {
const options = {validator: this.validator};
return secp256k1.keyFromPrivate(deriveSecret(this.seedBytes, options));
return secp256k1.keyFromPrivate(
derivePrivateKey(this.seedBytes, options));
}
return secp256k1.keyFromPublic(this.pubKeyCanonicalBytes());
}
@@ -123,5 +124,6 @@ class K256Pair extends KeyPair {
module.exports = {
K256Pair,
derivePrivateKey,
accountPublicFromPublicGenerator
};

View File

@@ -1,6 +1,7 @@
'use strict';
const assert = require('assert');
const hashjs = require('hash.js');
const BN = require('bn.js');
const Sha512 = require('./sha512');
function unused() {}
@@ -42,8 +43,13 @@ function bytesToHex(a) {
}).join('');
}
function createAccountID(pubKeyBytes) {
const hash256 = hashjs.sha256().update(pubKeyBytes).digest();
function hexToBytes(a) {
assert(a.length % 2 === 0);
return (new BN(a, 16)).toArray(null, a.length / 2);
}
function computePublicKeyHash(publicKeyBytes) {
const hash256 = hashjs.sha256().update(publicKeyBytes).digest();
const hash160 = hashjs.ripemd160().update(hash256).digest();
return hash160;
}
@@ -55,7 +61,8 @@ function seedFromPhrase(phrase) {
module.exports = {
cached,
bytesToHex,
createAccountID,
hexToBytes,
computePublicKeyHash,
isVirtual,
seedFromPhrase,
Sha512,

View File

@@ -0,0 +1,85 @@
'use strict';
const assert = require('assert');
const fixtures = require('./fixtures/api.json');
const api = require('../src/api');
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];
describe('api', () => {
it('generateSeed - secp256k1', () => {
assert.strictEqual(api.generateSeed({entropy}), fixtures.secp256k1.seed);
});
it('generateSeed - secp256k1, random', () => {
const seed = api.generateSeed();
assert(seed.charAt(0) === 's');
const {type, bytes} = decodeSeed(seed);
assert(type === 'secp256k1');
assert(bytes.length === 16);
});
it('generateSeed - ed25519', () => {
assert.strictEqual(api.generateSeed({entropy, algorithm: 'ed25519'}),
fixtures.ed25519.seed);
});
it('generateSeed - ed25519, random', () => {
const seed = api.generateSeed({algorithm: 'ed25519'});
assert(seed.slice(0, 3) === 'sEd');
const {type, bytes} = decodeSeed(seed);
assert(type === 'ed25519');
assert(bytes.length === 16);
});
it('deriveKeypair - secp256k1', () => {
const keypair = api.deriveKeypair(fixtures.secp256k1.seed);
assert.deepEqual(keypair, fixtures.secp256k1.keypair);
});
it('deriveKeypair - ed25519', () => {
const keypair = api.deriveKeypair(fixtures.ed25519.seed);
assert.deepEqual(keypair, fixtures.ed25519.keypair);
});
it('deriveAddress - secp256k1 public key', () => {
const address = api.deriveAddress(fixtures.secp256k1.keypair.publicKey);
assert.strictEqual(address, fixtures.secp256k1.address);
});
it('deriveAddress - ed25519 public key', () => {
const address = api.deriveAddress(fixtures.ed25519.keypair.publicKey);
assert.strictEqual(address, fixtures.ed25519.address);
});
it('sign - secp256k1', () => {
const privateKey = fixtures.secp256k1.keypair.privateKey;
const signature = api.sign(fixtures.secp256k1.message, privateKey);
assert.strictEqual(signature, fixtures.secp256k1.signature);
});
it('verify - secp256k1', () => {
const signature = fixtures.secp256k1.signature;
const publicKey = fixtures.secp256k1.keypair.publicKey;
const message = fixtures.secp256k1.message;
assert(api.verify(message, signature, publicKey));
});
it('sign - ed25519', () => {
const privateKey = fixtures.ed25519.keypair.privateKey;
const signature = api.sign(fixtures.ed25519.message, privateKey);
assert.strictEqual(signature, fixtures.ed25519.signature);
});
it('verify - ed25519', () => {
const signature = fixtures.ed25519.signature;
const publicKey = fixtures.ed25519.keypair.publicKey;
const message = fixtures.ed25519.message;
assert(api.verify(message, signature, publicKey));
});
it('deriveNodeAddress', () => {
const x = 'n9KHn8NfbBsZV5q8bLfS72XyGqwFt5mgoPbcTV4c6qKiuPTAtXYk';
const y = 'rU7bM9ENDkybaxNrefAVjdLTyNLuue1KaJ';
assert.strictEqual(api.deriveNodeAddress(x), y);
});
});

View File

@@ -0,0 +1,22 @@
{
"secp256k1": {
"seed": "sp5fghtJtpUorTwvof1NpDXAzNwf5",
"keypair": {
"privateKey": "00D78B9735C3F26501C7337B8A5727FD53A6EFDBC6AA55984F098488561F985E23",
"publicKey": "030D58EB48B4420B1F7B9DF55087E0E29FEF0E8468F9A6825B01CA2C361042D435"
},
"address": "rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1",
"message": "test message",
"signature": "30440220583A91C95E54E6A651C47BEC22744E0B101E2C4060E7B08F6341657DAD9BC3EE02207D1489C7395DB0188D3A56A977ECBA54B36FA9371B40319655B1B4429E33EF2D"
},
"ed25519": {
"seed": "sEdSKaCy2JT7JaM7v95H9SxkhP9wS2r",
"keypair": {
"privateKey": "EDB4C4E046826BD26190D09715FC31F4E6A728204EADD112905B08B14B7F15C4F3",
"publicKey": "ED01FA53FA5A7E77798F882ECE20B1ABC00BB358A9E55A202D0D0676BD0CE37A63"
},
"address": "rLUEXYuLiQptky37CqLcm9USQpPiz5rkpD",
"message": "test message",
"signature": "D9AB88033EFAFFBADF3AF5854885A0B42DAA91B94B64B10EB1C1BE376C0F452A93F31B13CEF00049FB36317B2E9E5ADBAD38040E71C17B01F01A2FB3EA30CD0F"
}
}

View File

@@ -0,0 +1,13 @@
'use strict';
const assert = require('assert');
const utils = require('../src/utils');
describe('utils', () => {
it('hexToBytes - zero', () => {
assert.deepEqual(utils.hexToBytes('000000'), [0, 0, 0]);
});
it('hexToBytes - DEADBEEF', () => {
assert.deepEqual(utils.hexToBytes('DEADBEEF'), [222, 173, 190, 239]);
});
});