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

BREAKING: Remove specification field in favour of SLIP-10 path type #124

Merged
merged 7 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/BIP44CoinTypeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,6 @@ export async function deriveBIP44AddressKey(
const childNode = await deriveChildNode({
path,
node,
specification: 'bip32',
});

return new BIP44Node(childNode);
Expand Down Expand Up @@ -414,7 +413,6 @@ export async function getBIP44AddressKeyDeriver(
: getUnhardenedBIP32NodeToken(address_index),
],
node: actualNode,
specification: 'bip32',
});

return new BIP44Node(slip10Node);
Expand Down
2 changes: 0 additions & 2 deletions src/BIP44Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ export class BIP44Node implements BIP44NodeInterface {
parentFingerprint,
index,
curve: 'secp256k1',
specification: 'bip32',
});

return new BIP44Node(node);
Expand Down Expand Up @@ -214,7 +213,6 @@ export class BIP44Node implements BIP44NodeInterface {
const node = await SLIP10Node.fromDerivationPath({
derivationPath,
curve: 'secp256k1',
specification: 'bip32',
});

return new BIP44Node(node);
Expand Down
45 changes: 8 additions & 37 deletions src/SLIP10Node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,6 @@ describe('SLIP10Node', () => {
privateKey: node.privateKey,
publicKey: node.publicKey,
chainCode: node.chainCode,
specification: node.specification,
});
});

