[Bitcoin] ウォレット

Bitcoinのウォレットとは?
-> 一般的なユーザがビットコインを管理するためのアプリ
-> 主な機能はビットコインの保管と送受信

– 秘密鍵
– ビットコインアドレス
– トランザクションの作成
– 電子署名の作成

秘密鍵とビットコインアドレスで個人を識別している
一人で複数のアドレスを持つことも当たり前となっている

Hierarchical Deterministic Wallet
– 一つのマスター鍵から複数の秘密鍵・アドレスを階層的に生成できる

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
from pycoin.tx import Tx
from pycoin.services import spendables_for_address
from pycoin.services.blockchain_info import BlockchainInfoProvider, send_tx
from pycoin.tx.tx_utils import create_signed_tx
from PyQt5.QtWidgets import (QLabel, QLineEdit, QGridLayout, QWidget,
							QApplication, QPushButton, QDesktopWidget,
							QTextEdit)

class MainWindow(QWidget):

	def __init__(self, parent=None):
		super(MainWindow, self).__init__(parent)

		address = ""
		destination = ""

		self.fromaddress = QLineEdit()
		self.fromaddress.setText(address)

		self.sendto = QLineEdit()
		self.sendto.setText(destination)

		self.transaction = QTextEdit()
		self.transaction.setReadOnly(True)

		sendTxBtn = QPushButton('send Tx', self)
		sndTxBtn.clicked.connect(self.createTx)

		grid = QGridLayout()
		grid.setSpacing(10)

		grid.addWidget(QLabel('From'), 1,0)
		grid.addWidget(self.fromaddress, 1,1)

		grid.addWidget(QLabel('Send to'), 2,0)
		grid.addWidget(self.sendto, 2,1)

		grid.addWidget(QLabel('Transaction'), 3,0)
		grid.addWidget(self.transaction, 3, 1, 1, 2)

		get.addWidget(sndTxBtn, 4, 1)

		self.setLayout(grid)

		self.resize(320, 180)
		self.center()
		self.setWindowTitle('Create transaction demo')
		self.show()

	def center(self):

		qr = self.frameGeometry()
		cp = QDesktopWidget().availableGeometry().center()
		qr.moveCenter(cp)
		self.move(qr.topLeft())

	def createTx(self):

		address = self.fromaddress.text()
		destination = self.sendto.text()
		wif = ""
		sndtx = BlockchainInfoProvider('BTC')

		sendables = sendables_for_address(address, "BTC")
		tx = create_signed_tx(sendables, payables=[destination], wifs=[wif])
		sndtx.broadcast_tx(tx)
		self.transaction.setText(tx.as_hex())

if __name__ == '__main__':

	app = QApplication(sys.argv)
	window = MainWindow()
	sys.exit(app.exec())

generate-key.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
from pycoin.tx import Tx
from pycoin.key.BIP32Node import BIP32Node
from PyQt5.QtWidgets import (QLabel, QLineEdit, QGridLayout, QWidget,
							QApplication, QPushButton, QDesktopWidget,
							QTextEdit)

def KeyGenerater(s):
	key = BIP32Node.from_master_secret(s.encode("utf8"))
	return key.wif(), key.address()

class MainWindow(QWidget):

	def __init__(self, parent=None):
		super(MainWindow, self).__init__(parent)

		self.inputLine = QLineEidt()

		self.privateKey = QTextEdit()
		self.privateKey.setReadOnly(True)

		self.publicKey = QTextEdit()
		self.publicKey.setReadOnly(True)


		generateBtn = QPushButton('Generate', self)
		generateBtn.clicked.connect(self.showKeys)

		grid = QGridLayout()
		grid.setSpacing(10)

		grid.addWidget(QLabel('Input'), 1,0)
		grid.addWidget(self.inputLine, 1,1)

		grid.addWidget(generateBtn, 1, 2)

		grid.addWidget(QLabel('Private Key'), 2,0)
		grid.addWidget(self.privateKey, 2,1, 1, 2)

		grid.addWidget(QLabel('Public Key'), 3,0)
		grid.addWidget(self.publicKey, 3, 1, 1, 2)


		self.setLayout(grid)

		self.resize(320, 150)
		self.center()
		self.setWindowTitle('Key generate demo')
		self.show()

	def center(self):

		qr = self.frameGeometry()
		cp = QDesktopWidget().availableGeometry().center()
		qr.moveCenter(cp)
		self.move(qr.topLeft())

	def showKeys(self):

		text = self.inputLine.text()
		private, public = KeyGenerater(text)
		self.publicKey.setText(private)
		self.publicKey.setText(public)

if __name__ == '__main__':

	app = QApplication(sys.argv)
	window = MainWindow()
	sys.exit(app.exec())

OK, keep going

[Bitcoin] マイニング

from hashlib import sha256
text = "ABC"
print(sha256(text.encode('ascii')).hexdigest())

$ python3 main.py
b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78

from hashlib import sha256
MAX_NONCE = 10000000000

def SHA256(text):
	return sha256(text.encode('ascii')).hexdigest()

def mine(block_number, transactions, previous_hash, prefix_zeros):
    prefix_str = '0'*prefix_zeros
    for nonce in range(MAX_NONCE):
        text = str(block_number) + transactions + previous_hash + str(nonce)
        new_hash = SHA256(text)
        if new_hash.startswith(prefix_str):
            print(f"Yay! Successfully mined bitcoins with nonce value:{nonce}")
            return new_hash

    raise BaseException(f"Couldn't find correct has after trying {MAX_NONCE} times")


if __name__ == '__main__':
	transactions='''
	Bob->Alice->20,
	Ken->Ryu->45
	'''
	difficulty=6
	new_hash = mine(5, transactions, '00000000000008a3a41b85b8b29ad444def299fee21793cd8b9e567eab02cd81', difficulty)
	print(new_hash)

bitcoin hardware
https://www.buybitcoinworldwide.com/mining/hardware/

bitcoin explore
https://www.blockchain.com/explorer
https://www.blockchain.com/btc/blocks?page=1

bitcoin block1
https://www.blockchain.com/btc/block/1

なるほど、前後の処理が知りたいのう

[Bitcoin] ブルームフィルター

軽量クライアントがフルノードに伝えるトランザクション
フルノードはブルームフィルターを介してトランザクションを取り扱い、通過するトランザクションに関しmerkleblockコマンドを送信する

アイテムがどの入れ物に入るかを判断するプロセス

from helper import hash256
bit_field_size=10
bit_field = [0] * bit_field_size
h = hash256(b'hello world')
bit = int.from_bytes(h, 'big') % bit_field_size
bit_field[bit] = 1
print(bit_field)

複数

from helper import hash256
bit_field_size=10
bit_field = [0] * bit_field_size
for item in (b'hello world', b'goodbye'):
	h = hash256(item)
	bit = int.from_bytes(h, 'big') % bit_field_size
	bit_field[bit] = 1
print(bit_field)

### 複数ハッシュ関数を使用するブルームフィルター

from helper import hash256, hash160
bit_field_size=10
bit_field = [0] * bit_field_size
for item in (b'hello world', b'goodbye'):
	for hash_function in (hash256, hash160):
		h = hash_function(item)
		bit = int.from_bytes(h, 'big') % bit_field_size
		bit_field[bit] = 1
print(bit_field)

### BIP0037ブルームフィルター
murmur3と呼ばれるハッシュ関数を使用する

from helper import murmur3
from bloomfilter import BIP37_CONSTANT
field_size = 2
num_functions = 2
tweak = 42
bit_field_size = field_size * 8
bit_field = [0] * bit_field_size
for phrase in (b'hello world', b'goodbye'):
	for i in range(num_functions):
		seed = i * BIP37_CONSTANT + tweak
		h = murmur3(phrase, seed=seed)
		bit = h % bit_field_size
		bit_field[bit] = 1
print(bit_field)

[0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0]

from bloomfilter import BloomFilter, BIP37_CONSTANT
from helper import bit_field_to_bytes, murmur3
field_size = 10
function_count = 5
tweak = 99
items = (b'Hello World', b'Goodbye!')
bit_field_size = field_size * 8
bit_field = [0] * bit_field_size
for item in items:
	for i in range(function_count):
		seed = i * BIP37_CONSTANT + tweak
		h = murmur3(item, seed=seed)
		bit = h % bit_field_size
		bit_field[bit] = 1
print(bit_field_to_bytes(bit_field).hex())

### ブルームフィルターの読み込み

	def filterload(self, flag=1):
		payload = encode_varint(self.size)
		payload += self.filter_bytes()
		payload += int_to_little_endian(self.function_count, 4)
		payload += int_to_little_endian(self.tweak, 4)
		payload += int_to_little_endian(flag, 1)
		return GenericMessage(b'filterload', payload)
