Construyendo tu propio Blockchain en JavaScript -Parte 1

En esta primera parte de la serie Javascript Blockchain, cubriremos el objeto bloque y el objeto blockchain y discutiremos su estructura e implicaciones.

Creo firmemente en la idea de que la mejor manera de aprender una tecnología es construirla. Naturalmente, mi interés en la tecnología de las cadenas de bloques me ha llevado a intentar construir mi propia cadena de bloques. Te guiaré a través de los pasos para construir una cadena de bloques básica con un sistema de prueba de trabajo incorporado. En el camino, discutiré las implicaciones de las diferentes características.

¿Qué necesitas?

  • La última versión de Node.js (Estoy usando v8.7.0)
  • La última versión de npm (Node Package Manager) (Estoy usando v5.6.0)

Crea una carpeta llamada js-blockchain y crea un archivo llamado main.js En esta guía, usaremos un solo archivo para que siempre puedas consultar el código fuente si encuentras algún problema.

Estructura de la cadena de bloques

Las cadenas de bloques se construyen mediante una combinación de listas enlazadas y árboles de merkle. La estructura de lista enlazada permite que la cadena se construya continuamente sobre sí misma y es de donde proviene el nombre blockchain. Una cadena de bloques es literalmente una cadena de bloques enlazados entre sí a través de la estructura de la lista enlazada. Una cosa a tener en cuenta, sin embargo, es que en lugar de mantener un puntero tradicional para referirse al bloque anterior, utiliza el hash del bloque anterior para referirse a él.

Si no estás familiarizado con lo que es un hash, aquí tiene un manual. Los Hashes son simplemente funciones determinísticas que crean salidas específicas para cada entrada, y normalmente son irreversibles, lo que significa que es extremadamente difícil derivar la entrada de la salida. Son fundamentales para el bloqueo de cadenas, ya que son la clave para que la cadena sea inmutable y para mantener la integridad de los datos.

Un Bloque como Objeto

Primero crearemos una clase llamada Block que tendrá una función constructor, una función calculateHash y una función mineBlock. Partimos de la función constructor que instancia los objetos Block y le proporciona sus propiedades.


class Block {
constructor(timestamp, data) {
this.index = 0;
this.timestamp = timestamp;
this.data = data;
this.previousHash = "0";
this.hash = this.calculateHash();
this.nonce = 0;
}

calculateHash() {

}

mineBlock(difficulty) {

}
}

Entradas de bloque

Cada objeto de bloque toma una marca de tiempo y datos de bloque. La marca de tiempo muestra cuándo se creó el bloque. Esto es útil, por ejemplo, en Bitcoin(BTC), ya que BTC está diseñado para tener una dificultad tal que el tiempo medio de extracción por bloque es de 10 minutos. Por lo tanto, el sistema puede utilizar estos datos de fecha y hora para reequilibrar la dificultad de la minería cada mes. Los datos del bloque contienen la información almacenada en la cadena. En muchas monedas como BTC, aquí es donde se almacenan los datos variables en forma de árboles de merkle.

Datos del bloque

Como puedes ver, además de los 3 campos de entrada de datos, nuestro objeto Block también contiene un índice, un hash anterior, un hash y un nonce. El índice comunica en qué lugar de la cadena se encuentra el bloque. El hash del bloque anterior es lo que mantiene la integridad de la cadena. El hash del bloque simplemente contiene el propio hash del bloque derivado de una función calculateHash. El nonce es otra pieza importante del bloque que es crítica para construir un mecanismo de minería para nuestra cadena de bloques que discutiremos en la parte 2: Construyendo tu propio Blockchain en JavaScript – Parte 2 de esta serie. Por ahora, pondremos nuestra variable nonce en 0.

Ten en cuenta, el hash del bloque anterior es lo que mantiene la integridad de la cadena. Para comprobar la integridad de la cadena pasamos por la cadena calculando el hash de cada bloque y cotejándolo con los datos de Hash anteriores. Si se altera una sola pieza de la información del bloque, se escupirá un hash completamente diferente y será inmediatamente detectable cuando se comparen con los datos de los Hash anteriores almacenados en el bloque que sigue al bloque alterado. Más adelante crearemos una función checkValid que lo demuestre.

Biblioteca Crypto-JS

Ahora añadiremos una función calculateHash a nuestro bloque. Para nuestra función hash, tomaremos prestado de la biblioteca crypto-js y usaremos su función hash SHA256. La función de hash SHA256 fue desarrollada por la NSA y es una función de hash irreversible. Esto se utiliza en la minería de BTC como su algoritmo de prueba de trabajo y en la creación de direcciones de BTC gracias a su seguridad.

Para instalar la librería crypto-js, ve a tu terminal e ingresa a tu carpeta js-blockchain y luego instala la librería con npm con el siguiente código:

npm install --save crypto-js

A continuación, verás el siguiente mensaje:

