Skip to content

Commit

Permalink
Validate parent fingerprint and master fingerprint for a given depth
Browse files Browse the repository at this point in the history
  • Loading branch information
Mrtenz committed Mar 7, 2023
1 parent 64e54b1 commit 0380556
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/BIP44CoinTypeNode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('BIP44CoinTypeNode', () => {
const options = {
depth: 2,
index: 0,
parentFingerprint: 0,
parentFingerprint: 1,
};

const inputs = [
Expand Down
4 changes: 2 additions & 2 deletions src/BIP44Node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('BIP44Node', () => {
privateKey,
chainCode,
depth: 2,
parentFingerprint: 0,
parentFingerprint: 1,
index: 0,
});

Expand All @@ -54,7 +54,7 @@ describe('BIP44Node', () => {
privateKey,
chainCode,
depth: 2,
parentFingerprint: 0,
parentFingerprint: 1,
index: 0,
});

Expand Down
61 changes: 61 additions & 0 deletions src/SLIP10Node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,67 @@ describe('SLIP10Node', () => {
'Invalid private key: Value is not a valid secp256k1 private key.',
);
});

it('throws if the depth is zero and the parent fingerprint is not zero', async () => {
await expect(
SLIP10Node.fromExtendedKey({
privateKey: new Uint8Array(32).fill(1),
chainCode: new Uint8Array(32).fill(1),
depth: 0,
parentFingerprint: 1,
index: 0,
curve: 'secp256k1',
}),
).rejects.toThrow(
'Invalid parent fingerprint: The fingerprint of the root node must be 0. Received: "1".',
);
});

it('throws if the depth is not zero and the parent fingerprint is zero', async () => {
await expect(
SLIP10Node.fromExtendedKey({
privateKey: new Uint8Array(32).fill(1),
chainCode: new Uint8Array(32).fill(1),
depth: 1,
parentFingerprint: 0,
index: 0,
curve: 'secp256k1',
}),
).rejects.toThrow(
'Invalid parent fingerprint: The fingerprint of a child node must not be 0. Received: "0".',
);
});

it('throws if the depth is >= 2 and the parent fingerprint is equal to the master fingerprint', async () => {
await expect(
SLIP10Node.fromExtendedKey({
privateKey: new Uint8Array(32).fill(1),
chainCode: new Uint8Array(32).fill(1),
depth: 2,
parentFingerprint: 1,
masterFingerprint: 1,
index: 0,
curve: 'secp256k1',
}),
).rejects.toThrow(
'Invalid parent fingerprint: The fingerprint of a child node cannot be equal to the master fingerprint. Received: "1".',
);
});

it('throws if the depth is zero and the index is not zero', async () => {
await expect(
SLIP10Node.fromExtendedKey({
privateKey: new Uint8Array(32).fill(1),
chainCode: new Uint8Array(32).fill(1),
depth: 0,
parentFingerprint: 0,
index: 1,
curve: 'secp256k1',
}),
).rejects.toThrow(
'Invalid index: The index of the root node must be 0. Received: "1".',
);
});
});

describe('fromDerivationPath', () => {
Expand Down
74 changes: 73 additions & 1 deletion src/SLIP10Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,13 @@ export class SLIP10Node implements SLIP10NodeInterface {
validateCurve(curve);
validateBIP32Depth(depth);
validateBIP32Index(index);
validateParentFingerprint(parentFingerprint);
validateRootIndex(index, depth);
validateParentFingerprint(parentFingerprint, depth);
validateMasterParentFingerprint(
masterFingerprint,
parentFingerprint,
depth,
);

const curveObject = getCurveByName(curve);

Expand Down Expand Up @@ -436,9 +442,13 @@ export function validateBIP32Depth(depth: unknown): asserts depth is number {
* integer `number`. Throws an error if validation fails.
*
* @param parentFingerprint - The parent fingerprint to validate.
* @param depth - The depth of the node to validate.
* @throws If the parent fingerprint is not a positive integer, or invalid for
* the current depth.
*/
export function validateParentFingerprint(
parentFingerprint: unknown,
depth: number,
): asserts parentFingerprint is number {
if (!isValidInteger(parentFingerprint)) {
throw new Error(
Expand All @@ -447,6 +457,68 @@ export function validateParentFingerprint(
)}".`,
);
}

if (depth === 0 && parentFingerprint !== 0) {
throw new Error(
`Invalid parent fingerprint: The fingerprint of the root node must be 0. Received: "${String(
parentFingerprint,
)}".`,
);
}

if (depth > 0 && parentFingerprint === 0) {
throw new Error(
`Invalid parent fingerprint: The fingerprint of a child node must not be 0. Received: "${String(
parentFingerprint,
)}".`,
);
}
}

/**
* Validate that a given combination of master fingerprint and parent
* fingerprint is valid for the given depth.
*
* @param masterFingerprint - The master fingerprint to validate.
* @param parentFingerprint - The parent fingerprint to validate.
* @param depth - The depth of the node to validate.
* @throws If the combination of master fingerprint and parent fingerprint is
* invalid for the given depth.
*/
export function validateMasterParentFingerprint(
masterFingerprint: number | undefined,
parentFingerprint: number,
depth: number,
) {
// The master fingerprint is optional.
if (!masterFingerprint) {
return;
}

if (depth >= 2 && masterFingerprint === parentFingerprint) {
throw new Error(
`Invalid parent fingerprint: The fingerprint of a child node cannot be equal to the master fingerprint. Received: "${String(
parentFingerprint,
)}".`,
);
}
}

/**
* Validate that the index is zero for the root node.
*
* @param index - The index to validate.
* @param depth - The depth of the node to validate.
* @throws If the index is not zero for the root node.
*/
export function validateRootIndex(index: number, depth: number) {
if (depth === 0 && index !== 0) {
throw new Error(
`Invalid index: The index of the root node must be 0. Received: "${String(
index,
)}".`,
);
}
}

type DeriveChildNodeArgs = {
Expand Down

0 comments on commit 0380556

Please sign in to comment.