老技術者がPythonでハッシュ関数Keccak-256やら楕円曲線暗号secp256k1を持ち出してEthereum Addressを求めてみた


はじめに

老技術者がAIからBlockChainに主戦場を移しはや4ヶ月。その間、『マスタリング・イーサリアム』をボロボロになるまで読み解き、30歳年下の技術者A氏に教えを乞いながら、EthereumやらHyperledger Besuやら何とかわかった感じになった次第。今日はちょいと暗号のお勉強をコード書きながらやろうと思ったら、いきなり「ModuleNotFoundError: No module named 'Crypto'」というお叱りを受け、そこを何とか乗り切った喜びから、久々にQiitaなんぞを書き始めてしまいました。

Anacondaにpipする(MacOSの場合)

Anacondaは便利だけど、インストールする前からPythonを入れてたりするとpipでライブラリをインストールしてもAnacondaでは使えないという初心者が必ず陥る試練。まずはこれを乗り切ることにする。
ターミナルで、次のコマンドを実行します。

/opt/anaconda3/bin/conda init シェル名

MacOSのデフォルトシェルである zsh を使っている場合は、

/opt/anaconda3/bin/conda init zsh

を実行し、ターミナルを再起動すると、自動でconda環境が立ち上がります。
こうしておくことで、ターミナルでpipを使いライブラリをインストールするとAnacondaで使えるようになります。
いやいや、毎回conda環境なんて立ち上がっちゃ困るという方は、ターミナルを再起動し、

conda config --set auto_activate_base false

としておけば、conda環境は自動では立ち上がらなくなります。
その場合、pipする前には忘れずに

conda activate

をターミナルから実行してください。
なお、ANACONDA NAVIGATORという便利なGUIを使えば、Environments>base(root)>Open Terminal
でconda環境に入れますので思う存分pipして下さい。

以前はよくAnaconda環境ってぶっ壊れてしょっちゅう再インストールしていたんですが、最近安定してきましたね。気のせいかも知れませんが。

pycryptodomeでKeccak-256

老人にありがちな長い前置きの後、いよいよハッシュ関数Keccak-256を使うことにします。
Keccakはケチャックと読みます(私は昔さんざん遊びに行ったバリ島への郷愁を込めてケチャと呼ぶかというと決して呼ばない、また余計なことを、かたじけない)。
まずはpycryptodomeからKeccak-256を使ってみましょう。The Python Package Index (PyPI)にあるようにpycryptodomeはなかなか多機能なライブラリです。
https://pypi.org/project/pycryptodome/

ライブラリのインストールは、上記の要領で立ち上げたconda環境のターミナルで、次のコマンドを実行します。

pip install pycryptodome

これで、Ethereumの暗号ハッシュ関数Keccak-256を計算する準備ができました。
試しに、Anaconda-Navigatorから起動したVS Codeで次のようなPython Scriptを実行してみましょう。

from Crypto.Hash import keccak
import binascii

keccak256 = keccak.new(data=b'hello', digest_bits=256).digest()
print("Keccak256:", binascii.hexlify(keccak256))

次のような結果が出力されればOKです。

Keccak256: b'1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8'

もしエラーが出たら

ひょっとすると

ModuleNotFoundError: No module named 'Crypto'

というエラーが出るかもしれません。
そうしたら、ターミナルを開いて次のコマンドを実行してみてください。

pip uninstall pycryptodome
pip uninstall pycrypto
pip install pycryptodome

これでエラーが出なくなると思います。

pysha3でKeccak-256

次はSHA-3のpython向けラッパーpysha3からハッシュ関数Keccak-256を使ってみます。解説はこちらをご覧ください。
https://github.com/tiran/pysha3
インストールはやはり前述の方法で立ち上げたconda環境のターミナルで、次のコマンドを実行します。

pip install pysha3

これで、Ethereumの暗号ハッシュ関数Keccak-256を計算する準備ができました。
試しに、Anaconda-Navigatorから起動したVS Codeで次のようなPython Scriptを実行してみましょう。

from sha3 import keccak_256

keccak256 = keccak_256(b'hello').digest()
print("Keccak256:", keccak256.hex())

次のような結果が出力されればOKです。

Keccak256: b'1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8'

Ethereum Addressを求める

Ethereum Addressは次の3ステップで求められます。
(1) private keyを生成する
(2) private keyからpublic keyを計算する
(3) public keyからEthereum Addressを計算する

(1) private keyを生成する

private keyは、例えば次のように、安全なエントロピー源を使って得た無作為なビット列をKeccak-256などの256ビットの数字を生成するハッシュ関数に入力して得ます。

from sha3 import keccak_256
import os

rdmnum = os.urandom(32) #256bit(32Byte)の暗号論的擬似乱数
private_key = keccak_256(rdmnum).digest() #ハッシュ関数keccak_256で秘密鍵生成
print("private key:", private_key.hex())

しかし、今はこの後の処理の再現性のために秘密鍵を
k = f8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315
とします。したがって上記のコードは下記に変えておきます。(あとで通しのコード載せるから安心してください。)

