// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 // // XmrigUpdater — fetch + verify + install the latest DRG-XMRig miner from the DragonX Gitea. // // Flow (mirrors util/Bootstrap): query the Gitea releases API for the latest release, pick the // asset matching this platform (…-linux-x64.zip / …-win-x64.zip), download it, verify its // published SHA-256, then extract the miner binary (flattening the versioned subdir the archive // nests it in) into the target directory and verify the extracted binary's SHA-256 before it is // made executable. All network/disk work runs on a background thread; progress is polled // thread-safely from the UI thread. // // Security: download-and-execute, so verification is mandatory — TLS is verified (libcurl // defaults), the host is the project's own Gitea over HTTPS, and BOTH the archive and the inner // binary are checked against the SHA-256 checksums published in the release body. The checksums // live in the release body markdown (no SHA256SUMS asset), parsed as " " lines. #pragma once #include #include #include #include #include #include #include namespace dragonx { namespace util { struct XmrigReleaseAsset { std::string name; std::string downloadUrl; long long size = 0; }; struct XmrigRelease { bool ok = false; std::string tag; // e.g. "v1.0.0" std::string name; // human title (e.g. "DRG-XMRig v6.25.3") std::string body; // release notes markdown (holds the checksum blocks) bool prerelease = false; // marked pre-release on the Gitea release std::string publishedAt; // ISO-8601 publish timestamp (date shown in the UI) std::vector assets; std::string error; }; // ── Release signature (opt-in supply-chain hardening) ──────────────────────── // // Pinned ed25519 public key (base64, 32 bytes) used to verify a detached signature over the // downloaded archive. EMPTY by default = signature verification DISABLED (TLS + the release's // published SHA-256 are the only gates, unchanged behavior). To enable: paste the base64 public // key here and have the release publish a ".sig" asset containing the base64 (or // raw 64-byte) ed25519 signature of the archive bytes (see scripts/sign-xmrig-release.sh). Once a // key is set, an install verifies the signature when the .sig asset is present; set // kXmrigRequireSignature=true to additionally refuse installs that publish no signature. inline constexpr const char* kXmrigSignaturePublicKeyBase64 = "j/9M+0E8NgcudP1q+23ar5uzwAFzQled8TtkFMaou6Q="; inline constexpr bool kXmrigRequireSignature = true; // enforced: refuse installs without a valid .sig // ── Pure helpers (no I/O; unit-tested) ─────────────────────────────────────── // Parse the Gitea GET /releases/latest JSON into an XmrigRelease (ok=false + error on failure). XmrigRelease parseXmrigRelease(const std::string& json); // Parse the Gitea GET /releases (array) JSON into the list of releases, newest first, skipping // drafts. Empty on parse failure. Lets the user browse + pin a specific (or pre-release) version. std::vector parseXmrigReleaseList(const std::string& json); // The asset-name token for the host platform: "linux-x64", "win-x64", "macos-x64", // "macos-arm64", or "" if unknown/unsupported. std::string currentXmrigPlatformToken(); // Index of the asset whose name matches the platform token (e.g. ends with "-linux-x64.zip"), // or -1 if none (e.g. no macOS build is published). int selectXmrigAsset(const XmrigRelease& release, const std::string& platformToken); // Parse " …" lines from the release body into { name -> lowercase-hex }. // Keys are the first whitespace-delimited token after the hash, so this captures both the archive // checksums (keyed by zip filename) and the inner-binary checksums (keyed by "xmrig"/"xmrig.exe"). std::map parseXmrigChecksums(const std::string& body); // Lowercase-hex SHA-256 of a buffer (libsodium). Empty string on failure. std::string sha256Hex(const void* data, std::size_t len); // The binary file basenames to extract for a platform: {"xmrig"} on POSIX, // {"xmrig.exe", "WinRing0x64.sys"} on Windows. First entry is always the miner binary. std::vector xmrigExtractBasenames(const std::string& platformToken); // Index of the detached-signature asset for a given archive (name ".sig" or // ".minisig"), or -1 if none is published. int selectXmrigSignatureAsset(const XmrigRelease& release, const std::string& archiveName); // Verify a detached ed25519 signature over `data`. `signatureFileContent` is the .sig asset body // (base64 of, or raw, a 64-byte ed25519 signature; whitespace tolerated). `pubKeyBase64` is the // base64 32-byte public key. Returns true only on a cryptographically valid signature. bool verifyXmrigSignature(const std::string& data, const std::string& signatureFileContent, const std::string& pubKeyBase64); // ── Background worker ──────────────────────────────────────────────────────── class XmrigUpdater { public: enum class State { Idle, Checking, UpToDate, UpdateAvailable, Unavailable, // no miner build is published for this platform (terminal, not an error) Listing, // fetching the full release list (Browse all releases) ReleaseList, // release list fetched; awaiting the user's pick Downloading, Verifying, Extracting, Done, Failed }; struct Progress { State state = State::Idle; double downloaded_bytes = 0; double total_bytes = 0; float percent = 0.0f; std::string status_text; std::string error; // non-empty on Failed std::string latest_tag; // tag reported by the API (once checked) std::string installed_tag; // caller-supplied current install (for update detection) bool update_available = false; }; // Gitea releases API for the DRG-XMRig fork. static constexpr const char* kApiUrl = "https://git.dragonx.is/api/v1/repos/DragonX/drg-xmrig/releases/latest"; // Full release list (newest first, includes pre-releases) for the "Browse all releases" picker. static constexpr const char* kReleasesUrl = "https://git.dragonx.is/api/v1/repos/DragonX/drg-xmrig/releases?limit=50"; XmrigUpdater() = default; ~XmrigUpdater(); XmrigUpdater(const XmrigUpdater&) = delete; XmrigUpdater& operator=(const XmrigUpdater&) = delete; // Query the latest release on a background thread. `installedTag` (may be empty/unknown) is // compared to the API tag to set Progress.update_available. End state: UpToDate / UpdateAvailable // / Failed. void startCheck(const std::string& installedTag); // Download → verify archive → extract (flatten) → verify binary → install into `targetDir` on a // background thread. Re-fetches the latest release so it is self-contained. End state: Done / // Failed. On Done, getProgress().latest_tag is the version that should be persisted as installed. void startInstall(const std::string& targetDir); // Fetch the full release list on a background thread (for "Browse all releases"). End state: // ReleaseList (then getReleases() holds the list, newest first) / Failed. void startListReleases(); // Snapshot of the release list fetched by startListReleases(). std::vector getReleases() const; // Install a SPECIFIC release (chosen from the browse list) into `targetDir` — same verify/extract // path as startInstall, but pinned to `release` instead of latest. End state: Done / Failed. void startInstallRelease(const std::string& targetDir, XmrigRelease release); void cancel(); Progress getProgress() const; bool isDone() const; // true once the worker reached a terminal state (Done/Failed/Unavailable) // Internal: called by the libcurl progress callback. Publishes live download bytes and returns // false to ask curl to abort (when cancel() was requested). Public only so the C callback in the // .cpp can reach it without leaking curl types into this header. bool onDownloadProgress(double downloadedBytes, double totalBytes); private: void runCheck(std::string installedTag); void runListReleases(); void runInstall(std::string targetDir); void installResolved(const std::string& targetDir, const XmrigRelease& rel); // shared install body void setProgress(State state, const std::string& text, double done = 0, double total = 0); bool downloadToFile(const std::string& url, const std::string& destPath); std::string httpGet(const std::string& url); mutable std::mutex mutex_; Progress progress_; std::vector releases_; std::atomic cancel_requested_{false}; std::atomic worker_running_{false}; std::thread worker_; }; } // namespace util } // namespace dragonx