KEEP!T Column 비트코인 뽀개기(3편)




KEEP!T Column


안녕하세요! KEEP!T입니다. 최초의 암호화폐인 비트코인을 누구나 쉽게 이해할 수 있도록 '비트코인 뽀개기'라는 주제로 콘텐츠를 연재하고 있습니다. 지난 '비트코인 뽀개기 2편'에서는 비트코인에서 데이터를 저장하기 위한 수단으로 왜 블록체인 기술을 활용했는지에 대한 내용을 전달해드렸습니다.

오늘은 비트코인의 블록 구조는 어떻게 구성되어 있고, 어떤 역할을 수행하는지에 대해서 자세히 알아보는 시간을 갖도록 하겠습니다. 혹시 혹시 앞선 비트코인 뽀개기 시리즈를 읽지 않으셨던 분들은 아래의 링크를 통해 읽어보시는 것도 좋을 것 같습니다.



블록(Block)


블록은 블록체인의 원소 개념으로 블록의 상태 정보와, 다수의 트랜잭션(거래) 정보가 포함되어 있으며, 블록체인(데이터 베이스)에 데이터를 저장하기 위한 데이터 묶음이라고 생각하시면 좋을 것 같습니다.

블록은 Height(높이)라는 용어로 불리며, 블록체인이 길게 이어진 수평선으로 보는 것이 아니라 한 칸씩 쌓아나가는 탑의 형태로 구성된다고 생각하여 Height(높이)라는 말을 쓴다고 합니다. 비트코인 네트워크에서는 평균적으로 10분에 한 번씩 블록이 생성될 수 있도록 설계되었으며, 블록의 크기는 1MB로 약 1800-4200건의 거래내역을 담을 수 있습니다. 이러한 블록은 다음과 같이 크게 3가지 정로도 나누어볼 수 있습니다.


  • 블록의 식별자 역할을 수행하는 블록 해시
  • 블록의 상태 정보를 가지고 있는 블록 헤더
  • 다수의 거래 정보가 포함되어있는 블록 바디


블록의 식별자 역할을 수행하는 블록 해시 정보는 블록 헤더 정보를 해시한 결과값이며, 블록 해시 정보를 이해하기 위해서는 먼저 블록 헤더의 구성요소와 역할에 대한 개념 정리가 필요합니다.



블록 헤더(Block Header)



블록 헤더는 블록의 상태 정보를 가지고 있으며 다음과 같은 구성요소를 가지고 있습니다.


  • 버전(Version)
  • 이전 블록 해시(Previous block hash)
  • 머클 루트(Merkle Root)
  • 타임(Time)
  • 난이도 목표(bits)
  • 논스(Nonce)


버전(Version)


블록 헤더에 저장되는 버전 정보는 블록이 만들어진 시점에 어떤 소프트웨어 정보를 사용했는지에 대해 기록이며, 블록 생성 시점에서 사용된 소프트웨어 버전 정보를 가지고 있습니다.


이전 블록 해시(Previous block hash)


이전 블록해시정보는 새로운 블록이 생성되는 시점에서 바로 이전 블록의 블록 해시정보를 참조합니다. 블록이 생성되는 시점에서 이전 블록 해시 정보를 참조함으로써 블록과 블록이 체인(링크드 리스트)형태로 연결된 자료구조를 가질 수 있었습니다.


3_1.png

위의 그림과 같이 3307번째 블록의 이전 블록 해시 정보는 3306번째 블록의 블록 해시 정보를 참조하고 있으며, 3306번째 블록 또한 마찬가지로 이전 블록인 3305번째 블록의 블록 해시 정보를 참조하게 됩니다.

이러한 방식으로 최초의 블록인 제네시스 블록까지 모든 블록이 시간의 흐름 순서에 따라 연결되어 있으며, 블록과 블록이 이러한 방식으로 체인 형태처럼 연결되어 있다고 하여 블록체인이라고 표현하는 것입니다.


머클 루트(Merkle Root)


'머클트리(Merkle Tree)' 혹은 '해시트리(Hash Tree)'라는 데이터 구조는 1979년 Ralph Merkle이 특허를 낸 개념으로 대용량 데이터 구조의 내용을 효율적이고 안전하게 확인할 수 있는 장점이 있습니다.

