Coders Packet

Block-Chain based Crypto Wallet with transactions using Core Java, ECDSA algorithm.

By Akash N

  • Wallet/
  • A console-based crypto wallet for sending signed transactions using a blockchain and digital signature built in Java with ECDSA algorithm.

    Introduction :


    Blockchain is an interlinked chain of blocks consisting of data. Cryptocurrencies like Bitcoin, Etherium are notable examples of Blockchain technology. In this project, I have designed a simple Crypto wallet using Java.


    Its functionalities include :

    1. Allowing users to create their own crypto wallet.

    2. Generating public and private keys through the Elliptic-Curve cryptography algorithm.

    3. Using the digital Signature to securely transfer funds.

    4. Allowing users to make transactions on the blockchain.

     

    Let's dive directly into the coding part.

     

    Java Classes


    1. Block: Each block stores the data about the time created, previous hash, current hash(digital fingerprint), data about transactions, nonce.

    The current hash value is calculated using the SHA256 algorithm with the remaining data of the block given as input. Hence, any change in the hash value of the previous block results in a change in the current block's hash value in turn changing all the hash values of the blocks thereafter.

    A Block also contains the mine_block method with a difficulty number 'count' given as input. The reason for doing proof of work for mining a block instead of just adding it to the chain is to increase the difficulty in adding a block by making the miners perform CPU-intensive tasks which in turn avoids tampering of blockchain by a malicious user.

    Proof of work is a piece of evidence for verifiers that miners have put a certain amount of computational effort by the means of computationally intensive tasks. 

    Here the task is to make the first 'count' elements of the hash value "0". The more 'count' value, the more is difficulty in mining a block.

     

    import java.util.Date;
    import java.util.ArrayList;
    
    public class Block {
    
      String my_hash;
      String prev_hash;
      String merkle_root;
      
      public ArrayList txns = new ArrayList();
      
      
      private long time_created;
      private int nonce;
      
      public Block(String prev_hash) {
        
      
        this.prev_hash = prev_hash;
        this.time_created = new Date().getTime();
        this.my_hash = hash_calculate();
        }	
    
      public String hash_calculate() {
        String hash = Sha256.apply_sha256(prev_hash+
                                      Long.toString(time_created)+
                                      Integer.toString(nonce)+
                                      merkle_root);
        
        return hash;
      }
      
      public void mine_block(int count)
      {
        merkle_root = StringUtil.get_merkle_root(txns);
        String target = new String(new char[count]).replace('\0','0');
        // \0 is a null character
        
        while(!my_hash.substring(0,count).equals(target))
        {
          nonce++;
          my_hash = hash_calculate();
                
        }
        
        
        System.out.println("Block Mined!!! : " + my_hash);
      }
      
      public boolean add_transaction(Transaction txn)
      {
        if(txn == null) return false;
        
        if(prev_hash!= "0")
        {
          if(txn.process_transaction()!= true)
          {
            System.out.println("Transaction could not be processed.Discarding the transaction ");
            return false;
          }
        }
        
        txns.add(txn);
        
        System.out.println("Transaction added to the block Successfully");
        
        return true;
      }
    }
    
    

     

    2. Sha256: is a class consisting of a method that takes all the strings of a block combinedly as input, applies the SHA256 algorithm, and returns a hash string of length 64. There are many other algorithms as well, but for this project, I have chosen SHA256.

    SHA256 algorithm can be accessed by importing java. security.MessageDigest. Even though the output by the SHA256 algorithm is 256 bits, it is converted into a 64-bit hexadecimal format to enhance readability.

     

    import java.security.MessageDigest;
    
    
    public class Sha256 {
    
      public static String apply_sha256(String input) {
        
        try {
          
          MessageDigest digest = MessageDigest.getInstance("SHA-256");
          byte[] hash = digest.digest(input.getBytes("UTF-8"));
          
          StringBuilder hex_string = new StringBuilder();
          
          for(int i=0;i<hash.length;i++)
          {
            String hex = Integer.toHexString(0xff & hash[i]);
            
            if(hex.length()==1)
            {
              hex_string.append('0');
              //to add the leading zeroes if they are dropped
              
            }
            
            hex_string.append(hex);
          }
          
          return hex_string.toString();
          
        }
        
        catch(Exception e)
        {
          throw new RuntimeException(e);
        }
        
      }
    }
    

     

    3. StringUtil: is a class consisting of two methods 

    1. 'apply_ECDSA_sign' that takes private_key and the output string from the SHA256 algorithm and returns the digital signature.

    2. 'verify_ECDSA_sign' that takes public_key, the output string from the SHA256 algorithm, and the digital signature and checks if the signature is valid.

    Note: Digital signature is generated using Private key and for verification of transactions data if it has tampered or not Public Key is used.

     

    import java.security.Key;
    import java.security.PrivateKey;
    import java.security.PublicKey;
    import java.security.Signature;
    import java.util.ArrayList;
    import java.util.Base64;
    
    public class StringUtil {
      
      public static byte[] apply_ECDSA_Sign(PrivateKey private_key , String in_put)
      {
        
        Signature signature;
        
        byte[] out_put = new byte[0];
        
        try {
          
          signature = Signature.getInstance("ECDSA" , "BC");
          signature.initSign(private_key);			 
          
          signature.update(in_put.getBytes());
          
          out_put = signature.sign();
          
          
        }
        
        catch(Exception e)
        {
          throw new RuntimeException(e);
        }
        
        return out_put;
      }
      
      public static boolean verify_ECDSA_Sign(PublicKey public_key , String in_put, byte[] input_signature)
      {
        try {
          Signature sign = Signature.getInstance("ECDSA" , "BC");
          sign.initVerify(public_key);
          sign.update(in_put.getBytes());
          
          return sign.verify(input_signature);
        }
        
        catch(Exception e) {
          throw new RuntimeException(e);
        }
      }
      
      public static String get_string_from_key(Key input_key)
      {
        return Base64.getEncoder().encodeToString(input_key.getEncoded());
      }
      
      public static String get_merkle_root(ArrayList txns)
      {
        int count = txns.size();
        
        ArrayList previous_tree_layer = new ArrayList();
        
        for(Transaction txn : txns)
        {
          previous_tree_layer.add(txn.transactionId);
        }
        
        ArrayList tree_layer = previous_tree_layer;
        
        while(count > 1)
        {
          tree_layer = new ArrayList();
          
          for(int i=1 ; i< previous_tree_layer.size(); i++)
          {
            tree_layer.add(Sha256.apply_sha256(previous_tree_layer.get(i-1) + previous_tree_layer.get(i)));
            
          }
          
          count = tree_layer.size();
          previous_tree_layer = tree_layer;
          
        }
        
        String merkle_root = (tree_layer.size() == 1)? tree_layer.get(0) : "";
        
        return merkle_root;
      }
      
      
    
    
    }
    

     

    4. Wallet: It is by using the objects of this method that transactions are initiated. Also, a Public key and Private key unique to a Wallet are generated in this class. Wallet's balance can also be checked.

     

    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 private_key;
      public PublicKey public_key;
      
      public HashMap unspent_txn_ops = new HashMap();
      
      public Wallet() {
        generate_keys();
      }
      
      public float get_balance()
      {
        float balance = 0;
        
        for(Map.Entry item : Blockchain.unspent_txn_ops.entrySet())
        {
          Transaction_output unspent_txn_op = item.getValue();
          
          if(unspent_txn_op.is_mine(public_key))
          {
            unspent_txn_ops.put(unspent_txn_op.id , unspent_txn_op);
            
            balance+=unspent_txn_op.value;
          }
          
        }
        
        return balance;
      }
      
      public Transaction send_funds(PublicKey reciever , float input_value)
      {
        if(get_balance() < input_value)
        {
          System.out.println("Insufficient funds .Transaction discarded .");
          return null;
        }
        
        ArrayList inputs = new ArrayList();
        
        float balance = 0;
        
        for(Map.Entry item : unspent_txn_ops.entrySet())
        {
          Transaction_output unspent_txn_op = item.getValue();
          
          balance+=unspent_txn_op.value;
          inputs.add(new Transaction_input(unspent_txn_op.id));
          
          if(balance>value) break;
          
        }
        
        Transaction new_txn = new Transaction(public_key , reciever , input_value , inputs );
        
        new_txn.generate_signature(private_key);
        
        for(Transaction_input input : inputs)
        {
          unspent_txn_ops.remove(input.txn_output_id);
        }
        
        return new_txn;
        
      }
      
      public void generate_keys() {
        
        try {
          
          KeyPairGenerator key_generater = KeyPairGenerator.getInstance("ECDSA","BC");
          SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
          ECGenParameterSpec ec_spec = new ECGenParameterSpec("prime192v1");
          
          key_generater.initialize(ec_spec, random);
          KeyPair key_pair = key_generater.generateKeyPair();
          
          private_key = key_pair.getPrivate();
          public_key = key_pair.getPublic();
        }catch(Exception e) {
          throw new RuntimeException(e);
        }
      }
      
      
      
    }
    

    5. Transaction: this is the place where we store all the necessary information regarding a transaction like transaction_id, public keys of receiver and sender, the amount to be sent, digital signature, etc. It is from here the generation and verification of the digital signature are initiated.

    import java.security.*;
    import java.util.ArrayList;
    import java.util.Base64;
    
    public class Transaction {
      
      public String transactionId;
      public PublicKey source;
      public PublicKey receiver;
      
      public float amount;
      public byte[] signature;
      
      public ArrayList input = new ArrayList();
      public ArrayList output = new ArrayList();
      
      public static int count = 0;
      
      public Transaction(PublicKey source , PublicKey receiver , float value , ArrayList input)
      {
        this.source = source;
        this.receiver = receiver;
        this.amount = amount;
        this.input = input;
      }
      
      public String calculateHash() {
        count++;
        
        return Sha256.apply_sha256(StringUtil.get_string_from_key(sender)+
                               StringUtil.get_string_from_key(receiver)+
                                  Float.toString(amount)+
                                  count
                                  );
        
      }
      
      
      public void generate_signature(PrivateKey private_key)
      {
        String data = StringUtil.get_string_from_key(source) + 
                  StringUtil.get_string_from_key(receiver) + 
                  Float.toString(amount);
        
        signature = StringUtil.apply_ECDSA_Sign(private_key , data);
      }
      
      public boolean verify_signature()
      {
        String data = StringUtil.get_string_from_key(source)+
                  StringUtil.get_string_from_key(receiver) + 
                  Float.toString(amount);
        
        return StringUtil.verify_ECDSA_Sign(source , data , signature);
      }
      
      
      public boolean process_transaction()
      {
        
        if(verify_signature()==false)
        {
          System.out.println(" Signature could not be verified");
          return false;
          
        }
        
        for(Transaction_input i : input )
        {
          i.unspent_txn_op = Blockchain.unspent_txn_ops.get(i.txn_output_id);
        }
        
        if(get_inputs_value() < Blockchain.minimum_transaction)
        {
          System.out.println(" Transaction Inputs too small: " + get_inputs_value());
          
          return false;
        }
        
        float left_over = get_inputs_value() - amount;
        
        String transaction_id = calculateHash();
        
        output.add(new Transaction_output(this.receiver , amount , transaction_id));
        output.add(new Transaction_output(this.source , left_over , transaction_id ));
        
        for(Transaction_output o : output)
        {
          Blockchain.unspent_txn_ops.put(o.id , o);
        }
        
        for(Transaction_input i : input)
        {
          if(i.unspent_txn_op == null)
            continue;
          
          Blockchain.unspent_txn_ops.remove(i.unspent_txn_op.id);
        }
        
        return true;
        
      }
      
      
      public float get_inputs_value()
      {
        float total = 0;
        
        for(Transaction_input i : input)
        {
          if(i.unspent_txn_op == null)
            continue;
          
          total += i.unspent_txn_op.amount;
        }
        
        return total;
      }
      
      public float get_outputs_value()
      {
        float total =0;
        
        for(Transaction_output o : output)
        {
          total += o.amount;
        }
        
        return total;
        
        
      }
      
      
      
      
    }
    

     

    6. Transaction_input: this class stores the unspent transactions and their id's.

    public class Transaction_input {
      public String txn_output_id;
      public Transaction_output unspent_txn_op;
      
      public Transaction_input(String txn_output_id)
      {
        this.txn_output_id = txn_output_id;
      }
    
    }
    

    7. Transaction_output:  This will store the last amount sent in that transaction. This will help us to check if there is sufficient fund to transfer.

    import java.security.PublicKey;
    
    public class Transaction_output {
      
      public String id;
      public PublicKey receiver;
      public float amount;
      public String parent_txn_id;
      
      public Transaction_output(PublicKey receiver , float amount , String parent_txn_id)
      {
        this.receiver = receiver;
        this.amount = amount;
        this.parent_txn_id = parent_txn_id;
        this.id = Sha256.apply_sha256(StringUtil.get_string_from_key(receiver)+
            Float.toString(amount) +
            parent_txn_id);
            
      }
      
      public boolean is_mine(PublicKey public_key)
      {
        return (public_key == receiver);
      }
    
    }
    

     

    8. Blockchain: This is an ArrayList consisting of a list of transactions. We need to test the blockchain by sending and receiving the transactions and check the validity of the blockchain. By hard-coding, I have released all the coins necessary, in the first block (the genesis block).

     

    import java.security.Security;
    import java.util.HashMap;
    import org.bouncycastle.*;
    import java.util.ArrayList;
    import java.util.HashMap;
    
    public class Blockchain {
      
      public static ArrayList block_chain = new ArrayList();
      public static HashMap unspent_txn_ops = new HashMap();
      public static int count = 3;
      public static float minimum_transaction = 0.1f;
      public static Wallet wallet_1;
      public static Wallet wallet_2;
      
      public static Transaction first_txn;
      
      public static void main(String[] args)
      {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        
        wallet_1 = new Wallet();
        wallet_2 = new Wallet();
        
        Wallet coin_base = new Wallet();
        
        first_txn = new Transaction(coin_base.public_key , wallet_1.public_key , 100f , null );
        first_txn.generate_signature((coin_base.private_key));
        first_txn.transactionId = "0";
        first_txn.output.add(new Transaction_output(first_txn.receiver , first_txn.value , first_txn.transactionId ));
        unspent_txn_ops.put(first_txn.output.get(0).id ,first_txn.output.get(0));
        
        
        System.out.println("Creating and Mining First block ....\n");
        Block first = new Block("0");
        first.add_transaction(first_txn);
        add_block(first);
        
        
        Block block_1 = new Block(first.my_hash);
        
        System.out.println("\nWallet 1's balance is : " + wallet_1.get_balance());
        System.out.println("\nWallet 1 is attempting to send funds (25) to Wallet 2 .. ");
        
        block_1.add_transaction(wallet_1.send_funds(wallet_2.public_key, 25f));
        
        add_block(block_1);
        
        System.out.println("\nWallet 1's balance is :" + wallet_1.get_balance());
        System.out.println("\nWallet 2's balance is :" + wallet_2.get_balance());
        
        
            Block block_2 = new Block(block_1.my_hash);
        
        
        System.out.println("\nInsufficient wallet balance in wallet 1 ");
        
        block_2.add_transaction(wallet_1.send_funds(wallet_2.public_key, 400f));
        
        add_block(block_2);
        
        System.out.println("\nWallet 1's balance is :" + wallet_1.get_balance());
        System.out.println("\nWallet 2's balance is :" + wallet_2.get_balance());
        
        
            Block block_3 = new Block(block_2.my_hash);
        
        
        System.out.println("\nWallet 2 is attempting to send funds (10) to Wallet 1 .. ");
        
        
        block_3.add_transaction(wallet_2.send_funds(wallet_1.public_key, 10f));
        
        add_block(block_3);
        
        System.out.println("\nWallet 1's balance is :" + wallet_1.get_balance());
        System.out.println("\nWallet 2's balance is :" + wallet_2.get_balance());
        
        is_chain_valid();
        
        
      }
      
      public static Boolean is_chain_valid()
      {
        Block current_block; 
        Block previous_block;
        String hash_target = new String(new char[count]).replace('\0', '0');
        HashMap<String,Transaction_output> temp_UTXOs = new HashMap<String,Transaction_output>();
        temp_UTXOs.put(first_txn.output.get(0).id, first_txn.output.get(0));
        
        
        for(int i=1; i < block_chain.size(); i++) {
          
          current_block = block_chain.get(i);
          previous_block = block_chain.get(i-1);
         
          if(!current_block.my_hash.equals(current_block.hash_calculate()) ){
            System.out.println("Current hash values donot match");
            return false;
          }
         
          if(!previous_block.my_hash.equals(current_block.prev_hash) ) {
            System.out.println("Previous hash values donot match");
            return false;
          }
         
          if(!current_block.my_hash.substring( 0, count).equals(hash_target)) {
            System.out.println("Block could not be mined ");
            return false;
          }
          
          
          Transaction_output temp_output;
          for(int x=0; x <current_block.txns.size(); x++) {
            Transaction current_txn = current_block.txns.get(x);
            
            if(!current_txn.verify_signature()) {
              System.out.println("Signature is Invalid on transaction(" + x + ")");
              return false; 
            }
            if(current_txn.get_inputs_value() != current_txn.get_outputs_value()) {
              System.out.println("Inputs on transaction(" + x + ") are not equal to outputs ");
              return false; 
            }
            
            for(Transaction_input input: current_txn.input) {	
              temp_output = temp_UTXOs.get(input.txn_output_id);
              
              if(temp_output == null) {
                System.out.println("Referenced input is missing on transaction(" + x + ") ");
                return false;
              }
              
              if(input.unspent_txn_op.value != temp_output.value) {
                System.out.println("Referenced input  value is Invalid on Transaction(" + x + ")");
                return false;
              }
              
              temp_UTXOs.remove(input.txn_output_id);
            }
            
            for(Transaction_output output: current_txn.output) {
              temp_UTXOs.put(output.id, output);
            }
            
            if( current_txn.output.get(0).receiver != current_txn.receiver) {
              System.out.println("transaction(" + x + ") output receiver is different");
              return false;
            }
            if( current_txn.output.get(1).receiver != current_txn.sender) {
              System.out.println("transaction(" + x + ") output  is not sender.");
              return false;
            }
            
          }
          
        }
        System.out.println("blockchain is valid");
        return true;
      }
      
      public static void add_block(Block new_block)
      {
        new_block.mine_block(count);
        block_chain.add(new_block);
      }
      
    
    }

     

    Output:

    output

     

     

    Download Complete Code

    Comments

    Comments for the packet "Block-Chain based Crypto Wallet with transactions using Core Java, ECDSA algorithm.";
    No comments yet