Skip to content

Commit

Permalink
Add linting (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
rekmarks authored Feb 23, 2021
1 parent cbd9d4c commit 754f5a5
Show file tree
Hide file tree
Showing 8 changed files with 1,276 additions and 126 deletions.
16 changes: 16 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = {
extends: [
'@metamask/eslint-config',
'@metamask/eslint-config/config/nodejs',
],
plugins: [
'json',
],
parserOptions: {
ecmaVersion: 2018,
},
ignorePatterns: [
'!.eslintrc.js',
'node_modules/',
],
};
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
"description": "",
"main": "src/index.js",
"types": "src/index.d.ts",
"files": [
"src/"
],
"scripts": {
"lint": "eslint . --ext js,json",
"lint:fix": "yarn lint --fix",
"test": "node ./test/index.js"
},
"author": "kumavis",
Expand All @@ -16,6 +21,11 @@
"secp256k1": "^3.5.0"
},
"devDependencies": {
"@metamask/eslint-config": "^5.0.0",
"eslint": "^7.20.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-json": "^2.1.2",
"eslint-plugin-node": "^11.1.0",
"tape": "^4.9.1"
}
}
90 changes: 46 additions & 44 deletions src/derivers/bip32.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,83 @@
// node
const crypto = require('crypto')
const assert = require('assert')
const crypto = require('crypto');
const assert = require('assert');
// npm
const secp256k1 = require('secp256k1')
const createKeccakHash = require('keccak')
const secp256k1 = require('secp256k1');
const createKeccakHash = require('keccak');

const HARDENED_OFFSET = 0x80000000
const HARDENED_OFFSET = 0x80000000;

module.exports = {
// standard
deriveChildKey,
// utility
bip32PathToMultipath,
privateKeyToEthAddress,
}
};

function bip32PathToMultipath(bip32Path) {
let pathParts = bip32Path.trim().split('/')
let pathParts = bip32Path.trim().split('/');
// strip "m" noop
if (pathParts[0].toLowerCase() === 'm') pathParts = pathParts.slice(1)
const multipath = pathParts.map(part => 'bip32:' + part).join('/')
return multipath
if (pathParts[0].toLowerCase() === 'm') {
pathParts = pathParts.slice(1);
}
const multipath = pathParts.map((part) => `bip32:${part}`).join('/');
return multipath;
}

function deriveChildKey (parentKey, pathPart) {
const isHardened = pathPart.includes(`'`)
const indexPart = pathPart.split(`'`)[0]
const childIndex = parseInt(indexPart, 10)
assert(childIndex < HARDENED_OFFSET, 'Invalid index')
assert(parentKey.length === 64, 'Parent key invalid length')
function deriveChildKey(parentKey, pathPart) {
const isHardened = pathPart.includes(`'`);
const indexPart = pathPart.split(`'`)[0];
const childIndex = parseInt(indexPart, 10);
assert(childIndex < HARDENED_OFFSET, 'Invalid index');
assert(parentKey.length === 64, 'Parent key invalid length');

const parentPrivateKey = parentKey.slice(0, 32)
const parentExtraEntropy = parentKey.slice(32)
const secretExtension = deriveSecretExtension({ parentPrivateKey, childIndex, isHardened })
const parentPrivateKey = parentKey.slice(0, 32);
const parentExtraEntropy = parentKey.slice(32);
const secretExtension = deriveSecretExtension({ parentPrivateKey, childIndex, isHardened });

const { privateKey, extraEntropy } = generateKey({
parentPrivateKey,
parentExtraEntropy,
secretExtension,
})
});

return Buffer.concat([privateKey, extraEntropy])
return Buffer.concat([privateKey, extraEntropy]);
}

