stellar-hd-wallet
Version:
Key derivation for Stellar (SEP-0005)
116 lines (115 loc) • 4.6 kB
JavaScript
import bip39 from "bip39";
import { derivePath } from "./hd-key.js";
import { Keypair } from "@stellar/stellar-base";
const ENTROPY_BITS = 256; // = 24 word mnemonic
const INVALID_SEED = "Invalid seed (must be a Buffer or hex string)";
const INVALID_MNEMONIC = "Invalid mnemonic (see bip39)";
/**
* Class for SEP-0005 key derivation.
* @see {@link https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md|SEP-0005}
*/
class StellarHDWallet {
/**
* Instance from a BIP39 mnemonic string.
* @param {string} mnemonic A BIP39 mnemonic
* @param {string} [password] Optional mnemonic password
* @param {string} [language='english'] Optional language of mnemonic
* @throws {Error} Invalid Mnemonic
*/
static fromMnemonic(mnemonic, password, language = "english") {
if (!StellarHDWallet.validateMnemonic(mnemonic, language)) {
throw new Error(INVALID_MNEMONIC);
}
return new StellarHDWallet(bip39.mnemonicToSeedSync(mnemonic, password).toString("hex"));
}
/**
* Instance from a seed
* @param {(string|Buffer)} seed binary seed
* @throws {TypeError} Invalid seed
*/
static fromSeed(seed) {
let seedHex;
if (Buffer.isBuffer(seed))
seedHex = seed.toString("hex");
else if (typeof seed === "string")
seedHex = seed;
else
throw new TypeError(INVALID_SEED);
return new StellarHDWallet(seedHex);
}
/**
* Generate a mnemonic using BIP39
* @param {Object} props Properties defining how to generate the mnemonic
* @param {Number} [props.entropyBits=256] Entropy bits
* @param {string} [props.language='english'] name of a language wordlist as
* defined in the 'bip39' npm module. See module.exports.wordlists:
* here https://github.com/bitcoinjs/bip39/blob/master/index.js
* @param {function} [props.rng] RNG function (default is crypto.randomBytes)
* @throws {TypeError} Language not supported by bip39 module
* @throws {TypeError} Invalid entropy
*/
static generateMnemonic({ entropyBits = ENTROPY_BITS, language = "english", rngFn } = {}) {
if (language && !(language in bip39.wordlists))
throw new TypeError(`Language ${language} does not have a wordlist in the bip39 module`);
const wordlist = bip39.wordlists[language];
return bip39.generateMnemonic(entropyBits, rngFn, wordlist);
}
/**
* Validate a mnemonic using BIP39
* @param {string} mnemonic A BIP39 mnemonic
* @param {string} [language='english'] name of a language wordlist as
* defined in the 'bip39' npm module. See module.exports.wordlists:
* here https://github.com/bitcoinjs/bip39/blob/master/index.js
* @throws {TypeError} Language not supported by bip39 module
*/
static validateMnemonic(mnemonic, language = "english") {
if (!mnemonic)
return false;
if (language && !(language in bip39.wordlists))
throw new TypeError(`Language ${language} does not have a wordlist in the bip39 module`);
const wordlist = bip39.wordlists[language];
return bip39.validateMnemonic(mnemonic, wordlist);
}
/**
* New instance from seed hex string
* @param {string} seedHex Hex string
*/
constructor(seedHex) {
this.seedHex = seedHex;
}
/**
* Derive key given a full BIP44 path
* @param {string} path BIP44 path string (eg. m/44'/148'/8')
* @return {Buffer} Key binary as Buffer
*/
derive(path) {
const data = derivePath(path, this.seedHex);
return data.key;
}
/**
* Get Stellar account keypair for child key at given index
* @param {Number} index Account index into path m/44'/148'/{index}
* @return {stellar-base.Keypair} Keypair instance for the account
*/
getKeypair(index) {
const key = this.derive(`m/44'/148'/${index}'`);
return Keypair.fromRawEd25519Seed(key);
}
/**
* Get public key for account at index
* @param {Number} index Account index into path m/44'/148'/{index}
* @return {string} Public key
*/
getPublicKey(index) {
return this.getKeypair(index).publicKey();
}
/**
* Get secret for account at index
* @param {Number} index Account index into path m/44'/148'/{index}
* @return {string} Secret
*/
getSecret(index) {
return this.getKeypair(index).secret();
}
}
export default StellarHDWallet;