v4.0.0: codec 2.60.4 security release, --pq-box sealed-box mode, F-16 fix
Some checks failed
CI / build-and-test (clang) (push) Has been cancelled
CI / build-and-test (gcc) (push) Has been cancelled
CI / strict-warnings (clang, -Wall -Wextra -Wpedantic -Wshadow -Wcast-align -Wstrict-prototypes -Wmissing-prototypes -Wnull-dereference -O2 -std=c11 -Werror) (push) Has been cancelled
CI / strict-warnings (gcc, -Wall -Wextra -Wpedantic -Wshadow -Wcast-align -Wstrict-prototypes -Wmissing-prototypes -Wnull-dereference -Wformat-security -Wlogical-op -Wjump-misses-init -Wdouble-promotion -O2 -std=c11 -Werror) (push) Has been cancelled
CI / sanitizers (push) Has been cancelled
CI / pie-hardening (push) Has been cancelled
CI / cross-aarch64 (push) Has been cancelled
CI / dist-reproducibility (push) Has been cancelled
CI / packaging-syntax (push) Has been cancelled
CI / release (push) Has been cancelled
Some checks failed
CI / build-and-test (clang) (push) Has been cancelled
CI / build-and-test (gcc) (push) Has been cancelled
CI / strict-warnings (clang, -Wall -Wextra -Wpedantic -Wshadow -Wcast-align -Wstrict-prototypes -Wmissing-prototypes -Wnull-dereference -O2 -std=c11 -Werror) (push) Has been cancelled
CI / strict-warnings (gcc, -Wall -Wextra -Wpedantic -Wshadow -Wcast-align -Wstrict-prototypes -Wmissing-prototypes -Wnull-dereference -Wformat-security -Wlogical-op -Wjump-misses-init -Wdouble-promotion -O2 -std=c11 -Werror) (push) Has been cancelled
CI / sanitizers (push) Has been cancelled
CI / pie-hardening (push) Has been cancelled
CI / cross-aarch64 (push) Has been cancelled
CI / dist-reproducibility (push) Has been cancelled
CI / packaging-syntax (push) Has been cancelled
CI / release (push) Has been cancelled
Major release. Highlights: - Codec: vendored VaptVupt codec moves to canonical 2.60.4 security release. Fixes a high-severity OOB heap write in the AVX2 decode fast path (reachable on a valid stream sized to exactly content_size, both tail variants). Brings CBMC-formally-verified BCJ filters with automatic ELF/PE/Mach-O detection. Compressed output stays byte-identical (ratio gate Δ 0.00%); wire format unchanged at v1.6. - New --pq-box sealed-box recipient mode (vendored libpqvaptvupt 0.6.0): ML-KEM-768 + X25519 combined via HKDF-SHA256 with domain separation, AES-256-CTR + HMAC-SHA256 EtM. Legacy --pq and --pq-sdk stay readable. - F-16: discloses and fixes a pre-existing data-loss defect in the <= 3.8.0 in-tree BCJ encoder. Full back-compat matrix decodes byte-exact under 4.0.0; every readable pre-4.0 archive remains readable. Repository hygiene: - Sync full 4.0.0 source tree (codec, crypto, SDK, GUI, packaging, tests). - Remove internal scratch files (PROMPT.md, FORMAL_AUDIT_PROMPT.md) and superseded version-specific docs (INTEGRATION_PROTOCOL_2.60.4.md, docs/FINDINGS-2.x.md) and a stray test binary. - Refresh README download/install section to real 4.0.0 release assets; bump version badge to 4.0.0. - Add .gitignore for build outputs (keeps vendored prebuilt libraries).
This commit is contained in:
parent
7619c4c577
commit
544a2cd647
98 changed files with 15615 additions and 1397 deletions
|
|
@ -1,14 +1,17 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||
"""Zupt GUI — Cross-Platform Post-Quantum Backup.
|
||||
"""VaptVupt GUI — Cross-Platform Post-Quantum Backup.
|
||||
|
||||
Renamed from "Zupt" in v3.0.0 due to INPI Brasil trademark.
|
||||
The .zupt file extension is preserved.
|
||||
|
||||
Tries PySide6 first (preferred), falls back to PyQt6 if PySide6 is
|
||||
not installed. PyQt6 is the default available package on Debian/Ubuntu
|
||||
without requiring pip; PySide6 ships with broader signal/slot semantics
|
||||
but the API surface used here is portable between the two.
|
||||
"""
|
||||
import sys, os, subprocess, shutil
|
||||
import sys, os, re, subprocess, shutil
|
||||
from pathlib import Path
|
||||
|
||||
# Try PySide6, fall back to PyQt6. The two have nearly-identical APIs;
|
||||
|
|
@ -36,48 +39,156 @@ except ImportError:
|
|||
QT_BINDING = "PyQt6"
|
||||
except ImportError:
|
||||
sys.stderr.write(
|
||||
"ERROR: zupt-gui requires PySide6 or PyQt6. Install one of:\n"
|
||||
"ERROR: vaptvupt-gui requires PySide6 or PyQt6. Install one of:\n"
|
||||
" Debian/Ubuntu: sudo apt install python3-pyqt6\n"
|
||||
" Fedora/RHEL: sudo dnf install python3-pyqt6\n"
|
||||
" pip (any OS): pip install PySide6\n"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# ── Find zupt binary ──
|
||||
def _find_zupt():
|
||||
if os.environ.get("ZUPT_BIN") and os.path.isfile(os.environ["ZUPT_BIN"]):
|
||||
return os.environ["ZUPT_BIN"]
|
||||
# Check local project tree FIRST (handles running from zupt-2.1.6/gui/)
|
||||
here = Path(getattr(sys, '_MEIPASS', Path(__file__).parent))
|
||||
for c in [here.parent.parent/"zupt", # zupt-2.1.6/gui/src -> zupt-2.1.6/zupt
|
||||
here.parent/"zupt", # zupt-2.1.6/gui -> zupt-2.1.6/zupt (shouldn't happen but safe)
|
||||
here/"zupt", # same dir as script
|
||||
here.parent.parent/"zupt.exe",
|
||||
here.parent/"zupt.exe",
|
||||
here/"zupt.exe"]:
|
||||
if c.is_file() and os.access(str(c), os.X_OK):
|
||||
return str(c.resolve())
|
||||
# Then system PATH
|
||||
found = shutil.which("zupt")
|
||||
if found: return found
|
||||
# Fallback common paths
|
||||
for c in [Path("/usr/local/bin/zupt"), Path("/usr/bin/zupt")]:
|
||||
if c.is_file(): return str(c)
|
||||
return "zupt"
|
||||
# ── Find vaptvupt binary ──
|
||||
#
|
||||
# v3.0.0 rename: the binary is now `vaptvupt`; older installations
|
||||
# (1.x/2.x) ship `zupt`. We try the new name first, fall back to the
|
||||
# old name, and on every candidate verify it's actually executable
|
||||
# (not just present). After picking a candidate, we run a quick
|
||||
# `version` liveness check — this catches the case where the binary
|
||||
# exists but can't load its shared library (the original bug report:
|
||||
# "GUI doesn't find zupt; copying to /usr/local/bin fixes it").
|
||||
#
|
||||
# Diagnostic output goes to stderr so users can `vaptvupt-gui 2>log`
|
||||
# to see exactly which path was tried and why each failed.
|
||||
|
||||
ZUPT = _find_zupt()
|
||||
_DISCOVERY_LOG = []
|
||||
|
||||
def _discovery_log(msg):
|
||||
_DISCOVERY_LOG.append(msg)
|
||||
# Echo to stderr if VAPTVUPT_DEBUG or ZUPT_DEBUG is set
|
||||
if os.environ.get("VAPTVUPT_DEBUG") or os.environ.get("ZUPT_DEBUG"):
|
||||
sys.stderr.write(f" [discovery] {msg}\n")
|
||||
|
||||
def _is_runnable(path):
|
||||
"""A path is runnable if it's a file, executable, and exits 0 on `version`."""
|
||||
p = str(path)
|
||||
if not os.path.isfile(p):
|
||||
return False, "not a regular file"
|
||||
if not os.access(p, os.X_OK):
|
||||
return False, "not executable (chmod +x needed?)"
|
||||
# Liveness check — catches missing shared libraries, broken rpath,
|
||||
# ABI mismatch, etc. 3-second cap so we never hang the GUI startup.
|
||||
try:
|
||||
r = subprocess.run([p, "version"], capture_output=True, timeout=3)
|
||||
if r.returncode != 0:
|
||||
err = r.stderr.decode("utf-8", errors="replace").strip()
|
||||
return False, f"exit {r.returncode}: {err.splitlines()[0] if err else 'no stderr'}"
|
||||
return True, "OK"
|
||||
except FileNotFoundError:
|
||||
return False, "FileNotFoundError on exec"
|
||||
except subprocess.TimeoutExpired:
|
||||
return False, "timeout (3s) on `version` — binary hung"
|
||||
except OSError as e:
|
||||
return False, f"OSError: {e}"
|
||||
|
||||
def _find_vaptvupt():
|
||||
# 1. Explicit env override
|
||||
for env in ("VAPTVUPT_BIN", "ZUPT_BIN"):
|
||||
p = os.environ.get(env)
|
||||
if p:
|
||||
ok, reason = _is_runnable(p)
|
||||
_discovery_log(f"env {env}={p}: {reason}")
|
||||
if ok:
|
||||
return p
|
||||
|
||||
# 2. Local project tree (running from a source checkout)
|
||||
# Try BOTH names (vaptvupt is v3.0.0+, zupt is legacy).
|
||||
here = Path(getattr(sys, "_MEIPASS", Path(__file__).parent))
|
||||
for parent in (here.parent.parent, here.parent, here):
|
||||
for name in ("vaptvupt", "zupt", "vaptvupt.exe", "zupt.exe"):
|
||||
c = parent / name
|
||||
ok, reason = _is_runnable(c)
|
||||
_discovery_log(f"local {c}: {reason}")
|
||||
if ok:
|
||||
return str(c.resolve())
|
||||
|
||||
# 3. System PATH — try new name first, then legacy
|
||||
for name in ("vaptvupt", "zupt"):
|
||||
found = shutil.which(name)
|
||||
if found:
|
||||
ok, reason = _is_runnable(found)
|
||||
_discovery_log(f"PATH which({name})={found}: {reason}")
|
||||
if ok:
|
||||
return found
|
||||
else:
|
||||
_discovery_log(f"PATH which({name}): not found")
|
||||
|
||||
# 4. Hard-coded common install paths — catches the "GUI launched
|
||||
# from a desktop session with a minimal PATH that omits /usr/bin"
|
||||
# scenario reported against v2.4.8.
|
||||
common = [
|
||||
# New name (v3.0.0+)
|
||||
"/usr/local/bin/vaptvupt", "/usr/bin/vaptvupt",
|
||||
"/opt/vaptvupt/bin/vaptvupt", "/opt/homebrew/bin/vaptvupt",
|
||||
# Legacy name (1.x/2.x)
|
||||
"/usr/local/bin/zupt", "/usr/bin/zupt",
|
||||
"/opt/zupt/bin/zupt", "/opt/homebrew/bin/zupt",
|
||||
# Termux (Android) install path
|
||||
"/data/data/com.termux/files/usr/bin/vaptvupt",
|
||||
"/data/data/com.termux/files/usr/bin/zupt",
|
||||
# Flatpak sandbox runtime path
|
||||
"/app/bin/vaptvupt", "/app/bin/zupt",
|
||||
]
|
||||
for path in common:
|
||||
ok, reason = _is_runnable(path)
|
||||
_discovery_log(f"common {path}: {reason}")
|
||||
if ok:
|
||||
return path
|
||||
|
||||
# 5. Last resort — return "vaptvupt" and let exec fail loudly later.
|
||||
# A caller-visible error is better than silently returning a path
|
||||
# that doesn't work.
|
||||
_discovery_log("FAILED: no runnable vaptvupt/zupt binary found")
|
||||
return "vaptvupt"
|
||||
|
||||
# Backward-compat: code elsewhere in this file still uses `ZUPT`.
|
||||
VAPTVUPT = _find_vaptvupt()
|
||||
ZUPT = VAPTVUPT # legacy alias used throughout the rest of zupt_gui.py
|
||||
|
||||
# ── Query version ONCE at import (cached) ──
|
||||
def _get_version():
|
||||
try:
|
||||
r = subprocess.run([ZUPT, "version"], capture_output=True, text=True, timeout=5)
|
||||
if r.returncode == 0:
|
||||
lines = r.stdout.strip().split("\n")
|
||||
return lines[0], r.stdout.strip()
|
||||
except Exception: pass
|
||||
return "zupt (not found)", ""
|
||||
#
|
||||
# The CLI's `version` first line is the brand banner. Examples:
|
||||
# v2.4.x: "zupt 2.4.8"
|
||||
# v3.0.x: "vaptvupt 3.0.0 (formerly zupt; renamed in v3.0.0 — INPI Brasil trademark)"
|
||||
#
|
||||
# We extract three things from that line:
|
||||
# - VER_SHORT: the full first line (used as a fallback display)
|
||||
# - VER_NUMBER: just the version number "3.0.0" or "2.4.8" (for hero text)
|
||||
# - VER_FULL: the entire stdout (used in the about panel)
|
||||
#
|
||||
# Earlier code did `ZUPT_VER_SHORT.replace("zupt ", "")` to peel the
|
||||
# product name. That breaks on v3.0.x because the same string "zupt "
|
||||
# also appears inside the parenthetical "formerly zupt; renamed".
|
||||
# We now use a strict regex anchored at the start of the line.
|
||||
|
||||
ZUPT_VER_SHORT, ZUPT_VER_FULL = _get_version()
|
||||
_VERSION_RE = re.compile(r'^(?:vaptvupt|zupt)\s+(\d+\.\d+\.\d+(?:[._A-Za-z0-9-]*)?)')
|
||||
|
||||
def _get_version():
|
||||
short = "vaptvupt (not found)"
|
||||
number = "?"
|
||||
full = ""
|
||||
try:
|
||||
r = subprocess.run([VAPTVUPT, "version"], capture_output=True, text=True, timeout=5)
|
||||
if r.returncode == 0:
|
||||
full = r.stdout.strip()
|
||||
lines = full.split("\n")
|
||||
short = lines[0]
|
||||
m = _VERSION_RE.match(short)
|
||||
if m:
|
||||
number = m.group(1)
|
||||
except Exception:
|
||||
pass
|
||||
return short, number, full
|
||||
|
||||
ZUPT_VER_SHORT, ZUPT_VER_NUMBER, ZUPT_VER_FULL = _get_version()
|
||||
|
||||
# ── Find icon file ──
|
||||
def _find_icon():
|
||||
|
|
@ -144,7 +255,7 @@ def run_zupt(args, timeout=30):
|
|||
try:
|
||||
r = subprocess.run([ZUPT]+list(args), capture_output=True, text=True, timeout=timeout)
|
||||
return r.returncode, r.stdout, r.stderr
|
||||
except FileNotFoundError: return -1, "", f"zupt not found: {ZUPT}"
|
||||
except FileNotFoundError: return -1, "", f"vaptvupt not found: {VAPTVUPT}\n\nDiscovery log:\n" + "\n".join(_DISCOVERY_LOG[-10:])
|
||||
except subprocess.TimeoutExpired: return -1, "", "Timed out"
|
||||
|
||||
class Worker(QObject):
|
||||
|
|
@ -152,7 +263,7 @@ class Worker(QObject):
|
|||
log = Signal(str)
|
||||
def __init__(self, args): super().__init__(); self.args = args
|
||||
def run(self):
|
||||
self.log.emit(f"$ zupt {' '.join(self.args)}")
|
||||
self.log.emit(f"$ {Path(VAPTVUPT).name} {' '.join(self.args)}")
|
||||
try:
|
||||
proc = subprocess.Popen([ZUPT]+self.args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
err_lines = []
|
||||
|
|
@ -161,7 +272,7 @@ class Worker(QObject):
|
|||
if line: err_lines.append(line); self.log.emit(line)
|
||||
stdout, _ = proc.communicate(timeout=7200)
|
||||
self.done.emit(proc.returncode, stdout or "", "\n".join(err_lines))
|
||||
except FileNotFoundError: self.done.emit(-1, "", f"zupt not found: {ZUPT}")
|
||||
except FileNotFoundError: self.done.emit(-1, "", f"vaptvupt not found: {VAPTVUPT}")
|
||||
except subprocess.TimeoutExpired: proc.kill(); self.done.emit(-1, "", "Timed out")
|
||||
|
||||
# ── Widgets ──
|
||||
|
|
@ -306,7 +417,7 @@ class KeysTab(QWidget):
|
|||
def _export(self):
|
||||
priv = self.exp_priv.path()
|
||||
pub = self.exp_pub.path()
|
||||
if not priv: QMessageBox.warning(self, "Zupt", "Select the private key file."); return
|
||||
if not priv: QMessageBox.warning(self, "VaptVupt", "Select the private key file."); return
|
||||
if not pub:
|
||||
pub = priv.rsplit(".", 1)[0] + "_public.key" if "." in priv else priv + ".pub"
|
||||
self.exp_pub.edit.setText(pub)
|
||||
|
|
@ -331,7 +442,7 @@ class CompressTab(QWidget):
|
|||
v.addWidget(H("Source files / directory"))
|
||||
self.src = PathField("Drop files here or browse", "multi"); v.addWidget(self.src)
|
||||
v.addWidget(H("Output archive"))
|
||||
self.dst = PathField("e.g. backup.zupt", "save", "Zupt (*.zupt);;All (*)"); v.addWidget(self.dst)
|
||||
self.dst = PathField("e.g. backup.zupt", "save", "VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.dst)
|
||||
row = QHBoxLayout(); row.setSpacing(16)
|
||||
for label, widget in [("Codec", self._mk_codec()), ("Level", self._mk_level())]:
|
||||
c = QVBoxLayout(); c.addWidget(H(label)); c.addWidget(widget); row.addLayout(c)
|
||||
|
|
@ -361,7 +472,7 @@ class CompressTab(QWidget):
|
|||
|
||||
def _run(self):
|
||||
srcs = self.src.paths()
|
||||
if not srcs or not srcs[0]: QMessageBox.warning(self, "Zupt", "Select files."); return
|
||||
if not srcs or not srcs[0]: QMessageBox.warning(self, "VaptVupt", "Select files."); return
|
||||
dst = self.dst.path() or srcs[0] + ".zupt"; self.dst.edit.setText(dst)
|
||||
cmd = ["compress", "-l", str(self.level.value())]
|
||||
cm = {"AUTO": None, "VaptVupt": "--vv", "LZHP": "--lzhp", "Store": "-s"}
|
||||
|
|
@ -384,7 +495,7 @@ class ExtractTab(QWidget):
|
|||
v = QVBoxLayout(inner); v.setContentsMargins(24,24,24,24); v.setSpacing(10)
|
||||
v.addWidget(QLabel("Extract and decrypt a .zupt archive."))
|
||||
v.addWidget(Sep())
|
||||
v.addWidget(H("Archive")); self.arc = PathField("Drop .zupt here", filters="Zupt (*.zupt);;All (*)"); v.addWidget(self.arc)
|
||||
v.addWidget(H("Archive")); self.arc = PathField("Drop .zupt here", filters="VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.arc)
|
||||
v.addWidget(H("Output directory")); self.out = PathField("Same as archive", "dir"); v.addWidget(self.out)
|
||||
enc = QHBoxLayout(); enc.setSpacing(16)
|
||||
pw = QVBoxLayout(); pw.addWidget(H("Password")); self.pw = PwField(); pw.addWidget(self.pw); enc.addLayout(pw)
|
||||
|
|
@ -402,7 +513,7 @@ class ExtractTab(QWidget):
|
|||
|
||||
def _run(self):
|
||||
arc = self.arc.path()
|
||||
if not arc: QMessageBox.warning(self, "Zupt", "Select an archive."); return
|
||||
if not arc: QMessageBox.warning(self, "VaptVupt", "Select an archive."); return
|
||||
cmd = ["extract"]
|
||||
if self.out.path(): cmd += ["-o", self.out.path()]
|
||||
if self.pw.text(): cmd += ["-p", self.pw.text()]
|
||||
|
|
@ -424,14 +535,14 @@ class VerifyTab(QWidget):
|
|||
v.addWidget(QLabel("Verify checksums or inspect archive metadata."))
|
||||
v.addWidget(Sep())
|
||||
v.addWidget(H("Verify integrity"))
|
||||
self.varc = PathField("Archive to verify", filters="Zupt (*.zupt);;All (*)"); v.addWidget(self.varc)
|
||||
self.varc = PathField("Archive to verify", filters="VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.varc)
|
||||
v.addWidget(H("Password (if encrypted)"))
|
||||
self.vpw = PwField("Leave empty if not encrypted"); v.addWidget(self.vpw)
|
||||
self.vbtn = QPushButton("Verify"); self.vbtn.setObjectName("amber"); self.vbtn.clicked.connect(self._verify); v.addWidget(self.vbtn)
|
||||
self.vlog = Log(120); v.addWidget(self.vlog)
|
||||
v.addWidget(Sep())
|
||||
v.addWidget(H("Archive info (no password needed)"))
|
||||
self.iarc = PathField("Archive to inspect", filters="Zupt (*.zupt);;All (*)"); v.addWidget(self.iarc)
|
||||
self.iarc = PathField("Archive to inspect", filters="VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.iarc)
|
||||
self.ibtn = QPushButton("Show Info"); self.ibtn.clicked.connect(self._info); v.addWidget(self.ibtn)
|
||||
self.ilog = Log(140); v.addWidget(self.ilog); v.addStretch()
|
||||
lay = QVBoxLayout(self); lay.setContentsMargins(0,0,0,0); lay.addWidget(scrollable(inner))
|
||||
|
|
@ -465,7 +576,7 @@ class DiskTab(QWidget):
|
|||
v.addWidget(H("Backup — source device or image"))
|
||||
self.bsrc = PathField("/dev/sdX or disk.img"); v.addWidget(self.bsrc)
|
||||
v.addWidget(H("Backup — output archive"))
|
||||
self.bout = PathField("backup.zupt", "save", "Zupt (*.zupt);;All (*)"); v.addWidget(self.bout)
|
||||
self.bout = PathField("backup.zupt", "save", "VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.bout)
|
||||
bopt = QHBoxLayout(); bopt.setSpacing(16)
|
||||
oc = QVBoxLayout(); oc.addWidget(H("Options")); self.bdedup = QCheckBox("Block deduplication"); oc.addWidget(self.bdedup); bopt.addLayout(oc)
|
||||
pc = QVBoxLayout(); pc.addWidget(H("Password")); self.bpw = PwField("Optional — AES-256"); pc.addWidget(self.bpw); bopt.addLayout(pc)
|
||||
|
|
@ -474,7 +585,7 @@ class DiskTab(QWidget):
|
|||
self.blog = Log(100); v.addWidget(self.blog)
|
||||
v.addWidget(Sep())
|
||||
v.addWidget(H("Restore — archive"))
|
||||
self.rarc = PathField("backup.zupt", filters="Zupt (*.zupt);;All (*)"); v.addWidget(self.rarc)
|
||||
self.rarc = PathField("backup.zupt", filters="VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.rarc)
|
||||
v.addWidget(H("Restore — target device or file"))
|
||||
self.rtgt = PathField("/dev/sdX or output.img", "save"); v.addWidget(self.rtgt)
|
||||
v.addWidget(H("Restore — password"))
|
||||
|
|
@ -485,7 +596,7 @@ class DiskTab(QWidget):
|
|||
|
||||
def _backup(self):
|
||||
s, o = self.bsrc.path(), self.bout.path()
|
||||
if not s or not o: QMessageBox.warning(self, "Zupt", "Set source and output."); return
|
||||
if not s or not o: QMessageBox.warning(self, "VaptVupt", "Set source and output."); return
|
||||
cmd = ["disk", "backup"]
|
||||
if self.bdedup.isChecked(): cmd.append("--dedup")
|
||||
if self.bpw.text(): cmd += ["-p", self.bpw.text()]
|
||||
|
|
@ -493,7 +604,7 @@ class DiskTab(QWidget):
|
|||
|
||||
def _restore(self):
|
||||
a, t = self.rarc.path(), self.rtgt.path()
|
||||
if not a or not t: QMessageBox.warning(self, "Zupt", "Set archive and target."); return
|
||||
if not a or not t: QMessageBox.warning(self, "VaptVupt", "Set archive and target."); return
|
||||
SB = QMessageBox.StandardButton
|
||||
if QMessageBox.warning(self, "Confirm", f"OVERWRITE {t}?", SB.Yes|SB.Cancel) != SB.Yes: return
|
||||
cmd = ["disk", "restore"]
|
||||
|
|
@ -506,33 +617,43 @@ class AboutTab(QWidget):
|
|||
super().__init__()
|
||||
inner = QWidget()
|
||||
v = QVBoxLayout(inner); v.setContentsMargins(24,24,24,24); v.setSpacing(4)
|
||||
# Extract version number from cached string
|
||||
ver_num = ZUPT_VER_SHORT.replace("zupt ", "").strip() if "zupt " in ZUPT_VER_SHORT else ZUPT_VER_SHORT
|
||||
for text, style in [
|
||||
("ZUPT", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||
(ver_num, "color:white;font-size:28px;font-weight:800;font-family:monospace;"),
|
||||
("VAPTVUPT", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||
(ZUPT_VER_NUMBER, "color:white;font-size:28px;font-weight:800;font-family:monospace;"),
|
||||
("", ""),
|
||||
("Post-quantum backup compression with ML-KEM-768 + X25519", "color:#6a8898;font-size:13px;"),
|
||||
("hybrid encryption, hardware-adaptive codecs, and block dedup.", "color:#6a8898;font-size:13px;"),
|
||||
("hybrid encryption, Argon2id KDF, and block deduplication.", "color:#6a8898;font-size:13px;"),
|
||||
("Renamed from Zupt in v3.0.0 (INPI Brasil trademark); .zupt", "color:#6a8898;font-size:13px;"),
|
||||
("archive extension and v1.6 wire format are unchanged.", "color:#6a8898;font-size:13px;"),
|
||||
("", ""),
|
||||
("CRYPTOGRAPHIC STACK", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||
("ML-KEM-768 FIPS 203 Post-Quantum KEM", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("X25519 RFC 7748 Elliptic Curve DH", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("X25519 RFC 7748 Elliptic Curve DH (hybrid w/ ML-KEM)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("AES-256-CTR FIPS 197 Symmetric Cipher", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("HMAC-SHA256 RFC 2104 Authentication", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("PBKDF2 RFC 8018 Key Derivation", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("HMAC-SHA256 RFC 2104 Authentication (Encrypt-then-MAC)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("Argon2id RFC 9106 Password KDF (default since 2.4.1)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("PBKDF2 RFC 8018 Password KDF (legacy; --kdf pbkdf2)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("HKDF RFC 5869 Key Derivation Function", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("SHA3/SHAKE FIPS 202 Hash / XOF", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("XXH64 (non-crypto) Per-block checksum (inside AEAD)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("", ""),
|
||||
("COMPRESSION CODEC", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||
("VaptVupt LZ + ANS 2.48.5 LZ77 + tabled ANS entropy", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("AVX2 / NEON SIMD acceleration; 1.27x zstd-3 decode aggregate", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("", ""),
|
||||
("CREDITS", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||
("zupt Cristian Cezar Moises MIT", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("git.securityops.co/cristiancmoises/zupt", "color:#3a5868;font-size:11px;font-family:monospace;"),
|
||||
("VaptVupt application Cristian Cezar Moisés", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
(" License: AGPL-3.0-or-later (commercial license available)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
(" git.securityops.co/cristiancmoises/zupt", "color:#3a5868;font-size:11px;font-family:monospace;"),
|
||||
("", ""),
|
||||
("zupt Cristian Cezar Moises AGPL-3.0+", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("git.securityops.co/cristiancmoises/zupt", "color:#3a5868;font-size:11px;font-family:monospace;"),
|
||||
("VaptVupt LZ + ANS codec Cristian Cezar Moisés", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
(" License: GPL-3.0-or-later (commercial license available)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
(" git.securityops.co/cristiancmoises/vaptvupt", "color:#3a5868;font-size:11px;font-family:monospace;"),
|
||||
("", ""),
|
||||
("WEBSITE", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||
("WEBSITE & CONTACT", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||
("https://zupt.securityops.co", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("zupt@riseup.net", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("sac@securityops.co (commercial licensing)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("zupt@riseup.net (general / bugs)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||
("", ""),
|
||||
(ZUPT_VER_SHORT, "color:#3a5868;font-size:11px;font-family:monospace;"),
|
||||
]:
|
||||
|
|
@ -549,7 +670,7 @@ class AboutTab(QWidget):
|
|||
class ZuptWindow(QMainWindow):
|
||||
def __init__(self, compress_files=None, extract_file=None):
|
||||
super().__init__()
|
||||
self.setWindowTitle(f"Zupt — {ZUPT_VER_SHORT}")
|
||||
self.setWindowTitle(f"VaptVupt {ZUPT_VER_NUMBER}")
|
||||
self.setMinimumSize(720, 500)
|
||||
self.resize(880, 640)
|
||||
self.setAcceptDrops(True)
|
||||
|
|
@ -564,12 +685,11 @@ class ZuptWindow(QMainWindow):
|
|||
# Header
|
||||
hdr = QFrame(); hdr.setStyleSheet("background:#050a0e;border-bottom:1px solid #1a2a30;")
|
||||
hl = QHBoxLayout(hdr); hl.setContentsMargins(20,10,20,10)
|
||||
title = QLabel("ZUPT"); title.setStyleSheet("color:white;font-size:15px;font-weight:800;letter-spacing:3px;")
|
||||
title = QLabel("VAPTVUPT"); title.setStyleSheet("color:white;font-size:15px;font-weight:800;letter-spacing:3px;")
|
||||
hl.addWidget(title)
|
||||
sub = QLabel("Post-Quantum Backup"); sub.setStyleSheet("color:#3a5868;font-size:10px;font-weight:600;letter-spacing:1px;margin-left:8px;")
|
||||
hl.addWidget(sub); hl.addStretch()
|
||||
ver_num = ZUPT_VER_SHORT.replace("zupt ", "v").strip()
|
||||
vl = QLabel(ver_num); vl.setStyleSheet("color:#3a5868;font-size:10px;font-family:monospace;background:#0a1018;padding:3px 10px;border-radius:4px;border:1px solid #1a2a30;")
|
||||
vl = QLabel(f"v{ZUPT_VER_NUMBER}"); vl.setStyleSheet("color:#3a5868;font-size:10px;font-family:monospace;background:#0a1018;padding:3px 10px;border-radius:4px;border:1px solid #1a2a30;")
|
||||
hl.addWidget(vl)
|
||||
layout.addWidget(hdr)
|
||||
|
||||
|
|
@ -588,7 +708,7 @@ class ZuptWindow(QMainWindow):
|
|||
layout.addWidget(self.tabs)
|
||||
|
||||
sb = QStatusBar()
|
||||
sb.showMessage(f"{ZUPT_VER_SHORT} | {ZUPT}")
|
||||
sb.showMessage(f"VaptVupt {ZUPT_VER_NUMBER} | {VAPTVUPT}")
|
||||
self.setStatusBar(sb)
|
||||
|
||||
def dragEnterEvent(self, e):
|
||||
|
|
@ -612,7 +732,7 @@ def main():
|
|||
else: compress_files = args
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("Zupt")
|
||||
app.setApplicationName("VaptVupt")
|
||||
if ICON_PATH: app.setWindowIcon(QIcon(ICON_PATH))
|
||||
app.setStyle("Fusion")
|
||||
app.setStyleSheet(STYLE)
|
||||
|
|
|
|||
Loading…
Reference in a new issue