秘密鍵の保存方法について

パスワードのような他人に知られてはまずい情報をどのように管理するかは 悩ましいです。 これは暗号通貨の秘密鍵やマスタシードに限った話ではありませんが、暗号通貨は 以下のような特徴があるため、管理が難しくなっているかと思います。

  1. 非常に高額な資産も扱える
  2. 紛失すると二度と手に入らなくなる
  3. 盗まれると返してもらえる可能性は非常に低い

2については例えば銀行や取引所のパスワードでは、時間はかかりますが本人の証明 ができれば大抵は再発行できるでしょう。 1,3については大抵は1日の取引上限額があったりしますし、盗む人も中央へのアクセス しないといけないため足がつきやすく、全額がいきなり盗まれて犯人の手掛かりが全くな いということは考えにくいです。

さて、どうしたものでしょうか。

紛失のリスクと盗難のリスク

まず紛失が怖いので冗長化することや、とても頑丈な何かに記録することが必須 でしょう。

冗長化であれば複数のPCや記録媒体、紙などに記録していろんな場所に保管すれば よいでしょうか。あるいは複数のクラウドサーバへ保管しましょうか。

頑丈な何か、というのは例えば耐火プレートか何かに刻みつけるとか、頑丈な金庫 の中や貸金庫に保管するとかでしょうか。それでも一箇所にしか保管していないと いうのは不安に思います。

あるいは記憶はどうでしょうか。しかし歳をとるとに不安感が増してきますし、 若くても不慮の事故などで記憶が飛んでしまうこともあるでしょう。

やはり冗長化は避けて通れなさそうに思います。

しかし、冗長化すればするほど盗難のリスクが高くなりますよね。

秘密鍵を分散して保存

例えばfoo bar bazというマスタシードを分散かつ冗長化して保存したい場合、 3つに分けて1/3 foo, 2/3 bar, 3/3 bazのような紙を2枚ずつ合計6枚作成し、 これらを2種類ずつに分けて3個所に保存したらどうでしょうか。

  • 拠点A
    • 1/3 foo
    • 2/3 bar
  • 拠点B
    • 3/3 baz
    • 1/3 foo
  • 拠点C
    • 2/3 bar
    • 3/3 baz

仮に拠点Aが火事で燃えてしまったとすると、残りの4枚があれば復元すること が可能です。

拠点Aの2枚が盗まれたらどうでしょうか。2/3の情報が盗まれてしまったので3/3 のbaz部分をブルートフォースアタックされてしまうかもしれません。 拠点B,Cの4枚を慌てて回収してもこれは防ぐことができません。やられる前に別の ウォレットに移動するしかなさそうです。

ssssを使った秘密分散

ssssというソフトを使うともっといい感じに柔軟に分散させることが可能です。

point-at-infinity.org

さっそく実行してみましょう。

$ ssss-split -t 3 -n 6
Generating shares using a (3,6) scheme with dynamic security level.
Enter the secret, at most 128 ASCII characters: (foo bar bazを入力)
Using a 88 bit security level.
1-61c2c92a120d325a6bc48c
2-5cce244b08bd3b4f48b8c2
3-ef640c2cdfbad1369e4032
4-68ef3c65d085c08737e2bc
5-db45140207822afee11a5e
6-e649f9631d3223ebc26634

6行の文字列が生成されました。この6行のうち-tオプションで指定した数、この例 では3行を集めると復号化することができます。-nオプションで全体の個数を指定 できます。この例では6個です。

3,5,6を使って復号化してみます。

$ ssss-combine -t 3
Enter 3 shares separated by newlines:
Share [1/3]: 3-ef640c2cdfbad1369e4032
Share [2/3]: 5-db45140207822afee11a5e
Share [3/3]: 6-e649f9631d3223ebc26634
Resulting secret: foo bar baz

復号化できました。

