
블록은 크게 다음과 같이 식별자, 헤더, 바디로 나뉜다.

블록 식별자의 해시값은 다음 블록의 Previous Block Hash 값이 된다.
블록 간 해시 값으로 연결되어 체인이 형성되고 이를 블록 체인이라 부른다.
이전 블록의 데이터가 변경되면 해시 값도 변경되므로 체인으로
연결된 모든 블록의 해시를 변경해야 하므로 보안성도 강화된다.

첫번째 블록은 이전 블록 해시값이 존재하지 않고
이 첫번째 블록을 genesis block이라 한다.
블록 식별자의 해시 값은 블록 헤더를 모두 합한 뒤 SHA256 해시 함수에 2번 넣은 결과 값이다.
해시를 계산하기 위하여 임의의 데이터를 정한다.
해당 데이터의 헤더 정보는 다음과 같다.
| Data | Value | Size (Byte) |
|---|---|---|
| Version | 1 | 4 |
| Previous Block Hash | 000000000002d01c1fccc21636b607dfd930d31d01c3a62104612a1719011250 | 32 |
| Merkle Root | f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766 | 32 |
| Timestamp | 1293623863 | 4 |
| Difficulty Target | 453281356 | 4 |
| Nonce | 274148111 | 4 |
얻은 데이터는 big-endian으로 표기되어있고, 비트코인은 little-endian으로 계산되기 때문에 데이터를 바꿔줄 필요가 있다.
Python을 이용해본다.
import requests
import hashlib
import binascii
url = f"https://blockchain.info/block-height/100000?format=json"
response = requests.get(url=url)
block = response.json()["blocks"][0]
header_version = binascii.hexlify(int(block["ver"]).to_bytes(4, byteorder="little")).decode()
header_previoushash = binascii.hexlify(binascii.unhexlify(block["prev_block"])[::-1]).decode()
header_merklehash = binascii.hexlify(binascii.unhexlify(block["mrkl_root"])[::-1]).decode()
header_time = binascii.hexlify(int(block["time"]).to_bytes(4, byteorder="little")).decode()
header_bits = binascii.hexlify(int(block["bits"]).to_bytes(4, byteorder="little")).decode()
header_nonce = binascii.hexlify(int(block["nonce"]).to_bytes(4, byteorder="little")).decode()
각각의 문자열 형태의 데이터를 바이트형으로 변환 후, little-endian으로 변경한다.
그리고 16진수 형태의 문자열로 다시 변환한 결과는 다음과 같다.
| Data | Value | Size (Byte) |
|---|---|---|
| Version | 01000000 | 4 |
| Previous Block Hash | 50120119172a610421a6c3011dd330d9df07b63616c2cc1f1cd0020000000000 | 32 |
| Merkle Root | 6657a9252aacd5c0b2940996ecff952228c3067cc38d4885efb5a4ac4247e9f3 | 32 |
| Timestamp | 37221b4d | 4 |
| Difficulty Target | 4c86041b | 4 |
| Nonce | 0f2b5710 | 4 |
변환된 모든 데이터를 순서대로 이어붙인 후 SHA256을 2번 돌린다.
그리고 big-endian으로 다시 변환 후 데이터와 비교하여 검증해본다.
_ = header_version + header_previoushash + header_merklehash + header_time + header_bits + header_nonce
le_block_hash = hashlib.sha256(hashlib.sha256(binascii.unhexlify(_)).digest()).digest()
be_block_hash = binascii.hexlify(le_block_hash[::-1]).decode()
print(f"calc : {be_block_hash}") # 000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e5061
print(f"validation : {be_block_hash == block['hash']}") # True
Previous Block Hash는 이전 블록의 블록 식별자고 Version은 소프트웨어의 버전이고 Timestamp는 블록 생성 시간 값이다.
Merkle Root는 블록에 포함된 Transaction들을 연산한 결과이다.
연산은 다음과 같이 이루어진다.

임의의 데이터를 이용하여
Merkle Root를 구해본다.
얻은 데이터는 big-endian으로 표기되어있고, 비트코인은 little-endian으로 계산되기 때문에 데이터를 바꿔줄 필요가 있다.
또한 트리의 leaf가 홀수이면 마지막 leaf를 추가하여 짝수를 맞춘다.
import requests
import hashlib
import binascii
def calculate_hash(a:str, b:str):
_=binascii.hexlify(binascii.unhexlify(a)[::-1]).decode() + binascii.hexlify(binascii.unhexlify(b)[::-1]).decode()
_=hashlib.sha256(hashlib.sha256(binascii.unhexlify(_)).digest()).digest()
_=binascii.hexlify(_[::-1]).decode()
return _
def calculate_merkle_root(transactions):
branches = [transaction["hash"] for transaction in transactions]
while len(branches) > 1:
if (len(branches) % 2) == 1: branches.append(branches[-1])
branches = [calculate_hash(a, b) for a, b in zip(branches[0::2], branches[1::2])]
return branches[0]
url = f"https://blockchain.info/block-height/100000?format=json"
response = requests.get(url=url)
block = response.json()["blocks"][0]
result = calculate_merkle_root(block["tx"])
print(f"calc : {result}") # f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766
print(f"validation : {result == block['mrkl_root']}") # True
블록의 구조 중 Difficulty Target에 대하여 알아본다.
채굴이란, 블록 식별자의 해시 값을 구하는 일이며
구해지는 해시의 기준이 Difficulty Target에서 정한 목표 값보다 낮아야한다.
임의의 데이터에서 목표 값을 구해본다.
bits는 453281356라는 값을 가지고 있고 16진수로 변환하면 0x1b04864c이다.
첫 바이트는 0x1b로 10진수로는 27이다.
나머지 3바이트는 0x04864c로 총 크기를 첫 바이트인 27바이트까지 패딩을 취해주면 다음과 같이 나온다.
0x04864c000000000000000000000000000000000000000000000000
이를 공식화 하면 "3바이트*2**(8*(1바이트 - 3))"가 된다.
SHA256의 출력 값은 32바이트 고정이므로 부족한 크기만큼 앞에 패딩을 취해주면 다음과 같이 나온다.
0x000000000004864c000000000000000000000000000000000000000000000000
즉, 이 값이 목표 값이고 블록 식별자 해시 값은 이 값보다 작아야한다.
비트코인은 10분마다 1블록씩 생성되고 2016블록이 쌓이는데 2주가 걸리도록 설계하였다.
따라서, 2016블록이 쌓이는데 2주 이상이 걸렸다면 난이도가 낮아지게(bits 값이 커짐) 하는 식으로 속도를 조절한다.
블록 헤더의 값들은 Nonce를 제외하면 모두 정해진 값이다.
따라서 해시를 계산할 때 변화되는 값이 필요한데 이때 Nonce를 변경한다.
즉, 채굴은 Nonce를 찾는 행위라고 볼 수 있다.
Nonce를 변경하며 목표 값보다 낮은 블록 식별자 해시를 찾았다면
해당 내용을 전파하고, 검증이 완료되었다면
블록을 생성한 계정에게 보상(비트코인)이 주어진다.
비트코인은 Transaction에 단순한 연산밖에 하지 못하여
암호 화폐를 전송하는 기능밖에 없었다.
하지만, 스마트 컨트랙트는 반복문 등 복잡한 연산도 가능하게 지원한다.
그래서 코딩을 통해 사용자 정의 프로그램들을 배포할 수 있게 되었다.
하지만, 무한 반복문 등 악의적인 프로그램을 배포하면
이더리움 네트워크에 장애가 생길 수 있다.
이를 방지하고자 스마트 컨트랙트를 배포하고, 이를 실행할 때마다 수수료(gas price)를 내게 되었다.
비트코인은 블록을 생성한 계정에게 보상(비트코인)이 주어졌다면,
이더리움은 블록을 생성해도 보상(이더리움)을 받지못한다.
대신에 블록 내 포함된 Transaction에서 지불한 수수료를 받는 식으로 변경되었다.
따라서 채굴자들은 수수료를 많이 낸 Transaction 위주로 채굴을 하며
수수료가 적다면 해당 거래가 적용되기 까지 오래걸릴 수 있는 단점이 있다.