Skip to content

Commit

Permalink
Merge pull request #1288 from cosmos/prepare-MsgCreateValidator
Browse files Browse the repository at this point in the history
Prepare fixing MsgCreateValidator/MsgEditorValidator Amino JSON
  • Loading branch information
webmaster128 committed Oct 13, 2022
2 parents ea69bfc + 472f78a commit 27c1e90
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,10 @@ and this project adheres to

### Added

- @cosmjs/amino: Add `encodeEd25519Pubkey` analogue to the existing
`encodeSecp256k1Pubkey`.
- @cosmjs/proto-signing: Add Ed25519 support to `encodePubkey` and
`anyToSinglePubkey`. Export `anyToSinglePubkey`.
- @cosmjs/utils: Add `isDefined` which checks for `undefined` in a
TypeScript-friendly way.

Expand Down
21 changes: 21 additions & 0 deletions packages/amino/src/encoding.spec.ts
@@ -1,10 +1,12 @@
import { Random } from "@cosmjs/crypto";
import { fromBase64, fromBech32, fromHex } from "@cosmjs/encoding";

import {
decodeAminoPubkey,
decodeBech32Pubkey,
encodeAminoPubkey,
encodeBech32Pubkey,
encodeEd25519Pubkey,
encodeSecp256k1Pubkey,
} from "./encoding";
import { Pubkey } from "./pubkeys";
Expand Down Expand Up @@ -37,6 +39,25 @@ describe("encoding", () => {
});
});

describe("encodeEd25519Pubkey", () => {
it("encodes a compressed pubkey", () => {
const pubkey = fromBase64("ICKLJPyWYIF35GpOclg0gu957WYJe4PHzyn2scCZoek=");
expect(encodeEd25519Pubkey(pubkey)).toEqual({
type: "tendermint/PubKeyEd25519",
value: "ICKLJPyWYIF35GpOclg0gu957WYJe4PHzyn2scCZoek=",
});
});

it("throws for wrong pubkey lengths", () => {
expect(() => encodeEd25519Pubkey(Random.getBytes(31))).toThrowError(
/ed25519 public key must be 32 bytes long/i,
);
expect(() => encodeEd25519Pubkey(Random.getBytes(64))).toThrowError(
/ed25519 public key must be 32 bytes long/i,
);
});
});

