Blockchain Technology
Table of Contents
- 1. Distributed Systems
- 2. Introduction to Blockchain
- 3. Fundamentals
- 4. Execution Flow of a Blockchain
- 5. Merkle Tree
- 6. Bitcoin
- 7. Software Stack
- 8. Case Study Topics
1. Distributed Systems
- This is a computing paradigm where two or more nodes work with each other in a coordinated fashion, to achieve a common outcome.
- The main challenges are:
- Coordination between nodes
- Fault tolerance
1.1. Brewer’s Cap Theorem
- Consistency: Every node gets the same latest data.
- Availability: Every request gets a response.
- Partition Tolerance: The system keeps functioning even if the network gets split into isolated but functional groups of nodes.
Brewers’ CAP theorem states that
You can only have CP or AP systems. During a partition, you must sacrifice either consistency or availability.
- Blockchains commonly choose AP, but they get eventual consistency.
- In case of a partition, the two groups may not sync with each other at the moment
- However, when the partition heals, the nodes communicate with each other (gossip protocol).
2. Introduction to Blockchain
2.1. What it is
A blockchain is a cryptographically secure, append-only, peer-to-peer distributed database structured as a chain of blocks maintained through decentralized consensus.
- A blockchain is a secure decentralized digital ledger that records transactions as a sequence of linked blocks.
- It was first proposed by Stuart Haber and W. Scott Stornetta in 1991.
- In 2008, a paper written by an author ’Satoshi Nakamoto’ published the bitcoin whitepaper (blockchain for a digital currency).
- Byzantine node: a node exhibiting arbitrary behavior
2.2. Gossip Protocol
- This is where nodes constantly keep communicating with each other in a peer-to-peer network.
2.3. Transaction
- A transaction is a single record of some action, mostly some transfer of value.
- A block is a container that bundles multiple transactions into a single unit.
Each block contains:
- Block header
- Transaction Data
- Hash
2.4. Blockchain Vs Tradition Ledger
| Traditional (centralized) ledger | Blockchain distributed ledger |
|---|---|
| One central authority (e.g., a bank) holds the official copy | Every node holds an identical copy |
| If the central server goes down, the system stops (single point of failure) | Even if some nodes are cut off, the rest of the system keep functioning (Partition tolerance, Availability) |
| Consistency is guaranteed because there’s only one copy to begin with | Consistency must be actively re-earned through consensus after a partition, it’s temporarily sacrificed |
| Trust is placed in the institution | Trust is placed in the consensus mechanism, not any single party |
In short: a traditional ledger sidesteps the CAP theorem entirely by having only one copy, while a blockchain’s distributed ledger must consciously trade off Consistency for Availability + Partition tolerance, then recover consistency over time via consensus.
2.4.1. Advantages of a Blockchain
- More security
- More transparency
- No single-point-of-failure
2.4.2. Disadvantages of a Blockchain
- Slower
- High computational cost
- Transactions are irreversible
- High storage requirements
2.5. Types of Blockchain based on governance
| Public | Private | Consortium | Hybrid |
|---|---|---|---|
| Open to Anyone | Single Organization | Group of organizations who don’t need to trust each other | Mix of Public and Private |
| Fully Decentralized | Centrally Controlled | Many organizations each have central control | Depends |
| Very Transparent but Slow | Not Transparant but Fast | Medium at Both | Depends |
2.6. Smart Contracts
- When you carry out transactions in a blockchain, instead of trusting a company, you trust some deterministic and immutable code stored on the blockchain. This code is called a smart contract.
- A smart contract is a self-executing program that automates the actions required in a blockchain transaction.
- They can be thought of as a vending machine.
2.7. Node
- A node is a computer on the peer-to-peer / blockchain network.
There are 3 types of nodes on a blockchain.
Full Node Simple Payment Verification (SPV/Light) Node Mining Node Stores the complete blockchain data They only verify and store specific transactions when needed They participate in validating transactions and adding new blocks to the blockchain They store only block headers
2.8. Rough Flow of Blochchain
- A transaction \(T_{x}\) occurs.
- \(T_{x}\) is verified using a consensus mechanism (a polling mechanism).
- Multiple transactions are grouped into a block (a collection of valid transactions). They’re arranged as a merkel tree.
- The block is added to the chain (each block is linked to the previous block using cryptographic hashes).
- The blockchain is updated.
2.9. Mempool
A mempool (short for “memory pool”) is a temporary holding area on a blockchain node where unconfirmed or pending transactions wait before being selected by a miner or validator.
2.10. Uses of Blockchain
- Cryptocurrencies
- Supply Chain Tracking
- Healthcare
- Voting Systems
3. Fundamentals
3.1. Hash
- A hash is a unique digital fingerprint of data. It’s a mathematical blender.
- A hash function will always return fixed length output, regardless of the input size.
- It is deterministic (the same input always produces the same hash)
- Even a tiny change in input produces a completely different hash. This is called the avalanche effect.
- Hashes are puzzle-friendly because the avalanche effect makes sure there are no shortcuts.
- Two inputs can never give the same hash.
- Hashes provide:
- Authentication: Verifies identity of sender
- Integrity: Ensures data wasn’t tampered
- Non-Repudiation: Makes sure that the sender can’t deny sending it
3.1.1. SHA-256
- SHA-256 works on 512-bit blocks.
- The last 64 bits are reserved for storing the length of the original message.
- The message itself is stored in the rest of the 448 bits.
- Algorithm
- Read the input
- Convert it into binary
- Append a bit with value 1, to the end of this sequence.
- Pad this sequence with enough 0s to span across 448 bits.
- Pad the length of the original message with enough 0s to span across 64 bits.
Define the working variables:
H0 = 6a09e667 H1 = bb67ae85 H2 = 3c6ef372 H3 = a54ff53a H4 = 510e527f H5 = 9b05688c H6 = 1f83d9ab H7 = 5be0cd19
Initialize variables
a,b,c,d,e,f,g,hwithH0,H1,H2,H3,H4,H5,H6,H7respectively.- Define the round constants:
- These are 64 words each of size 32 bits.
These are derived from the decimal part (eg. 2 from the number 1.2) of the cube roots of the first 64 prime numbers.
K0 = 0x428a2f98 K1 = 0x71374491 K2 = 0xb5c0fbcf K3 = 0xe9b5dba50x3956c25b K4 = 0x59f111f1 K5 = 0x923f82a4 K6 = 0xab1c5ed50xd807aa98 K7 = 0x12835b01 ... K63 = 0xc67178f2
- Split the block of 512 bits into 16 unsigned words w0, w1, w2, .. w15 (each would be 32 bits).
- Expand these 16 words into 64 words (create w16, w17, … w63) using
rotations, shifts and XOR. Each word is created as:
\[W_i = \sigma_1(W_{i-2}) + W_{i-7} + \sigma_0(W_{i-15}) + W_{i-16} \]
for \(16 \le i \le 63\)
- The current word \(w_{i}\) depends on 4 different previous words (\(w_{i-2}\), \(w_{i-7}\), \(w_{i-15}\) and \(w_{i-16}\)).
- The helper functions defined are:
- \(\operatorname{ROTR}^{n}(x)\): Rotate right (right circular shift) all the bits of word \(x\), \(n\) times.
- \(\operatorname{SHR}^{n}(x)\): Logical right shift (bits of the right are discarded on right shifting) all the bits of word \(x\), \(n\) times.
- \(\sigma_{0}(x) = \operatorname{ROTR}^{7}(x) \oplus \operatorname{ROTR}^{18}(x) \oplus \operatorname{SHR}^{3}(x)\)
- \(\sigma_{1}(x) = \operatorname{ROTR}^{17}(x) \oplus \operatorname{ROTR}^{19}(x) \oplus \operatorname{SHR}^{10}(x)\)
- For 64 times:
- Calculate:
\[T_1 = h + \Sigma_1(e) + \operatorname{Ch}(e,f,g) + K[i] + W[i] \]
\[T_2 = \Sigma_0(a) + \operatorname{Maj}(a,b,c) \]
The helper functions/variables are defined as:
- \(h\): This variable is one of the 8 variables defined earlier
- \(\Sigma_{0}(x) = \operatorname{ROTR}^{2} \oplus \operatorname{ROTR}^{13} \oplus \operatorname{ROTR}^{22}\)
- \(\Sigma_{1}(x) = \operatorname{ROTR}^{6} \oplus \operatorname{ROTR}^{11} \oplus \operatorname{ROTR}^{25}\)
- \(\operatorname{Ch}(e, f, g) = (e \wedge f) \oplus (\not e \vee g)\)
- This is called the Choice function and \(e\), \(f\) and \(g\) are 3 of the 8 working variables.
- For every bit position, if the bit of \(c\) is 0, then the corresponding bit is taken from \(f\) and if the bit of \(c\) is 1, then the corresponding bit is taken from \(g\).
- \(\operatorname{Maj}(a, b, c) = (a \wedge b) \oplus (a \wedge c) \oplus (b \wedge c)\)
- This is the Majority function and for each bit position, the output is the bit value (0 or 1) that appears in at least two of the three inputs.
- Update the worker variables as:
- \(\verb|H7| = \verb|H6|\)
- \(\verb|H6| = \verb|H5|\)
- \(\verb|H5| = \verb|H4|\)
- \(\verb|H4| = (\verb|H3| + T_{1}) \mod(2^{32})\)
- \(\verb|H3| = \verb|H2|\)
- \(\verb|H2| = \verb|H1|\)
- \(\verb|H1| = \verb|H0|\)
- \(\verb|H0| = (T_{1} + T_{2}) \mod(2^{32})\)
- Calculate:
\[T_1 = h + \Sigma_1(e) + \operatorname{Ch}(e,f,g) + K[i] + W[i] \]
\[T_2 = \Sigma_0(a) + \operatorname{Maj}(a,b,c) \]
The helper functions/variables are defined as:
- Concatenate all the worker variables and convert the result into hexadecimal.
- Rough Implementation in Python
def main(): message = "quickbrown" output = convert_string_to_binary(message) output+='1' output = pad_till_n(output, 448) output+= f"{len(message):0{64}b}" # working variables: H = [ f"{int(x,16):032b}" for x in [ "6a09e667", "bb67ae85", "3c6ef372", "a54ff53a", "510e527f", "9b05688c", "1f83d9ab", "5be0cd19", ] ] # round constants K = [ 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2 ] # split the block into 16 words (w0, w1, ... w15) of 32 bits each w = [] for i in range(0, 512, 32): w.append(output[i:i+32]) # creating words w16, w17, ... w63 (helper functions defined later) for i in range(16, 64): value = (sigma_1(w[i-2]) + int(w[i-7], 2) + sigma_0(w[i-15]) + int(w[i-16], 2)) % (2**32) w.append(f"{value:032b}") # the 64 rounds (helper functions defined later) for i in range(64): T1 = (int(H[7], 2) + SIGMA_1(H[4]) + Ch(H[4], H[5], H[6]) + K[i] + int(w[i], 2)) % (2**32) T2 = (SIGMA_0(H[0]) + Maj(H[0], H[1], H[2])) % (2**32) H[7] = H[6] H[6] = H[5] H[5] = H[4] H[4] = f"{(int(H[3],2) + T1) % (2**32):032b}" H[3] = H[2] H[2] = H[1] H[1] = H[0] H[0] = f"{(T1 + T2) % (2**32):032b}" print(hex(int("".join(H), 2))) def convert_string_to_binary(txt: str): binary = "" for c in txt: binary += f"{ord(c)-96:08b}" return binary def pad_till_n(txt: str, n): return txt.rjust(n, "0") # helper functions for creating words w16, w17, ... w63 def ROTR(w, n): n = n%len(w) return w[-n:] + w[:-n] def SHR(w, n): return w[:-n].rjust(len(w), "0") def sigma_0(x): return int(ROTR(x, 7), 2) ^ int(ROTR(x, 18), 2) ^ int(SHR(x, 3), 2) def sigma_1(x): return int(ROTR(x, 17), 2) ^ int(ROTR(x, 19), 2) ^ int(SHR(x, 10), 2) # helper functions for the 64 rounds def SIGMA_0(x): return int(ROTR(x, 2), 2) ^ int(ROTR(x, 13), 2) ^ int(ROTR(x, 22), 2) def SIGMA_1(x): return int(ROTR(x, 6), 2) ^ int(ROTR(x, 11), 2) ^ int(ROTR(x, 25), 2) def Ch(e, f, g): return (int(e, 2) & int(f, 2)) ^ (~int(e, 2) & int(g, 2)) def Maj(a, b, c): return (int(a, 2) & int(b, 2)) ^ (int(a, 2) & int(c, 2)) ^ (int(b, 2) & int(c, 2)) main()
0xc072e1042417d4a9c774c9ffeaea3d25440a6479978c2adc1cacdc9a894c35bc
- Using
hashlib
The steps in which this works is given below:
- Make
bytesObject
- A
bytesobject in Python is an immutable sequence of integers ranging from 0 to 255. You can make it using by passing a list of integers as the parameter to the
bytesfunction:list_bytes = bytes([72, 101, 108, 108, 111]) print(list_bytes)
b'Hello'
You can also use a
bsuffix to a string:list_bytes = b'hello' print(list_bytes)
b'hello'
You can also use the
.encode()method and it gives the same thing:list_bytes = 'hello'.encode() print(list_bytes)
b'hello'
If you index a
bytesobject, you get the ASCII value:print(b'ABC'[0])
65
- A
- Use
.sha256()method
From the
hashlibmodule, the.sha256()method takes abytesobject as a parameter, and returns aHASHobject:import hashlib print(type(hashlib.sha256(b'ABC')))
<class '_hashlib.HASH'>
- Use
.hexdigest()method
On the
HASHobject, you can call the.hexdigest()method to return the hashed value as hexadecimal characters:import hashlib hash_value = hashlib.sha256("text".encode()).hexdigest() print(hash_value)
982d9e3eb996f559e633f4d194def3761d909f5a3b647d1a851fead67c32c9d1
- Make
3.1.2. Hashing a dictionary
In Python, you’d have to convert the dictionary into a string i.e. JSON.
import json a = {"sender": "Alice", "amount": 10} x = json.dumps(a) print(x) print(type(x))
{"sender": "Alice", "amount": 10} <class 'str'>To make sure the order in which the key-value pairs are placed is uniform, we use the
sort_keys=Trueargument:import json import hashlib a = {"sender": "Alice", "amount": 10} x = json.dumps(a, sort_keys=True) hash_x = hashlib.sha256(x.encode('utf-8')).hexdigest() b = {"amount": 10, "sender": "Alice"} y = json.dumps(b, sort_keys=True) hash_y = hashlib.sha256(y.encode('utf-8')).hexdigest() print(hash_x) print(hash_y)
f1bcf16a3b21c0d70dac5cc5459ebd54ab103a910ba94f6f11da93fcfc8993c5 f1bcf16a3b21c0d70dac5cc5459ebd54ab103a910ba94f6f11da93fcfc8993c5
As seen, both
aandbhave the same hashes.
3.2. Cryptographic Keys
- These are mathematical codes verify a user’s identify and authorize transactions.
- They keys are generated as a pair (public key and private key).
- You can mathematically derive public key from private key but never private key from public key.
3.2.1. Based on Number of Keys
| Asymmetric Cryptography | Symmetric Cryptography |
|---|---|
| Receiver’s public key used for encryption, and receiver’s private key is used for decryption | A common private key is used for encryption and decryption |
| It is also known as public key cryptography | It is also known as private key cryptography |
- RSA (Rivest Shamir Adelman) Algorithm
- This is the model widely used Public Key Cryptography (PKC).
- To encrypt a message there is no need to exchange a secret key separately.
- Key Generation
- Select \(p\), \(q\), where \(p\) and \(q\) are prime.
- Calculate \(n = p \times q\)
- Select integer \(e\), such that \(\text{gcd}(\phi(n), e) = 1; 1 < e < \phi(n)\)
- Calculate \(d = e^{-1}\mod(\phi(n))\)
- Public Key \(\text{KU} = {e,n}\)
- Private Key \(\text{KR} = {d,n}\)
- Encryption (through an example)
- \(p=3\), \(q=11\)
- \(n=p \times q = 3 \times 11 = 33\)
- \(\phi(n) = \phi(33) = \phi(3 \times 11) = 2 \times 10 = 20\)
- Verification of φ(33): \(Z_{33}^{*}\) = (all the numbers from 1 to 33, coprime with \(33\)) = [1,2,4,5,7,8,10,13,14,16,17,19,20,23,25,26,28,29,31,32]
- Consider \(e=7\) (because it satisifies the GCD criteria):
\(d = e^{-1} \mod(20)\)
- \(d = 7^{-1} \mod(20)\)
\(q\) \(a\) \(m\) \(r\) \(u_{0}\) \(u_{1}\) \(u\) 0 7 20 6 1 0 1
3.3. Blockchain Address
- A blockchain address is derived from the public key using hash functions.
- The flow is
- Private Key is created
- Public Key is derived from the public key.
- Hash function transforms the public key to generate the blockchain address.
3.4. Message Digest
- This is a fixed length alphanumeric string that acts as a digital fingerprint.
- It’s basically the hash value of the message.
- It ensures data integrity.
3.5. Digital Signature
3.5.1. What it is
- This is created by taking the hashed message and encrypting it using the sender’s private key.
- Anyone can verify that this message was sent by the actual sender by using the sender’s public key to decrypt it and get back the original message.
- A message digest verifies the integrity of data only.
- A digital signature verifies authenticity, integrity, AND ownership of a transaction (broader purpose)
3.5.2. How it works
3.6. Miner
- A miner is an individual or a company that computationally validate transactions and add new blocks to the blockchain.
- They solve complex mathematical problems to prove that they have performed computational work before they are allowed to add a new block to the blockchain.
- This process secures the blockchain, prevents fraud and enables decentralized consensus.
- Mining is used to secure transactions utilizing high performance computing systems.
3.7. Difficulty
3.8. Nonce
- Nonce stands for number used only once.
- It is a 32 bit number such that the hash of the block satisfies the network’s difficulty requirement.
- A golden nonce is the specific number a cryptocurrency miner finds that produces a valid block hash below the network’s required target difficulty.
- If a miner obtains the golden nonce, they obtain the
- Block Reward
- Gas
The basic algorithm is:
nonce = 0 while hash doesn't satisfy difficulty: nonce += 1 calculate hash stop when valid
3.9. Consensus
- It is the procss by which all the nodes in a blockchain agree on the validity of transactions and the current state of the blockchain.
- Consensus mechanism is what determines which block/blockchain is accepted by the network.
- It ensures that every participant in the blockchain has the same copy of the ledger.
| CAP Theorem | Consensus |
|---|---|
| Explains trade offs | achieves agreement |
| Feature | Proof of Work (PoW) | Proof of Stake (PoS) |
|---|---|---|
| Basis of selection | Computational puzzle-solving (brute-force nonce guessing) | Amount of cryptocurrency staked as collateral |
| Resource cost | Huge electricity + specialized hardware (ASICs) | Minimal energy; no special hardware needed |
| Security mechanism | Attacker must out-compute the entire honest network’s hash power | Malicious validators get slashed — their staked funds are confiscated |
| Speed / throughput | Slower (Bitcoin ≈ 10 min/block, ~7 tx/sec) | Faster validation, higher throughput |
| Sybil-attack resistance | Strong — fake identities are cheap, but computing power isn’t | Strong — fake identities are cheap, but staking real capital isn’t |
| Example networks | Bitcoin | Ethereum (post-2022), Cardano |
Slashing (the enforcement mechanism): If a validator behaves maliciously, like approving fraudulent transactions, breaking protocol rules, the network can confiscate part or all of their staked assets.
3.10. Generations of Blockchain
| Blockchain 1.0 | Blockchain 2.0 | Blockchain 3.0 | Blockchain X |
|---|---|---|---|
| Only for cryptocurrency | Cryptocurrency + Smart Contracts | Blockchain used not only for finance but for health care, governance, etc | Future use case of blockchain as a search engine |
4. Execution Flow of a Blockchain
\[\text{Transaction} \rightarrow \text{Mempool} \rightarrow \text{Mining / Block Creation} \rightarrow \text{Blockchain} \rightarrow \text{Validation} \]
4.1. How Blockchains Accumulate Blocks
- A node starts at transaction by signing it with its private key.
- The transaction is propagated by using much desirable gossip protocol to peers, which validates the transaction based on pre-set criteria.
- Once the transaction is validated, it is included in a block, which is then propagated on to the network. At this point, the transaction is considered confirmed.
- The newly created block now becomes part of the ledger and the next block links itself cryptographically back to this block.
4.2. Example
Implement a simple blockchain where miners find a valid nonce by repeatedly changing it until the block hash satisfies a predefined difficulty condition.
import hashlib import json import time def main(): blockchain = Blockchain() transactions = [ {"sender": "Alice", "receiver": "Bob", "amount": 10}, {"sender": "Bob", "receiver": "Charlie", "amount": 5} ] blockchain.add_block(transactions) transactions = [ {"sender": "Charlie", "receiver": "Alice", "amount": 2} ] blockchain.add_block(transactions) for block in blockchain.chain: print("\nBlock Number:", block.block_no) print("Timestamp:", block.timestamp) print("Transactions:", block.transactions) print("Transaction Hashes:", block.transaction_hashes) print("Previous Hash:", block.previous_hash) print("Merkle Root:", block.merkle_root) print("Nonce:", block.nonce) print("Current Hash:", block.current_hash) print("\nIs chain valid?", blockchain.is_chain_valid()) # Test tampering blockchain.chain[1].transactions[0]["amount"] = 999 print("Is chain valid after tampering?", blockchain.is_chain_valid()) def hash(data): data = str(data).encode("utf-8") return hashlib.sha256(data).hexdigest() def hash_tx(tx): tx = json.dumps(tx, sort_keys=True) # convert into json return hash(tx) class Block: def __init__(self, block_no, transactions, previous_hash): self.block_no = block_no self.transactions = transactions self.previous_hash = previous_hash self.transaction_hashes = [hash_tx(tx) for tx in transactions] self.timestamp = time.time() self.merkle_root = self.calculate_merkle_root() self.nonce = 0 self.difficulty = 2 self.current_hash = self.calculate_block_hash() def calculate_block_hash(self): block_data = { "block_number": self.block_no, "timestamp": self.timestamp, "transactions": self.transactions, "transaction_hashes": self.transaction_hashes, "previous_hash": self.previous_hash, "merkle_root": self.merkle_root, "difficulty": self.difficulty, "nonce": self.nonce } serialized_data = json.dumps(block_data, sort_keys=True) return hashlib.sha256(serialized_data.encode('utf-8')).hexdigest() def mine(self): target = "0" * self.difficulty attempts = 0 while True: attempts += 1 self.current_hash = self.calculate_block_hash() if self.current_hash.startswith(target): return (attempts, self.current_hash) else: self.nonce += 1 def calculate_merkle_root(self): leaves = self.transaction_hashes.copy() if len(leaves) == 0: return hash("") while len(leaves) > 1: if len(leaves) % 2 != 0: leaves.append(leaves[-1]) next_level = [] for i in range(0, len(leaves), 2): next_level.append( hashlib.sha256( (leaves[i] + leaves[i+1]).encode('utf-8') ).hexdigest() ) leaves = next_level return leaves[0] class Blockchain: def __init__(self): self.chain = [] genesis = Block(0, [], "0" * 64) genesis.mine() self.chain.append(genesis) def get_latest_block(self): return self.chain[-1] def add_block(self, transactions): previous_block = self.get_latest_block() new_block = Block( len(self.chain), transactions, previous_block.current_hash ) attempts, block_hash = new_block.mine() self.chain.append(new_block) print( f"Block mined in {attempts} attempts. " f"Hash: {block_hash}" ) def is_chain_valid(self): for i, block in enumerate(self.chain): if block.current_hash != block.calculate_block_hash(): return False if not block.current_hash.startswith("0" * block.difficulty): return False if block.transaction_hashes != [ hash_tx(tx) for tx in block.transactions ]: return False if block.merkle_root != block.calculate_merkle_root(): return False if i > 0: if block.previous_hash != self.chain[i-1].current_hash: return False return True main()
Block mined in 213 attempts. Hash: 00816af415bbd5e200e31f38ecf5c71a049f90ed4f7f17298082f8bb0b27f19f
Block mined in 345 attempts. Hash: 002eff61b3ab3bb63791b12616dfc82b0af7360e32482b0b7bd1bc98efee2cfb
Block Number: 0
Timestamp: 1792175317.6662347
Transactions: []
Transaction Hashes: []
Previous Hash: 0000000000000000000000000000000000000000000000000000000000000000
Merkle Root: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Nonce: 99
Current Hash: 0056b6712bdbee19a01fe20822ee6d81f105d13b8fc0ba2f372f4ea0ed582bec
Block Number: 1
Timestamp: 1792175317.673822
Transactions: [{'sender': 'Alice', 'receiver': 'Bob', 'amount': 10}, {'sender': 'Bob', 'receiver': 'Charlie', 'amount': 5}]
Transaction Hashes: ['634205f68815df3adbe60b160bba9809e72b3fdf6ca15e3bf164508616fd7d6b', '0c338b26dc95cb386b5e9b4de6e04dba43785b23d30fd56bf683a66e0215dbf4']
Previous Hash: 0056b6712bdbee19a01fe20822ee6d81f105d13b8fc0ba2f372f4ea0ed582bec
Merkle Root: 00f653f3b7be4c01cde1c1a003de09a6586673b62324c65fc3fd86984e8492a8
Nonce: 212
Current Hash: 00816af415bbd5e200e31f38ecf5c71a049f90ed4f7f17298082f8bb0b27f19f
Block Number: 2
Timestamp: 1792175317.6748114
Transactions: [{'sender': 'Charlie', 'receiver': 'Alice', 'amount': 2}]
Transaction Hashes: ['66e3643203078e3ff27fd1aaf73354f807e20cb7be3d39a2239a466a3e2d6767']
Previous Hash: 00816af415bbd5e200e31f38ecf5c71a049f90ed4f7f17298082f8bb0b27f19f
Merkle Root: 66e3643203078e3ff27fd1aaf73354f807e20cb7be3d39a2239a466a3e2d6767
Nonce: 344
Current Hash: 002eff61b3ab3bb63791b12616dfc82b0af7360e32482b0b7bd1bc98efee2cfb
Is chain valid? True
Is chain valid after tampering? False
4.3. Example
Build a blockchain network consisting of 3 independent university nodes (A, B and C). Each university has a public-private key pair that acts as its blockchain identity. Design and implement a simplified and decentralized blockchain based certificate verification system in which the university’s issue digitally signed certificates and employers can independently verify them without relying on a central database. When a university issues a certificate:
- The certificate details forms a transaction.
- The transaction is digitally signed using the university’s private key.
- Other nodes can verify signatures using the university’s public key.
- Valid transactions are grouped into a block
The attributes of the block contains:
- Blocknumber
- Timestamp
- Transactions
- Previous block hash
- Transaction hash
- Current block hash
- Merkel Root
The participating nodes reach agreement using a consensus mechanism.
The validated block is replicated across peer to peer network.
An employer can verify a certificate using the student’s certificate ID and the issuing university’s public key.
4.4. Example
Implement a simple public blockchain for agriculture supply chain where:
- A farmer registers products on the blockchain.
- The supply chain team updates the produce status during transportation.
- The customer receives the produce.
- The produce reaches the customer
- The farmer recieves the benefit of the reward.
- All transactions must be stored in blocks linked using cryptographic hashes.
5. Merkle Tree
- It’s a binary tree where every node is the hash of its child.
- The leaf nodes (the lowest nodes in the tree) contain the hash of a transaction.
- The non-leaf nodes contain the hash of its child.
- Merkle trees are also called hash trees.
5.1. Example
Create a simple blockchain framework for the agricultural domain, where the participating entities are:
- A farmer
- Manufacturing unit
- Distributer
- Retail
- Consumer
The framework’s block should contain:
- Hash of the previous block’s header
- Hash of the current block
- Timestamp
- Nonce
- Merkle Root
Use Hash Pointers and Blockchain mining to establish a transparent peer-to-peer communication across the framework.
6. Bitcoin
- Bitcoin is a digital/encrypted currency.
- Bitcoin transactions happen with the help of operands, operators and OP CODES using scripts.
- In order to exeecute bitcoin transactions te spending condition mst be verified first.
- To spend a bitcoin, you must provide the correct public key and a valid digital signature using the corresponding private key.
Here are the OP CODES:
Part Meaning OP_DUPMakes a copy of the public key OP_HASH160Convert the public key into its hash <Public KeyHash>Compare it with the expected hash OP_EQUALCheck whether the two hashes are equal OP_CHECKINGCheck whether the signature is valid ScriptPubKeylocks the bitcoin and d4efines who is allowed to spend the bitcoin.ScriptSigprovides the signature and the public key needed to satisfy the condition.
6.1. Electronic Cash
- DigiCash was the first form of electronic cash created by a cryptographer David Chaum.
- HashCash, on the other hand, is a proof-of-work system used to limit email spam and denial-of-service attacks.
6.2. Example
Write a bitcoin program using SHA-256 hashing, merkle tree, mining and Proof of
Work.
6.3. UTXO Model
6.3.1. What it is
- UTXO stands for Unspent Transaction Output.
- It’s a discrete chunk of bitcoin that remains after a transaction and can be spent in future transactions.
6.3.2. How it works
- Each transaction consumes
7. Software Stack
This is a possible software stack to adopt for blockchain
7.1. Frontend
- React.js
- Next.js
- HTML
- CSS
- Tailwind CSS
- Bootstrap
- JavaScript/TypeScript
7.2. Blockchain Infrastructure
7.2.1. MetaMask
- These are used to create user wallets and signatures.
7.2.2. Wagmi + Diem
- These are used to create React → EVM (Ethereum Virtual Machine) blockchain interactions.
7.2.3. Bitcoin Wallets
7.2.4. Middleware
- Fabric Gateway
7.2.5. UI Components
- Ethers.js
- Webley.js ?
7.2.6. Blockchain Languages
- Solidity
- Hyperledger fabric (for designing permission or consortium blockchain)
7.2.7. Ethereum or Sepolia
7.2.8. Polygon Amoy
7.2.9. Ganache / Hardhat local network
- These are for local development and testing.
7.3. Backend
- Node.js for server-side applications
- Express.js
- Fast API with Python
- PostgreSQL / MongoDB / MySQL
8. Case Study Topics
- Proof of Working
Mining
Note: Requires Live Demo