Skip to content

Commit

Permalink
EthSecp
Browse files Browse the repository at this point in the history
add amino tests for EthSecp

decode ethsecp256k1 from bech32 is not possible yet: secp256k1 and ethsecp256k1 has the same prefix

chore: patch pubkey for EthSecp
  • Loading branch information
zakarialounes committed Jan 18, 2023
1 parent ef79d44 commit 5915b31
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 9 deletions.
64 changes: 64 additions & 0 deletions packages/amino/src/encoding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
encodeAminoPubkey,
encodeBech32Pubkey,
encodeEd25519Pubkey,
encodeEthSecp256k1Pubkey,
encodeSecp256k1Pubkey,
} from "./encoding";
import { Pubkey } from "./pubkeys";
Expand Down Expand Up @@ -58,6 +59,25 @@ describe("encoding", () => {
});
});

describe("encodeEthSecp256k1Pubkey", () => {
it("encodes a compressed pubkey", () => {
const pubkey = fromBase64("Ay+1uNze+glFQM+T05EfzL8fQ1Y/wqO8K7q6tUM3BGin");
expect(encodeEthSecp256k1Pubkey(pubkey)).toEqual({
type: "tendermint/PubKeyEthSecp256k1",
value: "Ay+1uNze+glFQM+T05EfzL8fQ1Y/wqO8K7q6tUM3BGin",
});
});

it("throws for uncompressed public keys", () => {
const pubkey = fromBase64(
"BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=",
);
expect(() => encodeEthSecp256k1Pubkey(pubkey)).toThrowError(
/public key must be compressed ethsecp256k1/i,
);
});
});

describe("decodeAminoPubkey", () => {
it("works for secp256k1", () => {
const amino = fromBech32(
Expand All @@ -69,6 +89,18 @@ describe("encoding", () => {
});
});

// @todo: find a way to bypass the identical prefix for secp256k1 and ethsecp256k1
// in @amino/encoding:decodeAminoPubkey
// it("works for ethsecp256k1", () => {
// const amino = fromBech32(
// "evmospub1addwnpepqvhmtwxummaqj32qe7fa8yglejl37s6k8lp280pth2at2sehq352w6wm5v0",
// ).data;
// expect(decodeAminoPubkey(amino)).toEqual({
// type: "tendermint/PubKeyEthSecp256k1",
// value: "Ay+1uNze+glFQM+T05EfzL8fQ1Y/wqO8K7q6tUM3BGin",
// });
// });

it("works for ed25519", () => {
// Encoded from `corald tendermint show-validator`
// Decoded from http://localhost:26657/validators
Expand Down Expand Up @@ -143,6 +175,17 @@ describe("encoding", () => {
});
});

// @todo: find a way to bypass the identical prefix for secp256k1 and ethsecp256k1
// in @amino/encoding:decodeAminoPubkey
// it("works for ethsecp256k1", () => {
// expect(
// decodeBech32Pubkey("evmospub1addwnpepqvhmtwxummaqj32qe7fa8yglejl37s6k8lp280pth2at2sehq352w6wm5v0"),
// ).toEqual({
// type: "tendermint/PubKeyEthSecp256k1",
// value: "Ay+1uNze+glFQM+T05EfzL8fQ1Y/wqO8K7q6tUM3BGin",
// });
// });

it("works for enigma pubkey", () => {
expect(
decodeBech32Pubkey("enigmapub1addwnpepqw5k9p439nw0zpg2aundx4umwx4nw233z5prpjqjv5anl5grmnchzp2xwvv"),
Expand Down Expand Up @@ -183,6 +226,17 @@ describe("encoding", () => {
expect(encodeAminoPubkey(pubkey)).toEqual(expected);
});

it("works for ethsecp256k1", () => {
const pubkey: Pubkey = {
type: "tendermint/PubKeyEthSecp256k1",
value: "Ay+1uNze+glFQM+T05EfzL8fQ1Y/wqO8K7q6tUM3BGin",
};
const expected = fromBech32(
"evmospub1addwnpepqvhmtwxummaqj32qe7fa8yglejl37s6k8lp280pth2at2sehq352w6wm5v0",
).data;
expect(encodeAminoPubkey(pubkey)).toEqual(expected);
});

it("works for ed25519", () => {
// Decoded from http://localhost:26657/validators
// Encoded from `corald tendermint show-validator`
Expand All @@ -208,6 +262,16 @@ describe("encoding", () => {
);
});

it("works for ethsecp256k1", () => {
const pubkey: Pubkey = {
type: "tendermint/PubKeyEthSecp256k1",
value: "Ay+1uNze+glFQM+T05EfzL8fQ1Y/wqO8K7q6tUM3BGin",
};
expect(encodeBech32Pubkey(pubkey, "evmospub")).toEqual(
"evmospub1addwnpepqvhmtwxummaqj32qe7fa8yglejl37s6k8lp280pth2at2sehq352w6wm5v0",
);
});

it("works for ed25519", () => {
// Decoded from http://localhost:26657/validators
// Encoded from `corald tendermint show-validator`
Expand Down
29 changes: 29 additions & 0 deletions packages/amino/src/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { arrayContentStartsWith } from "@cosmjs/utils";

import {
Ed25519Pubkey,
EthSecp256k1Pubkey,
isEd25519Pubkey,
isEthSecp256k1Pubkey,
isMultisigThresholdPubkey,
isSecp256k1Pubkey,
MultisigThresholdPubkey,
Expand All @@ -27,6 +29,20 @@ export function encodeSecp256k1Pubkey(pubkey: Uint8Array): Secp256k1Pubkey {
};
}

/**
* Takes an EthSecp256k1 public key as raw bytes and returns the Amino JSON
* representation of it (the type/value wrapper object).
*/
export function encodeEthSecp256k1Pubkey(pubkey: Uint8Array): EthSecp256k1Pubkey {
if (pubkey.length !== 33 || (pubkey[0] !== 0x02 && pubkey[0] !== 0x03)) {
throw new Error("Public key must be compressed ethsecp256k1, i.e. 33 bytes starting with 0x02 or 0x03");
}
return {
type: pubkeyType.ethsecp256k1,
value: toBase64(pubkey),
};
}

/**
* Takes an Edd25519 public key as raw bytes and returns the Amino JSON
* representation of it (the type/value wrapper object).
Expand All @@ -45,13 +61,15 @@ export function encodeEd25519Pubkey(pubkey: Uint8Array): Ed25519Pubkey {
// 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("eb5ae987" + "21" /* fixed length */);
const pubkeyAminoPrefixEthSecp256k1 = fromHex("eb5ae987" + "21" /* fixed length */);
const pubkeyAminoPrefixEd25519 = fromHex("1624de64" + "20" /* fixed length */);
const pubkeyAminoPrefixSr25519 = fromHex("0dfb1005" + "20" /* fixed length */);
/** See https://github.com/tendermint/tendermint/commit/38b401657e4ad7a7eeb3c30a3cbf512037df3740 */
const pubkeyAminoPrefixMultisigThreshold = fromHex("22c1f7e2" /* variable length not included */);

/**
* Decodes a pubkey in the Amino binary format to a type/value object.
* @todo: find a clean way to distinct Secp256k1 and EthSecp256k1 (has the same prefix)
*/
export function decodeAminoPubkey(data: Uint8Array): Pubkey {
if (arrayContentStartsWith(data, pubkeyAminoPrefixSecp256k1)) {
Expand All @@ -63,6 +81,15 @@ export function decodeAminoPubkey(data: Uint8Array): Pubkey {
type: pubkeyType.secp256k1,
value: toBase64(rest),
};
} else if (arrayContentStartsWith(data, pubkeyAminoPrefixEthSecp256k1)) {
const rest = data.slice(pubkeyAminoPrefixEthSecp256k1.length);
if (rest.length !== 33) {
throw new Error("Invalid rest data length. Expected 33 bytes (compressed ethsecp256k1 pubkey).");
}
return {
type: pubkeyType.ethsecp256k1,
value: toBase64(rest),
};
} else if (arrayContentStartsWith(data, pubkeyAminoPrefixEd25519)) {
const rest = data.slice(pubkeyAminoPrefixEd25519.length);
if (rest.length !== 32) {
Expand Down Expand Up @@ -207,6 +234,8 @@ export function encodeAminoPubkey(pubkey: Pubkey): Uint8Array {
return new Uint8Array([...pubkeyAminoPrefixEd25519, ...fromBase64(pubkey.value)]);
} else if (isSecp256k1Pubkey(pubkey)) {
return new Uint8Array([...pubkeyAminoPrefixSecp256k1, ...fromBase64(pubkey.value)]);
} else if (isEthSecp256k1Pubkey(pubkey)) {
return new Uint8Array([...pubkeyAminoPrefixEthSecp256k1, ...fromBase64(pubkey.value)]);
} else {
throw new Error("Unsupported pubkey type");
}
Expand Down
3 changes: 3 additions & 0 deletions packages/amino/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ export {
encodeAminoPubkey,
encodeBech32Pubkey,
encodeEd25519Pubkey,
encodeEthSecp256k1Pubkey,
encodeSecp256k1Pubkey,
} from "./encoding";
export { createMultisigThresholdPubkey } from "./multisig";
export { makeCosmoshubPath } from "./paths";
export {
Ed25519Pubkey,
EthSecp256k1Pubkey,
isEd25519Pubkey,
isEthSecp256k1Pubkey,
isMultisigThresholdPubkey,
isSecp256k1Pubkey,
isSinglePubkey,
Expand Down
14 changes: 12 additions & 2 deletions packages/amino/src/pubkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,20 @@ export function isSecp256k1Pubkey(pubkey: Pubkey): pubkey is Secp256k1Pubkey {
return (pubkey as Secp256k1Pubkey).type === "tendermint/PubKeySecp256k1";
}

export interface EthSecp256k1Pubkey extends SinglePubkey {
readonly type: "tendermint/PubKeyEthSecp256k1";
readonly value: string;
}

export function isEthSecp256k1Pubkey(pubkey: Pubkey): pubkey is EthSecp256k1Pubkey {
return (pubkey as EthSecp256k1Pubkey).type === "tendermint/PubKeyEthSecp256k1";
}

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 */
secp256k1: "tendermint/PubKeySecp256k1" as const,
ethsecp256k1: "tendermint/PubKeyEthSecp256k1" as const,
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */
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,
Expand Down
19 changes: 15 additions & 4 deletions packages/proto-signing/src/pubkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
encodeEd25519Pubkey,
encodeSecp256k1Pubkey,
isEd25519Pubkey,
isEthSecp256k1Pubkey,
isMultisigThresholdPubkey,
isSecp256k1Pubkey,
MultisigThresholdPubkey,
Expand Down Expand Up @@ -39,6 +40,14 @@ export function encodePubkey(pubkey: Pubkey): Any {
typeUrl: "/cosmos.crypto.ed25519.PubKey",
value: Uint8Array.from(CosmosCryptoEd25519Pubkey.encode(pubkeyProto).finish()),
});
} else if (isEthSecp256k1Pubkey(pubkey)) {
const pubkeyProto = CosmosCryptoSecp256k1Pubkey.fromPartial({
key: fromBase64(pubkey.value),
});
return Any.fromPartial({
typeUrl: "/ethermint.crypto.v1.ethsecp256k1.PubKey",
value: Uint8Array.from(CosmosCryptoSecp256k1Pubkey.encode(pubkeyProto).finish()),
});
} else if (isMultisigThresholdPubkey(pubkey)) {
const pubkeyProto = LegacyAminoPubKey.fromPartial({
threshold: Uint53.fromString(pubkey.value.threshold).toNumber(),
Expand All @@ -62,14 +71,15 @@ export function encodePubkey(pubkey: Pubkey): Any {
*/
export function anyToSinglePubkey(pubkey: Any): SinglePubkey {
switch (pubkey.typeUrl) {
case "/cosmos.crypto.secp256k1.PubKey": {
const { key } = CosmosCryptoSecp256k1Pubkey.decode(pubkey.value);
return encodeSecp256k1Pubkey(key);
}
case "/cosmos.crypto.ed25519.PubKey": {
const { key } = CosmosCryptoEd25519Pubkey.decode(pubkey.value);
return encodeEd25519Pubkey(key);
}
case "/ethermint.crypto.v1.ethsecp256k1.PubKey":
case "/cosmos.crypto.secp256k1.PubKey": {
const {key} = CosmosCryptoSecp256k1Pubkey.decode(pubkey.value);
return encodeSecp256k1Pubkey(key);
}
default:
throw new Error(`Pubkey type_url ${pubkey.typeUrl} not recognized as single public key type`);
}
Expand All @@ -81,6 +91,7 @@ export function decodePubkey(pubkey?: Any | null): Pubkey | null {
}

switch (pubkey.typeUrl) {
case "/ethermint.crypto.v1.ethsecp256k1.PubKey":
case "/cosmos.crypto.secp256k1.PubKey":
case "/cosmos.crypto.ed25519.PubKey": {
return anyToSinglePubkey(pubkey);
Expand Down
5 changes: 5 additions & 0 deletions packages/stargate/src/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ export function accountFromAny(input: Any): Account {
assert(baseAccount);
return accountFromBaseAccount(baseAccount);
}
case "/ethermint.types.v1.EthAccount": {
const baseAccount = ModuleAccount.decode(value).baseAccount;
assert(baseAccount);
return accountFromBaseAccount(baseAccount);
}

// vesting

Expand Down
19 changes: 16 additions & 3 deletions packages/stargate/src/signingstargateclient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { encodeSecp256k1Pubkey, makeSignDoc as makeSignDocAmino, StdFee } from "@cosmjs/amino";
import {
encodeEthSecp256k1Pubkey,
encodeSecp256k1Pubkey,
makeSignDoc as makeSignDocAmino,
StdFee,
} from "@cosmjs/amino";
import { fromBase64 } from "@cosmjs/encoding";
import { Int53, Uint53 } from "@cosmjs/math";
import {
Expand Down Expand Up @@ -346,7 +351,11 @@ export class SigningStargateClient extends StargateClient {
if (!accountFromSigner) {
throw new Error("Failed to retrieve account from signer");
}
const pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey));
const pubkey = encodePubkey(
chainId.startsWith("evmos_9001-")
? encodeEthSecp256k1Pubkey(accountFromSigner.pubkey)
: encodeSecp256k1Pubkey(accountFromSigner.pubkey),
);
const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON;
const msgs = messages.map((msg) => this.aminoTypes.toAmino(msg));
const signDoc = makeSignDocAmino(msgs, fee, chainId, memo, accountNumber, sequence);
Expand Down Expand Up @@ -391,7 +400,11 @@ export class SigningStargateClient extends StargateClient {
if (!accountFromSigner) {
throw new Error("Failed to retrieve account from signer");
}
const pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey));
const pubkey = encodePubkey(
chainId.startsWith("evmos_9001-")
? encodeEthSecp256k1Pubkey(accountFromSigner.pubkey)
: encodeSecp256k1Pubkey(accountFromSigner.pubkey),
);
const txBodyEncodeObject: TxBodyEncodeObject = {
typeUrl: "/cosmos.tx.v1beta1.TxBody",
value: {
Expand Down

0 comments on commit 5915b31

Please sign in to comment.