private_key_hex = 'f8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315'
private_key = bytes.fromhex(private_key_hex)

ちなみにこの秘密鍵は『マスタリング・イーサリアム』で説明用に使っているものです。

(2) private keyからpublic keyを計算する

楕円曲線とは
$$
y^2 = x^3 + ax + b
$$
という方程式をみたす点の集合です。
この集合の要素 $ P(x_p, y_p ) $ と $ Q(x_q, y_q ) $ の和は、
$ P \neq Q $ の場合、点 $ P, Q $ を通る直線と楕円曲線とのもう1つの交点 $ R(x_r, y_r ) $ の $ y $ 座標を反転させた点$ R'(x_r, -y_r ) $ と定義されます。
$ P = Q $ の場合、点 $ P $ で引いた楕円曲線の接線と楕円曲線とのもう1つの交点 $ R(x_r, y_r ) $ の $ y $ 座標を反転させた点$ R'(x_r, -y_r ) $ と定義されます。この場合、$ R' = P+P = 2P $ となり、さらにこれに $ P $ を加算していくことで $ 3P, 4P, 5P, \cdots $ と整数倍(スカラー倍算)が定義できます。
これで楕円曲線暗号によるpublic key生成の準備ができました。 
Ethereumで使用されるsecp256k1という楕円曲線暗号では、以下のようにしてpublic keyを生成します(詳しくはこちらを参照のこと https://en.bitcoin.it/wiki/Secp256k1 )。
まず、素数位数 $ P = 2^{256}-2^{32}-2^9-2^8-2^7-2^6-2^4-1 $ の有限体上で定義された楕円曲線
$$
y^2 = x^3 + 7
$$
の上の点の集合を用意します。その集合の中で生成点 $ G(x_g, y_g ) $

$$
x_g=0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179
$$

$$
y_g=0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
$$

を上述の楕円曲線演算によりprivate_key倍(スカラー倍算)してpublic keyを求めます。
で、大変ですよね。そういう時に人は皆、ライブラリを探します。
ありました。
https://pypi.org/project/coincurve/13.0.0rc1/#contents
coincurve.PrivateKeyというのを使わせていただきます。
インストールはやはり前述の方法で立ち上げたconda環境のターミナルで、次のコマンドを実行します。

pip install coincurve

そして、

public_key = PublicKey.from_valid_secret(private_key).format(compressed=False)[1:]
print('public key:', public_key.hex())

あっさりpublic keyのお出ましです。大丈夫なのか、と言うほどあっさり。

(3) public keyからEthereum Addressを計算する

ここまでくれば後はpublic keyをまたハッシュ関数Keccak256に通して、その最後の20Byteを持ってくればそれがもうEthereum Addressです。16進数のエンコーディングを表すプレフィックス「0x」もつけてあげます。
python
addr = keccak_256(public_key).digest()[-20:]
print('eth addr: 0x' + addr.hex())

ということで
Ethereum Addressを求める3ステップ
(1) private keyを生成する
(2) private keyからpublic keyを計算する
(3) public keyからEthereum Addressを計算する
をまとめてコードのせます。
まずは、答え合わせ用コード。

from coincurve import PublicKey
from sha3 import keccak_256

private_key_hex = 'f8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315'
private_key = bytes.fromhex(private_key_hex)

public_key = PublicKey.from_valid_secret(private_key).format(compressed=False)[1:]
addr = keccak_256(public_key).digest()[-20:]

print('private key:', private_key.hex())
print('public key:', public_key.hex())
print('eth addr: 0x' + addr.hex())

これで、

private key: f8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315
public key: 6e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b83b5c38e5e2b0c8529d7fa3f64d46daa1ece2d9ac14cab9477d042c84c32ccd0
eth addr: 0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9

と表示されればOKです。

そうしたら、今度は暗号論的擬似乱数からprivate keyを導出し、Ethereum Addressを計算する通しのコードです。

from coincurve import PublicKey
from sha3 import keccak_256
import os

rdmnum = os.urandom(32) #256bit(32Byte)の暗号論的擬似乱数
private_key = keccak_256(rdmnum).digest() #ハッシュ関数keccak_256で秘密鍵生成

public_key = PublicKey.from_valid_secret(private_key).format(compressed=False)[1:]
addr = keccak_256(public_key).digest()[-20:]

print('private key:', private_key.hex())
print('public key:', public_key.hex())
print('eth addr: 0x' + addr.hex())

おわりに

最初に断っておくべきでしたが、何でpythonなの?と思われた方も多いのでは?
すみませんこれは私がpython人だからです。アルゴリズム学習はまずpythonでというのが私の流儀でして。
とはいえ最近のpythonの隆盛をみると、いまにスマコンもpythonで書くようになるのでは。
ただsolidityで日々の糧を稼いでおられる方にも、solidityコードの検算用にここにあげたpythonコードを使ってもらえると望外の幸せです。