Question

I've seen a few Bitcoin Address form validation scripts for various languages, but surprisingly can't really find anything for two common web languages, Javascript and PHP.

Here's one for Python, but is there one for PHP and/or JS?

from hashlib import sha256

digits58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

def decode_base58(bc, length):
    n = 0
    for char in bc:
        n = n * 58 + digits58.index(char)
    return n.to_bytes(length, 'big')

def check_bc(bc):
    bcbytes = decode_base58(bc, 25)
    return bcbytes[-4:] == sha256(sha256(bcbytes[:-4]).digest()).digest()[:4]

if __name__ == '__main__':
    bc = '1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i'
    assert check_bc(bc)
    assert not check_bc( bc.replace('N', 'P', 1) )
    assert check_bc('1111111111111111111114oLvT2')
    assert check_bc("17NdbrSGoUotzeGCcMMCqnFkEvLymoou9j")
Was it helpful?

Solution

Here's a JSFiddle: http://jsfiddle.net/timrpeterson/XsCQq/2/

And here's the full code upon which the JSFiddle is based:

<html>
<head>
<script type="text/javascript" src="http://dl.dropboxusercontent.com/u/28441300/BigInt.js"></script> 
<script type="text/javascript" src="http://dl.dropboxusercontent.com/u/28441300/sha256.js"></script> 
</head>
<body>

<div id="text">
</div>

<script type="text/javascript">
var address = "1Eym7pyJcaambv8FG4ZoU8A4xsiL9us2zz";
if (check(address)) {
    document.getElementById('text').innerHTML += "valid";
} else {
    document.getElementById('text').innerHTML += "invalid";
}


function check(address) {
  var decoded = base58_decode(address);     
  if (decoded.length != 25) return false;

  var cksum = decoded.substr(decoded.length - 4); 
  var rest = decoded.substr(0, decoded.length - 4);  

  var good_cksum = hex2a(sha256_digest(hex2a(sha256_digest(rest)))).substr(0, 4);

  if (cksum != good_cksum) return false;
  return true;
}

function base58_decode(string) {
  var table = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
  var table_rev = new Array();

  var i;
  for (i = 0; i < 58; i++) {
    table_rev[table[i]] = int2bigInt(i, 8, 0);
  } 

  var l = string.length;
  var long_value = int2bigInt(0, 1, 0);  

  var num_58 = int2bigInt(58, 8, 0);

  var c;
  for(i = 0; i < l; i++) {
    c = string[l - i - 1];
    long_value = add(long_value, mult(table_rev[c], pow(num_58, i)));
  }

  var hex = bigInt2str(long_value, 16);  

  var str = hex2a(hex);  

  var nPad;
  for (nPad = 0; string[nPad] == table[0]; nPad++);  

  var output = str;
  if (nPad > 0) output = repeat("\0", nPad) + str;

  return output;
}

function hex2a(hex) {
    var str = '';
    for (var i = 0; i < hex.length; i += 2)
        str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
    return str;
}

function a2hex(str) {
    var aHex = "0123456789abcdef";
    var l = str.length;
    var nBuf;
    var strBuf;
    var strOut = "";
    for (var i = 0; i < l; i++) {
      nBuf = str.charCodeAt(i);
      strBuf = aHex[Math.floor(nBuf/16)];
      strBuf += aHex[nBuf % 16];
      strOut += strBuf;
    }
    return strOut;
}

function pow(big, exp) {
    if (exp == 0) return int2bigInt(1, 1, 0);
    var i;
    var newbig = big;
    for (i = 1; i < exp; i++) {
        newbig = mult(newbig, big);
    }

    return newbig;
}

function repeat(s, n){
    var a = [];
    while(a.length < n){
        a.push(s);
    }
    return a.join('');
}
</script>
</body>
</html>

And here is a PHP example (assuming your have PHP BC-Math):

<?php

function checkAddress($address)
{
    $origbase58 = $address;
    $dec = "0";

    for ($i = 0; $i < strlen($address); $i++)
    {
        $dec = bcadd(bcmul($dec,"58",0),strpos("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",substr($address,$i,1)),0);
    }

    $address = "";

    while (bccomp($dec,0) == 1)
    {
        $dv = bcdiv($dec,"16",0);
        $rem = (integer)bcmod($dec,"16");
        $dec = $dv;
        $address = $address.substr("0123456789ABCDEF",$rem,1);
    }

    $address = strrev($address);

    for ($i = 0; $i < strlen($origbase58) && substr($origbase58,$i,1) == "1"; $i++)
    {
        $address = "00".$address;
    }

    if (strlen($address)%2 != 0)
    {
        $address = "0".$address;
    }

    if (strlen($address) != 50)
    {
        return false;
    }

    if (hexdec(substr($address,0,2)) > 0)
    {
        return false;
    }

    return substr(strtoupper(hash("sha256",hash("sha256",pack("H*",substr($address,0,strlen($address)-8)),true))),0,8) == substr($address,strlen($address)-8);
}

?>

OTHER TIPS

Here is a better version of @Tim-Peterson 's answer. It fixes the base58 implementation he was using (which would not validate the address "12EJmB3cMGRNveskzA7g7kxW32gSbo2dHF".

I combined the validation code with all the needed libraries and removed a lot that wasn't needed. It only exposes a single api: "checkAddress". I created a little home-page for it, where you can download the module source or the minified version: http://www.julianhaight.com/javascript.shtml

