서론
평범한 화요일 오후, 비트코인 채굴 풀 운영자인 김 대표는 이상한 보고서를 하나 받습니다. “지난 2주간 풀의 예상 채굴 보상과 실제 보상 간에 1.2%의 불일치가 발견되었습니다.” 1.2%라는 숫자는 작아 보이지만, 연간 수십억 원 규모의 채굴 풀에서는 절대 무시할 수 없는 금액입니다. 더 충격적인 것은 범인이 외부 해커가 아니라, 풀에 참여하고 있는 정상적인 채굴자 중 한 명이라는 점이었습니다.
채굴 풀(Pool)은 소규모 채굴자들이 해시 파워를 모아 블록 발견 확률을 높이고, 보상을 나누는 필수적인 블록체인 인프라입니다. 하지만 이 구조 자체가 근본적인 보안 취약점을 내포하고 있습니다. 바로 Block Withholding(BWH) 공격입니다. 합법적인 풀 참여자가 유효한 블록을 찾았음에도 이를 의도적으로 제출하지 않아 풀 전체의 수익을 깎아먹는 공격입니다.
최근 학계에서는 이 공격의 진화형인 **T-PAW(Temporary Power Adjusting Withholding)**를 제안했습니다. 기존 PAW 공격의 한계를 극복한 이 새로운 공격은, 해시 파워가 고작 5%인 소규모 채굴자도 쉽게 공격자로 전환할 수 있으며, 기존 대비 최대 22배 높은 추가 보상을 획득할 수 있다고 합니다. 이 글에서는 이 공격이 어떻게 작동하는지, 왜 위험한지, 그리고 어떻게 방어해야 하는지 분석합니다.
⚠️ 윤리적 경고: 본 글에서 다루는 모든 공격 기법은 학술적 연구 목적으로 작성되었습니다. 실제 채굴 풀이나 네트워크에 대한 무단 공격은 범죄행위이며, 오직 방어적 관점에서만 활용되어야 합니다.
본론
1. 공격의 진화: BWH에서 PAW, 그리고 T-PAW까지
채굴 풀에 대한 공격은 꽤 오랜 역사를 가지고 있습니다. 각 세대별로 공격의 정교함이 어떻게 발전했는지 살펴보겠습니다.
1
2
3
4
5
6
7
8
| graph TD
A[BWH 공격] --> B[FAW 공격]
B --> C[PAW 공격]
C --> D[T-PAW 공격]
A --> A1[유효 블록을 단순히 버림]
B --> B1[블록을 보류하다가 다른 풀에서 채굴 시 제출]
C --> C1[네트워크 난이도 조정 영향을 활용]
D --> D1[최대 T시간만 보류하여 수익 극대화]
|
1-1. Block Withholding (BWH) 공격의 기본
전통적인 BWH 공격은 단순합니다. 공격자가 채굴 풀에 참여하여, 유효한 Full Proof of Work(fPoW)를 찾았을 때 이를 풀에 제출하지 않고 버립니다. 풀은 전체 해시 파워에 비해 예상보다 적은 블록을 채굴하게 되고, 결과적으로 모든 참여자의 수익이 감소합니다. 공격자 역시 수익이 감소하지만, 경쟁 풀의 상대적 손해를 통해 간접적인 이득을 취할 수 있습니다.
1-2. Power Adjusting Withholding (PAW) 공격
PAW 공격은 BWH의 진화형으로, 블록을 찾았을 때 이를 무기한 보류합니다. 공격자는 다른 마이너가 블록을 채굴할 때까지 기다렸다가, 자신이 보류한 블록을 적절한 시점에 방출합니다. 이를 통해 네트워크의 난이도 조정(Difficulty Adjustment) 메커니즘을 악용하여 추가적인 이득을 얻습니다.
하지만 PAW 공격은 치명적인 약점이 있습니다. 무기한 보류로 인해 공격자가 실제로 블록을 잃을 위험이 크다는 것입니다.
1-3. T-PAW: 시간 제한을 통한 최적화
T-PAW 공격은 PAW의 약점을 “시간 제한"으로 해결합니다. 핵심 아이디어는 간단합니다:
블록을 무기한 보류하지 말고, 최대 T시간까지만 보류하라
이 단순한 제약 조건이 수학적으로 엄청난 차이를 만들어냅니다.
2. T-PAW 공격 메커니즘 상세 분석
T-PAW 공격의 작동 원리를 단계별로 살펴보겠습니다.
1
2
3
4
5
6
7
8
9
| graph LR
A[공격자가 풀에서 fPoW 발견] --> B{다른 블록이 채굴되었는가?}
B -->|Yes| C[보류 중인 블록 폐기]
B -->|No| D{T시간 경과?}
D -->|Yes| E[블록을 네트워크에 방출]
D -->|No| F[계속 보류]
F --> B
C --> G[다음 라운드 대기]
E --> G
|
Step-by-Step 공격 시나리오
Step 1: 공격자의 풀 참여 공격자는 타겟 채굴 풀에 정상적인 채굴자로 위장하여 참여합니다. 자신의 해시 파워(α)를 풀에 제공합니다.
Step 2: 유효 블록 발견 시 보류 채굴 과정에서 유효한 fPoW(Full Proof of Work)를 발견하면, 이를 즉시 풀에 제출하지 않고 자신의 로컬 저장소에 보관합니다.
Step 3: 타이머 시작 블록을 보류한 순간부터 타이머를 시작합니다. 이 시간 동안 두 가지 상황을 모니터링합니다:
- 네트워크에서 다른 마이너가 새로운 블록을 채굴했는지
- T시간이 경과했는지
Step 4: 조건부 방출
- 다른 블록이 먼저 채굴되면: 보류 중인 블록을 폐기 (오펄 고아 블록이 됨)
- T시간이 경과할 때까지 다른 블록이 없으면: 보류 중인 블록을 네트워크에 방출
Step 5: 추가 보상 획득 이 과정을 반복하면서, 공격자는 정상적인 채굴보다 높은 수익률을 달성합니다.
3. 수학적 분석: 왜 T-PAW가 더 위력적인가?
T-PAW의 핵심은 시간 제한 T의 도입이 수학적으로 어떤 이점을 가져오는지에 있습니다. 다음은 공격 파라미터에 따른 추가 보상 비교입니다.
| 파라미터 조합 (α, β, γ) | PAW 추가 보상 | T-PAW 추가 보상 | 개선 배율 | | :— | :—: | :—: | :—: | | (0.05, 0.05, 0) | 낮음 | 높음 | 22x | | (0.10, 0.10, 0) | 낮음 | 중간 | ~8x | | (0.20, 0.20, 0) | 중간 | 중간 | ~3x | | (0.30, 0.30, 0.5) | 높음 | 높음 | ~1.5x |
여기서:
- α (alpha): 공격자의 전체 네트워크 해시 파워 비율
- β (beta): 타겟 풀의 전체 네트워크 해시 파워 비율
- γ (gamma): 공격자의 네트워크 영향력 (블록 전파 속도 등)
주목할 점은 α, β, γ가 작을수록 T-PAW의 이점이 기하급수적으로 증가한다는 것입니다. 이는 소규모 채굴자일수록 공격의 유혹을 느끼기 쉽다는 것을 의미합니다.
4. PoC 코드: T-PAW 공격 시뮬레이션
다음은 T-PAW 공격의 핵심 메커니즘을 보여주는 개념 증명(PoC) 시뮬레이션 코드입니다. 이 코드는 실제 네트워크가 아닌, 통제된 환경에서 공격의 수학적 특성을 이해하기 위한 학술 목적으로 작성되었습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
| """
T-PAW (Temporary Power Adjusting Withholding) Attack Simulation
==============================================================
이 코드는 교육 및 연구 목적으로만 사용해야 합니다.
실제 채굴 풀이나 네트워크에 대한 공격 시도는 엄격히 금지됩니다.
"""
import random
import numpy as np
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class Block:
"""블록 클래스"""
height: int
miner_id: str
timestamp: float
is_withheld: bool = False
@dataclass
class Miner:
"""마이너 클래스"""
id: str
hash_power: float # 전체 네트워크 대비 해시 파워 비율
is_attacker: bool = False
withheld_block: Optional[Block] = None
t_limit: float = 3600 # T-PAW의 시간 제한 (초 단위, 기본 1시간)
class TPoWSimulator:
"""T-PAW 공격 시뮬레이터"""
def __init__(self, alpha: float, beta: float, gamma: float, t_limit: float):
"""
Args:
alpha: 공격자 해시 파워 비율 (0~1)
beta: 타겟 풀 해시 파워 비율 (0~1)
gamma: 공격자 네트워크 영향력 (0~1)
t_limit: 최대 보류 시간 (초)
"""
self.alpha = alpha
self.beta = beta
self.gamma = gamma
self.t_limit = t_limit
# 마이너 초기화
self.attacker = Miner("attacker", alpha, is_attacker=True, t_limit=t_limit)
self.pool_miners = [
Miner("pool_honest_1", beta * 0.5),
Miner("pool_honest_2", beta * 0.3),
Miner("pool_honest_3", beta * 0.2),
]
self.network_miners = [
Miner("network_1", 0.2),
Miner("network_2", 0.15),
Miner("network_3", 0.1),
]
# 시뮬레이션 상태
self.current_height = 0
self.attacker_blocks = 0
self.honest_blocks = 0
self.orphan_blocks = 0
self.withheld_released = 0
def mine_round(self, duration: float) -> Optional[Block]:
"""한 라운드의 채굴 시뮬레이션"""
# 채굴자 선택 (해시 파워에 비례)
all_miners = [self.attacker] + self.pool_miners + self.network_miners
weights = [m.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| hash_power for m in all_miners]
miner = random.choices(all_miners, weights=weights, k=1)[0]
block = Block(
height=self.current_height,
miner_id=miner.id,
timestamp=duration
)
return block, miner
def simulate(self, num_blocks: int = 1000) -> dict:
"""시뮬레이션 실행"""
results = {
"attacker_reward": 0,
"withheld_count": 0,
"released_count": 0,
"expired_count": 0
}
for i in range(num_blocks):
block, miner = self.mine_round(i * 600) # 10분 간격 가정
# 공격자가 블록을 찾은 경우
if miner.is_attacker:
# 이전에 보류 중인 블록이 있으면 폐기
if self.attacker.withheld_block is not None:
self.orphan_blocks += 1
self.attacker.withheld_block = None
# T-PAW: 블록을 보류
block.is_withheld = True
self.attacker.withheld_block = block
self.withheld_released += 1
results["withheld_count"] += 1
else:
# 다른 마이너가 블록을 찾은 경우
self.current_height += 1
# 공격자의 보류 블록이 있으면 경쟁
if self.attacker.withheld_block is not None:
time_diff = block.timestamp - self.attacker.withheld_block.timestamp
# T 시간 이내이면 공격자가 방출 시도
if time_diff <= self.t_limit:
# γ 확률로 공격자 블록이 승리
if random.random() < self.gamma:
self.attacker_blocks += 1
results["released_count"] += 1
else:
self.honest_blocks += 1
else:
# T 시간 초과 시 폐기
self.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| orphan_blocks += 1
results["expired_count"] += 1
self.attacker.withheld_block = None
else:
self.honest_blocks += 1
# 마지막 보류 블록 처리
if self.attacker.withheld_block is not None:
self.orphan_blocks += 1
results["attacker_reward"] = self.attacker_blocks
results["total_blocks"] = num_blocks
return results
def compare_strategies(self) -> dict:
"""공격 전략 비교"""
# 정상 채굴 시뮬레이션
honest_sim = TPoWSimulator(self.alpha, self.beta, self.gamma, t_limit=0)
honest_results = honest_sim.simulate(5000)
# T-PAW 시
|
출처: http://arxiv.org/abs/2604.14135v1