Construyendo tu propio Blockchain en JavaScript – Parte 2

El objetivo de esta serie de tutoriales, es ayudarte a construir una imagen de cómo se podría desarrollar la tecnología de blockchain. Puedes encontrar la parte 1 aquí: Construyendo tu propio Blockchain en JavaScript – Parte 1.

En este segundo tutorial vamos a..:

  • Crear una billetera sencilla.
  • Enviar transacciones firmadas utilizando nuestra blockchain.

Todo lo anterior resultará en nuestra propia moneda criptográfica.

Continuando con el último tutorial, tenemos un Blockchain básico verificable. Pero actualmente nuestra cadena sólo almacena mensajes bastante inútiles. Hoy vamos a sustituir estos datos por transacciones (“nuestro bloque podrá realizar múltiples transacciones”), lo que nos permitirá crear una cripto-divisa muy sencilla. Llamaremos a nuestra nueva moneda “NoobCoin”.

Este tutorial asume que tú ha seguido el otro tutorial.

Dependencias: Necesitarás importar bounceycastle y GSON.

Preparar una billetera

En las cripto-divisas, la propiedad de la moneda se transfiere en la cadena de bloques como transacciones, los participantes tienen una dirección a la que se pueden enviar los fondos. En su forma básica las billeteras pueden simplemente almacenar estas direcciones, sin embargo, la mayoría de las billeteras también son software capaz de realizar nuevas transacciones en la Blockchain.

Así que vamos a crear una clase de wallet para mantener nuestra clave pública y privada:


package noobchain;
import java.security.*;

public class Wallet {
public PrivateKey privateKey;
public PublicKey publicKey;
}

¿Para qué sirven las claves públicas y privadas?

Para nuestro ‘noobcoin’ la clave pública actuará como nuestra dirección. Está bien compartir esta clave pública con otros para recibir el pago. Nuestra clave privada se utiliza para firmar nuestras transacciones, de modo que nadie puede gastar nuestros noobcoins más que el propietario de la clave privada. Los usuarios tendrán que mantener su clave privada en secreto. También enviamos nuestra clave pública junto con la transacción y puede ser utilizada para verificar que nuestra firma es válida y que los datos no han sido alterados.

Generamos nuestras claves privadas y públicas en un KeyPair. Usaremos criptografía de curvas elípticas para generar nuestros KeyPairs. Añadamos un método generateKeyPair() a nuestra clase Wallet y llamémoslo en el constructor:

package noobchain;
import java.security.*;

public class Wallet {

public PrivateKey privateKey;
public PublicKey publicKey;

public Wallet(){
generateKeyPair();
}

public void generateKeyPair() {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA","BC");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime192v1");
// Inicializar el generador de claves y generar un KeyPair
keyGen.initialize(ecSpec, random); //256 bytes provides an acceptable security level
KeyPair keyPair = keyGen.generateKeyPair();
// Configurar las claves pública y privada desde el keyPair
privateKey = keyPair.getPrivate();
publicKey = keyPair.getPublic();
}catch(Exception e) {
throw new RuntimeException(e);
}
}

}

Ahora que tenemos los contornos de nuestra clase de billetera, echemos un vistazo a las transacciones.

Transacciones y firmas

Cada transacción contendrá una cierta cantidad de datos:

  • La clave pública (dirección) del remitente de los fondos.
  • La clave(s) pública(s) del receptor de los fondos.
  • El valor/importe de los fondos a transferir.
  • Insumos, que son referencias a transacciones anteriores que prueban que el remitente tiene fondos para enviar.
  • Salidas, que muestra el importe de las direcciones relevantes recibidas en la operación. ( Estas salidas se refieren como entradas en las nuevas transacciones)
  • Una firma criptográfica, que prueba que el propietario de la dirección es el que envía la transacción y que los datos no han sido modificados. (por ejemplo: impedir que un tercero modifique el importe enviado)

