はじめに
UL Systems Advent Calendar 2018 の15日目です。
みなさん、Bitcoin 持ってますか?儲かってますか?
今年は、仮想通貨取引所で世間を騒がすハッキングのニュースもあったせいか、昨年末の高値からあれよあれよと大暴落。昨年の今頃から比べると5分の1以下の価値に。。。
ただし、暴落はあくまで仮想通貨取引市場で取引されている Bitcoin の価格のお話。 Bitcoinの仕組みやそれをを支える技術はまだまだ可能性があるはず、こんなときだからあえてBitcoinのアプリケーションを作ってみたい!、ということでBitcoinを送金するプログラムを作ってみました。
Bitcoin のアプリを作る場合、使用できるライブラリの候補がいくつかありますが、今回はJavaで手軽に利用できるbitcoinj (https://bitcoinj.github.io/) を使います。
bitcoinjとは
Bitcoin の操作を行うための Javaライブラリです。Bitcoinの入出金に必要な機能が単独で実現できる他、様々な機能がJavaで実装されています。Bitcoinを利用したちょっとした機能を実現するのに最適です。
bitcoinjライブラリの導入
さっそくbitcoinjのライブラリを導入します。
Eclipse や IntelliJなどのお好みのIDEを利用して、Mavenプロジェクトを作成し、pom.xml に以下の依存関係を追加します。
<!-- bitcoinj 本体 -->
<dependency>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-core</artifactId>
<version>0.14.7</version>
<scope>compile</scope>
</dependency>
<!-- Log出力用にlogbackを利用 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
※もちろんgradleでも同様の記載をすれば利用できますのでそこはお好みで。
Walletの生成
それではbitcoinjを使ってBitcoinの入出金を行ってみましょう、といきたいところですがはじめに少々準備をします。
お金を支払おうにも手持ちのお金がなければなりませんので、入金用のアドレスを用意して外部から送金してもらいます。そして、Bitcoinではお金を支払うときに秘密鍵を用いた署名必要になりますので、秘密鍵を用意します。
bitcoinjを用いた入金用アドレスと秘密鍵を用意するプログラムが以下の通りです。
// wallet を作成する
NetworkParameters params = TestNet3Params.get();
Wallet wallet = new Wallet(params);
// 作成したWalletを保存する
wallet.saveToFile(new File("testnet-wallet"));
// 作成したウォレットの情報と入金用アドレスを表示する
System.out.println(wallet);
System.out.println(wallet.currentReceiveAddress());
実行すると指定した名前のファイル(ここでは"testnet-wallet"
)にWalletが保存され、標準出力の最後に入金用のアドレスが表示されます。
こんな感じに。
mv6wpYpH9UGSk1pn5hosok4gc2EVGaftLR
それぞれのコードを見ていくと、
// wallet を作成する
NetworkParameters params = TestNet3Params.get();
Wallet wallet = new Wallet(params); // 新規にウォレット作成
wallet.saveToFile(new File("testnet-wallet"));
NetworkParameters params = TestNet3Params.get()
で接続するBitcoinのネットワークを指定しています。Bitcoinには3つのネットワークが用意されておりそれぞれ以下のような役割を持っています。
ネットワーク | 役割 |
---|---|
MainNet | 本番用ネットワーク。実在するノードに接続し価値を持ったコインのやり取りを行うためのもの。 |
TestNet | テスト用ネットワーク。MainNetと同様に動作する実在するノードに接続し動作の検証を行うもの。 |
RegTest | 開発用。外部のノードには接続されずプライベートな環境で動作するもの。 |
今回はテスト用のネットワークであるTestNetに接続するため TestNet3Params.get()
で得られるパラメータを Wallet
クラスに指定しています。
Walletクラスはインスタンス化した後、 saveToFile()
メソッドを使ってWalletが持つデータ(秘密鍵や入出金トランザクションなど)をファイルに保存しています。
Walletクラスはインスタンス化することにより、bitcoinjによってランダムに秘密鍵が生成されますが、この秘密鍵は再度インスタンス化すると違うものになってしまうので一旦ファイルに保存します。
逆に既にウォレットデータを保持している場合は、 Wallet.loadFromFile(walletFile);
を呼ぶことで過去のウォレットのデータを引き継ぐことが出来ます。
bitcoinj では、 Wallet
クラスが入出金を行う上で重要な役割を担っており、主に以下の機能を備えています。
- 秘密鍵の生成
- アドレスの生成
- 出金トランザクションの生成・承認・ブロードキャスト
- 残高計算
生成したアドレスの取得は以下のコードで実施しています。
System.out.println(wallet.currentReceiveAddress());
wallet.currentReceiveAddress()
を呼ぶことでWalletが持つアドレスを取得することができます。このアドレスは複数作成することができ、wallet.freshReceiveAddress()
メソッドを呼ぶことで新たなアドレスを取得することができます。
入金用アドレスにお金を入れる
先ほどのプログラムの実行で、
mv6wpYpH9UGSk1pn5hosok4gc2EVGaftLR
のようなアドレス文字列が出力されました。この文字列がアドレス文字列になり、外部からウォレットに入金する際に宛先アドレスとして利用します。
このアドレスにお金を入れるためにはFaucetと呼ばれる無料のサービスを利用します。
FaucetはTestnetのコインを指定したアドレスに(小額ですが)送金してくれるサービスです。仮想通貨のアプリケーション開発者はこうして手に入れたテスト用のコインを利用してアプリケーション開発やテストを行っています。
以下のサイトにアクセスして必要な情報とコイン数を入力して送ってもらって下さい。
https://testnet-faucet.mempool.co/
ありがたいサービスではありますが、それぞれのサービスが無限にコインを有しているわけではないので使い終わったら、Faucetに戻すのが礼儀です。
Faucetから送ってもらったトランザクションの確認
ブロックチェーン・エクスプローラというサービスでブロックチェーン上に送金されたか確認します。いくつかブロックチェーン・エクスプローラのサービスがあるのでお好みのサービスを使ってみて下さい。
大抵のサービスでは、トランザクションIDやブロックID、アドレス文字列などを検索のキーとしてトランザクションを特定することができます。
さきほどのFaucetの送金時に画面にFaucetからの送金トランザクションのID(TxID)が 2c5aeaa745dc1eec63feaa280cde92aab50a5c3a34a79db7d70de35ed7a90aed
と表示されていました。こちらをキーにしてブロックチェーン・エクスプローラで検索したトランザクションが以下になります。
https://tchain.btc.com/2c5aeaa745dc1eec63feaa280cde92aab50a5c3a34a79db7d70de35ed7a90aed
ブロックチェーンの受信
さて、Faucetから作成したWalletの入金用アドレス宛の送金がブロードキャストされました。
しかし、まだアプリケーションにブロックチェーンの情報が取り込まれていないので、Faucetから送られたコインを利用することができません。
ブロックチェーンの情報を取り込むためには、 BlockChain
、 BlockStore
および PeerGroup
を利用します。それぞれ以下のような役割です。
クラス | 役割 |
---|---|
BlockStore | ブロックチェーンのデータを保持するデータストアを表すインターフェースです。bitcoinjには、SPVBlockStore、LevelDBBlockStore、MemoryBlockStoreなどがあります。これらのクラスはSPVノードのため全てのデータは保持しておらず、必要なブロックデータのみ保持します。 |
BlockChain | ブロックチェーンを表すクラスで本クラスを通してブロックの追加等が行われます。 |
PeerGroup | Bitcoinのネットワークに接続されたピアの集合を現すクラスです。ブロックチェーンのダウンロードやトランザクションの送信時に利用します。 |
ブロックチェーンのデータをダウンロードしてウォレットの残高を確認するプログラムが以下になります。
NetworkParameters params = TestNet3Params.get();
// ウォレットを読み込む
File walletFile = new File("testnet-wallet");
Wallet wallet = Wallet.loadFromFile(walletFile);
// ブロックチェーンのデータストアの設定をする
BlockStore blockStore = new SPVBlockStore(params, new File("spv-blockstore"));
BlockChain chain = new BlockChain(params, wallet, blockStore);
// Peerからブロックチェーンデータをダウンロードする
PeerGroup peerGroup = new PeerGroup(params, chain);
peerGroup.addWallet(wallet);
peerGroup.addPeerDiscovery(new DnsDiscovery(params));
peerGroup.start();
peerGroup.downloadBlockChain();
// 残高を確認する
System.out.println("残高:" + wallet.getBalance());
// ウォレットを保存する
wallet.saveToFile(new File("testnet-wallet"));
それぞれのコードを見ていくと、
以下のコードでブロックチェーンのデータストアを設定しています。
ここではダウンロードしたデータをファイルに保存する SPVBlockStore
クラスを利用しています。
// ブロックチェーンのデータストアの設定をする
BlockStore blockStore = new SPVBlockStore(params, new File("spv-blockstore"));
BlockChain chain = new BlockChain(params, wallet, blockStore);
peerGroup.downloadBlockChain();
を実施することでブロックチェーンデータをウォレットにダウンロードすることができます。
// Peerからブロックチェーンデータをダウンロードする
PeerGroup peerGroup = new PeerGroup(params, chain);
peerGroup.addWallet(wallet);
peerGroup.addPeerDiscovery(new DnsDiscovery(params));
peerGroup.start();
peerGroup.downloadBlockChain();
受信したブロックの中に先ほどFaucetからの入金トランザクションが含まれており、ダウンロードされることによって、はじめてアプリケーションで利用することができます。
残高を確認するためのコードが wallet.getBalance()
です。
ウォレットを保存しないと次回起動時に残高の情報がなくなってしまうので最後に wallet.saveToFile()
でデータを保存しています。
ウォレットからの出金
ここまでで、作成したウォレットのアドレスに入金が出来るようになったので、いよいよ出金(支払い)を実施します。
bitcoinj では自分のコインを使って出金する方法がいくつか用意されていますが、今回は出金用のヘルパーメソッドを使って出金します。
SendRequest
クラスを使って出金のリクエストを作成し、 wallet.sendCoins()
メソッドで peerGroupを通してブロードキャストします。
※ローレベルのAPIを利用してBitcoinのトランザクション(TxOutやTxIn)を自分で構成してPeerにブロードキャストすることもできますがこちらは割りと上級者向けの用途なので割愛します。
// 指定されたアドレスにコインを送る
SendRequest sendRequest = SendRequest.to(Address.fromBase58(params, "moUPPTfnNp9JRKX82jTwMGfuZCM2PDmsKQ"),Coin.valueOf(100000));
Wallet.SendResult result = wallet.sendCoins(peerGroup, sendRequest); // 出金リクエストに署名しブロードキャストする
result.broadcastComplete.get(); // ブロードキャスト結果を待機する
System.out.println(result.tx);
SendRequest.to()
メソッドに宛先の Address
と送金額を指定して、オブジェクトを生成しています。その結果得られたオブジェクトを wallet.sendCoins()
メソッドで PeerGroup とともに指定すると、署名とブロードキャストが実施されます。
プログラムを実行するとログの最後のほうにブロードキャストをした旨のメッセージが出力されます。
10:59:03.473 [bitcoinj user thread] INFO org.bitcoinj.core.TransactionBroadcast - broadcastTransaction: SEEN_PEERS: TX a97b2bf8dff81d85f23f87291e39165d930a6368b70dfabfe950e8e10209672e seen by 3 peers
10:59:03.474 [bitcoinj user thread] INFO org.bitcoinj.core.TransactionBroadcast - broadcastTransaction: a97b2bf8dff81d85f23f87291e39165d930a6368b70dfabfe950e8e10209672e complete
上記の2行目に出力されている、 a97b2bf8dff81d85f23f87291e39165d930a6368b70dfabfe950e8e10209672e
が出金時のトランザクションを表すIDになります。
このトランザクションのIDをブロックチェーン・エクスプローラで確認するとこんな感じです。
Input がFaucetから送ってもらった0.01BTC です。
Output がプログラムで指定した moUPPTfnNp9JRKX82jTwMGfuZCM2PDmsKQ
に0.001BTC を出金していることがわかります。もう一つのアドレス mpSWo9eS556CJMvYcYSs6B2PPLdjLQgGiR
はWalletが自動で生成したおつり用のアドレスです。
Outputの2つのアドレスに対する出金額を合計してもInputの0.01BTCにはなりませんが、差額は手数料として徴収されたものです。
無事に出金できました。
まとめ
いかがでしたでしょうか。
bitcoinjの利用を通してなんとなくBitcoinを利用したアプリケーションの開発の感触がつかめたのではないでしょうか。
bitcoinjは全てJavaで実装されているので、JVMさえあれば様々なプラットフォーム(例えばスマホ)のアプリケーションを開発することが可能です。また、今回紹介した機能以外にも高レベルのAPIが多数用意されており、乱暴な言い方をすると、Bitcoinのブロックチェーンの構成を理解しなくても単純な入出金を簡単に実現することができます。さらに、マルチシグに対応しているので複数の秘密鍵を用いたセキュアなトランザクションの署名・出金も可能です。
ちょっとだけ難点があるとすれば、Witnessにはまだ正式対応しておらず、2018年現在では別ブランチがあるもののマージされずに残ってます。witnessへの対応が必須要件であるならば、bitcoinjの開発がもう少し進むのを待ったほうがよいです。
本稿がみなさんのBitcoinアプリ製作の一助となることを願っております。
参考サイト
- bitcoinj (https://bitcoinj.github.io/)
- Yet Another Bitcoin Testnet Faucet(https://testnet-faucet.mempool.co/)
- Bitcoin Block Explorer(https://tchain.btc.com)