diff --git a/baked_in.go b/baked_in.go index c9b1db4..191cc54 100644 --- a/baked_in.go +++ b/baked_in.go @@ -140,6 +140,7 @@ var ( "isbn10": isISBN10, "isbn13": isISBN13, "eth_addr": isEthereumAddress, + "eth_addr_checksum": isEthereumAddressChecksum, "btc_addr": isBitcoinAddress, "btc_addr_bech32": isBitcoinBech32Address, "uuid": isUUID, @@ -613,14 +614,16 @@ func isISBN10(fl FieldLevel) bool { func isEthereumAddress(fl FieldLevel) bool { address := fl.Field().String() + return ethAddressRegex.MatchString(address) +} + +// isEthereumAddressChecksum is the validation function for validating if the field's value is a valid checksumed Ethereum address. +func isEthereumAddressChecksum(fl FieldLevel) bool { + address := fl.Field().String() + if !ethAddressRegex.MatchString(address) { return false } - - if ethAddressRegexUpper.MatchString(address) || ethAddressRegexLower.MatchString(address) { - return true - } - // Checksum validation. Reference: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md address = address[2:] // Skip "0x" prefix. h := sha3.NewLegacyKeccak256() diff --git a/regexes.go b/regexes.go index 9c1c634..fb2ea18 100644 --- a/regexes.go +++ b/regexes.go @@ -118,8 +118,6 @@ var ( btcUpperAddressRegexBech32 = regexp.MustCompile(btcAddressUpperRegexStringBech32) btcLowerAddressRegexBech32 = regexp.MustCompile(btcAddressLowerRegexStringBech32) ethAddressRegex = regexp.MustCompile(ethAddressRegexString) - ethAddressRegexUpper = regexp.MustCompile(ethAddressUpperRegexString) - ethAddressRegexLower = regexp.MustCompile(ethAddressLowerRegexString) uRLEncodedRegex = regexp.MustCompile(uRLEncodedRegexString) hTMLEncodedRegex = regexp.MustCompile(hTMLEncodedRegexString) hTMLRegex = regexp.MustCompile(hTMLRegexString) diff --git a/validator_test.go b/validator_test.go index 62be26a..33abcb0 100644 --- a/validator_test.go +++ b/validator_test.go @@ -5672,7 +5672,7 @@ func TestEthereumAddressValidation(t *testing.T) { {"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true}, {"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true}, {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true}, - {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDB", false}, // Invalid checksum. + {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDB", true}, // Invalid checksum, but valid address. // Other. {"", false}, @@ -5703,6 +5703,56 @@ func TestEthereumAddressValidation(t *testing.T) { } } +func TestEthereumAddressChecksumValidation(t *testing.T) { + validate := New() + + tests := []struct { + param string + expected bool + }{ + // All caps. + {"0x52908400098527886E0F7030069857D2E4169EE7", true}, + {"0x8617E340B3D01FA5F11F306F4090FD50E238070D", true}, + + // All lower. + {"0x27b1fdb04752bbc536007a920d24acb045561c26", true}, + {"0x123f681646d4a755815f9cb19e1acc8565a0c2ac", false}, + + // Mixed case: runs checksum validation. + {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true}, + {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDB", false}, // Invalid checksum. + {"0x000000000000000000000000000000000000dead", false}, // Invalid checksum. + {"0x000000000000000000000000000000000000dEaD", true}, // Valid checksum. + + // Other. + {"", false}, + {"D1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", false}, // Missing "0x" prefix. + {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDbc", false}, // More than 40 hex digits. + {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aD", false}, // Less than 40 hex digits. + {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDw", false}, // Invalid hex digit "w". + } + + for i, test := range tests { + + errs := validate.Var(test.param, "eth_addr_checksum") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d eth_addr_checksum failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d eth_addr_checksum failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "eth_addr_checksum" { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } + } + } + } +} + func TestBitcoinAddressValidation(t *testing.T) { validate := New()