Skip to content

Commit

Permalink
Add last version
Browse files Browse the repository at this point in the history
This commit adds the last functional version for some points separated
into multiple files.
  • Loading branch information
davidchocholaty committed Aug 30, 2024
1 parent 399da97 commit acf06e0
Show file tree
Hide file tree
Showing 11 changed files with 462 additions and 1 deletion.
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ecdsa==0.19.0
pycryptodome==3.20.0
sha3==0.2.1
8 changes: 7 additions & 1 deletion run.sh
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
# Update this file to run your own code
#!/bin/bash

python3 -m venv venv
venv/bin/pip install --upgrade pip
venv/bin/pip install -r requirements.txt
source venv/bin/activate
python3 src/main.py --mempool=mempool > output.txt
31 changes: 31 additions & 0 deletions src/coinbase_transaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
COINBASE_TRANSACTION = {
"version": 2,
"locktime": 0xffffffff,
"vin": [
{
"txid": "0000000000000000000000000000000000000000000000000000000000000000",
"vout": 0xffffffff,
"sequence": 0xffffffff,
"is_coinbase": True,
"scriptsig": "160014fd91039e25b0827748473fce351afd8ead4ecdce",
"scriptsig_asm": "OP_PUSHBYTES_22 0014fd91039e25b0827748473fce351afd8ead4ecdce",
"witness": [
"0000000000000000000000000000000000000000000000000000000000000000",
]
}
],
"vout": [
{
"scriptpubkey": "0014ad4cc1cc859c57477bf90d0f944360d90a3998bf",
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 ad4cc1cc859c57477bf90d0f944360d90a3998bf",
"scriptpubkey_type": "v0_p2wpkh",
"scriptpubkey_address": "bc1q44xvrny9n3t5w7lep58egsmqmy9rnx9lt6u0tc",
"value": 100000
},
{
"scriptpubkey": "",
"scriptpubkey_type": "op_return",
"value": 0
}
]
}
1 change: 1 addition & 0 deletions src/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TARGET = "0000ffff00000000000000000000000000000000000000000000000000000000"
39 changes: 39 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import argparse

from src.coinbase_transaction import COINBASE_TRANSACTION
from src.mempool import MemPool
from src.mining import calculate_witness_commitment, block_mining
from src.transaction import calculate_txid

def parse_arguments():
parser = argparse.ArgumentParser(description='Simulation of the mining process of a block')
parser.add_argument('--mempool', type=str, required=True, help='Path to the directory containing the JSON files with transactions.')
return parser.parse_args()

if __name__ == '__main__':
args = parse_arguments()

if args.mempool is None:
# TODO error
pass

mempool = MemPool(args.mempool)

block_transactions = [COINBASE_TRANSACTION] + mempool.valid_transactions

transaction_hashes = [calculate_txid(COINBASE_TRANSACTION)] + [calculate_txid(json_transaction) for json_transaction in block_transactions[1:]]
block_hash = block_mining(transaction_hashes).hex()

wtxids = ["0000000000000000000000000000000000000000000000000000000000000000"] + transaction_hashes[1:]

witness_commitment = calculate_witness_commitment(wtxids)
scriptpubkey_wc = '6a24aa21a9ed' + witness_commitment

COINBASE_TRANSACTION["vout"][1]["scriptpubkey"] = scriptpubkey_wc

coinbase_serialized = serialize_transaction(COINBASE_TRANSACTION, segwit=True)

print(block_hash)
print(coinbase_serialized.hex())
for transaction in transaction_hashes:
print(transaction)
6 changes: 6 additions & 0 deletions src/mempool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class MemPool:
def __init__(self, root_dir):
self.root_dir = root_dir
self.transaction_files = [os.path.join(self.root_dir, file) for file in os.listdir(self.root_dir) if file.endswith('.json')]
self.transactions = [Transaction(file) for file in self.transaction_files]
self.valid_transactions = [transaction.json_transaction for transaction in self.transactions if transaction.is_valid()]
79 changes: 79 additions & 0 deletions src/mining.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import hashlib
import time

