mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-19 19:25:51 +00:00
Added key-derivation JavaScript examples analog to python version
This commit is contained in:
210
content/_code-samples/key-derivation/js/index.js
Normal file
210
content/_code-samples/key-derivation/js/index.js
Normal file
@@ -0,0 +1,210 @@
|
||||
'use strict';
|
||||
|
||||
//organize imports
|
||||
const assert = require("assert")
|
||||
const brorand = require("brorand")
|
||||
const BN = require("bn.js")
|
||||
const elliptic = require("elliptic");
|
||||
const Ed25519 = elliptic.eddsa('ed25519');
|
||||
const Secp256k1 = elliptic.ec('secp256k1');
|
||||
const { rfc1751MnemonicToKey, keyToRFC1751Mnemonic } = require("xrpl/dist/npm/Wallet/rfc1751");
|
||||
const hashjs = require("hash.js");
|
||||
const Sha512 = require("ripple-keypairs/dist/Sha512")
|
||||
const { codec, encodeAccountPublic, encodeNodePublic } = require("ripple-address-codec");
|
||||
|
||||
const XRPL_SEED_PREFIX = 0x21
|
||||
|
||||
const isHex = function(value) {
|
||||
const regex = new RegExp(/^0x([0-9a-f]+(\.[0-9a-f]*)?|\.[0-9a-f]+)(p[+-]?\d+)?$/, 'i')
|
||||
return regex.test(value)
|
||||
}
|
||||
|
||||
const bytesToHex = function(bytes) {
|
||||
return Array.from(bytes, (byteValue) => {
|
||||
const hex = byteValue.toString(16).toUpperCase();
|
||||
return hex.length > 1 ? hex : `0${hex}`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
const hexToBytes = function(hex) {
|
||||
assert.ok(hex.length % 2 === 0);
|
||||
return hex.length === 0 ? [] : new BN(hex, 16).toArray(null, hex.length / 2);
|
||||
}
|
||||
|
||||
const encodeUTF8 = function (string) {
|
||||
return encodeURIComponent(string)
|
||||
}
|
||||
|
||||
const sha512half = function (value) {
|
||||
return hashjs.sha512().update(value).digest().slice(0, 32);
|
||||
}
|
||||
|
||||
class Seed {
|
||||
|
||||
constructor(seedValue = '', checkRFC1751 = false) {
|
||||
this.bytes = this._initSeed(seedValue)
|
||||
this.ED25519Keypair = this._deriveED25519Keypair(this.bytes)
|
||||
this.Secp256K1Keypair = this._deriveSecp256K1Keypair(this.bytes)
|
||||
}
|
||||
|
||||
_initSeed(seedValue) {
|
||||
|
||||
//default to random seed
|
||||
if (!seedValue || seedValue === '') {
|
||||
const randomBuffer = brorand(16)
|
||||
return [...randomBuffer]
|
||||
}
|
||||
|
||||
//from hex formatted seed
|
||||
if (isHex(seedValue)) {
|
||||
const decoded = hexToBytes(seedValue);
|
||||
if(decoded.length === 16) {
|
||||
return decoded
|
||||
} else {
|
||||
//Raise Error
|
||||
console.log('Raise error!')
|
||||
}
|
||||
}
|
||||
|
||||
//from rfc1751 Mnemonic
|
||||
try {
|
||||
const rippledMnemonic = rfc1751MnemonicToKey(seedValue)
|
||||
if (rippledMnemonic.length === 16) {
|
||||
return [...rippledMnemonic]
|
||||
}
|
||||
} catch (error) {
|
||||
//try next seed mode
|
||||
}
|
||||
|
||||
//from password seed
|
||||
const encoded = encodeUTF8(seedValue)
|
||||
return hashjs.sha512().update(encoded).digest().slice(0, 16);
|
||||
}
|
||||
|
||||
_deriveED25519Keypair() {
|
||||
const prefix = 'ED';
|
||||
const rawPrivateKey = sha512half(this.bytes);
|
||||
const privateKey = prefix + bytesToHex(rawPrivateKey);
|
||||
const publicKey = prefix + bytesToHex(Ed25519.keyFromSecret(rawPrivateKey).pubBytes());
|
||||
return { privateKey, publicKey };
|
||||
}
|
||||
|
||||
_deriveSecp256K1Keypair(entropy, options) {
|
||||
const prefix = '00';
|
||||
const privateKey = prefix + this._deriveSecp256K1PrivateKey(entropy, options).toString(16, 64).toUpperCase()
|
||||
const publicKey = bytesToHex(Secp256k1.keyFromPrivate(privateKey.slice(2))
|
||||
.getPublic()
|
||||
.encodeCompressed());
|
||||
return { privateKey, publicKey };
|
||||
}
|
||||
|
||||
_deriveSecp256K1PrivateKey(seed, opts = {}) {
|
||||
const root = opts.validator;
|
||||
const order = Secp256k1.curve.n;
|
||||
// This private generator represents the `root` private key, and is what's
|
||||
// used by validators for signing when a keypair is generated from a seed.
|
||||
const privateGen = this._deriveScalar(seed);
|
||||
if (root) {
|
||||
// As returned by validation_create for a given seed
|
||||
return privateGen;
|
||||
}
|
||||
const publicGen = Secp256k1.g.mul(privateGen);
|
||||
// A seed can generate many keypairs as a function of the seed and a uint32.
|
||||
// Almost everyone just uses the first account, `0`.
|
||||
const accountIndex = opts.accountIndex || 0;
|
||||
|
||||
return this._deriveScalar(publicGen.encodeCompressed(), accountIndex)
|
||||
.add(privateGen)
|
||||
.mod(order);
|
||||
}
|
||||
|
||||
_deriveScalar(bytes, discrim) {
|
||||
const order = Secp256k1.curve.n;
|
||||
for (let i = 0; i <= 0xffffffff; i++) {
|
||||
// We hash the bytes to find a 256 bit number, looping until we are sure it
|
||||
// is less than the order of the curve.
|
||||
const hasher = new Sha512.default().add(bytes);
|
||||
// If the optional discriminator index was passed in, update the hash.
|
||||
if (discrim !== undefined) {
|
||||
hasher.addU32(discrim);
|
||||
}
|
||||
hasher.addU32(i);
|
||||
const key = hasher.first256BN();
|
||||
/* istanbul ignore else */
|
||||
if (key.cmpn(0) > 0 && key.cmp(order) < 0) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
// This error is practically impossible to reach.
|
||||
// The order of the curve describes the (finite) amount of points on the curve
|
||||
// https://github.com/indutny/elliptic/blob/master/lib/elliptic/curves.js#L182
|
||||
// How often will an (essentially) random number generated by Sha512 be larger than that?
|
||||
// There's 2^32 chances (the for loop) to get a number smaller than the order,
|
||||
// and it's rare that you'll even get past the first loop iteration.
|
||||
// Note that in TypeScript we actually need the throw, otherwise the function signature would be BN | undefined
|
||||
//
|
||||
/* istanbul ignore next */
|
||||
throw new Error('impossible unicorn ;)');
|
||||
}
|
||||
|
||||
getBase58ED25519Account() {
|
||||
const buffer = Buffer.from(this.ED25519Keypair.publicKey, "hex")
|
||||
return encodeAccountPublic([...buffer])
|
||||
}
|
||||
|
||||
getBase58ESecp256k1Account(validator = false) {
|
||||
const buffer = Buffer.from(this.Secp256K1Keypair.publicKey, "hex")
|
||||
if (validator) {
|
||||
return encodeNodePublic([...buffer])
|
||||
} else {
|
||||
return encodeAccountPublic([...buffer])
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
const base58Seed = codec.encodeChecked([XRPL_SEED_PREFIX].concat(this.bytes))
|
||||
const hexSeed = Buffer.from(this.bytes).toString('hex').toUpperCase()
|
||||
const rfc1751Rippled = keyToRFC1751Mnemonic(bytesToHex(this.bytes))
|
||||
|
||||
const base58ED25519Account = this.getBase58ED25519Account();
|
||||
|
||||
const base58Secp256k1Account = this.getBase58ESecp256k1Account();
|
||||
const base58Secp256k1AccountValidator = this.getBase58ESecp256k1Account(true);
|
||||
|
||||
const out = `
|
||||
Seed (base58): ${base58Seed}
|
||||
Seed (hex): ${hexSeed}
|
||||
Seed (rippled RFC-1751): ${rfc1751Rippled}
|
||||
|
||||
Ed25519 Secret Key (hex): ${seed.ED25519Keypair.privateKey}
|
||||
Ed25519 Public Key (hex): ${seed.ED25519Keypair.publicKey}
|
||||
Ed25519 Public Key (base58 - Account): ${base58ED25519Account}
|
||||
|
||||
secp256k1 Secret Key (hex): ${seed.Secp256K1Keypair.privateKey}
|
||||
secp256k1 Public Key (hex): ${seed.Secp256K1Keypair.publicKey}
|
||||
secp256k1 Public Key (base58 - Account): ${base58Secp256k1Account}
|
||||
secp256k1 Public Key (base58 - Validator): ${base58Secp256k1AccountValidator}
|
||||
`
|
||||
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
//from empty seed
|
||||
//const seed = new Seed();
|
||||
//console.log(seed.toString());
|
||||
|
||||
//from hex formatted seed
|
||||
//const hexSeed = '0x21edc3dec3ef1873cf8f333381c5f360c3'
|
||||
//const seed = new Seed(hexSeed);
|
||||
//console.log(seed.toString());
|
||||
|
||||
//from rippled RFC1751 mnemonic
|
||||
//const rippledMnemonic = 'KANT FISH GENE COST WEB JAKE CHUM HAD SOD MID KICK BOTH'
|
||||
//const seed = new Seed(rippledMnemonic);
|
||||
//console.log(seed.toString());
|
||||
|
||||
//from fixed seed
|
||||
const string = "sEdSKaCy2JT7JaM7v95H9SxkhP9wS2r"
|
||||
const seed = new Seed(string);
|
||||
console.log(seed.toString());
|
||||
12
content/_code-samples/key-derivation/js/package.json
Normal file
12
content/_code-samples/key-derivation/js/package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "key-derivation-examples",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xrpl": "^2.0.0"
|
||||
},
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user