さきほどの単純に3つに分割する方法と比べると、2つ盗まれても犯人は何も 情報を得られませんのでブルートフォースアタックの心配はなくなります。 また、盗まれたことに気付いた場合は残りの4枚を完全に消去してしまえば よいです。再度発行すれば別の文字列が得られますので、それに置き換え ればOKです。

まとめ

パスワードのような他人に知られてはまずい情報をssssを使って分散保存する方法を ご紹介しました。管理方法は人それぞれですが、1つの案として参考になれば幸いです。

Windows用のバイナリは今のところ見つけておりませんが、ソースは非常に小さく、 mini-gmpと組み合わせるとwasmで動作することも確認できております。 疑似乱数とUIをちゃんとすればブラウザ上でも使うことができるようになると思います。 (誰か作りませんか?)

なお、ssssはあまり大きなデータは保存できませんので、大きなデータを保存したい 場合はデータを暗号化し、そのパスフレーズなどの鍵を保存するようにすればよいか と思います。

暗号通貨の採掘ソフトをwasmに移植してみた

WebAssembly(wasm)というのは前から気になっていましたが、特に作りたいものが なかったため、あまり使い方を調べておりませんでした。

暗号通貨界隈ではcoinhiveというサービスでMoneroという暗号通貨の採掘ソフトを Webサービスだけで実現しているという話題があり、これを他の暗号通貨であるBitZeny に適用できないか調べてみることにしました。

coinhiveとPoWについて

coinhiveはソースが公開されているのかと思っていたのですが、どうやら一部の コードしか公開されていないらしく、特にPoW(Proof of Work)を処理している部分が よく分かりませんでした。

PoWというのはざっくり言うと、ブロックチェーンの末尾ブロックにあるバイナリ列 のハッシュ値と最新のトランザクションと適当な数値(nonce)をかけ合わせたバイナリ 列のハッシュ値を計算して、そのハッシュ値があるしきい値よりも小さいものを見つ ける処理です。ハッシュ値を計算するにはCPU, GPU, FPGA/ASIC, メモリなどの資源が 必要となります。

一番有名なbitcoinは公開当初はCPUでこの計算を行い、報酬をもらえていましたが、 GPUに移植されるとCPUでは勝ち目がなくなり、最終的にはASICで専用ハードが設計 されてGPUでも勝ち目がなくなりました。

MoneroやBitZenyというGPUでは効率よく採掘できないようなハッシュアルゴリズムが 使用されており、現在でもCPUでの採掘が有利となっております。 このため、wasmへ移植してもある程度は採掘ができるはずで、実際coinhiveではそれ を実現しているようです。

簡単な関数を試してみる

まずはwasmを使えるようになる必要があるので、Ubuntuでapt installしたEmscripten というのを試してみました。しかし、どうもこのソフトは開発がどんどん進んでいる ようで、Ubuntu 16.04 LTSのパッケージでは古すぎてWeb上にあるチュートリアルと 合わない感じでした。

このため、最新のEmscripten SDK(emsdk)というソフトをインストールし、それを使う とチュートリアルも簡単に動かすことができました。

C言語で書かれた簡単な関数を用意し、それをwasm化してJavaScriptから呼ぶことにも 成功し、これで準備が整いました。

複雑な関数の場合

次はCPU採掘ソフト(cpuminer)のyescryptハッシュ計算部分を単体で動かすために C言語の関数部分を取り出してみました。この関数は複雑で依存関係もいろいろと あるため、Emscriptenにすべてを任せてHTMLを生成する方法で動かしてみました。

適当バイナリ列のハッシュ計算を行うmain関数を用意し、それをemccでコンパイル し、生成されたHTMLを実行するとちゃんと計算結果を表示することができました。

ただし、このままでは自動生成されるHTMLだと使いにくいので、HTML内のJavaScript コードから必要部分だけを取り出す必要がありました。 またmain関数を呼ばずに任意の関数を直接呼べるようにしたり手を入れました。

ブロックハッシュの計算も必要

ここまでは割とすんなり動いたのですが、ここからが結構大変でした。 まずブロックハッシュはPoWとは違うハッシュ関数であるsha256dを使用しており、 これもコンパイルに含める必要がありました。

また、採掘プールからもらった情報とnonceからブロックを構築する必要もあり、 エンディアンを1個所でも間違うと当然ちゃんとしたハッシュ値にならず、結構 苦労しました。

=採掘プールとのプロトコルをWebSocket化 採掘プールは生のTCPで1行にJSONを入れて通信するのですが、これだとWebブラウザ からはアクセスできません。そこでGo言語でこれをWebSocket化するプロキシサーバ を作成しました。

リバースプロキシにはCaddyを使い、ほぼ全自動でLet's EncryptによるHTTPS対応を 行いました。WebSocketと静的コンテンツが同じポート番号で振り分けられるので、 非常に便利です。

まとめ

以上で暗号通貨の採掘ソフトをwasmに移植して、ブラウザで採掘ができるようにな りました。仕組み上どうしてもプロキシが必要となりますが、このプロキシはシン プルなので低リソースで動かすことができますし、分散させることも可能でしょう。

ソースコード

GitHub - ohac/cpuminer: [Updating] A multi-threaded CPU miner for BitZeny

GitHub - ohac/wasmminer: Browser mining on any pool. Currently support BitZeny.

デモページ

https://ohac.github.io/wasmminer/

速度は予想通りあまり出ませんでした。これはwasm化することにより低速化もあり ますが、特にSIMDが有効活用できないことに起因していると思います。今後wasmが SIMD対応すればそれを使うように改造することで改善するとは思います。

電気代に対して利益が出るようなものではありませんが、暗号通貨の採掘を体験し てみたいといった用途や広告の代替などには使えるかもしれません。

Monacoinのブロックチェインに含まれるOP_RETURNのデータを一覧表示

前回、MonacoinのブロックチェインにOP_RETURNのデータを保存する方法を説明しました。

OP_RETURNで80バイトまでのデータをMonacoinのブロックチェインに保存 - ohacのブログ

今回はこれを取り出して一覧表示する方法について説明します。

前回と同様に以下のpython-OP_RETURNの改造版をcloneします。

GitHub - ohac/python-OP_RETURN: Simple Python commands and library for using bitcoin OP_RETURNs

以下のようなスクリプトをlist-OP_RETURN.pyという名前で保存します。

import sys, string
from OP_RETURN import *

def OP_RETURN_list_sub(height, testnet=False):
    txns=OP_RETURN_get_block_txns(height, testnet)
    txids=txns.keys()
    for txid in txids:
        txn_unpacked=txns[txid]
        found=OP_RETURN_find_txn_data(txn_unpacked)
        if not found:
            continue
        vintxid = txn_unpacked['vin'][0]['txid']
        if vintxid == '0000000000000000000000000000000000000000000000000000000000000000':
            continue
        data = found['op_return']
        try:
            data = data.decode('utf-8')
            print(data)
        except:
            print OP_RETURN_bin_to_hex(data)

def OP_RETURN_list(testnet=False):
    max_height=int(OP_RETURN_bitcoin_cmd('getblockcount', testnet))
    heights = [655049, 660399, 660413, 684062, 684069, 859463, 860016, 873025,
               874951, 875066, 875582, 875615, 876901, 904773, 904868, 965884,
               965927]
    for height in heights:
        OP_RETURN_list_sub(height, testnet)
    for height in range(1005000,max_height):
        OP_RETURN_list_sub(height, testnet)

OP_RETURN_list()

heightsには既知のブロック高が入っており、range(1005000,max_height)で1005000から最新までを調べるようにしています。

以下、実行結果です。

$ python list-OP_RETURN.py 
Hello!
Hello world!
Hello! txout0!
0468507040118fd428901db808a1ff347ff9b2a68137403f79deed98cbb5e568
happy monacoin 80 byte op_return thank you for mr.watanabe 2016/06/09      
hello!
hello!
4d4e53540100000013c10000000000001343304e56426b36555141456f7a446d2e6a7067
4d4e5354ffffffff13c1000005af00001343304e56426b36555141456f7a446d2e6a7067
4d4e5354ffffffff7b0e0200730c0200e38188e38188e38198e38283e381aae38184e3818b2e6a7067
4d4e5354ffffffff1b700000706f0000e69982e99ba82e6a7067
4d4e5354ffffffff1b700000706f0000e69982e99ba82e6a7067
4d4e5354ffffffff1b700000606f0000e69982e99ba82e6a7067
4d4e5354ffffffff98fb000071f6000043746d58397a6356494141644a785f2e6a7067
4d4e5354ffffffff1b7000004c6f0000e69982e99ba82e6a7067
awduiefnpiuerhagnvaeuip;wiensaio;dwsdfviuvsndi uvsdn cubvsovdaduisnduipvndfb
vvm@wer0mv@0
Hello, Monacoin!
こんにちは世界

ここまでできたら、ポーリングで定期的に最新情報を取り出して、ある条件で絞り込んだ上位プロトコルを作ることもできそうですね。

今回の件は以下のAsk Monaの内容が大変参考になりました。ありがとうございます。

askmona.org

なお、今回結構苦労したのがSegwitのtx部分をスキップするところでした。

オリジナルのOP_RETURN.pyはSegwitに対応しておらず、ドキュメントをあちこち探しましたが、分かりやすく説明されたものがなくて、バイナリデータと英文を見比べながらようやく完成させることができました。

OP_RETURNで80バイトまでのデータをMonacoinのブロックチェインに保存

ブロックチェインを使ったアプリを作りたくなったので、まずは基本的なところから試してみた。

OP_RETURNを使えば80バイトまでのデータをBitcoinのブロックチェインに保存できますが、ご存知のようにBitcoinの価格が高騰しており、ちょっとした実験にも手数料がかかってしまいます。 これが原因でCounterpartyも同様に手数料がかかってしまい、とてもお気軽に使える状態ではないかと思います。

testnetを使えば実験はできますが、これは本当にテスト用なのでアプリを作るには適していません。

ということでMonacoinを使って実験してみました。

python-OP_RETURNというソフトがあったので、これをMonacoin用に調整してみました。

GitHub - ohac/python-OP_RETURN: Simple Python commands and library for using bitcoin OP_RETURNs

monacoindがlocalhostで動作しており、~/.monacoin/monacoin.confがあれば自動でRPCのパラメータを読んでくれます。

以下のコマンドでHello, Monacoin!という16バイトの文字列を送ってみました。 なおMR5DV..は私のウォレットアドレスの1つです。

$ python send-OP_RETURN.py MR5DVFxd6YxrYQSUoby6Q9DrVGDX5RFvVB 0.02 'Hello, Monacoin!'
TxID: 5090920aba9c64a1dca6c68553e6f45d65745cb88b5fe40f99e55ce083f2c00f
Wait a few seconds then check on: https://mona.chainsight.info/tx/5090920aba9c64a1dca6c68553e6f45d65745cb88b5fe40f99e55ce083f2c00f
monacoin-cli gettransaction 5090920aba9c64a1dca6c68553e6f45d65745cb88b5fe40f99e55ce083f2c00f
monacoin-cli getrawtransaction 5090920aba9c64a1dca6c68553e6f45d65745cb88b5fe40f99e55ce083f2c00f

monacoin-cliのgettransactionコマンドを使ってtxを確認します。

$ monacoin-cli gettransaction 5090920aba9c64a1dca6c68553e6f45d65745cb88b5fe40f99e55ce083f2c00f
{
  "amount": 0.00000000,
  "fee": -0.00200000,
  "confirmations": 25,
  "blockhash": "2d1f49b4a476c35d7435150e87d7a36674f12645c6eed7c373a225931d11ae69",
  "blockindex": 1,
  "blocktime": 1495857840,
  "txid": "5090920aba9c64a1dca6c68553e6f45d65745cb88b5fe40f99e55ce083f2c00f",
  "walletconflicts": [
  ],
  "time": 1495857057,
  "timereceived": 1495857057,
  "bip125-replaceable": "no",
  "details": [
    {
      "account": "",
      "category": "send",
      "amount": 0.00000000,
      "vout": 2,
      "fee": -0.00200000,
      "abandoned": false
    }
  ],
  "hex": "010000000128fb81d1cc4ac49dc853605223d9763f19c3aa3fd31cec1e1777574c936d9bb4000000006a47304402207ce1ca33838afc218667dddbacfa4f8a429f51613ccd845ca9b8b055b51527d1022032693910718bcafd3cc4885c8a9112dfd9abbcecac8d704423743b4372f9b2210121031a57e2277899a85fdbd999b94896b9815883187b4cb9349ef09c1ea60c3b990effffffff0380841e00000000001976a914bc61040096f2c4422d9470f6ffd70fd919fe397588acf7084fd3050000001976a91494f9592a5d3704d5e0e5f092bbb2e8ee73c0bb0388ac0000000000000000126a1048656c6c6f2c204d6f6e61636f696e2100000000"
}

hexのところの最後の方にある 48646c6c.. の部分が Hello, Monacoin! です。

$ echo -n 'Hello, Monacoin!'|xxd
00000000: 4865 6c6c 6f2c 204d 6f6e 6163 6f69 6e21  Hello, Monacoin!

Webブラウザからは以下のサイトで確認できます。

https://mona.chainsight.info/tx/5090920aba9c64a1dca6c68553e6f45d65745cb88b5fe40f99e55ce083f2c00f

詳細の + ボタンを押すと OP_RETURN 48656c6c6f2c204d6f6e61636f696e21 となっていることが確認できます。

ちなみにやってみたいことはBitTorrentでのデータホスティング冗長化サービスです。

BitTorrentSHA1ハッシュ(20バイト)をOP_RETURNで受け付け、サービス提供者のアドレスにMonacoinを送れば金額とデータサイズから提供期間を決めてホスティングするといったことが可能だと思います。 違法データをホスティングしてしまわないようにするためにある程度検閲する必要があるかもしれません。あるいは暗号化データしかホスティングしないことにしてデータのエントロピーを見て判断するといった対策ができるかもしれません。

Monacoin用のElectrum試作版

ElectrumをMonacoinで使えるようにしてみました。 ただし、PoWとかのチェックは全く行っていませんので、あくまでもテスト用です。 また、Mで始まるアドレスの対応やBTCをMONAに変更する対応などもできていないところがあります。 サーバはCoinomiさんのものをお借りしております。

Commits · ohac/electrum · GitHub

以下のようなDockerfileを準備します。

FROM ubuntu:16.04
RUN apt-get update && \
    apt-get upgrade -y && \
    apt-get install -y vim python-pip git python-qt4 pyqt4-dev-tools && \
    apt-get clean
RUN pip install --upgrade pip && \
    pip install dnspython pyaes ecdsa qrcode pbkdf2 protobuf pip requests \
                pysocks jsonrpclib ltc_scrypt
WORKDIR /root
RUN git clone https://github.com/ohac/electrum.git
WORKDIR /root/electrum
RUN git checkout monacoin-20170514
RUN pyrcc4 icons.qrc -o gui/qt/icons_rc.py

ビルドします。

$ docker build -t elemona .

以下のようなスクリプトを準備。

#!/bin/bash
XSOCK=/tmp/.X11-unix
XAUTH=/tmp/.docker.xauth
USENET=
#USENET="--net=none"
if ! [ -a $XAUTH ]; then
  touch $XAUTH
  xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
