[EIP55] Ethereumアドレスのチェックサムの仕組みを理解しよう！

# はじめに

初めまして。
**CryptoGames**というブロックチェーンゲーム企業でエンジニアをしている **cardene（かるでね）** です！
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

https://cryptogames.co.jp/

代表的なゲームは**クリプトスペルズ**というブロックチェーンゲームです。

https://cryptospells.jp/

今回は、Ethereumでの大文字と小文字が混在するアドレスのチェックサムの仕組みを提案している規格である**EIP55**についてまとめていきます！

:::note info
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-55

他にも様々なERCについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

# 仕様

```python
import eth_utils


def checksum_encode(addr): # Takes a 20-byte binary address as input
    hex_addr = addr.hex()
    checksummed_buffer = ""

    # Treat the hex address as ascii/utf-8 for keccak256 hashing
    hashed_address = eth_utils.keccak(text=hex_addr).hex()

    # Iterate over each character in the hex address
    for nibble_index, character in enumerate(hex_addr):

        if character in "0123456789":
            # We can't upper-case the decimal digits
            checksummed_buffer += character
        elif character in "abcdef":
            # Check if the corresponding hex digit (nibble) in the hash is 8 or higher
            hashed_address_nibble = int(hashed_address[nibble_index], 16)
            if hashed_address_nibble > 7:
                checksummed_buffer += character.upper()
            else:
                checksummed_buffer += character
        else:
            raise eth_utils.ValidationError(
                f"Unrecognized hex character {character!r} at position {nibble_index}"
            )

    return "0x" + checksummed_buffer


def test(addr_str):
    addr_bytes = eth_utils.to_bytes(hexstr=addr_str)
    checksum_encoded = checksum_encode(addr_bytes)
    assert checksum_encoded == addr_str, f"{checksum_encoded} != expected {addr_str}"


test("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
test("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359")
test("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB")
test("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb")
```

:::note info
このコードは、イーサリアム（Ethereum）のアドレスをチェックサムエンコードするためのPython関数と、それをテストするための関数を提供しています。
以下の手順に従ってコードの機能と動作を詳細に説明します。

### `checksum_encode`
- `checksum_encode`関数は、`20`バイトのバイナリアドレスを受け取り、チェックサム付きの形式で表現します。
- まず、入力のバイナリアドレスを`16`進数文字列に変換します（`addr.hex()`を使用）。
- 次に、`16`進数文字列をUTF-8エンコードしたものとして扱い、それを`keccak256`ハッシュ関数を用いてハッシュ化します。
    - このハッシュ化は、アドレスのチェックサム計算に使用されます。
- その後、`16`進数文字列を一文字ずつ処理します。
    - 各文字に対して以下の条件分岐を行います。
    - 数字（`0`から`9`）の場合
        - 文字をそのまま追加します。
    - 小文字の`16`進数文字（`a`から`f`）の場合
        - 対応するハッシュ値の対応するニブル（`4`ビット単位）が`8`以上の場合、大文字に変換して追加し、そうでない場合は小文字のまま追加します。
        - これにより、チェックサムの計算に寄与する文字を大文字で表現します。
    - 上記以外の文字の場合
        - エラーを発生させます。
        - これにより、アドレス文字列が`16`進数の文字以外を含む場合にエラーが報告されます。
- 最終的に、すべての文字を処理したら、**0x**を接頭辞としてチェックサムエンコードされたアドレス文字列を返します。

### `test`
- `test`関数は、アドレス文字列を受け取り、それが`checksum_encode`関数によって正しくエンコードされるかをテストします。
- 与えられたアドレス文字列を`16`進数のバイト列に変換し、`checksum_encode`関数を呼び出してチェックサムエンコードを行います。
- チェックサムエンコードされたアドレス文字列が元のアドレス文字列と一致しない場合、アサーションエラーが発生し、エラーメッセージが表示されます。

最後に、提供されたテストケースで`test`関数が呼び出されており、各アドレス文字列が正しくチェックサムエンコードされるかどうかを確認しています。

このコードは、Ethereumのアドレス文字列のチェックサムを計算するために使用できるユーティリティ関数を提供し、それが正しく機能することを確認するテストも提供しています。
:::


この要求に基づいて、アドレスを16進数文字列に変換し、特定の条件に従って大文字または小文字で出力するプロセスを説明します。
また、指定された条件に従った例も示します。

1. アドレスを`16`進数文字列に変換します。
    - 例
        - アドレス`0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed`は`5aaeb6053f3e94c9b9a09f33669435e7ef1beaed`に変換されます。
2. `16`進数文字列を小文字に変換し、UTF-8エンコードして、`keccak256`ハッシュ関数を使用してハッシュ値を計算します。
    - 例
        - ハッシュ化により、バイナリ表現`0010 1101 1000 0000 0101 1111 0010 0001 ...`が得られます。
3. ハッシュ値の各ビットを調べ、`4`ビットごとに文字を処理します。
4. 各文字が数字（`0`から`9`）である場合、そのまま出力します。
    - 例
        - `5`は数字であるため、そのまま出力されます。
5. 各文字が小文字の`16`進数文字（`a`から`f`）である場合、対応するハッシュ値の`4`ビット目（`0`から`3`のビット位置）を確認します。
    - 4ビット目が`1`であれば、その文字を大文字で出力し、`0`であれば小文字で出力します。
    - 例1
        - `a`は小文字であり、ハッシュ値の`4`ビット目が`1`であるため、大文字で出力されます（`A`）。
    - 例2
        - `f`は小文字であり、ハッシュ値の`4`ビット目が`0`であるため、小文字で出力されます（`f`）。
