Creating Blockchain From Scratch

https://www.udemy.com/course/build-blockchain/

Blockchain - Distributed and decentralized ledger that stores data such as transaction and publicly shared across all & the nodes of its network.

Every block is given unique value that looks like random string of characters called hash.

The last block is an integral part of creating the new block hash value. Link from block to block are created because each new block makes a reference to the last block value

Ledger is a record keeping book that stores all the transactions of an organization.

A blockchain ledger is distributed because the ledger itself is shared with everyone using blockchain network

Decentralized - Ex. Everyone records the data, everyone has equal power, fair and transparent system, trustless

Why use blockchain - Decentralization leads to a trustless system, No middle men and no fees, highly secure and no central point of failure, dependable data.

Cryptocurrency

  • digital medium of exchange
  • has 3 features - a secure blockchain, wallets and mining
  • uses cryptography to generate digital signature, has value is generated by a combination of the data and the private key of the individual
  • the public key is used to decrypt that signature and them read the original data

Wallets

  • objects that store the private and public key of an individual
  • the public key is the address of the wallet
  • help sign transactions

Mining

  • Transaction are Temp "Unconfirmed"
  • Include blocks of transaction by "proof of work" algorithm

Proof of work

  • Difficult to solve and computational expensive
  • once solved, miner can add the block and other miners will verify
  • Miners are rewarded
  • difficulty can adjust to control the rate new block

Block

  • timestamp in milisecond
  • lasthash - the hash of the block before it
  • hash - based on its own data
  • the data to store

Genesis Block

Genesis block serves the purpose of being the origin of the blockchain

Block Hashes and SHA-256

  • The hash is generated from the timestamp, lastHash, and stored data.
  • We'll use an algorithm called SHA-256
    - Produces a unique 32-byte (256 bit) hash value for unique data inputs.
    - one-way hash
  • Useful for block validation
#index.js

const Block = require('./block');

class Blockchain {
  constructor() {
    this.chain = [Block.genesis()];
  }

  addBlock(data) {
    const block = Block.mineBlock(this.chain[this.chain.length-1], data);
    this.chain.push(block);
    return block;
  }

  isValidChain(chain) {
    if(JSON.stringify(chain[0]) !== JSON.stringify(Block.genesis())) return false;

    for (let i = 1; i< chain.length; i++) {
      const block = chain[i];
      const lastBlock = chain[i-1];

      if (block.lastHash !== lastBlock.hash || block.hash !== Block.blockHash(block)) {
        return false;
      }
    }
    return true;
  }

  replaceChain(newChain){
    if (newChain.length <= this.chain.length) {
      console.log('Received chain is not longer than the current chain');
      return;
    } else if (!this.isValidChain(newChain)) {
      console.log('The received chain is not valid.');
      return;
    }

    console.log('Replacing blockchain with the new chain.');
    this.chain = newChain;
  }
}

module.exports = Blockchain;
# index.test.js

const Blockchain = require('./index');
const Block = require('./block');

describe('Blockchain', () => {
  let bc, bc2;
  beforeEach(() => {
    bc = new Blockchain();
    bc2 = new Blockchain();
  })

  it ('starts with genesis block', () => {
    expect(bc.chain[0]).toEqual(Block.genesis());
  });

  it('adds a new block', () => {
    const data = 'foo';
    bc.addBlock(data);

    expect(bc.chain[bc.chain.length-1].data).toEqual(data);
  });

  it('validates a valid chain', () => {
    bc2.addBlock('foo');
    expect(bc.isValidChain(bc2.chain)).toBe(true)
  });

  it('invalidates a chain with a corrupt genesis block', () => {
    bc2.chain[0].data = 'Bad data';
    expect(bc.isValidChain(bc2.chain)).toBe(false)
  })

  it('invalidates a corrupt chain', () => {
    bc2.addBlock('foo')
    bc2.chain[1].data = 'Not foo';
    expect(bc.isValidChain(bc2.chain)).toBe(false)
  }) 

  it ('replaces the chain with a valid chain', () => {
    bc2.addBlock('goo');
    bc.replaceChain(bc2.chain);
    expect(bc.chain).toEqual(bc2.chain);
  })

  it ('does not relace the chain with one of less than or equal to length', () => {
    bc.addBlock('foo');
    bc.replaceChain(bc2.chain);
    expect(bc.chain).not.toEqual(bc2.chain);
  }); 
});
# blockchain/index.js

const SHA256 = require('crypto-js/sha256')

class Block {
  constructor(timestamp, lastHash, hash, data) {
    this.timestamp = timestamp;
    this.lastHash = lastHash;
    this.hash = hash;
    this.data = data;
  }

  toString() {
    return `Block - 
      Timestamp : ${this.timestamp}
      Last Hash : ${this.lastHash.substring(1,10)}
      Hash      : ${this.hash.substring(0,10)}
      Data      : ${this.data}`
  }

  static genesis() {
    return new this('Genesis time', '-----', 'f1r57-h45h', [])
  }

  static mineBlock(lastBlock, data) {
    const timestamp = Date.now();
    const lastHash = lastBlock.hash;
    const hash = Block.hash(timestamp, lastHash, data);

    return new this(timestamp, lastHash, hash, data);
  }

  static hash(timestamp, lastHash, data) {
    return SHA256(`${timestamp}${lastHash}${data}`).toString();
  }

  static blockHash(block) {
    const { timestamp, lastHash, data } = block;
    return Block.hash(timestamp, lastHash, data);
  }
}

module.exports = Block;
# blockchain/index.test.js
const Block = require('./block');

describe('Block', () => {
  let data, lastBlock, block;
  beforeEach(() => {
    data = 'bar';
    lastBlock = Block.genesis();
    block = Block.mineBlock(lastBlock, data);
  })
  it('sets the `data` to match the input', () => {
    expect(block.data).toEqual(data);
  });

  it('sets the `lastHash` to match the hash of the lineBlock', () => {
    expect(block.lastHash).toEqual(lastBlock.hash)
  })
})

Blockchain API

npm i express --save
npm i body-parser --save
# app/index.js

const express = require('express');
const bodyParser = require('body-parser');
const Blockchain = require('../blockchain');
const P2pServer = require('./p2p-server');


const HTTP_PORT = process.env.HTTP_PORT || 3001

const app = express();
const bc = new Blockchain();
const p2pServer = new P2pServer(bc);

app.use(bodyParser.json());

app.get('/blocks', (req, res) => {
  res.json(bc.chain);
});

app.post('/mine', (req, res) => {
  const block = bc.addBlock(req.body.data);
  console.log(`New block added: ${block.toString()}`);
  p2pServer.syncChains();
  res.redirect('/blocks');
});

app.listen(HTTP_PORT, () => {
  console.log(`Listening on port ${HTTP_PORT}`);
});
p2pServer.listen();

Peer to Peer server

npm i ws --save
# app/p2p-server.js

const Websocket = require('ws');

const P2P_PORT = process.env.P2P_PORT ||5001;

const peers = process.env.PEERS ? process.env.PEERS.split(',') : [];

class P2pServer {
  constructor(blockchain) {
    this.blockchain = blockchain;
    this.sockets = [];
  }

  listen() {
    const server = new Websocket.Server({ port: P2P_PORT });
    server.on('connection', socket => this.connectSocket(socket));
    this.connectToPeers();
    console.log(`Listening for peer-to-peer connections on: ${P2P_PORT}`);

  }

  connectToPeers() {
    peers.forEach(peer => {
      const socket = new Websocket(peer);
      socket.on('open', () => this.connectSocket(socket));
    })
  }

  connectSocket(socket) {
    this.sockets.push(socket);
    console.log('Socket connected');

    this.messageHandler(socket);
    this.sendChain(socket);
  }

  messageHandler(socket) {
    socket.on('message', message => {
      const data = JSON.parse(message);
      this.blockchain.replaceChain(data);
    })
  }

  sendChain(socket) {
    socket.send(JSON.stringify(this.blockchain.chain));
  }

  syncChains() {
    this.sockets.forEach(socket => this.sendChain(socket));
  }
}

module.exports = P2pServer;
# add to index.js

const P2pServer = require('./p2p-server');
const p2pServer = new P2pServer(bc);
p2pServer.listen();

Proof of Work System

  • A system that requires miners to do computational work to add blocks.
  • Any peer can replace the blockchain.
  • The proof-of-work makes it expensive to generate corrupt chains
  • Manageable to submit one block, unproductive to generate an entire chain.
  • Hashcash was proof of work system to prevent email spamming.
    ex : difficulty = 6, Hash 000000haxi2910jasdflk
  • Generate hashes until a one with the mathing leading 0's is found.
  • A "nonce" value adjust in order to generate new hash
  • This computational work is "mining".
  • The difficulty sets a rate of mining.
  • Bitcoin sets the rate to new block around every 10 minutes.

51% Attack

  • A dishonest miner has more than at least 51% of the network's power.
  • a 51% attack for bitcoin would be more $6 billion (start of 2018)

Adding Difficulty

# config.js

const DIFFICULTY = 4;
module.exports = { DIFFICULTY };
# block.test.js

it('generates a hash that matches the difficulty', () => {
    expect(block.hash.substring(0, DIFFICULTY)).toEqual('0'.repeat(DIFFICULTY));
    console.log(block.toString());
  });
# block.js
const { DIFFICULTY } = require('../config');

  constructor(timestamp, lastHash, hash, data, nonce) {
	...
    this.nonce = nonce;
  }

toString() {
    return `Block - 
	  ...
      Nonce     : ${this.nonce}`
  }

static mineBlock(lastBlock, data) {
    let hash, timestamp;
    const lastHash = lastBlock.hash;
    let nonce = 0;

    do {
      nonce++;
      timestamp = Date.now();
      hash = Block.hash(timestamp, lastHash, data, nonce);
    } while(hash.substring(0, DIFFICULTY) !== '0'.repeat(DIFFICULTY));

    return new this(timestamp, lastHash, hash, data, nonce);
  }

  static hash(timestamp, lastHash, data, nonce) {
    return SHA256(`${timestamp}${lastHash}${data}${nonce}`).toString();
  }

  static blockHash(block) {
    const { timestamp, lastHash, data, nonce } = block;
    return Block.hash(timestamp, lastHash, data, nonce);
  }

Dynamic Block Difficulty

# config.js

const MINE_RATE = 3000;
# block.js

const { DIFFICULTY, MINE_RATE } = require('../config');


  constructor(timestamp, lastHash, hash, data, nonce, difficulty) {
	...
    this.difficulty = difficulty || DIFFICULTY;
  }

  toString() {
    return `Block - 
	  ...
      Difficulty: ${this.difficulty}`
  }

static genesis() {
    return new this('Genesis time', '-----', 'f1r57-h45h', [],0, DIFFICULTY)
  }

static mineBlock(lastBlock, data) {
	...
    let { difficulty } = lastBlock;

    do {
	  ...
      difficulty = Block.adjustDifficulty(lastBlock, timestamp);
      hash = Block.hash(timestamp, lastHash, data, nonce, difficulty);
    } while(hash.substring(0, difficulty) !== '0'.repeat(difficulty));

    return new this(timestamp, lastHash, hash, data, nonce, difficulty);
  }

static hash(timestamp, lastHash, data, nonce, difficulty) {
    return SHA256(`${timestamp}${lastHash}${data}${nonce}${difficulty}`).toString();
  }

  static blockHash(block) {
    const { timestamp, lastHash, data, nonce } = block;
    return Block.hash(timestamp, lastHash, data, nonce, difficulty);
  }

  static adjustDifficulty(lastBlock, currentTime) {
    let { difficulty } = lastBlock;
    difficulty = lastBlock.timestamp + MINE_RATE > currentTime ? difficulty + 1 : difficulty - 1;
    return difficulty;
  }

What is a wallet?

  • Wallets store the balance of an individual.
  • They store an individual's keys

Transactions

Digital Signatures

Blockchain-powered Cryptocurrencies

  • Contain wallet objects.
  • Keys for digital signatures and verification.
  • Have transactions objects to represent current exchange.

Wallets

$ npm i elliptic --save
# chain-util.js

const EC = require('elliptic').ec;
const ec = new EC('secp256k1');

class ChainUtil {
  static genKeyPair() {
    return ec.genKeyPair();
  }
}

module.exports = ChainUtil;
# wallet/index.js

const ChainUtil = require('../chain-util');
const { INITIAL_BALANCE } = require('../config');
class Wallet {
  constructor() {
    this.balance = INITIAL_BALANCE;
    this.keyPair = ChainUtil.genKeyPair();
    this.publicKey = this.keyPair.getPublic().encode('hex');
  }

  toString() {
    `Wallet - 
      publicKey : ${this.publicKey.toString()}
      balance   : ${this.balance}
    `
  }
}

module.exports = Wallet;
# config.js

const INITIAL_BALANCE = 500;
module.exports = { ... INITIAL_BALANCE };

Transaction

$ npm i uuid --save
# update chain-util.js
const uuidV1 = require('uuid/v1');
const SHA256 = require('crypto-js/sha256')

  static id() {
    return uuidV1();
  }

  static hash(data) {
    return SHA256(JSON.stringify(data)).toString();
  }
# wallet/transaction.js

const ChainUtil = require('../chain-util');

class Transaction {
  constructor() {
    this.id = ChainUtil.id();
    this.input = null;
    this.outputs = [];
  }

  static newTransaction(senderWallet, recipient, amount) {
    const transaction = new this();
    if (amount > senderWallet.balance) {
      console.log(`Amount: ${amount} exceeds balance.`);
      return;
    }

    transaction.outputs.push(...[
      { amount: senderWallet.balance - amount, address: senderWallet.publicKey },
      { amount, address: recipient}
    ]);

    return transaction;
  }
}

module.exports = Transaction;

Modify which uses SHA256

# chain-util.js

const SHA256 = require('crypto-js/sha256')


  static hash(data) {
    return SHA256(JSON.stringify(data)).toString();
  }

  static verifySignature(publicKey, signature, dataHash) {
    return ec.keyFromPublic(publicKey, 'hex').verify(dataHash, signature);
  }