블록체인에서 사용되는 머클트리는 데이터의 간편하고 확실한 인증을 위해 사용되며, 블록 바디에 저장된 트랜잭션들을 머클 트리한 최종 결과 값이 바로 '머클 루트(Merkle Root)' 정보 입니다.

머클루트 정보를 통해 블록 바디에 저장된 거래 내역 정보인 TXID의 정보가 변조되었는지에 대한 유효성을 쉽고 빠르게 확인할 수 있으며, 만약 블록 헤더 정보인 머클 루트 정보가 변경되었을 경우 블록해시 정보까지 변경됨으로 블록의 유효성 또한 쉽게 검사할 수 있습니다.


보다 심도 있게 머클 루트(Merkle Root)를 이해하기 위해서는 머클 루트 연산 과정에 대한 이해가 필요하며, 연산 과정을 이해하기 위해서는 트리 구조와, 엔디안에 대한 개념정리가 필요합니다.



트리구조란 그래프의 일종으로, 여러 노드가 한 노드를 가리킬 수 없는 구조를 가지고 있으며, 트리 구조에서 자식이 없는 자식이 없는 최하위 노드를 잎 노드(leaf node)라 표현하며, 잎 노드가 아닌 노드를 내부 노드(internal node), 마지막으로 최상위 노드를 루트 노드(root node)라고 표현합니다.

3_2.png



엔디안(Endianness)

엔디안은 컴퓨터의 메모리와 같은 1차원 공간에 데이터를 저장하는 방법에 대한 내용이며, 컴퓨터에서 어떤 크기의 데이터를 메모리에 저장할 때 바이트 단위로 나누어서 저장하게 됩니다. CPU의 설계가 어떻게 이루어졌는지에 따라서 바이트 저장순서가 달라지며, 낮은(시작) 주소에 하위 바이트로부터 기록되는 Intel CPU 계열을 리틀-엔디안(Little-Endian), 낮은(시작) 주소에서 상위 바이트로 부터 기록되는 Sparc / RISC CPU 계열의 빅-엔디안(Big-Endian)이라고하며, 두 경우에 속하지 않거나 둘을 모두 지원하는 것을 미들 엔디안(Middle-endian)이라고 합니다.

조금 쉽게 풀어서 설명을 해보자면, 컴퓨터는 0과 1의 전기적 신호를 처리하는 기계입니다. 그렇기 때문에 우리가 현재 보고 있는 한글 문자열 데이터를 그대로 컴퓨터에 저장할 수 없으며, 컴퓨터의 메모리와 같은 1차원 공간에 데이터를 저장하기 위해서는 컴퓨터가 이해할 수 있는 형태의 데이터로 변형하여 저장해야합니다.

컴퓨터에 데이터를 저장하는 방법에 관한 개념이 바로 엔디안의 개념이며, CPU의 설계에 따라 바이트의 저장 순서가 달라지기 때문에 여러 가지 형태의 엔디안 방식이 있다고 생각하시면 좋을 것 같습니다. (표준은 빅 엔디안 방식이라고 합니다.)


머클 루트(Merkle Root) 연산과정

머클 루트 결과 값은 블록바디에 저장된 TXID 정보를 머클 트리한 결과 값입니다. 그렇다면 TXID 정보를 토대로 어떻게 머클 루트 결과 값을 구하는지 알아보도록 하겠습니다.


3_3.png


  1. 모든 잎 노드의 TXID 문자열을 빅 엔디안 형태로 변형한다(빅 엔디언 형태로 변형하는 과정은 데이터 문자열을 스왑하고, 스왑된 데이터 문자열을 바이너리 형태로 변형하는 과정이다).
  2. 가장 가까운 두 노드의 문자열 값을 한 쌍으로 이어 붙인 후 SHA 256 해시 함수를 통해 2중 해시값으로 변형 하며, 최종적으로 하나의 결과 값이 나올 때 까지 이 과정을 반복한다.
  3. 최종적으로 하나의 결과 값이 남았을 경우 리틀 엔디언 형태로 변형한다(리틀 엔디언 형태로 변형하는 과정은 바이너리 데이터를 hex 값으로 변형하고, hex 값을 다시 스왑하는 과정이다).


실제 머클 루트 값 구하기

