오늘도 개발자 Backend Developer

Java로 배워보는 BlockChain

  • 해당 내용은 공부하면서 간단하게 만들어본 블록체인 입니다.

1. Block 만들기

  • BlockChain을 구성하는 Block 을 생성한다,
  • Block은 BlockChain 내에서 고유한 Hash(Digital Signature)값을 가지고 있다.
  • Block 내에는 이전 Block의 Hash 값과 데이터들이 포함되어 있다.

1) 소스코드

package kr.geun.o.bc.basic;

import java.util.Date;

/**
 * Block Class
 *
 * @author akageun
 */
public class Block {

   private String hash;
   private String previousHash;

   private String data;
   private long timeStamp;

   private Block() {

   }

   /**
    * Block Constructor
    *
    * @param data
    * @param previousHash
    */
   public Block(String data, String previousHash) {
      this.data = data;
      this.previousHash = previousHash;

      this.timeStamp = new Date().getTime();
   }

   /**
    * get previousHash
    *
    * @return
    */
   public String getPreviousHash() {
      return previousHash;
   }

   /**
    * get Hash
    *  - digital signature
    *
    * @return
    */
   public String getHash() {
      return hash;
   }

   /**
    * get TimeStamp
    *
    * @return
    */
   public long getTimeStamp() {
      return timeStamp;
   }

   /**
    * get Data
    *
    * @return
    */
   public String getData() {
      return data;
   }
}

2. Hash(Digital Signature) 생성 메소드 만들기

  • Sha256을 사용.
package kr.geun.o.bc.basic;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * BlockChain Utils
 *
 * @author akageun
 */
public class BcUtils {

   /**
    * Hash 생성
    *
    * @param inputValues
    * @return
    */
   public static String generateHash(String... inputValues) {
      try {
         StringBuffer sb = new StringBuffer();
         for (String inputValue : inputValues) {
            sb.append(inputValue);
         }

         String input = sb.toString();

         MessageDigest digest = MessageDigest.getInstance("SHA-256");

         byte[] hash = digest.digest(input.getBytes("UTF-8"));
         StringBuffer hexString = new StringBuffer();

         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 (NoSuchAlgorithmException e) {
         e.printStackTrace();
         throw new RuntimeException(e);

      } catch (UnsupportedEncodingException e) {
         e.printStackTrace();
         throw new RuntimeException(e);
      }
   }
}

3. Hash 계산로직 추가.

  • 위 BcUtils 를 이용하여 Block 안 Hash값을 만드는 로직을 추가해보자.
  • Block 내 메소드 추가(소스코드)
/**
 * Hash값 계산하기!
 *
 * @return
 */
public String calculateHash() {
   return BcUtils.generateHash(previousHash, Long.toString(timeStamp), data);
}
  • 생성자 내에 해당 메소드 호출 추가
/**
 * Block Constructor
 *
 * @param data
 * @param previousHash
 */
public Block(String data, String previousHash) {
   this.data = data;
   this.previousHash = previousHash;

   this.timeStamp = new Date().getTime();
   this.hash = calculateHash(); //추가!!
}

4. Genesis Block 호출하기!

  • 첫번째 블럭 생성
Block genesisBlock = new Block("이건 Genesis Block 입니다.", "0");
System.out.println("Genesis Block Hash : " + genesisBlock.getHash());
  • 메인 메소드를 만들어 호출하면 아래와 같은 결과를 얻을 수 있다.(호출할 때마다 Hash값이 달라진다.)

Genesis Block Hash : f57e2c72feba62234118d1356799c367a802c342156a43ac2774698bb316941e

  • 몇개를 더 추가해서 출력해보면…. 아래와 같다.
Block genesisBlock = new Block("이건 Genesis Block 입니다.", "0");
System.out.println("Genesis Block Hash : " + genesisBlock.getHash());

Block secBlock = new Block("이건 두번째 블럭 입니다.", genesisBlock.getHash());
System.out.println("Second Block Hash : " + secBlock.getHash());

Block thirdBlock = new Block("이건 세번째 블럭 입니다.", secBlock.getHash());
System.out.println("Hash for block 3 : " + thirdBlock.getHash());

Genesis Block Hash : f57e2c72feba62234118d1356799c367a802c342156a43ac2774698bb316941e Second Block Hash : 082902f4d78eab658b964d9743532178073da2b9d529b00752fa5588034fb2a2 Hash for block 3 : 97a821109da7ed12fc3263452670d42b392d71772dd70b81720a23e1d898bf15

5. 블록체인 만들기.

