Ccmmutty logo
Commutty IT
7 min read

Pythonでイーサリアムで使われている暗号技術を学ぶ ① - 秘密鍵・楕円曲線・公開鍵・アドレス -

https://cdn.magicode.io/media/notebox/4dad60ef-fdd3-4a58-8fdd-d74ae84f3cc0.jpeg

イーサリアムにおける暗号技術

イーサリアムでは資金(イーサ)の所有権や取引の管理をするために暗号技術が用いられている。一方、イーサリアムプロトコルには用いられていないため、イーサリアムネットワーク上の全てのコミュニケーションや状態の遷移は誰でも読むことができ、透明性の高い状態で合意形成することができる。
イーサリアムでは2種類のアカウントがある。
  • トランザクション外部所有アカウント(EOA):イーサリアムネットワークのユーザーによって、ユーザーのために作成されるアカウント。取引の主体。
  • コントラクトアカウント:EOA・他のコントラクトアカウントから受け取ったイーサを用いて何らかの処理をするコードが入っているアカウント。これ自身が取引を行うわけではない。
秘密鍵はEOAは一対一で結びついていて、個人が確実にプライベートで安全な方法で管理しなければならず、外部に漏れてはならないパスワードみたいなものである。この秘密鍵を知られずに、イーサの所有権・イーサの取引の許可などを証明するために、暗号技術が用いられている。
登場人物は主に3種類で、
  • 秘密鍵
  • イーサリアムアドレス
  • デジタル署名 である。
今回の記事では最後のデジタル署名以外の部分について見ていこうと思う。

秘密鍵

11~22562^{256}から無作為に選べられた数字と同義の文字列である。ちなみに、宇宙の原子数は108010^{80}で、22562^{256}107710^{77}の規模なので、宇宙に存在する全ての原子にイーサリアムアカウントを与えるのにほぼ十分な秘密鍵が存在するらしい。
秘密鍵は確実に安全な方法で管理しなければならず、外部に漏れてはならない。デジタルではなく、アナログで管理する方法がよく薦められている。
この秘密鍵を用いて、イーサリアムアドレスやデジタル署名が生成される。そして、秘密鍵が知られることのないようなシステムで、それらの検証がなされる。そのシステムに暗号技術が用いられている。
Pythonではecdsaというライブラリが暗号周辺のさまざまな関数を提供しています。
python
!pip install ecdsa

