Skip to content

Instantly share code, notes, and snippets.

@dmdeklerk
Created May 15, 2024 19:58
Show Gist options
  • Save dmdeklerk/ee306898416b309ff0043ba5d0a7ed2b to your computer and use it in GitHub Desktop.
Save dmdeklerk/ee306898416b309ff0043ba5d0a7ed2b to your computer and use it in GitHub Desktop.
const { fromSeed } = require("bip32")
const wif = require('wif')
/**
* Derives a bip44 path from a mnemonic
*
* @param {{
* seedHex: string,
* paths: Array<{
* path: string,
* includeWif?: boolean,
* }>
* }} params
* @returns {Array<{
* privateKey: string,
* publicKey: string,
* wif?: string,
* }>}
*/
function WALLET_DERIVE_KEY_PAIRS(params) {
const { seedHex, paths } = params
return deriveKeyPairs(seedHex, paths)
}
/**
* Derives a collection of keypairs from a seed, note that the seed is produced from
* {WALLET_MNEMONIC_TO_SEED}
* Keypairs derivation is optimized by calculating the parent nodes 1 time and re-using
* these in subsequent derivation.
*
* @param {string} seedHex
* @param {Array<{
* path: string,
* includeWif?: boolean,
* }>} paths
* @returns {Array<{
* privateKey: string,
* publicKey: string,
* wif?: string,
* }>}
*/
function deriveKeyPairs(seedHex, paths) {
const seed = Buffer.from(seedHex, 'hex')
const root = fromSeed(seed)
const result = []
// we cache each derived node here - key is the PathStep.hashKey()
const _nodeCache = {}
// parse all derivation paths into DerivationPath objects
const _paths = paths.map(path => new DerivationPath(path))
// loop over all paths
for (let i = 0; i < _paths.length; i++) {
const path = _paths[i];
// in order to derive nodes call either node.derive, node.deriveHardened or obtain from cache
let node = root;
for (let j = 0; j < path.steps.length; j++) {
const step = path.steps[j];
// lets find the next node from cache or calculate it
if (_nodeCache[step.hashKey()]) {
node = _nodeCache[step.hashKey()]
} else {
_nodeCache[step.hashKey()] = node = step.hardened ? node.deriveHardened(step.value) : node.derive(step.value)
}
}
const privateKey = node.privateKey.toString('hex')
const publicKey = 'skipped'
const data = {
privateKey, publicKey
}
if (path.includeWif) {
data.wif = toWif(node.privateKey)
}
result.push(data)
}
return result;
}
/**
* @param {Buffer} privateKey
* @returns String
*/
function toWif(privateKey) {
return wif.encode(128, privateKey, true);
}
/**
* Parses bip32 derivation path (m/44'/1'/0'/0/0) into a set of individual steps
* so we can later derive the nodes for these steps from a single root key.
* The optimization will be applied when a whole range of paths that share most
* parent nodes is requested at once - all so we dont have to call 'derivePath' on
* the full path each time but instead cach previously calculated nodes as efficient
* as possible.
* @param {{
* path: string,
* includeWif?: boolean,
* }} param
*/
function DerivationPath(param) {
const { path, includeWif } = param
const parts = path.split('/')
this.steps = []
this.includeWif = includeWif
let parent = null;
for (let i = 0; i < parts.length; i++) {
if (parts[i] == 'm' && i == 0)
continue;
let step = new PathStep(parts[i], parent)
this.steps.push(step)
parent = step
}
}
/**
* Expects a path part which look like 1 or 1' meaning the first is normal and
* the second is hardened.
* @param {string} part
* @param {PathStep} parentStep
*/
function PathStep(part, parentStep) {
this.value = parseInt(part);
this.hardened = part.endsWith("'");
this.parentStep = parentStep;
}
/**
* The hashKey uniquely identifies this pathStep and is used to later on to cache and identify
* derived nodes we derive from the rootNode.
*/
PathStep.prototype.hashKey = function () {
return (this.parentStep ? `${this.parentStep.hashKey()}/` : '') + `${this.value}${this.hardened ? "'" : ""}`;
}
module.exports = { WALLET_DERIVE_KEY_PAIRS }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment