Skip to content

Commit b7bbbbd

Browse files
feat: add support for EthSecp256k1 public keys and signatures
- Implemented encoding for EthSecp256k1 public keys in amino format. - Added functions to encode and decode EthSecp256k1 signatures. - Updated pubkey type definitions to include EthSecp256k1. - Enhanced signing clients to handle EthSecp256k1 accounts. - Created DirectEthSecp256k1HdWallet for managing EthSecp256k1 keypairs. - Added tests for DirectEthSecp256k1HdWallet and DirectEthSecp256k1Wallet functionalities.
1 parent f05c745 commit b7bbbbd

File tree

13 files changed

+838
-9
lines changed

13 files changed

+838
-9
lines changed

packages/amino/src/addresses.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import { ripemd160, sha256 } from "@cosmjs/crypto";
44
import { fromBase64, toBech32 } from "@cosmjs/encoding";
5+
import { keccak256 } from "@cosmjs/crypto";
6+
import { Secp256k1 } from "@cosmjs/crypto";
57

68
import { encodeAminoPubkey } from "./encoding";
79
import { isEd25519Pubkey, isMultisigThresholdPubkey, isSecp256k1Pubkey, Pubkey } from "./pubkeys";
@@ -20,6 +22,14 @@ export function rawSecp256k1PubkeyToRawAddress(pubkeyData: Uint8Array): Uint8Arr
2022
return ripemd160(sha256(pubkeyData));
2123
}
2224

