こぶたのラッパ » 11月 2017

Monthly Archives: 11月 2017

ビットコイン ブロックチェーン プログラミング 発表、ニュース

ビットコインのウォレットと、支払い用のプログラムを作ろう

Published by:

ビットコインのウォレットは数多くありますが、ハードウェアウォレットは高いしなあ・・・という方は、とりあえずウォレットアプリを利用するのがおすすめです。
ウォレットアプリだと、スマホが壊れたときや新しいスマホに買い替えたときに大事なビットコインがなくなっちゃうんじゃない?なんて心配になりますが、大丈夫!
すべてのビットコインはブロックチェーン上に保管されていて、そのブロックチェーンは世界中に遍在しています。

ハードウェアウォレットだろうとペーパーウォレットだろうと、もちろんウォレットアプリも同じですが、すべてその中にビットコインはありません!
これらウォレットはブロックチェーンに保管されているビットコインへの鍵のようなもので、仮にウォレットがぶっ壊れてもビットコインが失われたわけではなく、合鍵を使ってアクセスすればいいのです。

合鍵となるのがリカバリーフレーズですが、最初にウォレットを作るときに必ずメモを残すようにしましょう。
また、このリカバリーフレーズさえあればだれでもそのウォレットへアクセスできますので、十分気をつけて保管して下さい。

私が使っているCopayというウォレットアプリでは、12の日本語の単語(ひらがな)でリカバリーフレーズが作れます。
仮に私がCopayでウォレットを1つ作り、そのリカバリーフレーズを人にあげれば、それはそのまま相手のウォレットにもなっちゃいます。
この仕組み、本当に面白いですよね・・・

最近個人的に作っているプログラムでビットコインの支払いを受け付ける必要があったのですが、HDウォレット形式に対応することで毎回支払いのビットコインアドレスを変えることが出来ます。
こうすることでちゃんと支払われたかどうかをプログラムでチェックできるのでおすすめです。
1つのアドレスに全部送ってもらうようにすると、どれが誰のものか分からなくなってしまうんですよね。

下はTypeScript版のコードですが、これで新しい受取用のアドレスを生成することが出来ます。
Wallet.tsの「const mnemonic = ‘リカバリーフレーズ’;」のところに、Copayで生成したウォレットのリカバリーフレーズを入れます。

import bitcoin = require('bitcoinjs-lib');
import bip39 = require('bip39');

const mnemonicToM = (mnemonic, password, network) => {
    const seed = bip39.mnemonicToSeed(mnemonic, password || "")
    const m = bitcoin.HDNode.fromSeedBuffer(seed, bitcoin.networks[network || "bitcoin"])
    return m
}
const mnemonic = 'リカバリーフレーズ';

/**
 *  ビットコインウォレット
 */
export class Wallet {
  /**
   *  支払い用アドレス生成
   */
  public static createPaymentAddress(tx_index: number) {
    const m = mnemonicToM(mnemonic, '', 'bitcoin');  // 秘密鍵
    // console.log(m.derivePath("m/44'/0'/0'").toBase58()); // xpriv...
    // console.log(m.derivePath("m/44'/0'/0'").neutered().toBase58());  // xpub...
    // console.log(m.derivePath("m/44'/0'/0'/0/0").getAddress()); // 1DQ...

    let paymentAddress = m.derivePath("m/44'/0'/0'/0/" + tx_index).getAddress();
    return paymentAddress;
  }
}

次に、受取チェックのプログラム。

import blockexplorer = require('blockchain.info/blockexplorer');
import rp = require('request-promise-native');

import { BitcoinAddress } from "./BitcoinAddress";
import { BitcoinTransaction } from "./BitcoinTransaction";

/**
 *  ビットコイントランザクション
 */
export class Transaction {
  /**
   *  最新のブロック総数を取得
   */
  public static getBlockCount(): Promise<number> {
    let url = 'https://blockchain.info/ja/q/getblockcount';
    return rp(url)
      .then((body: string) => {
        let count = parseInt(body.trim());
        if (isNaN(count)) {
          return -1;
        }
        return count;
      });
  }

