Files
zeekay aa7f471d97 chore: rename Comet → Magnetar (decomplect metaphor consistency)
Aligns hqc/slhdsa comments + dudect timing test docs with the canonical
PQ signature family naming:
  Pulsar (M-LWE threshold ML-DSA)
  Corona (R-LWE / Corona wrapper)
  Magnetar (SLH-DSA threshold via GF(257) Shamir)

Old name 'Comet' was the working title for Magnetar during early
development; this commit completes the rename in:
- hqc/hqc.go (family-disjoint gap comment)
- slhdsa/gpu.go (FIPS 205 GPU dispatch header)
- slhdsa/gpu_test.go (test descriptions)

Also includes:
- mlkem/ct/dudect/{decaps,encaps,keygen}_ct.go + .c — timing analysis
  harness improvements (decaps is the most CT-critical ML-KEM routine)
- mlkem/ct/dudect/run-m1-overnight.sh — operator runbook for Apple M1
  overnight timing analysis
- .gitignore — build artifacts (dudect/, *.dylib, *.so, results/)
2026-05-20 16:19:09 -07:00

111 lines
3.5 KiB
C

/*
* Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
* See the file LICENSE for licensing terms.
*
* dudect_decaps.c -- dudect main loop driving mlkem.Decapsulate
* through the cgo bridge in decaps_ct.go.
*
* DECAPS IS THE MOST CT-CRITICAL ML-KEM ROUTINE.
*
* CT POPULATION (canonical FO-K framing):
* class A: a fixed VALID ciphertext (accepts in Decaps's FO check)
* class B: caller-random bytes (almost-always invalid → FO implicit
* rejection branch via the rejection seed)
*
* The CT property is that valid and invalid ciphertexts must take
* indistinguishable time. Any class A vs class B timing difference
* is a textbook FO-K timing leak.
*/
#define DUDECT_IMPLEMENTATION
#include "dudect/src/dudect.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern int mlkem_decaps_ct_setup(void);
extern size_t mlkem_decaps_ct_pool_size(void);
extern size_t mlkem_decaps_ct_input_size(void);
extern void mlkem_decaps_ct(uint8_t *data);
extern int mlkem_decaps_ct_copy_valid_ct(uint8_t *dst);
static size_t g_chunk_size = 0;
static uint8_t *g_valid_ct = NULL;
void prepare_inputs(dudect_config_t *cfg, uint8_t *input_data, uint8_t *classes) {
for (size_t i = 0; i < cfg->number_measurements; i++) {
classes[i] = randombit();
uint8_t *slot = input_data + (size_t)i * cfg->chunk_size;
if (classes[i] == 0) {
// class A: the canonical valid ciphertext
memcpy(slot, g_valid_ct, cfg->chunk_size);
} else {
// class B: random bytes → exercises FO implicit-rejection
randombytes(slot, cfg->chunk_size);
}
}
}
uint8_t do_one_computation(uint8_t *data) {
mlkem_decaps_ct(data);
uint8_t acc = 0;
for (size_t i = 0; i < g_chunk_size; i++) acc ^= data[i];
return acc;
}
int main(int argc, char **argv) {
(void)argc;
(void)argv;
int rc = mlkem_decaps_ct_setup();
if (rc != 0) {
fprintf(stderr, "mlkem_decaps_ct_setup failed: rc=%d\n", rc);
return 1;
}
g_chunk_size = mlkem_decaps_ct_input_size();
if (g_chunk_size == 0) {
fprintf(stderr, "mlkem_decaps_ct: setup returned 0 ciphertext size\n");
return 1;
}
g_valid_ct = (uint8_t *)malloc(g_chunk_size);
if (g_valid_ct == NULL) {
fprintf(stderr, "mlkem_decaps_ct: out of memory\n");
return 1;
}
if (mlkem_decaps_ct_copy_valid_ct(g_valid_ct) != 0) {
fprintf(stderr, "mlkem_decaps_ct: failed to copy reference ciphertext\n");
return 1;
}
const char *env_samples = getenv("DUDECT_SAMPLES");
const char *env_batches = getenv("DUDECT_MAX_BATCHES");
size_t samples = env_samples ? (size_t)atoll(env_samples) : 10000;
size_t batches = env_batches ? (size_t)atoll(env_batches) : 100;
dudect_config_t cfg = {
.chunk_size = g_chunk_size,
.number_measurements = samples,
};
dudect_ctx_t ctx;
dudect_init(&ctx, &cfg);
dudect_state_t state = DUDECT_NO_LEAKAGE_EVIDENCE_YET;
size_t batch_count = 0;
while (state == DUDECT_NO_LEAKAGE_EVIDENCE_YET && batch_count < batches) {
state = dudect_main(&ctx);
batch_count++;
if (batch_count % 10 == 0) {
fprintf(stderr, "[batch %zu/%zu]\n", batch_count, batches);
}
}
dudect_free(&ctx);
if (state == DUDECT_LEAKAGE_FOUND) {
fprintf(stderr, "VERDICT: leakage detected in mlkem.Decaps\n");
return 2;
}
fprintf(stdout, "VERDICT: no leakage detected within budget\n");
return 0;
}