Skip to content

Proof Of Work (PoW)

Using Deno v1

proofOfWork.ts

ts
import { createHash } from "https://deno.land/std/hash/mod.ts";

interface Transaction {
  from: string;
  to: string;
  value: number;
}

/**
 * Generate SHA-256
 * @param {string} message
 * @returns {string}
 */
const generateHash = (message: string): string => {
  return createHash("sha256").update(message).toString();
};

/**
 * Generate UUID V4
 * @returns {string}
 */
const generateUuid = () => {
  const PATTERN = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";

  return PATTERN.replace(/[xy]/g, (char: string) => {
    const random = (Math.random() * 16) | 0,
      idx = char === "x" ? random : (random & 0x3) | 0x8;
    return idx.toString(16);
  });
};

class Block {
  timeStamp: string;
  transactions: Transaction[];
  prevHash: string;
  hash: string;
  nonce: number;

  constructor(timeStamp = "", transactions: Transaction[] = []) {
    this.timeStamp = timeStamp;
    this.transactions = transactions;
    this.prevHash = "";
    this.hash = this.getHash();
    this.nonce = 0;
  }

  getHash(): string {
    return generateHash(this.timeStamp + JSON.stringify(this.transactions) + this.prevHash + this.nonce);
  }

  toString(): string {
    return `[BLOCK]
    TimeStamp:     ${this.timeStamp}
    Transactions:  ${JSON.stringify(this.transactions)}
    Previous Hash: ${this.prevHash}
    Hash:          ${this.hash}
    Nonce:         ${this.nonce}
    `;
  }

  proofOfWork(difficulty: number) {
    while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
      this.nonce++;
      this.hash = this.getHash();
    }
  }
}

class BlockChain {
  chain: Block[];
  difficulty: number;

  constructor() {
    this.chain = [this.getGenesisBlock()];
    this.difficulty = 2;
  }

  getGenesisBlock(): Block {
    const now = new Date().getTime().toString();
    return new Block(now);
  }

  getLastBlock(): Block {
    return this.chain[this.chain.length - 1];
  }

  addNewBlock(newBlock: Block) {
    newBlock.prevHash = this.getLastBlock().hash;
    // newBlock.hash = newBlock.getHash();
    newBlock.proofOfWork(this.difficulty);

    // I M M U T A B I L I T Y
    const frozenBlock = Object.freeze(newBlock);
    this.chain.push(frozenBlock);
  }

  isValid(): boolean {
    for (let i = 1; i < this.chain.length; i++) {
      const currentBlock = this.chain[i];
      const prevBlock = this.chain[i - 1];

      if (currentBlock.hash !== currentBlock.getHash() || prevBlock.hash !== currentBlock.prevHash) {
        return false;
      }
    }

    return true;
  }
}

proofOfWork.test.ts

deno test proofOfWork.test.ts

ts
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";

Deno.test("BlockChain", () => {
  const myChain = new BlockChain();

  const stAddress = generateUuid();
  const ndAddress = generateUuid();

  const now = new Date();

  const stBlock = new Block(now.getTime().toString(), [
    {
      from: stAddress,
      to: ndAddress,
      value: 20
    },
    {
      from: stAddress,
      to: ndAddress,
      value: 10
    }
  ]);

  myChain.addNewBlock(stBlock);

  assertEquals(myChain.getLastBlock().hash, stBlock.hash);

  const ndBlock = new Block(now.setMinutes(now.getMinutes() + 30).toString(), [
    {
      from: ndAddress,
      to: stAddress,
      value: 5
    }
  ]);

  myChain.addNewBlock(ndBlock);

  assertEquals(myChain.getLastBlock().hash, ndBlock.hash);

  const rdBlock = new Block(now.setHours(now.getHours() + 12).toString(), [
    {
      from: ndAddress,
      to: stAddress,
      value: 15
    }
  ]);

  myChain.addNewBlock(rdBlock);

  assertEquals(myChain.getLastBlock().hash, rdBlock.hash);

  assertEquals(myChain.isValid(), true);
});

Released under the MIT License.