Creating a simple Blockchain
The Wikipedia definition of blockchain says
A blockchain, originally block chain, is a continuously growing list of records, called blocks, which are linked and secured using cryptography.
Okay, let’s break it down to get a better understanding.
Blocks
A block contains transaction data, the cryptographic hash of the previous block, the timestamp and a nonce. You must be wondering what a nonce is, It will be explained in detail later, but for now just think of it as a random number.
A block’s hash is generated using the transaction data, timestamp, previous block’s hash and the nonce as input.
For example, let’s say the hashing algorithm used is SHA-256 (more complex hashing algorithms are used in actual implementations) and assume the following as the block’s data
Transaction data : "Hello, World"
Timestamp : 1527696247729
Previous block hash : 5591ae80713bc6324cae319ddfbdbedd710aabb96a33af80b0732ecd8e42314d Nonce : 383212
We append the all the data to a string, which looks something like this
Hello, World15276962477295591ae80713bc6324cae319ddfbdbedd710aabb96a33af80b0732ecd8e42314d383212
After applying SHA-256 on the above string we’ll get the value (you can experiment with SHA-256 online here)
4527f3eb2c927118f1da9ae1327862a1b52d998108ce608526b4814ca1a5af50
This is the value which is going to be used in the next block’s previous block hash property.
Enough with the theory, let’s dive into code
First let’s create a helper class to return the hash of a given string using SHA-256 algorithm
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.charset.StandardCharsets;
public class CryptoHelper {
public static String sha256(String text) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(text.getBytes(StandardCharsets.UTF_8));
byte[] hash = digest.digest();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < hash.length; i++)
sb.append(Integer.toString((hash[i] & 0xff) + 0x100, 16).substring(1));
return sb.toString().toUpperCase();
}
}
Don’t worry if you don’t completely understand this method, you can replace it with any other hashing algorithm that you understand.
Now, let’s create the Block class
public class Block {
public long nonce;
private long timestamp;
public String transactionData;
public String previousBlockHash;
public Block(String transactionData) {
nonce = 0;
timestamp = System.currentTimeMillis();
this.transactionData = transactionData;
}
public String getHash() throws NoSuchAlgorithmException {
return CryptoHelper.sha256(transactionData + String.valueOf(timestamp)
+ String.valueOf(previousBlockHash)
+ String.valueOf(nonce));
}
@Override
public String toString() {
return String.format("%s\t%d\t%d\t%s", previousBlockHash, timestamp, nonce, transactionData);
}
}
For brevity, transactionData
is a simple string and also let's not worry about the nonce for now, let it be 0, we'll be coming back to this later.
We need a class to represent a collection of blocks,
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
public class BlockChain {
public List<Block> chain;
public BlockChain() {
chain = new ArrayList<>();
}
public void addBlock(Block block) throws NoSuchAlgorithmException {
block.previousBlockHash = chain.size() == 0 ? CryptoHelper.sha256("0") : chain.get(chain.size()-1).getHash();
chain.add(block);
}
public boolean isValid() throws NoSuchAlgorithmException {
for (var i=1; i < chain.size(); ++i) {
var previousBlock = chain.get(i-1);
var currentBlock = chain.get(i);
if (!currentBlock.previousBlockHash.equals(previousBlock.getHash())) {
return false;
}
}
return true;
}
}
This class is pretty simple contains a List to store blocks and methods to add a block and validate the chain.
Now that we have the foundation classes, let’s create a Driver class for testing this out
public class Main {
public static void main(String[] args) {
try {
var blockChain = new BlockChain();
Block block1 = new Block("Hello");
Block block2 = new Block("World");
Block block3 = new Block("Whatever");
blockChain.addBlock(block1);
blockChain.addBlock(block2);
blockChain.addBlock(block3);
for (Block block: blockChain.chain) {
System.out.println(block);
}
System.out.println("Is valid chain : " + blockChain.isValid());
}
catch(Exception e) {
System.err.println(e);
}
}
}
On a sample run, we get the following values, the first column is the previous block’s hash value, followed by the timestamp, nonce and the transaction data.
5FECEB66FFC86F38D952786C6D696C79C2DBC239DD4E91B46729D73A27FB57E9 1527703318986 0 Hello
E5A6134694C22742C88639C8E3442BDF19FB7843F829B2351B24CB0CCC9AA917 1527703318986 0 World
759F215F98C2374696552663280BBC90378741DBD36B067A6DF0FAA50DD38769 1527703318986 0 Whatever
Is valid chain : true
From the output, we can see that the blockchain is valid (i,e) The previous block hashes are matching correctly.
Let’s try modifying one of the block’s transactionData
public static void main(String[] args) {
try {
var blockChain = new BlockChain();
Block block1 = new Block("Hello");
Block block2 = new Block("World");
Block block3 = new Block("Whatever");
blockChain.addBlock(block1);
blockChain.addBlock(block2);
blockChain.addBlock(block3);
for (Block block: blockChain.chain) {
System.out.println(block);
}
System.out.println("Is valid chain : " + blockChain.isValid());
// modify second block's data..
blockChain.chain.get(1).transactionData = "World1";
System.out.println("Is valid chain : " + blockChain.isValid()); // will be false
}
catch(Exception e) {
System.err.println(e);
}
}
Indeed, it’s not valid because when we changed the second block’s transactionData
, the next block's previousHash
has to be updated to match the new hash. We can solve this easily by recalculating the hashes and assigning the previousHash accordingly. If changing an element is so simple, what about this statement?
By design, a blockchain is inherently resistant to modification of the data
That’s where the nonce
comes into play.
So what is a nonce?
The nonce is a number that should be predicted and acts as a proof of work, making any modification in the blockchain very hard
Blockchain implementations have a difficulty level that depends on the implementation, but to make things simpler, let’s say the produced hash should start with a specified number of zeros
For example, When difficulty is 1, the block’s hash should start with 1 zero, when the difficulty is 2, the block’s hash should start with 2 zeros and so on. The only way in which we can produce a hash that satisfies the difficulty is to try changing the nonce value and hope that some number produces a hash that meets the difficulty criteria, this method of trying every possible value to find a solution is called bruteforcing.
To add that functionality to our implementation, let’s start with adding the mine
method to the Block
class
public void mine(int difficulty) throws NoSuchAlgorithmException {
String pre = "";
for (; pre.length() < difficulty; pre += "0");
while(!getHash().startsWith(pre))
nonce++;
}
In the method, we are incrementing the value of nonce
until the hash starts with the number of zeros specified by the difficulty
. Then we call this method in the BlockChain
class before adding a block to the chain
public class BlockChain {
public List<Block> chain;
private int difficulty;
public BlockChain(int difficulty) {
chain = new ArrayList<>();
this.difficulty =
difficulty;
}
}
modifying addBlock
to mine block before adding to the blockchain
public void addBlock(Block block) throws NoSuchAlgorithmException {
block.previousBlockHash = chain.size() == 0 ? CryptoHelper.sha256("0") : chain.get(chain.size()-1).getHash();
// before adding a block to the blockchain
// mine block for the magic nonce value
block.mine(difficulty);
chain.add(block);
}
also, the isValid
method needs to check every block if it satisfies the difficulty criteria, so it should look something like this
public boolean isValid() throws NoSuchAlgorithmException {
var pre = "";
for(;pre.length() < difficulty; pre += "0");
for (var i=1; i < chain.size(); ++i) {
var previousBlock = chain.get(i-1);
var currentBlock = chain.get(i);
if (!currentBlock.getHash().startsWith(pre) || !currentBlock.previousBlockHash.equals(previousBlock.getHash())) {
return false;
}
}
return true;
}
And finally, we pass the difficulty from the main method
public static void main(String[] args) {
try {
var difficulty = 5;
var blockChain = new BlockChain(difficulty);
Block block1 = new Block("Hello");
Block block2 = new Block("World");
Block block3 = new Block("Whatever");
blockChain.addBlock(block1);
blockChain.addBlock(block2);
blockChain.addBlock(block3);
System.out.println(String.format("%64s\t%13s\t%7s\t%s", "Previous Block's hash", "Timestamp", "Nonce", "Transaction Data"));
for (Block block: blockChain.chain) {
System.out.println(block);
}
System.out.println("Is valid chain : " + blockChain.isValid());
blockChain.chain.get(1).transactionData = "World1";
System.out.println("Is valid chain : " + blockChain.isValid());
}
catch(Exception e) {
System.err.println(e);
}
}
On a sample run, we get the following output
Previous Block's hash Timestamp Nonce Transaction Data
5FECEB66FFC86F38D952786C6D696C79C2DBC239DD4E91B46729D73A27FB57E9 1527709815253 511316 Hello
00000BE462BE07F21AC57939DC46310322F8ACF170B1C16CC03E3DCC431A59BE 1527709815253 1362348 World
00000E3D3CFFAEE5E8360EB8E7FF8AB39F270D39BCD5830A8505974632B98DC4 1527709815253 792204 Whatever
Is valid chain : true
Is valid chain : false
Observe the Nonce
values, for the first block, it took 5,11,316 calls to getHash()
to find a nonce value that satisfies the difficulty criteria. We have used a difficulty of 5 which is tiny, in actual implementations the difficulty is much higher and also a much complex hashing algorithm will be used.
Imagine having a chain of about 10 blocks, if block 3 is modified, then the nonce values for blocks starting from 3 need to be recalculated which is a pretty expensive and time consuming operation. The nonce is what makes it difficult to modify the blocks in a blockchain. Try tinkering with the difficulty values and you can observe that as the difficulty increases, the time taken to calculate the nonce increases exponentially.
The full source code is available here.