Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ commonly used in transport encryption protocols (e.g. [TLS]) and hybrid cryptosy
|----------------------|---------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|--------------------|
| [`dhkem`](./dhkem) | [![crates.io](https://img.shields.io/crates/v/dhkem.svg?logo=rust)](https://crates.io/crates/dhkem) | [![Documentation](https://docs.rs/dhkem/badge.svg)](https://docs.rs/dhkem) | Diffie-Hellman KEM |
| [`frodo‑kem`](./frodo-kem) | [![crates.io](https://img.shields.io/crates/v/frodo-kem.svg?logo=rust)](https://crates.io/crates/frodo-kem) | [![Documentation](https://docs.rs/frodo-kem/badge.svg)](https://docs.rs/frodo-kem) | Frodo KEM |
| [`hqc‑kem`](./hqc-kem) | [![crates.io](https://img.shields.io/crates/v/hqc-kem.svg?logo=rust)](https://crates.io/crates/hqc-kem) | [![Documentation](https://docs.rs/hqc-kem/badge.svg)](https://docs.rs/hqc-kem) | Hamming Quasi-Cyclic KEM |
| [`ml‑kem`](./ml-kem) | [![crates.io](https://img.shields.io/crates/v/ml-kem.svg?logo=rust)](https://crates.io/crates/ml-kem) | [![Documentation](https://docs.rs/ml-kem/badge.svg)](https://docs.rs/ml-kem) | Module Lattice KEM |
| [`x‑wing`](./x-wing) | [![crates.io](https://img.shields.io/crates/v/x-wing.svg?logo=rust)](https://crates.io/crates/x-wing) | [![Documentation](https://docs.rs/x-wing/badge.svg)](https://docs.rs/x-wing) | Hybrid PQ KEM |

Expand Down
23 changes: 12 additions & 11 deletions hqc-kem/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,32 @@ repository = "https://github.com/RustCrypto/KEMs"
version = "0.1.0"

[features]
default = ["kgen", "ecap", "dcap"]
default = ["kgen", "ecap", "dcap", "std"]
std = ["alloc"]
alloc = ["pkcs8?/alloc"]
kgen = []
ecap = []
dcap = []
kem = ["dep:kem_traits", "dep:hybrid-array", "dep:typenum", "kgen", "ecap", "dcap"]
pkcs8 = ["dep:const-oid", "dep:pkcs8"]
pkcs8 = ["dep:const-oid", "dep:pkcs8", "alloc", "kgen"]
pem = ["pkcs8/pem"]
alloc = ["pkcs8?/alloc"]
serde = ["dep:serdect", "dep:serde"]

[dependencies]
const-oid = { version = "0.10", optional = true }
hex = "0.4"
hybrid-array = { version = "0.4.10", optional = true, features = ["extra-sizes"] }
kem_traits = { package = "kem", version = "0.3.0-rc.6", optional = true }
kem_traits = { package = "kem", version = "0.3", optional = true }
pkcs8 = { version = "0.11", optional = true, default-features = false }
rand = "0.10"
ctutils = { version = "0.4", default-features = false }
rand = { version = "0.10", default-features = false }
sha3 = { version = "0.12", default-features = false }
shake = { version = "0.1", default-features = false }
serdect = { version = "0.4", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
subtle = "2"
thiserror = "2"
serdect = { version = "0.4", optional = true, default-features = false }
serde = { version = "1", features = ["derive"], optional = true, default-features = false }
subtle = { version = "2", default-features = false }
thiserror = { version = "2", default-features = false }
typenum = { version = "1", optional = true }
zeroize = { version = "1", features = ["derive"] }
zeroize = { version = "1", features = ["derive"], default-features = false }

[dev-dependencies]
criterion = "0.7"
Expand Down
3 changes: 2 additions & 1 deletion hqc-kem/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ HQC uses concatenated Reed-Solomon + Reed-Muller error correction with the Fujis
- [NIST FIPS 207 (HQC)](https://csrc.nist.gov/pubs/fips/207/ipd) - HQC Initial Public Draft
- [NIST Post-Quantum Cryptography](https://csrc.nist.gov/projects/post-quantum-cryptography) - NIST PQC project page
- [HQC Official Site](https://pqc-hqc.org/) - Reference implementations, specifications, and KAT vectors
- [HQC v5.0.0 Specification](https://pqc-hqc.org/doc/hqc-spec-2025-02-10.pdf) - Full specification document
- [HQC v5.0.0 Specification](https://pqc-hqc.org/doc/hqc-spec-2025-08-22.pdf) - Full specification document
- Tracks the [official reference implementation](https://gitlab.com/pqc-hqc/hqc) at commit `161cd4f` (2026-02-10, post-v5.0.0 sampling update); all 100 official KAT vectors per level are verified

## Security Levels

Expand Down
701 changes: 697 additions & 4 deletions hqc-kem/kat/hqc-1.rsp

Large diffs are not rendered by default.

701 changes: 697 additions & 4 deletions hqc-kem/kat/hqc-3.rsp

Large diffs are not rendered by default.

701 changes: 697 additions & 4 deletions hqc-kem/kat/hqc-5.rsp

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions hqc-kem/src/code.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
/// Concatenated code: Reed-Solomon then Reed-Muller.
use crate::params::HqcParameters;
use crate::params::{HqcParameters, MAX_N1};
use crate::reed_muller;
use crate::reed_solomon;

/// Encode message: RS encode then RM encode.
pub(crate) fn code_encode(em: &mut [u64], m: &[u8], p: &HqcParameters) {
let mut tmp = vec![0u8; p.n1];
reed_solomon::reed_solomon_encode(&mut tmp, m, p);
reed_muller::reed_muller_encode(em, &tmp, p);
let mut tmp = [0u8; MAX_N1];
let tmp = &mut tmp[..p.n1];
reed_solomon::reed_solomon_encode(tmp, m, p);
reed_muller::reed_muller_encode(em, tmp, p);
}

/// Decode codeword: RM decode then RS decode.
pub(crate) fn code_decode(m: &mut [u8], em: &[u64], p: &HqcParameters) {
let mut tmp = vec![0u8; p.n1];
reed_muller::reed_muller_decode(&mut tmp, em, p);
reed_solomon::reed_solomon_decode(m, &mut tmp, p);
let mut tmp = [0u8; MAX_N1];
let tmp = &mut tmp[..p.n1];
reed_muller::reed_muller_decode(tmp, em, p);
reed_solomon::reed_solomon_decode(m, tmp, p);
}
86 changes: 49 additions & 37 deletions hqc-kem/src/fft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,20 @@ fn radix(f0: &mut [u16], f1: &mut [u16], f: &[u16], m_f: usize) {

/// Radix conversion for larger sizes.
fn radix_big(f0: &mut [u16], f1: &mut [u16], f: &[u16], m_f: usize) {
// m_f <= fft parameter (max 5), so n <= 8; buffers bounded accordingly.
let n = 1usize << (m_f - 2);
let mut q = vec![0u16; 2 * n + 1];
let mut r = vec![0u16; 4 * n];
let mut q0 = vec![0u16; n];
let mut q1 = vec![0u16; n];
let mut r0 = vec![0u16; n];
let mut r1 = vec![0u16; n];
let mut q = [0u16; 17];
let q = &mut q[..2 * n + 1];
let mut r = [0u16; 32];
let r = &mut r[..4 * n];
let mut q0 = [0u16; 8];
let q0 = &mut q0[..n];
let mut q1 = [0u16; 8];
let q1 = &mut q1[..n];
let mut r0 = [0u16; 8];
let r0 = &mut r0[..n];
let mut r1 = [0u16; 8];
let r1 = &mut r1[..n];

q[..n].copy_from_slice(&f[3 * n..4 * n]);
q[n..2 * n].copy_from_slice(&f[3 * n..4 * n]);
Expand All @@ -89,8 +96,8 @@ fn radix_big(f0: &mut [u16], f1: &mut [u16], f: &[u16], m_f: usize) {
r[n + i] ^= q[i];
}

radix(&mut q0, &mut q1, &q, m_f - 1);
radix(&mut r0, &mut r1, &r, m_f - 1);
radix(q0, q1, q, m_f - 1);
radix(r0, r1, r, m_f - 1);

f0[..n].copy_from_slice(&r0[..n]);
f0[n..2 * n].copy_from_slice(&q0[..n]);
Expand All @@ -116,9 +123,12 @@ fn fft_rec(w: &mut [u16], f: &mut [u16], f_coeffs: usize, m: usize, m_f: usize,
return;
}

// m_f <= 5 throughout the recursion, so half_size <= 16.
let half_size = 1 << (m_f - 1);
let mut f0 = vec![0u16; half_size];
let mut f1 = vec![0u16; half_size];
let mut f0 = [0u16; 16];
let f0 = &mut f0[..half_size];
let mut f1 = [0u16; 16];
let f1 = &mut f1[..half_size];

// Step 2: twist by beta_m
if betas[m - 1] != 1 {
Expand All @@ -131,32 +141,29 @@ fn fft_rec(w: &mut [u16], f: &mut [u16], f_coeffs: usize, m: usize, m_f: usize,
}

// Step 3: radix
radix(&mut f0, &mut f1, f, m_f);
radix(f0, f1, f, m_f);

// Step 4: compute gammas and deltas
let mut gammas = vec![0u16; m];
let mut deltas = vec![0u16; m];
// Step 4: compute gammas and deltas (m <= PARAM_M - 1 = 7)
let mut gammas = [0u16; 8];
let gammas = &mut gammas[..m];
let mut deltas = [0u16; 8];
let deltas = &mut deltas[..m];
let inv_beta_m = gf_inverse(betas[m - 1]);
for i in 0..m - 1 {
gammas[i] = gf_mul(betas[i], inv_beta_m);
deltas[i] = gf_square(gammas[i]) ^ gammas[i];
}

// Compute gamma sums
let mut gammas_sums = vec![0u16; 1 << (m - 1)];
compute_subset_sums(&mut gammas_sums, &gammas, m - 1);
// Compute gamma sums (1 << (m - 1) <= 64)
let mut gammas_sums = [0u16; 64];
let gammas_sums = &mut gammas_sums[..1 << (m - 1)];
compute_subset_sums(gammas_sums, gammas, m - 1);

// Step 5: recurse
// Step 5: recurse (k <= 64)
let k = 1usize << (m - 1);
let mut u = vec![0u16; k];
fft_rec(
&mut u,
&mut f0,
f_coeffs.div_ceil(2),
m - 1,
m_f - 1,
&deltas,
);
let mut u = [0u16; 64];
let u = &mut u[..k];
fft_rec(u, f0, f_coeffs.div_ceil(2), m - 1, m_f - 1, deltas);

if f_coeffs <= 3 {
// f1 is constant
Expand All @@ -167,8 +174,9 @@ fn fft_rec(w: &mut [u16], f: &mut [u16], f_coeffs: usize, m: usize, m_f: usize,
w[k + i] = w[i] ^ f1[0];
}
} else {
let mut v = vec![0u16; k];
fft_rec(&mut v, &mut f1, f_coeffs / 2, m - 1, m_f - 1, &deltas);
let mut v = [0u16; 64];
let v = &mut v[..k];
fft_rec(v, f1, f_coeffs / 2, m - 1, m_f - 1, deltas);

// Step 6: combine
w[k..k + k].copy_from_slice(&v[..k]);
Expand All @@ -191,35 +199,39 @@ pub(crate) fn fft(w: &mut [u16; 256], f: &[u16], f_coeffs: usize, fft_param: usi
let mut betas_sums = [0u16; 1 << (PARAM_M - 1)];
compute_subset_sums(&mut betas_sums, &betas, PARAM_M - 1);

// fft_param <= 5, so fft_size <= 32 and half <= 16.
let fft_size = 1 << fft_param;
let mut f_padded = vec![0u16; fft_size];
let mut f_padded = [0u16; 32];
let f_padded = &mut f_padded[..fft_size];
f_padded[..f_coeffs.min(fft_size)].copy_from_slice(&f[..f_coeffs.min(fft_size)]);

let half = fft_size >> 1;
let mut f0 = vec![0u16; half];
let mut f1 = vec![0u16; half];
radix(&mut f0, &mut f1, &f_padded, fft_param);
let mut f0 = [0u16; 16];
let f0 = &mut f0[..half];
let mut f1 = [0u16; 16];
let f1 = &mut f1[..half];
radix(f0, f1, f_padded, fft_param);

let mut deltas = [0u16; PARAM_M - 1];
for i in 0..(PARAM_M - 1) {
deltas[i] = gf_square(betas[i]) ^ betas[i];
}

let k = 1usize << (PARAM_M - 1);
let mut u = vec![0u16; k];
let mut v = vec![0u16; k];
let mut u = [0u16; 1 << (PARAM_M - 1)];
let mut v = [0u16; 1 << (PARAM_M - 1)];

fft_rec(
&mut u,
&mut f0,
f0,
f_coeffs.div_ceil(2),
PARAM_M - 1,
fft_param - 1,
&deltas,
);
fft_rec(
&mut v,
&mut f1,
f1,
f_coeffs / 2,
PARAM_M - 1,
fft_param - 1,
Expand Down
Loading