WeChall - Britcoin

Challenge

自定义 PoW(Proof of Work)类

给定当前 session 的 block data,找一个 32 字符大写 HEX nonce,使 Skein512 hash 以 00000000 开头。

Britcoin

1
2
3
// GWF3 源码逻辑 (gizmore/gwf3 → www/challenge/britcoin/index.php)
$nonce_zero = GWF_Skein::hashString($payload); // payload = json_encode(block_data)
$hash = GWF_Skein::hashString($nonce_zero . $nonce); // ASCII 字符串拼接

要求: - nonce 匹配 ^[A-F0-9]{32}$(32 字符大写 HEX) - hash 使用 Skein512 算法(Threefish + UBI 模式) - hash 的大写 hex 结果以 00000000 开头(8 个零 = 32 bits 难度) - 输入是 ASCII 字符串 nonce_zero + nonce,不是 raw bytes —— 最常见的坑

Solution

1. 获取参数

挑战页面直接显示 computed nonce_zero(Skein512 对 block data 的 hash):

1
2
This is the computed nonce zero for comparison: F70F9DE66EA5...
Your nonce is your answer, uppercase 32 hex digits.

拷贝 nonce_zero 值即可。该值在 session 有效期内固定。

2. 库选择

必须使用 pyskeinpycryptodome 的 Skein 实现与 PHP 的 GWF_Skein 输出不兼容(Skein 规范版本差异),会产生完全不同的 hash

用官方规格 britcoinspecs.grfc 的参考向量验证库兼容性和拼接逻辑(以下两个断言已实测通过):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from skein import skein512

NZ_REF = (
'05113B96005C00ABE1BC67C7108EBDAA5B030A03F3FB2DB3279A1730D96E41C4'
'B1D43DF46BAF31B367A960005B9A7BFF48168722E336A0A900181861F55A88F4'
)

# 向量 1: Skein512('livinskull') 应等于规格给出的 nonce-zero
assert skein512(b'livinskull').hexdigest().upper() == NZ_REF

# 向量 2: nonce-zero + 32 个 'A' 应等于规格给出的目标 hash
assert skein512(NZ_REF.encode() + b'A' * 32).hexdigest().upper() == (
'6473BE426B22ED34DFF9AA5C1547CB3BCEC350B06910F639338AF1FA6F5A6DDA9'
'B938BBBB9B6F956439A90ED7A3996711DC30C00D1C8FA2E8D207EB2D1D03476'
)

两个向量都通过 = 库兼容 + ASCII 拼接逻辑正确。注意向量 2 的 nonce_zero 按大写 ASCII 参与拼接,用小写会产生不同结果。

3. PoW Miner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python3
import time
from skein import skein512
from itertools import count

nonce_zero = 'F70F9DE66EA5...' # 从页面获取,替换为完整 128 字符值

st = time.time()
prefix = nonce_zero.encode() # ASCII bytes,不是 hex decode

print('Mining prefix 00000000...')
for i in count():
# nonce 格式化为 32 个 hex 字符,encode 为 ASCII bytes
nonce = f'{i:032x}'.encode()
h = skein512(prefix + nonce)
if h.hexdigest().startswith('00000000'):
elapsed = time.time() - st
nonce_str = nonce.decode().upper()
print(f'Found: {nonce_str}')
print(f'Hash: {h.hexdigest().upper()}')
print(f'Time: {elapsed:.0f}s')
break

关键细节: - nonce 用 f'{i:032x}'.encode() → hash 对小写 ASCII bytes计算 - nonce_zero 按页面显示的大写原样 .encode() - 拼接: nonce_zero.encode() + f'{i:032x}'.encode() - 找到后 .decode().upper() 转大写提交

4. 优化多进程

难度 32 bits → 单次命中概率 2^-32,期望 2^32 ≈ 4.3B 次(中位数 ≈2.98B)。单核实测 ~885K h/s(期望 ~80 分钟),多进程显著加速:

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
#!/usr/bin/env python3
from multiprocessing import Pool, cpu_count
from skein import skein512

nonce_zero = 'F70F9DE66EA5...' # 从页面获取

def check(args):
nz, start, end = args
for i in range(start, end):
n = f'{i:032x}'.encode()
h = skein512(nz.encode() + n)
if h.hexdigest().startswith('00000000'):
return n.decode().upper()
return None

if __name__ == '__main__':
workers = cpu_count()
step = 2**32 // workers
ranges = [(nonce_zero, i * step, (i + 1) * step) for i in range(workers)]
pool = Pool(workers)
try:
for result in pool.imap_unordered(check, ranges):
if result:
print(f'Found: {result}')
break
finally:
pool.terminate()
pool.join()

实测(6 workers,~5.7M h/s): - 10 分钟 ~3.4B nonces → 命中率 ≈55% - 30 分钟 ~10B nonces → 命中率 ≈90% - 完整 2^32 扫描 → 命中率 63.2%