작업증명(Proof-of-Work) 방식의 합의 알고리즘은 비트코인에서 사용되는 합의 알고리즘으로써 어떤 트랜잭션이 발생했을 경우 해당 트랜잭션이 유효한 트랜잭션인지에 대한 합의 방법 및 새로운 블록이 진짜인지, 가짜인지에 대한 검증을 수행합니다. 작업 증명 방식(PoW)을 이해하기 위해서는 우선 마이닝 과정에 대한 개념정리가 필요합니다.

마이닝(채굴)이란?

마이닝(채굴)이란 임의의 nonce 값을 대입하여 블록 해시 결과 값을 생성하고, 생성된 결과 값이 제시된 Target 보다 작은 블록 해시를 찾는 과정입니다 (자세한 내용은 아래에서 설명). 해시함수의 특징때문에 어떤 nonce 값을 대입해야 제시된 Target 보다 작은 블록해시 정보를 찾을 수 있을지는 알 수 없습니다.

즉 올바른 결과 값을 찾기 위해서는 nonce의 값을 0부터 1씩 증가 시키면서 제시된 Target 보다 작은 결과 값이 나올때까지 무한 반복 작업을 수행해야 합니다. 이러한 수학문제를 풀이하는 과정을 1초에 몇번이나 수행할 수 있는지에 대한 수치 정보를 해시파워라고 표현하며, 해시파워가 높은 마이너일수록 더 많은 문제를 풀어낼 수 있으며 더 많은 문제를 풀어낼 수 있는 채굴자가 새로운 블록을 찾을 확률이 더 높습니다. 해시 파워가 높은 채굴자일수록 더 많은 일을 수행했다는 의미이며, 더 많은 일을 할 경우 확률적으로 더 많은 보상을 받게 됩니다. 그렇기 때문에 PoW를 정의할때 더 많은 일을 한 채굴자에게 더 많은 보상이 주어지는 방식이라고 표현합니다.


블록 데이터 해시 연결을 위해 블록에는 블록 헤더 데이터를 해시 함수로 계산한 블록 해시가 들어가고 이를 통해 해시 연결성을 검증해 블록체인 데이터가 중간에 위변조가 되지 않았음을 확인합니다.

작업증명(PoW)은 왜 필요할까?

이러한 작업증명 알고리즘의 필요성은 네트워크의 모든 노드가 동시에 블록을 만들 수 없게 하는 것 입니다. 작업증명을 통과해야만 블록을 생성할 수 있고, 이를 위해서는 엄청난 에너지가 소모됩니다. 그리고 작업증명 알고리즘은 Difiiculty 조절 알고리즘을 이용하여 10분당 1~2개의 블록이 생성된다는 것을 보장합니다.

네트워크의 노드들은 다음 블록을 생성하기 위해 A와 B 중 하나의 블록을 선택하여 블록을 생성합니다. 다음 블록을 생성하는 노드는 A와 B중 하나의 블록에 연결한 블록을 생성합니다. 또 다시 A를 선택한 노드와 B를 선택한 노드가 동시에 블록을 생성할 확률은 굉장히 낮겠죠. 나카모토 사토시는 확률 증명을 통해 블록 6개가 대립되는 블록이 계속 생성될 확률은 0에 가깝다는 것을 보였습니다.

작업증명 합의 알고리즘은 일시적으로 합의가 깨질 수 있으나 확률적으로 마지막엔 하나의 블록체인을 합의하게 되는 합의의 알고리즘 입니다. 작업증명 알고리즘은 기존 합의 알고리즘이 적은 수의 노드와 대다수는 올바른 행동을 한다는 가정(보통 3f+1 이상이 올바른 일을 해야 정상 운영, f는 잘못된 노드의 수)을 통해 작동하는 알고리즘인 것과 달리 글로벌한 규모의 완전히 오픈된 네트워크에서 운영할 수 있는 알고리즘 입니다.

장점

  • 최소 가격대 형성이 확실하게 정해져 있음
  • 강력한 보안성
  • 서비스 남용을 쉽게 방지

단점

  • 채굴난이도가 높아지면서 연산에 필요한 고사양 장비가 많이 필요하고, 과도한 전력소모로 인한 에너지 낭비가 커짐
  • 채굴난이도가 높아지면서 개인 채굴자는 채굴을 할 수 없는 수준까지 옴
  • 지속적으로 해시파워를 유지해야함
  • 채굴하는 업자끼리의 단합 문제

블록 구하는 방법

위에서는 마이닝(채굴)이 뭔지와 작업증명이 왜 필요한지를 설명했습니다. 지금부터는 블록을 어떻게 구할지와 작업증명의 성공 여부를 확인하는 방법을 설명하겠습니다. 

