はじめに
老技術者がAIからBlockChainに主戦場を移しはや2年。その間、『マスタリング・イーサリアム』をボロボロになるまで読み解き、30歳年下の技術者M氏に教えを乞いながら、EthereumやらHyperledger Besuやら何とかわかった感じになったのが1年くらい前。そのあたりから一転してむしろ教える立場になり、昨年末の定年を機にそれを商売にして今に至るのでした。最近はNFTやらDAOやらが授業依頼としては多く、お教えしているうちにプロジェクトが立ち上がってしまうという教育者冥利な事態に発展することもあるのでした。
さて今夜の話は以前にもQiitaに書いた公開鍵やイーサリアムアドレスをpythonで生成するお話。Pythonでそんなものを生成しても実務的なご利益はないのですが、秘密鍵からどうやって公開鍵やイーサリアムアドレスを生成するのか、その道筋を手を動かしてみてみるのは有意義だと思い、とある生徒さんにデジタル署名周りを教えるのを機に記事をリニューアルいたしました。
Colabを使えば何の悩みもなし
数年前まではanacondaやらVS Codeだとかに開発環境を作ることからお教えしたのですが、最近はColabという便利なものがあり、googleアカウントをお持ちなら環境構築は一瞬で終わるのでした。
https://colab.research.google.com/
pycryptodomeでKeccak-256
老人にありがちな長い前置きの後、いよいよハッシュ関数Keccak-256を使うことにします。
Keccakはケチャックと読みます(私は昔さんざん遊びに行ったバリ島への郷愁を込めてケチャと呼ぶかというと決して呼ばない、また余計なことを、かたじけない)。
まずはpycryptodomeからKeccak-256を使ってみましょう。The Python Package Index (PyPI)にあるようにpycryptodomeはなかなか多機能なライブラリです。
https://pypi.org/project/pycryptodome/
ライブラリのインストールは、上記の要領で立ち上げたColabで、次のコマンドを実行します。
Colabでpipするときはあたまに「!」をつけるがお作法です。
!pip install pycryptodome
これで、Ethereumの暗号ハッシュ関数Keccak-256を計算する準備ができました。
試しに、Colabで次のようなコードを実行してみましょう。
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'
helloの1文字を変えて例えばhelpoにしてみると出力が全然違うものになるのがハッシュ関数の特徴です。
from Crypto.Hash import keccak
import binascii
keccak256 = keccak.new(data=b'helpo', digest_bits=256).digest()
print("Keccak256:", binascii.hexlify(keccak256))
とすると
Keccak256: b'613072c793fc725fd65387060c7e6ba57f60c87c59808868888db49d25e195cc'
と似ても似つかぬ値に変わります。
pysha3でKeccak-256
次は結果は同じになるのですが、SHA-3のpython向けラッパーpysha3からハッシュ関数Keccak-256を使ってみます。解説はこちらをご覧ください。
https://github.com/tiran/pysha3
インストールはやはり前述の方法でColabにて、次のコマンドを実行します。
!pip install pysha3
これで、Ethereumの暗号ハッシュ関数Keccak-256を計算する準備ができました。
試しに、Colabで次のようなPythonコードを実行してみましょう。
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というのを使わせていただきます。
インストールはColabで、次のコマンドを実行します。
!pip install coincurve
そして、
from coincurve import PublicKey
private_key_hex = 'f8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315'
private_key = bytes.fromhex(private_key_hex)
public_key = PublicKey.from_valid_secret(private_key).format(compressed=False)[1:]
print('public key:', public_key.hex())
あっさりpublic keyのお出ましです。大丈夫なのか、と言うほどあっさり。
public key: 6e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b83b5c38e5e2b0c8529d7fa3f64d46daa1ece2d9ac14cab9477d042c84c32ccd0
(3) public keyからEthereum Addressを計算する
ここまでくれば後はpublic keyをまたハッシュ関数Keccak256に通して、その最後の20Byteを持ってくればそれがもうEthereum Addressです。16進数のエンコーディングを表すプレフィックス「0x」もつけてやります。
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です。
metamaskで生成したアカウントで秘密鍵からEthereum Addressを求めて、実際のアドレスと比べて答え合わせをしてみてください。
そうしたら、最後は暗号論的擬似乱数から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コードを使ってもらえると望外の幸せです。