Intro
In today's rapidly growing Web3 world of blockchains, understanding the fundamental technology behind cryptocurrencies, smart contracts, and decentralized applications has become increasingly important. To build a strong general knowledge of how blockchain works, I've decided to create a cycle of articles about creating a blockchain from scratch in Rust. This hands-on approach will provide deeper insights into the inner workings of blockchain technology while also offering practical programming experience with a powerful systems language.
What is Blockchain?
From Wikipedia:
The blockchain is a distributed ledger with growing lists of records (blocks) that are securely linked together via cryptographic hashes.
From ChatGPT:
Blockchain is essentially a digital, decentralised ledger that records transactions in a way that is secure, transparent, and tamper-resistant. Think of it as a chain of “blocks,” where each block contains a list of transactions. Once a block is added, it’s linked to the previous one, forming a chain—hence the name blockchain.
So, from both definitions we have the common keywords:
- Distributed
- Ledger
- Records/Block
- Security
Distributed – the blockchain's data isn't stored in just one central location or controlled by a single entity. Instead, it's copied and spread across many computers (called nodes) in different locations. This distribution makes the blockchain more resilient because if one node fails or is compromised, the data still exists on many others. This is a bit complicated thing to implement, so we’ll talk about it in later articles.
Ledger – a digital record-keeping system that tracks all transactions that have occurred on the blockchain. Unlike traditional ledgers that might be controlled by a single entity (like a bank), blockchain ledgers are distributed (our previous word) across the network, with each node having their own copy. This ensures transparency and makes it extremely difficult to alter past records, as any change would need to be approved by the majority of the network.
Block – a fundamental unit of a blockchain that contains a collection of transactions and metadata. When transactions occur on the blockchain, they are verified and grouped together into blocks. Each block contains:
- Block Number
- List of validated transactions
- Timestamp showing when the block was created
- Link to the previous block
The first block in the blockchain called master-block. So, now we have a ledger in a form of a chain of blocks, let’s move to the final keyword.
Security – comes from using cryptographic methods (mainly hash and asymmetric crypto functions). Each block has its own hash (like a digital fingerprint) created from what's inside the block. If someone changes anything in the block, the hash completely changes too. Because each block contains the previous block's hash, any changes are easy to spot, making the chain secure and hard to tamper with.
Okay, for now we have the fundamental definition of the blockchain, and to start our Rust simple implementation we just need to find out what a hash function is (we'll talk about asymmetric crypto functions later).
Hash function
Hash function – is like a digital fingerprint machine for data. It takes any information (like a transaction or block details) and converts it into a fixed-size string of characters that looks random. The main crucial properties of hash functions are:
- Any tiny change to the data completely changes the hash output
- You can't recreate the original data from just the hash
It's not a trivial task to create a secure and efficient hash function by yourself, and you need at least a PhD in cryptography, so we'll use existing ones.
For example, usage of hash functions by popular blockchains:
- Bitcoin – SHA-256
- Ethereum – Keccak-256
- Solana – SHA-256 / BLAKE3
- Litecoin – Scrypt
- Cardano – Blake2b-256
SHA-256 (256 means that the hash length is 256 bits) – is very popular and time-tested algorithm in cryptography that provides strong security guarantees.
SHA-3 (Keccak) – a newer member of the SHA family, standardized in 2015. Unlike SHA-256, it uses a different internal structure (sponge construction) making it resistant to attacks that might eventually threaten SHA-256. Ethereum chose Keccak-256 (a variant of SHA-3) for its blockchain to differentiate from Bitcoin and provide additional security margins.
Also, it's important to use a verified or even audited version of the crypto library, so for our case I'll choose the sha2 crate (since it's battle-proven with the Solana blockchain).
Initial implementation
Our blockchain project is named Fleming, after Ian Fleming, the creator of James Bond. The word "Bond" is similar-sounding to the financial instrument "bond" — a wordplay I find particularly interesting in the context of blockchain technology.
Since we decided to use sha2 as the library for hashing, let's install it as well (hex is used for encoding byte arrays to more human-readable strings):
[package]
name = "fleming"
version = "0.1.0"
edition = "2021"
[dependencies]
sha2 = "0.10.9"
hex = "0.4.3"
First of all, let's define the Transaction type (in this article it'll be just a string alias, we'll come back to it in the next articles):
pub type Transaction = String;
Now, let's add a more user-friendly type alias for hashes and use the hex crate to encode it to a string for debug purposes:
#[derive(Clone)]
pub struct BlockHash(pub [u8; 32]); // SHA-256 -> 32 bytes = 256 bits
impl Debug for BlockHash {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "\\"0х{}\\"", hex::encode(self.0))
}
}
Then the Block:
#[derive(Debug, Clone)]
pub struct Block {
number: u64, // just a serial number of block
transactions: Vec<Transaction>, // array of transactions
previous_hash: BlockHash, // SHA-256 of previous block
hash: BlockHash, // SHA-256 of current block
timestamp: u64, // block creation timestamp
}
And, finally, the Blockchain:
pub struct Blockchain {
chain: Vec<Block>, // chain of blocks
}
Now that we've defined our data structures, let's implement them. We'll start with the Block:
pub fn new(
block_number: u64,
transactions: Vec<Transaction>,
previous_hash: BlockHash,
) -> Self {
let mut block = Block {
number: block_number,
transactions,
timestamp: get_timestamp(),
previous_hash,
hash: BlockHash([0; 32]),
};
block.hash = block.calculate_hash();
block
}
To create a new block, we need three things: a block number, an array of transactions, and the previous block's hash. Once we have these, we calculate the block's hash:
fn calculate_hash(&self) -> BlockHash {
let mut hasher = Sha256::new();
hasher.update(self.number.to_le_bytes());
hasher.update(&self.previous_hash.0);
hasher.update(self.timestamp.to_le_bytes());
for tx in &self.transactions {
hasher.update(tx.as_bytes());
}
BlockHash(hasher.finalize().into())
}
All components must be included in the hash. To verify the block, we can recalculate its hash and compare it to the stored one:
pub fn is_valid(&self) -> bool {
let current_hash = self.calculate_hash();
self.hash.0 == current_hash.0
}
We're done with the Block. Let's move on to the Blockchain:
pub fn new() -> Self {
let master_block = Block::new(0, vec![], BlockHash([0; 32]));
println!("Master block: {:#?}", master_block);
Blockchain {
chain: vec![master_block],
}
}
To create a new blockchain, we initialize it with a master-block (block number 0) that has an empty transaction list and a zero hash as its previous hash, since there's no block before it.
pub fn append_block(&mut self, transactions: Vec<Transaction>) {
let previous_block = self.chain.last().unwrap();
let previous_hash = previous_block.hash().clone();
let block_number = previous_block.number() + 1;
let new_block = Block::new(block_number, transactions, previous_hash);
println!("Appending block: {:#?}", new_block);
self.chain.push(new_block);
}
To append a new block to the blockchain, we retrieve the last block from the chain, get its hash and block number, then create a new block with an incremented number and the previous block's hash. And, finally, to validate the whole blockchain:
pub fn is_valid(&self) -> bool {
for block_index in 1..self.chain.len() {
let current_block = &self.chain[block_index];
let previous_block = &self.chain[block_index - 1];
if !current_block.is_valid() {
println!("Block {} has invalid hash", current_block.number());
return false;
}
if current_block.previous_hash().0 != previous_block.hash().0 {
println!("Block {} has invalid previous_hash", current_block.number());
return false;
}
}
true
}
To validate the entire blockchain, we iterate through each block (starting from block 1, since the master block has no previous block to verify against), check if each block's hash is valid, and verify that each block's previous_hash matches the actual hash of the preceding block. And that's it. We've got a basic blockchain implementation in a couple of hundred lines of code. Don’t forget to write a couple unit tests to make sure our logic is fine:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blockchain_creation() {
let blockchain = Blockchain::new();
assert_eq!(blockchain.chain.len(), 1);
assert!(blockchain.is_valid());
}
#[test]
fn test_valid_blockchain() {
let mut blockchain = Blockchain::new();
blockchain.append_block(vec![String::from("A -> B: 10 FLMG")]);
blockchain.append_block(vec![String::from("C -> D: 5 FLMG")]);
assert_eq!(blockchain.chain.len(), 3);
assert!(blockchain.is_valid());
}
#[test]
fn test_tampered_blockchain_invalid() {
let mut blockchain = Blockchain::new();
blockchain.append_block(vec![String::from("A -> B: 10 FLMG")]);
blockchain.append_block(vec![String::from("C -> D: 5 FLMG")]);
blockchain.chain[1].tamper_transaction(0, String::from("A -> B: 1000 FLMG"));
assert!(!blockchain.is_valid());
}
}
Conclusion
In this article, we've built a foundational blockchain implementation in Rust from scratch. We explored the core concepts of blockchain technology — distributed ledgers, blocks, and cryptographic security — and implemented basic data structures for transactions, blocks, and the blockchain itself. Using the SHA-256 hash function via the sha2 crate, we created a system where each block contains transactions, a timestamp, and a cryptographic link to the previous block. We also implemented validation mechanisms to ensure the integrity of individual blocks and the entire chain, demonstrating how blockchain's immutability works in practice.
In the next article, we'll talk about accounting models, states, and make our transactions more functional, so stay tuned!
All code from this article can be found here or in the release here.
Top comments (0)