Post
Topic
Board Bitcoin Discussion
Re: Bitcoin puzzle transaction ~32 BTC prize to who solves it
by
user_not_here
on 25/04/2025, 01:15:50 UTC
Got bored and trying to waste time.
Created code to just scan range and convert addresses from it.
Speed is still fair?

Scanned: 8080480016 keys | Speed: 1611.89 Mkeys/s
Example privkey (hex): 000000000000000000000000000000000000000000000002000000003a3daf00
Compressed pubkey: 03957d109cb86e8b1316e1d27e5ecb347e654f13083a444c3c10c3ac7c834e7372
Uncompressed pubkey: 04957d109cb86e8b1316e1d27e5ecb347e654f13083a444c3c10c3ac7c834e7372a841548bce4a3 e38af176b5998944b017bb33695f0db559c15c7c02f7b515221
Compressed BTC Address: 1FkkYxzrE4CQNvk9PhSCsap1MnRpG8cRE
Uncompressed BTC Address: 17vHhKrRDEyZwBQLrgA4vo1XAikHEYtfvN

Code:
#include <iostream>
#include <iomanip>
#include <chrono>
#include <thread>
#include <vector>
#include <numeric>
#include <cstring>
#include <cstdint>
#include <algorithm> // added for std::find_if
#include <secp256k1.h>
#include <openssl/sha.h>
#include <openssl/ripemd.h>

// Define start and end key range in hex
constexpr const char* RANGE_START = "20000000000000000";
constexpr const char* RANGE_END   = "3FFFFFFFFFFFFFFFFF";
// Default thread count
constexpr unsigned int THREAD_COUNT = 8;
// Batch size per iteration
constexpr unsigned int BATCH_SIZE = 16; // deeper unrolling

// Parse a hex string into __uint128_t
static inline __uint128_t parseHex128(const char* hex) {
    __uint128_t v = 0;
    for (const char* p = hex; *p; ++p) {
        char c = *p;
        uint8_t nibble = 0;
        if (c >= '0' && c <= '9') nibble = c - '0';
        else if (c >= 'A' && c <= 'F') nibble = c - 'A' + 10;
        else if (c >= 'a' && c <= 'f') nibble = c - 'a' + 10;
        v = (v << 4) | nibble;
    }
    return v;
}

void uint128ToPrivKey(__uint128_t value, uint8_t out[32]) {
    memset(out, 0, 32);
    for (int i = 0; i < 16; ++i) {
        out[31 - i] = (uint8_t)(value >> (i * 8));
    }
}

bool derivePubkey(secp256k1_context* ctx, const uint8_t priv[32], uint8_t out[65], bool compressed) {
    secp256k1_pubkey pubkey;
    if (!secp256k1_ec_pubkey_create(ctx, &pubkey, priv)) return false;

    size_t len = compressed ? 33 : 65;
    return secp256k1_ec_pubkey_serialize(ctx, out, &len, &pubkey,
                                         compressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);
}

std::string toHex(const uint8_t* data, size_t len) {
    static const char hex_digits[] = "0123456789abcdef";
    std::string out;
    out.reserve(len * 2);
    for (size_t i = 0; i < len; ++i) {
        out.push_back(hex_digits[data[i] >> 4]);
        out.push_back(hex_digits[data[i] & 0xF]);
    }
    return out;
}

const char* BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

std::string base58Encode(const std::vector<uint8_t>& input) {
    std::vector<uint8_t> temp(input);
    std::string result;

    size_t zeroCount = 0;
    while (zeroCount < temp.size() && temp[zeroCount] == 0) {
        ++zeroCount;
    }

    std::vector<uint8_t> b58((temp.size() - zeroCount) * 138 / 100 + 1);
    size_t length = 0;

    for (size_t i = zeroCount; i < temp.size(); ++i) {
        int carry = temp[i];
        size_t j = 0;
        for (ssize_t k = b58.size() - 1; (carry != 0 || j < length) && k >= 0; --k, ++j) {
            carry += 256 * b58[k];
            b58[k] = carry % 58;
            carry /= 58;
        }
        length = j;
    }

    auto it = std::find_if(b58.begin(), b58.end(), [](uint8_t c) { return c != 0; });

    for (size_t i = 0; i < zeroCount; ++i) result += '1';
    for (; it != b58.end(); ++it) result += BASE58_ALPHABET[*it];

    return result;
}

std::string pubkeyToAddress(const uint8_t* pubkey, size_t len) {
    uint8_t sha256[SHA256_DIGEST_LENGTH];
    SHA256(pubkey, len, sha256);

    uint8_t ripemd[RIPEMD160_DIGEST_LENGTH];
    RIPEMD160(sha256, SHA256_DIGEST_LENGTH, ripemd);

    std::vector<uint8_t> addressBytes = {0x00};
    addressBytes.insert(addressBytes.end(), ripemd, ripemd + RIPEMD160_DIGEST_LENGTH);

    uint8_t hash1[SHA256_DIGEST_LENGTH];
    SHA256(addressBytes.data(), addressBytes.size(), hash1);

    uint8_t hash2[SHA256_DIGEST_LENGTH];
    SHA256(hash1, SHA256_DIGEST_LENGTH, hash2);

    addressBytes.insert(addressBytes.end(), hash2, hash2 + 4);

    return base58Encode(addressBytes);
}

int main() {
    secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);

    __uint128_t start = parseHex128(RANGE_START);
    __uint128_t end   = parseHex128(RANGE_END);
    __uint128_t total_range = end - start + 1;
    __uint128_t delta = total_range / THREAD_COUNT;

    alignas(64) uint64_t counters[THREAD_COUNT] = {0};
    alignas(64) __uint128_t lastScannedKey[THREAD_COUNT] = {0};

    auto tStart = std::chrono::steady_clock::now();

    auto worker = [&](int id, __uint128_t chunkStart, __uint128_t chunkEnd) {
        __uint128_t cur = chunkStart;
        uint64_t local = 0;
        while (cur <= chunkEnd) {
            // #pragma unroll 16
            for (unsigned i = 0; i < BATCH_SIZE && cur <= chunkEnd; ++i) {
                ++cur;
                ++local;
            }
            counters[id] += local;
            lastScannedKey[id] = cur;
            local = 0;
        }
    };

    std::vector<std::thread> pool;
    pool.reserve(THREAD_COUNT);
    for (unsigned i = 0; i < THREAD_COUNT; ++i) {
        __uint128_t chunkStart = start + delta * i;
        __uint128_t chunkEnd   = (i + 1 == THREAD_COUNT) ? end : (chunkStart + delta - 1);
        pool.emplace_back(worker, i, chunkStart, chunkEnd);
    }

    while (true) {
        std::this_thread::sleep_for(std::chrono::seconds(5));
        auto tNow = std::chrono::steady_clock::now();
        double elapsed = std::chrono::duration<double>(tNow - tStart).count();
        uint64_t total = std::accumulate(counters, counters + THREAD_COUNT, uint64_t(0));
        double speed = double(total) / elapsed / 1e6;

        uint8_t priv[32];
        uint8_t pubCompressed[33];
        uint8_t pubUncompressed[65];
        uint128ToPrivKey(lastScannedKey[0], priv);
        std::string hexPriv = toHex(priv, 32);
        std::string hexPubCompressed = "[invalid]";
        std::string hexPubUncompressed = "[invalid]";
        std::string addressCompressed = "[invalid]";
        std::string addressUncompressed = "[invalid]";

        if (derivePubkey(ctx, priv, pubCompressed, true)) {
            hexPubCompressed = toHex(pubCompressed, 33);
            addressCompressed = pubkeyToAddress(pubCompressed, 33);
        }

        if (derivePubkey(ctx, priv, pubUncompressed, false)) {
            hexPubUncompressed = toHex(pubUncompressed, 65);
            addressUncompressed = pubkeyToAddress(pubUncompressed, 65);
        }

        std::cout << "Scanned: " << total << " keys | Speed: "
                  << std::fixed << std::setprecision(2) << speed << " Mkeys/s\n"
                  << "Example privkey (hex): " << hexPriv << "\n"
                  << "Compressed pubkey: " << hexPubCompressed << "\n"
                  << "Uncompressed pubkey: " << hexPubUncompressed << "\n"
  << "Compressed BTC Address: " << addressCompressed << "\n"
                  << "Uncompressed BTC Address: " << addressUncompressed << "\n";
    }

    for (auto &t : pool) t.join();
    secp256k1_context_destroy(ctx);
    return 0;
}

When compare results against targets.txt speed gets fucked.
Have no idea how to improve it!

