Passo a passo: Criando um blockchain em Java!

 


Implementando uma simples Blockchain em Java

Neste tutorial, aprenderemos os conceitos básicos da tecnologia blockchain. Também implementaremos uma aplicação básica em Java que utilize  tais conceitos.

Mas o que é blockchain?

Bem, ele remonta à sua origem até o whitepaper publicado por Satoshi Nakamoto sobre o Bitcoin, em 2008.

Blockchain é um livro de informações descentralizado. Consiste em blocos de dados conectados através do uso de criptografia. Pertence a um conjunto de computadores conectados pela rede distribuída. Vamos entender melhor isso quando implementarmos o código.

Existem algumas características importantes que devemos entender, então vamos descrever cada uma delas:

  • À prova de adulteração: Em primeiro lugar, os dados como parte de um bloco são à prova de adulteração. Cada bloco é referenciado por um digestor criptográfico, comumente conhecido como hash, tornando o bloco à prova de adulteração.
  • Descentralizado: toda a blockchain é completamente descentralizada em toda a rede. Isso significa que não há nó mestre, e cada nó na rede tem a mesma cópia dos dados completos da blockchain.
  • Transparente: Cada nó participante da rede valida e adiciona um novo bloco à sua cadeia através de consenso com outros nódulos. Portanto, cada nó tem visibilidade completa dos dados.

Como o Blockchain funciona?

Agora, vamos entender como o blockchain funciona.

As unidades fundamentais de uma blockchain são blocos. Um único bloco pode encapsular várias transações ou outros dados valiosos.



Mineração de um Bloco

Representamos um bloco por um valor hash. Gerar o valor hash de um bloco é chamado de "mineração" do bloco. Minerar um bloco é tipicamente computacionalmente caro de se fazer, pois serve como a "prova de trabalho".

O hash de um bloco normalmente consiste nos seguintes dados:

  • Principalmente, o hash de um bloco consiste nas transações que encapsula
  • O hash também consiste no horário da criação do bloco
  • Também inclui um nonce, um número arbitrário usado na criptografia
  • Finalmente, o hash do bloco atual também inclui o hash do bloco anterior

Vários nós na rede podem competir para minerar o bloco ao mesmo tempo. Além de gerar o hash, os nós também têm que verificar se as transações que estão sendo adicionadas no bloco são legítimas. O primeiro a minar um bloco ganha a corrida!

Adicionando um bloco em blockchain

Embora a mineração de um bloco seja computacionalmente cara, verificar se um bloco é legítimo é relativamente muito mais fácil. Todos os nós da rede participam na verificação de um bloco recém-minerado.

Assim, um bloco recém-minerado é adicionado ao blockchain sobre o consenso dos nódulos.



Agora, existem vários protocolos de consenso disponíveis que podemos usar para verificação. Os nódulos na rede usam o mesmo protocolo para detectar ramificações maliciosas da cadeia. Assim, um ramo malicioso, mesmo que introduzido, em breve será rejeitado pela maioria dos nódulos.

Blockchain básico em Java

Agora temos contexto suficiente para começar a construir uma aplicação básica em Java.
Nosso simples exemplo aqui ilustrará os conceitos básicos que acabamos de ver. Um aplicativo de qualidade de produção implica um monte de considerações que estão além do escopo deste tutorial. 
public class Block {
    private String hash;
    private String previousHash;
    private String data;
    private long timeStamp;
    private int nonce;
 
    public Block(String data, String previousHash, long timeStamp) {
        this.data = data;
        this.previousHash = previousHash;
        this.timeStamp = timeStamp;
        this.hash = calculateBlockHash();
    }
    // standard getters and setters
}

Vamos entender o que implementamos aqui:

  • previousHash = Hash do bloco anterior, uma parte importante para construir a cadeia
  • data = Os dados reais, qualquer informação que tenha valor, como um contrato
  • timeStamp = A data da criação deste bloco
  • nonce = Nonce, é um número arbitrário usado na criptografia
  • hash = O hash deste bloco, calculado com base em outros dados

O que é um Hash? e como calcular o Hash

Um hash é o resultado de algo conhecido como função hash. Uma função hash é uma função que mapeia dados de entrada de qualquer tamanho e os transforma  na saída em um conjunto de dados de tamanho fixo. O hash de saída é sempre sensível a qualquer alteração nos dados de entrada, por mais pequenos que sejam.

Por exemplo:

o hash de maria é 94aec9fbed989ece189a7e172c9cf41669050495152bc4c1dbf2a38d7fd85627

o hash de mario é: 59195c6c541c8307f1da2d1e768d6f2280c984df217ad5f4c64c3542b04111a4

Veja que a mudança de uma única letra na palavra foi suficiente para provocar uma grande alteração no hash resultante. 

Outro exemplo, o hash resultante de "paulo e joanna" é dfcbaaa3f541374947b72572995e1dba1984ad2183da06fd1640232828fb536d.

Veja também que não importa o tamanho da mensagem de entrada o hash resultante é sempre do mesmo tamanho.

Além disso, é impossível recuperar os dados de entrada apenas a partir de seu hash. Essas propriedades tornam a função hash bastante útil na criptografia.

Então, vamos ver como podemos gerar o hash do nosso bloco em Java:

public String calculateBlockHash() {
    String dataToHash = previousHash 
      + Long.toString(timeStamp) 
      + Integer.toString(nonce) 
      + data;
    MessageDigest digest = null;
    byte[] bytes = null;
    try {
        digest = MessageDigest.getInstance("SHA-256");
        bytes = digest.digest(dataToHash.getBytes(UTF_8));
    } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
        logger.log(Level.SEVERE, ex.getMessage());
    }
    StringBuffer buffer = new StringBuffer();
    for (byte b : bytes) {
        buffer.append(String.format("%02x", b));
    }
    return buffer.toString();
}

Muitas coisas foi feita aqui, vamos entender em detalhes:

  • Primeiro, concatenamos diferentes partes do bloco para gerar uma única string que vai ser o nosso dado de entrada.
  • Segundo, criamos uma instância da classe MessageDigest, essa classe possuí a função de hash SHA-256 que usaremos para gear o hash do bloco.
  • Terceiro, geramos o valor hash de nosso dado de entrada, que é uma matriz byte
  • Por último, transformamos a matriz de bytes em uma sequência hexadecimal, e em seguida convertemos essa sequência em uma string.

Já mineramos o bloco?

O nosso código parece simples e elegante até aqui, mas temos que lembrar o fato de que ainda não implementamos a mineração o bloco. Então, o que exatamente implica minerar um bloco?, isso é algo que captura a fantasia de muitos desenvolvedores!

Bem, minerar um bloco significa resolver uma tarefa computacionalmente complexa para o computador. Calcular o hash de um bloco de dados é algo tecnicamente fácil para um computador, e é aqui que entra a mineração de dados, para que um bloco seja válido na blockchain o seu hash deve começar com uma determinada quantidade de zeros, essa quantidade de zeros exigida pela blockchain é chamada de difficulty.

Por exemplo, em uma blockchain com difficulty igual a 4 (4 zeros exigidos no hash), o bloco;

nonce=1.data="10 bitcoins",timestamp=123456,previushash=0000aaa3f541374947b72572995e1dba1984ad2183da06fd1640232828fb536d


Tem como hash resultante: 1b47e3acf9cd496936430df42e017269c3bb14f54dda91b41a90fd265960f124

Esse hash ainda não é válido para a blockchain, então o que o computador faz?

Ele vai incrementando o número nonce do bloco até obter um hash de quatro zeros.

Lembra que eu falei que uma simples alteração em um dado muda completamente o seu hash resultante? Pois, bem, o número nonce vai sendo incrementado até que se encontre um número que somado aos demais dados do bloco resulte em um hash de 4 zeros.

Continuando o exemplo:

nonce=2.data="10 bitcoins",timestamp=123456,previushash=0000aaa3f541374947b72572995e1dba1984ad2183da06fd1640232828fb536d

hash resultante: 58u7e3acf9cd496936430df42e017269c3bb14f54dda91b41a90fd265tyui985

Ainda não, vamos incrementar mais......

nonce=3445.data="10 bitcoins",timestamp=123456,previushash=0000aaa3f541374947b72572995e1dba1984ad2183da06fd1640232828fb536d

hash resultante: 0000u7ecf9cd496936430df42e017269c3bb14f54dda91b41a90fd265tyui985


 

Por fim, depois de milhares de incrementos encontramos o número 3445, esse é o nonce que gera o hash de 4 zeros, enfim mineramos o bloco.

 Encontrar o hash começando com quatro zeros não é fácil para um computador. Ainda mais complicado seria encontrar um hash começando com dez zeros, para temos uma ideia geral.

É nesse sentido que reside a segurança da blockchain, para alterar seus dados seria necessário encontrar o hash específico de cada bloco dentro de cada dificuldade especificada, o que é uma tarefa muito árdua.

public String mineBlock(int prefix) {
    String prefixString = new String(new char[prefix]).replace('\0', '0');
    while (!hash.substring(0, prefix).equals(prefixString)) {
        nonce++;
        hash = calculateBlockHash();
    }
    return hash;
}

Vamos ver o que estamos tentando fazer aqui são:

  • Começamos definindo a quantidade de zeros que desejamos encontrar
  • Então verificamos se encontramos a solução
  • Se não incrementamos a nonce e calculamos o hash em um loop
  • O loop continua até acertarmos um hash com número de zeros especificado

Estamos começando com o valor padrão de nonce aqui e incrementando-o por um. Mas há estratégias mais sofisticadas para começar e incrementar uma nonce em aplicações do mundo real. 

Vamos executar o exemplo

Agora que temos nosso bloco definido junto com suas funções, podemos usá-lo para criar um blockchain simples. Vamos armazenar isso em uma ArrayList:

List<Block> blockchain = new ArrayList<>();
int prefix = 4;
String prefixString = new String(new char[prefix]).replace('\0', '0');

Além disso, definimos um prefixo de quatro, o que significa efetivamente que queremos que nosso hash comece com quatro zeros.

Vamos ver como podemos adicionar um bloco aqui:

@Test
public void givenBlockchain_whenNewBlockAdded_thenSuccess() {
    Block newBlock = new Block(
      "The is a New Block.", 
      blockchain.get(blockchain.size() - 1).getHash(),
      new Date().getTime());
    newBlock.mineBlock(prefix);
    assertTrue(newBlock.getHash().substring(0, prefix).equals(prefixString));
    blockchain.add(newBlock);
}

Verificação de blockchain

Como um nó pode validar que uma blockchain é válida? Embora isso possa ser bastante complicado, vamos pensar em uma versão simples:

@Test
public void givenBlockchain_whenValidated_thenSuccess() {
    boolean flag = true;
    for (int i = 0; i < blockchain.size(); i++) {
        String previousHash = i==0 ? "0" : blockchain.get(i - 1).getHash();
        flag = blockchain.get(i).getHash().equals(blockchain.get(i).calculateBlockHash())
          && previousHash.equals(blockchain.get(i).getPreviousHash())
          && blockchain.get(i).getHash().substring(0, prefix).equals(prefixString);
            if (!flag) break;
    }
    assertTrue(flag);
}

Então, aqui estamos fazendo três verificações específicas para cada bloco:

  • O hash armazenado do bloco atual é na verdade o que ele calcula
  • O hash do bloco anterior armazenado no bloco atual é o hash do bloco anterior
  • O bloco atual foi minado

Considerações finais

Nosso exemplo básico traz à tona os conceitos básicos de um blockchain, certamente não está completo. Antes de colocar essa tecnologia em uso prático, várias outras considerações precisam ser feitas, tais como verificação de autenticidade das transações, distribuição de dados, recompensa por mineração, e etc.
A tecnologia blockchain possuí aplicações em mais diversas áreas e vale a pena um estudo aprofundado sobre o assunto.

Comentários

Postagens mais visitadas deste blog

Passo a passo: Criando gráficos com o Google Charts

Entenda o que são as Razor Pages do .NET CORE

Como instalar a linguagem SOL no Windows