It does a "deep" validation, checking that the checksum built into every bitcoin address matches the address. It needs the PyCrypto library for the SHA256 function.
I hereby release this code into the public domain, do with it what you will. And please let me know if you find any bugs in it.
BCAddressField.py:
Code:
#
# DJango field type for a Bitcoin Address
#
import re
from django import forms
from django.forms.util import ValidationError
from Crypto.Hash import SHA256
class BCAddressField(forms.CharField):
default_error_messages = {
'invalid': 'Invalid Bitcoin address.',
}
def __init__(self, *args, **kwargs):
super(BCAddressField, self).__init__(*args, **kwargs)
def clean(self, value):
value = value.strip()
if re.match(r"[a-zA-Z1-9]{27,35}$", value) is None:
raise ValidationError(self.error_messages['invalid'])
version = get_bcaddress_version(value)
if version is None:
raise ValidationError(self.error_messages['invalid'])
return value
import math
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
__b58base = len(__b58chars)
def b58encode(v):
""" encode v, which is a string of bytes, to base58.
"""
long_value = 0L
for (i, c) in enumerate(v[::-1]):
long_value += (256**i) * ord(c)
result = ''
while long_value >= __b58base:
div, mod = divmod(long_value, __b58base)
result = __b58chars[mod] + result
long_value = div
result = __b58chars[long_value] + result
# Bitcoin does a little leading-zero-compression:
# leading 0-bytes in the input become leading-1s
nPad = 0
for c in v:
if c == '\0': nPad += 1
else: break
return (__b58chars[0]*nPad) + result
def b58decode(v, length):
""" decode v into a string of len bytes
"""
long_value = 0L
for (i, c) in enumerate(v[::-1]):
long_value += __b58chars.find(c) * (__b58base**i)
result = ''
while long_value >= 256:
div, mod = divmod(long_value, 256)
result = chr(mod) + result
long_value = div
result = chr(long_value) + result
nPad = 0
for c in v:
if c == __b58chars[0]: nPad += 1
else: break
result = chr(0)*nPad + result
if length is not None and len(result) != length:
return None
return result
def get_bcaddress_version(strAddress):
""" Returns None if strAddress is invalid. Otherwise returns integer version of address. """
addr = b58decode(strAddress,25)
if addr is None: return None
version = addr[0]
checksum = addr[-4:]
vh160 = addr[:-4] # Version plus hash160 is what is checksummed
h3=SHA256.new(SHA256.new(vh160).digest()).digest()
if h3[0:4] == checksum:
return ord(version)
return None
October 20: Fixed bug with bitcoin addresses with leading-1's.
# DJango field type for a Bitcoin Address
#
import re
from django import forms
from django.forms.util import ValidationError
from Crypto.Hash import SHA256
class BCAddressField(forms.CharField):
default_error_messages = {
'invalid': 'Invalid Bitcoin address.',
}
def __init__(self, *args, **kwargs):
super(BCAddressField, self).__init__(*args, **kwargs)
def clean(self, value):
value = value.strip()
if re.match(r"[a-zA-Z1-9]{27,35}$", value) is None:
raise ValidationError(self.error_messages['invalid'])
version = get_bcaddress_version(value)
if version is None:
raise ValidationError(self.error_messages['invalid'])
return value
import math
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
__b58base = len(__b58chars)
def b58encode(v):
""" encode v, which is a string of bytes, to base58.
"""
long_value = 0L
for (i, c) in enumerate(v[::-1]):
long_value += (256**i) * ord(c)
result = ''
while long_value >= __b58base:
div, mod = divmod(long_value, __b58base)
result = __b58chars[mod] + result
long_value = div
result = __b58chars[long_value] + result
# Bitcoin does a little leading-zero-compression:
# leading 0-bytes in the input become leading-1s
nPad = 0
for c in v:
if c == '\0': nPad += 1
else: break
return (__b58chars[0]*nPad) + result
def b58decode(v, length):
""" decode v into a string of len bytes
"""
long_value = 0L
for (i, c) in enumerate(v[::-1]):
long_value += __b58chars.find(c) * (__b58base**i)
result = ''
while long_value >= 256:
div, mod = divmod(long_value, 256)
result = chr(mod) + result
long_value = div
result = chr(long_value) + result
nPad = 0
for c in v:
if c == __b58chars[0]: nPad += 1
else: break
result = chr(0)*nPad + result
if length is not None and len(result) != length:
return None
return result
def get_bcaddress_version(strAddress):
""" Returns None if strAddress is invalid. Otherwise returns integer version of address. """
addr = b58decode(strAddress,25)
if addr is None: return None
version = addr[0]
checksum = addr[-4:]
vh160 = addr[:-4] # Version plus hash160 is what is checksummed
h3=SHA256.new(SHA256.new(vh160).digest()).digest()
if h3[0:4] == checksum:
return ord(version)
return None