Skip to content

Commit

Permalink
Add option to BIP44Node and BIP44CoinTypeNode
Browse files Browse the repository at this point in the history
  • Loading branch information
Mrtenz committed Nov 25, 2024
1 parent 2cbb41d commit 779fb0a
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 65 deletions.
103 changes: 72 additions & 31 deletions src/BIP44CoinTypeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
HardenedBIP32Node,
} from './constants';
import { BIP_32_HARDENED_OFFSET } from './constants';
import type { CryptographicFunctions } from './cryptography';
import type { SupportedCurve } from './curves';
import { deriveChildNode } from './SLIP10Node';
import type { CoinTypeToAddressIndices } from './utils';
Expand Down Expand Up @@ -75,19 +76,28 @@ export class BIP44CoinTypeNode implements BIP44CoinTypeNodeInterface {
* @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.
* @param cryptographicFunctions - The cryptographic functions to use. If
* provided, these will be used instead of the built-in implementations.
*/
static async fromJSON(json: JsonBIP44Node, coin_type: number) {
static async fromJSON(
json: JsonBIP44Node,
coin_type: number,
cryptographicFunctions?: CryptographicFunctions,
) {
validateCoinType(coin_type);
validateCoinTypeNodeDepth(json.depth);

const node = await BIP44Node.fromExtendedKey({
depth: json.depth,
index: json.index,
parentFingerprint: json.parentFingerprint,
chainCode: hexStringToBytes(json.chainCode),
privateKey: nullableHexStringToBytes(json.privateKey),
publicKey: hexStringToBytes(json.publicKey),
});
const node = await BIP44Node.fromExtendedKey(
{
depth: json.depth,
index: json.index,
parentFingerprint: json.parentFingerprint,
chainCode: hexStringToBytes(json.chainCode),
privateKey: nullableHexStringToBytes(json.privateKey),
publicKey: hexStringToBytes(json.publicKey),
},
cryptographicFunctions,
);

return new BIP44CoinTypeNode(node, coin_type);
}
Expand All @@ -107,13 +117,21 @@ export class BIP44CoinTypeNode implements BIP44CoinTypeNodeInterface {
* `0 / 1 / 2 / 3 / 4 / 5`
*
* @param derivationPath - The derivation path for the key of this node.
* @param cryptographicFunctions - The cryptographic functions to use. If
* provided, these will be used instead of the built-in implementations.
*/
static async fromDerivationPath(derivationPath: CoinTypeHDPathTuple) {
static async fromDerivationPath(
derivationPath: CoinTypeHDPathTuple,
cryptographicFunctions?: CryptographicFunctions,
) {
validateCoinTypeNodeDepth(derivationPath.length - 1);

const node = await BIP44Node.fromDerivationPath({
derivationPath,
});
const node = await BIP44Node.fromDerivationPath(
{
derivationPath,
},
cryptographicFunctions,
);

// Split the bip32 string token and extract the coin_type index.
const pathPart = derivationPath[BIP_44_COIN_TYPE_DEPTH].split(
Expand Down Expand Up @@ -324,23 +342,29 @@ function validateCoinType(coin_type: unknown): asserts coin_type is number {
* @param indices.account - The `account` index. Default: `0`.
* @param indices.change - The `change` index. Default: `0`.
* @param indices.address_index - The `address_index` index.
* @param cryptographicFunctions - The cryptographic functions to use. If
* provided, these will be used instead of the built-in implementations.
* @returns The derived `address_index` key for the specified derivation path.
*/
export async function deriveBIP44AddressKey(
parentKeyOrNode: BIP44CoinTypeNode | JsonBIP44CoinTypeNode | string,
{ account = 0, change = 0, address_index }: CoinTypeToAddressIndices,
cryptographicFunctions?: CryptographicFunctions,
): Promise<BIP44Node> {
const path = getBIP44CoinTypeToAddressPathTuple({
account,
change,
address_index,
});

const node = await getNode(parentKeyOrNode);
const childNode = await deriveChildNode({
path,
node,
});
const node = await getNode(parentKeyOrNode, cryptographicFunctions);
const childNode = await deriveChildNode(
{
path,
node,
},
cryptographicFunctions,
);

return new BIP44Node(childNode);
}
Expand Down Expand Up @@ -391,16 +415,19 @@ export type BIP44AddressKeyDeriver = {
* This node contains a BIP-44 key of depth 2, `coin_type`.
* @param accountAndChangeIndices - The `account` and `change` indices that
* will be used to derive addresses.
* @param cryptographicFunctions - The cryptographic functions to use. If
* provided, these will be used instead of the built-in implementations.
* @returns The deriver function for the derivation path specified by the
* `coin_type` node, `account`, and `change` indices.
*/
export async function getBIP44AddressKeyDeriver(
node: BIP44CoinTypeNode | JsonBIP44CoinTypeNode | string,
accountAndChangeIndices?: Omit<CoinTypeToAddressIndices, 'address_index'>,
cryptographicFunctions?: CryptographicFunctions,
) {
const { account = 0, change = 0 } = accountAndChangeIndices ?? {};

const actualNode = await getNode(node);
const actualNode = await getNode(node, cryptographicFunctions);

const accountNode = getHardenedBIP32NodeToken(account);
const changeNode = getBIP32NodeToken(change);
Expand All @@ -409,16 +436,19 @@ export async function getBIP44AddressKeyDeriver(
address_index: number,
isHardened = false,
): Promise<BIP44Node> => {
const slip10Node = await deriveChildNode({
path: [
accountNode,
changeNode,
isHardened
? getHardenedBIP32NodeToken(address_index)
: getUnhardenedBIP32NodeToken(address_index),
],
node: actualNode,
});
const slip10Node = await deriveChildNode(
{
path: [
accountNode,
changeNode,
isHardened
? getHardenedBIP32NodeToken(address_index)
: getUnhardenedBIP32NodeToken(address_index),
],
node: actualNode,
},
cryptographicFunctions,
);

return new BIP44Node(slip10Node);
};
Expand All @@ -441,9 +471,13 @@ export async function getBIP44AddressKeyDeriver(
* 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.
* @param cryptographicFunctions - The cryptographic functions to use. If
* provided, these will be used instead of the built-in implementations. This is
* only used if the node is an extended key string or JSON object.
*/
async function getNode(
node: BIP44CoinTypeNode | JsonBIP44CoinTypeNode | string,
cryptographicFunctions?: CryptographicFunctions,
): Promise<BIP44CoinTypeNode> {
if (node instanceof BIP44CoinTypeNode) {
validateCoinTypeNodeDepth(node.depth);
Expand All @@ -452,7 +486,10 @@ async function getNode(
}

if (typeof node === 'string') {
const bip44Node = await BIP44Node.fromExtendedKey(node);
const bip44Node = await BIP44Node.fromExtendedKey(
node,
cryptographicFunctions,
);
const coinTypeNode = await BIP44CoinTypeNode.fromNode(
bip44Node,
bip44Node.index - BIP_32_HARDENED_OFFSET,
Expand All @@ -463,5 +500,9 @@ async function getNode(
return coinTypeNode;
}

return BIP44CoinTypeNode.fromJSON(node, node.coin_type);
return BIP44CoinTypeNode.fromJSON(
node,
node.coin_type,
cryptographicFunctions,
);
}
88 changes: 56 additions & 32 deletions src/BIP44Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
MAX_BIP_44_DEPTH,
MIN_BIP_44_DEPTH,
} from './constants';
import type { CryptographicFunctions } from './cryptography';
import type { SupportedCurve } from './curves';
import { decodeExtendedKey, PRIVATE_KEY_VERSION } from './extended-keys';
import { SLIP10Node, validateBIP32Depth } from './SLIP10Node';
Expand Down Expand Up @@ -104,9 +105,14 @@ export class BIP44Node implements BIP44NodeInterface {
* for documentation.
*
* @param json - The JSON representation of a SLIP-10 node.
* @param cryptographicFunctions - The cryptographic functions to use. If
* provided, these will be used instead of the built-in implementations.
*/
static async fromJSON(json: JsonBIP44Node): Promise<BIP44Node> {
return BIP44Node.fromExtendedKey(json);
static async fromJSON(
json: JsonBIP44Node,
cryptographicFunctions?: CryptographicFunctions,
): Promise<BIP44Node> {
return BIP44Node.fromExtendedKey(json, cryptographicFunctions);
}

/**
Expand All @@ -124,9 +130,12 @@ export class BIP44Node implements BIP44NodeInterface {
* @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.
* @param cryptographicFunctions - The cryptographic functions to use. If
* provided, these will be used instead of the built-in implementations.
*/
static async fromExtendedKey(
options: BIP44ExtendedKeyOptions | string,
cryptographicFunctions?: CryptographicFunctions,
): Promise<BIP44Node> {
if (typeof options === 'string') {
const extendedKey = decodeExtendedKey(options);
Expand All @@ -136,24 +145,30 @@ export class BIP44Node implements BIP44NodeInterface {
if (extendedKey.version === PRIVATE_KEY_VERSION) {
const { privateKey } = extendedKey;

return BIP44Node.fromExtendedKey({
depth,
parentFingerprint,
index,
privateKey,
chainCode,
});
return BIP44Node.fromExtendedKey(
{
depth,
parentFingerprint,
index,
privateKey,
chainCode,
},
cryptographicFunctions,
);
}

const { publicKey } = extendedKey;

return BIP44Node.fromExtendedKey({
depth,
parentFingerprint,
index,
publicKey,
chainCode,
});
return BIP44Node.fromExtendedKey(
{
depth,
parentFingerprint,
index,
publicKey,
chainCode,
},
cryptographicFunctions,
);
}

const {
Expand All @@ -167,15 +182,18 @@ export class BIP44Node implements BIP44NodeInterface {

validateBIP44Depth(depth);

const node = await SLIP10Node.fromExtendedKey({
privateKey,
publicKey,
chainCode,
depth,
parentFingerprint,
index,
curve: 'secp256k1',
});
const node = await SLIP10Node.fromExtendedKey(
{
privateKey,
publicKey,
chainCode,
depth,
parentFingerprint,
index,
curve: 'secp256k1',
},
cryptographicFunctions,
);

return new BIP44Node(node);
}
Expand All @@ -200,17 +218,23 @@ export class BIP44Node implements BIP44NodeInterface {
* @param options - An object containing the derivation path.
* @param options.derivationPath - The rooted HD tree path that will be used
* to derive the key of this node.
* @param cryptographicFunctions - The cryptographic functions to use. If
* provided, these will be used instead of the built-in implementations.
*/
static async fromDerivationPath({
derivationPath,
}: BIP44DerivationPathOptions): Promise<BIP44Node> {
static async fromDerivationPath(
{ derivationPath }: BIP44DerivationPathOptions,
cryptographicFunctions?: CryptographicFunctions,
): Promise<BIP44Node> {
validateBIP44Depth(derivationPath.length - 1);
validateBIP44DerivationPath(derivationPath, MIN_BIP_44_DEPTH);

const node = await SLIP10Node.fromDerivationPath({
derivationPath,
curve: 'secp256k1',
});
const node = await SLIP10Node.fromDerivationPath(
{
derivationPath,
curve: 'secp256k1',
},
cryptographicFunctions,
);

return new BIP44Node(node);
}
Expand Down
2 changes: 1 addition & 1 deletion src/cryptography.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type CryptographicFunctions = {
* @param password - The password to hash.
* @param salt - The salt to use.
* @param iterations - The number of iterations.
* @param keyLength - The desired key length.
* @param keyLength - The desired key length in bytes.
* @returns The PBKDF2 of the password.
*/
pbkdf2Sha512?: (
Expand Down
1 change: 0 additions & 1 deletion src/derivers/bip39.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ export async function deriveChildKey(
);
case 'cip3':
return entropyToCip3MasterNode(
// TODO: Replace this.
mnemonicToEntropy(path, englishWordlist),
curve,
cryptographicFunctions,
Expand Down

0 comments on commit 779fb0a

Please sign in to comment.