Skip to content
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
89 changes: 89 additions & 0 deletions packages/hdwallet-keepkey/src/keepkey-initialize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Unit tests for KeepKeyHDWallet.initialize() version-field validation.
*
* Regression: when transport.call() returns a Features payload missing
* majorVersion / minorVersion / patchVersion (e.g. wrong message type
* miscast, decode mismatch, or device in an unexpected state), the prior
* code constructed `vundefined.undefined.undefined` and called
* semver.gte() on it, which threw the opaque error
* "Invalid Version: vundefined.undefined.undefined"
* leaking from the WebUSB pair path with no actionable diagnostic.
*
* The fix throws a clear error before reaching semver.
*/
import { KeepKeyHDWallet } from "./keepkey";

function makeMockTransport(callResponse: any) {
return {
debugLink: false,
call: jest.fn().mockResolvedValue(callResponse),
getDeviceID: jest.fn().mockResolvedValue("mock-device-id"),
keyring: { addAlias: jest.fn() },
} as any;
}

describe("KeepKeyHDWallet.initialize() version-field validation", () => {
it("throws a clear error when Features has no version fields", async () => {
const transport = makeMockTransport({
message: {
deviceId: "mock-device-id",
// majorVersion / minorVersion / patchVersion intentionally absent
},
});
const wallet = new KeepKeyHDWallet(transport);

const err = await wallet.initialize().catch((e: any) => e);
expect(err).toBeInstanceOf(Error);
expect(err.message).toMatch(/KeepKey Initialize returned Features without firmware version/);
expect(err.message).toMatch(/major=undefined, minor=undefined, patch=undefined/);
});

it("throws when only majorVersion is missing", async () => {
const transport = makeMockTransport({
message: {
deviceId: "mock-device-id",
minorVersion: 14,
patchVersion: 0,
},
});
const wallet = new KeepKeyHDWallet(transport);
await expect(wallet.initialize()).rejects.toThrow(

Check failure on line 50 in packages/hdwallet-keepkey/src/keepkey-initialize.test.ts

View workflow job for this annotation

GitHub Actions / build (18)

Replace `⏎······/KeepKey·Initialize·returned·Features·without·firmware·version/⏎····` with `/KeepKey·Initialize·returned·Features·without·firmware·version/`
/KeepKey Initialize returned Features without firmware version/
);
});

it("resolves successfully when all version fields are present", async () => {
const transport = makeMockTransport({
message: {
deviceId: "mock-device-id",
majorVersion: 7,
minorVersion: 14,
patchVersion: 0,
},
});
const wallet = new KeepKeyHDWallet(transport);

const features = await wallet.initialize();
expect(features).toBeDefined();
expect(features.majorVersion).toBe(7);
expect(features.minorVersion).toBe(14);
expect(features.patchVersion).toBe(0);
});

it("regression: never produces 'Invalid Version: vundefined.undefined.undefined'", async () => {
const transport = makeMockTransport({
message: { deviceId: "mock-device-id" },
});
const wallet = new KeepKeyHDWallet(transport);
let err: any;
try {
await wallet.initialize();
} catch (e) {
err = e;
}
expect(err).toBeDefined();
expect(String(err.message ?? err)).not.toMatch(

Check failure on line 85 in packages/hdwallet-keepkey/src/keepkey-initialize.test.ts

View workflow job for this annotation

GitHub Actions / build (18)

Replace `⏎······/Invalid·Version:·vundefined\.undefined\.undefined/⏎····` with `/Invalid·Version:·vundefined\.undefined\.undefined/`
/Invalid Version: vundefined\.undefined\.undefined/
);
});
});
18 changes: 18 additions & 0 deletions packages/hdwallet-keepkey/src/keepkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,24 @@
this.transport.keyring.addAlias(transportDeviceID, out.deviceId);
}

// Validate version fields BEFORE constructing the semver string. Without this,
// a Features response missing major/minor/patch (e.g. wrong message type cast,
// device in an unexpected state, or runtime decode mismatch) produces
// `vundefined.undefined.undefined`, which semver.gte() rejects with the opaque
// error "Invalid Version: vundefined.undefined.undefined". Surface a clear
// diagnostic instead so callers can recognize the failure mode.
if (
typeof out.majorVersion !== "number" ||
typeof out.minorVersion !== "number" ||
typeof out.patchVersion !== "number"
) {
throw new Error(
`KeepKey Initialize returned Features without firmware version ` +
`(major=${out.majorVersion}, minor=${out.minorVersion}, patch=${out.patchVersion}). ` +

Check failure on line 1203 in packages/hdwallet-keepkey/src/keepkey.ts

View workflow job for this annotation

GitHub Actions / build (18)

Insert `··`
`Device may be in bootloader mode, mid-update, or returned an unexpected message type.`

Check failure on line 1204 in packages/hdwallet-keepkey/src/keepkey.ts

View workflow job for this annotation

GitHub Actions / build (18)

Insert `··`
);
}

const fwVersion = `v${out.majorVersion}.${out.minorVersion}.${out.patchVersion}`;
//Lost Support per proto 44.3
this._supportsOsmosis = semver.gte(fwVersion, "v7.7.0");
Expand Down
Loading