Photo by BTC Keychain
秋山です。
最近ビットコインなどの仮想通貨が話題になってますね。エンジニアであれば、ブロックチェーンなどの使われている技術に興味がある…という人も多いのではないでしょうか。
というわけで今回は、ビットコインとそのアドレス生成について書いてみたいと思います。仮想通貨の原型となっているビットコインのブロックチェーンやProof of workに少し触れつつ、実際にPythonでコードを書いてビットコインのアドレスをジェネレートしてみます。
ちなみに…Pythonでアドレスをジェネレートするコードには、下記のようなものがあります。ビットコインのアスキーアートになってておもしろいですね。(※こちらの記事から引用させていただいております)
_ =r"""A(W/2,*M(3*G *G*V(2*J%P),G,J,G)+((M((J-T )*V((G-S)%P),S,T,G)if(S@(G,J))if( W%2@(S,T)))if(W@(S,T);H=2**256;import&h ashlib&as&h,os,re,bi nascii&as&k;J$:int( k.b2a_hex(W),16);C$:C (W/ 58)+[W%58]if(W@ [];X=h.new("rip em d160");Y$:h.sha25 6(W).digest();I$ d=32:I(W/256,d-1)+ chr(W%256)if(d>0@""; U$:J(k.a2b_base 64(W));f=J(os.urando m(64)) %(H-U("AUVRIxl Qt1/EQC2hcy/JvsA="))+ 1;M$Q,R,G :((W*W-Q-G)%P, (W*(G+2*Q-W*W)-R)%P) ;P=H-2** 32-977;V$Q=P,L= 1,O=0:V(Q%W,W,O-Q/W* L,L)if(W@O%P;S, T=A(f,U("eb5mfvncu6 xVoGKVzocLBwKb/Nst zijZWfKBWxb4F5g="), U("SDra dyajxGVdpPv8DhEI qP0XtEimhVQZnEfQj/ sQ1Lg="), 0,0);F$:"1"+F(W [1:])if(W[:1 ]=="\0"@"" .join(map(B,C( J(W))));K$: F(W +Y(Y(W))[:4]); X.update(Y("\4"+ I(S)+I(T)));B$ :re.sub("[0OIl _]| [^\\w]","","".jo in(map(chr,ra nge (123))))[W];print"Addre ss:",K("\0"+X.dig est())+"\nPrivkey:",K( "\x80"+I(f))""";exec(reduce(lambda W,X: W.replace(*X),zip(" \n&$@",["","", " ","=lambda W,",")else "]) ,"A$G,J,S,T:"+_))
上記のコードを実行すると、ビットコインのアドレスと、それに対応する秘密鍵が生成されます。現段階でビットコインのアドレスとは何ぞ?という人には何を言っているのか分からないかと思いますが、詳しくはこのあと解説します。(ちなみに、このコードで作ったビットコインのアドレスはあんまり使わないほうがよいかもしれないです。記事を書いた方には申し訳ないですが、どこかに秘密鍵を送信するコードが紛れているかも…)
■ブロックチェーンとは
ブロックチェーンについては詳しい資料がたくさん出ているので、詳細まで知りたい方はそちらを見たほうがいいかと思いますが…(ブロックチェーン - Wikipedia)
ざっくり超簡単に言うと、取引データがブロックというデータ単位で管理されています。各ブロックは時系列順に並び、必ず一つ前のデータのハッシュ値を含んだハッシュ値を持っています。
ブロックチェーンに興味があってちゃんと勉強したい人は、後でリンク貼りますが『Mastering Bitcoin』を読むといいでしょう。
■Proof of workとは
Proof of workは、直訳すると「労力の証明」という意味になりますが、ざっくり超簡単に言うと、データのハッシュ値をある一定のルールに従った値になるように、 nonce と呼ばれるランダムな変数を探す作業…という感じです。(プルーフ・オブ・ワークシステム - Wikipedia)
nonceは、あるルールに従ったハッシュ値を生成するためだけに存在します。
ビットコインでは、これまでの Proof of work の完了時間などから、 平均10分程度で Proof of work (nonceを探す作業)が完了するように、 difficulty(難度)が調整されています。(※各ブロックのハッシュ値の頭から何番目までが 0 で埋まっているかで、difficultyが決まります)
例えば、先週は「10桁目までは0で埋める」というルールでnonceを探したけど平均10分を割っていて簡単すぎだったから、今週のブロックは「11桁まで0で埋める」というルールでいく…みたいな感じで調整されています。
ブロックチェーンとProof of workを組み合わせた技術が、ビットコインのコアな部分になるかと思います。
もちろん細かく見ていくとほかにもいろいろな要素はありますが、とりあえず、今までの取引データをもとに調整されたルールに従ってハッシュ値の取得を目指し、みんながいろいろな nonce を使ったハッシュ計算をして、正解を探している…ということですね。
これを改ざんしようと思ったら、該当の取引を改ざんするだけでなく、以降現在のブロックへとつながり得るハッシュ値をすべて計算しなければならず、ものすっごい計算力の高いスーパーコンピューターでもない限り不可能…という感じなので、今のところは改ざんに耐性があると言われています。
ビットコインについてもっと詳しく知りたい人は、少し前のものですが、個人的にはこちらのスライドがわかりやすくて全体が掴めるかと思います。
あとはさっきも言いましたが、Mastering Bitcoinという有名な書籍がおすすめです。和訳版のPDFも無料で読めます。
Translations | Mastering Bitcoin
Mastering Bitcoin: Programming the Open Blockchain
- 作者: Andreas M. Antonopoulos
- 出版社/メーカー: O'Reilly Media
- 発売日: 2017/06/12
- メディア: Kindle版
- この商品を含むブログを見る
※和訳版
- 作者: アンドレアス・M・アントノプロス,今井崇也,鳩貝淳一郎
- 出版社/メーカー: エヌティティ出版
- 発売日: 2016/07/14
- メディア: 大型本
- この商品を含むブログ (7件) を見る
■アドレスについて
では、実際にブロックに入ってる送受金データで使われるアドレスって何なんだろう…という話です。
アドレスは、必ず1か3で始まります。1で始まるアドレスは標準のアドレス、3で始まるアドレスはマルチシグアドレスといいます。
標準のアドレスでは、アドレスと秘密鍵が一対となっており、秘密鍵を所有する人が、そのアドレスにあるビットコインを移動させることができます。
対してマルチシグアドレスでは、アドレスに対して秘密鍵が複数あり、そのうち何個以上を持っている人だけが、そのアドレスにあるビットコインを移動させられるようになっています。秘密鍵が複数必要なので、標準のアドレスに比べるとセキュリティ的に堅牢であるとされています。
マルチシグアドレスの話は結構複雑なため、まずは標準的な1で始まるアドレスを実際に作ってみましょう。
■Pythonを使って標準のアドレスを作ってみる
基本的には、秘密鍵をランダム生成し、そこからビットコインアドレスを作る…というやり方になります。
ビットコインのアドレスで用いられる秘密鍵は、楕円曲線DSAのsecp256k1を使います。(Secp256k1 - Bitcoin Wiki)
openssl ecparam -genkey -name secp256k1 -out privatekey.pem
opensslが入っている環境であれば、上記のコマンドを叩けば鍵ができます。
これだけで終わりではなく、ビットコインのアドレス形式を作るには、まだいくつか加工手順が必要です。
hash160 と呼ばれる手順を使い、 SHA-256 でダイジェストを得て、そこからさらに RIPEMD160 でダイジェストを得ましょう。
アドレスと秘密鍵の両方におこなう処理としては
- 先頭に 0 をつける(ビットコインであるというヘッダ的な意味があります、アドレスが必ず1で始まる理由でもあります)
- チェックサムとして、sha256を2回行ったダイジェストの先頭4バイトを後ろに付与する
- base58 (i、l、1や0、Oのようなわかりにくい文字種を除外した変換)を行う
上記を行うと、アドレスが生成されます。
以下、ctypesでopensslを使ってアドレスを生成するコードをPythonで書いてみました。(Python3を使うとbytes周りの処理が面倒になったので、Python2で書いています)
#encoding:utf-8 import hashlib import ctypes import ctypes.util import sys ssl = ctypes.cdll.LoadLibrary (ctypes.util.find_library('ssl')) #sslでsecp256k1の新しい鍵を作る ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p secp256k1 = ssl.EC_KEY_new_by_curve_name(714) #secp256k1 = 714 で新しい鍵作る ssl.EC_KEY_generate_key(secp256k1) #アドレス用のpubkeyを得る size = ssl.i2o_ECPublicKey(secp256k1, 0) mb = ctypes.create_string_buffer(size) ssl.i2o_ECPublicKey(secp256k1, ctypes.byref(ctypes.pointer(mb))) pubkey = mb.raw.rjust(32, chr(0)) #秘密鍵を得る bn = ssl.EC_KEY_get0_private_key(secp256k1); bytes = (ssl.BN_num_bits(bn) + 7) / 8 mb = ctypes.create_string_buffer(bytes) n = ssl.BN_bn2bin(bn, mb) secret = mb.raw #アドレスをsha256でダイジェスト取得してさらにRIPEMD-160でダイジェスト取得 h1 = hashlib.new('ripemd160') h1.update(hashlib.sha256(pubkey).digest()) hash160 = h1.digest() #base58にエンコードする関数 b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' def base58_encode(s, version=0): #versionはbitcoinのアドレスですよという感じのヘッダ。アドレスでは先頭が必ず1 vs = chr(version) + str(s) #checksum バージョン+アドレスのsha256を2回かけたやつの先頭バイト check = hashlib.sha256(hashlib.sha256(vs).digest()).digest()[:4] s = vs + check n = int('0x' + s.encode('hex'), 16) l = [] while n > 0: n, r = divmod(n, 58) l.insert(0,(b58_digits[r])) res = ''.join(l) pad = 0 for c in s: if c == chr(0): pad += 1 else: break return b58_digits[0] * pad + res #base58にエンコードして完成!(base58_encodeではエンコードとさらにチェックサム付与などしてます) version = 0 address = base58_encode(hash160, version) secretkey = base58_encode(secret, 128+version) print(address,secretkey) ssl.EC_KEY_free(secp256k1)
これを実行すると、アドレスと秘密鍵の一対が作成されます。
paiza.IOなどのオープンな環境でも、もちろん実行自体はできます…が、基本的にそういった環境で作ったアドレスは他の人にも公開されているわけですから、使わないほうがいいでしょう。(実行&使用する際はくれぐれもご注意ください…)基本的にアドレス生成は、盗まれる可能性の低い手元の環境でやってください。
実際に実行してみた結果が以下になります(何かの間違いで送金とかされないようアドレスは黒塗りにしてあります)。
これ以降、会社のマシンを使ってビットコインウォレットを動かすのも何なので、検証にはこちらのWebウォレットを使っていきます。
Bitcoin Block Explorer - Blockchain
登録してログインすると…
設定のアドレスのところに、アドレスのインポートという項目があります。クリックすると以下のようなダイアログが出るので、さきほど生成した秘密鍵を貼り付けてみます。
あとはインポートボタンを押せば、以下のようにアドレスが認識されて表示されるかと思います。(アドレスは入力せず、秘密鍵のみでビットコインアドレスが表示されます)
こんな感じで、ビットコインのアドレスとその秘密鍵が作られて使えるようになります。
当たり前ですが、アドレスは各種ハッシュ関数を通っており、任意に決めるのは不可能です。
しかし、数撃ちゃいつかは当たるかもしれない…というわけで、アドレスを生成しまくって好きなワードを含んだアドレスを探せる vanitygen というツールがあります。
こんな感じでソースのリポジトリが公開されています。
github.com
当然ですが、もしこういったコードに鍵を盗むコードがあった場合は、ビットコインが盗まれてしまう可能性もあります。よく用心したい方は、ソースコードをしっかり読んでから実行するようにしましょう。
使い方は、
git clone https://github.com/exploitagency/vanitygen-plus cd vanitygen-plus make ./vanitygen -i 1Paiza
とコマンドを入れて少し待つと
Difficulty: 31912593
Pattern: 1Paiza
Address: 1PAizA54L~~中略~~Bh
Privkey: 5JDL31~~中略~~Hg
といった感じで、結果を得ることができます。( -iオプションをつけると、大文字小文字問わずに探します)
MacBookPro2016のi7で実行すると、例えば5文字のアルファベット(大文字小文字問わず)の組み合わせなら、2分ほどで見つけられるかと思います。6文字を超えると1時間以上かかったり、10文字の組み合わせで探そうとすると40年ぐらいかかってしまうようです。(人間が1年の間に落ちてきた隕石にぶつかって死ぬ確率は25万分の1らしいですが、アドレスが全く同じになってしまう確率はそれよりも低いので安心してください(?))
あとはマイニングと似たような話になりますが、GPUを何台も使ったりすることもできるため、「どうしてもお気に入りのアドレスがほしい!」という人は、グラフィックボードをたくさん買えば希望がかなうかもしれませんね。
■まとめ
今回は、私がPythonが好きなのでアドレス生成もPythonでやってみました。
繰り返しになりますが、くれぐれも秘密鍵は誰にも見られないようにしてください。クレジットカードなどであれば保険がきくケースもありますが、ビットコインは不可逆なので…。
「paizaラーニング」では、今まで有料だった「Python入門編」・「ITエンジニアの就活準備編」が、今月から【全編無料】となりましたのでぜひごらんください。
詳しくはこちら
「paizaラーニング」では、未経験者でもブラウザさえあれば、今すぐプログラミングの基礎が動画で学べるレッスンを多数公開しております。
そして、paizaでは、Webサービス開発企業などで求められるコーディング力や、テストケースを想定する力などが問われるプログラミングスキルチェック問題も提供しています。
スキルチェックに挑戦した人は、その結果によってS・A・B・C・D・Eの6段階のランクを取得できます。必要なスキルランクを取得すれば、書類選考なしで企業の求人に応募することも可能です。「自分のプログラミングスキルを客観的に知りたい」「スキルを使って転職したい」という方は、ぜひチャレンジしてみてください。