トランザクション

トランザクションのコンポーネント
1. バージョン(追加機能)
2. インプット(どのビットコインを使用するか)
3. アウトプット(どのビットコインを移動するか)
4. ロックタイム(トランザクションが有効になるか)

from helper import (
    hash256,
)

class Tx:

    def __init__(self, version, tx_ins, tx_outs, locktime, testnet=False):
        self.version = version
        self.tx_ins = tx_ins 
        self.tx_outs = tx_outs 
        self.locktime = locktime
        self.testnet = testnet

    def __repr__(self):
        tx_ins = ''
        for tx_in in self.tx_ins:
            tx_ins += tx_in.__repr__() + '\n'
        tx_outs = ''
        for tx_out in self.tx_outs:
            tx_outs += tx_out.__repr__() + '\n'
        return 'tx: {}\nversion: {}\ntx_ins:\n{}tx_outs:\nlocktime: {}'.format(
            self.id(),
            self.version,
            tx_ins,
            tx_outs,
            self.locktime,
        )
    
    def id(self):
        return self.hash().hex()

    def hash(self):
        return hash256(self.serialize())[::-1]

idはトランザクションを見つける際にブロックエクスプローラが用いる。リトルエンディアンで返す16進数のhash256

トランザクションのパース

    @classmethod
    def parse(cls, serialization):
        version = serialization[0:4]

変数serializationはバイト配列

↓ ストリームを使用し、readメソッドを使う
read(4)なので、4文字を読み込むという意味

    @classmethod
    def parse(cls, stream):
        serialized_version = stream.read(4)

Pythonのstream

I/O : 入出力input/outputのこと
stream: データが流れる通り道

Pythonおいてストリームを作り簡単な方法はファイルをopenすること
読み取りのストリームにはread(), tell(), seekなどのインターフェイスが用意されている
tellは読み取り位置取得、seekは読み取り位置変更

text_file.txt
1234567890abcdefg

f = open("text_file.txt", "r")
print("tell 1 ->", f.tell())

first4 = f.read(4)
print("first4 ->", first4)
print("tell2 ->", f.tell())
f.seek(10)
last = f.read()
print("last ->", last)
f.close()

$ python3 test.py
tell 1 -> 0
first4 -> 1234
tell2 -> 4
last -> abcdefg

Python @classmethodのcls

クラスメソッドの第1引数はclsで、クラス自身を示す

import datetime

class Reserve:

    dt_now = datetime.datetime.now()

    def __init__(self, dep, dest, time):
        self.dep = dep
        self.dest = dest
        self.time = time

    def plan(self):
        print(f"予約内容は、出発地:{self.dep}、目的地:{self.dest}、出発時間:{self.time}です")

    @classmethod
    def clock(cls):
        print(f"只今の時刻は、{cls.dt_now}です")


osakaTrip = Reserve("東京","大阪","16時")
osakaTrip.plan()
Reserve.clock()

$ python3 test.py
予約内容は、出発地:東京、目的地:大阪、出発時間:16時です
只今の時刻は、2023-11-16 02:47:03.822162です

print(f”只今の時刻は、{dt_now}です”) とすると、以下のようにエラーになります。
Traceback (most recent call last):
File “/home/vagrant/dev/test/test.py”, line 23, in
Reserve.clock()
File “/home/vagrant/dev/test/test.py”, line 18, in clock
print(f”只今の時刻は、{dt_now}です”)
NameError: name ‘dt_now’ is not defined

Pythonのhash関数

print(hash("abc"))
print(hash("def"))
print(hash(123))
print(hash(123.0))
print(hash(-123))
print(hash("abc"))

$ python3 test.py
-6216428559771217202
-5554558427243961036
123
123
-123
-6216428559771217202

秘密鍵からbitcoinテストネットのアドレス作成

ビットコインの秘密鍵は256ビットの16進数

