Skip to content

Instantly share code, notes, and snippets.

@ActiveTK
Last active September 3, 2025 15:03
Show Gist options
  • Select an option

  • Save ActiveTK/9d62b3a067f1d6f1930ef67d001f59b6 to your computer and use it in GitHub Desktop.

Select an option

Save ActiveTK/9d62b3a067f1d6f1930ef67d001f59b6 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import os, sys, signal, subprocess, threading, time, shutil, datetime, urllib.request, urllib.parse, mimetypes, random, string, glob, tarfile, tempfile, codecs, pty, select, re, fcntl, struct, termios
# 機能
#
# これを実行するだけでGPU(複数枚でもOK)でBitcoin Puzzleの71番を解き始められる
# 結果はTelegramで取得できる(一定周期ごとにサーバー情報を送信 & パズル完了の時にも情報送信)
# GPUだけ酷使しててCPUリソースはほぼ使わないのは可哀そうないのでとりあえずmkp224oでOnionのバニティも同時に走らせてみる
# つかいかた
#
# 1. https://btcpuzzle.info/ でアカウント作成 → トークンを取得
# 下のBCR_ENVのBC_USERTOKENを取得したトークンで置き換える
# BC_WORKERNAMEのところはワーカーの名前(適当で良いが公開される、「my_gpuserver」みたいなのが無難)を指定
# あと諸々のBCR_ENVの設定で変えたいところがあればChatGPTに聞いてくれ!
#
# 2. Telegramの通知を設定するために、まずはTelegramのbotを作り、そのbotにDMを送ってルームを作る
# 詳しいやり方はGeminiに聞いてくれ
# 作ったbotのトークンをTGRAM_TOKENに設定して、ルームのChatIDをTGRAM_CHATIDに指定する
# STATUS_PERIODに通知を受け取る周期を秒単位で指定する(テスト用に60秒に指定してある)
#
# 3. mkp224oの設定
# MKP_THREADSが利用するスレッド数で、MKP_TARGETSに探したい文字列を入れる(複数指定可能)
# もし鍵を見つけたら、自動で鍵をgzip圧縮してTelegramで送ってくれます
#
# 4. 実行
# 以下のURLにあるテンプレートを利用してvast.aiで鯖を借りる
# ただし RTX 5000シリーズのみ対応しているので注意
# RTX 4000シリーズでやりたければ ilkercndk/bitcrackrandomiser:latest でイメージを自作すること
#
# https://cloud.vast.ai?ref_id=293592&template_id=8d4bf52b582f6d9e4202de7718090eb0
#
# 複数GPUでも対応しているが、まずは安い1枚の鯖でやってみると良いかも
# 鯖を立ち上げたらコンソールから以下を実行
# > cd /app/
# > nano init.py
# このPythonスクリプトを貼り付けて Ctrl+O で保存、Ctrl+X で終了
# > python3 init.py
# Telegramに通知が来たら成功です
# ログは /app/init.log に書き込まれるので cat init.log で見れるはず
# なんかエラー出てたら教えてくれ、以上!
#
TGRAM_TOKEN = "Telegramのbotのトークン"
TGRAM_CHATID = "TelegramのbotとのDM画面のChatID"
STATUS_PERIOD = 60
BCR_ENV = {
"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/share/dotnet",
"BC_PUZZLE": "71",
"BC_USERTOKEN": "btcpuzzle.infoのユーザートークン",
"BC_WORKERNAME": "myworker",
"BC_APP_TYPE": "vanitysearch",
"BC_APP": "/app/VanitySearch/vanitysearch",
"BC_GPUSEPERATEDRANGE": "true",
"BC_TELEGRAM_SHARE": "true",
"BC_TELEGRAM_ACCESS_TOKEN": TGRAM_TOKEN,
"BC_TELEGRAM_CHAT_ID": TGRAM_CHATID,
"BC_TELEGRAM_SHARE_EACHKEY": "true",
"BC_UNTRUSTED_COMPUTER": "true",
"DOTNET_ROOT": "/usr/share/dotnet",
"DOTNET_CLI_TELEMETRY_OPTOUT": "1",
"DOTNET_SYSTEM_GLOBALIZATION_INVARIANT": "1",
}
MKP_THREADS = 4
MKP_TARGETS = ["hogehoge", "fugafuga"]
# 設定はここまでやぞ!by ActiveTK. (2025.9.3)
HOST = subprocess.getoutput("hostname -f 2>/dev/null || hostname").strip() or "unknown"
BASE = "/app"
MKPDIR = os.path.join(BASE, "mkp224o")
MKPOUTDIR = os.path.join(MKPDIR, "logs")
LOGDIR = "/var/log/bitcrack_ops"
BCRLOG = os.path.join(LOGDIR, "bcr.log")
MKPLOG = os.path.join(LOGDIR, "mkp.log")
PIDFILE = "/var/run/init_py_daemon.pid"
BCR_SH = "/app/bitcrackrandomiser/bitcrackrandomiser.sh"
INITLOG = os.path.abspath(os.path.join(os.getcwd(), "init.log"))
PRINT_TO_STDOUT = {"print": False}
INIT_FH = None
ANSI_RE = re.compile(r"\x1B\[[0-9;?]*[ -/]*[@-~]")
CSI_PATTERN = r"\x1B\[[0-9;?]*"
RE_TTY_LINEBREAK = re.compile(rf"(?:\r|{CSI_PATTERN}[G]|{CSI_PATTERN}[Hf]|{CSI_PATTERN}K{CSI_PATTERN}[G]|{CSI_PATTERN}2K{CSI_PATTERN}[G])")
OSC_RE = re.compile(r"\x1B\][^\a]*\a")
def now():
return datetime.datetime.now(datetime.timezone.utc).isoformat()
def send_text(text):
data = urllib.parse.urlencode({"chat_id": TGRAM_CHATID, "text": text}).encode()
req = urllib.request.Request(f"https://api.telegram.org/bot{TGRAM_TOKEN}/sendMessage", data=data)
try:
urllib.request.urlopen(req, timeout=15).read()
except Exception:
pass
def _mp(fields, files):
boundary = "".join(random.choices(string.ascii_letters + string.digits, k=30))
body = bytearray()
for k, v in fields.items():
body.extend(("--"+boundary+"\r\n").encode())
body.extend((f'Content-Disposition: form-data; name="{k}"\r\n\r\n').encode())
body.extend((str(v)+"\r\n").encode())
for k, path in files.items():
fn = os.path.basename(path)
ct = mimetypes.guess_type(fn)[0] or "application/octet-stream"
body.extend(("--"+boundary+"\r\n").encode())
body.extend((f'Content-Disposition: form-data; name="{k}"; filename="{fn}"\r\n').encode())
body.extend((f"Content-Type: {ct}\r\n\r\n").encode())
with open(path, "rb") as f:
body.extend(f.read())
body.extend("\r\n".encode())
body.extend(("--"+boundary+"--\r\n").encode())
return boundary, bytes(body)
def send_document(path, caption):
fields = {"chat_id": TGRAM_CHATID, "caption": caption}
files = {"document": path}
b, body = _mp(fields, files)
req = urllib.request.Request(f"https://api.telegram.org/bot{TGRAM_TOKEN}/sendDocument", data=body)
req.add_header("Content-Type", f"multipart/form-data; boundary={b}")
try:
urllib.request.urlopen(req, timeout=30).read()
except Exception:
pass
def sanitize_status_line(s):
if s is None:
return ""
t = s
t = RE_TTY_LINEBREAK.sub("\n", t)
t = t.replace("\r\n", "\n").replace("\r", "\n").split("\n")[-1]
t = OSC_RE.sub("", t)
t = ANSI_RE.sub("", t).strip()
return t
def _normalize_stream_text(s):
s = RE_TTY_LINEBREAK.sub("\n", s)
s = s.replace("\r\n", "\n").replace("\r", "\n")
s = OSC_RE.sub("", s)
return s
def pty_reader(master_fd, logfile_path, lastline_holder, print_flag):
os.makedirs(os.path.dirname(logfile_path), exist_ok=True)
decoder = codecs.getincrementaldecoder("utf-8")("replace")
carry = ""
MAX_CARRY = 1000000
with open(logfile_path, "a", buffering=1) as lf:
while True:
r, _, _ = select.select([master_fd], [], [], 0.5)
if master_fd not in r:
if carry:
normalized = _normalize_stream_text(carry)
parts = normalized.split("\n")
carry = parts.pop()
for line in parts:
lf.write(line + "\n")
if print_flag.get("print"):
try:
sys.stdout.write(line + "\n"); sys.stdout.flush()
except Exception:
pass
ls = sanitize_status_line(line)
if ls:
lastline_holder["last"] = line
lastline_holder["last_nonempty"] = ls
if len(carry) > MAX_CARRY:
lf.write(carry + "\n")
if print_flag.get("print"):
try:
sys.stdout.write(carry + "\n"); sys.stdout.flush()
except Exception:
pass
lastline_holder["last"] = carry
lastline_holder["last_nonempty"] = sanitize_status_line(carry)
carry = ""
continue
try:
chunk = os.read(master_fd, 4096)
except OSError:
break
if not chunk:
break
try:
s = decoder.decode(chunk)
except Exception:
s = chunk.decode(errors="replace")
carry += s
normalized = _normalize_stream_text(carry)
parts = normalized.split("\n")
carry = parts.pop()
for line in parts:
lf.write(line + "\n")
if print_flag.get("print"):
try:
sys.stdout.write(line + "\n"); sys.stdout.flush()
except Exception:
pass
ls = sanitize_status_line(line)
if ls:
lastline_holder["last"] = line
lastline_holder["last_nonempty"] = ls
if len(carry) > MAX_CARRY:
lf.write(carry + "\n")
if print_flag.get("print"):
try:
sys.stdout.write(carry + "\n"); sys.stdout.flush()
except Exception:
pass
lastline_holder["last"] = carry
lastline_holder["last_nonempty"] = sanitize_status_line(carry)
carry = ""
tail = decoder.decode(b"", final=True)
if tail:
carry += tail
if carry:
carry = _normalize_stream_text(carry)
lf.write(carry + "\n")
if print_flag.get("print"):
try:
sys.stdout.write(carry + "\n"); sys.stdout.flush()
except Exception:
pass
ls = sanitize_status_line(carry)
if ls:
lastline_holder["last"] = carry
lastline_holder["last_nonempty"] = ls
try:
os.close(master_fd)
except Exception:
pass
def line_reader(proc, logfile_path, lastline_holder, print_flag):
os.makedirs(os.path.dirname(logfile_path), exist_ok=True)
decoder = codecs.getincrementaldecoder("utf-8")("replace")
carry = ""
MAX_CARRY = 1000000
with open(logfile_path, "a", buffering=1) as lf:
reader = proc.stdout
readfn = getattr(reader, "read1", reader.read)
while True:
chunk = readfn(65536)
if not chunk:
break
try:
s = decoder.decode(chunk)
except Exception:
s = chunk.decode(errors="replace")
if not s:
continue
carry += s
normalized = _normalize_stream_text(carry)
parts = normalized.split("\n")
carry = parts.pop()
for line in parts:
lf.write(line + "\n")
if print_flag.get("print"):
try:
sys.stdout.write(line + "\n"); sys.stdout.flush()
except Exception:
pass
ls = sanitize_status_line(line)
if ls:
lastline_holder["last"] = line
lastline_holder["last_nonempty"] = ls
if len(carry) > MAX_CARRY:
lf.write(carry + "\n")
if print_flag.get("print"):
try:
sys.stdout.write(carry + "\n"); sys.stdout.flush()
except Exception:
pass
lastline_holder["last"] = carry
lastline_holder["last_nonempty"] = sanitize_status_line(carry)
carry = ""
tail = decoder.decode(b"", final=True)
if tail:
carry += tail
if carry:
carry = _normalize_stream_text(carry)
lf.write(carry + "\n")
if print_flag.get("print"):
try:
sys.stdout.write(carry + "\n"); sys.stdout.flush()
except Exception:
pass
ls = sanitize_status_line(carry)
if ls:
lastline_holder["last"] = carry
lastline_holder["last_nonempty"] = ls
try:
proc.stdout.close()
except Exception:
pass
def set_oom_protect(pid):
try:
with open(f"/proc/{pid}/oom_score_adj","w") as f:
f.write("-1000")
except Exception:
pass
def run(cmd, cwd=None, check=True):
return subprocess.run(cmd, cwd=cwd, stdout=INIT_FH, stderr=INIT_FH, check=check)
def ensure_dotnet():
if shutil.which("dotnet"):
return
try:
subprocess.run(["apt-get","update","-y"], stdout=INIT_FH, stderr=INIT_FH, check=False)
subprocess.run(["apt-get","install","-y","wget","gpg","ca-certificates","apt-transport-https","curl"], stdout=INIT_FH, stderr=INIT_FH, check=False)
tmpd = tempfile.mkdtemp()
deb = os.path.join(tmpd,"packages-microsoft-prod.deb")
subprocess.run(["curl","-L","https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb","-o",deb], stdout=INIT_FH, stderr=INIT_FH, check=True)
subprocess.run(["dpkg","-i",deb], stdout=INIT_FH, stderr=INIT_FH, check=False)
subprocess.run(["apt-get","update","-y"], stdout=INIT_FH, stderr=INIT_FH, check=False)
subprocess.run(["apt-get","install","-y","dotnet-runtime-8.0"], stdout=INIT_FH, stderr=INIT_FH, check=True)
except Exception:
pass
def ensure_mkp224o():
os.makedirs(BASE, exist_ok=True)
if not os.path.isdir(MKPDIR) or not os.path.isfile(os.path.join(MKPDIR,"configure.ac")):
tmp = os.path.join(BASE, f".mkp224o_tmp_{int(time.time())}")
if os.path.isdir(tmp):
shutil.rmtree(tmp, ignore_errors=True)
run(["git","clone","https://github.com/cathugger/mkp224o", tmp], cwd=BASE, check=True)
for _ in range(30):
if os.path.isdir(tmp) and os.path.isfile(os.path.join(tmp,"autogen.sh")):
break
time.sleep(1)
if os.path.isdir(MKPDIR):
shutil.rmtree(MKPDIR, ignore_errors=True)
os.rename(tmp, MKPDIR)
for _ in range(5):
if os.path.isfile(os.path.join(MKPDIR,"autogen.sh")):
break
time.sleep(1)
if not os.path.isfile(os.path.join(MKPDIR,"autogen.sh")):
run(["autoreconf","-fi"], cwd=MKPDIR, check=True)
else:
run(["/bin/sh","autogen.sh"], cwd=MKPDIR, check=False)
run(["./configure"], cwd=MKPDIR, check=True)
run(["make","-j","1"], cwd=MKPDIR, check=True)
os.makedirs(MKPOUTDIR, exist_ok=True)
def sanitize_sh(path):
try:
with open(path,"rb") as f:
data = f.read()
if data.startswith(b"\xEF\xBB\xBF"):
data = data[3:]
data = data.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
if not data.startswith(b"#!/"):
data = b"#!/bin/sh\n"+data
with open(path,"wb") as f:
f.write(data)
os.chmod(path,0o755)
except Exception:
pass
def _set_winsize(fd, rows=60, cols=180):
try:
fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack("HHHH", rows, cols, 0, 0))
except Exception:
pass
def start_bcr(print_flag):
sanitize_sh(BCR_SH)
env = dict(BCR_ENV)
env.setdefault("TERM", "xterm")
env.setdefault("COLUMNS", "180")
env.setdefault("LINES", "60")
master, slave = pty.openpty()
try:
_set_winsize(slave, rows=int(env["LINES"]), cols=int(env["COLUMNS"]))
except Exception:
pass
proc = subprocess.Popen(
["/bin/sh", os.path.basename(BCR_SH)],
stdin=slave,
stdout=slave,
stderr=slave,
env=env,
text=False,
bufsize=0,
cwd="/app/bitcrackrandomiser",
start_new_session=True,
preexec_fn=None,
close_fds=True,
)
try:
os.close(slave)
except Exception:
pass
set_oom_protect(proc.pid)
last = {"last": "", "last_nonempty": ""}
t = threading.Thread(target=pty_reader, args=(master, BCRLOG, last, print_flag), daemon=True)
t.start()
return proc, last
def start_mkp(print_flag):
args = [os.path.join(MKPDIR,"mkp224o")] + MKP_TARGETS + ["-v","-s","-t",str(MKP_THREADS),"-d",MKPOUTDIR]
proc = subprocess.Popen(
args,
cwd=MKPDIR,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=False,
bufsize=0,
start_new_session=True,
preexec_fn=None,
)
set_oom_protect(proc.pid)
last = {"last": "", "last_nonempty": ""}
t = threading.Thread(target=line_reader, args=(proc, MKPLOG, last, print_flag), daemon=True)
t.start()
return proc, last
def ship_tail_logs():
bt="/tmp/bcr_tail.log"; mt="/tmp/mkp_tail.log"
try:
with open(BCRLOG,"rb") as f:
open(bt,"wb").write(f.read()[-200000:])
except Exception:
pass
try:
with open(MKPLOG,"rb") as f:
open(mt,"wb").write(f.read()[-200000:])
except Exception:
pass
if os.path.exists(bt) and os.path.getsize(bt)>0:
send_document(bt,"bcr.log tail")
if os.path.exists(mt) and os.path.getsize(mt)>0:
send_document(mt,"mkp.log tail")
def detach_to_background():
PRINT_TO_STDOUT["print"] = False
try:
pid = os.fork()
if pid > 0:
return True
except OSError:
return False
os.setsid()
signal.signal(signal.SIGHUP, signal.SIG_IGN)
try:
pid = os.fork()
if pid > 0:
os._exit(0)
except OSError:
os._exit(1)
os.chdir("/")
os.umask(0)
try:
with open(PIDFILE,"w") as f:
f.write(str(os.getpid()))
except Exception:
pass
return False
def fmt_status(phase, bcr_state, bcr_pid, mkp_state, mkp_pid, threads, last_bcr, last_mkp):
lb = sanitize_status_line(last_bcr) if last_bcr else ""
lm = sanitize_status_line(last_mkp) if last_mkp else ""
return (
f"【status::{HOST}\n\n"
f"Date: {now()}\n"
f"Phase: {phase}\n"
f"process_bcr={bcr_state} pid={bcr_pid}\n"
f"process_mkp={mkp_state} pid={mkp_pid} ({threads} threads)\n"
f"bcr -> {lb}\n"
f"mkp -> {lm}"
)
def main():
if detach_to_background():
return
global INIT_FH
INIT_FH = open(INITLOG, "ab", buffering=0)
os.makedirs(LOGDIR, exist_ok=True); os.makedirs(MKPOUTDIR, exist_ok=True)
open(BCRLOG,"a").close(); open(MKPLOG,"a").close()
try:
subprocess.run(["apt-get","update","-y"], stdout=INIT_FH, stderr=INIT_FH, check=False)
subprocess.run(["apt-get","install","-y","curl","git","build-essential","autoconf","automake","libtool","pkg-config","libsodium-dev","ca-certificates","coreutils","procps","autoconf-archive","htop"], stdout=INIT_FH, stderr=INIT_FH, check=False)
except Exception:
pass
ensure_dotnet()
ensure_mkp224o()
try:
if os.path.exists(BCR_SH):
sanitize_sh(BCR_SH)
except Exception:
pass
prev_dirs = set()
try:
prev_dirs = set(d for d in os.listdir(MKPOUTDIR) if d.endswith(".onion") and os.path.isdir(os.path.join(MKPOUTDIR,d)))
except Exception:
pass
bcr, bcr_last = start_bcr(PRINT_TO_STDOUT)
mkp, mkp_last = start_mkp(PRINT_TO_STDOUT)
phase = "running"
next_status = time.monotonic() + STATUS_PERIOD
while True:
time.sleep(1)
lastline_bcr_nonempty = bcr_last.get("last_nonempty","")
if time.monotonic() >= next_status:
bcr_state = "RUNNING" if bcr.poll() is None else "NOT_RUNNING"
bcr_pid = bcr.pid if bcr.poll() is None else "-"
mkp_state = "RUNNING" if mkp.poll() is None else "NOT_RUNNING"
mkp_pid = mkp.pid if mkp.poll() is None else "-"
msg = (
f"【status::{HOST}\n\n"
f"Date: {now()}\n"
f"Phase: {phase}\n"
f"process_bcr={bcr_state} pid={bcr_pid}\n"
f"process_mkp={mkp_state} pid={mkp_pid} ({MKP_THREADS} threads)\n"
f"bcr -> {lastline_bcr_nonempty}\n"
f"mkp -> {sanitize_status_line(mkp_last.get('last',''))}"
)
send_text(msg)
next_status = time.monotonic() + STATUS_PERIOD
if bcr.poll() is not None:
ship_tail_logs()
bcr_state = "EXITED"
mkp_state = "RUNNING" if mkp.poll() is None else "NOT_RUNNING"
mkp_pid = mkp.pid if mkp.poll() is None else "-"
send_text(
f"【status::{HOST}\n\n"
f"Date: {now()}\n"
f"Phase: {phase}\n"
f"process_bcr={bcr_state} pid=-\n"
f"process_mkp={mkp_state} pid={mkp_pid} ({MKP_THREADS} threads)\n"
f"bcr -> {lastline_bcr_nonempty}\n"
f"mkp -> {sanitize_status_line(mkp_last.get('last',''))}"
)
time.sleep(5)
bcr, bcr_last = start_bcr({"print": False})
lastline_bcr_nonempty = ""
send_text(
f"【status::{HOST}\n\n"
f"Date: {now()}\n"
f"Phase: {phase}\n"
f"process_bcr=RUNNING pid={bcr.pid}\n"
f"process_mkp={'RUNNING' if mkp.poll() is None else 'NOT_RUNNING'} pid={(mkp.pid if mkp.poll() is None else '-')} ({MKP_THREADS} threads)\n"
f"bcr -> {lastline_bcr_nonempty}\n"
f"mkp -> {sanitize_status_line(mkp_last.get('last',''))}"
)
next_status = time.monotonic() + STATUS_PERIOD
if mkp.poll() is not None:
bcr_state = "RUNNING" if bcr.poll() is None else "NOT_RUNNING"
bcr_pid = bcr.pid if bcr.poll() is None else "-"
send_text(
f"【status::{HOST}\n\n"
f"Date: {now()}\n"
f"Phase: {phase}\n"
f"process_bcr={bcr_state} pid={bcr_pid}\n"
f"process_mkp=EXITED pid=- ({MKP_THREADS} threads)\n"
f"bcr -> {lastline_bcr_nonempty}\n"
f"mkp -> {sanitize_status_line(mkp_last.get('last',''))}"
)
time.sleep(5)
mkp, mkp_last = start_mkp({"print": False})
send_text(
f"【status::{HOST}\n\n"
f"Date: {now()}\n"
f"Phase: {phase}\n"
f"process_bcr={'RUNNING' if bcr.poll() is None else 'NOT_RUNNING'} pid={(bcr.pid if bcr.poll() is None else '-')}\n"
f"process_mkp=RUNNING pid={mkp.pid} ({MKP_THREADS} threads)\n"
f"bcr -> {lastline_bcr_nonempty}\n"
f"mkp -> {sanitize_status_line(mkp_last.get('last',''))}"
)
next_status = time.monotonic() + STATUS_PERIOD
try:
cur_dirs = set(d for d in os.listdir(MKPOUTDIR) if d.endswith(".onion") and os.path.isdir(os.path.join(MKPOUTDIR,d)))
except Exception:
cur_dirs = set()
new_dirs = sorted(list(cur_dirs - prev_dirs))
if new_dirs:
for nd in new_dirs:
full = os.path.join(MKPOUTDIR, nd)
if os.path.isdir(full):
send_text(f"alert\nhost={HOST}\ntime={now()}\nmkp224o_output_dir={nd}")
tarpath = f"/tmp/{nd}.tar.gz"
try:
with tarfile.open(tarpath,"w:gz") as tar:
tar.add(full, arcname=os.path.basename(full))
send_document(tarpath, f"mkp224o {nd}")
except Exception:
pass
prev_dirs = cur_dirs
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment