Merge pull request #417 from CosmWasm/268-ledger-offline-signer

Add LedgerWallet
This commit is contained in:
Simon Warta 2020-09-15 15:44:04 +02:00 committed by GitHub
commit 034f4adc8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 840 additions and 65 deletions

View File

@ -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

View File

@ -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<GasLimits<CosmWasmFeeTable>>; // 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();

View File

@ -71,6 +71,7 @@ export async function main(originalArgs: readonly string[]): Promise<void> {
"Ed25519",
"Ed25519Keypair",
"EnglishMnemonic",
"HdPath",
"Random",
"Secp256k1",
"Sha256",

View File

@ -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,

View File

@ -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/

View File

@ -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

View File

@ -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),

View File

@ -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);

View File

@ -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,

View File

@ -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;
}
/**

View File

@ -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;

View File

@ -0,0 +1 @@
../../.eslintignore

3
packages/launchpad-ledger/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build/
dist/
docs/

View File

@ -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)).

View File

@ -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<VersionResponse | ErrorResponse>;
appInfo: () => Promise<AppInfoResponse | ErrorResponse>;
publicKey: (path: Array<number>) => Promise<PublicKeyResponse | ErrorResponse>;
showAddressAndPubKey: (
path: Array<number>,
hrp: string,
) => Promise<AddressAndPublicKeyResponse | ErrorResponse>;
sign: (path: Array<number>, message: string) => Promise<SignResponse | ErrorResponse>;
}
}

View File

@ -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;
}

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ledger Demo</title>
<link rel="stylesheet" href="index.css"></style>
<script src="../dist/demo/ledger.js"></script>
</head>
<body>
<div>
<h1>Ledger Demo</h1>
</div>
<div>
<ol>
<li>Connect the Ledger device via USB</li>
<li>Open the Cosmos app on the Ledger device</li>
<li>Click the buttons below</li>
</ol>
</div>
<div>
<button onclick="getAccounts()">
Get Accounts
</button>
</div>
<div id="accounts"></div>
<div>
<label>Address</label>
<input id="address" type="text" value=""></input>
</div>
<div>
<label>Message</label>
<textarea id="message">
</textarea>
</div>
<div>
<button onclick="sign()">
Sign Message
</button>
</div>
<div id="signature"></div>
</body>
</html>

View File

@ -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 <willclarktech@users.noreply.github.com>"
],
"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"
}
}

View File

@ -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<void> {
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<void> {
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;
}
};

View File

@ -0,0 +1,2 @@
export { LaunchpadLedger } from "./launchpadledger";
export { LedgerSigner } from "./ledgersigner";

View File

@ -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<Transport> {
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<LaunchpadLedger> {
// 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<string> {
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<Uint8Array> {
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<readonly Uint8Array[]> {
return Promise.all(this.hdPaths.map(async (hdPath) => this.getPubkey(hdPath)));
}
async getCosmosAddress(pubkey?: Uint8Array): Promise<string> {
const pubkeyToUse = pubkey || (await this.getPubkey());
return CosmosApp.getBech32FromPK(this.prefix, Buffer.from(pubkeyToUse));
}
async sign(message: Uint8Array, hdPath?: HdPath): Promise<Uint8Array> {
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<string> {
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<void> {
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<void> {
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<void> {
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("Ledgers 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("Ledgers 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}`);
}
}
}

View File

@ -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<readonly AccountData[]> {
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<StdSignature> {
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);
}
}

View File

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "build",
"declarationDir": "build/types",
"rootDir": "src",
"lib": ["es2017", "dom"]
},
"include": ["src/**/*", "./custom_types/*"]
}

View File

@ -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,
};

View File

@ -0,0 +1,2 @@
export { LaunchpadLedger } from "./launchpadledger";
export { LedgerSigner } from "./ledgersigner";

View File

@ -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<LaunchpadLedger>;
getCosmosAppVersion(): Promise<string>;
getPubkey(hdPath?: HdPath): Promise<Uint8Array>;
getPubkeys(): Promise<readonly Uint8Array[]>;
getCosmosAddress(pubkey?: Uint8Array): Promise<string>;
sign(message: Uint8Array, hdPath?: HdPath): Promise<Uint8Array>;
private verifyAppMode;
private getOpenAppName;
private verifyAppVersion;
private verifyCosmosAppIsOpen;
private verifyDeviceIsReady;
private handleLedgerErrors;
}

View File

@ -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<readonly AccountData[]>;
sign(address: string, message: Uint8Array): Promise<StdSignature>;
}

View File

@ -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",
},
},
];

View File

@ -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<Secp256k1Wallet> {
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<Secp256k1Wallet> {
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,

View File

@ -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),

View File

@ -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<Secp256k1Wallet>;
static fromMnemonic(mnemonic: string, hdPath?: HdPath, prefix?: string): Promise<Secp256k1Wallet>;
/**
* 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<Secp256k1Wallet>;
/**

View File

@ -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.

View File

@ -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"
}
}

122
yarn.lock
View File

@ -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"