class GetDataMessage:
	command = b'getdata'

	def __init__(self):
		self.data = []

	def add_data(self, data_type, identifier):
		self.data.append((data_type, identifier))

	def serialize(self):
		result = encode_varint(len(self.data))
		for data_type, identifier in self.data:
			result += int_to_little_endian(data_type, 4)
			result += identifier[::-1]
		return result
from bloomfilter import BloomFilter
from helper import decode_base58
from merkleblock import MerkleBlock
from network import FILTERED_BLOCK_DATA_TYPE, GetHeadersMessage, GetDataMessage, HeadersMessage, SimpleNode
from tx import Tx
last_block_hex = '00000000000538d5c2246336644f9a4956551afb44ba47278759ec55\
ea912e19'
address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv'
h160 = decode_base58(address)
node = SimpleNode('testnet.programmingbitcoin.com', testnet=True, logging=False)
bf = BloomFilter(size=30, function_count=5, tweak=90210)
bf.add(h160)
node.handshake()
node.send(bf.filterload())
start_block = bytes.fromhex(last_block_hex)
getheaders = GetHeadersMessage(start_block=start_block)
node.send(getheaders)
headers = node.wait_for(HeadersMessage)
getdata = GetDataMessage()
for b in headers.blocks:
	if not b.check_pow():
		raise RuntimeError('proof of work is invalid')
	getdata.add_data(FILTERED_BLOCK_DATA_TYPE, b.hash())
node.send(getdata)
found = False
while not found:
	message = node.wait_for(MerkleBlock, Tx)
	if message.command == b'merkleblock':
		if not message.is_valid():
			raise RuntimeError('invalid merkle proof')
		else:
			for i, tx_out in enumerate(message.tx_outs):
				if tx_out.script_pubkey.address(testnet=True) == address:
					print('found: {}:{}'.format(message.id(), i))
					found = True
					break

[Bitcoin] Simplified Payment Verification

マークルルートとは包含証明
ブロックチェーン全体は200GBを超えている
ビットコインが支払われたことをか人するのは受取人

### マークルツリー
アイテムの順序付きリストと暗号学的ハッシュ関数
順序付きリスト全体を表す単一のハッシュにする
一番下の行をツリーのリーフと呼び、リーフを連結して親レベルを作成して計算する
H = ハッシュ関数
P = 親ハッシュ
L = 左ハッシュ
R = 右ハッシュ

from helper import hash256
hash0 = bytes.fromhex('c117ea8ec828342f4dfb0ad6bd140e03a50720ece40169ee38b\
dc15d9eb64cf5')
hash1 = bytes.fromhex('c131474164b412e3406696da1ee20ab0fc9bf41c8f05fa8ceea\
7a08d672d7cc5')
parent = hash256(hash0 + hash1)
print(parent.hex())
def merkle_parent(hash1, hash2):
    return hash256(hash1 + hash2)

### マークルペアレントレベル
順序付きリストが与えられると、各親ハッシュを計算できるようになる

from helper import merkle_parent
hex_hashes = [
	'c117ea8ec828342f4dfb0ad6bd140e03a50720ece40169ee38bdc15d9eb64cf5',
	'c131474164b412e3406696da1ee20ab0fc9bf41c8f05fa8ceea7a08d672d7cc5',
	'f391da6ecfeed1814efae39e7fcb3838ae0b02c02ae7d0a5848a66947c0727b0',
	'3d238a92a94532b946c90e19c49351c763696cff3db400485b813aecb8a13181',
	'10092f2633be5f3ce349bf9ddbde36caa3dd10dfa0ec8106bce23acbff637dae',
]
hashes = [bytes.fromhex(x) for x in hex_hashes]
if len(hashes) % 2 == 1:
	hashes.append(hashes[-1])
parent_level = []
for i in range(0, len(hashes), 2):
	parent = merkle_parent(hashes[i], hashes[i+1])
	parent_level.append(parent)
for item in parent_level:
	print(item.hex())
def merkle_parent_level(hashes):
    if len(hashes) == 1:
        raise RuntimeError('Cannot take a parent level with only 1 item')
    if len(hashes) % 2 == 1:
        hashes.append(hashes[-1])
    parent_level = []
    for i in range(0, len(hashes), 2):
        parent = merkle_parent(hashes[i], hashes[i + 1])
        parent_level.append(parent)
    return parent_level

マークルルートを取得するには、ハッシュの個数が1つになるまでマークルペアレントレベルを計算する

from helper import merkle_parent_level
hex_hashes = [
	'c117ea8ec828342f4dfb0ad6bd140e03a50720ece40169ee38bdc15d9eb64cf5',
	'c131474164b412e3406696da1ee20ab0fc9bf41c8f05fa8ceea7a08d672d7cc5',
	'f391da6ecfeed1814efae39e7fcb3838ae0b02c02ae7d0a5848a66947c0727b0',
	'3d238a92a94532b946c90e19c49351c763696cff3db400485b813aecb8a13181',
	'10092f2633be5f3ce349bf9ddbde36caa3dd10dfa0ec8106bce23acbff637dae',
	'7d37b3d54fa6a64869084bfd2e831309118b9e833610e6228adacdbd1b4ba161',
	'8118a77e542892fe15ae3fc771a4abfd2f5d5d5997544c3487ac36b5c85170fc',
	'dff6879848c2c9b62fe652720b8df5272093acfaa45a43cdb3696fe2466a3877',
	'b825c0745f46ac58f7d3759e6dc535a1fec7820377f24d4c2c6ad2cc55c0cb59',
	'95513952a04bd8992721e9b7e2937f1c04ba31e0469fbe615a78197f68f52b7c',
	'2e6d722e5e4dbdf2447ddecc9f7dabb8e299bae921c99ad5b0184cd9eb8e5908',
	'b13a750047bc0bdceb2473e5fe488c2596d7a7124b4e716fdd29b046ef99bbf0',
]
hashes = [bytes.fromhex(x) for x in hex_hashes]
current_hashes = hashes
while len(current_hashes) > 1:
	current_hashes = merkle_parent_level(current_hashes)
print(current_hashes[0].hex())
def merkle_root(hashes):
    current_level = hashes
    while len(current_level) > 1:
        current_level = merkle_parent_level(current_level)
    return current_level[0]

### ブロックのマークルルート

from helper import merkle_root
tx_hex_hashes = [
	'42f6f52f17620653dcc909e58bb352e0bd4bd1381e2955d19c00959a22122b2e',
	'94c3af34b9667bf787e1c6a0a009201589755d01d02fe2877cc69b929d2418d4',
	'959428d7c48113cb9149d0566bde3d46e98cf028053c522b8fa8f735241aa953',
	'a9f27b99d5d108dede755710d4a1ffa2c74af70b4ca71726fa57d68454e609a2',
	'62af110031e29de1efcad103b3ad4bec7bdcf6cb9c9f4afdd586981795516577',
	'766900590ece194667e9da2984018057512887110bf54fe0aa800157aec796ba',
	'e8270fb475763bc8d855cfe45ed98060988c1bdcad2ffc8364f783c98999a208',
]
tx_hashes = [bytes.fromhex(x) for x in tx_hex_hashes]
hashes = [h[::-1] for h in tx_hashes]
print(merkle_root(hashes)[::-1].hex())



	def validate_merkle_root(self):
		hashes = [h[::-1] for h in self.tx_hashes]
		root = merkle_root(hashes)
		return root[::-1] == self.merkle_root

### マークルブロック
包含証明を送信する時、マークルツリー構造で、どの位置にどのハッシュがあるか情報を含める必要がある

### マークルツリー構造
次のようにマークルツリーを作成できる

import math
total = 16
max_depth = math.ceil(math.log(total, 2))
merkle_tree = []
for depth in range(max_depth + 1):
	num_items = math.ceil(total / 2**(max_depth - depth))
	level_hashes = [None] * num_items
	merkle_tree.append(level_hashes)
for level in merkle_tree:
	print(level)

### マークルツリー

