'use strict'; // Organize imports const assert = require("assert") const brorand = require("brorand") const { BigNumber } = require("bignumber.js") const elliptic = require("elliptic"); const Ed25519 = elliptic.eddsa('ed25519'); const Secp256k1 = elliptic.ec('secp256k1'); const hashjs = require("hash.js"); const isomorphic = require("@xrplf/isomorphic/sha512") const noble = require('@noble/curves/abstract/utils') const { codec, encodeAccountPublic, encodeNodePublic } = require("ripple-address-codec"); const XRPL_SEED_PREFIX = 0x21 class Sha512 { // instantiate empty sha512 hash hash = isomorphic.sha512.create() static half(input) { return new Sha512().add(input).first256() } add(bytes) { this.hash.update(bytes) return this } addU32(i) { const buffer = new Uint8Array(4) new DataView(buffer.buffer).setUint32(0, i) return this.add(buffer) } finish() { return this.hash.digest() } first256() { return this.finish().slice(0, 32) } first256BigInt() { return noble.bytesToNumberBE(this.first256()) } } const isHex = function(value) { const regex = new RegExp(/^[0-9a-f]+$/, '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 BigNumber(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) { // No input given - default to random seed if (!seedValue || seedValue === '') { const randomBuffer = brorand(16) return [...randomBuffer] } // From base58 formatted seed try { const base58decoded = codec.decodeChecked(seedValue) if (base58decoded[0] === XRPL_SEED_PREFIX && base58decoded.length === 17) { return [...base58decoded.slice(1)]; } } catch (exception) { // Continue probing the seed for different format } // From hex formatted seed if (isHex(seedValue)) { const decoded = hexToBytes(seedValue); if(decoded.length === 16) { return decoded } else { // Raise Error throw new Error("incorrect decoded seed length") } } // 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; const scalar = this._deriveScalar(publicGen.encodeCompressed(), accountIndex) return BigNumber(scalar).plus(privateGen).mod(order); } _deriveScalar(bytes, discrim) { const order = Secp256k1.curve.n for (let i = 0; i <= 0xffff_ffff; 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().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.first256BigInt() /* istanbul ignore else */ if (key > 0 && key < order) { 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 bigint | 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 base58ED25519Account = this.getBase58ED25519Account(); const base58Secp256k1Account = this.getBase58ESecp256k1Account(); const base58Secp256k1AccountValidator = this.getBase58ESecp256k1Account(true); const out = ` Seed (base58): ${base58Seed} Seed (hex): ${hexSeed} Ed25519 Secret Key (hex): ${this.ED25519Keypair.privateKey} Ed25519 Public Key (hex): ${this.ED25519Keypair.publicKey} Ed25519 Public Key (base58 - Account): ${base58ED25519Account} secp256k1 Secret Key (hex): ${this.Secp256K1Keypair.privateKey} secp256k1 Public Key (hex): ${this.Secp256K1Keypair.publicKey} secp256k1 Public Key (base58 - Account): ${base58Secp256k1Account} secp256k1 Public Key (base58 - Validator): ${base58Secp256k1AccountValidator} ` return out } } if (process.argv[2] !== undefined) { console.log(process.argv[2]); const seed = new Seed(process.argv[2]); console.log(seed.toString()) } else { const seed = new Seed(); console.log(seed.toString()) }