mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-04 21:15:47 +00:00
Add simplified API
This commit is contained in:
113
packages/ripple-keypairs/src/api.js
Normal file
113
packages/ripple-keypairs/src/api.js
Normal 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
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
85
packages/ripple-keypairs/test/api-test.js
Normal file
85
packages/ripple-keypairs/test/api-test.js
Normal 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);
|
||||
});
|
||||
});
|
||||
22
packages/ripple-keypairs/test/fixtures/api.json
vendored
Normal file
22
packages/ripple-keypairs/test/fixtures/api.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
13
packages/ripple-keypairs/test/utils-test.js
Normal file
13
packages/ripple-keypairs/test/utils-test.js
Normal 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]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user