diff --git a/src/script.py b/src/script.py index 4b6de34..9422b4d 100644 --- a/src/script.py +++ b/src/script.py @@ -328,59 +328,34 @@ def op_checksig(self) -> int: """ if self.stack.size() < 2: raise InvalidScriptException("Stack too small for CHECKSIG") - pubkey = self.stack.pop() signature = self.stack.pop() - try: # Extract DER signature and hash type - if len(signature) < 1: + if len(signature) < 1: raise InvalidScriptException("Empty signature") - - #der_sig = signature[:-1] # Remove hash type byte - #hash_type = signature[-1] - - der_sig = signature[:-1] - r, s, hash_type = parse_der_signature_bytes(der_sig) - - der_len = len(der_sig) - signature_len = len(r + s) + 6 - - if der_len != signature_len: - self.stack.push(b'\x00') - return 1 - - sig = r + s - - #print(pubkey) + der_sig, hash_type = signature[:-1], signature[-1] # Create verifying key from public key bytes - try: - vk = ecdsa.VerifyingKey.from_string( - pubkey, - curve=ecdsa.SECP256k1, - hashfunc=hashlib.sha256 - ) - except Exception as e: - raise InvalidScriptException(f"Invalid public key: {str(e)}") - + vk = ecdsa.VerifyingKey.from_string( + pubkey, + curve=ecdsa.SECP256k1, + hashfunc=hashlib.sha256 + ) + # Create signature hash based on hash type sig_hash = self.create_signature_hash(hash_type) - + # Verify the signature - try: - verified = vk.verify(sig, sig_hash) - except Exception: - verified = False - + verified = vk.verify(der_sig, sig_hash, sigdecode=ecdsa.util.sigdecode_der) self.stack.push(b'\x01' if verified else b'\x00') - - #print(verified) - return 1 - + except ecdsa.BadSignatureError: + self.stack.push(b'\x00') + return 1 except Exception as e: self.stack.push(b'\x00') + print(f"Unexpected exception: {e}") return 1 def op_checkmultisig(self) -> int: diff --git a/src/transaction.py b/src/transaction.py index 713b04e..874612a 100644 --- a/src/transaction.py +++ b/src/transaction.py @@ -112,6 +112,7 @@ def valid_input(self, vin_idx, vin): if scriptpubkey_type == "p2pkh": return self.validate_p2pkh(vin_idx, vin) + #pass elif scriptpubkey_type == "p2sh": #return self.validate_p2sh(vin_idx, vin) pass @@ -121,9 +122,9 @@ def valid_input(self, vin_idx, vin): elif scriptpubkey_type == "v1_p2tr": pass elif scriptpubkey_type == "v0_p2wpkh": - pass - #self.has_witness = True - #return self.validate_p2wpkh(vin_idx, vin) + #pass + self.has_witness = True + return self.validate_p2wpkh(vin_idx, vin) # Unknown script type. @@ -207,47 +208,53 @@ def validate_p2wpkh(self, vin_idx, vin): Returns: bool: True if the P2WPKH input is valid, False otherwise """ - # Check for witness data + # Check for witness data (P2WPKH requires exactly 2 items: signature and pubkey) witness = vin.get("witness", []) - if len(witness) != 2: # P2WPKH requires exactly 2 witness items (signature and pubkey) + if len(witness) != 2: + return False + + # Extract the signature and public key from the witness data + try: + signature = bytes.fromhex(witness[0]) + pubkey = bytes.fromhex(witness[1]) + except ValueError: + # Handle invalid hex data return False - - # Get witness components - signature = bytes.fromhex(witness[0]) - pubkey = bytes.fromhex(witness[1]) - # Get previous output's scriptPubKey + # Check the length of the public key (33 bytes for compressed, 65 for uncompressed) + if len(pubkey) not in {33, 65}: + return False + + # Extract the previous output's scriptPubKey prevout = vin.get("prevout", {}) if not prevout: return False - + scriptpubkey = bytes.fromhex(prevout.get("scriptpubkey", "")) - # Verify the script is proper P2WPKH format (OP_0 <20-byte-key-hash>) + # Verify that the scriptPubKey matches the P2WPKH format (OP_0 <20-byte-key-hash>) if len(scriptpubkey) != 22 or scriptpubkey[0] != 0x00 or scriptpubkey[1] != 0x14: return False - # Create P2PKH-style script from witness data - # P2WPKH witness program is executed as if it were P2PKH scriptPubKey + # Create the equivalent P2PKH scriptPubKey from the witness data p2pkh_script = ( - bytes([len(signature)]) + # Push signature + bytes([len(signature)]) + # Push the signature signature + - bytes([len(pubkey)]) + # Push pubkey + bytes([len(pubkey)]) + # Push the public key pubkey + - # Standard P2PKH script operations bytes([ 0x76, # OP_DUP 0xa9, # OP_HASH160 - 0x14 # Push 20 bytes + 0x14 # Push 20 bytes (20-byte hash follows) ]) + - scriptpubkey[2:22] + # 20-byte pubkey hash from witness program + scriptpubkey[2:22] + # Extract 20-byte public key hash from scriptPubKey bytes([ 0x88, # OP_EQUALVERIFY 0xac # OP_CHECKSIG ]) ) - # Create script object with witness-specific transaction data + # Create a script object with the generated P2PKH script and transaction context script = Script( p2pkh_script, json_transaction=self.json_transaction, @@ -255,9 +262,8 @@ def validate_p2wpkh(self, vin_idx, vin): segwit=True ) - # Execute the script + # Execute the script and catch any errors during the process try: - #return script.execute() return script.execute() except Exception as e: print(f"P2WPKH validation error: {str(e)}") diff --git a/src/verify.py b/src/verify.py index 7844b78..916149b 100644 --- a/src/verify.py +++ b/src/verify.py @@ -46,12 +46,30 @@ def valid_transaction_syntax(json_transaction): def parse_der_signature_bytes(der_signature): - # Parse the DER signature + # Parse the DER signature + if der_signature[0] != 0x30: + raise ValueError("Invalid DER signature format") + + length = der_signature[1] + if length + 2 != len(der_signature): + raise ValueError("Invalid DER signature length") + + if der_signature[2] != 0x02: + raise ValueError("Invalid DER signature format") + r_length = der_signature[3] r = der_signature[4:4 + r_length] - s_length_index = 4 + r_length + 1 - s_length = der_signature[s_length_index] - s = der_signature[s_length_index + 1:s_length_index + 1 + s_length] - hash_type = der_signature[-1] - + + if der_signature[4 + r_length] != 0x02: + raise ValueError("Invalid DER signature format") + + s_length = der_signature[5 + r_length] + s = der_signature[6 + r_length:6 + r_length + s_length] + + # Determine the hash type + if len(der_signature) > 6 + r_length + s_length: + hash_type = der_signature[-1] + else: + hash_type = 0x01 # Default to SIGHASH_ALL + return r, s, hash_type