@metamask/key-tree
Advanced tools
Comparing version 3.0.1 to 4.0.0
@@ -9,2 +9,30 @@ # Changelog | ||
## [4.0.0] | ||
### Added | ||
- **NOTE:** This version is a significant rewrite of this package, and virtually all existing usage will break upon migrating from a previous major version. | ||
All pre-existing functionality is supported through different means, and various new features have been added. | ||
- Add extended public (`xpub`) and private (`xprv`) keys ([#58](https://github.com/MetaMask/key-tree/pull/58)) | ||
- Add support for public key derivation ([#56](https://github.com/MetaMask/key-tree/pull/56)) | ||
- Add support for non-secp256k1 curves via `SLIP10Node` class ([#43](https://github.com/MetaMask/key-tree/pull/43), [#37](https://github.com/MetaMask/key-tree/pull/37), [#53](https://github.com/MetaMask/key-tree/pull/53)) | ||
- Add support for ed25519 curve. | ||
- With this and other changes in this release, this package offers full [SLIP-10](https://github.com/satoshilabs/slips/blob/133ea52a8e43d338b98be208907e144277e44c0e/slip-0010.md) support for all curves except nist256p1. | ||
- These changes were made possible using the `@noble/*` suite of cryptography packages. | ||
- Add extended key to BIP-44 coin type node ([#59](https://github.com/MetaMask/key-tree/pull/59)) | ||
- Add convenience methods to get public keys and addresses ([#50](https://github.com/MetaMask/key-tree/pull/50)) | ||
- Enable deriving hardened `change` and `address_index` using `BIP44CoinTypeNode` ([#37](https://github.com/MetaMask/key-tree/pull/37)) | ||
### Changed | ||
- **BREAKING:** Change key representation format ([#58](https://github.com/MetaMask/key-tree/pull/58), [#54](https://github.com/MetaMask/key-tree/pull/54)) | ||
- Encode string keys in hexadecimal instead of Base64. | ||
- Always return a `SLIP10Node` (or child class) object from derivation functions. | ||
- **BREAKING:** Separate private keys and chain code into separate fields ([#54](https://github.com/MetaMask/key-tree/pull/54)) | ||
- **BREAKING:** Use named arguments instead of positional arguments in various functions ([#56](https://github.com/MetaMask/key-tree/pull/56)) | ||
- **BREAKING:** Make all derivation functions async ([#43](https://github.com/MetaMask/key-tree/pull/43), [#54](https://github.com/MetaMask/key-tree/pull/54)) | ||
- All key derivation functions are now async, and node objects are initialized via a static, async `.from(...)` method. This is because some cryptographic dependencies are async. | ||
- Update documentation to match new implementation ([#60](https://github.com/MetaMask/key-tree/pull/60), [#49](https://github.com/MetaMask/key-tree/pull/49)) | ||
### Fixed | ||
- Remove obsolete Jest snapshots ([#41](https://github.com/MetaMask/key-tree/pull/41)) | ||
- Replace node symbol with private field ([#42](https://github.com/MetaMask/key-tree/pull/42)) | ||
## [3.0.1] | ||
@@ -47,3 +75,4 @@ ### Changed | ||
[Unreleased]: https://github.com/MetaMask/key-tree/compare/v3.0.1...HEAD | ||
[Unreleased]: https://github.com/MetaMask/key-tree/compare/v4.0.0...HEAD | ||
[4.0.0]: https://github.com/MetaMask/key-tree/compare/v3.0.1...v4.0.0 | ||
[3.0.1]: https://github.com/MetaMask/key-tree/compare/v3.0.0...v3.0.1 | ||
@@ -50,0 +79,0 @@ [3.0.0]: https://github.com/MetaMask/key-tree/compare/v2.0.1...v3.0.0 |
/// <reference types="node" /> | ||
import { BIP39Node, BIP44PurposeNodeToken, CoinTypeHDPathString, HardenedBIP32Node, BIP44Depth } from './constants'; | ||
import { JsonBIP44Node, BIP44Node, BIP44NodeInterface } from './BIP44Node'; | ||
import { BIP39Node, BIP44Depth, BIP44PurposeNodeToken, CoinTypeHDPathString, HardenedBIP32Node } from './constants'; | ||
import { BIP44Node, BIP44NodeInterface, JsonBIP44Node } from './BIP44Node'; | ||
import { CoinTypeToAddressIndices, getBIP44ChangePathString } from './utils'; | ||
import { SupportedCurve } from './curves'; | ||
export declare type CoinTypeHDPathTuple = [ | ||
@@ -20,6 +21,2 @@ BIP39Node, | ||
/** | ||
* Used to conceal the inner {@link BIP44Node} from consumers. | ||
*/ | ||
declare const InnerNode: unique symbol; | ||
/** | ||
* A wrapper object for BIP-44 `coin_type` keys. `coin_type` is the index | ||
@@ -40,8 +37,3 @@ * specifying the protocol for which deeper keys are intended. For the | ||
export declare class BIP44CoinTypeNode implements BIP44CoinTypeNodeInterface { | ||
private readonly [InnerNode]; | ||
get depth(): BIP44Depth; | ||
get key(): string; | ||
get keyBuffer(): Buffer; | ||
readonly path: CoinTypeHDPathString; | ||
readonly coin_type: number; | ||
#private; | ||
/** | ||
@@ -61,9 +53,61 @@ * Constructs a BIP-44 `coin_type` node. `coin_type` is the index | ||
* | ||
* @param nodeOrPathTuple - The {@link BIP44Node} or derivation path for the | ||
* key of this `coin_type` node. | ||
* @param json - The {@link JsonBIP44Node} for the key of this node. | ||
* @param coin_type - The coin_type index of this node. Must be a non-negative | ||
* integer. | ||
*/ | ||
constructor(nodeOrPathTuple: CoinTypeHDPathTuple | BIP44Node | JsonBIP44Node, coin_type?: number); | ||
static fromJSON(json: JsonBIP44Node, coin_type: number): Promise<BIP44CoinTypeNode>; | ||
/** | ||
* Constructs a BIP-44 `coin_type` node. `coin_type` is the index | ||
* specifying the protocol for which deeper keys are intended. For the | ||
* authoritative list of coin types, please see | ||
* [SLIP-44](https://github.com/satoshilabs/slips/blob/master/slip-0044.md). | ||
* | ||
* Recall that a BIP-44 HD tree path consists of the following nodes: | ||
* | ||
* `m / 44' / coin_type' / account' / change / address_index` | ||
* | ||
* With the following depths: | ||
* | ||
* `0 / 1 / 2 / 3 / 4 / 5` | ||
* | ||
* @param derivationPath - The derivation path for the key of this node. | ||
*/ | ||
static fromDerivationPath(derivationPath: CoinTypeHDPathTuple): Promise<BIP44CoinTypeNode>; | ||
/** | ||
* Constructs a BIP-44 `coin_type` node. `coin_type` is the index | ||
* specifying the protocol for which deeper keys are intended. For the | ||
* authoritative list of coin types, please see | ||
* [SLIP-44](https://github.com/satoshilabs/slips/blob/master/slip-0044.md). | ||
* | ||
* Recall that a BIP-44 HD tree path consists of the following nodes: | ||
* | ||
* `m / 44' / coin_type' / account' / change / address_index` | ||
* | ||
* With the following depths: | ||
* | ||
* `0 / 1 / 2 / 3 / 4 / 5` | ||
* | ||
* @param node - The {@link BIP44Node} for the key of this node. | ||
* @param coin_type - The coin_type index of this node. Must be a non-negative | ||
* integer. | ||
*/ | ||
static fromNode(node: BIP44Node, coin_type: number): Promise<BIP44CoinTypeNode>; | ||
readonly path: CoinTypeHDPathString; | ||
readonly coin_type: number; | ||
private constructor(); | ||
get depth(): BIP44Depth; | ||
get privateKeyBuffer(): Buffer | undefined; | ||
get publicKeyBuffer(): Buffer; | ||
get chainCodeBuffer(): Buffer; | ||
get privateKey(): string | undefined; | ||
get publicKey(): string; | ||
get compressedPublicKeyBuffer(): Buffer; | ||
get chainCode(): string; | ||
get address(): string; | ||
get parentFingerprint(): number; | ||
get fingerprint(): number; | ||
get index(): number; | ||
get curve(): SupportedCurve; | ||
get extendedKey(): string; | ||
/** | ||
* Derives a BIP-44 `address_index` key corresponding to the path of this | ||
@@ -88,3 +132,3 @@ * node and the specified `account`, `change`, and `address_index` values. | ||
*/ | ||
deriveBIP44AddressKey({ account, change, address_index, }: CoinTypeToAddressIndices): Buffer; | ||
deriveBIP44AddressKey({ account, change, address_index, }: CoinTypeToAddressIndices): Promise<BIP44Node>; | ||
toJSON(): JsonBIP44CoinTypeNode; | ||
@@ -109,10 +153,11 @@ } | ||
*/ | ||
export declare function deriveBIP44AddressKey(parentKeyOrNode: string | BIP44CoinTypeNode | JsonBIP44CoinTypeNode, { account, change, address_index }: CoinTypeToAddressIndices): Buffer; | ||
interface BIP44AddressKeyDeriver { | ||
export declare function deriveBIP44AddressKey(parentKeyOrNode: BIP44CoinTypeNode | JsonBIP44CoinTypeNode | string, { account, change, address_index }: CoinTypeToAddressIndices): Promise<BIP44Node>; | ||
declare type BIP44AddressKeyDeriver = { | ||
/** | ||
* @param address_index - The `address_index` value. | ||
* @param isHardened - Whether the derived index is hardened. | ||
* @returns The key corresponding to the path of this deriver and the | ||
* specified `address_index` value. | ||
*/ | ||
(address_index: number): Buffer; | ||
(address_index: number, isHardened?: boolean): Promise<BIP44Node>; | ||
/** | ||
@@ -135,3 +180,3 @@ * A human-readable representation of the derivation path of this deriver | ||
coin_type: number; | ||
} | ||
}; | ||
/** | ||
@@ -157,3 +202,3 @@ * Creates a function that derives BIP-44 address keys corresponding to the | ||
*/ | ||
export declare function getBIP44AddressKeyDeriver(node: BIP44CoinTypeNode | JsonBIP44CoinTypeNode, accountAndChangeIndices?: Omit<CoinTypeToAddressIndices, 'address_index'>): BIP44AddressKeyDeriver; | ||
export declare function getBIP44AddressKeyDeriver(node: BIP44CoinTypeNode | JsonBIP44CoinTypeNode | string, accountAndChangeIndices?: Omit<CoinTypeToAddressIndices, 'address_index'>): Promise<BIP44AddressKeyDeriver>; | ||
export {}; |
"use strict"; | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
if (kind === "m") throw new TypeError("Private method is not writable"); | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); | ||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; | ||
}; | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); | ||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); | ||
}; | ||
var _BIP44CoinTypeNode_node; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getBIP44AddressKeyDeriver = exports.deriveBIP44AddressKey = exports.BIP44CoinTypeNode = exports.BIP_44_COIN_TYPE_DEPTH = void 0; | ||
const constants_1 = require("./constants"); | ||
const BIP44Node_1 = require("./BIP44Node"); | ||
const utils_1 = require("./utils"); | ||
const SLIP10Node_1 = require("./SLIP10Node"); | ||
exports.BIP_44_COIN_TYPE_DEPTH = 2; | ||
/** | ||
* Used to conceal the inner {@link BIP44Node} from consumers. | ||
*/ | ||
const InnerNode = Symbol('_node'); | ||
/** | ||
* A wrapper object for BIP-44 `coin_type` keys. `coin_type` is the index | ||
@@ -27,2 +37,9 @@ * specifying the protocol for which deeper keys are intended. For the | ||
class BIP44CoinTypeNode { | ||
constructor(node, coin_type) { | ||
_BIP44CoinTypeNode_node.set(this, void 0); | ||
__classPrivateFieldSet(this, _BIP44CoinTypeNode_node, node, "f"); | ||
this.coin_type = coin_type; | ||
this.path = utils_1.getBIP44CoinTypePathString(coin_type); | ||
Object.freeze(this); | ||
} | ||
/** | ||
@@ -42,51 +59,112 @@ * Constructs a BIP-44 `coin_type` node. `coin_type` is the index | ||
* | ||
* @param nodeOrPathTuple - The {@link BIP44Node} or derivation path for the | ||
* key of this `coin_type` node. | ||
* @param json - The {@link JsonBIP44Node} for the key of this node. | ||
* @param coin_type - The coin_type index of this node. Must be a non-negative | ||
* integer. | ||
*/ | ||
constructor(nodeOrPathTuple, coin_type) { | ||
if (Array.isArray(nodeOrPathTuple)) { | ||
if (coin_type !== undefined) { | ||
throw new Error('Invalid parameters: May not specify both coin type and a derivation path. The coin type will be computed from the derivation path.'); | ||
} | ||
validateCoinTypeNodeDepth(nodeOrPathTuple.length - 1); | ||
this[InnerNode] = new BIP44Node_1.BIP44Node({ | ||
derivationPath: nodeOrPathTuple, | ||
}); | ||
// Split the bip32 string token and extract the coin_type index | ||
this.coin_type = Number.parseInt(nodeOrPathTuple[exports.BIP_44_COIN_TYPE_DEPTH].split(':')[1].replace(`'`, ''), 10); | ||
static async fromJSON(json, coin_type) { | ||
validateCoinType(coin_type); | ||
validateCoinTypeNodeDepth(json.depth); | ||
const node = await BIP44Node_1.BIP44Node.fromExtendedKey({ | ||
depth: json.depth, | ||
index: json.index, | ||
parentFingerprint: json.parentFingerprint, | ||
chainCode: utils_1.hexStringToBuffer(json.chainCode), | ||
privateKey: utils_1.nullableHexStringToBuffer(json.privateKey), | ||
publicKey: utils_1.hexStringToBuffer(json.publicKey), | ||
}); | ||
return new BIP44CoinTypeNode(node, coin_type); | ||
} | ||
/** | ||
* Constructs a BIP-44 `coin_type` node. `coin_type` is the index | ||
* specifying the protocol for which deeper keys are intended. For the | ||
* authoritative list of coin types, please see | ||
* [SLIP-44](https://github.com/satoshilabs/slips/blob/master/slip-0044.md). | ||
* | ||
* Recall that a BIP-44 HD tree path consists of the following nodes: | ||
* | ||
* `m / 44' / coin_type' / account' / change / address_index` | ||
* | ||
* With the following depths: | ||
* | ||
* `0 / 1 / 2 / 3 / 4 / 5` | ||
* | ||
* @param derivationPath - The derivation path for the key of this node. | ||
*/ | ||
static async fromDerivationPath(derivationPath) { | ||
validateCoinTypeNodeDepth(derivationPath.length - 1); | ||
const node = await BIP44Node_1.BIP44Node.fromDerivationPath({ | ||
derivationPath, | ||
}); | ||
// Split the bip32 string token and extract the coin_type index | ||
const coinType = Number.parseInt(derivationPath[exports.BIP_44_COIN_TYPE_DEPTH].split(':')[1].replace(`'`, ''), 10); | ||
return new BIP44CoinTypeNode(node, coinType); | ||
} | ||
/** | ||
* Constructs a BIP-44 `coin_type` node. `coin_type` is the index | ||
* specifying the protocol for which deeper keys are intended. For the | ||
* authoritative list of coin types, please see | ||
* [SLIP-44](https://github.com/satoshilabs/slips/blob/master/slip-0044.md). | ||
* | ||
* Recall that a BIP-44 HD tree path consists of the following nodes: | ||
* | ||
* `m / 44' / coin_type' / account' / change / address_index` | ||
* | ||
* With the following depths: | ||
* | ||
* `0 / 1 / 2 / 3 / 4 / 5` | ||
* | ||
* @param node - The {@link BIP44Node} for the key of this node. | ||
* @param coin_type - The coin_type index of this node. Must be a non-negative | ||
* integer. | ||
*/ | ||
static async fromNode(node, coin_type) { | ||
if (!(node instanceof BIP44Node_1.BIP44Node)) { | ||
throw new Error('Invalid node: Expected an instance of BIP44Node.'); | ||
} | ||
else { | ||
validateCoinTypeNodeDepth(nodeOrPathTuple.depth); | ||
validateCoinTypeParentKey(nodeOrPathTuple.key); | ||
const keyBuffer = nodeOrPathTuple instanceof BIP44Node_1.BIP44Node | ||
? nodeOrPathTuple.keyBuffer | ||
: utils_1.base64StringToBuffer(nodeOrPathTuple.key); | ||
if (typeof coin_type !== 'number' || | ||
!Number.isInteger(coin_type) || | ||
coin_type < 0) { | ||
throw new Error('Invalid coin type: The specified coin type must be a non-negative integer number.'); | ||
} | ||
this.coin_type = coin_type; | ||
this[InnerNode] = | ||
nodeOrPathTuple instanceof BIP44Node_1.BIP44Node | ||
? nodeOrPathTuple | ||
: new BIP44Node_1.BIP44Node({ | ||
depth: exports.BIP_44_COIN_TYPE_DEPTH, | ||
key: keyBuffer, | ||
}); | ||
} | ||
this.path = utils_1.getBIP44CoinTypePathString(this.coin_type); | ||
Object.freeze(this); | ||
validateCoinType(coin_type); | ||
validateCoinTypeNodeDepth(node.depth); | ||
return new BIP44CoinTypeNode(node, coin_type); | ||
} | ||
get depth() { | ||
return this[InnerNode].depth; | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").depth; | ||
} | ||
get key() { | ||
return this[InnerNode].key; | ||
get privateKeyBuffer() { | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").privateKeyBuffer; | ||
} | ||
get keyBuffer() { | ||
return this[InnerNode].keyBuffer; | ||
get publicKeyBuffer() { | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").publicKeyBuffer; | ||
} | ||
get chainCodeBuffer() { | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").chainCodeBuffer; | ||
} | ||
get privateKey() { | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").privateKey; | ||
} | ||
get publicKey() { | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").publicKey; | ||
} | ||
get compressedPublicKeyBuffer() { | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").compressedPublicKeyBuffer; | ||
} | ||
get chainCode() { | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").chainCode; | ||
} | ||
get address() { | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").address; | ||
} | ||
get parentFingerprint() { | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").parentFingerprint; | ||
} | ||
get fingerprint() { | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").fingerprint; | ||
} | ||
get index() { | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").index; | ||
} | ||
get curve() { | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").curve; | ||
} | ||
get extendedKey() { | ||
return __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").extendedKey; | ||
} | ||
/** | ||
@@ -112,10 +190,11 @@ * Derives a BIP-44 `address_index` key corresponding to the path of this | ||
*/ | ||
deriveBIP44AddressKey({ account = 0, change = 0, address_index, }) { | ||
return this[InnerNode].derive(utils_1.getBIP44CoinTypeToAddressPathTuple({ account, change, address_index })).keyBuffer; | ||
async deriveBIP44AddressKey({ account = 0, change = 0, address_index, }) { | ||
return await __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").derive(utils_1.getBIP44CoinTypeToAddressPathTuple({ account, change, address_index })); | ||
} | ||
toJSON() { | ||
return Object.assign(Object.assign({}, this[InnerNode].toJSON()), { coin_type: this.coin_type, path: this.path }); | ||
return Object.assign(Object.assign({}, __classPrivateFieldGet(this, _BIP44CoinTypeNode_node, "f").toJSON()), { coin_type: this.coin_type, path: this.path }); | ||
} | ||
} | ||
exports.BIP44CoinTypeNode = BIP44CoinTypeNode; | ||
_BIP44CoinTypeNode_node = new WeakMap(); | ||
/** | ||
@@ -133,10 +212,12 @@ * Validates the depth of a `coin_type` node. Simply, ensures that it is the | ||
/** | ||
* Validates a `coin_type` Base64 string key. "Parent" is in the name because | ||
* it's also in the message that's thrown on validation failure. | ||
* Validates that the coin type is a non-negative integer number. An error is | ||
* thrown if validation fails. | ||
* | ||
* @param parentKey The `coin_type` key to validate. | ||
* @param coin_type - The coin type to validate. | ||
*/ | ||
function validateCoinTypeParentKey(parentKey) { | ||
if (!utils_1.isValidBase64StringKey(parentKey)) { | ||
throw new Error('Invalid parent key: Must be a non-zero 64-byte key.'); | ||
function validateCoinType(coin_type) { | ||
if (typeof coin_type !== 'number' || | ||
!Number.isInteger(coin_type) || | ||
coin_type < 0) { | ||
throw new Error('Invalid coin type: The specified coin type must be a non-negative integer number.'); | ||
} | ||
@@ -161,20 +242,14 @@ } | ||
*/ | ||
function deriveBIP44AddressKey(parentKeyOrNode, { account = 0, change = 0, address_index }) { | ||
if (typeof parentKeyOrNode === 'string') { | ||
validateCoinTypeParentKey(parentKeyOrNode); | ||
} | ||
else { | ||
validateCoinTypeNodeDepth(parentKeyOrNode.depth); | ||
validateCoinTypeParentKey(parentKeyOrNode.key); | ||
} | ||
let keyBuffer; | ||
if (parentKeyOrNode instanceof BIP44CoinTypeNode) { | ||
keyBuffer = parentKeyOrNode.keyBuffer; | ||
} | ||
else { | ||
keyBuffer = utils_1.base64StringToBuffer(typeof parentKeyOrNode === 'string' | ||
? parentKeyOrNode | ||
: parentKeyOrNode.key); | ||
} | ||
return BIP44Node_1.deriveChildNode(keyBuffer, exports.BIP_44_COIN_TYPE_DEPTH, utils_1.getBIP44CoinTypeToAddressPathTuple({ account, change, address_index })).keyBuffer; | ||
async function deriveBIP44AddressKey(parentKeyOrNode, { account = 0, change = 0, address_index }) { | ||
const path = utils_1.getBIP44CoinTypeToAddressPathTuple({ | ||
account, | ||
change, | ||
address_index, | ||
}); | ||
const node = await getNode(parentKeyOrNode); | ||
const childNode = await SLIP10Node_1.deriveChildNode({ | ||
path, | ||
node, | ||
}); | ||
return new BIP44Node_1.BIP44Node(childNode); | ||
} | ||
@@ -202,21 +277,22 @@ exports.deriveBIP44AddressKey = deriveBIP44AddressKey; | ||
*/ | ||
function getBIP44AddressKeyDeriver(node, accountAndChangeIndices) { | ||
async function getBIP44AddressKeyDeriver(node, accountAndChangeIndices) { | ||
const { account = 0, change = 0 } = accountAndChangeIndices || {}; | ||
const { key, depth } = node; | ||
validateCoinTypeNodeDepth(depth); | ||
validateCoinTypeParentKey(key); | ||
const parentKeyBuffer = node instanceof BIP44CoinTypeNode | ||
? node.keyBuffer | ||
: utils_1.base64StringToBuffer(key); | ||
const actualNode = await getNode(node); | ||
const accountNode = utils_1.getHardenedBIP32NodeToken(account); | ||
const changeNode = utils_1.getUnhardenedBIP32NodeToken(change); | ||
const bip44AddressKeyDeriver = (address_index) => { | ||
return BIP44Node_1.deriveChildNode(parentKeyBuffer, exports.BIP_44_COIN_TYPE_DEPTH, [ | ||
accountNode, | ||
changeNode, | ||
utils_1.getUnhardenedBIP32NodeToken(address_index), | ||
]).keyBuffer; | ||
const changeNode = utils_1.getBIP32NodeToken(change); | ||
const bip44AddressKeyDeriver = async (address_index, isHardened = false) => { | ||
const slip10Node = await SLIP10Node_1.deriveChildNode({ | ||
path: [ | ||
accountNode, | ||
changeNode, | ||
isHardened | ||
? utils_1.getHardenedBIP32NodeToken(address_index) | ||
: utils_1.getUnhardenedBIP32NodeToken(address_index), | ||
], | ||
node: actualNode, | ||
}); | ||
return new BIP44Node_1.BIP44Node(slip10Node); | ||
}; | ||
bip44AddressKeyDeriver.coin_type = node.coin_type; | ||
bip44AddressKeyDeriver.path = utils_1.getBIP44ChangePathString(node.path, { | ||
bip44AddressKeyDeriver.coin_type = actualNode.coin_type; | ||
bip44AddressKeyDeriver.path = utils_1.getBIP44ChangePathString(actualNode.path, { | ||
account, | ||
@@ -229,2 +305,23 @@ change, | ||
exports.getBIP44AddressKeyDeriver = getBIP44AddressKeyDeriver; | ||
/** | ||
* Get a BIP-44 coin type node from a JSON node or extended key string. If an existing coin type | ||
* node is provided, the same node is returned. | ||
* | ||
* The depth of the node is validated to be a valid coin type node. | ||
* | ||
* @param node - A BIP-44 coin type node, JSON node or extended key. | ||
*/ | ||
async function getNode(node) { | ||
if (node instanceof BIP44CoinTypeNode) { | ||
validateCoinTypeNodeDepth(node.depth); | ||
return node; | ||
} | ||
if (typeof node === 'string') { | ||
const bip44Node = await BIP44Node_1.BIP44Node.fromExtendedKey(node); | ||
const coinTypeNode = await BIP44CoinTypeNode.fromNode(bip44Node, bip44Node.index - constants_1.BIP_32_HARDENED_OFFSET); | ||
validateCoinTypeNodeDepth(coinTypeNode.depth); | ||
return coinTypeNode; | ||
} | ||
return BIP44CoinTypeNode.fromJSON(node, node.coin_type); | ||
} | ||
//# sourceMappingURL=BIP44CoinTypeNode.js.map |
/// <reference types="node" /> | ||
import { BIP44Depth, PartialHDPathTuple, RootedHDPathTuple } from './constants'; | ||
import { BIP44Depth, PartialHDPathTuple, RootedSLIP10PathTuple } from './constants'; | ||
import { SLIP10Node } from './SLIP10Node'; | ||
import { SupportedCurve } from './curves'; | ||
declare type BIP44ExtendedKeyOptions = { | ||
readonly depth: number; | ||
readonly parentFingerprint: number; | ||
readonly index: number; | ||
readonly chainCode: Buffer | string; | ||
readonly privateKey?: Buffer | string; | ||
readonly publicKey?: Buffer | string; | ||
}; | ||
declare type BIP44DerivationPathOptions = { | ||
readonly derivationPath: RootedSLIP10PathTuple; | ||
}; | ||
/** | ||
@@ -8,3 +21,3 @@ * A wrapper for BIP-44 Hierarchical Deterministic (HD) tree nodes, i.e. | ||
*/ | ||
export interface JsonBIP44Node { | ||
export declare type JsonBIP44Node = { | ||
/** | ||
@@ -23,13 +36,25 @@ * The 0-indexed BIP-44 path depth of this node. | ||
/** | ||
* The Base64 string representation of the key material for this node. | ||
* The fingerprint of the parent key, or 0 if this is a master node. | ||
*/ | ||
readonly key: string; | ||
} | ||
export declare type BIP44NodeInterface = JsonBIP44Node & { | ||
readonly parentFingerprint: number; | ||
/** | ||
* The raw bytes of the key material for this node, as a Node.js Buffer or | ||
* browser-equivalent. | ||
* The index of the node, or 0 if this is a master node. | ||
*/ | ||
keyBuffer: Buffer; | ||
readonly index: number; | ||
/** | ||
* The hexadecimal string representation of the private key for this node. | ||
* May be `undefined` if the node is a public node. | ||
*/ | ||
readonly privateKey?: string; | ||
/** | ||
* The hexadecimal string representation of the public key for this node. | ||
*/ | ||
readonly publicKey: string; | ||
/** | ||
* The hexadecimal string representation of the chain code for this node. | ||
*/ | ||
readonly chainCode: string; | ||
}; | ||
export declare type BIP44NodeInterface = JsonBIP44Node & { | ||
/** | ||
* @returns A JSON-compatible representation of this node's data fields. | ||
@@ -39,7 +64,2 @@ */ | ||
}; | ||
interface BIP44NodeOptions { | ||
readonly depth?: BIP44Depth; | ||
readonly key?: Buffer | string; | ||
readonly derivationPath?: RootedHDPathTuple; | ||
} | ||
/** | ||
@@ -54,18 +74,36 @@ * A wrapper for BIP-44 Hierarchical Deterministic (HD) tree nodes, i.e. | ||
export declare class BIP44Node implements BIP44NodeInterface { | ||
readonly depth: BIP44Depth; | ||
get key(): string; | ||
readonly keyBuffer: Buffer; | ||
#private; | ||
/** | ||
* Initializes a BIP-44 node. Accepts either: | ||
* - An existing 64-byte BIP-44 key, and its **0-indexed** BIP-44 path depth. | ||
* - The key may be in the form of a hexadecimal string, Base64 string, or a | ||
* {@link Buffer}. | ||
* - A BIP-44 derivation path starting with an `m` node. | ||
* - At present, the `m` node must be a BIP-39 node, given as a string of | ||
* the form `bip39:MNEMONIC`, where `MNEMONIC` is a space-separated list | ||
* of BIP-39 seed phrase words. | ||
* Wrapper of the {@link fromExtendedKey} function. Refer to that function | ||
* for documentation. | ||
* | ||
* @param json - The JSON representation of a SLIP-10 node. | ||
*/ | ||
static fromJSON(json: JsonBIP44Node): Promise<BIP44Node>; | ||
/** | ||
* Create a new BIP-44 node from a key and chain code. You must specify | ||
* either a private key or a public key. When specifying a private key, | ||
* the public key will be derived from the private key. | ||
* | ||
* All parameters are stringently validated, and an error is thrown if | ||
* validation fails. | ||
* | ||
* @param options - An object containing the extended key, or an extended | ||
* public (xpub) or private (xprv) key. | ||
* @param options.depth The depth of the node. | ||
* @param options.privateKey The private key for the node. | ||
* @param options.publicKey The public key for the node. If a private key is | ||
* specified, this parameter is ignored. | ||
* @param options.chainCode The chain code for the node. | ||
*/ | ||
static fromExtendedKey(options: BIP44ExtendedKeyOptions | string): Promise<BIP44Node>; | ||
/** | ||
* Create a new BIP-44 node from a derivation path. The derivation path | ||
* must be rooted, i.e. it must begin with a BIP-39 node, given as a string of | ||
* the form `bip39:MNEMONIC`, where `MNEMONIC` is a space-separated list of | ||
* BIP-39 seed phrase words. | ||
* | ||
* All parameters are stringently validated, and an error is thrown if | ||
* validation fails. | ||
* | ||
* Recall that a BIP-44 HD tree path consists of the following nodes: | ||
@@ -79,20 +117,25 @@ * | ||
* | ||
* @param options - Options bag. | ||
* @param options.depth - The 0-indexed BIP-44 tree depth of the `key`, if | ||
* specified. | ||
* @param options.key - The key of this node. Mutually exclusive with | ||
* `derivationPath`, and requires a `depth` to be specified. | ||
* @param options.derivationPath - The rooted HD tree path that will be used | ||
* to derive the key of this node. Mutually exclusive with `key`. | ||
* @param derivationPath The rooted HD tree path that will be used | ||
* to derive the key of this node. | ||
*/ | ||
constructor({ depth, key, derivationPath }: BIP44NodeOptions); | ||
static fromDerivationPath({ derivationPath, }: BIP44DerivationPathOptions): Promise<BIP44Node>; | ||
get depth(): BIP44Depth; | ||
get privateKeyBuffer(): Buffer | undefined; | ||
get publicKeyBuffer(): Buffer; | ||
get chainCodeBuffer(): Buffer; | ||
get privateKey(): string | undefined; | ||
get publicKey(): string; | ||
get compressedPublicKeyBuffer(): Buffer; | ||
get chainCode(): string; | ||
get address(): string; | ||
get parentFingerprint(): number; | ||
get fingerprint(): number; | ||
get index(): number; | ||
get extendedKey(): string; | ||
get curve(): SupportedCurve; | ||
constructor(node: SLIP10Node); | ||
/** | ||
* Constructor helper for validating and parsing the `key` parameter. An error | ||
* is thrown if validation fails. | ||
* | ||
* @param key - The key to parse. | ||
* @returns A {@link Buffer}, or `undefined` if no key parameter was | ||
* specified. | ||
* Returns a neutered version of this node, i.e. a node without a private key. | ||
*/ | ||
private static _parseKey; | ||
neuter(): BIP44Node; | ||
/** | ||
@@ -118,13 +161,13 @@ * Derives a child of the key contains be this node and returns a new | ||
*/ | ||
derive(path: PartialHDPathTuple): BIP44Node; | ||
derive(path: PartialHDPathTuple): Promise<BIP44Node>; | ||
toJSON(): JsonBIP44Node; | ||
} | ||
/** | ||
* Derives a child key from the given parent key, as a {@link BIP44Node}. | ||
* @param parentKey - The parent key to derive from. | ||
* @param parentDepth - The depth of the parent key. | ||
* @param pathToChild - The path to the child node / key. | ||
* @returns The {@link BIP44Node} corresponding to the derived child key. | ||
* Validates a BIP-44 path depth. Effectively, asserts that the depth is an | ||
* integer `number` N such that 0 <= N <= 5. Throws an error if validation | ||
* fails. | ||
* | ||
* @param depth - The depth to validate. | ||
*/ | ||
export declare function deriveChildNode(parentKey: Buffer, parentDepth: BIP44Depth, pathToChild: PartialHDPathTuple): BIP44Node; | ||
export declare function validateBIP44Depth(depth: unknown): asserts depth is BIP44Depth; | ||
export {}; |
"use strict"; | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
if (kind === "m") throw new TypeError("Private method is not writable"); | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); | ||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; | ||
}; | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); | ||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); | ||
}; | ||
var _BIP44Node_node; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.deriveChildNode = exports.BIP44Node = void 0; | ||
const derivation_1 = require("./derivation"); | ||
exports.validateBIP44Depth = exports.BIP44Node = void 0; | ||
const constants_1 = require("./constants"); | ||
const utils_1 = require("./utils"); | ||
const SLIP10Node_1 = require("./SLIP10Node"); | ||
const extended_keys_1 = require("./extended-keys"); | ||
/** | ||
@@ -16,15 +29,77 @@ * A wrapper for BIP-44 Hierarchical Deterministic (HD) tree nodes, i.e. | ||
class BIP44Node { | ||
constructor(node) { | ||
_BIP44Node_node.set(this, void 0); | ||
__classPrivateFieldSet(this, _BIP44Node_node, node, "f"); | ||
Object.freeze(this); | ||
} | ||
/** | ||
* Initializes a BIP-44 node. Accepts either: | ||
* - An existing 64-byte BIP-44 key, and its **0-indexed** BIP-44 path depth. | ||
* - The key may be in the form of a hexadecimal string, Base64 string, or a | ||
* {@link Buffer}. | ||
* - A BIP-44 derivation path starting with an `m` node. | ||
* - At present, the `m` node must be a BIP-39 node, given as a string of | ||
* the form `bip39:MNEMONIC`, where `MNEMONIC` is a space-separated list | ||
* of BIP-39 seed phrase words. | ||
* Wrapper of the {@link fromExtendedKey} function. Refer to that function | ||
* for documentation. | ||
* | ||
* @param json - The JSON representation of a SLIP-10 node. | ||
*/ | ||
static async fromJSON(json) { | ||
return BIP44Node.fromExtendedKey(json); | ||
} | ||
/** | ||
* Create a new BIP-44 node from a key and chain code. You must specify | ||
* either a private key or a public key. When specifying a private key, | ||
* the public key will be derived from the private key. | ||
* | ||
* All parameters are stringently validated, and an error is thrown if | ||
* validation fails. | ||
* | ||
* @param options - An object containing the extended key, or an extended | ||
* public (xpub) or private (xprv) key. | ||
* @param options.depth The depth of the node. | ||
* @param options.privateKey The private key for the node. | ||
* @param options.publicKey The public key for the node. If a private key is | ||
* specified, this parameter is ignored. | ||
* @param options.chainCode The chain code for the node. | ||
*/ | ||
static async fromExtendedKey(options) { | ||
if (typeof options === 'string') { | ||
const extendedKey = extended_keys_1.decodeExtendedKey(options); | ||
const { chainCode, depth, parentFingerprint, index } = extendedKey; | ||
if (extendedKey.version === extended_keys_1.PRIVATE_KEY_VERSION) { | ||
const { privateKey } = extendedKey; | ||
return BIP44Node.fromExtendedKey({ | ||
depth, | ||
parentFingerprint, | ||
index, | ||
privateKey, | ||
chainCode, | ||
}); | ||
} | ||
const { publicKey } = extendedKey; | ||
return BIP44Node.fromExtendedKey({ | ||
depth, | ||
parentFingerprint, | ||
index, | ||
publicKey, | ||
chainCode, | ||
}); | ||
} | ||
const { privateKey, publicKey, chainCode, depth, parentFingerprint, index, } = options; | ||
validateBIP44Depth(depth); | ||
const node = await SLIP10Node_1.SLIP10Node.fromExtendedKey({ | ||
privateKey, | ||
publicKey, | ||
chainCode, | ||
depth, | ||
parentFingerprint, | ||
index, | ||
curve: 'secp256k1', | ||
}); | ||
return new BIP44Node(node); | ||
} | ||
/** | ||
* Create a new BIP-44 node from a derivation path. The derivation path | ||
* must be rooted, i.e. it must begin with a BIP-39 node, given as a string of | ||
* the form `bip39:MNEMONIC`, where `MNEMONIC` is a space-separated list of | ||
* BIP-39 seed phrase words. | ||
* | ||
* All parameters are stringently validated, and an error is thrown if | ||
* validation fails. | ||
* | ||
* Recall that a BIP-44 HD tree path consists of the following nodes: | ||
@@ -38,75 +113,71 @@ * | ||
* | ||
* @param options - Options bag. | ||
* @param options.depth - The 0-indexed BIP-44 tree depth of the `key`, if | ||
* specified. | ||
* @param options.key - The key of this node. Mutually exclusive with | ||
* `derivationPath`, and requires a `depth` to be specified. | ||
* @param options.derivationPath - The rooted HD tree path that will be used | ||
* to derive the key of this node. Mutually exclusive with `key`. | ||
* @param derivationPath The rooted HD tree path that will be used | ||
* to derive the key of this node. | ||
*/ | ||
constructor({ depth, key, derivationPath }) { | ||
const _key = BIP44Node._parseKey(key); | ||
if (derivationPath) { | ||
if (_key) { | ||
throw new Error('Invalid parameters: May not specify a derivation path if a key is specified. Initialize the node with just the parent key and its depth, then call BIP44Node.derive() with your desired path.'); | ||
} | ||
if (depth) { | ||
throw new Error('Invalid parameters: May not specify a depth if a derivation path is specified. The depth will be calculated from the path.'); | ||
} | ||
if (derivationPath.length === 0) { | ||
throw new Error('Invalid derivation path: May not specify an empty derivation path.'); | ||
} | ||
const _depth = derivationPath.length - 1; | ||
validateBIP44Depth(_depth); | ||
this.depth = _depth; | ||
validateBIP44DerivationPath(derivationPath, constants_1.MIN_BIP_44_DEPTH); | ||
this.keyBuffer = derivation_1.deriveKeyFromPath(derivationPath, undefined, this.depth); | ||
static async fromDerivationPath({ derivationPath, }) { | ||
validateBIP44Depth(derivationPath.length - 1); | ||
validateBIP44DerivationPath(derivationPath, constants_1.MIN_BIP_44_DEPTH); | ||
const node = await SLIP10Node_1.SLIP10Node.fromDerivationPath({ | ||
derivationPath, | ||
curve: 'secp256k1', | ||
}); | ||
return new BIP44Node(node); | ||
} | ||
get depth() { | ||
return __classPrivateFieldGet(this, _BIP44Node_node, "f").depth; | ||
} | ||
get privateKeyBuffer() { | ||
return __classPrivateFieldGet(this, _BIP44Node_node, "f").privateKeyBuffer; | ||
} | ||
get publicKeyBuffer() { | ||
return __classPrivateFieldGet(this, _BIP44Node_node, "f").publicKeyBuffer; | ||
} | ||
get chainCodeBuffer() { | ||
return __classPrivateFieldGet(this, _BIP44Node_node, "f").chainCodeBuffer; | ||
} | ||
get privateKey() { | ||
return __classPrivateFieldGet(this, _BIP44Node_node, "f").privateKey; | ||
} | ||
get publicKey() { | ||
return __classPrivateFieldGet(this, _BIP44Node_node, "f").publicKey; | ||
} | ||
get compressedPublicKeyBuffer() { | ||
return __classPrivateFieldGet(this, _BIP44Node_node, "f").compressedPublicKeyBuffer; | ||
} | ||
get chainCode() { | ||
return __classPrivateFieldGet(this, _BIP44Node_node, "f").chainCode; | ||
} | ||
get address() { | ||
return __classPrivateFieldGet(this, _BIP44Node_node, "f").address; | ||
} | ||
get parentFingerprint() { | ||
return __classPrivateFieldGet(this, _BIP44Node_node, "f").parentFingerprint; | ||
} | ||
get fingerprint() { | ||
return __classPrivateFieldGet(this, _BIP44Node_node, "f").fingerprint; | ||
} | ||
get index() { | ||
return __classPrivateFieldGet(this, _BIP44Node_node, "f").index; | ||
} | ||
get extendedKey() { | ||
const data = { | ||
depth: this.depth, | ||
parentFingerprint: this.parentFingerprint, | ||
index: this.index, | ||
chainCode: this.chainCodeBuffer, | ||
}; | ||
if (this.privateKeyBuffer) { | ||
return extended_keys_1.encodeExtendedKey(Object.assign(Object.assign({}, data), { version: extended_keys_1.PRIVATE_KEY_VERSION, privateKey: this.privateKeyBuffer })); | ||
} | ||
else if (_key) { | ||
validateBIP44Depth(depth); | ||
this.depth = depth; | ||
this.keyBuffer = _key; | ||
} | ||
else { | ||
throw new Error('Invalid parameters: Must specify either key or derivation path.'); | ||
} | ||
Object.freeze(this); | ||
return extended_keys_1.encodeExtendedKey(Object.assign(Object.assign({}, data), { version: extended_keys_1.PUBLIC_KEY_VERSION, publicKey: this.publicKeyBuffer })); | ||
} | ||
get key() { | ||
return utils_1.bufferToBase64String(this.keyBuffer); | ||
get curve() { | ||
return __classPrivateFieldGet(this, _BIP44Node_node, "f").curve; | ||
} | ||
/** | ||
* Constructor helper for validating and parsing the `key` parameter. An error | ||
* is thrown if validation fails. | ||
* | ||
* @param key - The key to parse. | ||
* @returns A {@link Buffer}, or `undefined` if no key parameter was | ||
* specified. | ||
* Returns a neutered version of this node, i.e. a node without a private key. | ||
*/ | ||
static _parseKey(key) { | ||
if (key === undefined || key === null) { | ||
return undefined; | ||
} | ||
let bufferKey; | ||
if (Buffer.isBuffer(key)) { | ||
if (!utils_1.isValidBufferKey(key)) { | ||
throw new Error('Invalid buffer key: Must be a 64-byte, non-empty Buffer.'); | ||
} | ||
bufferKey = key; | ||
} | ||
else if (typeof key === 'string') { | ||
if (utils_1.isValidHexStringKey(key)) { | ||
bufferKey = utils_1.hexStringToBuffer(key); | ||
} | ||
else if (utils_1.isValidBase64StringKey(key)) { | ||
bufferKey = utils_1.base64StringToBuffer(key); | ||
} | ||
else { | ||
throw new Error('Invalid string key: Must be a 64-byte hexadecimal or Base64 string.'); | ||
} | ||
} | ||
else { | ||
throw new Error(`Invalid key: Must be a Buffer or string if specified. Received: "${typeof key}"`); | ||
} | ||
return bufferKey; | ||
neuter() { | ||
const node = __classPrivateFieldGet(this, _BIP44Node_node, "f").neuter(); | ||
return new BIP44Node(node); | ||
} | ||
@@ -133,7 +204,11 @@ /** | ||
*/ | ||
derive(path) { | ||
async derive(path) { | ||
if (this.depth === constants_1.MAX_BIP_44_DEPTH) { | ||
throw new Error('Illegal operation: This HD tree node is already a leaf node.'); | ||
} | ||
return deriveChildNode(this.keyBuffer, this.depth, path); | ||
const newDepth = this.depth + path.length; | ||
validateBIP44Depth(newDepth); | ||
validateBIP44DerivationPath(path, (this.depth + 1)); | ||
const node = await __classPrivateFieldGet(this, _BIP44Node_node, "f").derive(path); | ||
return new BIP44Node(node); | ||
} | ||
@@ -144,3 +219,7 @@ // This is documented in the interface of this class. | ||
depth: this.depth, | ||
key: this.key, | ||
parentFingerprint: this.parentFingerprint, | ||
index: this.index, | ||
privateKey: this.privateKey, | ||
publicKey: this.publicKey, | ||
chainCode: this.chainCode, | ||
}; | ||
@@ -150,25 +229,4 @@ } | ||
exports.BIP44Node = BIP44Node; | ||
_BIP44Node_node = new WeakMap(); | ||
/** | ||
* Derives a child key from the given parent key, as a {@link BIP44Node}. | ||
* @param parentKey - The parent key to derive from. | ||
* @param parentDepth - The depth of the parent key. | ||
* @param pathToChild - The path to the child node / key. | ||
* @returns The {@link BIP44Node} corresponding to the derived child key. | ||
*/ | ||
function deriveChildNode(parentKey, parentDepth, pathToChild) { | ||
if (pathToChild.length === 0) { | ||
throw new Error('Invalid HD tree derivation path: Deriving a path of length 0 is not defined'); | ||
} | ||
// Note that we do not subtract 1 from the length of the path to the child, | ||
// unlike when we calculate the depth of a rooted path. | ||
const newDepth = (parentDepth + pathToChild.length); | ||
validateBIP44Depth(newDepth); | ||
validateBIP44DerivationPath(pathToChild, (parentDepth + 1)); | ||
return new BIP44Node({ | ||
depth: newDepth, | ||
key: derivation_1.deriveKeyFromPath(pathToChild, parentKey), | ||
}); | ||
} | ||
exports.deriveChildNode = deriveChildNode; | ||
/** | ||
* Validates a BIP-44 path depth. Effectively, asserts that the depth is an | ||
@@ -181,9 +239,8 @@ * integer `number` N such that 0 <= N <= 5. Throws an error if validation | ||
function validateBIP44Depth(depth) { | ||
if (typeof depth !== 'number' || | ||
!Number.isInteger(depth) || | ||
depth < constants_1.MIN_BIP_44_DEPTH || | ||
depth > constants_1.MAX_BIP_44_DEPTH) { | ||
SLIP10Node_1.validateBIP32Depth(depth); | ||
if (depth < constants_1.MIN_BIP_44_DEPTH || depth > constants_1.MAX_BIP_44_DEPTH) { | ||
throw new Error(`Invalid HD tree path depth: The depth must be a positive integer N such that 0 <= N <= 5. Received: "${depth}"`); | ||
} | ||
} | ||
exports.validateBIP44Depth = validateBIP44Depth; | ||
/** | ||
@@ -214,3 +271,3 @@ * Ensures that the given derivation is valid by BIP-44. | ||
if (nodeToken !== constants_1.BIP44PurposeNodeToken) { | ||
throw new Error(`Invalid derivation path: The "purpose" node node (depth 1) must be the string "${constants_1.BIP44PurposeNodeToken}".`); | ||
throw new Error(`Invalid derivation path: The "purpose" node (depth 1) must be the string "${constants_1.BIP44PurposeNodeToken}".`); | ||
} | ||
@@ -229,9 +286,9 @@ break; | ||
case 4: | ||
if (!constants_1.BIP_32_PATH_REGEX.test(nodeToken) || utils_1.isHardened(nodeToken)) { | ||
throw new Error('Invalid derivation path: The "change" node (depth 4) must be an unhardened BIP-32 node.'); | ||
if (!constants_1.BIP_32_PATH_REGEX.test(nodeToken)) { | ||
throw new Error('Invalid derivation path: The "change" node (depth 4) must be a BIP-32 node.'); | ||
} | ||
break; | ||
case constants_1.MAX_BIP_44_DEPTH: // 5 | ||
if (!constants_1.BIP_32_PATH_REGEX.test(nodeToken) || utils_1.isHardened(nodeToken)) { | ||
throw new Error('Invalid derivation path: The "address_index" node (depth 5) must be an unhardened BIP-32 node.'); | ||
if (!constants_1.BIP_32_PATH_REGEX.test(nodeToken)) { | ||
throw new Error('Invalid derivation path: The "address_index" node (depth 5) must be a BIP-32 node.'); | ||
} | ||
@@ -238,0 +295,0 @@ break; |
@@ -1,8 +0,6 @@ | ||
export declare const BUFFER_KEY_LENGTH: 64; | ||
export declare const BASE_64_KEY_LENGTH: 88; | ||
export declare const BASE_64_ZERO: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="; | ||
export declare const HEXADECIMAL_KEY_LENGTH: 128; | ||
export declare const BASE_64_REGEX: RegExp; | ||
export declare const MIN_BIP_44_DEPTH: 0; | ||
export declare const MAX_BIP_44_DEPTH: 5; | ||
export declare const BUFFER_EXTENDED_KEY_LENGTH = 64; | ||
export declare const BUFFER_KEY_LENGTH = 32; | ||
export declare const HEXADECIMAL_KEY_LENGTH = 64; | ||
export declare const MIN_BIP_44_DEPTH = 0; | ||
export declare const MAX_BIP_44_DEPTH = 5; | ||
export declare type MinBIP44Depth = typeof MIN_BIP_44_DEPTH; | ||
@@ -29,2 +27,3 @@ export declare type MaxBIP44Depth = typeof MAX_BIP_44_DEPTH; | ||
export declare const BIP_39_PATH_REGEX: RegExp; | ||
export declare const BIP_32_HARDENED_OFFSET = 2147483648; | ||
declare type HDPathString0 = AnonymizedBIP39Node; | ||
@@ -34,4 +33,4 @@ declare type HDPathString1 = `${HDPathString0} / ${HardenedBIP32Node}`; | ||
declare type HDPathString3 = `${HDPathString2} / ${HardenedBIP32Node}`; | ||
declare type HDPathString4 = `${HDPathString3} / ${UnhardenedBIP32Node}`; | ||
declare type HDPathString5 = `${HDPathString4} / ${UnhardenedBIP32Node}`; | ||
declare type HDPathString4 = `${HDPathString3} / ${BIP32Node}`; | ||
declare type HDPathString5 = `${HDPathString4} / ${BIP32Node}`; | ||
export declare type CoinTypeHDPathString = HDPathString2; | ||
@@ -45,4 +44,4 @@ export declare type ChangeHDPathString = HDPathString4; | ||
declare type RootedHDPathTuple3 = readonly [...RootedHDPathTuple2, HardenedBIP32Node]; | ||
declare type RootedHDPathTuple4 = readonly [...RootedHDPathTuple3, UnhardenedBIP32Node]; | ||
declare type RootedHDPathTuple5 = readonly [...RootedHDPathTuple4, UnhardenedBIP32Node]; | ||
declare type RootedHDPathTuple4 = readonly [...RootedHDPathTuple3, BIP32Node]; | ||
declare type RootedHDPathTuple5 = readonly [...RootedHDPathTuple4, BIP32Node]; | ||
export declare type RootedHDPathTuple = RootedHDPathTuple0 | RootedHDPathTuple1 | RootedHDPathTuple2 | RootedHDPathTuple3 | RootedHDPathTuple4 | RootedHDPathTuple5; | ||
@@ -52,22 +51,12 @@ declare type PartialHDPathTuple1 = readonly [HardenedBIP32Node]; | ||
declare type PartialHDPathTuple3 = readonly [...PartialHDPathTuple2, HardenedBIP32Node]; | ||
declare type PartialHDPathTuple4 = readonly [ | ||
...PartialHDPathTuple3, | ||
UnhardenedBIP32Node | ||
]; | ||
declare type PartialHDPathTuple5 = readonly [ | ||
...PartialHDPathTuple4, | ||
UnhardenedBIP32Node | ||
]; | ||
declare type PartialHDPathTuple6 = readonly [UnhardenedBIP32Node]; | ||
declare type PartialHDPathTuple7 = readonly [UnhardenedBIP32Node, UnhardenedBIP32Node]; | ||
declare type PartialHDPathTuple8 = readonly [ | ||
HardenedBIP32Node, | ||
UnhardenedBIP32Node, | ||
UnhardenedBIP32Node | ||
]; | ||
declare type PartialHDPathTuple9 = readonly [HardenedBIP32Node, UnhardenedBIP32Node]; | ||
declare type PartialHDPathTuple4 = readonly [...PartialHDPathTuple3, BIP32Node]; | ||
declare type PartialHDPathTuple5 = readonly [...PartialHDPathTuple4, BIP32Node]; | ||
declare type PartialHDPathTuple6 = readonly [BIP32Node]; | ||
declare type PartialHDPathTuple7 = readonly [BIP32Node, BIP32Node]; | ||
declare type PartialHDPathTuple8 = readonly [HardenedBIP32Node, BIP32Node, BIP32Node]; | ||
declare type PartialHDPathTuple9 = readonly [HardenedBIP32Node, BIP32Node]; | ||
declare type PartialHDPathTuple10 = readonly [ | ||
HardenedBIP32Node, | ||
HardenedBIP32Node, | ||
UnhardenedBIP32Node | ||
BIP32Node | ||
]; | ||
@@ -77,4 +66,4 @@ declare type PartialHDPathTuple11 = readonly [ | ||
HardenedBIP32Node, | ||
UnhardenedBIP32Node, | ||
UnhardenedBIP32Node | ||
BIP32Node, | ||
BIP32Node | ||
]; | ||
@@ -87,3 +76,6 @@ export declare type CoinTypeToAddressTuple = PartialHDPathTuple8; | ||
export declare type HDPathTuple = RootedHDPathTuple | PartialHDPathTuple; | ||
export declare type RootedSLIP10PathTuple = readonly [BIP39Node, ...BIP32Node[]]; | ||
export declare type SLIP10PathTuple = readonly BIP32Node[]; | ||
export declare type SLIP10Path = RootedSLIP10PathTuple | SLIP10PathTuple; | ||
export declare type FullHDPathTuple = RootedHDPathTuple5; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.BIP_39_PATH_REGEX = exports.BIP_32_PATH_REGEX = exports.BIP44PurposeNodeToken = exports.MAX_BIP_44_DEPTH = exports.MIN_BIP_44_DEPTH = exports.BASE_64_REGEX = exports.HEXADECIMAL_KEY_LENGTH = exports.BASE_64_ZERO = exports.BASE_64_KEY_LENGTH = exports.BUFFER_KEY_LENGTH = void 0; | ||
exports.BUFFER_KEY_LENGTH = 64; | ||
exports.BASE_64_KEY_LENGTH = 88; | ||
exports.BASE_64_ZERO = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='; | ||
exports.HEXADECIMAL_KEY_LENGTH = 128; | ||
// Source: https://stackoverflow.com/a/475217 | ||
exports.BASE_64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/u; | ||
exports.BIP_32_HARDENED_OFFSET = exports.BIP_39_PATH_REGEX = exports.BIP_32_PATH_REGEX = exports.BIP44PurposeNodeToken = exports.MAX_BIP_44_DEPTH = exports.MIN_BIP_44_DEPTH = exports.HEXADECIMAL_KEY_LENGTH = exports.BUFFER_KEY_LENGTH = exports.BUFFER_EXTENDED_KEY_LENGTH = void 0; | ||
exports.BUFFER_EXTENDED_KEY_LENGTH = 64; | ||
exports.BUFFER_KEY_LENGTH = 32; | ||
exports.HEXADECIMAL_KEY_LENGTH = 64; | ||
exports.MIN_BIP_44_DEPTH = 0; | ||
@@ -25,2 +22,3 @@ exports.MAX_BIP_44_DEPTH = 5; | ||
exports.BIP_39_PATH_REGEX = /^bip39:([a-z]+){1}( [a-z]+){11,23}$/u; | ||
exports.BIP_32_HARDENED_OFFSET = 0x80000000; | ||
//# sourceMappingURL=constants.js.map |
@@ -1,6 +0,9 @@ | ||
/// <reference types="node" /> | ||
import { HDPathTuple, BIP44Depth } from './constants'; | ||
import { SLIP10Path } from './constants'; | ||
import { SLIP10Node } from './SLIP10Node'; | ||
import { BIP44Node } from './BIP44Node'; | ||
import { BIP44CoinTypeNode } from './BIP44CoinTypeNode'; | ||
import { SupportedCurve } from './curves'; | ||
/** | ||
* ethereum default seed path: "m/44'/60'/0'/0/{account_index}" | ||
* multipath: "bip32:44'/bip32:60'/bip32:0'/bip32:0/bip32:{account_index}" | ||
* Ethereum default seed path: "m/44'/60'/0'/0/{account_index}" | ||
* Multipath: "bip32:44'/bip32:60'/bip32:0'/bip32:0/bip32:{account_index}" | ||
* | ||
@@ -14,2 +17,13 @@ * m: { privateKey, chainCode } = sha512Hmac("Bitcoin seed", masterSeed) | ||
*/ | ||
declare type BaseDeriveKeyFromPathArgs = { | ||
path: SLIP10Path; | ||
depth?: number; | ||
}; | ||
declare type DeriveKeyFromPathNodeArgs = BaseDeriveKeyFromPathArgs & { | ||
node?: SLIP10Node | BIP44Node | BIP44CoinTypeNode; | ||
}; | ||
declare type DeriveKeyFromPathCurveArgs = BaseDeriveKeyFromPathArgs & { | ||
curve: SupportedCurve; | ||
}; | ||
declare type DeriveKeyFromPathArgs = DeriveKeyFromPathNodeArgs | DeriveKeyFromPathCurveArgs; | ||
/** | ||
@@ -27,10 +41,12 @@ * Takes a full or partial HD path string and returns the key corresponding to | ||
* | ||
* @param pathSegment - A full or partial HD path, e.g.: | ||
* @param args | ||
* @param args.path - A full or partial HD path, e.g.: | ||
* bip39:SEED_PHRASE/bip32:44'/bip32:60'/bip32:0'/bip32:0/bip32:0 | ||
* | ||
* BIP-39 seed phrases must be lowercase, space-delimited, and 12-24 words long. | ||
* @param parentKey - The parent key of the given path segment, if any. | ||
* @param args.node - The node to derive from. | ||
* @param args.depth - The depth of the segment. | ||
* @returns The derived key. | ||
*/ | ||
export declare function deriveKeyFromPath(pathSegment: HDPathTuple, parentKey?: Buffer, depth?: BIP44Depth): Buffer; | ||
export declare function deriveKeyFromPath(args: DeriveKeyFromPathArgs): Promise<SLIP10Node>; | ||
/** | ||
@@ -42,4 +58,7 @@ * The path segment must be one of the following: | ||
* | ||
* @param pathSegment - The path segment string to validate. | ||
* @param path - The path segment string to validate. | ||
* @param hasKey | ||
* @param depth | ||
*/ | ||
export declare function validatePathSegment(pathSegment: HDPathTuple, hasKey: boolean, depth?: BIP44Depth): void; | ||
export declare function validatePathSegment(path: SLIP10Path, hasKey: boolean, depth?: number): void; | ||
export {}; |
@@ -6,14 +6,7 @@ "use strict"; | ||
const derivers_1 = require("./derivers"); | ||
const SLIP10Node_1 = require("./SLIP10Node"); | ||
const BIP44Node_1 = require("./BIP44Node"); | ||
const BIP44CoinTypeNode_1 = require("./BIP44CoinTypeNode"); | ||
const curves_1 = require("./curves"); | ||
/** | ||
* 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]) | ||
* 0': { privateKey, chainCode } = parentKey.privateKey + sha512Hmac(parentKey.chainCode, [0x00, parentKey.privateKey, index + HARDENED_OFFSET]) | ||
* 0: { privateKey, chainCode } = parentKey.privateKey + sha512Hmac(parentKey.chainCode, [parentKey.publicKey, index]) | ||
* 0: { privateKey, chainCode } = parentKey.privateKey + sha512Hmac(parentKey.chainCode, [parentKey.publicKey, index]) | ||
*/ | ||
/** | ||
* Takes a full or partial HD path string and returns the key corresponding to | ||
@@ -30,18 +23,30 @@ * the given path, with the following constraints: | ||
* | ||
* @param pathSegment - A full or partial HD path, e.g.: | ||
* @param args | ||
* @param args.path - A full or partial HD path, e.g.: | ||
* bip39:SEED_PHRASE/bip32:44'/bip32:60'/bip32:0'/bip32:0/bip32:0 | ||
* | ||
* BIP-39 seed phrases must be lowercase, space-delimited, and 12-24 words long. | ||
* @param parentKey - The parent key of the given path segment, if any. | ||
* @param args.node - The node to derive from. | ||
* @param args.depth - The depth of the segment. | ||
* @returns The derived key. | ||
*/ | ||
function deriveKeyFromPath(pathSegment, parentKey, depth) { | ||
if (parentKey && !Buffer.isBuffer(parentKey)) { | ||
throw new Error('Parent key must be a Buffer if specified.'); | ||
async function deriveKeyFromPath(args) { | ||
const { path, depth = path.length } = args; | ||
const node = 'node' in args ? args.node : undefined; | ||
const curve = 'curve' in args ? args.curve : node === null || node === void 0 ? void 0 : node.curve; | ||
if (node && | ||
!(node instanceof SLIP10Node_1.SLIP10Node) && | ||
!(node instanceof BIP44Node_1.BIP44Node) && | ||
!(node instanceof BIP44CoinTypeNode_1.BIP44CoinTypeNode)) { | ||
throw new Error('Invalid arguments: Node must be a SLIP-10 node or a BIP-44 node when provided.'); | ||
} | ||
validatePathSegment(pathSegment, Boolean(parentKey), depth); | ||
let key = parentKey; | ||
if (!curve) { | ||
throw new Error('Invalid arguments: Must specify either a parent node or curve.'); | ||
} | ||
validatePathSegment(path, Boolean(node === null || node === void 0 ? void 0 : node.privateKey) || Boolean(node === null || node === void 0 ? void 0 : node.publicKey), depth); | ||
// derive through each part of path | ||
pathSegment.forEach((node) => { | ||
const [pathType, pathValue] = node.split(':'); | ||
// `pathSegment` needs to be cast to `string[]` because `HDPathTuple.reduce()` doesn't work | ||
return await path.reduce(async (promise, pathNode) => { | ||
const derivedNode = await promise; | ||
const [pathType, pathPart] = pathNode.split(':'); | ||
/* istanbul ignore if: should be impossible */ | ||
@@ -52,7 +57,8 @@ if (!hasDeriver(pathType)) { | ||
const deriver = derivers_1.derivers[pathType]; | ||
const childKey = deriver.deriveChildKey(pathValue, key); | ||
// continue deriving from child key | ||
key = childKey; | ||
}); | ||
return key; | ||
return await deriver.deriveChildKey({ | ||
path: pathPart, | ||
node: derivedNode, | ||
curve: curves_1.getCurveByName(curve), | ||
}); | ||
}, Promise.resolve(node)); | ||
} | ||
@@ -72,13 +78,15 @@ exports.deriveKeyFromPath = deriveKeyFromPath; | ||
* | ||
* @param pathSegment - The path segment string to validate. | ||
* @param path - The path segment string to validate. | ||
* @param hasKey | ||
* @param depth | ||
*/ | ||
function validatePathSegment(pathSegment, hasKey, depth) { | ||
if (pathSegment.length === 0) { | ||
function validatePathSegment(path, hasKey, depth) { | ||
if (path.length === 0) { | ||
throw new Error(`Invalid HD path segment: The segment must not be empty.`); | ||
} | ||
if (pathSegment.length - 1 > constants_1.MAX_BIP_44_DEPTH) { | ||
if (path.length - 1 > constants_1.MAX_BIP_44_DEPTH) { | ||
throw new Error(`Invalid HD path segment: The segment cannot exceed a 0-indexed depth of 5.`); | ||
} | ||
let startsWithBip39 = false; | ||
pathSegment.forEach((node, index) => { | ||
path.forEach((node, index) => { | ||
if (index === 0) { | ||
@@ -94,5 +102,4 @@ startsWithBip39 = constants_1.BIP_39_PATH_REGEX.test(node); | ||
}); | ||
if (depth === constants_1.MIN_BIP_44_DEPTH && | ||
(!startsWithBip39 || pathSegment.length !== 1)) { | ||
throw new Error(`Invalid HD path segment: The segment must consist of a single BIP-39 node for depths of ${constants_1.MIN_BIP_44_DEPTH}. Received: "${pathSegment}"`); | ||
if (depth === constants_1.MIN_BIP_44_DEPTH && (!startsWithBip39 || path.length !== 1)) { | ||
throw new Error(`Invalid HD path segment: The segment must consist of a single BIP-39 node for depths of ${constants_1.MIN_BIP_44_DEPTH}. Received: "${path}".`); | ||
} | ||
@@ -99,0 +106,0 @@ if (!hasKey && !startsWithBip39) { |
/// <reference types="node" /> | ||
import { Curve } from '../curves'; | ||
import { SLIP10Node } from '../SLIP10Node'; | ||
import { DeriveChildKeyArgs } from '.'; | ||
/** | ||
@@ -9,3 +12,3 @@ * Converts a BIP-32 private key to an Ethereum address. | ||
* | ||
* @param key - The `address_index` key buffer to convert to an Ethereum | ||
* @param key - The `address_index` private key buffer to convert to an Ethereum | ||
* address. | ||
@@ -16,5 +19,32 @@ * @returns The Ethereum address corresponding to the given key. | ||
/** | ||
* @param pathPart | ||
* @param parentKey | ||
* Converts a BIP-32 public key to an Ethereum address. | ||
* | ||
* **WARNING:** Only validates that the key is non-zero and of the correct | ||
* length. It is the consumer's responsibility to ensure that the specified | ||
* key is a valid BIP-44 Ethereum `address_index` key. | ||
* | ||
* @param key - The `address_index` public key buffer to convert to an Ethereum | ||
* address. | ||
* @returns The Ethereum address corresponding to the given key. | ||
*/ | ||
export declare function deriveChildKey(pathPart: string, parentKey: Buffer): Buffer; | ||
export declare function publicKeyToEthAddress(key: Buffer): Buffer; | ||
/** | ||
* Derive a BIP-32 child key with a given path from a parent key. | ||
* | ||
* @param path - The derivation path part to derive. | ||
* @param node - The node to derive from. | ||
* @param curve - The curve to use for derivation. | ||
* @returns A tuple containing the derived private key, public key and chain | ||
* code. | ||
*/ | ||
export declare function deriveChildKey({ path, node, curve, }: DeriveChildKeyArgs): Promise<SLIP10Node>; | ||
/** | ||
* Add a tweak to the private key: `(privateKey + tweak) % n`. | ||
* | ||
* @param privateKeyBuffer - The private key as 32 byte Uint8Array. | ||
* @param tweakBuffer - The tweak as 32 byte Uint8Array. | ||
* @param curve - The curve to use. | ||
* @throws If the private key or tweak is invalid. | ||
* @returns The private key with the tweak added to it. | ||
*/ | ||
export declare function privateAdd(privateKeyBuffer: Uint8Array, tweakBuffer: Uint8Array, curve: Curve): Buffer; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.deriveChildKey = exports.privateKeyToEthAddress = void 0; | ||
const crypto_1 = __importDefault(require("crypto")); | ||
const secp256k1_1 = __importDefault(require("secp256k1")); | ||
const keccak_1 = __importDefault(require("keccak")); | ||
exports.privateAdd = exports.deriveChildKey = exports.publicKeyToEthAddress = exports.privateKeyToEthAddress = void 0; | ||
const sha3_1 = require("@noble/hashes/sha3"); | ||
const hmac_1 = require("@noble/hashes/hmac"); | ||
const sha512_1 = require("@noble/hashes/sha512"); | ||
const constants_1 = require("../constants"); | ||
const utils_1 = require("../utils"); | ||
const HARDENED_OFFSET = 0x80000000; | ||
const curves_1 = require("../curves"); | ||
const SLIP10Node_1 = require("../SLIP10Node"); | ||
/** | ||
@@ -20,3 +18,3 @@ * Converts a BIP-32 private key to an Ethereum address. | ||
* | ||
* @param key - The `address_index` key buffer to convert to an Ethereum | ||
* @param key - The `address_index` private key buffer to convert to an Ethereum | ||
* address. | ||
@@ -26,32 +24,49 @@ * @returns The Ethereum address corresponding to the given key. | ||
function privateKeyToEthAddress(key) { | ||
if (!Buffer.isBuffer(key) || !utils_1.isValidBufferKey(key)) { | ||
throw new Error('Invalid key: The key must be a 64-byte, non-zero Buffer.'); | ||
if (!Buffer.isBuffer(key) || !utils_1.isValidBufferKey(key, constants_1.BUFFER_KEY_LENGTH)) { | ||
throw new Error('Invalid key: The key must be a 32-byte, non-zero Buffer.'); | ||
} | ||
const privateKey = key.slice(0, 32); | ||
const publicKey = secp256k1_1.default | ||
.publicKeyCreate(new Uint8Array(privateKey), false) | ||
.slice(1); | ||
return keccak(Buffer.from(publicKey)).slice(-20); | ||
const publicKey = curves_1.secp256k1.getPublicKey(key, false); | ||
return publicKeyToEthAddress(publicKey); | ||
} | ||
exports.privateKeyToEthAddress = privateKeyToEthAddress; | ||
/** | ||
* @param data | ||
* @param keccakBits | ||
* Converts a BIP-32 public key to an Ethereum address. | ||
* | ||
* **WARNING:** Only validates that the key is non-zero and of the correct | ||
* length. It is the consumer's responsibility to ensure that the specified | ||
* key is a valid BIP-44 Ethereum `address_index` key. | ||
* | ||
* @param key - The `address_index` public key buffer to convert to an Ethereum | ||
* address. | ||
* @returns The Ethereum address corresponding to the given key. | ||
*/ | ||
function keccak(data, keccakBits = '256') { | ||
return keccak_1.default(`keccak${keccakBits}`).update(data).digest(); | ||
function publicKeyToEthAddress(key) { | ||
if (!Buffer.isBuffer(key) || | ||
!utils_1.isValidBufferKey(key, curves_1.secp256k1.publicKeyLength)) { | ||
throw new Error('Invalid key: The key must be a 65-byte, non-zero Buffer.'); | ||
} | ||
return Buffer.from(sha3_1.keccak_256(key.slice(1)).slice(-20)); | ||
} | ||
exports.publicKeyToEthAddress = publicKeyToEthAddress; | ||
/** | ||
* @param pathPart | ||
* @param parentKey | ||
* Derive a BIP-32 child key with a given path from a parent key. | ||
* | ||
* @param path - The derivation path part to derive. | ||
* @param node - The node to derive from. | ||
* @param curve - The curve to use for derivation. | ||
* @returns A tuple containing the derived private key, public key and chain | ||
* code. | ||
*/ | ||
function deriveChildKey(pathPart, parentKey) { | ||
if (!parentKey) { | ||
throw new Error('Invalid parameters: Must specify a parent key.'); | ||
async function deriveChildKey({ path, node, curve = curves_1.secp256k1, }) { | ||
const isHardened = path.includes(`'`); | ||
if (!isHardened && !curve.deriveUnhardenedKeys) { | ||
throw new Error(`Invalid path: Cannot derive unhardened child keys with ${curve.name}.`); | ||
} | ||
if (parentKey.length !== constants_1.BUFFER_KEY_LENGTH) { | ||
throw new Error('Invalid parent key: Must be 64 bytes long.'); | ||
if (!node) { | ||
throw new Error('Invalid parameters: Must specify a node to derive from.'); | ||
} | ||
const isHardened = pathPart.includes(`'`); | ||
const indexPart = pathPart.split(`'`)[0]; | ||
if (isHardened && !node.privateKey) { | ||
throw new Error('Invalid parameters: Cannot derive hardened child keys without a private key.'); | ||
} | ||
const indexPart = path.split(`'`)[0]; | ||
const childIndex = parseInt(indexPart, 10); | ||
@@ -61,18 +76,46 @@ if (!/^\d+$/u.test(indexPart) || | ||
childIndex < 0 || | ||
childIndex >= HARDENED_OFFSET) { | ||
throw new Error(`Invalid BIP-32 index: The index must be a non-negative decimal integer less than ${HARDENED_OFFSET}.`); | ||
childIndex >= constants_1.BIP_32_HARDENED_OFFSET) { | ||
throw new Error(`Invalid BIP-32 index: The index must be a non-negative decimal integer less than ${constants_1.BIP_32_HARDENED_OFFSET}.`); | ||
} | ||
const parentPrivateKey = parentKey.slice(0, 32); | ||
const parentExtraEntropy = parentKey.slice(32); | ||
const secretExtension = deriveSecretExtension({ | ||
parentPrivateKey, | ||
if (node.privateKeyBuffer) { | ||
const secretExtension = await deriveSecretExtension({ | ||
privateKey: node.privateKeyBuffer, | ||
childIndex, | ||
isHardened, | ||
curve, | ||
}); | ||
const { privateKey, chainCode } = await generateKey({ | ||
privateKey: node.privateKeyBuffer, | ||
chainCode: node.chainCodeBuffer, | ||
secretExtension, | ||
curve, | ||
}); | ||
return SLIP10Node_1.SLIP10Node.fromExtendedKey({ | ||
privateKey, | ||
chainCode, | ||
depth: node.depth + 1, | ||
parentFingerprint: node.fingerprint, | ||
index: childIndex + (isHardened ? constants_1.BIP_32_HARDENED_OFFSET : 0), | ||
curve: curve.name, | ||
}); | ||
} | ||
const publicExtension = await derivePublicExtension({ | ||
parentPublicKey: node.compressedPublicKeyBuffer, | ||
childIndex, | ||
isHardened, | ||
curve, | ||
}); | ||
const { privateKey, extraEntropy } = generateKey({ | ||
parentPrivateKey, | ||
parentExtraEntropy, | ||
secretExtension, | ||
const { publicKey, chainCode } = generatePublicKey({ | ||
publicKey: node.compressedPublicKeyBuffer, | ||
chainCode: node.chainCodeBuffer, | ||
publicExtension, | ||
curve, | ||
}); | ||
return Buffer.concat([privateKey, extraEntropy]); | ||
return SLIP10Node_1.SLIP10Node.fromExtendedKey({ | ||
publicKey, | ||
chainCode, | ||
depth: node.depth + 1, | ||
parentFingerprint: node.fingerprint, | ||
index: childIndex, | ||
curve: curve.name, | ||
}); | ||
} | ||
@@ -83,12 +126,12 @@ exports.deriveChildKey = deriveChildKey; | ||
* @param options | ||
* @param options.parentPrivateKey | ||
* @param options.privateKey | ||
* @param options.childIndex | ||
* @param options.isHardened | ||
*/ | ||
function deriveSecretExtension({ parentPrivateKey, childIndex, isHardened, }) { | ||
async function deriveSecretExtension({ privateKey, childIndex, isHardened, curve, }) { | ||
if (isHardened) { | ||
// Hardened child | ||
const indexBuffer = Buffer.allocUnsafe(4); | ||
indexBuffer.writeUInt32BE(childIndex + HARDENED_OFFSET, 0); | ||
const pk = parentPrivateKey; | ||
indexBuffer.writeUInt32BE(childIndex + constants_1.BIP_32_HARDENED_OFFSET, 0); | ||
const pk = privateKey; | ||
const zb = Buffer.alloc(1, 0); | ||
@@ -100,22 +143,63 @@ return Buffer.concat([zb, pk, indexBuffer]); | ||
indexBuffer.writeUInt32BE(childIndex, 0); | ||
const parentPublicKey = secp256k1_1.default.publicKeyCreate(new Uint8Array(parentPrivateKey), true); | ||
const parentPublicKey = await curve.getPublicKey(privateKey, true); | ||
return Buffer.concat([parentPublicKey, indexBuffer]); | ||
} | ||
async function derivePublicExtension({ parentPublicKey, childIndex, }) { | ||
const indexBuffer = Buffer.alloc(4); | ||
indexBuffer.writeUInt32BE(childIndex, 0); | ||
return Buffer.concat([parentPublicKey, indexBuffer]); | ||
} | ||
/** | ||
* Add a tweak to the private key: `(privateKey + tweak) % n`. | ||
* | ||
* @param privateKeyBuffer - The private key as 32 byte Uint8Array. | ||
* @param tweakBuffer - The tweak as 32 byte Uint8Array. | ||
* @param curve - The curve to use. | ||
* @throws If the private key or tweak is invalid. | ||
* @returns The private key with the tweak added to it. | ||
*/ | ||
function privateAdd(privateKeyBuffer, tweakBuffer, curve) { | ||
const privateKey = utils_1.bytesToNumber(privateKeyBuffer); | ||
const tweak = utils_1.bytesToNumber(tweakBuffer); | ||
if (tweak >= curve.curve.n) { | ||
throw new Error('Invalid tweak: Tweak is larger than the curve order.'); | ||
} | ||
const added = curves_1.mod(privateKey + tweak, curve.curve.n); | ||
if (!curve.isValidPrivateKey(added)) { | ||
throw new Error('Invalid private key or tweak: The resulting private key is invalid.'); | ||
} | ||
return utils_1.hexStringToBuffer(added.toString(16).padStart(64, '0')); | ||
} | ||
exports.privateAdd = privateAdd; | ||
/** | ||
* @param options | ||
* @param options.parentPrivateKey | ||
* @param options.parentExtraEntropy | ||
* @param options.privateKey | ||
* @param options.chainCode | ||
* @param options.secretExtension | ||
*/ | ||
function generateKey({ parentPrivateKey, parentExtraEntropy, secretExtension, }) { | ||
const entropy = crypto_1.default | ||
.createHmac('sha512', parentExtraEntropy) | ||
.update(secretExtension) | ||
.digest(); | ||
async function generateKey({ privateKey, chainCode, secretExtension, curve, }) { | ||
const entropy = hmac_1.hmac(sha512_1.sha512, chainCode, secretExtension); | ||
const keyMaterial = Buffer.from(entropy.slice(0, 32)); | ||
const childChainCode = Buffer.from(entropy.slice(32)); | ||
// If curve is ed25519: The returned child key ki is parse256(IL). | ||
// https://github.com/satoshilabs/slips/blob/133ea52a8e43d338b98be208907e144277e44c0e/slip-0010.md#private-parent-key--private-child-key | ||
if (curve.name === 'ed25519') { | ||
const publicKey = await curve.getPublicKey(keyMaterial); | ||
return { privateKey: keyMaterial, publicKey, chainCode: childChainCode }; | ||
} | ||
const childPrivateKey = privateAdd(privateKey, keyMaterial, curve); | ||
const publicKey = await curve.getPublicKey(childPrivateKey); | ||
return { privateKey: childPrivateKey, publicKey, chainCode: childChainCode }; | ||
} | ||
function generatePublicKey({ publicKey, chainCode, publicExtension, curve, }) { | ||
const entropy = hmac_1.hmac(sha512_1.sha512, chainCode, publicExtension); | ||
const keyMaterial = entropy.slice(0, 32); | ||
// extraEntropy is also called "chaincode" | ||
const extraEntropy = entropy.slice(32); | ||
const privateKey = secp256k1_1.default.privateKeyTweakAdd(new Uint8Array(parentPrivateKey), new Uint8Array(keyMaterial)); | ||
return { privateKey, extraEntropy }; | ||
const childChainCode = entropy.slice(32); | ||
// This function may fail if the resulting key is invalid. | ||
const childPublicKey = curve.publicAdd(publicKey, Buffer.from(keyMaterial)); | ||
return { | ||
publicKey: Buffer.from(childPublicKey), | ||
chainCode: Buffer.from(childChainCode), | ||
}; | ||
} | ||
//# sourceMappingURL=bip32.js.map |
/// <reference types="node" /> | ||
import { BIP39Node } from '../constants'; | ||
import { Curve } from '../curves'; | ||
import { SLIP10Node } from '../SLIP10Node'; | ||
import { DeriveChildKeyArgs } from '.'; | ||
/** | ||
@@ -9,9 +12,10 @@ * @param mnemonic | ||
* @param pathPart | ||
* @param _parentKey | ||
* @param curve | ||
*/ | ||
export declare function deriveChildKey(pathPart: string, _parentKey?: never): Buffer; | ||
export declare function deriveChildKey({ path, curve, }: DeriveChildKeyArgs): Promise<SLIP10Node>; | ||
/** | ||
* @param seed - The cryptographic seed bytes. | ||
* @returns The bytes of the corresponding BIP-39 master key. | ||
* @param curve - The curve to use. | ||
* @returns An object containing the corresponding BIP-39 master key and chain code. | ||
*/ | ||
export declare function createBip39KeyFromSeed(seed: Buffer): Buffer; | ||
export declare function createBip39KeyFromSeed(seed: Buffer, curve?: Curve): Promise<SLIP10Node>; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createBip39KeyFromSeed = exports.deriveChildKey = exports.bip39MnemonicToMultipath = void 0; | ||
const crypto_1 = __importDefault(require("crypto")); | ||
const bip39_1 = require("bip39"); | ||
// This magic constant is analogous to a salt, and is consistent across all | ||
// major BIP-32 implementations. | ||
const ROOT_BASE_SECRET = Buffer.from('Bitcoin seed', 'utf8'); | ||
const bip39_1 = require("@scure/bip39"); | ||
const hmac_1 = require("@noble/hashes/hmac"); | ||
const sha512_1 = require("@noble/hashes/sha512"); | ||
const curves_1 = require("../curves"); | ||
const SLIP10Node_1 = require("../SLIP10Node"); | ||
/** | ||
@@ -22,6 +19,6 @@ * @param mnemonic | ||
* @param pathPart | ||
* @param _parentKey | ||
* @param curve | ||
*/ | ||
function deriveChildKey(pathPart, _parentKey) { | ||
return createBip39KeyFromSeed(bip39_1.mnemonicToSeedSync(pathPart)); | ||
async function deriveChildKey({ path, curve, }) { | ||
return createBip39KeyFromSeed(Buffer.from(bip39_1.mnemonicToSeedSync(path)), curve); | ||
} | ||
@@ -31,8 +28,19 @@ exports.deriveChildKey = deriveChildKey; | ||
* @param seed - The cryptographic seed bytes. | ||
* @returns The bytes of the corresponding BIP-39 master key. | ||
* @param curve - The curve to use. | ||
* @returns An object containing the corresponding BIP-39 master key and chain code. | ||
*/ | ||
function createBip39KeyFromSeed(seed) { | ||
return crypto_1.default.createHmac('sha512', ROOT_BASE_SECRET).update(seed).digest(); | ||
async function createBip39KeyFromSeed(seed, curve = curves_1.secp256k1) { | ||
const key = Buffer.from(hmac_1.hmac(sha512_1.sha512, curve.secret, seed)); | ||
const privateKey = key.slice(0, 32); | ||
const chainCode = key.slice(32); | ||
return SLIP10Node_1.SLIP10Node.fromExtendedKey({ | ||
privateKey, | ||
chainCode, | ||
depth: 0, | ||
parentFingerprint: 0, | ||
index: 0, | ||
curve: curve.name, | ||
}); | ||
} | ||
exports.createBip39KeyFromSeed = createBip39KeyFromSeed; | ||
//# sourceMappingURL=bip39.js.map |
/// <reference types="node" /> | ||
import { Curve } from '../curves'; | ||
import { SLIP10Node } from '../SLIP10Node'; | ||
import * as bip32 from './bip32'; | ||
import * as bip39 from './bip39'; | ||
export interface Deriver { | ||
deriveChildKey: (pathPart: string, parentKey?: Buffer) => Buffer; | ||
} | ||
export declare type DerivedKeys = { | ||
/** | ||
* The derived private key, can be undefined if public key derivation was used. | ||
*/ | ||
privateKey?: Buffer; | ||
publicKey: Buffer; | ||
chainCode: Buffer; | ||
}; | ||
export declare type DeriveChildKeyArgs = { | ||
path: string; | ||
curve?: Curve; | ||
node?: SLIP10Node; | ||
}; | ||
export declare type Deriver = { | ||
deriveChildKey: (args: DeriveChildKeyArgs) => Promise<SLIP10Node>; | ||
}; | ||
export declare const derivers: { | ||
@@ -8,0 +23,0 @@ bip32: typeof bip32; |
/// <reference types="node" /> | ||
export { BIP44Node, BIP44NodeInterface, JsonBIP44Node } from './BIP44Node'; | ||
export { SLIP10Node, SLIP10NodeInterface, JsonSLIP10Node } from './SLIP10Node'; | ||
export { secp256k1, ed25519 } from './curves'; | ||
export { BIP44CoinTypeNode, BIP44CoinTypeNodeInterface, BIP_44_COIN_TYPE_DEPTH, CoinTypeHDPathTuple, deriveBIP44AddressKey, getBIP44AddressKeyDeriver, JsonBIP44CoinTypeNode, } from './BIP44CoinTypeNode'; | ||
@@ -4,0 +6,0 @@ export { MIN_BIP_44_DEPTH, MAX_BIP_44_DEPTH, BIP44Depth, BIP44PurposeNodeToken, BIP32Node, BIP39Node, } from './constants'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.PackageBuffer = exports.BIP44PurposeNodeToken = exports.MAX_BIP_44_DEPTH = exports.MIN_BIP_44_DEPTH = exports.getBIP44AddressKeyDeriver = exports.deriveBIP44AddressKey = exports.BIP_44_COIN_TYPE_DEPTH = exports.BIP44CoinTypeNode = exports.BIP44Node = void 0; | ||
exports.PackageBuffer = exports.BIP44PurposeNodeToken = exports.MAX_BIP_44_DEPTH = exports.MIN_BIP_44_DEPTH = exports.getBIP44AddressKeyDeriver = exports.deriveBIP44AddressKey = exports.BIP_44_COIN_TYPE_DEPTH = exports.BIP44CoinTypeNode = exports.ed25519 = exports.secp256k1 = exports.SLIP10Node = exports.BIP44Node = void 0; | ||
var BIP44Node_1 = require("./BIP44Node"); | ||
Object.defineProperty(exports, "BIP44Node", { enumerable: true, get: function () { return BIP44Node_1.BIP44Node; } }); | ||
var SLIP10Node_1 = require("./SLIP10Node"); | ||
Object.defineProperty(exports, "SLIP10Node", { enumerable: true, get: function () { return SLIP10Node_1.SLIP10Node; } }); | ||
var curves_1 = require("./curves"); | ||
Object.defineProperty(exports, "secp256k1", { enumerable: true, get: function () { return curves_1.secp256k1; } }); | ||
Object.defineProperty(exports, "ed25519", { enumerable: true, get: function () { return curves_1.ed25519; } }); | ||
var BIP44CoinTypeNode_1 = require("./BIP44CoinTypeNode"); | ||
@@ -7,0 +12,0 @@ Object.defineProperty(exports, "BIP44CoinTypeNode", { enumerable: true, get: function () { return BIP44CoinTypeNode_1.BIP44CoinTypeNode; } }); |
/// <reference types="node" /> | ||
import { UnhardenedBIP32Node, CoinTypeHDPathString, CoinTypeToAddressTuple, HardenedBIP32Node, ChangeHDPathString } from './constants'; | ||
import { BIP32Node, ChangeHDPathString, CoinTypeHDPathString, CoinTypeToAddressTuple, HardenedBIP32Node, UnhardenedBIP32Node } from './constants'; | ||
/** | ||
@@ -13,8 +13,12 @@ * Gets a string representation of a BIP-44 path of depth 2, i.e.: | ||
export declare function getBIP44CoinTypePathString(coin_type: number): CoinTypeHDPathString; | ||
interface BIP44PathIndices { | ||
declare type BIP44PathIndex = number | { | ||
index: number; | ||
hardened: boolean; | ||
}; | ||
declare type BIP44PathIndices = { | ||
coin_type: number; | ||
account?: number; | ||
change?: number; | ||
address_index: number; | ||
} | ||
change?: BIP44PathIndex; | ||
address_index: BIP44PathIndex; | ||
}; | ||
export declare type CoinTypeToAddressIndices = Pick<BIP44PathIndices, 'account' | 'change' | 'address_index'>; | ||
@@ -64,2 +68,11 @@ /** | ||
/** | ||
* A hardened or unhardened BIP-32 node token, e.g. `bip32:0` or `bip32:0'`. | ||
* Validates that the index is a non-negative integer number, and throws an | ||
* error if validation fails. | ||
* | ||
* @param index - The index of the node. | ||
* @returns The hardened or unhardened BIP-32 node token. | ||
*/ | ||
export declare function getBIP32NodeToken(index: BIP44PathIndex): BIP32Node; | ||
/** | ||
* Validates that the index is a non-negative integer number. Throws an | ||
@@ -96,6 +109,6 @@ * error if validation fails. | ||
/** | ||
* @param base64String - The Base64 string to convert. | ||
* @returns The {@link Buffer} corresponding to the Base64 string. | ||
* @param hexString - The hexadecimal string to convert. | ||
* @returns The {@link Buffer} corresponding to the hexadecimal string. | ||
*/ | ||
export declare function base64StringToBuffer(base64String: string): Buffer; | ||
export declare function hexStringToBuffer(hexString: string | Buffer): Buffer; | ||
/** | ||
@@ -105,9 +118,4 @@ * @param hexString - The hexadecimal string to convert. | ||
*/ | ||
export declare function hexStringToBuffer(hexString: string): Buffer; | ||
export declare function nullableHexStringToBuffer(hexString?: string | Buffer): Buffer | undefined; | ||
/** | ||
* @param input - The {@link Buffer} to convert. | ||
* @returns The buffer as a Base64 string. | ||
*/ | ||
export declare function bufferToBase64String(input: Buffer): string; | ||
/** | ||
* Tests whether the specified {@link Buffer} is a valid BIP-32 key. | ||
@@ -117,23 +125,37 @@ * A valid buffer key is 64 bytes long and has at least one non-zero byte. | ||
* @param buffer - The {@link Buffer} to test. | ||
* @param expectedLength - The expected length of the buffer. | ||
* @returns Whether the buffer represents a valid BIP-32 key. | ||
*/ | ||
export declare function isValidBufferKey(buffer: Buffer): boolean; | ||
export declare function isValidBufferKey(buffer: Buffer, expectedLength: number): boolean; | ||
/** | ||
* Tests whether the specified hexadecimal string is a valid BIP-32 key. | ||
* A valid hexadecimal string key is 128 characters long (excluding any `0x` | ||
* prefix) and has at least one non-zero byte. | ||
* Tests whether the specified number is a valid integer equal to or greater than 0. | ||
* | ||
* @param stringKey - The hexadecimal string to test. | ||
* @returns Whether the string represents a valid BIP-32 key. | ||
* @param value - The number to test. | ||
* @returns Whether the number is a valid integer. | ||
*/ | ||
export declare function isValidHexStringKey(stringKey: string): boolean; | ||
export declare function isValidInteger(value: unknown): value is number; | ||
/** | ||
* Tests whether the specified Base64 string is a valid BIP-32 key. | ||
* A valid Base64 string key is 88 characters long and has at least one non-zero | ||
* byte. | ||
* Get a BigInt from a byte array. | ||
* | ||
* @param stringKey - The Base64 string to test. | ||
* @returns Whether the string represents a valid BIP-32 key. | ||
* @param bytes - The byte array to get the BigInt for. | ||
* @returns The byte array as BigInt. | ||
*/ | ||
export declare function isValidBase64StringKey(stringKey: string): boolean; | ||
export declare function bytesToNumber(bytes: Uint8Array): bigint; | ||
/** | ||
* Get a Buffer from a hexadecimal string or Buffer. Validates that the | ||
* length of the Buffer matches the specified length, and that the buffer | ||
* is not empty. | ||
* | ||
* @param value - The value to convert to a Buffer. | ||
* @param length - The length to validate the Buffer against. | ||
*/ | ||
export declare function getBuffer(value: unknown, length: number): Buffer; | ||
export declare const decodeBase58check: (value: string) => Buffer; | ||
export declare const encodeBase58check: (value: Buffer) => string; | ||
/** | ||
* Get the fingerprint of a compressed public key as number. | ||
* | ||
* @param publicKey - The compressed public key to get the fingerprint for. | ||
*/ | ||
export declare const getFingerprint: (publicKey: Buffer) => number; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isValidBase64StringKey = exports.isValidHexStringKey = exports.isValidBufferKey = exports.bufferToBase64String = exports.hexStringToBuffer = exports.base64StringToBuffer = exports.isValidHexString = exports.stripHexPrefix = exports.isHardened = exports.isValidBIP32Index = exports.validateBIP32Index = exports.getUnhardenedBIP32NodeToken = exports.getHardenedBIP32NodeToken = exports.getBIP44CoinTypeToAddressPathTuple = exports.getBIP44ChangePathString = exports.getBIP44CoinTypePathString = void 0; | ||
exports.getFingerprint = exports.encodeBase58check = exports.decodeBase58check = exports.getBuffer = exports.bytesToNumber = exports.isValidInteger = exports.isValidBufferKey = exports.nullableHexStringToBuffer = exports.hexStringToBuffer = exports.isValidHexString = exports.stripHexPrefix = exports.isHardened = exports.isValidBIP32Index = exports.validateBIP32Index = exports.getBIP32NodeToken = exports.getUnhardenedBIP32NodeToken = exports.getHardenedBIP32NodeToken = exports.getBIP44CoinTypeToAddressPathTuple = exports.getBIP44ChangePathString = exports.getBIP44CoinTypePathString = void 0; | ||
const utils_1 = require("@noble/hashes/utils"); | ||
const base_1 = require("@scure/base"); | ||
const sha256_1 = require("@noble/hashes/sha256"); | ||
const ripemd160_1 = require("@noble/hashes/ripemd160"); | ||
const constants_1 = require("./constants"); | ||
@@ -30,3 +34,3 @@ /** | ||
function getBIP44ChangePathString(coinTypePath, indices) { | ||
return `${coinTypePath} / ${getHardenedBIP32NodeToken(indices.account || 0)} / ${getUnhardenedBIP32NodeToken(indices.change || 0)}`; | ||
return `${coinTypePath} / ${getHardenedBIP32NodeToken(indices.account || 0)} / ${getBIP32NodeToken(indices.change || 0)}`; | ||
} | ||
@@ -48,4 +52,4 @@ exports.getBIP44ChangePathString = getBIP44ChangePathString; | ||
getHardenedBIP32NodeToken(account), | ||
getUnhardenedBIP32NodeToken(change), | ||
getUnhardenedBIP32NodeToken(address_index), | ||
getBIP32NodeToken(change), | ||
getBIP32NodeToken(address_index), | ||
]; | ||
@@ -81,2 +85,25 @@ } | ||
/** | ||
* A hardened or unhardened BIP-32 node token, e.g. `bip32:0` or `bip32:0'`. | ||
* Validates that the index is a non-negative integer number, and throws an | ||
* error if validation fails. | ||
* | ||
* @param index - The index of the node. | ||
* @returns The hardened or unhardened BIP-32 node token. | ||
*/ | ||
function getBIP32NodeToken(index) { | ||
if (typeof index === 'number') { | ||
return getUnhardenedBIP32NodeToken(index); | ||
} | ||
if (!index || | ||
!Number.isInteger(index.index) || | ||
typeof index.hardened !== 'boolean') { | ||
throw new Error('Invalid BIP-32 index: Must be an object containing the index and whether it is hardened.'); | ||
} | ||
if (index.hardened) { | ||
return getHardenedBIP32NodeToken(index.index); | ||
} | ||
return getUnhardenedBIP32NodeToken(index.index); | ||
} | ||
exports.getBIP32NodeToken = getBIP32NodeToken; | ||
/** | ||
* Validates that the index is a non-negative integer number. Throws an | ||
@@ -98,3 +125,3 @@ * error if validation fails. | ||
function isValidBIP32Index(index) { | ||
return Number.isInteger(index) && index >= 0; | ||
return isValidInteger(index); | ||
} | ||
@@ -131,10 +158,2 @@ exports.isValidBIP32Index = isValidBIP32Index; | ||
/** | ||
* @param base64String - The Base64 string to convert. | ||
* @returns The {@link Buffer} corresponding to the Base64 string. | ||
*/ | ||
function base64StringToBuffer(base64String) { | ||
return Buffer.from(base64String, 'base64'); | ||
} | ||
exports.base64StringToBuffer = base64StringToBuffer; | ||
/** | ||
* @param hexString - The hexadecimal string to convert. | ||
@@ -144,2 +163,8 @@ * @returns The {@link Buffer} corresponding to the hexadecimal string. | ||
function hexStringToBuffer(hexString) { | ||
if (Buffer.isBuffer(hexString)) { | ||
return hexString; | ||
} | ||
if (typeof hexString !== 'string' || !isValidHexString(hexString)) { | ||
throw new Error(`Invalid hex string: "${hexString}".`); | ||
} | ||
return Buffer.from(stripHexPrefix(hexString), 'hex'); | ||
@@ -149,9 +174,12 @@ } | ||
/** | ||
* @param input - The {@link Buffer} to convert. | ||
* @returns The buffer as a Base64 string. | ||
* @param hexString - The hexadecimal string to convert. | ||
* @returns The {@link Buffer} corresponding to the hexadecimal string. | ||
*/ | ||
function bufferToBase64String(input) { | ||
return input.toString('base64'); | ||
function nullableHexStringToBuffer(hexString) { | ||
if (hexString) { | ||
return hexStringToBuffer(hexString); | ||
} | ||
return undefined; | ||
} | ||
exports.bufferToBase64String = bufferToBase64String; | ||
exports.nullableHexStringToBuffer = nullableHexStringToBuffer; | ||
/** | ||
@@ -162,6 +190,7 @@ * Tests whether the specified {@link Buffer} is a valid BIP-32 key. | ||
* @param buffer - The {@link Buffer} to test. | ||
* @param expectedLength - The expected length of the buffer. | ||
* @returns Whether the buffer represents a valid BIP-32 key. | ||
*/ | ||
function isValidBufferKey(buffer) { | ||
if (buffer.length !== constants_1.BUFFER_KEY_LENGTH) { | ||
function isValidBufferKey(buffer, expectedLength) { | ||
if (buffer.length !== expectedLength) { | ||
return false; | ||
@@ -178,51 +207,77 @@ } | ||
/** | ||
* @param input - The string to test. | ||
* @returns Whether the given string is a valid Base64 string. | ||
* Tests whether the specified number is a valid integer equal to or greater than 0. | ||
* | ||
* @param value - The number to test. | ||
* @returns Whether the number is a valid integer. | ||
*/ | ||
function isValidBase64String(input) { | ||
return constants_1.BASE_64_REGEX.test(input); | ||
function isValidInteger(value) { | ||
return typeof value === 'number' && Number.isInteger(value) && value >= 0; | ||
} | ||
exports.isValidInteger = isValidInteger; | ||
/** | ||
* Tests whether the specified hexadecimal string is a valid BIP-32 key. | ||
* A valid hexadecimal string key is 128 characters long (excluding any `0x` | ||
* prefix) and has at least one non-zero byte. | ||
* Get a BigInt from a byte array. | ||
* | ||
* @param stringKey - The hexadecimal string to test. | ||
* @returns Whether the string represents a valid BIP-32 key. | ||
* @param bytes - The byte array to get the BigInt for. | ||
* @returns The byte array as BigInt. | ||
*/ | ||
function isValidHexStringKey(stringKey) { | ||
if (!isValidHexString(stringKey)) { | ||
return false; | ||
function bytesToNumber(bytes) { | ||
return BigInt(`0x${utils_1.bytesToHex(bytes)}`); | ||
} | ||
exports.bytesToNumber = bytesToNumber; | ||
/** | ||
* Get a Buffer from a hexadecimal string or Buffer. Validates that the | ||
* length of the Buffer matches the specified length, and that the buffer | ||
* is not empty. | ||
* | ||
* @param value - The value to convert to a Buffer. | ||
* @param length - The length to validate the Buffer against. | ||
*/ | ||
function getBuffer(value, length) { | ||
if (value instanceof Buffer) { | ||
validateBuffer(value, length); | ||
return value; | ||
} | ||
const stripped = stripHexPrefix(stringKey); | ||
if (stripped.length !== constants_1.HEXADECIMAL_KEY_LENGTH) { | ||
return false; | ||
if (typeof value === 'string') { | ||
if (!isValidHexString(value)) { | ||
throw new Error(`Invalid value: Must be a valid hex string of length: ${length * 2}.`); | ||
} | ||
const buffer = hexStringToBuffer(value); | ||
validateBuffer(buffer, length); | ||
return buffer; | ||
} | ||
if (/^0+$/iu.test(stripped)) { | ||
return false; | ||
throw new Error(`Invalid value: Expected a Buffer or hexadecimal string.`); | ||
} | ||
exports.getBuffer = getBuffer; | ||
function validateBuffer(buffer, length) { | ||
if (!isValidBufferKey(buffer, length)) { | ||
throw new Error(`Invalid value: Must be a non-zero ${length}-byte buffer.`); | ||
} | ||
return true; | ||
} | ||
exports.isValidHexStringKey = isValidHexStringKey; | ||
const decodeBase58check = (value) => { | ||
const base58Check = base_1.base58check(sha256_1.sha256); | ||
try { | ||
return Buffer.from(base58Check.decode(value)); | ||
} | ||
catch (_a) { | ||
throw new Error(`Invalid value: Value is not base58-encoded, or the checksum is invalid.`); | ||
} | ||
}; | ||
exports.decodeBase58check = decodeBase58check; | ||
const encodeBase58check = (value) => { | ||
const base58Check = base_1.base58check(sha256_1.sha256); | ||
return base58Check.encode(value); | ||
}; | ||
exports.encodeBase58check = encodeBase58check; | ||
/** | ||
* Tests whether the specified Base64 string is a valid BIP-32 key. | ||
* A valid Base64 string key is 88 characters long and has at least one non-zero | ||
* byte. | ||
* Get the fingerprint of a compressed public key as number. | ||
* | ||
* @param stringKey - The Base64 string to test. | ||
* @returns Whether the string represents a valid BIP-32 key. | ||
* @param publicKey - The compressed public key to get the fingerprint for. | ||
*/ | ||
function isValidBase64StringKey(stringKey) { | ||
if (!isValidBase64String(stringKey)) { | ||
return false; | ||
const getFingerprint = (publicKey) => { | ||
if (!isValidBufferKey(publicKey, 33)) { | ||
throw new Error(`Invalid public key: The key must be a 33-byte, non-zero Buffer.`); | ||
} | ||
if (stringKey.length !== constants_1.BASE_64_KEY_LENGTH) { | ||
return false; | ||
} | ||
if (stringKey === constants_1.BASE_64_ZERO) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
exports.isValidBase64StringKey = isValidBase64StringKey; | ||
return Buffer.from(ripemd160_1.ripemd160(publicKey)).readUInt32BE(0); | ||
}; | ||
exports.getFingerprint = getFingerprint; | ||
//# sourceMappingURL=utils.js.map |
{ | ||
"name": "@metamask/key-tree", | ||
"version": "3.0.1", | ||
"version": "4.0.0", | ||
"description": "An interface over BIP-32 and BIP-39 key derivation paths.", | ||
@@ -16,3 +16,3 @@ "repository": { | ||
"scripts": { | ||
"build": "tsc --project .", | ||
"build": "tsc --project tsconfig.build.json", | ||
"build:clean": "rimraf dist && yarn build", | ||
@@ -29,5 +29,7 @@ "lint": "yarn lint:eslint && yarn lint:misc --check", | ||
"dependencies": { | ||
"bip39": "^3.0.4", | ||
"keccak": "^3.0.2", | ||
"secp256k1": "^4.0.2" | ||
"@noble/ed25519": "^1.6.0", | ||
"@noble/hashes": "^1.0.0", | ||
"@noble/secp256k1": "^1.5.5", | ||
"@scure/base": "^1.0.0", | ||
"@scure/bip39": "^1.0.0" | ||
}, | ||
@@ -42,4 +44,2 @@ "devDependencies": { | ||
"@types/jest": "^27.0.2", | ||
"@types/keccak": "^3.0.1", | ||
"@types/secp256k1": "^4.0.3", | ||
"@typescript-eslint/eslint-plugin": "^5.0.0", | ||
@@ -54,4 +54,2 @@ "@typescript-eslint/parser": "^5.0.0", | ||
"eslint-plugin-prettier": "^4.0.0", | ||
"eth-hd-keyring": "^3.6.0", | ||
"ethereumjs-wallet": "^1.0.2", | ||
"jest": "^27.2.5", | ||
@@ -73,7 +71,5 @@ "prettier": "^2.4.1", | ||
"allowScripts": { | ||
"@lavamoat/preinstall-always-fail": false, | ||
"keccak": true, | ||
"secp256k1": true | ||
"@lavamoat/preinstall-always-fail": false | ||
} | ||
} | ||
} |
# @metamask/key-tree | ||
An interface over [BIP-44] key derivation paths. | ||
An interface over [SLIP-10] and [BIP-44] key derivation paths. | ||
@@ -15,3 +15,3 @@ ## Installation | ||
This package is designed to accommodate the creation of keys for any level of a BIP-44 path. | ||
This package is designed to accommodate the creation of keys for any level of a SLIP-10 or BIP-44 path. | ||
Recall that a BIP-44 HD tree path consists of the following nodes (and depths): | ||
@@ -28,3 +28,7 @@ | ||
This package exports two classes intended to facilitate the creation of keys in contexts with different privileges. | ||
The [SLIP-10] interface provides a more generic way for deriving keys, which is not constrained to the BIP-44 path | ||
nodes. Currently only Secp256k1 and Ed25519 are supported for SLIP-10, but NIST P-256 may be added if there is | ||
sufficient demand for it. | ||
This package exports a few classes intended to facilitate the creation of keys in contexts with different privileges. | ||
They are used as follows. | ||
@@ -43,3 +47,3 @@ | ||
const coinTypeNode = new BIP44CoinTypeNode([ | ||
const coinTypeNode = await BIP44CoinTypeNode.fromDerivationPath([ | ||
`bip39:${mnemonic}`, | ||
@@ -52,2 +56,3 @@ `bip32:44'`, // By BIP-44, the "purpose" node must be "44'" | ||
// can transmit JSON messages, such as window.postMessage. | ||
// Alternatively you can use `coinTypeNode.extendedKey` as well. | ||
stream.write(coinTypeNode.toJSON()); | ||
@@ -64,4 +69,8 @@ | ||
// { | ||
// key, // A Base64 string of the coin_type key | ||
// privateKey, // A hexadecimal string of the private key | ||
// publicKey, // A hexadecimal string of the public key | ||
// chainCode, // A hexadecimal string of the chain code | ||
// depth, // The number 2, which is the depth of coin_type nodes | ||
// parentFingerprint, // The fingerprint of the parent node as number | ||
// index, // The index of the node as number | ||
// coin_type, // In this case, the number 60 | ||
@@ -74,2 +83,3 @@ // path, // For visualization only. In this case: m / 44' / 60' | ||
// In this case, its path will be: m / 44' / 60' / 0' / 0 / address_index | ||
// Alternatively you can use an extended key (`xprv`) as well. | ||
const addressKeyDeriver = getBIP44AddressKeyDeriver(coinTypeNode); | ||
@@ -81,7 +91,10 @@ | ||
// m / 44' / 60' / 0' / 0 / 0 | ||
const addressKey0 = addressKeyDeriver(0); | ||
const addressKey0 = await addressKeyDeriver(0); | ||
// m / 44' / 60' / 0' / 0 / 1 | ||
const addressKey1 = addressKeyDeriver(1); | ||
const addressKey1 = await addressKeyDeriver(1); | ||
// m / 44' / 60' / 0' / 0 / 2' | ||
const addressKey2 = await addressKeyDeriver(2, true); | ||
// Now, the extended private keys can be used to derive the corresponding public | ||
@@ -91,4 +104,21 @@ // keys and protocol addresses. | ||
You can derive SLIP-10 keys as follows. | ||
```typescript | ||
import { SLIP10Node } from '@metamask/key-tree'; | ||
// Create a SLIP10Node from a derivation path. You can also specify a key and depth instead. | ||
const node = await SLIP10Node.fromDerivationPath({ | ||
curve: 'secp256k1', // or 'ed25519' | ||
derivationPath: [`bip39:${mnemonic}`, `bip32:0'`], | ||
}); | ||
// Derive the child node at m / 0' / 1' / 2'. This results in a new SLIP10Node. | ||
// Note that you cannot derive unhardened child nodes when using Ed25519 | ||
const childNode = await node.derive([`bip32:1'`, `bip32:2'`]); | ||
``` | ||
There are other ways of deriving keys in addition to the above example. | ||
See the docstrings in the [BIP44Node](./src/BIP44Node.ts) and [BIP44CoinTypeNode](./src/BIP44CoinTypeNode.ts) files for details. | ||
See the docstrings in the [BIP44Node](./src/BIP44Node.ts), [BIP44CoinTypeNode](./src/BIP44CoinTypeNode.ts) and | ||
[SLIP10Node](./src/SLIP10Node.ts) files for details. | ||
@@ -112,2 +142,3 @@ ### Internals | ||
- [BIP-44] | ||
- [SLIP-10] | ||
- [SLIP-44] | ||
@@ -119,2 +150,3 @@ - Network Working Group: ["Key Derivation Functions and their Uses"](https://trac.tools.ietf.org/html/draft-irtf-cfrg-kdf-uses-00) | ||
[bip-44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki | ||
[slip-10]: https://github.com/satoshilabs/slips/blob/master/slip-0010.md | ||
[slip-44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
245292
22
49
2822
143
5
1
+ Added@noble/ed25519@^1.6.0
+ Added@noble/hashes@^1.0.0
+ Added@noble/secp256k1@^1.5.5
+ Added@scure/base@^1.0.0
+ Added@scure/bip39@^1.0.0
+ Added@noble/ed25519@1.7.3(transitive)
+ Added@noble/secp256k1@1.7.1(transitive)
+ Added@scure/base@1.1.9(transitive)
+ Added@scure/bip39@1.4.0(transitive)
- Removedbip39@^3.0.4
- Removedkeccak@^3.0.2
- Removedsecp256k1@^4.0.2
- Removedbip39@3.1.0(transitive)
- Removedbn.js@4.12.0(transitive)
- Removedbrorand@1.1.0(transitive)
- Removedelliptic@6.6.0(transitive)
- Removedhash.js@1.1.7(transitive)
- Removedhmac-drbg@1.0.1(transitive)
- Removedinherits@2.0.4(transitive)
- Removedkeccak@3.0.4(transitive)
- Removedminimalistic-assert@1.0.1(transitive)
- Removedminimalistic-crypto-utils@1.0.1(transitive)
- Removednode-addon-api@2.0.25.1.0(transitive)
- Removednode-gyp-build@4.8.2(transitive)
- Removedreadable-stream@3.6.2(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedsecp256k1@4.0.4(transitive)
- Removedstring_decoder@1.3.0(transitive)
- Removedutil-deprecate@1.0.2(transitive)