zupt/AUDIT.md
Cristian Cezar Moisés 544a2cd647
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
v4.0.0: codec 2.60.4 security release, --pq-box sealed-box mode, F-16 fix
Major release. Highlights:

- Codec: vendored VaptVupt codec moves to canonical 2.60.4 security
  release. Fixes a high-severity OOB heap write in the AVX2 decode fast
  path (reachable on a valid stream sized to exactly content_size, both
  tail variants). Brings CBMC-formally-verified BCJ filters with
  automatic ELF/PE/Mach-O detection. Compressed output stays
  byte-identical (ratio gate Δ 0.00%); wire format unchanged at v1.6.
- New --pq-box sealed-box recipient mode (vendored libpqvaptvupt 0.6.0):
  ML-KEM-768 + X25519 combined via HKDF-SHA256 with domain separation,
  AES-256-CTR + HMAC-SHA256 EtM. Legacy --pq and --pq-sdk stay readable.
- F-16: discloses and fixes a pre-existing data-loss defect in the
  <= 3.8.0 in-tree BCJ encoder. Full back-compat matrix decodes
  byte-exact under 4.0.0; every readable pre-4.0 archive remains readable.

Repository hygiene:
- Sync full 4.0.0 source tree (codec, crypto, SDK, GUI, packaging, tests).
- Remove internal scratch files (PROMPT.md, FORMAL_AUDIT_PROMPT.md)
  and superseded version-specific docs (INTEGRATION_PROTOCOL_2.60.4.md,
  docs/FINDINGS-2.x.md) and a stray test binary.
- Refresh README download/install section to real 4.0.0 release assets;
  bump version badge to 4.0.0.
- Add .gitignore for build outputs (keeps vendored prebuilt libraries).
2026-06-10 18:48:58 -03:00

90 KiB
Raw Blame History

Security Audit — VaptVupt v4.0.0

Date: 2026-05-20 Author: Cristian Cezar Moisés Audit type: Self-audit with formal verification (Jasmin CT proofs, ACSL contracts) and NIST/RFC test vectors Status: No independent third-party audit performed

