diff --git a/src/__pycache__/validation.cpython-310.pyc b/src/__pycache__/validation.cpython-310.pyc index 621186a..9473ad6 100644 Binary files a/src/__pycache__/validation.cpython-310.pyc and b/src/__pycache__/validation.cpython-310.pyc differ diff --git a/src/validation.py b/src/validation.py index ae39e24..73e2704 100644 --- a/src/validation.py +++ b/src/validation.py @@ -1,4 +1,6 @@ import asyncio +import re +import time class ValidateTransaction: @@ -43,18 +45,72 @@ async def verify_input_script(self, prev_tx, vout, script_pubkey) -> bool: async def is_valid_output_script(self, scriptpubkey): """ Validate the script public key of the output - :param scriptpubkey: - :return: + :param scriptpubkey: The script public key to validate + :return: True if the script public key is valid, False otherwise """ + # Define a regular expression pattern to match P2PKH script public keys + p2pkh_pattern = r'^OP_DUP OP_HASH160 [0-9a-fA-F]{40} OP_EQUALVERIFY OP_CHECKSIG$' + + # Check if the script public key matches the P2PKH pattern + if re.match(p2pkh_pattern, scriptpubkey): + return True + else: + return False async def validate_transaction_amount(self, tx) -> bool: + async def validate_transaction_amount(self, tx) -> bool: + """ + Validates the transaction amount + :param tx: The transaction to validate + :return: True if the transaction amount is valid, False otherwise + """ + total_input_value = 0 + total_output_value = 0 + + # Calculate the total input value + for tx_input in tx.get("vin", []): + txid = tx_input.get("txid") + vout = tx_input.get("vout") + + # Retrieve the previous transaction output + prev_tx = await self.retrieve_transaction(txid) + if prev_tx is None: + # Previous transaction not found, skip this input + continue + + # Get the value of the previous output + prev_output = prev_tx.get("vout", [])[vout] + if prev_output: + total_input_value += prev_output.get("value", 0) + + # Calculate the total output value + for output in tx.get("vout", []): + total_output_value += output.get("value", 0) + + # Check if the total input value is greater than or equal to the total output value + return total_input_value >= total_output_value + + async def validate_script(self, prev_tx, vout, script_pubkey): """ - Validates the transaction amount - :param tx: - :return: bool + Validate the input script (scriptSig or witness) against the previous output's script public key + :param prev_tx: The previous transaction + :param vout: The index of the previous output + :param script_pubkey: The script public key of the previous output + :return: True if the input script is valid, False otherwise """ - # Implement logic to validate the transaction amount - pass + prev_input = prev_tx.get("vin", [])[0] # Assuming there's only one input + if not prev_input: + return False + + scriptsig = prev_input.get("scriptsig") + witness = prev_input.get("witness", []) + + print(f"scriptsig : {scriptsig}") + print(f"witness : {witness}") + # Validate the input script against the previous output's script public key + # ... + + return False async def get_prev_tx_output(self, txid, vout) -> (list, None): """ @@ -75,18 +131,16 @@ async def get_prev_tx_output(self, txid, vout) -> (list, None): if vout >= len(prev_output): return None, None - return prev_tx, prev_output - # Step 3: Verify that the output is unspent - # if not await self.is_unspent(prev_tx, vout): - # return None, None + if await self.is_spent(prev_tx, vout): + return None, None - # # Step 4: Retrieve the script pub key from the referred output - # script_pubkey = prev_tx.get("vout")[0].get("scriptpubkey") - # - # # Step 5: Validate the scriptSig or Witness - # if not await self.validate_script(prev_tx, vout, script_pubkey): - # return None, None + # Step 4: Retrieve the script pub key from the referred output + script_pubkey = prev_tx.get("vout")[0].get("scriptpubkey") + + # Step 5: Validate the scriptSig or Witness + if not await self.validate_script(prev_tx, vout, script_pubkey): + return None, None # Step 6: Ensure the sum of input values is greater than or equal to the sum of output values if not await self.validate_transaction_amount(prev_tx): @@ -96,18 +150,81 @@ async def get_prev_tx_output(self, txid, vout) -> (list, None): async def validate_locktime_and_sequence(self, tx): """ - Validates Transactions locktime - :param tx: - :return: + Validates the locktime and sequence fields of a transaction. + :param tx: The transaction to validate + :return: True if the locktime and sequence are valid, False otherwise + """ + locktime = tx.get("locktime") + sequence = tx.get("vin", [])[0].get("sequence", 0xFFFFFFFF) + + # Check if the transaction is a relative time-locked transaction + if sequence < 0xFFFFFFFF: + # Add logic to validate relative time-locked transactions + pass + + # Check if the transaction has a non-zero locktime + if locktime != 0: + # Check if the locktime is a block height + if locktime < 500000000: + # Add logic to validate locktime as a block height + pass + else: + # Validate locktime as a Unix timestamp + current_time = int(time.time()) + if current_time < locktime: + return False + + return True + + async def is_spent(self, tx, vout): + """ + Check if a specific output of a transaction has already been spent + :param tx: The transaction to check + :param vout: The output index to check + :return: True if the output is spent, False otherwise """ - pass + # Get the transaction ID + txid = tx.get("txid") + + # Iterate over all transactions + for other_tx in self.transaction_by_id.values(): + # Skip the transaction we're checking + if other_tx.get("txid") == txid: + continue + + # Check if any input of the other transaction spends the output we're checking + for other_input in other_tx.get("vin", []): + if other_input.get("txid") == txid and other_input.get("vout") == vout: + # Output is spent by this input + return True + + # Output is not spent + return False async def is_double_spend(self, tx): """ Check if the transaction is double spent - :param tx: - :return: + :param tx: The transaction to check + :return: True if the transaction is double spent, False otherwise """ + for tx_input in tx.get("vin", []): + txid = tx_input.get("txid") + vout = tx_input.get("vout") + + # Retrieve the previous transaction + prev_tx = await self.retrieve_transaction(txid) + if prev_tx is None: + # Previous transaction not found, assume not double spent + continue + + # Check if the referred output is already spent + if await self.is_spent(prev_tx, vout): + # Output is already spent, this is a double spend + return True + + # No double spend detected + return False + async def validate_transaction(self, tx) -> (bool, str): """ :param tx: @@ -146,7 +263,7 @@ async def validate_transaction(self, tx) -> (bool, str): if self.is_double_spend(tx): return False - + print("Everything is valid") return True # print("Transaction has valid version") tested