npm WARN saveError ENOENT: no such file or directory, open '/Users/spenserhuang/Desktop/js-blockchain/package.json'
npm WARN enoent ENOENT: no such file or directory, open '/Users/spenserhuang/Desktop/js-blockchain/package.json'
npm WARN js-blockchain No description
npm WARN js-blockchain No repository field.
npm WARN js-blockchain No README data
npm WARN js-blockchain No license field.

+ crypto-js@3.1.9-1
updated 1 package in 1.75s

No te preocupes por la advertencia por ahora ya que eso no nos afectará en el contexto de este proyecto. El error existe porque la carpeta que creamos es extremadamente baronesa y no tiene archivos adicionales como package.json u otros archivos de nodos y repositorios.

Calcular Hash

Sin embargo, ahora que tienes la librería instalada, puedes crear una constante SHA256 y extraer la información directamente con una sentencia require. Usaremos eso para crear nuestra función calculateHash.

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

class Block {
constructor(timestamp, data) {
this.index = 0;
this.timestamp = timestamp;
this.data = data;
this.previousHash = "0";
this.hash = this.calculateHash();
this.nonce = 0;
}

calculateHash() {
return SHA256(this.index + this.previousHash + this.timestamp + this.data + this.nonce).toString();
}

mineBlock(difficulty) {

}
}

Creamos nuestra propia función llamada calculateHash que usaremos para dar a cada bloque su propio hash. Como puede ver con la función, el hash toma cada pieza del objeto del bloque, la lanza a una función SHA256 y la convierte en una cadena. En este proyecto, convertimos nuestro resultado SHA256 en una cadena, ya que las cadenas son más fáciles de procesar y combinar.

La función calculateHash acepta CADA parte de los datos del bloque. Como resultado, si cualquier pieza de datos es manipulada, incluso si es un nuevo punto decimal, el hash del bloque será inmediatamente diferente. Esta es una gran característica para hacer que la cadena de bloqueo sea segura.

Construyendo la cadena de bloques

Ahora que hemos creado la estructura de bloques individual, necesitamos crear una estructura, un objeto de clase Blockchain, para enlazarlos y construir la funcionalidad básica que viene con los bloques normales. Para que las cosas sean lo más sencillas posible, nuestra cadena de bloques sólo tendrá una función constructor, una función createGenesis, una función latestBlock, una función addBlock y una función checkValid.

Objeto de cadena de bloques

Nuestra clase Blockchain necesita algunas funcionalidades clave, a saber, la capacidad de ser instanciado, de empezar, de acceder a la información del bloque más reciente y de extenderse añadiéndole un nuevo bloque.

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

createGenesis() {
return new Block(0, "01/01/2017", "Genesis block", "0")
}

latestBlock() {
return this.chain[this.chain.length - 1]
}

addBlock(newBlock){
newBlock.previousHash = this.latestBlock().hash;
newBlock.hash = newBlock.calculateHash();
this.chain.push(newBlock);
}

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

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

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

return true;
}
}

Función Constructor

Primero, necesitamos una función de constructor para instanciar nuestra cadena de bloques. Esta será una función simple que simplemente crea un objeto de bloqueo con la propiedad chain en una estructura de lista. Esta es la razón por la que nuestros objetos Block tenían índices. Las propiedades de índice de nuestros bloques se utilizan para encontrar su posición en nuestra lista de cadenas.

Crear función Génesis

En segundo lugar, nuestra cadena de bloques necesita una función createGenesis que crea el primer bloque de nuestra cadena. En la convención de la cadena de bloques, el primer bloque de cualquier cadena también se conoce como el “bloque Génesis”, de ahí el nombre createGenesis. Con este bloque, normalmente introducimos manualmente su información. Como es el primer bloque de la cadena, tiene un valor de índice de 0. Para su marca de tiempo y campos de datos, podemos poner la fecha de hoy y el “bloque Génesis” o lo que quieras. Al igual que en el campo Hash anterior, podemos simplemente poner “0” por ahora ya que no hay ningún hash precediendo al bloque Génesis.

Obtener la función de bloqueo más reciente

Tercero, nuestra cadena necesita una función latestBlock. Se utiliza para obtener información sobre el bloque más reciente. Esta funcionalidad se utilizará para implementar nuestra función addBlock.

Añadir nueva función de bloque

En cuarto lugar, para ampliar continuamente nuestro bloque, necesitamos una función addBlock. Las cadenas de bloques tienen una funcionalidad tal que necesitan ser cada vez más largas a medida que se introduce más información en ellas. Así, en el caso de BTC, por ejemplo, se crea un nuevo bloque cada 10 minutos aproximadamente y cada uno de estos nuevos bloques contendrá toda la información de la transacción que ocurrió dentro de ese período de tiempo de 10 minutos.

La implementación de una función addBlock es sencilla. Nuestra función toma un objeto Block como una entrada que ya posee una marca de tiempo y datos. Luego, nuestra función addBlock usará la función latestBlock para dar a nuestro nuevo bloque un índice y un campo Hash anterior. Después de eso, le damos a nuestro nuevo bloque su propio hash usando la función calculateHash que escribimos anteriormente. Finalmente, empujamos este nuevo bloque sobre nuestra cadena y ahora nuestra cadena de bloques tiene un nuevo bloque.