fi
docker run -it --rm \
  -v $XSOCK:$XSOCK:rw -v $XAUTH:$XAUTH:rw \
  -e DISPLAY -e XAUTHORITY=${XAUTH} --ipc=host $USENET \
  elemona

立ち上げ。

# ./electrum

試しに少額のMONAを送信してみると表示された。

f:id:ohac:20170514153730p:plain

しかし6検証を終えているのに何故かNot Verifiedのまま。 頻繁にNot connectedになり、どこかおかしいようだ。

また、別アドレスに送信しようとしたが以下の例外が出てうまく動かなかった。 もう少し調整が必要っぽい。

Traceback (most recent call last):
  File "/root/electrum/gui/qt/main_window.py", line 576, in timer_actions
    self.do_update_fee()
  File "/root/electrum/gui/qt/main_window.py", line 1172, in do_update_fee
    fee_rate = fee * 1000 / tx.estimated_size()
  File "/root/electrum/lib/util.py", line 203, in <lambda>
    return lambda *args, **kw_args: do_profile(func, args, kw_args)
  File "/root/electrum/lib/util.py", line 199, in do_profile
    o = func(*args, **kw_args)
  File "/root/electrum/lib/transaction.py", line 788, in estimated_size
    return len(self.serialize(True)) / 2 if not self.is_complete() or self.raw is None else len(self.raw) / 2 # ASCII hex string
  File "/root/electrum/lib/transaction.py", line 737, in serialize
    nLocktime = int_to_hex(self.locktime, 4)
  File "/root/electrum/lib/bitcoin.py", line 168, in int_to_hex
    return rev_hex(s)
  File "/root/electrum/lib/bitcoin.py", line 162, in rev_hex
    return s.decode('hex')[::-1].encode('hex')
  File "/usr/lib/python2.7/encodings/hex_codec.py", line 42, in hex_decode
    output = binascii.a2b_hex(input)
TypeError: Non-hexadecimal digit found

23:53追記

blockchain_headersをダウンロード可能にしてやればうまく動きました。 electrum-serverの立て方は別途記事にします。

ネットワークから遮断されたDockerコンテナ内でElectrum

以下のような感じのDockerfileを用意してイメージをbuildする。

FROM ubuntu:16.04
RUN apt-get update && \
    apt-get upgrade -y && \
    apt-get install -y vim python-pip git python-qt4 pyqt4-dev-tools && \
    apt-get clean
RUN pip install --upgrade pip && \
    pip install dnspython pyaes ecdsa qrcode pbkdf2 protobuf pip requests \
                pysocks jsonrpclib
WORKDIR /root
RUN git clone https://github.com/spesmilo/electrum.git
WORKDIR /root/electrum
RUN git checkout 2.8.2
RUN pyrcc4 icons.qrc -o gui/qt/icons_rc.py

ビルドする。

$ docker build -t electrum .

ネットで見つけた手順を少しアレンジした以下のような手順でコンテナを立ち上げる。

$ XSOCK=/tmp/.X11-unix
$ XAUTH=/tmp/.docker.xauth
$ touch $XAUTH
$ xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
$ docker run -it --rm \
    -v $XSOCK:$XSOCK:rw -v $XAUTH:$XAUTH:rw \
    -e DISPLAY -e XAUTHORITY=${XAUTH} --ipc=host --net=none \
    electrum
root@xxxxxxxxxxxx:~/electrum# ./electrum 

無事立ち上がった。

オフラインウォレットほど安全ではないが比較的安全かもしれない。

マストドン始めました

マストドン(のインスタンス)始めました。

m.sighash.info

入居者募集中です。 そんだけ。

ohac@sighash - Mastodon

追記(2017/4/17):

  • ちょっとミスってしまい、今はダウンしています。夜には復活させる予定。

追記(2017/4/18 00:50):

  • なおりました。t2.smallに昇格させました。