これはBlockChain Advent Calendar 2017で書いた記事です。
この記事ではEthereumのアドレス生成アルゴリズムを実装を交えて紹介します。
実装は主にSSLプロトコル・TLSプロトコルのオープンソースであるOpenSSLを利用しています。
アドレス生成の流れ
さっそくEthereumのアドレス生成の流れを見ていきましょう。以下のステップでEthereumのアドレスを生成することができます。
- ランダムな秘密鍵の生成
- 秘密鍵からECDSA(楕円曲線DSA)を用いて、公開鍵の導出
- 公開鍵をハッシュ関数Keccak-256に通し公開鍵ハッシュの生成
- 公開鍵ハッシュの後ろ20バイトを抜き出しアドレスとする
それでは実際にプログラムでEthereumのアドレスを生成していきます。
ECDSAによる鍵ペアの生成
EthereumではEthereumネットワークへのアクセス制御のために公開鍵暗号のECDSA(楕円曲線DSA)を利用しています。ECDSAの鍵ペアをOpenSSLを利用し導出していきます。
ECDSAではまずどの楕円曲線を利用するかを指定する必要があります。OpenSSLで扱える楕円曲線のリストを表示してみましょう。
$ openssl ecparam -list_curves
secp112r1 : SECG/WTLS curve over a 112 bit prime field
secp112r2 : SECG curve over a 112 bit prime field
secp128r1 : SECG curve over a 128 bit prime field
secp128r2 : SECG curve over a 128 bit prime field
secp160k1 : SECG curve over a 160 bit prime field
secp160r1 : SECG curve over a 160 bit prime field
secp160r2 : SECG/WTLS curve over a 160 bit prime field
secp192k1 : SECG curve over a 192 bit prime field
secp224k1 : SECG curve over a 224 bit prime field
secp224r1 : NIST/SECG curve over a 224 bit prime field
secp256k1 : SECG curve over a 256 bit prime field
secp384r1 : NIST/SECG curve over a 384 bit prime field
(略)
EthereumではBitcoinと同じsecp256k1曲線を利用しています。これはSEC(Standards for Efficient Cryptography)で標準パラメータとして策定されており( http://www.secg.org/sec2-v2.pdf )、Bitcoinで利用されてから人気があるようです。
opensslのecparamコマンドでsecp256k1曲線のパラメータを表示してみます。base64にエンコードされた形でsecp256k1曲線のパラメータが表示されます。詳細なパラメータを参照したい方はこちらをご覧ください。
$ openssl ecparam -name secp256k1
-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
それでは秘密鍵を作成してみます。opensslのecparamコマンドを利用し、pem形式の秘密鍵を作成します。
$ openssl ecparam -name secp256k1 -genkey > privkey.pem
$ cat privkey.pem
-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIPcpcx7I4sgaM0+oadEvaGhx2M2ZuhbxWlqEzH9LHZN6oAcGBSuBBAAK
oUQDQgAE9pSf3D/Zbvgo78JVwVK509nVyrrWI1oa9sx1UEox6hsvsLKjiJ3WO/Sb
9E97BhCcqz8j78zmxTnOosbT1OLZmA==
-----END EC PRIVATE KEY-----
これでランダムな秘密鍵を導出することができました。次に作成したpem形式の秘密鍵を利用し、opensslのecコマンドで公開鍵を作成します。
$ openssl ec < privkey.pem -text -noout > Key
$ cat Key
read EC key
Private-Key: (256 bit)
priv:
00:f7:29:73:1e:c8:e2:c8:1a:33:4f:a8:69:d1:2f:
68:68:71:d8:cd:99:ba:16:f1:5a:5a:84:cc:7f:4b:
1d:93:7a
pub:
04:f6:94:9f:dc:3f:d9:6e:f8:28:ef:c2:55:c1:52:
b9:d3:d9:d5:ca:ba:d6:23:5a:1a:f6:cc:75:50:4a:
31:ea:1b:2f:b0:b2:a3:88:9d:d6:3b:f4:9b:f4:4f:
7b:06:10:9c:ab:3f:23:ef:cc:e6:c5:39:ce:a2:c6:
d3:d4:e2:d9:98
ASN1 OID: secp256k1
16進数形式の秘密鍵と公開鍵のペアを作成することができました。公開鍵は65バイトの0x04
から始まる文字列となります。
Bitcoinの場合、公開鍵を圧縮して利用しますが、Ethereumでは非圧縮のまま利用します。
$ cat Key | grep pub -A 5 | tail -n +2 | tr -d '\n[:space:]:' | sed 's/^04//' > pub
$ cat pub
f6949fdc3fd96ef828efc255c152b9d3d9d5cabad6235a1af6cc75504a31ea1b2fb0b2a3889dd63bf49bf44f7b06109cab3f23efcce6c539cea2c6d3d4e2d998
ECDSAで導出した公開鍵は先頭に非圧縮形式を表す0x04
のプレフィックスが付きます。ハッシュ値を計算する際これを除く必要があるため、上記の工程で先頭の0x04
を削除しています。
公開鍵からアドレスを導出する
公開鍵からアドレスを生成するには、まず公開鍵のハッシュを生成する必要があります。Ethereumでは公開鍵のハッシュ化のためのハッシュ関数にKeccak-256が利用されています。
Keccak-256はSHA-3として選定されたアルゴリズムですが、EthereumではオリジナルのKeccak-256が利用されており、SHA-3標準とアルゴリズムが異なるため注意が必要です。
https://ethereum.stackexchange.com/questions/550/which-cryptographic-hash-function-does-ethereum-use
今回はKeccak-256に以下のライブラリを利用しています。
https://github.com/maandree/sha3su
先ほど生成した公開鍵をKeccak-256でハッシュ化します。
$ cat pub | keccak-256sum -x -l | tr -d ' -' > pubHash
$ cat pubHash
e8814fc9d1265ca4cc1b111b8b5d4078bc122cb300e667b3cd64b1fd5d841240
公開鍵のハッシュが作成できました。Ethereumのアドレスには後ろの20バイトが必要なので、前の12バイトを削除します。
tailコマンドを利用して後ろから40文字(20バイト)を抜き出します。(1文字は改行分)
$ cat pubHash | tail -c 41
8b5d4078bc122cb300e667b3cd64b1fd5d841240
これで、Ethereumのアドレスが生成できました。
0x8b5d4078bc122cb300e667b3cd64b1fd5d841240
16進数を表すためのプレフィックスとして0x
を付けています。
Gethに秘密鍵をインポートする
実装の確認のために、Gethに秘密鍵をインポートしてアドレスを導出してみます。
先ほど生成した16進数の鍵ペアから秘密鍵を抜き出します。ここで公開鍵と同様に、先頭の0x00
を削除する必要があります。
$ cat Key | grep priv -A 3 | tail -n +2 | tr -d '\n[:space:]:' | sed 's/^00//' > priv
$ cat priv
f729731ec8e2c81a334fa869d12f686871d8cd99ba16f15a5a84cc7f4b1d937a
抜き出した秘密鍵をGethのaccountコマンドでインポートしアドレスを生成します。
$ geth account import priv
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase:
Repeat passphrase:
Address: {8b5d4078bc122cb300e667b3cd64b1fd5d841240}
先ほどと同じアドレスの値が導出できました。うまくできているみたいですね。
おわりに
Ethereumのアドレス生成アルゴリズムを実装を交えて紹介しました。
今回は実装しませんでしたが、EthereumにもBitcoinのようにアドレスをエンコードする仕様が存在します。
https://github.com/ethereum/wiki/wiki/ICAP:-Inter-exchange-Client-Address-Protocol
ウォレットを作成する場合は、こちらに対応するとユーザービリティの向上に繋がります。
参考
Generating a usable Ethereum wallet and its corresponding keys
https://kobl.one/blog/create-full-ethereum-keypair-and-address/
maandree/libkeccak
https://github.com/maandree/libkeccak
$ git clone https://github.com/maandree/libkeccak.git
$ cd libkeccak
$ make LIBEXT=dylib LIBFLAGS=-dynamiclib
$ sudo make install
maandree/sha3sum
https://github.com/maandree/sha3su
$ git clone https://github.com/maandree/sha3sum.git
$ cd sha3sum
$ make
$ sudo make install