1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
//! HQC KEM object sizes (bytes) shared across the workspace.
//!
//! This module is the **single source of truth** for wire-format lengths used by
//! `lib-q-hqc` (`HqcParams` key/ciphertext sizes) and `lib-q-core` (`SecurityConstants`).
//!
//! # Serialized KEM secret key
//!
//! Matches `lib-q-hqc` `HqcKemSecretKey::as_bytes`:
//! `ek_pke` ‖ `dk_pke` ‖ `sigma` ‖ `seed_kem`.
//!
//! NIST reference `CRYPTO_SECRETKEYBYTES` in some packages used a shorter packing; this workspace
//! uses the layout above consistently, so `HQC*_SECRET_KEY_BYTES` here
//! is the length of that serialization, not necessarily the historical API constant.
/// PKE decryption key seed (`dk_pke`) length in bytes (`u8; 32` in `HqcPkeSecretKey`).
pub const PKE_DK_SEED_BYTES: usize = 32;
/// PKE encryption key seed (`seed_ek`) length in bytes — the fixed-size prefix of the public key.
pub const PKE_EK_SEED_BYTES: usize = 32;
/// `sigma` length (`PARAM_SECURITY_BYTES`) for HQC-128.
///
/// `PARAM_SECURITY_BYTES` is **per level** in the HQC v5.0.0 reference — 16/24/32 for
/// HQC-128/192/256 — and the vendored upstream `intermediates_values` dumps confirm it (`sigma`
/// and `m` are 16/24/32 hex-decoded bytes there; `salt` stays 16 at every level). Use
/// [`KEM_SIGMA_BYTES_192`] / [`KEM_SIGMA_BYTES_256`] for the higher levels rather than assuming
/// this one is universal.
pub const KEM_SIGMA_BYTES: usize = 16;
/// `sigma` length (`PARAM_SECURITY_BYTES`) for HQC-192.
pub const KEM_SIGMA_BYTES_192: usize = 24;
/// `sigma` length (`PARAM_SECURITY_BYTES`) for HQC-256.
pub const KEM_SIGMA_BYTES_256: usize = 32;
/// KEM seed length (`seed_kem` in `HqcKemSecretKey`) as held **in memory**.
///
/// This is 48, not 32, because the NIST KAT generator's `seed` is 48 bytes and this workspace
/// stores it whole. Upstream's own `seed_kem` is 32 bytes and only 32 are written to the wire —
/// use [`KEM_SEED_KEM_WIRE_BYTES`] for anything that computes a serialized length. Substituting
/// this constant into a wire-length formula silently overstates every HQC secret key by 16 bytes.
pub const KEM_SEED_KEM_BYTES: usize = 48;
/// `seed_kem` length **on the wire**, per upstream `SEED_BYTES`
/// (`reference/hqc/src/common/kem.c:63-67` writes exactly this many bytes as the final field).
///
/// Deliberately a separate constant from [`KEM_SEED_KEM_BYTES`]: the in-memory field is wider, and
/// conflating the two is a 16-byte length error that a round-trip test cannot catch, because both
/// serializer and parser would agree on the wrong size.
pub const KEM_SEED_KEM_WIRE_BYTES: usize = 32;
/// Byte length of a serialized KEM public key (`seed_ek` ‖ `s`) for parameter `N` (in bits), per
/// the HQC v5.0.0 (2025-08-22) specification. `PUBLIC_KEY_BYTES` must always equal this — see the
/// `params.rs` conformance test that checks it against `VEC_N_SIZE_BYTES` directly.
pub const
/// Byte length of a serialized KEM secret key for a given `ek_pke` / public key prefix size and
/// `sigma` length (`PARAM_SECURITY_BYTES`, which is per-level: 16/24/32).
pub const
/// Upstream HQC `CRYPTO_SECRETKEYBYTES`: `ek_pke` ‖ `dk_pke`(32) ‖ `sigma`(K) ‖ `seed_kem`(32).
///
/// Matches the v5.0.0 reference exactly at all three levels — 2321 / 4602 / 7333, cross-checked
/// against `src/common/hqc-{1,3,5}/api.h` and the write order in
/// `reference/hqc/src/common/kem.c:63-67` (`crypto_kem_keypair`).
///
/// BREAKING CHANGE (card `t_e3ac1c87`): this previously returned
/// `PKE_DK_SEED_BYTES + sigma_len + public_key_len` — a DIFFERENT field order
/// (`dk_pke ‖ sigma ‖ ek_pke`) that also omitted `seed_kem` entirely, giving 2289 / 4570 / 7301.
/// Both faults are fixed here, so the value grows by 32 at every level and the byte order changes.
/// A stored secret key produced by the old encoding cannot be re-parsed by the new one; it must be
/// re-serialized field-by-field, since this is a reordering, not an append.
///
/// Two stale numbers previously documented here are corrected for the record: the deltas were
/// written as `-32/-40/-48` against upstream. That measurement predates card `t_1558e72f`, which
/// fixed the HQC-192/256 public keys from the round-3 40-byte-`seed_ek` sizes (4522/7245) to the
/// v5.0.0 32-byte ones (4514/7237). With the corrected public keys the shortfall is uniformly
/// `-32` — exactly the omitted `seed_kem` — at every level, which is why restoring that one field
/// closes the divergence completely rather than only at HQC-128.
pub const
// --- HQC-128 (parameter set 1) ---
/// KEM public key length (`CRYPTO_PUBLICKEYBYTES` / `seed_ek` ‖ `s`).
pub const HQC128_PUBLIC_KEY_BYTES: usize = kem_public_key_bytes; // 2241
/// KEM ciphertext length (`CRYPTO_CIPHERTEXTBYTES`).
pub const HQC128_CIPHERTEXT_BYTES: usize = 4433;
/// Serialized KEM secret key length (see [`kem_secret_key_serialized_len`]).
pub const HQC128_SECRET_KEY_BYTES: usize =
kem_secret_key_serialized_len;
/// NIST KEM secret key wire length (`dk_pke` ‖ `sigma` ‖ `ek_pke`).
pub const HQC128_NIST_SECRET_KEY_BYTES: usize =
kem_nist_secret_key_bytes;
// --- HQC-192 (parameter set 3) ---
// NOTE (fix for card t_1558e72f): this was previously the literal `4522`, which is the HQC
// round-3 (2020 submission) size — that submission used a 40-byte `seed_ek` (40 + 4482 = 4522).
// HQC-192 was never migrated to the v5.0.0 (2025-08-22) 32-byte-seed format (HQC-128 was);
// deriving it from `kem_public_key_bytes` makes that drift structurally impossible to repeat.
// BREAKING CHANGE: this constant changes from 4522 to 4514. It is absorbed into `hash_h` during
// KEM encapsulation (`lib-q-hqc/src/hqc_kem.rs`), so the ciphertext and shared secret also change
// for HQC-192 — peers on lib-q-types <=0.0.10 will not interoperate. At-rest keys convert
// losslessly by truncation: the dropped 8 bytes are provably all-zero padding, so
// `pk_new = pk_old[..4514]` recovers the correct value from any previously stored key.
pub const HQC192_PUBLIC_KEY_BYTES: usize = kem_public_key_bytes; // 4514
pub const HQC192_CIPHERTEXT_BYTES: usize = 8978;
pub const HQC192_SECRET_KEY_BYTES: usize =
kem_secret_key_serialized_len;
pub const HQC192_NIST_SECRET_KEY_BYTES: usize =
kem_nist_secret_key_bytes;
// --- HQC-256 (parameter set 5) ---
// NOTE (fix for card t_1558e72f): same round-3-vs-v5.0.0 seed-size drift as HQC-192, see above.
// BREAKING CHANGE: 7245 -> 7237, with the same ciphertext/shared-secret and truncation-migration
// consequences as HQC-192 (`pk_new = pk_old[..7237]`).
pub const HQC256_PUBLIC_KEY_BYTES: usize = kem_public_key_bytes; // 7237
pub const HQC256_CIPHERTEXT_BYTES: usize = 14421;
pub const HQC256_SECRET_KEY_BYTES: usize =
kem_secret_key_serialized_len;
pub const HQC256_NIST_SECRET_KEY_BYTES: usize =
kem_nist_secret_key_bytes;