  /**
   *  指定のアドレスの情報を取得
   */
  public static getAddress(address): Promise<BitcoinAddress> {
    // 最新のブロック総数を取得
    return Transaction.getBlockCount()
      .then((count: number) => {
        // アドレス情報を取得
        return blockexplorer.getAddress(address)
          .then((result) => {
            let bitcoinAddress = new BitcoinAddress();
            bitcoinAddress.address = address;
            bitcoinAddress.total_received = result.total_received / 100000000;
            bitcoinAddress.last_confirmation = 0;
            bitcoinAddress.txs = [];

            // トランザクションの承認数を確認
            if (result.txs && result.txs.length > 0) {
              for (let i = 0; i < result.txs.length; i++) {
                let tx = result.txs[i];
                let bitcoinTransaction = new BitcoinTransaction();
                bitcoinTransaction.block_height = tx.block_height ? tx.block_height : count;
                bitcoinTransaction.confirmation = Math.max(0, count - tx.block_height);
                bitcoinTransaction.confirmation = !isNaN(bitcoinTransaction.confirmation) ? bitcoinTransaction.confirmation : 0;
                if (bitcoinAddress.last_confirmation == 0) {
                  bitcoinAddress.last_confirmation = bitcoinTransaction.confirmation;
                } else {
                  bitcoinAddress.last_confirmation = Math.min(bitcoinAddress.last_confirmation, bitcoinTransaction.confirmation);
                }
                bitcoinTransaction.btc_amount = 0;
                for (let j = 0; j < tx.out.length; j++) {
                  let out = tx.out[j];
                  if (out.addr == address) {
                    bitcoinTransaction.btc_amount += out.value / 100000000;
                  }
                }
                bitcoinAddress.txs.push(bitcoinTransaction);
              }
            }

            return bitcoinAddress;
          });
      });
  }
}
import { BitcoinTransaction } from "./BitcoinTransaction";

/**
 *  ビットコインアドレス
 */
 export class BitcoinAddress {
   public address: string;
   public total_received: number;
   public last_confirmation: number;
   public txs: BitcoinTransaction[];
 }
/**
 *  ビットコイントランザクション
 */
 export class BitcoinTransaction {
   public block_height: number;
   public confirmation: number;
   public btc_amount: number;
 }

実行するとこんな感じです。

import { BitcoinAddress } from "bitcoin/BitcoinAddress";
import { Transaction } from "bitcoin/Transaction";
import { Wallet } from "bitcoin/Wallet";

// tx_indexは0スタートのインクリメント値
let paymentAddress = Wallet.createPaymentAddress(0);
console.log('paymentAddress', paymentAddress);

Transaction.getAddress(paymentAddress)
  .then((bitcoinAddress: BitcoinAddress) => {
    console.log('bitcoinAddress', bitcoinAddress);
  });

$ npm run wallet_test 

> myprogram@1.0.0 wallet_test /home/xxxxxxxxx/myprogram
> env NODE_PATH=./build node ./build/wallet_test.js

paymentAddress 1rUTG3jWJ3rkEiKFLyvXbMaQcLnqxr49c
bitcoinAddress BitcoinAddress {
  address: '1rUTG3jWJ3rkEiKFLyvXbMaQcLnqxr49c',
  total_received: 0.0104252,
  last_confirmation: 1508,
  txs: 
   [ BitcoinTransaction {
       block_height: 492652,
       confirmation: 1508,
       btc_amount: 0.0104252 } ] }

生成された支払い法のアドレス(上の実行例では1rUTG3jWJ3rkEiKFLyvXbMaQcLnqxr49c)が、Copayで表示される受取用アドレスと一致してればOK。
または、Copayの設定画面で確認できる、ウォレットの拡張公開鍵と、Wallet.tsでコメントアウトしている「// console.log(m.derivePath(“m/44’/0’/0′”).neutered().toBase58()); // xpub…」のコメントを外して実行してみて、その出力結果が一致しているかどうかも確認した方がいいでしょう。

ソースのダウンロードはこちら