Vamos a crear esta nueva clase de transacción:

import java.security.*;
import java.util.ArrayList;

public class Transaction {

public String transactionId; // este es también el hash de la transacción.
public PublicKey sender; // dirección del remitente/llave pública.
public PublicKey reciepient; // Dirección del destinatario/clave pública.
public float value;
public byte[] signature; // Esto es para evitar que nadie más gaste fondos en nuestra billetera.

public ArrayList inputs = new ArrayList();
public ArrayList outputs = new ArrayList();

private static int sequence = 0; // un recuento aproximado de cuántas transacciones se han generado.

// Constructor:
public Transaction(PublicKey from, PublicKey to, float value, ArrayList inputs) {
this.sender = from;
this.reciepient = to;
this.value = value;
this.inputs = inputs;
}

// Calcula el hash de la transacción (que se utilizará como su Id)
private String calulateHash() {
sequence++; // aumentar la secuencia para evitar 2 transacciones idénticas con el mismo hash
return StringUtil.applySha256(
StringUtil.getStringFromKey(sender) +
StringUtil.getStringFromKey(reciepient) +
Float.toString(value) + sequence
);
}
}

También debemos crear clases vacías TransactionInput y TransactionOutput, no te preocupes, podemos rellenarlas más tarde.

Nuestra clase de transacción también contendrá métodos relevantes para generar/verificar la firma y verificar la transacción.

¿Cuál es el propósito de las firmas y cómo funcionan?

Las firmas realizan dos tareas muy importantes en nuestra blockchain: En primer lugar, permiten que sólo el propietario gaste sus monedas; en segundo lugar, impiden que otros manipulen la transacción presentada antes de que se extraiga un nuevo bloque (en el punto de entrada).

La clave privada se utiliza para firmar los datos y la clave pública puede utilizarse para verificar su integridad.

Por ejemplo: James quiere enviar 2 NoobCoins a Allie, así que el software de su billetera genera esta transacción y la envía a los mineros para que la incluyan en el siguiente bloque. Un minero intenta cambiar el destinatario de las 2 monedas a Juan. Sin embargo, afortunadamente, James había firmado los datos de la transacción con su clave privada, permitiendo a cualquiera verificar si los datos de la transacción han sido cambiados usando la clave pública de James (ya que ninguna otra persona podrá verificar la transacción).

Podemos ver (desde el bloque de código anterior) que nuestra firma será un montón de bytes, así que vamos a crear un método para generarlos. Lo primero que necesitaremos son unas cuantas funciones de ayuda en la clase StringUtil :

//Aplica la firma ECDSA y devuelve el resultado ( en bytes).
public static byte[] applyECDSASig(PrivateKey privateKey, String input) {
Signature dsa;
byte[] output = new byte[0];
try {
dsa = Signature.getInstance("ECDSA", "BC");
dsa.initSign(privateKey);
byte[] strByte = input.getBytes();
dsa.update(strByte);
byte[] realSig = dsa.sign();
output = realSig;
} catch (Exception e) {
throw new RuntimeException(e);
}
return output;
}

//Verifica una firma de cadena
public static boolean verifyECDSASig(PublicKey publicKey, String data, byte[] signature) {
try {
Signature ecdsaVerify = Signature.getInstance("ECDSA", "BC");
ecdsaVerify.initVerify(publicKey);
ecdsaVerify.update(data.getBytes());
return ecdsaVerify.verify(signature);
}catch(Exception e) {
throw new RuntimeException(e);
}
}

public static String getStringFromKey(Key key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}

Ahora vamos a utilizar estos métodos de firma en nuestra clase Transaction, añadiendo los métodos generateSignature() y verifiySignature():

//Firma todos los datos que no queremos que sean alterados.
public void generateSignature(PrivateKey privateKey) {
String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value) ;
signature = StringUtil.applyECDSASig(privateKey,data);
}
//Verifica que los datos que firmamos no hayan sido alterados
public boolean verifiySignature() {
String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value) ;
return StringUtil.verifyECDSASig(sender, data, signature);
}

