release: restore ZUPT and harden source-only 5.2.2

This commit is contained in:
Cristian Cezar Moisés 2026-08-31 14:14:36 -03:00
commit ff99770bd0
205 changed files with 19627 additions and 13215 deletions

View file

@ -1,5 +1,5 @@
/*
* VaptVupt Zupt Integration API Implementation
* VaptVupt ZUPT Integration API Implementation
* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright (c) 2025-2026 Cristian Cezar Moisés
*
@ -7,7 +7,7 @@
* backup-optimized defaults for VaptVupt 2.65.0.
*
* Defaults applied here (per ZUPT_INTEGRATION.md, Sprint 122):
* - opts.checksum = 0 (Zupt's HMAC-SHA256 / AES-GCM-SIV outer
* - opts.checksum = 0 (ZUPT's HMAC-SHA256 / AES-GCM-SIV outer
* already authenticates the compressed
* bytes; XXH64 footer is redundant work
* and saves ~10% encode time)
@ -38,7 +38,7 @@ int64_t vvz_compress(const uint8_t *src, size_t src_len,
uint8_t *dst, size_t dst_cap, int level) {
vv_options_t opts;
vv_default_options(&opts);
opts.checksum = 0; /* outer Zupt MAC authenticates compressed bytes */
opts.checksum = 0; /* outer ZUPT MAC authenticates compressed bytes */
opts.compat_v246_5_decoder = 0; /* allow 4-stream Huffman literal coding */
if (level <= 2) {

View file

@ -1617,7 +1617,7 @@ static void est_huff_lengths(const uint32_t freq[NSYM], uint8_t len[NSYM]) {
/* Depth of each node = depth(parent) + 1; parents always have
* higher ids, so one reverse pass suffices. */
uint8_t depth[2 * NSYM];
depth[nn - 1] = 0;
memset(depth, 0, sizeof(depth));
for (int i = nn - 2; i >= 0; i--)
depth[i] = (uint8_t)(depth[parent[i]] + 1);
for (int i = 0; i < n; i++)

View file

@ -13,13 +13,13 @@
* then compresses well. The inverse runs on decode, before the bytes are
* handed back to the caller.
*
* This is a clean-room reimplementation of the well-known x86 branch-
* converter algorithm (the same transform used by 7-Zip/xz and described
* in the LZMA SDK). The algorithm is exactly reversible on ARBITRARY input
* it is a bijection, so applying the filter to non-x86 data and then
* inverting it reproduces the input byte-for-byte. That property is
* fuzz-verified in tests; do not "optimize" the masking logic without
* re-checking inverse(forward(x)) == x on random and adversarial inputs.
* The x86 transform is adapted from Igor Pavlov's public-domain LZMA SDK
* Bra86.c state machine. The exact SDK revision used by the original
* integration was not retained; see THIRD-PARTY-NOTICES.md. The algorithm is
* exactly reversible on arbitrary input: applying the filter and then its
* inverse reproduces the input byte-for-byte. Deterministic randomized and
* adversarial regressions exercise inverse(forward(x)) == x; do not "optimize"
* the masking logic without rerunning them.
*
* The buffer is transformed in place. `encoding` is non-zero for the
* forward (compress-side) transform, zero for the inverse (decode-side).

View file

@ -149,6 +149,7 @@ decode_block_tokens_impl(
* Exit boundary: max is 1 token + 14 lits + 3 offset + 6 match_ext = 24.
* Plus match_copy_32 may over-copy 32 bytes past the real end, so
* op needs at least 64 bytes of margin. */
#if VV_INLINE_AVX2
const uint8_t *const ip_safe = (ip_len > 48) ? (ip_end - 48) : ip;
uint8_t *const op_safe = (dst_cap > 72) ? (op_end - 72) : op;
@ -162,7 +163,6 @@ decode_block_tokens_impl(
* rejected. */
const uint32_t max_valid_off = (off_bytes == 2) ? 0xFFFF : 0xFFFFFF;
#if VV_INLINE_AVX2
/* PERF: two-phase fast path.
* Phase 1 (warmup): op hasn't advanced far enough to make any offset
* automatically valid. Do full offset validation per sequence.

View file

@ -901,8 +901,6 @@ typedef struct { uint32_t off; int32_t len; } opt_cand_t;
* captures most of the available win at zero added complexity, so this
* sprint ships it and defers the two-pass design until the window-size
* lever has been measured (matters more for nci-class fixtures). */
static inline int32_t opt_lit_price(void) { return 8; }
/* SPRINT 129: per-byte literal prices from the block's byte histogram.
* The flat-8 model (Sprint 44) was chosen as the best single constant,
* but the real literal coder delivers ~4-6 bits/byte on text and 7-8
@ -1127,6 +1125,10 @@ static int opt_collect(const matcher_t *m, const uint8_t *data,
static size_t compress_block_optimal(const uint8_t *src, size_t start_pos,
size_t block_len, uint8_t *dst,
size_t dst_cap, matcher_t *m, int min_match) {
if (min_match < 1 || block_len > (size_t)INT32_MAX ||
start_pos > (size_t)INT32_MAX - block_len)
return 0;
uint8_t *op = dst;
int32_t base = (int32_t)start_pos;
int32_t end = (int32_t)(start_pos + block_len);
@ -1249,8 +1251,8 @@ static size_t compress_block_optimal(const uint8_t *src, size_t start_pos,
* bounds worst-case work on repetitive data: instead of
* O(match_len) work per interior position, we jump over
* the whole match. */
int32_t use = best_len;
if (i + use > N) use = N - i;
int32_t remaining_len = N - i;
int32_t use = best_len > remaining_len ? remaining_len : best_len;
int32_t np = price[i] + opt_match_price(prep[i], best_off, use, of_bits);
int32_t j = i + use;
if (np < price[j]) {
@ -1270,11 +1272,14 @@ static size_t compress_block_optimal(const uint8_t *src, size_t start_pos,
}
for (int c = 0; c < nc; c++) {
int32_t mlen = cands[c].len; uint32_t moff = cands[c].off;
if (i + mlen > N) mlen = N - i;
int32_t remaining_len = N - i;
if (mlen > remaining_len) mlen = remaining_len;
if (mlen < min_match) continue;
for (int32_t L = mlen; L >= min_match; L--) {
if (L <= 0 || L > remaining_len) continue;
int32_t np = price[i] + opt_match_price(prep[i], moff, L, of_bits);
int32_t j = i + L;
size_t j = (size_t)i + (size_t)L;
if (j > (size_t)N) continue;
if (np < price[j]) {
price[j] = np; plen[j] = L; poff[j] = moff;
opt_rep_push(prep[j], prep[i], moff);

View file

@ -83,7 +83,9 @@ static void copy_match_scalar(uint8_t *dst, uint32_t offset, size_t length) {
#if defined(__x86_64__) || defined(_M_X64)
#ifdef __AVX2__
#include <cpuid.h>
#include <immintrin.h>
static int vv_has_avx2(void) {
unsigned int eax, ebx, ecx, edx;
@ -91,9 +93,6 @@ static int vv_has_avx2(void) {
return (ebx & (1 << 5)) != 0; /* AVX2 bit */
}
#ifdef __AVX2__
#include <immintrin.h>
static void copy_fast_avx2(uint8_t *dst, const uint8_t *src, size_t n) {
while (n >= 32) {
__m256i v = _mm256_loadu_si256((const __m256i *)src);

View file

@ -1,8 +1,9 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-License-Identifier: GPL-3.0-or-later AND BSD-2-Clause
* Copyright (c) 2012-2021 Yann Collet
*
* VaptVupt XXH64 checksum (simplified, standalone)
* Based on xxHash by Yann Collet. Public domain.
* Based on xxHash by Yann Collet. See LICENSE-BSD-2-Clause.
*/
#include "vaptvupt.h"

View file

@ -2,7 +2,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
* Copyright (c) 2025-2026 Cristian Cezar Moisés
* ZUPT - AES-256 Block Cipher (FIPS 197)
* Pure C, constant-time T-table implementation.
* Pure C, portable table-based implementation.
* FRAMA-C: ACSL-annotated (v2.0.0)
*/
#include "zupt.h"

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
*
* Detects AES-NI, PCLMUL, AVX2, SSE4.1 at runtime.

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
*
@ -13,31 +13,36 @@
#include "zupt.h"
#include "zupt_acsl.h"
#include "zupt_jasmin.h"
#include "zupt_cpuid.h" /* JASMIN-VERIFIED: AES-NI dispatch */
#include "zupt_cpuid.h" /* CPU dispatch for the optional Jasmin AES-NI path */
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#if defined(__linux__)
#include <sys/syscall.h>
#include <unistd.h>
#endif
#ifndef _WIN32
#include <fcntl.h>
#endif
/* ═══════════════════════════════════════════════════════════════════
* CONSTANT-TIME EQUALITY (single audited primitive)
*
* Returns 1 if the two buffers are equal, 0 otherwise, in time that
* depends only on `n` never on the contents or on where the first
* mismatch occurs. This is the one place the MAC-tag comparison is
* implemented; the three former inline byte-OR loops (the v1.6 strict
* Returns 1 if the two buffers are equal and 0 otherwise. Its source-level
* control flow and memory-access pattern are intended to depend only on `n`,
* not the contents or mismatch position. This is the one place the MAC-tag
* comparison is implemented; the three former inline byte-OR loops (the v1.6 strict
* decrypt path, the v1.4/v1.5 legacy v2 candidate, and the F-08 archive-
* integrity-trailer check) now all call here, so the property is audited
* and timing-tested in exactly one location (see tests/test_ct_timing).
*
* A timing leak in a MAC comparison is a forgery oracle: if "wrong on
* byte 0" returned faster than "wrong on byte 31", an attacker could
* recover a valid tag byte-by-byte. The accumulator is therefore folded
* with OR (no early exit) and read through a volatile sink so the
* compiler cannot reintroduce a short-circuit or branch.
* recover a valid tag byte-by-byte. The source therefore uses volatile byte
* loads and an OR accumulator with no explicit early exit. Exact generated
* code remains compiler- and platform-dependent and is covered by a
* dudect-style regression when its positive control is conclusive.
*
* CT-REQUIRED: no secret-dependent branch or memory access. */
int zupt_ct_memeq(const void *a, const void *b, size_t n) {
@ -79,7 +84,7 @@ void zupt_random_bytes(uint8_t *buf, size_t len) {
if (r == (ssize_t)len) return;
#endif
#endif
FILE *f = fopen("/dev/urandom", "rb");
FILE *f = zupt_fopen_path("/dev/urandom", "rb");
if (f) {
size_t nread = fread(buf, 1, len, f);
fclose(f);
@ -242,8 +247,8 @@ void zupt_aes256_ctr(const uint8_t key[32], const uint8_t nonce[16],
memcpy(counter, nonce, 16);
#ifdef ZUPT_USE_JASMIN
/* JASMIN-VERIFIED: AES-NI path — constant-time, no T-table leakage.
* The Jasmin-generated assembly uses VEX-encoded instructions (vaesenc,
/* OPTIONAL ASSEMBLY PATH: AES-NI implementation uses no table lookups.
* The checked-in assembly uses VEX-encoded instructions (vaesenc,
* vmovdqu, vpxor, etc.) which require BOTH AES-NI AND AVX support.
* Checking only has_aesni would SIGILL on CPUs with AES-NI but no AVX,
* or where the OS hasn't enabled XSAVE for YMM state. */
@ -571,6 +576,288 @@ uint8_t *zupt_decrypt_buffer(const zupt_keyring_t *kr,
return zupt_decrypt_buffer_aad(kr, pkg, pkglen, block_seq, NULL, 0, olen);
}
/* Write a key file without ever opening an existing directory entry. Private
* material is created mode 0600 on POSIX independently of the caller's umask.
* Windows uses CREATE_NEW and, for private material, a protected DACL granting
* access only to the current token's user SID. A failed write, flush, or close
* leaves the exclusively created incomplete or durability-uncertain file in
* place for the user to review and remove. This deliberately avoids a
* pathname-based cleanup after close:
* another process with write access to the parent directory could otherwise
* replace the entry and trick cleanup into deleting an unrelated file.
*
* This is intentionally shared with the optional pq-box module. Public-key
* output is exclusive too: besides avoiding symlink truncation, that prevents
* `keygen --pub -o private.key -k private.key` from destroying the only copy of
* a private key. */
int zupt_keyfile_write_new(const char *path, const uint8_t *data, size_t length,
int private_material) {
if (!path || path[0] == '\0' || (!data && length != 0) ||
(private_material != 0 && private_material != 1)) {
errno = EINVAL;
return -1;
}
#ifdef _WIN32
if (length > (size_t)MAXDWORD) {
errno = EFBIG;
return -1;
}
wchar_t *wide_path = zupt_win_utf8_to_wide_alloc(path);
if (!wide_path) {
errno = EINVAL;
return -1;
}
SECURITY_ATTRIBUTES attributes;
SECURITY_DESCRIPTOR descriptor;
SECURITY_ATTRIBUTES *attributes_ptr = NULL;
HMODULE advapi = NULL;
HANDLE token = NULL;
TOKEN_USER *token_user = NULL;
ACL *acl = NULL;
if (private_material) {
typedef BOOL (WINAPI *open_process_token_fn)(HANDLE, DWORD, PHANDLE);
typedef BOOL (WINAPI *get_token_information_fn)(
HANDLE, TOKEN_INFORMATION_CLASS, LPVOID, DWORD, PDWORD);
typedef DWORD (WINAPI *get_length_sid_fn)(PSID);
typedef BOOL (WINAPI *initialize_acl_fn)(PACL, DWORD, DWORD);
typedef BOOL (WINAPI *add_access_allowed_ace_fn)(
PACL, DWORD, DWORD, PSID);
typedef BOOL (WINAPI *initialize_security_descriptor_fn)(
PSECURITY_DESCRIPTOR, DWORD);
typedef BOOL (WINAPI *set_security_descriptor_dacl_fn)(
PSECURITY_DESCRIPTOR, BOOL, PACL, BOOL);
typedef BOOL (WINAPI *set_security_descriptor_control_fn)(
PSECURITY_DESCRIPTOR, SECURITY_DESCRIPTOR_CONTROL,
SECURITY_DESCRIPTOR_CONTROL);
advapi = LoadLibraryW(L"advapi32.dll");
if (!advapi) goto windows_security_error;
open_process_token_fn open_process_token =
(open_process_token_fn)(void (*)(void))
GetProcAddress(advapi, "OpenProcessToken");
get_token_information_fn get_token_information =
(get_token_information_fn)(void (*)(void))
GetProcAddress(advapi, "GetTokenInformation");
get_length_sid_fn get_length_sid =
(get_length_sid_fn)(void (*)(void))
GetProcAddress(advapi, "GetLengthSid");
initialize_acl_fn initialize_acl =
(initialize_acl_fn)(void (*)(void))
GetProcAddress(advapi, "InitializeAcl");
add_access_allowed_ace_fn add_access_allowed_ace =
(add_access_allowed_ace_fn)(void (*)(void))
GetProcAddress(advapi, "AddAccessAllowedAce");
initialize_security_descriptor_fn initialize_security_descriptor =
(initialize_security_descriptor_fn)(void (*)(void))
GetProcAddress(advapi, "InitializeSecurityDescriptor");
set_security_descriptor_dacl_fn set_security_descriptor_dacl =
(set_security_descriptor_dacl_fn)(void (*)(void))
GetProcAddress(advapi, "SetSecurityDescriptorDacl");
set_security_descriptor_control_fn set_security_descriptor_control =
(set_security_descriptor_control_fn)(void (*)(void))
GetProcAddress(advapi, "SetSecurityDescriptorControl");
if (!open_process_token || !get_token_information || !get_length_sid ||
!initialize_acl || !add_access_allowed_ace ||
!initialize_security_descriptor || !set_security_descriptor_dacl ||
!set_security_descriptor_control)
goto windows_security_error;
if (!open_process_token(GetCurrentProcess(), TOKEN_QUERY, &token))
goto windows_security_error;
DWORD token_size = 0;
(void)get_token_information(token, TokenUser, NULL, 0, &token_size);
if (token_size == 0) goto windows_security_error;
token_user = (TOKEN_USER *)malloc(token_size);
if (!token_user) {
errno = ENOMEM;
goto windows_security_cleanup;
}
if (!get_token_information(token, TokenUser, token_user, token_size,
&token_size))
goto windows_security_error;
DWORD sid_size = get_length_sid(token_user->User.Sid);
if (sid_size == 0 ||
sid_size > MAXDWORD - (DWORD)sizeof(ACL) -
(DWORD)sizeof(ACCESS_ALLOWED_ACE))
goto windows_security_error;
DWORD acl_size = (DWORD)sizeof(ACL) +
(DWORD)sizeof(ACCESS_ALLOWED_ACE) -
(DWORD)sizeof(DWORD) + sid_size;
acl = (ACL *)malloc(acl_size);
if (!acl) {
errno = ENOMEM;
goto windows_security_cleanup;
}
if (!initialize_acl(acl, acl_size, ACL_REVISION) ||
!add_access_allowed_ace(acl, ACL_REVISION, GENERIC_ALL,
token_user->User.Sid) ||
!initialize_security_descriptor(&descriptor,
SECURITY_DESCRIPTOR_REVISION) ||
!set_security_descriptor_dacl(&descriptor, TRUE, acl, FALSE) ||
!set_security_descriptor_control(&descriptor, SE_DACL_PROTECTED,
SE_DACL_PROTECTED))
goto windows_security_error;
attributes.nLength = sizeof(attributes);
attributes.lpSecurityDescriptor = &descriptor;
attributes.bInheritHandle = FALSE;
attributes_ptr = &attributes;
}
HANDLE handle = CreateFileW(
wide_path, GENERIC_WRITE | DELETE, 0, attributes_ptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT |
FILE_FLAG_WRITE_THROUGH,
NULL);
{
DWORD create_error = handle == INVALID_HANDLE_VALUE ? GetLastError() : 0;
free(acl);
free(token_user);
if (token) CloseHandle(token);
if (advapi) FreeLibrary(advapi);
acl = NULL;
token_user = NULL;
token = NULL;
advapi = NULL;
if (handle == INVALID_HANDLE_VALUE) {
free(wide_path);
errno = (create_error == ERROR_FILE_EXISTS ||
create_error == ERROR_ALREADY_EXISTS)
? EEXIST : EACCES;
return -1;
}
}
DWORD written = 0;
int failed = !WriteFile(handle, data, (DWORD)length, &written, NULL) ||
written != (DWORD)length || !FlushFileBuffers(handle);
if (!CloseHandle(handle)) failed = 1;
if (failed) {
free(wide_path);
errno = EIO;
return -1;
}
free(wide_path);
return 0;
windows_security_error:
errno = EACCES;
windows_security_cleanup:
free(acl);
free(token_user);
if (token) CloseHandle(token);
if (advapi) FreeLibrary(advapi);
free(wide_path);
return -1;
#else
int flags = O_WRONLY | O_CREAT | O_EXCL;
#ifdef O_NOFOLLOW
flags |= O_NOFOLLOW;
#endif
#ifdef O_CLOEXEC
flags |= O_CLOEXEC;
#endif
int descriptor = open(path, flags, private_material ? 0600 : 0666);
if (descriptor < 0) return -1;
int failed = 0;
int saved_errno = 0;
if (private_material && fchmod(descriptor, 0600) != 0) {
failed = 1;
saved_errno = errno;
}
#ifndef O_CLOEXEC
if (!failed) {
int descriptor_flags = fcntl(descriptor, F_GETFD);
if (descriptor_flags < 0 ||
fcntl(descriptor, F_SETFD, descriptor_flags | FD_CLOEXEC) != 0) {
failed = 1;
saved_errno = errno;
}
}
#endif
size_t offset = 0;
while (!failed && offset < length) {
ssize_t amount = write(descriptor, data + offset, length - offset);
if (amount < 0 && errno == EINTR) continue;
if (amount <= 0) {
failed = 1;
saved_errno = amount < 0 ? errno : EIO;
break;
}
offset += (size_t)amount;
}
if (!failed && fsync(descriptor) != 0) {
failed = 1;
saved_errno = errno;
}
if (close(descriptor) != 0 && !failed) {
failed = 1;
saved_errno = errno;
}
if (failed) {
errno = saved_errno ? saved_errno : EIO;
return -1;
}
return 0;
#endif
}
/* Load and structurally validate one native key blob before exposing any key
* bytes to a caller. The historical writers have always serialized the XXH64
* trailer with zupt_le64_put(), so readers deliberately interpret it as
* little-endian on every host. This newly enforces the existing format rather
* than changing it; valid v1 files remain byte-for-byte compatible. */
static int load_native_key_blob(const char *path, const char magic[4],
uint8_t version, uint8_t private_flag,
size_t public_file_size,
size_t private_file_size,
int require_private,
uint8_t *buffer, size_t buffer_capacity,
size_t *file_size) {
if (!path || !buffer || !file_size || public_file_size < 16 ||
private_file_size <= public_file_size ||
private_file_size > buffer_capacity) {
errno = EINVAL;
return -1;
}
*file_size = 0;
FILE *stream = zupt_fopen_path(path, "rb");
if (!stream) return -1;
size_t length = fread(buffer, 1, private_file_size, stream);
int trailing = fgetc(stream);
int failed = ferror(stream) != 0;
if (fclose(stream) != 0) failed = 1;
int has_private = length >= 6 && buffer[5] == private_flag;
size_t expected_size = has_private ? private_file_size : public_file_size;
if (failed || trailing != EOF ||
(length != public_file_size && length != private_file_size) ||
memcmp(buffer, magic, 4) != 0 || buffer[4] != version ||
(buffer[5] != 0 && buffer[5] != private_flag) ||
buffer[6] != 0 || buffer[7] != 0 ||
length != expected_size || (require_private && !has_private)) {
zupt_secure_wipe(buffer, buffer_capacity);
errno = EINVAL;
return -1;
}
uint64_t stored_checksum = zupt_le64_get(buffer + length - 8);
uint64_t computed_checksum = zupt_xxh64(buffer, length - 8, 0);
if (stored_checksum != computed_checksum) {
zupt_secure_wipe(buffer, buffer_capacity);
errno = EINVAL;
return -1;
}
*file_size = length;
return 0;
}
/* ═══════════════════════════════════════════════════════════════════
* HYBRID POST-QUANTUM KEM: ML-KEM-768 + X25519 (v0.7.0)
*
@ -598,26 +885,25 @@ uint8_t *zupt_decrypt_buffer(const zupt_keyring_t *kr,
#define ZKEY_FLAG_PRIVATE 0x01
#define ZKEY_PUB_SIZE (8 + 1184 + 32) /* header + ml_kem_pk + x25519_pk */
#define ZKEY_PRIV_SIZE (8 + 1184 + 32 + 2400 + 32) /* + ml_kem_sk + x25519_sk */
#define ZKEY_CHECKSUM_SIZE 8
#define ZKEY_PUB_FILE_SIZE (ZKEY_PUB_SIZE + ZKEY_CHECKSUM_SIZE)
#define ZKEY_PRIV_FILE_SIZE (ZKEY_PRIV_SIZE + ZKEY_CHECKSUM_SIZE)
int zupt_hybrid_keygen(const char *keyfile) {
uint8_t ml_pk[MLKEM_PUBLICKEYBYTES], ml_sk[MLKEM_SECRETKEYBYTES];
uint8_t x_sk[32], x_pk[32];
uint8_t ml_pk[MLKEM_PUBLICKEYBYTES] = {0};
uint8_t ml_sk[MLKEM_SECRETKEYBYTES] = {0};
uint8_t x_sk[32] = {0}, x_pk[32] = {0};
uint8_t buf[ZKEY_PRIV_FILE_SIZE] = {0};
const size_t total = ZKEY_PRIV_SIZE;
int result = -1;
/* Generate ML-KEM-768 keypair */
if (zupt_mlkem768_keygen(ml_pk, ml_sk) != 0) return -1;
if (zupt_mlkem768_keygen(ml_pk, ml_sk) != 0) goto out;
/* Generate X25519 keypair */
zupt_random_bytes(x_sk, 32);
zupt_x25519_base(x_pk, x_sk);
/* Write private key file */
FILE *f = fopen(keyfile, "wb");
if (!f) return -1;
size_t total = ZKEY_PRIV_SIZE;
uint8_t *buf = (uint8_t *)calloc(total + 8, 1); /* +8 for checksum */
if (!buf) { fclose(f); return -1; }
memcpy(buf, ZKEY_MAGIC, 4);
buf[4] = ZKEY_VERSION;
buf[5] = ZKEY_FLAG_PRIVATE;
@ -628,85 +914,77 @@ int zupt_hybrid_keygen(const char *keyfile) {
memcpy(buf + 8 + 1184 + 32 + 2400, x_sk, 32);
/* Checksum */
uint64_t ck = zupt_xxh64(buf, total, 0);
zupt_le64_put(buf + total, ck);
size_t written = fwrite(buf, 1, total + 8, f);
fclose(f);
zupt_le64_put(buf + total, zupt_xxh64(buf, total, 0));
result = zupt_keyfile_write_new(keyfile, buf, sizeof(buf), 1);
out:
zupt_secure_wipe(ml_sk, sizeof(ml_sk));
zupt_secure_wipe(x_sk, 32);
zupt_secure_wipe(buf, total + 8);
free(buf);
return (written == total + 8) ? 0 : -1;
zupt_secure_wipe(x_sk, sizeof(x_sk));
zupt_secure_wipe(buf, sizeof(buf));
return result;
}
int zupt_hybrid_export_pubkey(const char *privfile, const char *pubfile) {
FILE *f = fopen(privfile, "rb");
if (!f) return -1;
uint8_t private_blob[ZKEY_PRIV_FILE_SIZE] = {0};
uint8_t public_blob[ZKEY_PUB_FILE_SIZE] = {0};
size_t private_size = 0;
const size_t total = ZKEY_PUB_SIZE;
int result = -1;
if (load_native_key_blob(privfile, ZKEY_MAGIC, ZKEY_VERSION,
ZKEY_FLAG_PRIVATE, ZKEY_PUB_FILE_SIZE,
ZKEY_PRIV_FILE_SIZE, 1, private_blob,
sizeof(private_blob), &private_size) != 0)
goto out;
uint8_t hdr[8];
if (fread(hdr, 1, 8, f) != 8 || memcmp(hdr, ZKEY_MAGIC, 4) != 0 ||
!(hdr[5] & ZKEY_FLAG_PRIVATE)) {
fclose(f); return -1;
}
memcpy(public_blob, ZKEY_MAGIC, 4);
public_blob[4] = ZKEY_VERSION;
public_blob[5] = 0; /* no private key */
public_blob[6] = public_blob[7] = 0;
memcpy(public_blob + 8, private_blob + 8, 1184 + 32);
uint8_t pk_data[1184 + 32];
if (fread(pk_data, 1, 1216, f) != 1216) { fclose(f); return -1; }
fclose(f);
zupt_le64_put(public_blob + total,
zupt_xxh64(public_blob, total, 0));
/* Write public key file */
FILE *out = fopen(pubfile, "wb");
if (!out) return -1;
size_t total = ZKEY_PUB_SIZE;
uint8_t buf[ZKEY_PUB_SIZE + 8];
memcpy(buf, ZKEY_MAGIC, 4);
buf[4] = ZKEY_VERSION;
buf[5] = 0; /* no private key */
buf[6] = buf[7] = 0;
memcpy(buf + 8, pk_data, 1216);
uint64_t ck = zupt_xxh64(buf, total, 0);
zupt_le64_put(buf + total, ck);
size_t written = fwrite(buf, 1, total + 8, out);
fclose(out);
return (written == total + 8) ? 0 : -1;
result = zupt_keyfile_write_new(pubfile, public_blob,
sizeof(public_blob), 0);
out:
zupt_secure_wipe(private_blob, sizeof(private_blob));
zupt_secure_wipe(public_blob, sizeof(public_blob));
return result;
}
/* Read public key from a .zupt-key file (works for both pub and priv files) */
static int read_pubkey(const char *path, uint8_t ml_pk[1184], uint8_t x_pk[32]) {
FILE *f = fopen(path, "rb");
if (!f) return -1;
uint8_t hdr[8];
if (fread(hdr, 1, 8, f) != 8 || memcmp(hdr, ZKEY_MAGIC, 4) != 0) {
fclose(f); return -1;
}
if (fread(ml_pk, 1, 1184, f) != 1184) { fclose(f); return -1; }
if (fread(x_pk, 1, 32, f) != 32) { fclose(f); return -1; }
fclose(f);
uint8_t blob[ZKEY_PRIV_FILE_SIZE] = {0};
size_t file_size = 0;
/* Accept a structurally valid private file here for compatibility: older
* releases explicitly allowed encryption directly with either ZKEY role. */
if (load_native_key_blob(path, ZKEY_MAGIC, ZKEY_VERSION,
ZKEY_FLAG_PRIVATE, ZKEY_PUB_FILE_SIZE,
ZKEY_PRIV_FILE_SIZE, 0, blob, sizeof(blob),
&file_size) != 0)
return -1;
memcpy(ml_pk, blob + 8, 1184);
memcpy(x_pk, blob + 8 + 1184, 32);
zupt_secure_wipe(blob, sizeof(blob));
return 0;
}
/* Read private key from a .zupt-key file */
static int read_privkey(const char *path, uint8_t ml_pk[1184], uint8_t x_pk[32],
uint8_t ml_sk[2400], uint8_t x_sk[32]) {
FILE *f = fopen(path, "rb");
if (!f) return -1;
uint8_t hdr[8];
if (fread(hdr, 1, 8, f) != 8 || memcmp(hdr, ZKEY_MAGIC, 4) != 0 ||
!(hdr[5] & ZKEY_FLAG_PRIVATE)) {
fclose(f); return -1;
}
if (fread(ml_pk, 1, 1184, f) != 1184) { fclose(f); return -1; }
if (fread(x_pk, 1, 32, f) != 32) { fclose(f); return -1; }
if (fread(ml_sk, 1, 2400, f) != 2400) { fclose(f); return -1; }
if (fread(x_sk, 1, 32, f) != 32) { fclose(f); return -1; }
fclose(f);
uint8_t blob[ZKEY_PRIV_FILE_SIZE] = {0};
size_t file_size = 0;
if (load_native_key_blob(path, ZKEY_MAGIC, ZKEY_VERSION,
ZKEY_FLAG_PRIVATE, ZKEY_PUB_FILE_SIZE,
ZKEY_PRIV_FILE_SIZE, 1, blob, sizeof(blob),
&file_size) != 0)
return -1;
memcpy(ml_pk, blob + 8, 1184);
memcpy(x_pk, blob + 8 + 1184, 32);
memcpy(ml_sk, blob + 8 + 1184 + 32, 2400);
memcpy(x_sk, blob + 8 + 1184 + 32 + 2400, 32);
zupt_secure_wipe(blob, sizeof(blob));
return 0;
}
@ -912,18 +1190,18 @@ int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
#define ZPQK_HDR 8
#define ZPQK_PUB_SIZE (ZPQK_HDR + 1184)
#define ZPQK_PRIV_SIZE (ZPQK_HDR + 1184 + 2400)
#define ZPQK_CHECKSUM_SIZE 8
#define ZPQK_PUB_FILE_SIZE (ZPQK_PUB_SIZE + ZPQK_CHECKSUM_SIZE)
#define ZPQK_PRIV_FILE_SIZE (ZPQK_PRIV_SIZE + ZPQK_CHECKSUM_SIZE)
#define ZUPT_PQ_ONLY_LABEL "ZUPT-PQ-ONLY-v1" /* 15 bytes */
int zupt_pq_keygen(const char *keyfile) {
uint8_t ml_pk[MLKEM_PUBLICKEYBYTES], ml_sk[MLKEM_SECRETKEYBYTES];
if (zupt_mlkem768_keygen(ml_pk, ml_sk) != 0) return -1;
FILE *f = fopen(keyfile, "wb");
if (!f) { zupt_secure_wipe(ml_sk, sizeof(ml_sk)); return -1; }
size_t total = ZPQK_PRIV_SIZE;
uint8_t *buf = (uint8_t *)calloc(total + 8, 1);
if (!buf) { fclose(f); zupt_secure_wipe(ml_sk, sizeof(ml_sk)); return -1; }
uint8_t ml_pk[MLKEM_PUBLICKEYBYTES] = {0};
uint8_t ml_sk[MLKEM_SECRETKEYBYTES] = {0};
uint8_t buf[ZPQK_PRIV_FILE_SIZE] = {0};
const size_t total = ZPQK_PRIV_SIZE;
int result = -1;
if (zupt_mlkem768_keygen(ml_pk, ml_sk) != 0) goto out;
memcpy(buf, ZPQK_MAGIC, 4);
buf[4] = ZPQK_VERSION;
@ -932,65 +1210,68 @@ int zupt_pq_keygen(const char *keyfile) {
memcpy(buf + ZPQK_HDR, ml_pk, 1184);
memcpy(buf + ZPQK_HDR + 1184, ml_sk, 2400);
uint64_t ck = zupt_xxh64(buf, total, 0);
zupt_le64_put(buf + total, ck);
size_t written = fwrite(buf, 1, total + 8, f);
if (fclose(f) != 0) written = 0;
zupt_le64_put(buf + total, zupt_xxh64(buf, total, 0));
result = zupt_keyfile_write_new(keyfile, buf, sizeof(buf), 1);
out:
zupt_secure_wipe(ml_sk, sizeof(ml_sk));
zupt_secure_wipe(buf, total + 8);
free(buf);
return (written == total + 8) ? 0 : -1;
zupt_secure_wipe(buf, sizeof(buf));
return result;
}
int zupt_pq_export_pubkey(const char *privfile, const char *pubfile) {
FILE *f = fopen(privfile, "rb");
if (!f) return -1;
uint8_t hdr[ZPQK_HDR];
if (fread(hdr, 1, ZPQK_HDR, f) != ZPQK_HDR || memcmp(hdr, ZPQK_MAGIC, 4) != 0 ||
!(hdr[5] & ZPQK_FLAG_PRIVATE)) { fclose(f); return -1; }
uint8_t ml_pk[1184];
if (fread(ml_pk, 1, 1184, f) != 1184) { fclose(f); return -1; }
fclose(f);
uint8_t private_blob[ZPQK_PRIV_FILE_SIZE] = {0};
uint8_t public_blob[ZPQK_PUB_FILE_SIZE] = {0};
size_t private_size = 0;
const size_t total = ZPQK_PUB_SIZE;
int result = -1;
if (load_native_key_blob(privfile, ZPQK_MAGIC, ZPQK_VERSION,
ZPQK_FLAG_PRIVATE, ZPQK_PUB_FILE_SIZE,
ZPQK_PRIV_FILE_SIZE, 1, private_blob,
sizeof(private_blob), &private_size) != 0)
goto out;
FILE *out = fopen(pubfile, "wb");
if (!out) return -1;
size_t total = ZPQK_PUB_SIZE;
uint8_t buf[ZPQK_PUB_SIZE + 8];
memcpy(buf, ZPQK_MAGIC, 4);
buf[4] = ZPQK_VERSION;
buf[5] = 0;
buf[6] = buf[7] = 0;
memcpy(buf + ZPQK_HDR, ml_pk, 1184);
uint64_t ck = zupt_xxh64(buf, total, 0);
zupt_le64_put(buf + total, ck);
size_t written = fwrite(buf, 1, total + 8, out);
if (fclose(out) != 0) written = 0;
return (written == total + 8) ? 0 : -1;
memcpy(public_blob, ZPQK_MAGIC, 4);
public_blob[4] = ZPQK_VERSION;
public_blob[5] = 0;
public_blob[6] = public_blob[7] = 0;
memcpy(public_blob + ZPQK_HDR, private_blob + ZPQK_HDR, 1184);
zupt_le64_put(public_blob + total,
zupt_xxh64(public_blob, total, 0));
result = zupt_keyfile_write_new(pubfile, public_blob,
sizeof(public_blob), 0);
out:
zupt_secure_wipe(private_blob, sizeof(private_blob));
zupt_secure_wipe(public_blob, sizeof(public_blob));
return result;
}
static int read_pq_pubkey(const char *path, uint8_t ml_pk[1184]) {
FILE *f = fopen(path, "rb");
if (!f) return -1;
uint8_t hdr[ZPQK_HDR];
if (fread(hdr, 1, ZPQK_HDR, f) != ZPQK_HDR || memcmp(hdr, ZPQK_MAGIC, 4) != 0) {
fclose(f); return -1;
}
if (fread(ml_pk, 1, 1184, f) != 1184) { fclose(f); return -1; }
fclose(f);
uint8_t blob[ZPQK_PRIV_FILE_SIZE] = {0};
size_t file_size = 0;
/* Preserve the historical convenience of encrypting with a valid private
* ZPQK file while still validating its private role, size, and checksum. */
if (load_native_key_blob(path, ZPQK_MAGIC, ZPQK_VERSION,
ZPQK_FLAG_PRIVATE, ZPQK_PUB_FILE_SIZE,
ZPQK_PRIV_FILE_SIZE, 0, blob, sizeof(blob),
&file_size) != 0)
return -1;
memcpy(ml_pk, blob + ZPQK_HDR, 1184);
zupt_secure_wipe(blob, sizeof(blob));
return 0;
}
static int read_pq_privkey(const char *path, uint8_t ml_pk[1184], uint8_t ml_sk[2400]) {
FILE *f = fopen(path, "rb");
if (!f) return -1;
uint8_t hdr[ZPQK_HDR];
if (fread(hdr, 1, ZPQK_HDR, f) != ZPQK_HDR || memcmp(hdr, ZPQK_MAGIC, 4) != 0 ||
!(hdr[5] & ZPQK_FLAG_PRIVATE)) { fclose(f); return -1; }
if (fread(ml_pk, 1, 1184, f) != 1184) { fclose(f); return -1; }
if (fread(ml_sk, 1, 2400, f) != 2400) { fclose(f); return -1; }
fclose(f);
uint8_t blob[ZPQK_PRIV_FILE_SIZE] = {0};
size_t file_size = 0;
if (load_native_key_blob(path, ZPQK_MAGIC, ZPQK_VERSION,
ZPQK_FLAG_PRIVATE, ZPQK_PUB_FILE_SIZE,
ZPQK_PRIV_FILE_SIZE, 1, blob, sizeof(blob),
&file_size) != 0)
return -1;
memcpy(ml_pk, blob + ZPQK_HDR, 1184);
memcpy(ml_sk, blob + ZPQK_HDR + 1184, 2400);
zupt_secure_wipe(blob, sizeof(blob));
return 0;
}

View file

@ -3,12 +3,12 @@
* Copyright (c) 2026 Cristian Cezar Moisés
*
* zupt_crypto_pqbox.c ZUPT_ENC_PQ_BOX_V1 (0x05): hybrid PQ sealed-box
* recipient encryption backed by vendored libpqvaptvupt (v0.6.0).
* recipient encryption backed by the optional system libpqvaptvupt.
*
* Why a third PQ mode:
* - legacy --pq (0x02) combines the ML-KEM and X25519 shared secrets
* with XOR+SHA3 functional, but not the modern recommendation;
* - --pq-sdk (0x03) is libzuptsdk's v2 envelope (kept for back-compat);
* - --pq-sdk (0x03) is libvuptsdk's v2 envelope (kept for back-compat);
* - --pq-box (0x05) uses libpqvaptvupt's sealed box, which combines the
* two KEM secrets through HKDF-SHA256 Extract/Expand with a
* domain-separating info string ("pqvv-seal-v1") the construction
@ -47,19 +47,23 @@
static int pqbox_write_keyfile(const char *path, char role,
const uint8_t *key, size_t klen) {
FILE *f = fopen(path, "wb");
if (!f) return -1;
int ok = fwrite(PQBOX_MAGIC, 1, PQBOX_MAGIC_LEN, f) == PQBOX_MAGIC_LEN
&& fputc(role, f) != EOF
&& fwrite(key, 1, klen, f) == klen;
if (fclose(f) != 0) ok = 0;
return ok ? 0 : -1;
if (klen > SIZE_MAX - PQBOX_HDR_LEN) return -1;
size_t length = PQBOX_HDR_LEN + klen;
uint8_t *blob = (uint8_t *)malloc(length);
if (!blob) return -1;
memcpy(blob, PQBOX_MAGIC, PQBOX_MAGIC_LEN);
blob[PQBOX_MAGIC_LEN] = (uint8_t)role;
memcpy(blob + PQBOX_HDR_LEN, key, klen);
int result = zupt_keyfile_write_new(path, blob, length, role == 'S');
if (role == 'S') zupt_secure_wipe(blob, length);
free(blob);
return result;
}
/* Reads and validates a key file. Returns 0 and fills `key` on success. */
static int pqbox_read_keyfile(const char *path, char role,
uint8_t *key, size_t klen) {
FILE *f = fopen(path, "rb");
FILE *f = zupt_fopen_path(path, "rb");
if (!f) return -1;
uint8_t hdr[PQBOX_HDR_LEN];
int ok = fread(hdr, 1, PQBOX_HDR_LEN, f) == PQBOX_HDR_LEN
@ -67,14 +71,18 @@ static int pqbox_read_keyfile(const char *path, char role,
&& hdr[PQBOX_MAGIC_LEN] == (uint8_t)role
&& fread(key, 1, klen, f) == klen
&& fgetc(f) == EOF; /* exact size — no trailing bytes */
fclose(f);
if (fclose(f) != 0) ok = 0;
if (!ok && role == 'S') zupt_secure_wipe(key, klen);
return ok ? 0 : -1;
}
int zupt_pqbox_keygen(const char *privkeyfile, const char *pubkeyfile) {
uint8_t pk[PQVV_PUBLICKEYBYTES];
uint8_t sk[PQVV_SECRETKEYBYTES];
if (pqvv_keygen(pk, sk) != PQVV_OK) return -1;
uint8_t pk[PQVV_PUBLICKEYBYTES] = {0};
uint8_t sk[PQVV_SECRETKEYBYTES] = {0};
if (pqvv_keygen(pk, sk) != PQVV_OK) {
zupt_secure_wipe(sk, sizeof(sk));
return -1;
}
int rc = 0;
if (pqbox_write_keyfile(privkeyfile, 'S', sk, sizeof(sk)) != 0) rc = -1;
@ -146,7 +154,7 @@ int zupt_pqbox_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
if (sealed_len != PQBOX_SEALED_SESSION || payload_len < 5 + (size_t)sealed_len)
return -1;
uint8_t sk[PQVV_SECRETKEYBYTES];
uint8_t sk[PQVV_SECRETKEYBYTES] = {0};
if (pqbox_read_keyfile(privkeyfile, 'S', sk, sizeof(sk)) != 0) {
fprintf(stderr, "Error: '%s' is not a pq-box SECRET key file.\n", privkeyfile);
return -1;
@ -184,16 +192,16 @@ int zupt_pqbox_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
#else /* !ZUPT_WITH_PQBOX */
/* Source-only build (no vendored libpqvaptvupt binary). The --pq-box sealed-box
* mode is unavailable; use native --pq (ML-KEM-768 + X25519) instead, or rebuild
* with `make WITH_SDK=1` (requires the vendored libpqvaptvupt). */
/* Baseline build without the optional system libpqvaptvupt. The --pq-box
* sealed-box mode is unavailable; use native --pq (ML-KEM-768 + X25519)
* instead, or rebuild with WITH_PQBOX=1 and the system development package. */
#include <stdio.h>
static int pqbox_unavailable(const char *what) {
fprintf(stderr,
"Error: this build has no libpqvaptvupt support, so %s is unavailable.\n"
" Use native --pq (ML-KEM-768 + X25519) instead, or rebuild with "
"'make WITH_SDK=1'.\n", what);
"'make WITH_PQBOX=1' and the system development package.\n", what);
return -1;
}

View file

@ -1,7 +1,7 @@
/* zupt_crypto_sdk.c — SDK-backed crypto for zupt v2.2+ archives.
*
* Replaces the legacy zupt_crypto.c hybrid path (XOR+SHA3-512 combiner) with
* libzuptsdk's HKDF-SHA3-256 combiner + key commitment + HPKE binding +
* libvuptsdk's HKDF-SHA3-256 combiner + key commitment + HPKE binding +
* anti-fault decap. Per-block AEAD switches from AES-256-CTR + HMAC-SHA256
* to XChaCha20-Poly1305 (default) or AES-256-SIV (nonce-misuse-resistant).
*
@ -210,7 +210,7 @@ int zupt_sdk_password_decrypt_init(zupt_keyring_t *kr, const char *password,
/* v3.4.0 self-describing KDF profile. Absent (33-byte header) means
* the implicit legacy profile; present (>=34 bytes) names it
* explicitly. Both currently map to the same libzuptsdk MODERATE
* explicitly. Both currently map to the same libvuptsdk MODERATE
* Argon2id derivation, so the key is identical and old archives keep
* decrypting. An unrecognised profile is refused rather than guessed
* better a clear failure than a wrong key derivation. */
@ -249,20 +249,20 @@ int zupt_sdk_password_decrypt_init(zupt_keyring_t *kr, const char *password,
#else /* !ZUPT_WITH_SDK */
/* Source-only build (no vendored libzuptsdk binary). The SDK-backed modes
/* Baseline build without the optional system libvuptsdk. The SDK-backed modes
* --pq-sdk and the Argon2id default password KDF are unavailable. These
* stubs let the project build and link from source with no prebuilt library;
* callers fall back to native crypto (PBKDF2-SHA256 password KDF, native
* ML-KEM-768 + X25519 via --pq) or report the requested mode as unsupported.
* Rebuild with `make WITH_SDK=1` (requires the vendored libzuptsdk) to enable. */
* Rebuild with WITH_SDK=1 and the system development package to enable. */
#include <stdio.h>
static int sdk_unavailable(const char *what) {
fprintf(stderr,
"Error: this build has no libzuptsdk support, so %s is unavailable.\n"
"Error: this build has no libvuptsdk support, so %s is unavailable.\n"
" Use native crypto instead (password mode uses PBKDF2-SHA256; "
"--pq uses ML-KEM-768 + X25519),\n"
" or rebuild with 'make WITH_SDK=1'.\n", what);
" or rebuild with 'make WITH_SDK=1' and the system development package.\n", what);
return -1;
}

View file

@ -1,11 +1,11 @@
/*
* SPDX-License-Identifier: AGPL-3.0-or-later
* Copyright (c) 2025-2026 Cristian Cezar Moisés
* Zupt v2.1.5 Block-Level Deduplication
* ZUPT v2.1.5 Block-Level Deduplication
* Copyright (c) 2026 Cristian Cezar Moises AGPL-3.0-or-later
*
* Eliminates redundant data blocks before compression using XXH64
* fingerprinting with full content verification on match.
* fingerprinting with an independent SHA-256/128 verification on match.
*
* Architecture:
* Source XXH64 fingerprint Hash table lookup Match?
@ -16,13 +16,14 @@
* capped at ZUPT_DEDUP_MAX_ENTRIES (2M entries = ~48MB RAM).
*
* Security:
* - XXH64 is not collision-resistant, so we verify full content
* on hash match before emitting a reference.
* - XXH64 is not collision-resistant, so a reference also requires an
* independent 128-bit prefix of SHA-256 to match.
* - Hash table memory is securely wiped on free.
* - Dedup operates on plaintext before encryption.
* - References are intra-archive offsets only.
*/
#include "zupt.h"
#include "zupt_internal.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
@ -31,8 +32,10 @@
typedef struct {
uint64_t fingerprint; /* XXH64 of the block content */
uint64_t block_offset; /* File offset where the block was written */
uint8_t digest[ZUPT_DEDUP_DIGEST_SIZE]; /* independent SHA-256 prefix */
uint32_t block_size; /* Uncompressed size of the block */
uint32_t occupied; /* 0 = empty, 1 = occupied */
uint64_t aad_seq; /* Logical position used to authenticate DATA */
} zupt_dedup_entry_t;
/* Dedup context */
@ -75,23 +78,25 @@ void zupt_dedup_free(zupt_dedup_ctx_t *ctx) {
* Look up a block in the dedup index.
* Returns 1 if a match is found (sets *ref_offset), 0 if not found.
*
* The caller must verify content equality before trusting the match
* (XXH64 is fast but not collision-resistant). The content verification
* is done by the caller who has access to the archive FILE* to seek
* and re-read the original block.
* XXH64 selects the probe chain; the independent SHA-256 prefix must also
* match before the stored offset is returned.
*/
int zupt_dedup_lookup(zupt_dedup_ctx_t *ctx, uint64_t fingerprint,
uint64_t *ref_offset, uint32_t *ref_size) {
if (!ctx || !ctx->table) return 0;
int zupt_dedup_lookup_secure(zupt_dedup_ctx_t *ctx, uint64_t fingerprint,
const uint8_t digest[ZUPT_DEDUP_DIGEST_SIZE],
uint64_t *ref_offset, uint32_t *ref_size,
uint64_t *ref_aad_seq) {
if (!ctx || !ctx->table || !digest) return 0;
uint32_t idx = (uint32_t)(fingerprint % ctx->capacity);
for (uint32_t i = 0; i < 64; i++) { /* Max 64 probes */
uint32_t slot = (idx + i) % ctx->capacity;
zupt_dedup_entry_t *e = &ctx->table[slot];
if (!e->occupied) return 0; /* Empty slot = not found */
if (e->fingerprint == fingerprint) {
if (e->fingerprint == fingerprint &&
memcmp(e->digest, digest, ZUPT_DEDUP_DIGEST_SIZE) == 0) {
if (ref_offset) *ref_offset = e->block_offset;
if (ref_size) *ref_size = e->block_size;
if (ref_aad_seq) *ref_aad_seq = e->aad_seq;
return 1;
}
}
@ -102,9 +107,11 @@ int zupt_dedup_lookup(zupt_dedup_ctx_t *ctx, uint64_t fingerprint,
* Insert a block into the dedup index.
* Returns 1 on success, 0 if table is full.
*/
int zupt_dedup_insert(zupt_dedup_ctx_t *ctx, uint64_t fingerprint,
uint64_t block_offset, uint32_t block_size) {
if (!ctx || !ctx->table) return 0;
int zupt_dedup_insert_secure(zupt_dedup_ctx_t *ctx, uint64_t fingerprint,
const uint8_t digest[ZUPT_DEDUP_DIGEST_SIZE],
uint64_t block_offset, uint32_t block_size,
uint64_t block_aad_seq) {
if (!ctx || !ctx->table || !digest) return 0;
if (ctx->count >= ctx->capacity * 3 / 4) return 0; /* 75% load factor limit */
uint32_t idx = (uint32_t)(fingerprint % ctx->capacity);
@ -114,7 +121,9 @@ int zupt_dedup_insert(zupt_dedup_ctx_t *ctx, uint64_t fingerprint,
if (!e->occupied) {
e->fingerprint = fingerprint;
e->block_offset = block_offset;
memcpy(e->digest, digest, ZUPT_DEDUP_DIGEST_SIZE);
e->block_size = block_size;
e->aad_seq = block_aad_seq;
e->occupied = 1;
ctx->count++;
return 1;
@ -123,6 +132,22 @@ int zupt_dedup_insert(zupt_dedup_ctx_t *ctx, uint64_t fingerprint,
return 0; /* Probe limit */
}
/* Preserve the published 5.2.1 symbols and signatures. First-party archive
* writers use the secure variants above with an independent digest. */
int zupt_dedup_lookup(zupt_dedup_ctx_t *ctx, uint64_t fingerprint,
uint64_t *ref_offset, uint32_t *ref_size) {
static const uint8_t legacy_digest[ZUPT_DEDUP_DIGEST_SIZE] = {0};
return zupt_dedup_lookup_secure(ctx, fingerprint, legacy_digest,
ref_offset, ref_size, NULL);
}
int zupt_dedup_insert(zupt_dedup_ctx_t *ctx, uint64_t fingerprint,
uint64_t block_offset, uint32_t block_size) {
static const uint8_t legacy_digest[ZUPT_DEDUP_DIGEST_SIZE] = {0};
return zupt_dedup_insert_secure(ctx, fingerprint, legacy_digest,
block_offset, block_size, 0);
}
void zupt_dedup_record_hit(zupt_dedup_ctx_t *ctx, uint64_t saved_bytes) {
if (!ctx) return;
ctx->blocks_deduped++;
@ -176,3 +201,121 @@ int zupt_dedup_write_ref(FILE *out, uint64_t ref_offset,
if (fwrite(payload, 1, 8, out) != 8) return -1;
return 0;
}
/* New encrypted archives authenticate the otherwise mutable reference offset.
* The logical size/checksum remain in the frame preface and are included in
* v1.6 preface AAD. The encrypted payload binds both the intra-archive offset
* and the logical AAD sequence used by the referenced DATA frame; the
* reference frame itself uses its own logical position as AAD. */
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) {
uint8_t reference[16];
zupt_le64_put(reference, ref_offset);
zupt_le64_put(reference + 8, referenced_aad_seq);
const uint8_t *payload = reference;
size_t payload_size = keyring && keyring->active ? sizeof(reference) : 8u;
uint16_t block_flags = 0;
uint8_t *encrypted = NULL;
if (keyring && keyring->active) {
size_t encrypted_size = 0;
if (keyring->use_preface_aad) {
uint8_t preface[ZUPT_PREFACE_AAD_LEN];
uint64_t predicted_size = 16u + sizeof(reference) + 32u;
zupt_serialize_preface_aad_scalars(
ZUPT_BLOCK_DEDUP_REF, ZUPT_CODEC_STORE,
ZUPT_BFLAG_ENCRYPTED, orig_size, predicted_size,
orig_checksum, preface);
encrypted = zupt_encrypt_buffer_aad(
keyring, reference, sizeof(reference), current_aad_seq,
preface, sizeof(preface), &encrypted_size);
zupt_secure_wipe(preface, sizeof(preface));
} else {
encrypted = zupt_encrypt_buffer(keyring, reference,
sizeof(reference), current_aad_seq,
&encrypted_size);
}
if (!encrypted) return -1;
payload = encrypted;
payload_size = encrypted_size;
block_flags = ZUPT_BFLAG_ENCRYPTED;
}
zupt_w8(out, ZUPT_BLOCK_MAGIC_0);
zupt_w8(out, ZUPT_BLOCK_MAGIC_1);
zupt_w8(out, ZUPT_BLOCK_DEDUP_REF);
zupt_w16le(out, ZUPT_CODEC_STORE);
zupt_w16le(out, block_flags);
zupt_write_varint(out, (uint64_t)orig_size);
zupt_write_varint(out, payload_size);
zupt_w64le(out, orig_checksum);
int result = fwrite(payload, 1, payload_size, out) == payload_size &&
!ferror(out) ? 0 : -1;
free(encrypted);
return result;
}
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) {
if (!block || !ref_offset || !referenced_aad_seq ||
block->block_type != ZUPT_BLOCK_DEDUP_REF ||
block->codec_id != ZUPT_CODEC_STORE || !block->payload)
return ZUPT_ERR_CORRUPT;
const uint8_t *payload = block->payload;
size_t payload_size = (size_t)block->compressed_size;
uint8_t *plain = NULL;
if (require_authentication) {
if (!(block->block_flags & ZUPT_BFLAG_ENCRYPTED) ||
!keyring || !keyring->active)
return ZUPT_ERR_AUTH_FAIL;
size_t plain_size = 0;
if (keyring->use_preface_aad) {
uint8_t preface[ZUPT_PREFACE_AAD_LEN];
zupt_serialize_preface_aad_scalars(
block->block_type, block->codec_id, block->block_flags,
block->uncompressed_size, block->compressed_size,
block->checksum, preface);
plain = zupt_decrypt_buffer_aad(
keyring, payload, payload_size, current_aad_seq,
preface, sizeof(preface), &plain_size);
zupt_secure_wipe(preface, sizeof(preface));
} else {
plain = zupt_decrypt_buffer(keyring, payload, payload_size,
current_aad_seq,
&plain_size);
}
if (!plain) return ZUPT_ERR_AUTH_FAIL;
if (plain_size != 16) {
zupt_secure_wipe(plain, plain_size);
free(plain);
return ZUPT_ERR_CORRUPT;
}
payload = plain;
payload_size = plain_size;
} else if (block->block_flags != 0 || payload_size != 8) {
return ZUPT_ERR_CORRUPT;
}
if ((!require_authentication && payload_size != 8) ||
(require_authentication && payload_size != 16)) {
free(plain);
return ZUPT_ERR_CORRUPT;
}
*ref_offset = zupt_le64_get(payload);
*referenced_aad_seq = require_authentication
? zupt_le64_get(payload + 8) : 0;
if (plain) {
zupt_secure_wipe(plain, payload_size);
free(plain);
}
return ZUPT_OK;
}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: AGPL-3.0-or-later
* Copyright (c) 2025-2026 Cristian Cezar Moisés
* Zupt v2.0.0 Adaptive Compression: File Type Detection
* ZUPT v2.0.0 Adaptive Compression: File Type Detection
* Copyright (c) 2026 Cristian Cezar Moisés AGPL-3.0-or-later
*
* Detects file type by magic bytes (not just extension) and returns

File diff suppressed because it is too large Load diff

57
src/zupt_internal.h Normal file
View file

@ -0,0 +1,57 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later */
#ifndef ZUPT_INTERNAL_H
#define ZUPT_INTERNAL_H
#include "zupt.h"
/* Keep the published 5.2.1 option layout intact. The high bit is private to
* the CLI/read path; ordinary nonzero verbose values retain their behavior. */
#define ZUPT_INTERNAL_ALLOW_LEGACY_NO_AIT 0x40000000
static inline void zupt_internal_set_verbose(zupt_options_t *options) {
options->verbose |= 1;
}
static inline int zupt_internal_verbose(const zupt_options_t *options) {
return options &&
(options->verbose & ~ZUPT_INTERNAL_ALLOW_LEGACY_NO_AIT) != 0;
}
static inline void zupt_internal_allow_legacy_no_ait(
zupt_options_t *options) {
options->verbose |= ZUPT_INTERNAL_ALLOW_LEGACY_NO_AIT;
}
static inline int zupt_internal_legacy_no_ait_allowed(
const zupt_options_t *options) {
if (!options) return 0;
int value = options->verbose;
return (value & ZUPT_INTERNAL_ALLOW_LEGACY_NO_AIT) != 0 &&
(value & ~(ZUPT_INTERNAL_ALLOW_LEGACY_NO_AIT | 1)) == 0;
}
/* A negative encoded capacity records an incomplete collection without
* enlarging the published zupt_filelist_t structure. */
static inline int zupt_internal_filelist_failed(
const zupt_filelist_t *filelist) {
return filelist && filelist->capacity < 0;
}
static inline void zupt_internal_filelist_mark_failed(
zupt_filelist_t *filelist) {
if (filelist && filelist->capacity >= 0)
filelist->capacity = -filelist->capacity - 1;
}
int zupt_dedup_lookup_secure(
zupt_dedup_ctx_t *context, uint64_t fingerprint,
const uint8_t digest[ZUPT_DEDUP_DIGEST_SIZE],
uint64_t *reference_offset, uint32_t *reference_size,
uint64_t *reference_aad_sequence);
int zupt_dedup_insert_secure(
zupt_dedup_ctx_t *context, uint64_t fingerprint,
const uint8_t digest[ZUPT_DEDUP_DIGEST_SIZE],
uint64_t block_offset, uint32_t block_size,
uint64_t block_aad_sequence);
#endif

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,7 +1,7 @@
/*
* SPDX-License-Identifier: AGPL-3.0-or-later
* Copyright (c) 2025-2026 Cristian Cezar Moisés
* ZUPT - LZ77 Compression Engine v2 (Zupt-LZ codec 0x0008)
* ZUPT - LZ77 Compression Engine v2 (ZUPT-LZ codec 0x0008)
*
* Improvements over v0.1:
* - 18-bit hash table (256K entries) for better match distribution

View file

@ -216,15 +216,16 @@ static void huff_build(const uint32_t *freq, int ns, hcode_t *codes) {
int ni=0;
while(hn>1){
hnode_t a=hp[0];hp[0]=hp[--hn];if(hn>0)h_down(hp,hn,0);
hnode_t a=hp[0];hp[0]=hp[--hn];h_down(hp,hn,0);
hnode_t b=hp[0];hp[0]=hp[--hn];if(hn>0)h_down(hp,hn,0);
L[ni]=a.s; R[ni]=b.s;
hnode_t in; in.f=a.f+b.f; in.s=-(ni+1); ni++;
hp[hn]=in; h_up(hp,hn); hn++;
}
uint8_t *dp=(uint8_t*)calloc(ns,1);
if(dp && hn==1) tree_depths(hp[0].s,0,L,R,dp,ns);
uint8_t *dp=(uint8_t*)calloc((size_t)ns, 1);
if (!dp) { free(hp); free(L); free(R); return; }
if(hn==1) tree_depths(hp[0].s,0,L,R,dp,ns);
/* Enforce max code length using Kraft-sum based redistribution.
*
@ -375,7 +376,7 @@ static size_t cl_encode(const uint8_t *lens, int count, uint8_t *out, size_t oca
out[op++] = (uint8_t)(r - 11);
i += r; run -= r;
} else if (run >= 3) {
int r = run > 10 ? 10 : run;
int r = run;
if (op + 2 > ocap) return 0;
out[op++] = 17;
out[op++] = (uint8_t)(r - 3);
@ -670,6 +671,8 @@ size_t zupt_lzh_compress(const uint8_t *src, size_t slen,
/* Compress code lengths with RLE */
uint8_t ll_lens[LZH_MAX_LITLEN], d_lens[LZH_MAX_DIST];
memset(ll_lens, 0, sizeof(ll_lens));
memset(d_lens, 0, sizeof(d_lens));
for (int i = 0; i < ll_cnt; i++) ll_lens[i] = ll_codes[i].len;
for (int i = 0; i < d_cnt; i++) d_lens[i] = d_codes[i].len;
@ -741,7 +744,6 @@ size_t zupt_lzh_decompress(const uint8_t *src, size_t slen,
int rle_on = (flags & 0x01);
uint32_t rle_orig = 0;
if (rle_on) {
if (ip + 4 > slen) return 0;
memcpy(&rle_orig, src + ip, 4); ip += 4;
}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,13 @@
/*
* 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
* SPDX-License-Identifier: AGPL-3.0-or-later AND CC0-1.0
*
* Portions are adapted from the pq-crystals/kyber reference implementation,
* offered upstream under CC0-1.0 or Apache-2.0. ZUPT uses the CC0-1.0
* option for those portions; see THIRD-PARTY-NOTICES.md. The exact upstream
* revision used for the original adaptation was not retained, so none is
* asserted here.
*
* ML-KEM-768 (FIPS 203, formerly CRYSTALS-Kyber).
* Pure C11, zero dependencies. Uses zupt_keccak.h for SHA3/SHAKE.
@ -592,11 +598,13 @@ int zupt_mlkem768_decaps(uint8_t ss[32], const uint8_t ct[1088],
uint8_t ct_prime[1088];
kpke_encrypt(ct_prime, pk, m_prime, kr + 32);
/* CT-REQUIRED: Compare ct and ct' via the single audited constant-time
* primitive (the same one used for MAC-tag verification; timing-tested
* by tests/test_ct_timing). A timing leak here would be a KEM
/* CT-REQUIRED: Compare ct and ct' via the single audited
* constant-time-intended primitive (the same one used for MAC-tag
* verification; regression-measured by tests/test_ct_timing when its
* control is conclusive). A timing leak here would be a KEM
* decapsulation oracle distinguishing valid from invalid ciphertexts
* breaks IND-CCA2 so this comparison must be constant-time over all
* breaks IND-CCA2 so the implementation requires content-independent
* behavior over all
* 1088 ciphertext bytes. zupt_ct_memeq returns 1 if the buffers are
* equal (ct matches success), 0 otherwise. */
int ct_equal = zupt_ct_memeq(ct, ct_prime, 1088);
@ -621,7 +629,7 @@ int zupt_mlkem768_decaps(uint8_t ss[32], const uint8_t ct[1088],
* ct_equal == 0 (ct differs): use ss_reject (implicit rejection) fail = 1. */
uint8_t fail = (uint8_t)(1 - ct_equal);
#ifdef ZUPT_USE_JASMIN
/* JASMIN-VERIFIED: CT select — proven by Jasmin type system.
/* JASMIN PATH: compiled masked select; no retained formal proof is claimed.
* fail=0 ss_success, fail=1 ss_reject */
zupt_ct_select_32(ss, ss_success, ss_reject, (uint64_t)fail);
#else

View file

@ -1,5 +1,5 @@
/*
* Zupt Memory Locking for Key Material
* ZUPT Memory Locking for Key Material
* Copyright (c) 2026 Cristian Cezar Moisés
* SPDX-License-Identifier: AGPL-3.0-or-later
*

View file

@ -9,23 +9,21 @@
* zupt_sha256.c bit-identical output but 3-8x faster on CPUs that
* implement the extensions (Intel Goldmont+/Ice Lake+, AMD Zen+).
*
* Security note: SHA-NI is constant-time by construction. It performs
* no data-dependent memory accesses or branches, so it has a strictly
* stronger side-channel posture than any table- or branch-based
* software SHA-256. Since Zupt's authentication is HMAC-SHA256 over
* attacker-influenced ciphertext, a constant-time compression function
* is the right default wherever the hardware provides it.
* Security note: this fixed-round SHA-NI path is designed without intended
* data-dependent memory access or branches. Exact generated-code behavior is
* compiler-, CPU-, and platform-dependent; this is not a formal constant-time
* claim. Avoiding table lookups is nevertheless useful for HMAC-SHA256 over
* attacker-influenced ciphertext.
*
* Dispatch: sha256_transform() in zupt_sha256.c calls
* zupt_sha256_transform_shani() when zupt_cpu.has_shani is set. On
* non-x86_64 targets this file compiles to nothing (the symbol is
* never referenced because has_shani is always 0).
*
* Reference: Intel SHA Extensions whitepaper (Gulley, Gopal, Yap,
* Feghali, Guilford, Wolrich, 2013) and the public-domain intrinsic
* reference by Jeffrey Walton. This implementation was written against
* the FIPS 180-4 spec and validated bit-exact against the scalar path
* and the NIST FIPS 180-4 test vectors on both paths.
* Adapted from Jeffrey Walton's public-domain SHA-Intrinsics x86 reference,
* itself based on Intel and miTLS material; see THIRD-PARTY-NOTICES.md. The
* resulting implementation is validated bit-exact against the scalar path and
* the NIST FIPS 180-4 test vectors on both paths.
*/
#include "zupt.h"

View file

@ -1,20 +1,24 @@
/*
* 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
* SPDX-License-Identifier: AGPL-3.0-or-later AND BSD-3-Clause
*
* Portions are adapted from curve25519-donna by Google Inc. and Adam Langley.
* This distribution conservatively retains the upstream repository's
* BSD-3-Clause terms; see THIRD-PARTY-NOTICES.md. The exact upstream revision
* used for the original adaptation was not retained, so none is asserted.
*
* X25519 Diffie-Hellman (RFC 7748) over Curve25519.
* Field: GF(2^255-19), represented as 4 × 64-bit limbs (donna64 layout).
* Montgomery ladder: constant-time by construction (no secret-dependent branches).
* Field: GF(2^255-19), represented as 5 x 51-bit limbs following
* curve25519-donna's 64-bit implementation approach.
* Fixed-iteration Montgomery ladder with no intended secret-dependent branch
* or table access; exact compiled timing remains platform-dependent.
*
* CT-REQUIRED: Every operation in this file must be constant-time.
* No branches on secret data. No secret-dependent memory access.
*
* v2.0.0: Rewritten from 5×51-bit to 4×64-bit limb representation
* to match Jasmin zupt_fe_cswap (4×u64 masked XOR swap).
*
* Representation: f = f[0] + f[1]*2^64 + f[2]*2^128 + f[3]*2^192
* where limbs can temporarily exceed 2^64 during intermediate calculations.
* Representation: f = f[0] + f[1]*2^51 + f[2]*2^102 + f[3]*2^153
* + f[4]*2^204. Limbs may temporarily exceed 51 bits during arithmetic;
* fe_reduce() brings the result back to canonical form mod 2^255-19.
*/
#include "zupt_x25519.h"
@ -23,30 +27,12 @@
#include <string.h>
/* ═══════════════════════════════════════════════════════════════════
* FIELD ARITHMETIC: GF(2^255 - 19), 4 × 64-bit limbs
* FIELD ARITHMETIC: GF(2^255 - 19), 5 x 51-bit limbs
*
* We use the 5×51-bit schoolbook approach internally for multiplication
* (to avoid requiring __int128 for 128×128 products) but store/swap
* in 4×64-bit layout to match Jasmin.
*
* Actually: we keep 5×51-bit for mul/sq (needs 64×64128 products)
* and convert to/from 4×64-bit at the boundary (frombytes/tobytes/cswap).
*
* CORRECTION: To truly match Jasmin's 4×u64 layout for fe_cswap,
* the field elements in memory MUST be 4×u64. We use 5×51-bit
* internally in registers only, and store back as 4×u64 after each
* operation. This is the donna64 approach used by libsodium.
*
* SIMPLER APPROACH: Keep everything as 5×51-bit (the proven working
* implementation) and just adapt fe_cswap to operate on 5 limbs
* with the Jasmin function swapping the first 4 u64 values plus
* a C swap of the 5th.
*
* SIMPLEST CORRECT APPROACH (chosen): Keep the proven 5×51-bit
* arithmetic but store field elements as 5×u64 (40 bytes). The
* Jasmin fe_cswap swaps 4×u64 (32 bytes). We call it for the first
* 4 limbs and handle the 5th limb in C. This is minimal change,
* the arithmetic is identical, and the CT property is preserved.
* The optional Jasmin swap operates on the first four stored uint64_t limbs;
* the fifth limb uses the same masked-XOR pattern in C. The default build uses
* the C loop for all five limbs. No retained formal-verification artifact is
* claimed for either path.
* */
typedef uint64_t fe[5]; /* Field element: 5 limbs, each < 2^52 */
@ -115,12 +101,12 @@ static void fe_tobytes(uint8_t s[32], const fe h) {
}
/* CT-REQUIRED: conditional swap — no branches on secret bit.
* JASMIN-VERIFIED: First 4 limbs swapped by Jasmin when available;
* JASMIN PATH: first 4 limbs swapped by compiled Jasmin code when available;
* 5th limb swapped in C (same constant-time XOR pattern). */
static void fe_cswap(fe a, fe b, uint64_t flag) {
uint64_t mask = -(uint64_t)(flag & 1);
#ifdef ZUPT_USE_JASMIN
/* JASMIN-VERIFIED: CT swap of first 32 bytes (4×u64).
/* JASMIN PATH: masked swap of first 32 bytes (4×u64).
* The Jasmin function operates on 4 consecutive u64 values. */
zupt_fe_cswap(a, b, flag & 1);
/* 5th limb: C fallback (same CT pattern) */
@ -248,13 +234,13 @@ static void fe_inv(fe h, const fe f) {
/* ═══════════════════════════════════════════════════════════════════
* X25519 MONTGOMERY LADDER
* CT-REQUIRED: No secret-dependent branches. The ladder is constant-time
* by construction: every iteration performs the same operations, with
* cswap selecting which point to operate on.
* CT-REQUIRED: no intended secret-dependent branches or memory access. Every
* iteration follows the same source-level operation sequence, with cswap
* selecting which point to operate on; this is not a compiled timing proof.
* */
/* FRAMA-C: X25519 Diffie-Hellman key agreement (RFC 7748)
* CT-REQUIRED: Montgomery ladder constant-time by construction */
* CT-REQUIRED: fixed-iteration, constant-time-intended Montgomery ladder */
/*@ requires \valid(out + (0..31));
@ requires \valid_read(scalar + (0..31));
@ requires \valid_read(point + (0..31));

View file

@ -1,5 +1,6 @@
/*
* SPDX-License-Identifier: AGPL-3.0-or-later
* SPDX-License-Identifier: AGPL-3.0-or-later AND BSD-2-Clause
* Copyright (c) 2012-2021 Yann Collet
* Copyright (c) 2025-2026 Cristian Cezar Moisés
* ZUPT - XXH64 Hash (based on xxHash by Yann Collet, BSD-2)
*/