History:

  • v2.0.0 — 2026-03-29 — initial audit baseline.

  • v2.2.4 — 2026-05-19 — five findings (F-01..F-05) closed.

  • v2.2.5 — 2026-05-19 — F-06 (high) and F-07 closed.

  • v2.3.0 — 2026-05-20 — F-08 closed via AIT (format v1.4 → v1.5).

  • v2.3.1 — 2026-05-20 — F-09 closed via preface-AAD MAC (format v1.5 → v1.6). Exhaustive byte sweep on PQ-SDK archive: 0/1827 undetected.

  • v2.4.0 — 2026-05-20 — Methodology release. §3.5 byte-sweep mandate added.

  • v2.4.1 — 2026-05-20 — F-10: KDF default flipped to Argon2id.

  • v2.4.2 — 2026-05-20 — F-11 closed: verbal probe-oracle eliminated.

  • v2.4.3 — 2026-05-20 — F-12: encrypted archive comments. Byte sweep on 1878-byte PQ-SDK+comment archive: 0/1878 undetected.

  • v2.4.4 — 2026-05-20 — Distribution packaging + reproducible make dist.

  • v2.4.5 — 2026-05-20 — Packaging completion: RPM, Nix flake, DISTRIBUTION.md.

  • v2.4.6 — 2026-05-20 — CI rewrite + THREAT_MODEL.md.

  • v2.4.7 — 2026-05-20 — Manpage refresh and shell completions (bash, zsh, fish). Also corrected three stale banner strings that claimed PBKDF2 was the default KDF despite the v2.4.1 flip to Argon2id (user-visible text only; no behavioural change). No source changes that affect security posture; audit posture unchanged from v2.4.3.

  • v2.4.8 — 2026-05-24 — Distro-friendly release. New make check target (curated 10-suite, 91-assertion subset) for OBS / Debian / RPM %check sections. openSUSE OBS files rewritten for cabelo (home:cabelo:innovators/zupt): license corrected MIT → AGPL-3.0-or-later, version bumped 1.5.5 → 2.4.8, changelog history preserved. No source changes; audit posture unchanged from v2.3.1.

  • v3.0.0 — 2026-05-25 — MAJOR: Zupt → VaptVupt INPI Brasil trademark rename. Codec upgrade to VaptVupt LZ + ANS 2.48.5 (two libFuzzer-found fixes: csz==0 OOB-READ in vv_dstream_decompress_chunk; UBSan-safe pointer arithmetic in vv_copy_match). GUI binary-discovery bug fixed with liveness-checking _find_vaptvupt + discovery log. Enhanced manpage (597 lines). Wire format unchanged at v1.6. Bidirectional v2.x ↔ v3.0.0 archive compatibility verified. F-09 byte sweep still 0/1827 silent accepts; F-06 HMAC fuzz still 0/2000. No new findings, no findings reopened. Security posture unchanged. (curated 10-suite, 91-assertion subset) for OBS / Debian / RPM %check sections. openSUSE OBS files rewritten for cabelo (home:cabelo:innovators/zupt): license corrected MIT → AGPL-3.0-or-later, version bumped 1.5.5 → 2.4.8, changelog history preserved. No source changes; audit posture unchanged from v2.3.1.

  • v3.0.1 — 2026-05-26 — GUI license cleanup (removed MIT credit line; gui/LICENSE-GUI rewritten AGPL-3.0-or-later with historical correction note). GUI version-string parsing bug fix (the v3.0.0 replace("zupt ", "") parser matched the wrong substring inside the new rename parenthetical, causing garbled window titles; fixed with anchored _VERSION_RE). New regression test tests/test_gui_branding.sh (11 assertions) wired into make check and make test. No source crypto changes; audit posture unchanged from v3.0.0 (which itself preserved the v2.3.1 baseline). Codec upgrade to VaptVupt LZ + ANS 2.48.5 (two libFuzzer-found fixes: csz==0 OOB-READ in vv_dstream_decompress_chunk; UBSan-safe pointer arithmetic in vv_copy_match). GUI binary-discovery bug fixed with liveness-checking _find_vaptvupt + discovery log. Enhanced manpage (597 lines). Wire format unchanged at v1.6. Bidirectional v2.x ↔ v3.0.0 archive compatibility verified. F-09 byte sweep still 0/1827 silent accepts; F-06 HMAC fuzz still 0/2000. No new findings, no findings reopened. Security posture unchanged. (curated 10-suite, 91-assertion subset) for OBS / Debian / RPM %check sections. openSUSE OBS files rewritten for cabelo (home:cabelo:innovators/zupt): license corrected MIT → AGPL-3.0-or-later, version bumped 1.5.5 → 2.4.8, changelog history preserved. No source changes; audit posture unchanged from v2.3.1.

  • v3.0.2 — 2026-05-26 — F-13 closed: usage() string literal in src/zupt_main.c exceeded C99's 4095-char ISO limit (was 4121). Refactored to 5 fprintf sections; -Woverlength-strings added to default CFLAGS so future regressions fail the build. Help text drift cleanup (stale "zupt" examples → "vaptvupt", stale "LZ77 + Huffman" → "VaptVupt LZ + ANS 2.48.5", license attribution). New regression test tests/test_help_consistency.sh (10 assertions including the F-13 byte-level guard). No source crypto changes; audit posture unchanged from v3.0.1. gui/LICENSE-GUI rewritten AGPL-3.0-or-later with historical correction note). GUI version-string parsing bug fix (the v3.0.0 replace("zupt ", "") parser matched the wrong substring inside the new rename parenthetical, causing garbled window titles; fixed with anchored _VERSION_RE). New regression test tests/test_gui_branding.sh (11 assertions) wired into make check and make test. No source crypto changes; audit posture unchanged from v3.0.0 (which itself preserved the v2.3.1 baseline). Codec upgrade to VaptVupt LZ + ANS 2.48.5 (two libFuzzer-found fixes: csz==0 OOB-READ in vv_dstream_decompress_chunk; UBSan-safe pointer arithmetic in vv_copy_match). GUI binary-discovery bug fixed with liveness-checking _find_vaptvupt + discovery log. Enhanced manpage (597 lines). Wire format unchanged at v1.6. Bidirectional v2.x ↔ v3.0.0 archive compatibility verified. F-09 byte sweep still 0/1827 silent accepts; F-06 HMAC fuzz still 0/2000. No new findings, no findings reopened. Security posture unchanged. (curated 10-suite, 91-assertion subset) for OBS / Debian / RPM %check sections. openSUSE OBS files rewritten for cabelo (home:cabelo:innovators/zupt): license corrected MIT → AGPL-3.0-or-later, version bumped 1.5.5 → 2.4.8, changelog history preserved. No source changes; audit posture unchanged from v2.3.1.

  • v3.0.3 — 2026-05-26 — Static-analysis cleanup. cppcheck knownConditionTrueFalse findings closed in varint decoders (dead && (x&0x80) AND-branch after preceding terminator-byte early-return). -Wconversion / -Wsign-conversion findings closed with explicit casts at two sites. Our 9-file non-vendored C now compiles clean under the union of strict GCC warnings including -Wconversion -Wsign-conversion -Werror. New regression test tests/test_static_analysis.sh (7 assertions) wired into make check and make test. Behaviour byte-identical; F-09 byte sweep still 0/1827 silent accepts. No source crypto changes. src/zupt_main.c exceeded C99's 4095-char ISO limit (was 4121). Refactored to 5 fprintf sections; -Woverlength-strings added to default CFLAGS so future regressions fail the build. Help text drift cleanup (stale "zupt" examples → "vaptvupt", stale "LZ77 + Huffman" → "VaptVupt LZ + ANS 2.48.5", license attribution). New regression test tests/test_help_consistency.sh (10 assertions including the F-13 byte-level guard). No source crypto changes; audit posture unchanged from v3.0.1. gui/LICENSE-GUI rewritten AGPL-3.0-or-later with historical correction note). GUI version-string parsing bug fix (the v3.0.0 replace("zupt ", "") parser matched the wrong substring inside the new rename parenthetical, causing garbled window titles; fixed with anchored _VERSION_RE). New regression test tests/test_gui_branding.sh (11 assertions) wired into make check and make test. No source crypto changes; audit posture unchanged from v3.0.0 (which itself preserved the v2.3.1 baseline). Codec upgrade to VaptVupt LZ + ANS 2.48.5 (two libFuzzer-found fixes: csz==0 OOB-READ in vv_dstream_decompress_chunk; UBSan-safe pointer arithmetic in vv_copy_match). GUI binary-discovery bug fixed with liveness-checking _find_vaptvupt + discovery log. Enhanced manpage (597 lines). Wire format unchanged at v1.6. Bidirectional v2.x ↔ v3.0.0 archive compatibility verified. F-09 byte sweep still 0/1827 silent accepts; F-06 HMAC fuzz still 0/2000. No new findings, no findings reopened. Security posture unchanged. (curated 10-suite, 91-assertion subset) for OBS / Debian / RPM %check sections. openSUSE OBS files rewritten for cabelo (home:cabelo:innovators/zupt): license corrected MIT → AGPL-3.0-or-later, version bumped 1.5.5 → 2.4.8, changelog history preserved. No source changes; audit posture unchanged from v2.3.1.

  • v3.1.0 — 2026-05-31 — VaptVupt codec 2.48.5 -> 2.53.3 (API byte-identical; 3 .c files changed). Inherits 6 upstream corrupt-input decoder memory-safety fixes (v2.52.4 + v2.53.2). F-14 closed: ASAN found a heap-buffer-overflow WRITE of size 32 in OUR decode wrapper — decode buffers were malloc(uncompressed _size) with no slack, but the codec AVX2 over-copy needs >=32 B slack (documented contract in vaptvupt.h). Old codec never reached it; new wider AVX2 hot path does. Fixed with shared ZUPT_VV_DECODE_SLACK (64 B) guard on both single-threaded (zupt_format.c) and parallel (zupt_parallel.c) decode paths. Verified: ASAN 24/24 single-threaded + 15/15 multi-threaded; bit-flip fuzz 300 trials 0 crashes / 300 clean rejects. F-09 byte sweep still 0/1827. vv_decoder.c scalar build made -Werror clean for aarch64/Termux. New test tests/test_vv_decode _slack.sh. Wire format unchanged (v1.6); 3.0.3 archives extract byte-exact. knownConditionTrueFalse findings closed in varint decoders (dead && (x&0x80) AND-branch after preceding terminator-byte early-return). -Wconversion / -Wsign-conversion findings closed with explicit casts at two sites. Our 9-file non-vendored C now compiles clean under the union of strict GCC warnings including -Wconversion -Wsign-conversion -Werror. New regression test tests/test_static_analysis.sh (7 assertions) wired into make check and make test. Behaviour byte-identical; F-09 byte sweep still 0/1827 silent accepts. No source crypto changes. src/zupt_main.c exceeded C99's 4095-char ISO limit (was 4121). Refactored to 5 fprintf sections; -Woverlength-strings added to default CFLAGS so future regressions fail the build. Help text drift cleanup (stale "zupt" examples → "vaptvupt", stale "LZ77 + Huffman" → "VaptVupt LZ + ANS 2.48.5", license attribution). New regression test tests/test_help_consistency.sh (10 assertions including the F-13 byte-level guard). No source crypto changes; audit posture unchanged from v3.0.1. gui/LICENSE-GUI rewritten AGPL-3.0-or-later with historical correction note). GUI version-string parsing bug fix (the v3.0.0 replace("zupt ", "") parser matched the wrong substring inside the new rename parenthetical, causing garbled window titles; fixed with anchored _VERSION_RE). New regression test tests/test_gui_branding.sh (11 assertions) wired into make check and make test. No source crypto changes; audit posture unchanged from v3.0.0 (which itself preserved the v2.3.1 baseline). Codec upgrade to VaptVupt LZ + ANS 2.48.5 (two libFuzzer-found fixes: csz==0 OOB-READ in vv_dstream_decompress_chunk; UBSan-safe pointer arithmetic in vv_copy_match). GUI binary-discovery bug fixed with liveness-checking _find_vaptvupt + discovery log. Enhanced manpage (597 lines). Wire format unchanged at v1.6. Bidirectional v2.x ↔ v3.0.0 archive compatibility verified. F-09 byte sweep still 0/1827 silent accepts; F-06 HMAC fuzz still 0/2000. No new findings, no findings reopened. Security posture unchanged. (curated 10-suite, 91-assertion subset) for OBS / Debian / RPM %check sections. openSUSE OBS files rewritten for cabelo (home:cabelo:innovators/zupt): license corrected MIT → AGPL-3.0-or-later, version bumped 1.5.5 → 2.4.8, changelog history preserved. No source changes; audit posture unchanged from v2.3.1.

  • v3.2.0 — 2026-06-01 — SHA-256 hardware acceleration (Intel SHA-NI). New SHA256RNDS2/MSG1/MSG2 compression path with CPUID runtime dispatch (has_shani, CPUID.07H:EBX[29]). Accelerates the Encrypt-then-MAC second pass (HMAC-SHA256) and PBKDF2. Security-relevant property: SHA-NI is constant-time by construction (no data-dependent memory access or branches), strengthening the side-channel posture of HMAC verification over attacker-influenced ciphertext vs the scalar software path. No cryptographic-correctness change: same SHA-256, same HMAC, same Encrypt-then-MAC, same wire bytes (format v1.6 unchanged). NIST FIPS 180-4 vectors pass on both paths; the 64 SHA-NI round constants are verified bit-identical to the scalar K[] table. Speedup ([ESTIMATED] 3-8x) NOT measured in this release — the build host lacks SHA-NI; figure to be confirmed on SHA-NI hardware. Scalar fallback unchanged and remains the path on non-SHA-NI CPUs (incl. aarch64). F-09 byte sweep still 0/1827. byte-identical; 3 .c files changed). Inherits 6 upstream corrupt-input decoder memory-safety fixes (v2.52.4 + v2.53.2). F-14 closed: ASAN found a heap-buffer-overflow WRITE of size 32 in OUR decode wrapper — decode buffers were malloc(uncompressed _size) with no slack, but the codec AVX2 over-copy needs >=32 B slack (documented contract in vaptvupt.h). Old codec never reached it; new wider AVX2 hot path does. Fixed with shared ZUPT_VV_DECODE_SLACK (64 B) guard on both single-threaded (zupt_format.c) and parallel (zupt_parallel.c) decode paths. Verified: ASAN 24/24 single-threaded + 15/15 multi-threaded; bit-flip fuzz 300 trials 0 crashes / 300 clean rejects. F-09 byte sweep still 0/1827. vv_decoder.c scalar build made -Werror clean for aarch64/Termux. New test tests/test_vv_decode _slack.sh. Wire format unchanged (v1.6); 3.0.3 archives extract byte-exact. knownConditionTrueFalse findings closed in varint decoders (dead && (x&0x80) AND-branch after preceding terminator-byte early-return). -Wconversion / -Wsign-conversion findings closed with explicit casts at two sites. Our 9-file non-vendored C now compiles clean under the union of strict GCC warnings including -Wconversion -Wsign-conversion -Werror. New regression test tests/test_static_analysis.sh (7 assertions) wired into make check and make test. Behaviour byte-identical; F-09 byte sweep still 0/1827 silent accepts. No source crypto changes. src/zupt_main.c exceeded C99's 4095-char ISO limit (was 4121). Refactored to 5 fprintf sections; -Woverlength-strings added to default CFLAGS so future regressions fail the build. Help text drift cleanup (stale "zupt" examples → "vaptvupt", stale "LZ77 + Huffman" → "VaptVupt LZ + ANS 2.48.5", license attribution). New regression test tests/test_help_consistency.sh (10 assertions including the F-13 byte-level guard). No source crypto changes; audit posture unchanged from v3.0.1. gui/LICENSE-GUI rewritten AGPL-3.0-or-later with historical correction note). GUI version-string parsing bug fix (the v3.0.0 replace("zupt ", "") parser matched the wrong substring inside the new rename parenthetical, causing garbled window titles; fixed with anchored _VERSION_RE). New regression test tests/test_gui_branding.sh (11 assertions) wired into make check and make test. No source crypto changes; audit posture unchanged from v3.0.0 (which itself preserved the v2.3.1 baseline). Codec upgrade to VaptVupt LZ + ANS 2.48.5 (two libFuzzer-found fixes: csz==0 OOB-READ in vv_dstream_decompress_chunk; UBSan-safe pointer arithmetic in vv_copy_match). GUI binary-discovery bug fixed with liveness-checking _find_vaptvupt + discovery log. Enhanced manpage (597 lines). Wire format unchanged at v1.6. Bidirectional v2.x ↔ v3.0.0 archive compatibility verified. F-09 byte sweep still 0/1827 silent accepts; F-06 HMAC fuzz still 0/2000. No new findings, no findings reopened. Security posture unchanged. (curated 10-suite, 91-assertion subset) for OBS / Debian / RPM %check sections. openSUSE OBS files rewritten for cabelo (home:cabelo:innovators/zupt): license corrected MIT → AGPL-3.0-or-later, version bumped 1.5.5 → 2.4.8, changelog history preserved. No source changes; audit posture unchanged from v2.3.1.

  • v3.3.0 — 2026-06-01 — Incremental HMAC-SHA256 for the per-block Encrypt-then-MAC hot path. ipad/opad key prefix folded once per keyring; MAC streamed (aad || nonce || ciphertext || seq) instead of concatenated into a malloc'd buffer. SECURITY-RELEVANT: identical authentication semantics — the MAC is byte-for-byte the same (RFC 2104 + SHA-256 Merkle-Damgard associativity), proven by RFC 4231 vectors, a new equivalence test, and byte-exact decryption of 3.2.x archives. Constant-time tag compares unchanged (byte-OR accumulator / Jasmin zupt_mac_verify_ct). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. Reduces secret-data heap footprint: the old path copied the full ciphertext into a second malloc'd buffer per block — now removed. No cryptographic- correctness change, no wire-format change (v1.6). ASan clean on both KDF paths. SHA-NI). New SHA256RNDS2/MSG1/MSG2 compression path with CPUID runtime dispatch (has_shani, CPUID.07H:EBX[29]). Accelerates the Encrypt-then-MAC second pass (HMAC-SHA256) and PBKDF2. Security-relevant property: SHA-NI is constant-time by construction (no data-dependent memory access or branches), strengthening the side-channel posture of HMAC verification over attacker-influenced ciphertext vs the scalar software path. No cryptographic-correctness change: same SHA-256, same HMAC, same Encrypt-then-MAC, same wire bytes (format v1.6 unchanged). NIST FIPS 180-4 vectors pass on both paths; the 64 SHA-NI round constants are verified bit-identical to the scalar K[] table. Speedup ([ESTIMATED] 3-8x) NOT measured in this release — the build host lacks SHA-NI; figure to be confirmed on SHA-NI hardware. Scalar fallback unchanged and remains the path on non-SHA-NI CPUs (incl. aarch64). F-09 byte sweep still 0/1827. byte-identical; 3 .c files changed). Inherits 6 upstream corrupt-input decoder memory-safety fixes (v2.52.4 + v2.53.2). F-14 closed: ASAN found a heap-buffer-overflow WRITE of size 32 in OUR decode wrapper — decode buffers were malloc(uncompressed _size) with no slack, but the codec AVX2 over-copy needs >=32 B slack (documented contract in vaptvupt.h). Old codec never reached it; new wider AVX2 hot path does. Fixed with shared ZUPT_VV_DECODE_SLACK (64 B) guard on both single-threaded (zupt_format.c) and parallel (zupt_parallel.c) decode paths. Verified: ASAN 24/24 single-threaded + 15/15 multi-threaded; bit-flip fuzz 300 trials 0 crashes / 300 clean rejects. F-09 byte sweep still 0/1827. vv_decoder.c scalar build made -Werror clean for aarch64/Termux. New test tests/test_vv_decode _slack.sh. Wire format unchanged (v1.6); 3.0.3 archives extract byte-exact. knownConditionTrueFalse findings closed in varint decoders (dead && (x&0x80) AND-branch after preceding terminator-byte early-return). -Wconversion / -Wsign-conversion findings closed with explicit casts at two sites. Our 9-file non-vendored C now compiles clean under the union of strict GCC warnings including -Wconversion -Wsign-conversion -Werror. New regression test tests/test_static_analysis.sh (7 assertions) wired into make check and make test. Behaviour byte-identical; F-09 byte sweep still 0/1827 silent accepts. No source crypto changes. src/zupt_main.c exceeded C99's 4095-char ISO limit (was 4121). Refactored to 5 fprintf sections; -Woverlength-strings added to default CFLAGS so future regressions fail the build. Help text drift cleanup (stale "zupt" examples → "vaptvupt", stale "LZ77 + Huffman" → "VaptVupt LZ + ANS 2.48.5", license attribution). New regression test tests/test_help_consistency.sh (10 assertions including the F-13 byte-level guard). No source crypto changes; audit posture unchanged from v3.0.1. gui/LICENSE-GUI rewritten AGPL-3.0-or-later with historical correction note). GUI version-string parsing bug fix (the v3.0.0 replace("zupt ", "") parser matched the wrong substring inside the new rename parenthetical, causing garbled window titles; fixed with anchored _VERSION_RE). New regression test tests/test_gui_branding.sh (11 assertions) wired into make check and make test. No source crypto changes; audit posture unchanged from v3.0.0 (which itself preserved the v2.3.1 baseline). Codec upgrade to VaptVupt LZ + ANS 2.48.5 (two libFuzzer-found fixes: csz==0 OOB-READ in vv_dstream_decompress_chunk; UBSan-safe pointer arithmetic in vv_copy_match). GUI binary-discovery bug fixed with liveness-checking _find_vaptvupt + discovery log. Enhanced manpage (597 lines). Wire format unchanged at v1.6. Bidirectional v2.x ↔ v3.0.0 archive compatibility verified. F-09 byte sweep still 0/1827 silent accepts; F-06 HMAC fuzz still 0/2000. No new findings, no findings reopened. Security posture unchanged. (curated 10-suite, 91-assertion subset) for OBS / Debian / RPM %check sections. openSUSE OBS files rewritten for cabelo (home:cabelo:innovators/zupt): license corrected MIT → AGPL-3.0-or-later, version bumped 1.5.5 → 2.4.8, changelog history preserved. No source changes; audit posture unchanged from v2.3.1.

  • v3.4.0 — 2026-06-01 — F-15: Argon2id KDF parameter transparency. The 0x04 Argon2id enc-header recorded only [type|salt|nonce] and nothing about the KDF cost (the PBKDF2 header records its iteration count). New archives append a one-byte KDF profile descriptor at offset 33 (ZUPT_ARGON2_PROFILE_MODERATE), making the header self- describing so a reader always knows which Argon2id cost produced the archive — eliminating a silent-undecryptability risk if the preset ever changes. The descriptor is covered by the F-08 archive- integrity trailer (tamper-evident; a flipped byte fails decryption, verified). Additive + back-compatible: legacy 33-byte archives decrypt byte-exact; 33B and 34B headers derive identical keys; unknown profiles are refused fail-closed (no wrong-key guessing). Build-time SDK-drift guard: the F-15 test asserts the KDF is deterministic and meets a coarse memory-hard cost floor (>=20 ms), failing the build if libzuptsdk is swapped for a weak/stub Argon2id. No cryptographic-correctness change, no wire-format change (v1.6). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. Note: the explicit RFC 9106 zsdk_argon2id() is header-declared but NOT exported by the vendored libzuptsdk.so, so the cost is recorded in- band rather than re-parameterised; revisit if the SDK exports it. Encrypt-then-MAC hot path. ipad/opad key prefix folded once per keyring; MAC streamed (aad || nonce || ciphertext || seq) instead of concatenated into a malloc'd buffer. SECURITY-RELEVANT: identical authentication semantics — the MAC is byte-for-byte the same (RFC 2104 + SHA-256 Merkle-Damgard associativity), proven by RFC 4231 vectors, a new equivalence test, and byte-exact decryption of 3.2.x archives. Constant-time tag compares unchanged (byte-OR accumulator / Jasmin zupt_mac_verify_ct). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. Reduces secret-data heap footprint: the old path copied the full ciphertext into a second malloc'd buffer per block — now removed. No cryptographic- correctness change, no wire-format change (v1.6). ASan clean on both KDF paths. SHA-NI). New SHA256RNDS2/MSG1/MSG2 compression path with CPUID runtime dispatch (has_shani, CPUID.07H:EBX[29]). Accelerates the Encrypt-then-MAC second pass (HMAC-SHA256) and PBKDF2. Security-relevant property: SHA-NI is constant-time by construction (no data-dependent memory access or branches), strengthening the side-channel posture of HMAC verification over attacker-influenced ciphertext vs the scalar software path. No cryptographic-correctness change: same SHA-256, same HMAC, same Encrypt-then-MAC, same wire bytes (format v1.6 unchanged). NIST FIPS 180-4 vectors pass on both paths; the 64 SHA-NI round constants are verified bit-identical to the scalar K[] table. Speedup ([ESTIMATED] 3-8x) NOT measured in this release — the build host lacks SHA-NI; figure to be confirmed on SHA-NI hardware. Scalar fallback unchanged and remains the path on non-SHA-NI CPUs (incl. aarch64). F-09 byte sweep still 0/1827. byte-identical; 3 .c files changed). Inherits 6 upstream corrupt-input decoder memory-safety fixes (v2.52.4 + v2.53.2). F-14 closed: ASAN found a heap-buffer-overflow WRITE of size 32 in OUR decode wrapper — decode buffers were malloc(uncompressed _size) with no slack, but the codec AVX2 over-copy needs >=32 B slack (documented contract in vaptvupt.h). Old codec never reached it; new wider AVX2 hot path does. Fixed with shared ZUPT_VV_DECODE_SLACK (64 B) guard on both single-threaded (zupt_format.c) and parallel (zupt_parallel.c) decode paths. Verified: ASAN 24/24 single-threaded + 15/15 multi-threaded; bit-flip fuzz 300 trials 0 crashes / 300 clean rejects. F-09 byte sweep still 0/1827. vv_decoder.c scalar build made -Werror clean for aarch64/Termux. New test tests/test_vv_decode _slack.sh. Wire format unchanged (v1.6); 3.0.3 archives extract byte-exact. knownConditionTrueFalse findings closed in varint decoders (dead && (x&0x80) AND-branch after preceding terminator-byte early-return). -Wconversion / -Wsign-conversion findings closed with explicit casts at two sites. Our 9-file non-vendored C now compiles clean under the union of strict GCC warnings including -Wconversion -Wsign-conversion -Werror. New regression test tests/test_static_analysis.sh (7 assertions) wired into make check and make test. Behaviour byte-identical; F-09 byte sweep still 0/1827 silent accepts. No source crypto changes. src/zupt_main.c exceeded C99's 4095-char ISO limit (was 4121). Refactored to 5 fprintf sections; -Woverlength-strings added to default CFLAGS so future regressions fail the build. Help text drift cleanup (stale "zupt" examples → "vaptvupt", stale "LZ77 + Huffman" → "VaptVupt LZ + ANS 2.48.5", license attribution). New regression test tests/test_help_consistency.sh (10 assertions including the F-13 byte-level guard). No source crypto changes; audit posture unchanged from v3.0.1. gui/LICENSE-GUI rewritten AGPL-3.0-or-later with historical correction note). GUI version-string parsing bug fix (the v3.0.0 replace("zupt ", "") parser matched the wrong substring inside the new rename parenthetical, causing garbled window titles; fixed with anchored _VERSION_RE). New regression test tests/test_gui_branding.sh (11 assertions) wired into make check and make test. No source crypto changes; audit posture unchanged from v3.0.0 (which itself preserved the v2.3.1 baseline). Codec upgrade to VaptVupt LZ + ANS 2.48.5 (two libFuzzer-found fixes: csz==0 OOB-READ in vv_dstream_decompress_chunk; UBSan-safe pointer arithmetic in vv_copy_match). GUI binary-discovery bug fixed with liveness-checking _find_vaptvupt + discovery log. Enhanced manpage (597 lines). Wire format unchanged at v1.6. Bidirectional v2.x ↔ v3.0.0 archive compatibility verified. F-09 byte sweep still 0/1827 silent accepts; F-06 HMAC fuzz still 0/2000. No new findings, no findings reopened. Security posture unchanged. (curated 10-suite, 91-assertion subset) for OBS / Debian / RPM %check sections. openSUSE OBS files rewritten for cabelo (home:cabelo:innovators/zupt): license corrected MIT → AGPL-3.0-or-later, version bumped 1.5.5 → 2.4.8, changelog history preserved. No source changes; audit posture unchanged from v2.3.1.

  • v3.5.0 — 2026-06-01 — Measured constant-time MAC comparison (dudect-style). The MAC tag compare — the most timing-sensitive operation, where a leak is a forgery oracle — was carried as three duplicated inline byte-OR loops marked CT-REQUIRED but never measured. Consolidated into one audited primitive zupt_ct_memeq (volatile OR-accumulate, no early exit, branch-free fold), used by the v1.6 strict decrypt path and the F-08 archive-integrity-trailer check. New dudect-style timing test (tests/test_ct_timing.c): Welch t-test over fixed-equal vs random-differing tag classes, built at -O2 so it exercises the shipped code (incl. that the volatile sink survives the optimiser). Verdict is environment- relative: a leaky-memcmp positive control must show a clear leak in the same environment, and zupt_ct_memeq must show <=20% of that signal (measured ~1%; median of 5 runs; INCONCLUSIVE rather than vacuous-pass if the host is too coarse). A reintroduced early- return/branch pushes the ratio toward 1.0 and fails. This turns an asserted CT property into a measured one + CI regression guard. The formally-verified Jasmin zupt_mac_verify_ct path (v1.4/v1.5 legacy compare) and the F-06 two-candidate fold are unchanged. No cryptographic-correctness change, no wire-format change (v1.6). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. The 0x04 Argon2id enc-header recorded only [type|salt|nonce] and nothing about the KDF cost (the PBKDF2 header records its iteration count). New archives append a one-byte KDF profile descriptor at offset 33 (ZUPT_ARGON2_PROFILE_MODERATE), making the header self- describing so a reader always knows which Argon2id cost produced the archive — eliminating a silent-undecryptability risk if the preset ever changes. The descriptor is covered by the F-08 archive- integrity trailer (tamper-evident; a flipped byte fails decryption, verified). Additive + back-compatible: legacy 33-byte archives decrypt byte-exact; 33B and 34B headers derive identical keys; unknown profiles are refused fail-closed (no wrong-key guessing). Build-time SDK-drift guard: the F-15 test asserts the KDF is deterministic and meets a coarse memory-hard cost floor (>=20 ms), failing the build if libzuptsdk is swapped for a weak/stub Argon2id. No cryptographic-correctness change, no wire-format change (v1.6). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. Note: the explicit RFC 9106 zsdk_argon2id() is header-declared but NOT exported by the vendored libzuptsdk.so, so the cost is recorded in- band rather than re-parameterised; revisit if the SDK exports it. Encrypt-then-MAC hot path. ipad/opad key prefix folded once per keyring; MAC streamed (aad || nonce || ciphertext || seq) instead of concatenated into a malloc'd buffer. SECURITY-RELEVANT: identical authentication semantics — the MAC is byte-for-byte the same (RFC 2104 + SHA-256 Merkle-Damgard associativity), proven by RFC 4231 vectors, a new equivalence test, and byte-exact decryption of 3.2.x archives. Constant-time tag compares unchanged (byte-OR accumulator / Jasmin zupt_mac_verify_ct). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. Reduces secret-data heap footprint: the old path copied the full ciphertext into a second malloc'd buffer per block — now removed. No cryptographic- correctness change, no wire-format change (v1.6). ASan clean on both KDF paths. SHA-NI). New SHA256RNDS2/MSG1/MSG2 compression path with CPUID runtime dispatch (has_shani, CPUID.07H:EBX[29]). Accelerates the Encrypt-then-MAC second pass (HMAC-SHA256) and PBKDF2. Security-relevant property: SHA-NI is constant-time by construction (no data-dependent memory access or branches), strengthening the side-channel posture of HMAC verification over attacker-influenced ciphertext vs the scalar software path. No cryptographic-correctness change: same SHA-256, same HMAC, same Encrypt-then-MAC, same wire bytes (format v1.6 unchanged). NIST FIPS 180-4 vectors pass on both paths; the 64 SHA-NI round constants are verified bit-identical to the scalar K[] table. Speedup ([ESTIMATED] 3-8x) NOT measured in this release — the build host lacks SHA-NI; figure to be confirmed on SHA-NI hardware. Scalar fallback unchanged and remains the path on non-SHA-NI CPUs (incl. aarch64). F-09 byte sweep still 0/1827. byte-identical; 3 .c files changed). Inherits 6 upstream corrupt-input decoder memory-safety fixes (v2.52.4 + v2.53.2). F-14 closed: ASAN found a heap-buffer-overflow WRITE of size 32 in OUR decode wrapper — decode buffers were malloc(uncompressed _size) with no slack, but the codec AVX2 over-copy needs >=32 B slack (documented contract in vaptvupt.h). Old codec never reached it; new wider AVX2 hot path does. Fixed with shared ZUPT_VV_DECODE_SLACK (64 B) guard on both single-threaded (zupt_format.c) and parallel (zupt_parallel.c) decode paths. Verified: ASAN 24/24 single-threaded + 15/15 multi-threaded; bit-flip fuzz 300 trials 0 crashes / 300 clean rejects. F-09 byte sweep still 0/1827. vv_decoder.c scalar build made -Werror clean for aarch64/Termux. New test tests/test_vv_decode _slack.sh. Wire format unchanged (v1.6); 3.0.3 archives extract byte-exact. knownConditionTrueFalse findings closed in varint decoders (dead && (x&0x80) AND-branch after preceding terminator-byte early-return). -Wconversion / -Wsign-conversion findings closed with explicit casts at two sites. Our 9-file non-vendored C now compiles clean under the union of strict GCC warnings including -Wconversion -Wsign-conversion -Werror. New regression test tests/test_static_analysis.sh (7 assertions) wired into make check and make test. Behaviour byte-identical; F-09 byte sweep still 0/1827 silent accepts. No source crypto changes. src/zupt_main.c exceeded C99's 4095-char ISO limit (was 4121). Refactored to 5 fprintf sections; -Woverlength-strings added to default CFLAGS so future regressions fail the build. Help text drift cleanup (stale "zupt" examples → "vaptvupt", stale "LZ77 + Huffman" → "VaptVupt LZ + ANS 2.48.5", license attribution). New regression test tests/test_help_consistency.sh (10 assertions including the F-13 byte-level guard). No source crypto changes; audit posture unchanged from v3.0.1. gui/LICENSE-GUI rewritten AGPL-3.0-or-later with historical correction note). GUI version-string parsing bug fix (the v3.0.0 replace("zupt ", "") parser matched the wrong substring inside the new rename parenthetical, causing garbled window titles; fixed with anchored _VERSION_RE). New regression test tests/test_gui_branding.sh (11 assertions) wired into make check and make test. No source crypto changes; audit posture unchanged from v3.0.0 (which itself preserved the v2.3.1 baseline). Codec upgrade to VaptVupt LZ + ANS 2.48.5 (two libFuzzer-found fixes: csz==0 OOB-READ in vv_dstream_decompress_chunk; UBSan-safe pointer arithmetic in vv_copy_match). GUI binary-discovery bug fixed with liveness-checking _find_vaptvupt + discovery log. Enhanced manpage (597 lines). Wire format unchanged at v1.6. Bidirectional v2.x ↔ v3.0.0 archive compatibility verified. F-09 byte sweep still 0/1827 silent accepts; F-06 HMAC fuzz still 0/2000. No new findings, no findings reopened. Security posture unchanged. (curated 10-suite, 91-assertion subset) for OBS / Debian / RPM %check sections. openSUSE OBS files rewritten for cabelo (home:cabelo:innovators/zupt): license corrected MIT → AGPL-3.0-or-later, version bumped 1.5.5 → 2.4.8, changelog history preserved. No source changes; audit posture unchanged from v2.3.1.

  • v3.6.0 — 2026-06-01 — NIST SP 800-38A AES-256-CTR known-answer vectors + ML-KEM self-test fixes. AES-256-CTR — the bulk cipher — previously had only indirect roundtrip coverage; added the canonical SP 800-38A F.5.5 (encrypt) and F.5.6 (decrypt) vectors, validating zupt_aes256_ctr against the standard on both the Jasmin AES-NI path (zupt_aes256_ctr4 + zupt_aes256_blk) and the C T-table fallback. Both match exactly, confirming the Jasmin single-block AES is correct vs the standard (retires the stale stack-offset concern for zupt_aes256_blk). Also fixed two ML-KEM-768 self-test bugs: (1) an inverted result check in test_vectors that printed OK when the self-test returned failure — it had been passing vacuously; (2) the NTT roundtrip self-test asserted a false ntt∘inv_ntt == identity (this pqcrystals/Kyber Montgomery convention recovers each coefficient scaled by R^-1 mod q = 169), now rewritten to assert the true consistent-linear-scaling invariant, which still catches genuine NTT bugs and no longer emits a misleading stderr "NTT roundtrip FAILED". ML-KEM correctness end-to-end was never affected — the K-PKE, KEM, and FIPS 203 roundtrip vectors and implicit-rejection all pass. No source-crypto behaviour change, no wire-format change (v1.6). test_vectors now 16 passed / 0 failed (was 14, one vacuous). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. (dudect-style). The MAC tag compare — the most timing-sensitive operation, where a leak is a forgery oracle — was carried as three duplicated inline byte-OR loops marked CT-REQUIRED but never measured. Consolidated into one audited primitive zupt_ct_memeq (volatile OR-accumulate, no early exit, branch-free fold), used by the v1.6 strict decrypt path and the F-08 archive-integrity-trailer check. New dudect-style timing test (tests/test_ct_timing.c): Welch t-test over fixed-equal vs random-differing tag classes, built at -O2 so it exercises the shipped code (incl. that the volatile sink survives the optimiser). Verdict is environment- relative: a leaky-memcmp positive control must show a clear leak in the same environment, and zupt_ct_memeq must show <=20% of that signal (measured ~1%; median of 5 runs; INCONCLUSIVE rather than vacuous-pass if the host is too coarse). A reintroduced early- return/branch pushes the ratio toward 1.0 and fails. This turns an asserted CT property into a measured one + CI regression guard. The formally-verified Jasmin zupt_mac_verify_ct path (v1.4/v1.5 legacy compare) and the F-06 two-candidate fold are unchanged. No cryptographic-correctness change, no wire-format change (v1.6). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. The 0x04 Argon2id enc-header recorded only [type|salt|nonce] and nothing about the KDF cost (the PBKDF2 header records its iteration count). New archives append a one-byte KDF profile descriptor at offset 33 (ZUPT_ARGON2_PROFILE_MODERATE), making the header self- describing so a reader always knows which Argon2id cost produced the archive — eliminating a silent-undecryptability risk if the preset ever changes. The descriptor is covered by the F-08 archive- integrity trailer (tamper-evident; a flipped byte fails decryption, verified). Additive + back-compatible: legacy 33-byte archives decrypt byte-exact; 33B and 34B headers derive identical keys; unknown profiles are refused fail-closed (no wrong-key guessing). Build-time SDK-drift guard: the F-15 test asserts the KDF is deterministic and meets a coarse memory-hard cost floor (>=20 ms), failing the build if libzuptsdk is swapped for a weak/stub Argon2id. No cryptographic-correctness change, no wire-format change (v1.6). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. Note: the explicit RFC 9106 zsdk_argon2id() is header-declared but NOT exported by the vendored libzuptsdk.so, so the cost is recorded in- band rather than re-parameterised; revisit if the SDK exports it. Encrypt-then-MAC hot path. ipad/opad key prefix folded once per keyring; MAC streamed (aad || nonce || ciphertext || seq) instead of concatenated into a malloc'd buffer. SECURITY-RELEVANT: identical authentication semantics — the MAC is byte-for-byte the same (RFC 2104 + SHA-256 Merkle-Damgard associativity), proven by RFC 4231 vectors, a new equivalence test, and byte-exact decryption of 3.2.x archives. Constant-time tag compares unchanged (byte-OR accumulator / Jasmin zupt_mac_verify_ct). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. Reduces secret-data heap footprint: the old path copied the full ciphertext into a second malloc'd buffer per block — now removed. No cryptographic- correctness change, no wire-format change (v1.6). ASan clean on both KDF paths. SHA-NI). New SHA256RNDS2/MSG1/MSG2 compression path with CPUID runtime dispatch (has_shani, CPUID.07H:EBX[29]). Accelerates the Encrypt-then-MAC second pass (HMAC-SHA256) and PBKDF2. Security-relevant property: SHA-NI is constant-time by construction (no data-dependent memory access or branches), strengthening the side-channel posture of HMAC verification over attacker-influenced ciphertext vs the scalar software path. No cryptographic-correctness change: same SHA-256, same HMAC, same Encrypt-then-MAC, same wire bytes (format v1.6 unchanged). NIST FIPS 180-4 vectors pass on both paths; the 64 SHA-NI round constants are verified bit-identical to the scalar K[] table. Speedup ([ESTIMATED] 3-8x) NOT measured in this release — the build host lacks SHA-NI; figure to be confirmed on SHA-NI hardware. Scalar fallback unchanged and remains the path on non-SHA-NI CPUs (incl. aarch64). F-09 byte sweep still 0/1827. byte-identical; 3 .c files changed). Inherits 6 upstream corrupt-input decoder memory-safety fixes (v2.52.4 + v2.53.2). F-14 closed: ASAN found a heap-buffer-overflow WRITE of size 32 in OUR decode wrapper — decode buffers were malloc(uncompressed _size) with no slack, but the codec AVX2 over-copy needs >=32 B slack (documented contract in vaptvupt.h). Old codec never reached it; new wider AVX2 hot path does. Fixed with shared ZUPT_VV_DECODE_SLACK (64 B) guard on both single-threaded (zupt_format.c) and parallel (zupt_parallel.c) decode paths. Verified: ASAN 24/24 single-threaded + 15/15 multi-threaded; bit-flip fuzz 300 trials 0 crashes / 300 clean rejects. F-09 byte sweep still 0/1827. vv_decoder.c scalar build made -Werror clean for aarch64/Termux. New test tests/test_vv_decode _slack.sh. Wire format unchanged (v1.6); 3.0.3 archives extract byte-exact. knownConditionTrueFalse findings closed in varint decoders (dead && (x&0x80) AND-branch after preceding terminator-byte early-return). -Wconversion / -Wsign-conversion findings closed with explicit casts at two sites. Our 9-file non-vendored C now compiles clean under the union of strict GCC warnings including -Wconversion -Wsign-conversion -Werror. New regression test tests/test_static_analysis.sh (7 assertions) wired into make check and make test. Behaviour byte-identical; F-09 byte sweep still 0/1827 silent accepts. No source crypto changes. src/zupt_main.c exceeded C99's 4095-char ISO limit (was 4121). Refactored to 5 fprintf sections; -Woverlength-strings added to default CFLAGS so future regressions fail the build. Help text drift cleanup (stale "zupt" examples → "vaptvupt", stale "LZ77 + Huffman" → "VaptVupt LZ + ANS 2.48.5", license attribution). New regression test tests/test_help_consistency.sh (10 assertions including the F-13 byte-level guard). No source crypto changes; audit posture unchanged from v3.0.1. gui/LICENSE-GUI rewritten AGPL-3.0-or-later with historical correction note). GUI version-string parsing bug fix (the v3.0.0 replace("zupt ", "") parser matched the wrong substring inside the new rename parenthetical, causing garbled window titles; fixed with anchored _VERSION_RE). New regression test tests/test_gui_branding.sh (11 assertions) wired into make check and make test. No source crypto changes; audit posture unchanged from v3.0.0 (which itself preserved the v2.3.1 baseline). Codec upgrade to VaptVupt LZ + ANS 2.48.5 (two libFuzzer-found fixes: csz==0 OOB-READ in vv_dstream_decompress_chunk; UBSan-safe pointer arithmetic in vv_copy_match). GUI binary-discovery bug fixed with liveness-checking _find_vaptvupt + discovery log. Enhanced manpage (597 lines). Wire format unchanged at v1.6. Bidirectional v2.x ↔ v3.0.0 archive compatibility verified. F-09 byte sweep still 0/1827 silent accepts; F-06 HMAC fuzz still 0/2000. No new findings, no findings reopened. Security posture unchanged. (curated 10-suite, 91-assertion subset) for OBS / Debian / RPM %check sections. openSUSE OBS files rewritten for cabelo (home:cabelo:innovators/zupt): license corrected MIT → AGPL-3.0-or-later, version bumped 1.5.5 → 2.4.8, changelog history preserved. No source changes; audit posture unchanged from v2.3.1.

  • v3.7.0 — 2026-06-01 — ML-KEM-768 decapsulation comparison routed through the audited constant-time primitive. The implicit- rejection check (received ct vs re-encrypted ct', 1088 bytes) was an inline byte-OR loop marked CT-REQUIRED but never measured and not sharing the zupt_ct_memeq primitive introduced in 3.5.0. A timing leak there is a KEM decapsulation oracle (valid vs invalid ciphertext) that breaks IND-CCA2. It now calls zupt_ct_memeq(ct, ct_prime, 1088); the fail bit is (1 - equal) so ML-KEM output is byte-identical (FIPS 203 roundtrip, implicit- rejection vector, PQ-hybrid roundtrip, wrong-key rejection all pass). This was the LAST security-critical comparison using a bespoke inline loop — MAC tag, F-08 trailer, and ML-KEM decaps now all route through one audited, length-independent primitive. tests/test_ct_timing extended to 1088 bytes with a source-routing guard. HONEST SCOPING: the 1088-byte dudect numbers are reported informational, not pass/fail — at that size on a shared vCPU the signal is memory-dominated and memcmp is not a cleanly-leaking control, so the 32-byte environment-relative ratio does not transfer. Constant-timeness of the 1088-byte compare instead follows from (a) the 32-byte pass proving zupt_ct_memeq is CT, (b) zupt_ct_memeq being length-independent by construction (OR-accumulate, no early exit, no data-dependent branch), and (c) the source-routing guard confirming decaps uses it. No cryptographic-correctness change, no wire-format change (v1.6). test_vectors 16/0; F-09 byte sweep 0/1827; F-06 0/2000. vectors + ML-KEM self-test fixes. AES-256-CTR — the bulk cipher — previously had only indirect roundtrip coverage; added the canonical SP 800-38A F.5.5 (encrypt) and F.5.6 (decrypt) vectors, validating zupt_aes256_ctr against the standard on both the Jasmin AES-NI path (zupt_aes256_ctr4 + zupt_aes256_blk) and the C T-table fallback. Both match exactly, confirming the Jasmin single-block AES is correct vs the standard (retires the stale stack-offset concern for zupt_aes256_blk). Also fixed two ML-KEM-768 self-test bugs: (1) an inverted result check in test_vectors that printed OK when the self-test returned failure — it had been passing vacuously; (2) the NTT roundtrip self-test asserted a false ntt∘inv_ntt == identity (this pqcrystals/Kyber Montgomery convention recovers each coefficient scaled by R^-1 mod q = 169), now rewritten to assert the true consistent-linear-scaling invariant, which still catches genuine NTT bugs and no longer emits a misleading stderr "NTT roundtrip FAILED". ML-KEM correctness end-to-end was never affected — the K-PKE, KEM, and FIPS 203 roundtrip vectors and implicit-rejection all pass. No source-crypto behaviour change, no wire-format change (v1.6). test_vectors now 16 passed / 0 failed (was 14, one vacuous). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. (dudect-style). The MAC tag compare — the most timing-sensitive operation, where a leak is a forgery oracle — was carried as three duplicated inline byte-OR loops marked CT-REQUIRED but never measured. Consolidated into one audited primitive zupt_ct_memeq (volatile OR-accumulate, no early exit, branch-free fold), used by the v1.6 strict decrypt path and the F-08 archive-integrity-trailer check. New dudect-style timing test (tests/test_ct_timing.c): Welch t-test over fixed-equal vs random-differing tag classes, built at -O2 so it exercises the shipped code (incl. that the volatile sink survives the optimiser). Verdict is environment- relative: a leaky-memcmp positive control must show a clear leak in the same environment, and zupt_ct_memeq must show <=20% of that signal (measured ~1%; median of 5 runs; INCONCLUSIVE rather than vacuous-pass if the host is too coarse). A reintroduced early- return/branch pushes the ratio toward 1.0 and fails. This turns an asserted CT property into a measured one + CI regression guard. The formally-verified Jasmin zupt_mac_verify_ct path (v1.4/v1.5 legacy compare) and the F-06 two-candidate fold are unchanged. No cryptographic-correctness change, no wire-format change (v1.6). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. The 0x04 Argon2id enc-header recorded only [type|salt|nonce] and nothing about the KDF cost (the PBKDF2 header records its iteration count). New archives append a one-byte KDF profile descriptor at offset 33 (ZUPT_ARGON2_PROFILE_MODERATE), making the header self- describing so a reader always knows which Argon2id cost produced the archive — eliminating a silent-undecryptability risk if the preset ever changes. The descriptor is covered by the F-08 archive- integrity trailer (tamper-evident; a flipped byte fails decryption, verified). Additive + back-compatible: legacy 33-byte archives decrypt byte-exact; 33B and 34B headers derive identical keys; unknown profiles are refused fail-closed (no wrong-key guessing). Build-time SDK-drift guard: the F-15 test asserts the KDF is deterministic and meets a coarse memory-hard cost floor (>=20 ms), failing the build if libzuptsdk is swapped for a weak/stub Argon2id. No cryptographic-correctness change, no wire-format change (v1.6). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. Note: the explicit RFC 9106 zsdk_argon2id() is header-declared but NOT exported by the vendored libzuptsdk.so, so the cost is recorded in- band rather than re-parameterised; revisit if the SDK exports it. Encrypt-then-MAC hot path. ipad/opad key prefix folded once per keyring; MAC streamed (aad || nonce || ciphertext || seq) instead of concatenated into a malloc'd buffer. SECURITY-RELEVANT: identical authentication semantics — the MAC is byte-for-byte the same (RFC 2104 + SHA-256 Merkle-Damgard associativity), proven by RFC 4231 vectors, a new equivalence test, and byte-exact decryption of 3.2.x archives. Constant-time tag compares unchanged (byte-OR accumulator / Jasmin zupt_mac_verify_ct). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. Reduces secret-data heap footprint: the old path copied the full ciphertext into a second malloc'd buffer per block — now removed. No cryptographic- correctness change, no wire-format change (v1.6). ASan clean on both KDF paths. SHA-NI). New SHA256RNDS2/MSG1/MSG2 compression path with CPUID runtime dispatch (has_shani, CPUID.07H:EBX[29]). Accelerates the Encrypt-then-MAC second pass (HMAC-SHA256) and PBKDF2. Security-relevant property: SHA-NI is constant-time by construction (no data-dependent memory access or branches), strengthening the side-channel posture of HMAC verification over attacker-influenced ciphertext vs the scalar software path. No cryptographic-correctness change: same SHA-256, same HMAC, same Encrypt-then-MAC, same wire bytes (format v1.6 unchanged). NIST FIPS 180-4 vectors pass on both paths; the 64 SHA-NI round constants are verified bit-identical to the scalar K[] table. Speedup ([ESTIMATED] 3-8x) NOT measured in this release — the build host lacks SHA-NI; figure to be confirmed on SHA-NI hardware. Scalar fallback unchanged and remains the path on non-SHA-NI CPUs (incl. aarch64). F-09 byte sweep still 0/1827. byte-identical; 3 .c files changed). Inherits 6 upstream corrupt-input decoder memory-safety fixes (v2.52.4 + v2.53.2). F-14 closed: ASAN found a heap-buffer-overflow WRITE of size 32 in OUR decode wrapper — decode buffers were malloc(uncompressed _size) with no slack, but the codec AVX2 over-copy needs >=32 B slack (documented contract in vaptvupt.h). Old codec never reached it; new wider AVX2 hot path does. Fixed with shared ZUPT_VV_DECODE_SLACK (64 B) guard on both single-threaded (zupt_format.c) and parallel (zupt_parallel.c) decode paths. Verified: ASAN 24/24 single-threaded + 15/15 multi-threaded; bit-flip fuzz 300 trials 0 crashes / 300 clean rejects. F-09 byte sweep still 0/1827. vv_decoder.c scalar build made -Werror clean for aarch64/Termux. New test tests/test_vv_decode _slack.sh. Wire format unchanged (v1.6); 3.0.3 archives extract byte-exact. knownConditionTrueFalse findings closed in varint decoders (dead && (x&0x80) AND-branch after preceding terminator-byte early-return). -Wconversion / -Wsign-conversion findings closed with explicit casts at two sites. Our 9-file non-vendored C now compiles clean under the union of strict GCC warnings including -Wconversion -Wsign-conversion -Werror. New regression test tests/test_static_analysis.sh (7 assertions) wired into make check and make test. Behaviour byte-identical; F-09 byte sweep still 0/1827 silent accepts. No source crypto changes. src/zupt_main.c exceeded C99's 4095-char ISO limit (was 4121). Refactored to 5 fprintf sections; -Woverlength-strings added to default CFLAGS so future regressions fail the build. Help text drift cleanup (stale "zupt" examples → "vaptvupt", stale "LZ77 + Huffman" → "VaptVupt LZ + ANS 2.48.5", license attribution). New regression test tests/test_help_consistency.sh (10 assertions including the F-13 byte-level guard). No source crypto changes; audit posture unchanged from v3.0.1. gui/LICENSE-GUI rewritten AGPL-3.0-or-later with historical correction note). GUI version-string parsing bug fix (the v3.0.0 replace("zupt ", "") parser matched the wrong substring inside the new rename parenthetical, causing garbled window titles; fixed with anchored _VERSION_RE). New regression test tests/test_gui_branding.sh (11 assertions) wired into make check and make test. No source crypto changes; audit posture unchanged from v3.0.0 (which itself preserved the v2.3.1 baseline). Codec upgrade to VaptVupt LZ + ANS 2.48.5 (two libFuzzer-found fixes: csz==0 OOB-READ in vv_dstream_decompress_chunk; UBSan-safe pointer arithmetic in vv_copy_match). GUI binary-discovery bug fixed with liveness-checking _find_vaptvupt + discovery log. Enhanced manpage (597 lines). Wire format unchanged at v1.6. Bidirectional v2.x ↔ v3.0.0 archive compatibility verified. F-09 byte sweep still 0/1827 silent accepts; F-06 HMAC fuzz still 0/2000. No new findings, no findings reopened. Security posture unchanged. (curated 10-suite, 91-assertion subset) for OBS / Debian / RPM %check sections. openSUSE OBS files rewritten for cabelo (home:cabelo:innovators/zupt): license corrected MIT → AGPL-3.0-or-later, version bumped 1.5.5 → 2.4.8, changelog history preserved. No source changes; audit posture unchanged from v2.3.1.

  • v4.0.0 — 2026-06-10 — Stack integration: codec → canonical 2.60.4 (security release; OOB heap write in AVX2 exact-size decode fixed; CBMC-verified BCJ). F-16 found, disclosed, fixed (see Findings). New --pq-box mode via vendored libpqvaptvupt 0.6.0 (HKDF-SHA256 domain-separated combiner; 13/13 adversarial checks; ASan/UBSan clean). New exact-content_size decode regression (80 cases, ASan). SHA-NI measured 5.8× same-box; v3.2.0 estimate retired. Clang strict build restored (as(1) for Jasmin .s). 8-mode back-compat byte-exact.

  • v4.0.0 — 2026-06-10 — F-16: data loss in ≤3.8.0 BCJ encoding (pre-existing; found by the 4.0.0 back-compat matrix; fixed by the codec move to canonical 2.60.4). The ≤3.8.0 tree vendored a divergent pre-release BCJ (upstream 2.53.3 has no BCJ; it landed in 2.53.4). On BCJ-detected executable content at L8/L9 the old encoder emitted streams that no decoder accepts — including 3.8.0 itself (verified: the 3.8.0 binary fails on the archive it just wrote; defect is at write time, deterministic on the binary fixture). L≤7 and non-BCJ content unaffected: the 8-mode matrix (plain L1/L5/L9, store, Argon2id, PBKDF2, --pq, --pq-sdk) decodes byte-exact under 4.0.0. Remediation: re-create affected archives with ≥4.0.0 and verify extraction before deleting sources. Regression guard: tests/test_codec_exact_size.sh includes tool-level BCJ roundtrips (L5/L9, real ELF fixture) plus 80 exact-size codec decodes under ASan covering the upstream OOB fix class. Forward-compat note: ≤3.8.0 cannot read 4.0.0 archives where the auto-filter fired (L3+ on ELF/PE/Mach-O); upgrade readers first in mixed fleets.

  • v3.8.0 — 2026-06-01 — Documentation-only release: consolidated measured benchmarks (BENCHMARKS.md). No source, cryptographic, or wire-format change — the binary is identical in behaviour to 3.7.0 (format v1.6). Publishes a complete reproducible benchmark set (compression ratio/throughput, encode-speed-vs-level, the KDF-vs-per-block crypto overhead split, and a head-to-head ratio comparison against zstd that shows where VaptVupt loses) with the test machine and method stated per table. The SHA-NI speedup is explicitly marked [ESTIMATED] as the test box has no SHA-NI. No new findings; all prior guarantees unchanged: test_vectors 16/0, F-09 byte sweep 0/1827, F-06 1-bit HMAC fuzz 0/2000, every security-critical comparison through the audited constant-time primitive. through the audited constant-time primitive. The implicit- rejection check (received ct vs re-encrypted ct', 1088 bytes) was an inline byte-OR loop marked CT-REQUIRED but never measured and not sharing the zupt_ct_memeq primitive introduced in 3.5.0. A timing leak there is a KEM decapsulation oracle (valid vs invalid ciphertext) that breaks IND-CCA2. It now calls zupt_ct_memeq(ct, ct_prime, 1088); the fail bit is (1 - equal) so ML-KEM output is byte-identical (FIPS 203 roundtrip, implicit- rejection vector, PQ-hybrid roundtrip, wrong-key rejection all pass). This was the LAST security-critical comparison using a bespoke inline loop — MAC tag, F-08 trailer, and ML-KEM decaps now all route through one audited, length-independent primitive. tests/test_ct_timing extended to 1088 bytes with a source-routing guard. HONEST SCOPING: the 1088-byte dudect numbers are reported informational, not pass/fail — at that size on a shared vCPU the signal is memory-dominated and memcmp is not a cleanly-leaking control, so the 32-byte environment-relative ratio does not transfer. Constant-timeness of the 1088-byte compare instead follows from (a) the 32-byte pass proving zupt_ct_memeq is CT, (b) zupt_ct_memeq being length-independent by construction (OR-accumulate, no early exit, no data-dependent branch), and (c) the source-routing guard confirming decaps uses it. No cryptographic-correctness change, no wire-format change (v1.6). test_vectors 16/0; F-09 byte sweep 0/1827; F-06 0/2000. vectors + ML-KEM self-test fixes. AES-256-CTR — the bulk cipher — previously had only indirect roundtrip coverage; added the canonical SP 800-38A F.5.5 (encrypt) and F.5.6 (decrypt) vectors, validating zupt_aes256_ctr against the standard on both the Jasmin AES-NI path (zupt_aes256_ctr4 + zupt_aes256_blk) and the C T-table fallback. Both match exactly, confirming the Jasmin single-block AES is correct vs the standard (retires the stale stack-offset concern for zupt_aes256_blk). Also fixed two ML-KEM-768 self-test bugs: (1) an inverted result check in test_vectors that printed OK when the self-test returned failure — it had been passing vacuously; (2) the NTT roundtrip self-test asserted a false ntt∘inv_ntt == identity (this pqcrystals/Kyber Montgomery convention recovers each coefficient scaled by R^-1 mod q = 169), now rewritten to assert the true consistent-linear-scaling invariant, which still catches genuine NTT bugs and no longer emits a misleading stderr "NTT roundtrip FAILED". ML-KEM correctness end-to-end was never affected — the K-PKE, KEM, and FIPS 203 roundtrip vectors and implicit-rejection all pass. No source-crypto behaviour change, no wire-format change (v1.6). test_vectors now 16 passed / 0 failed (was 14, one vacuous). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. (dudect-style). The MAC tag compare — the most timing-sensitive operation, where a leak is a forgery oracle — was carried as three duplicated inline byte-OR loops marked CT-REQUIRED but never measured. Consolidated into one audited primitive zupt_ct_memeq (volatile OR-accumulate, no early exit, branch-free fold), used by the v1.6 strict decrypt path and the F-08 archive-integrity-trailer check. New dudect-style timing test (tests/test_ct_timing.c): Welch t-test over fixed-equal vs random-differing tag classes, built at -O2 so it exercises the shipped code (incl. that the volatile sink survives the optimiser). Verdict is environment- relative: a leaky-memcmp positive control must show a clear leak in the same environment, and zupt_ct_memeq must show <=20% of that signal (measured ~1%; median of 5 runs; INCONCLUSIVE rather than vacuous-pass if the host is too coarse). A reintroduced early- return/branch pushes the ratio toward 1.0 and fails. This turns an asserted CT property into a measured one + CI regression guard. The formally-verified Jasmin zupt_mac_verify_ct path (v1.4/v1.5 legacy compare) and the F-06 two-candidate fold are unchanged. No cryptographic-correctness change, no wire-format change (v1.6). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. The 0x04 Argon2id enc-header recorded only [type|salt|nonce] and nothing about the KDF cost (the PBKDF2 header records its iteration count). New archives append a one-byte KDF profile descriptor at offset 33 (ZUPT_ARGON2_PROFILE_MODERATE), making the header self- describing so a reader always knows which Argon2id cost produced the archive — eliminating a silent-undecryptability risk if the preset ever changes. The descriptor is covered by the F-08 archive- integrity trailer (tamper-evident; a flipped byte fails decryption, verified). Additive + back-compatible: legacy 33-byte archives decrypt byte-exact; 33B and 34B headers derive identical keys; unknown profiles are refused fail-closed (no wrong-key guessing). Build-time SDK-drift guard: the F-15 test asserts the KDF is deterministic and meets a coarse memory-hard cost floor (>=20 ms), failing the build if libzuptsdk is swapped for a weak/stub Argon2id. No cryptographic-correctness change, no wire-format change (v1.6). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. Note: the explicit RFC 9106 zsdk_argon2id() is header-declared but NOT exported by the vendored libzuptsdk.so, so the cost is recorded in- band rather than re-parameterised; revisit if the SDK exports it. Encrypt-then-MAC hot path. ipad/opad key prefix folded once per keyring; MAC streamed (aad || nonce || ciphertext || seq) instead of concatenated into a malloc'd buffer. SECURITY-RELEVANT: identical authentication semantics — the MAC is byte-for-byte the same (RFC 2104 + SHA-256 Merkle-Damgard associativity), proven by RFC 4231 vectors, a new equivalence test, and byte-exact decryption of 3.2.x archives. Constant-time tag compares unchanged (byte-OR accumulator / Jasmin zupt_mac_verify_ct). F-09 byte sweep 0/1827; F-06 1-bit HMAC fuzz 0/2000. Reduces secret-data heap footprint: the old path copied the full ciphertext into a second malloc'd buffer per block — now removed. No cryptographic- correctness change, no wire-format change (v1.6). ASan clean on both KDF paths. SHA-NI). New SHA256RNDS2/MSG1/MSG2 compression path with CPUID runtime dispatch (has_shani, CPUID.07H:EBX[29]). Accelerates the Encrypt-then-MAC second pass (HMAC-SHA256) and PBKDF2. Security-relevant property: SHA-NI is constant-time by construction (no data-dependent memory access or branches), strengthening the side-channel posture of HMAC verification over attacker-influenced ciphertext vs the scalar software path. No cryptographic-correctness change: same SHA-256, same HMAC, same Encrypt-then-MAC, same wire bytes (format v1.6 unchanged). NIST FIPS 180-4 vectors pass on both paths; the 64 SHA-NI round constants are verified bit-identical to the scalar K[] table. Speedup ([ESTIMATED] 3-8x) NOT measured in this release — the build host lacks SHA-NI; figure to be confirmed on SHA-NI hardware. Scalar fallback unchanged and remains the path on non-SHA-NI CPUs (incl. aarch64). F-09 byte sweep still 0/1827. byte-identical; 3 .c files changed). Inherits 6 upstream corrupt-input decoder memory-safety fixes (v2.52.4 + v2.53.2). F-14 closed: ASAN found a heap-buffer-overflow WRITE of size 32 in OUR decode wrapper — decode buffers were malloc(uncompressed _size) with no slack, but the codec AVX2 over-copy needs >=32 B slack (documented contract in vaptvupt.h). Old codec never reached it; new wider AVX2 hot path does. Fixed with shared ZUPT_VV_DECODE_SLACK (64 B) guard on both single-threaded (zupt_format.c) and parallel (zupt_parallel.c) decode paths. Verified: ASAN 24/24 single-threaded + 15/15 multi-threaded; bit-flip fuzz 300 trials 0 crashes / 300 clean rejects. F-09 byte sweep still 0/1827. vv_decoder.c scalar build made -Werror clean for aarch64/Termux. New test tests/test_vv_decode _slack.sh. Wire format unchanged (v1.6); 3.0.3 archives extract byte-exact. knownConditionTrueFalse findings closed in varint decoders (dead && (x&0x80) AND-branch after preceding terminator-byte early-return). -Wconversion / -Wsign-conversion findings closed with explicit casts at two sites. Our 9-file non-vendored C now compiles clean under the union of strict GCC warnings including -Wconversion -Wsign-conversion -Werror. New regression test tests/test_static_analysis.sh (7 assertions) wired into make check and make test. Behaviour byte-identical; F-09 byte sweep still 0/1827 silent accepts. No source crypto changes. src/zupt_main.c exceeded C99's 4095-char ISO limit (was 4121). Refactored to 5 fprintf sections; -Woverlength-strings added to default CFLAGS so future regressions fail the build. Help text drift cleanup (stale "zupt" examples → "vaptvupt", stale "LZ77 + Huffman" → "VaptVupt LZ + ANS 2.48.5", license attribution). New regression test tests/test_help_consistency.sh (10 assertions including the F-13 byte-level guard). No source crypto changes; audit posture unchanged from v3.0.1. gui/LICENSE-GUI rewritten AGPL-3.0-or-later with historical correction note). GUI version-string parsing bug fix (the v3.0.0 replace("zupt ", "") parser matched the wrong substring inside the new rename parenthetical, causing garbled window titles; fixed with anchored _VERSION_RE). New regression test tests/test_gui_branding.sh (11 assertions) wired into make check and make test. No source crypto changes; audit posture unchanged from v3.0.0 (which itself preserved the v2.3.1 baseline). Codec upgrade to VaptVupt LZ + ANS 2.48.5 (two libFuzzer-found fixes: csz==0 OOB-READ in vv_dstream_decompress_chunk; UBSan-safe pointer arithmetic in vv_copy_match). GUI binary-discovery bug fixed with liveness-checking _find_vaptvupt + discovery log. Enhanced manpage (597 lines). Wire format unchanged at v1.6. Bidirectional v2.x ↔ v3.0.0 archive compatibility verified. F-09 byte sweep still 0/1827 silent accepts; F-06 HMAC fuzz still 0/2000. No new findings, no findings reopened. Security posture unchanged. (curated 10-suite, 91-assertion subset) for OBS / Debian / RPM %check sections. openSUSE OBS files rewritten for cabelo (home:cabelo:innovators/zupt): license corrected MIT → AGPL-3.0-or-later, version bumped 1.5.5 → 2.4.8, changelog history preserved. No source changes; audit posture unchanged from v2.3.1.