Función de verificación de validez

La función final que implementaremos es la función checkValid. Esto se utiliza para comprobar la integridad de la cadena de bloqueo y para detectar si se ha manipulado algo.

Como se ha dicho anteriormente, los hashes son críticos para detectar cambios en nuestra cadena de bloques, ya que incluso el cambio más pequeño en el objeto dará como resultado un hash completamente diferente. Por lo tanto, para nuestra función checkValid, usaremos un bucle for para pasar a través de la cadena de bloques e intentaremos hacer coincidir sus hashes para detectar cambios.

Hay dos partes de nuestro bucle, la primera es hacer coincidir currentBlock.hash con currentBlock.calculateHash() y la otra es hacer coincidir currentBlock.previousHash con previousBlock.hash. La primera se utiliza para comprobar si la información de currentBlock ha sido manipulada sin actualizar currentBlock.hash La segunda se utiliza para comprobar si se ha manipulado o no un bloque anterior.

Puedes tener la pregunta de si puede o no poner en la condición de if

if (currentBlock.previousHash !== previousBlock.calculateHash()) {
return false;
}

Esto teóricamente parece que debería funcionar. Sin embargo, previousBlock es en realidad un objeto ligeramente diferente al objeto de bloque real de la cadena, aunque básicamente contiene toda la misma información que el bloque real. Como resultado, producen diferentes hashes. Por ejemplo, durante mi prueba, el hash del bloque real es 0c27196c36691e915fb2a2f83e63867ec5042abfda4832b02383a6ab40aaa075c mientras que el hash del bloque anterior es e6a312951e9e4ed6e4b2ef049246b282f7114459993939ff6a5b97b0c53c941295
Así de sensibles son los hashes.

Poniendo todo junto

Ahora, tu código en general debería verse así:

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

class Block {
constructor(timestamp, data) {
this.index = 0;
this.timestamp = timestamp;
this.data = data;
this.previousHash = "0";
this.hash = this.calculateHash();
this.nonce = 0;
}

calculateHash() {
return SHA256(this.index + this.previousHash + this.timestamp + this.data + this.nonce).toString();
}

mineBlock(difficulty) {

}
}

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

createGenesis() {
return new Block(0, "01/01/2017", "Genesis block", "0")
}

latestBlock() {
return this.chain[this.chain.length - 1]
}

addBlock(newBlock){
newBlock.previousHash = this.latestBlock().hash;
newBlock.hash = newBlock.calculateHash();
this.chain.push(newBlock);
}

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

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

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

return true;
}
}

let jsChain = new Blockchain();
jsChain.addBlock(new Block("12/25/2017", {amount: 5}));
jsChain.addBlock(new Block("12/26/2017", {amount: 10}));

console.log(JSON.stringify(jsChain, null, 4));
console.log("Is blockchain valid? " + jsChain.checkValid());

Puede probar su cadena de bloqueo añadiendo las siguientes líneas al final

let jsChain = new Blockchain();
jsChain.addBlock(new Block("12/25/2017", {amount: 5}));
jsChain.addBlock(new Block("12/26/2017", {amount: 10}));

console.log(JSON.stringify(jsChain, null, 4));
console.log("Is blockchain valid? " + jsChain.checkValid());

Luego, ve a su terminal y corre

node main.js

Ahora deberías ver el siguiente mensaje:

{
"chain": [
{
"index": 0,
"timestamp": 0,
"data": "01/01/2017",
"previousHash": "Genesis block",
"hash": "8163cbd8feafd38a96cd193f2b44940473c22b21ddbb7445bd99ee310dac28ae",
"nonce": 0
},
{
"index": 1,
"timestamp": "12/25/2017",
"data": {
"amount": 5
},
"previousHash": "8163cbd8feafd38a96cd193f2b44940473c22b21ddbb7445bd99ee310dac28ae",
"hash": "744ce201216f78bba5b87e371579898b97e473ac644d6a13ddda9cdbe05100f6",
"nonce": 0
},
{
"index": 2,
"timestamp": "12/26/2017",
"data": {
"amount": 10
},
"previousHash": "744ce201216f78bba5b87e371579898b97e473ac644d6a13ddda9cdbe05100f6",
"hash": "9e3cf69ee7a3f3f651b33500ea3f32ccf1a13590115c6739dda74920d54702c8",
"nonce": 0
}
]
}
Is blockchain valid? true

Resumiendo

Ahora tú tiene una versión 1 de una cadena de bloqueo en funcionamiento. Sin embargo, hay un pequeño problema de seguridad que existe con esta cadena de bloqueo. Te animo a que construyas una cadena de bloques como esta y la pruebes. Intenta identificar dónde está el fallo de seguridad intentando manipular los datos.

Sugerencia: este fallo de seguridad está directamente relacionado con la implementación de la función minera que trataremos en la Parte 2 Construyendo tu propio Blockchain en JavaScript – Parte 2 de este tutorial.