describe("decodeAminoPubkey", () => {
it("works for secp256k1", () => {
const amino = fromBech32(
Expand Down
19 changes: 19 additions & 0 deletions packages/amino/src/encoding.ts
Expand Up @@ -3,6 +3,7 @@ import { Uint53 } from "@cosmjs/math";
import { arrayContentStartsWith } from "@cosmjs/utils";

import {
Ed25519Pubkey,
isEd25519Pubkey,
isMultisigThresholdPubkey,
isSecp256k1Pubkey,
Expand All @@ -12,6 +13,10 @@ import {
Secp256k1Pubkey,
} from "./pubkeys";

/**
* Takes a Secp256k1 public key as raw bytes and returns the Amino JSON
* representation of it (the type/value wrapper object).
*/
export function encodeSecp256k1Pubkey(pubkey: Uint8Array): Secp256k1Pubkey {
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");
Expand All @@ -22,6 +27,20 @@ export function encodeSecp256k1Pubkey(pubkey: Uint8Array): Secp256k1Pubkey {
};
}

/**
* Takes an Edd25519 public key as raw bytes and returns the Amino JSON
* representation of it (the type/value wrapper object).
*/
export function encodeEd25519Pubkey(pubkey: Uint8Array): Ed25519Pubkey {
if (pubkey.length !== 32) {
throw new Error("Ed25519 public key must be 32 bytes long");
}
return {
type: pubkeyType.ed25519,
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
Expand Down
1 change: 1 addition & 0 deletions packages/amino/src/index.ts
Expand Up @@ -10,6 +10,7 @@ export {
decodeBech32Pubkey,
encodeAminoPubkey,
encodeBech32Pubkey,
encodeEd25519Pubkey,
encodeSecp256k1Pubkey,
} from "./encoding";
export { createMultisigThresholdPubkey } from "./multisig";
Expand Down
2 changes: 1 addition & 1 deletion packages/proto-signing/src/index.ts
Expand Up @@ -8,7 +8,7 @@ export {
} from "./directsecp256k1hdwallet";
export { DirectSecp256k1Wallet } from "./directsecp256k1wallet";
export { makeCosmoshubPath } from "./paths";
export { decodePubkey, encodePubkey } from "./pubkey";
export { anyToSinglePubkey, decodePubkey, encodePubkey } from "./pubkey";
export {
DecodeObject,
EncodeObject,
Expand Down
24 changes: 24 additions & 0 deletions packages/proto-signing/src/pubkey.spec.ts
Expand Up @@ -8,6 +8,9 @@ describe("pubkey", () => {
const defaultPubkeyBase64 = "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP";
const defaultPubkeyBytes = fromBase64(defaultPubkeyBase64);
const defaultPubkeyProtoBytes = Uint8Array.from([0x0a, defaultPubkeyBytes.length, ...defaultPubkeyBytes]);
const ed25519PubkeyBase64 = "kEX3edqZB+HdCV92TPS7ePX0DtP62GWIjmrveZ5pnaQ=";
const ed25519PubkeyBytes = fromBase64(ed25519PubkeyBase64);
const ed25519PubkeyProtoBytes = Uint8Array.from([0x0a, ed25519PubkeyBytes.length, ...ed25519PubkeyBytes]);

describe("encodePubkey", () => {
it("works for secp256k1", () => {
Expand All @@ -20,6 +23,16 @@ describe("pubkey", () => {
);
});

it("works for ed25519", () => {
const pubkey = { type: "tendermint/PubKeyEd25519", value: ed25519PubkeyBase64 };
expect(encodePubkey(pubkey)).toEqual(
Any.fromPartial({
typeUrl: "/cosmos.crypto.ed25519.PubKey",
value: ed25519PubkeyProtoBytes,
}),
);
});

it("throws for unsupported pubkey types", () => {
const pubkey = {
type: "tendermint/PubKeyUnknown",
Expand All @@ -41,6 +54,17 @@ describe("pubkey", () => {
});
});

it("works for ed25519", () => {
const pubkey = {
typeUrl: "/cosmos.crypto.ed25519.PubKey",
value: ed25519PubkeyProtoBytes,
};
expect(decodePubkey(pubkey)).toEqual({
type: "tendermint/PubKeyEd25519",
value: ed25519PubkeyBase64,
});
});

it("throws for unsupported pubkey types", () => {
const pubkey = {
typeUrl: "/cosmos.crypto.unknown.PubKey",
Expand Down
45 changes: 37 additions & 8 deletions packages/proto-signing/src/pubkey.ts
@@ -1,6 +1,8 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {
encodeEd25519Pubkey,
encodeSecp256k1Pubkey,
isEd25519Pubkey,
isMultisigThresholdPubkey,
isSecp256k1Pubkey,
MultisigThresholdPubkey,
Expand All @@ -9,18 +11,33 @@ import {
} from "@cosmjs/amino";
import { fromBase64 } from "@cosmjs/encoding";
import { Uint53 } from "@cosmjs/math";
import { PubKey as CosmosCryptoEd25519Pubkey } from "cosmjs-types/cosmos/crypto/ed25519/keys";
import { LegacyAminoPubKey } from "cosmjs-types/cosmos/crypto/multisig/keys";
import { PubKey } from "cosmjs-types/cosmos/crypto/secp256k1/keys";
import { PubKey as CosmosCryptoSecp256k1Pubkey } from "cosmjs-types/cosmos/crypto/secp256k1/keys";
import { Any } from "cosmjs-types/google/protobuf/any";

/**
* Takes a pubkey in the Amino JSON object style (type/value wrapper)
* and convertes it into a protobuf `Any`.
*
* This is the reverse operation to `decodePubkey`.
*/
export function encodePubkey(pubkey: Pubkey): Any {
if (isSecp256k1Pubkey(pubkey)) {
const pubkeyProto = PubKey.fromPartial({
const pubkeyProto = CosmosCryptoSecp256k1Pubkey.fromPartial({
key: fromBase64(pubkey.value),
});
return Any.fromPartial({
typeUrl: "/cosmos.crypto.secp256k1.PubKey",
value: Uint8Array.from(PubKey.encode(pubkeyProto).finish()),
value: Uint8Array.from(CosmosCryptoSecp256k1Pubkey.encode(pubkeyProto).finish()),
});
} else if (isEd25519Pubkey(pubkey)) {
const pubkeyProto = CosmosCryptoEd25519Pubkey.fromPartial({
key: fromBase64(pubkey.value),
});
return Any.fromPartial({
typeUrl: "/cosmos.crypto.ed25519.PubKey",
value: Uint8Array.from(CosmosCryptoEd25519Pubkey.encode(pubkeyProto).finish()),
});
} else if (isMultisigThresholdPubkey(pubkey)) {
const pubkeyProto = LegacyAminoPubKey.fromPartial({
Expand All @@ -36,12 +53,23 @@ export function encodePubkey(pubkey: Pubkey): Any {
}
}

function decodeSinglePubkey(pubkey: Any): SinglePubkey {
/**
* Decodes a single pubkey (i.e. not a multisig pubkey) from `Any` into
* `SinglePubkey`.
*
* In most cases you probably want to use `decodePubkey`, but `anyToSinglePubkey`
* might be preferred in CosmJS 0.29.x due to https://github.com/cosmos/cosmjs/issues/1289.
*/
export function anyToSinglePubkey(pubkey: Any): SinglePubkey {
switch (pubkey.typeUrl) {
case "/cosmos.crypto.secp256k1.PubKey": {
const { key } = PubKey.decode(pubkey.value);
const { key } = CosmosCryptoSecp256k1Pubkey.decode(pubkey.value);
return encodeSecp256k1Pubkey(key);
}
case "/cosmos.crypto.ed25519.PubKey": {
const { key } = CosmosCryptoEd25519Pubkey.decode(pubkey.value);
return encodeEd25519Pubkey(key);
}
default:
throw new Error(`Pubkey type_url ${pubkey.typeUrl} not recognized as single public key type`);
}
Expand All @@ -53,16 +81,17 @@ export function decodePubkey(pubkey?: Any | null): Pubkey | null {
}

switch (pubkey.typeUrl) {
case "/cosmos.crypto.secp256k1.PubKey": {
return decodeSinglePubkey(pubkey);
case "/cosmos.crypto.secp256k1.PubKey":
case "/cosmos.crypto.ed25519.PubKey": {
return anyToSinglePubkey(pubkey);
}
case "/cosmos.crypto.multisig.LegacyAminoPubKey": {
const { threshold, publicKeys } = LegacyAminoPubKey.decode(pubkey.value);
const out: MultisigThresholdPubkey = {
type: "tendermint/PubKeyMultisigThreshold",
value: {
threshold: threshold.toString(),
pubkeys: publicKeys.map(decodeSinglePubkey),
pubkeys: publicKeys.map(anyToSinglePubkey),
},
};
return out;
Expand Down

0 comments on commit 27c1e90

Please sign in to comment.