Simple implementation blockchain implemented in Java on Linux.

Clone catalog GitHub repo

git clone git@github.com:karol-preiskorn/java-examples.git src/main/java/blockchain/

Setup environment

We need Gradle to test and run. Simple method to install. Try use https://sdkman.io/usage SDKMAN! (my favorite)

curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"

Install Java & Gradle

# list all version
sdk list java *
# choice one
sdk install java 13.0.1.j9-adpt
# list all version gradle
sdk list gradle *
# choice 6.* because we will test by jupiter
sdk install gradle 6.0.1

Minimum theory

We create simple list.

Every next element is linked by hash key with body block

Source @github

package blockchain;

import blockchain.StringUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Block {

  public String hash;
  public String previousHash;
  private String data; // our data will be a simple message.
  private long timeStamp; // as number of milliseconds since 1/1/1970.
  private int nonce;

  /**
   * Calculate Hash SHA256. Calculate new hash based on blocks contents.
   *
   * hash = previousHash + Long.toString(timeStamp) + Integer.toString(nonce) +
   * data
   *
   * Calculation in in @StringUtil.applySha256
   *
   * @return String calculated hash
   */
  public String calculateHash() {
    String calculatedhash = StringUtil
        .applySha256(previousHash + Long.toString(timeStamp) + Integer.toString(nonce) + data);
    return calculatedhash;
  }

  /**
   * Mine BlockChain.
   *
   * The mineBlock() method takes in an int called difficulty, this is the number
   * of 0’s they must solve for. We will require miners to do proof-of-work by
   * trying different variable values in the block until its hash starts with a
   * certain number of 0’s.
   * <p>
   * Lets add an int called nonce to be included in our calculateHash() method,
   * and the much needed mineBlock() method
   * <p>
   * Mining a block means solving a computationally complex task for the block.
   * While calculating the hash of a block is somewhat trivial, finding the hash
   * starting with five zeroes is not. Even more complicated would be to find a
   * hash starting with ten zeroes, and we get a general idea.
   * <p>
   * So, how exactly can we do this? Honestly, the solution is much less fancy!
   * It's with brute force that we attempt to achieve this goal. We make use of
   * nonce here: Let's see what we trying to do here are:
   *
   * <li>We start by defining the prefix we desire to find
   * <li>Then we check whether we've found the solution
   * <li>If not we increment the nonce and calculate the hash in a loop
   * <li>The loop goes on until we hit the jackpot
   *
   * We're starting with the default value of nonce here and incrementing it by
   * one. But there are more sophisticated strategies to start and increment a
   * nonce in real-world applications. Also, we're not verifying our data here,
   * which is typically an important part.
   *
   * @param difficulty
   *                     (1-5) how many leading zero should be
   * @see #setBounds(int,int,int,int)
   * @since e71a2d1dccace93e151f4c6f8f75ab17cd9ed728
   */
  public void mineBlock(int difficulty) {
    // Create a string with difficulty * "0"
    String target = new String(new char[difficulty]).replace('\0', '0');
    while (!hash.substring(0, difficulty).equals(target)) {
      nonce++;
      hash = calculateHash();
    }
    System.out.println("Block Mined at difficulty:= " + difficulty + ", hash:= " + hash + ", target:= " + target);
  }

  /**
   * getData from Block
   *
   * @return String data
   */
  public String getData() {
    return data;
  }

  /**
   * convertTime print timestamp long in reable format
   *
   * @param value
   * @return date String
   */
  public String convertTime(long time) {
    Date date = new Date(time);
    Format format = new SimpleDateFormat("yyyy MM dd HH:mm:ss");
    return format.format(date);
  }

  public List<String> merkleTree() {

    ArrayList<String> tree = new ArrayList<>();

    // Start by adding all the hashes of the transactions as leaves of the
    // tree.

    for (T t : transactions) {
      tree.add(t.hash());
    }

    int levelOffset = 0; // Offset in the list where the currently processed level starts. Step
                         // througheach level, stopping when we reach the root (levelSize == 1).

    for (int levelSize = transactions.size(); levelSize > 1; levelSize = (levelSize + 1) / 2) {
      // For each pair of nodes on that level:
      for (int left = 0; left < levelSize; left += 2) {

        // The right hand node can be the same as the left hand, in the case where we
        // don't have enough transactions.
        int right = Math.min(left + 1, levelSize - 1);
        String tleft = tree.get(levelOffset + left);
        String tright = tree.get(levelOffset + right);
        tree.add(SHA256.generateHash(tleft + tright));
      }

      // Move to the next level.
      levelOffset += levelSize;
    }
    return tree;

  }

  /**
   * Block Constructor.
   *
   * @param data
   *                       information in block data of blockchain
   * @param previousHash
   *                       link to previosus block
   * @return
   */

  public Block(String data, String previousHash) {
    this.data = data;
    this.previousHash = previousHash;
    this.timeStamp = new Date().getTime();
    this.hash = calculateHash(); // Making sure we do this after we set the other values.

    System.out.println("\nCreate block data: " + this.data + ", timestamp: " + Long.toString(timeStamp)
        + ", timeStamp in readable:= " + convertTime(timeStamp));
  }
}
package blockchain;

import java.security.Key;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Base64;