1. Cryptographic Test Vector Verification

All primitives tested against published reference vectors:

Primitive Standard Vectors Status
SHA-256 FIPS 180-4 3 (empty, "abc", 448-bit) PASS
HMAC-SHA256 RFC 4231 2 (TC2: "Jefe", TC3: 20×0xAA) PASS
AES-256-CTR NIST SP 800-38A 2 (F.5.5 encrypt, F.5.6 decrypt; 4 blocks) PASS
SHA3-256 FIPS 202 2 (empty, "abc") PASS
SHAKE-128 FIPS 202 1 (empty, 128-bit output) PASS
X25519 RFC 7748 §5.2 2 (both test vectors) PASS
ML-KEM-768 FIPS 203 2 (5-trial roundtrip + implicit rejection) PASS
XXH64 xxHash spec 1 (empty string, seed=0) PASS
ML-KEM-768 self-test internal 1 (NTT consistent-scaling + CBD bounds) PASS
Total 16 16/16 PASS

2. Jasmin Constant-Time Verification

Function Purpose Status
zupt_mac_verify_ct HMAC comparison Linked, CT-proven
zupt_ct_select_32 ML-KEM FO select Linked, CT-proven
zupt_fe_cswap X25519 conditional swap Linked, CT-proven
zupt_aes256_blk AES-256 single-block (AES-NI) Linked, CT by hardware
zupt_aes256_ctr4 AES-256 4-block pipeline Linked, CT by hardware