Expand Down Expand Up @@ -438,20 +437,12 @@ describe('SLIP10Node', () => {

it('initializes a new node from a derivation path with a Uint8Array using ed25519', async () => {
const node = await SLIP10Node.fromDerivationPath({
derivationPath: [
defaultBip39BytesToken,
BIP44PurposeNodeToken,
`bip32:60'`,
],
derivationPath: [defaultBip39BytesToken, `slip10:44'`, `slip10:60'`],
curve: 'ed25519',
});

const stringNode = await SLIP10Node.fromDerivationPath({
derivationPath: [
defaultBip39NodeToken,
BIP44PurposeNodeToken,
`bip32:60'`,
],
derivationPath: [defaultBip39NodeToken, `slip10:44'`, `slip10:60'`],
curve: 'ed25519',
});

Expand Down Expand Up @@ -522,9 +513,7 @@ describe('SLIP10Node', () => {
it('throws an error if no curve is specified', async () => {
await expect(
// @ts-expect-error No curve specified, but required in type
SLIP10Node.fromDerivationPath({
specification: 'bip32',
}),
SLIP10Node.fromDerivationPath({}),
).rejects.toThrow('Invalid curve: Must specify a curve.');
});

Expand All @@ -539,18 +528,6 @@ describe('SLIP10Node', () => {
'Invalid curve: Only the following curves are supported: secp256k1, ed25519.',
);
});

it('throws an error for unsupported specifications', async () => {
await expect(
SLIP10Node.fromDerivationPath({
curve: 'secp256k1',
// @ts-expect-error Invalid specification name for type
specification: 'foo bar',
}),
).rejects.toThrow(
'Invalid specification: Must be one of bip32, slip10. Received "foo bar".',
);
});
});

describe('derive', () => {
Expand Down Expand Up @@ -630,14 +607,14 @@ describe('SLIP10Node', () => {
const node = await SLIP10Node.fromDerivationPath({
derivationPath: [
defaultBip39NodeToken,
BIP44PurposeNodeToken,
`bip32:3'`,
`bip32:0'`,
`slip10:44'`,
`slip10:3'`,
`slip10:0'`,
],
curve: 'ed25519',
});

await expect(node.derive(['bip32:0'])).rejects.toThrow(
await expect(node.derive(['slip10:0'])).rejects.toThrow(
'Invalid path: Cannot derive unhardened child keys with ed25519.',
);
});
Expand Down Expand Up @@ -774,11 +751,7 @@ describe('SLIP10Node', () => {

it('throws an error when trying to get an address for an ed25519 node', async () => {
const node = await SLIP10Node.fromDerivationPath({
derivationPath: [
defaultBip39NodeToken,
BIP44PurposeNodeToken,
`bip32:60'`,
],
derivationPath: [defaultBip39NodeToken, `slip10:44'`, `slip10:60'`],
curve: 'ed25519',
});

Expand Down Expand Up @@ -863,7 +836,6 @@ describe('SLIP10Node', () => {
privateKey: node.privateKey,
publicKey: node.publicKey,
chainCode: node.chainCode,
specification: node.specification,
});

expect(JSON.parse(JSON.stringify(nodeJson))).toStrictEqual({
Expand All @@ -875,7 +847,6 @@ describe('SLIP10Node', () => {
privateKey: node.privateKey,
publicKey: node.publicKey,
chainCode: node.chainCode,
specification: node.specification,
});
});
});
Expand Down
42 changes: 0 additions & 42 deletions src/SLIP10Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,14 @@ import {
} from './constants';
import { getCurveByName, SupportedCurve } from './curves';
import { deriveKeyFromPath } from './derivation';
import { Specification } from './derivers';
import { publicKeyToEthAddress } from './derivers/bip32';
import {
getBytes,
getBytesUnsafe,
getFingerprint,
getSpecification,
isValidInteger,
validateBIP32Index,
validateCurve,
validateSpecification,
} from './utils';

/**
Expand Down Expand Up @@ -68,21 +65,6 @@ export type JsonSLIP10Node = {
* The name of the curve used by the node.
*/
readonly curve: SupportedCurve;

/**
* The specification used to derive this node. Defaults to `bip32` when the
* `secp256k1` curve is used, and `slip10` when the `ed25519` curve is used.
*
* While SLIP-10 and BIP-32 are largely compatible when the `secp256k1` curve
* is used, they differ in the way they handle key derivation errors. The
* probability of this happening is extremely low, but it is possible. When
* full compatibility with one of these specifications is required, this field
* should be set to the appropriate value.
*
* Note that `ed25519` is not supported by BIP-32, so the `bip32`
* specification is not available for this curve.
*/
readonly specification: Specification;
};

export type SLIP10NodeInterface = JsonSLIP10Node & {
Expand Down Expand Up @@ -114,7 +96,6 @@ export type SLIP10NodeConstructorOptions = {
readonly privateKey?: Uint8Array;
readonly publicKey: Uint8Array;
readonly curve: SupportedCurve;
readonly specification?: Specification;
};

export type SLIP10ExtendedKeyOptions = {
Expand All @@ -126,13 +107,11 @@ export type SLIP10ExtendedKeyOptions = {
readonly privateKey?: string | Uint8Array;
readonly publicKey?: string | Uint8Array;
readonly curve: SupportedCurve;
readonly specification?: Specification;
};

export type SLIP10DerivationPathOptions = {
readonly derivationPath: RootedSLIP10PathTuple;
readonly curve: SupportedCurve;
readonly specification?: Specification;
};

export class SLIP10Node implements SLIP10NodeInterface {
Expand Down Expand Up @@ -167,7 +146,6 @@ export class SLIP10Node implements SLIP10NodeInterface {
* specified, this parameter is ignored.
* @param options.chainCode - The chain code for the node.
* @param options.curve - The curve used by the node.
* @param options.specification - The specification used to derive this node.
*/
static async fromExtendedKey({
depth,
Expand All @@ -178,12 +156,10 @@ export class SLIP10Node implements SLIP10NodeInterface {
publicKey,
chainCode,
curve,
specification = getSpecification(curve),
}: SLIP10ExtendedKeyOptions) {
const chainCodeBytes = getBytes(chainCode, BYTES_KEY_LENGTH);

validateCurve(curve);
validateSpecification(specification);
validateBIP32Depth(depth);
validateBIP32Index(index);
validateRootIndex(index, depth);
Expand Down Expand Up @@ -213,7 +189,6 @@ export class SLIP10Node implements SLIP10NodeInterface {
privateKey: privateKeyBytes,
publicKey: await curveObject.getPublicKey(privateKeyBytes),
curve,
specification,
},
this.#constructorGuard,
);
Expand All @@ -231,7 +206,6 @@ export class SLIP10Node implements SLIP10NodeInterface {
chainCode: chainCodeBytes,
publicKey: publicKeyBytes,
curve,
specification,
},
this.#constructorGuard,
);
Expand Down Expand Up @@ -263,17 +237,13 @@ export class SLIP10Node implements SLIP10NodeInterface {
* @param options.derivationPath - The rooted HD tree path that will be used
* to derive the key of this node.
* @param options.curve - The curve used by the node.
* @param options.specification - The specification used to derive the key.
* Defaults to `slip10`.
* @returns A new SLIP-10 node.
*/
static async fromDerivationPath({
derivationPath,
curve,
specification = getSpecification(curve),
}: SLIP10DerivationPathOptions) {
validateCurve(curve);
validateSpecification(specification);

if (!derivationPath) {
throw new Error('Invalid options: Must provide a derivation path.');
Expand All @@ -289,16 +259,13 @@ export class SLIP10Node implements SLIP10NodeInterface {
path: derivationPath,
depth: derivationPath.length - 1,
curve,
specification,
});
}

static #constructorGuard = Symbol('SLIP10Node.constructor');

public readonly curve: SupportedCurve;

public readonly specification: Specification;

public readonly depth: number;

public readonly masterFingerprint?: number;
Expand All @@ -324,7 +291,6 @@ export class SLIP10Node implements SLIP10NodeInterface {
privateKey,
publicKey,
curve,
specification = getSpecification(curve),
}: SLIP10NodeConstructorOptions,
constructorGuard?: symbol,
) {
Expand All @@ -341,7 +307,6 @@ export class SLIP10Node implements SLIP10NodeInterface {
this.privateKeyBytes = privateKey;
this.publicKeyBytes = publicKey;
this.curve = curve;
this.specification = specification;

Object.freeze(this);
}
Expand Down Expand Up @@ -418,7 +383,6 @@ export class SLIP10Node implements SLIP10NodeInterface {
return await deriveChildNode({
path,
node: this,
specification: this.specification,
});
}

Expand All @@ -433,7 +397,6 @@ export class SLIP10Node implements SLIP10NodeInterface {
privateKey: this.privateKey,
publicKey: this.publicKey,
chainCode: this.chainCode,
specification: this.specification,
};
}
}
Expand Down Expand Up @@ -541,7 +504,6 @@ export function validateRootIndex(index: number, depth: number) {
type DeriveChildNodeArgs = {
path: SLIP10PathTuple;
node: SLIP10Node | BIP44Node | BIP44CoinTypeNode;
specification: Specification;
};

/**
Expand All @@ -550,14 +512,11 @@ type DeriveChildNodeArgs = {
* @param options - The options to use when deriving the child key.
* @param options.node - The node to derive from.
* @param options.path - The path to the child node / key.
* @param options.specification - The specification to use when deriving the
* child key.
* @returns The derived key and depth.
*/
export async function deriveChildNode({
path,
node,
specification,
}: DeriveChildNodeArgs): Promise<SLIP10Node> {
if (path.length === 0) {
throw new Error(
Expand All @@ -574,6 +533,5 @@ export async function deriveChildNode({
path,
node,
depth: newDepth,
specification,
});
}
22 changes: 20 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,33 @@ export type BIP44Depth = MinBIP44Depth | 1 | 2 | 3 | 4 | MaxBIP44Depth;
export type AnonymizedBIP39Node = 'm';
export type BIP39StringNode = `bip39:${string}`;
export type BIP39Node = BIP39StringNode | Uint8Array;

export type HardenedBIP32Node = `bip32:${number}'`;
export type UnhardenedBIP32Node = `bip32:${number}`;
export type BIP32Node = HardenedBIP32Node | UnhardenedBIP32Node;

export type HardenedSLIP10Node = `slip10:${number}'`;
export type UnhardenedSLIP10Node = `slip10:${number}`;
export type SLIP10PathNode = HardenedSLIP10Node | UnhardenedSLIP10Node;

export const BIP44PurposeNodeToken = `bip32:44'`;

export const UNPREFIXED_PATH_REGEX = /^\d+$/u;

/**
* e.g.
* - bip32:0
* - bip32:0'
*/
export const BIP_32_PATH_REGEX = /^bip32:\d+'?$/u;

/**
* e.g.
* - slip10:0
* - slip10:0'
*/
export const SLIP_10_PATH_REGEX = /^slip10:\d+'?$/u;

/**
* bip39:<SPACE_DELMITED_SEED_PHRASE>
*
Expand Down Expand Up @@ -138,8 +152,12 @@ export type PartialHDPathTuple =
*/
export type HDPathTuple = RootedHDPathTuple | PartialHDPathTuple;

export type RootedSLIP10PathTuple = readonly [BIP39Node, ...BIP32Node[]];
export type SLIP10PathTuple = readonly BIP32Node[];
export type RootedSLIP10PathTuple = readonly [
BIP39Node,
...(BIP32Node[] | SLIP10PathNode[]),
];

export type SLIP10PathTuple = readonly BIP32Node[] | readonly SLIP10PathNode[];
export type SLIP10Path = RootedSLIP10PathTuple | SLIP10PathTuple;

export type FullHDPathTuple = RootedHDPathTuple5;
Loading