Last active
December 2, 2025 13:46
-
-
Save akku1139/c0179988358fc0e8a8ee85d84c84f6a0 to your computer and use it in GitHub Desktop.
公式の認証UIに対し100倍の高速化を達成 47KH/s -> 5.7MH/s (Core i7-4770S) (vibe coder)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 0. download hash.sh | |
| you don't need to download pow_solver.c (it will be downloaded automatically) | |
| 1. join TK鯖 | |
| https://discord.gg/Cdr79JCspz | |
| 2. open verify | |
| ex: https://tksaba.activetk.jp/verify?token=YOUR_VERIFY_TOKEN_IS_HERE | |
| copy token, and you can close the tab | |
| 3. run this solver | |
| bash start.sh YOUR_VERIFY_TOKEN_IS_HERE | |
| 4. enjoy hashing | |
| 5. successful verification | |
| ------ | |
| This work is released under WTFPL2, check LICENSE. | |
| ------ | |
| TODO | |
| - port to OpenCL | |
| ------ | |
| published on tksaba | |
| https://discord.com/channels/1443942056903835823/1444698572116197456/1445365237980332074 | |
| WebGPU SHA-256 hasher (by yussy <@796972193287503913>) | |
| https://pow-bench.sprink.cloud/ | |
| https://discord.com/channels/1443942056903835823/1444321139491864757/1445324694256357497 | |
| OpenCL WIP | |
| https://discord.com/channels/1443942056903835823/1444321139491864757/1445410194963169413 | |
| ------ | |
| Test data | |
| {"challenge":"d81390769f60bc9095bc7dbb3f972e07","target":"0x000002843ebe81b06ec834b797b1620d3b51e5a5e1a678a52d4b92a58991e499","difficulty":1.5e-7} | |
| {"token":"xxxxxx","nonce":3689816,"elapsed_time":75.865} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # $1: token | |
| echo '[info] fetching challange' | |
| TK=$( curl "https://tksaba.activetk.jp/api/verify.php?action=get_challenge&token=$1" ) | |
| IFS=' ' read -r CHALLENGE TARGET <<< $( echo "$TK" | jq -r '(.challenge // "") + " " + (.target[2:] // "")' ) | |
| if [[ -z "$CHALLENGE" ]] || [[ -z "$TARGET" ]]; then | |
| echo '[info] could not fetch challange' | |
| echo '[info] api response' | |
| echo "$TK" | jq | |
| exit 1 | |
| fi | |
| echo '[info] challange is' | |
| echo 'source:' $CHALLENGE | |
| echo 'target:' $TARGET | |
| echo '[info] starting hash' | |
| MAKEFILE_TMP=$(mktemp) | |
| cat << 'EOF' > "$MAKEFILE_TMP" | |
| pow_solver: pow_solver.c | |
| gcc -o pow_solver pow_solver.c -lcrypto -lpthread -lm | |
| pow_solver.c: | |
| curl -O https://gist.githubusercontent.com/akku1139/c0179988358fc0e8a8ee85d84c84f6a0/raw/256cfe0aeffa6a3895912dc049a9d09091ac0e6a/pow_solver.c | |
| EOF | |
| make -f "$MAKEFILE_TMP" pow_solver | |
| rm "$MAKEFILE_TMP" | |
| make | |
| HA=$( ./pow_solver -c "$CHALLENGE" -t "$TARGET" | tee /dev/tty ) | |
| if [ "${PIPESTATUS[0]}" -ne 0 ]; then | |
| echo '[info] hasher error' | |
| exit 1 | |
| fi | |
| IFS=' ' read -r NONCE TIME <<< $( echo "$HA" | awk '$1 == "Nonce" { f=$3 } $1 == "Time" { t=$3 } END { print f " " t }' ) | |
| echo '[info] Nonce:' $NONCE | |
| echo '[info] Time' $TIME | |
| echo '[info] sending result' | |
| RS=$( curl -X POST 'https://tksaba.activetk.jp/api/verify.php?action=verify' \ | |
| -H 'Content-Type: application/json' \ | |
| -d '{"token": "'$1'", "nonce": '$NONCE', "elapsed_time": '$TIME'}' ) | |
| echo $RS | |
| echo "$RS" | jq -e '.success == true' | |
| if [ $? -ne 0 ]; then | |
| echo '[info] verify failed' | |
| exit 1 | |
| fi | |
| echo '[info] verify done' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
| Version 2, December 2004 | |
| Copyright (C) 2025 Tsuyukusa Akari <@1218933751950872728> | |
| Everyone is permitted to copy and distribute verbatim or modified | |
| copies of this license document, and changing it is allowed as long | |
| as the name is changed. | |
| DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
| TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
| 0. You just DO WHAT THE FUCK YOU WANT TO. | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define _GNU_SOURCE // CPUアフィニティ関数を有効にする | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <pthread.h> | |
| #include <time.h> | |
| #include <sched.h> // CPUアフィニティ用 | |
| #include <math.h> | |
| #include <sys/sysinfo.h> // コア数取得用 (Linux) | |
| #include <errno.h> | |
| #include <sys/time.h> | |
| #include <getopt.h> // コマンドライン引数処理用 | |
| // OpenSSL | |
| #include <openssl/sha.h> | |
| // --- 定数とマクロ --- | |
| #define HASH_SIZE 32 // SHA-256出力サイズ (256 bits = 32 bytes) | |
| #define CHALLENGE_MAX_LEN 100 | |
| #define TARGET_HEX_MAX_LEN (HASH_SIZE * 2 + 1) // 64文字 + null終端 | |
| #define PREFIX "TKsaba_PoW_challange_" | |
| #define PROGRESS_INTERVAL_MS 500 // 進捗表示の更新間隔 (ミリ秒) | |
| // 進捗表示のための制御文字 | |
| #define CLEAR_LINE "\r\033[K" | |
| #define CURSOR_UP "\033[A" | |
| // --- グローバル共有構造体 --- | |
| // 全スレッドで共有するデータと状態 | |
| typedef struct { | |
| unsigned char target[HASH_SIZE]; // ターゲットハッシュ (バイト配列) | |
| char challenge[CHALLENGE_MAX_LEN]; // チャレンジ文字列 | |
| int num_threads; // 使用するスレッド数 | |
| int num_cores; // システムの論理コア数 | |
| // 結果 | |
| volatile int solution_found; // 解決策が見つかったフラグ (1で見つかった) | |
| volatile long long found_nonce; // 見つかったNonce | |
| unsigned char final_hash[HASH_SIZE]; // 最終ハッシュ | |
| // 進捗 | |
| volatile long long total_hashes_tried; // 全スレッドの合計試行回数 | |
| pthread_mutex_t mutex; // 共有リソースへのアクセスを保護するためのミューテックス | |
| } SolverParams; | |
| // スレッド固有のデータ構造体 | |
| typedef struct { | |
| int thread_id; // 0から始まるスレッドID | |
| int core_id; // ピン留めするコアID | |
| long long start_nonce; // このスレッドが探索を開始するNonce | |
| long long nonce_increment; // Nonceの増分 (スレッド数に等しい) | |
| long long hashes_count; // このスレッドが試行したハッシュ数 | |
| SolverParams *params; | |
| } ThreadData; | |
| // --- ユーティリティ関数 --- | |
| /** | |
| * @brief 16進数文字を値に変換 | |
| * @param c 16進数文字 | |
| * @return 対応する値 (0-15) | |
| */ | |
| static unsigned char hex_char_to_int(char c) { | |
| if (c >= '0' && c <= '9') return c - '0'; | |
| if (c >= 'a' && c <= 'f') return c - 'a' + 10; | |
| if (c >= 'A' && c <= 'F') return c - 'A' + 10; | |
| return 0; | |
| } | |
| /** | |
| * @brief 16進数文字列をバイト配列に変換 | |
| * @param hex_str 16進数文字列 (大文字/小文字) | |
| * @param byte_arr 格納先バイト配列 (サイズはstrlen(hex_str)/2) | |
| * @param max_len byte_arrの最大サイズ (バイト単位) | |
| * @return 変換されたバイト数。エラーの場合は-1。 | |
| */ | |
| int hex_to_bytes(const char *hex_str, unsigned char *byte_arr, size_t max_len) { | |
| size_t len = strlen(hex_str); | |
| if (len % 2 != 0 || len / 2 > max_len) { | |
| return -1; // 長さが不正 | |
| } | |
| size_t byte_len = len / 2; | |
| for (size_t i = 0; i < byte_len; i++) { | |
| unsigned char high = hex_char_to_int(hex_str[2 * i]); | |
| unsigned char low = hex_char_to_int(hex_str[2 * i + 1]); | |
| byte_arr[i] = (high << 4) | low; | |
| } | |
| return (int)byte_len; | |
| } | |
| /** | |
| * @brief 256ビットのバイト配列(ビッグエンディアン)を比較 (a < b を判定) | |
| * @param a 最初のバイト配列 (ハッシュ) | |
| * @param b 2番目のバイト配列 (ターゲット) | |
| * @param len バイト配列の長さ (32) | |
| * @return a < b なら -1, a == b なら 0, a > b なら 1 | |
| */ | |
| int big_int_compare(const unsigned char *a, const unsigned char *b, size_t len) { | |
| for (size_t i = 0; i < len; i++) { | |
| if (a[i] < b[i]) return -1; | |
| if (a[i] > b[i]) return 1; | |
| } | |
| return 0; // 全て同じ | |
| } | |
| /** | |
| * @brief バイト配列を16進数文字列に変換 | |
| * @param byte_arr バイト配列 | |
| * @param hex_str 格納先16進数文字列 (サイズはlen * 2 + 1) | |
| * @param len バイト配列の長さ | |
| */ | |
| void bytes_to_hex(const unsigned char *byte_arr, char *hex_str, size_t len) { | |
| for (size_t i = 0; i < len; i++) { | |
| sprintf(hex_str + (i * 2), "%02x", byte_arr[i]); | |
| } | |
| hex_str[len * 2] = '\0'; | |
| } | |
| /** | |
| * @brief システムの論理コア数を取得 | |
| * @return 論理コア数。取得失敗時は1。 | |
| */ | |
| int get_num_cores() { | |
| #if defined(_SC_NPROCESSORS_ONLN) | |
| long cores = sysconf(_SC_NPROCESSORS_ONLN); | |
| if (cores > 0) return (int)cores; | |
| #elif defined(_WIN32) | |
| // Windows対応が必要な場合は追加 | |
| #endif | |
| // 取得できなかった場合はデフォルトで1コア | |
| return 1; | |
| } | |
| /** | |
| * @brief 高精度な現在時刻 (秒) を取得 | |
| * @return 現在時刻 (秒) | |
| */ | |
| double get_current_time() { | |
| struct timeval tv; | |
| gettimeofday(&tv, NULL); | |
| return tv.tv_sec + tv.tv_usec / 1000000.0; | |
| } | |
| // --- PoWソルバーロジック --- | |
| /** | |
| * @brief ハッシュ計算と検証を行うスレッド関数 | |
| * @param arg ThreadData構造体へのポインタ | |
| * @return NULL | |
| */ | |
| void *worker_thread(void *arg) { | |
| ThreadData *data = (ThreadData *)arg; | |
| SolverParams *params = data->params; | |
| // 1. CPUアフィニティの設定 | |
| int core_id = data->core_id % params->num_cores; | |
| cpu_set_t cpuset; | |
| CPU_ZERO(&cpuset); | |
| CPU_SET(core_id, &cpuset); | |
| if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) == 0) { | |
| fprintf(stderr, "Thread %d pinned to core %d.\n", data->thread_id, core_id); | |
| } else { | |
| // エラーログは出すが、続行 | |
| fprintf(stderr, "Warning: Thread %d failed to pin to core %d (Error: %s). Continuing without pinning.\n", | |
| data->thread_id, core_id, strerror(errno)); | |
| } | |
| // 2. 変数の初期化 | |
| SHA256_CTX sha_ctx; | |
| unsigned char hash_output[HASH_SIZE]; | |
| long long nonce = data->start_nonce; | |
| size_t prefix_len = strlen(PREFIX); | |
| size_t challenge_len = strlen(params->challenge); | |
| // 最大文字列長 = PREFIX長 + CHALLENGE長 + 20 (Nonceの桁数最大) + 1 (NULL) | |
| size_t max_str_len = prefix_len + challenge_len + 25; | |
| char *input_str = (char *)malloc(max_str_len); | |
| if (!input_str) { | |
| perror("malloc failed"); | |
| return NULL; | |
| } | |
| // 入力文字列のベース部分を準備 | |
| strcpy(input_str, PREFIX); | |
| strcat(input_str, params->challenge); | |
| size_t base_len = strlen(input_str); // Nonceを連結する位置 | |
| // 3. メインハッシュループ | |
| while (params->solution_found == 0) { | |
| // Nonceを文字列に連結 | |
| sprintf(input_str + base_len, "%lld", nonce); | |
| // SHA-256ハッシュの計算 | |
| SHA256_Init(&sha_ctx); | |
| SHA256_Update(&sha_ctx, input_str, strlen(input_str)); | |
| SHA256_Final(hash_output, &sha_ctx); | |
| data->hashes_count++; | |
| // ハッシュとターゲットの比較 | |
| // ハッシュ < ターゲット の場合、成功 (big_int_compareが-1を返す) | |
| if (big_int_compare(hash_output, params->target, HASH_SIZE) < 0) { | |
| // 解決策が見つかった! | |
| pthread_mutex_lock(¶ms->mutex); | |
| if (params->solution_found == 0) { | |
| params->solution_found = 1; | |
| params->found_nonce = nonce; | |
| memcpy(params->final_hash, hash_output, HASH_SIZE); | |
| } | |
| pthread_mutex_unlock(¶ms->mutex); | |
| // ループを終了 | |
| break; | |
| } | |
| // 次のNonceへ | |
| nonce += data->nonce_increment; | |
| // 進捗報告 (必要以上にロックしないように、ローカル変数にカウント) | |
| if (data->hashes_count % 100000 == 0) { | |
| pthread_mutex_lock(¶ms->mutex); | |
| params->total_hashes_tried += 100000; | |
| pthread_mutex_unlock(¶ms->mutex); | |
| data->hashes_count = 0; | |
| } | |
| } | |
| // 最後に残りのハッシュ数を報告 | |
| pthread_mutex_lock(¶ms->mutex); | |
| params->total_hashes_tried += data->hashes_count; | |
| pthread_mutex_unlock(¶ms->mutex); | |
| free(input_str); | |
| return NULL; | |
| } | |
| /** | |
| * @brief 動的な進捗表示を処理 | |
| * @param params SolverParams構造体へのポインタ | |
| * @param start_time 処理開始時刻 | |
| * @param last_total_hashes 前回表示時の合計ハッシュ数 | |
| * @param last_progress_time 前回表示時の時刻 | |
| */ | |
| void print_progress(SolverParams *params, double start_time, long long *last_total_hashes, double *last_progress_time) { | |
| long long current_hashes = params->total_hashes_tried; | |
| double current_time = get_current_time(); | |
| // 前回の更新から一定時間経過しているか確認 | |
| if (current_time - *last_progress_time < (double)PROGRESS_INTERVAL_MS / 1000.0) { | |
| return; | |
| } | |
| double time_delta = current_time - *last_progress_time; | |
| long long hashes_delta = current_hashes - *last_total_hashes; | |
| // ハッシュレートの計算 | |
| double hashrate = (time_delta > 0) ? (double)hashes_delta / time_delta : 0.0; | |
| // 合計経過時間の計算 | |
| double elapsed_time = current_time - start_time; | |
| // 進捗表示をコンソールに出力 | |
| // \rでカーソルを先頭に戻し、\033[Kで現在の行をクリア | |
| fprintf(stderr, CLEAR_LINE "Progress: %lld hashes (%.2f H/s)", | |
| current_hashes, hashrate); | |
| fflush(stderr); | |
| // 状態を更新 | |
| *last_total_hashes = current_hashes; | |
| *last_progress_time = current_time; | |
| } | |
| // --- メイン関数 --- | |
| int main(int argc, char *argv[]) { | |
| SolverParams params = {0}; | |
| pthread_t *threads = NULL; | |
| ThreadData *t_data = NULL; | |
| char *challenge_hex = NULL; | |
| char *target_hex_str = NULL; | |
| // --- 1. コマンドライン引数の解析 --- | |
| int opt; | |
| while ((opt = getopt(argc, argv, "c:t:")) != -1) { | |
| switch (opt) { | |
| case 'c': | |
| challenge_hex = optarg; | |
| break; | |
| case 't': | |
| target_hex_str = optarg; | |
| break; | |
| default: | |
| fprintf(stderr, "Usage: %s -c <challenge> -t <target>\n", argv[0]); | |
| return EXIT_FAILURE; | |
| } | |
| } | |
| // 必須引数のチェック | |
| if (!challenge_hex || !target_hex_str) { | |
| fprintf(stderr, "Error: Both challenge (-c) and target (-t) must be provided.\n"); | |
| fprintf(stderr, "Usage: %s -c <challenge> -t <target>\n", argv[0]); | |
| return EXIT_FAILURE; | |
| } | |
| // --- 2. 初期化とパラメータ設定 --- | |
| // チャレンジのコピーと検証 | |
| if (strlen(challenge_hex) >= CHALLENGE_MAX_LEN) { | |
| fprintf(stderr, "Error: Challenge length exceeds %d characters.\n", CHALLENGE_MAX_LEN - 1); | |
| return EXIT_FAILURE; | |
| } | |
| strcpy(params.challenge, challenge_hex); | |
| // ターゲットの変換と検証 | |
| if (hex_to_bytes(target_hex_str, params.target, HASH_SIZE) != HASH_SIZE) { | |
| fprintf(stderr, "Error: Target is not a valid 256-bit (64 character) hex string.\n"); | |
| return EXIT_FAILURE; | |
| } | |
| // コア数とスレッド数の決定 | |
| params.num_cores = get_num_cores(); | |
| params.num_threads = params.num_cores > 0 ? params.num_cores : 1; | |
| pthread_mutex_init(¶ms.mutex, NULL); | |
| // --- 3. UIの表示とスレッドの準備 --- | |
| fprintf(stdout, "Detected %d logical cores. Using %d threads.\n", params.num_cores, params.num_threads); | |
| fprintf(stdout, "--- C Proof-of-Work Solver (Multithreaded) ---\n"); | |
| fprintf(stdout, "Challenge: %s\n", params.challenge); | |
| fprintf(stdout, "Target: %s\n", target_hex_str); | |
| fprintf(stdout, "Searching with %d threads...\n", params.num_threads); | |
| threads = (pthread_t *)malloc(params.num_threads * sizeof(pthread_t)); | |
| t_data = (ThreadData *)malloc(params.num_threads * sizeof(ThreadData)); | |
| if (!threads || !t_data) { | |
| perror("Failed to allocate memory for threads/data"); | |
| goto cleanup; | |
| } | |
| // --- 4. スレッドの起動 --- | |
| double start_time = get_current_time(); | |
| for (int i = 0; i < params.num_threads; i++) { | |
| t_data[i].thread_id = i; | |
| t_data[i].core_id = i % params.num_cores; // コアIDは論理コア数で巡回 | |
| t_data[i].start_nonce = i; // 各スレッドは異なる開始Nonceを持つ | |
| t_data[i].nonce_increment = params.num_threads; // スレッド数分のインクリメント | |
| t_data[i].hashes_count = 0; | |
| t_data[i].params = ¶ms; | |
| if (pthread_create(&threads[i], NULL, worker_thread, &t_data[i]) != 0) { | |
| perror("Failed to create thread"); | |
| params.num_threads = i; // 起動済みのスレッド数に調整 | |
| goto cleanup; | |
| } | |
| } | |
| // --- 5. 進捗表示ループと結果待機 --- | |
| long long last_total_hashes = 0; | |
| double last_progress_time = start_time; | |
| while (params.solution_found == 0) { | |
| // 進捗表示を更新 | |
| print_progress(¶ms, start_time, &last_total_hashes, &last_progress_time); | |
| // 解決策が見つかったか、またはタイムアウトするまで待機 | |
| usleep(PROGRESS_INTERVAL_MS * 1000); | |
| } | |
| // 解決策が見つかった場合、最後の進捗を更新 | |
| print_progress(¶ms, start_time, &last_total_hashes, &last_progress_time); | |
| // 解決策を見つけたスレッド以外を停止させるために、すべてのスレッドを結合 | |
| for (int i = 0; i < params.num_threads; i++) { | |
| pthread_join(threads[i], NULL); | |
| } | |
| double end_time = get_current_time(); | |
| double time_elapsed = end_time - start_time; | |
| // --- 6. 結果の表示 --- | |
| // 最終進捗行をクリア | |
| fprintf(stderr, CLEAR_LINE); | |
| if (params.solution_found) { | |
| char final_hash_hex[TARGET_HEX_MAX_LEN]; | |
| bytes_to_hex(params.final_hash, final_hash_hex, HASH_SIZE); | |
| fprintf(stdout, "\n--- SUCCESS ---\n"); | |
| fprintf(stdout, "Nonce found: %lld\n", params.found_nonce); | |
| fprintf(stdout, "Final Hash: %s\n", final_hash_hex); | |
| fprintf(stdout, "Hashes tried: %lld\n", params.total_hashes_tried); | |
| fprintf(stdout, "Time elapsed: %.4f sec\n", time_elapsed); | |
| fprintf(stdout, "Hash rate: %.2f H/s\n", (double)params.total_hashes_tried / time_elapsed); | |
| } else { | |
| fprintf(stderr, "\n--- FAILED ---\n"); | |
| fprintf(stderr, "Search finished without finding a solution.\n"); | |
| } | |
| // --- 7. クリーンアップ --- | |
| cleanup: | |
| if (threads) free(threads); | |
| if (t_data) free(t_data); | |
| pthread_mutex_destroy(¶ms.mutex); | |
| return params.solution_found ? EXIT_SUCCESS : EXIT_FAILURE; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment