Skip to content

Commit

Permalink
Added solution of P2WPKH and P2PKH Transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
i-am-yuvi committed May 1, 2024
1 parent ee245ca commit b93178c
Show file tree
Hide file tree
Showing 6 changed files with 485 additions and 3 deletions.
Binary file added .DS_Store
Binary file not shown.
88 changes: 88 additions & 0 deletions SOLUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,94 @@ Step by step implementation for creating TXID and then updating the json file in
- Write the `updated_data` dictionary to the file using `json.dump()`.
- Specify an indentation of 2 spaces using `indent=2` for better readability.

## Verifying the transactions

During the verification process I found out that the total number of P2WPKH were lots(around 3000-4000) of them.

And also the P2TR were lots. So verifying the P2TR is out of the scope of this project. The P2TR is considered valid by default.

## Verifying P2PKH Transactions

1. Parse the transaction JSON:

2. Load the transaction JSON data into a Python dictionary called tx.


3. Iterate over each input (vin) in the transaction:

4. For each input, retrieve the scriptPubKey and scriptSig from the transaction data.


5. Extract the public key hash from the scriptPubKey:

6. The public key hash is located in the scriptPubKey field, typically starting from the 7th character and ending 4 characters before the end.


7. Extract the signature and public key from the scriptSig:

8. The scriptSig contains the signature and public key.
The signature length is obtained by converting the 3rd and 4th characters of the scriptSig from hexadecimal to integer.

9. The signature starts after the length and continues for the specified number of bytes.
The public key starts immediately after the signature.


### Verify the public key hash:

1. Hash the extracted public key using SHA-256.

2. Apply the RIPEMD-160 hash function to the SHA-256 hash to obtain the public key hash.

3. Compare the newly computed public key hash with the one extracted from the scriptPubKey.

4. If the hashes don't match, print an error message indicating a public key hash mismatch and return False.


### Verify the signature:

1. Decode the extracted public key hash from hexadecimal to bytes.

2. Create a VerifyingKey object using the extracted public key and the SECP256k1 elliptic curve.

3. Verify the signature using the verify_digest method of the VerifyingKey object.

4. The verify_digest method takes the signature, the decoded public key hash, and the hash function (SHA-256) as arguments.

5. If the signature verification fails, print an error message indicating the failure and return False.


6. If all inputs pass the verification steps without any errors:

7. Return True to signify a valid transaction.



## Verifying P2WPKH Transactions

Verifying a P2WPKH transaction is a bit different than the traditional one. We need to create a message of the transaction and later verify the message with public key and Elliptic Curve.

1. Creating the preimage of the transaction:
- preimage = version + hash256(inputs(txid+vout)) + hash256(sequence) + inputs + scriptcode + amount + sequence + hash256(outputs) + locktime

- Script Code is basically is a modified version of the ScriptPubKey from the output we're spending. To create the scriptcode, we need to find the ScriptPubKey on the output we want to spend, extract the public key hash, and place it in to the following P2PKH ScriptPubKey structure:

**scriptcode = 1976a914{publickeyhash}88ac**

- Now we need add the signature hash type to the end if preimage which is SIGHASH_ALL(0x01)
- Finally we create message which is

**message = hash256(preimage)**


2. Now verify the message and signature created for the transaction using ECDSA.

3. If it is successful then return true.


**NOTE: hash256(x) = sha256(sha256(x))**






Expand Down
55 changes: 52 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,53 @@
from .python_files import structural_check
import os
import json

# Run the structural check on the transactions in the mempool
structural_check.check_structure_transactions("./mempool")

def count_transaction_types(folder_path):
p2pkh_count = 0
p2wpkh_count = 0
p2tr_count = 0

# Iterate over all files in the folder
for filename in os.listdir(folder_path):
if filename.endswith(".json"):
file_path = os.path.join(folder_path, filename)

# Read the JSON file
with open(file_path, "r") as file:
data = json.load(file)

is_p2pkh = False
is_p2wpkh = False
is_p2tr = False

# Check the transaction type for each input (vin)
for input_data in data["vin"]:
if "prevout" in input_data:
if input_data["prevout"]["scriptpubkey_type"] == "p2pkh":
is_p2pkh = True
elif input_data["prevout"]["scriptpubkey_type"] == "v0_p2wpkh":
is_p2wpkh = True
elif input_data["prevout"]["scriptpubkey_type"] == "v1_p2tr":
is_p2tr = True

# Increment the count based on the transaction type
if is_p2pkh:
p2pkh_count += 1
elif is_p2wpkh:
p2wpkh_count += 1
elif is_p2tr:
p2tr_count += 1

return p2pkh_count, p2wpkh_count, p2tr_count


# Specify the folder path containing the JSON transaction files
folder_path = "mempool_valid"

# Count the number of p2pkh, p2wpkh & p2tr_count transactions
p2pkh_count, p2wpkh_count, p2tr_count = count_transaction_types(folder_path)

# Print the results
print(f"Number of P2PKH transactions: {p2pkh_count}")
print(f"Number of P2WPKH transactions: {p2wpkh_count}")
print(f"Number of P2TR transactions: {p2tr_count}")
60 changes: 60 additions & 0 deletions python_files/p2pkh_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import json
import hashlib
from ecdsa import SigningKey, VerifyingKey, SECP256k1


def validate_transaction(tx_json):
# Parse the transaction JSON
tx = json.loads(tx_json)

# Iterate over the inputs
for vin in tx["vin"]:
# Get the scriptPubKey and scriptSig
scriptpubkey = vin["prevout"]["scriptpubkey"]
scriptsig = vin["scriptsig"]

# Extract the public key hash from the scriptPubKey
pubkey_hash = scriptpubkey[6:-4]

# Extract the signature and public key from the scriptSig
sig_len = int(scriptsig[2:4], 16)
sig_start = 4
sig_end = sig_start + sig_len * 2
signature = scriptsig[sig_start:sig_end]
pubkey_start = sig_end + 2
pubkey = scriptsig[pubkey_start:]

# Verify the public key hash matches the hash of the public key
hash_object = hashlib.sha256(bytes.fromhex(pubkey))
pubkey_hash_new = hashlib.new("ripemd160", hash_object.digest()).hexdigest()
if pubkey_hash_new != pubkey_hash:
print(f"Public key hash mismatch for input {vin['txid']}:{vin['vout']}")
return False

# Verify the signature
try:
# Decode the public key hash
decoded_hash = bytes.fromhex(pubkey_hash)

# Decode the public key
public_key = VerifyingKey.from_string(
bytes.fromhex(pubkey), curve=SECP256k1
)

# Verify the signature
public_key.verify_digest(
bytes.fromhex(signature),
decoded_hash,
sigdecode=VerifyingKey.from_string(
bytes.fromhex(pubkey), curve=SECP256k1, hashfunc=hashlib.sha256
).verify_digest,
)
except Exception as e:
print(
f"Signature verification failed for input {vin['txid']}:{vin['vout']}"
)
print(f"Error: {str(e)}")
return False

print("All inputs are valid!")
return True
Loading

0 comments on commit b93178c

Please sign in to comment.