  • 간단하게 static List를 만들어서 블럭들을 생성해 보자.
/**
 * BlockChain 메인 클래스
 *
 * @author akageun
 */
public class BlockChain {

   public static List<Block> BLOCK_CHAIN = new LinkedList<>();

   public static void main(String[] args) {

      BLOCK_CHAIN.add(new Block("이건 Genesis Block 입니다.", "0"));
      BLOCK_CHAIN.add(new Block("이건 두번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
      BLOCK_CHAIN.add(new Block("이건 세번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));

      for (Block block : BLOCK_CHAIN) {
         System.out.println("=========");

         System.out.println("hash : " + block.getHash());
         System.out.println("previousHash : " + block.getPreviousHash());
         System.out.println("timeStamp : " + block.getTimeStamp());
         System.out.println("Data : " + block.getData());
      }

      System.out.println("=========");
   }

}

  • 결과 ```

=========

hash : 0f7e40c1f99be348a2a600f678ef6e64136d4ea6dc3d7457561e572129c628c9

previousHash : 0

timeStamp : 1532238023070

Data : 이건 Genesis Block 입니다.

=========

hash : b3a2226d4dafb77bfca41c2d792e7590df544e34450bfd5ecd69e71124f6baea

previousHash : 0f7e40c1f99be348a2a600f678ef6e64136d4ea6dc3d7457561e572129c628c9

timeStamp : 1532238023086

Data : 이건 두번째 블럭 입니다.

=========

hash : 57caa0507cbed37fbde89a4d80b2ea68450cff3698fba56856858093d8ac6982

previousHash : b3a2226d4dafb77bfca41c2d792e7590df544e34450bfd5ecd69e71124f6baea

timeStamp : 1532238023086

Data : 이건 세번째 블럭 입니다.


# 6. 무결성 검사
- 계산한 Hash값이 같은지, Block 내 PreviousBlock 과 실제 이전 블럭 Hash값이 같은지를 검증하는 로직
- 구현 소스

```java
/**
 * 블록 검증
 *
 *
 * @return
 */
private static boolean isValidBlockChain() {
   Block currentBlock;
   Block previousBlock;

   for (int i = 1; i < BLOCK_CHAIN.size(); i++) { //GenesisBlock 은 제외한다.
      currentBlock = BLOCK_CHAIN.get(i);
      previousBlock = BLOCK_CHAIN.get(i - 1);

      if (currentBlock.getHash().equals(currentBlock.calculateHash()) == false) { //만들어 놓은 Hash값과 계산한 Hash값이 같은지 검증!
         System.out.println("Not Equals Current Block Hash!!");
         return false;
      }

      if (currentBlock.getPreviousHash().equals(previousBlock.getHash()) == false) {
         System.out.println("Not Equals Previous Block Hash!!");
         return false;
      }

   }

   return true;
}

7. 채굴!(마이닝)

  • 작업증명’(Proof-of-Work) : BlockChain의 Block Hash는 Difficulty에 따라 선택된 Target 데이터 규격을 만족해야 한다.
  • Block Class 수정( nonce 값 추가)
private int nonce;

/**
 * get nonce
 * 
 * @return
 */
public int getNonce() {
   return nonce;
}
  - Hash  연산하는 부분에 nonce  추가

/**
 * Hash값 계산하기!
 *
 * @return
 */
public String calculateHash() {
   return BcUtils.generateHash(previousHash, Long.toString(timeStamp), Integer.toString(nonce) , data);
}
  - 채굴 함수 추가

/**
 * Mining
 */
public void mineBlock() {
   char[] targetChar = new char[BlockChain.DIFFICULTY];
   Arrays.fill(targetChar, '0');
   String target = String.valueOf(targetChar);

   while (hash.substring(0, BlockChain.DIFFICULTY).equals(target) == false) {
      nonce++;
      hash = calculateHash();
   }

   System.out.println("Block Mined!!! : " + hash);
}

  • 난이도 추가

public static int DIFFICULTY = 5;

  • 무결성 검증 코드 내 로직 추가
if (currentBlock.getHash().substring(0, DIFFICULTY).equals(hashTarget) == false) {
   System.out.println("This block hasn't been mined");
   return false;
}
  • 채굴해보기
BLOCK_CHAIN.add(new Block("이건 Genesis Block 입니다.", "0"));
BLOCK_CHAIN.get(0).mineBlock();

BLOCK_CHAIN.add(new Block("이건 두번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
BLOCK_CHAIN.get(1).mineBlock();

BLOCK_CHAIN.add(new Block("이건 세번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
BLOCK_CHAIN.get(2).mineBlock();

BLOCK_CHAIN.add(new Block("이건 네번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
BLOCK_CHAIN.get(3).mineBlock();

  • 결과

Block Mined!!! : 00000fdd8a35c69f5925051899635e1704af98bdd7da3504ed9bc36b5be30901

Block Mined!!! : 00000d8e9a9e735e8f7cbdae41458379ab8b4ac3de7826671eb10f2b7def35bd

Block Mined!!! : 000005dd4dc19710d82dda43d230ec6eabc8041af0fc0a489279802d172f8d41

Block Mined!!! : 000006ff67b1e5735d8891e3e389620f541aa3467e73471a4e2d7b11f841ed3a

=========

hash : 00000fdd8a35c69f5925051899635e1704af98bdd7da3504ed9bc36b5be30901

previousHash : 0

timeStamp : 1532240373740

Data : 이건 Genesis Block 입니다.

nonce : 465518

=========

hash : 00000d8e9a9e735e8f7cbdae41458379ab8b4ac3de7826671eb10f2b7def35bd

previousHash : 00000fdd8a35c69f5925051899635e1704af98bdd7da3504ed9bc36b5be30901

timeStamp : 1532240374917

Data : 이건 두번째 블럭 입니다.

nonce : 1603790

=========

hash : 000005dd4dc19710d82dda43d230ec6eabc8041af0fc0a489279802d172f8d41

previousHash : 00000d8e9a9e735e8f7cbdae41458379ab8b4ac3de7826671eb10f2b7def35bd

timeStamp : 1532240378568

Data : 이건 세번째 블럭 입니다.

nonce : 2130959

=========

hash : 000006ff67b1e5735d8891e3e389620f541aa3467e73471a4e2d7b11f841ed3a

previousHash : 000005dd4dc19710d82dda43d230ec6eabc8041af0fc0a489279802d172f8d41

timeStamp : 1532240383239

Data : 이건 네번째 블럭 입니다.

nonce : 504539

=========

This Blockchain is Valid : true


전체소스

1. Block 소스


package kr.geun.o.bc.basic;

import java.util.Arrays;
import java.util.Date;

/**
 * Block Class
 *
 * @author akageun
 */
public class Block {

   private String hash;
   private String previousHash;

   private String data;
   private long timeStamp;

   private int nonce;

   private Block() {

   }

   /**
    * Block Constructor
    *
    * @param data
    * @param previousHash
    */
   public Block(String data, String previousHash) {
      this.data = data;
      this.previousHash = previousHash;

      this.timeStamp = new Date().getTime();
      this.hash = calculateHash();
   }

   /**
    * Hash값 계산하기!
    *
    * @return
    */
   public String calculateHash() {
      return BcUtils.generateHash(previousHash, Long.toString(timeStamp), Integer.toString(nonce), data);
   }

   /**
    * Mining
    */
   public void mineBlock() {
      char[] targetChar = new char[BlockChain.DIFFICULTY];
      Arrays.fill(targetChar, '0');
      String target = String.valueOf(targetChar);

      while (hash.substring(0, BlockChain.DIFFICULTY).equals(target) == false) {
         nonce++;
         hash = calculateHash();
      }

      System.out.println("Block Mined!!! : " + hash);
   }

   /**
    * get previousHash
    *
    * @return
    */
   public String getPreviousHash() {
      return previousHash;
   }

   /**
    * get Hash
    *  - digital signature
    *
    * @return
    */
   public String getHash() {
      return hash;
   }

   /**
    * get TimeStamp
    *
    * @return
    */
   public long getTimeStamp() {
      return timeStamp;
   }

   /**
    * get Data
    *
    * @return
    */
   public String getData() {
      return data;
   }

   /**
    * get nonce
    *
    * @return
    */
   public int getNonce() {
      return nonce;
   }
}

2. BcUtils

package kr.geun.o.bc.basic;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * BlockChain Utils
 *
 * @author akageun
 */
public class BcUtils {

   /**
    * Hash 생성
    *
    * @param inputValues
    * @return
    */
   public static String generateHash(String... inputValues) {
      try {
         StringBuffer sb = new StringBuffer();
         for (String inputValue : inputValues) {
            sb.append(inputValue);
         }

         String input = sb.toString();

         MessageDigest digest = MessageDigest.getInstance("SHA-256");

         byte[] hash = digest.digest(input.getBytes("UTF-8"));
         StringBuffer hexString = new StringBuffer();

         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 (NoSuchAlgorithmException e) {
         e.printStackTrace();
         throw new RuntimeException(e);

      } catch (UnsupportedEncodingException e) {
         e.printStackTrace();
         throw new RuntimeException(e);
      }
   }
}

3. BlockChain


package kr.geun.o.bc.basic;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * BlockChain 메인 클래스
 *
 * @author akageun
 */
public class BlockChain {

   public static List<Block> BLOCK_CHAIN = new LinkedList<>();

   public static int DIFFICULTY = 5;

   public static void main(String[] args) {

      BLOCK_CHAIN.add(new Block("이건 Genesis Block 입니다.", "0"));
      BLOCK_CHAIN.get(0).mineBlock();

      BLOCK_CHAIN.add(new Block("이건 두번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
      BLOCK_CHAIN.get(1).mineBlock();

      BLOCK_CHAIN.add(new Block("이건 세번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
      BLOCK_CHAIN.get(2).mineBlock();

      BLOCK_CHAIN.add(new Block("이건 네번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
      BLOCK_CHAIN.get(3).mineBlock();

      for (Block block : BLOCK_CHAIN) {
         System.out.println("=========");

         System.out.println("hash : " + block.getHash());
         System.out.println("previousHash : " + block.getPreviousHash());
         System.out.println("timeStamp : " + block.getTimeStamp());
         System.out.println("Data : " + block.getData());
         System.out.println("nonce : " + block.getNonce());
      }

      System.out.println("=========");

      System.out.println("This Blockchain is Valid : " + isValidBlockChain());

   }

   /**
    * 블록 검증
    *
    *
    * @return
    */
   private static boolean isValidBlockChain() {
      Block currentBlock;
      Block previousBlock;

      char[] target = new char[BlockChain.DIFFICULTY];
      Arrays.fill(target, '0');

      String hashTarget = String.valueOf(target);

      for (int i = 1; i < BLOCK_CHAIN.size(); i++) { //GenesisBlock 은 제외한다.
         currentBlock = BLOCK_CHAIN.get(i);
         previousBlock = BLOCK_CHAIN.get(i - 1);

         if (currentBlock.getHash().equals(currentBlock.calculateHash()) == false) { //만들어 놓은 Hash값과 계산한 Hash값이 같은지 검증!
            System.out.println("Not Equals Current Block Hash!!");
            return false;
         }

         if (currentBlock.getPreviousHash().equals(previousBlock.getHash()) == false) {
            System.out.println("Not Equals Previous Block Hash!!");
            return false;
         }

         if (currentBlock.getHash().substring(0, DIFFICULTY).equals(hashTarget) == false) {
            System.out.println("This block hasn't been mined");
            return false;
         }

      }

      return true;
   }
}


참고 페이지

  • https://medium.com/programmers-blockchain/create-simple-blockchain-java-tutorial-from-scratch-6eeed3cb03fa
  • http://guruble.com/java-%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8blockchain/