Pages:
Author

Topic: Python code for validating bitcoin address - page 3. (Read 47500 times)

legendary
Activity: 1652
Merit: 2216
Chief Scientist
October 20, 2010, 11:18:17 AM
#6
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.
administrator
Activity: 5166
Merit: 12850
October 19, 2010, 09:50:10 PM
#5
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/checkaddress
http://theymos.ath.cx:64150/q/addresstohash
http://theymos.ath.cx:64150/q/hashtoaddress
http://theymos.ath.cx:64150/q/hashpubkey
(Source not public yet.)
legendary
Activity: 1652
Merit: 2216
Chief Scientist
October 19, 2010, 08:43:36 PM
#4
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.
sr. member
Activity: 416
Merit: 277
October 19, 2010, 07:40:42 PM
#3
 (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
legendary
Activity: 1372
Merit: 1007
1davout
October 19, 2010, 06:55:32 PM
#2
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 ?
legendary
Activity: 1652
Merit: 2216
Chief Scientist
September 13, 2010, 08:38:24 AM
#1
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:
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.
Pages:
Jump to: