Desenvolvimento

21 fev, 2018

Como implementar o Blockchain em JavaScript

Publicidade

Ouvimos falar sobre Bitcoin, Ethereum e outras moedas praticamente todos os dias. 2017, afinal, foi o ano das cryptocurrencies. Neste artigo, porém, não vou focar em nenhuma dessas moedas digitais, mas sim na tecnologia por trás delas, que muitas pessoas dizem ser tão revolucionárias quanto a própria internet, o Blockchain.

A ideia é implementar o passo a passo de uma versão simplificada do Blockchain em JavaScript e ir explicando como essa tecnologia disruptiva funciona por baixo dos panos.

Então, continue lendo este artigo para aprender:

  • O que é e como funciona o Blockchain
  • Proof of Work?
  • Para quê servem os Blocos
  • O que é mineração

Introdução

O Blockchain parece uma tecnologia de outro mundo e gera muitas dúvidas. Apesar disso, é algo extremamente fácil de definir:

O Blockchain nada mais é do que um banco de dados aberto e distribuído.

Esse banco de dados é composto por Blocos. E todos esses Blocos são ligados entre si em uma sequência. Daí o nome Blockchain (cadeia de blocos).

Além disso, esse banco de dados é imutável, e faz sentido que seja.

Imagina se fosse possível que alguém, intencionalmente. modificasse sua conta. Agora os 10 Bitcoins que você possui, viraram um.

Blocos

Vamos começar a implementação pela parte mais fácil: os Blocos. A estrutura de um Bloco deve possuir os seguintes campos:

  • Index
  • Timestamp
  • Hash
  • PreviousHash
  • Data
Crédito da imagem

O index e o timestamp são campos comuns em praticamente todos os bancos de dados. O campo data serve principalmente pra guardar transações, mas podemos também colocar outras informações. O hash é calculado internamente e serve para manter a integridade de cada Bloco e a segurança de todo o sistema (como veremos no final do artigo). E, por fim, o previousHash, elo de ligação que liga cada Bloco ao seu Bloco anterior.

Com isso, temos a primeira implementação de um Bloco:

const sha256 = require('crypto-js/sha256')
class Block {
    constructor(index = 0, previousHash = null, data = 'Genesis block') {
        this.index = index
        this.previousHash = previousHash
        this.data = data
        this.timestamp = new Date()
        this.hash = this.generateHash()
    }
    generateHash() {
        return sha256(this.index + this.previousHash + JSON.stringify(this.data) + this.timestamp).toString()
    }
}
module.exports = Block

A função generateHash usa a biblioteca externa crypto-js pra gerar o hash seguindo o padrão sha256.

Parece complicado, mas tudo o que você precisa saber é que essa função vai receber uma string, como por exemplo:

foo

E vai retornar uma string encriptada:

2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae

Blockchain

Agora que já temos uma versão mínima de um Bloco, já podemos começar a construir o Blockchain, que – como falei anteriormente – é uma sequência de Blocos ligados entre si.

Com isso, essa é a primeira versão do Blockchain:

const Block = require('./block')

class Blockchain {
    constructor() {
        this.blocks = [new Block()]
        this.index = 1
    }

    getLastBlock() {
        return this.blocks[this.blocks.length - 1]
    }

    addBlock(data) {
        const index = this.index
        const previousHash = this.getLastBlock().hash

        const block = new Block(index, previousHash, data)

        this.index++
        this.blocks.push(block)
    }
}

module.exports = Blockchain

No construtor da classe, temos o array de Blocos inicializado já com o Genesis Block (o primeiro Bloco criado no registro do Bitcoin). Adicionei também o index para poder incrementar toda vez que um novo Bloco for adicionado no Blockchain.

Além disso, duas funções foram criadas:

  • getLastBlock
  • addBlock

A primeira é extremamente simples, ela serve para pegar o último Bloco que foi criado. A segunda é uma um pouco mais complicada, mas também não é nada de outro mundo. Ela serve para adicionar novos Blocos ao Blockchain.

Integridade

Apesar de a versão anterior já funcionar razoavelmente bem, precisamos adicionar alguma garantia de que o Blockchain não tenha sido alterado por algum ataque malicioso.

Precisamos adicionar uma nova função para checar a integridade do Blockchain:

isValid() {
    for (let i = 1; i < this.blocks.length; i++) {
        const currentBlock = this.blocks[i]
        const previousBlock = this.blocks[i - 1]

        if (currentBlock.hash !== currentBlock.generateHash()) {
            return false
        }

        if (currentBlock.index !== previousBlock.index + 1) {
            return false
        }

        if (currentBlock.previousHash !== previousBlock.hash) {
            return false
        }
    }
    return true
}

Lembrando que para verificarmos a integridade do Blockchain, precisamos garantir três características:

  • Se o hash de cada Bloco foi gerado corretamente
  • Se o index dos Blocos está em sequência
  • Se os Blocos estão ligados entre si através dos hashes

Com essa simples função podemos testar se as modificações maliciosas foram feitas e se o Blockchain deve ser invalidado:

const Blockchain = require('./blockchain')

const blockchain = new Blockchain()
blockchain.addBlock({ amount: 4 })
blockchain.addBlock({ amount: 50 })

console.log(blockchain.isValid()) // true
blockchain.blocks[1].data.amount = 30000 // ataque malicioso
console.log(blockchain.isValid()) // false

E finalmente, com isso já temos uma primeira versão básica e funcional do Blockchain em JavaScript:

const Block = require('./block')

class Blockchain {
    constructor() {
        this.blocks = [new Block()]
        this.index = 1
    }

    getLastBlock() {
        return this.blocks[this.blocks.length - 1]
    }

    addBlock(data) {
        const index = this.index
        const previousHash = this.getLastBlock().hash

        const block = new Block(index, previousHash, data)

        this.index++
        this.blocks.push(block)
    }

    isValid() {
        for (let i = 1; i < this.blocks.length; i++) {
            const currentBlock = this.blocks[i]
            const previousBlock = this.blocks[i - 1]

            if (currentBlock.hash !== currentBlock.generateHash()) {
                return false
            }

            if (currentBlock.index !== previousBlock.index + 1) {
                return false
            }

            if (currentBlock.previousHash !== previousBlock.hash) {
                return false
            }
        }
        return true
    }
}

module.exports = Blockchain

Problemas

Apesar de já termos uma versão inicial funcionando, ainda podemos melhorar bastante a nossa implementação.

Um dos problemas com essa versão atual é que usuários podem criar novos Blocos de forma muito rápida, podendo invalidar o Blockchain, ou coisas ainda piores. Não vou entrar em muitos detalhes dessa particularidade da tecnologia, mas esse artigo faz um excelente trabalho nesse sentido.

Em resumo, precisamos de alguma ferramenta que não permita que os usuários possam criar Blocos desenfreadamente.

Proof of Work

O conceito de Proof of Work, como o próprio nome já sugere, é um mecanismo que faz com que um usuário tenha um trabalho computacional significativo ao realizar determinada tarefa.

Essa ferramenta já era utilizada antes do Bitcoin ser inventado para evitar, por exemplo, spams e ataques DoS.

No Bitcoin, o Proof of Work funciona da seguinte forma: o hash que é gerado automaticamente em cada Bloco deve começar com uma quantidade X de zeros, dependendo da dificuldade geral do sistema.

Por exemplo, se a dificuldade geral do sistema for 1, esse hash é inválido porque não começa com um zero:

a5036427617139d3ad9bf650d74ae43710e36d4f63829b92b807da37c5d38e8d

Porém, esse outro hash é válido porque começa com um zero:

07da8bff6cfea68a3f0a5bafc9b24d07f503e2282db36ffb58d43f9f4857c54b

Sempre que um usuário for criar um novo Bloco, ele vai precisar criar diversos hashes, até que um deles tenha a quantidade de zeros no começo fazendo com que a regra geral do sistema seja atendida.

Ah, o nome disso é Mineração.

Lembrando que, quanto maior o número de zeros que devem estar no começo do hash, maior o poder computacional necessário para a tarefa.

Dito isso, vamos agora implementar a versão final do Blockchain com a Mineração.

Primeiramente, vamos alterar os Blocos.

class Block {
    constructor(index = 0, previousHash = null, data = 'Genesis block', difficulty = 1) {
        this.index = index
        this.previousHash = previousHash
        this.data = data
        this.timestamp = new Date()
        this.difficulty = difficulty
        this.nonce = 0
        
        this.mine()
    }
    
    /* */
}

No construtor, adicionamos os campos difficulty (dificuldade geral do sistema) e nonce (quantidade de tentativas até que o hash correto seja criado). Além disso, temos também uma chamada para a função mine.

mine() {
    this.hash = this.generateHash()

    while (!(/^0*$/.test(this.hash.substring(0, this.difficulty)))) {
        this.nonce++
        this.hash = this.generateHash()
    }
}

A função mine vai criar hashes até que a quantidade de zeros à esquerda do hash seja atentida.

Lembrando que, para que os hashes criados sejam diferentes, devemos adicionar o campo nonce na função generateHash:

generateHash() {
    return sha256(this.index + this.previousHash + JSON.stringify(this.data) + this.timestamp + this.nonce).toString()
}

Com isso, temos a versão final dos Blocos com a mineração:

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

class Block {
    constructor(index = 0, previousHash = null, data = 'Genesis block', difficulty = 1) {
        this.index = index
        this.previousHash = previousHash
        this.data = data
        this.timestamp = new Date()
        this.difficulty = difficulty
        this.nonce = 0
        
        this.mine()
    }

    generateHash() {
        return sha256(this.index + this.previousHash + JSON.stringify(this.data) + this.timestamp + this.nonce).toString()
    }

    mine() {
        this.hash = this.generateHash()

        while (!(/^0*$/.test(this.hash.substring(0, this.difficulty)))) {
            this.nonce++
            this.hash = this.generateHash()
        }
    }
}

module.exports = Block

Agora basta alterarmos o Blockchain para que o campo difficulty seja passado para os Blocos:

const Block = require('./block')

class Blockchain {
    constructor(difficulty = 1) {
        this.blocks = [new Block()]
        this.index = 1
        this.difficulty = difficulty
    }

    getLastBlock() {
        return this.blocks[this.blocks.length - 1]
    }

    addBlock(data) {
        const index = this.index
        const difficulty = this.difficulty
        const previousHash = this.getLastBlock().hash

        const block = new Block(index, previousHash, data, difficulty)

        this.index++
        this.blocks.push(block)
    }

    isValid() {
        for (let i = 1; i < this.blocks.length; i++) {
            const currentBlock = this.blocks[i]
            const previousBlock = this.blocks[i - 1]

            if (currentBlock.hash !== currentBlock.generateHash()) {
                return false
            }

            if (currentBlock.index !== previousBlock.index + 1) {
                return false
            }

            if (currentBlock.previousHash !== previousBlock.hash) {
                return false
            }
        }
        return true
    }
}

module.exports = Blockchain

E é só isso. Lembrando que todo o código está no GitHub.

Ah! E se você quiser saber mais sobre o Blockchain, ouça esse Podcast.

Outras implementações:

***

Este artigo foi publicado originalmente em: https://www.concrete.com.br/2018/02/07/como-implementar-o-blockchain-em-javascript/