From b9e93b69a797036757fd03f30028fa06e6270d4e Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Sun, 16 Aug 2015 17:22:53 +0200 Subject: [PATCH] crypto, common, xeth: implemented checksum addresses --- common/types.go | 27 ++++++++++++++++++++++++++ crypto/address.go | 44 ++++++++++++++++++++++++++++++++++++++++++ crypto/address_test.go | 33 +++++++++++++++++++++++++++++++ crypto/crypto.go | 13 ------------- jsre/ethereum_js.go | 17 +++++++++++++++- xeth/xeth.go | 23 +++++++++++++++++----- 6 files changed, 138 insertions(+), 19 deletions(-) create mode 100644 crypto/address.go create mode 100644 crypto/address_test.go diff --git a/common/types.go b/common/types.go index 624f4b8265..e0cc82defd 100644 --- a/common/types.go +++ b/common/types.go @@ -117,6 +117,33 @@ func (a *Address) Set(other Address) { } } +// ChecksumAddress is an address with an included checksum. A checksum address +// is usually made out of SHA3(A) + A +type ChecksumAddress struct { + Checksum [4]byte + Address +} + +// HexToChecksumAddress converts a hex checksum address to a ChecksumAddress +// type +func HexToChecksumAddress(s string) ChecksumAddress { + if len(s) > 2 && s[:2] == "0x" { + s = s[2:] + } + if len(s) != 48 { + return ChecksumAddress{} + } + + addr := ChecksumAddress{[4]byte{}, HexToAddress(s[4:])} + copy(addr.Checksum[:], Hex2Bytes(s[:8])) + return addr +} + +// Hex returns the hexadecimal representation of a checksum address +func (ca ChecksumAddress) Hex() string { + return "0x" + Bytes2Hex(ca.Checksum[:]) + ca.Address.Hex()[2:] +} + // PP Pretty Prints a byte slice in the following format: // hex(value[:4])...(hex[len(value)-4:]) func PP(value []byte) string { diff --git a/crypto/address.go b/crypto/address.go new file mode 100644 index 0000000000..57b18ff9b8 --- /dev/null +++ b/crypto/address.go @@ -0,0 +1,44 @@ +package crypto + +import ( + "bytes" + "crypto/ecdsa" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +const checksumLen = 4 + +// Creates an ethereum address given the bytes and the nonce +func CreateAddress(b common.Address, nonce uint64) common.Address { + data, _ := rlp.EncodeToBytes([]interface{}{b, nonce}) + return common.BytesToAddress(Sha3(data)[12:]) +} + +// ChecksumAddress creates modifies an address to include a checksum +func ChecksumAddress(addr common.Address) common.ChecksumAddress { + var checksumaddr common.ChecksumAddress + checksumaddr.Address = addr + copy(checksumaddr.Checksum[:], Sha3(addr.Bytes())[:4]) + + return checksumaddr +} + +// ValidateAddress validates the given address. It must include a checksum +// on the end. It returns the address or an error if the checksum or length of +// the input failed. +func ValidateAddress(addr common.ChecksumAddress) (common.Address, error) { + if !bytes.Equal(Sha3(addr.Address[:])[:4], addr.Checksum[:]) { + return common.Address{}, fmt.Errorf("checksum failed for address %x (%x)", addr.Address, addr.Checksum) + } + + return addr.Address, nil +} + +// PubkeyToAddress converts a std ecdsa public key to a common.Address +func PubkeyToAddress(p ecdsa.PublicKey) common.Address { + pubBytes := FromECDSAPub(&p) + return common.BytesToAddress(Sha3(pubBytes[1:])[12:]) +} diff --git a/crypto/address_test.go b/crypto/address_test.go new file mode 100644 index 0000000000..dfea776a76 --- /dev/null +++ b/crypto/address_test.go @@ -0,0 +1,33 @@ +package crypto + +import ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestChecksum(t *testing.T) { + var addr common.Address + addr[0] = 1 + + checksumed := ChecksumAddress(addr) + if !bytes.Equal(checksumed.Checksum[:], Sha3(checksumed.Address[:])[:4]) { + t.Error("checksum failed") + } + + addr, err := ValidateAddress(checksumed) + if err != nil { + t.Error(err) + } + + if addr != addr { + t.Error("address failed") + } + + checksumed.Checksum[3] |= 1 + _, err = ValidateAddress(checksumed) + if err == nil { + t.Error("checksum success, should have failed") + } +} diff --git a/crypto/crypto.go b/crypto/crypto.go index a474d6f13f..7cb2da845e 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -38,7 +38,6 @@ import ( "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/crypto/sha3" - "github.com/ethereum/go-ethereum/rlp" "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/ripemd160" ) @@ -68,13 +67,6 @@ func Sha3Hash(data ...[]byte) (h common.Hash) { return h } -// Creates an ethereum address given the bytes and the nonce -func CreateAddress(b common.Address, nonce uint64) common.Address { - data, _ := rlp.EncodeToBytes([]interface{}{b, nonce}) - return common.BytesToAddress(Sha3(data)[12:]) - //return Sha3(common.NewValue([]interface{}{b, nonce}).Encode())[12:] -} - func Sha256(data []byte) []byte { hash := sha256.Sum256(data) @@ -332,8 +324,3 @@ func PKCS7Unpad(in []byte) []byte { } return in[:len(in)-int(padding)] } - -func PubkeyToAddress(p ecdsa.PublicKey) common.Address { - pubBytes := FromECDSAPub(&p) - return common.BytesToAddress(Sha3(pubBytes[1:])[12:]) -} diff --git a/jsre/ethereum_js.go b/jsre/ethereum_js.go index f33bb7c25a..d3eee39364 100644 --- a/jsre/ethereum_js.go +++ b/jsre/ethereum_js.go @@ -2256,6 +2256,17 @@ var isAddress = function (address) { return /^(0x)?[0-9a-f]{40}$/.test(address); }; +/** + * Checks if the given string is an address + * + * @method isChecksumAddress + * @param {String} address the given HEX adress + * @return {Boolean} +*/ +var isChecksumAddress = function (address) { + return /^(0x)?[0-9a-f]{48}$/.test(address); +}; + /** * Transforms given string to valid 20 bytes-length addres with 0x prefix * @@ -2378,6 +2389,7 @@ module.exports = { isBigNumber: isBigNumber, isStrictAddress: isStrictAddress, isAddress: isAddress, + isChecksumAddress: isChecksumAddress, isFunction: isFunction, isString: isString, isObject: isObject, @@ -2513,6 +2525,7 @@ web3.toBigNumber = utils.toBigNumber; web3.toWei = utils.toWei; web3.fromWei = utils.fromWei; web3.isAddress = utils.isAddress; +web3.isChecksumAddress = utils.isChecksumAddress; web3.isIBAN = utils.isIBAN; web3.sha3 = sha3; web3.createBatch = function () { @@ -3735,8 +3748,10 @@ var inputAddressFormatter = function (address) { return address; } else if (utils.isAddress(address)) { return '0x' + address; + } else if (utils.isChecksumAddress(address)) { + return address; } - throw 'invalid address'; + throw 'invalid address' }; module.exports = { diff --git a/xeth/xeth.go b/xeth/xeth.go index 5a57608bc1..c180de262e 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -389,7 +389,7 @@ func (self *XEth) Accounts() []string { accounts, _ := self.backend.AccountManager().Accounts() accountAddresses := make([]string, len(accounts)) for i, ac := range accounts { - accountAddresses[i] = ac.Address.Hex() + accountAddresses[i] = crypto.ChecksumAddress(ac.Address).Hex() } return accountAddresses } @@ -890,7 +890,6 @@ func (self *XEth) Frontend() Frontend { } func (self *XEth) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { - // this minimalistic recoding is enough (works for natspec.js) var jsontx = fmt.Sprintf(`{"params":[{"to":"%s","data": "%s"}]}`, toStr, codeStr) if !self.ConfirmTransaction(jsontx) { @@ -898,13 +897,27 @@ func (self *XEth) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceS return "", err } - if len(toStr) > 0 && toStr != "0x" && !isAddress(toStr) { - return "", errors.New("Invalid address") + var ( + to common.Address + err error + ) + // If the address is larges than 20 bytes (+ 0x) assume a checksum + // address and verify. + if len(toStr) > 42 { + checkAddr := common.HexToChecksumAddress(toStr) + to, err = crypto.ValidateAddress(checkAddr) + if err != nil { + return "", err + } + } else { + if len(toStr) > 0 && toStr != "0x" && !isAddress(toStr) { + return "", errors.New("Invalid address") + } + to = common.HexToAddress(toStr) } var ( from = common.HexToAddress(fromStr) - to = common.HexToAddress(toStr) value = common.Big(valueStr) gas *big.Int price *big.Int