Why I Built a Blockchain from Scratch
Over the past few days I have been increasingly interested in cryptography and how it protects all the machines we interact with. While I went down that rabbit hole, I bumped into this idea that makes cryptocurrencies work that I found fascinating. So naturally, I tried to recreate it on my own. Well at least a toy version.
Here is an attempt of me trying to write about what i learnt.
The general premise of a cryptocurrency is that they try to decentralize payments. So instead of a bank trying to manage and validate transactions for you, you do that on your own; or rather we all do it; Karl Marx would've loved it.
Every computer starts out as a node that yells out transactions to nodes around it and since you cannot rely on someone to manage transactions for you; every node has to have a copy of every transaction that has ever occurred. So when you hear a transaction, you need to write it down and propagate it forward. So, when you want to transfer 500 rupees to your friend, you just yell that out to everyone and everyone writes it down.
However in a system like this, how do you make sure you actually meant to send those 500 rupees? Clearly anyone can yell anything they want on the internet right?
To combat this you use the powers of The Discrete Logarithm Problem. The general idea is just that sometimes in math, it is really easy to compute something one way but really(really) hard to reverse it. For example, another irreversible operations is the factorization problem, if I take two very large prime numbers and multiply them and give them to you to factorize. Even with the fastest computers on Earth right now, it would take you longer than the time it would take for the sun to swallow the earth whole. Well, until quantum computers come and ruin the internet for everyone.
Elliptic Curve Cryptography
You start out with the Bitcoin curve:
Here is just a huge prime number that binds us to a finite plane. Why prime you ask? Because cryptography has a thing for prime numbers I've come to see. But seriously, it's just because it lets you divide in modular arithmetic. Why can't you just divide? Because division in modular arithemetic doens't really exist. Division implies modulo inverse; and for every non zero number to have an inverse, the modulo has to be prime. This is because of Fermat's Little theorem. It was really fun exploring these theorems from the 17th century coming from people who were just playing around with numbers just because they enjoyed it. Ironically, the first time I heard about this theorem was about 8 years ago. I distinctly remember thinking Fermat's Little Theorem has to be the most obscure and useless thing ever.
We start by defining operations these operation on the curve
Take x and y and join them with a line. See where this line cuts the curve and reflect it about the x axis (curve is symmetric about it). Why not just add them? Because then they wouldn't lie on the curve.
Here is the point and is just some scalar. So you just take x and add it with itself k times. The connecting line would just be a tangent. The neat part about this is you don't even bother to multiply to itself times. Let's say you want to get to 53 by adding 1 to itself certain times.
So instead of adding 1 to itself 53 times. You just keep doubling it and adding it whenever the binary bit is 1. This is one of the few times i got to optimize things like this using bit operations.
Every person who wants to make transactions has a public and a private key. The public key is just a unique identifier for every person and a private key acts like something you can use to validate your identity. Remember the goal is for you to be able to make transactions and verify that you were indeed the one who made them.
To do this, you take a random point , let's call it the generator, on the curve; get a private key and multiply that to the generator. This new number is your public key. It's just another point on the curve but it's yours now. It has an x coordinate and a y coordinate and everything and it's very dear to you because it's literally all you are here. You can tell anyone your public key because it is mathematically infeasible to retrieve the private key if you know and the public key.
So now, you just take a transaction, get it's hash, which is just another one way function that outputs a deterministic but random looking string(more on this later) and sign it using your private key. Signing again just means performing mathematical operations on this hash. This signature can be verified by anyone with the public key but can only be made using the private key.
This is all good and we've already seen this before. A lot of systems already use this technology for signatures or even encryption but there's another problem with cryptocurrencies. Since the system relies on peer to peer communication, you could exploit the system by doing something like this: You have 80 rupees. You tell Alice you give her 80 rupees and you tell Bob you give him 80 rupees at the same time. Both of those transactions are valid since you used your private key to sign them. However both cannot happen at the same time since you cannot spend 160 rupees if you have only 80. This is called the double spend problem. Bitcoin addresses this in a really beautiful way.
Proof of Work
Proof of work relies on hash functions. These are one way functions that take in some input and output a fixed length output. They are irreversible, meaning you cannot recompute the original input given a hash. This is how you compute integrity of software. After you download a piece of software, you compute it's hash and verify if it matches with the one published by the distributor. Even passwords are stored as hashes. When you enter your password, the server computes it's hash and writes that to the database. Actually with passwords, you add a random string called a salt to the password, hash them together and then send the hash and the salt over to the database. This ensures two identical passwords don't have the same hash. So an attacker doesn't get to infer this information from the hashes.
In cryptocurrencies, you just listen to transactions on the network and add them to your mempool. This is like the staging area for your transactions. You take some transactions from this mempool and try to make a block out of it. To make a block you take these transactions and then try to find a number called nonce ( ) such that the hash of the transactions with starts with a certain number of zeroes. The number of zeroes depend on the difficulty( ). The difficulty is set such that a block is mined roughly once every ten minutes. After you find a block, you get to add a new transaction at the end of it called the block reward. This is called mining a block. This is also how new coins enter the market. It is about 3.125 BTC right now. That's about 1.8 crores in Indian Rupees. That's like free money right? All you have to do is just find lucky number, Why shouldn't you do this as your full time job? That's mainly a skill issue on your computer's part. You see, to mine a block your computer has to compete with supercomputers performing hundreds of trillions of hashes per second. Your core i5 can manage few millions on a good day.
Each block has the hash of the previous block in it's header. Blocks are essentially chained together like this and hence, Blockchain. When you find conflicting blocks, you just defer to the longest chain, meaning the chain with the most proof of work. If two nodes ever find two valid blocks at the same time, you just wait until one of them is longer and then everyone moves to that one. The other one is abandoned. This is how Bitcoin solves the double spend problem.
But wait. Wasn't the point to decentralize the system. Did we do all this work to centralize the system again to the person with the most compute. Kind of. The system only starts falling apart when someone has more than 50% of the entire compute on the network. Then, this person can start mining blocks and would be faster than all of the network combined. This means this person has the power to refuse certain transactions from the block or even undo their recent transactions by building a longer chain. However, they cannot just forge transactions since that doesn't depend on proof of work. They can only control by outcomputing everyone else. It has always been my childhood dream to take over a distributed and decentralized payment system by taking over all of the world's compute one day.
Birth of BTC
In 2008, Satoshi Nakamoto emailed to a cryptography mailing list, a paper titled Bitcoin: A Peer to Peer Electronic Cash System that birthed the concept of Proof of Work. He was the one who solved the double spend problem in his paper. He also then released the first Bitcoin software. He created documentation, answered forum posts for early adopters and then; disappeared. The public key in his name is believed to hold about 1.1 Million BTC. That's about 80 Billion Dollars of unclaimed money.
The blockchain needs to start somewhere. The first block was mined by Satoshi himself. This was called the Genesis block. It had no previous block in it's header and had only one transaction: the block reward, which was about 50 BTC at the time. This reward halves every 210 thousand blocks. You can even add a transaction fee to your transaction that is given to whoever mines your block. Since 1 BTC is so valuable, it is usually used in Satoshis. 1 BTC is equal to 100,000,000 Satoshis. Since the lowest unit possible is one Satoshi, the block reward turns to 0 after about 114 years. This also means there will only ever be about 21 Million BTC.
The first block also had a headline that said "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" referencing a headline on the UK news and mocking traditional banks that were having a tough time at the time. What a guy.
My work
From here i decided to mimic how Bitcoin works by running nodes as Docker containers that all try to make random transactions once they mine a block and have sufficient money by talking over TCP sockets. I tried to implement ECC from scratch just to learn more about it and this was also an attempt to get more familiar with Java. 10/10. Had fun. Would recommend.
How to run
Linux
bashsudo apt update sudo apt install openjdk-17-jdk docker.io docker-compose-v2 sudo systemctl enable --now docker
macOS
Using Homebrew:
bashbrew install openjdk@17 docker
Then verify:
bashjava -version docker --version docker compose version bash --version curl --version
Windows
You're on your own bro. Good luck with the Path variables.
Run the project
Start the containers:
bashdocker compose up --build
To see the new blocks:
bash./monitor.sh
To add a malicious node:
bash./run_attacker.sh