proper bech32 address validation

pull/355/head
josh 7 years ago
parent 3df85bdcfc
commit 6deddf27eb
  1. 153
      baked_in.go
  2. 11
      doc.go
  3. 6
      regexes.go
  4. 47
      validator_test.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.

@ -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

@ -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)
)

@ -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) {

Loading…
Cancel
Save