Las firmas serán verificadas por los mineros a medida que se añada una nueva transacción a un bloque.

Pruebas de las carteras y firmas

Ahora estamos casi a mitad de camino probemos algunas cosas que están funcionando. En la clase NoobChain agreguemos algunas variables nuevas y reemplacemos el contenido de nuestro método principal:

import java.security.Security;
import java.util.ArrayList;
import java.util.Base64;
import com.google.gson.GsonBuilder;

public class NoobChain {

public static ArrayList blockchain = new ArrayList();
public static int difficulty = 5;
public static Wallet walletA;
public static Wallet walletB;

public static void main(String[] args) {
//Configurar el castillo de Bouncey como un proveedor de seguridad
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
//Crear las nuevas carteras
walletA = new Wallet();
walletB = new Wallet();
//Probar claves públicas y privadas
System.out.println("Claves privadas y públicas:");
System.out.println(StringUtil.getStringFromKey(walletA.privateKey));
System.out.println(StringUtil.getStringFromKey(walletA.publicKey));
//Crear una transacción de prueba de WalletA a WalletB
Transaction transaction = new Transaction(walletA.publicKey, walletB.publicKey, 5, null);
transaction.generateSignature(walletA.privateKey);
//Verificar el funcionamiento de la firma y verificarla desde la clave pública
System.out.println("Se verifica la firma");
System.out.println(transaction.verifiySignature());

}

Inputs & Outputs 1: Cómo se posee la divisa criptográfica….

Para que tú tengas un (1) bitcoin, tiense que recibir 1 Bitcoin. El libro mayor no le agrega realmente una moneda de bitcoin y menos una moneda de bitcoin del remitente, el remitente hace referencia a que él/ella recibió previamente una moneda de bitcoin, entonces se creó una salida de transacción que muestra que 1 Bitcoin fue enviado a su dirección. (Las entradas de transacción son referencias a salidas de transacción anteriores).

El saldo de tu billetera es la suma de todas las salidas de transacciones no utilizadas dirigidas a usted.

A partir de este punto seguiremos la convención de bitcoins y llamaremos a las salidas de transacciones no utilizadas: UTXO’s.

Así que vamos a crear una clase de entrada de transacciones:

public class TransactionInput {
public String transactionOutputId; // Referencia a TransactionOutputs -> transactionId
public TransactionOutput UTXO; // Contiene la salida de la transacción no utilizada

public TransactionInput(String transactionOutputId) {
this.transactionOutputId = transactionOutputId;
}
}

Y una clase TransactionOutputs:

import java.security.PublicKey;

public class TransactionOutput {
public String id;
public PublicKey reciepient; // también conocido como el nuevo dueño de estas monedas.
public float value; // la cantidad de monedas que poseen
public String parentTransactionId; // el identificador de la transacción en la que se creó esta salida

//Constructor
public TransactionOutput(PublicKey reciepient, float value, String parentTransactionId) {
this.reciepient = reciepient;
this.value = value;
this.parentTransactionId = parentTransactionId;
this.id = StringUtil.applySha256(StringUtil.getStringFromKey(reciepient)+Float.toString(value)+parentTransactionId);
}

//Comprueba si la moneda le pertenece
public boolean isMine(PublicKey publicKey) {
return (publicKey == reciepient);
}

}

Los resultados de la transacción mostrarán la cantidad final enviada a cada parte de la transacción. Éstos, cuando se mencionan como entradas en las nuevas transacciones, actúan como prueba de que tiene monedas que enviar.

Inputs & Outputs 2: Procesando la transacción….

Los bloques de la cadena pueden recibir muchas transacciones y la cadena de bloques puede ser muy, muy larga, podría llevar eones procesar una nueva transacción porque tenemos que encontrar y comprobar sus entradas. Para evitar esto, mantendremos una colección extra de todas las transacciones no gastadas que pueden ser utilizadas como insumos. En nuestra clase NoobChain agrega esta colección de todos los UTXOs:

public class NoobChain {

public static ArrayList blockchain = new ArrayList();
public static HashMap<String,TransactionOutputs> UTXOs = new HashMap<String,TransactionOutputs>(); //lista de todas las transacciones no gastadas.
public static int difficulty = 5;
public static Wallet walletA;
public static Wallet walletB;

public static void main(String[] args) {

Ahora vamos a poner todo junto para procesar la transacción con un método booleano processTransaction en nuestra Clase de Transacción:

//Devuelve true si se pudiera crear una nueva transacción.
public boolean processTransaction() {

if(verifiySignature() == false) {
System.out.println("#la Firma de la transacción no se verificó");
return false;
}

//Recopilar los datos de las transacciones (asegúrese de que no se hayan gastado):
for(TransactionInput i : inputs) {
i.UTXO = NoobChain.UTXOs.get(i.transactionOutputId);
}

//verificar si la transacción es válida:
if(getInputsValue() < NoobChain.minimumTransaction) {
System.out.println("#Insumos de transacciones a pequeños: " + getInputsValue());
return false;
}

//generar resultados de transacciones:
float leftOver = getInputsValue() - value; //get value of inputs then the left over change:
transactionId = calulateHash();
outputs.add(new TransactionOutput( this.reciepient, value,transactionId)); //enviar valor al destinatario
outputs.add(new TransactionOutput( this.sender, leftOver,transactionId)); // enviar la parte de atrás 'cambiar' de nuevo al remitente

//añadir salidas a la lista de productos no utilizados
for(TransactionOutput o : outputs) {
NoobChain.UTXOs.put(o.id , o);
}

//Eliminar las entradas de transacción de las listas UTXO como gastadas:
for(TransactionInput i : inputs) {
if(i.UTXO == null) continue; //if Transaction can't be found skip it
NoobChain.UTXOs.remove(i.UTXO.id);
}

return true;
}

//devuelve la suma de los valores de las entradas (UTXOs)
public float getInputsValue() {
float total = 0;
for(TransactionInput i : inputs) {
if(i.UTXO == null) continue; //Si no se encuentra la transacción, sáltela.
total += i.UTXO.value;
}
return total;
}

//returns sum of outputs:
public float getOutputsValue() {
float total = 0;
for(TransactionOutput o : outputs) {
total += o.value;
}
return total;
}

…Con este método realizamos algunas verificaciones para asegurarnos de que la transacción es válida, luego recogemos entradas y generamos salidas. (Ver líneas comentadas en el código para más información).

Es importante destacar que, hacia el final, descartamos las entradas de nuestra lista de UTXO, lo que significa que una salida de transacción sólo se puede utilizar una vez como entrada… Por lo tanto, se debe utilizar el valor completo de las entradas, de modo que el remitente se devuelve a sí mismo el “cambio”.

Por fin vamos a actualizar nuestra billetera a:

Reúne nuestro balance (haciendo un bucle a través de la lista de UTXOs y comprobando si la salida de una transacción es Mine())
Y generar transacciones para nosotros….

import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class Wallet {

public PrivateKey privateKey;
public PublicKey publicKey;

public HashMap<String,TransactionOutput> UTXOs = new HashMap<String,TransactionOutput>(); //only UTXOs owned by this wallet.

public Wallet() {...

public void generateKeyPair() {...

//devuelve el saldo y almacena los UTXO's propiedad de esta cartera en este.UTXOs
public float getBalance() {
float total = 0;
for (Map.Entry<String, TransactionOutput> item: NoobChain.UTXOs.entrySet()){
TransactionOutput UTXO = item.getValue();
if(UTXO.isMine(publicKey)) { // si la salida me pertenece ( si las monedas me pertenecen)
UTXOs.put(UTXO.id,UTXO); //añádalo a nuestra lista de transacciones no gastadas.
total += UTXO.value ;
}
}
return total;
}
//Genera y devuelve una nueva transacción desde esta cartera.
public Transaction sendFunds(PublicKey _recipient,float value ) {
if(getBalance() < value) { //reunir el saldo y verificar los fondos.
System.out.println("#No hay fondos suficientes para enviar la transacción. Transacción descartada.");
return null;
}
//create array list of inputs
ArrayList inputs = new ArrayList();

float total = 0;
for (Map.Entry<String, TransactionOutput> item: UTXOs.entrySet()){
TransactionOutput UTXO = item.getValue();
total += UTXO.value;
inputs.add(new TransactionInput(UTXO.id));
if(total > value) break;
}

Transaction newTransaction = new Transaction(publicKey, _recipient , value, inputs);
newTransaction.generateSignature(privateKey);

for(TransactionInput input: inputs){
UTXOs.remove(input.transactionOutputId);
}
return newTransaction;
}

}

Añadiendo transacciones a nuestros bloques:

Ahora que tenemos un sistema de transacciones que funciona, necesitamos implementarlo en nuestra cadena de bloques. Deberíamos reemplazar los datos inútiles que teníamos en nuestros bloques por un ArrayList de transacciones. Sin embargo, puede haber miles de transacciones en un solo bloque, demasiadas para incluirlas en nuestro cálculo hash… pero no se preocupe, podemos usar la raíz merkle de las transacciones (puede leer rápidamente acerca de los árboles merkle aquí *pronto*).

Añadamos un método helper para generar el merkleroot en StringUtils:

//Realiza una serie de transacciones y devuelve una raíz de merkle.
public static String getMerkleRoot(ArrayList transactions) {
int count = transactions.size();
ArrayList previousTreeLayer = new ArrayList();
for(Transaction transaction : transactions) {
previousTreeLayer.add(transaction.transactionId);
}
ArrayList treeLayer = previousTreeLayer;
while(count > 1) {
treeLayer = new ArrayList();
for(int i=1; i < previousTreeLayer.size(); i++) {
treeLayer.add(applySha256(previousTreeLayer.get(i-1) + previousTreeLayer.get(i)));
}
count = treeLayer.size();
previousTreeLayer = treeLayer;
}
String merkleRoot = (treeLayer.size() == 1) ? treeLayer.get(0) : "";
return merkleRoot;
}

Ahora vamos a implementar nuestros cambios en la clase de bloques:

import java.util.ArrayList;
import java.util.Date;

public class Block {

public String hash;
public String previousHash;
public String merkleRoot;
public ArrayList transactions = new ArrayList(); //our data will be a simple message.
public long timeStamp; //como número de milisegundos desde 1/1/1970.
public int nonce;

//Block Constructor.
public Block(String previousHash ) {
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();

this.hash = calculateHash(); //Making sure we do this after we set the other values.
}

//Calcular nuevo hash basado en el contenido de los bloques
public String calculateHash() {
String calculatedhash = StringUtil.applySha256(
previousHash +
Long.toString(timeStamp) +
Integer.toString(nonce) +
merkleRoot
);
return calculatedhash;
}

//Aumenta el valor de nonce hasta que se alcanza el objetivo de hash.
public void mineBlock(int difficulty) {
merkleRoot = StringUtil.getMerkleRoot(transactions);
String target = StringUtil.getDificultyString(difficulty); //Create a string with difficulty * "0"
while(!hash.substring( 0, difficulty).equals(target)) {
nonce ++;
hash = calculateHash();
}
System.out.println("Bloque minado!!! : " + hash);
}

//Añadir transacciones a este bloque

public boolean addTransaction(Transaction transaction) {
//procesar la transacción y verificar si es válida, a menos que el bloque sea un bloque de génesis y luego ignorar.
if(transaction == null) return false;
if((previousHash != "0")) {
if((transaction.processTransaction() != true)) {
System.out.println("Transaction failed to process. Discarded.");
return false;
}
}
transactions.add(transaction);
System.out.println("Transacción añadida con éxito al Bloque");
return true;
}

}

Nota que también actualizamos nuestro constructor de bloques ya que no necesitamos pasar los datos de la cadena e incluimos la raíz de merkle en el método de cálculo hash.

Nuestro método booleano addTransaction añadirá las transacciones y sólo devolverá true si la transacción se ha añadido correctamente.

El Gran Final (En el principio no había ninguna moneda):

Deberíamos probar el envío de monedas desde y hacia las billeteras, y actualizar nuestro control de validez de la blockchain. Pero primero necesitamos una manera de introducir nuevas monedas en la mezcla. Hay muchas maneras de crear nuevas monedas, en la cadena de bloques de bitcoin por ejemplo: los mineros pueden incluir una transacción para ellos mismos como recompensa por cada bloque extraído. Por ahora, sin embargo, sólo liberaremos todas las monedas que deseamos tener, en el primer bloque (el bloque génesis). Al igual que bitcoin, codificaremos el bloque génesis.

Vamos a actualizar nuestra clase NoobChain con todo lo que necesita:

  • Un bloque de Genesis que libera 100 Noobcoins a la billetera.
  • Una verificación de validez de cadena actualizada que tiene en cuenta las operaciones.
  • Algunas transacciones de prueba para ver que todo funciona.

public class NoobChain {

public static ArrayList blockchain = new ArrayList();
public static HashMap<String,TransactionOutput> UTXOs = new HashMap<String,TransactionOutput>();

public static int difficulty = 3;
public static float minimumTransaction = 0.1f;
public static Wallet walletA;
public static Wallet walletB;
public static Transaction genesisTransaction;

public static void main(String[] args) {
//añadir nuestros bloques a la lista ArrayList de bloques:
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); //Setup Bouncey castle as a Security Provider

//Crear billeteras
walletA = new Wallet();
walletB = new Wallet();
Wallet coinbase = new Wallet();

//crear una transacción de génesis, que envía 100 NoobCoin a la billeteraA:
genesisTransaction = new Transaction(coinbase.publicKey, walletA.publicKey, 100f, null);
genesisTransaction.generateSignature(coinbase.privateKey); //firmar manualmente la transacción de génesis
genesisTransaction.transactionId = "0"; //configurar manualmente el ID de la transacción
genesisTransaction.outputs.add(new TransactionOutput(genesisTransaction.reciepient, genesisTransaction.value, genesisTransaction.transactionId)); //añadir manualmente la salida de transacciones
UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0)); // es importante almacenar nuestra primera transacción en la lista de UTXOs.

System.out.println("Creación y explotación del bloque Génesis.... ");
Block genesis = new Block("0");
genesis.addTransaction(genesisTransaction);
addBlock(genesis);

//prueba
Block block1 = new Block(genesis.hash);
System.out.println("\nEl balance de WalletA es: " + walletA.getBalance());
System.out.println("\nWalletA está intentando enviar fondos (40) a WalletB...");
block1.addTransaction(walletA.sendFunds(walletB.publicKey, 40f));
addBlock(block1);
System.out.println("\n el balance de WalletA es: " + walletA.getBalance());
System.out.println("El balance de WalletB es: " + walletB.getBalance());

Block block2 = new Block(block1.hash);
System.out.println("\nWalletA Intentando enviar más fondos (1000) de los que tiene...");
block2.addTransaction(walletA.sendFunds(walletB.publicKey, 1000f));
addBlock(block2);
System.out.println("\nel balance de WalletA es: " + walletA.getBalance());
System.out.println("el balance de WalletB es: " + walletB.getBalance());

Block block3 = new Block(block2.hash);
System.out.println("\nWalletB está intentando enviar fondos (20) a WalletA...");
block3.addTransaction(walletB.sendFunds( walletA.publicKey, 20));
System.out.println("\nel balance de WalletA es: " + walletA.getBalance());
System.out.println("el balance de WalletB es: " + walletB.getBalance());

isChainValid();

}

public static Boolean isChainValid() {
Block currentBlock;
Block previousBlock;
String hashTarget = new String(new char[difficulty]).replace('\0', '0');
HashMap<String,TransactionOutput> tempUTXOs = new HashMap<String,TransactionOutput>(); //a temporary working list of unspent transactions at a given block state.
tempUTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));

//a través de la cadena de bloques para comprobar los hashes:
for(int i=1; i < blockchain.size(); i++) {

currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i-1);
//comparar el hash registrado y el hash calculado:
if(!currentBlock.hash.equals(currentBlock.calculateHash()) ){
System.out.println("#Hashes actuales no son iguales");
return false;
}
//comparar el hash anterior y el hash anterior registrado
if(!previousBlock.hash.equals(currentBlock.previousHash) ) {
System.out.println("#Hashes anteriores no son iguales");
return false;
}
//comprobar si se ha resuelto el problema del hash
if(!currentBlock.hash.substring( 0, difficulty).equals(hashTarget)) {
System.out.println("#Este bloque no ha sido minado");
return false;
}

//a través de las transacciones de bloqueo:
TransactionOutput tempOutput;
for(int t=0; t <currentBlock.transactions.size(); t++) {
Transaction currentTransaction = currentBlock.transactions.get(t);

if(!currentTransaction.verifiySignature()) {
System.out.println("#La firma en la transacción (" + t + ") es inválida");
return false;
}
if(currentTransaction.getInputsValue() != currentTransaction.getOutputsValue()) {
System.out.println("#Las entradas son iguales a las salidas en la transacción(" + t + ")");
return false;
}

for(TransactionInput input: currentTransaction.inputs) {
tempOutput = tempUTXOs.get(input.transactionOutputId);

if(tempOutput == null) {
System.out.println("#Falta la entrada referenciada en Transacción(" + t + ")");
return false;
}

if(input.UTXO.value != tempOutput.value) {
System.out.println("#El valor de la entrada referenciada Transacción(" + t + ") es inválido");
return false;
}

tempUTXOs.remove(input.transactionOutputId);
}

for(TransactionOutput output: currentTransaction.outputs) {
tempUTXOs.put(output.id, output);
}

if( currentTransaction.outputs.get(0).reciepient != currentTransaction.reciepient) {
System.out.println("#El destinatario de la salida de la transacción (" + t + ") no es quien debería ser");
return false;
}
if( currentTransaction.outputs.get(1).reciepient != currentTransaction.sender) {
System.out.println("#Transacción(" + t + ") salida 'cambio' no es el remitente.");
return false;
}

}

}
System.out.println("La Blockchain es válida");
return true;
}

public static void addBlock(Block newBlock) {
newBlock.mineBlock(difficulty);
blockchain.add(newBlock);
}
}

Las billeteras ahora pueden enviar fondos de forma segura en tu blockchain, sólo si tienen fondos para enviarlos. Esto significa que tú tienes tu propia criptocurrencia local.

¡Ya terminaron con las transacciones en su cadena de bloqueo!

Has creado con éxito tu propia criptocurrencia. Tu Bockchain ahora:

  • Permite a los usuarios crear carteras con `nueva Cartera();”.
  • Proporciona carteras con claves públicas y privadas mediante criptografía de curvas elípticas.
  • Asegura la transferencia de fondos, utilizando un algoritmo de firma digital para probar la propiedad.
  • Y finalmente permitir a los usuarios hacer transacciones en su cadena de bloques con ‘Block.addTransaction(walletA.sendFunds( walletB.publicKey, 20));’;”Block.addTransaction(walletA.sendFunds( walletB.publicKey, 20));’.