Skip to content

Commit 8f3270c

Browse files
authored
hqc-kem: updates include NicsTr fixes (#353)
Closes #352 --------- Signed-off-by: Mike Lodder <redmike7@gmail.com>
1 parent 72c2764 commit 8f3270c

22 files changed

Lines changed: 3289 additions & 422 deletions

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ commonly used in transport encryption protocols (e.g. [TLS]) and hybrid cryptosy
1616
|----------------------|---------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|--------------------|
1717
| [`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 |
1818
| [`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 |
19+
| [`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 |
1920
| [`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 |
2021
| [`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 |
2122

hqc-kem/Cargo.toml

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,32 @@ repository = "https://github.com/RustCrypto/KEMs"
1111
version = "0.1.0"
1212

1313
[features]
14-
default = ["kgen", "ecap", "dcap"]
14+
default = ["kgen", "ecap", "dcap", "std"]
15+
std = ["alloc"]
16+
alloc = ["pkcs8?/alloc"]
1517
kgen = []
1618
ecap = []
1719
dcap = []
1820
kem = ["dep:kem_traits", "dep:hybrid-array", "dep:typenum", "kgen", "ecap", "dcap"]
19-
pkcs8 = ["dep:const-oid", "dep:pkcs8"]
21+
pkcs8 = ["dep:const-oid", "dep:pkcs8", "alloc", "kgen"]
2022
pem = ["pkcs8/pem"]
21-
alloc = ["pkcs8?/alloc"]
2223
serde = ["dep:serdect", "dep:serde"]
2324

2425
[dependencies]
2526
const-oid = { version = "0.10", optional = true }
26-
hex = "0.4"
2727
hybrid-array = { version = "0.4.10", optional = true, features = ["extra-sizes"] }
28-
kem_traits = { package = "kem", version = "0.3.0-rc.6", optional = true }
28+
kem_traits = { package = "kem", version = "0.3", optional = true }
2929
pkcs8 = { version = "0.11", optional = true, default-features = false }
30-
rand = "0.10"
30+
ctutils = { version = "0.4", default-features = false }
31+
rand = { version = "0.10", default-features = false }
3132
sha3 = { version = "0.12", default-features = false }
3233
shake = { version = "0.1", default-features = false }
33-
serdect = { version = "0.4", optional = true }
34-
serde = { version = "1", features = ["derive"], optional = true }
35-
subtle = "2"
36-
thiserror = "2"
34+
serdect = { version = "0.4", optional = true, default-features = false }
35+
serde = { version = "1", features = ["derive"], optional = true, default-features = false }
36+
subtle = { version = "2", default-features = false }
37+
thiserror = { version = "2", default-features = false }
3738
typenum = { version = "1", optional = true }
38-
zeroize = { version = "1", features = ["derive"] }
39+
zeroize = { version = "1", features = ["derive"], default-features = false }
3940

4041
[dev-dependencies]
4142
criterion = "0.7"

hqc-kem/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ HQC uses concatenated Reed-Solomon + Reed-Muller error correction with the Fujis
1313
- [NIST FIPS 207 (HQC)](https://csrc.nist.gov/pubs/fips/207/ipd) - HQC Initial Public Draft
1414
- [NIST Post-Quantum Cryptography](https://csrc.nist.gov/projects/post-quantum-cryptography) - NIST PQC project page
1515
- [HQC Official Site](https://pqc-hqc.org/) - Reference implementations, specifications, and KAT vectors
16-
- [HQC v5.0.0 Specification](https://pqc-hqc.org/doc/hqc-spec-2025-02-10.pdf) - Full specification document
16+
- [HQC v5.0.0 Specification](https://pqc-hqc.org/doc/hqc-spec-2025-08-22.pdf) - Full specification document
17+
- 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
1718

1819
## Security Levels
1920

hqc-kem/kat/hqc-1.rsp

Lines changed: 697 additions & 4 deletions
Large diffs are not rendered by default.

hqc-kem/kat/hqc-3.rsp

Lines changed: 697 additions & 4 deletions
Large diffs are not rendered by default.

hqc-kem/kat/hqc-5.rsp

Lines changed: 697 additions & 4 deletions
Large diffs are not rendered by default.

hqc-kem/src/code.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
/// Concatenated code: Reed-Solomon then Reed-Muller.
2-
use crate::params::HqcParameters;
2+
use crate::params::{HqcParameters, MAX_N1};
33
use crate::reed_muller;
44
use crate::reed_solomon;
55

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

1314
/// Decode codeword: RM decode then RS decode.
1415
pub(crate) fn code_decode(m: &mut [u8], em: &[u64], p: &HqcParameters) {
15-
let mut tmp = vec![0u8; p.n1];
16-
reed_muller::reed_muller_decode(&mut tmp, em, p);
17-
reed_solomon::reed_solomon_decode(m, &mut tmp, p);
16+
let mut tmp = [0u8; MAX_N1];
17+
let tmp = &mut tmp[..p.n1];
18+
reed_muller::reed_muller_decode(tmp, em, p);
19+
reed_solomon::reed_solomon_decode(m, tmp, p);
1820
}

hqc-kem/src/fft.rs

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,20 @@ fn radix(f0: &mut [u16], f1: &mut [u16], f: &[u16], m_f: usize) {
7272

7373
/// Radix conversion for larger sizes.
7474
fn radix_big(f0: &mut [u16], f1: &mut [u16], f: &[u16], m_f: usize) {
75+
// m_f <= fft parameter (max 5), so n <= 8; buffers bounded accordingly.
7576
let n = 1usize << (m_f - 2);
76-
let mut q = vec![0u16; 2 * n + 1];
77-
let mut r = vec![0u16; 4 * n];
78-
let mut q0 = vec![0u16; n];
79-
let mut q1 = vec![0u16; n];
80-
let mut r0 = vec![0u16; n];
81-
let mut r1 = vec![0u16; n];
77+
let mut q = [0u16; 17];
78+
let q = &mut q[..2 * n + 1];
79+
let mut r = [0u16; 32];
80+
let r = &mut r[..4 * n];
81+
let mut q0 = [0u16; 8];
82+
let q0 = &mut q0[..n];
83+
let mut q1 = [0u16; 8];
84+
let q1 = &mut q1[..n];
85+
let mut r0 = [0u16; 8];
86+
let r0 = &mut r0[..n];
87+
let mut r1 = [0u16; 8];
88+
let r1 = &mut r1[..n];
8289

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

92-
radix(&mut q0, &mut q1, &q, m_f - 1);
93-
radix(&mut r0, &mut r1, &r, m_f - 1);
99+
radix(q0, q1, q, m_f - 1);
100+
radix(r0, r1, r, m_f - 1);
94101

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

126+
// m_f <= 5 throughout the recursion, so half_size <= 16.
119127
let half_size = 1 << (m_f - 1);
120-
let mut f0 = vec![0u16; half_size];
121-
let mut f1 = vec![0u16; half_size];
128+
let mut f0 = [0u16; 16];
129+
let f0 = &mut f0[..half_size];
130+
let mut f1 = [0u16; 16];
131+
let f1 = &mut f1[..half_size];
122132

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

133143
// Step 3: radix
134-
radix(&mut f0, &mut f1, f, m_f);
144+
radix(f0, f1, f, m_f);
135145

136-
// Step 4: compute gammas and deltas
137-
let mut gammas = vec![0u16; m];
138-
let mut deltas = vec![0u16; m];
146+
// Step 4: compute gammas and deltas (m <= PARAM_M - 1 = 7)
147+
let mut gammas = [0u16; 8];
148+
let gammas = &mut gammas[..m];
149+
let mut deltas = [0u16; 8];
150+
let deltas = &mut deltas[..m];
139151
let inv_beta_m = gf_inverse(betas[m - 1]);
140152
for i in 0..m - 1 {
141153
gammas[i] = gf_mul(betas[i], inv_beta_m);
142154
deltas[i] = gf_square(gammas[i]) ^ gammas[i];
143155
}
144156

145-
// Compute gamma sums
146-
let mut gammas_sums = vec![0u16; 1 << (m - 1)];
147-
compute_subset_sums(&mut gammas_sums, &gammas, m - 1);
157+
// Compute gamma sums (1 << (m - 1) <= 64)
158+
let mut gammas_sums = [0u16; 64];
159+
let gammas_sums = &mut gammas_sums[..1 << (m - 1)];
160+
compute_subset_sums(gammas_sums, gammas, m - 1);
148161

149-
// Step 5: recurse
162+
// Step 5: recurse (k <= 64)
150163
let k = 1usize << (m - 1);
151-
let mut u = vec![0u16; k];
152-
fft_rec(
153-
&mut u,
154-
&mut f0,
155-
f_coeffs.div_ceil(2),
156-
m - 1,
157-
m_f - 1,
158-
&deltas,
159-
);
164+
let mut u = [0u16; 64];
165+
let u = &mut u[..k];
166+
fft_rec(u, f0, f_coeffs.div_ceil(2), m - 1, m_f - 1, deltas);
160167

161168
if f_coeffs <= 3 {
162169
// f1 is constant
@@ -167,8 +174,9 @@ fn fft_rec(w: &mut [u16], f: &mut [u16], f_coeffs: usize, m: usize, m_f: usize,
167174
w[k + i] = w[i] ^ f1[0];
168175
}
169176
} else {
170-
let mut v = vec![0u16; k];
171-
fft_rec(&mut v, &mut f1, f_coeffs / 2, m - 1, m_f - 1, &deltas);
177+
let mut v = [0u16; 64];
178+
let v = &mut v[..k];
179+
fft_rec(v, f1, f_coeffs / 2, m - 1, m_f - 1, deltas);
172180

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

202+
// fft_param <= 5, so fft_size <= 32 and half <= 16.
194203
let fft_size = 1 << fft_param;
195-
let mut f_padded = vec![0u16; fft_size];
204+
let mut f_padded = [0u16; 32];
205+
let f_padded = &mut f_padded[..fft_size];
196206
f_padded[..f_coeffs.min(fft_size)].copy_from_slice(&f[..f_coeffs.min(fft_size)]);
197207

198208
let half = fft_size >> 1;
199-
let mut f0 = vec![0u16; half];
200-
let mut f1 = vec![0u16; half];
201-
radix(&mut f0, &mut f1, &f_padded, fft_param);
209+
let mut f0 = [0u16; 16];
210+
let f0 = &mut f0[..half];
211+
let mut f1 = [0u16; 16];
212+
let f1 = &mut f1[..half];
213+
radix(f0, f1, f_padded, fft_param);
202214

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

208220
let k = 1usize << (PARAM_M - 1);
209-
let mut u = vec![0u16; k];
210-
let mut v = vec![0u16; k];
221+
let mut u = [0u16; 1 << (PARAM_M - 1)];
222+
let mut v = [0u16; 1 << (PARAM_M - 1)];
211223

212224
fft_rec(
213225
&mut u,
214-
&mut f0,
226+
f0,
215227
f_coeffs.div_ceil(2),
216228
PARAM_M - 1,
217229
fft_param - 1,
218230
&deltas,
219231
);
220232
fft_rec(
221233
&mut v,
222-
&mut f1,
234+
f1,
223235
f_coeffs / 2,
224236
PARAM_M - 1,
225237
fft_param - 1,

0 commit comments

Comments
 (0)