Skip to content

Commit

Permalink
eth2util: implement checksum address (#1694)
Browse files Browse the repository at this point in the history
Implements ethereum EIP155 address checksum as part of moving away from geth libraries. Also add an`PublicKeyToAddress` function.

category: refactor
ticket: #1626
  • Loading branch information
corverroos authored Jan 27, 2023
1 parent bfff703 commit f6f959d
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 0 deletions.
46 changes: 46 additions & 0 deletions eth2util/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@ package eth2util

import (
"context"
"encoding/hex"
"strings"
"unicode"

eth2client "github.com/attestantio/go-eth2-client"
eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
k1 "github.com/decred/dcrd/dcrec/secp256k1/v4"
"golang.org/x/crypto/sha3"

"github.com/obolnetwork/charon/app/errors"
"github.com/obolnetwork/charon/app/z"
)

// EpochFromSlot returns epoch calculated from given slot.
Expand All @@ -33,3 +39,43 @@ func EpochFromSlot(ctx context.Context, eth2Cl eth2client.SlotsPerEpochProvider,

return eth2p0.Epoch(uint64(slot) / slotsPerEpoch), nil
}

// ChecksumAddress returns an EIP55-compliant 0xhex representation of the 0xhex ethereum address.
func ChecksumAddress(address string) (string, error) {
if !strings.HasPrefix(address, "0x") || len(address) != 2+20*2 {
return "", errors.New("invalid ethereum address", z.Str("address", address))
}
b, err := hex.DecodeString(address[2:])
if err != nil {
return "", errors.New("invalid ethereum hex address", z.Str("address", address))
}

return checksumAddressBytes(b), nil
}

// ChecksumAddress returns an EIP55-compliant 0xhex representation of the 0xhex ethereum address.
func checksumAddressBytes(addressBytes []byte) string {
hexAddr := hex.EncodeToString(addressBytes)

h := sha3.NewLegacyKeccak256()
_, _ = h.Write([]byte(hexAddr))
hexHash := hex.EncodeToString(h.Sum(nil))

resp := []rune{'0', 'x'}
for i, c := range []rune(hexAddr) {
if c > '9' && hexHash[i] > '7' {
c = unicode.ToUpper(c)
}
resp = append(resp, c)
}

return string(resp)
}

// PublicKeyToAddress returns the EIP55-compliant 0xhex ethereum address of the public key.
func PublicKeyToAddress(pubkey *k1.PublicKey) string {
h := sha3.NewLegacyKeccak256()
_, _ = h.Write(pubkey.SerializeUncompressed()[1:])

return checksumAddressBytes(h.Sum(nil)[12:])
}
81 changes: 81 additions & 0 deletions eth2util/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright © 2022 Obol Labs Inc.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <http://www.gnu.org/licenses/>.

package eth2util_test

import (
"encoding/hex"
"strings"
"testing"

k1 "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/stretchr/testify/require"

"github.com/obolnetwork/charon/eth2util"
)

func TestChecksummedAddress(t *testing.T) {
// Test examples from https://eips.ethereum.org/EIPS/eip-55.
addrs := []string{
"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
}
for _, addr := range addrs {
t.Run(addr, func(t *testing.T) {
checksummed, err := eth2util.ChecksumAddress(addr)
require.NoError(t, err)
require.Equal(t, addr, checksummed)

checksummed, err = eth2util.ChecksumAddress(strings.ToLower(addr))
require.NoError(t, err)
require.Equal(t, addr, checksummed)

checksummed, err = eth2util.ChecksumAddress("0x" + strings.ToUpper(addr[2:]))
require.NoError(t, err)
require.Equal(t, addr, checksummed)
})
}
}

func TestInvalidAddrs(t *testing.T) {
addrs := []string{
"0x0000000000000000000000000000000000dead",
"0x00000000000000000000000000000000000000dead",
"0x0000000000000000000000000000000000000bar",
"000000000000000000000000000000000000dead",
}
for _, addr := range addrs {
t.Run(addr, func(t *testing.T) {
_, err := eth2util.ChecksumAddress(addr)
require.Error(t, err)
})
}
}

func TestPublicKeyToAddress(t *testing.T) {
// Test fixtures from geth/crypto package.
const testAddrHex = "0x970E8128AB834E8EAC17Ab8E3812F010678CF791"
const testPrivHex = "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"

b, err := hex.DecodeString(testPrivHex)
require.NoError(t, err)

privKey := k1.PrivKeyFromBytes(b)

actual := eth2util.PublicKeyToAddress(privKey.PubKey())
require.Equal(t, testAddrHex, actual)
}

0 comments on commit f6f959d

Please sign in to comment.