3. ACSL Formal Annotations

19 security-critical functions annotated with requires/ensures/assigns contracts. Target: frama-c -wp -wp-rte -wp-model Typed+Cast

4. Security Hardening

Feature Status
mlock() key protection Active
Buffer canaries (keyring) Active
Always-decrypt timing mitigation Active
AFL++ fuzz harnesses Available (make fuzz-build)

5. VaptVupt Codec Tests

Test Status
Roundtrip all 3 modes (UF/BAL/EXT) PASS
Roundtrip + AES-256 encryption PASS
Roundtrip + PQ hybrid encryption PASS
Roundtrip + multi-threaded PASS
Roundtrip + solid mode PASS
Incompressible fallback to store PASS
Empty/small input PASS
Multi-block (2 MB) PASS
Total 11/11 PASS
Suite Tests Result What It Covers
Regression 16 16/16 PASS All codecs, modes, encryption, edge cases, corruption detection
Multi-threaded 14 14/14 PASS N=1/2/4/8 threads, large files, 1000 files, MT+encryption
Post-quantum 10 10/10 PASS Keygen, PQ encrypt/decrypt, wrong key, password compat, PQ+MT, 2MB
Quick smoke 9 9/9 PASS Normal, solid, encrypted, wrong pw, MT, fast, store, PQ, integrity
NIST vectors 14 14/14 PASS See table above
Total 62 62/62 PASS

Reproduction: make test-all


3. Memory Safety

Tool Command Result
AddressSanitizer make test-asan Zero errors
UndefinedBehaviorSanitizer Built with -fsanitize=address,undefined Zero errors
All code paths tested Normal + solid + encrypted + PQ + MT Clean

Reproduction:

make test-asan
./zupt_asan compress /tmp/t.zupt /path/to/data/
./zupt_asan extract -o /tmp/out/ /tmp/t.zupt
./zupt_asan keygen -o /tmp/k.key
./zupt_asan compress --pq /tmp/pub.key /tmp/pq.zupt /path/to/data/
./zupt_asan extract --pq /tmp/k.key -o /tmp/pqout/ /tmp/pq.zupt

4. Compiler Warning Audit

Compiler Flags Warnings
GCC 13.x -Wall -Wextra -Wpedantic -O2 -std=c11 Zero
Clang 18.x -Wall -Wextra -Wpedantic -O2 -std=c11 Zero

5. Constant-Time Analysis

Function Location CT Method Jasmin Verified? Risk Level
HMAC comparison zupt_crypto.c:252 4×u64 XOR accumulation Yeszupt_mac_verify_ct linked None (Jasmin proven)
ML-KEM FO select zupt_mlkem.c:593 4×u64 masked select Yeszupt_ct_select_32 linked None (Jasmin proven)
ML-KEM NTT butterfly zupt_mlkem.c Montgomery reduction (branchless) No Low
ML-KEM CBD sampling zupt_mlkem.c Bitwise operations only No Low
X25519 fe_cswap zupt_x25519.c:95 Masked XOR swap No (limb mismatch) Low (C is branchless)
X25519 Montgomery ladder zupt_x25519.c:243 Fixed 255 iterations No Low
AES-256 encrypt zupt_aes256.c:59 Table-based S-box No HIGH on shared HW
SHA-256 zupt_sha256.c Table-based constants No Low (not secret-indexed)
Keccak-f[1600] zupt_keccak.c Bitwise XOR/ROT only No None
Key wipe zupt_crypto.c explicit_bzero / volatile No Low