from src.constants import TARGET

def calculate_witness_commitment(wtxids):
merkle_root = calculate_merkle_root(wtxids)
merkle_root_bytes = bytes.fromhex(merkle_root)
witness_reserved_value = '0000000000000000000000000000000000000000000000000000000000000000'
witness_reserved_value_bytes = bytes.fromhex(witness_reserved_value)
return hashlib.sha256(hashlib.sha256(b''.join([merkle_root_bytes,witness_reserved_value_bytes])).digest()).hexdigest()

def calculate_merkle_root(transactions):
transaction_hashes = []
# reverse
for tx in transactions:
tx_bytes = bytes.fromhex(tx)
reversed_tx_bytes = tx_bytes[::-1]
transaction_hashes.append(reversed_tx_bytes.hex())

while len(transaction_hashes) > 1:
new_hashes = []

for i in range(0, len(transaction_hashes), 2):
if (i + 1 == len(transaction_hashes)):
new_hash = hashlib.sha256(hashlib.sha256(bytes.fromhex(transaction_hashes[i] + transaction_hashes[i])).digest()).hexdigest()
else:
new_hash = hashlib.sha256(hashlib.sha256(bytes.fromhex(transaction_hashes[i] + transaction_hashes[i + 1])).digest()).hexdigest()
new_hashes.append(new_hash)

transaction_hashes = new_hashes

return transaction_hashes[0]

def is_valid_block_hash(block_hash, target):
if block_hash == "":
return False

return block_hash < target

def calculate_bits(target_hex):
leading_zeros = len(target_hex) - len(target_hex.lstrip('0'))
exponent = (len(target_hex) - 1) // 2

coefficient_hex = target_hex[leading_zeros:].rstrip('0')
coefficient = int(coefficient_hex or '0', 16)

bits = (exponent << 24) + coefficient

return bits

def block_mining(transaction_hashes, version=4):
# Calculate Merkle root hash of transactions
merkle_root_hashed = calculate_merkle_root(transaction_hashes)
prev_block_hash = "0000000000000000000000000000000000000000000000000000000000000000"
nonce = 0
bits = calculate_bits(TARGET)
timestamp = int(time.time())

block_hash = ""

block_header = []

while not is_valid_block_hash(block_hash, TARGET):
# Construct block header
block_header = []
block_header += [version.to_bytes(4, byteorder='little')]
block_header += [bytes.fromhex(prev_block_hash)[::-1]]
block_header += [bytes.fromhex(merkle_root_hashed)]
block_header += [timestamp.to_bytes(4, byteorder='little')]
block_header += [bits.to_bytes(4, byteorder='little')]
block_header += [nonce.to_bytes(4, byteorder='little')]

# Double sha256 and reverse
block_hash = hashlib.sha256(hashlib.sha256(b''.join(block_header)).digest()).digest()
block_hash = block_hash[::-1].hex()
nonce += 1

return b''.join(block_header)
80 changes: 80 additions & 0 deletions src/serialize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
def serialize_input(tx_input, override=None):
serialized_input = []
serialized_input += [bytes.fromhex(tx_input["txid"])[::-1]] # Reversed txid
serialized_input += [tx_input["vout"].to_bytes(4, byteorder="little")]

if override is None:
serialized_input += [serialize_script(bytes.fromhex(tx_input["scriptsig"]))]
elif override is True:
serialized_input += [serialize_script(bytes.fromhex(tx_input["prevout"]["scriptpubkey"]))]
elif override is False:
serialized_input += [serialize_script(bytes.fromhex(""))]

serialized_input += [tx_input["sequence"].to_bytes(4, byteorder="little")]

return b''.join(serialized_input)

def encode_int(i, nbytes, encoding='little'):
return i.to_bytes(nbytes, encoding)

def serialize_script(script):
return b''.join([encode_varint(len(script)), script])

def serialize_output(output):
serialized_output = []

serialized_output += [output["value"].to_bytes(8, byteorder="little")]
serialized_output += [serialize_script(bytes.fromhex(output["scriptpubkey"]))]