// the bip32 secret extension is created from the parent private or public key and the child index
function deriveSecretExtension ({ parentPrivateKey, childIndex, isHardened }) {
function deriveSecretExtension({ parentPrivateKey, childIndex, isHardened }) {
if (isHardened) {
// Hardened child
const indexBuffer = Buffer.allocUnsafe(4)
indexBuffer.writeUInt32BE(childIndex + HARDENED_OFFSET, 0)
const pk = parentPrivateKey
const zb = Buffer.alloc(1, 0)
return Buffer.concat([zb, pk, indexBuffer])
} else {
// Normal child
const indexBuffer = Buffer.allocUnsafe(4)
indexBuffer.writeUInt32BE(childIndex, 0)
const parentPublicKey = secp256k1.publicKeyCreate(parentPrivateKey, true)
return Buffer.concat([parentPublicKey, indexBuffer])
const indexBuffer = Buffer.allocUnsafe(4);
indexBuffer.writeUInt32BE(childIndex + HARDENED_OFFSET, 0);
const pk = parentPrivateKey;
const zb = Buffer.alloc(1, 0);
return Buffer.concat([zb, pk, indexBuffer]);
}
// Normal child
const indexBuffer = Buffer.allocUnsafe(4);
indexBuffer.writeUInt32BE(childIndex, 0);
const parentPublicKey = secp256k1.publicKeyCreate(parentPrivateKey, true);
return Buffer.concat([parentPublicKey, indexBuffer]);

}

function generateKey ({ parentPrivateKey, parentExtraEntropy, secretExtension }) {
const entropy = crypto.createHmac('sha512', parentExtraEntropy).update(secretExtension).digest()
const keyMaterial = entropy.slice(0, 32)
function generateKey({ parentPrivateKey, parentExtraEntropy, secretExtension }) {
const entropy = crypto.createHmac('sha512', parentExtraEntropy).update(secretExtension).digest();
const keyMaterial = entropy.slice(0, 32);
// extraEntropy is also called "chaincode"
const extraEntropy = entropy.slice(32)
const privateKey = secp256k1.privateKeyTweakAdd(parentPrivateKey, keyMaterial)
return { privateKey, extraEntropy }
const extraEntropy = entropy.slice(32);
const privateKey = secp256k1.privateKeyTweakAdd(parentPrivateKey, keyMaterial);
return { privateKey, extraEntropy };
}

function keccak (a, bits = 256) {
return createKeccakHash('keccak' + bits).update(a).digest()
function keccak(a, bits = 256) {
return createKeccakHash(`keccak${bits}`).update(a).digest();
}

function privateKeyToEthAddress(keyBuffer) {
const privateKey = keyBuffer.slice(0, 32)
const publicKey = secp256k1.publicKeyCreate(privateKey, false).slice(1)
return keccak(publicKey).slice(-20)
const privateKey = keyBuffer.slice(0, 32);
const publicKey = secp256k1.publicKeyCreate(privateKey, false).slice(1);
return keccak(publicKey).slice(-20);
}
20 changes: 10 additions & 10 deletions src/derivers/bip39.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
// node
const crypto = require('crypto')
const crypto = require('crypto');
// npm
const bip39 = require('bip39')
const bip39 = require('bip39');

const ROOT_BASE_SECRET = Buffer.from('Bitcoin seed', 'utf8')
const ROOT_BASE_SECRET = Buffer.from('Bitcoin seed', 'utf8');

module.exports = {
// standard
deriveChildKey,
// utility
bip39MnemonicToMultipath,
}
};

function bip39MnemonicToMultipath(mnemonic) {
return `bip39:${mnemonic.trim()}`
return `bip39:${mnemonic.trim()}`;
}

// this creates a child key using bip39, ignoring the parent key
function deriveChildKey (_parentKey, pathPart) {
const mnemonic = pathPart
const seedBuffer = bip39.mnemonicToSeed(mnemonic)
const entropy = crypto.createHmac('sha512', ROOT_BASE_SECRET).update(seedBuffer).digest()
function deriveChildKey(_parentKey, pathPart) {
const mnemonic = pathPart;
const seedBuffer = bip39.mnemonicToSeed(mnemonic);
const entropy = crypto.createHmac('sha512', ROOT_BASE_SECRET).update(seedBuffer).digest();

return entropy
return entropy;
}
6 changes: 3 additions & 3 deletions src/derivers/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const bip32 = require('./bip32')
const bip39 = require('./bip39')
const bip32 = require('./bip32');
const bip39 = require('./bip39');

module.exports = {
bip32,
bip39,
}
};
44 changes: 22 additions & 22 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
const bip39 = require('bip39')
const bip39 = require('bip39');

const derivers = require('./derivers')
const derivers = require('./derivers');

module.exports = {
deriveKeyFromPath,
mnemonicToSeed,
}
};

/**
* ethereum default seed path: "m/44'/60'/0'/0/{account_index}"
* multipath: "bip32:44'/bip32:60'/bip32:0'/bip32:0/bip32:{account_index}"
*
*
* m: { privateKey, chainCode } = sha512Hmac("Bitcoin seed", masterSeed)
* 44': { privateKey, chainCode } = parentKey.privateKey + sha512Hmac(parentKey.chainCode, [0x00, parentKey.privateKey, index + HARDENED_OFFSET])
* 60': { privateKey, chainCode } = parentKey.privateKey + sha512Hmac(parentKey.chainCode, [0x00, parentKey.privateKey, index + HARDENED_OFFSET])
Expand All @@ -24,21 +24,21 @@ module.exports = {
* - bip32:0
* - bip32:0'
*/
const BIP_32_PATH_REGEX = /^bip32:\d+'?$/u
const BIP_32_PATH_REGEX = /^bip32:\d+'?$/u;

/**
* bip39:<SPACE_DELMITED_SEED_PHRASE>
*
*
* The seed phrase must consist of 12 <= 24 words.
*/
const BIP_39_PATH_REGEX = /^bip39:(\w+){1}( \w+){11,23}$/u
const BIP_39_PATH_REGEX = /^bip39:(\w+){1}( \w+){11,23}$/u;

/**
* e.g.
* - bip32:44'/bip32:60'/bip32:0'/bip32:0/bip32:0
* - bip39:<SPACE_DELMITED_SEED_PHRASE>/bip32:44'/bip32:60'/bip32:0'/bip32:0/bip32:0
*/
const MULTI_PATH_REGEX = /^(bip39:(\w+){1}( \w+){11,23}\/)?(bip32:\d+'?\/){3,4}(bip32:\d+'?)$/u
const MULTI_PATH_REGEX = /^(bip39:(\w+){1}( \w+){11,23}\/)?(bip32:\d+'?\/){3,4}(bip32:\d+'?)$/u;

function validateDeriveKeyParams(pathSegment, parentKey) {
// The path segment must be one of the following:
Expand All @@ -50,22 +50,22 @@ function validateDeriveKeyParams(pathSegment, parentKey) {
BIP_39_PATH_REGEX.test(pathSegment) ||
MULTI_PATH_REGEX.test(pathSegment)
)) {
throw new Error('Invalid HD path segment. Ensure that the HD path segment is correctly formatted.')
throw new Error('Invalid HD path segment. Ensure that the HD path segment is correctly formatted.');
}

// BIP-39 segments can only initiate HD paths
if (BIP_39_PATH_REGEX.test(pathSegment) && parentKey) {
throw new Error('May not specify parent key and BIP-39 path segment.')
throw new Error('May not specify parent key and BIP-39 path segment.');
}

// BIP-32 segments cannot initiate HD paths
if (!pathSegment.startsWith('bip39') && !parentKey) {
throw new Error('Must specify parent key if the first path of the path segment is not BIP-39.')
throw new Error('Must specify parent key if the first path of the path segment is not BIP-39.');
}

// The parent key must be a Buffer
if (parentKey && !Buffer.isBuffer(parentKey)) {
throw new Error('Parent key must be a Buffer if specified.')
throw new Error('Parent key must be a Buffer if specified.');
}
}

Expand All @@ -75,25 +75,25 @@ function validateDeriveKeyParams(pathSegment, parentKey) {
* @param {Buffer} [parentKey] - The parent key of the given path segment.
*/
function deriveKeyFromPath(pathSegment, parentKey) {
validateDeriveKeyParams(pathSegment, parentKey)
validateDeriveKeyParams(pathSegment, parentKey);

let key = parentKey
let key = parentKey;

// derive through each part of path
pathSegment.split('/').forEach((path) => {
const [pathType, pathValue] = path.split(':')
const deriver = derivers[pathType]
const [pathType, pathValue] = path.split(':');
const deriver = derivers[pathType];
if (!deriver) {
throw new Error(`Unknown derivation type "${pathType}"`)
throw new Error(`Unknown derivation type "${pathType}"`);
}
const childKey = deriver.deriveChildKey(key, pathValue)
const childKey = deriver.deriveChildKey(key, pathValue);
// continue deriving from child key
key = childKey
})
key = childKey;
});

return key
return key;
}

function mnemonicToSeed(mnemonic) {
return bip39.mnemonicToSeed(mnemonic)
return bip39.mnemonicToSeed(mnemonic);
}
Loading

0 comments on commit 754f5a5

Please sign in to comment.