Bit-Wasp/bitcoin-php 的簡單使用:建立錢包 + 進行交易

家豬配種專家 發表於 2020-09-15
PHP

Bit-Wasp/bitcoin-php 的簡單使用:

這裡簡單介紹了一下最最最基礎的 Bit-Wasp/bitcoin-php 的用法,如果你對 bitcoin 還很陌生,推薦 【回形針PaperClip】區塊鏈到底是什麼? 瞭解 bitcoin 和 比特幣交易如何防偽?私鑰公鑰地址啥意思?李永樂老師講比特幣(2) 大致瞭解 bitcoin 的交易流程。想進一步瞭解 bitcoin,推薦閱讀 《精通比特幣》

  1. 建立錢包

    不同於中心化的錢包( 支付寶 / 微信 ),建立 Bitcoin 錢包不需要網路,只需要一個足夠隨機的數字 n ( 1 < n < 1.158 * 10^77 -1 ,略小於 2^256 ,關於這個數是否足夠安全,可以看這個視訊:256位加密有多安全?),公鑰和地址都是由這個 n 生成,它被稱作 私鑰

    建立錢包本質上就是找到一個隨機數。

    私鑰和公鑰及地址的關係

    但是你不能用 php 自帶的隨機數生成函式!!!因為 php 自帶的這些隨機數生成函式並不是密碼學安全的隨機數生成器(它是基於 unix時間戳 / mac地址 / etc 生成的數字,並不是真正的隨機數)。
    隨機數生成器

    但是我們可以使用 Bit-Wasp/bitcoin-php 中密碼學安全的隨機數生成器生成一個隨機數來作為我們的錢包:
    在 Laravel Job 中的實現

     <?php
     namespace App\Jobs\Bitcoin;
    
     use Illuminate\Bus\Queueable;
     use Illuminate\Contracts\Queue\ShouldQueue;
     use Illuminate\Foundation\Bus\Dispatchable;
     use Illuminate\Queue\InteractsWithQueue;
     use Illuminate\Queue\SerializesModels;
    
     use App\Models\Bitcoin\Wallet;
    
     use BitWasp\Bitcoin\Address\PayToPubKeyHashAddress;
     use BitWasp\Bitcoin\Bitcoin;
     use BitWasp\Bitcoin\Crypto\Random\Random;
     use BitWasp\Bitcoin\Key\Factory\PrivateKeyFactory;
    
     class GenerateWallet implements ShouldQueue
     {
         use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
         /**
          * Execute the job.
          *
          * @return void
          */
         public function handle()
         {
             $random = new Random();
             $private_key_factory = new PrivateKeyFactory();
             $private_key = $private_key_factory->generateCompressed($random);
             $public_key = $private_key->getPublicKey();
    
             // p2pkh 格式的地址
             $address = new PayToPubKeyHashAddress($public_key->getPubKeyHash());
    
             // 將生成的錢包儲存到資料庫中
             $wallet = new Wallet;
             $wallet->wif = $private_key->toWif($network);
             $wallet->address = $address->getAddress();
    
             $wallet->save();
    
             // 儲存到日誌檔案中
             $log  = "wif: ". $wallet->wif . PHP_EOL .
                     "address: ". $wallet->address . PHP_EOL .
                     "-------------". date('Y-m-d Hs') ."------------" . PHP_EOL;
             file_put_contents('./wallets.log', $log, FILE_APPEND);
         }
     }

    下面就是我們建立的一個新錢包的示例:
    Bit-Wasp/bitcoin-php 的簡單使用:建立錢包 + 交易
    wif 是一種私鑰的格式,也是各個交易所、錢包所通用的私鑰匯入格式,原始的二進位制數主要用於程式內部。

  2. 建立交易

    我們首先來看一下一個已完成的交易有那些內容:
    比特幣交易
    此標準交易的主要組成部分採用顏色編碼:

    • 交易ID(以黃色突出顯示)

    • 描述符和後設資料(藍色花括號在右邊詳細說明)

    • 輸入(粉色區域)

    • 輸出(綠色區域)

      我們要做的就是要離線構造一個如上的資料,在通過第三方或者你自己的結點廣播出去,待區塊確認後,即可視為完成了交易。
      在這裡需要注意以下幾點:

    1. 交易資訊裡面,收款人的資訊在輸出裡面,鎖定指令碼(scriptPubKey)將限制使用人,也就是地址所對的私鑰。

    2. 交易時需要提供上次交易的 id,來證明你對 bitcoin 的所有權

      bitcoin 的地址存在幾種型別,這裡暫不討論,這裡將根據提供的 p2pkh 地址傳送到 p2wpkh 地址
      在 Laravel Job 中的實現

      <?php
      namespace App\Jobs\Bitcoin;
      
      use Illuminate\Bus\Queueable;
      use Illuminate\Contracts\Queue\ShouldQueue;
      use Illuminate\Foundation\Bus\Dispatchable;
      use Illuminate\Queue\InteractsWithQueue;
      use Illuminate\Queue\SerializesModels;
      
      use BitWasp\Bitcoin\Address\PayToPubKeyHashAddress;
      use BitWasp\Bitcoin\Bitcoin;
      use BitWasp\Bitcoin\Key\Factory\PrivateKeyFactory;
      use BitWasp\Bitcoin\Script\Interpreter\InterpreterInterface as I;
      use BitWasp\Bitcoin\Script\ScriptFactory;
      use BitWasp\Bitcoin\Transaction\Factory\Signer;
      use BitWasp\Bitcoin\Transaction\Factory\TxBuilder;
      use BitWasp\Bitcoin\Transaction\OutPoint;
      use BitWasp\Bitcoin\Transaction\TransactionOutput;
      use BitWasp\Buffertools\Buffer;
      use BitWasp\Bitcoin\Transaction\Factory\SignData;
      use BitWasp\Bitcoin\Script\WitnessScript;
      use BitWasp\Bitcoin\Script\P2shScript;
      use BitWasp\Bitcoin\Address\SegwitAddress;
      use BitWasp\Bitcoin\Script\WitnessProgram;
      use BitWasp\Bitcoin\Address\AddressCreator;
      
      use GuzzleHttp\Client;
      use GuzzleHttp\Exception\ClientException;
      
      class Transfer implements ShouldQueue
      {
       use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
      
       /**
        * Execute the job.
        *
        * @return void
        */
       public function handle()
       {
           // 支付錢包的私鑰(wif 格式)
           $wif = 'KxwUMCtXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxEGooz';
           // 支付錢包上一次交易的 id
           $txid = '4c7a031a31fe794e64ef5ca2714bdd9dd10ceae44650bce025952282bdeeda8b';
           // 收款錢包地址(p2pkh 格式)
           $address = '1LRqwmjqLNNybJ3M2zQHiwvyA6ikbQQkyS';
      
           $privKeyFactory = new PrivateKeyFactory;
           $key = $privKeyFactory->fromWif($wif);
      
           $witnessScript = new WitnessScript(
               ScriptFactory::scriptPubKey()->payToPubKeyHash($key->getPubKeyHash())
           );
      
           $dest = new SegwitAddress(
               WitnessProgram::v0(
                   (new AddressCreator())->fromString($address)->getHash()
               )
           );
      
           // UTXO
           $outpoint = new OutPoint(Buffer::hex($txid, 32), 0);
           // 這裡一共將支出 100000 聰(0.00000001 BTC = 1 聰)
           $txOut = new TransactionOutput(100000, $witnessScript);
      
           // 收款人將收到 90000 聰,中間的差將作為礦工的手續費
           $builder = (new TxBuilder())
               ->spendOutPoint($outpoint)
               ->payToAddress(90000, $dest);
      
           // 簽署交易
           $signer = new Signer($builder->get(), Bitcoin::getEcAdapter());
           $input = $signer->input(0, $txOut);
           $input->sign($key);
      
           $signed = $signer->get();
      
           // 需要進行廣播的交易
           $broadcast = $signed->getBaseSerialization()->getHex();
      
           // 我這裡使用 blockchain.info 的介面進行廣播,你可以使用你自己的結點,或者現成的 RPC 介面
           $client = new Client;
           try {
               $response = $client->request('POST', 'https://blockchain.info/pushtx', [
                   'form_params' => [
                       'tx' => $signed->getBaseSerialization()->getHex()
                   ]
               ]);
               var_dump(json_decode($response->getBody(), true));
           } catch (ClientException $e) {
               var_dump($e->getResponse());
           }
       }

      成功進行廣播你將收到:
      成功廣播

      待 10-15 分鐘後,交易被寫入區塊,再等待後續生成 2-3 個區塊,即可視為交易完成。
      你可以在 Electrum 這樣的開源錢包中檢視你收到的錢
      Bit-Wasp/bitcoin-php 的簡單使用:建立錢包 + 交易

    關於手續費
    你完全可以一毛不拔,一聰都不給,但是就沒有礦工願意打包你的交易,你的交易將永遠無法完成。bitcoin 礦工的收入來自打包完成的系統獎勵(到 2024 年就沒有了)和該區塊中的所有交易的手續費,所以礦工總是優先打包手續費多的交易。

本作品採用《CC 協議》,轉載必須註明作者和本文連結