Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Support ethermint #1

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
7,506 changes: 3,927 additions & 3,579 deletions .pnp.cjs

Large diffs are not rendered by default.

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
12 changes: 10 additions & 2 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 { makeCosmoshubPath, makeEthermintPath } from "./paths";
export {
Ed25519Pubkey,
EthSecp256k1Pubkey,
isEd25519Pubkey,
isEthSecp256k1Pubkey,
isMultisigThresholdPubkey,
isSecp256k1Pubkey,
isSinglePubkey,
Expand All @@ -29,7 +32,12 @@ export {
} from "./pubkeys";
export { extractKdfConfiguration, Secp256k1HdWallet, Secp256k1HdWalletOptions } from "./secp256k1hdwallet";
export { Secp256k1Wallet } from "./secp256k1wallet";
export { decodeSignature, encodeSecp256k1Signature, StdSignature } from "./signature";
export {
decodeSignature,
encodeEthSecp256k1Signature,
encodeSecp256k1Signature,
StdSignature,
} from "./signature";
export { AminoMsg, makeSignDoc, serializeSignDoc, StdFee, StdSignDoc } from "./signdoc";
export { AccountData, Algo, AminoSignResponse, OfflineAminoSigner } from "./signer";
export { isStdTx, makeStdTx, StdTx } from "./stdtx";
Expand Down
23 changes: 22 additions & 1 deletion packages/amino/src/paths.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Slip10RawIndex } from "@cosmjs/crypto";

import { makeCosmoshubPath } from "./paths";
import { makeCosmoshubPath, makeEthermintPath } from "./paths";

describe("paths", () => {
describe("makeCosmoshubPath", () => {
Expand All @@ -23,4 +23,25 @@ describe("paths", () => {
]);
});
});

describe("makeEthermintPath", () => {
it("works", () => {
// m/44'/60'/0'/0/0
expect(makeEthermintPath(0)).toEqual([
Slip10RawIndex.hardened(44),
Slip10RawIndex.hardened(60),
Slip10RawIndex.hardened(0),
Slip10RawIndex.normal(0),
Slip10RawIndex.normal(0),
]);
// m/44'/60'/0'/0/123
expect(makeEthermintPath(123)).toEqual([
Slip10RawIndex.hardened(44),
Slip10RawIndex.hardened(60),
Slip10RawIndex.hardened(0),
Slip10RawIndex.normal(0),
Slip10RawIndex.normal(123),
]);
});
});
});
10 changes: 10 additions & 0 deletions packages/amino/src/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,13 @@ export function makeCosmoshubPath(a: number): HdPath {
Slip10RawIndex.normal(a),
];
}

export function makeEthermintPath(a: number): HdPath {
return [
Slip10RawIndex.hardened(44),
Slip10RawIndex.hardened(60),
Slip10RawIndex.hardened(0),
Slip10RawIndex.normal(0),
Slip10RawIndex.normal(a),
];
}
6 changes: 6 additions & 0 deletions packages/amino/src/pubkeys.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ describe("pubkeys", () => {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
};
const pubkeyEthSecp256k1 = {
type: "tendermint/PubKeyEthSecp256k1",
value: "Ay+1uNze+glFQM+T05EfzL8fQ1Y/wqO8K7q6tUM3BGin",
};
const pubkeyMultisigThreshold = {
type: "tendermint/PubKeyMultisigThreshold",
value: {
Expand Down Expand Up @@ -38,6 +42,7 @@ describe("pubkeys", () => {
it("works", () => {
expect(isSinglePubkey(pubkeyEd25519)).toEqual(true);
expect(isSinglePubkey(pubkeySecp256k1)).toEqual(true);
expect(isSinglePubkey(pubkeyEthSecp256k1)).toEqual(true);
expect(isSinglePubkey(pubkeyMultisigThreshold)).toEqual(false);
});
});
Expand All @@ -46,6 +51,7 @@ describe("pubkeys", () => {
it("works", () => {
expect(isMultisigThresholdPubkey(pubkeyEd25519)).toEqual(false);
expect(isMultisigThresholdPubkey(pubkeySecp256k1)).toEqual(false);
expect(isMultisigThresholdPubkey(pubkeyEthSecp256k1)).toEqual(false);
expect(isMultisigThresholdPubkey(pubkeyMultisigThreshold)).toEqual(true);
});
});
Expand Down
21 changes: 18 additions & 3 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 All @@ -53,7 +63,12 @@ export interface SinglePubkey extends Pubkey {
}

export function isSinglePubkey(pubkey: Pubkey): pubkey is SinglePubkey {
const singPubkeyTypes: string[] = [pubkeyType.ed25519, pubkeyType.secp256k1, pubkeyType.sr25519];
const singPubkeyTypes: string[] = [
pubkeyType.ed25519,
pubkeyType.secp256k1,
pubkeyType.sr25519,
pubkeyType.ethsecp256k1,
];
return singPubkeyTypes.includes(pubkey.type);
}

Expand Down
Loading