The corrected base58_decode (from https://github.com/cryptocoinjs/bs58):

// from https://github.com/cryptocoinjs/bs58
// Base58 encoding/decoding
// Originally written by Mike Hearn for BitcoinJ
// Copyright (c) 2011 Google Inc
// Ported to JavaScript by Stefan Thomas
// Merged Buffer refactorings from base58-native by Stephen Pair
// Copyright (c) 2013 BitPay Inc

var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
var ALPHABET_MAP = {}
for(var i = 0; i < ALPHABET.length; i++) {
  ALPHABET_MAP[ALPHABET.charAt(i)] = i
}
var BASE = 58

function base58_decode(string) {
  if (string.length === 0) return []

  var i, j, bytes = [0]
  for (i = 0; i < string.length; i++) {
    var c = string[i]
    if (!(c in ALPHABET_MAP)) throw new Error('Non-base58 character')

    for (j = 0; j < bytes.length; j++) bytes[j] *= BASE
    bytes[0] += ALPHABET_MAP[c]

    var carry = 0
    for (j = 0; j < bytes.length; ++j) {
      bytes[j] += carry

      carry = bytes[j] >> 8
      bytes[j] &= 0xff
    }

    while (carry) {
      bytes.push(carry & 0xff)

      carry >>= 8
    }
  }

  // deal with leading zeros
  for (i = 0; string[i] === '1' && i < string.length - 1; i++) bytes.push(0)

  bytes = bytes.reverse()
  output = '';
  for (i=0; i<bytes.length; i++) {
      output += String.fromCharCode(bytes[i]);
  }
  return output;
}

Bitcoin Address (example: 3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC) is not valid in many the PHP examples. One of the example which works fine especially to the above address is:

Click here to see the PHP Function to validate Bitcoin Address

I wrote a simple PHP library to do this based on the answers above. It can be found at my related github repo:

<?php
class Btc_address_validator {

    /**
     * [validate description]
     * @param  String $address BTC Address string
     * @return Boolean validation result
     */
    public function validate($address)
    {        
        $addr = $this->decode_base58($address);
        if (strlen($addr) != 50)
        {
          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);
    }
    private function encode_hex($dec)
    {
        $hexchars = "0123456789ABCDEF";
        $return = "";
        while (bccomp($dec, 0) == 1)
        {
            $dv = (string) bcdiv($dec, "16", 0);
            $rem = (integer) bcmod($dec, "16");
            $dec = $dv;
            $return = $return . $hexchars[$rem];
        }
        return strrev($return);
   }
    /**
    * Convert a Base58-encoded integer into the equivalent hex string representation
    *
    * @param string $base58
    * @return string
    * @access private
    */
    private function decode_base58($base58)
    {
        $origbase58 = $base58;    
        $base58chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; 
        $return = "0";
        for ($i = 0; $i < strlen($base58); $i++)
        {
          $current = (string) strpos($base58chars, $base58[$i]);
          $return = (string) bcmul($return, "58", 0);
          $return = (string) bcadd($return, $current, 0);
        }
        $return = $this->encode_hex($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;
    }
}

For those using javascript, you can use wallet-address-validator javascript plugin.

<script src="wallet-address-validator.min.js"></script>

// WAValidator is stored in the windows object

networkType - Optional. Use 'prod' (default) to enforce standard address, 'testnet' to enforce testnet address and 'both' to enforce nothing.

var valid = WAValidator.validate('12h7E1q5UUoPgZ1VtcYb57maFF9Cbk4u5X','BTC','both');
if(valid){
    alert('This is a valid address');
} else {
    alert('Address INVALID');
}
// will alert "This is a valid address"

var valid = WAValidator.validate('12h7E1q5UUoPgZ1VtcYb57maFF9Cbk4u5X', 'ETH', 'both');
if(valid){
    alert('This is a valid address');
} else {
    alert('Address INVALID');
}
// will alert "Address INVALID"

Here is a short and modern implementation in Javascript which depends on CryptoJS:

import sha256 from 'crypto-js/sha256'
import CryptoJS from 'crypto-js'

function isBTCAddress (address) {
  if (!/^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(address)) return false
  const bufferLength = 25
  let buffer = new Uint8Array(bufferLength)
  const digits58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
  for (var i = 0; i < address.length; i++) {
    const num = digits58.indexOf(address[i])
    // buffer = buffer * 58 + num
    let carry = 0
    for (var j = bufferLength - 1; j >= 0; --j) {
      // num < 256, so we just add it to last
      const result = buffer[j] * 58 + carry + (j === bufferLength - 1 ? num : 0)
      buffer[j] = result % (1 << 8)
      carry = Math.floor(result / (1 << 8))
    }
  }
  // check whether sha256(sha256(buffer[:-4]))[:4] === buffer[-4:]
  const hashedWords1 = sha256(CryptoJS.lib.WordArray.create(buffer.slice(0, 21)))
  const hashedWords = sha256(hashedWords1).words
  // get buffer[-4:] with big-endian
  const lastWordAddress = new DataView(buffer.slice(-4).buffer).getInt32(0, false)
  const expectedLastWord = hashedWords[0]
  return lastWordAddress === expectedLastWord
}

This is a nice REGEX from this answer that I have made into a function for you. Only works with non-segwit addresses.

function validate_bitcoin_address(btc_address)
 {
     return btc_address.match("^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$") !== null;
 }

 alert(validate_bitcoin_address("16CbQcqDtBak5NzPbmFP1v9Pi4DwP5G4Wn")); //example usage

The regex matches strings that:

  • Starts with 1 or 3
  • Afterwards, 25 to 34 characters of either a-z, A-Z, or 0-9 but not l, I, O and 0

The REGEX excludes characters that are not allow in bitcoin addresses (l, I, O and 0)

This works for each address format (today) https://github.com/Merkeleon/php-cryptocurrency-address-validation.

It needs BC Math, so probably... sudo apt install php-bcmath

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top