Skip to content

Commit

Permalink
Derive Cosmos addresses from public keys
Browse files Browse the repository at this point in the history
  • Loading branch information
joshklop committed Oct 27, 2024
1 parent 63bbf2c commit bcc0c62
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 0 deletions.
27 changes: 27 additions & 0 deletions monomer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package monomer

import (
"context"
"crypto/ecdsa"
"crypto/sha256"
"encoding/binary"
"errors"
Expand All @@ -13,14 +14,17 @@ import (

abcitypes "github.com/cometbft/cometbft/abci/types"
bfttypes "github.com/cometbft/cometbft/types"
"github.com/cosmos/cosmos-sdk/types/bech32"
opeth "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
"github.com/polymerdao/monomer/utils"
"golang.org/x/crypto/ripemd160" //nolint:staticcheck
)

type Application interface {
Expand Down Expand Up @@ -237,3 +241,26 @@ func NewChainConfig(chainID *big.Int) *params.ChainConfig {
CanyonTime: utils.Ptr(uint64(0)),
}
}

// CosmosETHAddress is a Cosmos address packed into an Ethereum address.
// Only addresses derived from secp256k1 keys can be packed into an Ethereum address.
// See [ADR-28] for more details.
//
//nolint:lll // [ADR-28]: https://github.com/cosmos/cosmos-sdk/blob/8bfcf554275c1efbb42666cc8510d2da139b67fa/docs/architecture/adr-028-public-key-addresses.md?plain=1#L85
type CosmosETHAddress common.Address

// PubkeyToCosmosETHAddress converts a secp256k1 public key to a CosmosETHAddress.
// Passing in a non-secp256k1 key results in undefined behavior.
func PubkeyToCosmosETHAddress(pubKey *ecdsa.PublicKey) CosmosETHAddress {
sha := sha256.Sum256(secp256k1.CompressPubkey(pubKey.X, pubKey.Y))
hasherRIPEMD160 := ripemd160.New()
if _, err := hasherRIPEMD160.Write(sha[:]); err != nil {
// hash.Hash never returns an error on Write. This panic should never execute.
panic(fmt.Errorf("ripemd160: %v", err))
}
return CosmosETHAddress(hasherRIPEMD160.Sum(nil))
}

func (a CosmosETHAddress) Encode(hrp string) (string, error) {
return bech32.ConvertAndEncode(hrp, common.Address(a).Bytes())
}
18 changes: 18 additions & 0 deletions monomer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"time"

bfttypes "github.com/cometbft/cometbft/types"
cosmossecp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
opeth "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -219,3 +222,18 @@ func TestPayloadAttributesValidForkchoiceUpdateResult(t *testing.T) {
PayloadID: payloadID,
}, result)
}

func TestCosmosETHAddress(t *testing.T) {
privKey, err := secp256k1.GeneratePrivateKey()
require.NoError(t, err)
pubKey := privKey.PubKey().ToECDSA()
got := monomer.PubkeyToCosmosETHAddress(pubKey)
wantBytes := (&cosmossecp256k1.PubKey{
Key: privKey.PubKey().SerializeCompressed(), // https://github.com/cosmos/cosmos-sdk/blob/346044afd0ecd4738c13993d2ac75da8e242266d/crypto/keys/secp256k1/secp256k1.go#L44-L45
}).Address().Bytes()
require.Equal(t, wantBytes, common.Address(got).Bytes())
// We have to use the `cosmos` hrp here because sdk.AccAddress.String() uses the global SDK config variable that uses the `cosmos` hrp.
gotEncoded, err := got.Encode("cosmos")
require.NoError(t, err)
require.Equal(t, sdk.AccAddress(wantBytes).String(), gotEncoded)
}

0 comments on commit bcc0c62

Please sign in to comment.