Jasmin Assembly Verification

Two functions confirmed active in binary via nm:

0000000000014ae0 T zupt_mac_verify_ct    ← Jasmin assembly, CT proven
0000000000014b20 T zupt_ct_select_32     ← Jasmin assembly, CT proven

Assembly generated by jasminc 2026.03.0. Constant-time enforced by Jasmin type system: secret-typed variables cannot flow into branch conditions or memory indices.

Not Wired (with reason)

Function Issue Fallback
zupt_fe_cswap Jasmin: 4×u64 limbs, C: 5×u51 — incompatible C masked XOR (branchless)
zupt_aes256_blk Stack offset bug: rk.[1][rsp+1] not [rsp+16] C table-based AES

6. Key Material Lifecycle

Phase Method Verified
Generation OS CSPRNG: getrandom(2) / /dev/urandom / RtlGenRandom Hard fail if unavailable
Storage Stack-local arrays (no heap allocation for keys) ASAN verified
Usage Passed by const pointer to AES-CTR / HMAC No copies to heap
Wipe zupt_secure_wipe(): explicit_bzero (glibc 2.25+), SecureZeroMemory (Win), volatile fallback Compiler cannot optimize out
Scope exit Stack frame destroyed Keys were on stack

All intermediate buffers in PBKDF2, hybrid KEM, ML-KEM encaps/decaps, and X25519 wiped before return.


