From ac9a721cc63095c94f3691e25ed31c9d1f84b402 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Mon, 30 Sep 2019 17:11:17 +0200 Subject: [PATCH] Format Ethereum addresses with EIP55 checksum --- Gopkg.lock | 2 +- bchain/coins/eth/erc20.go | 7 +++--- bchain/coins/eth/ethparser.go | 34 ++++++++++++++++++++++++++---- bchain/coins/eth/ethparser_test.go | 18 ++++++++-------- tests/rpc/rpc.go | 24 ++++++++++++++++----- 5 files changed, 62 insertions(+), 23 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 15c4e21b..f9b028a0 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -235,7 +235,7 @@ branch = "master" name = "golang.org/x/crypto" packages = ["ripemd160", "sha3"] - revision = "d6449816ce06963d9d136eee5a56fca5b0616e7e" + revision = "a832865fa7ada6126f4c6124ac49f71be71bff2a" [[projects]] branch = "master" diff --git a/bchain/coins/eth/erc20.go b/bchain/coins/eth/erc20.go index 8da5b818..135859e7 100644 --- a/bchain/coins/eth/erc20.go +++ b/bchain/coins/eth/erc20.go @@ -9,7 +9,6 @@ import ( "sync" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/golang/glog" "github.com/juju/errors" ) @@ -165,7 +164,7 @@ func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.Addre contract, found := cachedContracts[cds] cachedContractsMux.Unlock() if !found { - address := hexutil.Encode(contractDesc) + address := EIP55Address(contractDesc) data, err := b.ethCall(erc20NameSignature, address) if err != nil { return nil, err @@ -204,8 +203,8 @@ func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.Addre // EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) { - addr := hexutil.Encode(addrDesc) - contract := hexutil.Encode(contractDesc) + addr := EIP55Address(addrDesc) + contract := EIP55Address(contractDesc) req := erc20BalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:] data, err := b.ethCall(req, contract) if err != nil { diff --git a/bchain/coins/eth/ethparser.go b/bchain/coins/eth/ethparser.go index 1118dc93..f3790699 100644 --- a/bchain/coins/eth/ethparser.go +++ b/bchain/coins/eth/ethparser.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/golang/protobuf/proto" "github.com/juju/errors" + "golang.org/x/crypto/sha3" ) // EthereumTypeAddressDescriptorLen - in case of EthereumType, the AddressDescriptor has fixed length @@ -176,9 +177,34 @@ func (p *EthereumParser) GetAddrDescFromAddress(address string) (bchain.AddressD return hex.DecodeString(address) } +// EIP55Address returns an EIP55-compliant hex string representation of the address +func EIP55Address(addrDesc bchain.AddressDescriptor) string { + raw := hexutil.Encode(addrDesc) + if len(raw) != 42 { + return raw + } + sha := sha3.NewLegacyKeccak256() + result := []byte(raw) + sha.Write(result[2:]) + hash := sha.Sum(nil) + + for i := 2; i < len(result); i++ { + hashByte := hash[(i-2)>>1] + if i%2 == 0 { + hashByte = hashByte >> 4 + } else { + hashByte &= 0xf + } + if result[i] > '9' && hashByte > 7 { + result[i] -= 32 + } + } + return string(result) +} + // GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable func (p *EthereumParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) { - return []string{hexutil.Encode(addrDesc)}, true, nil + return []string{EIP55Address(addrDesc)}, true, nil } // GetScriptFromAddrDesc returns output script for given address descriptor @@ -308,7 +334,7 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { rt := rpcTransaction{ AccountNonce: hexutil.EncodeUint64(pt.Tx.AccountNonce), BlockNumber: hexutil.EncodeUint64(uint64(pt.BlockNumber)), - From: hexutil.Encode(pt.Tx.From), + From: EIP55Address(pt.Tx.From), GasLimit: hexutil.EncodeUint64(pt.Tx.GasLimit), Hash: hexutil.Encode(pt.Tx.Hash), Payload: hexutil.Encode(pt.Tx.Payload), @@ -316,7 +342,7 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { // R: hexEncodeBig(pt.R), // S: hexEncodeBig(pt.S), // V: hexEncodeBig(pt.V), - To: hexutil.Encode(pt.Tx.To), + To: EIP55Address(pt.Tx.To), TransactionIndex: hexutil.EncodeUint64(uint64(pt.Tx.TransactionIndex)), Value: hexEncodeBig(pt.Tx.Value), } @@ -329,7 +355,7 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { topics[j] = hexutil.Encode(t) } logs[i] = &rpcLog{ - Address: hexutil.Encode(l.Address), + Address: EIP55Address(l.Address), Data: hexutil.Encode(l.Data), Topics: topics, } diff --git a/bchain/coins/eth/ethparser_test.go b/bchain/coins/eth/ethparser_test.go index c23cd78b..e956a30b 100644 --- a/bchain/coins/eth/ethparser_test.go +++ b/bchain/coins/eth/ethparser_test.go @@ -77,14 +77,14 @@ func init() { Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b", Vin: []bchain.Vin{ { - Addresses: []string{"0x3e3a3d69dc66ba10737f531ed088954a9ec89d97"}, + Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"}, }, }, Vout: []bchain.Vout{ { ValueSat: *big.NewInt(1999622000000000000), ScriptPubKey: bchain.ScriptPubKey{ - Addresses: []string{"0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f"}, + Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"}, }, }, }, @@ -93,12 +93,12 @@ func init() { AccountNonce: "0xb26c", GasPrice: "0x430e23400", GasLimit: "0x5208", - To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f", + To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f", Value: "0x1bc0159d530e6000", Payload: "0x", Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b", BlockNumber: "0x41eee8", - From: "0x3e3a3d69dc66ba10737f531ed088954a9ec89d97", + From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97", TransactionIndex: "0xa", }, Receipt: &rpcReceipt{ @@ -115,14 +115,14 @@ func init() { Txid: "0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101", Vin: []bchain.Vin{ { - Addresses: []string{"0x20cd153de35d469ba46127a0c8f18626b59a256a"}, + Addresses: []string{"0x20cD153de35D469BA46127A0C8F18626b59a256A"}, }, }, Vout: []bchain.Vout{ { ValueSat: *big.NewInt(0), ScriptPubKey: bchain.ScriptPubKey{ - Addresses: []string{"0x4af4114f73d1c1c903ac9e0361b379d1291808a2"}, + Addresses: []string{"0x4af4114F73d1c1C903aC9E0361b379D1291808A2"}, }, }, }, @@ -131,19 +131,19 @@ func init() { AccountNonce: "0xd0", GasPrice: "0x9502f9000", GasLimit: "0x130d5", - To: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2", + To: "0x4af4114F73d1c1C903aC9E0361b379D1291808A2", Value: "0x0", Payload: "0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000", Hash: "0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101", BlockNumber: "0x41eee8", - From: "0x20cd153de35d469ba46127a0c8f18626b59a256a", + From: "0x20cD153de35D469BA46127A0C8F18626b59a256A", TransactionIndex: "0x0"}, Receipt: &rpcReceipt{ GasUsed: "0xcb39", Status: "0x1", Logs: []*rpcLog{ { - Address: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2", + Address: "0x4af4114F73d1c1C903aC9E0361b379D1291808A2", Data: "0x00000000000000000000000000000000000000000000021e19e0c9bab2400000", Topics: []string{ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", diff --git a/tests/rpc/rpc.go b/tests/rpc/rpc.go index 41652f0a..0ae25d16 100644 --- a/tests/rpc/rpc.go +++ b/tests/rpc/rpc.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "path/filepath" "reflect" + "strings" "testing" "time" @@ -177,8 +178,8 @@ func testGetTransaction(t *testing.T, h *TestHandler) { // CoinSpecificData are not specified in the fixtures got.CoinSpecificData = nil - normalizeEmptyAddresses(want) - normalizeEmptyAddresses(got) + normalizeAddresses(want, h.Chain.GetChainParser()) + normalizeAddresses(got, h.Chain.GetChainParser()) if !reflect.DeepEqual(got, want) { t.Errorf("GetTransaction() got %+#v, want %+#v", got, want) @@ -196,8 +197,8 @@ func testGetTransactionForMempool(t *testing.T, h *TestHandler) { t.Fatal(err) } - normalizeEmptyAddresses(want) - normalizeEmptyAddresses(got) + normalizeAddresses(want, h.Chain.GetChainParser()) + normalizeAddresses(got, h.Chain.GetChainParser()) // transactions parsed from JSON may contain additional data got.Confirmations, got.Blocktime, got.Time, got.CoinSpecificData = 0, 0, 0, nil @@ -208,15 +209,28 @@ func testGetTransactionForMempool(t *testing.T, h *TestHandler) { } // empty slice can be either []slice{} or nil; reflect.DeepEqual treats them as different value -func normalizeEmptyAddresses(tx *bchain.Tx) { +// remove checksums from ethereum addresses +func normalizeAddresses(tx *bchain.Tx, parser bchain.BlockChainParser) { for i := range tx.Vin { if len(tx.Vin[i].Addresses) == 0 { tx.Vin[i].Addresses = nil + } else { + if parser.GetChainType() == bchain.ChainEthereumType { + for j := range tx.Vin[i].Addresses { + tx.Vin[i].Addresses[j] = strings.ToLower(tx.Vin[i].Addresses[j]) + } + } } } for i := range tx.Vout { if len(tx.Vout[i].ScriptPubKey.Addresses) == 0 { tx.Vout[i].ScriptPubKey.Addresses = nil + } else { + if parser.GetChainType() == bchain.ChainEthereumType { + for j := range tx.Vout[i].ScriptPubKey.Addresses { + tx.Vout[i].ScriptPubKey.Addresses[j] = strings.ToLower(tx.Vout[i].ScriptPubKey.Addresses[j]) + } + } } } }