조금 더 깊히 있는 학습을위해, 머클 루트 연산 과정이 정확히 코드상으로 어떻게 이루어지는지 확인하고, 실제 값이 정상적으로 구해지는지 검증해보도록 하겠습니다.

거래내역이 많을 경우 이해하기 어려운 관계로 '블록체인 인포' 사이트를 통해 거래내역이 두건인 블록 정보를 찾았습니다. 자세한 정보는 아래의 그림과 같습니다.


3_4.png


블록 #99997의 머클 루트 값은 5140e5972f672bf8e81bc189894c55a410723b095716eaeec845490aed785f0e이며 블록 바디에 저장된 거래내역인 TXID 정보는 다음과 같습니다.

  • TX1 : b86f5ef1da8ddbdb29ec269b535810ee61289eeac7bf2b2523b494551f03897c
  • TX2 : 80c6f121c3e9fe0a59177e49874d8c703cbadee0700a782e4002e87d862373c6


위의 두개의 TXID 정보를 통해 Merkle Root 값을 구하는 예제소스는 아래와 같습니다. (예제 소스는 PHP로 작성되었습니다.)


<?

    function swapendian($data) {
        return implode('', array_reverse(str_split($data, 2)));
    }
    
    function hextobin($hexstr) 
    { 
        $n = strlen($hexstr); 
        $sbin="";   
        $i=0; 
        while($i<$n) 
        {       
            $a =substr($hexstr,$i,2);           
            $c = pack("H*",$a); 
            if ($i==0){$sbin=$c;} 
            else {$sbin.=$c;} 
            $i+=2; 
        } 
        return $sbin; 
    } 

    //-- TXID 정보.
    $tx_1 = 'b86f5ef1da8ddbdb29ec269b535810ee61289eeac7bf2b2523b494551f03897c';
    $tx_2 = '80c6f121c3e9fe0a59177e49874d8c703cbadee0700a782e4002e87d862373c6';
    
    //-- 1. 모든 잎 노드의 TXID 정보를 빅-엔디안 형태로 변형한다.

    //-- 1-1. TXID 문자열 정보를 스왑한다.
    $endian_tx_1 = swapendian($tx_1);
    $endian_tx_2 = swapendian($tx_2);
    
    //-- 1-2. 스왑한 문자열을 바이너리 형태로 변형한다.
    $binary_tx_1 = hextobin($endian_tx_1);
    $binary_tx_2 = hextobin($endian_tx_2);
    
    //-- 2. 두 노드의 문자열을 합친 후 이중 해시한다.
    $res_tx_1    = hash('sha256', hash('sha256', $binary_tx_1.$binary_tx_2, true), true);
    
    //-- 3. 결과 값을 리틀 엔디안 형태로 변형 한다.
    //      (바이너리 값을 hex 값으로 출력, 출력된 값을 스왑한다.)
    $merkleroot  = swapendian(bin2hex($res_tx_1));

    echo $merkleroot;

    
?>


위의 예제 소스를 조금 간략하게 다시 설명해보면 다음과 같습니다

(1) 블록 바디에 저장된 모든 TXID 정보는 잎 노드(leaf node)이기 때문에, 두 TXID 값을 모두 빅-엔디안 형태로 변형하기 위하여, 문자열을 스왑하고, 스왑된 문자열을 바이너리 정보로 변경하였습니다.

(2) 두 TXID 문자열을 이어 붙인 후 SHA 256 함수로 해시 값을 출력하고, 출력된 해시 값을 다시 SHA 256 함수를 통해 해시 값으로 출력하였습니다.

(3) 결과 값이 최종적으로 하나만 남게되었으므로 리틀 엔디안 형태로 병형하기 위해, 바이너리 값을 hex 값으로, 출력된 hex 값을 다시 스왑하였습니다.

(4) Merkle Root 결과 값을 화면에 출력하였습니다.


예제 소스를 실행한 후 결과 값이 블록 #99997의 머클루트 결과 값과 동일한지 확인해보겠습니다.


3_5.png


머클 루트 결과값을 출력한 결과, 실제 블록의 머클 루트 값과 정확하게 일치하는것을 확인하였습니다.


