diff --git a/main.py b/main.py index 1254793..6086010 100644 --- a/main.py +++ b/main.py @@ -33,6 +33,7 @@ async def main(): # Step 2: Validate transactions asynchronously valid_transactions = await validate_transactions(transactions) + print(valid_transactions) # Step 3: Mine the block block_data = mine_block(valid_transactions) diff --git a/requirements.txt b/requirements.txt index caf24cf..b350510 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,7 @@ docutils~=0.17.1 keyring~=24.3.0 typing_extensions~=4.9.0 setuptools~=69.0.3 -future~=0.18.2 \ No newline at end of file +future~=0.18.2 +bitcoinlib~=0.6.15 +ply~=3.11 +bcrypt~=3.2.0 \ No newline at end of file diff --git a/src/__pycache__/mine.cpython-310.pyc b/src/__pycache__/mine.cpython-310.pyc index 4928247..0b3ea13 100644 Binary files a/src/__pycache__/mine.cpython-310.pyc and b/src/__pycache__/mine.cpython-310.pyc differ diff --git a/src/__pycache__/transaction.cpython-310.pyc b/src/__pycache__/transaction.cpython-310.pyc index 2494e41..e271267 100644 Binary files a/src/__pycache__/transaction.cpython-310.pyc and b/src/__pycache__/transaction.cpython-310.pyc differ diff --git a/src/__pycache__/validation.cpython-310.pyc b/src/__pycache__/validation.cpython-310.pyc index bb4bfed..ab51b4d 100644 Binary files a/src/__pycache__/validation.cpython-310.pyc and b/src/__pycache__/validation.cpython-310.pyc differ diff --git a/src/mine.py b/src/mine.py index 17b8d7a..84073e1 100644 --- a/src/mine.py +++ b/src/mine.py @@ -1,13 +1,68 @@ +import hashlib +import time + def mine_block(valid_transactions) -> str: """ - Mine all blocks from all the valid transactions + Mine a block by including the valid transactions and finding a valid block hash. - :param valid_transactions: all the validated transactions - :return: str + :param valid_transactions: List of valid transactions to include in the block. + :return: str representing the block data in the required format. """ - # TODO: implement block mining here + # Initialize the block header + version = 0x20000000 # Version number (0x20000000 for Bitcoin) + prev_block_hash = bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000") # Placeholder for the previous block hash + merkle_root = calculate_merkle_root(valid_transactions) + time_stamp = int(time.time()) # Current Unix timestamp + bits = 0x1d00ffff # Difficulty target (0x1d00ffff for the given target) + nonce = 0 # Initial nonce value + + # Construct the block header + block_header = ( + version.to_bytes(4, byteorder="little") + + prev_block_hash + + merkle_root.digest() + + time_stamp.to_bytes(4, byteorder="little") + + bits.to_bytes(4, byteorder="little") + + nonce.to_bytes(4, byteorder="little") + ) + + # Mine the block by finding a valid hash + target = 0x00000000ffff0000000000000000000000000000000000000000000000000000 + while True: + block_hash = hashlib.sha256(hashlib.sha256(block_header).digest()).digest() + if int.from_bytes(block_hash, byteorder="little") < target: + break + nonce += 1 + block_header = ( + block_header[:68] + + nonce.to_bytes(4, byteorder="little") + ) + + # Construct the block data + block_data = f"{block_header.hex()}\n" # Block header + block_data += f"{calculate_coinbase_transaction(valid_transactions).hex()}\n" # Coinbase transaction + block_data += "\n".join(tx.txid for tx in valid_transactions) # Transaction IDs - block_data = "Block header\nCoinbase transaction\n" - for tx in valid_transactions: - block_data += f"{tx.txid}\n" return block_data + +def calculate_merkle_root(transactions): + """ + Calculate the Merkle root for the given transactions. + + :param transactions: List of transactions to include in the Merkle tree. + :return: Hashlib object representing the Merkle root. + """ + # TODO: Implement the Merkle tree calculation logic + # For now, we'll use a placeholder value + return hashlib.sha256(b"placeholder_merkle_root") + +def calculate_coinbase_transaction(transactions): + """ + Calculate the coinbase transaction for the given block. + + :param transactions: List of transactions to include in the block. + :return: Bytes representing the coinbase transaction. + """ + # TODO: Implement the coinbase transaction calculation logic + # For now, we'll use a placeholder value + return b"placeholder_coinbase_transaction" \ No newline at end of file diff --git a/src/transaction.py b/src/transaction.py index 8926044..846738e 100644 --- a/src/transaction.py +++ b/src/transaction.py @@ -1,4 +1,6 @@ -from marshmallow import Schema, fields, post_load +# transaction.py +from marshmallow import Schema, fields, post_load, ValidationError +from typing import Optional class PrevOutSchema(Schema): scriptpubkey = fields.Str(required=True) @@ -14,7 +16,7 @@ class VinSchema(Schema): scriptsig = fields.Str(required=True) scriptsig_asm = fields.Str(required=True) witness = fields.List(fields.Str(), required=False) - is_coinbase = fields.Bool(required=True) + is_coinbase = fields.Bool(required=False, missing="") sequence = fields.Int(required=True) inner_redeemscript_asm = fields.Str(required=False) inner_witnessscript_asm = fields.Str(required=False) @@ -44,4 +46,4 @@ def __init__(self, version, locktime, vin, vout) -> None: self.version = version self.locktime = locktime self.vin = vin - self.vout = vout + self.vout = vout \ No newline at end of file diff --git a/src/validation.py b/src/validation.py index b341f51..5b0f808 100644 --- a/src/validation.py +++ b/src/validation.py @@ -1,75 +1,126 @@ import asyncio + +from bitcoin.core import MAX_BLOCK_SIZE from cryptography.hazmat.primitives.asymmetric import ec from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend +from typing import Tuple +from bitcoinlib.transactions import Transaction as BitcoinTransaction - -def validate_transaction(transaction): +async def validate_transaction(transaction) -> Tuple[bool, str]: + """ + Validate a single transaction. + :param transaction: The transaction to validate. + :return: A tuple containing a boolean indicating whether the transaction is valid, and a string message. + """ # Check if the transaction has inputs and outputs if not transaction.vin: return False, "Transaction has no inputs" if not transaction.vout: return False, "Transaction has no outputs" + # Initialize the total input and output values + total_input_value = 0 + total_output_value = 0 # Iterate over the inputs for tx_input in transaction.vin: - # Extract the public key from the input witness - if not tx_input.witness: - return False, f"Input {tx_input.txid}:{tx_input.vout} has no witness" - - public_key_bytes = bytes.fromhex(tx_input.witness[-1]) - - # Construct the public key object - public_key = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), public_key_bytes) + # Handle coinbase transactions separately + if tx_input.is_coinbase: + # Coinbase transactions have specific rules that need to be validated + # Check if the coinbase transaction follows the correct format + # and has a valid block height and coinbase value + if not validate_coinbase_transaction(tx_input): + return False, f"Invalid coinbase transaction input: {tx_input}" + else: + # Extract the public key from the input witness + if not tx_input.witness: + return False, f"Input {tx_input.txid}:{tx_input.vout} has no witness" + + public_key_bytes = bytes.fromhex(tx_input.witness[-1]) + + # Construct the public key object + public_key = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), public_key_bytes) + + # Extract the signature from the input witness + signature_bytes = b"".join(bytes.fromhex(witness_item) for witness_item in tx_input.witness[:-1]) + + # Get the transaction data that was signed for this input + tx_input_data = get_tx_input_data(transaction, tx_input) + + # Define the signature algorithm + signature_algorithm = ec.EllipticCurveSignatureAlgorithm(hashes.SHA256()) + + try: + # Verify the signature using the public key, signature, and transaction data + public_key.verify( + signature_bytes, + tx_input_data, + signature_algorithm + ) + except InvalidSignature: + return False, f"Invalid signature for input {tx_input.txid}:{tx_input.vout}" + + # Add the input value to the total input value + total_input_value += tx_input.prevout.value + + # Validate the input script + try: + bitcoin_tx = BitcoinTransaction.from_dict(transaction) + bitcoin_tx.verify_input_signature(tx_input.vout) + except Exception as e: + return False, f"Invalid input script for {tx_input.txid}:{tx_input.vout}: {str(e)}" + + # Iterate over the outputs + for tx_output in transaction.vout: + # Add the output value to the total output value + total_output_value += tx_output.value + + # Validate the output script + try: + bitcoin_tx = BitcoinTransaction.from_dict(transaction) + bitcoin_tx.verify_output_script(tx_output.scriptpubkey_asm) + except Exception as e: + return False, f"Invalid output script: {str(e)}" - # Extract the signature from the input witness - signature_bytes = b"".join(bytes.fromhex(witness_item) for witness_item in tx_input.witness[:-1]) + # Check if the total input value is greater than or equal to the total output value + if total_input_value < total_output_value: + return False, "Total input value is less than total output value" - # Get the transaction data that was signed for this input - tx_input_data = get_tx_input_data(transaction, tx_input) + # Calculate the transaction fee + transaction_fee = total_input_value - total_output_value - # Define the signature algorithm - signature_algorithm = ec.EllipticCurveSignatureAlgorithm(hashes.SHA256()) + # Validate transaction fee according to the fee rules + if transaction_fee < 0: + return False, "Transaction fee cannot be negative" - try: - # Verify the signature using the public key, signature, and transaction data - public_key.verify( - signature_bytes, - tx_input_data, - signature_algorithm - ) - except InvalidSignature: - return False, f"Invalid signature for input {tx_input.txid}:{tx_input.vout}" + # Check if the transaction size exceeds the maximum block size + transaction_size = sum(len(tx_input.scriptsig) for tx_input in transaction.vin) + \ + sum(len(tx_output.scriptpubkey) for tx_output in transaction.vout) + if transaction_size > MAX_BLOCK_SIZE: + return False, "Transaction size exceeds the maximum block size" return True, "Transaction is valid" - -def get_tx_input_data(transaction, tx_input): +def validate_coinbase_transaction(tx_input) -> bool: """ - Helper function to construct the transaction data that was signed for a given input. - This implementation assumes the transaction version is 1 or higher. + Validate a coinbase transaction input. + :param tx_input: The coinbase transaction input to validate. + :return: True if the coinbase transaction input is valid, False otherwise. """ - tx_data = b"" - tx_data += transaction.version.to_bytes(4, byteorder="little") - tx_data += tx_input.prevout.scriptpubkey.encode() - tx_data += tx_input.prevout.value.to_bytes(8, byteorder="little") - tx_data += tx_input.sequence.to_bytes(4, byteorder="little") - tx_data += transaction.locktime.to_bytes(4, byteorder="little") - - return tx_data - + # Implement your coinbase transaction validation logic here + # For example, you could check if the block height and coinbase value are valid + # based on the current network rules and block subsidies. + # This is just a placeholder function, you need to implement the actual validation logic. + return True async def validate_transactions(transactions) -> list: """ - gather and return all valid transactions - :param transactions: all transactions, both valid and invalid - :return: list + Validate a list of transactions asynchronously. + :param transactions: A list of transactions to validate. + :return: A list of valid transactions. """ - async_tasks = [] - for tx in transactions: - async_task = validate_transaction(tx) - async_tasks.append(async_task) + async_tasks = [validate_transaction(tx) for tx in transactions] try: validation_results = await asyncio.gather(*async_tasks) @@ -77,10 +128,20 @@ async def validate_transactions(transactions) -> list: print(f"An error occurred during transaction validation, Error: {e}") return [] - valid_transactions = [] - # Check and store valid transactions - for tx, is_valid in zip(transactions, validation_results): - if is_valid: - valid_transactions.append(tx) - + valid_transactions = [tx for tx, (is_valid, _) in zip(transactions, validation_results) if is_valid] + return valid_transactions + +def get_tx_input_data(transaction, tx_input): + """ + Helper function to construct the transaction data that was signed for a given input. + This implementation assumes the transaction version is 1 or higher. + """ + tx_data = b"" + tx_data += transaction.version.to_bytes(4, byteorder="little") + tx_data += tx_input.prevout.scriptpubkey.encode() + tx_data += tx_input.prevout.value.to_bytes(8, byteorder="little") + tx_data += tx_input.sequence.to_bytes(4, byteorder="little") + tx_data += transaction.locktime.to_bytes(4, byteorder="little") + + return tx_data \ No newline at end of file