public class StringUtil {
  // Applies ECDSA Signature and returns the result ( as 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;
  }

  // Verifies a String signature
  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());
  }

  /**
   * Apply SHA256 to input. Applies Sha256 to a string and returns the result.
   *
   * @param input
   * @return String hexString.toString()
   */
  public static String applySha256(String input) {
    try {
      MessageDigest digest = MessageDigest.getInstance("SHA-256");
      // Applies sha256 to our input,
      byte[] hash = digest.digest(input.getBytes("UTF-8"));
      StringBuffer hexString = new StringBuffer(); // This will contain hash as hexidecimal
      for (int i = 0; i < hash.length; i++) {
        String hex = Integer.toHexString(0xff & hash[i]);
        if (hex.length() == 1) {
          hexString.append('0');
        }
        hexString.append(hex);
      }
      return hexString.toString();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}
package blockchain;

import java.security.*;
import java.security.spec.ECGenParameterSpec;

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

  /**
   * @return
   */
  public Wallet() {
    generateKeyPair();
  }

  public PublicKey get() {
    return publicKey;
  }

  public void generateKeyPair() {
    try {
      KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA", "BC");
      SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
      ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime192v1");
      // Initialize the key generator and generate a KeyPair
      keyGen.initialize(ecSpec, random); // 256 bytes provides an acceptable security level
      KeyPair keyPair = keyGen.generateKeyPair();
      // Set the public and private keys from the keyPair
      privateKey = keyPair.getPrivate();
      publicKey = keyPair.getPublic();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}
public class TransactionInput {
  public String transactionOutputId; // Reference to TransactionOutputs -> transactionId
  public TransactionOutput UTXO; // Contains the Unspent transaction output

  public TransactionInput(String transactionOutputId) {
    this.transactionOutputId = transactionOutputId;
  }
}
import java.security.PublicKey;

public class TransactionOutput {
  public String id;
  public PublicKey reciepient; // also known as the new owner of these coins.
  public float value; // the amount of coins they own
  public String parentTransactionId; // the id of the transaction this output was created in

  // 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);
  }

  // Check if coin belongs to you
  public boolean isMine(PublicKey publicKey) {
    return (publicKey == reciepient);
  }

}
package blockchain;

import com.google.gson.GsonBuilder;
import java.util.ArrayList;

public class NoobChain {
  public static ArrayList<Block> blockchain = new ArrayList<Block>();
  public static int difficulty = 6;

  /**
   * @return Boolean
   */
  public static Boolean isChainValid() {
    Block currentBlock;
    Block previousBlock;
    String hashTarget = new String(new char[difficulty]).replace('\0', '0');

    // loop through blockchain to check hashes:
    for (int i = 1; i < blockchain.size(); i++) {
      currentBlock = blockchain.get(i);
      previousBlock = blockchain.get(i - 1);
      // compare registered hash and calculated hash:
      if (!currentBlock.hash.equals(currentBlock.calculateHash())) {
        System.out.println("Current Hashes not equal");
        return false;
      }
      // compare previous hash and registered previous hash
      if (!previousBlock.hash.equals(currentBlock.previousHash)) {
        System.out.println("Previous Hashes not equal");
        return false;
      }
      // check if hash is solved
      if (!currentBlock.hash.substring(0, difficulty).equals(hashTarget)) {
        System.out.println("This block hasn't been mined");
        return false;
      }
    }
    return true;
  }

  /**
   * Noob blockchain.
   *
   * @param args
   *               no arguments
   */
  public static void main(String[] args) {

    blockchain.add(new Block("root", "0"));
    blockchain.get(0).mineBlock(difficulty);
    for (int j = 1; j < 10; j++) {
      blockchain.add(new Block("Yo im the " + j + " block", blockchain.get(blockchain.size() - 1).hash));
      blockchain.get(j).mineBlock(difficulty);
    }
    System.out.println("\nBlockchain is Valid: " + isChainValid());
    String blockchainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockchain);
    System.out.println(blockchainJson);
  }
}
package blockchain;

import java.security.Security;
import java.util.ArrayList;

public class NoobChainV2 {
  public static ArrayList<Block> blockchain = new ArrayList<Block>();
  public static int difficulty = 5;

  public static Wallet walletA;
  public static Wallet walletB;

  /**
   * @return Boolean
   */
  public static Boolean isChainValid() {
    Block currentBlock;
    Block previousBlock;
    String hashTarget = new String(new char[difficulty]).replace('\0', '0');

    // loop through blockchain to check hashes:
    for (int i = 1; i < blockchain.size(); i++) {
      currentBlock = blockchain.get(i);
      previousBlock = blockchain.get(i - 1);
      // compare registered hash and calculated hash:
      if (!currentBlock.hash.equals(currentBlock.calculateHash())) {
        System.out.println("Current Hashes not equal");
        return false;
      }
      // compare previous hash and registered previous hash
      if (!previousBlock.hash.equals(currentBlock.previousHash)) {
        System.out.println("Previous Hashes not equal");
        return false;
      }
      // check if hash is solved
      if (!currentBlock.hash.substring(0, difficulty).equals(hashTarget)) {
        System.out.println("This block hasn't been mined");
        return false;
      }
    }
    return true;
  }

  /**
   * Noob blockchain.
   *
   * @param args
   *               no arguments
   */

  public static void main(String[] args) {
    // Setup Bouncey castle as a Security Provider
    Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    // Create the new wallets
    walletA = new Wallet();
    walletB = new Wallet();
    // Test public and private keys
    System.out.println("Private key: " + StringUtil.getStringFromKey(walletA.privateKey));
    System.out.println("Public key: " + StringUtil.getStringFromKey(walletA.publicKey));
    // Create a test transaction from WalletA to walletB
    Transaction transaction = new Transaction(walletA.publicKey, walletB.publicKey, 5, null);
    transaction.generateSignature(walletA.privateKey);
    // Verify the signature works and verify it from the public key
    System.out.println("Is signature verified: " + transaction.verifiySignature());
  }
}

References

  1. https://medium.com/programmers-blockchain/creating-your-first-blockchain-with-java-part-2-transactions-2cdac335e0ce