Published Date : 2022年2月1日16:52

031 Pythonでビットコインを学ぶ (BASE58の仕組みをPythonと図を使って理解してみよう パート6)
031 Use python to learn bitcoin (Understand how BASE 58 works in using Python and diagrams Part 6)


This blog has an English translation


ニコニコ動画にアップした動画のまとめ記事です。

This is a summary blog post about a video I uploaded to NicoNico.

細かい部分は動画を参考にしてください。

Please refer to the video for details.


目次

Table of Contents




① 動画の説明
① Video Description



00:00 前回の動画の続きです。

00:00 This is a continuation of the previous video.

00:00 ビットコインアドレス生成に必要なbase58チェックのスクリプトを書いてみましょう。

00:00 Let's write a script for the base 58 check required to generate a bitcoin address.

00:00 まずは前回の動画で作成したbase58のスクリプトのおさらいです。

00:00 First, let's review the base 58 script I created in the previous video.

number = "0123456789"
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
alphanumeric = number + alphabet
misreadable_chars = ["I", "l", "0", "O"]
base58_chars = ""
for c in alphanumeric:
    if c not in misreadable_chars:
        base58_chars += c
base = len(base58_chars)

00:23 次に、バイト文字列と文字列のタイプの入力に対応する為の関数を作成します。

00:23 Next, we create a function for byte string and string type input.

def convert2byte_seq(string):
    if isinstance(string, str):
        return string.encode('utf8')
    elif isinstance(string, bytes) or isinstance(string, bytearray):
        return string
    else:
        raise TypeError("Please enter a string, byte sequence for the argument.")

00:37 ビットコインアドレスにはさまざまな接頭辞が付与されます。

00:37 Bitcoin addresses are given various prefixes.

00:37 例えばアドレスの接頭辞に[00]が与えられた時はメインネットを表します。

00:37 For example, an address prefix of [00] indicates the mainnet.

00:37 接頭辞[00]がアドレスに加えられた時の処理を前回作成したbase58エンコード関数に付け加えましょう。

00:37 Let's add what happens when a prefix [00] is added to an address to the base 58 encoding function we created last time.

def encode_base58(string):
    b_string = convert2byte_seq(string)
    original_length = len(b_string)
    b_string = b_string.lstrip(b'\0')
    length_after_removing = len(b_string)
    int_value = int.from_bytes(b_string, byteorder='big')
    quotient, remainder = int_value, 0
    list_of_remainders = []
    while quotient > 0:
        quotient, remainder = divmod(quotient, base)
        list_of_remainders.append(remainder)
    prefix = base58_chars[0] * (original_length - length_after_removing)
    return prefix + ''.join([base58_chars[r] for r in reversed(list_of_remainders)])

01:00 binasciiをインポートして、文字列を直接バイト列に変換して上記の動作を試してみましょう。

01:00 Import binascii and try the above by converting strings directly into bytes.

import binascii

01:03 base58では0は1を表します。

01:03 In base 58, 0 represents 1.

print(0 % 58)
0
print(base58_chars[0 % 58])
1

01:12 ただの文字列の場合、00は11になります。

01:12 If it is just a string, 00 becomes 11.

print('00')
00
print(''.join([base58_chars[0 % 58] for _ in range(2)]))
11

01:17 しかしバイト列では1バイトずつ計算されるので、00は1と表示されるのが正解です。

01:17 However, the correct answer is that 00 is displayed as 1 because each byte is calculated in a sequence of bytes.

print(binascii.unhexlify('00f6'))
b'\x00\xf6'
print(binascii.unhexlify('00f6').lstrip(b'\0'))
b'\xf6'
print(len(binascii.unhexlify('00f6')))
2
print(len(binascii.unhexlify('00f6').lstrip(b'\0')))
1
print(base58_chars[0] * (2 - 1))
1

01:41 ではデコードの関数も同じ様に作成しましょう。

01:41 Now let's create a decoding function as well.

def decode_base58(base58_encoded_string):
    original_length = len(base58_encoded_string)
    base58_encoded_string = base58_encoded_string.lstrip(base58_chars[0])
    length_after_removing = len(base58_encoded_string)
    base58_decoded_int_value = 0
    for idx, c in enumerate(reversed(base58_encoded_string)):
        remainder = base58_chars.find(c)
        int_value = remainder * (base ** idx)
        base58_decoded_int_value += int_value
    length = len(hex(base58_decoded_int_value)[2:])//2
    decoded_bytes = base58_decoded_int_value.to_bytes(length, byteorder='big')
    prefix = b'\0' * (original_length - length_after_removing)
    return prefix + decoded_bytes

01:57 前回の動画の時と同じように、文字列でテストしてみましょう。

01:57 Just like the previous video, let's test with strings.

string = "hello world"
base58_encoded_string = encode_base58(string)
print("encoded string:", base58_encoded_string)
decoded_bytes = decode_base58(base58_encoded_string)
print("decoded string:", decoded_bytes.decode('utf8'))

02:04 ビットコインアドレスを作る際には、base58チェックという作業を行います。

02:04 When you create a bitcoin address, you perform a task called base 58 check.

02:04 関数に渡されたバイト文字列をSHA-256で二重にハッシュして、その値の先頭の4バイトをチェックサムとして使用します。

02:04 Double-hash the byte string passed to the function with SHA-256 and use the first 4 bytes of the value as the checksum.

02:04 そして渡されたバイト文字列の後ろにチェックサムを付け足し、base58でエンコードします。

02:04 It then appends a checksum to the passed byte string and encodes it with base 58.

def base58_encode_check(s):
    s = convert2byte_seq(s)
    double_hash = hashlib.sha256(hashlib.sha256(s).digest()).digest()
    return encode_base58(s + double_hash[:4])

02:22 今度はそのアドレスが正しいかどうかの検証をします。

02:22 Now verify that the address is correct.

02:22 関数に渡された文字列をBASE58でデコードして、データ部分とチェックサムの部分に分けます。

02:22 Decodes the string passed to the function with BASE 58, separating the data and checksum parts.

02:22 後はデータ部分をまた二重にSHA-256で二重にハッシュして、その値とチェックサムが正しいかどうかを検証します。

02:22 The data portion is then double-hashed with SHA-256 to verify that its value and checksum are correct.

def base58_decode_check(base58_encode_check_string):
    decoded_bytes = decode_base58(base58_encode_check_string)
    decoded_bytes, check = decoded_bytes[:-4], decoded_bytes[-4:]
    double_hash = hashlib.sha256(
        hashlib.sha256(decoded_bytes).digest()).digest()
    if check != double_hash[:4]:
        raise ValueError(
            "An invalid checksum was detected. The value may have been tampered with.")
    return decoded_bytes

02:37 ただの文字列を使って、先ほど作ったbase58チェックのエンコードとデコードが正常に機能するかどうかを確かめてみましょう。

02:37 Let's use a plain string to see if the encoding and decoding of the base 58 check we just created works.

string = "abcdefgihjklmnopqrstuvwxyz"
base58_encoded_string = encode_base58(string)
print("encoded string:", base58_encoded_string)
base58_encode_check_string = base58_encode_check(string)
print("encode check string:", base58_encode_check_string)
decoded_bytes = decode_base58(base58_encoded_string)
print("decoded string:", decoded_bytes.decode('utf8'))
base58_decode_check_string = base58_decode_check(base58_encode_check_string)
print("decode cehck string:", base58_decode_check_string.decode('utf8'))
# デコードチェックが失敗した場合。
# If the decode check does not pass.
base58_decode_check(base58_encoded_string)

03:09 それではビットコインアドレスを作ってみましょう。

03:09 Let's make a bitcoin address.

03:09 まずは、pipを使って楕円曲線電子署名アルゴリズム(ECDSA)のサードパーティーモジュールであるecdsaをインストールしましょう。

03:09 First, use pip to install ecdsa, a third-party module for the Elliptic Curve Digital Signature Algorithm (ECDSA).

pip install ecdsa

03:09 因みにSECP256k1についてはこの動画シリーズのパート5を参照してください。

03:09 See Part 5 of this video series for SECP256k1.

sk = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)
vk = sk.verifying_key

03:24 パブリックキーには非圧縮と圧縮の二つの形があります。

03:24 Public keys come in two forms, uncompressed and compressed.

uncompressed_key = vk.to_string("uncompressed").hex()
print(f"uncompressed: {uncompressed_key}")
compressed_key = vk.to_string("compressed").hex()
print(f"compressed: {compressed_key}")
pt = vk.pubkey.point
print(hex(pt.x()))
print(hex(pt.y()))
print(hex(pt.y())[-2:])
print(int(hex(pt.y())[-2:], 16))
if int(hex(pt.y())[-2:], 16) % 2 == 0:
    print("Because the Y value is even, the prefix is '02'.")
else:
    print("Because the Y value is odd, the prefix is '03'.")
print(
    f"The X value ({hex(pt.x())[2:]}) is prefixed with '{compressed_key[:2]}' to give the compressed key value {compressed_key}.")

04:08 このように、非圧縮の形の場合は04というヘッダバイトがX32バイトとY32バイトの頭に付きます。

04:08 Thus, in the uncompressed form, a header byte of 04 is prepended to the X 32 bytes and Y 32 bytes.

04:08 そして、圧縮の形の場合は、Yの値が偶数の場合はヘッダバイトは02で、奇数の場合は03となり、そのヘッダバイトがX32バイトの頭に付きます。

04:08 Then, in the case of compression, if the Y value is even, the header byte is 02, if it is odd, the header byte is 03, and the header byte is prepended to the head of X 32 bytes.

04:08 特別な理由がなければ、普通は圧縮の形のパブリックキーを使用します。

04:08 Unless you have a specific reason to do so, you typically use public keys in the form of compression.

04:14 秘密鍵と検証鍵は生成する度に違う値となることを確認しましょう。

04:14 Make sure that the secret and verification keys have different values each time you generate them.

sk2 = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)
vk2 = sk2.verifying_key
uncompressed_key2 = vk2.to_string("uncompressed").hex()
print(f"uncompressed: {uncompressed_key2}")
compressed_key2 = vk2.to_string("compressed").hex()
print(f"compressed: {compressed_key2}")

04:50 ではビットコインアドレスを作る一連の流れを再現していきましょう。

04:50 Now let's recreate the sequence of creating bitcoin addresses.

# まずはECDSAを使い秘密鍵を生成し、その秘密鍵で公開鍵を生成します。
# First, use ECDSA to generate a private key and generate a public key with the private key.
ecdsa_secret_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)
ecdsa_verifying_key = ecdsa_secret_key.verifying_key
ecdsa_compressed_key = ecdsa_verifying_key.to_string("compressed").hex()
print("ECDSA Compressed Verifying Key: ", ecdsa_compressed_key)

05:01 binasciiのunhexlifyメソッドは16進数の文字列をバイナリ文字列へと変換します。

05:01 The binascii unhexlify method converts a hexadecimal number string into a binary string.

print("Ascii Text to Binary: ", binascii.unhexlify(ecdsa_compressed_key))

05:07 RIPEMD160はハッシュ関数の一種で、160ビット(20バイト)のハッシュ値を作り出します。

05:07 RIPEMD160 is a type of hash function that produces a 160 bit (20 byte) hash value.

05:07 hashlibモジュールでのRIPEMD160の使用は、標準ではSHA256のようにメソッドとして用意されていないので、下記のように使用します。

05:07 The use of RIPEMD160 in the hashlib module is not provided as a method like SHA256 by default, so use it as follows.

# そして、ハッシュ関数SHA-256を使用してその公開鍵のハッシュ値を導き出します。
# Then, it uses the hash function SHA-256 to derive a hash value for its public key.
hashed_with_SHA256 = hashlib.sha256(
    binascii.unhexlify(ecdsa_compressed_key)).hexdigest()
print("Value of ECDSA Compressed Verifying Key hashed with SHA-256: ",
      hashed_with_SHA256)

# ハッシュ関数RIPEMD-160を使い、Step 2で得られたハッシュ値のハッシュ値を得ます。
# Use the hash function RIPEMD-160 to obtain the hash value of the hash value obtained in Step 2.
hashed_with_RIPEMD160 = hashlib.new(
    'ripemd160', binascii.unhexlify(hashed_with_SHA256)).hexdigest()
print("Value of hashed with RIPEMD-160: ", hashed_with_RIPEMD160)

print(hashlib.algorithms_available)
print(hashlib.new('ripemd160', binascii.unhexlify(hashed_with_SHA256)).digest())
print(len(hashlib.new('ripemd160', binascii.unhexlify(hashed_with_SHA256)).digest()))

05:52 続いてプレフィクス(接頭辞)について簡単な説明です。

05:52 A brief description of the prefix follows.

05:52 ビットコインネットワークには実際にビットコインの取引を行う「メインネット」とビットコインの送信などを試すことができる「テストネット」というものがあります。

05:52 There are various kinds of bitcoin networks, such as [Mainnet] which actually trades bitcoin and and [Testnet] which can test the transmission of bitcoin.

