From 00e126190fc5d8b19ac058a45d82aedccf9def81 Mon Sep 17 00:00:00 2001 From: joon9823 Date: Tue, 15 Oct 2024 13:37:15 +0900 Subject: [PATCH 1/4] Add EthPublicKey --- src/core/PublicKey.spec.ts | 10 ++++ src/core/PublicKey.ts | 107 ++++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/core/PublicKey.spec.ts b/src/core/PublicKey.spec.ts index 10fb54e..6db9f28 100644 --- a/src/core/PublicKey.spec.ts +++ b/src/core/PublicKey.spec.ts @@ -1,4 +1,5 @@ import { + EthPublicKey, LegacyAminoMultisigPublicKey, SimplePublicKey, ValConsPublicKey, @@ -34,4 +35,13 @@ describe('PublicKey', () => { 'initvalcons1mlhj044zpxqdeaajfxpnav59rp4ap38tgp3hzm' ) }) + + it('EthPubkey address', () => { + const pubkey = new EthPublicKey( + 'Ahng0jM7JGSIWF38ey+qwH7T5EcUvzQqued27hn5kSgl' + ) + expect(pubkey.address()).toEqual( + 'init18cuwmw9f423hgfl9k8d6an8p6ffvvghvtmu6l7' + ) + }) }) diff --git a/src/core/PublicKey.ts b/src/core/PublicKey.ts index 4133d39..b142870 100644 --- a/src/core/PublicKey.ts +++ b/src/core/PublicKey.ts @@ -1,9 +1,11 @@ +import * as secp256k1 from 'secp256k1' import { JSONSerializable } from '../util/json' -import { sha256, ripemd160 } from '../util/hash' +import { sha256, ripemd160, keccak256 } from '../util/hash' import { LegacyAminoPubKey as LegacyAminoPubKey_pb } from '@initia/initia.proto/cosmos/crypto/multisig/keys' import { Any } from '@initia/initia.proto/google/protobuf/any' import { PubKey as PubKey_pb } from '@initia/initia.proto/cosmos/crypto/secp256k1/keys' import { PubKey as ValConsPubKey_pb } from '@initia/initia.proto/cosmos/crypto/ed25519/keys' +import { PubKey as EthPubKey_pb } from '@initia/initia.proto/initia/crypto/v1beta1/ethsecp256k1/keys' import { bech32 } from 'bech32' // As discussed in https://github.com/binance-chain/javascript-sdk/issues/163 @@ -37,16 +39,19 @@ export type PublicKey = | SimplePublicKey | LegacyAminoMultisigPublicKey | ValConsPublicKey + | EthPublicKey export namespace PublicKey { export type Amino = | SimplePublicKey.Amino | LegacyAminoMultisigPublicKey.Amino | ValConsPublicKey.Amino + | EthPublicKey.Amino export type Data = | SimplePublicKey.Data | LegacyAminoMultisigPublicKey.Data | ValConsPublicKey.Data + | EthPublicKey.Data export type Proto = Any export function fromAmino(data: PublicKey.Amino): PublicKey { @@ -57,6 +62,8 @@ export namespace PublicKey { return LegacyAminoMultisigPublicKey.fromAmino(data) case 'tendermint/PubKeyEd25519': return ValConsPublicKey.fromAmino(data) + case 'initia/PubKeyEthSecp256k1': + return EthPublicKey.fromAmino(data) } } @@ -68,6 +75,8 @@ export namespace PublicKey { return LegacyAminoMultisigPublicKey.fromData(data) case '/cosmos.crypto.ed25519.PubKey': return ValConsPublicKey.fromData(data) + case '/initia.crypto.v1beta1.ethsecp256k1.PubKey': + return EthPublicKey.fromData(data) } } @@ -79,6 +88,8 @@ export namespace PublicKey { return LegacyAminoMultisigPublicKey.unpackAny(pubkeyAny) } else if (typeUrl === '/cosmos.crypto.ed25519.PubKey') { return ValConsPublicKey.unpackAny(pubkeyAny) + } else if (typeUrl === '/initia.crypto.v1beta1.ethsecp256k1.PubKey') { + return EthPublicKey.unpackAny(pubkeyAny) } throw new Error(`Pubkey type ${typeUrl} not recognized`) @@ -385,3 +396,97 @@ export namespace ValConsPublicKey { export type Proto = ValConsPubKey_pb } + +export class EthPublicKey extends JSONSerializable< + EthPublicKey.Amino, + EthPublicKey.Data, + EthPublicKey.Proto +> { + constructor(public key: string) { + super() + } + + public static fromAmino(data: EthPublicKey.Amino): EthPublicKey { + return new EthPublicKey(data.value) + } + + public toAmino(): EthPublicKey.Amino { + return { + type: 'initia/PubKeyEthSecp256k1', + value: this.key, + } + } + + public static fromData(data: EthPublicKey.Data): EthPublicKey { + return new EthPublicKey(data.key) + } + + public toData(): EthPublicKey.Data { + return { + '@type': '/initia.crypto.v1beta1.ethsecp256k1.PubKey', + key: this.key, + } + } + + public static fromProto(pubkeyProto: EthPublicKey.Proto): EthPublicKey { + return new EthPublicKey(Buffer.from(pubkeyProto.key).toString('base64')) + } + + public toProto(): EthPublicKey.Proto { + return EthPubKey_pb.fromPartial({ + key: Buffer.from(this.key, 'base64'), + }) + } + + public packAny(): Any { + return Any.fromPartial({ + typeUrl: '/initia.crypto.v1beta1.ethsecp256k1.PubKey', + value: EthPubKey_pb.encode(this.toProto()).finish(), + }) + } + + public static unpackAny(pubkeyAny: Any): EthPublicKey { + return EthPublicKey.fromProto(EthPubKey_pb.decode(pubkeyAny.value)) + } + + // public encodeAminoPubkey(): Uint8Array { + // return Buffer.concat([ + // pubkeyAminoPrefixSecp256k1, + // Buffer.from(this.key, 'base64'), + // ]) + // } + + public rawAddress(): Uint8Array { + const pubKey = secp256k1.publicKeyVerify(Buffer.from(this.key, 'base64')); + if (!pubKey) { + throw new Error('Invalid public key'); + } + + // Serialize the public key in uncompressed format (65 bytes) + const pubBytes = secp256k1.publicKeyConvert(Buffer.from(this.key, 'base64'), false); + + return keccak256(pubBytes.slice(1)).slice(12); + } + + public address(): string { + return bech32.encode('init', bech32.toWords(this.rawAddress())) + } + + // public pubkeyAddress(): string { + // return bech32.encode('initpub', bech32.toWords(this.encodeAminoPubkey())) + // } +} + +export namespace EthPublicKey { + export interface Amino { + type: 'initia/PubKeyEthSecp256k1' + value: string + } + + export interface Data { + '@type': '/initia.crypto.v1beta1.ethsecp256k1.PubKey' + key: string + } + + export type Proto = EthPubKey_pb +} From c14d12b15f6d7417edd69bccff985b7074612db3 Mon Sep 17 00:00:00 2001 From: joon9823 Date: Tue, 15 Oct 2024 13:37:51 +0900 Subject: [PATCH 2/4] Apply lint --- src/core/PublicKey.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/core/PublicKey.ts b/src/core/PublicKey.ts index b142870..d1f291e 100644 --- a/src/core/PublicKey.ts +++ b/src/core/PublicKey.ts @@ -457,15 +457,18 @@ export class EthPublicKey extends JSONSerializable< // } public rawAddress(): Uint8Array { - const pubKey = secp256k1.publicKeyVerify(Buffer.from(this.key, 'base64')); + const pubKey = secp256k1.publicKeyVerify(Buffer.from(this.key, 'base64')) if (!pubKey) { - throw new Error('Invalid public key'); + throw new Error('Invalid public key') } // Serialize the public key in uncompressed format (65 bytes) - const pubBytes = secp256k1.publicKeyConvert(Buffer.from(this.key, 'base64'), false); + const pubBytes = secp256k1.publicKeyConvert( + Buffer.from(this.key, 'base64'), + false + ) - return keccak256(pubBytes.slice(1)).slice(12); + return keccak256(pubBytes.slice(1)).slice(12) } public address(): string { From c6b49eb0f5655a392383bdfed5ccf7edc5a128a4 Mon Sep 17 00:00:00 2001 From: joon9823 Date: Tue, 15 Oct 2024 14:09:21 +0900 Subject: [PATCH 3/4] Remove unusd methods --- src/core/PublicKey.ts | 41 ++--------------------------------------- 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/src/core/PublicKey.ts b/src/core/PublicKey.ts index d1f291e..ac917ce 100644 --- a/src/core/PublicKey.ts +++ b/src/core/PublicKey.ts @@ -15,10 +15,6 @@ const pubkeyAminoPrefixSecp256k1 = Buffer.from( 'eb5ae987' + '21' /* fixed length */, 'hex' ) -const pubkeyAminoPrefixEd25519 = Buffer.from( - '1624de64' + '20' /* fixed length */, - 'hex' -) /** See https://github.com/tendermint/tendermint/commit/38b401657e4ad7a7eeb3c30a3cbf512037df3740 */ const pubkeyAminoPrefixMultisigThreshold = Buffer.from( '22c1f7e2' /* variable length not included */, @@ -163,10 +159,6 @@ export class SimplePublicKey extends JSONSerializable< public address(): string { return bech32.encode('init', bech32.toWords(this.rawAddress())) } - - public pubkeyAddress(): string { - return bech32.encode('initpub', bech32.toWords(this.encodeAminoPubkey())) - } } export namespace SimplePublicKey { @@ -217,10 +209,6 @@ export class LegacyAminoMultisigPublicKey extends JSONSerializable< return bech32.encode('init', bech32.toWords(this.rawAddress())) } - public pubkeyAddress(): string { - return bech32.encode('initpub', bech32.toWords(this.encodeAminoPubkey())) - } - public static fromAmino( data: LegacyAminoMultisigPublicKey.Amino ): LegacyAminoMultisigPublicKey { @@ -359,13 +347,6 @@ export class ValConsPublicKey extends JSONSerializable< return ValConsPublicKey.fromProto(ValConsPubKey_pb.decode(pubkeyAny.value)) } - public encodeAminoPubkey(): Uint8Array { - return Buffer.concat([ - pubkeyAminoPrefixEd25519, - Buffer.from(this.key, 'base64'), - ]) - } - public rawAddress(): Uint8Array { const pubkeyData = Buffer.from(this.key, 'base64') return sha256(pubkeyData).slice(0, 20) @@ -374,13 +355,6 @@ export class ValConsPublicKey extends JSONSerializable< public address(): string { return bech32.encode('initvalcons', bech32.toWords(this.rawAddress())) } - - public pubkeyAddress(): string { - return bech32.encode( - 'initvalconspub', - bech32.toWords(this.encodeAminoPubkey()) - ) - } } export namespace ValConsPublicKey { @@ -449,16 +423,9 @@ export class EthPublicKey extends JSONSerializable< return EthPublicKey.fromProto(EthPubKey_pb.decode(pubkeyAny.value)) } - // public encodeAminoPubkey(): Uint8Array { - // return Buffer.concat([ - // pubkeyAminoPrefixSecp256k1, - // Buffer.from(this.key, 'base64'), - // ]) - // } - public rawAddress(): Uint8Array { - const pubKey = secp256k1.publicKeyVerify(Buffer.from(this.key, 'base64')) - if (!pubKey) { + const verified = secp256k1.publicKeyVerify(Buffer.from(this.key, 'base64')) + if (!verified) { throw new Error('Invalid public key') } @@ -474,10 +441,6 @@ export class EthPublicKey extends JSONSerializable< public address(): string { return bech32.encode('init', bech32.toWords(this.rawAddress())) } - - // public pubkeyAddress(): string { - // return bech32.encode('initpub', bech32.toWords(this.encodeAminoPubkey())) - // } } export namespace EthPublicKey { From 823ded097dd76c6a2446af903885d976e12b5f09 Mon Sep 17 00:00:00 2001 From: joon9823 Date: Tue, 15 Oct 2024 14:11:31 +0900 Subject: [PATCH 4/4] Bump version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bdd3222..025599b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@initia/initia.js", - "version": "0.2.17", + "version": "0.2.18", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@initia/initia.js", - "version": "0.2.17", + "version": "0.2.18", "license": "Apache-2.0", "dependencies": { "@initia/initia.proto": "^0.2.3", diff --git a/package.json b/package.json index 8e06a8c..7762f09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@initia/initia.js", - "version": "0.2.17", + "version": "0.2.18", "description": "The JavaScript SDK for Initia", "license": "Apache-2.0", "author": "Initia Foundation",