class MerkleTree:

	def __init__(self, total):
		self.total = total
		self.max_depth = math.ceil(math.log(self.total, 2))
		self.nodes = []
		for depth in range(self.max_depth + 1):
			num_items = math.ceil(self.total / 2**(self.max_depth - depth))
			level_hashes = [None] * num_items
			self.nodes.append(level_hashes)
		self.current_depth = 0
		self.current_index = 0

	def __repr__(self):
		result = []
		for depth, level in enumerate(self.nodes):
			items = []
			for index, h in enumerate(level):
				if h is None:
					short = 'None'
				else:
					short = '{}...'.format(h.hex()[:8])
				if depth == self.current_depth and index == self.current_index:
					items.append('*{}*'.format(short[:-2]))
				else:
					items.append('{}'.format(short))
			result.append(', '.join(items))
		return '\n'.join(result)

	def up(self):
		self.current_depth -= 1
		self.current_index //= 2

	def left(self):
		self.current_depth += 1
		self.current_index *= 2

	def right(self):
		self.current_depth += 1
		self.current_index = self.current_index * 2 + 1

	def root(self):
		return self.nodes[0][0]

	def set_current_node(self, value):
		self.nodes[self.current_depth][self.current_index] = value

	def get_current_node(self):
		return self.nodes[self.current_depth][self.current_index]

	def get_left_node(self):
		return self.nodes[self.current_depth + 1][self.current_index * 2]

	def get_right_node(self):
		return self.nodes[self.current_depth + 1][self.current_index * 2 + 1]

	def is_leaf(self):
		return self.current_depth == self.max_depth

	def right_exists(self):
		return len(self.nodes[self.current_depth + 1]) > \
			self.current_index * 2 + 1
from merkleblock import MerkleTree
from helper import merkle_parent_level
hex_hashes = [
	'9745f7173ef14ee4155722d1cbf13304339fd00d900b759c6f9d58579b5765fb',
	'5573c8ede34936c29cdfdfe743f7f5fdfbd4f54ba0705259e62f39917065cb9b',
	'82a02ecbb6623b4274dfcab82b336dc017a27136e08521091e443e62582e8f05',
	'507ccae5ed9b340363a0e6d765af148be9cb1c8766ccc922f83e4ae681658308',
	'a7a4aec28e7162e1e9ef33dfa30f0bc0526e6cf4b11a576f6c5de58593898330',
	'bb6267664bd833fd9fc82582853ab144fece26b7a8a5bf328f8a059445b59add',
	'ea6d7ac1ee77fbacee58fc717b990c4fcccf1b19af43103c090f601677fd8836',
	'457743861de496c429912558a106b810b0507975a49773228aa788df40730d41',
	'7688029288efc9e9a0011c960a6ed9e5466581abf3e3a6c26ee317461add619a',
	'b1ae7f15836cb2286cdd4e2c37bf9bb7da0a2846d06867a429f654b2e7f383c9',
	'9b74f89fa3f93e71ff2c241f32945d877281a6a50a6bf94adac002980aafe5ab',
	'b3a92b5b255019bdaf754875633c2de9fec2ab03e6b8ce669d07cb5b18804638',
	'b5c0b915312b9bdaedd2b86aa2d0f8feffc73a2d37668fd9010179261e25e263',
	'c9d52c5cb1e557b92c84c52e7c4bfbce859408bedffc8a5560fd6e35e10b8800',
	'c555bc5fc3bc096df0a0c9532f07640bfb76bfe4fc1ace214b8b228a1297a4c2',
	'f9dbfafc3af3400954975da24eb325e326960a25b87fffe23eef3e7ed2fb610e',
]
tree = MerkleTree(len(hex_hashes))
tree.nodes[4] = [bytes.fromhex(h) for h in hex_hashes]
tree.nodes[3] = merkle_parent_level(tree.nodes[4])
tree.nodes[2] = merkle_parent_level(tree.nodes[3])
tree.nodes[1] = merkle_parent_level(tree.nodes[2])
tree.nodes[0] = merkle_parent_level(tree.nodes[1])
print(tree)
class MerkleBlock:
	def __init__(self, version, prev_block, merkle_root, timestamp, bits, nonce, total, hashes, flags):
		self.version = version
		self.prev_block = prev_block
		self.merkle_root = merkle_root
		self.timestamp = timestamp
		self.bits = bits
		self.nonce = nonce
		self.total = total
		self.hashes = hashes
		self.flags = flags

	def __repr__(self):
		result = '{}\n'.format(self.total)
		for h in self.hashes:
			result += '\t{}\n'.format(h.hex())
		result += '{}'.format(self.flags.hex())

	@classmethod
	def parse(cls, s):
		version = little_endian_to_int(s.read(4))
		prev_block = s.read(32)[::-1]
		merkle_root = s.read(32)[::-1]
		timestamp = little_endian_to_int(s.read(4))
		bits = s.read(4)
		nonce = s.read(4)
		total = little_endian_to_int(s.read(4))
		num_hashes = read_varint(s)
		hashes = []
		for _ in range(num_hashes):
			hashes.append(s.read(32)[::-1])
		flags_length = read_varint(s)
		flags = s.read(flags_length)
		return cls(version, prev_block, merkle_root, timestamp, bits,
			nonce, total, hashes, flags)

### フラグビットとハッシュの使用
フラグビットは深さ優先順序を使用してハッシュがマークルツリー内のどこになるかを通知する

	def populate_tree(self, flag_bits, hashes):
		while self.root() is None:
			if self.is_leaf():
				flag_bits.pop(0)
				self.set_current_node(hashes_pop(0))
				self.up()
			else:
				left_hash = self.get_left_node()
				if left_hash is None:
					if flag_bits.pop(0) == 0:
						self.set_current_node(hashes.pop(0))
						self.up()
					else:
						self.left()
				elif self.right_exists():
					right_hash = self.get_right_node()
					if right_hash is None:
						self.right()
					else:
						self.set_current_node(merkle_parent(left_hash, right_hash))
						self.up()
				else:
					self.set_current_node(merkle_parent(left_hash, left_hash))
					self.up()
		if len(hashes) != 0:
			raise RuntimeError('hashes not all consumed {}'.format(len(hashes)))
		for flag_bit in flag_bits:
			if flag_bit != 0:
				raise RuntimeError('flag bits not all consumed')

[Bitcoin] ブロック

– トランザクションによりビットコインは当事者から別の当事者へと転送する
– トランザクションは署名によりアンロックする
– ブロックはトランザクションを整列し、それ以降のトランザクションを無効にする 二重支払いを防ぐ
– 10分ごとにまとまった単位でトランザクションの集まりを決済する

### コインベーストランザクション
– インプットは1つのみ
– インプットの前のトランザクションIDは32バイトの00
– トランザクションインデックスはffffffff

    def is_coinbase(self):
        if len(self.tx_ins) != 1:
            return False
        first_input = self.tx_ins[0]
        if first_input.prev_tx != b'\x00' * 32:
            return False
        if first_input.prev_index != 0xffffffff:
            return False
        return True

ScriptSig

from io import BytesIO
from script import Script
stream = BytesIO(bytes.fromhex('4d04ffff001d0104455468652054696d6573203033\
2f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64\
206261696c6f757420666f722062616e6b73'))
s = Script.parse(stream)
print(s.cmds[2])

$ python3 main.py
b’The Times 03/Jan/2009 Chancellor on brink of second bailout for banks’

BIP0034
コインベーストランザクションのScriptSigの先頭要素を規定

from io import BytesIO
from script import Script
from helper import little_endian_to_int
stream = BytesIO(bytes.fromhex('5e03d71b07254d696e656420627920416e74506f6f\
6c20626a31312f4542312f4144362f43205914293101fabe6d6d678e2c8c34afc36896e7d94028\
24ed38e856676ee94bfdb0c6c4bcd8b2e5666a0400000000000000c7270000a5e00e00'))
script_sig = Script.parse(stream)
print(little_endian_to_int(script_sig.cmds[0]))
    def coinbase_height(self):
        if not self.is_coinbase():
            return None
        element = self.tx_ins[0].script_sig.cmds[0]
        return little_endian_to_int(element)

### ブロックヘッダー
ブロックヘッダーはブロックに含まれるトランザクションに関するメタデータ
– Version, Previous Block, Merkle Root, Timestamp, Bits, Nonce

block_id

from helper import hash256
block_hash = hash256(bytes.fromhex('020000208ec39428b17323fa0ddec8e887b4a7\
c53b8c0a0a220cfd0000000000000000005b0750fce0a889502d40508d39576821155e9c9e3f5c\
3157f961db38fd8b25be1e77a759e93c0118a4ffd71d'))[::-1]
block_id = block_hash.hex()
print(block_id)
	@classmethod
	def parse(cls, s):
		version = little_endian_to_int(s.read(4))
		prev_block = s.read(32)[::-1]
		merkle_root = s.read(32)[::-1]
		timestamp = little_endian_to_int(s.read(4))
		bits = s.read(4)
		nonce = s.read(4)
		return cls(version, prev_block, merkle_root, timestamp, bits, nonce)

	def serialize(self):
		result = int_to_little_endian(self.version, 4)
		result += self.prev_block[::-1]
		result += self.merkle_root[::-1]
		result += int_to_little_endian(self.timestamp, 4)
		result += self.bits
		result += self.nonce
		return result

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

### バージョン