6. 上記の条件に当てはまらない場合、そのまま出力します。

上記のプロセスにより、指定された条件に基づいて、アドレスの各`16`進数文字が大文字または小文字で正確に出力されます。
特定の言葉や略語についても、指定された条件に従って変換されます。

# 補足

## 利点

- 既存の多くの`16`進数パーサーと互換性があり、大文字と小文字を混在して受け入れることができるため、徐々に導入するのが容易です。
    - これは、従来の`16`進数アドレス表現と新しいチェックサムエンコード表現の両方が受け入れられることを意味します。
- アドレスの長さを`40`文字に保ちます。
   - アドレスの長さは変わらず、従来の形式と同じ`40`文字です。
- 平均的にアドレスごとに`15`個のチェックビットがあり、ランダムに生成されたアドレスが誤ってチェックを通過する確率は約`0.0247％`です。
    - これはICAPに比べて約`50`倍の改善であり、`4`バイトのチェックコードに比べると劣ります。
    - チェックサムを使用することで、アドレスの誤りを検出しやすくし、間違って受け入れられる確率を大幅に減少させます。
   - ただし、`4`バイトのチェックコードに比べると精度は劣ります。

:::note info
新しいチェックサムエンコード方法には、アドレス表現の改善に関するいくつかの利点があります。
まず、既存の`16`進数パーサーとの互換性を保持し、新しい方法を段階的に導入することが容易です。
また、アドレスの長さは`40`文字のままであり、形式の変更が必要ありません。
さらに、アドレスごとに平均的に`15`個のチェックビットがあり、ランダムに生成されたアドレスが誤ってチェックを通過する確率は非常に低いため、エラーの検出が向上します。
これにより、アドレスの信頼性が高まりますが、`4`バイトのチェックコードに比べるとわずかに劣る精度を持っています。
:::

# 実装

```javascript
const createKeccakHash = require('keccak')

function toChecksumAddress (address) {
  address = address.toLowerCase().replace('0x', '')
  var hash = createKeccakHash('keccak256').update(address).digest('hex')
  var ret = '0x'

  for (var i = 0; i < address.length; i++) {
    if (parseInt(hash[i], 16) >= 8) {
      ret += address[i].toUpperCase()
    } else {
      ret += address[i]
    }
  }

  return ret
}
```

:::note info
イーサリアム（Ethereum）のアドレス文字列をチェックサムエンコードするためのJavaScript関数`toChecksumAddress`を実装しています。
以下はコードの機能と動作の詳細な説明です。

### `createKeccakHash`ライブラリのインポート
最初に、`createKeccakHash`ライブラリを`require`を使ってインポートしています。
このライブラリは、**Keccak-256**ハッシュ関数を提供します。

### `toChecksumAddress`関数
`toChecksumAddress`関数は、チェックサムを適用するためのメインの関数です。
引数として、チェックサムを適用したいアドレス文字列を受け取ります。

### アドレスの前処理
関数内で、受け取ったアドレス文字列を小文字に変換し、**0x**の接頭辞を取り除きます。
これにより、アドレス文字列が小文字で始まることと、接頭辞**0x**が存在しないことが保証されます。

### アドレスのハッシュ化
- `createKeccakHash('keccak256')`を使用して、**Keccak-256**ハッシュ関数を初期化します。
- `update(address)`メソッドを使用して、前処理されたアドレス文字列をハッシュ関数に渡し、ハッシュ値を計算します。
- `digest('hex')`メソッドを使用して、ハッシュ値を`16`進数文字列として取得します。

### チェックサムの計算
- ハッシュ値と前処理されたアドレス文字列を比較し、チェックサムを計算します。
- `for`ループを使用して、アドレス文字列の各文字に対して以下の条件分岐を行います。
    - アドレス文字列の各文字を`16`進数に変換し、`10`進数として取得します（`parseInt(hash[i], 16)`）。
    - 取得した`10`進数が`8`以上の場合、対応するアドレス文字を大文字に変換して`ret`変数に追加します。
        - これにより、チェックサムの計算に寄与する文字が大文字で表現されます。
    - それ以外の場合、アドレス文字をそのまま`ret`変数に追加します。
### チェックサムエンコードされたアドレスの返却
- `ret`変数にはチェックサムエンコードされたアドレス文字列が含まれています。
- 関数は最終的にこの文字列を`return`して返します。

このコードは、イーサリアムのアドレス文字列にチェックサムを適用し、大文字と小文字を適切に変換して、アドレスの正確性を向上させるためのユーティリティ関数を提供しています。
:::

```javascript
> toChecksumAddress('0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359')
'0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359'
```

**Keccak-256**ハッシュへの入力は、小文字の`16`進文字列（つまり、ASCIIとしてエンコードされた`16`進アドレス）であることに注意する。

```javascript
var hash = createKeccakHash('keccak256').update(Buffer.from(address.toLowerCase(), 'ascii')).digest()
```

# テスト

```text
# All caps
0x52908400098527886E0F7030069857D2E4169EE7
0x8617E340B3D01FA5F11F306F4090FD50E238070D
# All Lower
0xde709f2102306220921060314715629080e2fb77
0x27b1fdb04752bbc536007a920d24acb045561c26
# Normal
0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed
0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359
0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB
0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb
```

# 引用

Vitalik Buterin <vitalik.buterin@ethereum.org>, Alex Van de Sande <avsa@ethereum.org>, "ERC-55: Mixed-case checksum address encoding," Ethereum Improvement Proposals, no. 55, January 2016. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-55.

# 最後に

今回は「Ethereumでの大文字と小文字が混在するアドレスのチェックサムの仕組みを提案している規格である**EIP55**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58


https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4