From cc4b1d334955c033278692c62de208167e97b7f5 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 9 Sep 2020 13:50:58 +0200 Subject: [PATCH 01/33] launchpad-ledger: Add basic package setup --- packages/launchpad-ledger/.eslintignore | 1 + packages/launchpad-ledger/.gitignore | 3 ++ packages/launchpad-ledger/.nycrc.yml | 1 + packages/launchpad-ledger/README.md | 12 +++++ .../launchpad-ledger/jasmine-testrunner.js | 33 +++++++++++++ packages/launchpad-ledger/karma.conf.js | 47 +++++++++++++++++++ packages/launchpad-ledger/package.json | 44 +++++++++++++++++ packages/launchpad-ledger/tsconfig.json | 12 +++++ packages/launchpad-ledger/typedoc.js | 13 +++++ .../launchpad-ledger/webpack.web.config.js | 19 ++++++++ 10 files changed, 185 insertions(+) create mode 120000 packages/launchpad-ledger/.eslintignore create mode 100644 packages/launchpad-ledger/.gitignore create mode 120000 packages/launchpad-ledger/.nycrc.yml create mode 100644 packages/launchpad-ledger/README.md create mode 100755 packages/launchpad-ledger/jasmine-testrunner.js create mode 100644 packages/launchpad-ledger/karma.conf.js create mode 100644 packages/launchpad-ledger/package.json create mode 100644 packages/launchpad-ledger/tsconfig.json create mode 100644 packages/launchpad-ledger/typedoc.js create mode 100644 packages/launchpad-ledger/webpack.web.config.js diff --git a/packages/launchpad-ledger/.eslintignore b/packages/launchpad-ledger/.eslintignore new file mode 120000 index 00000000..86039baf --- /dev/null +++ b/packages/launchpad-ledger/.eslintignore @@ -0,0 +1 @@ +../../.eslintignore \ No newline at end of file diff --git a/packages/launchpad-ledger/.gitignore b/packages/launchpad-ledger/.gitignore new file mode 100644 index 00000000..68bf3735 --- /dev/null +++ b/packages/launchpad-ledger/.gitignore @@ -0,0 +1,3 @@ +build/ +dist/ +docs/ diff --git a/packages/launchpad-ledger/.nycrc.yml b/packages/launchpad-ledger/.nycrc.yml new file mode 120000 index 00000000..1f95ac55 --- /dev/null +++ b/packages/launchpad-ledger/.nycrc.yml @@ -0,0 +1 @@ +../../.nycrc.yml \ No newline at end of file diff --git a/packages/launchpad-ledger/README.md b/packages/launchpad-ledger/README.md new file mode 100644 index 00000000..e8c0ad23 --- /dev/null +++ b/packages/launchpad-ledger/README.md @@ -0,0 +1,12 @@ +# @cosmjs/launchpad-ledger + +[![npm version](https://img.shields.io/npm/v/@cosmjs/launchpad-ledger.svg)](https://www.npmjs.com/package/@cosmjs/launchpad-ledger) + +``` + +## License + +This package is part of the cosmjs repository, licensed under the Apache License +2.0 (see [NOTICE](https://github.com/CosmWasm/cosmjs/blob/master/NOTICE) and +[LICENSE](https://github.com/CosmWasm/cosmjs/blob/master/LICENSE)). +``` diff --git a/packages/launchpad-ledger/jasmine-testrunner.js b/packages/launchpad-ledger/jasmine-testrunner.js new file mode 100755 index 00000000..7a17962e --- /dev/null +++ b/packages/launchpad-ledger/jasmine-testrunner.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node + +/* eslint-disable @typescript-eslint/naming-convention */ +require("source-map-support").install(); +const defaultSpecReporterConfig = require("../../jasmine-spec-reporter.config.json"); + +// setup Jasmine +const Jasmine = require("jasmine"); +const jasmine = new Jasmine(); +jasmine.loadConfig({ + spec_dir: "build", + spec_files: ["**/*.spec.js"], + helpers: [], + random: false, + seed: null, + stopSpecOnExpectationFailure: false, +}); +jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 1000; + +// setup reporter +const { SpecReporter } = require("jasmine-spec-reporter"); +const reporter = new SpecReporter({ + ...defaultSpecReporterConfig, + spec: { + ...defaultSpecReporterConfig.spec, + displaySuccessful: !process.argv.includes("--quiet"), + }, +}); + +// initialize and execute +jasmine.env.clearReporters(); +jasmine.addReporter(reporter); +jasmine.execute(); diff --git a/packages/launchpad-ledger/karma.conf.js b/packages/launchpad-ledger/karma.conf.js new file mode 100644 index 00000000..006da5fe --- /dev/null +++ b/packages/launchpad-ledger/karma.conf.js @@ -0,0 +1,47 @@ +module.exports = function (config) { + config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: ".", + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ["jasmine"], + + // list of files / patterns to load in the browser + files: ["dist/web/tests.js"], + + client: { + jasmine: { + random: false, + timeoutInterval: 15000, + }, + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ["progress", "kjhtml"], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ["Firefox"], + + browserNoActivityTimeout: 90000, + + // Keep brower open for debugging. This is overridden by yarn scripts + singleRun: false, + }); +}; diff --git a/packages/launchpad-ledger/package.json b/packages/launchpad-ledger/package.json new file mode 100644 index 00000000..593e6edb --- /dev/null +++ b/packages/launchpad-ledger/package.json @@ -0,0 +1,44 @@ +{ + "name": "@cosmjs/launchpad-ledger", + "version": "0.22.2", + "description": "A library for interacting with the Cosmos Launchpad Ledger Nano App", + "contributors": [ + "Will Clark " + ], + "license": "Apache-2.0", + "main": "build/index.js", + "types": "types/index.d.ts", + "files": [ + "build/", + "types/", + "*.md", + "!*.spec.*", + "!**/testdata/" + ], + "repository": { + "type": "git", + "url": "https://github.com/CosmWasm/cosmjs/tree/master/packages/launchpad-ledger" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "docs": "typedoc --options typedoc.js", + "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", + "format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"", + "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", + "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", + "move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.{demo,spec}.d.ts && shx rm ./types/**/*.spec.d.ts", + "format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"", + "prebuild": "shx rm -rf ./build", + "build": "tsc", + "postbuild": "yarn move-types && yarn format-types", + "build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build", + "test-node": "node jasmine-testrunner.js", + "test-firefox": "yarn pack-web && karma start --single-run --browsers Firefox", + "test-chrome": "yarn pack-web && karma start --single-run --browsers ChromeHeadless", + "test": "yarn build-or-skip && yarn test-node", + "coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet", + "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" + } +} diff --git a/packages/launchpad-ledger/tsconfig.json b/packages/launchpad-ledger/tsconfig.json new file mode 100644 index 00000000..167e8c02 --- /dev/null +++ b/packages/launchpad-ledger/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "declarationDir": "build/types", + "rootDir": "src" + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/launchpad-ledger/typedoc.js b/packages/launchpad-ledger/typedoc.js new file mode 100644 index 00000000..4dfbe49d --- /dev/null +++ b/packages/launchpad-ledger/typedoc.js @@ -0,0 +1,13 @@ +const packageJson = require("./package.json"); + +module.exports = { + inputFiles: ["./src"], + out: "docs", + exclude: "**/*.spec.ts", + name: `${packageJson.name} Documentation`, + readme: "README.md", + mode: "file", + excludeExternals: true, + excludeNotExported: true, + excludePrivate: true, +}; diff --git a/packages/launchpad-ledger/webpack.web.config.js b/packages/launchpad-ledger/webpack.web.config.js new file mode 100644 index 00000000..7373cace --- /dev/null +++ b/packages/launchpad-ledger/webpack.web.config.js @@ -0,0 +1,19 @@ +const glob = require("glob"); +const path = require("path"); +const webpack = require("webpack"); + +const target = "web"; +const distdir = path.join(__dirname, "dist", "web"); + +module.exports = [ + { + // bundle used for Karma tests + target: target, + entry: glob.sync("./build/**/*.spec.js"), + output: { + path: distdir, + filename: "tests.js", + }, + plugins: [new webpack.EnvironmentPlugin(["WASMD_ENABLED"])], + }, +]; From f16ef5da7a1c35ac2fce4a915f109776b12f146f Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 9 Sep 2020 13:58:38 +0200 Subject: [PATCH 02/33] launchpad-ledger: Add ledger-cosmos-js dependency --- packages/launchpad-ledger/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/launchpad-ledger/package.json b/packages/launchpad-ledger/package.json index 593e6edb..d933cd01 100644 --- a/packages/launchpad-ledger/package.json +++ b/packages/launchpad-ledger/package.json @@ -40,5 +40,8 @@ "test": "yarn build-or-skip && yarn test-node", "coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet", "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" + }, + "dependencies": { + "ledger-cosmos-js": "^2.1.7" } } From 6a3d20b8e3b0f8f5bab912551c2804a679d12d3e Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 9 Sep 2020 15:35:21 +0200 Subject: [PATCH 03/33] launchpad-ledger: Add @ledgerhq/hw-transport-webusb dep --- packages/launchpad-ledger/package.json | 1 + yarn.lock | 102 ++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/packages/launchpad-ledger/package.json b/packages/launchpad-ledger/package.json index d933cd01..dbcc37ce 100644 --- a/packages/launchpad-ledger/package.json +++ b/packages/launchpad-ledger/package.json @@ -42,6 +42,7 @@ "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" }, "dependencies": { + "@ledgerhq/hw-transport-webusb": "^5.23.0", "ledger-cosmos-js": "^2.1.7" } } diff --git a/yarn.lock b/yarn.lock index cca65fc2..37811588 100644 --- a/yarn.lock +++ b/yarn.lock @@ -144,6 +144,13 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.3.tgz#7e71d892b0d6e7d04a1af4c3c79d72c1f10f5315" integrity sha512-oJtNJCMFdIMwXGmx+KxuaD7i3b8uS7TTFYW/FNG2BT8m+fmGHoiPYoH0Pe3gya07WuFmM5FCDIr1x0irkD/hyA== +"@babel/runtime@^7.7.4": + version "7.11.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" + integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.10.1", "@babel/template@^7.10.3": version "7.10.3" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.3.tgz#4d13bc8e30bf95b0ce9d175d30306f42a2c9a7b8" @@ -290,6 +297,72 @@ dependencies: vary "^1.1.2" +"@ledgerhq/devices@^4.78.0": + version "4.78.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-4.78.0.tgz#149b572f0616096e2bd5eb14ce14d0061c432be6" + integrity sha512-tWKS5WM/UU82czihnVjRwz9SXNTQzWjGJ/7+j/xZ70O86nlnGJ1aaFbs5/WTzfrVKpOKgj1ZoZkAswX67i/JTw== + dependencies: + "@ledgerhq/errors" "^4.78.0" + "@ledgerhq/logs" "^4.72.0" + rxjs "^6.5.3" + +"@ledgerhq/devices@^5.23.0": + version "5.23.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.23.0.tgz#e5b800de858e45d247be56708c832c1e51727fe0" + integrity sha512-XR9qTwn14WwN8VSMsYD9NTX/TgkmrTnXEh0pIj6HMRZwFzBPzslExOcXuCm3V9ssgAEAxv3VevfV8UulvvZUXA== + dependencies: + "@ledgerhq/errors" "^5.23.0" + "@ledgerhq/logs" "^5.23.0" + rxjs "^6.6.3" + +"@ledgerhq/errors@^4.78.0": + version "4.78.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-4.78.0.tgz#23daf3af54d03b1bda3e616002b555da1bdb705a" + integrity sha512-FX6zHZeiNtegBvXabK6M5dJ+8OV8kQGGaGtuXDeK/Ss5EmG4Ltxc6Lnhe8hiHpm9pCHtktOsnUVL7IFBdHhYUg== + +"@ledgerhq/errors@^5.23.0": + version "5.23.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.23.0.tgz#30a0338dafba8264556011604abed08bf24979f3" + integrity sha512-qtpX8aFrUUlYfOMu7BxTvxqUa8CniE+tEBpVEjYUhVbFdVJjM4ouwJD++RtQkMAU2c5jE7xb12WnUnf5BlAgLQ== + +"@ledgerhq/hw-transport-webusb@^5.23.0": + version "5.23.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-5.23.0.tgz#f374415ab1bda6ba55898c0e74097f2005fd06f7" + integrity sha512-kz0LhlxvE97gIU0TvvzY3Co4DsRudwXoWZ/RCW3/ewn4zJ5ZT/fvp9qCrqbRsXJ+RTuZ7JQOiR1kA+Iz3qJWCg== + dependencies: + "@ledgerhq/devices" "^5.23.0" + "@ledgerhq/errors" "^5.23.0" + "@ledgerhq/hw-transport" "^5.23.0" + "@ledgerhq/logs" "^5.23.0" + +"@ledgerhq/hw-transport@^4.77.0": + version "4.78.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-4.78.0.tgz#714786658e1f2fbc0569e06e2abf8d15d310d931" + integrity sha512-xQu16OMPQjFYLjqCysij+8sXtdWv2YLxPrB6FoLvEWGTlQ7yL1nUBRQyzyQtWIYqZd4THQowQmzm1VjxuN6SZw== + dependencies: + "@ledgerhq/devices" "^4.78.0" + "@ledgerhq/errors" "^4.78.0" + events "^3.0.0" + +"@ledgerhq/hw-transport@^5.23.0": + version "5.23.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-5.23.0.tgz#ed3445b9579c43a58cd959610ad7e464b36b87ca" + integrity sha512-ICTG3Bst62SkC+lYYFgpKk5G4bAOxeIvptXnTLOhf6VqeN7gdHfiRzZwNPnKzI2pxmcEVbBitgsxEIEQJmDKVA== + dependencies: + "@ledgerhq/devices" "^5.23.0" + "@ledgerhq/errors" "^5.23.0" + events "^3.2.0" + +"@ledgerhq/logs@^4.72.0": + version "4.72.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-4.72.0.tgz#43df23af013ad1135407e5cf33ca6e4c4c7708d5" + integrity sha512-o+TYF8vBcyySRsb2kqBDv/KMeme8a2nwWoG+lAWzbDmWfb2/MrVWYCVYDYvjXdSoI/Cujqy1i0gIDrkdxa9chA== + +"@ledgerhq/logs@^5.23.0": + version "5.23.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.23.0.tgz#7a86b1e6479c8aa8e8b9affe00eb8e369efdbc3b" + integrity sha512-88M8RkVHl44k6MAhfrYhx25opnJV24/2XpuTUVklID11f9rBdE+6RZ9OMs39dyX2sDv7TuzIPi5nTRoCqZMDYw== + "@lerna/add@3.21.0": version "3.21.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.21.0.tgz#27007bde71cc7b0a2969ab3c2f0ae41578b4577b" @@ -2051,7 +2124,7 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -bech32@^1.1.4: +bech32@^1.1.3, bech32@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== @@ -3660,6 +3733,11 @@ events@^3.0.0: resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== +events@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" + integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -5304,6 +5382,16 @@ koa@^2.11.0: type-is "^1.6.16" vary "^1.1.2" +ledger-cosmos-js@^2.1.7: + version "2.1.7" + resolved "https://registry.yarnpkg.com/ledger-cosmos-js/-/ledger-cosmos-js-2.1.7.tgz#362d1139ac2504ccb56029abda053b2c5b290e8d" + integrity sha512-RyaP+6lRhllQTgk5X14PiZtsUE8bYP7mOiFkOMAzPQvUDdy8IZr17mDaH9whqHtE0wZd95YPN89VAhbfV+Re8w== + dependencies: + "@babel/runtime" "^7.7.4" + "@ledgerhq/hw-transport" "^4.77.0" + bech32 "^1.1.3" + ripemd160 "^2.0.2" + lerna@^3.22.1: version "3.22.1" resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.22.1.tgz#82027ac3da9c627fd8bf02ccfeff806a98e65b62" @@ -7134,6 +7222,11 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +regenerator-runtime@^0.13.4: + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" @@ -7332,6 +7425,13 @@ rxjs@^6.4.0: dependencies: tslib "^1.9.0" +rxjs@^6.5.3, rxjs@^6.6.3: + version "6.6.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" + integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" From 14464dcb44ef94cbed63c4277288be2375c6462a Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 9 Sep 2020 15:35:47 +0200 Subject: [PATCH 04/33] launchpad-ledger: Add DOM lib to tsconfig.json --- packages/launchpad-ledger/tsconfig.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/launchpad-ledger/tsconfig.json b/packages/launchpad-ledger/tsconfig.json index 167e8c02..39c5e368 100644 --- a/packages/launchpad-ledger/tsconfig.json +++ b/packages/launchpad-ledger/tsconfig.json @@ -4,9 +4,8 @@ "baseUrl": ".", "outDir": "build", "declarationDir": "build/types", - "rootDir": "src" + "rootDir": "src", + "lib": ["es2017", "dom"] }, - "include": [ - "src/**/*" - ] + "include": ["src/**/*"] } From cc49d373cb9ba8ad4356e6931779e058d2f2e59a Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 9 Sep 2020 16:14:55 +0200 Subject: [PATCH 05/33] launchpad-ledger: Add semver dependency --- packages/launchpad-ledger/package.json | 6 +++++- yarn.lock | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/launchpad-ledger/package.json b/packages/launchpad-ledger/package.json index dbcc37ce..ee398f48 100644 --- a/packages/launchpad-ledger/package.json +++ b/packages/launchpad-ledger/package.json @@ -43,6 +43,10 @@ }, "dependencies": { "@ledgerhq/hw-transport-webusb": "^5.23.0", - "ledger-cosmos-js": "^2.1.7" + "ledger-cosmos-js": "^2.1.7", + "semver": "^7.3.2" + }, + "devDependencies": { + "@types/semver": "^7.3.3" } } diff --git a/yarn.lock b/yarn.lock index 37811588..a28a3797 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1443,6 +1443,11 @@ dependencies: "@types/node" "*" +"@types/semver@^7.3.3": + version "7.3.3" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.3.tgz#3ad6ed949e7487e7bda6f886b4a2434a2c3d7b1a" + integrity sha512-jQxClWFzv9IXdLdhSaTf16XI3NYe6zrEbckSpb5xhKfPbWgIyAY0AFyWWWfaiDcBuj3UHmMkCIwSRqpKMTZL2Q== + "@types/serve-static@*": version "1.13.3" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1" From 59a02aa6e44bc7e523b57d4b5627aef4b1915c3b Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 9 Sep 2020 16:20:56 +0200 Subject: [PATCH 06/33] launchpad-ledger: Add @types/ledgerhq__hw-transport-webusb --- packages/launchpad-ledger/package.json | 1 + yarn.lock | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/launchpad-ledger/package.json b/packages/launchpad-ledger/package.json index ee398f48..47d4e6a6 100644 --- a/packages/launchpad-ledger/package.json +++ b/packages/launchpad-ledger/package.json @@ -47,6 +47,7 @@ "semver": "^7.3.2" }, "devDependencies": { + "@types/ledgerhq__hw-transport-webusb": "^4.70.0", "@types/semver": "^7.3.3" } } diff --git a/yarn.lock b/yarn.lock index a28a3797..c0c33ea5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1374,6 +1374,21 @@ dependencies: "@types/koa" "*" +"@types/ledgerhq__hw-transport-webusb@^4.70.0": + version "4.70.0" + resolved "https://registry.yarnpkg.com/@types/ledgerhq__hw-transport-webusb/-/ledgerhq__hw-transport-webusb-4.70.0.tgz#8e03b1f8bec52a8291ef41b00470b07f28285788" + integrity sha512-7YY+q85pwZfja9foc0RxmcgMwsxbBo9u/Qe9BFg0uKVcF6TZFcBT99+AHWj+40EmXL3BxzK3QB+aoSNed+v8Gg== + 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/libsodium-wrappers@^0.7.7": version "0.7.7" resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.7.tgz#cdb25e85458612ec80f0157c3815fac187d0b6d2" From fbe1b2fa5f4fd21e288035eddbe0850e0468374d Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 9 Sep 2020 16:53:31 +0200 Subject: [PATCH 07/33] launchpad-ledger: Adjust build script --- packages/launchpad-ledger/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/launchpad-ledger/package.json b/packages/launchpad-ledger/package.json index 47d4e6a6..4b54a1b1 100644 --- a/packages/launchpad-ledger/package.json +++ b/packages/launchpad-ledger/package.json @@ -28,7 +28,7 @@ "format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"", "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", - "move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.{demo,spec}.d.ts && shx rm ./types/**/*.spec.d.ts", + "move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.spec.d.ts", "format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"", "prebuild": "shx rm -rf ./build", "build": "tsc", From dd73f24ebf5f117fcc506e58cc1e5e06bb85f446 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 9 Sep 2020 17:39:59 +0200 Subject: [PATCH 08/33] launchpad-ledger: Add @cosmjs/utils dependency --- packages/launchpad-ledger/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/launchpad-ledger/package.json b/packages/launchpad-ledger/package.json index 4b54a1b1..404f72d0 100644 --- a/packages/launchpad-ledger/package.json +++ b/packages/launchpad-ledger/package.json @@ -42,6 +42,7 @@ "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" }, "dependencies": { + "@cosmjs/utils": "^0.22.2", "@ledgerhq/hw-transport-webusb": "^5.23.0", "ledger-cosmos-js": "^2.1.7", "semver": "^7.3.2" From dc0d5d2649cae1965081b21f1db0a2e9729a574d Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 9 Sep 2020 18:14:33 +0200 Subject: [PATCH 09/33] launchpad-ledger: Add basic type declaration for ledger-cosmos-js --- .../custom_types/ledger-cosmos-js.d.ts | 53 +++++++++++++++++++ packages/launchpad-ledger/tsconfig.json | 2 +- tsconfig.json | 4 +- 3 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 packages/launchpad-ledger/custom_types/ledger-cosmos-js.d.ts diff --git a/packages/launchpad-ledger/custom_types/ledger-cosmos-js.d.ts b/packages/launchpad-ledger/custom_types/ledger-cosmos-js.d.ts new file mode 100644 index 00000000..f49d8461 --- /dev/null +++ b/packages/launchpad-ledger/custom_types/ledger-cosmos-js.d.ts @@ -0,0 +1,53 @@ +declare module "ledger-cosmos-js" { + import Transport from "@ledgerhq/hw-transport"; + + export interface ErrorResponse { + readonly return_code: number; + readonly error_message: string; + } + + export interface VersionResponse { + readonly major: number; + readonly minor: number; + readonly patch: number; + readonly test_mode: boolean; + readonly error_message: string; + readonly device_locked: boolean; + } + + export interface AppInfoResponse { + readonly appName: string; + readonly error_message: string; + } + + export interface PublicKeyResponse { + readonly compressed_pk: Buffer; + readonly error_message: string; + } + + export interface AddressAndPublicKeyResponse { + readonly compressed_pk: Buffer; + readonly address: string; + readonly error_message: string; + } + + export interface SignResponse { + readonly signature: Buffer; + readonly error_message: string; + } + + export default class CosmosApp { + static getBech32FromPK(hrp: string, pk: Buffer): string; + + constructor(transport: Transport, scrambleKey?: string); + + getVersion: () => Promise; + appInfo: () => Promise; + publicKey: (path: Array) => Promise; + showAddressAndPubKey: ( + path: Array, + hrp: string, + ) => Promise; + sign: (path: Array, message: string) => Promise; + } +} diff --git a/packages/launchpad-ledger/tsconfig.json b/packages/launchpad-ledger/tsconfig.json index 39c5e368..44242629 100644 --- a/packages/launchpad-ledger/tsconfig.json +++ b/packages/launchpad-ledger/tsconfig.json @@ -7,5 +7,5 @@ "rootDir": "src", "lib": ["es2017", "dom"] }, - "include": ["src/**/*"] + "include": ["src/**/*", "./custom_types/*"] } diff --git a/tsconfig.json b/tsconfig.json index 7817c12b..b74563e3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "declaration": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, + "lib": ["es2017"], "module": "commonjs", "moduleResolution": "node", "newLine": "LF", @@ -17,7 +18,6 @@ "resolveJsonModule": true, "sourceMap": true, "strict": true, - "target": "es2017", - "lib": ["es2017"] + "target": "es2017" } } From 33a7824215380eb4386c769b762177e344bbd0ef Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 9 Sep 2020 18:17:18 +0200 Subject: [PATCH 10/33] launchpad-ledger: Add LaunchpadLedger class --- packages/launchpad-ledger/src/index.ts | 1 + .../launchpad-ledger/src/launchpadledger.ts | 230 ++++++++++++++++++ packages/launchpad-ledger/types/index.d.ts | 1 + .../types/launchpadledger.d.ts | 29 +++ 4 files changed, 261 insertions(+) create mode 100644 packages/launchpad-ledger/src/index.ts create mode 100644 packages/launchpad-ledger/src/launchpadledger.ts create mode 100644 packages/launchpad-ledger/types/index.d.ts create mode 100644 packages/launchpad-ledger/types/launchpadledger.d.ts diff --git a/packages/launchpad-ledger/src/index.ts b/packages/launchpad-ledger/src/index.ts new file mode 100644 index 00000000..0ca77769 --- /dev/null +++ b/packages/launchpad-ledger/src/index.ts @@ -0,0 +1 @@ +export { LaunchpadLedger } from "./launchpadledger"; diff --git a/packages/launchpad-ledger/src/launchpadledger.ts b/packages/launchpad-ledger/src/launchpadledger.ts new file mode 100644 index 00000000..3f9f4216 --- /dev/null +++ b/packages/launchpad-ledger/src/launchpadledger.ts @@ -0,0 +1,230 @@ +import { Secp256k1Signature } from "@cosmjs/crypto"; +import { fromUtf8 } from "@cosmjs/encoding"; +import { assert } from "@cosmjs/utils"; +import Transport from "@ledgerhq/hw-transport"; +import TransportWebUsb from "@ledgerhq/hw-transport-webusb"; +import CosmosApp, { + AppInfoResponse, + PublicKeyResponse, + SignResponse, + VersionResponse, +} from "ledger-cosmos-js"; +import semver from "semver"; + +const defaultInteractionTimeout = 120; // seconds to wait for user action on Ledger, currently is always limited to 60 +const requiredCosmosAppVersion = "1.5.3"; + +function isWindows(platform: string): boolean { + return platform.indexOf("Win") > -1; +} + +function verifyBrowserIsSupported(platform: string, userAgent: string): void { + if (isWindows(platform)) { + throw new Error("Windows is not currently supported."); + } + + const ua = userAgent.toLowerCase(); + const isChrome = /chrome|crios/.test(ua) && !/edge|opr\//.test(ua); + if (!isChrome) { + throw new Error("Your browser does not support Ledger devices."); + } +} + +async function createTransport(timeout: number): Promise { + try { + const transport = await TransportWebUsb.create(timeout * 1000); + return transport; + } catch (error) { + const trimmedErrorMessage = error.message.trim(); + if (trimmedErrorMessage.startsWith("No WebUSB interface found for your Ledger device")) { + throw new Error( + "Could not connect to a Ledger device. Please use Ledger Live to upgrade the Ledger firmware to version 1.5.5 or later.", + ); + } + if (trimmedErrorMessage.startsWith("Unable to claim interface")) { + throw new Error("Could not access Ledger device. Is it being used in another tab?"); + } + if (trimmedErrorMessage.startsWith("Not supported")) { + throw new Error( + "Your browser does not seem to support WebUSB yet. Try updating it to the latest version.", + ); + } + if (trimmedErrorMessage.startsWith("No device selected")) { + throw new Error( + "You did not select a Ledger device. If you did not see your Ledger, check if the Ledger is plugged in and unlocked.", + ); + } + + throw error; + } +} + +const cosmosHdPath = [44, 118, 0, 0, 0]; +const cosmosBech32Prefix = "cosmos"; + +export class LaunchpadLedger { + private readonly testModeAllowed: boolean; + private readonly hdPath: number[]; + private readonly prefix: string; + private cosmosApp: CosmosApp | null; + public readonly platform: string; + public readonly userAgent: string; + + constructor( + { testModeAllowed }: { testModeAllowed: boolean } = { testModeAllowed: false }, + hdPath: number[] = cosmosHdPath, + prefix: string = cosmosBech32Prefix, + ) { + this.testModeAllowed = testModeAllowed; + this.hdPath = hdPath; + this.prefix = prefix; + this.cosmosApp = null; + this.platform = navigator.platform; + this.userAgent = navigator.userAgent; + } + + // // quickly test connection and compatibility with the LaunchpadLedger device throwing away the connection + // async testDevice(): Promise { + // // poll device with low timeout to check if the device is connected + // const secondsTimeout = 3; // a lower value always timeouts + // await this.connect(secondsTimeout); + // this.cosmosApp = null; + + // return this; + // } + + async connect(timeout = defaultInteractionTimeout): Promise { + // assume good connection if connected once + if (this.cosmosApp) { + return this; + } + + verifyBrowserIsSupported(this.platform, this.userAgent); + + const transport = await createTransport(timeout * 1000); + this.cosmosApp = new CosmosApp(transport); + + await this.verifyDeviceIsReady(); + return this; + } + + async getCosmosAppVersion(): Promise { + await this.connect(); + assert(this.cosmosApp, "Cosmos Ledger App is not connected"); + + const response = await this.cosmosApp.getVersion(); + this.handleLedgerErrors(response); + // eslint-disable-next-line @typescript-eslint/naming-convention + const { major, minor, patch, test_mode: testMode } = response as VersionResponse; + this.verifyAppMode(testMode); + return `${major}.${minor}.${patch}`; + } + + async getPubKey(): Promise { + await this.connect(); + assert(this.cosmosApp, "Cosmos Ledger App is not connected"); + + const response = await this.cosmosApp.publicKey(this.hdPath); + this.handleLedgerErrors(response); + return (response as PublicKeyResponse).compressed_pk; + } + + async getCosmosAddress(): Promise { + const pubKey = await this.getPubKey(); + return CosmosApp.getBech32FromPK(this.prefix, pubKey); + } + + // async verifyLedgerAddress(): Promise { + // await this.connect(); + // assert(this.cosmosApp, "Cosmos Ledger App is not connected"); + + // const response = await this.cosmosApp.showAddressAndPubKey(this.hdPath, this.bech32Prefix); + // this.handleLedgerErrors(response, { + // rejectionMessage: "Displayed address was rejected by the user", + // }); + // } + + async sign(message: Uint8Array): Promise { + await this.connect(); + assert(this.cosmosApp, "Cosmos Ledger App is not connected"); + + const response = await this.cosmosApp.sign(this.hdPath, fromUtf8(message)); + this.handleLedgerErrors(response, { + rejectionMessage: "Transaction signing request was rejected by the user", + }); + const parsedSignature = Secp256k1Signature.fromDer((response as SignResponse).signature); + return Uint8Array.from([...parsedSignature.r(), ...parsedSignature.s()]); + } + + private verifyAppMode(testMode: boolean): void { + if (testMode && !this.testModeAllowed) { + throw new Error(`DANGER: The Cosmos Ledger app is in test mode and should not be used on mainnet!`); + } + } + + private async getOpenAppName(): Promise { + await this.connect(); + assert(this.cosmosApp, "Cosmos Ledger App is not connected"); + + const response = await this.cosmosApp.appInfo(); + this.handleLedgerErrors(response); + return (response as AppInfoResponse).appName; + } + + private async verifyAppVersion(): Promise { + const version = await this.getCosmosAppVersion(); + if (!semver.gte(version, requiredCosmosAppVersion)) { + throw new Error("Outdated version: Please update Cosmos Ledger App to the latest version."); + } + } + + private async verifyCosmosAppIsOpen(): Promise { + const appName = await this.getOpenAppName(); + + if (appName.toLowerCase() === `dashboard`) { + throw new Error(`Please open the Cosmos Ledger app on your Ledger device.`); + } + if (appName.toLowerCase() !== `cosmos`) { + throw new Error(`Please close ${appName} and open the Cosmos Ledger app on your Ledger device.`); + } + } + + private async verifyDeviceIsReady(): Promise { + await this.verifyAppVersion(); + await this.verifyCosmosAppIsOpen(); + } + + /* eslint-disable @typescript-eslint/naming-convention */ + private handleLedgerErrors( + { + error_message: errorMessage, + device_locked: deviceLocked = false, + }: { error_message: string; device_locked?: boolean }, + { rejectionMessage = "Request was rejected by the user" } = {}, + ): void { + if (deviceLocked) { + throw new Error("Ledger’s screensaver mode is on"); + } + switch (errorMessage) { + case "U2F: Timeout": + throw new Error("Connection timed out. Please try again."); + case "Cosmos app does not seem to be open": + throw new Error("Cosmos app is not open"); + case "Command not allowed": + throw new Error("Transaction rejected"); + case "Transaction rejected": + throw new Error(rejectionMessage); + case "Unknown Status Code: 26628": + throw new Error("Ledger’s screensaver mode is on"); + case "Instruction not supported": + throw new Error( + `Your Cosmos Ledger App is not up to date. Please update to version ${requiredCosmosAppVersion}.`, + ); + case "No errors": + break; + default: + throw new Error(`Ledger Native Error: ${errorMessage}`); + } + } + /* eslint-enable */ +} diff --git a/packages/launchpad-ledger/types/index.d.ts b/packages/launchpad-ledger/types/index.d.ts new file mode 100644 index 00000000..0ca77769 --- /dev/null +++ b/packages/launchpad-ledger/types/index.d.ts @@ -0,0 +1 @@ +export { LaunchpadLedger } from "./launchpadledger"; diff --git a/packages/launchpad-ledger/types/launchpadledger.d.ts b/packages/launchpad-ledger/types/launchpadledger.d.ts new file mode 100644 index 00000000..323bcecc --- /dev/null +++ b/packages/launchpad-ledger/types/launchpadledger.d.ts @@ -0,0 +1,29 @@ +/// +export declare class LaunchpadLedger { + private readonly testModeAllowed; + private readonly hdPath; + private readonly prefix; + private cosmosApp; + readonly platform: string; + readonly userAgent: string; + constructor( + { + testModeAllowed, + }?: { + testModeAllowed: boolean; + }, + hdPath?: number[], + prefix?: string, + ); + connect(timeout?: number): Promise; + getCosmosAppVersion(): Promise; + getPubKey(): Promise; + getCosmosAddress(): Promise; + sign(message: Uint8Array): Promise; + private verifyAppMode; + private getOpenAppName; + private verifyAppVersion; + private verifyCosmosAppIsOpen; + private verifyDeviceIsReady; + private handleLedgerErrors; +} From 59c5ae5c8b9ae884b821bb058000e803cf081691 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 10 Sep 2020 11:26:11 +0200 Subject: [PATCH 11/33] launchpad-ledger: Add demo html/css --- packages/launchpad-ledger/ledger-demo.css | 24 ++++++++++ packages/launchpad-ledger/ledger-demo.html | 54 ++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 packages/launchpad-ledger/ledger-demo.css create mode 100644 packages/launchpad-ledger/ledger-demo.html diff --git a/packages/launchpad-ledger/ledger-demo.css b/packages/launchpad-ledger/ledger-demo.css new file mode 100644 index 00000000..4cc56f58 --- /dev/null +++ b/packages/launchpad-ledger/ledger-demo.css @@ -0,0 +1,24 @@ +div { + display: flex; + align-items: center; + justify-content: center; + margin: auto; + padding: 0.5em; +} + +button { + padding: 1em; +} + +label { + margin-right: 1em; +} + +input { + width: 32em; +} + +textarea { + width: 32em; + height: 14em; +} diff --git a/packages/launchpad-ledger/ledger-demo.html b/packages/launchpad-ledger/ledger-demo.html new file mode 100644 index 00000000..ecff0065 --- /dev/null +++ b/packages/launchpad-ledger/ledger-demo.html @@ -0,0 +1,54 @@ + + + + + + Ledger Demo + + + + +
+

