Bitcoinのブロックチェーンに任意のデータを刻む

背景

電子の海に己が存在を刻み込むのよ!!

BitcoinのOP_RETURNをつかってブロックチェーンに任意のデータ保存する手法、プログラミング言語ベースで解説されている記事が数個みつけましたが、pubScriptベースで解説されている日本語記事がみつからなかったので書くことにしました。

概要

以下、概要

pubScript

pubScriptについてはこちらを参照してください。

qiita.com

Bitcoin環境の作成

おあつらえ向きのDockerfileがGithubに公開されているのでつかわせてもらいます。

$ git clone https://github.com/kylemanna/docker-bitcoind
$ cd cd docker-bitcoind

bin/btc_initbitcoin.conf として利用されるようになっているので以下のように編集しました。

#!/bin/bash

set -ex

# This shouldn't be in the Dockerfile or containers built from the same image
# will have the same credentials.
if [ ! -e "$HOME/.bitcoin/bitcoin.conf" ]; then
    mkdir -p $HOME/.bitcoin

    echo "Creating bitcoin.conf"

    # Seed a random password for JSON RPC server
    cat <<EOF > $HOME/.bitcoin/bitcoin.conf
testnet=3
rpcallowip=172.17.0.0/16
rpcconnect=127.0.0.1
rpcuser=[user]
rpcpassword=[password]
rpcport=18332
printtoconsole=1
txindex=1
EOF

fi

cat $HOME/.bitcoin/bitcoin.conf

echo "Initialization completed successfully"

testnet=3 を追記してtestnetに接続するようにしています。testnetのrpcportは慣例的に18332を利用するのでこれも変更しました。

dockerイメージをビルドして動かしてみましょう。

$ docker build -t bitcoin .
$ docker run -v $HOME/bitcoin-data:/bitcoin --name=bitcoind-node -d -p 18333:18333 -p 127.0.0.1:18332:18332 bitcoin

18333ポートは他ノードとの通信に利用されるようです。

$HOME/bitcoin-data にはBitcoinのブロックチェーンがダウンロードされるようになっています。2018/2/19 15時現在、testnetのブロックチェーンは15GBほどあります。

以下のコマンドでコンテナにアタッチできます

$ docker exec -i -t bitcoind-node bash

アタッチしたらbitcoin-cliコマンドでブロックの状況を確認してみましょう*1

# root@c4a8538d33c6:~# bitcoin-cli getinfo
{
  "deprecation-warning": "WARNING: getinfo is deprecated and will be fully removed in 0.16. Projects should transition to using getblockchaininfo, getnetworkinfo, and getwalletinfo before upgrading to 0.16",
  "version": 150100,
  "protocolversion": 70015,
  "walletversion": 139900,
  "balance": 1.29998000,
  "blocks": 1283618,
  "timeoffset": 0,
  "connections": 20,
  "proxy": "",
  "difficulty": 953631.231859672,
  "testnet": true,
  "keypoololdest": 1518769536,
  "keypoolsize": 1999,
  "paytxfee": 0.00000000,
  "relayfee": 0.00001000,
  "errors": "Warning: Unknown block versions being mined! It's possible unknown rules are in effect"
}

下準備

早速電子の海に己が存在を刻み込みたいところですが幾つか下準備が必要になります。

まずは手数料の準備です。先程 getinfo によって取得した情報によるとrelayfeeが0.00001000BTC (1000satoshi) となっていますのでこれ以上を用意しておく必要があります。

testnet faucetを探して自分のアドレスに送金しておきましょう。最長でも15~30分くらいで1 confirmされると思います。

さらに1000satoshi以上用意してある場合はお釣りをもらうアドレスも必要ですので作成しておきましょう。

トランザクションの作成

まずはお釣りのトランザクションを作成してしまいます。未使用のトランザクションのIDとお釣りを送る先のアドレスを確認しておきましょう。

# bitcoin-cli listunspent
[
  {
    "txid": "b85859e623a4486f800935ac06814ff879404d18e672322eb5868fd0603677f0",
    "vout": 0,
    "address": "mk1AgTJSJJtaujpEwXMYLGLunRvoWLakXD",
    "account": "",
    "scriptPubKey": "76a91431367d536429a0f8d4e1ccbe0e0086260f83e50388ac",
    "amount": 1.29998000,
    "confirmations": 125,
    "spendable": true,
    "solvable": true,
    "safe": true
  }
]
# bitcoin-cli getaddressesbyaccount ""
[
  "mk1AgTJSJJtaujpEwXMYLGLunRvoWLakXD",
  "mroc2MUN47VWd5YcUYpVvHPYkyCTVxbASJ",
  "n1U1c27rtNGNs5FvBuQ8Eqx4PoHJwX1Y82",
  "n2yqHkko8P9WaevzHrdShp5iwxvJcfH4X3",
  "n3SUf3ePtEzi2MW6DR3JT1ch5waLkZaEta",
  "n45RrVcGXU6ejk4FdTG4vXXyrxkTgVB3Tv",
  "n4YyHACYQCefGf5MoBa8xYDouBnMmiuTgL"
]

1.29998BTCの未使用トランザクションがあったのでこれを使います。このtxidから新しいトランザクションを作成します。アドレスは一番上のものに1.29998BTCあるのでなにもはいっていない2番目のものを使うことにします。

トランザクションの作成には createrawtransaction を使います。送金額はいま持っている1.29998から手数料を引いた1.29997BTCに設定します。

# bitcoin-cli createrawtransaction '
[{"txid": "b85859e623a4486f800935ac06814ff879404d18e672322eb5868fd0603677f0","vout": 0}]
' '
{"mroc2MUN47VWd5YcUYpVvHPYkyCTVxbASJ": 1.29997}
'
0200000001f0773660d08f86b52e3272e6184d4079f84f8106ac3509806f48a423e65958b80000000000ffffffff01c898bf07000000001976a9147bcf88aa523b792fb77cee6f6fc9931189bbfe7f88ac00000000

これでトランザクションが作成されました。細かく中身を見ていきましょう。

02000000          < バージョン
01                < インプットするトランザクションの数
f0773660d08f86b52e3272e6184d4079f84f8106ac3509806f48a423e65958b8
                  < トランザクションID (32bitのリトルエンディアン)
00000000          < Vout
00                < scriptSigの長さ
ffffffff          < シーケンス番号
01                < アウトプットの数
c898bf0700000000  < アウトプットの金額
19                < pubScriptの大きさ
76a9147bcf88aa523b792fb77cee6f6fc9931189bbfe7f88ac
                  < pubScript
                  < (OP_DUP OP_HASH160 7bcf88aa523b792fb77cee6f6fc9931189bbfe7f OP_EQUALVERIFY OP_CHECKSIG)
00000000          < Lock time

ここのトランザクションのアウトプットの数を1増やしてそこにOP_RETURN命令を追加すれば良いでしょう。ということで書き換えたトランザクションが以下のとおりです。

02000000          < バージョン
01                < インプットするトランザクションの数
f0773660d08f86b52e3272e6184d4079f84f8106ac3509806f48a423e65958b8
                  < トランザクションID
00000000          < Vout
00                < scriptSigの長さ
ffffffff          < シーケンス番号
02                < アウトプットの数
c898bf0700000000  < アウトプットの金額(1)
19                < pubScriptの大きさ(1)
76a9147bcf88aa523b792fb77cee6f6fc9931189bbfe7f88ac
                  < pubScript(1)
                  < (OP_DUP OP_HASH160 7bcf88aa523b792fb77cee6f6fc9931189bbfe7f OP_EQUALVERIFY OP_CHECKSIG)
0000000000000000  < アウトプットの金額(2)
1a                < pubScriptの大きさ(2)
6a18e4bfbae3818ce38190e38289e381bde38280e381a0efbc81
                  < pubScript(2)
                  < (OP_RETURN e4bfbae3818ce38190e38289e381bde38280e381a0efbc81)
00000000          < Lock time

補足に(2)と書いてある部分が追記したところです。まずアウトプットの金額は0ですので0を埋めてpubScriptの大きさを1byteで記述します。OP_RETURNのopcodeは0x6aですので、これとデータのサイズを示す1byteと実データのサイズを含めた数字を入れましょう。 今回突っ込むデータは24byteだったのでpubScriptの大きさは0x1aとなりました。

さて、これで大丈夫そうかデコードしてみます。

# bitcoin-cli decoderawtransaction
{
  "txid": "c4fb7e7af51880753fcd2a630d8355a81069d74474a497a76df80aea0d65e5d5",
  "hash": "c4fb7e7af51880753fcd2a630d8355a81069d74474a497a76df80aea0d65e5d5",
  "version": 2,
  "size": 120,
  "vsize": 120,
  "locktime": 0,
  "vin": [
    {
      "txid": "b85859e623a4486f800935ac06814ff879404d18e672322eb5868fd0603677f0",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 1.29997000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 7bcf88aa523b792fb77cee6f6fc9931189bbfe7f OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a9147bcf88aa523b792fb77cee6f6fc9931189bbfe7f88ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mroc2MUN47VWd5YcUYpVvHPYkyCTVxbASJ"
        ]
      }
    },
    {
      "value": 0.00000000,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_RETURN e4bfbae3818ce38190e38289e381bde38280e381a0efbc81",
        "hex": "6a18e4bfbae3818ce38190e38289e381bde38280e381a0efbc81",
        "type": "nulldata"
      }
    }
  ]
}

2つ目のアウトプットもいい感じにデコードされていますね。

トランザクションの送信

このままではトランザクションコードを作成しただけでブロックチェーンネットワークに反映されないので、これを送信していきます。

まずは署名します。

# bitcoin-cli signrawtransaction 0200000001f0773660d08f86b52e3272e6184d4079f84f8106ac3509806f48a423e65958b80000000000ffffffff02c898bf07000000001976a9147bcf88aa523b792fb77cee6f6fc9931189bbfe7f88ac00000000000000001a6a18e4bfbae3818ce38190e38289e381bde38280e381a0efbc8100000000
{
  "hex": "0200000001f0773660d08f86b52e3272e6184d4079f84f8106ac3509806f48a423e65958b80000000000ffffffff02c898bf07000000001976a9147bcf88aa523b792fb77cee6f6fc9931189bbfe7f88ac00000000000000001a6a18e4bfbae3818ce38190e38289e381bde38280e381a0efbc8100000000",
  "complete": true
}

署名済みのhexコードがでてきたのでこれを送信しましょう。

# bitcoin-cli sendrawtransaction 0200000001f0773660d08f86b52e3272e6184d4079f84f8106ac3509806f48a423e65958b80000000000ffffffff02c898bf07000000001976a9147bcf88aa523b792fb77cee6f6fc9931189bbfe7f88ac00000000000000001a6a18e4bfbae3818ce38190e38289e381bde38280e381a0efbc8100000000
fb16f47bc12f490bb243dfff195ef36610ec1996a46584d11fb33393fa7a6b16

最後に表示されているのが送信されたトランザクションのtxidになります。念のため確認してみましょう。

# bitcoin-cli gettransaction fb16f47bc12f490bb243dfff195ef366
{
  "amount": 0.00000000,
  "fee": -0.00001000,
  "confirmations": 1,
  "blockhash": "000000000000016bf4956eb603bd972e0271d41873c59480cce0becc1caa4724",
  "blockindex": 39,
  "blocktime": 1519024661,
  "txid": "fb16f47bc12f490bb243dfff195ef36610ec1996a46584d11fb33393fa7a6b16",
  "walletconflicts": [
  ],
  "time": 1519024410,
  "timereceived": 1519024410,
  "bip125-replaceable": "no",
  "details": [
    {
      "account": "",
      "address": "mroc2MUN47VWd5YcUYpVvHPYkyCTVxbASJ",
      "category": "send",
      "amount": -1.29997000,
      "label": "",
      "vout": 0,
      "fee": -0.00001000,
      "abandoned": false
    },
    {
      "account": "",
      "category": "send",
      "amount": 0.00000000,
      "vout": 1,
      "fee": -0.00001000,
      "abandoned": false
    },
    {
      "account": "",
      "address": "mroc2MUN47VWd5YcUYpVvHPYkyCTVxbASJ",
      "category": "receive",
      "amount": 1.29997000,
      "label": "",
      "vout": 0
    }
  ],
  "hex": "0200000001f0773660d08f86b52e3272e6184d4079f84f8106ac3509806f48a423e65958b8000000006b483045022100d554d09f60df7a338c5cb272e3772fd67eccca032359c1fb77f93e5c584daaca0220132cac750d8314e4ead8fccd3bbc1e15f2e9f4b99c3af18802a7cd206607de1d01210283880535cfb90692bce2a7ec51840691a8b8cd9295983cca3ed2e0c6f77396dfffffffff02c898bf07000000001976a9147bcf88aa523b792fb77cee6f6fc9931189bbfe7f88ac00000000000000001a6a18e4bfbae3818ce38190e38289e381bde38280e381a0efbc8100000000"
}

送信できているみたいなのでconfirmされるまで待ってブロックエクスプローラーを確認してみます。今回のトランザクションはこちらにのっています。

Bitcoin Transaction fb16f47bc12f490bb243dfff195ef36610ec1996a46584d11fb33393fa7a6b16

f:id:gurapomu:20180219162645p:plain

自分の入力したデータがブロックチェーンに残っていることが確認できました!!!

*1:getinfoはもう使えなくなるらしいですね