import hmac
import hashlib
import time
import math
import argparse
import sys
import multiprocessing
import itertools
from collections import defaultdict, Counter
from concurrent.futures import ProcessPoolExecutor, as_completed
import os

def unshiftRight(x, shift):
    res = x
    for i in range(32):
        res = x ^ res >> shift
    return res

def unshiftLeft(x, shift, mask):
    res = x
    for i in range(32):
        res = x ^ (res << shift & mask)
    return res

def untemper(v):
    v = unshiftRight(v, 18)
    v = unshiftLeft(v, 15, 0xefc60000)
    v = unshiftLeft(v, 7, 0x9d2c5680)
    v = unshiftRight(v, 11)
    return v

def setup_cuda_environment():
    base_path = r"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA"
    if not os.path.exists(base_path):
        return False
        
    versions = []
    try:
        for d in os.listdir(base_path):
            full_path = os.path.join(base_path, d)
            if os.path.isdir(full_path) and d.startswith("v"):
                versions.append((d, full_path))
    except:
        return False

    def sort_key(v):
        ver_str = v[0][1:]
        try:
            ver_num = float(ver_str)
            if 12 <= ver_num < 13: return ver_num + 100
            if 11 <= ver_num < 12: return ver_num + 50
            return ver_num
        except: return 0

    versions.sort(key=sort_key, reverse=True)
    
    for ver_name, ver_path in versions:
        bin_dir = os.path.join(ver_path, "bin")
        if os.path.exists(bin_dir):
            os.environ["CUDA_HOME"] = ver_path
            os.environ["CUDA_PATH"] = ver_path
            os.environ["PATH"] = bin_dir + os.pathsep + os.environ["PATH"]
            return True
            
    return False

setup_cuda_environment()
os.environ["NUMBA_ENABLE_CUDASIM"] = "0"


from numba import cuda, uint32, uint8, uint64
import numpy as np
import time
import math

# SHA-256 K constants
K = np.array([
    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
], dtype=np.uint32)

# MD5 T constants
T_MD5 = np.array([
    0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
    0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
    0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
    0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
    0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
    0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
    0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
    0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
], dtype=np.uint32)

@cuda.jit(device=True, inline=True)
def rotr(x, n):
    return uint32( (x >> n) | (x << (32 - n)) )

@cuda.jit(device=True, inline=True)
def ch(x, y, z):
    return uint32( (x & y) ^ ((~x) & z) )

@cuda.jit(device=True, inline=True)
def maj(x, y, z):
    return uint32( (x & y) ^ (x & z) ^ (y & z) )

@cuda.jit(device=True, inline=True)
def sigma0(x):
    return uint32( rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22) )

@cuda.jit(device=True, inline=True)
def sigma1(x):
    return uint32( rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25) )

@cuda.jit(device=True, inline=True)
def gamma0(x):
    return uint32( rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3) )

@cuda.jit(device=True, inline=True)
def gamma1(x):
    return uint32( rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10) )

@cuda.jit(device=True)
def sha256_transform(state, data, k_dev, w_glob, w_offset):
    for i in range(16):
        w_glob[w_offset + i] = uint32(data[i*4]) << 24 | uint32(data[i*4+1]) << 16 | \
                               uint32(data[i*4+2]) << 8 | uint32(data[i*4+3])
        
    for i in range(16, 64):
        term1 = (gamma1(w_glob[w_offset + i-2]) + w_glob[w_offset + i-7]) & 0xFFFFFFFF
        term2 = (gamma0(w_glob[w_offset + i-15]) + w_glob[w_offset + i-16]) & 0xFFFFFFFF
        w_glob[w_offset + i] = uint32( (term1 + term2) & 0xFFFFFFFF )
        
    a, b, c, d, e, f, g, h = state[0], state[1], state[2], state[3], state[4], state[5], state[6], state[7]
    
    for i in range(64):
        sum1 = (uint32(h) + sigma1(uint32(e))) & 0xFFFFFFFF
        sum2 = (ch(uint32(e), uint32(f), uint32(g)) + k_dev[i]) & 0xFFFFFFFF
        temp1 = (sum1 + sum2 + w_glob[w_offset + i]) & 0xFFFFFFFF
        
        sum3 = (sigma0(uint32(a)) + maj(uint32(a), uint32(b), uint32(c))) & 0xFFFFFFFF
        temp2 = sum3
        
        h = uint32(g)
        g = uint32(f)
        f = uint32(e)
        e = uint32( (d + temp1) & 0xFFFFFFFF )
        d = uint32(c)
        c = uint32(b)
        b = uint32(a)
        a = uint32( (temp1 + temp2) & 0xFFFFFFFF )
        
    state[0] = uint32( (state[0] + a) & 0xFFFFFFFF )
    state[1] = uint32( (state[1] + b) & 0xFFFFFFFF )
    state[2] = uint32( (state[2] + c) & 0xFFFFFFFF )
    state[3] = uint32( (state[3] + d) & 0xFFFFFFFF )
    state[4] = uint32( (state[4] + e) & 0xFFFFFFFF )
    state[5] = uint32( (state[5] + f) & 0xFFFFFFFF )
    state[6] = uint32( (state[6] + g) & 0xFFFFFFFF )
    state[7] = uint32( (state[7] + h) & 0xFFFFFFFF )

@cuda.jit(device=True, inline=True)
def rotl(x, n):
    return uint32( (x << n) | (x >> (32 - n)) )
    
@cuda.jit(device=True, inline=True)
def md5_F(x, y, z): return uint32((x & y) | ((~x) & z))

@cuda.jit(device=True, inline=True)
def md5_G(x, y, z): return uint32((x & z) | (y & (~z)))

@cuda.jit(device=True, inline=True)
def md5_H(x, y, z): return uint32(x ^ y ^ z)

@cuda.jit(device=True, inline=True)
def md5_I(x, y, z): return uint32(y ^ (x | (~z)))

@cuda.jit(device=True)
def md5_transform(state, data, t_dev, w_glob, w_offset):
    a, b, c, d = state[0], state[1], state[2], state[3]
    
    # decode little endian chunk
    for i in range(16):
        w_glob[w_offset + i] = uint32(data[i*4]) | (uint32(data[i*4+1]) << 8) | \
                               (uint32(data[i*4+2]) << 16) | (uint32(data[i*4+3]) << 24)
                               
    s = cuda.local.array(64, uint32)
    shift_vals = (7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
                  5,  9, 14, 20, 5,  9, 14, 20, 5,  9, 14, 20, 5,  9, 14, 20,
                  4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
                  6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21)
                  
    for i in range(64): s[i] = shift_vals[i]

    for i in range(64):
        if i < 16:
            f = md5_F(b, c, d)
            g = i
        elif i < 32:
            f = md5_G(b, c, d)
            g = (5*i + 1) % 16
        elif i < 48:
            f = md5_H(b, c, d)
            g = (3*i + 5) % 16
        else:
            f = md5_I(b, c, d)
            g = (7*i) % 16
            
        temp = d
        d = c
        c = b
        b = uint32(b + rotl(uint32(a + f + t_dev[i] + w_glob[w_offset + g]), s[i]))
        a = temp
        
    state[0] = uint32( (state[0] + a) & 0xFFFFFFFF )
    state[1] = uint32( (state[1] + b) & 0xFFFFFFFF )
    state[2] = uint32( (state[2] + c) & 0xFFFFFFFF )
    state[3] = uint32( (state[3] + d) & 0xFFFFFFFF )

@cuda.jit(device=True)
def hmac_sha256_stake_check(server_seed_len, server_seed, client_seed_len, client_seed_msg, target_outcome, k_dev, w_glob, w_offset):
    block = cuda.local.array(64, uint8)
    for i in range(64):
        b = 0x36
        if i < server_seed_len:
            b ^= server_seed[i]
        block[i] = b
        
    state = cuda.local.array(8, uint32)
    state[0], state[1], state[2], state[3] = 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a
    state[4], state[5], state[6], state[7] = 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
    
    sha256_transform(state, block, k_dev, w_glob, w_offset)
    
    for i in range(64): block[i] = 0
    for i in range(client_seed_len): block[i] = client_seed_msg[i]
    block[client_seed_len] = 0x80
    
    msg_len_bits = (64 + client_seed_len) * 8
    block[62] = uint8(msg_len_bits >> 8)
    block[63] = uint8(msg_len_bits)
    
    sha256_transform(state, block, k_dev, w_glob, w_offset)
    
    inner_hash = cuda.local.array(32, uint8)
    for i in range(8):
        val = state[i]
        inner_hash[i*4] = uint8(val >> 24)
        inner_hash[i*4+1] = uint8(val >> 16)
        inner_hash[i*4+2] = uint8(val >> 8)
        inner_hash[i*4+3] = uint8(val)

    for i in range(64):
        b = 0x5c
        if i < server_seed_len:
            b ^= server_seed[i]
        block[i] = b
        
    state[0], state[1], state[2], state[3] = 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a
    state[4], state[5], state[6], state[7] = 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
    
    sha256_transform(state, block, k_dev, w_glob, w_offset)
    
    for i in range(64): block[i] = 0
    for i in range(32): block[i] = inner_hash[i]
    block[32] = 0x80
    
    block[62] = 0x03
    block[63] = 0x00
    
    sha256_transform(state, block, k_dev, w_glob, w_offset)
    
    val = state[0]
    if val < uint32(4294967296 / 10001 * 10000):
        roll = (val % 10000) / 100.0
        # Manual absolute value to avoid issues within cuda device functions
        diff = roll - target_outcome
        if diff < 0: diff = -diff
        if diff < 0.001:
            return True
            
    return False

@cuda.jit
def brute_force_kernel_beatstake(k_dev, client_msg_base_dev, client_msg_base_len, target_outcomes, num_targets, result_found, result_len, result_seed_idx, current_len, alphabet_dev, offset_idx, workspace_dev, prefix_dev, prefix_len, start_nonce):
    idx = cuda.grid(1)
    global_idx = idx + offset_idx
    w_offset = idx * 64
    
    server_seed = cuda.local.array(64, uint8)
    temp_idx = global_idx
    
    total_len = prefix_len + current_len
    
    for i in range(prefix_len):
        server_seed[i] = prefix_dev[i]
        
    for i in range(current_len):
        rem = temp_idx % 16
        server_seed[total_len - 1 - i] = alphabet_dev[rem]
        temp_idx //= 16
        
    # Check sequences by swapping nonce injected byte dynamically
    msg_buffer = cuda.local.array(64, uint8)
    for i in range(client_msg_base_len):
        msg_buffer[i] = client_msg_base_dev[i]
        
    all_matched = True
    nonce_str_buf = cuda.local.array(20, uint8)
    for t_idx in range(num_targets):
        target = target_outcomes[t_idx]
        target_nonce = start_nonce + t_idx
        
        msg_len = client_msg_base_len
        nonce_len = uint64_to_string(target_nonce, nonce_str_buf)
        for i in range(nonce_len):
            msg_buffer[msg_len] = nonce_str_buf[i]
            msg_len += 1
            
        msg_buffer[msg_len] = 58 # ':'
        msg_len += 1
        msg_buffer[msg_len] = 48 # '0'
        msg_len += 1
        
        if not hmac_sha256_stake_check(total_len, server_seed, msg_len, msg_buffer, target, k_dev, workspace_dev, w_offset):
            all_matched = False
            break
            
    if all_matched:
        if result_found[0] == 0:
            result_found[0] = 1
            result_len[0] = total_len
            result_seed_idx[0] = global_idx

@cuda.jit(device=True)
def uint64_to_string(num, out_buffer):
    """ Converts a uint64 number to byte string backwards, returns length """
    if num == 0:
        out_buffer[0] = 48
        return 1
    
    length = 0
    temp = num
    while temp > 0:
        length += 1
        temp //= 10
        
    for i in range(length):
        out_buffer[length - 1 - i] = 48 + (num % 10)
        num //= 10
        
    return length

@cuda.jit(device=True)
def float64_to_string_3_decimals(num, out_buffer):
    # e.g 167890.123 -> "167890.123"
    int_part = uint64(num)
    length = uint64_to_string(int_part, out_buffer)
    
    out_buffer[length] = 46 # '.'
    length += 1
    
    frac = uint32((num - int_part) * 1000)
    out_buffer[length] = 48 + ((frac // 100) % 10)
    length += 1
    out_buffer[length] = 48 + ((frac // 10) % 10)
    length += 1
    out_buffer[length] = 48 + (frac % 10)
    length += 1
    
    return length

@cuda.jit(device=True)
def bytes_to_hex_string(buffer, in_len, out_buffer):
    hex_map = (48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102)
    for i in range(in_len):
        b = buffer[i]
        out_buffer[i*2] = hex_map[b >> 4]
        out_buffer[i*2 + 1] = hex_map[b & 0x0f]
    return in_len * 2

@cuda.jit
def brute_force_kernel_time(k_dev, t_md5_dev, client_msg_base_dev, client_msg_base_len, target_outcomes, num_targets, result_found, result_type, result_ts, base_ts_ms, workspace_dev, start_nonce):
    idx = cuda.grid(1)
    # Each thread checks a unique millisecond timestamp
    current_ts = base_ts_ms + idx
    w_offset = idx * 64
    
    ts_str = cuda.local.array(20, uint8)
    ts_len = uint64_to_string(current_ts, ts_str)
    
    # We will test 4 string representation types:
    # 0 = Raw Integer String
    # 1 = Raw Float String (e.g. 16700000.123)
    # 2 = MD5 Hash String
    # 3 = SHA256 Hash String
    
    for type_idx in range(4):
        test_seed = cuda.local.array(64, uint8)
        test_len = 0
        
        if type_idx == 0:
            for i in range(ts_len): test_seed[i] = ts_str[i]
            test_len = ts_len
            
        elif type_idx == 1:
            # Fake the float string format dividing by 1000
            test_len = float64_to_string_3_decimals(current_ts / 1000.0, test_seed)
            
        elif type_idx == 2:
            # MD5 hash of the raw integer string
            md5_block = cuda.local.array(64, uint8)
            for i in range(64): md5_block[i] = 0
            for i in range(ts_len): md5_block[i] = ts_str[i]
            md5_block[ts_len] = 0x80
            md5_block[56] = uint8((ts_len * 8) & 0xFF)
            
            md5_state = cuda.local.array(4, uint32)
            md5_state[0], md5_state[1], md5_state[2], md5_state[3] = 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476
            md5_transform(md5_state, md5_block, t_md5_dev, workspace_dev, w_offset)
            
            # Format to bytes
            inner_hash = cuda.local.array(16, uint8)
            for i in range(4):
                val = md5_state[i]
                inner_hash[i*4] = uint8(val & 0xFF)
                inner_hash[i*4+1] = uint8((val >> 8) & 0xFF)
                inner_hash[i*4+2] = uint8((val >> 16) & 0xFF)
                inner_hash[i*4+3] = uint8(val >> 24)
                
            test_len = bytes_to_hex_string(inner_hash, 16, test_seed)
            
        elif type_idx == 3:
            # SHA256 of the raw integer string
            sha_block = cuda.local.array(64, uint8)
            for i in range(64): sha_block[i] = 0
            for i in range(ts_len): sha_block[i] = ts_str[i]
            sha_block[ts_len] = 0x80
            msg_bits = ts_len * 8
            sha_block[62] = uint8(msg_bits >> 8)
            sha_block[63] = uint8(msg_bits & 0xFF)
            
            sha_state = cuda.local.array(8, uint32)
            sha_state[0], sha_state[1], sha_state[2], sha_state[3] = 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a
            sha_state[4], sha_state[5], sha_state[6], sha_state[7] = 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
            sha256_transform(sha_state, sha_block, k_dev, workspace_dev, w_offset)
            
            inner_hash = cuda.local.array(32, uint8)
            for i in range(8):
                val = sha_state[i]
                inner_hash[i*4] = uint8(val >> 24)
                inner_hash[i*4+1] = uint8(val >> 16)
                inner_hash[i*4+2] = uint8(val >> 8)
                inner_hash[i*4+3] = uint8(val)
                
            test_len = bytes_to_hex_string(inner_hash, 32, test_seed)

        # Check this specific string variation against the target matrix
        msg_buffer = cuda.local.array(64, uint8)
        for i in range(client_msg_base_len):
            msg_buffer[i] = client_msg_base_dev[i]
            
        all_matched = True
        nonce_str_buf = cuda.local.array(20, uint8)
        for t_idx in range(num_targets):
            target = target_outcomes[t_idx]
            target_nonce = start_nonce + t_idx
            
            msg_len = client_msg_base_len
            nonce_len = uint64_to_string(target_nonce, nonce_str_buf)
            for i in range(nonce_len):
                msg_buffer[msg_len] = nonce_str_buf[i]
                msg_len += 1
                
            msg_buffer[msg_len] = 58 # ':'
            msg_len += 1
            msg_buffer[msg_len] = 48 # '0'
            msg_len += 1
            
            if not hmac_sha256_stake_check(test_len, test_seed, msg_len, msg_buffer, target, k_dev, workspace_dev, w_offset):
                all_matched = False
                break
                
        if all_matched:
            if result_found[0] == 0:
                result_found[0] = 1
                result_type[0] = type_idx
                result_ts[0] = current_ts

def verify_server_seed(unhashed_seed: str, public_hash: str) -> bool:
    """
    Validates if the discovered raw string matches the 
    commitment hash provided by the platform.
    """
    derived_hash = hashlib.sha256(unhashed_seed.encode()).hexdigest()
    if derived_hash == public_hash:
        print(f"\n[+] VALIDATION SUCCESS: Seed '{unhashed_seed}' matches known hash!")
        return True
    else:
        print(f"\n[-] VALIDATION FAILED: Derived {derived_hash} != {public_hash}")
        return False

# --- CORE RNG ENGINE ---
class StakeRNG:
    def __init__(self, server_seed: str, client_seed: str):
        self.server_seed = server_seed
        self.client_seed = client_seed

    @staticmethod
    def get_next_seed_in_chain(current_seed: str) -> str:
        """
        In many systems, the 'Next' game's seed is simply 
        the SHA-256 hash of the 'Current' seed.
        """
        return hashlib.sha256(current_seed.encode()).hexdigest()

    def _generate_hmac_sha256(self, nonce: int, cursor: int = 0) -> str:
        message = f"{self.client_seed}:{nonce}:{cursor}"
        return hmac.new(
            self.server_seed.encode('utf-8'),
            message.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()

    def get_dice_roll(self, nonce: int) -> float:
        hash_hex = self._generate_hmac_sha256(nonce)
        for i in range(0, len(hash_hex), 8):
            chunk = hash_hex[i:i+8]
            val = int(chunk, 16)
            if val < 4294967296 / 10001 * 10000:
                return (val % 10000) / 100.0
        return 0.0

# --- ATTACK VECTOR 1: MASSIVE GPU TIME CRACKING ---
def aggressive_time_crack(client_seed: str, target_timestamp_ms: int, target_outcomes: list, start_nonce: int = 1, variance_sec=10):
    print(f"\n[!] INITIATING BATTLE-HARDENED GPU TIME CRACK")
    print(f"[*] Target Epoch: {target_timestamp_ms} | Variance: +/- {variance_sec}s")
    print(f"[*] Target Outcome Chain: {target_outcomes}")
    print(f"[*] Compiling massively parallel CUDA execution grid...")
    
    if not cuda.is_available():
        print("[-] CUDA not available. Cannot run GPU time crack.")
        return None
        
    start_time = time.time()
    
    # Setup Device Inputs
    msg_base_str = f"{client_seed}:"
    msg_base_bytes = msg_base_str.encode('utf-8')
    client_msg_base_len = len(msg_base_bytes)
    
    if client_msg_base_len > 50:
        print("[-] Warning: Client base message too long for single block HMAC optimization.")
        return None
        
    client_msg_base_dev = cuda.to_device(np.frombuffer(msg_base_bytes, dtype=np.uint8))
    target_outcomes_dev = cuda.to_device(np.array(target_outcomes, dtype=np.float64))
    num_targets = len(target_outcomes)
    
    k_dev = cuda.to_device(K)
    t_md5_dev = cuda.to_device(T_MD5)
    
    # Setup Returns
    result_found_host = np.zeros(1, dtype=np.int32)
    result_type_host = np.zeros(1, dtype=np.int32)
    result_ts_host = np.zeros(1, dtype=np.uint64)
    
    result_found = cuda.to_device(result_found_host)
    result_type = cuda.to_device(result_type_host)
    result_ts = cuda.to_device(result_ts_host)
    
    # Setup Grid
    variance_ms = variance_sec * 1000
    start_ts = np.uint64(target_timestamp_ms - variance_ms)
    total_threads = variance_ms * 2
    
    threads_per_block = 128
    blocks = (total_threads + threads_per_block - 1) // threads_per_block
    
    chunk_size = threads_per_block * blocks
    workspace_size = chunk_size * 64
    workspace_dev = cuda.device_array(workspace_size, dtype=np.uint32)
    
    print(f"    -> Sweeping {total_threads:,} unique epochs across 4 format variations ({total_threads * 4:,} total permutations)")
    
    stream = cuda.stream()
    brute_force_kernel_time[blocks, threads_per_block, stream](
        k_dev, t_md5_dev, client_msg_base_dev, client_msg_base_len, target_outcomes_dev, num_targets,
        result_found, result_type, result_ts, start_ts, workspace_dev, start_nonce
    )
    
    result_found.copy_to_host(result_found_host, stream=stream)
    stream.synchronize()
    
    elapsed = time.time() - start_time
    
    if result_found_host[0] == 1:
        result_type.copy_to_host(result_type_host, stream=stream)
        result_ts.copy_to_host(result_ts_host, stream=stream)
        stream.synchronize()
        
        found_ts = int(result_ts_host[0])
        type_idx = int(result_type_host[0])
        
        final_seed = ""
        formats = ["RAW INTEGER", "RAW FLOAT", "MD5 HASH", "SHA256 HASH"]
        f_type = formats[type_idx]
        
        if type_idx == 0:
            final_seed = str(found_ts)
        elif type_idx == 1:
            final_seed = f"{found_ts / 1000.0:.3f}"
        elif type_idx == 2:
            final_seed = hashlib.md5(str(found_ts).encode()).hexdigest()
        elif type_idx == 3:
            final_seed = hashlib.sha256(str(found_ts).encode()).hexdigest()
            
        print(f"\n[+] GPU TIME CRACK SUCCESSFUL IN {elapsed:.2f}s!")
        print(f"[+] Discovered Seed: '{final_seed}' (Format: {f_type} | Timestamp: {found_ts})")
        return final_seed
    else:
        print(f"\n[-] Exhausted all {total_threads:,} permutations in {elapsed:.2f}s without finding a match.")
        return None

# --- ATTACK VECTOR 2: N-GRAM MARKOV SEQUENCE PREDICTION ---
def analyze_n_gram_transitions(history: list, n_gram_size: int = 3, bucket_size: int = 10):
    """
    Advanced Sequence Prediction. Standard probabilities assume independence.
    This builds an N-Gram Markov state transition matrix to find hidden algorithmic biases.
    """
    print(f"\n[!] INITIATING {n_gram_size}-GRAM MARKOV SEQUENCE DISSECTION")
    if len(history) < 500:
        print("[!] Need large dataset (>500 rolls) for accurate battle-hardened tracking.")
        return
        
    def to_bucket(val):
        return int(val // bucket_size) * bucket_size
        
    buckets = [to_bucket(x) for x in history]
    transitions = defaultdict(Counter)
    
    # Build transition matrix for sequences of length N
    for i in range(len(buckets) - n_gram_size):
        state = tuple(buckets[i : i+n_gram_size])
        next_state = buckets[i+n_gram_size]
        transitions[state][next_state] += 1
        
    current_state = tuple(buckets[-n_gram_size:])
    print(f"[*] Current Historical N-Gram State Context: {current_state}")
    
    if current_state in transitions and transitions[current_state]:
        total = sum(transitions[current_state].values())
        print(f"[+] Prediction Vector Generated (Sample Size: {total} occurrences)")
        
        for next_val, count in transitions[current_state].most_common(5):
            prob = (count / total) * 100
            print(f"    -> PREDICTED RANGE: {next_val:02d}.00 - {next_val+bucket_size - 1:02d}.99 | Certainty: {prob:.2f}%")
    else:
        print("[-] Insufficient historical density for this specific N-Gram sequence.")

# --- ATTACK VECTOR 3: MULTI-CORE COLLISION FARMING ---
def aggressive_collision_farm(client_seed: str, target_outcomes: list, start_nonce: int = 1, target_length: int = 64, public_hash: str = None):
    import random
    print(f"\n[!] INITIATING BATTLE-HARDENED GPU SEQUENCE COLLISION FARM")
    print(f"[*] Target Outcome Chain: {target_outcomes} mapped cleanly to ascending Nonces")
    if public_hash:
        print(f"[*] Target Public Hash: {public_hash} (Will verify strict match upon collision)")
    print(f"[*] combining massive random prefixes and sequential CUDA grids to blast state space...")
    
    if not cuda.is_available():
        print("[-] CUDA not available. Executing CPU Multiprocessing fallback...")
        return cpu_collision_farm(client_seed, target_outcomes, start_nonce, target_length, public_hash)
        
    start_time = time.time()
    alphabet_str = "0123456789abcdef"
    alphabet_dev = cuda.to_device(np.frombuffer(alphabet_str.encode('ascii'), dtype=np.uint8))
    
    msg_base_str = f"{client_seed}:"
    msg_base_bytes = msg_base_str.encode('utf-8')
    client_msg_base_len = len(msg_base_bytes)
    
    if client_msg_base_len > 50:
        print("[-] Warning: Client base message too long for single block HMAC optimization.")
        return None
        
    client_msg_base_dev = cuda.to_device(np.frombuffer(msg_base_bytes, dtype=np.uint8))
    target_outcomes_dev = cuda.to_device(np.array(target_outcomes, dtype=np.float64))
    num_targets = len(target_outcomes)
    
    k_dev = cuda.to_device(K)
    
    result_found_host = np.zeros(128, dtype=np.int32)
    result_len_host = np.zeros(1, dtype=np.int32)
    result_seed_idx_host = np.zeros(1, dtype=np.uint64)
    
    result_found = cuda.to_device(result_found_host)
    result_len = cuda.to_device(result_len_host)
    result_seed_idx = cuda.to_device(result_seed_idx_host)
    
    suffix_len = 6 # Sweeping 16^6 (16.7 million states) per random prefix block
    if target_length < suffix_len:
        suffix_len = target_length
    prefix_len = target_length - suffix_len
    
    total_perms = 16 ** suffix_len
    threads_per_block = 128
    chunk_size = 1024 * 512
    if chunk_size % threads_per_block != 0:
         chunk_size += threads_per_block - (chunk_size % threads_per_block)
         
    workspace_size = chunk_size * 64
    workspace_dev = cuda.device_array(workspace_size, dtype=np.uint32)
    
    attempt = 1
    while True:
        prefix_str = "".join(random.choices(alphabet_str, k=prefix_len)) if prefix_len > 0 else ""
        prefix_bytes = prefix_str.encode('ascii')
        prefix_dev = cuda.to_device(np.frombuffer(prefix_bytes, dtype=np.uint8))
        
        print(f"\n[*] Iteration {attempt:,} | Using Random Prefix: '{prefix_str}'...")
        stream = cuda.stream()
        
        for offset in range(0, total_perms, chunk_size):
            if offset % (chunk_size * 20) == 0 and offset > 0:
                print(f"    -> Swept {offset:,} / {total_perms:,} suffixes...")
                sys.stdout.flush()
                
            current_batch = min(chunk_size, total_perms - offset)
            blocks = (current_batch + threads_per_block - 1) // threads_per_block
            
            brute_force_kernel_beatstake[blocks, threads_per_block, stream](
                k_dev, client_msg_base_dev, client_msg_base_len, target_outcomes_dev, num_targets,
                result_found, result_len, result_seed_idx,
                suffix_len, alphabet_dev, offset, workspace_dev, prefix_dev, prefix_len, start_nonce
            )
            
            result_found.copy_to_host(result_found_host, stream=stream)
            stream.synchronize()
            
            if result_found_host[0] == 1:
                result_seed_idx.copy_to_host(result_seed_idx_host, stream=stream)
                stream.synchronize()
                seed_idx = int(result_seed_idx_host[0])
                seed_str = ""
                temp = seed_idx
                for i in range(suffix_len):
                    seed_str = alphabet_str[temp % 16] + seed_str
                    temp //= 16
                
                final_seed = prefix_str + seed_str
                elapsed = time.time() - start_time
                print(f"\n[+] GPU COLLISION PREDICTION CHAIN {num_targets}x SECURED IN {elapsed:.2f}s!")
                print(f"[+] Multi-Nonce Sequential Ready Seed: '{final_seed}' perfectly matches historical string {target_outcomes}")
                
                if public_hash:
                    verify_server_seed(final_seed, public_hash)
                    
                return final_seed
                
        attempt += 1

def worker_cpu_collision(args):
    prefix_str, suffix_range, alphabet_str, client_seed, num_targets, target_outcomes, start_nonce = args
    for offset in suffix_range:
        temp = offset
        seed_str = ""
        for i in range(6): 
            seed_str = alphabet_str[temp % 16] + seed_str
            temp //= 16
        
        test_seed = prefix_str + seed_str
        rng = StakeRNG(test_seed, client_seed)
        
        all_matched = True
        for t_idx, target in enumerate(target_outcomes):
            roll = rng.get_dice_roll(start_nonce + t_idx)
            if abs(roll - target) >= 0.001:
                all_matched = False
                break
                
        if all_matched:
            return test_seed
            
    return None

def cpu_collision_farm(client_seed: str, target_outcomes: list, start_nonce: int = 1, target_length: int = 64, public_hash: str = None):
    import random
    from concurrent.futures import ProcessPoolExecutor
    
    alphabet_str = "0123456789abcdef"
    suffix_len = 6 
    prefix_len = target_length - suffix_len
    total_perms = 16 ** suffix_len
    
    cores = multiprocessing.cpu_count()
    print(f"[*] Firing up {cores} CPU cores to process {total_perms:,} states per batch...")
    
    attempt = 1
    chunk_size = total_perms // cores
    
    start_time = time.time()
    
    while True:
        prefix_str = "".join(random.choices(alphabet_str, k=prefix_len))
        print(f"\n[*] CPU Iteration {attempt:,} | Using Random Prefix: '{prefix_str}'...")
        sys.stdout.flush()
        
        work_chunks = []
        for i in range(cores):
            start_off = i * chunk_size
            end_off = (i + 1) * chunk_size if i < cores - 1 else total_perms
            work_chunks.append((prefix_str, range(start_off, end_off), alphabet_str, client_seed, len(target_outcomes), target_outcomes, start_nonce))
            
        with ProcessPoolExecutor(max_workers=cores) as executor:
            for result in executor.map(worker_cpu_collision, work_chunks):
                if result:
                    executor.shutdown(wait=False, cancel_futures=True)
                    elapsed = time.time() - start_time
                    print(f"\n[+] CPU COLLISION MATCH {len(target_outcomes)}x SECURED IN {elapsed:.2f}s!")
                    print(f"[+] Multi-Nonce Sequential Ready Seed: '{result}' matches historical strings perfectly")
                    if public_hash:
                        verify_server_seed(result, public_hash)
                    return result
                    
        attempt += 1

# --- ATTACK VECTOR 6: GHOST MINE (CURSOR FARMING) ---
def build_mines_board(server_seed: str, client_seed: str, nonce: int, total_mines: int) -> list:
    """
    Simulates Stake's Javascript `byteGenerator` logic to map an HMAC-SHA256 
    cursor accurately over a 25-tile grid for the Mines game.
    """
    tiles_remaining = list(range(25)) # 0 to 24 representing the grid
    mines_placed = []
    
    cursor = 0
    current_round = 0
    while len(mines_placed) < total_mines:
        message = f"{client_seed}:{nonce}:{current_round}"
        hash_hex = hmac.new(
            server_seed.encode('utf-8'),
            message.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        
        # We parse the hash in chunks of 4 bytes (8 hex chars)
        for i in range(0, len(hash_hex), 8):
            if len(mines_placed) >= total_mines:
                break
                
            chunk = hash_hex[i:i+8]
            val = int(chunk, 16)
            
            # The exact math used by the casino
            float_val = val / (2**32) 
            
            # Multiply by remaining tiles
            index = int(float_val * len(tiles_remaining))
            
            mine_pos = tiles_remaining.pop(index)
            mines_placed.append(mine_pos)
            
            cursor += 1
            
        current_round += 1
        
    return mines_placed

def xray_mines_board(unhashed_seed: str, client_seed: str, nonce: int, total_mines: int = 3):
    print(f"\n[!] INITIATING BATTLE-HARDENED X-RAY CURSOR FARM")
    print(f"[*] Target Active Server Seed: {unhashed_seed}")
    print(f"[*] Target Active Nonce: {nonce}  | Target Mines: {total_mines}")
    print(f"[*] Reconstructing HMAC-SHA256 cursor generation mechanics...\n")
    
    mines_placed = build_mines_board(unhashed_seed, client_seed, nonce, total_mines)
    
    print(f"[+] Exact Mathematical Grid Generated!")
    print(f"[+] The {total_mines} mines are hidden at array indices: {sorted(mines_placed)}")
    
    print("\n   [ 0] [ 1] [ 2] [ 3] [ 4]")
    print("   [ 5] [ 6] [ 7] [ 8] [ 9]")
    print("   [10] [11] [12] [13] [14]")
    print("   [15] [16] [17] [18] [19]")
    print("   [20] [21] [22] [23] [24]")
    
    print("\n   --- LIVE GAME BOARD ---")
    board = ""
    for row in range(5):
        row_str = "   "
        for col in range(5):
            idx = (row * 5) + col
            if idx in mines_placed:
                row_str += "[ X] " # Mine
            else:
                row_str += "[ O] " # Safe Gem
        board += row_str + "\n"
        
    print(board)
    print("   -----------------------")
    print(f"   [X] = MINE  |  [O] = SAFE TO CLICK")
    print(f"   Click all 🟢 Safe tiles and CASH OUT.\n")

# --- ATTACK VECTOR 5: PRNG (MT19937) STATE RECOVERY ---
def analyze_prng_state(observed_outcomes: list, predict_depth: int = 10):
    import random
    print(f"\n[!] INITIATING MT19937 PRNG STATE RECOVERY")
    
    if len(observed_outcomes) < 624:
        print("[-] Need exactly 624 consecutive observed outcomes to recover state.")
        print(f"[-] You provided {len(observed_outcomes)}.")
        return

    observed = observed_outcomes[:624]
    
    try:
        print("[*] Untempering 624 observed outcomes to reconstruct PRNG internal state...")
        scaled_outcomes = [min(x / 100.0, 0.999999999) for x in observed]
        untempered_outputs = [untemper(int(x * (2**32))) for x in scaled_outcomes]
        
        recovered_rng_state = (3, tuple(untempered_outputs + [624]), None)
        
        predicted_rng = random.Random()
        predicted_rng.setstate(recovered_rng_state)
        
        print(f"[+] PRNG State Successfully Recovered! Generating next {predict_depth} predictions...\n")
        print(f"{'ROLL #':<8} | {'PREDICTED OUTCOME':<20}")
        print("-" * 31)
        
        for i in range(1, predict_depth + 1):
            next_val = predicted_rng.random() * 100
            print(f"+{i:<7} | {next_val:.2f}")
            
    except Exception as e:
        print(f"[-] PRNG state recovery failed: {e}")

# --- ATTACK VECTOR 4: UNHASHED PREDICTION SEQUENCE ---
def predict_sequence(client_seed: str, unhashed_seed: str, start_nonce: int = 1, depth: int = 10):
    print(f"\n[!] INITIATING BATTLE-HARDENED DETERMINISM PREDICTION")
    print(f"[*] Base Discovered Seed: {unhashed_seed}")
    print(f"[*] Client Seed: {client_seed}")
    print(f"[*] Forecasting the precise outcome of the next {depth} rounds...\n")
    
    current_seed = unhashed_seed
    print(f"{'ROUND':<10} | {'PREDICTED SERVER SEED HASH (SHOWN TO PUBLIC)':<68} | {'EXACT ROLL'}")
    print("-" * 95)
    
    for round_num in range(1, depth + 1):
        # The public commitment is the hash of the current seed
        seed_hash = hashlib.sha256(current_seed.encode()).hexdigest()
        
        # Calculate the roll for this valid game state using the starting nonce
        rng = StakeRNG(seed_hash, client_seed)
        roll = rng.get_dice_roll(start_nonce)
        
        # Format and output the prediction row
        print(f"Game +{round_num:<4} | {seed_hash:<68} | {roll:.2f}")
        
        # Advance the determinism chain mathematically 
        current_seed = StakeRNG.get_next_seed_in_chain(current_seed)
        
    print(f"\n[+] Prediction Complete. Weaponized foresight fully generated.")

def main():
    parser = argparse.ArgumentParser(description="BeatStake - Real World Provably Fair Logic Defeater")
    parser.add_argument("--client", type=str, required=True, help="Your active client seed")
    parser.add_argument("--attack", choices=["time", "markov", "collision", "predict", "prng", "mines"], required=True, help="Attack vector to execute")
    parser.add_argument("--start-nonce", type=int, default=1, help="Starting nonce for the outcome chain")
    
    # Args for Time crack
    parser.add_argument("--target-ts", type=int, help="Target Unix epoch for time attack")
    parser.add_argument("--observed", type=str, help="Comma separated list of observed outcomes (e.g., 42.11,88.92)")
    
    # Args for Markov
    parser.add_argument("--history", type=int, default=5000, help="Simulate N historical hits for Markov matrix")
    
    # Args for Collision
    parser.add_argument("--target-rolls", type=str, help="Comma separated list of sequential exact dice rolls to farm a collision prediction seed for (e.g. 50.00,42.11)")
    parser.add_argument("--public-hash", type=str, help="Public hash commitment to automatically verify against if farming a collision")
    
    # Args for Prediction / Mines
    parser.add_argument("--unhashed-seed", type=str, help="The raw unhashed server seed discovered by the collision farm (or time crack)")
    parser.add_argument("--nonce", type=int, help="The exact current nonce to attack (for mines cursor farm)")
    parser.add_argument("--predict-depth", type=int, default=10, help="Number of future games to predict in sequence")
    parser.add_argument("--mines", type=int, default=3, help="Number of mines on the active game board")
    
    parser.add_argument("--cores", type=int, default=multiprocessing.cpu_count(), help="CPU Cores to utilize for parallel processing")

    args = parser.parse_args()
    
    print("""
     lets go
    """)

    if args.attack == "time":
        if not args.target_ts or not args.observed:
            print("[!] time attack requires --target-ts and --observed")
            sys.exit(1)
            
        observed_list = [float(x.strip()) for x in args.observed.split(',')]
        aggressive_time_crack(args.client, args.target_ts, observed_list, start_nonce=args.start_nonce, variance_sec=60)
        
    elif args.attack == "markov":
        print(f"[*] Generating synthetic real-world history of {args.history} rounds for sequence profiling...")
        # We need a hidden seed to act as a target
        hidden_seed = hashlib.sha256(b"casino_server_secret").hexdigest()
        rng = StakeRNG(hidden_seed, args.client)
        
        history = [rng.get_dice_roll(i) for i in range(1, args.history + 1)]
        analyze_n_gram_transitions(history, n_gram_size=4, bucket_size=10)
        
    elif args.attack == "collision":
        if not args.target_rolls:
            print("[!] collision attack requires --target-rolls")
            sys.exit(1)
            
        target_list = [float(x.strip()) for x in args.target_rolls.split(',')]
        aggressive_collision_farm(args.client, target_list, start_nonce=args.start_nonce, target_length=64, public_hash=args.public_hash)

    elif args.attack == "predict":
        if not args.unhashed_seed:
            print("[!] predict attack requires --unhashed-seed")
            sys.exit(1)
            
        predict_sequence(args.client, args.unhashed_seed, start_nonce=args.start_nonce, depth=args.predict_depth)

    elif args.attack == "prng":
        if not args.observed:
            print("[!] prng attack requires --observed (comma separated list of 624+ outcomes)")
            sys.exit(1)
            
        observed_list = [float(x.strip()) for x in args.observed.split(',')]
        analyze_prng_state(observed_list, predict_depth=args.predict_depth)

    elif args.attack == "mines":
        if not args.unhashed_seed or not args.nonce:
            print("[!] mines attack requires --unhashed-seed and --nonce")
            sys.exit(1)
            
        xray_mines_board(args.unhashed_seed, args.client, args.nonce, args.mines)

if __name__ == "__main__":
    # Prevent multiprocessing errors on Windows
    multiprocessing.freeze_support()
    main()