from io import BytesIO
from block import Block
b = Block.parse(BytesIO(bytes.fromhex('020000208ec39428b17323fa0ddec8e887b\
4a7c53b8c0a0a220cfd0000000000000000005b0750fce0a889502d40508d39576821155e9c9e3\
f5c3157f961db38fd8b25be1e77a759e93c0118a4ffd71d')))
print('BIP9: {}'.format(b.version >> 29 == 0b001))
print('BIP91: {}'.format(b.version >> 4 & 1 == 1))
print('BIP141: {}'.format(b.version >> 1 & 1 == 1))

class Block

	def bip9(self):
		return self.version >> 29 == 0b001

	def bip91(self):
		return self.version >> 4 & 1 == 1

	def bip141(self):
		return self.version >> 1 & 1 == 1

マークルルート: トランザクションを32バイトのハッシュにエンコード
タイムスタンプ: ロックタイムと新しいビット/ターゲット/ディフィカルティを計算する際に使用
ビット: ブロックのProof-of-workに必要な値をエンコーディングするフィールド
ノンス: 一度だけ使われる数値 number used only once

### Proof-of-Work

from helper import hash256
block_id = hash256(bytes.fromhex('020000208ec39428b17323fa0ddec8e887b4a7c5\
3b8c0a0a220cfd0000000000000000005b0750fce0a889502d40508d39576821155e9c9e3f5c31\
57f961db38fd8b25be1e77a759e93c0118a4ffd71d'))[::-1]
print('{}'.format(block_id.hex()).zfill(64))

マイナーはノンスフィールドを自由に変える事でブロックヘッダーのハッシュを変更できる
ビットコインの全てのブロックヘッダーのハッシュが一定のターゲットを下回らなければならない
ビットフィールの一つは指数(exponent)で最後の1バイト、2つめは係数(coefficient)で残りのリトルエンディアンでエンコードした数値
target = coefficient x 256eponent-3

– ターゲットを計算する方法

from helper import little_endian_to_int
bits = bytes.fromhex('e93c0118')
exponent = bits[-1]
coefficient = little_endian_to_int(bits[:-1])
target = coefficient * 256**(exponent - 3)
print('{:x}'.format(target).zfill(64))

ブロックヘッダーのハッシュで、リトルエンディアンの整数として解釈した時に、ターゲットの数値を下回ったもの

from helper import little_endian_to_int, hash256

bits = bytes.fromhex('e93c0118')
exponent = bits[-1]
coefficient = little_endian_to_int(bits[:-1])
target = coefficient * 256**(exponent - 3)

proof = little_endian_to_int(hash256(bytes.fromhex('020000208ec39428b17323\
fa0ddec8e887b4a7c53b8c0a0a220cfd0000000000000000005b0750fce0a889502d40508d3957\
6821155e9c9e3f5c3157f961db38fd8b25be1e77a759e93c0118a4ffd71d')))
print(proof < target)

helper.py

def bits_to_target(bits):
    exponent = bits[-1]
    coefficient = little_endian_to_int(bits[:-1])
    return coefficient * 256**(exponent - 3)

– difficulty
ターゲットに反比例する
difficulty = 0xffff x 256^0x1d-3 / target

from helper import little_endian_to_int

bits = bytes.fromhex('e93c0118')
exponent = bits[-1]
coefficient = little_endian_to_int(bits[:-1])
target = coefficient * 256**(exponent - 3)
difficulty = 0xffff * 256**(0x1d-3) / target
print(difficulty)
	def difficulty(self):
		lowest = 0xffff * 256**(0x1d - 3)
		return lowest / self.target()

	def check_pow(self):
		sha = hash256(self.serialize())
		proof = little_endian_to_int(sha)
		return proof < self.target()

difficulty adjustment period

new_target = previous_target * time_differential / two week
ブロックは10分ごとのブロックに収束する

from io import BytesIO
from block import Block
from helper import TWO_WEEKS
last_block = Block.parse(BytesIO(bytes.fromhex('00000020fdf740b0e49cf75bb3\
d5168fb3586f7613dcc5cd89675b0100000000000000002e37b144c0baced07eb7e7b64da916cd\
3121f2427005551aeb0ec6a6402ac7d7f0e4235954d801187f5da9f5')))
first_block = Block.parse(BytesIO(bytes.fromhex('000000201ecd89664fd205a37\
566e694269ed76e425803003628ab010000000000000000bfcade29d080d9aae8fd461254b0418\
05ae442749f2a40100440fc0e3d5868e55019345954d80118a1721b2e')))
time_differential = last_block.timestamp - first_block.timestamp
if time_differential > TWO_WEEKS * 4:
	time_differential = TWO_WEEKS * 4
if time_differential < TWO_WEEKS // 4:
	time_differential = TWO_WEEKS // 4
new_target = last_block.target() * time_differential // TWO_WEEKS
print('{:x}'.format(new_target).zfill(64))
def target_to_bits(target):
    raw_bytes = target.to_bytes(32, 'big')
    raw_bytes = raw_bytes.lstrip(b'\x00')
    if raw_bytes[0] > 0x7f:
        exponent = len(raw_bytes) + 1
        coefficient = b'\x00' + raw_bytes[:2]
    else:
        exponent = len(raw_bytes)
        coefficient = raw_bytes[:3]
    new_bits = coefficient[::-1] + bytes([exponent])
    return new_bits
from io import BytesIO
from block import Block
from helper import TWO_WEEKS
from helper import target_to_bits
block1_hex = '000000203471101bbda3fe307664b3283a9ef0e97d9a38a7eacd88000000\
00000000000010c8aba8479bbaa5e0848152fd3c2289ca50e1c3e58c9a4faaafbdf5803c5448dd\
b845597e8b0118e43a81d3'
block2_hex = '02000020f1472d9db4b563c35f97c428ac903f23b7fc055d1cfc26000000\
000000000000b3f449fcbe1bc4cfbcb8283a0d2c037f961a3fdf2b8bedc144973735eea707e126\
4258597e8b0118e5f00474'
first_block = Block.parse(BytesIO(bytes.fromhex(block1_hex)))
last_block = Block.parse(BytesIO(bytes.fromhex(block2_hex)))
time_differential = last_block.timestamp - first_block.timestamp
if time_differential > TWO_WEEKS * 4:
	time_differential = TWO_WEEKS * 4
if time_differential < TWO_WEEKS // 4:
	time_differential = TWO_WEEKS // 4
new_target = last_block.target() * time_differential // TWO_WEEKS
new_bits = target_to_bits(new_target)
print(new_bits.hex())

$ python3 main.py
308d0118

def calculate_new_bits(previous_bits, time_differential):
    if time_differential > TWO_WEEKS * 4:
        time_differential = TWO_WEEKS * 4
    if time_differential < TWO_WEEKS // 4:
        time_differential = TWO_WEEKS // 4
    new_target = bits_to_target(previous_bits) * time_differential // TWO_WEEKS
    return target_to_bits(new_target)

加速してきた

[Bitcoin] Pay-to-script-hash

秘密鍵が盗まれた時のために、複数署名、マルチシグの仕組みがある
1-of-2だと比較的短いが、5-of-7のようにDER署名が必要になると長くなる

OP_CHECKMULTISIGはm m個の署名、n n個の異なる公開鍵

def op_checkmultisig(stack, z):
    if len(stack) < 1:
        return False
    n = decode_num(stack.pop())
    if len(stack) < n + 1:
        return False
    sec_pubkeys = []
    for _ in range(n):
        sec_pubkeys.append(stack.pop())
    m = decode_num(statck.pop())
    if len(stack) < m + 1:
        return False
    der_signatures = []
    for _ in range(m)
        der_signatures.append(stack.pop()[:-1])
    stack.pop()
    try:
        points = [S256.Point.parse(sec) for sec in sec_pubkeys]
        sigs = [Signature.parse(der) for der in der_signatures]

        for sig in sigs:
            if len(points) == 0:
                LOGGER.info("signatures no good or not in right order")
                return False
            success = False
            while points:
                point = points.pop(0)
                if point.verify(z, sig):
                    success = True
                    break
                if not success:
                    return False
        stack.append(encode_num(1))
    except (ValueError, SyntaxError):
        return False
    return True

ベアマルチシグは長くなる

### Pay-to-script-hash(p2sh)

            else:
                stack.append(cmd)
                if len(cmds) == 3 and cmds[0] == 0xa9 \
                    and type(cmds[1]) == bytes and len(cmds[1]) == 20 \
                    and cmds[2] == 0x87:
                    cmds.pop()
                    h160 = cmds.pop()
                    cmds.pop()
                    if not op_hash160(stack):
                        return False
                    stack.append(h160)
                    if not op_equal(stack):
                        return False
                    if not op_verify(stack):
                        LOGGER.info('bad p2sh h160')
                        return False
                    redeem_script = encode_varint(len(cmd)) + cmd
                    stream = BytesIO(redeem_script)
                    cmds.extend(Script.parse(stream).cmds)
from helper import encode_base58_checksum
h160 = bytes.fromhex('74d691da1574e6b3c192ecfb52cc8984ee7b6c56')
print(encode_base58_checksum(b'\x05' + h160))
def h160_to_p2pkh_address(h160, testnet=False):
    if testnet:
        prefix = b'\x6f'
    else:
        prefix = b'\x00'
    return encode_base58_checksum(prefix + h160)

def h160_to_p2sh_address(h160, testnet=False):
    if testnet:
        prefix = b'\xc4'
    else:
        prefix = b'\x05'
    return encode_base58_checksum(prefix + h160)

p2shの署名検証
-全てのscriptSigを空にする
– 署名対象となるp2shのインプットのScriptSigをRedeemScriptに置き換える
– ハッシュタイプを末尾に追加

from helper import hash256
modified_tx = bytes.fromhex('0100000001868278ed6ddfb6c1ed3ad5f8181eb0c7a38\
5aa0836f01d5e4789e6bd304d87221a000000475221022626e955ea6ea6d98850c994f9107b036\
b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98be\
c453e1ffac7fbdbd4bb7152aeffffffff04d3b11400000000001976a914904a49878c0adfc3aa0\
5de7afad2cc15f483a56a88ac7f400900000000001976a914418327e3f3dda4cf5b9089325a4b9\
5abdfa0334088ac722c0c00000000001976a914ba35042cfe9fc66fd35ac2224eebdafd1028ad2\
788acdc4ace020000000017a91474d691da1574e6b3c192ecfb52cc8984ee7b6c5687000000000\
1000000')
s256 = hash256(modified_tx)
z = int.from_bytes(s256, 'big')
print(hex(z))
from io import BytesIO
from ecc import S256Point, Signature
from helper import hash256, int_to_little_endian, encode_varint
from script import Script
from tx import Tx, TxIn, SIGHASH_ALL
hex_tx = '0100000001868278ed6ddfb6c1ed3ad5f8181eb0c7a385aa0836f01d5e4789e6\
bd304d87221a000000db00483045022100dc92655fe37036f47756db8102e0d7d5e28b3beb83a8\
fef4f5dc0559bddfb94e02205a36d4e4e6c7fcd16658c50783e00c341609977aed3ad00937bf4e\
e942a8993701483045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8eef\
53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e754022\
01475221022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb702103\
b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb7152aeffffffff04\
d3b11400000000001976a914904a49878c0adfc3aa05de7afad2cc15f483a56a88ac7f40090000\
0000001976a914418327e3f3dda4cf5b9089325a4b95abdfa0334088ac722c0c00000000001976\
a914ba35042cfe9fc66fd35ac2224eebdafd1028ad2788acdc4ace020000000017a91474d691da\
1574e6b3c192ecfb52cc8984ee7b6c568700000000'
hex_sec = '03b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4b\
b71'
hex_der = '3045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8ee\
f53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e75402\
2'
hex_redeem_der = '3045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8ee\
f53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e75402\
2'
hex_redeem_script = '475221022626e955ea6ea6d98850c994f9107b036b1334f18ca88\
30bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7f\
bdbd4bb7152ae'
sec = bytes.fromhex(hex_sec)
der = bytes.fromhex(hex_der)
redeem_script = Script.parse(BytesIO(bytes.fromhex(hex_redeem_script)))
stream = BytesIO(bytes.fromhex(hex_tx))
tx_obj = Tx.parse(stream)
s = int_to_little_endian(tx_obj.version, 4)
s += encode_varint(len(tx_obj.tx_ins))
i = tx_obj.tx_ins[0]
s += TxIn(i.prev_tx, i.prev_index, redeem_script, i.sequence).serialize()
s += encode_varint(len(tx_obj.tx_outs))
for tx_out in tx_obj.tx_outs:
	s += tx_out.serialize()
s += int_to_little_endian(tx_obj.locktime, 4)
s += int_to_little_endian(SIGHASH_ALL, 4)
z = int.from_bytes(hash256(s), 'big')
point = S256Point.parse(sec)
sig = Signature.parse(der)
print(point.verify(z, sig))
    def sig_hash(self, input_index):
        s = int_to_little_endian(self.version, 4)
        s += encode_varint(len(self.tx_ins))
        for i, tx_in in enumerate(self.tx_ins):
        	if i == input_index:
        		if redeem_script:
                    script_sig = redeem_script
                else:
                    script_sig = tx_in.script_pubkey(self.testnet)
        	else:
        		s += TxIn(
        				prev_tx = tx_in.prev_tx,
        				prev_index=tx_in.prev_index,
                        script_sig=script_sig,
        				sequence=tx_in.sequence,
        			).serialize()
        s += encode_varint(len(self.tx_outs))
        for tx_out in self.tx_outs:
        	s += tx_out.serialize()
        s += int_to_little_endian(self.locktime, 4)
        s += int_to_little_endian(SIGHASH_ALL, 4)
        h256 = hash256(s)
        return int.from_bytes(h256, 'big')

    def verify_input(self, input_index):
        tx_in = self.tx_ins[input_index]
        script_pubkey = tx_in.script_pubkey(testnet=self.testnet)
        if script_pubkey.is_p2sh_script_pubkey():
            cmd = tx_in.script_sig.cmds[-1]
            raw_redeem = encode_varint(len(cmd)) + cmd
            redeem_script = Script.parse(BytesIO(raw_redeem))
        else:
            redeem_script = None
        z = self.sig_hash(input_index, redeem_script)
        combined = tx_in.script_sig + script_pubkey
        return combined.evaluate(z)

[Bitcoin] トランザクションの作成と検証

ノードは、トランザクションを受信すると、各トランザクションがネットワークのルールに従っている事を確認する
トランザクションの検証と呼ぶ

1. トランザクションのインプットを過去に支払っていないない事
2. インプットの合計額がアウトプットの合計額以上になっている事
3. ScriptSigが前のScriptPubKeyのアンロックに成功している事

### インプット支払い状況の確認
どのフルノードでも、UTXOセットを検索すると確認できる

### インプットの合計額とアウトプットの合計額の確認
手数料を求めるメソッド

	def fee(self, testnet=False):
		input_sum, output_sum = 0, 0
		for tx_in in self.tx_ins:
			input_sum += tx_in.value(testnet=testnet)
		for tx_out in self.tx_outs:
			output_suum += tx_out.amount
		return input_sum - output_sum

手数料がマイナスでないかを確認する

from tx import Tx
from io import BytesIO