ビットコインの秘密鍵は256ビット(32バイト、16進数で64文字)で、1から2^256の範囲であればほぼすべての数字が秘密鍵として利用

from ecc import S256Point, PrivateKey
from helper import hash160, little_endian_to_int

import os

private_key = os.urandom(32)
p = little_endian_to_int(hash160(private_key))
print(p)

privateKey = PrivateKey(p)
wallet = privateKey.point.address(testnet=True)
print(wallet)

little_endian_to_int でint型にしてアドレスを作成する

Pythonのビッグ/リトルエンディアン

バイトを受け取り、リトルエンディアンとして解釈して数値を返す関数

def little_endian_to_int(b):

    return int.from_bytes(b, 'little')

数字を受け取り、リトルエンディアンとしてバイトを返却

def int_to_little_endian(n, length):

    return n.to_bytes(length, 'little')

バイト数も256とか指定するのではなく、引数lengthとして受け付ける。また、数字はiよりnの方がベター

ウォレットインポート形式(WIF)

秘密鍵は256ビットの数値である。
WIFは人間が読めるように秘密鍵をシリアライズした形式。アドレスと同じようにBase58エンコーディングを用いる。

1. メインネット用の秘密鍵は0x80、テストネット用は0xefのプレフィックスで開始
2. 秘密鍵を32バイトのビッグエンディアンでエンコード
3. 公開鍵アドレス用のSECフォーマットが圧縮形式の場合、末尾に0x01を追加
4. 1.のプレフィックス、2.のシリアライズした秘密鍵、3.のサフィックスを順に結合
5. 4.の結果にhash256を実行し、先頭の4バイトを取得
6. 4.と5.を結合させて、Base58でエンコード

    def wif(self, compressed=True, testnet=False):
        secret_bytes = self.secret.to_bytes(32, 'big')
        if testnet:
            prefix = b'\xef'
        else:
            prefix = b'\x80'
        if compressed:
            suffix = b'\x01'
        else:
            suffix = b''
        return encode_base58_checksum(prefix + secret_bytes + suffix)

秘密鍵を”5003″とする場合のWIF

from ecc import S256Point, PrivateKey
import helper

privateKey = PrivateKey(5003)
wif = privateKey.wif(compressed=True, testnet=True)
print(wif)

$ python3 main.py
cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN8rFTv2sfUK

ビットコインのアドレス形式

アドレスを短くして安全性を高めるにはripemd160ハッシュを使用する。33バイトから20バイトへ短縮できる。

1. メインネットアドレスは先頭を0x00、テストネットは0x6fで開始する。
2. SECフォーマット(圧縮・非圧縮)を取り出し、sha256とripemd160ハッシュ操作を行う。この組み合わせをhash160操作と呼ぶ。
3. 1のプレフィックスと2のハッシュ操作を結合する。
4. 3の結果にhash256を行い、先頭の4バイトを取得する
5. 3と4を結合させてBase58でエンコードする

4の結果をチェックサムと呼ぶ

def encode_base58_checksum(b):
    return encode_base58(b + hash256(b)[:4])
import helper from encode_base58_checksum, hash160

def encode_base58_checksum(b):
    return encode_base58(b + hash256(b)[:4])

def hash160(s):
    return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest()
    def hash160(self, compressed=True):
        return hash160(self.sec(compressed))

    def address(self, compressed=True, testnet=False):
        h160 = self.hash160(compressed)
        if testnet:
            prefix = b'\x6f'
        else:
            prefix = b'\x00'
        return encode_base58_checksum(prefix + h160)

秘密鍵を”5002″とする場合のアドレスの求め方

from ecc import S256Point, PrivateKey

privateKey = PrivateKey(5002)
address = privateKey.point.address(compressed=False, testnet=True)
print(address)

$ python3 main.py
mmTPbXQFxboEtNRkwfh6K51jvdtHLxGeMA

圧縮・非圧縮やメインネット、テストネットは引数で切り替える
address = privateKey.point.address(compressed=True, testnet=False)