https://www.blockchain.com/에 들어가면 현재 채굴이 된 블록을 조회할 수 있습니다. 아래의 예시는 125552번째 블록을 채굴한다고 가정하여 125552를 검색해 정보를 확인합니다. (https://www.blockchain.com/btc/block/00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d - 125552를 검색한 결과 URL)



위의 주소로 들어가 정보를 살펴보면 타임 스탬프, Bits, 해시 난수, 이전 차단, Merkle Root 정보를 확인할 수 있습니다. 앞서 설명한 블록 해시를 구하는 공식에는 6가지 요소가 필요하나 여기에는 버전 정보가 누락되어 있습니다. 그리고 타임 스탬프 정보 또한 우리가 보기 편한 형식으로 변형되어 있습니다. 실제로 블록해시를 구하기 위해 JSON 형태로 데이터를 획득해야 합니다. (https://blockchain.info/block-height/125552?format=json)

위 URL처럼 정보를 구성하여 요청하면 125552번째 블록 정보를 JSON 형태로 제공받을 수 있습니다. 참고로 위의 URL의 /125552? 이 부분에 숫자 부분이 블록 높이 정보입니다. 다른 블록을 확인하고자 할때는 원하는 블록 높이 값을 입력하여 호출하면 됩니다.


여기서 필요한 값은 ver, prev_block, mrkl_root, time, bits, nonce 입니다. 위 정보로 확인하면 nonce가 2504433986 일 때, 해시함수의 결과값이 00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d으로 작업증명이 성공한 것을 알 수 있습니다.

블록해시 구하는법

아래에서는 파이썬 3.+ 버전으로 작성하여 연습을 해봅니다.

import hashlib
import struct


little_endian = lambda value: struct.pack('<L', value).hex()
reverse_order_pair = lambda value: ''.join([value[i - 2:i] for i in range(len(value), 0, -2)])

# 125552번째 블록 정보
# 아래의 정보로 결과가 00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d 나오면 성공
block_info = {
    'hash': '00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d',
    'ver': 1,
    'prev_block': '00000000000008a3a41b85b8b29ad444def299fee21793cd8b9e567eab02cd81',
    'mrkl_root': '2b12fcf1b09288fcaff797d71e950e71ae42b91e8bdb2304758dfcffc2b620e3',
    'time': 1305998791,
    'bits': 440711666,
    'fee': 1000000,
    'nonce': 2504433986,
    }

convert_block_info = {}

# 1. 버전, 타임, bits, nonce 정보를 리틀 엔디안 형태로 변형.
convert_block_info['ver'] = little_endian(block_info['ver'])
convert_block_info['time'] = little_endian(block_info['time'])
convert_block_info['bits'] = little_endian(block_info['bits'])
convert_block_info['nonce'] = little_endian(block_info['nonce'])

# 2. 이전 블록 해시, 머클루트 결과 값을 모두 반대 순서로 변형
convert_block_info['prev_block'] = reverse_order_pair(block_info['prev_block'])
convert_block_info['mrkl_root'] = reverse_order_pair(block_info['mrkl_root'])

# 3. 블록 헤더 정보를 모두 합산(순서가 꼭 맞아야합니다.)
header_hex = convert_block_info['ver'] + convert_block_info['prev_block'] + convert_block_info['mrkl_root'] + convert_block_info['time'] + convert_block_info['bits'] + convert_block_info['nonce']

# 4. 바이너리 형태로 변형
header_bin = bytes.fromhex(header_hex)

# 5. SHA 256으로 변환한 결과를 다시 SHA 256 으로 변환(2번진행)
hash = hashlib.sha256(hashlib.sha256(header_bin).digest()).digest()

# 6. hash 결과를 뒤집은 다음  16진수로 변경
result_header_hex = hash[::-1].hex()

# result : 00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d
# 성공

위의 코드를 실행하면 현재 블록의 해시 값이 나온 것을 확인할 수 있습니다. 

실제로의 채굴은 nonce를 0부터 1씩 증가시키면서 Target 값과 비교하여 작거나 같을 때까지 반복실행합니다. 

(왜 값을 Little Endian으로 변형하고 16진수 값을 뒤집고 하는지 이유는...)

정리

  1. ver, time, bits, nonce는 Little Endian 형태로 변형
  2. prev_hash, mrkl_root는 결과 값을 모두 반대 순서로 변경 (16진수 이므로 2개씩 묶어서 반대로 변경)
  3. 헤더 정보들을 모두 합산(문자열 값을 이어붙임. 이때 순서가 변경되면 안됨)
  4. 합산한 헤더 정보를 바이너리 형태로 변경
  5. SHA256 형태로 변경한 결과를 다시 SHA256으로 변형
  6. 5번에서 나온 결과를 전부 뒤집은 다음 16진수로 변경

작업증명 성공 여부의 기준은 어떻게?

작업증명의 성공 여부 뿐만 아니라 작업난이도도 bits로 결정이 됩니다. 

작업증명 값 도출 방법

nonce로 작업증명을 성공하기 위해선 bits가 제시하는 숫자와 같거나 적으면 성공입니다. 그런데 bits에서는 지수와 가수를 이용하여 표현되고 있어 조금 복잡합니다. 예를 들어, 수학에서 큰 수나 작은 수를 표현할 때, 0.1234*10^5이나 0.1234*10^(-5)과 같이 표현합니다. 이를 더 친숙하게 표현하자면 처음 예를 든 수는 12,340이고 마지막 수는 0.000001234 입니다. 이때, 0.1234를 가수, 5나 -5를 지수라 표현합니다. 위에서 Little Endian으로 구한 bits는 ffff001d지만 Big Endian으로 바꿔서 1d00ffff로 계산해야합니다. Endian으로 계산한 수는 16진수 이므로 2자리씩 끊어서 이해해야 하므로 1d, 00, ff, ff로 파악해야 합니다. 여기서 bits는 첫 수인 1d를 지수로 보고 00ffff는 가수로 봐야 합니다. 즉, 1d는 진수, 00ffff는 가수로 봅니다. (가수의 경우, 앞에 소수점 0. 이 생략되어 있으므로 0.00ffff 라는 의미입니다.)

여기서 bits가 제시하는 숫자의 계산법은 00.00ffff*256^1d 입니다. 만약 지수가 01이였다면 256^1로 소수점이 우측으로 한칸 움직인 00.ffff가 됩니다. (* 여기서 주의할 점은 16진수이므로 16당 1칸이라 생각하면됩니다.)


다시 위 구하고자 하는 bits로 돌아오면 [00].[00][ff][ff]*256^1d 즉 [00].[00][ff][ff]*256^29 이므로 (1d를 https://ko.calcuworld.com/%EC%88%98%ED%95%99/16%EC%A7%84%EB%B2%95-%EA%B3%84%EC%82%B0%EA%B8%B0/ 에 넣어 10진수로 변환하면 29의 값이 나옵니다.) 우측으로 소수점을 29칸 옮기라는 소리입니다. 다시말해서 [ff][ff] 뒤에 [00]을 26번 작성하면 됩니다. 여기서 끝을 내면 28개로 이루어지기 때문에 도출한 28개의 값 앞에 [00]을 4개 더 붙인 [00][00][00][00][ff][ff][00][00]…[00] (32bytes) - (글자 수로는 64자)가 나오게 됩니다. 

결과적으로 bits가 제시한 숫자는 000000ff0000...00 (32bytes) - (글자 수로는 64자)입니다. nonce를 변경해가며 위 에서 나온 해시결과값이 이 값보다 작거나 같아야 작업증명이 성공하게 됩니다.

작업 난이도

블록 해쉬가 특정 숫자보다 낮게 나올 때의 nonce값을 찾아내는 것이 작업 증명이라고 위에서 설명을 했습니다. 앞에서 블록 해쉬는 32바이트의 숫자라고 했는데, 이를 가지고 설명하려면 너무 복잡하기 때문에 부호가 없는 1바이트로 가정합니다. 그러면 해시함수의 결과값은 0~255 사이의 결과가 나오게 됩니다. 만약 블록해시가 128보다 작아야 된다고 하면 128보다 작은 블록해시 결과값이 나올 확률은 128/256 이므로 50%의 확률입니다.

다시 블록해시 결과값이 64보다 작아야 한다고 하면 64/256이므로 25%의 확률로 nonce를 구할 수 있습니다. 여기서 예시를 든 128, 64라는 값이 bits입니다. 

보상은?

보상은 새로 발행되는 비트코인과 해당 블록에 포함되는 거래의 거래 수수료의 합입니다. 비트코인의 새로운 발행은, 채굴자가 블록을 처음 구성할 때 채굴자의 지갑으로 일정량의 비트코인이 입금되는 거래를 그 블록의 첫 거래(generation transaction)로 추가하는 방식으로 이루어집니다. 새로 발행되는 비트코인은 최초에 50BTC에서 시작해서 블록 체인에 21만개의 블록이 추가될 때마다 절반으로 줄어듭니다. 거래 수수료는 각 거래 당사자끼리 자율적으로 정할 수 있고, 거래가 블록에 추가되는 우선 순위를 결정하는데 거래 수수료가 입력값으로 사용되기도 합니다. 즉 보상은 nonce값을 찾아내고, 그 결과 새로운 블록을 블록 체인에 추가해서, 해당 블록에 포함된 모든 거래를 유효한 거래로 확정시켜준 대가라고 할 수 있습니다.

요약

  • PoW는 비트코인의 합의 알고리즘이 대표 케이스
  • 데이터의 위변조 방지


+ Recent posts