Loaded 3 target addresses.
Initial scan example (starting from RANGE_START):
Privkey: 0000000000000000000000000000000000000000000000020000000000000000
Compressed pubkey: 028d26200250cebdae120ef31b04c80cd50d4cddc8eadbcf29fc696d32c0ade462
Compressed BTC Address: 1LgpDjsqkxF9cTkz3UYSbdTJuvbZ45PKvx

MATCH FOUND (Compressed):
Privkey: 000000000000000000000000000000000000000000000002000000000000e690
Address: 1H3o2oVZApFkzp3DnoDjg8FCGXFtmBNBz5
[Speed] Scanned: 813040 keys | 0.15 Mkeys/s
[Speed] Scanned: 1559888 keys | 0.15 Mkeys/s
[Speed] Scanned: 2278496 keys | 0.15 Mkeys/s

Code:
#include <iostream>
#include <iomanip>
#include <chrono>
#include <thread>
#include <vector>
#include <numeric>
#include <cstring>
#include <cstdint>
#include <algorithm>
#include <unordered_set>
#include <fstream>
#include <mutex>
#include <secp256k1.h>
#include <openssl/sha.h>
#include <openssl/ripemd.h>

constexpr const char* RANGE_START = "20000000000000000";
constexpr const char* RANGE_END   = "3FFFFFFFFFFFFFFFFF";
constexpr unsigned int THREAD_COUNT = 8;
constexpr unsigned int BATCH_SIZE = 16;

std::mutex printMutex;

static inline __uint128_t parseHex128(const char* hex) {
    __uint128_t v = 0;
    for (const char* p = hex; *p; ++p) {
        char c = *p;
        uint8_t nibble = 0;
        if (c >= '0' && c <= '9') nibble = c - '0';
        else if (c >= 'A' && c <= 'F') nibble = c - 'A' + 10;
        else if (c >= 'a' && c <= 'f') nibble = c - 'a' + 10;
        v = (v << 4) | nibble;
    }
    return v;
}

void uint128ToPrivKey(__uint128_t value, uint8_t out[32]) {
    memset(out, 0, 32);
    for (int i = 0; i < 16; ++i) {
        out[31 - i] = (uint8_t)(value >> (i * 8));
    }
}

bool derivePubkey(secp256k1_context* ctx, const uint8_t priv[32], uint8_t out[65], bool compressed) {
    secp256k1_pubkey pubkey;
    if (!secp256k1_ec_pubkey_create(ctx, &pubkey, priv)) return false;

    size_t len = compressed ? 33 : 65;
    return secp256k1_ec_pubkey_serialize(ctx, out, &len, &pubkey,
                                         compressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);
}

std::string toHex(const uint8_t* data, size_t len) {
    static const char hex_digits[] = "0123456789abcdef";
    std::string out;
    out.reserve(len * 2);
    for (size_t i = 0; i < len; ++i) {
        out.push_back(hex_digits[data[i] >> 4]);
        out.push_back(hex_digits[data[i] & 0xF]);
    }
    return out;
}

const char* BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

std::string base58Encode(const std::vector<uint8_t>& input) {
    std::vector<uint8_t> temp(input);
    std::string result;

    size_t zeroCount = 0;
    while (zeroCount < temp.size() && temp[zeroCount] == 0) {
        ++zeroCount;
    }

    std::vector<uint8_t> b58((temp.size() - zeroCount) * 138 / 100 + 1);
    size_t length = 0;

    for (size_t i = zeroCount; i < temp.size(); ++i) {
        int carry = temp[i];
        size_t j = 0;
        for (ssize_t k = b58.size() - 1; (carry != 0 || j < length) && k >= 0; --k, ++j) {
            carry += 256 * b58[k];
            b58[k] = carry % 58;
            carry /= 58;
        }
        length = j;
    }

    auto it = std::find_if(b58.begin(), b58.end(), [](uint8_t c) { return c != 0; });

    for (size_t i = 0; i < zeroCount; ++i) result += '1';
    for (; it != b58.end(); ++it) result += BASE58_ALPHABET[*it];

    return result;
}

std::string pubkeyToAddress(const uint8_t* pubkey, size_t len) {
    uint8_t sha256[SHA256_DIGEST_LENGTH];
    SHA256(pubkey, len, sha256);

    uint8_t ripemd[RIPEMD160_DIGEST_LENGTH];
    RIPEMD160(sha256, SHA256_DIGEST_LENGTH, ripemd);

    std::vector<uint8_t> addressBytes = {0x00};
    addressBytes.insert(addressBytes.end(), ripemd, ripemd + RIPEMD160_DIGEST_LENGTH);

    uint8_t hash1[SHA256_DIGEST_LENGTH];
    SHA256(addressBytes.data(), addressBytes.size(), hash1);

    uint8_t hash2[SHA256_DIGEST_LENGTH];
    SHA256(hash1, SHA256_DIGEST_LENGTH, hash2);

    addressBytes.insert(addressBytes.end(), hash2, hash2 + 4);

    return base58Encode(addressBytes);
}

int main() {
    std::unordered_set<std::string> targetAddresses;
    std::ifstream infile("targets.txt");
    std::string line;
    while (std::getline(infile, line)) {
        if (!line.empty()) targetAddresses.insert(line);
    }
    std::cout << "Loaded " << targetAddresses.size() << " target addresses.\n";

    std::ofstream foundFile("found.txt", std::ios::app);
    secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);

    __uint128_t start = parseHex128(RANGE_START);
    __uint128_t end   = parseHex128(RANGE_END);
    __uint128_t total_range = end - start + 1;
    __uint128_t delta = total_range / THREAD_COUNT;

    alignas(64) uint64_t counters[THREAD_COUNT] = {0};
    alignas(64) __uint128_t lastScannedKey[THREAD_COUNT] = {0};

    auto tStart = std::chrono::steady_clock::now();

    auto worker = [&](int id, __uint128_t chunkStart, __uint128_t chunkEnd) {
        uint8_t priv[32], pubCompressed[33];
        __uint128_t cur = chunkStart;

        while (cur <= chunkEnd) {
            for (unsigned i = 0; i < BATCH_SIZE && cur <= chunkEnd; ++i, ++cur) {
                uint128ToPrivKey(cur, priv);
                std::string hexPriv = toHex(priv, 32);

                if (derivePubkey(ctx, priv, pubCompressed, true)) {
                    std::string addr = pubkeyToAddress(pubCompressed, 33);
                    if (targetAddresses.count(addr)) {
                        std::lock_guard<std::mutex> lock(printMutex);
                        std::cout << "\nMATCH FOUND (Compressed):\nPrivkey: " << hexPriv << "\nAddress: " << addr << "\n";
                        foundFile << "Compressed:\nPrivkey: " << hexPriv << "\nAddress: " << addr << "\n\n";
                        foundFile.flush();
                    }
                }
            }
            counters[id] += BATCH_SIZE;
            lastScannedKey[id] = cur;
        }
    };

    std::vector<std::thread> pool;
    pool.reserve(THREAD_COUNT);
    for (unsigned i = 0; i < THREAD_COUNT; ++i) {
        __uint128_t chunkStart = start + delta * i;
        __uint128_t chunkEnd   = (i + 1 == THREAD_COUNT) ? end : (chunkStart + delta - 1);
        pool.emplace_back(worker, i, chunkStart, chunkEnd);
    }

    // Show the true first key (from RANGE_START)
    __uint128_t firstKey = parseHex128(RANGE_START);
    uint8_t priv[32], pubCompressed[33];
    uint128ToPrivKey(firstKey, priv);
    std::string hexPriv = toHex(priv, 32);
    std::string hexPubCompressed = "[invalid]";
    std::string addressCompressed = "[invalid]";

    if (derivePubkey(ctx, priv, pubCompressed, true)) {
        hexPubCompressed = toHex(pubCompressed, 33);
        addressCompressed = pubkeyToAddress(pubCompressed, 33);
    }

    std::cout << "Initial scan example (starting from RANGE_START):\n"
              << "Privkey: " << hexPriv << "\n"
              << "Compressed pubkey: " << hexPubCompressed << "\n"
              << "Compressed BTC Address: " << addressCompressed << "\n";

    std::thread monitor([&]() {
        while (true) {
            std::this_thread::sleep_for(std::chrono::seconds(5));
            auto tNow = std::chrono::steady_clock::now();
            double elapsed = std::chrono::duration<double>(tNow - tStart).count();
            uint64_t total = std::accumulate(counters, counters + THREAD_COUNT, uint64_t(0));
            double speed = double(total) / elapsed / 1e6;

            std::lock_guard<std::mutex> lock(printMutex);
            std::cout << "[Speed] Scanned: " << total << " keys | "
                      << std::fixed << std::setprecision(2) << speed << " Mkeys/s\n";
        }
    });

    for (auto &t : pool) t.join();
    monitor.detach();
    secp256k1_context_destroy(ctx);
    return 0;
}