Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cryptographic functions with option to specify custom functions #197

Merged
merged 11 commits into from
Nov 25, 2024
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,
FrederikBolding marked this conversation as resolved.
Show resolved Hide resolved
) {
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
Loading
Loading