package abispec import ( "fmt" "math/big" "regexp" "strings" "unicode" "github.com/ethereum/go-ethereum/common" "github.com/status-im/status-go/eth-node/crypto" ) var unicodeZeroPattern = regexp.MustCompile("^(?:\u0000)*") var hexZeroPattern = regexp.MustCompile("^(?:00)*") var hexStringPattern = regexp.MustCompile("(?i)^[0-9a-f]+$") var hexPrefixPattern = regexp.MustCompile("(?i)^0x") var addressBasicPattern = regexp.MustCompile("(?i)^(0x)?[0-9a-f]{40}$") var addressLowerCasePattern = regexp.MustCompile("^(0x|0X)?[0-9a-f]{40}$") var addressUpperCasePattern = regexp.MustCompile("^(0x|0X)?[0-9A-F]{40}$") func HexToNumber(hex string) string { num, success := big.NewInt(0).SetString(hex, 16) if success { return num.String() } return "" } func NumberToHex(numString string) string { num, success := big.NewInt(0).SetString(numString, 0) if success { return fmt.Sprintf("%x", num) } return "" } func Sha3(str string) string { bytes := crypto.Keccak256([]byte(str)) return common.Bytes2Hex(bytes) } func reverse(str string) string { bytes := []byte(str) var out []byte for i := len(bytes) - 1; i >= 0; i-- { out = append(out, bytes[i]) } return string(out) } // remove \u0000 padding from either side func removeUnicodeZeroPadding(str string) string { found := unicodeZeroPattern.FindString(str) str = strings.Replace(str, found, "", 1) str = reverse(str) found = unicodeZeroPattern.FindString(str) str = strings.Replace(str, found, "", 1) return reverse(str) } // remove 00 padding from either side func removeHexZeroPadding(str string) string { found := hexZeroPattern.FindString(str) str = strings.Replace(str, found, "", 1) str = reverse(str) found = hexZeroPattern.FindString(str) str = strings.Replace(str, found, "", 1) return reverse(str) } // implementation referenced from https://github.com/ChainSafe/web3.js/blob/edcd215bf657a4bba62fabaafd08e6e70040976e/packages/web3-utils/src/utils.js#L165 func Utf8ToHex(str string) (string, error) { str, err := Utf8encode(str) if err != nil { return "", err } str = removeUnicodeZeroPadding(str) var hex = "" for _, r := range str { n := fmt.Sprintf("%x", r) if len(n) < 2 { hex += "0" + n } else { hex += n } } return "0x" + hex, nil } // implementation referenced from https://github.com/ChainSafe/web3.js/blob/edcd215bf657a4bba62fabaafd08e6e70040976e/packages/web3-utils/src/utils.js#L193 func HexToUtf8(hexString string) (string, error) { hexString = removeHexPrefix(hexString) if !hexStringPattern.MatchString(hexString) { return "", fmt.Errorf("the parameter '%s' must be a valid HEX string", hexString) } if len(hexString)%2 != 0 { return "", fmt.Errorf("the parameter '%s' must have a even number of digits", hexString) } hexString = removeHexZeroPadding(hexString) n := big.NewInt(0) var bytes []byte for i := 0; i < len(hexString); i += 2 { hex := hexString[i : i+2] n, success := n.SetString(hex, 16) if !success { return "", fmt.Errorf("invalid hex value %s", hex) } r := rune(n.Int64()) bs := stringFromCharCode(r) bytes = appendBytes(bytes, bs) } bytes, err := Utf8decode(string(bytes)) if err != nil { return "", err } return string(bytes), nil } func removeHexPrefix(str string) string { found := hexPrefixPattern.FindString(str) return strings.Replace(str, found, "", 1) } // implementation referenced from https://github.com/ChainSafe/web3.js/blob/edcd215bf657a4bba62fabaafd08e6e70040976e/packages/web3-utils/src/utils.js#L107 func CheckAddressChecksum(address string) (bool, error) { address = removeHexPrefix(address) addressHash := Sha3(strings.ToLower(address)) n := big.NewInt(0) for i := 0; i < 40; i++ { // the nth letter should be uppercase if the nth digit of casemap is 1 n, success := n.SetString(addressHash[i:i+1], 16) if !success { return false, fmt.Errorf("failed to convert hex value '%s' to int", addressHash[i:i+1]) } v := n.Int64() if (v > 7 && uint8(unicode.ToUpper(rune(address[i]))) != address[i]) || (v <= 7 && uint8(unicode.ToLower(rune(address[i]))) != address[i]) { return false, nil } } return true, nil } // implementation referenced from https://github.com/ChainSafe/web3.js/blob/edcd215bf657a4bba62fabaafd08e6e70040976e/packages/web3-utils/src/utils.js#L85 func IsAddress(address string) (bool, error) { // check if it has the basic requirements of an address if !addressBasicPattern.MatchString(address) { return false, nil } else if addressLowerCasePattern.MatchString(address) || addressUpperCasePattern.MatchString(address) { return true, nil } else { return CheckAddressChecksum(address) } } // implementation referenced from https://github.com/ChainSafe/web3.js/blob/2022b17d52d31ce95559d18d5530d18c83eb4d1c/packages/web3-utils/src/index.js#L283 func ToChecksumAddress(address string) (string, error) { if strings.Trim(address, "") == "" { return "", nil } if !addressBasicPattern.MatchString(address) { return "", fmt.Errorf("given address '%s' is not a valid Ethereum address", address) } address = strings.ToLower(address) address = removeHexPrefix(address) addressHash := Sha3(address) var checksumAddress = []rune("0x") var n = big.NewInt(0) for i := 0; i < len(address); i++ { // If ith character is 9 to f then make it uppercase n, success := n.SetString(addressHash[i:i+1], 16) if !success { return "", fmt.Errorf("failed to convert hex value '%s' to int", addressHash[i:i+1]) } if n.Int64() > 7 { upper := unicode.ToUpper(rune(address[i])) checksumAddress = append(checksumAddress, upper) } else { checksumAddress = append(checksumAddress, rune(address[i])) } } return string(checksumAddress), nil }