BLOCKCHAIN TECHNOLOGIES LAB
WEEK-3
NAME: [Link]
ROLL NO: 23R25A6209
TOPIC: Simulating Bitcoin Transactions and Double-Spending
PROBLEM SATEMENT-1:
Design a Python script to create a transaction with sender, receiver, amount, and a digital signature.
How would you verify the transaction’s authenticity?
SOURCE CODE:
import json
import hashlib
from [Link] import hashes, serialization
from [Link] import padding, rsa
from [Link] import default_backend
# Transaction class
class Transaction:
def __init__(self, sender_public_key, receiver, amount, signature):
[Link] = sender_public_key
[Link] = receiver
[Link] = amount
[Link] = signature
def to_dict(self):
return {
"sender": [Link],
"receiver": [Link],
"amount": [Link],
"signature": [Link]
}
def to_json(self):
return [Link](self.to_dict(), sort_keys=True)
# Block class
class Block:
def __init__(self, transactions, previous_hash):
[Link] = transactions
self.previous_hash = previous_hash
[Link] = self.compute_hash()
def compute_hash(self):
tx_data = [tx.to_json() for tx in [Link]]
block_string = [Link](tx_data, sort_keys=True) + self.previous_hash
return hashlib.sha256(block_string.encode()).hexdigest()
# Blockchain class
class Blockchain:
def __init__(self):
[Link] = []
genesis_block = Block([], "0" * 64)
[Link](genesis_block)
def add_block(self, transactions):
previous_hash = [Link][-1].hash
block = Block(transactions, previous_hash)
[Link](block)
def is_chain_valid(self):
for i in range(1, len([Link])):
current = [Link][i]
prev = [Link][i - 1]
if current.previous_hash != [Link]:
return False
if [Link] != current.compute_hash():
return False
return True
# Digital signature functions
def generate_keys():
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_key = private_key.public_key()
return private_key, public_key
def sign_transaction(private_key, message):
signature = private_key.sign(
[Link](),
padding.PKCS1v15(),
hashes.SHA256()
)
return [Link]()
def verify_signature(public_key_pem, message, signature_hex):
public_key = serialization.load_pem_public_key(
public_key_pem.encode(),
backend=default_backend()
)
try:
public_key.verify(
[Link](signature_hex),
[Link](),
padding.PKCS1v15(),
hashes.SHA256()
)
return True
except Exception:
return False
# Pretty print blockchain
def print_blockchain(blockchain):
for i, block in enumerate([Link]):
print(f"\nBlock {i}:" + (" (Genesis Block)" if i == 0 else ""))
print(f"Hash: {[Link]}")
print(f"Previous Hash: {block.previous_hash}")
tx_list = [tx.to_dict() for tx in [Link]]
print("Transactions:")
print([Link](tx_list, indent=2))
# Main test
def main():
# Generate keys for sender
sender_private_key, sender_public_key = generate_keys()
# Export public key to PEM format
sender_pem = sender_public_key.public_bytes(
encoding=[Link],
format=[Link]
).decode()
# Create blockchain
blockchain = Blockchain()
# Create 3 transactions
tx1_msg = f"{sender_pem}Bob100"
tx1_sig = sign_transaction(sender_private_key, tx1_msg)
tx1 = Transaction(sender_pem, "Bob", 100, tx1_sig)
tx2_msg = f"{sender_pem}Charlie50"
tx2_sig = sign_transaction(sender_private_key, tx2_msg)
tx2 = Transaction(sender_pem, "Charlie", 50, tx2_sig)
tx3_msg = f"{sender_pem}Dave200"
tx3_sig = sign_transaction(sender_private_key, tx3_msg)
tx3 = Transaction(sender_pem, "Dave", 200, tx3_sig)
# Verify one transaction
print("Is transaction valid?", verify_signature(sender_pem, tx1_msg, tx1_sig))
# Add transactions to blockchain
blockchain.add_block([tx1])
blockchain.add_block([tx2])
blockchain.add_block([tx3])
print("\n[+] Blockchain created with 3 transaction blocks.")
print_blockchain(blockchain)
print("\n[!] Blockchain valid?", blockchain.is_chain_valid())
# Tampering test
print("\n[!] Tampering Block 2 (Changing amount to 9999)...\n")
[Link][2].transactions[0].amount = 9999 # Tamper with Block 2
[Link][2].hash = [Link][2].compute_hash() # Recompute hash
print_blockchain(blockchain)
print("\n[!] Blockchain valid after tampering?", blockchain.is_chain_valid())
if __name__ == "__main__":
main()
OUTPUT:
Case1
Case2
Case3:
PROBLEM STATEMENT-2:
Implement a simple blockchain with a proof-of-work mechanism. What happens to the blockchain’s
integrity if two miners simultaneously attempt to add conflicting transactions? Ascertain the
transaction that remains valid.
SOURCE CODE:
import hashlib
import time
import copy
class Block:
def __init__(self, index, timestamp, data, previous_hash, difficulty=4):
[Link] = index
[Link] = timestamp
[Link] = data
self.previous_hash = previous_hash
[Link] = 0
[Link] = difficulty
[Link] = self.mine_block()
def calculate_hash(self):
value = f"{[Link]}{[Link]}{[Link]}{self.previous_hash}{[Link]}"
return hashlib.sha256([Link]()).hexdigest()
def mine_block(self):
prefix = "0" * [Link]
while True:
hash_value = self.calculate_hash()
if hash_value.startswith(prefix):
return hash_value
[Link] += 1
class Blockchain:
def __init__(self):
[Link] = [self.create_genesis_block()]
[Link] = 4
def create_genesis_block(self):
return Block(0, [Link](), "Genesis Block", "0")
def get_latest_block(self):
return [Link][-1]
def add_block(self, data):
last_block = self.get_latest_block()
new_block = Block(len([Link]), [Link](), data, last_block.hash, [Link])
[Link](new_block)
print(f"Block {new_block.index} mined: Nonce={new_block.nonce},
Hash={new_block.hash}")
def is_chain_valid(self, chain=None):
if chain is None:
chain = [Link]
for i in range(1, len(chain)):
current = chain[i]
previous = chain[i - 1]
if [Link] != current.calculate_hash():
return False
if current.previous_hash != [Link]:
return False
return True
def print_chain(self):
for block in [Link]:
print(f"\nBlock {[Link]}")
print(f"Timestamp: {[Link]}")
print(f"Previous Hash: {block.previous_hash}")
print(f"Hash: {[Link]}")
print(f"Nonce: {[Link]}")
print(f"Data: {[Link]}")
def simulate_fork_and_consensus(original_chain):
# Simulate Miner 1 extending the chain by 2 blocks
miner1_chain = [Link](original_chain.chain)
miner1 = Blockchain()
[Link] = miner1_chain
print("\n--- Miner 1 mining blocks ---")
for i in range(4, 6):
block_data = f"Tx {i}: Miner 1"
last_block = miner1.get_latest_block()
new_block = Block(len([Link]), [Link](), block_data, last_block.hash)
[Link](new_block)
print(f"Miner 1 mined Block {new_block.index}: Nonce={new_block.nonce},
Hash={new_block.hash}")
# Simulate Miner 2 extending the chain by 1 block
miner2_chain = [Link](original_chain.chain)
miner2 = Blockchain()
[Link] = miner2_chain
print("\n--- Miner 2 mining blocks ---")
block_data = "Tx 4: Miner 2"
last_block = miner2.get_latest_block()
new_block = Block(len([Link]), [Link](), block_data, last_block.hash)
[Link](new_block)
print(f"Miner 2 mined Block {new_block.index}: Nonce={new_block.nonce},
Hash={new_block.hash}")
# Consensus: choose the longest valid chain
if len([Link]) > len([Link]) and
Blockchain().is_chain_valid([Link]):
print("\nMiner 1 has the longer chain. This is the longest valid chain now.")
return [Link]
else:
print("\nMiner 2 has the longer chain. This is the longest valid chain now.")
return [Link]
def tamper_chain(blockchain):
print("\n--- Tampering Block 2 ---")
[Link][2].data = "Tampered Tx: Bob -> Eve"
[Link][2].hash = [Link][2].calculate_hash()
def main():
blockchain = Blockchain()
print("Mining block 1...")
blockchain.add_block("Tx 1: Alice -> Bob")
print("Mining block 2...")
blockchain.add_block("Tx 2: Bob -> Charlie")
print("Mining block 3...")
blockchain.add_block("Tx 3: Charlie -> Dave")
print("\nOriginal Blockchain:")
blockchain.print_chain()
print("\nIs blockchain valid?", blockchain.is_chain_valid())
print("\n--- Simulating Fork ---")
new_chain = simulate_fork_and_consensus(blockchain)
[Link] = new_chain
print("\nFinal Blockchain After Consensus:")
blockchain.print_chain()
print("\nIs final blockchain valid?", blockchain.is_chain_valid())
# Tampering case at end
tamper_chain(blockchain)
print("\nBlockchain After Tampering:")
blockchain.print_chain()
print("\nIs blockchain valid after tampering?", blockchain.is_chain_valid())
if __name__ == "__main__":
main()
OUTPUT:
Case1
Case2
Case3
\
PROBLEM STATEMENT-3:
Simulate a double-spend attack by attempting to spend the same Bitcoin twice in two different
transactions. Propose a solution to detect and prevent this attack.
SOURCE CODE:
import hashlib
import time
class Transaction:
def __init__(self, tx_id, sender, recipient, amount, timestamp=None):
self.tx_id = tx_id
[Link] = sender
[Link] = recipient
[Link] = amount
[Link] = timestamp if timestamp else [Link]()
def __str__(self):
return f"Tx ID: {self.tx_id} | Sender: {[Link]} | Recipient: {[Link]} |
Amount: {[Link]} | Timestamp: {[Link]}"
class Block:
def __init__(self, index, transactions, previous_hash):
[Link] = index
[Link] = [Link]()
[Link] = transactions
self.previous_hash = previous_hash
[Link], [Link] = self.mine_block()
def compute_hash(self):
tx_data = ''.join([str(tx.__dict__) for tx in [Link]])
block_string = str([Link]) + str([Link]) + tx_data + self.previous_hash +
str([Link])
return hashlib.sha256(block_string.encode()).hexdigest()
def mine_block(self):
[Link] = 0
computed_hash = self.compute_hash()
while not computed_hash.startswith('0000'):
[Link] += 1
computed_hash = self.compute_hash()
return [Link], computed_hash
def __str__(self):
block_info = f"Block {[Link]}\n"
block_info += f"Timestamp: {[Link]}\n"
block_info += f"Previous Hash: {self.previous_hash}\n"
block_info += f"Hash: {[Link]}\n"
block_info += f"Nonce: {[Link]}\n"
block_info += "Transactions:\n"
for tx in [Link]:
block_info += f" {tx}\n"
return block_info
class Blockchain:
def __init__(self):
[Link] = []
self.used_tx_signatures = set()
self.create_genesis_block()
def create_genesis_block(self):
print("STEP 1: Create Blockchain and Genesis Block\nCreating Blockchain and
Genesis Block...\n")
genesis_tx = Transaction("genesis_tx", "SYSTEM", "Alice", 100)
self.add_block_to_chain([genesis_tx])
def add_block_to_chain(self, transactions):
valid_txs = []
for tx in transactions:
tx_signature = (tx.tx_id, [Link])
if tx_signature in self.used_tx_signatures:
print(f"[ERROR] Double Spend Detected! Transaction '{tx.tx_id}' with
timestamp '{[Link]}' already used.")
return False
valid_txs.append(tx)
if not valid_txs:
print("No valid transactions to add. Block rejected.\n")
return False
previous_hash = [Link][-1].hash if [Link] else "0"
block_index = len([Link])
new_block = Block(block_index, valid_txs, previous_hash)
[Link](new_block)
for tx in valid_txs:
self.used_tx_signatures.add((tx.tx_id, [Link]))
print(new_block)
return True
def is_chain_valid(self):
for i in range(1, len([Link])):
current = [Link][i]
previous = [Link][i-1]
if current.previous_hash != [Link]:
return False
if [Link] != current.compute_hash():
return False
return True
def simulate():
blockchain = Blockchain()
print("\nSTEP 2: Add Block 1\n")
tx1 = Transaction("tx1", "Alice", "Bob", 50)
blockchain.add_block_to_chain([tx1])
print("\nSTEP 3: Add Block 2\n")
tx2 = Transaction("tx2", "Bob", "Charlie", 25)
blockchain.add_block_to_chain([tx2])
print("\nSTEP 4: Simulate Double Spend Attempt\n")
print("Block 3")
print("Attempting transaction:")
print(f" Tx ID: {tx1.tx_id} | Sender: Alice | Recipient: Eve | Amount: 50 | Timestamp:
{[Link]}\n")
# Reusing same tx_id and timestamp intentionally to simulate double spending
tx3 = Transaction("tx1", "Alice", "Eve", 50, timestamp=[Link])
blockchain.add_block_to_chain([tx3])
print("\nSTEP 5: Validate Blockchain")
print("Blockchain valid:", blockchain.is_chain_valid())
simulate()
OUTPUT:
PROBLEM STATEMENT-4:
Modify the proof-of-work difficulty in your simulation. How does increasing or decreasing the
difficulty affect the time taken to mine a block?
SOURCE CODE:
import hashlib
import time
def is_even_hex_char(char):
return [Link]() in ['0', '2', '4', '6', '8', 'a', 'c', 'e']
def proof_of_work(difficulty):
prefix = '0' * difficulty
nonce = 0
start_time = [Link]()
while True:
text = f"block-data-{nonce}"
hash_result = hashlib.sha256([Link]()).hexdigest()
if hash_result.startswith(prefix) and is_even_hex_char(hash_result[-1]):
end_time = [Link]()
return nonce, hash_result, end_time - start_time
nonce += 1
def simulate_modified_pow():
print("Proof of Work Simulation")
print("------------------------")
print("Rule: Hash must start with N zeros AND end with an even hex digit (0, 2, 4, 6, 8, a, c, e).\
n")
for difficulty in range(2, 6):
nonce, hash_result, elapsed = proof_of_work(difficulty)
print(f"Difficulty: {difficulty} (Target hash starts with {difficulty} zeros)")
print(f" Nonce found: {nonce}")
print(f" Hash: {hash_result}")
print(f" Time taken: {elapsed:.4f} seconds\n")
print("Conclusion: Increasing difficulty -> Exponentially more time to mine.")
simulate_modified_pow()
OUTPUT: