기사 메일전송
자바(Java)로 미니 블록체인 개발해봐요(4) - SECTION 03. 미니 블록체인의 틀을 직접 만들어보자 - 필자 나동빈은 '안경잡이' 개발자로 현재 프리랜서로 활약하고 있다.
  • 기사등록 2018-04-02 21:02:24
기사수정



  지난 시간에 우리는 자바(Java) 프로그래밍 언어를 이용해 간단히 SHA-256 해시 함수를 구현해보았다. 또한 임의의 난이도를 설정한 뒤에 해시 함수를 이용해 채굴(Mining)이 이루어지는 로직을 직접 프로그램으로 만들어 테스트해보는 시간을 가졌다. 이번 시간에는 가장 먼저 블록(Block) 클래스를 생성해 하나의 블록이 가지는 특성을 정의할 것이다. 이후에 블록체인 모듈을 개발하여 다수의 블록들이 서로 긴밀하게 연결되어 해시 값을 도출하는 과정을 직접 확인해보도록 하자.



  바로 이전 시간에 이어서 core 패키지 안에 새로운 클래스(Class)를 생성해준다. 클래스 이름은 Block으로 이 클래스는 하나의 블록에 대한 정보를 담고 처리하는 역할을 수행한다.



  클래스 생성 이후에는 다음과 같이 Block 클래스의 소스코드를 작성해준다.


package core;


import util.Util;


public class Block {


  private int blockID;

  private int nonce;

  private String data;


  public int getBlockID() {

    return blockID;

  }

  public void setBlockID(int blockID) {

    this.blockID = blockID;

  }

  public int getNonce() {

    return nonce;

  }

  public void setNonce(int nonce) {

    this.nonce = nonce;

  }

  public String getData() {

    return data;

  }

  public void setData(String data) {

    this.data = data;

  }

  public Block(int blockID, int nonce, String data) {

    this.blockID = blockID;

    this.nonce = nonce;

    this.data = data;

  }

  public void getInformation() {

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

    System.out.println("블록 번호: " + getBlockID());

    System.out.println("채굴 변수 값: " + getNonce());

    System.out.println("블록 데이터: " + getData());

    System.out.println("블록 해시: " + getBlockHash());

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

  }

  public String getBlockHash() {

    return Util.getHash(nonce + data);

  }

}


  위 소스코드에서 볼 수 있듯이 블록(Block) 클래스는 내부적으로 블록 번호(Block ID), 정답 값(Nonce), 데이터(Data)라는 세 개의 변수를 가지고 있다. 그리고 getInformation()이라는 메소드를 구현하여 현재 특정한 블록이 가지는 정보를 출력할 수 있도록 해주었다. 이제 이렇게 정의한 블록 클래스를 실제로 사용해보기 위해 BlockChainStarter 클래스의 소스코드를 수정해보자.


package core;


public class BlockChainStarter {


  public static void main(String[] args) {

    Block block = new Block(1, 0, "데이터");

    block.getInformation();

  }

}


  위 소스코드는 블록 번호가 ‘1’이고, 정답 값이 ‘0’이며 블록이 포함하고 있는 데이터가 ‘데이터’인 하나의 블록을 생성한 뒤에 그 블록에 대한 정보를 출력한다. 작성 이후에 이클립스(Eclipse)의 실행(F11) 버튼을 눌러 프로그램을 동작시키면 다음과 같은 결과가 출력된다.



  위와 같이 우리가 임의로 만들어 본 블록에 대한 정보가 성공적으로 출력되는 것을 알 수 있다. 이제 특정한 블록을 대상으로 하여 채굴(Mining)을 수행하는 메소드를 작성해보자. Block 클래스 내부에 다음과 같은 메소드를 추가해준다.


public void mine() {

  while(true) {

    if(getBlockHash().substring(0, 4).equals("0000")) {

      System.out.println(blockID + "번째 블록의 채굴에 성공했습니다.");

      break;

    }

    nonce++;

  }

}


  이제 다음과 같이 BlockChainStarter 클래스 내용을 다음과 같이 수정하여 우리가 임의로 만든 블록이 채굴을 수행한 이후에 블록 정보를 출력하도록 해주자.


package core;


public class BlockChainStarter {


  public static void main(String[] args) {

    Block block = new Block(1, 0, "데이터");

    block.mine();

    block.getInformation();

  }

}



  다시 프로그램을 실행해보면 위와 같이 특정한 블록에 대해 채굴이 이루어지고, 블록에 대한 정보가 출력되는 것을 알 수 있다. 또한 우리가 채굴 메소드를 구현할 때 블록 해시의 앞부분이 ‘0000’일 때 채굴에 성공하도록 설정해주었으므로 실제 출력 결과를 보았을 때 블록 해시 값의 앞부분이 ‘0000’인 것을 알 수 있다. 또한 현재 채굴의 난이도가 고정되어있도록 구현을 했으나 실제 대부분의 블록체인 시스템은 난이도가 채굴의 상황에 따라서 자동으로 변경되도록 개발된다.


  더불어 우리는 앞서 채굴 수행 메소드를 구현할 때 ‘데이터(Data)’를 해시 값 생성 함수의 입력 값으로 포함하도록 구현했다. 따라서 데이터를 조금만 변경해도 해시 값이 자동으로 변경될 것이다. 이를 확인하기 위해 다음과 같이 데이터의 내용을 ‘바뀐 데이터’로 바꾸어 실행해보자.


package core;


public class BlockChainStarter {


  public static void main(String[] args) {

    Block block = new Block(1, 0, "바뀐 데이터");

    block.mine();

    block.getInformation();

  }

}



  프로그램을 실행했을 때 위 그림과 같이 아까와는 완전히 다른 채굴 변수 값이 나오는 것을 알 수 있다. 데이터가 변경되었으므로 채굴을 성공시키는 변수 값이 아까와는 완전히 다른 정답 값(Nonce)인 것이다. 이처럼 데이터를 조금만 바꾸어도 정답 값과 해시 값이 완전히 변경된다는 점은 임의대로 해시 값을 쉽게 변경할 수 없도록 하므로 블록체인 시스템에 강력한 보안성을 제공한다.


  또한 현재는 간단히 데이터(Data)라고 표현했지만 실제 블록체인에서는 트랜잭션 정보가 이에 포함된다. 블록체인에서 특정한 블록의 내부 데이터가 변경되면 해시 값은 자동으로 변경되고, 이는 다른 블록에게도 영향을 미친다는 점에서 데이터 변조 여부를 쉽게 파악할 수 있다는 장점이 있다. 이를 직접 확인하기 위해 각 블록이 서로 연결된 형태인 블록체인(Block Chain) 구조를 만들어보자.


package core;


import util.Util;


public class Block {


  private int blockID;

  private String previousBlockHash;

  private int nonce;

  private String data;


  public int getBlockID() {

    return blockID;

  }

  public void setBlockID(int blockID) {

    this.blockID = blockID;

  }

  public String getPreviousBlockHash() {

    return previousBlockHash;

  }

  public void setPreviousBlockHash(String previousBlockHash) {

    this.previousBlockHash = previousBlockHash;

  }

  public int getNonce() {

    return nonce;

  }

  public void setNonce(int nonce) {

    this.nonce = nonce;

  }

  public String getData() {

    return data;

  }

  public void setData(String data) {

    this.data = data;

  }

  public Block(int blockID, String previousBlockHash, int nonce, String data) {

    this.blockID = blockID;

    this.previousBlockHash = previousBlockHash;

    this.nonce = nonce;

    this.data = data;

  }

  public void getInformation() {

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

    System.out.println("블록 번호: " + getBlockID());

    System.out.println("이전 해시: " + getPreviousBlockHash());

    System.out.println("채굴 변수 값: " + getNonce());

    System.out.println("블록 데이터: " + getData());

    System.out.println("블록 해시: " + getBlockHash());

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

  }

  public String getBlockHash() {

    return Util.getHash(nonce + data + previousBlockHash);

  }

  public void mine() {

    while(true) {

      if(getBlockHash().substring(0, 4).equals("0000")) {

        System.out.println(blockID + "번째 블록의 채굴에 성공했습니다.");

        break;

      }

      nonce++;

    }

  }

}


  위와 같이 해시 값을 도출할 때 ‘이전 블록의 해시’ 값도 사용하도록 소스코드를 수정한다. 해시(Hash)는 조금만 입력이 변경되어도 출력이 완전히 뒤바뀐다는 특징이 있으므로 이전 블록의 해시 값이 변경되면 현재 블록의 해시 값도 변경된다. 다시 말해 블록체인에서 특정한 블록의 데이터를 변경하면 뒤에 이어지는 모든 블록들이 영향을 받는다는 것이다. 이는 매우 강력한 보안성을 제공한다.


package core;


public class BlockChainStarter {


  public static void main(String[] args) {


    Block block1 = new Block(1, null, 0, "데이터");

    block1.mine();

    block1.getInformation();

    

    Block block2 = new Block(2, block1.getBlockHash(), 0, "데이터");

    block2.mine();

    block2.getInformation();

    

    Block block3 = new Block(3, block2.getBlockHash(), 0, "데이터");

    block3.mine();

    block3.getInformation();


    Block block4 = new Block(4, block3.getBlockHash(), 0, "데이터");

    block4.mine();

    block4.getInformation();

  }

}


  위와 같이 여러 개의 블록이 연결되어 차례대로 채굴이 이루어지도록 BlockChainStarter 클래스의 소스코드를 수정한 이후에 실행해보자. 그러면 다음 그림과 같이 모든 블록들은 ‘이전 블록(Previous Block)’의 해시 값을 그대로 가지면서 현재 블록의 해시 값을 도출하게 된다.



  이제 우리가 블록체인을 공격하는 악성 해커라고 가정하고, 중간에 존재하는 두 번째 블록의 데이터를 임의대로 변조해보자. 저자는 ‘변조된 데이터’라고 간단히 데이터를 수정해보았다.


package core;


public class BlockChainStarter {


  public static void main(String[] args) {


    Block block1 = new Block(1, null, 0, "데이터");

    block1.mine();

    block1.getInformation();

    

    Block block2 = new Block(2, block1.getBlockHash(), 0, "변조된 데이터");

    block2.mine();

    block2.getInformation();

    

    Block block3 = new Block(3, block2.getBlockHash(), 0, "데이터");

    block3.mine();

    block3.getInformation();


    Block block4 = new Block(4, block3.getBlockHash(), 0, "데이터");

    block4.mine();

    block4.getInformation();

  }

}



  두 번째 블록의 데이터를 변조하게 되면 위 그림과 같이 두 번째 블록(Block 2)를 포함해 그 뒤에 이어지는 세 번째 블록(Block 3)과 네 번째 블록(Block 4)까지 모두 영향을 받게 된다. 따라서 중간에 있는 특정한 데이터가 바뀐다면, 그 뒤에 있는 모든 블록들의 해시 값까지 다 이전과 다르게 바뀌어야만 하는 것이다. 물론 우리가 작성한 프로그램은 빠른 확인을 위해 채굴 난이도를 일부러 낮게 설정하였으므로 뒤에 있는 모든 블록 데이터까지 바꾸는 과정이 금방 이루어질 수도 있다.


  하지만 실제 블록체인은 채굴을 위해 많은 시간과 자원이 필요하다. 예를 들어 전체 블록의 개수가 N개일 때 N–101번째 블록의 데이터를 변조하고 싶다면 그 뒤로 이어지는 100개의 블록에 대해 전부 채굴(Mining)을 다시 수행해야 한다. 이는 왜 블록체인 해킹이 매우 어려운지 설명해주는 부분으로, 일반적인 해커에게는 현실적으로 불가능한 수준의 연산양이다. 실제로 현 시점에서 외계인이 지구로 와서 미래의 슈퍼컴퓨터를 주지 않는 이상 비트코인(Bit Coin)을 대상으로 하여 100개의 블록에 대한 채굴을 순식간에 수행할 정도의 컴퓨터 연산 능력을 갖추는 것은 절대 불가능하다.

0
기사수정

다른 곳에 퍼가실 때는 아래 고유 링크 주소를 출처로 사용해주세요.

http://www.bitweb.co.kr/news/view.php?idx=638
기자프로필
프로필이미지
나도 한마디
※ 로그인 후 의견을 등록하시면, 자신의 의견을 관리하실 수 있습니다. 0/1000
실시간 암호화폐 순위 확인하기
코인마켓캡
모바일 버전 바로가기