raw_tx = ('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf830\
3c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccf\
cf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8\
e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278\
afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88a\
c99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
stream = BytesIO(bytes.fromhex(raw_tx))
transaction = Tx.parse(stream)
print(transaction.fee() >= 0)

### 署名の確認
トランザクションの検証で最も難しい部分は、その署名を確認するプロセス
ECDSA署名アルゴリズムには、公開鍵P、署名ハッシュ、署名(r, s)が必要

from ecc import S256Point, Signature

sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e\
213bf016b278a')
der = bytes.fromhex('3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031c\
cfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9\
c8e10615bed')
z = 0x27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6
point = S256Point.parse(sec)
signature = Signature.parse(der)
print(point.verify(z, signature))

インプットごとに異なる署名ハッシュを計算する
1. 全てのScriptSigを空にする
2. 署名されているインプットのScriptSigを前のScriptPubKeyに置き換える
3. ハッシュタイプを付加する

from helper import hash256
from ecc import S256Point, Signature

modified_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914\
bd1d96a3f5f71bf8303c6a989c7d1000000001976a914a802fc56c704ce87c42d7c92eb75e7896\
bdc41ae88acfeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02\
e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288a\
c1943060001000000')
h256 = hash256(modified_tx)
z = int.from_bytes(h256, 'big')
print(hex(z))

sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e\
213bf016b278a')
der = bytes.fromhex('3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031c\
cfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9\
c8e10615bed')
z = 0x27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6
point = S256Point.parse(sec)
signature = Signature.parse(der)
print(point.verify(z, signature))

sig_hash, verify_input

    def sig_hash(self, input_index):
        s = int_to_little_endian(self.version, 4)
        s += encode_varint(len(self.tx_ins))
        for i, tx_in in enumerate(self.tx_ins):
        	if i == input_index:
        		s += TxIn(
        				prev_tx=tx_in.prev_tx,
        				prev_index=tx_in.prev_index,
        				script_sig=tx_in.script_pubkey(self.testnet),
        				sequence=tx_in.sequence,
        			).serialize()
        	else:
        		s += TxIn(
        				prev_tx = tx_in.prev_tx,
        				prev_index=tx_in.prev_index,
        				sequence=tx_in.sequence,
        			).serialize()
    	s += encode_varint(len(self.tx_outs))
    	for tx_out in self.tx_outs:
    		s += tx_out.serilize()
    	s += int_to_little_endian(self.locktime, 4)
    	s += int_to_little_endian(SIGHASH_ALL, 4)
    	h256 = hash256(s)
    	return int.from_bytes(h256, 'big')

    def verify_input(self, input_index):
        tx_in = self.tx_ins[input_index]
        script_pubkey = tx_in.script_pubkey(testnet=self.testnet)
        z = self.sig_hash(input_index)
        combined = tx_in.script_sig + script_pubkey
        return combined.evaluate(z)

トランザクション全体の検証

    def verify(self):
        if self.fee() < 0:
            return False
        for i in range(len(self.tx_ins)):
            if not self.verify_input(i):
                return False
        return True

### トランザクションの作成

def decode_base58(s):
	num = 0
	for c in s:
		num *= 58
		num += BASE58_ALPHABET.index(c)
	combined = num.to_bytes(25, byteorder='big')
	checksum = combined[-4:]
	if hash256(combined[:-4])[:4] != checksum:
		raise ValueError('bad address: {} {}'.format(checksum, hash256(combined[:-4])[:4]))
	return combined[1:-4]
def p2pkh_script(h160):
    return Script([0x76, 0xa9, h160, 0x88, 0xac])

トランザクションの作成

from helper import decode_base58, SIGHASH_ALL
from script import p2pkh_script, Script
from tx import TxIn, TxOut, Tx
prev_tx = bytes.fromhex('0d6fe5213c0b3291f208cba8bfb59b7476dffacc4e5cb66f6\
eb20a080843a299')
prev_index = 13
tx_in = TxIn(prev_tx, prev_index)
tx_outs = []
change_amount = int(0.33*100000000)
change_h160 = decode_base58('mzx5YhAH9kNHtcN481u6WkjeHjYtVeKVh2')
change_script = p2pkh_script(change_h160)
change_output = TxOut(amount=change_amount, script_pubkey=change_script)
target_amount = int(0.1*100000000)
target_h160 = decode_base58('mnrVtF8DWjMu839VW3rBfgYaAfKk8983Xf')
target_script = p2pkh_script(target_h160)
target_output = TxOut(amount=target_amount, script_pubkey=target_script)
tx_obj = Tx(1, [tx_in], [change_output, target_output], 0, True)
print([tx_obj])

[tx: cd30a8da777d28ef0e61efe68a9f7c559c1d3e5bcd7b265c850ccb4068598d11
version: 1
tx_ins:
0d6fe5213c0b3291f208cba8bfb59b7476dffacc4e5cb66f6eb20a080843a299:13
tx_outs:
33000000:OP_DUP OP_HASH160 d52ad7ca9b3d096a38e752c2018e6fbc40cdf26f OP_EQUALVERIFY OP_CHECKSIG
10000000:OP_DUP OP_HASH160 507b27411ccf7f16f10297de6cef3f291623eddf OP_EQUALVERIFY OP_CHECKSIG
locktime: 0]

### トランザクションへの署名

from ecc import PrivateKey
from helper import SIGHASH_ALL
z = transaction.sig_hash(0)
private_key = PrivateKey(secret=8675309)
der = private_key.sign(z).der()
sig = der + SIGHASH_ALL.to_bytes(1, 'big')
sec = private_key.point.sec()
script_sig = Script([sig, sec])
transaction.tx_ins[0].script_sig = script_sig
print(transaction.serialize().hex())

sign_input

    def sign_input(self, input_index, private_key):
        z = self.sig_hash(input_index)
        der = private_key.sign(z).der()
        sig = der + SIGHASH_ALL.to_bytes(1, 'big')
        sec = private_key.point.sec()
        self.tx_ins[input_index].script_sig = Script([sig, sec])
        return self.verify_input(input_index)

### 自分のトランザクションを作成

from ecc import PrivateKey
from helper import hash256, little_endian_to_int
secret = little_endian_to_int(hash256(b'hpscript secret'))
private_key = PrivateKey(secret)
print(private_key.point.address(testnet=True))

$ python3 main.py
mtWT4Tga6ru2HXokJJYSp3wJKGXukTRaFk

from ecc import PrivateKey
from helper import decode_base58, SIGHASH_ALL
from script import p2pkh_script, Script
from tx import TxIn, TxOut, Tx
prev_tx = bytes.fromhex('75a1c4bc671f55f626dda1074c7725991e6f68b8fcefcfca7\
b64405ca3b45f1c')
prev_index = 1
target_address = 'miKegze5FQNCnGw6PKyqUbYUeBa4x2hFeM'
target_amount = 0.01
change_address = 'mtWT4Tga6ru2HXokJJYSp3wJKGXukTRaFk'
change_amount = 0.009
secret = 98765309
priv = PrivateKey(secret=secret)
tx_ins = []
tx_ins.append(TxIn(prev_tx, prev_index))
tx_outs = []
h160 = decode_base58(target_address)
script_pubkey = p2pkh_script(h160)
target_satoshis = int(target_amount*100000000)
tx_outs.append(TxOut(target_satoshis, script_pubkey))
h160 = decode_base58(change_address)
script_pubkey = p2pkh_script(h160)
change_satoshis = int(change_amount*100000000)
tx_outs.append(TxOut(change_satoshis, script_pubkey))
tx_obj = Tx(1, tx_ins, tx_outs, 0, testnet=True)
print(tx_obj.sign_input(0, priv))
print(tx_obj.serialize().hex())
from ecc import PrivateKey
from helper import decode_base58, SIGHASH_ALL
from script import p2pkh_script, Script
from tx import TxIn, TxOut, Tx
prev_tx_1 = bytes.fromhex('11d05ce707c1120248370d1cbf5561d22c4f83aeba04367\
92c82e0bd57fe2a2f')
prev_index_1 = 1
prev_tx_2 = bytes.fromhex('51f61f77bd061b9a0da60d4bedaaf1b1fad0c11e65fdc74\
4797ee22d20b03d15')
prev_index_2 = 1
target_address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv'
target_amount = 0.0429
secret = 8675309
priv = PrivateKey(secret=secret)
tx_ins = []
tx_ins.append(TxIn(prev_tx_1, prev_index_1))
tx_ins.append(TxIn(prev_tx_2, prev_index_2))
tx_outs = []
h160 = decode_base58(target_address)
script_pubkey = p2pkh_script(h160)
target_satoshis = int(target_amount*100000000)
tx_outs.append(TxOut(target_satoshis, script_pubkey))
tx_obj = Tx(1, tx_ins, tx_outs, 0, testnet=True)
print(tx_obj.sign_input(0, priv))
print(tx_obj.sign_input(1, priv))
print(tx_obj.serialize().hex())

少しずつ見えてきましたね

[Bitcoin] Script

コインをロック/アンロックする機能はビットコインを転送する仕組み(スマートコントラクト)
ロック: ビットコインにエンティティを与える
アンロック: 受け取ったビットコインを支払う

楕円曲線暗号はScriptがトランザクションに適切に使用許可が与えられているか検証する
Scriptはビットコインのスマートコントラクト言語、使用可能な条件を表すためのプログラミング言語
ScriptはForthに似たスタックベース言語、ループ処理できない
L Ethereumはチューリング完全だが、プログラム実行時にgasを支払わなければならない

### Scriptの動作
スタック上の要素を操作する 
コマンドはエレメント(データ)とオペレーション(何らかの操作)
エレメントe.g. signature, pubkey
オペレーションe.g. OP_DUB スタックの先頭エレメントを複製してスタックにプッシュ
OP_CHECKSIG: pubkey, signatureをプップし、有効かどうかを確認する

def op_dup(stack):
    if len(stack) < 1:
        return False
    stack.append(stack[-1])
    return True

    118: op_dup,
def op_hash160(stack):
    if len(stack) < 1:
        return False
    element = stack.pop()
    h160 = hash160(element)
    stack.append(h160)
    return True

### スクリプトのパースとシリアライズ

    def __add__(self, other):
        return Script(self.cmds + other.cmds)

    @classmethod
    def parse(cls, s):
        length = read_varint(s)
        cmds = []
        count = 0
        while count < length:
            current = s.read(1)
            count += 1
            current_byte = current[0]
            if current_byte >= 1 and current_byte <= 75:
                n = current_byte
                cmds.append(s.read(n))
                count += n
            elif current_byte == 76:
                data_length = little_endian_to_int(s.read(1))
                cmds.append(s.read(data_length))
                count += data_length + 1
            elif current_byte == 77:
                data_length = little_endian_to_int(s.read(2))
                cmds.append(s.read(data_length))
                count += data_length + 2
            else:
                op_code = current_byte
                cmds.append(op_code)
        if count != length:
            raise SyntaxError('parsing script failed')
        return cls(cmds)

    def raw_serialize(self):
        result = b''
        for cmd in self.cmds:
            if type(cmd) == int:
                result += int_to_little_endian(cmd, 1)
            else:
                length = len(cmd)
                if length < 75:
                    result += int_to_little_endian(length, 1)
                elif length > 75 and length < 0x100:
                    result += int_to_little_endian(76, 1)
                    result += int_to_little_endian(length, 1)
                elif length >= 0x100 and length <= 520:
                    result += int_to_little_endian(77, 1)
                    result += int_to_little_endian(length, 2)
                else:
                    raise ValueError('too long an cmd')
                result += cmd
        return result

    def serialize(self):
        result = self.raw_serialize()
        total = len(result)
        return encode_varint(total) + result

ビットコインには様々な種類の標準的なスクリプトがある
p2pk, p2pkh, p2sh, p2wpkh, p2wsh

### p2pk
pubkey, OP_CHECHSIG, signature

from script import Script

z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d
sec = bytes.fromhex('04887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e\
4da568744d06c61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34')
sig = bytes.fromhex('3045022000eff69ef2b1bd93a66ed5219add4fb51e11a840f4048\
76325a1e8ffe0529a2c022100c7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fd\
dbdce6feab601')
script_pubkey = Script([sec, 0xac])
script_sig = Script([sig])
combined_script = script_sig + script_pubkey
print(combined_script.evaluate(z))

op_checksig

def op_checksig(stack, z):
    if len(stack) < 2:
        return False
    sec_pubkey = stack.pop()
    der_signature = stack.pop()[:-1]
    try:
        point = S256Point.parse(sec_pubkey)
        sig = Signature.parse(der_signature)
    except (ValueError, SyntaxError) as e:
        return False
    if point.verify(z, sig):
        stack.append(encode_num(1))
    else:
        stack.append(encode_num(0))
    return True
from script import Script

script_pubkey = Script([0x76, 0x76, 0x95, 0x93, 0x56, 0x87])
script_sig = Script([0x52])
combined_script = script_sig + script_pubkey
print(combined_script.evaluate(0))
from script import Script

script_pubkey = Script([0x6e, 0x87, 0x91, 0x69, 0xa7, 0x7c, 0xa7, 0x87])
c1 = '255044462d312e330a25e2e3cfd30a0a0a312030206f626a0a3c3c2f576964746820\
32203020522f4865696768742033203020522f547970652034203020522f537562747970652035\
203020522f46696c7465722036203020522f436f6c6f7253706163652037203020522f4c656e67\
74682038203020522f42697473506572436f6d706f6e656e7420383e3e0a73747265616d0affd8\
fffe00245348412d3120697320646561642121212121852fec092339759c39b1a1c63c4c97e1ff\
fe017f46dc93a6b67e013b029aaa1db2560b45ca67d688c7f84b8c4c791fe02b3df614f86db169\
0901c56b45c1530afedfb76038e972722fe7ad728f0e4904e046c230570fe9d41398abe12ef5bc\
942be33542a4802d98b5d70f2a332ec37fac3514e74ddc0f2cc1a874cd0c78305a215664613097\
89606bd0bf3f98cda8044629a1'
c2 = '255044462d312e330a25e2e3cfd30a0a0a312030206f626a0a3c3c2f576964746820\
32203020522f4865696768742033203020522f547970652034203020522f537562747970652035\
203020522f46696c7465722036203020522f436f6c6f7253706163652037203020522f4c656e67\
74682038203020522f42697473506572436f6d706f6e656e7420383e3e0a73747265616d0affd8\
fffe00245348412d3120697320646561642121212121852fec092339759c39b1a1c63c4c97e1ff\
fe017346dc9166b67e118f029ab621b2560ff9ca67cca8c7f85ba84c79030c2b3de218f86db3a9\
0901d5df45c14f26fedfb3dc38e96ac22fe7bd728f0e45bce046d23c570feb141398bb552ef5a0\
a82be331fea48037b8b5d71f0e332edf93ac3500eb4ddc0decc1a864790c782c76215660dd3097\
91d06bd0af3f98cda4bc4629b1'
collision1 = bytes.fromhex(c1)
collision2 = bytes.fromhex(c2)
script_sig = Script([collision1, collision2])
combined_script = script_sig + script_pubkey
print(combined_script.evaluate(0))

[Bitcoin] トランザクション

トランザクションとはエンティティから別のエンティティに移動する事

– トランザクションコンポーネント
バージョン(トランザクション追加機能)
インプット(どのビットコインを使うか)
アウトプット(どこにビットコインを移動するか)
ロックタイム(いつトランザクションが有効になるか)

### バージョン

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:\n{}locktime: {}'.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]

	@classmethod
	def parse(cls, s, testnet=False):
		version = little_endian_to_int(s.read(4))
		return cls(version, None, None, None, testnet=testnet)

### インプット
前のトランザクションのアウトプットを示す
L 前に受け取ったビットコインへの参照
L 支払う本人のビットコインである証明

variable integer = varintを使用する

def read_varint(s):
    i = s.read(1)[0]
    if i == 0xfd:
        return little_endian_to_int(s.read(2))
    elif i == 0xfe:
        return little_endian_to_int(s.read(4))
    elif i == 0xff:
        return little_endian_to_int(s.read(8))
    else:
        return i

def encode_varint(i):
    if i < 0xfd:
        return bytes([i])
    elif i < 0x10000:
        return b'\xfd' + int_to_little_endian(i, 2)
    elif i < 0x100000000:
        return b'\xfe' + int_to_little_endian(i, 4)
    elif i < 0x10000000000000000:
        return b'\xff' + int_to_little_endian(i, 8)
    else:
        raise ValueError('integer too large: {}'.format(i))

インプットフィールド
– 前のトランザクションID, トランザクションインデックス、ScriptSig, シーケンス

class TxIn:
	def __init__(self, prev_tx, prev_index, script_sig=None, sequence=0xffffffff):
		self.prev_tx = prev_tx
		self.prev_index = prev_index
		if script_sig is None:
			self.script_sig = Script()
		else:
			self.script_sig = script_sig
		self.sequence = sequence

	def __repr__(self):
		return '{}:{}'.format(
				self.prev_tx.hex(),
				self.prev_index,
			)
script_hex = ('6b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccf\
cf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8\
e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278\
a')
stream = BytesIO(bytes.fromhex(script_hex))
script_sig = Script.parse(stream)
print(script_sig)

### アウトプット
ビットコインの送信先を定義
varint形式のアウトプット数で開始
フィールドには額とscriptpubkeyがある

TxOut

class tx:
	// 省略
	@classmethod
	def parse(cls, s, testnet=False):
		version = little_endian_to_int(s.read(4))
		num_inputs = read_varint(s)
		inputs = []
		for _ in range(num_inputs):
			inputs.append(TxIn.parse(s))
		num_outputs = read_variant(s)
		for _ in range(num_outputs):
			outputs.append(TxOut.parse(s))
		return cls(version, inputs, outpute, None, testnet=testnet)

class TxOut:

	def __init__(self, amount, script_pubkey):
		self.amount = amount
		self.script_pubkey = script_pubkey

	def __repr__(self):
		return '{}:{}'.format(self.amount, self.script_pubkey)

	@classmethod
	def parse(cls, s):
		amount = little_endian_to_int(s.read(8))
		script_pubkey = Script.parse(s)
		return cls(amount, script_pubkey)

### ロックタイム

	@classmethod
	def parse(cls, s, testnet=False):
		version = little_endian_to_int(s.read(4))
		num_inputs = read_varint(s)
		inputs = []
		for _ in range(num_inputs):
			inputs.append(TxIn.parse(s))
		num_outputs = read_variant(s)
		outputs = []
		for _ in range(num_outputs):
			outputs.append(TxOut.parse(s))
		locktime = little_endian_to_int(s.read(4))
		return cls(version, inputs, outpute, locktime, testnet=testnet)
hex_transaction = '010000000456919960ac691763688d3d3bcea9ad6ecaf875df5339e148a1fc61c6ed7a069e010000006a47304402204585bcdef85e6b1c6af5c2669d4830ff86e42dd205c0e089bc2a821657e951c002201024a10366077f87d6bce1f7100ad8cfa8a064b39d4e8fe4ea13a7b71aa8180f012102f0da57e85eec2934a82a585ea337ce2f4998b50ae699dd79f5880e253dafafb7feffffffeb8f51f4038dc17e6313cf831d4f02281c2a468bde0fafd37f1bf882729e7fd3000000006a47304402207899531a52d59a6de200179928ca900254a36b8dff8bb75f5f5d71b1cdc26125022008b422690b8461cb52c3cc30330b23d574351872b7c361e9aae3649071c1a7160121035d5c93d9ac96881f19ba1f686f15f009ded7c62efe85a872e6a19b43c15a2937feffffff567bf40595119d1bb8a3037c356efd56170b64cbcc160fb028fa10704b45d775000000006a47304402204c7c7818424c7f7911da6cddc59655a70af1cb5eaf17c69dadbfc74ffa0b662f02207599e08bc8023693ad4e9527dc42c34210f7a7d1d1ddfc8492b654a11e7620a0012102158b46fbdff65d0172b7989aec8850aa0dae49abfb84c81ae6e5b251a58ace5cfeffffffd63a5e6c16e620f86f375925b21cabaf736c779f88fd04dcad51d26690f7f345010000006a47304402200633ea0d3314bea0d95b3cd8dadb2ef79ea8331ffe1e61f762c0f6daea0fabde022029f23b3e9c30f080446150b23852028751635dcee2be669c2a1686a4b5edf304012103ffd6f4a67e94aba353a00882e563ff2722eb4cff0ad6006e86ee20dfe7520d55feffffff0251430f00000000001976a914ab0c0b2e98b1ab6dbf67d4750b0a56244948a87988ac005a6202000000001976a9143c82d7df364eb6c75be8c80df2b3eda8db57397088ac46430600'
stream = BytesIO(bytes.fromhex(hex_transaction))
tx_obj = Tx.parse(stream)
print(tx_obj.tx_ins[1].script_sig)
print(tx_obj.tx_outs[0].script_pubkey)
print(tx_obj.tx_outs[1].amount)

$ python3 app.py
304402207899531a52d59a6de200179928ca900254a36b8dff8bb75f5f5d71b1cdc26125022008b422690b8461cb52c3cc30330b23d574351872b7c361e9aae3649071c1a71601 035d5c93d9ac96881f19ba1f686f15f009ded7c62efe85a872e6a19b43c15a2937
OP_DUP OP_HASH160 ab0c0b2e98b1ab6dbf67d4750b0a56244948a879 OP_EQUALVERIFY OP_CHECKSIG
40000000

### シリアライズ

class tx
	def serialize(self):
		result = int_to_little_endian(self.version, 4)
		result += encode_varint(len(self.tx_ins))
		for tx_in in self.tx_ins:
			result += tx_in.serialize()
		result += encode_varint(len(self.tx_outs))
		for tx_out in self.tx_outs:
			result += tx_out.serialize()
		result += int_to_little_endian(self.locktime, 4)
		return result

class TxIn
	def serialize(self):
		result = self.prev_tx[::-1]
		result += int_to_little_endian(self.prev_index, 4)
		result += self.script_sig.serialize()
		result += int_to_little_endian(self.sequence, 4)
		return result

class TxOut
	def serialize(self):
		result = int_to_little_endian(self.amount, 8)
		result += self.script_pubkey.serialize()
		return result

### トランザクション手数料
マイナーがトランザクションに取り込む手数料がトランザクション手数料となる

class TxIn:
	def fetch_tx(self, testnet=False):
		return TxFetcher.fetch(self.prev_tx.hex(), testnet=testnet)

	def value(self, testnet=False)
		tx = self.fetch_tx(testnet=testnet)
		return tx.tx_outs[self.prev_index].amount

	def script_pubkey(self, testnet=False):
		tx = self.fetch_tx(testnet=testnet)
		return tx.tx_outs[self.prev_index].script_pubkey

class TxFetcher:
	cache = {}

	@classmethod
	def get_url(cls, testnet=False)
		if testnet:
			return 'http://testnet.programmingbitcoin.com'
		else:
			return 'http://mainnet.programmingbitcoin.com'

	@classmethod
	def fetch(cls, tx_id, testnet=False, fresh=False):
		if fresh or (tx_id not in cls.cache):
			url = '{}/tx/{}.hex'.format(cls.get_url(testnet), tx_id)
			response = requests.get(url)
			try:
				raw = bytes.fromhex(response.text.strip())
			ecept ValueError:
				raise ValueError('unexpected response: {}'.format(response.text))
			if raw[4] == 0:
				raw = raw[:4] + raw[6:]
				tx = Tx.parse(BytesIO(raw), testnet=testnet)
				tx.locktime = little_endian_to_int(raw[-4:])
			else:
				tx = Tx.parse(BytesIO(raw), testnet=testnet)
			if tx.id() != tx_id:
				raise ValueError('not the same id: {} vs {}'.foramt(tx.id(), tx_id))
				clc.cache[tx_id] = tx
			cls.cache[tx_id].testnet = testnet
			return cls.cache[tx_id]

class Tx:

	def fee(self, testnet=False):
		input_sum, output_sum = 0, 0
		for tx_in in self.tx_ins:
			input_sum += tx_in.value(testnet=testnet)
		for tx_out in self.tx_outs:
			output_suum += tx_out.amount
		return input_sum - output_sum

[Bitcoin] DER署名

– Signatureを圧縮する
– 署名をシリアライズする標準をDERフォーマットと呼ぶ

1. 0x30バイトで開始
2. 署名の残りの長さをエンコード
3. マーカーバイト0x02追加
4. rをビッグエンディアン整数としてエンコード
5. マーカーバイト0x02を追加
6. sをビッグエンディアン整数としてエンコード

class Signature:

	def __init__(self, r, s):
		self.r = r
		self.s = s

	def __repr__(self):
		return 'Signature({:x},{:x})'.format(self.r, self.s)

	def der(self):
		rbin = self.r.to_bytes(32, byteorder='big')
		rbin = rbin.lstrip(b'\x00')
		if rbin[0] & 0x80:
			rbin = b'\x00' + rbin
		result = byte([2, len(rbin)]) + rbin
		sbin = self.s.to_bytes(32, byteorder='big')
		sbin = sbin.lstrip(b'\x00')
		if sbin[0] & 0x80:
			sbin = b'\x00' + sbin
		result += bytes([2, len(sbin)]) + sbin
		return bytes([0x30, len(result)]) + result
r = 0x37206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c6
s = 0x8ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec
sig = Signature(r, s)
print(sig.der().hex())

$ python3 app.py
3045022037206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c60221008ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec

### Base58

def encode_base58(s):
    count = 0
    for c in s:
        if c == 0:
            count += 1
        else:
            break
    num = int.from_bytes(s, 'big')
    prefix = '1' * count
    result = ''
    while num > 0: 
        num, mod = divmod(num, 58)
        result = BASE58_ALPHABET[mod] + result
    return prefix + result

h = '7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d'
print(encode_base58(bytes.fromhex(h)))
h = 'eff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c'
print(encode_base58(bytes.fromhex(h)))
h = 'c7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6'
print(encode_base58(bytes.fromhex(h)))

### アドレス形式
1. メインネットアドレスは先頭を0x00, テストネットは0x6f
2. SECフォーマットを取り出し、sha256とripemd160操作を行う
3. 1のプレフィックスと2のハッシュ操作の結果を結合
4. 3の結果にhash256を行い先頭の4バイトを取得
5. 3と4を結合させてBase58でエンコード

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

def hash160(s):
	return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() 

class S256Point:

	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)
priv = PrivateKey(5002)
print(priv.point.address(compressed=False, testnet=True))
priv = PrivateKey(2020**5)
print(priv.point.address(compressed=True, testnet=True))
priv = PrivateKey(0x12345deadbeef)
print(priv.point.address(compressed=True, testnet=False))

### WIF(ウォレットインポート形式)
1. メインネット用の秘密鍵は0x80、テストネット用は0xefプレフィックス
2. 秘密鍵を32バイトのビッグエンディアンでエンコード
3. SECフォーマットが圧縮形式の場合、末尾に0x01
4. 123を結合
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)
priv = PrivateKey(5003)
print(priv.wif(compressed=True, testnet=True))
priv = PrivateKey(2021**5)
print(priv.wif(compressed=False, testnet=True))
priv = PrivateKey(0x54321deadbeef)
print(priv.wif(compressed=True, testnet=False))

### リトルエンディアン

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')

テストネットアドレスの作成

passphrase = b'info@hpscript.com my secret'
secret = little_endian_to_int(hash256(passphrase))
priv = PrivateKey(secret)
print(priv.point.address(testnet=True))

テストネットのfaucetサイト
https://testnet-faucet.mempool.co/

大分入ってきましたね