きっかけ
ちょっと前に bitcoin ディベロッパーの方たちのお話を聞く機会があったのですが、なんだかすっごく楽しそうなんです、あの人達。暗号通貨ってもしかしたら未来なのだろうか、などとも思ったり。なので僕も少しやってみようかな、と思い立ちました。
はじめに
bitcoin プログラミング の勉強をしてみようと思った時に最初に候補に挙がるのが bitcoin-core 。bitcoin ライブラリの総本山みたいなやつですね。Mastering Bitcoin という解説本も出ています(無料版あり)。けれども bitcoin-core は C++ を習熟していない人にとっては敷居が高く、さらにフルノードタイプであるためブロックチェーンデータを全てダウンロード(もうすぐ100G)しなければいけないため、気軽に導入することができない。そこで、ブロックヘッダーしかダウンロードしない SPV(Simplified Payment Verification)型のライブラリか、サーバ側にフルノードデータを置いた Client-Server 型のライブラリを選択することになります。
参考:ビットコインウォレットの開発にはどれがいいか(JSON-RPC,bitcoinjs-lib,bitcore-lib)
ここでは .NET Core のお試しも兼ねて C# の bitcoin ライブラリ、NBitcoin と、サーバに置いたブロックチェーンデータを参照できる QBitNinja 、.NET Core + Ubuntu といった環境でシンプルな送金プログラムを実行してみます。送金プログラムは Programming the Blockchain in C# の ビットコインを支払いに使う にあるものをほぼそのまま使います。
NBitcoin , QbitNinja ,そしてチュートリアルの Programming the Blockchain in C# は Nicolas Dorier さんが書いたもの。Nicolas さんは日本在住。
必要環境
Ubuntu 16.10 (14.04,16,04 でもOK)
.NET Core 1.0.3
あと Visual Studio Code があると便利かも。
ちなみに私は Ubuntu の軽量版 Lubuntu を使ってます。
Ubuntu に .NET Core を インストールする
ここを見て素直に進める。
Install for Ubuntu 14.04, 16.04, 16.10 & Linux Mint 17 (64 bit)
プロジェクトの作成
とりあえずプロジェクト名は bcpg とする。
dotnet new console -o bcpg
cd bcpg
NBitcinとQbitNinja パッケージのインストール
NuGet 的な操作。
dotnet add package NBitcoin
dotnet add package QBitNinja.Client
Code
Program.cs に以下のような感じで書く。Mainnetでやる際は慎重に!私は少額のビットコインを別のウオレットに移してから試しました。
※ コードの内容については Programming the Blockchain in C# の ビットコインを支払いに使う で学習してくださいね。
※トランザクション手数料については Bitcoinにおける手数料の考え方 by Yuki Akiyama
を参照してください。
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NBitcoin;
using NBitcoin.Protocol;
using QBitNinja.Client;
using QBitNinja.Client.Models;
namespace SpendYourCoins
{
class Program
{
static void Main()
{
#region IMPORT PRIVKEY
var bitcoinPrivateKey = new BitcoinSecret("自分のウオレットの秘密鍵");
var network = bitcoinPrivateKey.Network;
#endregion
// ビットコインアドレスの導出
var address = bitcoinPrivateKey.GetAddress();
Console.WriteLine(bitcoinPrivateKey);
Console.WriteLine(network); // TestNet or MainNet
Console.WriteLine(address);
Console.WriteLine();
var client = new QBitNinjaClient(network);
var transactionId = uint256.Parse("使いたいコインが含まれるトランザクションID");
var transactionResponse = client.GetTransaction(transactionId).Result;
Console.WriteLine(transactionResponse.TransactionId);
Console.WriteLine(transactionResponse.Block.Confirmations);
Console.WriteLine();
// トランザクションインプット:送金元情報を組み立てる
var receivedCoins = transactionResponse.ReceivedCoins;
OutPoint outPointToSpend = null;
foreach (var coin in receivedCoins)
{
if (coin.TxOut.ScriptPubKey == bitcoinPrivateKey.ScriptPubKey)
{
outPointToSpend = coin.Outpoint;
}
}
if (outPointToSpend == null)
throw new Exception("TxOut doesn't contain our ScriptPubKey");
Console.WriteLine("We want to spend {0}. outpoint:", outPointToSpend.N + 1);
var transaction = new Transaction();
transaction.Inputs.Add(new TxIn()
{
PrevOut = outPointToSpend
});
var destinationAddress = new BitcoinPubKeyAddress("送金先のビットコインアドレス");
// いくら相手に送るか?
var destinationAmount = new Money((decimal)0.0065, MoneyUnit.BTC);
// 送金手数料 ここでは0.0005BTCとしていますが、市場価格に応じて増減してください。
var minerFee = new Money((decimal)0.0005, MoneyUnit.BTC);
// 入力の総額(入力の全額としなければならない)
var txInAmount = (Money)receivedCoins[(int)outPointToSpend.N].Amount;
// 入力の総額から送金分を引いて自分に戻す分
Money changeBackAmount = txInAmount - destinationAmount - minerFee;
//トランザクションアウトプットの組み立て
//送金先
TxOut destinationTxOut = new TxOut()
{
Value = destinationAmount,
ScriptPubKey = destinationAddress.ScriptPubKey
};
//自分に戻す分
TxOut changeBackTxOut = new TxOut()
{
Value = changeBackAmount,
ScriptPubKey = bitcoinPrivateKey.ScriptPubKey
};
transaction.Outputs.Add(destinationTxOut);
transaction.Outputs.Add(changeBackTxOut); // おつりがゼロならここは不要
var message = "メッセージとかあればここに";
var bytes = Encoding.UTF8.GetBytes(message);
transaction.Outputs.Add(new TxOut()
{
Value = Money.Zero,
ScriptPubKey = TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes)
});
transaction.Inputs[0].ScriptSig = address.ScriptPubKey;
// It is also OK:
transaction.Inputs[0].ScriptSig = bitcoinPrivateKey.ScriptPubKey;
transaction.Sign(bitcoinPrivateKey, false);
Console.WriteLine(transaction.ToString());
BroadcastResponse broadcastResponse = client.Broadcast(transaction).Result;
if (!broadcastResponse.Success)
{
Console.WriteLine(string.Format("ErrorCode: {0}", broadcastResponse.Error.ErrorCode));
Console.WriteLine("Error message: " + broadcastResponse.Error.Reason);
}
else
{
Console.WriteLine("Success! You can check out the hash of the transaciton in any block explorer:");
Console.WriteLine(transaction.GetHash());
}
Console.ReadLine();
}
}
実行
dotnet restore
dotnet run
トランザクションの内容とかが出力されて、最後に
Success! You can check out the hash of the transaciton in any block explorer:
(トランザクションID)
と出てきたら成功!
トランザクションIDをコピーして https://blockchain.info/ で確認!
感想など
まず Windows(Visual Studio)で 試してから .NET Core でやってみたんですが、コードの変更なしであっさり動きました。予想外。.NET Core ってもしかして結構使える?Java の代替になりそうな予感。
あと、この記事を書く際にMainNetで使用済みトランザクションで二重払い(Double Spend)してしまいました。しかし NBitcoin はエラーは返さず。もちろんブロックチェーン上では無効なトランザクションとなっているようなのですが、自分でチェックロジック書かきゃいけないのかな?ここらへんきちんと理解できていません。。。