2️⃣

작업증명과 난이도 조절

채굴

경제학적 관점에서 채굴은 신규 비트코인을 생성하는 프로세스이다.
네트워크 관점에서 채굴은 비트코인 시스템을 안전하게 보호하고 중앙 권력 없이 합의를 이룰 수 있게 해주는 것이다.
소프트웨어 관점에서 채굴은 비트코인 트랜잭션 데이터를 모아서 블록이라는 데이터 구조를 완성하는 방법이다.
채굴은 약 10분에 한번씩 일어난다.
채굴에 성공한 사람은 채굴 인센티브와 블록에 담긴 트랜잭션의 수수료를 받는다.
채굴 인센티브는 50비트코인부터 시작하여 210,000블록(약 4년)마다 절반으로 줄어든다.
아래 그림은 시간의 흐름에 따른 비트코인의 총량을 보여준다.
#50BTC start_block_reward = 50 * 10**8 #반감기 블록 수 reward_interval = 210000 def max_money(): current_reward = start_block_reward total = 0 while current_reward > 0: total += reward_interval * current_reward current_reward /= 2 return total print("Total BTC to ever be created:", max_money(), "Satoshis") #------ 결과 ------ Total BTC to ever be created: 2100000000000000.0 Satoshis
Python
복사
채굴 인센티브는 총 2100만개가 되면 종료된다.

트랜잭션 검증

노드들은 채굴에 앞서 트랜잭션을 수집해야 한다.
수집된 트랜잭션 중에 검증에 실패한 트랜잭션은 네트워크로 퍼지지 않고 첫 노드에서 폐기된다.
아래 체크리스트를 통해 트랜잭션을 검증한다.
트랜잭션의 구문과 구조가 정확한가?
입력값이나 출력값이 비어 있지 않은가?
거래의 크기가 MAX_BLOCK_SIZE보다 작은가?
입력, 출력 금액이 허용된 범위 (1사토시 이상 2100만 BTC 이하)인가?
입력 해시값은 0이 아닌가? (코인베이스 트랜잭션은 전송이 안된다.)
락타임 검증
트랜잭션의 크기가 100바이트 이상인가?
한도 서명수를 넘지 않았는가?
해제 스크립트와 잠금 스크립트가 표준인가?
출력의 UTXO가 존재하는가?
출력이 사용된 거래가 아닌가?
출력이 코인베이스라면 COINBASE_MATURITY(100) 승인을 받았는가?
입력값 금액이 출력값 총액보다 큰가?
수수료가 너무 작은가?
해제 스크립트는 잠금 스크립트를 잘 해제하였는가?
트랜잭션의 검증이 완료되면 거래 풀(memory pool, mempool)에 저장된다.
네트워크 상에서 이전 거래가 담긴 블록이 도착하면 거래 풀에 있는 트랜잭션을 가져와 새로운 블록을 생성한다.

작업증명

