├── misc ├── blupdater.bin └── firmware_v6.3.0.bin ├── src ├── global │ ├── device-policy-enum.ts │ ├── language.ts │ ├── firmware-file-metadata.d.ts │ ├── buffer-helper.ts │ ├── message-handler.d.ts │ ├── coin-name.ts │ ├── features.ts │ └── coin-type.ts ├── hid-helper.ts ├── actions │ ├── cancel-action.ts │ ├── action-helper.ts │ ├── wipe-device-action.ts │ ├── end-session-action.ts │ ├── passphrase-ack-action.ts │ ├── initialize-action.ts │ ├── get-ethereum-address-action.ts │ ├── pin-matrix-ack-action.ts │ ├── word-ack-action.ts │ ├── get-public-key-action.ts │ ├── get-address-action.ts │ ├── cipher-node-vector-action.ts │ ├── cipher-account-name-action.ts │ ├── character-ack-action.ts │ ├── change-pin-action.ts │ ├── apply-policy-action.ts │ ├── apply-settings-action.ts │ ├── sign-message-action.ts │ ├── cipher-key-value-action.ts │ ├── encrypt-message-action.ts │ ├── reset-device-action.ts │ ├── recover-device-action.ts │ └── firmware-upload-action.ts ├── button-request-message-handler.ts ├── entropy-request-message-handler.ts ├── device-message-states.ts ├── purpose-code-helper.ts ├── device-client-methods.ts ├── device-message-helper.ts ├── node-vector.ts ├── device-client-manager.ts ├── transport.ts ├── features-service.ts ├── stateful-device-messenger.ts ├── device-client.ts └── message-states.ts ├── .gitignore ├── device-profiles ├── keepkey-0.x.x.hjson ├── keepkey-5.mfg.hjson ├── keepkey-5.2.1.hjson ├── trezor-bootloader.hjson ├── keepkey-1.x.x.hjson ├── keepkey-2.x.x.hjson ├── trezor-1.4.x.hjson ├── keepkey-5.x.x.hjson ├── trezor-1.3.5.hjson ├── trezor-1.3.6.hjson ├── keepkey-3.x.x.hjson ├── keepkey-4.x.x.hjson ├── keepkey-1.1.0.hjson ├── keepkey-5.0.0.hjson ├── keepkey-5.0.1.hjson ├── keepkey-5.1.0.hjson ├── keepkey-5.1.1.hjson ├── keepkey-5.1.2.hjson ├── keepkey-5.2.2.hjson ├── keepkey-5.2.4.hjson ├── keepkey-5.3.0.hjson ├── keepkey-5.4.1.hjson ├── keepkey-5.4.2.hjson ├── keepkey-5.5.0.hjson ├── keepkey-5.6.1.hjson ├── keepkey-5.7.0.hjson ├── keepkey-5.8.0.hjson ├── keepkey-5.8.1.hjson ├── keepkey-5.9.0.hjson ├── keepkey-5.9.1.hjson ├── keepkey-6.0.0.hjson ├── keepkey-6.0.1.hjson ├── keepkey-6.0.2.hjson ├── keepkey-6.0.4.hjson ├── keepkey-5.10.0.hjson ├── keepkey-5.10.1.hjson ├── keepkey-5.10.2.hjson ├── keepkey-bootloader-1.1.0.hjson ├── keepkey-bootloader-2.0.0.hjson ├── keepkey-6.1.0.hjson ├── keepkey-6.1.1.hjson ├── keepkey-6.2.0.hjson ├── keepkey-6.2.1.hjson ├── keepkey-6.2.2.hjson ├── keepkey-6.3.0.hjson ├── keepkey-bootloader-1.0.0.hjson ├── keepkey-bootloader-1.0.1.hjson ├── keepkey-bootloader-1.0.2.hjson ├── keepkey-bootloader-1.0.4.hjson └── keepkey-bootloader-1.0.3.hjson ├── README.md ├── tsconfig.json ├── LICENSE.md ├── package.json └── gulpfile.js /misc/blupdater.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keepkey/device-client/HEAD/misc/blupdater.bin -------------------------------------------------------------------------------- /src/global/device-policy-enum.ts: -------------------------------------------------------------------------------- 1 | export enum DevicePolicyEnum { 2 | ShapeShift 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | src/*.js 3 | node_modules 4 | dist 5 | .DS_Store 6 | misc/.DS_Store 7 | npm-debug.log -------------------------------------------------------------------------------- /misc/firmware_v6.3.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keepkey/device-client/HEAD/misc/firmware_v6.3.0.bin -------------------------------------------------------------------------------- /device-profiles/keepkey-0.x.x.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | major_version: 0 4 | } 5 | capabilities: { 6 | prereleaseDevice: true 7 | } 8 | } -------------------------------------------------------------------------------- /src/hid-helper.ts: -------------------------------------------------------------------------------- 1 | import {DeviceClient} from "./device-client"; 2 | 3 | export interface HidHelper { 4 | getActiveClient(): Promise; 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @keepkey/device-client 2 | 3 | This package has been deprecated. Please use [@shapeshiftoss/hdwallet-*](https://github.com/shapeshift/hdwallet) for new projects! 4 | -------------------------------------------------------------------------------- /src/global/language.ts: -------------------------------------------------------------------------------- 1 | export enum Language { 2 | english, 3 | japanese, 4 | spanish, 5 | chinese_simplified, 6 | chinese_standard, 7 | french, 8 | italian 9 | } 10 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.mfg.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | bootloader_mode: null 6 | label: manufacturing firmware 7 | } 8 | capabilities: { 9 | vendorName: KeepKey 10 | newKeepKey: true 11 | } 12 | } -------------------------------------------------------------------------------- /src/global/firmware-file-metadata.d.ts: -------------------------------------------------------------------------------- 1 | interface FirmwareFileMetadata { 2 | file: string; 3 | digest: string; 4 | trezorDigest: string; 5 | size: number; 6 | timeStamp: Date; 7 | version: string; 8 | isBootloaderUpdater: boolean; 9 | modelNumber?: string; 10 | } 11 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.2.1.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 2 6 | patch_version: 1 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | upgradeSkipable: false 13 | } 14 | } -------------------------------------------------------------------------------- /device-profiles/trezor-bootloader.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: bitcointrezor.com 4 | bootloader_mode: true 5 | } 6 | capabilities: { 7 | vendorName: TREZOR 8 | firmwareImageAvailable: false 9 | defaultMnemonicSeedLength: 24 10 | supportsCipheredKeyRecovery: false 11 | supportsSecureAccountTransfer: false 12 | usesProtectCallButtonRequests: true 13 | } 14 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-1.x.x.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 1 5 | bootloader_mode: null 6 | } 7 | capabilities: { 8 | vendorName: KeepKey 9 | firmwareImageAvailable: true 10 | defaultMnemonicSeedLength: 12 11 | supportsCipheredKeyRecovery: true 12 | supportsSecureAccountTransfer: false 13 | usesProtectCallButtonRequests: true 14 | upgradeSkipable: false 15 | } 16 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-2.x.x.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 2 5 | bootloader_mode: null 6 | } 7 | capabilities: { 8 | vendorName: KeepKey 9 | firmwareImageAvailable: true 10 | defaultMnemonicSeedLength: 12 11 | supportsCipheredKeyRecovery: true 12 | supportsSecureAccountTransfer: true 13 | usesProtectCallButtonRequests: false 14 | upgradeSkipable: false 15 | } 16 | } -------------------------------------------------------------------------------- /device-profiles/trezor-1.4.x.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: bitcointrezor.com 4 | major_version: 1 5 | minor_version: 4 6 | bootloader_mode: null 7 | } 8 | capabilities: { 9 | vendorName: TREZOR 10 | firmwareImageAvailable: false 11 | defaultMnemonicSeedLength: 24 12 | supportsCipheredKeyRecovery: false 13 | supportsSecureAccountTransfer: false 14 | usesProtectCallButtonRequests: true 15 | } 16 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-5.x.x.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | bootloader_mode: null 6 | } 7 | capabilities: { 8 | vendorName: KeepKey 9 | firmwareImageAvailable: true 10 | defaultMnemonicSeedLength: 12 11 | supportsCipheredKeyRecovery: true 12 | supportsSecureAccountTransfer: true 13 | usesProtectCallButtonRequests: false 14 | usesShapeshiftResponseV2: true 15 | } 16 | } -------------------------------------------------------------------------------- /device-profiles/trezor-1.3.5.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: bitcointrezor.com 4 | major_version: 1 5 | minor_version: 3 6 | patch_version: 5 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: TREZOR 11 | firmwareImageAvailable: false 12 | defaultMnemonicSeedLength: 24 13 | supportsCipheredKeyRecovery: false 14 | supportsSecureAccountTransfer: false 15 | usesProtectCallButtonRequests: true 16 | } 17 | } -------------------------------------------------------------------------------- /device-profiles/trezor-1.3.6.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: bitcointrezor.com 4 | major_version: 1 5 | minor_version: 3 6 | patch_version: 6 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: TREZOR 11 | firmwareImageAvailable: false 12 | defaultMnemonicSeedLength: 24 13 | supportsCipheredKeyRecovery: false 14 | supportsSecureAccountTransfer: false 15 | usesProtectCallButtonRequests: true 16 | } 17 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-3.x.x.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 3 5 | bootloader_mode: null 6 | } 7 | capabilities: { 8 | vendorName: KeepKey 9 | firmwareImageAvailable: true 10 | defaultMnemonicSeedLength: 12 11 | supportsCipheredKeyRecovery: true 12 | supportsSecureAccountTransfer: true 13 | usesProtectCallButtonRequests: false 14 | usesShapeshiftResponseV2: true 15 | upgradeSkipable: false 16 | } 17 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-4.x.x.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 4 5 | bootloader_mode: null 6 | } 7 | capabilities: { 8 | vendorName: KeepKey 9 | firmwareImageAvailable: true 10 | defaultMnemonicSeedLength: 12 11 | supportsCipheredKeyRecovery: true 12 | supportsSecureAccountTransfer: true 13 | usesProtectCallButtonRequests: false 14 | usesShapeshiftResponseV2: true 15 | upgradeSkipable: false 16 | } 17 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-1.1.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 1 5 | minor_version: 1 6 | patch_version: 0 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: true 16 | upgradeSkipable: false 17 | } 18 | } -------------------------------------------------------------------------------- /src/actions/cancel-action.ts: -------------------------------------------------------------------------------- 1 | import {BasicClient} from "../device-client"; 2 | import {DeviceMessageHelper} from "../device-message-helper"; 3 | import {Features} from "../global/features"; 4 | 5 | export class CancelAction { 6 | public static operation(client: BasicClient) { 7 | return client.featuresService.promise 8 | .then((features: Features) => { 9 | var message = DeviceMessageHelper.factory('Cancel'); 10 | return client.writeToDevice(message); 11 | }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/actions/action-helper.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | export class ActionHelper { 4 | public static ignoreCancelledAction(message: string | any) { 5 | if (!ActionHelper.isCancelledMessage(message)) { 6 | return Promise.reject(message); 7 | } 8 | } 9 | 10 | public static isCancelledMessage(message: string | any): boolean { 11 | return (_.isString(message) && message.endsWith('cancelled')) || 12 | (_.isString(message.code) && message.code.endsWith('Cancelled')); 13 | } 14 | } -------------------------------------------------------------------------------- /src/actions/wipe-device-action.ts: -------------------------------------------------------------------------------- 1 | import {BasicClient} from "../device-client"; 2 | import WipeDevice = DeviceMessages.WipeDevice; 3 | import {DeviceMessageHelper} from "../device-message-helper"; 4 | 5 | export class WipeDeviceAction { 6 | public static operation(client: BasicClient): Promise { 7 | var message: WipeDevice = DeviceMessageHelper.factory('WipeDevice'); 8 | return client.writeToDevice(message) 9 | .then((response) => { 10 | client.featuresService.clear(); 11 | }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.0.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 0 6 | patch_version: 0 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | } 19 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-5.0.1.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 0 6 | patch_version: 1 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | } 19 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-5.1.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 1 6 | patch_version: 0 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | } 19 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-5.1.1.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 1 6 | patch_version: 1 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | } 19 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-5.1.2.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 1 6 | patch_version: 2 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | } 19 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-5.2.2.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 2 6 | patch_version: 2 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | } 19 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-5.2.4.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 2 6 | patch_version: 4 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | } 19 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-5.3.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 3 6 | patch_version: 0 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | } 19 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-5.4.1.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 4 6 | patch_version: 1 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.4.2.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 4 6 | patch_version: 2 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.5.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 5 6 | patch_version: 0 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.6.1.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 6 6 | patch_version: 1 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/global/buffer-helper.ts: -------------------------------------------------------------------------------- 1 | import ByteBuffer = require('bytebuffer'); 2 | 3 | export class BufferHelper { 4 | public static pad(buffer: ByteBuffer, padSize: number) { 5 | var bufferLength = buffer.limit - buffer.offset; 6 | if (bufferLength % padSize) { 7 | var newBufferLength = Math.ceil(bufferLength / padSize) * padSize; 8 | var padded = ByteBuffer.allocate(newBufferLength).fill(0).reset(); 9 | buffer.copyTo(padded); 10 | return padded.reset(); 11 | } else { 12 | return buffer; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/button-request-message-handler.ts: -------------------------------------------------------------------------------- 1 | import {MessageHandler} from "./global/message-handler"; 2 | import {DeviceMessageHelper} from "./device-message-helper"; 3 | import ButtonRequest = DeviceMessages.ButtonRequest; 4 | import ButtonAck = DeviceMessages.ButtonAck; 5 | 6 | export class ButtonRequestMessageHandler implements MessageHandler { 7 | public static messageNames = ['ButtonRequest']; 8 | 9 | public messageHandler(request: ButtonRequest): ButtonAck { 10 | return DeviceMessageHelper.factory('ButtonAck'); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.7.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 7 6 | patch_version: 0 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | hasPagedCoinTable: true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.8.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 8 6 | patch_version: 0 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | hasPagedCoinTable: true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.8.1.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 8 6 | patch_version: 1 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | hasPagedCoinTable: true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.9.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 9 6 | patch_version: 0 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: true 18 | hasPagedCoinTable: true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.9.1.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 9 6 | patch_version: 1 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | hasPagedCoinTable: true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /device-profiles/keepkey-6.0.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 6 5 | minor_version: 0 6 | patch_version: 0 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: true 18 | hasPagedCoinTable: true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /device-profiles/keepkey-6.0.1.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 6 5 | minor_version: 0 6 | patch_version: 1 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: true 18 | hasPagedCoinTable: true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /device-profiles/keepkey-6.0.2.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 6 5 | minor_version: 0 6 | patch_version: 2 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: true 18 | hasPagedCoinTable: true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /device-profiles/keepkey-6.0.4.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 6 5 | minor_version: 0 6 | patch_version: 4 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: true 18 | hasPagedCoinTable: true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.10.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 10 6 | patch_version: 0 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: false 18 | hasPagedCoinTable: true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.10.1.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 10 6 | patch_version: 1 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: true 18 | hasPagedCoinTable: true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /device-profiles/keepkey-5.10.2.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 5 5 | minor_version: 10 6 | patch_version: 2 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: true 18 | hasPagedCoinTable: true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /device-profiles/keepkey-bootloader-1.1.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | bootloader_mode: true 5 | major_version: 1 6 | minor_version: 1 7 | patch_version: 0 8 | } 9 | hashes: [ 10 | { 11 | hash: e45f587fb07533d832548402d0e71d8e8234881da54d86c4b699c28a6482b0ee 12 | upgradable: false 13 | upgradeSkipable: true 14 | tag: v1.1.0 15 | } 16 | ] 17 | capabilities: { 18 | vendorName: KeepKey 19 | firmwareImageAvailable: true 20 | bootloaderUpgradable: false 21 | bootloaderUpgradeSkipable: true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /device-profiles/keepkey-bootloader-2.0.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | bootloader_mode: true 5 | major_version: 2 6 | minor_version: 0 7 | patch_version: 0 8 | } 9 | hashes: [ 10 | { 11 | hash: 9bf1580d1b21250f922b68794cdadd6c8e166ae5b15ce160a42f8c44a2f05936 12 | upgradable: false 13 | upgradeSkipable: true 14 | tag: v2.0.0 15 | } 16 | ] 17 | capabilities: { 18 | vendorName: KeepKey 19 | firmwareImageAvailable: true 20 | bootloaderUpgradable: false 21 | bootloaderUpgradeSkipable: true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/actions/end-session-action.ts: -------------------------------------------------------------------------------- 1 | import {BasicClient} from "../device-client"; 2 | import ClearSession = DeviceMessages.ClearSession; 3 | import {DeviceMessageHelper} from "../device-message-helper"; 4 | import {Features} from "../global/features"; 5 | 6 | export class EndSessionAction { 7 | public static operation(client: BasicClient): Promise { 8 | return client.featuresService.promise 9 | .then((features: Features) => { 10 | var message: ClearSession = DeviceMessageHelper.factory('ClearSession'); 11 | return client.writeToDevice(message); 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /device-profiles/keepkey-6.1.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 6 5 | minor_version: 1 6 | patch_version: 0 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: true 18 | hasPagedCoinTable: true 19 | supportsRecoveryDryRun: true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /device-profiles/keepkey-6.1.1.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 6 5 | minor_version: 1 6 | patch_version: 1 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: true 18 | hasPagedCoinTable: true 19 | supportsRecoveryDryRun: true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /device-profiles/keepkey-6.2.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 6 5 | minor_version: 2 6 | patch_version: 0 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: true 18 | hasPagedCoinTable: true 19 | supportsRecoveryDryRun: true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /device-profiles/keepkey-6.2.1.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 6 5 | minor_version: 2 6 | patch_version: 1 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: true 18 | hasPagedCoinTable: true 19 | supportsRecoveryDryRun: true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /device-profiles/keepkey-6.2.2.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 6 5 | minor_version: 2 6 | patch_version: 2 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: true 18 | hasPagedCoinTable: true 19 | supportsRecoveryDryRun: true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /device-profiles/keepkey-6.3.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | major_version: 6 5 | minor_version: 3 6 | patch_version: 0 7 | bootloader_mode: null 8 | } 9 | capabilities: { 10 | vendorName: KeepKey 11 | firmwareImageAvailable: true 12 | defaultMnemonicSeedLength: 12 13 | supportsCipheredKeyRecovery: true 14 | supportsSecureAccountTransfer: true 15 | usesProtectCallButtonRequests: false 16 | usesShapeshiftResponseV2: true 17 | upgradeSkipable: true 18 | hasPagedCoinTable: true 19 | supportsRecoveryDryRun: true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/actions/passphrase-ack-action.ts: -------------------------------------------------------------------------------- 1 | import {BasicClient} from "../device-client"; 2 | import PassphraseAck = DeviceMessages.PassphraseAck; 3 | import {DeviceMessageHelper} from "../device-message-helper"; 4 | 5 | export class PassphraseAckAction { 6 | public static operation(client: BasicClient, passphrase: string): Promise { 7 | var message: PassphraseAck = DeviceMessageHelper.factory('PassphraseAck'); 8 | message.setPassphrase(passphrase); 9 | 10 | return client.writeToDevice(message) 11 | .catch(() => { 12 | console.log('failure while sending passphrase'); 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.6", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "lib": [ 6 | "dom", 7 | "es5", 8 | "es2015.core", 9 | "es2015.promise", 10 | "es2015.symbol", 11 | "scripthost" 12 | ], 13 | "module": "CommonJS", 14 | "declaration": true, 15 | "declarationDir": "./dist", 16 | "removeComments": true, 17 | "traceResolution": true, 18 | "allowJs": false, 19 | "outDir": "./dist", 20 | "types": [ 21 | "bytebuffer" 22 | ] 23 | }, 24 | "include": [ 25 | "dist/messages.d.ts", 26 | "src/global/*.d.ts", 27 | "src/device-client-manager.ts" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /src/actions/initialize-action.ts: -------------------------------------------------------------------------------- 1 | import {BasicClient} from "../device-client"; 2 | import Initialize = DeviceMessages.Initialize; 3 | import {DeviceMessageHelper} from "../device-message-helper"; 4 | import {IFeatures} from "../global/features"; 5 | 6 | export class InitializeAction { 7 | public static operation(client: BasicClient, skipBootloaderHashCheck?: boolean): Promise { 8 | var initialize: Initialize = DeviceMessageHelper.factory('Initialize'); 9 | 10 | return client.writeToDevice(initialize) 11 | .then((features: IFeatures) => { 12 | return client.featuresService.setValue(features, client, !!skipBootloaderHashCheck); 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /device-profiles/keepkey-bootloader-1.0.0.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | bootloader_mode: true 5 | major_version: 1 6 | minor_version: 0 7 | patch_version: 0 8 | } 9 | hashes: [ 10 | { 11 | hash: 6397c446f6b9002a8b150bf4b9b4e0bb66800ed099b881ca49700139b0559f10 12 | upgradable: true 13 | upgradeSkipable: false 14 | tag: v1.0.0 15 | } 16 | { 17 | hash: f13ce228c0bb2bdbc56bdcb5f4569367f8e3011074ccc63331348deb498f2d8f 18 | upgradable: true 19 | upgradeSkipable: false 20 | tag: v1.0.0-patched 21 | } 22 | ] 23 | capabilities: { 24 | model: K1-14AM 25 | vendorName: KeepKey 26 | firmwareImageAvailable: true 27 | bootloaderUpgradable: true 28 | bootloaderUpgradeSkipable: false 29 | } 30 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-bootloader-1.0.1.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | bootloader_mode: true 5 | major_version: 1 6 | minor_version: 0 7 | patch_version: 1 8 | } 9 | hashes: [ 10 | { 11 | hash: d544b5e06b0c355d68b868ac7580e9bab2d224a1e2440881cc1bca2b816752d5 12 | upgradable: true 13 | upgradeSkipable: false 14 | tag: v1.0.1 15 | } 16 | { 17 | hash: ec618836f86423dbd3114c37d6e3e4ffdfb87d9e4c6199cf3e163a67b27498a2 18 | upgradable: true 19 | upgradeSkipable: false 20 | tag: v1.0.1-patched 21 | } 22 | ] 23 | capabilities: { 24 | model: K1-14AM 25 | vendorName: KeepKey 26 | firmwareImageAvailable: true 27 | bootloaderUpgradable: true 28 | bootloaderUpgradeSkipable: false 29 | } 30 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-bootloader-1.0.2.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | bootloader_mode: true 5 | major_version: 1 6 | minor_version: 0 7 | patch_version: 2 8 | } 9 | hashes: [ 10 | { 11 | hash: cd702b91028a2cfa55af43d3407ba0f6f752a4a2be0583a172983b303ab1032e 12 | upgradable: true 13 | upgradeSkipable: false 14 | tag: v1.0.2 15 | } 16 | { 17 | hash: bcafb38cd0fbd6e2bdbea89fb90235559fdda360765b74e4a8758b4eff2d4921 18 | upgradable: true 19 | upgradeSkipable: false 20 | tag: v1.0.2-patched 21 | } 22 | ] 23 | capabilities: { 24 | model: K1-14AM 25 | vendorName: KeepKey 26 | firmwareImageAvailable: true 27 | bootloaderUpgradable: true 28 | bootloaderUpgradeSkipable: false 29 | } 30 | } -------------------------------------------------------------------------------- /device-profiles/keepkey-bootloader-1.0.4.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | bootloader_mode: true 5 | major_version: 1 6 | minor_version: 0 7 | patch_version: 4 8 | } 9 | hashes: [ 10 | { 11 | hash: 770b30aaa0be884ee8621859f5d055437f894a5c9c7ca22635e7024e059857b7 12 | upgradable: true 13 | upgradeSkipable: false 14 | tag: v1.0.4 15 | } 16 | { 17 | hash: fc4e5c4dc2e5127b6814a3f69424c936f1dc241d1daf2c5a2d8f0728eb69d20d 18 | upgradable: true 19 | upgradeSkipable: false 20 | tag: v1.0.4-patched 21 | } 22 | ] 23 | capabilities: { 24 | model: K1-14WL-S 25 | vendorName: KeepKey 26 | firmwareImageAvailable: true 27 | bootloaderUpgradable: true 28 | bootloaderUpgradeSkipable: false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/entropy-request-message-handler.ts: -------------------------------------------------------------------------------- 1 | import EntropyRequest = DeviceMessages.EntropyRequest; 2 | import EntropyAck = DeviceMessages.EntropyAck; 3 | import {DeviceMessageHelper} from "./device-message-helper"; 4 | import ByteBuffer = require('bytebuffer'); 5 | import * as Bitcore from "bitcore-lib"; 6 | import {MessageHandler} from "./global/message-handler"; 7 | 8 | export class EntropyRequestMessageHandler implements MessageHandler { 9 | public static messageNames = ['EntropyRequest']; 10 | 11 | public messageHandler(request: EntropyRequest): EntropyAck { 12 | var message: EntropyAck = DeviceMessageHelper.factory('EntropyAck'); 13 | let randomBytes = ByteBuffer.wrap(Bitcore.crypto.Random.getRandomBuffer(32)); 14 | message.setEntropy(randomBytes); 15 | return message; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/actions/get-ethereum-address-action.ts: -------------------------------------------------------------------------------- 1 | import {NodeVector} from "../node-vector"; 2 | import {DeviceMessageHelper} from "../device-message-helper"; 3 | import EthereumGetAddress = DeviceMessages.EthereumGetAddress; 4 | import EthereumAddress = DeviceMessages.EthereumAddress; 5 | import {ActionHelper} from "./action-helper"; 6 | 7 | export class GetEthereumAddressAction { 8 | public static operation(client, 9 | addressN: NodeVector, 10 | showDisplay: boolean): Promise { 11 | 12 | var message: EthereumGetAddress = DeviceMessageHelper.factory('EthereumGetAddress'); 13 | message.setAddressN(addressN.toArray()); 14 | message.setShowDisplay(showDisplay); 15 | 16 | return client.writeToDevice(message) 17 | .catch(ActionHelper.ignoreCancelledAction); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/global/message-handler.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import ProtoBufModel = DeviceMessages.ProtoBufModel; 4 | import * as ByteBuffer from "bytebuffer"; 5 | 6 | export interface ReflectableProtoBufModel extends ProtoBufModel { 7 | toArrayBuffer(): ArrayBuffer; 8 | toBuffer(): Buffer; 9 | encode(): ByteBuffer; 10 | toBase64(): string; 11 | toString(): string; 12 | $type: MessageTypeMetaData; 13 | } 14 | 15 | export interface MessageTypeMetaData { 16 | name: string; 17 | } 18 | 19 | export interface MessageHandler { 20 | messageHandler: (request: M) => R; 21 | } 22 | 23 | export interface MessageHandlerClass { 24 | new (...args: any[]): MessageHandler; 25 | messageNames: Array; 26 | } 27 | -------------------------------------------------------------------------------- /src/actions/pin-matrix-ack-action.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | import {BasicClient} from "../device-client"; 4 | import {DeviceMessageHelper} from "../device-message-helper"; 5 | import {Features} from "../global/features"; 6 | 7 | export interface PinMatrixAckOptions { 8 | pin?: string; 9 | } 10 | 11 | export class PinMatrixAckAction { 12 | private static DEFAULT_OPTIONS: PinMatrixAckOptions = { 13 | pin: '' 14 | }; 15 | 16 | public static operation(client: BasicClient, options: PinMatrixAckOptions) { 17 | var o: PinMatrixAckOptions = _.extend( 18 | {}, PinMatrixAckAction.DEFAULT_OPTIONS, options); 19 | 20 | return client.featuresService.promise 21 | .then((features: Features) => { 22 | var message = DeviceMessageHelper.factory('PinMatrixAck'); 23 | message.setPin(o.pin); 24 | 25 | return client.writeToDevice(message); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/actions/word-ack-action.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | import {BasicClient} from "../device-client"; 4 | import WordAck = DeviceMessages.WordAck; 5 | import {DeviceMessageHelper} from "../device-message-helper"; 6 | import {Features} from "../global/features"; 7 | 8 | export interface WordAckOptions { 9 | word?: string; 10 | } 11 | 12 | export class WordAckAction { 13 | private static DEFAULT_OPTIONS: WordAckOptions = { 14 | word: '' 15 | }; 16 | 17 | public static operation(client: BasicClient, options: WordAckOptions) { 18 | var o: WordAckOptions = _.extend( 19 | {}, WordAckAction.DEFAULT_OPTIONS, options); 20 | 21 | return client.featuresService.promise 22 | .then((features: Features) => { 23 | var message: WordAck = DeviceMessageHelper.factory('WordAck'); 24 | message.setWord(o.word); 25 | 26 | return client.writeToDevice(message); 27 | }); 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/actions/get-public-key-action.ts: -------------------------------------------------------------------------------- 1 | import {BasicClient} from "../device-client"; 2 | import {NodeVector} from "../node-vector"; 3 | import {DeviceMessageHelper} from "../device-message-helper"; 4 | import GetPublicKey = DeviceMessages.GetPublicKey; 5 | import {Features} from "../global/features"; 6 | 7 | export class GetPublicKeyAction { 8 | 9 | public static operation(client: BasicClient, addressN: NodeVector | string, 10 | showDisplay: boolean): Promise { 11 | return client.featuresService.promise 12 | .then((features: Features) => { 13 | if (features.initialized) { 14 | var message: GetPublicKey = DeviceMessageHelper.factory('GetPublicKey'); 15 | message.setAddressN(NodeVector.fromString(addressN).toArray()); 16 | message.setShowDisplay(showDisplay); 17 | 18 | return client.writeToDevice(message); 19 | } else { 20 | return Promise.reject('getPublicKey: device not initialized'); 21 | } 22 | }); 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/actions/get-address-action.ts: -------------------------------------------------------------------------------- 1 | import {NodeVector} from "../node-vector"; 2 | import {DeviceMessageHelper} from "../device-message-helper"; 3 | import GetAddress = DeviceMessages.GetAddress; 4 | import MultisigRedeemScriptType = DeviceMessages.MultisigRedeemScriptType; 5 | import {ActionHelper} from "./action-helper"; 6 | 7 | export class GetAddressAction { 8 | public static operation(client, 9 | addressN: NodeVector, 10 | coinName: string, 11 | showDisplay: boolean, 12 | multisig: MultisigRedeemScriptType): Promise { 13 | 14 | var message: GetAddress = DeviceMessageHelper.factory('GetAddress'); 15 | message.setAddressN(addressN.toArray()); 16 | message.setCoinName(coinName); 17 | message.setShowDisplay(showDisplay); 18 | if (multisig) { 19 | message.setMultisig(multisig); 20 | } 21 | 22 | return client.writeToDevice(message) 23 | .catch(ActionHelper.ignoreCancelledAction); 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/actions/cipher-node-vector-action.ts: -------------------------------------------------------------------------------- 1 | import * as ByteBuffer from'bytebuffer'; 2 | 3 | import {CipherKeyValueAction} from "./cipher-key-value-action"; 4 | import {DeviceClient} from "../device-client"; 5 | import {NodeVector} from "../node-vector"; 6 | import CipheredKeyValue = DeviceMessages.CipheredKeyValue; 7 | 8 | export const NODE_VECTOR_ENCYPTION_KEY = 'node-location'; 9 | const ROOT_NODE_VECTOR: NodeVector = new NodeVector([]); 10 | 11 | export class CipherNodeVectorAction { 12 | public static operation(client: DeviceClient, 13 | encrypt: boolean, 14 | nodeVector: NodeVector | string) { 15 | var value = encrypt ? NodeVector.fromString(nodeVector).toBuffer() 16 | : ByteBuffer.fromHex(nodeVector); 17 | return CipherKeyValueAction.operation(client, encrypt, ROOT_NODE_VECTOR, 18 | NODE_VECTOR_ENCYPTION_KEY, value, false, false) 19 | .then((response: CipheredKeyValue) => { 20 | return encrypt ? response.value.toHex() : NodeVector.fromBuffer(response.value); 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/actions/cipher-account-name-action.ts: -------------------------------------------------------------------------------- 1 | import ByteBuffer = require('bytebuffer'); 2 | 3 | import {CipherKeyValueAction} from "./cipher-key-value-action"; 4 | import {DeviceClient} from "../device-client"; 5 | import CipheredKeyValue = DeviceMessages.CipheredKeyValue; 6 | import {NodeVector} from "../node-vector"; 7 | 8 | const ACCOUNT_NAME_ENCYPTION_KEY = 'node-location'; 9 | 10 | export class CipherAccountNameAction { 11 | public static operation(client: DeviceClient, 12 | encrypt: boolean, 13 | nodePath: NodeVector, 14 | value: string): Promise { 15 | 16 | var valueBuffer = encrypt ? ByteBuffer.wrap(value) : ByteBuffer.fromHex(value); 17 | 18 | return CipherKeyValueAction.operation(client, encrypt, 19 | NodeVector.fromString(nodePath), ACCOUNT_NAME_ENCYPTION_KEY, 20 | valueBuffer, false, false) 21 | .then((response: CipheredKeyValue) => { 22 | return encrypt ? response.value.toHex() : response.value.toUTF8().replace(/\0/g, ''); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/actions/character-ack-action.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | import CharacterAck = DeviceMessages.CharacterAck; 4 | import {DeviceMessageHelper} from "../device-message-helper"; 5 | import {Features} from "../global/features"; 6 | 7 | export interface CharacterAckOptions { 8 | character?: string; 9 | delete?: boolean; 10 | done?: boolean; 11 | } 12 | 13 | export class CharacterAckAction { 14 | private static DEFAULT_OPTIONS: CharacterAckOptions = { 15 | character: '', 16 | delete: false, 17 | done: false 18 | }; 19 | 20 | public static operation(client, options: CharacterAckOptions) { 21 | var o: CharacterAckOptions = _.extend( 22 | {}, CharacterAckAction.DEFAULT_OPTIONS, options); 23 | 24 | return client.featuresService.promise 25 | .then((features: Features) => { 26 | var message: CharacterAck = DeviceMessageHelper.factory('CharacterAck'); 27 | message.setCharacter(o.character); 28 | message.setDelete(o.delete); 29 | message.setDone(o.done); 30 | 31 | return client.writeToDevice(message); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2017 KeepKey, LLC. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/actions/change-pin-action.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | import {BasicClient} from "../device-client"; 4 | import {DeviceMessageHelper} from "../device-message-helper"; 5 | import ChangePin = DeviceMessages.ChangePin; 6 | import {Features} from "../global/features"; 7 | import {ActionHelper} from "./action-helper"; 8 | 9 | export interface ChangePinOptions { 10 | remove?: boolean; 11 | } 12 | 13 | const DEFAULT_OPTIONS: ChangePinOptions = { 14 | remove: false 15 | }; 16 | 17 | export class ChangePinAction { 18 | public static operation(client: BasicClient, options: ChangePinOptions): Promise { 19 | var o: ChangePinOptions = _.extend( 20 | {}, DEFAULT_OPTIONS, options); 21 | 22 | return client.featuresService.promise 23 | .then((features: Features) => { 24 | if (!features.initialized) { 25 | return Promise.reject('Device not initialized'); 26 | } 27 | }) 28 | .then((features) => { 29 | var message: ChangePin = DeviceMessageHelper.factory('ChangePin'); 30 | message.setRemove(o.remove); 31 | 32 | return client.writeToDevice(message); 33 | }) 34 | .catch(ActionHelper.ignoreCancelledAction); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/actions/apply-policy-action.ts: -------------------------------------------------------------------------------- 1 | import {DeviceClient} from "../device-client"; 2 | import {DeviceMessageHelper} from "../device-message-helper"; 3 | import {DevicePolicyEnum} from "../global/device-policy-enum"; 4 | import {Features} from "../global/features"; 5 | import PolicyType = DeviceMessages.PolicyType; 6 | import ApplyPolicies = DeviceMessages.ApplyPolicies; 7 | 8 | export class ApplyPolicyAction { 9 | 10 | public static operation(client: DeviceClient, policyName: DevicePolicyEnum, 11 | enabled: boolean): Promise { 12 | return client.featuresService.promise 13 | .then((features: Features) => { 14 | if (features.initialized) { 15 | var policy: PolicyType = DeviceMessageHelper.factory('PolicyType'); 16 | policy.setPolicyName(DevicePolicyEnum[policyName]); 17 | policy.setEnabled(enabled); 18 | 19 | var message: ApplyPolicies = DeviceMessageHelper.factory('ApplyPolicies'); 20 | message.setPolicy([policy]); 21 | 22 | return client.writeToDevice(message); 23 | } else { 24 | return Promise.reject('ApplyPolicies: device not initialized'); 25 | } 26 | }) 27 | .then(() => { 28 | client.featuresService.clear(); 29 | return client.initialize(); 30 | }); 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/global/coin-name.ts: -------------------------------------------------------------------------------- 1 | export enum CoinName { 2 | // Standard chains 3 | Bitcoin = 0, 4 | Testnet = 1, 5 | Namecoin = 7, 6 | Litecoin = 2, 7 | Dogecoin = 3, 8 | Dash = 5, 9 | Ethereum = 6, 10 | BitcoinCash = 145, 11 | BitcoinGold = 156, 12 | Zcash = 133, 13 | // ERC20 Tokens 14 | Aragon = 601, 15 | Augur = 602, 16 | BAT = 603, 17 | Civic = 604, 18 | district0x = 605, 19 | EOS = 606, 20 | FunFair = 607, 21 | Golem = 608, 22 | Gnosis = 609, 23 | OmiseGo = 610, 24 | SALT = 611, 25 | Bancor = 612, 26 | SingularDTV = 613, 27 | ICONOMI = 614, 28 | DigixDAO = 615, 29 | Melon = 616, 30 | SwarmCity = 617, 31 | Wings = 618, 32 | WeTrust = 619, 33 | iExec = 620, 34 | Matchpool = 621, 35 | Status = 622, 36 | Numeraire = 623, 37 | Edgeless = 624, 38 | Metal = 625, 39 | TenX = 626, 40 | "Qtum ICO Token" = 627, 41 | "0x" = 628, 42 | FirstBlood = 629, 43 | RCN = 630, 44 | Storj = 631, 45 | BinanceCoin = 632, 46 | Tether = 633, 47 | PolyMath = 634, 48 | Zilliqa = 635, 49 | Decentraland = 636, 50 | "0xBitcoin" = 637, 51 | Gifto = 638, 52 | IOSToken = 639, 53 | Aelf = 640, 54 | TrueUSD = 641, 55 | Aeternity = 642, 56 | Maker = 643, 57 | Dai = 644, 58 | SpankChain = 645, 59 | CyberMiles = 646, 60 | "Crypto.com" = 647, 61 | Populous = 648, 62 | ODEM = 649, 63 | } 64 | -------------------------------------------------------------------------------- /src/device-message-states.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | import {MessageSender} from "./message-states"; 4 | import {MessageState} from "./message-states"; 5 | import {MessageStates} from "./message-states"; 6 | import {MessageName} from "./message-states"; 7 | import {MessageDirection} from "./message-states"; 8 | 9 | export class DeviceMessageStates { 10 | public static Cancel: MessageName = 'Cancel'; 11 | public static TxRequest: MessageName = 'TxRequest'; 12 | public static EthereumTxRequest: MessageName = 'EthereumTxRequest'; 13 | 14 | public static isInterstitialMessage(lastMessage: MessageState, message: MessageName) { 15 | return lastMessage.interstitialMessages && 16 | (_.indexOf(lastMessage.interstitialMessages, message) !== -1); 17 | } 18 | 19 | /*** 20 | * return the message state information for a request from the host 21 | * @type {any} 22 | */ 23 | public static getHostMessageState(name: MessageName): MessageState { 24 | return MessageStates.getMessageState(MessageSender.host, MessageDirection.request, name); 25 | } 26 | 27 | /*** 28 | * return the message state information for request from the device 29 | * @type {any} 30 | */ 31 | public static getDeviceMessageState(name: MessageName): MessageState { 32 | return MessageStates.getMessageState(MessageSender.device, MessageDirection.request, name); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/actions/apply-settings-action.ts: -------------------------------------------------------------------------------- 1 | import {BasicClient} from "../device-client"; 2 | import {DeviceMessageHelper} from "../device-message-helper"; 3 | import ApplySettings = DeviceMessages.ApplySettings; 4 | import {Language} from "../global/language"; 5 | import {Features} from "../global/features"; 6 | import {ActionHelper} from "./action-helper"; 7 | 8 | export class ApplySettingsAction { 9 | public static operation(client: BasicClient, usePassphrase: boolean, 10 | language: Language, label: string, autoLockDelayMs: number) { 11 | return client.featuresService.promise 12 | .then((features: Features) => { 13 | if (!features.initialized) { 14 | return Promise.reject('Device not initialized'); 15 | } 16 | }) 17 | .then((features) => { 18 | var message: ApplySettings = DeviceMessageHelper.factory('ApplySettings'); 19 | if (label !== null) { 20 | message.setLabel(label); 21 | } 22 | if (language !== null) { 23 | message.setLanguage(Language[language]); 24 | } 25 | if (usePassphrase !== null) { 26 | message.setUsePassphrase(usePassphrase); 27 | } 28 | if (autoLockDelayMs !== null) { 29 | message.setAutoLockDelayMs(autoLockDelayMs); 30 | } 31 | 32 | return client.writeToDevice(message); 33 | }) 34 | .catch(ActionHelper.ignoreCancelledAction); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/actions/sign-message-action.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import ByteBuffer = require('bytebuffer'); 3 | 4 | import {BasicClient} from "../device-client"; 5 | import {NodeVector} from "../node-vector"; 6 | import SignMessage = DeviceMessages.SignMessage; 7 | import {DeviceMessageHelper} from "../device-message-helper"; 8 | import {Features} from "../global/features"; 9 | 10 | export interface SignMessageOptions { 11 | addressN?: NodeVector | string; 12 | message?: ByteBuffer; 13 | coinName?: string; 14 | } 15 | 16 | const DEFAULT_OPTIONS: SignMessageOptions = { 17 | addressN: new NodeVector([]), 18 | message: ByteBuffer.wrap(''), 19 | coinName: 'Bitcoin' 20 | }; 21 | 22 | export class SignMessageAction { 23 | public static operation(client: BasicClient, options: SignMessageOptions): Promise { 24 | var o: SignMessageOptions = _.extend( 25 | {}, DEFAULT_OPTIONS, options); 26 | 27 | return client.featuresService.promise 28 | .then(function (features: Features) { 29 | if (features.initialized) { 30 | var message: SignMessage = DeviceMessageHelper.factory('SignMessage'); 31 | message.setAddressN(NodeVector.fromString(o.addressN).toArray()); 32 | message.setMessage(o.message); 33 | message.setCoinName(o.coinName); 34 | 35 | return client.writeToDevice(message); 36 | } else { 37 | return Promise.reject('signMessage: device not initialized'); 38 | } 39 | }); 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /device-profiles/keepkey-bootloader-1.0.3.hjson: -------------------------------------------------------------------------------- 1 | { 2 | identity: { 3 | vendor: keepkey.com 4 | bootloader_mode: true 5 | major_version: 1 6 | minor_version: 0 7 | patch_version: 3 8 | } 9 | hashes: [ 10 | { 11 | hash: cb222548a39ff6cbe2ae2f02c8d431c9ae0df850f814444911f521b95ab02f4c 12 | upgradable: true 13 | upgradeSkipable: false 14 | tag: v1.0.3 15 | } 16 | { 17 | hash: 917d1952260c9b89f3a96bea07eea4074afdcc0e8cdd5d064e36868bdd68ba7d 18 | upgradable: true 19 | upgradeSkipable: false 20 | tag: v1.0.3-patched 21 | } 22 | { 23 | hash: 6465bc505586700a8111c4bf7db6f40af73e720f9e488d20db56135e5a690c4f 24 | upgradable: true 25 | upgradeSkipable: false 26 | tag: v1.0.3v2 27 | } 28 | { 29 | hash: db4bc389335e876e942ae3b12558cecd202b745903e79b34dd2c32532708860e 30 | upgradable: true 31 | upgradeSkipable: false 32 | tag: v1.0.3v2-patched 33 | } 34 | { 35 | hash: 2e38950143cf350345a6ddada4c0c4f21eb2ed337309f39c5dbc70b6c091ae00 36 | upgradable: true 37 | upgradeSkipable: false 38 | tag: v1.0.3v3 39 | } 40 | { 41 | hash: 83d14cb6c7c48af2a83bc326353ee6b9abdd74cfe47ba567de1cb564da65e8e9 42 | upgradable: true 43 | upgradeSkipable: false 44 | tag: v1.0.3v3-patched 45 | } 46 | ] 47 | capabilities: { 48 | model: K1-14AM 49 | vendorName: KeepKey 50 | firmwareImageAvailable: true 51 | bootloaderUpgradable: true 52 | bootloaderUpgradeSkipable: false 53 | } 54 | } -------------------------------------------------------------------------------- /src/actions/cipher-key-value-action.ts: -------------------------------------------------------------------------------- 1 | import ByteBuffer = require('bytebuffer'); 2 | 3 | import {BasicClient} from "../device-client"; 4 | import {NodeVector} from "../node-vector"; 5 | import {DeviceMessageHelper} from "../device-message-helper"; 6 | import CipherKeyValue = DeviceMessages.CipherKeyValue; 7 | import {Features} from "../global/features"; 8 | import {BufferHelper} from "../global/buffer-helper"; 9 | 10 | export class CipherKeyValueAction { 11 | public static operation(client: BasicClient, 12 | encrypt: boolean, 13 | addressN: NodeVector, 14 | key: string, 15 | value: ByteBuffer, 16 | askOnEncrypt: boolean, 17 | askOnDecrypt: boolean): Promise { 18 | 19 | return client.featuresService.promise 20 | .then((features: Features) => { 21 | if (features.initialized) { 22 | var message: CipherKeyValue = DeviceMessageHelper.factory('CipherKeyValue'); 23 | message.setAddressN(addressN.toArray()); 24 | message.setKey(key); 25 | message.setValue(BufferHelper.pad(value, 16)); 26 | message.setEncrypt(encrypt); 27 | message.setAskOnEncrypt(askOnEncrypt); 28 | message.setAskOnDecrypt(askOnDecrypt); 29 | 30 | return client.writeToDevice(message); 31 | } else { 32 | return Promise.reject('encryptKeyValue: device not initialized'); 33 | } 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keepkey/device-client", 3 | "version": "6.3.0", 4 | "description": "A library to communicate with a KeepKey device", 5 | "main": "dist/device-client-manager.js", 6 | "module": "dist/**/*.js", 7 | "typings": "dist/*.d.ts", 8 | "files": [ 9 | "dist/**/*", 10 | "misc/*" 11 | ], 12 | "scripts": { 13 | "prepublish": "gulp pre-tsc && tsc", 14 | "test": "echo \"Error: no test specified\" && exit 1" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/keepkey/keepkey-device-client.git" 19 | }, 20 | "author": "KeepKey, LLC", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/keepkey/device-client/issues" 24 | }, 25 | "homepage": "https://github.com/keepkey/keepkey-device-client#readme", 26 | "devDependencies": { 27 | "@types/bitcore-lib": "keepkey/bitcore-lib-types", 28 | "@types/bytebuffer": "^5.0.33", 29 | "@types/concat-stream": "^1.6.0", 30 | "@types/lodash": "4.14.115", 31 | "@types/node": "^10.5.4", 32 | "device-protocol": "keepkey/device-protocol#v6.0.2", 33 | "gulp": "github:gulpjs/gulp#6d71a658c61edb3090221579d8f97dbe086ba2ed", 34 | "gulp-bump": "^3.1.1", 35 | "gulp-hjson": "^2.0.3", 36 | "gulp-jsoncombine": "^1.0.3", 37 | "gulp-pbjs": "keepkey/gulp-pbjs", 38 | "gulp-rename": "^1.2.2", 39 | "gulp-replace": "^1.0.0", 40 | "gulp-typescript": "4.0.2", 41 | "proto2typescript": "keepkey/Proto2TypeScript", 42 | "typescript": "^3.0.1" 43 | }, 44 | "dependencies": { 45 | "bignumber.js": "^7.2.1", 46 | "bitcore-lib": "keepkey/bitcore-lib", 47 | "bytebuffer": "^5.0.1", 48 | "concat-stream": "^1.6.0", 49 | "events": "^3.0.0", 50 | "lodash": "4.17.11", 51 | "protobufjs": "^5.0", 52 | "replace-ext": "^1.0.0", 53 | "through2": "^2.0.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/actions/encrypt-message-action.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import ByteBuffer = require('bytebuffer'); 3 | 4 | import {BasicClient} from "../device-client"; 5 | import {NodeVector} from "../node-vector"; 6 | import {DeviceMessageHelper} from "../device-message-helper"; 7 | import EncryptMessage = DeviceMessages.EncryptMessage; 8 | import {Features} from "../global/features"; 9 | import {BufferHelper} from "../global/buffer-helper"; 10 | 11 | export interface EncryptMessageOptions { 12 | addressN?: NodeVector | string; 13 | message?: ByteBuffer; 14 | publicKey?: ByteBuffer; 15 | displayOnly?: boolean; 16 | coinName?: string; 17 | } 18 | 19 | const DEFAULT_OPTIONS: EncryptMessageOptions = { 20 | addressN: new NodeVector([]), 21 | message: ByteBuffer.wrap(''), 22 | publicKey: ByteBuffer.wrap(''), 23 | displayOnly: false, 24 | coinName: 'Bitcoin' 25 | }; 26 | 27 | 28 | //FIXME I can't get this to work. The device always returns an error that the key is incorrect. 29 | export class EncryptMessageAction { 30 | public static operation(client: BasicClient, options: EncryptMessageOptions): Promise { 31 | var o: EncryptMessageOptions = _.extend( 32 | {}, DEFAULT_OPTIONS, options); 33 | 34 | return client.featuresService.promise 35 | .then(function (features: Features) { 36 | if (features.initialized) { 37 | var message: EncryptMessage = DeviceMessageHelper.factory('EncryptMessage'); 38 | message.setPubkey(BufferHelper.pad(o.publicKey, 33)); 39 | message.setMessage(o.message); 40 | message.setDisplayOnly(o.displayOnly); 41 | message.setAddressN(NodeVector.fromString(options.addressN).toArray()); 42 | message.setCoinName(o.coinName); 43 | 44 | return client.writeToDevice(message); 45 | } else { 46 | return Promise.reject('encryptMessage: device not initialized'); 47 | } 48 | }); 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/purpose-code-helper.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-bitwise */ 2 | 3 | export class ExtendedPurposeCodeHelper { 4 | public static encode(purpose: string): number { 5 | var encodedPurpose = 0; 6 | var count = 0; 7 | 8 | purpose.split('').forEach(function (chr: string) { 9 | if (count++ < 6) { 10 | encodedPurpose = encodedPurpose << 5 | encodeChar(chr.charCodeAt(0)); 11 | } 12 | }); 13 | 14 | for (; count < 6; count++) { 15 | encodedPurpose = encodedPurpose << 5; 16 | } 17 | 18 | /* set the hardened and extended purpose bits */ 19 | return (encodedPurpose | 0xC0000000) >>> 0; 20 | } 21 | 22 | public static decode(encodedPurpose: number): string { 23 | var base32Decode = [ 24 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 25 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 26 | 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 27 | 'Y', 'Z' 28 | ]; 29 | var purpose = []; 30 | 31 | if (((0xC0000000 & encodedPurpose) >>> 0) !== 0xC0000000) { 32 | return undefined; 33 | } 34 | 35 | /* strip hardened and extended purpose bits */ 36 | encodedPurpose &= 0x3FFFFFFF; 37 | while (purpose.length < 6) { 38 | purpose.push(base32Decode[encodedPurpose & 0x1f]); 39 | encodedPurpose = encodedPurpose >>> 5; 40 | } 41 | 42 | return purpose.reverse().join(''); 43 | } 44 | } 45 | 46 | function encodeChar(charCode: number): number { 47 | var base32Encode = [ 48 | /* Digits */ 49 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 50 | /* Uppercase */ 51 | 0, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, 22, 23, 24, 25, 26, 27, 27, 28, 29, 30, 31, 0, 0, 0, 0, 0, 52 | /* Lowercase */ 53 | 0, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, 22, 23, 24, 25, 26, 27, 27, 28, 29, 30, 31 54 | ]; 55 | if (charCode < 48 || charCode > 122) { 56 | return 0; 57 | } else { 58 | return base32Encode[charCode - 48]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/actions/reset-device-action.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | import {BasicClient} from "../device-client"; 4 | import {DeviceMessageHelper} from "../device-message-helper"; 5 | import ResetDevice = DeviceMessages.ResetDevice; 6 | import {Features} from "../global/features"; 7 | 8 | export interface ResetDeviceOptions { 9 | display_random?: boolean; 10 | strength?: number; 11 | passphrase_protection?: boolean; 12 | pin_protection?: boolean; 13 | language?: string; 14 | label?: string; 15 | } 16 | 17 | export class ResetDeviceAction { 18 | private static DEFAULT_OPTIONS: ResetDeviceOptions = { 19 | display_random: false, 20 | passphrase_protection: false, 21 | pin_protection: true, 22 | language: 'english', 23 | label: null 24 | }; 25 | 26 | public static operation(client: BasicClient, options: ResetDeviceOptions) { 27 | return client.featuresService.promise 28 | .then((features: Features) => { 29 | 30 | if (!features.initialized) { 31 | var o: ResetDeviceOptions = 32 | _.extend({}, ResetDeviceAction.DEFAULT_OPTIONS, options); 33 | 34 | var message: ResetDevice = DeviceMessageHelper.factory('ResetDevice'); 35 | message.setDisplayRandom(o.display_random); 36 | message.setStrength(o.strength || 37 | ResetDeviceAction.wordCount2KeyStrength( 38 | features.defaultMnemonicSeedLength) || 128); 39 | message.setPassphraseProtection(o.passphrase_protection); 40 | message.setPinProtection(o.pin_protection); 41 | message.setLanguage(o.language); 42 | message.setLabel(o.label); 43 | message.setU2fCounter(Math.floor(Date.now() / 1000)) 44 | 45 | return client.writeToDevice(message); 46 | } else { 47 | return Promise.reject('Expected device to be uninitialized. Run WipeDevice and try again.'); 48 | } 49 | 50 | }); 51 | } 52 | 53 | private static wordCount2KeyStrength(wc: number): number { 54 | switch (wc) { 55 | case 12: return 128; 56 | case 18: return 192; 57 | case 24: return 256; 58 | default: return 0; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/device-client-methods.ts: -------------------------------------------------------------------------------- 1 | import {NodeVector} from "./node-vector"; 2 | import {DevicePolicyEnum} from "./global/device-policy-enum"; 3 | 4 | export type Initialize = { 5 | (skipBootloaderHashCheck?: boolean): Promise; 6 | } 7 | 8 | export type GetAddressFunction = { 9 | (nodePath: NodeVector, coinName: string, showDisplay: boolean): Promise; 10 | } 11 | 12 | export type GetEthereumAddressFunction = { 13 | (nodePath: NodeVector, showDisplay: boolean): Promise; 14 | } 15 | 16 | export type GetPublicKeyFunction = { 17 | (nodePath: NodeVector, showDisplay: boolean): Promise; 18 | } 19 | 20 | export type ChangeLabelFunction = { 21 | (label: string): Promise; 22 | } 23 | 24 | export type ChangePinTimeoutFunction = { 25 | (timeout: number): Promise; 26 | } 27 | 28 | export type EnablePassphraseFunction = { 29 | (enabled: boolean): Promise; 30 | } 31 | 32 | export type DecryptNodeVectorFunction = { 33 | (encrypted: string): Promise; 34 | } 35 | 36 | export type CipherAccountNameFunction = { 37 | (node: NodeVector, value: string): Promise; 38 | } 39 | 40 | export type SendPassphraseFunction = { 41 | (passphrase: string): Promise; 42 | } 43 | 44 | export type EncryptNodeVectorFunction = { 45 | (node: NodeVector): Promise; 46 | } 47 | 48 | export type FirmwareUploadFunction = { 49 | (firmwareId: string): Promise; 50 | } 51 | 52 | export type VerifyDevice = { 53 | (callback: (message: any)=>void): Promise; 54 | } 55 | 56 | export type RecoveryDeviceFunction = { 57 | (options: DeviceRecoveryRequest): Promise; 58 | } 59 | 60 | export type ResetDeviceFunction = { 61 | (options: ResetRequest): Promise; 62 | } 63 | 64 | export type ApplyPolicyFunction = { 65 | (policy: DevicePolicyEnum, enabled: boolean): Promise; 66 | } 67 | 68 | export interface DeviceRecoveryRequest { 69 | messageType: string; 70 | passphrase_protection: boolean; 71 | pin_protection: boolean; 72 | language: string; 73 | label: string; 74 | word_count: number; 75 | enforce_wordlist: boolean; 76 | use_character_cipher: boolean; 77 | } 78 | 79 | export interface ResetRequest { 80 | messageType: string; 81 | display_random: boolean; 82 | passphrase_protection: boolean; 83 | pin_protection: boolean; 84 | language: string; 85 | label: string; 86 | } 87 | -------------------------------------------------------------------------------- /src/actions/recover-device-action.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | import {BasicClient} from "../device-client"; 4 | import RecoveryDevice = DeviceMessages.RecoveryDevice; 5 | import {DeviceMessageHelper} from "../device-message-helper"; 6 | import {Features} from "../global/features"; 7 | 8 | export interface RecoverDeviceOptions { 9 | passphrase_protection?: boolean; 10 | pin_protection?: boolean; 11 | language?: string; 12 | label?: string; 13 | word_count?: number; 14 | enforce_wordlist?: boolean; 15 | use_character_cipher?: boolean; 16 | dry_run?: boolean; 17 | } 18 | 19 | enum FailureCodes { 20 | 'Failure_ActionCancelled' 21 | } 22 | 23 | export class RecoverDeviceAction { 24 | private static DEFAULT_OPTIONS: RecoverDeviceOptions = { 25 | passphrase_protection: false, 26 | pin_protection: true, 27 | language: null, 28 | label: null, 29 | word_count: 12, 30 | enforce_wordlist: true, 31 | use_character_cipher: true, 32 | dry_run: false 33 | }; 34 | 35 | public static operation(client: BasicClient, options: RecoverDeviceOptions) { 36 | var o: RecoverDeviceOptions = _.extend( 37 | {}, RecoverDeviceAction.DEFAULT_OPTIONS, options); 38 | 39 | return client.featuresService.promise 40 | .then((features: Features) => { 41 | if (features.initialized && !o.dry_run) { 42 | return Promise.reject("Expected the device to be uninitialized"); 43 | } 44 | 45 | if (!features.initialized && o.dry_run) { 46 | return Promise.reject("Expected the device to be initialized"); 47 | } 48 | 49 | o.use_character_cipher = features.supportsCipheredKeyRecovery; 50 | if (o.use_character_cipher) { 51 | o.word_count = 0; 52 | } else { 53 | o.word_count = features.defaultMnemonicSeedLength; 54 | } 55 | 56 | var message: RecoveryDevice = DeviceMessageHelper.factory('RecoveryDevice'); 57 | message.setWordCount(o.word_count); 58 | message.setPassphraseProtection(o.passphrase_protection); 59 | message.setPinProtection(o.pin_protection); 60 | message.setLanguage(o.language); 61 | message.setLabel(o.label); 62 | message.setEnforceWordlist(o.enforce_wordlist); 63 | message.setUseCharacterCipher(o.use_character_cipher); 64 | message.setDryRun(o.dry_run); 65 | message.setU2fCounter(Math.floor(Date.now() / 1000)) 66 | 67 | return client.writeToDevice(message) 68 | .catch((failureMessage) => { 69 | if (!FailureCodes[failureMessage.code]) { 70 | return Promise.reject(failureMessage); 71 | } 72 | }); 73 | }) 74 | .catch(function (failure) { 75 | console.log('deviceRecovery failed', arguments); 76 | return Promise.reject(failure); 77 | }); 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/device-message-helper.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import ByteBuffer = require('bytebuffer'); 3 | import Long = require('long'); 4 | import {ReflectableProtoBufModel} from "./global/message-handler"; 5 | 6 | export class DeviceMessageHelper { 7 | public static messageFactories = require('../dist/messages.js'); 8 | 9 | public static factory(MessageType, ...args) { 10 | return new (Function.prototype.bind.apply(DeviceMessageHelper.messageFactories[MessageType], args)); 11 | } 12 | 13 | public static decode(messageType: string, message: ByteBuffer): T { 14 | return DeviceMessageHelper.messageFactories[messageType].decode(message); 15 | } 16 | 17 | public static hydrate(pbMessage: ReflectableProtoBufModel) { 18 | var objReflection: any = pbMessage.$type; 19 | var newMessage = _.cloneDeepWith(pbMessage, (value) => { 20 | if (ByteBuffer.isByteBuffer(value)) { 21 | return value; 22 | } 23 | }); 24 | 25 | var enumFields = _.filter(objReflection._fields, (it : any) => { 26 | return it.resolvedType && it.resolvedType.className === 'Enum'; 27 | }); 28 | 29 | _.each(enumFields, function (it: any) { 30 | var value = pbMessage[it.name]; 31 | var match: any = _.find(it.resolvedType.children, {id: value}); 32 | newMessage[it.name] = match && match.name; 33 | }); 34 | 35 | newMessage.typeName = pbMessage.$type.name; 36 | return newMessage; 37 | } 38 | 39 | public static toPrintable(pbMessage) { 40 | return JSON.stringify( 41 | DeviceMessageHelper.hydrate(pbMessage), 42 | DeviceMessageHelper.buffer2Hex, 43 | 4 44 | ); 45 | } 46 | 47 | public static buffer2Hex(key, value: any) { 48 | if (key === 'passphrase' || key === 'pin' || key === 'character') { 49 | return ''; 50 | } else if (value && value.buffer) { 51 | // NOTE: v.buffer is type Buffer in node and ArrayBuffer in chrome 52 | if (value.buffer instanceof Buffer) { 53 | return value.toHex(); 54 | } 55 | 56 | var hexstring = ''; 57 | if (value.limit > 1000) { 58 | return ''; 59 | } 60 | for (var i = value.offset; i < value.limit; i++) { 61 | if (value.view[i] < 16) { 62 | hexstring += 0; 63 | } 64 | hexstring += value.view[i].toString(16); 65 | } 66 | return hexstring; 67 | } else if (value && !_.isUndefined(value.low) && !_.isUndefined(value.high) && !_.isUndefined(value.unsigned)) { 68 | return (new Long(value.low, value.high, value.unsigned)).toString(); 69 | } else { 70 | return value; 71 | } 72 | } 73 | 74 | public static getSelectedResponse(signedResponse: DeviceMessages.SignedExchangeResponse): DeviceMessages.ExchangeResponse | DeviceMessages.ExchangeResponseV2 { 75 | let responseV1 = signedResponse.getResponse(); 76 | let responseV2 = signedResponse.getResponseV2(); 77 | 78 | if (responseV1 && responseV2) { 79 | throw 'Invalid signed response. Both V1 and V2 responses returned'; 80 | } else if (!responseV1 && !responseV2) { 81 | throw 'Invalid signed response. No response returned'; 82 | } 83 | return responseV1 || responseV2; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/node-vector.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-bitwise */ 2 | import * as _ from 'lodash'; 3 | import ByteBuffer = require('bytebuffer'); 4 | 5 | import {ExtendedPurposeCodeHelper} from "./purpose-code-helper"; 6 | 7 | const HARDENED_NODE_FLAG = 0x80000000; 8 | const EXTENDED_PURPOSE_FLAG = 0x40000000; 9 | 10 | export class NodeVector { 11 | private _value: Array; 12 | 13 | constructor(args: Array) { 14 | this._value = _.clone(args); 15 | } 16 | 17 | public static fromString(addressN: NodeVector | string): NodeVector { 18 | if (!addressN) { 19 | return null; 20 | } 21 | if (addressN instanceof NodeVector) { 22 | return addressN; 23 | } 24 | var nodeVectorStrings = (addressN).toUpperCase().split('/'); 25 | 26 | if (nodeVectorStrings[0] === 'M') { 27 | nodeVectorStrings = nodeVectorStrings.slice(1); 28 | } 29 | var result: number[] = []; 30 | 31 | _.transform(nodeVectorStrings, NodeVector.convertNodeStringToNumber, result); 32 | 33 | return new NodeVector(result); 34 | } 35 | 36 | public static fromBuffer(buffer: ByteBuffer): NodeVector { 37 | var vectorArray = []; 38 | buffer.BE(); 39 | 40 | var length = buffer.readUint8(); 41 | for (var i = 0; i < length; i++) { 42 | vectorArray.push(buffer.readUint32()); 43 | } 44 | 45 | return new NodeVector(vectorArray); 46 | } 47 | 48 | public static join(vectors: Array) { 49 | return _.reduce( 50 | vectors, 51 | (result, vector) => result.join(NodeVector.fromString(vector)), 52 | new NodeVector([]) 53 | ); 54 | } 55 | 56 | private static convertNodeStringToNumber(result: number[], nodeString: string) { 57 | if (nodeString.substring(nodeString.length - 1) === "'") { 58 | nodeString = '-' + nodeString.substring(0, nodeString.length - 1); 59 | } 60 | 61 | var value = parseInt(nodeString, 10); 62 | if (isNaN(value)) { 63 | value = ExtendedPurposeCodeHelper.encode(nodeString); 64 | } else if (value < 0) { 65 | value = (Math.abs(value) | HARDENED_NODE_FLAG) >>> 0; 66 | } 67 | 68 | if (nodeString === '-0') { 69 | result.push(HARDENED_NODE_FLAG); 70 | } else { 71 | result.push(value); 72 | } 73 | 74 | return result; 75 | }; 76 | 77 | public join(o: NodeVector | string): NodeVector { 78 | if (o) { 79 | let vector = NodeVector.fromString(o); 80 | return new NodeVector(this._value.concat(vector.toArray())); 81 | } else { 82 | return new NodeVector(this._value); 83 | } 84 | } 85 | 86 | public toString(): string { 87 | var converted = ['m']; 88 | this._value.forEach((it: number) => { 89 | if (it & HARDENED_NODE_FLAG) { 90 | if (it & EXTENDED_PURPOSE_FLAG) { 91 | converted.push(ExtendedPurposeCodeHelper.decode(it)); 92 | } else { 93 | converted.push((it ^ HARDENED_NODE_FLAG) + '\''); 94 | } 95 | } else { 96 | converted.push(it.toString()); 97 | } 98 | }); 99 | return converted.join('/'); 100 | } 101 | 102 | public toArray(): Array { 103 | return this._value; 104 | } 105 | 106 | public toBuffer(): ByteBuffer { 107 | var nodeVectorDepth = this._value.length; 108 | var buffer = ByteBuffer.allocate(nodeVectorDepth * 4 + 1).BE(); 109 | buffer.writeUint8(nodeVectorDepth); 110 | this._value.forEach(function (node) { 111 | buffer.writeUint32(node); 112 | }); 113 | buffer.reset(); 114 | return buffer; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/global/features.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import * as ByteBuffer from "bytebuffer"; 3 | 4 | import {DevicePolicyEnum} from "./device-policy-enum"; 5 | import {CoinName} from "./coin-name"; 6 | import {CoinTypeConfiguration} from "./coin-type"; 7 | import {CoinType} from "./coin-type"; 8 | 9 | export interface DeviceCapabilities { 10 | firmwareImageAvailable: boolean; 11 | defaultMnemonicSeedLength: number; 12 | supportsCipheredKeyRecovery: boolean; 13 | supportsSecureAccountTransfer: boolean; 14 | hasPagedCoinTable: boolean; 15 | supportsRecoveryDryRun: boolean; 16 | } 17 | 18 | export interface BootloaderInfo { 19 | hash: string; 20 | upgradable: boolean; 21 | upgradeSkipable: boolean; 22 | tag: string; 23 | } 24 | 25 | export interface Policy { 26 | policy_name: string; 27 | enabled: boolean; 28 | } 29 | 30 | export interface IFeatureCoin { 31 | coin_name: string; 32 | coin_shortcut: string; 33 | address_type: number; 34 | maxfee_kb: string; 35 | address_type_p2sh: number; 36 | address_type_p2wpkh: number; 37 | address_type_p2wsh: number; 38 | signed_message_header: string; 39 | bip44_account_path: number; 40 | forkid: number; 41 | decimals: number; 42 | contract_address: ByteBuffer; 43 | gas_limit: ByteBuffer; 44 | } 45 | 46 | export interface DeviceProfile { 47 | identity: Object; 48 | capabilities: DeviceCapabilities; 49 | } 50 | 51 | export interface IFeatures { 52 | available_firmware_versions: Array; 53 | bootloader_hash: ByteBuffer; 54 | bootloader_mode: any; 55 | coins: Array; 56 | device_id: string; 57 | initialized: boolean; 58 | label: string; 59 | language: string; 60 | major_version: number; 61 | minor_version: number; 62 | passphrase_cached: boolean; 63 | passphrase_protection: boolean; 64 | patch_version: number; 65 | pin_cached: boolean; 66 | pin_protection: boolean; 67 | policies: Array; 68 | revision: string; 69 | vendor: string; 70 | coin_metadata: Array; 71 | deviceCapabilities: DeviceCapabilities; 72 | bootloaderInfo: BootloaderInfo; 73 | model: string; 74 | version: string; 75 | } 76 | 77 | export interface ICoinTable { 78 | table: Array; 79 | num_coins: number; 80 | chunk_size: number; 81 | } 82 | 83 | export class Features { 84 | public get version(): string { 85 | return [ 86 | this.data.major_version.toString(), 87 | this.data.minor_version.toString(), 88 | this.data.patch_version.toString() 89 | ].join('.'); 90 | } 91 | 92 | public get initialized(): boolean { 93 | return this.data.initialized; 94 | } 95 | 96 | public get bootloaderMode(): boolean { 97 | return this.data.bootloader_mode; 98 | } 99 | 100 | public get supportsCipheredKeyRecovery(): boolean { 101 | return _.get(this.data, 'deviceCapabilities.supportsCipheredKeyRecovery'); 102 | } 103 | 104 | public get defaultMnemonicSeedLength(): number { 105 | return _.get(this.data, 'deviceCapabilities.defaultMnemonicSeedLength'); 106 | } 107 | 108 | public get usesShapeshiftResponseV2(): boolean { 109 | return _.get(this.data, 'deviceCapabilities.usesShapeshiftResponseV2'); 110 | } 111 | 112 | public get raw(): IFeatures { 113 | return this.data; 114 | } 115 | 116 | constructor(private data: IFeatures) {}; 117 | 118 | public supportsPolicy(policy: DevicePolicyEnum): boolean { 119 | return !!(this.findPolicy(policy)); 120 | } 121 | 122 | public policyEnabled(policy: DevicePolicyEnum): boolean { 123 | return _.get(this.findPolicy(policy), 'enabled'); 124 | } 125 | 126 | public supportsCoinType(coin: CoinName): boolean { 127 | var coinName: string = CoinName[coin]; 128 | return !!(_.find(this.data.coin_metadata, {name: coinName})); 129 | } 130 | 131 | public getTokenList(): Array { 132 | return this.data.coins.filter((coin: IFeatureCoin) => !!coin.contract_address); 133 | } 134 | 135 | public get model(): string { 136 | return this.data.model; 137 | } 138 | 139 | private findPolicy(policy: DevicePolicyEnum) { 140 | return _.find(this.data.policies, { 141 | policy_name: DevicePolicyEnum[policy] 142 | }); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/device-client-manager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2017 KeepKey, LLC. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 7 | * "Software"), to deal in the Software without restriction, including 8 | * without limitation the rights to use, copy, modify, merge, publish, 9 | * distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to 11 | * the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | import * as _ from 'lodash'; 26 | import {DeviceClient, FirmwareStreamFactory} from "./device-client"; 27 | import {Transport} from "./transport"; 28 | import {DeviceMessageHelper} from "./device-message-helper"; 29 | import {EventEmitter} from "events"; 30 | import {HidHelper} from "./hid-helper"; 31 | 32 | const RECOGNIZED_DEVICES: Array = [ 33 | { 34 | type: "KEEPKEY", 35 | vendorId: 11044, 36 | productId: 1 // HID KeepKey 37 | }, 38 | { 39 | type: "KEEPKEY", 40 | vendorId: 11044, 41 | productId: 2 // WebUSB KeepKey 42 | }, 43 | { 44 | type: "TREZOR", 45 | vendorId: 21324, 46 | productId: 1 47 | } 48 | ]; 49 | 50 | export class DeviceClientManager extends EventEmitter { 51 | public static DEVICE_CONNECTED_EVENT = 'connected'; 52 | private static _instance: DeviceClientManager; 53 | 54 | public static get instance(): DeviceClientManager { 55 | if (!DeviceClientManager._instance) { 56 | DeviceClientManager._instance = new DeviceClientManager(); 57 | } 58 | return DeviceClientManager._instance; 59 | } 60 | 61 | private _hidHelpers: HidHelper[] = []; 62 | 63 | public addHidHelper(helper: HidHelper) { 64 | this._hidHelpers.push(helper); 65 | } 66 | 67 | public removeHidHelpers() { 68 | this._hidHelpers = []; 69 | } 70 | 71 | public hidHelperIsSet() { 72 | return !!this._hidHelpers[0] 73 | } 74 | 75 | private _rawFirmwareStreamFactory: FirmwareStreamFactory; 76 | public get rawFirmwareStreamFactory(): FirmwareStreamFactory { 77 | return this._rawFirmwareStreamFactory; 78 | } 79 | 80 | public set rawFirmwareStreamFactory(streamFactory: FirmwareStreamFactory) { 81 | this._rawFirmwareStreamFactory = streamFactory; 82 | } 83 | 84 | public clients: _.Dictionary = {}; 85 | 86 | public findByDeviceId(deviceId): DeviceClient { 87 | return this.clients[deviceId]; 88 | } 89 | 90 | public remove(deviceId): void { 91 | if (this.clients[deviceId]) { 92 | this.clients[deviceId].destroy(); 93 | delete this.clients[deviceId]; 94 | } 95 | } 96 | 97 | public getActiveClient(): Promise { 98 | const hidHelper = this._hidHelpers[0]; 99 | return hidHelper ? hidHelper.getActiveClient() : Promise.reject('No Active Client'); 100 | } 101 | 102 | public factory(transport: Transport): DeviceClient { 103 | var deviceType = _.find(RECOGNIZED_DEVICES, { 104 | vendorId: transport.vendorId, 105 | productId: transport.productId 106 | }); 107 | if (deviceType) { 108 | transport.setMessageMap(deviceType, DeviceMessageHelper.messageFactories); 109 | return this.findByDeviceId(transport.deviceId) || 110 | this.createNewDeviceClient(transport); 111 | } else { 112 | throw 'unrecognized device: ' + transport; 113 | } 114 | } 115 | 116 | private createNewDeviceClient(transport): DeviceClient { 117 | var client = new DeviceClient(transport, this.rawFirmwareStreamFactory); 118 | this.clients[client.transport.deviceId] = client; 119 | 120 | this.emit(DeviceClientManager.DEVICE_CONNECTED_EVENT, client); 121 | 122 | return client; 123 | } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /src/transport.ts: -------------------------------------------------------------------------------- 1 | import ByteBuffer = require('bytebuffer'); 2 | 3 | const OLD_MESSAGE_HEADER_START = '##'; 4 | const MESSAGE_HEADER_START = String.fromCharCode(0x3f) + '##'; 5 | 6 | export abstract class Transport { 7 | public static MSG_HEADER_LENGTH = 6; 8 | 9 | public deviceInUse: boolean = false; 10 | 11 | private messageMaps = {}; 12 | private messageMap; 13 | private protoBuf; 14 | private pendingWriteQueue = Promise.resolve(); 15 | 16 | public get deviceId() { 17 | return this.deviceData.deviceId || this.deviceData.device; 18 | } 19 | 20 | public get vendorId() { 21 | return this.deviceData.vendorId; 22 | } 23 | 24 | public get productId() { 25 | return this.deviceData.productId; 26 | } 27 | 28 | protected get hasReportId(): boolean { 29 | return this.deviceData.maxInputReportSize !== 63; 30 | } 31 | 32 | protected get reportId() { 33 | return this.hasReportId ? 0 : 63; 34 | } 35 | 36 | protected get messageHeaderStart(): string { 37 | return this.hasReportId ? MESSAGE_HEADER_START : OLD_MESSAGE_HEADER_START; 38 | } 39 | 40 | constructor(private deviceData) {} 41 | 42 | protected abstract _write(message: ByteBuffer): Promise; 43 | protected abstract _read(): Promise; 44 | 45 | public setMessageMap(deviceType, proto) { 46 | var msgClass = '', currentMsgClass = ''; 47 | 48 | if (!this.messageMaps.hasOwnProperty(deviceType)) { 49 | this.messageMaps[deviceType] = { 50 | msgTypeToClass: {}, 51 | msgClassToType: {} 52 | }; 53 | 54 | // cache message maps 55 | for (msgClass in proto.MessageType) { 56 | if (proto.MessageType.hasOwnProperty(msgClass)) { 57 | currentMsgClass = msgClass.replace('MessageType_', ''); 58 | this.messageMaps[deviceType].msgClassToType[currentMsgClass] = 59 | proto.MessageType[msgClass]; 60 | this.messageMaps[deviceType].msgTypeToClass[proto.MessageType[msgClass]] = 61 | currentMsgClass; 62 | } 63 | } 64 | } 65 | 66 | this.messageMap = this.messageMaps[deviceType]; 67 | this.protoBuf = proto; 68 | } 69 | 70 | public write(txProtoMsg) { 71 | var msgAB = txProtoMsg.encodeAB(); 72 | const hash = '#'.charCodeAt(0); 73 | var msgBB = new ByteBuffer(8 + msgAB.byteLength) 74 | .writeByte(hash) 75 | .writeByte(hash) 76 | .writeUint16(this.getMsgType(txProtoMsg.$type.name)) 77 | .writeUint32(msgAB.byteLength) 78 | .append(msgAB) 79 | .reset(); 80 | 81 | console.log('adding message to the queue'); 82 | return this.pendingWriteQueue 83 | .then(() => { 84 | console.log('sending message'); 85 | return this._write(msgBB); 86 | }); 87 | } 88 | 89 | public read() { 90 | return this._read() 91 | .then((rxMsg) => { 92 | var trimmedBuffer = ByteBuffer.wrap( 93 | rxMsg.bufferBB.toArrayBuffer().slice(0, rxMsg.header.msgLength)); 94 | return this.parseMsg(rxMsg.header.msgType, trimmedBuffer); 95 | }); 96 | } 97 | 98 | protected parseMsgHeader(msgBB) { 99 | // check for header message start 100 | for (var i = 0, 101 | iMax = this.messageHeaderStart.length; i < iMax; i += 1) { 102 | var next = String.fromCharCode(msgBB.readByte()); 103 | 104 | if (next !== this.messageHeaderStart[i]) { 105 | throw { 106 | name : 'Error', 107 | message: 'Message header not found' 108 | }; 109 | } 110 | } 111 | 112 | // unpack header 113 | var msgType = msgBB.readUint16(); 114 | var msgLength = msgBB.readUint32(); 115 | 116 | // reset msg bytebuffer 117 | msgBB.reset(); 118 | 119 | return { 120 | msgType : msgType, 121 | msgLength: msgLength 122 | }; 123 | } 124 | 125 | private parseMsg(msgType, msgBB) { 126 | var msgClass = this.getMsgClass(msgType); 127 | return this.protoBuf[msgClass].decode(msgBB); 128 | } 129 | 130 | private getMsgType(msgClass) { 131 | if (!this.messageMap.msgClassToType.hasOwnProperty(msgClass)) { 132 | throw { 133 | name : 'Error', 134 | message: 'Cannot find message name.' 135 | }; 136 | } else { 137 | return this.messageMap.msgClassToType[msgClass]; 138 | } 139 | } 140 | 141 | private getMsgClass(msgType) { 142 | if (!this.messageMap.msgTypeToClass.hasOwnProperty(msgType)) { 143 | throw { 144 | name : 'Error', 145 | message: 'Cannot find message id.' 146 | }; 147 | } else { 148 | return this.messageMap.msgTypeToClass[msgType]; 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/features-service.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import {BootloaderInfo, DeviceProfile, Features, IFeatureCoin, IFeatures, ICoinTable} from "./global/features"; 3 | import {CoinType} from "./global/coin-type"; 4 | import {BasicClient} from "./device-client"; 5 | import {DeviceMessageHelper} from "./device-message-helper"; 6 | import GetCoinTable = DeviceMessages.GetCoinTable; 7 | 8 | const FIRMWARE_METADATA_FILE: Array = require('../dist/firmware.json'); 9 | const OFFICIAL_BOOTLOADER_HASHES: Array = require('../dist/bootloader-profiles.json'); 10 | const DEVICE_PROFILES: Array = require('../dist/device-profiles.json'); 11 | 12 | export class FeaturesService { 13 | private static getDeviceCapabilities(features: any): any { 14 | var deviceProfile: DeviceProfile = _.find(DEVICE_PROFILES, (profile: any) => { 15 | return !!(_.find([features], profile.identity)); 16 | }); 17 | 18 | if (deviceProfile.capabilities.supportsRecoveryDryRun === undefined) 19 | deviceProfile.capabilities.supportsRecoveryDryRun = false; 20 | 21 | if (!deviceProfile) { 22 | console.error('Unknown device or version'); 23 | return undefined; 24 | } else { 25 | return deviceProfile.capabilities; 26 | } 27 | } 28 | 29 | private resolver: Function; 30 | private rejector: Function; 31 | private _promise: Promise; 32 | 33 | public async setValue(features: IFeatures, client: BasicClient, 34 | skipBootloaderHashCheck?: boolean): Promise { 35 | features.deviceCapabilities = FeaturesService.getDeviceCapabilities(features); 36 | 37 | if (!features.bootloader_mode) { 38 | // Legacy support for devices that still put the CoinTable in the 39 | // Features object. 40 | features.coins = features.coins || []; 41 | 42 | if (features.deviceCapabilities.hasPagedCoinTable || 43 | features.major_version > 5 || 44 | (features.major_version == 5 && features.minor_version >= 7)) { 45 | // Grab all of the pages of the coin table. 46 | // 47 | // To start, we have to find out how many coins the device supports, 48 | // and what chunk size it is prepared to return them back in. 49 | let head_message: GetCoinTable = DeviceMessageHelper.factory('GetCoinTable'); 50 | let head = await client.writeToDevice(head_message); 51 | 52 | // Then iterate through and request each of the chunks, building up 53 | // a chain of promises of the features object. 54 | for (let i = 0; i < Math.min(100, head.num_coins); i += head.chunk_size) { 55 | let tail_message: GetCoinTable = DeviceMessageHelper.factory('GetCoinTable'); 56 | tail_message.setStart(i); 57 | tail_message.setEnd(Math.min(i + head.chunk_size, head.num_coins)); 58 | let tail: ICoinTable = await client.writeToDevice(tail_message); 59 | // And add those chunks to the coin table. We do this to minimize the 60 | // impact on users of device-client, and keep some semblance of API 61 | // stability, despite having paginated the coins table. 62 | features.coins = features.coins.concat(tail.table); 63 | } 64 | } 65 | 66 | features.coins.forEach((coin) => { 67 | CoinType.fromFeatureCoin(coin); 68 | }); 69 | 70 | let coin_list = CoinType.getList(); 71 | features.coin_metadata = coin_list.map((coin:CoinType) => coin.toFeatureCoinMetadata()); 72 | } 73 | 74 | features.version = `v${features.major_version}.${features.minor_version}.${features.patch_version}`; 75 | 76 | if (!features.model || features.model === "Unknown") { 77 | features.model = 'K1-14AM'; 78 | } 79 | 80 | let availableVersions = [] 81 | for (var i = 0; i < FIRMWARE_METADATA_FILE.length; i++) { 82 | if (FIRMWARE_METADATA_FILE[i].isBootloaderUpdater === false) { 83 | availableVersions.push(FIRMWARE_METADATA_FILE[i].version); 84 | } 85 | } 86 | features.available_firmware_versions = availableVersions; 87 | 88 | let bootloaderHash: string = features.bootloader_mode ? '' : features.bootloader_hash.toHex(); 89 | features.bootloaderInfo = _.find(OFFICIAL_BOOTLOADER_HASHES, {hash: bootloaderHash}); 90 | 91 | if (!this._promise || !this.resolver) { 92 | if (features.deviceCapabilities) { 93 | this._promise = Promise.resolve(new Features(features)); 94 | } else { 95 | this._promise = Promise.reject('Unknown device or version'); 96 | } 97 | } else { 98 | if (features.deviceCapabilities) { 99 | this.resolver(new Features(features)); 100 | } else { 101 | this.rejector('Unknown device or version'); 102 | } 103 | this.resolver = undefined; 104 | this.rejector = undefined; 105 | } 106 | 107 | return this.promise; 108 | } 109 | 110 | public get promise(): Promise { 111 | if (!this._promise) { 112 | this._promise = new Promise((resolve, reject) => { 113 | this.resolver = resolve; 114 | this.rejector = reject; 115 | }); 116 | } 117 | return this._promise; 118 | } 119 | 120 | public clear() { 121 | this._promise = undefined; 122 | this.resolver = undefined; 123 | CoinType.clearList(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/actions/firmware-upload-action.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import ByteBuffer = require('bytebuffer'); 3 | import {DeviceClient} from "../device-client"; 4 | import {DeviceMessageHelper} from "../device-message-helper"; 5 | import FirmwareUpload = DeviceMessages.FirmwareUpload; 6 | import FirmwareErase = DeviceMessages.FirmwareErase; 7 | import {Features} from "../global/features"; 8 | import * as Bitcore from "bitcore-lib"; 9 | import concat = require("concat-stream"); 10 | 11 | const FIRMWARE_METADATA_FILE: Array = require('../../dist/firmware.json'); 12 | 13 | export class FirmwareUploadAction { 14 | private static firmwareFileMetaData: FirmwareFileMetadata; 15 | 16 | private static client: DeviceClient; 17 | private static payload: ArrayBuffer; 18 | 19 | public static operation(client: DeviceClient, firmwareId: string): Promise { 20 | let modelNumber: string; 21 | if (!client.rawFirmwareStreamFactory) { 22 | throw 'firmware stream factory required to upload firmware'; 23 | } 24 | FirmwareUploadAction.client = client; 25 | console.log('starting firmware upload'); 26 | return client.featuresService.promise 27 | .then((features) => { 28 | if (firmwareId === 'bootloaderUpdater') { 29 | FirmwareUploadAction.firmwareFileMetaData = _.find(FIRMWARE_METADATA_FILE, {isBootloaderUpdater: true}); 30 | console.assert(FirmwareUploadAction.firmwareFileMetaData, `Bootloader updater metadata not found`); 31 | } else { 32 | console.assert(features.model, "Device model number not available from the device feature object"); 33 | modelNumber = features.model; 34 | FirmwareUploadAction.firmwareFileMetaData = _.find(FIRMWARE_METADATA_FILE, {isBootloaderUpdater: false, version: firmwareId}); 35 | console.assert(FirmwareUploadAction.firmwareFileMetaData, `${firmwareId} firmware metadata not found for ${modelNumber}`); 36 | } 37 | console.log(`Installing ${FirmwareUploadAction.firmwareFileMetaData.file} version ${FirmwareUploadAction.firmwareFileMetaData.version}`); 38 | return features; 39 | }) 40 | .then(FirmwareUploadAction.checkDeviceInBootloaderMode) 41 | .then((): Promise => { 42 | return new Promise((resolve, reject) => { 43 | client.rawFirmwareStreamFactory(FirmwareUploadAction.firmwareFileMetaData.file) 44 | .on('error', (err: Error) => { 45 | console.error(err); 46 | reject(err); 47 | }) 48 | .pipe(concat((firmwareBuffer) => { 49 | FirmwareUploadAction.payload = firmwareBuffer; 50 | resolve(); 51 | })); 52 | }); 53 | 54 | }) 55 | .then(FirmwareUploadAction.validateFirmwareFileSize) 56 | .then(FirmwareUploadAction.validateFirmwarePayloadDigest) 57 | .then(FirmwareUploadAction.validateFirmwareImageDigest) 58 | .then(FirmwareUploadAction.verifyManufacturerPrefixInFirmwareImage) 59 | .then(FirmwareUploadAction.eraseFirmware) 60 | .then(FirmwareUploadAction.sendFirmwareToDevice); 61 | } 62 | 63 | private static checkDeviceInBootloaderMode(features: Features): Promise { 64 | console.log('check for device in bootloader mode'); 65 | if (!features.bootloaderMode) { 66 | return Promise.reject('Device must be in bootloader mode'); 67 | } else { 68 | return Promise.resolve(FirmwareUploadAction.firmwareFileMetaData.file); 69 | } 70 | } 71 | 72 | private static validateFirmwareFileSize(): Promise { 73 | if (FirmwareUploadAction.payload.byteLength !== FirmwareUploadAction.firmwareFileMetaData.size) { 74 | return Promise.reject( 75 | `Size of firmware file (${FirmwareUploadAction.payload.byteLength}) doesn't match the expected size of ${FirmwareUploadAction.firmwareFileMetaData.size}`); 76 | } else { 77 | return Promise.resolve(); 78 | } 79 | } 80 | 81 | private static checkHash(hash, hashName, expectedHash): Promise < void > { 82 | var hexHash = ByteBuffer.wrap(hash).toHex(); 83 | console.log('verifying %s: expecting %s', hashName, expectedHash); 84 | console.log(hashName + ":", hexHash); 85 | if (hexHash !== expectedHash) { 86 | return Promise.reject(`Hash ${hashName} doesn't match expected value`); 87 | } else { 88 | return Promise.resolve(); 89 | } 90 | } 91 | 92 | private static validateFirmwarePayloadDigest(): Promise { 93 | return FirmwareUploadAction.checkHash( 94 | Bitcore.crypto.Hash.sha256(Buffer.from(FirmwareUploadAction.payload.slice(256))), 95 | "firmware payload digest", 96 | FirmwareUploadAction.firmwareFileMetaData.trezorDigest 97 | ); 98 | } 99 | 100 | private static validateFirmwareImageDigest(): Promise { 101 | return FirmwareUploadAction.checkHash( 102 | Bitcore.crypto.Hash.sha256(Buffer.from(FirmwareUploadAction.payload)), 103 | "firmware file digest", 104 | FirmwareUploadAction.firmwareFileMetaData.digest 105 | ); 106 | } 107 | 108 | private static verifyManufacturerPrefixInFirmwareImage(): Promise { 109 | console.log('verifying manufacturers prefix in firmware file'); 110 | 111 | var firmwareManufacturerTag = ByteBuffer 112 | .wrap(FirmwareUploadAction.payload.slice(0, 4)) 113 | .toString('utf8'); 114 | if (firmwareManufacturerTag === 'KPKY') { 115 | return Promise.resolve(); 116 | } else { 117 | return Promise.reject( 118 | 'Firmware image is from an unknown manufacturer. Unable to upload to the device.'); 119 | } 120 | } 121 | 122 | private static sendFirmwareToDevice(): Promise { 123 | console.log('sending firmware to device'); 124 | 125 | var message: FirmwareUpload = DeviceMessageHelper.factory('FirmwareUpload'); 126 | message.setPayload(ByteBuffer.wrap(FirmwareUploadAction.payload)); 127 | message.setPayloadHash( 128 | ByteBuffer.fromHex(FirmwareUploadAction.firmwareFileMetaData.digest)); 129 | 130 | return FirmwareUploadAction.client.writeToDevice(message); 131 | } 132 | 133 | private static eraseFirmware() { 134 | console.log('erasing firmware'); 135 | var message: FirmwareErase = DeviceMessageHelper.factory('FirmwareErase'); 136 | return FirmwareUploadAction.client.writeToDevice(message); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/stateful-device-messenger.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | import {DeviceMessageHelper} from "./device-message-helper"; 4 | import {DeviceMessageStates} from "./device-message-states"; 5 | import {MessageDirection, MessageSender, MessageState, MessageStates} from "./message-states"; 6 | import EventEmitter = require('events'); 7 | import {ReflectableProtoBufModel} from "./global/message-handler"; 8 | 9 | interface WriteRequestInProgress extends MessageState { 10 | resolve?: (any) => any; 11 | reject?: (any) => any; 12 | } 13 | 14 | export class StatefulDeviceMessenger extends EventEmitter { 15 | public static UNEXPECTED_MESSAGE_EVENT = 'unexpected-message'; 16 | 17 | private writeRequestInProgress: Array = []; 18 | private pendingMessageQueue: Array = []; 19 | private isDisabled: boolean = false; 20 | private cancelInitiated: boolean = false; 21 | 22 | constructor(private transport) { 23 | super(); 24 | } 25 | 26 | /*** 27 | * Send a message the device 28 | * @param message 29 | * @returns {Promise} 30 | */ 31 | public send(message: ReflectableProtoBufModel): Promise { 32 | //TODO Don't send messages when the device is in the wrong mode 33 | if (this.isDisabled) { 34 | return Promise.reject("failed state"); 35 | } 36 | var messageType = message.$type.name; 37 | var state = DeviceMessageStates.getHostMessageState(messageType); 38 | 39 | if (!state && !MessageStates.getMessageState(MessageSender.host, MessageDirection.response, messageType)) { 40 | throw(`Unknown message: ${messageType}`); 41 | } 42 | 43 | if (this.writeRequestInProgress.length) { 44 | var lastRequest = _.last(this.writeRequestInProgress); 45 | if (lastRequest.resolveMessage === messageType) { 46 | this.writeRequestInProgress.pop(); 47 | } else if (messageType === DeviceMessageStates.Cancel) { 48 | _.each(this.writeRequestInProgress, (request: WriteRequestInProgress) => { 49 | request.reject && request.reject(`${request.messageName} cancelled`); 50 | }); 51 | this.writeRequestInProgress.length = 0; 52 | this.cancelPendingRequests(); 53 | } else if (!DeviceMessageStates.isInterstitialMessage(lastRequest, messageType)) { 54 | return this.enqueueMessage(message); 55 | } 56 | } 57 | 58 | return new Promise((resolve, reject) => { 59 | if (state && state.resolveMessage) { 60 | var requestInProgress = (_.extend({ 61 | resolve: resolve, 62 | reject: reject 63 | }, state)); 64 | this.writeRequestInProgress.push(requestInProgress); 65 | } else { 66 | resolve(); 67 | } 68 | 69 | console.log('proxy --> device:\n [%s] %s\n WaitFor: %s', 70 | message.$type.name, DeviceMessageHelper.toPrintable(message), 71 | state && state.resolveMessage); 72 | 73 | this.transport.write.call(this.transport, message) 74 | .then(() => { 75 | if (this.writeRequestInProgress.length === 0) { 76 | this.dequeueMessage(); 77 | } 78 | }) 79 | .catch(() => { 80 | console.log('Failed when writing to device'); 81 | this.writeRequestInProgress.length = 0; 82 | this.cancelPendingRequests(); 83 | reject.apply(message); 84 | }); 85 | }); 86 | 87 | } 88 | 89 | /*** 90 | * Process an received message 91 | * @param message 92 | * returns true when a valid message is received 93 | */ 94 | public receive(message): boolean { 95 | var messageType = message.$type.name; 96 | var hydratedMessage = DeviceMessageHelper.hydrate(message); 97 | if (!this.isDisabled) { 98 | if (this.writeRequestInProgress.length && !this.cancelInitiated) { 99 | var writeRequest = _.last(this.writeRequestInProgress); 100 | 101 | if (messageType === DeviceMessageStates.TxRequest) { 102 | messageType += '_' + hydratedMessage.request_type; 103 | } 104 | 105 | if (writeRequest.resolveMessage === messageType) { 106 | // Got the expected response 107 | this.writeRequestInProgress.pop(); 108 | writeRequest.resolve(hydratedMessage); 109 | this.dequeueMessage(); 110 | } else if ( 111 | DeviceMessageStates.isInterstitialMessage(writeRequest, messageType)) { 112 | // Got an interstitial message 113 | this.writeRequestInProgress.push( 114 | DeviceMessageStates.getDeviceMessageState(message.$type.name)); 115 | return true; 116 | } else if (writeRequest.rejectMessage === messageType) { 117 | // Got the failure response 118 | this.writeRequestInProgress.pop(); 119 | writeRequest.reject(hydratedMessage); 120 | } else { 121 | this.cancelPendingRequests(); 122 | this.isDisabled = true; 123 | this.emit(StatefulDeviceMessenger.UNEXPECTED_MESSAGE_EVENT, { 124 | message: messageType 125 | }); 126 | } 127 | } else if (messageType === 'Failure') { 128 | // Unexpected failure is handled elsewhere, so don't put it in the error log 129 | } else { 130 | // Not expecting a message 131 | console.error('no incoming messages expected. got:', messageType); 132 | this.isDisabled = true; 133 | this.emit(StatefulDeviceMessenger.UNEXPECTED_MESSAGE_EVENT, { 134 | message: messageType 135 | }); 136 | 137 | } 138 | } 139 | return !this.isDisabled; 140 | } 141 | 142 | private cancelPendingRequests() { 143 | this.cancelInitiated = true; 144 | _.each(this.pendingMessageQueue, (pendingMessage) => { 145 | pendingMessage.reject( 146 | `${pendingMessage.message.$type.name} not sent due to Cancel request` 147 | ); 148 | }); 149 | this.pendingMessageQueue.length = 0; 150 | this.cancelInitiated = false; 151 | }; 152 | 153 | private enqueueMessage(message): Promise { 154 | var resolver, rejector; 155 | 156 | var promise = new Promise((resolve, reject) => { 157 | resolver = resolve; 158 | rejector = reject; 159 | }); 160 | 161 | this.pendingMessageQueue.push({ 162 | message: message, 163 | resolve: resolver, 164 | reject: rejector 165 | }); 166 | 167 | return promise; 168 | } 169 | 170 | private dequeueMessage() { 171 | if (this.pendingMessageQueue.length) { 172 | var pendingMessage = this.pendingMessageQueue.shift(); 173 | this.send(pendingMessage.message) 174 | .then(pendingMessage.resolve) 175 | .catch(pendingMessage.reject); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2017 KeepKey, LLC. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 7 | * "Software"), to deal in the Software without restriction, including 8 | * without limitation the rights to use, copy, modify, merge, publish, 9 | * distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to 11 | * the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | let gulp = require('gulp'); 26 | let replaceExtension = require('replace-ext'); 27 | let rename = require('gulp-rename'); 28 | let replace = require('gulp-replace'); 29 | let pbjs = require('gulp-pbjs'); 30 | let jsoncombine = require('gulp-jsoncombine'); 31 | let _ = require('lodash'); 32 | let proto2ts = require('proto2typescript'); 33 | let through = require('through2'); 34 | let hjson = require('gulp-hjson'); 35 | let crypto = require('crypto'); 36 | let bump = require('gulp-bump'); 37 | 38 | const paths = { 39 | distDirectory: 'dist', 40 | deviceProfiles: 'device-profiles/*.hjson', 41 | messagesJs: 'dist/messages.js', 42 | messagesJson: 'dist/messages.json', 43 | firmware: 'misc/*.bin', 44 | typescriptSources: [ 45 | 'src/**/*.ts', '!src/**/*.spec.ts' 46 | ], 47 | versionedFiles: ['package.json', 'README.md'] 48 | }; 49 | 50 | gulp.task('buildDeviceProfiles', function gatherConfigs() { 51 | return gulp.src(paths.deviceProfiles) 52 | .pipe(hjson({to: 'json'})) 53 | .pipe(jsoncombine('device-profiles.json', function (files) { 54 | let profiles = _.map(files, (file) => { 55 | return _.pick(file, ['identity', 'capabilities']); 56 | }); 57 | return new Buffer(JSON.stringify(profiles)); 58 | })) 59 | .pipe(gulp.dest(paths.distDirectory)); 60 | }); 61 | 62 | gulp.task('buildBootloaderProfiles', function gatherConfigs() { 63 | return gulp.src(paths.deviceProfiles) 64 | .pipe(hjson({to: 'json'})) 65 | .pipe(jsoncombine('bootloader-profiles.json', function (files) { 66 | let profiles = _.reduce(files, (acc, file) => { 67 | if (file.hashes) { 68 | acc.push(file.hashes); 69 | } 70 | return acc; 71 | }, []); 72 | return new Buffer(JSON.stringify(_.flatten(profiles))); 73 | })) 74 | .pipe(gulp.dest(paths.distDirectory)); 75 | }); 76 | 77 | gulp.task('bumpPatch', function () { 78 | return gulp.src(paths.versionedFiles) 79 | .pipe(bump({type: 'patch'})) 80 | .pipe(gulp.dest('./')); 81 | }); 82 | 83 | gulp.task('bumpMinor', function () { 84 | return gulp.src(paths.versionedFiles) 85 | .pipe(bump({type: 'minor'})) 86 | .pipe(gulp.dest('./')); 87 | }); 88 | 89 | gulp.task('bumpMajor', function () { 90 | return gulp.src(paths.versionedFiles) 91 | .pipe(bump({type: 'major'})) 92 | .pipe(gulp.dest('./')); 93 | }); 94 | 95 | function extractString(file, cursor) { 96 | let nextChar, str = ''; 97 | nextChar = file.contents.slice(cursor++, cursor).toString(); 98 | 99 | let max = 20; 100 | while (nextChar.charCodeAt(0) && max--) { 101 | str += nextChar; 102 | nextChar = file.contents.slice(cursor++, cursor).toString(); 103 | } 104 | 105 | if (max < 0) { 106 | throw 'version string is too long'; 107 | } 108 | return str; 109 | } 110 | 111 | const MODEL_NUMBERS = { 112 | 'firmware.keepkey.bin': 'K1-14AM', 113 | 'firmware.salt.bin': 'K1-14WL-S', 114 | }; 115 | 116 | function fileMetaData2Json() { 117 | return through.obj(function (file, enc, callback) { 118 | if (file.isStream()) { 119 | this.emit('error', new PluginError(PLUGIN_NAME, 'Streams are not supported!')); 120 | return callback(); 121 | } 122 | 123 | if (file.isBuffer()) { 124 | let modelNumber = MODEL_NUMBERS[file.relative]; 125 | 126 | let versionMarkerLocation = file.contents.indexOf('VERSION'); 127 | if (versionMarkerLocation === -1) { 128 | throw 'Firmware file needs a version tag'; 129 | } 130 | let firmwareVersion = extractString(file, versionMarkerLocation + 7); 131 | 132 | let fileHash = crypto.createHash('sha256'); 133 | fileHash.update(file.contents); 134 | 135 | let fileHashTrezor = crypto.createHash('sha256'); 136 | fileHashTrezor.update(file.contents.slice(256)); 137 | 138 | let isBootloaderUpdater = file.relative === 'blupdater.bin'; 139 | 140 | let metaData = { 141 | file: file.relative, 142 | digest: fileHash.digest('hex'), 143 | trezorDigest: fileHashTrezor.digest('hex'), 144 | size: file.stat.size, 145 | timeStamp: file.stat.mtime, 146 | version: firmwareVersion, 147 | modelNumber: modelNumber, 148 | isBootloaderUpdater: isBootloaderUpdater 149 | }; 150 | 151 | file.path = replaceExtension(file.path, '.json'); 152 | file.contents = new Buffer(JSON.stringify(metaData)); 153 | } 154 | 155 | 156 | callback(null, file); 157 | }); 158 | } 159 | 160 | gulp.task('extractMetadataFromFirmware', function () { 161 | return gulp.src(paths.firmware) 162 | .pipe(fileMetaData2Json()) 163 | .pipe(jsoncombine('firmware.json', function (files) { 164 | return new Buffer(JSON.stringify(_.values(files))); 165 | })) 166 | .pipe(gulp.dest(paths.distDirectory)); 167 | }); 168 | 169 | function setExtensionToJson(path) { 170 | path.extname = '.json'; 171 | } 172 | 173 | function protocolBuffers() { 174 | return gulp.src('node_modules/device-protocol/messages.proto') 175 | .pipe(pbjs()) 176 | .pipe(gulp.dest(paths.distDirectory)); 177 | } 178 | 179 | function extractMessagesJson() { 180 | return gulp.src(paths.messagesJs) 181 | .pipe(replace(/^.*\{([\w\W\n\r]+)\}.*$/, '{$1}')) 182 | .pipe(rename(setExtensionToJson)) 183 | .pipe(gulp.dest(paths.distDirectory)); 184 | } 185 | 186 | function extractMessagesDts(cb) { 187 | return gulp.src(paths.messagesJson) 188 | .pipe(through.obj(function (file, enc, cb) { 189 | let protoJson = JSON.parse(file.contents); 190 | protoJson.package = 'DeviceMessages'; 191 | proto2ts(JSON.stringify(protoJson), { 192 | camelCaseGetSet: true, 193 | properties: true, 194 | underscoreGetSet: false 195 | }, function (err, out) { 196 | file.contents = new Buffer(out); 197 | cb(err, file); 198 | }); 199 | })) 200 | .pipe(replace(/delete/g, 'isDelete')) 201 | .pipe(replace(/null/g, 'isNull')) 202 | .pipe(rename(function (path) { 203 | path.basename += '.d'; 204 | path.extname = '.ts'; 205 | })) 206 | .pipe(gulp.dest(paths.distDirectory)); 207 | } 208 | 209 | 210 | gulp.task('messages.d.ts', gulp.series( 211 | protocolBuffers, 212 | extractMessagesJson, 213 | extractMessagesDts 214 | )); 215 | 216 | function copyDtsToDist() { 217 | return gulp.src('src/**/*.d.ts') 218 | .pipe(gulp.dest(paths.distDirectory)); 219 | } 220 | 221 | gulp.task('pre-tsc', gulp.series( 222 | 'buildDeviceProfiles', 223 | 'buildBootloaderProfiles', 224 | 'extractMetadataFromFirmware', 225 | 'messages.d.ts', 226 | copyDtsToDist 227 | )); 228 | 229 | module.exports = gulp; 230 | -------------------------------------------------------------------------------- /src/device-client.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as _ from 'lodash'; 4 | 5 | import ProtoBufModel = DeviceMessages.ProtoBufModel; 6 | import {MessageHandlerClass} from "./global/message-handler"; 7 | import {WordAckAction} from "./actions/word-ack-action"; 8 | import {CharacterAckAction} from "./actions/character-ack-action"; 9 | import {InitializeAction} from "./actions/initialize-action"; 10 | import {CancelAction} from "./actions/cancel-action"; 11 | import {WipeDeviceAction} from "./actions/wipe-device-action"; 12 | import {ResetDeviceAction} from "./actions/reset-device-action"; 13 | import {RecoverDeviceAction} from "./actions/recover-device-action"; 14 | import {PinMatrixAckAction} from "./actions/pin-matrix-ack-action"; 15 | import {FirmwareUploadAction} from "./actions/firmware-upload-action"; 16 | import {GetAddressAction} from "./actions/get-address-action"; 17 | import {GetPublicKeyAction} from "./actions/get-public-key-action"; 18 | import {SignMessageAction} from "./actions/sign-message-action"; 19 | import {EncryptMessageAction} from "./actions/encrypt-message-action"; 20 | import {EndSessionAction} from "./actions/end-session-action"; 21 | import {ChangePinAction} from "./actions/change-pin-action"; 22 | import {ApplySettingsAction} from "./actions/apply-settings-action"; 23 | import {DeviceMessageHelper} from "./device-message-helper"; 24 | import {StatefulDeviceMessenger} from "./stateful-device-messenger"; 25 | import {EntropyRequestMessageHandler} from "./entropy-request-message-handler"; 26 | import {ButtonRequestMessageHandler} from "./button-request-message-handler"; 27 | import {CipherNodeVectorAction} from "./actions/cipher-node-vector-action"; 28 | import {CipherAccountNameAction} from "./actions/cipher-account-name-action"; 29 | import {PassphraseAckAction} from "./actions/passphrase-ack-action"; 30 | import {Transport} from "./transport"; 31 | import {ApplyPolicyAction} from "./actions/apply-policy-action"; 32 | import * as DeviceClientMethods from "./device-client-methods"; 33 | import {GetEthereumAddressAction} from "./actions/get-ethereum-address-action"; 34 | import {FeaturesService} from "./features-service"; 35 | import {EventEmitter} from "events"; 36 | import {Readable} from "stream"; 37 | 38 | export const KEEPKEY = 'KEEPKEY'; 39 | 40 | export type FirmwareStreamFactory = (model: string) => Readable 41 | 42 | export interface BasicClient { 43 | featuresService: FeaturesService; 44 | writeToDevice: (message) => Promise; 45 | } 46 | 47 | export class DeviceClient extends EventEmitter implements BasicClient { 48 | public static UNEXPECTED_MESSAGE_EVENT = StatefulDeviceMessenger.UNEXPECTED_MESSAGE_EVENT; 49 | 50 | /* imported methods */ 51 | public initialize = _.partial(InitializeAction.operation, this, _); 52 | public cancel = _.partial(CancelAction.operation, this); 53 | public wipeDevice = _.partial(WipeDeviceAction.operation, this); 54 | public resetDevice = 55 | _.partial(ResetDeviceAction.operation, this, _); 56 | public recoveryDevice = 57 | _.partial(RecoverDeviceAction.operation, this, _); 58 | public pinMatrixAck = _.partial(PinMatrixAckAction.operation, this, _); 59 | public wordAck = _.partial(WordAckAction.operation, this, _); 60 | public characterAck = _.partial(CharacterAckAction.operation, this, _); 61 | public firmwareUpload = 62 | _.partial(FirmwareUploadAction.operation, this, _); 63 | public getAddress = 64 | _.partial(GetAddressAction.operation, this, _, _, _, null); 65 | public getEthereumAddress = 66 | _.partial(GetEthereumAddressAction.operation, this, _, _); 67 | public getPublicKey = 68 | (_.partial(GetPublicKeyAction.operation, this, _, _)); 69 | public signMessage = _.partial(SignMessageAction.operation, this, _); 70 | public encryptMessage = _.partial(EncryptMessageAction.operation, this, _); 71 | public endSession = _.partial(EndSessionAction.operation, this); 72 | public changePin = _.partial(ChangePinAction.operation, this, _); 73 | public changeLabel = 74 | _.partial(ApplySettingsAction.operation, this, null, null, _, null); 75 | public enablePassphrase = 76 | _.partial(ApplySettingsAction.operation, this, _, null, null, null); 77 | public changePinTimeout = 78 | _.partial(ApplySettingsAction.operation, this, null, null, null, _); 79 | public encryptNodeVector = 80 | _.partial(CipherNodeVectorAction.operation, this, true, _); 81 | public decryptNodeVector = 82 | (_.partial(CipherNodeVectorAction.operation, this, false, _)); 83 | public encryptAccountName = 84 | _.partial(CipherAccountNameAction.operation, this, true, _, _); 85 | public decryptAccountName = 86 | (_.partial(CipherAccountNameAction.operation, this, false, _, _)); 87 | public sendPassphrase = 88 | (_.partial(PassphraseAckAction.operation, this, _)); 89 | public enablePolicy = 90 | (_.partial(ApplyPolicyAction.operation, this, _, _)); 91 | private _deviceMessenger: StatefulDeviceMessenger; 92 | public get deviceMessenger(): StatefulDeviceMessenger { 93 | if (!this._deviceMessenger) { 94 | this._deviceMessenger = new StatefulDeviceMessenger(this.transport); 95 | this._deviceMessenger.on(StatefulDeviceMessenger.UNEXPECTED_MESSAGE_EVENT, (message: any) => { 96 | this.emit(DeviceClient.UNEXPECTED_MESSAGE_EVENT, message); 97 | }); 98 | } 99 | return this._deviceMessenger; 100 | } 101 | 102 | private _featuresService: FeaturesService; 103 | get featuresService(): FeaturesService { 104 | if (!this._featuresService) { 105 | this._featuresService = new FeaturesService(); 106 | } 107 | return this._featuresService; 108 | } 109 | 110 | private devicePollingInterval: NodeJS.Timer = setInterval(() => { 111 | this.pollDevice(); 112 | }, 0); 113 | 114 | constructor(public transport: Transport, public rawFirmwareStreamFactory?: FirmwareStreamFactory) { 115 | super(); 116 | this.addMessageHandler(ButtonRequestMessageHandler); 117 | this.addMessageHandler(EntropyRequestMessageHandler); 118 | } 119 | 120 | public destroy() { 121 | this.stopPolling(); 122 | this.removeAllListeners(); 123 | console.log("%s Disconnected: %d", this.deviceType, this.transport.deviceId); 124 | } 125 | 126 | public writeToDevice(message): Promise { 127 | return this.deviceMessenger.send(message); 128 | } 129 | 130 | public addMessageHandler(handler: MessageHandlerClass, ...args) { 131 | var handlerInstance = new (Function.prototype.bind.apply(handler, arguments)); 132 | _.each(handler.messageNames, (messageName: string) => { 133 | console.log(`${messageName} message handler added.`); 134 | this.addListener(messageName, 135 | this.messageHandlerWrapper(handlerInstance)); 136 | }); 137 | } 138 | 139 | public removeMessageHandler(handler: MessageHandlerClass) { 140 | _.each(handler.messageNames, (messageName: string) => { 141 | console.log(`${messageName} message handler removed.`); 142 | this.removeAllListeners(messageName); 143 | }); 144 | } 145 | 146 | public stopPolling() { 147 | clearInterval(this.devicePollingInterval); 148 | } 149 | 150 | get deviceType() { 151 | return this.transport.vendorId; 152 | } 153 | 154 | private messageHandlerWrapper(handlerInstance) { 155 | return (...args) => { 156 | var reply = handlerInstance.messageHandler.apply(handlerInstance, args); 157 | if (reply) { 158 | this.writeToDevice(reply); 159 | } 160 | }; 161 | } 162 | 163 | private pollDevice() { 164 | if (!this.transport.deviceInUse) { 165 | this.transport.deviceInUse = true; 166 | this.transport.read() 167 | .then((message: any) => { 168 | this.transport.deviceInUse = false; 169 | 170 | var hydratedMessage = DeviceMessageHelper.hydrate(message); 171 | 172 | console.log('device --> proxy: [%s]\n', message.$type.name, 173 | JSON.stringify(hydratedMessage, DeviceMessageHelper.buffer2Hex, 4)); 174 | if (message) { 175 | if (this.deviceMessenger.receive.call(this.deviceMessenger, message)) { 176 | this.emit(hydratedMessage.typeName, hydratedMessage); 177 | if (hydratedMessage.request_type) { 178 | this.emit(`${hydratedMessage.typeName}_${hydratedMessage.request_type}`, hydratedMessage); 179 | } 180 | } 181 | } 182 | }) 183 | .catch((err) => { 184 | //TODO Send this to the UI 185 | console.error('caught in client:', err); 186 | setTimeout(() => { 187 | this.transport.deviceInUse = false; 188 | }); 189 | }); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/global/coin-type.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as ByteBuffer from'bytebuffer'; 3 | import {BigNumber} from "bignumber.js"; 4 | import Long = require('long'); 5 | 6 | import {CoinName} from "./coin-name"; 7 | import {IFeatureCoin} from "./features"; 8 | 9 | export interface CoinTypeConfiguration { 10 | name: string, 11 | addressFormat: string; 12 | dust: number | string; 13 | defaultDecimals?: number; 14 | exchangeForbidden?: boolean; 15 | } 16 | 17 | //TODO Addresses should be validated using the address validation checks from the core client. Using regexp allows checksum errors. 18 | //TODO There should be on cononical coin list that comes from the device. This class should go away. 19 | 20 | const ASSUMED_TX_SIZE = 182; 21 | const BITCOIN_DUST_RELAY_FEE = 3000; // From bitcoin source code 22 | const LITECOIN_DUST_RELAY_FEE = 100000; // https://github.com/litecoin-project/litecoin/blob/master/src/policy/policy.h#L48 23 | const DASH_MIN_RELAY_TX_FEE = 10000; // https://github.com/dashpay/dash/blob/master/src/wallet/wallet.h#L57 24 | 25 | const ETHEREUM_ADDRESS_FORMAT = "^(0x)?[0-9a-fA-F]{40}$"; 26 | 27 | export class CoinType { 28 | private static instances: Array = []; 29 | 30 | private static newDustCalculation(dustRelayFee: number | BigNumber | string): string { 31 | return new BigNumber(dustRelayFee).div(1000).times(ASSUMED_TX_SIZE).decimalPlaces(0, BigNumber.ROUND_UP).toString(); 32 | } 33 | 34 | private static oldDustCalculation(minRelayTxFee: number | BigNumber | string): string { 35 | return new BigNumber(minRelayTxFee).div(1000).times(3).times(ASSUMED_TX_SIZE).decimalPlaces(0, BigNumber.ROUND_UP).toString(); 36 | } 37 | 38 | public static get(type: CoinName): CoinType { 39 | return _.find(CoinType.instances, {name: CoinName[type]}); 40 | } 41 | 42 | public static getByName(name: string): CoinType { 43 | return _.find(CoinType.instances, {name: name}); 44 | } 45 | 46 | public static getBySymbol(symbol: string): CoinType { 47 | var upperSymbol = symbol.toUpperCase(); 48 | return _.find(CoinType.instances, {symbol: upperSymbol}); 49 | } 50 | 51 | public static getList(): Array { 52 | return CoinType.instances; 53 | } 54 | 55 | public static clearList() { 56 | CoinType.instances.length = 0; 57 | } 58 | 59 | public static fromFeatureCoin(coin: IFeatureCoin) { 60 | let config: CoinTypeConfiguration = _.find(CoinType.config, {name: coin.coin_name}); 61 | if (!config) { 62 | console.warn(`Coin ${coin.coin_name} was skipped. It doesn't have a configuration defined.`) 63 | } else if (!CoinType.get(CoinName[coin.coin_name])) { 64 | let instance = new CoinType(config); 65 | 66 | instance.isToken = false; 67 | instance.symbol = coin.coin_shortcut; 68 | instance.decimals = coin.decimals || config.defaultDecimals || 0; 69 | instance.coinTypeCode = (coin.bip44_account_path < 0x80000000) ? 70 | '' + coin.bip44_account_path : '' + (coin.bip44_account_path - 0x80000000) + '\''; 71 | instance.amountParameters = { 72 | DECIMAL_PLACES: instance.decimals, 73 | EXPONENTIAL_AT: [-(instance.decimals + 1), 40] 74 | }; 75 | 76 | instance.pubkeyhash = coin.address_type; 77 | instance.scripthash = coin.address_type_p2sh; 78 | 79 | if (!!coin.contract_address) { 80 | // it is an ERC20 token 81 | instance.isToken = true; 82 | instance.contractAddressString = "0x"+coin.contract_address.toHex(); 83 | // v6.0.0 firmware removed gas_limit from the coin table, but the 84 | // KeepKey client still needs it: 85 | coin.gas_limit = coin.gas_limit || ByteBuffer.fromHex("000000000000000000000000000000000000000000000000000000000001e848"); 86 | instance.gasLimitFromBuffer = coin.gas_limit; 87 | } 88 | 89 | CoinType.instances.push(instance); 90 | 91 | return instance; 92 | } 93 | } 94 | 95 | // expose configuration values 96 | public get name(): string { 97 | return this.configuration.name; 98 | } 99 | 100 | public get addessFormat(): string { 101 | return this.configuration.addressFormat; 102 | } 103 | 104 | private _dust: BigNumber; 105 | public get dust(): BigNumber { 106 | if (!this._dust) { 107 | this._dust = this.parseAmount(this.configuration.dust) 108 | } 109 | return this._dust; 110 | } 111 | 112 | // properties that are set from IFeatureCoin 113 | public decimals: number; 114 | public coinTypeCode: string; 115 | public isToken: boolean; 116 | public exchangeForbidden: boolean; 117 | public pubkeyhash: number; 118 | public scripthash: number; 119 | 120 | private _symbol: string; 121 | public get symbol(): string { 122 | return this._symbol; 123 | } 124 | public set symbol(s: string) { 125 | this._symbol = s; 126 | } 127 | 128 | private _contractAddress: ByteBuffer; 129 | public get contractAddress(): ByteBuffer { 130 | return this._contractAddress; 131 | } 132 | public set contractAddressString(a: string) { 133 | if (a.startsWith('0x')) { 134 | this._contractAddress = ByteBuffer.fromHex(a.substr(2)); 135 | } else { 136 | this._contractAddress = ByteBuffer.fromHex(a); 137 | } 138 | } 139 | 140 | private _gasLimit: BigNumber; 141 | public set gasLimitFromBuffer(n: ByteBuffer) { 142 | this._gasLimit = this.number2Big(n); 143 | } 144 | public get gasLimit(): BigNumber { 145 | return this._gasLimit; 146 | } 147 | 148 | private _amountConstructor: typeof BigNumber; 149 | private get amountConstructor(): typeof BigNumber { 150 | console.assert(this._amountConstructor, 'AmountConstructor not set'); 151 | return this._amountConstructor; 152 | } 153 | public set amountParameters(config: Partial) { 154 | this._amountConstructor = BigNumber.clone(config); 155 | } 156 | 157 | // public methods 158 | public parseAmount(amount: number | BigNumber | string | ByteBuffer): BigNumber { 159 | return this.number2Big(amount); 160 | } 161 | 162 | public amountToFloat(amount: Long | string): BigNumber { 163 | return new this.amountConstructor(amount.toString()) 164 | .shiftedBy(-this.decimals); 165 | } 166 | 167 | public floatToAmount(amount: number | BigNumber | string): BigNumber { 168 | return new this.amountConstructor(amount) 169 | .shiftedBy(this.decimals); 170 | } 171 | 172 | public equals(other: any): boolean { 173 | return other instanceof CoinType && this.name === other.name; 174 | } 175 | 176 | public toFeatureCoinMetadata() { 177 | return { 178 | addressFormat: this.addessFormat, 179 | amountParameters: { 180 | DECIMAL_PLACES: this.decimals + 1, 181 | EXPONENTIAL_AT: [ -(this.decimals + 1), 40] 182 | }, 183 | coinTypeCode: this.coinTypeCode, 184 | currencySymbol: this.symbol, 185 | decimals: this.decimals, 186 | dust: this.dust.toString(), 187 | name: this.name, 188 | isToken: this.isToken, 189 | exchangeForbidden: this.exchangeForbidden, 190 | } 191 | } 192 | 193 | private constructor(public configuration: CoinTypeConfiguration) { 194 | this.exchangeForbidden = !!this.configuration.exchangeForbidden; 195 | } 196 | 197 | private number2Big(n: ByteBuffer | Long | number | string | BigNumber): BigNumber { 198 | if (n instanceof ByteBuffer) { 199 | return this.fromBuffer(n); 200 | } else if (n instanceof Long) { 201 | return new this.amountConstructor(n.toString()); 202 | } else { 203 | return new this.amountConstructor(n); 204 | } 205 | } 206 | 207 | private fromBuffer(buffer: ByteBuffer): BigNumber { 208 | return new this.amountConstructor(buffer.toHex() || "00", 16); 209 | } 210 | 211 | // Create all instances 212 | private static config: Array = [{ 213 | name : CoinName[CoinName.Bitcoin], 214 | addressFormat : "(^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$)|(^(bc1)[a-zA-HJ-NP-Z0-9]{25,39}$)", 215 | dust : CoinType.newDustCalculation(BITCOIN_DUST_RELAY_FEE), 216 | defaultDecimals : 8 217 | }, { 218 | name : CoinName[CoinName.Litecoin], 219 | addressFormat : "(^[LM][a-km-zA-HJ-NP-Z1-9]{26,33}$)|(^(ltc1)[a-zA-HJ-NP-Z0-9]{25,39}$)", 220 | dust : CoinType.newDustCalculation(LITECOIN_DUST_RELAY_FEE), 221 | defaultDecimals : 8 222 | }, { 223 | name : CoinName[CoinName.Dogecoin], 224 | addressFormat : "^[DA9][1-9A-HJ-NP-Za-km-z]{33}$", 225 | dust : "100000000", 226 | defaultDecimals : 8 227 | }, { 228 | name : CoinName[CoinName.Ethereum], 229 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 230 | dust : 1, 231 | defaultDecimals : 18 232 | }, { 233 | name : CoinName[CoinName.Dash], 234 | addressFormat : "^[X7][a-km-zA-HJ-NP-Z1-9]{25,34}$", 235 | dust : CoinType.oldDustCalculation(DASH_MIN_RELAY_TX_FEE), 236 | defaultDecimals : 8 237 | }, { 238 | name : CoinName[CoinName.BitcoinCash], 239 | addressFormat : "(^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$)|(^bitcoincash:[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{25,55}$)|(^bitcoincash:[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{25,55}$)", 240 | dust : CoinType.newDustCalculation(BITCOIN_DUST_RELAY_FEE), 241 | defaultDecimals : 8 242 | }, { 243 | name : CoinName[CoinName.BitcoinGold], 244 | addressFormat : "(^[AG][a-km-zA-HJ-NP-Z1-9]{25,34}$)|(^(btg1)[a-zA-HJ-NP-Z0-9]{25,39}$)", 245 | dust : CoinType.newDustCalculation(BITCOIN_DUST_RELAY_FEE), 246 | defaultDecimals : 8 247 | }, { 248 | name : CoinName[CoinName.Zcash], 249 | addressFormat : "^t1[a-km-zA-HJ-NP-Z1-9]{33}$", 250 | dust : CoinType.newDustCalculation(BITCOIN_DUST_RELAY_FEE), 251 | defaultDecimals : 8 252 | }, { 253 | name : CoinName[CoinName.Aragon], 254 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 255 | dust : 1, 256 | defaultDecimals : 18 257 | }, { 258 | name : CoinName[CoinName.Augur], 259 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 260 | dust : 1, 261 | defaultDecimals : 18 262 | }, { 263 | name : CoinName[CoinName.BAT], 264 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 265 | dust : 1, 266 | defaultDecimals : 18 267 | }, { 268 | name : CoinName[CoinName.Civic], 269 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 270 | dust : 1, 271 | defaultDecimals : 8 272 | }, { 273 | name : CoinName[CoinName.district0x], 274 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 275 | dust : 1, 276 | defaultDecimals : 18 277 | }, { 278 | name : CoinName[CoinName.FunFair], 279 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 280 | dust : 1, 281 | defaultDecimals : 8 282 | }, { 283 | name : CoinName[CoinName.Gnosis], 284 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 285 | dust : 1, 286 | defaultDecimals : 18 287 | }, { 288 | name : CoinName[CoinName.Golem], 289 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 290 | dust : 1, 291 | defaultDecimals : 18 292 | }, { 293 | name : CoinName[CoinName.OmiseGo], 294 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 295 | dust : 1, 296 | defaultDecimals : 18 297 | }, { 298 | name : CoinName[CoinName.SALT], 299 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 300 | dust : 1, 301 | defaultDecimals : 8 302 | }, { 303 | name : CoinName[CoinName.Bancor], 304 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 305 | dust : 1, 306 | defaultDecimals : 18 307 | }, { 308 | name : CoinName[CoinName.SingularDTV], 309 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 310 | dust : 1, 311 | defaultDecimals : 0 312 | }, { 313 | name : CoinName[CoinName.ICONOMI], 314 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 315 | dust : 1, 316 | defaultDecimals : 18 317 | }, { 318 | name : CoinName[CoinName.DigixDAO], 319 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 320 | dust : 1, 321 | defaultDecimals : 9 322 | }, { 323 | name : CoinName[CoinName.Melon], 324 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 325 | dust : 1, 326 | defaultDecimals : 18 327 | }, { 328 | name : CoinName[CoinName.SwarmCity], 329 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 330 | dust : 1, 331 | defaultDecimals : 18 332 | }, { 333 | name : CoinName[CoinName.Wings], 334 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 335 | dust : 1, 336 | defaultDecimals : 18 337 | }, { 338 | name : CoinName[CoinName.WeTrust], 339 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 340 | dust : 1, 341 | defaultDecimals : 6 342 | }, { 343 | name : CoinName[CoinName.iExec], 344 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 345 | dust : 1, 346 | defaultDecimals : 9 347 | }, { 348 | name : CoinName[CoinName.Matchpool], 349 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 350 | dust : 1, 351 | defaultDecimals : 3, 352 | exchangeForbidden: true, // Disabled for exchange because expected withdrawal amount calculation differs from SS calculation 353 | }, { 354 | name : CoinName[CoinName.Status], 355 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 356 | dust : 1, 357 | defaultDecimals : 18 358 | }, { 359 | name : CoinName[CoinName.Numeraire], 360 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 361 | dust : 1, 362 | defaultDecimals : 18 363 | }, { 364 | name : CoinName[CoinName.Edgeless], 365 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 366 | dust : 1, 367 | defaultDecimals : 0, 368 | exchangeForbidden: true, // Disabled for exchange because expected withdrawal amount calculation differs from SS calculation 369 | }, { 370 | name : CoinName[CoinName.Metal], 371 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 372 | dust : 1, 373 | defaultDecimals : 8 374 | }, { 375 | name : CoinName[CoinName.TenX], 376 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 377 | dust : 1, 378 | defaultDecimals : 18 379 | }, { 380 | name : "Qtum ICO Token", 381 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 382 | dust : 1, 383 | defaultDecimals : 18 384 | }, { 385 | name : "0x", 386 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 387 | dust : 1, 388 | defaultDecimals : 18 389 | }, { 390 | name : CoinName[CoinName.FirstBlood], 391 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 392 | dust : 1, 393 | defaultDecimals : 18 394 | }, { 395 | name : CoinName[CoinName.RCN], 396 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 397 | dust : 1, 398 | defaultDecimals : 18 399 | }, { 400 | name : CoinName[CoinName.Storj], 401 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 402 | dust : 1, 403 | defaultDecimals : 8 404 | }, { 405 | name : CoinName[CoinName.BinanceCoin], 406 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 407 | dust : 1, 408 | defaultDecimals : 8 409 | }, { 410 | name : CoinName[CoinName.Tether], 411 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 412 | dust : 1, 413 | defaultDecimals : 6 414 | }, { 415 | name : CoinName[CoinName.PolyMath], 416 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 417 | dust : 1, 418 | defaultDecimals : 8 419 | }, { 420 | name : CoinName[CoinName.Zilliqa], 421 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 422 | dust : 1, 423 | defaultDecimals : 12 424 | }, { 425 | name : CoinName[CoinName.Decentraland], 426 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 427 | dust : 1, 428 | defaultDecimals : 18 429 | }, { 430 | name : "0xBitcoin", 431 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 432 | dust : 1, 433 | defaultDecimals : 8 434 | }, { 435 | name : CoinName[CoinName.Gifto], 436 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 437 | dust : 1, 438 | defaultDecimals : 5 439 | }, { 440 | name : CoinName[CoinName.IOSToken], 441 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 442 | dust : 1, 443 | defaultDecimals : 18 444 | }, { 445 | name : CoinName[CoinName.Aelf], 446 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 447 | dust : 1, 448 | defaultDecimals : 18 449 | }, { 450 | name : CoinName[CoinName.TrueUSD], 451 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 452 | dust : 1, 453 | defaultDecimals : 18 454 | }, { 455 | name : CoinName[CoinName.Aeternity], 456 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 457 | dust : 1, 458 | defaultDecimals : 18 459 | }, { 460 | name : CoinName[CoinName.Maker], 461 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 462 | dust : 1, 463 | defaultDecimals : 18 464 | }, { 465 | name : CoinName[CoinName.Dai], 466 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 467 | dust : 1, 468 | defaultDecimals : 18 469 | }, { 470 | name : CoinName[CoinName.SpankChain], 471 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 472 | dust : 1, 473 | defaultDecimals : 18 474 | }, { 475 | name : CoinName[CoinName.CyberMiles], 476 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 477 | dust : 1, 478 | defaultDecimals : 18 479 | }, { 480 | name : "Crypto.com", 481 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 482 | dust : 1, 483 | defaultDecimals : 8 484 | }, { 485 | name : CoinName[CoinName.Populous], 486 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 487 | dust : 1, 488 | defaultDecimals : 8 489 | }, { 490 | name : CoinName[CoinName.ODEM], 491 | addressFormat : ETHEREUM_ADDRESS_FORMAT, 492 | dust : 1, 493 | defaultDecimals : 18 494 | }]; 495 | } 496 | 497 | -------------------------------------------------------------------------------- /src/message-states.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | export type MessageName = string; 4 | 5 | export enum DeviceExecutionMode { 6 | running, bootLoader, debug 7 | } 8 | 9 | export enum MessageDirection { 10 | request, 11 | response 12 | } 13 | 14 | export enum MessageSender { 15 | host, 16 | device 17 | } 18 | 19 | export interface MessageState { 20 | messageName: MessageName; 21 | validMode: DeviceExecutionMode; 22 | sender: MessageSender; 23 | messageType: MessageDirection; 24 | userInteractionRequired: boolean; 25 | resolveMessage?: MessageName; 26 | rejectMessage?: MessageName; 27 | interstitialMessages?: Array; 28 | } 29 | 30 | export class MessageStates { 31 | 32 | private static states: Array = []; 33 | 34 | public static getMessageState(sender: MessageSender, direction: MessageDirection, name: MessageName): MessageState { 35 | return _.find(MessageStates.states, { 36 | messageName: name, 37 | sender: sender, 38 | messageType: direction 39 | }); 40 | } 41 | 42 | public static register(state: MessageState) { 43 | MessageStates.states.push(state); 44 | } 45 | } 46 | 47 | MessageStates.register({ 48 | messageName: "Initialize", 49 | validMode: DeviceExecutionMode.running, 50 | sender: MessageSender.host, 51 | messageType: MessageDirection.request, 52 | resolveMessage: "Features", 53 | userInteractionRequired: false 54 | }); 55 | MessageStates.register({ 56 | messageName: "GetFeatures", 57 | validMode: DeviceExecutionMode.running, 58 | sender: MessageSender.host, 59 | messageType: MessageDirection.request, 60 | resolveMessage: "Features", 61 | userInteractionRequired: false 62 | }); 63 | MessageStates.register({ 64 | messageName: "Features", 65 | validMode: DeviceExecutionMode.running, 66 | sender: MessageSender.device, 67 | messageType: MessageDirection.response, 68 | userInteractionRequired: false 69 | }); 70 | MessageStates.register({ 71 | messageName: "GetCoinTable", 72 | validMode: DeviceExecutionMode.running, 73 | sender: MessageSender.host, 74 | messageType: MessageDirection.request, 75 | resolveMessage: "CoinTable", 76 | userInteractionRequired: false 77 | }); 78 | MessageStates.register({ 79 | messageName: "CoinTable", 80 | validMode: DeviceExecutionMode.running, 81 | sender: MessageSender.device, 82 | messageType: MessageDirection.response, 83 | userInteractionRequired: false 84 | }) 85 | MessageStates.register({ 86 | messageName: "ClearSession", 87 | validMode: DeviceExecutionMode.running, 88 | sender: MessageSender.host, 89 | messageType: MessageDirection.request, 90 | resolveMessage: "Success", 91 | userInteractionRequired: false 92 | }); 93 | MessageStates.register({ 94 | messageName: "ApplySettings", 95 | validMode: DeviceExecutionMode.running, 96 | sender: MessageSender.host, 97 | messageType: MessageDirection.request, 98 | resolveMessage: "Success", 99 | rejectMessage: "Failure", 100 | interstitialMessages: [ 101 | "ButtonRequest", 102 | "PinMatrixRequest" 103 | ], 104 | userInteractionRequired: true 105 | }); 106 | MessageStates.register({ 107 | messageName: "ChangePin", 108 | validMode: DeviceExecutionMode.running, 109 | sender: MessageSender.host, 110 | messageType: MessageDirection.request, 111 | resolveMessage: "Success", 112 | rejectMessage: "Failure", 113 | interstitialMessages: [ 114 | "ButtonRequest", 115 | "PinMatrixRequest" 116 | ], 117 | userInteractionRequired: true 118 | }); 119 | MessageStates.register({ 120 | messageName: "Ping", 121 | validMode: DeviceExecutionMode.running, 122 | sender: MessageSender.host, 123 | messageType: MessageDirection.request, 124 | resolveMessage: "Success", 125 | userInteractionRequired: false 126 | }); 127 | MessageStates.register({ 128 | messageName: "Success", 129 | validMode: DeviceExecutionMode.running, 130 | sender: MessageSender.device, 131 | messageType: MessageDirection.response, 132 | userInteractionRequired: false 133 | }); 134 | MessageStates.register({ 135 | messageName: "Failure", 136 | validMode: DeviceExecutionMode.running, 137 | sender: MessageSender.device, 138 | messageType: MessageDirection.response, 139 | userInteractionRequired: false 140 | }); 141 | MessageStates.register({ 142 | messageName: "ButtonRequest", 143 | validMode: DeviceExecutionMode.running, 144 | sender: MessageSender.device, 145 | messageType: MessageDirection.request, 146 | resolveMessage: "ButtonAck", 147 | userInteractionRequired: false 148 | }); 149 | MessageStates.register({ 150 | messageName: "ButtonAck", 151 | validMode: DeviceExecutionMode.running, 152 | sender: MessageSender.host, 153 | messageType: MessageDirection.response, 154 | userInteractionRequired: false 155 | }); 156 | MessageStates.register({ 157 | messageName: "PinMatrixRequest", 158 | validMode: DeviceExecutionMode.running, 159 | sender: MessageSender.device, 160 | messageType: MessageDirection.request, 161 | resolveMessage: "PinMatrixAck", 162 | userInteractionRequired: true 163 | }); 164 | MessageStates.register({ 165 | messageName: "PinMatrixAck", 166 | validMode: DeviceExecutionMode.running, 167 | sender: MessageSender.host, 168 | messageType: MessageDirection.response, 169 | userInteractionRequired: false 170 | }); 171 | MessageStates.register({ 172 | messageName: "Cancel", 173 | validMode: DeviceExecutionMode.running, 174 | sender: MessageSender.host, 175 | messageType: MessageDirection.request, 176 | userInteractionRequired: false, 177 | resolveMessage: "Failure" 178 | }); 179 | MessageStates.register({ 180 | messageName: "PassphraseRequest", 181 | validMode: DeviceExecutionMode.running, 182 | sender: MessageSender.device, 183 | messageType: MessageDirection.request, 184 | resolveMessage: "PassphraseAck", 185 | userInteractionRequired: true 186 | }); 187 | MessageStates.register({ 188 | messageName: "PassphraseAck", 189 | validMode: DeviceExecutionMode.running, 190 | sender: MessageSender.host, 191 | messageType: MessageDirection.response, 192 | userInteractionRequired: false 193 | }); 194 | MessageStates.register({ 195 | messageName: "GetEntropy", 196 | validMode: DeviceExecutionMode.running, 197 | sender: MessageSender.host, 198 | messageType: MessageDirection.request, 199 | resolveMessage: "Entropy", 200 | rejectMessage: "Failure", 201 | interstitialMessages: [ 202 | "ButtonRequest" 203 | ], 204 | userInteractionRequired: false 205 | }); 206 | MessageStates.register({ 207 | messageName: "Entropy", 208 | validMode: DeviceExecutionMode.running, 209 | sender: MessageSender.device, 210 | messageType: MessageDirection.response, 211 | userInteractionRequired: false 212 | }); 213 | MessageStates.register({ 214 | messageName: "GetPublicKey", 215 | validMode: DeviceExecutionMode.running, 216 | sender: MessageSender.host, 217 | messageType: MessageDirection.request, 218 | resolveMessage: "PublicKey", 219 | rejectMessage: "Failure", 220 | interstitialMessages: [ 221 | "PassphraseRequest", 222 | "PinMatrixRequest" 223 | ], 224 | userInteractionRequired: true 225 | }); 226 | MessageStates.register({ 227 | messageName: "PublicKey", 228 | validMode: DeviceExecutionMode.running, 229 | sender: MessageSender.device, 230 | messageType: MessageDirection.response, 231 | userInteractionRequired: false 232 | }); 233 | MessageStates.register({ 234 | messageName: "GetAddress", 235 | validMode: DeviceExecutionMode.running, 236 | sender: MessageSender.host, 237 | messageType: MessageDirection.request, 238 | resolveMessage: "Address", 239 | rejectMessage: "Failure", 240 | interstitialMessages: [ 241 | "ButtonRequest", 242 | "PinMatrixRequest", 243 | "PassphraseRequest" 244 | ], 245 | userInteractionRequired: true 246 | }); 247 | MessageStates.register({ 248 | messageName: "EthereumGetAddress", 249 | validMode: DeviceExecutionMode.running, 250 | sender: MessageSender.host, 251 | messageType: MessageDirection.request, 252 | resolveMessage: "EthereumAddress", 253 | rejectMessage: "Failure", 254 | interstitialMessages: [ 255 | "ButtonRequest", 256 | "PinMatrixRequest", 257 | "PassphraseRequest" 258 | ], 259 | userInteractionRequired: true 260 | }); 261 | MessageStates.register({ 262 | messageName: "Address", 263 | validMode: DeviceExecutionMode.running, 264 | sender: MessageSender.device, 265 | messageType: MessageDirection.response, 266 | userInteractionRequired: false 267 | }); 268 | MessageStates.register({ 269 | messageName: "EthereumAddress", 270 | validMode: DeviceExecutionMode.running, 271 | sender: MessageSender.device, 272 | messageType: MessageDirection.response, 273 | userInteractionRequired: false 274 | }); 275 | MessageStates.register({ 276 | messageName: "WipeDevice", 277 | validMode: DeviceExecutionMode.running, 278 | sender: MessageSender.host, 279 | messageType: MessageDirection.request, 280 | resolveMessage: "Success", 281 | rejectMessage: "Failure", 282 | interstitialMessages: [ 283 | "ButtonRequest" 284 | ], 285 | userInteractionRequired: false 286 | }); 287 | MessageStates.register({ 288 | messageName: "LoadDevice", 289 | validMode: DeviceExecutionMode.running, 290 | sender: MessageSender.host, 291 | messageType: MessageDirection.request, 292 | resolveMessage: "Success", 293 | rejectMessage: "Failure", 294 | interstitialMessages: [ 295 | "ButtonRequest", 296 | "PassphraseRequest" 297 | ], 298 | userInteractionRequired: true 299 | }); 300 | MessageStates.register({ 301 | messageName: "ResetDevice", 302 | validMode: DeviceExecutionMode.running, 303 | sender: MessageSender.host, 304 | messageType: MessageDirection.request, 305 | resolveMessage: "Success", 306 | rejectMessage: "Failure", 307 | interstitialMessages: [ 308 | "EntropyRequest", 309 | "PinMatrixRequest", 310 | "ButtonRequest", 311 | "PassphraseRequest" 312 | ], 313 | userInteractionRequired: true 314 | }); 315 | MessageStates.register({ 316 | messageName: "EntropyRequest", 317 | validMode: DeviceExecutionMode.running, 318 | sender: MessageSender.device, 319 | messageType: MessageDirection.request, 320 | resolveMessage: "EntropyAck", 321 | userInteractionRequired: false 322 | }); 323 | MessageStates.register({ 324 | messageName: "EntropyAck", 325 | validMode: DeviceExecutionMode.running, 326 | sender: MessageSender.host, 327 | messageType: MessageDirection.response, 328 | userInteractionRequired: false 329 | }); 330 | MessageStates.register({ 331 | messageName: "RecoveryDevice", 332 | validMode: DeviceExecutionMode.running, 333 | sender: MessageSender.host, 334 | messageType: MessageDirection.request, 335 | resolveMessage: "Success", 336 | rejectMessage: "Failure", 337 | interstitialMessages: [ 338 | "WordRequest", 339 | "PinMatrixRequest", 340 | "CharacterRequest", 341 | "ButtonRequest" 342 | ], 343 | userInteractionRequired: true 344 | }); 345 | MessageStates.register({ 346 | messageName: "WordRequest", 347 | validMode: DeviceExecutionMode.running, 348 | sender: MessageSender.device, 349 | messageType: MessageDirection.request, 350 | resolveMessage: "WordAck", 351 | userInteractionRequired: false 352 | }); 353 | MessageStates.register({ 354 | messageName: "WordAck", 355 | validMode: DeviceExecutionMode.running, 356 | sender: MessageSender.host, 357 | messageType: MessageDirection.response, 358 | userInteractionRequired: true 359 | }); 360 | MessageStates.register({ 361 | messageName: "CharacterRequest", 362 | validMode: DeviceExecutionMode.running, 363 | sender: MessageSender.device, 364 | messageType: MessageDirection.request, 365 | resolveMessage: "CharacterAck", 366 | userInteractionRequired: false 367 | }); 368 | MessageStates.register({ 369 | messageName: "CharacterAck", 370 | validMode: DeviceExecutionMode.running, 371 | sender: MessageSender.host, 372 | messageType: MessageDirection.response, 373 | userInteractionRequired: true 374 | }); 375 | MessageStates.register({ 376 | messageName: "SignMessage", 377 | validMode: DeviceExecutionMode.running, 378 | sender: MessageSender.host, 379 | messageType: MessageDirection.request, 380 | resolveMessage: "MessageSignature", 381 | rejectMessage: "Failure", 382 | interstitialMessages: [ 383 | "PinMatrixRequest", 384 | "PassphraseRequest", 385 | "ButtonRequest" 386 | ], 387 | userInteractionRequired: false 388 | }); 389 | MessageStates.register({ 390 | messageName: "VerifyMessage", 391 | validMode: DeviceExecutionMode.running, 392 | sender: MessageSender.host, 393 | messageType: MessageDirection.request, 394 | resolveMessage: "Success", 395 | rejectMessage: "Failure", 396 | interstitialMessages: [ 397 | "PinMatrixRequest", 398 | "PassphraseRequest" 399 | ], 400 | userInteractionRequired: false 401 | }); 402 | MessageStates.register({ 403 | messageName: "MessageSignature", 404 | validMode: DeviceExecutionMode.running, 405 | sender: MessageSender.device, 406 | messageType: MessageDirection.response, 407 | userInteractionRequired: false 408 | }); 409 | MessageStates.register({ 410 | messageName: "EncryptMessage", 411 | validMode: DeviceExecutionMode.running, 412 | sender: MessageSender.host, 413 | messageType: MessageDirection.request, 414 | resolveMessage: "EncryptedMessage", 415 | rejectMessage: "Failure", 416 | interstitialMessages: [ 417 | "PinMatrixRequest", 418 | "PassphraseRequest" 419 | ], 420 | userInteractionRequired: false 421 | }); 422 | MessageStates.register({ 423 | messageName: "EncryptedMessage", 424 | validMode: DeviceExecutionMode.running, 425 | sender: MessageSender.device, 426 | messageType: MessageDirection.response, 427 | userInteractionRequired: false 428 | }); 429 | MessageStates.register({ 430 | messageName: "DecryptMessage", 431 | validMode: DeviceExecutionMode.running, 432 | sender: MessageSender.host, 433 | messageType: MessageDirection.request, 434 | resolveMessage: "Success", 435 | rejectMessage: "Failure", 436 | interstitialMessages: [ 437 | "PinMatrixRequest", 438 | "PassphraseRequest" 439 | ], 440 | userInteractionRequired: false 441 | }); 442 | MessageStates.register({ 443 | messageName: "DecryptedMessage", 444 | validMode: DeviceExecutionMode.running, 445 | sender: MessageSender.device, 446 | messageType: MessageDirection.response, 447 | userInteractionRequired: false 448 | }); 449 | MessageStates.register({ 450 | messageName: "CipherKeyValue", 451 | validMode: DeviceExecutionMode.running, 452 | sender: MessageSender.host, 453 | messageType: MessageDirection.request, 454 | resolveMessage: "CipheredKeyValue", 455 | rejectMessage: "Failure", 456 | interstitialMessages: [ 457 | "PinMatrixRequest", 458 | "PassphraseRequest" 459 | ], 460 | userInteractionRequired: false 461 | }); 462 | MessageStates.register({ 463 | messageName: "CipheredKeyValue", 464 | validMode: DeviceExecutionMode.running, 465 | sender: MessageSender.device, 466 | messageType: MessageDirection.response, 467 | userInteractionRequired: false 468 | }); 469 | MessageStates.register({ 470 | messageName: "EstimateTxSize", 471 | validMode: DeviceExecutionMode.running, 472 | sender: MessageSender.host, 473 | messageType: MessageDirection.request, 474 | resolveMessage: "TxSize", 475 | rejectMessage: "Failure", 476 | userInteractionRequired: false 477 | }); 478 | MessageStates.register({ 479 | messageName: "TxSize", 480 | validMode: DeviceExecutionMode.running, 481 | sender: MessageSender.device, 482 | messageType: MessageDirection.response, 483 | userInteractionRequired: false 484 | }); 485 | MessageStates.register({ 486 | messageName: "SignTx", 487 | validMode: DeviceExecutionMode.running, 488 | sender: MessageSender.host, 489 | messageType: MessageDirection.request, 490 | resolveMessage: "TxRequest_TXFINISHED", 491 | rejectMessage: "Failure", 492 | interstitialMessages: [ 493 | "PassphraseRequest", 494 | "PinMatrixRequest", 495 | "ButtonRequest", 496 | "TxRequest_TXINPUT", 497 | "TxRequest_TXOUTPUT", 498 | "TxRequest_TXMETA" 499 | ], 500 | userInteractionRequired: true 501 | }); 502 | MessageStates.register({ 503 | messageName: "SimpleSignTx", 504 | validMode: DeviceExecutionMode.running, 505 | sender: MessageSender.host, 506 | messageType: MessageDirection.request, 507 | resolveMessage: "TxRequest", 508 | rejectMessage: "Failure", 509 | interstitialMessages: [ 510 | "PassphraseRequest", 511 | "PinMatrixRequest" 512 | ], 513 | userInteractionRequired: true 514 | }); 515 | MessageStates.register({ 516 | messageName: "TxRequest", 517 | validMode: DeviceExecutionMode.running, 518 | sender: MessageSender.device, 519 | messageType: MessageDirection.request, 520 | resolveMessage: "TxAck", 521 | userInteractionRequired: false 522 | }); 523 | MessageStates.register({ 524 | messageName: "TxAck", 525 | validMode: DeviceExecutionMode.running, 526 | sender: MessageSender.host, 527 | messageType: MessageDirection.response, 528 | userInteractionRequired: false 529 | }); 530 | MessageStates.register({ 531 | messageName: "EthereumSignTx", 532 | validMode: DeviceExecutionMode.running, 533 | sender: MessageSender.host, 534 | messageType: MessageDirection.request, 535 | resolveMessage: "EthereumTxRequest", 536 | rejectMessage: "Failure", 537 | interstitialMessages: [ 538 | "PassphraseRequest", 539 | "PinMatrixRequest", 540 | "ButtonRequest" 541 | ], 542 | userInteractionRequired: true 543 | }); 544 | MessageStates.register({ 545 | messageName: "EthereumTxRequest", 546 | validMode: DeviceExecutionMode.running, 547 | sender: MessageSender.device, 548 | messageType: MessageDirection.response, 549 | userInteractionRequired: false 550 | }); 551 | MessageStates.register({ 552 | messageName: "SignIdentity", 553 | validMode: DeviceExecutionMode.running, 554 | sender: MessageSender.host, 555 | messageType: MessageDirection.request, 556 | resolveMessage: "SignedIdentity", 557 | rejectMessage: "Failure", 558 | userInteractionRequired: false 559 | }); 560 | MessageStates.register({ 561 | messageName: "SignedIdentity", 562 | validMode: DeviceExecutionMode.running, 563 | sender: MessageSender.device, 564 | messageType: MessageDirection.response, 565 | userInteractionRequired: false 566 | }); 567 | MessageStates.register({ 568 | messageName: "FirmwareErase", 569 | validMode: DeviceExecutionMode.bootLoader, 570 | sender: MessageSender.host, 571 | messageType: MessageDirection.request, 572 | resolveMessage: "Success", 573 | rejectMessage: "Failure", 574 | interstitialMessages: [ 575 | "ButtonRequest" 576 | ], 577 | userInteractionRequired: false 578 | }); 579 | MessageStates.register({ 580 | messageName: "FirmwareUpload", 581 | validMode: DeviceExecutionMode.bootLoader, 582 | sender: MessageSender.host, 583 | messageType: MessageDirection.request, 584 | resolveMessage: "Success", 585 | rejectMessage: "Failure", 586 | userInteractionRequired: false 587 | }); 588 | MessageStates.register({ 589 | messageName: "ApplyPolicies", 590 | validMode: DeviceExecutionMode.running, 591 | sender: MessageSender.host, 592 | messageType: MessageDirection.request, 593 | resolveMessage: "Success", 594 | rejectMessage: "Failure", 595 | interstitialMessages: [ 596 | "ButtonRequest" 597 | ], 598 | userInteractionRequired: true 599 | }); 600 | MessageStates.register({ 601 | messageName: "DebugLinkDecision", 602 | validMode: DeviceExecutionMode.debug, 603 | sender: MessageSender.host, 604 | messageType: MessageDirection.request, 605 | resolveMessage: "Success", 606 | userInteractionRequired: false 607 | }); 608 | MessageStates.register({ 609 | messageName: "DebugLinkGetState", 610 | validMode: DeviceExecutionMode.debug, 611 | sender: MessageSender.host, 612 | messageType: MessageDirection.request, 613 | resolveMessage: "DebugLinkState", 614 | userInteractionRequired: false 615 | }); 616 | MessageStates.register({ 617 | messageName: "DebugLinkState", 618 | validMode: DeviceExecutionMode.debug, 619 | sender: MessageSender.device, 620 | messageType: MessageDirection.response, 621 | userInteractionRequired: false 622 | }); 623 | MessageStates.register({ 624 | messageName: "DebugLinkStop", 625 | validMode: DeviceExecutionMode.debug, 626 | sender: MessageSender.host, 627 | messageType: MessageDirection.request, 628 | userInteractionRequired: false 629 | }); 630 | MessageStates.register({ 631 | messageName: "DebugLinkLog", 632 | validMode: DeviceExecutionMode.debug, 633 | sender: MessageSender.device, 634 | messageType: MessageDirection.request, 635 | userInteractionRequired: false 636 | }); 637 | MessageStates.register({ 638 | messageName: "DebugLinkFillConfig", 639 | validMode: DeviceExecutionMode.debug, 640 | sender: MessageSender.device, 641 | messageType: MessageDirection.request, 642 | userInteractionRequired: false 643 | }); 644 | --------------------------------------------------------------------------------