From 745aee049d093e2c563c5a189e7331a9580c4549 Mon Sep 17 00:00:00 2001 From: BuilderFred Date: Mon, 9 Feb 2026 14:10:33 +0700 Subject: [PATCH] feat: implement Windows .exe installer source with tray app and build instructions --- installers/windows/README.md | 51 +++ installers/windows/build_windows.py | 34 ++ installers/windows/src/installer.nsi | 91 ++++++ installers/windows/src/tray_app.py | 161 ++++++++++ tools/fingerprint_checks.py | 450 +++++++++++++++++++++++++++ tools/fingerprint_results.json | 87 ++++++ tools/test_output.txt | 40 +++ 7 files changed, 914 insertions(+) create mode 100644 installers/windows/README.md create mode 100644 installers/windows/build_windows.py create mode 100644 installers/windows/src/installer.nsi create mode 100644 installers/windows/src/tray_app.py create mode 100644 tools/fingerprint_checks.py create mode 100644 tools/fingerprint_results.json create mode 100644 tools/test_output.txt diff --git a/installers/windows/README.md b/installers/windows/README.md new file mode 100644 index 0000000..bf4b2d5 --- /dev/null +++ b/installers/windows/README.md @@ -0,0 +1,51 @@ +# RustChain Windows Installer Source + +This repository contains the source code for the RustChain Proof-of-Antiquity Miner Windows Installer (.exe). + +## Features +- **Bundled Python**: Uses PyInstaller to bundle the miner with its own Python environment. +- **System Tray Icon**: Provides a persistent tray icon for status monitoring (Gray=Stopped, Green=Mining, Red=Error) and control. +- **Custom Installer**: NSIS script that prompts for a wallet name, sets up Start Menu shortcuts, and configures auto-start. +- **Non-Admin Install**: Installs to the user's profile directory by default. + +## Contents +- `src/tray_app.py`: Python script for the system tray application. +- `src/installer.nsi`: NSIS script for creating the installer executable. +- `build_windows.py`: Helper script to download the latest miner components and build the binary. + +## Build Instructions (on Windows) + +### 1. Prerequisites +- Python 3.8+ +- [NSIS](https://nsis.sourceforge.io/Download) installed and added to PATH. + +### 2. Setup Environment +```bash +pip install pyinstaller pystray Pillow requests +``` + +### 3. Prepare Miner Files +Run the build script to fetch the latest Windows-specific miner scripts: +```bash +python build_windows.py +``` + +### 4. Build Tray App +Compile the Python tray app into a standalone executable: +```bash +pyinstaller --onefile --noconsole --add-data "rustchain_miner.py;." --add-data "fingerprint_checks.py;." src/tray_app.py +``` +This will create `dist/tray_app.exe`. + +### 5. Create Installer +Copy `dist/tray_app.exe` to the `src` directory (or update the `.nsi` path) and run: +```bash +makensis src/installer.nsi +``` +The final `RustChainMinerInstaller.exe` will be created in the root directory. + +## Testing +1. Run `RustChainMinerInstaller.exe`. +2. Follow the prompts to set your wallet name. +3. Verify the miner appears in the system tray and starts mining. +4. Check the logs at `%USERPROFILE%\.rustchain\miner.log`. diff --git a/installers/windows/build_windows.py b/installers/windows/build_windows.py new file mode 100644 index 0000000..6c1dd4c --- /dev/null +++ b/installers/windows/build_windows.py @@ -0,0 +1,34 @@ +import os +import subprocess +import urllib.request + +# Configuration +REPO_BASE_LINUX = "https://raw.githubusercontent.com/Scottcjn/Rustchain/main/miners/linux/" +REPO_BASE_WINDOWS = "https://raw.githubusercontent.com/Scottcjn/Rustchain/main/miners/windows/" +FILES = [ + (REPO_BASE_WINDOWS + "rustchain_windows_miner.py", "rustchain_miner.py"), + (REPO_BASE_LINUX + "fingerprint_checks.py", "fingerprint_checks.py") +] + +def download_files(): + print("Downloading miner files...") + for url, local in FILES: + print(f" {url} -> {local}") + urllib.request.urlretrieve(url, local) + +def build_exe(): + print("Building tray_app.exe with PyInstaller...") + subprocess.run([ + "pyinstaller", + "--onefile", + "--noconsole", + "--add-data", "rustchain_miner.py;.", + "--add-data", "fingerprint_checks.py;.", + "src/tray_app.py" + ], check=True) + +if __name__ == "__main__": + download_files() + # Note: Building needs PyInstaller and NSIS installed on a Windows host + # build_exe() + print("Code prepared. To build, run on Windows with PyInstaller and NSIS.") diff --git a/installers/windows/src/installer.nsi b/installers/windows/src/installer.nsi new file mode 100644 index 0000000..47ff439 --- /dev/null +++ b/installers/windows/src/installer.nsi @@ -0,0 +1,91 @@ +!define APPNAME "RustChain Miner" +!define COMPANYNAME "Scottcjn" +!define DESCRIPTION "Proof-of-Antiquity Miner for RustChain" +!define VERSIONMAJOR 3 +!define VERSIONMINOR 2 +!define VERSIONBUILD 0 + +!include "MUI2.nsh" +!include "nsDialogs.nsh" + +Name "${APPNAME}" +OutFile "RustChainMinerInstaller.exe" +InstallDir "$PROFILE\.rustchain" +RequestExecutionLevel user + +Var Dialog +Var Label +Var WalletNameInput +Var WalletName + +!define MUI_ABORTWARNING + +!insertmacro MUI_PAGE_WELCOME +Page custom WalletPageShow WalletPageLeave +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_LANGUAGE "English" + +Function WalletPageShow + nsDialogs::Create 1018 + Pop $Dialog + + ${If} $Dialog == error + Abort + ${EndIf} + + ${NSD_CreateLabel} 0 0 100% 12u "Enter your RustChain wallet name:" + Pop $Label + + ${NSD_CreateText} 0 13u 100% 12u "my-desktop-miner" + Pop $WalletNameInput + + nsDialogs::Show +FunctionEnd + +Function WalletPageLeave + ${NSD_GetText} $WalletNameInput $WalletName + ${If} $WalletName == "" + MessageBox MB_OK "Please enter a wallet name." + Abort + ${EndIf} +FunctionEnd + +Section "Install" + SetOutPath "$INSTDIR" + + # Save wallet name to file + FileOpen $0 "$INSTDIR\wallet.txt" w + FileWrite $0 $WalletName + FileClose $0 + + # These files are expected to be in the same directory as the .nsi during build + File "tray_app.exe" + File "rustchain_miner.py" + File "fingerprint_checks.py" + + # Create shortcuts + CreateDirectory "$SMPROGRAMS\${APPNAME}" + CreateShortcut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\tray_app.exe" + CreateShortcut "$SMPROGRAMS\${APPNAME}\Uninstall ${APPNAME}.lnk" "$INSTDIR\uninstall.exe" + + # Add to startup + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "${APPNAME}" '"$INSTDIR\tray_app.exe"' + + WriteUninstaller "$INSTDIR\uninstall.exe" + + # Launch app after install + Exec '"$INSTDIR\tray_app.exe"' +SectionEnd + +Section "Uninstall" + # Stop processes + nsExec::Exec 'taskkill /F /IM tray_app.exe' + + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "${APPNAME}" + + RMDir /r "$SMPROGRAMS\${APPNAME}" + RMDir /r "$INSTDIR" +SectionEnd diff --git a/installers/windows/src/tray_app.py b/installers/windows/src/tray_app.py new file mode 100644 index 0000000..1390dd3 --- /dev/null +++ b/installers/windows/src/tray_app.py @@ -0,0 +1,161 @@ +import os +import sys +import subprocess +import threading +import time +import webbrowser +import json +from PIL import Image, ImageDraw +import pystray +from pystray import MenuItem as item +import tkinter as tk +from tkinter import messagebox + +# Helper for bundled files +def resource_path(relative_path): + if hasattr(sys, '_MEIPASS'): + return os.path.join(sys._MEIPASS, relative_path) + return os.path.join(os.path.abspath("."), relative_path) + +# Configuration +INSTALL_DIR = os.path.expanduser("~/.rustchain") +MINER_SCRIPT = resource_path("rustchain_miner.py") +LOG_FILE = os.path.join(INSTALL_DIR, "miner.log") +WALLET_FILE = os.path.join(INSTALL_DIR, "wallet.txt") + +class MinerTrayApp: + def __init__(self): + self.process = None + self.status = "Stopped" + self.icon = None + self.wallet_name = self.load_wallet() + os.makedirs(INSTALL_DIR, exist_ok=True) + + def load_wallet(self): + if os.path.exists(WALLET_FILE): + with open(WALLET_FILE, "r") as f: + return f.read().strip() + return "default-miner" + + def create_image(self, color): + image = Image.new('RGB', (64, 64), (255, 255, 255)) + dc = ImageDraw.Draw(image) + dc.ellipse((10, 10, 54, 54), fill=color, outline="black") + return image + + def get_status_color(self): + if self.status == "Mining": + return "green" + elif self.status == "Error": + return "red" + else: + return "gray" + + def update_icon(self): + if self.icon: + self.icon.icon = self.create_image(self.get_status_color()) + self.icon.title = f"RustChain Miner - {self.status}" + + def start_miner(self): + if self.status == "Mining": + return + + # Check if venv exists + venv_python = os.path.join(INSTALL_DIR, "venv", "Scripts", "python.exe") + if not os.path.exists(venv_python): + # Fallback to system python if venv not found + venv_python = sys.executable + + def run(): + try: + self.status = "Mining" + self.update_icon() + self.process = subprocess.Popen( + [venv_python, MINER_SCRIPT, "--wallet", self.wallet_name], + stdout=open(LOG_FILE, "a"), + stderr=subprocess.STDOUT, + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0 + ) + self.process.wait() + if self.process and self.process.returncode != 0: + self.status = "Error" + else: + self.status = "Stopped" + except Exception as e: + self.status = "Error" + finally: + self.update_icon() + + threading.Thread(target=run, daemon=True).start() + + def stop_miner(self): + if self.process: + self.process.terminate() + self.process = None + self.status = "Stopped" + self.update_icon() + + def view_logs(self): + if os.path.exists(LOG_FILE): + if sys.platform == "win32": + os.startfile(LOG_FILE) + else: + subprocess.run(["xdg-open", LOG_FILE]) + + def open_status(self): + webbrowser.open("https://50.28.86.131/api/miners") + + def quit_app(self, icon): + self.stop_miner() + icon.stop() + + def setup_menu(self): + return ( + item(f'Status: {self.status}', lambda: None, enabled=False), + item(f'Wallet: {self.wallet_name}', lambda: None, enabled=False), + item('Start Miner', self.start_miner), + item('Stop Miner', self.stop_miner), + item('View Logs', self.view_logs), + item('Network Status', self.open_status), + item('Exit', self.quit_app), + ) + + def run(self): + self.icon = pystray.Icon( + "RustChain Miner", + self.create_image(self.get_status_color()), + "RustChain Miner", + self.setup_menu() + ) + + threading.Thread(target=self.status_monitor, daemon=True).start() + + # Auto-start miner if wallet is configured + if self.wallet_name != "default-miner": + self.start_miner() + + self.icon.run() + + def status_monitor(self): + while True: + if self.process and self.process.poll() is not None: + if self.process.returncode != 0: + self.status = "Error" + else: + self.status = "Stopped" + self.process = None + self.update_icon() + + # Refresh menu to update status text + if self.icon: + self.icon.menu = pystray.Menu(*self.setup_menu()) + + time.sleep(5) + +if __name__ == "__main__": + app = MinerTrayApp() + app.run() + +if __name__ == "__main__": + app = MinerTrayApp() + app.run() diff --git a/tools/fingerprint_checks.py b/tools/fingerprint_checks.py new file mode 100644 index 0000000..74613d2 --- /dev/null +++ b/tools/fingerprint_checks.py @@ -0,0 +1,450 @@ +#!/usr/bin/env python3 +""" +RIP-PoA Hardware Fingerprint Validation +======================================== +7 Required Checks for RTC Reward Approval +ALL MUST PASS for antiquity multiplier rewards + +Checks: +1. Clock-Skew & Oscillator Drift +2. Cache Timing Fingerprint +3. SIMD Unit Identity +4. Thermal Drift Entropy +5. Instruction Path Jitter +6. Anti-Emulation Behavioral Checks +7. ROM Fingerprint (retro platforms only) +""" + +import hashlib +import os +import platform +import statistics +import subprocess +import time +from typing import Dict, List, Optional, Tuple + +# Import ROM fingerprint database if available +try: + from rom_fingerprint_db import ( + identify_rom, + is_known_emulator_rom, + compute_file_hash, + detect_platform_roms, + get_real_hardware_rom_signature, + ) + ROM_DB_AVAILABLE = True +except ImportError: + ROM_DB_AVAILABLE = False + +def check_clock_drift(samples: int = 200) -> Tuple[bool, Dict]: + """Check 1: Clock-Skew & Oscillator Drift""" + intervals = [] + reference_ops = 5000 + + for i in range(samples): + data = "drift_{}".format(i).encode() + start = time.perf_counter_ns() + for _ in range(reference_ops): + hashlib.sha256(data).digest() + elapsed = time.perf_counter_ns() - start + intervals.append(elapsed) + if i % 50 == 0: + time.sleep(0.001) + + mean_ns = statistics.mean(intervals) + stdev_ns = statistics.stdev(intervals) + cv = stdev_ns / mean_ns if mean_ns > 0 else 0 + + drift_pairs = [intervals[i] - intervals[i-1] for i in range(1, len(intervals))] + drift_stdev = statistics.stdev(drift_pairs) if len(drift_pairs) > 1 else 0 + + data = { + "mean_ns": int(mean_ns), + "stdev_ns": int(stdev_ns), + "cv": round(cv, 6), + "drift_stdev": int(drift_stdev), + } + + valid = True + if cv < 0.0001: + valid = False + data["fail_reason"] = "synthetic_timing" + elif drift_stdev == 0: + valid = False + data["fail_reason"] = "no_drift" + + return valid, data + + +def check_cache_timing(iterations: int = 100) -> Tuple[bool, Dict]: + """Check 2: Cache Timing Fingerprint (L1/L2/L3 Latency)""" + l1_size = 8 * 1024 + l2_size = 128 * 1024 + l3_size = 4 * 1024 * 1024 + + def measure_access_time(buffer_size: int, accesses: int = 1000) -> float: + buf = bytearray(buffer_size) + for i in range(0, buffer_size, 64): + buf[i] = i % 256 + start = time.perf_counter_ns() + for i in range(accesses): + _ = buf[(i * 64) % buffer_size] + elapsed = time.perf_counter_ns() - start + return elapsed / accesses + + l1_times = [measure_access_time(l1_size) for _ in range(iterations)] + l2_times = [measure_access_time(l2_size) for _ in range(iterations)] + l3_times = [measure_access_time(l3_size) for _ in range(iterations)] + + l1_avg = statistics.mean(l1_times) + l2_avg = statistics.mean(l2_times) + l3_avg = statistics.mean(l3_times) + + l2_l1_ratio = l2_avg / l1_avg if l1_avg > 0 else 0 + l3_l2_ratio = l3_avg / l2_avg if l2_avg > 0 else 0 + + data = { + "l1_ns": round(l1_avg, 2), + "l2_ns": round(l2_avg, 2), + "l3_ns": round(l3_avg, 2), + "l2_l1_ratio": round(l2_l1_ratio, 3), + "l3_l2_ratio": round(l3_l2_ratio, 3), + } + + valid = True + if l2_l1_ratio < 1.01 and l3_l2_ratio < 1.01: + valid = False + data["fail_reason"] = "no_cache_hierarchy" + elif l1_avg == 0 or l2_avg == 0 or l3_avg == 0: + valid = False + data["fail_reason"] = "zero_latency" + + return valid, data + + +def check_simd_identity() -> Tuple[bool, Dict]: + """Check 3: SIMD Unit Identity (SSE/AVX/AltiVec/NEON)""" + flags = [] + arch = platform.machine().lower() + + try: + with open("/proc/cpuinfo", "r") as f: + for line in f: + if "flags" in line.lower() or "features" in line.lower(): + parts = line.split(":") + if len(parts) > 1: + flags = parts[1].strip().split() + break + except: + pass + + if not flags: + try: + result = subprocess.run( + ["sysctl", "-a"], + capture_output=True, text=True, timeout=5 + ) + for line in result.stdout.split("\n"): + if "feature" in line.lower() or "altivec" in line.lower(): + flags.append(line.split(":")[-1].strip()) + except: + pass + + has_sse = any("sse" in f.lower() for f in flags) + has_avx = any("avx" in f.lower() for f in flags) + has_altivec = any("altivec" in f.lower() for f in flags) or "ppc" in arch + has_neon = any("neon" in f.lower() for f in flags) or "arm" in arch + + data = { + "arch": arch, + "simd_flags_count": len(flags), + "has_sse": has_sse, + "has_avx": has_avx, + "has_altivec": has_altivec, + "has_neon": has_neon, + "sample_flags": flags[:10] if flags else [], + } + + valid = has_sse or has_avx or has_altivec or has_neon or len(flags) > 0 + if not valid: + data["fail_reason"] = "no_simd_detected" + + return valid, data + + +def check_thermal_drift(samples: int = 50) -> Tuple[bool, Dict]: + """Check 4: Thermal Drift Entropy""" + cold_times = [] + for i in range(samples): + start = time.perf_counter_ns() + for _ in range(10000): + hashlib.sha256("cold_{}".format(i).encode()).digest() + cold_times.append(time.perf_counter_ns() - start) + + for _ in range(100): + for __ in range(50000): + hashlib.sha256(b"warmup").digest() + + hot_times = [] + for i in range(samples): + start = time.perf_counter_ns() + for _ in range(10000): + hashlib.sha256("hot_{}".format(i).encode()).digest() + hot_times.append(time.perf_counter_ns() - start) + + cold_avg = statistics.mean(cold_times) + hot_avg = statistics.mean(hot_times) + cold_stdev = statistics.stdev(cold_times) + hot_stdev = statistics.stdev(hot_times) + drift_ratio = hot_avg / cold_avg if cold_avg > 0 else 0 + + data = { + "cold_avg_ns": int(cold_avg), + "hot_avg_ns": int(hot_avg), + "cold_stdev": int(cold_stdev), + "hot_stdev": int(hot_stdev), + "drift_ratio": round(drift_ratio, 4), + } + + valid = True + if cold_stdev == 0 and hot_stdev == 0: + valid = False + data["fail_reason"] = "no_thermal_variance" + + return valid, data + + +def check_instruction_jitter(samples: int = 100) -> Tuple[bool, Dict]: + """Check 5: Instruction Path Jitter""" + def measure_int_ops(count: int = 10000) -> float: + start = time.perf_counter_ns() + x = 1 + for i in range(count): + x = (x * 7 + 13) % 65537 + return time.perf_counter_ns() - start + + def measure_fp_ops(count: int = 10000) -> float: + start = time.perf_counter_ns() + x = 1.5 + for i in range(count): + x = (x * 1.414 + 0.5) % 1000.0 + return time.perf_counter_ns() - start + + def measure_branch_ops(count: int = 10000) -> float: + start = time.perf_counter_ns() + x = 0 + for i in range(count): + if i % 2 == 0: + x += 1 + else: + x -= 1 + return time.perf_counter_ns() - start + + int_times = [measure_int_ops() for _ in range(samples)] + fp_times = [measure_fp_ops() for _ in range(samples)] + branch_times = [measure_branch_ops() for _ in range(samples)] + + int_avg = statistics.mean(int_times) + fp_avg = statistics.mean(fp_times) + branch_avg = statistics.mean(branch_times) + + int_stdev = statistics.stdev(int_times) + fp_stdev = statistics.stdev(fp_times) + branch_stdev = statistics.stdev(branch_times) + + data = { + "int_avg_ns": int(int_avg), + "fp_avg_ns": int(fp_avg), + "branch_avg_ns": int(branch_avg), + "int_stdev": int(int_stdev), + "fp_stdev": int(fp_stdev), + "branch_stdev": int(branch_stdev), + } + + valid = True + if int_stdev == 0 and fp_stdev == 0 and branch_stdev == 0: + valid = False + data["fail_reason"] = "no_jitter" + + return valid, data + + +def check_anti_emulation() -> Tuple[bool, Dict]: + """Check 6: Anti-Emulation Behavioral Checks""" + vm_indicators = [] + + vm_paths = [ + "/sys/class/dmi/id/product_name", + "/sys/class/dmi/id/sys_vendor", + "/proc/scsi/scsi", + ] + + vm_strings = ["vmware", "virtualbox", "kvm", "qemu", "xen", "hyperv", "parallels"] + + for path in vm_paths: + try: + with open(path, "r") as f: + content = f.read().lower() + for vm in vm_strings: + if vm in content: + vm_indicators.append("{}:{}".format(path, vm)) + except: + pass + + for key in ["KUBERNETES", "DOCKER", "VIRTUAL", "container"]: + if key in os.environ: + vm_indicators.append("ENV:{}".format(key)) + + try: + with open("/proc/cpuinfo", "r") as f: + if "hypervisor" in f.read().lower(): + vm_indicators.append("cpuinfo:hypervisor") + except: + pass + + data = { + "vm_indicators": vm_indicators, + "indicator_count": len(vm_indicators), + "is_likely_vm": len(vm_indicators) > 0, + } + + valid = len(vm_indicators) == 0 + if not valid: + data["fail_reason"] = "vm_detected" + + return valid, data + + +def check_rom_fingerprint() -> Tuple[bool, Dict]: + """ + Check 7: ROM Fingerprint (for retro platforms) + + Detects if running with a known emulator ROM dump. + Real vintage hardware should have unique/variant ROMs. + Emulators all use the same pirated ROM packs. + """ + if not ROM_DB_AVAILABLE: + # Skip for modern hardware or if DB not available + return True, {"skipped": True, "reason": "rom_db_not_available_or_modern_hw"} + + arch = platform.machine().lower() + rom_hashes = {} + emulator_detected = False + detection_details = [] + + # Check for PowerPC (Mac emulation target) + if "ppc" in arch or "powerpc" in arch: + # Try to get real hardware ROM signature + real_rom = get_real_hardware_rom_signature() + if real_rom: + rom_hashes["real_hardware"] = real_rom + else: + # Check if running under emulator with known ROM + platform_roms = detect_platform_roms() + if platform_roms: + for platform_name, rom_hash in platform_roms.items(): + if is_known_emulator_rom(rom_hash, "md5"): + emulator_detected = True + rom_info = identify_rom(rom_hash, "md5") + detection_details.append({ + "platform": platform_name, + "hash": rom_hash, + "known_as": rom_info, + }) + + # Check for 68K (Amiga, Atari ST, old Mac) + elif "m68k" in arch or "68000" in arch: + platform_roms = detect_platform_roms() + for platform_name, rom_hash in platform_roms.items(): + if "amiga" in platform_name.lower(): + if is_known_emulator_rom(rom_hash, "sha1"): + emulator_detected = True + rom_info = identify_rom(rom_hash, "sha1") + detection_details.append({ + "platform": platform_name, + "hash": rom_hash, + "known_as": rom_info, + }) + elif "mac" in platform_name.lower(): + if is_known_emulator_rom(rom_hash, "apple"): + emulator_detected = True + rom_info = identify_rom(rom_hash, "apple") + detection_details.append({ + "platform": platform_name, + "hash": rom_hash, + "known_as": rom_info, + }) + + # For modern hardware, report "N/A" but pass + else: + return True, { + "skipped": False, + "arch": arch, + "is_retro_platform": False, + "rom_check": "not_applicable_modern_hw", + } + + data = { + "arch": arch, + "is_retro_platform": True, + "rom_hashes": rom_hashes, + "emulator_detected": emulator_detected, + "detection_details": detection_details, + } + + if emulator_detected: + data["fail_reason"] = "known_emulator_rom" + return False, data + + return True, data + + +def validate_all_checks(include_rom_check: bool = True) -> Tuple[bool, Dict]: + """Run all 7 fingerprint checks. ALL MUST PASS for RTC approval.""" + results = {} + all_passed = True + + checks = [ + ("clock_drift", "Clock-Skew & Oscillator Drift", check_clock_drift), + ("cache_timing", "Cache Timing Fingerprint", check_cache_timing), + ("simd_identity", "SIMD Unit Identity", check_simd_identity), + ("thermal_drift", "Thermal Drift Entropy", check_thermal_drift), + ("instruction_jitter", "Instruction Path Jitter", check_instruction_jitter), + ("anti_emulation", "Anti-Emulation Checks", check_anti_emulation), + ] + + # Add ROM check for retro platforms + if include_rom_check and ROM_DB_AVAILABLE: + checks.append(("rom_fingerprint", "ROM Fingerprint (Retro)", check_rom_fingerprint)) + + print(f"Running {len(checks)} Hardware Fingerprint Checks...") + print("=" * 50) + + total_checks = len(checks) + for i, (key, name, func) in enumerate(checks, 1): + print(f"\n[{i}/{total_checks}] {name}...") + try: + passed, data = func() + except Exception as e: + passed = False + data = {"error": str(e)} + results[key] = {"passed": passed, "data": data} + if not passed: + all_passed = False + print(" Result: {}".format("PASS" if passed else "FAIL")) + + print("\n" + "=" * 50) + print("OVERALL RESULT: {}".format("ALL CHECKS PASSED" if all_passed else "FAILED")) + + if not all_passed: + failed = [k for k, v in results.items() if not v["passed"]] + print("Failed checks: {}".format(failed)) + + return all_passed, results + + +if __name__ == "__main__": + import json + passed, results = validate_all_checks() + print("\n\nDetailed Results:") + print(json.dumps(results, indent=2, default=str)) diff --git a/tools/fingerprint_results.json b/tools/fingerprint_results.json new file mode 100644 index 0000000..85284d3 --- /dev/null +++ b/tools/fingerprint_results.json @@ -0,0 +1,87 @@ +{ + "system": { + "platform": "Linux", + "machine": "aarch64", + "processor": "aarch64", + "version": "#91-Ubuntu SMP PREEMPT_DYNAMIC Tue Nov 18 13:53:54 UTC 2025" + }, + "timestamp": 1770358319, + "results": { + "clock_drift": { + "passed": true, + "data": { + "mean_ns": 1409497, + "stdev_ns": 31366, + "cv": 0.022254, + "drift_stdev": 35698 + } + }, + "cache_timing": { + "passed": true, + "data": { + "l1_ns": 48.99, + "l2_ns": 49.58, + "l3_ns": 51.81, + "l2_l1_ratio": 1.012, + "l3_l2_ratio": 1.045 + } + }, + "simd_identity": { + "passed": true, + "data": { + "arch": "aarch64", + "simd_flags_count": 31, + "has_sse": false, + "has_avx": false, + "has_altivec": false, + "has_neon": false, + "sample_flags": [ + "fp", + "asimd", + "evtstrm", + "aes", + "pmull", + "sha1", + "sha2", + "crc32", + "atomics", + "fphp" + ] + } + }, + "thermal_drift": { + "passed": true, + "data": { + "cold_avg_ns": 4550063, + "hot_avg_ns": 4542299, + "cold_stdev": 64032, + "hot_stdev": 68631, + "drift_ratio": 0.9983 + } + }, + "instruction_jitter": { + "passed": true, + "data": { + "int_avg_ns": 619079, + "fp_avg_ns": 504182, + "branch_avg_ns": 319678, + "int_stdev": 15987, + "fp_stdev": 11697, + "branch_stdev": 11532 + } + }, + "anti_emulation": { + "passed": false, + "data": { + "vm_indicators": [ + "/sys/class/dmi/id/product_name:qemu", + "/sys/class/dmi/id/sys_vendor:qemu", + "/proc/scsi/scsi:qemu" + ], + "indicator_count": 3, + "is_likely_vm": true, + "fail_reason": "vm_detected" + } + } + } +} \ No newline at end of file diff --git a/tools/test_output.txt b/tools/test_output.txt new file mode 100644 index 0000000..4affd94 --- /dev/null +++ b/tools/test_output.txt @@ -0,0 +1,40 @@ +================================================================================ +RustChain Hardware Fingerprint Preflight Validator +================================================================================ +System: Linux aarch64 +CPU: aarch64 +-------------------------------------------------------------------------------- +[*] Running Clock-Skew & Oscillator Drift... + Status: PASS + Coefficient of Variation (CV): 0.022254 (Min required: 0.0001) + +[*] Running Cache Timing Fingerprint... + Status: PASS + L2/L1 Latency Ratio: 1.012 + L3/L2 Latency Ratio: 1.045 + [!] WARNING: Flat cache hierarchy detected. + +[*] Running SIMD Unit Identity... + Status: PASS + SIMD: SSE=False, AVX=False, AltiVec=False, NEON=False + +[*] Running Thermal Drift Entropy... + Status: PASS + +[*] Running Instruction Path Jitter... + Status: PASS + +[*] Running Anti-Emulation Behavioral Checks... + Status: FAIL + [!] VM Indicators Found: /sys/class/dmi/id/product_name:qemu, /sys/class/dmi/id/sys_vendor:qemu, /proc/scsi/scsi:qemu + +-------------------------------------------------------------------------------- +TEST SUMMARY: FAILED +-------------------------------------------------------------------------------- +Failed checks: anti_emulation + +Actionable Recommendations: +- Review the VM indicators listed in the diagnostic section above. + +================================================================================ +[*] Results exported to fingerprint_results.json