작업증명은 블록을 만들 때 블록 헤더의 논스값을 찾는 과정이다.
작업증명은 해시알고리즘의 결과를 통해 입력 인자를 알아낼 수 없다는 것을 활용한다.
작업증명 방식은 단순 반복 작업을 통해 이루어지고, 검증은 쉽게 할 수 있다.
아래의 예시를 통해 작업증명의 방식을 이해할 수 있다.
from __future__ import print_function import hashlib import time text = "I am Satoshi Nakamoto" target = bytes.fromhex('1000000000000000000000000000000000000000000000000000000000000000') for nonce in range(20): input_data = text + str(nonce) hash_data = hashlib.sha256(input_data.encode()).digest() print(input_data, '=>', hash_data.hex()) if hash_data < target: print('nonce : ', nonce) break #------ 결과 ------ I am Satoshi Nakamoto0 => a80a81401765c8eddee25df36728d732acb6d135bcdee6c2f87a3784279cfaed I am Satoshi Nakamoto1 => f7bc9a6304a4647bb41241a677b5345fe3cd30db882c8281cf24fbb7645b6240 I am Satoshi Nakamoto2 => ea758a8134b115298a1583ffb80ae62939a2d086273ef5a7b14fbfe7fb8a799e I am Satoshi Nakamoto3 => bfa9779618ff072c903d773de30c99bd6e2fd70bb8f2cbb929400e0976a5c6f4 I am Satoshi Nakamoto4 => bce8564de9a83c18c31944a66bde992ff1a77513f888e91c185bd08ab9c831d5 I am Satoshi Nakamoto5 => eb362c3cf3479be0a97a20163589038e4dbead49f915e96e8f983f99efa3ef0a I am Satoshi Nakamoto6 => 4a2fd48e3be420d0d28e202360cfbaba410beddeebb8ec07a669cd8928a8ba0e I am Satoshi Nakamoto7 => 790b5a1349a5f2b909bf74d0d166b17a333c7fd80c0f0eeabf29c4564ada8351 I am Satoshi Nakamoto8 => 702c45e5b15aa54b625d68dd947f1597b1fa571d00ac6c3dedfa499f425e7369 I am Satoshi Nakamoto9 => 7007cf7dd40f5e933cd89fff5b791ff0614d9c6017fbe831d63d392583564f74 I am Satoshi Nakamoto10 => c2f38c81992f4614206a21537bd634af717896430ff1de6fc1ee44a949737705 I am Satoshi Nakamoto11 => 7045da6ed8a914690f087690e1e8d662cf9e56f76b445d9dc99c68354c83c102 I am Satoshi Nakamoto12 => 60f01db30c1a0d4cbce2b4b22e88b9b93f58f10555a8f0f4f5da97c3926981c0 I am Satoshi Nakamoto13 => 0ebc56d59a34f5082aaef3d66b37a661696c2b618e62432727216ba9531041a5 nonce : 13
Python
복사
왼쪽은 해시알고리즘(SHA-256)의 입력값이고 오른쪽은 출력값이다.
위 결과에서 변화하는 변수로 사용되는 값 (0~13)를 논스 라고 한다.
작업증명은 특정값 보다 작은 해시코드의 결과를 보여주는 논스를 찾는 것이다.
예를들어 “I am Satoshi Nakamoto13” 의 결과값은 ‘0x10000….’ 값보다 작다.
이 때 논스는 13이다.
실제 비트코인에서는 ‘I am Satoshi Nakamoto’가 아닌 (논스가 제외된) 블록의 헤더가 사용된다.
target(목표값)은 난이도 조절에 따라 변경된다.

목표값(난이도)

목표값은 영문으로는 target bits로 블록 헤더에 한 항목이다.
bits는 총 8바이트로 구성되며 앞의 두 자리는 지수(exponent)이고 나머지 6자리는 계수(coefficient)이다.
목표값을 얻기 위해서는 다음 공식을 따른다.
target=coefficient×256exponent3target = coefficient \times 256^{exponent-3}
작업증명의 기준값을 변경하여 채굴 확률을 조정하기 때문에 난이도라고 표현하기도 한다.
예를들어 277316블록을 보자.
목표값은 bits 항목에 담긴다.
‘1903a30c’ 중 ‘19’는 지수(exponent)이고 ‘03a30c’는 계수(coefficient)이다.
아래 코드를 통해 계산해보면, 결과를 얻을 수 있다.
from helper import little_endian_to_int bits = bytes.fromhex('1903a30c') exponent = bits[-1] coefficient = little_endian_to_int(bits[:-1]) target = coefficient * 256**(exponent - 3) print('{:x}'.format(target).zfill(64)) #------ 결과 ------ 0000000000000000000000000000000000000000a30319000000000000000000
Python
복사
논스를 포함한 블록 헤더의 해시가 해당 숫자보다 작아야 작업증명이 완료된다.
난이도(difficulty)는 얼마나 해시값을 얻는것이 어려운지를 직관적으로 보여주는 수치이다.
난이도의 비교를 통해서 얼마나 더 어려운지를 쉽게 파악할 수 있다.
난이도는 아래의 공식을따른다.
difficulty=0xffff×2560x1d3/targetdifficulty = 0xffff \times 256^{0x1d-3} / target
난이도는 제네시스 블록에서 1이었지만 80만 번째 블록에서는 53911173001054.59에 이른다.
채굴이 약 53조배 어려워졌다는 것을 직관적으로 알 수 있다.

난이도 조절

비트코인의 채굴은 약 10분에 한번씩 이루어진다. 통화 발행 빈도는 시간이 흘러도 일정하게 유지될 수 있어야 한다.
따라서 비트코인 네트워크에서는 10분의 간격을 충족하기 위해 2016블록마다 조정된다.
2016블록이 채굴되는동안 20,160분(2주)을 기준으로 얼마나 차이가 나는지 계산하고 전 주기의 난이도(previous_target)을 기준으로 다음 주기의 난이도(new_target)를 결정한다.
새로운 목표값을 도출하는 식은 아래와 같다.
new target=previous target×time differential/1209600{new\ target}={previous\ target} \times time\ differential / 1209600\\
time differential : 나이도 조정 중 마지막 블록의 타임스탬프 - 난이도 주정 중 첫 블록의 타임 스탬프
만약 time differential이 8주보다 크면 8주로 설정한다.
만약 time differential이 3.5일보다 작으면 3.5일로 설정한다.
1209600 는 2주의 기간을 초로 표현한 것이다.
이를 통해 블록이 평균 10분마다 생성되게 방향을 맞춘다.
off-by-one 버그
time differential이 2016시간이 아닌 2015시간으로 설정되어 있다.

블록 검증

네트워크 상에 존재하는 모든 풀 노드는 완성된 블록을 검증한다.
비트코인 클라이언트에 checkBlock, checkBlockHeader 함수에 나와있다.
검증 항목은 다음과 같다.
데이터 구조가 문법적으로 유효하다.
블록헤더의 해시값이 목표값보다 작아야 한다.
타임스탬프가 2시간 이내다.
블록의 크기가 허용한도 내에 있다.
첫 거래가 코인베이스 거래이다.
모든 트랜잭션이 유효하다.

구현: 작업증명과 난이도

from io import BytesIO from unittest import TestCase from helper import ( bits_to_target, hash256, int_to_little_endian, little_endian_to_int, merkle_root, ) GENESIS_BLOCK = bytes.fromhex('0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c') TESTNET_GENESIS_BLOCK = bytes.fromhex('0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae18') LOWEST_BITS = bytes.fromhex('ffff001d') class Block: #... 생략 #난이도를 표현 def target(self): return bits_to_target(self.bits) #난이도 계산 def difficulty(self): lowest = 0xffff * 256**(0x1d - 3) return lowest / self.target() #작업 증명 검증 def check_pow(self): # 블록을 직렬화하고 해시코드를 구함. h256 = hash256(self.serialize()) # 리틀엔디언으로 변경 proof = little_endian_to_int(h256) # 목표값보다 해시코드가 작은지 검증함. return proof < self.target() #... 생략
Python
복사

구현: 난이도 조절

TWO_WEEKS = 60 * 60 * 24 * 14 MAX_TARGET = 0xffff * 256**(0x1d - 3) def calculate_new_bits(previous_bits, time_differential): # time differential이 8주보다 큰 경우 if time_differential > TWO_WEEKS * 4: time_differential = TWO_WEEKS * 4 # time differential이 3.5일보다 작은 경우 if time_differential < TWO_WEEKS // 4: time_differential = TWO_WEEKS // 4 # 새로운 난이도를 구하는 공식 new_target = bits_to_target(previous_bits) * time_differential // TWO_WEEKS # 최대값 보다 크면 최대값으로 설정한다. if new_target > MAX_TARGET: new_target = MAX_TARGET return target_to_bits(new_target)
Python
복사