Ledger Demo

+
+
+
    +
  1. Connect the Ledger device via USB
  2. +
  3. Open the Cosmos app on the Ledger device
  4. +
  5. Click the buttons below
  6. +
+
+
+ +
+
+
+ + +
+
+ + +
+
+ +
+
+ + From a5891f147fb89f9ce110976573b9c88cafba4d76 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 10 Sep 2020 11:32:43 +0200 Subject: [PATCH 12/33] launchpad-ledger: Add ledger demo and wallet --- packages/launchpad-ledger/src/index.ts | 1 + packages/launchpad-ledger/src/ledger.demo.ts | 38 ++++++++++++++++ packages/launchpad-ledger/src/ledgerwallet.ts | 45 +++++++++++++++++++ packages/launchpad-ledger/types/index.d.ts | 1 + .../launchpad-ledger/types/ledger.demo.d.ts | 1 + .../launchpad-ledger/types/ledgerwallet.d.ts | 13 ++++++ .../launchpad-ledger/webpack.web.config.js | 24 +++++++--- 7 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 packages/launchpad-ledger/src/ledger.demo.ts create mode 100644 packages/launchpad-ledger/src/ledgerwallet.ts create mode 100644 packages/launchpad-ledger/types/ledger.demo.d.ts create mode 100644 packages/launchpad-ledger/types/ledgerwallet.d.ts diff --git a/packages/launchpad-ledger/src/index.ts b/packages/launchpad-ledger/src/index.ts index 0ca77769..f0148827 100644 --- a/packages/launchpad-ledger/src/index.ts +++ b/packages/launchpad-ledger/src/index.ts @@ -1 +1,2 @@ export { LaunchpadLedger } from "./launchpadledger"; +export { LedgerWallet } from "./ledgerwallet"; diff --git a/packages/launchpad-ledger/src/ledger.demo.ts b/packages/launchpad-ledger/src/ledger.demo.ts new file mode 100644 index 00000000..023b466f --- /dev/null +++ b/packages/launchpad-ledger/src/ledger.demo.ts @@ -0,0 +1,38 @@ +import { toHex, toUtf8 } from "@cosmjs/encoding"; + +import { LedgerWallet } from "./ledgerwallet"; + +declare const window: any; +declare const document: any; + +const ledgerWallet = new LedgerWallet({ testModeAllowed: true }); + +window.getAccounts = async function getAccounts(): Promise { + const addressInput = document.getElementById("address"); + const accountsDiv = document.getElementById("accounts"); + accountsDiv.textContent = "Loading..."; + + try { + const accounts = await ledgerWallet.getAccounts(); + const prettyAccounts = accounts.map((account) => ({ ...account, pubkey: toHex(account.pubkey) })); + accountsDiv.textContent = JSON.stringify(prettyAccounts, null, "\t"); + addressInput.value = accounts[0].address; + } catch (error) { + accountsDiv.textContent = error; + } +}; + +window.sign = async function sign(): Promise { + const signatureDiv = document.getElementById("signature"); + signatureDiv.textContent = "Loading..."; + + try { + const address = document.getElementById("address").value; + const rawMessage = document.getElementById("message").textContent; + const message = JSON.stringify(JSON.parse(rawMessage)); + const signature = await ledgerWallet.sign(address, toUtf8(message)); + signatureDiv.textContent = JSON.stringify(signature, null, "\t"); + } catch (error) { + signatureDiv.textContent = error; + } +}; diff --git a/packages/launchpad-ledger/src/ledgerwallet.ts b/packages/launchpad-ledger/src/ledgerwallet.ts new file mode 100644 index 00000000..55e0ae13 --- /dev/null +++ b/packages/launchpad-ledger/src/ledgerwallet.ts @@ -0,0 +1,45 @@ +import { AccountData, encodeSecp256k1Signature, OfflineSigner, StdSignature } from "@cosmjs/launchpad"; + +import { LaunchpadLedger } from "./launchpadledger"; + +interface LedgerWalletOptions { + readonly testModeAllowed: boolean; +} + +export class LedgerWallet implements OfflineSigner { + private readonly ledger: LaunchpadLedger; + private address: string | undefined; + private pubkey: Uint8Array | undefined; + + constructor(options?: LedgerWalletOptions) { + this.ledger = new LaunchpadLedger(options); + } + + public async getAccounts(): Promise { + await this.ledger.connect(); + + const address = (this.address = this.address || (await this.ledger.getCosmosAddress())); + const pubkey = (this.pubkey = this.pubkey || (await this.ledger.getPubKey())); + + return [ + { + algo: "secp256k1", + address: address, + pubkey: pubkey, + }, + ]; + } + + public async sign(address: string, message: Uint8Array): Promise { + await this.ledger.connect(); + + const thisAddress = (this.address = this.address || (await this.ledger.getCosmosAddress())); + if (address !== thisAddress) { + throw new Error(`Address ${address} not found in wallet`); + } + + const signature = await this.ledger.sign(message); + const pubkey = (this.pubkey = this.pubkey || (await this.ledger.getPubKey())); + return encodeSecp256k1Signature(pubkey, signature); + } +} diff --git a/packages/launchpad-ledger/types/index.d.ts b/packages/launchpad-ledger/types/index.d.ts index 0ca77769..f0148827 100644 --- a/packages/launchpad-ledger/types/index.d.ts +++ b/packages/launchpad-ledger/types/index.d.ts @@ -1 +1,2 @@ export { LaunchpadLedger } from "./launchpadledger"; +export { LedgerWallet } from "./ledgerwallet"; diff --git a/packages/launchpad-ledger/types/ledger.demo.d.ts b/packages/launchpad-ledger/types/ledger.demo.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/packages/launchpad-ledger/types/ledger.demo.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/launchpad-ledger/types/ledgerwallet.d.ts b/packages/launchpad-ledger/types/ledgerwallet.d.ts new file mode 100644 index 00000000..f47f48b2 --- /dev/null +++ b/packages/launchpad-ledger/types/ledgerwallet.d.ts @@ -0,0 +1,13 @@ +import { AccountData, OfflineSigner, StdSignature } from "@cosmjs/launchpad"; +interface LedgerWalletOptions { + readonly testModeAllowed: boolean; +} +export declare class LedgerWallet implements OfflineSigner { + private readonly ledger; + private address; + private pubkey; + constructor(options?: LedgerWalletOptions); + getAccounts(): Promise; + sign(address: string, message: Uint8Array): Promise; +} +export {}; diff --git a/packages/launchpad-ledger/webpack.web.config.js b/packages/launchpad-ledger/webpack.web.config.js index 7373cace..fe7d59e8 100644 --- a/packages/launchpad-ledger/webpack.web.config.js +++ b/packages/launchpad-ledger/webpack.web.config.js @@ -1,19 +1,29 @@ const glob = require("glob"); const path = require("path"); -const webpack = require("webpack"); +// const webpack = require("webpack"); const target = "web"; -const distdir = path.join(__dirname, "dist", "web"); +// const distdir = path.join(__dirname, "dist", "web"); +const demodir = path.join(__dirname, "dist", "demo"); module.exports = [ + // { + // // bundle used for Karma tests + // target: target, + // entry: glob.sync("./build/**/*.spec.js"), + // output: { + // path: distdir, + // filename: "tests.js", + // }, + // plugins: [new webpack.EnvironmentPlugin(["WASMD_ENABLED"])], + // }, { - // bundle used for Karma tests + // bundle used for Ledger demo target: target, - entry: glob.sync("./build/**/*.spec.js"), + entry: glob.sync("./build/**/*.demo.js"), output: { - path: distdir, - filename: "tests.js", + path: demodir, + filename: "ledger.js", }, - plugins: [new webpack.EnvironmentPlugin(["WASMD_ENABLED"])], }, ]; From 5a970ab6ca87337b75faa5adab45b8bd00eb3422 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 10 Sep 2020 11:34:53 +0200 Subject: [PATCH 13/33] launchpad-ledger: Add @cosmjs/launchpad dep --- packages/launchpad-ledger/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/launchpad-ledger/package.json b/packages/launchpad-ledger/package.json index 404f72d0..7246cd09 100644 --- a/packages/launchpad-ledger/package.json +++ b/packages/launchpad-ledger/package.json @@ -42,6 +42,7 @@ "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" }, "dependencies": { + "@cosmjs/launchpad": "^0.22.2", "@cosmjs/utils": "^0.22.2", "@ledgerhq/hw-transport-webusb": "^5.23.0", "ledger-cosmos-js": "^2.1.7", From 78fb56856c6be0c2cfa7f86c94e564e425f901dd Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 10 Sep 2020 11:46:54 +0200 Subject: [PATCH 14/33] launchpad-ledger: Rename isChrome -> isChromeOrBrave --- packages/launchpad-ledger/src/launchpadledger.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/launchpad-ledger/src/launchpadledger.ts b/packages/launchpad-ledger/src/launchpadledger.ts index 3f9f4216..38b0ffb3 100644 --- a/packages/launchpad-ledger/src/launchpadledger.ts +++ b/packages/launchpad-ledger/src/launchpadledger.ts @@ -23,9 +23,8 @@ function verifyBrowserIsSupported(platform: string, userAgent: string): void { throw new Error("Windows is not currently supported."); } - const ua = userAgent.toLowerCase(); - const isChrome = /chrome|crios/.test(ua) && !/edge|opr\//.test(ua); - if (!isChrome) { + const isChromeOrBrave = /chrome|crios/i.test(userAgent) && !/edge|opr\//i.test(userAgent); + if (!isChromeOrBrave) { throw new Error("Your browser does not support Ledger devices."); } } From 4fe034e9949cc98d62c5e31183a53643659e2773 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 10 Sep 2020 11:51:39 +0200 Subject: [PATCH 15/33] crypto: Add Secp256k1Signature.toFixedLength method --- packages/crypto/src/secp256k1signature.spec.ts | 9 +++++++++ packages/crypto/src/secp256k1signature.ts | 4 ++++ packages/crypto/types/secp256k1signature.d.ts | 1 + 3 files changed, 14 insertions(+) diff --git a/packages/crypto/src/secp256k1signature.spec.ts b/packages/crypto/src/secp256k1signature.spec.ts index 27b58767..9baa7fe6 100644 --- a/packages/crypto/src/secp256k1signature.spec.ts +++ b/packages/crypto/src/secp256k1signature.spec.ts @@ -76,6 +76,15 @@ describe("Secp256k1Signature", () => { ).toThrowError(/unsigned integer s must be encoded as unpadded big endian./i); }); + it("can be encoded as fixed length", () => { + const signature = new Secp256k1Signature(new Uint8Array([0x22, 0x33]), new Uint8Array([0xaa])); + expect(signature.toFixedLength()).toEqual( + fromHex( + "000000000000000000000000000000000000000000000000000000000000223300000000000000000000000000000000000000000000000000000000000000aa", + ), + ); + }); + it("can encode to DER", () => { // Signature 3045022100f25b86e1d8a11d72475b3ed273b0781c7d7f6f9e1dae0dd5d3ee9b84f3fab891022063d9c4e1391de077244583e9a6e3d8e8e1f236a3bf5963735353b93b1a3ba935 // decoded by http://asn1-playground.oss.com/ diff --git a/packages/crypto/src/secp256k1signature.ts b/packages/crypto/src/secp256k1signature.ts index 0defcb19..be10f026 100644 --- a/packages/crypto/src/secp256k1signature.ts +++ b/packages/crypto/src/secp256k1signature.ts @@ -119,6 +119,10 @@ export class Secp256k1Signature { } } + public toFixedLength(): Uint8Array { + return new Uint8Array([...this.r(32), ...this.s(32)]); + } + public toDer(): Uint8Array { // DER supports negative integers but our data is unsigned. Thus we need to prepend // a leading 0 byte when the higest bit is set to differentiate nagative values diff --git a/packages/crypto/types/secp256k1signature.d.ts b/packages/crypto/types/secp256k1signature.d.ts index 1b7a5025..627b53e2 100644 --- a/packages/crypto/types/secp256k1signature.d.ts +++ b/packages/crypto/types/secp256k1signature.d.ts @@ -12,6 +12,7 @@ export declare class Secp256k1Signature { constructor(r: Uint8Array, s: Uint8Array); r(length?: number): Uint8Array; s(length?: number): Uint8Array; + toFixedLength(): Uint8Array; toDer(): Uint8Array; } /** From f071b8d25d38122989187804d82aeb178fb8407d Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 10 Sep 2020 11:52:59 +0200 Subject: [PATCH 16/33] launchpad-ledger: Use Secp256k1Signature.toFixedLength --- packages/launchpad-ledger/src/launchpadledger.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/launchpad-ledger/src/launchpadledger.ts b/packages/launchpad-ledger/src/launchpadledger.ts index 38b0ffb3..78e43947 100644 --- a/packages/launchpad-ledger/src/launchpadledger.ts +++ b/packages/launchpad-ledger/src/launchpadledger.ts @@ -151,8 +151,7 @@ export class LaunchpadLedger { this.handleLedgerErrors(response, { rejectionMessage: "Transaction signing request was rejected by the user", }); - const parsedSignature = Secp256k1Signature.fromDer((response as SignResponse).signature); - return Uint8Array.from([...parsedSignature.r(), ...parsedSignature.s()]); + return Secp256k1Signature.fromDer((response as SignResponse).signature).toFixedLength(); } private verifyAppMode(testMode: boolean): void { From 521fdcc2ab5a7e53e0ef2a737b4c7cc88ec1bd0e Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 10 Sep 2020 12:27:29 +0200 Subject: [PATCH 17/33] launchpad-ledger: Use HD path helpers --- .../launchpad-ledger/src/launchpadledger.ts | 19 +++++++++++++------ .../types/launchpadledger.d.ts | 3 ++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/launchpad-ledger/src/launchpadledger.ts b/packages/launchpad-ledger/src/launchpadledger.ts index 78e43947..cf7a75e0 100644 --- a/packages/launchpad-ledger/src/launchpadledger.ts +++ b/packages/launchpad-ledger/src/launchpadledger.ts @@ -1,5 +1,6 @@ -import { Secp256k1Signature } from "@cosmjs/crypto"; +import { Secp256k1Signature, Slip10RawIndex } from "@cosmjs/crypto"; import { fromUtf8 } from "@cosmjs/encoding"; +import { makeCosmoshubPath } from "@cosmjs/launchpad"; import { assert } from "@cosmjs/utils"; import Transport from "@ledgerhq/hw-transport"; import TransportWebUsb from "@ledgerhq/hw-transport-webusb"; @@ -58,12 +59,16 @@ async function createTransport(timeout: number): Promise { } } -const cosmosHdPath = [44, 118, 0, 0, 0]; +function unharden(hdPath: readonly Slip10RawIndex[]): number[] { + return hdPath.map((n) => (n.isHardened() ? n.toNumber() - 2 ** 31 : n.toNumber())); +} + +const cosmosHdPath = makeCosmoshubPath(0); const cosmosBech32Prefix = "cosmos"; export class LaunchpadLedger { private readonly testModeAllowed: boolean; - private readonly hdPath: number[]; + private readonly hdPath: readonly Slip10RawIndex[]; private readonly prefix: string; private cosmosApp: CosmosApp | null; public readonly platform: string; @@ -71,7 +76,7 @@ export class LaunchpadLedger { constructor( { testModeAllowed }: { testModeAllowed: boolean } = { testModeAllowed: false }, - hdPath: number[] = cosmosHdPath, + hdPath: readonly Slip10RawIndex[] = cosmosHdPath, prefix: string = cosmosBech32Prefix, ) { this.testModeAllowed = testModeAllowed; @@ -123,7 +128,8 @@ export class LaunchpadLedger { await this.connect(); assert(this.cosmosApp, "Cosmos Ledger App is not connected"); - const response = await this.cosmosApp.publicKey(this.hdPath); + // ledger-cosmos-js hardens the first three indices + const response = await this.cosmosApp.publicKey(unharden(this.hdPath)); this.handleLedgerErrors(response); return (response as PublicKeyResponse).compressed_pk; } @@ -147,7 +153,8 @@ export class LaunchpadLedger { await this.connect(); assert(this.cosmosApp, "Cosmos Ledger App is not connected"); - const response = await this.cosmosApp.sign(this.hdPath, fromUtf8(message)); + // ledger-cosmos-js hardens the first three indices + const response = await this.cosmosApp.sign(unharden(this.hdPath), fromUtf8(message)); this.handleLedgerErrors(response, { rejectionMessage: "Transaction signing request was rejected by the user", }); diff --git a/packages/launchpad-ledger/types/launchpadledger.d.ts b/packages/launchpad-ledger/types/launchpadledger.d.ts index 323bcecc..4dbf3064 100644 --- a/packages/launchpad-ledger/types/launchpadledger.d.ts +++ b/packages/launchpad-ledger/types/launchpadledger.d.ts @@ -1,4 +1,5 @@ /// +import { Slip10RawIndex } from "@cosmjs/crypto"; export declare class LaunchpadLedger { private readonly testModeAllowed; private readonly hdPath; @@ -12,7 +13,7 @@ export declare class LaunchpadLedger { }?: { testModeAllowed: boolean; }, - hdPath?: number[], + hdPath?: readonly Slip10RawIndex[], prefix?: string, ); connect(timeout?: number): Promise; From 461edfb358819b71a9bcab51ccedd6b927038842 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 10 Sep 2020 13:13:51 +0200 Subject: [PATCH 18/33] launchpad-ledger: Remove demo from types --- packages/launchpad-ledger/package.json | 2 +- packages/launchpad-ledger/types/ledger.demo.d.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 packages/launchpad-ledger/types/ledger.demo.d.ts diff --git a/packages/launchpad-ledger/package.json b/packages/launchpad-ledger/package.json index 7246cd09..20720c62 100644 --- a/packages/launchpad-ledger/package.json +++ b/packages/launchpad-ledger/package.json @@ -28,7 +28,7 @@ "format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"", "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", - "move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.spec.d.ts", + "move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.demo.d.ts", "format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"", "prebuild": "shx rm -rf ./build", "build": "tsc", diff --git a/packages/launchpad-ledger/types/ledger.demo.d.ts b/packages/launchpad-ledger/types/ledger.demo.d.ts deleted file mode 100644 index cb0ff5c3..00000000 --- a/packages/launchpad-ledger/types/ledger.demo.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; From 79e4af464322d3ad2896840710dbe86ab8d74b8c Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 10:52:06 +0200 Subject: [PATCH 19/33] launchpad-ledger: Fix README --- packages/launchpad-ledger/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/launchpad-ledger/README.md b/packages/launchpad-ledger/README.md index e8c0ad23..9b547645 100644 --- a/packages/launchpad-ledger/README.md +++ b/packages/launchpad-ledger/README.md @@ -2,11 +2,8 @@ [![npm version](https://img.shields.io/npm/v/@cosmjs/launchpad-ledger.svg)](https://www.npmjs.com/package/@cosmjs/launchpad-ledger) -``` - ## License This package is part of the cosmjs repository, licensed under the Apache License 2.0 (see [NOTICE](https://github.com/CosmWasm/cosmjs/blob/master/NOTICE) and [LICENSE](https://github.com/CosmWasm/cosmjs/blob/master/LICENSE)). -``` From d43ada9660e02348717663d831b4d4a3eb7ec6f8 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 10:54:37 +0200 Subject: [PATCH 20/33] launchpad-ledger: Rename LedgerWallet -> LedgerSigner --- packages/launchpad-ledger/src/index.ts | 2 +- packages/launchpad-ledger/src/ledger.demo.ts | 8 ++++---- .../src/{ledgerwallet.ts => ledgersigner.ts} | 6 +++--- packages/launchpad-ledger/types/index.d.ts | 2 +- .../types/{ledgerwallet.d.ts => ledgersigner.d.ts} | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) rename packages/launchpad-ledger/src/{ledgerwallet.ts => ledgersigner.ts} (90%) rename packages/launchpad-ledger/types/{ledgerwallet.d.ts => ledgersigner.d.ts} (69%) diff --git a/packages/launchpad-ledger/src/index.ts b/packages/launchpad-ledger/src/index.ts index f0148827..155dfd14 100644 --- a/packages/launchpad-ledger/src/index.ts +++ b/packages/launchpad-ledger/src/index.ts @@ -1,2 +1,2 @@ export { LaunchpadLedger } from "./launchpadledger"; -export { LedgerWallet } from "./ledgerwallet"; +export { LedgerSigner } from "./ledgersigner"; diff --git a/packages/launchpad-ledger/src/ledger.demo.ts b/packages/launchpad-ledger/src/ledger.demo.ts index 023b466f..46a9259e 100644 --- a/packages/launchpad-ledger/src/ledger.demo.ts +++ b/packages/launchpad-ledger/src/ledger.demo.ts @@ -1,11 +1,11 @@ import { toHex, toUtf8 } from "@cosmjs/encoding"; -import { LedgerWallet } from "./ledgerwallet"; +import { LedgerSigner } from "./ledgersigner"; declare const window: any; declare const document: any; -const ledgerWallet = new LedgerWallet({ testModeAllowed: true }); +const signer = new LedgerSigner({ testModeAllowed: true }); window.getAccounts = async function getAccounts(): Promise { const addressInput = document.getElementById("address"); @@ -13,7 +13,7 @@ window.getAccounts = async function getAccounts(): Promise { accountsDiv.textContent = "Loading..."; try { - const accounts = await ledgerWallet.getAccounts(); + const accounts = await signer.getAccounts(); const prettyAccounts = accounts.map((account) => ({ ...account, pubkey: toHex(account.pubkey) })); accountsDiv.textContent = JSON.stringify(prettyAccounts, null, "\t"); addressInput.value = accounts[0].address; @@ -30,7 +30,7 @@ window.sign = async function sign(): Promise { const address = document.getElementById("address").value; const rawMessage = document.getElementById("message").textContent; const message = JSON.stringify(JSON.parse(rawMessage)); - const signature = await ledgerWallet.sign(address, toUtf8(message)); + const signature = await signer.sign(address, toUtf8(message)); signatureDiv.textContent = JSON.stringify(signature, null, "\t"); } catch (error) { signatureDiv.textContent = error; diff --git a/packages/launchpad-ledger/src/ledgerwallet.ts b/packages/launchpad-ledger/src/ledgersigner.ts similarity index 90% rename from packages/launchpad-ledger/src/ledgerwallet.ts rename to packages/launchpad-ledger/src/ledgersigner.ts index 55e0ae13..c0b0b262 100644 --- a/packages/launchpad-ledger/src/ledgerwallet.ts +++ b/packages/launchpad-ledger/src/ledgersigner.ts @@ -2,16 +2,16 @@ import { AccountData, encodeSecp256k1Signature, OfflineSigner, StdSignature } fr import { LaunchpadLedger } from "./launchpadledger"; -interface LedgerWalletOptions { +interface LedgerSignerOptions { readonly testModeAllowed: boolean; } -export class LedgerWallet implements OfflineSigner { +export class LedgerSigner implements OfflineSigner { private readonly ledger: LaunchpadLedger; private address: string | undefined; private pubkey: Uint8Array | undefined; - constructor(options?: LedgerWalletOptions) { + constructor(options?: LedgerSignerOptions) { this.ledger = new LaunchpadLedger(options); } diff --git a/packages/launchpad-ledger/types/index.d.ts b/packages/launchpad-ledger/types/index.d.ts index f0148827..155dfd14 100644 --- a/packages/launchpad-ledger/types/index.d.ts +++ b/packages/launchpad-ledger/types/index.d.ts @@ -1,2 +1,2 @@ export { LaunchpadLedger } from "./launchpadledger"; -export { LedgerWallet } from "./ledgerwallet"; +export { LedgerSigner } from "./ledgersigner"; diff --git a/packages/launchpad-ledger/types/ledgerwallet.d.ts b/packages/launchpad-ledger/types/ledgersigner.d.ts similarity index 69% rename from packages/launchpad-ledger/types/ledgerwallet.d.ts rename to packages/launchpad-ledger/types/ledgersigner.d.ts index f47f48b2..1bb54aeb 100644 --- a/packages/launchpad-ledger/types/ledgerwallet.d.ts +++ b/packages/launchpad-ledger/types/ledgersigner.d.ts @@ -1,12 +1,12 @@ import { AccountData, OfflineSigner, StdSignature } from "@cosmjs/launchpad"; -interface LedgerWalletOptions { +interface LedgerSignerOptions { readonly testModeAllowed: boolean; } -export declare class LedgerWallet implements OfflineSigner { +export declare class LedgerSigner implements OfflineSigner { private readonly ledger; private address; private pubkey; - constructor(options?: LedgerWalletOptions); + constructor(options?: LedgerSignerOptions); getAccounts(): Promise; sign(address: string, message: Uint8Array): Promise; } From 738f0b3f65072d808031d090c72ca4f003ddc998 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 11:02:56 +0200 Subject: [PATCH 21/33] launchpad-ledger: Refactor LedgerSigner options --- .../launchpad-ledger/src/launchpadledger.ts | 21 ++++++++++++++----- packages/launchpad-ledger/src/ledgersigner.ts | 8 ++----- .../types/launchpadledger.d.ts | 15 ++++++------- .../launchpad-ledger/types/ledgersigner.d.ts | 7 ++----- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/packages/launchpad-ledger/src/launchpadledger.ts b/packages/launchpad-ledger/src/launchpadledger.ts index cf7a75e0..83019811 100644 --- a/packages/launchpad-ledger/src/launchpadledger.ts +++ b/packages/launchpad-ledger/src/launchpadledger.ts @@ -66,6 +66,12 @@ function unharden(hdPath: readonly Slip10RawIndex[]): number[] { const cosmosHdPath = makeCosmoshubPath(0); const cosmosBech32Prefix = "cosmos"; +export interface LaunchpadLedgerOptions { + readonly hdPath?: readonly Slip10RawIndex[]; + readonly prefix?: string; + readonly testModeAllowed?: boolean; +} + export class LaunchpadLedger { private readonly testModeAllowed: boolean; private readonly hdPath: readonly Slip10RawIndex[]; @@ -74,11 +80,16 @@ export class LaunchpadLedger { public readonly platform: string; public readonly userAgent: string; - constructor( - { testModeAllowed }: { testModeAllowed: boolean } = { testModeAllowed: false }, - hdPath: readonly Slip10RawIndex[] = cosmosHdPath, - prefix: string = cosmosBech32Prefix, - ) { + constructor(options: LaunchpadLedgerOptions = {}) { + const defaultOptions = { + hdPath: cosmosHdPath, + prefix: cosmosBech32Prefix, + testModeAllowed: false, + }; + const { hdPath, prefix, testModeAllowed } = { + ...defaultOptions, + ...options, + }; this.testModeAllowed = testModeAllowed; this.hdPath = hdPath; this.prefix = prefix; diff --git a/packages/launchpad-ledger/src/ledgersigner.ts b/packages/launchpad-ledger/src/ledgersigner.ts index c0b0b262..145f1ddc 100644 --- a/packages/launchpad-ledger/src/ledgersigner.ts +++ b/packages/launchpad-ledger/src/ledgersigner.ts @@ -1,17 +1,13 @@ import { AccountData, encodeSecp256k1Signature, OfflineSigner, StdSignature } from "@cosmjs/launchpad"; -import { LaunchpadLedger } from "./launchpadledger"; - -interface LedgerSignerOptions { - readonly testModeAllowed: boolean; -} +import { LaunchpadLedger, LaunchpadLedgerOptions } from "./launchpadledger"; export class LedgerSigner implements OfflineSigner { private readonly ledger: LaunchpadLedger; private address: string | undefined; private pubkey: Uint8Array | undefined; - constructor(options?: LedgerSignerOptions) { + constructor(options?: LaunchpadLedgerOptions) { this.ledger = new LaunchpadLedger(options); } diff --git a/packages/launchpad-ledger/types/launchpadledger.d.ts b/packages/launchpad-ledger/types/launchpadledger.d.ts index 4dbf3064..6dcaa64c 100644 --- a/packages/launchpad-ledger/types/launchpadledger.d.ts +++ b/packages/launchpad-ledger/types/launchpadledger.d.ts @@ -1,5 +1,10 @@ /// import { Slip10RawIndex } from "@cosmjs/crypto"; +export interface LaunchpadLedgerOptions { + readonly hdPath?: readonly Slip10RawIndex[]; + readonly prefix?: string; + readonly testModeAllowed?: boolean; +} export declare class LaunchpadLedger { private readonly testModeAllowed; private readonly hdPath; @@ -7,15 +12,7 @@ export declare class LaunchpadLedger { private cosmosApp; readonly platform: string; readonly userAgent: string; - constructor( - { - testModeAllowed, - }?: { - testModeAllowed: boolean; - }, - hdPath?: readonly Slip10RawIndex[], - prefix?: string, - ); + constructor(options?: LaunchpadLedgerOptions); connect(timeout?: number): Promise; getCosmosAppVersion(): Promise; getPubKey(): Promise; diff --git a/packages/launchpad-ledger/types/ledgersigner.d.ts b/packages/launchpad-ledger/types/ledgersigner.d.ts index 1bb54aeb..abf5b3cb 100644 --- a/packages/launchpad-ledger/types/ledgersigner.d.ts +++ b/packages/launchpad-ledger/types/ledgersigner.d.ts @@ -1,13 +1,10 @@ import { AccountData, OfflineSigner, StdSignature } from "@cosmjs/launchpad"; -interface LedgerSignerOptions { - readonly testModeAllowed: boolean; -} +import { LaunchpadLedgerOptions } from "./launchpadledger"; export declare class LedgerSigner implements OfflineSigner { private readonly ledger; private address; private pubkey; - constructor(options?: LedgerSignerOptions); + constructor(options?: LaunchpadLedgerOptions); getAccounts(): Promise; sign(address: string, message: Uint8Array): Promise; } -export {}; From 2ef93a0b261abfdda0df3fb425aa6dcbb08ecae0 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 11:18:42 +0200 Subject: [PATCH 22/33] launchpad-ledger: Refactor ledger pubkey/address methods --- .../launchpad-ledger/src/launchpadledger.ts | 10 ++++---- packages/launchpad-ledger/src/ledgersigner.ts | 25 +++++++++++++------ .../types/launchpadledger.d.ts | 5 ++-- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/packages/launchpad-ledger/src/launchpadledger.ts b/packages/launchpad-ledger/src/launchpadledger.ts index 83019811..ea2aa82d 100644 --- a/packages/launchpad-ledger/src/launchpadledger.ts +++ b/packages/launchpad-ledger/src/launchpadledger.ts @@ -135,19 +135,19 @@ export class LaunchpadLedger { return `${major}.${minor}.${patch}`; } - async getPubKey(): Promise { + async getPubkey(): Promise { await this.connect(); assert(this.cosmosApp, "Cosmos Ledger App is not connected"); // ledger-cosmos-js hardens the first three indices const response = await this.cosmosApp.publicKey(unharden(this.hdPath)); this.handleLedgerErrors(response); - return (response as PublicKeyResponse).compressed_pk; + return Uint8Array.from((response as PublicKeyResponse).compressed_pk); } - async getCosmosAddress(): Promise { - const pubKey = await this.getPubKey(); - return CosmosApp.getBech32FromPK(this.prefix, pubKey); + async getCosmosAddress(pubkey?: Uint8Array): Promise { + const pubkeyToUse = pubkey || (await this.getPubkey()); + return CosmosApp.getBech32FromPK(this.prefix, Buffer.from(pubkeyToUse)); } // async verifyLedgerAddress(): Promise { diff --git a/packages/launchpad-ledger/src/ledgersigner.ts b/packages/launchpad-ledger/src/ledgersigner.ts index 145f1ddc..ef6d83d8 100644 --- a/packages/launchpad-ledger/src/ledgersigner.ts +++ b/packages/launchpad-ledger/src/ledgersigner.ts @@ -14,14 +14,18 @@ export class LedgerSigner implements OfflineSigner { public async getAccounts(): Promise { await this.ledger.connect(); - const address = (this.address = this.address || (await this.ledger.getCosmosAddress())); - const pubkey = (this.pubkey = this.pubkey || (await this.ledger.getPubKey())); + if (!this.pubkey) { + this.pubkey = await this.ledger.getPubkey(); + } + if (!this.address) { + this.address = await this.ledger.getCosmosAddress(this.pubkey); + } return [ { algo: "secp256k1", - address: address, - pubkey: pubkey, + address: this.address, + pubkey: this.pubkey, }, ]; } @@ -29,13 +33,18 @@ export class LedgerSigner implements OfflineSigner { public async sign(address: string, message: Uint8Array): Promise { await this.ledger.connect(); - const thisAddress = (this.address = this.address || (await this.ledger.getCosmosAddress())); - if (address !== thisAddress) { + if (!this.pubkey) { + this.pubkey = await this.ledger.getPubkey(); + } + if (!this.address) { + this.address = await this.ledger.getCosmosAddress(this.pubkey); + } + + if (address !== this.address) { throw new Error(`Address ${address} not found in wallet`); } const signature = await this.ledger.sign(message); - const pubkey = (this.pubkey = this.pubkey || (await this.ledger.getPubKey())); - return encodeSecp256k1Signature(pubkey, signature); + return encodeSecp256k1Signature(this.pubkey, signature); } } diff --git a/packages/launchpad-ledger/types/launchpadledger.d.ts b/packages/launchpad-ledger/types/launchpadledger.d.ts index 6dcaa64c..b4f01c24 100644 --- a/packages/launchpad-ledger/types/launchpadledger.d.ts +++ b/packages/launchpad-ledger/types/launchpadledger.d.ts @@ -1,4 +1,3 @@ -/// import { Slip10RawIndex } from "@cosmjs/crypto"; export interface LaunchpadLedgerOptions { readonly hdPath?: readonly Slip10RawIndex[]; @@ -15,8 +14,8 @@ export declare class LaunchpadLedger { constructor(options?: LaunchpadLedgerOptions); connect(timeout?: number): Promise; getCosmosAppVersion(): Promise; - getPubKey(): Promise; - getCosmosAddress(): Promise; + getPubkey(): Promise; + getCosmosAddress(pubkey?: Uint8Array): Promise; sign(message: Uint8Array): Promise; private verifyAppMode; private getOpenAppName; From 4b9b40f69b4890f29cabbe678b423651db8afa3c Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 11:33:20 +0200 Subject: [PATCH 23/33] launchpad-ledger: Refactor demo structure --- packages/launchpad-ledger/{ledger-demo.css => demo/index.css} | 0 .../launchpad-ledger/{ledger-demo.html => demo/index.html} | 4 ++-- packages/launchpad-ledger/package.json | 2 +- .../launchpad-ledger/src/{ledger.demo.ts => demo/index.ts} | 2 +- packages/launchpad-ledger/webpack.web.config.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename packages/launchpad-ledger/{ledger-demo.css => demo/index.css} (100%) rename packages/launchpad-ledger/{ledger-demo.html => demo/index.html} (91%) rename packages/launchpad-ledger/src/{ledger.demo.ts => demo/index.ts} (96%) diff --git a/packages/launchpad-ledger/ledger-demo.css b/packages/launchpad-ledger/demo/index.css similarity index 100% rename from packages/launchpad-ledger/ledger-demo.css rename to packages/launchpad-ledger/demo/index.css diff --git a/packages/launchpad-ledger/ledger-demo.html b/packages/launchpad-ledger/demo/index.html similarity index 91% rename from packages/launchpad-ledger/ledger-demo.html rename to packages/launchpad-ledger/demo/index.html index ecff0065..5b9f4c1b 100644 --- a/packages/launchpad-ledger/ledger-demo.html +++ b/packages/launchpad-ledger/demo/index.html @@ -4,8 +4,8 @@ Ledger Demo - - + +
diff --git a/packages/launchpad-ledger/package.json b/packages/launchpad-ledger/package.json index 20720c62..fb08196c 100644 --- a/packages/launchpad-ledger/package.json +++ b/packages/launchpad-ledger/package.json @@ -28,7 +28,7 @@ "format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"", "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", - "move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.demo.d.ts", + "move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -rf ./types/demo", "format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"", "prebuild": "shx rm -rf ./build", "build": "tsc", diff --git a/packages/launchpad-ledger/src/ledger.demo.ts b/packages/launchpad-ledger/src/demo/index.ts similarity index 96% rename from packages/launchpad-ledger/src/ledger.demo.ts rename to packages/launchpad-ledger/src/demo/index.ts index 46a9259e..820a0105 100644 --- a/packages/launchpad-ledger/src/ledger.demo.ts +++ b/packages/launchpad-ledger/src/demo/index.ts @@ -1,6 +1,6 @@ import { toHex, toUtf8 } from "@cosmjs/encoding"; -import { LedgerSigner } from "./ledgersigner"; +import { LedgerSigner } from "../ledgersigner"; declare const window: any; declare const document: any; diff --git a/packages/launchpad-ledger/webpack.web.config.js b/packages/launchpad-ledger/webpack.web.config.js index fe7d59e8..8f6d7e36 100644 --- a/packages/launchpad-ledger/webpack.web.config.js +++ b/packages/launchpad-ledger/webpack.web.config.js @@ -20,7 +20,7 @@ module.exports = [ { // bundle used for Ledger demo target: target, - entry: glob.sync("./build/**/*.demo.js"), + entry: glob.sync("./build/demo/index.js"), output: { path: demodir, filename: "ledger.js", From e89c45663488b03a6b9e1502bf027bcaff4a94cd Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 11:51:17 +0200 Subject: [PATCH 24/33] launchpad-ledger: Add MsgSend to demo --- packages/launchpad-ledger/demo/index.css | 2 +- packages/launchpad-ledger/demo/index.html | 11 -------- packages/launchpad-ledger/src/demo/index.ts | 29 ++++++++++++++++++++- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/launchpad-ledger/demo/index.css b/packages/launchpad-ledger/demo/index.css index 4cc56f58..974bced3 100644 --- a/packages/launchpad-ledger/demo/index.css +++ b/packages/launchpad-ledger/demo/index.css @@ -20,5 +20,5 @@ input { textarea { width: 32em; - height: 14em; + height: 28em; } diff --git a/packages/launchpad-ledger/demo/index.html b/packages/launchpad-ledger/demo/index.html index 5b9f4c1b..1575c6bd 100644 --- a/packages/launchpad-ledger/demo/index.html +++ b/packages/launchpad-ledger/demo/index.html @@ -31,17 +31,6 @@
diff --git a/packages/launchpad-ledger/src/demo/index.ts b/packages/launchpad-ledger/src/demo/index.ts index 820a0105..8269c3a3 100644 --- a/packages/launchpad-ledger/src/demo/index.ts +++ b/packages/launchpad-ledger/src/demo/index.ts @@ -5,18 +5,45 @@ import { LedgerSigner } from "../ledgersigner"; declare const window: any; declare const document: any; +function createMessage(address: string): string { + return `{ + "account_number": 0, + "chain_id": "testing", + "fee": { + "amount": [{ "amount": 100, "denom": "ucosm" }], + "gas": 250 + }, + "memo": "Some memo", + "msgs": [{ + "type": "cosmos-sdk/MsgSend", + "value": { + "amount": [{ + "amount": "1234567", + "denom": "ucosm" + }], + "from_address": "${address}", + "to_address": "${address}" + } + }], + "sequence": 0 + }`; +} + const signer = new LedgerSigner({ testModeAllowed: true }); window.getAccounts = async function getAccounts(): Promise { const addressInput = document.getElementById("address"); const accountsDiv = document.getElementById("accounts"); + const messageTextArea = document.getElementById("message"); accountsDiv.textContent = "Loading..."; try { const accounts = await signer.getAccounts(); const prettyAccounts = accounts.map((account) => ({ ...account, pubkey: toHex(account.pubkey) })); accountsDiv.textContent = JSON.stringify(prettyAccounts, null, "\t"); - addressInput.value = accounts[0].address; + const address = accounts[0].address; + addressInput.value = address; + messageTextArea.textContent = createMessage(address); } catch (error) { accountsDiv.textContent = error; } From bf97908592fea1a21e8129c305eb1dddf7c77ba4 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 12:27:46 +0200 Subject: [PATCH 25/33] launchpad-ledger: Add demo instructions to README --- packages/launchpad-ledger/README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/launchpad-ledger/README.md b/packages/launchpad-ledger/README.md index 9b547645..4cd9ca24 100644 --- a/packages/launchpad-ledger/README.md +++ b/packages/launchpad-ledger/README.md @@ -2,6 +2,33 @@ [![npm version](https://img.shields.io/npm/v/@cosmjs/launchpad-ledger.svg)](https://www.npmjs.com/package/@cosmjs/launchpad-ledger) +## Supported platforms + +We use the +[@ledgerhq/hw-transport-webusb](https://github.com/LedgerHQ/ledgerjs/tree/master/packages/hw-transport-webusb) +library to connect to Ledger devices from the browser via USB. You can check the +support status of this library +[here](https://github.com/LedgerHQ/ledgerjs/tree/master/packages/hw-transport-webusb#support-status). + +## Running the demo + +Build the package for web: + +```sh +yarn pack-web +``` + +Host the `launchpad-ledger` package directory, for example using Python 3: + +```sh +python3 -m http.server +``` + +Visit the demo page in a browser, for example if using the Python 3 option: +[http://localhost:8000/demo](). + +Then follow the instructions on that page. + ## License This package is part of the cosmjs repository, licensed under the Apache License From f3f691d134ce3214c551bd1f8ee4cd1afebea455 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 12:28:02 +0200 Subject: [PATCH 26/33] launchpad-ledger: Tidy auxiliary files --- packages/launchpad-ledger/.nycrc.yml | 1 - .../launchpad-ledger/jasmine-testrunner.js | 33 ------------- packages/launchpad-ledger/karma.conf.js | 47 ------------------- packages/launchpad-ledger/package.json | 7 +-- packages/launchpad-ledger/typedoc.js | 2 +- .../launchpad-ledger/webpack.demo.config.js | 17 +++++++ .../launchpad-ledger/webpack.web.config.js | 29 ------------ 7 files changed, 20 insertions(+), 116 deletions(-) delete mode 120000 packages/launchpad-ledger/.nycrc.yml delete mode 100755 packages/launchpad-ledger/jasmine-testrunner.js delete mode 100644 packages/launchpad-ledger/karma.conf.js create mode 100644 packages/launchpad-ledger/webpack.demo.config.js delete mode 100644 packages/launchpad-ledger/webpack.web.config.js diff --git a/packages/launchpad-ledger/.nycrc.yml b/packages/launchpad-ledger/.nycrc.yml deleted file mode 120000 index 1f95ac55..00000000 --- a/packages/launchpad-ledger/.nycrc.yml +++ /dev/null @@ -1 +0,0 @@ -../../.nycrc.yml \ No newline at end of file diff --git a/packages/launchpad-ledger/jasmine-testrunner.js b/packages/launchpad-ledger/jasmine-testrunner.js deleted file mode 100755 index 7a17962e..00000000 --- a/packages/launchpad-ledger/jasmine-testrunner.js +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env node - -/* eslint-disable @typescript-eslint/naming-convention */ -require("source-map-support").install(); -const defaultSpecReporterConfig = require("../../jasmine-spec-reporter.config.json"); - -// setup Jasmine -const Jasmine = require("jasmine"); -const jasmine = new Jasmine(); -jasmine.loadConfig({ - spec_dir: "build", - spec_files: ["**/*.spec.js"], - helpers: [], - random: false, - seed: null, - stopSpecOnExpectationFailure: false, -}); -jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 1000; - -// setup reporter -const { SpecReporter } = require("jasmine-spec-reporter"); -const reporter = new SpecReporter({ - ...defaultSpecReporterConfig, - spec: { - ...defaultSpecReporterConfig.spec, - displaySuccessful: !process.argv.includes("--quiet"), - }, -}); - -// initialize and execute -jasmine.env.clearReporters(); -jasmine.addReporter(reporter); -jasmine.execute(); diff --git a/packages/launchpad-ledger/karma.conf.js b/packages/launchpad-ledger/karma.conf.js deleted file mode 100644 index 006da5fe..00000000 --- a/packages/launchpad-ledger/karma.conf.js +++ /dev/null @@ -1,47 +0,0 @@ -module.exports = function (config) { - config.set({ - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: ".", - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ["jasmine"], - - // list of files / patterns to load in the browser - files: ["dist/web/tests.js"], - - client: { - jasmine: { - random: false, - timeoutInterval: 15000, - }, - }, - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ["progress", "kjhtml"], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: false, - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ["Firefox"], - - browserNoActivityTimeout: 90000, - - // Keep brower open for debugging. This is overridden by yarn scripts - singleRun: false, - }); -}; diff --git a/packages/launchpad-ledger/package.json b/packages/launchpad-ledger/package.json index fb08196c..0fdc2129 100644 --- a/packages/launchpad-ledger/package.json +++ b/packages/launchpad-ledger/package.json @@ -34,12 +34,9 @@ "build": "tsc", "postbuild": "yarn move-types && yarn format-types", "build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build", - "test-node": "node jasmine-testrunner.js", - "test-firefox": "yarn pack-web && karma start --single-run --browsers Firefox", - "test-chrome": "yarn pack-web && karma start --single-run --browsers ChromeHeadless", - "test": "yarn build-or-skip && yarn test-node", + "test": "echo 'Please check README for information on how to manually run the demo'", "coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet", - "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" + "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.demo.config.js" }, "dependencies": { "@cosmjs/launchpad": "^0.22.2", diff --git a/packages/launchpad-ledger/typedoc.js b/packages/launchpad-ledger/typedoc.js index 4dfbe49d..3b3d649e 100644 --- a/packages/launchpad-ledger/typedoc.js +++ b/packages/launchpad-ledger/typedoc.js @@ -3,7 +3,7 @@ const packageJson = require("./package.json"); module.exports = { inputFiles: ["./src"], out: "docs", - exclude: "**/*.spec.ts", + exclude: ["**/*.spec.ts", "./src/demo"], name: `${packageJson.name} Documentation`, readme: "README.md", mode: "file", diff --git a/packages/launchpad-ledger/webpack.demo.config.js b/packages/launchpad-ledger/webpack.demo.config.js new file mode 100644 index 00000000..e16bea23 --- /dev/null +++ b/packages/launchpad-ledger/webpack.demo.config.js @@ -0,0 +1,17 @@ +const glob = require("glob"); +const path = require("path"); + +const target = "web"; +const demodir = path.join(__dirname, "dist", "demo"); + +module.exports = [ + { + // bundle used for Ledger demo + target: target, + entry: glob.sync("./build/demo/index.js"), + output: { + path: demodir, + filename: "ledger.js", + }, + }, +]; diff --git a/packages/launchpad-ledger/webpack.web.config.js b/packages/launchpad-ledger/webpack.web.config.js deleted file mode 100644 index 8f6d7e36..00000000 --- a/packages/launchpad-ledger/webpack.web.config.js +++ /dev/null @@ -1,29 +0,0 @@ -const glob = require("glob"); -const path = require("path"); -// const webpack = require("webpack"); - -const target = "web"; -// const distdir = path.join(__dirname, "dist", "web"); -const demodir = path.join(__dirname, "dist", "demo"); - -module.exports = [ - // { - // // bundle used for Karma tests - // target: target, - // entry: glob.sync("./build/**/*.spec.js"), - // output: { - // path: distdir, - // filename: "tests.js", - // }, - // plugins: [new webpack.EnvironmentPlugin(["WASMD_ENABLED"])], - // }, - { - // bundle used for Ledger demo - target: target, - entry: glob.sync("./build/demo/index.js"), - output: { - path: demodir, - filename: "ledger.js", - }, - }, -]; From 74141806074006a7150c7c15b8dc077f9e7b4dce Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 12:48:13 +0200 Subject: [PATCH 27/33] launchpad-ledger: Remove unused commented code --- .../launchpad-ledger/src/launchpadledger.ts | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/packages/launchpad-ledger/src/launchpadledger.ts b/packages/launchpad-ledger/src/launchpadledger.ts index ea2aa82d..6b078d0f 100644 --- a/packages/launchpad-ledger/src/launchpadledger.ts +++ b/packages/launchpad-ledger/src/launchpadledger.ts @@ -98,16 +98,6 @@ export class LaunchpadLedger { this.userAgent = navigator.userAgent; } - // // quickly test connection and compatibility with the LaunchpadLedger device throwing away the connection - // async testDevice(): Promise { - // // poll device with low timeout to check if the device is connected - // const secondsTimeout = 3; // a lower value always timeouts - // await this.connect(secondsTimeout); - // this.cosmosApp = null; - - // return this; - // } - async connect(timeout = defaultInteractionTimeout): Promise { // assume good connection if connected once if (this.cosmosApp) { @@ -150,16 +140,6 @@ export class LaunchpadLedger { return CosmosApp.getBech32FromPK(this.prefix, Buffer.from(pubkeyToUse)); } - // async verifyLedgerAddress(): Promise { - // await this.connect(); - // assert(this.cosmosApp, "Cosmos Ledger App is not connected"); - - // const response = await this.cosmosApp.showAddressAndPubKey(this.hdPath, this.bech32Prefix); - // this.handleLedgerErrors(response, { - // rejectionMessage: "Displayed address was rejected by the user", - // }); - // } - async sign(message: Uint8Array): Promise { await this.connect(); assert(this.cosmosApp, "Cosmos Ledger App is not connected"); From cb19a54233086c30369c27d103ae2107dafb20c0 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 13:31:20 +0200 Subject: [PATCH 28/33] crypto: Add HdPath type alias --- packages/crypto/src/index.ts | 1 + packages/crypto/src/slip10.spec.ts | 56 +++++++++++++----------------- packages/crypto/src/slip10.ts | 12 +++---- packages/crypto/types/index.d.ts | 1 + packages/crypto/types/slip10.d.ts | 7 ++-- 5 files changed, 36 insertions(+), 41 deletions(-) diff --git a/packages/crypto/src/index.ts b/packages/crypto/src/index.ts index 925865b3..bd19288f 100644 --- a/packages/crypto/src/index.ts +++ b/packages/crypto/src/index.ts @@ -18,6 +18,7 @@ export { Secp256k1, Secp256k1Keypair } from "./secp256k1"; export { ExtendedSecp256k1Signature, Secp256k1Signature } from "./secp256k1signature"; export { Sha1, Sha256, Sha512 } from "./sha"; export { + HdPath, pathToString, stringToPath, Slip10, diff --git a/packages/crypto/src/slip10.spec.ts b/packages/crypto/src/slip10.spec.ts index 52718df8..7dc72565 100644 --- a/packages/crypto/src/slip10.spec.ts +++ b/packages/crypto/src/slip10.spec.ts @@ -1,6 +1,7 @@ import { fromHex } from "@cosmjs/encoding"; import { + HdPath, pathToString, Slip10, Slip10Curve, @@ -22,7 +23,7 @@ describe("Slip10", () => { const seed = fromHex("000102030405060708090a0b0c0d0e0f"); it("can derive path m", () => { - const path: readonly Slip10RawIndex[] = []; + const path: HdPath = []; const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path); expect(derived.chainCode).toEqual( fromHex("873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508"), @@ -33,7 +34,7 @@ describe("Slip10", () => { }); it("can derive path m/0'", () => { - const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0)]; + const path: HdPath = [Slip10RawIndex.hardened(0)]; const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path); expect(derived.chainCode).toEqual( fromHex("47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141"), @@ -44,7 +45,7 @@ describe("Slip10", () => { }); it("can derive path m/0'/1", () => { - const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0), Slip10RawIndex.normal(1)]; + const path: HdPath = [Slip10RawIndex.hardened(0), Slip10RawIndex.normal(1)]; const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path); expect(derived.chainCode).toEqual( fromHex("2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19"), @@ -55,11 +56,7 @@ describe("Slip10", () => { }); it("can derive path m/0'/1/2'", () => { - const path: readonly Slip10RawIndex[] = [ - Slip10RawIndex.hardened(0), - Slip10RawIndex.normal(1), - Slip10RawIndex.hardened(2), - ]; + const path: HdPath = [Slip10RawIndex.hardened(0), Slip10RawIndex.normal(1), Slip10RawIndex.hardened(2)]; const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path); expect(derived.chainCode).toEqual( fromHex("04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f"), @@ -70,7 +67,7 @@ describe("Slip10", () => { }); it("can derive path m/0'/1/2'/2", () => { - const path: readonly Slip10RawIndex[] = [ + const path: HdPath = [ Slip10RawIndex.hardened(0), Slip10RawIndex.normal(1), Slip10RawIndex.hardened(2), @@ -86,7 +83,7 @@ describe("Slip10", () => { }); it("can derive path m/0'/1/2'/2/1000000000", () => { - const path: readonly Slip10RawIndex[] = [ + const path: HdPath = [ Slip10RawIndex.hardened(0), Slip10RawIndex.normal(1), Slip10RawIndex.hardened(2), @@ -110,7 +107,7 @@ describe("Slip10", () => { ); it("can derive path m", () => { - const path: readonly Slip10RawIndex[] = []; + const path: HdPath = []; const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path); expect(derived.chainCode).toEqual( fromHex("60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689"), @@ -121,7 +118,7 @@ describe("Slip10", () => { }); it("can derive path m/0", () => { - const path: readonly Slip10RawIndex[] = [Slip10RawIndex.normal(0)]; + const path: HdPath = [Slip10RawIndex.normal(0)]; const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path); expect(derived.chainCode).toEqual( fromHex("f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c"), @@ -132,7 +129,7 @@ describe("Slip10", () => { }); it("can derive path m/0/2147483647'", () => { - const path: readonly Slip10RawIndex[] = [Slip10RawIndex.normal(0), Slip10RawIndex.hardened(2147483647)]; + const path: HdPath = [Slip10RawIndex.normal(0), Slip10RawIndex.hardened(2147483647)]; const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path); expect(derived.chainCode).toEqual( fromHex("be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9"), @@ -143,7 +140,7 @@ describe("Slip10", () => { }); it("can derive path m/0/2147483647'/1", () => { - const path: readonly Slip10RawIndex[] = [ + const path: HdPath = [ Slip10RawIndex.normal(0), Slip10RawIndex.hardened(2147483647), Slip10RawIndex.normal(1), @@ -158,7 +155,7 @@ describe("Slip10", () => { }); it("can derive path m/0/2147483647'/1/2147483646'", () => { - const path: readonly Slip10RawIndex[] = [ + const path: HdPath = [ Slip10RawIndex.normal(0), Slip10RawIndex.hardened(2147483647), Slip10RawIndex.normal(1), @@ -174,7 +171,7 @@ describe("Slip10", () => { }); it("can derive path m/0/2147483647'/1/2147483646'/2", () => { - const path: readonly Slip10RawIndex[] = [ + const path: HdPath = [ Slip10RawIndex.normal(0), Slip10RawIndex.hardened(2147483647), Slip10RawIndex.normal(1), @@ -196,7 +193,7 @@ describe("Slip10", () => { const seed = fromHex("000102030405060708090a0b0c0d0e0f"); it("can derive path m", () => { - const path: readonly Slip10RawIndex[] = []; + const path: HdPath = []; const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path); expect(derived.chainCode).toEqual( fromHex("90046a93de5380a72b5e45010748567d5ea02bbf6522f979e05c0d8d8ca9fffb"), @@ -207,7 +204,7 @@ describe("Slip10", () => { }); it("can derive path m/0'", () => { - const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0)]; + const path: HdPath = [Slip10RawIndex.hardened(0)]; const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path); expect(derived.chainCode).toEqual( fromHex("8b59aa11380b624e81507a27fedda59fea6d0b779a778918a2fd3590e16e9c69"), @@ -218,7 +215,7 @@ describe("Slip10", () => { }); it("can derive path m/0'/1'", () => { - const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0), Slip10RawIndex.hardened(1)]; + const path: HdPath = [Slip10RawIndex.hardened(0), Slip10RawIndex.hardened(1)]; const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path); expect(derived.chainCode).toEqual( fromHex("a320425f77d1b5c2505a6b1b27382b37368ee640e3557c315416801243552f14"), @@ -229,7 +226,7 @@ describe("Slip10", () => { }); it("can derive path m/0'/1'/2'", () => { - const path: readonly Slip10RawIndex[] = [ + const path: HdPath = [ Slip10RawIndex.hardened(0), Slip10RawIndex.hardened(1), Slip10RawIndex.hardened(2), @@ -244,7 +241,7 @@ describe("Slip10", () => { }); it("can derive path m/0'/1'/2'/2'", () => { - const path: readonly Slip10RawIndex[] = [ + const path: HdPath = [ Slip10RawIndex.hardened(0), Slip10RawIndex.hardened(1), Slip10RawIndex.hardened(2), @@ -260,7 +257,7 @@ describe("Slip10", () => { }); it("can derive path m/0'/1'/2'/2'/1000000000'", () => { - const path: readonly Slip10RawIndex[] = [ + const path: HdPath = [ Slip10RawIndex.hardened(0), Slip10RawIndex.hardened(1), Slip10RawIndex.hardened(2), @@ -284,7 +281,7 @@ describe("Slip10", () => { ); it("can derive path m", () => { - const path: readonly Slip10RawIndex[] = []; + const path: HdPath = []; const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path); expect(derived.chainCode).toEqual( fromHex("ef70a74db9c3a5af931b5fe73ed8e1a53464133654fd55e7a66f8570b8e33c3b"), @@ -295,7 +292,7 @@ describe("Slip10", () => { }); it("can derive path m/0'", () => { - const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0)]; + const path: HdPath = [Slip10RawIndex.hardened(0)]; const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path); expect(derived.chainCode).toEqual( fromHex("0b78a3226f915c082bf118f83618a618ab6dec793752624cbeb622acb562862d"), @@ -306,10 +303,7 @@ describe("Slip10", () => { }); it("can derive path m/0'/2147483647'", () => { - const path: readonly Slip10RawIndex[] = [ - Slip10RawIndex.hardened(0), - Slip10RawIndex.hardened(2147483647), - ]; + const path: HdPath = [Slip10RawIndex.hardened(0), Slip10RawIndex.hardened(2147483647)]; const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path); expect(derived.chainCode).toEqual( fromHex("138f0b2551bcafeca6ff2aa88ba8ed0ed8de070841f0c4ef0165df8181eaad7f"), @@ -320,7 +314,7 @@ describe("Slip10", () => { }); it("can derive path m/0'/2147483647'/1'", () => { - const path: readonly Slip10RawIndex[] = [ + const path: HdPath = [ Slip10RawIndex.hardened(0), Slip10RawIndex.hardened(2147483647), Slip10RawIndex.hardened(1), @@ -335,7 +329,7 @@ describe("Slip10", () => { }); it("can derive path m/0'/2147483647'/1'/2147483646'", () => { - const path: readonly Slip10RawIndex[] = [ + const path: HdPath = [ Slip10RawIndex.hardened(0), Slip10RawIndex.hardened(2147483647), Slip10RawIndex.hardened(1), @@ -351,7 +345,7 @@ describe("Slip10", () => { }); it("can derive path m/0'/2147483647'/1'/2147483646'/2'", () => { - const path: readonly Slip10RawIndex[] = [ + const path: HdPath = [ Slip10RawIndex.hardened(0), Slip10RawIndex.hardened(2147483647), Slip10RawIndex.hardened(1), diff --git a/packages/crypto/src/slip10.ts b/packages/crypto/src/slip10.ts index 9f507954..39a0bd8a 100644 --- a/packages/crypto/src/slip10.ts +++ b/packages/crypto/src/slip10.ts @@ -49,16 +49,14 @@ export class Slip10RawIndex extends Uint32 { } } +export type HdPath = readonly Slip10RawIndex[]; + const secp256k1 = new elliptic.ec("secp256k1"); // Universal private key derivation accoring to // https://github.com/satoshilabs/slips/blob/master/slip-0010.md export class Slip10 { - public static derivePath( - curve: Slip10Curve, - seed: Uint8Array, - path: readonly Slip10RawIndex[], - ): Slip10Result { + public static derivePath(curve: Slip10Curve, seed: Uint8Array, path: HdPath): Slip10Result { let result = this.master(curve, seed); for (const rawIndex of path) { result = this.child(curve, result.privkey, result.chainCode, rawIndex); @@ -185,7 +183,7 @@ export class Slip10 { } } -export function pathToString(path: readonly Slip10RawIndex[]): string { +export function pathToString(path: HdPath): string { return path.reduce((current, component): string => { const componentString = component.isHardened() ? `${component.toNumber() - 2 ** 31}'` @@ -194,7 +192,7 @@ export function pathToString(path: readonly Slip10RawIndex[]): string { }, "m"); } -export function stringToPath(input: string): readonly Slip10RawIndex[] { +export function stringToPath(input: string): HdPath { if (!input.startsWith("m")) throw new Error("Path string must start with 'm'"); let rest = input.slice(1); diff --git a/packages/crypto/types/index.d.ts b/packages/crypto/types/index.d.ts index 925865b3..bd19288f 100644 --- a/packages/crypto/types/index.d.ts +++ b/packages/crypto/types/index.d.ts @@ -18,6 +18,7 @@ export { Secp256k1, Secp256k1Keypair } from "./secp256k1"; export { ExtendedSecp256k1Signature, Secp256k1Signature } from "./secp256k1signature"; export { Sha1, Sha256, Sha512 } from "./sha"; export { + HdPath, pathToString, stringToPath, Slip10, diff --git a/packages/crypto/types/slip10.d.ts b/packages/crypto/types/slip10.d.ts index ffd92295..cd90a65f 100644 --- a/packages/crypto/types/slip10.d.ts +++ b/packages/crypto/types/slip10.d.ts @@ -21,8 +21,9 @@ export declare class Slip10RawIndex extends Uint32 { static normal(normalIndex: number): Slip10RawIndex; isHardened(): boolean; } +export declare type HdPath = readonly Slip10RawIndex[]; export declare class Slip10 { - static derivePath(curve: Slip10Curve, seed: Uint8Array, path: readonly Slip10RawIndex[]): Slip10Result; + static derivePath(curve: Slip10Curve, seed: Uint8Array, path: HdPath): Slip10Result; private static master; private static child; /** @@ -36,5 +37,5 @@ export declare class Slip10 { private static isGteN; private static n; } -export declare function pathToString(path: readonly Slip10RawIndex[]): string; -export declare function stringToPath(input: string): readonly Slip10RawIndex[]; +export declare function pathToString(path: HdPath): string; +export declare function stringToPath(input: string): HdPath; From bfbb01c6bfe9f68dd9a21508e6854e7443ea9303 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 13:33:58 +0200 Subject: [PATCH 29/33] cli: Use HdPath type --- packages/cli/examples/coralnet.ts | 12 ++++++------ packages/cli/src/cli.ts | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/cli/examples/coralnet.ts b/packages/cli/examples/coralnet.ts index 9a489f66..c6c9117c 100644 --- a/packages/cli/examples/coralnet.ts +++ b/packages/cli/examples/coralnet.ts @@ -1,20 +1,20 @@ interface Options { readonly httpUrl: string; readonly bech32prefix: string; - readonly hdPath: readonly Slip10RawIndex[]; + readonly hdPath: HdPath; readonly gasPrice: GasPrice; readonly gasLimits: Partial>; // only set the ones you want to override } const coralnetOptions: Options = { - httpUrl: 'https://lcd.coralnet.cosmwasm.com', + httpUrl: "https://lcd.coralnet.cosmwasm.com", gasPrice: GasPrice.fromString("0.025ushell"), - bech32prefix: 'coral', + bech32prefix: "coral", hdPath: makeCosmoshubPath(0), - gasLimits: { + gasLimits: { upload: 1500000, - } -} + }, +}; const wallet = await Secp256k1Wallet.generate(12, coralnetOptions.hdPath, coralnetOptions.bech32prefix); const [{ address }] = await wallet.getAccounts(); diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index e84a737e..b338bce1 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -71,6 +71,7 @@ export async function main(originalArgs: readonly string[]): Promise { "Ed25519", "Ed25519Keypair", "EnglishMnemonic", + "HdPath", "Random", "Secp256k1", "Sha256", From 83a17752843654ed993a69f33171ffdc573f4e19 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 13:34:29 +0200 Subject: [PATCH 30/33] launchpad: Use HdPath type --- packages/launchpad/src/secp256k1wallet.ts | 10 +++++----- packages/launchpad/src/wallet.ts | 3 ++- packages/launchpad/types/secp256k1wallet.d.ts | 10 +++------- packages/launchpad/types/wallet.d.ts | 4 ++-- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/launchpad/src/secp256k1wallet.ts b/packages/launchpad/src/secp256k1wallet.ts index 6c393cfa..5f5aea9a 100644 --- a/packages/launchpad/src/secp256k1wallet.ts +++ b/packages/launchpad/src/secp256k1wallet.ts @@ -1,12 +1,12 @@ import { Bip39, EnglishMnemonic, + HdPath, pathToString, Random, Secp256k1, Slip10, Slip10Curve, - Slip10RawIndex, stringToPath, } from "@cosmjs/crypto"; import { fromBase64, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding"; @@ -104,7 +104,7 @@ export function extractKdfConfiguration(serialization: string): KdfConfiguration * Derivation information required to derive a keypair and an address from a mnemonic. */ interface Secp256k1Derivation { - readonly hdPath: readonly Slip10RawIndex[]; + readonly hdPath: HdPath; readonly prefix: string; } @@ -118,7 +118,7 @@ export class Secp256k1Wallet implements OfflineSigner { */ public static async fromMnemonic( mnemonic: string, - hdPath: readonly Slip10RawIndex[] = makeCosmoshubPath(0), + hdPath: HdPath = makeCosmoshubPath(0), prefix = "cosmos", ): Promise { const mnemonicChecked = new EnglishMnemonic(mnemonic); @@ -143,7 +143,7 @@ export class Secp256k1Wallet implements OfflineSigner { */ public static async generate( length: 12 | 15 | 18 | 21 | 24 = 12, - hdPath: readonly Slip10RawIndex[] = makeCosmoshubPath(0), + hdPath: HdPath = makeCosmoshubPath(0), prefix = "cosmos", ): Promise { const entropyLength = 4 * Math.floor((11 * length) / 33); @@ -223,7 +223,7 @@ export class Secp256k1Wallet implements OfflineSigner { private constructor( mnemonic: EnglishMnemonic, - hdPath: readonly Slip10RawIndex[], + hdPath: HdPath, privkey: Uint8Array, pubkey: Uint8Array, prefix: string, diff --git a/packages/launchpad/src/wallet.ts b/packages/launchpad/src/wallet.ts index f8e4b1ab..eaded173 100644 --- a/packages/launchpad/src/wallet.ts +++ b/packages/launchpad/src/wallet.ts @@ -1,5 +1,6 @@ import { Argon2id, + HdPath, isArgon2idOptions, Random, Sha256, @@ -52,7 +53,7 @@ export function prehash(bytes: Uint8Array, type: PrehashType): Uint8Array { * The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a` * with 0-based account index `a`. */ -export function makeCosmoshubPath(a: number): readonly Slip10RawIndex[] { +export function makeCosmoshubPath(a: number): HdPath { return [ Slip10RawIndex.hardened(44), Slip10RawIndex.hardened(118), diff --git a/packages/launchpad/types/secp256k1wallet.d.ts b/packages/launchpad/types/secp256k1wallet.d.ts index 06e9510c..a24a3366 100644 --- a/packages/launchpad/types/secp256k1wallet.d.ts +++ b/packages/launchpad/types/secp256k1wallet.d.ts @@ -1,4 +1,4 @@ -import { Slip10RawIndex } from "@cosmjs/crypto"; +import { HdPath } from "@cosmjs/crypto"; import { StdSignature } from "./types"; import { AccountData, EncryptionConfiguration, KdfConfiguration, OfflineSigner, PrehashType } from "./wallet"; /** @@ -40,11 +40,7 @@ export declare class Secp256k1Wallet implements OfflineSigner { * @param hdPath The BIP-32/SLIP-10 derivation path. Defaults to the Cosmos Hub/ATOM path `m/44'/118'/0'/0/0`. * @param prefix The bech32 address prefix (human readable part). Defaults to "cosmos". */ - static fromMnemonic( - mnemonic: string, - hdPath?: readonly Slip10RawIndex[], - prefix?: string, - ): Promise; + static fromMnemonic(mnemonic: string, hdPath?: HdPath, prefix?: string): Promise; /** * Generates a new wallet with a BIP39 mnemonic of the given length. * @@ -54,7 +50,7 @@ export declare class Secp256k1Wallet implements OfflineSigner { */ static generate( length?: 12 | 15 | 18 | 21 | 24, - hdPath?: readonly Slip10RawIndex[], + hdPath?: HdPath, prefix?: string, ): Promise; /** diff --git a/packages/launchpad/types/wallet.d.ts b/packages/launchpad/types/wallet.d.ts index c0818ef2..5c284f5d 100644 --- a/packages/launchpad/types/wallet.d.ts +++ b/packages/launchpad/types/wallet.d.ts @@ -1,4 +1,4 @@ -import { Slip10RawIndex } from "@cosmjs/crypto"; +import { HdPath } from "@cosmjs/crypto"; import { StdSignature } from "./types"; export declare type PrehashType = "sha256" | "sha512" | null; export declare type Algo = "secp256k1" | "ed25519" | "sr25519"; @@ -22,7 +22,7 @@ export declare function prehash(bytes: Uint8Array, type: PrehashType): Uint8Arra * The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a` * with 0-based account index `a`. */ -export declare function makeCosmoshubPath(a: number): readonly Slip10RawIndex[]; +export declare function makeCosmoshubPath(a: number): HdPath; /** * A fixed salt is chosen to archive a deterministic password to key derivation. * This reduces the scope of a potential rainbow attack to all CosmJS users. From 8e7cadc605c89685c3de7cad11eeb3c867a361d7 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 13:27:36 +0200 Subject: [PATCH 31/33] launchpad-ledger: Allow multiple HD paths in Ledger --- .../launchpad-ledger/src/launchpadledger.ts | 28 ++++++++------ packages/launchpad-ledger/src/ledgersigner.ts | 37 ++++++++----------- .../types/launchpadledger.d.ts | 11 +++--- .../launchpad-ledger/types/ledgersigner.d.ts | 3 +- 4 files changed, 39 insertions(+), 40 deletions(-) diff --git a/packages/launchpad-ledger/src/launchpadledger.ts b/packages/launchpad-ledger/src/launchpadledger.ts index 6b078d0f..b3c8bf05 100644 --- a/packages/launchpad-ledger/src/launchpadledger.ts +++ b/packages/launchpad-ledger/src/launchpadledger.ts @@ -1,4 +1,4 @@ -import { Secp256k1Signature, Slip10RawIndex } from "@cosmjs/crypto"; +import { HdPath, Secp256k1Signature } from "@cosmjs/crypto"; import { fromUtf8 } from "@cosmjs/encoding"; import { makeCosmoshubPath } from "@cosmjs/launchpad"; import { assert } from "@cosmjs/utils"; @@ -59,7 +59,7 @@ async function createTransport(timeout: number): Promise { } } -function unharden(hdPath: readonly Slip10RawIndex[]): number[] { +function unharden(hdPath: HdPath): number[] { return hdPath.map((n) => (n.isHardened() ? n.toNumber() - 2 ** 31 : n.toNumber())); } @@ -67,14 +67,14 @@ const cosmosHdPath = makeCosmoshubPath(0); const cosmosBech32Prefix = "cosmos"; export interface LaunchpadLedgerOptions { - readonly hdPath?: readonly Slip10RawIndex[]; + readonly hdPaths?: readonly HdPath[]; readonly prefix?: string; readonly testModeAllowed?: boolean; } export class LaunchpadLedger { private readonly testModeAllowed: boolean; - private readonly hdPath: readonly Slip10RawIndex[]; + private readonly hdPaths: readonly HdPath[]; private readonly prefix: string; private cosmosApp: CosmosApp | null; public readonly platform: string; @@ -82,16 +82,16 @@ export class LaunchpadLedger { constructor(options: LaunchpadLedgerOptions = {}) { const defaultOptions = { - hdPath: cosmosHdPath, + hdPaths: [cosmosHdPath], prefix: cosmosBech32Prefix, testModeAllowed: false, }; - const { hdPath, prefix, testModeAllowed } = { + const { hdPaths, prefix, testModeAllowed } = { ...defaultOptions, ...options, }; this.testModeAllowed = testModeAllowed; - this.hdPath = hdPath; + this.hdPaths = hdPaths; this.prefix = prefix; this.cosmosApp = null; this.platform = navigator.platform; @@ -125,27 +125,33 @@ export class LaunchpadLedger { return `${major}.${minor}.${patch}`; } - async getPubkey(): Promise { + async getPubkey(hdPath?: HdPath): Promise { await this.connect(); assert(this.cosmosApp, "Cosmos Ledger App is not connected"); + const hdPathToUse = hdPath || this.hdPaths[0]; // ledger-cosmos-js hardens the first three indices - const response = await this.cosmosApp.publicKey(unharden(this.hdPath)); + const response = await this.cosmosApp.publicKey(unharden(hdPathToUse)); this.handleLedgerErrors(response); return Uint8Array.from((response as PublicKeyResponse).compressed_pk); } + async getPubkeys(): Promise { + return Promise.all(this.hdPaths.map(async (hdPath) => this.getPubkey(hdPath))); + } + async getCosmosAddress(pubkey?: Uint8Array): Promise { const pubkeyToUse = pubkey || (await this.getPubkey()); return CosmosApp.getBech32FromPK(this.prefix, Buffer.from(pubkeyToUse)); } - async sign(message: Uint8Array): Promise { + async sign(message: Uint8Array, hdPath?: HdPath): Promise { await this.connect(); assert(this.cosmosApp, "Cosmos Ledger App is not connected"); + const hdPathToUse = hdPath || this.hdPaths[0]; // ledger-cosmos-js hardens the first three indices - const response = await this.cosmosApp.sign(unharden(this.hdPath), fromUtf8(message)); + const response = await this.cosmosApp.sign(unharden(hdPathToUse), fromUtf8(message)); this.handleLedgerErrors(response, { rejectionMessage: "Transaction signing request was rejected by the user", }); diff --git a/packages/launchpad-ledger/src/ledgersigner.ts b/packages/launchpad-ledger/src/ledgersigner.ts index ef6d83d8..836c7480 100644 --- a/packages/launchpad-ledger/src/ledgersigner.ts +++ b/packages/launchpad-ledger/src/ledgersigner.ts @@ -4,8 +4,7 @@ import { LaunchpadLedger, LaunchpadLedgerOptions } from "./launchpadledger"; export class LedgerSigner implements OfflineSigner { private readonly ledger: LaunchpadLedger; - private address: string | undefined; - private pubkey: Uint8Array | undefined; + private accounts?: readonly AccountData[]; constructor(options?: LaunchpadLedgerOptions) { this.ledger = new LaunchpadLedger(options); @@ -14,37 +13,31 @@ export class LedgerSigner implements OfflineSigner { public async getAccounts(): Promise { await this.ledger.connect(); - if (!this.pubkey) { - this.pubkey = await this.ledger.getPubkey(); - } - if (!this.address) { - this.address = await this.ledger.getCosmosAddress(this.pubkey); + if (!this.accounts) { + const pubkeys = await this.ledger.getPubkeys(); + this.accounts = await Promise.all( + pubkeys.map(async (pubkey) => ({ + algo: "secp256k1" as const, + address: await this.ledger.getCosmosAddress(pubkey), + pubkey: pubkey, + })), + ); } - return [ - { - algo: "secp256k1", - address: this.address, - pubkey: this.pubkey, - }, - ]; + return this.accounts; } public async sign(address: string, message: Uint8Array): Promise { await this.ledger.connect(); - if (!this.pubkey) { - this.pubkey = await this.ledger.getPubkey(); - } - if (!this.address) { - this.address = await this.ledger.getCosmosAddress(this.pubkey); - } + const accounts = this.accounts || (await this.getAccounts()); + const accountForAddress = accounts.find((account) => account.address === address); - if (address !== this.address) { + if (!accountForAddress) { throw new Error(`Address ${address} not found in wallet`); } const signature = await this.ledger.sign(message); - return encodeSecp256k1Signature(this.pubkey, signature); + return encodeSecp256k1Signature(accountForAddress.pubkey, signature); } } diff --git a/packages/launchpad-ledger/types/launchpadledger.d.ts b/packages/launchpad-ledger/types/launchpadledger.d.ts index b4f01c24..c9167fc3 100644 --- a/packages/launchpad-ledger/types/launchpadledger.d.ts +++ b/packages/launchpad-ledger/types/launchpadledger.d.ts @@ -1,12 +1,12 @@ -import { Slip10RawIndex } from "@cosmjs/crypto"; +import { HdPath } from "@cosmjs/crypto"; export interface LaunchpadLedgerOptions { - readonly hdPath?: readonly Slip10RawIndex[]; + readonly hdPaths?: readonly HdPath[]; readonly prefix?: string; readonly testModeAllowed?: boolean; } export declare class LaunchpadLedger { private readonly testModeAllowed; - private readonly hdPath; + private readonly hdPaths; private readonly prefix; private cosmosApp; readonly platform: string; @@ -14,9 +14,10 @@ export declare class LaunchpadLedger { constructor(options?: LaunchpadLedgerOptions); connect(timeout?: number): Promise; getCosmosAppVersion(): Promise; - getPubkey(): Promise; + getPubkey(hdPath?: HdPath): Promise; + getPubkeys(): Promise; getCosmosAddress(pubkey?: Uint8Array): Promise; - sign(message: Uint8Array): Promise; + sign(message: Uint8Array, hdPath?: HdPath): Promise; private verifyAppMode; private getOpenAppName; private verifyAppVersion; diff --git a/packages/launchpad-ledger/types/ledgersigner.d.ts b/packages/launchpad-ledger/types/ledgersigner.d.ts index abf5b3cb..dc4c4e49 100644 --- a/packages/launchpad-ledger/types/ledgersigner.d.ts +++ b/packages/launchpad-ledger/types/ledgersigner.d.ts @@ -2,8 +2,7 @@ import { AccountData, OfflineSigner, StdSignature } from "@cosmjs/launchpad"; import { LaunchpadLedgerOptions } from "./launchpadledger"; export declare class LedgerSigner implements OfflineSigner { private readonly ledger; - private address; - private pubkey; + private accounts?; constructor(options?: LaunchpadLedgerOptions); getAccounts(): Promise; sign(address: string, message: Uint8Array): Promise; From 9d0e601a179107cb150dfb6db400394106d9a856 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 14:59:14 +0200 Subject: [PATCH 32/33] launchpad-ledger: Improve error handling --- .../launchpad-ledger/src/launchpadledger.ts | 21 ++++++++++++------- .../types/launchpadledger.d.ts | 4 ++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/launchpad-ledger/src/launchpadledger.ts b/packages/launchpad-ledger/src/launchpadledger.ts index b3c8bf05..d781ea95 100644 --- a/packages/launchpad-ledger/src/launchpadledger.ts +++ b/packages/launchpad-ledger/src/launchpadledger.ts @@ -12,6 +12,13 @@ import CosmosApp, { } from "ledger-cosmos-js"; import semver from "semver"; +/* eslint-disable @typescript-eslint/naming-convention */ +export interface LedgerAppErrorResponse { + readonly error_message?: string; + readonly device_locked?: boolean; +} +/* eslint-enable */ + const defaultInteractionTimeout = 120; // seconds to wait for user action on Ledger, currently is always limited to 60 const requiredCosmosAppVersion = "1.5.3"; @@ -152,9 +159,7 @@ export class LaunchpadLedger { const hdPathToUse = hdPath || this.hdPaths[0]; // ledger-cosmos-js hardens the first three indices const response = await this.cosmosApp.sign(unharden(hdPathToUse), fromUtf8(message)); - this.handleLedgerErrors(response, { - rejectionMessage: "Transaction signing request was rejected by the user", - }); + this.handleLedgerErrors(response, "Transaction signing request was rejected by the user"); return Secp256k1Signature.fromDer((response as SignResponse).signature).toFixedLength(); } @@ -196,13 +201,14 @@ export class LaunchpadLedger { await this.verifyCosmosAppIsOpen(); } - /* eslint-disable @typescript-eslint/naming-convention */ private handleLedgerErrors( + /* eslint-disable @typescript-eslint/naming-convention */ { - error_message: errorMessage, + error_message: errorMessage = "No errors", device_locked: deviceLocked = false, - }: { error_message: string; device_locked?: boolean }, - { rejectionMessage = "Request was rejected by the user" } = {}, + }: LedgerAppErrorResponse, + /* eslint-enable */ + rejectionMessage = "Request was rejected by the user", ): void { if (deviceLocked) { throw new Error("Ledger’s screensaver mode is on"); @@ -228,5 +234,4 @@ export class LaunchpadLedger { throw new Error(`Ledger Native Error: ${errorMessage}`); } } - /* eslint-enable */ } diff --git a/packages/launchpad-ledger/types/launchpadledger.d.ts b/packages/launchpad-ledger/types/launchpadledger.d.ts index c9167fc3..6eda113d 100644 --- a/packages/launchpad-ledger/types/launchpadledger.d.ts +++ b/packages/launchpad-ledger/types/launchpadledger.d.ts @@ -1,4 +1,8 @@ import { HdPath } from "@cosmjs/crypto"; +export interface LedgerAppErrorResponse { + readonly error_message?: string; + readonly device_locked?: boolean; +} export interface LaunchpadLedgerOptions { readonly hdPaths?: readonly HdPath[]; readonly prefix?: string; From e84bf97ff526c94389de7863d8313501c7a2a3fd Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 15 Sep 2020 15:03:06 +0200 Subject: [PATCH 33/33] root: Update CHANGELOG for launchpad-ledger etc --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d85a0ac1..e039e715 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 0.23.0 (unreleased) +- @cosmjs/cli: Expose `HdPath` type. - @cosmjs/cosmwasm: Rename `CosmWasmClient.postTx` method to `.broadcastTx`. - @cosmjs/cosmwasm: Rename `FeeTable` type to `CosmWasmFeeTable`. - @cosmjs/cosmwasm: `SigningCosmWasmClient` constructor now takes optional @@ -13,6 +14,8 @@ init, migrate and handle messages (in `WasmExtension.wasm.queryContractSmart`, `CosmWasmClient.queryContractSmart`, `SigningCosmWasmClient.instantiate`, `SigningCosmWasmClient.migrate`, `SigningCosmWasmClient.execute`). +- @cosmjs/crypto: Export new type alias `HdPath`. +- @cosmjs/crypto: Add `Secp256k1Signature.toFixedLength` method. - @cosmjs/demo-staking: Remove package and supporting scripts. - @cosmjs/encoding: Add `limit` parameter to `Bech32.encode` and `.decode`. The new default limit for decoding is infinity (was 90 before). Set it to 90 to @@ -42,6 +45,9 @@ `isSearchBySentFromOrToQuery` and `isSearchByTagsQuery`. - @cosmjs/launchpad: Change type of `TxsResponse.logs` and `BroadcastTxsResponse.logs` to `unknown[]`. +- @cosmjs/launchpad-ledger: Add package supporting Ledger device integration for + Launchpad. Two new classes are provided: `LedgerSigner` (for most use cases) + and `LaunchpadLedger` for more fine-grained access. - @cosmjs/math: Add `.multiply` method to `Decimal` class. - @cosmjs/tendermint-rpc: Make `BroadcastTxCommitResponse.height` non-optional. - @cosmjs/tendermint-rpc: Change type of `GenesisResponse.appState` to