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 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", 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/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/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/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; } /** 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; 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/README.md b/packages/launchpad-ledger/README.md new file mode 100644 index 00000000..4cd9ca24 --- /dev/null +++ b/packages/launchpad-ledger/README.md @@ -0,0 +1,36 @@ +# @cosmjs/launchpad-ledger + +[![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 +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/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/demo/index.css b/packages/launchpad-ledger/demo/index.css new file mode 100644 index 00000000..974bced3 --- /dev/null +++ b/packages/launchpad-ledger/demo/index.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: 28em; +} diff --git a/packages/launchpad-ledger/demo/index.html b/packages/launchpad-ledger/demo/index.html new file mode 100644 index 00000000..1575c6bd --- /dev/null +++ b/packages/launchpad-ledger/demo/index.html @@ -0,0 +1,43 @@ + + + + + + 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. +
+
+
+ +
+
+
+ + +
+
+ + +
+
+ +
+
+ + diff --git a/packages/launchpad-ledger/package.json b/packages/launchpad-ledger/package.json new file mode 100644 index 00000000..0fdc2129 --- /dev/null +++ b/packages/launchpad-ledger/package.json @@ -0,0 +1,52 @@ +{ + "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 -rf ./types/demo", + "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": "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.demo.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", + "semver": "^7.3.2" + }, + "devDependencies": { + "@types/ledgerhq__hw-transport-webusb": "^4.70.0", + "@types/semver": "^7.3.3" + } +} diff --git a/packages/launchpad-ledger/src/demo/index.ts b/packages/launchpad-ledger/src/demo/index.ts new file mode 100644 index 00000000..8269c3a3 --- /dev/null +++ b/packages/launchpad-ledger/src/demo/index.ts @@ -0,0 +1,65 @@ +import { toHex, toUtf8 } from "@cosmjs/encoding"; + +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"); + const address = accounts[0].address; + addressInput.value = address; + messageTextArea.textContent = createMessage(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 signer.sign(address, toUtf8(message)); + signatureDiv.textContent = JSON.stringify(signature, null, "\t"); + } catch (error) { + signatureDiv.textContent = error; + } +}; diff --git a/packages/launchpad-ledger/src/index.ts b/packages/launchpad-ledger/src/index.ts new file mode 100644 index 00000000..155dfd14 --- /dev/null +++ b/packages/launchpad-ledger/src/index.ts @@ -0,0 +1,2 @@ +export { LaunchpadLedger } from "./launchpadledger"; +export { LedgerSigner } from "./ledgersigner"; diff --git a/packages/launchpad-ledger/src/launchpadledger.ts b/packages/launchpad-ledger/src/launchpadledger.ts new file mode 100644 index 00000000..d781ea95 --- /dev/null +++ b/packages/launchpad-ledger/src/launchpadledger.ts @@ -0,0 +1,237 @@ +import { HdPath, Secp256k1Signature } 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"; +import CosmosApp, { + AppInfoResponse, + PublicKeyResponse, + SignResponse, + VersionResponse, +} 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"; + +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 isChromeOrBrave = /chrome|crios/i.test(userAgent) && !/edge|opr\//i.test(userAgent); + if (!isChromeOrBrave) { + 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; + } +} + +function unharden(hdPath: HdPath): number[] { + return hdPath.map((n) => (n.isHardened() ? n.toNumber() - 2 ** 31 : n.toNumber())); +} + +const cosmosHdPath = makeCosmoshubPath(0); +const cosmosBech32Prefix = "cosmos"; + +export interface LaunchpadLedgerOptions { + readonly hdPaths?: readonly HdPath[]; + readonly prefix?: string; + readonly testModeAllowed?: boolean; +} + +export class LaunchpadLedger { + private readonly testModeAllowed: boolean; + private readonly hdPaths: readonly HdPath[]; + private readonly prefix: string; + private cosmosApp: CosmosApp | null; + public readonly platform: string; + public readonly userAgent: string; + + constructor(options: LaunchpadLedgerOptions = {}) { + const defaultOptions = { + hdPaths: [cosmosHdPath], + prefix: cosmosBech32Prefix, + testModeAllowed: false, + }; + const { hdPaths, prefix, testModeAllowed } = { + ...defaultOptions, + ...options, + }; + this.testModeAllowed = testModeAllowed; + this.hdPaths = hdPaths; + this.prefix = prefix; + this.cosmosApp = null; + this.platform = navigator.platform; + this.userAgent = navigator.userAgent; + } + + 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(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(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, 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(hdPathToUse), fromUtf8(message)); + this.handleLedgerErrors(response, "Transaction signing request was rejected by the user"); + return Secp256k1Signature.fromDer((response as SignResponse).signature).toFixedLength(); + } + + 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(); + } + + private handleLedgerErrors( + /* eslint-disable @typescript-eslint/naming-convention */ + { + error_message: errorMessage = "No errors", + device_locked: deviceLocked = false, + }: LedgerAppErrorResponse, + /* eslint-enable */ + 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}`); + } + } +} diff --git a/packages/launchpad-ledger/src/ledgersigner.ts b/packages/launchpad-ledger/src/ledgersigner.ts new file mode 100644 index 00000000..836c7480 --- /dev/null +++ b/packages/launchpad-ledger/src/ledgersigner.ts @@ -0,0 +1,43 @@ +import { AccountData, encodeSecp256k1Signature, OfflineSigner, StdSignature } from "@cosmjs/launchpad"; + +import { LaunchpadLedger, LaunchpadLedgerOptions } from "./launchpadledger"; + +export class LedgerSigner implements OfflineSigner { + private readonly ledger: LaunchpadLedger; + private accounts?: readonly AccountData[]; + + constructor(options?: LaunchpadLedgerOptions) { + this.ledger = new LaunchpadLedger(options); + } + + public async getAccounts(): Promise { + await this.ledger.connect(); + + 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 this.accounts; + } + + public async sign(address: string, message: Uint8Array): Promise { + await this.ledger.connect(); + + const accounts = this.accounts || (await this.getAccounts()); + const accountForAddress = accounts.find((account) => account.address === address); + + if (!accountForAddress) { + throw new Error(`Address ${address} not found in wallet`); + } + + const signature = await this.ledger.sign(message); + return encodeSecp256k1Signature(accountForAddress.pubkey, signature); + } +} diff --git a/packages/launchpad-ledger/tsconfig.json b/packages/launchpad-ledger/tsconfig.json new file mode 100644 index 00000000..44242629 --- /dev/null +++ b/packages/launchpad-ledger/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "declarationDir": "build/types", + "rootDir": "src", + "lib": ["es2017", "dom"] + }, + "include": ["src/**/*", "./custom_types/*"] +} diff --git a/packages/launchpad-ledger/typedoc.js b/packages/launchpad-ledger/typedoc.js new file mode 100644 index 00000000..3b3d649e --- /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", "./src/demo"], + name: `${packageJson.name} Documentation`, + readme: "README.md", + mode: "file", + excludeExternals: true, + excludeNotExported: true, + excludePrivate: true, +}; diff --git a/packages/launchpad-ledger/types/index.d.ts b/packages/launchpad-ledger/types/index.d.ts new file mode 100644 index 00000000..155dfd14 --- /dev/null +++ b/packages/launchpad-ledger/types/index.d.ts @@ -0,0 +1,2 @@ +export { LaunchpadLedger } from "./launchpadledger"; +export { LedgerSigner } from "./ledgersigner"; diff --git a/packages/launchpad-ledger/types/launchpadledger.d.ts b/packages/launchpad-ledger/types/launchpadledger.d.ts new file mode 100644 index 00000000..6eda113d --- /dev/null +++ b/packages/launchpad-ledger/types/launchpadledger.d.ts @@ -0,0 +1,31 @@ +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; + readonly testModeAllowed?: boolean; +} +export declare class LaunchpadLedger { + private readonly testModeAllowed; + private readonly hdPaths; + private readonly prefix; + private cosmosApp; + readonly platform: string; + readonly userAgent: string; + constructor(options?: LaunchpadLedgerOptions); + connect(timeout?: number): Promise; + getCosmosAppVersion(): Promise; + getPubkey(hdPath?: HdPath): Promise; + getPubkeys(): Promise; + getCosmosAddress(pubkey?: Uint8Array): Promise; + sign(message: Uint8Array, hdPath?: HdPath): Promise; + private verifyAppMode; + private getOpenAppName; + private verifyAppVersion; + private verifyCosmosAppIsOpen; + private verifyDeviceIsReady; + private handleLedgerErrors; +} diff --git a/packages/launchpad-ledger/types/ledgersigner.d.ts b/packages/launchpad-ledger/types/ledgersigner.d.ts new file mode 100644 index 00000000..dc4c4e49 --- /dev/null +++ b/packages/launchpad-ledger/types/ledgersigner.d.ts @@ -0,0 +1,9 @@ +import { AccountData, OfflineSigner, StdSignature } from "@cosmjs/launchpad"; +import { LaunchpadLedgerOptions } from "./launchpadledger"; +export declare class LedgerSigner implements OfflineSigner { + private readonly ledger; + private accounts?; + constructor(options?: LaunchpadLedgerOptions); + getAccounts(): Promise; + sign(address: string, message: Uint8Array): Promise; +} 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/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. 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" } } diff --git a/yarn.lock b/yarn.lock index cca65fc2..c0c33ea5 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" @@ -1301,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" @@ -1370,6 +1458,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" @@ -2051,7 +2144,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 +3753,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 +5402,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 +7242,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 +7445,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"