만약 TXID 정보가 하나만있을 경우에는 어떻게 머클 루트 값을 구할 수 있을까요? TXID 정보가 하나만 있을 경우에는 더이상 머클 트리할 결과 값이 없으므로 TXID 정보가 머클 루트 값이 됩니다. 3개의 TXID 정보가 있을 경우에는 1~2번의 노드의 머클 트리 결과값에 3번 노드를 결합하여 머클 트리한 결과 값이 머클 루트 결과값이됩니다.

결국 최종적으로 하나의 머클 트리 결과 값이 남을 때 까지 계속적으로 연산하고, 연산된 최종 결과 값이 머클 루트 정보가 되는것입니다. 위의 예제 코드는 이해를 돕기 위해 불필요한 소스가 일부 제거된 코드이며, 전체적으로 다수의 TXID 정보를 통해 머클 루트 값을 구하는 예제는 github에 in3rsha님께서 오픈 소스 형태로 제공한 소스를 참조하시면 좋을 것 같습니다. (오픈소스 링크)


요약정리


블록체인은 블록과 블록이 체인(링크드 리스트) 형태로 연결된 자료구조를 가지고 있으며, 블록에는 블록의 상태정보와 다수의 거래정보가 포함되어 있습니다. 이러한 블록은 크게 블록해시, 블록 헤더, 블록 바디로 나누어볼 수 있습니다.

  1. 블록 해시 : 블록의 식별자 역할을 수행하며, 블록의 헤더 정보를 통해 값을 구할 수 있다.
  2. 블록 헤더 : 블록헤더는 버전, 이전 블록해시, 머클 루트, 타임, 난이도 목표, 논스 정보로 구성되며, 블록의 상태 정보를 나타낸다.
  3. 블록 바디 : 다수의 거래(TXID) 정보가 포함되어있다.


머클 트리는 데이터의 간편하고 확실한 인증을 위해 사용되며, 블록 바디에 저장된 TXID 정보를 통해 머클 트리한 최종 결과 값이 바로 '머클 루트(Merkle Root)' 정보입니다.


머클 루트(Merkle Root) 정보를 통해 블록 바디에 저장된 거래 내역 정보인 TXID의 정보가 변조되었는지에 대한 유효성을 쉽고 빠르게 확인할 수 있으며, 만약 블록 헤더 정보인 머클 루트 정보가 변경되었을 경우 블록해시 정보까지 변경됨으로 블록의 유효성 또한 쉽게 검사할 수 있었습니다.


만약 블록 바디에 저장된 거래내역이 4,000건이라고 가정했을 경우 해당 블록의 거래 정보가 조작되었는지 검증하기 위해서 하나식 대조하여 검증하게되면 엄청난 자원이 낭비되고, 매우 비효율적일것입니다. 하지만 해시 함수의 특징인 입력 값의 정보가 조금이라도 변경되었을 경우 전혀 다른 결과 값을 출력하고, 동일한 입력값엔 항상 같은 결과 값을 출력하는 특징을 활용한다면 매우 효과적인 검증할 수 있게됩니다.


조금더 이해를 돕기 위해 다음의 그림을 살펴보도록하겠습니다.



3_6.png


위의 그림과 같이 만약 TX 1번의 데이터가 조작되었을 경우, 해시 함수의 특징으로 인해 상위 노드의 머클 트리 값이 전혀 다른 결과 값이 출력되고, 최종적으로 머클 루트 결과 값까지 변경됩니다.

즉, 블록 바디에 저장된 거래 내역 데이터 중 어떤 데이터를 수정하더라도 머클 루트 값이 변경되고, 나아가 블록 해시 정보까지 변경됨으로써 블록의 조작 여부를 쉽고 빠르게 검증할 수 있게 되는 것입니다.


이상으로 비트코인 뽀개기 3편을 마치며, 4편에서는 블록의 구조와, 블록 해시에 대한 내용으로 찾아뵙도록 하겟습니다.


yahweh87


참고자료


https://en.wikipedia.org/wiki/Merkle_tree

https://en.wikipedia.org/wiki/Ralph_Merkle

https://ko.wikipedia.org/wiki/%ED%8A%B8%EB%A6%AC_%EA%B5%AC%EC%A1%B0


logo_end.gif


이 저작물은 크리에이티브 커먼즈 저작자표시-비영리-변경금지 4.0 국제 라이선스에 따라 이용할 수 있습니다.

H2
H3
H4
3 columns
2 columns
1 column
7 Comments