05:52 これらを識別する場合は、メインネットが「0x00」、テストネットが「0x6F」というように16進数のプレフィックスがアドレスの先頭に付与されます。

05:52 To identify them, the address is prefixed with hexadecimal number prefix, such as [0x00] for the mainnet and [0x6F] for the testnet.

05:52 これらをBase58でエンコードするとメインネットが「1」、テストネットが「m(n)」でビットコインアドレスが始まるように表示されます。

05:52 Encoding them in Base 58 displays the mainnet as [1] and the testnet as [m(n)], starting with a bitcoin address.

05:52 他にもさまざまなプレフィックスが用意されていますが、今回はメインネットとテストネットのプレフィクスで試してみましょう。

05:52 There are a variety of other prefixes available, but this time we'll try the mainnet and testnet prefixes.

05:52 ハッシュ値の先頭にプレフィックスとして00を加える。(メインネットを表す)

05:52 Add the prefix 00 to the beginning of the hash value. (represents the mainnet)

05:52 ハッシュ値の先頭にプレフィックスとして6Fを加える。(テストネットを表す)

05:52 Add the prefix 6F to the beginning of the hash value. (represents the testnet)

05:52 ステップ4で生成されたバイト列を、SHA-256で二重にハッシュ化します。

05:52 Double-hash the byte sequence generated in Step 4 with SHA-256.

05:52 そしてその値の先頭4バイトをチェックサムとしてステップ4で生成されたバイト列の一番後ろに加えて、Base58でエンコーディングする。

05:52 Then, the first 4 bytes of the value are added as a checksum to the end of the sequence of bytes generated in step 4 and encode it in Base 58.

05:52 生成されたアドレスが正しいかどうかを検証してみましょう。

05:52 Let's verify that the generated address is correct.

# ハッシュ値の先頭にプレフィックスとして00を加える。(メインネットを表す)
# Add the prefix 00 to the beginning of the hash value. (represents the mainnet)
hash_with_mainnet_prefix = '00' + hashed_with_RIPEMD160
# ハッシュ値の先頭にプレフィックスとして6Fを加える。(テストネットを表す)
# Add the prefix 6F to the beginning of the hash value. (represents the testnet)
hash_with_testnet_prefix = '6f' + hashed_with_RIPEMD160
print("Mainnet: ", hash_with_mainnet_prefix)
print("Testnet: ", hash_with_testnet_prefix)

# ステップ4で生成されたバイト列を、SHA-256で二重にハッシュ化します。
# Double-hash the byte sequence generated in Step 4 with SHA-256.
double_hashed_mainnet = hashlib.sha256(hashlib.sha256(
    binascii.unhexlify(hash_with_mainnet_prefix)).digest()).hexdigest()
double_hashed_testnet = hashlib.sha256(hashlib.sha256(
    binascii.unhexlify(hash_with_testnet_prefix)).digest()).hexdigest()
print("Double hashed mainnet: ", double_hashed_mainnet)
print("Double hashed testnet: ", double_hashed_testnet)

# そしてその値の先頭4バイトをチェックサムとしてステップ4で生成されたバイト列の一番後ろに加えて、Base58でエンコーディングする。
# Then, the first 4 bytes of the value are added as a checksum to the end of the sequence of bytes generated in step 4 and encode it in Base 58.
cheksum_mainnet = double_hashed_mainnet[:8]
cheksum_testnet = double_hashed_testnet[:8]
print("Mainnet checksum (first 4 bytes): ", cheksum_mainnet)
print("Testnet checksum (first 4 bytes): ", cheksum_testnet)

mainnet_bitcoin_address = encode_base58(
    binascii.unhexlify(hash_with_mainnet_prefix + cheksum_mainnet))
testnet_bitcoin_address = encode_base58(
    binascii.unhexlify(hash_with_testnet_prefix + cheksum_testnet))
print("Mainnet Bitcoin Address: ", mainnet_bitcoin_address)
print("Testnet Bitcoin Address: ", testnet_bitcoin_address)

mainnet_bitcoin_address = base58_encode_check(
    binascii.unhexlify(hash_with_mainnet_prefix))
testnet_bitcoin_address = base58_encode_check(
    binascii.unhexlify(hash_with_testnet_prefix))
print("Mainnet Bitcoin Address: ", mainnet_bitcoin_address)
print("Testnet Bitcoin Address: ", testnet_bitcoin_address)

