/*
* QR-NSP Volcanic Edition — Temporal Jitter Shaping
* Module 4: Timing-based covert channel implementation
*
* Signal chain (TX):
* payload → FEC(1/3) → Manchester → spread(Gold code) → delay schedule
*
* Signal chain (RX):
* timestamps → IPAT → preamble correlate → despread → Manchester → FEC → payload
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "qrnsp_jitter.h"
#include "mlkem_params.h" /* mlkem_hash_h for seeded PRNG */
#include <string.h>
#include <math.h>
/* ═════════════════════════════════════════════
* PRNG: xorshift128+ (fast, deterministic from seed)
* ═════════════════════════════════════════════ */
static inline uint64_t
xorshift128p(uint64_t state[2])
{
uint64_t s1 = state[0];
uint64_t s0 = state[1];
state[0] = s0;
s1 ^= s1 << 23;
s1 ^= s1 >> 17;
s1 ^= s0;
s1 ^= s0 >> 26;
state[1] = s1;
return s0 + s1;
}
static void
prng_seed(uint64_t state[2], const uint8_t seed[32], const char *domain)
{
/* Derive PRNG seed: SHA3-256(seed || domain) */
uint8_t buf[64];
size_t dlen = strlen(domain);
memcpy(buf, seed, 32);
memcpy(buf + 32, domain, dlen < 32 ? dlen : 32);
uint8_t h[32];
mlkem_hash_h(h, buf, 32 + (dlen < 32 ? dlen : 32));
memcpy(&state[0], h, 8);
memcpy(&state[1], h + 8, 8);
/* Ensure non-zero */
if (state[0] == 0) state[0] = 1;
if (state[1] == 0) state[1] = 1;
memset(buf, 0, sizeof(buf));
memset(h, 0, sizeof(h));
}
/* ═════════════════════════════════════════════
* Gaussian Random (Box-Muller transform)
*
* Returns samples from N(0, 1).
* Multiply by σ and add μ for N(μ, σ²).
* ═════════════════════════════════════════════ */
static double
gaussian_sample(jitter_tx_t *tx)
{
if (tx->has_spare) {
tx->has_spare = 0;
return tx->spare_val;
}
double u, v, s;
do {
u = ((double)(xorshift128p(tx->rng_state) >> 11) / (double)(1ULL << 53)) * 2.0 - 1.0;
v = ((double)(xorshift128p(tx->rng_state) >> 11) / (double)(1ULL << 53)) * 2.0 - 1.0;
s = u * u + v * v;
} while (s >= 1.0 || s == 0.0);
double mul = sqrt(-2.0 * log(s) / s);
tx->spare_val = v * mul;
tx->has_spare = 1;
return u * mul;
}
/* ═════════════════════════════════════════════
* Gold Code Generation
*
* Gold codes are preferred sequences for CDMA — low cross-correlation.
* Generate from two m-sequences (LFSR) of length 2^7 - 1 = 127.
*
* LFSR polynomials for degree 7:
* G1: x^7 + x^3 + 1 (taps: 7, 3)
* G2: x^7 + x^3 + x^2 + x + 1 (taps: 7, 3, 2, 1)
*
* Gold code = G1 ⊕ G2 (with relative shift derived from seed)
* ═════════════════════════════════════════════ */
static void
generate_gold_code(uint8_t code[JITTER_CHIP_SEQ_LEN], const uint8_t seed[32])
{
/* Derive shift from seed */
uint8_t h[32];
mlkem_hash_h(h, seed, 32);
uint32_t shift = (h[0] | ((uint32_t)h[1] << 8)) % JITTER_CHIP_SEQ_LEN;
/* Generate m-sequence G1: x^7 + x^3 + 1 */
uint8_t g1[JITTER_CHIP_SEQ_LEN];
uint8_t lfsr1 = 0x7F; /* All ones initial state */
for (int i = 0; i < JITTER_CHIP_SEQ_LEN; i++) {
g1[i] = lfsr1 & 1;
uint8_t fb = ((lfsr1 >> 6) ^ (lfsr1 >> 2)) & 1;
lfsr1 = (lfsr1 >> 1) | (fb << 6);
}
/* Generate m-sequence G2: x^7 + x^3 + x^2 + x + 1 */
uint8_t g2[JITTER_CHIP_SEQ_LEN];
uint8_t lfsr2 = 0x7F;
for (int i = 0; i < JITTER_CHIP_SEQ_LEN; i++) {
g2[i] = lfsr2 & 1;
uint8_t fb = ((lfsr2 >> 6) ^ (lfsr2 >> 2) ^ (lfsr2 >> 1) ^ lfsr2) & 1;
lfsr2 = (lfsr2 >> 1) | (fb << 6);
}
/* Gold code = G1 ⊕ G2(shifted) */
for (int i = 0; i < JITTER_CHIP_SEQ_LEN; i++) {
code[i] = g1[i] ^ g2[(i + shift) % JITTER_CHIP_SEQ_LEN];
}
memset(h, 0, sizeof(h));
}
/* ═════════════════════════════════════════════
* Barker-13 Preamble
*
* Barker codes have optimal autocorrelation properties —
* sharp peak at zero lag, low sidelobes. Barker-13 is the
* longest known Barker code.
*
* Pattern: +1 +1 +1 +1 +1 −1 −1 +1 +1 −1 +1 −1 +1
* (as bits: 1 1 1 1 1 0 0 1 1 0 1 0 1)
* ═════════════════════════════════════════════ */
static const uint8_t barker13[13] = {1,1,1,1,1,0,0,1,1,0,1,0,1};
/* Extended preamble: Barker-13 + 3 guard bits (0,0,0) */
static const uint8_t preamble_bits[JITTER_PREAMBLE_BITS] = {
1,1,1,1,1,0,0,1,1,0,1,0,1, 0,0,0
};
/* ═════════════════════════════════════════════
* Manchester Encoding
*
* bit 0 → 01
* bit 1 → 10
*
* Guarantees transitions for clock recovery.
* Doubles the bit rate but eliminates baseline wander.
* ═════════════════════════════════════════════ */
static uint32_t
manchester_encode(uint8_t *out, const uint8_t *bits, uint32_t nbits)
{
uint32_t j = 0;
for (uint32_t i = 0; i < nbits; i++) {
if (bits[i]) {
out[j++] = 1;
out[j++] = 0;
} else {
out[j++] = 0;
out[j++] = 1;
}
}
return j;
}
static uint32_t
manchester_decode(uint8_t *out, const uint8_t *encoded, uint32_t nencoded)
{
uint32_t j = 0;
for (uint32_t i = 0; i + 1 < nencoded; i += 2) {
/* 10 → 1, 01 → 0, else → error (use majority) */
if (encoded[i] == 1 && encoded[i+1] == 0)
out[j++] = 1;
else if (encoded[i] == 0 && encoded[i+1] == 1)
out[j++] = 0;
else
out[j++] = encoded[i]; /* Best guess on error */
}
return j;
}
/* ═════════════════════════════════════════════
* FEC: Rate 1/3 Repetition Code
*
* Simple but effective for timing channels where BER is high.
* Each bit is repeated 3 times; majority vote on decode.
* ═════════════════════════════════════════════ */
static uint32_t
fec_encode(uint8_t *out, const uint8_t *bits, uint32_t nbits)
{
uint32_t j = 0;
for (uint32_t i = 0; i < nbits; i++) {
for (int r = 0; r < JITTER_FEC_RATE_DEN; r++)
out[j++] = bits[i];
}
return j;
}
static uint32_t
fec_decode(uint8_t *out, const uint8_t *encoded, uint32_t nencoded)
{
uint32_t j = 0;
for (uint32_t i = 0; i + 2 < nencoded; i += JITTER_FEC_RATE_DEN) {
/* Majority vote */
int sum = 0;
for (int r = 0; r < JITTER_FEC_RATE_DEN; r++)
sum += encoded[i + r];
out[j++] = (sum > JITTER_FEC_RATE_DEN / 2) ? 1 : 0;
}
return j;
}
/* ═════════════════════════════════════════════
* Spread Spectrum: chip a bit stream with Gold code
*
* Each data bit is XOR'd with CHIPS_PER_BIT chips from the
* Gold code. Receiver correlates to despread.
* ═════════════════════════════════════════════ */
static uint32_t
spread(uint8_t *out, const uint8_t *bits, uint32_t nbits,
const uint8_t gold[JITTER_CHIP_SEQ_LEN])
{
uint32_t j = 0;
uint32_t gold_idx = 0;
for (uint32_t i = 0; i < nbits; i++) {
for (int c = 0; c < JITTER_CHIPS_PER_BIT; c++) {
out[j++] = bits[i] ^ gold[gold_idx % JITTER_CHIP_SEQ_LEN];
gold_idx++;
}
}
return j;
}
/*
* Despread: correlate received chips against Gold code,
* recover original bits via majority decision per chip group.
*/
static uint32_t
despread(uint8_t *out, const uint8_t *chips, uint32_t nchips,
const uint8_t gold[JITTER_CHIP_SEQ_LEN])
{
uint32_t j = 0;
uint32_t gold_idx = 0;
for (uint32_t i = 0; i + JITTER_CHIPS_PER_BIT <= nchips;
i += JITTER_CHIPS_PER_BIT) {
int sum = 0;
for (int c = 0; c < JITTER_CHIPS_PER_BIT; c++) {
/* XOR with Gold code to despread, then count */
uint8_t despread_chip = chips[i + c] ^ gold[gold_idx % JITTER_CHIP_SEQ_LEN];
sum += despread_chip;
gold_idx++;
}
out[j++] = (sum > JITTER_CHIPS_PER_BIT / 2) ? 1 : 0;
}
return j;
}
/* ═════════════════════════════════════════════
* Bytes ↔ Bits conversion
* ═════════════════════════════════════════════ */
static uint32_t
bytes_to_bits(uint8_t *bits, const uint8_t *bytes, size_t nbytes)
{
uint32_t j = 0;
for (size_t i = 0; i < nbytes; i++) {
for (int b = 7; b >= 0; b--)
bits[j++] = (bytes[i] >> b) & 1;
}
return j;
}
static size_t
bits_to_bytes(uint8_t *bytes, const uint8_t *bits, uint32_t nbits)
{
size_t nbytes = nbits / 8;
memset(bytes, 0, nbytes);
for (uint32_t i = 0; i < nbytes * 8; i++) {
bytes[i / 8] |= (bits[i] & 1) << (7 - (i % 8));
}
return nbytes;
}
/* ═════════════════════════════════════════════
* TX Implementation
* ═════════════════════════════════════════════ */
int
jitter_tx_init(jitter_tx_t *tx, const uint8_t seed[32])
{
memset(tx, 0, sizeof(*tx));
tx->state = JITTER_STATE_IDLE;
generate_gold_code(tx->gold_code, seed);
prng_seed(tx->rng_state, seed, "jitter_tx_noise");
return 0;
}
int
jitter_tx_load(jitter_tx_t *tx, const uint8_t *payload, size_t len)
{
if (len > JITTER_MAX_PAYLOAD || len == 0)
return -1;
/*
* Encoding pipeline:
* 1. Add length prefix (1 byte)
* 2. Convert to bits
* 3. FEC encode (×3)
* 4. Manchester encode (×2)
* 5. Spread with Gold code (×CHIPS_PER_BIT)
* 6. Prepend preamble chips
*/
/* Step 1: length prefix + payload */
uint8_t framed[JITTER_MAX_PAYLOAD + 1];
framed[0] = (uint8_t)len;
memcpy(framed + 1, payload, len);
size_t framed_len = len + 1;
/* Step 2: bytes → bits */
uint8_t data_bits[8 * (JITTER_MAX_PAYLOAD + 1)];
uint32_t ndata_bits = bytes_to_bits(data_bits, framed, framed_len);
/* Step 3: FEC encode */
uint8_t fec_bits[8 * (JITTER_MAX_PAYLOAD + 1) * JITTER_FEC_RATE_DEN];
uint32_t nfec = fec_encode(fec_bits, data_bits, ndata_bits);
/* Step 4: Manchester encode */
uint8_t manc_bits[8 * (JITTER_MAX_PAYLOAD + 1) * JITTER_FEC_RATE_DEN * 2];
uint32_t nmanc = manchester_encode(manc_bits, fec_bits, nfec);
/* Step 5: Spread spectrum */
uint8_t data_chips[65536]; /* Generous buffer */
uint32_t ndata_chips = spread(data_chips, manc_bits, nmanc, tx->gold_code);
/* Step 6: Preamble chips */
uint8_t preamble_manc[JITTER_PREAMBLE_BITS * 2];
uint32_t npre_manc = manchester_encode(preamble_manc, preamble_bits,
JITTER_PREAMBLE_BITS);
uint8_t preamble_chips[JITTER_PREAMBLE_BITS * 2 * JITTER_CHIPS_PER_BIT];
uint32_t npre_chips = spread(preamble_chips, preamble_manc, npre_manc,
tx->gold_code);
/* Assemble: preamble || data */
if (npre_chips + ndata_chips > sizeof(tx->chip_stream))
return -1;
memcpy(tx->chip_stream, preamble_chips, npre_chips);
memcpy(tx->chip_stream + npre_chips, data_chips, ndata_chips);
tx->chip_count = npre_chips + ndata_chips;
tx->chip_index = 0;
tx->gold_phase = 0;
tx->state = JITTER_STATE_PREAMBLE;
return 0;
}
uint64_t
jitter_tx_next_delay(jitter_tx_t *tx)
{
if (tx->state == JITTER_STATE_COMPLETE || tx->state == JITTER_STATE_IDLE)
return 0;
if (tx->chip_index >= tx->chip_count) {
tx->state = JITTER_STATE_COMPLETE;
return 0;
}
/* Determine base delay based on current chip value */
uint8_t chip = tx->chip_stream[tx->chip_index];
double base_us = (double)JITTER_T_BASE_US;
if (chip) {
base_us += (double)JITTER_T_DELTA_US;
}
/* Add Gaussian jitter for stealth */
double noise_us = gaussian_sample(tx) * (double)JITTER_SIGMA_US;
double delay_us = base_us + noise_us;
/* Clamp to prevent negative or excessively large delays */
if (delay_us < (double)(JITTER_T_BASE_US / 2))
delay_us = (double)(JITTER_T_BASE_US / 2);
if (delay_us > (double)(JITTER_T_BASE_US + JITTER_T_DELTA_US) * 3.0)
delay_us = (double)(JITTER_T_BASE_US + JITTER_T_DELTA_US) * 3.0;
tx->chip_index++;
/* Update state transition */
if (tx->chip_index >= JITTER_PREAMBLE_CHIPS &&
tx->state == JITTER_STATE_PREAMBLE) {
tx->state = JITTER_STATE_DATA;
}
/* Convert to nanoseconds */
return (uint64_t)(delay_us * 1000.0);
}
void
jitter_tx_packet_sent(jitter_tx_t *tx, uint64_t timestamp_ns)
{
tx->last_tx_ns = timestamp_ns;
tx->packets_sent++;
}
double
jitter_tx_progress(const jitter_tx_t *tx)
{
if (tx->chip_count == 0) return 0.0;
return (double)tx->chip_index / (double)tx->chip_count;
}
void
jitter_tx_destroy(jitter_tx_t *tx)
{
memset(tx, 0, sizeof(*tx));
}
/* ═════════════════════════════════════════════
* RX Implementation
* ═════════════════════════════════════════════ */
int
jitter_rx_init(jitter_rx_t *rx, const uint8_t seed[32])
{
memset(rx, 0, sizeof(*rx));
rx->state = JITTER_STATE_IDLE;
generate_gold_code(rx->gold_code, seed);
/* Initialize timing estimates */
rx->estimated_t_base = (double)JITTER_T_BASE_US * 1000.0; /* ns */
rx->estimated_t_delta = (double)JITTER_T_DELTA_US * 1000.0;
rx->estimated_sigma = (double)JITTER_SIGMA_US * 1000.0;
return 0;
}
/*
* Classify an IPAT sample as chip 0 or chip 1.
*
* Decision boundary: T_base + T_delta/2
* Below → chip 0, Above → chip 1
*/
static uint8_t
classify_ipat(int64_t ipat_ns, double t_base_ns, double t_delta_ns)
{
double threshold = t_base_ns + t_delta_ns / 2.0;
return (ipat_ns > (int64_t)threshold) ? 1 : 0;
}
/*
* Preamble correlation detector.
*
* Cross-correlate the received chip stream with the known
* preamble pattern. A strong peak indicates synchronization.
*
* Returns the offset of the peak, or -1 if no strong peak found.
*/
static int
detect_preamble(const uint8_t *chips, uint32_t nchips,
const uint8_t gold[JITTER_CHIP_SEQ_LEN])
{
/* Generate expected preamble chip pattern */
uint8_t preamble_manc[JITTER_PREAMBLE_BITS * 2];
uint32_t npre_manc = manchester_encode(preamble_manc, preamble_bits,
JITTER_PREAMBLE_BITS);
uint8_t expected[JITTER_PREAMBLE_BITS * 2 * JITTER_CHIPS_PER_BIT];
uint32_t nexpected = spread(expected, preamble_manc, npre_manc, gold);
if (nchips < nexpected)
return -1;
/* Sliding correlation */
int best_offset = -1;
double best_corr = 0.0;
double threshold = 0.7 * (double)nexpected; /* 70% match required */
uint32_t search_range = nchips - nexpected;
if (search_range > 1000) search_range = 1000; /* Limit search */
for (uint32_t off = 0; off < search_range; off++) {
double corr = 0.0;
for (uint32_t i = 0; i < nexpected; i++) {
/* +1 for match, -1 for mismatch */
corr += (chips[off + i] == expected[i]) ? 1.0 : -1.0;
}
if (corr > best_corr) {
best_corr = corr;
best_offset = (int)off;
}
}
if (best_corr >= threshold)
return best_offset;
return -1;
}
int
jitter_rx_feed(jitter_rx_t *rx, uint64_t timestamp_ns)
{
/* Store arrival time */
uint32_t idx = rx->arrival_head;
rx->arrival_times[idx % 32768] = timestamp_ns;
rx->arrival_head++;
rx->arrival_count++;
rx->packets_received++;
/* Need at least 2 arrivals to compute IPAT */
if (rx->arrival_count < 2)
return 0;
/* Compute IPAT from last two arrivals */
uint64_t prev = rx->arrival_times[(idx - 1) % 32768];
int64_t ipat = (int64_t)(timestamp_ns - prev);
/* Filter: ignore unreasonable IPATs (< 1ms or > 100ms) */
if (ipat < 1000000 || ipat > 100000000)
return 0;
/* Store IPAT */
if (rx->ipat_count < 16384) {
rx->ipat_samples[rx->ipat_count] = ipat;
/* Classify chip */
rx->raw_chips[rx->ipat_count] = classify_ipat(
ipat, rx->estimated_t_base, rx->estimated_t_delta);
rx->ipat_count++;
rx->raw_chip_count = rx->ipat_count;
}
/* ── Preamble detection phase ── */
if (!rx->preamble_detected && rx->raw_chip_count >= JITTER_PREAMBLE_CHIPS + 32) {
int pre_off = detect_preamble(rx->raw_chips, rx->raw_chip_count,
rx->gold_code);
if (pre_off >= 0) {
rx->preamble_detected = 1;
rx->preamble_offset = (uint32_t)pre_off;
rx->state = JITTER_STATE_DATA;
/* Refine timing estimates from preamble region */
/* (Adaptive: compute mean IPAT for known 0s and 1s) */
return 1;
}
return 0;
}
/* ── Data reception phase ── */
if (rx->preamble_detected && rx->state == JITTER_STATE_DATA) {
/* Data chips start after preamble */
uint32_t data_start = rx->preamble_offset + JITTER_PREAMBLE_CHIPS;
if (rx->raw_chip_count <= data_start)
return 0; /* Still in preamble region */
uint32_t data_chips_available = rx->raw_chip_count - data_start;
/* Try to decode what we have so far */
/* Need at minimum: 1 byte (length prefix) =
* 8 bits × 3 (FEC) × 2 (Manchester) × 8 (chips) = 384 chips */
if (data_chips_available < 384)
return 0;
/* Despread */
uint8_t despread_bits[4096];
uint32_t ndespread = despread(despread_bits,
rx->raw_chips + data_start,
data_chips_available,
rx->gold_code);
/* Manchester decode */
uint8_t manc_decoded[2048];
uint32_t nmanc = manchester_decode(manc_decoded, despread_bits, ndespread);
/* FEC decode */
uint8_t fec_decoded[1024];
uint32_t nfec = fec_decode(fec_decoded, manc_decoded, nmanc);
/* Convert bits → bytes */
if (nfec < 8)
return 0; /* Not enough for length byte */
uint8_t decoded_bytes[JITTER_MAX_PAYLOAD + 1];
size_t nbytes = bits_to_bytes(decoded_bytes, fec_decoded, nfec);
if (nbytes < 1)
return 0;
/* First byte is length */
uint8_t claimed_len = decoded_bytes[0];
if (claimed_len == 0 || claimed_len > JITTER_MAX_PAYLOAD)
return 0; /* Keep waiting — probably incomplete */
/* Check if we have enough chips for the full message */
uint32_t needed_data_bits = (claimed_len + 1) * 8; /* +1 for length prefix */
uint32_t needed_chips = needed_data_bits * JITTER_FEC_RATE_DEN * 2 *
JITTER_CHIPS_PER_BIT;
if (data_chips_available < needed_chips)
return 0; /* Still receiving */
/* We have enough — final decode */
if (nbytes >= (size_t)(claimed_len + 1)) {
memcpy(rx->decoded_data, decoded_bytes + 1, claimed_len);
rx->decoded_len = claimed_len;
rx->state = JITTER_STATE_COMPLETE;
return 2; /* Message complete */
}
return 0; /* Keep collecting */
}
return 0;
}
int
jitter_rx_get_data(const jitter_rx_t *rx,
uint8_t *out, size_t out_cap,
size_t *out_len)
{
if (rx->state != JITTER_STATE_COMPLETE)
return -1;
if (rx->decoded_len > out_cap)
return -1;
memcpy(out, rx->decoded_data, rx->decoded_len);
*out_len = rx->decoded_len;
return 0;
}
void
jitter_rx_reset(jitter_rx_t *rx)
{
uint8_t gold_save[JITTER_CHIP_SEQ_LEN];
memcpy(gold_save, rx->gold_code, sizeof(gold_save));
double t_base = rx->estimated_t_base;
double t_delta = rx->estimated_t_delta;
double t_sigma = rx->estimated_sigma;
memset(rx, 0, sizeof(*rx));
memcpy(rx->gold_code, gold_save, sizeof(gold_save));
rx->estimated_t_base = t_base;
rx->estimated_t_delta = t_delta;
rx->estimated_sigma = t_sigma;
rx->state = JITTER_STATE_IDLE;
}
void
jitter_rx_destroy(jitter_rx_t *rx)
{
memset(rx, 0, sizeof(*rx));
}