# wallet/index.js

const ChainUtil = require('../chain-util');

  constructor() {
	...
    this.keyPair = ChainUtil.genKeyPair();

  sign(dataHash) {
    return this.keyPair.sign(dataHash);
  }
# wallet/transaction.js

const ChainUtil = require('../chain-util');


  static signTransaction(transaction, senderWallet) {
    transaction.input = {
		...
      signature: senderWallet.sign(ChainUtil.hash(transaction.outputs))
    }
  }


  update(senderWallet, recipient, amount) {
    const senderOutput = this.outputs.find(output => output.address === senderWallet.publicKey);
    if (amount > senderOutput.amount) {
      console.log(`Amount : ${amount} exceeds balance.`);
      return;
    }

    senderOutput.amount = senderOutput.amount - amount;
    this.outputs.push({amount, address: recipient});
    Transaction.signTransaction(this, senderWallet);

    return this;
  }


  static verifyTransaction(transaction) {
    return ChainUtil.verifySignature(
      transaction.input.address,
      transaction.input.signature,
      ChainUtil.hash(transaction.outputs)
    );
  }

Transaction pool

  • an object that contains all new transactions submitted by individuals
# wallet/transaction-pool.js

class TransactionPool {
  constructor() {
    this.transactions = [];
  }

  updateOrAddTransaction(transaction) {
    let transactionWithId = this.transactions.find(t => t.id === transaction.id);

    if (transactionWithId) {
      this.transactions[this.transactions.indexOf(transactionWithId)] = transaction;
    } else {
      this.transactions.push(transaction);
    }
  }
}

module.exports = TransactionPool;
# wallet/index.js
  

const Transaction = require('./transaction');

createTransaction(recipient, amount, transactionPool) {
    if (amount > this.balance) {
      console.log(`Amount: ${amount} exceeds current balance: ${this.balance}`);
      return;
    }

    let transaction = transactionPool.existingTransaction(this.publicKey);

    if (transaction) {
      transaction.update(this, recipient, amount);
    } else {
      transaction = Transaction.newTransaction(this, recipient, amount);
      transactionPool.updateOrAddTransaction(transaction);
    }
    return transaction;
  }

Get & Post Transaction p2p

# app/index.js

const Wallet = require('../wallet');
const TransactionPool = require('../wallet/transaction-pool');
const wallet = new Wallet();
const tp = new TransactionPool();

app.get('/transactions', (req, res) => {
  res.json(tp.transactions);
});

app.post('/transact', (req,res) => {
  const { recipient, amount } = req.body;
  const transaction = wallet.createTransaction(recipient, amount, tp);
  p2pServer.broadcastTransaction(transaction);
  res.redirect('/transactions');
});
# p2p-server.js

messageHandler(socket) {
    socket.on('message', message => {
      const data = JSON.parse(message);
      switch(data.type) {
        case MESSAGE_TYPES.chain:
          this.blockchain.replaceChain(data.chain);
          break;
        case MESSAGE_TYPES.transaction:
          this.transactionPool.updateOrAddTransaction(data.transaction);
          break;
      }
    })
  }

  sendChain(socket) {
    socket.send(JSON.stringify( { 
      type: MESSAGE_TYPES.chain, 
      chain: this.blockchain.chain 
    }));
  }

  sendTransaction(socket, transaction) {
    socket.send(JSON.stringify( { 
      type: MESSAGE_TYPES.transaction, 
      transaction 
    }));
  }

Miners of Transactions

  • Miner take transactions from the pool and store them into blocks.
  • Miners receive rewards for mining.
  • Transactions go from "unconfirmed" in the pool to "confirmed" in the chain.
# app/miner.js

class Miner {
  constructor(blockchain, transactionPool, wallet, p2pServer) {
    this.blockchain = blockchain;
    this.transactionPool = transactionPool;
    this.wallet = wallet;
    this.p2pServer = p2pServer;
  }

  mine() {
    const validTransactions = this.transactionPool.validTransactions();
    // include a reward for the miner
    // create a block consisting of the valid transactions
    // synchronize chains in the peer to peer server
    // clear the transaction pool
    // broadcast to every miner to clear their transaction pools
  }
}

module.exports = Miner;
# wallet/transaction-pool.js

  validTransactions() {
    return this.transactions.filter(transaction => {
      const outputTotal = transaction.outputs.reduce((total, output) => {
        return total + output.amount;
      }, 0);

      if (transaction.input.amount != outputTotal) {
        console.log(`Invalid transaction from ${transaction.input.address}`);
        return;
      }

      if (!Transaction.verifyTransaction(transaction)) {
        console.log(`Invalid signature from ${transaction.input.address}.`);
        return;
      }
      return transaction;

    });

    
  }

Subscribe to You Live What You Learn

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]
Subscribe