return b''.join(serialized_output)

def encode_varint(i):
if i < 0xfd:
return bytes([i])
elif i < 0x10000:
return b'\xfd' + i.to_bytes(2, 'little')
elif i < 0x100000000:
return b'\xfe' + i.to_bytes(4, 'little')
elif i < 0x10000000000000000:
return b'\xff' + i.to_bytes(8, 'little')
else:
raise ValueError("integer too large: %d" % (i, ))

def serialize_transaction(transaction, index=-1, sighash_type=1, segwit=False):
# for now for p2pkh
message = []
message += [transaction["version"].to_bytes(4, byteorder="little")]

if segwit:
message += [b'\x00\x01'] # segwit marker

# inputs
message += [encode_varint(len(transaction["vin"]))]

inputs = transaction["vin"]
outputs = transaction["vout"]

if index == -1:
message += [serialize_input(tx_in) for tx_in in inputs]
else:
message += [serialize_input(tx_in, index == i) for i, tx_in in enumerate(inputs)]

# outputs
message += [encode_varint(len(transaction["vout"]))]
message += [serialize_output(tx_out) for tx_out in outputs]

# witness
if segwit:
for tx_in in inputs:
message += [encode_varint(len(tx_in["witness"]))]

for item in tx_in["witness"]:
item_bytes = bytes.fromhex(item)
message += [encode_varint(len(item_bytes)), item_bytes]

# encode rest of data
message += [transaction["locktime"].to_bytes(4, byteorder="little")]
hash_type = 1
message += [hash_type.to_bytes(4, 'little') if index != -1 else b''] # 1 = SIGHASH_ALL

return b''.join(message)
72 changes: 72 additions & 0 deletions src/transaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import json

from src.verify import valid_transaction_syntax, non_empty_vin_vout

def calculate_txid(transaction_content, coinbase=False):
# Serialize the transaction content
if coinbase:
serialized_transaction = serialize_transaction(transaction_content, segwit=True) #json.dumps(transaction_content, sort_keys=True).encode()
else:
serialized_transaction = serialize_transaction(transaction_content) #json.dumps(transaction_content, sort_keys=True).encode()

# Calculate double SHA-256 hash
hash_result = hashlib.sha256(hashlib.sha256(serialized_transaction).digest()).digest()

# Reverse byte order to obtain txid
txid = hash_result[::-1].hex()

return txid

class Transaction:
def __init__(self, transaction_json_file):
# Parse transaction.
with open(transaction_json_file) as transaction:
json_transaction = json.load(transaction)

# check jestli je valid
if valid_transaction_syntax(json_transaction):
self.transaction_name = get_filename_without_extension(transaction_json_file)
self.version = json_transaction['version']
self.locktime = json_transaction['locktime']
self.vin = json_transaction['vin']
self.vout = json_transaction['vout']
self.json_transaction = json_transaction
else:
print('Invalid transaction syntax')

def is_valid(self):
if not non_empty_vin_vout(self.vin, self.vout):
return False

input_sum = 0
for input in self.vin:
input_sum = input_sum + input['prevout']['value']

output_sum = 0
for output in self.vout:
output_sum = output_sum + output['value']

if input_sum < output_sum:
return False

input_idx = 0
for input in self.vin:
if 'scriptsig' in input:
scriptsig = input['scriptsig']

scriptpubkey_type = input['prevout']['scriptpubkey_type']

if scriptsig == "" or scriptpubkey_type not in ["p2pkh", "p2sh"]:
return False

if scriptpubkey_type == 'p2pkh':
if not verify_p2pkh_transaction(input_idx, self.json_transaction):
return False
else:
return False
else:
return False

input_idx += 1

return True
8 changes: 8 additions & 0 deletions src/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import os

def get_filename_without_extension(file_path):
# Get the base filename from the path
filename = os.path.basename(file_path)
# Remove the extension
filename_without_extension = os.path.splitext(filename)[0]
return filename_without_extension
Loading

0 comments on commit acf06e0

Please sign in to comment.