Gavin Andresen (OP)
Legendary
Offline
Activity: 1652
Merit: 2216
Chief Scientist
|
|
September 13, 2010, 12:38:24 PM Last edit: October 20, 2010, 03:18:57 PM by gavinandresen |
|
This bitcoind address validator is a subclass of the Django forms.CharField class, but could easily be adapted to other frameworks or to be standalone code. 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: # # 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.
|
How often do you get the chance to work on a potentially world-changing project?
|
|
|
|
|
|
|
|
Unlike traditional banking where clients have only a few account numbers, with Bitcoin people can create an unlimited number of accounts (addresses). This can be used to easily track payments, and it improves anonymity.
|
|
|
Advertised sites are not endorsed by the Bitcoin Forum. They may be unsafe, untrustworthy, or illegal in your jurisdiction.
|
|
|
davout
Legendary
Offline
Activity: 1372
Merit: 1007
1davout
|
|
October 19, 2010, 10:55:32 PM |
|
Thanks for your code ! Contributing with a Ruby translation (more specifically a Rails drop-in validator) require 'digest'
class BitcoinAddressValidator < ActiveModel::EachValidator def validate(record, field, value) unless valid_bitcoin_address?(value) record.errors[field] << "Bitcoin address is invalid" end end
private
B58Chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' B58Base = B58Chars.length
def self.valid_bitcoin_address?(address) (address =~ /^[a-zA-Z1-9]{33,35}$/) and version(address) end
def self.version(address) decoded = b58_decode(address, 25) version = decoded[0, 1] checksum = decoded[-4, decoded.length] vh160 = decoded[0, decoded.length - 4]
hashed = (Digest::SHA2.new << (Digest::SHA2.new << vh160).digest).digest
hashed[0, 4] == checksum ? version[0] : nil end
def self.b58_decode(value, length) long_value = 0 index = 0 result = ""
value.reverse.each_char do |c| long_value += B58Chars.index(c) * (B58Base ** index) index += 1 end
while long_value >= 256 do div, mod = long_value.divmod 256 result = mod.chr + result long_value = div end
result = long_value.chr + result
if result.length < length result = 0.chr * (length - result.length) + result end
result end end Passes unit tests (see http://github.com/davout/bitcoin-bank) Just a question, what's the use of your b58_encode method ?
|
|
|
|
ByteCoin
|
|
October 19, 2010, 11:40:42 PM |
|
(address =~ /^[a-zA-Z1-9]{33,35}$/) You should bear in mind the fact that 1111111111111111111114oLvT2 is a valid Bitcoin address. It has 27 characters. 28,29,30... etc character addresses also exist. I have mentioned this before in ByteCoin
|
|
|
|
Gavin Andresen (OP)
Legendary
Offline
Activity: 1652
Merit: 2216
Chief Scientist
|
|
October 20, 2010, 12:43:36 AM |
|
RE: what's the use of b58_encode? It is dead code for this use-- bitcointools (where I first implemented this stuff) uses it to translate from binary hash160 to human-readable bitcoin addresses.
RE: 27 character bitcoin addresses: I'm puzzled. There's a mismatch between the way bitcoin treats leading zeros (leading 1's when base58 encoded) and the way my python code treats them.
ByteCoin: have you dissected the bitcoin code enough to explain how it decides how many leading zeros to add? According to my code, '14oLvT2' and '11111111111111111111111111114oLvT2' are the same bitcoin address (corresponding to the public key with a hash of all zero bytes).
But bitcoin only likes the 27-character '1111111111111111111114oLvT2' version.
I'll have to stare at the code some more tomorrow when I'm more awake.
|
How often do you get the chance to work on a potentially world-changing project?
|
|
|
theymos
Administrator
Legendary
Offline
Activity: 5194
Merit: 12983
|
|
October 20, 2010, 01:50:10 AM Last edit: October 20, 2010, 02:04:37 AM by theymos |
|
For every leading 0x00 byte, add a leading 1 (encode). For every leading 1, add a 0x00 byte (decode). For encode, don't accept input that doesn't have an even number of bits (divisible by eight), as this doesn't make sense. For decode, pad leading zeros until you have an even number of bits. I just check that the output immediately post-DecodeBase58 (including leading zeroes, checksum, versionNumber) is 25 bytes long. I think that Bitcoin does something similar to this. I made some web tools for dealing with addresses: http://theymos.ath.cx:64150/q/checkaddresshttp://theymos.ath.cx:64150/q/addresstohashhttp://theymos.ath.cx:64150/q/hashtoaddresshttp://theymos.ath.cx:64150/q/hashpubkey(Source not public yet.)
|
1NXYoJ5xU91Jp83XfVMHwwTUyZFK64BoAD
|
|
|
Gavin Andresen (OP)
Legendary
Offline
Activity: 1652
Merit: 2216
Chief Scientist
|
|
October 20, 2010, 03:18:17 PM |
|
Thanks theymos! I completely missed the leading-zero-bytes become leading-'1'-chars one-for-one.
I fixed the python code in the first message of this thread.
|
How often do you get the chance to work on a potentially world-changing project?
|
|
|
ploum
|
|
October 25, 2010, 07:46:52 AM |
|
Are you working on a Django bitcoin project ? I'm highly interested, thinking about it myself too.
|
|
|
|
MagicalTux
VIP
Hero Member
Offline
Activity: 608
Merit: 501
-
|
|
November 15, 2010, 07:01:46 PM |
|
For those interested, I rewrote base58_encode/base58_decode in PHP, and added a "decode_btc" function (that will return an array containing "version" and "hash" for a given bitcoin address). This code requires the following php extensions: - gmp (for bignum values, we could use bc, or even the php-native implementation in pear, I'm just too lazy and I got gmp installed on all my servers anyway)
- hash (or mhash, someone could insert a php-native sha256 implementation to fallback to)
Here's the code, largely inspired from the code of the previous authors of this thread: <?php $btc = decode_btc('1Eym7pyJcaambv8FG4ZoU8A4xsiL9us2zz'); var_dump($btc); var_dump(encode_btc($btc)); $btc = decode_btc('1111111111111111111114oLvT2'); var_dump($btc); var_dump(encode_btc($btc));
function hash_sha256($string) { if (function_exists('hash')) return hash('sha256', $string, true); if (function_exists('mhash')) return mhash(MHASH_SHA256, $string); // insert native php implementation of sha256 here throw new Exception('Too lazy to fallback when the guy who configured php was lazy too'); }
function encode_btc($btc) { $btc = chr($btc['version']).pack('H*', $btc['hash']); if (strlen($btc) != 21) return false; $cksum = substr(hash_sha256(hash_sha256($btc)), 0, 4); return base58_encode($btc.$cksum); }
function decode_btc($btc) { $btc = base58_decode($btc); if (strlen($btc) != 25) return false; // invalid $version = ord($btc[0]); $cksum = substr($btc, -4); // checksum is double sha256 (take 4 first bytes of result) $good_cksum = substr(hash_sha256(hash_sha256(substr($btc, 0, -4))), 0, 4); if ($cksum != $good_cksum) return false; return array('version' => $version, 'hash' => bin2hex(substr($btc, 1, 20))); }
function base58_encode($string) { $table = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
$long_value = gmp_init(bin2hex($string), 16);
$result = ''; while(gmp_cmp($long_value, 58) > 0) { list($long_value, $mod) = gmp_div_qr($long_value, 58); $result .= $table[gmp_intval($mod)]; } $result .= $table[gmp_intval($long_value)];
for($nPad = 0; $string[$nPad] == "\0"; ++$nPad);
return str_repeat($table[0], $nPad).strrev($result); }
function base58_decode($string) { $table = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; static $table_rev = null; if (is_null($table_rev)) { $table_rev = array(); for($i=0;$i<58;++$i) $table_rev[$table[$i]]=$i; }
$l = strlen($string); $long_value = gmp_init('0'); for($i=0;$i<$l;++$i) { $c=$string[$l-$i-1]; $long_value = gmp_add($long_value, gmp_mul($table_rev[$c], gmp_pow(58, $i))); }
// php is lacking binary output for gmp $res = pack('H*', gmp_strval($long_value, 16));
for($nPad = 0; $string[$nPad] == $table[0]; ++$nPad); return str_repeat("\0", $nPad).$res; }
This outputs: array(2) { ["version"]=> int(0) ["hash"]=> string(40) "9955c1b44fa66688d27aaa06ba4ad02c6dd91088" } string(34) "1Eym7pyJcaambv8FG4ZoU8A4xsiL9us2zz" array(2) { ["version"]=> int(0) ["hash"]=> string(40) "0000000000000000000000000000000000000000" } string(27) "1111111111111111111114oLvT2"
|
|
|
|
jerfelix
|
|
May 28, 2011, 10:44:52 PM |
|
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'])
Gavin, I'm trying to understand your usage of default.error_messages (first line in the snippet), vs. self.error_messages (last line in the snippet, above), in your code. Does Django somehow link those two together? Or is that a bug in the code? (I'm sure this code has been tested a lot since October!) Seems to me that you should be using the same dictionary in both places.
|
|
|
|
grondilu
Legendary
Offline
Activity: 1288
Merit: 1076
|
|
May 28, 2011, 11:05:22 PM |
|
It's kind of frustrating that nobody seems to want to use my bash functions to check bitcoin addresses. It only requires basic unix tools such as xxd, dc and openssl. base58=({1..9} {A..H} {J..N} {P..Z} {a..k} {m..z}) bitcoinregex="^[$(printf "%s" "${base58[@]}")]{34}$"
decodeBase58() { local s=$1 for i in {0..57} do s="${s//${base58[i]}/ $i}" done dc <<< "16o0d${s// /+58*}+f" }
checksum() { xxd -p -r <<<"$1" | openssl dgst -sha256 -binary | openssl dgst -sha256 -binary | xxd -p -c 80 | head -c 8 }
checkBitcoinAddress() { if [[ "$1" =~ $bitcoinregex ]] then h=$(decodeBase58 "$1") checksum "00${h::${#h}-8}" | grep -qi "^${h: -8}$" else return 2 fi }
|
|
|
|
beppu
Newbie
Offline
Activity: 5
Merit: 0
|
|
May 29, 2011, 10:10:19 PM |
|
It's kind of frustrating that nobody seems to want to use my bash functions to check bitcoin addresses.
It only requires basic unix tools such as xxd, dc and openssl.
This is beautiful and concise shell scripting. Nice job.
|
|
|
|
grondilu
Legendary
Offline
Activity: 1288
Merit: 1076
|
|
May 29, 2011, 11:09:01 PM |
|
It's kind of frustrating that nobody seems to want to use my bash functions to check bitcoin addresses.
It only requires basic unix tools such as xxd, dc and openssl.
This is beautiful and concise shell scripting. Nice job. Ah, finally! Thanks!
|
|
|
|
Yogafan00000
|
|
November 15, 2012, 02:38:58 PM |
|
.NET 4.0 version of bitcoin address validation, Translated by YogaFan: Imports System.Security.Cryptography Imports System.Numerics
Module ValidateBitCoinAddress
Function isValidBitcoinAddress(s As String) As Boolean Dim regExPattern As String = "[a-zA-Z1-9]{27,35}$" If MatchString(s, regExPattern) Then If get_bcaddress_version(s) <> "" Then isValidBitcoinAddress = True Else isValidBitcoinAddress = False End If Else isValidBitcoinAddress = False End If End Function
Function MatchString(ByVal str As String, ByVal regexstr As String) As Boolean str = str.Trim() Dim pattern As New System.Text.RegularExpressions.Regex(regexstr) Return pattern.IsMatch(str) End Function
Function get_bcaddress_version(strAddress) As String
Dim addr As Byte() Dim version As Byte Dim checksum(3) As Byte Dim h3(3) As Byte Dim x As Integer
addr = b58decode(strAddress, 25)
If IsNothing(addr) Then 'fail get_bcaddress_version = "" Exit Function End If
Dim lenAddr As Integer = addr.GetLength(0) Dim vh160(lenAddr - 5) As Byte Dim sha As New SHA256Managed()
version = addr(0) checksum = {addr(lenAddr - 4), addr(lenAddr - 3), addr(lenAddr - 2), addr(lenAddr - 1)}
For x = 0 To vh160.GetLength(0) - 1 vh160(x) = addr(x) Next
Dim Hash() As Byte = sha.ComputeHash(vh160) Dim SecondHash() As Byte = sha.ComputeHash(Hash)
h3 = {SecondHash(0), SecondHash(1), SecondHash(2), SecondHash(3)}
If h3(0) = checksum(0) And h3(1) = checksum(1) And h3(2) = checksum(2) And h3(3) = checksum(3) Then get_bcaddress_version = version.ToString Else 'fail get_bcaddress_version = "" End If
End Function
Function b58encode(b() As Byte) As String
Dim b58chars As String = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" Dim b58base As BigInteger = 58 Dim x As BigInteger Dim long_value As BigInteger = 0 Dim result As String = "" Dim iMod As BigInteger = 0 Dim iDiv As BigInteger = 0 Dim b256base As BigInteger = 256
Dim c As Integer Dim lv As Integer
lv = b.GetLength(0) - 1 c = 0 For x = lv To 0 Step -1 long_value = long_value + BigInteger.Pow(b256base, x) * b(c) c = c + 1 Next
Do While long_value >= b58base iMod = long_value Mod b58base iDiv = long_value / b58base
result = b58chars(iMod) & result long_value = iDiv Loop result = b58chars(long_value) & result
For x = 0 To lv If b(x) = CByte(0) Then result = b58chars(0) & result Else Exit For End If Next
Return result End Function
Function insertat(b As Byte, bArr As Byte(), pos As Integer) As Byte()
If IsNothing(bArr) Then 'empty array; return single celled array Dim tmpsomeOtherBarr(0) As Byte tmpsomeOtherBarr(0) = b insertat = tmpsomeOtherBarr Exit Function End If
Dim x As Integer Dim tmpbArr(bArr.GetLength(0)) As Byte
If pos = -1 Then 'insert at end of array For x = 0 To bArr.GetLength(0) - 1 tmpbArr(x) = bArr(x) Next tmpbArr(bArr.GetLength(0)) = b
Return tmpbArr ElseIf pos = 0 Then 'insert at beginning tmpbArr(0) = b For x = 1 To bArr.GetLength(0) tmpbArr(x) = bArr(x - 1) Next Return tmpbArr Else 'insert in the middle For x = 0 To pos - 1 tmpbArr(x) = bArr(x) Next tmpbArr(pos) = b For x = pos + 1 To bArr.GetLength(0) tmpbArr(x) = bArr(x) Next Return tmpbArr
End If End Function
Function b58decode(v As String, l As Integer) As Byte()
Dim b58chars As String = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" Dim b58base As BigInteger = 58 Dim long_value As BigInteger = 0 Dim lv As Integer Dim c As Integer Dim x As BigInteger Dim biPos As BigInteger = 0 Dim iMod As BigInteger = 0 Dim iDiv As BigInteger = 0 Dim result() As Byte
lv = Len(v) - 1
For x = lv To 0 Step -1 c = c + 1 biPos = b58chars.IndexOf(Mid(v, c, 1)) long_value = long_value + BigInteger.Pow(b58base, x) * biPos Next
Do While long_value >= 256 iMod = long_value Mod 256 iDiv = long_value / 256 result = insertat(CByte(iMod), result, 0) long_value = iDiv Loop result = insertat(CByte(long_value), result, 0)
For x = 1 To Len(v) If Mid(v, x, 1) = b58chars(0) Then result = insertat(CByte(0), result, 0) Else Exit For End If Next
If l > 0 And result.GetLength(0) <> l Then Return Nothing Else Return result End If
End Function
End Module
|
1YogAFA... (oh, nevermind)
|
|
|
Xenland
Legendary
Offline
Activity: 980
Merit: 1003
I'm not just any shaman, I'm a Sha256man
|
|
November 17, 2012, 10:03:13 AM |
|
|
|
|
|
matmar10
Newbie
Offline
Activity: 47
Merit: 0
|
|
November 27, 2012, 05:18:21 PM |
|
For those interested, I rewrote base58_encode/base58_decode in PHP, and added a "decode_btc" function (that will return an array containing "version" and "hash" for a given bitcoin address). This code requires the following php extensions: - gmp (for bignum values, we could use bc, or even the php-native implementation in pear, I'm just too lazy and I got gmp installed on all my servers anyway)
- hash (or mhash, someone could insert a php-native sha256 implementation to fallback to)
Here's the code, largely inspired from the code of the previous authors of this thread: <?php $btc = decode_btc('1Eym7pyJcaambv8FG4ZoU8A4xsiL9us2zz'); var_dump($btc); var_dump(encode_btc($btc)); $btc = decode_btc('1111111111111111111114oLvT2'); var_dump($btc); var_dump(encode_btc($btc));
function hash_sha256($string) { if (function_exists('hash')) return hash('sha256', $string, true); if (function_exists('mhash')) return mhash(MHASH_SHA256, $string); // insert native php implementation of sha256 here throw new Exception('Too lazy to fallback when the guy who configured php was lazy too'); }
function encode_btc($btc) { $btc = chr($btc['version']).pack('H*', $btc['hash']); if (strlen($btc) != 21) return false; $cksum = substr(hash_sha256(hash_sha256($btc)), 0, 4); return base58_encode($btc.$cksum); }
function decode_btc($btc) { $btc = base58_decode($btc); if (strlen($btc) != 25) return false; // invalid $version = ord($btc[0]); $cksum = substr($btc, -4); // checksum is double sha256 (take 4 first bytes of result) $good_cksum = substr(hash_sha256(hash_sha256(substr($btc, 0, -4))), 0, 4); if ($cksum != $good_cksum) return false; return array('version' => $version, 'hash' => bin2hex(substr($btc, 1, 20))); }
function base58_encode($string) { $table = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
$long_value = gmp_init(bin2hex($string), 16);
$result = ''; while(gmp_cmp($long_value, 58) > 0) { list($long_value, $mod) = gmp_div_qr($long_value, 58); $result .= $table[gmp_intval($mod)]; } $result .= $table[gmp_intval($long_value)];
for($nPad = 0; $string[$nPad] == "\0"; ++$nPad);
return str_repeat($table[0], $nPad).strrev($result); }
function base58_decode($string) { $table = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; static $table_rev = null; if (is_null($table_rev)) { $table_rev = array(); for($i=0;$i<58;++$i) $table_rev[$table[$i]]=$i; }
$l = strlen($string); $long_value = gmp_init('0'); for($i=0;$i<$l;++$i) { $c=$string[$l-$i-1]; $long_value = gmp_add($long_value, gmp_mul($table_rev[$c], gmp_pow(58, $i))); }
// php is lacking binary output for gmp $res = pack('H*', gmp_strval($long_value, 16));
for($nPad = 0; $string[$nPad] == $table[0]; ++$nPad); return str_repeat("\0", $nPad).$res; }
This outputs: array(2) { ["version"]=> int(0) ["hash"]=> string(40) "9955c1b44fa66688d27aaa06ba4ad02c6dd91088" } string(34) "1Eym7pyJcaambv8FG4ZoU8A4xsiL9us2zz" array(2) { ["version"]=> int(0) ["hash"]=> string(40) "0000000000000000000000000000000000000000" } string(27) "1111111111111111111114oLvT2" Thanks for sharing this source! I've used your implementation, but I noticed some addresses don't validate. For example: 12HzMcHURwmAxAkfWgtktYsF3vRTkBz4F3 Any thoughts on why?
|
|
|
|
grondilu
Legendary
Offline
Activity: 1288
Merit: 1076
|
|
November 27, 2012, 07:23:50 PM |
|
Has anyone written a test-suite for key generation-validation?
I mean, a text/json/whatever file where you'd have a couple of address/private key pairs?
And in all possible formats (test network, compressed/uncompressed and so on...).
That would be useful.
|
|
|
|
jgarzik
Legendary
Offline
Activity: 1596
Merit: 1091
|
|
November 27, 2012, 08:00:11 PM |
|
Has anyone written a test-suite for key generation-validation?
I mean, a text/json/whatever file where you'd have a couple of address/private key pairs?
And in all possible formats (test network, compressed/uncompressed and so on...).
That would be useful.
There is some JSON test data at https://github.com/bitcoin/bitcoin/tree/master/src/test/dataI'm using this for a C-based "libccoin" bitcoin library testing https://github.com/jgarzik/picocoin/
|
Jeff Garzik, Bloq CEO, former bitcoin core dev team; opinions are my own. Visit bloq.com / metronome.io Donations / tip jar: 1BrufViLKnSWtuWGkryPsKsxonV2NQ7Tcj
|
|
|
|
grondilu
Legendary
Offline
Activity: 1288
Merit: 1076
|
|
November 28, 2012, 01:05:54 AM |
|
|
|
|
|
matmar10
Newbie
Offline
Activity: 47
Merit: 0
|
|
November 28, 2012, 09:02:58 AM |
|
Thanks for this contribution! I ran the test fixtures for valid and invalid public keys listed below, with a few of my own known keys added; I got the following result: There was 1 failure: 1) Lmh\BitcoinTransferBundle\Tests\BitcoinAddressValidatorTest::testInvalid Test invalid public key '37qgekLpCCHrQuSjvX3fs496FWTGsHFHizjJAs6NPcR47aefnnCWECAhHV6E3g4YN7u7Yuwod5Y' returns 1 error. Failed asserting that 0 matches expected 1. /Users/matmar10/Projects/bitcoinbymobile/src/Lmh/BitcoinTransferBundle/Tests/BitcoinAddressValidatorTest.php:69 FAILURES! Tests: 2, Assertions: 22, Failures: 1. By the way, your PHP code had an undefined variable throwing an error and also an unused variable; I'll send you a git push request.
|
|
|
|
nima
|
|
December 09, 2012, 10:07:13 PM |
|
This bitcoind address validator is a subclass of the Django forms.CharField class, but could easily be adapted to other frameworks or to be standalone code. 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: # # 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. Hi Gavin, Thanks for the code. I used it in a python script to validate a bunch of addresses. One invalid address managed to escape the validation (miwxGypTcHDXT3m4avmrMMC4co7XWqbG9r). Would you please confirm if the address says to be valid when you check it with your code? Thank you
|
|
|
|
cande
Member
Offline
Activity: 107
Merit: 10
https://bt.cx
|
|
February 17, 2013, 10:23:46 AM Last edit: February 18, 2013, 09:08:17 AM by cande |
|
Thanks for this contribution! I ran the test fixtures for valid and invalid public keys listed below, with a few of my own known keys added; I got the following result: There was 1 failure: 1) Lmh\BitcoinTransferBundle\Tests\BitcoinAddressValidatorTest::testInvalid Test invalid public key '37qgekLpCCHrQuSjvX3fs496FWTGsHFHizjJAs6NPcR47aefnnCWECAhHV6E3g4YN7u7Yuwod5Y' returns 1 error. Failed asserting that 0 matches expected 1. /Users/matmar10/Projects/bitcoinbymobile/src/Lmh/BitcoinTransferBundle/Tests/BitcoinAddressValidatorTest.php:69 FAILURES! Tests: 2, Assertions: 22, Failures: 1. By the way, your PHP code had an undefined variable throwing an error and also an unused variable; I'll send you a git push request. Hi, using the PHP code I can see that there has been no update on that gitrepo for the past 8 months, what correction did you find? can you post it here? false negative address I found: 13saQdsv2XbkL9ef9FFT92m2zDaBGKwVGM
|
|
|
|
Rotsor
Full Member
Offline
Activity: 309
Merit: 102
Presale is live!
|
|
May 03, 2013, 06:10:52 PM |
|
Haskell translation: module Main where
import Data.List import Data.Maybe import Data.Word import Data.Tuple import Control.Arrow import Control.Monad import qualified Crypto.Hash.SHA256 as SHA256 import qualified Data.ByteString as B
fromBase :: Integral a => Integer -> [a] -> Integer fromBase b = foldl (\a c -> fromIntegral c + a * b) 0
toBase :: Num a => Integer -> Integer -> [a] toBase b = reverse . unfoldr f where f 0 = Nothing f n = Just $ first fromInteger $ swap $ n `divMod` b
b58chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
b58encode :: [Word8] -> [Char] b58encode l = replicate (length $ takeWhile(==0) l) '1' ++ (map (b58chars!!) . toBase 58 . fromBase 256) l
fromb58char c = fst . fromJust . find ((==c) . snd) $ zip [0..] b58chars
b58decode v = case span (== b58chars !! 0) v of (ones, v) -> replicate (length ones) 0 ++ (toBase 256 . fromBase 58 . map fromb58char) v
b58decodeL l v = let res = b58decode v in guard (length res == l) >> return res
sha256x2 = B.unpack . SHA256.hash . SHA256.hash . B.pack where
checksum = take 4 . sha256x2
mkAddress :: Word8 -> [Word8] -> [Char] mkAddress v d = b58encode $ v : d ++ checksum (v : d)
validateAddress :: [Char] -> Maybe (Word8, [Word8]) validateAddress addr = do (version : d) <- b58decodeL 25 addr case splitAt 20 d of (d, check) -> do guard $ checksum (version : d) == check return (version, d)
The code is under WTFPL.
|
|
|
|
grondilu
Legendary
Offline
Activity: 1288
Merit: 1076
|
|
May 03, 2013, 06:15:42 PM |
|
BTW did I tell you guys that there is a rosetta code entry for bitcoin address validation?
|
|
|
|
Viceroy
|
|
May 07, 2013, 05:05:44 PM Last edit: May 09, 2013, 09:02:09 PM by Viceroy |
|
I REALLY did want to use your code, grondilu, but it lacks documentation. I tried to use the perl version and the bash version but couldn't get either to work. I'm sure it's a lack of understanding on my part.
Instead I grabbed Xenland's modification of MagicalTux's php script and made a couple changes:
EDIT: THIS CODE GIVES FALSE NEGATIVES SHOWING PERFECTLY GOOD ADDRESSES AS BAD!!!
[code retracted]
working code a few posts down the thread
that way | | | V
|
|
|
|
grondilu
Legendary
Offline
Activity: 1288
Merit: 1076
|
|
May 07, 2013, 10:22:33 PM |
|
I REALLY did want to use your code, grondilu, but it lacks documentation. I tried to use the perl version and the bash version but couldn't get either to work. I'm sure it's a lack of understanding on my part.
Could you report your error message here or on the rosettacode talk page please?
|
|
|
|
Viceroy
|
|
May 08, 2013, 11:25:37 PM |
|
I REALLY did want to use your code, grondilu, but it lacks documentation. I tried to use the perl version and the bash version but couldn't get either to work. I'm sure it's a lack of understanding on my part.
Could you report your error message here or on the rosettacode talk page please? lol, it would look like this: I'm too dumb to figure out how to use a bash script and I forget what issue it was that caused me to not want to use the perl, oh yea... I had no idea how to"feed" the address into the program. So I gave up. No reason to noise up your page with that. now I'm looking at http://blockexplorer.com/q/checkaddress as I found the above php useless.... it just gives false negatives. why is there no simple tool like http://blockexplorer.com/q/checkaddressoh, wait, maybe there is over at http://blockexplorer.com/q/checkaddress.. I'll report back (unless I just get lost in smoke and code). Oh, I should probably start with my objective.... but finishing with it will suffice: I am trying to figure out how to check bitcoin addresses and make bitcoin addresses and valid data in an online php form.
|
|
|
|
theymos
Administrator
Legendary
Offline
Activity: 5194
Merit: 12983
|
|
May 08, 2013, 11:48:35 PM |
|
/q/checkaddress uses this plus some extra checking. Note that the checkAddress function assumes that the input is valid base58. Do something like: if(preg_match('/^[1-9A-HJ-NP-Za-km-z]+$/', $address) && strlen($address) <= 34 && checkAddress($address)) address is valid
|
1NXYoJ5xU91Jp83XfVMHwwTUyZFK64BoAD
|
|
|
Viceroy
|
|
May 09, 2013, 12:24:48 AM Last edit: May 09, 2013, 09:01:49 PM by Viceroy |
|
thanks!
EDIT
working code:
Shows Good Addresses as bad.
[retracted code]
working code a few posts down the thread
that way | | | V
|
|
|
|
jackjack
Legendary
Offline
Activity: 1176
Merit: 1233
May Bitcoin be touched by his Noodly Appendage
|
|
May 09, 2013, 02:21:08 PM |
|
Did you try pywallet's code? Look for DecodeBase58Check or something like that
|
Own address: 19QkqAza7BHFTuoz9N8UQkryP4E9jHo4N3 - Pywallet support: 1AQDfx22pKGgXnUZFL1e4UKos3QqvRzNh5 - Bitcointalk++ script support: 1Pxeccscj1ygseTdSV1qUqQCanp2B2NMM2 Pywallet: instructions. Encrypted wallet support, export/import keys/addresses, backup wallets, export/import CSV data from/into wallet, merge wallets, delete/import addresses and transactions, recover altcoins sent to bitcoin addresses, sign/verify messages and files with Bitcoin addresses, recover deleted wallets, etc.
|
|
|
Viceroy
|
|
May 09, 2013, 02:21:19 PM Last edit: May 09, 2013, 02:37:06 PM by Viceroy |
|
I'm not getting the same results I see from http://blockexplorer.com/q/checkaddress/ with the above php code. Checking further... Are there any commercial use restrictions on grabbing data from blockexplore.com? ps. got a link for that, jack?
|
|
|
|
jackjack
Legendary
Offline
Activity: 1176
Merit: 1233
May Bitcoin be touched by his Noodly Appendage
|
|
May 09, 2013, 02:36:15 PM |
|
|
Own address: 19QkqAza7BHFTuoz9N8UQkryP4E9jHo4N3 - Pywallet support: 1AQDfx22pKGgXnUZFL1e4UKos3QqvRzNh5 - Bitcointalk++ script support: 1Pxeccscj1ygseTdSV1qUqQCanp2B2NMM2 Pywallet: instructions. Encrypted wallet support, export/import keys/addresses, backup wallets, export/import CSV data from/into wallet, merge wallets, delete/import addresses and transactions, recover altcoins sent to bitcoin addresses, sign/verify messages and files with Bitcoin addresses, recover deleted wallets, etc.
|
|
|
Viceroy
|
|
May 09, 2013, 02:37:18 PM Last edit: May 09, 2013, 02:56:31 PM by Viceroy |
|
thank you thank you. dang dude, I can't dive into a 1700 line program right now. what's that do with all those lines? fwiw my code is down to 7 lines, and two of those are comments. <?php // use with validator.php?refund=btc-address-here $validateBTCURL = 'http://blockexplorer.com/q/checkaddress/' . $_GET["refund"]; //echo $validateBTCURL; $validBTC = file_get_contents($validateBTCURL); //echo $validBTC; if($validBTC == "00"){ //do something } else { //do something else } ?>
|
|
|
|
jackjack
Legendary
Offline
Activity: 1176
Merit: 1233
May Bitcoin be touched by his Noodly Appendage
|
|
May 09, 2013, 03:45:08 PM |
|
That's why I told you to look for the definition of DecodeBase58Check in the code, that's the function to decode address to hash160
|
Own address: 19QkqAza7BHFTuoz9N8UQkryP4E9jHo4N3 - Pywallet support: 1AQDfx22pKGgXnUZFL1e4UKos3QqvRzNh5 - Bitcointalk++ script support: 1Pxeccscj1ygseTdSV1qUqQCanp2B2NMM2 Pywallet: instructions. Encrypted wallet support, export/import keys/addresses, backup wallets, export/import CSV data from/into wallet, merge wallets, delete/import addresses and transactions, recover altcoins sent to bitcoin addresses, sign/verify messages and files with Bitcoin addresses, recover deleted wallets, etc.
|
|
|
theymos
Administrator
Legendary
Offline
Activity: 5194
Merit: 12983
|
|
May 09, 2013, 08:15:36 PM |
|
In what cases? It should be identical other than /q/checkaddress's additional info about why the address failed.
|
1NXYoJ5xU91Jp83XfVMHwwTUyZFK64BoAD
|
|
|
Viceroy
|
|
May 09, 2013, 08:57:53 PM |
|
after further testing it appears to be working as expected. Here's the working code: called with: http://yourdomain/btcvalidate.php?address="btc-address-here" btcvalidate.php: <?php include("base58.php"); ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <!--- - - - - - - - - - - - - - - - - - - - - - ---> <!--- name this file btcvalidate.php on webserver ---> <!--- ---> <!--- place base58.php in same directory ---> <!--- ---> <!--- call with btc address as get function: ---> <!--- btcvalidate.php?address="btc-address-here" ---> <!--- ---> <!--- warning: no checking on input ---> <!--- - - - - - - - - - - - - - - - - - - - - - ---> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>btc address validator</title> </head> <body> <?php $address = $_GET["address"]; if(preg_match('/^[1-9A-HJ-NP-Za-km-z]+$/', $address) && strlen($address) <= 34 && checkAddress($address)){ echo "<p>address is valid</p>"; } else { echo "<p>address is NOT valid</p>"; } ?> </body> </html>
base58.php: <?php /* <!--- - - - - - - - - - - - - - - - - - - - - - ---> <!--- name this file base58.php on webserver ---> <!--- - - - - - - - - - - - - - - - - - - - - - ---> */ //hex input must be in uppercase, with no leading 0x define("ADDRESSVERSION","00"); //this is a hex byte
function decodeHex($hex){ $hex=strtoupper($hex); $chars="0123456789ABCDEF"; $return="0"; for($i=0;$i<strlen($hex);$i++){ $current=(string)strpos($chars,$hex[$i]); $return=(string)bcmul($return,"16",0); $return=(string)bcadd($return,$current,0); } return $return; }
function encodeHex($dec){ $chars="0123456789ABCDEF"; $return=""; while (bccomp($dec,0)==1){ $dv=(string)bcdiv($dec,"16",0); $rem=(integer)bcmod($dec,"16"); $dec=$dv; $return=$return.$chars[$rem]; } return strrev($return); }
function decodeBase58($base58){ $origbase58=$base58; $chars="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; $return="0"; for($i=0;$i<strlen($base58);$i++){ $current=(string)strpos($chars,$base58[$i]); $return=(string)bcmul($return,"58",0); $return=(string)bcadd($return,$current,0); } $return=encodeHex($return); //leading zeros for($i=0;$i<strlen($origbase58)&&$origbase58[$i]=="1";$i++){ $return="00".$return; } if(strlen($return)%2!=0){ $return="0".$return; } return $return; }
function encodeBase58($hex){ if(strlen($hex)%2!=0){ die("encodeBase58: uneven number of hex characters"); } $orighex=$hex; $chars="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; $hex=decodeHex($hex); $return=""; while (bccomp($hex,0)==1){ $dv=(string)bcdiv($hex,"58",0); $rem=(integer)bcmod($hex,"58"); $hex=$dv; $return=$return.$chars[$rem]; } $return=strrev($return); //leading zeros for($i=0;$i<strlen($orighex)&&substr($orighex,$i,2)=="00";$i+=2){ $return="1".$return; } return $return; }
function hash160ToAddress($hash160,$addressversion=ADDRESSVERSION){ $hash160=$addressversion.$hash160; $check=pack("H*" , $hash160); $check=hash("sha256",hash("sha256",$check,true)); $check=substr($check,0,8); $hash160=strtoupper($hash160.$check); return encodeBase58($hash160); }
function addressToHash160($addr){ $addr=decodeBase58($addr); $addr=substr($addr,2,strlen($addr)-10); return $addr; }
function checkAddress($addr,$addressversion=ADDRESSVERSION){ $addr=decodeBase58($addr); if(strlen($addr)!=50){ return false; } $version=substr($addr,0,2); if(hexdec($version)>hexdec($addressversion)){ return false; } $check=substr($addr,0,strlen($addr)-8); $check=pack("H*" , $check); $check=strtoupper(hash("sha256",hash("sha256",$check,true))); $check=substr($check,0,8); return $check==substr($addr,strlen($addr)-8); }
function hash160($data){ $data=pack("H*" , $data); return strtoupper(hash("ripemd160",hash("sha256",$data,true))); }
function pubKeyToAddress($pubkey){ return hash160ToAddress(hash160($pubkey)); }
function remove0x($string){ if(substr($string,0,2)=="0x"||substr($string,0,2)=="0X"){ $string=substr($string,2); } return $string; }
?>
|
|
|
|
Eisenhower34
Legendary
Offline
Activity: 906
Merit: 1002
|
|
May 27, 2013, 12:16:27 PM |
|
The code mentioned in the original post returns true for BTC and LTC addresses, because get_bcaddress_version(address)
returns 0 for BTC addresses and 48 for LTC addresses, so both is not None. I wouldnt call this a bug, more a feature that allowes to check other coin addresses.
|
|
|
|
Melbustus
Legendary
Offline
Activity: 1722
Merit: 1003
|
|
July 08, 2013, 08:29:18 AM |
|
/q/checkaddress uses this plus some extra checking. Note that the checkAddress function assumes that the input is valid base58. Do something like: if(preg_match('/^[1-9A-HJ-NP-Za-km-z]+$/', $address) && strlen($address) <= 34 && checkAddress($address)) address is valid theymos - that pastebin-linked code is yours, right, from: http://code.gogulski.com/bitcoin-php/Just want to make sure the public domain license applies before I integrate. Thanks!
|
Bitcoin is the first monetary system to credibly offer perfect information to all economic participants.
|
|
|
theymos
Administrator
Legendary
Offline
Activity: 5194
Merit: 12983
|
|
July 08, 2013, 01:37:29 PM |
|
The pastebin code is mine and public domain. That bitcoin-php library is a separate thing that I wasn't involved with.
|
1NXYoJ5xU91Jp83XfVMHwwTUyZFK64BoAD
|
|
|
Melbustus
Legendary
Offline
Activity: 1722
Merit: 1003
|
|
July 08, 2013, 08:07:42 PM |
|
The pastebin code is mine and public domain. That bitcoin-php library is a separate thing that I wasn't involved with.
Great, thanks. I just glanced at the function sigs and assumed they were by the same author. In any event, I just integrated your code from that pastebin link. That was easy - thanks very much!
|
Bitcoin is the first monetary system to credibly offer perfect information to all economic participants.
|
|
|
super3
Legendary
Offline
Activity: 1094
Merit: 1006
|
|
July 18, 2013, 11:17:25 PM |
|
Is there any standalone Python code floating around out there? This is what I am using right now: def validate_address(self, address): """ Does simple validation of a bitcoin-like address. Source: http://bit.ly/17OhFP5
param : address : an ASCII or unicode string, of a bitcoin address. returns : boolean, indicating that the address has a correct format.
"""
address = self.clean(address) # The first character indicates the "version" of the address. CHARS_OK_FIRST = "123" # alphanumeric characters without : l I O 0 CHARS_OK = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
# We do not check the high length limit of the address. # Usually, it is 35, but nobody knows what could happen in the future. if len(address) < 27: return False elif address[0] not in CHARS_OK_FIRST: return False
# We use the function "all" by passing it an enumerator as parameter. # It does a little optimization : # if one of the character is not valid, the next ones are not tested. return all( ( char in CHARS_OK for char in address[1:] ) )
|
|
|
|
airborne305
Newbie
Offline
Activity: 27
Merit: 0
|
|
January 04, 2014, 10:43:39 PM |
|
This is regardless of whatever language you are trying to use and will require a bitcoind wallet running on your machine. basically take whatever text field that has the string you're trying to validate and turn it into a variable. in your app or web page... textbox = $varAddress
onclick or script... whatever, send this to the wallet. bitcoind validateaddress $varAddressmonitor response from [ isvalid]. if "true," execute code. if false, execute other code. Valid Address: [isvalid] = true [address] = submitted addressserver:~$ bitcoind validateaddress mkZuF2onXAxyGuuvoALbPEesMg99uv6k7r { "isvalid" : true, "address" : "mkZuF2onXAxyGuuvoALbPEesMg99uv6k7r", "ismine" : false } Invalid Address: [isvalid] = falseserver:~$ bitcoind validateaddress mkZuF2onXAxyGuuvoALbPEeIMg99uv6k7r { "isvalid" : false } Invalid Address: [isvalid] = falseserver:~$ bitcoind validateaddress 7kZuF2onXAxyGuuvoALbPEeIMg99uv6k7r { "isvalid" : false }
|
|
|
|
Podey
Newbie
Offline
Activity: 3
Merit: 0
|
|
January 19, 2014, 03:18:20 PM |
|
I wrote an Ada version of the bitcoin address validation code and posted it at rosettacode.org. The example address someone posted in this thread, "miwxGypTcHDXT3m4avmrMMC4co7XWqbG9r," gets reported as invalid, but it also gets reported as invalid by http://blockexplorer.com/q/checkaddress/miwxGypTcHDXT3m4avmrMMC4co7XWqbG9rDoes someone have a quick answer to this puzzle? Ada is a good language to write reliable code, so I think it would be a good idea to have reference code for Ada for key functions like this.
|
|
|
|
Podey
Newbie
Offline
Activity: 3
Merit: 0
|
|
January 20, 2014, 12:34:07 PM |
|
OK, duh, the problem was with the version number. The address' version is 6F, which is bogus, but otherwise it is valid. Code which doesn't care about the version number will report the address as valid.
|
|
|
|
PyTis
Newbie
Offline
Activity: 1
Merit: 0
|
|
February 25, 2014, 11:47:14 PM Last edit: February 26, 2014, 06:34:20 PM by PyTis |
|
Special thanks to Gavin Anderson So I re-worked Gavin's code just a little bit, so I could basically type cast a Bitcoin address. Below is my code. Please let me know if you find any issues. #!/usr/bin/env python """ # Special thanks to Gavin Andresen from bitcointalk.org # in reference to: https://bitcointalk.org/index.php?topic=1026.0 # Edited By/Author Josh Lee PyTis.com, I also release my changes into the # public domain. """
class BTCaddress(str): """ Custom Bitcoin Address Verify-turend into a type for type-casting. """ import re
address_pattern = "[a-zA-Z1-9]{27,35}$" address_expr = re.compile(r"%s"%address_pattern) __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' __b58base = len(__b58chars) _valid = False _version = None
def __new__(cls,val): if val is None: # allow None to be passed in, so that we may clear out bitcoin addresses # if you want, youc an raise an error instead, so that None doesn't pass silently inst = str.__new__(cls, None) inst._valid = False inst._version = None return inst
# I don't know if there could be a valid address with only numbers, but just in-case if isinstance(val, int): val = str(val)
# remove preceading and trailing whitespace if isinstance(val, basestring): val = val.strip() if not val: # again, allow '' to be passed in, so that we may clear out bitcoin addresses # if you want, you can raise an error instead, so that None doesn't pass silently inst = str.__new__(cls, None) inst._valid = False inst._version = None return inst else: if not cls.address_expr.match(val): raise ValueError('Invalid Bitcoin address. Bad RE match r"%s".' % cls.address_pattern) version = cls.getVersion(val) if version is None: raise ValueError('Invalid Bitcoin address. Version Missmatch: %r' % version) else: inst = str.__new__(cls, val) inst._valid = True inst._version = version return inst else: raise TypeError('Invalid Bitcoin address. Not a string.') valid = property(lambda s: s._valid) version = property(lambda s: s._version)
@classmethod def getVersion(cls, strAddress): """ Returns None if strAddress is invalid. Otherwise returns integer version of address. """ from Crypto.Hash import SHA256 addr = cls.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
@classmethod def b58encode(cls, 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 >= cls.__b58base: div, mod = divmod(long_value, cls.__b58base) result = cls.__b58chars[mod] + result long_value = div result = cls.__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 (cls.__b58chars[0]*nPad) + result
@classmethod def b58decode(cls, v, length): """ decode v into a string of len bytes """ long_value = 0L for (i, c) in enumerate(v[::-1]): long_value += cls.__b58chars.find(c) * (cls.__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 == cls.__b58chars[0]: nPad += 1 else: break
result = chr(0)*nPad + result if length is not None and len(result) != length: return None
return result
# - - - - - - - - - - - - - - # END OF Custom Type class. #
# # tests below # I found a page on a bitcoin wiki to explain to me different bitcoin address # versions, I decided to test these, and other things that could go wrong. # I then altered my code above, to get desired results. # # For more information on bitcoin address versions, please follow link: # https://en.bitcoin.it/wiki/List_of_address_prefixes
tests = [ '17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem', # version 0 || TEST 1 u'3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX', # version 5 || TEST 2 'LhK2kQwiaAvhjWY799cZvMyYwnQAcxkarr', # vesrion 48 || TEST 3 'NATX6zEUNfxfvgVwz8qVnnw3hLhhYXhgQn', # version 52 || TEST 4 'mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn', # version 111 || TEST 5 '5Hwgr3u458GLafKBgxtssHSPqJnYoGrSzgQsPwLFhLNYskDPyyA', # version 128 || TEST 6 '92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcc', # version 239 || TEST 7 None, # testing None, No version || TEST 8 ' 1111111111111111111114oLvT2', # test from https://bitcointalk.org/index.php?topic=1026.0 || TEST 9 float(123456789.01), #|| TEST 10 bool(True), # || TEST 11 '', # || TEST 12 '1NnZEPLmBEK2HryY4heeJQhQPB8LLbNiPe' # my bitcoin address :-) || TEST 13 ]
for i, test in enumerate(tests): print '-'*80 print 'typeof test: ', type(test) print 'original value: ', test try: t = BTCaddress(test) except TypeError, e: print "TypeError: %s" % str(e) except ValueError, e: print "ValueError: %s" % str(e) else: print 'test %s' % i, t print 'test %s.type' % i, type(t) print 'test %s.valid' % i, t.valid print 'test %s.version' % i, t.version print 't.getVersion()', t.getVersion(str(t))
print
-- fixed Test output below: -------------------------------------------------------------------------------- typeof test: <type 'str'> original value: 17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem test 0 17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem test 0.type <class '__main__.BTCaddress'> test 0.valid True test 0.version 0 -------------------------------------------------------------------------------- typeof test: <type 'unicode'> original value: 3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX test 1 3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX test 1.type <class '__main__.BTCaddress'> test 1.valid True test 1.version 5 -------------------------------------------------------------------------------- typeof test: <type 'str'> original value: LhK2kQwiaAvhjWY799cZvMyYwnQAcxkarr test 2 LhK2kQwiaAvhjWY799cZvMyYwnQAcxkarr test 2.type <class '__main__.BTCaddress'> test 2.valid True test 2.version 48 -------------------------------------------------------------------------------- typeof test: <type 'str'> original value: NATX6zEUNfxfvgVwz8qVnnw3hLhhYXhgQn test 3 NATX6zEUNfxfvgVwz8qVnnw3hLhhYXhgQn test 3.type <class '__main__.BTCaddress'> test 3.valid True test 3.version 52 -------------------------------------------------------------------------------- typeof test: <type 'str'> original value: mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn test 4 mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn test 4.type <class '__main__.BTCaddress'> test 4.valid True test 4.version 111 -------------------------------------------------------------------------------- typeof test: <type 'str'> original value: 5Hwgr3u458GLafKBgxtssHSPqJnYoGrSzgQsPwLFhLNYskDPyyA ValueError: Invalid Bitcoin address. Bad RE match r"[a-zA-Z1-9]{27,35}$". -------------------------------------------------------------------------------- typeof test: <type 'str'> original value: 92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcc ValueError: Invalid Bitcoin address. Bad RE match r"[a-zA-Z1-9]{27,35}$". -------------------------------------------------------------------------------- typeof test: <type 'NoneType'> original value: None test 7 None test 7.type <class '__main__.BTCaddress'> test 7.valid False test 7.version None -------------------------------------------------------------------------------- typeof test: <type 'str'> original value: 1111111111111111111114oLvT2 test 8 1111111111111111111114oLvT2 test 8.type <class '__main__.BTCaddress'> test 8.valid True test 8.version 0 -------------------------------------------------------------------------------- typeof test: <type 'float'> original value: 123456789.01 TypeError: Invalid Bitcoin address. Not a string. -------------------------------------------------------------------------------- typeof test: <type 'bool'> original value: True ValueError: Invalid Bitcoin address. Bad RE match r"[a-zA-Z1-9]{27,35}$". -------------------------------------------------------------------------------- typeof test: <type 'str'> original value: test 11 None test 11.type <class '__main__.BTCaddress'> test 11.valid False test 11.version None -------------------------------------------------------------------------------- typeof test: <type 'str'> original value: 1NnZEPLmBEK2HryY4heeJQhQPB8LLbNiPe test 12 1NnZEPLmBEK2HryY4heeJQhQPB8LLbNiPe test 12.type <class '__main__.BTCaddress'> test 12.valid True test 12.version 0
-- last fix was just a typo in a code's comment
|
|
|
|
kadoban
Newbie
Offline
Activity: 16
Merit: 0
|
|
March 18, 2014, 11:42:15 AM |
|
A few people mentioned a 27 character bitcoin address, but there's been no mention that 26 character addresses exist. 11111111111111111111ufYVpS is an example. The odds of it actually coming up in practice seem astronomical, but it is still valid (and a couple of the code examples here give a false negative for it).
|
|
|
|
|