Bitcoin address form validation JavaScript and PHP
Asked Answered
R

9

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")
Rollmop answered 4/2, 2014 at 17:57 Comment(6)
This question appears to be off-topic because it is about writing the code for you.Derangement
how else can I ask it? I just gave the Python as an example. Just looking for any insight really...Rollmop
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, tryDerangement
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.Gouge
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.Escalera
I'm already talking to a live bitcoind, so I just ask the bitcoin daemon to validate the address.Genuine
R
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);
}

?>
Rollmop answered 4/2, 2014 at 18:4 Comment(8)
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: julianhaight.com/btcvalid.jsStudding
@tim-peterson - Looks like there is a problem with the base58_decode you are using. I replaced it with this one:github.com/cryptocoinjs/bs58 and it works better. Updated my minified library (removing some unused stuff to, now clocks in at 3.9k).Studding
JS code doesn't work on multisig bitcoin addresses, like 3P37h58Az9sqUv8vDW4c1UGgwB5cPKs8DB which is valid but detected as invalid by JS code.Robson
it returns invalid address even if the address is VALIDGrane
Doesn't work for multisig address 3LutQT57xPExqBrYPUTpX8q1EJYgBCVC4sImpinge
Finally got this working bitcoinjs lib cdnjs.cloudflare.com/ajax/libs/bitcoinjs-lib/0.2.0-1/…Impinge
@tim peterson : Above code is not working with address like '3B9HFDhmBVVpCLmkP5A6vUjwMmU73zAxed'Huambo
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.Coucal
S
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;
}
Studding answered 10/12, 2014 at 19:35 Comment(4)
Great stuff @Julian, can you perhaps explain why that Bitcoin address doesn't validate?Rollmop
@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: julianhaight.com/btcvalid.source.jsStudding
The above code is working for address like '3B9FDFDhmBVVpCLmkP5A6vUjwMmU83zAPed'..?Huambo
@Codebrekers, other apps also says that address is bad. Were did you get it? Is it supposed to be bad or good?Studding
W
1

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;
    }
}
Woodbridge answered 9/3, 2014 at 0:9 Comment(0)
M
1

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

Mantra answered 6/5, 2015 at 11:36 Comment(1)
Link is not working more, can you please provide latest link or code for the sameHuambo
G
1

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"
Gaiety answered 23/8, 2018 at 12:43 Comment(1)
This will crash if the currency is not supported.Billy
N
0

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
}
Nertie answered 18/8, 2017 at 6:27 Comment(0)
M
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)

Manger answered 3/2, 2021 at 8:34 Comment(3)
This answer works for some but cannot validate all wallets as BTC addresses can start with bc (Segwit)Scandinavian
@NicosKaralis Thanks, I've updated my answer accordinglyManger
Hi, is this answer able to check for BTC addresses that start with bc?Fragmentary
A
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

Aftercare answered 16/9, 2022 at 16:52 Comment(0)
B
0

I think it much better to use bitcoinjs library for address validation. This is much more complex than just regex.

import * as bitcoinjs from "bitcoinjs-lib";
const value ="bc1qy5rfa6hlt850fr5l77lcex4n3hxm9ffzc974zt";

const isBitcoin = !!bitcoinjs.address.toOutputScript(value, bitcoinjs.networks.bitcoin);

https://github.com/bitcoinjs/bitcoinjs-lib/issues/1919

Balenciaga answered 9/7 at 19:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.