mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-10 21:49:15 +00:00
Merge pull request #366 from CosmWasm/encodeBech32Pubkey-tests
Backport pubkey helpers to 0.22
This commit is contained in:
commit
749f195677
@ -1,5 +1,13 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 0.22.1 (unreleased)
|
||||
|
||||
- @cosmjs/cli: Import `encodeAminoPubkey`, `encodeBech32Pubkey`,
|
||||
`decodeAminoPubkey` and `decodeBech32Pubkey` by default.
|
||||
- @cosmjs/launchpad: Add ed25519 support to `encodeBech32Pubkey`.
|
||||
- @cosmjs/launchpad: Add `encodeAminoPubkey` and `decodeAminoPubkey`.
|
||||
- @cosmjs/utils: Add `arrayContentEquals`.
|
||||
|
||||
## 0.22.0 (2020-08-03)
|
||||
|
||||
- @cosmjs/cli: Now supports HTTPs URLs for `--init` code sources.
|
||||
|
@ -89,6 +89,10 @@ export async function main(originalArgs: readonly string[]): Promise<void> {
|
||||
[
|
||||
"coin",
|
||||
"coins",
|
||||
"decodeAminoPubkey",
|
||||
"decodeBech32Pubkey",
|
||||
"encodeAminoPubkey",
|
||||
"encodeBech32Pubkey",
|
||||
"encodeSecp256k1Pubkey",
|
||||
"encodeSecp256k1Signature",
|
||||
"logs",
|
||||
@ -112,7 +116,7 @@ export async function main(originalArgs: readonly string[]): Promise<void> {
|
||||
],
|
||||
],
|
||||
["@cosmjs/math", ["Decimal", "Int53", "Uint32", "Uint53", "Uint64"]],
|
||||
["@cosmjs/utils", ["assert", "sleep"]],
|
||||
["@cosmjs/utils", ["assert", "arrayContentEquals", "sleep"]],
|
||||
]);
|
||||
|
||||
console.info(colors.green("Initializing session for you. Have fun!"));
|
||||
@ -156,6 +160,12 @@ export async function main(originalArgs: readonly string[]): Promise<void> {
|
||||
const data = toAscii("foo bar");
|
||||
const signature = await wallet.sign(address, data);
|
||||
|
||||
const bechPubkey = "coralvalconspub1zcjduepqvxg72ccnl9r65fv0wn3amlk4sfzqfe2k36l073kjx2qyaf6sk23qw7j8wq";
|
||||
assert(encodeBech32Pubkey(decodeBech32Pubkey(bechPubkey), "coralvalconspub") == bechPubkey);
|
||||
|
||||
const aminoPubkey = fromHex("eb5ae98721034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c70290");
|
||||
assert(arrayContentEquals(encodeAminoPubkey(decodeAminoPubkey(aminoPubkey)), aminoPubkey));
|
||||
|
||||
console.info("Done testing, will exit now.");
|
||||
process.exit(0);
|
||||
`;
|
||||
|
@ -44,7 +44,6 @@
|
||||
"@cosmjs/math": "^0.22.0",
|
||||
"@cosmjs/utils": "^0.22.0",
|
||||
"axios": "^0.19.0",
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"pako": "^1.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -45,8 +45,7 @@
|
||||
"@cosmjs/encoding": "^0.22.0",
|
||||
"@cosmjs/math": "^0.22.0",
|
||||
"@cosmjs/utils": "^0.22.0",
|
||||
"axios": "^0.19.0",
|
||||
"fast-deep-equal": "^3.1.1"
|
||||
"axios": "^0.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"readonly-date": "^1.0.0"
|
||||
|
@ -84,7 +84,13 @@ export {
|
||||
uint64ToString,
|
||||
} from "./lcdapi";
|
||||
export { isMsgDelegate, isMsgSend, Msg, MsgDelegate, MsgSend } from "./msgs";
|
||||
export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey";
|
||||
export {
|
||||
decodeAminoPubkey,
|
||||
decodeBech32Pubkey,
|
||||
encodeAminoPubkey,
|
||||
encodeBech32Pubkey,
|
||||
encodeSecp256k1Pubkey,
|
||||
} from "./pubkey";
|
||||
export { findSequenceForSignedTx } from "./sequence";
|
||||
export { encodeSecp256k1Signature, decodeSignature } from "./signature";
|
||||
export { FeeTable, SigningCosmosClient } from "./signingcosmosclient";
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { fromBase64 } from "@cosmjs/encoding";
|
||||
import { Bech32, fromBase64 } from "@cosmjs/encoding";
|
||||
|
||||
import { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey";
|
||||
import {
|
||||
decodeAminoPubkey,
|
||||
decodeBech32Pubkey,
|
||||
encodeAminoPubkey,
|
||||
encodeBech32Pubkey,
|
||||
encodeSecp256k1Pubkey,
|
||||
} from "./pubkey";
|
||||
import { PubKey } from "./types";
|
||||
|
||||
describe("pubkey", () => {
|
||||
@ -21,6 +27,30 @@ describe("pubkey", () => {
|
||||
});
|
||||
});
|
||||
|
||||
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(
|
||||
@ -39,6 +69,44 @@ describe("pubkey", () => {
|
||||
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", () => {
|
||||
@ -51,5 +119,17 @@ describe("pubkey", () => {
|
||||
"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",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Bech32, fromBase64, fromHex, toBase64, toHex } from "@cosmjs/encoding";
|
||||
import equal from "fast-deep-equal";
|
||||
import { arrayContentEquals } from "@cosmjs/utils";
|
||||
|
||||
import { PubKey, pubkeyType } from "./types";
|
||||
|
||||
@ -21,12 +21,13 @@ const pubkeyAminoPrefixEd25519 = fromHex("1624de6420");
|
||||
const pubkeyAminoPrefixSr25519 = fromHex("0dfb1005");
|
||||
const pubkeyAminoPrefixLength = pubkeyAminoPrefixSecp256k1.length;
|
||||
|
||||
export function decodeBech32Pubkey(bechEncoded: string): PubKey {
|
||||
const { data } = Bech32.decode(bechEncoded);
|
||||
|
||||
/**
|
||||
* 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 (equal(aminoPrefix, pubkeyAminoPrefixSecp256k1)) {
|
||||
if (arrayContentEquals(aminoPrefix, pubkeyAminoPrefixSecp256k1)) {
|
||||
if (rest.length !== 33) {
|
||||
throw new Error("Invalid rest data length. Expected 33 bytes (compressed secp256k1 pubkey).");
|
||||
}
|
||||
@ -34,7 +35,7 @@ export function decodeBech32Pubkey(bechEncoded: string): PubKey {
|
||||
type: pubkeyType.secp256k1,
|
||||
value: toBase64(rest),
|
||||
};
|
||||
} else if (equal(aminoPrefix, pubkeyAminoPrefixEd25519)) {
|
||||
} else if (arrayContentEquals(aminoPrefix, pubkeyAminoPrefixEd25519)) {
|
||||
if (rest.length !== 32) {
|
||||
throw new Error("Invalid rest data length. Expected 32 bytes (Ed25519 pubkey).");
|
||||
}
|
||||
@ -42,7 +43,7 @@ export function decodeBech32Pubkey(bechEncoded: string): PubKey {
|
||||
type: pubkeyType.ed25519,
|
||||
value: toBase64(rest),
|
||||
};
|
||||
} else if (equal(aminoPrefix, pubkeyAminoPrefixSr25519)) {
|
||||
} else if (arrayContentEquals(aminoPrefix, pubkeyAminoPrefixSr25519)) {
|
||||
if (rest.length !== 32) {
|
||||
throw new Error("Invalid rest data length. Expected 32 bytes (Sr25519 pubkey).");
|
||||
}
|
||||
@ -55,17 +56,42 @@ export function decodeBech32Pubkey(bechEncoded: string): PubKey {
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string {
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
|
||||
const data = new Uint8Array([...aminoPrefix, ...fromBase64(pubkey.value)]);
|
||||
return Bech32.encode(prefix, data);
|
||||
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));
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ export interface StdSignature {
|
||||
}
|
||||
|
||||
export interface PubKey {
|
||||
// type is one of the strings defined in pubkeyTypes
|
||||
// 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/CosmWasm/cosmjs/pull/44#pullrequestreview-353280504
|
||||
readonly type: string;
|
||||
@ -54,5 +54,3 @@ export const pubkeyType = {
|
||||
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */
|
||||
sr25519: "tendermint/PubKeySr25519" as const,
|
||||
};
|
||||
|
||||
export const pubkeyTypes: readonly string[] = [pubkeyType.secp256k1, pubkeyType.ed25519, pubkeyType.sr25519];
|
||||
|
8
packages/launchpad/types/index.d.ts
vendored
8
packages/launchpad/types/index.d.ts
vendored
@ -82,7 +82,13 @@ export {
|
||||
uint64ToString,
|
||||
} from "./lcdapi";
|
||||
export { isMsgDelegate, isMsgSend, Msg, MsgDelegate, MsgSend } from "./msgs";
|
||||
export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey";
|
||||
export {
|
||||
decodeAminoPubkey,
|
||||
decodeBech32Pubkey,
|
||||
encodeAminoPubkey,
|
||||
encodeBech32Pubkey,
|
||||
encodeSecp256k1Pubkey,
|
||||
} from "./pubkey";
|
||||
export { findSequenceForSignedTx } from "./sequence";
|
||||
export { encodeSecp256k1Signature, decodeSignature } from "./signature";
|
||||
export { FeeTable, SigningCosmosClient } from "./signingcosmosclient";
|
||||
|
20
packages/launchpad/types/pubkey.d.ts
vendored
20
packages/launchpad/types/pubkey.d.ts
vendored
@ -1,4 +1,24 @@
|
||||
import { PubKey } from "./types";
|
||||
export declare function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey;
|
||||
/**
|
||||
* Decodes a pubkey in the Amino binary format to a type/value object.
|
||||
*/
|
||||
export declare function decodeAminoPubkey(data: Uint8Array): PubKey;
|
||||
/**
|
||||
* 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 declare function decodeBech32Pubkey(bechEncoded: string): PubKey;
|
||||
/**
|
||||
* Encodes a public key to binary Amino.
|
||||
*/
|
||||
export declare function encodeAminoPubkey(pubkey: PubKey): Uint8Array;
|
||||
/**
|
||||
* 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 declare function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string;
|
||||
|
1
packages/launchpad/types/types.d.ts
vendored
1
packages/launchpad/types/types.d.ts
vendored
@ -36,4 +36,3 @@ export declare const pubkeyType: {
|
||||
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */
|
||||
sr25519: "tendermint/PubKeySr25519";
|
||||
};
|
||||
export declare const pubkeyTypes: readonly string[];
|
||||
|
33
packages/utils/src/array.spec.ts
Normal file
33
packages/utils/src/array.spec.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { arrayContentEquals } from "./arrays";
|
||||
|
||||
describe("array", () => {
|
||||
describe("arrayContentEquals", () => {
|
||||
it("can compare number arrays", () => {
|
||||
expect(arrayContentEquals([1, 2, 3], [1, 2, 3])).toEqual(true);
|
||||
expect(arrayContentEquals([1, 2, 3], [1, 2, 3, 4])).toEqual(false);
|
||||
expect(arrayContentEquals([1, 2, 3], [3, 2, 1])).toEqual(false);
|
||||
});
|
||||
|
||||
it("can compare string arrays", () => {
|
||||
expect(arrayContentEquals(["a", "b"], ["a", "b"])).toEqual(true);
|
||||
expect(arrayContentEquals(["a", "b"], ["a", "b", "c"])).toEqual(false);
|
||||
expect(arrayContentEquals(["a", "b"], ["b", "a"])).toEqual(false);
|
||||
});
|
||||
|
||||
it("can compare bool arrays", () => {
|
||||
expect(arrayContentEquals([true, false], [true, false])).toEqual(true);
|
||||
expect(arrayContentEquals([true, false], [true, false, true])).toEqual(false);
|
||||
expect(arrayContentEquals([true, false], [false, true])).toEqual(false);
|
||||
});
|
||||
|
||||
it("can compare different array types", () => {
|
||||
expect(arrayContentEquals([1, 2, 3], new Uint8Array([1, 2, 3]))).toEqual(true);
|
||||
expect(arrayContentEquals([1, 2, 3], new Uint8Array([3, 2, 1]))).toEqual(false);
|
||||
});
|
||||
|
||||
it("works for empty arrays", () => {
|
||||
expect(arrayContentEquals([], [])).toEqual(true);
|
||||
expect(arrayContentEquals([], new Uint8Array([]))).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
18
packages/utils/src/arrays.ts
Normal file
18
packages/utils/src/arrays.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Compares the content of two arrays-like objects for equality.
|
||||
*
|
||||
* Equality is defined as having equal length and element values, where element equality means `===` returning `true`.
|
||||
*
|
||||
* This allows you to compare the content of a Buffer, Uint8Array or number[], ignoring the specific type.
|
||||
* As a consequence, this returns different results than Jasmine's `toEqual`, which ensures elements have the same type.
|
||||
*/
|
||||
export function arrayContentEquals<T extends string | number | boolean>(
|
||||
a: ArrayLike<T>,
|
||||
b: ArrayLike<T>,
|
||||
): boolean {
|
||||
if (a.length !== b.length) return false;
|
||||
for (let i = 0; i < a.length; ++i) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
export { arrayContentEquals } from "./arrays";
|
||||
export { assert } from "./assert";
|
||||
export { sleep } from "./sleep";
|
||||
export { isNonNullObject, isUint8Array } from "./typechecks";
|
||||
|
12
packages/utils/types/arrays.d.ts
vendored
Normal file
12
packages/utils/types/arrays.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Compares the content of two arrays-like objects for equality.
|
||||
*
|
||||
* Equality is defined as having equal length and element values, where element equality means `===` returning `true`.
|
||||
*
|
||||
* This allows you to compare the content of a Buffer, Uint8Array or number[], ignoring the specific type.
|
||||
* As a consequence, this returns different results than Jasmine's `toEqual`, which ensures elements have the same type.
|
||||
*/
|
||||
export declare function arrayContentEquals<T extends string | number | boolean>(
|
||||
a: ArrayLike<T>,
|
||||
b: ArrayLike<T>,
|
||||
): boolean;
|
1
packages/utils/types/index.d.ts
vendored
1
packages/utils/types/index.d.ts
vendored
@ -1,3 +1,4 @@
|
||||
export { arrayContentEquals } from "./arrays";
|
||||
export { assert } from "./assert";
|
||||
export { sleep } from "./sleep";
|
||||
export { isNonNullObject, isUint8Array } from "./typechecks";
|
||||
|
Loading…
x
Reference in New Issue
Block a user