# 生成されたアドレスが正しいかどうかを検証してみましょう。
# Let's verify that the generated address is correct.
print("Test the decode check.")
base58_decode_check(mainnet_bitcoin_address) == binascii.unhexlify(
    hash_with_mainnet_prefix)
print("Passed test.")
print("Test the decode check.")
base58_decode_check(testnet_bitcoin_address) == binascii.unhexlify(
    hash_with_testnet_prefix)
print("Passed test.")

07:23 それでは一連の流れを復習してみましょう。

07:23 Let's review the sequence.

07:23 先ほどまでの解説のスクリプトは、分かりやすくする為にバイト文字列を16進数表記の文字列に直していました。

07:23 In the previous script, the byte string was changed to a hexadecimal number string for clarity.

07:23 今回はそれをそのままバイト文字列として処理していきます。

07:23 This time we will process it as a byte string.

07:23 Step 1: ECDSAを使い秘密鍵を生成し、その秘密鍵で公開鍵を生成します。

07:23 Step 1: Use ECDSA to generate a private key and generate a public key with the private key.

07:23 Step 2: ハッシュ関数SHA-256を使用してその公開鍵のハッシュ値を導き出します。

07:23 Step 2: It uses the hash function SHA-256 to derive a hash value for its public key.

07:23 Step 3: ハッシュ関数RIPEMD-160を使い、Step 2で得られたハッシュ値のハッシュ値を得ます。

07:23 Step 3: Use the hash function RIPEMD-160 to obtain the hash value of the hash value obtained in Step 2.

07:23 Step 4: ハッシュ値の先頭にプレフィックスとして00を加える。(メインネットを表す)

07:23 Step 4: Add the prefix 00 to the beginning of the hash value. (represents the mainnet)

07:23 Step 5: ステップ4で生成されたバイト列を、SHA-256で二重にハッシュ化します。

07:23 Step 5: Double-hash the byte sequence generated in Step 4 with SHA-256.

07:23 Step 6: 生成されたアドレスが正しいかどうかを検証する。

07:23 Step 6: Verify that the generated address is correct.

# Step 1: ECDSAを使い秘密鍵を生成し、その秘密鍵で公開鍵を生成します。
# Step 1: Use ECDSA to generate a private key and generate a public key with the private key.
ecdsa_secret_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)
ecdsa_verifying_key = ecdsa_secret_key.verifying_key
ecdsa_compressed_key = ecdsa_verifying_key.to_string("compressed")
print(ecdsa_compressed_key)
# Step 2: ハッシュ関数SHA-256を使用してその公開鍵のハッシュ値を導き出します。
# Step 2: It uses the hash function SHA-256 to derive a hash value for its public key.
hashed_with_SHA256 = hashlib.sha256(ecdsa_compressed_key).digest()
print(hashed_with_SHA256)
# Step 3: ハッシュ関数RIPEMD-160を使い、Step 2で得られたハッシュ値のハッシュ値を得ます。
# Step 3: Use the hash function RIPEMD-160 to obtain the hash value of the hash value obtained in Step 2.
hashed_with_RIPEMD160 = hashlib.new('ripemd160', hashed_with_SHA256).digest()
print(hashed_with_RIPEMD160)
# Step 4: ハッシュ値の先頭にプレフィックスとして00を加える。(メインネットを表す)
# Step 4: Add the prefix 00 to the beginning of the hash value. (represents the mainnet)
hash_with_mainnet_prefix = b'\0' + hashed_with_RIPEMD160
print(hash_with_mainnet_prefix)
# Step 5: ステップ4で生成されたバイト列を、SHA-256で二重にハッシュ化します。
# Step 5: Double-hash the byte sequence generated in Step 4 with SHA-256.
# そしてその値の先頭4バイトをチェックサムとしてステップ4で生成されたバイト列の一番後ろに加えて、Base58でエンコーディングする。
# Then, the first 4 bytes of the value are added as a checksum to the end of the sequence of bytes generated in step 4 and encode it in Base 58.
bitcoin_address = base58_encode_check(hash_with_mainnet_prefix)
print("Bitcoin Address: ", bitcoin_address)
# Step 6: 生成されたアドレスが正しいかどうかを検証する。
# Step 6: Verify that the generated address is correct.
print("Test the decode check.")
print(base58_decode_check(bitcoin_address))
print("Passed test.")


以上です。お疲れ様です。

That's all. Thank you for your hard work.