From f8cf23766cf4cf2f00acc5b12b03ad4fa61101a5 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 23 Mar 2021 13:37:18 +0100 Subject: [PATCH] Pull out pubkey functionality into @cosmjs/amino --- CHANGELOG.md | 2 + packages/amino/.eslintignore | 8 ++ packages/amino/.gitignore | 3 + packages/amino/.nycrc.yml | 1 + packages/amino/README.md | 12 ++ packages/amino/jasmine-testrunner.js | 39 +++++ packages/amino/karma.conf.js | 47 ++++++ packages/amino/nonces/README.txt | 1 + packages/amino/package.json | 48 +++++++ packages/amino/src/encoding.spec.ts | 135 ++++++++++++++++++ packages/amino/src/encoding.ts | 97 +++++++++++++ packages/amino/src/index.ts | 8 ++ packages/amino/src/pubkeys.ts | 18 +++ packages/amino/tsconfig.json | 11 ++ packages/amino/typedoc.js | 11 ++ packages/amino/webpack.web.config.js | 17 +++ packages/cosmwasm-stargate/package.json | 1 + .../src/signingcosmwasmclient.ts | 2 +- packages/launchpad/package.json | 1 + packages/launchpad/src/address.ts | 3 +- packages/launchpad/src/cosmosclient.ts | 2 +- packages/launchpad/src/index.ts | 20 +-- packages/launchpad/src/lcdapi/auth.ts | 3 +- packages/launchpad/src/lcdapi/utils.spec.ts | 3 +- packages/launchpad/src/lcdapi/utils.ts | 4 +- packages/launchpad/src/pubkey.spec.ts | 135 ------------------ packages/launchpad/src/pubkey.ts | 97 ------------- packages/launchpad/src/signature.ts | 4 +- packages/launchpad/src/types.ts | 21 +-- packages/proto-signing/package.json | 1 + packages/proto-signing/src/pubkey.ts | 6 +- packages/stargate/package.json | 1 + packages/stargate/src/accounts.ts | 2 +- packages/stargate/src/aminotypes.spec.ts | 2 +- packages/stargate/src/aminotypes.ts | 3 +- .../stargate/src/signingstargateclient.ts | 2 +- 36 files changed, 493 insertions(+), 278 deletions(-) create mode 100644 packages/amino/.eslintignore create mode 100644 packages/amino/.gitignore create mode 120000 packages/amino/.nycrc.yml create mode 100644 packages/amino/README.md create mode 100755 packages/amino/jasmine-testrunner.js create mode 100644 packages/amino/karma.conf.js create mode 100644 packages/amino/nonces/README.txt create mode 100644 packages/amino/package.json create mode 100644 packages/amino/src/encoding.spec.ts create mode 100644 packages/amino/src/encoding.ts create mode 100644 packages/amino/src/index.ts create mode 100644 packages/amino/src/pubkeys.ts create mode 100644 packages/amino/tsconfig.json create mode 100644 packages/amino/typedoc.js create mode 100644 packages/amino/webpack.web.config.js diff --git a/CHANGELOG.md b/CHANGELOG.md index a5a6e5fb..7285afae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ and this project adheres to connection tx, as well as Tendermint. - @cosmjs/stargate: Add support for IBC message types in `SigningStargateClient`. +- @cosmjs/amino: New package created that contains the shared amino signing + functionality for @cosmjs/launchpad and @cosmjs/stargate. ### Changed diff --git a/packages/amino/.eslintignore b/packages/amino/.eslintignore new file mode 100644 index 00000000..f373a53f --- /dev/null +++ b/packages/amino/.eslintignore @@ -0,0 +1,8 @@ +node_modules/ + +build/ +custom_types/ +dist/ +docs/ +generated/ +types/ diff --git a/packages/amino/.gitignore b/packages/amino/.gitignore new file mode 100644 index 00000000..68bf3735 --- /dev/null +++ b/packages/amino/.gitignore @@ -0,0 +1,3 @@ +build/ +dist/ +docs/ diff --git a/packages/amino/.nycrc.yml b/packages/amino/.nycrc.yml new file mode 120000 index 00000000..1f95ac55 --- /dev/null +++ b/packages/amino/.nycrc.yml @@ -0,0 +1 @@ +../../.nycrc.yml \ No newline at end of file diff --git a/packages/amino/README.md b/packages/amino/README.md new file mode 100644 index 00000000..31c09c37 --- /dev/null +++ b/packages/amino/README.md @@ -0,0 +1,12 @@ +# @cosmjs/amino + +[![npm version](https://img.shields.io/npm/v/@cosmjs/amino.svg)](https://www.npmjs.com/package/@cosmjs/amino) + +Helpers for Amino based signing which are shared between @cosmjs/launchpad and +@cosmjs/stargate. + +## License + +This package is part of the cosmjs repository, licensed under the Apache License +2.0 (see [NOTICE](https://github.com/cosmos/cosmjs/blob/main/NOTICE) and +[LICENSE](https://github.com/cosmos/cosmjs/blob/main/LICENSE)). diff --git a/packages/amino/jasmine-testrunner.js b/packages/amino/jasmine-testrunner.js new file mode 100755 index 00000000..6558a133 --- /dev/null +++ b/packages/amino/jasmine-testrunner.js @@ -0,0 +1,39 @@ +#!/usr/bin/env node +/* eslint-disable @typescript-eslint/naming-convention */ + +if (process.env.SES_ENABLED) { + require("ses/lockdown"); + // eslint-disable-next-line no-undef + lockdown(); +} + +require("source-map-support").install(); +const defaultSpecReporterConfig = require("../../jasmine-spec-reporter.config.json"); + +// setup Jasmine +const Jasmine = require("jasmine"); +const jasmine = new Jasmine(); +jasmine.loadConfig({ + spec_dir: "build", + spec_files: ["**/*.spec.js"], + helpers: [], + random: false, + seed: null, + stopSpecOnExpectationFailure: false, +}); +jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 1000; + +// setup reporter +const { SpecReporter } = require("jasmine-spec-reporter"); +const reporter = new SpecReporter({ + ...defaultSpecReporterConfig, + spec: { + ...defaultSpecReporterConfig.spec, + displaySuccessful: !process.argv.includes("--quiet"), + }, +}); + +// initialize and execute +jasmine.env.clearReporters(); +jasmine.addReporter(reporter); +jasmine.execute(); diff --git a/packages/amino/karma.conf.js b/packages/amino/karma.conf.js new file mode 100644 index 00000000..006da5fe --- /dev/null +++ b/packages/amino/karma.conf.js @@ -0,0 +1,47 @@ +module.exports = function (config) { + config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: ".", + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ["jasmine"], + + // list of files / patterns to load in the browser + files: ["dist/web/tests.js"], + + client: { + jasmine: { + random: false, + timeoutInterval: 15000, + }, + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ["progress", "kjhtml"], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ["Firefox"], + + browserNoActivityTimeout: 90000, + + // Keep brower open for debugging. This is overridden by yarn scripts + singleRun: false, + }); +}; diff --git a/packages/amino/nonces/README.txt b/packages/amino/nonces/README.txt new file mode 100644 index 00000000..092fe732 --- /dev/null +++ b/packages/amino/nonces/README.txt @@ -0,0 +1 @@ +Directory used to trigger lerna package updates for all packages diff --git a/packages/amino/package.json b/packages/amino/package.json new file mode 100644 index 00000000..bc9cb37d --- /dev/null +++ b/packages/amino/package.json @@ -0,0 +1,48 @@ +{ + "name": "@cosmjs/amino", + "version": "0.25.0-alpha.0", + "description": "Helpers for Amino based signing which are shared between @cosmjs/launchpad and @cosmjs/stargate.", + "contributors": [ + "Simon Warta " + ], + "license": "Apache-2.0", + "main": "build/index.js", + "types": "build/index.d.ts", + "files": [ + "build/", + "*.md", + "!*.spec.*", + "!**/testdata/" + ], + "repository": { + "type": "git", + "url": "https://github.com/cosmos/cosmjs/tree/main/packages/amino" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "docs": "typedoc --options typedoc.js", + "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", + "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", + "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", + "format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"", + "test-node": "node jasmine-testrunner.js", + "test-edge": "yarn pack-web && karma start --single-run --browsers Edge", + "test-firefox": "yarn pack-web && karma start --single-run --browsers Firefox", + "test-chrome": "yarn pack-web && karma start --single-run --browsers ChromeHeadless", + "test-safari": "yarn pack-web && karma start --single-run --browsers Safari", + "test": "yarn build-or-skip && yarn test-node", + "coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet", + "prebuild": "shx rm -rf ./build", + "build": "tsc", + "build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build", + "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" + }, + "dependencies": { + "@cosmjs/encoding": "^0.25.0-alpha.0", + "@cosmjs/utils": "^0.25.0-alpha.0" + }, + "devDependencies": { + } +} diff --git a/packages/amino/src/encoding.spec.ts b/packages/amino/src/encoding.spec.ts new file mode 100644 index 00000000..02c6900d --- /dev/null +++ b/packages/amino/src/encoding.spec.ts @@ -0,0 +1,135 @@ +import { Bech32, fromBase64 } from "@cosmjs/encoding"; + +import { + decodeAminoPubkey, + decodeBech32Pubkey, + encodeAminoPubkey, + encodeBech32Pubkey, + encodeSecp256k1Pubkey, +} from "./encoding"; +import { PubKey } from "./pubkeys"; + +describe("pubkey", () => { + describe("encodeSecp256k1Pubkey", () => { + it("encodes a compresed pubkey", () => { + const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"); + expect(encodeSecp256k1Pubkey(pubkey)).toEqual({ + type: "tendermint/PubKeySecp256k1", + value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", + }); + }); + + it("throws for uncompressed public keys", () => { + const pubkey = fromBase64( + "BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=", + ); + expect(() => encodeSecp256k1Pubkey(pubkey)).toThrowError(/public key must be compressed secp256k1/i); + }); + }); + + describe("decodeAminoPubkey", () => { + it("works for secp256k1", () => { + const amino = Bech32.decode( + "cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5", + ).data; + expect(decodeAminoPubkey(amino)).toEqual({ + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }); + }); + + it("works for ed25519", () => { + // Encoded from `corald tendermint show-validator` + // Decoded from http://localhost:26657/validators + const amino = Bech32.decode( + "coralvalconspub1zcjduepqvxg72ccnl9r65fv0wn3amlk4sfzqfe2k36l073kjx2qyaf6sk23qw7j8wq", + ).data; + expect(decodeAminoPubkey(amino)).toEqual({ + type: "tendermint/PubKeyEd25519", + value: "YZHlYxP5R6olj3Tj3f7VgkQE5VaOvv9G0jKATqdQsqI=", + }); + }); + }); + + describe("decodeBech32Pubkey", () => { + it("works", () => { + expect( + decodeBech32Pubkey("cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5"), + ).toEqual({ + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }); + }); + + it("works for enigma pubkey", () => { + expect( + decodeBech32Pubkey("enigmapub1addwnpepqw5k9p439nw0zpg2aundx4umwx4nw233z5prpjqjv5anl5grmnchzp2xwvv"), + ).toEqual({ + type: "tendermint/PubKeySecp256k1", + value: "A6lihrEs3PEFCu8m01ebcas3KjEVAjDIEmU7P9ED3PFx", + }); + }); + + it("works for ed25519", () => { + // Encoded from `corald tendermint show-validator` + // Decoded from http://localhost:26657/validators + const decoded = decodeBech32Pubkey( + "coralvalconspub1zcjduepqvxg72ccnl9r65fv0wn3amlk4sfzqfe2k36l073kjx2qyaf6sk23qw7j8wq", + ); + expect(decoded).toEqual({ + type: "tendermint/PubKeyEd25519", + value: "YZHlYxP5R6olj3Tj3f7VgkQE5VaOvv9G0jKATqdQsqI=", + }); + }); + }); + + describe("encodeAminoPubkey", () => { + it("works for secp256k1", () => { + const pubkey: PubKey = { + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }; + const expected = Bech32.decode( + "cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5", + ).data; + expect(encodeAminoPubkey(pubkey)).toEqual(expected); + }); + + it("works for ed25519", () => { + // Decoded from http://localhost:26657/validators + // Encoded from `corald tendermint show-validator` + const pubkey: PubKey = { + type: "tendermint/PubKeyEd25519", + value: "YZHlYxP5R6olj3Tj3f7VgkQE5VaOvv9G0jKATqdQsqI=", + }; + const expected = Bech32.decode( + "coralvalconspub1zcjduepqvxg72ccnl9r65fv0wn3amlk4sfzqfe2k36l073kjx2qyaf6sk23qw7j8wq", + ).data; + expect(encodeAminoPubkey(pubkey)).toEqual(expected); + }); + }); + + describe("encodeBech32Pubkey", () => { + it("works for secp256k1", () => { + const pubkey: PubKey = { + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }; + expect(encodeBech32Pubkey(pubkey, "cosmospub")).toEqual( + "cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5", + ); + }); + + it("works for ed25519", () => { + // Decoded from http://localhost:26657/validators + // Encoded from `corald tendermint show-validator` + const pubkey: PubKey = { + type: "tendermint/PubKeyEd25519", + value: "YZHlYxP5R6olj3Tj3f7VgkQE5VaOvv9G0jKATqdQsqI=", + }; + expect(encodeBech32Pubkey(pubkey, "coralvalconspub")).toEqual( + "coralvalconspub1zcjduepqvxg72ccnl9r65fv0wn3amlk4sfzqfe2k36l073kjx2qyaf6sk23qw7j8wq", + ); + }); + }); +}); diff --git a/packages/amino/src/encoding.ts b/packages/amino/src/encoding.ts new file mode 100644 index 00000000..cc6297a0 --- /dev/null +++ b/packages/amino/src/encoding.ts @@ -0,0 +1,97 @@ +import { Bech32, fromBase64, fromHex, toBase64, toHex } from "@cosmjs/encoding"; +import { arrayContentEquals } from "@cosmjs/utils"; + +import { PubKey, pubkeyType } from "./pubkeys"; + +export function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey { + if (pubkey.length !== 33 || (pubkey[0] !== 0x02 && pubkey[0] !== 0x03)) { + throw new Error("Public key must be compressed secp256k1, i.e. 33 bytes starting with 0x02 or 0x03"); + } + return { + type: pubkeyType.secp256k1, + value: toBase64(pubkey), + }; +} + +// As discussed in https://github.com/binance-chain/javascript-sdk/issues/163 +// Prefixes listed here: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/docs/spec/blockchain/encoding.md#public-key-cryptography +// Last bytes is varint-encoded length prefix +const pubkeyAminoPrefixSecp256k1 = fromHex("eb5ae98721"); +const pubkeyAminoPrefixEd25519 = fromHex("1624de6420"); +const pubkeyAminoPrefixSr25519 = fromHex("0dfb1005"); +const pubkeyAminoPrefixLength = pubkeyAminoPrefixSecp256k1.length; + +/** + * Decodes a pubkey in the Amino binary format to a type/value object. + */ +export function decodeAminoPubkey(data: Uint8Array): PubKey { + const aminoPrefix = data.slice(0, pubkeyAminoPrefixLength); + const rest = data.slice(pubkeyAminoPrefixLength); + if (arrayContentEquals(aminoPrefix, pubkeyAminoPrefixSecp256k1)) { + if (rest.length !== 33) { + throw new Error("Invalid rest data length. Expected 33 bytes (compressed secp256k1 pubkey)."); + } + return { + type: pubkeyType.secp256k1, + value: toBase64(rest), + }; + } else if (arrayContentEquals(aminoPrefix, pubkeyAminoPrefixEd25519)) { + if (rest.length !== 32) { + throw new Error("Invalid rest data length. Expected 32 bytes (Ed25519 pubkey)."); + } + return { + type: pubkeyType.ed25519, + value: toBase64(rest), + }; + } else if (arrayContentEquals(aminoPrefix, pubkeyAminoPrefixSr25519)) { + if (rest.length !== 32) { + throw new Error("Invalid rest data length. Expected 32 bytes (Sr25519 pubkey)."); + } + return { + type: pubkeyType.sr25519, + value: toBase64(rest), + }; + } else { + throw new Error("Unsupported Pubkey type. Amino prefix: " + toHex(aminoPrefix)); + } +} + +/** + * Decodes a bech32 pubkey to Amino binary, which is then decoded to a type/value object. + * The bech32 prefix is ignored and discareded. + * + * @param bechEncoded the bech32 encoded pubkey + */ +export function decodeBech32Pubkey(bechEncoded: string): PubKey { + const { data } = Bech32.decode(bechEncoded); + return decodeAminoPubkey(data); +} + +/** + * Encodes a public key to binary Amino. + */ +export function encodeAminoPubkey(pubkey: PubKey): Uint8Array { + let aminoPrefix: Uint8Array; + switch (pubkey.type) { + // Note: please don't add cases here without writing additional unit tests + case pubkeyType.secp256k1: + aminoPrefix = pubkeyAminoPrefixSecp256k1; + break; + case pubkeyType.ed25519: + aminoPrefix = pubkeyAminoPrefixEd25519; + break; + default: + throw new Error("Unsupported pubkey type"); + } + return new Uint8Array([...aminoPrefix, ...fromBase64(pubkey.value)]); +} + +/** + * Encodes a public key to binary Amino and then to bech32. + * + * @param pubkey the public key to encode + * @param prefix the bech32 prefix (human readable part) + */ +export function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string { + return Bech32.encode(prefix, encodeAminoPubkey(pubkey)); +} diff --git a/packages/amino/src/index.ts b/packages/amino/src/index.ts new file mode 100644 index 00000000..7f01fc17 --- /dev/null +++ b/packages/amino/src/index.ts @@ -0,0 +1,8 @@ +export { + decodeAminoPubkey, + decodeBech32Pubkey, + encodeAminoPubkey, + encodeBech32Pubkey, + encodeSecp256k1Pubkey, +} from "./encoding"; +export { PubKey, pubkeyType } from "./pubkeys"; diff --git a/packages/amino/src/pubkeys.ts b/packages/amino/src/pubkeys.ts new file mode 100644 index 00000000..3036afcf --- /dev/null +++ b/packages/amino/src/pubkeys.ts @@ -0,0 +1,18 @@ +export interface PubKey { + // type is one of the strings defined in pubkeyType + // I don't use a string literal union here as that makes trouble with json test data: + // https://github.com/cosmos/cosmjs/pull/44#pullrequestreview-353280504 + readonly type: string; + // Value field is base64-encoded in all cases + // Note: if type is Secp256k1, this must contain a COMPRESSED pubkey - to encode from bcp/keycontrol land, you must compress it first + readonly value: string; +} + +export const pubkeyType = { + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */ + secp256k1: "tendermint/PubKeySecp256k1" as const, + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23 */ + ed25519: "tendermint/PubKeyEd25519" as const, + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */ + sr25519: "tendermint/PubKeySr25519" as const, +}; diff --git a/packages/amino/tsconfig.json b/packages/amino/tsconfig.json new file mode 100644 index 00000000..df66add1 --- /dev/null +++ b/packages/amino/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "rootDir": "src" + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/amino/typedoc.js b/packages/amino/typedoc.js new file mode 100644 index 00000000..ffe4be64 --- /dev/null +++ b/packages/amino/typedoc.js @@ -0,0 +1,11 @@ +const packageJson = require("./package.json"); + +module.exports = { + entryPoints: ["./src"], + out: "docs", + exclude: "**/*.spec.ts", + name: `${packageJson.name} Documentation`, + readme: "README.md", + excludeExternals: true, + excludePrivate: true, +}; diff --git a/packages/amino/webpack.web.config.js b/packages/amino/webpack.web.config.js new file mode 100644 index 00000000..9d5836a8 --- /dev/null +++ b/packages/amino/webpack.web.config.js @@ -0,0 +1,17 @@ +const glob = require("glob"); +const path = require("path"); + +const target = "web"; +const distdir = path.join(__dirname, "dist", "web"); + +module.exports = [ + { + // bundle used for Karma tests + target: target, + entry: glob.sync("./build/**/*.spec.js"), + output: { + path: distdir, + filename: "tests.js", + }, + }, +]; diff --git a/packages/cosmwasm-stargate/package.json b/packages/cosmwasm-stargate/package.json index 8261f247..fa4945e3 100644 --- a/packages/cosmwasm-stargate/package.json +++ b/packages/cosmwasm-stargate/package.json @@ -42,6 +42,7 @@ "postdefine-proto": "prettier --write \"src/codec/**/*.ts\"" }, "dependencies": { + "@cosmjs/amino": "^0.25.0-alpha.0", "@cosmjs/cosmwasm-launchpad": "^0.25.0-alpha.0", "@cosmjs/crypto": "^0.25.0-alpha.0", "@cosmjs/encoding": "^0.25.0-alpha.0", diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index e4d12d71..7b008c5e 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import { encodeSecp256k1Pubkey } from "@cosmjs/amino"; import { ChangeAdminResult, CosmWasmFeeTable, @@ -16,7 +17,6 @@ import { buildFeeTable, Coin, CosmosFeeTable, - encodeSecp256k1Pubkey, GasLimits, GasPrice, logs, diff --git a/packages/launchpad/package.json b/packages/launchpad/package.json index 0c8a4bfa..d6ed8030 100644 --- a/packages/launchpad/package.json +++ b/packages/launchpad/package.json @@ -39,6 +39,7 @@ "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" }, "dependencies": { + "@cosmjs/amino": "^0.25.0-alpha.0", "@cosmjs/crypto": "^0.25.0-alpha.0", "@cosmjs/encoding": "^0.25.0-alpha.0", "@cosmjs/math": "^0.25.0-alpha.0", diff --git a/packages/launchpad/src/address.ts b/packages/launchpad/src/address.ts index 87fede26..a35b2a0e 100644 --- a/packages/launchpad/src/address.ts +++ b/packages/launchpad/src/address.ts @@ -1,8 +1,7 @@ +import { PubKey, pubkeyType } from "@cosmjs/amino"; import { ripemd160, sha256 } from "@cosmjs/crypto"; import { Bech32, fromBase64 } from "@cosmjs/encoding"; -import { PubKey, pubkeyType } from "./types"; - export function rawSecp256k1PubkeyToAddress(pubkeyRaw: Uint8Array, prefix: string): string { if (pubkeyRaw.length !== 33) { throw new Error(`Invalid Secp256k1 pubkey length (compressed): ${pubkeyRaw.length}`); diff --git a/packages/launchpad/src/cosmosclient.ts b/packages/launchpad/src/cosmosclient.ts index 79194bc9..d49e018b 100644 --- a/packages/launchpad/src/cosmosclient.ts +++ b/packages/launchpad/src/cosmosclient.ts @@ -1,3 +1,4 @@ +import { PubKey } from "@cosmjs/amino"; import { sha256 } from "@cosmjs/crypto"; import { fromBase64, fromHex, toHex } from "@cosmjs/encoding"; import { Uint53 } from "@cosmjs/math"; @@ -13,7 +14,6 @@ import { } from "./lcdapi"; import { Log, parseLogs } from "./logs"; import { StdTx, WrappedStdTx } from "./tx"; -import { PubKey } from "./types"; export interface GetSequenceResult { readonly accountNumber: number; diff --git a/packages/launchpad/src/index.ts b/packages/launchpad/src/index.ts index dc7a11d0..46b2e770 100644 --- a/packages/launchpad/src/index.ts +++ b/packages/launchpad/src/index.ts @@ -1,3 +1,14 @@ +// Re-exports for backwards compatibility +export { + decodeAminoPubkey, + decodeBech32Pubkey, + encodeAminoPubkey, + encodeBech32Pubkey, + encodeSecp256k1Pubkey, + pubkeyType, + PubKey, +} from "@cosmjs/amino"; + import * as logs from "./logs"; export { logs }; @@ -125,19 +136,12 @@ export { MsgWithdrawValidatorCommission, } from "./msgs"; export { makeCosmoshubPath } from "./paths"; -export { - decodeAminoPubkey, - decodeBech32Pubkey, - encodeAminoPubkey, - encodeBech32Pubkey, - encodeSecp256k1Pubkey, -} from "./pubkey"; export { findSequenceForSignedTx } from "./sequence"; export { encodeSecp256k1Signature, decodeSignature } from "./signature"; export { AccountData, Algo, AminoSignResponse, OfflineSigner } from "./signer"; export { CosmosFeeTable, SigningCosmosClient } from "./signingcosmosclient"; export { isStdTx, isWrappedStdTx, makeStdTx, CosmosSdkTx, StdTx, WrappedStdTx, WrappedTx } from "./tx"; -export { pubkeyType, PubKey, StdFee, StdSignature } from "./types"; +export { StdFee, StdSignature } from "./types"; export { executeKdf, KdfConfiguration } from "./wallet"; export { extractKdfConfiguration, Secp256k1HdWallet } from "./secp256k1hdwallet"; export { Secp256k1Wallet } from "./secp256k1wallet"; diff --git a/packages/launchpad/src/lcdapi/auth.ts b/packages/launchpad/src/lcdapi/auth.ts index 6b316466..009c91ba 100644 --- a/packages/launchpad/src/lcdapi/auth.ts +++ b/packages/launchpad/src/lcdapi/auth.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import { PubKey } from "@cosmjs/amino"; + import { Coin } from "../coins"; -import { PubKey } from "../types"; import { LcdClient } from "./lcdclient"; /** diff --git a/packages/launchpad/src/lcdapi/utils.spec.ts b/packages/launchpad/src/lcdapi/utils.spec.ts index 27b370a9..848ea00c 100644 --- a/packages/launchpad/src/lcdapi/utils.spec.ts +++ b/packages/launchpad/src/lcdapi/utils.spec.ts @@ -1,4 +1,5 @@ -import { PubKey } from "../types"; +import { PubKey } from "@cosmjs/amino"; + import { normalizePubkey, uint64ToNumber, uint64ToString } from "./utils"; describe("utils", () => { diff --git a/packages/launchpad/src/lcdapi/utils.ts b/packages/launchpad/src/lcdapi/utils.ts index 5f797746..1fd35af3 100644 --- a/packages/launchpad/src/lcdapi/utils.ts +++ b/packages/launchpad/src/lcdapi/utils.ts @@ -1,8 +1,6 @@ +import { decodeBech32Pubkey, PubKey } from "@cosmjs/amino"; import { Uint64 } from "@cosmjs/math"; -import { decodeBech32Pubkey } from "../pubkey"; -import { PubKey } from "../types"; - /** * Converts an integer expressed as number or string to a number. * Throws if input is not a valid uint64 or if the value exceeds MAX_SAFE_INTEGER. diff --git a/packages/launchpad/src/pubkey.spec.ts b/packages/launchpad/src/pubkey.spec.ts index 9576239b..e69de29b 100644 --- a/packages/launchpad/src/pubkey.spec.ts +++ b/packages/launchpad/src/pubkey.spec.ts @@ -1,135 +0,0 @@ -import { Bech32, fromBase64 } from "@cosmjs/encoding"; - -import { - decodeAminoPubkey, - decodeBech32Pubkey, - encodeAminoPubkey, - encodeBech32Pubkey, - encodeSecp256k1Pubkey, -} from "./pubkey"; -import { PubKey } from "./types"; - -describe("pubkey", () => { - describe("encodeSecp256k1Pubkey", () => { - it("encodes a compresed pubkey", () => { - const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"); - expect(encodeSecp256k1Pubkey(pubkey)).toEqual({ - type: "tendermint/PubKeySecp256k1", - value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", - }); - }); - - it("throws for uncompressed public keys", () => { - const pubkey = fromBase64( - "BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=", - ); - expect(() => encodeSecp256k1Pubkey(pubkey)).toThrowError(/public key must be compressed secp256k1/i); - }); - }); - - describe("decodeAminoPubkey", () => { - it("works for secp256k1", () => { - const amino = Bech32.decode( - "cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5", - ).data; - expect(decodeAminoPubkey(amino)).toEqual({ - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }); - }); - - it("works for ed25519", () => { - // Encoded from `corald tendermint show-validator` - // Decoded from http://localhost:26657/validators - const amino = Bech32.decode( - "coralvalconspub1zcjduepqvxg72ccnl9r65fv0wn3amlk4sfzqfe2k36l073kjx2qyaf6sk23qw7j8wq", - ).data; - expect(decodeAminoPubkey(amino)).toEqual({ - type: "tendermint/PubKeyEd25519", - value: "YZHlYxP5R6olj3Tj3f7VgkQE5VaOvv9G0jKATqdQsqI=", - }); - }); - }); - - describe("decodeBech32Pubkey", () => { - it("works", () => { - expect( - decodeBech32Pubkey("cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5"), - ).toEqual({ - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }); - }); - - it("works for enigma pubkey", () => { - expect( - decodeBech32Pubkey("enigmapub1addwnpepqw5k9p439nw0zpg2aundx4umwx4nw233z5prpjqjv5anl5grmnchzp2xwvv"), - ).toEqual({ - type: "tendermint/PubKeySecp256k1", - value: "A6lihrEs3PEFCu8m01ebcas3KjEVAjDIEmU7P9ED3PFx", - }); - }); - - it("works for ed25519", () => { - // Encoded from `corald tendermint show-validator` - // Decoded from http://localhost:26657/validators - const decoded = decodeBech32Pubkey( - "coralvalconspub1zcjduepqvxg72ccnl9r65fv0wn3amlk4sfzqfe2k36l073kjx2qyaf6sk23qw7j8wq", - ); - expect(decoded).toEqual({ - type: "tendermint/PubKeyEd25519", - value: "YZHlYxP5R6olj3Tj3f7VgkQE5VaOvv9G0jKATqdQsqI=", - }); - }); - }); - - describe("encodeAminoPubkey", () => { - it("works for secp256k1", () => { - const pubkey: PubKey = { - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }; - const expected = Bech32.decode( - "cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5", - ).data; - expect(encodeAminoPubkey(pubkey)).toEqual(expected); - }); - - it("works for ed25519", () => { - // Decoded from http://localhost:26657/validators - // Encoded from `corald tendermint show-validator` - const pubkey: PubKey = { - type: "tendermint/PubKeyEd25519", - value: "YZHlYxP5R6olj3Tj3f7VgkQE5VaOvv9G0jKATqdQsqI=", - }; - const expected = Bech32.decode( - "coralvalconspub1zcjduepqvxg72ccnl9r65fv0wn3amlk4sfzqfe2k36l073kjx2qyaf6sk23qw7j8wq", - ).data; - expect(encodeAminoPubkey(pubkey)).toEqual(expected); - }); - }); - - describe("encodeBech32Pubkey", () => { - it("works for secp256k1", () => { - const pubkey: PubKey = { - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }; - expect(encodeBech32Pubkey(pubkey, "cosmospub")).toEqual( - "cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5", - ); - }); - - it("works for ed25519", () => { - // Decoded from http://localhost:26657/validators - // Encoded from `corald tendermint show-validator` - const pubkey: PubKey = { - type: "tendermint/PubKeyEd25519", - value: "YZHlYxP5R6olj3Tj3f7VgkQE5VaOvv9G0jKATqdQsqI=", - }; - expect(encodeBech32Pubkey(pubkey, "coralvalconspub")).toEqual( - "coralvalconspub1zcjduepqvxg72ccnl9r65fv0wn3amlk4sfzqfe2k36l073kjx2qyaf6sk23qw7j8wq", - ); - }); - }); -}); diff --git a/packages/launchpad/src/pubkey.ts b/packages/launchpad/src/pubkey.ts index bebc3c62..e69de29b 100644 --- a/packages/launchpad/src/pubkey.ts +++ b/packages/launchpad/src/pubkey.ts @@ -1,97 +0,0 @@ -import { Bech32, fromBase64, fromHex, toBase64, toHex } from "@cosmjs/encoding"; -import { arrayContentEquals } from "@cosmjs/utils"; - -import { PubKey, pubkeyType } from "./types"; - -export function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey { - if (pubkey.length !== 33 || (pubkey[0] !== 0x02 && pubkey[0] !== 0x03)) { - throw new Error("Public key must be compressed secp256k1, i.e. 33 bytes starting with 0x02 or 0x03"); - } - return { - type: pubkeyType.secp256k1, - value: toBase64(pubkey), - }; -} - -// As discussed in https://github.com/binance-chain/javascript-sdk/issues/163 -// Prefixes listed here: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/docs/spec/blockchain/encoding.md#public-key-cryptography -// Last bytes is varint-encoded length prefix -const pubkeyAminoPrefixSecp256k1 = fromHex("eb5ae98721"); -const pubkeyAminoPrefixEd25519 = fromHex("1624de6420"); -const pubkeyAminoPrefixSr25519 = fromHex("0dfb1005"); -const pubkeyAminoPrefixLength = pubkeyAminoPrefixSecp256k1.length; - -/** - * Decodes a pubkey in the Amino binary format to a type/value object. - */ -export function decodeAminoPubkey(data: Uint8Array): PubKey { - const aminoPrefix = data.slice(0, pubkeyAminoPrefixLength); - const rest = data.slice(pubkeyAminoPrefixLength); - if (arrayContentEquals(aminoPrefix, pubkeyAminoPrefixSecp256k1)) { - if (rest.length !== 33) { - throw new Error("Invalid rest data length. Expected 33 bytes (compressed secp256k1 pubkey)."); - } - return { - type: pubkeyType.secp256k1, - value: toBase64(rest), - }; - } else if (arrayContentEquals(aminoPrefix, pubkeyAminoPrefixEd25519)) { - if (rest.length !== 32) { - throw new Error("Invalid rest data length. Expected 32 bytes (Ed25519 pubkey)."); - } - return { - type: pubkeyType.ed25519, - value: toBase64(rest), - }; - } else if (arrayContentEquals(aminoPrefix, pubkeyAminoPrefixSr25519)) { - if (rest.length !== 32) { - throw new Error("Invalid rest data length. Expected 32 bytes (Sr25519 pubkey)."); - } - return { - type: pubkeyType.sr25519, - value: toBase64(rest), - }; - } else { - throw new Error("Unsupported Pubkey type. Amino prefix: " + toHex(aminoPrefix)); - } -} - -/** - * Decodes a bech32 pubkey to Amino binary, which is then decoded to a type/value object. - * The bech32 prefix is ignored and discareded. - * - * @param bechEncoded the bech32 encoded pubkey - */ -export function decodeBech32Pubkey(bechEncoded: string): PubKey { - const { data } = Bech32.decode(bechEncoded); - return decodeAminoPubkey(data); -} - -/** - * Encodes a public key to binary Amino. - */ -export function encodeAminoPubkey(pubkey: PubKey): Uint8Array { - let aminoPrefix: Uint8Array; - switch (pubkey.type) { - // Note: please don't add cases here without writing additional unit tests - case pubkeyType.secp256k1: - aminoPrefix = pubkeyAminoPrefixSecp256k1; - break; - case pubkeyType.ed25519: - aminoPrefix = pubkeyAminoPrefixEd25519; - break; - default: - throw new Error("Unsupported pubkey type"); - } - return new Uint8Array([...aminoPrefix, ...fromBase64(pubkey.value)]); -} - -/** - * Encodes a public key to binary Amino and then to bech32. - * - * @param pubkey the public key to encode - * @param prefix the bech32 prefix (human readable part) - */ -export function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string { - return Bech32.encode(prefix, encodeAminoPubkey(pubkey)); -} diff --git a/packages/launchpad/src/signature.ts b/packages/launchpad/src/signature.ts index d94ad665..15f37f00 100644 --- a/packages/launchpad/src/signature.ts +++ b/packages/launchpad/src/signature.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import { encodeSecp256k1Pubkey, pubkeyType } from "@cosmjs/amino"; import { fromBase64, toBase64 } from "@cosmjs/encoding"; -import { encodeSecp256k1Pubkey } from "./pubkey"; -import { pubkeyType, StdSignature } from "./types"; +import { StdSignature } from "./types"; /** * Takes a binary pubkey and signature to create a signature object diff --git a/packages/launchpad/src/types.ts b/packages/launchpad/src/types.ts index 8f0b5565..7cccf774 100644 --- a/packages/launchpad/src/types.ts +++ b/packages/launchpad/src/types.ts @@ -1,4 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import { PubKey } from "@cosmjs/amino"; + import { Coin } from "./coins"; export interface StdFee { @@ -10,22 +12,3 @@ export interface StdSignature { readonly pub_key: PubKey; readonly signature: string; } - -export interface PubKey { - // type is one of the strings defined in pubkeyType - // I don't use a string literal union here as that makes trouble with json test data: - // https://github.com/cosmos/cosmjs/pull/44#pullrequestreview-353280504 - readonly type: string; - // Value field is base64-encoded in all cases - // Note: if type is Secp256k1, this must contain a COMPRESSED pubkey - to encode from bcp/keycontrol land, you must compress it first - readonly value: string; -} - -export const pubkeyType = { - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */ - secp256k1: "tendermint/PubKeySecp256k1" as const, - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23 */ - ed25519: "tendermint/PubKeyEd25519" as const, - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */ - sr25519: "tendermint/PubKeySr25519" as const, -}; diff --git a/packages/proto-signing/package.json b/packages/proto-signing/package.json index 64355abd..806b3a8f 100644 --- a/packages/proto-signing/package.json +++ b/packages/proto-signing/package.json @@ -43,6 +43,7 @@ "postdefine-proto": "prettier --write \"src/codec/**/*.ts\"" }, "dependencies": { + "@cosmjs/amino": "^0.25.0-alpha.0", "@cosmjs/launchpad": "^0.25.0-alpha.0", "long": "^4.0.0", "protobufjs": "~6.10.2" diff --git a/packages/proto-signing/src/pubkey.ts b/packages/proto-signing/src/pubkey.ts index 08bb9b2f..e44d3b80 100644 --- a/packages/proto-signing/src/pubkey.ts +++ b/packages/proto-signing/src/pubkey.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import { encodeSecp256k1Pubkey, PubKey as AminoPubKey } from "@cosmjs/amino"; import { fromBase64 } from "@cosmjs/encoding"; -import { encodeSecp256k1Pubkey, PubKey as LaunchpadPubKey } from "@cosmjs/launchpad"; import { PubKey } from "./codec/cosmos/crypto/secp256k1/keys"; import { Any } from "./codec/google/protobuf/any"; -export function encodePubkey(pubkey: LaunchpadPubKey): Any { +export function encodePubkey(pubkey: AminoPubKey): Any { switch (pubkey.type) { case "tendermint/PubKeySecp256k1": { const pubkeyProto = PubKey.fromPartial({ @@ -21,7 +21,7 @@ export function encodePubkey(pubkey: LaunchpadPubKey): Any { } } -export function decodePubkey(pubkey?: Any | null): LaunchpadPubKey | null { +export function decodePubkey(pubkey?: Any | null): AminoPubKey | null { if (!pubkey || !pubkey.value) { return null; } diff --git a/packages/stargate/package.json b/packages/stargate/package.json index 70925d26..0e68bab6 100644 --- a/packages/stargate/package.json +++ b/packages/stargate/package.json @@ -43,6 +43,7 @@ }, "dependencies": { "@confio/ics23": "^0.6.3", + "@cosmjs/amino": "^0.25.0-alpha.0", "@cosmjs/encoding": "^0.25.0-alpha.0", "@cosmjs/launchpad": "^0.25.0-alpha.0", "@cosmjs/math": "^0.25.0-alpha.0", diff --git a/packages/stargate/src/accounts.ts b/packages/stargate/src/accounts.ts index 58280523..e6594b35 100644 --- a/packages/stargate/src/accounts.ts +++ b/packages/stargate/src/accounts.ts @@ -1,4 +1,4 @@ -import { PubKey } from "@cosmjs/launchpad"; +import { PubKey } from "@cosmjs/amino"; import { Uint64 } from "@cosmjs/math"; import { decodePubkey } from "@cosmjs/proto-signing"; import { assert } from "@cosmjs/utils"; diff --git a/packages/stargate/src/aminotypes.spec.ts b/packages/stargate/src/aminotypes.spec.ts index 0c768341..bf598e3c 100644 --- a/packages/stargate/src/aminotypes.spec.ts +++ b/packages/stargate/src/aminotypes.spec.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import { encodeBech32Pubkey } from "@cosmjs/amino"; import { fromBase64 } from "@cosmjs/encoding"; import { coin, coins, - encodeBech32Pubkey, MsgBeginRedelegate as LaunchpadMsgBeginRedelegate, MsgCreateValidator as LaunchpadMsgCreateValidator, MsgDelegate as LaunchpadMsgDelegate, diff --git a/packages/stargate/src/aminotypes.ts b/packages/stargate/src/aminotypes.ts index b79edf93..7392af22 100644 --- a/packages/stargate/src/aminotypes.ts +++ b/packages/stargate/src/aminotypes.ts @@ -1,8 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import { decodeBech32Pubkey, encodeBech32Pubkey } from "@cosmjs/amino"; import { fromBase64, toBase64 } from "@cosmjs/encoding"; import { - decodeBech32Pubkey, - encodeBech32Pubkey, Msg, MsgBeginRedelegate as LaunchpadMsgBeginRedelegate, MsgCreateValidator as LaunchpadMsgCreateValidator, diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index b7d5bc75..08d70484 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -1,9 +1,9 @@ +import { encodeSecp256k1Pubkey } from "@cosmjs/amino"; import { fromBase64 } from "@cosmjs/encoding"; import { buildFeeTable, Coin, CosmosFeeTable, - encodeSecp256k1Pubkey, GasLimits, GasPrice, makeSignDoc as makeSignDocAmino,