15

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")
tim peterson
  • 22,033
  • 50
  • 162
  • 279
  • 2
    This question appears to be off-topic because it is about writing the code for you. – kero Feb 04 '14 at 17:58
  • how else can I ask it? I just gave the Python as an example. Just looking for any insight really... – tim peterson Feb 04 '14 at 17:59
  • 3
    You can try this yourself - and if you are having problems with a specific part, after thorough research, you can ask about that part. If you want something to be done, hire. If you want to do it yourself, try – kero Feb 04 '14 at 18:03
  • 6
    Why are people so toxic? SO is a Q+A site. He's asking a question. Not all code questions require that he waste time reinventing the wheel before he's allowed to ask if anyone is aware of existing solutions. – Citizen Feb 04 '14 at 21:17
  • 2
    So you answered your own question... I just have a quick suggestion. If you are using the blockchain.info or coinbase api, all you have to do to verify the address is to send the money. Then have a try catch, and if it fails then the address is wrong. – nahtnam Feb 05 '14 at 06:12
  • I'm already talking to a live bitcoind, so I just ask the bitcoin daemon to validate the address. – Michael Hampton Aug 23 '18 at 14:47

7 Answers7

14

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);
}

?>
tim peterson
  • 22,033
  • 50
  • 162
  • 279
  • 1
    Excellent answer. I combined it all in a single module. Here it is, wrapped in an anonymous function and minified. It exposes a single global function: "checkAddress" and clocks in at only 5.1k: http://www.julianhaight.com/btcvalid.js – Julian Dec 06 '14 at 05:42
  • @tim-peterson - Looks like there is a problem with the base58_decode you are using. I replaced it with this one:https://github.com/cryptocoinjs/bs58 and it works better. Updated my minified library (removing some unused stuff to, now clocks in at 3.9k). – Julian Dec 10 '14 at 18:47
  • 2
    JS code doesn't work on multisig bitcoin addresses, like 3P37h58Az9sqUv8vDW4c1UGgwB5cPKs8DB which is valid but detected as invalid by JS code. – Ami Dec 02 '15 at 15:14
  • it returns invalid address even if the address is VALID – silent_coder14 Mar 08 '16 at 13:52
  • Doesn't work for multisig address 3LutQT57xPExqBrYPUTpX8q1EJYgBCVC4s – dark knight Jun 15 '16 at 04:48
  • Finally got this working bitcoinjs lib https://cdnjs.cloudflare.com/ajax/libs/bitcoinjs-lib/0.2.0-1/bitcoinjs-min.js – dark knight Jun 15 '16 at 05:19
  • @tim peterson : Above code is not working with address like '3B9HFDhmBVVpCLmkP5A6vUjwMmU73zAxed' – Codebrekers Jun 29 '17 at 07:01
  • The links are dead and in the code provided in the answer the OP has left out the int2bigInt function. This answer is a total mess as a result. – Majid Fouladpour Nov 01 '18 at 08:41
3

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;
}
Julian
  • 2,729
  • 19
  • 30
  • Great stuff @Julian, can you perhaps explain why that Bitcoin address doesn't validate? – tim peterson Dec 10 '14 at 22:49
  • @timperterson I dug down as far as the base58-decode. It was giving the wrong result from that function. I didn't dig further into it since I had a working replacement for it. I note the replacement function I used features a section labeled "deal with leading zeros" and I think the bad address did have leading zeros in it. I posted the working code here, so you can compare how it's b58decode differs if you like: http://www.julianhaight.com/btcvalid.source.js – Julian Dec 11 '14 at 21:12
  • The above code is working for address like '3B9FDFDhmBVVpCLmkP5A6vUjwMmU83zAPed'..? – Codebrekers Jun 29 '17 at 06:59
  • @Codebrekers, other apps also says that address is bad. Were did you get it? Is it supposed to be bad or good? – Julian Jun 29 '17 at 22:21
2

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;
    }
}
e-sushi
  • 12,227
  • 10
  • 35
  • 55
JordanC
  • 4,220
  • 1
  • 20
  • 15
2

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

Web_Developer
  • 1,053
  • 1
  • 17
  • 32
2

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"
Fillipo Sniper
  • 321
  • 6
  • 24
1

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
}
lz96
  • 2,401
  • 1
  • 22
  • 42
0

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)

Oscar Chambers
  • 690
  • 7
  • 19