Pull out pubkey functionality into @cosmjs/amino

This commit is contained in:
Simon Warta 2021-03-23 13:37:18 +01:00
parent 9a87826142
commit f8cf23766c
36 changed files with 493 additions and 278 deletions

View File

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

View File

@ -0,0 +1,8 @@
node_modules/
build/
custom_types/
dist/
docs/
generated/
types/

3
packages/amino/.gitignore vendored Normal file
View File

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

1
packages/amino/.nycrc.yml Symbolic link
View File

@ -0,0 +1 @@
../../.nycrc.yml

12
packages/amino/README.md Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
Directory used to trigger lerna package updates for all packages

View File

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

View File

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

View File

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

View File

@ -0,0 +1,8 @@
export {
decodeAminoPubkey,
decodeBech32Pubkey,
encodeAminoPubkey,
encodeBech32Pubkey,
encodeSecp256k1Pubkey,
} from "./encoding";
export { PubKey, pubkeyType } from "./pubkeys";

View File

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

View File

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

11
packages/amino/typedoc.js Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import { PubKey } from "../types";
import { PubKey } from "@cosmjs/amino";
import { normalizePubkey, uint64ToNumber, uint64ToString } from "./utils";
describe("utils", () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
import { encodeSecp256k1Pubkey } from "@cosmjs/amino";
import { fromBase64 } from "@cosmjs/encoding";
import {
buildFeeTable,
Coin,
CosmosFeeTable,
encodeSecp256k1Pubkey,
GasLimits,
GasPrice,
makeSignDoc as makeSignDocAmino,