7. Nonce Security

Scheme: per_block_nonce = base_nonce XOR pad_le(block_seq, 8)

  • base_nonce: 128-bit random from CSPRNG, generated once per archive.
  • block_seq: monotonically increasing 0, 1, 2, ... per archive.
  • Uniqueness within archive: Guaranteed (distinct seq → distinct nonce).
  • Uniqueness across archives: 2^-128 collision probability per pair (birthday bound on random base).

8. Encrypt-then-MAC Ordering

Step Action Verified
1 Compute HMAC over nonce ‖ ciphertext HMAC input is nonce+ct, not plaintext
2 Verify HMAC before any decryption Code path: MAC check → early return if fail → decrypt only on success
3 Decrypt only authenticated data No plaintext produced from unauthenticated ciphertext

Prevents: Chosen-ciphertext attacks, padding oracles, ciphertext tampering.


9. Bugs Found and Fixed (v0.5.1 → v1.5.0)

Bug Severity Version Fixed Impact
Huffman Kraft-inequality violation Critical v0.5.1 Data corruption on specific inputs
Heap-buffer-overflow in LZ match finder Critical v0.5.1 Potential code execution
rand() CSPRNG fallback Critical v0.5.1 Predictable encryption keys
ML-KEM poly_basemul OOB Critical v1.0.0 Buffer overread in NTT
ML-KEM missing poly_tomont Critical v1.0.0 Public key in wrong domain
ML-KEM inverted FO cmov Critical v1.0.0 Always selected rejection key
ML-KEM inv_ntt wrong table High v1.0.0 NTT roundtrip failure
PQ nonce mismatch High v1.0.0 Encrypt/decrypt used different nonces
X25519 AA + a24*E formula High v1.1.0 Wrong curve, not interoperable
Dead match_cost() Low v1.1.0 Clang warning
const polyvec qualifier Low v1.1.0 Pedantic warnings
__int128 pedantic Low v1.1.0 Pedantic warning

10. Known Limitations

Limitation Impact Mitigation Status
Table-based AES (C fallback) Cache-timing on shared hardware Jasmin AES-NI path exists but has offset bug Open — fix .jazz source
Table-based SHA-256 Theoretical cache-timing Not used on secret-indexed data Accepted
PBKDF2 not quantum-safe Quantum password brute-force Use --pq mode Documented
No mlock() Keys swappable to disk Short key lifetime + zupt_secure_wipe Planned
No fuzzing performed Undiscovered bugs AFL++ setup in FUZZING.md Planned
No independent audit Self-assessed only Open source + Jasmin proofs Planned
X25519 Jasmin not linked C fallback for fe_cswap C is branchless but compiler-dependent Open — limb mismatch

© 2026 Cristian Cezar Moisés — AGPL-3.0-or-later


v2.2.1 audit pass — 2026-04-27

This pass focused on the production-readiness of the libzuptsdk integration introduced in v2.2.0 and on adversarial review of the existing code paths not previously audited.

Methodology

Two-pass adversarial review:

  • Pass A (read-and-reason): read each source file, identify invariants, ask "what does an attacker control?", "what happens at boundaries?".
  • Pass B (test-driven): write a failing test that exercises the suspected bug, fix it, write a regression test that fails before the fix and passes after.

When A and B disagreed, the discrepancy was investigated rather than papered over.

Findings (all fixed in v2.2.1)

# File:line Severity Description
1 zupt_format.c:146 low varint reader truncated at 9 bytes
2 zupt_format.c:1529..1699 (×6) medium unchecked fwrite in extract path → silent corruption
3 zupt_crypto_sdk.c:90.. low (defense-in-depth) mac_key aliased to enc_key in SDK paths
4 zupt_lz.c:33 high size_t overflow in LZ length decoder
5 zupt_format.c:1610,1681 high dedup-ref recursion + OOB seek (DoS)
6 zupt_format.c:446,883 low encrypt failure left partial archive

