Search code examples
phphashpublic-key-encryptionbitcoin

Hashing from a public key to a bitcoin address in php


I am trying to follow the instructions required to turn a 65 byte public key into a bitcoin address using php. The instructions are quite explicit. Can anyone help me with the practicality of doing that in php?

Instructions are

1 - Take the corresponding public key generated with it (65 bytes, 1 byte 0x04, 32 bytes corresponding to X coordinate, 32 bytes corresponding to Y coordinate)

0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6

2 - Perform SHA-256 hashing on the public key

600FFE422B4E00731A59557A5CCA46CC183944191006324A447BDB2D98D4B408

3 - Perform RIPEMD-160 hashing on the result of SHA-256

010966776006953D5567439E5E39F86A0D273BEE

4 - Add version byte in front of RIPEMD-160 hash (0x00 for Main Network)

00010966776006953D5567439E5E39F86A0D273BEE

5 - Perform SHA-256 hash on the extended RIPEMD-160 result

445C7A8007A93D8733188288BB320A8FE2DEBD2AE1B47F0F50BC10BAE845C094

6 - Perform SHA-256 hash on the result of the previous SHA-256 hash

D61967F63C7DD183914A4AE452C9F6AD5D462CE3D277798075B107615C1A8A30

7 - Take the first 4 bytes of the second SHA-256 hash. This is the address checksum

D61967F6

8 - Add the 4 checksum bytes from point 7 at the end of extended RIPEMD-160 hash from point 4. This is the 25-byte binary Bitcoin Address.

00010966776006953D5567439E5E39F86A0D273BEED61967F6

9 - Convert the result from a byte string into a base58 string using Base58Check encoding. This is the most commonly used Bitcoin Address format

16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM

My first attempt is

// step 1

$publickey='0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6';
$step1=$publickey;

echo "step1 ".$publickey."<br>";

// step 2

$step2=hash("sha256",$step1);
echo "step2 ".$step2."<br>";

// step 3

$step3=hash('ripemd160',$step2);
echo "step3 ".$step3."<br>";

// step 4

$step4="00".$step3;
echo "step4 ".$step4."<br>";

// step 5

$step5=hash("sha256",$step4);
echo "step5 ".$step5."<br>";

// step 6

$step6=hash("sha256",$step5);
echo "step6 ".$step6."<br>";

// step 7

$checksum=substr($step6,0,8);
echo "step7 ".$checksum."<br>";

// step 8

$step8=$step4.$checksum;
echo "step8 ".$step8."<br>";

//step 9

$step9=base58_encode($step8);
echo "step9 ".$step9."<br><br>";

This fails at the first step. Any help appreciated.

This is the output

step1 0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6
step2 32511e82d56dcea68eb774094e25bab0f8bdd9bc1eca1ceeda38c7a43aceddce
step3 7528c664cdc34c5ce809778eb688d32f89a538c0
step4 007528c664cdc34c5ce809778eb688d32f89a538c0
step5 86e76f4ff0bf0387339ac70a552e0fed615f7def34cc4809df1429e243f6c1fa
step6 b885b7225b370e7ff27ee0afb4f89b52b8675d5dc342d63de3abe7535f86cadb
step7 b885b722
step8 007528c664cdc34c5ce809778eb688d32f89a538c0b885b722
step9 1

Base58 function is

function base58_encode($input)
{
    $alphabet =     '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
    $base_count = strval(strlen($alphabet));
    $encoded = '';
    while (floatval($input) >= floatval($base_count))
    {
        $div = bcdiv($input, $base_count);
        $mod = bcmod($input, $base_count);
        $encoded = substr($alphabet, intval($mod), 1) . $encoded;
        $input = $div;
    }
    if (floatval($input) > 0)
    {
        $encoded = substr($alphabet, intval($input), 1) . $encoded;
    }
    return($encoded);
}

Solution

  • Solution below with thanks to Sammitch for spotting syntax and providing the base conversions.

    <?php
    
    // step 1
    
    $publickey='0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6';
    
    $step1=hexStringToByteString($publickey);
    
    echo "step1 ".$publickey."<br>";
    
    // step 2
    
    $step2=hash("sha256",$step1);
    echo "step2 ".$step2."<br>";
    
    // step 3
    
    $step3=hash('ripemd160',hexStringToByteString($step2));
    echo "step3 ".$step3."<br>";
    
    // step 4
    
    $step4="00".$step3;
    echo "step4 ".$step4."<br>";
    
    // step 5
    
    $step5=hash("sha256",hexStringToByteString($step4));
    echo "step5 ".$step5."<br>";
    
    // step 6
    
    $step6=hash("sha256",hexStringToByteString($step5));
    echo "step6 ".$step6."<br>";
    
    // step 7
    
    $checksum=substr($step6,0,8);
    echo "step7 ".$checksum."<br>";
    
    // step 8
    
    $step8=$step4.$checksum;
    echo "step8 ".$step8."<br>";
    
    // step 9
    // base conversion is from hex to base58 via decimal. 
    // Leading hex zero converts to 1 in base58 but it is dropped
    // in the intermediate decimal stage.  Simply added back manually.
    
    $step9="1".bc_base58_encode(bc_hexdec($step8));
    echo "step9 ".$step9."<br><br>";
    
    ?>
    

    hash requires a byte string not a hex string. hexStringToByteString is

    function hexStringToByteString($hexString){
        $len=strlen($hexString);
    
        $byteString="";
        for ($i=0;$i<$len;$i=$i+2){
            $charnum=hexdec(substr($hexString,$i,2));
            $byteString.=chr($charnum);
        }
    
    return $byteString;
    }
    

    base conversion (thanks to Sammitch - amended to use Bitcoin base58)

    // BCmath version for huge numbers
    function bc_arb_encode($num, $basestr) {
        if( ! function_exists('bcadd') ) {
            Throw new Exception('You need the BCmath extension.');
        }
    
        $base = strlen($basestr);
        $rep = '';
    
        while( true ){
            if( strlen($num) < 2 ) {
                if( intval($num) <= 0 ) {
                    break;
                }
            }
            $rem = bcmod($num, $base);
            $rep = $basestr[intval($rem)] . $rep;
            $num = bcdiv(bcsub($num, $rem), $base);
        }
        return $rep;
    }
    
    function bc_arb_decode($num, $basestr) {
        if( ! function_exists('bcadd') ) {
            Throw new Exception('You need the BCmath extension.');
        }
    
        $base = strlen($basestr);
        $dec = '0';
    
        $num_arr = str_split((string)$num);
        $cnt = strlen($num);
        for($i=0; $i < $cnt; $i++) {
            $pos = strpos($basestr, $num_arr[$i]);
            if( $pos === false ) {
                Throw new Exception(sprintf('Unknown character %s at offset %d', $num_arr[$i], $i));
            }
            $dec = bcadd(bcmul($dec, $base), $pos);
        }
        return $dec;
    }
    
    
    // base 58 alias
    function bc_base58_encode($num) {   
        return bc_arb_encode($num, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
    }
    function bc_base58_decode($num) {
        return bc_arb_decode($num, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
    }
    
    //hexdec with BCmath
    function bc_hexdec($num) {
        return bc_arb_decode(strtolower($num), '0123456789abcdef');
    }
    function bc_dechex($num) {
        return bc_arb_encode($num, '0123456789abcdef');
    }
    

    final output

    step1 0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6
    step2 600ffe422b4e00731a59557a5cca46cc183944191006324a447bdb2d98d4b408
    step3 010966776006953d5567439e5e39f86a0d273bee
    step4 00010966776006953d5567439e5e39f86a0d273bee
    step5 445c7a8007a93d8733188288bb320a8fe2debd2ae1b47f0f50bc10bae845c094
    step6 d61967f63c7dd183914a4ae452c9f6ad5d462ce3d277798075b107615c1a8a30
    step7 d61967f6
    step8 00010966776006953d5567439e5e39f86a0d273beed61967f6
    step9 16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM