diff --git a/main/api/ledger/utils/ledger.ts b/main/api/ledger/utils/ledger.ts index b8ebe2af..2621b200 100644 --- a/main/api/ledger/utils/ledger.ts +++ b/main/api/ledger/utils/ledger.ts @@ -9,6 +9,7 @@ import IronfishApp, { ResponseViewKey, ResponseProofGenKey, ResponseSign, + KeyResponse, } from "@zondax/ledger-ironfish"; import { z } from "zod"; @@ -34,6 +35,11 @@ const ERROR_TYPES = [ "CANNOT_OPEN_DEVICE", ]; +type ResponseError = { + returnCode: number; + errorMessage: string; +}; + type Transport = Awaited>; const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -138,11 +144,16 @@ class LedgerManager { const app = new IronfishApp(transport); let appInfo: Awaited> | null = null; let retries = 3; + let error: string = "UNKNOWN_ERROR"; + + while ( + retries > 0 && + (appInfo === null || appInfo?.appName !== IRONFISH_APP_NAME) + ) { + retries--; - while (retries > 0 && appInfo?.appName !== IRONFISH_APP_NAME) { try { appInfo = await app.appInfo(); - if (appInfo.appName !== IRONFISH_APP_NAME) { logger.debug( `Unable to open Ironfish app. App info:\n\n${JSON.stringify( @@ -155,28 +166,34 @@ class LedgerManager { throw new Error(`Invalid app name: ${appInfo.appName}`); } } catch (err) { - retries -= 1; + if (isResponseError(err)) { + logger.debug( + `Ledger ResponseError returnCode: ${err.returnCode.toString(16)}`, + ); + if (err.returnCode === LedgerDeviceLockedError.returnCode) { + error = "LEDGER_LOCKED"; + } else { + error = "IRONFISH_NOT_OPEN"; + } + } + throw err; } - } - if (app && appInfo?.appName === IRONFISH_APP_NAME) { - return { - status: "SUCCESS", - data: app, - error: null, - }; + if (app && appInfo?.appName === IRONFISH_APP_NAME) { + return { + status: "SUCCESS", + data: app, + error: null, + }; + } } await this.disconnect(); - return { status: "ERROR", data: null, error: { - type: - appInfo?.returnCode === 0x5515 - ? "LEDGER_LOCKED" - : "IRONFISH_NOT_OPEN", + type: error, message: `Failed to open Ironfish app. Latest app name: ${ appInfo?.appName || "Unknown" }`, @@ -220,15 +237,19 @@ class LedgerManager { ironfishAppReponse.error?.type !== "LEDGER_LOCKED"; returnStatus.isIronfishAppOpen = ironfishAppReponse.status === "SUCCESS"; - returnStatus.deviceName = transport.deviceModel?.productName ?? ""; + returnStatus.deviceName = + transport.deviceModel?.productName ?? "Ledger"; if (ironfishAppReponse.status === "SUCCESS") { - const keyResponse: ResponseAddress = - await ironfishAppReponse.data.retrieveKeys( - DERIVATION_PATH, - IronfishKeys.PublicAddress, - false, - ); + const keyResponse = await ironfishAppReponse.data.retrieveKeys( + DERIVATION_PATH, + IronfishKeys.PublicAddress, + false, + ); + + if (!isResponseAddress(keyResponse)) { + throw new Error("No public address returned"); + } returnStatus.publicAddress = keyResponse.publicAddress ? keyResponse.publicAddress.toString("hex") : ""; @@ -280,44 +301,37 @@ class LedgerManager { throw new Error(ironfishAppReponse.error.message); } - const publicAddressResponse: ResponseAddress = + const publicAddressResponse = await ironfishAppReponse.data.retrieveKeys( DERIVATION_PATH, IronfishKeys.PublicAddress, false, ); - if (!publicAddressResponse.publicAddress) { + if (!isResponseAddress(publicAddressResponse)) { throw new Error("No public address returned"); } - const viewKeyResponse: ResponseViewKey = - await ironfishAppReponse.data.retrieveKeys( - DERIVATION_PATH, - IronfishKeys.ViewKey, - true, - ); + const viewKeyResponse = await ironfishAppReponse.data.retrieveKeys( + DERIVATION_PATH, + IronfishKeys.ViewKey, + true, + ); - if ( - !viewKeyResponse.viewKey || - !viewKeyResponse.ovk || - !viewKeyResponse.ivk - ) { - logger.debug(`No view key returned`); - logger.debug(viewKeyResponse.returnCode.toString()); - throw new Error(viewKeyResponse.errorMessage); + if (!isResponseViewKey(viewKeyResponse)) { + throw new Error(`No view key returned`); } - const responsePGK: ResponseProofGenKey = - await ironfishAppReponse.data.retrieveKeys( - DERIVATION_PATH, - IronfishKeys.ProofGenerationKey, - false, - ); + const responsePGK = await ironfishAppReponse.data.retrieveKeys( + DERIVATION_PATH, + IronfishKeys.ProofGenerationKey, + false, + ); - if (!responsePGK.ak || !responsePGK.nsk) { - logger.debug(`No proof authorizing key returned`); - throw new Error(responsePGK.errorMessage); + if (!isResponseProofGenKey(responsePGK)) { + const error = `No proof authorizing key returned`; + logger.debug(error); + throw new Error(error); } const accountName = transport.deviceModel?.productName ?? "Ledger"; @@ -420,7 +434,7 @@ class LedgerManager { } if (!signResponse.signature) { - throw new Error(signResponse.errorMessage || "No signature returned"); + throw new Error("No signature returned"); } const ironfish = await manager.getIronfish(); @@ -454,4 +468,36 @@ class LedgerManager { } } +function isResponseAddress(response: KeyResponse): response is ResponseAddress { + return "publicAddress" in response; +} + +function isResponseViewKey(response: KeyResponse): response is ResponseViewKey { + return "viewKey" in response; +} + +function isResponseProofGenKey( + response: KeyResponse, +): response is ResponseProofGenKey { + return "ak" in response && "nsk" in response; +} + +function isResponseError(error: unknown): error is ResponseError { + return ( + "errorMessage" in (error as object) && "returnCode" in (error as object) + ); +} + +export class LedgerError extends Error { + name = this.constructor.name; +} + +export class LedgerDeviceLockedError extends LedgerError { + static returnCode = 0x5515; +} + +export class LedgerAppNotOpenError extends LedgerError { + static returnCode = 0x6f00; +} + export const ledgerManager = new LedgerManager(); diff --git a/package-lock.json b/package-lock.json index 7bd9bf69..b5fe6ddc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,7 @@ "@types/uuid": "^9.0.5", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", - "@zondax/ledger-ironfish": "^0.1.2", + "@zondax/ledger-ironfish": "^0.5.1", "axios": "0.21.4", "babel-plugin-formatjs": "^10.5.10", "compare-versions": "6.1.0", @@ -4771,29 +4771,29 @@ } }, "node_modules/@ledgerhq/devices": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.4.0.tgz", - "integrity": "sha512-TUrMlWZJ+5AFp2lWMw4rGQoU+WtjIqlFX5SzQDL9phaUHrt4TFierAGHsaj5+tUHudhD4JhIaLI2cn1NOyq5NQ==", + "version": "8.4.4", + "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.4.4.tgz", + "integrity": "sha512-sz/ryhe/R687RHtevIE9RlKaV8kkKykUV4k29e7GAVwzHX1gqG+O75cu1NCJUHLbp3eABV5FdvZejqRUlLis9A==", "dependencies": { - "@ledgerhq/errors": "^6.17.0", + "@ledgerhq/errors": "^6.19.1", "@ledgerhq/logs": "^6.12.0", "rxjs": "^7.8.1", "semver": "^7.3.5" } }, "node_modules/@ledgerhq/errors": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.17.0.tgz", - "integrity": "sha512-xnOVpy/gUUkusEORdr2Qhw3Vd0MGfjyVGgkGR9Ck6FXE26OIdIQ3tNmG5BdZN+gwMMFJJVxxS4/hr0taQfZ43w==" + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.19.1.tgz", + "integrity": "sha512-75yK7Nnit/Gp7gdrJAz0ipp31CCgncRp+evWt6QawQEtQKYEDfGo10QywgrrBBixeRxwnMy1DP6g2oCWRf1bjw==" }, "node_modules/@ledgerhq/hw-transport": { - "version": "6.28.1", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.28.1.tgz", - "integrity": "sha512-RaZe+abn0zBIz82cE9tp7Y7aZkHWWbEaE2yJpfxT8AhFz3fx+BU0kLYzuRN9fmA7vKueNJ1MTVUCY+Ex9/CHSQ==", - "dev": true, + "version": "6.31.2", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.31.2.tgz", + "integrity": "sha512-B27UIzMzm2IXPGYnEB95R7eHxpXBkTBHh6MUJJQZVknt8LilEz1tfpTYUdzAKDGQ+Z5MZyYb01Eh3Zqm3kn3uw==", "dependencies": { - "@ledgerhq/devices": "^8.0.0", - "@ledgerhq/errors": "^6.12.3", + "@ledgerhq/devices": "^8.4.2", + "@ledgerhq/errors": "^6.18.0", + "@ledgerhq/logs": "^6.12.0", "events": "^3.3.0" } }, @@ -4824,28 +4824,6 @@ "node-hid": "2.1.2" } }, - "node_modules/@ledgerhq/hw-transport-node-hid-noevents/node_modules/@ledgerhq/hw-transport": { - "version": "6.31.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.31.0.tgz", - "integrity": "sha512-BY1poLk8vlJdIYngp8Zfaa/V9n14dqgt1G7iNetVRhJVFEKp9EYONeC3x6q/N7x81LUpzBk6M+T+s46Z4UiXHw==", - "dependencies": { - "@ledgerhq/devices": "^8.4.0", - "@ledgerhq/errors": "^6.17.0", - "@ledgerhq/logs": "^6.12.0", - "events": "^3.3.0" - } - }, - "node_modules/@ledgerhq/hw-transport-node-hid/node_modules/@ledgerhq/hw-transport": { - "version": "6.31.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.31.0.tgz", - "integrity": "sha512-BY1poLk8vlJdIYngp8Zfaa/V9n14dqgt1G7iNetVRhJVFEKp9EYONeC3x6q/N7x81LUpzBk6M+T+s46Z4UiXHw==", - "dependencies": { - "@ledgerhq/devices": "^8.4.0", - "@ledgerhq/errors": "^6.17.0", - "@ledgerhq/logs": "^6.12.0", - "events": "^3.3.0" - } - }, "node_modules/@ledgerhq/logs": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-6.12.0.tgz", @@ -7067,21 +7045,21 @@ } }, "node_modules/@zondax/ledger-ironfish": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@zondax/ledger-ironfish/-/ledger-ironfish-0.1.2.tgz", - "integrity": "sha512-a9qnSOHxAf76pMonJBy5jI9oauR2W7WpVu/cCBs151uEW78NeSu4IMHOLGCo8KNiTPzpGwGa/7+1bpzxlQiEng==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@zondax/ledger-ironfish/-/ledger-ironfish-0.5.1.tgz", + "integrity": "sha512-yzoyejbz5kRFSD3D3u2pIkEOwmAWWKBXiVr7ssb5TRXdimLUTHFgST7CIMp1iqRrkw8bw6HcM7RKcGol5bd9xQ==", "dev": true, "dependencies": { - "@zondax/ledger-js": "^0.2.1" + "@zondax/ledger-js": "^1.0.1" } }, "node_modules/@zondax/ledger-js": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@zondax/ledger-js/-/ledger-js-0.2.2.tgz", - "integrity": "sha512-7wOUlRF2+kRaRU2KSzKb7XjPfScwEg3Cjg6NH/p+ikQLJ9eMkGC45NhSxYn8lixIIk+TgZ4yzTNOzFvF836gQw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@zondax/ledger-js/-/ledger-js-1.0.1.tgz", + "integrity": "sha512-9h+aIXyEK+Rdic5Ppsmq+tptDFwPTacG1H6tpZHFdhtBFHYFOLLkKTTmq5rMTv84aAPS1v0tnsF1e2Il6M05Cg==", "dev": true, "dependencies": { - "@ledgerhq/hw-transport": "6.28.1" + "@ledgerhq/hw-transport": "6.31.2" } }, "node_modules/7zip-bin": { diff --git a/package.json b/package.json index a9ec44f5..db4d2e2b 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "@types/uuid": "^9.0.5", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", - "@zondax/ledger-ironfish": "^0.1.2", + "@zondax/ledger-ironfish": "^0.5.1", "axios": "0.21.4", "babel-plugin-formatjs": "^10.5.10", "compare-versions": "6.1.0",