The only finding rated as high severity (#4 and #5) are exploitable from a malicious archive: an attacker who can convince the user to extract their archive could trigger a process crash. None of the findings allow code execution or key recovery; the AEAD layer's authentication tag still prevents arbitrary writes.

Test coverage after fixes

Suite Count Status
Native (run_quick.sh) 9
SDK roundtrip (test_sdk.sh) 11
Audit double-validated (test_audit.sh) 10 ✓ NEW
Inherited from libzuptsdk 2.1.5 169
Inherited fuzz iterations (ASAN-clean) 750,000
Total verified test points 199 + 750k fuzz

Notes for users

If you are using zupt in production:

  • v2.2.1 is a recommended upgrade.
  • Archives written with v2.2.0 or earlier remain readable; no migration needed.
  • The high-severity findings (#4, #5) only affect the extract path. If you only ever extract archives you created yourself, you are not affected by them. If you accept third-party archives, upgrade.
  • The --pq-sdk mode introduced in v2.2.0 was not affected by any of these findings; it was introduced clean and remained clean.

2026-04-27 — v2.2.1 audit pass

Internal code review against an internal audit checklist (AUDIT_PROMPT — superseded by FORMAL_AUDIT_PROMPT.md). Six bugs identified and fixed in the same release. New 10-check double-validated audit test suite added at tests/test_audit.sh.

Bugs found and fixed

# File:line Severity Description
1 src/zupt_format.c:146 low uint64 varint truncated to 63 bits
2 src/zupt_format.c (×6) medium unchecked fwrite returns in extract path
3 src/zupt_crypto_sdk.c low mac_key was copy of enc_key, now KDF-split
4 src/zupt_lz.c:33 high lz_read_extra size_t overflow → OOB copy
5 src/zupt_format.c (×2) medium dedup-ref forward offset + recursion accepted
6 src/zupt_format.c (×2) low partial archive not removed on encrypt-init fail

Test methodology

  • Path A: code review identifies invariant; a failing test is constructed.
  • Path B: an independent property-based check exercises the same invariant from a different angle.
  • A test passes only when A and B agree. Disagreement is treated as a finding.

10 audit checks across four categories (authenticated archives, format security, format compatibility, robustness). All passing.

Cumulative test surface (2.2.1)

Suite Tests Status
make test (run_quick) 9
tests/test_sdk.sh 11
tests/test_audit.sh 10
zupt total 30
Inherited libzuptsdk audit 42
Inherited libzuptsdk RFC + roundtrip 84
Inherited libzuptsdk binding contracts 57
Inherited libzuptsdk Wycheproof 5
Combined zupt + SDK 218
Mutation-fuzz iters (ASAN/UBSAN) 750,000

Open items (not blockers)

  • No external audit yet.
  • make test-asan not wired into the zupt Makefile (only the SDK Makefile has it).
  • The deduplication path is structurally complex and would benefit from property-based testing (currently covered by 30 tests, none property-based).

2026-04-27 — v2.2.2 audit pass

Second internal review against the same audit checklist, focused on format parser robustness and dedup path correctness.

Bugs found and fixed (4)

# File:line Severity Description
7 zupt_format.c:166 medium realloc-pair atomicity: UB on partial failure
8 zupt_format.c:138 low in-memory varint decoder had same 9-byte truncation as file variant
9 zupt_format.c:1267 medium encryption_header_off not bounds-checked before seek
10 zupt_format.c:1402 medium index_offset not bounds-checked before seek

New test surface

  • 12 dedup property-based checks (test_dedup_props.sh) — covers byte-exact roundtrip, dedup space savings, 100%-duplicate sets, and dedup + PQ encryption interaction.
  • 1000 ASAN/UBSAN fuzz iterations (fuzz_format) — zero crashes, zero memory errors.

Cumulative test surface (2.2.2)

Suite Tests Status
run_quick.sh 9
test_sdk.sh 11
test_audit.sh 10
test_dedup_props.sh 12 ✓ NEW
zupt total 42
Format mutation fuzz (ASAN/UBSAN) 1,000 iters ✓ NEW
Inherited libzuptsdk audit 42
Inherited libzuptsdk RFC + roundtrip 84
Inherited libzuptsdk binding contracts 57
Inherited libzuptsdk Wycheproof 5
Inherited libzuptsdk fuzz 750,000 iters
Combined zupt + SDK 260 tests + 751k fuzz

CI

GitHub Actions workflow added at .github/workflows/ci.yml: build-and-test, asan-build, fuzz-format, package-deb. Each run exercises the full test surface plus fuzz under sanitizers and verifies the .deb installs cleanly.

Open items

  • External audit still pending (cost-bound, not engineering-bound).
  • AppImage build via real appimagetool not yet automated in CI.
  • The fuzz harness uses a single fixed seed archive; corpus diversification (different file types, multi-file archives, encrypted seeds) would strengthen coverage further.

2026-04-27 — v2.2.2 formal audit (no version bump)

Formal cryptographic audit pass conducted using methodology in FORMAL_AUDIT_PROMPT.md. Auditor profile: senior cryptographic engineering (15+ years production crypto). Threat model: government archives with 30+ year retention, financial institutions under Brazilian Central Bank Resolução 4.658/2018, healthcare (LGPD-Saúde), defense (CNSA 2.0 alignment).

Methodology

  • Path A: line-by-line manual review with documented preconditions, postconditions, invariants, trust boundaries, failure modes.
  • Path B: independent adversarial test exercising the same invariant.
  • Bug confirmed only when both paths agreed.

Bugs found and fixed (4)

# File Severity Description
11 zupt_format.c (×2) HIGH Zip Slip path traversal in extract — e->path to fopen without validation
12 zupt_format.c (×2) MEDIUM symlink-follow on extract output (fopen "wb" follows symlinks)
13 zupt_format.c:1593 LOW size_t overflow on solid-extract size cap (32-bit)
14 zupt_format.c:parse_index LOW count * sizeof(entry) overflow before calloc (32-bit)

Cryptographic primitive review (no findings)

Reviewed every public crypto path against:

  • FIPS 197 (AES) — key/IV size, counter init, nonce reuse
  • FIPS 202 (Keccak/SHA-3) — rate/capacity, no domain confusion
  • FIPS 203 (ML-KEM) — parameter set correctness, key sanitization, decap fault resistance
  • RFC 5297 (AES-SIV) — nonce-misuse resistance, AD coverage
  • RFC 5869 (HKDF) — salt-vs-IKM separation, info domain separation
  • RFC 7748 (X25519) — scalar clamping, all-zero output rejection
  • RFC 8439 (ChaCha20-Poly1305) — 192-bit XChaCha nonce, AD coverage
  • RFC 9106 (Argon2) — m≥64 MiB, t≥3, p≥1, salt≥16B
  • RFC 9180 (HPKE) — suite ID, mode binding, encap context

Findings: none. All primitives correctly implemented.

New regression test suite

tests/test_path_traversal.sh — 5 property checks covering:

  1. Patched archive with ../ entry does not escape parent dir
  2. Patched archive with absolute path does not write to /tmp/owned
  3. Symlink at extract target is not followed (sentinel preserved)
  4. Legitimate paths still extract correctly
  5. Deep nested safe paths still work

Cumulative test surface (2.2.2 final)

Suite Tests Status
run_quick.sh 9
test_sdk.sh 11
test_audit.sh 10
test_dedup_props.sh 12
test_path_traversal.sh 5 ✓ NEW
zupt total 47
Format mutation fuzz (ASAN/UBSAN) 1,000 iters
Inherited libzuptsdk audit 42
Inherited libzuptsdk RFC + roundtrip 84
Inherited libzuptsdk binding contracts 57
Inherited libzuptsdk Wycheproof 5
Inherited libzuptsdk fuzz 750,000 iters
Combined zupt + SDK 265 tests + 751k fuzz

Portability re-verification

Static portability scan: clean.

  • No unaligned pointer casts
  • No raw / separators (uses ZUPT_PATH_SEP)
  • No htonl/ntohl/struct casts (LE helpers throughout)
  • No POSIX-only headers without #ifdef _WIN32 guards

GCC + -Wpedantic build: clean. Win32 paths verified via -D_WIN32 -E synthetic preprocessing.

Cumulative bug count across audit sprints

Sprint Bugs found Severity range
v2.2.1 (first audit) 6 low to high
v2.2.2 (second audit) 4 low to medium
v2.2.2 formal 4 low to high (Zip Slip path traversal)
v2.2.2 sprint 4 1 critical (silent extract via arg parser)
v2.2.2 god-tier audit 1 critical (block-swap AEAD)
Total 16 all fixed and regression-tested

Open items

  • External independent audit still pending (cost, not engineering)
  • Side-channel timing leak testing not performed
  • Cross-OS CI (macOS / Windows / FreeBSD runners) not yet wired
  • Formal verification beyond Jasmin constant-time primitives (F*, ProVerif) not pursued

2026-05-01 — v2.2.3 release audit (VaptVupt 2.48.2 integration)

Two independent test passes performed: one on the working tree, a second on a clean build from the produced source tarball (zupt-2.2.3-source.tar.gz). Both passes identical and clean.

Surfaces verified

Surface Test target Pass 1 Pass 2 Notes
Quick suite make test 9 + 11 + 10 + 12 + 5 + 8 + 6 = 61 OK 61 OK All tests/*.sh
Regression tests/regression.sh 22/22 22/22 T17 fixed (see CHANGELOG)
Threaded tests/test_threaded.sh 14/14 14/14 MT compress/decompress
Post-quantum tests/test_pq.sh 10/10 10/10 --pq-sdk and legacy --pq
VaptVupt unit make test-vv 11/11 11/11 All modes + format_v2
NIST vectors make test-vectors 14/14 14/14 XXH64, SHA-256, ML-KEM (incl. internal self-test), X25519, AES, HMAC
ASAN/UBSan make test-asan clean clean plain + password + --pq-sdk; levels 1, 5, 9
Format mutation fuzz make fuzz-format-run 1000 iters, 0 crashes 1000 iters, 0 crashes ASAN-instrumented binary as victim
License audit make audit-licenses clean clean All SPDX correct (AGPL for Zupt, GPL for VaptVupt)
GCC strict warnings -Wall -Wextra -Wpedantic 0 0 C11 strict
Disk backup zupt disk backup/restore byte-exact sha256 5 MB image, all PATTERN markers preserved

Cumulative cases passing: 112 across 12 suites, both passes.

Defect found and fixed in this release cycle

VaptVupt 2.48.2 + format_v2 = 1 + VV_MODE_ULTRA_FAST produces output the decoder rejects with VV_ERR_OVERFLOW. The combination is not in VaptVupt's upstream test matrix (vaptvupt-2.48.2/tests/test_zupt_integration.c exercises format_v2 only with BALANCED and EXTREME). Caught by Zupt's own tests/regression.sh T17 (VaptVupt all levels) before release.

Workaround in src/vaptvupt_api.c: set opts.format_v2 = 0 for levels 12 (VV_MODE_ULTRA_FAST); leave format_v2 = 1 for levels 39. To be reported upstream; once VaptVupt validates the combination the guard can be lifted.

Defect found and fixed in this release cycle (build system)

The STALE_OBJS arch-safety guard in Makefile was comparing the canonical strings x86-64 (from file(1)) against x86_64 (from $(CC) -dumpmachine) and treating them as different architectures, causing every make invocation to wipe and rebuild every .o file even on a consistent host. Both sides are now normalised through tr -d '_-' | tr [:upper:] [:lower:] so the comparison succeeds on a same-arch tree and only fires when the tarball really did include cross-arch objects.

Packages produced and verified

All built from the same source tree, then exercised end-to-end (encrypted compress + extract + sha256 byte-compare) outside the build host's normal library search path:

Package File Size Roundtrip
Debian/Ubuntu zupt_2.2.3_amd64.deb 365 KB encrypted OK
RPM zupt-2.2.3-1.x86_64.rpm 468 KB encrypted OK
AppImage zupt-2.2.3-x86_64.AppImage 569 KB encrypted OK (extracted)
AppDir tarball zupt-2.2.3-x86_64.AppDir.tar.gz 377 KB encrypted OK
Generic Linux zupt-2.2.3-linux-x86_64.tar.gz 430 KB encrypted OK
Source zupt-2.2.3-source.tar.gz 736 KB rebuilt + full suite OK

All six produce byte-identical output on the test corpus (records.csv

  • 256 KB random binary + hello.txt).