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 isValidBIP32PathSegment function #131

Merged
merged 5 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 9 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export type BIP44Depth = MinBIP44Depth | 1 | 2 | 3 | 4 | MaxBIP44Depth;
// always "0". Here's an example Ethereum HD path for account "0":
// m / 44' / 60' / 0' / 0 / 0

export type UnprefixedNode = `${number}'`;

export type AnonymizedBIP39Node = 'm';
export type BIP39StringNode = `bip39:${string}`;
export type BIP39Node = BIP39StringNode | Uint8Array;
Expand All @@ -35,6 +37,13 @@ export const BIP44PurposeNodeToken = `bip32:44'`;

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

/**
* e.g.
* - 0
* - 0'
*/
export const UNPREFIXED_BIP_32_PATH_REGEX = /^(?<index>\d+)'?$/u;

/**
* e.g.
* - bip32:0
Expand Down
2 changes: 2 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
SLIP10Node,
secp256k1,
ed25519,
isValidBIP32PathSegment,
} from '.';

// This is purely for coverage shenanigans
Expand All @@ -15,5 +16,6 @@ describe('index', () => {
expect(SLIP10Node).toBeDefined();
expect(secp256k1).toBeDefined();
expect(ed25519).toBeDefined();
expect(isValidBIP32PathSegment).toBeDefined();
});
});
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ export {
} from './BIP44CoinTypeNode';
export * from './constants';
export type { CoinTypeToAddressIndices } from './utils';
export { isValidBIP32PathSegment } from './utils';
20 changes: 20 additions & 0 deletions src/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
decodeBase58check,
mnemonicPhraseToBytes,
getBytesUnsafe,
isValidBIP32PathSegment,
} from './utils';

// Inputs used for testing non-negative integers
Expand Down Expand Up @@ -197,6 +198,25 @@ describe('isValidBIP32Index', () => {
});
});

describe('isValidBIP32PathSegment', () => {
it('returns true if the path segment is valid', () => {
expect(isValidBIP32PathSegment(`0`)).toBe(true);
expect(isValidBIP32PathSegment(`0'`)).toBe(true);
expect(isValidBIP32PathSegment(`1`)).toBe(true);
expect(isValidBIP32PathSegment(`1'`)).toBe(true);
expect(isValidBIP32PathSegment(`1000`)).toBe(true);
expect(isValidBIP32PathSegment(`1000'`)).toBe(true);
});

it.each(['foo', `123''`, `'123'`, `123'/456'`, ...inputs])(
'returns false if the path segment is invalid',
(input) => {
// @ts-expect-error Invalid type.
expect(isValidBIP32PathSegment(input)).toBe(false);
},
);
});

describe('isHardened', () => {
it('returns true if the index is hardened', () => {
expect(isHardened(`0'`)).toBe(true);
Expand Down
25 changes: 25 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
HardenedBIP32Node,
MAX_BIP_32_INDEX,
UnhardenedBIP32Node,
UNPREFIXED_BIP_32_PATH_REGEX,
UnprefixedNode,
} from './constants';
import { curves, SupportedCurve } from './curves';

Expand Down Expand Up @@ -170,6 +172,29 @@ export function isValidBIP32Index(index: number): boolean {
return isValidInteger(index) && index <= MAX_BIP_32_INDEX;
}

/**
* Check if the value is a valid BIP-32 path segment, i.e., a string of the form
* `0'`.
*
* @param segment - The BIP-32 path segment to test.
* @returns Whether the path segment is a valid BIP-32 path segment.
*/
export function isValidBIP32PathSegment(
segment: string,
): segment is UnprefixedNode {
if (typeof segment !== 'string') {
return false;
}

const match = segment.match(UNPREFIXED_BIP_32_PATH_REGEX);
if (!match?.groups) {
return false;
}

const index = parseInt(match.groups.index, 10);
return isValidBIP32Index(index);
}

/**
* Check if the value is a hardened BIP-32 index. This only checks if the value
* ends with a `'` character, and does not validate that the index is a valid
Expand Down