Writing a blockchain in 60 readable lines of TypeScript
Tags: blockchain, typescript, javascript, tutorial
Published: 2021-03-29 23:59:35

Blockchain: it gets more confusing every year because people explain it with weird metaphors instead of talking 5mn about the actual maths & code involved

{% twitter 1030225104234373121 %}

And it frustrate me a lot because the concept of a blockchain is actually pretty simple:

- a blockchain is a list of blocks
- a block is
  - some data
  - the hash of the previous block
  - some token
  - the hash of the above

A block is valid if the hash has a magic prefix, which is when it begins by a certain number of zeros, so if you want to add some data to the blockchain, you'll have to pack it into a block, and to do so you'll have to find a token that produces a block hash that begins by a certain number of zeros, which is computationally intensive, and that's how you get [proof of work](https://en.wikipedia.org/wiki/Proof_of_work).

Ok to be honest, I'm just talking about a particular type of blockchain there, but that's not the point. Coding the following blockchain helped me understand some crypto basics and I hope that'll help you too. I am *not* bragging about how clever I am to code the same tech as Bitcoin in 60 lines or whatever.

# Anyway here's the code

I am using Deno and TypeScript.

Let's start by importing some hasher we'll need later on
```ts
import { createHash } from "https://deno.land/std@0.91.0/hash/mod.ts";
```

Let's define blocks and agents. An agent represents the kind of programs running on your computer that can trade Bitcoins with other agents around the world.
```ts
type Block = {
  data: string;
  prev: string;
  token: string;
  hash: string;
};
type Agent = {
  addAgent(agent: Agent): void;
  receiveBlock(block: Block): void;
  addData(data: Block["data"]): void;
};
```

We're gonna use md5 as our hash function. It's definitely not the most secure one but we don't really care. Let's define our magic prefix `00000` there too so we don't repeat it afterward. The more `0` there are, the more difficult it is to mine a new block.
```ts
const hashOf = (str: string) => createHash("md5")
  .update(str)
  .toString();
const magicPrefix = Array(5).fill("0").join("");
```

Now let's create our agent factory. Internally, it keeps the whole chain in memory, and a list of all agents it needs to broadcast to when mining a new block.

The first block is the "Genesis Block" which is exempted from pointing to the previous block's hash, or having a magic prefix.

```ts
const createAgent = (): Agent => {
  const chain: Block[] = [{
    data: "",
    prev: "",
    token: "",
    hash: hashOf(""),
  }];
  const agents: Agent[] = [];
  return {
    addAgent(agent) { /* ... */ },
    addData(data) { /* ... */ },
    receiveBlock(block) { /* ... */ },
  };
};
```

The `addAgent` method does not need further explanations:

```ts
addAgent(agent) {
  agents.push(agent);
},
```

`addData` is basically where the mining happens. It's the computationally intensive loop to find the token that'll produce a block hash with a magic prefix.

I chose `hashOf(Math.random().toString())` to generate a random string because that's a very concise way to do so but hashing is not needed there.

```ts
addData(data) {
  while (true) {
    const prev = chain[chain.length - 1].hash;
    const token = hashOf(Math.random().toString());
    const hash = hashOf(data + prev + token);
    if (hash.startsWith(magicPrefix)) {
      const block: Block = { data, prev, token, hash };
      chain.push(block);
      for (const agent of agents) {
        agent.receiveBlock(block);
      }
      return;
    }
  }
},
```

`receiveBlock` validates if a new block can be added on top of the chain according to the conditions above, and if everything is ok, adds it, else throws.

```ts
receiveBlock(block) {
  if (block.prev != chain[chain.length - 1].hash) {
    throw new Error(
      "Hash does not point to the previous hash in the chain",
    );
  }
  if (!block.hash.startsWith(magicPrefix)) {
    throw new Error("Hash does not start with the magic prefix");
  }
  const actualHash = hashOf(block.data + block.prev + block.token);
  if (actualHash !== block.hash) {
    throw new Error("Hash is not the hash of data|prev|token");
  }
  chain.push(block);
},
```

And... that's it!

You can run "simulations" with such agents, for example this one where two agents add greeting blocks. It should run without printing anything and without throwing:

```ts
const alice = createAgent();
const bob = createAgent();
alice.addAgent(bob);
bob.addAgent(alice);
alice.addData("Hello Bob! -Alice");
bob.addData("Hello Alice! -Bob");
```

or this "simulation" where we try to inject a malevolent block into the blockchain but it gets caught:

```ts
const alice = createAgent();
const data = "bad things";
const prev = hashOf("");
alice.receiveBlock({
  data,
  prev,
  token: "",
  hash: hashOf(data + prev),
});
// error: Uncaught Error: Hash does not start with the magic prefix
```

# So... that's it?

Well, yes, at the core, there's not a lot more to blockchain than this. But in order to go from there to building a real cryptocurrency, you'd probably have to

- replace the string data payload by signed and timestamped transactions
- find ways to compensate miners for their work
- build the p2p network layer
- convince people that this cryptocurrency has value
- etc

**TL;DR it's usually not the blockchain part that is hard but rather what's around it**