If you have been a reader of my blog for a while, you would know that I am an avid cryptocurrency enthusiast. I believe in the tech more so than the financial side. I find blockchains fascinating because, despite their perceived complexity, you can implement a blockchain in any programming language; Javascript included.
I thought it would be fun to create a blockchain using TypeScript and then iteratively change the code to offer more flexibility, such as the ability to add metadata into the blocks and query the blocks themselves.
Disclaimer: This is all proof of concept and if you’re planning on using this for actual financial purposes, I would consider making the code better. This is just a bit of fun.
Please also note that the crypto-js
package is being used here to handle the sha256 hashes.
import * as CryptoJS from "crypto-js"; class Block { public index: number; public timestamp: number; public data: string; public jsonData: any; public previousHash: string; public hash: string; constructor(index: number, data: string, jsonData: any, previousHash: string) { this.index = index; this.timestamp = Date.now(); this.data = data; this.jsonData = jsonData; this.previousHash = previousHash; this.hash = this.calculateHash(); } public calculateHash(): string { // Use SHA256 to generate a hash for the current block return CryptoJS.SHA256(this.index + this.timestamp + this.data + JSON.stringify(this.jsonData) + this.previousHash).toString(); } } class Blockchain { public chain: Block[]; constructor() { this.chain = [this.createGenesisBlock()]; } public createGenesisBlock(): Block { // The first block in the blockchain is called the Genesis Block return new Block(0, "Genesis Block", "0"); } public getLatestBlock(): Block { // Returns the last block in the chain return this.chain[this.chain.length - 1]; } public addBlock(newBlock: Block): void { // Assign the hash of the previous block to the new block's previousHash property newBlock.previousHash = this.getLatestBlock().hash; // Calculate the new block's hash newBlock.hash = newBlock.calculateHash(); // Add the new block to the chain this.chain.push(newBlock); } public isChainValid(): boolean { for (let i = 1; i < this.chain.length; i++) { const currentBlock = this.chain[i]; const previousBlock = this.chain[i - 1]; // Check if the current block's hash is still valid if (currentBlock.hash !== currentBlock.calculateHash()) { return false; } // Check if the previousHash of the current block is still valid if (currentBlock.previousHash !== previousBlock.hash) { return false; } } // If all checks pass, the chain is valid return true; } }
We have a simplistic blockchain where the chain and blocks are separate, using classes to clean things up.
And to use our newfound blockchain, here is how we would implement it:
// Import crypto-js library import * as CryptoJS from "crypto-js"; // Create a new blockchain const myBlockchain = new Blockchain(); // Add some blocks to the blockchain myBlockchain.addBlock(new Block(1, "This is block 1", {name: "John", age: 30}, myBlockchain.getLatestBlock().hash)); myBlockchain.addBlock(new Block(2, "This is block 2", {name: "Jane", age: 25}, myBlockchain.getLatestBlock().hash)); myBlockchain.addBlock(new Block(3, "This is block 3", {name: "Bob", age: 35}, myBlockchain.getLatestBlock().hash)); // Check if the blockchain is valid console.log(myBlockchain.isChainValid()); // Output: true
We have a functional blockchain now, but let’s make one more change to make it useful—the ability to get blocks by ID or metadata properties in the block. Inside the Blockchain
class we create a method called getBlock
which can query for blocks in our chain.
import * as CryptoJS from "crypto-js"; class Block { public index: number; public timestamp: number; public data: string; public jsonData: any; public previousHash: string; public hash: string; constructor(index: number, data: string, jsonData: any, previousHash: string) { this.index = index; this.timestamp = Date.now(); this.data = data; this.jsonData = jsonData; this.previousHash = previousHash; this.hash = this.calculateHash(); } public calculateHash(): string { // Use SHA256 to generate a hash for the current block return CryptoJS.SHA256(this.index + this.timestamp + this.data + JSON.stringify(this.jsonData) + this.previousHash).toString(); } } class Blockchain { public chain: Block[]; constructor() { this.chain = [this.createGenesisBlock()]; } public createGenesisBlock(): Block { // The first block in the blockchain is called the Genesis Block return new Block(0, "Genesis Block", "0"); } public getLatestBlock(): Block { // Returns the last block in the chain return this.chain[this.chain.length - 1]; } public addBlock(newBlock: Block): void { // Assign the hash of the previous block to the new block's previousHash property newBlock.previousHash = this.getLatestBlock().hash; // Calculate the new block's hash newBlock.hash = newBlock.calculateHash(); // Add the new block to the chain this.chain.push(newBlock); } public getBlock(searchTerm: string | number, by: 'id' | 'metadata' = 'id'): Block | undefined { if (by === 'id') { const block = this.chain.find((b) => b.index === searchTerm); return block; } else if (by === 'metadata') { // define a function to check if the jsonData of a block contains the searchTerm const checkMetadata = (jsonData: any) => { for (let key in jsonData) { if (jsonData[key] === searchTerm) { return true; } } return false; } // check if jsonData property exists before searching by metadata values const block = this.chain.find((b) => b.jsonData && checkMetadata(b.jsonData)); return block; } } public isChainValid(): boolean { for (let i = 1; i < this.chain.length; i++) { const currentBlock = this.chain[i]; const previousBlock = this.chain[i - 1]; // Check if the current block's hash is still valid if (currentBlock.hash !== currentBlock.calculateHash()) { return false; } // Check if the previousHash of the current block is still valid if (currentBlock.previousHash !== previousBlock.hash) { return false; } } // If all checks pass, the chain is valid return true; } }
This method takes two parameters searchTerm
and by
, where searchTerm is the value you want to search for and by is either ‘id’ or ‘metadata’ to define what you want to search by. By default, it is set to ‘id’, so if you call the method without passing any second parameter, it will search by id.
If by
is ‘id’, it uses the Array.prototype.find
method to search the chain
array for a block with a matching index
property. If it finds a match, it returns the block. Otherwise, it returns undefined
.
If by
is ‘metadata’, it uses the Array.prototype.find
method to search the chain
array for a block with metadata that matches the searchTerm . It uses a helper function checkMetadata that iterates over the jsonData of the block and checks if any of the values match the searchTerm. If it finds a match, it returns the block. Otherwise, it returns undefined
.
You can then use this method to search for blocks by ID or metadata values like this:
console.log(myBlockchain.getBlock(1)); console.log(myBlockchain.getBlock("John", 'metadata'));
Conclusion
As you can see, a blockchain is just a collection of objects with hashes. While this is a rather simplistic implementation of a blockchain, it shows that it’s not as complicated as you would think.
Call me a pessimist but of itself what does this actually give you? The idea of the hashes is to somehow prevent non-tampering, but what’s to stop an entire blockchain being modified and hashes recomputed?
Point is, the underlying data itself still needs to be protected from modification.
This just shows how the concept of a blockchain works, but it doesn’t handle immutable storage or anything else you’d associate with a blockchain. I just thought it would be cool to show what a blockchain looks like in a Web based language.