Compare commits

...
Author SHA1 Message Date
Cristian Cezar Moisés
f8844f806a docs: record audited 5.2.8 release evidence
Some checks are pending
CI / Source-only, license, shell and secret policy (push) Waiting to run
CI / Build and full tests (clang) (push) Blocked by required conditions
CI / Build and full tests (gcc) (push) Blocked by required conditions
CI / Strict warnings (clang) (push) Blocked by required conditions
CI / Strict warnings (gcc) (push) Blocked by required conditions
CI / ASan, LSan and UBSan (push) Blocked by required conditions
CI / GCC static analyzer (push) Blocked by required conditions
CI / Reproducible audited source archive (push) Blocked by required conditions
CI / Debian/Ubuntu source-built package (push) Blocked by required conditions
CI / openSUSE Tumbleweed x86_64 RPM gate (push) Blocked by required conditions
CI / Fedora noarch GUI RPM and SRPM gate (push) Blocked by required conditions
CI / Linux x86_64 notice-bearing CLI tar.xz gate (push) Blocked by required conditions
CI / Source-only GUI portable ZIP gate (push) Blocked by required conditions
CI / Windows and macOS release gates (push) Blocked by required conditions
CI / Windows x86_64 package and smoke test (push) Blocked by required conditions
CI / macOS native DMG and installed-image test (push) Blocked by required conditions
2026-08-31 22:28:57 -03:00
Cristian Cezar Moisés
33eb90454d packaging: validate genuine source RPM metadata
Some checks failed
CI / Source-only, license, shell and secret policy (push) Has been cancelled
CI / Build and full tests (clang) (push) Has been cancelled
CI / Build and full tests (gcc) (push) Has been cancelled
CI / Strict warnings (clang) (push) Has been cancelled
CI / Strict warnings (gcc) (push) Has been cancelled
CI / ASan, LSan and UBSan (push) Has been cancelled
CI / GCC static analyzer (push) Has been cancelled
CI / Reproducible audited source archive (push) Has been cancelled
CI / Debian/Ubuntu source-built package (push) Has been cancelled
CI / openSUSE Tumbleweed x86_64 RPM gate (push) Has been cancelled
CI / Fedora noarch GUI RPM and SRPM gate (push) Has been cancelled
CI / Linux x86_64 notice-bearing CLI tar.xz gate (push) Has been cancelled
CI / Source-only GUI portable ZIP gate (push) Has been cancelled
CI / Windows and macOS release gates (push) Has been cancelled
CI / Windows x86_64 package and smoke test (push) Has been cancelled
CI / macOS native DMG and installed-image test (push) Has been cancelled
2026-08-31 22:12:31 -03:00
Cristian Cezar Moisés
ebb9ab3aa1 packaging: repin canonical 5.2.8 archive
Some checks failed
CI / Source-only, license, shell and secret policy (push) Has been cancelled
CI / Build and full tests (clang) (push) Has been cancelled
CI / Build and full tests (gcc) (push) Has been cancelled
CI / Strict warnings (clang) (push) Has been cancelled
CI / Strict warnings (gcc) (push) Has been cancelled
CI / ASan, LSan and UBSan (push) Has been cancelled
CI / GCC static analyzer (push) Has been cancelled
CI / Reproducible audited source archive (push) Has been cancelled
CI / Debian/Ubuntu source-built package (push) Has been cancelled
CI / openSUSE Tumbleweed x86_64 RPM gate (push) Has been cancelled
CI / Fedora noarch GUI RPM and SRPM gate (push) Has been cancelled
CI / Linux x86_64 notice-bearing CLI tar.xz gate (push) Has been cancelled
CI / Source-only GUI portable ZIP gate (push) Has been cancelled
CI / Windows and macOS release gates (push) Has been cancelled
CI / Windows x86_64 package and smoke test (push) Has been cancelled
CI / macOS native DMG and installed-image test (push) Has been cancelled
2026-08-31 21:37:11 -03:00
Cristian Cezar Moisés
f6bbb95eee docs: use canonical release epoch 2026-08-31 21:36:27 -03:00
Cristian Cezar Moisés
4741d34783 packaging: pin audited 5.2.8 source archive 2026-08-31 21:26:32 -03:00
Cristian Cezar Moisés
a6e330827e docs: record final security gate evidence 2026-08-31 21:25:16 -03:00
Cristian Cezar Moisés
6fd78e0515 test: make Windows Unicode gate byte-exact 2026-08-31 21:25:10 -03:00
Cristian Cezar Moisés
cb60cf0218 packaging: pin final 5.2.8 source archive 2026-08-31 20:56:23 -03:00
Cristian Cezar Moisés
af87e67a87 docs: finalize 5.2.8 checksum record 2026-08-31 20:55:32 -03:00
Cristian Cezar Moisés
7a8e5c5fbd packaging: refresh 5.2.8 source checksums
Some checks failed
CI / Source-only, license, shell and secret policy (push) Has been cancelled
CI / Build and full tests (clang) (push) Has been cancelled
CI / Build and full tests (gcc) (push) Has been cancelled
CI / Strict warnings (clang) (push) Has been cancelled
CI / Strict warnings (gcc) (push) Has been cancelled
CI / ASan, LSan and UBSan (push) Has been cancelled
CI / GCC static analyzer (push) Has been cancelled
CI / Reproducible audited source archive (push) Has been cancelled
CI / Debian/Ubuntu source-built package (push) Has been cancelled
CI / openSUSE Tumbleweed x86_64 RPM gate (push) Has been cancelled
CI / Fedora noarch GUI RPM and SRPM gate (push) Has been cancelled
CI / Linux x86_64 notice-bearing CLI tar.xz gate (push) Has been cancelled
CI / Source-only GUI portable ZIP gate (push) Has been cancelled
CI / Windows and macOS release gates (push) Has been cancelled
CI / Windows x86_64 package and smoke test (push) Has been cancelled
CI / macOS native DMG and installed-image test (push) Has been cancelled
2026-08-31 20:52:54 -03:00
Cristian Cezar Moisés
65bc5bd427 test: close follow-up security gate findings 2026-08-31 20:51:59 -03:00
Cristian Cezar Moisés
69fc26bfec packaging: pin 5.2.8 source archive checksums
Some checks failed
CI / Source-only, license, shell and secret policy (push) Has been cancelled
CI / Build and full tests (clang) (push) Has been cancelled
CI / Build and full tests (gcc) (push) Has been cancelled
CI / Strict warnings (clang) (push) Has been cancelled
CI / Strict warnings (gcc) (push) Has been cancelled
CI / ASan, LSan and UBSan (push) Has been cancelled
CI / GCC static analyzer (push) Has been cancelled
CI / Reproducible audited source archive (push) Has been cancelled
CI / Debian/Ubuntu source-built package (push) Has been cancelled
CI / openSUSE Tumbleweed x86_64 RPM gate (push) Has been cancelled
CI / Fedora noarch GUI RPM and SRPM gate (push) Has been cancelled
CI / Linux x86_64 notice-bearing CLI tar.xz gate (push) Has been cancelled
CI / Source-only GUI portable ZIP gate (push) Has been cancelled
CI / Windows and macOS release gates (push) Has been cancelled
CI / Windows x86_64 package and smoke test (push) Has been cancelled
CI / macOS native DMG and installed-image test (push) Has been cancelled
2026-08-31 20:33:52 -03:00
Cristian Cezar Moisés
7687cfa577 release: prepare ZUPT 5.2.8 2026-08-31 20:32:42 -03:00
Cristian Cezar Moisés
d8668e6d64 security: close filesystem race findings 2026-08-31 20:27:14 -03:00
Cristian Cezar Moisés
8cca841258 packaging: pin 5.2.7 source archive checksums [skip ci] 2026-08-31 19:07:55 -03:00
Cristian Cezar Moisés
cb794e100c release: prepare ZUPT 5.2.7 native test gates 2026-08-31 19:05:55 -03:00
Cristian Cezar Moisés
6d9f740a2b packaging: pin 5.2.6 source archive checksums [skip ci] 2026-08-31 18:13:54 -03:00
Cristian Cezar Moisés
7821523942 release: prepare ZUPT 5.2.6 native gates 2026-08-31 18:12:27 -03:00
Cristian Cezar Moisés
0fb13c2b49 packaging: pin 5.2.5 source archive checksums [skip ci] 2026-08-31 17:03:42 -03:00
Cristian Cezar Moisés
fcce1b5a4a release: correct OBS harness for ZUPT 5.2.5 2026-08-31 17:02:39 -03:00
Cristian Cezar Moisés
c27e3d5528 packaging: refresh 5.2.4 source checksums [skip ci] 2026-08-31 16:23:53 -03:00
Cristian Cezar Moisés
353b649bec docs: correct historical ZUPT release labels 2026-08-31 16:22:40 -03:00
Cristian Cezar Moisés
70f5233888 packaging: pin 5.2.4 source archive checksums [skip ci] 2026-08-31 16:11:06 -03:00
Cristian Cezar Moisés
cdc08870de release: correct CRLF policy gate for ZUPT 5.2.4 2026-08-31 16:03:25 -03:00
Cristian Cezar Moisés
f623205f45 packaging: pin 5.2.3 source archive checksums [skip ci] 2026-08-31 15:41:34 -03:00
Cristian Cezar Moisés
09dc804b3e release: correct ZUPT 5.2.3 integration 2026-08-31 15:33:17 -03:00
Cristian Cezar Moisés
8db676568b chore: reconcile GitHub ZUPT history
Some checks failed
CI / Source-only, license, shell and secret policy (push) Has been cancelled
CI / Build and full tests (clang) (push) Has been cancelled
CI / Build and full tests (gcc) (push) Has been cancelled
CI / Strict warnings (clang) (push) Has been cancelled
CI / Strict warnings (gcc) (push) Has been cancelled
CI / ASan, LSan and UBSan (push) Has been cancelled
CI / GCC static analyzer (push) Has been cancelled
CI / Reproducible audited source archive (push) Has been cancelled
CI / Debian/Ubuntu source-built package (push) Has been cancelled
CI / openSUSE Tumbleweed x86_64 RPM gate (push) Has been cancelled
CI / Fedora noarch GUI RPM and SRPM gate (push) Has been cancelled
CI / Linux x86_64 notice-bearing CLI tar.xz gate (push) Has been cancelled
CI / Source-only GUI portable ZIP gate (push) Has been cancelled
CI / Windows and macOS release gates (push) Has been cancelled
CI / Windows x86_64 package and smoke test (push) Has been cancelled
CI / macOS native DMG and installed-image test (push) Has been cancelled
2026-08-31 14:32:41 -03:00
Cristian Cezar Moisés
85c1366b58 packaging: pin 5.2.2 source archive checksums 2026-08-31 14:31:18 -03:00
Cristian Cezar Moisés
0a9a63db66 docs: record 5.2.2 release audit results 2026-08-31 14:28:42 -03:00
Cristian Cezar Moisés
ff99770bd0 release: restore ZUPT and harden source-only 5.2.2 2026-08-31 14:14:36 -03:00
Cristian Cezar Moisés
5febc89764
Update README.md 2026-08-16 09:29:30 -03:00
Cristian Cezar Moisés
74e393ba3e docs: define VaptVupt commercial-license boundary 2026-08-11 12:02:32 -03:00
Cristian Cezar Moisés
3f89719056 v5.2.1: robust GUI Verify/Extract + refreshed comparison & audit tables
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
cross-platform / windows (push) Has been cancelled
cross-platform / macos (push) Has been cancelled
cross-platform / portable (push) Has been cancelled
Version bump to 5.2.1. Docs: 'What's new in 5.2.1', a refreshed Compression
comparison section (ratio + throughput vs zstd/gzip/lz4 on codec 2.65.3, all
round-trips byte-exact) and a new Audit status table (make check 16/16, FIPS 203
3/3, the full security regression matrix). Packaging versions bumped. (The GUI
Verify/Extract code fix is commit 2512844.)
2026-07-12 15:42:57 -03:00
Cristian Cezar Moisés
2512844bc4 gui: robust Verify (+Extract) — auto-detect encryption, guide, run async
Reported: verification errors in the GUI. The Verify tab made the user pick a
PQ mode from a dropdown and remember the private key; a wrong pick or a missing
credential produced a raw CLI decrypt-error dump ("Error: Archive uses
post-quantum encryption. Use --pq" / "Authentication failed"), and it ran the
verify SYNCHRONOUSLY on the GUI thread (freezing the window on a large archive).

- New _detect_archive_enc(): reads the archive header via `info` (no credential)
  and returns none/password/pq/pqonly/sdk + a human label.
- Verify now auto-detects the encryption, uses the MATCHING decrypt flag (a
  wrong-mode mismatch is impossible), and if the needed credential is missing
  shows a clear message ("This archive is password-encrypted. Enter the password
  above, then click Verify again." / "...select the matching private key...")
  instead of a raw error. Removed the now-redundant Verify PQ-mode dropdown.
- Verify runs through the async _Job (own progress bar) so it never freezes;
  run_async gained ok_msg/fail_msg so it prints "All checksums passed." /
  "Verification failed.".
- Same auto-detect + missing-credential guidance applied to Extract (it had the
  same footgun); its message on wrong creds is now "Extraction failed.".

Verified on real X (thread-safe instrumentation, no cross-thread access): plain/
password/hybrid/pq-only verify pass; missing password and missing PQ key each
give guidance; wrong password/key and a non-archive fail cleanly; extract flows
byte-exact.
2026-07-12 15:36:39 -03:00
Cristian Cezar Moisés
fce2522aad v5.2.0: version bump + docs (GUI crash fix, codec 2.65.3, libvuptsdk)
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
cross-platform / windows (push) Has been cancelled
cross-platform / macos (push) Has been cancelled
cross-platform / portable (push) Has been cancelled
CHANGELOG 5.2.0 entry; README 'What's new in 5.2.0' + version-history row +
download-table version refs; bump ZUPT_VERSION_STRING and the rpm/opensuse/
homebrew/guix packaging versions to 5.2.0.
2026-07-12 14:50:16 -03:00
Cristian Cezar Moisés
51fa068f50 sdk: integrate libvuptsdk (renamed libzuptsdk); decouple --pq-box
libvuptsdk (git.securityops.co/cristiancmoises/libvuptsdk) is the renamed
libzuptsdk: only the .so filename/SONAME changed (libzuptsdk.so.2 ->
libvuptsdk.so.2); the C API (zuptsdk_* symbols, zuptsdk.h) is unchanged.

- Rename vendor/zuptsdk -> vendor/vuptsdk with libvuptsdk's headers.
- Makefile WITH_SDK=1 now links -lvuptsdk (+ its transitive libcrypto/libargon2
  deps via SDK_DEPLIBS) instead of -lzuptsdk, and installs libvuptsdk.so.*.
- DECOUPLE --pq-box: it needs the SEPARATE libpqvaptvupt, which libvuptsdk does
  NOT provide, so gate it behind a new WITH_PQBOX=1 (was folded into WITH_SDK).
  zupt_crypto_pqbox.c now keys on ZUPT_WITH_PQBOX; WITH_SDK=1 alone builds and
  links cleanly with just libvuptsdk and enables --pq-sdk + Argon2id.
- Banner/help renamed libzuptsdk -> libvuptsdk; the machine-readable 'Build:'
  line lists --pq-box only under WITH_PQBOX. GUI _get_caps matches on 'vuptsdk'.

Validated on Guix: WITH_SDK=1 links libvuptsdk + libcrypto + libargon2, runs,
banner 'Build: full (libvuptsdk: Argon2id, --pq-sdk available)', keygen --sdk +
--pq-sdk encrypt/decrypt byte-exact roundtrip. Default source-only build
unchanged (make check 16/16).
2026-07-12 14:47:40 -03:00
Cristian Cezar Moisés
59f9ebc59e codec: update vendored VaptVupt codec 2.65.0 -> 2.65.3
From vaptvupt-codec tag v2.65.3. Output is byte-identical to 2.65.0 (same
ratio, wire format v1.6 unchanged) but extreme-mode encode is ~1.6-2x faster
(Sprint 132 optimal-parser speedup) and the extreme prepass window allocation
is capped at wlog=20 = 8 MiB virtual instead of up to 128 MiB (Sprint 133
memory hygiene). Our AVX2 offset-read decoder guard is now UPSTREAM (dropped
from the local patch set); the ANS safe-zone 2*SAFEZONE_MAX_RUN reserve is
re-applied on top (still not upstream). make check 16/16, KAT 16/16, cross-
version roundtrip with 5.1.0 archives verified.
2026-07-12 14:47:40 -03:00
Cristian Cezar Moisés
df534503ec gui: fix cross-thread widget access that crashed compress (app closes / corrupts)
Reported: files corrupt, compress does nothing on hybrid, app closes on
full-PQ compress. Root cause: run_async connected plain Python CLOSURES
(finish / on_pct / release) to signals emitted from the worker QThread.
PySide6 runs a plain-closure slot in the EMITTING thread regardless of the
requested connection type — even an explicit Qt.QueuedConnection — because a
bare functor has no receiver QObject to give it GUI-thread affinity (verified:
such a slot ran on the worker thread; a bound method of a main-thread QObject
ran on MAIN). Those closures then called QProgressBar.setValue/setRange/hide,
QPushButton.setEnabled and QTextEdit.append from the worker thread: cross-thread
QWidget access is undefined behaviour and crashed the app under real X11/Wayland
rendering. It only survived offscreen tests (which tolerate the race), which is
why prior driver runs passed. The progress-bar work added in 5.1.0 multiplied
the cross-thread calls and made the crash reliable; a crash mid-compress also
left truncated/corrupt archives.

Fix: a _Job(QObject) controller parented to a GUI-thread widget, so it lives in
the GUI thread and every slot (on_log/on_pct/on_done/on_finished) is a bound
method that Qt auto-marshals to the GUI thread. closeEvent updated to the new
_jobs (list of _Job) shape.

Verified on real X (window shown, progress bar rendering) with a QProgressBar/
QPushButton instrumentation that flags ANY worker-thread call: BEFORE = setRange/
setValue/hide flagged on the worker thread; AFTER = zero cross-thread calls, and
hybrid + full-PQ + password compress/extract all byte-exact round-trip; Verify/
Info/Disk backup+restore/two-concurrent-jobs/close-mid-job all pass.
2026-07-12 08:54:17 -03:00
Cristian Cezar Moisés
7c062bc92c v5.1.0: docs, comparison tables, packaging version bump
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
cross-platform / windows (push) Has been cancelled
cross-platform / macos (push) Has been cancelled
cross-platform / portable (push) Has been cancelled
README 'What's new in 5.1.0' + a new 'Compression comparison' section with
measured ratio/throughput tables vs zstd/gzip/lz4 and a 5.0.0->5.1.0 delta
table; fix the stale codec-internals section (2.60.4 -> 2.65.0, and the
now-wrong 'wrapper forces format_v2' paragraph). CHANGELOG 5.1.0 entry.
Bump Version: in the rpm/opensuse specs (+changelog), the homebrew formula
url/version, the guix package version, and the gui README to 5.1.0.
2026-07-11 22:53:44 -03:00
Cristian Cezar Moisés
c42513e0db gui: parse CLI progress frames so compress no longer looks stuck
The CLI paints progress as carriage-return frames ("\r  path [###] 42%", no
newline until 100%). Worker.run read the child line-by-line, so it emitted
NOTHING for the whole job and the GUI looked frozen on any file bigger than one
block — reported as 'I can't compress, the app stucks'. The worker now reads
with read1() and splits on both \n and \r, parsing the percentage out of the
progress frames and driving the QProgressBar (indeterminate until the first %).
Also run the child with stdin=/dev/null (a prompt on inherited stdin would block
forever) and merge stderr into stdout so a filling second pipe can't deadlock.
2026-07-11 22:53:44 -03:00
Cristian Cezar Moisés
a2350dd0f6 codec: vendor VaptVupt 2.65.0 + fix two ratio-crippling wrapper defaults
Vendor codec 2.65.0 (from vaptvupt-codec tag v2.65.0), re-applying the two
in-tree audit patches on top: the ANS decode safe-zone 2*SAFEZONE_MAX_RUN
reserve (heap-overflow guard, not yet upstream) and the AVX2 offset-read
bound in vv_decoder.c.

Two settings in the integration layer were leaving most of the codec's ratio
on the table:

  * vaptvupt_api.c forced opts.format_v2=1 for balanced+extreme. Since codec
    v2.61.0 that routes text through the binary/greedy path and HALVES the
    extreme-mode text ratio (codec-level 7.6x -> 3.7x). The codec auto-enables
    format_v2 for binary-detected input on its own, so stop forcing it: text
    keeps the optimal parser, binary still gets v2.

  * auto_block_size() capped the extreme block at 512 KiB. The block IS the
    codec's LZ window, so the 'large-window extreme' parser could never match
    past 512 KiB. Scale block size with level (128 KiB fast -> 8 MiB extreme).
    Because block size also sets --dedup granularity (a large block rarely
    finds a byte-exact duplicate), --dedup now overrides to a small 256 KiB
    block so block-level dedup still works.

Measured, level 9 extreme: text 3.77x->5.98x (+58%), logs 7.21x->9.07x (+26%),
json 8.25x->9.38x, source 4.93x->5.63x. Wire format unchanged (v1.6); 5.0.0
and 5.1.0 archives interoperate both directions (verified, all modes). Bump
ZUPT_CODEC_RELEASE to 2.65.0 and ZUPT_VERSION_STRING to 5.1.0.
2026-07-11 22:53:44 -03:00
Cristian Cezar Moisés
df232f25b1 gui: fix crash on every compress/extract job finish (QThread GC abort)
The app aborted ('QThread: Destroyed while thread is still running')
whenever an async job completed — reported as 'the software closes
automatically after selecting files and adding my key'. Reproduced
deterministically with a headless flow driver: run_async's finish()
dropped the (QThread, Worker) refs from parent._jobs right after
t.quit(), while the OS thread was still winding down; the next cyclic-GC
pass collected the live QThread wrapper and Qt aborted the process.

Fix: finish() (queued from Worker.done) now only re-enables the UI and
calls t.quit(); a release() slot connected to QThread.finished (queued)
does t.wait() and only then drops the refs — the thread is provably dead
before its wrapper can be collected.

Hardening from adversarial review of the fix:
- Worker.run: catch-all except -> done.emit(-1, ..., str(exc)) so no
  exception can strand a job with the button disabled forever (fatal
  under PyQt6); errors='replace' on the pipes so non-UTF-8 CLI output
  cannot raise mid-read.
- Worker.cancel() + _cancelled flag: kills the child CLI on window close
  and closes the cancel-before-Popen startup race.
- ZuptWindow.closeEvent: confirm 'Quit and abort it?' when jobs are
  running (a killed disk restore is destructive — never silent), then
  cancel + quit + wait(3000) each thread; if one cannot be joined,
  os._exit(0) instead of letting teardown abort.
- Drop dead _thread/_worker single-slot attrs from the pre-_jobs scheme.

Verified: full GUI function matrix (keygen hybrid/pq-only/export,
compress PQ-hybrid x3 / password / pq-only, extract all modes with
byte-identical round-trips, verify, info, two concurrent jobs, confirm-
close mid-job, instant close after start) — 16/16 PASS on offscreen and
xcb, no aborts; --selftest OK.
2026-07-11 19:56:30 -03:00
Cristian Cezar Moisés
95d9a8279b gui: XWayland fallback when the Wayland window is never mapped
On Sway 1.12 + Qt 6.9 (Guix, NVIDIA) Qt-Wayland deadlocks before mapping:
WAYLAND_DEBUG shows the client completes xdg_toplevel setup but never sends
the initial wl_surface.commit, so the compositor never sends configure and
the surface never maps — the event loop runs, the app prints its startup
notice, and no window ever appears. This reproduces with a bare PySide6
QLabel, so it is a toolkit/compositor bug, not ours; no Qt env knob
(fractional-scale disable, scale pinning) unblocks it, while the same
window maps instantly on XWayland.

Fix: a map watchdog on Wayland platforms. An event filter LATCHES the first
Expose on the toplevel QWindow (sampling isExposed() at a deadline would
misfire: a healthy hidden window — other workspace, scratchpad, locker —
reads unexposed ~100 ms after frame callbacks stop). If no expose ever
arrived after 4 s, re-exec the same process with QT_QPA_PLATFORM=xcb.
Safety rails: a sentinel env var (VAPTVUPT_XCB_FALLBACK_DONE) makes a
second fallback impossible even if '-platform wayland' argv (which outranks
the env override) brings the child up on Wayland again; execve failure is
caught and degrades to the no-fallback message; frozen bundles reuse argv
as-is (PyInstaller sets argv[0] to the exe); DISPLAY-unset systems just get
an honest notice; VAPTVUPT_NO_XCB_FALLBACK=1 opts out.

Verified live: wayland launch relaunches at 4 s and the window appears in
the sway tree (title 'VaptVupt 5.0.0', visible, tiled) — first time the GUI
is actually on screen on this machine; latch flips true where expose events
exist (offscreen); sentinel path stays wayland with no exec; selftest OK.
2026-07-11 11:42:47 -03:00
Cristian Cezar Moisés
92488d47b9 gui: fix Wayland SIGSEGV on launch; guard stderr for windowed bundles
e15329a's window-activation code crashed the GUI at startup on Wayland:
raise_()/activateWindow()/move() go through Qt-Wayland's xdg restack/
xdg-activation paths, which SEGSEGV in PySide6 6.9 as shipped on Guix
(faulthandler pinpointed win.raise_(); QT_QPA_PLATFORM=wayland reproduces,
--selftest — plain show() — never crashed). Gate the whole center/raise/
activate block behind platformName() == "xcb": on X11 it is the canonical
show-then-activate sequence and verified working under XWayland; on Wayland
the compositor maps and focuses the new toplevel itself (raise/activate were
no-ops there even before they crashed). Strict equality keeps wayland-egl
and any unknown platform on the safe plain-show path. Centering is computed
before show() again, as pre-e15329a, so X11 windows map already-centered.

Also make every sys.stderr access None-safe: PyInstaller --windowed (>=5.8,
i.e. the shipped Windows .exe) sets sys.stderr = None, so the unguarded
startup notice would have raised AttributeError right after show() and
killed the window; a dead pipe similarly raises on flush. Guard the notice
(+ try/except OSError), the import-error message, and the debug discovery
echo. Verified: launch stays alive on wayland, xcb, and offscreen-with-
fd2-closed; --selftest passes offscreen/wayland incl. VAPTVUPT_DEBUG=1
with stderr closed.
2026-07-11 10:56:17 -03:00
Cristian Cezar Moisés
e15329a7c9 gui: robust window activation + headless --selftest/--version flags
Center the main window on the active screen and raise()/activateWindow()
it on launch, so it cannot open off-screen or behind the focused frame on
a tiling compositor (Sway/i3/Hyprland) — the usual cause of reports that
'the GUI won't start / is stuck': it launched, but was not visible.

Add lightweight non-GUI flags handled before the QApplication is built:
  --version  print GUI + pinned-CLI version and Qt binding
  --help     usage
  --selftest build the full UI, spin the event loop once, exit 0
so a successful launch can be verified with no display (headless/CI/remote).
Also emit a one-line 'window open' notice to stderr so a terminal launch
is not mistaken for a hung shell.
2026-07-10 23:54:32 -03:00
Cristian Cezar Moisés
5cb04bf837 packaging: add GNU Guix package (CLI + GUI) with the PySide6 libGL fix
Adds packaging/guix/vaptvupt.scm so VaptVupt is reproducibly installable on
GNU Guix (guix package -f). The GUI launcher sets LD_LIBRARY_PATH to the Qt6
leaf libraries PySide6 needs but does not carry in its RUNPATH — libGL (mesa),
libxkbcommon, the X11/xcb family, libzstd (from zstd's "lib" output), harfbuzz,
icu, wayland, glib, dbus, ... Without them, `import PySide6.QtWidgets` fails with
"libGL.so.1 / libzstd.so.1: cannot open shared object file" and the GUI prints
"requires PySide6 or PyQt6" on Guix (which has no global /usr/lib to satisfy
them). Qt's own libraries are deliberately excluded from LD_LIBRARY_PATH so they
resolve via PySide6's RUNPATH (a second copy causes private-API symbol clashes).
Also puts python-shiboken-6's site-packages on GUIX_PYTHONPATH.

Verified: full PySide6 QtWidgets/QtGui/QtCore import + QApplication, and the
windowed GUI launches, on this Guix system.
2026-07-10 23:21:16 -03:00
Cristian Cezar Moisés
040b9326bc ci: fix cross-platform.yml YAML (quote msys2 shell default) so jobs run 2026-07-10 17:31:47 -03:00
Cristian Cezar Moisés
0e92156055 docs: unify GUI package version to 5.0.0 in README download tables
Some checks failed
cross-platform.yml / docs: unify GUI package version to 5.0.0 in README download tables (push) Failing after 0s
CI / cross-aarch64 (push) Has been cancelled
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 / dist-reproducibility (push) Has been cancelled
CI / packaging-syntax (push) Has been cancelled
CI / release (push) Has been cancelled
2026-07-10 17:26:18 -03:00
Cristian Cezar Moisés
5050570b23 v5.0.0: version bump, audit fixes, documentation overhaul
Version bumped to 5.0.0 across include/zupt.h, all packaging recipes, man
page, and docs.

Audit fixes (pre-5.0.0 review):
- src/zupt_format.c: overflow-safe bound in the solid-mode `test` path
  (off+sz could wrap and drive an OOB read in zupt_xxh64 on a crafted archive;
  the extract path was already hardened, the test path was not).
- gui: run_async now marshals the completion callback onto the GUI thread with
  QueuedConnection (a bare functor connected DirectConnection and touched
  widgets off the worker thread); Extract auto-detect note survives the log
  clear via a new `info` param.
- .github/workflows/ci.yml: trigger on `master` (was main/develop, so CI never
  ran); `make dist` tarball is vaptvupt-*.tar.gz not zupt-*; the ASAN PQ
  round-trip uses native --pq (was --pq-sdk, which fails on the source-only
  build and blocked the release job).

Documentation:
- New AUDIT.md (methodology, FIPS 203 conformance validation, findings, repro).
- CHANGELOG 5.0.0 entry covers the FIPS 203 conformance fix + BREAKING note and
  the GUI/CLI/security/packaging work.
- README "What's new in 5.0.0", download tables (incl. Windows/macOS/BSD +
  portable GUI), version-history row.
- SECURITY.md + THREAT_MODEL.md: ML-KEM-768 documented as FIPS 203, validated
  byte-for-byte against OpenSSL 3.5.
- Accuracy fixes: man page (--kdf default is PBKDF2 on source-only; codec
  2.60.4), rpm %description, debian control/copyright, homebrew header
  (no vendored library on source-only builds).

make check 16/16 (FIPS 203 conformance 3/3, all distro-safe checks).
2026-07-10 17:22:02 -03:00
Cristian Cezar Moisés
862f4a2df6 crypto: make ML-KEM-768 genuinely FIPS 203-conformant (validated vs OpenSSL)
The in-tree ML-KEM-768 was round-3 CRYSTALS-Kyber mislabelled "FIPS 203" and
was NOT interoperable with a compliant ML-KEM. Discovered and fixed by
validating against OpenSSL 3.5's FIPS 203 ML-KEM-768 as an oracle.

Three deviations, all fixed in src/zupt_mlkem.c:
1. Matrix  transpose convention. FIPS 203 K-PKE.KeyGen samples
   Â[i][j] = SampleNTT(XOF(ρ, j, i)) and K-PKE.Encrypt uses (ρ, i, j); the
   code had both index orders swapped. It was self-consistent (encaps/decaps
   round-tripped) but transposed vs the standard — which is exactly why a
   self-consistency-only round-trip test never caught it. With the same seed,
   keygen now produces a byte-identical ek to OpenSSL.
2. Shared-secret KDF. FIPS 203 returns K = G(m‖H(ek))[0:32] directly; removed
   the round-3 final K = KDF(K̄‖H(c)) step (encaps + decaps success key).
3. Implicit rejection. Now K̄ = J(z‖c) = SHAKE256(z ‖ full-ciphertext) instead
   of the round-3 KDF(z‖H(c)).

Validation (tests/test_mlkem_fips203.sh + mlkem_fips203_harness.c, wired into
make check): against OpenSSL 3.5 ML-KEM-768 —
  - deterministic keygen (same d‖z seed) -> byte-identical ek
  - our encaps -> OpenSSL decap: shared secret matches
  - OpenSSL encap -> our decaps: shared secret matches
The harness feeds a fixed random stream (MLKEM_RAND) so the FIPS 203 seed is
reproducible; the test skips gracefully without an ML-KEM-capable openssl.

BREAKING: --pq / --pq-only keys and archives from <= 4.2.1 no longer decrypt
(the KEM math changed). Regenerate keys and re-encrypt. Password mode and plain
compression are unaffected; wire format stays v1.6. make check 16/16.
2026-07-10 13:27:37 -03:00
Cristian Cezar Moisés
43d6306a06 GUI rework for source-only builds + CLI security fixes + packaging + cross-platform scaffolding
Fixes the "GUI functions don't work on GNU Guix" report and a batch of
adversarial-audit findings across the CLI, decoder, crypto and packaging.

GUI (gui/src/zupt_gui.py)
- Root cause of the broken GUI: every encryption path defaulted to the
  libzuptsdk "SDK v2" modes, which are absent from the source-only build and
  exit 1. Reworked Keys/Compress/Extract around the native modes with a
  build-aware PQ-mode selector: Hybrid (--pq, default), Full PQ (--pq-only),
  and SDK v2 only when the binary reports WITH_SDK support. Capability is
  detected from `version` ("Build:"/"KDF:" lines) with a `help` fallback.
- Extract/Verify gain a PQ private-key input with auto-detect (reads the
  archive via `info` to pick --pq vs --pq-only). Verify could not verify any
  PQ archive before (password field only).
- run_async now holds a LIST of in-flight (thread, worker) refs; DiskTab's two
  buttons previously shared one slot, so a second op GC'd the first QThread
  mid-run. About tab corrected (codec 2.60.4, PBKDF2 default, --pq-only, URL).

CLI (src/zupt_main.c)
- compress -p <archive> <files> swallowed the archive name as the password and
  truncated the first input file (silent data loss, exit 0). Added a
  data-loss guard: refuse to overwrite an existing non-.zupt file as the output
  archive unless -y/--force; plus a self-overwrite guard.
- compress <archive> <src> -p <pw> wrote an UNENCRYPTED archive (exit 0)
  because options after the first positional were treated as files. Now errors
  on a misplaced option (with a `--` escape for real dashed filenames).
- Removed duplicated dead --pq-box/--pq-sdk branches. version/banner/usage now
  state the build's real KDF (PBKDF2 on source-only) and repo URL; examples
  lead with native --pq / --pq-only.

Security (audit findings)
- vv_decoder.c: the two AVX2 fast-path token decoders read a 2-/3-byte match
  offset after only checking 1 byte remained -> heap over-read on a crafted
  archive. Added the `ip + off_bytes > ip_end` guard the tail path already had.
- zupt_crypto.c: wipe ML-KEM/X25519 secret-key buffers when hybrid decrypt
  init fails on key read (matches the pq-only path).
- zupt_format.c: bound attacker-controlled encryption_header_off before the
  (off_t)+7 arithmetic in the info enc_type reader (avoid signed-overflow UB).

Packaging (would fail source-only)
- debian/rules (staged into debian/zupt, installed vendored .so), aur/PKGBUILD,
  nix/flake.nix, homebrew (vendored .so + nonexistent AUDIT.md, stale /zupt
  URLs) now build source-only. opensuse spec %files ships the shell completions
  make install writes (rpmbuild no longer fails on unpackaged files);
  _service + debian/control point at the vaptvupt repo; KDF claim corrected.

Cross-platform GUI packaging (new)
- packaging/portable/: OS-agnostic GUI package (zupt_gui.py + .bat/.command/.sh
  launchers + README) that runs on Windows/macOS/Linux/BSD with Python+PySide6.
- .github/workflows/cross-platform.yml: builds real native binaries on
  windows-latest + macos-latest runners (CLI, PyInstaller GUI, Inno Setup
  installer, .dmg) and the portable zip, attaching them to the release on tag.
- packaging/windows/vaptvupt-gui.iss: Inno Setup installer script.

Validation: make check 16/16 (all distro-safe checks), GUI imports + command
contracts verified against the fixed CLI. test_help_consistency updated to
assert the truthful (build-aware) default KDF.
2026-07-10 12:45:46 -03:00
Cristian Cezar Moisés
e8f7b3adb2 v4.2.1: bump packaging recipes and docs to 4.2.1
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
Version fields, changelogs (rpm/deb/openSUSE), and download references
across README/INSTALL/DISTRIBUTION/gui-README bumped to 4.2.1 for the
info-label point release. GUI stays 1.3.0. No code change here.
2026-07-10 07:00:09 -03:00
Cristian Cezar Moisés
9800530d2e v4.2.1: info correctly reports --pq-only vs hybrid post-quantum mode
`vaptvupt info` mislabelled full post-quantum (--pq-only, enc_type 0x06)
archives as "PQ Hybrid: YES (ML-KEM-768 + X25519)". Full-PQ archives set
the generic ZUPT_FLAG_PQ_HYBRID header flag (the enc_type byte is what
distinguishes hybrid 0x02 from pure 0x06), but info only checked the flag.

info now seeks to hdr.encryption_header_off, reads the real enc_type from
the encryption-header block, and reports the actual mode: "ML-KEM-768
only, no classical layer" for --pq-only, and hybrid / SDK-v2 / sealed-box
for the others. Reader-side only — no wire-format change; existing 4.2.0
archives are relabelled correctly with no re-encryption.
2026-07-10 00:20:49 -03:00
Cristian Cezar Moisés
97cf7b4e5c packaging: make build-appimage.sh source-only
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
The CLI AppImage no longer bundles libzuptsdk/libpqvaptvupt (removed in
the 4.1.0 source-only switch); the binary links only libc/libm/pthread
from the host. Drop the vendored-library install and the LD_LIBRARY_PATH
in AppRun, and default VERSION to 4.2.0.
2026-07-09 23:36:00 -03:00
Cristian Cezar Moisés
846faac428 packaging: correct 4.2.0 changelog weekday and make Fedora rpm spec source-only
- 2026-07-09 is a Thursday; fix the weekday in the rpm, debian, and
  openSUSE 4.2.0 changelog entries (was "Wed").
- packaging/rpm/vaptvupt.spec: drop the stale vendored-library install
  and %files entries (the libraries were removed in 4.1.0's source-only
  switch), build with WITH_SDK=0, and list the actual vaptvupt binary
  and shell completions. Mirrors the already-source-only openSUSE spec.
2026-07-09 23:30:24 -03:00
Cristian Cezar Moisés
124958aea9 v4.2.0: full (pure) post-quantum mode + critical dedup nonce fix
Add a native full post-quantum encryption mode and fix a critical
keystream-reuse bug in deduplicated encrypted archives.

Full post-quantum mode (--pq-only)
- New envelope type 0x06 (ZUPT_ENC_PQ_ONLY): ML-KEM-768 (FIPS 203) as
  the sole key-establishment mechanism, with no classical X25519
  component. Archive key = SHA3-512(ml_ss || ml_ct || "ZUPT-PQ-ONLY-v1").
- For compliance postures that require a single NIST-standardised PQ
  primitive with no classical KEM in the envelope (CNSA 2.0-style
  "PQ-only"). Hybrid --pq stays the recommended default; --pq-only has
  no classical fallback, so a break of ML-KEM-768 alone breaks it.
- keygen --pq-only / keygen --pub --pq-only (ZPQK magic, 1200B pub /
  3600B priv; not interchangeable with hybrid --pq keys). Wrong or
  tampered ciphertext is rejected via ML-KEM FO implicit rejection plus
  the HMAC-SHA256 Encrypt-then-MAC envelope. In-tree, default build.

Security (critical): AES-256-CTR keystream reuse under --dedup
- Dedup assigns block sequence 0 to every data block (the sentinel that
  keeps cross-file dedup references authenticating consistently). The
  per-block nonce was base_nonce XOR block_seq, so under --dedup every
  block collapsed to the same nonce, reusing the CTR keystream across
  distinct plaintexts (a many-time-pad). Each block now uses a fresh
  random 128-bit nonce stored in the block prefix and bound into the
  block MAC; block_seq is still bound as MAC AAD. Regression test:
  tests/test_dedup_nonce.sh. Re-encrypt any --dedup encrypted archives
  written by <= 4.1.0.

Other
- keygen --sdk / --box on a source-only build now fails with a clear
  message pointing to native --pq / --pq-only (or a WITH_SDK=1 build).
- Documentation: README, SECURITY, THREAT_MODEL, man page, CHANGELOG,
  and all packaging recipes updated for the new mode and the security
  fix; version bumped to 4.2.0. Wire format v1.6 unchanged (0x06 is
  additive).

Validation: make check 16/16, quick suite 11/11 (incl. PQ-only),
dedup-nonce regression (all block nonces distinct), cppcheck clean.
2026-07-09 21:15:14 -03:00
Cristian Cezar Moisés
748001aa65
Update README.md 2026-04-30 19:15:35 -03:00
Cristian Cezar Moisés
d198dbb205 v2.1.7: Zupt is now licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later) + VaptVupt v2.46.1(GPLv3) 2026-04-26 01:47:45 -03:00
Cristian Cezar Moisés
ca54a19a27 2026-04-22 22:20:24 -03:00
Cristian Cezar Moisés
26c0d4b4ea 2026-04-22 03:54:32 -03:00
Cristian Cezar Moisés
572031910c Release: v2.1.6 - Added VaptVupt 2.40 2026-04-22 03:46:59 -03:00
Cristian Cezar Moisés
8e0eb1ee41
Update README.md 2026-04-12 19:23:40 -03:00
Cristian Cezar Moisés
92c1a1b377 apt install zupt-web-readme -y 2026-04-12 19:22:08 -03:00
Cristian Cezar Moisés
25494a1877 Updated: Readme 2026-04-12 15:31:23 -03:00
Cristian Cezar Moisés
bb0a62ca76 Release: 2.1.5 - Block-level deduplication (--dedup), XXH64 fingerprint index, DEDUP_REF block type, 81 tests 2026-04-12 15:29:42 -03:00
Cristian Cezar Moisés
a4aab7a1b6 Release: 2.1.4 - Resolved high-severity vulnerabilities by removing TOCTOU filesystem races via fd-first open()/fstat() patterns and enforcing non-optimizable secure memory zeroization for cryptographic material. 2026-04-11 17:24:32 -03:00
Cristian Cezar Moisés
288df8ea0b Release 2.1.3: Fix disk backup corruption, solid PQ encryption, block I/O, and format mismatches
- Corrected LZHP prediction encoding in disk backups to prevent data corruption
- Disabled spurious SOLID flag for per-block disk archives
- Shared write_enc_header() across all encryption paths to eliminate format mismatches
- Enabled solid compression with PQ encryption support
- Updated block device restore to use O_SYNC + fsync/sync
- Improved Termux/Android host detection for safer builds
- Made zupt_w8(), zupt_w16le(), zupt_w64le() non-static for shared use
2026-04-11 15:59:59 -03:00
Cristian Cezar Moisés
3e3da54173
Update README.md 2026-04-06 19:55:24 -03:00
Cristian Cezar Moisés
1ee9f30ae8 Zupt v2.1.2: add full-disk backup/restore, expand test suite, improve compression handling
- Introduced full-disk backup/restore (src/zupt_disk.c, ~530 LOC)
  - Backup: streams 4MB blocks with sparse detection and all codecs
  - Restore: per-block checksum validation for integrity
  - Supports password (-p), PQ hybrid (--pq), or no encryption
  - Real-time progress bar with throughput reporting (stderr)

- Implemented sparse detection (8-byte zero scan)
  - Zero blocks stored as STORE (near-zero overhead)

- Added disk-aware safeguards
  - ZUPT_FLAG_DISK_IMAGE prevents misuse with extract
  - Immediate failure on wrong password during first block decrypt

- Cross-platform device size detection
  - Linux: BLKGETSIZE64
  - macOS: DKIOCGETBLOCKCOUNT
  - Fallback: lseek

- Compression behavior
  - ~2928:1 on highly repetitive data
  - ~1.33:1 on random data (auto STORE fallback)

- Expanded test suite to 77 tests:
  - 11 VV unit
  - 13 NIST/RFC vectors
  - 22 regression
  - 14 multi-threaded
  - 10 post-quantum
  - 7 disk (normal, encrypted, PQ, sparse, LZHP, extreme compression, wrong-password)

Release stats:
- 74 files, 168KB, zero .o artifacts
- 77/77 tests passing
- Fully clean under ASAN + UBSan
2026-04-06 19:33:37 -03:00
Cristian Cezar Moisés
5fcb9977ad Zupt v2.1.2: add full-disk backup/restore, expand test suite, improve compression handling
- Introduced full-disk backup/restore (src/zupt_disk.c, ~530 LOC)
  - Backup: streams 4MB blocks with sparse detection and all codecs
  - Restore: per-block checksum validation for integrity
  - Supports password (-p), PQ hybrid (--pq), or no encryption
  - Real-time progress bar with throughput reporting (stderr)

- Implemented sparse detection (8-byte zero scan)
  - Zero blocks stored as STORE (near-zero overhead)

- Added disk-aware safeguards
  - ZUPT_FLAG_DISK_IMAGE prevents misuse with extract
  - Immediate failure on wrong password during first block decrypt

- Cross-platform device size detection
  - Linux: BLKGETSIZE64
  - macOS: DKIOCGETBLOCKCOUNT
  - Fallback: lseek

- Compression behavior
  - ~2928:1 on highly repetitive data
  - ~1.33:1 on random data (auto STORE fallback)

- Expanded test suite to 77 tests:
  - 11 VV unit
  - 13 NIST/RFC vectors
  - 22 regression
  - 14 multi-threaded
  - 10 post-quantum
  - 7 disk (normal, encrypted, PQ, sparse, LZHP, extreme compression, wrong-password)

Release stats:
- 74 files, 168KB, zero .o artifacts
- 77/77 tests passing
- Fully clean under ASAN + UBSan
2026-04-06 19:25:24 -03:00
Cristian Cezar Moisés
2b68548e93 Zupt v2.1.1: fix cross-arch build issues, eliminate UB, improve Android/Termux support
- Removed all shipped .o files from tarball (fixes aarch64/Termux linker errors with x86_64 objects)
- Added arch-safety guard in Makefile to auto-detect and remove incompatible .o files
- Switched default compiler from gcc to cc (Termux uses clang)
- Skipped -lpthread on Android (bionic provides pthreads)
- Added Android detection via uname -o
- Fixed Keccak UB: ROL64(x,0) no longer expands to undefined x >> 64
- Achieved zero UBSan/ASAN issues across all PQ crypto paths
- Moved sys/syscall.h include to file scope with proper __linux__ guard

Release stats:
- 73 files, 159KB, zero .o artifacts
- 70/70 tests passing
- Fully clean under ASAN + UBSan

Note: full-disk encryption (--disk) deferred to v2.2.0 (requires raw device I/O, sparse detection, and privilege handling)
2026-04-06 19:17:37 -03:00
Cristian Cezar Moisés
79213bdc7b Adjust: Readme.md 2026-04-05 23:29:46 -03:00
Cristian Cezar Moisés
3e8fd9a3a4 Release: 2.1.0 Added:VaptVupt 1.4.0: cross-block dictionary carry, context decode prefetch, faster adaptive window trial (2.6× encode), integration API 2026-04-05 23:14:18 -03:00
Cristian Cezar Moisés
c42251f0a2 Release v2.0.0: VaptVupt codec integration, auto codec detection, Jasmin and VaptVupt fixes, multi-arch & performance enhancements, security hardening, ACSL annotations 2026-04-05 15:35:49 -03:00
Cristian Cezar Moisés
0e8e3b11c6 Updated: Docs 2026-04-05 15:30:55 -03:00
Cristian Cezar Moisés
7388038404 Removed: test_vaptvupt and test_vectors 2026-04-05 15:14:54 -03:00
Cristian Cezar Moisés
f567d0de79 Fix: Workflow 2026-04-05 14:56:59 -03:00
Cristian Cezar Moisés
570a7023c9 Feat: Added vaptvupt codec, fix jasmin tests 2026-03-30 06:56:52 -03:00
Cristian Cezar Moisés
4c5a2efe63 Feat: Added vaptvupt codec, fix jasmin tests 2026-03-30 06:51:34 -03:00
Cristian Cezar Moisés
dfa52fd576 Add: Donations + Liberapay 2026-03-29 06:16:20 -03:00
Cristian Cezar Moisés
d9a249fb65 Add: Install.sh 2026-03-28 23:11:18 -03:00
Cristian Cezar Moisés
5bb280eb25 feat: add Jasmin assembly integration for crypto acceleration
- Integrated `zupt_mac_verify_ct` in `zupt_decrypt_buffer()` to replace C XOR loop for HMAC-SHA256
- Integrated `zupt_ct_select_32` in `zupt_mlkem768_decaps()` to replace C `cmov()` for FO transformation
- Added `include/zupt_jasmin.h` with extern declarations and ABI docs
- Added `#ifdef ZUPT_USE_JASMIN` guards with clean C fallbacks in `zupt_crypto.c` and `zupt_mlkem.c`
- Makefile now auto-detects `jasmin/*.s`, assembles and links with `-DZUPT_USE_JASMIN`

Closes #3
2026-03-28 23:03:04 -03:00
Alessandro de Oliveira Faria (A.K.A.CABELO)
26e45b5c3e
Merge pull request #12 from cabelo/all-opensuse-03
cabelo@opensuse.org: Available in all openSUSE and DiraQ Linux
2026-03-28 15:47:54 -03:00
Alessandro de Oliveira Faria (A.K.A. CABELO)
44a429213a cabelo@opensuse.org: Available in all openSUSE and DiraQ Linux 2026-03-28 15:44:43 -03:00
Alessandro de Oliveira Faria (A.K.A.CABELO)
372ceb3b6d
Merge pull request #11 from cabelo/all-opensuse
All opensuse
2026-03-28 15:39:05 -03:00
Alessandro de Oliveira Faria (A.K.A. CABELO)
763438d3ee cabelo@opensuse.org: Available in all openSUSE distributions 2026-03-28 15:35:37 -03:00
Alessandro de Oliveira Faria (A.K.A.CABELO)
77f3d5f58e
Update README with openSUSE manual installation instructions 2026-03-25 12:32:16 -03:00
Cristian Cezar Moisés
8a429693ef
Updated: Readme - added Logo 2026-03-24 22:00:15 -03:00
Cristian Cezar Moisés
fb7e071892 Updated: Readme 2026-03-24 21:00:23 -03:00
Cristian Cezar Moisés
aaaedcffaf Updated: Install.sh 2026-03-24 20:58:22 -03:00
Cristian Cezar Moisés
5f303b3693 Updated: Readme 2026-03-24 20:53:19 -03:00
Cristian Cezar Moisés
19811e79e4 Updated: Removed old .tar.gz 2026-03-24 20:24:18 -03:00
Cristian Cezar Moisés
3c4fcf208c Updated: Readme - OpenSuse pacakges 2026-03-24 20:22:06 -03:00
Cristian Cezar Moisés
266f6e21b7 Added: tarball 2026-03-24 20:13:18 -03:00
Cristian Cezar Moisés
89f03aa6f9 Removed: Zupt binary 2026-03-24 20:05:57 -03:00
Cristian Cezar Moisés
58e2355e9a Merge remote-tracking branch 'github/master' 2026-03-24 20:00:07 -03:00
Cristian Cezar Moisés
23e9613390 Added: Donations 2026-03-24 19:57:48 -03:00
Alessandro de Oliveira Faria (A.K.A.CABELO)
68182c221f
Update copyright year in README.md 2026-03-24 19:48:31 -03:00
Cristian Cezar Moisés
cf6e635309 Updated: Install.sh 2026-03-24 19:44:00 -03:00
Cristian Cezar Moisés
d585de91a9 fix(cli): clarify password requirement for -p option
Updated CLI help and prompt message to show that -p/--password requires a password.
If no password is provided, the user will be prompted. Improves usability and avoids confusion.
2026-03-24 19:28:21 -03:00
Cristian Cezar Moisés
6adf3ceb95 Updated: Readme - Build 2026-03-24 19:13:23 -03:00
Cristian Cezar Moisés
cbca9a44b2 Fixed: Version on zupt.h
Closes #8
2026-03-24 19:08:06 -03:00
Cristian Cezar Moisés
4edcf3af5e Merge PR #4: Update Makefile (cabelo) 2026-03-24 18:53:04 -03:00
Alessandro de Oliveira Faria (A.K.A. CABELO)
bd15c72fc9 cabelo@opensuse.org - Makefile 2026-03-23 17:50:16 -03:00
213 changed files with 25314 additions and 12905 deletions

27
.gitattributes vendored Normal file
View file

@ -0,0 +1,27 @@
* text=auto eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
*.s text eol=lf
*.S text eol=lf
*.jazz text eol=lf
*.png binary
*.ico binary
*.jpg binary
*.jpeg binary
*.gif binary
*.webp binary
*.zip binary
*.gz binary
*.xz binary
*.bz2 binary
*.zst binary
# These downstream recipes pin the checksum of the release tarball itself.
# Excluding them from `git archive` avoids a self-referential checksum while
# keeping every recipe versioned in Git and available to its package manager.
/packaging/aur/** export-ignore
/packaging/homebrew/** export-ignore
/packaging/guix/** export-ignore

View file

@ -1,251 +1,640 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
#
# Zupt CI matrix.
#
# Mirrors the project's local-verification protocol from PROMPT.md §6:
# 1. Plain GCC build
# 2. Plain Clang build
# 3. Strict GCC (full warning set)
# 4. Strict Clang (full warning set)
# 5. ASAN + UBSAN
# 6. Full regression suite (12 suites: audit, dedup, path-traversal,
# argument-order, block-swap, F-08, F-09 byte sweep, F-10, F-11,
# F-12, packaging syntax, dist reproducibility)
# 7. License header audit
# 8. `make dist` reproducibility (two runs, sha256 must match)
# 9. aarch64 cross-test via QEMU emulation
# 10. Automatic release on git tag push
name: CI
on:
push:
branches: [main, develop]
tags: ['v*']
branches:
- master
- 'codex/**'
tags:
- 'v*'
pull_request:
branches: [main, develop]
branches:
- master
workflow_dispatch:
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name != 'workflow_dispatch' && !startsWith(github.ref, 'refs/tags/') }}
permissions:
contents: read
jobs:
# ─── Plain build + test, exactly as a user would do it ───
source-policy:
name: Source-only, license, shell and secret policy
runs-on: ubuntu-24.04
steps:
- name: Check out all refs without LFS or submodules
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
lfs: false
submodules: false
- name: Install audit tools
run: |
sudo apt-get update
sudo apt-get install -y \
dpkg-dev file git-lfs libarchive-tools libxml2-utils make python3 ruby \
shellcheck unzip
- name: Audit tracked files, worktree and HEAD archive
run: bash scripts/check-source-only.sh
- name: Exercise positive and negative scanner fixtures
run: bash tests/test_source_only.sh
- name: Audit license headers
run: make WITH_SDK=0 WITH_PQBOX=0 audit-licenses
- name: Validate release packaging metadata
run: bash tests/test_packaging_syntax.sh
- name: ShellCheck release and source-policy scripts
run: |
shellcheck \
packaging/build-deb.sh \
packaging/build-rpm.sh \
packaging/build-appimage.sh \
packaging/build-dmg.sh \
packaging/build-gui-appimage.sh \
packaging/build-gui-deb.sh \
packaging/build-gui-rpm.sh \
packaging/opensuse/source-audit.sh \
scripts/check-source-only.sh \
scripts/export-opensuse-package.sh \
scripts/test-installed-zupt.sh \
tests/test_atomic_archive_output.sh \
tests/test_authenticated_dedup_reorder.sh \
tests/test_benchmark_temp_safety.sh \
tests/test_block_type_confusion.sh \
tests/test_disk_device_capacity.sh \
tests/test_f09_preface.sh \
tests/test_key_files.sh \
tests/test_legacy_disk_5_2_1.sh \
tests/test_path_traversal.sh \
tests/test_pqbox.sh \
tests/test_sdk.sh \
tests/test_source_only.sh
- name: Credential material audit (paths only)
shell: bash
run: |
set -Eeuo pipefail
findings=$(git grep -Il -E -- \
"-----BEGIN (RSA |OPENSSH |EC |DSA )?PRIVATE KEY-----|https?://[^/@[:space:]]+:[A-Za-z0-9_+=.-]{20,}@|gh[pousr]_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{20,}|AKIA[A-Z0-9]{16}|xox[baprs]-[A-Za-z0-9-]{20,}|(FORGEJO_TOKEN|SECURITYOPS_TOKEN|GITHUB_TOKEN|CODEBERG_TOKEN)[[:space:]]*[:=][[:space:]]*['\\\"]?[A-Za-z0-9_+=./-]{20,}" \
-- . || true)
if [[ -n $findings ]]; then
printf '%s\n' "$findings" >&2
echo 'credential-like material found in tracked files' >&2
exit 1
fi
echo 'No private-key block, named token assignment, or credential-bearing URL found.'
build-and-test:
name: Build and full tests (${{ matrix.cc }})
needs: source-policy
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
cc: [gcc, clang]
steps:
- uses: actions/checkout@v4
- name: Install build deps
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
lfs: false
submodules: false
- name: Install build tools
run: |
sudo apt-get update
sudo apt-get install -y build-essential clang dpkg-dev python3
- name: Build (${{ matrix.cc }})
run: make CC=${{ matrix.cc }} -j$(nproc)
- name: zupt version
run: ./zupt version
- name: Full regression suite
run: make test
- name: License header audit
run: make audit-licenses
sudo apt-get install -y build-essential clang file libarchive-tools python3 unzip
- name: Clean source-only build
run: |
make clean
make -j"$(nproc)" CC=${{ matrix.cc }} V=1 WITH_SDK=0 WITH_PQBOX=0
- name: Distribution checks
run: make CC=${{ matrix.cc }} V=1 WITH_SDK=0 WITH_PQBOX=0 check
- name: Extended upstream tests
run: make CC=${{ matrix.cc }} V=1 WITH_SDK=0 WITH_PQBOX=0 test-all
- name: In-tree SDK atomic key-save regression
run: make CC=${{ matrix.cc }} V=1 sdk-test
- name: Functional test of the built CLI
run: bash scripts/test-installed-zupt.sh "$PWD/zupt"
# ─── Strict warning matrix — what the project's §6 protocol uses ───
strict-warnings:
name: Strict warnings (${{ matrix.cc }})
needs: source-policy
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
include:
- cc: gcc
cflags: "-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"
flags: >-
-O2 -g -std=c11 -Wall -Wextra -Wpedantic -Wshadow
-Wcast-align -Wstrict-prototypes -Wmissing-prototypes
-Wnull-dereference -Wformat=2 -Werror
- cc: clang
cflags: "-Wall -Wextra -Wpedantic -Wshadow -Wcast-align -Wstrict-prototypes -Wmissing-prototypes -Wnull-dereference -O2 -std=c11 -Werror"
flags: >-
-O2 -g -std=c11 -Wall -Wextra -Wpedantic -Wshadow
-Wcast-align -Wstrict-prototypes -Wmissing-prototypes
-Wnull-dereference -Wformat=2 -Werror
steps:
- uses: actions/checkout@v4
- name: Install build deps
run: sudo apt-get update && sudo apt-get install -y build-essential clang
- name: Strict ${{ matrix.cc }} build (warnings → errors)
run: make CC=${{ matrix.cc }} CFLAGS="${{ matrix.cflags }}" -j$(nproc)
# ─── ASAN + UBSAN — catches memory bugs the warning matrix can't ───
sanitizers:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install build deps
run: sudo apt-get update && sudo apt-get install -y build-essential python3
- name: Build with ASAN + UBSAN
run: make test-asan
- name: PQ-SDK byte-exact roundtrip under ASAN
env:
ASAN_OPTIONS: detect_leaks=0:abort_on_error=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: |
./zupt_asan keygen --sdk -o /tmp/k.priv
./zupt_asan compress --pq-sdk /tmp/k.priv.pub /tmp/a.zupt include/
mkdir -p /tmp/extracted
./zupt_asan extract --pq-sdk /tmp/k.priv -o /tmp/extracted /tmp/a.zupt
diff -qr include /tmp/extracted/include
# ─── PIE hardening build — verifies no runtime breakage from -fPIE ───
pie-hardening:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install build deps
run: sudo apt-get update && sudo apt-get install -y build-essential
- name: Build with PIE + hardening
run: |
make CFLAGS="-O2 -std=c11 -fPIE -fstack-protector-strong -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security" \
LDFLAGS="-pie -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack" \
-j$(nproc)
- name: Verify binary is PIE
run: |
file ./zupt | grep -E "ELF .*executable.*pie|ELF .*shared object" || \
{ file ./zupt; echo "binary is not PIE"; exit 1; }
- name: Smoke test
run: |
echo "test" > /tmp/in.txt
./zupt c -p secret /tmp/a.zupt /tmp/in.txt
mkdir /tmp/out
(cd /tmp/out && ./../../home/runner/work/zupt/zupt/zupt x -p secret /tmp/a.zupt) || \
{ cd /tmp/out && "$GITHUB_WORKSPACE/zupt" x -p secret /tmp/a.zupt; }
diff -q /tmp/in.txt /tmp/out/in.txt
# ─── aarch64 cross-build via QEMU emulation ───
cross-aarch64:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
platforms: arm64
- name: Build + test inside aarch64 container
run: |
docker run --rm --platform linux/arm64 \
-v "$PWD":/src -w /src \
ubuntu:24.04 \
bash -c '
apt-get update -qq
apt-get install -y -qq build-essential python3
make -j$(nproc)
./zupt version
make test
'
# ─── make dist reproducibility ───
dist-reproducibility:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install build deps
run: sudo apt-get update && sudo apt-get install -y build-essential python3
- name: First dist build
run: make dist
- name: Capture sha256 (run 1)
id: sha1
run: |
VER=$(grep '^#define ZUPT_VERSION_STRING' include/zupt.h | awk -F'"' '{print $2}')
SHA=$(sha256sum /tmp/zupt-$VER.tar.gz | awk '{print $1}')
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "ver=$VER" >> "$GITHUB_OUTPUT"
echo "Run 1: $SHA"
- name: Second dist build (must produce identical sha256)
run: make dist
- name: Verify reproducibility
run: |
VER="${{ steps.sha1.outputs.ver }}"
SHA2=$(sha256sum /tmp/zupt-$VER.tar.gz | awk '{print $1}')
if [ "$SHA2" != "${{ steps.sha1.outputs.sha }}" ]; then
echo "::error::make dist is NOT reproducible"
echo " run 1: ${{ steps.sha1.outputs.sha }}"
echo " run 2: $SHA2"
exit 1
fi
echo "Reproducible ✓ ($SHA2)"
- name: Upload reproducible source tarball
uses: actions/upload-artifact@v4
with:
name: zupt-source-tarball
path: /tmp/zupt-*.tar.gz
# ─── Packaging-recipe syntax (cross-distro) ───
packaging-syntax:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install validators
persist-credentials: false
- name: Install compilers
run: |
sudo apt-get update
sudo apt-get install -y build-essential dpkg-dev ruby rpm
- name: Build (for include/zupt.h to exist; not strictly needed for syntax test)
run: make -j$(nproc)
- name: Run packaging syntax test
run: bash tests/test_packaging_syntax.sh
sudo apt-get install -y build-essential clang
- name: Compile with warnings as errors
run: |
make clean
make -j"$(nproc)" CC=${{ matrix.cc }} V=1 WITH_SDK=0 WITH_PQBOX=0 \
CFLAGS="${{ matrix.flags }}"
# ─── Automatic GitHub release on git tag push ───
release:
if: startsWith(github.ref, 'refs/tags/v')
needs: [build-and-test, strict-warnings, sanitizers, dist-reproducibility, packaging-syntax]
sanitizers:
name: ASan, LSan and UBSan
needs: source-policy
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Install build deps
run: sudo apt-get update && sudo apt-get install -y build-essential python3
- name: Build reproducible source tarball
run: make dist
- name: Get version
id: ver
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install compiler and test tools
run: |
VER=$(grep '^#define ZUPT_VERSION_STRING' include/zupt.h | awk -F'"' '{print $2}')
echo "version=$VER" >> "$GITHUB_OUTPUT"
- name: Verify tag matches version
sudo apt-get update
sudo apt-get install -y build-essential file python3
- name: Instrumented functional tests
env:
ASAN_OPTIONS: detect_leaks=1:abort_on_error=1
UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1
run: make V=1 WITH_SDK=0 WITH_PQBOX=0 test-asan-run
- name: Mutation smoke under sanitizers
env:
ASAN_OPTIONS: detect_leaks=1:abort_on_error=1
UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1
run: make V=1 WITH_SDK=0 WITH_PQBOX=0 fuzz-format-run
static-analysis:
name: GCC static analyzer
needs: source-policy
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install GCC
run: |
TAG="${GITHUB_REF#refs/tags/}"
EXPECTED="v${{ steps.ver.outputs.version }}"
if [ "$TAG" != "$EXPECTED" ]; then
echo "::error::tag $TAG doesn't match include/zupt.h $EXPECTED"
sudo apt-get update
sudo apt-get install -y build-essential
- name: Analyze every source translation unit
run: |
make clean
make -j"$(nproc)" CC=gcc V=1 WITH_SDK=0 WITH_PQBOX=0 \
CFLAGS="-O1 -g -std=c11 -Wall -Wextra -Werror -fanalyzer"
source-archive:
name: Reproducible audited source archive
needs: source-policy
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
lfs: false
submodules: false
- name: Install archive audit tools
run: |
sudo apt-get update
sudo apt-get install -y file libarchive-tools python3 unzip
- name: Build the source archive twice
run: |
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
mkdir -p "$RUNNER_TEMP/dist-one" "$RUNNER_TEMP/dist-two" \
"$RUNNER_TEMP/release-source"
make DIST_TARBALL="$RUNNER_TEMP/dist-one/zupt-$version.tar.gz" dist
make DIST_TARBALL="$RUNNER_TEMP/dist-two/zupt-$version.tar.gz" dist
cmp "$RUNNER_TEMP/dist-one/zupt-$version.tar.gz" \
"$RUNNER_TEMP/dist-two/zupt-$version.tar.gz"
cp "$RUNNER_TEMP/dist-one/zupt-$version.tar.gz" \
"$RUNNER_TEMP/release-source/"
(cd "$RUNNER_TEMP/release-source" && sha256sum "zupt-$version.tar.gz" > \
"zupt-$version.tar.gz.sha256")
bash scripts/check-source-only.sh --archive \
"$RUNNER_TEMP/release-source/zupt-$version.tar.gz"
- name: Match downstream recipe checksums to the tagged source archive
if: startsWith(github.ref, 'refs/tags/v')
run: |
set -Eeuo pipefail
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
source_tar="$RUNNER_TEMP/release-source/zupt-$version.tar.gz"
actual_sha=$(sha256sum "$source_tar" | awk '{print $1}')
aur_sha=$(awk -F"'" '/^sha256sums=/ { print $2; exit }' packaging/aur/PKGBUILD)
homebrew_sha=$(awk -F'"' '/^[[:space:]]*sha256 / { print $2; exit }' packaging/homebrew/zupt.rb)
guix_base32=$(sed -n 's/^[[:space:]]*(base32 "\([^"]*\)").*/\1/p' \
packaging/guix/zupt.scm | head -n 1)
actual_base32=$(python3 - "$source_tar" <<'PY'
import hashlib
import pathlib
import sys
alphabet = "0123456789abcdfghijklmnpqrsvwxyz"
digest = hashlib.sha256(pathlib.Path(sys.argv[1]).read_bytes()).digest()
value = int.from_bytes(digest, "little")
length = (len(digest) * 8 + 4) // 5
print("".join(alphabet[(value >> (5 * index)) & 31]
for index in range(length - 1, -1, -1)))
PY
)
[[ $aur_sha == "$actual_sha" && $homebrew_sha == "$actual_sha" ]] || {
echo 'AUR or Homebrew checksum does not match the source archive' >&2
exit 1
}
[[ $guix_base32 == "$actual_base32" ]] || {
echo 'Guix checksum does not match the source archive' >&2
exit 1
}
- name: Upload source and checksum
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-source
path: ${{ runner.temp }}/release-source/*
if-no-files-found: error
retention-days: 7
debian-package:
name: Debian/Ubuntu source-built package
needs: [source-policy, build-and-test]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
- name: Install Debian package tools
run: |
sudo apt-get update
sudo apt-get install -y build-essential binutils dpkg-dev file git libarchive-tools python3 python3-pyqt6 unzip
- name: Build and extract-test the DEB
run: |
mkdir -p "$RUNNER_TEMP/release-deb"
DIST_DIR="$RUNNER_TEMP/release-deb" RUN_CHECKS=1 bash packaging/build-deb.sh
- name: Build and content-test the GUI DEB
run: |
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
test -n "$version"
mkdir -p "$RUNNER_TEMP/release-gui-deb"
DIST_DIR="$RUNNER_TEMP/release-gui-deb" bash packaging/build-gui-deb.sh
gui_deb="$RUNNER_TEMP/release-gui-deb/zupt-gui_${version}_all.deb"
test -s "$gui_deb"
test "$(dpkg-deb -f "$gui_deb" Package)" = zupt-gui
test "$(dpkg-deb -f "$gui_deb" Version)" = "$version"
test "$(dpkg-deb -f "$gui_deb" Architecture)" = all
- name: Install, functionally test and uninstall the DEBs
run: |
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
test -n "$version"
deb=$(find "$RUNNER_TEMP/release-deb" -maxdepth 1 -type f -name '*.deb' -print -quit)
gui_deb="$RUNNER_TEMP/release-gui-deb/zupt-gui_${version}_all.deb"
test -n "$deb" && test -s "$gui_deb"
sudo apt-get install -y "$deb" "$gui_deb"
bash scripts/test-installed-zupt.sh /usr/bin/zupt
QT_QPA_PLATFORM=offscreen zupt-gui --version | grep -Fx "zupt-gui $version"
test ! -e /usr/bin/vaptvupt
sudo apt-get purge -y zupt-gui zupt
test ! -e /usr/bin/zupt-gui
test ! -e /usr/bin/zupt
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-deb
path: ${{ runner.temp }}/release-deb/*.deb
if-no-files-found: error
retention-days: 7
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-gui-deb
path: ${{ runner.temp }}/release-gui-deb/*.deb
if-no-files-found: error
retention-days: 7
tumbleweed-rpm:
name: openSUSE Tumbleweed x86_64 RPM gate
needs: [source-policy, build-and-test]
runs-on: ubuntu-24.04
container: opensuse/tumbleweed:latest
defaults:
run:
shell: bash
steps:
- name: Bootstrap Git before checkout
run: |
zypper --non-interactive --gpg-auto-import-keys refresh
zypper --non-interactive install --no-recommends \
bash ca-certificates git-core
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
lfs: false
submodules: false
- name: Trust the exact checked-out workspace
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Install native openSUSE tooling
run: |
if rpm -q busybox-gawk >/dev/null 2>&1; then
zypper --non-interactive remove busybox-gawk
fi
zypper --non-interactive install --no-recommends \
bash binutils cpio coreutils diffutils file findutils gawk gcc git-core grep gzip \
libxml2-tools make osc obs-service-obs_scm obs-service-recompress \
obs-service-tar python3-base rpm-build rpmlint sed \
shadow spec-cleaner tar unzip util-linux
- name: Confirm the Factory architecture gate
run: test "$(uname -m)" = x86_64
- name: Validate OBS service and spec syntax
run: |
xmllint --noout packaging/opensuse/_service
test -x /usr/lib/obs/service/obs_scm
test -x /usr/lib/obs/service/tar
test -x /usr/lib/obs/service/recompress
rpmspec -P packaging/opensuse/zupt.spec >/dev/null
spec-cleaner --no-copyright packaging/opensuse/zupt.spec \
> "$RUNNER_TEMP/zupt.spec.cleaned"
diff -u packaging/opensuse/zupt.spec \
"$RUNNER_TEMP/zupt.spec.cleaned"
- name: Exercise pinned OBS source service chain on release tags
if: startsWith(github.ref, 'refs/tags/v')
run: |
service_dir=$RUNNER_TEMP/obs-service
mkdir -p "$service_dir"
cp packaging/opensuse/_service "$service_dir/"
# `osc service runall` additionally requires OBS working-copy metadata.
# Use osc's installed service executor to validate this standalone,
# repository-owned _service file with the exact same local services.
python3 - "$service_dir" <<'PY'
import os
import sys
from xml.etree import ElementTree
from osc.obs_scm.serviceinfo import Serviceinfo
service_dir = sys.argv[1]
os.chdir(service_dir)
service_info = Serviceinfo()
service_info.read(ElementTree.parse(f"{service_dir}/_service").getroot())
raise SystemExit(service_info.execute(service_dir, "all", verbose=True))
PY
mapfile -t service_archives < <(find "$service_dir" -maxdepth 1 \
-type f -name 'zupt-*.tar.gz' -print)
test "${#service_archives[@]}" -eq 1
bash scripts/check-source-only.sh --archive "${service_archives[0]}"
- name: Build source and binary RPMs with real checks
run: |
mkdir -p "$RUNNER_TEMP/release-rpm"
DIST_DIR="$RUNNER_TEMP/release-rpm" bash packaging/build-rpm.sh
- name: Run rpmlint without suppressions
shell: bash
run: |
set -Eeuo pipefail
rpmlint "$RUNNER_TEMP"/release-rpm/*.rpm 2>&1 \
| tee "$RUNNER_TEMP/rpmlint.log"
if grep -Eq ': E:' "$RUNNER_TEMP/rpmlint.log"; then
echo 'rpmlint reported one or more errors' >&2
exit 1
fi
- name: Compute sha256
id: sha
- name: Install, functionally test and uninstall the RPM
run: |
VER="${{ steps.ver.outputs.version }}"
SHA=$(sha256sum /tmp/zupt-$VER.tar.gz | awk '{print $1}')
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "$SHA zupt-$VER.tar.gz" > /tmp/zupt-$VER.tar.gz.sha256
- name: Create GitHub release
uses: softprops/action-gh-release@v2
rpm_file=$(find "$RUNNER_TEMP/release-rpm" -maxdepth 1 -type f \
-name '*.rpm' ! -name '*.src.rpm' -print -quit)
test -n "$rpm_file"
zypper --non-interactive install --allow-unsigned-rpm "$rpm_file"
test_home=/tmp/zupt-ci-user
useradd --create-home --home-dir "$test_home" --shell /bin/bash zupt-ci
runuser -u zupt-ci -- env HOME="$test_home" TMPDIR="$test_home" \
bash "$GITHUB_WORKSPACE/scripts/test-installed-zupt.sh" \
/usr/bin/zupt
test ! -e /usr/bin/vaptvupt
zypper --non-interactive remove zupt
test ! -e /usr/bin/zupt
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
files: |
/tmp/zupt-${{ steps.ver.outputs.version }}.tar.gz
/tmp/zupt-${{ steps.ver.outputs.version }}.tar.gz.sha256
body: |
## Zupt v${{ steps.ver.outputs.version }}
name: release-rpm
path: ${{ runner.temp }}/release-rpm/*.rpm
if-no-files-found: error
retention-days: 7
Reproducible source tarball.
gui-rpm-package:
name: Fedora noarch GUI RPM and SRPM gate
needs: [source-policy, build-and-test]
runs-on: ubuntu-24.04
container: fedora:latest
defaults:
run:
shell: bash
steps:
- name: Bootstrap checkout dependencies
run: dnf install -y ca-certificates git
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
lfs: false
submodules: false
- name: Trust the exact checked-out workspace
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Install native build, package, audit and GUI runtime tools
run: |
dnf install -y \
binutils cpio file findutils gcc git-core gzip libarchive make \
python3 python3-pyside6 rpm-build rpmdevtools tar unzip
- name: Build and content-test the GUI RPM and source RPM
run: |
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
test -n "$version"
mkdir -p "$RUNNER_TEMP/release-gui-rpm"
DIST_DIR="$RUNNER_TEMP/release-gui-rpm" bash packaging/build-gui-rpm.sh
gui_rpm="$RUNNER_TEMP/release-gui-rpm/zupt-gui-$version-1.noarch.rpm"
gui_srpm="$RUNNER_TEMP/release-gui-rpm/zupt-gui-$version-1.src.rpm"
test -s "$gui_rpm"
test -s "$gui_srpm"
test "$(rpm -qp --qf '%{NAME}' "$gui_rpm")" = zupt-gui
test "$(rpm -qp --qf '%{VERSION}-%{RELEASE}' "$gui_rpm")" = "$version-1"
test "$(rpm -qp --qf '%{ARCH}' "$gui_rpm")" = noarch
rpm -qp --requires "$gui_rpm" | grep -Fx "zupt >= $version"
test "$(rpm -qp --qf '%{NAME}' "$gui_srpm")" = zupt-gui
test "$(rpm -qp --qf '%{VERSION}-%{RELEASE}' "$gui_srpm")" = "$version-1"
test "$(rpm -qp --qf '%{SOURCEPACKAGE}' "$gui_srpm")" = 1
test "$(rpm -qp --qf '%{SOURCERPM}' "$gui_srpm")" = '(none)'
test "$(rpm -qpl "$gui_srpm" | wc -l)" -eq 2
rpm -qpl "$gui_srpm" | grep -Fx "zupt-gui-$version.tar.gz"
rpm -qpl "$gui_srpm" | grep -Fx zupt-gui.spec
- name: Build the matching Fedora CLI RPM
run: |
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
core_top="$RUNNER_TEMP/core-rpmbuild"
mkdir -p "$core_top"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
make DIST_TARBALL="$core_top/SOURCES/zupt-$version.tar.gz" dist
rpmbuild --define "_topdir $core_top" -ba packaging/rpm/zupt.spec
- name: Install and functionally test the GUI with the packaged CLI
run: |
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
test -n "$version"
core_rpm=$(find "$RUNNER_TEMP/core-rpmbuild/RPMS" -type f \
-name "zupt-$version-1.*.rpm" ! -name '*-debuginfo-*' \
! -name '*-debugsource-*' -print -quit)
gui_rpm="$RUNNER_TEMP/release-gui-rpm/zupt-gui-$version-1.noarch.rpm"
test -n "$core_rpm" && test -s "$gui_rpm"
dnf install -y "$core_rpm" "$gui_rpm"
bash scripts/test-installed-zupt.sh /usr/bin/zupt
QT_QPA_PLATFORM=offscreen zupt-gui --version | grep -Fx "zupt-gui $version"
test ! -e /usr/bin/vaptvupt
dnf remove -y zupt-gui zupt
test ! -e /usr/bin/zupt-gui
test ! -e /usr/bin/zupt
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-gui-rpm
path: ${{ runner.temp }}/release-gui-rpm/*.rpm
if-no-files-found: error
retention-days: 7
```
sha256: ${{ steps.sha.outputs.sha }}
```
linux-portable:
name: Linux x86_64 notice-bearing CLI tar.xz gate
needs: [source-policy, build-and-test]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
lfs: false
submodules: false
- name: Install build and archive tools
run: |
sudo apt-get update
sudo apt-get install -y build-essential binutils file python3 xz-utils
- name: Build and audit the native executable
run: |
test "$(uname -m)" = x86_64
make clean
make -j"$(nproc)" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
make V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
bash scripts/test-installed-zupt.sh "$PWD/zupt"
if readelf -d zupt | grep -Eq '(RPATH|RUNPATH)'; then
echo 'Linux portable binary contains RPATH/RUNPATH' >&2
exit 1
fi
mapfile -t needed < <(readelf -d zupt | sed -n 's/.*Shared library: \[\([^]]*\)\].*/\1/p')
((${#needed[@]} > 0))
for library in "${needed[@]}"; do
case $library in
libc.so.6|libm.so.6|libpthread.so.0) ;;
*) echo "unexpected Linux runtime dependency: $library" >&2; exit 1 ;;
esac
done
if ldd zupt | grep -Fq 'not found'; then
echo 'Linux portable binary has an unresolved runtime dependency' >&2
exit 1
fi
- name: Assemble and extracted-package-test the tar.xz
run: |
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
epoch=$(<.source-date-epoch)
root="$RUNNER_TEMP/linux-work/zupt-$version-linux-x86_64"
output="$RUNNER_TEMP/release-linux-x86_64/zupt-$version-linux-x86_64.tar.xz"
mkdir -p "$root" "$(dirname "$output")"
install -m 0755 zupt "$root/zupt"
install -m 0644 README.md CHANGELOG.md SECURITY.md THREAT_MODEL.md \
LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause \
LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE \
THIRD-PARTY-NOTICES.md "$root/"
tar --sort=name --mtime="@$epoch" --owner=0 --group=0 --numeric-owner \
-C "$(dirname "$root")" -cJf "$output" "$(basename "$root")"
extract=$(mktemp -d)
tar -xJf "$output" -C "$extract"
bash scripts/test-installed-zupt.sh \
"$extract/$(basename "$root")/zupt"
test "$(find "$extract/$(basename "$root")" -maxdepth 1 -type f | wc -l)" -eq 13
sha256sum "$output"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-linux-x86_64
path: ${{ runner.temp }}/release-linux-x86_64/*.tar.xz
if-no-files-found: error
retention-days: 7
See CHANGELOG.md for release notes.
gui-portable:
name: Source-only GUI portable ZIP gate
needs: [source-policy, build-and-test]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
lfs: false
submodules: false
- name: Install GUI smoke-test and archive tools
run: |
sudo apt-get update
sudo apt-get install -y build-essential file python3 python3-pyqt6 unzip zip
- name: Assemble, audit and execute the portable GUI source bundle
run: |
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
make -j"$(nproc)" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
root="$RUNNER_TEMP/gui-work/zupt-gui-$version-portable"
output="$RUNNER_TEMP/release-gui-portable/zupt-gui-$version-portable.zip"
mkdir -p "$root/assets" "$(dirname "$output")"
install -m 0644 gui/src/zupt_gui.py "$root/zupt_gui.py"
install -m 0755 packaging/portable/zupt-gui.sh \
packaging/portable/zupt-gui.command "$root/"
install -m 0644 packaging/portable/zupt-gui.bat "$root/"
install -m 0644 packaging/portable/README.txt "$root/README.txt"
install -m 0644 gui/assets/zupt-icon.png gui/assets/zupt.ico "$root/assets/"
install -m 0644 LICENSE-AGPL-3.0 gui/LICENSE-GUI CHANGELOG.md "$root/"
install -m 0644 gui/assets/README.md "$root/ASSET-PROVENANCE.md"
bash scripts/check-source-only.sh --tree "$root"
QT_QPA_PLATFORM=offscreen PATH="$PWD:$PATH" \
"$root/zupt-gui.sh" --version | grep -Fx "zupt-gui $version"
epoch=$(<.source-date-epoch)
find "$root" -exec touch -d "@$epoch" {} +
(cd "$(dirname "$root")" && zip -X -9 -r "$output" "$(basename "$root")")
extract=$(mktemp -d)
unzip -q "$output" -d "$extract"
bash scripts/check-source-only.sh --tree "$extract/$(basename "$root")"
QT_QPA_PLATFORM=offscreen PATH="$PWD:$PATH" \
"$extract/$(basename "$root")/zupt-gui.sh" --version | \
grep -Fx "zupt-gui $version"
sha256sum "$output"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-gui-portable
path: ${{ runner.temp }}/release-gui-portable/*.zip
if-no-files-found: error
retention-days: 7
### Verifying the tarball
```sh
sha256sum -c zupt-${{ steps.ver.outputs.version }}.tar.gz.sha256
```
### Building
```sh
tar xzf zupt-${{ steps.ver.outputs.version }}.tar.gz
cd zupt-${{ steps.ver.outputs.version }}
make
make test
sudo make install
```
target-packages:
name: Windows and macOS release gates
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
needs:
- source-policy
- build-and-test
- strict-warnings
- sanitizers
- static-analysis
- source-archive
- debian-package
- tumbleweed-rpm
- gui-rpm-package
- linux-portable
- gui-portable
uses: ./.github/workflows/cross-platform.yml
permissions:
contents: read

265
.github/workflows/cross-platform.yml vendored Normal file
View file

@ -0,0 +1,265 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
name: target release packages
on:
workflow_call:
workflow_dispatch:
permissions:
contents: read
jobs:
windows-x86_64:
name: Windows x86_64 package and smoke test
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- name: Check out the audited source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
lfs: false
submodules: false
- name: Install the Windows C toolchain
uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0
with:
msystem: UCRT64
update: true
install: >-
mingw-w64-ucrt-x86_64-binutils
mingw-w64-ucrt-x86_64-gcc
bsdtar
coreutils
diffutils
file
findutils
git
gzip
make
python
tar
unzip
zip
- name: Audit source before building
run: bash scripts/check-source-only.sh
- name: Build from source
run: |
test "$(uname -m)" = x86_64
make clean
make -j2 CC=gcc V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
- name: Run the source-only distribution checks on Windows
run: make CC=gcc V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
- name: Native CLI smoke and round-trip
run: |
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
if [[ -x ./zupt.exe ]]; then
exe=$PWD/zupt.exe
elif [[ -x ./zupt ]]; then
exe=$PWD/zupt
else
echo 'ZUPT executable was not produced' >&2
exit 1
fi
version_output=$("$exe" --version)
version_line=${version_output%%$'\n'*}
read -r product reported_version _ <<< "$version_line"
if [[ $product != zupt || $reported_version != "$version" ]]; then
echo 'native Windows executable version does not match include/zupt.h' >&2
exit 1
fi
"$exe" --help >/dev/null
if "$exe" --definitely-invalid-option >/dev/null 2>&1; then
echo 'invalid option returned success' >&2
exit 1
fi
test_root=$(mktemp -d)
trap 'chmod -R u+rwX "$test_root" 2>/dev/null || true; rm -rf -- "$test_root"' EXIT
output_dir="$test_root/saída-安全"
archive="$test_root/cópia-安全.zupt"
emoji_name=$'emoji-\xF0\x9F\x98\x80.bin'
mkdir -p "$test_root/input/subdir" "$output_dir"
printf 'Windows release smoke test\n' > "$test_root/input/café.txt"
printf 'UTF-8: café-安全\n' > "$test_root/input/subdir/ação-安全.txt"
printf 'emoji filename\n' > "$test_root/input/subdir/$emoji_name"
: > "$test_root/input/empty"
dd if=/dev/urandom of="$test_root/input/subdir/random.bin" bs=4096 count=4 2>/dev/null
(cd "$test_root" && "$exe" compress "$archive" input)
"$exe" test "$archive"
"$exe" list "$archive" > "$test_root/list.txt" 2>&1
"$exe" extract -o "$output_dir" "$archive"
diff -r "$test_root/input" "$output_dir/input"
python3 - "$test_root/list.txt" <<'PY'
import pathlib
import sys
listing = pathlib.Path(sys.argv[1]).read_bytes()
expected = {
"Latin-1": bytes.fromhex("636166c3a92e747874"),
"BMP": bytes.fromhex("61c3a7c3a36f2de5ae89e585a82e747874"),
"non-BMP": bytes.fromhex("656d6f6a692df09f98802e62696e"),
}
missing = [label for label, name in expected.items() if name not in listing]
if missing:
raise SystemExit("list output is missing exact UTF-8 names: " +
", ".join(missing))
PY
objdump -p "$exe" > "$test_root/imports.txt"
if grep -Eqi '(vendor[/\\]|libvuptsdk|libpqvaptvupt|libgcc_s|libstdc\+\+|libwinpthread|msys-2[.]0|cygwin1)[^[:space:]]*[.]dll' \
"$test_root/imports.txt"; then
echo 'Windows binary imports a non-system or vendored runtime' >&2
exit 1
fi
version_output=$(env PATH='/c/Windows/System32:/c/Windows' "$exe" --version)
version_line=${version_output%%$'\n'*}
read -r product reported_version _ <<< "$version_line"
if [[ $product != zupt || $reported_version != "$version" ]]; then
echo 'restricted-PATH Windows executable version does not match include/zupt.h' >&2
exit 1
fi
- name: Assemble Windows release files
run: |
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
bundle="out/work/zupt-$version-windows-x86_64"
mkdir -p out "$bundle"
if [[ -x ./zupt.exe ]]; then source_exe=./zupt.exe; else source_exe=./zupt; fi
install -m 0755 "$source_exe" "$bundle/zupt.exe"
install -m 0644 README.md CHANGELOG.md LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 \
LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 \
NOTICE THIRD-PARTY-NOTICES.md "$bundle/"
toolchain_prefix=${MINGW_PREFIX:-/ucrt64}
install -m 0644 \
"$toolchain_prefix/share/licenses/crt/COPYING" \
"$bundle/MINGW-CRT-COPYING.txt"
install -m 0644 \
"$toolchain_prefix/share/licenses/crt/COPYING.MinGW-w64-runtime.txt" \
"$bundle/COPYING.MinGW-w64-runtime.txt"
install -m 0644 \
"$toolchain_prefix/share/licenses/crt/COPYING.MinGW-w64.txt" \
"$bundle/COPYING.MinGW-w64.txt"
install -m 0644 \
"$toolchain_prefix/share/licenses/gcc-libs/COPYING3" \
"$bundle/GCC-COPYING3.txt"
install -m 0644 \
"$toolchain_prefix/share/licenses/gcc-libs/COPYING.RUNTIME" \
"$bundle/GCC-RUNTIME-LIBRARY-EXCEPTION.txt"
zip_path=$PWD/out/zupt-$version-windows-x86_64.zip
(cd out/work && zip -9 -r "$zip_path" \
"zupt-$version-windows-x86_64")
- name: Extract and functionally test the Windows ZIP
run: |
set -Eeuo pipefail
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
zip_path=$PWD/out/zupt-$version-windows-x86_64.zip
unzip -t "$zip_path"
extract_root=$(mktemp -d)
cleanup() {
chmod -R u+rwX "$extract_root" 2>/dev/null || true
rm -rf -- "$extract_root"
}
trap cleanup EXIT HUP INT TERM
unzip -q "$zip_path" -d "$extract_root"
for notice in MINGW-CRT-COPYING.txt COPYING.MinGW-w64-runtime.txt \
COPYING.MinGW-w64.txt GCC-COPYING3.txt \
GCC-RUNTIME-LIBRARY-EXCEPTION.txt; do
test -s "$extract_root/zupt-$version-windows-x86_64/$notice"
done
packaged_exe=$extract_root/zupt-$version-windows-x86_64/zupt.exe
test -x "$packaged_exe"
version_output=$(env PATH='/c/Windows/System32:/c/Windows' \
"$packaged_exe" --version)
version_line=${version_output%%$'\n'*}
read -r product reported_version _ <<< "$version_line"
if [[ $product != zupt || $reported_version != "$version" ]]; then
echo 'Windows ZIP executable version does not match include/zupt.h' >&2
exit 1
fi
env PATH='/c/Windows/System32:/c/Windows' "$packaged_exe" --help >/dev/null
mkdir -p "$extract_root/smoke/input" "$extract_root/smoke/saída-安全"
printf 'Windows ZIP package test\n' > "$extract_root/smoke/input/payload-ação-😀.txt"
(
cd "$extract_root/smoke"
env PATH='/c/Windows/System32:/c/Windows' \
"$packaged_exe" compress cópia-安全.zupt input
env PATH='/c/Windows/System32:/c/Windows' \
"$packaged_exe" test cópia-安全.zupt
env PATH='/c/Windows/System32:/c/Windows' \
"$packaged_exe" extract -o saída-安全 cópia-安全.zupt
)
cmp "$extract_root/smoke/input/payload-ação-😀.txt" \
"$extract_root/smoke/saída-安全/input/payload-ação-😀.txt"
- name: Upload tested Windows files
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-windows-x86_64
path: out/*.zip
if-no-files-found: error
retention-days: 7
macos-native:
name: macOS native DMG and installed-image test
runs-on: macos-latest
steps:
- name: Check out the audited source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
lfs: false
submodules: false
- name: Audit source before building
run: bash scripts/check-source-only.sh
- name: Build and validate the native DMG
run: |
mkdir -p out
DIST_DIR="$PWD/out" RUN_CHECKS=1 bash packaging/build-dmg.sh
- name: Mount and functionally test the packaged binary
run: |
dmg=$(find out -maxdepth 1 -type f -name '*.dmg' -print -quit)
test -n "$dmg"
mount_point=$(mktemp -d)
cleanup() {
hdiutil detach "$mount_point" >/dev/null 2>&1 || true
chmod -R u+rwX "$mount_point" 2>/dev/null || true
rm -rf -- "$mount_point"
}
trap cleanup EXIT HUP INT TERM
hdiutil attach -nobrowse -readonly -mountpoint "$mount_point" "$dmg" >/dev/null
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' \
include/zupt.h)
packaged_binary=$mount_point/ZUPT.app/Contents/MacOS/zupt
version_output=$("$packaged_binary" --version)
version_line=${version_output%%$'\n'*}
read -r product reported_version _ <<< "$version_line"
if [[ $product != zupt || $reported_version != "$version" ]]; then
echo 'mounted macOS executable version does not match include/zupt.h' >&2
exit 1
fi
bash packaging/build-dmg.sh --test-binary \
"$packaged_binary"
hdiutil detach "$mount_point"
trap - EXIT HUP INT TERM
rmdir "$mount_point"
- name: Upload tested macOS DMG
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-macos-native
path: out/*.dmg
if-no-files-found: error
retention-days: 7

656
.github/workflows/promote-release.yml vendored Normal file
View file

@ -0,0 +1,656 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
name: Promote a tested release
on:
workflow_dispatch:
inputs:
source_run_id:
description: Successful manually dispatched CI run that produced the assets
required: true
type: number
tag:
description: Existing annotated release tag, for example v5.2.8
required: true
type: string
permissions: {}
concurrency:
group: promote-release-${{ inputs.tag }}
cancel-in-progress: false
jobs:
promote:
name: Promote tested assets to the canonical GitHub release
runs-on: ubuntu-24.04
timeout-minutes: 45
permissions:
actions: read
contents: write
steps:
- name: Validate the tag and source CI run through the GitHub API
id: provenance
env:
GH_TOKEN: ${{ github.token }}
SOURCE_RUN_ID: ${{ inputs.source_run_id }}
RELEASE_TAG: ${{ inputs.tag }}
run: |
set -Eeuo pipefail
set +x
umask 077
[[ $SOURCE_RUN_ID =~ ^[1-9][0-9]*$ ]] || {
echo 'source_run_id must be a positive integer' >&2
exit 1
}
[[ $RELEASE_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo 'tag must have the form vX.Y.Z' >&2
exit 1
}
tag_ref_api="repos/$GITHUB_REPOSITORY/git/ref/tags/$RELEASE_TAG"
tag_object_type=$(gh api "$tag_ref_api" --jq '.object.type')
tag_object_sha=$(gh api "$tag_ref_api" --jq '.object.sha')
[[ $tag_object_type == tag && $tag_object_sha =~ ^[0-9a-f]{40}$ ]] || {
echo 'GitHub release ref is not an annotated tag' >&2
exit 1
}
tag_object_api="repos/$GITHUB_REPOSITORY/git/tags/$tag_object_sha"
target_type=$(gh api "$tag_object_api" --jq '.object.type')
peeled_sha=$(gh api "$tag_object_api" --jq '.object.sha')
[[ $target_type == commit && $peeled_sha =~ ^[0-9a-f]{40}$ ]] || {
echo 'annotated tag does not point directly to a commit' >&2
exit 1
}
run_api="repos/$GITHUB_REPOSITORY/actions/runs/$SOURCE_RUN_ID"
run_status=$(gh api "$run_api" --jq '.status')
run_conclusion=$(gh api "$run_api" --jq '.conclusion')
run_event=$(gh api "$run_api" --jq '.event')
run_head_branch=$(gh api "$run_api" --jq '.head_branch // ""')
run_workflow_id=$(gh api "$run_api" --jq '.workflow_id')
run_sha=$(gh api "$run_api" --jq '.head_sha')
run_repository=$(gh api "$run_api" --jq '.head_repository.full_name // ""')
workflow_path=$(gh api \
"repos/$GITHUB_REPOSITORY/actions/workflows/$run_workflow_id" \
--jq '.path')
[[ $run_status == completed && $run_conclusion == success ]] || {
echo 'source CI run is not completed successfully' >&2
exit 1
}
[[ $run_event == workflow_dispatch ]] || {
echo 'source CI run must have been started with workflow_dispatch' >&2
exit 1
}
[[ $run_head_branch == "$RELEASE_TAG" ]] || {
echo 'source CI run must have been dispatched from the release tag' >&2
exit 1
}
[[ $workflow_path == .github/workflows/ci.yml ]] || {
echo 'source run did not execute .github/workflows/ci.yml' >&2
exit 1
}
[[ $run_repository == "$GITHUB_REPOSITORY" ]] || {
echo 'source CI run belongs to a different head repository' >&2
exit 1
}
[[ $run_sha =~ ^[0-9a-f]{40}$ && $run_sha == "$peeled_sha" ]] || {
echo 'source CI head SHA does not match the peeled release tag' >&2
exit 1
}
artifact_json=$RUNNER_TEMP/source-run-artifacts.json
gh api "$run_api/artifacts?per_page=100" > "$artifact_json"
python3 - "$artifact_json" <<'PY'
import json
import pathlib
import sys
payload = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8"))
expected = {
"release-source",
"release-deb",
"release-rpm",
"release-gui-deb",
"release-gui-rpm",
"release-linux-x86_64",
"release-gui-portable",
"release-windows-x86_64",
"release-macos-native",
}
artifacts = payload.get("artifacts", [])
names = [artifact.get("name", "") for artifact in artifacts]
if payload.get("total_count") != len(expected):
raise SystemExit("source CI run artifact count mismatch")
if set(names) != expected or len(names) != len(set(names)):
raise SystemExit("source CI run artifact-name allowlist mismatch")
if any(artifact.get("expired") for artifact in artifacts):
raise SystemExit("one or more source CI artifacts have expired")
PY
{
printf 'head_sha=%s\n' "$peeled_sha"
printf 'tag_object_sha=%s\n' "$tag_object_sha"
printf 'tag=%s\n' "$RELEASE_TAG"
} >> "$GITHUB_OUTPUT"
- name: Check out the exact tested commit without persisted credentials
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ steps.provenance.outputs.head_sha }}
persist-credentials: false
fetch-depth: 0
lfs: false
submodules: false
- name: Confirm the local annotated tag and source version
id: release
env:
RELEASE_TAG: ${{ steps.provenance.outputs.tag }}
EXPECTED_SHA: ${{ steps.provenance.outputs.head_sha }}
EXPECTED_TAG_OBJECT: ${{ steps.provenance.outputs.tag_object_sha }}
run: |
set -Eeuo pipefail
[[ $(git rev-parse HEAD) == "$EXPECTED_SHA" ]] || {
echo 'checked-out commit differs from the validated source run' >&2
exit 1
}
[[ $(git cat-file -t "refs/tags/$RELEASE_TAG") == tag ]] || {
echo 'checked-out release ref is not an annotated tag' >&2
exit 1
}
[[ $(git rev-parse "refs/tags/$RELEASE_TAG") == "$EXPECTED_TAG_OBJECT" ]] || {
echo 'local annotated tag object differs from the validated GitHub tag' >&2
exit 1
}
[[ $(git rev-parse "$RELEASE_TAG^{commit}") == "$EXPECTED_SHA" ]] || {
echo 'local peeled tag does not match the tested commit' >&2
exit 1
}
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' \
include/zupt.h)
[[ -n $version && $RELEASE_TAG == "v$version" ]] || {
echo 'tag does not match include/zupt.h' >&2
exit 1
}
printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT"
- name: Install validation tools
run: |
sudo apt-get update
sudo apt-get install -y file libarchive-tools python3 python3-pyqt6 rpm unzip xz-utils
- name: Download the exact source artifact from the validated run
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-source
path: ${{ runner.temp }}/incoming/release-source
repository: ${{ github.repository }}
run-id: ${{ inputs.source_run_id }}
github-token: ${{ github.token }}
- name: Download the exact DEB artifact from the validated run
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-deb
path: ${{ runner.temp }}/incoming/release-deb
repository: ${{ github.repository }}
run-id: ${{ inputs.source_run_id }}
github-token: ${{ github.token }}
- name: Download the exact RPM artifact from the validated run
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-rpm
path: ${{ runner.temp }}/incoming/release-rpm
repository: ${{ github.repository }}
run-id: ${{ inputs.source_run_id }}
github-token: ${{ github.token }}
- name: Download the exact GUI DEB artifact from the validated run
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-gui-deb
path: ${{ runner.temp }}/incoming/release-gui-deb
repository: ${{ github.repository }}
run-id: ${{ inputs.source_run_id }}
github-token: ${{ github.token }}
- name: Download the exact GUI RPM artifacts from the validated run
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-gui-rpm
path: ${{ runner.temp }}/incoming/release-gui-rpm
repository: ${{ github.repository }}
run-id: ${{ inputs.source_run_id }}
github-token: ${{ github.token }}
- name: Download the exact Linux tar.xz artifact from the validated run
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-linux-x86_64
path: ${{ runner.temp }}/incoming/release-linux-x86_64
repository: ${{ github.repository }}
run-id: ${{ inputs.source_run_id }}
github-token: ${{ github.token }}
- name: Download the exact portable GUI source bundle
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-gui-portable
path: ${{ runner.temp }}/incoming/release-gui-portable
repository: ${{ github.repository }}
run-id: ${{ inputs.source_run_id }}
github-token: ${{ github.token }}
- name: Download the exact Windows artifact from the validated run
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-windows-x86_64
path: ${{ runner.temp }}/incoming/release-windows-x86_64
repository: ${{ github.repository }}
run-id: ${{ inputs.source_run_id }}
github-token: ${{ github.token }}
- name: Download the exact macOS artifact from the validated run
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-macos-native
path: ${{ runner.temp }}/incoming/release-macos-native
repository: ${{ github.repository }}
run-id: ${{ inputs.source_run_id }}
github-token: ${{ github.token }}
- name: Enforce the allowlist and validate every release format
env:
RELEASE_TAG: ${{ steps.provenance.outputs.tag }}
VERSION: ${{ steps.release.outputs.version }}
run: |
set -Eeuo pipefail
umask 077
export LC_ALL=C
incoming=$RUNNER_TEMP/incoming
asset_dir=$RUNNER_TEMP/release-assets
mkdir -p "$asset_dir"
artifact_names=(
release-source
release-deb
release-rpm
release-gui-deb
release-gui-rpm
release-linux-x86_64
release-gui-portable
release-windows-x86_64
release-macos-native
)
expected_dirs=$RUNNER_TEMP/artifact-dirs.expected
actual_dirs=$RUNNER_TEMP/artifact-dirs.actual
printf '%s\0' "${artifact_names[@]}" | LC_ALL=C sort -z > "$expected_dirs"
find "$incoming" -mindepth 1 -maxdepth 1 -type d -printf '%f\0' | \
LC_ALL=C sort -z > "$actual_dirs"
cmp "$expected_dirs" "$actual_dirs" || {
echo 'downloaded artifact directory allowlist mismatch' >&2
exit 1
}
if find "$incoming" -mindepth 1 -maxdepth 1 ! -type d -print -quit | \
grep -q .; then
echo 'unexpected non-directory entry in artifact download root' >&2
exit 1
fi
if find "$incoming" -mindepth 2 ! -type f -print -quit | grep -q .; then
echo 'artifact contains a directory, symlink, or special file' >&2
exit 1
fi
source_name="zupt-$VERSION.tar.gz"
source_sidecar="$source_name.sha256"
deb_name="zupt_${VERSION}_amd64.deb"
rpm_name="zupt-$VERSION-0.x86_64.rpm"
srpm_name="zupt-$VERSION-0.src.rpm"
gui_deb_name="zupt-gui_${VERSION}_all.deb"
gui_rpm_name="zupt-gui-$VERSION-1.noarch.rpm"
gui_srpm_name="zupt-gui-$VERSION-1.src.rpm"
linux_tar_name="zupt-$VERSION-linux-x86_64.tar.xz"
gui_portable_name="zupt-gui-$VERSION-portable.zip"
windows_zip_name="zupt-$VERSION-windows-x86_64.zip"
dmg_relative=()
for arch in x86_64 arm64; do
candidate="release-macos-native/ZUPT-$VERSION-macOS-$arch.dmg"
[[ ! -f $incoming/$candidate || -L $incoming/$candidate ]] || \
dmg_relative+=("$candidate")
done
((${#dmg_relative[@]} == 1)) || {
echo 'expected exactly one native macOS DMG' >&2
exit 1
}
expected_relative=(
"release-source/$source_name"
"release-source/$source_sidecar"
"release-deb/$deb_name"
"release-rpm/$rpm_name"
"release-rpm/$srpm_name"
"release-gui-deb/$gui_deb_name"
"release-gui-rpm/$gui_rpm_name"
"release-gui-rpm/$gui_srpm_name"
"release-linux-x86_64/$linux_tar_name"
"release-gui-portable/$gui_portable_name"
"release-windows-x86_64/$windows_zip_name"
"${dmg_relative[0]}"
)
expected_relative_list=$RUNNER_TEMP/artifact-files.expected
actual_relative_list=$RUNNER_TEMP/artifact-files.actual
printf '%s\0' "${expected_relative[@]}" | LC_ALL=C sort -z \
> "$expected_relative_list"
find "$incoming" -mindepth 2 -type f -printf '%P\0' | LC_ALL=C sort -z \
> "$actual_relative_list"
cmp "$expected_relative_list" "$actual_relative_list" || {
echo 'downloaded file allowlist mismatch' >&2
exit 1
}
expected_assets=()
for relative in "${expected_relative[@]}"; do
name=${relative#*/}
cp -- "$incoming/$relative" "$asset_dir/$name"
expected_assets+=("$name")
done
expected_list=$RUNNER_TEMP/release-assets.expected
printf '%s\0' "${expected_assets[@]}" | LC_ALL=C sort -z > "$expected_list"
source_tar=$asset_dir/$source_name
sidecar=$asset_dir/$source_sidecar
actual_source_sha=$(sha256sum "$source_tar" | awk '{print $1}')
[[ $(<"$sidecar") == "$actual_source_sha $source_name" ]] || {
echo 'source archive sidecar is not the exact expected SHA-256 record' >&2
exit 1
}
(cd "$asset_dir" && sha256sum -c -- "$source_sidecar")
file "$source_tar" | grep -Eqi 'gzip compressed data'
tar -tzf "$source_tar" >/dev/null
bash scripts/check-source-only.sh --archive "$source_tar"
archive_version=$(tar -xOf "$source_tar" \
"zupt-$VERSION/include/zupt.h" | sed -n \
's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p')
[[ $archive_version == "$VERSION" && $RELEASE_TAG == "v$archive_version" ]] || {
echo 'source archive version does not match the release tag' >&2
exit 1
}
deb=$asset_dir/$deb_name
dpkg-deb --info "$deb" >/dev/null
[[ $(dpkg-deb -f "$deb" Package) == zupt ]]
[[ $(dpkg-deb -f "$deb" Version) == "$VERSION" ]]
[[ $(dpkg-deb -f "$deb" Architecture) == amd64 ]]
rpm_file=$asset_dir/$rpm_name
[[ $(rpm -qp --qf '%{NAME}' "$rpm_file") == zupt ]]
[[ $(rpm -qp --qf '%{VERSION}' "$rpm_file") == "$VERSION" ]]
[[ $(rpm -qp --qf '%{RELEASE}' "$rpm_file") == 0 ]]
[[ $(rpm -qp --qf '%{ARCH}' "$rpm_file") == x86_64 ]]
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$rpm_file") == '(none)' ]]
[[ $(rpm -qp --qf '%{SOURCERPM}' "$rpm_file") == "$srpm_name" ]]
srpm=$asset_dir/$srpm_name
[[ $(rpm -qp --qf '%{NAME}' "$srpm") == zupt ]]
[[ $(rpm -qp --qf '%{VERSION}' "$srpm") == "$VERSION" ]]
[[ $(rpm -qp --qf '%{RELEASE}' "$srpm") == 0 ]]
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$srpm") == 1 ]]
[[ $(rpm -qp --qf '%{SOURCERPM}' "$srpm") == '(none)' ]]
[[ $(rpm -qpl "$srpm" | wc -l) -eq 2 ]]
rpm -qpl "$srpm" | grep -Fx "zupt-$VERSION.tar.gz"
rpm -qpl "$srpm" | grep -Fx zupt.spec
gui_deb=$asset_dir/$gui_deb_name
dpkg-deb --info "$gui_deb" >/dev/null
[[ $(dpkg-deb -f "$gui_deb" Package) == zupt-gui ]]
[[ $(dpkg-deb -f "$gui_deb" Version) == "$VERSION" ]]
[[ $(dpkg-deb -f "$gui_deb" Architecture) == all ]]
gui_rpm=$asset_dir/$gui_rpm_name
[[ $(rpm -qp --qf '%{NAME}' "$gui_rpm") == zupt-gui ]]
[[ $(rpm -qp --qf '%{VERSION}' "$gui_rpm") == "$VERSION" ]]
[[ $(rpm -qp --qf '%{RELEASE}' "$gui_rpm") == 1 ]]
[[ $(rpm -qp --qf '%{ARCH}' "$gui_rpm") == noarch ]]
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$gui_rpm") == '(none)' ]]
[[ $(rpm -qp --qf '%{SOURCERPM}' "$gui_rpm") == "$gui_srpm_name" ]]
rpm -qp --requires "$gui_rpm" | grep -Fx "zupt >= $VERSION"
gui_srpm=$asset_dir/$gui_srpm_name
[[ $(rpm -qp --qf '%{NAME}' "$gui_srpm") == zupt-gui ]]
[[ $(rpm -qp --qf '%{VERSION}' "$gui_srpm") == "$VERSION" ]]
[[ $(rpm -qp --qf '%{RELEASE}' "$gui_srpm") == 1 ]]
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$gui_srpm") == 1 ]]
[[ $(rpm -qp --qf '%{SOURCERPM}' "$gui_srpm") == '(none)' ]]
[[ $(rpm -qpl "$gui_srpm" | wc -l) -eq 2 ]]
rpm -qpl "$gui_srpm" | grep -Fx "zupt-gui-$VERSION.tar.gz"
rpm -qpl "$gui_srpm" | grep -Fx zupt-gui.spec
linux_tar=$asset_dir/$linux_tar_name
python3 - "$linux_tar" "zupt-$VERSION-linux-x86_64" <<'PY'
import pathlib
import sys
import tarfile
archive = pathlib.Path(sys.argv[1])
root = sys.argv[2]
expected_files = {
"zupt", "README.md", "CHANGELOG.md", "SECURITY.md",
"THREAT_MODEL.md", "LICENSE", "LICENSE-AGPL-3.0",
"LICENSE-GPL-3.0", "LICENSE-BSD-2-Clause",
"LICENSE-BSD-3-Clause", "LICENSE-CC0-1.0", "NOTICE",
"THIRD-PARTY-NOTICES.md",
}
with tarfile.open(archive, "r:xz") as package:
members = package.getmembers()
names = [member.name for member in members]
if len(names) != len(set(names)):
raise SystemExit("duplicate Linux tar member")
actual_files = set()
for member in members:
path = pathlib.PurePosixPath(member.name)
if (path.is_absolute() or ".." in path.parts or not path.parts or
path.parts[0] != root or member.issym() or member.islnk() or
not (member.isdir() or member.isfile())):
raise SystemExit("unsafe Linux tar member")
if member.isfile():
actual_files.add("/".join(path.parts[1:]))
if actual_files != expected_files:
raise SystemExit("Linux tar member allowlist mismatch")
PY
linux_extract=$RUNNER_TEMP/linux-package
mkdir -p "$linux_extract"
tar -xJf "$linux_tar" -C "$linux_extract"
linux_binary="$linux_extract/zupt-$VERSION-linux-x86_64/zupt"
file "$linux_binary" | grep -Eqi 'ELF.*executable'
bash scripts/test-installed-zupt.sh "$linux_binary"
gui_portable=$asset_dir/$gui_portable_name
python3 - "$gui_portable" "zupt-gui-$VERSION-portable" <<'PY'
import pathlib
import sys
import zipfile
archive = pathlib.Path(sys.argv[1])
root = sys.argv[2]
expected = {
f"{root}/", f"{root}/assets/", f"{root}/zupt_gui.py",
f"{root}/zupt-gui.sh", f"{root}/zupt-gui.command",
f"{root}/zupt-gui.bat", f"{root}/README.txt",
f"{root}/assets/zupt-icon.png", f"{root}/assets/zupt.ico",
f"{root}/LICENSE-AGPL-3.0", f"{root}/LICENSE-GUI",
f"{root}/ASSET-PROVENANCE.md", f"{root}/CHANGELOG.md",
}
with zipfile.ZipFile(archive) as package:
names = package.namelist()
if len(names) != len(set(names)) or set(names) != expected:
raise SystemExit("portable GUI ZIP member allowlist mismatch")
for name in names:
path = pathlib.PurePosixPath(name)
if path.is_absolute() or ".." in path.parts or path.parts[0] != root:
raise SystemExit("unsafe portable GUI ZIP member")
PY
bash scripts/check-source-only.sh --archive "$gui_portable"
gui_extract=$RUNNER_TEMP/gui-portable
mkdir -p "$gui_extract"
unzip -q "$gui_portable" -d "$gui_extract"
QT_QPA_PLATFORM=offscreen ZUPT_BIN="$linux_binary" \
"$gui_extract/zupt-gui-$VERSION-portable/zupt-gui.sh" --version | \
grep -Fx "zupt-gui $VERSION"
windows_zip=$asset_dir/$windows_zip_name
unzip -t "$windows_zip" >/dev/null
python3 - "$windows_zip" "zupt-$VERSION-windows-x86_64" <<'PY'
import pathlib
import sys
import zipfile
archive = pathlib.Path(sys.argv[1])
root = sys.argv[2]
with zipfile.ZipFile(archive) as package:
names = package.namelist()
if len(names) != len(set(names)):
raise SystemExit("duplicate Windows ZIP member")
expected = {
f"{root}/",
f"{root}/zupt.exe",
f"{root}/README.md",
f"{root}/CHANGELOG.md",
f"{root}/LICENSE",
f"{root}/LICENSE-AGPL-3.0",
f"{root}/LICENSE-GPL-3.0",
f"{root}/LICENSE-BSD-2-Clause",
f"{root}/LICENSE-BSD-3-Clause",
f"{root}/LICENSE-CC0-1.0",
f"{root}/NOTICE",
f"{root}/THIRD-PARTY-NOTICES.md",
f"{root}/MINGW-CRT-COPYING.txt",
f"{root}/COPYING.MinGW-w64-runtime.txt",
f"{root}/COPYING.MinGW-w64.txt",
f"{root}/GCC-COPYING3.txt",
f"{root}/GCC-RUNTIME-LIBRARY-EXCEPTION.txt",
}
if set(names) != expected:
raise SystemExit("Windows ZIP member allowlist mismatch")
for name in names:
path = pathlib.PurePosixPath(name)
if (path.is_absolute() or "\\" in name or ".." in path.parts or
not path.parts or path.parts[0] != root):
raise SystemExit("unsafe or unexpected Windows ZIP member")
executable = f"{root}/zupt.exe"
if names.count(executable) != 1:
raise SystemExit("Windows ZIP executable is missing or duplicated")
for notice in (
f"{root}/MINGW-CRT-COPYING.txt",
f"{root}/COPYING.MinGW-w64-runtime.txt",
f"{root}/COPYING.MinGW-w64.txt",
f"{root}/GCC-COPYING3.txt",
f"{root}/GCC-RUNTIME-LIBRARY-EXCEPTION.txt",
):
if not package.read(notice):
raise SystemExit("Windows toolchain notice is empty")
PY
unzip -p "$windows_zip" \
"zupt-$VERSION-windows-x86_64/zupt.exe" \
> "$RUNNER_TEMP/windows-zip-zupt.exe"
python3 - "$RUNNER_TEMP/windows-zip-zupt.exe" <<'PY'
import pathlib
import struct
import sys
executable = pathlib.Path(sys.argv[1])
with executable.open("rb") as stream:
header = stream.read(64)
if len(header) != 64 or header[:2] != b"MZ":
raise SystemExit("Windows ZIP executable lacks MZ magic")
pe_offset = struct.unpack_from("<I", header, 0x3C)[0]
stream.seek(pe_offset)
if stream.read(4) != b"PE\0\0":
raise SystemExit("Windows ZIP executable lacks PE signature")
PY
dmg_name=${dmg_relative[0]#*/}
dmg=$asset_dir/$dmg_name
python3 - "$dmg" <<'PY'
import pathlib
import sys
image = pathlib.Path(sys.argv[1])
with image.open("rb") as stream:
stream.seek(-512, 2)
if stream.read(4) != b"koly":
raise SystemExit("DMG lacks the UDIF trailer magic")
PY
checksum_tmp=$RUNNER_TEMP/SHA256SUMS
(cd "$asset_dir" && xargs -0 sha256sum < "$expected_list") \
> "$checksum_tmp"
mv "$checksum_tmp" "$asset_dir/SHA256SUMS"
(cd "$asset_dir" && sha256sum -c SHA256SUMS)
cp "$expected_list" "$RUNNER_TEMP/release-assets.list"
printf 'SHA256SUMS\0' >> "$RUNNER_TEMP/release-assets.list"
echo 'All downloaded release assets match the exact allowlist and formats.'
- name: Refuse to mutate an existing GitHub release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.provenance.outputs.tag }}
run: |
set -Eeuo pipefail
set +x
umask 077
existing_tags=$RUNNER_TEMP/github-release-tags
gh api --paginate "repos/$GITHUB_REPOSITORY/releases" \
--jq '.[].tag_name' > "$existing_tags"
if grep -Fxq -- "$RELEASE_TAG" "$existing_tags"; then
echo 'GitHub release already exists; refusing to replace or add assets' >&2
exit 1
fi
- name: Publish the already-tested byte-identical asset set
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.provenance.outputs.tag }}
RELEASE_COMMIT: ${{ steps.provenance.outputs.head_sha }}
VERSION: ${{ steps.release.outputs.version }}
SOURCE_RUN_ID: ${{ inputs.source_run_id }}
run: |
set -Eeuo pipefail
set +x
umask 077
asset_dir=$RUNNER_TEMP/release-assets
(cd "$asset_dir" && sha256sum -c SHA256SUMS)
mapfile -d '' -t asset_names < "$RUNNER_TEMP/release-assets.list"
release_assets=()
for name in "${asset_names[@]}"; do
path=$asset_dir/$name
[[ -f $path && ! -L $path ]] || {
printf 'validated release asset disappeared or changed type: %q\n' \
"$name" >&2
exit 1
}
release_assets+=("$path")
done
cat > "$RUNNER_TEMP/release-notes.md" <<EOF
ZUPT $VERSION was built and tested by manually dispatched CI run
https://github.com/$GITHUB_REPOSITORY/actions/runs/$SOURCE_RUN_ID
for annotated tag $RELEASE_TAG at commit $RELEASE_COMMIT. The job
definitions and logs in that run record the runner images,
architectures, toolchains, results, and explicit skips.
The attached source archive, CLI DEB/RPM/source RPM, GUI
DEB/RPM/source RPM, notice-bearing Linux x86_64 tar.xz, source-only
portable GUI ZIP, Windows CLI ZIP, and native macOS CLI DMG are the
exact artifacts validated by that run. SHA256SUMS records every
attached payload asset. GitHub is the canonical upstream release.
Binary packages are release-page assets only. The Git tree and source
archive remain source-only, built with WITH_SDK=0 and WITH_PQBOX=0.
AppImage and bare executables are intentionally excluded: the former
lacks an audited runtime source/relink handoff, while the latter does
not carry the required license and notice payload beside the program.
EOF
gh release create "$RELEASE_TAG" "${release_assets[@]}" \
--repo "$GITHUB_REPOSITORY" --draft --verify-tag \
--title "ZUPT $VERSION" \
--notes-file "$RUNNER_TEMP/release-notes.md"
gh release edit "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --draft=false

44
.gitignore vendored
View file

@ -1,39 +1,65 @@
# Build outputs
/vaptvupt
/zupt
# Optional compatibility symlink produced by legacy-alias builds.
/vaptvupt
*.o
*.obj
*.a
*.la
*.so
*.so.*
*.dll
*.dylib
*.exe
*.obj
*.com
*.class
*.jar
*.war
*.wasm
*.lib
*.pdb
*.out
*.bin
*.elf
*.ko
*.mod
# Python bytecode caches
__pycache__/
*.pyc
*.pyo
# Coverage / profiling
*.gcda
*.gcno
*.gcov
*.profraw
*.profdata
/coverage/
# CMake / out-of-tree build dirs
/build/
/dist/
/out/
/target/
/sdk/build/
/cmake-build-*/
CMakeCache.txt
CMakeFiles/
# Distribution tarballs and packages (published as release assets, not committed)
/*.tar.gz
/*.tar.xz
/*.zip
/*.deb
/*.rpm
/*.AppImage
*.tar.gz
*.tar.xz
*.zip
*.deb
*.rpm
*.AppImage
*.msi
*.apk
*.ipa
*.dmg
*.AppDir/
SHA256SUMS
SHA256SUMS.txt
# Test/scratch binaries
@ -43,6 +69,8 @@ SHA256SUMS.txt
# Editor / OS noise
*.swp
*.tmp
*.log
*~
.DS_Store
.idea/

1
.source-date-epoch Normal file
View file

@ -0,0 +1 @@
1788134400

357
AUDIT.md Normal file
View file

@ -0,0 +1,357 @@
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
# ZUPT 5.2.8 audit guide and finding history
This document describes review surfaces and reproducible checks. It is an
upstream self-review, not an independent audit, certification, or guarantee.
`SECURITY.md` defines reporting policy and `THREAT_MODEL.md` defines the
security boundary.
## 5.2.8 scope
The baseline scope is the source-only CLI and its bundled source codec:
- first-party C and headers under `src/` and `include/`;
- textual architecture-specific source under `jasmin/`, distinguishing
compiler-generated output from separately identified hand-written assembly;
- VaptVupt codec source at release 2.65.3, with provenance and licensing in
`THIRD-PARTY-NOTICES.md`;
- CLI tests, source scanner, build system, CI, and packaging recipes;
- the Python GUI source as a caller of the CLI.
The baseline is built with `WITH_SDK=0 WITH_PQBOX=0`. The optional system
`libvuptsdk` and `libpqvaptvupt` implementations are outside this scope unless
their exact source packages and versions are added to an assessment. Assembly
under `jasmin/` is disabled by default and is a separate `WITH_JASMIN=1` build
choice on supported x86_64 compiler targets. Generated files must record their
compiler provenance; hand-written files must not be represented as compiler
output.
## Source-only review
The 5.2.8 baseline retains the source-only boundary introduced in 5.2.2, which
removed incomplete SDK/PQBOX header snapshots and local precompiled-library
expectations. Git and new upstream source
archives are intended to contain no compiled executable, object, shared/static
library, distribution package, unsafe symlink, or unresolved Git LFS pointer.
Run the same scanner over each representation:
```sh
# tracked files and working tree
scripts/check-source-only.sh
# committed Git tree or immutable tag
scripts/check-source-only.sh --tag HEAD
scripts/check-source-only.sh --tag v5.2.8
# generated source archive
scripts/check-source-only.sh --archive /path/to/zupt-5.2.8.tar.gz
```
The scanner checks extensions and magic bytes, nested archives, symlink targets,
LFS pointers, generated compiler output, and stale vendor-library references.
It reports paths without printing file contents. Its negative tests include
renamed ELF, ar, PE/MZ, versioned `.so`, RPM/DEB/AppImage, escaping symlinks,
and LFS pointers; textual assembly is a permitted source type.
Archive inspection must also fail closed at bounded recursion depth, member
count, individual expanded size, and total expanded size so a nested archive or
decompression bomb cannot turn the release scanner into an unbounded resource
consumer. On committed Linux candidate `ff99770`, this hardening and its
adversarial fixtures passed all 39 source-only scanner cases, including GNU
thin-archive and safe-diagnostic-path cases.
An unknown `.bin` fails by default. A necessary binary data fixture can be
declared only through `--data-manifest`, with four tab-separated fields for
path, purpose, provenance, and SPDX license. That manifest does not override a
compiled/executable magic finding.
An artifact is not clean merely because it has a harmless extension. Conversely,
binary image data is not executable code: the documented GUI icon assets are
necessary data and are reviewed separately for purpose, provenance, and license.
## Reproducible project checks
The baseline gates are:
```sh
make clean
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 1)" \
WITH_SDK=0 WITH_PQBOX=0 V=1
make WITH_SDK=0 WITH_PQBOX=0 check
make WITH_SDK=0 WITH_PQBOX=0 test-all
make sdk-test
```
Relevant review layers include:
| Layer | Evidence source | Interpretation |
|---|---|---|
| Source boundary | `scripts/check-source-only.sh`, `tests/test_source_only.sh` | Fails on prohibited artifacts or unsafe source layout |
| Primitive vectors | `tests/test_vectors.c` | Known-answer regression for implemented primitives |
| ML-KEM interoperability | `tests/test_mlkem_fips203.sh` | Runs only with an ML-KEM-capable OpenSSL 3.5+; otherwise `SKIP` |
| Archive behavior | quick/regression, traversal, argument-order, block-swap, nonce, and exact-size tests | Exercises current parser, integrity, and round-trip properties |
| Password sources | `tests/test_password_sources.sh` | Exercises password-file, inherited-descriptor and explicit-prompt rejection paths without logging password contents |
| Key files | native key regressions | Exercises no-replace private-file creation, POSIX mode `0600`/Windows current-user-only DACL, failed-partial behavior, checksum, and exact ZKEY/ZPQK version/flags/reserved/size/role validation |
| SDK key publication | `make sdk-test` | Exercises atomic descriptor/handle-backed key copies, POSIX private/public modes, and symlink/hardlink target preservation; this now runs in `release-check` and hosted GCC/Clang Linux CI |
| Terminal output | archive-comment regression | Requires displayed untrusted comments to contain no raw terminal-control sequence |
| Prompt cleanup | PTY signal regression | Requires handled POSIX interruption to restore the saved terminal state |
| Sanitizers | `make test-asan-run` | Builds and executes separate ASan/UBSan/LSan evidence where supported; not a substitute for normal tests |
| Static analysis | compiler analyzer, cppcheck, scan-build, clang-tidy where installed | Tool-specific findings must be reviewed, not suppressed globally |
| Shell and metadata | shellcheck, SPDX/license checks, packaging syntax checks | Applies only when the named tool actually executed |
| Source reproducibility | two `make dist` runs with identical committed input and epoch | Requires equal SHA-256 digests and clean archive scans |
| Installed package | target-native package inspection and `scripts/test-installed-zupt.sh` | Applies only to the exact OS/release/architecture tested |
This table identifies evidence layers rather than results. Missing tools, OBS
access, other architectures, Leap, and SLE must not be reported as passing
without evidence.
## Prior 5.2.2 committed-candidate local Linux evidence
The following upstream self-audit results apply only to the 5.2.2 candidate at
commit `ff99770` on the recorded local Linux environments. The immutable 5.2.2
tag was not promoted after post-tag CI integration failures. These results are
not independent certification, a 5.2.8 result, or evidence that release assets
were published.
| Gate | Result | Recorded evidence |
|---|---|---|
| Full project gate | PASS | `make release-check` completed successfully on `ff99770`, including the late key-file, terminal-comment, password-prompt, explicit-Bash, and scanner-limit regressions. |
| Packaging policy/syntax | PASS | `PASS=49 FAIL=0 SKIP=0`. |
| Source-only scanner adversarial suite | PASS | 39/39, including GNU thin archives, bounded archive expansion, and safe diagnostic cases. |
| Strict compilers and compiler analyzer | PASS | GCC and Clang strict builds passed; GCC `-fanalyzer` passed. |
| Static-analysis suite | PASS | 9/9 in the full tool-enabled run. A separate reduced-environment `release-check` run completed six available checks and reported `cppcheck` unavailable; unavailable tooling was not relabelled as a pass. |
| Dynamic analysis | PASS | ASan, UBSan, and LSan runs passed. |
| Mutation fuzzing | PASS | 1,000 mutation iterations completed without a sanitizer-detected crash. |
An earlier off-screen GUI smoke run remains supporting evidence, but is not
represented as an exact-`ff99770` GUI-package result. The immutable 5.2.3
candidate was not promoted because its source-policy test assumed LF for a
Windows `.bat` checkout that correctly used CRLF.
## Prior 5.2.4 exact-tag integration evidence
GitHub Actions exact-tag run `33431386002` completed 12 jobs successfully. Its
sole failed job was the openSUSE gate: the standalone `Serviceinfo` harness did
not change into the directory containing `_service` before executing the
service chain. Dependent native Windows and macOS jobs were therefore skipped,
and v5.2.4 was not promoted. The tag and its record remain immutable.
A separate local openSUSE Tumbleweed reproduction resolved the explicit
`refs/tags/v5.2.4` revision to the tagged commit and, after
`os.chdir(service_dir)`, completed `obs_scm`, `tar`, and `recompress`. It
produced exactly one `zupt-5.2.4.tar.gz`, which passed the source-only scanner.
This isolates a release/test harness defect; it is not evidence of a product,
archive-format, cryptographic, codec, or SDK ABI change. It also does not turn
the skipped native jobs into passes or transfer any result to 5.2.8.
## Prior 5.2.5 exact-tag native-gate evidence
The immutable `v5.2.5` candidate was not promoted. Exact-tag GitHub Actions run
`33434986357` completed 13 jobs successfully, while its native Windows and
macOS jobs failed. The Windows regression did not preserve every requested
hostile path byte across its command-line boundary. The macOS gate exposed both
an unavailable `explicit_bzero` assumption and Bash 3.2 empty-array behavior in
the source scanner exercised by `make check`.
The 5.2.6 corrections select the existing compiler-resistant volatile wipe on
Darwin and NetBSD, guard every relevant scanner array, and make the Windows
fixture accept explicit hexadecimal bytes, verify the full requested path in
the archive, and reject each dangerous raw byte fragment anywhere in diagnostic
output. These changes do not alter the archive format, cryptography, bundled
codec, or SDK ABI.
A separate local compatibility run executed the corrected scanner with genuine
GNU Bash 3.2.57 in a clean clone. All four exercised modes completed: the
repository audit reported 609 files and one archive; `--tree` reported 204/0;
`--archive` reported 201/1; and `--root` plus `--tag v5.2.5` reported 810/2.
This is targeted scanner compatibility evidence only, not exact-v5.2.6 or
v5.2.8 hosted CI, package, native-platform, or promotion evidence.
## Prior 5.2.6 exact-tag native-gate evidence
The immutable `v5.2.6` candidate was not promoted. Exact-tag GitHub Actions run
`33442264243` completed 13 jobs successfully and failed two native jobs. On
macOS arm64, the strict SHA-NI regression build diagnosed x86-only helper
declarations as unused under `-Werror`. On Windows, argv transcoding of the safe
printable UTF-8 fixture caused the path regression to abort before its intended
archive and diagnostic assertions.
The 5.2.7 changes scope those helper declarations to supported x86 builds and
carry the safe UTF-8 fixture across the Windows argument boundary without
locale-dependent byte conversion. These are test/release integration changes,
not archive-format, cryptographic, codec, or SDK ABI changes.
## Prior 5.2.7 exact-tag native-gate evidence
The immutable `v5.2.7` candidate was not promoted. Exact-tag GitHub Actions run
`33445470664` concluded `cancelled` at `2026-08-31T23:11:19Z`, with 13
successful jobs, one failed macOS job, and one cancelled Windows job. The macOS
runner filesystem rejected creation of the
raw-C1 filename fixture with `EILSEQ`. The hosted Windows job stalled in `make
check`; a MinGW/Wine reproduction isolated the cause to
`test --password-prompt ... </dev/null` entering `_getch`, and the remaining
native job was cancelled.
The 5.2.8 fixture treats that creation refusal as an explicit `SKIP`; on a
filesystem that accepts the byte, the scanner must still reject the compiled
magic and render the path without emitting the raw C1 byte. No result from the
immutable tag transfers automatically to 5.2.8. The Windows prompt correction
rejects a redirected/non-console standard input before entering `_getch` and
adds a native EOF regression; it also requires fresh exact-candidate evidence.
## 5.2.8 CodeQL High path-race corrections
CodeQL High #5 identified SDK key-copy publication that reopened the output
path and then applied `chmod` to that mutable name. The copy now uses the core
atomic publisher, sets POSIX mode on the open temporary descriptor, checks
read/close/publication failures, and replaces only the destination directory
entry. `sdk-test` covers symlink and hardlink sentinels and expected key modes.
CodeQL High #6 identified the POSIX disk-restore `lstat`-then-`open` sequence.
Restore now opens once with `O_NOFOLLOW` and without truncation, classifies that
descriptor with `fstat`, and retains the same raw-device descriptor through
capacity checks and writes. Regular-file destinations retain the existing
atomic publisher.
CodeQL High #7 identified benchmark cleanup's `lstat`-then-recursive-path
sequence. POSIX cleanup now pins each component with `openat` and removes leaves
with `unlinkat`; Windows pins ancestors/children, opens reparse points without
following them, recurses only into a plain directory held against rename, and
after emptying a directory reopens it relative to the pinned parent, verifies
its volume and file index against the traversal handle, and marks only that
identity-checked handle for deletion. The live-workspace
regression injects a directory symlink and verifies that its external sentinel
survives. These are reviewed fixes and regression coverage, not independent
certification by themselves; the exact-tag evidence is recorded below.
The C/C++ default-branch analysis of commit `69fc26b` closed #5, #6, and #7,
then reported High #8, #9, and #10 solely in the newly added SDK regression:
its sentinel and mode checks used `stat`/`lstat` before later path operations.
Each individual content or metadata check now opens without following links
and uses `fstat` or reads through that already-open descriptor. A static guard
rejects a return to path-level `stat`/`lstat` in this test. The subsequent
C/C++ default-branch scan run `33452563116` completed successfully at commit
`7a8e5c5`; alerts #5 through #10 are fixed, and the authenticated
code-scanning API reported zero open alerts.
Manual pre-tag CI run `33452602634` at the same functional commit completed 14
of 15 jobs successfully. Linux compilers, analyzers, sanitizers, source policy,
reproducibility, DEB, RPM/SRPM, portable bundles, and the native macOS DMG gate
passed. On Windows, source audit, build, and `make check` passed; the subsequent
smoke failed only when the old MSYS `grep` tried to match a literal non-BMP
filename after ZUPT had compressed and verified all five inputs. Independent
MinGW/Wine reproduction confirmed that the redirected ZUPT listing contained
the exact UTF-8 bytes. The gate now creates the non-BMP name through byte
escapes, verifies Latin-1/BMP/non-BMP listing bytes without locale-sensitive
matching, and requires extraction plus a full tree diff. The path-confinement
regression independently constructs the BMP/non-BMP archive name from ASCII
hex and requires byte-exact listing and extraction.
Because that pre-tag run failed, it remains diagnostic evidence rather than
release approval. The immutable `v5.2.8` tag at
`ebb9ab3aa1d42c50030ca02883f6162dc4771fe1` repeated the complete suite in
manually dispatched run `33456209269`: all 15 jobs passed, including native
Windows/macOS, the pinned local OBS service chain, source reproducibility,
DEB/RPM/SRPM, installed-package, sanitizer, analyzer, and source-only gates.
The canonical source archive was reproduced at 798296 bytes with SHA-256
`378b9506211545b9594cf0d38ac8955d9b1cac34eb6b379ae0ec26b84edb65f7`.
Initial promotion run `33457344882` stopped before release creation because
its validator incorrectly assumed that an SRPM's `%{ARCH}` must be `src`.
Both artifacts were genuine source packages: `%{SOURCEPACKAGE}` was `1`,
`%{SOURCERPM}` was absent, each payload was exactly its Source0 plus spec, and
each binary RPM referenced the matching SRPM. Commit `33eb904` changed the
gate to those canonical metadata and payload checks. Corrected promotion run
`33457868306` then validated the same tag-bound artifacts and published exactly
13 assets. No asset was rebuilt to pass promotion. These are reproducible
project records, not independent certification.
## Cryptographic review boundary
The repository includes NIST/RFC known-answer tests and a conditional OpenSSL
ML-KEM interoperability check. These establish specific functional outputs in
the environments where they pass; they do not prove implementation security,
constant-time execution, or resistance to every malformed input.
Portable C is the default. Sensitive comparison/select code is written to avoid
secret-dependent branching, but compiler output remains platform-dependent.
Optional generated Jasmin functions have a narrower assurance scope and do not
formally verify the parser, codec, key management, or whole application. The C
AES implementation has documented cache-timing risk on hostile shared hardware.
## Historical findings
The following entries are retained as release history. Their regression tests
should be rerun, but the historical resolution does not itself constitute a
5.2.8 test result.
| First corrected | Severity | Finding | Resolution recorded at the time |
|---|---|---|---|
| 4.2.0 | Critical | AES-CTR nonce reuse across encrypted `--dedup` blocks | Changed to fresh random per-block nonces; older affected archives should be re-encrypted |
| 5.0.0 | High | Native ML-KEM used round-3 CRYSTALS-Kyber semantics while labelled FIPS 203 | Corrected matrix/KDF/rejection behavior and added OpenSSL interoperability regression; native PQ compatibility changed |
| 5.0.0 | High | A malformed password-option ordering could overwrite an input | Added output/self-overwrite and argument-order guards |
| 5.0.0 | High | A misplaced option could cause plaintext output when encryption was intended | Reject misplaced options unless explicitly escaped |
| 5.0.0 | Medium | AVX2 codec offset read could exceed a crafted input tail | Added the scalar-equivalent bound and exact-size regression |
| 5.2.2 | Build/supply chain | Build and packaging paths expected local precompiled optional libraries | Removed incomplete vendor trees; optional integrations now require explicit system development packages |
| 5.2.2 | High | Extraction used mutable string paths and could follow hostile parent/leaf links or publish partially verified output | Added strict index-path validation, descriptor/handle-relative traversal, no-replace atomic publication, exact size/hash validation, and hostile-archive regressions |
| 5.2.2 | High | Compression or disk backup could name its own input through an alternate spelling, hardlink, or symlink | Compare open-file identity before creating the output in normal, solid, and disk-image writers; `--force` cannot bypass the guard |
| 5.2.2 | High | Disk restore validated a pathname before destructively consuming it and did not prove raw-device capacity before writing | Snapshot the measured archive privately before target open, restore from the same stream, and fail closed on unknown or insufficient device capacity |
| 5.2.2 | High | Some decoders did not require DATA at every payload position, and legacy encrypted+dedup disk references used a different AAD sequence | Enforce frame types in serial, threaded, solid, test, and disk readers; reconstruct the exact v5.2.1 disk AAD sequence with an actual encrypted DATA/DATA/REF/DATA fixture |
| 5.2.2 | Medium | Benchmark scratch paths were predictable from the process ID | Create one random private scratch directory and clean it without following links |
| 5.2.2 | High | Native private-key output and ZKEY/ZPQK readers did not uniformly enforce no-replace private permissions and every structural field | Create without replacement using POSIX mode `0600` or a Windows current-user-only DACL; validate checksum, version, flags, reserved bytes, exact size, and public/private role before use; leave a failed exclusive partial for manual removal rather than risk unlinking a pathname replacement |
| 5.2.2 | Medium | An authenticated archive comment could still inject terminal control sequences when displayed | Render untrusted comment bytes in a terminal-safe form without changing the authenticated archive value |
| 5.2.2 | Medium | A signal during an interactive POSIX password prompt could leave terminal echo/state altered | Restore saved terminal settings on handled interruptions; cover the behavior with a PTY regression |
| 5.2.2 | Test reliability | `tests/regression.sh` used Bash syntax without making the interpreter contract explicit | Execute the suite explicitly with Bash and keep syntax/interpreter checks in release gates |
| 5.2.2 | Documentation/licensing | Current documentation incorrectly denied historical MIT grants visible in published Git history | Added a factual erratum: current source follows current SPDX notices, while earlier grants and immutable tags remain valid and unmodified |
| 5.2.8 | High | CodeQL #5: SDK key copies changed permissions through a re-resolved destination path | Publish through the core atomic output object and apply permissions to its open descriptor; run link-target/mode regressions through `sdk-test` |
| 5.2.8 | High | CodeQL #6: POSIX disk restore classified a pathname before reopening it destructively | Open without truncation or symlink following, classify with `fstat`, and retain the same device descriptor through write |
| 5.2.8 | High | CodeQL #7: benchmark cleanup classified entries before recursively resolving their path | Traverse pinned descriptors/handles, refuse link/reparse traversal, remove entries relative to pinned parents, and verify Windows directory identity before handle deletion |
| 5.2.8 | High (test-only) | CodeQL #8/#9/#10: the new SDK regression inspected paths before later reads or cleanup | Open each fixture without following links, inspect/read through `fstat` and the same descriptor, and reject path-level `stat`/`lstat` in the static gate |
See `CHANGELOG.md` for the complete per-release history and compatibility notes.
Old tags remain immutable and may contain artifacts or build assumptions removed
from current branches and new tags.
## Packaging review
The openSUSE package must build the immutable source tag with
`WITH_SDK=0 WITH_PQBOX=0`, preserve distribution flags and debuginfo, run a real
`%check`, install through `DESTDIR`, and omit the renamed-era `vaptvupt` alias from
the main package. Review the built RPM for dependencies, paths, permissions,
RPATH/RUNPATH, hardening, debug information, licenses, and unowned files, then
test it after installation in a disposable target environment.
Release-page DEB, binary RPM, SRPM, notice-bearing Linux tar.xz, Windows ZIP,
and macOS DMG packages are separate outputs, never members of Git or source
archives. Publish only formats built and tested for their stated target and
include SHA-256 checksums. The gated GUI set adds the architecture-independent
DEB, noarch/source RPM, and source-only portable GUI ZIP. Package gates include
exact payload/dependency and installed off-screen integration checks; the
portable ZIP additionally receives source scans, an exact safe-member allowlist,
and an extracted launcher test. An AppImage is not promoted by the 5.2.8
policy; AppDir and Flatpak bundles, GUI platform installers, and bare
Linux/Windows executables are also excluded. Windows ZIP and macOS DMG outputs
remain CLI-only.
No Wine result is retained as release evidence for 5.2.8. Cross-compilation
does not establish native-Windows behavior. Extended-length/device namespace
paths, raw UNC output roots, and mapped/network-drive output are unsupported;
the native Windows workflow remains a publication gate for the ZIP containing
the executable.
## Known limitations
- No independent security audit or certification has been performed.
- Fuzzing and sanitizers sample behavior; they cannot prove absence of memory or
parser defects.
- Static analysis is tool-, configuration-, and path-dependent.
- Optional SDK/PQBOX code is outside the default assessment.
- Side-channel behavior is compiler-, CPU-, OS-, and workload-dependent.
- A clean source archive does not by itself establish that a binary was built
reproducibly or on a trusted runner.
- Passing on one OS or architecture is not evidence for another.
Record exact commands, tool versions, target, exit status, and non-sensitive
logs for every release gate. Never convert an unavailable or unexecuted check
from `SKIP` to `PASS`.

View file

@ -1,4 +1,757 @@
# VaptVupt Changelog
# ZUPT Changelog
## [5.2.8] — 2026-08-31 — Path-race hardening and native-fixture correction
Corrective successor to the immutable, unpromoted `v5.2.7` candidate.
Exact-tag GitHub Actions run `33445470664` reached a macOS failure because the
runner filesystem rejected creation of the raw-C1 filename fixture with
`EILSEQ`. The workflow concluded `cancelled` at `2026-08-31T23:11:19Z`, with
13 successful jobs, one failed macOS job, and one cancelled Windows job.
The hosted Windows job stalled in `make check`; a MinGW/Wine reproduction
isolated the cause to `test --password-prompt ... </dev/null` entering
`_getch` despite redirected standard input. The tag and its evidence remain
unchanged.
- Close CodeQL High #5 in SDK key saving by copying into the core atomic
publisher's already-open private object, applying POSIX key permissions with
`fchmod` on that descriptor, checking read/close/publication failures, and
replacing only the requested directory entry. Symlink and hardlink target
sentinels and private/public key modes are covered by the SDK regression.
- Close CodeQL High #6 in POSIX disk restore by opening the target once without
truncation or final-symlink following, classifying that descriptor with
`fstat`, and retaining the same device descriptor through capacity checks and
writes. Regular-file restores continue to use atomic publication.
- Close CodeQL High #7 in benchmark cleanup by resolving POSIX components with
`openat(..., O_NOFOLLOW)`, deleting relative to pinned descriptors with
`unlinkat`, and using pinned, reparse-point-aware handles for Windows
traversal. The regression injects a directory symlink into a live workspace
and verifies that cleanup does not visit its target.
- The first default-branch rescan closed #5, #6, and #7 and then identified
test-only path checks as High #8, #9, and #10 in the new SDK regression.
Replace every test-side `stat`/`lstat` sequence with one no-follow `open`
followed by `fstat` and descriptor reads, and retain that boundary in the
static regression gate. Default-branch run `33452563116` completed
successfully with #5 through #10 fixed and zero open code-scanning alerts.
- Treat inability to create the raw-C1 scanner filename as an explicit fixture
skip on filesystems that reject the byte; when creation succeeds, the unsafe
diagnostic-escaping assertions still run unchanged. Normalize Bash 3.2's
sign-extended `%d` character conversion to an unsigned octet so raw and
UTF-8 C1 diagnostics retain their canonical `\\xNN`/`\\uNNNN` form.
- Reject redirected or otherwise non-console Windows password prompts before
entering `_getch`, handle console EOF as an error, and cover the native
redirected-input path so it cannot hang a release gate.
- On Windows, validate private-key confinement as the protected,
current-user-only DACL that the implementation creates; do not treat MSYS's
synthetic `stat` mode as a POSIX `0600` result. POSIX continues to require
the real descriptor mode `0600` under multiple umasks.
- Replace the old MSYS `grep` non-BMP boundary exposed after 14 successful jobs
in pre-tag run `33452602634` with byte-exact Python checks for Latin-1, BMP,
and non-BMP UTF-8 listing output. Construct the emoji fixture from ASCII byte
escapes and require list, extract, and full-tree equality; the independent
path regression also creates its BMP/non-BMP name from ASCII hex.
- Run `sdk-test` from both `release-check` and the hosted GCC/Clang Linux job so
the atomic key-save regression cannot silently fall outside release gates.
- Carry the v5.2.7 archive format, cryptography, bundled codec release, and SDK
ABI forward unchanged. These are implementation and test-integration
corrections, not a wire-format or API change.
- Realign current code, package, workflow, artifact, and documentation
references to 5.2.8, and pin the AUR/Homebrew SHA-256 and Guix content hash
to the final reproducible source archive before tagging.
- Exact-tag run `33456209269` passed all 15 source, package, analyzer,
sanitizer, OBS, Windows, and macOS jobs at `ebb9ab3`. Promotion initially
stopped before publication because its validator treated `%{ARCH}` as the
SRPM discriminator; the genuine SRPMs instead correctly carry
`%{SOURCEPACKAGE}=1`. Commit `33eb904` corrected and strengthened that gate,
and run `33457868306` published the exact 13 tested assets without rebuilding
them. No v5.2.7 result transfers to this evidence.
## [5.2.7] — 2026-08-31 — Native test-harness portability corrections
Corrective successor to the immutable, unpromoted `v5.2.6` candidate. Exact-tag
GitHub Actions run `33442264243` completed 13 jobs successfully, but the native
Windows and macOS jobs failed, so no 5.2.6 assets were promoted. The tag and its
recorded evidence remain unchanged.
- Scope the SHA-NI regression helper declarations to the supported x86 build
path so macOS arm64 strict compilation does not diagnose unused declarations
under `-Werror`.
- Carry the safe printable UTF-8 path fixture through an explicit
byte-stable Windows argument representation so argv transcoding cannot abort
the regression before its intended archive and diagnostic assertions.
- Carry the 5.2.6 source-only and security baseline forward without changing
the archive format, cryptography, bundled codec release, or SDK ABI.
- Realign current source, package, workflow, artifact, and tag references to
5.2.7, then pin the final reproducible release-archive checksum and content
hash in the downstream recipes before tagging.
- Require fresh exact-`v5.2.7` source, checksum, hosted CI, native-platform,
package, OBS, and promotion evidence. No prior candidate result transfers
automatically, and this entry does not claim those gates passed.
## [5.2.6] — 2026-08-31 — Native release-gate portability corrections
Corrective successor to the immutable, unpromoted `v5.2.5` candidate. Exact-tag
GitHub Actions run `33434986357` completed 13 jobs successfully, but the native
Windows and macOS jobs failed, so no 5.2.5 assets were promoted. The tag and its
recorded evidence remain unchanged.
- Use the compiler-resistant volatile wipe fallback on macOS and NetBSD instead
of assuming that their C libraries export `explicit_bzero`; supported glibc,
FreeBSD, and OpenBSD paths retain their existing selection.
- Make the source-only scanner's empty-array handling compatible with the
system Bash 3.2 shipped by macOS, including repository, tag, standalone-tree,
standalone-archive, and path-component traversal paths.
- Make hostile archive-path fixtures accept explicit hexadecimal bytes, verify
the requested path bytes in the generated archive, and reject the dangerous
raw byte fragment anywhere in diagnostic output, so the Windows regression
does not depend on command-line conversion or benign path prefixes.
- Carry the 5.2.5 source-only and security baseline forward without changing
the archive format, cryptography, bundled codec release, or SDK ABI.
- Realign current package, workflow, artifact, and tag references to 5.2.6;
leave release-archive checksums explicitly pending until the final source
archive is generated.
- Require fresh exact-`v5.2.6` source, checksum, hosted CI, native-platform,
package, OBS, and promotion evidence. No prior candidate result transfers
automatically, and this changelog entry does not claim those gates passed.
## [5.2.5] — 2026-08-31 — OBS service working-directory correction
Corrective successor to the immutable `v5.2.4` candidate. GitHub Actions run
`33431386002` did not promote that tag: 12 jobs passed, the openSUSE Tumbleweed
RPM gate failed because its standalone `Serviceinfo` executor inherited the
repository working directory instead of the isolated service directory, and
the dependent Windows/macOS gate was skipped. The tag and its recorded
checksums remain unchanged.
- Run the standalone OBS `obs_scm``tar``recompress` chain from its
isolated service directory so downstream services can find `.obsinfo`.
- Add a packaging-policy regression for the OBS executor working directory
while retaining the immutable `refs/tags/v5.2.5` service revision assertion.
- Record the successful local Tumbleweed reproduction of the corrected service
chain; it produced one source archive that passed the source-only scanner.
- Carry the 5.2.4 security and source-only baseline forward without changing
the archive format, cryptography, bundled codec release, or SDK ABI.
- Realign current package, workflow, artifact, and tag references to 5.2.5;
leave AUR, Homebrew, and Guix hashes pending the final source archive.
- Require fresh exact-`v5.2.5` source, checksum, hosted CI, native-platform,
package, and promotion evidence; no v5.2.4 result transfers automatically.
## [5.2.4] — 2026-08-31 — Source-policy line-ending correction
Corrective successor to the immutable `v5.2.3` candidate. That candidate was
not promoted because its source-policy test assumed LF bytes for a Windows
`.bat` file that Git correctly materializes as CRLF according to
`.gitattributes`; the tag and its historical record remain unchanged.
- Correct the release/source-policy integration so the required CRLF checkout
form is validated without treating it as source drift.
- Carry the 5.2.3 release-integration work and the 5.2.2 security/source-only
baseline forward without an archive-format, cryptographic, codec, or SDK ABI
change.
- Realign current documentation, package, workflow, artifact, and tag references
to 5.2.4 while keeping generated binaries outside Git and source archives.
- Correct three internal regression-test headers so pre-3.0 product releases
retain the historical ZUPT name; VaptVupt remains only the bundled codec and
compatibility-facing identifier where applicable.
- Require fresh exact-`v5.2.4` source, checksum, hosted CI, native-platform,
package, and promotion evidence; no 5.2.3 result transfers automatically.
- Keep authenticated OBS/Factory validation pending and retain the unresolved,
unsuppressed openSUSE automatic `debugsource` rpmlint `no-binary` finding.
## [5.2.3] — 2026-08-31 — Corrective release integration
Corrective successor to the immutable `v5.2.2` candidate. Post-tag CI
integration failures prevented 5.2.2 asset promotion; the tag and its historical
record remain unchanged.
- Carry the 5.2.2 source-only, security, naming, format, and compatibility work
forward without a new archive-format version, codec release, or SDK ABI.
- Realign current source, package, workflow, documentation, artifact, and tag
version references to 5.2.3. Release binaries remain outside Git and source
archives.
- Make `zupt-gui --version` emit the stable machine-readable line
`zupt-gui 5.2.3`, and cover that contract in GUI/package regressions.
- Derive Debian and Fedora GUI integration paths, package metadata checks, and
CLI dependency checks from the authoritative version header instead of a
prior-release literal.
- Repair native RPM container gates: persist the exact checked-out workspace as
a Git safe directory, and remove Tumbleweed's conflicting `busybox-gawk`
before installing the native `gawk`/RPM toolchain.
- Require a new exact-`v5.2.3` source archive and checksum set plus fresh hosted
CI, native Windows/macOS, package, and promotion evidence. Prior local 5.2.2
results do not transfer automatically.
- Keep authenticated OBS/Factory validation pending. The automatic openSUSE
`debugsource` rpmlint `no-binary` finding remains unresolved and unsuppressed.
- Continue to exclude AppImage/AppDir/Flatpak, GUI platform installers, and bare
executables from the promoted set; retain the notice-bearing CLI archives and
source-only portable GUI ZIP policy.
## [5.2.2] — 2026-08-31 — ZUPT identity, source-only upstream tree, and openSUSE packaging
This maintenance release keeps the `.zupt` extension, the v1.6 version byte,
and the bundled VaptVupt codec at 2.65.3. It adds flag-gated 5.2.2 encodings for
authenticated encrypted-dedup references and disk-image integrity/index
metadata. The 5.2.2 reader retains a narrow legacy v5.2.1 plain disk-index
path; older readers are not claimed to accept every archive written by 5.2.2.
### Product identity and compatibility
- Restored the original **ZUPT** product name and `zupt` command across the
application, GUI, packages, documentation, and release artifacts.
- Moved the canonical project location to
`https://github.com/cristiancmoises/zupt`.
- Kept the `.zupt` extension, format v1.6, `ZUPT` magic bytes, codec IDs,
`zupt_*`/`ZUPT_*` identifiers, and `zuptsdk_*` ABI unchanged. This is a
product-identity change, not an archive or cryptographic format change.
- Retained the name VaptVupt where it identifies the bundled codec, its
`vv_*` API/wire format, the `--vv`/`--vaptvupt` codec selector, or an external
compatibility contract. An optional `vaptvupt` command alias can support
scripts written for versions 3.0.0 through 5.2.1.
### Source and build
- Removed the incomplete `vendor/vuptsdk/` and `vendor/pqvaptvupt/` header
snapshots and every build expectation that a precompiled local `.so`, `.a`,
or `.o` is available. Git and newly generated source archives contain source
and necessary data only.
- `WITH_SDK` and `WITH_PQBOX` are disabled by default. When requested, they
resolve separately installed development libraries through `pkg-config` (or
explicit packager-supplied flags) and fail clearly when unavailable. The
build never downloads dependencies or falls back to a binary under `vendor/`.
- Reworked the Makefile to honor `CC`, `CPPFLAGS`, `CFLAGS`, `LDFLAGS`,
`LDLIBS`, `AR`, `RANLIB`, `STRIP`, `DESTDIR`, `PREFIX`, and install-directory
overrides. Architecture detection follows the compiler target. Baseline
builds no longer apply AVX2 to the whole program, add a private-library
RPATH, or strip distribution binaries.
- `make install DESTDIR=... PREFIX=/usr` supports staged package builds. The
`vaptvupt` command is an optional compatibility install, not part of the
openSUSE main package.
- Added `--password-prompt`, `--pass-file`, and `--pass-fd` to every CLI path
that accepts a password. These explicit sources avoid the historical
optional-argument ambiguity of `-p`; file/descriptor input rejects empty,
NUL-containing, and overlong values.
- Hardened native key handling: private outputs use no-replace creation with
POSIX mode `0600` or a Windows current-user-only DACL, and ZKEY/ZPQK inputs
must pass checksum, version, flags, reserved-byte, exact-size, and
public/private-role validation. A write/flush/close failure leaves its
incomplete or durability-uncertain exclusive file for manual review and
removal instead of risking an unlink-after-close race against a replacement
pathname.
- Added signal-aware POSIX password-prompt cleanup that restores saved terminal
state on handled interruption, with a PTY regression in the final gate.
- Corrected bundled-code provenance: pq-crystals/kyber-derived ML-KEM portions
now carry the upstream CC0-1.0 option and complete license text; the x86 BCJ
state machine is identified as an adaptation of Igor Pavlov's public-domain
LZMA SDK source instead of making an unsupported clean-room claim; and
curve25519-donna-derived X25519 portions retain the conservative upstream
BSD-3-Clause notice. The SHA-NI path now records its immutable
public-domain SHA-Intrinsics reference.
- Removed unsupported `JASMIN-VERIFIED` labels. The repository retains
Jasmin textual sources and generated/hand-written assembly plus runtime
tests, but no reproducible formal-proof certificate or log for these paths;
historical changelog claims below are qualified accordingly.
- Treat older “constant-time by construction” and formal-verification wording
as historical design claims unless a current reproducible proof artifact is
named. Source review and timing regressions do not prove the behavior of
every compiler, CPU, or final binary.
### Archive integrity and path security
- Reject archive entry names containing absolute roots, `.` or `..`
components, control characters, NTFS alternate-stream syntax, trailing
dot/space components, or reserved DOS device names. Empty, overlong, and
embedded-NUL index paths are rejected during parsing.
- Create output below a pinned destination. POSIX systems traverse parents with
`openat()`/`mkdirat()` and `O_NOFOLLOW` after resolving the user-selected root
to a physical path once; Windows traverses and creates each component with
handle-relative `NtCreateFile` and publishes with a handle-relative
`FileRenameInfo`. Neither implementation re-resolves a checked archive path
through a mutable parent.
- Write to a private, newly created temporary file and publish it atomically
only after the decoded size, per-file checksum, archive integrity checks, and
close/flush operations succeed. Existing regular files, hardlinks, symlinks,
or reparse points are never overwritten.
- Normal, solid, and disk-image compression now use the same private-temporary
discipline in the destination directory. Publication replaces only the
requested directory entry, so an output symlink or hardlink cannot truncate
its target; any input, encryption, or write failure preserves the previous
archive and removes the temporary. Disk-image indices now use the canonical
varint encoding and pass the normal `list` and `test` parsers. Disk backup
measures and reads the source through one open descriptor, rejects a size
change, and cannot be redirected by exchanging the source pathname. Before
creating the temporary output, normal compression and disk backup also reject
an output that identifies the same file as the input through an alternate
spelling, hardlink, or symlink; `--force` does not bypass this guard.
- Disk restore now copies the measured compacted archive into one private,
auto-deleted scratch file before opening the destructive destination. Its
preflight and restore phases consume that same snapshot, so exchanging the
source pathname cannot change the bytes after validation. `ZUPT_TMPDIR`
selects an existing scratch directory and fails without fallback when it is
invalid or lacks space. Linux, macOS, and FreeBSD raw block-device capacity
is queried before the first write; an unknown or undersized device fails
closed. Regular-file restore retains atomic publication.
- Bind every encrypted DATA frame to its logical file/block position (or disk
block position), including when deduplication is enabled. An authenticated
DEDUP_REF is bound to its own logical position and carries the authenticated
source position needed to verify the referenced DATA frame. Swapping either
kind of frame therefore fails authentication instead of relying on the former
archive-wide dedup AAD sentinel. Generic `test` and byte-exact `disk restore`
regressions cover encrypted `disk backup --dedup`; they remain part of the
exact final-candidate gate described below.
- Require both XXH64 and an independent SHA-256/128 digest match before
emitting a dedup reference, and authenticate reference offsets in new
encrypted archives.
- Require a valid archive-integrity trailer in the `extract`, `list`, `test`,
and `disk restore` paths by default, regardless of unauthenticated header
flags. `--allow-legacy-no-ait` is an explicit recovery-only override for
those commands when given a known, trusted pre-AIT archive; it emits a
downgrade warning. Writers never use the override or create a no-AIT archive.
`info` remains an unauthenticated framing inspection: it reports AIT presence
but does not validate the trailer or archive contents.
- Authenticate the encrypted disk index and record a chained whole-image XXH64
content hash in new disk archives. Generic `test` and `disk restore` verify
block count, restored size, reference targets, and that content hash. XXH64
remains a non-cryptographic corruption check in a plain archive.
- Retain compatibility parsers for the fixed little-endian disk index and the
linear encrypted-dedup AAD sequence published through 5.2.1. The regression
fixture is an actual v5.2.1 password-encrypted DATA/DATA/REF/DATA disk archive,
stored as hexadecimal text with its source tag/commit, password, input hash,
and archive hash. The 5.2.2 candidate lists, tests, extracts, and restores it byte-exact;
the legacy index has no whole-image hash and produces an explicit warning.
The full local Linux gate passed on commit `ff99770`; no broader historical
compatibility claim is made.
- Serialize fixed-width archive/header, footer, index and PBKDF iteration
fields explicitly in little-endian order. Varint readers now reject overlong
encodings and values wider than 64 bits instead of accepting an ambiguous or
wrapped scalar.
- Require a DATA frame wherever a decoder consumes file or disk payload. The
multithreaded and serial readers, solid reader, generic `test`, and disk
restore now reject type-confused frames rather than decoding COMMENT or INDEX
payload as ordinary data.
- Add structurally valid hostile-archive fixtures covering traversal, absolute
and Windows-special paths, destination leaf/ancestor links, a relative user
output root, corrupt payload cleanup, nested UTF-8 paths, and safe separator
normalization.
- Scope the Windows output boundary to normal local Win32 paths. Cross-build
and Wine results are not native-Windows evidence; the native package workflow,
including its Unicode round trip, remains a release gate.
Extended-length/device namespace paths, raw UNC output roots, and
mapped/network-drive output are unsupported in 5.2.2.
- Create benchmark corpora, archives, extraction outputs, and concatenation
inputs below a randomly generated private temporary directory and clean it
recursively without following links. This replaces the predictable
process-ID-only scratch path used by earlier builds.
- Render archive comments as untrusted terminal data: control bytes are shown
safely rather than emitted as raw terminal-control sequences. The stored and
authenticated comment bytes are unchanged.
### Licensing and provenance erratum
- Corrected prior documentation that incorrectly denied all historical MIT
grants. Commit `d4660e6539c8b6eeba81751c018217d978fdd618` distributed the
then-current first-party application and GUI with MIT license files, and the
immutable `v2.2.2` tag contains an MIT-form `gui/LICENSE-GUI` alongside an
AGPL SPDX notice in the GUI source. Those records are preserved and their
historical permissions are not revoked or reinterpreted by 5.2.2.
- Current source follows its current per-file SPDX notices: the application,
GUI, cryptographic tool, build, test, and documentation code are
AGPL-3.0-or-later, while the identified bundled codec source is
GPL-3.0-or-later. The erratum corrects the record; it does not rewrite old
tags or change the license of a historical copy.
- Distinguished Jasmin compiler output from hand-written textual assembly.
`zupt_aes_ctr4.s` is a hand-written production implementation corresponding
to an algorithm-only `.jazz` description; generated files retain their
available compiler provenance.
- Corrected the stale claim that the adapted XXH64 code was public domain.
Both derived implementations now preserve Yann Collet's BSD-2-Clause
copyright, conditions, disclaimer, and compound SPDX scope; binary package
metadata and license payloads include BSD-2-Clause.
### Audit, tests, packaging, and release
- Added a reusable source-only scanner for tracked files, the working tree,
`git archive`, release archives, nested archives, unsafe symlinks, Git LFS
pointers, compiler output, executable magic, and stale vendor-library
references. Positive and negative regression tests cover renamed ELF, ar,
PE/MZ, versioned `.so`, RPM/DEB/AppImage, escaping symlinks, and LFS pointers
while permitting textual assembly. Necessary non-code `.bin` data requires a
manifest entry with purpose, provenance, and SPDX license; compiled magic is
never allowlisted. Nested inspection now has fail-closed recursion, member,
per-entry expansion, and total-expansion limits with decompression-bomb
regressions.
- Make `tests/regression.sh`'s Bash interpreter requirement explicit so running
it through a non-Bash `/bin/sh` cannot masquerade as a product regression.
- Added upstream openSUSE/OBS packaging under `packaging/opensuse/`, built with
`WITH_SDK=0 WITH_PQBOX=0`, real `%check` execution, staged installation, and
no installed `vaptvupt` alias. The OBS service tracks the canonical ZUPT
repository and an immutable release tag; no acceptance by OBS or Factory is
implied by files being present upstream.
- Updated CI and release checks around clean source builds, tests, source
archive inspection, packaging metadata, licenses, and secret hygiene.
- The gated release path is defined to produce the audited source archive plus
an Ubuntu amd64 CLI DEB, openSUSE Tumbleweed x86_64 CLI binary/source RPMs,
a notice-bearing Linux x86_64 CLI tar.xz, an architecture-independent GUI
DEB, a noarch GUI RPM and matching source RPM, a source-only portable GUI
ZIP, a CLI Windows x86_64 ZIP containing the executable and notices, and a
native-architecture CLI macOS DMG.
Each binary format is built separately from the tagged source and may be
published only after its target-specific package and functional gates pass;
`SHA256SUMS` covers the promoted assets. Packages never enter Git or the
source archive.
- Exclude AppImage, AppDir and Flatpak bundles, GUI platform installers, and
bare executables from the 5.2.2 release set: the inspected type-2 runtime's
static dependency notice omitted mimalloc and the available inputs did not
provide a complete LGPL source/relink handoff. The offline helper now
requires runtime-specific compliance material from its operator.
- Publish the Windows executable only inside its ZIP with AGPL, GPL,
BSD-2-Clause, BSD-3-Clause, CC0-1.0, toolchain-runtime, NOTICE, and
third-party notices; no bare EXE is promoted.
- Limit the 5.2.2 GUI artifact promise to `zupt-gui_5.2.2_all.deb`,
`zupt-gui-5.2.2-1.noarch.rpm`, its matching source RPM, and
`zupt-gui-5.2.2-portable.zip`. Package artifacts have exact dependency and
installed off-screen GUI/CLI tests. The portable ZIP contains source and
launchers only and passes source scans, an exact safe-member allowlist, and an
extracted off-screen launcher test. Windows and macOS artifacts remain
CLI-only.
- Remove the standalone GUI `setup.py` sdist/wheel route, whose outputs omitted
the complete AGPL and artwork-provenance payload. Reviewed GUI installers and
package helpers preserve those notices with every included icon.
- Updated the README, installation, distribution, security, audit, GUI, and
manual-page documentation for the 5.2.2 source-only workflow.
The committed Linux candidate `ff99770` passed the full local
`make release-check`. Recorded results include packaging
`PASS=49 FAIL=0 SKIP=0`; 39/39 source-only scanner cases, including GNU thin
archives and safe diagnostics; strict GCC and Clang; GCC `-fanalyzer`; 9/9 in
the full tool-enabled static-analysis run; ASan/UBSan/LSan; and 1,000
mutation-fuzz iterations without a sanitizer-detected crash. An earlier
off-screen GUI smoke run remains supporting rather than exact-candidate package
evidence.
These are upstream self-audit results, not independent certification or a
published-release claim. Native Windows and macOS, hosted GitHub CI and release
promotion, authenticated OBS, and the openSUSE automatic `debugsource` rpmlint
`no-binary` finding remain pending. Missing or unexecuted gates remain `SKIP`,
not `PASS`.
## [5.2.1] — 2026-07-12 — GUI Verify/Extract robustness; refreshed comparison + audit tables
Backward-compatible; no format, key, or codec change (codec stays 2.65.3, wire
format v1.6). GUI-only fix + documentation.
### GUI — Verify and Extract no longer error on a mis-set mode / missing credential
- Verifying an encrypted archive required picking the correct PQ mode from a
dropdown and supplying the matching key; a wrong pick produced a raw CLI
decrypt error ("Error: Archive uses post-quantum encryption. Use --pq" /
"Authentication failed"), and Verify ran synchronously on the GUI thread
(freezing the window on a large archive).
- New `_detect_archive_enc()` reads the archive header via `info` (no credential)
and classifies it none / password / pq / pqonly / sdk. The Verify tab now
auto-detects the encryption, uses the matching decrypt flag automatically (a
wrong-mode mismatch is impossible — the PQ-mode dropdown was removed), and if
the required password or key is missing it shows a clear instruction instead
of a raw error. Verify runs through the async job runner (own progress bar),
so it never freezes; results print "All checksums passed." / "Verification
failed."
- The same header-detection + missing-credential guidance is applied to the
Extract tab (it had the identical footgun).
### Documentation
- Refreshed the README **Compression comparison** tables (ratio + throughput vs
zstd/gzip/lz4) on codec 2.65.3, every round-trip byte-exact.
- Added an **Audit status** table: `make check` 16/16, NIST/RFC KAT 16/16,
ML-KEM-768 FIPS 203 conformance 3/3, path-traversal 5/5, block-swap 6/6,
dedup-nonce 1/1, arg-order 8/8, decode-slack 7/7, SHA-NI + incremental HMAC
9/9, exact-size decode 80/80, GUI branding 11/11.
### Validation
Full GUI function matrix on a real X display with thread-safety instrumentation
(zero cross-thread widget access): compress/extract (hybrid + full-PQ + password,
byte-exact), Verify (plain/password/hybrid/pq-only pass; missing password and
missing PQ key each produce guidance; wrong credential and non-archive fail
cleanly), Info, Disk backup+restore, two concurrent jobs, close-mid-job.
## [5.2.0] — 2026-07-12 — GUI compress-crash fix; codec 2.65.3; libvuptsdk
Backward-compatible with 5.0.x/5.1.0: `.zupt` wire format unchanged (v1.6),
archives interoperate in both directions, and `--pq` / `--pq-only` keys keep
working.
### GUI — the compress crash / corruption / "nothing happens" is fixed
The 5.1.0 progress-bar work introduced a cross-thread bug that made compress
crash the app ("app closes"), hang ("nothing happens"), or leave a truncated
(corrupt) archive — worst on the full-PQ path.
- Root cause: `run_async` connected plain Python CLOSURES (`finish`/`on_pct`/
`release`) to signals emitted from the worker `QThread`. PySide6 runs a
plain-closure slot in the EMITTING thread regardless of the requested
connection type — even an explicit `Qt.QueuedConnection` — because a bare
functor has no receiver QObject to give it GUI-thread affinity. Those closures
then called `QProgressBar.setValue/setRange/hide`, `QPushButton.setEnabled`
and `QTextEdit.append` from the worker thread. Cross-thread QWidget access is
undefined behaviour and crashed the app under real X11/Wayland rendering; it
only survived offscreen tests, so prior automated runs missed it.
- Fix: a `_Job(QObject)` controller parented to a GUI-thread widget, so every
slot is a bound method Qt auto-marshals to the GUI thread.
- Verified on a real X display (window shown, progress bar rendering) with a
QProgressBar/QPushButton instrumentation that flags any worker-thread call:
zero cross-thread calls after the fix, and hybrid + full-PQ + password
compress/extract all byte-exact round-trip; Verify/Info/Disk backup+restore/
two-concurrent-jobs/close-mid-job all pass.
### Codec — VaptVupt 2.65.3
Byte-identical output to 2.65.0 (same ratio, wire format v1.6) but extreme-mode
encode is ~1.62× faster (Sprint 132) and the extreme prepass window
reservation is capped at 8 MiB instead of up to 128 MiB (Sprint 133 memory
hygiene). Our AVX2 decoder offset-read guard is now upstream; the ANS
safe-zone reserve patch is re-applied on top.
### SDK — libvuptsdk (renamed from libzuptsdk)
`libvuptsdk` (git.securityops.co/cristiancmoises/libvuptsdk) is the renamed
`libzuptsdk`; only the shared-object filename/SONAME changed
(`libzuptsdk.so.2``libvuptsdk.so.2`), the C API is unchanged. A `WITH_SDK=1`
build now links `-lvuptsdk` and enables `--pq-sdk` + the Argon2id password KDF.
`--pq-box` needs the SEPARATE `libpqvaptvupt`, which `libvuptsdk` does not
provide, so it is now behind its own `WITH_PQBOX=1` flag rather than folded into
`WITH_SDK`. The default distributed build stays source-only (`WITH_SDK=0`):
native `--pq` / `--pq-only` + PBKDF2, no external libraries. Validated:
`WITH_SDK=1` links libvuptsdk + libcrypto + libargon2, `keygen --sdk` and
`--pq-sdk` encrypt/decrypt round-trip byte-exact.
### Validation
`make check` 16/16 (source-only); codec KAT 16/16; ML-KEM-768 FIPS 203
conformance 3/3; full GUI function matrix on real X with zero cross-thread
access; SDK round-trip on a `WITH_SDK=1` build.
## [5.1.0] — 2026-07-11 — codec 2.65.0; large ratio gains; GUI compress fixes
Backward-compatible with 5.0.0: the `.zupt` wire format is unchanged (v1.6) and
archives interoperate in both directions. `--pq` / `--pq-only` keys and archives
from 5.0.0 continue to work — no key regeneration needed.
### Compression — much better ratio (two settings were leaving it on the table)
- **Vendored codec upgraded 2.60.4 → 2.65.0** (`git.securityops.co/cristiancmoises/vaptvupt-codec`,
tag v2.65.0): faster balanced encoder and the Sprint 124130 extreme-mode
literal-pricing work. The two in-tree audit patches (ANS decode safe-zone
`2*SAFEZONE_MAX_RUN` reserve against a crafted-sequence heap overflow, and the
AVX2 offset-read bound in `vv_decoder.c`) are re-applied on top; the safe-zone
fix is not yet upstream.
- **`format_v2` is no longer forced** in the integration wrapper (`src/vaptvupt_api.c`).
Forcing the binary-oriented `format_v2`/min_match=3 path on *every* input routed
text through the greedy parser and **halved the extreme-mode ratio on text**
(7.6× → 3.7× at the codec level). Since codec v2.61.0 the encoder auto-enables
`format_v2` for binary-detected input and keeps the optimal parser for text, so
the wrapper now leaves it on auto — text gets the optimal parser, binary still
gets v2.
- **Block size scales with level** (`auto_block_size` in `src/zupt_format.c`). The
block is the codec's LZ window; the old flat 512 KiB extreme block meant the
"large-window extreme" parser could never match beyond 512 KiB. New defaults:
≤2 → 128 KiB, ≤4 → 1 MiB, ≤6 → 2 MiB, 7 (balanced) → 4 MiB, ≥8 (extreme) → 8 MiB.
- **`--dedup` keeps a small block automatically** (256 KiB). Block size also sets
dedup granularity, and a large block almost never finds a byte-exact duplicate,
so dedup + large-window are mutually exclusive; `--dedup` now picks the small
block regardless of level, restoring dedup ratios that the block bump broke.
Measured, level 9 (extreme), 2025 MB per class, single thread (full tables in
README → *Compression comparison*):
| Data class | 5.0.0 | 5.1.0 | Change |
|---|--:|--:|--:|
| Text (docs, Markdown) | 3.77× | 5.98× | +58% |
| Server logs | 7.21× | 9.07× | +26% |
| JSON (structured records) | 8.25× | 9.38× | +14% |
| Source code (C / headers) | 4.93× | 5.63× | +14% |
Extreme mode trades encode speed for the larger window (its optimal DP now runs
over a bigger block); balanced (`-l 7`, the default) also improves and stays
fast. Decode speed and memory are unaffected by block size; peak RSS at extreme
is ~30 MB per thread.
### GUI — "app closes / gets stuck when I compress" fixed
- **Crash on every job completion.** `run_async` dropped its `QThread`/`Worker`
references immediately after `quit()`; Python's cyclic GC then collected the
still-running `QThread` and Qt aborted the process ("QThread: Destroyed while
thread is still running"). References are now released from a slot on
`QThread.finished` after `wait()`.
- **"App stuck" during compress.** The CLI paints live progress as `
` frames
(no newline until 100%); the worker read line-by-line and so emitted nothing
for the whole job — the window looked frozen on any file larger than one block.
The worker now parses `
` progress frames and drives the progress bar, and
runs the child with `stdin=/dev/null` so a prompt can never block it.
- **Window never appeared on Wayland** (Sway 1.12 + Qt 6.9): the toolkit never
sent the initial `wl_surface.commit`, so the compositor never mapped the
surface. The GUI now watches for its first expose and, if none arrives, relaunches
itself on XWayland (`QT_QPA_PLATFORM=xcb`). Earlier: a Wayland-launch SIGSEGV
from self-`raise()`/`activateWindow()` (gated to X11 now).
- Worker exceptions can no longer strand a job (catch-all → failure report;
`errors="replace"` on pipes); closing the window mid-job asks for confirmation
then aborts cleanly; added `vaptvupt-gui --version` / `--help` / `--selftest`
for headless launch verification.
### Validation
- `make check` 16/16; codec KAT vectors 16/16; ML-KEM-768 FIPS 203 conformance
3/3 (still byte-exact vs OpenSSL 3.5); path-traversal, block-swap, dedup-nonce,
arg-order, decode-slack suites green.
- Cross-version interop: 5.0.0 ↔ 5.1.0 archives (password + `--pq` hybrid +
`--pq-only`) extract byte-identically in both directions, all levels.
- Full GUI function matrix (keygen / compress with PQ key / password / pq-only /
extract all modes with byte-identical round-trips / verify / info / concurrent
jobs / close-mid-job) 16/16 on offscreen and X11; big-file (300 MB) extreme
round-trips clean, peak RSS 761 MB at 24 threads.
## [5.0.0] — 2026-07-10 — genuine FIPS 203 ML-KEM-768; GUI + CLI hardening
### Security / correctness — ML-KEM-768 is now FIPS 203-conformant
- The in-tree ML-KEM-768 was **round-3 CRYSTALS-Kyber, not final FIPS 203**, so
it was **not interoperable** with a compliant ML-KEM despite the "FIPS 203"
label. Found by validating against OpenSSL 3.5's ML-KEM-768. Three deviations,
all fixed in `src/zupt_mlkem.c`:
1. **Matrix  transpose convention** — FIPS 203 K-PKE.KeyGen samples
`Â[i][j] = SampleNTT(XOF(ρ, j, i))` and K-PKE.Encrypt uses `(ρ, i, j)`; both
index orders were swapped. Self-consistent (round-trips passed) but
transposed vs the standard — precisely why a self-consistency-only test
never caught it.
2. **Encaps/decaps KDF** — the shared secret is now `K` from `G(m‖H(ek))`
directly; the round-3 final `K = KDF(K̄‖H(c))` step was removed.
3. **Implicit rejection** — now `K̄ = J(z‖c)` (SHAKE256 over the full
ciphertext) instead of `KDF(z‖H(c))`.
- **Validated for genuine conformance against OpenSSL 3.5's FIPS 203
ML-KEM-768** (`tests/test_mlkem_fips203.sh`, wired into `make check`):
deterministic keygen produces a byte-identical `ek`, and the shared secret
matches in **both** cross-decapsulation directions. This permanent conformance
test replaces the previous self-consistency-only round-trip.
### Security — CLI
- **Data-loss guard.** `compress -p out.zupt file1 file2` used to let `-p`
swallow the archive name as the password, then overwrite `file1` with the
archive (silent, exit 0). Now refuses to overwrite an existing non-`.zupt`
file as the output archive (override with `-y`/`--force`), plus a
self-overwrite guard.
- **Silent-plaintext guard.** `compress out.zupt dir -p pw` used to write an
**unencrypted** archive (exit 0) because options after the first positional
were treated as files. Now errors on a misplaced option (`--` escapes a real
dashed filename).
- **Heap OOB read** in the AVX2 decoder fast path on crafted archives: the 2-/3-
byte match-offset read is now bounded like the scalar tail path.
- Wipe ML-KEM/X25519 secret buffers on hybrid-decrypt key-read failure; bound
the attacker-controlled `encryption_header_off` in the `info` reader.
- `version`/`help`/banners now state the build's real KDF (PBKDF2-SHA256 on the
source-only build) and repo URL.
### GUI — reworked for the source-only build
- The GUI defaulted every encryption path to the libzuptsdk "SDK v2" modes,
which are absent from the source-only build and fail — so key generation and
encryption failed out of the box. Reworked around the native modes: a
build-aware **PQ-mode selector** (Hybrid `--pq` default · Full-PQ `--pq-only` ·
SDK v2 only when the binary reports `WITH_SDK` support), detected from
`version`. Extract/Verify gained a PQ private-key input with **auto-detect**
(reads the archive via `info` to pick `--pq` vs `--pq-only`); Verify could not
check any PQ archive before. Fixed a DiskTab QThread-lifetime bug (two buttons
shared one slot); corrected the About tab (codec, default KDF, `--pq-only`,
repo URL). Guix packaging: put Shiboken6 on the launcher path so PySide6
actually imports.
### Packaging & cross-platform
- `debian/rules`, `aur`, `nix`, `homebrew` no longer install the removed
vendored `.so`/`AUDIT.md` or use stale `/zupt` URLs; `opensuse` `%files` now
ships the shell completions (no more "unpackaged files" rpmbuild failure).
- New **portable cross-platform GUI package** (`packaging/portable/`) that runs
on Windows/macOS/Linux/BSD with Python + PySide6, and a **GitHub Actions
workflow** (`.github/workflows/cross-platform.yml`) that builds native
Windows (`.exe` + Inno Setup installer) and macOS (`.dmg`) artifacts on real
runners and attaches them to the release.
### BREAKING
- **`--pq` and `--pq-only` keys and archives created by ≤ 4.2.1 are not
readable by this release** (the KEM math changed). Regenerate keys
(`keygen` / `keygen --pq-only`) and re-encrypt affected archives. Password
mode (`-p`) and plain compression are unaffected. Wire format stays v1.6.
## [4.2.1] — 2026-07-10 — `info` correctly reports the post-quantum mode
### Fixed
- **`vaptvupt info` mislabelled `--pq-only` archives as hybrid.** Full
post-quantum archives set the generic `ZUPT_FLAG_PQ_HYBRID` header flag (the
`enc_type` byte is what distinguishes hybrid `0x02` from pure `0x06`), but
`info` only checked the flag and always printed "PQ Hybrid: YES (ML-KEM-768 +
X25519)". It now reads the real `enc_type` from the encryption-header block
and reports the actual mode: "ML-KEM-768 only, no classical layer" for
`--pq-only`, and hybrid / SDK-v2 / sealed-box for the others. Reader-side only
— no wire-format change; existing 4.2.0 archives are relabelled correctly with
no re-encryption. The crypto was always correct; only the `info` label was
wrong.
## [4.2.0] — 2026-07-09 — Full (pure) post-quantum mode; dedup keystream-reuse fix
### Added — full post-quantum encryption (`--pq-only`)
- New native **full post-quantum** mode: `--pq-only` uses **ML-KEM-768**
(FIPS 203) as the *sole* key-establishment mechanism, with no classical
X25519 component. It complements the existing hybrid `--pq` for compliance
postures that require a single NIST-standardised PQ primitive with no
classical KEM in the envelope (CNSA 2.0-style "PQ-only" requirements).
- Wire format: new envelope type `0x06` (`ZUPT_ENC_PQ_ONLY`). The archive key
is `SHA3-512(ml_ss || ml_ct || "ZUPT-PQ-ONLY-v1")`. Keypairs use the `ZPQK`
magic (1200-byte public, 3600-byte private) and are **not** interchangeable
with hybrid `--pq` keys.
- Keygen: `vaptvupt keygen --pq-only` and `keygen --pub --pq-only`. Encrypt
with `compress --pq-only pub.key`, restore with `extract --pq-only priv.key`.
Wrong/tampered ciphertext is rejected via ML-KEM Fujisaki-Okamoto implicit
rejection plus the HMAC-SHA256 Encrypt-then-MAC envelope.
- Built entirely from the in-tree crypto — no external library, always
available in the default source-only build. The security trade-off vs the
hybrid is documented explicitly: `--pq-only` has no classical safety net, so
a future break of ML-KEM-768 alone breaks the envelope. `--pq` (hybrid)
remains the default recommendation.
### Fixed
- **Deterministic keygen guidance for the SDK path.** `keygen --sdk` /
`--box` on a source-only build now fails with a clear message pointing to the
native `--pq` / `--pq-only` keygen (or a `WITH_SDK=1` build) instead of an
opaque error.
### Security
- **Critical — AES-256-CTR keystream reuse under deduplication.** In `--dedup`
mode every data block was assigned block sequence 0 (the sentinel that lets
cross-file dedup references authenticate consistently). The per-block AEAD
nonce was previously derived as `base_nonce XOR block_seq`, so under dedup
every block collapsed to the *same* nonce — reusing the CTR keystream across
distinct plaintexts (a many-time-pad, from which XOR of ciphertexts leaks
plaintext XOR). Each block now uses a **fresh random 128-bit nonce** stored in
the block prefix and bound into the block MAC; `block_seq` is still bound as
MAC AAD. Nonces are now distinct across all blocks in every mode (regression
test `tests/test_dedup_nonce.sh`). Found by an adversarial review of the
encryption path and confirmed empirically. Archives written by 4.1.0 and
earlier in `--dedup` + encryption mode should be re-encrypted with 4.2.0.
## [4.1.0] — 2026-07-07 — Source-only build; multithreaded-encryption fix; hardening
@ -137,9 +890,10 @@ later + commercial; its own suite: 66/66):
pristine upstream files.
- `VV_SOURCES` gained `vv_bcj.c` (the `test-asan` target could not link
since BCJ arrived).
- Corrected a Makefile comment that misstated the codec license as
"Apache-2.0 / MIT" — the codec is **GPL-3.0-or-later**; the tool is
**AGPL-3.0-or-later** (never MIT).
- Corrected a Makefile comment that misstated the current codec license as
"Apache-2.0 / MIT" — the current codec is **GPL-3.0-or-later** and the
current tool is **AGPL-3.0-or-later**. See the 5.2.2 licensing erratum for
preserved historical MIT grants.
### Compatibility summary
@ -1194,10 +1948,10 @@ The v3.0.0 GUI's about panel had a credit line:
zupt Cristian Cezar Moises MIT
```
That was false. The GUI's SPDX header has always been
`AGPL-3.0-or-later`, the top-level `LICENSE` is AGPL, and the project
policy is **AGPL-3.0-or-later with commercial dual-licensing forever**.
The MIT line was a templating mistake inherited from an early scaffold.
That entry described the intended license of the then-current GUI, but its
historical conclusion was incorrect. Earlier published repository revisions
did contain MIT license notices, and those grants cannot be retroactively
denied. See the 5.2.2 licensing erratum above.
Removed. The CREDITS block now has two correctly-attributed rows:
@ -1206,10 +1960,10 @@ Removed. The CREDITS block now has two correctly-attributed rows:
Both rows carry the commercial-licensing contact `sac@securityops.co`.
`gui/LICENSE-GUI` was an actual MIT license file. Replaced with the
AGPL-3.0-or-later text + commercial-dual-licensing note + a historical
note explaining the prior MIT mistake (so anyone with an old tarball
can't legitimately claim to have received an MIT grant).
`gui/LICENSE-GUI` was changed from an MIT-form file to an
AGPL-3.0-or-later notice for the then-current source. That change did not revoke
MIT permissions already conveyed for historical material. The current
`gui/LICENSE-GUI` records both the current notice and the factual erratum.
Top-level `LICENSE` preamble updated to reflect the Zupt → VaptVupt
rename.
@ -1257,9 +2011,10 @@ All call sites updated.
### New regression test: `tests/test_gui_branding.sh`
11 assertions covering exactly the bugs we just fixed:
- No MIT references in the GUI source (excluding the explanatory comment)
- `gui/LICENSE-GUI` is AGPL-licensed and does not start with "MIT License"
Assertions covering the current branding and license presentation:
- No claim in the GUI source that the current GUI is MIT-only
- `gui/LICENSE-GUI` presents the current AGPL notice first and preserves the
evidenced historical MIT grant (see the 5.2.2 erratum)
- GUI source SPDX header is `AGPL-3.0-or-later`
- No `replace("zupt ", ...)` parser in code
- An anchored `_VERSION_RE` regex is present
@ -1743,7 +2498,7 @@ bump and packaging-syntax test expansion; archive format unchanged.
### `.github/workflows/ci.yml` — 8-job CI matrix
Replaces the prior 4-job CI with a comprehensive matrix that mirrors
the project's local-verification protocol from `PROMPT.md §6`:
the project's historical local-verification protocol:
| Job | What it does |
|---|---|
@ -1771,7 +2526,8 @@ Covers, with appropriate plain-language honesty:
(encrypted modes), byte-level tamper detection (0 silent accepts
in v1.6 sweep), authentication-failure indistinguishability
(F-11), post-quantum forward secrecy in `--pq-sdk`, side-channel
resistance on Jasmin-proven hot paths
resistance on hot paths then described as Jasmin-proven (5.2.2 records that
no reproducible formal-proof artifact was retained)
- **What Zupt does NOT protect against**: compromised endpoints,
key compromise (no forward secrecy across archives, no rotation
feature), weak passwords (with concrete brute-force numbers),
@ -2465,7 +3221,7 @@ size to run. It needs to be a sprint-protocol step, not a one-off.
### What changed
**`PROMPT.md` — Prompt v2.**
**Historical sprint instructions — version 2.**
- **NEW §3.5: The exhaustive byte-sweep mandate.** Every
format-touching change runs the full byte sweep before claiming
@ -2485,7 +3241,7 @@ size to run. It needs to be a sprint-protocol step, not a one-off.
with the regression-test names that catch each one. The table is
the canonical "things that have shipped and must never recur"
reference; keeping it current is part of every sprint.
- **Footer stamp**: Prompt v2, 2026-05-20.
- **Footer stamp**: historical instruction revision 2, 2026-05-20.
**`Makefile`.** The help-target banner version is now derived
from `include/zupt.h` via a `grep | awk` substitution:
@ -2496,7 +3252,7 @@ help:
include/zupt.h | awk -F'"' '{print $$2}') build targets:"
```
This closes a recurring bug noted in `PROMPT.md §6 step 9`
This closes a recurring bug noted in the historical sprint checklist
prior sprints (2.3.0, 2.3.1) left the banner stale even after the
sprint protocol said to bump it. Making it auto-derived removes
the drift opportunity entirely.
@ -2527,7 +3283,6 @@ the drift opportunity entirely.
### Files touched
```
PROMPT.md (§3.5 NEW, §6 step renumber, §10 kickoff, §11 rows, v2 stamp)
Makefile (help banner auto-derives version from header)
include/zupt.h (version 2.3.1 → 2.4.0)
CHANGELOG.md (this entry)
@ -2711,7 +3466,7 @@ the footer.
- **Encrypted modes**: AIT = `HMAC-SHA256(mac_key, hdr[0..63] || footer[0..23])`
- **Plaintext modes**: AIT = `XXH64(...)` in the first 8 bytes, zeros in
the rest. Best-effort (`OPAQUE`-class per PROMPT.md §5).
the rest. Best-effort (`OPAQUE` structural-integrity class).
The MAC input deliberately excludes `footer[24..31]` = `"ZEND" || u32 version`.
Both are structurally validated by the read path (`locate_footer_v15`
@ -2901,7 +3656,7 @@ so flipping it (e.g. from `0x02 INDEX` to `0x03 ENC_HEADER`) did
not cause auth failure; the downstream parser was tolerant.
Severity: low. Fix at `src/zupt_format.c` adds the structural
check immediately after `read_block`. This makes the byte
`OPAQUE`-class per PROMPT.md §5 (tamper detected by parser, not by
`OPAQUE` structural-integrity class (tamper detected by parser, not by
MAC).
**F-02b — RECLASSIFIED.** The 2.2.4 hypothesis that the archive
@ -2938,7 +3693,7 @@ other v2.3.0 changes than as a standalone patch.
### Verification
- `make` — clean on plain GCC and Clang.
- `make` with strict GCC flags (full set from PROMPT.md §6) — clean.
- `make` with the historical strict GCC warning set — clean.
- `make` with strict Clang flags — clean.
- `make test`**61/61 passing**.
- `make test-vectors`**14/14 passing**.
@ -2974,8 +3729,8 @@ SECURITY.md (integrity statement reaffirmed)
Patch release. No format changes, no feature changes, no on-disk
compatibility impact. Five findings closed against the v2.2.3 baseline
under the methodology in the new top-level `PROMPT.md` (continuous
improvement prompt) and tracked in `docs/FINDINGS-2.x.md` (durable
under the methodology in the then-current audit instructions and tracked in
`docs/FINDINGS-2.x.md` (durable
numbered ledger that survives between work sessions).
### Findings closed
@ -3047,9 +3802,9 @@ preserved.
### New process artefacts
- **`PROMPT.md`** — top-level "god-tier" continuous-improvement prompt.
Designed to be pasted verbatim into a fresh chat alongside the latest
source tarball. Encodes the methodology that produced this release:
- **Historical continuous-improvement instructions** — removed from the
current source tree after their durable material was consolidated into the
audit and security documents. They encoded the methodology that produced this release:
three-line workflow (survey → fix-with-test → ship), explicit
authentication-coverage invariant (every archive byte covered by
per-block HMAC OR a separate index MAC OR a footer MAC — no third
@ -3097,7 +3852,7 @@ preserved.
### Files touched
```
PROMPT.md (new, top-level)
[historical sprint-instruction file] (removed from the current tree)
docs/FINDINGS-2.x.md (new)
tests/test_audit_flake.sh (new)
src/zupt_main.c (F-01)
@ -3209,20 +3964,20 @@ Two `make test` runs back-to-back, both clean. Cumulative test count:
### Documentation cleanup
Four design/audit-prompt documents that were sprint-internal scratch
Four design/audit-instruction documents that were sprint-internal scratch
have been removed from the source tree (consolidated into the
remaining permanent docs):
| Removed | Where the content lives now |
|---|---|
| `AUDIT_PROMPT.md` | superseded by `FORMAL_AUDIT_PROMPT.md` |
| Two historical audit-instruction files | consolidated into the permanent audit and security documents |
| `ROOT_CAUSE_ANALYSIS.md` | reproducible-bug postmortems are now per-release entries in `CHANGELOG.md` |
| `COMPAT.md` | the table moved into `README.md` § "Architecture & platform support" |
| `DONATIONS.md` | one-liner moved into `README.md` § "Supporting Zupt" |
Surviving canonical docs: `README.md`, `CHANGELOG.md` (this file),
`SECURITY.md`, `INSTALL.md`, `LICENSE`, `THIRD-PARTY-NOTICES.md`,
`AUDIT.md`, `FORMAL_AUDIT_PROMPT.md`, `ROADMAP.md`.
`AUDIT.md` and the then-current roadmap.
## [2.2.2-final2] — 2026-05-01 — CLI help, man pages, deb copyright
@ -3332,10 +4087,9 @@ yml, and a few packaging files had no SPDX line at all. Added:
it: `vv_ans.c`, `vv_decoder.c`, `vv_encoder.c`, `vv_huffman.c`,
`vv_simd.c`, `vv_xxh64.c`, `vv_ans.h`, `vv_huffman.h`)
The 5 Jasmin `.jazz` source files previously declared "MIT License" in
their headers as a copy-paste artifact from an earlier draft. They have
been **relicensed to AGPL-3.0-or-later** (sole-author relicensing — no
external contributor's work was relicensed).
The current headers of five Jasmin `.jazz` source files were changed from MIT
notices to AGPL-3.0-or-later notices. This describes the current revision; it
does not revoke the MIT permissions attached to exact historical material.
The 5 VaptVupt headers in `vendor/zuptsdk/include/` (vaptvupt.h,
vaptvupt_api.h, vv_ans.h, vv_huffman.h, vv_platform.h) were tagged
@ -3401,7 +4155,7 @@ the github-old → github-new migration sprint) deleted as obsolete.
## [2.2.2] god-tier audit — bug #16 (block-swap attack) fix
Independent formal cryptographic audit (per FORMAL_AUDIT_PROMPT.md two-pass
Independent formal cryptographic audit (using the then-current two-pass
methodology) discovered a critical authenticated-encryption flaw in the
shipped 2.2.2 binary. Investigation, root-cause, fix, regression test, and
final verification documented below.
@ -3600,7 +4354,7 @@ Compile-tested with `-Wpedantic` under GCC. Win32 code paths verified via
- `AUDIT.md`: 2026-04-27 formal audit entry with cumulative test table
- `README.md`: Security section bumped with audit confirmation
- `doc/zupt.1`: SECURITY section mentions path-traversal protection
- `FORMAL_AUDIT_PROMPT.md`: methodology document at repo root for future audits
- the then-current formal-audit methodology document
## [2.2.2] — 2026-04-27
@ -3816,7 +4570,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
### Added — Block-Level Deduplication (`--dedup`)
- **New `--dedup` / `-D` flag** for `zupt compress` and `zupt disk backup`. Eliminates redundant data blocks before compression using XXH64 fingerprinting with full content verification on match.
- **New `--dedup` / `-D` flag** for `zupt compress` and `zupt disk backup`. Eliminates redundant data blocks before compression using XXH64 fingerprinting (strengthened with an independent SHA-256/128 match in 5.2.2).
- **New block type `ZUPT_BLOCK_DEDUP_REF` (0x04)**: Reference blocks store an 8-byte offset to the original data block instead of the full block payload. A 4MB duplicate block becomes 8 bytes.
- **Hash table index**: Open-addressing with linear probing, capped at 2M entries (~48MB RAM). 75% load factor limit. Secure wipe on free.
- **Content verification**: XXH64 fingerprint match is verified by block size comparison to prevent hash-collision corruption.
@ -3991,8 +4745,8 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
## [1.5.0] — 2026-03-28
### Added — Jasmin Assembly Integration (Sprint 1)
- **`zupt_mac_verify_ct`** Jasmin assembly linked into `zupt_decrypt_buffer()`. Replaces the C XOR accumulation loop for HMAC-SHA256 comparison. 4×u64 unrolled XOR, proven constant-time by Jasmin type system. Symbol confirmed active via `nm`: `T zupt_mac_verify_ct`.
- **`zupt_ct_select_32`** Jasmin assembly linked into `zupt_mlkem768_decaps()`. Replaces the C `cmov()` function for Fujisaki-Okamoto implicit rejection key selection. 4×u64 masked select, proven constant-time. Symbol confirmed active via `nm`: `T zupt_ct_select_32`.
- **`zupt_mac_verify_ct`** Jasmin assembly linked into `zupt_decrypt_buffer()`. Replaces the C XOR accumulation loop for HMAC-SHA256 comparison. 4×u64 unrolled XOR, then described as proven constant-time; 5.2.2 records that no reproducible proof artifact was retained. Symbol confirmed active via `nm`: `T zupt_mac_verify_ct`.
- **`zupt_ct_select_32`** Jasmin assembly linked into `zupt_mlkem768_decaps()`. Replaces the C `cmov()` function for Fujisaki-Okamoto implicit rejection key selection. 4×u64 masked select, with the same historical proof qualification above. Symbol confirmed active via `nm`: `T zupt_ct_select_32`.
- **`include/zupt_jasmin.h`** — extern declarations for all Jasmin functions with ABI documentation.
- **`#ifdef ZUPT_USE_JASMIN`** dispatch guards in `zupt_crypto.c` and `zupt_mlkem.c` with clean C fallback.
- **Makefile** auto-detects `jasmin/*.s` files, assembles to `.o`, links into binary, sets `-DZUPT_USE_JASMIN`.
@ -4024,8 +4778,9 @@ All 4 `.jazz` files rewritten to fix compilation errors:
### Changed
- Removed all `-CT` flag references (does not exist in jasminc 2026.03.0).
- CT enforced by Jasmin type system during normal compilation.
- Safety: `jasminc -arch x86-64 -checksafety`.
- The release claimed CT enforcement by the Jasmin type system during normal
compilation and use of `jasminc -arch x86-64 -checksafety`; no reproducible
certificate/log for those claims was retained (documented in 5.2.2).
- All compound expressions split into separate register operations.
- All output parameters changed from `reg ptr` to `reg u64` raw pointers.
- Byte-level access avoided: 4×u64 instead of 32×u8.

View file

@ -1,16 +0,0 @@
cmake_minimum_required(VERSION 3.10)
project(zupt VERSION 0.4.0 LANGUAGES C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(SOURCES
src/zupt_main.c src/zupt_format.c src/zupt_lz.c src/zupt_lzh.c src/zupt_xxh.c
src/zupt_sha256.c src/zupt_aes256.c src/zupt_crypto.c src/zupt_predict.c)
add_executable(zupt ${SOURCES})
target_include_directories(zupt PRIVATE include)
target_link_libraries(zupt m)
if(MSVC)
target_compile_options(zupt PRIVATE /W4 /D_CRT_SECURE_NO_WARNINGS)
else()
target_compile_options(zupt PRIVATE -Wall -Wextra -O2)
endif()
install(TARGETS zupt DESTINATION bin)

View file

@ -1,258 +1,327 @@
# Distributing VaptVupt
# Distributing ZUPT 5.2.8
This document describes the upstream packaging recipes shipped under `packaging/` and the path from a local source tree to an installable package.
This document describes the packaging material maintained in the ZUPT
source repository. A recipe in `packaging/` is not evidence that a package has
been accepted by a distribution or that every target platform has been tested.
Record each build and test result separately; an unexecuted target is a skip.
Real submission to AUR / Debian / Fedora / Homebrew / NixOS / openSUSE is operational work outside this repository.
The canonical repository is:
## Producing a reproducible source tarball
Every packaging recipe expects an upstream tarball `vaptvupt-VERSION.tar.gz` produced by the project's `make dist` target. The tarball is byte-reproducible:
```sh
make dist
# → /tmp/vaptvupt-4.1.0.tar.gz
```text
https://github.com/cristiancmoises/zupt
```
Re-running `make dist` on the same source tree produces an identical sha256 (verified by `tests/test_dist_reproducible.sh`, wired into `make test`). This lets distros pin a stable hash in their recipes.
GitHub is the canonical source and release host. Packaging must never fetch
`zupt-web` or substitute an asset from another project.
The reproducibility properties:
The `v5.2.2`, `v5.2.3`, `v5.2.4`, `v5.2.5`, `v5.2.6`, and `v5.2.7` tags are
immutable non-promoted candidates.
The v5.2.3 source-policy test assumed LF for a Windows `.bat` file that Git
correctly checks out as CRLF. Exact-tag GitHub Actions run `33431386002` then
recorded 12 successful v5.2.4 jobs, one openSUSE service-harness failure caused
by its working directory, and skipped dependent Windows/macOS jobs. A local
Tumbleweed reproduction confirmed that `refs/tags/v5.2.4` is valid and that
entering the service directory completes the source-service chain. Corrective
working-directory integration was carried by v5.2.5, whose exact-tag GitHub
Actions run `33434986357` completed 13 jobs successfully but failed the native
Windows and macOS jobs. Its v5.2.6 corrections reached exact-tag run
`33442264243`, where 13 jobs succeeded but macOS arm64 failed on unused x86
SHA-NI test-helper declarations under `-Werror`, and Windows aborted during safe
UTF-8 fixture argv transcoding. Version 5.2.7 corrected those failures, but
exact-tag run `33445470664` ended with 13 successful jobs, a macOS raw-C1
fixture failure with `EILSEQ`, and a cancelled Windows job after the hosted job
stalled in `make check`; a MinGW/Wine reproduction isolated a non-console
password-prompt hang in `_getch`. Manual 5.2.8 pre-tag run `33452602634`
subsequently passed 14 of 15 jobs, including native macOS and the complete
Windows distribution checks, before an old MSYS `grep` non-BMP pattern failed
in the later smoke. ZUPT's redirected listing was byte-correct; the corrected
gate uses byte-exact, locale-independent checks and requires extraction plus a
full tree diff. The failed run is diagnostic evidence only.
Exact-tag run `33456209269` subsequently passed all 15 jobs at
`ebb9ab3aa1d42c50030ca02883f6162dc4771fe1`, including the pinned local OBS
source-service chain, native
Windows/macOS, and every package gate. Promotion run `33457868306` published
the exact tested 13-file set; the source archive SHA-256 is
`378b9506211545b9594cf0d38ac8955d9b1cac34eb6b379ae0ec26b84edb65f7`.
Corrective packages and release assets use `v5.2.8`; never move or
overwrite an earlier tag or checksum, and never transfer prior evidence
automatically. Version 5.2.8 corrects those native test boundaries, hardens
three path-race boundaries, and adds the SDK regression to release/hosted Linux
gates. The archive format, cryptography, codec, and SDK ABI remain unchanged.
- Files sorted by name (deterministic order across filesystems)
- mtime fixed to `SOURCE_DATE_EPOCH` (default `1747699200`; override via env)
- uid/gid pinned to root (0/0) via `--owner=0 --group=0 --numeric-owner`
- gzip wrapped with `-9n` (no embedded timestamp or filename)
- Source-only — no `.o`, no built binaries, no `.git/` tree
## Source-only boundary
The tree is source-only. The default `make` build needs only a C compiler, make, libm, and pthread — no external crypto library, and it installs no `.so`. The optional SDK-backed modes (`--pq-sdk`, `--pq-box`) and the Argon2id KDF are built only with `make WITH_SDK=1` against the separately distributed `libzuptsdk` / `libpqvaptvupt` libraries.
Git, `git archive`, and the upstream source tarball contain source code,
textual assembly, documentation, packaging metadata, and necessary data files.
They do not contain compiled objects or executables, shared or static libraries,
or DEB/RPM/AppImage packages.
To force a specific epoch (for distro release-day pinning):
The default build is deliberately independent of the optional SDK and PQBOX
libraries:
```sh
SOURCE_DATE_EPOCH=1727740800 make dist # 2024-10-01 UTC
make clean
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 1)" \
WITH_SDK=0 WITH_PQBOX=0
make WITH_SDK=0 WITH_PQBOX=0 check
```
## Recipes shipped
`WITH_SDK=1` and `WITH_PQBOX=1` use separately installed system development
libraries. They never load a library committed under `vendor/`, never download
a dependency during build or test, and fail explicitly when their development
metadata is unavailable. Distribution builds should keep both options at `0`
unless the corresponding source-built system packages are declared as build
requirements.
| Distro / Platform | Path | Format |
|-------------------|---------------------------------|----------------|
| Arch Linux | `packaging/aur/PKGBUILD` | AUR PKGBUILD |
| Debian / Ubuntu | `packaging/debian/` | Source package (`3.0 (quilt)`) |
| Fedora / RHEL | `packaging/rpm/vaptvupt.spec` | RPM .spec |
| openSUSE | `packaging/opensuse/` | RPM .spec (OBS) |
| macOS | `packaging/homebrew/vaptvupt.rb`| Homebrew formula |
| NixOS / Nix flake | `packaging/nix/flake.nix` | Nix flake |
All recipes:
- Install the binary to `$PREFIX/bin/vaptvupt` (default `/usr/bin/vaptvupt`)
- Install manpage to `$PREFIX/share/man/man1/vaptvupt.1.gz`
- Install docs (README, SECURITY, CHANGELOG) to `$PREFIX/share/doc/vaptvupt/`
- Run the full upstream regression suite (`make test`) during build when the distro's package guidelines allow check-phase execution
## Arch Linux (AUR)
Maintainer flow:
Audit the current tree or a generated archive with:
```sh
# 1. Produce the upstream tarball
make dist
# → /tmp/vaptvupt-4.1.0.tar.gz
# 2. Upload to a stable URL (e.g. git.securityops.co releases)
# 3. Update packaging/aur/PKGBUILD:
# - Set pkgver=4.1.0
# - Set sha256sums=("$(sha256sum /tmp/vaptvupt-4.1.0.tar.gz | awk '{print $1}')")
# 4. Generate .SRCINFO
cd packaging/aur && makepkg --printsrcinfo > .SRCINFO
# 5. Test locally
makepkg -s
# 6. Push to AUR
git clone ssh://aur@aur.archlinux.org/vaptvupt.git aur-vaptvupt
cp packaging/aur/PKGBUILD packaging/aur/.SRCINFO aur-vaptvupt/
cd aur-vaptvupt && git add -A && git commit -m "v4.1.0" && git push
scripts/check-source-only.sh
scripts/check-source-only.sh --archive /path/to/zupt-5.2.8.tar.gz
```
User install:
The scanner reports paths, not file contents, and exits nonzero on a violation.
## Reproducible source archive
`make dist` verifies committed `HEAD` and exports its tree object, normalizes
member order, timestamps, owner/group metadata, and gzip metadata, and audits
the result before moving it to its destination. Exporting the tree rather than
the commit omits Git's commit-ID PAX header:
```sh
yay -S vaptvupt # or paru, pikaur, etc.
make DIST_TARBALL=/tmp/zupt-5.2.8.tar.gz dist
sha256sum /tmp/zupt-5.2.8.tar.gz
```
## Shell completions
The canonical release uses the tracked `.source-date-epoch`; an explicit
`SOURCE_DATE_EPOCH` override intentionally creates a different archive. With
identical committed input and epoch, repeated exports must have the same
SHA-256 digest. Do not generate a release tarball from uncommitted working-tree
files.
`make install` automatically installs Bash, zsh, and fish completion files alongside the binary and manpage:
The AUR, Homebrew, and Guix recipes pin the checksum of this tarball. They are
marked `export-ignore` in `.gitattributes` so their own checksum fields do not
make the archive self-referential. A commit changing only those ignored recipes
therefore leaves the fixed-epoch archive byte-identical. The recipes remain
versioned in Git and must be updated after the final source archive checksum is
known.
| Shell | Path |
|---|---|
| Bash | `$PREFIX/share/bash-completion/completions/vaptvupt` |
| zsh | `$PREFIX/share/zsh/site-functions/_vaptvupt` |
| fish | `$PREFIX/share/fish/vendor_completions.d/vaptvupt.fish` |
Do not commit the generated tarball or checksum file. Host them as immutable
release assets after the release tag is published.
The source files live under `completions/` in the project tree. Distros that prefer a different install location should override the relevant paths in their `make install` invocation.
## Staged installation
For per-user installation without root:
Packagers should preserve distribution flags and install into a package root:
```sh
# Bash
cp completions/vaptvupt.bash ~/.local/share/bash-completion/completions/vaptvupt
# zsh (somewhere in $fpath; add the directory to ~/.zshrc if needed)
cp completions/_vaptvupt ~/.zsh/completion/_vaptvupt
# fish
cp completions/vaptvupt.fish ~/.config/fish/completions/vaptvupt.fish
make -j"${JOBS:-1}" WITH_SDK=0 WITH_PQBOX=0 \
CPPFLAGS="$CPPFLAGS" CFLAGS="$CFLAGS" \
LDFLAGS="$LDFLAGS" LDLIBS="$LDLIBS"
make WITH_SDK=0 WITH_PQBOX=0 check
make DESTDIR="$pkgroot" PREFIX=/usr \
WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 install
```
Completions cover every CLI flag the binary actually parses (`--kdf`, `--comment`, `--comment-file`, `--pq`, `--dedup`, etc.) and are validated on every CI run via `tests/test_completions_manpage.sh`.
`INSTALL_LEGACY_ALIAS=0` installs only `zupt`. The `vaptvupt`
command can be requested explicitly with `INSTALL_LEGACY_ALIAS=1`, but it is
not installed by default and is not part of the openSUSE main package. This
keeps the canonical package surface limited to ZUPT and `zupt`.
## Debian / Ubuntu
The Makefile accepts the usual `BINDIR`, `LIBDIR`, `INCLUDEDIR`, `MANDIR`, and
completion-directory overrides. It does not strip package builds or add a
private-library RPATH.
The `packaging/debian/` tree is a Debian source-package layout. Maintainer flow:
## Packaging material
| Target | Maintained path | Intended output |
|---|---|---|
| openSUSE / OBS | `packaging/opensuse/` | source and binary RPM through OBS |
| Debian / Ubuntu | `packaging/debian/`, `packaging/build-deb.sh` | Debian metadata and binary DEB after the target gate |
| RPM release artifact | `packaging/opensuse/zupt.spec`, `packaging/build-rpm.sh` | source and binary RPM after the target gate |
| GUI DEB | `packaging/build-gui-deb.sh` | `zupt-gui_5.2.8_all.deb` after payload/dependency and installed integration gates |
| GUI RPM | `packaging/build-gui-rpm.sh` | `zupt-gui-5.2.8-1.noarch.rpm` and matching `.src.rpm` after package and installed integration gates |
| Linux CLI archive | `.github/workflows/ci.yml` | `zupt-5.2.8-linux-x86_64.tar.xz` with notices after dependency, member, and extracted functional gates |
| Portable GUI source | `packaging/portable/`, `.github/workflows/ci.yml` | `zupt-gui-5.2.8-portable.zip` after source scan, member allowlist, and extracted off-screen integration gate |
| Fedora / RPM-based systems | `packaging/rpm/zupt.spec` | downstream RPM starting point |
| AppImage helper | `packaging/build-appimage.sh` | downstream-only helper; no 5.2.8 AppImage is promoted |
| Windows | `.github/workflows/cross-platform.yml` | native ZIP (executable plus notices) after the required native gate |
| macOS | `packaging/build-dmg.sh` | native-architecture DMG after the native gate |
| Arch Linux | `packaging/aur/PKGBUILD` | AUR package recipe |
| Homebrew | `packaging/homebrew/zupt.rb` | formula-built package |
| Guix | `packaging/guix/zupt.scm` | Guix package definition |
| Nix | `packaging/nix/flake.nix` | flake-built package |
These files are upstream starting points. Use each distribution's isolated
builder and current policy checks; do not claim support based only on parsing a
recipe.
### openSUSE / OBS
The authoritative instructions, tested matrix, and outstanding gates are in
`packaging/opensuse/README.md`. The normal local flow is:
```sh
# 1. Produce the upstream tarball with the standard Debian
# orig.tar.gz naming convention:
make dist
cp /tmp/vaptvupt-4.1.0.tar.gz /tmp/vaptvupt_4.1.0.orig.tar.gz
# 2. Unpack and overlay the debian/ tree:
cd /tmp && tar xzf vaptvupt_4.1.0.orig.tar.gz && cd vaptvupt-4.1.0
cp -a /path/to/vaptvupt/packaging/debian ./debian
# 3. Build the source package:
dpkg-buildpackage -S -us -uc # source-only
dpkg-buildpackage -b -us -uc # binary
# 4. Lint:
lintian vaptvupt_4.1.0-1_*.deb
# 5. Submit via the standard Debian mentors process:
# https://mentors.debian.net/intro-maintainers/
cd packaging/opensuse
xmllint --noout _service
osc service manualrun
rpmspec -P zupt.spec >/dev/null
osc build openSUSE_Tumbleweed x86_64 zupt.spec
```
User install (after the package lands in Debian unstable / Ubuntu):
Run `rpmlint` on all produced RPMs and install the binary RPM in a disposable
environment for `--version`, `--help`, and archive round-trip tests. Presence of
the OBS files upstream does not mean the package has been submitted or accepted
by openSUSE Factory.
### Debian and RPM release artifacts
The release helper scripts build from this source tree, stage into temporary
directories, run their format and installed-binary checks, and place only their
final outputs in an explicitly selected directory. Run them from an exact
checkout of the immutable tag inside a clean target container, chroot, or VM:
```sh
sudo apt install vaptvupt
release_dir=$(mktemp -d)
# Native Debian/Ubuntu binary package
DIST_DIR="$release_dir" RUN_CHECKS=1 packaging/build-deb.sh
# Source and binary RPM using the openSUSE spec
DIST_DIR="$release_dir" packaging/build-rpm.sh
# Architecture-independent GUI DEB and noarch/source GUI RPM
DIST_DIR="$release_dir" packaging/build-gui-deb.sh
DIST_DIR="$release_dir" packaging/build-gui-rpm.sh
```
## Fedora / RHEL / CentOS
`packaging/build-deb.sh` creates a native binary DEB; it does not claim to
create a Debian source package. The files in `packaging/debian/` are Debian
source-package metadata and must be staged as the source package's top-level
`debian/` directory before using `dpkg-buildpackage`. Running
`dpkg-buildpackage` directly at the ZUPT repository root is not the
documented release-artifact path.
`packaging/build-rpm.sh` creates its audited Source0 archive, builds both the
binary RPM and source RPM, inspects the installed payload, and copies both
outputs to `DIST_DIR`. The separate `packaging/rpm/zupt.spec` is a
Fedora-family downstream starting point; build and lint it only after staging
Source0 in a normal RPM build tree.
Run the target's metadata and lint tools in addition to the script gates. A
package built for one distribution release or architecture is not evidence for
another.
The GUI helpers package Python/Qt source rather than compiled application code.
They validate exact version, payload, dependency, ownership and legacy-alias
expectations, then test the installed launcher off-screen against the matching
`zupt` CLI. A successful GUI DEB gate does not imply an RPM gate, or vice versa.
### Portable and native release artifacts
The Linux x86_64 gate packages the tested `zupt` executable as
`zupt-5.2.8-linux-x86_64.tar.xz` beside README, changelog, security guidance,
and every applicable public license and notice. Its dynamic-library allowlist,
archive member allowlist, and extracted CLI functional suite must pass.
The `zupt-gui-5.2.8-portable.zip` artifact is source-only: it contains the GUI
Python source, shell/macOS/Windows launchers, icons, provenance, changelog, and
licenses, but no Python, Qt, CLI, or compiled runtime. The gate scans both the
assembled and extracted trees, verifies an exact safe member allowlist, and
runs the extracted launcher off-screen against the tested CLI.
AppImage creation is deliberately offline and is not a 5.2.8 release gate.
Supply a locally verified `appimagetool`, type-2 runtime, and the complete
license/source-relink compliance notice for those exact runtime bytes; the
helper never downloads any input:
```sh
# 1. Produce the tarball
make dist
cp /tmp/vaptvupt-4.1.0.tar.gz ~/rpmbuild/SOURCES/
# 2. Drop the .spec into the SPECS directory:
cp packaging/rpm/vaptvupt.spec ~/rpmbuild/SPECS/
# 3. Build source + binary RPMs:
cd ~/rpmbuild && rpmbuild -ba SPECS/vaptvupt.spec
# 4. Lint:
rpmlint RPMS/x86_64/vaptvupt-4.1.0-1.fc*.rpm
# 5. Submit via the Fedora new-package review process:
# https://docs.fedoraproject.org/en-US/package-maintainers/Package_Review_Process/
# EPEL automatically inherits Fedora packages.
DIST_DIR="$release_dir" RUN_CHECKS=1 \
APPIMAGETOOL=/verified/path/appimagetool \
APPIMAGE_RUNTIME_FILE=/verified/path/runtime-x86_64 \
APPIMAGE_RUNTIME_COMPLIANCE_FILE=/verified/path/runtime-compliance.txt \
packaging/build-appimage.sh
```
User install (after the package lands in Fedora / EPEL):
The runtime inspected while preparing 5.2.2 omitted a linked component from
its notice and did not provide the complete LGPL source/relink handoff required
by this release policy. No AppImage produced by this helper is promoted by the
upstream 5.2.8 workflow. AppDir and Flatpak bundles and GUI platform installers
are also excluded. Bare Linux and Windows executables are not promoted; their
CLI programs appear only inside notice-bearing archives. The Windows ZIP and
macOS DMG remain CLI-only.
Run `packaging/build-dmg.sh` only on a native macOS host. It records the host
architecture in the filename and tests the binary before and after packaging:
```sh
sudo dnf install vaptvupt # Fedora
sudo dnf install epel-release vaptvupt # RHEL/CentOS via EPEL
DIST_DIR="$release_dir" RUN_CHECKS=1 packaging/build-dmg.sh
```
## openSUSE
The Windows ZIP (including its executable and notices) must be built and tested
by the Windows job in `.github/workflows/cross-platform.yml`; it is not a
cross-compiled release claim from a Linux build. No Wine result is retained as
5.2.8 release evidence. Extended-length/device namespace paths, raw UNC output
roots, and mapped/network-drive output are not supported in 5.2.8. Publish the
exact architecture recorded by the native job.
These helpers create binary distribution artifacts for the release page, not
content to be committed to Git or included in the source archive.
The `packaging/opensuse/` tree carries an RPM `.spec` suited to the Open Build Service (OBS).
### AUR, Homebrew, Guix, and Nix
```sh
# 1. Produce the tarball
make dist
After calculating the final reproducible source archive, but before creating or
publishing the immutable tag, update each recipe to version 5.2.8 and to the
exact digest or content hash expected by its package manager. These recipe
directories are excluded from the source archive, so this does not create a
checksum cycle. Commit the pinned recipes in the tagged tree, then build and
test with each package manager before publishing its recipe. Keep build inputs
offline-capable: the check phase must not fetch source or dependencies
dynamically.
# 2. In an OBS package checkout (osc), stage the sources and spec:
cp /tmp/vaptvupt-4.1.0.tar.gz .
cp /path/to/vaptvupt/packaging/opensuse/vaptvupt.spec .
## Release-page artifacts
# 3. Build locally against a target repository:
osc build openSUSE_Tumbleweed x86_64
The source-only policy applies to Git and upstream source archives. A release
page may also carry CLI/GUI DEB and RPM artifacts, the notice-bearing Linux CLI
tar.xz, source-only portable GUI ZIP, CLI Windows ZIP, or CLI macOS DMG when
each is built from the tagged source in its target environment and passes its
format-specific tests. These are separate outputs, never inputs to a source
build.
# 4. Commit to OBS once the build and check phase pass:
osc addremove && osc commit
```
For every published artifact:
User install (after the package lands in a distribution or OBS repository):
1. start from the immutable `v5.2.8` tag;
2. keep `WITH_SDK=0 WITH_PQBOX=0` unless system dependencies are declared;
3. record the exact OS, distribution release, architecture, and toolchain;
4. run format validation plus installed `--version`, `--help`, and archive
round-trip tests;
5. publish a SHA-256 checksum;
6. scan the source inputs and ensure no credential or build path is embedded;
7. label an unbuilt or untested target `SKIP`, never `PASS`.
```sh
sudo zypper install vaptvupt
```
Do not infer multi-architecture compatibility from portable source. Do not add
precompiled optional libraries to make a package build.
## macOS (Homebrew)
The 13 gated assets are published at the
[canonical GitHub release](https://github.com/cristiancmoises/zupt/releases/tag/v5.2.8).
If an expected asset is absent or has a different checksum, report that target
as unpublished rather than redirecting consumers to an unverified file.
```sh
# 1. Produce the tarball and upload to a stable release URL.
## Downstream checklist
# 2. Update packaging/homebrew/vaptvupt.rb:
# - Set url to the release URL
# - Set sha256 to the upstream tarball sha256
# 3. Test locally:
brew install --build-from-source ./packaging/homebrew/vaptvupt.rb
brew test vaptvupt
brew audit --strict --online vaptvupt
# 4. Submit to homebrew-core (preferred, requires popularity threshold):
# https://docs.brew.sh/Adding-Software-to-Homebrew
#
# OR host in your own tap:
# https://docs.brew.sh/How-to-Create-and-Maintain-a-Tap
```
User install (after submission lands):
```sh
brew install vaptvupt
# OR from a custom tap:
brew install cristiancmoises/tap/vaptvupt
```
## NixOS / Nix flake
```sh
# 1. Build directly from the flake (no central submission needed):
nix build github:cristiancmoises/vaptvupt#vaptvupt
nix run github:cristiancmoises/vaptvupt#vaptvupt -- version
# 2. To consume from another flake:
# inputs.vaptvupt.url = "github:cristiancmoises/vaptvupt?ref=v4.1.0";
# packages.x86_64-linux.default = inputs.vaptvupt.packages.x86_64-linux.vaptvupt;
# 3. To submit to nixpkgs (https://github.com/NixOS/nixpkgs):
# - Adapt packaging/nix/flake.nix's `vaptvupt` derivation into a
# pkgs/by-name/va/vaptvupt/package.nix using fetchurl and a hash.
# - Follow the nixpkgs contribution guide:
# https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md
```
## Submitting upstream — checklist
Before pushing any recipe to a distro repository:
- [ ] `make dist` produces a reproducible tarball (verified by `tests/test_dist_reproducible.sh` on every `make test`)
- [ ] The tarball is uploaded to a stable, immutable URL
- [ ] The recipe's checksum field is updated to match `sha256sum /tmp/vaptvupt-VERSION.tar.gz`
- [ ] The recipe builds and tests pass in a clean chroot/container
- [ ] The CHANGELOG mentions distro-relevant changes since the last release
- [ ] The license metadata is correct (AGPL-3.0-or-later for VaptVupt core; GPL-3.0-or-later for the vendored VaptVupt codec)
## Security posture for downstream
Every packaging recipe runs `make test` during build (`check()` for AUR, `override_dh_auto_test` for Debian, `%check` for RPM and openSUSE, `checkPhase` for Nix, `test` block for Homebrew). The test suite runs in each recipe's check phase, including the tamper/integrity regressions and the `make dist` byte-identical reproducibility check.
A build that doesn't pass `make test` will fail at distro check time — the recipes don't paper over regressions.
- [ ] The source URL resolves to the immutable `v5.2.8` tag.
- [ ] The source archive passes `scripts/check-source-only.sh --archive`.
- [ ] The recipe checksum matches the downloaded source exactly.
- [ ] `WITH_SDK=0 WITH_PQBOX=0` is explicit, or system dependencies are complete.
- [ ] Distribution compiler and linker flags are preserved.
- [ ] The real upstream `check` target runs without network access.
- [ ] Installation uses `DESTDIR` and does not write under `/usr/local`.
- [ ] The main package installs `zupt`; any `vaptvupt` alias is explicitly documented as compatibility-only.
- [ ] Licenses include AGPL-3.0-or-later for the application,
GPL-3.0-or-later for the bundled source codec, and BSD-2-Clause for the
xxHash-derived XXH64 routines, plus CC0-1.0 for the
pq-crystals/kyber-derived ML-KEM portions and BSD-3-Clause for the
curve25519-donna-derived X25519 portions.
- [ ] Package contents, dependencies, hardening, RPATH/RUNPATH, and debug info
have been inspected with target-native tools.
- [ ] Installed-package smoke and round-trip tests pass.
- [ ] Only tested target artifacts are attached to the release.

View file

@ -1,232 +1,255 @@
# VaptVupt + VaptVupt GUI — Install Guide for Linux
# Installing ZUPT 5.2.8
If you're seeing the error:
This guide covers the ZUPT command-line program and the optional Python GUI.
The canonical source repository is
`https://github.com/cristiancmoises/zupt`.
```
vaptvupt-gui depende de python3-pyqt6 | python3-pyside6; porém:
Pacote python3-pyqt6 não está instalado.
vaptvupt-gui depende de vaptvupt (>= 4.1.0); porém:
Versão de vaptvupt no sistema é 2.1.7-1.
```
## Choosing an installation method
This is correct behavior. The `vaptvupt-gui` deb requires:
- Python 3 with **PyQt6** or **PySide6** (the GUI toolkit)
- The **vaptvupt CLI 4.1.0** or newer
- Build from the immutable source tag when you want the upstream source-only
path described below.
- Use a distribution package only when it matches your distribution release
and architecture.
- Release-page DEB, RPM, Linux tar.xz, portable GUI ZIP, Windows ZIP, and macOS
files are separate artifacts. Their presence does not make them part of the
Git tree or upstream source archive. Use only artifacts whose release notes
record a successful format-specific test for your target.
## The fastest fix — one command (Linux Mint, Ubuntu, Debian)
The immutable `v5.2.2` candidate was not promoted after CI integration
failures. The immutable `v5.2.3` candidate was not promoted because its
source-policy test assumed LF for a Windows `.bat` file checked out as the
required CRLF. The immutable `v5.2.4` candidate was not promoted after exact-tag
GitHub Actions run `33431386002`: 12 jobs succeeded, the sole openSUSE
service-harness job failed because its executor did not enter the service
directory, and dependent Windows/macOS jobs were skipped. A local Tumbleweed
reproduction confirmed both the explicit tag ref and the corrected
working-directory contract. This is release/test integration only; the product,
archive format, cryptography, codec, and SDK ABI are unchanged. The immutable
`v5.2.5` candidate was likewise not promoted: exact-tag GitHub Actions run
`33434986357` recorded 13 successful jobs and failed native Windows/macOS jobs.
The immutable `v5.2.6` candidate was not promoted after run `33442264243`
recorded 13 successful jobs and two native failures: unused x86 SHA-NI helper
declarations on macOS arm64 under `-Werror`, and early Windows abortion while
argv-transcoding a safe UTF-8 fixture. Version 5.2.7 corrected those boundaries
but was not promoted after exact-tag run `33445470664`: 13 jobs succeeded,
macOS failed because its filesystem rejected the raw-C1 filename fixture with
`EILSEQ`, and Windows was cancelled after the hosted job stalled in `make
check`; a MinGW/Wine reproduction isolated the cause to the non-console
password-prompt test entering `_getch`. Version 5.2.8 makes both
fixtures portable, hardens the three CodeQL High path-race boundaries described
in the security documents, and adds `sdk-test` to release and hosted Linux
gates. Exact-tag run `33456209269` passed all 15 jobs, and promotion run
`33457868306` published the exact tested set. Do not treat any prior candidate's
artifacts or evidence as 5.2.8 packages or validation.
Put all the downloaded files in the same folder, then:
The published 5.2.8 package set is exactly these 13 gated assets:
```bash
sudo bash install-zupt-gui.sh
```
This script auto-detects your distribution and installs everything in
the right order.
## Manual fix — three commands (if you prefer)
### Linux Mint / Ubuntu / Debian / Pop!_OS
```bash
# 1. Install the Qt6 Python binding
sudo apt update
sudo apt install -y python3-pyqt6
# 2. Upgrade vaptvupt CLI to 4.1.0
sudo dpkg -i vaptvupt_4.1.0_amd64.deb
# 3. Install the GUI
sudo dpkg -i vaptvupt-gui_1.3.0_all.deb
```
If step 3 still complains about deps, run:
```bash
sudo apt --fix-broken install
```
### Fedora / RHEL / Rocky / AlmaLinux
```bash
sudo dnf install -y python3-pyqt6
sudo dnf install -y vaptvupt-4.1.0-1.x86_64.rpm vaptvupt-gui-1.3.0-1.noarch.rpm
```
(Or build the RPM from the SRPM tarball with `rpmbuild -bb SPECS/vaptvupt.spec`)
### openSUSE Leap / Tumbleweed
```bash
sudo zypper install python3-pyqt6
# Build the RPM from the source tarball — see the SRPM .tar.gz
```
### Arch Linux / Manjaro / EndeavourOS
```bash
sudo pacman -S python-pyqt6
# Build vaptvupt from the source tarball
```
### Anything else (or no apt/dnf/pacman handy)
Use the AppImage — no install needed:
```bash
tar xzf VaptVupt-GUI-1.3.0-x86_64.AppDir.tar.gz
cd vaptvupt-gui.AppDir
./AppRun
```
The AppImage still needs Python 3 + Qt6 binding on the host. For a
fully standalone executable with no Python dependency, use a future
PyInstaller-built version (not in this release).
## Why does the GUI need Qt6?
The VaptVupt GUI is written in Python, using either PyQt6 or PySide6 (it
auto-detects whichever is installed). These are bindings to the Qt 6
graphical toolkit — they're how the GUI draws windows, buttons, and
dialogs.
PyQt6 is in the default repositories of major Linux distributions, so
installing it is one apt/dnf/zypper/pacman command away. We don't bundle
Qt6 inside the deb because:
- It's already on most modern systems
- Bundling would make the deb 80 MB+ instead of 35 KB
- Distribution-managed Qt gets security updates automatically
## Why does the GUI need vaptvupt 4.1.0?
The GUI calls `vaptvupt --pq` and `vaptvupt keygen` for native
post-quantum encryption (ML-KEM-768 + X25519, in-tree implementation).
Older CLI versions lack these flags, so the GUI's compress/extract will
fail against them.
## After installing — verify
```bash
vaptvupt version # should show: 4.1.0
vaptvupt-gui # should launch the GUI window
```
## If the GUI window still doesn't appear
```bash
# Run from terminal to see error messages
vaptvupt-gui
# If you see "ImportError: No module named 'PyQt6'":
# The GUI fell back through both PyQt6 and PySide6 imports.
# Re-check: python3 -c 'import PyQt6.QtWidgets'
# If you see "DISPLAY not set":
# You're on SSH without X forwarding. Use ssh -X or run locally.
# If you see "qt.qpa.plugin: Could not load the Qt platform plugin":
# Missing Qt platform plugin. On Mint/Ubuntu:
# sudo apt install qt6-qpa-plugins
```
## Reporting issues
If you've tried the above and vaptvupt-gui still won't work, open an issue
at https://git.securityops.co/cristiancmoises/vaptvupt/issues with:
1. Output of `lsb_release -a` (or `cat /etc/os-release`)
2. Output of `python3 --version`
3. Output of `python3 -c 'import PyQt6; print(PyQt6.__version__)' 2>&1`
4. Output of `vaptvupt version`
5. Output of `vaptvupt-gui` (the error message it printed to terminal)
---
## Building from source
If you want to build VaptVupt from the source tarball instead of installing
the pre-built `.deb` / `.rpm` packages, you'll need only a C compiler and
make. The default build has NO external crypto dependency and installs no
shared library.
### Build dependencies (default build)
| Component | Why needed |
| Component | Gated artifacts |
|---|---|
| `gcc` ≥ 7 or `clang` ≥ 10 | C11 compiler |
| `make` | build driver |
| libm, pthread | math and threading (part of the standard C library/toolchain) |
| Source and checksums | `zupt-5.2.8.tar.gz`, `zupt-5.2.8.tar.gz.sha256`, and `SHA256SUMS` |
| CLI | `zupt_5.2.8_amd64.deb`, `zupt-5.2.8-0.x86_64.rpm`, `zupt-5.2.8-0.src.rpm`, `zupt-5.2.8-linux-x86_64.tar.xz`, `zupt-5.2.8-windows-x86_64.zip`, and exactly one `ZUPT-5.2.8-macOS-{x86_64\|arm64}.dmg` |
| GUI | `zupt-gui_5.2.8_all.deb`, `zupt-gui-5.2.8-1.noarch.rpm`, `zupt-gui-5.2.8-1.src.rpm`, and `zupt-gui-5.2.8-portable.zip` |
The default build uses PBKDF2-SHA256 (600k iterations) for password KDF
and the in-tree native `--pq` mode (ML-KEM-768 + X25519) for post-quantum
encryption. No `libzuptsdk`, no OpenSSL, no libargon2 is required.
The GUI packages require the matching `zupt` CLI package and must pass exact
payload/dependency checks plus an installed off-screen GUI/CLI integration
test. The source-only portable GUI ZIP bundles launchers, notices, and GUI
source, but not Python, Qt, or the CLI. The Linux tar.xz carries the tested CLI
beside the complete public license/notice payload. AppImage, AppDir, Flatpak
bundles, GUI platform installers, and bare Linux/Windows executables are not
promoted for 5.2.8. The Windows ZIP and macOS DMG contain the CLI only. Exact
target boundaries are listed in `README.md`.
The release's `SHA256SUMS` and validation notes, not the mere presence of a
download link, identify an artifact that completed its gate.
### Install build dependencies (Debian/Ubuntu/Mint)
Do not install a package for a different distribution or CPU architecture.
```bash
sudo apt install build-essential
## Build requirements
The default CLI build requires:
- a C11 compiler;
- GNU make;
- the platform C library, math library, and threading support;
- standard build utilities including `gzip` for installation and source export.
It does not need a vendored binary, OpenSSL, libargon2, `libvuptsdk`, or
`libpqvaptvupt`. Dependencies must be installed before the build; `make` does
not download anything.
Typical package-manager commands are:
```sh
# Debian / Ubuntu
sudo apt install build-essential gzip
# Fedora / RHEL family
sudo dnf install gcc make gzip
# openSUSE
sudo zypper install gcc make gzip
# Arch Linux
sudo pacman -S base-devel gzip
```
### Install build dependencies (Fedora/RHEL/openSUSE)
Package names can differ by distribution release. These commands are examples,
not a statement that 5.2.8 has been accepted into each distribution repository.
```bash
sudo dnf install gcc make # Fedora/RHEL
sudo zypper install gcc make # openSUSE
## Build and test from source
Verify the checkout or extracted archive, then use the source-only feature set:
```sh
scripts/check-source-only.sh
make clean
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 1)" \
WITH_SDK=0 WITH_PQBOX=0 V=1
make WITH_SDK=0 WITH_PQBOX=0 check
./zupt --version
./zupt --help
```
### Build VaptVupt itself
From a release archive, run the scanner as follows before extraction or from a
trusted checkout after download:
```bash
tar -xzf vaptvupt-4.1.0-source.tar.gz
cd vaptvupt-4.1.0
make # build the `./vaptvupt` binary
sudo make install # install to /usr/local/bin (override with PREFIX=/usr)
./vaptvupt version # verify
```sh
scripts/check-source-only.sh --archive /path/to/zupt-5.2.8.tar.gz
```
The `make` step takes 10-30 seconds. The build emits the binary as
`./vaptvupt`. The default install prefix is `/usr/local`; override with
`PREFIX=/usr` for system-wide install.
The default build provides the native password, ML-KEM-768 + X25519 hybrid
`--pq`, and ML-KEM-768 `--pq-only` paths. See `SECURITY.md` and
`THREAT_MODEL.md` before selecting an encryption mode.
### Run the test suite
For password encryption, prefer one of the explicit non-argv inputs:
```bash
make test
```sh
# Interactive, without terminal echo; compress confirms the password.
zupt compress --password-prompt backup.zupt files/
# Read the first line of a mode-0600 file.
zupt test --pass-file /secure/path/password.txt backup.zupt
# Read the first line from an inherited descriptor.
zupt extract --pass-fd 3 -o restored backup.zupt 3</secure/path/password.txt
```
Covers roundtrip, multi-file, cross-block, dedup property, path-traversal,
argument-order, and block-swap regression. Each suite reports its own
pass/fail count.
`-p/--password PASSWORD` remains available for compatibility, but the password
can be visible in shell history and process listings. `--pass-file` and
`--pass-fd` reject empty, NUL-containing, or overlong input and remove the
line-ending delimiter.
### Cross-compilation
`make check` is the downstream-safe test gate. `make test-all` runs the broader
upstream suite. Optional tests remain conditional on their corresponding
system-built dependencies and must be reported as skipped when unavailable.
VaptVupt builds on x86_64, aarch64, armhf, ppc64le, s390x, and riscv64. To
cross-compile:
## Install
```bash
make CC=aarch64-linux-gnu-gcc # for AArch64
make CC=arm-linux-gnueabihf-gcc # for ARMv7 (Raspberry Pi 32-bit)
The upstream default prefix is `/usr/local`:
```sh
sudo make WITH_SDK=0 WITH_PQBOX=0 install
zupt --version
```
The Makefile auto-detects target architecture via `$(CC) -dumpmachine`
and selects the appropriate SIMD flags (NEON on AArch64, SSE4/AVX2 on
x86_64).
For a distribution-style `/usr` installation, or when building a package:
### Optional: WITH_SDK=1 build
The SDK-backed modes — `--pq-sdk`, `--pq-box` (sealed-box), and the
Argon2id KDF — are optional. They are not in the default build and require
building against the separately distributed `libzuptsdk` / `libpqvaptvupt`
libraries:
```bash
make WITH_SDK=1
```sh
make DESTDIR="$pkgroot" PREFIX=/usr \
WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 install
```
This build additionally needs the SDK development package and its runtime
dependencies (OpenSSL libcrypto, libargon2), which ship with the SDK
distribution. Without `WITH_SDK=1`, the `--pq-sdk` and `--pq-box` flags are
unavailable; use the native `--pq` mode instead.
`DESTDIR` stages the files below a package root; it is not embedded in installed
paths. `PREFIX`, `BINDIR`, `LIBDIR`, `INCLUDEDIR`, and `MANDIR` can be overridden
without replacing packager-supplied compiler or linker flags.
The default installation provides `zupt`. To install the `vaptvupt` command
and manual-page compatibility aliases for versions 3.0.0 through 5.2.1, use:
```sh
sudo make INSTALL_LEGACY_ALIAS=1 install
```
The openSUSE main package installs `/usr/bin/zupt` as the primary command and
does not need the optional compatibility alias.
To remove an installation made with the same prefix:
```sh
sudo make PREFIX=/usr/local uninstall
```
## Optional system integrations
The SDK and PQBOX integrations are independent and disabled by default:
```sh
# Requires a system libvuptsdk development package or explicit SDK_* flags
make WITH_SDK=1 WITH_PQBOX=0
# Requires a system libpqvaptvupt development package or explicit PQBOX_* flags
make WITH_SDK=0 WITH_PQBOX=1
# Enable both only when both system dependencies are installed
make WITH_SDK=1 WITH_PQBOX=1
```
The Makefile normally obtains flags from `pkg-config`. A packager may provide
`SDK_CPPFLAGS`/`SDK_LDLIBS` or `PQBOX_CPPFLAGS`/`PQBOX_LDLIBS` explicitly. A
missing dependency is an error: there is no download and no fallback to a local
precompiled library.
Textual assembly under `jasmin/` can be selected separately with
`WITH_JASMIN=1` on a supported x86_64 compiler target. The directory contains
Jasmin-generated outputs and a separately identified hand-written production
unit; it is off by default and the portable C implementations are the baseline
build. Do not infer that an architecture is supported until that target has
actually built and passed its tests.
## GUI
The GUI invokes the `zupt` CLI; it does not replace the CLI or implement
archive cryptography in Python. Install and verify the CLI first:
```sh
zupt --version
python3 -m venv ~/.local/share/zupt-gui-venv
~/.local/share/zupt-gui-venv/bin/pip install PySide6
~/.local/share/zupt-gui-venv/bin/python gui/src/zupt_gui.py
```
The GUI can use PySide6 or PyQt6. Prefer a distribution-managed Qt binding when
available. A package-specific installer may provide launchers and desktop
integration; consult its release notes instead of assuming a particular GUI
package version or filename.
For a headless sanity check:
```sh
python3 gui/src/zupt_gui.py --version
python3 gui/src/zupt_gui.py --selftest
```
## Troubleshooting
If the CLI is not found, inspect the selected prefix:
```sh
command -v zupt
printf '%s\n' "$PATH"
```
If the GUI cannot find it, install the CLI in a directory on `PATH` or set
`ZUPT_BIN` to its absolute path. `VAPTVUPT_BIN` remains a compatibility
fallback. For Qt import failures, verify the same
Python interpreter that starts the GUI:
```sh
python3 -c 'import PySide6.QtWidgets'
```
For build failures, rerun with `V=1` and include the compiler target, full build
command, and first error in the issue report. Do not attach credentials,
private keys, passwords, or sensitive archives.
Report issues at:
`https://github.com/cristiancmoises/zupt/issues`.

121
LICENSE
View file

@ -1,92 +1,65 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
ZUPT licensing summary
======================
Copyright (C) 2026 Cristian Cezar Moisés <zupt@riseup.net>
Copyright (C) 2025-2026 Cristian Cezar Moisés
VaptVupt (formerly Zupt; renamed in v3.0.0 due to a prior INPI Brasil
trademark registration of "Zupt" for unrelated software) is free
software: you can redistribute it and/or modify it under the terms of
the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your
option) any later version.
The ZUPT application, command-line interface, graphical interface,
cryptographic tool code, build files, and documentation identified by the
following SPDX expression are free software under:
VaptVupt is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Affero General Public License for more details.
AGPL-3.0-or-later
You should have received a copy of the GNU Affero General Public
License along with this program. If not, see:
The integrated VaptVupt compression codec is a separately identified component.
The codec files carry this SPDX expression:
https://www.gnu.org/licenses/agpl-3.0.txt
https://www.gnu.org/licenses/agpl-3.0.html
GPL-3.0-or-later
SPDX-License-Identifier: AGPL-3.0-or-later
The two source files derived from Yann Collet's xxHash implementation carry an
additional BSD-2-Clause obligation:
─────────────────────────────────────────────────────────────────────
src/zupt_xxh.c
src/vv_xxh64.c
ABOUT THIS LICENSE
Portions of the native ML-KEM implementation were adapted from the
pq-crystals/kyber reference implementation. Upstream offers that code under
CC0-1.0 or Apache-2.0; this distribution uses the CC0-1.0 option for those
portions:
The GNU Affero General Public License v3 (AGPLv3) is a copyleft
license designed for software that may be run as a network service.
It is identical to the GNU General Public License v3, with one
additional requirement (Section 13): if you modify libzuptsdk and
make the modified version available to users over a computer network,
you must offer those users access to the corresponding modified
source code.
src/zupt_mlkem.c
This protects Zupt against being adopted by SaaS providers as
a private fork without contributing back, while keeping it freely
usable by individuals, small businesses, and the broader open-source
community.
Portions of the native X25519 implementation were adapted from
curve25519-donna and conservatively retain its repository BSD-3-Clause terms:
If you write a separate program that is distributed alongside
Zupt (for example, statically linking it into your own
application), the AGPL requires you to license that combined work
under the AGPL as well — which means you must publish the source.
If this is not acceptable for your use case, please contact the
author for commercial licensing options:
src/zupt_x25519.c
sac@securityops.co
https://git.securityops.co/cristiancmoises/zupt
The codec scope consists of src/vv_*.c, src/vaptvupt_api.c,
include/vaptvupt*.h, and include/vv_*.h. Per-file SPDX notices are
authoritative if a file falls outside this summary.
─────────────────────────────────────────────────────────────────────
GPL-3.0-or-later and AGPL-3.0-or-later code may be combined under section 13 of
the licenses. Distribution of this repository therefore needs to preserve both
license scopes and their notices. The unmodified license texts are provided in:
The full text of the GNU Affero General Public License version 3
should accompany this distribution as a separate file (or you may
download it from the URLs above). It is approximately 35 KB / 619
lines of plain text.
LICENSE-AGPL-3.0
LICENSE-GPL-3.0
LICENSE-BSD-2-Clause
LICENSE-BSD-3-Clause
LICENSE-CC0-1.0
─────────────────────────────────────────────────────────────────────
ZUPT is distributed without warranty; see the applicable license text for
the complete terms.
NOTE ON VAPTVUPT (GPL, NOT AGPL)
Historical licensing note: published repository history includes earlier
first-party application and GUI material distributed with MIT license notices.
Those permissions remain applicable to the exact material distributed under
them; the current license summary does not revoke or reinterpret an earlier
grant. The 5.2.2 erratum in CHANGELOG.md identifies the known repository
evidence. Current files follow their current per-file SPDX notices.
The VaptVupt LZ + tANS codec, located in:
The applicable copyright holder may separately offer commercial terms for
first-party rights that the holder controls. LICENSE-COMMERCIAL is only a
licensing inquiry and scope notice; it is not a commercial license grant and
does not relicense third-party or separately noticed material.
src/vv_*.c
src/vaptvupt_api.c
include/vaptvupt*.h
include/vv_*.h
is licensed under the GNU General Public License version 3 or later
(GPL-3.0-or-later), NOT the AGPL. This deliberate licensing choice is
made so that, with sufficient maturity, VaptVupt can be considered
for upstreaming into the Linux or BSD kernels (which require GPL-
compatible licensing).
The standalone repository for VaptVupt is at:
https://git.securityops.co/cristiancmoises/vaptvupt
The combination of GPL-licensed VaptVupt with AGPL-licensed Zupt is
explicitly intended by the author and consistent with the rights
retained by sole-authorship.
─────────────────────────────────────────────────────────────────────
COMMERCIAL LICENSING
Both AGPL and GPL components may be commercially relicensed by the
author. If you require relief from copyleft terms, contact:
sac@securityops.co
Commercial licensing contact: sac@securityops.co
Canonical repository: https://github.com/cristiancmoises/zupt

661
LICENSE-AGPL-3.0 Normal file
View file

@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

26
LICENSE-BSD-2-Clause Normal file
View file

@ -0,0 +1,26 @@
xxHash Library
Copyright (c) 2012-2021 Yann Collet
All rights reserved.
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

46
LICENSE-BSD-3-Clause Normal file
View file

@ -0,0 +1,46 @@
Copyright 2008, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
curve25519-donna: Curve25519 elliptic curve, public key function
http://code.google.com/p/curve25519-donna/
Adam Langley <agl@imperialviolet.org>
Derived from public domain C code by Daniel J. Bernstein <djb@cr.yp.to>
More information about curve25519 can be found here
http://cr.yp.to/ecdh.html
djb's sample implementation of curve25519 is written in a special assembly
language called qhasm and uses the floating point registers.
This is, almost, a clean room reimplementation from the curve25519 paper. It
uses many of the tricks described therein. Only the crecip function is taken
from the sample implementation.

121
LICENSE-CC0-1.0 Normal file
View file

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

22
LICENSE-COMMERCIAL Normal file
View file

@ -0,0 +1,22 @@
ZUPT COMMERCIAL LICENSING NOTICE
First-party ZUPT code is publicly licensed under the per-file terms:
AGPL-3.0-or-later for the application/GUI/cryptographic tool code and
GPL-3.0-or-later for the separately identified VaptVupt compression codec.
The applicable copyright holder may also offer those first-party rights under
a separate written commercial agreement executed with the licensee.
THIS FILE IS NOT A COMMERCIAL LICENSE GRANT. It provides no permission outside
the applicable AGPL or GPL public option. Commercial-option rights, including
any proprietary redistribution right, exist only in an executed agreement that
identifies its exact files, version, use, and licensee. This notice promises no
support, warranty, patent, indemnification, trademark, pricing, or sublicensing
term.
Vendored, generated, contributed, and separately noticed material is not
relicensed by this option unless the executed agreement expressly covers
rights owned or controlled by the licensor. See THIRD-PARTY-NOTICES.md and all
per-file SPDX notices.
Commercial licensing inquiries: sac@securityops.co

674
LICENSE-GPL-3.0 Normal file
View file

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

953
Makefile

File diff suppressed because it is too large Load diff

39
NOTICE Normal file
View file

@ -0,0 +1,39 @@
ZUPT notices
============
Copyright remains with the holders identified by per-file notices and repository
history.
Public source-license scopes:
- application, GUI, cryptographic tool, build and documentation code:
AGPL-3.0-or-later;
- integrated VaptVupt compression codec files identified in LICENSE:
GPL-3.0-or-later.
- xxHash-derived routines in `src/zupt_xxh.c` and `src/vv_xxh64.c`:
BSD-2-Clause in addition to their application/codec license.
- pq-crystals/kyber-derived portions in `src/zupt_mlkem.c`:
CC0-1.0 in addition to the application license.
- curve25519-donna-derived portions in `src/zupt_x25519.c`:
BSD-3-Clause in addition to the application license; Copyright 2008,
Google Inc.
- the x86 BCJ state machine in `src/vv_bcj.c` is adapted from Igor Pavlov's
public-domain LZMA SDK source.
- the SHA-NI compression path in `src/zupt_sha256_shani.c` is adapted from
Jeffrey Walton's public-domain SHA-Intrinsics reference.
The corresponding unmodified texts are LICENSE-AGPL-3.0,
LICENSE-GPL-3.0, LICENSE-BSD-2-Clause, LICENSE-BSD-3-Clause, and
LICENSE-CC0-1.0. Preserve THIRD-PARTY-NOTICES.md and all per-file SPDX and
copyright notices when redistributing the source.
Published historical revisions contain MIT notices for some first-party
application and GUI material. Those historical permissions remain attached to
the exact material distributed under them; see the 5.2.2 licensing erratum in
CHANGELOG.md. The current source scopes above do not revoke an earlier grant.
LICENSE-COMMERCIAL describes a possible separately executed commercial
agreement for controlled first-party rights. It grants no additional permission
by itself and does not alter the public licenses.
Commercial licensing contact: sac@securityops.co

1225
README.md

File diff suppressed because it is too large Load diff

View file

@ -1,271 +1,404 @@
# Security Policy — VaptVupt 4.1.0
# Security Policy — ZUPT 5.2.8
## Reporting Vulnerabilities
## Reporting vulnerabilities
Report privately by email to **zupt@riseup.net** with `[security]` in the
subject. Do not open a public issue on the project's git server.
Report suspected vulnerabilities privately to **zupt@riseup.net** with
`[security]` in the subject. Do not open a public issue before coordinated
disclosure.
Include:
Include the output of `zupt --version`, operating system and architecture,
impact, and the smallest safe reproducer. Remove passwords, keys, tokens,
personal data, and confidential archive contents.
- Version (`vaptvupt --version`) and platform.
- Description, impact assessment, and a reproduction (a minimal archive or
a code snippet).
The project aims to acknowledge reports within five business days and to target
high-severity fixes within 30 days, with the disclosure timeline agreed case by
case. These are targets, not a warranty.
Disclosure SLA: acknowledgement within 5 business days; target fix within
30 days for high-severity issues. Coordinated disclosure preferred; the
timeline is discussed case by case. A PGP key is on the project's
keyserver entry.
ZUPT has not had an independent third-party security audit or certification.
Treat the in-repository review and tests as reproducible project evidence, not
as external assurance.
The project has not had an external independent audit. For high-stakes
deployments, treat it as "reviewed but unaudited" and do your own review.
## Supported security modes
---
| Mode | CLI | Key establishment / derivation | Payload protection |
|---|---|---|---|
| Plain | no encryption option | none | compression checksums only |
| Password | `-p/--password`, `--password-prompt`, `--pass-file`, or `--pass-fd` | PBKDF2-SHA256, 600,000 iterations | AES-256-CTR + HMAC-SHA256 |
| Native hybrid PQ | `--pq` | ML-KEM-768 + X25519, SHA3-512 combiner | AES-256-CTR + HMAC-SHA256 |
| Native PQ only | `--pq-only` | ML-KEM-768, SHA3-512 derivation | AES-256-CTR + HMAC-SHA256 |
## Encryption Modes
The native hybrid mode is the recommended post-quantum mode unless a policy
forbids a classical component. `--pq-only` removes the X25519 fallback: a break
of ML-KEM-768 alone would then compromise key establishment. Password security
is bounded by password entropy; PBKDF2 cannot make a short or reused password
safe against offline guessing.
| Mode | CLI Flag | Algorithm | PQ-Safe? | Use Case |
|------|----------|-----------|----------|----------|
| Password | `-p` | PBKDF2-SHA256 → AES-256-CTR + HMAC-SHA256 | No | Short-term backups, personal use |
| PQ Hybrid | `--pq` | ML-KEM-768 + X25519 → AES-256-CTR + HMAC-SHA256 | Yes | Long-term archives, high-value data |
| None | (default) | No encryption (compression only) | N/A | Non-sensitive data |
The `-p/--password PASSWORD` argument form can be visible to process-list users
and shell history. Prefer `--password-prompt`, `--pass-file` with restrictive
permissions, or `--pass-fd` with a descriptor inherited from a trusted caller.
The file/descriptor forms read one line, remove LF and an optional preceding CR,
and reject empty, NUL-containing, or overlong input. ZUPT does not enforce
password-file ownership or mode; the caller remains responsible for creating,
protecting, and deleting that file. The descriptor form duplicates the supplied
descriptor and does not close the caller's original descriptor.
The duplicate shares the same underlying stream and offset, and buffered input
may consume beyond the password line. Pass a descriptor dedicated to this one
password read; do not reuse it as a multi-record protocol channel.
Password mode (`-p`) is not quantum-safe. For protection against "harvest
now, decrypt later" quantum attacks, use `--pq` — the recommended
post-quantum mode. `--pq` is native and in-tree; it needs no external
library.
On POSIX terminals, the explicit prompt saves terminal state and installs
signal-aware cleanup so a handled interruption restores echo and other changed
settings before termination. This behavior is covered by a PTY regression and
passed in the full local Linux gate for commit `ff99770`.
On Windows, the prompt requires a real console input handle before entering
`_getch`; redirected input and console EOF fail instead of blocking a native
release gate.
Optional SDK modes (`--pq-sdk`, `--pq-box`) are available only in an
upstream `make WITH_SDK=1` build linked against the separately distributed
libzuptsdk / libpqvaptvupt libraries. They are not part of the default
build and are not defaults.
## Native key files
---
Native private keys use no-replace creation: POSIX files are mode `0600` and
Windows files receive a current-user-only DACL. An existing destination is
never truncated. If write, flush/fsync, or close fails, ZUPT deliberately leaves
the exclusively created incomplete or durability-uncertain file at that path
for the user to inspect and remove. It does not unlink by pathname after close,
which avoids deleting a replacement installed during a race. Public keys may be
shared deliberately and are not treated as secret. ZKEY and ZPQK readers
validate the checksum, format version, flags, reserved bytes, exact encoded
size, and public/private role before using any key material. A truncated,
extended, structurally invalid, or role-confused key is rejected rather than
partially accepted.
## Cryptographic Algorithms
### Optional integrations
| Component | Algorithm | Standard | Key Size | Security Level |
|-----------|-----------|----------|----------|---------------|
| Symmetric encryption | AES-256-CTR | FIPS 197 | 256-bit | 128-bit post-quantum (Grover) |
| Authentication | HMAC-SHA256 | RFC 2104 | 256-bit | 128-bit post-quantum (Grover) |
| Password KDF (default) | PBKDF2-SHA256 | RFC 8018 | 600K iterations | Password-dependent |
| Password KDF (WITH_SDK=1 option) | Argon2id | RFC 9106 | OWASP minimums | Password-dependent, memory-hard |
| Post-quantum KEM | ML-KEM-768 | FIPS 203 | 1184B pk / 2400B sk | NIST Level 3 |
| Classical KEM | X25519 | RFC 7748 | 32B scalar | ~128-bit classical |
| Hybrid KDF | SHA3-512 | FIPS 202 | 512-bit output | Secure if either KEM holds |
| Integrity | XXH64 | xxHash spec | 64-bit checksum | Non-cryptographic |
| Hashing | SHA3-256, SHA3-512 | FIPS 202 | 256/512-bit | Standard |
| Random | OS CSPRNG | getrandom(2) / RtlGenRandom | N/A | Hard fail if unavailable |
The 5.2.8 default is `WITH_SDK=0 WITH_PQBOX=0`:
The default build uses PBKDF2-SHA256 (600k iterations) for password mode.
Argon2id is available only in a `make WITH_SDK=1` build.
- `WITH_SDK=1` enables libvuptsdk-backed features, including the SDK PQ mode
and Argon2id support, using a separately installed system development package.
- `WITH_PQBOX=1` independently enables the libpqvaptvupt sealed-box mode using
its separately installed system development package.
---
Neither library is committed as a precompiled artifact, and no build path
downloads it. A missing requested dependency is a build error. Security
properties of these optional libraries are outside the source-only CLI audit
unless their exact source package and version are reviewed separately.
## Security Architecture
The in-repository SDK adapter saves copied keys through the core atomic output
publisher. POSIX permissions are applied to its already-open temporary
descriptor, and publication replaces only the requested directory entry after
copy/close checks succeed. `make sdk-test` exercises private/public modes and
preservation of pre-existing symlink and hardlink targets. This covers the
adapter boundary; it does not certify the separately installed SDK library.
### Per-Block Authenticated Encryption
## Cryptographic construction
```
For each data block (sequence 0, 1, 2, ...):
Encrypted blocks use a fresh 128-bit nonce, AES-256-CTR, and HMAC-SHA256. The
MAC binds the encrypted payload, canonical block metadata, and the frame's
logical position, and is checked before restored data is accepted. In 5.2.2,
this positional AAD applies to DATA and DEDUP_REF frames. An authenticated
reference is bound to its own position and carries the authenticated source
position needed to verify the referenced DATA frame.
nonce = base_nonce XOR pad_le(block_seq, 8) [16 bytes]
ciphertext = AES-256-CTR(enc_key, nonce, plaintext)
mac = HMAC-SHA256(mac_key, nonce ‖ ciphertext) [32 bytes]
stored = nonce ‖ ciphertext ‖ mac
An archive-integrity trailer (AIT) covers global metadata. The 5.2.2
`extract`, `list`, `test`, and `disk restore` paths refuse a no-AIT layout by
default, without trusting the archive's unauthenticated `ENCRYPTED` bit to
decide whether that check matters. `--allow-legacy-no-ait` is accepted only by
those commands; it is a recovery-only opt-in for a known, trusted pre-AIT
archive and emits a downgrade warning. Never use it for an archive from
untrusted or attacker-writable storage. `info` only reports unauthenticated
framing and apparent AIT presence; it does not validate the trailer or archive
contents. Plain archives use non-cryptographic checksums and do not provide
protection against an attacker who can rewrite the archive.
Archive comments are authenticated according to the archive mode, but they are
still untrusted display data. ZUPT renders control bytes safely when showing a
comment and does not emit raw terminal-control sequences. This prevents a valid
or attacker-supplied comment from rewriting terminal output; it does not make a
plain archive cryptographically authentic.
In new 5.2.2 encrypted+dedup archives, each reference offset is included in
the authenticated reference payload. New encrypted disk archives also
authenticate their index; the index binds the image size, block count, and a
chained XXH64 hash of the complete restored byte stream. The writer
additionally requires an XXH64 and SHA-256/128 match before emitting a dedup
reference, but that SHA-256/128 digest is an in-memory collision guard and is
not stored as an on-disk integrity claim. XXH64 is non-cryptographic: in a
plain archive it
detects accidental corruption but can be recomputed by an attacker.
The native hybrid derivation implemented by 5.2.2 is:
```text
ml_ss = ML-KEM-768 shared secret
x25519_ss = X25519 shared secret
hybrid_ikm = ml_ss XOR x25519_ss
archive_key = SHA3-512(hybrid_ikm || ml_ct || ephemeral_pk ||
"ZUPT-HYBRID-v1")
```
### Encrypt-then-MAC
`--pq-only` derives the archive key as
`SHA3-512(ml_ss || ml_ct || "ZUPT-PQ-ONLY-v1")`.
HMAC is computed over `nonce ‖ ciphertext` and verified **before** any
decryption. This prevents:
The native modes are at-rest archive encryption. They do not provide protocol
session forward secrecy: later compromise of the relevant long-term private key
can compromise archives encrypted to it.
- Chosen-ciphertext attacks
- Padding oracle attacks
- Processing of tampered data
## Constant-time and side-channel scope
### Hybrid Post-Quantum KEM (`--pq`)
Portable C is the 5.2.8 default. Sensitive comparisons and selections use
branchless helpers, but generated machine-code behavior remains dependent on
the compiler and platform. This is not a formal whole-program constant-time
claim. The C AES implementation uses table lookups and is unsuitable for a
claim of cache-timing resistance on hostile shared hardware.
```
Encapsulation:
ML-KEM-768.Encaps(pk) → ml_ct[1088], ml_ss[32]
eph_sk ← CSPRNG(32)
eph_pk = X25519(eph_sk, basepoint)
x25519_ss = X25519(eph_sk, recipient_pk)
hybrid_ikm = ml_ss XOR x25519_ss
archive_key = SHA3-512(hybrid_ikm ‖ ml_ct ‖ eph_pk ‖ "ZUPT-HYBRID-v1")
enc_key = archive_key[0:32]
mac_key = archive_key[32:64]
Sensitive VaptVupt working buffers are cleared through a compiler-resistant
wipe helper. Platforms with a guaranteed libc `explicit_bzero` use it; macOS
and NetBSD use the portable volatile-write fallback because the supported
deployment targets do not guarantee that symbol. This source-level choice
resists ordinary dead-store elimination but is not a formal claim about every
compiler binary.
Textual assembly under `jasmin/` can be enabled explicitly with
`WITH_JASMIN=1` on a supported x86_64 compiler target. The directory contains
Jasmin-generated output and separately identified hand-written assembly; all of
it is disabled by default and its inclusion must be confirmed in the exact
binary being assessed. Its availability does not imply formal verification of
the archive parser, compression codec, or the whole program.
## Security boundary and limitations
ZUPT is designed for backups created and restored on trusted endpoints. It
does not protect against:
- malware, keyloggers, memory inspection, or a compromised user account on the
machine handling plaintext or keys;
- disclosure of a password or private key;
- denial of service from arbitrarily large or adversarial input;
- traffic analysis from archive size and visible framing metadata;
- hiding that a file is a ZUPT archive;
- compression-length side channels when attacker-controlled and secret data are
compressed together and an attacker can observe output length;
- network transport attacks, multi-party access control, threshold recovery, or
key rotation;
- every compiler-, microarchitecture-, power-, or speculative-execution side
channel.
Archive entry paths reject traversal, absolute paths, control characters,
Windows alternate streams/device names, and ambiguous trailing dot/space
components. POSIX extraction resolves each parent relative to a pinned file
descriptor with no-follow semantics after canonicalizing the user-selected
output root once; symlinks below that root remain forbidden. Windows resolves
each parent and temporary
file relative to a directory handle and performs the final no-replace rename by
handle, so a checked path is not looked up again through a mutable junction or
reparse point. An existing destination leaf is never overwritten.
Decoded bytes first go to a private, exclusively created temporary file. The
final name is published only after the expected decoded size and chained
checksum match and the stream closes successfully; failures remove the
temporary by descriptor/handle. These controls reduce traversal, link, race,
and partial-output risks, but they do not make privileged extraction
appropriate. Extract untrusted archives as a dedicated unprivileged user into
a new empty directory, inspect the result before moving it, and apply OS
sandboxing where available.
Benchmark workspaces are random private directories. POSIX cleanup opens each
directory component without following links and removes entries relative to
pinned descriptors. Windows retains no-delete-sharing handles for the resolved
ancestors and refuses reparse-point recursion. After emptying a directory, it
reopens that entry relative to the pinned parent, verifies the volume and file
index against the traversal handle, and marks only the identity-checked handle
for deletion. An injected link is removed as a link rather than traversed to
its target.
Disk restore has a separate destructive-device boundary. It measures and
copies the compacted archive to an exclusively created, auto-deleted private
scratch file, then performs validation and restoration from that same snapshot.
`ZUPT_TMPDIR` is an explicit existing scratch-directory override; an invalid
override fails without fallback. On POSIX, the target is opened once without
truncation or final-symlink following, classified with `fstat`, and—when it is
a supported Linux, macOS, or FreeBSD raw block device—the same descriptor is
retained through capacity checks and writes. Regular files continue through
atomic publication. Unknown device capacity, an undersized device, a
source/destination identity match, or a snapshot failure stops before the
first target write. These checks do not
make raw-device restore non-destructive: verify both operands and keep recovery
media before proceeding.
These changes address the three 5.2.8 CodeQL High reports: #5 at SDK key
publication, #6 at POSIX disk-target classification/use, and #7 at benchmark
workspace cleanup. The regressions and source review are project evidence, not
an independent certification. Exact-tag run `33456209269` subsequently passed
all 15 jobs at `ebb9ab3aa1d42c50030ca02883f6162dc4771fe1`.
The C/C++ default-branch analysis of commit `69fc26b` closed #5, #6, and #7,
then opened test-only High #8, #9, and #10 because the new SDK regression used
path-level `stat`/`lstat` before later reads or cleanup. Each individual test
check now uses a no-follow descriptor with `fstat` or descriptor reads; the
static gate rejects reintroduction of path-level metadata checks there. The
subsequent C/C++ default-branch scan run `33452563116` completed successfully at
commit `7a8e5c5`; alerts #5 through #10 are fixed, and the authenticated
code-scanning API reported zero open alerts.
The final release-commit CodeQL run `33456049125` also completed successfully;
the authenticated API again reported zero open alerts, with #5 through #10
recorded as fixed rather than dismissed.
The Windows handle-relative implementation is scoped to normal local Win32
paths. Win32 extended-length and device-namespace paths, raw UNC output roots,
and mapped/network-drive output are not supported in 5.2.8. Cross-build and
Wine results are not native-Windows evidence; the `windows-latest` package gate
must pass its Unicode round trip before Windows assets are published. Restore
to a normal local directory first and move verified output to network storage
afterward.
## Historical compatibility and fixes
These statements are historical release records, not claims that every current
gate was rerun on every platform:
- In 4.2.0, encrypted deduplication changed from a repeated derived nonce to a
fresh random per-block nonce. Re-encrypt encrypted `--dedup` archives written
by releases through 4.1.0.
- In 5.0.0, native ML-KEM was corrected from round-3 CRYSTALS-Kyber semantics
to FIPS 203 ML-KEM-768. Native `--pq` and `--pq-only` keys and archives from
releases through 4.2.1 are not compatible with the corrected mode. Password
and plain archive paths were not affected by that KEM change.
- Releases predating the archive-integrity trailer may have a structurally
valid no-AIT layout. Such an archive now fails closed unless the caller uses
`--allow-legacy-no-ait` on a supported read command. This option permits
recovery of trusted old media; it is not a general compatibility mode and
does not make unauthenticated header/footer metadata safe.
- Readers since 5.2.2 accept the fixed-width disk index and encrypted-dedup linear
AAD sequence published through 5.2.1 and warns that the legacy index has no
whole-image content hash. Its regression fixture is an actual v5.2.1
password-encrypted DATA/DATA/REF/DATA disk archive stored as hexadecimal text with
source and hash provenance. The candidate lists, tests, extracts, and restores it
byte-exact; the full local Linux gate passed on commit `ff99770`. This does not
claim that 5.2.1 readers accept the new flag-gated 5.2.2 records or that every
historical encrypted+dedup combination was validated.
The repository contains NIST/RFC vector tests and an OpenSSL 3.5 ML-KEM
interoperability test. The OpenSSL test can only execute when the environment
provides an ML-KEM-capable OpenSSL; otherwise it must be reported as skipped.
## Source and release integrity
Git and upstream source archives contain no compiled executable, object,
shared/static library, or distribution package. Audit them with:
```sh
scripts/check-source-only.sh
scripts/check-source-only.sh --archive /path/to/zupt-5.2.8.tar.gz
```
Security model: secure if EITHER ML-KEM-768 (post-quantum, NIST Level 3)
OR X25519 (classical, ~128-bit) remains unbroken. Both must be compromised
simultaneously to recover the archive key. Same approach as Signal
(PQXDH), Apple iMessage (PQ3), and OpenSSH 9.0+.
Nested archive inspection is required to enforce bounded recursion, member
count, per-entry expanded size, and total expanded size, and to fail closed on
limit violations. On commit `ff99770`, the source-only scanner suite passed
39/39, including GNU thin archives, resource-limit cases, and safe diagnostics.
The `--pq-sdk` mode (WITH_SDK=1 only) uses an HKDF-SHA3-256 combiner, a
32-byte key commitment tag, HPKE-style context binding (RFC 9180 §5),
anti-fault double decapsulation, and XChaCha20-Poly1305 AEAD.
DEB, binary RPM, SRPM, notice-bearing Linux tar.xz, source-only portable GUI
ZIP, Windows ZIP, and macOS DMG release assets are separate outputs. An
AppImage is not promoted for 5.2.8. A bare Linux or Windows executable is also
excluded; executables are distributed only inside their notice-bearing
archives. Trust an artifact only when its exact format has a recorded build,
content/metadata inspection, extracted or installed smoke test, and applicable
archive round trip. Never treat an unexecuted platform as passing.
---
The gated 5.2.8 set is the CLI package/archive set plus the exact GUI DEB,
noarch/source RPM, and source-only portable ZIP documented in the README. The
portable GUI ZIP contains no compiled runtime and is scanned as source before
and after extraction. Other GUI packages, AppImage, AppDir and Flatpak bundles,
and GUI platform installers are excluded. Windows ZIP and macOS DMG artifacts
remain CLI-only.
## Constant-Time Guarantees
## Reproducing project checks
### Jasmin-Verified (assembly linked into binary)
Start with the baseline source-only build:
| Function | Purpose | Proof |
|----------|---------|-------|
| `zupt_mac_verify_ct` | HMAC comparison (32 bytes) | Jasmin type system: no branch on diff value |
| `zupt_ct_select_32` | ML-KEM FO implicit rejection | Jasmin type system: no branch on cond value |
```sh
make clean
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 1)" \
WITH_SDK=0 WITH_PQBOX=0 V=1
make WITH_SDK=0 WITH_PQBOX=0 check
make WITH_SDK=0 WITH_PQBOX=0 test-all
make sdk-test
```
These functions are compiled from Jasmin source to x86-64 assembly. The
Jasmin compiler enforces that no secret-typed variable flows into branch
conditions or memory addresses. This guarantee holds at the machine code
level — no C compiler optimization can introduce timing leaks.
Where the compiler supports them, run the sanitizer target separately:
### C Constant-Time (branchless, compiler-dependent)
| Function | Method | Risk |
|----------|--------|------|
| X25519 `fe_cswap` | Masked XOR (`mask & (a ^ b)`) | Low — branchless but compiler may optimize |
| ML-KEM NTT/basemul | Montgomery reduction (no branches) | Low |
| ML-KEM CBD sampling | Bitwise operations only | Low |
| Key wipe (`zupt_secure_wipe`) | `explicit_bzero` / volatile | Low |
### NOT Constant-Time (documented risks)
| Function | Risk | Mitigation |
|----------|------|------------|
| AES-256 block encrypt | HIGH on shared hardware — S-box table lookups leak via cache timing | Jasmin AES-NI path planned; do not use on multi-tenant VMs |
| SHA-256 | Low — table constants are public, not indexed by secret data | Accepted |
---
## Threat Model
### What VaptVupt Protects
| Asset | Protection |
|-------|-----------|
| File contents | AES-256-CTR encryption |
| File names, sizes, structure | Encrypted in central index block, HMAC-protected |
| Archive integrity (payloads + index) | Per-block HMAC-SHA256 |
| Archive integrity (header + footer metadata) | v1.5+ archives: 32-byte archive-integrity-trailer HMAC-SHA256 over `hdr ‖ ft[0..23]`. v1.4 archives: not covered, downgrade warning on extract. |
| Against stolen backups | AES-256 requires key/password to read |
| Against tampering of file contents, names, sizes, offsets | HMAC detects any modification |
| Against tampering of per-block frame preface bytes (codec_id, block_flags, varints, plaintext-XXH64) | v1.6: per-block MAC binds the canonical preface AAD; encryption-header block validated structurally |
| Against tampering of archive comment (when present) | Comment block goes through the same per-block AEAD pipeline as data (AES-256-CTR + HMAC-SHA256 + preface AAD); `hdr.comment_offset` pointer is in the AIT-signed region |
| Against block-swap (reorder) attacks | MAC binds an 8-byte position AAD; a block moved to another position fails verification and its partial output is unlinked. Dedup refs use sentinel seq=0 and rely on plaintext XXH64 for per-block integrity. |
| Against malicious archive entries (Zip Slip / path traversal) | `zupt_path_is_safe()` rejects `..`, absolute paths, Windows drive/UNC paths, embedded NULs |
| Against symlink at extract target (TOCTOU) | `zupt_safe_fopen_output()` uses `O_NOFOLLOW` on POSIX. Windows relies on directory ACLs (documented limitation). |
| Against quantum adversary | `--pq` mode: ML-KEM-768 (NIST Level 3) hybridized with X25519 |
The wire/on-disk format is v1.6. See CHANGELOG.md for the per-release
finding history behind these protections.
### What VaptVupt Does NOT Protect Against
| Threat | Reason | Mitigation Path |
|--------|--------|----------------|
| Attacker who knows the password or has the private key | Fundamental to encryption | Use strong passwords (12+ chars); protect key files |
| Endpoint compromise (keylogger, malware on the host) | Outside the archive's trust boundary | Secure the machine where you type the password or hold the key |
| Cache-timing side channels (C AES) | Table-based S-box lookups | Build with Jasmin AES-NI when available; avoid multi-tenant VMs |
| Memory forensics during operation | Keys on stack during compress/extract | `zupt_secure_wipe()` on completion; `mlock()` planned |
| Deniability | Archive header identifies format | `.zupt` magic bytes visible; ENCRYPTED flag in header |
| Weak passwords | PBKDF2-SHA256 (600k) is the default KDF; Argon2id (memory-hard) is available in a WITH_SDK=1 build | Use `--pq` mode for critical data — keys are random, not derived from a password |
| Traffic analysis / metadata | Archive size reveals data volume; file list, sizes, mtimes not padded | Outside VaptVupt's scope |
| File permission/ownership | Not stored in archive | Documented in README.md |
| Spectre-class side channels in callers | Below the constant-time primitive layer | Host OS / compiler mitigations |
### Quantum Threat Analysis
Scenario: adversary captures an encrypted archive today, stores it, and
attempts decryption when a cryptographically-relevant quantum computer is
available.
| Mode | Classical Security | Quantum Security | Verdict |
|------|-------------------|-----------------|---------|
| Password (`-p`) | Password-dependent + 256-bit AES | ~128-bit (Grover on AES), PBKDF2 accelerated | Vulnerable — use `--pq` |
| PQ Hybrid (`--pq`) | ~128-bit (X25519) | NIST Level 3 (ML-KEM-768) | Protected |
In `--pq` mode: even if Shor's algorithm breaks X25519, ML-KEM-768
protects the archive; even if a novel classical attack breaks ML-KEM,
X25519 still provides ~128-bit security. The hybrid design is secure if
either component holds.
### Extracting untrusted archives — operational guidance
The in-binary defenses are the primary control; the following are defense
in depth:
1. Extract into a dedicated empty directory (not `~/Downloads` or `/tmp`).
2. Audit symlinks in the target directory before extraction.
3. Run extraction as a low-privilege user, never root.
4. On Windows, pre-create the target directory with restrictive ACLs
(the `O_NOFOLLOW` defense is POSIX-only).
### Out of scope
- External independent audit.
- Side-channel testing on production hardware (timing leaks).
- Formal verification beyond the Jasmin constant-time primitives.
---
## CSPRNG Policy
| Platform | Primary Source | Fallback | Failure Mode |
|----------|---------------|----------|--------------|
| Linux | `getrandom(2)` | `/dev/urandom` | Hard exit — no encryption without CSPRNG |
| macOS | `/dev/urandom` | None | Hard exit |
| Windows | `RtlGenRandom` | None | Hard exit |
There is no `rand()`, `srand()`, or any weak PRNG fallback anywhere in the
codebase. If the OS CSPRNG is unavailable, VaptVupt exits with an error.
This is a deliberate design choice — weak random keys are worse than no
encryption.
---
## Supported Platforms
| Platform | Compiler | Threading | CSPRNG | Status |
|----------|----------|-----------|--------|--------|
| Linux x86-64 | GCC 5+ / Clang 3.5+ | pthreads | `getrandom(2)` | Primary |
| Linux ARM64 | GCC 5+ | pthreads | `getrandom(2)` | Tested |
| macOS x86-64/ARM64 | Apple Clang | pthreads | `/dev/urandom` | Tested |
| Windows x86-64 | MinGW / MSVC 2015+ | Win32 threads | `RtlGenRandom` | Tested |
| FreeBSD | GCC / Clang | pthreads | `/dev/urandom` | Untested (expected to work) |
---
## Verification Commands
Anyone can verify the security claims. The default build needs only a C
compiler + make (plus libm/pthread); no external crypto library.
```bash
# Build
make
# Functional tests
make test-all
# Memory safety
```sh
make test-asan
# NIST/RFC test vectors
make test-vectors && ./test_vectors
# Verify Jasmin symbols are active
nm vaptvupt | grep "zupt_mac_verify_ct\|zupt_ct_select_32"
# Expected: T zupt_mac_verify_ct
# T zupt_ct_select_32
# Verify Jasmin compilation (requires jasminc)
jasminc -arch x86-64 -o /dev/null jasmin/zupt_mac_verify.jazz
jasminc -arch x86-64 -o /dev/null jasmin/zupt_mlkem_select.jazz
make test-asan-run
```
---
The first command builds the sanitizer configuration; the second executes its
test suite. Neither substitutes for the normal optimized build and tests.
© 2026 Cristian Cezar Moisés — AGPL-3.0-or-later (dual-licensed AGPL + commercial)
The full local Linux `make release-check` passed on the immutable, non-promoted
5.2.2 candidate at `ff99770`. Its recorded evidence includes packaging
`PASS=49 FAIL=0 SKIP=0`,
the 39/39 source-only scanner suite, strict GCC and Clang builds, GCC
`-fanalyzer`, 9/9 static analysis in a tool-enabled run, ASan/UBSan/LSan, and
1,000 mutation-fuzz iterations without a sanitizer-detected crash. An earlier
off-screen GUI smoke run is supporting evidence, not an exact-candidate package
result.
Post-tag CI integration failures prevented 5.2.2 promotion. Those upstream
self-audit results are not independent certification and do not transfer to
5.2.8. The immutable 5.2.3 candidate was not promoted because its source-policy
test assumed LF for a Windows `.bat` file checked out as CRLF. The immutable
v5.2.4 candidate was not promoted after exact-tag GitHub Actions run
`33431386002`: 12 jobs succeeded, the sole openSUSE job failed in its
standalone source-service harness because it did not enter the service
directory, and dependent Windows and macOS jobs were skipped. A local
Tumbleweed reproduction confirmed that `refs/tags/v5.2.4` is valid and that
`os.chdir(service_dir)` lets `obs_scm`, `tar`, and `recompress` complete with a
source-scanned archive. This was a release/test integration defect, not a
product, archive, cryptographic, codec, or SDK ABI change, and its evidence does
not transfer automatically to 5.2.8. The immutable v5.2.5 candidate was also
not promoted: exact-tag GitHub Actions run `33434986357` recorded 13 successful
jobs and failed native Windows/macOS jobs. Its Windows fixture-byte and macOS
secure-wipe/Bash 3.2 defects were corrected for 5.2.6. A targeted clean-clone
run of the corrected scanner under genuine GNU Bash 3.2.57 passed repository, standalone
tree, standalone archive, and root-plus-tag modes; that local compatibility
result does not transfer to any other gate. Exact-tag v5.2.6 run `33442264243`
then completed 13 jobs successfully but failed native macOS because x86 SHA-NI
test helpers were unused on arm64 under `-Werror`, and failed native Windows
when argv transcoding aborted the safe UTF-8 fixture. Those are test-harness
integration defects, not product, archive, cryptographic, codec, or SDK ABI
changes; v5.2.6 remained unpromoted, so its results did not transfer to the
required 5.2.8 suite. The immutable v5.2.7 candidate was likewise not
promoted: exact-tag run `33445470664` concluded `cancelled` at
`2026-08-31T23:11:19Z`, with 13 successful jobs, one failed macOS job after
raw-C1 fixture creation returned `EILSEQ`, and one cancelled Windows job after
the hosted job stalled in `make check`; a MinGW/Wine reproduction isolated the
cause to a redirected password prompt entering `_getch`. Version 5.2.8 makes
both test boundaries fail or skip without hanging. Manual pre-tag run
`33452602634` subsequently passed 14 of 15 jobs, including the native macOS
DMG and the Windows source audit, build, and full distribution checks. The
remaining Windows smoke failure was an old MSYS `grep` non-BMP pattern boundary
after ZUPT had compressed and verified all inputs; MinGW/Wine confirmed ZUPT's
byte-exact UTF-8 listing. The corrected gate validates Latin-1, BMP, and
non-BMP listing bytes without locale-sensitive matching, then requires
extraction and a full tree diff. The failed run is not exact-candidate
evidence. Exact-tag run `33456209269` then completed 15/15 jobs successfully,
including native Windows/macOS, the pinned local OBS service chain, package
installation/round trips, source-only checks, analyzers, and sanitizers.
Promotion run `33457868306` published the exact 13-file allowlist after
format, metadata, payload, and checksum validation. An unavailable or
unexecuted environment remains `SKIP`, never `PASS`; successful project CI is
still not independent security certification.
Run target-native static analyzers and package checks as additional evidence.
Do not infer x86_64, aarch64, ppc64le, s390x, riscv64, macOS, Windows, Leap, or
SLE success from these commands unless that exact environment produced a
successful recorded result.
ZUPT application code is distributed under AGPL-3.0-or-later. The bundled
VaptVupt codec source is GPL-3.0-or-later. The two xxHash-derived XXH64 units
also carry BSD-2-Clause. The pq-crystals/kyber-derived portions of native
ML-KEM carry CC0-1.0 in addition to the application license, and the x86 BCJ
state machine is adapted from public-domain LZMA SDK source. Native X25519
portions adapted from curve25519-donna conservatively retain BSD-3-Clause. See
`LICENSE`, `LICENSE-GPL-3.0`, `LICENSE-BSD-2-Clause`, `LICENSE-BSD-3-Clause`,
`LICENSE-CC0-1.0`, `NOTICE`, and `THIRD-PARTY-NOTICES.md`. Historical license
grants for exact earlier material are recorded in the 5.2.2 licensing erratum;
the current notices do not revoke them.

View file

@ -1,110 +1,179 @@
THIRD-PARTY NOTICES
===================
# Third-party and bundled-component notices
This document records VaptVupt's runtime dependencies and build-time
tools. If you redistribute VaptVupt, you must preserve this attribution
document along with the LICENSE file.
This file records bundled source, generated textual source and optional system
dependencies. Preserve it with LICENSE, NOTICE, and the applicable license
texts.
-------------------------------------------------------------------------
Licensing
-------------------------------------------------------------------------
## Bundled VaptVupt codec
**Note on VaptVupt LZ codec licensing**: the VaptVupt LZ codec
(src/vv_*.c, src/vaptvupt_api.c, include/vaptvupt*.h) is licensed
GPL-3.0-or-later (not AGPL like the rest of the project) so that, with
sufficient maturity, it can be considered for upstreaming into the Linux
or BSD kernels, which require GPL-compatible licenses. The author retains
the right to dual-license the codec under other terms for commercial use;
contact sac@securityops.co for inquiries.
The compression codec in src/vv_*.c, src/vaptvupt_api.c,
include/vaptvupt*.h, and include/vv_*.h is bundled as source and licensed
GPL-3.0-or-later.
The rest of the project (vaptvupt CLI, Jasmin source, GUI) is licensed
AGPL-3.0-or-later. Commercial licenses (relief from the AGPL network-use
clause) are available; contact sac@securityops.co.
- Recorded codec release: 2.65.3
- Recorded upstream tag: v2.65.3
- Standalone upstream: https://git.securityops.co/cristiancmoises/vaptvupt-codec
- Integration commit in this repository:
59f9ebc59ea13c6edf1d199ca795cdbf00e62226
-------------------------------------------------------------------------
Build-time tool (not redistributed)
-------------------------------------------------------------------------
The integration commit records an in-tree ANS safe-zone reserve applied on top
of that tag. Earlier integration commit a2350dd also records wrapper-default
changes used by the CLI. This repository did not retain the standalone tag
object hash, so the tag name and the immutable integration commits are the
provenance evidence available here; no unverified external hash is asserted.
**jasminc** — the Jasmin language compiler
The openSUSE package truthfully declares
bundled(vaptvupt-codec) = 2.65.3. No compiled codec object or library is
distributed in the source tree or source archive.
The constant-time cryptographic primitives in jasmin/*.jazz are
compiled to native assembly (jasmin/*.s) using the external `jasminc`
compiler. The jasminc tool is not bundled with VaptVupt; the AGPL .jazz
source files and their AGPL-licensed .s assembly output are bundled.
## Jasmin and textual assembly
Upstream: https://github.com/jasmin-lang/jasmin
License: MIT (the compiler itself; not relevant to VaptVupt's licensing)
Used by: VaptVupt's build system, only when re-generating jasmin/*.s
from jasmin/*.jazz (most users won't need to do this —
pre-built .s files ship in this repo).
Files under `jasmin/` include AGPL-licensed `.jazz` source or algorithm
descriptions and textual GNU assembly `.s`. The assembly is source, not an
object file. Provenance is recorded per production unit rather than treating
every `.s` file as generated:
-------------------------------------------------------------------------
Runtime system libraries (linked from the OS, never bundled)
-------------------------------------------------------------------------
- `zupt_mac_verify.s`, `zupt_mlkem_select.s`, and `zupt_x25519_fe.s` identify
themselves as output of Jasmin Compiler 2026.03.0;
- `zupt_aes_ctr.s` is recorded in its file header as `jasminc` output, but the
exact compiler version was not retained in that file, so no version stronger
than the repository record is asserted;
- `zupt_aes_ctr4.s` is hand-written production assembly matching the algorithm
documented by `zupt_aes_ctr4.jazz`; that `.jazz` file is not compiled.
These are standard system libraries provided by the operating system's
package manager (apt, dnf, pacman, etc.). They are dynamically linked
at runtime and are NOT redistributed as part of VaptVupt.
Regeneration of files identified as compiler output uses the external
`jasminc` compiler:
**libargon2** — Argon2id password hashing function (RFC 9106)
- Upstream: https://github.com/jasmin-lang/jasmin
- Compiler license: MIT
Required only for: the optional `make WITH_SDK=1` build. The default
build uses native PBKDF2-SHA256 and does not link
libargon2.
Linked at runtime: libargon2.so.1
Version expected: 1.0+ (Debian/Ubuntu: libargon2-1)
Upstream: https://github.com/P-H-C/phc-winner-argon2
License: Apache-2.0 OR CC0-1.0 (dual)
Copyright: (c) 2015 The Argon2 Authors
Used by: Argon2id password-derived encryption mode
The compiler itself is not bundled or redistributed. Hand-written assembly
must not be represented as generated or formally verified merely because a
corresponding `.jazz` description exists.
**OpenSSL libcrypto** — AES, SHA-256, AES-NI hardware backends
## Optional system libraries
Linked at runtime: libcrypto.so.3
Version expected: 3.0+
Upstream: https://www.openssl.org
License: Apache-2.0
Copyright: (c) 1998-2026 The OpenSSL Project
Used by: AES-256-CTR, SHA-256, hardware-accelerated paths
The default WITH_SDK=0 WITH_PQBOX=0 build uses the operating system's C runtime,
math and threading libraries and does not bundle a shared library.
-------------------------------------------------------------------------
Compatibility with public standards
-------------------------------------------------------------------------
WITH_SDK=1 and WITH_PQBOX=1 are opt-in integrations. They use only headers and
libraries supplied by the system/toolchain configuration and fail explicitly
when those dependencies are unavailable:
Where VaptVupt implements public standards, it does so independently from
any reference implementation. Other projects in the post-quantum hybrid
encryption space (libsodium, age, Tink, rustls, etc.) were referenced as
prior art during design, but no code was copied from any external
project. Standards followed:
- libvuptsdk: enables --pq-sdk and the Argon2id-backed SDK path;
- libpqvaptvupt: enables --pq-box.
- FIPS 197 (AES)
- FIPS 202 (Keccak / SHA-3)
- FIPS 203 (ML-KEM)
- RFC 5297 (AES-SIV)
- RFC 5869 (HKDF)
- RFC 7748 (X25519)
- RFC 8032 (Ed25519)
- RFC 8439 (ChaCha20-Poly1305)
- RFC 9106 (Argon2)
- RFC 9180 (HPKE)
The former vendor/vuptsdk and vendor/pqvaptvupt header snapshots and all
fallbacks to local precompiled libraries were removed. No download occurs in
make, packaging build, or package checks.
-------------------------------------------------------------------------
Reporting attribution issues
-------------------------------------------------------------------------
## xxHash-derived source
If you believe VaptVupt redistributes code from a project not listed here,
or if attribution information is incomplete, please email:
`src/zupt_xxh.c` and `src/vv_xxh64.c` contain adapted XXH64 routines based on
xxHash by Yann Collet. xxHash is BSD-2-Clause, not public domain. The upstream
copyright, conditions, and disclaimer are preserved in
`LICENSE-BSD-2-Clause`; those obligations apply in addition to the AGPL or GPL
scope identified by each source file.
sac@securityops.co
- Upstream: https://github.com/Cyan4973/xxHash
- Upstream license: https://github.com/Cyan4973/xxHash/blob/dev/LICENSE
with the subject "[third-party]" and details of the issue.
## pq-crystals/kyber-derived ML-KEM source
-------------------------------------------------------------------------
License summary
-------------------------------------------------------------------------
`src/zupt_mlkem.c` contains portions adapted from the pq-crystals/kyber
reference implementation, including its NTT, base multiplication, Montgomery
conversion, and related representation conventions. The upstream project
offers that reference code under either CC0-1.0 or Apache-2.0; ZUPT elects
the CC0-1.0 option for those portions. Local integration and modifications
remain under AGPL-3.0-or-later, as recorded by the compound per-file SPDX
identifier.
VaptVupt CLI, Jasmin source, GUI: AGPL-3.0-or-later
VaptVupt LZ codec: GPL-3.0-or-later
Commercial license (any component): contact sac@securityops.co
- Upstream: https://github.com/pq-crystals/kyber
- Upstream license record: https://github.com/pq-crystals/kyber/blob/main/LICENSE
- Local introduction commit: c80332778fb10364a606bf0380f440dc7be66ced
- Local FIPS 203 correction commit: 862f4a2df6c756ebd0369e176ea68b5ac506f422
Project home: https://git.securityops.co/cristiancmoises/vaptvupt
The repository did not retain an immutable upstream Kyber revision for the
original adaptation. No unverified upstream commit is asserted. The complete
CC0-1.0 legal text is in `LICENSE-CC0-1.0`.
## curve25519-donna-derived X25519 source
`src/zupt_x25519.c` contains portions adapted from the 5x51-bit
curve25519-donna implementation, including its field representation, packing,
constant-time swap, and inversion-chain approach. The upstream source file
describes the code as public domain, while the repository preserves a
BSD-3-Clause notice. This distribution conservatively retains that complete
BSD-3-Clause notice in `LICENSE-BSD-3-Clause`; local integration and
modifications remain AGPL-3.0-or-later under the compound per-file SPDX
identifier.
- Upstream: https://github.com/agl/curve25519-donna
- Upstream license record: https://github.com/agl/curve25519-donna/blob/master/LICENSE.md
- Upstream copyright: Copyright 2008, Google Inc.
- Upstream author record: Adam Langley
- Local introduction commit: c80332778fb10364a606bf0380f440dc7be66ced
The repository did not retain an immutable upstream revision for the original
adaptation. No unverified upstream commit is asserted, and the historical
reference to libsodium is treated as an implementation comparison rather than
an unsupported claim that libsodium was the copied source.
## LZMA SDK x86 BCJ source
The x86 state machine in `src/vv_bcj.c` is adapted from Igor Pavlov's
`C/Bra86.c` in the LZMA SDK. The official LZMA SDK is placed in the public
domain. The AArch64 filter in the same file is separately documented local
code and is not represented as LZMA SDK source.
- Upstream: https://www.7-zip.org/sdk.html
- Upstream author: Igor Pavlov
- Upstream status: public domain
The exact SDK version or revision used by the original integration was not
retained, so none is asserted. The former `clean-room` description was removed
because repository evidence cannot establish that development process.
## SHA-Intrinsics SHA-NI source
The SHA-NI compression path in `src/zupt_sha256_shani.c` is adapted from
Jeffrey Walton's public-domain `SHA-Intrinsics/sha256-x86.c` reference, which
records that it is based on Intel and Sean Gulley's miTLS material. The
immutable upstream reference below explicitly places the code in the public
domain; it therefore adds no separate package-license term. Local integration
and modifications remain AGPL-3.0-or-later.
- Upstream: https://github.com/noloader/SHA-Intrinsics
- Audited source revision: d03795497f3e4576083fc2cd8fe0b924f24d0bb2
- Upstream source: https://github.com/noloader/SHA-Intrinsics/blob/d03795497f3e4576083fc2cd8fe0b924f24d0bb2/sha256-x86.c
- Upstream author: Jeffrey Walton
- Upstream status: public domain
- Local introduction commit: 544a2cd64758478690e33a923b2ab75347122f51
## GUI image data
The PNG and ICO files under gui/assets/ are non-executable first-party GUI data.
Their purpose, Git provenance and license scope, including the historical MIT
grant attached to their unchanged Git blobs, are recorded in
`gui/assets/README.md`.
## AppImage type-2 runtime
No AppImage is a promised or promoted 5.2.8 release asset. The upstream
type-2 runtime inspected during the 5.2.2 review statically linked musl, libfuse,
squashfuse, zstd, zlib, and mimalloc, but its own license notice did not list
mimalloc and the available release inputs did not provide a complete
LGPL-compatible source/relink handoff. ZUPT therefore does not
redistribute that runtime.
`packaging/build-appimage.sh` remains an offline downstream helper. It accepts
no network input and requires the operator to supply both a locally verified
runtime and `APPIMAGE_RUNTIME_COMPLIANCE_FILE`, containing the license notices,
source correspondence or offer, and relink information applicable to those
exact runtime bytes. An artifact produced independently with that helper is
not covered by the 5.2.8 upstream release gates.
## Reporting attribution issues
Report incomplete or incorrect attribution to sac@securityops.co with the
subject [third-party].

View file

@ -1,311 +1,362 @@
# VaptVupt threat model
Plain-English description of what VaptVupt protects against, what it
doesn't, and what assumptions you're making when you use it.
This document is for users and downstream packagers. Read it before
trusting VaptVupt with anything you can't afford to lose.
---
## TL;DR
VaptVupt is designed for at-rest backup encryption by someone who
controls the machine doing the encryption and the machine doing the
extraction. It is not a network protocol, a multi-party scheme, or
a substitute for full-disk encryption.
| Use case | VaptVupt is appropriate? |
|---|---|
| Backing up files to an untrusted cloud (S3, Backblaze, Google Drive) | Yes |
| Backing up a disk image to external media you might lose | Yes |
| Long-term archival of personal/business data | Yes |
| Sharing an encrypted archive with someone you trust to handle the key | Yes, with care (see "Key distribution" below) |
| Real-time encrypted communication | No (use Signal, age, or TLS) |
| Multi-party access (n-of-m) | No (no threshold scheme) |
| Hiding the existence of an archive (steganography) | No (archive header has fixed magic bytes) |
| Protecting against a hostile machine you're encrypting on | No (a compromised host can read plaintext before encryption) |
---
## Modes referenced in this document
- `-p` / password mode: symmetric encryption with a key derived from a
password. The default build derives the key with PBKDF2-SHA256
(600k iterations). Argon2id is available only in an upstream
`make WITH_SDK=1` build against the separately distributed
libraries.
- `--pq`: native post-quantum mode (ML-KEM-768 + X25519), the PQ mode
in the default build. The ML-KEM-768 implementation is in-tree.
- `--pq-sdk` / `--pq-box`: optional post-quantum modes backed by the
separately distributed `libzuptsdk` / `libpqvaptvupt` libraries.
Available only in a `make WITH_SDK=1` build. Key files for these
modes are produced by `vaptvupt keygen --sdk`, also SDK-only.
---
## What VaptVupt protects against
### 1. Confidentiality of archive contents (encrypted mode)
An attacker with read access to the archive bytes cannot recover
plaintext file contents, file names, file sizes, file modes, or
embedded comments without the key/password, assuming:
- The chosen mode is one of the encrypted modes (`-p`, `--pq`, or the
optional `--pq-sdk` / `--pq-box`)
- The password is strong enough to resist offline brute-force
(see "Password strength" below)
- The key file (for `--pq-sdk` / `--pq-box`) was not compromised at
generation time
### 2. Integrity of every byte of an encrypted archive
If any single bit of the on-disk archive bytes is flipped, the
extraction fails with an authentication error. Coverage layers:
- Per-block HMAC-SHA256 with frame-preface AAD (F-09): every data
block carries an HMAC over its ciphertext and over the canonical
29-byte preface (block_type, codec_id, block_flags, sizes,
plaintext-XXH64)
- Archive Integrity Trailer (F-08): HMAC-SHA256 over the 64-byte
header and 24 bytes of footer, appended after the footer
- Strict structural validation of the encryption-header block (F-09):
codec must be `STORE`, flags must be 0, csz must equal usz, the
plaintext XXH64 must match
### 3. Tamper detection on plaintext archives (best-effort)
Plaintext archives (no `-p`, no `--pq*`) are protected by XXH64
plaintext checksums per block plus structural validation. This is
not cryptographic integrity — a determined attacker with write access
can produce a tampered plaintext archive that passes the checksum
(XXH64 is not collision-resistant). It does catch accidental
corruption and naive tampering.
Use an encrypted mode if you need cryptographic integrity.
### 4. Authentication failure indistinguishability (F-11)
The default error message for "wrong password", "wrong PQ key",
and "actual header tamper" is the same single line:
> `Error: Authentication failed (wrong key, wrong password, or tampered archive).`
This prevents an attacker who can issue extraction attempts from
learning which check failed first via the stderr output. Timing is
also constant (HMAC is always run, branchless return).
The detailed cause is available via `--verbose` for debugging on
machines under the user's own control.
### 5. Post-quantum forward secrecy (`--pq` and optional `--pq-sdk`)
The native `--pq` mode uses ML-KEM-768 (FIPS 203) hybridized with
X25519 via an HKDF combiner. Archives encrypted today cannot be
decrypted by a future quantum adversary holding only the ciphertext,
assuming:
- ML-KEM-768 retains its claimed security level (NIST Category 3,
192-bit classical / 96-bit quantum strength)
- X25519 hybridization protects against an unforeseen ML-KEM break
- The recipient's private key is not later compromised
The optional `--pq-sdk` mode provides the same hybrid guarantee via
the separately distributed SDK libraries.
### 6. Side-channel resistance for cryptographic primitives
The hot crypto paths (AES-256-CTR, HMAC-SHA256 comparison, X25519
field operations, ML-KEM polynomial arithmetic) are implemented in
Jasmin and proved constant-time at the assembly level on x86_64.
Non-Jasmin platforms (aarch64, fallback x86_64) use C implementations
that avoid secret-dependent branches and memory accesses where
feasible — but without formal proof.
---
## What VaptVupt does NOT protect against
### 1. Compromised endpoints
VaptVupt cannot protect against:
- Malware on the machine doing the encryption (it sees plaintext
before any crypto is applied)
- Malware on the machine doing the extraction (it sees plaintext
after decryption)
- A hardware keylogger capturing the password
- A compromised user account that can read your files or
`~/.zupt-key` directly
- Cold-boot attacks on running machines
If you don't trust the machine, VaptVupt cannot help.
### 2. Key compromise
If the password or `~/.zupt-key` is leaked:
- All archives encrypted with that key are decryptable
- VaptVupt has no forward secrecy across archives — each archive
is encrypted under a single static key derived from the password
or stored in the key file
- There is no key-rotation feature; rotate by re-encrypting
archives under a new password/key and securely deleting the old
password/key
For high-value, long-term archives, treat the key file as you
would a master password: store it offline, encrypt it under
another layer (e.g. on an encrypted USB), and rotate periodically.
### 3. Password strength
Password mode derives the key with PBKDF2-SHA256 (600k iterations)
in the default build, or Argon2id in a `make WITH_SDK=1` build. A
key derivation function slows offline guessing but does not make a
short, common password safe: a determined attacker with GPU clusters
or cloud compute can still exhaust a weak password.
Use a long, high-entropy password — a multi-word diceware passphrase
or a random 16+ character string with a full alphabet. For critical
data, use a key-file mode (native `--pq`, or the optional `--pq-sdk`
with a random key file from `vaptvupt keygen --sdk`) so the key is
CSPRNG output, not derived from human-typed text.
### 4. Metadata leakage from archive structure
Even with encryption, an attacker who can see the archive bytes
can infer:
- Approximate file count (from `total_blocks` in the footer)
- Total archive size (file size on disk)
- Whether the archive is encrypted at all (`ZUPT_FLAG_ENCRYPTED`
in the global flags is visible)
- Whether the archive is solid or per-file mode (visible flag)
- Whether post-quantum mode is in use (visible flag)
- Approximate file size distribution (block sizes are visible
even when block payloads are encrypted)
- Archive creation time (a 64-bit timestamp in the header)
- A random 16-byte UUID per archive (no information leak, but
globally identifies the archive across copies)
If metadata privacy matters, layer VaptVupt under another tool that
hides bulk metadata (e.g., put the `.zupt` file inside a fixed-size
encrypted container).
### 5. Network attacks
VaptVupt is not a network protocol. There is no:
- Forward-secure session establishment (use TLS or Noise)
- Mutual authentication of remote parties (use signed messages or
TLS client certs)
- Replay protection across sessions (archives can be replayed by
an attacker who can write to the destination)
- Network-layer encryption (use TLS to transport `.zupt` files)
### 6. Multi-party schemes
There is no threshold cryptography, no n-of-m sharing, no
multi-party computation, no proxy re-encryption. Each archive
has exactly one decryption credential (one password OR one
recipient key). To give two people access to the same archive,
they must share the password or the key file.
### 7. Plausible deniability / hidden volumes
VaptVupt archives have a fixed 6-byte magic `\x90\x5a\x55\x50\x54\x01`
at offset 0. Anyone scanning the bytes can see it's a VaptVupt
archive. VaptVupt has no hidden-volume or duress-password feature.
### 8. Side channels we don't claim to address
- Power analysis (relevant for embedded targets, not commodity desktops)
- Electromagnetic emanation
- Acoustic side channels
- Network timing of upload patterns
- Filesystem-level metadata (mtime/atime of the `.zupt` file)
### 9. Trusted setup of post-quantum primitives
The in-tree ML-KEM-768 implementation was not independently audited
at the time of writing. We use NIST KAT vectors for correctness
verification but have not formally proven constant-time properties
for every PQ code path.
For maximum assurance, treat the post-quantum layer as a hedge — it
does not replace the X25519 layer; both must be broken for an
attacker to recover plaintext.
### 10. Format extension attacks
The format is versioned (v1.6). Older readers may accept newer
archives in unexpected ways. We try to maintain forward
compatibility, but a careful attacker who can produce
malformed-but-just-valid archives may find parser-state issues that
don't rise to the level of a CVE. The fuzzing harness
(`make fuzz-format`) is the primary mitigation; report bugs.
### 11. Compression-side-channel attacks (CRIME / BREACH style)
VaptVupt compresses before encryption. If an attacker can:
- Influence part of the plaintext (e.g. inject a known prefix)
- Observe the resulting archive size precisely
then they can use the compression ratio to learn information about
the rest of the plaintext — this is the classic CRIME/BREACH attack
against TLS compression.
VaptVupt is designed for offline backup, where attacker-controlled
plaintext injection is rare. If your threat model includes
attacker-chosen plaintext mixed with secret plaintext in the same
archive, use `--no-compress` (codec 0 = STORE) to disable the
LZ codec and eliminate this side channel.
---
## Cryptographic assumptions
VaptVupt's security rests on the following standard assumptions:
| Assumption | What breaks if it fails |
|---|---|
| AES-256-CTR is a secure stream cipher | All encrypted archives become readable |
| HMAC-SHA256 is a secure PRF / MAC | Tamper detection fails; integrity can be forged |
| PBKDF2-SHA256 (or Argon2id, WITH_SDK) is a secure password KDF | Password-mode archives become brute-forceable faster |
| ML-KEM-768 retains NIST Category 3 security | `--pq` / `--pq-sdk` reduce to the X25519 layer |
| X25519 retains 128-bit security (no quantum) | PQ modes reduce to the ML-KEM layer; classical password mode unaffected |
| HKDF-SHA256 is a secure key-derivation construction | Combined PQ + classical keys may be predictable |
| SHA3 / SHAKE retain pre-image and collision resistance | Auxiliary protocol bindings may be forged |
If you don't trust one of these primitives, VaptVupt cannot protect
you. We rely on the same primitives the broader cryptographic
community has standardized.
---
# ZUPT 5.2.8 threat model
This document defines the security boundary of the ZUPT archive tool. It is
not a certification, a guarantee against every hostile input, or a substitute
for reviewing the exact source and binary used for important data.
## Intended use
ZUPT is intended for at-rest backup archives created and restored on
machines controlled by the user. It can be used when the storage provider or
physical medium is not trusted, provided encryption is enabled and credentials
remain secret.
It is not a network protocol, a full-disk encryption system, a multi-party or
threshold scheme, a password manager, or a way to make an archive's existence
plausibly deniable.
## Baseline considered here
The upstream baseline is built from the 5.2.8 source with:
```sh
make WITH_SDK=0 WITH_PQBOX=0
```
It contains the native password, ML-KEM-768 + X25519 hybrid `--pq`, and
ML-KEM-768-only `--pq-only` modes. It does not load a precompiled library from
the repository and does not download a dependency while building.
`WITH_SDK=1` and `WITH_PQBOX=1` add separately installed system libraries and
change the assessed code boundary. The SDK and PQBOX integrations must be
reviewed with their exact packaged source and version; success of the baseline
tests is not evidence for them.
Textual assembly under `jasmin/` is a separate `WITH_JASMIN=1` option for
supported x86_64 compiler targets. The directory contains both generated and
separately identified hand-written assembly. Portable C is the default.
Architecture portability is a source property, not evidence that an unexecuted
architecture passed.
## Assets
The assets ZUPT tries to protect are:
- archived file contents and encrypted index data;
- the integrity and ordering of encrypted archive blocks and current global
metadata covered by the archive integrity trailer;
- private keys, passwords, and derived encryption/MAC keys while held by the
trusted caller;
- safe placement of extracted entries within the requested destination.
The archive's existence, total byte length, magic, encryption/framing flags, and
some size/structure information are observable. Plain archives provide
corruption detection, not cryptographic protection against an active attacker.
## Adversaries considered
The design considers an adversary who can read, copy, truncate, reorder, or
modify stored archive bytes but cannot read the encryption endpoint's memory or
credentials. It also considers accidental corruption and malicious archive
entry paths during extraction.
The following adversaries are outside the protection boundary:
- malware, a keylogger, or an administrator on the source or restore endpoint;
- an attacker who obtains the password or matching private key;
- a malicious compiler, kernel, CPU, firmware, or random-number generator;
- an attacker with unrestricted side-channel observation of a shared machine;
- an attacker allowed unbounded CPU, memory, or storage denial of service.
## Security properties
### Encrypted archive confidentiality
Password and native PQ modes encrypt blocks with AES-256-CTR and authenticate
them with HMAC-SHA256. Confidentiality depends on unique nonces, correct
implementations, OS randomness, and credential secrecy. In password mode it
also depends on password entropy; PBKDF2-SHA256 slows but cannot prevent offline
guessing of a weak password.
Prefer `--password-prompt`, `--pass-file`, or `--pass-fd`. A password supplied
through `-p/--password` can be visible through process inspection or shell
history. A password file is protected only by the caller's filesystem choices;
ZUPT does not validate its ownership or permission bits. A descriptor is
trusted input inherited from the caller. Both non-interactive forms read one
line and reject empty, NUL-containing, or overlong values. The descriptor form
duplicates but shares the underlying stream/offset and may buffer beyond the
line, so callers should provide a descriptor dedicated to that password read.
On POSIX, handled prompt interruptions restore the saved terminal state before
termination; an exact-candidate PTY regression is required before release.
On Windows, a prompt is entered only for a real console input handle;
redirected input and console EOF fail instead of blocking in `_getch`.
Native private-key generation uses no-replace creation with POSIX mode `0600`
or a Windows current-user-only DACL. A failed write, flush/fsync, or close leaves
the incomplete or durability-uncertain exclusive file for manual review and
removal instead of risking an unlink-after-close race against a replacement
pathname. ZKEY and ZPQK inputs
are accepted only after checksum, version, flags, reserved bytes, exact size,
and public/private role validation. This prevents role confusion and
partial/trailing-key acceptance; it does not protect a key after endpoint or
account compromise.
When the optional system SDK is enabled, the in-repository adapter copies a key
through the core atomic publisher, applies POSIX mode through the already-open
temporary descriptor, and publishes only after copy/close checks succeed. Its
`sdk-test` regression preserves existing symlink/hardlink targets and verifies
private/public modes. This narrows the adapter boundary; it does not extend the
baseline assessment to the external SDK implementation.
### Encrypted archive integrity
Current encrypted archives authenticate ciphertext, canonical block metadata,
and each frame's logical position. DATA and DEDUP_REF frames both receive this
positional AAD. A reference is authenticated at its own position and carries
the authenticated source position needed to verify the referenced DATA frame,
so exchanging otherwise equivalent frames is not accepted.
Current archives carry an archive-integrity trailer for global metadata. The
`extract`, `list`, `test`, and `disk restore` paths refuse any no-AIT layout by
default without relying on an unauthenticated header flag.
`--allow-legacy-no-ait` is a narrowly scoped, warning-producing recovery option
for those commands when the caller already trusts a pre-AIT archive. Selecting
it for attacker-controlled storage removes the header/footer authentication
assumption and is outside this threat model. `info` is an unauthenticated
framing inspection that reports apparent AIT presence but validates neither the
trailer nor archive contents. These checks do not prevent deletion of the
entire archive, rollback to an older valid archive, or storage-layer replay.
Archive comments remain untrusted presentation data even when they are
authenticated. Display paths render control bytes without emitting raw terminal
control sequences, limiting terminal-output injection while leaving the stored
and authenticated comment bytes unchanged.
New 5.2.2 encrypted+dedup archives authenticate each reference offset. New
encrypted disk archives also authenticate an index that binds image size,
block count, and a chained XXH64 hash of the complete restored stream. The
writer's additional SHA-256/128 comparison is only an in-memory collision guard
before deduplication; it is not an on-disk cryptographic hash. XXH64 is not
cryptographic, so a writer who controls a plain archive can recompute it.
Plain archives use non-cryptographic checksums. A writer who controls a plain
archive can recompute them.
### Native hybrid post-quantum mode
The `--pq` mode combines an ML-KEM-768 shared secret and an X25519 shared secret
as implemented in 5.2.2:
```text
hybrid_ikm = ml_ss XOR x25519_ss
archive_key = SHA3-512(hybrid_ikm || ml_ct || ephemeral_pk ||
"ZUPT-HYBRID-v1")
```
Its goal is harvest-now/decrypt-later resistance if ML-KEM-768 remains secure,
with X25519 as a classical hedge under the combiner assumptions. This is not
session forward secrecy: compromise of the recipient's long-term private key
can compromise previously captured archives encrypted to it.
The native `--pq-only` mode removes X25519 and derives a key from ML-KEM-768
alone. Use it only when a policy specifically excludes the classical component;
it loses the hybrid hedge.
The in-tree ML-KEM code has project tests, including known-answer vectors and a
conditional OpenSSL 3.5 interoperability test. It has not been independently
audited or formally verified as a whole implementation.
### Extraction containment
The reader rejects absolute paths, traversal components, control characters,
ambiguous trailing dot/space components, NTFS alternate-stream syntax, and
reserved Windows device names. POSIX extraction resolves every parent below a
pinned destination descriptor with no-follow operations after canonicalizing
the user-selected root once. Windows extraction
uses handle-relative traversal, rejects reparse-point parents, and publishes the
final name by handle without replacing an existing leaf. A checked path is not
re-resolved through a mutable parent.
Decoded bytes are first written to a private, exclusively created temporary
file. The final name is published only after the expected decoded size and
chained checksum match and the stream closes successfully; failures remove the
temporary through its descriptor or handle. These controls reduce traversal,
link, race, and partial-output risks, but do not establish that no parser or
filesystem bug can exist.
Benchmark scratch data lives in a random private directory. Cleanup resolves
POSIX components without following links and deletes relative to pinned
descriptors. On Windows it retains no-delete-sharing ancestor handles, refuses
reparse-point recursion, then reopens each emptied directory relative to its
pinned parent and verifies its filesystem identity before handle-based
deletion. An attacker who inserts a link can cause cleanup failure, but the
cleanup must not traverse to the link target.
The Windows handle-relative boundary in 5.2.8 covers normal local Win32 paths.
Win32 extended-length and device-namespace paths, raw UNC output roots, and
mapped/network-drive output are not supported. Cross-build and Wine results are
not a substitute for the required native `windows-latest` Unicode package
gate. Restore locally before moving verified output to network storage.
Disk restore copies the measured compacted archive into one exclusively
created, auto-deleted scratch file. Preflight and restoration consume that same
open snapshot. An explicit `ZUPT_TMPDIR` selects an existing scratch directory;
failure there does not fall back to consuming the mutable source pathname. On
POSIX, the destination is opened once without truncation or final-symlink
following, classified with `fstat`, and the same raw-device descriptor is
retained for supported Linux, macOS, and FreeBSD capacity checks and writes.
Regular-file output retains atomic publication. A raw target is rejected before
writing if its capacity is unknown or smaller than the image. These controls
reduce source exchange, target exchange, and immediate overrun risk but do not
protect against a compromised kernel/device, a wrongly selected sufficiently
large device, power loss, or hardware failure.
The SDK publication, POSIX disk-target, and benchmark-cleanup changes address
CodeQL High #5, #6, and #7 respectively. Their source review and regressions
alone are project evidence, not independent certification. Exact-tag run
`33456209269` subsequently passed all 15 hosted jobs at
`ebb9ab3aa1d42c50030ca02883f6162dc4771fe1`; final release-commit CodeQL run
`33456049125` completed successfully with zero open alerts.
For an untrusted archive:
1. use a new empty destination outside sensitive trees;
2. run as a dedicated unprivileged user, never root;
3. apply a container, sandbox, resource limits, and a storage quota when
available;
4. inspect extracted paths, types, permissions, and content before moving them;
5. never restore a disk image to a device without independently confirming both
source and destination.
## Non-goals and residual risks
ZUPT does not claim to provide:
- resistance to cache, power, EM, acoustic, speculative-execution, or all
compiler-introduced timing side channels;
- bounded resource consumption for every malformed archive;
- confidentiality of archive size or complete framing metadata;
- protection against compression-length oracles when secret and
attacker-controlled data are compressed together;
- rollback detection across multiple valid versions of a backup;
- forward-secure sessions, remote authentication, replay protection, or secure
transport;
- automatic key rotation, recovery, escrow, threshold access, or secure
deletion;
- preservation of every operating-system ACL, ownership attribute, extended
attribute, or special-file semantic;
- safe operation on a compromised host.
## Credential handling
- Generate PQ keys on a trusted system using the OS CSPRNG.
- Keep private keys separate from the archive and from release/package inputs.
- Store an offline recovery copy and test recovery before relying on a backup.
- Use a distinct high-entropy credential where compromise isolation matters.
- Re-encrypt under a new credential after suspected disclosure; there is no
in-place key rotation.
- Never include credentials or sensitive archives in bug reports or CI logs.
## Supply-chain boundary
Git and upstream source archives are source-only. They must pass
`scripts/check-source-only.sh` and must not contain executable code artifacts,
objects, shared/static libraries, distribution packages, unsafe symlinks, or Git
LFS pointers.
Nested inspection is itself an untrusted-input boundary. The release scanner
must cap recursion depth, archive members, per-entry expansion, and total
expanded bytes and fail closed when a cap is reached. Commit `ff99770` passed
all 39 source-only scanner cases, including GNU thin archives, scanner-bomb
limits, and safe diagnostic cases.
DEB, binary RPM, SRPM, notice-bearing Linux tar.xz, source-only portable GUI
ZIP, Windows ZIP, and macOS DMG files can be published separately from the
tagged source. Each artifact extends the trust boundary to its builder,
toolchain, runner image, and packaging scripts. Treat it as validated only when
the exact target has a recorded build, content/package inspection, extracted or
installed smoke test, and applicable archive round trip. An AppImage is not
promoted for 5.2.8; bare Linux and Windows executables are also excluded.
For 5.2.8, that gated artifact scope covers the CLI files plus the exact GUI
DEB, noarch/source RPM, and source-only portable ZIP named in the README. The
portable ZIP contains no compiled runtime and crosses the release boundary only
after source scans and an exact safe-member check. AppDir and Flatpak bundles
and GUI platform installers remain excluded; Windows ZIP and macOS DMG outputs
remain CLI-only.
The immutable, non-promoted 5.2.2 candidate at `ff99770` passed the full local
`make release-check`: packaging reported `PASS=49 FAIL=0 SKIP=0`; strict GCC,
strict Clang, GCC `-fanalyzer`, the 9/9 tool-enabled static-analysis run,
ASan/UBSan/LSan, and 1,000 mutation-fuzz iterations passed. Earlier off-screen
GUI smoke evidence is retained separately. Post-tag CI integration failures
prevented 5.2.2 promotion. This upstream self-review is not an independent
certification and is not 5.2.8 evidence. The immutable 5.2.3 candidate was not
promoted because its source-policy test assumed LF for a Windows `.bat` checkout
that correctly used CRLF. The immutable v5.2.4 candidate was not promoted after
exact-tag GitHub Actions run `33431386002`: 12 jobs succeeded, the sole openSUSE
service-harness job failed because its standalone executor did not enter the
service directory, and dependent Windows/macOS jobs were skipped. A local
Tumbleweed reproduction established that the explicit `refs/tags/v5.2.4`
revision works and that `os.chdir(service_dir)` completes the source-service
chain. This narrows the failure to release/test integration; it changes no
product, archive, cryptographic, codec, or SDK ABI boundary and supplies no
automatic 5.2.8 evidence. The immutable v5.2.5 candidate was not promoted after
exact-tag GitHub Actions run `33434986357`: 13 jobs succeeded, but native
Windows and macOS failed on fixture-byte preservation and Darwin/Bash 3.2
portability respectively. The corresponding 5.2.6 corrections were followed by
exact-tag run `33442264243`: 13 jobs succeeded, while native macOS failed on
x86-only SHA-NI helper declarations unused on arm64 under `-Werror`, and native
Windows aborted during safe UTF-8 fixture argv transcoding. The v5.2.6 tag was
not promoted. Version 5.2.7 corrected those two boundaries, but its exact-tag
run `33445470664` concluded `cancelled` at `2026-08-31T23:11:19Z`, with 13
successful jobs, one failed macOS job after raw-C1 filename creation returned
`EILSEQ`, and one cancelled Windows job after the hosted job stalled in `make
check`; a MinGW/Wine reproduction isolated the cause to a redirected password
prompt entering `_getch`.
The corresponding 5.2.8 fixture and prompt corrections alone did not establish
a result. Exact-tag run `33456209269` then passed 15/15 jobs, including
`sdk-test`, native Windows/macOS, the pinned local OBS source-service chain,
and the package/source gates. Promotion run `33457868306` published the exact
13 tested assets. Official authenticated OBS/Factory acceptance, the full
automatic debug-package result, and untested architectures remain unclaimed;
the earlier `debugsource` rpmlint `no-binary` finding remains unresolved and
unsuppressed.
## Historical compatibility notes
These are historical facts about earlier releases, retained to support recovery:
- Releases through 4.1.0 could reuse an AES-CTR nonce in encrypted `--dedup`
archives. Release 4.2.0 changed to fresh random per-block nonces. Re-encrypt
affected older archives.
- Releases through 4.2.1 used round-3 CRYSTALS-Kyber semantics in the native PQ
path. Release 5.0.0 corrected the implementation to FIPS 203 ML-KEM-768,
changing native PQ key/archive compatibility. See `CHANGELOG.md` before
planning cross-version restoration.
- Pre-AIT archive layouts now fail closed by default. The explicit
`--allow-legacy-no-ait` read option is only for recovery from a known, trusted
historical archive and leaves its header/footer metadata outside the current
authenticated boundary.
- The 5.2.2 reader retains compatibility parsers for the fixed-width disk index
and encrypted-dedup linear AAD sequence published through 5.2.1. An actual
v5.2.1 password-encrypted DATA/DATA/REF/DATA disk fixture is stored as hexadecimal
text with source and hash provenance. The candidate lists, tests, extracts, and restores
it byte-exact, with a warning that the legacy index has no whole-image hash;
the full local Linux gate passed on commit `ff99770`. Older readers are not
claimed to accept new flag-gated 5.2.2 records, and untested historical mode
combinations remain unclaimed.
Historical test counts in the changelog describe those releases. They do not
automatically become 5.2.8 results; current outcomes belong in the release
validation record, with unavailable environments marked `SKIP`. In particular,
runs made before the final positional-AAD and mandatory-AIT changes are not
final release gates for the resulting candidate.
## Reporting security issues
Email `sac@securityops.co` with the subject `VaptVupt security report`.
PGP key available on request.
Email **zupt@riseup.net** with `[security]` in the subject. Include the version,
platform, impact, and a minimal non-sensitive reproducer. Do not disclose the
issue publicly until a coordinated timeline has been agreed.
We will:
- Acknowledge receipt within 7 days
- Investigate and publish a CVE / advisory if warranted
- Credit you in the CHANGELOG if you wish
Please don't open public issues for security reports until we've
coordinated disclosure. For non-security bugs (parser edge cases,
documentation typos, performance issues), open a public issue
normally.
---
## Document version
This threat model covers archive format v1.6 as shipped in VaptVupt
4.1.0. It is part of the source tree (`THREAT_MODEL.md`) and
versioned with the project; this section will be updated as the
format evolves.
Document version: 5.2.8, 2026-08-31.

View file

@ -1,21 +0,0 @@
@echo off
echo Zupt v0.4 Build Script for Windows
where gcc >nul 2>nul
if %ERRORLEVEL% EQU 0 (
gcc -Wall -Wextra -O2 -std=c11 -Iinclude ^
src\zupt_main.c src\zupt_format.c src\zupt_lz.c src\zupt_lzh.c src\zupt_xxh.c ^
src\zupt_sha256.c src\zupt_aes256.c src\zupt_crypto.c src\zupt_predict.c ^
-lm -o zupt.exe
if %ERRORLEVEL% EQU 0 (echo [OK] zupt.exe) else (echo [FAIL])
exit /b %ERRORLEVEL%
)
where cl >nul 2>nul
if %ERRORLEVEL% EQU 0 (
cl /nologo /W4 /O2 /Iinclude /D_CRT_SECURE_NO_WARNINGS ^
src\zupt_main.c src\zupt_format.c src\zupt_lz.c src\zupt_lzh.c src\zupt_xxh.c ^
src\zupt_sha256.c src\zupt_aes256.c src\zupt_crypto.c src\zupt_predict.c ^
/Fe:zupt.exe & del *.obj 2>nul
exit /b 0
)
echo No C compiler found.
exit /b 1

View file

@ -1,136 +0,0 @@
#compdef vaptvupt zupt
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
#
# Install:
# sudo install -m 644 completions/_zupt /usr/share/zsh/site-functions/_zupt
# or for a single user (anywhere in $fpath):
# cp completions/_zupt ~/.zsh/completion/_zupt
# # then in ~/.zshrc:
# # fpath=(~/.zsh/completion $fpath)
# # autoload -U compinit && compinit
_zupt_levels() {
_values 'compression level' \
'1[fastest, smallest window]' \
'2[fast]' \
'3[balanced (low)]' \
'4[balanced]' \
'5[balanced (high)]' \
'6[high compression]' \
'7[default; high]' \
'8[maximum, 1MB window]' \
'9[maximum, deep search]'
}
_zupt_kdf() {
_values 'KDF' \
'argon2id[memory-hard, default since v2.4.1]' \
'pbkdf2[legacy 600k-iter PBKDF2-SHA256]'
}
_zupt_threads() {
_values 'threads' '0[auto]' '1' '2' '4' '8' '16' '32' '64'
}
_zupt_compress_opts() {
_arguments \
'(-l --level)'{-l,--level}'[compression level]:level:_zupt_levels' \
'(-b --block)'{-b,--block}'[block size in bytes]:size:' \
'(-s --store)'{-s,--store}'[store without compression]' \
'(-f --fast)'{-f,--fast}'[use fast LZ codec]' \
'(--vv --vaptvupt)'{--vv,--vaptvupt}'[use VaptVupt codec]' \
'--lzhp[use Zupt-LZHP codec (LZ77+Huffman, no SIMD)]' \
'(-p --password)'{-p,--password}'[encrypt with password]:password:' \
'--kdf[password KDF]:kdf:_zupt_kdf' \
'(-c --comment)'{-c,--comment}'[embed archive comment]:text:' \
'--comment-file[read comment from file]:file:_files' \
'--pq[legacy PQ encryption]:pubkey:_files' \
'--pq-sdk[PQ encryption via libzuptsdk]:pubkey:_files' \
'(-D --dedup)'{-D,--dedup}'[block-level deduplication]' \
'--solid[solid mode: single stream]' \
'(-v --verbose)'{-v,--verbose}'[verbose output]' \
'(-q --quiet)'{-q,--quiet}'[suppress non-error output]' \
'(-t --threads)'{-t,--threads}'[thread count]:threads:_zupt_threads' \
'*:files:_files'
}
_zupt_extract_opts() {
_arguments \
'(-o --output)'{-o,--output}'[output directory]:directory:_directories' \
'(-p --password)'{-p,--password}'[decryption password]:password:' \
'--pq[legacy PQ decryption]:privkey:_files' \
'--pq-sdk[PQ decryption via libzuptsdk]:privkey:_files' \
'(-v --verbose)'{-v,--verbose}'[verbose output]' \
'(-t --threads)'{-t,--threads}'[thread count]:threads:_zupt_threads' \
'*:archive:_files -g "*.zupt"'
}
_zupt() {
local context curcontext="$curcontext" state line
local -a subcommands
subcommands=(
'compress:create an archive'
'c:create an archive (alias)'
'extract:extract an archive'
'x:extract an archive (alias)'
'list:list archive entries'
'l:list archive entries (alias)'
'test:verify archive integrity'
't:verify archive integrity (alias)'
'info:archive metadata (no key needed)'
'bench:benchmark levels 1-9'
'disk:full-disk backup/restore'
'keygen:generate a key file'
'version:print version info'
'help:print help'
)
_arguments -C \
'(-): :->command' \
'(-)*:: :->args'
case $state in
command)
_describe -t commands 'zupt subcommand' subcommands
;;
args)
case $line[1] in
compress|c)
_zupt_compress_opts
;;
extract|x)
_zupt_extract_opts
;;
list|l|test|t)
_arguments \
'(-p --password)'{-p,--password}'[password]:password:' \
'--pq[legacy PQ privkey]:privkey:_files' \
'--pq-sdk[PQ privkey]:privkey:_files' \
'(-v --verbose)'{-v,--verbose}'[verbose]' \
'*:archive:_files -g "*.zupt"'
;;
info)
_arguments '*:archive:_files -g "*.zupt"'
;;
disk)
_values 'disk action' 'backup' 'restore'
;;
keygen)
_arguments \
'--sdk[generate SDK v2 keypair]' \
'--pq-sdk[same as --sdk]' \
'-o[output keyfile]:file:_files' \
'--pub[export public key from -k]' \
'-k[source private key for --pub]:file:_files'
;;
bench)
_arguments '*:files:_files'
;;
esac
;;
esac
}
_zupt "$@"

132
completions/_zupt Normal file
View file

@ -0,0 +1,132 @@
#compdef zupt
# SPDX-License-Identifier: AGPL-3.0-or-later
local context state state_descr line
local -a _zupt_disk_legacy_options
typeset -A opt_args
_zupt_password_options=(
'(-p --password)'{-p,--password}'[password in process arguments]:password:'
'--password-prompt[read password interactively without echo]'
'--pass-file[read password from first line of file]:password file:_files'
'--pass-fd[read password from inherited file descriptor]:file descriptor:'
)
_zupt_pq_options=(
'--pq[native ML-KEM-768 + X25519 hybrid key]:key file:_files'
'--pq-only[native ML-KEM-768-only key]:key file:_files'
'--pq-sdk[optional system libvuptsdk key]:key file:_files'
'--pq-box[optional system libpqvaptvupt key]:key file:_files'
)
_zupt_read_options=(
"${_zupt_password_options[@]}"
"${_zupt_pq_options[@]}"
'(-v --verbose)'{-v,--verbose}'[additional diagnostics]'
'--allow-legacy-no-ait[recover a trusted old archive without an integrity trailer]'
)
_arguments -C \
'1:command:->command' \
'*::argument:->arguments'
case $state in
command)
_values 'command' \
'compress:create an archive' 'c:create an archive' \
'extract:extract an archive' 'x:extract an archive' \
'list:list archive entries' 'l:list archive entries' \
'test:verify archive integrity' 't:verify archive integrity' \
'info:show framing metadata' 'i:show framing metadata' \
'bench:benchmark compression levels' 'b:benchmark compression levels' \
'disk:back up or restore a disk image' \
'keygen:generate or export a recipient key' \
'version:show version and build information' \
'help:show command help' '--version:show version and build information' \
'-V:show version and build information' '--help:show command help' \
'-h:show command help'
;;
arguments)
case ${line[1]} in
compress|c)
_arguments \
'(-l --level)'{-l,--level}'[compression level]:level:(1 2 3 4 5 6 7 8 9)' \
'(-b --block)'{-b,--block}'[block size in bytes]:bytes:' \
'(-s --store)'{-s,--store}'[store without compression]' \
'(-f --fast)'{-f,--fast}'[use fast LZ codec]' \
'(--vv --vaptvupt)'{--vv,--vaptvupt}'[force VaptVupt LZ + ANS codec]' \
'--lzhp[force portable LZHP codec]' \
"${_zupt_password_options[@]}" \
'--kdf[password KDF]:KDF:(pbkdf2 argon2id)' \
'(-c --comment)'{-c,--comment}'[archive comment]:comment:' \
'--comment-file[read archive comment from file]:comment file:_files' \
"${_zupt_pq_options[@]}" \
'(-D --dedup)'{-D,--dedup}'[block-level deduplication]' \
'(-S --solid)'{-S,--solid}'[solid single-stream compression]' \
'(-y --force)'{-y,--force}'[overwrite an existing non-.zupt output]' \
'(-v --verbose)'{-v,--verbose}'[additional progress output]' \
'(-t --threads)'{-t,--threads}'[compression thread count]:threads:' \
'1:output archive:_files -g "*.zupt"' \
'*:input file or directory:_files'
;;
extract|x)
_arguments \
'(-o --output)'{-o,--output}'[output directory]:directory:_directories' \
"${_zupt_read_options[@]}" \
'(-t --threads)'{-t,--threads}'[decompression thread count]:threads:' \
'1:archive:_files -g "*.zupt"'
;;
list|l|test|t)
_arguments \
"${_zupt_read_options[@]}" \
'1:archive:_files -g "*.zupt"'
;;
info|i)
_arguments '1:archive:_files -g "*.zupt"'
;;
bench|b)
_arguments '--compare[compare available external compressors]' \
'*:input file or directory:_files'
;;
disk)
_zupt_disk_legacy_options=()
if [[ ${line[2]-} == restore ]]; then
_zupt_disk_legacy_options=(
'--allow-legacy-no-ait[recover a trusted old disk archive without an integrity trailer]'
)
fi
_arguments -C \
'1:disk command:(backup restore)' \
'(-l --level)'{-l,--level}'[compression level]:level:(1 2 3 4 5 6 7 8 9)' \
'(-b --block)'{-b,--block}'[block size in bytes]:bytes:' \
'(-s --store)'{-s,--store}'[store without compression]' \
'(--vv --vaptvupt)'{--vv,--vaptvupt}'[force VaptVupt LZ + ANS codec]' \
'--lzhp[force portable LZHP codec]' \
"${_zupt_password_options[@]}" \
'--kdf[password KDF]:KDF:(pbkdf2 argon2id)' \
'(-c --comment)'{-c,--comment}'[archive comment]:comment:' \
'--comment-file[read archive comment from file]:comment file:_files' \
'--pq[native hybrid key]:key file:_files' \
'--pq-only[native ML-KEM-768-only key]:key file:_files' \
'(-D --dedup)'{-D,--dedup}'[block-level deduplication]' \
'(-v --verbose)'{-v,--verbose}'[additional progress output]' \
'(-t --threads)'{-t,--threads}'[thread count]:threads:' \
"${_zupt_disk_legacy_options[@]}" \
'2:archive:_files' \
'3:device or file:_files'
;;
keygen)
_arguments \
'(-o --output)'{-o,--output}'[output key file]:output file:_files' \
'--pub[export a public key]' \
'(-k --key)'{-k,--key}'[source private key]:private key:_files' \
'(--pq-only --pqonly)'{--pq-only,--pqonly}'[native ML-KEM-768-only key format]' \
'(--sdk --pq-sdk)'{--sdk,--pq-sdk}'[optional system libvuptsdk key format]' \
'(--box --pq-box)'{--box,--pq-box}'[optional system libpqvaptvupt key format]'
;;
esac
;;
esac
unset _zupt_password_options _zupt_pq_options _zupt_read_options
unset _zupt_disk_legacy_options

View file

@ -1,160 +0,0 @@
# bash completion for vaptvupt (with `zupt` legacy alias)
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
#
# Install (system-wide):
# sudo install -m 644 completions/vaptvupt.bash /usr/share/bash-completion/completions/vaptvupt
# sudo ln -sf vaptvupt /usr/share/bash-completion/completions/zupt
# or for a single user:
# cp completions/vaptvupt.bash ~/.local/share/bash-completion/completions/vaptvupt
#
# Reload your shell or `source` the file to pick up changes.
_vaptvupt() {
local cur prev words cword
_init_completion -n = 2>/dev/null || {
# _init_completion missing on this host; fall back to manual setup.
local IFS=$' \t\n'
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
cword=$COMP_CWORD
words=("${COMP_WORDS[@]}")
}
local subcommands="compress c extract x list l test t info bench disk keygen version help"
local global_opts="-v --verbose -q --quiet -t --threads -h --help"
# First positional → subcommand
if [ "$cword" -eq 1 ]; then
COMPREPLY=( $(compgen -W "$subcommands" -- "$cur") )
return 0
fi
local subcmd="${words[1]}"
case "$prev" in
-p|--password)
# Don't complete passwords from filesystem
COMPREPLY=()
return 0
;;
-l|--level)
COMPREPLY=( $(compgen -W "1 2 3 4 5 6 7 8 9" -- "$cur") )
return 0
;;
--kdf)
COMPREPLY=( $(compgen -W "argon2id pbkdf2" -- "$cur") )
return 0
;;
-t|--threads)
COMPREPLY=( $(compgen -W "0 1 2 4 8 16 32" -- "$cur") )
return 0
;;
-b|--block)
COMPREPLY=( $(compgen -W "65536 131072 262144 524288 1048576" -- "$cur") )
return 0
;;
-o|--output)
_filedir -d
return 0
;;
--pq|--pq-sdk)
# Key files (no extension constraint)
_filedir
return 0
;;
--comment-file)
_filedir
return 0
;;
-c|--comment)
# Free-form text; no useful completion
COMPREPLY=()
return 0
;;
-k)
_filedir
return 0
;;
esac
case "$subcmd" in
compress|c)
if [[ "$cur" == -* ]]; then
COMPREPLY=( $(compgen -W "
-l --level -b --block -s --store -f --fast
--vv --vaptvupt --lzhp
-p --password --kdf
-c --comment --comment-file
--pq --pq-sdk
--dedup -D --solid
-v --verbose -q --quiet -t --threads
$global_opts
" -- "$cur") )
else
_filedir
fi
;;
extract|x)
if [[ "$cur" == -* ]]; then
COMPREPLY=( $(compgen -W "
-o --output -p --password
--pq --pq-sdk
-v --verbose -t --threads
$global_opts
" -- "$cur") )
else
_filedir 'zupt'
fi
;;
list|l|test|t)
if [[ "$cur" == -* ]]; then
COMPREPLY=( $(compgen -W "
-p --password --pq --pq-sdk
-v --verbose
$global_opts
" -- "$cur") )
else
_filedir 'zupt'
fi
;;
info)
_filedir 'zupt'
;;
disk)
if [ "$cword" -eq 2 ]; then
COMPREPLY=( $(compgen -W "backup restore" -- "$cur") )
elif [[ "$cur" == -* ]]; then
COMPREPLY=( $(compgen -W "
-p --password --pq --pq-sdk
--kdf -c --comment --comment-file
-v --verbose
" -- "$cur") )
else
_filedir
fi
;;
keygen)
if [[ "$cur" == -* ]]; then
COMPREPLY=( $(compgen -W "--sdk --pq-sdk -o --pub -k" -- "$cur") )
else
_filedir
fi
;;
bench)
_filedir
;;
version|help)
COMPREPLY=()
;;
*)
_filedir
;;
esac
return 0
}
complete -F _vaptvupt vaptvupt
# v3.0.0: legacy `zupt` name retained as an alias.
complete -F _vaptvupt zupt

View file

@ -1,112 +0,0 @@
# Fish completions for zupt
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
#
# Install:
# sudo install -m 644 completions/zupt.fish /usr/share/fish/vendor_completions.d/
# or for a single user:
# cp completions/zupt.fish ~/.config/fish/completions/
# ─── Subcommands ───
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'compress c' -d 'Create an archive'
complete -c zupt -f -n '__fish_use_subcommand' -a 'compress c' -d 'Create an archive'
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'extract x' -d 'Extract an archive'
complete -c zupt -f -n '__fish_use_subcommand' -a 'extract x' -d 'Extract an archive'
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'list l' -d 'List archive entries'
complete -c zupt -f -n '__fish_use_subcommand' -a 'list l' -d 'List archive entries'
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'test t' -d 'Verify archive integrity'
complete -c zupt -f -n '__fish_use_subcommand' -a 'test t' -d 'Verify archive integrity'
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'info' -d 'Archive metadata (no key needed)'
complete -c zupt -f -n '__fish_use_subcommand' -a 'info' -d 'Archive metadata (no key needed)'
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'bench' -d 'Benchmark compression levels'
complete -c zupt -f -n '__fish_use_subcommand' -a 'bench' -d 'Benchmark compression levels'
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'disk' -d 'Full-disk backup/restore'
complete -c zupt -f -n '__fish_use_subcommand' -a 'disk' -d 'Full-disk backup/restore'
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'keygen' -d 'Generate a key file'
complete -c zupt -f -n '__fish_use_subcommand' -a 'keygen' -d 'Generate a key file'
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'version' -d 'Print version info'
complete -c zupt -f -n '__fish_use_subcommand' -a 'version' -d 'Print version info'
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'help' -d 'Print help'
complete -c zupt -f -n '__fish_use_subcommand' -a 'help' -d 'Print help'
# Helper predicates
function __fish_zupt_using_subcommand
set -l cmd (commandline -opc)
if test (count $cmd) -gt 1
contains -- $cmd[2] $argv
return $status
end
return 1
end
# ─── Compress options ───
set -l compress_cmds compress c
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s l -l level -d 'Compression level (1-9, default 7)' -x -a '1 2 3 4 5 6 7 8 9'
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s l -l level -d 'Compression level (1-9, default 7)' -x -a '1 2 3 4 5 6 7 8 9'
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s b -l block -d 'Block size in bytes' -x
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s b -l block -d 'Block size in bytes' -x
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s s -l store -d 'Store without compression'
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s s -l store -d 'Store without compression'
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s f -l fast -d 'Use fast LZ codec'
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s f -l fast -d 'Use fast LZ codec'
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l vv -l vaptvupt -d 'Use VaptVupt codec'
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l vv -l vaptvupt -d 'Use VaptVupt codec'
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l lzhp -d 'Use Zupt-LZHP codec (no SIMD needed)'
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l lzhp -d 'Use Zupt-LZHP codec (no SIMD needed)'
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s p -l password -d 'Encrypt with password (prompted if empty)' -x
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s p -l password -d 'Encrypt with password (prompted if empty)' -x
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l kdf -d 'Password KDF' -x -a 'argon2id pbkdf2'
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l kdf -d 'Password KDF' -x -a 'argon2id pbkdf2'
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s c -l comment -d 'Embed archive comment (UTF-8, ≤4096 B)' -x
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s c -l comment -d 'Embed archive comment (UTF-8, ≤4096 B)' -x
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l comment-file -d 'Read comment from file' -r
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l comment-file -d 'Read comment from file' -r
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l pq -d 'Legacy PQ public key' -r
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l pq -d 'Legacy PQ public key' -r
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l pq-sdk -d 'PQ public key (libzuptsdk)' -r
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l pq-sdk -d 'PQ public key (libzuptsdk)' -r
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s D -l dedup -d 'Block-level deduplication'
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s D -l dedup -d 'Block-level deduplication'
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l solid -d 'Solid mode (single stream)'
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l solid -d 'Solid mode (single stream)'
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s v -l verbose -d 'Verbose output'
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s v -l verbose -d 'Verbose output'
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s q -l quiet -d 'Suppress non-error output'
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s q -l quiet -d 'Suppress non-error output'
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s t -l threads -d 'Thread count (0=auto)' -x
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s t -l threads -d 'Thread count (0=auto)' -x
# ─── Extract / List / Test options ───
set -l rw_cmds extract x list l test t
complete -c vaptvupt -n "__fish_zupt_using_subcommand $rw_cmds" -s o -l output -d 'Output directory' -x -a '(__fish_complete_directories)'
complete -c zupt -n "__fish_zupt_using_subcommand $rw_cmds" -s o -l output -d 'Output directory' -x -a '(__fish_complete_directories)'
complete -c vaptvupt -n "__fish_zupt_using_subcommand $rw_cmds" -s p -l password -d 'Decryption password' -x
complete -c zupt -n "__fish_zupt_using_subcommand $rw_cmds" -s p -l password -d 'Decryption password' -x
complete -c vaptvupt -n "__fish_zupt_using_subcommand $rw_cmds" -l pq -d 'Legacy PQ private key' -r
complete -c zupt -n "__fish_zupt_using_subcommand $rw_cmds" -l pq -d 'Legacy PQ private key' -r
complete -c vaptvupt -n "__fish_zupt_using_subcommand $rw_cmds" -l pq-sdk -d 'PQ private key (libzuptsdk)' -r
complete -c zupt -n "__fish_zupt_using_subcommand $rw_cmds" -l pq-sdk -d 'PQ private key (libzuptsdk)' -r
complete -c vaptvupt -n "__fish_zupt_using_subcommand $rw_cmds" -s v -l verbose -d 'Verbose output (surfaces top-MAC/SDK details on failure)'
complete -c zupt -n "__fish_zupt_using_subcommand $rw_cmds" -s v -l verbose -d 'Verbose output (surfaces top-MAC/SDK details on failure)'
complete -c vaptvupt -n "__fish_zupt_using_subcommand $rw_cmds" -s t -l threads -d 'Thread count' -x
complete -c zupt -n "__fish_zupt_using_subcommand $rw_cmds" -s t -l threads -d 'Thread count' -x
# ─── Disk subcommand ───
complete -c vaptvupt -f -n "__fish_zupt_using_subcommand disk; and not __fish_seen_subcommand_from backup restore" \
complete -c zupt -f -n "__fish_zupt_using_subcommand disk; and not __fish_seen_subcommand_from backup restore" \
-a 'backup' -d 'Read a block device into an archive'
complete -c vaptvupt -f -n "__fish_zupt_using_subcommand disk; and not __fish_seen_subcommand_from backup restore" \
complete -c zupt -f -n "__fish_zupt_using_subcommand disk; and not __fish_seen_subcommand_from backup restore" \
-a 'restore' -d 'Write an archive to a block device'
# ─── Keygen options ───
complete -c vaptvupt -n '__fish_zupt_using_subcommand keygen' -l sdk -d 'Generate SDK v2 keypair'
complete -c zupt -n '__fish_zupt_using_subcommand keygen' -l sdk -d 'Generate SDK v2 keypair'
complete -c vaptvupt -n '__fish_zupt_using_subcommand keygen' -l pq-sdk -d 'Same as --sdk'
complete -c zupt -n '__fish_zupt_using_subcommand keygen' -l pq-sdk -d 'Same as --sdk'
complete -c vaptvupt -n '__fish_zupt_using_subcommand keygen' -s o -d 'Output keyfile path' -r
complete -c zupt -n '__fish_zupt_using_subcommand keygen' -s o -d 'Output keyfile path' -r
complete -c vaptvupt -n '__fish_zupt_using_subcommand keygen' -l pub -d 'Export public key from -k'
complete -c zupt -n '__fish_zupt_using_subcommand keygen' -l pub -d 'Export public key from -k'
complete -c vaptvupt -n '__fish_zupt_using_subcommand keygen' -s k -d 'Source private keyfile' -r
complete -c zupt -n '__fish_zupt_using_subcommand keygen' -s k -d 'Source private keyfile' -r

112
completions/zupt.bash Normal file
View file

@ -0,0 +1,112 @@
# bash completion for ZUPT
# SPDX-License-Identifier: AGPL-3.0-or-later
_zupt()
{
local cur prev command disk_command
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD-1]}
command=${COMP_WORDS[1]-}
disk_command=${COMP_WORDS[2]-}
case $prev in
-l|--level)
COMPREPLY=( $(compgen -W '1 2 3 4 5 6 7 8 9' -- "$cur") )
return
;;
-b|--block|-t|--threads|--pass-fd|-p|--password|-c|--comment)
return
;;
--kdf)
COMPREPLY=( $(compgen -W 'pbkdf2 argon2id' -- "$cur") )
return
;;
-o|--output|-k|--key|--pass-file|--comment-file|--pq|--pq-only|--pq-sdk|--pq-box)
COMPREPLY=( $(compgen -f -- "$cur") )
return
;;
esac
if (( COMP_CWORD == 1 )); then
COMPREPLY=( $(compgen -W \
'compress c extract x list l test t info i bench b disk keygen version help --version -V --help -h' \
-- "$cur") )
return
fi
local password_options='-p --password --password-prompt --pass-file --pass-fd'
local pq_options='--pq --pq-only --pq-sdk --pq-box'
local legacy_read_option='--allow-legacy-no-ait'
local common_read_options="-v --verbose $password_options $pq_options $legacy_read_option"
case $command in
compress|c)
if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W \
"-l --level -b --block -s --store -f --fast --vv --vaptvupt --lzhp
$password_options --kdf -c --comment --comment-file $pq_options
-D --dedup -S --solid -y --force -v --verbose -t --threads" \
-- "$cur") )
else
COMPREPLY=( $(compgen -f -- "$cur") )
fi
;;
extract|x)
if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W \
"-o --output $common_read_options -t --threads" -- "$cur") )
else
COMPREPLY=( $(compgen -f -X '!*.zupt' -- "$cur") )
fi
;;
list|l|test|t)
if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W "$common_read_options" -- "$cur") )
else
COMPREPLY=( $(compgen -f -X '!*.zupt' -- "$cur") )
fi
;;
info|i)
COMPREPLY=( $(compgen -f -X '!*.zupt' -- "$cur") )
;;
bench|b)
if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W '--compare' -- "$cur") )
else
COMPREPLY=( $(compgen -f -- "$cur") )
fi
;;
disk)
if (( COMP_CWORD == 2 )); then
COMPREPLY=( $(compgen -W 'backup restore' -- "$cur") )
elif [[ $cur == -* ]]; then
local disk_options
disk_options="-l --level -b --block -s --store --vv --vaptvupt --lzhp
$password_options --kdf -c --comment --comment-file
--pq --pq-only -D --dedup -v --verbose -t --threads"
if [[ $disk_command == restore ]]; then
disk_options+=" $legacy_read_option"
fi
COMPREPLY=( $(compgen -W \
"$disk_options" \
-- "$cur") )
elif [[ $disk_command == restore ]]; then
COMPREPLY=( $(compgen -f -X '!*.zupt' -- "$cur") )
else
COMPREPLY=( $(compgen -f -- "$cur") )
fi
;;
keygen)
if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W \
'-o --output --pub -k --key --pq-only --pqonly --sdk --pq-sdk --box --pq-box' \
-- "$cur") )
else
COMPREPLY=( $(compgen -f -- "$cur") )
fi
;;
esac
}
complete -F _zupt zupt

163
completions/zupt.fish Normal file
View file

@ -0,0 +1,163 @@
# fish completion for ZUPT
# SPDX-License-Identifier: AGPL-3.0-or-later
function __fish_zupt_needs_command
set -l tokens (commandline -opc)
test (count $tokens) -eq 1
end
function __fish_zupt_using_command
set -l tokens (commandline -opc)
test (count $tokens) -gt 1; and contains -- $tokens[2] $argv
end
function __fish_zupt_disk_needs_command
set -l tokens (commandline -opc)
test (count $tokens) -eq 2; and test "$tokens[2]" = disk
end
function __fish_zupt_disk_using_command
set -l tokens (commandline -opc)
test (count $tokens) -gt 2; and test "$tokens[2]" = disk; and contains -- $tokens[3] $argv
end
complete -c zupt -f -n __fish_zupt_needs_command \
-a 'compress c' -d 'Create an archive'
complete -c zupt -f -n __fish_zupt_needs_command \
-a 'extract x' -d 'Extract an archive'
complete -c zupt -f -n __fish_zupt_needs_command \
-a 'list l' -d 'List archive entries'
complete -c zupt -f -n __fish_zupt_needs_command \
-a 'test t' -d 'Verify archive integrity'
complete -c zupt -f -n __fish_zupt_needs_command \
-a 'info i' -d 'Show archive framing metadata'
complete -c zupt -f -n __fish_zupt_needs_command \
-a 'bench b' -d 'Benchmark compression levels'
complete -c zupt -f -n __fish_zupt_needs_command \
-a disk -d 'Back up or restore a disk image'
complete -c zupt -f -n __fish_zupt_needs_command \
-a keygen -d 'Generate or export a recipient key'
complete -c zupt -f -n __fish_zupt_needs_command \
-a version -d 'Show version and build information'
complete -c zupt -f -n __fish_zupt_needs_command \
-a help -d 'Show command help'
complete -c zupt -f -n __fish_zupt_needs_command \
-a '--version -V' -d 'Show version and build information'
complete -c zupt -f -n __fish_zupt_needs_command \
-a '--help -h' -d 'Show command help'
set -l compress_condition '__fish_zupt_using_command compress c'
complete -c zupt -n "$compress_condition" -s l -l level \
-d 'Compression level' -x -a '1 2 3 4 5 6 7 8 9'
complete -c zupt -n "$compress_condition" -s b -l block \
-d 'Block size in bytes' -x
complete -c zupt -n "$compress_condition" -s s -l store \
-d 'Store without compression'
complete -c zupt -n "$compress_condition" -s f -l fast \
-d 'Use fast LZ codec'
complete -c zupt -n "$compress_condition" -l vv -l vaptvupt \
-d 'Force VaptVupt LZ + ANS codec'
complete -c zupt -n "$compress_condition" -l lzhp \
-d 'Force portable LZHP codec'
complete -c zupt -n "$compress_condition" -l kdf \
-d 'Password KDF' -x -a 'pbkdf2 argon2id'
complete -c zupt -n "$compress_condition" -s c -l comment \
-d 'Store an archive comment' -x
complete -c zupt -n "$compress_condition" -l comment-file \
-d 'Read archive comment from file' -r
complete -c zupt -n "$compress_condition" -s D -l dedup \
-d 'Enable block-level deduplication'
complete -c zupt -n "$compress_condition" -s S -l solid \
-d 'Use a solid single compression stream'
complete -c zupt -n "$compress_condition" -s y -l force \
-d 'Overwrite an existing non-.zupt output'
complete -c zupt -n "$compress_condition" -s t -l threads \
-d 'Compression thread count' -x
set -l read_condition '__fish_zupt_using_command compress c extract x list l test t'
complete -c zupt -n "$read_condition" -s p -l password \
-d 'Password in process arguments' -x
complete -c zupt -n "$read_condition" -l password-prompt \
-d 'Read password interactively without echo'
complete -c zupt -n "$read_condition" -l pass-file \
-d 'Read password from first line of file' -r
complete -c zupt -n "$read_condition" -l pass-fd \
-d 'Read password from inherited file descriptor' -x
complete -c zupt -n "$read_condition" -l pq \
-d 'Native ML-KEM-768 + X25519 hybrid key' -r
complete -c zupt -n "$read_condition" -l pq-only \
-d 'Native ML-KEM-768-only key' -r
complete -c zupt -n "$read_condition" -l pq-sdk \
-d 'Optional system libvuptsdk key' -r
complete -c zupt -n "$read_condition" -l pq-box \
-d 'Optional system libpqvaptvupt key' -r
complete -c zupt -n "$read_condition" -s v -l verbose \
-d 'Additional progress or diagnostic output'
set -l legacy_read_condition '__fish_zupt_using_command extract x list l test t'
complete -c zupt -n "$legacy_read_condition" -l allow-legacy-no-ait \
-d 'Recover a trusted old archive without an integrity trailer'
complete -c zupt -n '__fish_zupt_using_command extract x' \
-s o -l output -d 'Output directory' -r
complete -c zupt -n '__fish_zupt_using_command extract x' \
-s t -l threads -d 'Decompression thread count' -x
complete -c zupt -n '__fish_zupt_using_command bench b' \
-l compare -d 'Compare available external compressors'
complete -c zupt -f -n __fish_zupt_disk_needs_command \
-a backup -d 'Create a disk-image archive'
complete -c zupt -f -n __fish_zupt_disk_needs_command \
-a restore -d 'Restore a disk-image archive'
set -l disk_condition '__fish_zupt_using_command disk'
complete -c zupt -n "$disk_condition" -s l -l level \
-d 'Compression level' -x -a '1 2 3 4 5 6 7 8 9'
complete -c zupt -n "$disk_condition" -s b -l block \
-d 'Block size in bytes' -x
complete -c zupt -n "$disk_condition" -s s -l store \
-d 'Store without compression'
complete -c zupt -n "$disk_condition" -l vv -l vaptvupt \
-d 'Force VaptVupt LZ + ANS codec'
complete -c zupt -n "$disk_condition" -l lzhp \
-d 'Force portable LZHP codec'
complete -c zupt -n "$disk_condition" -s p -l password \
-d 'Password in process arguments' -x
complete -c zupt -n "$disk_condition" -l password-prompt \
-d 'Read password interactively without echo'
complete -c zupt -n "$disk_condition" -l pass-file \
-d 'Read password from first line of file' -r
complete -c zupt -n "$disk_condition" -l pass-fd \
-d 'Read password from inherited file descriptor' -x
complete -c zupt -n "$disk_condition" -l kdf \
-d 'Password KDF' -x -a 'pbkdf2 argon2id'
complete -c zupt -n "$disk_condition" -s c -l comment \
-d 'Store an archive comment' -x
complete -c zupt -n "$disk_condition" -l comment-file \
-d 'Read archive comment from file' -r
complete -c zupt -n "$disk_condition" -l pq \
-d 'Native ML-KEM-768 + X25519 hybrid key' -r
complete -c zupt -n "$disk_condition" -l pq-only \
-d 'Native ML-KEM-768-only key' -r
complete -c zupt -n "$disk_condition" -s D -l dedup \
-d 'Enable block-level deduplication'
complete -c zupt -n "$disk_condition" -s v -l verbose \
-d 'Additional progress or diagnostic output'
complete -c zupt -n "$disk_condition" -s t -l threads \
-d 'Thread count' -x
complete -c zupt -n '__fish_zupt_disk_using_command restore' \
-l allow-legacy-no-ait \
-d 'Recover a trusted old disk archive without an integrity trailer'
set -l keygen_condition '__fish_zupt_using_command keygen'
complete -c zupt -n "$keygen_condition" -s o -l output \
-d 'Output key file' -r
complete -c zupt -n "$keygen_condition" -l pub \
-d 'Export a public key'
complete -c zupt -n "$keygen_condition" -s k -l key \
-d 'Source private key' -r
complete -c zupt -n "$keygen_condition" -l pq-only -l pqonly \
-d 'Native ML-KEM-768-only key format'
complete -c zupt -n "$keygen_condition" -l sdk -l pq-sdk \
-d 'Optional system libvuptsdk key format'
complete -c zupt -n "$keygen_condition" -l box -l pq-box \
-d 'Optional system libpqvaptvupt key format'

View file

@ -1,127 +0,0 @@
.\" Manpage for vaptvupt-gui (formerly zupt-gui; INPI Brasil trademark rename in v3.0.0)
.\" SPDX-License-Identifier: AGPL-3.0-or-later
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
.TH VAPTVUPT-GUI 1 "2026-06-11" "vaptvupt-gui 1.3.0" "User Commands"
.SH NAME
vaptvupt-gui \- graphical interface for the VaptVupt post-quantum backup utility
.SH SYNOPSIS
.B vaptvupt-gui
.RI [ ARCHIVE ]
.SH DESCRIPTION
.B vaptvupt-gui
is a graphical frontend for
.BR vaptvupt (1).
It provides tabs for compression, extraction, key management, and
full-disk backup. Both PQ encryption modes are exposed:
.B legacy --pq
and
.B SDK v2 --pq-sdk
(HKDF combiner, key commitment, HPKE binding, Argon2id).
The legacy command name
.B zupt-gui
is preserved as a symlink for backward compatibility; both invocations
behave identically.
If
.I ARCHIVE
is given on the command line, the GUI opens directly on the
extract tab with that archive preloaded.
.B vaptvupt-gui
uses Qt 6. It works with either of the following Python Qt bindings,
auto-detected at startup in this order:
.IP \(bu 2
PySide6 (Qt for Python)
.IP \(bu 2
PyQt6
.PP
If neither is installed, the GUI prints an instructive error and exits.
.SH TABS
.TP
.B Compress
Select files or directories, choose codec, level, password and/or PQ
key. The
.B Mode
panel controls whether the SDK v2 path or the legacy path is used.
.TP
.B Extract
Open a .zupt archive, select output directory, provide password
and/or PQ private key.
.TP
.B Keygen
Generate ML-KEM-768 + X25519 keypair. The
.B SDK v2 format
checkbox controls whether the keypair is generated via
.B vaptvupt keygen --sdk
(producing
.IR file
and
.IR file.pub
in one step) or via the legacy
.BR "vaptvupt keygen" .
.TP
.B Disk
Full-disk backup and restore. Enumerates block devices with
human-readable sizes. Same encryption mode controls as Compress.
.SH FILES
.TP
.I /usr/bin/vaptvupt-gui
Wrapper script that invokes the Python entry point (and the symlinked
legacy
.IR /usr/bin/zupt-gui ).
.TP
.I /usr/lib/vaptvupt-gui/zupt_gui.py
Main Python source.
.TP
.I /usr/share/applications/vaptvupt-gui.desktop
Desktop entry for menu integration.
.TP
.I /usr/share/icons/hicolor/256x256/apps/vaptvupt-gui.png
Application icon.
.SH ENVIRONMENT
.TP
.B VAPTVUPT_BIN
Override the path to the
.B vaptvupt
binary (default: search
.IR PATH ).
The legacy name
.B ZUPT_BIN
is also honoured.
.TP
.B VAPTVUPT_DEBUG
Enable binary-discovery debug logging on stderr. The legacy name
.B ZUPT_DEBUG
is also honoured.
.SH BUGS
Report at
.UR https://git.securityops.co/cristiancmoises/vaptvupt/issues
.UE .
.SH AUTHOR
Cristian Cezar Moisés
.MT zupt@riseup.net
.ME
.SH SEE ALSO
.BR vaptvupt (1).
.SH LICENSE
.PP
vaptvupt-gui is licensed under the
.B GNU Affero General Public License version 3 or later
(AGPL-3.0-or-later). Commercial license available for relief from
copyleft terms; contact
.MT sac@securityops.co
.ME .
.SH PROJECT
.PP
Home page:
.UR https://git.securityops.co/cristiancmoises/vaptvupt
.UE

View file

@ -1,618 +0,0 @@
.\" Manpage for vaptvupt (formerly zupt; INPI Brasil trademark rename in v3.0.0)
.\" SPDX-License-Identifier: AGPL-3.0-or-later
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
.TH VAPTVUPT 1 "July 2026" "vaptvupt 4.1.0" "User Commands"
.SH NAME
vaptvupt \- post-quantum backup compression utility (formerly zupt)
.SH SYNOPSIS
.B vaptvupt compress
.RI [ options ]
.I out.zupt
.I files...
.br
.B vaptvupt extract
.RI [ options ]
.I archive.zupt
.br
.B vaptvupt list
.RI [ options ]
.I archive.zupt
.br
.B vaptvupt test
.RI [ options ]
.I archive.zupt
.br
.B vaptvupt info
.I archive.zupt
.br
.B vaptvupt bench
.I files/dirs...
.br
.B vaptvupt disk backup
.RI [ options ]
.I out.zupt
.I device
.br
.B vaptvupt disk restore
.RI [ options ]
.I archive.zupt
.I device
.br
.B vaptvupt keygen
.RI [ options ]
.br
.B vaptvupt version
.br
.B vaptvupt help
.PP
The legacy command name
.B zupt
is preserved as an alias for backward compatibility; both invocations
behave identically.
.SH DESCRIPTION
.B vaptvupt
compresses and encrypts files, directories, and whole block devices
into self-contained, authenticated archives with the
.B .zupt
extension. It targets long-lived backup storage where:
.RS
.IP \(bu 2
the archive is written once and restored under time pressure many years later;
.IP \(bu 2
the encryption envelope must remain secure against a future cryptographically-relevant quantum computer (ML-KEM-768);
.IP \(bu 2
every byte of the archive — header, footer, per-block metadata, comments — is authenticated, and a single bit-flip is rejected at restore time.
.RE
.PP
The compression layer is the
.B VaptVupt LZ + ANS
codec (version 2.53.3), which prioritises decode speed and ratio over
encode speed. Aggregate decode throughput on this build is 1.27\(mu
zstd\-3; encode throughput is 0.2\(mu\(en0.5\(mu zstd\-3 depending on
content. See
.B PERFORMANCE
below.
.PP
The on-disk format is v1.6 and has been wire-compatible since release
v2.3.1. The product was renamed from
.B Zupt
to
.B VaptVupt
in v3.0.0 because of a prior INPI Brasil trademark registration of the
name "Zupt" for unrelated software. The
.B .zupt
file extension and the
.B ZUPT
header magic bytes are unchanged: archives produced by any v2.x release
extract cleanly under v3.0.0 and vice versa.
.SH COMMANDS
.TP
.B compress
Create an archive. Default codec is VaptVupt (level 7). Compression
is multi-threaded; one worker per detected CPU by default.
.TP
.B extract
Decompress an archive into the current directory (or
.BR -o " " \fIdir\fR ).
Refuses to write outside the destination directory (path-traversal
defence). Files are written with their original permissions and
mtime preserved.
.TP
.B list
Print archive metadata: per-file path, size, mtime, mode, compressed
size, codec. With
.B --verbose
also prints per-block sizes and HMAC tags (first 8 bytes).
.TP
.B test
Decompress all blocks in memory and verify HMAC tags + archive
integrity trailer. Does not write any files. Use to validate an
archive without restoring it. Exit code is non-zero on any failure.
.TP
.B info
Print archive header metadata without requiring the decryption key.
Reports: format version, codec, encryption type (none / PBKDF2 /
Argon2id / ML-KEM-768+X25519), KDF iteration count, file count,
creation timestamp, archive UUID, AIT presence. Safe to run on an
untrusted archive.
.TP
.B bench
Compare compression levels 1\(en9 on the supplied files; reports
ratio and encode/decode throughput per level. Useful when picking
the right
.B -l
for a given workload.
.TP
.B disk backup
Read a block device and write a sparse-aware archive. Detects
all-zero regions and records them as runs rather than compressing
them.
.TP
.B disk restore
Inverse of
.BR "disk backup" .
Writes the archive's contents back to a block device. Verifies
target device size before writing; refuses if the target is smaller
than the archived size. With
.B --sync
issues
.BR fsync (2)
after each block.
.TP
.B keygen
Generate a key file for keyfile-mode encryption. With
.B --sdk
generates an ML-KEM-768 + X25519 hybrid keypair suitable for
.B --pq-sdk
mode. With
.B --box
generates a libpqvaptvupt sealed-box keypair (writes \fIfile\fR and
\fIfile\fR.pub) for
.B --pq-box
mode. With
.B --pub
extracts the public key from an existing private key.
.SH GLOBAL OPTIONS
.TP
.BR -v ", " --verbose
Print per-file and per-block details during compress/extract/list/test.
.TP
.BR -q ", " --quiet
Suppress non-error output.
.TP
.BR -j " " \fIN\fR ", " --jobs " " \fIN\fR
Worker thread count for parallel compression. Default: number of
online CPUs.
.SH COMPRESS OPTIONS
.TP
.BR -l " " \fI1..9\fR ", " --level " " \fI1..9\fR
Compression level. 1\(en2 = ultra-fast (~80 MB/s encode on typical
hardware, lower ratio). 3\(en7 = balanced (default 7). 8\(en9 =
extreme (optimal parsing, ~5\(en10\(mu slower encode, best ratio).
.TP
.B --codec \fIid\fR
Force a specific codec by id. Accepted values:
.BR store " (0x0000), "
.BR vaptvupt-lz " (0x0008), "
.BR vaptvupt-lzh " (0x0009), "
.BR vaptvupt-lzhp " (0x000A), "
.BR vaptvupt " (0x0010 — default), "
.BR auto " (0xFFFF — pick at runtime)."
.TP
.BR -p " " \fIpassword\fR
Enable password-based encryption (PBKDF2-SHA256 KDF; Argon2id is available
only in a WITH_SDK=1 build via \fB--kdf argon2id\fR).
Reading the password from a flag exposes it in
.BR ps (1)
output; prefer
.B --pass-file
or interactive prompt.
.TP
.B --pass-file \fIpath\fR
Read password from the first line of the file. The file's permission
bits should be 0600.
.TP
.B --pass-fd \fIN\fR
Read password from file descriptor N.
.TP
.B --kdf \fIalgo\fR
Choose key-derivation function for password mode:
.BR argon2id " (default since v2.4.1; memory-hard) or "
.BR pbkdf2 " (SHA-256, 600 000 iter; needed for compatibility with v2.4.0 and earlier readers)."
.TP
.B --keyfile \fIpath\fR
Use a 32-byte raw key file (generated with
.BR "vaptvupt keygen" ).
.TP
.B --pq-box \fIpub\fR
Enable post-quantum sealed-box encryption via the vendored libpqvaptvupt
(v4.0.0+, envelope type 0x05). ML-KEM-768 + X25519 shared secrets are
combined through HKDF-SHA256 with a domain-separating info string
("pqvv-seal-v1"); the box carries AES-256-CTR + HMAC-SHA256
Encrypt-then-MAC. The
.I pub
argument is the recipient's public-key file from
.B keygen --box
(magic-tagged; public and secret key files are not interchangeable).
On extraction, pass the secret key:
.B --pq-box
\fIpriv\fR.
.TP
.B --pq-sdk \fIpub\fR
Enable post-quantum hybrid encryption. Uses ML-KEM-768 + X25519 with
HKDF combiner, HPKE binding, and key commitment. The
.I pub
argument is the recipient's public-key file generated by
.BR "vaptvupt keygen --sdk" .
.TP
.B -c \fItext\fR ", " --comment " " \fItext\fR
Embed an encrypted UTF-8 comment in the archive (up to 4096 bytes).
The comment is bound to the archive's frame-preface AAD; tampering
is detected at extract time.
.TP
.B --comment-file \fIpath\fR
Read the comment from a file rather than the command line.
.TP
.B -b \fIsize\fR ", " --block-size " " \fIsize\fR
Compression block size. Default 4 MiB. Smaller blocks improve
random-access decode but lose some ratio.
.SH EXTRACT, LIST, TEST OPTIONS
.TP
.BR -o " " \fIdir\fR ", " --output " " \fIdir\fR
Extract into
.IR dir
(created if it doesn't exist). Default: current directory.
.TP
.B --no-mtime
Do not restore archived modification times; use current time instead.
.TP
.B --strip-components \fIN\fR
Strip
.I N
leading path components from each entry, like
.BR tar 's
flag of the same name.
.SH POST-QUANTUM ENCRYPTION
.B vaptvupt
implements a hybrid KEM as specified in FIPS 203 (ML-KEM) combined
with X25519 (RFC 7748). The session key is derived as:
.RS
.nf
ss_pq = ML-KEM-768.decaps(sk_pq, ct_pq)
ss_ec = X25519(sk_ec, pk_ec_peer)
session = HKDF-SHA256(ss_pq || ss_ec,
info = "vaptvupt-pq-sdk-v1",
salt = archive_uuid)
.fi
.RE
The hybrid combiner means the session key is at least as strong as
the strongest of {ML-KEM-768, X25519}: an attacker must break both
to recover the key.
.PP
.B Key commitment:
the ciphertext is bound to the exact public key it was encrypted to
via an HPKE-style derivation. An attacker cannot present a different
public key that decrypts to the same plaintext (this defeats the
"partitioning" attack class).
.PP
.B Implementation notes:
the ML-KEM-768 implementation is vendored from a clean reference and
verified against the FIPS 203 KAT vectors. The X25519 implementation
uses 4\(mu64-bit field arithmetic with Jasmin-verified constant-time
field operations on x86_64. On other architectures the same routines
run in pure C, also constant-time by construction.
.SH SECURITY
.SS Threat model
What
.B vaptvupt
.B protects against:
.RS
.IP \(bu 2
Confidentiality of archived data at rest (AES-256-CTR with HMAC-SHA256 EtM, or AEAD via libzuptsdk on the
.B --pq-sdk
path).
.IP \(bu 2
End-to-end byte-level tamper detection on encrypted archives. The F-09 byte-sweep regression (1827 positions on a representative archive, 2000 trials, every run) shows zero silent-accept positions.
.IP \(bu 2
Wrong-password and tampered-archive indistinguishability at the user-visible message layer (F-11). The default error wording is identical for both cases; only
.B --verbose
prints the distinguishing detail. This closes the "verbal probe-oracle" attack class where the error string leaked which check failed first.
.IP \(bu 2
Post-quantum forward secrecy on archives encrypted with
.B --pq-sdk
(assuming ML-KEM-768 holds against future quantum attack).
.IP \(bu 2
Archive-header and footer authentication via a 32-byte HMAC-SHA256 trailer (F-08). Tampering with the file count, comment offset, or timestamp is detected at archive open time.
.IP \(bu 2
Path-traversal at extract time. Entries with absolute paths or
.B ..
components are refused or stripped.
.RE
What it does
.B NOT
protect against:
.RS
.IP \(bu 2
Compromise of the endpoint that creates or restores the archive. If the host is compromised, the password, key file, or plaintext is accessible.
.IP \(bu 2
Compromise of the key file or password. Key custody is the user's responsibility.
.IP \(bu 2
A weak password. Argon2id with default parameters needs ~256 MiB and ~1 s to derive a key on commodity hardware; a 4-character password is still trivially crackable.
.IP \(bu 2
Metadata leakage. File names, sizes, and modification times are encrypted, but the archive's total size and the count of compressed blocks are visible to an observer.
.IP \(bu 2
Side channels on the compression layer (CRIME/BREACH-style). If the same archive contains both attacker-controlled and secret data and the attacker can observe the compressed size, length-based oracles may be possible.
.IP \(bu 2
Denial-of-service via malformed input on the decoder. The decoder rejects malformed input cleanly (no crashes in the fuzz harness), but a very large compressed input can still consume CPU and memory proportional to its size.
.RE
.SS Cryptographic primitives
.TS
tab(|);
l l.
SHA-256 | FIPS 180-4
SHA-3 / SHAKE | FIPS 202
ML-KEM-768 | FIPS 203
AES-256-CTR | NIST SP 800-38A
HMAC-SHA256 | RFC 2104 / FIPS 198-1
X25519 | RFC 7748
HKDF-SHA256 | RFC 5869
PBKDF2-SHA256 | RFC 8018
Argon2id | RFC 9106
XXH64 | non-cryptographic; used only inside the AEAD envelope
.TE
.SS Constant-time guarantees
All secret-dependent comparisons and table lookups in the cryptographic
core are constant-time. On x86_64 the hot paths (HMAC equality compare,
ML-KEM Fujisaki-Okamoto implicit rejection) are implemented in Jasmin
and assembled with
.BR jasminc (1).
On other architectures the same routines run in portable C; the
constant-time property is preserved by source-level construction.
.SH FILES
.TP
.I ~/.config/vaptvupt/
Per-user configuration directory (reserved; not used in v3.0.0).
.TP
.I /etc/vaptvupt/
System-wide configuration directory (reserved; not used in v3.0.0).
.TP
.I /usr/share/bash-completion/completions/vaptvupt
Bash completion (and the symlinked legacy
.IR /usr/share/bash-completion/completions/zupt ).
.TP
.I /usr/share/zsh/site-functions/_vaptvupt
zsh completion.
.TP
.I /usr/share/fish/vendor_completions.d/vaptvupt.fish
fish completion.
.SH ENVIRONMENT
.TP
.B VAPTVUPT_BIN
Override the binary path used by the GUI front-end. Legacy
.B ZUPT_BIN
is also honoured.
.TP
.B VAPTVUPT_DEBUG
If set to any non-empty value, the GUI front-end prints its binary-discovery log to stderr.
.SH EXIT STATUS
.TP
.B 0
Success.
.TP
.B 1
General error (bad arguments, file not found, etc.).
.TP
.B 2
Authentication failed. Wrong password, wrong key file, or the archive has been tampered with. Use
.B --verbose
to see the distinguishing detail (subject to F-11's threat model: detailed messages may leak which failure cause fired first).
.TP
.B 3
Archive-format error (wrong magic bytes, unsupported format version, corrupted header).
.TP
.B 4
I/O error (disk full, permission denied, network failure).
.TP
.B 5
Compressed-data integrity error (per-block HMAC mismatch detected mid-stream).
.SH PERFORMANCE
Numbers below are from the v3.0.0 release benchmark (May 2026), run
on an Intel Xeon @ 2.8 GHz with the codec compiled with the
distribution's default optimisation level.
.TS
tab(|);
l l l l l.
\fBFixture\fR | \fBTool\fR | \fBRatio\fR | \fBEnc MB/s\fR | \fBDec MB/s\fR
text 8 MB | vv-9 | 34.6% | 5.4 | 219
text 8 MB | gzip-9 | 30.9% | 6.2 | 137
text 8 MB | zstd-3 | 31.6% | 137 | 427
text 8 MB | zstd-19 | 25.5% | 1.6 | 384
source 670 KB | vv-9 | 25.1% | 11.4 | 128
source 670 KB | gzip-9 | 23.2% | 10.0 | 107
source 670 KB | zstd-3 | 24.1% | 91 | 160
binary 2.4 MB | vv-9 | 44.7% | 7.7 | 153
binary 2.4 MB | gzip-9 | 52.0% | 12.6 | 109
binary 2.4 MB | zstd-3 | 77.3% | 164 | 382
binary 2.4 MB | zstd-19 | 48.0% | 5.8 | 229
random 5 MB | vv-9 | 100.0% | 11.1 | 477
random 5 MB | zstd-3 | 100.0% | 397 | 681
.TE
.PP
Honest reading of these numbers:
.RS
.IP \(bu 2
On binary-structured data (game saves, mmap'd structures, struct arrays),
.B vaptvupt
beats zstd-3 by a wide margin on ratio (44.7% vs 77.3%) at the cost of being ~20\(mu slower to encode. For write-once / restore-often workloads this is the right trade.
.IP \(bu 2
On text and source, zstd-19 beats
.B vaptvupt
on ratio. The fundamental codec difference is that zstd's reference encoder has had years of compiler-engineering attention that
.B vaptvupt
has not.
.IP \(bu 2
Encode throughput is
.BR vaptvupt 's
weak axis. If encode latency matters more than ratio, use
.B -l 1
or
.BR -l 2 .
.IP \(bu 2
On random / already-compressed data, all codecs hit the incompressibility wall; comparing encode/decode throughput in that regime is mostly measuring memcpy speed plus framing overhead.
.RE
.SH EXAMPLES
.PP
Compress with default settings (PBKDF2-SHA256 password, VaptVupt level 7, multi-threaded):
.RS
.nf
$ vaptvupt compress -p secret backup.zupt ~/Documents
.fi
.RE
Compress with post-quantum hybrid encryption to a published public key:
.RS
.nf
$ vaptvupt keygen --sdk -o ~/.config/vaptvupt-mykey
$ vaptvupt keygen -o ~/.config/vaptvupt-mykey
$ vaptvupt keygen --pub -o mykey.pub -k ~/.config/vaptvupt-mykey
$ vaptvupt compress --pq mykey.pub backup.zupt ~/Documents
.fi
.RE
Backup a block device, sparse-aware:
.RS
.nf
$ sudo vaptvupt disk backup -p secret system.zupt /dev/nvme0n1p2
.fi
.RE
Verify an archive without restoring:
.RS
.nf
$ vaptvupt test -p secret backup.zupt
.fi
.RE
Print archive metadata without supplying a password:
.RS
.nf
$ vaptvupt info backup.zupt
.fi
.RE
Compare compression levels:
.RS
.nf
$ vaptvupt bench ~/Downloads/big-dataset.bin
.fi
.RE
Run the GUI from a desktop session where /usr/bin isn't on PATH (the bug fixed in v3.0.0):
.RS
.nf
$ VAPTVUPT_DEBUG=1 vaptvupt-gui 2> /tmp/discovery.log
.fi
.RE
.SH STANDARDS
ISO C11; POSIX.1-2017 for I/O and threading. The cryptographic
primitives implement the specifications listed in
.BR SECURITY
above. The on-disk archive format is documented in
.B FORMAT.md
in the source distribution.
.SH AUTHORS
Cristian Cezar Moisés <zupt@riseup.net> — primary author and maintainer.
.SH BUGS
Report bugs at https://git.securityops.co/cristiancmoises/vaptvupt/issues
or by email to <zupt@riseup.net>.
.SH LICENSE
AGPL-3.0-or-later for the application; GPL-3.0-or-later for the
embedded VaptVupt codec. Dual-licensed: a commercial licence is
available from <sac@securityops.co>.
.SH SEE ALSO
.BR vaptvupt-gui (1),
.BR zstd (1),
.BR gzip (1),
.BR xz (1),
.BR tar (1),
.BR cryptsetup (8),
.BR jasminc (1).
.PP
Project home: https://git.securityops.co/cristiancmoises/vaptvupt
.br
Threat model: see
.B THREAT_MODEL.md
in the source distribution.
.br
Archive format spec: see
.B FORMAT.md
in the source distribution.

View file

@ -1,127 +1,158 @@
.\" Manpage for vaptvupt-gui (formerly zupt-gui; INPI Brasil trademark rename in v3.0.0)
.\" SPDX-License-Identifier: AGPL-3.0-or-later
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
.TH VAPTVUPT-GUI 1 "2026-06-11" "vaptvupt-gui 1.3.0" "User Commands"
.TH ZUPT-GUI 1 "2026-08-31" "ZUPT 5.2.8" "User Commands"
.SH NAME
vaptvupt-gui \- graphical interface for the VaptVupt post-quantum backup utility
zupt-gui \- Qt interface for the ZUPT backup utility
.SH SYNOPSIS
.B vaptvupt-gui
.RI [ ARCHIVE ]
.SH DESCRIPTION
.B vaptvupt-gui
is a graphical frontend for
.BR vaptvupt (1).
It provides tabs for compression, extraction, key management, and
full-disk backup. Both PQ encryption modes are exposed:
.B legacy --pq
and
.B SDK v2 --pq-sdk
(HKDF combiner, key commitment, HPKE binding, Argon2id).
The legacy command name
.B zupt-gui
is preserved as a symlink for backward compatibility; both invocations
behave identically.
If
.I ARCHIVE
is given on the command line, the GUI opens directly on the
extract tab with that archive preloaded.
.B vaptvupt-gui
uses Qt 6. It works with either of the following Python Qt bindings,
auto-detected at startup in this order:
.IP \(bu 2
PySide6 (Qt for Python)
.IP \(bu 2
PyQt6
.RI [ ARCHIVE.zupt ]
.br
.B zupt-gui
.BI --compress " FILE ..."
.br
.B zupt-gui
.BI --extract " ARCHIVE.zupt"
.br
.B zupt-gui
.RB [ --help | --version | --selftest ]
.SH DESCRIPTION
.B zupt-gui
is a Python Qt 6 frontend for
.BR zupt (1).
It creates, inspects, verifies, and extracts archives by running the CLI as a
child process. It can also request CLI disk backup and restore operations.
The GUI does not implement an archive codec or cryptography itself.
.PP
If neither is installed, the GUI prints an instructive error and exits.
PySide6 is tried first and PyQt6 is used as a fallback. The selected
.B zupt
command is checked by executing
.BR "zupt version" .
The ZUPT command and environment variables are preferred; renamed-era names
are accepted only for compatibility with an existing installation.
.PP
When paired with the source-only baseline CLI, the frontend uses a build with
.B WITH_SDK=0
and
.BR WITH_PQBOX=0 .
Native password,
.B --pq
(ML-KEM-768 plus X25519), and
.B --pq-only
(ML-KEM-768) modes remain available. The GUI parses the CLI's
.B Build integrations:
line and exposes
.B --pq-sdk
or
.B --pq-box
only when libvuptsdk or libpqvaptvupt is independently reported enabled.
These two optional integrations are detected separately.
.PP
The gated 5.2.8 GUI release set is limited to the architecture-independent DEB,
noarch/source RPM, and source-only portable ZIP named in the project README.
Package gates require exact checks and installed off-screen GUI/CLI integration.
The portable ZIP receives source scans, an exact safe-member allowlist, and an
extracted launcher test; it bundles no Python, Qt, CLI, or compiled runtime.
AppImage, AppDir and Flatpak bundles and Windows/macOS GUI installers are not
promoted; the Windows ZIP and macOS DMG are CLI-only.
.PP
The GUI does not expose the CLI's recovery-only
.B --allow-legacy-no-ait
option. A known, trusted pre-AIT archive must be recovered explicitly with the
CLI; untrusted trailerless archives must remain rejected.
.SH OPTIONS
.TP
.B --compress
Open the Compress tab with the remaining arguments selected as inputs.
.TP
.B --extract
Open the Extract tab with the following archive selected.
.TP
.B --selftest
Create the complete interface, run the event loop briefly, and exit. A display
backend (or a suitable off-screen Qt backend) is still required.
.TP
.BR --version , " -V"
Print the GUI, Qt binding, CLI version, and selected CLI path.
.TP
.BR --help , " -h"
Print command-line usage.
.SH TABS
.TP
.B Keys
Generate and export recipient keys. Mode choices follow CLI capability
detection, including independent SDK and PQ-box choices when enabled.
.TP
.B Compress
Select files or directories, choose codec, level, password and/or PQ
key. The
.B Mode
panel controls whether the SDK v2 path or the legacy path is used.
Choose inputs, destination, codec options, password, and an optional recipient
public key.
.TP
.B Extract
Open a .zupt archive, select output directory, provide password
and/or PQ private key.
Choose an archive, output directory, and any required password or private key.
The GUI uses
.B zupt info
to auto-detect supported archive protection modes. That framing inspection is
unauthenticated and is only a mode-selection hint; the subsequent CLI extract
or test operation performs the required AIT and content validation.
.TP
.B Keygen
Generate ML-KEM-768 + X25519 keypair. The
.B SDK v2 format
checkbox controls whether the keypair is generated via
.B vaptvupt keygen --sdk
(producing
.IR file
and
.IR file.pub
in one step) or via the legacy
.BR "vaptvupt keygen" .
.B Verify
Inspect an archive header or run the CLI integrity test with the detected
credential type.
.TP
.B Disk
Full-disk backup and restore. Enumerates block devices with
human-readable sizes. Same encryption mode controls as Compress.
.SH FILES
Request full-device or image backup and restore through the CLI. Raw devices
may require operating-system privileges. Restore overwrites its selected target
and requires explicit confirmation in the GUI.
.TP
.I /usr/bin/vaptvupt-gui
Wrapper script that invokes the Python entry point (and the symlinked
legacy
.IR /usr/bin/zupt-gui ).
.TP
.I /usr/lib/vaptvupt-gui/zupt_gui.py
Main Python source.
.TP
.I /usr/share/applications/vaptvupt-gui.desktop
Desktop entry for menu integration.
.TP
.I /usr/share/icons/hicolor/256x256/apps/vaptvupt-gui.png
Application icon.
.B About
Show the detected CLI version and build information.
.SH ENVIRONMENT
.TP
.B VAPTVUPT_BIN
Override the path to the
.B vaptvupt
binary (default: search
.IR PATH ).
The legacy name
.B ZUPT_BIN
is also honoured.
Absolute or executable path to the preferred
.B zupt
command. It must pass the CLI version liveness check.
.TP
.B VAPTVUPT_DEBUG
Enable binary-discovery debug logging on stderr. The legacy name
.B ZUPT_DEBUG
is also honoured.
Print command-discovery diagnostics to standard error when non-empty.
.TP
.B ZUPT_NO_XCB_FALLBACK
Disable the guarded XWayland relaunch used when a Wayland window is never
exposed.
.TP
.BR VAPTVUPT_BIN , " VAPTVUPT_DEBUG" , " VAPTVUPT_NO_XCB_FALLBACK"
Renamed-era compatibility aliases for the corresponding ZUPT variables.
New integrations should use the ZUPT names.
.SH FILES
.TP
.I /usr/bin/zupt-gui
Installed launcher.
.TP
.I /usr/lib/zupt-gui/zupt_gui.py
GUI source location used by the DEB or source installer. The noarch RPM may use
an architecture-independent shared-data directory instead.
.TP
.I /usr/share/applications/zupt-gui.desktop
Desktop entry.
.PP
Distribution packages do not install a
.B vaptvupt-gui
alias. The source installer can create one only with its explicit
.B --legacy-alias
option. The optional alias has no separate manual page.
.SH BUGS
Report at
.UR https://git.securityops.co/cristiancmoises/vaptvupt/issues
Report reproducible issues at
.UR https://github.com/cristiancmoises/zupt/issues
the ZUPT issue tracker
.UE .
.SH AUTHOR
Cristian Cezar Moisés
.MT zupt@riseup.net
.ME
.SH SEE ALSO
.BR vaptvupt (1).
.SH LICENSE
.PP
vaptvupt-gui is licensed under the
.B GNU Affero General Public License version 3 or later
(AGPL-3.0-or-later). Commercial license available for relief from
copyleft terms; contact
.MT sac@securityops.co
.ME .
.SH PROJECT
.PP
Home page:
.UR https://git.securityops.co/cristiancmoises/vaptvupt
.UE
The current integrated GUI source carries AGPL-3.0-or-later notices. Published
historical revisions include MIT grants that remain applicable to the exact
material distributed under them. See
.I gui/LICENSE-GUI
and the 5.2.2 licensing erratum in
.I CHANGELOG.md
for scope and repository evidence.
.SH SEE ALSO
.BR zupt (1)

View file

@ -1 +0,0 @@
vaptvupt.1

636
doc/zupt.1 Normal file
View file

@ -0,0 +1,636 @@
.\" SPDX-License-Identifier: AGPL-3.0-or-later
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
.TH ZUPT 1 "2026-08-31" "ZUPT 5.2.8" "User Commands"
.
.SH NAME
zupt \- source-built backup compression and authenticated-encryption utility
.
.SH SYNOPSIS
.B zupt compress
.RI [ options ]
.I output.zupt input...
.br
.B zupt extract
.RI [ options ]
.I archive.zupt
.br
.B zupt list
.RI [ options ]
.I archive.zupt
.br
.B zupt test
.RI [ options ]
.I archive.zupt
.br
.B zupt info
.I archive.zupt
.br
.B zupt bench
.RB [ --compare ]
.I input...
.br
.B zupt disk backup
.RI [ options ]
.I output.zupt device-or-file
.br
.B zupt disk restore
.RI [ options ]
.I archive.zupt target-device-or-file
.br
.B zupt keygen
.RI [ key-options ]
.B -o
.I output
.br
.B zupt
.RB { help | --help | -h | version | --version | -V }
.
.SH DESCRIPTION
.B zupt
creates self-contained backup archives with the historical
.B .zupt
extension. Version 5.2.2 restores the original product name, ZUPT, and the
primary installed command is
.BR zupt .
The archive extension and
.B ZUPT
format magic, codec identifiers, and archive compatibility remain unchanged.
.
.PP
Plain archives provide compression checksums for accidental-corruption
detection. They do not provide cryptographic authentication against an attacker
who can rewrite an archive. Encrypted archives use AES-256-CTR with
HMAC-SHA256 and authenticate current per-block framing, logical frame position,
and archive metadata. The validating read paths require an archive-integrity
trailer by default; a no-trailer archive fails closed unless the caller selects
the explicit trusted-legacy override described below.
See
.B SECURITY.md
and
.B THREAT_MODEL.md
for the exact boundary and historical-format limitations.
.
.PP
The bundled compression codec is VaptVupt codec 2.65.3.
Automatic codec selection uses VaptVupt where the supported AVX2 or NEON path
is available and uses the portable LZHP codec otherwise. Use a codec-selection
option only when a specific choice is required.
.
.PP
The renamed-era
.B vaptvupt
command is an optional compatibility alias and is not installed by default.
Distribution packages, including the openSUSE main package, expose
.B zupt
as the canonical command.
.
.PP
Git and the upstream source tarball are source-only. Separately built CLI DEB,
binary RPM, SRPM, notice-bearing Linux tar.xz, Windows ZIP, and macOS DMG assets
may be published from the immutable tag only after their target-specific gates
pass; they never enter Git or the source tarball. An AppImage is not promoted
for 5.2.8; neither are AppDir/Flatpak bundles, GUI platform installers, or bare
Linux/Windows executables. The Python/Qt frontend remains available as source;
its gated architecture-independent DEB, noarch/source RPM, and source-only
portable ZIP are included in the release claim. The portable ZIP contains no
Python, Qt, CLI, or compiled runtime. Windows and macOS artifacts remain
CLI-only.
.
.SH COMMANDS
.TP
.BR compress , " c"
Create an archive from one or more files or directories. Directories are
traversed recursively. All options must precede
.IR output.zupt .
A literal input name beginning with a hyphen can follow a
.B --
separator after the output name.
.
.TP
.BR extract , " x"
Extract regular-file contents below the current directory or the directory
selected by
.BR -o .
Entry names are validated and resolved below a pinned destination. Existing
destination files are not overwritten. Decoded data is published from a
private temporary file only after its expected size and checksum are verified.
ZUPT does not restore ownership, ACLs, extended attributes, original mode,
or modification time.
.
.TP
.BR list , " l"
Open and verify the archive as required, then print its format, block and
protection flags followed by each entry's path, original size, compressed size,
and compression ratio. No file is extracted.
.
.TP
.BR test , " t"
Decode and verify the archive without writing extracted files. Exit status is
nonzero on an authentication, integrity, format, or I/O failure.
.
.TP
.BR info , " i"
Read non-secret framing metadata without a password or private key. Current
output includes archive size, format version, integrity-trailer type, UUID,
creation timestamp, block count when a footer is found, encryption/PQ mode,
selected global feature flags, and whether a comment is present. This command
does not decrypt, list, or extract entries. It does not validate the trailer or
archive contents: a reported trailer is framing information, and successful
execution is not an integrity result. Its input must still be treated as
untrusted.
.
.TP
.BR bench , " b"
Run the built-in compression-level benchmark on supplied input. With
.BR --compare ,
also compare external compressors available on the host. Results depend on the
input, compiler, CPU, storage, and system load and are not release support
claims.
.
.TP
.B disk backup
Read a device or regular file sequentially and create a sparse-aware archive.
Zero regions are represented without storing their full contents.
.
.TP
.B disk restore
Restore a disk-image archive to a device or regular file. The target is written
destructively; verify both operands and use the least privilege required. The
archive is first copied to a private, auto-deleted scratch file, and validation
and restoration consume that same snapshot before the target is opened. Set
.B ZUPT_TMPDIR
to an existing private scratch directory when needed; an invalid override fails
without fallback. A raw block device is rejected before the first write when
its capacity cannot be determined or is smaller than the restored image.
.
.TP
.B keygen
Generate a native ML-KEM-768 plus X25519 hybrid private key by default. Other
key formats require the matching key-generation option and, for optional SDK or
PQBOX modes, a build with the corresponding system integration enabled.
.
.TP
.BR version , " --version" , " -V"
Print the program, archive-format and codec versions, compiled optional
integrations, runtime CPU acceleration, license scopes, and canonical project
URL.
.
.TP
.BR help , " --help" , " -h"
Print command usage and the options compiled into the program.
.
.SH PASSWORD INPUT
The password options are accepted by
.BR compress ,
.BR extract ,
.BR list ,
.BR test ,
and both
.B disk
subcommands. For
.B compress
and
.BR "disk backup" ,
the interactive form asks for confirmation.
.
.TP
.BR -p " " password , " --password " password
Read a password directly from the next process argument. This is compatible
with older command lines but can expose the password through shell history or
process inspection. Prefer one of the non-argv forms below.
.
.TP
.B --password-prompt
Read the password from the terminal without echo. This explicit form avoids
the optional-argument ambiguity of historical
.BR -p .
On POSIX, handled interruptions restore the terminal settings saved before the
prompt.
.
.TP
.BI --pass-file " file"
Read the first line from
.IR file .
The trailing LF and an optional preceding CR are removed. Empty input, an
embedded NUL, or an overlong password is rejected. Protect the file with
restrictive permissions and remove it securely when it is no longer needed.
.
.TP
.BI --pass-fd " fd"
Read the first line from the inherited numeric file descriptor
.IR fd .
ZUPT duplicates the descriptor for reading and does not close the caller's
original descriptor. The duplicate shares the underlying stream and offset;
buffered input may consume beyond the password line, so dedicate the descriptor
to this read. The same input validation as
.B --pass-file
applies.
.
.SH COMPRESSION OPTIONS
These options are accepted by
.B compress
unless stated otherwise.
.
.TP
.BR -l " " 1..9 , " --level " 1..9
Set the compression level. The default is 7. Automatic block sizes are 128 KiB
for levels 1-2, 1 MiB for levels 3-4, 2 MiB for levels 5-6, 4 MiB for level 7,
and 8 MiB for levels 8-9. Deduplication uses a smaller automatic granularity.
.
.TP
.BR -b " " bytes , " --block " bytes
Set the block size in bytes. Values are constrained to the supported range of
64 KiB through 256 MiB.
.
.TP
.BR -s , " --store"
Store data without compression.
.
.TP
.BR -f , " --fast"
Select the fast LZ codec.
.
.TP
.BR --vv , " --vaptvupt"
Force the bundled VaptVupt LZ plus ANS codec.
.
.TP
.B --lzhp
Force the portable LZHP codec.
.
.TP
.BI --kdf " algorithm"
Select the password KDF. The default source-only build supports and defaults to
.BR pbkdf2 ,
using PBKDF2-SHA256 with 600,000 iterations. A
.B WITH_SDK=1
build additionally supports
.B argon2id
and uses it by default. A build must reject a requested KDF that it cannot
provide rather than silently changing algorithms.
.
.TP
.BR -c " " text , " --comment " text
Store a comment of up to 4095 CLI bytes. In an encrypted archive the comment is
encrypted and authenticated with the archive; in a plain archive it receives
only the plain archive's non-cryptographic integrity treatment.
.
.TP
.BI --comment-file " file"
Read the comment from
.IR file .
Trailing CR/LF characters are removed.
.
.TP
.BR -D , " --dedup"
Enable block-level deduplication. Encrypted deduplicated blocks still use fresh
per-block nonces in current archives. DATA and DEDUP_REF frames are bound to
their logical positions. An authenticated reference also carries the source
position required to authenticate the referenced DATA frame.
.
.TP
.BR -S , " --solid"
Use one solid compression stream. Solid mode is single-threaded.
.
.TP
.BR -y , " --force"
Allow
.B compress
to overwrite an existing output whose name does not end in
.BR .zupt .
This does not relax extraction's no-overwrite policy.
.
.TP
.BR -t " " count , " --threads " count
Set the compression or extraction thread count. Zero selects automatic
detection; explicit values are limited to 64. This option is accepted by
.B compress
and
.BR extract ,
and by disk backup/restore, but not by
.B list
or
.BR test .
.
.TP
.BR -v , " --verbose"
Enable additional progress or diagnostic output where the selected command
implements it. Authentication failures remain intentionally generic unless
verbose diagnostics are requested, reducing the default verbal probe-oracle.
.
.SH POST-QUANTUM OPTIONS
.TP
.BI --pq " key"
Use the native hybrid envelope: ML-KEM-768 plus X25519 with the archive-key
combiner documented in
.BR THREAT_MODEL.md .
Use the recipient public key for creation and the matching private key for
reading. This is the recommended native PQ mode.
.
.TP
.BI --pq-only " key"
Use native ML-KEM-768 without the X25519 hedge. Choose it only when a policy
requires a single post-quantum KEM. Its keys are generated with
.BR "zupt keygen --pq-only" .
.
.TP
.BI --pq-sdk " key"
Use the optional system
.B libvuptsdk
integration. This option is unavailable unless ZUPT was built with
.BR WITH_SDK=1 .
It is not enabled by the default source-only distribution build.
.
.TP
.BI --pq-box " key"
Use the optional system
.B libpqvaptvupt
sealed-box integration. This option is unavailable unless ZUPT was built
with
.BR WITH_PQBOX=1 .
It is not enabled by the default source-only distribution build.
.
.SH EXTRACTION OPTIONS
.TP
.BR -o " " directory , " --output " directory
Extract below
.IR directory .
The directory is created when needed. The default is the current directory.
.
.PP
Extraction also accepts the password, PQ,
.BR -v ,
and
.B -t
options described above. Unlike compression, extract/list/test options may
appear before or after the single archive operand.
.
.SH TRUSTED LEGACY READ OPTION
.TP
.B --allow-legacy-no-ait
Permit recovery of a known, trusted historical archive that predates the
archive-integrity trailer. By default the validating read commands reject an
archive without an AIT, without trusting its unauthenticated header flags. This
option emits a downgrade warning and is accepted only by
.BR extract ,
.BR list ,
.BR test ,
and
.BR "disk restore" .
It is rejected by compression and disk backup, which always write a current
trailer.
.
.PP
Do not use this option for an archive obtained from untrusted or
attacker-writable storage. It does not authenticate the legacy header or footer.
After recovery, verify the restored data and create a new current archive.
.
.SH DISK OPTIONS
Disk options must precede the archive and device/file operands. Both disk
subcommands accept
.BR -l / --level ,
.BR -b / --block ,
.BR -s / --store ,
.BR --vv / --vaptvupt ,
.BR --lzhp ,
the password input options,
.BR --pq ,
.BR --pq-only ,
.BR -D / --dedup ,
.BR -c / --comment ,
.BR --comment-file ,
.BR --kdf ,
.BR -t / --threads ,
and
.BR -v / --verbose .
The optional SDK/PQBOX envelope options and solid mode are not disk-command
options.
.
.PP
.B --allow-legacy-no-ait
is accepted by disk restore only.
.
.SH KEY GENERATION OPTIONS
.TP
.BR -o " " file , " --output " file
Write the generated private key or exported public key to
.IR file .
This option is required.
.
.TP
.BR --pub
Export a public key from the private key selected by
.BR -k .
Use the same mode option as the private key.
.
.TP
.BR -k " " file , " --key " file
Read the source private key used by
.BR --pub .
.
.TP
.BR --pq-only , " --pqonly"
Generate or export a native ML-KEM-768-only key.
.
.TP
.BR --sdk , " --pq-sdk"
Generate an SDK-format key through the optional
.B libvuptsdk
integration.
.
.TP
.BR --box , " --pq-box"
Generate a sealed-box key through the optional
.B libpqvaptvupt
integration.
.
.PP
With no mode option,
.B keygen
generates or exports the native ML-KEM-768 plus X25519 hybrid format used by
.BR --pq .
.PP
Private-key output uses no-replace creation, POSIX mode 0600, or a Windows
current-user-only DACL. A write, flush, or close failure leaves the invalid
exclusive partial for manual removal rather than unlinking a possibly replaced
pathname. Native ZKEY and ZPQK inputs must have a valid checksum, version,
flags, reserved bytes, exact size, and public/private role; malformed or
role-confused keys are rejected.
.
.SH SECURITY NOTES
Use encrypted archives when an attacker may modify storage. Plain checksums are
not authentication. Keep private keys separate from archives, use high-entropy
passwords, and prefer
.BR --password-prompt ,
.BR --pass-file ,
or
.B --pass-fd
over argv passwords.
.PP
Archive comments remain untrusted display data even when authenticated. ZUPT
renders control bytes without emitting raw terminal-control sequences.
.
.PP
Extract untrusted archives as a dedicated unprivileged user into a new empty
local directory. POSIX builds canonicalize the user-selected output root once,
then traverse below a pinned directory descriptor with no-follow operations.
Windows builds use handle-relative traversal and
no-replace publication for normal local Win32 destinations. Extended-length and
device-namespace paths, raw UNC output roots, and mapped/network-drive output
are not supported in 5.2.8. Cross-compilation and Wine results are not native
Windows evidence; the native Windows package gate, including its Unicode round
trip, is separate and mandatory before publication.
.
.PP
Current encrypted archives protect ciphertext, canonical framing metadata,
logical frame position, and current header/footer metadata. The default AIT
requirement prevents a silent downgrade to a trailerless layout; the explicit
legacy override intentionally leaves old header/footer metadata outside that
authenticated boundary. ZUPT does not protect a compromised endpoint, a
disclosed credential, archive rollback or deletion, traffic analysis,
compression-length side channels, or every compiler and microarchitectural side
channel. Native PQ archive encryption is at-rest encryption, not session
forward secrecy.
.
.SH EXIT STATUS
.TP
.B 0
The requested operation completed successfully.
.
.TP
.B 1
Invalid arguments or an operational, format, authentication, integrity, or I/O
failure.
.
.SH FILES
.TP
.I /usr/bin/zupt
Distribution-installed command.
.
.TP
.I /usr/share/bash-completion/completions/zupt
Bash completion for the current command.
.
.TP
.I /usr/share/zsh/site-functions/_zupt
zsh completion for the current command.
.
.TP
.I /usr/share/fish/vendor_completions.d/zupt.fish
fish completion for the current command.
.
.SH EXAMPLES
Create a plain archive:
.PP
.RS
.B zupt compress backup.zupt Documents/
.RE
.
.PP
Create and restore a password-protected archive without placing the password in
argv:
.PP
.RS
.B zupt compress --password-prompt secure.zupt Documents/
.br
.B zupt test --password-prompt secure.zupt
.br
.B zupt extract --password-prompt -o restored secure.zupt
.RE
.
.PP
Use a password supplied on file descriptor 3:
.PP
.RS
.B zupt test --pass-fd 3 secure.zupt 3<password-file
.RE
.
.PP
Create a native hybrid recipient key and archive:
.PP
.RS
.B zupt keygen -o recipient.key
.br
.B zupt keygen --pub -k recipient.key -o recipient.pub
.br
.B zupt compress --pq recipient.pub backup.zupt Documents/
.br
.B zupt extract --pq recipient.key -o restored backup.zupt
.RE
.
.PP
Inspect and verify an archive without extraction:
.PP
.RS
.B zupt info backup.zupt
.br
.B zupt list backup.zupt
.br
.B zupt test backup.zupt
.RE
.
.SH COMPATIBILITY
The on-disk version byte remains v1.6, with flag-gated 5.2.2 encodings for
positional authenticated dedup references and disk-image integrity metadata.
Readers since 5.2.2 retain compatibility parsers for the fixed-width disk index
and the encrypted-dedup linear AAD sequence published through 5.2.1. The narrow
compatibility fixture is an actual v5.2.1 password-encrypted
DATA/DATA/REF/DATA disk archive stored as hexadecimal text with source and hash
provenance. The candidate lists, tests, extracts, and restores that fixture
byte-exact. The exact 5.2.8 candidate must repeat the gate. It does not imply
that a 5.2.1 reader
accepts every new 5.2.2 archive or that every historical encrypted mode was
retested.
.
.PP
The FIPS 203 correction in VaptVupt 5.0.0 changed
native
.B --pq
and
.B --pq-only
key/archive compatibility relative to releases through 4.2.1. Password and
plain archives were not affected by that KEM correction. Consult
.B CHANGELOG.md
before relying on cross-version recovery, and test restoration before depending
on a backup.
.
.SH LICENSE
The ZUPT application and tool code are AGPL-3.0-or-later. The bundled
VaptVupt codec source is GPL-3.0-or-later. The two xxHash-derived XXH64 units
also carry BSD-2-Clause. The pq-crystals/kyber-derived portions of native
ML-KEM also carry CC0-1.0; the x86 BCJ state machine is adapted from
public-domain LZMA SDK source. Native X25519 portions adapted from
curve25519-donna also carry BSD-3-Clause. Preserve
.BR LICENSE ,
.BR LICENSE-AGPL-3.0 ,
.BR LICENSE-GPL-3.0 ,
.BR LICENSE-BSD-2-Clause ,
.BR LICENSE-BSD-3-Clause ,
.BR LICENSE-CC0-1.0 ,
.BR NOTICE ,
and
.B THIRD-PARTY-NOTICES.md
when redistributing the source.
Published historical revisions include MIT grants for exact material shipped
with those notices; the current license summary does not revoke them. See the
5.2.2 licensing erratum in
.B CHANGELOG.md
for repository evidence.
.
.SH AUTHORS
Cristian Cezar Moisés is the primary upstream author and maintainer.
Packaging credits belong in their applicable packaging history and do not
imply authorship of the upstream program.
.
.SH REPORTING BUGS
Project issues:
.UR https://github.com/cristiancmoises/zupt/issues
ZUPT issue tracker
.UE
.
.PP
Report security vulnerabilities privately as described in
.BR SECURITY.md .
.
.SH SEE ALSO
.BR zupt-gui (1)

View file

@ -3,14 +3,13 @@
Copyright (C) 2026 Cristian Cezar Moisés <zupt@riseup.net>
VaptVupt GUI (formerly Zupt GUI; parent application renamed in v3.0.0
due to a prior INPI Brasil trademark registration of "Zupt") is free
ZUPT GUI is free
software: you can redistribute it and/or modify it under the terms of
the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your
option) any later version.
VaptVupt GUI is distributed in the hope that it will be useful, but
ZUPT GUI is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Affero General Public License for more details.
@ -23,21 +22,26 @@
─────────────────────────────────────────────────────────────────────
PRIOR LICENSE NOTE
HISTORICAL LICENSE NOTE
Earlier copies of this file may have stated "MIT License" — that was
a packaging mistake inherited from a template. The GUI source code's
SPDX-License-Identifier header has always been AGPL-3.0-or-later;
the file-level license here is corrected to match. There is no
historical MIT-licensed release of VaptVupt GUI; do not assume MIT
grant from any prior tarball that contained this file.
Published repository history includes earlier copies of this file
under the MIT License. In particular, commit d4660e6539c8b6eeba81751c018217d978fdd618
and the v2.2.2 source tag contain an MIT-form gui/LICENSE-GUI. Those
permissions remain applicable to the exact material distributed under
them; this file does not revoke or reinterpret a historical grant.
The current GUI source carries AGPL-3.0-or-later SPDX notices. Apply
the notices shipped with the exact source revision being used. See the
5.2.2 licensing erratum in CHANGELOG.md for the repository record.
─────────────────────────────────────────────────────────────────────
COMMERCIAL LICENSING
The VaptVupt GUI may be commercially relicensed by the author. If
you require relief from copyleft terms (proprietary derivatives,
closed-source bundling, etc.), contact:
The applicable copyright holder may offer controlled first-party
rights under a separately executed commercial agreement. This notice
is not a commercial license grant, does not revoke a public license,
and cannot relicense rights that the licensor does not control. For
inquiries, contact:
sac@securityops.co

View file

@ -1,123 +1,149 @@
# VaptVupt GUI
# ZUPT GUI
Desktop application for [vaptvupt](https://git.securityops.co/cristiancmoises/vaptvupt) backup compression with ML-KEM-768 + X25519 post-quantum hybrid encryption.
The ZUPT GUI is a Python/Qt front end for the ZUPT 5.2.8 command-line
program. It starts the CLI as a subprocess; compression, archive parsing, and
cryptography remain in the C program.
Works on GNU/Linux, BSD, macOS, and Windows.
The canonical project repository is
`https://github.com/cristiancmoises/zupt`.
## Install
## Requirements
### Linux (recommended)
- a working `zupt` CLI from the same release, available on `PATH` or through
the `ZUPT_BIN` environment variable;
- Python 3.9 or newer;
- PySide6 or PyQt6;
- a graphical session for normal use.
```bash
tar xzf vaptvupt-gui.tar.gz && cd vaptvupt-gui
./vaptvupt-gui # auto-creates venv, installs PySide6
./install.sh --user # adds right-click menu integration
Install and test the source-only CLI first:
```sh
make clean
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 1)" \
WITH_SDK=0 WITH_PQBOX=0
make WITH_SDK=0 WITH_PQBOX=0 check
./zupt --version
```
### Windows
The GUI detects the capabilities reported by that binary. Native `--pq` and
`--pq-only` are available in the default build. SDK and PQBOX controls are
usable only when the CLI was built explicitly against the corresponding system
development libraries; no precompiled optional library is shipped in Git.
**Option A — Installer (recommended):**
## Run from the source tree
Download `VaptVuptGUI-1.3.0-Setup.exe` and run it. Installs to Program Files, adds Start Menu shortcut, desktop shortcut, right-click context menus, and .zupt file association. Includes uninstaller.
Using a virtual environment keeps Python packages outside the repository:
**Option B — Build from source:**
```cmd
cd packaging\windows
build-windows.bat
```sh
python3 -m venv ~/.local/share/zupt-gui-venv
~/.local/share/zupt-gui-venv/bin/pip install PySide6
ZUPT_BIN="$PWD/zupt" \
~/.local/share/zupt-gui-venv/bin/python gui/src/zupt_gui.py
```
Requires Python 3.9+, NSIS 3.x, and a compiled `vaptvupt.exe`.
Installing PySide6 can access the Python package index. Do that as an explicit
setup step; upstream CLI builds, package builds, and checks do not download
dependencies.
**Option C — Run directly:**
For noninteractive checks:
```cmd
pip install PySide6
python src\zupt_gui.py
```sh
python3 gui/src/zupt_gui.py --version
python3 gui/src/zupt_gui.py --selftest
```
### macOS / BSD
The first command does not prove that a full desktop session works. Test the
actual windows and archive operations on every platform for which a GUI package
is published.
```bash
pip3 install PySide6
python3 src/zupt_gui.py
## Functions
| Area | Function |
|---|---|
| Keys | Generate keys supported by the selected CLI build |
| Compress | Select input, compression settings, and an available encryption mode |
| Extract | Detect archive encryption, request the needed credential, and extract |
| Verify | Run archive integrity validation without extraction |
| Info | Display metadata reported by the CLI |
| Disk | Front end for the CLI disk backup and restore commands |
Disk operations can require additional operating-system privileges. Run only
the specific CLI operation that needs them; do not run the whole desktop session
as root.
## Desktop integration
`gui/install.sh --user` installs the integration supported by that script for
the current user. Review the script and its destination paths before running
it. File-manager menus and file associations differ across desktops and
operating systems and must be tested on the target system.
## Packaged GUI builds
Release pages provide only these GUI artifacts after their separate package and
installed off-screen GUI/CLI integration gates pass:
- `zupt-gui_5.2.8_all.deb`;
- `zupt-gui-5.2.8-1.noarch.rpm`;
- `zupt-gui-5.2.8-1.src.rpm`;
- `zupt-gui-5.2.8-portable.zip`.
The DEB/RPM packages install the Python/Qt source and depend on the matching
`zupt` CLI package. The portable ZIP contains source, launchers, icons, licenses,
and provenance only; it bundles no Python, Qt, CLI, or compiled runtime. Its
gate scans the assembled and extracted trees, enforces an exact safe-member
allowlist, and runs the extracted launcher off-screen against the tested CLI.
An absent artifact did not pass its gate and must not be inferred from another
format's result.
GUI AppImage, AppDir and Flatpak bundles, and Windows/macOS GUI installers are
not promoted by the upstream 5.2.8 release gates.
`packaging/build-gui-appimage.sh` is a downstream-only helper and fails unless
its operator supplies the exact verified runtime plus a complete
license/source-relink notice through `APPIMAGE_RUNTIME_COMPLIANCE_FILE`; that
material is included in the resulting AppDir.
The downstream Windows GUI helper similarly requires
`ZUPT_WINDOWS_RUNTIME_NOTICES_DIR` with a `MANIFEST.txt` that identifies
the exact Python, PyInstaller, Qt and PySide/PyQt runtime inputs and their
notices. It fails unless the directory also has non-empty
`PYTHON-NOTICE.txt`, `PYINSTALLER-NOTICE.txt`, `QT-NOTICE.txt`, and either
`PYSIDE6-NOTICE.txt` or `PYQT6-NOTICE.txt`. The installer includes that
directory together with every ZUPT license and notice. This requirement does
not make the untested GUI installer a 5.2.8 release asset. The promoted Windows
ZIP and macOS DMG are CLI-only.
Packaging recipes and scripts under `gui/packaging/` and `packaging/` are build
inputs, not evidence that a package has been accepted by a distribution. They
must build the CLI from the immutable source tag with
`WITH_SDK=0 WITH_PQBOX=0` unless source-built system dependencies are declared.
Generated packages, application bundles, and executables must remain outside
Git and outside upstream source archives.
The former standalone `gui/setup.py` sdist/wheel route is intentionally absent:
its outputs did not carry the complete project license payload. Use
`gui/install.sh` or the reviewed distribution helpers so the AGPL text and
artwork provenance are installed with the GUI.
## Troubleshooting
Verify the exact interpreter and CLI used by the GUI:
```sh
python3 -c 'import PySide6.QtWidgets'
zupt --version
ZUPT_BIN=/absolute/path/to/zupt \
python3 gui/src/zupt_gui.py --selftest
```
### AppImage (universal Linux)
```bash
chmod +x VaptVupt-GUI-1.3.0-x86_64.AppImage
./VaptVupt-GUI-1.3.0-x86_64.AppImage
```
### Flatpak
```bash
flatpak-builder --install build packaging/flatpak/dev.zupt.gui.yml
flatpak run dev.zupt.gui
```
## Features
| Tab | Function |
|-----|----------|
| Keys | Generate ML-KEM-768 + X25519 hybrid keypairs |
| Compress | All codecs, levels 1-9, dedup, solid, password, PQ keys |
| Extract | Decrypt and extract .zupt archives |
| Verify | Check block checksums, view archive metadata |
| Disk | Full-disk backup and restore |
| About | Version, cryptographic stack, credits |
All tabs support drag-and-drop. Drop a .zupt file anywhere on the window to extract it. Drop any other file to compress it.
## System Integration
### Linux (Nemo / Cinnamon)
After `./install.sh --user`:
- Right-click any file: **Compress with VaptVupt**
- Right-click .zupt file: **Extract with VaptVupt**
- Double-click .zupt: opens in VaptVupt GUI
### Windows (after installer)
- Right-click any file: **Compress with VaptVupt**
- Right-click any folder: **Compress with VaptVupt**
- Double-click .zupt: opens in VaptVupt GUI
- Right-click .zupt: **Verify Integrity**
## Architecture
```
VaptVupt GUI (PySide6, Python)
|
|-- subprocess.Popen() with streaming stderr
|
v
vaptvupt CLI (Pure C11 binary)
ML-KEM-768 + X25519 + AES-256-CTR
VaptVupt / LZHP / Store codecs
Block deduplication, full-disk backup
```
The GUI calls the vaptvupt CLI binary — all cryptography runs in native C, not Python.
## Packaging
| Platform | Format | Tool |
|----------|--------|------|
| Any | pip | `pip install .` |
| Debian/Ubuntu/Mint | .deb | `packaging/deb/control` |
| Fedora/RHEL | .rpm | `rpmbuild -ba packaging/rpm/vaptvupt.spec` |
| Universal Linux | .AppImage | `packaging/appimage/build-appimage.sh` |
| Sandboxed Linux | .flatpak | `packaging/flatpak/dev.zupt.gui.yml` |
| Windows | .exe installer | `packaging/windows/build-windows.bat` |
| Windows | NSIS .exe | `packaging/windows/zupt-installer.nsi` |
## Credits
- **vaptvupt** v4.1.0 — Cristian Cezar Moisés ([github](https://git.securityops.co/cristiancmoises/vaptvupt))
If no window appears, run the GUI from a terminal and check the display/Wayland
or X11 error. When reporting a problem, include OS and desktop versions, Python
and Qt binding versions, `zupt --version`, and the non-sensitive error
message. Never attach passwords, private keys, tokens, or confidential archives.
## License
AGPL-3.0-or-later
The current GUI source is AGPL-3.0-or-later. Published historical revisions
include MIT notices whose grants remain applicable to the exact material
distributed under them. See `gui/LICENSE-GUI`, the 5.2.2 licensing erratum in
`CHANGELOG.md`, and the repository-level license notices.

23
gui/assets/README.md Normal file
View file

@ -0,0 +1,23 @@
# GUI image assets
These files are runtime data used by the graphical interface and its packaging;
they are not executable code or compiler output.
| File | Purpose |
| --- | --- |
| `zupt-icon.png` | Main 48 px application icon used by the GUI and packaging. |
| `zupt-128.png` | 128 px application icon. |
| `zupt.png` | 256 px application/AppDir icon. |
| `zupt.ico` | Windows application icon container. |
Git provenance: all four files were first added by Cristian Cezar Moisés in
ZUPT repository commit `d4660e6539c8b6eeba81751c018217d978fdd618`; the repository
records no earlier or external origin. Their current Git blobs are byte-for-byte
the same blobs present in that commit. That revision distributed them with MIT
license notices, whose permissions remain available for those exact files.
The current GUI tree also carries AGPL-3.0-or-later notices; apply the license
option appropriate to the exact material and revision being redistributed and
preserve `gui/LICENSE-GUI` and the repository notices. Their historical
filenames are retained because build and desktop-integration files refer to
them.

View file

@ -1,90 +1,130 @@
#!/bin/bash
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
# Install Zupt GUI + desktop integration
# Run: sudo ./install.sh (or ./install.sh --user for per-user install)
set -e
DIR="$(cd "$(dirname "$0")" && pwd)"
USER_INSTALL=0
[ "$1" = "--user" ] && USER_INSTALL=1
# Install the integrated ZUPT GUI from the checked-out source tree.
# This script never downloads Python modules or operating-system packages.
if [ "$USER_INSTALL" -eq 1 ]; then
BIN="$HOME/.local/bin"
APPS="$HOME/.local/share/applications"
NEMO="$HOME/.local/share/nemo/actions"
MIME="$HOME/.local/share/mime"
NAUTILUS="$HOME/.local/share/nautilus/scripts"
else
BIN="/usr/local/bin"
APPS="/usr/share/applications"
NEMO="/usr/share/nemo/actions"
MIME="/usr/share/mime"
NAUTILUS=""
set -Eeuo pipefail
die() {
printf 'zupt-gui install: %s\n' "$*" >&2
exit 1
}
usage() {
cat <<'EOF'
Usage: gui/install.sh [OPTIONS]
--user install below $HOME/.local
--prefix DIR installation prefix (default: /usr/local)
--destdir DIR staging root for package builds
--legacy-alias install opt-in vaptvupt-gui compatibility symlink
-h, --help show this help
Python 3.9+ and either PySide6 or PyQt6 must already be installed. The
zupt CLI must also be installed or selected with ZUPT_BIN at runtime.
VAPTVUPT_BIN remains a renamed-era compatibility fallback.
EOF
}
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
repo_root=$(cd -- "$script_dir/.." && pwd -P)
prefix=/usr/local
destdir=${DESTDIR:-}
legacy_alias=0
while (($#)); do
case $1 in
--user)
[[ -n ${HOME:-} ]] || die 'HOME is unset; cannot use --user'
prefix=$HOME/.local
;;
--prefix)
(($# >= 2)) || die '--prefix requires a directory'
prefix=$2
shift
;;
--destdir)
(($# >= 2)) || die '--destdir requires a directory'
destdir=$2
shift
;;
--legacy-alias) legacy_alias=1 ;;
-h|--help) usage; exit 0 ;;
*) die "unknown option: $1" ;;
esac
shift
done
[[ $prefix == /* ]] || die '--prefix must be an absolute path'
[[ -z $destdir || $destdir == /* ]] || die '--destdir must be an absolute path'
bindir=${BINDIR:-$prefix/bin}
libexecdir=${LIBEXECDIR:-$prefix/lib/zupt-gui}
datadir=${DATADIR:-$prefix/share}
for source_file in \
"$script_dir/src/zupt_gui.py" \
"$script_dir/assets/zupt-icon.png" \
"$script_dir/packaging/zupt-gui.desktop" \
"$repo_root/doc/zupt-gui.1" \
"$repo_root/LICENSE" \
"$repo_root/LICENSE-AGPL-3.0" \
"$script_dir/LICENSE-GUI" \
"$script_dir/assets/README.md"; do
[[ -f $source_file ]] || die "required source file is missing: $source_file"
done
stage_bindir=$destdir$bindir
stage_libexecdir=$destdir$libexecdir
stage_datadir=$destdir$datadir
install -d -- "$stage_bindir" "$stage_libexecdir" \
"$stage_datadir/applications" \
"$stage_datadir/icons/hicolor/256x256/apps" \
"$stage_datadir/man/man1" \
"$stage_datadir/licenses/zupt-gui"
install -m 0644 -- "$script_dir/src/zupt_gui.py" "$stage_libexecdir/zupt_gui.py"
install -m 0644 -- "$script_dir/packaging/zupt-gui.desktop" \
"$stage_datadir/applications/zupt-gui.desktop"
install -m 0644 -- "$script_dir/assets/zupt-icon.png" \
"$stage_datadir/icons/hicolor/256x256/apps/zupt-gui.png"
install -m 0644 -- "$repo_root/doc/zupt-gui.1" \
"$stage_datadir/man/man1/zupt-gui.1"
install -m 0644 -- "$repo_root/LICENSE" \
"$stage_datadir/licenses/zupt-gui/LICENSE"
install -m 0644 -- "$repo_root/LICENSE-AGPL-3.0" \
"$stage_datadir/licenses/zupt-gui/LICENSE-AGPL-3.0"
install -m 0644 -- "$script_dir/LICENSE-GUI" \
"$stage_datadir/licenses/zupt-gui/LICENSE-GUI"
install -m 0644 -- "$script_dir/assets/README.md" \
"$stage_datadir/licenses/zupt-gui/ASSET-PROVENANCE.md"
# Quote the installed module path for a POSIX shell without embedding DESTDIR.
quoted_libexec=${libexecdir//\'/\'\\\'\'}
launcher_tmp=$(mktemp "${TMPDIR:-/tmp}/zupt-gui-launcher.XXXXXXXX")
trap 'rm -f -- "$launcher_tmp"' EXIT HUP INT TERM
cat >"$launcher_tmp" <<EOF
#!/bin/sh
exec python3 '$quoted_libexec/zupt_gui.py' "\$@"
EOF
install -m 0755 -- "$launcher_tmp" "$stage_bindir/zupt-gui"
if ((legacy_alias)); then
ln -s -- zupt-gui "$stage_bindir/vaptvupt-gui"
fi
mkdir -p "$BIN" "$APPS"
# ── Install launcher ──
cat > "$BIN/zupt-gui" << LAUNCHER
#!/bin/bash
DIR="$DIR"
VENV="\$DIR/.venv"
PY="\$VENV/bin/python3"
[ ! -x "\$PY" ] && python3 -m venv "\$VENV" && "\$VENV/bin/pip" install PySide6 -q
exec "\$PY" "\$DIR/src/zupt_gui.py" "\$@"
LAUNCHER
chmod +x "$BIN/zupt-gui"
echo "Installed: $BIN/zupt-gui"
# ── Desktop entry ──
cp "$DIR/packaging/zupt-gui.desktop" "$APPS/"
echo "Installed: $APPS/zupt-gui.desktop"
# ── Nemo actions (Linux Mint / Cinnamon) ──
if [ -d "$(dirname "$NEMO")" ] || [ "$USER_INSTALL" -eq 1 ]; then
mkdir -p "$NEMO"
cp "$DIR/packaging/desktop-integration/nemo/"*.nemo_action "$NEMO/" 2>/dev/null && \
echo "Installed: Nemo right-click actions" || true
fi
# ── Nautilus scripts (GNOME) ──
if [ -n "$NAUTILUS" ]; then
mkdir -p "$NAUTILUS"
cat > "$NAUTILUS/Compress with Zupt" << 'NSCRIPT'
#!/bin/bash
zupt-gui --compress $NAUTILUS_SCRIPT_SELECTED_FILE_PATHS
NSCRIPT
chmod +x "$NAUTILUS/Compress with Zupt"
echo "Installed: Nautilus script"
fi
# ── MIME type for .zupt files ──
MIME_XML="$MIME/packages/zupt.xml"
if [ ! -f "$MIME_XML" ]; then
mkdir -p "$(dirname "$MIME_XML")"
cat > "$MIME_XML" << 'MIMEXML'
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-zupt">
<comment>Zupt Archive</comment>
<glob pattern="*.zupt"/>
<icon name="package-x-generic"/>
</mime-type>
</mime-info>
MIMEXML
if command -v update-mime-database >/dev/null; then
update-mime-database "$MIME" 2>/dev/null
if [[ -z $destdir ]]; then
if command -v update-desktop-database >/dev/null 2>&1; then
update-desktop-database "$datadir/applications" >/dev/null 2>&1 || true
fi
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
gtk-update-icon-cache -q "$datadir/icons/hicolor" >/dev/null 2>&1 || true
fi
echo "Registered: .zupt MIME type"
fi
# ── Associate .zupt files with zupt-gui ──
if command -v xdg-mime >/dev/null; then
xdg-mime default zupt-gui.desktop application/x-zupt 2>/dev/null
echo "Associated: .zupt files open with Zupt GUI"
printf 'Installed zupt-gui below %s%s\n' "$destdir" "$prefix"
if ((!legacy_alias)); then
printf 'Legacy vaptvupt-gui alias was not installed (use --legacy-alias to opt in).\n'
fi
echo ""
echo "Done. Right-click any file in your file manager to see Zupt options."
echo "Double-click any .zupt file to open it in Zupt GUI."

View file

@ -1,51 +1,7 @@
#!/bin/bash
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
# Build Zupt GUI AppImage
# Requires: appimagetool, python3, pip
set -e
APP="zupt-gui"
VERSION="1.0.0"
APPDIR="${APP}.AppDir"
rm -rf "$APPDIR" "${APP}-${VERSION}-x86_64.AppImage"
mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/share/zupt-gui" "$APPDIR/usr/share/applications" "$APPDIR/usr/share/icons/hicolor/256x256/apps"
# Install Python + deps into AppDir
python3 -m venv "$APPDIR/usr/python"
"$APPDIR/usr/python/bin/pip" install PySide6 --quiet
# Copy app
cp ../../src/zupt_gui.py "$APPDIR/usr/share/zupt-gui/"
cp ../../assets/zupt.png "$APPDIR/usr/share/icons/hicolor/256x256/apps/zupt-gui.png" 2>/dev/null || true
# Create launcher
cat > "$APPDIR/AppRun" << 'APPRUN'
#!/bin/bash
HERE="$(dirname "$(readlink -f "$0")")"
export PATH="$HERE/usr/bin:$HERE/usr/python/bin:$PATH"
exec python3 "$HERE/usr/share/zupt-gui/zupt_gui.py" "$@"
APPRUN
chmod +x "$APPDIR/AppRun"
# Desktop file
cat > "$APPDIR/${APP}.desktop" << DESKTOP
[Desktop Entry]
Type=Application
Name=Zupt GUI
Comment=Post-Quantum Backup Utility
Exec=zupt-gui
Icon=zupt-gui
Categories=Utility;Archiving;Security;
Terminal=false
DESKTOP
# Build AppImage
if command -v appimagetool >/dev/null; then
ARCH=x86_64 appimagetool "$APPDIR" "${APP}-${VERSION}-x86_64.AppImage"
echo "Built: ${APP}-${VERSION}-x86_64.AppImage"
else
echo "appimagetool not found. Install from https://github.com/AppImage/AppImageKit"
echo "AppDir ready at: $APPDIR/"
fi
# Compatibility entry point for the canonical source-only GUI builder.
set -Eeuo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../../.." && pwd -P)
exec "$repo_root/packaging/build-gui-appimage.sh" "$@"

View file

@ -1,94 +1,7 @@
#!/bin/bash
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
# Build zupt-gui .deb package
set -e
cd "$(dirname "$0")/.."
VERSION="${VERSION:-2.2.1}"
ARCH="all"
PKG="zupt-gui_${VERSION}_${ARCH}"
ROOT="/tmp/$PKG"
rm -rf "$ROOT"
mkdir -p "$ROOT/DEBIAN" \
"$ROOT/usr/bin" \
"$ROOT/usr/lib/python3/dist-packages" \
"$ROOT/usr/share/applications" \
"$ROOT/usr/share/icons/hicolor/256x256/apps" \
"$ROOT/usr/share/doc/zupt-gui"
# Python module
install -m 644 src/zupt_gui.py "$ROOT/usr/lib/python3/dist-packages/zupt_gui.py"
# Launcher
cat > "$ROOT/usr/bin/zupt-gui" <<'LAUNCH'
#!/usr/bin/env python3
import sys
sys.path.insert(0, "/usr/lib/python3/dist-packages")
from zupt_gui import main
sys.exit(main())
LAUNCH
chmod +x "$ROOT/usr/bin/zupt-gui"
# Desktop file
install -m 644 packaging/zupt-gui.desktop "$ROOT/usr/share/applications/" 2>/dev/null || cat > "$ROOT/usr/share/applications/zupt-gui.desktop" <<'DESK'
[Desktop Entry]
Name=Zupt GUI
GenericName=Post-Quantum Backup Utility
Comment=Compress and encrypt files with hybrid PQ crypto
Exec=zupt-gui
Terminal=false
Type=Application
Categories=Utility;Archiving;Security;
Icon=zupt-gui
DESK
# Icon (use a real one if assets exist)
if [ -f assets/zupt-256.png ]; then
install -m 644 assets/zupt-256.png "$ROOT/usr/share/icons/hicolor/256x256/apps/zupt-gui.png"
elif [ -d ../assets ] && [ -f ../assets/zupt-256.png ]; then
install -m 644 ../assets/zupt-256.png "$ROOT/usr/share/icons/hicolor/256x256/apps/zupt-gui.png"
else
# 1×1 placeholder
printf '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\rIDATx\x9cc\xfc\xcf\xc0\x00\x00\x00\x05\x00\x01\xa5\xf6E@\x00\x00\x00\x00IEND\xaeB`\x82' > "$ROOT/usr/share/icons/hicolor/256x256/apps/zupt-gui.png"
fi
# Docs
install -m 644 README.md "$ROOT/usr/share/doc/zupt-gui/"
gzip -9n -c ../CHANGELOG.md > "$ROOT/usr/share/doc/zupt-gui/changelog.gz" 2>/dev/null || true
cat > "$ROOT/usr/share/doc/zupt-gui/copyright" <<'COPYRIGHT'
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: zupt-gui
Upstream-Contact: Cristian Cezar Moisés <zupt@riseup.net>
Source: https://git.securityops.co/cristiancmoises/zupt
Files: *
Copyright: 2025-2026 Cristian Cezar Moisés
License: AGPL-3.0+
COPYRIGHT
INSTALLED_SIZE=$(du -sk "$ROOT" | cut -f1)
cat > "$ROOT/DEBIAN/control" <<EOF
Package: zupt-gui
Version: $VERSION
Section: utils
Priority: optional
Architecture: $ARCH
Depends: python3 (>= 3.9), python3-pyside6, zupt (>= 2.2.0)
Maintainer: Cristian Cezar Moisés <zupt@riseup.net>
Installed-Size: $INSTALLED_SIZE
Homepage: https://git.securityops.co/cristiancmoises/zupt
Description: Zupt GUI — Post-Quantum Backup Utility
Cross-platform graphical interface for the zupt backup compression
utility with post-quantum hybrid encryption (ML-KEM-768 + X25519).
.
v2.2+ uses libzuptsdk under the hood for HKDF-SHA3 hybrid combiner,
32-byte key commitment, HPKE binding (RFC 9180), and anti-fault
double-decapsulation. Supports legacy archives via auto-detection.
EOF
dpkg-deb --build --root-owner-group "$ROOT" "/tmp/$PKG.deb"
echo "Built: /tmp/$PKG.deb"
dpkg-deb --info "/tmp/$PKG.deb" | head -12
# Compatibility entry point for the canonical source-only GUI builder.
set -Eeuo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd -P)
exec "$repo_root/packaging/build-gui-deb.sh" "$@"

View file

@ -1,13 +1,12 @@
Package: zupt-gui
Version: 1.0.0
Version: 5.2.8
Section: utils
Priority: optional
Architecture: all
Depends: python3 (>= 3.9), python3-pyside6, zupt (>= 2.1.6)
Maintainer: Cristian Cezar Moises <cristian@zupt.dev>
Depends: python3 (>= 3.9), python3-pyqt6 | python3-pyside6.qtwidgets, zupt (= 5.2.8)
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
Homepage: https://github.com/cristiancmoises/zupt
Description: Zupt GUI — Post-Quantum Backup Utility
Cross-platform graphical interface for the zupt backup compression
utility with post-quantum hybrid encryption (ML-KEM-768 + X25519),
hardware-adaptive codecs, block-level deduplication, and full-disk
backup/restore support.
Description: Qt graphical interface for the ZUPT backup utility
The GUI creates, inspects, verifies, and extracts .zupt archives through the
separately packaged ZUPT command. Native post-quantum modes are available
in the baseline build; SDK and PQ-box controls follow CLI capability detection.

View file

@ -1,7 +1,8 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
[Nemo Action]
Name=Compress with Zupt
Comment=Create encrypted .zupt archive
Name=Compress with ZUPT
Comment=Create a .zupt archive with ZUPT GUI
Exec=zupt-gui --compress %F
Icon-Name=package-x-generic
Icon-Name=zupt-gui
Selection=Any
Extensions=any;

View file

@ -1,7 +1,8 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
[Nemo Action]
Name=Extract with Zupt
Comment=Decrypt and extract .zupt archive
Name=Extract with ZUPT
Comment=Extract a .zupt archive with ZUPT GUI
Exec=zupt-gui --extract %F
Icon-Name=package-x-generic
Icon-Name=zupt-gui
Selection=S
Extensions=zupt;

View file

@ -1,39 +1,42 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2026 Cristian Cezar Moisés
# Offline build manifest for the integrated source tree. The Qt/Python base and
# SDK are resolved by Flatpak before the build; no command below uses pip or
# downloads project dependencies.
app-id: dev.zupt.gui
runtime: org.freedesktop.Platform
runtime-version: '24.08'
sdk: org.freedesktop.Sdk
runtime: org.kde.Platform
runtime-version: '6.8'
sdk: org.kde.Sdk
base: io.qt.PySide.BaseApp
base-version: '6.8'
command: zupt-gui
finish-args:
- --share=ipc
- --socket=x11
- --socket=fallback-x11
- --socket=wayland
- --filesystem=home
- --device=all # For disk backup (block devices)
modules:
- name: python3-pyside6
buildsystem: simple
build-commands:
- pip3 install --prefix=/app PySide6
- name: zupt
buildsystem: simple
build-commands:
- make
- install -Dm755 zupt /app/bin/zupt
sources:
- type: git
url: https://git.securityops.co/cristiancmoises/zupt
tag: v2.1.6
- name: zupt-gui
buildsystem: simple
build-commands:
- install -Dm755 src/zupt_gui.py /app/bin/zupt-gui
- install -Dm644 packaging/zupt-gui.desktop /app/share/applications/dev.zupt.gui.desktop
- make -j${FLATPAK_BUILDER_N_JOBS} WITH_SDK=0 WITH_PQBOX=0
- make WITH_SDK=0 WITH_PQBOX=0 check
- make PREFIX=/app WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 install
- install -Dm644 gui/src/zupt_gui.py /app/bin/zupt_gui.py
- install -Dm755 packaging/portable/zupt-gui.sh /app/bin/zupt-gui
- install -Dm644 gui/packaging/zupt-gui.desktop /app/share/applications/dev.zupt.gui.desktop
- sed -i 's/^Icon=.*/Icon=dev.zupt.gui/' /app/share/applications/dev.zupt.gui.desktop
- install -Dm644 gui/assets/zupt-icon.png /app/share/icons/hicolor/256x256/apps/dev.zupt.gui.png
- install -Dm644 doc/zupt-gui.1 /app/share/man/man1/zupt-gui.1
- install -d /app/share/licenses/zupt
- install -m 0644 LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE THIRD-PARTY-NOTICES.md /app/share/licenses/zupt/
- install -d /app/share/licenses/zupt-gui
- install -m 0644 LICENSE-AGPL-3.0 /app/share/licenses/zupt-gui/LICENSE-AGPL-3.0
- install -m 0644 gui/LICENSE-GUI /app/share/licenses/zupt-gui/LICENSE-GUI
- install -m 0644 gui/assets/README.md /app/share/licenses/zupt-gui/ASSET-PROVENANCE.md
sources:
- type: dir
path: .
path: ../../..

View file

@ -1,92 +1,102 @@
@echo off
REM ══════════════════════════════════════════════════
REM Zupt GUI — Windows Build Script
REM Creates: ZuptGUI-2.1.6-Setup.exe
REM
REM Prerequisites:
REM 1. Python 3.9+ (python.org)
REM 2. NSIS 3.x (nsis.sourceforge.io)
REM 3. zupt.exe (compiled zupt CLI binary for Windows)
REM
REM Usage:
REM cd packaging\windows
REM build-windows.bat
REM ══════════════════════════════════════════════════
setlocal
rem SPDX-License-Identifier: AGPL-3.0-or-later
rem Build the ZUPT GUI installer without downloading dependencies.
rem
rem Prerequisites must already be installed: Python 3.9+, PySide6, PyInstaller,
rem Inno Setup 6, and a source-built/tested zupt.exe. The CLI path can be
rem selected with ZUPT_CLI_EXE. Final output goes to ZUPT_DIST_DIR,
rem which defaults to a directory below %%TEMP%% (outside the Git checkout).
rem ZUPT_WINDOWS_RUNTIME_NOTICES_DIR is mandatory and must contain the
rem license/notices for the exact Python, PyInstaller, Qt and PySide/PyQt
rem runtime files embedded by this local build.
echo.
echo Zupt GUI — Windows Build
echo ════════════════════════
echo.
setlocal EnableExtensions
for %%I in ("%~dp0\..\..\..") do set "REPO_ROOT=%%~fI"
set "VERSION=%~1"
if not defined VERSION set "VERSION=5.2.8"
if not defined ZUPT_DIST_DIR set "ZUPT_DIST_DIR=%TEMP%\zupt-release"
if not defined ZUPT_CLI_EXE set "ZUPT_CLI_EXE=%REPO_ROOT%\zupt.exe"
set "WORK=%TEMP%\zupt-gui-build-%RANDOM%-%RANDOM%"
set "RC=1"
REM ── Step 1: Install Python deps ──
echo [1/4] Installing dependencies...
pip install PySide6 pyinstaller --quiet --upgrade
if errorlevel 1 (
echo ERROR: pip install failed. Is Python in PATH?
pause
exit /b 1
where pyinstaller >nul 2>nul || (
echo ERROR: PyInstaller is required and is not downloaded by this script.>&2
goto :cleanup
)
where ISCC.exe >nul 2>nul || (
echo ERROR: Inno Setup 6 ISCC.exe is required.>&2
goto :cleanup
)
if not exist "%ZUPT_CLI_EXE%" (
echo ERROR: source-built CLI not found: %ZUPT_CLI_EXE%>&2
goto :cleanup
)
if not defined ZUPT_WINDOWS_RUNTIME_NOTICES_DIR (
echo ERROR: set ZUPT_WINDOWS_RUNTIME_NOTICES_DIR for the exact bundled runtime.>&2
goto :cleanup
)
if not exist "%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%\MANIFEST.txt" (
echo ERROR: runtime notice directory must contain MANIFEST.txt.>&2
goto :cleanup
)
for %%I in ("%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%\MANIFEST.txt") do if %%~zI LEQ 0 (
echo ERROR: runtime notice MANIFEST.txt must not be empty.>&2
goto :cleanup
)
for %%N in (PYTHON-NOTICE.txt PYINSTALLER-NOTICE.txt QT-NOTICE.txt) do (
if not exist "%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%\%%N" (
echo ERROR: runtime notice directory is missing %%N.>&2
goto :cleanup
)
for %%I in ("%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%\%%N") do if %%~zI LEQ 0 (
echo ERROR: runtime notice %%N must not be empty.>&2
goto :cleanup
)
)
set "QT_BINDING_NOTICE_FOUND="
for %%N in (PYSIDE6-NOTICE.txt PYQT6-NOTICE.txt) do if exist "%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%\%%N" (
for %%I in ("%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%\%%N") do if %%~zI GTR 0 set "QT_BINDING_NOTICE_FOUND=1"
)
if not defined QT_BINDING_NOTICE_FOUND (
echo ERROR: runtime notices need non-empty PYSIDE6-NOTICE.txt or PYQT6-NOTICE.txt.>&2
goto :cleanup
)
REM ── Step 2: Build .exe with PyInstaller ──
echo [2/4] Building ZuptGUI.exe...
if exist dist rmdir /s /q dist
if exist build rmdir /s /q build
mkdir "%WORK%" || goto :cleanup
if not exist "%ZUPT_DIST_DIR%" mkdir "%ZUPT_DIST_DIR%" || goto :cleanup
pyinstaller --onefile --windowed ^
--name "ZuptGUI" ^
--icon "..\..\assets\zupt.ico" ^
--add-data "..\..\assets\zupt.ico;assets" ^
--add-data "..\..\assets\zupt.png;assets" ^
"..\..\src\zupt_gui.py"
if not exist "dist\ZuptGUI.exe" (
echo ERROR: PyInstaller build failed.
pause
exit /b 1
"%ZUPT_CLI_EXE%" version >"%WORK%\cli-version.txt" 2>&1 || goto :cleanup
findstr /b /c:"zupt %VERSION%" "%WORK%\cli-version.txt" >nul || (
echo ERROR: CLI version does not match %VERSION%.>&2
goto :cleanup
)
echo Built: dist\ZuptGUI.exe
"%ZUPT_CLI_EXE%" help >nul 2>&1 || goto :cleanup
REM ── Step 3: Check for zupt.exe ──
echo [3/4] Checking for zupt.exe...
if not exist "zupt.exe" (
echo.
echo WARNING: zupt.exe not found in this directory.
echo The installer needs zupt.exe to bundle the CLI tool.
echo Options:
echo a) Copy zupt.exe here and re-run this script
echo b) Build zupt from source with MSYS2/MinGW:
echo pacman -S mingw-w64-x86_64-gcc make
echo cd zupt-2.1.6 ^&^& make
echo cp zupt.exe packaging/windows/
echo.
)
pyinstaller --noconfirm --clean --onefile --windowed ^
--name zupt-gui ^
--icon "%REPO_ROOT%\gui\assets\zupt.ico" ^
--add-data "%REPO_ROOT%\gui\assets\zupt.ico;assets" ^
--add-data "%REPO_ROOT%\gui\assets\zupt-icon.png;assets" ^
--distpath "%WORK%\dist" ^
--workpath "%WORK%\build" ^
--specpath "%WORK%" ^
"%REPO_ROOT%\gui\src\zupt_gui.py" || goto :cleanup
REM ── Step 4: Build NSIS installer ──
echo [4/4] Building installer...
where makensis >nul 2>&1
if errorlevel 1 (
echo.
echo NSIS not found. Install from: https://nsis.sourceforge.io
echo Then run: makensis zupt-installer.nsi
echo.
echo Standalone exe ready at: dist\ZuptGUI.exe
pause
exit /b 0
)
set "GUI_EXE=%WORK%\dist\zupt-gui.exe"
if not exist "%GUI_EXE%" goto :cleanup
set "ZUPT_BIN=%ZUPT_CLI_EXE%"
"%GUI_EXE%" --version >"%WORK%\gui-version.txt" 2>&1 || goto :cleanup
findstr /b /c:"zupt-gui %VERSION%" "%WORK%\gui-version.txt" >nul || goto :cleanup
makensis zupt-installer.nsi
if errorlevel 1 (
echo ERROR: NSIS build failed.
pause
exit /b 1
)
ISCC.exe "/DAppVersion=%VERSION%" "/DGuiExecutable=%GUI_EXE%" ^
"/DCliExecutable=%ZUPT_CLI_EXE%" "/DBuildOutputDir=%ZUPT_DIST_DIR%" ^
"/DRuntimeNoticesDir=%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%" ^
"%REPO_ROOT%\packaging\windows\zupt-gui.iss" || goto :cleanup
echo.
echo ════════════════════════════════════════════
echo Build complete!
echo Standalone: dist\ZuptGUI.exe
echo Installer: ZuptGUI-2.1.6-Setup.exe
echo ════════════════════════════════════════════
echo.
pause
if not exist "%ZUPT_DIST_DIR%\ZUPT-Setup-%VERSION%.exe" goto :cleanup
echo PASS: built %ZUPT_DIST_DIR%\ZUPT-Setup-%VERSION%.exe
set "RC=0"
:cleanup
if exist "%WORK%" rmdir /s /q "%WORK%"
endlocal & exit /b %RC%

View file

@ -1,132 +0,0 @@
; Zupt GUI NSIS Installer Script
; Builds: ZuptGUI-Setup.exe
;
; Prerequisites on the build machine:
; 1. NSIS 3.x installed (https://nsis.sourceforge.io)
; 2. Run build-windows.bat first to create dist/ZuptGUI.exe
; 3. Place zupt.exe in this directory
; 4. Then: makensis zupt-installer.nsi
!include "MUI2.nsh"
!include "FileFunc.nsh"
; Config
!define APPNAME "Zupt"
!define APPVERSION "2.1.6"
!define GUIVERSION "1.0.0"
!define PUBLISHER "Cristian Cezar Moises"
!define HELPURL "https://github.com/cristiancmoises/zupt"
!define EXE "ZuptGUI.exe"
!define CLI "zupt.exe"
Name "${APPNAME} ${APPVERSION}"
OutFile "ZuptGUI-${APPVERSION}-Setup.exe"
InstallDir "$PROGRAMFILES\${APPNAME}"
InstallDirRegKey HKLM "Software\${APPNAME}" "InstallDir"
RequestExecutionLevel admin
; UI
!define MUI_ICON "..\..\assets\zupt.ico"
!define MUI_UNICON "..\..\assets\zupt.ico"
!define MUI_ABORTWARNING
!define MUI_WELCOMEPAGE_TITLE "Install ${APPNAME} ${APPVERSION}"
!define MUI_WELCOMEPAGE_TEXT "Post-quantum backup compression with ML-KEM-768 + X25519 hybrid encryption.$\r$\n$\r$\nThis will install the Zupt GUI and CLI tools."
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "..\..\LICENSE"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
; Install
Section "Install"
SetOutPath $INSTDIR
; Copy files
File "dist\${EXE}"
File "${CLI}"
File "..\..\assets\zupt.ico"
File "..\..\LICENSE"
File "..\..\README.md"
; Write uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"
; Start Menu
CreateDirectory "$SMPROGRAMS\${APPNAME}"
CreateShortcut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\${EXE}" "" "$INSTDIR\zupt.ico"
CreateShortcut "$SMPROGRAMS\${APPNAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
; Desktop shortcut
CreateShortcut "$DESKTOP\${APPNAME}.lnk" "$INSTDIR\${EXE}" "" "$INSTDIR\zupt.ico"
; Add to PATH (so zupt.exe is available system-wide)
EnVar::AddValue "PATH" "$INSTDIR"
; Register .zupt file association
WriteRegStr HKCR ".zupt" "" "ZuptArchive"
WriteRegStr HKCR "ZuptArchive" "" "Zupt Archive"
WriteRegStr HKCR "ZuptArchive\DefaultIcon" "" "$INSTDIR\zupt.ico"
WriteRegStr HKCR "ZuptArchive\shell\open\command" "" '"$INSTDIR\${EXE}" --extract "%1"'
WriteRegStr HKCR "ZuptArchive\shell\verify\command" "" '"$INSTDIR\${CLI}" test "%1"'
WriteRegStr HKCR "ZuptArchive\shell\verify" "" "Verify Integrity"
; Right-click "Compress with Zupt" on any file
WriteRegStr HKCR "*\shell\ZuptCompress" "" "Compress with Zupt"
WriteRegStr HKCR "*\shell\ZuptCompress\Icon" "" "$INSTDIR\zupt.ico"
WriteRegStr HKCR "*\shell\ZuptCompress\command" "" '"$INSTDIR\${EXE}" --compress "%1"'
; Right-click on directories
WriteRegStr HKCR "Directory\shell\ZuptCompress" "" "Compress with Zupt"
WriteRegStr HKCR "Directory\shell\ZuptCompress\Icon" "" "$INSTDIR\zupt.ico"
WriteRegStr HKCR "Directory\shell\ZuptCompress\command" "" '"$INSTDIR\${EXE}" --compress "%1"'
; Add/Remove Programs entry
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayName" "${APPNAME} — Post-Quantum Backup"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "UninstallString" "$INSTDIR\Uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayIcon" "$INSTDIR\zupt.ico"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "Publisher" "${PUBLISHER}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayVersion" "${APPVERSION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "URLInfoAbout" "${HELPURL}"
; Calculate installed size
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "EstimatedSize" "$0"
SectionEnd
; Uninstall
Section "Uninstall"
; Remove files
Delete "$INSTDIR\${EXE}"
Delete "$INSTDIR\${CLI}"
Delete "$INSTDIR\zupt.ico"
Delete "$INSTDIR\LICENSE"
Delete "$INSTDIR\README.md"
Delete "$INSTDIR\Uninstall.exe"
RMDir "$INSTDIR"
; Remove shortcuts
Delete "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk"
Delete "$SMPROGRAMS\${APPNAME}\Uninstall.lnk"
RMDir "$SMPROGRAMS\${APPNAME}"
Delete "$DESKTOP\${APPNAME}.lnk"
; Remove from PATH
EnVar::DeleteValue "PATH" "$INSTDIR"
; Remove file associations
DeleteRegKey HKCR ".zupt"
DeleteRegKey HKCR "ZuptArchive"
DeleteRegKey HKCR "*\shell\ZuptCompress"
DeleteRegKey HKCR "Directory\shell\ZuptCompress"
; Remove Add/Remove Programs entry
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}"
DeleteRegKey HKLM "Software\${APPNAME}"
SectionEnd

View file

@ -1,12 +1,13 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
[Desktop Entry]
Type=Application
Name=Zupt GUI
GenericName=Post-Quantum Backup
Comment=Compress, encrypt, and backup with quantum-resistant cryptography
Exec=zupt-gui
Name=ZUPT GUI
GenericName=Backup and Compression Utility
Comment=Create, inspect, verify, and extract ZUPT archives
Exec=zupt-gui %f
Icon=zupt-gui
Categories=Utility;Archiving;Security;
Keywords=backup;compress;encrypt;quantum;zupt;
Categories=Utility;Archiving;Compression;
Keywords=backup;archive;compression;encryption;post-quantum;zupt;
Terminal=false
StartupNotify=true
MimeType=application/x-zupt;

View file

@ -1,34 +0,0 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
from setuptools import setup, find_packages
setup(
name="zupt-gui",
version="1.1.1",
description="Zupt GUI — Cross-Platform Post-Quantum Backup Utility",
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
author="Cristian Cezar Moisés",
url="https://git.securityops.co/cristiancmoises/zupt",
license="AGPL-3.0-or-later",
packages=find_packages(where="src"),
package_dir={"": "src"},
py_modules=["zupt_gui"],
python_requires=">=3.9",
install_requires=["PySide6>=6.5"],
entry_points={
"console_scripts": ["zupt-gui=zupt_gui:main"],
"gui_scripts": ["zupt-gui=zupt_gui:main"],
},
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: X11 Applications :: Qt",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Topic :: Security :: Cryptography",
"Topic :: System :: Archiving :: Compression",
],
)

File diff suppressed because it is too large Load diff

View file

@ -1,46 +1,31 @@
#!/bin/bash
set -e
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
DIR="$(cd "$(dirname "$0")" && pwd)"
VENV="$DIR/.venv"
PY="$VENV/bin/python3"
PIP="$VENV/bin/pip"
GUI="$DIR/src/zupt_gui.py"
# Source-tree launcher for ZUPT GUI.
# It performs no package installation and never downloads dependencies.
set -Eeuo pipefail
# ─── System deps (Qt xcb needs these on Debian/Mint/Ubuntu) ───
NEED_APT=0
for pkg in libxcb-cursor0 libxcb-xinerama0 libxkbcommon-x11-0 libegl1 python3-full; do
dpkg -s "$pkg" >/dev/null 2>&1 || NEED_APT=1
done
if [ "$NEED_APT" -eq 1 ]; then
echo "Installing system dependencies..."
sudo apt-get update -qq
sudo apt-get install -y python3-full python3-venv \
libxcb-cursor0 libxcb-xinerama0 libxkbcommon-x11-0 libegl1 2>/dev/null
fi
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
gui=$script_dir/src/zupt_gui.py
[[ -f $gui ]] || {
printf 'zupt-gui: GUI source is missing: %s\n' "$gui" >&2
exit 1
}
# ─── Venv ───
if [ ! -x "$PY" ]; then
rm -rf "$VENV"
python3 -m venv "$VENV"
fi
if ! "$PY" -c "import PySide6" 2>/dev/null; then
echo "Installing PySide6..."
"$PIP" install --upgrade pip -q 2>/dev/null
"$PIP" install PySide6 -q
fi
# ─── Find zupt — local build FIRST, then system ───
if [ -z "$ZUPT_BIN" ]; then
# Check project tree first (gui/ is inside zupt-2.1.6/)
for p in "$DIR/../zupt" "$DIR/../../zupt" "$DIR/zupt"; do
[ -x "$p" ] && export ZUPT_BIN="$(readlink -f "$p")" && break
done
# Then system PATH
if [ -z "$ZUPT_BIN" ]; then
p="$(command -v zupt 2>/dev/null)"
[ -x "$p" ] && export ZUPT_BIN="$p"
if [[ -z ${ZUPT_BIN:-} ]]; then
if [[ -n ${VAPTVUPT_BIN:-} ]]; then
export ZUPT_BIN=$VAPTVUPT_BIN
elif [[ -x $script_dir/../zupt ]]; then
export ZUPT_BIN=$script_dir/../zupt
elif command -v zupt >/dev/null 2>&1; then
ZUPT_BIN=$(command -v zupt)
export ZUPT_BIN
elif [[ -x $script_dir/../vaptvupt ]]; then
export ZUPT_BIN=$script_dir/../vaptvupt
elif command -v vaptvupt >/dev/null 2>&1; then
ZUPT_BIN=$(command -v vaptvupt)
export ZUPT_BIN
fi
fi
exec "$PY" "$GUI" "$@"
exec python3 "$gui" "$@"

View file

@ -1,9 +1,9 @@
/*
* VaptVupt Zupt Integration API
* VaptVupt VaptVupt Integration API
* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright 2026 Cristian.
*
* ZUPT-COMPAT: This is the API that Zupt calls. It wraps the internal
* EMBED-COMPAT: This is the API that a host application calls. It wraps the internal
* VaptVupt API with sensible defaults for backup workloads:
* - Checksum always enabled (data integrity is critical for backups)
* - Adaptive window selection (auto-detect optimal wlog per file)

View file

@ -1,5 +1,5 @@
/*
* Zupt Backup-oriented compression with AES-256 encryption
* ZUPT Backup-oriented compression with AES-256 encryption
* Copyright (c) 2026 Cristian Cezar Moisés
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@ -15,12 +15,79 @@
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#include <io.h>
#include <wchar.h>
#define ZUPT_PATH_SEP '\\'
#define zupt_mkdir(p) _mkdir(p)
static inline wchar_t *zupt_win_utf8_to_wide_alloc(const char *text) {
if (!text) return NULL;
int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
text, -1, NULL, 0);
if (length <= 0) return NULL;
wchar_t *wide = (wchar_t *)malloc((size_t)length * sizeof(wchar_t));
if (!wide || !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
text, -1, wide, length)) {
free(wide);
return NULL;
}
return wide;
}
static inline char *zupt_win_wide_to_utf8_alloc(const wchar_t *text) {
if (!text) return NULL;
int length = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
text, -1, NULL, 0, NULL, NULL);
if (length <= 0) return NULL;
char *utf8 = (char *)malloc((size_t)length);
if (!utf8 || !WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
text, -1, utf8, length, NULL, NULL)) {
free(utf8);
return NULL;
}
return utf8;
}
static inline FILE *zupt_win_fopen_utf8(const char *path, const char *mode) {
wchar_t *wide_path = zupt_win_utf8_to_wide_alloc(path);
wchar_t *wide_mode = zupt_win_utf8_to_wide_alloc(mode);
if (!wide_path || !wide_mode) {
free(wide_path);
free(wide_mode);
return NULL;
}
FILE *stream = _wfopen(wide_path, wide_mode);
free(wide_path);
free(wide_mode);
return stream;
}
static inline DWORD zupt_win_get_attributes_utf8(const char *path) {
wchar_t *wide = zupt_win_utf8_to_wide_alloc(path);
if (!wide) return INVALID_FILE_ATTRIBUTES;
DWORD attributes = GetFileAttributesW(wide);
free(wide);
return attributes;
}
static inline int zupt_win_mkdir_utf8(const char *path) {
wchar_t *wide = zupt_win_utf8_to_wide_alloc(path);
if (!wide) return -1;
int result = _wmkdir(wide);
free(wide);
return result;
}
/* Project path strings are UTF-8 on every platform. Call this wrapper
* explicitly; never rewrite the C library's fopen in consumer code. */
static inline FILE *zupt_fopen_path(const char *path, const char *mode) {
return zupt_win_fopen_utf8(path, mode);
}
#define zupt_mkdir(p) zupt_win_mkdir_utf8(p)
#else
#include <sys/stat.h>
#include <sys/types.h>
@ -28,32 +95,38 @@
#include <unistd.h>
#define ZUPT_PATH_SEP '/'
#define zupt_mkdir(p) mkdir(p, 0755)
static inline FILE *zupt_fopen_path(const char *path, const char *mode) {
return fopen(path, mode);
}
#endif
/* ─── Product identity ─────────────────────────────────────────────
*
* v3.0.0 (INPI Brasil trademark rename):
* - Product name is now "VaptVupt" (was "Zupt"). The earlier name
* conflicted with a software trademark already registered at INPI
* Brasil under "Zupt".
* - File extension stays `.zupt` for archive-format continuity:
* v1.0v2.4.x archives remain readable, the magic bytes
* `\x5A\x55\x50\x54\x1A\x00` ("ZUPT" + sub-version) are unchanged.
* - C identifier prefix stays `zupt_` / `ZUPT_` for ABI continuity
* with libzuptsdk and existing callers. Only user-visible strings
* (binary name, banner, help text, package names) change.
* - The binary is now `vaptvupt`. Distro packages may ship a
* compatibility symlink `zupt -> vaptvupt` for one major version.
* v5.2.2 (product identity restored):
* - The public product and primary command are again "ZUPT" and `zupt`.
* - On-disk compatibility is deliberately unchanged: magic remains
* "ZUPT", the archive extension remains .zupt, and format version
* remains 1.6.
* - Internal zupt_* symbols, SDK identifiers, codec IDs, and the bundled
* VaptVupt codec ABI remain unchanged.
* - Distributors may offer `vaptvupt -> zupt` only as an explicit
* compatibility alias for scripts written for releases 3.0.0--5.2.1.
*/
#define ZUPT_PRODUCT_NAME "VaptVupt"
#define ZUPT_PRODUCT_NAME_LC "vaptvupt" /* lowercase: binary name */
#define ZUPT_PRODUCT_NAME "ZUPT"
#define ZUPT_PRODUCT_NAME_LC "zupt" /* lowercase: binary name */
#define ZUPT_PRODUCT_EXTENSION ".zupt" /* on-disk archive extension (kept stable) */
#define ZUPT_PRODUCT_TAGLINE "Post-quantum backup compression"
#define ZUPT_VERSION_STRING "4.1.0"
/* v5.2.3 corrects release packaging and CI; archive format remains v1.6. */
/* v5.2.4 makes package metadata checks CRLF-safe; format remains v1.6. */
/* v5.2.5 corrects the OBS service harness cwd; format remains v1.6. */
/* v5.2.6 corrects native release-gate portability; format remains v1.6. */
/* v5.2.7 corrects native test-harness portability; format remains v1.6. */
/* v5.2.8 hardens three path-race boundaries; format remains v1.6. */
#define ZUPT_VERSION_STRING "5.2.8"
/* Vendored codec release (upstream tag) — single source for display strings.
* The codec's own VV_VERSION_* is its internal API version, not the release. */
#define ZUPT_CODEC_RELEASE "2.60.4"
#define ZUPT_CODEC_RELEASE "2.65.3"
#define ZUPT_FORMAT_MAJOR 1
#define ZUPT_FORMAT_MINOR 6
@ -67,10 +140,13 @@
* to keep the field stable across format-version transitions. Both bytes are
* structurally validated by read_footer().
*
* Read path falls back to v1.4 layout (no trailer) when the footer magic is
* found at EOF-32 instead of EOF-64. */
* The reader can identify a legacy v1.4 layout at EOF-32, but refuses it by
* default because the missing trailer is indistinguishable from an integrity
* downgrade. Trusted old archives require --allow-legacy-no-ait. */
#define ZUPT_AIT_SIZE 32
#define ZUPT_AIT_MAC_INPUT_LEN (sizeof(zupt_archive_header_t) + 24)
#define ZUPT_ARCHIVE_HEADER_SIZE 64u
#define ZUPT_FOOTER_SIZE 32u
#define ZUPT_AIT_MAC_INPUT_LEN (ZUPT_ARCHIVE_HEADER_SIZE + 24u)
#define ZUPT_MAGIC_0 0x5A
#define ZUPT_MAGIC_1 0x55
@ -83,6 +159,11 @@
#define ZUPT_MAX_PATH 4096
#define ZUPT_MAX_FILES 2000000
/* A decoded index entry contains a fixed-size path buffer. Cap aggregate
* allocation independently of the wire count so a compact malicious index
* cannot request several gigabytes of zeroed memory. */
#define ZUPT_MAX_INDEX_ALLOC_BYTES (256u * 1024u * 1024u)
#define ZUPT_MIN_INDEX_ENTRY_BYTES 47u
#define ZUPT_DEFAULT_BLOCK_SZ (4 * 1024 * 1024)
#define ZUPT_MIN_BLOCK_SZ (64 * 1024)
#define ZUPT_MAX_BLOCK_SZ (256 * 1024 * 1024)
@ -97,13 +178,16 @@
#define ZUPT_FLAG_DEDUP (1u << 7) /* Block-level deduplication enabled */
#define ZUPT_FLAG_AAD_SEQ (1u << 8) /* MAC binds block_seq as AAD (anti-reorder) */
#define ZUPT_FLAG_AAD_PREFACE (1u << 9) /* v1.6: MAC also binds per-block frame preface (F-09) */
#define ZUPT_FLAG_AUTH_DEDUP_REFS (1u << 10) /* Dedup offsets carry per-block authentication */
#define ZUPT_FLAG_DISK_CONTENT_HASH (1u << 11) /* Disk index hashes restored bytes */
/* Encryption types (stored in encryption header block) */
#define ZUPT_ENC_PBKDF2 0x01 /* Password-based: PBKDF2 → AES-256-CTR + HMAC */
#define ZUPT_ENC_PQ_HYBRID 0x02 /* ML-KEM-768 + X25519 hybrid KEM (legacy XOR+SHA3) */
#define ZUPT_ENC_PQ_SDK_V2 0x03 /* libzuptsdk v2 header: HKDF combiner + commitment + HPKE binding */
#define ZUPT_ENC_PW_ARGON2 0x04 /* Password-based via libzuptsdk: Argon2id + XChaCha20-Poly1305 */
#define ZUPT_ENC_PQ_SDK_V2 0x03 /* libvuptsdk v2 header: HKDF combiner + commitment + HPKE binding */
#define ZUPT_ENC_PW_ARGON2 0x04 /* Password-based via libvuptsdk: Argon2id + XChaCha20-Poly1305 */
#define ZUPT_ENC_PQ_BOX_V1 0x05 /* libpqvaptvupt sealed box: HKDF-SHA256 domain-separated combiner */
#define ZUPT_ENC_PQ_ONLY 0x06 /* Full post-quantum: ML-KEM-768 only (no X25519), SHA3-512 KDF (v4.2.0) */
/* Argon2id KDF profile descriptor (v3.4.0).
*
@ -122,12 +206,12 @@
* descriptor is covered by the archive-integrity trailer (F-08), so it
* cannot be stripped or forged without failing authentication.
*
* Profile 0 (implicit, absent byte) == the historical libzuptsdk
* Profile 0 (implicit, absent byte) == the historical libvuptsdk
* "MODERATE" Argon2id preset reached via zuptsdk_easy_derive_key.
* Profile 1 is the same derivation with the descriptor made explicit so
* future profiles (should the cost change) get distinct IDs. */
#define ZUPT_ARGON2_PROFILE_LEGACY 0x00 /* implicit: pre-3.4.0, no descriptor byte */
#define ZUPT_ARGON2_PROFILE_MODERATE 0x01 /* explicit: libzuptsdk MODERATE preset */
#define ZUPT_ARGON2_PROFILE_MODERATE 0x01 /* explicit: libvuptsdk MODERATE preset */
#define ZUPT_ARGON2_HDR_LEN_V1 33 /* [type|salt16|nonce16] */
#define ZUPT_ARGON2_HDR_LEN_V2 34 /* + [profile1] */
@ -135,7 +219,7 @@
#define ZUPT_BLOCK_DATA 0x00
#define ZUPT_BLOCK_INDEX 0x02
#define ZUPT_BLOCK_ENC_HEADER 0x03
#define ZUPT_BLOCK_DEDUP_REF 0x04 /* Dedup reference: payload = 8B offset of original block */
#define ZUPT_BLOCK_DEDUP_REF 0x04 /* Dedup reference; authenticated v5.2.2 payload also carries source AAD sequence */
#define ZUPT_BLOCK_COMMENT 0x05 /* v2.4.3: free-form UTF-8 comment, encrypted same as data blocks */
#define ZUPT_MAX_COMMENT_LEN 4096 /* Maximum comment payload size (bytes). */
@ -269,8 +353,9 @@ typedef struct {
int level; uint32_t block_size; uint16_t codec_id;
int verbose, encrypt, quiet, solid, threads;
int pq_mode; /* 1 = post-quantum hybrid KEM mode */
int sdk_mode; /* 1 = use libzuptsdk-backed v3 crypto (HKDF combiner + commitment + HPKE) */
int sdk_mode; /* 1 = use libvuptsdk-backed v3 crypto (HKDF combiner + commitment + HPKE) */
int box_mode; /* 1 = libpqvaptvupt sealed-box mode (ZUPT_ENC_PQ_BOX_V1) */
int pqonly_mode; /* 1 = full post-quantum mode: ML-KEM-768 only (ZUPT_ENC_PQ_ONLY) */
int dedup; /* 1 = block-level deduplication enabled */
int kdf_legacy_pbkdf2; /* v2.4.1: 1 = force PBKDF2-SHA256 enc-header (compat with v2.4.0 and older readers). Default 0 = Argon2id. */
char password[256];
@ -343,7 +428,7 @@ static inline void zupt_secure_wipe(void *ptr, size_t len) {
static inline int zupt_is_regular_file(const char *path) {
#ifdef _WIN32
DWORD attr = GetFileAttributesA(path);
DWORD attr = zupt_win_get_attributes_utf8(path);
if (attr == INVALID_FILE_ATTRIBUTES) return 0;
return !(attr & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE |
FILE_ATTRIBUTE_REPARSE_POINT));
@ -394,10 +479,11 @@ void zupt_hmac_sha256_init(zupt_hmac_ctx *c, const uint8_t *key, size_t klen);
void zupt_hmac_sha256_update(zupt_hmac_ctx *c, const uint8_t *data, size_t dlen);
void zupt_hmac_sha256_final(zupt_hmac_ctx *c, uint8_t mac[32]);
/* Constant-time buffer equality. Returns 1 if equal, 0 otherwise, in
* time dependent only on n (not contents / mismatch position). The single
* audited MAC-tag comparison primitive; timing-verified by the
* dudect-style test in tests/test_ct_timing.c. CT-REQUIRED. */
/* Constant-time-intended buffer equality. Returns 1 if equal, 0 otherwise.
* The source has a fixed-length OR-accumulate loop without an intended
* content-dependent exit or access. The dudect-style regression in
* tests/test_ct_timing.c measures exact builds when its control is conclusive;
* it is not a formal guarantee about compiler output. CT-REQUIRED. */
int zupt_ct_memeq(const void *a, const void *b, size_t n);
void zupt_pbkdf2_sha256(const uint8_t *pw, size_t pwlen, const uint8_t *salt, size_t slen, uint32_t iter, uint8_t *out, size_t olen);
void zupt_aes256_ctr(const uint8_t key[32], const uint8_t nonce[16], const uint8_t *in, uint8_t *out, size_t len);
@ -475,8 +561,20 @@ zupt_error_t zupt_compress_files(const char *out, const char **arc, const char *
zupt_error_t zupt_extract_archive(const char *arc, const char *dir, zupt_options_t *opts);
zupt_error_t zupt_list_archive(const char *arc, zupt_options_t *opts);
zupt_error_t zupt_test_archive(const char *arc, zupt_options_t *opts);
/* Internal stream variant used when a caller has pinned a private snapshot.
* It never closes stream; the caller retains ownership. */
zupt_error_t zupt_test_archive_stream(FILE *stream, zupt_options_t *opts);
zupt_error_t zupt_open_archive_internal(FILE *stream, zupt_options_t *opts,
zupt_archive_header_t *header,
zupt_footer_t *footer,
zupt_index_entry_t **entries,
int *num_entries);
/* ─── Hybrid PQ KEM (ML-KEM-768 + X25519) ─── */
/* Internal no-replace writer shared by the native and optional pq-box key
* formats. Private material receives platform-specific restrictive access. */
int zupt_keyfile_write_new(const char *path, const uint8_t *data, size_t length,
int private_material);
int zupt_hybrid_keygen(const char *keyfile);
int zupt_hybrid_export_pubkey(const char *privfile, const char *pubfile);
int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
@ -484,14 +582,22 @@ int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
const uint8_t *enc_hdr, size_t enc_hdr_len);
/* ─── SDK-backed crypto (zupt v2.2+, libzuptsdk under the hood) ─── */
/* ─── Full post-quantum crypto: ML-KEM-768 only, no X25519 (v4.2.0) ─── */
int zupt_pq_keygen(const char *keyfile);
int zupt_pq_export_pubkey(const char *privfile, const char *pubfile);
int zupt_pq_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
uint8_t *enc_hdr, size_t *enc_hdr_len);
int zupt_pq_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
const uint8_t *enc_hdr, size_t enc_hdr_len);
/* ─── SDK-backed crypto (zupt v2.2+, optional libvuptsdk) ─── */
int zupt_sdk_hybrid_keygen(const char *privkeyfile, const char *pubkeyfile);
int zupt_sdk_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
uint8_t *enc_hdr, size_t *enc_hdr_len);
int zupt_sdk_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
const uint8_t *enc_hdr, size_t enc_hdr_len);
/* pq-box mode (ZUPT_ENC_PQ_BOX_V1, vendored libpqvaptvupt) */
/* pq-box mode (ZUPT_ENC_PQ_BOX_V1, optional system libpqvaptvupt) */
int zupt_pqbox_keygen(const char *privkeyfile, const char *pubkeyfile);
int zupt_pqbox_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
uint8_t *enc_hdr, size_t *enc_hdr_len);
@ -509,7 +615,7 @@ void zupt_format_size(uint64_t bytes, char *buf, size_t cap);
/* Resolve ZUPT_CODEC_AUTO to a concrete codec based on hardware.
* On x86_64 with AVX2: VaptVupt (fast ANS+SIMD decode).
* On all other arches: Zupt-LZHP (no SIMD dependency).
* On all other arches: ZUPT-LZHP (no SIMD dependency).
* Decompression of ALL codecs works on ALL architectures. */
uint16_t zupt_resolve_auto_codec(void);
@ -528,18 +634,58 @@ zupt_error_t zupt_disk_restore(const char *archive_path, const char *target_path
zupt_options_t *opts);
/* ─── Internal Block I/O (used by format + disk modules) ─── */
typedef struct zupt_atomic_output zupt_atomic_output_t;
/* Create an archive in a private file next to OUTPUT_PATH. finish(..., 1)
* atomically replaces only the final directory entry; it never follows a
* symlink/reparse point at the leaf. finish(..., 0) removes the temporary. */
zupt_atomic_output_t *zupt_atomic_output_open(const char *output_path,
FILE **stream_out);
int zupt_atomic_output_finish(zupt_atomic_output_t *output, int publish);
zupt_error_t read_block(FILE *f, zupt_block_t *b);
zupt_error_t read_enc_header(FILE *f, zupt_archive_header_t *hdr, zupt_options_t *opts);
zupt_error_t decompress_block(const zupt_block_t *b, const zupt_keyring_t *kr,
uint64_t block_seq, uint8_t **out, size_t *olen);
/* Published 5.2.1 encrypted+dedup disk images bound DATA authentication to
* each frame's linear sequence, while legacy references stored only offsets.
* Readers build this private offset-to-sequence map before restoring them. */
typedef struct {
uint64_t offset;
uint64_t aad_seq;
} zupt_legacy_disk_aad_entry_t;
typedef struct {
zupt_legacy_disk_aad_entry_t *entries;
size_t count;
size_t capacity;
} zupt_legacy_disk_aad_map_t;
zupt_error_t zupt_legacy_disk_aad_map_build(
FILE *stream, uint64_t first_block_offset, uint32_t block_count,
zupt_legacy_disk_aad_map_t *map);
int zupt_legacy_disk_aad_map_lookup(
const zupt_legacy_disk_aad_map_t *map, uint64_t offset,
uint64_t *aad_seq);
void zupt_legacy_disk_aad_map_free(zupt_legacy_disk_aad_map_t *map);
zupt_error_t write_enc_header(FILE *out, zupt_archive_header_t *hdr,
zupt_options_t *opts);
int zupt_w8(FILE *f, uint8_t v);
int zupt_w16le(FILE *f, uint16_t v);
int zupt_w64le(FILE *f, uint64_t v);
void zupt_serialize_archive_header(const zupt_archive_header_t *header,
uint8_t out[ZUPT_ARCHIVE_HEADER_SIZE]);
void zupt_serialize_footer(const zupt_footer_t *footer,
uint8_t out[ZUPT_FOOTER_SIZE]);
int zupt_write_archive_header(FILE *stream,
const zupt_archive_header_t *header);
int zupt_write_footer(FILE *stream, const zupt_footer_t *footer);
/* ─── Block-Level Deduplication ─── */
#define ZUPT_DEDUP_MAX_ENTRIES (2 * 1024 * 1024) /* 2M entries, ~48MB RAM */
#define ZUPT_DEDUP_MAX_ENTRIES (2 * 1024 * 1024) /* 2M entries, ~80MB RAM */
#define ZUPT_DEDUP_DIGEST_SIZE 16 /* SHA-256 prefix paired with XXH64 */
typedef struct zupt_dedup_ctx zupt_dedup_ctx_t;
@ -556,6 +702,17 @@ void zupt_dedup_stats(const zupt_dedup_ctx_t *ctx,
uint64_t *bytes_saved);
int zupt_dedup_write_ref(FILE *out, uint64_t ref_offset,
uint32_t orig_size, uint64_t orig_checksum);
int zupt_dedup_write_ref_secure(FILE *out, uint64_t ref_offset,
uint32_t orig_size, uint64_t orig_checksum,
uint64_t current_aad_seq,
uint64_t referenced_aad_seq,
const zupt_keyring_t *keyring);
zupt_error_t zupt_dedup_read_ref(const zupt_block_t *block,
const zupt_keyring_t *keyring,
int require_authentication,
uint64_t current_aad_seq,
uint64_t *ref_offset,
uint64_t *referenced_aad_seq);
/* ─── Archive Info (read-only metadata inspection) ─── */
zupt_error_t zupt_archive_info(const char *path);

View file

@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: AGPL-3.0-or-later
* Copyright (c) 2025-2026 Cristian Cezar Moisés
* Zupt ACSL Custom Predicates for Frama-C/WP
* ZUPT ACSL Custom Predicates for Frama-C/WP
* Copyright (c) 2026 Cristian Cezar Moisés AGPL-3.0-or-later
*
* Usage: frama-c -wp -wp-rte -wp-model Typed+Cast

View file

@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: AGPL-3.0-or-later
* Copyright (c) 2025-2026 Cristian Cezar Moisés
* Zupt CPU Feature Detection
* ZUPT CPU Feature Detection
* Copyright (c) 2026 Cristian Cezar Moisés AGPL-3.0-or-later
*/
#ifndef ZUPT_CPUID_H

View file

@ -1,16 +1,18 @@
/*
* SPDX-License-Identifier: AGPL-3.0-or-later
* Copyright (c) 2025-2026 Cristian Cezar Moisés
* Zupt Jasmin Verified Crypto Declarations
* ZUPT optional x86_64 crypto assembly declarations
* Copyright (c) 2026 Cristian Cezar Moisés AGPL-3.0-or-later
*
* Extern declarations for Jasmin-compiled assembly functions.
* These replace C fallbacks when built with -DZUPT_USE_JASMIN.
* Four declarations below correspond to checked-in jasminc output. The
* zupt_aes256_ctr4 implementation is separately identified hand-written
* assembly matching an algorithm-only .jazz description. These functions
* replace C fallbacks when built with -DZUPT_USE_JASMIN.
*
* Calling convention: System V AMD64 ABI.
* Pointer args passed in RDI, RSI, RDX, RCX, R8, R9.
*
* v2.0.0: All 4 Jasmin functions wired and active.
* All five optional declarations are wired when the feature is enabled.
*/
#ifndef ZUPT_JASMIN_H
#define ZUPT_JASMIN_H
@ -18,24 +20,25 @@
#ifdef ZUPT_USE_JASMIN
#include <stdint.h>
/* JASMIN-VERIFIED: CT MAC comparison (4×u64 XOR accumulation).
/* JASMIN PATH: CT-intended MAC comparison (4×u64 XOR accumulation).
* Returns 0 if all 32 bytes match, nonzero if any differ.
* Replaces XOR loop in zupt_decrypt_buffer(). */
extern uint64_t zupt_mac_verify_ct(const void *expected, const void *actual);
/* JASMIN-VERIFIED: CT conditional select (4×u64 masked select).
/* JASMIN PATH: CT-intended conditional select (4×u64 masked select).
* if cond==0: copies aout. if cond!=0: copies bout.
* Replaces cmov in zupt_mlkem768_decaps(). */
extern void zupt_ct_select_32(void *out, const void *a,
const void *b, uint64_t cond);
/* JASMIN-VERIFIED: CT conditional swap (4×u64 masked XOR swap).
/* JASMIN PATH: CT-intended conditional swap (4×u64 masked XOR swap).
* if cond==0: no-op. if cond==1: swaps ab in place.
* Replaces fe_cswap in zupt_x25519.c.
* NOTE: Requires 4×u64 field element layout (donna64). */
* Operates on exactly four consecutive u64 values; the X25519 caller handles
* its fifth 51-bit limb separately. */
extern void zupt_fe_cswap(void *a, void *b, uint64_t cond);
/* JASMIN-VERIFIED: AES-256 single-block encrypt via AES-NI.
/* JASMIN PATH: AES-256 single-block encrypt via AES-NI.
* out = AES-256-ECB(key, ctr) XOR in.
* FIX v2.0.0: Stack offset bug resolved round keys at correct
* 16-byte aligned offsets. Requires AES-NI (checked via CPUID).
@ -49,7 +52,7 @@ extern void zupt_fe_cswap(void *a, void *b, uint64_t cond);
extern void zupt_aes256_blk(void *out, const void *in,
const void *key, const void *ctr);
/* JASMIN-VERIFIED: AES-256-CTR 4-block pipeline via AES-NI.
/* HAND-WRITTEN ASSEMBLY PATH: AES-256-CTR 4-block pipeline via AES-NI.
* Processes nblocks×16 bytes with 4-way interleaving.
* Counter is updated in-place (big-endian increment in bytes [8..15]).
* Requires AES-NI. Falls back to zupt_aes256_blk for remaining 1-3 blocks.

View file

@ -1,5 +1,5 @@
/*
* Zupt Backup-oriented compression with AES-256 encryption
* ZUPT Backup-oriented compression with AES-256 encryption
* Copyright (c) 2026 Cristian Cezar Moisés
* SPDX-License-Identifier: AGPL-3.0-or-later
*

View file

@ -1,5 +1,5 @@
/*
* Zupt Backup-oriented compression with AES-256 encryption
* ZUPT Backup-oriented compression with AES-256 encryption
* Copyright (c) 2026 Cristian Cezar Moisés
* SPDX-License-Identifier: AGPL-3.0-or-later
*
@ -64,7 +64,7 @@ int zupt_mlkem768_decaps(uint8_t ss[MLKEM_SSBYTES],
/* Self-test: NTT/iNTT roundtrip + CBD-sampler range invariants.
* Returns 1 on pass, 0 on fail. Called from test_vectors.c case 14
* (F-04, Zupt 2.2.4). */
* (F-04, ZUPT 2.2.4). */
int zupt_mlkem768_selftest(void);
#endif

View file

@ -1,10 +1,11 @@
/*
* Zupt Backup-oriented compression with AES-256 encryption
* ZUPT Backup-oriented compression with AES-256 encryption
* Copyright (c) 2026 Cristian Cezar Moisés
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* X25519 Diffie-Hellman key agreement (RFC 7748).
* Montgomery ladder constant-time by construction.
* Fixed-iteration Montgomery ladder, designed without secret-dependent
* branches or table lookups; compiled timing remains platform-dependent.
*/
#ifndef ZUPT_X25519_H
#define ZUPT_X25519_H
@ -12,7 +13,8 @@
#include <stdint.h>
/* X25519(scalar, point) → result. All inputs/outputs are 32 bytes.
* CT-REQUIRED: Montgomery ladder is inherently constant-time. */
* CT-REQUIRED: keep the ladder free of intended secret-dependent branches and
* memory access. This source-level property is not a compiled timing proof. */
void zupt_x25519(uint8_t out[32], const uint8_t scalar[32], const uint8_t point[32]);
/* X25519 with the standard basepoint (9).

View file

@ -1,29 +1,33 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
# Fast Installer for VaptVupt - GNU/Linux
# Fast installer for ZUPT - GNU/Linux
set -e
set -Eeuo pipefail
umask 077
echo "🔧 Installing VaptVupt..."
VERSION=${VERSION:-5.2.8}
PREFIX=${PREFIX:-/usr/local}
echo "🔧 Installing ZUPT..."
# Create temporary directory
TMP_DIR=$(mktemp -d)
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/zupt-install.XXXXXXXX")
trap 'chmod -R u+rwX "$TMP_DIR" 2>/dev/null || true; rm -rf -- "$TMP_DIR"' EXIT HUP INT TERM
# Clone and build
git clone https://git.securityops.co/cristiancmoises/vaptvupt.git "$TMP_DIR/vaptvupt"
cd "$TMP_DIR/vaptvupt"
git clone --depth 1 --branch "v$VERSION" \
https://github.com/cristiancmoises/zupt.git "$TMP_DIR/zupt"
cd "$TMP_DIR/zupt"
make clean
make
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 2)" \
WITH_SDK=0 WITH_PQBOX=0
make WITH_SDK=0 WITH_PQBOX=0 check
# Install
sudo make install
sudo make PREFIX="$PREFIX" WITH_SDK=0 WITH_PQBOX=0 \
INSTALL_LEGACY_ALIAS=0 install
echo "✅ VaptVupt successfully installed to /usr/local/bin/vaptvupt"
echo "🔒 You can now run: vaptvupt (legacy 'zupt' symlink also installed)"
# Cleanup
cd ~
rm -rf "$TMP_DIR"
echo "🧹 Cleanup completed"
echo "✅ ZUPT $VERSION successfully installed to $PREFIX/bin/zupt"
echo "🔒 You can now run: zupt"

View file

@ -1,8 +1,9 @@
/* Zupt — AES-256 Single Block Encrypt via AES-NI (Jasmin)
/* ZUPT — AES-256 Single Block Encrypt via AES-NI (Jasmin)
* Copyright (c) 2026 Cristian Cezar Moisés
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* CT-REQUIRED: AES-NI has no data-dependent timing.
* CT-REQUIRED: designed without intended secret-dependent branches or memory
* access. Compiled and microarchitectural timing is not proven here.
*
* FIX v2.0.0: replaced `stack u128[15] rk` with 15 individual
* `stack u128` variables. The array form uses byte-offset indexing

View file

@ -1,15 +1,14 @@
/* Zupt — AES-256-CTR 4-Block Pipeline via AES-NI (Jasmin)
/* ZUPT — AES-256-CTR 4-Block Pipeline via AES-NI (Jasmin)
* Copyright (c) 2026 Cristian Cezar Moisés
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* CT-REQUIRED: AES-NI has no data-dependent timing.
* CT-REQUIRED: designed without intended secret-dependent branches or memory
* access. Compiled and microarchitectural timing is not proven here.
*
* Interleaves 4 independent counter blocks through the AES round
* pipeline. AES-NI has 4-cycle latency, 1-cycle throughput — so
* 4 independent blocks saturate the pipeline for ~4× throughput.
*
* Expected: ~3.5 GB/s AES-256-CTR on modern x86-64 (Zen3/Alder Lake).
*
* Interface:
* zupt_aes256_ctr4(out, in, key, ctr, nblocks)
* Encrypts nblocks×16 bytes. Counter is incremented in the last 8 bytes

View file

@ -1,6 +1,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2026 Cristian Cezar Moisés
# Generated from jasmin/zupt_aes_ctr4.jazz by jasminc.
# Hand-written production assembly matching the algorithm documented in
# jasmin/zupt_aes_ctr4.jazz; this file is not jasminc output.
.intel_syntax noprefix
.text
.p2align 5

View file

@ -1,4 +1,4 @@
/* Zupt — Constant-Time MAC Comparison (Jasmin)
/* ZUPT — Constant-Time MAC Comparison (Jasmin)
* Copyright (c) 2026 Cristian Cezar Moisés
* SPDX-License-Identifier: AGPL-3.0-or-later
*

View file

@ -1,4 +1,4 @@
/* Zupt — ML-KEM Constant-Time Select (Jasmin)
/* ZUPT — ML-KEM Constant-Time Select (Jasmin)
* Copyright (c) 2026 Cristian Cezar Moisés
* SPDX-License-Identifier: AGPL-3.0-or-later
*

View file

@ -1,11 +1,11 @@
/* Zupt — X25519 Constant-Time Conditional Swap (Jasmin)
/* ZUPT — X25519 Constant-Time Conditional Swap (Jasmin)
* Copyright (c) 2026 Cristian Cezar Moisés
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* CT-REQUIRED: fe_cswap must not leak cond via timing.
* This is the only CT-critical field operation in X25519.
* fe_add/fe_sub/fe_mul use C fallback (data-independent timing
* on x86-64 — ADD/MUL have fixed latency).
* fe_add/fe_sub/fe_mul use the C fallback. No fixed-latency claim is made for
* every compiler, x86-64 CPU, or resulting binary.
*
* 4 × u64 limbs, pure register operations, no intrinsics needed.
*/

View file

@ -1,64 +1,60 @@
# Maintainer: Cristian Cezar Moisés <sac@securityops.co>
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# AUR submission instructions:
# 1. Run `make dist` upstream to produce zupt-VERSION.tar.gz.
# 2. Upload that tarball somewhere stable (GitHub release / git.securityops.co).
# 2. Upload that tarball to the canonical GitHub release.
# 3. Update `source=()` URL and `sha256sums=()` below.
# 4. Run `makepkg --printsrcinfo > .SRCINFO` in this directory.
# 5. Commit and push to ssh://aur@aur.archlinux.org/zupt.git
# 5. Commit and push to the separately maintained AUR package repository.
#
# Test locally: `makepkg -s` in this directory after dropping a copy of the
# zupt-VERSION.tar.gz alongside the PKGBUILD.
# Test locally with `makepkg -s` after the release archive is published.
pkgname=vaptvupt
pkgver=4.1.0
pkgname=zupt
pkgver=5.2.8
pkgrel=1
provides=('zupt')
replaces=('zupt')
conflicts=('zupt')
pkgdesc='Pure-C11 post-quantum backup compression utility (AES-256-CTR + HMAC-SHA256 + ML-KEM-768 + X25519)'
arch=('x86_64' 'aarch64')
url='https://git.securityops.co/cristiancmoises/zupt'
license=('AGPL-3.0-or-later')
arch=('x86_64')
url='https://github.com/cristiancmoises/zupt'
license=('AGPL-3.0-or-later AND GPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND CC0-1.0')
depends=('glibc')
makedepends=('gcc')
makedepends=('gcc' 'git' 'make')
checkdepends=('python')
# Replace SHA256 placeholder with output of:
# sha256sum /tmp/zupt-2.4.4.tar.gz
source=("${pkgname}-${pkgver}.tar.gz::https://git.securityops.co/cristiancmoises/zupt/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
sha256sums=('SKIP')
source=("${pkgname}-${pkgver}.tar.gz::https://github.com/cristiancmoises/zupt/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
# Byte-reproducible upstream v5.2.8 source archive.
sha256sums=('378b9506211545b9594cf0d38ac8955d9b1cac34eb6b379ae0ec26b84edb65f7')
build() {
cd "${pkgname}-${pkgver}"
# Strict-warning build that the project's own §6 verification matrix uses.
# Source-only build (WITH_SDK=0) with the project's strict warning set.
CFLAGS="${CFLAGS:--O2 -std=c11} -Wall -Wextra -Wpedantic" \
make -j"$(nproc)"
make WITH_SDK=0 WITH_PQBOX=0 -j"$(nproc)"
}
check() {
cd "${pkgname}-${pkgver}"
# Project regression suite — F-06 HMAC, F-08 top-MAC, F-09 byte sweep, etc.
make test
# Distro-safe quick, traversal, integrity, codec and NIST/RFC checks.
make WITH_SDK=0 WITH_PQBOX=0 check
}
package() {
cd "${pkgname}-${pkgver}"
make DESTDIR="${pkgdir}" PREFIX=/usr install
# Source-only build (no vendored libraries); `make install` places the
# binary, man page and shell completions under the public zupt name.
make DESTDIR="${pkgdir}" PREFIX=/usr WITH_SDK=0 WITH_PQBOX=0 \
INSTALL_LEGACY_ALIAS=0 install
# Docs that aren't part of `make install`
install -Dm644 README.md "${pkgdir}/usr/share/doc/${pkgname}/README.md"
install -Dm644 SECURITY.md "${pkgdir}/usr/share/doc/${pkgname}/SECURITY.md"
install -Dm644 CHANGELOG.md "${pkgdir}/usr/share/doc/${pkgname}/CHANGELOG.md"
install -Dm644 AUDIT.md "${pkgdir}/usr/share/doc/${pkgname}/AUDIT.md"
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
# Vendored libzuptsdk shipped alongside the binary because the binary
# is linked with -Wl,-rpath,$ORIGIN/vendor/zuptsdk. For system install
# we move it to /usr/lib/zupt/ and the binary's rpath remains relative.
install -d "${pkgdir}/usr/lib/${pkgname}"
install -Dm755 vendor/zuptsdk/libzuptsdk.so.2.0.0 \
"${pkgdir}/usr/lib/${pkgname}/libzuptsdk.so.2.0.0"
ln -sf libzuptsdk.so.2.0.0 "${pkgdir}/usr/lib/${pkgname}/libzuptsdk.so.2"
ln -sf libzuptsdk.so.2.0.0 "${pkgdir}/usr/lib/${pkgname}/libzuptsdk.so"
install -Dm644 README.md "${pkgdir}/usr/share/doc/${pkgname}/README.md"
install -Dm644 SECURITY.md "${pkgdir}/usr/share/doc/${pkgname}/SECURITY.md"
install -Dm644 CHANGELOG.md "${pkgdir}/usr/share/doc/${pkgname}/CHANGELOG.md"
install -Dm644 LICENSE-AGPL-3.0 "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE-AGPL-3.0"
install -Dm644 LICENSE-GPL-3.0 "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE-GPL-3.0"
install -Dm644 LICENSE-BSD-2-Clause "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE-BSD-2-Clause"
install -Dm644 LICENSE-BSD-3-Clause "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE-BSD-3-Clause"
install -Dm644 LICENSE-CC0-1.0 "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE-CC0-1.0"
install -Dm644 NOTICE "${pkgdir}/usr/share/licenses/${pkgname}/NOTICE"
install -Dm644 THIRD-PARTY-NOTICES.md \
"${pkgdir}/usr/share/licenses/${pkgname}/THIRD-PARTY-NOTICES.md"
}

View file

@ -1,64 +1,165 @@
#!/bin/bash
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
# Build vaptvupt CLI as AppImage (portable single-file binary).
# Includes a legacy `zupt` symlink so AppDir users can invoke either name.
set -e
cd "$(dirname "$0")/.."
VERSION="${VERSION:-3.0.0}"
ARCH="${ARCH:-x86_64}"
PKGNAME="vaptvupt"
LEGACY="zupt"
NAME="$PKGNAME-$VERSION-$ARCH"
OUT="/tmp/${NAME}.AppDir"
set -Eeuo pipefail
rm -rf "$OUT"
mkdir -p "$OUT/usr/bin" "$OUT/usr/lib" "$OUT/usr/share/applications" "$OUT/usr/share/icons/hicolor/256x256/apps"
umask 022
export LC_ALL=C
install -m 755 $PKGNAME "$OUT/usr/bin/$PKGNAME"
ln -sf $PKGNAME "$OUT/usr/bin/$LEGACY"
install -m 644 vendor/zuptsdk/libzuptsdk.so.2.0.0 "$OUT/usr/lib/"
ln -sf libzuptsdk.so.2.0.0 "$OUT/usr/lib/libzuptsdk.so.2"
ln -sf libzuptsdk.so.2 "$OUT/usr/lib/libzuptsdk.so"
install -m 644 vendor/pqvaptvupt/libpqvaptvupt.so.0.6.0 "$OUT/usr/lib/"
ln -sf libpqvaptvupt.so.0.6.0 "$OUT/usr/lib/libpqvaptvupt.so.0"
ln -sf libpqvaptvupt.so.0 "$OUT/usr/lib/libpqvaptvupt.so"
die() {
printf 'FAIL: %s\n' "$*" >&2
exit 1
}
cat > "$OUT/AppRun" <<APPRUN
#!/bin/bash
HERE="\$(dirname "\$(readlink -f "\${0}")")"
export LD_LIBRARY_PATH="\$HERE/usr/lib:\$LD_LIBRARY_PATH"
export PATH="\$HERE/usr/bin:\$PATH"
exec "\$HERE/usr/bin/$PKGNAME" "\$@"
APPRUN
chmod +x "$OUT/AppRun"
[[ $(uname -s) == Linux ]] || die 'AppImage packages must be built on Linux'
cat > "$OUT/$PKGNAME.desktop" <<DESK
[Desktop Entry]
Name=VaptVupt
Comment=Post-quantum backup compression utility (formerly Zupt)
Exec=$PKGNAME
Terminal=true
Type=Application
Categories=Utility;Archiving;Security;
Icon=$PKGNAME
DESK
cp "$OUT/$PKGNAME.desktop" "$OUT/usr/share/applications/"
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
cd -- "$repo_root"
# 1x1 PNG placeholder — replace with a real icon when the brand asset exists
printf '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\rIDATx\x9cc\xfc\xcf\xc0\x00\x00\x00\x05\x00\x01\xa5\xf6E@\x00\x00\x00\x00IEND\xaeB`\x82' > "$OUT/$PKGNAME.png"
cp "$OUT/$PKGNAME.png" "$OUT/usr/share/icons/hicolor/256x256/apps/$PKGNAME.png"
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
version=${VERSION:-$header_version}
[[ -n $version && $version == "$header_version" ]] || \
die "VERSION '$version' does not match include/zupt.h '$header_version'"
[[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "invalid package version: $version"
if command -v appimagetool >/dev/null 2>&1; then
ARCH=$ARCH appimagetool "$OUT" "/tmp/${NAME}.AppImage" 2>&1 | tail -5
echo "Built: /tmp/${NAME}.AppImage"
case $(uname -m) in
x86_64|amd64) native_arch=x86_64 ;;
aarch64|arm64) native_arch=aarch64 ;;
*) die "unsupported native AppImage architecture: $(uname -m)" ;;
esac
case ${ARCH:-$native_arch} in
x86_64|amd64) arch=x86_64 ;;
aarch64|arm64) arch=aarch64 ;;
*) die "unsupported AppImage architecture: ${ARCH:-$native_arch}" ;;
esac
[[ $arch == "$native_arch" ]] || \
die "ARCH=$arch does not match the native build architecture $native_arch"
appimagetool=${APPIMAGETOOL:-appimagetool}
if [[ $appimagetool == */* ]]; then
[[ -x $appimagetool ]] || die "APPIMAGETOOL is not executable: $appimagetool"
appimagetool=$(cd -- "$(dirname -- "$appimagetool")" && pwd -P)/$(basename -- "$appimagetool")
else
appimagetool=$(command -v -- "$appimagetool" || true)
[[ -n $appimagetool ]] || die 'appimagetool not found; set APPIMAGETOOL to a verified local executable'
fi
runtime_file=${APPIMAGE_RUNTIME_FILE:-}
[[ -n $runtime_file && -s $runtime_file ]] || \
die 'set APPIMAGE_RUNTIME_FILE to a locally verified type-2 runtime (network downloads are not performed)'
runtime_file=$(cd -- "$(dirname -- "$runtime_file")" && pwd -P)/$(basename -- "$runtime_file")
runtime_compliance_file=${APPIMAGE_RUNTIME_COMPLIANCE_FILE:-}
[[ -n $runtime_compliance_file && -s $runtime_compliance_file ]] || \
die 'set APPIMAGE_RUNTIME_COMPLIANCE_FILE to the audited runtime license/source-compliance notice'
runtime_compliance_file=$(cd -- "$(dirname -- "$runtime_compliance_file")" && pwd -P)/$(basename -- "$runtime_compliance_file")
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
mkdir -p -- "$dist_dir"
dist_dir=$(cd -- "$dist_dir" && pwd -P)
output=$dist_dir/zupt-${version}-linux-${arch}.AppImage
[[ ! -e $output ]] || die "refusing to overwrite existing output: $output"
for command_name in make readelf file sha256sum; do
command -v -- "$command_name" >/dev/null 2>&1 || die "required command not found: $command_name"
done
run_checks=${RUN_CHECKS:-1}
[[ $run_checks == 0 || $run_checks == 1 ]] || die 'RUN_CHECKS must be 0 or 1'
if [[ $run_checks == 1 ]]; then
command -v git >/dev/null 2>&1 || die 'git is required when RUN_CHECKS=1'
fi
# Always produce the AppDir tarball as well -- some environments (no FUSE,
# strict execve policies, etc.) cannot run the .AppImage directly. The
# tarball is the universal fallback: extract and run AppRun.
cd /tmp
tar -czf "${NAME}.AppDir.tar.gz" "$(basename "$OUT")"
echo "Built: /tmp/${NAME}.AppDir.tar.gz"
echo "Users can run: tar xzf ${NAME}.AppDir.tar.gz && ./${NAME}.AppDir/AppRun version"
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-appimage.XXXXXXXX")
appdir=$work/ZUPT.AppDir
image_tmp=$work/$(basename -- "$output")
cleanup() {
make -C "$repo_root" clean >/dev/null 2>&1 || true
chmod -R u+rwX "$work" 2>/dev/null || true
rm -rf -- "$work"
}
trap cleanup EXIT HUP INT TERM
printf '[AppImage] source-only build of ZUPT %s (%s)\n' "$version" "$arch"
make clean
make -j"$jobs" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
if [[ $run_checks == 1 ]]; then
make V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
fi
make DESTDIR="$appdir" PREFIX=/usr WITH_SDK=0 WITH_PQBOX=0 \
INSTALL_LEGACY_ALIAS=0 install
binary=$appdir/usr/bin/zupt
[[ -x $binary ]] || die 'AppDir executable is missing'
[[ ! -e $appdir/usr/bin/vaptvupt ]] || die 'legacy vaptvupt alias must not be packaged'
if readelf -d "$binary" 2>/dev/null | grep -Eq '(RPATH|RUNPATH)'; then
readelf -d "$binary" | grep -E '(RPATH|RUNPATH)' >&2
die 'AppDir executable contains RPATH/RUNPATH'
fi
if readelf -d "$binary" 2>/dev/null | grep -Eqi '(vendor/|libvuptsdk|libpqvaptvupt)'; then
die 'AppDir executable references a vendored optional library'
fi
mkdir -p -- "$appdir/usr/share/applications" \
"$appdir/usr/share/doc/zupt" \
"$appdir/usr/share/icons/hicolor/128x128/apps" \
"$appdir/usr/share/licenses/zupt"
install -m 0644 README.md CHANGELOG.md SECURITY.md THREAT_MODEL.md \
"$appdir/usr/share/doc/zupt/"
install -m 0644 LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 \
LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE \
THIRD-PARTY-NOTICES.md "$appdir/usr/share/licenses/zupt/"
install -m 0644 gui/LICENSE-GUI \
"$appdir/usr/share/licenses/zupt/GUI-LICENSE.txt"
install -m 0644 gui/assets/README.md \
"$appdir/usr/share/licenses/zupt/GUI-ASSET-PROVENANCE.md"
install -m 0644 "$runtime_compliance_file" \
"$appdir/usr/share/licenses/zupt/AppImage-runtime-compliance.txt"
install -m 0644 gui/assets/zupt-128.png \
"$appdir/usr/share/icons/hicolor/128x128/apps/zupt.png"
cp -- "$appdir/usr/share/icons/hicolor/128x128/apps/zupt.png" "$appdir/zupt.png"
ln -s -- zupt.png "$appdir/.DirIcon"
desktop_file=dev.zupt.cli.desktop
cat > "$appdir/$desktop_file" <<'EOF'
[Desktop Entry]
Type=Application
Name=ZUPT
Comment=Backup compression with authenticated and post-quantum encryption
Exec=zupt
Icon=zupt
Terminal=true
Categories=Utility;Archiving;
EOF
cp -- "$appdir/$desktop_file" "$appdir/usr/share/applications/$desktop_file"
cat > "$appdir/AppRun" <<'EOF'
#!/bin/sh
set -eu
appdir=$(CDPATH= cd -P "$(dirname "$0")" && pwd -P)
exec "$appdir/usr/bin/zupt" "$@"
EOF
chmod 0755 "$appdir/AppRun"
forbidden=$(find "$appdir" -type f \( \
-name '*.o' -o -name '*.obj' -o -name '*.a' -o -name '*.so' -o \
-name '*.so.*' -o -name '*.dll' -o -name '*.dylib' \
\) -print)
[[ -z $forbidden ]] || {
printf '%s\n' "$forbidden" >&2
die 'compiled library or object found in AppDir'
}
bash scripts/test-installed-zupt.sh "$appdir/AppRun"
export ARCH=$arch
export VERSION=$version
export APPIMAGE_EXTRACT_AND_RUN=1
"$appimagetool" --runtime-file "$runtime_file" "$appdir" "$image_tmp"
chmod 0755 "$image_tmp"
file "$image_tmp" | grep -q 'ELF' || die 'generated AppImage does not have ELF magic'
bash scripts/test-installed-zupt.sh "$image_tmp"
mv -- "$image_tmp" "$output"
sha256sum "$output"
printf 'PASS: built and executed-package-tested %s\n' "$output"

View file

@ -1,134 +1,139 @@
#!/bin/bash
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
#
# Build self-contained vaptvupt CLI .deb package.
#
# v3.0.0 rename: the binary is now `vaptvupt`; we install it at
# /usr/bin/vaptvupt and create /usr/bin/zupt → /usr/bin/vaptvupt as
# a legacy symlink for one major version cycle. The package name
# is `vaptvupt` with Provides/Replaces/Conflicts on `zupt` so
# `apt install zupt` still resolves cleanly.
#
# Bundles libzuptsdk.so.2 under /usr/lib/vaptvupt/ so users do NOT
# need to separately install the libzuptsdk package.
set -e
cd "$(dirname "$0")/.."
set -Eeuo pipefail
VERSION="${VERSION:-3.0.0}"
ARCH="${ARCH:-amd64}"
PKGNAME="vaptvupt"
LEGACY="zupt"
umask 022
export LC_ALL=C
PKG="${PKGNAME}_${VERSION}_${ARCH}"
ROOT="/tmp/$PKG"
# Vendored libzuptsdk path (relative to project root)
SDK_LIB="vendor/zuptsdk/libzuptsdk.so.2.0.0"
if [ ! -f "$SDK_LIB" ]; then
echo "ERROR: $SDK_LIB not found. Vendor the libzuptsdk shared object first." >&2
exit 1
fi
PQVV_LIB="vendor/pqvaptvupt/libpqvaptvupt.so.0.6.0"
if [ ! -f "$PQVV_LIB" ]; then
echo "ERROR: $PQVV_LIB not found. Vendor the libpqvaptvupt shared object first." >&2
die() {
printf 'FAIL: %s\n' "$*" >&2
exit 1
}
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
cd -- "$repo_root"
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
version=${VERSION:-$header_version}
[[ -n $version && $version == "$header_version" ]] || \
die "VERSION '$version' does not match include/zupt.h '$header_version'"
[[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "invalid package version: $version"
native_arch=$(dpkg --print-architecture)
arch=${ARCH:-$native_arch}
[[ $arch == "$native_arch" ]] || \
die "ARCH=$arch does not match the native dpkg architecture $native_arch"
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
mkdir -p -- "$dist_dir"
dist_dir=$(cd -- "$dist_dir" && pwd -P)
output=$dist_dir/zupt_${version}_${arch}.deb
[[ ! -e $output ]] || die "refusing to overwrite existing output: $output"
for command_name in make dpkg dpkg-deb readelf sha256sum; do
command -v -- "$command_name" >/dev/null 2>&1 || die "required command not found: $command_name"
done
run_checks=${RUN_CHECKS:-1}
[[ $run_checks == 0 || $run_checks == 1 ]] || die 'RUN_CHECKS must be 0 or 1'
if [[ $run_checks == 1 ]]; then
command -v git >/dev/null 2>&1 || die 'git is required when RUN_CHECKS=1'
fi
echo "[deb] Building vaptvupt"
make clean >/dev/null 2>&1 || true
make -j"$(nproc)" >/dev/null
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-deb.XXXXXXXX")
stage=$work/stage
extract=$work/extract
echo "[deb] Patching rpath -> /usr/lib/$PKGNAME:/usr/lib64/$PKGNAME"
patchelf --set-rpath "/usr/lib/$PKGNAME:/usr/lib64/$PKGNAME" $PKGNAME
cleanup() {
make -C "$repo_root" clean >/dev/null 2>&1 || true
chmod -R u+rwX "$work" 2>/dev/null || true
rm -rf -- "$work"
}
trap cleanup EXIT HUP INT TERM
if ! readelf -d $PKGNAME | grep -q "RUNPATH.*\[/usr/lib/$PKGNAME:/usr/lib64/$PKGNAME\]"; then
echo "ERROR: built $PKGNAME does not have correct RUNPATH" >&2
readelf -d $PKGNAME | grep -E "RPATH|RUNPATH"
exit 1
printf '[deb] source-only build of ZUPT %s (%s)\n' "$version" "$arch"
make clean
make -j"$jobs" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
if [[ $run_checks == 1 ]]; then
make V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
fi
rm -rf "$ROOT"
mkdir -p "$ROOT/DEBIAN" \
"$ROOT/usr/bin" \
"$ROOT/usr/lib/$PKGNAME" \
"$ROOT/usr/share/doc/$PKGNAME" \
"$ROOT/usr/share/man/man1" \
"$ROOT/usr/share/bash-completion/completions" \
"$ROOT/usr/share/zsh/site-functions" \
"$ROOT/usr/share/fish/vendor_completions.d"
make DESTDIR="$stage" PREFIX=/usr WITH_SDK=0 WITH_PQBOX=0 \
INSTALL_LEGACY_ALIAS=0 install
# Binary + legacy symlink
install -m 755 $PKGNAME "$ROOT/usr/bin/$PKGNAME"
ln -sf $PKGNAME "$ROOT/usr/bin/$LEGACY"
binary=$stage/usr/bin/zupt
[[ -x $binary ]] || die 'staged /usr/bin/zupt is missing'
[[ ! -e $stage/usr/bin/vaptvupt ]] || die 'legacy /usr/bin/vaptvupt must not be packaged'
# Bundled libzuptsdk
install -m 755 "$SDK_LIB" "$ROOT/usr/lib/$PKGNAME/libzuptsdk.so.2.0.0"
ln -sf libzuptsdk.so.2.0.0 "$ROOT/usr/lib/$PKGNAME/libzuptsdk.so.2"
ln -sf libzuptsdk.so.2.0.0 "$ROOT/usr/lib/$PKGNAME/libzuptsdk.so"
install -m 755 "$PQVV_LIB" "$ROOT/usr/lib/$PKGNAME/libpqvaptvupt.so.0.6.0"
ln -sf libpqvaptvupt.so.0.6.0 "$ROOT/usr/lib/$PKGNAME/libpqvaptvupt.so.0"
ln -sf libpqvaptvupt.so.0.6.0 "$ROOT/usr/lib/$PKGNAME/libpqvaptvupt.so"
# Manpage (gzip-compressed); install + legacy alias
if [ -f doc/vaptvupt.1 ]; then
gzip -9n -c doc/vaptvupt.1 > "$ROOT/usr/share/man/man1/$PKGNAME.1.gz"
ln -sf $PKGNAME.1.gz "$ROOT/usr/share/man/man1/$LEGACY.1.gz"
if readelf -d "$binary" 2>/dev/null | grep -Eq '(RPATH|RUNPATH)'; then
readelf -d "$binary" | grep -E '(RPATH|RUNPATH)' >&2
die 'staged executable contains RPATH/RUNPATH'
fi
if readelf -d "$binary" 2>/dev/null | grep -Eqi '(vendor/|libvuptsdk|libpqvaptvupt)'; then
die 'staged executable references a vendored optional library'
fi
# Shell completions
if [ -f completions/vaptvupt.bash ]; then
install -m 0644 completions/vaptvupt.bash "$ROOT/usr/share/bash-completion/completions/$PKGNAME"
ln -sf $PKGNAME "$ROOT/usr/share/bash-completion/completions/$LEGACY"
fi
if [ -f completions/_vaptvupt ]; then
install -m 0644 completions/_vaptvupt "$ROOT/usr/share/zsh/site-functions/_$PKGNAME"
ln -sf _$PKGNAME "$ROOT/usr/share/zsh/site-functions/_$LEGACY"
fi
if [ -f completions/vaptvupt.fish ]; then
install -m 0644 completions/vaptvupt.fish "$ROOT/usr/share/fish/vendor_completions.d/$PKGNAME.fish"
forbidden=$(find "$stage" -type f \( \
-name '*.o' -o -name '*.obj' -o -name '*.a' -o -name '*.so' -o \
-name '*.so.*' -o -name '*.dll' -o -name '*.dylib' -o -name '*.exe' \
\) -print)
[[ -z $forbidden ]] || {
printf '%s\n' "$forbidden" >&2
die 'compiled library or object found in package staging tree'
}
docdir=$stage/usr/share/doc/zupt
mkdir -p -- "$docdir"
install -m 0644 README.md CHANGELOG.md SECURITY.md "$docdir/"
for document in THREAT_MODEL.md NOTICE THIRD-PARTY-NOTICES.md LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0; do
[[ ! -f $document ]] || install -m 0644 "$document" "$docdir/"
done
install -m 0644 LICENSE "$docdir/copyright"
mkdir -p -- "$work/debian" "$stage/DEBIAN"
if [[ -n ${DEB_DEPENDS:-} ]]; then
depends=$DEB_DEPENDS
else
command -v dpkg-shlibdeps >/dev/null 2>&1 || \
die 'dpkg-shlibdeps is required unless DEB_DEPENDS is explicitly set'
printf 'Source: zupt\nPackage: zupt\n' > "$work/debian/control"
shlib_line=$(cd -- "$work" && dpkg-shlibdeps -O -e"$binary")
depends=${shlib_line#shlibs:Depends=}
[[ -n $depends && $depends != "$shlib_line" ]] || \
die 'dpkg-shlibdeps did not determine runtime dependencies'
fi
# Docs
install -m 0644 README.md "$ROOT/usr/share/doc/$PKGNAME/README.md"
install -m 0644 LICENSE "$ROOT/usr/share/doc/$PKGNAME/copyright"
[ -f SECURITY.md ] && install -m 0644 SECURITY.md "$ROOT/usr/share/doc/$PKGNAME/SECURITY.md"
[ -f CHANGELOG.md ] && install -m 0644 CHANGELOG.md "$ROOT/usr/share/doc/$PKGNAME/CHANGELOG.md"
[ -f THREAT_MODEL.md ] && install -m 0644 THREAT_MODEL.md "$ROOT/usr/share/doc/$PKGNAME/THREAT_MODEL.md"
# DEBIAN/control
INSTALLED_KB=$(du -sk "$ROOT/usr" | awk '{print $1}')
cat > "$ROOT/DEBIAN/control" <<EOF
Package: $PKGNAME
Version: $VERSION
installed_kib=$(du -sk "$stage/usr" | awk '{print $1}')
cat > "$stage/DEBIAN/control" <<EOF
Package: zupt
Version: $version
Section: utils
Priority: optional
Architecture: $ARCH
Provides: $LEGACY (= $VERSION)
Replaces: $LEGACY (<< 3.0.0)
Conflicts: $LEGACY (<< 3.0.0)
Depends: libargon2-1, libssl3 | libssl3t64
Installed-Size: $INSTALLED_KB
Architecture: $arch
Depends: $depends
Installed-Size: $installed_kib
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
Homepage: https://git.securityops.co/cristiancmoises/zupt
Description: Post-quantum backup compression utility (formerly zupt)
VaptVupt (renamed from Zupt in v3.0.0 due to a prior INPI Brasil
trademark on the name) is a pure-C11 backup compression utility
featuring post-quantum hybrid encryption (ML-KEM-768 + X25519,
FIPS 203), AES-256-CTR + HMAC-SHA256 authenticated encryption,
Argon2id KDF (PBKDF2-SHA256 via --kdf pbkdf2), multi-threaded
compression with the VaptVupt LZ + ANS codec 2.48.5, full-disk
backup with sparse-region detection, and end-to-end byte-level
tamper detection on encrypted archives (F-09: 0/1827 silent
accepts).
.
The .zupt archive extension is unchanged; v2.x and v3.0.0
archives are bidirectionally compatible. The legacy /usr/bin/zupt
symlink is preserved for one major version cycle.
Homepage: https://github.com/cristiancmoises/zupt
Description: Backup compression with authenticated and post-quantum encryption
ZUPT creates compressed backup archives with optional password encryption
or ML-KEM-768 and X25519 hybrid key encapsulation. This package is built from
source with the optional libvuptsdk and libpqvaptvupt integrations disabled.
EOF
DEB_OUT="/tmp/${PKGNAME}_${VERSION}_${ARCH}.deb"
dpkg-deb --build --root-owner-group "$ROOT" "$DEB_OUT" >/dev/null
echo "Built: $DEB_OUT ($(du -h "$DEB_OUT" | cut -f1))"
dpkg-deb -I "$DEB_OUT" | sed -n '1,20p'
package_tmp=$work/$(basename -- "$output")
dpkg-deb --build --root-owner-group "$stage" "$package_tmp" >/dev/null
dpkg-deb --info "$package_tmp" >/dev/null
dpkg-deb --contents "$package_tmp" > "$work/contents.txt"
if grep -Eq '(/usr/bin/vaptvupt$|\.(o|obj|a|so|so\.[^/]+|dll|dylib)$)' "$work/contents.txt"; then
cat "$work/contents.txt" >&2
die 'forbidden alias or compiled library/object found in .deb contents'
fi
mkdir -p -- "$extract"
dpkg-deb --extract "$package_tmp" "$extract"
bash scripts/test-installed-zupt.sh "$extract/usr/bin/zupt"
mv -- "$package_tmp" "$output"
sha256sum "$output"
printf 'PASS: built and extracted-package-tested %s\n' "$output"

View file

@ -1,188 +1,228 @@
#!/bin/bash
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
#
# Build a macOS .dmg installer for the Zupt CLI.
#
# This script MUST be run on macOS — `hdiutil` is required and only
# ships with macOS. There is no portable way to produce a .dmg from
# Linux that Apple's installer will mount cleanly (libdmg-hfsplus and
# dmg2img exist but produce read-only images that some macOS versions
# reject).
#
# On macOS:
# xcode-select --install # one-time, for clang
# make # build the zupt binary
# VERSION=2.4.7 bash packaging/build-dmg.sh
#
# Produces: /tmp/Zupt-VERSION.dmg with:
# - zupt binary (universal2 if built with -arch x86_64 -arch arm64)
# - libzuptsdk dylib alongside the binary at @loader_path
# - install.command (drag-to-install script)
# - README.md, LICENSE
# - Optional: code-signed and notarized if APPLE_DEV_ID env is set
#
# For Homebrew installation, prefer packaging/homebrew/zupt.rb instead.
# The .dmg is for users who don't want to install Homebrew.
set -e
cd "$(dirname "$0")/.."
set -Eeuo pipefail
VERSION="${VERSION:-2.4.7}"
ARCH="${ARCH:-$(uname -m)}" # x86_64 or arm64
NAME="Zupt-${VERSION}-${ARCH}"
STAGE="/tmp/${NAME}.app/Contents"
umask 022
export LC_ALL=C
# ── Platform check ──
if [ "$(uname)" != "Darwin" ]; then
cat >&2 <<EOF
ERROR: build-dmg.sh must be run on macOS.
The .dmg format requires Apple's hdiutil. On Linux:
- Use the .deb (packaging/build-deb.sh) for Debian/Ubuntu/Mint
- Use the .rpm (packaging/build-rpm.sh) for Fedora/RHEL/openSUSE
- Use the AppImage (packaging/build-appimage.sh) for universal Linux
- Use the Homebrew formula on macOS (packaging/homebrew/zupt.rb)
If you need a macOS .pkg without macOS hardware, GitHub Actions has
macos-14 runners that can produce signed .dmg/.pkg artefacts. See
.github/workflows/ci.yml for the matrix template.
EOF
die() {
printf 'FAIL: %s\n' "$*" >&2
exit 1
}
[[ $(uname -s) == Darwin ]] || die 'DMG packages must be built and tested on macOS'
test_macos_binary() (
set -Eeuo pipefail
local candidate=$1 binary test_root archive_size
if [[ $candidate == */* ]]; then
[[ -x $candidate ]] || die "executable not found: $candidate"
binary=$(cd "$(dirname "$candidate")" && pwd -P)/$(basename "$candidate")
else
binary=$(command -v "$candidate" || true)
[[ -n $binary ]] || die "executable not found on PATH: $candidate"
fi
for command_name in cmp dd diff find grep shasum sort; do
command -v "$command_name" >/dev/null 2>&1 || \
die "required smoke-test command not found: $command_name"
done
test_root=$(mktemp -d "${TMPDIR:-/tmp}/zupt-macos-smoke.XXXXXX")
trap 'chmod -R u+rwX "$test_root" 2>/dev/null || true; rm -rf "$test_root"' \
EXIT HUP INT TERM
mkdir -p "$test_root/input/subdir" "$test_root/output" \
"$test_root/password-output" "$test_root/escape-output" "$test_root/outside"
printf 'ZUPT macOS package smoke test\n' > "$test_root/input/text file.txt"
printf 'conteúdo UTF-8\n' > "$test_root/input/subdir/café-安全.txt"
: > "$test_root/input/empty file"
dd if=/dev/urandom of="$test_root/input/subdir/random.bin" \
bs=4096 count=8 >/dev/null 2>&1
printf 'do-not-overwrite\n' > "$test_root/outside/sentinel"
"$binary" --version > "$test_root/version.log" 2>&1
grep -q '^zupt ' "$test_root/version.log"
"$binary" --help > "$test_root/help.log" 2>&1
grep -q '^Usage:' "$test_root/help.log"
if "$binary" --definitely-invalid-option >/dev/null 2>&1; then
die 'invalid option returned success'
fi
(
cd "$test_root"
"$binary" compress plain.zupt input
"$binary" test plain.zupt
"$binary" extract -o output plain.zupt
)
diff -r "$test_root/input" "$test_root/output/input"
(
cd "$test_root/input"
find . -type f -exec shasum -a 256 {} \; | sort
) > "$test_root/original.sha256"
(
cd "$test_root/output/input"
find . -type f -exec shasum -a 256 {} \; | sort
) > "$test_root/extracted.sha256"
cmp "$test_root/original.sha256" "$test_root/extracted.sha256"
(
cd "$test_root"
"$binary" compress -p 'ZUPT-test-password-2026!' \
password.zupt 'input/text file.txt'
"$binary" test -p 'ZUPT-test-password-2026!' password.zupt
"$binary" extract -p 'ZUPT-test-password-2026!' \
-o password-output password.zupt
)
cmp "$test_root/input/text file.txt" \
"$test_root/password-output/input/text file.txt"
if "$binary" extract -p incorrect-password -o "$test_root/wrong-password" \
"$test_root/password.zupt" >/dev/null 2>&1; then
die 'incorrect password returned success'
fi
archive_size=$(wc -c < "$test_root/plain.zupt")
((archive_size > 32)) || die 'archive unexpectedly small'
dd if="$test_root/plain.zupt" of="$test_root/corrupt.zupt" bs=1 \
count="$((archive_size - 17))" >/dev/null 2>&1
if "$binary" test "$test_root/corrupt.zupt" >/dev/null 2>&1; then
die 'truncated archive returned success'
fi
ln -s "$test_root/outside" "$test_root/escape-output/input"
"$binary" extract -o "$test_root/escape-output" \
"$test_root/plain.zupt" >/dev/null 2>&1 || true
[[ $(<"$test_root/outside/sentinel") == do-not-overwrite ]] || \
die 'extraction overwrote outside sentinel'
[[ ! -e $test_root/outside/text\ file.txt && ! -e $test_root/outside/subdir ]] || \
die 'extraction escaped through a destination symlink'
[[ $(id -u) -ne 0 ]] || die 'macOS package smoke test unexpectedly ran as root'
printf 'PASS: native macOS package functional test suite\n'
)
if [[ ${1:-} == --test-binary ]]; then
(($# == 2)) || die 'usage: build-dmg.sh --test-binary PATH'
test_macos_binary "$2"
exit 0
elif (($# != 0)); then
die 'usage: build-dmg.sh [--test-binary PATH]'
fi
# ── Build zupt (universal binary if possible) ──
echo "[dmg] Building zupt"
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
cd -- "$repo_root"
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
version=${VERSION:-$header_version}
[[ -n $version && $version == "$header_version" ]] || \
die "VERSION '$version' does not match include/zupt.h '$header_version'"
[[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "invalid package version: $version"
native_arch=$(uname -m)
arch=${ARCH:-$native_arch}
[[ $arch == "$native_arch" ]] || \
die "ARCH=$arch does not match the native macOS architecture $native_arch"
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
mkdir -p "$dist_dir"
dist_dir=$(cd "$dist_dir" && pwd -P)
output=$dist_dir/ZUPT-${version}-macOS-${arch}.dmg
[[ ! -e $output ]] || die "refusing to overwrite existing output: $output"
for command_name in make clang hdiutil otool plutil shasum; do
command -v -- "$command_name" >/dev/null 2>&1 || die "required command not found: $command_name"
done
run_checks=${RUN_CHECKS:-1}
[[ $run_checks == 0 || $run_checks == 1 ]] || die 'RUN_CHECKS must be 0 or 1'
if [[ $run_checks == 1 ]]; then
command -v git >/dev/null 2>&1 || die 'git is required when RUN_CHECKS=1'
fi
jobs=${JOBS:-$(sysctl -n hw.ncpu 2>/dev/null || printf '1')}
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-dmg.XXXXXXXX")
app=$work/ZUPT.app
contents=$app/Contents
dmg_root=$work/dmg-root
dmg_tmp=$work/$(basename "$output")
mkdir -p "$contents/MacOS" "$contents/Resources" "$dmg_root"
cleanup() {
make -C "$repo_root" clean >/dev/null 2>&1 || true
chmod -R u+rwX "$work" 2>/dev/null || true
rm -rf "$work"
}
trap cleanup EXIT HUP INT TERM
printf '[dmg] source-only build of ZUPT %s (%s)\n' "$version" "$arch"
make clean
if xcrun --sdk macosx clang -dM -E - </dev/null | grep -q __aarch64__; then
# arm64 host → can cross-build for x86_64 via -arch flag
CFLAGS="-O2 -std=c11 -arch arm64 -arch x86_64" \
LDFLAGS="-arch arm64 -arch x86_64" \
make -j"$(sysctl -n hw.ncpu)" || make -j"$(sysctl -n hw.ncpu)"
else
make -j"$(sysctl -n hw.ncpu)"
make -j"$jobs" CC=clang V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
if [[ $run_checks == 1 ]]; then
make CC=clang V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
fi
test_macos_binary "$repo_root/zupt"
install -m 0755 zupt "$contents/MacOS/zupt"
for document in README.md CHANGELOG.md LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE THIRD-PARTY-NOTICES.md; do
[[ ! -f $document ]] || install -m 0644 "$document" "$contents/Resources/"
done
if otool -l "$contents/MacOS/zupt" | grep -q 'cmd LC_RPATH'; then
otool -l "$contents/MacOS/zupt" >&2
die 'macOS executable contains LC_RPATH'
fi
if otool -L "$contents/MacOS/zupt" | grep -Eqi \
'(vendor/|libvuptsdk|libpqvaptvupt|/home/|/Users/[^/]+/|/opt/(homebrew|local)/|/usr/local/)'; then
otool -L "$contents/MacOS/zupt" >&2
die 'macOS executable references a build path or vendored optional library'
fi
# ── Stage the .app bundle ──
echo "[dmg] Staging .app bundle"
rm -rf "/tmp/${NAME}.app"
mkdir -p "$STAGE/MacOS" "$STAGE/Resources" "$STAGE/Frameworks"
install -m 755 zupt "$STAGE/MacOS/zupt"
# Vendored libzuptsdk — on macOS it'd be .dylib, but if the vendored
# build is Linux-style .so, ship that and warn. A proper macOS build
# would produce libzuptsdk.2.0.0.dylib.
if [ -f vendor/zuptsdk/libzuptsdk.2.0.0.dylib ]; then
install -m 755 vendor/zuptsdk/libzuptsdk.2.0.0.dylib "$STAGE/Frameworks/"
install_name_tool -id "@loader_path/../Frameworks/libzuptsdk.2.0.0.dylib" \
"$STAGE/Frameworks/libzuptsdk.2.0.0.dylib"
install_name_tool -change "vendor/zuptsdk/libzuptsdk.so.2" \
"@loader_path/../Frameworks/libzuptsdk.2.0.0.dylib" \
"$STAGE/MacOS/zupt"
elif [ -f vendor/zuptsdk/libzuptsdk.so.2.0.0 ]; then
cat >&2 <<EOF
WARNING: vendor/zuptsdk ships .so (Linux), not .dylib (macOS).
The .dmg will include the Linux library which won't load on macOS.
Build libzuptsdk natively on macOS first, or modify the Makefile
to produce .dylib output on Darwin.
EOF
install -m 755 vendor/zuptsdk/libzuptsdk.so.2.0.0 "$STAGE/Frameworks/"
fi
# Info.plist (minimal — zupt is a CLI, so the .app is mostly a wrapper)
cat > "$STAGE/Info.plist" <<PLIST
cat > "$contents/Info.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>co.securityops.zupt</string>
<key>CFBundleName</key>
<string>Zupt</string>
<key>CFBundleDisplayName</key>
<string>Zupt</string>
<key>CFBundleVersion</key>
<string>${VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>${VERSION}</string>
<key>CFBundleExecutable</key>
<string>zupt</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>CFBundleIdentifier</key><string>dev.zupt.cli</string>
<key>CFBundleName</key><string>ZUPT</string>
<key>CFBundleDisplayName</key><string>ZUPT</string>
<key>CFBundleExecutable</key><string>zupt</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleVersion</key><string>$version</string>
<key>CFBundleShortVersionString</key><string>$version</string>
</dict>
</plist>
PLIST
EOF
plutil -lint "$contents/Info.plist"
cp README.md "$STAGE/Resources/" 2>/dev/null || true
cp LICENSE "$STAGE/Resources/" 2>/dev/null || true
# ── Drag-to-install command file ──
cat > "/tmp/${NAME}-install.command" <<'INSTALL'
#!/bin/bash
# Drag-installer for Zupt CLI. Copies the binary to /usr/local/bin
# (or the user's ~/bin if /usr/local isn't writable).
set -e
DIR="$(cd "$(dirname "$0")" && pwd)"
APP="$DIR/Zupt.app"
TARGET="/usr/local/bin"
if [ ! -w "$TARGET" ]; then
TARGET="$HOME/bin"
mkdir -p "$TARGET"
echo "Installing to $TARGET (add to PATH if missing)"
fi
cp "$APP/Contents/MacOS/zupt" "$TARGET/zupt"
chmod 755 "$TARGET/zupt"
# Bundle the dylib alongside under a stable path
LIBDIR="/usr/local/lib/zupt"
[ -w /usr/local/lib ] || LIBDIR="$HOME/lib/zupt"
mkdir -p "$LIBDIR"
if [ -d "$APP/Contents/Frameworks" ]; then
cp -P "$APP/Contents/Frameworks"/* "$LIBDIR/" 2>/dev/null || true
fi
echo "Installed: $TARGET/zupt"
"$TARGET/zupt" version
INSTALL
chmod 755 "/tmp/${NAME}-install.command"
# ── Optional: code sign ──
if [ -n "${APPLE_DEV_ID:-}" ]; then
echo "[dmg] Code-signing with Developer ID: $APPLE_DEV_ID"
codesign --force --options runtime --sign "$APPLE_DEV_ID" \
--entitlements packaging/macos/entitlements.plist \
"$STAGE/MacOS/zupt" 2>&1 || echo " (no entitlements file — proceeding unsigned for hardening)"
codesign --force --sign "$APPLE_DEV_ID" "/tmp/${NAME}.app" || true
if [[ -n ${CODESIGN_IDENTITY:-} ]]; then
codesign --force --options runtime --timestamp --sign "$CODESIGN_IDENTITY" "$app"
codesign --verify --deep --strict "$app"
fi
# ── Build .dmg ──
echo "[dmg] Building disk image"
DMG="/tmp/${NAME}.dmg"
rm -f "$DMG"
# Stage a directory tree that becomes the .dmg root
DMGSRC="/tmp/${NAME}-dmgsrc"
rm -rf "$DMGSRC"
mkdir -p "$DMGSRC"
cp -R "/tmp/${NAME}.app" "$DMGSRC/Zupt.app"
cp "/tmp/${NAME}-install.command" "$DMGSRC/Install Zupt.command"
[ -f README.md ] && cp README.md "$DMGSRC/"
[ -f LICENSE ] && cp LICENSE "$DMGSRC/"
hdiutil create -fs HFS+ -srcfolder "$DMGSRC" -volname "Zupt ${VERSION}" \
-format UDZO -ov "$DMG"
# ── Optional: notarize ──
if [ -n "${APPLE_DEV_ID:-}" ] && [ -n "${APPLE_NOTARIZE_KEY:-}" ]; then
echo "[dmg] Submitting for notarization"
xcrun notarytool submit "$DMG" --apple-id "$APPLE_DEV_ID" \
--password "$APPLE_NOTARIZE_KEY" --wait
xcrun stapler staple "$DMG"
cp -R "$app" "$dmg_root/ZUPT.app"
cat > "$dmg_root/Install ZUPT.command" <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
installer_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
source_binary=$installer_dir/ZUPT.app/Contents/MacOS/zupt
target_dir=/usr/local/bin
if [[ ! -d $target_dir || ! -w $target_dir ]]; then
target_dir=${XDG_BIN_HOME:-$HOME/.local/bin}
mkdir -p "$target_dir"
fi
install -m 0755 "$source_binary" "$target_dir/zupt"
printf 'Installed %s\n' "$target_dir/zupt"
"$target_dir/zupt" --version
EOF
chmod 0755 "$dmg_root/Install ZUPT.command"
for document in README.md CHANGELOG.md LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE THIRD-PARTY-NOTICES.md; do
[[ ! -f $document ]] || install -m 0644 "$document" "$dmg_root/"
done
echo ""
echo "Built: $DMG ($(du -h "$DMG" | cut -f1))"
echo "Users mount and drag 'Zupt.app' or double-click 'Install Zupt.command'."
hdiutil create -fs HFS+ -srcfolder "$dmg_root" -volname "ZUPT $version" \
-format UDZO -ov "$dmg_tmp"
hdiutil verify "$dmg_tmp"
mv "$dmg_tmp" "$output"
shasum -a 256 "$output"
printf 'PASS: built and native-binary-tested %s\n' "$output"

View file

@ -1,121 +1,140 @@
#!/bin/bash
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
# Build zupt-gui AppImage. Since the GUI is pure Python + Qt, the AppDir
# bundles only the Python source and metadata; it relies on system
# python3 + PyQt6/PySide6 at runtime. This keeps the AppImage tiny
# (~50 KB) and lets it work on any Linux with Qt6 Python bindings.
#
# For a true self-contained AppImage with bundled Python interpreter,
# use python-appimage (https://github.com/niess/python-appimage) on
# the build host — it produces a ~80 MB AppImage. The portable variant
# below is the better tradeoff for most distributions.
set -e
cd "$(dirname "$0")/.."
VERSION="${VERSION:-1.2.0}"
APPDIR="/tmp/vaptvupt-gui.AppDir"
# Build a dependency-light GUI AppImage. The ZUPT CLI is compiled from this
# tree and bundled; Python 3 plus PySide6 or PyQt6 remain host requirements.
rm -rf "$APPDIR"
mkdir -p "$APPDIR/usr/bin" \
"$APPDIR/usr/lib/vaptvupt-gui" \
"$APPDIR/usr/share/applications" \
"$APPDIR/usr/share/icons/hicolor/256x256/apps"
set -Eeuo pipefail
umask 022
export LC_ALL=C
# Python source
install -m 644 gui/src/zupt_gui.py "$APPDIR/usr/lib/vaptvupt-gui/"
die() { printf 'FAIL: %s\n' "$*" >&2; exit 1; }
# Wrapper
cat > "$APPDIR/usr/bin/vaptvupt-gui" <<'WRAP'
[[ $(uname -s) == Linux ]] || die 'AppImage packages must be built on Linux'
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
cd -- "$repo_root"
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
version=${VERSION:-$header_version}
[[ $version == "$header_version" && $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || \
die "VERSION '$version' does not match include/zupt.h '$header_version'"
case $(uname -m) in
x86_64|amd64) native_arch=x86_64 ;;
aarch64|arm64) native_arch=aarch64 ;;
*) die "unsupported native AppImage architecture: $(uname -m)" ;;
esac
case ${ARCH:-$native_arch} in
x86_64|amd64) arch=x86_64 ;;
aarch64|arm64) arch=aarch64 ;;
*) die "unsupported AppImage architecture: ${ARCH:-$native_arch}" ;;
esac
[[ $arch == "$native_arch" ]] || \
die "ARCH=$arch does not match the native build architecture $native_arch"
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
mkdir -p -- "$dist_dir"
dist_dir=$(cd -- "$dist_dir" && pwd -P)
case $dist_dir/ in "$repo_root"/*) die 'DIST_DIR must be outside the repository' ;; esac
output=$dist_dir/ZUPT-GUI-$version-linux-$arch.AppImage
[[ ! -e $output ]] || die "refusing to overwrite existing output: $output"
appimagetool=${APPIMAGETOOL:-appimagetool}
appimagetool=$(command -v -- "$appimagetool" 2>/dev/null || true)
[[ -n $appimagetool ]] || die 'appimagetool not found; no network fallback is performed'
runtime_file=${APPIMAGE_RUNTIME_FILE:-}
[[ -n $runtime_file && -s $runtime_file ]] || \
die 'set APPIMAGE_RUNTIME_FILE to a non-empty verified local type-2 runtime'
runtime_file=$(cd -- "$(dirname -- "$runtime_file")" && pwd -P)/$(basename -- "$runtime_file")
runtime_compliance_file=${APPIMAGE_RUNTIME_COMPLIANCE_FILE:-}
[[ -n $runtime_compliance_file && -s $runtime_compliance_file ]] || \
die 'set APPIMAGE_RUNTIME_COMPLIANCE_FILE to the audited runtime license/source-compliance notice'
runtime_compliance_file=$(cd -- "$(dirname -- "$runtime_compliance_file")" && pwd -P)/$(basename -- "$runtime_compliance_file")
for command_name in make python3 readelf file sha256sum; do
command -v -- "$command_name" >/dev/null 2>&1 || \
die "required command not found: $command_name"
done
python3 -c 'import PySide6.QtWidgets' 2>/dev/null || \
python3 -c 'import PyQt6.QtWidgets' 2>/dev/null || \
die 'the build/test host needs PySide6 or PyQt6; the AppImage does not download it'
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-gui-appimage.XXXXXXXX")
appdir=$work/ZUPT-GUI.AppDir
image_tmp=$work/$(basename -- "$output")
cleanup() {
make -C "$repo_root" clean >/dev/null 2>&1 || true
chmod -R u+rwX "$work" 2>/dev/null || true
rm -rf -- "$work"
}
trap cleanup EXIT HUP INT TERM
make clean
make -j"$jobs" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
make V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
make DESTDIR="$appdir" PREFIX=/usr WITH_SDK=0 WITH_PQBOX=0 \
INSTALL_LEGACY_ALIAS=0 install
binary=$appdir/usr/bin/zupt
[[ -x $binary ]] || die 'source-built CLI is missing from AppDir'
[[ ! -e $appdir/usr/bin/vaptvupt ]] || die 'legacy vaptvupt alias must not be packaged'
if readelf -d "$binary" 2>/dev/null | grep -Eq '(RPATH|RUNPATH|libvuptsdk|libpqvaptvupt|vendor/)'; then
readelf -d "$binary" >&2
die 'CLI has RPATH/RUNPATH or an optional-library reference'
fi
install -Dm0644 gui/src/zupt_gui.py "$appdir/usr/lib/zupt-gui/zupt_gui.py"
install -Dm0644 gui/assets/zupt-icon.png \
"$appdir/usr/share/icons/hicolor/256x256/apps/zupt-gui.png"
install -Dm0644 gui/packaging/zupt-gui.desktop \
"$appdir/usr/share/applications/zupt-gui.desktop"
install -d "$appdir/usr/share/licenses/zupt"
install -m 0644 LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 \
LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE \
THIRD-PARTY-NOTICES.md "$appdir/usr/share/licenses/zupt/"
install -d "$appdir/usr/share/licenses/zupt-gui"
install -m 0644 LICENSE-AGPL-3.0 \
"$appdir/usr/share/licenses/zupt-gui/LICENSE-AGPL-3.0"
install -m 0644 gui/LICENSE-GUI \
"$appdir/usr/share/licenses/zupt-gui/LICENSE-GUI"
install -m 0644 gui/assets/README.md \
"$appdir/usr/share/licenses/zupt-gui/ASSET-PROVENANCE.md"
install -Dm0644 "$runtime_compliance_file" \
"$appdir/usr/share/licenses/zupt/AppImage-runtime-compliance.txt"
cp -- "$appdir/usr/share/icons/hicolor/256x256/apps/zupt-gui.png" \
"$appdir/zupt-gui.png"
cat >"$appdir/usr/bin/zupt-gui" <<'WRAP'
#!/bin/sh
exec python3 "$(dirname "$0")/../lib/vaptvupt-gui/zupt_gui.py" "$@"
here=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P)
export ZUPT_BIN=$here/bin/zupt
exec python3 "$here/lib/zupt-gui/zupt_gui.py" "$@"
WRAP
chmod 755 "$APPDIR/usr/bin/vaptvupt-gui"
# Desktop file
cat > "$APPDIR/vaptvupt-gui.desktop" <<'DESKTOP'
[Desktop Entry]
Type=Application
Name=VaptVupt GUI
GenericName=Backup and Compression Utility
Comment=Post-quantum backup with HKDF combiner, key commitment, HPKE binding
Exec=vaptvupt-gui %f
Icon=vaptvupt-gui
Terminal=false
Categories=Utility;Archiving;Compression;Security;
StartupNotify=true
DESKTOP
cp "$APPDIR/vaptvupt-gui.desktop" "$APPDIR/usr/share/applications/"
# Icon
if [ -f gui/assets/zupt-icon.png ]; then
cp gui/assets/zupt-icon.png "$APPDIR/vaptvupt-gui.png"
cp gui/assets/zupt-icon.png "$APPDIR/usr/share/icons/hicolor/256x256/apps/vaptvupt-gui.png"
else
python3 -c "
import struct, zlib
def png(w, h, color):
raw = b''.join(b'\\0' + bytes(color) * w for _ in range(h))
def chunk(t, d): return struct.pack('>I', len(d)) + t + d + struct.pack('>I', zlib.crc32(t+d) & 0xffffffff)
return b'\\x89PNG\\r\\n\\x1a\\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0)) + chunk(b'IDAT', zlib.compress(raw)) + chunk(b'IEND', b'')
open('$APPDIR/zupt-gui.png','wb').write(png(256, 256, (88, 92, 215)))
"
cp "$APPDIR/vaptvupt-gui.png" "$APPDIR/usr/share/icons/hicolor/256x256/apps/vaptvupt-gui.png"
fi
# AppRun — sets PATH so zupt-gui finds the bundled wrapper, falls
# back to system zupt CLI if not present in /usr/bin alongside.
cat > "$APPDIR/AppRun" <<'APPRUN'
chmod 0755 "$appdir/usr/bin/zupt-gui"
cat >"$appdir/AppRun" <<'APPRUN'
#!/bin/sh
HERE="$(dirname "$(readlink -f "$0")")"
export PATH="$HERE/usr/bin:$PATH"
# Pre-flight check: is python3 available? Is a Qt6 binding installed?
if ! command -v python3 >/dev/null 2>&1; then
cat >&2 <<EOF
vaptvupt-gui: python3 is not installed.
Install: sudo apt install python3 (Debian/Ubuntu)
sudo dnf install python3 (Fedora/RHEL)
EOF
exit 1
fi
if ! python3 -c 'import PyQt6.QtWidgets' 2>/dev/null \
&& ! python3 -c 'import PySide6.QtWidgets' 2>/dev/null; then
cat >&2 <<EOF
vaptvupt-gui: needs a Qt6 Python binding (PyQt6 or PySide6).
Install one of:
Debian/Ubuntu: sudo apt install python3-pyqt6
Fedora/RHEL: sudo dnf install python3-pyqt6
pip (any): pip install --user PySide6
EOF
exit 1
fi
if ! command -v vaptvupt >/dev/null 2>&1 && ! command -v zupt >/dev/null 2>&1; then
cat >&2 <<EOF
vaptvupt-gui: warning — neither 'vaptvupt' nor legacy 'zupt' CLI was found in PATH.
Install the vaptvupt package or place the binary in PATH.
The GUI will start but compress/extract operations will fail.
EOF
fi
exec "$HERE/usr/bin/vaptvupt-gui" "$@"
appdir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
exec "$appdir/usr/bin/zupt-gui" "$@"
APPRUN
chmod 755 "$APPDIR/AppRun"
chmod 0755 "$appdir/AppRun"
cp -- "$appdir/usr/share/applications/zupt-gui.desktop" "$appdir/"
# Build AppImage
if command -v appimagetool >/dev/null 2>&1; then
ARCH=x86_64 appimagetool "$APPDIR" "/tmp/VaptVupt-GUI-$VERSION-x86_64.AppImage" 2>&1 | tail -5
echo "Built: /tmp/VaptVupt-GUI-$VERSION-x86_64.AppImage"
else
cd /tmp
rm -f "VaptVupt-GUI-$VERSION-x86_64.AppDir.tar.gz"
tar -czf "VaptVupt-GUI-$VERSION-x86_64.AppDir.tar.gz" vaptvupt-gui.AppDir
cd - >/dev/null
echo "appimagetool unavailable; portable AppDir tarball at:"
echo " /tmp/VaptVupt-GUI-$VERSION-x86_64.AppDir.tar.gz"
echo "Run via: tar -xzf ... && ./vaptvupt-gui.AppDir/AppRun"
echo "Convert to AppImage on a host with appimagetool:"
echo " ARCH=x86_64 appimagetool vaptvupt-gui.AppDir VaptVupt-GUI-$VERSION-x86_64.AppImage"
fi
forbidden=$(find "$appdir" -type f \( \
-name '*.o' -o -name '*.obj' -o -name '*.a' -o -name '*.so' -o \
-name '*.so.*' -o -name '*.dll' -o -name '*.dylib' \
\) -print)
[[ -z $forbidden ]] || { printf '%s\n' "$forbidden" >&2; die 'compiled library/object in AppDir'; }
QT_QPA_PLATFORM=offscreen "$appdir/AppRun" --version | grep -Fq "zupt-gui $version" || \
die 'AppDir GUI/CLI integration check failed'
export ARCH=$arch APPIMAGE_EXTRACT_AND_RUN=1
"$appimagetool" --runtime-file "$runtime_file" "$appdir" "$image_tmp"
chmod 0755 "$image_tmp"
file "$image_tmp" | grep -q ELF || die 'generated AppImage does not have ELF magic'
QT_QPA_PLATFORM=offscreen "$image_tmp" --version | grep -Fq "zupt-gui $version" || \
die 'generated AppImage execution check failed'
mv -- "$image_tmp" "$output"
sha256sum "$output"
printf 'PASS: built and execution-tested %s\n' "$output"

View file

@ -1,181 +1,113 @@
#!/bin/bash
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
# Build zupt-gui .deb (Python/Qt GUI). Works with PyQt6 OR PySide6.
set -e
cd "$(dirname "$0")/.."
VERSION="${VERSION:-1.2.0}"
ARCH="all"
PKG="vaptvupt-gui_${VERSION}_${ARCH}"
ROOT="/tmp/$PKG"
# Build the architecture-independent GUI package from tracked source. The CLI
# dependency is built and tested in baseline mode but is packaged separately.
rm -rf "$ROOT"
mkdir -p "$ROOT/DEBIAN" \
"$ROOT/usr/bin" \
"$ROOT/usr/lib/vaptvupt-gui" \
"$ROOT/usr/share/applications" \
"$ROOT/usr/share/icons/hicolor/256x256/apps" \
"$ROOT/usr/share/man/man1" \
"$ROOT/usr/share/doc/vaptvupt-gui"
set -Eeuo pipefail
umask 022
export LC_ALL=C
# Source files
install -m 644 gui/src/zupt_gui.py "$ROOT/usr/lib/vaptvupt-gui/"
die() { printf 'FAIL: %s\n' "$*" >&2; exit 1; }
# Wrapper script in /usr/bin
cat > "$ROOT/usr/bin/vaptvupt-gui" <<'WRAP'
#!/bin/sh
exec python3 /usr/lib/vaptvupt-gui/zupt_gui.py "$@"
WRAP
chmod 755 "$ROOT/usr/bin/vaptvupt-gui"
# v3.0.0: legacy zupt-gui symlink
ln -sf vaptvupt-gui "$ROOT/usr/bin/zupt-gui"
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
cd -- "$repo_root"
# Desktop entry
cat > "$ROOT/usr/share/applications/vaptvupt-gui.desktop" <<'DESKTOP'
[Desktop Entry]
Type=Application
Name=VaptVupt GUI
GenericName=Backup and Compression Utility
Comment=Post-quantum backup with HKDF combiner, key commitment, HPKE binding
Exec=vaptvupt-gui %f
Icon=vaptvupt-gui
Terminal=false
Categories=Utility;Archiving;Compression;Security;
StartupNotify=true
MimeType=application/x-zupt;
Keywords=archive;compression;encryption;post-quantum;backup;
DESKTOP
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
version=${VERSION:-$header_version}
[[ $version == "$header_version" && $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || \
die "VERSION '$version' does not match include/zupt.h '$header_version'"
# Man page
if [ -f doc/vaptvupt-gui.1 ]; then
install -m 644 doc/vaptvupt-gui.1 "$ROOT/usr/share/man/man1/vaptvupt-gui.1"
gzip -9n "$ROOT/usr/share/man/man1/vaptvupt-gui.1"
fi
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
mkdir -p -- "$dist_dir"
dist_dir=$(cd -- "$dist_dir" && pwd -P)
case $dist_dir/ in "$repo_root"/*) die 'DIST_DIR must be outside the repository' ;; esac
output=$dist_dir/zupt-gui_${version}_all.deb
[[ ! -e $output ]] || die "refusing to overwrite existing output: $output"
# Icon
if [ -f gui/assets/zupt-icon.png ]; then
cp gui/assets/zupt-icon.png "$ROOT/usr/share/icons/hicolor/256x256/apps/vaptvupt-gui.png"
else
python3 -c "
import struct, zlib
def png(w, h, color):
raw = b''.join(b'\\0' + bytes(color) * w for _ in range(h))
def chunk(t, d): return struct.pack('>I', len(d)) + t + d + struct.pack('>I', zlib.crc32(t+d) & 0xffffffff)
return b'\\x89PNG\\r\\n\\x1a\\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0)) + chunk(b'IDAT', zlib.compress(raw)) + chunk(b'IEND', b'')
open('$ROOT/usr/share/icons/hicolor/256x256/apps/zupt-gui.png','wb').write(png(256, 256, (88, 92, 215)))
"
fi
for command_name in make python3 dpkg-deb gzip sha256sum; do
command -v -- "$command_name" >/dev/null 2>&1 || \
die "required command not found: $command_name"
done
# Docs
install -m 644 gui/README.md "$ROOT/usr/share/doc/vaptvupt-gui/" 2>/dev/null || true
gzip -9n -c CHANGELOG.md > "$ROOT/usr/share/doc/vaptvupt-gui/changelog.gz"
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-gui-deb.XXXXXXXX")
stage=$work/stage
extract=$work/extract
cleanup() {
make -C "$repo_root" clean >/dev/null 2>&1 || true
chmod -R u+rwX "$work" 2>/dev/null || true
rm -rf -- "$work"
}
trap cleanup EXIT HUP INT TERM
cat > "$ROOT/usr/share/doc/vaptvupt-gui/copyright" <<'COPYRIGHT'
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: vaptvupt-gui
Upstream-Contact: Cristian Cezar Moisés <zupt@riseup.net>
Source: https://git.securityops.co/cristiancmoises/zupt
printf '[GUI deb] validating source-only CLI dependency %s\n' "$version"
make clean
make -j"$jobs" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
make V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
./zupt version | grep -Fq "zupt $version" || die 'CLI version check failed'
Files: *
Copyright: 2025-2026 Cristian Cezar Moisés
License: AGPL-3.0+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
.
On Debian systems, the complete text of the GNU Affero General Public
License version 3 can be found in /usr/share/common-licenses/AGPL-3.
COPYRIGHT
PYTHONDONTWRITEBYTECODE=1 python3 - <<'PY'
from pathlib import Path
source = Path("gui/src/zupt_gui.py").read_text(encoding="utf-8")
compile(source, "gui/src/zupt_gui.py", "exec")
PY
# Control
INSTALLED_SIZE=$(du -sk "$ROOT" | cut -f1)
cat > "$ROOT/DEBIAN/control" <<EOF
Package: vaptvupt-gui
Version: $VERSION
mkdir -p -- "$stage"
bash gui/install.sh --destdir "$stage" --prefix /usr
[[ ! -e $stage/usr/bin/vaptvupt-gui ]] || die 'legacy vaptvupt-gui alias must not be packaged'
install -d -- "$stage/usr/share/doc/zupt-gui" "$stage/DEBIAN"
install -m 0644 -- gui/README.md "$stage/usr/share/doc/zupt-gui/README.md"
gzip -9n -c CHANGELOG.md >"$stage/usr/share/doc/zupt-gui/changelog.gz"
install -m 0644 -- LICENSE-AGPL-3.0 "$stage/usr/share/doc/zupt-gui/copyright"
gzip -9n -- "$stage/usr/share/man/man1/zupt-gui.1"
installed_kib=$(du -sk "$stage/usr" | awk '{print $1}')
cat >"$stage/DEBIAN/control" <<EOF
Package: zupt-gui
Version: $version
Section: utils
Priority: optional
Architecture: $ARCH
Depends: python3 (>= 3.9), python3-pyqt6 | python3-pyside6, vaptvupt (>= 3.0.0) | zupt (>= 2.2.3)
Provides: zupt-gui (= ${VERSION})
Replaces: zupt-gui (<< 1.2.0)
Conflicts: zupt-gui (<< 1.2.0)
Maintainer: Cristian Cezar Moisés <zupt@riseup.net>
Installed-Size: $INSTALLED_SIZE
Homepage: https://git.securityops.co/cristiancmoises/zupt
Description: Graphical interface for VaptVupt post-quantum backup utility
PySide6/PyQt6 frontend for VaptVupt (formerly zupt-gui in 1.x). Supports compression, extraction, key
management, and full disk backup/restore. Exposes both legacy --pq
and new --pq-sdk (libzuptsdk: HKDF combiner, key commitment, HPKE
binding, Argon2id) encryption modes.
Architecture: all
Depends: python3 (>= 3.9), python3-pyqt6 | python3-pyside6.qtwidgets, zupt (= $version)
Installed-Size: $installed_kib
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
Homepage: https://github.com/cristiancmoises/zupt
Description: Qt graphical interface for the ZUPT backup utility
The GUI creates, inspects, verifies, and extracts .zupt archives through the
separately packaged zupt command. Optional SDK and PQ-box controls are
shown only when the installed command reports those integrations enabled.
EOF
# Postinst: refresh icon cache + desktop database, print first-run guidance
cat > "$ROOT/DEBIAN/postinst" <<'POSTINST'
#!/bin/sh
set -e
if [ -x /usr/bin/update-desktop-database ]; then
update-desktop-database -q /usr/share/applications || true
fi
if [ -x /usr/bin/gtk-update-icon-cache ]; then
gtk-update-icon-cache -q /usr/share/icons/hicolor || true
forbidden=$(find "$stage" -type f \( \
-name '*.o' -o -name '*.obj' -o -name '*.a' -o -name '*.so' -o \
-name '*.so.*' -o -name '*.dll' -o -name '*.dylib' -o -name '*.exe' \
\) -print)
[[ -z $forbidden ]] || { printf '%s\n' "$forbidden" >&2; die 'compiled artifact in GUI package'; }
package_tmp=$work/$(basename -- "$output")
source_epoch=${SOURCE_DATE_EPOCH:-$(sed -n '1p' .source-date-epoch 2>/dev/null || true)}
[[ $source_epoch =~ ^[0-9]+$ ]] || die 'SOURCE_DATE_EPOCH is not available'
SOURCE_DATE_EPOCH=$source_epoch dpkg-deb -Zxz --build --root-owner-group \
"$stage" "$package_tmp" >/dev/null
dpkg-deb --info "$package_tmp" >/dev/null
dpkg-deb --contents "$package_tmp" >"$work/contents.txt"
grep -q './usr/bin/zupt-gui' "$work/contents.txt" || die 'GUI launcher missing from .deb'
if grep -Eq '(/usr/bin/vaptvupt-gui|\.(o|obj|a|so|so\.[^/]+|dll|dylib|exe)$)' "$work/contents.txt"; then
cat "$work/contents.txt" >&2
die 'forbidden compatibility alias or compiled artifact in .deb'
fi
# Friendly first-run check: warn the user if no Qt6 binding is installed.
# We don't fail the install (deb deps already enforce this); we just print
# clear guidance for users who saw "unmet dependencies" earlier.
if ! python3 -c 'import PyQt6.QtWidgets' 2>/dev/null \
&& ! python3 -c 'import PySide6.QtWidgets' 2>/dev/null; then
cat << 'MSG'
mkdir -p -- "$extract"
dpkg-deb --extract "$package_tmp" "$extract"
PYTHONDONTWRITEBYTECODE=1 python3 - <<PY
from pathlib import Path
p = Path("$extract/usr/lib/zupt-gui/zupt_gui.py")
compile(p.read_text(encoding="utf-8"), str(p), "exec")
PY
──────────────────────────────────────────────────────────────────────
vaptvupt-gui installed, but no Qt6 Python binding is available.
Install one of the following to enable the GUI:
Debian/Ubuntu/Mint: sudo apt install python3-pyqt6
Fedora/RHEL/Rocky: sudo dnf install python3-pyqt6
Arch/Manjaro: sudo pacman -S python-pyqt6
pip (any distro): pip install --user PySide6
After installing the binding, launch with: vaptvupt-gui
──────────────────────────────────────────────────────────────────────
MSG
fi
# Same friendly warning if zupt CLI not installed.
if ! command -v vaptvupt >/dev/null 2>&1 && ! command -v zupt >/dev/null 2>&1; then
cat << 'MSG'
──────────────────────────────────────────────────────────────────────
vaptvupt-gui needs the 'vaptvupt' CLI to function. Install it:
Debian/Ubuntu/Mint: sudo dpkg -i vaptvupt_3.0.0_amd64.deb
(followed by: sudo apt --fix-broken install)
──────────────────────────────────────────────────────────────────────
MSG
fi
exit 0
POSTINST
chmod 755 "$ROOT/DEBIAN/postinst"
cat > "$ROOT/DEBIAN/postrm" <<'POSTRM'
#!/bin/sh
set -e
if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then
if [ -x /usr/bin/update-desktop-database ]; then
update-desktop-database -q /usr/share/applications || true
fi
if [ -x /usr/bin/gtk-update-icon-cache ]; then
gtk-update-icon-cache -q /usr/share/icons/hicolor || true
fi
fi
POSTRM
chmod 755 "$ROOT/DEBIAN/postrm"
dpkg-deb -Zxz --build --root-owner-group "$ROOT" "/tmp/$PKG.deb"
echo "Built: /tmp/$PKG.deb"
dpkg-deb --info "/tmp/$PKG.deb" | head -12
mv -- "$package_tmp" "$output"
sha256sum "$output"
printf 'PASS: built and content-validated %s\n' "$output"

View file

@ -1,151 +1,175 @@
#!/bin/bash
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
# Build zupt-gui RPM. Falls back to SRPM-equivalent tarball if rpmbuild absent.
set -e
cd "$(dirname "$0")/.."
VERSION="${VERSION:-1.2.0}"
RPMROOT="/tmp/rpmbuild-vaptvupt-gui"
rm -rf "$RPMROOT"
mkdir -p "$RPMROOT"/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
# Build a real noarch RPM and source RPM. Run this in a native RPM build
# environment; there is deliberately no --nodeps or tarball fallback.
TMP="/tmp/vaptvupt-gui-$VERSION"
rm -rf "$TMP" && mkdir -p "$TMP/src" "$TMP/doc" "$TMP/assets"
cp gui/src/zupt_gui.py "$TMP/src/"
cp doc/vaptvupt-gui.1 "$TMP/doc/" 2>/dev/null || true
cp gui/README.md "$TMP/" 2>/dev/null || true
cp LICENSE "$TMP/" 2>/dev/null || true
[ -f gui/assets/zupt-icon.png ] && cp gui/assets/zupt-icon.png "$TMP/assets/"
tar -czf "$RPMROOT/SOURCES/vaptvupt-gui-$VERSION.tar.gz" -C /tmp "vaptvupt-gui-$VERSION"
set -Eeuo pipefail
umask 022
export LC_ALL=C
cat > "$RPMROOT/SPECS/vaptvupt-gui.spec" <<EOF
Name: vaptvupt-gui
Version: $VERSION
Release: 1%{?dist}
Summary: Graphical interface for VaptVupt post-quantum backup utility (formerly zupt-gui)
die() { printf 'FAIL: %s\n' "$*" >&2; exit 1; }
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
cd -- "$repo_root"
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
version=${VERSION:-$header_version}
[[ $version == "$header_version" && $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || \
die "VERSION '$version' does not match include/zupt.h '$header_version'"
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
mkdir -p -- "$dist_dir"
dist_dir=$(cd -- "$dist_dir" && pwd -P)
case $dist_dir/ in "$repo_root"/*) die 'DIST_DIR must be outside the repository' ;; esac
for command_name in make python3 rpmbuild rpm rpm2cpio cpio tar sha256sum; do
command -v -- "$command_name" >/dev/null 2>&1 || \
die "required command not found: $command_name"
done
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-gui-rpm.XXXXXXXX")
top=$work/rpmbuild
tree=$work/zupt-gui-$version
extract=$work/extract
mkdir -p -- "$top"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} \
"$tree"/{src,assets,doc} "$extract"
cleanup() {
make -C "$repo_root" clean >/dev/null 2>&1 || true
chmod -R u+rwX "$work" 2>/dev/null || true
rm -rf -- "$work"
}
trap cleanup EXIT HUP INT TERM
printf '[GUI rpm] validating source-only CLI dependency %s\n' "$version"
make clean
make -j"$jobs" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
make V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
./zupt version | grep -Fq "zupt $version" || die 'CLI version check failed'
install -m 0644 gui/src/zupt_gui.py "$tree/src/"
install -m 0644 gui/assets/zupt-icon.png "$tree/assets/"
install -m 0644 gui/packaging/zupt-gui.desktop "$tree/"
install -m 0644 doc/zupt-gui.1 "$tree/doc/"
install -m 0644 gui/README.md "$tree/README.md"
install -m 0644 LICENSE LICENSE-AGPL-3.0 "$tree/"
install -m 0644 gui/LICENSE-GUI "$tree/LICENSE-GUI"
install -m 0644 gui/assets/README.md "$tree/ASSET-PROVENANCE.md"
source_epoch=${SOURCE_DATE_EPOCH:-$(sed -n '1p' .source-date-epoch 2>/dev/null || true)}
[[ $source_epoch =~ ^[0-9]+$ ]] || die 'SOURCE_DATE_EPOCH is not available'
source_tar=$top/SOURCES/zupt-gui-$version.tar.gz
tar --sort=name --mtime="@$source_epoch" --owner=0 --group=0 --numeric-owner \
-czf "$source_tar" -C "$work" "zupt-gui-$version"
cat >"$top/SPECS/zupt-gui.spec" <<EOF
Name: zupt-gui
Version: $version
Release: 1
Summary: Qt graphical interface for the ZUPT backup utility
License: AGPL-3.0-or-later
URL: https://git.securityops.co/cristiancmoises/zupt
Source0: vaptvupt-gui-%{version}.tar.gz
URL: https://github.com/cristiancmoises/zupt
Source0: %{name}-%{version}.tar.gz
BuildArch: noarch
BuildRequires: python3 >= 3.9
Requires: python3 >= 3.9
Requires: (python3-qt6 or python3-pyside6 or python3-pyqt6)
Requires: (vaptvupt >= 3.0.0 or zupt >= 2.2.3)
Provides: zupt-gui = %{version}-%{release}
Obsoletes: zupt-gui < 1.2.0
Conflicts: zupt-gui < 1.2.0
Requires: zupt >= %{version}
%description
PySide6/PyQt6 frontend for VaptVupt (renamed from zupt-gui in 1.x). Supports compression, extraction, key
management, and full disk backup/restore. Exposes both legacy --pq and
new --pq-sdk (libzuptsdk: HKDF combiner, key commitment, HPKE binding,
Argon2id) encryption modes. Auto-detects whichever Qt6 binding is
installed at startup.
ZUPT GUI creates, inspects, verifies, and extracts .zupt archives through
the separately packaged zupt command. Optional SDK and PQ-box controls are
shown only when that command reports the corresponding integration enabled.
%prep
%autosetup
%build
# nothing to build; pure Python
%check
python3 -c 'from pathlib import Path; p=Path("src/zupt_gui.py"); compile(p.read_text(encoding="utf-8"), str(p), "exec")'
%install
install -Dm0644 src/zupt_gui.py %{buildroot}%{_datadir}/zupt-gui/zupt_gui.py
install -Dm0644 zupt-gui.desktop %{buildroot}%{_datadir}/applications/zupt-gui.desktop
install -Dm0644 assets/zupt-icon.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/zupt-gui.png
install -Dm0644 doc/zupt-gui.1 %{buildroot}%{_mandir}/man1/zupt-gui.1
mkdir -p %{buildroot}%{_bindir}
mkdir -p %{buildroot}%{_libdir}/vaptvupt-gui
mkdir -p %{buildroot}%{_datadir}/applications
mkdir -p %{buildroot}%{_datadir}/icons/hicolor/256x256/apps
mkdir -p %{buildroot}%{_mandir}/man1
install -m 644 src/zupt_gui.py %{buildroot}%{_libdir}/vaptvupt-gui/
cat > %{buildroot}%{_bindir}/vaptvupt-gui <<'WRAP'
cat >%{buildroot}%{_bindir}/zupt-gui <<'WRAP'
#!/bin/sh
exec python3 %{_libdir}/vaptvupt-gui/zupt_gui.py "\$@"
exec python3 %{_datadir}/zupt-gui/zupt_gui.py "\$@"
WRAP
chmod 755 %{buildroot}%{_bindir}/vaptvupt-gui
# v3.0.0: legacy zupt-gui symlink for one major version cycle
ln -sf vaptvupt-gui %{buildroot}%{_bindir}/zupt-gui
cat > %{buildroot}%{_datadir}/applications/vaptvupt-gui.desktop <<'DESKTOP'
[Desktop Entry]
Type=Application
Name=VaptVupt GUI
GenericName=Backup and Compression Utility
Comment=Post-quantum backup with HKDF combiner, key commitment, HPKE binding
Exec=vaptvupt-gui %f
Icon=vaptvupt-gui
Terminal=false
Categories=Utility;Archiving;Compression;Security;
StartupNotify=true
DESKTOP
[ -f doc/vaptvupt-gui.1 ] && install -m 644 doc/vaptvupt-gui.1 %{buildroot}%{_mandir}/man1/
[ -f assets/zupt-icon.png ] && install -m 644 assets/zupt-icon.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/vaptvupt-gui.png || true
# Generate placeholder icon if no real one exists
if [ ! -f %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/vaptvupt-gui.png ]; then
python3 -c "
import struct, zlib
def png(w, h, color):
raw = b''.join(b'\\0' + bytes(color) * w for _ in range(h))
def chunk(t, d): return struct.pack('>I', len(d)) + t + d + struct.pack('>I', zlib.crc32(t+d) & 0xffffffff)
return b'\\x89PNG\\r\\n\\x1a\\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0)) + chunk(b'IDAT', zlib.compress(raw)) + chunk(b'IEND', b'')
open('%{buildroot}%{_datadir}/icons/hicolor/256x256/apps/vaptvupt-gui.png','wb').write(png(256, 256, (88, 92, 215)))
"
fi
%post
if [ -x /usr/bin/update-desktop-database ]; then
update-desktop-database -q /usr/share/applications || :
fi
if [ -x /usr/bin/gtk-update-icon-cache ]; then
gtk-update-icon-cache -q /usr/share/icons/hicolor || :
fi
%postun
if [ \$1 -eq 0 ]; then
if [ -x /usr/bin/update-desktop-database ]; then
update-desktop-database -q /usr/share/applications || :
fi
fi
chmod 0755 %{buildroot}%{_bindir}/zupt-gui
%files
%doc README.md
%license LICENSE
%{_bindir}/vaptvupt-gui
%license LICENSE LICENSE-AGPL-3.0 LICENSE-GUI
%doc README.md ASSET-PROVENANCE.md
%{_bindir}/zupt-gui
%{_libdir}/vaptvupt-gui/zupt_gui.py
%{_datadir}/applications/vaptvupt-gui.desktop
%{_datadir}/icons/hicolor/256x256/apps/vaptvupt-gui.png
%{_datadir}/zupt-gui/zupt_gui.py
%{_datadir}/applications/zupt-gui.desktop
%{_datadir}/icons/hicolor/256x256/apps/zupt-gui.png
%{_mandir}/man1/zupt-gui.1*
%changelog
* Sun May 25 2026 Cristian Cezar Moisés <zupt@riseup.net> - $VERSION-1
- v1.2.0: package renamed zupt-gui → vaptvupt-gui (parent CLI also
renamed; INPI Brasil trademark on "Zupt"). Legacy /usr/bin/zupt-gui
symlink preserved. GUI binary-discovery bug fix: _find_vaptvupt
with liveness check + discovery log via VAPTVUPT_DEBUG=1.
* Mon Apr 27 2026 Cristian Cezar Moisés <zupt@riseup.net> - 1.1.1-1
- Cross-binding (PySide6 OR PyQt6 auto-detected)
- SDK v2 mode toggles in compress/extract/keygen tabs
- Man page added
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - $version-1
- Package the integrated GUI under its restored ZUPT identity.
- Require the separately built source-only baseline CLI package.
EOF
if command -v rpmbuild >/dev/null 2>&1; then
# On Debian/Ubuntu, the host's `rpm` doesn't see `python3` as an RPM
# (it's a deb), so the BuildRequires check would fail. Use --nodeps
# since the runtime check on the target system is what actually
# matters. The Requires: lines still apply on install.
rpmbuild --define "_topdir $RPMROOT" --nodeps -bb "$RPMROOT/SPECS/vaptvupt-gui.spec" 2>&1 | tail -3
if [ -f "$RPMROOT/RPMS/noarch/vaptvupt-gui-${VERSION}-1.noarch.rpm" ]; then
cp "$RPMROOT/RPMS/noarch/vaptvupt-gui-${VERSION}-1.noarch.rpm" \
"/tmp/vaptvupt-gui-${VERSION}-1.noarch.rpm"
echo "Built: /tmp/vaptvupt-gui-${VERSION}-1.noarch.rpm"
fi
cp "$RPMROOT/RPMS/noarch/zupt-gui-$VERSION-1."*.rpm /tmp/ 2>/dev/null || true
ls /tmp/zupt-gui-$VERSION-*.rpm 2>/dev/null
else
SRPM_TAR="/tmp/zupt-gui-$VERSION.srpm.tar.gz"
tar -czf "$SRPM_TAR" -C "$RPMROOT" SPECS SOURCES
echo "rpmbuild unavailable; SRPM-equivalent at: $SRPM_TAR"
rpmbuild --define "_topdir $top" -ba "$top/SPECS/zupt-gui.spec"
mapfile -t main_rpms < <(find "$top/RPMS" -type f -name "zupt-gui-$version-*.noarch.rpm" -print | sort)
mapfile -t source_rpms < <(find "$top/SRPMS" -type f -name "zupt-gui-$version-*.src.rpm" -print | sort)
[[ ${#main_rpms[@]} -eq 1 ]] || die "expected one GUI RPM, found ${#main_rpms[@]}"
[[ ${#source_rpms[@]} -eq 1 ]] || die "expected one GUI source RPM, found ${#source_rpms[@]}"
main_rpm=${main_rpms[0]}
source_rpm=${source_rpms[0]}
[[ $(rpm -qp --qf '%{NAME}' "$main_rpm") == zupt-gui ]] || \
die 'GUI binary RPM name metadata is not zupt-gui'
[[ $(rpm -qp --qf '%{VERSION}' "$main_rpm") == "$version" ]] || \
die 'GUI binary RPM version metadata does not match the release'
[[ $(rpm -qp --qf '%{RELEASE}' "$main_rpm") == 1 ]] || \
die 'GUI binary RPM release metadata is not 1'
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$main_rpm") == '(none)' ]] || \
die 'GUI binary RPM is marked as a source package'
[[ $(rpm -qp --qf '%{SOURCERPM}' "$main_rpm") == "$(basename -- "$source_rpm")" ]] || \
die 'GUI binary RPM does not reference the matching source RPM'
[[ $(rpm -qp --qf '%{NAME}' "$source_rpm") == zupt-gui ]] || \
die 'GUI source RPM name metadata is not zupt-gui'
[[ $(rpm -qp --qf '%{VERSION}' "$source_rpm") == "$version" ]] || \
die 'GUI source RPM version metadata does not match the release'
[[ $(rpm -qp --qf '%{RELEASE}' "$source_rpm") == 1 ]] || \
die 'GUI source RPM release metadata is not 1'
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$source_rpm") == 1 ]] || \
die 'GUI source RPM is not marked as a source package'
[[ $(rpm -qp --qf '%{SOURCERPM}' "$source_rpm") == '(none)' ]] || \
die 'GUI source RPM unexpectedly references another source RPM'
mapfile -t source_members < <(rpm -qpl "$source_rpm" | sort)
expected_source_members=("zupt-gui-${version}.tar.gz" zupt-gui.spec)
mapfile -t expected_source_members < <(printf '%s\n' "${expected_source_members[@]}" | sort)
[[ ${#source_members[@]} -eq 2 && \
${source_members[*]} == "${expected_source_members[*]}" ]] || \
die 'GUI source RPM payload is not the exact Source0/spec pair'
rpm -qpl "$main_rpm" >"$work/contents.txt"
grep -q '^/usr/bin/zupt-gui$' "$work/contents.txt" || die 'GUI launcher missing from RPM'
if grep -Eq '(^/usr/bin/vaptvupt-gui$|\.(o|obj|a|so|so\.[^/]+|dll|dylib|exe)$)' "$work/contents.txt"; then
cat "$work/contents.txt" >&2
die 'forbidden compatibility alias or compiled artifact in GUI RPM'
fi
(cd -- "$extract" && rpm2cpio "$main_rpm" | cpio -idm --quiet)
PYTHONDONTWRITEBYTECODE=1 python3 - <<PY
from pathlib import Path
p = Path("$extract/usr/share/zupt-gui/zupt_gui.py")
compile(p.read_text(encoding="utf-8"), str(p), "exec")
PY
for artifact in "$main_rpm" "$source_rpm"; do
destination=$dist_dir/$(basename -- "$artifact")
[[ ! -e $destination ]] || die "refusing to overwrite existing output: $destination"
cp -- "$artifact" "$destination"
sha256sum "$destination"
done
printf 'PASS: built and content-validated GUI RPM and source RPM in %s\n' "$dist_dir"

View file

@ -1,339 +0,0 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
"""
Build a binary RPM for zupt without rpmbuild.
Constructs an RPM-format file directly from the file tree we have for deb.
This is intentionally minimal but produces a valid RPM that:
- Can be installed via `rpm -i` on RHEL/Fedora and other RPM-based distributions
- Contains correct dependency info
- Has working pre/post scripts
- Includes the binary, library, headers, docs, license
"""
import struct, os, sys, hashlib, gzip, io, time, subprocess
VERSION = os.environ.get('VERSION', '2.2.3')
RELEASE = '1'
ARCH = 'x86_64'
NAME = 'zupt'
# RPM tag values (from rpmtag.h)
RPMTAG_NAME = 1000
RPMTAG_VERSION = 1001
RPMTAG_RELEASE = 1002
RPMTAG_SUMMARY = 1004
RPMTAG_DESCRIPTION = 1005
RPMTAG_BUILDTIME = 1006
RPMTAG_BUILDHOST = 1007
RPMTAG_SIZE = 1009
RPMTAG_DISTRIBUTION = 1010
RPMTAG_VENDOR = 1011
RPMTAG_LICENSE = 1014
RPMTAG_PACKAGER = 1015
RPMTAG_GROUP = 1016
RPMTAG_URL = 1020
RPMTAG_OS = 1021
RPMTAG_ARCH = 1022
RPMTAG_PREIN = 1023
RPMTAG_POSTIN = 1024
RPMTAG_PREUN = 1025
RPMTAG_POSTUN = 1026
RPMTAG_FILESIZES = 1028
RPMTAG_FILEMODES = 1030
RPMTAG_FILERDEVS = 1033
RPMTAG_FILEMTIMES = 1034
RPMTAG_FILEDIGESTS = 1035
RPMTAG_FILELINKTOS = 1036
RPMTAG_FILEFLAGS = 1037
RPMTAG_FILEUSERNAME = 1039
RPMTAG_FILEGROUPNAME = 1040
RPMTAG_PROVIDENAME = 1047
RPMTAG_REQUIREFLAGS = 1048
RPMTAG_REQUIRENAME = 1049
RPMTAG_REQUIREVERSION = 1050
RPMTAG_BASENAMES = 1117
RPMTAG_DIRNAMES = 1118
RPMTAG_DIRINDEXES = 1116
RPMTAG_PAYLOADFORMAT = 1124
RPMTAG_PAYLOADCOMPRESSOR = 1125
RPMTAG_FILEDIGESTALGO = 5011
# Type codes
RPM_NULL_TYPE = 0
RPM_CHAR_TYPE = 1
RPM_INT8_TYPE = 2
RPM_INT16_TYPE = 3
RPM_INT32_TYPE = 4
RPM_INT64_TYPE = 5
RPM_STRING_TYPE = 6
RPM_BIN_TYPE = 7
RPM_STRING_ARRAY_TYPE = 8
class Header:
def __init__(self):
self.entries = [] # (tag, type, value)
def add(self, tag, typ, value):
self.entries.append((tag, typ, value))
def serialize(self):
# Build store + index
store = bytearray()
index = []
for tag, typ, value in self.entries:
if typ == RPM_STRING_TYPE:
count = 1
data = value.encode('utf-8') + b'\x00'
offset = len(store)
store.extend(data)
elif typ == RPM_STRING_ARRAY_TYPE:
count = len(value)
data = b''.join(s.encode('utf-8') + b'\x00' for s in value)
offset = len(store)
store.extend(data)
elif typ == RPM_INT32_TYPE:
if not isinstance(value, list):
value = [value]
count = len(value)
# align to 4
while len(store) % 4: store.append(0)
offset = len(store)
for v in value:
store.extend(struct.pack('>I', v & 0xFFFFFFFF))
elif typ == RPM_INT16_TYPE:
if not isinstance(value, list):
value = [value]
count = len(value)
while len(store) % 2: store.append(0)
offset = len(store)
for v in value:
store.extend(struct.pack('>H', v & 0xFFFF))
elif typ == RPM_BIN_TYPE:
count = len(value)
offset = len(store)
store.extend(value)
elif typ == RPM_NULL_TYPE:
count = 1
offset = 0
else:
raise ValueError(f"Unsupported type {typ}")
index.append(struct.pack('>IIII', tag, typ, offset, count))
index_bytes = b''.join(index)
# Header magic + reserved + index count + store size
out = struct.pack('>3sBI4sII', b'\x8e\xad\xe8', 1, 0, b'\x00\x00\x00\x00',
len(self.entries), len(store))
out += index_bytes + bytes(store)
return out
def make_cpio(file_list, source_root, payload_size_out):
"""Build a cpio archive (newc format) of the files."""
out = io.BytesIO()
inode = 1
total = 0
for arc_path, src_path, mode, is_dir, link_target in file_list:
if is_dir:
data = b''
file_size = 0
elif link_target is not None:
data = link_target.encode('utf-8')
file_size = len(data)
else:
with open(src_path, 'rb') as f:
data = f.read()
file_size = len(data)
total += file_size
name = ('.' + arc_path).encode('utf-8') + b'\x00'
# newc header: 110 bytes
header = (
b'070701'
+ format(inode, '08x').encode('ascii')
+ format(mode, '08x').encode('ascii')
+ b'00000000' # uid
+ b'00000000' # gid
+ b'00000001' # nlink
+ format(int(time.time()), '08x').encode('ascii')
+ format(file_size, '08x').encode('ascii')
+ b'00000000' * 4 # devmajor/minor + rdevmajor/minor
+ format(len(name), '08x').encode('ascii')
+ b'00000000' # check
)
out.write(header)
out.write(name)
# pad to 4
pad = (4 - ((len(header) + len(name)) % 4)) % 4
out.write(b'\x00' * pad)
out.write(data)
# pad data to 4
pad = (4 - (file_size % 4)) % 4
out.write(b'\x00' * pad)
inode += 1
# Trailer
trailer_name = b'TRAILER!!!\x00'
out.write(b'070701' + b'0' * 8 + b'0' * 8 + b'0' * 8 + b'0' * 8 + b'00000001'
+ b'0' * 8 + b'0' * 8 + b'0' * 8 + b'0' * 8 + b'0' * 8 + b'0' * 8
+ format(len(trailer_name), '08x').encode('ascii') + b'0' * 8)
out.write(trailer_name)
pad = (4 - ((110 + len(trailer_name)) % 4)) % 4
out.write(b'\x00' * pad)
payload_size_out[0] = total
return out.getvalue()
def main():
# Files to include (source_path inside our deb tree)
deb_root = f'/tmp/zupt_{VERSION}_amd64'
files = [] # (arc_path, source_path, mode, is_dir, link_target)
for root, dirs, fnames in os.walk(deb_root):
for d in sorted(dirs):
full = os.path.join(root, d)
arc = full[len(deb_root):]
files.append((arc, full, 0o40755, True, None))
for fn in sorted(fnames):
full = os.path.join(root, fn)
arc = full[len(deb_root):]
if 'DEBIAN' in arc:
continue
if os.path.islink(full):
files.append((arc, full, 0o120777, False, os.readlink(full)))
else:
mode = 0o100755 if os.access(full, os.X_OK) else 0o100644
files.append((arc, full, mode, False, None))
# Sort and build basename/dirname/dirindex arrays
files.sort(key=lambda x: x[0])
basenames = []
dirnames_set = []
dirname_to_idx = {}
dirindexes = []
filesizes = []
filemodes = []
filemtimes = []
filedigests = []
filelinktos = []
filerdevs = []
fileflags = []
fileuser = []
filegroup = []
for arc, src, mode, is_dir, link in files:
d, b = os.path.split(arc)
d = d + '/'
if d not in dirname_to_idx:
dirname_to_idx[d] = len(dirnames_set)
dirnames_set.append(d)
basenames.append(b or '.')
dirindexes.append(dirname_to_idx[d])
if is_dir:
filesizes.append(0)
filedigests.append('')
filelinktos.append('')
elif link:
filesizes.append(len(link))
filedigests.append('')
filelinktos.append(link)
else:
filesizes.append(os.path.getsize(src))
with open(src, 'rb') as f:
filedigests.append(hashlib.sha256(f.read()).hexdigest())
filelinktos.append('')
filemodes.append(mode)
filemtimes.append(int(time.time()))
filerdevs.append(0)
fileflags.append(0)
fileuser.append('root')
filegroup.append('root')
payload_size = [0]
cpio_data = make_cpio(files, deb_root, payload_size)
# Compress payload with gzip
gz_payload = gzip.compress(cpio_data)
# Build main header
h = Header()
h.add(RPMTAG_NAME, RPM_STRING_TYPE, NAME)
h.add(RPMTAG_VERSION, RPM_STRING_TYPE, VERSION)
h.add(RPMTAG_RELEASE, RPM_STRING_TYPE, RELEASE)
h.add(RPMTAG_SUMMARY, RPM_STRING_ARRAY_TYPE, ['Post-quantum backup compression utility'])
h.add(RPMTAG_DESCRIPTION, RPM_STRING_ARRAY_TYPE, [
'Zupt provides hybrid post-quantum encryption (ML-KEM-768 + X25519)\n'
'with multi-threaded compression and full-disk backup support.\n'
'Bundled with libzuptsdk for HKDF-SHA3 hybrid KDF, key commitment,\n'
'HPKE binding, and anti-fault decapsulation.'
])
h.add(RPMTAG_BUILDTIME, RPM_INT32_TYPE, int(time.time()))
h.add(RPMTAG_BUILDHOST, RPM_STRING_TYPE, 'localhost')
h.add(RPMTAG_SIZE, RPM_INT32_TYPE, sum(filesizes))
h.add(RPMTAG_LICENSE, RPM_STRING_TYPE, 'AGPL-3.0-or-later')
h.add(RPMTAG_PACKAGER, RPM_STRING_TYPE, 'Cristian Cezar Moises <zupt@riseup.net>')
h.add(RPMTAG_GROUP, RPM_STRING_ARRAY_TYPE, ['Applications/Archiving'])
h.add(RPMTAG_URL, RPM_STRING_TYPE, 'https://git.securityops.co/cristiancmoises/zupt')
h.add(RPMTAG_OS, RPM_STRING_TYPE, 'linux')
h.add(RPMTAG_ARCH, RPM_STRING_TYPE, ARCH)
h.add(RPMTAG_POSTIN, RPM_STRING_TYPE, '/sbin/ldconfig\n')
h.add(RPMTAG_POSTUN, RPM_STRING_TYPE, '/sbin/ldconfig\n')
h.add(RPMTAG_BASENAMES, RPM_STRING_ARRAY_TYPE, basenames)
h.add(RPMTAG_DIRNAMES, RPM_STRING_ARRAY_TYPE, dirnames_set)
h.add(RPMTAG_DIRINDEXES, RPM_INT32_TYPE, dirindexes)
h.add(RPMTAG_FILESIZES, RPM_INT32_TYPE, filesizes)
h.add(RPMTAG_FILEMODES, RPM_INT16_TYPE, filemodes)
h.add(RPMTAG_FILEMTIMES, RPM_INT32_TYPE, filemtimes)
h.add(RPMTAG_FILEDIGESTS, RPM_STRING_ARRAY_TYPE, filedigests)
h.add(RPMTAG_FILELINKTOS, RPM_STRING_ARRAY_TYPE, filelinktos)
h.add(RPMTAG_FILEFLAGS, RPM_INT32_TYPE, fileflags)
h.add(RPMTAG_FILERDEVS, RPM_INT16_TYPE, filerdevs)
h.add(RPMTAG_FILEUSERNAME, RPM_STRING_ARRAY_TYPE, fileuser)
h.add(RPMTAG_FILEGROUPNAME, RPM_STRING_ARRAY_TYPE, filegroup)
h.add(RPMTAG_PROVIDENAME, RPM_STRING_ARRAY_TYPE, [NAME])
h.add(RPMTAG_REQUIRENAME, RPM_STRING_ARRAY_TYPE, ['libargon2.so.1()(64bit)', 'libcrypto.so.3()(64bit)', 'libc.so.6()(64bit)'])
h.add(RPMTAG_REQUIREFLAGS, RPM_INT32_TYPE, [0, 0, 0])
h.add(RPMTAG_REQUIREVERSION, RPM_STRING_ARRAY_TYPE, ['', '', ''])
h.add(RPMTAG_PAYLOADFORMAT, RPM_STRING_TYPE, 'cpio')
h.add(RPMTAG_PAYLOADCOMPRESSOR, RPM_STRING_TYPE, 'gzip')
h.add(RPMTAG_FILEDIGESTALGO, RPM_INT32_TYPE, 8) # SHA-256
main_hdr = h.serialize()
# Signature header (minimal: just size of payload after sig hdr)
sig = Header()
sig_payload = main_hdr + gz_payload
sig.add(1000, RPM_INT32_TYPE, len(sig_payload)) # SIZE
sig.add(1004, RPM_BIN_TYPE, hashlib.md5(sig_payload).digest()) # MD5
sig_bytes = sig.serialize()
# Pad sig hdr to 8-byte boundary
pad = (8 - (len(sig_bytes) % 8)) % 8
sig_bytes += b'\x00' * pad
# Lead (96 bytes)
lead = struct.pack('>4sBBhh66sHH16s',
b'\xed\xab\xee\xdb', # magic
3, 0, # major, minor
0, # type (binary)
1, # archnum
NAME.encode().ljust(66, b'\x00'),
1, # osnum
5, # signature_type
b'\x00' * 16)
out_path = f'/tmp/{NAME}-{VERSION}-{RELEASE}.{ARCH}.rpm'
with open(out_path, 'wb') as f:
f.write(lead)
f.write(sig_bytes)
f.write(main_hdr)
f.write(gz_payload)
print(f'Built: {out_path} ({os.path.getsize(out_path)} bytes)')
# Try rpm -Kvv to verify if rpm is installed
try:
result = subprocess.run(['rpm', '-qpi', out_path], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
print(result.stdout[:500])
except Exception:
pass
if __name__ == '__main__':
main()

View file

@ -1,195 +1,154 @@
#!/bin/bash
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
#
# Build self-contained vaptvupt RPM (formerly zupt). Bundles
# libzuptsdk.so.2 under /usr/lib/vaptvupt/ so users do NOT need
# a separate libzuptsdk package. Installs a legacy /usr/bin/zupt
# symlink for one major version cycle.
set -e
cd "$(dirname "$0")/.."
set -Eeuo pipefail
VERSION="${VERSION:-3.0.0}"
ARCH="${ARCH:-x86_64}"
RELEASE="1"
PKGNAME="vaptvupt"
LEGACY="zupt"
umask 022
export LC_ALL=C
SDK_LIB="vendor/zuptsdk/libzuptsdk.so.2.0.0"
if [ ! -f "$SDK_LIB" ]; then
echo "ERROR: $SDK_LIB not found." >&2
die() {
printf 'FAIL: %s\n' "$*" >&2
exit 1
fi
}
echo "[rpm] Building $PKGNAME"
make clean >/dev/null 2>&1 || true
make -j"$(nproc)" >/dev/null
echo "[rpm] Patching rpath -> /usr/lib/$PKGNAME:/usr/lib64/$PKGNAME"
patchelf --set-rpath "/usr/lib/$PKGNAME:/usr/lib64/$PKGNAME" $PKGNAME
if ! readelf -d $PKGNAME | grep -q "RUNPATH.*\[/usr/lib/$PKGNAME:/usr/lib64/$PKGNAME\]"; then
echo "ERROR: $PKGNAME does not have correct RUNPATH" >&2
exit 1
fi
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
cd -- "$repo_root"
if ! command -v rpmbuild >/dev/null 2>&1; then
echo "[rpm] rpmbuild not found; install rpm package to proceed"
exit 1
fi
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
version=${VERSION:-$header_version}
[[ -n $version && $version == "$header_version" ]] || \
die "VERSION '$version' does not match include/zupt.h '$header_version'"
[[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "invalid package version: $version"
RPMROOT="/tmp/rpmbuild-$PKGNAME"
rm -rf "$RPMROOT"
mkdir -p "$RPMROOT"/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
spec=packaging/opensuse/zupt.spec
[[ -f $spec ]] || die "spec file not found: $spec"
spec_version=$(sed -n 's/^Version:[[:space:]]*//p' "$spec" | head -n 1)
[[ $spec_version == "$version" ]] || die "spec version '$spec_version' does not match '$version'"
STAGE="/tmp/$PKGNAME-rpm-stage-${VERSION}"
rm -rf "$STAGE"
mkdir -p "$STAGE/$PKGNAME-${VERSION}/completions"
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
mkdir -p -- "$dist_dir"
dist_dir=$(cd -- "$dist_dir" && pwd -P)
cp $PKGNAME "$STAGE/$PKGNAME-${VERSION}/$PKGNAME"
cp "$SDK_LIB" "$STAGE/$PKGNAME-${VERSION}/libzuptsdk.so.2.0.0"
cp "vendor/pqvaptvupt/libpqvaptvupt.so.0.6.0" "$STAGE/$PKGNAME-${VERSION}/libpqvaptvupt.so.0.6.0"
cp README.md CHANGELOG.md SECURITY.md AUDIT.md LICENSE "$STAGE/$PKGNAME-${VERSION}/"
[ -f doc/vaptvupt.1 ] && cp doc/vaptvupt.1 "$STAGE/$PKGNAME-${VERSION}/$PKGNAME.1"
[ -f completions/vaptvupt.bash ] && cp completions/vaptvupt.bash "$STAGE/$PKGNAME-${VERSION}/completions/"
[ -f completions/_vaptvupt ] && cp completions/_vaptvupt "$STAGE/$PKGNAME-${VERSION}/completions/"
[ -f completions/vaptvupt.fish ] && cp completions/vaptvupt.fish "$STAGE/$PKGNAME-${VERSION}/completions/"
tar -czf "$RPMROOT/SOURCES/$PKGNAME-${VERSION}.tar.gz" -C "$STAGE" "$PKGNAME-${VERSION}"
for command_name in make git rpmbuild rpm rpm2cpio cpio date readelf sha256sum tar; do
command -v -- "$command_name" >/dev/null 2>&1 || die "required command not found: $command_name"
done
cat > "$RPMROOT/SPECS/$PKGNAME.spec" <<EOF
Name: $PKGNAME
Version: $VERSION
Release: ${RELEASE}%{?dist}
Summary: Post-quantum backup compression utility (formerly zupt)
License: AGPL-3.0-or-later AND GPL-3.0-or-later
URL: https://git.securityops.co/cristiancmoises/zupt
Source0: $PKGNAME-%{version}.tar.gz
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-rpm.XXXXXXXX")
top=$work/rpmbuild
extract=$work/extract
mkdir -p -- "$top/BUILD" "$top/BUILDROOT" "$top/RPMS" "$top/SOURCES" \
"$top/SPECS" "$top/SRPMS" "$extract"
# v3.0.0 rename — INPI Brasil trademark on the prior name "Zupt".
# Cleanly supersede legacy 'zupt' RPMs.
Provides: $LEGACY = %{version}-%{release}
Obsoletes: $LEGACY < 3.0.0
Conflicts: $LEGACY < 3.0.0
cleanup() {
chmod -R u+rwX "$work" 2>/dev/null || true
rm -rf -- "$work"
}
trap cleanup EXIT HUP INT TERM
Requires: libargon2
Requires: openssl-libs >= 3.0
AutoReqProv: no
source_tar=$top/SOURCES/zupt-${version}.tar.gz
printf '[rpm] creating audited source archive for ZUPT %s\n' "$version"
make DIST_TARBALL="$source_tar" WITH_SDK=0 WITH_PQBOX=0 dist
archive_version=$(tar -xOf "$source_tar" "zupt-${version}/include/zupt.h" | \
sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p')
[[ $archive_version == "$version" ]] || die "source archive version is '$archive_version', expected '$version'"
%global debug_package %{nil}
%global __os_install_post %{nil}
%global _build_id_links none
install -m 0644 "$spec" "$top/SPECS/zupt.spec"
# OBS converts zupt.changes into RPM changelog metadata. Standalone
# rpmbuild does not, so add an equivalent release entry only to the temporary
# spec used for this release artifact.
changelog_sections=$(grep -Ec '^%changelog[[:space:]]*$' "$top/SPECS/zupt.spec" || true)
[[ $changelog_sections -eq 1 ]] || \
die "expected exactly one %changelog section, found $changelog_sections"
source_epoch=${SOURCE_DATE_EPOCH:-$(sed -n '1p' .source-date-epoch 2>/dev/null || true)}
[[ $source_epoch =~ ^[0-9]+$ ]] || die 'SOURCE_DATE_EPOCH is not available'
changelog_date=$(date -u --date="@$source_epoch" '+%a %b %d %Y')
cat >> "$top/SPECS/zupt.spec" <<EOF
%description
VaptVupt (renamed from Zupt in v3.0.0 due to INPI Brasil trademark
on the prior name) is a backup-oriented compression utility with
hybrid post-quantum encryption (ML-KEM-768 + X25519). Provides
AES-256-CTR + HMAC-SHA256 authenticated encryption, multi-threaded
compression, full-disk backup/restore, block-level deduplication,
and embeds the VaptVupt 2.48.5 LZ + ANS codec with AVX2 and NEON
SIMD acceleration. The libzuptsdk shared library is bundled under
/usr/lib/$PKGNAME -- no separate package required.
The on-disk archive extension is unchanged (.zupt); v2.x and v3.0.0
archives are bidirectionally compatible. The legacy /usr/bin/zupt
symlink is preserved for one major version cycle.
%prep
%setup -q
%build
# Pre-built before rpmbuild was invoked; nothing to do.
%install
mkdir -p %{buildroot}%{_bindir}
mkdir -p %{buildroot}%{_libdir}/$PKGNAME
mkdir -p %{buildroot}%{_docdir}/$PKGNAME
mkdir -p %{buildroot}%{_licensedir}/$PKGNAME
mkdir -p %{buildroot}%{_mandir}/man1
mkdir -p %{buildroot}%{_datadir}/bash-completion/completions
mkdir -p %{buildroot}%{_datadir}/zsh/site-functions
mkdir -p %{buildroot}%{_datadir}/fish/vendor_completions.d
install -m 755 $PKGNAME %{buildroot}%{_bindir}/$PKGNAME
ln -sf $PKGNAME %{buildroot}%{_bindir}/$LEGACY
install -m 755 libzuptsdk.so.2.0.0 %{buildroot}%{_libdir}/$PKGNAME/libzuptsdk.so.2.0.0
ln -sf libzuptsdk.so.2.0.0 %{buildroot}%{_libdir}/$PKGNAME/libzuptsdk.so.2
ln -sf libzuptsdk.so.2.0.0 %{buildroot}%{_libdir}/$PKGNAME/libzuptsdk.so
install -m 755 libpqvaptvupt.so.0.6.0 %{buildroot}%{_libdir}/$PKGNAME/libpqvaptvupt.so.0.6.0
ln -sf libpqvaptvupt.so.0.6.0 %{buildroot}%{_libdir}/$PKGNAME/libpqvaptvupt.so.0
ln -sf libpqvaptvupt.so.0.6.0 %{buildroot}%{_libdir}/$PKGNAME/libpqvaptvupt.so
install -m 644 README.md CHANGELOG.md SECURITY.md AUDIT.md %{buildroot}%{_docdir}/$PKGNAME/
install -m 644 LICENSE %{buildroot}%{_licensedir}/$PKGNAME/
if [ -f $PKGNAME.1 ]; then
install -m 644 $PKGNAME.1 %{buildroot}%{_mandir}/man1/$PKGNAME.1
gzip -9n %{buildroot}%{_mandir}/man1/$PKGNAME.1
ln -sf $PKGNAME.1.gz %{buildroot}%{_mandir}/man1/$LEGACY.1.gz
fi
if [ -f completions/vaptvupt.bash ]; then
install -m 644 completions/vaptvupt.bash %{buildroot}%{_datadir}/bash-completion/completions/$PKGNAME
ln -sf $PKGNAME %{buildroot}%{_datadir}/bash-completion/completions/$LEGACY
fi
if [ -f completions/_vaptvupt ]; then
install -m 644 completions/_vaptvupt %{buildroot}%{_datadir}/zsh/site-functions/_$PKGNAME
ln -sf _$PKGNAME %{buildroot}%{_datadir}/zsh/site-functions/_$LEGACY
fi
if [ -f completions/vaptvupt.fish ]; then
install -m 644 completions/vaptvupt.fish %{buildroot}%{_datadir}/fish/vendor_completions.d/$PKGNAME.fish
fi
%files
%license %{_licensedir}/$PKGNAME/LICENSE
%doc %{_docdir}/$PKGNAME/README.md
%doc %{_docdir}/$PKGNAME/CHANGELOG.md
%doc %{_docdir}/$PKGNAME/SECURITY.md
%doc %{_docdir}/$PKGNAME/AUDIT.md
%{_bindir}/$PKGNAME
%{_bindir}/$LEGACY
%dir %{_libdir}/$PKGNAME
%{_libdir}/$PKGNAME/libzuptsdk.so
%{_libdir}/$PKGNAME/libzuptsdk.so.2
%{_libdir}/$PKGNAME/libzuptsdk.so.2.0.0
%{_libdir}/$PKGNAME/libpqvaptvupt.so
%{_libdir}/$PKGNAME/libpqvaptvupt.so.0
%{_libdir}/$PKGNAME/libpqvaptvupt.so.0.6.0
%{_mandir}/man1/$PKGNAME.1.gz
%{_mandir}/man1/$LEGACY.1.gz
%{_datadir}/bash-completion/completions/$PKGNAME
%{_datadir}/bash-completion/completions/$LEGACY
%{_datadir}/zsh/site-functions/_$PKGNAME
%{_datadir}/zsh/site-functions/_$LEGACY
%{_datadir}/fish/vendor_completions.d/$PKGNAME.fish
%changelog
* Sun May 25 2026 Cristian Cezar Moises <zupt@riseup.net> - $VERSION-$RELEASE
- v3.0.0: Renamed from "Zupt" to "VaptVupt" because of a prior INPI
Brasil trademark on "Zupt". Archive extension .zupt is preserved;
v2.x and v3.0.0 archives are bidirectionally compatible. Legacy
/usr/bin/zupt is installed as a symlink to /usr/bin/vaptvupt.
- Integrated VaptVupt LZ + ANS codec 2.48.5: fixes csz==0 heap-
buffer-overflow READ in vv_dstream_decompress_chunk (libFuzzer-
found, medium severity), UBSan-safe pointer arithmetic in
vv_copy_match.
- Enhanced manpage (597 lines, was 422): POST-QUANTUM ENCRYPTION,
PERFORMANCE table, SECURITY/threat-model, ENVIRONMENT and
EXIT STATUS sections.
- Fixed GUI binary-discovery bug (PATH-missing-/usr/bin scenario);
GUI now does liveness check + logs discovery to stderr with
VAPTVUPT_DEBUG=1.
- 91/91 distro-safe regression suite green; F-09 byte sweep
0/1827 silent accepts; F-06 HMAC fuzz 0/2000 silent accepts.
* $changelog_date Cristian Cezar Moisés <sac@securityops.co> - $version-0
- Build the release package from audited source with optional SDK and PQBOX
features disabled.
EOF
rpmbuild --define "_topdir $top" -ba "$top/SPECS/zupt.spec"
rpmbuild --define "_topdir $RPMROOT" \
--define "_binary_payload w2.gzdio" \
-bb "$RPMROOT/SPECS/$PKGNAME.spec" 2>&1 | tail -5
mapfile -t main_rpms < <(find "$top/RPMS" -type f -name "zupt-${version}-*.rpm" \
! -name '*-debuginfo-*' ! -name '*-debugsource-*' -print | sort)
[[ ${#main_rpms[@]} -eq 1 ]] || die "expected one main RPM, found ${#main_rpms[@]}"
main_rpm=${main_rpms[0]}
RPM_PATH=$(find "$RPMROOT/RPMS" -name "$PKGNAME-${VERSION}-*.rpm" | head -1)
if [ -n "$RPM_PATH" ]; then
cp "$RPM_PATH" "/tmp/$PKGNAME-${VERSION}-${RELEASE}.${ARCH}.rpm"
echo ""
echo "Built: /tmp/$PKGNAME-${VERSION}-${RELEASE}.${ARCH}.rpm ($(du -h "/tmp/$PKGNAME-${VERSION}-${RELEASE}.${ARCH}.rpm" | cut -f1))"
rpm -qpi "/tmp/$PKGNAME-${VERSION}-${RELEASE}.${ARCH}.rpm" 2>&1 | head -15
mapfile -t source_rpms < <(find "$top/SRPMS" -type f -name "zupt-${version}-*.src.rpm" -print | sort)
[[ ${#source_rpms[@]} -eq 1 ]] || die "expected one source RPM, found ${#source_rpms[@]}"
source_rpm=${source_rpms[0]}
[[ $(rpm -qp --qf '%{NAME}' "$main_rpm") == zupt ]] || \
die 'binary RPM name metadata is not zupt'
[[ $(rpm -qp --qf '%{VERSION}' "$main_rpm") == "$version" ]] || \
die 'binary RPM version metadata does not match the release'
[[ $(rpm -qp --qf '%{RELEASE}' "$main_rpm") == 0 ]] || \
die 'binary RPM release metadata is not 0'
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$main_rpm") == '(none)' ]] || \
die 'binary RPM is marked as a source package'
[[ $(rpm -qp --qf '%{SOURCERPM}' "$main_rpm") == "$(basename -- "$source_rpm")" ]] || \
die 'binary RPM does not reference the matching source RPM'
[[ $(rpm -qp --qf '%{NAME}' "$source_rpm") == zupt ]] || \
die 'source RPM name metadata is not zupt'
[[ $(rpm -qp --qf '%{VERSION}' "$source_rpm") == "$version" ]] || \
die 'source RPM version metadata does not match the release'
[[ $(rpm -qp --qf '%{RELEASE}' "$source_rpm") == 0 ]] || \
die 'source RPM release metadata is not 0'
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$source_rpm") == 1 ]] || \
die 'source RPM is not marked as a source package'
[[ $(rpm -qp --qf '%{SOURCERPM}' "$source_rpm") == '(none)' ]] || \
die 'source RPM unexpectedly references another source RPM'
mapfile -t source_members < <(rpm -qpl "$source_rpm" | sort)
expected_source_members=("zupt-${version}.tar.gz" zupt.spec)
mapfile -t expected_source_members < <(printf '%s\n' "${expected_source_members[@]}" | sort)
[[ ${#source_members[@]} -eq 2 && \
${source_members[*]} == "${expected_source_members[*]}" ]] || \
die 'source RPM payload is not the exact Source0/spec pair'
rpm -qpi "$main_rpm" >/dev/null
rpm -qpl "$main_rpm" > "$work/contents.txt"
if grep -Eq '(^/usr/bin/vaptvupt$|\.(o|obj|a|so|so\.[^/]+|dll|dylib)$)' "$work/contents.txt"; then
cat "$work/contents.txt" >&2
die 'forbidden alias or compiled library/object found in RPM contents'
fi
if grep -q '^/usr/local/' "$work/contents.txt"; then
cat "$work/contents.txt" >&2
die 'RPM contains files below /usr/local'
fi
(cd -- "$extract" && rpm2cpio "$main_rpm" | cpio -idm --quiet)
binary=$extract/usr/bin/zupt
[[ -x $binary ]] || die 'RPM does not contain executable /usr/bin/zupt'
if ! readelf -h "$binary" 2>/dev/null | grep -Eq 'Type:[[:space:]]+DYN'; then
die 'RPM executable is not a position-independent executable (PIE)'
fi
if ! readelf -W -l "$binary" 2>/dev/null | grep -q 'GNU_RELRO'; then
die 'RPM executable lacks a GNU_RELRO segment'
fi
stack_segment=$(readelf -W -l "$binary" 2>/dev/null | grep 'GNU_STACK' || true)
[[ -n $stack_segment && $stack_segment != *RWE* ]] || \
die 'RPM executable has a missing or executable GNU_STACK segment'
if readelf -d "$binary" 2>/dev/null | grep -Eq '(RPATH|RUNPATH)'; then
readelf -d "$binary" | grep -E '(RPATH|RUNPATH)' >&2
die 'RPM executable contains RPATH/RUNPATH'
fi
if readelf -d "$binary" 2>/dev/null | grep -Eqi '(vendor/|libvuptsdk|libpqvaptvupt)'; then
die 'RPM executable references a vendored optional library'
fi
bash scripts/test-installed-zupt.sh "$binary"
artifacts=("$main_rpm" "$source_rpm")
for artifact in "${artifacts[@]}"; do
destination=$dist_dir/$(basename -- "$artifact")
[[ ! -e $destination ]] || die "refusing to overwrite existing output: $destination"
done
for artifact in "${artifacts[@]}"; do
destination=$dist_dir/$(basename -- "$artifact")
cp -- "$artifact" "$destination"
sha256sum "$destination"
done
printf 'PASS: built and extracted-package-tested %s\n' "$dist_dir/$(basename -- "$main_rpm")"
printf 'PASS: built source RPM %s\n' "$dist_dir/$(basename -- "$source_rpm")"

View file

@ -1,3 +1,148 @@
zupt (5.2.8-1) UNRELEASED; urgency=medium
* Close CodeQL High path-race findings in SDK key publication, disk-restore
target handling, and benchmark workspace cleanup.
* Treat a filesystem refusal to create the macOS raw-C1 scanner fixture as
an explicit skip; reject redirected Windows prompts before _getch; and run
sdk-test in the release and hosted Linux gates.
* Preserve immutable, unpromoted v5.2.7 run 33445470664: 13 jobs succeeded,
macOS failed the raw-C1 fixture, and Windows was cancelled after the hosted
job stalled; a MinGW/Wine reproduction isolated redirected _getch entry.
Require fresh 5.2.8 evidence.
-- Cristian Cezar Moisés <sac@securityops.co> Mon, 31 Aug 2026 23:30:00 +0000
zupt (5.2.7-1) UNRELEASED; urgency=medium
* Scope SHA-NI test helpers to supported x86 builds so macOS arm64 strict
compilation does not fail on unused declarations.
* Preserve safe UTF-8 fixture bytes across the Windows argv boundary.
* Preserve the immutable, unpromoted 5.2.6 history and require fresh 5.2.7
package, checksum, native-platform, OBS, and promotion gates.
-- Cristian Cezar Moisés <sac@securityops.co> Mon, 31 Aug 2026 23:00:00 +0000
zupt (5.2.6-1) UNRELEASED; urgency=medium
* Use the compiler-resistant volatile wipe fallback on macOS and NetBSD, and
make the source scanner's empty-array handling compatible with Bash 3.2.
* Preserve hostile archive-path fixture bytes exactly on Windows.
* Preserve the immutable, unpromoted 5.2.5 history and require fresh 5.2.6
package, checksum, native-platform, OBS, and promotion gates.
-- Cristian Cezar Moisés <sac@securityops.co> Mon, 31 Aug 2026 21:30:00 +0000
zupt (5.2.5-1) UNRELEASED; urgency=medium
* Run the standalone OBS source-service chain from its isolated working
directory and add a packaging-policy regression for that contract.
* Preserve the immutable, unpromoted 5.2.4 history and require fresh 5.2.5
package, checksum, native-platform, and promotion gates.
-- Cristian Cezar Moisés <sac@securityops.co> Mon, 31 Aug 2026 19:55:00 +0000
zupt (5.2.4-1) UNRELEASED; urgency=medium
* Make the static Windows GUI package-version check robust to canonical
CRLF checkouts.
* Advance source-only package metadata and prepare final archive checksums.
-- Cristian Cezar Moisés <sac@securityops.co> Mon, 31 Aug 2026 18:55:00 +0000
zupt (5.2.3-1) UNRELEASED; urgency=medium
* Derive package checks from the upstream version header and stabilize the
GUI version output consumed by package gates.
* Replace busybox-gawk before installing the native openSUSE RPM toolchain.
-- Cristian Cezar Moisés <sac@securityops.co> Mon, 31 Aug 2026 18:15:00 +0000
zupt (5.2.2-1) UNRELEASED; urgency=medium
* Prepare a source-only upstream release and remove incomplete vendored SDK
and PQBOX inputs together with every precompiled-library fallback.
* Make optional integrations explicit system dependencies, disabled by
default, and preserve distribution compiler/linker flags and DESTDIR.
* Add the reusable source scanner and openSUSE/OBS source packaging.
* Restore the ZUPT/zupt application, package, executable, documentation, and
artifact names; build and test with WITH_SDK=0 WITH_PQBOX=0.
* Add explicit password prompt, file, and inherited-descriptor inputs.
* Correct the licensing record without revoking historical MIT grants present
in earlier repository revisions; current files follow current SPDX notices.
* Preserve Yann Collet's BSD-2-Clause notice for the two xxHash-derived
XXH64 source units and include it in package license metadata.
* Record the CC0-1.0 option for pq-crystals/kyber-derived ML-KEM portions
and ship the complete license text in every binary bundle.
* Preserve the BSD-3-Clause notice for curve25519-donna-derived X25519
portions and document their provenance without inventing a revision.
* Promote only license-complete release assets: Windows is ZIP-only and the
AppImage remains downstream-only pending a complete runtime source/relink
compliance handoff.
* Qualify older changelog statements about formally verified or
constant-time assembly: 5.2.2 retains source, generated output and runtime
regressions, but no reproducible formal-proof certificate for those paths.
-- Cristian Cezar Moisés <sac@securityops.co> Mon, 31 Aug 2026 00:00:00 +0000
vaptvupt (5.0.0-1) UNRELEASED; urgency=high
* ML-KEM-768 is now genuinely FIPS 203-conformant. Earlier releases shipped
round-3 CRYSTALS-Kyber under a "FIPS 203" label; it was secure but not
interoperable. Fixed a transposed matrix-A sampling convention (keygen +
encrypt), the round-3 KDF, and the implicit-rejection domain. Validated
byte-for-byte against OpenSSL 3.5's FIPS 203 ML-KEM-768 in both
cross-decapsulation directions (tests/test_mlkem_fips203.sh, in make check).
* BREAKING: --pq / --pq-only keys and archives from <= 4.2.1 no longer decrypt
(the KEM math changed). Regenerate keys and re-encrypt. Password mode and
plain compression are unaffected; wire format stays v1.6.
* Security: compress -p <archive> <files> data-loss guard (was overwriting an
input file); compress <archive> <dir> -p <pw> silent-plaintext guard; heap
OOB read in the AVX2 decoder fast path bounded; overflow-safe bound in the
solid-mode test path; secret-wipe on hybrid-decrypt key-read error.
* GUI reworked for the source-only build: build-aware Hybrid/Full-PQ selector
(no more SDK-mode defaults that fail), PQ-key auto-detect on Extract/Verify,
thread-safety + About fixes.
* Truthful banner/help (real default KDF); cross-platform packaging.
-- Cristian Cezar Moisés <sac@securityops.co> Fri, 10 Jul 2026 18:00:00 +0000
vaptvupt (4.2.1-1) UNRELEASED; urgency=medium
* Fix: `vaptvupt info` mislabelled full post-quantum (--pq-only, enc_type
0x06) archives as "PQ Hybrid (ML-KEM-768 + X25519)". Full-PQ archives
set the generic ZUPT_FLAG_PQ_HYBRID header flag, but info only checked
that flag. info now reads the real enc_type from the encryption-header
block and reports the actual mode ("ML-KEM-768 only, no classical
layer" for --pq-only; hybrid / SDK-v2 / sealed-box otherwise).
Reader-side only — no wire-format change; existing 4.2.0 archives are
relabelled with no re-encryption.
-- Cristian Cezar Moisés <sac@securityops.co> Fri, 10 Jul 2026 12:00:00 +0000
vaptvupt (4.2.0-1) UNRELEASED; urgency=high
* New native full (pure) post-quantum mode --pq-only: ML-KEM-768 (FIPS
203) as the sole key-establishment mechanism, no classical X25519
component (envelope type 0x06; archive key SHA3-512(ml_ss || ml_ct ||
"ZUPT-PQ-ONLY-v1")). For compliance postures that require a single
NIST-standardised PQ primitive with no classical KEM in the envelope
(CNSA 2.0-style "PQ-only"). Keys via keygen --pq-only (ZPQK magic;
not interchangeable with hybrid --pq keys). Hybrid --pq remains the
recommended default; --pq-only has no classical fallback, so a break
of ML-KEM-768 alone breaks the archive. In-tree, default build.
* Security (critical): AES-256-CTR keystream reuse under --dedup. Dedup
blocks all use sequence 0, so the previous nonce (base_nonce XOR seq)
collapsed to a single value across blocks, reusing the CTR keystream
(a many-time-pad). Each block now uses a fresh random 128-bit nonce
stored in the block prefix and bound into the block MAC; block_seq is
still bound as MAC AAD. Regression test tests/test_dedup_nonce.sh.
Re-encrypt any --dedup encrypted archives written by <= 4.1.0.
* keygen --sdk / --box on a source-only build now fails with a clear
message pointing to native --pq / --pq-only (or a WITH_SDK=1 build).
* Wire format v1.6 unchanged; the 0x06 envelope is additive.
-- Cristian Cezar Moisés <sac@securityops.co> Thu, 09 Jul 2026 12:00:00 +0000
vaptvupt (4.1.0-1) UNRELEASED; urgency=high
* Source-only build: the prebuilt vendored libraries libzuptsdk.so and
@ -318,12 +463,10 @@ vaptvupt (3.0.2-1) UNRELEASED; urgency=medium
vaptvupt (3.0.1-1) UNRELEASED; urgency=medium
* GUI license cleanup: removed MIT-license credit line from the
about panel (the GUI is AGPL-3.0-or-later with commercial dual-
licensing; the MIT reference was a templating mistake). Replaced
gui/LICENSE-GUI (was MIT) with AGPL-3.0-or-later, mirroring the
top-level LICENSE. Top-level LICENSE preamble updated to reflect
the v3.0.0 Zupt → VaptVupt rename.
* GUI license metadata changed to AGPL-3.0-or-later for the then-current
source. The original entry incorrectly called earlier MIT notices a
templating mistake; the 5.2.2 erratum records that historical grants remain
valid for the exact material distributed under them.
* GUI version-string parsing bug fix: the v3.0.0 GUI used
`replace("zupt ", "")` to peel the product name out of the CLI's
version banner, but that substring also appears inside the v3.0.0

View file

@ -1,41 +1,49 @@
Source: vaptvupt
Source: zupt
Section: utils
Priority: optional
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
Build-Depends:
bash,
coreutils,
debhelper-compat (= 13),
diffutils,
file,
findutils,
gcc,
gawk,
git,
grep,
gzip,
libarchive-tools,
make,
libc6-dev,
python3 (>= 3.8)
python3 (>= 3.8),
sed,
tar
Standards-Version: 4.6.2
Homepage: https://git.securityops.co/cristiancmoises/zupt
Vcs-Browser: https://git.securityops.co/cristiancmoises/zupt
Vcs-Git: https://git.securityops.co/cristiancmoises/zupt.git
Homepage: https://github.com/cristiancmoises/zupt
Vcs-Browser: https://github.com/cristiancmoises/zupt
Vcs-Git: https://github.com/cristiancmoises/zupt.git
Rules-Requires-Root: no
Package: vaptvupt
Package: zupt
Architecture: any
Provides: zupt (= ${binary:Version})
Replaces: zupt
Conflicts: zupt
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Post-quantum backup compression utility (formerly Zupt)
VaptVupt (renamed from Zupt in v3.0.0 due to INPI Brasil trademark) is
a pure-C11 backup compression utility featuring:
Description: Post-quantum backup compression utility
ZUPT is a pure-C11 backup compression utility featuring:
* Post-quantum hybrid encryption (ML-KEM-768 + X25519, FIPS 203)
* AES-256-CTR + HMAC-SHA256 authenticated encryption (Encrypt-then-MAC)
* Argon2id password-based key derivation (default since 2.4.1)
* Multi-threaded compression with the VaptVupt LZ + ANS codec 2.48.5
* PBKDF2-SHA256 password key derivation (Argon2id in WITH_SDK=1 builds)
* Multi-threaded compression with the VaptVupt LZ + ANS codec 2.65.3
* Full-disk backup and restore with sparse-region detection
* End-to-end byte-level tamper detection on encrypted archives
(0 silent-accept positions in the v1.6 exhaustive byte sweep)
* Constant-time cryptographic primitives verified with Jasmin
* Authenticated encrypted-archive metadata and per-block integrity checks
* Portable C implementations with optional source-built assembly paths
* NIST/RFC test vectors for SHA-256, SHA-3, ML-KEM-768, AES-256-CTR,
HMAC-SHA256, X25519, PBKDF2, Argon2id
HMAC-SHA256, X25519 and PBKDF2
.
The archive extension stays .zupt for format continuity (header magic
unchanged). The binary `zupt` is preserved as a symlink to `vaptvupt`.
unchanged). The package installs only /usr/bin/zupt.
.
The archive format includes an integrity trailer that authenticates the
header and footer, per-block HMAC with bound frame-preface AAD, and
optional encrypted comments.
Encrypted archives include an integrity trailer that authenticates the
header and footer, per-block HMAC with bound frame-preface AAD, and optional
encrypted comments. Plain archives use non-cryptographic checksums.

View file

@ -1,19 +1,35 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: zupt
Upstream-Name: ZUPT
Upstream-Contact: Cristian Cezar Moisés <sac@securityops.co>
Source: https://git.securityops.co/cristiancmoises/zupt
Source: https://github.com/cristiancmoises/zupt
Files: *
Copyright: 2025-2026 Cristian Cezar Moisés
License: AGPL-3.0-or-later
Files: src/vv_*.c include/vaptvupt*.h include/vv_*.h vendor/zuptsdk/include/vv_*.h vendor/zuptsdk/include/vaptvupt*.h
Files: src/vaptvupt_api.c src/vv_*.c include/vaptvupt*.h include/vv_*.h
Copyright: 2025-2026 Cristian Cezar Moisés (VaptVupt codec)
License: GPL-3.0-or-later
Files: vendor/zuptsdk/*
Copyright: 2025-2026 Cristian Cezar Moisés (libzuptsdk)
License: GPL-3.0-or-later
Files: src/zupt_xxh.c
Copyright: 2012-2021 Yann Collet
2025-2026 Cristian Cezar Moisés
License: AGPL-3.0-or-later and BSD-2-Clause
Files: src/zupt_mlkem.c
Copyright: 2025-2026 Cristian Cezar Moisés
pq-crystals/kyber contributors (adapted portions)
License: AGPL-3.0-or-later and CC0-1.0
Files: src/zupt_x25519.c
Copyright: 2008 Google Inc.
2025-2026 Cristian Cezar Moisés
License: AGPL-3.0-or-later and BSD-3-Clause
Files: src/vv_xxh64.c
Copyright: 2012-2021 Yann Collet
2025-2026 Cristian Cezar Moisés (VaptVupt codec adaptation)
License: GPL-3.0-or-later and BSD-2-Clause
Files: debian/*
Copyright: 2025-2026 Cristian Cezar Moisés <sac@securityops.co>
@ -47,3 +63,41 @@ License: GPL-3.0-or-later
.
On Debian systems, the full text of the GNU General Public License
version 3 can be found in the file `/usr/share/common-licenses/GPL-3'.
License: BSD-2-Clause
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
.
* Redistributions of source code must retain the copyright notice, this list
of conditions and the disclaimer.
* Redistributions in binary form must reproduce the copyright notice, this
list of conditions and the disclaimer in the documentation and/or other
materials provided with the distribution.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
License: CC0-1.0
To the extent possible under law, the upstream affirmer has waived all
copyright and related or neighboring rights to the adapted portions.
.
The complete CC0 1.0 Universal legal text is shipped in
`/usr/share/doc/zupt/LICENSE-CC0-1.0`.
License: BSD-3-Clause
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the copyright notice, conditions,
and disclaimer are retained; neither the name of Google Inc. nor contributor
names may be used to endorse derived products without prior permission.
.
The complete BSD-3-Clause notice and disclaimer are shipped in
`/usr/share/doc/zupt/LICENSE-BSD-3-Clause`.

View file

@ -2,7 +2,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# Honour Debian's reproducible-build epoch when set by dpkg-buildpackage.
export SOURCE_DATE_EPOCH ?= 1747699200
export SOURCE_DATE_EPOCH ?= 1788134400
# Hardening flags — Debian's defaults are already strong, this adds project-
# specific ones.
@ -14,24 +14,19 @@ export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
dh $@
override_dh_auto_build:
$(MAKE) -j$$(nproc)
# Source-only build: no vendored libraries, native crypto only.
$(MAKE) WITH_SDK=0 WITH_PQBOX=0 -j$$(nproc)
override_dh_auto_test:
# Project's own regression suite covers F-06..F-12.
$(MAKE) test
# Distro-safe quick, traversal, integrity, codec and NIST/RFC checks.
$(MAKE) WITH_SDK=0 WITH_PQBOX=0 check
override_dh_auto_install:
$(MAKE) DESTDIR=$(CURDIR)/debian/zupt PREFIX=/usr install
# Vendored libzuptsdk goes alongside the binary at a relative rpath.
install -d $(CURDIR)/debian/zupt/usr/lib/zupt
install -m 0755 vendor/zuptsdk/libzuptsdk.so.2.0.0 \
$(CURDIR)/debian/zupt/usr/lib/zupt/libzuptsdk.so.2.0.0
ln -sf libzuptsdk.so.2.0.0 $(CURDIR)/debian/zupt/usr/lib/zupt/libzuptsdk.so.2
ln -sf libzuptsdk.so.2.0.0 $(CURDIR)/debian/zupt/usr/lib/zupt/libzuptsdk.so
# Binary package is `zupt` -> stage into debian/zupt (dh derives the
# staging dir from the Package: name in debian/control). Source-only: nothing
# to install beyond `make install` (no .so).
$(MAKE) DESTDIR=$(CURDIR)/debian/zupt PREFIX=/usr \
WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 install
override_dh_auto_clean:
$(MAKE) clean
# Skip dh_strip's separate -dbgsym packages for a single-source-package layout.
override_dh_strip:
dh_strip --no-automatic-dbgsym

View file

@ -0,0 +1,12 @@
README.md
CHANGELOG.md
SECURITY.md
THREAT_MODEL.md
NOTICE
THIRD-PARTY-NOTICES.md
LICENSE
LICENSE-AGPL-3.0
LICENSE-GPL-3.0
LICENSE-BSD-2-Clause
LICENSE-BSD-3-Clause
LICENSE-CC0-1.0

219
packaging/guix/zupt.scm Normal file
View file

@ -0,0 +1,219 @@
;;; SPDX-License-Identifier: AGPL-3.0-or-later
;;; Copyright (c) 2026 Cristian Cezar Moisés
;;;
;;; GNU Guix package definitions for ZUPT (CLI + PySide6 GUI).
;;; Source-only build (no vendored libraries): the CLI links only libc/libm/
;;; pthread from the store.
;;;
;;; Install into your profile (additive; keeps everything else):
;;; guix package -f packaging/guix/zupt.scm ; installs the GUI
;;; guix package -e '(@ (guix) …)' — or, for the CLI on its own:
;;; guix install -f packaging/guix/zupt.scm ; (last expr = GUI)
;;; The last expression is the GUI, which carries the CLI as an input; to get
;;; the `zupt` command in your profile too, also run:
;;; guix package --install-from-expression='(begin (load "packaging/guix/zupt.scm") zupt)'
;;;
;;; GUI-on-Guix note: PySide6's Qt6 links several leaf libraries (libGL from
;;; mesa, libxkbcommon, the X11/xcb family, libzstd, harfbuzz, icu, ...) that are
;;; NOT in its RUNPATH. The launcher therefore sets LD_LIBRARY_PATH to those
;;; libraries (see %gui-runtime-libs). Without this, `import PySide6.QtWidgets`
;;; fails with "libGL.so.1: cannot open shared object file" and the GUI prints
;;; "requires PySide6 or PyQt6". Qt's OWN libraries are intentionally excluded
;;; from LD_LIBRARY_PATH — they resolve via PySide6's RUNPATH; forcing a second
;;; copy causes Qt private-API symbol clashes.
(use-modules (guix packages)
(guix download)
(guix gexp)
(guix utils)
(guix build-system gnu)
(guix build-system copy)
((guix licenses) #:prefix license:)
(gnu packages python) ; python
(gnu packages qt) ; python-pyside-6, python-shiboken-6, qtbase, qtwayland
(gnu packages bash) ; bash-minimal
(gnu packages gl) ; mesa (libGL)
(gnu packages xdisorg) ; libxkbcommon, pixman, mtdev
(gnu packages fontutils) ; fontconfig, freetype, graphite2
(gnu packages xorg) ; libX11 + xcb family, libxft, libevdev
(gnu packages freedesktop); wayland, libinput-minimal
(gnu packages glib) ; glib, dbus
(gnu packages compression); zlib, zstd, brotli
(gnu packages image) ; libpng, libjpeg-turbo
(gnu packages xml) ; expat, libxml2
(gnu packages gtk) ; harfbuzz
(gnu packages icu4c) ; icu4c
(gnu packages maths) ; double-conversion
(gnu packages pcre) ; pcre2
(gnu packages markup) ; md4c
(gnu packages crypto) ; libb2
(gnu packages linux)) ; eudev (libudev)
;; Leaf runtime libraries PySide6's Qt6 (Core/Gui/Widgets) needs but that are
;; NOT in its RUNPATH. NEVER add qtbase/qtwayland here (see header note). These
;; already live in PySide6's closure, so referencing them adds no store size.
(define %gui-runtime-libs
(list mesa libxkbcommon fontconfig freetype graphite2 harfbuzz
icu4c double-conversion pcre2 md4c libb2 brotli
libpng libjpeg-turbo zlib expat libxml2 pixman glib dbus wayland
libx11 libxext libxrender libxcb libxrandr libxi libxcursor libxft
libxfixes libxdamage libxcomposite libxtst libxinerama libsm libice
libxau libxdmcp xcb-util xcb-util-image xcb-util-keysyms
xcb-util-renderutil xcb-util-wm xcb-util-cursor
libinput-minimal mtdev libevdev eudev))
(define %zupt-version "5.2.8")
(define %zupt-source
(origin
(method url-fetch)
(uri (string-append
"https://github.com/cristiancmoises/zupt"
"/releases/download/v" %zupt-version
"/zupt-" %zupt-version ".tar.gz"))
(sha256
(base32 "1xv5vd7bh9pcw2d3fszb6jn1r6sxjp48mlzh9icvji8m4439b2rp"))))
(define-public zupt
(package
(name "zupt")
(version %zupt-version)
(source %zupt-source)
(build-system gnu-build-system)
(arguments
(list
#:make-flags
#~(list (string-append "PREFIX=" #$output)
"WITH_SDK=0"
"WITH_PQBOX=0"
(string-append "CC=" #$(cc-for-target)))
#:phases
#~(modify-phases %standard-phases
(delete 'configure) ; plain Makefile, no ./configure
(replace 'check
;; Self-contained NIST/RFC known-answer vectors (FIPS 180-4/202/203,
;; SP 800-38A, RFC 4231/7748) are the crypto gate.
(lambda* (#:key tests? #:allow-other-keys)
(when tests?
(invoke "make" "WITH_SDK=0" "WITH_PQBOX=0"
(string-append "CC=" #$(cc-for-target))
"test-vectors")
(invoke "./test_vectors")))))))
(home-page "https://github.com/cristiancmoises/zupt")
(synopsis "Post-quantum backup compression utility")
(description
"ZUPT is a pure-C11 backup compressor with native
post-quantum encryption. Two in-tree PQ modes: @code{--pq} hybridizes
ML-KEM-768 with X25519 (recommended), and
@code{--pq-only} uses ML-KEM-768 alone for @dfn{PQ-only} compliance postures.
Payload protection is AES-256-CTR + HMAC-SHA256 Encrypt-then-MAC with a fresh
random per-block nonce; AES-NI/SHA-NI dispatch at runtime; the bundled
VaptVupt 2.65.3 LZ+ANS codec has portable fallbacks. Password mode uses
PBKDF2-SHA256. The tool is
AGPL-3.0-or-later; the embedded codec is GPL-3.0-or-later; the two
xxHash-derived XXH64 units additionally carry BSD-2-Clause; and portions of
native ML-KEM adapted from pq-crystals/kyber carry CC0-1.0. Native X25519
portions adapted from curve25519-donna retain BSD-3-Clause.
The x86 BCJ filter and SHA-NI path also record their public-domain LZMA SDK
and SHA-Intrinsics origins; installed NOTICE and THIRD-PARTY-NOTICES.md carry
the full provenance record.")
(license (list license:agpl3+ license:gpl3+ license:bsd-2 license:bsd-3 license:cc0))))
(define-public zupt-gui
(package
(name "zupt-gui")
(version %zupt-version)
(source (package-source zupt)) ; same release tarball
(build-system copy-build-system)
(arguments
(list
#:install-plan
#~'(("gui/src/zupt_gui.py" "lib/zupt-gui/")
("gui/assets/zupt-icon.png"
"share/icons/hicolor/256x256/apps/zupt-gui.png")
("gui/README.md" "share/doc/zupt-gui/")
("LICENSE-AGPL-3.0"
"share/licenses/zupt-gui/LICENSE-AGPL-3.0")
("gui/LICENSE-GUI"
"share/licenses/zupt-gui/LICENSE-GUI")
("gui/assets/README.md"
"share/licenses/zupt-gui/ASSET-PROVENANCE.md"))
#:phases
#~(modify-phases %standard-phases
(add-after 'install 'make-launcher
(lambda* (#:key inputs outputs #:allow-other-keys)
(let* ((out (assoc-ref outputs "out"))
(bin (string-append out "/bin"))
(gui (string-append
out "/lib/zupt-gui/zupt_gui.py"))
(sh (search-input-file inputs "/bin/sh"))
(python3 (search-input-file inputs "/bin/python3"))
(cli (search-input-file inputs "/bin/zupt"))
(pyside (assoc-ref inputs "python-pyside-6"))
(site (car (find-files pyside "^site-packages$"
#:directories? #t)))
;; Shiboken6 is a SEPARATE package PySide6 imports at
;; runtime; its site-packages must be on GUIX_PYTHONPATH too.
(shiboken (assoc-ref inputs "python-shiboken-6"))
(shsite (car (find-files shiboken "^site-packages$"
#:directories? #t)))
(qtbase (assoc-ref inputs "qtbase"))
(qtwl (assoc-ref inputs "qtwayland"))
;; zstd ships libzstd.so.1 in its "lib" output (not "out").
(zstdlib (assoc-ref inputs "zstd"))
(ldpath (string-join
(append
(list #$@(map (lambda (p) (file-append p "/lib"))
%gui-runtime-libs))
(list (string-append zstdlib "/lib")))
":")))
(mkdir-p bin)
(call-with-output-file (string-append bin "/zupt-gui")
(lambda (port)
(format port "#!~a
export ZUPT_BIN=\"~a\"
export GUIX_PYTHONPATH=\"~a:~a${GUIX_PYTHONPATH:+:}$GUIX_PYTHONPATH\"
export QT_PLUGIN_PATH=\"~a/lib/qt6/plugins:~a/lib/qt6/plugins${QT_PLUGIN_PATH:+:}$QT_PLUGIN_PATH\"
export LD_LIBRARY_PATH=\"~a${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH\"
exec \"~a\" \"~a\" \"$@\"\n"
sh cli site shsite qtbase qtwl ldpath python3 gui)))
(chmod (string-append bin "/zupt-gui") #o755))))
(add-after 'make-launcher 'install-desktop-file
(lambda* (#:key outputs #:allow-other-keys)
(let* ((out (assoc-ref outputs "out"))
(apps (string-append out "/share/applications")))
(mkdir-p apps)
(call-with-output-file
(string-append apps "/zupt-gui.desktop")
(lambda (port)
(format port "[Desktop Entry]
Type=Application
Name=ZUPT
GenericName=Post-Quantum Backup
Comment=Compress, encrypt and restore .zupt archives
Exec=~a/bin/zupt-gui %F
Icon=zupt-gui
Terminal=false
Categories=Utility;Archiving;Security;
MimeType=application/x-zupt;
Keywords=backup;encryption;post-quantum;compression;zupt;\n"
out)))))))))
(inputs
(append (list bash-minimal python python-pyside-6 python-shiboken-6
qtbase qtwayland zupt
(list zstd "lib")) ; libzstd.so.1 is in zstd's "lib" output
%gui-runtime-libs))
(home-page "https://github.com/cristiancmoises/zupt")
(synopsis "Desktop frontend for the ZUPT post-quantum backup tool")
(description
"PySide6 (Qt 6) graphical frontend for ZUPT: create, inspect and
extract @code{.zupt} archives with password or post-quantum recipient
encryption, including the @code{--pq} hybrid and @code{--pq-only} full
post-quantum modes. The launcher pins the matching @code{zupt} CLI from the
store via @env{ZUPT_BIN} and sets @env{LD_LIBRARY_PATH} to the Qt6 leaf
libraries PySide6 needs but does not carry in its RUNPATH.")
(license license:agpl3+)))
;; `guix package -f' evaluates the file's last expression — the GUI, which
;; carries the CLI as an input.
zupt-gui

View file

@ -1,71 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Homebrew formula for zupt.
#
# To publish:
# 1. Run `make dist` upstream to produce zupt-VERSION.tar.gz (reproducible).
# 2. Upload to a stable release URL.
# 3. Update `url`, `version`, and `sha256` below.
# 4. Submit to homebrew-core via PR OR host in your own tap
# (e.g. cristiancmoises/homebrew-tap).
#
# Local test:
# brew install --build-from-source ./zupt.rb
# brew test zupt
# brew audit --strict --online zupt
#
# Notes for macOS:
# * Jasmin assembly is disabled at build time on Darwin (no jasminc dep);
# the C fallback for AES-256-CTR / HMAC compare paths is shipped.
# * libzuptsdk is vendored and installed alongside the binary; the binary
# uses @loader_path rpath so users don't have to set DYLD paths.
class Vaptvupt < Formula
desc "Post-quantum backup compression utility (ML-KEM-768 + AES-256-CTR + HMAC-SHA256)"
homepage "https://git.securityops.co/cristiancmoises/zupt"
url "https://git.securityops.co/cristiancmoises/zupt/releases/download/v4.1.0/vaptvupt-4.1.0.tar.gz"
version "4.1.0"
sha256 "REPLACE_WITH_SHA256_OF_RELEASE_TARBALL"
license "AGPL-3.0-or-later"
depends_on "python@3.12" => :test # only for test-suite tamper harness
def install
# macOS build: no Jasmin, C-fallback crypto paths are used.
# The Makefile auto-detects Jasmin availability and falls back cleanly.
ENV["CFLAGS"] = "#{ENV.cflags} -O2 -std=c11 -Wall -Wextra"
system "make", "-j#{ENV.make_jobs}"
system "make", "DESTDIR=#{prefix}", "PREFIX=", "install"
# Vendored libzuptsdk goes into lib/zupt/ with @loader_path rpath.
# Note: Linux ships .so.2.0.0; macOS .dylib equivalent must be built
# separately by the vendored makefile. For the initial Homebrew
# submission this assumes the upstream tarball includes a .dylib build;
# if not, build it here.
lib_zupt = lib/"zupt"
lib_zupt.mkpath
if File.exist?("vendor/zuptsdk/libzuptsdk.dylib")
cp "vendor/zuptsdk/libzuptsdk.dylib", lib_zupt
elsif File.exist?("vendor/zuptsdk/libzuptsdk.so.2.0.0")
# Fallback: link Linux-style .so on macOS (works for direct loads but
# not for dlopen-on-Darwin scenarios). Upstream is tracking this.
cp "vendor/zuptsdk/libzuptsdk.so.2.0.0", lib_zupt
end
# Docs
doc.install "README.md", "SECURITY.md", "CHANGELOG.md", "AUDIT.md"
end
test do
# End-to-end sanity check: build a real archive, extract it, byte-compare.
(testpath/"input.txt").write("homebrew formula test payload\n")
system bin/"zupt", "c", "-p", "test", "out.zupt", "input.txt"
system bin/"zupt", "info", "out.zupt"
mkdir "extracted"
cd "extracted" do
system bin/"zupt", "x", "-p", "test", "../out.zupt"
end
system "diff", "-q", "input.txt", "extracted/input.txt"
end
end

View file

@ -0,0 +1,62 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Homebrew formula for ZUPT.
#
# To publish:
# 1. Run `make dist` upstream to produce zupt-VERSION.tar.gz.
# 2. Upload to a stable release URL.
# 3. Update `url`, `version`, and `sha256` below.
# 4. Submit to homebrew-core via PR OR host in your own tap
# (e.g. cristiancmoises/homebrew-tap).
#
# Local test:
# brew install --build-from-source ./zupt.rb
# brew test zupt
# brew audit --strict --online zupt
#
# Notes for macOS:
# * Jasmin assembly is disabled at build time on Darwin (no jasminc dep);
# the C fallback for AES-256-CTR / HMAC compare paths is shipped.
# * Source-only build: no vendored libraries; native crypto only.
class Zupt < Formula
desc "Post-quantum backup compression utility (ML-KEM-768 + AES-256-CTR + HMAC-SHA256)"
homepage "https://github.com/cristiancmoises/zupt"
url "https://github.com/cristiancmoises/zupt/releases/download/v5.2.8/zupt-5.2.8.tar.gz"
version "5.2.8"
sha256 "378b9506211545b9594cf0d38ac8955d9b1cac34eb6b379ae0ec26b84edb65f7"
license all_of: ["AGPL-3.0-or-later", "GPL-3.0-or-later", "BSD-2-Clause", "BSD-3-Clause", "CC0-1.0"]
depends_on "python@3.12" => :test # only for test-suite tamper harness
def install
# Source-only build (WITH_SDK=0): native crypto only, no vendored libraries.
# macOS uses the C-fallback crypto paths (no Jasmin); the Makefile detects
# this and falls back cleanly.
ENV["CFLAGS"] = "#{ENV.cflags} -O2 -std=c11 -Wall -Wextra"
system "make", "WITH_SDK=0", "WITH_PQBOX=0", "-j#{ENV.make_jobs}"
system "make", "PREFIX=#{prefix}", "WITH_SDK=0", "WITH_PQBOX=0",
"INSTALL_LEGACY_ALIAS=0", "install"
# Docs (no vendored .so/.dylib in the source-only build). `make install`
# also installs the complete project license/notice set.
doc.install "README.md", "SECURITY.md", "CHANGELOG.md"
%w[LICENSE-BSD-3-Clause LICENSE-CC0-1.0].each do |notice|
odie "missing installed license #{notice}" unless \
(share/"licenses/zupt"/notice).exist?
end
end
test do
# End-to-end sanity check: build a real archive, extract it, byte-compare.
(testpath/"input.txt").write("homebrew formula test payload\n")
system bin/"zupt", "c", "-p", "test", "out.zupt", "input.txt"
system bin/"zupt", "t", "-p", "test", "out.zupt"
mkdir "extracted"
cd "extracted" do
system bin/"zupt", "x", "-p", "test", "../out.zupt"
end
system "diff", "-q", "input.txt", "extracted/input.txt"
end
end

View file

@ -1,139 +1,8 @@
#!/bin/bash
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
# Zupt + Zupt GUI all-in-one installer for Linux
# Detects your distro, installs all dependencies, then installs
# zupt and zupt-gui. Run as root or with sudo.
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ZUPT_CLI_DEB="$SCRIPT_DIR/zupt_2.2.3_amd64.deb"
ZUPT_GUI_DEB="$SCRIPT_DIR/zupt-gui_1.1.1_all.deb"
print_step() { echo ""; echo "═══ $* ═══"; }
print_err() { echo "ERROR: $*" >&2; exit 1; }
# Must be root
if [ "$EUID" -ne 0 ]; then
print_err "Run with sudo: sudo bash $0"
fi
# Detect distro
if [ -f /etc/os-release ]; then
. /etc/os-release
DISTRO="$ID"
DISTRO_LIKE="${ID_LIKE:-}"
else
print_err "Cannot detect distribution (no /etc/os-release)"
fi
print_step "Detected: $PRETTY_NAME"
# 1. Install Python 3 + Qt6 binding
print_step "Step 1/3: Installing Python 3 and Qt6 binding"
case "$DISTRO" in
debian|ubuntu|linuxmint|pop)
apt-get update
apt-get install -y python3 python3-pyqt6 || \
apt-get install -y python3 python3-pyside6
;;
fedora|rhel|centos|rocky|almalinux)
if command -v dnf >/dev/null; then
dnf install -y python3 python3-pyqt6 || dnf install -y python3 python3-pyside6
else
yum install -y python3 python3-pyqt6 || yum install -y python3 python3-pyside6
fi
;;
opensuse*|suse)
zypper install -y python3 python3-pyqt6 || zypper install -y python3 python3-PyQt6 \
|| zypper install -y python3 python3-pyside6
;;
arch|manjaro|endeavouros)
pacman -S --noconfirm python python-pyqt6 || pacman -S --noconfirm python python-pyside6
;;
alpine)
apk add python3 py3-pyqt6 || apk add python3 py3-pyside6
;;
*)
# Fallback: try pip
echo "Unknown distribution '$DISTRO'. Trying pip fallback..."
if command -v pip3 >/dev/null; then
pip3 install --break-system-packages PySide6 || pip3 install PySide6
else
print_err "No pip3 available. Install python3-pyqt6 manually for your distro."
fi
;;
esac
# Verify Qt6 binding works
if ! python3 -c 'import PyQt6.QtWidgets' 2>/dev/null \
&& ! python3 -c 'import PySide6.QtWidgets' 2>/dev/null; then
print_err "Failed to install Qt6 Python binding. Install manually with your package manager."
fi
echo "✓ Python 3 + Qt6 binding installed"
# 2. Install zupt CLI
print_step "Step 2/3: Installing zupt CLI 2.2.3"
case "$DISTRO" in
debian|ubuntu|linuxmint|pop)
if [ ! -f "$ZUPT_CLI_DEB" ]; then
print_err "Cannot find $ZUPT_CLI_DEB next to this script"
fi
# Force-replace any older zupt
dpkg -i "$ZUPT_CLI_DEB" || apt-get -f install -y
;;
fedora|rhel|centos|rocky|almalinux|opensuse*|suse)
ZUPT_CLI_RPM="$SCRIPT_DIR/zupt-2.2.3-1.x86_64.rpm"
if [ -f "$ZUPT_CLI_RPM" ]; then
rpm -Uvh --force "$ZUPT_CLI_RPM"
else
print_err "RPM build not provided. Build from source tarball or install via SRPM."
fi
;;
*)
# Fallback: tarball install
ZUPT_CLI_TAR="$SCRIPT_DIR/zupt-2.2.3-linux-x86_64.tar.gz"
if [ -f "$ZUPT_CLI_TAR" ]; then
tar -xzf "$ZUPT_CLI_TAR" -C /opt/
ln -sf /opt/zupt-2.2.3-linux-x86_64/zupt /usr/local/bin/zupt
else
print_err "No suitable installer for $DISTRO"
fi
;;
esac
echo "✓ zupt CLI installed"
# 3. Install zupt-gui
print_step "Step 3/3: Installing zupt-gui"
case "$DISTRO" in
debian|ubuntu|linuxmint|pop)
dpkg -i "$ZUPT_GUI_DEB" || apt-get -f install -y
;;
fedora|rhel|centos|rocky|almalinux|opensuse*|suse)
ZUPT_GUI_RPM="$SCRIPT_DIR/zupt-gui-1.1.1-1.noarch.rpm"
if [ -f "$ZUPT_GUI_RPM" ]; then
rpm -Uvh --force "$ZUPT_GUI_RPM"
fi
;;
*)
# Manual fallback
mkdir -p /opt/zupt-gui /usr/local/bin
cp "$SCRIPT_DIR/zupt_gui.py" /opt/zupt-gui/ 2>/dev/null || true
cat > /usr/local/bin/zupt-gui <<'WRAP'
#!/bin/sh
exec python3 /opt/zupt-gui/zupt_gui.py "$@"
WRAP
chmod +x /usr/local/bin/zupt-gui
;;
esac
echo "✓ zupt-gui installed"
print_step "Installation complete"
echo ""
echo "Run:"
echo " zupt help # CLI help"
echo " zupt-gui # Graphical interface"
echo ""
echo "If you encounter issues, check that your zupt version is correct:"
echo " zupt version # should show 2.2.3"
# Stable entry point for the source installer. Dependency installation belongs
# to the operating-system package manager; this script performs no downloads.
set -Eeuo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
exec "$repo_root/gui/install.sh" "$@"

View file

@ -1,25 +1,22 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Nix flake for zupt.
# Nix flake for ZUPT.
#
# Usage (with flakes enabled):
# nix build .#zupt # build the package
# nix run .#zupt -- version # run zupt directly
# nix run .#zupt -- --version # run ZUPT directly
# nix develop # drop into a dev shell
# nix flake check # lint the flake
#
# To consume from another flake:
# inputs.zupt.url = "git+https://git.securityops.co/cristiancmoises/zupt?ref=v2.4.4";
# inputs.zupt.url = "github:cristiancmoises/zupt/v5.2.8";
# ...packages.x86_64-linux.default = inputs.zupt.packages.x86_64-linux.zupt;
#
# Reproducibility:
# * Nix already pins the source tree by hash.
# * `make dist` is also reproducible (tests/test_dist_reproducible.sh).
# * Together, two independent Nix evaluations of the same flake.lock
# produce byte-identical /nix/store outputs.
# `make dist` has its own reproducibility gate. This development flake has no
# committed lock file and therefore makes no independent locked-output claim.
{
description = "Zupt post-quantum backup compression utility (C11)";
description = "ZUPT post-quantum backup compression utility (C11)";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
@ -27,22 +24,25 @@
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system:
flake-utils.lib.eachSystem [ "x86_64-linux" ] (system:
let
pkgs = import nixpkgs { inherit system; };
zupt = pkgs.stdenv.mkDerivation {
pname = "vaptvupt";
version = "4.1.0";
pname = "zupt";
version = "5.2.8";
# When publishing, replace this with `fetchurl` against the
# release tarball. For local development the flake assumes it
# lives in the same directory as the source.
src = ./.;
src = builtins.path { path = ../..; name = "zupt-source"; };
nativeBuildInputs = with pkgs; [
gcc
git
gnumake
file
gnutar
];
# python3 is only used by the regression-test harness.
@ -52,47 +52,40 @@
# hardening flags. Don't override -O2 from stdenv.
NIX_CFLAGS_COMPILE = "-Wall -Wextra -Wpedantic -std=c11";
# `make` builds the binary using vendored libzuptsdk via rpath.
# Source-only build (WITH_SDK=0): native crypto, no vendored libraries.
buildPhase = ''
runHook preBuild
make -j$NIX_BUILD_CORES
make WITH_SDK=0 WITH_PQBOX=0 -j$NIX_BUILD_CORES
runHook postBuild
'';
# Run the full upstream regression suite. Disable per-package by
# setting doCheck = false; on by default.
# Distro-safe regression subset. Disable with doCheck = false;.
doCheck = true;
checkPhase = ''
runHook preCheck
make test
make WITH_SDK=0 WITH_PQBOX=0 check
runHook postCheck
'';
installPhase = ''
runHook preInstall
make DESTDIR=$out PREFIX= install
# Move libzuptsdk into $out/lib/zupt/. The binary's rpath is
# $ORIGIN/../lib/zupt after autopatchelf rewrites it during
# the fixup phase.
mkdir -p $out/lib/zupt
install -m 0755 vendor/zuptsdk/libzuptsdk.so.2.0.0 \
$out/lib/zupt/libzuptsdk.so.2.0.0
ln -sf libzuptsdk.so.2.0.0 $out/lib/zupt/libzuptsdk.so.2
ln -sf libzuptsdk.so.2.0.0 $out/lib/zupt/libzuptsdk.so
make PREFIX=$out WITH_SDK=0 WITH_PQBOX=0 \
INSTALL_LEGACY_ALIAS=0 install
# Docs
mkdir -p $out/share/doc/zupt
cp README.md SECURITY.md CHANGELOG.md AUDIT.md $out/share/doc/zupt/
cp README.md SECURITY.md CHANGELOG.md $out/share/doc/zupt/
test -f $out/share/licenses/zupt/LICENSE-BSD-3-Clause
test -f $out/share/licenses/zupt/LICENSE-CC0-1.0
runHook postInstall
'';
meta = with pkgs.lib; {
description = "Post-quantum backup compression utility (ML-KEM-768 + AES-256-CTR + HMAC-SHA256 + Argon2id)";
homepage = "https://git.securityops.co/cristiancmoises/zupt";
license = with licenses; [ agpl3Plus gpl3Plus ];
description = "Post-quantum backup compression utility (ML-KEM-768 + X25519 + AES-256-CTR + HMAC-SHA256)";
homepage = "https://github.com/cristiancmoises/zupt";
license = with licenses; [ agpl3Plus gpl3Plus bsd2 bsd3 cc0 ];
maintainers = [ ];
platforms = [ "x86_64-linux" "aarch64-linux" ];
platforms = [ "x86_64-linux" ];
mainProgram = "zupt";
};
};

View file

@ -1,88 +1,324 @@
# openSUSE Build Service update for `home:cabelo:innovators/vaptvupt`
# ZUPT 5.2.8 for openSUSE Build Service
This directory contains the three files needed to build vaptvupt `4.1.0`
in OBS:
This directory is the upstream, source-only OBS recipe for ZUPT. It is a
handoff for the downstream maintainer; its presence does not mean that the
package has been submitted to or accepted by openSUSE Factory.
| File | Purpose |
|---------------|-------------------------------------------------------------------------|
| `_service` | `revision` pinned to `v4.1.0`. Format unchanged (still `tar_scm`). |
| `vaptvupt.spec` | `Version: 4.1.0`. `License: AGPL-3.0-or-later`. `%check` calls `make check`. |
| `vaptvupt.changes`| Changelog for the 4.x series. Older history preserved verbatim. |
Cristian Cezar Moisés, ZUPT's creator and current upstream maintainer,
prepared the current source, build, test, documentation, and upstream packaging
changes in this handoff. Alessandro de Oliveira Faria (Cabelo) is credited only
as the openSUSE collaborator and downstream OBS package maintainer: he reviews
the handoff, commits it through the portal/project he maintains, and may make
the openSUSE-side adjustments he considers necessary. This role does not
attribute upstream code or the
5.2.2/5.2.3/5.2.4/5.2.5/5.2.6/5.2.7/5.2.8 upstream changes to Cabelo.
## Spec notes
## Files and source policy
1. **License**`AGPL-3.0-or-later` (dual-licensed AGPL-3.0-or-later
+ commercial).
| File | Purpose |
|---|---|
| `_service` | Fetch the immutable `v5.2.8` tag and create `Source0` at build time. |
| `zupt.spec` | Build and test the CLI with optional external system integrations disabled. |
| `zupt.changes` | openSUSE-format package history. |
| `source-audit.sh` | Handoff wrapper for the repository scanner; run it from the complete handoff tree. |
2. **No BuildRequires beyond the toolchain** — the default build needs
only `gcc gzip make` (plus `libm`/`pthread` from glibc). There are
**no system library BuildRequires**. The repository is source-only:
the previously vendored `libzuptsdk.so` and `libpqvaptvupt.so` have
been removed from the tree, `%build` and `%install` run with
`WITH_SDK=0`, and `%files` no longer lists any `.so`. The package
installs no shared library. Do not add system crypto BuildRequires.
The source service uses `obs_scm`, with Git submodules and Git LFS explicitly
disabled. Its primary URL is the canonical upstream:
The optional SDK modes (`--pq-sdk`, `--pq-box`) and the Argon2id KDF
require an upstream `make WITH_SDK=1` build linked against the
separately distributed `libzuptsdk`/`libpqvaptvupt` libraries. They
are not part of this package.
3. **`%check` target** — the s390x branch falls back to `test-vectors`;
other architectures run `make check`. This exercises the HMAC tamper
detection, archive-integrity trailer, byte-level integrity preface
AAD, default-KDF, auth-fail, and encrypted-comment suites, the
NIST/RFC vectors (SHA-256, SHA-3, ML-KEM-768, AES-256-CTR, HMAC,
X25519, PBKDF2), and the path-traversal, argument-order, and
block-swap regressions.
The default password KDF is **PBKDF2-SHA256** (600k iterations).
Argon2id test vectors run only in a `WITH_SDK=1` build and are not
checked here.
4. **URLs** — the `URL:` field points at the canonical project URL
`https://git.securityops.co/cristiancmoises/vaptvupt`. The `_service`
file still fetches from GitHub
(`https://github.com/cristiancmoises/vaptvupt`), which is what the
existing `tar_scm` configuration uses in OBS.
## How to apply
```sh
# 1. Check out the package
osc checkout home:cabelo:innovators vaptvupt
cd home:cabelo:innovators/vaptvupt
# 2. Drop the new files in (assuming this README is at
# /path/to/vaptvupt-source/packaging/opensuse/README.md)
cp /path/to/vaptvupt-source/packaging/opensuse/_service .
cp /path/to/vaptvupt-source/packaging/opensuse/vaptvupt.spec .
cp /path/to/vaptvupt-source/packaging/opensuse/vaptvupt.changes .
# 3. Trigger the service locally to fetch v4.1.0 from GitHub
osc service runall
# Produces vaptvupt-4.1.0.tar.gz in the current directory.
# 4. (Optional) Local build to verify before committing
osc build openSUSE_Tumbleweed x86_64
# 5. Commit upstream
osc status # confirm vaptvupt-4.1.0.tar.gz is staged alongside the
# three text files
osc commit -m "Update to 4.1.0"
```text
https://github.com/cristiancmoises/zupt.git
```
## Notes for future updates
`obs_scm` stores an `.obscpio` plus `.obsinfo`. The `tar` and `recompress`
services reconstruct `zupt-5.2.8.tar.gz` inside the build environment, which
matches `Source0` in the spec.
* The `_service` `revision` is pinned to `v4.1.0`. To track a new
release, edit that one line and re-run `osc service runall`.
* The spec's `Version:` field is hard-coded — when you bump `_service`
`revision`, also bump `Version:` to match.
* `BuildRequires` is intentionally minimal (`gcc gzip make`). vaptvupt
has no external library dependencies in the default build; do not add
system crypto BuildRequires.
This source policy does not prohibit separately built release-page packages.
The upstream 5.2.8 gates may publish the CLI source tarball, DEB, binary RPM,
SRPM, notice-bearing Linux tar.xz, Windows ZIP, and macOS DMG, together with a
GUI DEB, noarch RPM, GUI SRPM, and source-only portable GUI ZIP after each
format-specific test succeeds. None of those files is an OBS `Source0` input
or belongs in Git. AppImage and bare executables remain excluded: the former
lacks an audited runtime source/relink handoff, while the latter does not carry
the required license and notice payload beside the program.
## Reporting issues
## License and bundled codec
* Upstream bugs: https://git.securityops.co/cristiancmoises/vaptvupt
* openSUSE packaging bugs: https://bugs.opensuse.org/
* Cabelo's OBS project: https://build.opensuse.org/project/show/home:cabelo:innovators
The resulting executable combines the AGPL-3.0-or-later application with the
GPL-3.0-or-later VaptVupt codec, adapted BSD-2-Clause XXH64 routines, and
CC0-1.0 pq-crystals/kyber-derived ML-KEM portions, plus BSD-3-Clause
curve25519-donna-derived X25519 portions, so the RPM uses:
```text
AGPL-3.0-or-later AND GPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND CC0-1.0
```
The bundled codec is VaptVupt codec tag `v2.65.3`. It was integrated into this
repository by commit `59f9ebc59ea13c6edf1d199ca795cdbf00e62226` and is declared
as `bundled(vaptvupt-codec) = 2.65.3`. That integration commit records the local
ANS safe-zone reserve patch applied on top of the upstream tag. The package
retains all license and notice files, including Yann Collet's xxHash notice;
it does not claim that the codec is unbundled.
## Optional SDK and PQBOX integrations
The OBS package always builds with:
```text
WITH_SDK=0 WITH_PQBOX=0
```
The resulting CLI retains the in-tree password, ML-KEM-768, X25519 and hybrid
features. It does not enable the optional libvuptsdk-backed Argon2id/`--pq-sdk`
integration or the separate libpqvaptvupt-backed `--pq-box` integration. Those
options may only be enabled in a future package after their complete source or
system development packages, licenses, ABI and dependencies have been audited.
The build does not download dependencies and never loads a repository-local
`.so`, `.a` or `.o` fallback.
## Archive integrity and compatibility in 5.2.2
New encrypted archives bind every DATA and DEDUP_REF frame to its logical
position. An authenticated reference also carries the authenticated position of
the source DATA frame, and new disk archives use flag-gated index/content-hash
metadata. The on-disk version byte remains 1.6, but an older reader is not
claimed to accept every new 5.2.2 encoding.
The packaged `extract`, `list`, `test`, and `disk restore` paths require an
archive-integrity trailer by default, without trusting unauthenticated header
flags. `--allow-legacy-no-ait` is accepted only by those commands for recovery
of a known, trusted pre-AIT archive and emits a downgrade warning. `info` merely
reports unauthenticated framing and apparent AIT presence; it does not validate
the trailer or contents. Package documentation must not recommend the override
for untrusted input or present `info` success as an integrity result.
The separate v5.2.1 compatibility claim is narrow: an actual
password-encrypted, deduplicated DATA/DATA/REF/DATA disk archive created from the
immutable v5.2.1 tag is stored as hexadecimal text with its source and SHA-256
provenance. The 5.2.2 reader reconstructs the legacy linear block-AAD sequence,
lists, tests, extracts, and restores its input byte-exact through the
fixed-width legacy disk-index parser. This does not cover every historical mode
and passed in the full local Linux gate for commit `ff99770`; the target RPM
`%check` must still exercise it before that package is promoted.
Disk restore also snapshots the measured archive into a private scratch file
before opening the destination, then validates and restores from that same
stream. An invalid `ZUPT_TMPDIR` override (or the compatibility fallback
`VAPTVUPT_TMPDIR`) and an unknown or insufficient raw-device capacity fail
before the first target write. The package check covers
the unprivileged unknown-capacity path; its loop-device size regression is
reported `SKIP`, not `PASS`, when the builder cannot create a loop device.
## Migration from the former package name
The main package is named `zupt` and installs only `/usr/bin/zupt`, its man
page, and its completions. The spec has a versioned `Provides: vaptvupt` and
`Obsoletes: vaptvupt` so an installed package under the former public name can
upgrade cleanly. It intentionally does not claim or install a second
`/usr/bin/vaptvupt` executable. The bundled codec and optional library keep
their established VaptVupt identifiers because those are compatibility-facing
API names, not the application package name.
## Local validation workflow
Run these commands in an OBS package checkout, not in the upstream Git tree:
```sh
xmllint --noout _service
osc service manualrun
rpmspec -P zupt.spec >/dev/null
spec-cleaner --diff zupt.spec
osc build --clean --keep-pkgs="$PWD/.osc-build-results" \
openSUSE_Tumbleweed x86_64
rpmlint .osc-build-results/*.rpm
```
`osc service manualrun` materializes the service marked `manual` (the pinned
SCM input). The tarball itself is
reconstructed by the build-time services. Neither `%build` nor `%check` may
access the network.
For a source RPM check outside OBS, place the service-produced
`zupt-5.2.8.tar.gz` next to the spec and use a disposable RPM build tree:
```sh
rpm_top=$(mktemp -d)
trap 'rm -rf -- "$rpm_top"' EXIT
mkdir -p "$rpm_top"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
rpmbuild --define "_topdir $rpm_top" --define "_sourcedir $PWD" \
-bs zupt.spec
```
After building, inspect the RPM contents and dependencies, run `rpmlint`, then
install it in a disposable openSUSE environment and execute
`scripts/test-installed-zupt.sh`. Do not claim a repository or architecture
as supported until its build and installed smoke test have actually passed.
## Prior 5.2.2 committed-candidate local Linux validation
The immutable 5.2.2 candidate at `ff99770` passed the full local
`make release-check`. Packaging policy
and syntax reported `PASS=49 FAIL=0 SKIP=0`; source-only scanner testing passed
39/39, including GNU thin archives and safe diagnostic cases; strict GCC,
strict Clang, GCC `-fanalyzer`, the 9/9 full tool-enabled static-analysis run,
ASan/UBSan/LSan, and 1,000 mutation-fuzz iterations passed. A reduced
environment completed six available static checks and reported `cppcheck`
unavailable rather than passing it. Earlier off-screen GUI smoke evidence is
supporting evidence, not an exact-commit package result.
Post-tag CI integration failures prevented 5.2.2 promotion. These historical
local results do not establish 5.2.8, native Windows or macOS success, hosted
GitHub CI/release promotion, authenticated OBS acceptance, or resolution of the
automatic openSUSE `debugsource` rpmlint `no-binary` finding. The immutable
5.2.3 candidate was not promoted because its source-policy test assumed LF for
a Windows `.bat` file checked out as CRLF.
## Prior 5.2.4 exact-tag source-service evidence
The immutable v5.2.4 candidate was not promoted. Exact-tag GitHub Actions run
`33431386002` recorded 12 successful jobs and one failed openSUSE job. That job's
standalone `Serviceinfo` harness passed the service directory to the executor
but did not make it the process working directory; dependent native Windows and
macOS jobs were skipped.
A disposable local openSUSE Tumbleweed reproduction independently resolved
`refs/tags/v5.2.4` to the tagged commit. With `osc` 1.27.3,
`obs-service-obs_scm` 0.12.4, `obs-service-tar` 0.12.4, and
`obs-service-recompress` 0.5.2 installed, the same executor completed
`obs_scm`, `tar`, and `recompress` after `os.chdir(service_dir)`. It produced
exactly one `zupt-5.2.4.tar.gz`; its SHA-256 was
`aa68a58fc2e88ee92296542de1f189e2b8a803154d832fb04d5296b25acaef8f`, and the
source scanner reported `PASS source-only: 204 files, 1 archives`.
This result establishes that the explicit tag revision works and isolates a
release/test harness defect. It does not change the product, archive format,
cryptography, codec, or SDK ABI; it does not make skipped native jobs pass or
establish authenticated OBS/Factory acceptance. No v5.2.4 evidence transferred
automatically to v5.2.8; the exact candidate later repeated every applicable
upstream gate in run `33456209269`, as recorded below. The automatic openSUSE
`debugsource` rpmlint `no-binary` finding remains unresolved and unsuppressed.
## Prior 5.2.5 exact-tag native-gate evidence
The immutable v5.2.5 candidate was not promoted. Exact-tag GitHub Actions run
`33434986357` completed 13 jobs successfully and failed the native Windows and
macOS jobs. Windows exposed a hostile-path fixture that did not preserve its
requested bytes across the command-line boundary; macOS exposed the unsupported
`explicit_bzero` assumption and Bash 3.2 empty-array handling. The 5.2.6
corrections address those release/test integration defects without an archive,
cryptographic, codec, or SDK ABI change.
## Prior 5.2.6 exact-tag native-gate evidence
The immutable v5.2.6 candidate was not promoted. Exact-tag GitHub Actions run
`33442264243` completed 13 jobs successfully and failed two native jobs. The
macOS arm64 SHA-NI test build treated unused x86-only helper declarations as
errors under `-Werror`; Windows argv transcoding aborted the safe printable
UTF-8 fixture before its intended path assertions. The 5.2.7 changes correct
those test-harness boundaries without an archive-format, cryptographic, codec,
or SDK ABI change. They do not establish 5.2.8 hosted, native, OBS, or promotion
evidence.
## Prior 5.2.7 exact-tag native-gate evidence
The immutable v5.2.7 candidate was not promoted. Exact-tag GitHub Actions run
`33445470664` concluded `cancelled` at `2026-08-31T23:11:19Z`, with 13
successful jobs, one failed macOS job, and one cancelled Windows job. macOS
rejected creation of the raw-C1 scanner fixture
with `EILSEQ`; the hosted Windows job stalled in `make check`, and a MinGW/Wine
reproduction isolated the cause to a redirected password prompt entering
`_getch`. Version 5.2.8 makes those test
boundaries fail or skip without hanging, addresses CodeQL High #5/#6/#7 in SDK
key publication, disk restore, and benchmark cleanup, and adds `sdk-test` to
release and hosted Linux gates. None of those changes establishes an exact
5.2.8 OBS, native, hosted-CI, or promotion result.
## 5.2.8 exact-tag upstream package evidence
Manually dispatched exact-tag GitHub Actions run `33456209269` passed all 15
jobs at `ebb9ab3aa1d42c50030ca02883f6162dc4771fe1`. Its openSUSE Tumbleweed
x86_64 job parsed and normalized the spec, executed the pinned standalone OBS
source-service chain, source-scanned the resulting archive, built the binary
RPM and genuine SRPM, ran `rpmlint` without suppressions, and completed the
install/round-trip/uninstall test. The canonical source archive is 798296 bytes
with SHA-256
`378b9506211545b9594cf0d38ac8955d9b1cac34eb6b379ae0ec26b84edb65f7`.
Promotion run `33457868306` published the exact tested binary RPM and SRPM with
the other gated assets. The source package is identified by
`%{SOURCEPACKAGE}=1` and an absent `%{SOURCERPM}`; its `%{ARCH}` legitimately
reflects the spec's build architecture and is not the SRPM discriminator.
Repository, Git archive, and upstream source tarball scans remain binary-free.
This is upstream local-service and package evidence, not a claim that the
package was submitted to or accepted by openSUSE Factory, nor a result for the
full set of automatically generated OBS debug packages or any untested
architecture.
## Prior openSUSE packaging validation
The local results below were produced on 2026-08-24 from the 5.2.2 candidate
snapshot captured for the packaging run, in a disposable openSUSE Tumbleweed
20260822 x86_64 container. This matrix was documented afterward, so the results
validate that captured snapshot, not the later documentation edit, a future
commit or a tag. Commit- and tag-dependent checks must be repeated after the
final commit; the validation tarball checksum below is not a release checksum.
`SKIP` is not success.
| Gate | Result | Evidence |
|---|---|---|
| `_service` XML syntax | PASS | `xmllint --noout`; installed service definitions and parameters also exercised locally. |
| ShellCheck for packaging, export, source-policy, and security regression scripts | PASS | ShellCheck 0.10.0 returned zero for the scripts listed in the CI source-policy job, including the scanner and new archive/disk regressions; repeat after the final commit/tag. |
| Upstream source-only scanner and adversarial scanner tests | PASS | Clean snapshot: 191 files; OBS tar: 191 files/1 archive; SRPM tree: 193 files/1 archive; 29 positive/negative scanner regressions passed. |
| Reproducible source archive (two builds, same SHA-256) | PASS | Two local `obs_scm`/`tar`/`recompress` runs were byte-identical (`39e59f5e...`, validation only; regenerate after the real tag). |
| Upstream build, `make check`, and `make test-all` | SKIP | The real RPM `%check`/`make check` passed; an exact-candidate `make test-all` result was not produced by this packaging run. |
| Positional DATA/DEDUP_REF AAD and mandatory-AIT regressions | PASS | `%check` passed AIT removal, F-09 preface, DATA/REF reorder/replay, little-endian, varint and atomic-output regressions. |
| v5.2.1 encrypted+dedup disk compatibility | PASS | Working-tree candidate decoded the textual 718-byte v5.2.1 DATA/DATA/REF/DATA fixture, then `list`, `test`, generic extraction, and byte-exact disk restore passed; repeat after the final commit/tag. |
| `rpmspec` parse | PASS | Both `rpmspec -P` and `rpmspec --parse` returned zero; Source0 resolved to `zupt-5.2.2.tar.gz`. |
| `spec-cleaner` | PASS | Version 1.2.4+2 returned zero and proposed no diff. |
| `rpmbuild` source and binary RPM | PASS | `rpmbuild -bs` and `-ba` passed from the service-generated Source0 with the openSUSE `.changes` conversion. |
| `rpmlint` main RPM + SRPM | PASS | 0 errors and one `invalid-url Source0` warning for the service-generated local Source0; no `rpmlintrc` or suppression was added. |
| `rpmlint` including automatic debug packages | FAIL | `debugsource: no-binary` error and expected `debuginfo: unstripped-binary-or-object` warning from the complete generated package set; debug packages were not disabled or suppressed. |
| `osc service` | PASS | Installed `obs_scm` 0.12.4, `tar` 0.12.4 and `recompress` 0.5.2 produced the correctly named source tar locally; canonical tag fetch remains tag-dependent. |
| Tumbleweed x86_64 local build/install/round trip/uninstall | PASS | Tumbleweed 20260822 container: RPM `%check`, root and `nobody` installed tests, content/hardening audit and clean uninstall passed. This is not an OBS/Factory result. |
| Official OBS `osc build` invocation | FAIL | The command reached `https://api.opensuse.org` but returned HTTP 401 because no OBS credentials are configured. |
| Factory/Tumbleweed x86_64 OBS validation | SKIP | The failed authenticated `osc build` invocation produced no Factory build result; local Tumbleweed evidence is not promoted to Factory evidence. |
| aarch64, ppc64le, s390x, riscv64 | SKIP | No build evidence yet. |
| Leap and SLE | SKIP | No build evidence yet. |
`SKIP` is not success. Factory/Tumbleweed x86_64 remains the primary downstream
gate.
## Handoff procedure for Alessandro/Cabelo
1. Upstream completes every applicable pre-tag source and local audit gate,
then creates and verifies the annotated `v5.2.8` tag. Exact-tag hosted,
native-platform, package, and promotion gates must pass before release or
downstream handoff; the tag itself is never moved to repair a failure.
2. With Git, `file`, bsdtar, tar, zip, unzip and SHA-256 tools installed, run
`scripts/export-opensuse-package.sh v5.2.8`. Verify the reported ZIP and
SHA-256 outside the Git index. The handoff includes both
`packaging/opensuse/source-audit.sh` and its required
`scripts/check-source-only.sh`; keep that relative layout while auditing.
3. Check out the OBS package:
```sh
osc checkout home:cabelo:innovators zupt
cd home:cabelo:innovators/zupt
```
4. From the extracted handoff root, run
`packaging/opensuse/source-audit.sh --archive /path/to/zupt-5.2.8.tar.gz`.
Then copy `_service`, `zupt.spec`, `zupt.changes` and `README.md`
into the flat OBS package checkout. The audit wrapper is not an OBS build
source and must not be copied without its companion `scripts/` directory.
5. Run the local validation workflow above, including the installed round-trip
test. Build every repository and architecture enabled in the OBS project;
record failures or unavailable gates as such.
6. Review `osc diff`, confirm that no RPM or other binary was added as a source,
and commit to OBS only after the required gates pass.
For future releases, increment the stable patch version, create a new immutable
tag, update the matching revision/version in `_service`, spec and changes, run
the source-only scanner, regenerate the handoff, and repeat every OBS gate.
Never move an existing tag or consume forge release binaries as `Source0`.

View file

@ -1,16 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
<services>
<service name="tar_scm" mode="manual">
<param name="url">https://github.com/cristiancmoises/zupt</param>
<param name="scm">git</param>
<param name="revision">v4.1.0</param>
<param name="versionformat">@PARENT_TAG@</param>
<param name="versionrewrite-pattern">v(.*)</param>
<param name="submodules">enable</param>
<param name="filename">vaptvupt</param>
</service>
<service name="recompress" mode="manual">
<param name="file">*.tar</param>
<param name="compression">gz</param>
</service>
<service name="set_version" mode="manual"/>
<service name="obs_scm" mode="manual">
<param name="url">https://github.com/cristiancmoises/zupt.git</param>
<param name="scm">git</param>
<param name="revision">refs/tags/v5.2.8</param>
<param name="versionformat">@PARENT_TAG@</param>
<param name="versionrewrite-pattern">^v(.*)$</param>
<param name="versionrewrite-replacement">\1</param>
<param name="filename">zupt</param>
<param name="submodules">disable</param>
<param name="lfs">disable</param>
</service>
<service name="tar" mode="buildtime"/>
<service name="recompress" mode="buildtime">
<param name="file">*.tar</param>
<param name="compression">gz</param>
</service>
</services>

View file

@ -0,0 +1,14 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
set -Eeuo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
SCANNER=$SCRIPT_DIR/../../scripts/check-source-only.sh
if [[ ! -x $SCANNER ]]; then
printf 'ERROR: source-only scanner is missing or not executable: %s\n' "$SCANNER" >&2
exit 2
fi
exec "$SCANNER" "$@"

View file

@ -1,103 +0,0 @@
#
# spec file for package vaptvupt
#
# Copyright (c) 2026 SUSE LLC
# Copyright (c) 2026 Alessandro de Oliveira Faria (A.K.A CABELO) <cabelo@opensuse.org>
# Copyright (c) 2025-2026 Cristian Cezar Moisés <zupt@riseup.net> (upstream)
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
# Please submit bugfixes or comments via https://bugs.opensuse.org/
#
Name: vaptvupt
Version: 4.1.0
Release: 0
Summary: Post-quantum backup compression with AES-256 + ML-KEM-768 hybrid encryption
License: AGPL-3.0-or-later
Group: Productivity/Archiving/Compression
URL: https://git.securityops.co/cristiancmoises/vaptvupt
Source0: %{name}-%{version}.tar.gz
BuildRequires: gcc
BuildRequires: gzip
BuildRequires: make
# v3.0.0 renamed the project Zupt -> VaptVupt (prior INPI Brasil
# trademark on "Zupt"). Cleanly supersede any installed zupt package;
# the binary still installs a /usr/bin/zupt compatibility symlink.
Provides: zupt = %{version}-%{release}
Obsoletes: zupt < 3.0.0
%description
VaptVupt (formerly Zupt; renamed in v3.0.0 due to a prior INPI Brasil
trademark on the name "Zupt") compresses and encrypts backup archives.
LZ + ANS compression (VaptVupt codec, ~2-3 GB/s decompression on x86_64
with AVX2 / aarch64 with NEON), AES-256-CTR + HMAC-SHA256 per-block
authenticated encryption, multi-threaded, with ML-KEM-768 + X25519
post-quantum hybrid key encapsulation (FIPS 203 + RFC 7748) via --pq.
This package builds entirely from source with no external library
dependency. The password KDF is PBKDF2-SHA256 (600k iterations). The
optional libzuptsdk-backed modes (Argon2id KDF, --pq-sdk, --pq-box) are
not built here; they require an upstream WITH_SDK=1 build against the
separately distributed libzuptsdk/libpqvaptvupt.
Pure C11, ~5,000 lines of core code. Constant-time cryptographic
primitives are formally verified with Jasmin on x86_64
(zupt_mac_verify_ct, zupt_ct_select_32); a clean C fallback runs on
aarch64 and other architectures.
%prep
%autosetup -p1
chmod +x tests/*.sh
%build
%make_build V=1 WITH_SDK=0 \
CFLAGS="%{optflags} -fPIE -Wall -Wextra -std=c11 -Iinclude -Isrc" \
LDFLAGS="%{?build_ldflags} -pie" \
LDLIBS="-lm -lpthread"
%check
# `make check` is the distro-safe subset added in 2.4.8: runs the
# security-critical regressions (F-06 HMAC, F-08 AIT, F-09 byte
# integrity, F-10 KDF, F-11 auth-fail, F-12 comments) plus NIST/RFC
# vectors. Skips threaded and dist-reproducibility tests that are
# sensitive to build-host environment.
#
# On s390x, fall back to just the vector tests (Jasmin assembly is
# x86_64-only; threading harness has been flaky on big-endian).
%ifarch s390x
%make_build V=1 WITH_SDK=0 \
CFLAGS="%{optflags} -fPIE -Wall -Wextra -std=c11 -Iinclude -Isrc" \
LDFLAGS="%{?build_ldflags} -pie" \
LDLIBS="-lm -lpthread" \
test-vectors
./test_vectors
%else
%make_build V=1 WITH_SDK=0 \
CFLAGS="%{optflags} -fPIE -Wall -Wextra -std=c11 -Iinclude -Isrc" \
LDFLAGS="%{?build_ldflags} -pie" \
LDLIBS="-lm -lpthread" \
check
%endif
%install
%make_install WITH_SDK=0 PREFIX=%{_prefix}
%files
%license LICENSE
%doc README.md SECURITY.md CHANGELOG.md
%{_bindir}/vaptvupt
%{_bindir}/zupt
%{_mandir}/man1/vaptvupt.1%{?ext_man}
%{_mandir}/man1/zupt.1%{?ext_man}
%changelog

View file

@ -1,3 +1,183 @@
-------------------------------------------------------------------
Mon Aug 31 23:30:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
- Update to 5.2.8:
* Close CodeQL High path-race findings in SDK key publication,
descriptor-first disk restore, and benchmark workspace cleanup.
* Make the raw-C1 scanner fixture explicitly skip filesystems that reject
the byte with EILSEQ, reject redirected Windows prompts before _getch, and
add sdk-test to release/hosted Linux gates.
* Preserve immutable, unpromoted v5.2.7 run 33445470664: 13 jobs succeeded,
macOS failed the raw-C1 fixture, and Windows was cancelled after the hosted
job stalled; a MinGW/Wine reproduction isolated redirected _getch entry.
* Pin the OBS source service to the immutable v5.2.8 tag and require fresh
exact-candidate evidence before promotion.
-------------------------------------------------------------------
Mon Aug 31 23:00:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
- Update to 5.2.7:
* Scope SHA-NI test helpers away from unsupported macOS arm64 builds.
* Preserve safe UTF-8 fixture bytes across the Windows argv boundary.
* Preserve immutable, unpromoted 5.2.6 history and require fresh 5.2.7 gates.
* Pin the OBS source service to the immutable v5.2.7 tag.
-------------------------------------------------------------------
Mon Aug 31 21:30:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
- Update to 5.2.6:
* Use the compiler-resistant volatile wipe fallback on macOS and NetBSD.
* Make source-scanner empty-array handling compatible with Bash 3.2.
* Preserve hostile archive-path fixture bytes exactly on Windows.
* Preserve immutable, unpromoted 5.2.5 history and require fresh 5.2.6 gates.
* Pin the OBS source service to the immutable v5.2.6 tag.
-------------------------------------------------------------------
Mon Aug 31 19:55:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
- Update to 5.2.5:
* Run the standalone OBS source-service chain from its isolated working
directory so downstream services can find .obsinfo.
* Add a packaging-policy regression for the executor working directory.
* Preserve immutable, unpromoted 5.2.4 history and require fresh 5.2.5 gates.
* Pin the OBS source service to the immutable v5.2.5 tag.
-------------------------------------------------------------------
Mon Aug 31 18:55:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
- Update to 5.2.4:
* Make the static Windows GUI package-version check robust to canonical
CRLF checkouts.
* Advance source-only package metadata and prepare final archive hashes.
* Pin the OBS source service to the immutable v5.2.4 tag.
-------------------------------------------------------------------
Mon Aug 31 18:15:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
- Update to 5.2.3:
* Derive package checks from the upstream version header and stabilize the
GUI version output consumed by package gates.
* Replace busybox-gawk before installing the native Tumbleweed RPM tooling.
* Pin the OBS source service to the immutable v5.2.3 tag.
-------------------------------------------------------------------
Mon Aug 31 00:00:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
- Update to 5.2.2:
* Convert the upstream and OBS inputs to a source-only release: remove
precompiled library and object inputs and reject their reintroduction with
reusable source-archive auditing.
* Build with WITH_SDK=0 and WITH_PQBOX=0. The optional integrations now
require an explicit source or packaged system dependency and never use a
private precompiled fallback.
* Preserve portable compiler and linker flags, architecture-local optimized
translation units, scalar fallbacks, parallel build, and staged DESTDIR
installation.
* Build the packaged executable as PIE with full RELRO/NOW and a
non-executable stack while preserving automatic debuginfo generation and
avoiding manual stripping or RPATH/RUNPATH.
* Update OBS source services to obs_scm pinned to the immutable v5.2.2 tag;
disable submodules and Git LFS and create the compressed tarball at build
time.
* Run the real upstream check target from the RPM check phase without
architecture-specific test suppression.
* Harden archive extraction against traversal, symlink/hardlink and Windows
reparse-point races; publish only fully size/checksum-verified temporary
output and add structurally valid hostile-archive regression fixtures.
* Reject normal, solid, and disk-backup output aliases of an input file,
including alternate spellings, hardlinks, and symlinks, before creating the
output; --force cannot bypass this data-loss guard.
* Snapshot disk-restore input privately before opening its destructive
destination and restore from the same validated stream. Reject raw devices
whose capacity is unknown or smaller than the image before the first write.
* Enforce DATA frame types across serial, threaded, solid, test, and disk
readers, and retain the exact encrypted+dedup AAD sequence used by v5.2.1.
Test an actual v5.2.1 password-encrypted DATA/DATA/REF/DATA disk fixture
through list, test, generic extraction, and disk restore.
* Use random private benchmark scratch directories and remove them without
following links instead of using a predictable process-ID path.
* Package the AGPL-3.0-or-later application together with the bundled
GPL-3.0-or-later VaptVupt codec 2.65.3 and the BSD-2-Clause XXH64-derived
routines; preserve all applicable notices.
* Rename the application and package back to ZUPT/zupt. Install only the
zupt command and add versioned Provides/Obsoletes for migration from the
former vaptvupt package without shipping a duplicate executable.
* Add the source-only openSUSE handoff/export workflow and validation matrix.
* Add explicit password prompt, file, and inherited-descriptor inputs.
* Validate the source audit, rpmbuild -bs/-ba, the complete RPM check phase,
package contents and dependencies, installed round trips, and clean
uninstall in a disposable openSUSE Tumbleweed 20260822 x86_64 container.
OBS/Factory, other architectures, Leap, and SLE remain separate unexecuted
downstream gates and are not claimed by this validation.
* Correct the licensing record without revoking historical MIT grants present
in earlier repository revisions; current files follow current SPDX notices.
* Correct the stale public-domain statement for XXH64-derived code and retain
Yann Collet's BSD-2-Clause copyright, conditions, and disclaimer.
* Record the CC0-1.0 option and provenance for pq-crystals/kyber-derived
ML-KEM portions, the BSD-3-Clause curve25519-donna origin of native X25519
portions, and the public-domain LZMA SDK origin of the x86 BCJ code.
* Keep AppImage outside the 5.2.2 promoted set until its static runtime has a
complete license/source-relink handoff; publish Windows only as a ZIP with
the executable and notices.
* Gate notice-bearing Linux tar.xz and Windows/macOS CLI bundles plus GUI
DEB, noarch RPM, source RPM, and source-only portable ZIP artifacts; keep
bare executables out of the promoted set.
* Qualify historical formal-verification and constant-time wording: current
source review and runtime regressions are not a proof for every compiler,
CPU, or final package binary.
-------------------------------------------------------------------
Fri Jul 10 18:00:00 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org>
- Update to 5.0.0:
* ML-KEM-768 is now genuinely FIPS 203-conformant (was round-3
CRYSTALS-Kyber): fixed a transposed matrix-A sampling convention,
the round-3 KDF, and the implicit-rejection domain. Validated
byte-for-byte against OpenSSL 3.5's FIPS 203 ML-KEM-768
(tests/test_mlkem_fips203.sh, run in %check).
* BREAKING: --pq / --pq-only keys and archives from <= 4.2.1 no
longer decrypt (the KEM math changed). Regenerate keys and
re-encrypt. Password mode and plain compression are unaffected;
wire format stays v1.6.
* Security: compress data-loss and silent-plaintext guards; heap
OOB read in the AVX2 decoder bounded; overflow-safe solid-mode
test path; secret-wipe on hybrid-decrypt key-read error.
* GUI reworked for the source-only build; truthful banner/help.
-------------------------------------------------------------------
Fri Jul 10 12:00:00 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org>
- Update to 4.2.1:
* Fix: "vaptvupt info" mislabelled full post-quantum (--pq-only)
archives as "PQ Hybrid (ML-KEM-768 + X25519)". info now reads the
real enc_type from the encryption-header block and reports the
actual mode ("ML-KEM-768 only, no classical layer" for --pq-only).
Reader-side only; no wire-format change, existing 4.2.0 archives are
relabelled with no re-encryption.
-------------------------------------------------------------------
Thu Jul 9 12:00:00 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org>
- Update to 4.2.0:
* New native full (pure) post-quantum mode --pq-only: ML-KEM-768
(FIPS 203) as the sole key-establishment mechanism, with no
classical X25519 component (envelope type 0x06; archive key
SHA3-512(ml_ss || ml_ct || "ZUPT-PQ-ONLY-v1")). For compliance
postures that mandate a single NIST-standardised PQ primitive with
no classical KEM in the envelope (CNSA 2.0-style "PQ-only"). Keys
via keygen --pq-only. In-tree crypto, built in the default
source-only package. Hybrid --pq remains the recommended default;
--pq-only has no classical fallback.
* Security (critical): fixed AES-256-CTR keystream reuse under
--dedup. Dedup blocks all use sequence 0, so the previous nonce
(base_nonce XOR seq) collapsed to a single value across blocks,
reusing the CTR keystream. Each block now uses a fresh random
128-bit nonce. Re-encrypt any --dedup encrypted archives written
by <= 4.1.0.
* keygen --sdk / --box now gives clear guidance toward native --pq /
--pq-only on a source-only build.
* Wire format v1.6 unchanged; the 0x06 envelope is additive.
-------------------------------------------------------------------
Tue Jul 7 12:00:00 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org>
@ -192,10 +372,10 @@ Tue May 26 02:27:34 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org
Tue May 26 00:43:52 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org>
- Update to 3.0.1
* GUI license cleanup: removed MIT credit line from the about
panel; gui/LICENSE-GUI replaced (was MIT) with AGPL-3.0-or-later
to match the source SPDX header. The GUI was never actually
released under MIT — that was a templating mistake.
* GUI license metadata changed to AGPL-3.0-or-later for the then-current
source. The original entry incorrectly denied earlier MIT grants; the
5.2.2 erratum records that they remain valid for the exact historical
material distributed under them.
* GUI version-string parsing bug fix (the replace("zupt ", ...)
substring also matched inside the v3.0.0 parenthetical). Window
title, splash header, status bar and about-panel hero number now
@ -244,7 +424,7 @@ Sun May 24 13:08:04 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org
OBS %check (no `make clean` mid-stream, no threading-flaky
tests). Spec now calls `make check` on x86_64/aarch64.
* License field corrected: AGPL-3.0-or-later (was MIT in 1.5.x).
Project is dual-licensed AGPL-3.0-or-later + commercial.
Commercial-terms inquiry information was documented separately.
* Upstream URL updated to git.securityops.co.
-------------------------------------------------------------------

View file

@ -0,0 +1,83 @@
#
# spec file for package zupt
#
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2026 SUSE LLC
# Copyright (c) 2026 Alessandro de Oliveira Faria (A.K.A. Cabelo) <cabelo@opensuse.org>
# Alessandro's attribution is for downstream openSUSE/OBS packaging only.
# Copyright (c) 2025-2026 Cristian Cezar Moisés <sac@securityops.co> (upstream)
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
#
Name: zupt
Version: 5.2.8
Release: 0
Summary: Backup compression with authenticated and post-quantum encryption
License: AGPL-3.0-or-later AND GPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND CC0-1.0
URL: https://github.com/cristiancmoises/zupt
Source0: %{name}-%{version}.tar.gz
BuildRequires: bash
BuildRequires: coreutils
BuildRequires: diffutils
BuildRequires: file
BuildRequires: findutils
BuildRequires: gawk
BuildRequires: gcc
BuildRequires: git-core
BuildRequires: grep
BuildRequires: gzip
BuildRequires: make
BuildRequires: python3-base
BuildRequires: sed
BuildRequires: tar
Provides: bundled(vaptvupt-codec) = 2.65.3
Provides: vaptvupt = %{version}-%{release}
Obsoletes: vaptvupt < %{version}
%description
ZUPT creates compressed backup archives with optional authenticated
password encryption or ML-KEM-768 and X25519 hybrid key encapsulation. The
default package is built entirely from the source in the release archive.
Optional SDK and PQBOX features are disabled because audited development
packages are unavailable. No private compiled library is installed.
%prep
%autosetup -p1
bash scripts/check-source-only.sh --tree .
%build
%make_build WITH_SDK=0 WITH_PQBOX=0 \
CFLAGS="%{optflags} -fPIE" \
LDFLAGS="%{?build_ldflags} -Wl,-z,relro,-z,now -pie"
%check
%make_build WITH_SDK=0 WITH_PQBOX=0 \
CFLAGS="%{optflags} -fPIE" \
LDFLAGS="%{?build_ldflags} -Wl,-z,relro,-z,now -pie" \
check
%install
%make_install WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 INSTALL_LICENSES=0 \
PREFIX=%{_prefix} \
BINDIR=%{_bindir} \
MANDIR=%{_mandir}
%files
%license LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE THIRD-PARTY-NOTICES.md
%doc README.md CHANGELOG.md SECURITY.md THREAT_MODEL.md
%{_bindir}/zupt
%{_datadir}/bash-completion/completions/zupt
%{_datadir}/zsh/site-functions/_zupt
%{_datadir}/fish/vendor_completions.d/zupt.fish
%{_mandir}/man1/zupt.1%{?ext_man}
%changelog

View file

@ -0,0 +1,59 @@
ZUPT GUI — source-only portable launcher template
=====================================================
This tracked directory contains three launcher templates and this assembly
guide; it is not a complete bundle by itself. A downstream source-only bundle
may add the integrated Python GUI source and artwork listed below, together
with the required license/provenance files. It must not contain Python, Qt, a
precompiled ZUPT command, or a vendored library. Its presence in a release
would not be evidence that every target operating system was tested; consult
that release's validation matrix.
Contents
--------
zupt_gui.py GUI source module (the historical module filename is
retained internally for source compatibility).
zupt-gui.bat Windows launcher.
zupt-gui.command macOS Finder launcher.
zupt-gui.sh POSIX shell launcher.
assets/zupt-icon.png PNG application artwork.
assets/zupt.ico Windows application artwork.
LICENSE-AGPL-3.0 Complete current GUI source license text.
LICENSE-GUI GUI licensing and historical-license note.
ASSET-PROVENANCE.md Artwork purpose, provenance, and license record.
CHANGELOG.md Release history and current compatibility notes.
Requirements
------------
1. Python 3.9 or newer.
2. PySide6 6.5 or newer, or a compatible PyQt6 package.
3. ZUPT 5.2.8, installed as `zupt` on PATH or placed beside the launcher
(`zupt.exe` on Windows). A local command must have been built
and tested independently; this bundle never downloads one.
Running
-------
Windows: zupt-gui.bat
macOS: zupt-gui.command
POSIX: ./zupt-gui.sh
The launchers set ZUPT_BIN when a local command is present. The GUI then
checks `zupt version`, discovers native and optional capabilities, and
exposes SDK or PQ-box modes only when the command reports the corresponding
system-library integration enabled.
Troubleshooting
---------------
* "requires PySide6 or PyQt6": install one Qt binding through your operating
system package manager or another trusted, preconfigured Python source.
* "zupt not found": install ZUPT 5.2.8 or place its command beside
the launcher.
* Set ZUPT_DEBUG=1 to print command-discovery diagnostics to stderr.
The old user-facing command name is not installed by this bundle. The `.zupt`
archive extension remains unchanged for format compatibility.
Current GUI source license: AGPL-3.0-or-later. Published historical revisions
include MIT grants for the exact material covered by their notices; see
LICENSE-GUI and the 5.2.2 erratum in CHANGELOG.md.
Project: https://github.com/cristiancmoises/zupt

View file

@ -0,0 +1,30 @@
@echo off
rem SPDX-License-Identifier: AGPL-3.0-or-later
rem ZUPT GUI launcher for Windows (portable package).
rem
rem Requirements on the target machine:
rem * Python 3.9+
rem * PySide6 or PyQt6: py -m pip install PySide6
rem * The ZUPT CLI: zupt.exe next to this file, or on PATH.
rem
rem If zupt.exe sits beside this launcher we pin it via ZUPT_BIN so the
rem GUI drives the bundled CLI rather than any other copy on PATH.
setlocal
set "HERE=%~dp0"
if exist "%HERE%zupt.exe" set "ZUPT_BIN=%HERE%zupt.exe"
rem Prefer the py launcher, fall back to python on PATH.
where py >nul 2>nul
if %ERRORLEVEL%==0 (
py -3 "%HERE%zupt_gui.py" %*
) else (
python "%HERE%zupt_gui.py" %*
)
set "RC=%ERRORLEVEL%"
if not "%RC%"=="0" (
echo.
echo zupt-gui exited with code %RC%.
echo If you saw an import error, install the Qt binding: py -m pip install PySide6
echo If the CLI was not found, put zupt.exe next to this launcher or on PATH.
)
endlocal & exit /b %RC%

View file

@ -0,0 +1,17 @@
#!/bin/bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# ZUPT GUI launcher for macOS (portable package).
# Double-clickable in Finder (.command). Requirements on the target Mac:
# * Python 3.9+
# * PySide6 or PyQt6: python3 -m pip install PySide6
# * The ZUPT CLI: `zupt` next to this file, or on PATH
# (Homebrew: `brew install cristiancmoises/tap/zupt`).
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
[ -x "$HERE/zupt" ] && export ZUPT_BIN="$HERE/zupt"
PY="$(command -v python3 || command -v python || true)"
if [ -z "$PY" ]; then
osascript -e 'display alert "ZUPT GUI" message "Python 3.9 or newer was not found. Install Python and a trusted PySide6 or PyQt6 package."' 2>/dev/null
echo "Python 3 not found." >&2; exit 1
fi
exec "$PY" "$HERE/zupt_gui.py" "$@"

21
packaging/portable/zupt-gui.sh Executable file
View file

@ -0,0 +1,21 @@
#!/bin/sh
# SPDX-License-Identifier: AGPL-3.0-or-later
# ZUPT GUI launcher for Linux and the BSDs (portable package).
# Requirements on the target system:
# * Python 3.9+
# * PySide6 or PyQt6
# Debian/Ubuntu: sudo apt install python3-pyqt6
# Fedora/RHEL: sudo dnf install python3-pyqt6
# FreeBSD: pkg install py311-pyside6 (or py311-qt6-pyqt)
# OpenBSD: pkg_add py3-pyside6
# any OS via pip: python3 -m pip install PySide6
# * The ZUPT CLI: `zupt` next to this file, or on PATH.
HERE="$(cd "$(dirname "$0")" && pwd)"
[ -x "$HERE/zupt" ] && export ZUPT_BIN="$HERE/zupt"
PY="$(command -v python3 || command -v python || true)"
if [ -z "$PY" ]; then
echo "zupt-gui: Python 3 not found on PATH." >&2
exit 1
fi
exec "$PY" "$HERE/zupt_gui.py" "$@"

View file

@ -1,119 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Fedora / RHEL / CentOS RPM spec for vaptvupt.
#
# Build with:
# spectool -g vaptvupt.spec # fetches the upstream tarball
# rpmbuild -ba vaptvupt.spec # builds source + binary RPMs
#
# To bring a release into production:
# 1. Run `make dist` upstream → /tmp/vaptvupt-VERSION.tar.gz (reproducible).
# 2. Upload to a stable release URL (git.securityops.co releases).
# 3. Update %{version} below.
# 4. Run `sha256sum /tmp/vaptvupt-VERSION.tar.gz` and update Source0
# checksum (handled by spectool when configured) or pin via
# sha256sum in a separate manifest if your distro requires it.
# 5. rpmbuild --define '_topdir ~/rpmbuild' -ba zupt.spec
#
# This spec is written for Fedora 38+ and EPEL 9+; it should also work
# on RHEL 8 (with EPEL) by adjusting BuildRequires if Python 3.8+ isn't
# in the base.
Name: vaptvupt
Version: 4.1.0
Release: 1%{?dist}
Summary: Post-quantum backup compression utility (AES-256 + ML-KEM-768 + Argon2id, formerly Zupt)
License: AGPL-3.0-or-later AND GPL-3.0-or-later
URL: https://git.securityops.co/cristiancmoises/vaptvupt
Source0: %{url}/releases/download/v%{version}/%{name}-%{version}.tar.gz
# v3.0.0: legacy `zupt` package is superseded. Renaming was forced
# by a prior INPI Brasil trademark registration on "Zupt". The
# archive extension (.zupt), wire format, magic bytes, and C ABI
# are unchanged.
Provides: zupt = %{version}-%{release}
Obsoletes: zupt < 3.0.0
Conflicts: zupt < 3.0.0
BuildRequires: gcc
BuildRequires: make
BuildRequires: glibc-devel
BuildRequires: python3 >= 3.8
# python3 is only needed for the regression-test harness (byte sweeps,
# tamper injection). The shipped binary has no Python dependency.
Requires: glibc
%description
Zupt is a pure-C11 backup compression utility featuring:
* Post-quantum hybrid encryption (ML-KEM-768 + X25519, FIPS 203)
* AES-256-CTR + HMAC-SHA256 authenticated encryption (Encrypt-then-MAC)
* Argon2id password-based key derivation (default since 2.4.1)
* Multi-threaded compression with the VaptVupt LZ codec
* Full-disk backup and restore with sparse-region detection
* End-to-end byte-level tamper detection on encrypted archives
(0 silent-accept positions in the v1.6 exhaustive byte sweep)
* Constant-time cryptographic primitives verified with Jasmin
* NIST/RFC test vectors for SHA-256, SHA-3, ML-KEM-768, AES-256-CTR,
HMAC-SHA256, X25519, PBKDF2, Argon2id
The archive format includes an integrity trailer that authenticates
the header and footer, per-block HMAC with bound frame-preface AAD,
and optional encrypted comments.
%global debug_package %{nil}
# Single source RPM, no -debuginfo split for the initial release.
%prep
%autosetup -n %{name}-%{version}
%build
# Use Fedora's default optflags but with the project's preferred warning set.
CFLAGS="%{optflags} -Wall -Wextra -Wpedantic -std=c11" \
LDFLAGS="%{?build_ldflags}" \
%make_build
%check
# Run the upstream regression suite. F-06 HMAC trials, F-08 top-MAC sweep,
# F-09 byte sweep (1827 positions), F-10..F-12 regressions, dist
# reproducibility. ~3 minutes on modern hardware.
%make_build test
%install
%make_install DESTDIR=%{buildroot} PREFIX=/usr
# Install the vendored libzuptsdk into /usr/lib/zupt/ — the binary is
# linked with -Wl,-rpath,$ORIGIN/vendor/zuptsdk so we preserve the same
# layout under /usr/.
install -d %{buildroot}%{_libdir}/%{name}
install -m 0755 vendor/zuptsdk/libzuptsdk.so.2.0.0 \
%{buildroot}%{_libdir}/%{name}/libzuptsdk.so.2.0.0
ln -sf libzuptsdk.so.2.0.0 %{buildroot}%{_libdir}/%{name}/libzuptsdk.so.2
ln -sf libzuptsdk.so.2.0.0 %{buildroot}%{_libdir}/%{name}/libzuptsdk.so
install -m 0755 vendor/pqvaptvupt/libpqvaptvupt.so.0.6.0 \
%{buildroot}%{_libdir}/%{name}/libpqvaptvupt.so.0.6.0
ln -sf libpqvaptvupt.so.0.6.0 %{buildroot}%{_libdir}/%{name}/libpqvaptvupt.so.0
ln -sf libpqvaptvupt.so.0.6.0 %{buildroot}%{_libdir}/%{name}/libpqvaptvupt.so
%files
%license LICENSE
%doc README.md SECURITY.md CHANGELOG.md
%{_bindir}/zupt
%{_libdir}/%{name}/libzuptsdk.so.2.0.0
%{_libdir}/%{name}/libzuptsdk.so.2
%{_libdir}/%{name}/libzuptsdk.so
%{_libdir}/%{name}/libpqvaptvupt.so.0.6.0
%{_libdir}/%{name}/libpqvaptvupt.so.0
%{_libdir}/%{name}/libpqvaptvupt.so
%if 0%{?_mandir:1}
%{_mandir}/man1/zupt.1*
%endif
%changelog
* Tue May 20 2025 Cristian Cezar Moisés <sac@securityops.co> - 2.4.4-1
- Initial Fedora/EPEL RPM package.
- Tracks upstream v2.4.4: distribution packaging release; archive
format unchanged from v2.4.3 (v1.6, 0/1878 silent-accept byte
tampers).

180
packaging/rpm/zupt.spec Normal file
View file

@ -0,0 +1,180 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Fedora / RHEL / CentOS RPM spec for zupt.
#
# Build with:
# spectool -g zupt.spec # fetches the upstream tarball
# rpmbuild -ba zupt.spec # builds source + binary RPMs
#
# To bring a release into production:
# 1. Run `make dist` upstream → /tmp/zupt-VERSION.tar.gz (reproducible).
# 2. Upload to the canonical GitHub release.
# 3. Update %{version} below.
# 4. Run `sha256sum /tmp/zupt-VERSION.tar.gz` and update Source0
# checksum (handled by spectool when configured) or pin via
# sha256sum in a separate manifest if your distro requires it.
# 5. rpmbuild --define '_topdir /path/to/rpmbuild' -ba zupt.spec
#
# This is an upstream Fedora-family recipe. A target is supported only after
# that exact distribution release and architecture have built and passed the
# installed smoke test.
Name: zupt
Version: 5.2.8
Release: 1%{?dist}
Summary: Backup compression with authenticated and post-quantum encryption
License: AGPL-3.0-or-later AND GPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND CC0-1.0
URL: https://github.com/cristiancmoises/zupt
Source0: %{url}/releases/download/v%{version}/%{name}-%{version}.tar.gz
BuildRequires: gcc
BuildRequires: git-core
BuildRequires: make
BuildRequires: glibc-devel
BuildRequires: python3 >= 3.8
BuildRequires: bash
BuildRequires: coreutils
BuildRequires: diffutils
BuildRequires: file
BuildRequires: findutils
BuildRequires: gawk
BuildRequires: grep
BuildRequires: gzip
BuildRequires: sed
BuildRequires: tar
# python3 is only needed for the regression-test harness (byte sweeps,
# tamper injection). The shipped binary has no Python dependency.
Provides: bundled(vaptvupt-codec) = 2.65.3
%description
ZUPT is a pure-C11 backup compression utility featuring:
* Post-quantum hybrid encryption (ML-KEM-768 + X25519) and full
ML-KEM-768 mode (--pq-only)
* AES-256-CTR + HMAC-SHA256 authenticated encryption (Encrypt-then-MAC)
* PBKDF2-SHA256 password key derivation (Argon2id in WITH_SDK=1 builds)
* Multi-threaded compression with the VaptVupt LZ + ANS codec
* Full-disk backup and restore with sparse-region detection
* Authenticated encrypted-archive metadata and per-block integrity checks
* Portable C implementations with optional source-built assembly paths
* NIST/RFC test vectors for SHA-256, SHA-3, ML-KEM-768, AES-256-CTR,
HMAC-SHA256, X25519 and PBKDF2
Encrypted archives include an integrity trailer that authenticates the header
and footer, per-block HMAC with bound frame-preface AAD, and optional encrypted
comments. Plain archives use non-cryptographic checksums.
%prep
%autosetup -n %{name}-%{version}
%build
# Source-only build (WITH_SDK=0): no vendored libraries, no external crypto
# dependency. Fedora's default optflags plus the project's warning set.
%make_build V=1 WITH_SDK=0 WITH_PQBOX=0 \
CFLAGS="%{optflags}" \
LDFLAGS="%{?build_ldflags}"
%check
# Distro-safe quick, path-traversal, integrity, codec, HMAC and NIST/RFC
# checks. Full, optional-integration and dist-reproducibility suites remain
# release gates outside the package build.
%make_build V=1 WITH_SDK=0 WITH_PQBOX=0 \
CFLAGS="%{optflags}" \
LDFLAGS="%{?build_ldflags}" \
check
%install
%make_install WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 INSTALL_LICENSES=0 \
PREFIX=%{_prefix} BINDIR=%{_bindir} MANDIR=%{_mandir}
%files
%license LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE THIRD-PARTY-NOTICES.md
%doc README.md SECURITY.md THREAT_MODEL.md CHANGELOG.md
%{_bindir}/%{name}
%{_datadir}/bash-completion/completions/%{name}
%{_datadir}/zsh/site-functions/_%{name}
%{_datadir}/fish/vendor_completions.d/%{name}.fish
%if 0%{?_mandir:1}
%{_mandir}/man1/%{name}.1*
%endif
%changelog
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.8-1
- Close CodeQL High path-race findings in SDK key save, disk restore, and
benchmark cleanup; add the SDK gate, portable raw-C1 fixture handling, and
redirected Windows password-prompt rejection.
- Preserve immutable, unpromoted v5.2.7 run 33445470664: 13 jobs succeeded,
macOS failed the raw-C1 fixture, and Windows was cancelled after the hosted
job stalled; a MinGW/Wine reproduction isolated redirected _getch entry.
- Require fresh 5.2.8 gates.
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.7-1
- Correct native test integration: scope SHA-NI helpers away from macOS arm64
and preserve safe UTF-8 fixture bytes across the Windows argv boundary.
- Preserve immutable, unpromoted 5.2.6 history and require fresh 5.2.7 gates.
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.6-1
- Correct native release gates: use the secure volatile wipe fallback on
macOS and NetBSD, support Bash 3.2 empty arrays in the source scanner, and
preserve hostile archive-path fixture bytes exactly on Windows.
- Preserve immutable, unpromoted 5.2.5 history and require fresh 5.2.6 gates.
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.5-1
- Run the standalone OBS source-service chain from its isolated working
directory and add a packaging-policy regression for that contract.
- Preserve immutable, unpromoted 5.2.4 history and require fresh 5.2.5 gates.
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.4-1
- Make the static Windows GUI package-version check robust to canonical CRLF
checkouts, advance source-only package metadata, and prepare final hashes.
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.3-1
- Correct the release-package CI version checks and portable GUI version
contract, and make the openSUSE container replace busybox-gawk before
installing the native RPM toolchain.
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.2-1
- Source-only release; optional SDK/PQBOX integrations use system development
packages only and are disabled for this package.
- Preserve distribution flags and debuginfo, remove RPATH/vendor-library
fallbacks, run the real upstream check target, and restore the zupt command.
* Sat Jul 11 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.1.0-1
- Codec 2.65.0; large compression-ratio gains (auto format_v2 + level-scaled
block window); --dedup keeps a small block; GUI compress-hang and
job-completion-crash fixes. Wire format unchanged (v1.6).
* Fri Jul 10 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.0.0-1
- ML-KEM-768 is now genuinely FIPS 203-conformant (was round-3 CRYSTALS-Kyber):
fixed a transposed matrix-A sampling convention, the round-3 KDF, and the
implicit-rejection domain. Validated byte-for-byte against OpenSSL 3.5's
FIPS 203 ML-KEM-768 (tests/test_mlkem_fips203.sh, run in %%check).
- BREAKING: --pq / --pq-only keys and archives from <= 4.2.1 no longer decrypt.
Regenerate keys and re-encrypt. Password mode / plain compression unaffected.
- Security: compress data-loss + silent-plaintext guards; AVX2 decoder heap
OOB-read bound; overflow-safe solid-mode test path; secret-wipe on error.
- GUI reworked for the source-only build; truthful banner/help.
* Fri Jul 10 2026 Cristian Cezar Moisés <sac@securityops.co> - 4.2.1-1
- Fix: "vaptvupt info" mislabelled full post-quantum (--pq-only) archives as
"PQ Hybrid (ML-KEM-768 + X25519)". info now reads the real enc_type from the
encryption-header block and reports the actual mode ("ML-KEM-768 only, no
classical layer" for --pq-only). Reader-side only; no wire-format change.
* Thu Jul 09 2026 Cristian Cezar Moisés <sac@securityops.co> - 4.2.0-1
- New native full (pure) post-quantum mode --pq-only: ML-KEM-768 as the
sole KEM, no classical X25519 (envelope 0x06). For "PQ-only" compliance
postures; hybrid --pq remains the recommended default. In-tree crypto.
- Security (critical): fixed AES-256-CTR keystream reuse under --dedup
(every block now uses a fresh random 128-bit nonce). Re-encrypt any
--dedup encrypted archives written by <= 4.1.0.
- Clearer keygen --sdk/--box guidance on source-only builds.
- Wire format v1.6 unchanged.
* Tue May 20 2025 Cristian Cezar Moisés <sac@securityops.co> - 2.4.4-1
- Initial Fedora/EPEL RPM package.
- Tracks upstream v2.4.4: distribution packaging release; archive
format unchanged from v2.4.3 (v1.6, 0/1878 silent-accept byte
tampers).

View file

@ -0,0 +1,98 @@
; SPDX-License-Identifier: AGPL-3.0-or-later
; Inno Setup 6 recipe for target-built ZUPT Windows artifacts.
;
; All paths are mandatory command-line definitions. This prevents the recipe
; from silently picking up a stale or placeholder executable from the tree.
#ifndef AppVersion
#error AppVersion must be defined
#endif
#ifndef GuiExecutable
#error GuiExecutable must name a tested PyInstaller GUI executable
#endif
#ifndef CliExecutable
#error CliExecutable must name a tested source-built zupt.exe
#endif
#ifndef BuildOutputDir
#error BuildOutputDir must be an external output directory
#endif
#ifndef RuntimeNoticesDir
#error RuntimeNoticesDir must contain notices for the exact bundled GUI runtime
#endif
[Setup]
AppId={{59AD35E4-1860-445D-8E89-4563DB9ED4E2}
AppName=ZUPT
AppVersion={#AppVersion}
AppPublisher=Cristian Cezar Moises
AppPublisherURL=https://github.com/cristiancmoises/zupt
AppSupportURL=https://github.com/cristiancmoises/zupt/issues
DefaultDirName={autopf}\ZUPT
DefaultGroupName=ZUPT
UninstallDisplayIcon={app}\zupt-gui.exe
OutputDir={#BuildOutputDir}
OutputBaseFilename=ZUPT-Setup-{#AppVersion}
Compression=lzma2
SolidCompression=yes
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
WizardStyle=modern
LicenseFile=..\..\LICENSE
ChangesAssociations=yes
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Files]
Source: "{#GuiExecutable}"; DestDir: "{app}"; DestName: "zupt-gui.exe"; Flags: ignoreversion
Source: "{#CliExecutable}"; DestDir: "{app}"; DestName: "zupt.exe"; Flags: ignoreversion
Source: "..\..\LICENSE"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\..\LICENSE-AGPL-3.0"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\..\LICENSE-GPL-3.0"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\..\LICENSE-BSD-2-Clause"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\..\LICENSE-BSD-3-Clause"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\..\LICENSE-CC0-1.0"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\..\NOTICE"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\..\THIRD-PARTY-NOTICES.md"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\..\gui\LICENSE-GUI"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\..\gui\assets\README.md"; DestDir: "{app}"; DestName: "GUI-ASSET-PROVENANCE.md"; Flags: ignoreversion
Source: "{#RuntimeNoticesDir}\*"; DestDir: "{app}\third-party-runtime-notices"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\..\README.md"; DestDir: "{app}"; Flags: ignoreversion isreadme
Source: "..\..\CHANGELOG.md"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
Name: "{group}\ZUPT GUI"; Filename: "{app}\zupt-gui.exe"
Name: "{group}\ZUPT command prompt"; Filename: "{cmd}"; Parameters: "/K cd /d ""{app}"""
Name: "{group}\Uninstall ZUPT"; Filename: "{uninstallexe}"
Name: "{autodesktop}\ZUPT GUI"; Filename: "{app}\zupt-gui.exe"; Tasks: desktopicon
[Tasks]
Name: "desktopicon"; Description: "Create a desktop shortcut"; GroupDescription: "Additional icons:"
Name: "addtopath"; Description: "Add the ZUPT command to PATH for this user"; GroupDescription: "Command line:"
[Registry]
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; \
ValueData: "{olddata};{app}"; Tasks: addtopath; Check: NeedsAddPath('{app}')
Root: HKCU; Subkey: "Software\Classes\.zupt"; ValueType: string; ValueName: ""; \
ValueData: "ZUPT.Archive"; Flags: uninsdeletevalue
Root: HKCU; Subkey: "Software\Classes\ZUPT.Archive"; ValueType: string; \
ValueName: ""; ValueData: "ZUPT archive"; Flags: uninsdeletekey
Root: HKCU; Subkey: "Software\Classes\ZUPT.Archive\shell\open\command"; \
ValueType: string; ValueName: ""; ValueData: """{app}\zupt-gui.exe"" --extract ""%1"""
[Run]
Filename: "{app}\zupt-gui.exe"; Description: "Launch ZUPT GUI"; \
Flags: nowait postinstall skipifsilent
[Code]
function NeedsAddPath(Param: string): Boolean;
var
OrigPath: string;
begin
if not RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', OrigPath) then
begin
Result := True;
exit;
end;
Result := Pos(';' + ExpandConstant(Param) + ';', ';' + OrigPath + ';') = 0;
end;

View file

@ -1,312 +0,0 @@
#!/bin/bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) 2025-2026 Cristian Cezar Moisés
# ╔════════════════════════════════════════════════════════════════════╗
# ║ ZUPT 2.2.3 + ZUPT-GUI 1.1.1 — UNIVERSAL LINUX INSTALLER ║
# ║ ║
# ║ One script, all distributions. Self-extracting. No internet ║
# ║ needed for the package install (only for Qt6 dependency). ║
# ║ ║
# ║ Usage: sudo bash zupt-installer.sh ║
# ║ Or: sudo bash zupt-installer.sh --gui-only ║
# ║ Or: sudo bash zupt-installer.sh --cli-only ║
# ║ Or: sudo bash zupt-installer.sh --appimage ║
# ║ Or: sudo bash zupt-installer.sh --uninstall ║
# ╚════════════════════════════════════════════════════════════════════╝
set -e
VERSION="2.2.3"
GUI_VERSION="1.1.1"
EXTRACT_DIR=""
cleanup() {
[ -n "$EXTRACT_DIR" ] && [ -d "$EXTRACT_DIR" ] && rm -rf "$EXTRACT_DIR"
}
trap cleanup EXIT
# ── Color output (if terminal supports) ─────────────────────────────
if [ -t 1 ]; then
BOLD='\033[1m'; CYAN='\033[36m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; RESET='\033[0m'
else
BOLD=''; CYAN=''; GREEN=''; YELLOW=''; RED=''; RESET=''
fi
step() { echo -e "${CYAN}${BOLD}═══ $* ═══${RESET}"; }
ok() { echo -e "${GREEN}${RESET} $*"; }
warn() { echo -e "${YELLOW}${RESET} $*"; }
err() { echo -e "${RED}${RESET} $*" >&2; }
die() { err "$*"; exit 1; }
# ── Parse arguments ─────────────────────────────────────────────────
MODE="full"
case "${1:-}" in
--cli-only) MODE="cli" ;;
--gui-only) MODE="gui" ;;
--appimage) MODE="appimage" ;;
--uninstall) MODE="uninstall" ;;
--help|-h)
sed -n '2,15p' "$0" | sed 's/^# //'
exit 0 ;;
"") MODE="full" ;;
*) die "Unknown option: $1. Use --help for options." ;;
esac
# ── Root check (except for AppImage) ────────────────────────────────
if [ "$MODE" != "appimage" ] && [ "$EUID" -ne 0 ]; then
die "Run with sudo: sudo bash $0 ${1:-}"
fi
# ── Distro detection ────────────────────────────────────────────────
detect_distro() {
if [ -f /etc/os-release ]; then
# Use subshell to prevent /etc/os-release VERSION from clobbering ours
DISTRO=$(. /etc/os-release; echo "${ID:-unknown}")
DISTRO_LIKE=$(. /etc/os-release; echo "${ID_LIKE:-}")
DISTRO_NAME=$(. /etc/os-release; echo "${PRETTY_NAME:-$DISTRO}")
else
DISTRO="unknown"; DISTRO_LIKE=""; DISTRO_NAME="Unknown Linux"
fi
}
detect_distro
# Categorize
DEB_BASED=0; RPM_BASED=0; ARCH_BASED=0; ALPINE=0
case "$DISTRO" in
debian|ubuntu|linuxmint|pop|elementary|kali|raspbian|deepin|zorin) DEB_BASED=1 ;;
fedora|rhel|centos|rocky|almalinux|ol) RPM_BASED=1 ;;
opensuse*|suse|sles) RPM_BASED=1 ;;
arch|manjaro|endeavouros|garuda|artix) ARCH_BASED=1 ;;
alpine) ALPINE=1 ;;
*)
case "$DISTRO_LIKE" in
*debian*|*ubuntu*) DEB_BASED=1 ;;
*fedora*|*rhel*|*suse*) RPM_BASED=1 ;;
*arch*) ARCH_BASED=1 ;;
esac ;;
esac
# ── Self-extract embedded payload ───────────────────────────────────
extract_payload() {
EXTRACT_DIR=$(mktemp -d -t zupt-installer.XXXXXX)
# Find the line number where the payload starts (marker: __PAYLOAD_BELOW__)
local marker_line
marker_line=$(grep -an '^__PAYLOAD_BELOW__$' "$0" | head -1 | cut -d: -f1)
[ -z "$marker_line" ] && die "Installer is corrupt — no payload marker."
# Skip past marker line, decode base64 → tar
tail -n +$((marker_line + 1)) "$0" | base64 -d | tar -xzC "$EXTRACT_DIR"
[ -f "$EXTRACT_DIR/zupt_${VERSION}_amd64.deb" ] || die "Payload extraction failed."
}
# ── Install Qt6 binding (needs network) ─────────────────────────────
install_qt6() {
if python3 -c 'import PyQt6.QtWidgets' 2>/dev/null \
|| python3 -c 'import PySide6.QtWidgets' 2>/dev/null; then
ok "Qt6 binding already installed"
return 0
fi
step "Installing Python 3 + Qt6 binding"
if [ $DEB_BASED -eq 1 ]; then
apt-get update -qq || warn "apt-get update failed (network?); continuing anyway"
apt-get install -y python3 python3-pyqt6 \
|| apt-get install -y python3 python3-pyside6 \
|| warn "Could not install Qt6 binding via apt"
elif [ $RPM_BASED -eq 1 ]; then
case "$DISTRO" in
opensuse*|suse|sles)
zypper --non-interactive install python3 python3-pyqt6 \
|| zypper --non-interactive install python3 python3-PyQt6 \
|| zypper --non-interactive install python3 python3-pyside6 ;;
*)
if command -v dnf >/dev/null; then
dnf install -y python3 python3-pyqt6 \
|| dnf install -y python3 python3-pyside6
else
yum install -y python3 python3-pyqt6 \
|| yum install -y python3 python3-pyside6
fi ;;
esac
elif [ $ARCH_BASED -eq 1 ]; then
pacman -Sy --noconfirm python python-pyqt6 \
|| pacman -Sy --noconfirm python python-pyside6
elif [ $ALPINE -eq 1 ]; then
apk add python3 py3-pyqt6 || apk add python3 py3-pyside6
else
warn "Unknown distribution. Trying pip fallback..."
if command -v pip3 >/dev/null; then
pip3 install --break-system-packages PySide6 2>/dev/null \
|| pip3 install --user PySide6
else
warn "No pip3. Install python3-pyqt6 manually."
fi
fi
if python3 -c 'import PyQt6.QtWidgets' 2>/dev/null \
|| python3 -c 'import PySide6.QtWidgets' 2>/dev/null; then
ok "Qt6 binding installed"
else
warn "Qt6 binding install failed. The CLI will still work; the GUI won't."
fi
}
# ── Install zupt CLI ────────────────────────────────────────────────
install_cli() {
step "Installing zupt CLI ${VERSION}"
if [ $DEB_BASED -eq 1 ]; then
dpkg -i "$EXTRACT_DIR/zupt_${VERSION}_amd64.deb" 2>&1 \
| grep -v '^Selecting\|^Preparing\|^Unpacking\|^Setting up\|^Processing' || true
# Resolve any missing libs from apt
apt-get -f install -y 2>/dev/null || true
ok "zupt CLI installed: $(zupt version 2>&1 | head -1)"
elif [ $RPM_BASED -eq 1 ]; then
local rpmtar="$EXTRACT_DIR/zupt-${VERSION}.srpm.tar.gz"
if command -v rpmbuild >/dev/null; then
local rpmroot=$(mktemp -d)
tar -xzC "$rpmroot" -f "$rpmtar"
rpmbuild --define "_topdir $rpmroot" -bb "$rpmroot/SPECS/zupt.spec"
rpm -Uvh --force "$rpmroot"/RPMS/x86_64/zupt-*.rpm
rm -rf "$rpmroot"
else
# rpmbuild not available — fall back to tarball
warn "rpmbuild missing — using portable binary install"
local appdir="$EXTRACT_DIR/zupt-${VERSION}-x86_64.AppDir.tar.gz"
mkdir -p /opt
tar -xzC /opt -f "$appdir"
ln -sf "/opt/zupt-${VERSION}-x86_64.AppDir/AppRun" /usr/local/bin/zupt
ok "zupt CLI installed (portable mode)"
fi
else
# Universal fallback: portable AppDir tarball
warn "No native package format for $DISTRO. Using portable binary."
mkdir -p /opt /usr/local/bin
tar -xzC /opt -f "$EXTRACT_DIR/zupt-${VERSION}-x86_64.AppDir.tar.gz"
ln -sf "/opt/zupt-${VERSION}-x86_64.AppDir/AppRun" /usr/local/bin/zupt
ok "zupt CLI installed (portable mode)"
fi
}
# ── Install zupt-gui ────────────────────────────────────────────────
install_gui() {
step "Installing zupt-gui ${GUI_VERSION}"
if [ $DEB_BASED -eq 1 ]; then
dpkg -i "$EXTRACT_DIR/zupt-gui_${GUI_VERSION}_all.deb" 2>&1 \
| grep -v '^Selecting\|^Preparing\|^Unpacking\|^Setting up\|^Processing' || true
apt-get -f install -y 2>/dev/null || true
ok "zupt-gui installed"
elif [ $RPM_BASED -eq 1 ]; then
local rpmtar="$EXTRACT_DIR/zupt-gui-${GUI_VERSION}.srpm.tar.gz"
if command -v rpmbuild >/dev/null; then
local rpmroot=$(mktemp -d)
tar -xzC "$rpmroot" -f "$rpmtar"
rpmbuild --define "_topdir $rpmroot" -bb "$rpmroot/SPECS/zupt-gui.spec"
rpm -Uvh --force "$rpmroot"/RPMS/noarch/zupt-gui-*.rpm
rm -rf "$rpmroot"
else
warn "rpmbuild missing — using portable mode"
mkdir -p /opt /usr/local/bin
tar -xzC /opt -f "$EXTRACT_DIR/Zupt-GUI-${GUI_VERSION}-x86_64.AppDir.tar.gz"
ln -sf "/opt/zupt-gui.AppDir/AppRun" /usr/local/bin/zupt-gui
ok "zupt-gui installed (portable)"
fi
else
# Portable
mkdir -p /opt /usr/local/bin
tar -xzC /opt -f "$EXTRACT_DIR/Zupt-GUI-${GUI_VERSION}-x86_64.AppDir.tar.gz"
ln -sf "/opt/zupt-gui.AppDir/AppRun" /usr/local/bin/zupt-gui
# Desktop integration if possible
if [ -d /usr/share/applications ]; then
cp /opt/zupt-gui.AppDir/zupt-gui.desktop /usr/share/applications/ 2>/dev/null || true
fi
ok "zupt-gui installed (portable)"
fi
}
# ── AppImage extract (no install) ───────────────────────────────────
install_appimage() {
step "Extracting AppImage to current directory"
local target="${PWD}/zupt-portable"
mkdir -p "$target"
tar -xzC "$target" -f "$EXTRACT_DIR/zupt-${VERSION}-x86_64.AppDir.tar.gz"
tar -xzC "$target" -f "$EXTRACT_DIR/Zupt-GUI-${GUI_VERSION}-x86_64.AppDir.tar.gz"
cat > "$target/zupt" <<EOF
#!/bin/sh
exec "$target/zupt-${VERSION}-x86_64.AppDir/AppRun" "\$@"
EOF
cat > "$target/zupt-gui" <<EOF
#!/bin/sh
exec "$target/zupt-gui.AppDir/AppRun" "\$@"
EOF
chmod +x "$target/zupt" "$target/zupt-gui"
ok "Portable install at: $target"
echo "Run: $target/zupt help"
echo " $target/zupt-gui"
echo
warn "Portable mode still needs Python 3 + PyQt6 system-wide."
warn "To install Qt6: sudo apt install python3-pyqt6 (or equivalent)"
}
# ── Uninstall ───────────────────────────────────────────────────────
do_uninstall() {
step "Uninstalling zupt + zupt-gui"
if [ $DEB_BASED -eq 1 ]; then
dpkg -r zupt-gui 2>/dev/null || true
dpkg -r zupt 2>/dev/null || true
elif [ $RPM_BASED -eq 1 ]; then
rpm -e zupt-gui 2>/dev/null || true
rpm -e zupt 2>/dev/null || true
fi
rm -rf /opt/zupt-2.2.3-x86_64.AppDir /opt/zupt-gui.AppDir 2>/dev/null
rm -f /usr/local/bin/zupt /usr/local/bin/zupt-gui 2>/dev/null
rm -f /usr/share/applications/zupt-gui.desktop 2>/dev/null
ok "Uninstall complete"
}
# ─────────────────────────────────────────────────────────────────────
# MAIN
# ─────────────────────────────────────────────────────────────────────
cat <<HEADER
${BOLD}${CYAN}
╔══════════════════════════════════════════════════════════════╗
║ ZUPT ${VERSION} + ZUPT-GUI ${GUI_VERSION}
║ Post-quantum backup compression — Linux installer ║
╚══════════════════════════════════════════════════════════════╝
${RESET}
Distribution detected: ${BOLD}${DISTRO_NAME}${RESET}
Mode: ${BOLD}${MODE}${RESET}
HEADER
if [ "$MODE" = "uninstall" ]; then
do_uninstall
exit 0
fi
extract_payload
case "$MODE" in
cli)
install_cli ;;
gui)
install_qt6
install_gui ;;
full)
install_qt6
install_cli
install_gui ;;
appimage)
install_appimage ;;
esac
echo
step "Installation complete"
case "$MODE" in
cli|full) echo " Run: zupt help" ;;
esac
case "$MODE" in
gui|full) echo " Run: zupt-gui (or find 'Zupt GUI' in your applications menu)" ;;
esac
case "$MODE" in
appimage) echo " Run: ./zupt-portable/zupt help" ;;
esac
echo
exit 0

Some files were not shown because too many files have changed in this diff Show more