Collecting ecdsa
Downloading ecdsa-0.17.0-py2.py3-none-any.whl (119 kB) [?25l |██▊ | 10 kB 32.8 MB/s eta 0:00:01 |█████▌ | 20 kB 19.0 MB/s eta 0:00:01 |████████▎ | 30 kB 10.3 MB/s eta 0:00:01
|███████████ | 40 kB 4.5 MB/s eta 0:00:01 |█████████████▊ | 51 kB 4.0 MB/s eta 0:00:01 |████████████████▌ | 61 kB 4.7 MB/s eta 0:00:01 |███████████████████▏ | 71 kB 4.8 MB/s eta 0:00:01 |██████████████████████ | 81 kB 3.9 MB/s eta 0:00:01 |████████████████████████▊ | 92 kB 4.4 MB/s eta 0:00:01 |███████████████████████████▌ | 102 kB 4.8 MB/s eta 0:00:01 |██████████████████████████████▏ | 112 kB 4.8 MB/s eta 0:00:01 |████████████████████████████████| 119 kB 4.8 MB/s [?25hRequirement already satisfied: six>=1.9.0 in /srv/conda/envs/notebook/lib/python3.7/site-packages (from ecdsa) (1.16.0)
Installing collected packages: ecdsa
Successfully installed ecdsa-0.17.0
python
from ecdsa import SECP256k1
from ecdsa import SigningKey

sk = SigningKey.generate(curve=SECP256k1) #this is your sign (private key)
private_key = sk.to_string().hex() #private key to hex (=16進数)
print("Private key (hex): ",private_key)

Private key (hex): cabf51d9a6456500717471f18a3e7b6707bcaa5cad168dc4a35fc487ed6e7b39
256bitは4bitごとに16進数にエンコードされ、64桁で表示されます。
python
len(private_key)

64

公開鍵・イーサリアムアドレス

イーサリアムアドレスは公に公開できるアカウント名で、公開鍵から生成される。

公開鍵

公開鍵は公にできるが、秘密鍵は公にしてはならない。ということは公開鍵から秘密鍵を推測できてはならない。一方、秘密鍵から公開鍵は生成されるため、生成関数として現代のコンピュータシステムでよく用いられている楕円曲線暗号(Elliptic curve cryptography)が採用されている。イーサリアムではsecp256k1が採用されている。
https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:Secp256k1.png
関数の詳細は、xxを秘密鍵、yyを公開鍵、p=225623229282726241p = 2^{256}-2^{32}-2^9-2^8-2^7-2^6-2^4-1(巨大な素数ということ)としたときに、
y2modp=(x3+7)modpy^2\bmod p = (x^3+7) \bmod p
となる。
例えば、ppがデカすぎるので、p=17p=17としたときに、これを満たす点は(x,y)=(1,5)(x,y)=(1,5)である。
python
p = 17
x = 1
y = 5
(y**2 - x**3 - 7) % p

0
楕円曲線上では点同士の加算が可能、つまり乗算も可能である。 例えば、p1+p2=p3p_1+p_2=p_3において、p3p_3p1p_1p2p_2を通る直線と楕円曲線の交点p3(x,y)p'_3(x,y)に対してp3(x,y)p_3(x,-y)と定義される。さまざまな都合の良い性質が重なり、点同士の加算が可能となり、従って乗算も可能となっている。
ある生成点GGが与えられた時に、それを秘密鍵kkの値分だけかければ公開鍵KKが計算される。
K=kGK = k * G
つまり、秘密鍵kkから公開鍵KKを生成することはGG同士の加算をkk回繰り返すということである。
この順方向の計算は簡単だが、KKからkkを導き出すのはほぼ不可能であると言われている。
python
vk = sk.get_verifying_key() #this is your verification key (public key)
public_key_hex = vk.to_string().hex()
print("Public key (hex): {}".format(public_key_hex))

Public key (hex): 376ad4b80d4e6ae0390b4af3bd548a86f3cd6581d5d7220ec185ebff63115817873b071783065c2a8e44528f2e707fbbc041ef6ee10afea92e97c30bf709c131
python
public_key_x = public_key_hex[:64]
public_key_y = public_key_hex[64:]
print("x of Public key: {}".format(public_key_x))
print("y of Public key: {}".format(public_key_y))

x of Public key: 376ad4b80d4e6ae0390b4af3bd548a86f3cd6581d5d7220ec185ebff63115817 y of Public key: 873b071783065c2a8e44528f2e707fbbc041ef6ee10afea92e97c30bf709c131
イーサリアムでは圧縮されていない公開鍵のみを使用する。
Public keyのx,y座標はそれぞれ16進数で64桁の数字で表され、非圧縮点を意味する"04" prefixを前につけて、以下のようにシリアライズされる。
04+x+y04 + x + y
python
public_key = "04" + public_key_x + public_key_y
print("Public key (serialized): {}".format(public_key))

Public key (serialized): 04376ad4b80d4e6ae0390b4af3bd548a86f3cd6581d5d7220ec185ebff63115817873b071783065c2a8e44528f2e707fbbc041ef6ee10afea92e97c30bf709c131

イーサリアムアドレス

イーサリアムアドレスはKeccak-256というハッシュ関数を用いて公開鍵から生成される。
そもそもハッシュ関数とは「任意のサイズのデータを固定のサイズのデータにマップさせるために使用することができる任意の関数」であり、安全性が重視されるようなプラットフォームに有用な特性を持っているハッシュ関数は特に暗号ハッシュ関数と言われる。Keccak-256はその一つである。
アドレスを生成する際には公開鍵にprefixをつける必要はなく、そのままの16進数を入力すれば良い。
python
!pip install pysha3

Collecting pysha3
Downloading pysha3-1.0.2.tar.gz (829 kB) [?25l |▍ | 10 kB 25.8 MB/s eta 0:00:01 |▉ | 20 kB 17.4 MB/s eta 0:00:01 |█▏ | 30 kB 10.3 MB/s eta 0:00:01 |█▋ | 40 kB 4.5 MB/s eta 0:00:01 |██ | 51 kB 4.0 MB/s eta 0:00:01 |██▍ | 61 kB 4.7 MB/s eta 0:00:01 |██▊ | 71 kB 4.9 MB/s eta 0:00:01 |███▏ | 81 kB 3.9 MB/s eta 0:00:01 |███▋ | 92 kB 4.4 MB/s eta 0:00:01 |████ | 102 kB 4.8 MB/s eta 0:00:01 |████▍ | 112 kB 4.8 MB/s eta 0:00:01 |████▊ | 122 kB 4.8 MB/s eta 0:00:01 |█████▏ | 133 kB 4.8 MB/s eta 0:00:01 |█████▌ | 143 kB 4.8 MB/s eta 0:00:01 |██████ | 153 kB 4.8 MB/s eta 0:00:01 |██████▎ | 163 kB 4.8 MB/s eta 0:00:01 |██████▊ | 174 kB 4.8 MB/s eta 0:00:01 |███████▏ | 184 kB 4.8 MB/s eta 0:00:01 |███████▌ | 194 kB 4.8 MB/s eta 0:00:01 |████████ | 204 kB 4.8 MB/s eta 0:00:01 |████████▎ | 215 kB 4.8 MB/s eta 0:00:01 |████████▊ | 225 kB 4.8 MB/s eta 0:00:01 |█████████ | 235 kB 4.8 MB/s eta 0:00:01 |█████████▌ | 245 kB 4.8 MB/s eta 0:00:01 |█████████▉ | 256 kB 4.8 MB/s eta 0:00:01 |██████████▎ | 266 kB 4.8 MB/s eta 0:00:01 |██████████▊ | 276 kB 4.8 MB/s eta 0:00:01 |███████████ | 286 kB 4.8 MB/s eta 0:00:01 |███████████▌ | 296 kB 4.8 MB/s eta 0:00:01 |███████████▉ | 307 kB 4.8 MB/s eta 0:00:01 |████████████▎ | 317 kB 4.8 MB/s eta 0:00:01 |████████████▋ | 327 kB 4.8 MB/s eta 0:00:01 |█████████████ | 337 kB 4.8 MB/s eta 0:00:01 |█████████████▍ | 348 kB 4.8 MB/s eta 0:00:01 |█████████████▉ | 358 kB 4.8 MB/s eta 0:00:01 |██████████████▎ | 368 kB 4.8 MB/s eta 0:00:01 |██████████████▋ | 378 kB 4.8 MB/s eta 0:00:01 |███████████████ | 389 kB 4.8 MB/s eta 0:00:01 |███████████████▍ | 399 kB 4.8 MB/s eta 0:00:01 |███████████████▉ | 409 kB 4.8 MB/s eta 0:00:01 |████████████████▏ | 419 kB 4.8 MB/s eta 0:00:01 |████████████████▋ | 430 kB 4.8 MB/s eta 0:00:01 |█████████████████ | 440 kB 4.8 MB/s eta 0:00:01 |█████████████████▍ | 450 kB 4.8 MB/s eta 0:00:01 |█████████████████▉ | 460 kB 4.8 MB/s eta 0:00:01 |██████████████████▏ | 471 kB 4.8 MB/s eta 0:00:01 |██████████████████▋ | 481 kB 4.8 MB/s eta 0:00:01 |███████████████████ | 491 kB 4.8 MB/s eta 0:00:01 |███████████████████▍ | 501 kB 4.8 MB/s eta 0:00:01 |███████████████████▊ | 512 kB 4.8 MB/s eta 0:00:01 |████████████████████▏ | 522 kB 4.8 MB/s eta 0:00:01 |████████████████████▌ | 532 kB 4.8 MB/s eta 0:00:01 |█████████████████████ | 542 kB 4.8 MB/s eta 0:00:01 |█████████████████████▍ | 552 kB 4.8 MB/s eta 0:00:01 |█████████████████████▊ | 563 kB 4.8 MB/s eta 0:00:01 |██████████████████████▏ | 573 kB 4.8 MB/s eta 0:00:01 |██████████████████████▌ | 583 kB 4.8 MB/s eta 0:00:01 |███████████████████████ | 593 kB 4.8 MB/s eta 0:00:01 |███████████████████████▎ | 604 kB 4.8 MB/s eta 0:00:01 |███████████████████████▊ | 614 kB 4.8 MB/s eta 0:00:01 |████████████████████████ | 624 kB 4.8 MB/s eta 0:00:01 |████████████████████████▌ | 634 kB 4.8 MB/s eta 0:00:01 |█████████████████████████ | 645 kB 4.8 MB/s eta 0:00:01 |█████████████████████████▎ | 655 kB 4.8 MB/s eta 0:00:01 |█████████████████████████▊ | 665 kB 4.8 MB/s eta 0:00:01 |██████████████████████████ | 675 kB 4.8 MB/s eta 0:00:01
|██████████████████████████▌ | 686 kB 4.8 MB/s eta 0:00:01 |██████████████████████████▉ | 696 kB 4.8 MB/s eta 0:00:01 |███████████████████████████▎ | 706 kB 4.8 MB/s eta 0:00:01 |███████████████████████████▋ | 716 kB 4.8 MB/s eta 0:00:01 |████████████████████████████ | 727 kB 4.8 MB/s eta 0:00:01 |████████████████████████████▌ | 737 kB 4.8 MB/s eta 0:00:01 |████████████████████████████▉ | 747 kB 4.8 MB/s eta 0:00:01 |█████████████████████████████▎ | 757 kB 4.8 MB/s eta 0:00:01 |█████████████████████████████▋ | 768 kB 4.8 MB/s eta 0:00:01 |██████████████████████████████ | 778 kB 4.8 MB/s eta 0:00:01 |██████████████████████████████▍ | 788 kB 4.8 MB/s eta 0:00:01 |██████████████████████████████▉ | 798 kB 4.8 MB/s eta 0:00:01 |███████████████████████████████▏| 808 kB 4.8 MB/s eta 0:00:01 |███████████████████████████████▋| 819 kB 4.8 MB/s eta 0:00:01 |████████████████████████████████| 829 kB 4.8 MB/s [?25h
Preparing metadata (setup.py) ... [?25l
-
 done [?25hBuilding wheels for collected packages: pysha3 Building wheel for pysha3 (setup.py) ... [?25l
-
 \
 |
 done [?25h Created wheel for pysha3: filename=pysha3-1.0.2-cp37-cp37m-linux_x86_64.whl size=135833 sha256=e070c4485b4abeee0e335cd59332c019b1f5d1ff866102ec0d1967bc39fac3d9 Stored in directory: /home/jovyan/.cache/pip/wheels/0d/9e/bc/789fa0986c1fef30cafcc29da4dd07bc17ecba3fab78e27ed6 Successfully built pysha3
Installing collected packages: pysha3
Successfully installed pysha3-1.0.2
python
import sha3

k = sha3.keccak_256()
k.update(public_key_hex.encode("utf-8"))
k_output = k.hexdigest()
k_output

'1d151ad434793918260514a465279d2d307afa1d271eeac45f7d29366b6f83fb'
アドレスには最後の20バイト(16進数では最後の40文字)を用いる。
python
address = k_output[-40:]
address

'65279d2d307afa1d271eeac45f7d29366b6f83fb'
0x prefixをつけることもある。
python
address_prefix = "0x" + address
address_prefix

'0x65279d2d307afa1d271eeac45f7d29366b6f83fb'

チェックサム・EIP-55

アドレスの入力ミス・誤読などを防ぐためにチェックサムという機能が必要である。イーサリアムの性質上、ビルトインチェックサム機能はないため、代替手段としてのアドレスのエンコーディング法が用いられている。最近よく用いられているのが、EIP-55(Ethereum Improvement Proposal 55)である。
これはアドレスの一部をある規則に基づいて大文字にするというエンコーディング法である。従来のアドレス識別では使われなかった大文字小文字を検証に用いることで、高精度でエラーを検出することができる。
次にエンコーディングの過程を順に見ていこう。
①アドレスをkeccak256に突っ込む
python
a_k = sha3.keccak_256()
a_k.update(address.encode("utf-8"))
a_k_output = k.hexdigest()
a_k_output

'1d151ad434793918260514a465279d2d307afa1d271eeac45f7d29366b6f83fb'
②アドレスとハッシュ値を並べて、ハッシュ値に対応する16進数が8以上の場合に、対応するアドレスの文字がアルファベットだったら大文字にする
python
address_EIP = ""
for i in range(40):
  if int('0x'+a_k_output[i], 0) >= 8:
    if address[i].isalpha():
      address_EIP += address[i].upper()
    else:
      address_EIP += address[i]
  else:
    address_EIP += address[i]
address_EIP

'65279D2d307AfA1D271eeaC45f7d29366b6F83fB'
以上まとめると、
python
def get_hash(address):
  k = sha3.keccak_256()
  k.update(address.encode("utf-8"))
  hash_value = k.hexdigest()
  return hash_value

def EIP_55(address,hash_value):
  address_EIP = ""
  for i in range(40):
    if int('0x'+hash_value[i], 0) >= 8:
      if address[i].isalpha():
        address_EIP += address[i].upper()
      else:
        address_EIP += address[i]
    else:
      address_EIP += address[i]
  return address_EIP

エラーの検出

それではEIP-55を用いてエラーの検出をしてみよう。試しに正しいEIPエンコーディング後のアドレスの一番最後の文字を1bitズレて入力してしまったと仮定する。
python
hash_value = get_hash(address)
address_EIP = EIP_55(address,hash_value)
last_decimal = int('0x'+address_EIP[-1], 0) + 1 % 16
last_hex = hex(last_decimal)[2:]
address_wrong = address_EIP[:-1] + last_hex

print("Correct Address: {}".format(address_EIP))
print("Wrong Address: {}".format(address_wrong))

Correct Address: 65279d2d307afA1d271eEAC45F7D29366B6f83Fb Wrong Address: 65279d2d307afA1d271eEAC45F7D29366B6f83F7
間違っているアドレスを小文字にしてハッシュ値を求めると1bitの差が大きなハッシュの変化をもたらすことがわかります。
python
address_wrong_lower = address_wrong.lower()
hash_wrong = get_hash(address_wrong_lower)
print("Correct Hash: {}".format(hash_value))
print("Wrong Hash: {}".format(hash_wrong))

Correct Hash: 5a5e70d68ba17ef0f6b3f8a9eb5b0c602de4d895cda8a1d65aacac97b022c687 Wrong Hash: bf613466ac413d976fde9054be8c6ee2946757c8743c11481b04733afb868570
さらに間違ったハッシュ値からEIP-55で大文字化してみます。
python
address_EIP_wrong = EIP_55(address_wrong_lower,hash_wrong)
print("Wrong Address:\n{}".format(address_wrong))
print("Checksum:\n{}".format(address_EIP_wrong))

Wrong Address: 65279d2d307afA1d271eEAC45F7D29366B6f83F7 Checksum: 65279d2d307afA1d271EEac45F7D29366b6f83F7
すると、入力した大文字のアドレスと計算されたチェックサムが違うことがわかります。これは、入力されたアドレスでEIPエンコーディングとの整合性がない変更があったことを意味し、何かがおかしいことを示しています。このようにして、EIP-55エンコーディングを用いて、アドレスが正しいものかどうか検証することができます。

まとめ

今回は秘密鍵・公開鍵・イーサリアムアドレスについてPythonを用いて検証しながら学んでみた。次は今回触れていないデジタル署名について学んでいきたいと思う。
何か違うことなどあればご指摘お願いしますm(_ _)m

Discussion

コメントにはログインが必要です。