From ba5836f763296c1b4fb7c75e72d42501981e481c Mon Sep 17 00:00:00 2001 From: josh Date: Fri, 6 Apr 2018 19:58:09 -0700 Subject: [PATCH 1/7] add bitcoin and ethereum address regexes --- regexes.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/regexes.go b/regexes.go index 78f3ea0..82f7ec2 100644 --- a/regexes.go +++ b/regexes.go @@ -32,6 +32,9 @@ const ( sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // https://tools.ietf.org/html/rfc952 hostnameRegexStringRFC1123 = `^[a-zA-Z0-9][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 + btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address + btcAddressRegexStringBech32 = `^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` ) var ( @@ -64,4 +67,7 @@ var ( sSNRegex = regexp.MustCompile(sSNRegexString) hostnameRegexRFC952 = regexp.MustCompile(hostnameRegexStringRFC952) hostnameRegexRFC1123 = regexp.MustCompile(hostnameRegexStringRFC1123) + btcAddressRegex = regexp.MustCompile(btcAddressRegexString) + btcAddressRegexBech32 = regexp.MustCompile(btcAddressRegexStringBech32) + ethAddressRegex = regexp.MustCompile(ethAddressRegexString) ) From 36d83b0b83a881aa09b2d4e5d6a6493f435b4c82 Mon Sep 17 00:00:00 2001 From: josh Date: Sun, 8 Apr 2018 19:38:12 -0700 Subject: [PATCH 2/7] Added in validation function, tests, and docs --- baked_in.go | 16 ++++++++++ doc.go | 16 +++++++++- validator_test.go | 79 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/baked_in.go b/baked_in.go index 231b78e..df790d8 100644 --- a/baked_in.go +++ b/baked_in.go @@ -105,6 +105,8 @@ var ( "isbn": isISBN, "isbn10": isISBN10, "isbn13": isISBN13, + "eth_addr": isEthereumAddress, + "btc_addr": isBitcoinAddress, "uuid": isUUID, "uuid3": isUUID3, "uuid4": isUUID4, @@ -387,6 +389,20 @@ func isISBN10(fl FieldLevel) bool { return checksum%11 == 0 } +// IsEthereumAddress is the validation function for validating if the field's value is a valid ethereum address based currently only on the format +func isEthereumAddress(fl FieldLevel) bool { + field := fl.Field() + + return ethAddressRegex.MatchString(field.String()) +} + +// IsBitcoinAddress is the validation function for validating if the field's value is a valid btc address, currently only based on the format +func isBitcoinAddress(fl FieldLevel) bool { + field := fl.Field() + + return btcAddressRegex.MatchString(field.String()) || btcAddressRegexBech32.MatchString(field.String()) +} + // ExcludesRune is the validation function for validating that the field's value does not contain the rune specified within the param. func excludesRune(fl FieldLevel) bool { return !containsRune(fl) diff --git a/doc.go b/doc.go index f7efe23..5522ebb 100644 --- a/doc.go +++ b/doc.go @@ -609,6 +609,21 @@ this with the omitempty tag. Usage: base64 +Bitcoin Address + +This validates that a string value contains a valid bitcoin address. +The format of the string is checked to ensure it matches one of the three formats +P2PKH, P2SH, or Bech32. Currently no further validation is performed. + + Usage: btc_addr + +Ethereum Address + +This validates that a string value contains a valid ethereum address. +The format of the string is checked to ensure it matches the standard Ethereum address format + + Usage: eth_addr + Contains This validates that a string value contains the substring value. @@ -665,7 +680,6 @@ This validates that a string value contains a valid isbn13 value. Usage: isbn13 - Universally Unique Identifier UUID This validates that a string value contains a valid UUID. diff --git a/validator_test.go b/validator_test.go index 9600d0f..d8be987 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4398,6 +4398,85 @@ func TestBase64Validation(t *testing.T) { AssertError(t, errs, "", "", "", "", "base64") } +func TestEthereumAddressValidation(t *testing.T){ + + validate := New() + + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"0x02F9AE5f22EA3fA88F05780B30385bEC", false}, + {"123f681646d4a755815f9cb19e1acc8565a0c2ac", false}, + {"0x02F9AE5f22EA3fA88F05780B30385bECFacbf130", true}, + {"0x123f681646d4a755815f9cb19e1acc8565a0c2ac", true}, + } + + + + for i, test := range tests { + + errs := validate.Var(test.param, "eth_addr") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d eth_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d eth_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "eth_addr" { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } + } + } + } +} + +func TestBitcoinAddressValidation(t *testing.T){ + + validate := New() + + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"0x02F9AE5f22EA3fA88F05780B30385bEC", false}, + {"123f681646d4a755815f9cb19e1acc8565a0c2ac", false}, + {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, + {"3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy", true}, + {"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", true}, + } + + + + for i, test := range tests { + + errs := validate.Var(test.param, "btc_addr") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d btc_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d btc_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "btc_addr" { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } + } + } + } +} + + + func TestNoStructLevelValidation(t *testing.T) { type Inner struct { From 60417282a404387d562239f17211e67d7f66c17d Mon Sep 17 00:00:00 2001 From: josh Date: Sun, 8 Apr 2018 19:38:12 -0700 Subject: [PATCH 3/7] verifies p2pkh addresses --- baked_in.go | 48 +++++++++++++++++++++++++++++++++++++++++++++-- validator_test.go | 15 ++++++++------- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/baked_in.go b/baked_in.go index df790d8..55bd4c4 100644 --- a/baked_in.go +++ b/baked_in.go @@ -11,6 +11,8 @@ import ( "sync" "time" "unicode/utf8" + "crypto/sha256" + "bytes" ) // Func accepts a FieldLevel interface for all validation needs. The return @@ -398,9 +400,51 @@ func isEthereumAddress(fl FieldLevel) bool { // IsBitcoinAddress is the validation function for validating if the field's value is a valid btc address, currently only based on the format func isBitcoinAddress(fl FieldLevel) bool { - field := fl.Field() + address := fl.Field().String() + + if btcAddressRegex.MatchString(address) { + + alphabet := []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") + + decode := [25]byte{} + + for _, n := range []byte(address) { + d := bytes.IndexByte(alphabet, n) + + if(d == -1){ + return false + } + + for i := 24; i >= 0; i-- { + d += 58 * int(decode[i]) + decode[i] = byte(d % 256) + d /= 256 + } + } + + + if decode[0] != 0 { + return false + } + + h := sha256.New() + h.Write(decode[:21]) + d := h.Sum([]byte{}) + h = sha256.New() + h.Write(d) + + validchecksum := [4]byte{} + computedchecksum := [4]byte{} + + copy(computedchecksum[:], h.Sum(d[:0])) + copy(validchecksum[:], decode[21:]) + + println(address, "::", validchecksum[:], computedchecksum[:]) + + return validchecksum == computedchecksum + } - return btcAddressRegex.MatchString(field.String()) || btcAddressRegexBech32.MatchString(field.String()) + return btcAddressRegexBech32.MatchString(address) } // ExcludesRune is the validation function for validating that the field's value does not contain the rune specified within the param. diff --git a/validator_test.go b/validator_test.go index d8be987..c16d426 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4446,10 +4446,11 @@ func TestBitcoinAddressValidation(t *testing.T){ }{ {"", false}, {"0x02F9AE5f22EA3fA88F05780B30385bEC", false}, - {"123f681646d4a755815f9cb19e1acc8565a0c2ac", false}, - {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, - {"3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy", true}, - {"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", true}, + {"1A1zP1ePQGefi2DMPTifTL5SLmv7DivfNa", false}, + {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, // valid p2pkh address + {"3P14159f73E4gFr7JterCCQh9QjiTjiZrG", true}, // valid p2sh address + {"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", true}, // valid bech32 address + } @@ -4460,15 +4461,15 @@ func TestBitcoinAddressValidation(t *testing.T){ if test.expected { if !IsEqual(errs, nil) { - t.Fatalf("Index: %d btc_addr failed Error: %s", i, errs) + t.Fatalf("Index: %d btc_addr failed with Error: %s", i, errs) } } else { if IsEqual(errs, nil) { - t.Fatalf("Index: %d btc_addr failed Error: %s", i, errs) + t.Fatalf("Index: %d btc_addr failed with Error: %s", i, errs) } else { val := getError(errs, "", "") if val.Tag() != "btc_addr" { - t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + t.Fatalf("Index: %d Latitude failed with Error: %s", i, errs) } } } From 3df85bdcfcb38cf72848f07236c888bf0b8f4402 Mon Sep 17 00:00:00 2001 From: josh Date: Sun, 8 Apr 2018 19:38:12 -0700 Subject: [PATCH 4/7] p2sh validation and more tests --- baked_in.go | 2 +- validator_test.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/baked_in.go b/baked_in.go index 55bd4c4..41f68a2 100644 --- a/baked_in.go +++ b/baked_in.go @@ -423,7 +423,7 @@ func isBitcoinAddress(fl FieldLevel) bool { } - if decode[0] != 0 { + if !(decode[0] == 0 || decode[0] == 5) { return false } diff --git a/validator_test.go b/validator_test.go index c16d426..aaab196 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4446,7 +4446,10 @@ func TestBitcoinAddressValidation(t *testing.T){ }{ {"", false}, {"0x02F9AE5f22EA3fA88F05780B30385bEC", false}, - {"1A1zP1ePQGefi2DMPTifTL5SLmv7DivfNa", false}, + {"1A1zP1ePQGefi2DMPTifTL5SLmv7DivfNa", false}, // invalid p2pkh address with invalid characters + {"1P9RQEr2XeE3PEb44ZE35sfZRRW1JH8Uqx", false}, // invald p2pkh address with valid characters + {"3P14159I73E4gFr7JterCCQh9QjiTjiZrG", false}, // invalid p2sh address with invalid characters + {"3P141597f3E4gFr7JterCCQh9QjiTjiZrG", false}, // invalid p2sh address with valid characters {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, // valid p2pkh address {"3P14159f73E4gFr7JterCCQh9QjiTjiZrG", true}, // valid p2sh address {"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", true}, // valid bech32 address From 6deddf27eb8d4d5e680e7afb4af6a844d9744021 Mon Sep 17 00:00:00 2001 From: josh Date: Sun, 8 Apr 2018 19:38:12 -0700 Subject: [PATCH 5/7] proper bech32 address validation --- baked_in.go | 153 +++++++++++++++++++++++++++++++++++++--------- doc.go | 11 +++- regexes.go | 6 +- validator_test.go | 47 ++++++++++++-- 4 files changed, 181 insertions(+), 36 deletions(-) diff --git a/baked_in.go b/baked_in.go index 41f68a2..1dae5a9 100644 --- a/baked_in.go +++ b/baked_in.go @@ -109,6 +109,7 @@ var ( "isbn13": isISBN13, "eth_addr": isEthereumAddress, "btc_addr": isBitcoinAddress, + "btc_addr_bech32": isBitcoinBech32Address, "uuid": isUUID, "uuid3": isUUID3, "uuid4": isUUID4, @@ -393,58 +394,150 @@ func isISBN10(fl FieldLevel) bool { // IsEthereumAddress is the validation function for validating if the field's value is a valid ethereum address based currently only on the format func isEthereumAddress(fl FieldLevel) bool { - field := fl.Field() + address := fl.Field().String() + + if !ethAddressRegex.MatchString(address) { + return false + } + + if ethaddressRegexUpper.MatchString(address) || ethAddressRegexLower.MatchString(address) { + return true + } - return ethAddressRegex.MatchString(field.String()) + // checksum validation is blocked by https://github.com/golang/crypto/pull/28 + + return true } -// IsBitcoinAddress is the validation function for validating if the field's value is a valid btc address, currently only based on the format +// IsBitcoinAddress is the validation function for validating if the field's value is a valid btc address func isBitcoinAddress(fl FieldLevel) bool { address := fl.Field().String() - if btcAddressRegex.MatchString(address) { - - alphabet := []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") + if !btcAddressRegex.MatchString(address) { + return false + } - decode := [25]byte{} + alphabet := []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") - for _, n := range []byte(address) { - d := bytes.IndexByte(alphabet, n) + decode := [25]byte{} - if(d == -1){ - return false - } + for _, n := range []byte(address) { + d := bytes.IndexByte(alphabet, n) - for i := 24; i >= 0; i-- { - d += 58 * int(decode[i]) - decode[i] = byte(d % 256) - d /= 256 - } + for i := 24; i >= 0; i-- { + d += 58 * int(decode[i]) + decode[i] = byte(d % 256) + d /= 256 } + } - if !(decode[0] == 0 || decode[0] == 5) { + if !(decode[0] == 0 || decode[0] == 5) { + return false + } + + h := sha256.New() + h.Write(decode[:21]) + d := h.Sum([]byte{}) + h = sha256.New() + h.Write(d) + + validchecksum := [4]byte{} + computedchecksum := [4]byte{} + + copy(computedchecksum[:], h.Sum(d[:0])) + copy(validchecksum[:], decode[21:]) + + return validchecksum == computedchecksum +} + +// IsBitcoinAddress is the validation function for validating if the field's value is a valid bech32 btc address +func isBitcoinBech32Address(fl FieldLevel) bool { + address := fl.Field().String() + + if !btcAddressRegexBech32.MatchString(address){ + return false + } + + am := len(address) % 8 + + if am == 0 || am == 3 || am == 5{ + return false + } + + address = strings.ToLower(address) + + alphabet := "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + + hr := []int{3, 3, 0, 2, 3} // the human readable part will always be bc + dp := []int{} + + for _, c := range []rune(address[3:]) { + dp = append(dp, strings.IndexRune(alphabet, c)) + } + + ver := dp[0] + + if ver < 0 || ver > 16{ + return false + } + + if ver == 0 { + if len(address) != 42 && len(address) != 62 { return false } + } + + values := append(hr, dp...) + + GEN := []int{ 0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3 } + + p := 1 + + for _, v := range values { + b := p >> 25 + p = (p & 0x1ffffff) << 5 ^ v + + for i := 0; i < 5; i++ { + if (b >> uint(i)) & 1 == 1 { + p ^= GEN[i] + } + } + } - h := sha256.New() - h.Write(decode[:21]) - d := h.Sum([]byte{}) - h = sha256.New() - h.Write(d) + if p != 1 { + return false + } - validchecksum := [4]byte{} - computedchecksum := [4]byte{} + b := uint(0) + acc := 0 + mv := (1 << 5) - 1 - copy(computedchecksum[:], h.Sum(d[:0])) - copy(validchecksum[:], decode[21:]) + sw := []int{} - println(address, "::", validchecksum[:], computedchecksum[:]) + dp = dp[:len(dp) - 6] - return validchecksum == computedchecksum + for _, v := range dp[1:]{ + if v < 0 || (v >> 5) != 0{ + return false + } + acc = (acc << 5) | v + b += 5 + for b >= 8{ + b -= 8 + sw = append(sw, (acc>>b)&mv) + } + } + + if len(sw) < 2 || len(sw) > 40{ + return false + } + + if ver == 0 && len(sw) != 20 && len(sw) != 32 { + return false } - return btcAddressRegexBech32.MatchString(address) + return true } // ExcludesRune is the validation function for validating that the field's value does not contain the rune specified within the param. diff --git a/doc.go b/doc.go index 5522ebb..fdb689a 100644 --- a/doc.go +++ b/doc.go @@ -613,14 +613,23 @@ Bitcoin Address This validates that a string value contains a valid bitcoin address. The format of the string is checked to ensure it matches one of the three formats -P2PKH, P2SH, or Bech32. Currently no further validation is performed. +P2PKH, P2SH and performs checksum validation. Usage: btc_addr +Bitcoin Bech32 Address (segwit) + +This validates that a string value contains a valid bitcoin Bech32 address as defined +by bip-0173 (https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) +Special thanks to Pieter Wuille for providng reference implementations. + + Usage: btc_addr_bech32 + Ethereum Address This validates that a string value contains a valid ethereum address. The format of the string is checked to ensure it matches the standard Ethereum address format +Full validation is blocked by https://github.com/golang/crypto/pull/28 Usage: eth_addr diff --git a/regexes.go b/regexes.go index 82f7ec2..3492c6a 100644 --- a/regexes.go +++ b/regexes.go @@ -33,8 +33,10 @@ const ( hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // https://tools.ietf.org/html/rfc952 hostnameRegexStringRFC1123 = `^[a-zA-Z0-9][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address - btcAddressRegexStringBech32 = `^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + btcAddressRegexStringBech32 = `^([bB][cC]1)[02-9ac-hj-np-zAC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` + ethAddressUpperRegexString = `^0x[0-9A-F]{40}$` + ethAddressLowerRegexString = `^0x[0-9a-f]{40}$` ) var ( @@ -70,4 +72,6 @@ var ( btcAddressRegex = regexp.MustCompile(btcAddressRegexString) btcAddressRegexBech32 = regexp.MustCompile(btcAddressRegexStringBech32) ethAddressRegex = regexp.MustCompile(ethAddressRegexString) + ethaddressRegexUpper = regexp.MustCompile(ethAddressUpperRegexString) + ethAddressRegexLower = regexp.MustCompile(ethAddressLowerRegexString) ) diff --git a/validator_test.go b/validator_test.go index aaab196..a9ef1d4 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4452,12 +4452,8 @@ func TestBitcoinAddressValidation(t *testing.T){ {"3P141597f3E4gFr7JterCCQh9QjiTjiZrG", false}, // invalid p2sh address with valid characters {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, // valid p2pkh address {"3P14159f73E4gFr7JterCCQh9QjiTjiZrG", true}, // valid p2sh address - {"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", true}, // valid bech32 address - } - - for i, test := range tests { errs := validate.Var(test.param, "btc_addr") @@ -4479,6 +4475,49 @@ func TestBitcoinAddressValidation(t *testing.T){ } } +func TestBitcoinBech32AddressValidation(t *testing.T){ + + validate := New() + + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", false}, // invalid bech32 address with invalid startingcharacters + {"BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", false}, + {"bc1rw5uspcuh", false}, + {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, + {"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", false}, + {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, + {"bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", false}, + {"bc1gmk9yu", false}, + {"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", true}, + {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", true}, + {"BC1SW50QA3JX3S", true}, + {"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", true}, + } + + for i, test := range tests { + + errs := validate.Var(test.param, "btc_addr_bech32") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d btc_addr_bech32 failed with Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d btc_addr_bech32 failed with Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "btc_addr_bech32" { + t.Fatalf("Index: %d Latitude failed with Error: %s", i, errs) + } + } + } + } +} func TestNoStructLevelValidation(t *testing.T) { From ce6284a6fc9915bcfc735ce87d0347224c8a3944 Mon Sep 17 00:00:00 2001 From: josh Date: Sun, 8 Apr 2018 19:38:12 -0700 Subject: [PATCH 6/7] confirm only all upper or all lower case address are valid --- baked_in.go | 2 +- regexes.go | 138 +++++++++++++++++++++++----------------------- validator_test.go | 1 + 3 files changed, 72 insertions(+), 69 deletions(-) diff --git a/baked_in.go b/baked_in.go index 1dae5a9..057ba0f 100644 --- a/baked_in.go +++ b/baked_in.go @@ -455,7 +455,7 @@ func isBitcoinAddress(fl FieldLevel) bool { func isBitcoinBech32Address(fl FieldLevel) bool { address := fl.Field().String() - if !btcAddressRegexBech32.MatchString(address){ + if !btcLowerAddressRegexBech32.MatchString(address) && !btcUpperAddressRegexBech32.MatchString(address){ return false } diff --git a/regexes.go b/regexes.go index 3492c6a..13ae0b6 100644 --- a/regexes.go +++ b/regexes.go @@ -3,75 +3,77 @@ package validator import "regexp" const ( - alphaRegexString = "^[a-zA-Z]+$" - alphaNumericRegexString = "^[a-zA-Z0-9]+$" - alphaUnicodeRegexString = "^[\\p{L}]+$" - alphaUnicodeNumericRegexString = "^[\\p{L}\\p{N}]+$" - numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$" - numberRegexString = "^[0-9]+$" - hexadecimalRegexString = "^[0-9a-fA-F]+$" - hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" - rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$" - rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" - hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$" - hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" - emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" - base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" - iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$" - iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$" - uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" - uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" - uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" - uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" - aSCIIRegexString = "^[\x00-\x7F]*$" - printableASCIIRegexString = "^[\x20-\x7E]*$" - multibyteRegexString = "[^\x00-\x7F]" - dataURIRegexString = "^data:.+\\/(.+);base64$" - latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" - longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" - sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` - hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // https://tools.ietf.org/html/rfc952 - hostnameRegexStringRFC1123 = `^[a-zA-Z0-9][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 - btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address - btcAddressRegexStringBech32 = `^([bB][cC]1)[02-9ac-hj-np-zAC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 - ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` - ethAddressUpperRegexString = `^0x[0-9A-F]{40}$` - ethAddressLowerRegexString = `^0x[0-9a-f]{40}$` + alphaRegexString = "^[a-zA-Z]+$" + alphaNumericRegexString = "^[a-zA-Z0-9]+$" + alphaUnicodeRegexString = "^[\\p{L}]+$" + alphaUnicodeNumericRegexString = "^[\\p{L}\\p{N}]+$" + numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$" + numberRegexString = "^[0-9]+$" + hexadecimalRegexString = "^[0-9a-fA-F]+$" + hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" + rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$" + rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" + hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$" + hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" + emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" + base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" + iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$" + iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$" + uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" + uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + aSCIIRegexString = "^[\x00-\x7F]*$" + printableASCIIRegexString = "^[\x20-\x7E]*$" + multibyteRegexString = "[^\x00-\x7F]" + dataURIRegexString = "^data:.+\\/(.+);base64$" + latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" + longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" + sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` + hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // https://tools.ietf.org/html/rfc952 + hostnameRegexStringRFC1123 = `^[a-zA-Z0-9][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 + btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address + btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` + ethAddressUpperRegexString = `^0x[0-9A-F]{40}$` + ethAddressLowerRegexString = `^0x[0-9a-f]{40}$` ) var ( - alphaRegex = regexp.MustCompile(alphaRegexString) - alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString) - alphaUnicodeRegex = regexp.MustCompile(alphaUnicodeRegexString) - alphaUnicodeNumericRegex = regexp.MustCompile(alphaUnicodeNumericRegexString) - numericRegex = regexp.MustCompile(numericRegexString) - numberRegex = regexp.MustCompile(numberRegexString) - hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString) - hexcolorRegex = regexp.MustCompile(hexcolorRegexString) - rgbRegex = regexp.MustCompile(rgbRegexString) - rgbaRegex = regexp.MustCompile(rgbaRegexString) - hslRegex = regexp.MustCompile(hslRegexString) - hslaRegex = regexp.MustCompile(hslaRegexString) - emailRegex = regexp.MustCompile(emailRegexString) - base64Regex = regexp.MustCompile(base64RegexString) - iSBN10Regex = regexp.MustCompile(iSBN10RegexString) - iSBN13Regex = regexp.MustCompile(iSBN13RegexString) - uUID3Regex = regexp.MustCompile(uUID3RegexString) - uUID4Regex = regexp.MustCompile(uUID4RegexString) - uUID5Regex = regexp.MustCompile(uUID5RegexString) - uUIDRegex = regexp.MustCompile(uUIDRegexString) - aSCIIRegex = regexp.MustCompile(aSCIIRegexString) - printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString) - multibyteRegex = regexp.MustCompile(multibyteRegexString) - dataURIRegex = regexp.MustCompile(dataURIRegexString) - latitudeRegex = regexp.MustCompile(latitudeRegexString) - longitudeRegex = regexp.MustCompile(longitudeRegexString) - sSNRegex = regexp.MustCompile(sSNRegexString) - hostnameRegexRFC952 = regexp.MustCompile(hostnameRegexStringRFC952) - hostnameRegexRFC1123 = regexp.MustCompile(hostnameRegexStringRFC1123) - btcAddressRegex = regexp.MustCompile(btcAddressRegexString) - btcAddressRegexBech32 = regexp.MustCompile(btcAddressRegexStringBech32) - ethAddressRegex = regexp.MustCompile(ethAddressRegexString) - ethaddressRegexUpper = regexp.MustCompile(ethAddressUpperRegexString) - ethAddressRegexLower = regexp.MustCompile(ethAddressLowerRegexString) + alphaRegex = regexp.MustCompile(alphaRegexString) + alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString) + alphaUnicodeRegex = regexp.MustCompile(alphaUnicodeRegexString) + alphaUnicodeNumericRegex = regexp.MustCompile(alphaUnicodeNumericRegexString) + numericRegex = regexp.MustCompile(numericRegexString) + numberRegex = regexp.MustCompile(numberRegexString) + hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString) + hexcolorRegex = regexp.MustCompile(hexcolorRegexString) + rgbRegex = regexp.MustCompile(rgbRegexString) + rgbaRegex = regexp.MustCompile(rgbaRegexString) + hslRegex = regexp.MustCompile(hslRegexString) + hslaRegex = regexp.MustCompile(hslaRegexString) + emailRegex = regexp.MustCompile(emailRegexString) + base64Regex = regexp.MustCompile(base64RegexString) + iSBN10Regex = regexp.MustCompile(iSBN10RegexString) + iSBN13Regex = regexp.MustCompile(iSBN13RegexString) + uUID3Regex = regexp.MustCompile(uUID3RegexString) + uUID4Regex = regexp.MustCompile(uUID4RegexString) + uUID5Regex = regexp.MustCompile(uUID5RegexString) + uUIDRegex = regexp.MustCompile(uUIDRegexString) + aSCIIRegex = regexp.MustCompile(aSCIIRegexString) + printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString) + multibyteRegex = regexp.MustCompile(multibyteRegexString) + dataURIRegex = regexp.MustCompile(dataURIRegexString) + latitudeRegex = regexp.MustCompile(latitudeRegexString) + longitudeRegex = regexp.MustCompile(longitudeRegexString) + sSNRegex = regexp.MustCompile(sSNRegexString) + hostnameRegexRFC952 = regexp.MustCompile(hostnameRegexStringRFC952) + hostnameRegexRFC1123 = regexp.MustCompile(hostnameRegexStringRFC1123) + btcAddressRegex = regexp.MustCompile(btcAddressRegexString) + btcUpperAddressRegexBech32 = regexp.MustCompile(btcAddressUpperRegexStringBech32) + btcLowerAddressRegexBech32 = regexp.MustCompile(btcAddressLowerRegexStringBech32) + ethAddressRegex = regexp.MustCompile(ethAddressRegexString) + ethaddressRegexUpper = regexp.MustCompile(ethAddressUpperRegexString) + ethAddressRegexLower = regexp.MustCompile(ethAddressLowerRegexString) ) diff --git a/validator_test.go b/validator_test.go index a9ef1d4..0c121b7 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4488,6 +4488,7 @@ func TestBitcoinBech32AddressValidation(t *testing.T){ {"BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", false}, {"bc1rw5uspcuh", false}, {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, + {"BC1QW508d6QEJxTDG4y5R3ZArVARY0C5XW7KV8F3T4", false}, {"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", false}, {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, {"bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", false}, From 073682ea75151552c065098c4b587321a16bc5d3 Mon Sep 17 00:00:00 2001 From: josh Date: Mon, 9 Apr 2018 01:49:06 -0700 Subject: [PATCH 7/7] added in more test cases and removed some redundant validation steps --- baked_in.go | 19 ++-------- validator_test.go | 92 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 87 insertions(+), 24 deletions(-) diff --git a/baked_in.go b/baked_in.go index 057ba0f..0688e49 100644 --- a/baked_in.go +++ b/baked_in.go @@ -431,11 +431,6 @@ func isBitcoinAddress(fl FieldLevel) bool { } } - - if !(decode[0] == 0 || decode[0] == 5) { - return false - } - h := sha256.New() h.Write(decode[:21]) d := h.Sum([]byte{}) @@ -451,7 +446,7 @@ func isBitcoinAddress(fl FieldLevel) bool { return validchecksum == computedchecksum } -// IsBitcoinAddress is the validation function for validating if the field's value is a valid bech32 btc address +// IsBitcoinBech32Address is the validation function for validating if the field's value is a valid bech32 btc address func isBitcoinBech32Address(fl FieldLevel) bool { address := fl.Field().String() @@ -512,15 +507,9 @@ func isBitcoinBech32Address(fl FieldLevel) bool { b := uint(0) acc := 0 mv := (1 << 5) - 1 - sw := []int{} - dp = dp[:len(dp) - 6] - - for _, v := range dp[1:]{ - if v < 0 || (v >> 5) != 0{ - return false - } + for _, v := range dp[1:len(dp) - 6]{ acc = (acc << 5) | v b += 5 for b >= 8{ @@ -533,10 +522,6 @@ func isBitcoinBech32Address(fl FieldLevel) bool { return false } - if ver == 0 && len(sw) != 20 && len(sw) != 32 { - return false - } - return true } diff --git a/validator_test.go b/validator_test.go index 0c121b7..2d345bb 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4445,13 +4445,85 @@ func TestBitcoinAddressValidation(t *testing.T){ expected bool }{ {"", false}, + {"x", false}, {"0x02F9AE5f22EA3fA88F05780B30385bEC", false}, - {"1A1zP1ePQGefi2DMPTifTL5SLmv7DivfNa", false}, // invalid p2pkh address with invalid characters - {"1P9RQEr2XeE3PEb44ZE35sfZRRW1JH8Uqx", false}, // invald p2pkh address with valid characters - {"3P14159I73E4gFr7JterCCQh9QjiTjiZrG", false}, // invalid p2sh address with invalid characters - {"3P141597f3E4gFr7JterCCQh9QjiTjiZrG", false}, // invalid p2sh address with valid characters - {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, // valid p2pkh address - {"3P14159f73E4gFr7JterCCQh9QjiTjiZrG", true}, // valid p2sh address + {"1A1zP1ePQGefi2DMPTifTL5SLmv7DivfNa", false}, + {"1P9RQEr2XeE3PEb44ZE35sfZRRW1JH8Uqx", false}, + {"3P14159I73E4gFr7JterCCQh9QjiTjiZrG", false}, + {"3P141597f3E4gFr7JterCCQh9QjiTjiZrG", false}, + {"37qgekLpCCHrQuSjvX3fs496FWTGsHFHizjJAs6NPcR47aefnnCWECAhHV6E3g4YN7u7Yuwod5Y", false}, + {"dzb7VV1Ui55BARxv7ATxAtCUeJsANKovDGWFVgpTbhq9gvPqP3yv", false}, + {"MuNu7ZAEDFiHthiunm7dPjwKqrVNCM3mAz6rP9zFveQu14YA8CxExSJTHcVP9DErn6u84E6Ej7S", false}, + {"rPpQpYknyNQ5AEHuY6H8ijJJrYc2nDKKk9jjmKEXsWzyAQcFGpDLU2Zvsmoi8JLR7hAwoy3RQWf", false}, + {"4Uc3FmN6NQ6zLBK5QQBXRBUREaaHwCZYsGCueHauuDmJpZKn6jkEskMB2Zi2CNgtb5r6epWEFfUJq", false}, + {"7aQgR5DFQ25vyXmqZAWmnVCjL3PkBcdVkBUpjrjMTcghHx3E8wb", false}, + {"17QpPprjeg69fW1DV8DcYYCKvWjYhXvWkov6MJ1iTTvMFj6weAqW7wybZeH57WTNxXVCRH4veVs", false}, + {"KxuACDviz8Xvpn1xAh9MfopySZNuyajYMZWz16Dv2mHHryznWUp3", false}, + {"7nK3GSmqdXJQtdohvGfJ7KsSmn3TmGqExug49583bDAL91pVSGq5xS9SHoAYL3Wv3ijKTit65th", false}, + {"cTivdBmq7bay3RFGEBBuNfMh2P1pDCgRYN2Wbxmgwr4ki3jNUL2va", false}, + {"gjMV4vjNjyMrna4fsAr8bWxAbwtmMUBXJS3zL4NJt5qjozpbQLmAfK1uA3CquSqsZQMpoD1g2nk", false}, + {"emXm1naBMoVzPjbk7xpeTVMFy4oDEe25UmoyGgKEB1gGWsK8kRGs", false}, + {"7VThQnNRj1o3Zyvc7XHPRrjDf8j2oivPTeDXnRPYWeYGE4pXeRJDZgf28ppti5hsHWXS2GSobdqyo", false}, + {"1G9u6oCVCPh2o8m3t55ACiYvG1y5BHewUkDSdiQarDcYXXhFHYdzMdYfUAhfxn5vNZBwpgUNpso", false}, + {"31QQ7ZMLkScDiB4VyZjuptr7AEc9j1SjstF7pRoLhHTGkW4Q2y9XELobQmhhWxeRvqcukGd1XCq", false}, + {"DHqKSnpxa8ZdQyH8keAhvLTrfkyBMQxqngcQA5N8LQ9KVt25kmGN", false}, + {"2LUHcJPbwLCy9GLH1qXmfmAwvadWw4bp4PCpDfduLqV17s6iDcy1imUwhQJhAoNoN1XNmweiJP4i", false}, + {"7USRzBXAnmck8fX9HmW7RAb4qt92VFX6soCnts9s74wxm4gguVhtG5of8fZGbNPJA83irHVY6bCos", false}, + {"1DGezo7BfVebZxAbNT3XGujdeHyNNBF3vnficYoTSp4PfK2QaML9bHzAMxke3wdKdHYWmsMTJVu", false}, + {"2D12DqDZKwCxxkzs1ZATJWvgJGhQ4cFi3WrizQ5zLAyhN5HxuAJ1yMYaJp8GuYsTLLxTAz6otCfb", false}, + {"8AFJzuTujXjw1Z6M3fWhQ1ujDW7zsV4ePeVjVo7D1egERqSW9nZ", false}, + {"163Q17qLbTCue8YY3AvjpUhotuaodLm2uqMhpYirsKjVqnxJRWTEoywMVY3NbBAHuhAJ2cF9GAZ", false}, + {"2MnmgiRH4eGLyLc9eAqStzk7dFgBjFtUCtu", false}, + {"461QQ2sYWxU7H2PV4oBwJGNch8XVTYYbZxU", false}, + {"2UCtv53VttmQYkVU4VMtXB31REvQg4ABzs41AEKZ8UcB7DAfVzdkV9JDErwGwyj5AUHLkmgZeobs", false}, + {"cSNjAsnhgtiFMi6MtfvgscMB2Cbhn2v1FUYfviJ1CdjfidvmeW6mn", false}, + {"gmsow2Y6EWAFDFE1CE4Hd3Tpu2BvfmBfG1SXsuRARbnt1WjkZnFh1qGTiptWWbjsq2Q6qvpgJVj", false}, + {"nksUKSkzS76v8EsSgozXGMoQFiCoCHzCVajFKAXqzK5on9ZJYVHMD5CKwgmX3S3c7M1U3xabUny", false}, + {"L3favK1UzFGgdzYBF2oBT5tbayCo4vtVBLJhg2iYuMeePxWG8SQc", false}, + {"7VxLxGGtYT6N99GdEfi6xz56xdQ8nP2dG1CavuXx7Rf2PrvNMTBNevjkfgs9JmkcGm6EXpj8ipyPZ ",false}, + {"2mbZwFXF6cxShaCo2czTRB62WTx9LxhTtpP", false}, + {"dB7cwYdcPSgiyAwKWL3JwCVwSk6epU2txw", false}, + {"HPhFUhUAh8ZQQisH8QQWafAxtQYju3SFTX", false}, + {"4ctAH6AkHzq5ioiM1m9T3E2hiYEev5mTsB", false}, + {"31uEbMgunupShBVTewXjtqbBv5MndwfXhb", false}, + {"175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W", false}, + {"Hn1uFi4dNexWrqARpjMqgT6cX1UsNPuV3cHdGg9ExyXw8HTKadbktRDtdeVmY3M1BxJStiL4vjJ", false}, + {"Sq3fDbvutABmnAHHExJDgPLQn44KnNC7UsXuT7KZecpaYDMU9Txs", false}, + {"6TqWyrqdgUEYDQU1aChMuFMMEimHX44qHFzCUgGfqxGgZNMUVWJ", false}, + {"giqJo7oWqFxNKWyrgcBxAVHXnjJ1t6cGoEffce5Y1y7u649Noj5wJ4mmiUAKEVVrYAGg2KPB3Y4", false}, + {"cNzHY5e8vcmM3QVJUcjCyiKMYfeYvyueq5qCMV3kqcySoLyGLYUK", false}, + {"37uTe568EYc9WLoHEd9jXEvUiWbq5LFLscNyqvAzLU5vBArUJA6eydkLmnMwJDjkL5kXc2VK7ig", false}, + {"EsYbG4tWWWY45G31nox838qNdzksbPySWc", false}, + {"nbuzhfwMoNzA3PaFnyLcRxE9bTJPDkjZ6Rf6Y6o2ckXZfzZzXBT", false}, + {"cQN9PoxZeCWK1x56xnz6QYAsvR11XAce3Ehp3gMUdfSQ53Y2mPzx", false}, + {"1Gm3N3rkef6iMbx4voBzaxtXcmmiMTqZPhcuAepRzYUJQW4qRpEnHvMojzof42hjFRf8PE2jPde", false}, + {"2TAq2tuN6x6m233bpT7yqdYQPELdTDJn1eU", false}, + {"ntEtnnGhqPii4joABvBtSEJG6BxjT2tUZqE8PcVYgk3RHpgxgHDCQxNbLJf7ardf1dDk2oCQ7Cf", false}, + {"Ky1YjoZNgQ196HJV3HpdkecfhRBmRZdMJk89Hi5KGfpfPwS2bUbfd", false}, + {"2A1q1YsMZowabbvta7kTy2Fd6qN4r5ZCeG3qLpvZBMzCixMUdkN2Y4dHB1wPsZAeVXUGD83MfRED", false}, + {"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", true}, + {"1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ", true}, + {"1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv", true}, + {"1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu", true}, + {"1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4", true}, + {"19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r", true}, + {"13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE", true}, + {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, + {"3P14159f73E4gFr7JterCCQh9QjiTjiZrG", true}, + {"3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou", true}, + {"3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy", true}, + {"3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks", true}, + {"33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk", true}, + {"3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y", true}, + {"37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3", true}, + {"3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G", true}, + {"12KYrjTdVGjFMtaxERSk3gphreJ5US8aUP", true}, + {"12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y", true}, + {"1oNLrsHnBcR6dpaBpwz3LSwutbUNkNSjs", true}, + {"1SQHtwR5oJRKLfiWQ2APsAd9miUc4k2ez", true}, + {"116CGDLddrZhMrTwhCVJXtXQpxygTT1kHd", true}, + {"3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt", true}, + } for i, test := range tests { @@ -4484,17 +4556,23 @@ func TestBitcoinBech32AddressValidation(t *testing.T){ expected bool }{ {"", false}, - {"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", false}, // invalid bech32 address with invalid startingcharacters + {"bc1rw5uspcuh", false}, + {"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", false}, {"BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", false}, + {"qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", false}, {"bc1rw5uspcuh", false}, {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, {"BC1QW508d6QEJxTDG4y5R3ZArVARY0C5XW7KV8F3T4", false}, {"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", false}, + {"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", false}, {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, + {"bc1pw508d6qejxtdg4y5r3zarqfsj6c3", false}, {"bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", false}, {"bc1gmk9yu", false}, + {"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", false}, {"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", true}, {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", true}, + {"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", true}, {"BC1SW50QA3JX3S", true}, {"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", true}, }