From 425ca93aa0141fc18775a89a52d9bdeebb1c093c Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 17 Sep 2019 13:21:18 +0200 Subject: [PATCH] Add Ledger interfaces (#211) --- .eslintignore | 1 + packages/ui-keyring/package.json | 4 + packages/ui-keyring/src/index.ts | 4 +- packages/ui-keyring/src/ledger-polkadot.d.ts | 36 +++++++++ packages/ui-keyring/src/ledger.ts | 22 +++++ .../src/ledgerhq_hw-transport-webusb.d.ts | 1 + packages/ui-settings/src/Settings.ts | 17 +++- yarn.lock | 80 ++++++++++++++++++- 8 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 packages/ui-keyring/src/ledger-polkadot.d.ts create mode 100644 packages/ui-keyring/src/ledger.ts create mode 100644 packages/ui-keyring/src/ledgerhq_hw-transport-webusb.d.ts diff --git a/.eslintignore b/.eslintignore index b6f52714..42fe98a2 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ **/build/* **/coverage/* **/node_modules/* +**/*.d.ts diff --git a/packages/ui-keyring/package.json b/packages/ui-keyring/package.json index 65324dff..76f28372 100644 --- a/packages/ui-keyring/package.json +++ b/packages/ui-keyring/package.json @@ -11,9 +11,13 @@ "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.6.0", + "@ledgerhq/hw-transport-u2f": "^4.70.0", + "@ledgerhq/hw-transport-webusb": "^4.70.0", + "@types/ledgerhq__hw-transport-u2f": "^4.21.1", "@types/mkdirp": "^0.5.2", "@types/store": "^2.0.2", "extensionizer": "^1.0.1", + "ledger-polkadot": "^0.0.3", "mkdirp": "^0.5.1", "store": "^2.0.12", "styled-components": "^4.3.1" diff --git a/packages/ui-keyring/src/index.ts b/packages/ui-keyring/src/index.ts index 437072ab..5b843190 100644 --- a/packages/ui-keyring/src/index.ts +++ b/packages/ui-keyring/src/index.ts @@ -5,11 +5,13 @@ import { assertSingletonPackage } from '@polkadot/util'; import keyring, { Keyring } from './Keyring'; +import { openLedger } from './ledger'; assertSingletonPackage('@polkadot/ui-keyring'); export default keyring; export { - Keyring + Keyring, + openLedger }; diff --git a/packages/ui-keyring/src/ledger-polkadot.d.ts b/packages/ui-keyring/src/ledger-polkadot.d.ts new file mode 100644 index 00000000..02972296 --- /dev/null +++ b/packages/ui-keyring/src/ledger-polkadot.d.ts @@ -0,0 +1,36 @@ +declare module 'ledger-polkadot' { + import Transport from '@ledgerhq/hw-transport'; + + export interface ResponseBase { + error_message: string; + return_code: number; + } + + export interface ReponseAddress extends ResponseBase { + address: string; + pubKey: string; + } + + export interface ResponseVersion extends ResponseBase { + device_locked: boolean; + major: number; + minor: number; + patch: number; + test_mode: boolean; + } + + export interface ResponseSign extends ResponseBase { + signature: string; + } + + declare class LedgerApp { + constructor (transport: Transport, scrambleKey?: string); + + getVersion (): Promise; + getAddress (account: number, change: number, addressIndex: number, requireConfirmation?: boolean): Promise; + signSendChunk (chunkIdx: number, chunkNum: number, chunk: Buffer): Promise; + sign (account: number, change: number, addressIndex: number, message: Uint8Array): Promise; + } + + export default LedgerApp; +} diff --git a/packages/ui-keyring/src/ledger.ts b/packages/ui-keyring/src/ledger.ts new file mode 100644 index 00000000..6bc26e70 --- /dev/null +++ b/packages/ui-keyring/src/ledger.ts @@ -0,0 +1,22 @@ +// Copyright 2017-2019 @polkadot/ui-keyring authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import Transport from '@ledgerhq/hw-transport'; +import LedgerU2F from '@ledgerhq/hw-transport-u2f'; +import LedgerWebUSB from '@ledgerhq/hw-transport-webusb'; +import LedgerApp from 'ledger-polkadot'; + +export async function openLedger (type: 'u2f' | 'webusb'): Promise { + let transport: Transport; + + if (type === 'u2f') { + transport = await LedgerU2F.create(7500); + } else if (type === 'webusb') { + transport = await LedgerWebUSB.create(); + } else { + throw new Error(`Unsupported transport ${type}`); + } + + return new LedgerApp(transport); +} diff --git a/packages/ui-keyring/src/ledgerhq_hw-transport-webusb.d.ts b/packages/ui-keyring/src/ledgerhq_hw-transport-webusb.d.ts new file mode 100644 index 00000000..6dcc0dee --- /dev/null +++ b/packages/ui-keyring/src/ledgerhq_hw-transport-webusb.d.ts @@ -0,0 +1 @@ +declare module '@ledgerhq/hw-transport-webusb'; diff --git a/packages/ui-settings/src/Settings.ts b/packages/ui-settings/src/Settings.ts index 0ec5301c..6ebbd058 100644 --- a/packages/ui-settings/src/Settings.ts +++ b/packages/ui-settings/src/Settings.ts @@ -2,15 +2,21 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import EventEmitter from 'eventemitter3'; import store from 'store'; import { isUndefined } from '@polkadot/util'; import { CRYPTOS, ENDPOINT_DEFAULT, ENDPOINTS, ICON_DEFAULT, ICONS, LANGUAGE_DEFAULT, LANGUAGES, LEDGER_CONN, LEDGER_CONN_DEFAULT, LOCKING_DEFAULT, LOCKING, PREFIX_DEFAULT, PREFIXES, UIMODE_DEFAULT, UIMODES, UITHEME_DEFAULT, UITHEMES } from './defaults'; import { Option, SettingsStruct } from './types'; +type ChangeCallback = (settings: SettingsStruct) => void; +type OnTypes = 'change'; + export class Settings implements SettingsStruct { private _apiUrl: string; + private _emitter: EventEmitter; + private _i18nLang: string; private _icon: string; @@ -28,6 +34,8 @@ export class Settings implements SettingsStruct { public constructor () { const settings = store.get('settings') || {}; + this._emitter = new EventEmitter(); + this._apiUrl = settings.apiUrl || process.env.WS_URL || ENDPOINT_DEFAULT; this._ledgerConn = settings.ledgerConn || LEDGER_CONN_DEFAULT; this._i18nLang = settings.i18nLang || LANGUAGE_DEFAULT; @@ -129,7 +137,14 @@ export class Settings implements SettingsStruct { this._uiMode = settings.uiMode || this._uiMode; this._uiTheme = settings.uiTheme || this._uiTheme; - store.set('settings', this.get()); + const newValues = this.get(); + + store.set('settings', newValues); + this._emitter.emit('change', newValues); + } + + public on (type: OnTypes, cb: ChangeCallback): void { + this._emitter.on(type, cb); } } diff --git a/yarn.lock b/yarn.lock index 73bcba75..35530ad8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -919,7 +919,7 @@ core-js "^2.6.5" regenerator-runtime "^0.13.2" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.6.0": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.6.0": version "7.6.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.0.tgz#4fc1d642a9fd0299754e8b5de62c631cf5568205" integrity sha512-89eSBLJsxNxOERC0Op4vd+0Bqm6wRMqMbFtV3i0/fbaWw/mJ8Q3eBvgX0G4SyrOOLCtbu98HspF8o09MRT+KzQ== @@ -1238,6 +1238,54 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" +"@ledgerhq/devices@^4.70.0": + version "4.70.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-4.70.0.tgz#db335f555851cd575edc9e08c4c7d8b468710d61" + integrity sha512-xgcPls2BJivdG4pK8l4RFBf0CaMUNApP0sb+Izr9OlwbOH9iyLcLL2XC9AJuvoHGO1DUkDrCM2TkXJIx3Ui+Xg== + dependencies: + "@ledgerhq/errors" "^4.70.0" + "@ledgerhq/logs" "^4.70.0" + rxjs "^6.5.3" + +"@ledgerhq/errors@^4.70.0": + version "4.70.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-4.70.0.tgz#3ec196ed8f1211ed2f4e84969c26e9e97a3a4bc0" + integrity sha512-XFFuVGJ34dhkIfqTUGudPvnSx3cRGuZ+oVr8lR4UFuc29TwDVjf1X4GJ5wQU4XUqS4rNe+I+Tq7BK7V1DFX9lQ== + +"@ledgerhq/hw-transport-u2f@^4.70.0": + version "4.70.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-4.70.0.tgz#1f10465d7cab016f787c552d05aa48c6bd235a58" + integrity sha512-ttQtwdg/reTUWUwySwf13+3u3skr6/l3MaTFwxMri9H6sq+EicrcH+8pD74JtZ3gT/GhESskwGpo7QzfaO+YQQ== + dependencies: + "@ledgerhq/errors" "^4.70.0" + "@ledgerhq/hw-transport" "^4.70.0" + "@ledgerhq/logs" "^4.70.0" + u2f-api "0.2.7" + +"@ledgerhq/hw-transport-webusb@^4.70.0": + version "4.70.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-4.70.0.tgz#7fe27d06d6b5f6bd9f1499ed80b66d83a97c8f25" + integrity sha512-HxxSdYTXWLdgFPN51yKp6LKea4HJsE+Y0Y+YIbpD8toOhAz4G3trf8n0nHTagBaDGIWTK5JTXMFO2XTv4Gt4vg== + dependencies: + "@ledgerhq/devices" "^4.70.0" + "@ledgerhq/errors" "^4.70.0" + "@ledgerhq/hw-transport" "^4.70.0" + "@ledgerhq/logs" "^4.70.0" + +"@ledgerhq/hw-transport@^4.35.0", "@ledgerhq/hw-transport@^4.70.0": + version "4.70.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-4.70.0.tgz#2733b83c6d600f8d3c5ffb588d010e40b9d0bcc3" + integrity sha512-kl3TGlpy6iH3byVT+9Df9ObN3Ahi7O6eg8FHAWORO4jGUHROPiI8FYL03DTEvBXG5GqyKN1NfvvcKyC3N2dL2w== + dependencies: + "@ledgerhq/devices" "^4.70.0" + "@ledgerhq/errors" "^4.70.0" + events "^3.0.0" + +"@ledgerhq/logs@^4.70.0": + version "4.70.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-4.70.0.tgz#b5176430ccb5a81becce25fcbbd8afcfa91a4388" + integrity sha512-hyMppU1b2tBDRyKJiV71RnN51Tkkriv/7DHQF9thAmrwEDFmfmEwAQaS2VyeIogir0HD7aDcKg7DnHqgiWe/8g== + "@lerna/add@3.16.2": version "3.16.2" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.16.2.tgz#90ecc1be7051cfcec75496ce122f656295bd6e94" @@ -2376,6 +2424,21 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636" integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A== +"@types/ledgerhq__hw-transport-u2f@^4.21.1": + version "4.21.1" + resolved "https://registry.yarnpkg.com/@types/ledgerhq__hw-transport-u2f/-/ledgerhq__hw-transport-u2f-4.21.1.tgz#8193aa3199cc5ba6cf7f9d880a88f2993a6c6c3b" + integrity sha512-8mVUb2/Oj3VtP/ERpm//HL/JOLxKFZycMeFjKxkRMp3283Vz0slBkeHxSdjxRQjiZi7zRu9amQ8dPXnr1B5nIg== + dependencies: + "@types/ledgerhq__hw-transport" "*" + "@types/node" "*" + +"@types/ledgerhq__hw-transport@*": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@types/ledgerhq__hw-transport/-/ledgerhq__hw-transport-4.21.2.tgz#92eaea9e4669df2c8ec5292b694097d1629b05c1" + integrity sha512-NhJwkdxdsqj/ZTq9KuBYtN+rDYOVYrlR3ByYNfK78iUOIlmXcXFBtfkhBUpX8c7dbE5uJZOjDdM3EC7Kf3Fakg== + dependencies: + "@types/node" "*" + "@types/memoizee@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@types/memoizee/-/memoizee-0.4.3.tgz#f48270d19327c1709620132cf54d598650f98492" @@ -9157,6 +9220,14 @@ lcov-parse@^0.0.10: resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-0.0.10.tgz#1b0b8ff9ac9c7889250582b70b71315d9da6d9a3" integrity sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM= +ledger-polkadot@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/ledger-polkadot/-/ledger-polkadot-0.0.3.tgz#a7d611bad64607c48bc13f23db3dcc339410434d" + integrity sha512-TkT4oXnuSTIraUSa4ERBsK2JGSh6FFh7/d+RYneglud254NzIAhpX1ksPCXjvVRxY09RLGxLFJT018Fa/UqTLQ== + dependencies: + "@babel/runtime" "^7.4.4" + "@ledgerhq/hw-transport" "^4.35.0" + left-pad@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" @@ -12903,7 +12974,7 @@ rxjs@^5.4.3: dependencies: symbol-observable "1.0.1" -rxjs@^6.4.0: +rxjs@^6.4.0, rxjs@^6.5.3: version "6.5.3" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a" integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA== @@ -14295,6 +14366,11 @@ typescript@3.5.x, typescript@^3.6.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da" integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw== +u2f-api@0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/u2f-api/-/u2f-api-0.2.7.tgz#17bf196b242f6bf72353d9858e6a7566cc192720" + integrity sha512-fqLNg8vpvLOD5J/z4B6wpPg4Lvowz1nJ9xdHcCzdUPKcFE/qNCceV2gNZxSJd5vhAZemHr/K/hbzVA0zxB5mkg== + ua-parser-js@^0.7.18: version "0.7.20" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.20.tgz#7527178b82f6a62a0f243d1f94fd30e3e3c21098"