Skip to content

Commit

Permalink
Add isValidBIP32PathSegment function (#131)
Browse files Browse the repository at this point in the history
* Add isValidBIP32Path function

* Add more test cases

* Rename function to `isValidBIP32PathSegment`

* Address review comments

* Add test for coverage
  • Loading branch information
Mrtenz authored Jun 19, 2023
1 parent 77b7bff commit e720e13
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 0 deletions.
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

0 comments on commit e720e13

Please sign in to comment.