25+
export function rawEthSecp256k1PubkeyToRawAddress(pubkeyData: Uint8Array): Uint8Array {
26+
const uncompressed = Secp256k1.uncompressPubkey(pubkeyData);
27+
const pubkeyWithoutPrefix = uncompressed.slice(1);
28+
const hash = keccak256(pubkeyWithoutPrefix);
29+
return hash.slice(-20);
30+
}
31+
32+
2333
// For secp256k1 this assumes we already have a compressed pubkey.
2434
export function pubkeyToRawAddress(pubkey: Pubkey): Uint8Array {
2535
if (isSecp256k1Pubkey(pubkey)) {

packages/amino/src/encoding.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { arrayContentStartsWith } from "@cosmjs/utils";
44

55
import {
66
Ed25519Pubkey,
7+
EthSecp256k1Pubkey,
78
isEd25519Pubkey,
89
isMultisigThresholdPubkey,
910
isSecp256k1Pubkey,
@@ -41,6 +42,20 @@ export function encodeEd25519Pubkey(pubkey: Uint8Array): Ed25519Pubkey {
4142
};
4243
}
4344

45+
/**
46+
* Takes a EthSecp256k1 public key as raw bytes and returns the Amino JSON
47+
* representation of it (the type/value wrapper object).
48+
*/
49+
export function encodeEthSecp256k1Pubkey(pubkey: Uint8Array): EthSecp256k1Pubkey {
50+
if (pubkey.length !== 33 || (pubkey[0] !== 0x02 && pubkey[0] !== 0x03)) {
51+
throw new Error("Public key must be compressed secp256k1, i.e. 33 bytes starting with 0x02 or 0x03");
52+
}
53+
return {
54+
type: pubkeyType.ethsecp256k1,
55+
value: toBase64(pubkey),
56+
};
57+
}
58+
4459
// As discussed in https://github.com/binance-chain/javascript-sdk/issues/163
4560
// Prefixes listed here: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/docs/spec/blockchain/encoding.md#public-key-cryptography
4661
// Last bytes is varint-encoded length prefix

packages/amino/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export {
33
pubkeyToRawAddress,
44
rawEd25519PubkeyToRawAddress,
55
rawSecp256k1PubkeyToRawAddress,
6+
rawEthSecp256k1PubkeyToRawAddress,
67
} from "./addresses";
78
export { addCoins, Coin, coin, coins, parseCoins } from "./coins";
89
export {
@@ -12,6 +13,7 @@ export {
1213
encodeBech32Pubkey,
1314
encodeEd25519Pubkey,
1415
encodeSecp256k1Pubkey,
16+
encodeEthSecp256k1Pubkey,
1517
} from "./encoding";
1618
export { createMultisigThresholdPubkey } from "./multisig";
1719
export { omitDefault } from "./omitdefault";
@@ -21,16 +23,18 @@ export {
2123
isEd25519Pubkey,
2224
isMultisigThresholdPubkey,
2325
isSecp256k1Pubkey,
26+
isEthSecp256k1Pubkey,
2427
isSinglePubkey,
2528
MultisigThresholdPubkey,
2629
Pubkey,
2730
pubkeyType,
2831
Secp256k1Pubkey,
32+
EthSecp256k1Pubkey,
2933
SinglePubkey,
3034
} from "./pubkeys";
3135
export { extractKdfConfiguration, Secp256k1HdWallet, Secp256k1HdWalletOptions } from "./secp256k1hdwallet";
3236
export { Secp256k1Wallet } from "./secp256k1wallet";
33-
export { decodeSignature, encodeSecp256k1Signature, StdSignature } from "./signature";
37+
export { decodeSignature, encodeEthSecp256k1Signature, encodeSecp256k1Signature, StdSignature } from "./signature";
3438
export { AminoMsg, makeSignDoc, serializeSignDoc, StdFee, StdSignDoc } from "./signdoc";
3539
export { AccountData, Algo, AminoSignResponse, OfflineAminoSigner } from "./signer";
3640
export { isStdTx, makeStdTx, StdTx } from "./stdtx";

packages/amino/src/pubkeys.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,21 @@ export function isSecp256k1Pubkey(pubkey: Pubkey): pubkey is Secp256k1Pubkey {
2424
return (pubkey as Secp256k1Pubkey).type === "tendermint/PubKeySecp256k1";
2525
}
2626

27+
export interface EthSecp256k1Pubkey extends SinglePubkey {
28+
readonly type: "os/PubKeyEthSecp256k1";
29+
readonly value: string;
30+
}
31+
32+
export function isEthSecp256k1Pubkey(pubkey: Pubkey): pubkey is EthSecp256k1Pubkey {
33+
return (pubkey as EthSecp256k1Pubkey).type === "os/PubKeyEthSecp256k1";
34+
}
35+
2736
export const pubkeyType = {
28-
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */
29-
secp256k1: "tendermint/PubKeySecp256k1" as const,
3037
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23 */
38+
secp256k1: "tendermint/PubKeySecp256k1" as const,
39+
/** @see https://github.com/cosmos/evm/blob/main/crypto/ethsecp256k1/ethsecp256k1.go#L36 */
40+
ethsecp256k1: "os/PubKeyEthSecp256k1" as const,
41+
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */
3142
ed25519: "tendermint/PubKeyEd25519" as const,
3243
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */
3344
sr25519: "tendermint/PubKeySr25519" as const,

packages/amino/src/signature.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable @typescript-eslint/naming-convention */
22
import { fromBase64, toBase64 } from "@cosmjs/encoding";
33

4-
import { encodeSecp256k1Pubkey } from "./encoding";
4+
import { encodeEthSecp256k1Pubkey, encodeSecp256k1Pubkey } from "./encoding";
55
import { Pubkey, pubkeyType } from "./pubkeys";
66

77
export interface StdSignature {
@@ -28,6 +28,25 @@ export function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Arr
2828
};
2929
}
3030

31+
/**
32+
* Takes a binary pubkey and signature to create a signature object
33+
*
34+
* @param pubkey a compressed secp256k1 public key
35+
* @param signature a 64 byte fixed length representation of secp256k1 signature components r and s
36+
*/
37+
export function encodeEthSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature {
38+
if (signature.length !== 64) {
39+
throw new Error(
40+
"Signature must be 64 bytes long. Cosmos SDK uses a 2x32 byte fixed length encoding for the secp256k1 signature integers r and s.",
41+
);
42+
}
43+
44+
return {
45+
pub_key: encodeEthSecp256k1Pubkey(pubkey),
46+
signature: toBase64(signature),
47+
};
48+
}
49+
3150
export function decodeSignature(signature: StdSignature): {
3251
readonly pubkey: Uint8Array;
3352
readonly signature: Uint8Array;
@@ -39,6 +58,11 @@ export function decodeSignature(signature: StdSignature): {
3958
pubkey: fromBase64(signature.pub_key.value),
4059
signature: fromBase64(signature.signature),
4160
};
61+
case pubkeyType.ethsecp256k1:
62+
return {
63+
pubkey: fromBase64(signature.pub_key.value),
64+
signature: fromBase64(signature.signature),
65+
};
4266
default:
4367
throw new Error("Unsupported pubkey type");
4468
}

packages/cosmwasm-stargate/src/signingcosmwasmclient.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable @typescript-eslint/naming-convention */
2-
import { encodeSecp256k1Pubkey, makeSignDoc as makeSignDocAmino } from "@cosmjs/amino";
2+
import { encodeEthSecp256k1Pubkey, encodeSecp256k1Pubkey, makeSignDoc as makeSignDocAmino } from "@cosmjs/amino";
33
import { sha256 } from "@cosmjs/crypto";
44
import { fromBase64, toHex, toUtf8 } from "@cosmjs/encoding";
55
import { Int53, Uint53 } from "@cosmjs/math";
@@ -750,7 +750,12 @@ export class SigningCosmWasmClient extends CosmWasmClient {
750750
if (!accountFromSigner) {
751751
throw new Error("Failed to retrieve account from signer");
752752
}
753-
const pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey));
753+
let pubkey;
754+
if (accountFromSigner.algo == "eth_secp256k1") {
755+
pubkey = encodePubkey(encodeEthSecp256k1Pubkey(accountFromSigner.pubkey));
756+
} else {
757+
pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey));
758+
}
754759
const txBody: TxBodyEncodeObject = {
755760
typeUrl: "/cosmos.tx.v1beta1.TxBody",
756761
value: {

0 commit comments

Comments
 (0)