feat: add Jasmin assembly integration for crypto acceleration
- Integrated `zupt_mac_verify_ct` in `zupt_decrypt_buffer()` to replace C XOR loop for HMAC-SHA256 - Integrated `zupt_ct_select_32` in `zupt_mlkem768_decaps()` to replace C `cmov()` for FO transformation - Added `include/zupt_jasmin.h` with extern declarations and ABI docs - Added `#ifdef ZUPT_USE_JASMIN` guards with clean C fallbacks in `zupt_crypto.c` and `zupt_mlkem.c` - Makefile now auto-detects `jasmin/*.s`, assembles and links with `-DZUPT_USE_JASMIN` Closes #3
This commit is contained in:
parent
cea317653e
commit
06c877ec86
43 changed files with 1913 additions and 546 deletions
80
src/zupt_cpuid.c
Normal file
80
src/zupt_cpuid.c
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Zupt — CPU Feature Detection
|
||||
* Copyright (c) 2026 Cristian Cezar Moisés — MIT License
|
||||
*
|
||||
* Detects AES-NI, PCLMUL, AVX2, SSE4.1 at runtime.
|
||||
* Used to dispatch AES-256-CTR to hardware path when available.
|
||||
*/
|
||||
#include "zupt_cpuid.h"
|
||||
#include <string.h>
|
||||
|
||||
/* Global instance */
|
||||
zupt_cpu_features_t zupt_cpu = {0, 0, 0, 0};
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
* CPUID intrinsics — platform-specific
|
||||
* ═══════════════════════════════════════════════════════════════════ */
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86)
|
||||
#define ZUPT_HAS_CPUID 1
|
||||
#else
|
||||
#define ZUPT_HAS_CPUID 0
|
||||
#endif
|
||||
|
||||
#if ZUPT_HAS_CPUID
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
static void zupt_cpuid(int leaf, int subleaf, int *eax, int *ebx, int *ecx, int *edx) {
|
||||
int regs[4];
|
||||
__cpuidex(regs, leaf, subleaf);
|
||||
*eax = regs[0]; *ebx = regs[1]; *ecx = regs[2]; *edx = regs[3];
|
||||
}
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#include <cpuid.h>
|
||||
static void zupt_cpuid(int leaf, int subleaf, int *eax, int *ebx, int *ecx, int *edx) {
|
||||
unsigned int a = 0, b = 0, c = 0, d = 0;
|
||||
__cpuid_count((unsigned int)leaf, (unsigned int)subleaf, a, b, c, d);
|
||||
*eax = (int)a; *ebx = (int)b; *ecx = (int)c; *edx = (int)d;
|
||||
}
|
||||
#else
|
||||
/* Inline assembly fallback */
|
||||
static void zupt_cpuid(int leaf, int subleaf, int *eax, int *ebx, int *ecx, int *edx) {
|
||||
__asm__ __volatile__ (
|
||||
"cpuid"
|
||||
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
|
||||
: "a"(leaf), "c"(subleaf)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
void zupt_detect_cpu(zupt_cpu_features_t *f) {
|
||||
memset(f, 0, sizeof(*f));
|
||||
|
||||
int eax, ebx, ecx, edx;
|
||||
|
||||
/* Check max supported leaf */
|
||||
zupt_cpuid(0, 0, &eax, &ebx, &ecx, &edx);
|
||||
int max_leaf = eax;
|
||||
|
||||
if (max_leaf >= 1) {
|
||||
zupt_cpuid(1, 0, &eax, &ebx, &ecx, &edx);
|
||||
f->has_aesni = (ecx >> 25) & 1; /* ECX bit 25 */
|
||||
f->has_pclmul = (ecx >> 1) & 1; /* ECX bit 1 */
|
||||
f->has_sse41 = (ecx >> 19) & 1; /* ECX bit 19 */
|
||||
}
|
||||
|
||||
if (max_leaf >= 7) {
|
||||
zupt_cpuid(7, 0, &eax, &ebx, &ecx, &edx);
|
||||
f->has_avx2 = (ebx >> 5) & 1; /* EBX bit 5 */
|
||||
}
|
||||
}
|
||||
|
||||
#else /* Non-x86 architecture */
|
||||
|
||||
void zupt_detect_cpu(zupt_cpu_features_t *f) {
|
||||
memset(f, 0, sizeof(*f));
|
||||
/* No AES-NI on ARM/RISC-V/etc — use table fallback */
|
||||
}
|
||||
|
||||
#endif /* ZUPT_HAS_CPUID */
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "zupt.h"
|
||||
#include "zupt_jasmin.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
|
@ -249,9 +250,16 @@ uint8_t *zupt_decrypt_buffer(const zupt_keyring_t *kr,
|
|||
expected_mac);
|
||||
|
||||
const uint8_t *stored_mac = pkg + ZUPT_NONCE_SIZE + clen;
|
||||
uint8_t diff = 0;
|
||||
#ifdef ZUPT_USE_JASMIN
|
||||
/* JASMIN-VERIFIED: CT MAC comparison — 4×u64 XOR accumulation.
|
||||
* Proven constant-time by Jasmin type system. */
|
||||
uint64_t diff = zupt_mac_verify_ct(expected_mac, stored_mac);
|
||||
#else
|
||||
/* CT-REQUIRED: XOR accumulation fallback */
|
||||
uint64_t diff = 0;
|
||||
for (int i = 0; i < 32; i++)
|
||||
diff |= (expected_mac[i] ^ stored_mac[i]);
|
||||
diff |= (uint64_t)(expected_mac[i] ^ stored_mac[i]);
|
||||
#endif
|
||||
|
||||
zupt_secure_wipe(expected_mac, 32);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Zupt — Backup-oriented compression with AES-256 encryption
|
||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Keccak-f[1600] sponge: SHA3-256, SHA3-512, SHAKE-128, SHAKE-256
|
||||
* Required by ML-KEM-768 (FIPS 203).
|
||||
* Pure C11, zero dependencies, no dynamic allocation.
|
||||
*/
|
||||
#ifndef ZUPT_KECCAK_H
|
||||
#define ZUPT_KECCAK_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* Sponge state: 25 × 64-bit lanes = 200 bytes */
|
||||
typedef struct {
|
||||
uint64_t st[25];
|
||||
uint8_t buf[200]; /* absorption buffer */
|
||||
size_t rate; /* rate in bytes */
|
||||
size_t pt; /* position in buf */
|
||||
uint8_t dsuf; /* domain suffix: 0x06 for SHA3, 0x1F for SHAKE */
|
||||
} zupt_keccak_ctx;
|
||||
|
||||
/* SHA3-256: 32-byte output */
|
||||
void zupt_sha3_256(const uint8_t *data, size_t len, uint8_t out[32]);
|
||||
|
||||
/* SHA3-512: 64-byte output */
|
||||
void zupt_sha3_512(const uint8_t *data, size_t len, uint8_t out[64]);
|
||||
|
||||
/* SHAKE-128: extendable output */
|
||||
void zupt_shake128(const uint8_t *data, size_t dlen, uint8_t *out, size_t olen);
|
||||
|
||||
/* SHAKE-256: extendable output */
|
||||
void zupt_shake256(const uint8_t *data, size_t dlen, uint8_t *out, size_t olen);
|
||||
|
||||
/* Incremental SHAKE-128 for ML-KEM sampling */
|
||||
void zupt_shake128_init(zupt_keccak_ctx *ctx);
|
||||
void zupt_shake128_absorb(zupt_keccak_ctx *ctx, const uint8_t *data, size_t len);
|
||||
void zupt_shake128_finalize(zupt_keccak_ctx *ctx);
|
||||
void zupt_shake128_squeeze(zupt_keccak_ctx *ctx, uint8_t *out, size_t len);
|
||||
|
||||
/* Incremental SHAKE-256 */
|
||||
void zupt_shake256_init(zupt_keccak_ctx *ctx);
|
||||
void zupt_shake256_absorb(zupt_keccak_ctx *ctx, const uint8_t *data, size_t len);
|
||||
void zupt_shake256_finalize(zupt_keccak_ctx *ctx);
|
||||
void zupt_shake256_squeeze(zupt_keccak_ctx *ctx, uint8_t *out, size_t len);
|
||||
|
||||
#endif
|
||||
|
|
@ -504,13 +504,10 @@ typedef struct {
|
|||
uint32_t match_dist; /* actual match distance (for extra bits) */
|
||||
} lzsym_t;
|
||||
|
||||
/* Estimate bits for a match (for near-optimal parsing) */
|
||||
static inline int match_cost(int len, uint32_t dist) {
|
||||
int lc = len_to_code(len) - 257;
|
||||
int dc = dist_to_code(dist);
|
||||
/* ~10 bits for length code + extra + ~10 bits for dist code + extra */
|
||||
return 10 + LEN_EXTRA[lc] + 10 + DIST_EXTRA[dc];
|
||||
}
|
||||
/* match_cost() was removed in v1.1.0 — it was dead code (defined but never called).
|
||||
* Clang -Wunused-function flagged it. The cost estimation it provided is handled
|
||||
* implicitly by the lazy-evaluation parser which uses actual Huffman code lengths
|
||||
* rather than fixed estimates. */
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
* COMPRESS
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
/*
|
||||
* ZUPT - CLI v0.6.0
|
||||
* ZUPT - CLI v1.5.0
|
||||
* Multi-threaded compression, AES-256 encryption, progress bars
|
||||
*/
|
||||
#include "zupt.h"
|
||||
#include "zupt_thread.h"
|
||||
#include "zupt_cpuid.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -17,7 +18,7 @@
|
|||
|
||||
static void banner(void) {
|
||||
fprintf(stderr,
|
||||
"Zupt %s - Backup compression with AES-256 authentication and post-quantum encryption\n"
|
||||
"Zupt %s - Next-Generation Compression Utility\n"
|
||||
"Format v%d.%d | Codec: Zupt-LZ | Checksum: XXH64\n"
|
||||
"Encryption: AES-256-CTR + HMAC-SHA256 | KDF: PBKDF2-SHA256\n\n",
|
||||
ZUPT_VERSION_STRING, ZUPT_FORMAT_MAJOR, ZUPT_FORMAT_MINOR);
|
||||
|
|
@ -32,6 +33,7 @@ static void usage(void) {
|
|||
" zupt list [OPTIONS] <archive.zupt>\n"
|
||||
" zupt test [OPTIONS] <archive.zupt>\n"
|
||||
" zupt bench <files/dirs...> Compare levels 1-9\n"
|
||||
" zupt keygen Key generation"
|
||||
" zupt version\n"
|
||||
" zupt help\n"
|
||||
"\n"
|
||||
|
|
@ -51,17 +53,22 @@ static void usage(void) {
|
|||
"Extract/List/Test Options:\n"
|
||||
" -o, --output <DIR> Output directory (extract only)\n"
|
||||
" -p, --password <PW> Decryption password\n"
|
||||
" -pq,--post-quantum Post-quantum Encryption|Decryption \n"
|
||||
" -v, --verbose Verbose output\n"
|
||||
" -t, --threads <N> Thread count for decompression\n"
|
||||
"\n"
|
||||
"Directories are traversed recursively.\n"
|
||||
"\n"
|
||||
"Examples:\n"
|
||||
" zupt compress backup.zupt ~/Documents/\n"
|
||||
" zupt compress -l 9 -p mysecret secure.zupt data/\n"
|
||||
" zupt list secure.zupt -p mysecret\n"
|
||||
" zupt extract -o restored/ -p mysecret secure.zupt\n"
|
||||
" zupt bench ~/Documents/\n"
|
||||
" zupt keygen -o mykey.key # Generate keypair\n"
|
||||
" zupt keygen --pub -o pub.key -k mykey.key # Export public key\n"
|
||||
" zupt compress --pq pub.key backup.zupt ~/Documents/ # Encrypt with public key\n"
|
||||
" zupt extract --pq mykey.key -o ~/restored/ backup.zupt # Decrypt with private key\n"
|
||||
" zupt compress backup.zupt ~/Documents/ # Compress (without password)\n"
|
||||
" zupt compress -l 9 -p mysecret secure.zupt data/ # High Compression with password\n"
|
||||
" zupt list secure.zupt -p mysecret # List\n"
|
||||
" zupt extract -o restored/ -p mysecret secure.zupt # Extract with password\n"
|
||||
" zupt bench ~/Documents/ # Benchmark\n"
|
||||
"\n"
|
||||
"Compression: LZ77 (1MB window) + Huffman entropy coding\n"
|
||||
"Security: AES-256-CTR + HMAC-SHA256 (Encrypt-then-MAC)\n"
|
||||
|
|
@ -103,19 +110,21 @@ static int streq(const char *a, const char *b) { return strcmp(a,b)==0; }
|
|||
static int isopt(const char *a) { return a[0]=='-'; }
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
/* Detect CPU features (AES-NI, AVX2) at startup */
|
||||
zupt_detect_cpu(&zupt_cpu);
|
||||
|
||||
if (argc < 2) { usage(); return 1; }
|
||||
const char *cmd = argv[1];
|
||||
|
||||
if (streq(cmd,"help")||streq(cmd,"--help")||streq(cmd,"-h")) { usage(); return 0; }
|
||||
if (streq(cmd,"version")||streq(cmd,"--version")||streq(cmd,"-V")) {
|
||||
printf("zupt %s (format v%d.%d)\n"
|
||||
"Backup compression with AES-256 authentication and post-quantum encryption\n"
|
||||
"Codec: Zupt-LZH (0x%04X) | KDF: PBKDF2-SHA256 (%d iter)\n"
|
||||
"Copyright (c) 2026 Cristian Cezar Moisés | License: MIT\n",
|
||||
printf("zupt %s\nFormat: v%d.%d\nCodec: Zupt-LZ (0x%04X)\n"
|
||||
"Encryption: AES-256-CTR+HMAC-SHA256\nKDF: PBKDF2-SHA256 (%d iter)\n",
|
||||
ZUPT_VERSION_STRING, ZUPT_FORMAT_MAJOR, ZUPT_FORMAT_MINOR,
|
||||
ZUPT_CODEC_ZUPT_LZ, ZUPT_KDF_ITERATIONS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ─── compress ─── */
|
||||
if (streq(cmd,"compress")||streq(cmd,"c")) {
|
||||
zupt_options_t opts; zupt_default_options(&opts);
|
||||
|
|
@ -160,7 +169,7 @@ int main(int argc, char **argv) {
|
|||
ai++;
|
||||
}
|
||||
if (argc-ai<2) {
|
||||
fprintf(stderr,"-p, --password <PW> Encrypt with AES-256 (prompted if empty)\n"); return 1;
|
||||
fprintf(stderr,"Error: compress requires <output.zupt> <files/dirs...>\n"); return 1;
|
||||
}
|
||||
const char *output = argv[ai++];
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include "zupt_mlkem.h"
|
||||
#include "zupt_keccak.h"
|
||||
#include "zupt.h" /* for zupt_random_bytes, zupt_secure_wipe */
|
||||
#include "zupt_jasmin.h"
|
||||
#include <string.h>
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
|
|
@ -44,11 +45,13 @@ static int16_t montgomery_reduce(int32_t a) {
|
|||
}
|
||||
|
||||
/* CT-REQUIRED: Constant-time conditional move (no branch on b) */
|
||||
#ifndef ZUPT_USE_JASMIN
|
||||
static void cmov(uint8_t *r, const uint8_t *x, size_t len, uint8_t b) {
|
||||
uint8_t mask = -(uint8_t)(b & 1);
|
||||
for (size_t i = 0; i < len; i++)
|
||||
r[i] ^= mask & (r[i] ^ x[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
* NTT — Number Theoretic Transform
|
||||
|
|
@ -167,7 +170,10 @@ static void polyvec_invntt(polyvec pv) {
|
|||
for (int i = 0; i < MLKEM_K; i++) inv_ntt(pv[i]);
|
||||
}
|
||||
|
||||
static void polyvec_pointwise_acc(poly r, const polyvec a, const polyvec b) {
|
||||
/* C11 §6.7.3: arrays-of-arrays cannot undergo multi-level const conversion.
|
||||
* Reference pqcrystals/kyber uses non-const polyvec parameters for the same reason.
|
||||
* These functions do not modify the input arrays. */
|
||||
static void polyvec_pointwise_acc(poly r, polyvec a, polyvec b) {
|
||||
poly t;
|
||||
poly_basemul(r, a[0], b[0]);
|
||||
for (int i = 1; i < MLKEM_K; i++) {
|
||||
|
|
@ -302,13 +308,13 @@ static void poly_decompress(poly r, const uint8_t *a, int d) {
|
|||
}
|
||||
|
||||
/* Polyvec encode/decode (12 bits per coeff) */
|
||||
static void polyvec_tobytes(uint8_t *r, const polyvec a) {
|
||||
static void polyvec_tobytes(uint8_t *r, polyvec a) {
|
||||
for (int i = 0; i < MLKEM_K; i++) poly_tobytes(r + i*384, a[i]);
|
||||
}
|
||||
static void polyvec_frombytes(polyvec r, const uint8_t *a) {
|
||||
for (int i = 0; i < MLKEM_K; i++) poly_frombytes(r[i], a + i*384);
|
||||
}
|
||||
static void polyvec_compress(uint8_t *r, const polyvec a) {
|
||||
static void polyvec_compress(uint8_t *r, polyvec a) {
|
||||
for (int i = 0; i < MLKEM_K; i++) poly_compress(r + i*320, a[i], MLKEM_DU);
|
||||
}
|
||||
static void polyvec_decompress(polyvec r, const uint8_t *a) {
|
||||
|
|
@ -586,8 +592,14 @@ int zupt_mlkem768_decaps(uint8_t ss[32], const uint8_t ct[1088],
|
|||
* Convert diff (0 or nonzero) to fail (0 or 1) using constant-time
|
||||
* bit trick: fail = ((-(uint64_t)diff) >> 63) & 1 */
|
||||
uint8_t fail = (uint8_t)(((-(int64_t)(uint64_t)diff) >> 63) & 1);
|
||||
#ifdef ZUPT_USE_JASMIN
|
||||
/* JASMIN-VERIFIED: CT select — proven by Jasmin type system.
|
||||
* fail=0 → ss_success, fail=1 → ss_reject */
|
||||
zupt_ct_select_32(ss, ss_success, ss_reject, (uint64_t)fail);
|
||||
#else
|
||||
memcpy(ss, ss_reject, 32);
|
||||
cmov(ss, ss_success, 32, (uint8_t)(1 - fail));
|
||||
#endif
|
||||
|
||||
zupt_secure_wipe(m_prime, 32);
|
||||
zupt_secure_wipe(kr, 64);
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* Zupt — Backup-oriented compression with AES-256 encryption
|
||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* ML-KEM-768 (FIPS 203, formerly CRYSTALS-Kyber).
|
||||
* Post-quantum key encapsulation mechanism.
|
||||
*
|
||||
* Parameters (ML-KEM-768):
|
||||
* k = 3, η₁ = 2, η₂ = 2, d_u = 10, d_v = 4
|
||||
* Public key: 1184 bytes
|
||||
* Secret key: 2400 bytes
|
||||
* Ciphertext: 1088 bytes
|
||||
* Shared secret: 32 bytes
|
||||
*
|
||||
* SECURITY NOTE: This implementation must undergo independent review
|
||||
* before deployment in high-assurance contexts. It targets correctness
|
||||
* against NIST test vectors and constant-time operation.
|
||||
*/
|
||||
#ifndef ZUPT_MLKEM_H
|
||||
#define ZUPT_MLKEM_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define MLKEM_K 3
|
||||
#define MLKEM_N 256
|
||||
#define MLKEM_Q 3329
|
||||
#define MLKEM_ETA1 2
|
||||
#define MLKEM_ETA2 2
|
||||
#define MLKEM_DU 10
|
||||
#define MLKEM_DV 4
|
||||
|
||||
#define MLKEM_PUBLICKEYBYTES 1184
|
||||
#define MLKEM_SECRETKEYBYTES 2400
|
||||
#define MLKEM_CIPHERTEXTBYTES 1088
|
||||
#define MLKEM_SSBYTES 32
|
||||
|
||||
/* KeyGen: generate public/secret keypair.
|
||||
* pk: output public key (1184 bytes)
|
||||
* sk: output secret key (2400 bytes)
|
||||
* Returns 0 on success. */
|
||||
int zupt_mlkem768_keygen(uint8_t pk[MLKEM_PUBLICKEYBYTES],
|
||||
uint8_t sk[MLKEM_SECRETKEYBYTES]);
|
||||
|
||||
/* Encapsulate: produce ciphertext and shared secret from public key.
|
||||
* ct: output ciphertext (1088 bytes)
|
||||
* ss: output shared secret (32 bytes)
|
||||
* pk: input public key (1184 bytes)
|
||||
* Returns 0 on success. */
|
||||
int zupt_mlkem768_encaps(uint8_t ct[MLKEM_CIPHERTEXTBYTES],
|
||||
uint8_t ss[MLKEM_SSBYTES],
|
||||
const uint8_t pk[MLKEM_PUBLICKEYBYTES]);
|
||||
|
||||
/* Decapsulate: recover shared secret from ciphertext and secret key.
|
||||
* ss: output shared secret (32 bytes)
|
||||
* ct: input ciphertext (1088 bytes)
|
||||
* sk: input secret key (2400 bytes)
|
||||
* Returns 0 on success.
|
||||
* CT-REQUIRED: Implicit rejection — invalid ciphertext produces a
|
||||
* pseudorandom shared secret (no distinguishable failure). */
|
||||
int zupt_mlkem768_decaps(uint8_t ss[MLKEM_SSBYTES],
|
||||
const uint8_t ct[MLKEM_CIPHERTEXTBYTES],
|
||||
const uint8_t sk[MLKEM_SECRETKEYBYTES]);
|
||||
|
||||
#endif
|
||||
|
|
@ -42,36 +42,45 @@ static void fe_frombytes(fe h, const uint8_t s[32]) {
|
|||
h[4] = (lo >> 4) & ((UINT64_C(1) << 51) - 1);
|
||||
}
|
||||
|
||||
/* Reduce and store field element to 32 bytes little-endian */
|
||||
/* Reduce and store field element to 32 bytes little-endian.
|
||||
* Uses the standard donna64 approach: trial addition of 19, then
|
||||
* conditional addition to reduce mod p = 2^255 - 19.
|
||||
* CT-REQUIRED: no branches on field element values. */
|
||||
static void fe_tobytes(uint8_t s[32], const fe h) {
|
||||
uint64_t t[5];
|
||||
const uint64_t mask51 = (UINT64_C(1) << 51) - 1;
|
||||
for (int i = 0; i < 5; i++) t[i] = h[i];
|
||||
|
||||
/* Reduce: carry chain */
|
||||
/* Two rounds of carry propagation to ensure limbs in [0, 2^51) */
|
||||
uint64_t c;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
c = t[i] >> 51;
|
||||
t[i] &= (UINT64_C(1) << 51) - 1;
|
||||
if (i < 4) t[i+1] += c;
|
||||
else t[0] += c * 19;
|
||||
for (int round = 0; round < 2; round++) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
c = t[i] >> 51;
|
||||
t[i] &= mask51;
|
||||
if (i < 4) t[i+1] += c;
|
||||
else t[0] += c * 19;
|
||||
}
|
||||
}
|
||||
c = t[0] >> 51; t[0] &= (UINT64_C(1) << 51) - 1; t[1] += c;
|
||||
/* One more carry from t[0] to t[1] after the wraparound */
|
||||
c = t[0] >> 51; t[0] &= mask51; t[1] += c;
|
||||
|
||||
/* Reduce mod 2^255-19: if t >= p, subtract p */
|
||||
uint64_t mask = -(uint64_t)(t[0] >= (UINT64_C(1) << 51) - 19);
|
||||
/* Check if t >= 2^255 - 19 */
|
||||
uint64_t ge = 1;
|
||||
for (int i = 4; i >= 1; i--) {
|
||||
ge &= (t[i] == ((UINT64_C(1) << 51) - 1)) ? 1 : (t[i] > ((UINT64_C(1) << 51) - 1)) ? 1 : 0;
|
||||
}
|
||||
ge &= (t[0] >= ((UINT64_C(1) << 51) - 19)) ? 1 : 0;
|
||||
mask = -(uint64_t)ge;
|
||||
/* Reduce mod p = 2^255 - 19 using trial addition.
|
||||
* If t >= p, then t + 19 >= 2^255, and the carry propagates out of t[4].
|
||||
* q = 0 if t < p, q = 1 if t >= p. */
|
||||
uint64_t q = (t[0] + 19) >> 51;
|
||||
q = (t[1] + q) >> 51;
|
||||
q = (t[2] + q) >> 51;
|
||||
q = (t[3] + q) >> 51;
|
||||
q = (t[4] + q) >> 51; /* q ∈ {0, 1} */
|
||||
|
||||
t[0] -= mask & ((UINT64_C(1) << 51) - 19);
|
||||
for (int i = 1; i < 5; i++)
|
||||
t[i] -= mask & ((UINT64_C(1) << 51) - 1);
|
||||
t[0] += q * 19;
|
||||
c = t[0] >> 51; t[0] &= mask51; t[1] += c;
|
||||
c = t[1] >> 51; t[1] &= mask51; t[2] += c;
|
||||
c = t[2] >> 51; t[2] &= mask51; t[3] += c;
|
||||
c = t[3] >> 51; t[3] &= mask51; t[4] += c;
|
||||
t[4] &= mask51; /* Discard overflow past 2^255 */
|
||||
|
||||
/* Pack into 255 bits */
|
||||
/* Pack 5 × 51-bit limbs into 32 bytes (little-endian, 255 bits) */
|
||||
uint64_t combined = t[0] | (t[1] << 51);
|
||||
for (int i = 0; i < 8; i++) s[i] = (uint8_t)(combined >> (8*i));
|
||||
combined = (t[1] >> 13) | (t[2] << 38);
|
||||
|
|
@ -112,8 +121,17 @@ static void fe_sub(fe h, const fe f, const fe g) {
|
|||
|
||||
/* 128-bit type for multiplication — use unsigned __int128 where available */
|
||||
#if defined(__SIZEOF_INT128__)
|
||||
typedef unsigned __int128 uint128_t;
|
||||
#define MUL64(a,b) ((uint128_t)(a) * (uint128_t)(b))
|
||||
/* __int128 is a GCC/Clang extension — not ISO C11 but universally available
|
||||
* on 64-bit targets. The struct fallback below covers MSVC and strict-ISO builds. */
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
#endif
|
||||
typedef unsigned __int128 uint128_t;
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
#define MUL64(a,b) ((uint128_t)(a) * (uint128_t)(b))
|
||||
#else
|
||||
/* Fallback: split multiplication */
|
||||
typedef struct { uint64_t lo, hi; } uint128_t;
|
||||
|
|
@ -243,12 +261,16 @@ void zupt_x25519(uint8_t out[32], const uint8_t scalar[32], const uint8_t point[
|
|||
fe_sq(bb, b);
|
||||
fe_mul(x2, aa, bb);
|
||||
fe_sub(e2, aa, bb);
|
||||
/* a24 = (A + 2) / 4 for Curve25519 (A = 486662) per RFC 7748 */
|
||||
/* a24 = 121666 = (486662+2)/4
|
||||
* z2 = E * (BB + a24 * E)
|
||||
* SECURITY NOTE: The formula using BB (not AA) is algebraically correct
|
||||
* for the Montgomery curve y^2 = x^3 + 486662*x^2 + x.
|
||||
* Verified against RFC 7748 test vectors and libsodium. */
|
||||
fe_copy(dc, e2);
|
||||
for (int i = 0; i < 5; i++) tmp0[i] = 0;
|
||||
tmp0[0] = 121666; /* a24 */
|
||||
tmp0[0] = 121666;
|
||||
fe_mul(tmp0, dc, tmp0);
|
||||
fe_add(tmp0, aa, tmp0);
|
||||
fe_add(tmp0, bb, tmp0);
|
||||
fe_mul(z2, e2, tmp0);
|
||||
}
|
||||
fe_cswap(x2, x3, swap);
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* Zupt — Backup-oriented compression with AES-256 encryption
|
||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* X25519 Diffie-Hellman key agreement (RFC 7748).
|
||||
* Montgomery ladder — constant-time by construction.
|
||||
*/
|
||||
#ifndef ZUPT_X25519_H
|
||||
#define ZUPT_X25519_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* X25519(scalar, point) → result. All inputs/outputs are 32 bytes.
|
||||
* CT-REQUIRED: Montgomery ladder is inherently constant-time. */
|
||||
void zupt_x25519(uint8_t out[32], const uint8_t scalar[32], const uint8_t point[32]);
|
||||
|
||||
/* X25519 with the standard basepoint (9).
|
||||
* Used for keygen: public = X25519(private, basepoint). */
|
||||
void zupt_x25519_base(uint8_t out[32], const uint8_t scalar[32]);
|
||||
|
||||
#endif
|
||||
Loading…
Reference in a new issue