Skip to content

Commit eb57652

Browse files
committed
Add isValidBIP32Path function
1 parent 7356280 commit eb57652

File tree

4 files changed

+52
-1
lines changed

4 files changed

+52
-1
lines changed

src/constants.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export type BIP44Depth = MinBIP44Depth | 1 | 2 | 3 | 4 | MaxBIP44Depth;
1919
// always "0". Here's an example Ethereum HD path for account "0":
2020
// m / 44' / 60' / 0' / 0 / 0
2121

22+
export type UnprefixedNode = `${number}'`;
23+
2224
export type AnonymizedBIP39Node = 'm';
2325
export type BIP39StringNode = `bip39:${string}`;
2426
export type BIP39Node = BIP39StringNode | Uint8Array;
@@ -35,6 +37,13 @@ export const BIP44PurposeNodeToken = `bip32:44'`;
3537

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

40+
/**
41+
* e.g.
42+
* - 0
43+
* - 0'
44+
*/
45+
export const UNPREFIXED_BIP_32_PATH_REGEX = /^(?<index>\d+)'?$/u;
46+
3847
/**
3948
* e.g.
4049
* - bip32:0

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ export {
2828
getBIP44AddressKeyDeriver,
2929
} from './BIP44CoinTypeNode';
3030
export * from './constants';
31-
export type { CoinTypeToAddressIndices } from './utils';
31+
export type { CoinTypeToAddressIndices, isValidBIP32Path } from './utils';

src/utils.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
decodeBase58check,
2424
mnemonicPhraseToBytes,
2525
getBytesUnsafe,
26+
isValidBIP32Path,
2627
} from './utils';
2728

2829
// Inputs used for testing non-negative integers
@@ -197,6 +198,24 @@ describe('isValidBIP32Index', () => {
197198
});
198199
});
199200

201+
describe('isValidBIP32Path', () => {
202+
it('returns true if the path is valid', () => {
203+
expect(isValidBIP32Path(`0`)).toBe(true);
204+
expect(isValidBIP32Path(`0'`)).toBe(true);
205+
expect(isValidBIP32Path(`1`)).toBe(true);
206+
expect(isValidBIP32Path(`1'`)).toBe(true);
207+
expect(isValidBIP32Path(`1000`)).toBe(true);
208+
expect(isValidBIP32Path(`1000'`)).toBe(true);
209+
});
210+
211+
it.each(['foo', `123''`, `'123'`, `123'/456'`])(
212+
'returns false if the index is invalid',
213+
(input) => {
214+
expect(isValidBIP32Path(input)).toBe(false);
215+
},
216+
);
217+
});
218+
200219
describe('isHardened', () => {
201220
it('returns true if the index is hardened', () => {
202221
expect(isHardened(`0'`)).toBe(true);

src/utils.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
HardenedBIP32Node,
1414
MAX_BIP_32_INDEX,
1515
UnhardenedBIP32Node,
16+
UNPREFIXED_BIP_32_PATH_REGEX,
17+
UnprefixedNode,
1618
} from './constants';
1719
import { curves, SupportedCurve } from './curves';
1820

@@ -170,6 +172,27 @@ export function isValidBIP32Index(index: number): boolean {
170172
return isValidInteger(index) && index <= MAX_BIP_32_INDEX;
171173
}
172174

175+
/**
176+
* Check if the value is a valid BIP-32 path, i.e., a string of the form
177+
* `0'`.
178+
*
179+
* @param path - The BIP-32 path to test.
180+
* @returns Whether the path is a valid BIP-32 path.
181+
*/
182+
export function isValidBIP32Path(path: string): path is UnprefixedNode {
183+
if (typeof path !== 'string') {
184+
return false;
185+
}
186+
187+
const match = path.match(UNPREFIXED_BIP_32_PATH_REGEX);
188+
if (!match?.groups) {
189+
return false;
190+
}
191+
192+
const index = parseInt(match.groups.index, 10);
193+
return isValidBIP32Index(index);
194+
}
195+
173196
/**
174197
* Check if the value is a hardened BIP-32 index. This only checks if the value
175198
* ends with a `'` character, and does not validate that the index is a valid

0 commit comments

Comments
 (0)