以前から気になって何度かプログラムを作ってはいたのですが、失敗を繰り返していました。AIに聞いたらあっけないくらい簡単に回答が出たので、載せておきます。
ランダムな10桁の文字列から、先頭が”000000″(0が6個連続)になるハッシュ値(SHA-256)を見つけるまでのプログラムを作って速度を比較しました。
環境
OS windows11
CPU Intelcore i7 14700
GPU NVIDIA GeForce RTX 4060(8GB)
比較方法(準備)
①CPU版(Cuda不使用)
# python hash_256_CPU.py
import random
import string
import hashlib
import time
TARGET = "000000"
CHARS = string.ascii_letters + string.digits # a-zA-Z0-9
def randstr(n=10):
return ''.join(random.choices(CHARS, k=n))
count = 0
start_time = time.time()
last_time = start_time
last_count = 0
try:
while True:
s = randstr(10)
h = hashlib.sha256(s.encode()).hexdigest()
#count += 1
if h.startswith(TARGET):
elapsed = time.time() - start_time
print("\n=== FOUND ===")
print("STRING :", s)
print("HASH :", h)
print("TIME :", f"{elapsed:.4f} sec")
#print("TRIES :", f"{count:,}")
break
except KeyboardInterrupt:
print("\nInterrupted by Ctrl+C")②GPU版(Cuda使用版)
# python hash_256_GPU.py
import cupy as cp
import time
import hashlib
import random
# ===== 設定 =====
ZEROS = 6
SHIFT = 32 - ZEROS * 4 # 上位何bitを見るか
TARGET = "0" * ZEROS
BLOCK = 256
GRID = 65535
BATCH = BLOCK * GRID
# ===== base62文字 =====
table = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
def base62(n):
s = ""
for _ in range(10):
s = table[n % 62] + s
n //= 62
return s
# ================= CUDA KERNEL =================
kernel = r'''
__device__ __constant__ char table[63] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
extern "C" __global__
void brute(unsigned long long start, unsigned long long total,
int shift, int *found, unsigned long long *result) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx >= total || *found) return;
unsigned long long n = start + idx;
char s[10];
for (int i = 9; i >= 0; i--) {
s[i] = table[n % 62];
n /= 62;
}
unsigned int h0 = 0x6a09e667;
unsigned int h1 = 0xbb67ae85;
unsigned int h2 = 0x3c6ef372;
unsigned int h3 = 0xa54ff53a;
unsigned int h4 = 0x510e527f;
unsigned int h5 = 0x9b05688c;
unsigned int h6 = 0x1f83d9ab;
unsigned int h7 = 0x5be0cd19;
unsigned char chunk[64] = {0};
for(int i=0;i<10;i++) chunk[i] = s[i];
chunk[10] = 0x80;
chunk[63] = 80;
unsigned int w[64];
for(int i=0;i<16;i++){
w[i] = (chunk[i*4]<<24)|(chunk[i*4+1]<<16)|(chunk[i*4+2]<<8)|(chunk[i*4+3]);
}
for(int i=16;i<64;i++){
unsigned int s0 = __funnelshift_r(w[i-15], w[i-15], 7) ^
__funnelshift_r(w[i-15], w[i-15], 18) ^ (w[i-15] >> 3);
unsigned int s1 = __funnelshift_r(w[i-2], w[i-2], 17) ^
__funnelshift_r(w[i-2], w[i-2], 19) ^ (w[i-2] >> 10);
w[i] = w[i-16] + s0 + w[i-7] + s1;
}
unsigned int a=h0,b=h1,c=h2,d=h3,e=h4,f=h5,g=h6,h=h7;
unsigned int k[64]={
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
};
for(int i=0;i<64;i++){
unsigned int S1 = __funnelshift_r(e,e,6)^__funnelshift_r(e,e,11)^__funnelshift_r(e,e,25);
unsigned int ch = (e & f) ^ ((~e) & g);
unsigned int temp1 = h + S1 + ch + k[i] + w[i];
unsigned int S0 = __funnelshift_r(a,a,2)^__funnelshift_r(a,a,13)^__funnelshift_r(a,a,22);
unsigned int maj = (a & b) ^ (a & c) ^ (b & c);
unsigned int temp2 = S0 + maj;
h=g; g=f; f=e; e=d+temp1; d=c; c=b; b=a; a=temp1+temp2;
}
h0 += a;
if((h0 >> shift) == 0){
*found = 1;
*result = start + idx;
}
}
'''
# ================= Python 側 =================
mod = cp.RawModule(code=kernel)
func = mod.get_function("brute")
found = cp.zeros(1, dtype=cp.int32)
result = cp.zeros(1, dtype=cp.uint64)
start = random.randrange(62**10)
count = 0
t0 = time.time()
try:
while True:
found.fill(0)
func((GRID,), (BLOCK,), (start, BATCH, SHIFT, found, result))
cp.cuda.runtime.deviceSynchronize()
count += BATCH
if int(found.get()[0]):
num = int(result.get()[0])
s = base62(num)
h = hashlib.sha256(s.encode()).hexdigest()
if h.startswith(TARGET):
print("\n=== FOUND ===")
#print("NUMBER:", num)
print("STRING:", s)
print("HASH :", h)
#print("TRIES :", f"{count:,}")
print("TIME :", f"{time.time()-t0:.8f} sec")
break
start += BATCH
except KeyboardInterrupt:
print("\nInterrupted by Ctrl+C")
※Cudaを使用するための準備
# Cudaのバージョンを確認
c:\ > nvidia-smi
Wed Feb 11 11:37:27 2026
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 576.28 Driver Version: 576.28 CUDA Version: 12.9 |
|-----------------------------------------+------------------------+----------------------+
# 私の環境だと CUDA Version: 12.9
# Cuda用のpythonライブラリをインストール
c:\ > pip install cupy-cuda12x numpy # cupy-cuda12x 12系のライブラリ
比較
① CPU版を実行した様子(1回目)
# CPU版実行結果
c:\ > python hash_256_CPU.py
=== FOUND ===
STRING : MFipUySTvU
HASH : 00000090d7a0b1299cbe218e63a82d0dc74ee788436936f4b3c0c3f3ee0bc6c5
TIME : 6.7857 sec② GPU版の実行結果(1回目)
# GPU版実行結果 ZEROS = 6
c:\ > python hash_256_GPU.py
=== FOUND ===
STRING: 0Y61DC4953
HASH : 00000097a57f46641bf79a146cd1c0c972842cf6bd86f77ceee45cf2079d7df2
TIME : 0.00095725 secぱっと見ただけで、GPUの方が圧倒的に早いことがわかります。
③ 一覧による比較
それぞれのプログラムを10回ずつ実行して、平均値を求め比較したところ、
CPU 15.23284秒
GPU 0.009985256秒
という結果になりました。
このプログラムで比較したところ、私の環境では、GPU版の方がCPU版より
約1525倍速い(15.23284/0.009985256)
という結果になりました。ヤッタネ!
| CPU | 発見までの時間 | 文字列 | HASH値(SHA-256) |
| 1 | 6.7857 | MFipUySTvU | 00000090d7a0b1299cbe218e63a82d0dc74ee788436936f4b3c0c3f3ee0bc6c5 |
| 2 | 3.8864 | jvsiieLXis | 00000017f324ffe29452ffb20d9c443f73049d709cef90b5f0c58558a21530b7 |
| 3 | 7.4533 | hMaOzHEE23 | 000000911e09a5e1b9dece956331f85ed8b645c822a876ad2e72c2630fdabdea |
| 4 | 4.909 | 1OhIsWYkPX | 000000b6b0a24dd38ee3e8709c4c88794162141b43d3b87a7ea40daa707cc4db |
| 5 | 62.2687 | f2SfpoW7o2 | 000000e21ed7a9a3327797138203cf06e0d7c4385dcdac71a140aab8bd2a367f |
| 6 | 4.1982 | tzwY5mbKZM | 00000068f3caf3f06c14a97fe23bfc880866c70a0860b2b2ffa1805e846fe401 |
| 7 | 17.7354 | huZQrP927x | 00000001730353ea98d75936f8d427eb690bdebce53aa505ead45693df5497d8 |
| 8 | 18.2202 | Xh4d6gxEqR | 000000690079a1713fb6363f7afa0e370ca592eb8fa69037f3d46b722db954a6 |
| 9 | 10.2253 | 59stXEBykl | 000000ac4d46e2593127bbbf28f946ed245b7f8247dc04f91e3c7786edc8ad30 |
| 10 | 16.6462 | piYYES4b4J | 000000d250133a42ca5d27f5f158b56320c5c9cb0f2714cd5c80cfb427a92705 |
| AVE | 15.23284 |
| GPU | 発見までの時間 | 文字列 | HASH値(SHA-256) |
| 1 | 0.00095725 | 0Y61DC4953 | 00000097a57f46641bf79a146cd1c0c972842cf6bd86f77ceee45cf2079d7df2 |
| 2 | 0.01149106 | UZ6Yv0bE0i | 0000006a367eaa33a313cfb471806126cc67c34da1e7915bbd345c915add7882 |
| 3 | 0.00199962 | jyOiugp1M7 | 00000008ca2544733bf163d7f0301269008a6b8c9828793033e8c728a565715a |
| 4 | 0.01000357 | 7rVRnnxT8O | 0000001454d35d8d6874a27de444171d61c5b682aa6787138b41467bee68c936 |
| 5 | 0.03151608 | FkkVRNebes | 0000002d365a7b1bebd02c5b5a113307c131355e092e7960fa2dd753a15b1173 |
| 6 | 0.00400233 | KgEnsu3UT1 | 0000007e02e5f8537554638af32ab82e4ba391bd9ebb598e29ec5b550d6ba950 |
| 7 | 0.0206964 | nJIpEdH0Uh | 00000090fc18d072ef457370adf886af560827d1c0da455f6367f1c6f9893494 |
| 8 | 0.004107 | zZfrXNRuLR | 0000009d9b26bfbfdc15f27d7aa561f053b6e17788349759d370d0073dbdd8fe |
| 9 | 0.00499701 | SyiSUDcQqI | 0000000bddaeb0a71a2d6b92f67856a5160693f86c44f7308d93d6525b14118e |
| 10 | 0.01008224 | FTZVLCIwGy | 00000022a5ef411410c1644cb5dd2f0a7c157d3ff8f618f7f4056d4f28c1b58a |
| AVE | 0.009985256 |
④ 更に0の桁数が大きいHASH値の検索
7桁が”0″になる文字列検索を行ったところ、次のような結果になりました。
# python ZEROS = 7
c:\ > python hash_256_CPU.py
=== FOUND ===
STRING: eLJz8Piawt
HASH : 0000000ee86c1a3f9d329533089ca7876998d7c0d0745d6eeaef622e95115841
TIME : 0.02571392 sec
8桁が”0″になる文字列検索を行ったところ、次のような結果になりました。
# python ZEROS = 8
c:\ > python hash_256_GPU.py
=== FOUND ===
STRING: fDbwxZsc81
HASH : 000000009125b14edbe6fd9ca785a04306c53e0d6f3f73e95dad8ef053e2091f
TIME : 2.03842711 secこの調子で9桁目を計算しようとしましたが、答えが返ってきません。
問題点 ~ 0が9つ以上並ぶHASHの検索ができない
これまでの方法だと
0が8つ並んだHASH値は検索できる
ものの、
0が9つ以上並んだHASH値は検索できない
ことがわかりました。ただ、単に時間がかかっているだけなのかもしれませんが、入力文字数10桁の中から先頭が9桁0になるHASH値を探すのは現実的ではないのかもしれません。(そもそも存在しない可能性もある)
そこで、入力文字列をさらに上げることにしました。
実装したところ、11桁以上のランダム文字列を生成すること自体は容易なのですが、その探索開始位置を示す start 変数が極端に大きくなるという問題に突き当りました。
今回の実装では、
start = random.randrange(63**N)
のようにして検索開始位置を CUDA に渡していました。
しかし、入力文字が11桁になるとstartが
63**10 = 984,930,292,000,000,000 ≒ 9.8×10^17
となり、Cuda 側で扱える整数サイズを超過し、オーバーフローになってしまうのです。
Python 側では int に上限はないとのことですが、CUDA カーネル内部は Cの整数型(long long等) に変換されるため、サイズ超過したことが原因だと思われます。
解決策
この問題を解決するために、入力文字列を2つに分け、それぞれをカウントする方法に変更しました。
入力文字列 = 入力文字列1 + 入力文字列2
Cudaに渡す変数は増えますが、理論的にはさらに大きな文字列を渡してカウントすることが可能であり、この方法を使えば事実上無制限に文字列をカウントアップすることが可能です。
作成したHash値検索プログラム
import cupy as cp
import time
import hashlib
import random
# ===== 設定 =====
ZEROS = 9
TARGET = "0" * ZEROS
BLOCK = 256
GRID = 65535
# ===== base62 =====
table = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
def base62_8(n):
s = ""
for _ in range(8):
s = table[n % 62] + s
n //= 62
return s
# ================= CUDA KERNEL =================
kernel = r'''
__device__ __constant__ char table[63] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
__device__ __constant__ unsigned int k[64]={
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
};
extern "C" __global__
void brute(unsigned long long start1,
unsigned long long start2,
int *found,
unsigned long long *r1,
unsigned long long *r2) {
unsigned int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (*found) return;
unsigned long long n1 = start1 + tid;
unsigned long long n2 = start2 + blockIdx.x;
char s[16];
#pragma unroll
for (int i = 7; i >= 0; i--) {
s[i] = table[n1 % 62];
n1 /= 62;
}
#pragma unroll
for (int i = 15; i >= 8; i--) {
s[i] = table[n2 % 62];
n2 /= 62;
}
unsigned int h0 = 0x6a09e667;
unsigned int h1 = 0xbb67ae85;
unsigned int h2 = 0x3c6ef372;
unsigned int h3 = 0xa54ff53a;
unsigned int h4 = 0x510e527f;
unsigned int h5 = 0x9b05688c;
unsigned int h6 = 0x1f83d9ab;
unsigned int h7 = 0x5be0cd19;
unsigned char chunk[64] = {0};
#pragma unroll
for(int i=0;i<16;i++) chunk[i] = s[i];
chunk[16] = 0x80;
chunk[63] = 128;
unsigned int w[64];
#pragma unroll
for(int i=0;i<16;i++){
w[i] = (chunk[i*4]<<24)|(chunk[i*4+1]<<16)|(chunk[i*4+2]<<8)|(chunk[i*4+3]);
}
#pragma unroll
for(int i=16;i<64;i++){
unsigned int s0 = __funnelshift_r(w[i-15], w[i-15], 7) ^
__funnelshift_r(w[i-15], w[i-15], 18) ^ (w[i-15] >> 3);
unsigned int s1 = __funnelshift_r(w[i-2], w[i-2], 17) ^
__funnelshift_r(w[i-2], w[i-2], 19) ^ (w[i-2] >> 10);
w[i] = w[i-16] + s0 + w[i-7] + s1;
}
unsigned int a=h0,b=h1,c=h2,d=h3,e=h4,f=h5,g=h6,h=h7;
#pragma unroll
for(int i=0;i<64;i++){
unsigned int S1 = __funnelshift_r(e,e,6)^__funnelshift_r(e,e,11)^__funnelshift_r(e,e,25);
unsigned int ch = (e & f) ^ ((~e) & g);
unsigned int temp1 = h + S1 + ch + k[i] + w[i];
unsigned int S0 = __funnelshift_r(a,a,2)^__funnelshift_r(a,a,13)^__funnelshift_r(a,a,22);
unsigned int maj = (a & b) ^ (a & c) ^ (b & c);
unsigned int temp2 = S0 + maj;
h=g; g=f; f=e; e=d+temp1; d=c; c=b; b=a; a=temp1+temp2;
}
h0 += a;
h1 += b;
if(h0 == 0 && (h1 >> 28) == 0){
if(atomicCAS(found, 0, 1) == 0){
*r1 = start1 + tid;
*r2 = start2 + blockIdx.x;
}
}
}
'''
# ================= Python 側 =================
mod = cp.RawModule(code=kernel)
func = mod.get_function("brute")
found = cp.zeros(1, dtype=cp.int32)
r1 = cp.zeros(1, dtype=cp.uint64)
r2 = cp.zeros(1, dtype=cp.uint64)
start1 = random.randrange(62**8)
start2 = random.randrange(62**8)
t0 = time.time()
try:
while True:
found.fill(0)
func((GRID,), (BLOCK,), (start1, start2, found, r1, r2))
cp.cuda.runtime.deviceSynchronize()
if int(found.get()[0]):
n1 = int(r1.get()[0])
n2 = int(r2.get()[0])
s = base62_8(n1) + base62_8(n2)
h = hashlib.sha256(s.encode()).hexdigest()
if h.startswith(TARGET):
print("\n=== FOUND ===")
print("STRING:", s)
print("HASH :", h)
print("TIME :", f"{time.time()-t0:.3f} sec")
break
start1 += GRID * BLOCK
if start1 >= 62**8:
start1 = 0
start2 += GRID
except KeyboardInterrupt:
print("\nInterrupted")
更にHASH値の検索 ~10桁まで
このプログラムを使って、0が9個並ぶHASH値の検索は、10回平均値をとったところ、
28.53秒
でした。
# python ZEROS = 9
c:\ > python hash_256_GPU_30.py
=== FOUND ===
STRING: XFOX2Rz6YrrJ0YIT
HASH : 000000000e17df1847867773195ce216d7bbcf469129d3ed66d9c494a3dc1c02
TIME : 19.627 sec続いて0が10個並ぶHASH値を検索したところ、
>python hash_256_GPU_30.py
=== FOUND ===
STRING: sP2kuyFP6HDUWzS7
HASH : 00000000008a622b53cecd8ecf47f0194f55034124af739d81963a5168555e95
TIME : 742.806 secでした。(三回の平均値は376秒でした)
予想 ~マイニングが成功するまでの道のり
ここまでの結果をもとに、ビットコインマイニングに必要な時間を計算しました。
nが1増えると検索時間がおおむね10倍になっていると仮定すると、ビットコインのマイニングに必要なN=19のハッシュ値計算に必要な時間は、
1万2先年
となりました。
かなり強引な計算ですので全くあてにはできませんが、マイニングがどれほど大変なのかがわかりました。
| n(ハッシュ値の0が続く回数) | 検索時間(秒) | 備考(約) |
| 9 | 28.5368 | |
| 10 | 376.727 | 6.27分 |
| 11 | 4000(予想) | 66.66分 |
| 12 | 40000(予想) | 11時間 |
| 13 | 400000(予想) | 4.62日 |
| 14 | 4000000(予想) | 46.29日 |
| 15 | 40000000(予想) | 1.26年 |
| 16 | 400000000(予想) | 12.68年 |
| 17 | 4000000000(予想) | 126.83年 |
| 18 | 40000000000(予想) | 1268.39年 |
| 19 | 400000000000(予想) | 12683.91年 |
結果
今回はCPUとGPUの速度比較から始まって脱線し、ビットコインマイニングに必要な0が19個並ぶHASH値の検索に挑みましたが、全く歯が立たないことがわかりました。
それにしてもCuda早いですね。プログラムが複雑になるので、私の頭ではプログラムを組めませんが、AIに手伝ってもらえば、なんとかなりそうな印象です。(ほぼお任せですが)
