有限体とは

位数が有限である体
四則演算が定義され閉じている有限集

整数の集合は無限の要素だが、有限体は要素が有限で、四則演算(足し算・引き算・掛け算・割り算)が閉じている
=> 閉じているとは、演算結果が、有理数でなくなるということがない
=> 演算を施した結果がふたたびもとの集合に属する
素数 P で割った余り

これ、初手でいきなりつまづくな…

pythonとhmac-sha512

HMACは暗号ハッシュ関数を使用してメッセージ認証を行う仕組み

import hmac
import hashlib

key=b"secret2"
text=b"foo bar"
signature=hmac.new(key,text,hashlib.md5).hexdigest()
print(signature)

b””としないと、エラーになる。
self._hmac = _hashopenssl.hmac_new(key, msg, digestmod=digestmod)
TypeError: Strings must be encoded before hashing

16進数と整数の変換

0xはその文字列が16進数であることを示す

print(format(0xabcd, 'x'))
print(hex(0xabcd))
print('%02x' % 0xabcd)

binasciiはバイト列に変換する

import binascii
print(binascii.unhexlify('abcd'))
print(str(binascii.hexlify(b'\xab\xcd'), 'utf-8'))

blockchain

import hashlib

class Block():
    def __init__(self, data, prev_hash):
        self.index = 0
        self.nonce = 0
        self.prev_hash = prev_hash
        self.data = data

    def blockhash(self):
        blockheader = str(self.index) + str(self.prev_hash) + str(self.data) + str(self.nonce)
        block_hash = hashlib.sha256(blockheader.encode()).hexdigest()
        return block_hash

    def __str__(self):
        return "Block Hash: " + self.blockhash() + "\nPrevious Hash: " + self.prev_hash + "\nindex: " + str(self.index) + "\nData: " + str(self.data) + "\nNonce: " + str(self.nonce) + "\n--------------"

class Hashchain():
    def __init__(self):
        self.chain = ["0000000000000000000000000000000000000000000000000000000000000000"]

    def add(self, hash):
        self.chain.append(hash)

hashchain = Hashchain()
target = 0x777777 * 2**(8*(0x1e - 0x03))

for i in range(30):
    block = Block("Block " + str(i+1), hashchain.chain[-1])
    block.index = block.index + i + 1
    for n in range(4294967296):
        block.nonce = block.nonce + n
        if int(block.blockhash(), 16) < target:
            print(block)
            hashchain.add(block.blockhash())
            break

Proof of work

簡易的なhash計算

import hashlib

input_text = "satoshi"

for nonce in range(20):
    input_data = input_text + str(nonce)
    hash = hashlib.sha256(input_data.encode("UTF-8")).hexdigest()
    print(input_data + " → " + hash)

ブロックヘッダのdifficulty bitsにハッシュ値の条件が書かれている
0x1e777777 のような形式で記述されている。

# difficulty_bits = 0x1e777777
# exponent = 0x1e
# coefficient = 0x777777

target = 0x777777 * 2**(8*(0x1e - 0x03))
print(target)

target_hex = hex(target)[2:].zfill(64)
print(target_hex)
import hashlib

class Block():
    def __init__(self, data, prev_hash):
        self.index = 0
        self.nonce = 0
        self.prev_hash = prev_hash
        self.data = data

    def blockhash(self):
        blockheader = str(self.index) + str(self.prev_hash) + str(self.data) + str(self.nonce)
        block_hash = hashlib.sha256(blockheader.encode()).hexdigest()
        return block_hash

    def __str__(self):
        return "Block Hash: " + self.blockhash() + "\nPrevious Hash: " + self.prev_hash + "\nindex: " + str(self.index) + "\nData: " + str(self.data) + "\nNonce: " + str(self.nonce) + "\n--------------"

class Hashchain():
    def __init__(self):
        self.chain = ["0000000000000000000000000000000000000000000000000000000000000000"]

    def add(self, hash):
        self.chain.append(hash)

hashchain = Hashchain()
target = 0x777777 * 2**(8*(0x1e - 0x03))

for i in range(30):
    block = Block("Block " + str(i+1), hashchain.chain[-1])
    block.index = block.index + i + 1
    for n in range(4294967296):
        block.nonce = block.nonce + n
        if int(block.blockhash(), 16) < target:
            print(block)
            hashchain.add(block.blockhash())
            break

UTXO

import json
import requests

address = "3FkenCiXpSLqD8L79intRNXUgjRoH9sjXa"

res = requests.get("https://blockchain.info/unspent?active=" + address)
utxo_list = json.loads(res.text)["unspent_outputs"]

print(str(len(utxo_list)) + "個のutxoが見つかりました")
for utxo in utxo_list:
    print(utxo["tx_hash"] + ":" + str(utxo["value"]) + " satoshis")

blockcain exploreのtransactionからも確認できる。
https://www.blockchain.com/explorer

$ python3 utxo.py
2個のutxoが見つかりました
0aed6c01112af0a2dd51981e983ce41ef271c3bbec834121a47dbe31c30519d7:548546 satoshis
768b2e91eaa99208d32d3dde2a0f70214940584754b66378a33c9e3bd60136c3:311694 satoshis

HD Wallet

import os
import binascii
import ecdsa
import hmac
import hashlib

seed = os.urandom(32)
root_key = b'Bitcoin Seed'

def hmac_sha512(data, keymessage):
    hash = hmac.new(data, keymessage, hashlib.sha512).digest()
    return hash

def create_pubkey(private_key):
    publickey = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.SECP256k1).verifying_key.to_string()
    return publickey

master = hmac_sha512(seed, root_key)
master_secretkey = master[:32]
master_chaincode = master[32:]

master_publickey = create_pubkey(master_secretkey)
master_publickey_integer = int.from_bytes(master_publickey[32:], byteorder="big")

if master_publickey_integer % 2 == 0:
    master_publickey_x = b"\x02" + master_publickey[:32]
else:
    master_publickey_x = b"\x03" + master_publickey[:32]

print("マスター秘密鍵")
print(binascii.hexlify(master_secretkey))
print("\n")
print("マスターチェーンコード")
print(binascii.hexlify(master_chaincode))
print("\n")
print("マスター公開鍵")
print(binascii.hexlify(master_publickey_x))

index = 0
index_bytes = index.to_bytes(8, "big")
data = master_publickey_x + index_bytes
result_hmac512 = hmac_sha512(data, master_chaincode)

sum_integer = int.from_bytes(master_secretkey, "big") + int.from_bytes(result_hmac512[:32], "big")

p = 2**256 - 2**32 - 2**9 - 2**8 - 2**7 - 2**6 - 2**4 - 1
child_secretkey = (sum_integer % p).to_bytes(32, "big")

print("\n")
print("子秘密鍵")
print(binascii.hexlify(child_secretkey))

bitcoin アドレスの生成

import os
import ecdsa
import hashlib
import base58
from Crypto.Hash import RIPEMD160

private_key = os.urandom(32)
public_key = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.SECP256k1).verifying_key.to_string()

prefix_and_pubkey = b"\x04" + public_key

intermediate = hashlib.sha256(prefix_and_pubkey).digest()
ripemd160 = RIPEMD160.new()
ripemd160.update(intermediate)
hash160 = ripemd160.digest()

prefix_and_hash160 = b"\x00" + hash160

double_hash = hashlib.sha256(hashlib.sha256(prefix_and_hash160).digest()).digest()
checksum = double_hash[:4]
pre_address = prefix_and_hash160 + checksum

address = base58.b58encode(pre_address)
print(address.decode())

hashlibで以下のように書くとエラーになる。
ripemd160 = hashlib.new(‘ripemd160’)

$ python3 address.py
Traceback (most recent call last):
File “/usr/lib/python3.10/hashlib.py”, line 160, in __hash_new
return _hashlib.new(name, data, **kwargs)
ValueError: [digital envelope routines] unsupported

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/home/vagrant/dev/work/address.py”, line 12, in
ripemd160 = hashlib.new(‘ripemd160’)
File “/usr/lib/python3.10/hashlib.py”, line 166, in __hash_new
return __get_builtin_constructor(name)(data)
File “/usr/lib/python3.10/hashlib.py”, line 123, in __get_builtin_constructor
raise ValueError(‘unsupported hash type ‘ + name)
ValueError: unsupported hash type ripemd160

従って、pycryptodomeをインストールして、ripemd160を使う
$ pip install pycryptodome

bitcoin 秘密鍵の生成

import os
import binascii

private_key = os.urandom(32)
print(private_key)
print(binascii.hexlify(private_key))

32バイトの乱数を生成して、バイナリデータを16進数に変換

$ python3 wallet.py
b’W~\x9cj\xcc}\x0f1J\xab\xa6Hh\x87\xe7\xa6x2\xb1c,\xe9\x1dZp{R\xc3\x8e9-\xe2′
b’577e9c6acc7d0f314aaba6486887e7a67832b1632ce91d5a707b52c38e392de2′

公開鍵に楕円曲線暗号を使用します。
$ pip install ecdsa

import os
import ecdsa
import binascii

private_key = os.urandom(32)
public_key = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.SECP256k1).verifying_key.to_string()

print(binascii.hexlify(private_key))
print(binascii.hexlify(public_key))

y^2 = x^3 + 7 mod p

import os
import ecdsa
import binascii

private_key = os.urandom(32)
public_key = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.SECP256k1).verifying_key.to_string()

print(binascii.hexlify(private_key))
print(binascii.hexlify(public_key))

# public_key y-axis
public_key_y = int.from_bytes(public_key[32:], "big")

# create compressed public_key
if public_key_y % 2 == 0:
    public_key_compressed = b"\x02" + public_key[:32]
else:
    public_key_compressed = b"\x03" + public_key[:32]

print(binascii.hexlify(public_key_compressed))