← All Research
[Research Publication — April 2026]

Every HQC Encoder Ships the Same Power-Analysis Vulnerability. Here Is the Fix.

The Reed-Muller encoder inside every HQC implementation leaks individual message bits with 96.9% accuracy from a single power trace. Not because the code is badly written — because the algorithm forces it to test message bits. PermNet-RM removes that test entirely by reformulating encoding as a fixed-topology butterfly network where message bits never control any operation.

Author Bader Alissaei
Date April 2026
Affiliation VaultBytes Innovation Ltd
Date April 2026
Topic Reed-Muller · HQC · Side-Channel · Power Analysis · Post-Quantum
Keywords GF(2) Zeta Transform · Constant-Time · HQC · RM(1,m)

What We Found

No cryptography background needed to understand this

01

HQC is the newest post-quantum standard

NIST selected HQC for standardisation in March 2025. It will protect encrypted communications against future quantum computers. At its core, HQC wraps every message in a Reed-Muller error-correcting code before transmitting it. That encoding step is where the problem lives.

02

The encoder leaks the secret message through power consumption

Every HQC implementation uses the same encoder. It works by testing each secret bit one at a time, and XOR-ing a generator row into the output if the bit is 1. This means the chip draws different amounts of power depending on each bit. A researcher plugged a power meter into a microcontroller and recovered individual message bits with 96.9% accuracy from a single measurement.

03

Fixing the code doesn't fix the algorithm

Previous defences — masking the decoder, shuffling operation order, rewriting the loop as an FFT — all failed because the root problem is mathematical: the algorithm inherently needs to know which bits are set. Any code that expresses that will leak. The only fix is to stop the algorithm from testing bits at all.

04

PermNet-RM: message bits set state once, never touched again

We discovered that Reed-Muller encoding is identical to a GF(2) zeta transform applied to a fixed indicator vector. This transform has a butterfly decomposition where every stage is a fixed XOR-and-shift — the wiring never changes. Message bits enter as the initial register state and are never read again. Zero branching. Zero message-dependent operations. Zero timing spread across all 256 inputs.

// The core insight

The standard encoder asks "is bit i set?" for every generator row. Any way you write that check will leak. PermNet-RM never asks the question. Each message bit is placed at a fixed position in a 128-bit register, and then a predetermined sequence of XOR operations propagates it to all the right output positions — like a sorting network, but for bits. The encoder is provably constant-time at the binary level across all six GCC optimisation levels tested. It is a direct drop-in for reed_muller_encode() with zero changes to calling code.

// How the encoder works — old vs. new
Old encoder
Read bit i
tests secret ⚠
mask = -(bit & 1)
HW ∈ {0, 64} leaks
cw ^= mask & G[i]
power varies ⚠

PermNet-RM
Inject all bits
fixed positions, once
Butterfly stage ×7
reg ^= (reg & mask) << k
Output codeword
0 timing spread ✓

The Numbers

0
Timing spread across
all 256 possible inputs
96.9%
Bit-recovery accuracy
of the Jeon attack on old encoder
21 ns
Total overhead per full
HQC-128 encapsulation
256 + 512
Input values exhaustively
verified correct (RM(1,7) + RM(1,8))
6
GCC optimisation levels
confirmed branch-free
// Encoder throughput on Intel Core Ultra 9 275HX — gcc 15.2.0 -O3 -march=native (higher is better)
Branched (naive)
644 Mops/s · 4.77 cyc
LEAKY
BIT0MASK (reference)
605 Mops/s · 5.08 cyc
LEAKY
PermNet-RM
480 Mops/s · 6.41 cyc
SAFE

PermNet-RM costs 1.33 additional cycles per encode compared to the reference encoder. Across all 16 encoder invocations per HQC-128 encapsulation, that is 21 nanoseconds — unmeasurable against the 200–500 µs total encapsulation time.

// Per-input timing spread across all 256 inputs at -O3 — zero spread = constant time
BIT0MASK
min 4 / max 5 — spread: 1 cycle
LEAKS
Branched
min 4 / max 5 — spread: 1 cycle
LEAKS
PermNet-RM
min 6 / max 6 — spread: 0 cycles
SAFE

Every one of the 256 possible input bytes takes exactly 6 cycles to encode. The BIT0MASK and Branched encoders exhibit 1-cycle spread — confirming data-dependent timing at -O3 on this measurement setup.

Drop-in C implementation — RM(1,7) for HQC-128

Seven butterfly stages. No conditionals. No loops. All masks and shifts are compile-time constants. Exhaustively verified against the generator-matrix encoder across all 256 inputs.

/* Step 1: inject each message bit at its singleton-set position */
uint8_t m = msg[0];
uint64_t lo = (uint64_t)((m >> 0) & 1); /* a0 → pos 0 */
lo ^= (uint64_t)((m >> 1) & 1) << 1; /* a1 → pos 1 */
lo ^= (uint64_t)((m >> 2) & 1) << 2; /* a2 → pos 2 */
lo ^= (uint64_t)((m >> 3) & 1) << 4; /* a3 → pos 4 */
lo ^= (uint64_t)((m >> 4) & 1) << 8; /* a4 → pos 8 */
lo ^= (uint64_t)((m >> 5) & 1) << 16; /* a5 → pos 16 */
lo ^= (uint64_t)((m >> 6) & 1) << 32; /* a6 → pos 32 */
uint64_t hi = (uint64_t)((m >> 7) & 1); /* a7 → pos 64 */

/* Step 2: seven butterfly stages of the GF(2) zeta transform */
lo ^= (lo & 0x5555555555555555ULL) << 1; /* stage 0 */
hi ^= (hi & 0x5555555555555555ULL) << 1;
lo ^= (lo & 0x3333333333333333ULL) << 2; /* stage 1 */
hi ^= (hi & 0x3333333333333333ULL) << 2;
lo ^= (lo & 0x0F0F0F0F0F0F0F0FULL) << 4; /* stage 2 */
hi ^= (hi & 0x0F0F0F0F0F0F0F0FULL) << 4;
lo ^= (lo & 0x00FF00FF00FF00FFULL) << 8; /* stage 3 */
hi ^= (hi & 0x00FF00FF00FF00FFULL) << 8;
lo ^= (lo & 0x0000FFFF0000FFFFULL) << 16; /* stage 4 */
hi ^= (hi & 0x0000FFFF0000FFFFULL) << 16;
lo ^= (lo & 0x00000000FFFFFFFFULL) << 32; /* stage 5 */
hi ^= (hi & 0x00000000FFFFFFFFULL) << 32;
hi ^= lo; /* stage 6 */

/* Step 3: write 128-bit codeword */
memcpy(cdw, &lo, 8); memcpy(cdw + 8, &hi, 8);

Cite This Work

// BibTeX @misc{alissaei2026permnetrm,
  author       = {Bader Alissaei},
  title        = {PermNet-RM: A Provably Constant-Time Reed-Muller Encoder
                   for HQC via {GF}(2) Zeta-Transform Butterfly Decomposition},
  year         = {2026},
  note         = {Available on request from b@vaultbytes.com},
}