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/eth-pubkey #85

Merged
merged 4 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
10 changes: 10 additions & 0 deletions src/core/PublicKey.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
EthPublicKey,
LegacyAminoMultisigPublicKey,
SimplePublicKey,
ValConsPublicKey,
Expand Down Expand Up @@ -34,4 +35,13 @@ describe('PublicKey', () => {
'initvalcons1mlhj044zpxqdeaajfxpnav59rp4ap38tgp3hzm'
)
})

it('EthPubkey address', () => {
const pubkey = new EthPublicKey(
'Ahng0jM7JGSIWF38ey+qwH7T5EcUvzQqued27hn5kSgl'
)
expect(pubkey.address()).toEqual(
'init18cuwmw9f423hgfl9k8d6an8p6ffvvghvtmu6l7'
)
})
})
125 changes: 98 additions & 27 deletions src/core/PublicKey.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -13,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 */,
Expand All @@ -37,16 +35,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 {
Expand All @@ -57,6 +58,8 @@ export namespace PublicKey {
return LegacyAminoMultisigPublicKey.fromAmino(data)
case 'tendermint/PubKeyEd25519':
return ValConsPublicKey.fromAmino(data)
case 'initia/PubKeyEthSecp256k1':
return EthPublicKey.fromAmino(data)
}
}

Expand All @@ -68,6 +71,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)
}
}

Expand All @@ -79,6 +84,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`)
Expand Down Expand Up @@ -152,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 {
Expand Down Expand Up @@ -206,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 {
Expand Down Expand Up @@ -348,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)
Expand All @@ -363,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 {
Expand All @@ -385,3 +370,89 @@ 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 rawAddress(): Uint8Array {
const verified = secp256k1.publicKeyVerify(Buffer.from(this.key, 'base64'))
if (!verified) {
throw new Error('Invalid public key')
joon9823 marked this conversation as resolved.
Show resolved Hide resolved
}

// 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()))
}
}

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
}